@phnx-labs/agents-cli 1.20.6 → 1.20.8
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 +13 -0
- package/dist/commands/computer-actions.d.ts +55 -0
- package/dist/commands/computer-actions.js +486 -0
- package/dist/commands/computer.js +67 -56
- package/dist/commands/inspect.d.ts +38 -7
- package/dist/commands/inspect.js +194 -24
- package/dist/commands/sessions.js +9 -12
- package/dist/commands/setup.js +2 -2
- package/dist/commands/versions.js +2 -2
- package/dist/index.js +23 -1
- package/dist/lib/computer-rpc.d.ts +2 -0
- package/dist/lib/computer-rpc.js +21 -1
- package/dist/lib/daemon.js +4 -7
- package/dist/lib/exec.d.ts +9 -0
- package/dist/lib/exec.js +61 -5
- package/dist/lib/platform/exec.d.ts +9 -0
- package/dist/lib/platform/exec.js +24 -0
- package/dist/lib/platform/index.d.ts +20 -0
- package/dist/lib/platform/index.js +20 -0
- package/dist/lib/platform/paths.d.ts +22 -0
- package/dist/lib/platform/paths.js +49 -0
- package/dist/lib/platform/process.d.ts +12 -0
- package/dist/lib/platform/process.js +22 -0
- package/dist/lib/pty-client.js +13 -5
- package/dist/lib/pty-server.d.ts +24 -1
- package/dist/lib/pty-server.js +102 -25
- package/dist/lib/refresh.js +2 -2
- package/dist/lib/session/artifacts.js +8 -2
- package/dist/lib/shims.d.ts +13 -8
- package/dist/lib/shims.js +84 -4
- package/dist/lib/teams/agents.js +5 -7
- package/package.json +1 -1
- package/scripts/postinstall.js +18 -1
|
@@ -3,22 +3,24 @@ import * as fs from 'fs';
|
|
|
3
3
|
import * as os from 'os';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import { registerCommandGroups } from '../lib/help.js';
|
|
6
|
-
import { openComputerClient, resolveHelperApp, resolveHelperExec, resolveSocketPath, resolveLogPath, resolvePolicyPath, resolvePeersPath,
|
|
6
|
+
import { openComputerClient, resolveHelperApp, resolveHelperExec, resolveSocketPath, resolveLogPath, resolvePolicyPath, resolvePeersPath, loadComputerAllowList, loadDefaultPeers, writeComputerPolicy, writeComputerPeers, } from '../lib/computer-rpc.js';
|
|
7
|
+
import { registerActionCommands, withClient, unwrap, pickTarget } from './computer-actions.js';
|
|
7
8
|
// Help groups — mirror `agents browser` so the mental model carries over.
|
|
8
9
|
const COMPUTER_HELP_GROUPS = [
|
|
9
|
-
{ title: 'Installation', names: ['
|
|
10
|
+
{ title: 'Installation', names: ['setup'] },
|
|
10
11
|
{ title: 'Daemon lifecycle', names: ['start', 'stop', 'reload', 'status'] },
|
|
11
|
-
{ title: '
|
|
12
|
+
{ title: 'Observe', names: ['apps', 'describe', 'screenshot', 'get-text'] },
|
|
13
|
+
{ title: 'Interact', names: ['launch', 'raise', 'click', 'right-click', 'type', 'type-text', 'key', 'drag', 'scroll', 'ax-action', 'focus', 'wait'] },
|
|
12
14
|
];
|
|
13
15
|
export function registerComputerCommand(program) {
|
|
14
16
|
const computer = program
|
|
15
|
-
.command('
|
|
17
|
+
.command('computer')
|
|
16
18
|
.description('Drive macOS apps via Accessibility — list, screenshot, click, type (macOS only)')
|
|
17
19
|
// The whole subsystem is macOS Accessibility / TCC. Fail fast with a clear
|
|
18
20
|
// message on other platforms instead of a downstream ENOENT / launchctl error.
|
|
19
21
|
.hook('preAction', () => {
|
|
20
22
|
if (process.platform !== 'darwin') {
|
|
21
|
-
console.error('agents
|
|
23
|
+
console.error('agents computer: macOS only — it drives apps via the macOS Accessibility API.');
|
|
22
24
|
process.exit(1);
|
|
23
25
|
}
|
|
24
26
|
});
|
|
@@ -26,18 +28,15 @@ export function registerComputerCommand(program) {
|
|
|
26
28
|
registerCommandGroups(computer, COMPUTER_HELP_GROUPS);
|
|
27
29
|
}
|
|
28
30
|
export function registerComputerSubcommands(program) {
|
|
29
|
-
|
|
31
|
+
registerSetupCommand(program);
|
|
30
32
|
registerStartCommand(program);
|
|
31
33
|
registerStopCommand(program);
|
|
32
34
|
registerReloadCommand(program);
|
|
33
35
|
registerStatusCommand(program);
|
|
34
36
|
registerScreenshotCommand(program);
|
|
37
|
+
registerActionCommands(program);
|
|
35
38
|
registerCommandGroups(program, COMPUTER_HELP_GROUPS);
|
|
36
39
|
}
|
|
37
|
-
function reportMissingHelper() {
|
|
38
|
-
console.error('helper not built. Run: ./packages/computer-helper/scripts/build.sh debug');
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
41
40
|
function registerStatusCommand(program) {
|
|
42
41
|
program
|
|
43
42
|
.command('status')
|
|
@@ -58,7 +57,7 @@ function registerStatusCommand(program) {
|
|
|
58
57
|
console.log(`peers: ${callers.length} caller${callers.length === 1 ? '' : 's'} (peer-auth on socket)`);
|
|
59
58
|
if (!installed) {
|
|
60
59
|
console.log('');
|
|
61
|
-
console.log('Run: agents computer
|
|
60
|
+
console.log('Run: agents computer setup');
|
|
62
61
|
return;
|
|
63
62
|
}
|
|
64
63
|
if (!socketUp) {
|
|
@@ -92,50 +91,55 @@ function registerStatusCommand(program) {
|
|
|
92
91
|
function registerScreenshotCommand(program) {
|
|
93
92
|
program
|
|
94
93
|
.command('screenshot')
|
|
95
|
-
.description('Capture a
|
|
96
|
-
.option('--bundle <id>', 'Bundle id to capture (default:
|
|
94
|
+
.description('Capture a window (default: largest), enumerate windows (--list), or the whole display (--display)')
|
|
95
|
+
.option('--bundle <id>', 'Bundle id to capture (default: frontmost allow-listed app)')
|
|
96
|
+
.option('--pid <n>', 'Target pid directly (overrides --bundle)', (v) => parseInt(v, 10))
|
|
97
|
+
.option('--list', 'List the app\'s windows (id/title/layer/bounds) instead of capturing — reveals modals/popups')
|
|
98
|
+
.option('--window-id <n>', 'Capture a specific window by id (from --list)', (v) => parseInt(v, 10))
|
|
99
|
+
.option('--display', 'Capture the whole display the app is on (composites stacked modals)')
|
|
97
100
|
.option('--out <path>', 'Output JPEG path', './computer-screenshot.jpg')
|
|
98
101
|
.option('--quality <n>', 'JPEG quality 1-100', (v) => parseInt(v, 10), 85)
|
|
102
|
+
.option('--json', 'Emit JSON (metadata for captures; window list for --list)')
|
|
99
103
|
.action(async (opts) => {
|
|
100
|
-
const transport = describeTransport();
|
|
101
|
-
if (transport.kind === 'none')
|
|
102
|
-
reportMissingHelper();
|
|
103
104
|
const quality = Math.max(1, Math.min(100, opts.quality || 85));
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const list = apps.result?.apps || [];
|
|
113
|
-
let target;
|
|
114
|
-
if (opts.bundle) {
|
|
115
|
-
target = list.find((a) => a.bundle_id === opts.bundle);
|
|
116
|
-
if (!target) {
|
|
117
|
-
console.error(`bundle not in allow list (or not running): ${opts.bundle}`);
|
|
118
|
-
console.error(`add Computer(${opts.bundle}) to a permissions group, then \`agents computer reload\``);
|
|
105
|
+
await withClient(async (client) => {
|
|
106
|
+
// Resolve the target pid (explicit --pid, else --bundle, else frontmost).
|
|
107
|
+
let pid = opts.pid;
|
|
108
|
+
if (pid == null) {
|
|
109
|
+
const list = unwrap(await client.call('list_apps')).apps || [];
|
|
110
|
+
const picked = pickTarget(list, { bundle: opts.bundle });
|
|
111
|
+
if (!picked.ok) {
|
|
112
|
+
console.error(picked.error);
|
|
119
113
|
process.exit(1);
|
|
120
114
|
}
|
|
115
|
+
pid = picked.app.pid;
|
|
121
116
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
117
|
+
// --list: enumerate windows, no image.
|
|
118
|
+
if (opts.list) {
|
|
119
|
+
const res = unwrap(await client.call('screenshot', { pid, list: true }));
|
|
120
|
+
const windows = res.windows || [];
|
|
121
|
+
if (opts.json) {
|
|
122
|
+
console.log(JSON.stringify(res, null, 2));
|
|
128
123
|
}
|
|
124
|
+
else if (windows.length === 0) {
|
|
125
|
+
console.log('(no windows)');
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
for (const w of windows) {
|
|
129
|
+
const b = w.bounds || [];
|
|
130
|
+
console.log(`${String(w.window_id).padStart(8)} layer ${w.layer} [${b.join(',')}] ${w.title || '(untitled)'}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
129
134
|
}
|
|
130
|
-
//
|
|
131
|
-
const
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
const height = shot.result?.height;
|
|
135
|
+
// Capture: window (default / --window-id) or full display.
|
|
136
|
+
const params = { pid, quality };
|
|
137
|
+
if (opts.display)
|
|
138
|
+
params.display = true;
|
|
139
|
+
else if (opts.windowId != null)
|
|
140
|
+
params.window_id = opts.windowId;
|
|
141
|
+
const res = unwrap(await client.call('screenshot', params));
|
|
142
|
+
const b64 = res.image_data;
|
|
139
143
|
if (!b64) {
|
|
140
144
|
console.error('helper returned no image_data');
|
|
141
145
|
process.exit(1);
|
|
@@ -143,14 +147,20 @@ function registerScreenshotCommand(program) {
|
|
|
143
147
|
const buf = Buffer.from(b64, 'base64');
|
|
144
148
|
const outPath = path.resolve(opts.out);
|
|
145
149
|
fs.writeFileSync(outPath, buf);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
150
|
+
if (opts.json) {
|
|
151
|
+
// Drop the heavy base64 from the metadata echo; report where it went.
|
|
152
|
+
const meta = { ...res, image_data: `<saved to ${outPath}>` };
|
|
153
|
+
console.log(JSON.stringify(meta, null, 2));
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const origin = res.origin || [];
|
|
157
|
+
const originStr = origin.length === 2 ? `, origin [${origin.join(',')}], scale ${res.scale ?? '?'}` : '';
|
|
158
|
+
console.log(`saved: ${outPath} (${res.width ?? '?'}x${res.height ?? '?'}, ${buf.byteLength} bytes${originStr})`);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
151
161
|
});
|
|
152
162
|
}
|
|
153
|
-
// install-helper:
|
|
163
|
+
// setup (alias: install-helper):
|
|
154
164
|
// 1. resolve dist .app
|
|
155
165
|
// 2. copy to /Applications/Computer Helper.app
|
|
156
166
|
// 3. codesign --verify the destination
|
|
@@ -167,9 +177,10 @@ const HELPER_BUNDLE_ID = 'com.phnx-labs.computer-helper';
|
|
|
167
177
|
const HELPER_APP_NAME = 'Computer Helper.app';
|
|
168
178
|
const HELPER_APP_DEST = `/Applications/${HELPER_APP_NAME}`;
|
|
169
179
|
const HELPER_LABEL = HELPER_BUNDLE_ID;
|
|
170
|
-
function
|
|
180
|
+
function registerSetupCommand(program) {
|
|
171
181
|
program
|
|
172
|
-
.command('
|
|
182
|
+
.command('setup')
|
|
183
|
+
.alias('install-helper')
|
|
173
184
|
.description('Install ComputerHelper.app to /Applications/ (does NOT activate the daemon — run `start` to enable)')
|
|
174
185
|
.action(async () => {
|
|
175
186
|
const srcApp = resolveHelperApp();
|
|
@@ -263,12 +274,12 @@ function registerStartCommand(program) {
|
|
|
263
274
|
const logPath = resolveLogPath();
|
|
264
275
|
if (!fs.existsSync(plistPath)) {
|
|
265
276
|
console.error(`plist not found at ${plistPath}`);
|
|
266
|
-
console.error('run: agents computer
|
|
277
|
+
console.error('run: agents computer setup');
|
|
267
278
|
process.exit(1);
|
|
268
279
|
}
|
|
269
280
|
if (!fs.existsSync(HELPER_APP_DEST)) {
|
|
270
281
|
console.error(`helper app not found at ${HELPER_APP_DEST}`);
|
|
271
|
-
console.error('run: agents computer
|
|
282
|
+
console.error('run: agents computer setup');
|
|
272
283
|
process.exit(1);
|
|
273
284
|
}
|
|
274
285
|
const uid = process.getuid?.();
|
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `agents inspect <
|
|
2
|
+
* `agents inspect <target>` — detail view for one agent+version or one DotAgents repo.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* counts, sessions).
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Agent targets (`claude`, `claude@2.1.170`) show the per-version header (paths,
|
|
5
|
+
* shim, capabilities, resource counts, sessions). Repo targets (`user`, `system`,
|
|
6
|
+
* `project`, a registered extra-repo alias, or a filesystem path to a repo with a
|
|
7
|
+
* `.agents/` dir or to a DotAgents root itself) show the repo root, git state, and
|
|
8
|
+
* per-kind resource counts. Drill-down flags (`--skills`, `--hooks`, `--mcp`, ...)
|
|
9
|
+
* list one resource kind for either target form; passing a positional query to the
|
|
10
|
+
* same flag fuzzy-searches for a single resource and prints its detail. Resource
|
|
11
|
+
* names render as OSC-8 hyperlinks to the marker file (SKILL.md / WORKFLOW.md /
|
|
12
|
+
* AGENT.md / the file itself) so users can click straight to the source.
|
|
10
13
|
*/
|
|
11
14
|
import { Command } from 'commander';
|
|
15
|
+
/** Resource kinds the inspect command can drill into. */
|
|
16
|
+
declare const DRILLABLE_KINDS: readonly ["commands", "skills", "hooks", "mcp", "rules", "plugins", "workflows", "subagents"];
|
|
17
|
+
type DrillableKind = typeof DRILLABLE_KINDS[number];
|
|
18
|
+
interface ResourceItem {
|
|
19
|
+
name: string;
|
|
20
|
+
source: string;
|
|
21
|
+
/** Absolute path to the resource entry (file or directory). */
|
|
22
|
+
path: string;
|
|
23
|
+
/** Path the OSC-8 link should point at — marker file inside bundles, else `path`. */
|
|
24
|
+
linkTarget: string;
|
|
25
|
+
/** One-line description (frontmatter `description:` or first non-frontmatter line). */
|
|
26
|
+
description: string;
|
|
27
|
+
}
|
|
12
28
|
interface InspectOptions {
|
|
13
29
|
brief?: boolean;
|
|
14
30
|
json?: boolean;
|
|
@@ -23,4 +39,19 @@ interface InspectOptions {
|
|
|
23
39
|
}
|
|
24
40
|
export declare function registerInspectCommand(program: Command): void;
|
|
25
41
|
export declare function inspectAction(target: string, options: InspectOptions): Promise<void>;
|
|
42
|
+
export interface RepoTarget {
|
|
43
|
+
/** Display label: 'user' | 'system' | 'project', an extra-repo alias, or a path-derived name. */
|
|
44
|
+
label: string;
|
|
45
|
+
/** Absolute path to the DotAgents root (the dir holding commands/, skills/, ...). */
|
|
46
|
+
root: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve a non-agent target as a DotAgents repo: the built-in layer names,
|
|
50
|
+
* a registered extra-repo alias, or a filesystem path. Paths accept either a
|
|
51
|
+
* DotAgents root itself or a repo whose `.agents/` dir should be inspected.
|
|
52
|
+
* Returns null when the target is none of these.
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveRepoTarget(target: string, cwd?: string): RepoTarget | null;
|
|
55
|
+
/** List one resource kind from a single repo root — no layering, no overrides. */
|
|
56
|
+
export declare function collectRepoKind(repo: RepoTarget, kind: DrillableKind): ResourceItem[];
|
|
26
57
|
export {};
|
package/dist/commands/inspect.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `agents inspect <
|
|
2
|
+
* `agents inspect <target>` — detail view for one agent+version or one DotAgents repo.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* counts, sessions).
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Agent targets (`claude`, `claude@2.1.170`) show the per-version header (paths,
|
|
5
|
+
* shim, capabilities, resource counts, sessions). Repo targets (`user`, `system`,
|
|
6
|
+
* `project`, a registered extra-repo alias, or a filesystem path to a repo with a
|
|
7
|
+
* `.agents/` dir or to a DotAgents root itself) show the repo root, git state, and
|
|
8
|
+
* per-kind resource counts. Drill-down flags (`--skills`, `--hooks`, `--mcp`, ...)
|
|
9
|
+
* list one resource kind for either target form; passing a positional query to the
|
|
10
|
+
* same flag fuzzy-searches for a single resource and prints its detail. Resource
|
|
11
|
+
* names render as OSC-8 hyperlinks to the marker file (SKILL.md / WORKFLOW.md /
|
|
12
|
+
* AGENT.md / the file itself) so users can click straight to the source.
|
|
10
13
|
*/
|
|
14
|
+
import { execSync } from 'child_process';
|
|
11
15
|
import * as fs from 'fs';
|
|
12
16
|
import * as os from 'os';
|
|
13
17
|
import * as path from 'path';
|
|
@@ -15,7 +19,7 @@ import chalk from 'chalk';
|
|
|
15
19
|
import * as yaml from 'yaml';
|
|
16
20
|
import { AGENTS, getCliState } from '../lib/agents.js';
|
|
17
21
|
import { supports } from '../lib/capabilities.js';
|
|
18
|
-
import { readMeta } from '../lib/state.js';
|
|
22
|
+
import { readMeta, getUserAgentsDir, getSystemAgentsDir, getProjectAgentsDir, getEnabledExtraRepos, } from '../lib/state.js';
|
|
19
23
|
import { getVersionHomePath } from '../lib/versions.js';
|
|
20
24
|
import { getShimsDir, getVersionedAliasPath } from '../lib/shims.js';
|
|
21
25
|
import { getAgentResources, listResources, } from '../lib/resources.js';
|
|
@@ -39,8 +43,8 @@ const CAPABILITY_NAMES = [
|
|
|
39
43
|
// ─── Command registration ────────────────────────────────────────────────────
|
|
40
44
|
export function registerInspectCommand(program) {
|
|
41
45
|
const cmd = program
|
|
42
|
-
.command('inspect <
|
|
43
|
-
.description('Inspect one installed agent at one version — paths, capabilities, resources, drill into any kind.')
|
|
46
|
+
.command('inspect <target>')
|
|
47
|
+
.description('Inspect one installed agent at one version, or a DotAgents repo (user|system|project|alias|path) — paths, capabilities, resources, drill into any kind.')
|
|
44
48
|
.option('--brief', 'header + capabilities only; skip resources/sessions')
|
|
45
49
|
.option('--json', 'machine-readable JSON output');
|
|
46
50
|
for (const kind of DRILLABLE_KINDS) {
|
|
@@ -52,6 +56,20 @@ export function registerInspectCommand(program) {
|
|
|
52
56
|
}
|
|
53
57
|
// ─── Main dispatcher ─────────────────────────────────────────────────────────
|
|
54
58
|
export async function inspectAction(target, options) {
|
|
59
|
+
const agentKey = target.split('@')[0].toLowerCase();
|
|
60
|
+
if (!(agentKey in AGENTS)) {
|
|
61
|
+
const repo = resolveRepoTarget(target);
|
|
62
|
+
if (repo) {
|
|
63
|
+
await inspectRepo(repo, options);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const extras = getEnabledExtraRepos();
|
|
67
|
+
console.error(chalk.red(`Unknown target: ${target}`));
|
|
68
|
+
console.error(chalk.gray(`Agents: ${Object.keys(AGENTS).join(', ')}`));
|
|
69
|
+
const aliases = extras.length > 0 ? `, ${extras.map(e => e.alias).join(', ')}` : '';
|
|
70
|
+
console.error(chalk.gray(`Repos: user, system, project${aliases} — or a path to a repo with a .agents/ dir`));
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
55
73
|
const { agent, version } = parseTarget(target);
|
|
56
74
|
const versionHome = getVersionHomePath(agent, version);
|
|
57
75
|
if (!fs.existsSync(versionHome)) {
|
|
@@ -75,13 +93,7 @@ export async function inspectAction(target, options) {
|
|
|
75
93
|
}
|
|
76
94
|
function parseTarget(target) {
|
|
77
95
|
const [rawAgent, rawVersion] = target.split('@');
|
|
78
|
-
const
|
|
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;
|
|
96
|
+
const agent = (rawAgent || '').toLowerCase();
|
|
85
97
|
let version = rawVersion;
|
|
86
98
|
if (!version || version === 'default') {
|
|
87
99
|
const meta = readMeta();
|
|
@@ -110,6 +122,160 @@ function pickDrillKind(options) {
|
|
|
110
122
|
}
|
|
111
123
|
return active[0];
|
|
112
124
|
}
|
|
125
|
+
/** Files at a DotAgents root that mark it as one, beyond the per-kind dirs. */
|
|
126
|
+
const REPO_MARKER_FILES = ['agents.yaml', 'hooks.yaml'];
|
|
127
|
+
/**
|
|
128
|
+
* Resolve a non-agent target as a DotAgents repo: the built-in layer names,
|
|
129
|
+
* a registered extra-repo alias, or a filesystem path. Paths accept either a
|
|
130
|
+
* DotAgents root itself or a repo whose `.agents/` dir should be inspected.
|
|
131
|
+
* Returns null when the target is none of these.
|
|
132
|
+
*/
|
|
133
|
+
export function resolveRepoTarget(target, cwd) {
|
|
134
|
+
if (target === 'user')
|
|
135
|
+
return { label: 'user', root: getUserAgentsDir() };
|
|
136
|
+
if (target === 'system')
|
|
137
|
+
return { label: 'system', root: getSystemAgentsDir() };
|
|
138
|
+
if (target === 'project') {
|
|
139
|
+
const dir = getProjectAgentsDir(cwd);
|
|
140
|
+
if (!dir) {
|
|
141
|
+
console.error(chalk.red('No project .agents/ directory found from the current directory.'));
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
return { label: 'project', root: dir };
|
|
145
|
+
}
|
|
146
|
+
for (const extra of getEnabledExtraRepos()) {
|
|
147
|
+
if (extra.alias === target)
|
|
148
|
+
return { label: extra.alias, root: extra.dir };
|
|
149
|
+
}
|
|
150
|
+
const expanded = target.startsWith('~/') ? path.join(os.homedir(), target.slice(2)) : target;
|
|
151
|
+
const abs = path.resolve(cwd ?? process.cwd(), expanded);
|
|
152
|
+
const stat = safeStat(abs);
|
|
153
|
+
if (!stat || !stat.isDirectory())
|
|
154
|
+
return null;
|
|
155
|
+
// A dir that is itself a DotAgents root wins over its nested .agents/ —
|
|
156
|
+
// extra repos like ~/.agents-extras keep resources at the top level and use
|
|
157
|
+
// .agents/ only for worktrees.
|
|
158
|
+
if (isDotAgentsRoot(abs)) {
|
|
159
|
+
const label = path.basename(abs) === '.agents' ? path.basename(path.dirname(abs)) : path.basename(abs);
|
|
160
|
+
return { label, root: abs };
|
|
161
|
+
}
|
|
162
|
+
if (path.basename(abs) !== '.agents') {
|
|
163
|
+
const nested = path.join(abs, '.agents');
|
|
164
|
+
if (safeStat(nested)?.isDirectory()) {
|
|
165
|
+
return { label: path.basename(abs), root: nested };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
function isDotAgentsRoot(dir) {
|
|
171
|
+
for (const marker of REPO_MARKER_FILES) {
|
|
172
|
+
if (fs.existsSync(path.join(dir, marker)))
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
for (const kind of DRILLABLE_KINDS) {
|
|
176
|
+
if (safeStat(path.join(dir, kind))?.isDirectory())
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
async function inspectRepo(repo, options) {
|
|
182
|
+
const drill = pickDrillKind(options);
|
|
183
|
+
const jsonHead = { repo: repo.label, root: repo.root };
|
|
184
|
+
if (drill) {
|
|
185
|
+
const items = collectRepoKind(repo, drill.kind);
|
|
186
|
+
if (drill.query === true || drill.query === undefined) {
|
|
187
|
+
renderItemList(repo.label, jsonHead, drill.kind, items, options);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
renderItemDetail(repo.label, jsonHead, drill.kind, String(drill.query), items, options);
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
renderRepoSummary(repo, options);
|
|
195
|
+
}
|
|
196
|
+
/** List one resource kind from a single repo root — no layering, no overrides. */
|
|
197
|
+
export function collectRepoKind(repo, kind) {
|
|
198
|
+
const dir = path.join(repo.root, kind);
|
|
199
|
+
let entries;
|
|
200
|
+
try {
|
|
201
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
const items = [];
|
|
207
|
+
for (const entry of entries) {
|
|
208
|
+
if (entry.name.startsWith('.'))
|
|
209
|
+
continue;
|
|
210
|
+
const p = path.join(dir, entry.name);
|
|
211
|
+
items.push({
|
|
212
|
+
name: entry.name.replace(/\.(md|yaml|yml|toml|json)$/, ''),
|
|
213
|
+
source: repo.label,
|
|
214
|
+
path: p,
|
|
215
|
+
linkTarget: linkTarget(p),
|
|
216
|
+
description: readDescription(p),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return items.sort((a, b) => a.name.localeCompare(b.name));
|
|
220
|
+
}
|
|
221
|
+
function renderRepoSummary(repo, options) {
|
|
222
|
+
const git = repoGitInfo(repo.root);
|
|
223
|
+
const manifests = REPO_MARKER_FILES.filter(m => fs.existsSync(path.join(repo.root, m)));
|
|
224
|
+
const counts = {};
|
|
225
|
+
if (!options.brief) {
|
|
226
|
+
for (const kind of DRILLABLE_KINDS) {
|
|
227
|
+
const items = collectRepoKind(repo, kind);
|
|
228
|
+
counts[kind] = { total: items.length, bySource: { [repo.label]: items.length } };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (options.json) {
|
|
232
|
+
console.log(JSON.stringify({
|
|
233
|
+
repo: repo.label,
|
|
234
|
+
root: repo.root,
|
|
235
|
+
git,
|
|
236
|
+
manifests,
|
|
237
|
+
resources: options.brief ? null : Object.fromEntries(DRILLABLE_KINDS.map(kind => [kind, counts[kind].total])),
|
|
238
|
+
}, null, 2));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
console.log('\n' + chalk.bold(repo.label) + ' ' + chalk.gray('[dotagents repo]') + '\n');
|
|
242
|
+
const rows = [['root', termLink(repo.root, repo.root)]];
|
|
243
|
+
if (git) {
|
|
244
|
+
const dirty = git.dirty > 0 ? ` ${chalk.gray('·')} ${chalk.yellow(`${git.dirty} dirty`)}` : '';
|
|
245
|
+
const url = git.url ? ` ${chalk.gray('·')} ${chalk.gray(git.url)}` : '';
|
|
246
|
+
rows.push(['git', `${git.branch}${dirty}${url}`]);
|
|
247
|
+
}
|
|
248
|
+
if (manifests.length > 0)
|
|
249
|
+
rows.push(['manifests', manifests.join(', ')]);
|
|
250
|
+
for (const [k, v] of rows)
|
|
251
|
+
console.log(` ${k.padEnd(10)} ${v}`);
|
|
252
|
+
if (!options.brief) {
|
|
253
|
+
console.log('\n' + chalk.bold('Resources'));
|
|
254
|
+
for (const kind of DRILLABLE_KINDS) {
|
|
255
|
+
console.log(` ${kind.padEnd(10)} ${String(counts[kind].total).padStart(4)}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
console.log('');
|
|
259
|
+
console.log(chalk.gray(`Drill in: agents inspect ${repo.label} --skills <query>`));
|
|
260
|
+
console.log('');
|
|
261
|
+
}
|
|
262
|
+
function repoGitInfo(root) {
|
|
263
|
+
const git = (args) => {
|
|
264
|
+
try {
|
|
265
|
+
return execSync(`git -C ${JSON.stringify(root)} ${args}`, { stdio: ['ignore', 'pipe', 'ignore'] })
|
|
266
|
+
.toString().trim();
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
const branch = git('rev-parse --abbrev-ref HEAD');
|
|
273
|
+
if (branch === null)
|
|
274
|
+
return null;
|
|
275
|
+
const status = git('status --porcelain');
|
|
276
|
+
const dirty = status ? status.split('\n').filter(Boolean).length : 0;
|
|
277
|
+
return { branch, dirty, url: git('remote get-url origin') };
|
|
278
|
+
}
|
|
113
279
|
// ─── Summary mode ────────────────────────────────────────────────────────────
|
|
114
280
|
async function renderSummary(agent, version, versionHome, options) {
|
|
115
281
|
const meta = readMeta();
|
|
@@ -184,17 +350,19 @@ async function renderSummary(agent, version, versionHome, options) {
|
|
|
184
350
|
// ─── List mode ───────────────────────────────────────────────────────────────
|
|
185
351
|
async function renderList(agent, version, versionHome, kind, options) {
|
|
186
352
|
const items = collectKind(agent, versionHome, kind);
|
|
353
|
+
renderItemList(`${agent}@${version}`, { agent, version }, kind, items, options);
|
|
354
|
+
}
|
|
355
|
+
function renderItemList(header, jsonHead, kind, items, options) {
|
|
187
356
|
if (options.json) {
|
|
188
357
|
console.log(JSON.stringify({
|
|
189
|
-
|
|
190
|
-
version,
|
|
358
|
+
...jsonHead,
|
|
191
359
|
kind,
|
|
192
360
|
count: items.length,
|
|
193
361
|
items: items.map(i => ({ name: i.name, source: i.source, path: i.path, description: i.description })),
|
|
194
362
|
}, null, 2));
|
|
195
363
|
return;
|
|
196
364
|
}
|
|
197
|
-
console.log('\n' + chalk.bold(
|
|
365
|
+
console.log('\n' + chalk.bold(header) + ' ' + chalk.gray(`${kind} (${items.length})`) + '\n');
|
|
198
366
|
if (items.length === 0) {
|
|
199
367
|
console.log(chalk.gray(` (none installed)`));
|
|
200
368
|
console.log('');
|
|
@@ -212,11 +380,14 @@ async function renderList(agent, version, versionHome, kind, options) {
|
|
|
212
380
|
// ─── Detail mode (fuzzy) ─────────────────────────────────────────────────────
|
|
213
381
|
async function renderDetail(agent, version, versionHome, kind, query, options) {
|
|
214
382
|
const items = collectKind(agent, versionHome, kind);
|
|
383
|
+
renderItemDetail(`${agent}@${version}`, { agent, version }, kind, query, items, options);
|
|
384
|
+
}
|
|
385
|
+
function renderItemDetail(header, jsonHead, kind, query, items, options) {
|
|
215
386
|
const matches = findMatches(items, query);
|
|
216
387
|
if (matches.length === 0) {
|
|
217
388
|
const suggestions = suggestClosest(items, query, 3);
|
|
218
389
|
if (options.json) {
|
|
219
|
-
console.log(JSON.stringify({
|
|
390
|
+
console.log(JSON.stringify({ ...jsonHead, kind, query, match: null, suggestions: suggestions.map(s => s.name) }, null, 2));
|
|
220
391
|
}
|
|
221
392
|
else {
|
|
222
393
|
console.error(chalk.red(`No ${kind} matching '${query}'.`));
|
|
@@ -231,8 +402,7 @@ async function renderDetail(agent, version, versionHome, kind, query, options) {
|
|
|
231
402
|
if (options.json) {
|
|
232
403
|
const detail = buildDetail(best.item, kind);
|
|
233
404
|
console.log(JSON.stringify({
|
|
234
|
-
|
|
235
|
-
version,
|
|
405
|
+
...jsonHead,
|
|
236
406
|
kind,
|
|
237
407
|
query,
|
|
238
408
|
match: { ...detail, matchKind: best.matchKind },
|
|
@@ -240,7 +410,7 @@ async function renderDetail(agent, version, versionHome, kind, query, options) {
|
|
|
240
410
|
}, null, 2));
|
|
241
411
|
return;
|
|
242
412
|
}
|
|
243
|
-
console.log('\n' + chalk.bold(
|
|
413
|
+
console.log('\n' + chalk.bold(header) + ' ' + chalk.gray(`${kind} matching "${query}"`) + '\n');
|
|
244
414
|
const matchTag = best.matchKind === 'exact' ? 'exact' : best.matchKind === 'substring' ? 'substring' : `~${best.distance}`;
|
|
245
415
|
console.log(` ${chalk.green('✓')} ${termLink(chalk.bold.cyan(best.item.name), best.item.linkTarget)} ${chalk.gray(`[${matchTag}, ${best.item.source}]`)}`);
|
|
246
416
|
if (best.item.description) {
|
|
@@ -15,6 +15,7 @@ import chalk from 'chalk';
|
|
|
15
15
|
import ora from 'ora';
|
|
16
16
|
import { SESSION_AGENTS } from '../lib/session/types.js';
|
|
17
17
|
import { discoverArtifacts, readArtifact, resolveArtifact } from '../lib/session/artifacts.js';
|
|
18
|
+
import { looksLikePath, toComparablePath, homeDir } from '../lib/platform/index.js';
|
|
18
19
|
import { getActiveSessions } from '../lib/session/active.js';
|
|
19
20
|
import { discoverSessions, countSessionsInScope, resolveSessionById, searchContentIndex } from '../lib/session/discover.js';
|
|
20
21
|
import { filterTeamSessions } from '../lib/session/team-filter.js';
|
|
@@ -69,15 +70,6 @@ function createScanProgressTracker(verbs, suffix, spinner) {
|
|
|
69
70
|
}
|
|
70
71
|
const PICKER_RECENT_COUNT = 15;
|
|
71
72
|
const PICKER_POOL_LIMIT = 200;
|
|
72
|
-
/**
|
|
73
|
-
* Detect whether a positional argument looks like a filesystem path.
|
|
74
|
-
* Naked paths (., ./, ../, /, ~) filter sessions by project directory.
|
|
75
|
-
* Everything else is treated as a search query string.
|
|
76
|
-
*/
|
|
77
|
-
function isPathLike(query) {
|
|
78
|
-
return query === '.' || query.startsWith('./') || query.startsWith('../')
|
|
79
|
-
|| query.startsWith('/') || query.startsWith('~');
|
|
80
|
-
}
|
|
81
73
|
/**
|
|
82
74
|
* Resolve a path-like query to an absolute directory path.
|
|
83
75
|
*/
|
|
@@ -153,8 +145,13 @@ function contextColor(context) {
|
|
|
153
145
|
function shortCwd(cwd) {
|
|
154
146
|
if (!cwd)
|
|
155
147
|
return '-';
|
|
156
|
-
const home =
|
|
157
|
-
|
|
148
|
+
const home = homeDir();
|
|
149
|
+
// Compare in normalized form so the `~` shorthand also lands on Windows
|
|
150
|
+
// (case-insensitive, backslash paths); on POSIX this is byte-identical to the
|
|
151
|
+
// previous `cwd.startsWith(home)`. The displayed tail keeps original casing.
|
|
152
|
+
return toComparablePath(cwd).startsWith(toComparablePath(home))
|
|
153
|
+
? '~' + cwd.slice(home.length)
|
|
154
|
+
: cwd;
|
|
158
155
|
}
|
|
159
156
|
function formatStartedAt(startedAtMs) {
|
|
160
157
|
if (!startedAtMs)
|
|
@@ -334,7 +331,7 @@ async function sessionsAction(query, options) {
|
|
|
334
331
|
// Path-like queries filter by project directory instead of text search.
|
|
335
332
|
let pathFilter;
|
|
336
333
|
let searchQuery;
|
|
337
|
-
if (query &&
|
|
334
|
+
if (query && looksLikePath(query)) {
|
|
338
335
|
const resolved = resolvePathFilter(query);
|
|
339
336
|
if (!fs.existsSync(resolved)) {
|
|
340
337
|
console.log(chalk.yellow(`Path not found: ${resolved}`));
|
package/dist/commands/setup.js
CHANGED
|
@@ -145,8 +145,8 @@ export async function runSetup(program, options = {}) {
|
|
|
145
145
|
if (!isShimsInPath()) {
|
|
146
146
|
const pathResult = addShimsToPath();
|
|
147
147
|
if (pathResult.success && !pathResult.alreadyPresent) {
|
|
148
|
-
console.log(chalk.green(`\nAdded shims to
|
|
149
|
-
console.log(chalk.gray(
|
|
148
|
+
console.log(chalk.green(`\nAdded shims to ${pathResult.location}`));
|
|
149
|
+
console.log(chalk.gray(pathResult.reloadHint));
|
|
150
150
|
}
|
|
151
151
|
else if (!pathResult.success) {
|
|
152
152
|
console.log(chalk.yellow('\nTo enable version switching, add shims to PATH:'));
|
|
@@ -402,8 +402,8 @@ export function registerVersionsCommands(program) {
|
|
|
402
402
|
if (!isShimsInPath()) {
|
|
403
403
|
const pathResult = addShimsToPath();
|
|
404
404
|
if (pathResult.success && !pathResult.alreadyPresent) {
|
|
405
|
-
console.log(chalk.green(` Added shims to
|
|
406
|
-
console.log(chalk.gray('
|
|
405
|
+
console.log(chalk.green(` Added shims to ${pathResult.location}`));
|
|
406
|
+
console.log(chalk.gray(' ' + pathResult.reloadHint));
|
|
407
407
|
}
|
|
408
408
|
else if (!pathResult.success) {
|
|
409
409
|
console.log(chalk.yellow('\nCould not auto-add shims to PATH:'));
|