@phnx-labs/agents-cli 1.20.4 → 1.20.6
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 +19 -0
- package/README.md +49 -18
- package/dist/commands/browser.js +31 -4
- package/dist/commands/cli.js +1 -1
- package/dist/commands/cloud.js +1 -1
- package/dist/commands/commands.js +2 -0
- package/dist/commands/computer.js +10 -2
- package/dist/commands/defaults.d.ts +7 -0
- package/dist/commands/defaults.js +89 -0
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/exec.js +73 -19
- package/dist/commands/hooks.js +6 -6
- package/dist/commands/inspect.d.ts +26 -0
- package/dist/commands/inspect.js +590 -0
- package/dist/commands/mcp.js +17 -16
- package/dist/commands/models.js +1 -1
- package/dist/commands/packages.js +6 -4
- package/dist/commands/permissions.js +13 -12
- package/dist/commands/plugins.d.ts +13 -0
- package/dist/commands/plugins.js +100 -11
- package/dist/commands/prune.js +3 -2
- package/dist/commands/pull.d.ts +12 -5
- package/dist/commands/pull.js +26 -422
- package/dist/commands/push.d.ts +14 -0
- package/dist/commands/push.js +30 -0
- package/dist/commands/repo.d.ts +1 -1
- package/dist/commands/repo.js +155 -112
- package/dist/commands/resource-view.d.ts +2 -0
- package/dist/commands/resource-view.js +12 -3
- package/dist/commands/routines.js +32 -7
- package/dist/commands/rules.js +4 -4
- package/dist/commands/secrets.js +46 -9
- package/dist/commands/sessions.js +1 -0
- package/dist/commands/setup.d.ts +3 -3
- package/dist/commands/setup.js +17 -17
- package/dist/commands/skills.js +6 -5
- package/dist/commands/subagents.js +5 -4
- package/dist/commands/sync.d.ts +18 -5
- package/dist/commands/sync.js +251 -65
- package/dist/commands/teams.js +109 -11
- package/dist/commands/tmux.d.ts +25 -0
- package/dist/commands/tmux.js +415 -0
- package/dist/commands/trash.d.ts +2 -2
- package/dist/commands/trash.js +1 -1
- package/dist/commands/versions.js +2 -2
- package/dist/commands/view.d.ts +12 -1
- package/dist/commands/view.js +128 -40
- package/dist/commands/workflows.js +4 -3
- package/dist/commands/worktree.d.ts +4 -5
- package/dist/commands/worktree.js +4 -4
- package/dist/index.js +106 -41
- package/dist/lib/agents.d.ts +23 -10
- package/dist/lib/agents.js +88 -25
- package/dist/lib/auto-pull-worker.d.ts +1 -1
- package/dist/lib/auto-pull-worker.js +2 -2
- package/dist/lib/auto-pull.d.ts +1 -1
- package/dist/lib/auto-pull.js +1 -1
- package/dist/lib/beta.d.ts +1 -1
- package/dist/lib/beta.js +1 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +84 -3
- package/dist/lib/capabilities.js +2 -0
- package/dist/lib/commands.d.ts +28 -1
- package/dist/lib/commands.js +125 -20
- package/dist/lib/doctor-diff.js +2 -2
- package/dist/lib/exec.d.ts +14 -0
- package/dist/lib/exec.js +59 -5
- package/dist/lib/fuzzy.d.ts +12 -2
- package/dist/lib/fuzzy.js +29 -4
- package/dist/lib/git.js +8 -1
- package/dist/lib/hooks.d.ts +2 -2
- package/dist/lib/hooks.js +97 -10
- package/dist/lib/mcp.js +32 -2
- package/dist/lib/migrate.d.ts +51 -0
- package/dist/lib/migrate.js +233 -5
- package/dist/lib/models.js +62 -15
- package/dist/lib/permissions.d.ts +59 -2
- package/dist/lib/permissions.js +299 -7
- package/dist/lib/plugin-marketplace.d.ts +98 -40
- package/dist/lib/plugin-marketplace.js +196 -93
- package/dist/lib/plugins.d.ts +21 -4
- package/dist/lib/plugins.js +130 -49
- package/dist/lib/profiles-presets.js +12 -12
- package/dist/lib/project-launch.d.ts +70 -0
- package/dist/lib/project-launch.js +404 -0
- package/dist/lib/pty-client.js +1 -1
- package/dist/lib/pty-server.d.ts +1 -1
- package/dist/lib/pty-server.js +8 -5
- package/dist/lib/refresh.d.ts +26 -0
- package/dist/lib/refresh.js +315 -0
- package/dist/lib/resource-patterns.d.ts +1 -1
- package/dist/lib/resource-patterns.js +1 -1
- package/dist/lib/resources/commands.js +2 -2
- package/dist/lib/resources/hooks.d.ts +1 -1
- package/dist/lib/resources/hooks.js +1 -1
- package/dist/lib/resources/mcp.d.ts +1 -1
- package/dist/lib/resources/mcp.js +5 -6
- package/dist/lib/resources/permissions.js +5 -2
- package/dist/lib/resources/rules.js +3 -2
- package/dist/lib/resources/skills.js +3 -2
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources.d.ts +2 -0
- package/dist/lib/resources.js +4 -3
- package/dist/lib/rotate.d.ts +1 -1
- package/dist/lib/rotate.js +7 -19
- package/dist/lib/routines.d.ts +16 -4
- package/dist/lib/routines.js +67 -17
- package/dist/lib/rules/compile.js +22 -10
- package/dist/lib/rules/rules.js +3 -3
- package/dist/lib/run-config.d.ts +9 -0
- package/dist/lib/run-config.js +35 -0
- package/dist/lib/run-defaults.d.ts +42 -0
- package/dist/lib/run-defaults.js +180 -0
- package/dist/lib/runner.js +16 -3
- package/dist/lib/scheduler.js +15 -1
- 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/_CodeSignature/CodeResources +9 -1
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/dist/lib/secrets/install-helper.d.ts +11 -3
- package/dist/lib/secrets/install-helper.js +48 -6
- package/dist/lib/secrets/linux.d.ts +56 -9
- package/dist/lib/secrets/linux.js +327 -59
- package/dist/lib/session/db.js +15 -2
- package/dist/lib/session/discover.js +118 -3
- package/dist/lib/session/parse.js +3 -0
- package/dist/lib/session/types.d.ts +1 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +18 -9
- package/dist/lib/shims.js +133 -50
- package/dist/lib/skills.d.ts +1 -1
- package/dist/lib/skills.js +10 -9
- package/dist/lib/staleness/detectors/commands.d.ts +3 -0
- package/dist/lib/staleness/detectors/commands.js +46 -0
- package/dist/lib/staleness/detectors/hooks.d.ts +3 -0
- package/dist/lib/staleness/detectors/hooks.js +44 -0
- package/dist/lib/staleness/detectors/mcp.d.ts +3 -0
- package/dist/lib/staleness/detectors/mcp.js +31 -0
- package/dist/lib/staleness/detectors/permissions.d.ts +3 -0
- package/dist/lib/staleness/detectors/permissions.js +201 -0
- package/dist/lib/staleness/detectors/plugins.d.ts +8 -0
- package/dist/lib/staleness/detectors/plugins.js +23 -0
- package/dist/lib/staleness/detectors/rules.d.ts +3 -0
- package/dist/lib/staleness/detectors/rules.js +34 -0
- package/dist/lib/staleness/detectors/skills.d.ts +3 -0
- package/dist/lib/staleness/detectors/skills.js +71 -0
- package/dist/lib/staleness/detectors/subagents.d.ts +3 -0
- package/dist/lib/staleness/detectors/subagents.js +50 -0
- package/dist/lib/staleness/detectors/types.d.ts +22 -0
- package/dist/lib/staleness/detectors/types.js +1 -0
- package/dist/lib/staleness/detectors/workflows.d.ts +3 -0
- package/dist/lib/staleness/detectors/workflows.js +28 -0
- package/dist/lib/staleness/registry.d.ts +26 -0
- package/dist/lib/staleness/registry.js +123 -0
- package/dist/lib/staleness/writers/commands.d.ts +3 -0
- package/dist/lib/staleness/writers/commands.js +111 -0
- package/dist/lib/staleness/writers/hooks.d.ts +3 -0
- package/dist/lib/staleness/writers/hooks.js +47 -0
- package/dist/lib/staleness/writers/kinds.d.ts +10 -0
- package/dist/lib/staleness/writers/kinds.js +15 -0
- package/dist/lib/staleness/writers/lazy-map.d.ts +13 -0
- package/dist/lib/staleness/writers/lazy-map.js +19 -0
- package/dist/lib/staleness/writers/mcp.d.ts +10 -0
- package/dist/lib/staleness/writers/mcp.js +19 -0
- package/dist/lib/staleness/writers/permissions.d.ts +13 -0
- package/dist/lib/staleness/writers/permissions.js +26 -0
- package/dist/lib/staleness/writers/plugins.d.ts +7 -0
- package/dist/lib/staleness/writers/plugins.js +31 -0
- package/dist/lib/staleness/writers/rules.d.ts +7 -0
- package/dist/lib/staleness/writers/rules.js +55 -0
- package/dist/lib/staleness/writers/skills.d.ts +3 -0
- package/dist/lib/staleness/writers/skills.js +81 -0
- package/dist/lib/staleness/writers/sources.d.ts +16 -0
- package/dist/lib/staleness/writers/sources.js +72 -0
- package/dist/lib/staleness/writers/subagents.d.ts +3 -0
- package/dist/lib/staleness/writers/subagents.js +53 -0
- package/dist/lib/staleness/writers/types.d.ts +36 -0
- package/dist/lib/staleness/writers/types.js +1 -0
- package/dist/lib/staleness/writers/workflows.d.ts +7 -0
- package/dist/lib/staleness/writers/workflows.js +31 -0
- package/dist/lib/state.d.ts +34 -11
- package/dist/lib/state.js +58 -13
- package/dist/lib/subagents.d.ts +0 -2
- package/dist/lib/subagents.js +6 -6
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/teams/api.d.ts +67 -0
- package/dist/lib/teams/api.js +78 -0
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/tmux/binary.d.ts +67 -0
- package/dist/lib/tmux/binary.js +141 -0
- package/dist/lib/tmux/index.d.ts +8 -0
- package/dist/lib/tmux/index.js +8 -0
- package/dist/lib/tmux/paths.d.ts +17 -0
- package/dist/lib/tmux/paths.js +30 -0
- package/dist/lib/tmux/session.d.ts +122 -0
- package/dist/lib/tmux/session.js +305 -0
- package/dist/lib/types.d.ts +73 -13
- package/dist/lib/types.js +1 -1
- package/dist/lib/usage.js +1 -1
- package/dist/lib/versions.d.ts +4 -4
- package/dist/lib/versions.js +138 -496
- package/dist/lib/workflows.d.ts +2 -4
- package/dist/lib/workflows.js +3 -4
- package/package.json +6 -3
- package/scripts/postinstall.js +16 -63
- package/dist/commands/status.d.ts +0 -9
- package/dist/commands/status.js +0 -25
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents inspect <agent>[@version]` — single-agent, single-version detail view.
|
|
3
|
+
*
|
|
4
|
+
* Summary mode shows the per-version header (paths, shim, capabilities, resource
|
|
5
|
+
* counts, sessions). Drill-down flags (`--skills`, `--hooks`, `--mcp`, ...) list
|
|
6
|
+
* one resource kind; passing a positional query to the same flag fuzzy-searches
|
|
7
|
+
* for a single resource and prints its detail. Resource names render as OSC-8
|
|
8
|
+
* hyperlinks to the marker file (SKILL.md / WORKFLOW.md / AGENT.md / the file
|
|
9
|
+
* itself) so users can click straight to the source.
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import * as os from 'os';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import chalk from 'chalk';
|
|
15
|
+
import * as yaml from 'yaml';
|
|
16
|
+
import { AGENTS, getCliState } from '../lib/agents.js';
|
|
17
|
+
import { supports } from '../lib/capabilities.js';
|
|
18
|
+
import { readMeta } from '../lib/state.js';
|
|
19
|
+
import { getVersionHomePath } from '../lib/versions.js';
|
|
20
|
+
import { getShimsDir, getVersionedAliasPath } from '../lib/shims.js';
|
|
21
|
+
import { getAgentResources, listResources, } from '../lib/resources.js';
|
|
22
|
+
import { discoverPlugins } from '../lib/plugins.js';
|
|
23
|
+
import { countSessionsInScope } from '../lib/session/discover.js';
|
|
24
|
+
import { damerauLevenshtein } from '../lib/fuzzy.js';
|
|
25
|
+
/** Resource kinds the inspect command can drill into. */
|
|
26
|
+
const DRILLABLE_KINDS = [
|
|
27
|
+
'commands',
|
|
28
|
+
'skills',
|
|
29
|
+
'hooks',
|
|
30
|
+
'mcp',
|
|
31
|
+
'rules',
|
|
32
|
+
'plugins',
|
|
33
|
+
'workflows',
|
|
34
|
+
'subagents',
|
|
35
|
+
];
|
|
36
|
+
const CAPABILITY_NAMES = [
|
|
37
|
+
'hooks', 'mcp', 'skills', 'commands', 'subagents', 'plugins', 'workflows', 'rules', 'allowlist',
|
|
38
|
+
];
|
|
39
|
+
// ─── Command registration ────────────────────────────────────────────────────
|
|
40
|
+
export function registerInspectCommand(program) {
|
|
41
|
+
const cmd = program
|
|
42
|
+
.command('inspect <agent>')
|
|
43
|
+
.description('Inspect one installed agent at one version — paths, capabilities, resources, drill into any kind.')
|
|
44
|
+
.option('--brief', 'header + capabilities only; skip resources/sessions')
|
|
45
|
+
.option('--json', 'machine-readable JSON output');
|
|
46
|
+
for (const kind of DRILLABLE_KINDS) {
|
|
47
|
+
cmd.option(`--${kind} [query]`, `list ${kind}; pass a name (fuzzy) to show detail`);
|
|
48
|
+
}
|
|
49
|
+
cmd.action(async (target, options) => {
|
|
50
|
+
await inspectAction(target, options);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// ─── Main dispatcher ─────────────────────────────────────────────────────────
|
|
54
|
+
export async function inspectAction(target, options) {
|
|
55
|
+
const { agent, version } = parseTarget(target);
|
|
56
|
+
const versionHome = getVersionHomePath(agent, version);
|
|
57
|
+
if (!fs.existsSync(versionHome)) {
|
|
58
|
+
console.error(chalk.red(`${agent}@${version} is not installed.`));
|
|
59
|
+
console.error(chalk.gray(`Run 'agents add ${agent}@${version}' first.`));
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const drill = pickDrillKind(options);
|
|
64
|
+
if (drill) {
|
|
65
|
+
const { kind, query } = drill;
|
|
66
|
+
if (query === true || query === undefined) {
|
|
67
|
+
await renderList(agent, version, versionHome, kind, options);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
await renderDetail(agent, version, versionHome, kind, String(query), options);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
await renderSummary(agent, version, versionHome, options);
|
|
75
|
+
}
|
|
76
|
+
function parseTarget(target) {
|
|
77
|
+
const [rawAgent, rawVersion] = target.split('@');
|
|
78
|
+
const agentKey = (rawAgent || '').toLowerCase();
|
|
79
|
+
if (!(agentKey in AGENTS)) {
|
|
80
|
+
console.error(chalk.red(`Unknown agent: ${rawAgent}`));
|
|
81
|
+
console.error(chalk.gray(`Known agents: ${Object.keys(AGENTS).join(', ')}`));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
const agent = agentKey;
|
|
85
|
+
let version = rawVersion;
|
|
86
|
+
if (!version || version === 'default') {
|
|
87
|
+
const meta = readMeta();
|
|
88
|
+
const def = meta.agents?.[agent];
|
|
89
|
+
if (!def) {
|
|
90
|
+
console.error(chalk.red(`No default version set for ${agent}.`));
|
|
91
|
+
console.error(chalk.gray(`Pass a version: agents inspect ${agent}@<version>`));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
version = def;
|
|
95
|
+
}
|
|
96
|
+
return { agent, version };
|
|
97
|
+
}
|
|
98
|
+
function pickDrillKind(options) {
|
|
99
|
+
const active = [];
|
|
100
|
+
for (const kind of DRILLABLE_KINDS) {
|
|
101
|
+
const value = options[kind];
|
|
102
|
+
if (value !== undefined)
|
|
103
|
+
active.push({ kind, query: value });
|
|
104
|
+
}
|
|
105
|
+
if (active.length === 0)
|
|
106
|
+
return null;
|
|
107
|
+
if (active.length > 1) {
|
|
108
|
+
console.error(chalk.red(`Pick at most one drill-down flag. Got: ${active.map(a => '--' + a.kind).join(', ')}`));
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
return active[0];
|
|
112
|
+
}
|
|
113
|
+
// ─── Summary mode ────────────────────────────────────────────────────────────
|
|
114
|
+
async function renderSummary(agent, version, versionHome, options) {
|
|
115
|
+
const meta = readMeta();
|
|
116
|
+
const isDefault = meta.agents?.[agent] === version;
|
|
117
|
+
const strategy = meta.run?.[agent]?.strategy ?? 'pinned';
|
|
118
|
+
const cliState = await getCliState(agent).catch(() => null);
|
|
119
|
+
const configSymlink = path.join(os.homedir(), `.${agent}`);
|
|
120
|
+
const configTarget = readSymlinkSafe(configSymlink);
|
|
121
|
+
const shimPath = path.join(getShimsDir(), AGENTS[agent].cliCommand);
|
|
122
|
+
const aliasPath = getVersionedAliasPath(agent, version);
|
|
123
|
+
const capabilities = collectCapabilities(agent, version);
|
|
124
|
+
const counts = options.brief ? null : collectCounts(agent, versionHome);
|
|
125
|
+
const sessions = options.brief ? null : {
|
|
126
|
+
total: safeCountSessions(agent),
|
|
127
|
+
};
|
|
128
|
+
if (options.json) {
|
|
129
|
+
const json = {
|
|
130
|
+
agent,
|
|
131
|
+
version,
|
|
132
|
+
default: isDefault,
|
|
133
|
+
home: versionHome,
|
|
134
|
+
configSymlink: configTarget ? { from: configSymlink, to: configTarget } : null,
|
|
135
|
+
shim: shimPath,
|
|
136
|
+
alias: aliasPath,
|
|
137
|
+
strategy,
|
|
138
|
+
installedShim: cliState?.installed === true ? cliState.path : null,
|
|
139
|
+
capabilities,
|
|
140
|
+
resources: counts,
|
|
141
|
+
sessions,
|
|
142
|
+
};
|
|
143
|
+
console.log(JSON.stringify(json, null, 2));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// Plain text
|
|
147
|
+
const head = `${chalk.bold(agent)} ${chalk.gray('@')} ${chalk.cyan(version)}${isDefault ? ' ' + chalk.green('[default]') : ''}`;
|
|
148
|
+
console.log('\n' + head + '\n');
|
|
149
|
+
const rows = [
|
|
150
|
+
['install', versionHome],
|
|
151
|
+
['config', configTarget ? `${configSymlink} ${chalk.gray('→')} ${configTarget}` : chalk.gray('(no symlink)')],
|
|
152
|
+
['shim', shimPath],
|
|
153
|
+
['alias', aliasPath],
|
|
154
|
+
['strategy', strategy],
|
|
155
|
+
];
|
|
156
|
+
for (const [k, v] of rows)
|
|
157
|
+
console.log(` ${k.padEnd(10)} ${v}`);
|
|
158
|
+
console.log('\n' + chalk.bold('Capabilities'));
|
|
159
|
+
for (const cap of CAPABILITY_NAMES) {
|
|
160
|
+
const res = capabilities[cap];
|
|
161
|
+
const mark = res.ok ? chalk.green('✓') : chalk.red('✗');
|
|
162
|
+
const reason = res.ok ? '' : chalk.gray(`(${res.reason}${res.need ? ' ' + res.need : ''})`);
|
|
163
|
+
console.log(` ${cap.padEnd(10)} ${mark} ${reason}`);
|
|
164
|
+
}
|
|
165
|
+
if (counts) {
|
|
166
|
+
console.log('\n' + chalk.bold('Resources'));
|
|
167
|
+
for (const kind of DRILLABLE_KINDS) {
|
|
168
|
+
const c = counts[kind];
|
|
169
|
+
if (!c)
|
|
170
|
+
continue;
|
|
171
|
+
const breakdown = formatScopeBreakdown(c.bySource);
|
|
172
|
+
console.log(` ${kind.padEnd(10)} ${String(c.total).padStart(4)} ${chalk.gray(breakdown)}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (sessions) {
|
|
176
|
+
console.log('\n' + chalk.bold('Sessions'));
|
|
177
|
+
console.log(` ${'total'.padEnd(10)} ${sessions.total} ${chalk.gray('(across all versions)')}`);
|
|
178
|
+
}
|
|
179
|
+
console.log('');
|
|
180
|
+
console.log(chalk.gray(`Drill in: agents inspect ${agent}@${version} --skills <query>`));
|
|
181
|
+
console.log(chalk.gray(`Diagnose: agents doctor ${agent}@${version}`));
|
|
182
|
+
console.log('');
|
|
183
|
+
}
|
|
184
|
+
// ─── List mode ───────────────────────────────────────────────────────────────
|
|
185
|
+
async function renderList(agent, version, versionHome, kind, options) {
|
|
186
|
+
const items = collectKind(agent, versionHome, kind);
|
|
187
|
+
if (options.json) {
|
|
188
|
+
console.log(JSON.stringify({
|
|
189
|
+
agent,
|
|
190
|
+
version,
|
|
191
|
+
kind,
|
|
192
|
+
count: items.length,
|
|
193
|
+
items: items.map(i => ({ name: i.name, source: i.source, path: i.path, description: i.description })),
|
|
194
|
+
}, null, 2));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
console.log('\n' + chalk.bold(`${agent}@${version}`) + ' ' + chalk.gray(`${kind} (${items.length})`) + '\n');
|
|
198
|
+
if (items.length === 0) {
|
|
199
|
+
console.log(chalk.gray(` (none installed)`));
|
|
200
|
+
console.log('');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
for (const item of items) {
|
|
204
|
+
const tag = chalk.gray(`[${item.source}]`.padEnd(10));
|
|
205
|
+
console.log(` ${tag} ${termLink(chalk.cyan(item.name), item.linkTarget)}`);
|
|
206
|
+
if (item.description) {
|
|
207
|
+
console.log(` ${chalk.gray(truncate(item.description, 90))}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
console.log('');
|
|
211
|
+
}
|
|
212
|
+
// ─── Detail mode (fuzzy) ─────────────────────────────────────────────────────
|
|
213
|
+
async function renderDetail(agent, version, versionHome, kind, query, options) {
|
|
214
|
+
const items = collectKind(agent, versionHome, kind);
|
|
215
|
+
const matches = findMatches(items, query);
|
|
216
|
+
if (matches.length === 0) {
|
|
217
|
+
const suggestions = suggestClosest(items, query, 3);
|
|
218
|
+
if (options.json) {
|
|
219
|
+
console.log(JSON.stringify({ agent, version, kind, query, match: null, suggestions: suggestions.map(s => s.name) }, null, 2));
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
console.error(chalk.red(`No ${kind} matching '${query}'.`));
|
|
223
|
+
if (suggestions.length > 0) {
|
|
224
|
+
console.error(chalk.gray(`Closest: ${suggestions.map(s => s.name).join(', ')}`));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
const best = matches[0];
|
|
230
|
+
const others = matches.slice(1, 4);
|
|
231
|
+
if (options.json) {
|
|
232
|
+
const detail = buildDetail(best.item, kind);
|
|
233
|
+
console.log(JSON.stringify({
|
|
234
|
+
agent,
|
|
235
|
+
version,
|
|
236
|
+
kind,
|
|
237
|
+
query,
|
|
238
|
+
match: { ...detail, matchKind: best.matchKind },
|
|
239
|
+
others: others.map(o => ({ name: o.item.name, source: o.item.source, path: o.item.path, matchKind: o.matchKind })),
|
|
240
|
+
}, null, 2));
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
console.log('\n' + chalk.bold(`${agent}@${version}`) + ' ' + chalk.gray(`${kind} matching "${query}"`) + '\n');
|
|
244
|
+
const matchTag = best.matchKind === 'exact' ? 'exact' : best.matchKind === 'substring' ? 'substring' : `~${best.distance}`;
|
|
245
|
+
console.log(` ${chalk.green('✓')} ${termLink(chalk.bold.cyan(best.item.name), best.item.linkTarget)} ${chalk.gray(`[${matchTag}, ${best.item.source}]`)}`);
|
|
246
|
+
if (best.item.description) {
|
|
247
|
+
console.log(` ${chalk.gray(truncate(best.item.description, 100))}`);
|
|
248
|
+
}
|
|
249
|
+
for (const [k, v] of buildDetailRows(best.item, kind)) {
|
|
250
|
+
console.log(` ${chalk.gray(k.padEnd(10))} ${v}`);
|
|
251
|
+
}
|
|
252
|
+
if (others.length > 0) {
|
|
253
|
+
console.log('\n' + chalk.gray('Other matches:'));
|
|
254
|
+
for (const m of others) {
|
|
255
|
+
const tag = m.matchKind === 'substring' ? 'substring' : `~${m.distance}`;
|
|
256
|
+
console.log(` ${termLink(chalk.cyan(m.item.name), m.item.linkTarget)} ${chalk.gray(`(${tag}) [${m.item.source}]`)}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
console.log('');
|
|
260
|
+
}
|
|
261
|
+
// ─── Data collection ─────────────────────────────────────────────────────────
|
|
262
|
+
function collectCapabilities(agent, version) {
|
|
263
|
+
const out = {};
|
|
264
|
+
for (const cap of CAPABILITY_NAMES) {
|
|
265
|
+
const res = supports(agent, cap, version);
|
|
266
|
+
if (res.ok) {
|
|
267
|
+
out[cap] = { ok: true };
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
out[cap] = { ok: false, reason: res.reason, need: res.need };
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return out;
|
|
274
|
+
}
|
|
275
|
+
function collectCounts(agent, versionHome) {
|
|
276
|
+
const out = {};
|
|
277
|
+
for (const kind of DRILLABLE_KINDS) {
|
|
278
|
+
const items = collectKind(agent, versionHome, kind);
|
|
279
|
+
const bySource = {};
|
|
280
|
+
for (const item of items)
|
|
281
|
+
bySource[item.source] = (bySource[item.source] || 0) + 1;
|
|
282
|
+
out[kind] = { total: items.length, bySource };
|
|
283
|
+
}
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
286
|
+
function collectKind(agent, versionHome, kind) {
|
|
287
|
+
switch (kind) {
|
|
288
|
+
case 'commands':
|
|
289
|
+
case 'hooks':
|
|
290
|
+
case 'workflows':
|
|
291
|
+
return entriesFromAgentResources(agent, versionHome, kind);
|
|
292
|
+
case 'skills':
|
|
293
|
+
return skillsFromAgentResources(agent, versionHome);
|
|
294
|
+
case 'mcp':
|
|
295
|
+
return mcpItems(agent, versionHome);
|
|
296
|
+
case 'rules':
|
|
297
|
+
case 'subagents':
|
|
298
|
+
return listResources(kind).map(r => ({
|
|
299
|
+
name: r.name,
|
|
300
|
+
source: r.source,
|
|
301
|
+
path: r.path,
|
|
302
|
+
linkTarget: linkTarget(r.path),
|
|
303
|
+
description: readDescription(r.path),
|
|
304
|
+
}));
|
|
305
|
+
case 'plugins':
|
|
306
|
+
return pluginItems();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function pluginItems() {
|
|
310
|
+
const plugins = discoverPlugins();
|
|
311
|
+
return plugins.map(p => ({
|
|
312
|
+
name: p.name,
|
|
313
|
+
source: 'user',
|
|
314
|
+
path: p.root,
|
|
315
|
+
linkTarget: linkTarget(p.root),
|
|
316
|
+
description: p.manifest.description ?? '',
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
function entriesFromAgentResources(agent, versionHome, kind) {
|
|
320
|
+
const res = getAgentResources(agent, { home: versionHome });
|
|
321
|
+
const list = res[kind];
|
|
322
|
+
return list.map(e => ({
|
|
323
|
+
name: e.name,
|
|
324
|
+
source: e.scope,
|
|
325
|
+
path: e.path,
|
|
326
|
+
linkTarget: linkTarget(e.path),
|
|
327
|
+
description: readDescription(e.path),
|
|
328
|
+
}));
|
|
329
|
+
}
|
|
330
|
+
function skillsFromAgentResources(agent, versionHome) {
|
|
331
|
+
const res = getAgentResources(agent, { home: versionHome });
|
|
332
|
+
return res.skills.map(s => ({
|
|
333
|
+
name: s.name,
|
|
334
|
+
source: s.scope,
|
|
335
|
+
path: s.path,
|
|
336
|
+
linkTarget: linkTarget(s.path),
|
|
337
|
+
description: readDescription(s.path),
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
340
|
+
function mcpItems(agent, versionHome) {
|
|
341
|
+
const res = getAgentResources(agent, { home: versionHome });
|
|
342
|
+
return res.mcp.map(m => ({
|
|
343
|
+
name: m.name,
|
|
344
|
+
source: m.scope,
|
|
345
|
+
path: '',
|
|
346
|
+
linkTarget: '',
|
|
347
|
+
description: m.version ? `version ${m.version}` : '',
|
|
348
|
+
}));
|
|
349
|
+
}
|
|
350
|
+
// ─── Detail field builders ───────────────────────────────────────────────────
|
|
351
|
+
function buildDetail(item, kind) {
|
|
352
|
+
const rows = buildDetailRows(item, kind);
|
|
353
|
+
const out = {
|
|
354
|
+
name: item.name,
|
|
355
|
+
source: item.source,
|
|
356
|
+
path: item.path,
|
|
357
|
+
description: item.description,
|
|
358
|
+
};
|
|
359
|
+
for (const [k, v] of rows)
|
|
360
|
+
out[k] = v;
|
|
361
|
+
return out;
|
|
362
|
+
}
|
|
363
|
+
function buildDetailRows(item, kind) {
|
|
364
|
+
const rows = [];
|
|
365
|
+
if (item.path && kind !== 'mcp') {
|
|
366
|
+
const stat = safeStat(item.path);
|
|
367
|
+
if (stat)
|
|
368
|
+
rows.push(['size', stat.isDirectory() ? '(bundle)' : `${stat.size} bytes`]);
|
|
369
|
+
}
|
|
370
|
+
// Kind-specific fields
|
|
371
|
+
if (kind === 'skills' || kind === 'commands' || kind === 'subagents') {
|
|
372
|
+
const fm = readFrontmatter(item.path);
|
|
373
|
+
if (fm) {
|
|
374
|
+
// description was already printed by caller; skip if redundant
|
|
375
|
+
if (typeof fm.description === 'string' && fm.description.trim() !== item.description.trim()) {
|
|
376
|
+
rows.push(['description', truncate(fm.description, 120)]);
|
|
377
|
+
}
|
|
378
|
+
if (Array.isArray(fm.triggers))
|
|
379
|
+
rows.push(['triggers', fm.triggers.join(', ')]);
|
|
380
|
+
if (typeof fm.model === 'string')
|
|
381
|
+
rows.push(['model', fm.model]);
|
|
382
|
+
if (Array.isArray(fm.tools))
|
|
383
|
+
rows.push(['tools', fm.tools.join(', ')]);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return rows;
|
|
387
|
+
}
|
|
388
|
+
function findMatches(items, query) {
|
|
389
|
+
const q = query.toLowerCase();
|
|
390
|
+
const out = [];
|
|
391
|
+
for (const item of items) {
|
|
392
|
+
const name = item.name.toLowerCase();
|
|
393
|
+
if (name === q) {
|
|
394
|
+
out.push({ item, matchKind: 'exact', distance: 0 });
|
|
395
|
+
}
|
|
396
|
+
else if (name.includes(q)) {
|
|
397
|
+
out.push({ item, matchKind: 'substring', distance: name.length - q.length });
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (out.length > 0) {
|
|
401
|
+
out.sort((a, b) => rankMatch(a) - rankMatch(b));
|
|
402
|
+
return out;
|
|
403
|
+
}
|
|
404
|
+
// No substring hits — fall back to edit distance.
|
|
405
|
+
const threshold = Math.max(2, Math.floor(q.length * 0.3));
|
|
406
|
+
for (const item of items) {
|
|
407
|
+
const d = damerauLevenshtein(q, item.name.toLowerCase());
|
|
408
|
+
if (d <= threshold)
|
|
409
|
+
out.push({ item, matchKind: 'fuzzy', distance: d });
|
|
410
|
+
}
|
|
411
|
+
out.sort((a, b) => a.distance - b.distance);
|
|
412
|
+
return out;
|
|
413
|
+
}
|
|
414
|
+
function rankMatch(m) {
|
|
415
|
+
if (m.matchKind === 'exact')
|
|
416
|
+
return 0;
|
|
417
|
+
if (m.matchKind === 'substring')
|
|
418
|
+
return 100 + m.distance;
|
|
419
|
+
return 1000 + m.distance;
|
|
420
|
+
}
|
|
421
|
+
function suggestClosest(items, query, n) {
|
|
422
|
+
const q = query.toLowerCase();
|
|
423
|
+
const scored = items.map(item => ({ item, d: damerauLevenshtein(q, item.name.toLowerCase()) }));
|
|
424
|
+
scored.sort((a, b) => a.d - b.d);
|
|
425
|
+
return scored.slice(0, n).map(s => s.item);
|
|
426
|
+
}
|
|
427
|
+
// ─── Frontmatter + description helpers ───────────────────────────────────────
|
|
428
|
+
function readDescription(p) {
|
|
429
|
+
if (!p)
|
|
430
|
+
return '';
|
|
431
|
+
let filePath = p;
|
|
432
|
+
try {
|
|
433
|
+
if (fs.statSync(p).isDirectory()) {
|
|
434
|
+
for (const marker of ['SKILL.md', 'WORKFLOW.md', 'AGENT.md', 'README.md']) {
|
|
435
|
+
const c = path.join(p, marker);
|
|
436
|
+
if (fs.existsSync(c)) {
|
|
437
|
+
filePath = c;
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
return '';
|
|
445
|
+
}
|
|
446
|
+
const fm = readFrontmatter(filePath);
|
|
447
|
+
if (fm && typeof fm.description === 'string' && fm.description.trim().length > 0) {
|
|
448
|
+
return fm.description.trim();
|
|
449
|
+
}
|
|
450
|
+
return readFirstProseLine(filePath);
|
|
451
|
+
}
|
|
452
|
+
function readFrontmatter(p) {
|
|
453
|
+
if (!p)
|
|
454
|
+
return null;
|
|
455
|
+
let filePath = p;
|
|
456
|
+
try {
|
|
457
|
+
if (fs.statSync(p).isDirectory()) {
|
|
458
|
+
for (const marker of ['SKILL.md', 'WORKFLOW.md', 'AGENT.md']) {
|
|
459
|
+
const c = path.join(p, marker);
|
|
460
|
+
if (fs.existsSync(c)) {
|
|
461
|
+
filePath = c;
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
catch {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
if (!filePath.endsWith('.md'))
|
|
471
|
+
return null;
|
|
472
|
+
let head = '';
|
|
473
|
+
try {
|
|
474
|
+
const fd = fs.openSync(filePath, 'r');
|
|
475
|
+
const buf = Buffer.alloc(4096);
|
|
476
|
+
const n = fs.readSync(fd, buf, 0, 4096, 0);
|
|
477
|
+
fs.closeSync(fd);
|
|
478
|
+
head = buf.subarray(0, n).toString('utf-8');
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
if (!head.startsWith('---'))
|
|
484
|
+
return null;
|
|
485
|
+
const end = head.indexOf('\n---', 3);
|
|
486
|
+
if (end === -1)
|
|
487
|
+
return null;
|
|
488
|
+
const body = head.slice(3, end).trim();
|
|
489
|
+
try {
|
|
490
|
+
const parsed = yaml.parse(body);
|
|
491
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
492
|
+
}
|
|
493
|
+
catch {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function readFirstProseLine(p) {
|
|
498
|
+
try {
|
|
499
|
+
const stat = fs.statSync(p);
|
|
500
|
+
if (stat.isDirectory())
|
|
501
|
+
return '';
|
|
502
|
+
if (stat.size > 64 * 1024)
|
|
503
|
+
return '';
|
|
504
|
+
const text = fs.readFileSync(p, 'utf-8');
|
|
505
|
+
// Skip frontmatter
|
|
506
|
+
let body = text;
|
|
507
|
+
if (body.startsWith('---')) {
|
|
508
|
+
const end = body.indexOf('\n---', 3);
|
|
509
|
+
if (end !== -1)
|
|
510
|
+
body = body.slice(end + 4);
|
|
511
|
+
}
|
|
512
|
+
for (const raw of body.split('\n')) {
|
|
513
|
+
const line = raw.trim();
|
|
514
|
+
if (!line)
|
|
515
|
+
continue;
|
|
516
|
+
if (line.startsWith('#'))
|
|
517
|
+
return line.replace(/^#+\s*/, '');
|
|
518
|
+
return line;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
catch { /* ignore */ }
|
|
522
|
+
return '';
|
|
523
|
+
}
|
|
524
|
+
// ─── OSC-8 + path helpers ────────────────────────────────────────────────────
|
|
525
|
+
function termLink(text, filePath) {
|
|
526
|
+
if (!filePath)
|
|
527
|
+
return text;
|
|
528
|
+
const url = `file://${filePath}`;
|
|
529
|
+
return `\x1b]8;;${url}\x1b\\${text}\x1b]8;;\x1b\\`;
|
|
530
|
+
}
|
|
531
|
+
function linkTarget(p) {
|
|
532
|
+
if (!p)
|
|
533
|
+
return '';
|
|
534
|
+
try {
|
|
535
|
+
if (!fs.statSync(p).isDirectory())
|
|
536
|
+
return p;
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
return p;
|
|
540
|
+
}
|
|
541
|
+
for (const marker of ['SKILL.md', 'WORKFLOW.md', 'AGENT.md']) {
|
|
542
|
+
const c = path.join(p, marker);
|
|
543
|
+
if (fs.existsSync(c))
|
|
544
|
+
return c;
|
|
545
|
+
}
|
|
546
|
+
return p;
|
|
547
|
+
}
|
|
548
|
+
function readSymlinkSafe(p) {
|
|
549
|
+
try {
|
|
550
|
+
const stat = fs.lstatSync(p);
|
|
551
|
+
if (!stat.isSymbolicLink())
|
|
552
|
+
return null;
|
|
553
|
+
return fs.readlinkSync(p);
|
|
554
|
+
}
|
|
555
|
+
catch {
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function safeStat(p) {
|
|
560
|
+
try {
|
|
561
|
+
return fs.statSync(p);
|
|
562
|
+
}
|
|
563
|
+
catch {
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const SESSION_AGENTS = new Set([
|
|
568
|
+
'claude', 'codex', 'gemini', 'opencode', 'openclaw', 'rush', 'hermes', 'grok', 'kimi',
|
|
569
|
+
]);
|
|
570
|
+
function safeCountSessions(agent) {
|
|
571
|
+
if (!SESSION_AGENTS.has(agent))
|
|
572
|
+
return 0;
|
|
573
|
+
try {
|
|
574
|
+
return countSessionsInScope({ agent: agent });
|
|
575
|
+
}
|
|
576
|
+
catch {
|
|
577
|
+
return 0;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function truncate(s, n) {
|
|
581
|
+
if (s.length <= n)
|
|
582
|
+
return s;
|
|
583
|
+
return s.slice(0, n - 1) + '…';
|
|
584
|
+
}
|
|
585
|
+
function formatScopeBreakdown(bySource) {
|
|
586
|
+
const entries = Object.entries(bySource);
|
|
587
|
+
if (entries.length === 0)
|
|
588
|
+
return '';
|
|
589
|
+
return '[' + entries.map(([k, v]) => `${k}:${v}`).join(' ') + ']';
|
|
590
|
+
}
|