@phnx-labs/agents-cli 1.20.85 → 1.20.86
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/CHANGELOG.md +56 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/events.d.ts +16 -0
- package/dist/commands/events.js +44 -5
- package/dist/commands/models.js +1 -1
- package/dist/commands/sessions-picker.js +5 -4
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +2 -2
- package/dist/lib/models.d.ts +21 -0
- package/dist/lib/models.js +133 -4
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +2 -2
- package/dist/lib/session/parse.d.ts +11 -0
- package/dist/lib/session/parse.js +24 -7
- package/dist/lib/session/state.d.ts +6 -5
- package/dist/lib/session/state.js +15 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.20.86
|
|
4
|
+
|
|
5
|
+
- **`agents sessions` now shows a Kimi session's todo list and its file-touching
|
|
6
|
+
tool calls.** Kimi writes its checklist with `TodoList` (items shaped
|
|
7
|
+
`{title, status}`, where finished is `done`) rather than Claude's `TodoWrite`
|
|
8
|
+
(`{content, status: "completed"}`), so the checklist registry matched nothing
|
|
9
|
+
and every Kimi session rendered with no todos — in the picker preview, the
|
|
10
|
+
session detail, and the `--active` fan-out that carries progress off remote
|
|
11
|
+
devices. Kimi also names the file argument `path` where Claude names it
|
|
12
|
+
`file_path`, so `Read`/`Write`/`Edit` calls summarized as a bare `Read ` with
|
|
13
|
+
no file. Both spellings are now handled, and the snapshot-checklist tool names
|
|
14
|
+
live in one exported registry (`SNAPSHOT_TODO_TOOLS`) that the picker and the
|
|
15
|
+
state engine share instead of each hardcoding its own pair. Source:
|
|
16
|
+
`apps/cli/src/lib/session/parse.ts`, `apps/cli/src/lib/session/state.ts`,
|
|
17
|
+
`apps/cli/src/commands/sessions-picker.ts`.
|
|
18
|
+
|
|
19
|
+
- **`agents view` now shows Grok's default model (e.g. `grok-4.5`).** Claude,
|
|
20
|
+
Codex, Antigravity, and Kimi already filled the model column via their
|
|
21
|
+
catalogs; Grok was missing from `locateModelSource`, so
|
|
22
|
+
`resolveConfiguredModel` returned null and the column stayed blank. Grok has
|
|
23
|
+
no `settings.json` `model` field (its config is `config.toml` +
|
|
24
|
+
`models_cache.json`); the authoritative default is `grok models` →
|
|
25
|
+
`Default model: <id>`. The catalog extractor now spawns that command against
|
|
26
|
+
the version-home binary (skipping failed-download stubs) and flags the
|
|
27
|
+
default, so `agents view`, `agents view --json` (`configuredModel`), and the
|
|
28
|
+
other identity-cluster surfaces show it. Source: `apps/cli/src/lib/models.ts`,
|
|
29
|
+
`apps/cli/src/commands/models.ts`.
|
|
30
|
+
|
|
31
|
+
- **`agents events --limit 0` now reads the whole stream, and a capped read says
|
|
32
|
+
so.** `--limit` parsed as `Math.max(1, parseInt(raw) || 50)`, so `--limit 0`
|
|
33
|
+
collapsed back to `50` (`0 || 50`) and there was no way to read past the default
|
|
34
|
+
cap at all. The cap is applied after filtering and before the caller sees
|
|
35
|
+
anything, so every aggregation over `--json` silently ranked the newest 50
|
|
36
|
+
records instead of the matching set — measured against a real 7-day corpus of
|
|
37
|
+
2,135 CLI failures in 9 classes, 8 of 9 ranks came out wrong with counts off by
|
|
38
|
+
roughly 100x, and nothing warned. `--limit 0` now means no cap (29,649 records
|
|
39
|
+
on a 30-day stream here, against 50 before), a truncated read prints
|
|
40
|
+
`Showing the newest 50 — more events matched. Pass --limit 0 for all.` (on
|
|
41
|
+
stderr under `--json`, so a `| jq` pipeline still receives clean JSON), and a
|
|
42
|
+
non-numeric, negative, or empty `--limit` exits 2 rather than quietly becoming
|
|
43
|
+
50 — an empty one (`--limit "$LIMIT"` with the variable unset) would otherwise
|
|
44
|
+
have read as "no cap" and returned the whole stream unannounced.
|
|
45
|
+
Source: `apps/cli/src/commands/events.ts`, `apps/cli/tests/events-limit.test.ts`,
|
|
46
|
+
`apps/cli/docs/06-observability.md`.
|
|
47
|
+
|
|
48
|
+
- **Desktop notifications now show the current agents-cli mark, not the old
|
|
49
|
+
logo.** The menu-bar helper's app icon — the icon macOS puts on the left of
|
|
50
|
+
every notification banner it posts (the menu bar helper's own notices and every
|
|
51
|
+
`agents run --notify` finish notice) — was generated from the retired gradient
|
|
52
|
+
"A" logo, so notifications carried stale branding while the menu-bar status
|
|
53
|
+
item already used the new lowercase `a`. The shared master logo
|
|
54
|
+
(`assets/logo.png`) is now the current `a` mark, so the menu-bar helper, the
|
|
55
|
+
`agents computer` helper, and the keychain helper all regenerate their
|
|
56
|
+
`AppIcon.icns` from it on the next build. Source: `assets/logo.png`,
|
|
57
|
+
`apps/cli/menubar/scripts/build.sh`.
|
|
58
|
+
|
|
3
59
|
## 1.20.85
|
|
4
60
|
|
|
5
61
|
- **`agents feed post` can now be mirrored to the systems you actually watch.**
|
package/dist/bin/agents
CHANGED
|
Binary file
|
|
@@ -14,4 +14,20 @@
|
|
|
14
14
|
* today's operational log live.
|
|
15
15
|
*/
|
|
16
16
|
import type { Command } from 'commander';
|
|
17
|
+
/**
|
|
18
|
+
* Resolve `--limit` into a record cap. `0` means "no cap" — without it there is
|
|
19
|
+
* no way to read the whole stream, and any aggregation (group-by failure, count
|
|
20
|
+
* per module) silently ranks the newest 50 records instead of the real set.
|
|
21
|
+
* A non-numeric or negative value is a usage error, not a quiet fallback.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveEventsLimit(raw: string | undefined): number | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Cap `fetched` (read with `limit + 1`) to `limit`, reporting whether records
|
|
26
|
+
* were dropped. The caller announces the cap so a truncated read is never
|
|
27
|
+
* mistaken for the complete set.
|
|
28
|
+
*/
|
|
29
|
+
export declare function capRecords<T>(fetched: T[], limit: number | undefined): {
|
|
30
|
+
records: T[];
|
|
31
|
+
truncated: boolean;
|
|
32
|
+
};
|
|
17
33
|
export declare function registerEventsCommand(program: Command): void;
|
package/dist/commands/events.js
CHANGED
|
@@ -17,6 +17,33 @@ import chalk from 'chalk';
|
|
|
17
17
|
import * as fs from 'fs';
|
|
18
18
|
import { getLogsPath } from '../lib/events.js';
|
|
19
19
|
import { readUnifiedEvents } from '../lib/event-stream.js';
|
|
20
|
+
/**
|
|
21
|
+
* Resolve `--limit` into a record cap. `0` means "no cap" — without it there is
|
|
22
|
+
* no way to read the whole stream, and any aggregation (group-by failure, count
|
|
23
|
+
* per module) silently ranks the newest 50 records instead of the real set.
|
|
24
|
+
* A non-numeric or negative value is a usage error, not a quiet fallback.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveEventsLimit(raw) {
|
|
27
|
+
const token = raw ?? '50';
|
|
28
|
+
// Number('') and Number(' ') are both 0, which would read as "no cap" — an
|
|
29
|
+
// empty --limit (an unset "$LIMIT" in a script) must be rejected, not silently
|
|
30
|
+
// turned into the unbounded read.
|
|
31
|
+
const value = token.trim() === '' ? NaN : Number(token);
|
|
32
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
33
|
+
throw new RangeError(`Invalid --limit ${raw} — pass a whole number, or 0 for no cap.`);
|
|
34
|
+
}
|
|
35
|
+
return value === 0 ? undefined : value;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Cap `fetched` (read with `limit + 1`) to `limit`, reporting whether records
|
|
39
|
+
* were dropped. The caller announces the cap so a truncated read is never
|
|
40
|
+
* mistaken for the complete set.
|
|
41
|
+
*/
|
|
42
|
+
export function capRecords(fetched, limit) {
|
|
43
|
+
if (limit === undefined || fetched.length <= limit)
|
|
44
|
+
return { records: fetched, truncated: false };
|
|
45
|
+
return { records: fetched.slice(0, limit), truncated: true };
|
|
46
|
+
}
|
|
20
47
|
/** Parse `--since`: relative offsets (30s/5m/2h/7d/4w) or an ISO/absolute date. */
|
|
21
48
|
function parseSince(s) {
|
|
22
49
|
const m = s.match(/^(\d+)([smhdw])$/);
|
|
@@ -80,7 +107,7 @@ export function registerEventsCommand(program) {
|
|
|
80
107
|
.option('--agent <name>', 'Only events tagged with this agent')
|
|
81
108
|
.option('--since <time>', 'Only events newer than this (e.g. 2h, 7d, or ISO date)')
|
|
82
109
|
.option('--audit', 'Operational events only (skip agent activity)')
|
|
83
|
-
.option('--limit <n>', 'Max records to show (default 50)', '50')
|
|
110
|
+
.option('--limit <n>', 'Max records to show; 0 for no cap (default 50)', '50')
|
|
84
111
|
.option('--json', 'Output raw records as JSON')
|
|
85
112
|
.option('-f, --follow', "Tail today's operational log live")
|
|
86
113
|
.addHelpText('after', `
|
|
@@ -90,31 +117,41 @@ Examples:
|
|
|
90
117
|
agents events --audit Operational events only (secrets / teams / ...)
|
|
91
118
|
agents events --event pr.opened --since 7d
|
|
92
119
|
agents events --module secrets Every secret accessed or revealed
|
|
93
|
-
agents events -f Live tail (operational)
|
|
120
|
+
agents events -f Live tail (operational)
|
|
121
|
+
agents events --event pr.opened --since 30d --limit 0 --json
|
|
122
|
+
Every match — use --limit 0 whenever you
|
|
123
|
+
aggregate, or you rank only the newest 50`)
|
|
94
124
|
.action(async (options) => {
|
|
95
125
|
if (options.follow) {
|
|
96
126
|
await followLog();
|
|
97
127
|
return;
|
|
98
128
|
}
|
|
99
|
-
|
|
129
|
+
let limit;
|
|
100
130
|
let startDate;
|
|
101
131
|
try {
|
|
132
|
+
limit = resolveEventsLimit(options.limit);
|
|
102
133
|
startDate = options.since ? parseSince(options.since) : undefined;
|
|
103
134
|
}
|
|
104
135
|
catch (err) {
|
|
105
136
|
console.error(chalk.red(err.message));
|
|
106
137
|
process.exit(2);
|
|
107
138
|
}
|
|
108
|
-
|
|
139
|
+
// Read one past the cap so we can tell a full result from a clipped one.
|
|
140
|
+
const fetched = readUnifiedEvents({
|
|
109
141
|
startDate,
|
|
110
142
|
eventTypes: options.event && options.event.length ? options.event : undefined,
|
|
111
143
|
agent: options.agent,
|
|
112
144
|
command: options.command,
|
|
113
145
|
module: options.module,
|
|
114
|
-
limit,
|
|
146
|
+
limit: limit === undefined ? undefined : limit + 1,
|
|
115
147
|
includeActivity: !options.audit,
|
|
116
148
|
});
|
|
149
|
+
const { records, truncated } = capRecords(fetched, limit);
|
|
150
|
+
const capNote = `Showing the newest ${limit} — more events matched. Pass --limit 0 for all.`;
|
|
117
151
|
if (options.json) {
|
|
152
|
+
// Notice goes to stderr so `--json | jq` still receives clean JSON.
|
|
153
|
+
if (truncated)
|
|
154
|
+
console.error(chalk.yellow(capNote));
|
|
118
155
|
console.log(JSON.stringify(records, null, 2));
|
|
119
156
|
return;
|
|
120
157
|
}
|
|
@@ -126,6 +163,8 @@ Examples:
|
|
|
126
163
|
for (const r of records.slice().reverse())
|
|
127
164
|
console.log(renderRow(r));
|
|
128
165
|
console.log(chalk.gray(`\n${records.length} event(s). Log: ${getLogsPath()}`));
|
|
166
|
+
if (truncated)
|
|
167
|
+
console.log(chalk.yellow(capNote));
|
|
129
168
|
});
|
|
130
169
|
}
|
|
131
170
|
/** commander repeatable-option collector. */
|
package/dist/commands/models.js
CHANGED
|
@@ -13,7 +13,7 @@ import { listInstalledVersions, getGlobalDefault, resolveVersion, resolveVersion
|
|
|
13
13
|
import { getModelCatalog, locateModelSource } from '../lib/models.js';
|
|
14
14
|
import { terminalWidth, truncateToWidth, stringWidth } from '../lib/session/width.js';
|
|
15
15
|
import { wrapJoined } from './inspect.js';
|
|
16
|
-
const MODEL_CAPABLE_AGENTS = ['claude', 'codex', 'opencode', 'cursor', 'openclaw', 'antigravity', 'kimi'];
|
|
16
|
+
const MODEL_CAPABLE_AGENTS = ['claude', 'codex', 'opencode', 'cursor', 'openclaw', 'antigravity', 'kimi', 'grok'];
|
|
17
17
|
/**
|
|
18
18
|
* Agents that don't necessarily install under ~/.agents/versions (cursor ships
|
|
19
19
|
* via a curl script). For these, fall back to the PATH binary and synthesize
|
|
@@ -9,7 +9,7 @@ import fs from 'node:fs';
|
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import chalk from 'chalk';
|
|
11
11
|
import { truncate, humanDuration } from '../lib/format.js';
|
|
12
|
-
import { parseSession, sanitizeForTerminal } from '../lib/session/parse.js';
|
|
12
|
+
import { parseSession, sanitizeForTerminal, SNAPSHOT_TODO_TOOLS } from '../lib/session/parse.js';
|
|
13
13
|
import { cleanSessionPrompt, extractSessionTopic } from '../lib/session/prompt.js';
|
|
14
14
|
import { linkPath, linkUrl, relativeToCwd, shortenModel } from '../lib/session/render.js';
|
|
15
15
|
import { linearIssueUrl } from '../lib/session/linear.js';
|
|
@@ -312,9 +312,10 @@ function formatCompactPreview(events, session) {
|
|
|
312
312
|
if (!planFile && p && /\/plans\/[^/]+\.md$/.test(p)) {
|
|
313
313
|
planFile = p;
|
|
314
314
|
}
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
|
|
315
|
+
// Every harness's checklist-snapshot tool (Claude TodoWrite, Kimi TodoList,
|
|
316
|
+
// Codex update_plan, …) — the same registry the state engine folds through
|
|
317
|
+
// extractTodoProgress. Prefer the most recent write.
|
|
318
|
+
if (SNAPSHOT_TODO_TOOLS.has(tool)) {
|
|
318
319
|
const progress = extractTodoProgress(event.args);
|
|
319
320
|
if (progress)
|
|
320
321
|
latestTodos = progress;
|
|
Binary file
|
|
Binary file
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<dict>
|
|
7
7
|
<key>Resources/AppIcon.icns</key>
|
|
8
8
|
<data>
|
|
9
|
-
|
|
9
|
+
DFq5H08EkhgWIC3UvGMR9B58BZw=
|
|
10
10
|
</data>
|
|
11
11
|
</dict>
|
|
12
12
|
<key>files2</key>
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<dict>
|
|
16
16
|
<key>hash2</key>
|
|
17
17
|
<data>
|
|
18
|
-
|
|
18
|
+
mBSjM6jlvN7J1jQowsvqTl7CHM0pur0e6qsHMuzk5k0=
|
|
19
19
|
</data>
|
|
20
20
|
</dict>
|
|
21
21
|
</dict>
|
package/dist/lib/models.d.ts
CHANGED
|
@@ -57,6 +57,27 @@ export interface ModelSource {
|
|
|
57
57
|
* Returns null if nothing usable is found.
|
|
58
58
|
*/
|
|
59
59
|
export declare function locateModelSource(agent: AgentId, version: string): ModelSource | null;
|
|
60
|
+
/**
|
|
61
|
+
* Parse `grok models` stdout into a catalog. Exported for unit tests.
|
|
62
|
+
*
|
|
63
|
+
* Output shape (verified 0.2.118):
|
|
64
|
+
* You are logged in with grok.com.
|
|
65
|
+
*
|
|
66
|
+
* Default model: grok-4.5
|
|
67
|
+
*
|
|
68
|
+
* Available models:
|
|
69
|
+
* * grok-4.5 (default)
|
|
70
|
+
*
|
|
71
|
+
* The `Default model:` line is authoritative; rows may also carry a leading `*`
|
|
72
|
+
* and a `(default)` flag. Grok has no `--json` on this subcommand. Settings live
|
|
73
|
+
* in `config.toml` / `models_cache.json`, not `settings.json`, so the native
|
|
74
|
+
* settings.json reader cannot surface the default — the catalog is the source
|
|
75
|
+
* that makes `resolveConfiguredModel` return a cli-default for Grok.
|
|
76
|
+
*/
|
|
77
|
+
export declare function parseGrokModelsStdout(stdout: string): {
|
|
78
|
+
models: ModelInfo[];
|
|
79
|
+
aliases: Record<string, string>;
|
|
80
|
+
};
|
|
60
81
|
/**
|
|
61
82
|
* Build (or load from cache) the model catalog for a specific (agent, version).
|
|
62
83
|
* Cache is keyed on source-file mtime (binary or js module), so re-extracts
|
package/dist/lib/models.js
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Each agent ships its model list differently -- Claude and Codex embed it in
|
|
5
5
|
* compiled bundles/binaries, Gemini exports it from a JS module, and OpenCode/
|
|
6
|
-
* Cursor/OpenClaw expose it via CLI commands. This
|
|
7
|
-
* `getModelCatalog()` and `resolveModel()` interface
|
|
8
|
-
* by a file-system cache keyed on source mtime.
|
|
6
|
+
* Cursor/OpenClaw/Antigravity/Kimi/Grok expose it via CLI commands. This
|
|
7
|
+
* module provides a unified `getModelCatalog()` and `resolveModel()` interface
|
|
8
|
+
* over all of them, backed by a file-system cache keyed on source mtime.
|
|
9
9
|
*/
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import { execFileSync } from 'child_process';
|
|
13
13
|
import chalk from 'chalk';
|
|
14
|
-
import { getVersionDir, getVersionHomePath } from './versions.js';
|
|
14
|
+
import { getVersionDir, getVersionHomePath, getBinaryPath } from './versions.js';
|
|
15
15
|
import { getModelsCachePath } from './state.js';
|
|
16
16
|
import { agentConfigDirName } from './agents.js';
|
|
17
17
|
import { resolveRunDefaults } from './run-defaults.js';
|
|
@@ -188,6 +188,36 @@ export function locateModelSource(agent, version) {
|
|
|
188
188
|
return { path: pathBin, kind: 'cli' };
|
|
189
189
|
return null;
|
|
190
190
|
}
|
|
191
|
+
if (agent === 'grok') {
|
|
192
|
+
// Grok ships a native binary under the version home's `.grok/downloads/`,
|
|
193
|
+
// not node_modules/.bin. Prefer a real binary over a failed-download stub
|
|
194
|
+
// (a 99-byte placeholder sometimes left beside a prior good download).
|
|
195
|
+
const preferred = getBinaryPath('grok', version);
|
|
196
|
+
if (isUsableGrokBinary(preferred))
|
|
197
|
+
return { path: preferred, kind: 'cli' };
|
|
198
|
+
const downloads = path.join(getVersionHomePath('grok', version), '.grok', 'downloads');
|
|
199
|
+
try {
|
|
200
|
+
const candidates = fs
|
|
201
|
+
.readdirSync(downloads)
|
|
202
|
+
.filter((e) => e.startsWith('grok-'))
|
|
203
|
+
.map((e) => path.join(downloads, e))
|
|
204
|
+
.filter(isUsableGrokBinary)
|
|
205
|
+
.sort((a, b) => {
|
|
206
|
+
try {
|
|
207
|
+
return fs.statSync(b).size - fs.statSync(a).size;
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
if (candidates[0])
|
|
214
|
+
return { path: candidates[0], kind: 'cli' };
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
/* empty downloads */
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
191
221
|
if (agent === 'cursor') {
|
|
192
222
|
// cursor-agent is installed via curl script, not agents-cli. Version argument
|
|
193
223
|
// is accepted for API symmetry but ignored -- cursor lives on PATH.
|
|
@@ -198,6 +228,16 @@ export function locateModelSource(agent, version) {
|
|
|
198
228
|
}
|
|
199
229
|
return null;
|
|
200
230
|
}
|
|
231
|
+
/** Real Grok binaries are ~100MB+; failed-download stubs are tens of bytes. */
|
|
232
|
+
function isUsableGrokBinary(filePath) {
|
|
233
|
+
try {
|
|
234
|
+
const st = fs.statSync(filePath);
|
|
235
|
+
return st.isFile() && st.size > 1024 * 1024;
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
201
241
|
/** Search PATH for a command and return its absolute path, or null. */
|
|
202
242
|
function findOnPath(command) {
|
|
203
243
|
const pathEnv = process.env.PATH || '';
|
|
@@ -679,6 +719,93 @@ function extractAntigravityCatalog(binaryPath) {
|
|
|
679
719
|
}
|
|
680
720
|
return { models, aliases: {} };
|
|
681
721
|
}
|
|
722
|
+
/**
|
|
723
|
+
* Parse `grok models` stdout into a catalog. Exported for unit tests.
|
|
724
|
+
*
|
|
725
|
+
* Output shape (verified 0.2.118):
|
|
726
|
+
* You are logged in with grok.com.
|
|
727
|
+
*
|
|
728
|
+
* Default model: grok-4.5
|
|
729
|
+
*
|
|
730
|
+
* Available models:
|
|
731
|
+
* * grok-4.5 (default)
|
|
732
|
+
*
|
|
733
|
+
* The `Default model:` line is authoritative; rows may also carry a leading `*`
|
|
734
|
+
* and a `(default)` flag. Grok has no `--json` on this subcommand. Settings live
|
|
735
|
+
* in `config.toml` / `models_cache.json`, not `settings.json`, so the native
|
|
736
|
+
* settings.json reader cannot surface the default — the catalog is the source
|
|
737
|
+
* that makes `resolveConfiguredModel` return a cli-default for Grok.
|
|
738
|
+
*/
|
|
739
|
+
export function parseGrokModelsStdout(stdout) {
|
|
740
|
+
// Strip ANSI in case a spinner or color codes slip through.
|
|
741
|
+
// eslint-disable-next-line no-control-regex
|
|
742
|
+
const plain = stdout.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
|
|
743
|
+
let defaultId = null;
|
|
744
|
+
const defaultLine = plain.match(/Default model:\s*(\S+)/i);
|
|
745
|
+
if (defaultLine)
|
|
746
|
+
defaultId = defaultLine[1];
|
|
747
|
+
const models = [];
|
|
748
|
+
const seen = new Set();
|
|
749
|
+
for (const raw of plain.split('\n')) {
|
|
750
|
+
const line = raw.trim();
|
|
751
|
+
if (!line)
|
|
752
|
+
continue;
|
|
753
|
+
// Rows: "* grok-4.5 (default)" or "grok-4.5" or " grok-code-fast-1"
|
|
754
|
+
const m = line.match(/^\*?\s*([A-Za-z0-9][A-Za-z0-9._-]*)(?:\s+\(([^)]*)\))?\s*$/);
|
|
755
|
+
if (!m)
|
|
756
|
+
continue;
|
|
757
|
+
const id = m[1];
|
|
758
|
+
// Real model ids are grok-* (or match the Default model: line). Skip banner words.
|
|
759
|
+
if (!/^grok[-_]/i.test(id) && id !== defaultId)
|
|
760
|
+
continue;
|
|
761
|
+
if (seen.has(id))
|
|
762
|
+
continue;
|
|
763
|
+
seen.add(id);
|
|
764
|
+
const flags = (m[2] ?? '').split(',').map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
765
|
+
models.push({
|
|
766
|
+
id,
|
|
767
|
+
isDefault: (defaultId != null && id === defaultId) || flags.includes('default'),
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
// If Default model was set but did not appear as a row, still surface it.
|
|
771
|
+
if (defaultId && !seen.has(defaultId)) {
|
|
772
|
+
models.unshift({ id: defaultId, isDefault: true });
|
|
773
|
+
}
|
|
774
|
+
// Normalize: exactly one default when we know the Default model: id.
|
|
775
|
+
if (defaultId) {
|
|
776
|
+
for (const model of models)
|
|
777
|
+
model.isDefault = model.id === defaultId;
|
|
778
|
+
}
|
|
779
|
+
else if (models.length > 0 && !models.some((model) => model.isDefault)) {
|
|
780
|
+
models[0].isDefault = true;
|
|
781
|
+
}
|
|
782
|
+
return { models, aliases: {} };
|
|
783
|
+
}
|
|
784
|
+
/** Extract Grok's catalog via `grok models` (see parseGrokModelsStdout). */
|
|
785
|
+
function extractGrokCatalog(binaryPath) {
|
|
786
|
+
const env = { ...process.env };
|
|
787
|
+
// Point GROK_HOME at the version home that owns this binary so auth +
|
|
788
|
+
// models_cache come from the right install, not a host ~/.grok symlink.
|
|
789
|
+
// binary: <home>/.grok/downloads/grok-<ver>-...
|
|
790
|
+
const downloadsDir = path.dirname(binaryPath);
|
|
791
|
+
if (path.basename(downloadsDir) === 'downloads') {
|
|
792
|
+
env.GROK_HOME = path.dirname(downloadsDir);
|
|
793
|
+
}
|
|
794
|
+
let stdout;
|
|
795
|
+
try {
|
|
796
|
+
stdout = execFileSync(binaryPath, ['models'], {
|
|
797
|
+
encoding: 'utf-8',
|
|
798
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
799
|
+
timeout: 15_000,
|
|
800
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
801
|
+
env,
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
catch {
|
|
805
|
+
return { models: [], aliases: {} };
|
|
806
|
+
}
|
|
807
|
+
return parseGrokModelsStdout(stdout);
|
|
808
|
+
}
|
|
682
809
|
/**
|
|
683
810
|
* Extract Kimi's catalog via `kimi provider list --json`, which emits the raw
|
|
684
811
|
* providers/models config. Model ids are the `models` object keys (e.g.
|
|
@@ -789,6 +916,8 @@ export function getModelCatalog(agent, version) {
|
|
|
789
916
|
({ models, aliases } = extractAntigravityCatalog(src.path));
|
|
790
917
|
else if (agent === 'kimi')
|
|
791
918
|
({ models, aliases } = extractKimiCatalog(src.path));
|
|
919
|
+
else if (agent === 'grok')
|
|
920
|
+
({ models, aliases } = extractGrokCatalog(src.path));
|
|
792
921
|
}
|
|
793
922
|
const catalog = {
|
|
794
923
|
agent,
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<dict>
|
|
7
7
|
<key>Resources/AppIcon.icns</key>
|
|
8
8
|
<data>
|
|
9
|
-
|
|
9
|
+
DFq5H08EkhgWIC3UvGMR9B58BZw=
|
|
10
10
|
</data>
|
|
11
11
|
</dict>
|
|
12
12
|
<key>files2</key>
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<dict>
|
|
16
16
|
<key>hash2</key>
|
|
17
17
|
<data>
|
|
18
|
-
|
|
18
|
+
mBSjM6jlvN7J1jQowsvqTl7CHM0pur0e6qsHMuzk5k0=
|
|
19
19
|
</data>
|
|
20
20
|
</dict>
|
|
21
21
|
<key>embedded.provisionprofile</key>
|
|
@@ -27,6 +27,17 @@ export declare function safeReadSessionFile(filePath: string, maxBytes?: number)
|
|
|
27
27
|
export declare function parseSession(filePath: string, agent?: SessionAgentId): SessionEvent[];
|
|
28
28
|
/** Infer the agent type from a session file path using known directory conventions. */
|
|
29
29
|
export declare function detectAgent(filePath: string): SessionAgentId | null;
|
|
30
|
+
/**
|
|
31
|
+
* Checklist-snapshot tool names across harnesses — each sends the WHOLE list on
|
|
32
|
+
* every write, so the last call is the current checklist. Claude `TodoWrite`,
|
|
33
|
+
* Kimi `TodoList`, Droid/OpenCode `todo_write`, Codex `update_plan`.
|
|
34
|
+
*/
|
|
35
|
+
export declare const SNAPSHOT_TODO_TOOLS: Set<string>;
|
|
36
|
+
/**
|
|
37
|
+
* Whether a harness's checklist status means "finished". Claude/Codex write
|
|
38
|
+
* `completed`; Kimi writes `done`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isCompletedTodoStatus(status: unknown): boolean;
|
|
30
41
|
/**
|
|
31
42
|
* Summarize a tool_use into a one-liner string.
|
|
32
43
|
*/
|
|
@@ -205,6 +205,19 @@ export function detectAgent(filePath) {
|
|
|
205
205
|
return 'gemini';
|
|
206
206
|
return null;
|
|
207
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Checklist-snapshot tool names across harnesses — each sends the WHOLE list on
|
|
210
|
+
* every write, so the last call is the current checklist. Claude `TodoWrite`,
|
|
211
|
+
* Kimi `TodoList`, Droid/OpenCode `todo_write`, Codex `update_plan`.
|
|
212
|
+
*/
|
|
213
|
+
export const SNAPSHOT_TODO_TOOLS = new Set(['TodoWrite', 'TodoList', 'todo_write', 'update_plan']);
|
|
214
|
+
/**
|
|
215
|
+
* Whether a harness's checklist status means "finished". Claude/Codex write
|
|
216
|
+
* `completed`; Kimi writes `done`.
|
|
217
|
+
*/
|
|
218
|
+
export function isCompletedTodoStatus(status) {
|
|
219
|
+
return status === 'completed' || status === 'done';
|
|
220
|
+
}
|
|
208
221
|
/**
|
|
209
222
|
* Summarize a tool_use into a one-liner string.
|
|
210
223
|
*/
|
|
@@ -214,12 +227,13 @@ export function summarizeToolUse(tool, args) {
|
|
|
214
227
|
switch (tool) {
|
|
215
228
|
case 'Bash':
|
|
216
229
|
return `Bash: ${truncate(String(args.command || '').replace(/\n/g, ' ').trim(), 120)}`;
|
|
230
|
+
// `path` is the Kimi spelling of Claude's `file_path` for the same tools.
|
|
217
231
|
case 'Read':
|
|
218
|
-
return `Read ${shortenPath(args.file_path || '')}`;
|
|
232
|
+
return `Read ${shortenPath(args.file_path || args.path || '')}`;
|
|
219
233
|
case 'Write':
|
|
220
|
-
return `Write ${shortenPath(args.file_path || '')}`;
|
|
234
|
+
return `Write ${shortenPath(args.file_path || args.path || '')}`;
|
|
221
235
|
case 'Edit':
|
|
222
|
-
return `Edit ${shortenPath(args.file_path || '')}`;
|
|
236
|
+
return `Edit ${shortenPath(args.file_path || args.path || '')}`;
|
|
223
237
|
case 'Glob':
|
|
224
238
|
return `Glob ${args.pattern || ''}`;
|
|
225
239
|
case 'Grep':
|
|
@@ -234,14 +248,17 @@ export function summarizeToolUse(tool, args) {
|
|
|
234
248
|
const steps = Array.isArray(args.plan) ? args.plan.length : 0;
|
|
235
249
|
return `Plan: ${steps} step${steps === 1 ? '' : 's'}`;
|
|
236
250
|
}
|
|
237
|
-
//
|
|
238
|
-
|
|
251
|
+
// Live checklist: show progress + the current step, not a bare "TodoWrite".
|
|
252
|
+
// Claude writes `TodoWrite`, Kimi writes `TodoList`; both carry the whole list
|
|
253
|
+
// under `todos`, with Kimi spelling the item text `title` and "done" `done`.
|
|
254
|
+
case 'TodoWrite':
|
|
255
|
+
case 'TodoList': {
|
|
239
256
|
const todos = Array.isArray(args.todos) ? args.todos : [];
|
|
240
257
|
if (todos.length === 0)
|
|
241
258
|
return 'Plan: 0 steps';
|
|
242
|
-
const done = todos.filter((t) => t?.status
|
|
259
|
+
const done = todos.filter((t) => isCompletedTodoStatus(t?.status)).length;
|
|
243
260
|
const active = todos.find((t) => t?.status === 'in_progress');
|
|
244
|
-
const step = active?.activeForm || active?.content;
|
|
261
|
+
const step = active?.activeForm || active?.content || active?.title;
|
|
245
262
|
return step
|
|
246
263
|
? `Plan ${done}/${todos.length}: ${truncate(String(step), 80)}`
|
|
247
264
|
: `Plan: ${done}/${todos.length} done`;
|
|
@@ -119,11 +119,12 @@ export interface StateContext {
|
|
|
119
119
|
activeWindowMs?: number;
|
|
120
120
|
}
|
|
121
121
|
/**
|
|
122
|
-
* Derive live plan progress from a checklist tool call's args. Accepts
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
122
|
+
* Derive live plan progress from a checklist tool call's args. Accepts Claude's
|
|
123
|
+
* `TodoWrite` (`todos: [{content,status,activeForm}]`), Kimi's `TodoList`
|
|
124
|
+
* (`todos: [{title,status}]`, where finished is `done` rather than `completed`)
|
|
125
|
+
* and Codex's `update_plan` (`plan: [{step,status}]`) shapes, so the CLI is the
|
|
126
|
+
* single source of checklist state for every agent. Returns undefined when there
|
|
127
|
+
* is no usable list, so a session with no plan carries no `todos` field.
|
|
127
128
|
*/
|
|
128
129
|
export declare function extractTodoProgress(args?: Record<string, any>): TodoProgress | undefined;
|
|
129
130
|
/** Fold snapshot checklist tools and Claude TaskCreate/TaskUpdate event logs. */
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* shape + mtime — same function, driven off the normalized events.
|
|
16
16
|
*/
|
|
17
17
|
import * as path from 'path';
|
|
18
|
-
import { summarizeToolUse } from './parse.js';
|
|
18
|
+
import { isCompletedTodoStatus, SNAPSHOT_TODO_TOOLS, summarizeToolUse } from './parse.js';
|
|
19
19
|
/**
|
|
20
20
|
* Detect per-session rate-limit / usage-limit signals in assistant or error
|
|
21
21
|
* text (RUSH-1523). Matches the same shapes Factory's prewarm detectBlockingPrompt
|
|
@@ -48,15 +48,15 @@ const PROSE_QUESTION_FRESH_MS = 30 * 60_000;
|
|
|
48
48
|
/** Claude tool names that structurally mean "the agent handed control back to you". */
|
|
49
49
|
const PLAN_TOOL = 'ExitPlanMode';
|
|
50
50
|
const ASK_TOOL = 'AskUserQuestion';
|
|
51
|
-
const SNAPSHOT_TODO_TOOLS = new Set(['TodoWrite', 'todo_write', 'update_plan']);
|
|
52
51
|
const TASK_CREATE_TOOL = 'TaskCreate';
|
|
53
52
|
const TASK_UPDATE_TOOL = 'TaskUpdate';
|
|
54
53
|
/**
|
|
55
|
-
* Derive live plan progress from a checklist tool call's args. Accepts
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
54
|
+
* Derive live plan progress from a checklist tool call's args. Accepts Claude's
|
|
55
|
+
* `TodoWrite` (`todos: [{content,status,activeForm}]`), Kimi's `TodoList`
|
|
56
|
+
* (`todos: [{title,status}]`, where finished is `done` rather than `completed`)
|
|
57
|
+
* and Codex's `update_plan` (`plan: [{step,status}]`) shapes, so the CLI is the
|
|
58
|
+
* single source of checklist state for every agent. Returns undefined when there
|
|
59
|
+
* is no usable list, so a session with no plan carries no `todos` field.
|
|
60
60
|
*/
|
|
61
61
|
export function extractTodoProgress(args) {
|
|
62
62
|
const input = args?.input && typeof args.input === 'object' ? args.input : args;
|
|
@@ -76,10 +76,16 @@ export function extractTodoProgress(args) {
|
|
|
76
76
|
? t.text
|
|
77
77
|
: typeof t?.step === 'string' && t.step
|
|
78
78
|
? t.step
|
|
79
|
-
:
|
|
79
|
+
: typeof t?.title === 'string' && t.title
|
|
80
|
+
? t.title
|
|
81
|
+
: activeForm ?? '';
|
|
80
82
|
if (!content)
|
|
81
83
|
continue;
|
|
82
|
-
const status = t?.status
|
|
84
|
+
const status = isCompletedTodoStatus(t?.status)
|
|
85
|
+
? 'completed'
|
|
86
|
+
: t?.status === 'in_progress'
|
|
87
|
+
? 'in_progress'
|
|
88
|
+
: 'pending';
|
|
83
89
|
const description = typeof t?.description === 'string' && t.description ? t.description : undefined;
|
|
84
90
|
items.push({ content, status, ...(description ? { description } : {}), ...(activeForm ? { activeForm } : {}) });
|
|
85
91
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.86",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|