@phnx-labs/agents-cli 1.20.32 → 1.20.34

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.
Files changed (110) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +28 -2
  3. package/dist/commands/commands.js +3 -3
  4. package/dist/commands/computer-actions.js +1 -0
  5. package/dist/commands/computer.d.ts +23 -0
  6. package/dist/commands/computer.js +45 -3
  7. package/dist/commands/cost.js +2 -2
  8. package/dist/commands/doctor.d.ts +10 -0
  9. package/dist/commands/doctor.js +51 -2
  10. package/dist/commands/exec.js +56 -1
  11. package/dist/commands/hooks.js +3 -3
  12. package/dist/commands/import.js +1 -1
  13. package/dist/commands/inspect.js +13 -17
  14. package/dist/commands/mcp.js +3 -3
  15. package/dist/commands/permissions.js +3 -3
  16. package/dist/commands/rules.js +3 -3
  17. package/dist/commands/secrets-migrate.js +23 -11
  18. package/dist/commands/secrets.d.ts +20 -0
  19. package/dist/commands/secrets.js +53 -1
  20. package/dist/commands/sessions.js +18 -1
  21. package/dist/commands/skills.js +3 -3
  22. package/dist/commands/status.d.ts +12 -0
  23. package/dist/commands/status.js +81 -0
  24. package/dist/commands/sync.js +2 -2
  25. package/dist/commands/teams.js +77 -18
  26. package/dist/commands/usage.js +2 -2
  27. package/dist/commands/utils.d.ts +8 -0
  28. package/dist/commands/utils.js +20 -0
  29. package/dist/commands/versions.js +4 -3
  30. package/dist/commands/view.d.ts +39 -0
  31. package/dist/commands/view.js +217 -84
  32. package/dist/commands/workflows.js +3 -3
  33. package/dist/index.js +16 -2
  34. package/dist/lib/acp/harnesses.d.ts +1 -1
  35. package/dist/lib/acp/harnesses.js +2 -2
  36. package/dist/lib/agent-spec/index.d.ts +18 -0
  37. package/dist/lib/agent-spec/index.js +35 -0
  38. package/dist/lib/agent-spec/primitives.d.ts +28 -0
  39. package/dist/lib/agent-spec/primitives.js +57 -0
  40. package/dist/lib/agent-spec/provider.d.ts +2 -0
  41. package/dist/lib/agent-spec/provider.js +9 -0
  42. package/dist/lib/agent-spec/resolve.d.ts +33 -0
  43. package/dist/lib/agent-spec/resolve.js +174 -0
  44. package/dist/lib/agent-spec/types.d.ts +57 -0
  45. package/dist/lib/agent-spec/types.js +18 -0
  46. package/dist/lib/agents.d.ts +12 -0
  47. package/dist/lib/agents.js +115 -32
  48. package/dist/lib/browser/chrome.js +20 -0
  49. package/dist/lib/browser/drivers/ssh.d.ts +19 -0
  50. package/dist/lib/browser/drivers/ssh.js +18 -3
  51. package/dist/lib/crabbox/cli.d.ts +98 -0
  52. package/dist/lib/crabbox/cli.js +218 -0
  53. package/dist/lib/crabbox/lease.d.ts +41 -0
  54. package/dist/lib/crabbox/lease.js +73 -0
  55. package/dist/lib/crabbox/runtimes.d.ts +57 -0
  56. package/dist/lib/crabbox/runtimes.js +109 -0
  57. package/dist/lib/doctor-diff.js +29 -2
  58. package/dist/lib/drift-sync.d.ts +43 -0
  59. package/dist/lib/drift-sync.js +179 -0
  60. package/dist/lib/exec.d.ts +15 -0
  61. package/dist/lib/exec.js +21 -11
  62. package/dist/lib/hosts/dispatch.d.ts +27 -10
  63. package/dist/lib/hosts/dispatch.js +55 -19
  64. package/dist/lib/hosts/option.d.ts +14 -0
  65. package/dist/lib/hosts/option.js +19 -0
  66. package/dist/lib/hosts/passthrough.d.ts +30 -0
  67. package/dist/lib/hosts/passthrough.js +141 -0
  68. package/dist/lib/hosts/remote-cmd.d.ts +36 -0
  69. package/dist/lib/hosts/remote-cmd.js +56 -0
  70. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  71. package/dist/lib/platform/winpath.d.ts +31 -2
  72. package/dist/lib/platform/winpath.js +133 -24
  73. package/dist/lib/pwsh.d.ts +11 -0
  74. package/dist/lib/pwsh.js +13 -0
  75. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  76. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  77. package/dist/lib/secrets/agent.d.ts +42 -1
  78. package/dist/lib/secrets/agent.js +89 -11
  79. package/dist/lib/secrets/bundles.js +61 -21
  80. package/dist/lib/secrets/filestore.js +31 -1
  81. package/dist/lib/secrets/index.d.ts +44 -1
  82. package/dist/lib/secrets/index.js +108 -10
  83. package/dist/lib/secrets/linux.d.ts +14 -0
  84. package/dist/lib/secrets/linux.js +21 -0
  85. package/dist/lib/secrets/windows.d.ts +74 -0
  86. package/dist/lib/secrets/windows.js +440 -0
  87. package/dist/lib/session/active.d.ts +8 -0
  88. package/dist/lib/session/active.js +18 -1
  89. package/dist/lib/session/provenance.d.ts +56 -0
  90. package/dist/lib/session/provenance.js +157 -0
  91. package/dist/lib/shims.d.ts +20 -0
  92. package/dist/lib/shims.js +53 -20
  93. package/dist/lib/ssh-exec.d.ts +22 -0
  94. package/dist/lib/ssh-exec.js +59 -2
  95. package/dist/lib/ssh-tunnel.d.ts +0 -5
  96. package/dist/lib/ssh-tunnel.js +65 -8
  97. package/dist/lib/startup/command-registry.d.ts +1 -0
  98. package/dist/lib/startup/command-registry.js +2 -0
  99. package/dist/lib/sync-status.d.ts +102 -0
  100. package/dist/lib/sync-status.js +135 -0
  101. package/dist/lib/teams/agents.d.ts +24 -0
  102. package/dist/lib/teams/agents.js +30 -1
  103. package/dist/lib/types.d.ts +20 -1
  104. package/dist/lib/usage.d.ts +30 -0
  105. package/dist/lib/usage.js +159 -2
  106. package/dist/lib/versions.d.ts +2 -4
  107. package/dist/lib/versions.js +7 -20
  108. package/package.json +2 -1
  109. package/dist/lib/agent-spec.d.ts +0 -36
  110. package/dist/lib/agent-spec.js +0 -157
