@phnx-labs/agents-cli 1.20.7 → 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/dist/commands/computer-actions.d.ts +19 -0
- package/dist/commands/computer-actions.js +159 -1
- package/dist/commands/computer.js +2 -2
- package/dist/commands/setup.js +2 -2
- package/dist/commands/versions.js +2 -2
- package/dist/lib/computer-rpc.d.ts +2 -0
- package/dist/lib/computer-rpc.js +21 -1
- package/dist/lib/refresh.js +2 -2
- package/dist/lib/shims.d.ts +13 -8
- package/dist/lib/shims.js +46 -11
- package/package.json +1 -1
- package/scripts/postinstall.js +7 -7
|
@@ -31,6 +31,25 @@ export declare function buildElementOrCoords(opts: {
|
|
|
31
31
|
ok: false;
|
|
32
32
|
error: string;
|
|
33
33
|
};
|
|
34
|
+
export declare function buildRaiseParams(opts: {
|
|
35
|
+
windowId?: number;
|
|
36
|
+
title?: string;
|
|
37
|
+
}): Record<string, unknown>;
|
|
38
|
+
export declare function buildWaitParams(opts: {
|
|
39
|
+
duration?: number;
|
|
40
|
+
id?: string;
|
|
41
|
+
until?: string;
|
|
42
|
+
role?: string;
|
|
43
|
+
label?: string;
|
|
44
|
+
identifier?: string;
|
|
45
|
+
timeout?: number;
|
|
46
|
+
}): {
|
|
47
|
+
ok: true;
|
|
48
|
+
params: Record<string, unknown>;
|
|
49
|
+
} | {
|
|
50
|
+
ok: false;
|
|
51
|
+
error: string;
|
|
52
|
+
};
|
|
34
53
|
export declare function withClient<T>(fn: (client: ComputerClient) => Promise<T>): Promise<T>;
|
|
35
54
|
export declare function unwrap(r: RPCResponse): Record<string, unknown>;
|
|
36
55
|
export declare function registerActionCommands(program: Command): void;
|
|
@@ -60,6 +60,51 @@ export function buildElementOrCoords(opts) {
|
|
|
60
60
|
return { ok: true, params: { x: opts.x, y: opts.y } };
|
|
61
61
|
return { ok: false, error: 'pass --id <@eN> (from `describe`) or --x <n> --y <n>' };
|
|
62
62
|
}
|
|
63
|
+
// Build the focus_window params for `raise`. Pure + tested: window_id and
|
|
64
|
+
// title are both optional refinements over the app-level activate.
|
|
65
|
+
export function buildRaiseParams(opts) {
|
|
66
|
+
const params = {};
|
|
67
|
+
if (opts.windowId != null)
|
|
68
|
+
params.window_id = opts.windowId;
|
|
69
|
+
if (opts.title)
|
|
70
|
+
params.title = opts.title;
|
|
71
|
+
return params;
|
|
72
|
+
}
|
|
73
|
+
// Build the wait RPC params. Pure + tested. Three modes, mirroring the
|
|
74
|
+
// daemon's Wait.run: --duration (unconditional sleep), --id + --until
|
|
75
|
+
// (cached-element poll), or --role/--label/--identifier (live locator poll).
|
|
76
|
+
export function buildWaitParams(opts) {
|
|
77
|
+
if (opts.duration != null) {
|
|
78
|
+
return { ok: true, params: { duration_ms: opts.duration } };
|
|
79
|
+
}
|
|
80
|
+
const params = {};
|
|
81
|
+
if (opts.until)
|
|
82
|
+
params.until = opts.until;
|
|
83
|
+
if (opts.timeout != null)
|
|
84
|
+
params.timeout_ms = opts.timeout;
|
|
85
|
+
if (opts.id) {
|
|
86
|
+
return { ok: true, params: { ...params, element_id: opts.id } };
|
|
87
|
+
}
|
|
88
|
+
const locator = {};
|
|
89
|
+
if (opts.role)
|
|
90
|
+
locator.role = opts.role;
|
|
91
|
+
if (opts.label)
|
|
92
|
+
locator.label = opts.label;
|
|
93
|
+
if (opts.identifier)
|
|
94
|
+
locator.identifier = opts.identifier;
|
|
95
|
+
if (Object.keys(locator).length === 0) {
|
|
96
|
+
return { ok: false, error: 'pass --duration <ms>, --id <@eN>, or a locator (--role/--label/--identifier)' };
|
|
97
|
+
}
|
|
98
|
+
return { ok: true, params: { ...params, locator } };
|
|
99
|
+
}
|
|
100
|
+
// postToPid keyboard delivery is dropped by key-window-gated apps (Parallels
|
|
101
|
+
// VMs and friends) — when the daemon reports the target was not frontmost,
|
|
102
|
+
// the keystrokes may have landed nowhere. Surface that loudly on stderr.
|
|
103
|
+
function warnIfNotFrontmost(res) {
|
|
104
|
+
if (res.frontmost === false) {
|
|
105
|
+
console.error('warning: target was not the frontmost app — keystrokes may have been dropped. Run `agents computer raise` first.');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
63
108
|
function reportMissingHelper() {
|
|
64
109
|
console.error('helper not built. Run: ./packages/computer-helper/scripts/build.sh debug');
|
|
65
110
|
process.exit(1);
|
|
@@ -99,6 +144,12 @@ async function resolveTargetPid(client, opts) {
|
|
|
99
144
|
}
|
|
100
145
|
return picked.app.pid;
|
|
101
146
|
}
|
|
147
|
+
// --raise flag: app-level focus_window before the main action so coordinate
|
|
148
|
+
// clicks and keystrokes land on a visible, key window.
|
|
149
|
+
async function raiseIfRequested(client, pid, raise) {
|
|
150
|
+
if (raise)
|
|
151
|
+
unwrap(await client.call('focus_window', { pid }));
|
|
152
|
+
}
|
|
102
153
|
function emit(result, json, human) {
|
|
103
154
|
if (json) {
|
|
104
155
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -158,6 +209,7 @@ export function registerActionCommands(program) {
|
|
|
158
209
|
.description('Click an element (--id) or screen coordinate (--x --y)')
|
|
159
210
|
.option('--count <n>', 'Click count (2 = double-click)', (v) => parseInt(v, 10))
|
|
160
211
|
.option('--background', 'Focus-safe postToPid delivery (plain AppKit only; skips HID tap)')
|
|
212
|
+
.option('--raise', 'Bring the target app to the front first')
|
|
161
213
|
.option('--json', 'Emit JSON'))).action(async (opts) => {
|
|
162
214
|
await withClient(async (client) => {
|
|
163
215
|
const pid = await resolveTargetPid(client, opts);
|
|
@@ -166,6 +218,7 @@ export function registerActionCommands(program) {
|
|
|
166
218
|
console.error(spec.error);
|
|
167
219
|
process.exit(1);
|
|
168
220
|
}
|
|
221
|
+
await raiseIfRequested(client, pid, opts.raise);
|
|
169
222
|
const params = { pid, ...spec.params };
|
|
170
223
|
if (opts.count != null)
|
|
171
224
|
params.count = opts.count;
|
|
@@ -221,13 +274,19 @@ export function registerActionCommands(program) {
|
|
|
221
274
|
.description('Type an arbitrary unicode string into the focused field (focus first via click/focus)')
|
|
222
275
|
.requiredOption('--text <s>', 'Text to type')
|
|
223
276
|
.option('--commit', 'Press Return after typing')
|
|
277
|
+
.option('--raise', 'Bring the target app to the front first')
|
|
278
|
+
.option('--require-frontmost', 'Fail (not warn) if the target is not the frontmost app')
|
|
224
279
|
.option('--json', 'Emit JSON')).action(async (opts) => {
|
|
225
280
|
await withClient(async (client) => {
|
|
226
281
|
const pid = await resolveTargetPid(client, opts);
|
|
282
|
+
await raiseIfRequested(client, pid, opts.raise);
|
|
227
283
|
const params = { pid, text: opts.text };
|
|
228
284
|
if (opts.commit)
|
|
229
285
|
params.commit = true;
|
|
286
|
+
if (opts.requireFrontmost)
|
|
287
|
+
params.require_frontmost = true;
|
|
230
288
|
const res = unwrap(await client.call('type_text', params));
|
|
289
|
+
warnIfNotFrontmost(res);
|
|
231
290
|
emit(res, Boolean(opts.json), () => `typed ${res.chars ?? opts.text.length} char(s)`);
|
|
232
291
|
});
|
|
233
292
|
});
|
|
@@ -236,10 +295,17 @@ export function registerActionCommands(program) {
|
|
|
236
295
|
.command('key')
|
|
237
296
|
.description('Send a key chord, e.g. "cmd+shift+s", "enter", "esc"')
|
|
238
297
|
.requiredOption('--keys <chord>', 'Key chord')
|
|
298
|
+
.option('--raise', 'Bring the target app to the front first')
|
|
299
|
+
.option('--require-frontmost', 'Fail (not warn) if the target is not the frontmost app')
|
|
239
300
|
.option('--json', 'Emit JSON')).action(async (opts) => {
|
|
240
301
|
await withClient(async (client) => {
|
|
241
302
|
const pid = await resolveTargetPid(client, opts);
|
|
242
|
-
|
|
303
|
+
await raiseIfRequested(client, pid, opts.raise);
|
|
304
|
+
const params = { pid, keys: opts.keys };
|
|
305
|
+
if (opts.requireFrontmost)
|
|
306
|
+
params.require_frontmost = true;
|
|
307
|
+
const res = unwrap(await client.call('key', params));
|
|
308
|
+
warnIfNotFrontmost(res);
|
|
243
309
|
emit(res, Boolean(opts.json), () => `sent ${opts.keys}`);
|
|
244
310
|
});
|
|
245
311
|
});
|
|
@@ -251,6 +317,7 @@ export function registerActionCommands(program) {
|
|
|
251
317
|
.requiredOption('--to <x,y>', 'End coordinate "x,y"')
|
|
252
318
|
.option('--button <left|right>', 'Mouse button', 'left')
|
|
253
319
|
.option('--background', 'Focus-safe postToPid delivery (plain AppKit only)')
|
|
320
|
+
.option('--raise', 'Bring the target app to the front first')
|
|
254
321
|
.option('--json', 'Emit JSON')).action(async (opts) => {
|
|
255
322
|
let from;
|
|
256
323
|
let to;
|
|
@@ -264,6 +331,7 @@ export function registerActionCommands(program) {
|
|
|
264
331
|
}
|
|
265
332
|
await withClient(async (client) => {
|
|
266
333
|
const pid = await resolveTargetPid(client, opts);
|
|
334
|
+
await raiseIfRequested(client, pid, opts.raise);
|
|
267
335
|
const params = {
|
|
268
336
|
pid,
|
|
269
337
|
from: [from.x, from.y],
|
|
@@ -282,9 +350,11 @@ export function registerActionCommands(program) {
|
|
|
282
350
|
.description('Scroll by a pixel delta at an element or coordinate')
|
|
283
351
|
.option('--dy <n>', 'Vertical delta (negative = down)', (v) => parseInt(v, 10))
|
|
284
352
|
.option('--dx <n>', 'Horizontal delta', (v) => parseInt(v, 10))
|
|
353
|
+
.option('--raise', 'Bring the target app to the front first')
|
|
285
354
|
.option('--json', 'Emit JSON'))).action(async (opts) => {
|
|
286
355
|
await withClient(async (client) => {
|
|
287
356
|
const pid = await resolveTargetPid(client, opts);
|
|
357
|
+
await raiseIfRequested(client, pid, opts.raise);
|
|
288
358
|
const params = { pid };
|
|
289
359
|
if (opts.id)
|
|
290
360
|
params.element_id = opts.id;
|
|
@@ -325,4 +395,92 @@ export function registerActionCommands(program) {
|
|
|
325
395
|
emit(res, Boolean(opts.json), () => `focused ${opts.id}`);
|
|
326
396
|
});
|
|
327
397
|
});
|
|
398
|
+
// raise — bring an app (or one of its windows) to the front. The window
|
|
399
|
+
// forms (--window-id/--title) also switch macOS Spaces, which is the only
|
|
400
|
+
// way to reach a fullscreen-Space window (VM, fullscreen editor) for
|
|
401
|
+
// capture and HID-tap input.
|
|
402
|
+
addTargetOpts(program
|
|
403
|
+
.command('raise')
|
|
404
|
+
.description('Bring an app (or a specific window) to the front — switches Spaces for fullscreen windows')
|
|
405
|
+
.option('--window-id <n>', 'Raise a specific window by id (from `screenshot --list`)', (v) => parseInt(v, 10))
|
|
406
|
+
.option('--title <s>', 'Raise the window whose title contains this string')
|
|
407
|
+
.option('--json', 'Emit JSON')).action(async (opts) => {
|
|
408
|
+
await withClient(async (client) => {
|
|
409
|
+
const pid = await resolveTargetPid(client, opts);
|
|
410
|
+
const res = unwrap(await client.call('focus_window', { pid, ...buildRaiseParams(opts) }));
|
|
411
|
+
emit(res, Boolean(opts.json), () => {
|
|
412
|
+
const scope = res.raised_window ? `window ${res.title ?? res.window_id ?? ''}`.trim() : 'app';
|
|
413
|
+
return `raised ${scope} (${res.focus_elapsed_ms ?? 0}ms)`;
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
// wait — settle the UI before the next action
|
|
418
|
+
addTargetOpts(program
|
|
419
|
+
.command('wait')
|
|
420
|
+
.description('Wait for a duration (--duration) or for an element (--id / --role/--label) to satisfy --until')
|
|
421
|
+
.option('--duration <ms>', 'Unconditional sleep in ms (50-30000)', (v) => parseInt(v, 10))
|
|
422
|
+
.option('--id <@eN>', 'Element id from `describe` to poll')
|
|
423
|
+
.option('--until <cond>', 'Condition: exists | enabled | disappears (default: exists)')
|
|
424
|
+
.option('--role <s>', 'Locator: AX role (e.g. AXButton)')
|
|
425
|
+
.option('--label <s>', 'Locator: element label')
|
|
426
|
+
.option('--identifier <s>', 'Locator: AX identifier')
|
|
427
|
+
.option('--timeout <ms>', 'Poll timeout in ms (default 5000)', (v) => parseInt(v, 10))
|
|
428
|
+
.option('--json', 'Emit JSON')).action(async (opts) => {
|
|
429
|
+
const spec = buildWaitParams(opts);
|
|
430
|
+
if (!spec.ok) {
|
|
431
|
+
console.error(spec.error);
|
|
432
|
+
process.exit(1);
|
|
433
|
+
}
|
|
434
|
+
await withClient(async (client) => {
|
|
435
|
+
const params = { ...spec.params };
|
|
436
|
+
// duration-only waits don't need a target pid
|
|
437
|
+
if (params.duration_ms == null)
|
|
438
|
+
params.pid = await resolveTargetPid(client, opts);
|
|
439
|
+
const res = unwrap(await client.call('wait', params));
|
|
440
|
+
emit(res, Boolean(opts.json), () => res.satisfied ? `satisfied (${res.waited_ms}ms)` : `timed out (${res.waited_ms}ms)`);
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
// get-text — read text without OCR
|
|
444
|
+
addTargetOpts(program
|
|
445
|
+
.command('get-text')
|
|
446
|
+
.description('Extract visible text from the app (or a subtree via --id)')
|
|
447
|
+
.option('--id <@eN>', 'Element id from `describe` to scope the extraction')
|
|
448
|
+
.option('--max-chars <n>', 'Cap the extracted text length', (v) => parseInt(v, 10))
|
|
449
|
+
.option('--json', 'Emit JSON')).action(async (opts) => {
|
|
450
|
+
await withClient(async (client) => {
|
|
451
|
+
const pid = await resolveTargetPid(client, opts);
|
|
452
|
+
const params = { pid };
|
|
453
|
+
if (opts.id)
|
|
454
|
+
params.element_id = opts.id;
|
|
455
|
+
if (opts.maxChars != null)
|
|
456
|
+
params.max_chars = opts.maxChars;
|
|
457
|
+
const res = unwrap(await client.call('get_text', params));
|
|
458
|
+
emit(res, Boolean(opts.json), () => String(res.text ?? ''));
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
// launch — start an app (no target resolution: it isn't running yet)
|
|
462
|
+
program
|
|
463
|
+
.command('launch')
|
|
464
|
+
.description('Launch an app by bundle id, path, or name')
|
|
465
|
+
.option('--bundle <id>', 'Bundle id (e.g. com.apple.TextEdit)')
|
|
466
|
+
.option('--path <p>', 'Path to the .app bundle')
|
|
467
|
+
.option('--name <s>', 'App name (resolved via /Applications and LaunchServices)')
|
|
468
|
+
.option('--json', 'Emit JSON')
|
|
469
|
+
.action(async (opts) => {
|
|
470
|
+
if (!opts.bundle && !opts.path && !opts.name) {
|
|
471
|
+
console.error('pass one of --bundle, --path, --name');
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
await withClient(async (client) => {
|
|
475
|
+
const params = {};
|
|
476
|
+
if (opts.bundle)
|
|
477
|
+
params.bundle_id = opts.bundle;
|
|
478
|
+
if (opts.path)
|
|
479
|
+
params.path = opts.path;
|
|
480
|
+
if (opts.name)
|
|
481
|
+
params.name = opts.name;
|
|
482
|
+
const res = unwrap(await client.call('launch_app', params));
|
|
483
|
+
emit(res, Boolean(opts.json), () => `launched ${res.name} (pid ${res.pid})`);
|
|
484
|
+
});
|
|
485
|
+
});
|
|
328
486
|
}
|
|
@@ -9,8 +9,8 @@ import { registerActionCommands, withClient, unwrap, pickTarget } from './comput
|
|
|
9
9
|
const COMPUTER_HELP_GROUPS = [
|
|
10
10
|
{ title: 'Installation', names: ['setup'] },
|
|
11
11
|
{ title: 'Daemon lifecycle', names: ['start', 'stop', 'reload', 'status'] },
|
|
12
|
-
{ title: 'Observe', names: ['apps', 'describe', 'screenshot'] },
|
|
13
|
-
{ title: 'Interact', names: ['click', 'right-click', 'type', 'type-text', 'key', 'drag', 'scroll', 'ax-action', 'focus'] },
|
|
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'] },
|
|
14
14
|
];
|
|
15
15
|
export function registerComputerCommand(program) {
|
|
16
16
|
const computer = program
|
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:'));
|
|
@@ -21,6 +21,8 @@ export declare function writeComputerPeers(allowedExecPaths: string[]): void;
|
|
|
21
21
|
export declare function resolveHelperExec(): string | null;
|
|
22
22
|
export declare function resolveHelperApp(): string | null;
|
|
23
23
|
export declare function openComputerClient(): ComputerClient;
|
|
24
|
+
export declare const RPC_TIMEOUT_MS = 30000;
|
|
25
|
+
export declare function resolveRpcTimeoutMs(env: string | undefined): number;
|
|
24
26
|
export declare function describeTransport(): {
|
|
25
27
|
kind: 'socket' | 'stdio' | 'none';
|
|
26
28
|
path: string | null;
|
package/dist/lib/computer-rpc.js
CHANGED
|
@@ -200,6 +200,15 @@ export function openComputerClient() {
|
|
|
200
200
|
}
|
|
201
201
|
return new StdioClient(helperExec);
|
|
202
202
|
}
|
|
203
|
+
// Per-call RPC timeout. Without it a hung daemon (deadlocked connection
|
|
204
|
+
// queue, stopped process) hangs the CLI forever — the waiter map never
|
|
205
|
+
// settles. 30s clears every daemon-side ceiling (wait caps at 30s,
|
|
206
|
+
// launch_app at 10s, screenshot at 5s). Overridable for slower flows.
|
|
207
|
+
export const RPC_TIMEOUT_MS = 30_000;
|
|
208
|
+
export function resolveRpcTimeoutMs(env) {
|
|
209
|
+
const n = Number(env);
|
|
210
|
+
return Number.isFinite(n) && n > 0 ? n : RPC_TIMEOUT_MS;
|
|
211
|
+
}
|
|
203
212
|
// Shared waiter map + line parser. Both transports plug their reader into
|
|
204
213
|
// `handleChunk` and their writer into `send`.
|
|
205
214
|
class BaseClient {
|
|
@@ -241,8 +250,19 @@ class BaseClient {
|
|
|
241
250
|
}
|
|
242
251
|
const id = this.nextId++;
|
|
243
252
|
const payload = JSON.stringify({ id, method, params: params ?? {} }) + '\n';
|
|
253
|
+
const timeoutMs = resolveRpcTimeoutMs(process.env.COMPUTER_HELPER_RPC_TIMEOUT_MS);
|
|
244
254
|
return new Promise((resolve) => {
|
|
245
|
-
|
|
255
|
+
const timer = setTimeout(() => {
|
|
256
|
+
if (this.waiters.delete(id)) {
|
|
257
|
+
resolve({ id, error: { code: 'rpc_timeout', message: `helper did not respond within ${timeoutMs}ms` } });
|
|
258
|
+
}
|
|
259
|
+
}, timeoutMs);
|
|
260
|
+
// Resolve as an error (never reject) so callers flow through unwrap()
|
|
261
|
+
// uniformly, matching failPending's contract.
|
|
262
|
+
this.waiters.set(id, (r) => {
|
|
263
|
+
clearTimeout(timer);
|
|
264
|
+
resolve(r);
|
|
265
|
+
});
|
|
246
266
|
this.send(payload);
|
|
247
267
|
});
|
|
248
268
|
}
|
package/dist/lib/refresh.js
CHANGED
|
@@ -212,8 +212,8 @@ export async function refresh(options = {}) {
|
|
|
212
212
|
if (!isShimsInPath()) {
|
|
213
213
|
const pathResult = addShimsToPath();
|
|
214
214
|
if (pathResult.success && !pathResult.alreadyPresent) {
|
|
215
|
-
console.log(chalk.green(`\nAdded shims to
|
|
216
|
-
console.log(chalk.gray(
|
|
215
|
+
console.log(chalk.green(`\nAdded shims to ${pathResult.location}`));
|
|
216
|
+
console.log(chalk.gray(pathResult.reloadHint));
|
|
217
217
|
}
|
|
218
218
|
else if (!pathResult.success) {
|
|
219
219
|
console.log(chalk.yellow('\nCould not auto-add shims to PATH:'));
|
package/dist/lib/shims.d.ts
CHANGED
|
@@ -248,20 +248,25 @@ export declare function isShimsInPath(): boolean;
|
|
|
248
248
|
* Get shell configuration instructions for adding shims to PATH.
|
|
249
249
|
*/
|
|
250
250
|
export declare function getPathSetupInstructions(): string;
|
|
251
|
+
interface ShimPathResult {
|
|
252
|
+
success: boolean;
|
|
253
|
+
alreadyPresent?: boolean;
|
|
254
|
+
rcFile?: string;
|
|
255
|
+
/** Human label of where the entry landed, e.g. `~/.zshrc` or `your user PATH`. */
|
|
256
|
+
location?: string;
|
|
257
|
+
/** Per-platform "how to pick it up" hint, e.g. `source ~/.zshrc` / open a new terminal. */
|
|
258
|
+
reloadHint?: string;
|
|
259
|
+
error?: string;
|
|
260
|
+
}
|
|
251
261
|
/**
|
|
252
|
-
* Add shims directory to shell
|
|
253
|
-
*
|
|
262
|
+
* Add the shims directory to PATH: edits the shell rc file on POSIX, or registers
|
|
263
|
+
* it on the Windows User PATH (registry + WM_SETTINGCHANGE). Idempotent.
|
|
254
264
|
*/
|
|
255
265
|
export declare function addShimsToPath(overrides?: {
|
|
256
266
|
homeDir?: string;
|
|
257
267
|
shell?: string;
|
|
258
268
|
shimsDir?: string;
|
|
259
|
-
}):
|
|
260
|
-
success: boolean;
|
|
261
|
-
alreadyPresent?: boolean;
|
|
262
|
-
rcFile?: string;
|
|
263
|
-
error?: string;
|
|
264
|
-
};
|
|
269
|
+
}): ShimPathResult;
|
|
265
270
|
export declare function listAgentsWithInstalledVersions(): AgentId[];
|
|
266
271
|
/**
|
|
267
272
|
* Resource diff between two versions. Each field lists resources present in
|
package/dist/lib/shims.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import * as fs from 'fs';
|
|
12
12
|
import * as path from 'path';
|
|
13
13
|
import * as os from 'os';
|
|
14
|
+
import { execFileSync } from 'child_process';
|
|
14
15
|
import { fileURLToPath } from 'url';
|
|
15
16
|
import { confirm, select } from '@inquirer/prompts';
|
|
16
17
|
import { IS_WINDOWS } from './platform/index.js';
|
|
@@ -1520,18 +1521,15 @@ Then restart your shell or run:
|
|
|
1520
1521
|
source ~/${rcFile}`;
|
|
1521
1522
|
}
|
|
1522
1523
|
/**
|
|
1523
|
-
* Add shims directory to shell
|
|
1524
|
-
*
|
|
1524
|
+
* Add the shims directory to PATH: edits the shell rc file on POSIX, or registers
|
|
1525
|
+
* it on the Windows User PATH (registry + WM_SETTINGCHANGE). Idempotent.
|
|
1525
1526
|
*/
|
|
1526
1527
|
export function addShimsToPath(overrides) {
|
|
1527
|
-
// Windows has no shell rc file to edit
|
|
1528
|
-
//
|
|
1529
|
-
//
|
|
1530
|
-
// Report "already present" so callers don't emit a misleading "added to
|
|
1531
|
-
// ~/.bashrc / source ~/.bashrc" message. (The `shell` override is the test hook
|
|
1532
|
-
// for exercising the POSIX path, so it bypasses this short-circuit.)
|
|
1528
|
+
// Windows has no shell rc file to edit. Register the shims dir on the User PATH
|
|
1529
|
+
// via the platform-native mechanism instead. (The `shell` override is the test
|
|
1530
|
+
// hook for exercising the POSIX path, so it bypasses this branch.)
|
|
1533
1531
|
if (IS_WINDOWS && !overrides?.shell) {
|
|
1534
|
-
return
|
|
1532
|
+
return addShimsToWindowsUserPath(overrides?.shimsDir || getShimsDir());
|
|
1535
1533
|
}
|
|
1536
1534
|
const shimsDir = overrides?.shimsDir || getShimsDir();
|
|
1537
1535
|
const { rcFile, rcPath, shell } = getShellRcFile(overrides);
|
|
@@ -1565,16 +1563,53 @@ export function addShimsToPath(overrides) {
|
|
|
1565
1563
|
const separator = contentWithoutShimLines.length > 0 && !contentWithoutShimLines.endsWith('\n') ? '\n' : '';
|
|
1566
1564
|
let newContent = contentWithoutShimLines + separator + exportBlock;
|
|
1567
1565
|
newContent = newContent.replace(/\n{2,}$/g, '\n');
|
|
1566
|
+
const location = `~/${rcFile}`;
|
|
1567
|
+
const reloadHint = `Restart your shell or run: source ~/${rcFile}`;
|
|
1568
1568
|
if (newContent === content) {
|
|
1569
|
-
return { success: true, alreadyPresent: true, rcFile };
|
|
1569
|
+
return { success: true, alreadyPresent: true, rcFile, location, reloadHint };
|
|
1570
1570
|
}
|
|
1571
1571
|
fs.writeFileSync(rcPath, newContent, 'utf-8');
|
|
1572
|
-
return { success: true, rcFile };
|
|
1572
|
+
return { success: true, rcFile, location, reloadHint };
|
|
1573
1573
|
}
|
|
1574
1574
|
catch (err) {
|
|
1575
1575
|
return { success: false, error: `Could not write ${rcFile}: ${err.message}` };
|
|
1576
1576
|
}
|
|
1577
1577
|
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Register the shims dir on the Windows User PATH via the .NET environment API,
|
|
1580
|
+
* which writes the registry AND broadcasts WM_SETTINGCHANGE — the correct analog
|
|
1581
|
+
* of editing a shell rc file (no `setx` truncation, no manual step). Idempotent:
|
|
1582
|
+
* a no-op when the dir is already present. The shims dir is passed via an env var
|
|
1583
|
+
* so it is never interpolated into the PowerShell script text.
|
|
1584
|
+
*/
|
|
1585
|
+
function addShimsToWindowsUserPath(shimsDir) {
|
|
1586
|
+
const script = [
|
|
1587
|
+
'$d = $env:AGENTS_SHIMS_DIR',
|
|
1588
|
+
"$u = [Environment]::GetEnvironmentVariable('Path','User')",
|
|
1589
|
+
"if ($null -eq $u) { $u = '' }",
|
|
1590
|
+
"$parts = @($u -split ';' | Where-Object { $_ -ne '' })",
|
|
1591
|
+
"if ($parts -contains $d) { 'present' } else {",
|
|
1592
|
+
" [Environment]::SetEnvironmentVariable('Path', (($parts + $d) -join ';'), 'User')",
|
|
1593
|
+
" 'added'",
|
|
1594
|
+
'}',
|
|
1595
|
+
].join('\n');
|
|
1596
|
+
try {
|
|
1597
|
+
const out = execFileSync('powershell', ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
1598
|
+
encoding: 'utf-8',
|
|
1599
|
+
env: { ...process.env, AGENTS_SHIMS_DIR: shimsDir },
|
|
1600
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1601
|
+
}).trim();
|
|
1602
|
+
return {
|
|
1603
|
+
success: true,
|
|
1604
|
+
alreadyPresent: out.includes('present'),
|
|
1605
|
+
location: 'your user PATH',
|
|
1606
|
+
reloadHint: 'Open a new terminal for the change to take effect.',
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
catch (err) {
|
|
1610
|
+
return { success: false, error: `Could not update the Windows user PATH: ${err.message}` };
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1578
1613
|
export function listAgentsWithInstalledVersions() {
|
|
1579
1614
|
const versionsDir = getVersionsDir();
|
|
1580
1615
|
if (!fs.existsSync(versionsDir)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.8",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
package/scripts/postinstall.js
CHANGED
|
@@ -144,17 +144,17 @@ function isAlreadyConfigured(rcFile) {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
async function main() {
|
|
147
|
-
// Windows has no shell rc files to edit. Write the `.cmd` shorthands
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
147
|
+
// Windows has no shell rc files to edit. Write the `.cmd` shorthands here; the
|
|
148
|
+
// shims dir gets registered on the User PATH by `agents setup` (via the native
|
|
149
|
+
// registry API), so we point the user there instead of mutating PATH from an
|
|
150
|
+
// npm lifecycle script. The primary `agents` command is already on PATH via
|
|
151
|
+
// npm's global bin and works immediately.
|
|
151
152
|
if (process.platform === 'win32') {
|
|
152
153
|
console.log(`\nagents-cli installed.`);
|
|
153
154
|
const written = writeAliasShims();
|
|
154
155
|
console.log(` Installed shorthands: ${written.join(', ')}`);
|
|
155
|
-
console.log(`\
|
|
156
|
-
console.log(`
|
|
157
|
-
console.log(` PowerShell: setx PATH "$env:PATH;${SHIMS_DIR}" (then open a new terminal)`);
|
|
156
|
+
console.log(`\nNext: run agents setup — it finishes setup and adds the shims dir to your PATH`);
|
|
157
|
+
console.log(`(so the bare shorthands ${ALIASES.join(', ')} and versioned aliases work in a new terminal).`);
|
|
158
158
|
}
|
|
159
159
|
// Opt-in: AGENTS_INIT_SHELL=1 npm install -g @phnx-labs/agents-cli
|
|
160
160
|
else if (process.env.AGENTS_INIT_SHELL === '1') {
|