@matterailab/orbcode 0.1.3 → 0.1.4
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/README.md +71 -69
- package/dist/api/models.js +11 -0
- package/dist/auth/auth.js +7 -2
- package/dist/ui/App.js +57 -8
- package/dist/ui/components/StatusBar.js +42 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -144,12 +144,13 @@ The two Axon models are built in; `/model` opens a scroll-and-select picker
|
|
|
144
144
|
(`/model <id>` still selects directly). Additional models can be declared via
|
|
145
145
|
`customModels` in settings.json. The choice persists across sessions.
|
|
146
146
|
|
|
147
|
-
| id
|
|
148
|
-
|
|
149
|
-
| `axon-
|
|
150
|
-
| `axon-code-2-5-
|
|
147
|
+
| id | context | max output | pricing |
|
|
148
|
+
| ---------------------- | ------- | ---------- | ------------------ |
|
|
149
|
+
| `axon-eido-3-code-pro` | 400k | 64k | $3/M in · $9/M out |
|
|
150
|
+
| `axon-code-2-5-pro` | 400k | 64k | $2/M in · $6/M out |
|
|
151
|
+
| `axon-code-2-5-mini` | 400k | 64k | free |
|
|
151
152
|
|
|
152
|
-
`axon-code-2-5-pro` is the default.
|
|
153
|
+
`axon-code-2-5-pro` is the default. All three support native JSON tool calls and
|
|
153
154
|
image input. Cost comes from the API's usage chunks (`is_byok`-aware) and is
|
|
154
155
|
shown in the status bar.
|
|
155
156
|
|
|
@@ -196,35 +197,35 @@ shown in the status bar.
|
|
|
196
197
|
|
|
197
198
|
## Slash commands
|
|
198
199
|
|
|
199
|
-
| command
|
|
200
|
-
|
|
201
|
-
| `/help`
|
|
202
|
-
| `/model`
|
|
203
|
-
| `/clear`
|
|
204
|
-
| `/new`
|
|
205
|
-
| `/resume`
|
|
206
|
-
| `/analytics` | open the MatterAI analytics dashboard (app.matterai.so/orbital) in the browser
|
|
207
|
-
| `/compact`
|
|
208
|
-
| `/tasks`
|
|
209
|
-
| `/status`
|
|
210
|
-
| `/cost`
|
|
211
|
-
| `/init`
|
|
212
|
-
| `/login`
|
|
213
|
-
| `/logout`
|
|
214
|
-
| `/version`
|
|
215
|
-
| `/exit`
|
|
200
|
+
| command | action |
|
|
201
|
+
| ------------ | ----------------------------------------------------------------------------------------------------- |
|
|
202
|
+
| `/help` | list commands |
|
|
203
|
+
| `/model` | scrollable model picker (`/model pro` / `/model mini` / full id selects directly) |
|
|
204
|
+
| `/clear` | clear the screen only, like the terminal's `clear` — the conversation and context continue |
|
|
205
|
+
| `/new` | start a fresh conversation/session with a clean slate |
|
|
206
|
+
| `/resume` | pick a previous session for this directory and continue it (screen is cleared, conversation replayed) |
|
|
207
|
+
| `/analytics` | open the MatterAI analytics dashboard (app.matterai.so/orbital) in the browser |
|
|
208
|
+
| `/compact` | summarize the conversation and replace history with the summary |
|
|
209
|
+
| `/tasks` | print the current task list |
|
|
210
|
+
| `/status` | version, model, account, gateway, context usage, cost, approval modes |
|
|
211
|
+
| `/cost` | show session cost and fetch account balance |
|
|
212
|
+
| `/init` | analyze the codebase and create/improve `AGENTS.md` |
|
|
213
|
+
| `/login` | start the browser sign-in flow |
|
|
214
|
+
| `/logout` | remove the saved token |
|
|
215
|
+
| `/version` | print the CLI version |
|
|
216
|
+
| `/exit` | quit |
|
|
216
217
|
|
|
217
218
|
## Keyboard shortcuts
|
|
218
219
|
|
|
219
|
-
| key
|
|
220
|
-
|
|
221
|
-
| `Esc`
|
|
222
|
-
| `Ctrl+C`
|
|
223
|
-
| `Ctrl+O`
|
|
224
|
-
| `Shift+Tab`
|
|
225
|
-
| `↑` / `↓`
|
|
226
|
-
| `Ctrl+A` / `Ctrl+E` | start / end of line
|
|
227
|
-
| `Ctrl+U`
|
|
220
|
+
| key | action |
|
|
221
|
+
| ------------------- | ---------------------------------------------------------------------- |
|
|
222
|
+
| `Esc` | interrupt the running turn (or cancel login polling / close a menu) |
|
|
223
|
+
| `Ctrl+C` | quit |
|
|
224
|
+
| `Ctrl+O` | toggle thinking display for the whole transcript (past turns included) |
|
|
225
|
+
| `Shift+Tab` | cycle approval mode: ask → accept edits → auto-approve |
|
|
226
|
+
| `↑` / `↓` | input history, or navigate any open menu |
|
|
227
|
+
| `Ctrl+A` / `Ctrl+E` | start / end of line |
|
|
228
|
+
| `Ctrl+U` | clear the input line |
|
|
228
229
|
|
|
229
230
|
## Approvals & safety
|
|
230
231
|
|
|
@@ -267,22 +268,22 @@ Two kinds of files under `~/.orbcode/`:
|
|
|
267
268
|
|
|
268
269
|
```json
|
|
269
270
|
{
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
271
|
+
"apiKey": "<token used instead of logging in>",
|
|
272
|
+
"baseUrl": "https://my-gateway.example.com/v1",
|
|
273
|
+
"model": "my-custom-model",
|
|
274
|
+
"autoApproveEdits": false,
|
|
275
|
+
"autoApproveSafeCommands": false,
|
|
276
|
+
"customModels": [
|
|
277
|
+
{
|
|
278
|
+
"id": "my-custom-model",
|
|
279
|
+
"name": "My Custom Model",
|
|
280
|
+
"contextWindow": 128000,
|
|
281
|
+
"maxOutputTokens": 32000,
|
|
282
|
+
"inputPrice": 0.000001,
|
|
283
|
+
"outputPrice": 0.000002
|
|
284
|
+
}
|
|
285
|
+
],
|
|
286
|
+
"env": { "MY_VAR": "value" }
|
|
286
287
|
}
|
|
287
288
|
```
|
|
288
289
|
|
|
@@ -295,15 +296,15 @@ config.json.
|
|
|
295
296
|
Sessions are stored in `~/.orbcode/sessions/<id>.json` and power `/resume`
|
|
296
297
|
and `--resume <id>`.
|
|
297
298
|
|
|
298
|
-
| env var
|
|
299
|
-
|
|
300
|
-
| `ORBCODE_TOKEN`
|
|
301
|
-
| `ORBCODE_API_KEY`
|
|
302
|
-
| `ORBCODE_BASE_URL`
|
|
303
|
-
| `ORBCODE_MODEL`
|
|
304
|
-
| `ORBCODE_CONFIG_DIR`
|
|
305
|
-
| `ORBCODE_BACKEND_URL` | device-auth backend (default `https://api.matterai.so`)
|
|
306
|
-
| `ORBCODE_APP_URL`
|
|
299
|
+
| env var | effect |
|
|
300
|
+
| --------------------- | ----------------------------------------------------------------- |
|
|
301
|
+
| `ORBCODE_TOKEN` | auth token (overrides everything) |
|
|
302
|
+
| `ORBCODE_API_KEY` | same as `apiKey` in settings.json |
|
|
303
|
+
| `ORBCODE_BASE_URL` | same as `baseUrl` in settings.json |
|
|
304
|
+
| `ORBCODE_MODEL` | model override (what `--model` sets internally) |
|
|
305
|
+
| `ORBCODE_CONFIG_DIR` | config directory (default `~/.orbcode`) |
|
|
306
|
+
| `ORBCODE_BACKEND_URL` | device-auth backend (default `https://api.matterai.so`) |
|
|
307
|
+
| `ORBCODE_APP_URL` | webapp for the authorize page (default `https://app.matterai.so`) |
|
|
307
308
|
|
|
308
309
|
`autoApproveEdits` / `autoApproveSafeCommands` set the session defaults for the
|
|
309
310
|
approval prompts (dangerous commands still always prompt); shift+tab cycles
|
|
@@ -365,19 +366,19 @@ reset date — the same data as the extension's profile view.
|
|
|
365
366
|
|
|
366
367
|
Active in the CLI (schemas byte-identical to the extension's `native-tools`):
|
|
367
368
|
|
|
368
|
-
| tool
|
|
369
|
-
|
|
370
|
-
| `read_file`
|
|
371
|
-
| `file_edit`
|
|
372
|
-
| `multi_file_edit`
|
|
373
|
-
| `file_write`
|
|
374
|
-
| `list_files`
|
|
375
|
-
| `search_files`
|
|
376
|
-
| `execute_command`
|
|
377
|
-
| `web_search` / `web_fetch` | proxied through the MatterAI backend with your token
|
|
378
|
-
| `update_todo_list`
|
|
379
|
-
| `ask_followup_question`
|
|
380
|
-
| `attempt_completion`
|
|
369
|
+
| tool | executor notes |
|
|
370
|
+
| -------------------------- | -------------------------------------------------------------------------------------------- |
|
|
371
|
+
| `read_file` | line-numbered output (`LINE\|content`, 6-char pad), 1000-line cap, offset/limit |
|
|
372
|
+
| `file_edit` | single replacement; unique-match enforcement; `replace_all`; empty `old_string` = whole file |
|
|
373
|
+
| `multi_file_edit` | batched edits grouped per file, per-edit OK/FAILED results |
|
|
374
|
+
| `file_write` | creates parent dirs, full-content writes |
|
|
375
|
+
| `list_files` | optional recursive, ignores node_modules/.git/build dirs, 800-entry cap |
|
|
376
|
+
| `search_files` | JS regex search with glob `file_pattern` (picomatch), 300-match cap, binary skip |
|
|
377
|
+
| `execute_command` | user's shell, 120s timeout, 30k output cap, optional cwd |
|
|
378
|
+
| `web_search` / `web_fetch` | proxied through the MatterAI backend with your token |
|
|
379
|
+
| `update_todo_list` | drives the TUI todo panel |
|
|
380
|
+
| `ask_followup_question` | interactive menu in the TUI |
|
|
381
|
+
| `attempt_completion` | ends the turn with a completion card |
|
|
381
382
|
|
|
382
383
|
Present in `tools/schemas/` but **inactive** (need IDE services): `codebase_search`,
|
|
383
384
|
`lsp`, `list_code_definition_names`, `use_skill`, `check_past_chat_memories`,
|
|
@@ -417,6 +418,7 @@ Source-of-truth rule: behavior is **ported from the Orbital extension repo**
|
|
|
417
418
|
schemas byte-identical rather than editing them here.
|
|
418
419
|
|
|
419
420
|
Backend/web pieces of the device-auth flow live in:
|
|
421
|
+
|
|
420
422
|
- `gravity-console-backend` → `src/controller/orbcodeAuthController.ts`
|
|
421
423
|
(+ OAuth state in `router.ts`, callbacks in `authController.ts`)
|
|
422
424
|
- `gravity-console-webapp` → authorize dialog in `src/App.js`, sign-in q-p
|
package/dist/api/models.js
CHANGED
|
@@ -21,6 +21,17 @@ export const AXON_MODELS = {
|
|
|
21
21
|
outputPrice: 0.000006,
|
|
22
22
|
free: false,
|
|
23
23
|
},
|
|
24
|
+
"axon-eido-3-code-pro": {
|
|
25
|
+
id: "axon-eido-3-code-pro",
|
|
26
|
+
name: "Axon Eido 3 Pro",
|
|
27
|
+
description: "Axon Eido 3 Pro is the frontier Axon Code model for coding tasks, long running agents and general intelligence, fine-tuned on open source models.",
|
|
28
|
+
contextWindow: 400000,
|
|
29
|
+
maxOutputTokens: 64000,
|
|
30
|
+
supportsImages: true,
|
|
31
|
+
inputPrice: 0.000003,
|
|
32
|
+
outputPrice: 0.000009,
|
|
33
|
+
free: false,
|
|
34
|
+
},
|
|
24
35
|
};
|
|
25
36
|
export const DEFAULT_MODEL_ID = "axon-code-2-5-pro";
|
|
26
37
|
/** Add user-defined models (from settings.json) to the registry. */
|
package/dist/auth/auth.js
CHANGED
|
@@ -78,7 +78,10 @@ export async function pollDeviceAuth(devicecode) {
|
|
|
78
78
|
export async function fetchProfile(token) {
|
|
79
79
|
const url = getUrlFromToken("https://api.matterai.so/axoncode/profile", token);
|
|
80
80
|
const response = await fetch(url, {
|
|
81
|
-
headers: {
|
|
81
|
+
headers: {
|
|
82
|
+
Authorization: `Bearer ${token}`,
|
|
83
|
+
"Content-Type": "application/json",
|
|
84
|
+
},
|
|
82
85
|
});
|
|
83
86
|
if (!response.ok) {
|
|
84
87
|
throw new Error(`Profile request failed (${response.status}). Your token may be invalid or expired.`);
|
|
@@ -150,7 +153,9 @@ export async function fetchTaskTitle(taskId, token, maxRetries = 3, retryDelayMs
|
|
|
150
153
|
export async function fetchBalance(token, organizationId) {
|
|
151
154
|
try {
|
|
152
155
|
const url = getUrlFromToken("https://api.matterai.so/api/profile/balance", token);
|
|
153
|
-
const headers = {
|
|
156
|
+
const headers = {
|
|
157
|
+
Authorization: `Bearer ${token}`,
|
|
158
|
+
};
|
|
154
159
|
if (organizationId)
|
|
155
160
|
headers["X-KiloCode-OrganizationId"] = organizationId;
|
|
156
161
|
const response = await fetch(url, { headers });
|
package/dist/ui/App.js
CHANGED
|
@@ -41,6 +41,28 @@ function setTerminalTitle(title) {
|
|
|
41
41
|
process.stdout.write(`\x1b]0;${title}\x07`);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
+
function formatRelativeTime(isoStr) {
|
|
45
|
+
if (!isoStr)
|
|
46
|
+
return "???";
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
const target = new Date(isoStr).getTime();
|
|
49
|
+
if (Number.isNaN(target))
|
|
50
|
+
return "???";
|
|
51
|
+
const diff = target - now;
|
|
52
|
+
if (diff <= 0)
|
|
53
|
+
return "now";
|
|
54
|
+
const sec = Math.floor(diff / 1000);
|
|
55
|
+
const min = Math.floor(sec / 60);
|
|
56
|
+
const hrs = Math.floor(min / 60);
|
|
57
|
+
const days = Math.floor(hrs / 24);
|
|
58
|
+
if (days >= 1)
|
|
59
|
+
return `in ${days} day${days > 1 ? "s" : ""}`;
|
|
60
|
+
if (hrs >= 1)
|
|
61
|
+
return `in ${hrs}h ${min % 60}m`;
|
|
62
|
+
if (min >= 1)
|
|
63
|
+
return `in ${min}m`;
|
|
64
|
+
return "soon";
|
|
65
|
+
}
|
|
44
66
|
/** Human lines for the /status and /cost usage block (extension profile data). */
|
|
45
67
|
function usageLines(profile) {
|
|
46
68
|
const lines = [];
|
|
@@ -52,8 +74,22 @@ function usageLines(profile) {
|
|
|
52
74
|
if (typeof profile.remainingReviews === "number") {
|
|
53
75
|
lines.push(`Reviews ${profile.remainingReviews} remaining`);
|
|
54
76
|
}
|
|
55
|
-
if (profile.
|
|
77
|
+
if (profile.tieredUsage) {
|
|
78
|
+
const ws = [
|
|
79
|
+
["5hr", "5-Hour", profile.tieredUsage.fiveHour],
|
|
80
|
+
["wk", "Weekly", profile.tieredUsage.weekly],
|
|
81
|
+
["mo", "Monthly", profile.tieredUsage.monthly],
|
|
82
|
+
];
|
|
83
|
+
for (const [short, label, w] of ws) {
|
|
84
|
+
const remaining = Math.max(0, w.remaining || 0);
|
|
85
|
+
const pct = Math.max(0, Math.min(100, w.percentage || 0));
|
|
86
|
+
const reset = formatRelativeTime(w.resetsAt);
|
|
87
|
+
lines.push(`${label.padEnd(12)} ${remaining.toFixed(1)}/${w.limit.toFixed(1)} left (${pct}% used) · Resets ${reset}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (profile.creditsResetDate) {
|
|
56
91
|
lines.push(`Resets ${profile.creditsResetDate}`);
|
|
92
|
+
}
|
|
57
93
|
return lines;
|
|
58
94
|
}
|
|
59
95
|
const INIT_PROMPT = `Analyze this codebase and create an AGENTS.md file containing:
|
|
@@ -139,7 +175,7 @@ export function App({ initialView, initialPrompt, initialSession, }) {
|
|
|
139
175
|
if (!token)
|
|
140
176
|
return;
|
|
141
177
|
fetchProfile(token)
|
|
142
|
-
.then((profile) => setUsage({ plan: profile.plan, usagePercentage: profile.usagePercentage }))
|
|
178
|
+
.then((profile) => setUsage({ plan: profile.plan, usagePercentage: profile.usagePercentage, tieredUsage: profile.tieredUsage }))
|
|
143
179
|
.catch(() => { });
|
|
144
180
|
}, []);
|
|
145
181
|
const agentRef = useRef(null);
|
|
@@ -227,6 +263,10 @@ export function App({ initialView, initialPrompt, initialSession, }) {
|
|
|
227
263
|
case "usage":
|
|
228
264
|
setContextTokens(event.inputTokens + event.outputTokens);
|
|
229
265
|
setTotalCost(event.totalCost);
|
|
266
|
+
// A usage chunk arrives once per LLM response, so the plan/usage
|
|
267
|
+
// shown below the chat box stays current mid-turn, not only
|
|
268
|
+
// after the turn finishes.
|
|
269
|
+
refreshUsage();
|
|
230
270
|
break;
|
|
231
271
|
case "completion":
|
|
232
272
|
pushRow({ kind: "completion", text: event.result });
|
|
@@ -324,11 +364,20 @@ export function App({ initialView, initialPrompt, initialSession, }) {
|
|
|
324
364
|
if (arg && isValidAxonModel(arg)) {
|
|
325
365
|
switchModel(arg);
|
|
326
366
|
}
|
|
327
|
-
else if (arg && isValidAxonModel(`axon-code-2-5-${arg}`)) {
|
|
328
|
-
switchModel(`axon-code-2-5-${arg}`);
|
|
329
|
-
}
|
|
330
367
|
else if (arg) {
|
|
331
|
-
|
|
368
|
+
// Allow short suffixes like "pro" or "mini" to resolve to the
|
|
369
|
+
// latest matching registered id, so /model pro keeps working
|
|
370
|
+
// as new model generations are added.
|
|
371
|
+
const matches = ids
|
|
372
|
+
.filter((id) => id.endsWith(`-${arg}`))
|
|
373
|
+
.sort()
|
|
374
|
+
.reverse();
|
|
375
|
+
if (matches.length > 0) {
|
|
376
|
+
switchModel(matches[0]);
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
pushRow({ kind: "error", text: `Unknown model "${arg}". Available: ${ids.join(", ")}` });
|
|
380
|
+
}
|
|
332
381
|
}
|
|
333
382
|
else {
|
|
334
383
|
setModelPickerOpen(true);
|
|
@@ -539,7 +588,7 @@ export function App({ initialView, initialPrompt, initialSession, }) {
|
|
|
539
588
|
saveSettings(updated);
|
|
540
589
|
agentRef.current = null;
|
|
541
590
|
setView("chat");
|
|
542
|
-
setUsage({ plan: profile.plan, usagePercentage: profile.usagePercentage });
|
|
591
|
+
setUsage({ plan: profile.plan, usagePercentage: profile.usagePercentage, tieredUsage: profile.tieredUsage });
|
|
543
592
|
const who = profile.user?.name || profile.user?.email;
|
|
544
593
|
pushRow({ kind: "info", text: `Signed in${who ? ` as ${who}` : ""}. Ready when you are.` });
|
|
545
594
|
}, [pushRow]);
|
|
@@ -558,7 +607,7 @@ export function App({ initialView, initialPrompt, initialSession, }) {
|
|
|
558
607
|
pushRow({ kind: "user", text: answer });
|
|
559
608
|
pendingFollowup.resolve(answer);
|
|
560
609
|
setPendingFollowup(null);
|
|
561
|
-
} }) })), busy && !pendingApproval && !pendingFollowup && !streamingText && (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: busyLabel }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(InputBox, { active: inputActive, slashCommands: SLASH_COMMANDS, onSubmit: handleSubmit }), _jsx(StatusBar, { modelId: settings.model, contextTokens: contextTokens, totalCost: totalCost, state: busy ? busyLabel : "", approvalMode: approvalMode, busy: busy, title: sessionTitle, plan: usage?.plan, usagePercentage: usage?.usagePercentage })] })] }))] }));
|
|
610
|
+
} }) })), busy && !pendingApproval && !pendingFollowup && !streamingText && (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: busyLabel }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(InputBox, { active: inputActive, slashCommands: SLASH_COMMANDS, onSubmit: handleSubmit }), _jsx(StatusBar, { modelId: settings.model, contextTokens: contextTokens, totalCost: totalCost, state: busy ? busyLabel : "", approvalMode: approvalMode, busy: busy, title: sessionTitle, plan: usage?.plan, usagePercentage: usage?.usagePercentage, tieredUsage: usage?.tieredUsage })] })] }))] }));
|
|
562
611
|
}
|
|
563
612
|
function tail(text, lines) {
|
|
564
613
|
const all = text.split("\n").filter((l) => l.trim());
|
|
@@ -7,12 +7,51 @@ const MODE_LABELS = {
|
|
|
7
7
|
edits: "⏵⏵ accept edits on",
|
|
8
8
|
auto: "⏵⏵⏵ auto-approve on",
|
|
9
9
|
};
|
|
10
|
+
function formatRelativeTime(isoStr) {
|
|
11
|
+
if (!isoStr)
|
|
12
|
+
return "???";
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
const target = new Date(isoStr).getTime();
|
|
15
|
+
if (Number.isNaN(target))
|
|
16
|
+
return "???";
|
|
17
|
+
const diff = target - now;
|
|
18
|
+
if (diff <= 0)
|
|
19
|
+
return "now";
|
|
20
|
+
const sec = Math.floor(diff / 1000);
|
|
21
|
+
const min = Math.floor(sec / 60);
|
|
22
|
+
const hrs = Math.floor(min / 60);
|
|
23
|
+
const days = Math.floor(hrs / 24);
|
|
24
|
+
if (days >= 1)
|
|
25
|
+
return `${days}d`;
|
|
26
|
+
if (hrs >= 1)
|
|
27
|
+
return `${hrs}h${min % 60 > 0 ? ` ${min % 60}m` : ""}`;
|
|
28
|
+
if (min >= 1)
|
|
29
|
+
return `${min}m`;
|
|
30
|
+
return "soon";
|
|
31
|
+
}
|
|
10
32
|
function truncate(text, max) {
|
|
11
33
|
return text.length > max ? text.slice(0, max - 1) + "…" : text;
|
|
12
34
|
}
|
|
13
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Picks the most constrained window (highest percentage used) and returns a
|
|
37
|
+
* compact string like "5hr 12% · resets in 3h 15m".
|
|
38
|
+
*/
|
|
39
|
+
function mostConstrainedSummary(tu) {
|
|
40
|
+
const windows = [
|
|
41
|
+
["5hr", tu.fiveHour.percentage, tu.fiveHour.resetsAt],
|
|
42
|
+
["wk", tu.weekly.percentage, tu.weekly.resetsAt],
|
|
43
|
+
["mo", tu.monthly.percentage, tu.monthly.resetsAt],
|
|
44
|
+
];
|
|
45
|
+
const [label, pct, resetsAt] = windows.reduce((best, cur) => cur[1] >= best[1] ? cur : best);
|
|
46
|
+
return `${label} ${Math.round(pct)}% · resets ${formatRelativeTime(resetsAt)}`;
|
|
47
|
+
}
|
|
48
|
+
export function StatusBar({ modelId, contextTokens, totalCost, state, approvalMode, busy, title, plan, usagePercentage, tieredUsage, }) {
|
|
14
49
|
const model = getModel(modelId);
|
|
15
50
|
const contextPct = Math.min(100, Math.round((contextTokens / model.contextWindow) * 100));
|
|
16
|
-
const
|
|
17
|
-
|
|
51
|
+
const constrainedLine = tieredUsage
|
|
52
|
+
? mostConstrainedSummary(tieredUsage)
|
|
53
|
+
: typeof usagePercentage === "number"
|
|
54
|
+
? `${Math.round(usagePercentage)}% used`
|
|
55
|
+
: null;
|
|
56
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { dimColor: true, children: [_jsx(Text, { color: approvalMode === "ask" ? undefined : COLORS.warning, children: MODE_LABELS[approvalMode] }), " (shift+tab to cycle)", busy && " · esc to interrupt", state ? _jsxs(Text, { color: COLORS.thinking, children: [" \u00B7 ", state] }) : null] }), _jsxs(Text, { dimColor: true, children: [title ? `${truncate(title, 32)} · ` : "", model.name, " \u00B7 ctx ", contextTokens.toLocaleString(), " (", contextPct, "%)", model.free ? " · free" : ` · $${totalCost.toFixed(4)}`] })] }), (plan || constrainedLine) && (_jsx(Box, { justifyContent: "flex-end", children: _jsxs(Text, { dimColor: true, children: [plan && constrainedLine ? " · " : "", constrainedLine] }) }))] }));
|
|
18
57
|
}
|