@@ -9,6 +9,7 @@
9
9
  import type { Command } from 'commander';
10
10
  import type { AgentId } from '../lib/types.js';
11
11
  import { type ProfileSummary } from '../lib/profiles.js';
12
+ type SyncState = 'synced' | 'new' | 'modified' | 'deleted';
12
13
  /** Per-section filter flags. When any are true, only those sections render. */
13
14
  export interface ViewSectionFilter {
14
15
  commands?: boolean;
@@ -27,6 +28,7 @@ export interface ViewJsonVersion {
27
28
  isDefault: boolean;
28
29
  signedIn: boolean;
29
30
  email: string | null;
31
+ accountId?: string | null;
30
32
  plan: string | null;
31
33
  usageStatus: 'available' | 'rate_limited' | 'out_of_credits' | null;
32
34
  overageCredits?: {
@@ -40,6 +42,9 @@ export interface ViewJsonVersion {
40
42
  }>;
41
43
  lastActive: string | null;
42
44
  path: string;
45
+ /** Present only when --resources / --detailed (or --json with a section
46
+ * flag) is passed: this version's resource inventory with git sync-state. */
47
+ resources?: VersionResourcesJson;
43
48
  }
44
49
  /** Machine-readable entry for one agent's installed versions. */
45
50
  export interface ViewJsonAgent {
@@ -47,6 +52,37 @@ export interface ViewJsonAgent {
47
52
  versions: ViewJsonVersion[];
48
53
  profiles: ProfileSummary[];
49
54
  }
55
+ /** Resource sections that --resources can include in --json output. */
56
+ export type ResourceSection = 'commands' | 'skills' | 'mcp' | 'memory' | 'hooks' | 'workflows' | 'plugins';
57
+ /** One resource entry in `--json --resources` output. */
58
+ export interface ResourceItemJson {
59
+ name: string;
60
+ scope?: 'user' | 'project';
61
+ /** Git sync-state vs ~/.agents. Omitted for project-scoped resources or when
62
+ * ~/.agents isn't a git repo. When queried via --host it reflects the
63
+ * remote's ~/.agents — i.e. per-host resource drift. */
64
+ syncState?: SyncState;
65
+ description?: string;
66
+ /** Skills only, when > 0. */
67
+ ruleCount?: number;
68
+ }
69
+ /** Per-version resource inventory; one key per requested section. */
70
+ export interface VersionResourcesJson {
71
+ commands?: ResourceItemJson[];
72
+ skills?: ResourceItemJson[];
73
+ mcp?: ResourceItemJson[];
74
+ memory?: ResourceItemJson[];
75
+ hooks?: ResourceItemJson[];
76
+ workflows?: ResourceItemJson[];
77
+ plugins?: ResourceItemJson[];
78
+ }
79
+ /** Build the set of resource sections to include in `--json` from the
80
+ * `--resources` / `--detailed` flags, plus (in --json mode) the per-section
81
+ * boolean filters (`--skills` etc.) that plain `--json` historically ignored. */
82
+ export declare function parseResourceSections(options: {
83
+ resources?: string | boolean;
84
+ detailed?: boolean;
85
+ } & ViewSectionFilter, jsonMode: boolean): Set<ResourceSection>;
50
86
  /**
51
87
  * Prune older installed versions that share an email with a newer installed
52
88
  * version. Keeps the highest semver per email, skips the global default (with
@@ -66,6 +102,9 @@ export declare function viewAction(agentArg?: string, options?: {
66
102
  prune?: boolean;
67
103
  yes?: boolean;
68
104
  dryRun?: boolean;
105
+ resources?: string | boolean;
106
+ detailed?: boolean;
69
107
  } & ViewSectionFilter): Promise<void>;
70
108
  /** Register the `agents view` command. */
71
109
  export declare function registerViewCommand(program: Command): void;
110
+ export {};
@@ -1,3 +1,4 @@
1
+ import { addHostOption } from '../lib/hosts/option.js';
1
2
  import chalk from 'chalk';
2
3
  import ora from 'ora';
3
4
  import * as fs from 'fs';
@@ -5,9 +6,10 @@ import * as path from 'path';
5
6
  import { AGENTS, ALL_AGENT_IDS, getAllCliStates, getAccountInfo, resolveAgentName, formatAgentError, agentLabel, colorAgent, } from '../lib/agents.js';
6
7
  import { deriveUsageStatusFromSnapshot, formatUsageSection, formatUsageSummary, formatUsageStatusBadge, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, } from '../lib/usage.js';
7
8
  import { readManifest } from '../lib/manifest.js';
8
- import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, resolveVersionAlias, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, promptNewResourceSelection, syncResourcesToVersion, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, } from '../lib/versions.js';
9
+ import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, promptNewResourceSelection, syncResourcesToVersion, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, } from '../lib/versions.js';
9
10
  import { ensureVersionedAliasCurrent, removeShim, } from '../lib/shims.js';
10
11
  import { getAgentResources } from '../lib/resources.js';
12
+ import { resolveVersionFilter, AgentSpecError } from '../lib/agent-spec/index.js';
11
13
  import { listCliStatus } from '../lib/cli-resources.js';
12
14
  import { isCapable } from '../lib/capabilities.js';
13
15
  import { discoverPlugins, pluginSupportsAgent } from '../lib/plugins.js';
@@ -22,8 +24,24 @@ import { loadManifest, isStale } from '../lib/staleness/index.js';
22
24
  import { confirm } from '@inquirer/prompts';
23
25
  import { formatPath, isInteractiveTerminal, isPromptCancelled } from './utils.js';
24
26
  // Shown in the email column for agents that are signed in but expose no email
25
- // address locally (Antigravity, Kimi store an opaque OAuth/JWT credential).
27
+ // address locally (Antigravity stores an opaque OAuth grant with no identity).
26
28
  const SIGNED_IN_LABEL = 'signed in';
29
+ /**
30
+ * Text for the account (email) column. Prefers the real email; otherwise, for a
31
+ * signed-in agent whose credential carries no email but does carry an opaque
32
+ * account id (Kimi's user_id), show `id:<user_id>` so distinct accounts read
33
+ * distinctly instead of a generic "signed in". Falls back to "signed in" when we
34
+ * have neither (Antigravity), and empty when signed out.
35
+ */
36
+ function accountColumnLabel(info) {
37
+ if (!info)
38
+ return '';
39
+ if (info.email)
40
+ return info.email;
41
+ if (info.signedIn)
42
+ return info.accountId ? `id:${info.accountId}` : SIGNED_IN_LABEL;
43
+ return '';
44
+ }
27
45
  /**
28
46
  * Group profile summaries by their host harness, optionally filtered to a
29
47
  * single agent. Profile YAMLs that fail validation are silently skipped by
@@ -308,7 +326,10 @@ async function showInstalledVersions(filterAgentId) {
308
326
  return info;
309
327
  return {
310
328
  ...info,
311
- plan: canon.plan,
329
+ // Prefer a plan the live usage fetch surfaced (Kimi reports membership
330
+ // tier in /usages; its local auth file has none) and fall back to the
331
+ // account-derived plan (Claude's billingType).
332
+ plan: usageByKey.get(key)?.snapshot?.plan ?? canon.plan,
312
333
  // Throttle state comes from the live usage windows, not the pay-as-you-go
313
334
  // overage flag that AccountInfo.usageStatus used to carry. A maxed window
314
335
  // means rate-limited; no snapshot means no badge. See
@@ -351,10 +372,9 @@ async function showInstalledVersions(filterAgentId) {
351
372
  maxVerLabel = Math.max(maxVerLabel, label.length);
352
373
  const rawInfo = infoMap.get(`${agentId}:${v}`);
353
374
  const info = rawInfo ? mergeCanonical(rawInfo) : undefined;
354
- if (info?.email)
355
- maxEmail = Math.max(maxEmail, info.email.length);
356
- else if (info?.signedIn)
357
- maxEmail = Math.max(maxEmail, SIGNED_IN_LABEL.length);
375
+ const accountLabel = accountColumnLabel(info);
376
+ if (accountLabel)
377
+ maxEmail = Math.max(maxEmail, accountLabel.length);
358
378
  if (info?.plan)
359
379
  maxPlanWidth = Math.max(maxPlanWidth, info.plan.length);
360
380
  }
@@ -430,9 +450,10 @@ async function showInstalledVersions(filterAgentId) {
430
450
  }
431
451
  else {
432
452
  if (hasEmail || hasUsage || hasActive || signedIn) {
433
- // Signed-in agents without a local email (Antigravity, Kimi) show a
434
- // "signed in" placeholder so they read as logged in, not blank.
435
- const display = vInfo?.email || (signedIn ? SIGNED_IN_LABEL : '');
453
+ // Signed-in agents without a local email show their account id
454
+ // (Kimi's user_id) or a "signed in" placeholder (Antigravity) so they
455
+ // read as logged in, not blank.
456
+ const display = accountColumnLabel(vInfo);
436
457
  const emailCol = display.padEnd(maxEmail);
437
458
  parts.push(display ? chalk.cyan(emailCol) : ' '.repeat(maxEmail));
438
459
  }
@@ -513,7 +534,7 @@ async function showInstalledVersions(filterAgentId) {
513
534
  const gUsageStr = formatUsageSummary(gInfo?.plan || null, gUsage?.snapshot || null);
514
535
  const gActiveStr = gInfo ? formatLastActive(gInfo.lastActive) : '';
515
536
  if (gInfo?.email || gUsageStr || gActiveStr || gInfo?.signedIn) {
516
- const gDisplay = gInfo?.email || (gInfo?.signedIn ? SIGNED_IN_LABEL : '');
537
+ const gDisplay = accountColumnLabel(gInfo);
517
538
  parts.push(gDisplay ? chalk.cyan(gDisplay) : '');
518
539
  }
519
540
  if (gUsageStr || gActiveStr)
@@ -684,53 +705,9 @@ async function showAgentResources(agentId, requestedVersion, filter) {
684
705
  }
685
706
  }
686
707
  const home = getVersionHomePath(agentId, version);
687
- // Get git sync status if ~/.agents/ is a git repo
688
- const userAgentsDir = getUserAgentsDir();
689
- const hasGitRepo = isGitRepo(userAgentsDir);
690
- const commandsSync = hasGitRepo ? await getGitSyncStatus(userAgentsDir, 'commands') : null;
691
- const skillsSync = hasGitRepo ? await getGitSyncStatus(userAgentsDir, 'skills') : null;
692
- const hooksSync = hasGitRepo ? await getGitSyncStatus(userAgentsDir, 'hooks') : null;
693
- const memorySync = hasGitRepo ? await getGitSyncStatus(userAgentsDir, 'rules') : null;
694
- // Helper to determine sync state for a resource
695
- const getSyncState = (resourceName, resourceType, syncStatus) => {
696
- if (!syncStatus)
697
- return undefined;
698
- let relativePath;
699
- if (resourceType === 'commands') {
700
- relativePath = `commands/${resourceName}.md`;
701
- }
702
- else if (resourceType === 'skills') {
703
- relativePath = `skills/${resourceName}`;
704
- }
705
- else if (resourceType === 'hooks') {
706
- relativePath = `hooks/${resourceName}`;
707
- }
708
- else {
709
- // Rules files: map agent-specific name (CLAUDE.md) back to canonical (AGENTS.md)
710
- const centralName = getCentralRulesFileName(agentId);
711
- relativePath = `rules/${centralName}`;
712
- }
713
- const matchesPath = (f) => f === relativePath || f.startsWith(relativePath + '/');
714
- const isNew = syncStatus.new.some(matchesPath);
715
- const isStaged = syncStatus.staged.some(matchesPath);
716
- const isModified = syncStatus.modified.some(matchesPath);
717
- const isDeleted = syncStatus.deleted.some(matchesPath);
718
- const isSynced = syncStatus.synced.some(matchesPath);
719
- if (isNew || isStaged) {
720
- return 'new';
721
- }
722
- if (isModified) {
723
- return 'modified';
724
- }
725
- if (isDeleted) {
726
- return 'deleted';
727
- }
728
- if (isSynced) {
729
- return 'synced';
730
- }
731
- // Not in any array = local-only (untracked with no files)
732
- return 'new';
733
- };
708
+ // Git sync status if ~/.agents/ is a git repo (shared loader — see
709
+ // loadResourceSyncData / resolveResourceSyncState, reused by --json --resources).
710
+ const { hasGitRepo, commands: commandsSync, skills: skillsSync, hooks: hooksSync, memory: memorySync } = await loadResourceSyncData();
734
711
  const resources = getAgentResources(agentId, {
735
712
  cwd,
736
713
  scope: 'all',
@@ -743,23 +720,23 @@ async function showAgentResources(agentId, requestedVersion, filter) {
743
720
  version,
744
721
  commands: resources.commands.map(r => ({
745
722
  ...r,
746
- syncState: r.scope === 'project' ? undefined : getSyncState(r.name, 'commands', commandsSync),
723
+ syncState: r.scope === 'project' ? undefined : resolveResourceSyncState(agentId, r.name, 'commands', commandsSync),
747
724
  })),
748
725
  skills: resources.skills.map(r => ({
749
726
  ...r,
750
727
  // ruleCount of 0 is noise — every skill has 0 unless it ships subrules, which is rare.
751
728
  ruleCount: r.ruleCount && r.ruleCount > 0 ? r.ruleCount : undefined,
752
- syncState: r.scope === 'project' ? undefined : getSyncState(r.name, 'skills', skillsSync),
729
+ syncState: r.scope === 'project' ? undefined : resolveResourceSyncState(agentId, r.name, 'skills', skillsSync),
753
730
  })),
754
731
  skillErrors: resources.skillErrors,
755
732
  mcp: resources.mcp.map(r => ({ name: r.name, scope: r.scope, syncState: r.scope === 'project' ? undefined : 'synced' })),
756
733
  memory: resources.memory.map(r => ({
757
734
  ...r,
758
- syncState: r.scope === 'project' ? undefined : getSyncState(r.name, 'memory', memorySync),
735
+ syncState: r.scope === 'project' ? undefined : resolveResourceSyncState(agentId, r.name, 'memory', memorySync),
759
736
  })),
760
737
  hooks: resources.hooks.map(r => ({
761
738
  ...r,
762
- syncState: r.scope === 'project' ? undefined : getSyncState(r.name, 'hooks', hooksSync),
739
+ syncState: r.scope === 'project' ? undefined : resolveResourceSyncState(agentId, r.name, 'hooks', hooksSync),
763
740
  })),
764
741
  workflows: resources.workflows.map(r => ({ name: r.name, path: r.path, scope: r.scope })),
765
742
  };
@@ -825,13 +802,10 @@ async function showAgentResources(agentId, requestedVersion, filter) {
825
802
  cliVersion: version,
826
803
  info: accountInfo,
827
804
  });
828
- const emailStr = accountInfo.email
829
- ? chalk.cyan(` ${accountInfo.email}`)
830
- : accountInfo.signedIn
831
- ? chalk.cyan(` ${SIGNED_IN_LABEL}`)
832
- : '';
805
+ const accountLabel = accountColumnLabel(accountInfo);
806
+ const emailStr = accountLabel ? chalk.cyan(` ${accountLabel}`) : '';
833
807
  const status = chalk.green(version);
834
- const usageStr = formatUsageSummary(accountInfo.plan, null);
808
+ const usageStr = formatUsageSummary(usageInfo.snapshot?.plan ?? accountInfo.plan, null);
835
809
  const usagePart = usageStr ? ` ${usageStr}` : '';
836
810
  console.log(` ${colorAgent(agentId)(AGENTS[agentId].name.padEnd(14))} ${status}${emailStr}${usagePart}`);
837
811
  const usageLines = formatUsageSection(usageInfo);
@@ -971,14 +945,144 @@ async function showAgentResources(agentId, requestedVersion, filter) {
971
945
  console.log(chalk.gray('Legend:'), chalk.green('Tracked'), chalk.blue('Local-only'), chalk.yellow('Modified'), chalk.red('Deleted'));
972
946
  }
973
947
  }
948
+ const ALL_RESOURCE_SECTIONS = ['commands', 'skills', 'mcp', 'memory', 'hooks', 'workflows', 'plugins'];
949
+ /** Load git sync-state for the tracked resource kinds. Shared by the human
950
+ * detail view (showAgentResources) and the `--json --resources` path. */
951
+ async function loadResourceSyncData() {
952
+ const userAgentsDir = getUserAgentsDir();
953
+ const hasGitRepo = isGitRepo(userAgentsDir);
954
+ return {
955
+ hasGitRepo,
956
+ commands: hasGitRepo ? await getGitSyncStatus(userAgentsDir, 'commands') : null,
957
+ skills: hasGitRepo ? await getGitSyncStatus(userAgentsDir, 'skills') : null,
958
+ hooks: hasGitRepo ? await getGitSyncStatus(userAgentsDir, 'hooks') : null,
959
+ memory: hasGitRepo ? await getGitSyncStatus(userAgentsDir, 'rules') : null,
960
+ };
961
+ }
962
+ /** Resolve one resource's git sync-state. Extracted from showAgentResources so
963
+ * the human view and the JSON path derive drift identically. */
964
+ function resolveResourceSyncState(agentId, resourceName, resourceType, syncStatus) {
965
+ if (!syncStatus)
966
+ return undefined;
967
+ let relativePath;
968
+ if (resourceType === 'commands') {
969
+ relativePath = `commands/${resourceName}.md`;
970
+ }
971
+ else if (resourceType === 'skills') {
972
+ relativePath = `skills/${resourceName}`;
973
+ }
974
+ else if (resourceType === 'hooks') {
975
+ relativePath = `hooks/${resourceName}`;
976
+ }
977
+ else {
978
+ // Rules files: map agent-specific name (CLAUDE.md) back to canonical (AGENTS.md)
979
+ const centralName = getCentralRulesFileName(agentId);
980
+ relativePath = `rules/${centralName}`;
981
+ }
982
+ const matchesPath = (f) => f === relativePath || f.startsWith(relativePath + '/');
983
+ if (syncStatus.new.some(matchesPath) || syncStatus.staged.some(matchesPath))
984
+ return 'new';
985
+ if (syncStatus.modified.some(matchesPath))
986
+ return 'modified';
987
+ if (syncStatus.deleted.some(matchesPath))
988
+ return 'deleted';
989
+ if (syncStatus.synced.some(matchesPath))
990
+ return 'synced';
991
+ // Not in any array = local-only (untracked with no files)
992
+ return 'new';
993
+ }
994
+ /** Collect one version's resources for `--json`, limited to `sections`. Scans
995
+ * the version's `home`, so per-version differences are reported accurately. */
996
+ function collectVersionResources(agentId, home, cwd, cliInstalled, sections, sync) {
997
+ const res = getAgentResources(agentId, { cwd, scope: 'all', cliInstalled, home });
998
+ const out = {};
999
+ const withSync = (r, type, syncStatus) => {
1000
+ const item = { name: r.name, scope: r.scope };
1001
+ if (r.scope !== 'project') {
1002
+ const s = resolveResourceSyncState(agentId, r.name, type, syncStatus);
1003
+ if (s)
1004
+ item.syncState = s;
1005
+ }
1006
+ if (r.description)
1007
+ item.description = r.description;
1008
+ return item;
1009
+ };
1010
+ if (sections.has('commands'))
1011
+ out.commands = res.commands.map((r) => withSync(r, 'commands', sync.commands));
1012
+ if (sections.has('skills')) {
1013
+ out.skills = res.skills.map((r) => {
1014
+ const item = withSync(r, 'skills', sync.skills);
1015
+ // ruleCount of 0 is noise — every skill has 0 unless it ships subrules.
1016
+ if (r.ruleCount && r.ruleCount > 0)
1017
+ item.ruleCount = r.ruleCount;
1018
+ return item;
1019
+ });
1020
+ }
1021
+ if (sections.has('mcp')) {
1022
+ out.mcp = res.mcp.map((r) => {
1023
+ const item = { name: r.name, scope: r.scope };
1024
+ if (r.scope !== 'project')
1025
+ item.syncState = 'synced';
1026
+ return item;
1027
+ });
1028
+ }
1029
+ if (sections.has('memory'))
1030
+ out.memory = res.memory.map((r) => withSync(r, 'memory', sync.memory));
1031
+ if (sections.has('hooks'))
1032
+ out.hooks = res.hooks.map((r) => withSync(r, 'hooks', sync.hooks));
1033
+ if (sections.has('workflows'))
1034
+ out.workflows = res.workflows.map((r) => ({ name: r.name, scope: r.scope }));
1035
+ if (sections.has('plugins')) {
1036
+ out.plugins = discoverPlugins()
1037
+ .filter((p) => pluginSupportsAgent(p, agentId))
1038
+ .map((p) => ({ name: p.name }));
1039
+ }
1040
+ return out;
1041
+ }
1042
+ /** Build the set of resource sections to include in `--json` from the
1043
+ * `--resources` / `--detailed` flags, plus (in --json mode) the per-section
1044
+ * boolean filters (`--skills` etc.) that plain `--json` historically ignored. */
1045
+ export function parseResourceSections(options, jsonMode) {
1046
+ const set = new Set();
1047
+ const addAll = () => ALL_RESOURCE_SECTIONS.forEach((s) => set.add(s));
1048
+ if (options.detailed)
1049
+ addAll();
1050
+ const rv = options.resources;
1051
+ if (rv !== undefined) {
1052
+ if (rv === true || String(rv).trim() === '' || String(rv).toLowerCase() === 'all') {
1053
+ addAll();
1054
+ }
1055
+ else {
1056
+ for (const raw of String(rv).split(',').map((s) => s.trim().toLowerCase()).filter(Boolean)) {
1057
+ const section = raw === 'rules' ? 'memory' : raw; // --rules is the memory file
1058
+ if (ALL_RESOURCE_SECTIONS.includes(section))
1059
+ set.add(section);
1060
+ }
1061
+ }
1062
+ }
1063
+ if (jsonMode) {
1064
+ const flagToSection = [
1065
+ ['commands', 'commands'], ['skills', 'skills'], ['mcp', 'mcp'],
1066
+ ['workflows', 'workflows'], ['plugins', 'plugins'], ['hooks', 'hooks'], ['rules', 'memory'],
1067
+ ];
1068
+ for (const [flag, section] of flagToSection)
1069
+ if (options[flag])
1070
+ set.add(section);
1071
+ }
1072
+ return set;
1073
+ }
974
1074
  /**
975
1075
  * Collect structured info for one or more agents without rendering to the
976
1076
  * terminal. Used by `--json` output and any programmatic consumer (e.g. the
977
1077
  * agents-cli extension's "resume current session in best available version"
978
1078
  * command).
979
1079
  */
980
- async function collectAgentsJson(filterAgentId) {
1080
+ async function collectAgentsJson(filterAgentId, resourceSections) {
981
1081
  const agentsToShow = filterAgentId ? [filterAgentId] : ALL_AGENT_IDS;
1082
+ const wantResources = !!resourceSections && resourceSections.size > 0;
1083
+ // Only pay for the git-status + resource scans when resources were requested.
1084
+ const resourceSync = wantResources ? await loadResourceSyncData() : null;
1085
+ const cliStates = wantResources ? await getAllCliStates() : null;
982
1086
  const infoFetches = [];
983
1087
  for (const agentId of agentsToShow) {
984
1088
  for (const ver of listInstalledVersions(agentId)) {
@@ -1002,7 +1106,10 @@ async function collectAgentsJson(filterAgentId) {
1002
1106
  return info;
1003
1107
  return {
1004
1108
  ...info,
1005
- plan: canon.plan,
1109
+ // Prefer a plan the live usage fetch surfaced (Kimi reports membership
1110
+ // tier in /usages; its local auth file has none) and fall back to the
1111
+ // account-derived plan (Claude's billingType).
1112
+ plan: usageByKey.get(key)?.snapshot?.plan ?? canon.plan,
1006
1113
  // Throttle state comes from the live usage windows, not the pay-as-you-go
1007
1114
  // overage flag that AccountInfo.usageStatus used to carry. A maxed window
1008
1115
  // means rate-limited; no snapshot means no badge. See
@@ -1012,7 +1119,7 @@ async function collectAgentsJson(filterAgentId) {
1012
1119
  };
1013
1120
  };
1014
1121
  const byAgent = new Map();
1015
- for (const { agentId, version, info: rawInfo } of infoResults) {
1122
+ for (const { agentId, version, home, info: rawInfo } of infoResults) {
1016
1123
  const info = mergeCanonical(rawInfo);
1017
1124
  const globalDefault = getGlobalDefault(agentId);
1018
1125
  const usageKey = getUsageLookupKey(info);
@@ -1023,6 +1130,7 @@ async function collectAgentsJson(filterAgentId) {
1023
1130
  isDefault: version === globalDefault,
1024
1131
  signedIn: info.signedIn,
1025
1132
  email: info.email,
1133
+ accountId: info.accountId,
1026
1134
  plan: info.plan,
1027
1135
  usageStatus: info.usageStatus,
1028
1136
  overageCredits: info.overageCredits,
@@ -1036,6 +1144,9 @@ async function collectAgentsJson(filterAgentId) {
1036
1144
  lastActive: info.lastActive ? info.lastActive.toISOString() : null,
1037
1145
  path: getVersionDir(agentId, version),
1038
1146
  };
1147
+ if (wantResources && resourceSync) {
1148
+ entry.resources = collectVersionResources(agentId, home, process.cwd(), cliStates?.[agentId]?.installed ?? false, resourceSections, resourceSync);
1149
+ }
1039
1150
  const existing = byAgent.get(agentId);
1040
1151
  if (existing)
1041
1152
  existing.push(entry);
@@ -1246,7 +1357,10 @@ export async function pruneDuplicates(filterAgentId, yes, dryRun) {
1246
1357
  * Exported for use by deprecated aliases.
1247
1358
  */
1248
1359
  export async function viewAction(agentArg, options) {
1249
- const json = options?.json === true;
1360
+ // --resources / --detailed imply --json (they only shape structured output).
1361
+ const explicitResources = options?.detailed === true || options?.resources !== undefined;
1362
+ const json = options?.json === true || explicitResources;
1363
+ const resourceSections = parseResourceSections(options ?? {}, json);
1250
1364
  const prune = options?.prune === true;
1251
1365
  const yes = options?.yes === true;
1252
1366
  const dryRun = options?.dryRun === true;
@@ -1278,7 +1392,7 @@ export async function viewAction(agentArg, options) {
1278
1392
  return;
1279
1393
  }
1280
1394
  if (json) {
1281
- const data = await collectAgentsJson();
1395
+ const data = await collectAgentsJson(undefined, resourceSections);
1282
1396
  console.log(JSON.stringify(data, null, 2));
1283
1397
  return;
1284
1398
  }
@@ -1298,12 +1412,25 @@ export async function viewAction(agentArg, options) {
1298
1412
  console.log(chalk.red(formatAgentError(agentName)));
1299
1413
  process.exit(1);
1300
1414
  }
1301
- // Keep 'default'/'pinned' as-is since showAgentResources handles 'default';
1302
- // resolveVersionAlias returns undefined for both (they're synonyms), which
1303
- // would otherwise skip the detailed view.
1304
- const requestedVersion = (parts[1] === 'default' || parts[1] === 'pinned')
1305
- ? 'default'
1306
- : (resolveVersionAlias(agentId, parts[1]) ?? null);
1415
+ // Resolve the @version filter through the agent-spec engine:
1416
+ // bare/@any null (show all versions), @default/@pinned → 'default'
1417
+ // (showAgentResources handles it), @latest/@oldest/@x.y.z concrete.
1418
+ let requestedVersion;
1419
+ try {
1420
+ requestedVersion = resolveVersionFilter(agentId, parts[1]).version;
1421
+ }
1422
+ catch (e) {
1423
+ if (e instanceof AgentSpecError) {
1424
+ if (json) {
1425
+ console.log(JSON.stringify({ error: e.message }));
1426
+ }
1427
+ else {
1428
+ console.log(chalk.red(e.message));
1429
+ }
1430
+ process.exit(1);
1431
+ }
1432
+ throw e;
1433
+ }
1307
1434
  if (prune) {
1308
1435
  if (requestedVersion) {
1309
1436
  console.log(chalk.red('--prune does not take a @version suffix.'));
@@ -1314,9 +1441,9 @@ export async function viewAction(agentArg, options) {
1314
1441
  return;
1315
1442
  }
1316
1443
  if (json) {
1317
- // --json ignores the @version suffix (detailed resource view is not yet
1318
- // exposed as structured data). Emit the version list for the agent.
1319
- const data = await collectAgentsJson(agentId);
1444
+ // --json ignores the @version suffix, but --resources/--detailed (or a
1445
+ // section flag) now attach each version's resource inventory + sync-state.
1446
+ const data = await collectAgentsJson(agentId, resourceSections);
1320
1447
  console.log(JSON.stringify(data[0] ?? { agent: agentId, versions: [], profiles: [] }, null, 2));
1321
1448
  return;
1322
1449
  }
@@ -1336,10 +1463,11 @@ export async function viewAction(agentArg, options) {
1336
1463
  }
1337
1464
  /** Register the `agents view` command. */
1338
1465
  export function registerViewCommand(program) {
1339
- program
1340
- .command('view [agent]')
1466
+ addHostOption(program.command('view [agent]'))
1341
1467
  .description('Show what agent CLIs are installed and which versions you have. Inspect resources when you pass agent@version.')
1342
1468
  .option('--json', 'Emit machine-readable JSON (version list, usage, signed-in status).')
1469
+ .option('--resources [sections]', 'In --json mode, include each version\'s resources: "all" (default) or a comma list (skills,plugins,mcp,commands,workflows,memory,hooks). Implies --json.')
1470
+ .option('--detailed', 'Include all resources in --json output (alias for --resources all). Implies --json.')
1343
1471
  .option('--prune', 'Remove older installed versions that share an account with a newer installed version. Skips the global default.')
1344
1472
  .option('--dry-run', 'With --prune, show duplicate versions without deleting')
1345
1473
  .option('-y, --yes', 'Skip the prune confirmation prompt.')
@@ -1367,6 +1495,11 @@ Examples:
1367
1495
  # Machine-readable output (used by tools that pick a version programmatically)
1368
1496
  agents view claude --json
1369
1497
 
1498
+ # One call: full inventory + what's synced on a host (installed agents,
1499
+ # versions, accounts, usage, and per-version resource sync-state)
1500
+ agents view claude --host yosemite-s0 --json --resources all
1501
+ agents view claude --json --resources skills,plugins # just those sections
1502
+
1370
1503
  # Prune older versions that duplicate an account already used by a newer version
1371
1504
  agents view --prune --dry-run
1372
1505
  agents view claude --prune
@@ -8,9 +8,9 @@ import { resolveAgentName, agentLabel } from '../lib/agents.js';
8
8
  import { capableAgents } from '../lib/capabilities.js';
9
9
  import { cloneRepo } from '../lib/git.js';
10
10
  import { discoverWorkflowsFromRepo, installWorkflowCentrally, removeWorkflow, listInstalledWorkflows, listWorkflowsForAgent, removeWorkflowFromVersion, iterWorkflowsCapableVersions, } from '../lib/workflows.js';
11
- import { getVersionHomePath, getGlobalDefault, resolveVersionAlias, syncResourcesToVersion, promptAgentVersionSelection, } from '../lib/versions.js';
11
+ import { getVersionHomePath, getGlobalDefault, syncResourcesToVersion, promptAgentVersionSelection, } from '../lib/versions.js';
12
12
  import { recordVersionResources, getUserWorkflowsDir } from '../lib/state.js';
13
- import { isPromptCancelled, isInteractiveTerminal, requireInteractiveSelection, printWithPager, promptRemovalTargets, parseCommaSeparatedList, resolveAgentTargetsAutoInstalling, } from './utils.js';
13
+ import { isPromptCancelled, isInteractiveTerminal, requireInteractiveSelection, printWithPager, promptRemovalTargets, parseCommaSeparatedList, resolveAgentTargetsAutoInstalling, resolveListFilterOrExit, } from './utils.js';
14
14
  import { showResourceList, buildTargetsSection, } from './resource-view.js';
15
15
  /** Register the `agents workflows` command tree (list, view, add, remove). */
16
16
  export function registerWorkflowsCommands(program) {
@@ -73,7 +73,7 @@ Examples:
73
73
  process.exit(1);
74
74
  }
75
75
  filterAgent = resolved;
76
- filterVersion = parts[1] ? resolveVersionAlias(resolved, parts[1]) : undefined;
76
+ filterVersion = resolveListFilterOrExit(resolved, parts[1]);
77
77
  }
78
78
  const rows = buildWorkflowRows({ filterAgent, filterVersion });
79
79
  spinner.stop();
package/dist/index.js CHANGED
@@ -51,7 +51,7 @@ if (IS_DEV_BUILD) {
51
51
  // module on each invocation (which loaded the whole ~50-module tree before the
52
52
  // first byte of output), the registry maps a command name to a thunk that
53
53
  // imports only what that command needs. See src/lib/startup/command-registry.ts.
54
- import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadPackages, loadDaemon, loadRoutines, loadRun, loadDefaults, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadProfiles, loadSecrets, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadBudget, loadAlias, loadPty, loadTmux, loadBrowser, loadComputer, loadHosts, loadSsh, loadPull, loadPush, loadRepo, loadSetup, } from './lib/startup/command-registry.js';
54
+ import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadPackages, loadDaemon, loadRoutines, loadRun, loadDefaults, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadStatus, loadProfiles, loadSecrets, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadBudget, loadAlias, loadPty, loadTmux, loadBrowser, loadComputer, loadHosts, loadSsh, loadPull, loadPush, loadRepo, loadSetup, } from './lib/startup/command-registry.js';
55
55
  import { applyGlobalHelpConventions } from './lib/help.js';
56
56
  import { IS_WINDOWS } from './lib/platform/index.js';
57
57
  // Transparent shim delegate: the generated Windows `.cmd` shims invoke
@@ -764,6 +764,7 @@ async function registerAllEagerCommands() {
764
764
  await reg(loadTrash);
765
765
  await reg(loadRestore);
766
766
  await reg(loadDoctor);
767
+ await reg(loadStatus);
767
768
  registerExecAliasCommand(program);
768
769
  await reg(loadProfiles);
769
770
  await reg(loadSecrets);
@@ -847,6 +848,18 @@ const requestedCommand = passedArgs.find((arg) => !arg.startsWith('-'));
847
848
  // Help and version output are pure documentation — they must never gate on
848
849
  // setup, otherwise `agents <cmd> --help` becomes useless on a fresh box.
849
850
  const helpOrVersionRequested = passedArgs.some((arg) => arg === '--help' || arg === '-h' || arg === '--version' || arg === '-V');
851
+ // `--host` passthrough: run this invocation on a remote machine over SSH instead
852
+ // of locally. Handled before any local command registration / update check /
853
+ // background sync — a remote run needs none of that. Only the allowlisted
854
+ // read-only + config + teams commands route here; `run`/`sessions` are absent
855
+ // from the table and fall through to their own richer `--host` handling below.
856
+ // `--help`/`--version` stay local (docs must work without a reachable host).
857
+ if (requestedCommand !== undefined && !helpOrVersionRequested) {
858
+ const { maybeRunOnHost } = await import('./lib/hosts/passthrough.js');
859
+ if (await maybeRunOnHost(requestedCommand, passedArgs)) {
860
+ process.exit(process.exitCode ?? 0);
861
+ }
862
+ }
850
863
  // Register only the command(s) this invocation actually uses. Lazy commands
851
864
  // (sessions/teams/cloud) are handled after applyGlobalHelpConventions below.
852
865
  const isLazyRequest = requestedCommand !== undefined && LAZY_COMMAND_NAMES.has(requestedCommand);
@@ -882,7 +895,8 @@ if (!helpOrVersionRequested) {
882
895
  // fire-and-forget the next background sync. System repo gets a real fast-forward
883
896
  // pull (read-only locally, safe). User repo and extras get fetch-only + a
884
897
  // status marker that we'll print on the *next* invocation.
885
- const { spawnDetachedSync } = await import('./lib/auto-pull.js');
898
+ const { spawnDetachedSync, printPendingUpdateNotices } = await import('./lib/auto-pull.js');
899
+ printPendingUpdateNotices();
886
900
  spawnDetachedSync();
887
901
  }
888
902
  // First-run experience: no args + no config yet + TTY -> launch interactive setup.
@@ -2,7 +2,7 @@
2
2
  * Per-harness spawn configuration for Agent Client Protocol (ACP) mode.
3
3
  *
4
4
  * Each entry describes how to launch a coding agent CLI as an ACP server
5
- * (stdio JSON-RPC). Unsupported harnesses (amp, roo, goose-stdio) are omitted;
5
+ * (stdio JSON-RPC). Unsupported harnesses (amp, goose-stdio) are omitted;
6
6
  * callers should fall back to the legacy direct-exec path for those.
7
7
  *
8
8
  * Sources: https://agentclientprotocol.com/get-started/agents + vendor docs.
@@ -2,7 +2,7 @@
2
2
  * Per-harness spawn configuration for Agent Client Protocol (ACP) mode.
3
3
  *
4
4
  * Each entry describes how to launch a coding agent CLI as an ACP server
5
- * (stdio JSON-RPC). Unsupported harnesses (amp, roo, goose-stdio) are omitted;
5
+ * (stdio JSON-RPC). Unsupported harnesses (amp, goose-stdio) are omitted;
6
6
  * callers should fall back to the legacy direct-exec path for those.
7
7
  *
8
8
  * Sources: https://agentclientprotocol.com/get-started/agents + vendor docs.
@@ -61,7 +61,7 @@ export const ACP_HARNESSES = {
61
61
  // goose: ACP over HTTP via `goosed`, not a clean stdio subcommand.
62
62
  // copilot, kiro: excluded for now (not installed in the reference environment,
63
63
  // no local verification possible).
64
- // amp, roo: not on the ACP agents list.
64
+ // amp: not on the ACP agents list.
65
65
  };
66
66
  /** Returns the ACP spawn spec for an agent, or undefined if the harness does not speak ACP. */
67
67
  export function getAcpSpec(agent) {
@@ -0,0 +1,18 @@
1
+ import type { AgentId } from '../types.js';
2
+ import type { AgentTarget, ResolveOptions, VersionFilter } from './types.js';
3
+ export * from './types.js';
4
+ export * from './primitives.js';
5
+ /** Shared `--help` epilog so every agent-spec command documents the same grammar. */
6
+ export declare const AGENT_SPEC_HELP: string;
7
+ /** Resolve a spec (single or comma-list) into concrete installed targets. */
8
+ export declare function resolveAgentTargets(spec: string, opts?: ResolveOptions): AgentTarget[];
9
+ /** Resolve a spec that must name exactly one installed version. */
10
+ export declare function resolveSingleAgentTarget(spec: string, opts?: ResolveOptions): {
11
+ agent: AgentId;
12
+ version: string;
13
+ source: import('./types.js').VersionSource;
14
+ };
15
+ /** Resolve a read/list command's version filter (undefined → all, @default → the default, else concrete). */
16
+ export declare function resolveVersionFilter(agent: AgentId, qualifier: string | undefined | null, opts?: ResolveOptions): VersionFilter;
17
+ /** Concrete version filter for list/display commands (undefined → show all, @default → the default version). */
18
+ export declare function resolveListFilter(agent: AgentId, qualifier: string | undefined | null, opts?: ResolveOptions): string | undefined;