@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
package/dist/lib/pty-server.js
CHANGED
|
@@ -15,6 +15,7 @@ import * as crypto from 'crypto';
|
|
|
15
15
|
import { execFileSync } from 'child_process';
|
|
16
16
|
import { fileURLToPath } from 'url';
|
|
17
17
|
import { getPtyDir as getPtyDirRoot } from './state.js';
|
|
18
|
+
import { isAlive } from './platform/index.js';
|
|
18
19
|
/**
|
|
19
20
|
* Capture a stable identifier for a process at the moment it was started.
|
|
20
21
|
* Used to defeat PID reuse: a kill(pid, ...) is only safe when the process
|
|
@@ -52,6 +53,7 @@ export function captureProcessStartTime(pid) {
|
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
// --- Constants ---
|
|
56
|
+
const IS_WINDOWS = process.platform === 'win32';
|
|
55
57
|
const SENTINEL = '__AGENTS_PTY_DONE__';
|
|
56
58
|
const SOCKET_NAME = 'pty.sock';
|
|
57
59
|
const PID_FILE = 'pty.pid';
|
|
@@ -72,24 +74,81 @@ const PTY_ENV_ALLOWLIST = [
|
|
|
72
74
|
'EDITOR', 'VISUAL', 'PAGER', 'LESS',
|
|
73
75
|
'NO_COLOR', 'FORCE_COLOR',
|
|
74
76
|
];
|
|
77
|
+
/**
|
|
78
|
+
* Windows allowlist. cmd.exe / PowerShell refuse to start (or misbehave) without
|
|
79
|
+
* SystemRoot, ComSpec, PATHEXT and the USERPROFILE/APPDATA family, so a Unix-style
|
|
80
|
+
* allowlist would spawn a broken shell. PATH/TERM/color/NODE vars are shared with
|
|
81
|
+
* the Unix list; the rest are Windows-specific.
|
|
82
|
+
*/
|
|
83
|
+
const PTY_ENV_ALLOWLIST_WIN = [
|
|
84
|
+
'SystemRoot', 'SystemDrive', 'windir', 'ComSpec', 'PATH', 'PATHEXT',
|
|
85
|
+
'TEMP', 'TMP', 'USERPROFILE', 'HOMEDRIVE', 'HOMEPATH', 'HOME',
|
|
86
|
+
'APPDATA', 'LOCALAPPDATA', 'PROGRAMFILES', 'PROGRAMDATA',
|
|
87
|
+
'USERNAME', 'USERDOMAIN', 'COMPUTERNAME', 'OS',
|
|
88
|
+
'PROCESSOR_ARCHITECTURE', 'NUMBER_OF_PROCESSORS',
|
|
89
|
+
'TERM', 'COLORTERM', 'NO_COLOR', 'FORCE_COLOR',
|
|
90
|
+
'NODE_PATH', 'BUN_INSTALL',
|
|
91
|
+
];
|
|
75
92
|
function buildPtyEnv() {
|
|
76
93
|
const env = {};
|
|
77
|
-
|
|
94
|
+
const allowlist = IS_WINDOWS ? PTY_ENV_ALLOWLIST_WIN : PTY_ENV_ALLOWLIST;
|
|
95
|
+
for (const key of allowlist) {
|
|
78
96
|
const v = process.env[key];
|
|
79
97
|
if (v !== undefined)
|
|
80
98
|
env[key] = v;
|
|
81
99
|
}
|
|
82
100
|
return env;
|
|
83
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Wrap a user command so a `__SENTINEL__:<exit>` line is printed after it
|
|
104
|
+
* finishes — that line drives completion detection in the exec/read flow.
|
|
105
|
+
* The separator and exit-code variable are shell-family specific:
|
|
106
|
+
* POSIX sh/zsh/bash : `cmd; echo "S:$?"`
|
|
107
|
+
* PowerShell : `cmd; echo "S:$LASTEXITCODE"`
|
|
108
|
+
* cmd.exe : `cmd & echo S:%errorlevel%` (`&` always runs the echo)
|
|
109
|
+
* Only the completion marker matters; the numeric exit code is informational
|
|
110
|
+
* (the authoritative code comes from node-pty's onExit).
|
|
111
|
+
*/
|
|
112
|
+
export function buildSentinelCommand(shell, command) {
|
|
113
|
+
// Split on both separators: a Windows shell path (`C:\…\cmd.exe`) must be
|
|
114
|
+
// recognized even when this code runs under POSIX path.basename, which does
|
|
115
|
+
// not treat `\` as a separator.
|
|
116
|
+
const name = (shell.split(/[\\/]/).pop() || shell).toLowerCase();
|
|
117
|
+
if (name === 'cmd.exe' || name === 'cmd') {
|
|
118
|
+
return `${command} & echo ${SENTINEL}:%errorlevel%`;
|
|
119
|
+
}
|
|
120
|
+
if (name === 'powershell.exe' || name === 'powershell' || name === 'pwsh.exe' || name === 'pwsh') {
|
|
121
|
+
return `${command}; echo "${SENTINEL}:$LASTEXITCODE"`;
|
|
122
|
+
}
|
|
123
|
+
return `${command}; echo "${SENTINEL}:$?"`;
|
|
124
|
+
}
|
|
84
125
|
/** Get the PTY helper directory, creating it if needed. */
|
|
85
126
|
function getPtyDir() {
|
|
86
127
|
const dir = getPtyDirRoot();
|
|
87
128
|
fs.mkdirSync(dir, { recursive: true });
|
|
88
129
|
return dir;
|
|
89
130
|
}
|
|
90
|
-
/**
|
|
131
|
+
/**
|
|
132
|
+
* Resolve the IPC endpoint for a given platform + PTY scratch dir. Pure so both
|
|
133
|
+
* branches are testable without stubbing process.platform.
|
|
134
|
+
*
|
|
135
|
+
* Unix: an AF_UNIX socket file inside the scratch dir.
|
|
136
|
+
* Windows: a named pipe (`\\.\pipe\…`). Named pipes are NOT filesystem objects,
|
|
137
|
+
* so the name is derived from a hash of the (per-user) scratch dir to keep it
|
|
138
|
+
* stable across invocations and isolated per user — and callers must never probe
|
|
139
|
+
* it with fs.existsSync (it always reports false). Both forms are accepted by
|
|
140
|
+
* net.createServer/createConnection.
|
|
141
|
+
*/
|
|
142
|
+
export function derivePtyEndpoint(platform, ptyDir) {
|
|
143
|
+
if (platform === 'win32') {
|
|
144
|
+
const hash = crypto.createHash('sha1').update(ptyDir).digest('hex').slice(0, 16);
|
|
145
|
+
return `\\\\.\\pipe\\agents-pty-${hash}`;
|
|
146
|
+
}
|
|
147
|
+
return path.join(ptyDir, SOCKET_NAME);
|
|
148
|
+
}
|
|
149
|
+
/** Get the IPC endpoint the PTY server listens on / clients connect to. */
|
|
91
150
|
export function getSocketPath() {
|
|
92
|
-
return
|
|
151
|
+
return derivePtyEndpoint(process.platform, getPtyDir());
|
|
93
152
|
}
|
|
94
153
|
/** Get the path to the PTY server PID file. */
|
|
95
154
|
export function getPtyPidPath() {
|
|
@@ -109,16 +168,17 @@ export function isPtyServerRunning() {
|
|
|
109
168
|
const pid = parseInt(fs.readFileSync(pidPath, 'utf-8').trim(), 10);
|
|
110
169
|
if (isNaN(pid))
|
|
111
170
|
return false;
|
|
112
|
-
|
|
113
|
-
|
|
171
|
+
if (isAlive(pid))
|
|
172
|
+
return true;
|
|
114
173
|
}
|
|
115
174
|
catch {
|
|
116
|
-
|
|
117
|
-
fs.unlinkSync(pidPath);
|
|
118
|
-
}
|
|
119
|
-
catch { }
|
|
120
|
-
return false;
|
|
175
|
+
// read failed — fall through and treat the pid file as stale
|
|
121
176
|
}
|
|
177
|
+
try {
|
|
178
|
+
fs.unlinkSync(pidPath);
|
|
179
|
+
}
|
|
180
|
+
catch { }
|
|
181
|
+
return false;
|
|
122
182
|
}
|
|
123
183
|
// --- Logging ---
|
|
124
184
|
function rotateLogsIfNeeded(logPath) {
|
|
@@ -221,8 +281,10 @@ export async function runPtyServer() {
|
|
|
221
281
|
fs.unlinkSync(pidPath);
|
|
222
282
|
}
|
|
223
283
|
catch { } });
|
|
224
|
-
// Remove stale socket from a prior crashed server. Safe now that we hold the PID
|
|
225
|
-
|
|
284
|
+
// Remove stale socket from a prior crashed server. Safe now that we hold the PID
|
|
285
|
+
// slot. Windows named pipes are not filesystem inodes — they vanish with their
|
|
286
|
+
// owning process, so there's nothing to unlink (and existsSync always reports false).
|
|
287
|
+
if (!IS_WINDOWS && fs.existsSync(socketPath)) {
|
|
226
288
|
try {
|
|
227
289
|
fs.unlinkSync(socketPath);
|
|
228
290
|
}
|
|
@@ -286,8 +348,10 @@ export async function runPtyServer() {
|
|
|
286
348
|
case 'start': {
|
|
287
349
|
const rows = req.params?.rows || 24;
|
|
288
350
|
const cols = req.params?.cols || 120;
|
|
289
|
-
const shell = req.params?.shell
|
|
290
|
-
|
|
351
|
+
const shell = req.params?.shell
|
|
352
|
+
|| (IS_WINDOWS ? (process.env.ComSpec || 'powershell.exe') : (process.env.SHELL || 'zsh'));
|
|
353
|
+
const cwd = req.params?.cwd
|
|
354
|
+
|| (IS_WINDOWS ? (process.env.USERPROFILE || process.env.HOME || process.cwd()) : (process.env.HOME || '/'));
|
|
291
355
|
const id = generateId();
|
|
292
356
|
let ptyProcess;
|
|
293
357
|
try {
|
|
@@ -358,7 +422,9 @@ export async function runPtyServer() {
|
|
|
358
422
|
session.appActive = true;
|
|
359
423
|
session.activeCommand = command;
|
|
360
424
|
session.pendingOutput = '';
|
|
361
|
-
|
|
425
|
+
// Windows conpty submits on CR; POSIX line discipline expects LF.
|
|
426
|
+
const submit = IS_WINDOWS ? '\r' : '\n';
|
|
427
|
+
session.pty.write(`${buildSentinelCommand(session.shell, command)}${submit}`);
|
|
362
428
|
session.lastActivity = Date.now();
|
|
363
429
|
return { ok: true, submitted: true };
|
|
364
430
|
}
|
|
@@ -525,20 +591,28 @@ export async function runPtyServer() {
|
|
|
525
591
|
// any local user with execute on the parent dir could connect to the socket
|
|
526
592
|
// during the listen()-to-chmod() window. macOS BSD AF_UNIX semantics make
|
|
527
593
|
// socket mode advisory only, so the parent dir is the real boundary.
|
|
594
|
+
//
|
|
595
|
+
// On Windows the transport is a named pipe, not a filesystem inode: chmod/umask
|
|
596
|
+
// are no-ops (and umask throws in some Node builds), and pipe ACLs default to
|
|
597
|
+
// the creating user. So we skip the Unix hardening entirely there.
|
|
528
598
|
const agentsDir = getPtyDirRoot();
|
|
529
599
|
fs.mkdirSync(agentsDir, { recursive: true });
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
600
|
+
if (!IS_WINDOWS) {
|
|
601
|
+
fs.chmodSync(agentsDir, 0o700);
|
|
602
|
+
// umask covers any inherited group/other bits while listen() is creating
|
|
603
|
+
// the socket inode — it only matters for the unobservable instant before
|
|
604
|
+
// we can chmod the inode itself.
|
|
605
|
+
process.umask(0o077);
|
|
606
|
+
}
|
|
535
607
|
await new Promise((resolve) => {
|
|
536
608
|
server.listen(socketPath, () => resolve());
|
|
537
609
|
});
|
|
538
610
|
// Surface chmod failures: a 0o600 socket is a load-bearing security
|
|
539
611
|
// assumption, not a nice-to-have. If we can't lock it down, refuse to
|
|
540
|
-
// start so the caller learns immediately.
|
|
541
|
-
|
|
612
|
+
// start so the caller learns immediately. (No-op on Windows named pipes.)
|
|
613
|
+
if (!IS_WINDOWS) {
|
|
614
|
+
fs.chmodSync(socketPath, 0o600);
|
|
615
|
+
}
|
|
542
616
|
log('INFO', `PTY server started (PID: ${process.pid}, socket: ${socketPath})`);
|
|
543
617
|
// Shutdown handler
|
|
544
618
|
function shutdown() {
|
|
@@ -549,10 +623,13 @@ export async function runPtyServer() {
|
|
|
549
623
|
sessions.clear();
|
|
550
624
|
clearInterval(cleanupInterval);
|
|
551
625
|
server.close();
|
|
552
|
-
|
|
553
|
-
|
|
626
|
+
// Named pipes are reclaimed by the OS on close; only Unix sockets leave a file.
|
|
627
|
+
if (!IS_WINDOWS) {
|
|
628
|
+
try {
|
|
629
|
+
fs.unlinkSync(socketPath);
|
|
630
|
+
}
|
|
631
|
+
catch { }
|
|
554
632
|
}
|
|
555
|
-
catch { }
|
|
556
633
|
try {
|
|
557
634
|
fs.unlinkSync(getPtyPidPath());
|
|
558
635
|
}
|
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:'));
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import * as fs from 'fs';
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import { parseSession } from './parse.js';
|
|
11
|
+
import { toComparablePath } from '../platform/index.js';
|
|
11
12
|
/** Tool names that produce file artifacts (writes, edits, patches). */
|
|
12
13
|
const WRITE_TOOLS = new Set([
|
|
13
14
|
'Write', 'Edit', 'write_file', 'edit_file', 'create_file', 'replace', 'patch',
|
|
@@ -87,8 +88,13 @@ export function resolveArtifact(artifacts, name) {
|
|
|
87
88
|
return byBase[0];
|
|
88
89
|
if (byBase.length > 1)
|
|
89
90
|
return byBase[0];
|
|
90
|
-
// Path suffix match (e.g. "src/foo.ts")
|
|
91
|
-
|
|
91
|
+
// Path suffix match (e.g. "src/foo.ts"). Normalize so Windows `\` paths match
|
|
92
|
+
// a forward-slash query too; on POSIX this is identical to the old comparison.
|
|
93
|
+
const target = toComparablePath(name);
|
|
94
|
+
const bySuffix = artifacts.filter(a => {
|
|
95
|
+
const ap = toComparablePath(a.path);
|
|
96
|
+
return ap.endsWith('/' + target) || ap === target;
|
|
97
|
+
});
|
|
92
98
|
if (bySuffix.length >= 1)
|
|
93
99
|
return bySuffix[0];
|
|
94
100
|
return null;
|
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,8 +11,10 @@
|
|
|
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';
|
|
17
|
+
import { IS_WINDOWS } from './platform/index.js';
|
|
16
18
|
import { getShimsDir, getVersionsDir, getBackupsDir, ensureAgentsDir } from './state.js';
|
|
17
19
|
export { getShimsDir };
|
|
18
20
|
import { AGENTS } from './agents.js';
|
|
@@ -521,8 +523,28 @@ export function createShim(agent) {
|
|
|
521
523
|
const shimPath = path.join(shimsDir, agentConfig.cliCommand);
|
|
522
524
|
const script = generateShimScript(agent);
|
|
523
525
|
fs.writeFileSync(shimPath, script, { mode: 0o755 });
|
|
526
|
+
// Windows can't execute the bash shim directly. Drop a `.cmd` companion next
|
|
527
|
+
// to it that delegates to the node-side transparent resolver (`agents __shim`),
|
|
528
|
+
// so the version resolution stays single-sourced instead of reimplemented in batch.
|
|
529
|
+
if (IS_WINDOWS) {
|
|
530
|
+
writeWindowsCmdShim(shimPath + '.cmd', agentConfig.cliCommand);
|
|
531
|
+
}
|
|
524
532
|
return shimPath;
|
|
525
533
|
}
|
|
534
|
+
/**
|
|
535
|
+
* Generate a Windows `.cmd` launcher that delegates to `agents __shim <spec>`.
|
|
536
|
+
* `spec` is the agent's cliCommand for the default-version shim, or
|
|
537
|
+
* `cliCommand@version` for a versioned alias. node + the dist entrypoint are
|
|
538
|
+
* resolved at generation time so the launcher does not depend on `agents`
|
|
539
|
+
* already being on PATH.
|
|
540
|
+
*/
|
|
541
|
+
function writeWindowsCmdShim(cmdPath, spec) {
|
|
542
|
+
const indexJs = getAgentsBinForGeneratedShim();
|
|
543
|
+
const content = `@echo off\r\n` +
|
|
544
|
+
`rem Auto-generated by agents-cli - do not edit\r\n` +
|
|
545
|
+
`node "${indexJs}" __shim ${spec} %*\r\n`;
|
|
546
|
+
fs.writeFileSync(cmdPath, content);
|
|
547
|
+
}
|
|
526
548
|
/**
|
|
527
549
|
* Remove the shim for an agent.
|
|
528
550
|
*/
|
|
@@ -532,6 +554,12 @@ export function removeShim(agent) {
|
|
|
532
554
|
const shimPath = path.join(shimsDir, agentConfig.cliCommand);
|
|
533
555
|
if (fs.existsSync(shimPath)) {
|
|
534
556
|
fs.unlinkSync(shimPath);
|
|
557
|
+
if (IS_WINDOWS) {
|
|
558
|
+
try {
|
|
559
|
+
fs.unlinkSync(shimPath + '.cmd');
|
|
560
|
+
}
|
|
561
|
+
catch { }
|
|
562
|
+
}
|
|
535
563
|
return true;
|
|
536
564
|
}
|
|
537
565
|
return false;
|
|
@@ -682,6 +710,9 @@ export function createVersionedAlias(agent, version) {
|
|
|
682
710
|
const aliasPath = path.join(shimsDir, `${agentConfig.cliCommand}@${version}`);
|
|
683
711
|
const script = generateVersionedAliasScript(agent, version);
|
|
684
712
|
fs.writeFileSync(aliasPath, script, { mode: 0o755 });
|
|
713
|
+
if (IS_WINDOWS) {
|
|
714
|
+
writeWindowsCmdShim(aliasPath + '.cmd', `${agentConfig.cliCommand}@${version}`);
|
|
715
|
+
}
|
|
685
716
|
return aliasPath;
|
|
686
717
|
}
|
|
687
718
|
/**
|
|
@@ -693,6 +724,12 @@ export function removeVersionedAlias(agent, version) {
|
|
|
693
724
|
const aliasPath = path.join(shimsDir, `${agentConfig.cliCommand}@${version}`);
|
|
694
725
|
if (fs.existsSync(aliasPath)) {
|
|
695
726
|
fs.unlinkSync(aliasPath);
|
|
727
|
+
if (IS_WINDOWS) {
|
|
728
|
+
try {
|
|
729
|
+
fs.unlinkSync(aliasPath + '.cmd');
|
|
730
|
+
}
|
|
731
|
+
catch { }
|
|
732
|
+
}
|
|
696
733
|
return true;
|
|
697
734
|
}
|
|
698
735
|
return false;
|
|
@@ -1484,10 +1521,16 @@ Then restart your shell or run:
|
|
|
1484
1521
|
source ~/${rcFile}`;
|
|
1485
1522
|
}
|
|
1486
1523
|
/**
|
|
1487
|
-
* Add shims directory to shell
|
|
1488
|
-
*
|
|
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.
|
|
1489
1526
|
*/
|
|
1490
1527
|
export function addShimsToPath(overrides) {
|
|
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.)
|
|
1531
|
+
if (IS_WINDOWS && !overrides?.shell) {
|
|
1532
|
+
return addShimsToWindowsUserPath(overrides?.shimsDir || getShimsDir());
|
|
1533
|
+
}
|
|
1491
1534
|
const shimsDir = overrides?.shimsDir || getShimsDir();
|
|
1492
1535
|
const { rcFile, rcPath, shell } = getShellRcFile(overrides);
|
|
1493
1536
|
// Read current rc file content
|
|
@@ -1520,16 +1563,53 @@ export function addShimsToPath(overrides) {
|
|
|
1520
1563
|
const separator = contentWithoutShimLines.length > 0 && !contentWithoutShimLines.endsWith('\n') ? '\n' : '';
|
|
1521
1564
|
let newContent = contentWithoutShimLines + separator + exportBlock;
|
|
1522
1565
|
newContent = newContent.replace(/\n{2,}$/g, '\n');
|
|
1566
|
+
const location = `~/${rcFile}`;
|
|
1567
|
+
const reloadHint = `Restart your shell or run: source ~/${rcFile}`;
|
|
1523
1568
|
if (newContent === content) {
|
|
1524
|
-
return { success: true, alreadyPresent: true, rcFile };
|
|
1569
|
+
return { success: true, alreadyPresent: true, rcFile, location, reloadHint };
|
|
1525
1570
|
}
|
|
1526
1571
|
fs.writeFileSync(rcPath, newContent, 'utf-8');
|
|
1527
|
-
return { success: true, rcFile };
|
|
1572
|
+
return { success: true, rcFile, location, reloadHint };
|
|
1528
1573
|
}
|
|
1529
1574
|
catch (err) {
|
|
1530
1575
|
return { success: false, error: `Could not write ${rcFile}: ${err.message}` };
|
|
1531
1576
|
}
|
|
1532
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
|
+
}
|
|
1533
1613
|
export function listAgentsWithInstalledVersions() {
|
|
1534
1614
|
const versionsDir = getVersionsDir();
|
|
1535
1615
|
if (!fs.existsSync(versionsDir)) {
|
package/dist/lib/teams/agents.js
CHANGED
|
@@ -14,6 +14,7 @@ import * as path from 'path';
|
|
|
14
14
|
import * as os from 'os';
|
|
15
15
|
import { randomUUID } from 'crypto';
|
|
16
16
|
import { resolveAgentsDir } from './persistence.js';
|
|
17
|
+
import { findExecutable } from '../platform/index.js';
|
|
17
18
|
import { normalizeEvents } from './parsers.js';
|
|
18
19
|
import { debug } from './debug.js';
|
|
19
20
|
import { setGeminiAutoUpdateDisabled, updateGeminiSettings } from '../gemini-settings.js';
|
|
@@ -300,13 +301,10 @@ export function checkCliAvailable(agentType) {
|
|
|
300
301
|
if (!executable) {
|
|
301
302
|
return [false, `Unknown agent type: ${agentType}`];
|
|
302
303
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
catch {
|
|
308
|
-
return [false, `CLI tool '${executable}' not found in PATH. Install it first.`];
|
|
309
|
-
}
|
|
304
|
+
const resolved = findExecutable(executable);
|
|
305
|
+
return resolved
|
|
306
|
+
? [true, resolved]
|
|
307
|
+
: [false, `CLI tool '${executable}' not found in PATH. Install it first.`];
|
|
310
308
|
}
|
|
311
309
|
/** Check availability of all known agent CLIs. Returns a map of agent type to install status. */
|
|
312
310
|
export function checkAllClis() {
|
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
|
@@ -95,6 +95,11 @@ function writeAliasShims() {
|
|
|
95
95
|
const target = path.join(SHIMS_DIR, name);
|
|
96
96
|
const script = `#!/bin/sh\nAGENTS_BIN=${shellQuote(AGENTS_BIN)}\nif [ -z "$AGENTS_BIN" ] || [ ! -x "$AGENTS_BIN" ]; then\n echo "agents: agents-cli entrypoint missing or not executable: $AGENTS_BIN" >&2\n exit 127\nfi\nexec "$AGENTS_BIN" ${name} "$@"\n`;
|
|
97
97
|
fs.writeFileSync(target, script, { mode: 0o755 });
|
|
98
|
+
// Windows can't run the POSIX shim; drop a `.cmd` companion that invokes the
|
|
99
|
+
// entrypoint via node so the bare shorthand works in a Windows shell.
|
|
100
|
+
if (process.platform === 'win32') {
|
|
101
|
+
fs.writeFileSync(target + '.cmd', `@echo off\r\nnode "${AGENTS_BIN}" ${name} %*\r\n`);
|
|
102
|
+
}
|
|
98
103
|
written.push(name);
|
|
99
104
|
}
|
|
100
105
|
return written;
|
|
@@ -139,8 +144,20 @@ function isAlreadyConfigured(rcFile) {
|
|
|
139
144
|
}
|
|
140
145
|
|
|
141
146
|
async function main() {
|
|
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.
|
|
152
|
+
if (process.platform === 'win32') {
|
|
153
|
+
console.log(`\nagents-cli installed.`);
|
|
154
|
+
const written = writeAliasShims();
|
|
155
|
+
console.log(` Installed shorthands: ${written.join(', ')}`);
|
|
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
|
+
}
|
|
142
159
|
// Opt-in: AGENTS_INIT_SHELL=1 npm install -g @phnx-labs/agents-cli
|
|
143
|
-
if (process.env.AGENTS_INIT_SHELL === '1') {
|
|
160
|
+
else if (process.env.AGENTS_INIT_SHELL === '1') {
|
|
144
161
|
const rcFile = getShellRc();
|
|
145
162
|
if (!isAlreadyConfigured(rcFile)) {
|
|
146
163
|
const addition = `\n# agents-cli: version switching for AI coding agents\n${exportLine}\n`;
|