@getrouter/getrouter-cli 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.mjs CHANGED
@@ -8,7 +8,7 @@ import { randomInt } from "node:crypto";
8
8
  import prompts from "prompts";
9
9
 
10
10
  //#region package.json
11
- var version = "0.1.5";
11
+ var version = "0.1.7";
12
12
 
13
13
  //#endregion
14
14
  //#region src/generated/router/dashboard/v1/index.ts
@@ -29,6 +29,25 @@ function createSubscriptionServiceClient(handler) {
29
29
  });
30
30
  } };
31
31
  }
32
+ function createUsageServiceClient(handler) {
33
+ return { ListUsage(request) {
34
+ const path$1 = `v1/dashboard/usages`;
35
+ const body = null;
36
+ const queryParams = [];
37
+ if (request.pageSize) queryParams.push(`pageSize=${encodeURIComponent(request.pageSize.toString())}`);
38
+ if (request.pageToken) queryParams.push(`pageToken=${encodeURIComponent(request.pageToken.toString())}`);
39
+ let uri = path$1;
40
+ if (queryParams.length > 0) uri += `?${queryParams.join("&")}`;
41
+ return handler({
42
+ path: uri,
43
+ method: "GET",
44
+ body
45
+ }, {
46
+ service: "UsageService",
47
+ method: "ListUsage"
48
+ });
49
+ } };
50
+ }
32
51
  function createConsumerServiceClient(handler) {
33
52
  return {
34
53
  CreateConsumer(request) {
@@ -113,6 +132,26 @@ function createConsumerServiceClient(handler) {
113
132
  }
114
133
  };
115
134
  }
135
+ function createModelServiceClient(handler) {
136
+ return { ListModels(request) {
137
+ const path$1 = `v1/dashboard/models`;
138
+ const body = null;
139
+ const queryParams = [];
140
+ if (request.pageSize) queryParams.push(`pageSize=${encodeURIComponent(request.pageSize.toString())}`);
141
+ if (request.pageToken) queryParams.push(`pageToken=${encodeURIComponent(request.pageToken.toString())}`);
142
+ if (request.filter) queryParams.push(`filter=${encodeURIComponent(request.filter.toString())}`);
143
+ let uri = path$1;
144
+ if (queryParams.length > 0) uri += `?${queryParams.join("&")}`;
145
+ return handler({
146
+ path: uri,
147
+ method: "GET",
148
+ body
149
+ }, {
150
+ service: "ModelService",
151
+ method: "ListModels"
152
+ });
153
+ } };
154
+ }
116
155
  function createAuthServiceClient(handler) {
117
156
  return {
118
157
  Authorize(request) {
@@ -162,45 +201,6 @@ function createAuthServiceClient(handler) {
162
201
  }
163
202
  };
164
203
  }
165
- function createModelServiceClient(handler) {
166
- return { ListModels(request) {
167
- const path$1 = `v1/dashboard/models`;
168
- const body = null;
169
- const queryParams = [];
170
- if (request.pageSize) queryParams.push(`pageSize=${encodeURIComponent(request.pageSize.toString())}`);
171
- if (request.pageToken) queryParams.push(`pageToken=${encodeURIComponent(request.pageToken.toString())}`);
172
- if (request.filter) queryParams.push(`filter=${encodeURIComponent(request.filter.toString())}`);
173
- let uri = path$1;
174
- if (queryParams.length > 0) uri += `?${queryParams.join("&")}`;
175
- return handler({
176
- path: uri,
177
- method: "GET",
178
- body
179
- }, {
180
- service: "ModelService",
181
- method: "ListModels"
182
- });
183
- } };
184
- }
185
- function createUsageServiceClient(handler) {
186
- return { ListUsage(request) {
187
- const path$1 = `v1/dashboard/usages`;
188
- const body = null;
189
- const queryParams = [];
190
- if (request.pageSize) queryParams.push(`pageSize=${encodeURIComponent(request.pageSize.toString())}`);
191
- if (request.pageToken) queryParams.push(`pageToken=${encodeURIComponent(request.pageToken.toString())}`);
192
- let uri = path$1;
193
- if (queryParams.length > 0) uri += `?${queryParams.join("&")}`;
194
- return handler({
195
- path: uri,
196
- method: "GET",
197
- body
198
- }, {
199
- service: "UsageService",
200
- method: "ListUsage"
201
- });
202
- } };
203
- }
204
204
 
205
205
  //#endregion
206
206
  //#region src/core/config/fs.ts
@@ -445,7 +445,7 @@ const generateAuthCode = () => {
445
445
  for (let i = 0; i < 13; i += 1) out += alphabet[randomInt(32)];
446
446
  return out;
447
447
  };
448
- const buildLoginUrl = (authCode) => `https://getrouter.dev/#/a/${authCode}`;
448
+ const buildLoginUrl = (authCode) => `https://getrouter.dev/auth/${authCode}`;
449
449
  const openLoginUrl = async (url) => {
450
450
  try {
451
451
  if (process.platform === "darwin") {
@@ -1014,6 +1014,7 @@ const providerValues = () => ({
1014
1014
  });
1015
1015
  const matchHeader = (line) => line.match(/^\s*\[([^\]]+)\]\s*$/);
1016
1016
  const matchKey = (line) => line.match(/^\s*([A-Za-z0-9_.-]+)\s*=/);
1017
+ const matchProviderValue = (line) => line.match(/^\s*model_provider\s*=\s*(['"]?)([^'"]+)\1\s*(?:#.*)?$/);
1017
1018
  const mergeCodexToml = (content, input) => {
1018
1019
  const updated = [...content.length ? content.split(/\r?\n/) : []];
1019
1020
  const rootValueMap = rootValues(input);
@@ -1074,6 +1075,74 @@ const mergeAuthJson = (data, apiKey) => ({
1074
1075
  ...data,
1075
1076
  OPENAI_API_KEY: apiKey
1076
1077
  });
1078
+ const stripGetrouterProviderSection = (lines) => {
1079
+ const updated = [];
1080
+ let skipSection = false;
1081
+ for (const line of lines) {
1082
+ const headerMatch = matchHeader(line);
1083
+ if (headerMatch) {
1084
+ if ((headerMatch[1]?.trim() ?? "") === PROVIDER_SECTION) {
1085
+ skipSection = true;
1086
+ continue;
1087
+ }
1088
+ skipSection = false;
1089
+ }
1090
+ if (skipSection) continue;
1091
+ updated.push(line);
1092
+ }
1093
+ return updated;
1094
+ };
1095
+ const stripRootKeys = (lines) => {
1096
+ const updated = [];
1097
+ let currentSection = null;
1098
+ for (const line of lines) {
1099
+ const headerMatch = matchHeader(line);
1100
+ if (headerMatch) {
1101
+ currentSection = headerMatch[1]?.trim() ?? null;
1102
+ updated.push(line);
1103
+ continue;
1104
+ }
1105
+ if (currentSection === null) {
1106
+ if (/^\s*model\s*=/.test(line)) continue;
1107
+ if (/^\s*model_reasoning_effort\s*=/.test(line)) continue;
1108
+ if (/^\s*model_provider\s*=/.test(line)) continue;
1109
+ }
1110
+ updated.push(line);
1111
+ }
1112
+ return updated;
1113
+ };
1114
+ const removeCodexConfig = (content) => {
1115
+ const lines = content.length ? content.split(/\r?\n/) : [];
1116
+ let providerIsGetrouter = false;
1117
+ let currentSection = null;
1118
+ for (const line of lines) {
1119
+ const headerMatch = matchHeader(line);
1120
+ if (headerMatch) {
1121
+ currentSection = headerMatch[1]?.trim() ?? null;
1122
+ continue;
1123
+ }
1124
+ if (currentSection !== null) continue;
1125
+ if ((matchProviderValue(line)?.[2]?.trim())?.toLowerCase() === CODEX_PROVIDER) providerIsGetrouter = true;
1126
+ }
1127
+ let updated = stripGetrouterProviderSection(lines);
1128
+ if (providerIsGetrouter) updated = stripRootKeys(updated);
1129
+ const nextContent = updated.join("\n");
1130
+ return {
1131
+ content: nextContent,
1132
+ changed: nextContent !== content
1133
+ };
1134
+ };
1135
+ const removeAuthJson = (data) => {
1136
+ if (!("OPENAI_API_KEY" in data)) return {
1137
+ data,
1138
+ changed: false
1139
+ };
1140
+ const { OPENAI_API_KEY: _ignored, ...rest } = data;
1141
+ return {
1142
+ data: rest,
1143
+ changed: true
1144
+ };
1145
+ };
1077
1146
 
1078
1147
  //#endregion
1079
1148
  //#region src/cmd/codex.ts
@@ -1092,6 +1161,7 @@ const ensureCodexDir = () => {
1092
1161
  fs.mkdirSync(dir, { recursive: true });
1093
1162
  return dir;
1094
1163
  };
1164
+ const resolveCodexDir = () => path.join(os.homedir(), CODEX_DIR);
1095
1165
  const requireInteractive$1 = () => {
1096
1166
  if (!process.stdin.isTTY) throw new Error("Interactive mode required for codex configuration.");
1097
1167
  };
@@ -1107,7 +1177,8 @@ const promptReasoning = async (model) => await fuzzySelect({
1107
1177
  });
1108
1178
  const formatReasoningLabel = (id) => REASONING_CHOICES.find((choice) => choice.id === id)?.label ?? id;
1109
1179
  const registerCodexCommand = (program) => {
1110
- program.command("codex").description("Configure Codex").option("-m, --model <model>", "Set codex model (skips model selection)").action(async (options) => {
1180
+ const codex = program.command("codex").description("Configure Codex");
1181
+ codex.option("-m, --model <model>", "Set codex model (skips model selection)").action(async (options) => {
1111
1182
  requireInteractive$1();
1112
1183
  const model = options.model && options.model.trim().length > 0 ? options.model.trim() : await promptModel();
1113
1184
  if (!model) return;
@@ -1138,6 +1209,28 @@ const registerCodexCommand = (program) => {
1138
1209
  console.log("✅ Updated ~/.codex/config.toml");
1139
1210
  console.log("✅ Updated ~/.codex/auth.json");
1140
1211
  });
1212
+ codex.command("uninstall").description("Remove getrouter Codex configuration").action(() => {
1213
+ const codexDir = resolveCodexDir();
1214
+ const configPath = path.join(codexDir, "config.toml");
1215
+ const authPath = path.join(codexDir, "auth.json");
1216
+ const configExists = fs.existsSync(configPath);
1217
+ const authExists = fs.existsSync(authPath);
1218
+ const configContent = configExists ? readFileIfExists(configPath) : "";
1219
+ const configResult = configExists ? removeCodexConfig(configContent) : null;
1220
+ const authContent = authExists ? fs.readFileSync(authPath, "utf8").trim() : "";
1221
+ const authData = authExists ? authContent ? JSON.parse(authContent) : {} : null;
1222
+ const authResult = authData ? removeAuthJson(authData) : null;
1223
+ if (!configExists) console.log(`ℹ️ ${configPath} not found`);
1224
+ else if (configResult?.changed) {
1225
+ fs.writeFileSync(configPath, configResult.content, "utf8");
1226
+ console.log(`✅ Removed getrouter entries from ${configPath}`);
1227
+ } else console.log(`ℹ️ No getrouter entries in ${configPath}`);
1228
+ if (!authExists) console.log(`ℹ️ ${authPath} not found`);
1229
+ else if (authResult?.changed) {
1230
+ fs.writeFileSync(authPath, JSON.stringify(authResult.data, null, 2));
1231
+ console.log(`✅ Removed getrouter entries from ${authPath}`);
1232
+ } else console.log(`ℹ️ No getrouter entries in ${authPath}`);
1233
+ });
1141
1234
  };
1142
1235
 
1143
1236
  //#endregion
@@ -6,7 +6,7 @@
6
6
 
7
7
  现有 CLI 的 `auth login` 仅提示 OAuth 未就绪。根据最新 auth 流程,CLI 需改为类似 tailscale 的设备码式登录:
8
8
  - CLI 本地生成 `auth_code`(13 位小写 base32)
9
- - 打开浏览器访问 `https://getrouter.dev/#/a/{auth_code}`
9
+ - 打开浏览器访问 `https://getrouter.dev/auth/{auth_code}`
10
10
  - CLI 轮询 `POST /v1/dashboard/auth/authorize`,body `{ code: auth_code }`
11
11
  - 成功返回 `AuthToken` 后写入 `~/.getrouter/auth.json`
12
12
 
@@ -42,7 +42,7 @@
42
42
 
43
43
  ```
44
44
  To authenticate, visit:
45
- https://getrouter.dev/#/a/<auth_code>
45
+ https://getrouter.dev/auth/<auth_code>
46
46
  Waiting for confirmation...
47
47
  ```
48
48
 
@@ -222,7 +222,7 @@ export const generateAuthCode = () => {
222
222
  };
223
223
 
224
224
  export const buildLoginUrl = (authCode: string) =>
225
- `https://getrouter.dev/#/a/${authCode}`;
225
+ `https://getrouter.dev/auth/${authCode}`;
226
226
 
227
227
  export const openLoginUrl = async (url: string) => {
228
228
  try {
@@ -0,0 +1,34 @@
1
+ # Codex Uninstall Design
2
+
3
+ ## Goal
4
+ Add a `getrouter codex uninstall` subcommand that removes only getrouter-managed Codex configuration, without touching unrelated user settings.
5
+
6
+ ## Scope
7
+ - Remove the `[model_providers.getrouter]` section from `~/.codex/config.toml`.
8
+ - Remove `model`, `model_reasoning_effort`, and `model_provider` only when `model_provider` is set to `"getrouter"`.
9
+ - Remove `OPENAI_API_KEY` from `~/.codex/auth.json`.
10
+ - Preserve all other keys, providers, comments, and formatting as much as possible.
11
+
12
+ ## CLI Shape
13
+ - Keep the existing `getrouter codex` interactive flow unchanged.
14
+ - Add `getrouter codex uninstall` as a non-interactive subcommand.
15
+ - The uninstall action reports per-file status: removed, no-op, or missing.
16
+
17
+ ## Data Flow
18
+ 1. Resolve `~/.codex` via `os.homedir()`.
19
+ 2. For each file:
20
+ - If missing, log and continue.
21
+ - Read content, apply a removal helper, and write back only if changed.
22
+ 3. Abort with a clear error if parsing fails; avoid partial writes.
23
+
24
+ ## Implementation Notes
25
+ - Extend `src/core/setup/codex.ts` with:
26
+ - `removeCodexConfig(content)` for TOML line removal.
27
+ - `removeAuthJson(data)` for JSON key removal.
28
+ - Update `src/cmd/codex.ts` to register the uninstall subcommand.
29
+
30
+ ## Testing
31
+ - Add command tests for uninstall in `tests/cmd/codex.test.ts`:
32
+ - Removes getrouter entries but keeps other providers/keys.
33
+ - No-op when getrouter entries do not exist.
34
+ - Root keys only removed when `model_provider == "getrouter"`.
@@ -0,0 +1,252 @@
1
+ # Codex Uninstall Implementation Plan
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** Add `getrouter codex uninstall` to remove getrouter-managed Codex config entries while preserving user data.
6
+
7
+ **Architecture:** Extend `src/core/setup/codex.ts` with removal helpers for TOML and JSON. Wire a new `uninstall` subcommand in `src/cmd/codex.ts` that reads, cleans, and conditionally writes the files with clear status output.
8
+
9
+ **Tech Stack:** TypeScript, Commander.js, Vitest, Node fs/path/os.
10
+
11
+ ### Task 1: Add uninstall command tests
12
+
13
+ **Files:**
14
+ - Modify: `tests/cmd/codex.test.ts`
15
+
16
+ **Step 1: Write the failing tests**
17
+
18
+ ```ts
19
+ it("uninstall removes getrouter entries but keeps others", async () => {
20
+ const dir = makeDir();
21
+ process.env.HOME = dir;
22
+ const codexDir = path.join(dir, ".codex");
23
+ fs.mkdirSync(codexDir, { recursive: true });
24
+ fs.writeFileSync(
25
+ codexConfigPath(dir),
26
+ [
27
+ 'theme = "dark"',
28
+ 'model = "keep"',
29
+ 'model_reasoning_effort = "low"',
30
+ 'model_provider = "getrouter"',
31
+ "",
32
+ "[model_providers.getrouter]",
33
+ 'name = "getrouter"',
34
+ 'base_url = "https://api.getrouter.dev/codex"',
35
+ "",
36
+ "[model_providers.other]",
37
+ 'name = "other"',
38
+ ].join("\n"),
39
+ );
40
+ fs.writeFileSync(
41
+ codexAuthPath(dir),
42
+ JSON.stringify({ OTHER: "keep", OPENAI_API_KEY: "old" }, null, 2),
43
+ );
44
+
45
+ const program = createProgram();
46
+ await program.parseAsync(["node", "getrouter", "codex", "uninstall"]);
47
+
48
+ const config = fs.readFileSync(codexConfigPath(dir), "utf8");
49
+ expect(config).toContain('theme = "dark"');
50
+ expect(config).toContain('[model_providers.other]');
51
+ expect(config).not.toContain('[model_providers.getrouter]');
52
+ expect(config).not.toContain('model_provider = "getrouter"');
53
+
54
+ const auth = JSON.parse(fs.readFileSync(codexAuthPath(dir), "utf8"));
55
+ expect(auth.OTHER).toBe("keep");
56
+ expect(auth.OPENAI_API_KEY).toBeUndefined();
57
+ });
58
+
59
+ it("uninstall leaves root keys when provider is not getrouter", async () => {
60
+ const dir = makeDir();
61
+ process.env.HOME = dir;
62
+ const codexDir = path.join(dir, ".codex");
63
+ fs.mkdirSync(codexDir, { recursive: true });
64
+ fs.writeFileSync(
65
+ codexConfigPath(dir),
66
+ [
67
+ 'model = "keep"',
68
+ 'model_reasoning_effort = "low"',
69
+ 'model_provider = "other"',
70
+ "",
71
+ "[model_providers.getrouter]",
72
+ 'name = "getrouter"',
73
+ ].join("\n"),
74
+ );
75
+
76
+ const program = createProgram();
77
+ await program.parseAsync(["node", "getrouter", "codex", "uninstall"]);
78
+
79
+ const config = fs.readFileSync(codexConfigPath(dir), "utf8");
80
+ expect(config).toContain('model = "keep"');
81
+ expect(config).toContain('model_provider = "other"');
82
+ expect(config).not.toContain('[model_providers.getrouter]');
83
+ });
84
+ ```
85
+
86
+ **Step 2: Run test to verify it fails**
87
+
88
+ Run: `bun run test -- tests/cmd/codex.test.ts`
89
+ Expected: FAIL with an error like "unknown command 'uninstall'" or missing behavior.
90
+
91
+ **Step 3: Commit**
92
+
93
+ ```bash
94
+ git add tests/cmd/codex.test.ts
95
+ git commit -m "test: cover codex uninstall"
96
+ ```
97
+
98
+ ### Task 2: Implement removal helpers
99
+
100
+ **Files:**
101
+ - Modify: `src/core/setup/codex.ts`
102
+
103
+ **Step 1: Write the minimal implementation**
104
+
105
+ ```ts
106
+ export const removeCodexConfig = (content: string) => {
107
+ const lines = content.length ? content.split(/\r?\n/) : [];
108
+ const updated: string[] = [];
109
+ let inGetrouterSection = false;
110
+ let providerIsGetrouter = false;
111
+
112
+ for (let i = 0; i < lines.length; i += 1) {
113
+ const line = lines[i] ?? "";
114
+ const headerMatch = matchHeader(line);
115
+ if (headerMatch) {
116
+ const section = headerMatch[1]?.trim() ?? "";
117
+ inGetrouterSection = section === PROVIDER_SECTION;
118
+ if (inGetrouterSection) continue;
119
+ }
120
+ if (inGetrouterSection) continue;
121
+
122
+ const keyMatch = matchKey(line);
123
+ if (keyMatch && keyMatch[1] === "model_provider") {
124
+ providerIsGetrouter = /getrouter/i.test(line);
125
+ }
126
+
127
+ updated.push(line);
128
+ }
129
+
130
+ if (providerIsGetrouter) {
131
+ return {
132
+ content: updated
133
+ .filter(
134
+ (line) =>
135
+ !/^\s*model\s*=/.test(line) &&
136
+ !/^\s*model_reasoning_effort\s*=/.test(line) &&
137
+ !/^\s*model_provider\s*=/.test(line),
138
+ )
139
+ .join("\n"),
140
+ changed: true,
141
+ };
142
+ }
143
+
144
+ return { content: updated.join("\n"), changed: updated.join("\n") !== content };
145
+ };
146
+
147
+ export const removeAuthJson = (data: Record<string, unknown>) => {
148
+ if (!("OPENAI_API_KEY" in data)) return { data, changed: false };
149
+ const { OPENAI_API_KEY: _ignored, ...rest } = data;
150
+ return { data: rest, changed: true };
151
+ };
152
+ ```
153
+
154
+ **Step 2: Run test to verify it still fails**
155
+
156
+ Run: `bun run test -- tests/cmd/codex.test.ts`
157
+ Expected: FAIL until CLI wiring is added.
158
+
159
+ **Step 3: Commit**
160
+
161
+ ```bash
162
+ git add src/core/setup/codex.ts
163
+ git commit -m "feat: add codex uninstall helpers"
164
+ ```
165
+
166
+ ### Task 3: Wire the uninstall subcommand
167
+
168
+ **Files:**
169
+ - Modify: `src/cmd/codex.ts`
170
+
171
+ **Step 1: Add the uninstall command**
172
+
173
+ ```ts
174
+ import { removeAuthJson, removeCodexConfig } from "../core/setup/codex";
175
+
176
+ const logMissing = (filePath: string) =>
177
+ console.log(`ℹ️ ${filePath} not found`);
178
+
179
+ const logNoop = (filePath: string) =>
180
+ console.log(`ℹ️ No getrouter entries in ${filePath}`);
181
+
182
+ const logRemoved = (filePath: string) =>
183
+ console.log(`✅ Removed getrouter entries from ${filePath}`);
184
+
185
+ // in registerCodexCommand
186
+ const codex = program.command("codex").description("Configure Codex");
187
+
188
+ codex
189
+ .command("uninstall")
190
+ .description("Remove getrouter Codex configuration")
191
+ .action(() => {
192
+ const codexDir = ensureCodexDir();
193
+ const configPath = path.join(codexDir, "config.toml");
194
+ const authPath = path.join(codexDir, "auth.json");
195
+
196
+ if (!fs.existsSync(configPath)) {
197
+ logMissing(configPath);
198
+ } else {
199
+ const content = readFileIfExists(configPath);
200
+ const removed = removeCodexConfig(content);
201
+ if (removed.changed) {
202
+ fs.writeFileSync(configPath, removed.content, "utf8");
203
+ logRemoved(configPath);
204
+ } else {
205
+ logNoop(configPath);
206
+ }
207
+ }
208
+
209
+ if (!fs.existsSync(authPath)) {
210
+ logMissing(authPath);
211
+ } else {
212
+ const raw = fs.readFileSync(authPath, "utf8").trim();
213
+ const data = raw ? (JSON.parse(raw) as Record<string, unknown>) : {};
214
+ const removed = removeAuthJson(data);
215
+ if (removed.changed) {
216
+ fs.writeFileSync(authPath, JSON.stringify(removed.data, null, 2));
217
+ logRemoved(authPath);
218
+ } else {
219
+ logNoop(authPath);
220
+ }
221
+ }
222
+ });
223
+ ```
224
+
225
+ **Step 2: Run tests to verify they pass**
226
+
227
+ Run: `bun run test -- tests/cmd/codex.test.ts`
228
+ Expected: PASS
229
+
230
+ **Step 3: Commit**
231
+
232
+ ```bash
233
+ git add src/cmd/codex.ts tests/cmd/codex.test.ts
234
+ git commit -m "feat: add codex uninstall command"
235
+ ```
236
+
237
+ ### Task 4: Final verification
238
+
239
+ **Files:**
240
+ - Verify: `src/cmd/codex.ts`, `src/core/setup/codex.ts`, `tests/cmd/codex.test.ts`
241
+
242
+ **Step 1: Run full checks**
243
+
244
+ Run: `bun run test && bun run lint && bun run format`
245
+ Expected: PASS
246
+
247
+ **Step 2: Commit formatting (if needed)**
248
+
249
+ ```bash
250
+ git add src/cmd/codex.ts src/core/setup/codex.ts tests/cmd/codex.test.ts
251
+ git commit -m "chore: format codex uninstall"
252
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getrouter/getrouter-cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "description": "CLI for getrouter.dev",
6
6
  "bin": {
package/src/cmd/codex.ts CHANGED
@@ -11,7 +11,12 @@ import {
11
11
  } from "../core/interactive/codex";
12
12
  import { fuzzySelect } from "../core/interactive/fuzzy";
13
13
  import { selectConsumer } from "../core/interactive/keys";
14
- import { mergeAuthJson, mergeCodexToml } from "../core/setup/codex";
14
+ import {
15
+ mergeAuthJson,
16
+ mergeCodexToml,
17
+ removeAuthJson,
18
+ removeCodexConfig,
19
+ } from "../core/setup/codex";
15
20
 
16
21
  const CODEX_DIR = ".codex";
17
22
 
@@ -35,6 +40,8 @@ const ensureCodexDir = () => {
35
40
  return dir;
36
41
  };
37
42
 
43
+ const resolveCodexDir = () => path.join(os.homedir(), CODEX_DIR);
44
+
38
45
  const requireInteractive = () => {
39
46
  if (!process.stdin.isTTY) {
40
47
  throw new Error("Interactive mode required for codex configuration.");
@@ -64,9 +71,9 @@ type CodexCommandOptions = {
64
71
  };
65
72
 
66
73
  export const registerCodexCommand = (program: Command) => {
67
- program
68
- .command("codex")
69
- .description("Configure Codex")
74
+ const codex = program.command("codex").description("Configure Codex");
75
+
76
+ codex
70
77
  .option("-m, --model <model>", "Set codex model (skips model selection)")
71
78
  .action(async (options: CodexCommandOptions) => {
72
79
  requireInteractive();
@@ -119,4 +126,49 @@ export const registerCodexCommand = (program: Command) => {
119
126
  console.log("✅ Updated ~/.codex/config.toml");
120
127
  console.log("✅ Updated ~/.codex/auth.json");
121
128
  });
129
+
130
+ codex
131
+ .command("uninstall")
132
+ .description("Remove getrouter Codex configuration")
133
+ .action(() => {
134
+ const codexDir = resolveCodexDir();
135
+ const configPath = path.join(codexDir, "config.toml");
136
+ const authPath = path.join(codexDir, "auth.json");
137
+
138
+ const configExists = fs.existsSync(configPath);
139
+ const authExists = fs.existsSync(authPath);
140
+
141
+ const configContent = configExists ? readFileIfExists(configPath) : "";
142
+ const configResult = configExists
143
+ ? removeCodexConfig(configContent)
144
+ : null;
145
+
146
+ const authContent = authExists
147
+ ? fs.readFileSync(authPath, "utf8").trim()
148
+ : "";
149
+ const authData = authExists
150
+ ? authContent
151
+ ? (JSON.parse(authContent) as Record<string, unknown>)
152
+ : {}
153
+ : null;
154
+ const authResult = authData ? removeAuthJson(authData) : null;
155
+
156
+ if (!configExists) {
157
+ console.log(`ℹ️ ${configPath} not found`);
158
+ } else if (configResult?.changed) {
159
+ fs.writeFileSync(configPath, configResult.content, "utf8");
160
+ console.log(`✅ Removed getrouter entries from ${configPath}`);
161
+ } else {
162
+ console.log(`ℹ️ No getrouter entries in ${configPath}`);
163
+ }
164
+
165
+ if (!authExists) {
166
+ console.log(`ℹ️ ${authPath} not found`);
167
+ } else if (authResult?.changed) {
168
+ fs.writeFileSync(authPath, JSON.stringify(authResult.data, null, 2));
169
+ console.log(`✅ Removed getrouter entries from ${authPath}`);
170
+ } else {
171
+ console.log(`ℹ️ No getrouter entries in ${authPath}`);
172
+ }
173
+ });
122
174
  };
@@ -31,7 +31,7 @@ export const generateAuthCode = () => {
31
31
  };
32
32
 
33
33
  export const buildLoginUrl = (authCode: string) =>
34
- `https://getrouter.dev/#/a/${authCode}`;
34
+ `https://getrouter.dev/auth/${authCode}`;
35
35
 
36
36
  export const openLoginUrl = async (url: string) => {
37
37
  try {