Skip to content

Commit bb7a8aa

Browse files
committed
fix(release): repair local governance command routing
1 parent 3296ed1 commit bb7a8aa

10 files changed

Lines changed: 210 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ This repository's current stable release line is `2.x`.
77
Current stable release notes live in `docs/releases/`.
88
This top-level changelog preserves the foundational `0.x` milestones and points older iteration history to `docs/releases/legacy-pre-0.1-history.md`.
99

10+
## [2.1.1] - 2026-04-29
11+
12+
Patch release for the local governance command router and bridge token JSON
13+
output. See [docs/releases/v2.1.1.md](docs/releases/v2.1.1.md) for full
14+
details.
15+
16+
### Fixed
17+
18+
- route all `codex auth` local governance and bridge subcommands through the
19+
multi-auth wrapper instead of falling through to the official Codex CLI
20+
- return valid JSON for `codex auth bridge token list --json` when no local
21+
bridge tokens are configured
22+
1023
## [2.1.0] - 2026-04-29
1124

1225
Stable release for local usage governance and the local bridge. See [docs/releases/v2.1.0.md](docs/releases/v2.1.0.md) for full details.

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -359,9 +359,9 @@ codex auth doctor --json
359359

360360
## Release Notes
361361

362-
- Current stable: [docs/releases/v2.1.0.md](docs/releases/v2.1.0.md)
363-
- Previous stable: [docs/releases/v2.0.2.md](docs/releases/v2.0.2.md)
364-
- Earlier stable: [docs/releases/v2.0.1.md](docs/releases/v2.0.1.md)
362+
- Current stable: [docs/releases/v2.1.1.md](docs/releases/v2.1.1.md)
363+
- Previous stable: [docs/releases/v2.1.0.md](docs/releases/v2.1.0.md)
364+
- Earlier stable: [docs/releases/v2.0.2.md](docs/releases/v2.0.2.md)
365365
- Earlier stable: [docs/releases/v1.3.2.md](docs/releases/v1.3.2.md)
366366
- Stable archive: [docs/releases/v1.3.1.md](docs/releases/v1.3.1.md)
367367
- Full release archive: [docs/README.md#release-history](docs/README.md#release-history)

docs/README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ Public documentation for `codex-multi-auth`.
3232

3333
| Document | Focus |
3434
| --- | --- |
35-
| [releases/v2.1.0.md](releases/v2.1.0.md) | Current stable release notes |
36-
| [releases/v2.0.2.md](releases/v2.0.2.md) | Prior stable release notes |
35+
| [releases/v2.1.1.md](releases/v2.1.1.md) | Current stable release notes |
36+
| [releases/v2.1.0.md](releases/v2.1.0.md) | Prior stable release notes |
37+
| [releases/v2.0.2.md](releases/v2.0.2.md) | Earlier stable release notes |
3738
| [releases/v2.0.1.md](releases/v2.0.1.md) | Earlier stable release notes |
3839
| [releases/v2.0.0.md](releases/v2.0.0.md) | Earlier stable release notes |
3940
| [releases/v1.3.2.md](releases/v1.3.2.md) | Stable archive |
@@ -81,8 +82,9 @@ Public documentation for `codex-multi-auth`.
8182
| [reference/storage-paths.md](reference/storage-paths.md) | Canonical and compatibility storage paths |
8283
| [reference/public-api.md](reference/public-api.md) | Public API stability and semver contract |
8384
| [reference/error-contracts.md](reference/error-contracts.md) | CLI, JSON, and helper error semantics |
84-
| [releases/v2.1.0.md](releases/v2.1.0.md) | Current stable release notes |
85-
| [releases/v2.0.2.md](releases/v2.0.2.md) | Prior stable release notes |
85+
| [releases/v2.1.1.md](releases/v2.1.1.md) | Current stable release notes |
86+
| [releases/v2.1.0.md](releases/v2.1.0.md) | Prior stable release notes |
87+
| [releases/v2.0.2.md](releases/v2.0.2.md) | Earlier stable release notes |
8688
| [releases/v2.0.1.md](releases/v2.0.1.md) | Earlier stable release notes |
8789
| [releases/v0.1.0-beta.0.md](releases/v0.1.0-beta.0.md) | Archived prerelease reference |
8890
| [Release history](#release-history) | Stable, previous, and archived release notes |

docs/releases/v2.1.1.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Release v2.1.1
2+
3+
Release line: `stable`
4+
5+
This patch release fixes two command-surface regressions found during local
6+
installed-package verification of `v2.1.0`.
7+
8+
## Scope
9+
10+
- Package version prepared for publish: `2.1.1`
11+
- Previous stable release: `v2.1.0`
12+
- Semver rationale: patch fixes for wrapper command routing and JSON output.
13+
14+
## Fixed
15+
16+
- `codex auth usage`, `codex auth account`, `codex auth budget`,
17+
`codex auth bridge`, `codex auth integrations`, `codex auth models`, and
18+
`codex auth monitor` now route through the local multi-auth command handler
19+
instead of falling through to the official Codex CLI.
20+
- `codex auth bridge token list --json` now returns valid JSON in the empty
21+
token-store state.
22+
- Bridge token JSON output omits token hashes and only returns plaintext tokens
23+
from create or rotate actions.
24+
25+
## Validation
26+
27+
- `npm test -- test/codex-manager-bridge-command.test.ts test/codex-routing.test.ts`
28+
- `npm test -- test/local-bridge.test.ts test/local-client-tokens.test.ts test/integration-generators.test.ts test/codex-manager-integrations-command.test.ts test/codex-manager-monitor-command.test.ts test/codex-manager-usage-command.test.ts test/model-capability-matrix.test.ts test/account-policy.test.ts test/routing-profiles.test.ts`
29+
- `npm run lint`
30+
- `npm run typecheck`
31+
- `npm run build`
32+
- installed local CLI command matrix for usage, models, monitor, config,
33+
budget, account policy, integrations, bridge token list, features, and verify
34+
paths
35+
36+
## Release Notes
37+
38+
- Previous release notes: `docs/releases/v2.1.0.md`

lib/codex-manager/commands/bridge.ts

Lines changed: 86 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
addLocalClientToken,
3+
type LocalClientTokenRecord,
34
loadLocalClientTokenStore,
45
revokeLocalClientToken,
56
rotateLocalClientToken,
@@ -14,14 +15,45 @@ function printBridgeUsage(logInfo: (message: string) => void): void {
1415
logInfo(
1516
[
1617
"Usage:",
17-
" codex auth bridge token create [--label <label>]",
18-
" codex auth bridge token list",
19-
" codex auth bridge token rotate <id>",
20-
" codex auth bridge token revoke <id>",
18+
" codex auth bridge token create [--label <label>] [--json]",
19+
" codex auth bridge token list [--json]",
20+
" codex auth bridge token rotate <id> [--json]",
21+
" codex auth bridge token revoke <id> [--json]",
2122
].join("\n"),
2223
);
2324
}
2425

26+
function printJson(logInfo: (message: string) => void, value: unknown): void {
27+
logInfo(JSON.stringify(value, null, 2));
28+
}
29+
30+
function publicTokenRecord(token: LocalClientTokenRecord): {
31+
id: string;
32+
label: string;
33+
prefix: string;
34+
createdAt: number;
35+
lastUsedAt: number | null;
36+
revokedAt: number | null;
37+
state: "active" | "revoked";
38+
} {
39+
return {
40+
id: token.id,
41+
label: token.label,
42+
prefix: token.prefix,
43+
createdAt: token.createdAt,
44+
lastUsedAt: token.lastUsedAt,
45+
revokedAt: token.revokedAt,
46+
state: token.revokedAt === null ? "active" : "revoked",
47+
};
48+
}
49+
50+
function consumeJsonFlag(args: string[]): { json: boolean; rest: string[] } {
51+
return {
52+
json: args.includes("--json") || args.includes("-j"),
53+
rest: args.filter((arg) => arg !== "--json" && arg !== "-j"),
54+
};
55+
}
56+
2557
function parseLabel(args: string[]): { ok: true; label?: string } | { ok: false; message: string } {
2658
let label: string | undefined;
2759
for (let i = 0; i < args.length; i += 1) {
@@ -58,19 +90,40 @@ export async function runBridgeCommand(
5890
return 1;
5991
}
6092
if (action === "create") {
61-
const parsed = parseLabel(rest);
93+
const { json, rest: actionArgs } = consumeJsonFlag(rest);
94+
const parsed = parseLabel(actionArgs);
6295
if (!parsed.ok) {
6396
logError(parsed.message);
6497
return 1;
6598
}
6699
const created = await addLocalClientToken({ label: parsed.label });
100+
if (json) {
101+
printJson(logInfo, {
102+
command: "bridge token create",
103+
token: publicTokenRecord(created.record),
104+
plainToken: created.plainToken,
105+
});
106+
return 0;
107+
}
67108
logInfo(`Token id: ${created.record.id}`);
68109
logInfo(`Token prefix: ${created.record.prefix}`);
69110
logInfo(`Token: ${created.plainToken}`);
70111
return 0;
71112
}
72113
if (action === "list") {
114+
const { json, rest: actionArgs } = consumeJsonFlag(rest);
115+
if (actionArgs.length > 0) {
116+
logError(`Unknown bridge token list option: ${actionArgs[0] ?? ""}`);
117+
return 1;
118+
}
73119
const store = await loadLocalClientTokenStore();
120+
if (json) {
121+
printJson(logInfo, {
122+
command: "bridge token list",
123+
tokens: store.tokens.map(publicTokenRecord),
124+
});
125+
return 0;
126+
}
74127
if (store.tokens.length === 0) {
75128
logInfo("No local bridge tokens configured.");
76129
return 0;
@@ -82,32 +135,58 @@ export async function runBridgeCommand(
82135
return 0;
83136
}
84137
if (action === "rotate") {
85-
const id = rest[0];
138+
const { json, rest: actionArgs } = consumeJsonFlag(rest);
139+
const id = actionArgs[0];
86140
if (!id) {
87141
logError("Missing token id");
88142
return 1;
89143
}
144+
if (actionArgs.length > 1) {
145+
logError(`Unknown bridge token rotate option: ${actionArgs[1] ?? ""}`);
146+
return 1;
147+
}
90148
const rotated = await rotateLocalClientToken({ id });
91149
if (!rotated) {
92150
logError("Token not found or already revoked.");
93151
return 1;
94152
}
153+
if (json) {
154+
printJson(logInfo, {
155+
command: "bridge token rotate",
156+
token: publicTokenRecord(rotated.record),
157+
plainToken: rotated.plainToken,
158+
});
159+
return 0;
160+
}
95161
logInfo(`Token id: ${rotated.record.id}`);
96162
logInfo(`Token prefix: ${rotated.record.prefix}`);
97163
logInfo(`Token: ${rotated.plainToken}`);
98164
return 0;
99165
}
100166
if (action === "revoke") {
101-
const id = rest[0];
167+
const { json, rest: actionArgs } = consumeJsonFlag(rest);
168+
const id = actionArgs[0];
102169
if (!id) {
103170
logError("Missing token id");
104171
return 1;
105172
}
173+
if (actionArgs.length > 1) {
174+
logError(`Unknown bridge token revoke option: ${actionArgs[1] ?? ""}`);
175+
return 1;
176+
}
106177
const revoked = await revokeLocalClientToken(id);
107178
if (!revoked) {
108179
logError("Token not found or already revoked.");
109180
return 1;
110181
}
182+
if (json) {
183+
printJson(logInfo, {
184+
command: "bridge token revoke",
185+
id,
186+
revoked: true,
187+
});
188+
return 0;
189+
}
111190
logInfo("Token revoked.");
112191
return 0;
113192
}

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codex-multi-auth",
3-
"version": "2.1.0",
3+
"version": "2.1.1",
44
"description": "Multi-account OAuth manager and codex auth wrapper for the official @openai/codex CLI, with switching, health checks, and recovery tools",
55
"main": "./dist/index.js",
66
"types": "./dist/index.d.ts",

scripts/codex-routing.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,19 @@ const AUTH_SUBCOMMANDS = new Set([
66
"best",
77
"check",
88
"features",
9+
"usage",
910
"verify-flagged",
1011
"verify",
1112
"forecast",
1213
"report",
1314
"fix",
1415
"doctor",
16+
"account",
17+
"budget",
18+
"bridge",
19+
"integrations",
20+
"models",
21+
"monitor",
1522
"rotation",
1623
"why-selected",
1724
"config",

test/codex-manager-bridge-command.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,51 @@ describe("bridge command", () => {
4747
expect(listOutput).toContain("OpenCode active");
4848
expect(listOutput).not.toContain(token);
4949
});
50+
51+
it("prints valid json for an empty token list", async () => {
52+
const logInfo = vi.fn();
53+
expect(
54+
await runBridgeCommand(["token", "list", "--json"], {
55+
logInfo,
56+
logError: vi.fn(),
57+
}),
58+
).toBe(0);
59+
60+
const output = logInfo.mock.calls.map((call) => String(call[0])).join("\n");
61+
expect(JSON.parse(output)).toEqual({
62+
command: "bridge token list",
63+
tokens: [],
64+
});
65+
});
66+
67+
it("omits token hashes from bridge token json output", async () => {
68+
const logInfo = vi.fn();
69+
expect(
70+
await runBridgeCommand(["token", "create", "--label", "Env", "--json"], {
71+
logInfo,
72+
logError: vi.fn(),
73+
}),
74+
).toBe(0);
75+
76+
const created = JSON.parse(String(logInfo.mock.calls[0]?.[0])) as {
77+
plainToken?: string;
78+
token?: Record<string, unknown>;
79+
};
80+
expect(created.plainToken).toMatch(/^cma_local_/);
81+
expect(created.token?.tokenHash).toBeUndefined();
82+
83+
logInfo.mockClear();
84+
expect(
85+
await runBridgeCommand(["token", "list", "--json"], {
86+
logInfo,
87+
logError: vi.fn(),
88+
}),
89+
).toBe(0);
90+
const listed = JSON.parse(String(logInfo.mock.calls[0]?.[0])) as {
91+
tokens?: Array<Record<string, unknown>>;
92+
};
93+
expect(listed.tokens).toHaveLength(1);
94+
expect(listed.tokens?.[0]?.tokenHash).toBeUndefined();
95+
expect(JSON.stringify(listed)).not.toContain(created.plainToken);
96+
});
5097
});

test/codex-routing.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,17 @@ describe("codex routing helpers", () => {
2929
"switch",
3030
"check",
3131
"features",
32+
"usage",
3233
"verify-flagged",
3334
"forecast",
3435
"best",
3536
"report",
37+
"account",
38+
"budget",
39+
"bridge",
40+
"integrations",
41+
"models",
42+
"monitor",
3643
"rotation",
3744
"why-selected",
3845
"verify",

0 commit comments

Comments
 (0)