@getrouter/getrouter-cli 0.1.13 → 0.1.14
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 +522 -441
- package/package.json +1 -1
- package/src/cli.ts +4 -2
- package/src/cmd/auth.ts +2 -2
- package/src/cmd/claude.ts +2 -2
- package/src/cmd/codex.ts +24 -16
- package/src/cmd/env.ts +15 -11
- package/src/cmd/index.ts +2 -2
- package/src/cmd/keys.ts +59 -35
- package/src/cmd/models.ts +24 -26
- package/src/cmd/status.ts +113 -62
- package/src/cmd/usages.ts +4 -4
- package/src/core/api/client.ts +12 -14
- package/src/core/api/pagination.ts +3 -3
- package/src/core/api/providerModels.ts +15 -14
- package/src/core/auth/device.ts +38 -23
- package/src/core/auth/index.ts +19 -11
- package/src/core/auth/refresh.ts +22 -17
- package/src/core/config/fs.ts +6 -6
- package/src/core/config/index.ts +16 -11
- package/src/core/config/paths.ts +12 -4
- package/src/core/config/redact.ts +4 -4
- package/src/core/config/types.ts +14 -10
- package/src/core/http/errors.ts +18 -10
- package/src/core/http/request.ts +29 -34
- package/src/core/http/retry.ts +37 -24
- package/src/core/http/url.ts +11 -6
- package/src/core/interactive/clipboard.ts +10 -9
- package/src/core/interactive/keys.ts +22 -26
- package/src/core/output/table.ts +5 -5
- package/src/core/output/usages.ts +34 -33
- package/src/core/setup/codex.ts +195 -142
- package/src/core/setup/env.ts +49 -42
- package/src/core/usages/aggregate.ts +3 -3
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -2,12 +2,14 @@ import { Command } from "commander";
|
|
|
2
2
|
import { version } from "../package.json";
|
|
3
3
|
import { registerCommands } from "./cmd";
|
|
4
4
|
|
|
5
|
-
export
|
|
5
|
+
export function createProgram(): Command {
|
|
6
6
|
const program = new Command();
|
|
7
7
|
program
|
|
8
8
|
.name("getrouter")
|
|
9
9
|
.description("CLI for getrouter.dev")
|
|
10
10
|
.version(version);
|
|
11
|
+
|
|
11
12
|
registerCommands(program);
|
|
13
|
+
|
|
12
14
|
return program;
|
|
13
|
-
}
|
|
15
|
+
}
|
package/src/cmd/auth.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "../core/auth/device";
|
|
10
10
|
import { writeAuth } from "../core/config";
|
|
11
11
|
|
|
12
|
-
export
|
|
12
|
+
export function registerAuthCommands(program: Command): void {
|
|
13
13
|
program
|
|
14
14
|
.command("login")
|
|
15
15
|
.description("Login with device flow")
|
|
@@ -41,4 +41,4 @@ export const registerAuthCommands = (program: Command) => {
|
|
|
41
41
|
clearAuth();
|
|
42
42
|
console.log("Cleared local auth data.");
|
|
43
43
|
});
|
|
44
|
-
}
|
|
44
|
+
}
|
package/src/cmd/claude.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
import { buildAnthropicEnv, registerEnvCommand } from "./env";
|
|
3
3
|
|
|
4
|
-
export
|
|
4
|
+
export function registerClaudeCommand(program: Command): void {
|
|
5
5
|
registerEnvCommand(program, {
|
|
6
6
|
name: "claude",
|
|
7
7
|
description: "Configure Claude environment",
|
|
8
8
|
vars: buildAnthropicEnv,
|
|
9
9
|
});
|
|
10
|
-
}
|
|
10
|
+
}
|
package/src/cmd/codex.ts
CHANGED
|
@@ -107,7 +107,7 @@ type CodexCommandOptions = {
|
|
|
107
107
|
model?: string;
|
|
108
108
|
};
|
|
109
109
|
|
|
110
|
-
export
|
|
110
|
+
export function registerCodexCommand(program: Command): void {
|
|
111
111
|
const codex = program.command("codex").description("Configure Codex");
|
|
112
112
|
|
|
113
113
|
codex
|
|
@@ -168,40 +168,43 @@ export const registerCodexCommand = (program: Command) => {
|
|
|
168
168
|
updatedAt: now,
|
|
169
169
|
};
|
|
170
170
|
backup.updatedAt = now;
|
|
171
|
+
|
|
171
172
|
backup.config ??= {};
|
|
172
173
|
backup.config.previous ??= {};
|
|
174
|
+
const previousConfig = backup.config.previous;
|
|
173
175
|
if (
|
|
174
|
-
|
|
176
|
+
previousConfig.model === undefined &&
|
|
175
177
|
existingRoot.model &&
|
|
176
178
|
existingRoot.model !== installedRoot.model
|
|
177
179
|
) {
|
|
178
|
-
|
|
180
|
+
previousConfig.model = existingRoot.model;
|
|
179
181
|
}
|
|
180
182
|
if (
|
|
181
|
-
|
|
183
|
+
previousConfig.reasoning === undefined &&
|
|
182
184
|
existingRoot.reasoning &&
|
|
183
185
|
existingRoot.reasoning !== installedRoot.reasoning
|
|
184
186
|
) {
|
|
185
|
-
|
|
187
|
+
previousConfig.reasoning = existingRoot.reasoning;
|
|
186
188
|
}
|
|
187
189
|
if (
|
|
188
|
-
|
|
190
|
+
previousConfig.provider === undefined &&
|
|
189
191
|
existingRoot.provider &&
|
|
190
192
|
existingRoot.provider !== installedRoot.provider
|
|
191
193
|
) {
|
|
192
|
-
|
|
194
|
+
previousConfig.provider = existingRoot.provider;
|
|
193
195
|
}
|
|
194
196
|
backup.config.installed = installedRoot;
|
|
195
197
|
|
|
196
198
|
backup.auth ??= {};
|
|
199
|
+
const authBackup = backup.auth;
|
|
197
200
|
if (
|
|
198
|
-
|
|
201
|
+
authBackup.previousOpenaiKey === undefined &&
|
|
199
202
|
existingOpenaiKey &&
|
|
200
203
|
existingOpenaiKey !== apiKey
|
|
201
204
|
) {
|
|
202
|
-
|
|
205
|
+
authBackup.previousOpenaiKey = existingOpenaiKey;
|
|
203
206
|
}
|
|
204
|
-
|
|
207
|
+
authBackup.installedOpenaiKey = apiKey;
|
|
205
208
|
writeCodexBackup(backup);
|
|
206
209
|
|
|
207
210
|
const mergedConfig = mergeCodexToml(existingConfig, {
|
|
@@ -248,11 +251,16 @@ export const registerCodexCommand = (program: Command) => {
|
|
|
248
251
|
const authContent = authExists
|
|
249
252
|
? fs.readFileSync(authPath, "utf8").trim()
|
|
250
253
|
: "";
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
254
|
+
|
|
255
|
+
let authData: Record<string, unknown> | null = null;
|
|
256
|
+
if (authExists) {
|
|
257
|
+
if (authContent) {
|
|
258
|
+
authData = JSON.parse(authContent) as Record<string, unknown>;
|
|
259
|
+
} else {
|
|
260
|
+
authData = {};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
256
264
|
const authResult = authData
|
|
257
265
|
? removeAuthJson(authData, {
|
|
258
266
|
installed: installedOpenaiKey,
|
|
@@ -283,4 +291,4 @@ export const registerCodexCommand = (program: Command) => {
|
|
|
283
291
|
fs.unlinkSync(backupPath);
|
|
284
292
|
}
|
|
285
293
|
});
|
|
286
|
-
}
|
|
294
|
+
}
|
package/src/cmd/env.ts
CHANGED
|
@@ -32,10 +32,10 @@ type EnvCommandConfig = {
|
|
|
32
32
|
vars: (apiKey: string) => EnvVars;
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
export
|
|
35
|
+
export function registerEnvCommand(
|
|
36
36
|
program: Command,
|
|
37
37
|
config: EnvCommandConfig,
|
|
38
|
-
)
|
|
38
|
+
): void {
|
|
39
39
|
program
|
|
40
40
|
.command(config.name)
|
|
41
41
|
.description(config.description)
|
|
@@ -90,14 +90,18 @@ export const registerEnvCommand = (
|
|
|
90
90
|
console.log(sourceLine);
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
|
-
}
|
|
93
|
+
}
|
|
94
94
|
|
|
95
|
-
export
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
export function buildOpenAIEnv(apiKey: string): EnvVars {
|
|
96
|
+
return {
|
|
97
|
+
openaiBaseUrl: CODEX_BASE_URL,
|
|
98
|
+
openaiApiKey: apiKey,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
99
101
|
|
|
100
|
-
export
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
export function buildAnthropicEnv(apiKey: string): EnvVars {
|
|
103
|
+
return {
|
|
104
|
+
anthropicBaseUrl: CLAUDE_BASE_URL,
|
|
105
|
+
anthropicApiKey: apiKey,
|
|
106
|
+
};
|
|
107
|
+
}
|
package/src/cmd/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { registerModelsCommands } from "./models";
|
|
|
7
7
|
import { registerStatusCommand } from "./status";
|
|
8
8
|
import { registerUsagesCommand } from "./usages";
|
|
9
9
|
|
|
10
|
-
export
|
|
10
|
+
export function registerCommands(program: Command): void {
|
|
11
11
|
registerAuthCommands(program);
|
|
12
12
|
registerCodexCommand(program);
|
|
13
13
|
registerClaudeCommand(program);
|
|
@@ -15,4 +15,4 @@ export const registerCommands = (program: Command) => {
|
|
|
15
15
|
registerModelsCommands(program);
|
|
16
16
|
registerStatusCommand(program);
|
|
17
17
|
registerUsagesCommand(program);
|
|
18
|
-
}
|
|
18
|
+
}
|
package/src/cmd/keys.ts
CHANGED
|
@@ -66,18 +66,41 @@ const requireInteractiveForSelection = () =>
|
|
|
66
66
|
const requireInteractiveForAction = (action: string) =>
|
|
67
67
|
requireInteractive(`Interactive mode required for keys ${action}.`);
|
|
68
68
|
|
|
69
|
+
type PromptKeyResult =
|
|
70
|
+
| { cancelled: true }
|
|
71
|
+
| { cancelled: false; name: string | undefined; enabled: boolean };
|
|
72
|
+
|
|
73
|
+
const promptKeyDetails = async (
|
|
74
|
+
initialName: string | undefined,
|
|
75
|
+
initialEnabled: boolean,
|
|
76
|
+
): Promise<PromptKeyResult> => {
|
|
77
|
+
const nameResult = await promptKeyName(initialName);
|
|
78
|
+
if (nameResult.cancelled) return { cancelled: true };
|
|
79
|
+
|
|
80
|
+
const enabledResult = await promptKeyEnabled(initialEnabled);
|
|
81
|
+
if (enabledResult.cancelled) return { cancelled: true };
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
cancelled: false,
|
|
85
|
+
name: nameResult.name,
|
|
86
|
+
enabled: enabledResult.enabled,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
69
90
|
const updateConsumer = async (
|
|
70
91
|
consumerService: Pick<ConsumerService, "UpdateConsumer">,
|
|
71
92
|
consumer: routercommonv1_Consumer,
|
|
72
93
|
name: string | undefined,
|
|
73
94
|
enabled: boolean | undefined,
|
|
74
95
|
) => {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
.
|
|
96
|
+
const updateMaskParts: string[] = [];
|
|
97
|
+
if (name !== undefined && name !== consumer.name) {
|
|
98
|
+
updateMaskParts.push("name");
|
|
99
|
+
}
|
|
100
|
+
if (enabled !== undefined && enabled !== consumer.enabled) {
|
|
101
|
+
updateMaskParts.push("enabled");
|
|
102
|
+
}
|
|
103
|
+
const updateMask = updateMaskParts.join(",");
|
|
81
104
|
if (!updateMask) {
|
|
82
105
|
return consumer;
|
|
83
106
|
}
|
|
@@ -108,42 +131,32 @@ const listConsumers = async (
|
|
|
108
131
|
outputConsumers(sorted, showApiKey);
|
|
109
132
|
};
|
|
110
133
|
|
|
111
|
-
const
|
|
112
|
-
consumerService: Pick<ConsumerService, "GetConsumer" | "ListConsumers">,
|
|
113
|
-
id?: string,
|
|
114
|
-
) => {
|
|
115
|
-
if (id) {
|
|
116
|
-
return consumerService.GetConsumer({ id });
|
|
117
|
-
}
|
|
118
|
-
requireInteractiveForSelection();
|
|
119
|
-
return await selectConsumerList(consumerService, "Select key to update");
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const resolveConsumerForDelete = async (
|
|
134
|
+
const resolveConsumer = async (
|
|
123
135
|
consumerService: Pick<ConsumerService, "GetConsumer" | "ListConsumers">,
|
|
136
|
+
message: string,
|
|
124
137
|
id?: string,
|
|
125
138
|
) => {
|
|
126
139
|
if (id) {
|
|
127
140
|
return consumerService.GetConsumer({ id });
|
|
128
141
|
}
|
|
129
142
|
requireInteractiveForSelection();
|
|
130
|
-
return await selectConsumerList(consumerService,
|
|
143
|
+
return await selectConsumerList(consumerService, message);
|
|
131
144
|
};
|
|
132
145
|
|
|
133
146
|
const createConsumer = async (
|
|
134
147
|
consumerService: Pick<ConsumerService, "CreateConsumer" | "UpdateConsumer">,
|
|
135
148
|
) => {
|
|
136
149
|
requireInteractiveForAction("create");
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
150
|
+
|
|
151
|
+
const details = await promptKeyDetails(undefined, true);
|
|
152
|
+
if (details.cancelled) return;
|
|
153
|
+
|
|
141
154
|
let consumer = await consumerService.CreateConsumer({});
|
|
142
155
|
consumer = await updateConsumer(
|
|
143
156
|
consumerService,
|
|
144
157
|
consumer,
|
|
145
|
-
|
|
146
|
-
|
|
158
|
+
details.name,
|
|
159
|
+
details.enabled,
|
|
147
160
|
);
|
|
148
161
|
outputConsumerTable(consumer, true);
|
|
149
162
|
console.log("Please store this API key securely.");
|
|
@@ -157,17 +170,24 @@ const updateConsumerById = async (
|
|
|
157
170
|
id?: string,
|
|
158
171
|
) => {
|
|
159
172
|
requireInteractiveForAction("update");
|
|
160
|
-
const selected = await
|
|
173
|
+
const selected = await resolveConsumer(
|
|
174
|
+
consumerService,
|
|
175
|
+
"Select key to update",
|
|
176
|
+
id,
|
|
177
|
+
);
|
|
161
178
|
if (!selected?.id) return;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
179
|
+
|
|
180
|
+
const details = await promptKeyDetails(
|
|
181
|
+
selected.name,
|
|
182
|
+
selected.enabled ?? true,
|
|
183
|
+
);
|
|
184
|
+
if (details.cancelled) return;
|
|
185
|
+
|
|
166
186
|
const consumer = await updateConsumer(
|
|
167
187
|
consumerService,
|
|
168
188
|
selected,
|
|
169
|
-
|
|
170
|
-
|
|
189
|
+
details.name,
|
|
190
|
+
details.enabled,
|
|
171
191
|
);
|
|
172
192
|
outputConsumerTable(consumer, false);
|
|
173
193
|
};
|
|
@@ -180,7 +200,11 @@ const deleteConsumerById = async (
|
|
|
180
200
|
id?: string,
|
|
181
201
|
) => {
|
|
182
202
|
requireInteractiveForAction("delete");
|
|
183
|
-
const selected = await
|
|
203
|
+
const selected = await resolveConsumer(
|
|
204
|
+
consumerService,
|
|
205
|
+
"Select key to delete",
|
|
206
|
+
id,
|
|
207
|
+
);
|
|
184
208
|
if (!selected?.id) return;
|
|
185
209
|
const confirmed = await confirmDelete(selected);
|
|
186
210
|
if (!confirmed) return;
|
|
@@ -188,7 +212,7 @@ const deleteConsumerById = async (
|
|
|
188
212
|
outputConsumerTable(selected, false);
|
|
189
213
|
};
|
|
190
214
|
|
|
191
|
-
export
|
|
215
|
+
export function registerKeysCommands(program: Command): void {
|
|
192
216
|
const keys = program.command("keys").description("Manage API keys");
|
|
193
217
|
keys.option("--show", "Show full API keys");
|
|
194
218
|
keys.allowExcessArguments(false);
|
|
@@ -233,4 +257,4 @@ export const registerKeysCommands = (program: Command) => {
|
|
|
233
257
|
const { consumerService } = createApiClients({});
|
|
234
258
|
await deleteConsumerById(consumerService, id);
|
|
235
259
|
});
|
|
236
|
-
}
|
|
260
|
+
}
|
package/src/cmd/models.ts
CHANGED
|
@@ -5,20 +5,22 @@ import type { routercommonv1_Model } from "../generated/router/dashboard/v1";
|
|
|
5
5
|
|
|
6
6
|
const modelHeaders = ["ID", "NAME", "AUTHOR", "ENABLED", "UPDATED_AT"];
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
function formatModelRow(model: routercommonv1_Model): string[] {
|
|
9
|
+
return [
|
|
10
|
+
String(model.id ?? ""),
|
|
11
|
+
String(model.name ?? ""),
|
|
12
|
+
String(model.author ?? ""),
|
|
13
|
+
String(model.enabled ?? ""),
|
|
14
|
+
String(model.updatedAt ?? ""),
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function outputModels(models: routercommonv1_Model[]): void {
|
|
17
19
|
console.log("🧠 Models");
|
|
18
|
-
console.log(renderTable(modelHeaders, models.map(
|
|
19
|
-
}
|
|
20
|
+
console.log(renderTable(modelHeaders, models.map(formatModelRow)));
|
|
21
|
+
}
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
async function listModels(): Promise<void> {
|
|
22
24
|
const { modelService } = createApiClients({});
|
|
23
25
|
const res = await modelService.ListModels({
|
|
24
26
|
pageSize: undefined,
|
|
@@ -26,24 +28,20 @@ const listModels = async () => {
|
|
|
26
28
|
filter: undefined,
|
|
27
29
|
});
|
|
28
30
|
const models = res?.models ?? [];
|
|
31
|
+
|
|
29
32
|
if (models.length === 0) {
|
|
30
33
|
console.log("😕 No models found");
|
|
31
34
|
return;
|
|
32
35
|
}
|
|
33
|
-
outputModels(models);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export const registerModelsCommands = (program: Command) => {
|
|
37
|
-
const models = program.command("models").description("List models");
|
|
38
36
|
|
|
39
|
-
models
|
|
40
|
-
|
|
41
|
-
});
|
|
37
|
+
outputModels(models);
|
|
38
|
+
}
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
export function registerModelsCommands(program: Command): void {
|
|
41
|
+
const models = program
|
|
42
|
+
.command("models")
|
|
45
43
|
.description("List models")
|
|
46
|
-
.action(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
44
|
+
.action(listModels);
|
|
45
|
+
|
|
46
|
+
models.command("list").description("List models").action(listModels);
|
|
47
|
+
}
|
package/src/cmd/status.ts
CHANGED
|
@@ -4,103 +4,154 @@ import { getAuthStatus } from "../core/auth";
|
|
|
4
4
|
|
|
5
5
|
const LABEL_WIDTH = 10;
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
type LineValue = string | number | undefined;
|
|
8
|
+
|
|
9
|
+
type SectionLine = readonly [label: string, value: LineValue];
|
|
10
|
+
type SectionItem = SectionLine | false | null | undefined;
|
|
11
|
+
|
|
12
|
+
type AuthStatus = "logged_in" | "logged_out";
|
|
13
|
+
|
|
14
|
+
type Subscription = {
|
|
15
|
+
plan?: {
|
|
16
|
+
name?: string;
|
|
17
|
+
requestPerMinute?: number;
|
|
18
|
+
tokenPerMinute?: string | number;
|
|
19
|
+
};
|
|
20
|
+
status?: string;
|
|
21
|
+
startAt?: string;
|
|
22
|
+
endAt?: string;
|
|
10
23
|
};
|
|
11
24
|
|
|
12
|
-
|
|
13
|
-
|
|
25
|
+
function line(label: string, value: LineValue): SectionLine {
|
|
26
|
+
return [label, value];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function formatLine(label: string, value: LineValue): string | null {
|
|
30
|
+
if (value == null || value === "") {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return ` ${label.padEnd(LABEL_WIDTH, " ")}: ${value}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function renderSection(title: string, items: SectionItem[]): string {
|
|
38
|
+
const lines: string[] = [];
|
|
39
|
+
|
|
40
|
+
for (const item of items) {
|
|
41
|
+
if (!item) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const rendered = formatLine(item[0], item[1]);
|
|
46
|
+
if (rendered) {
|
|
47
|
+
lines.push(rendered);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return [title, ...lines].join("\n");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function formatAuthStatus(status: AuthStatus): string {
|
|
55
|
+
if (status === "logged_in") {
|
|
56
|
+
return "✅ Logged in";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return "❌ Logged out";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function formatToken(token?: string): string | undefined {
|
|
63
|
+
if (!token) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
14
66
|
|
|
15
|
-
const formatToken = (token?: string) => {
|
|
16
|
-
if (!token) return undefined;
|
|
17
67
|
const trimmed = token.trim();
|
|
18
|
-
if (trimmed.length <= 12)
|
|
68
|
+
if (trimmed.length <= 12) {
|
|
69
|
+
return trimmed;
|
|
70
|
+
}
|
|
71
|
+
|
|
19
72
|
return `${trimmed.slice(0, 4)}...${trimmed.slice(-4)}`;
|
|
20
|
-
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function formatWindow(startAt?: string, endAt?: string): string | undefined {
|
|
76
|
+
if (!startAt && !endAt) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (startAt && endAt) {
|
|
81
|
+
return `${startAt} → ${endAt}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (startAt) {
|
|
85
|
+
return `${startAt} →`;
|
|
86
|
+
}
|
|
21
87
|
|
|
22
|
-
const formatWindow = (startAt?: string, endAt?: string) => {
|
|
23
|
-
if (!startAt && !endAt) return undefined;
|
|
24
|
-
if (startAt && endAt) return `${startAt} → ${endAt}`;
|
|
25
|
-
if (startAt) return `${startAt} →`;
|
|
26
88
|
return `→ ${endAt}`;
|
|
27
|
-
}
|
|
89
|
+
}
|
|
28
90
|
|
|
29
|
-
|
|
91
|
+
function formatLimits(
|
|
30
92
|
requestPerMinute?: number,
|
|
31
93
|
tokenPerMinute?: string | number,
|
|
32
|
-
)
|
|
94
|
+
): string | undefined {
|
|
33
95
|
const parts: string[] = [];
|
|
96
|
+
|
|
34
97
|
if (typeof requestPerMinute === "number") {
|
|
35
98
|
parts.push(`${requestPerMinute} req/min`);
|
|
36
99
|
}
|
|
100
|
+
|
|
37
101
|
if (tokenPerMinute) {
|
|
38
102
|
parts.push(`${tokenPerMinute} tok/min`);
|
|
39
103
|
}
|
|
40
|
-
|
|
104
|
+
|
|
105
|
+
if (parts.length === 0) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
41
109
|
return parts.join(" · ");
|
|
42
|
-
}
|
|
110
|
+
}
|
|
43
111
|
|
|
44
|
-
|
|
112
|
+
function renderAuthSection(): string {
|
|
45
113
|
const status = getAuthStatus();
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
: null,
|
|
57
|
-
status.status === "logged_in"
|
|
58
|
-
? formatLine("Refresh", formatToken(status.refreshToken))
|
|
59
|
-
: null,
|
|
60
|
-
].filter(Boolean) as string[];
|
|
61
|
-
return ["🔐 Auth", ...lines].join("\n");
|
|
62
|
-
};
|
|
114
|
+
const isLoggedIn = status.status === "logged_in";
|
|
115
|
+
|
|
116
|
+
return renderSection("🔐 Auth", [
|
|
117
|
+
line("Status", formatAuthStatus(status.status)),
|
|
118
|
+
isLoggedIn && line("Expires", status.expiresAt),
|
|
119
|
+
isLoggedIn && line("TokenType", status.tokenType),
|
|
120
|
+
isLoggedIn && line("Access", formatToken(status.accessToken)),
|
|
121
|
+
isLoggedIn && line("Refresh", formatToken(status.refreshToken)),
|
|
122
|
+
]);
|
|
123
|
+
}
|
|
63
124
|
|
|
64
|
-
|
|
65
|
-
subscription: {
|
|
66
|
-
plan?: {
|
|
67
|
-
name?: string;
|
|
68
|
-
requestPerMinute?: number;
|
|
69
|
-
tokenPerMinute?: string | number;
|
|
70
|
-
};
|
|
71
|
-
status?: string;
|
|
72
|
-
startAt?: string;
|
|
73
|
-
endAt?: string;
|
|
74
|
-
} | null,
|
|
75
|
-
) => {
|
|
125
|
+
function renderSubscriptionSection(subscription: Subscription | null): string {
|
|
76
126
|
if (!subscription) {
|
|
77
|
-
return
|
|
78
|
-
|
|
79
|
-
|
|
127
|
+
return renderSection("📦 Subscription", [
|
|
128
|
+
line("Status", "No active subscription"),
|
|
129
|
+
]);
|
|
80
130
|
}
|
|
131
|
+
|
|
81
132
|
const limits = formatLimits(
|
|
82
133
|
subscription.plan?.requestPerMinute,
|
|
83
134
|
subscription.plan?.tokenPerMinute,
|
|
84
135
|
);
|
|
85
|
-
const windowLabel = formatWindow(subscription.startAt, subscription.endAt);
|
|
86
|
-
const lines = [
|
|
87
|
-
formatLine("Plan", subscription.plan?.name),
|
|
88
|
-
formatLine("Status", subscription.status),
|
|
89
|
-
formatLine("Window", windowLabel),
|
|
90
|
-
formatLine("Limits", limits),
|
|
91
|
-
].filter(Boolean) as string[];
|
|
92
|
-
return ["📦 Subscription", ...lines].join("\n");
|
|
93
|
-
};
|
|
94
136
|
|
|
95
|
-
|
|
137
|
+
return renderSection("📦 Subscription", [
|
|
138
|
+
line("Plan", subscription.plan?.name),
|
|
139
|
+
line("Status", subscription.status),
|
|
140
|
+
line("Window", formatWindow(subscription.startAt, subscription.endAt)),
|
|
141
|
+
line("Limits", limits),
|
|
142
|
+
]);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function registerStatusCommand(program: Command): void {
|
|
96
146
|
program
|
|
97
147
|
.command("status")
|
|
98
148
|
.description("Show login and subscription status")
|
|
99
149
|
.action(async () => {
|
|
100
150
|
const { subscriptionService } = createApiClients({});
|
|
101
151
|
const subscription = await subscriptionService.CurrentSubscription({});
|
|
152
|
+
|
|
102
153
|
console.log(renderAuthSection());
|
|
103
154
|
console.log("");
|
|
104
155
|
console.log(renderSubscriptionSection(subscription));
|
|
105
156
|
});
|
|
106
|
-
}
|
|
157
|
+
}
|
package/src/cmd/usages.ts
CHANGED
|
@@ -8,7 +8,7 @@ type UsageListResponse = {
|
|
|
8
8
|
nextPageToken?: string;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
async function collectUsages(): Promise<ReturnType<typeof aggregateUsages>> {
|
|
12
12
|
const { usageService } = createApiClients({});
|
|
13
13
|
const res = (await usageService.ListUsage({
|
|
14
14
|
pageSize: 7,
|
|
@@ -16,9 +16,9 @@ const collectUsages = async () => {
|
|
|
16
16
|
})) as UsageListResponse;
|
|
17
17
|
const usages = res?.usages ?? [];
|
|
18
18
|
return aggregateUsages(usages, 7);
|
|
19
|
-
}
|
|
19
|
+
}
|
|
20
20
|
|
|
21
|
-
export
|
|
21
|
+
export function registerUsagesCommand(program: Command): void {
|
|
22
22
|
program
|
|
23
23
|
.command("usages")
|
|
24
24
|
.description("Show recent usage")
|
|
@@ -26,4 +26,4 @@ export const registerUsagesCommand = (program: Command) => {
|
|
|
26
26
|
const aggregated = await collectUsages();
|
|
27
27
|
console.log(renderUsageChart(aggregated));
|
|
28
28
|
});
|
|
29
|
-
}
|
|
29
|
+
}
|