@phnx-labs/agents-cli 1.20.87 → 1.20.88
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 +60 -0
- package/README.md +3 -3
- package/dist/bin/agents +0 -0
- package/dist/commands/doctor.d.ts +0 -19
- package/dist/commands/doctor.js +219 -305
- package/dist/commands/exec.js +7 -19
- package/dist/commands/inspect.js +3 -5
- package/dist/commands/routines.js +2 -2
- package/dist/commands/sessions.js +1 -0
- package/dist/commands/ssh.js +3 -3
- package/dist/commands/usage.d.ts +3 -2
- package/dist/commands/usage.js +2 -9
- package/dist/lib/agents.d.ts +31 -1
- package/dist/lib/agents.js +55 -0
- package/dist/lib/command-skills.d.ts +10 -0
- package/dist/lib/command-skills.js +14 -0
- package/dist/lib/commands.js +19 -1
- package/dist/lib/daemon.js +17 -2
- package/dist/lib/devices/doctor-findings.d.ts +167 -0
- package/dist/lib/devices/doctor-findings.js +893 -0
- package/dist/lib/devices/fleet-divergence.d.ts +22 -0
- package/dist/lib/devices/fleet-divergence.js +34 -10
- package/dist/lib/devices/fleet-inventory.d.ts +17 -6
- package/dist/lib/devices/fleet-inventory.js +56 -8
- package/dist/lib/exec.d.ts +14 -3
- package/dist/lib/exec.js +41 -8
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/project-resources.js +34 -20
- package/dist/lib/runner.d.ts +14 -1
- package/dist/lib/runner.js +37 -8
- package/dist/lib/sandbox.d.ts +2 -0
- package/dist/lib/sandbox.js +38 -0
- 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/rc-hygiene.d.ts +0 -6
- package/dist/lib/secrets/rc-hygiene.js +0 -24
- package/dist/lib/session/active.d.ts +6 -6
- package/dist/lib/session/active.js +6 -6
- package/dist/lib/session/discover.d.ts +5 -0
- package/dist/lib/session/discover.js +137 -1
- package/dist/lib/session/parse.d.ts +2 -0
- package/dist/lib/session/parse.js +76 -37
- package/dist/lib/session/sync/agents.js +0 -0
- package/dist/lib/session/types.d.ts +1 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/staleness/detectors/commands.js +14 -5
- package/dist/lib/staleness/types.d.ts +2 -0
- package/dist/lib/staleness/writers/commands.js +13 -7
- package/dist/lib/usage.d.ts +72 -1
- package/dist/lib/usage.js +21 -27
- package/dist/lib/versions.js +30 -13
- package/package.json +1 -1
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Commands writer.
|
|
3
3
|
*
|
|
4
|
-
* Two physical formats,
|
|
4
|
+
* Two physical formats, selected per-(agent, version) at write time. Most
|
|
5
|
+
* agents receive one format; dual-write registry targets receive both:
|
|
5
6
|
*
|
|
6
7
|
* - command-as-skill — fires when `shouldInstallCommandAsSkill(agent, version)`
|
|
7
8
|
* is true. Used for Codex >= 0.117.0 (commands capability ends, skills
|
|
8
9
|
* capability remains) and agents with skills but no native command-file dir
|
|
9
|
-
* such as
|
|
10
|
+
* such as Kimi. Cursor also receives this format in addition to its IDE
|
|
11
|
+
* command file. Writes `{agentDir}/skills/<name>/SKILL.md` with the
|
|
10
12
|
* `agents_command` marker; the agent picks it up as a slash-command equivalent.
|
|
11
13
|
*
|
|
12
14
|
* - native command file — `{agentDir}/<commandsSubdir>/<name>.md` (or .toml
|
|
@@ -23,7 +25,7 @@ import { supports } from '../../capabilities.js';
|
|
|
23
25
|
import { safeJoin } from '../../paths.js';
|
|
24
26
|
import { markdownToToml } from '../../convert.js';
|
|
25
27
|
import { commandAppliesTo, parseCommandMetadata } from '../../commands.js';
|
|
26
|
-
import { installCommandSkillToVersion, shouldInstallCommandAsSkill } from '../../command-skills.js';
|
|
28
|
+
import { installCommandSkillToVersion, shouldAlsoInstallCommandAsSkill, shouldInstallCommandAsSkill, } from '../../command-skills.js';
|
|
27
29
|
import { installGooseCommandToVersion } from '../../goose-commands.js';
|
|
28
30
|
import { resolveCommandSource, trustedSkillRoots } from './sources.js';
|
|
29
31
|
import { lazyAgentMap } from './lazy-map.js';
|
|
@@ -35,6 +37,7 @@ function buildCommandsWriter(agent) {
|
|
|
35
37
|
const agentConfig = AGENTS[agent];
|
|
36
38
|
const agentDir = path.join(versionHome, agentConfigDirName(agent));
|
|
37
39
|
const commandsAsSkills = shouldInstallCommandAsSkill(agent, version);
|
|
40
|
+
const commandsAlsoAsSkills = shouldAlsoInstallCommandAsSkill(agent, version);
|
|
38
41
|
const supportsCommands = supports(agent, 'commands', version).ok;
|
|
39
42
|
// Version-gated agents (e.g. goose skills >= 1.25.0) are registered but
|
|
40
43
|
// may be called at a version too old for both paths — skip gracefully.
|
|
@@ -54,23 +57,25 @@ function buildCommandsWriter(agent) {
|
|
|
54
57
|
const metadata = parseCommandMetadata(srcFile);
|
|
55
58
|
if (!commandAppliesTo(agent, version, metadata).ok)
|
|
56
59
|
continue;
|
|
57
|
-
if (commandsAsSkills) {
|
|
60
|
+
if (commandsAsSkills || commandsAlsoAsSkills) {
|
|
58
61
|
const installed = installCommandSkillToVersion(agentDir, cmd, srcFile, skillRoots);
|
|
62
|
+
// installed.skipped means a real skill source already owns this name,
|
|
63
|
+
// which is a deliberate no-op, not a failure — the native file is still written.
|
|
59
64
|
if (!installed.success)
|
|
60
65
|
continue;
|
|
61
66
|
}
|
|
62
|
-
|
|
67
|
+
if (supportsCommands && agent === 'goose') {
|
|
63
68
|
// Goose: recipe YAML + config.yaml slash_commands entry, not a file copy.
|
|
64
69
|
const installed = installGooseCommandToVersion(versionHome, cmd, srcFile);
|
|
65
70
|
if (!installed.success)
|
|
66
71
|
continue;
|
|
67
72
|
}
|
|
68
|
-
else if (agentConfig.format === 'toml') {
|
|
73
|
+
else if (supportsCommands && agentConfig.format === 'toml') {
|
|
69
74
|
const content = fs.readFileSync(srcFile, 'utf-8');
|
|
70
75
|
const tomlContent = markdownToToml(cmd, content);
|
|
71
76
|
fs.writeFileSync(safeJoin(commandsTarget, `${cmd}.toml`), tomlContent);
|
|
72
77
|
}
|
|
73
|
-
else {
|
|
78
|
+
else if (supportsCommands) {
|
|
74
79
|
fs.copyFileSync(srcFile, safeJoin(commandsTarget, `${cmd}.md`));
|
|
75
80
|
}
|
|
76
81
|
synced.push(cmd);
|
|
@@ -84,6 +89,7 @@ function buildCommandsWriter(agent) {
|
|
|
84
89
|
// Registration covers two cases:
|
|
85
90
|
// - native commands (claude, codex < 0.117.0, grok, etc.) — `commands` cap
|
|
86
91
|
// - commands-as-skills (kimi, codex >= 0.117.0)
|
|
92
|
+
// - dual-write commands plus command-skills (cursor)
|
|
87
93
|
//
|
|
88
94
|
// Agents that have skills but use a NATIVE non-file slash-command system
|
|
89
95
|
// (openclaw → Gateway-based commands) are NOT registered. They declare
|
package/dist/lib/usage.d.ts
CHANGED
|
@@ -66,7 +66,35 @@ interface ClaudeOauthCredentials {
|
|
|
66
66
|
rateLimitTier?: string | null;
|
|
67
67
|
organizationUuid?: string | null;
|
|
68
68
|
}
|
|
69
|
-
/**
|
|
69
|
+
/** The single registry of agent usage sources and their transport. */
|
|
70
|
+
declare const USAGE_SOURCES: {
|
|
71
|
+
readonly claude: {
|
|
72
|
+
readonly fetch: typeof getClaudeUsageInfo;
|
|
73
|
+
readonly network: true;
|
|
74
|
+
};
|
|
75
|
+
readonly codex: {
|
|
76
|
+
readonly fetch: typeof getCodexUsageInfo;
|
|
77
|
+
readonly network: false;
|
|
78
|
+
};
|
|
79
|
+
readonly kimi: {
|
|
80
|
+
readonly fetch: typeof getKimiUsageInfo;
|
|
81
|
+
readonly network: true;
|
|
82
|
+
};
|
|
83
|
+
readonly droid: {
|
|
84
|
+
readonly fetch: typeof getDroidUsageInfo;
|
|
85
|
+
readonly network: true;
|
|
86
|
+
};
|
|
87
|
+
readonly grok: {
|
|
88
|
+
readonly fetch: typeof getGrokUsageInfo;
|
|
89
|
+
readonly network: false;
|
|
90
|
+
};
|
|
91
|
+
readonly cursor: {
|
|
92
|
+
readonly fetch: typeof getCursorUsageInfo;
|
|
93
|
+
readonly network: true;
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
export declare const USAGE_SOURCE_AGENT_IDS: (keyof typeof USAGE_SOURCES)[];
|
|
97
|
+
/** Fetch usage info for a given agent through the canonical source registry. */
|
|
70
98
|
export declare function getUsageInfo(agentId: AgentId, options?: UsageOptions): Promise<UsageInfo>;
|
|
71
99
|
/** Derive a stable lookup key from account info for usage deduplication. */
|
|
72
100
|
export declare function getUsageLookupKey(info?: Pick<AccountInfo, 'usageKey' | 'accountKey'> | null): string | null;
|
|
@@ -151,6 +179,8 @@ export declare function deriveUsageStatusFromSnapshot(snapshot: UsageSnapshot |
|
|
|
151
179
|
export declare function formatUsageStatusBadge(usageStatus: 'available' | 'rate_limited' | 'out_of_credits' | null | undefined): string;
|
|
152
180
|
/** Format a multi-line usage section for detailed agent views. */
|
|
153
181
|
export declare function formatUsageSection(usage: UsageInfo): string[];
|
|
182
|
+
/** Fetch Codex usage by scanning the most recent session files for rate-limit events. */
|
|
183
|
+
declare function getCodexUsageInfo(options?: UsageOptions): Promise<UsageInfo>;
|
|
154
184
|
/**
|
|
155
185
|
* The access token to use for a READ-ONLY Claude usage fetch, or null when the
|
|
156
186
|
* stored token is within the refresh leeway.
|
|
@@ -167,6 +197,8 @@ export declare function formatUsageSection(usage: UsageInfo): string[];
|
|
|
167
197
|
* Pure — unit-tested.
|
|
168
198
|
*/
|
|
169
199
|
export declare function claudeUsageAccessTokenNoRefresh(oauth: Pick<ClaudeOauthCredentials, 'accessToken' | 'expiresAt'>): string | null;
|
|
200
|
+
/** Fetch Claude usage via the Anthropic OAuth usage API. */
|
|
201
|
+
declare function getClaudeUsageInfo(options?: UsageOptions): Promise<UsageInfo>;
|
|
170
202
|
/** Raw quota bucket from the Kimi /usages response (numbers arrive as strings). */
|
|
171
203
|
interface KimiUsageQuota {
|
|
172
204
|
limit?: string | number | null;
|
|
@@ -192,6 +224,18 @@ export interface KimiUsagesResponse {
|
|
|
192
224
|
} | null> | null;
|
|
193
225
|
subType?: string | null;
|
|
194
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Fetch Kimi usage via the Kimi Code /usages API. Kimi's JWT has no email
|
|
229
|
+
* claim, so the account row can't show an address — but /usages returns quota
|
|
230
|
+
* windows and the membership tier, which is what we render.
|
|
231
|
+
*
|
|
232
|
+
* Deliberately NO token refresh: `agents view` is a read/inspect command and
|
|
233
|
+
* must not rotate the user's Kimi OAuth credential (rewriting the file,
|
|
234
|
+
* invalidating the old refresh token, racing a concurrently-running kimi CLI).
|
|
235
|
+
* The kimi CLI refreshes on its own launch; if the stored token is expired we
|
|
236
|
+
* skip the live fetch and let the SWR cache serve the last-seen snapshot.
|
|
237
|
+
*/
|
|
238
|
+
declare function getKimiUsageInfo(options?: UsageOptions): Promise<UsageInfo>;
|
|
195
239
|
/** Normalize the Kimi /usages payload into the common UsageWindow shape. */
|
|
196
240
|
export declare function normalizeKimiWindows(data: KimiUsagesResponse): UsageWindow[];
|
|
197
241
|
/** Derive a display plan label from Kimi's membership tier or subscription type. */
|
|
@@ -212,6 +256,22 @@ export interface DroidBillingLimitsResponse {
|
|
|
212
256
|
} | null;
|
|
213
257
|
} | null;
|
|
214
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Fetch Droid usage via Factory's billing limits API — the same endpoint the
|
|
261
|
+
* droid CLI polls for its token-limit banner. The WorkOS access token comes
|
|
262
|
+
* from the locally decrypted ~/.factory/auth.v2.file (the same credential
|
|
263
|
+
* account identity in agents.ts reads).
|
|
264
|
+
*
|
|
265
|
+
* Deliberately NO token refresh, for a sharper reason than Kimi's: WorkOS
|
|
266
|
+
* refresh tokens are single-use and rotate on every exchange, so refreshing
|
|
267
|
+
* here would race a concurrently running droid session and can permanently
|
|
268
|
+
* invalidate the user's login chain. Droid refreshes its own credential when
|
|
269
|
+
* it runs; if the stored token is expired we skip the live fetch and let the
|
|
270
|
+
* SWR cache serve the last-seen snapshot. This same single-use-rotation property
|
|
271
|
+
* is why `agents apply` refuses to propagate droid credentials across machines
|
|
272
|
+
* (see `isCredentialSafeToPropagate` in `../fleet/auth-sync.ts`).
|
|
273
|
+
*/
|
|
274
|
+
declare function getDroidUsageInfo(options?: UsageOptions): Promise<UsageInfo>;
|
|
215
275
|
/**
|
|
216
276
|
* Live auth probes — the same authenticated GET the usage fetchers above do,
|
|
217
277
|
* but surfacing the raw HTTP status instead of swallowing 401/expired to null.
|
|
@@ -296,6 +356,8 @@ export declare function writeClaudeUsageCache(usageKey: string, snapshot: UsageS
|
|
|
296
356
|
* Returns true only when a valid access token can be obtained.
|
|
297
357
|
*/
|
|
298
358
|
export declare function isClaudeAuthValid(home?: string): Promise<boolean>;
|
|
359
|
+
/** Parse the latest billing info from Grok's unified log. */
|
|
360
|
+
declare function getGrokUsageInfo(options?: UsageOptions): Promise<UsageInfo>;
|
|
299
361
|
/** Per-model bucket in Cursor's /api/usage response. */
|
|
300
362
|
interface CursorUsageModel {
|
|
301
363
|
numRequests?: number | null;
|
|
@@ -319,4 +381,13 @@ export interface CursorUsageResponse {
|
|
|
319
381
|
* bar to draw here and return no windows rather than a misleading empty gauge.
|
|
320
382
|
*/
|
|
321
383
|
export declare function normalizeCursorUsage(data: CursorUsageResponse): UsageWindow[];
|
|
384
|
+
/**
|
|
385
|
+
* Fetch Cursor usage from the dashboard usage endpoint. Cursor authenticates the
|
|
386
|
+
* request with a `WorkosCursorSessionToken` cookie of the form
|
|
387
|
+
* `<oauth-subject>::<access-token>` (the same pair the web dashboard sends), not a
|
|
388
|
+
* bearer header. Free/legacy plans return a monthly request bar; usage-based plans
|
|
389
|
+
* have no request cap, so they return a live-but-window-less snapshot (the account
|
|
390
|
+
* row still renders, without a misleading empty gauge).
|
|
391
|
+
*/
|
|
392
|
+
declare function getCursorUsageInfo(options?: UsageOptions): Promise<UsageInfo>;
|
|
322
393
|
export {};
|
package/dist/lib/usage.js
CHANGED
|
@@ -59,24 +59,23 @@ const COMPACT_BAR_LEN = 5;
|
|
|
59
59
|
const USAGE_BAR_LEN = 10;
|
|
60
60
|
const FULL = '\u2588';
|
|
61
61
|
const EMPTY = '\u2591';
|
|
62
|
-
/**
|
|
62
|
+
/** The single registry of agent usage sources and their transport. */
|
|
63
|
+
const USAGE_SOURCES = {
|
|
64
|
+
claude: { fetch: getClaudeUsageInfo, network: true },
|
|
65
|
+
codex: { fetch: getCodexUsageInfo, network: false },
|
|
66
|
+
kimi: { fetch: getKimiUsageInfo, network: true },
|
|
67
|
+
droid: { fetch: getDroidUsageInfo, network: true },
|
|
68
|
+
grok: { fetch: getGrokUsageInfo, network: false },
|
|
69
|
+
cursor: { fetch: getCursorUsageInfo, network: true },
|
|
70
|
+
};
|
|
71
|
+
export const USAGE_SOURCE_AGENT_IDS = Object.keys(USAGE_SOURCES);
|
|
72
|
+
function getUsageSource(agentId) {
|
|
73
|
+
return USAGE_SOURCES[agentId];
|
|
74
|
+
}
|
|
75
|
+
/** Fetch usage info for a given agent through the canonical source registry. */
|
|
63
76
|
export async function getUsageInfo(agentId, options) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return getClaudeUsageInfo(options);
|
|
67
|
-
case 'codex':
|
|
68
|
-
return getCodexUsageInfo(options);
|
|
69
|
-
case 'kimi':
|
|
70
|
-
return getKimiUsageInfo(options);
|
|
71
|
-
case 'droid':
|
|
72
|
-
return getDroidUsageInfo(options);
|
|
73
|
-
case 'grok':
|
|
74
|
-
return getGrokUsageInfo(options);
|
|
75
|
-
case 'cursor':
|
|
76
|
-
return getCursorUsageInfo(options);
|
|
77
|
-
default:
|
|
78
|
-
return { snapshot: null, error: null };
|
|
79
|
-
}
|
|
77
|
+
const source = getUsageSource(agentId);
|
|
78
|
+
return source ? source.fetch(options) : { snapshot: null, error: null };
|
|
80
79
|
}
|
|
81
80
|
/** Derive a stable lookup key from account info for usage deduplication. */
|
|
82
81
|
export function getUsageLookupKey(info) {
|
|
@@ -117,12 +116,7 @@ export function buildCanonicalUsageContext(inputs) {
|
|
|
117
116
|
* versus simply not applicable (Antigravity, OpenCode).
|
|
118
117
|
*/
|
|
119
118
|
export function agentReportsUsage(agentId) {
|
|
120
|
-
return (agentId
|
|
121
|
-
agentId === 'codex' ||
|
|
122
|
-
agentId === 'kimi' ||
|
|
123
|
-
agentId === 'droid' ||
|
|
124
|
-
agentId === 'grok' ||
|
|
125
|
-
agentId === 'cursor');
|
|
119
|
+
return getUsageSource(agentId) !== undefined;
|
|
126
120
|
}
|
|
127
121
|
/** Fetch usage info for all unique accounts in parallel, keyed by usage key. */
|
|
128
122
|
export async function getUsageInfoByIdentity(inputs, opts) {
|
|
@@ -160,14 +154,14 @@ const inFlightRefreshes = new Map();
|
|
|
160
154
|
export async function getUsageInfoForIdentity(input, opts) {
|
|
161
155
|
const usageKey = getUsageLookupKey(input.info);
|
|
162
156
|
const forceRefresh = opts?.forceRefresh === true;
|
|
163
|
-
// Agents whose usage
|
|
157
|
+
// Agents whose registered usage source makes a live network call go
|
|
164
158
|
// through the stale-while-revalidate cache below so `agents run`/`agents view`
|
|
165
159
|
// stay off the network on the hot path. Everything else (Codex reads local
|
|
166
160
|
// session logs) takes the legacy blocking path. The on-disk cache is shared and
|
|
167
161
|
// keyed by usageKey, which is namespaced per agent (`claude:org=…`,
|
|
168
|
-
// `kimi:user=…`, `droid:org=…`), so one cache file holds every
|
|
169
|
-
// collision.
|
|
170
|
-
const usesNetworkUsage = input.agentId ===
|
|
162
|
+
// `kimi:user=…`, `droid:org=…`, `cursor:user=…`), so one cache file holds every
|
|
163
|
+
// account without collision.
|
|
164
|
+
const usesNetworkUsage = getUsageSource(input.agentId)?.network === true;
|
|
171
165
|
if (!usesNetworkUsage || !usageKey) {
|
|
172
166
|
return getUsageInfo(input.agentId, {
|
|
173
167
|
home: input.home,
|
package/dist/lib/versions.js
CHANGED
|
@@ -43,7 +43,7 @@ import { discoverPlugins, marketplaceSpecForName } from './plugins.js';
|
|
|
43
43
|
import { loadManifest, saveManifest, buildManifest as buildSyncManifest, isStale } from './staleness/index.js';
|
|
44
44
|
import { emit } from './events.js';
|
|
45
45
|
import { safeJoin } from './paths.js';
|
|
46
|
-
import { readSkillSourceCommandMarker, shouldInstallCommandAsSkill } from './command-skills.js';
|
|
46
|
+
import { readSkillSourceCommandMarker, shouldAlsoInstallCommandAsSkill, shouldInstallCommandAsSkill, } from './command-skills.js';
|
|
47
47
|
import { getWriter, getDetector } from './staleness/registry.js';
|
|
48
48
|
import { syncMemoryToVersionHome } from './memory.js';
|
|
49
49
|
import { listPluginSkillNames, resolveCommandSource, resolveSkillSource } from './staleness/writers/sources.js';
|
|
@@ -2555,21 +2555,30 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2555
2555
|
};
|
|
2556
2556
|
const trustedCommandNames = (names) => names.filter((name) => resolveCommandSource(name) !== null);
|
|
2557
2557
|
// Sync commands — dispatch through WRITERS.commands. The writer dispatches
|
|
2558
|
-
// between native (file copy / TOML conversion)
|
|
2559
|
-
//
|
|
2560
|
-
// previous COMMANDS_CAPABLE_AGENTS gate excluded
|
|
2561
|
-
//
|
|
2558
|
+
// between native (file copy / TOML conversion), commands-as-skills, and the
|
|
2559
|
+
// registry-driven dual-write path based on the command-skill predicates. The
|
|
2560
|
+
// previous COMMANDS_CAPABLE_AGENTS gate excluded skills-only targets such as
|
|
2561
|
+
// Kimi, silently dropping every converted command.
|
|
2562
2562
|
const commandsWriter = getWriter('commands', agent);
|
|
2563
2563
|
const commandsToSync = selection
|
|
2564
2564
|
? trustedCommandNames(resolveSelection(selection.commands, available.commands))
|
|
2565
2565
|
: trustedCommandNames(available.commands); // No selection = sync all trusted commands, excluding project-only commands
|
|
2566
2566
|
const commandsAsSkills = shouldInstallCommandAsSkill(agent, version);
|
|
2567
|
+
const commandsAlsoAsSkills = shouldAlsoInstallCommandAsSkill(agent, version);
|
|
2568
|
+
const commandsInstallAsSkills = commandsAsSkills || commandsAlsoAsSkills;
|
|
2569
|
+
let writtenCommands = [];
|
|
2567
2570
|
if (commandsToSync.length > 0 && commandsWriter) {
|
|
2568
|
-
|
|
2571
|
+
// Agents that replace native command files with skills (codex >= 0.117.0,
|
|
2572
|
+
// kimi) must have their legacy command dir removed, or files written by an
|
|
2573
|
+
// older version linger forever: the orphan sweep below is gated off for
|
|
2574
|
+
// them, and `agents prune` only sees their skills. Gated on
|
|
2575
|
+
// `commandsAsSkills`, never `commandsAlsoAsSkills` -- a dual-write agent's
|
|
2576
|
+
// native dir is a live product surface (Cursor IDE) and must not be wiped.
|
|
2569
2577
|
if (commandsAsSkills && agentConfig.commandsSubdir) {
|
|
2570
|
-
removePath(
|
|
2578
|
+
removePath(path.join(agentDir, agentConfig.commandsSubdir));
|
|
2571
2579
|
}
|
|
2572
2580
|
const r = commandsWriter.write({ version, versionHome, selection: commandsToSync, cwd });
|
|
2581
|
+
writtenCommands = r.synced;
|
|
2573
2582
|
result.commands = r.synced.length > 0;
|
|
2574
2583
|
}
|
|
2575
2584
|
// Orphan-sweep stale top-level command files from previous syncs under a
|
|
@@ -2584,13 +2593,19 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2584
2593
|
if (fs.existsSync(commandsTargetSweep)) {
|
|
2585
2594
|
const ext = agentConfig.format === 'toml' ? '.toml' : '.md';
|
|
2586
2595
|
const trustedCommands = new Set(commandsToSync);
|
|
2596
|
+
// A dual-write target's native directory also belongs to another product
|
|
2597
|
+
// surface (Cursor IDE). Delete only names the command writer recorded as
|
|
2598
|
+
// successfully emitted during the preceding full sync.
|
|
2599
|
+
const previouslyManagedCommands = commandsAlsoAsSkills
|
|
2600
|
+
? new Set(loadManifest(agent, version)?.writtenCommands ?? [])
|
|
2601
|
+
: null;
|
|
2587
2602
|
for (const entry of fs.readdirSync(commandsTargetSweep, { withFileTypes: true })) {
|
|
2588
2603
|
if (!entry.isFile() || entry.name.startsWith('.'))
|
|
2589
2604
|
continue;
|
|
2590
2605
|
if (!entry.name.endsWith(ext))
|
|
2591
2606
|
continue;
|
|
2592
2607
|
const name = entry.name.slice(0, -ext.length);
|
|
2593
|
-
if (!trustedCommands.has(name)) {
|
|
2608
|
+
if (!trustedCommands.has(name) && (!previouslyManagedCommands || previouslyManagedCommands.has(name))) {
|
|
2594
2609
|
removePath(safeJoin(commandsTargetSweep, entry.name));
|
|
2595
2610
|
}
|
|
2596
2611
|
}
|
|
@@ -2612,7 +2627,7 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2612
2627
|
let skillsToSync = userPassedSelection
|
|
2613
2628
|
? selectedSkillsToSync
|
|
2614
2629
|
: Array.from(new Set([...selectedSkillsToSync, ...pluginSkillsToSync]));
|
|
2615
|
-
if (
|
|
2630
|
+
if (commandsInstallAsSkills && commandsToSync.length > 0 && skillsToSync.length > 0) {
|
|
2616
2631
|
const commandNames = new Set(commandsToSync);
|
|
2617
2632
|
const skillRoots = [
|
|
2618
2633
|
path.join(getUserAgentsDir(), 'skills'),
|
|
@@ -2639,13 +2654,13 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2639
2654
|
// dot-dirs to keep plugin-managed subtrees (.plugins/, .promptcuts) intact.
|
|
2640
2655
|
const skillsTargetSweep = path.join(agentDir, 'skills');
|
|
2641
2656
|
if (!userPassedSelection && fs.existsSync(skillsTargetSweep) && !fs.lstatSync(skillsTargetSweep).isSymbolicLink()) {
|
|
2642
|
-
// Trust real skills AND command-skills: when
|
|
2657
|
+
// Trust real skills AND command-skills: when commandsInstallAsSkills, the
|
|
2643
2658
|
// commands writer (above) materialized each command as a skill dir under
|
|
2644
2659
|
// skills/. Those names are not in skillsToSync, so without this they'd be
|
|
2645
2660
|
// swept as orphans — silently deleting every converted command (e.g.
|
|
2646
|
-
// /recap on
|
|
2661
|
+
// /recap on Kimi or newer Codex releases).
|
|
2647
2662
|
const trustedSkills = new Set(skillsToSync);
|
|
2648
|
-
if (
|
|
2663
|
+
if (commandsInstallAsSkills)
|
|
2649
2664
|
for (const cmd of commandsToSync)
|
|
2650
2665
|
trustedSkills.add(cmd);
|
|
2651
2666
|
for (const entry of fs.readdirSync(skillsTargetSweep, { withFileTypes: true })) {
|
|
@@ -2866,7 +2881,9 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
|
|
|
2866
2881
|
// one-off override, so the resulting state matches what the manifest
|
|
2867
2882
|
// records as the synced set.
|
|
2868
2883
|
if (!userPassedSelection) {
|
|
2869
|
-
|
|
2884
|
+
const manifest = buildSyncManifest(agent, version, cwd);
|
|
2885
|
+
manifest.writtenCommands = writtenCommands;
|
|
2886
|
+
saveManifest(agent, version, manifest);
|
|
2870
2887
|
}
|
|
2871
2888
|
return result;
|
|
2872
2889
|
}
|
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.88",
|
|
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",
|