@phnx-labs/agents-cli 1.22.114 → 1.22.116
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 +245 -0
- package/README.md +22 -102
- package/dist/browser.js +0 -0
- package/dist/cli/command-registry.js +0 -7
- package/dist/commands/accounts.d.ts +3 -77
- package/dist/commands/accounts.js +13 -366
- package/dist/commands/audit.js +1 -1
- package/dist/commands/auth.js +0 -2
- package/dist/commands/browser.d.ts +53 -0
- package/dist/commands/browser.js +210 -9
- package/dist/commands/doctor.d.ts +2 -3
- package/dist/commands/doctor.js +0 -11
- package/dist/commands/events.d.ts +1 -3
- package/dist/commands/events.js +4 -7
- package/dist/commands/exec.d.ts +0 -1
- package/dist/commands/exec.js +214 -148
- package/dist/commands/feed-watch.js +52 -8
- package/dist/commands/feed.js +65 -14
- package/dist/commands/focus.js +1 -1
- package/dist/commands/go.d.ts +0 -17
- package/dist/commands/go.js +2 -19
- package/dist/commands/logs.js +1 -1
- package/dist/commands/mcp.js +8 -83
- package/dist/commands/memory.js +4 -47
- package/dist/commands/message.js +4 -4
- package/dist/commands/plugins.js +1 -93
- package/dist/commands/repo.js +0 -44
- package/dist/commands/resume.js +4 -1
- package/dist/commands/secrets-passthrough.js +2 -2
- package/dist/commands/send.d.ts +4 -4
- package/dist/commands/send.js +6 -51
- package/dist/commands/sessions-backup-setup.js +1 -1
- package/dist/commands/sessions-picker.d.ts +11 -0
- package/dist/commands/sessions-picker.js +88 -7
- package/dist/commands/sessions-resume.js +0 -1
- package/dist/commands/sessions-share.d.ts +5 -7
- package/dist/commands/sessions-share.js +98 -49
- package/dist/commands/sessions.d.ts +21 -2
- package/dist/commands/sessions.js +158 -11
- package/dist/commands/setup-browser.js +18 -2
- package/dist/commands/setup-computer.js +20 -6
- package/dist/commands/setup-secrets.d.ts +2 -2
- package/dist/commands/setup-secrets.js +24 -4
- package/dist/commands/setup-term.d.ts +24 -0
- package/dist/commands/setup-term.js +70 -0
- package/dist/commands/setup-terminal.d.ts +3 -0
- package/dist/commands/setup-terminal.js +22 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +29 -11
- package/dist/commands/skills.js +0 -8
- package/dist/commands/ssh.d.ts +6 -0
- package/dist/commands/ssh.js +93 -302
- package/dist/commands/sync.js +14 -5
- package/dist/commands/teams.js +1 -1
- package/dist/commands/traces.js +1 -1
- package/dist/lib/accounting/rotate.d.ts +63 -1
- package/dist/lib/accounting/rotate.js +56 -0
- package/dist/lib/accounts/add.d.ts +0 -5
- package/dist/lib/accounts/add.js +3 -9
- package/dist/lib/accounts/slots.js +32 -2
- package/dist/lib/answer-router.d.ts +11 -2
- package/dist/lib/answer-router.js +26 -2
- package/dist/lib/artifacts-client.d.ts +20 -0
- package/dist/lib/artifacts-client.js +46 -0
- package/dist/lib/auth-mint.d.ts +5 -4
- package/dist/lib/auth-mint.js +6 -5
- package/dist/lib/browser/drivers/arc.d.ts +1 -1
- package/dist/lib/browser/runtime-state.d.ts +55 -0
- package/dist/lib/browser/runtime-state.js +99 -18
- package/dist/lib/browser/service.d.ts +10 -0
- package/dist/lib/browser/service.js +229 -37
- package/dist/lib/browser/types.d.ts +18 -0
- package/dist/lib/cli-resources.js +3 -1
- package/dist/lib/cloud/dispatch.js +1 -1
- package/dist/lib/cloudflare/creds.d.ts +10 -0
- package/dist/lib/cloudflare/creds.js +46 -0
- package/dist/lib/cloudflare/provision.d.ts +35 -0
- package/dist/lib/cloudflare/provision.js +144 -0
- package/dist/lib/computer/sessions-list.d.ts +55 -0
- package/dist/lib/computer/sessions-list.js +168 -1
- package/dist/lib/config-keys.d.ts +1 -1
- package/dist/lib/config-keys.js +5 -0
- package/dist/lib/daemon/daemon.js +8 -1
- package/dist/lib/daemon/feed-stream-service.d.ts +23 -0
- package/dist/lib/daemon/feed-stream-service.js +40 -0
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/device-config.js +61 -0
- package/dist/lib/devices/connect.d.ts +49 -5
- package/dist/lib/devices/connect.js +169 -21
- package/dist/lib/devices/doctor-findings.js +2 -6
- package/dist/lib/feed/answer.d.ts +153 -4
- package/dist/lib/feed/answer.js +716 -105
- package/dist/lib/feed/envelope.d.ts +79 -0
- package/dist/lib/feed/envelope.js +23 -0
- package/dist/lib/feed/events.d.ts +6 -0
- package/dist/lib/feed/events.js +8 -0
- package/dist/lib/feed/feed.d.ts +61 -1
- package/dist/lib/feed/feed.js +226 -14
- package/dist/lib/feed/hub-server.d.ts +141 -0
- package/dist/lib/feed/hub-server.js +586 -0
- package/dist/lib/feed/hub.d.ts +95 -0
- package/dist/lib/feed/hub.js +255 -0
- package/dist/lib/feed/pr-status.d.ts +8 -0
- package/dist/lib/feed/pr-status.js +9 -1
- package/dist/lib/feed/tool-activity.d.ts +108 -0
- package/dist/lib/feed/tool-activity.js +313 -0
- package/dist/lib/feed/tools.d.ts +198 -0
- package/dist/lib/feed/tools.js +265 -0
- package/dist/lib/feed/watch.d.ts +50 -50
- package/dist/lib/feed/watch.js +147 -16
- package/dist/lib/feed-outcome.d.ts +1 -1
- package/dist/lib/feed-outcome.js +9 -2
- package/dist/lib/feed-policy.js +9 -3
- package/dist/lib/fleet/auth-sync.d.ts +2 -55
- package/dist/lib/fleet/auth-sync.js +2 -89
- package/dist/lib/format.d.ts +1 -1
- package/dist/lib/format.js +1 -1
- package/dist/lib/git.d.ts +0 -16
- package/dist/lib/git.js +0 -58
- package/dist/lib/harness-auth-capabilities.js +7 -2
- package/dist/lib/helper-versions.js +1 -1
- package/dist/lib/hosts/dispatch.d.ts +20 -1
- package/dist/lib/hosts/dispatch.js +52 -30
- package/dist/lib/hosts/remote-cmd.d.ts +72 -1
- package/dist/lib/hosts/remote-cmd.js +151 -10
- package/dist/lib/hosts/remote-cmd.test-fixture.d.ts +2 -0
- package/dist/lib/hosts/remote-cmd.test-fixture.js +22 -0
- package/dist/lib/mailbox.d.ts +12 -0
- package/dist/lib/mailbox.js +16 -2
- package/dist/lib/mcp.js +17 -11
- package/dist/lib/menubar/snapshot.d.ts +51 -0
- package/dist/lib/menubar/snapshot.js +42 -3
- package/dist/lib/open-url.js +2 -2
- package/dist/lib/probe.d.ts +4 -1
- package/dist/lib/probe.js +5 -2
- package/dist/lib/projects.d.ts +23 -0
- package/dist/lib/projects.js +78 -0
- package/dist/lib/pwsh.d.ts +33 -0
- package/dist/lib/pwsh.js +56 -0
- package/dist/lib/redact.d.ts +8 -0
- package/dist/lib/redact.js +11 -0
- package/dist/lib/refresh.d.ts +6 -2
- package/dist/lib/refresh.js +92 -72
- package/dist/lib/secrets-cli.d.ts +11 -0
- package/dist/lib/secrets-cli.js +30 -0
- package/dist/lib/secrets-client.js +3 -2
- package/dist/lib/session/active.d.ts +1 -0
- package/dist/lib/session/active.js +8 -0
- package/dist/lib/session/db.d.ts +67 -3
- package/dist/lib/session/db.js +381 -126
- package/dist/lib/session/detached.d.ts +7 -0
- package/dist/lib/session/detached.js +29 -0
- package/dist/lib/session/prompt.d.ts +23 -7
- package/dist/lib/session/prompt.js +46 -8
- package/dist/lib/session/remote/peer-stream.d.ts +24 -2
- package/dist/lib/session/remote/peer-stream.js +33 -6
- package/dist/lib/session/remote/remote-list.d.ts +20 -0
- package/dist/lib/session/remote/remote-list.js +22 -6
- package/dist/lib/session/remote/watch.d.ts +12 -0
- package/dist/lib/session/remote/watch.js +9 -0
- package/dist/lib/session/remote-preview-cache.d.ts +29 -0
- package/dist/lib/session/remote-preview-cache.js +373 -0
- package/dist/lib/session/sync/backend.d.ts +3 -3
- package/dist/lib/session/sync/backend.js +3 -3
- package/dist/lib/session/sync/provision.d.ts +1 -1
- package/dist/lib/session/sync/provision.js +2 -2
- package/dist/lib/session/tail.d.ts +50 -0
- package/dist/lib/session/tail.js +219 -0
- package/dist/lib/sessions-client.js +0 -3
- package/dist/lib/setup-tool-install.d.ts +3 -0
- package/dist/lib/setup-tool-install.js +27 -0
- package/dist/lib/setup-tool-status.d.ts +22 -0
- package/dist/lib/setup-tool-status.js +220 -0
- package/dist/lib/share-runtime.d.ts +11 -0
- package/dist/lib/share-runtime.js +63 -0
- package/dist/lib/signin-badge.d.ts +19 -4
- package/dist/lib/signin-badge.js +29 -11
- package/dist/lib/smart-launch.d.ts +1 -5
- package/dist/lib/smart-launch.js +3 -11
- package/dist/lib/ssh-exec.d.ts +44 -0
- package/dist/lib/ssh-exec.js +119 -0
- package/dist/lib/startup/command-registry.d.ts +6 -4
- package/dist/lib/startup/command-registry.js +10 -7
- package/dist/lib/state.js +2 -2
- package/dist/lib/storage/selection.d.ts +2 -2
- package/dist/lib/storage/selection.js +2 -2
- package/dist/lib/sync-umbrella.d.ts +5 -0
- package/dist/lib/sync-umbrella.js +18 -10
- package/dist/lib/term-driver.d.ts +24 -0
- package/dist/lib/term-driver.js +36 -0
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/index.js +1 -1
- package/dist/lib/terminal/inject.d.ts +38 -0
- package/dist/lib/terminal/inject.js +55 -9
- package/dist/lib/terminal/transport.d.ts +15 -5
- package/dist/lib/terminal/transport.js +61 -11
- package/dist/lib/traces/backend.d.ts +1 -2
- package/dist/lib/traces/backend.js +1 -2
- package/dist/lib/traces/provision.d.ts +1 -1
- package/dist/lib/traces/provision.js +2 -2
- package/dist/lib/types.d.ts +8 -6
- package/package.json +2 -3
- package/dist/commands/artifacts-setup.d.ts +0 -53
- package/dist/commands/artifacts-setup.js +0 -161
- package/dist/commands/artifacts.d.ts +0 -18
- package/dist/commands/artifacts.js +0 -58
- package/dist/commands/attach.d.ts +0 -12
- package/dist/commands/attach.js +0 -86
- package/dist/commands/auth-mint.d.ts +0 -12
- package/dist/commands/auth-mint.js +0 -108
- package/dist/commands/reconnect.d.ts +0 -46
- package/dist/commands/reconnect.js +0 -115
- package/dist/commands/share.d.ts +0 -293
- package/dist/commands/share.js +0 -1424
- package/dist/lib/fleet/remote-login.d.ts +0 -170
- package/dist/lib/fleet/remote-login.js +0 -568
- package/dist/lib/share/analytics.d.ts +0 -13
- package/dist/lib/share/analytics.js +0 -45
- package/dist/lib/share/backend.d.ts +0 -120
- package/dist/lib/share/backend.js +0 -176
- package/dist/lib/share/capture.d.ts +0 -31
- package/dist/lib/share/capture.js +0 -174
- package/dist/lib/share/config.d.ts +0 -72
- package/dist/lib/share/config.js +0 -211
- package/dist/lib/share/delete.d.ts +0 -123
- package/dist/lib/share/delete.js +0 -173
- package/dist/lib/share/html.d.ts +0 -20
- package/dist/lib/share/html.js +0 -88
- package/dist/lib/share/http-error.d.ts +0 -53
- package/dist/lib/share/http-error.js +0 -65
- package/dist/lib/share/og.d.ts +0 -26
- package/dist/lib/share/og.js +0 -84
- package/dist/lib/share/provision.d.ts +0 -127
- package/dist/lib/share/provision.js +0 -285
- package/dist/lib/share/publish.d.ts +0 -379
- package/dist/lib/share/publish.js +0 -818
- package/dist/lib/share/worker-template.d.ts +0 -27
- package/dist/lib/share/worker-template.js +0 -2424
- package/dist/lib/storage/index.d.ts +0 -14
- package/dist/lib/storage/index.js +0 -14
- package/dist/lib/storage/visibility.d.ts +0 -82
- package/dist/lib/storage/visibility.js +0 -99
package/dist/commands/browser.js
CHANGED
|
@@ -25,7 +25,11 @@ import { withActorEnv } from '../lib/hosts/dispatch.js';
|
|
|
25
25
|
import { streamAgentsOnHost, passthroughSshOptions, } from '../lib/hosts/passthrough.js';
|
|
26
26
|
import { buildRemoteAgentsInvocation, HOST_ROUTING_SPECS, stripRoutingFlags, } from '../lib/hosts/remote-cmd.js';
|
|
27
27
|
import { resolveRemoteOsSync } from '../lib/hosts/remote-os.js';
|
|
28
|
-
import { sshExec, SSH_OPTS } from '../lib/ssh-exec.js';
|
|
28
|
+
import { sshExec, SSH_OPTS, sshStreamWithArgs } from '../lib/ssh-exec.js';
|
|
29
|
+
import { getBrowserRuntimeDir } from '../lib/state.js';
|
|
30
|
+
import { buildSshInvocation, writeAskpassShim } from '../lib/devices/connect.js';
|
|
31
|
+
import { resolveDeviceProfile } from '../lib/devices/resolve-profile.js';
|
|
32
|
+
import { resolveDeviceTarget } from '../lib/devices/resolve-target.js';
|
|
29
33
|
import { flagValue } from '../lib/hosts/routing-flag.js';
|
|
30
34
|
import { browserTaskPicker } from './browser-picker.js';
|
|
31
35
|
import { assertRemoteControlAllowed, isFleetRemoteInvocation } from '../lib/browser/remote-control.js';
|
|
@@ -35,7 +39,7 @@ import { registerCommandGroups, setHelpSections } from '../lib/help.js';
|
|
|
35
39
|
import { buildHar } from '../lib/browser/har.js';
|
|
36
40
|
import { getCliVersion } from '../lib/version.js';
|
|
37
41
|
import { runBrowserIPCStream } from '../lib/browser/stream.js';
|
|
38
|
-
import { machineId } from '../lib/machine-id.js';
|
|
42
|
+
import { machineId, normalizeHost } from '../lib/machine-id.js';
|
|
39
43
|
import { isArcRunning } from '../lib/browser/drivers/arc.js';
|
|
40
44
|
import { resolveBrowserTarget } from '../lib/browser/resolve-target.js';
|
|
41
45
|
/**
|
|
@@ -179,6 +183,158 @@ export function remoteStartTaskName(stdout, explicit) {
|
|
|
179
183
|
}
|
|
180
184
|
return trimmed.split('\n').map((line) => line.trim()).find(Boolean);
|
|
181
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Fetch an absolute remote file into a private local path, bounded.
|
|
188
|
+
*
|
|
189
|
+
* Bounded for real, not by a pre-flight `stat`: the byte budget is enforced on the
|
|
190
|
+
* stream as it arrives (`sshExecRawStream` + `cat`), so a file that grows between
|
|
191
|
+
* a size check and the copy — or a peer that lies about the size — still cannot
|
|
192
|
+
* spend more than the budget. `scp` could not do this; it would transfer
|
|
193
|
+
* everything before this process got a chance to refuse.
|
|
194
|
+
*
|
|
195
|
+
* The local file is written 0600 inside a 0700 directory, because a capture can
|
|
196
|
+
* show anything that was on the peer's screen and a world-readable temp path
|
|
197
|
+
* would publish it to every local user.
|
|
198
|
+
*/
|
|
199
|
+
/**
|
|
200
|
+
* The command that streams one file's RAW BYTES to stdout on the device's shell.
|
|
201
|
+
*
|
|
202
|
+
* Platform-correct, not one command with a portable-looking name. On Windows
|
|
203
|
+
* `cat` is an alias for `Get-Content`, which is a TEXT reader: it decodes the
|
|
204
|
+
* file, splits it into lines, and re-encodes on output — so a screenshot arrives
|
|
205
|
+
* corrupted rather than merely reordered. The .NET path opens a `FileStream` and
|
|
206
|
+
* copies it into the raw stdout handle, bypassing PowerShell's object/text
|
|
207
|
+
* pipeline entirely, which is the only way binary survives.
|
|
208
|
+
*/
|
|
209
|
+
export function remoteReadFileArgv(remotePath, shell) {
|
|
210
|
+
if (shell === 'powershell') {
|
|
211
|
+
// Single-quoted inside the script with pwsh's own doubling, because this whole
|
|
212
|
+
// script is ONE argv token to the outer powershell.
|
|
213
|
+
const literal = remotePath.replace(/'/g, "''");
|
|
214
|
+
return ['powershell', '-NoProfile', '-Command', [
|
|
215
|
+
'$ErrorActionPreference=' + "'Stop';",
|
|
216
|
+
`$in=[System.IO.File]::OpenRead('${literal}');`,
|
|
217
|
+
'try{$out=[System.Console]::OpenStandardOutput();$in.CopyTo($out);$out.Flush()}finally{$in.Dispose()}',
|
|
218
|
+
].join(' ')];
|
|
219
|
+
}
|
|
220
|
+
// `--` so a path that begins with a dash is a path, not an option.
|
|
221
|
+
return ['cat', '--', remotePath];
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Largest capture `browser show --device` will pull. A capture is a screenshot,
|
|
225
|
+
* a PDF or a short recording; anything past this is not something a viewer should
|
|
226
|
+
* stream over ssh, and the cap is what makes the transfer BOUNDED rather than
|
|
227
|
+
* "however big the peer's file happens to be".
|
|
228
|
+
*/
|
|
229
|
+
export const REMOTE_VIEW_MAX_BYTES = 64 * 1024 * 1024;
|
|
230
|
+
/** Write a whole buffer, looping until it is all out. */
|
|
231
|
+
function writeFully(handle, chunk) {
|
|
232
|
+
let offset = 0;
|
|
233
|
+
while (offset < chunk.length) {
|
|
234
|
+
// `writeSync` may write FEWER bytes than asked (a signal, a pipe boundary),
|
|
235
|
+
// and ignoring the return value silently truncated the file mid-capture.
|
|
236
|
+
const wrote = fs.writeSync(handle, chunk, offset, chunk.length - offset);
|
|
237
|
+
if (wrote <= 0)
|
|
238
|
+
throw new Error('local write made no progress');
|
|
239
|
+
offset += wrote;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Fetch an absolute remote file into a private local path, bounded.
|
|
244
|
+
*
|
|
245
|
+
* Auth is the CANONICAL device path, not a hand-rolled ssh: `buildSshInvocation`
|
|
246
|
+
* supplies the askpass shim for a password-auth box, `-i`/`IdentitiesOnly` for an
|
|
247
|
+
* explicit identity file, and the managed known-hosts pinning. Building the ssh
|
|
248
|
+
* args here instead meant a password-auth device could not be reached at all and
|
|
249
|
+
* an explicit `identityFile` was silently ignored.
|
|
250
|
+
*
|
|
251
|
+
* Bounded for real, not by a pre-flight `stat`: the byte budget is enforced on the
|
|
252
|
+
* stream as it arrives, so a file that grows between a size check and the copy — or
|
|
253
|
+
* a peer that misreports its size — still cannot spend more than the budget.
|
|
254
|
+
*
|
|
255
|
+
* The local file is written 0600 inside a 0700 directory, because a capture can
|
|
256
|
+
* show anything that was on the peer's screen and a world-readable temp path
|
|
257
|
+
* would publish it to every local user.
|
|
258
|
+
*/
|
|
259
|
+
export async function fetchRemoteFileForViewing(device, remotePath, localPath, opts = {}) {
|
|
260
|
+
const posixAbsolute = path.posix.isAbsolute(remotePath);
|
|
261
|
+
const windowsAbsolute = /^[A-Za-z]:[\\/]/.test(remotePath) || /^\\\\/.test(remotePath);
|
|
262
|
+
if (!posixAbsolute && !windowsAbsolute) {
|
|
263
|
+
throw new Error(`Remote path must be absolute: got "${remotePath}".\n`
|
|
264
|
+
+ 'This machine cannot resolve the peer\'s working directory, so a relative path\n'
|
|
265
|
+
+ 'would name a different file there than you meant here.');
|
|
266
|
+
}
|
|
267
|
+
// `resolveDeviceTarget` is the SAME resolver `agents ssh` uses, so it yields the
|
|
268
|
+
// full device profile — platform, shell and auth — that `buildSshInvocation`
|
|
269
|
+
// needs. `resolveHost` returns a hosts-registry `Host`, which carries none of
|
|
270
|
+
// that: with it, a password-auth device could not be reached and an explicit
|
|
271
|
+
// identityFile was silently dropped.
|
|
272
|
+
const resolved = await resolveDeviceTarget(device);
|
|
273
|
+
if (!resolved)
|
|
274
|
+
throw new Error(`Unknown device "${device}". Next: agents devices list`);
|
|
275
|
+
const profile = resolveDeviceProfile(resolved);
|
|
276
|
+
const maxBytes = opts.maxBytes ?? REMOTE_VIEW_MAX_BYTES;
|
|
277
|
+
// A POSIX device cannot read a Windows path and vice versa; saying so here beats
|
|
278
|
+
// a confusing shell error from the peer.
|
|
279
|
+
if (profile.shell === 'powershell' && posixAbsolute && !windowsAbsolute) {
|
|
280
|
+
throw new Error(`"${remotePath}" is a POSIX path but ${device} runs PowerShell. Pass the Windows path (e.g. C:\\Users\\…).`);
|
|
281
|
+
}
|
|
282
|
+
if (profile.shell !== 'powershell' && windowsAbsolute) {
|
|
283
|
+
throw new Error(`"${remotePath}" is a Windows path but ${device} is a POSIX host. Pass the absolute POSIX path.`);
|
|
284
|
+
}
|
|
285
|
+
const resolvedLocal = path.resolve(localPath);
|
|
286
|
+
const dir = path.dirname(resolvedLocal);
|
|
287
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
288
|
+
fs.chmodSync(dir, 0o700);
|
|
289
|
+
const handle = fs.openSync(resolvedLocal, 'w', 0o600);
|
|
290
|
+
let written = 0;
|
|
291
|
+
let overflow = false;
|
|
292
|
+
const controller = new AbortController();
|
|
293
|
+
try {
|
|
294
|
+
// `argv: true` is what makes a path containing a space or a quote survive —
|
|
295
|
+
// the same exact-token delivery `agents ssh --argv` exposes.
|
|
296
|
+
const { args, env } = buildSshInvocation(profile, remoteReadFileArgv(remotePath, profile.shell), writeAskpassShim(), {}, { argv: true });
|
|
297
|
+
const result = await sshStreamWithArgs({
|
|
298
|
+
args,
|
|
299
|
+
env,
|
|
300
|
+
timeoutMs: opts.timeoutMs ?? 120_000,
|
|
301
|
+
signal: controller.signal,
|
|
302
|
+
onStdout: (chunk) => {
|
|
303
|
+
if (overflow)
|
|
304
|
+
return;
|
|
305
|
+
if (written + chunk.length > maxBytes) {
|
|
306
|
+
overflow = true;
|
|
307
|
+
controller.abort();
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
writeFully(handle, chunk);
|
|
311
|
+
written += chunk.length;
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
if (overflow) {
|
|
315
|
+
throw new Error(`Refusing to pull ${device}:${remotePath}: larger than the ${maxBytes}-byte view budget.`);
|
|
316
|
+
}
|
|
317
|
+
if (result.timedOut)
|
|
318
|
+
throw new Error(`Timed out pulling ${device}:${remotePath}.`);
|
|
319
|
+
if (result.code !== 0) {
|
|
320
|
+
const detail = result.stderr.toString('utf8').trim();
|
|
321
|
+
throw new Error(`Failed to read ${device}:${remotePath}${detail ? `: ${detail}` : ` (ssh exited ${result.code})`}`);
|
|
322
|
+
}
|
|
323
|
+
if (written === 0)
|
|
324
|
+
throw new Error(`${device}:${remotePath} is empty or was not readable.`);
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
// Never leave a partial file behind for a viewer to open as if it were whole.
|
|
328
|
+
try {
|
|
329
|
+
fs.unlinkSync(resolvedLocal);
|
|
330
|
+
}
|
|
331
|
+
catch { /* nothing to clean */ }
|
|
332
|
+
throw error;
|
|
333
|
+
}
|
|
334
|
+
finally {
|
|
335
|
+
fs.closeSync(handle);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
182
338
|
async function pullRemoteFile(device, remotePath, localPath) {
|
|
183
339
|
const host = await resolveHost(device);
|
|
184
340
|
if (!host) {
|
|
@@ -2071,8 +2227,14 @@ function registerTaskCommands(browser) {
|
|
|
2071
2227
|
});
|
|
2072
2228
|
browser
|
|
2073
2229
|
.command('show <url>')
|
|
2074
|
-
.description('Open a URL for a human to read: goes to browser.viewer (default: browser.profile), and binds no task')
|
|
2230
|
+
.description('Open a URL or local file for a human to read: goes to browser.viewer (default: browser.profile), and binds no task')
|
|
2075
2231
|
.option('--os-browser', 'Use the OS default handler instead of the configured viewer')
|
|
2232
|
+
// `--device` here names WHERE THE FILE IS, not where to open it. `show` is the
|
|
2233
|
+
// one browser verb narrowly exempt from the task-routing refusal
|
|
2234
|
+
// (`REJECT_DEVICE_MESSAGE`) because it binds no task and has nothing to route:
|
|
2235
|
+
// it is a local viewer, so the flag can only mean "the source lives there".
|
|
2236
|
+
// Every other verb still refuses `--device` — a task is bound at `start`.
|
|
2237
|
+
.option('--device <name>', 'fetch the file from this device, then view it locally (absolute path only)')
|
|
2076
2238
|
.option('--json', 'Output machine-readable JSON')
|
|
2077
2239
|
.action(async (url, opts) => {
|
|
2078
2240
|
// The entry point external tools need. `navigate` binds a task, which the
|
|
@@ -2083,20 +2245,48 @@ function registerTaskCommands(browser) {
|
|
|
2083
2245
|
// drive letter (`C:\Users\me\plan.html`), so a real path was treated as a
|
|
2084
2246
|
// URL. A scheme is at least two characters.
|
|
2085
2247
|
const isLocalFile = !/^[a-z][a-z0-9+.-]+:/i.test(url);
|
|
2086
|
-
|
|
2087
|
-
|
|
2248
|
+
let localTarget = isLocalFile ? path.resolve(url) : undefined;
|
|
2249
|
+
let fetched;
|
|
2250
|
+
if (opts.device) {
|
|
2251
|
+
// A URL is already reachable from here, so `--device` with one is a
|
|
2252
|
+
// contradiction rather than a no-op: it says the source is remote while
|
|
2253
|
+
// naming something that is not a file on that peer.
|
|
2254
|
+
if (!isLocalFile) {
|
|
2255
|
+
console.error(`--device names the device holding a FILE, so it cannot be combined with a URL.\n Drop --device to open ${url} here, or pass an absolute path on ${opts.device}.`);
|
|
2256
|
+
process.exit(1);
|
|
2257
|
+
}
|
|
2258
|
+
// The peer's path, verbatim — NOT path.resolve()'d, which would rewrite it
|
|
2259
|
+
// against this machine's cwd and name a different file there.
|
|
2260
|
+
const remotePath = url;
|
|
2261
|
+
const dir = path.join(getBrowserRuntimeDir(), 'remote-views');
|
|
2262
|
+
fetched = path.join(dir, `${normalizeHost(opts.device)}-${Date.now()}-${path.basename(remotePath) || 'capture'}`);
|
|
2263
|
+
try {
|
|
2264
|
+
await fetchRemoteFileForViewing(opts.device, remotePath, fetched);
|
|
2265
|
+
}
|
|
2266
|
+
catch (error) {
|
|
2267
|
+
console.error(error.message);
|
|
2268
|
+
process.exit(1);
|
|
2269
|
+
}
|
|
2270
|
+
localTarget = fetched;
|
|
2271
|
+
}
|
|
2272
|
+
const outcome = localTarget
|
|
2273
|
+
? await showFile(localTarget, { osBrowser: opts.osBrowser })
|
|
2088
2274
|
: await showUrl(url, { osBrowser: opts.osBrowser });
|
|
2275
|
+
// The fetched copy is what a viewer actually opened, so it is what the
|
|
2276
|
+
// caller is told about — pointing at the peer's path would name a file that
|
|
2277
|
+
// is not on this machine.
|
|
2278
|
+
const shown = fetched ?? url;
|
|
2089
2279
|
if (opts.json) {
|
|
2090
|
-
console.log(JSON.stringify(outcome, null, 2));
|
|
2280
|
+
console.log(JSON.stringify({ ...outcome, ...(fetched ? { source: `${opts.device}:${url}`, local: fetched } : {}) }, null, 2));
|
|
2091
2281
|
}
|
|
2092
2282
|
else if (outcome.via === 'none') {
|
|
2093
|
-
console.error(`Could not open a browser — open this yourself:\n ${
|
|
2283
|
+
console.error(`Could not open a browser — open this yourself:\n ${shown}`);
|
|
2094
2284
|
}
|
|
2095
2285
|
else if (outcome.via === 'profile') {
|
|
2096
|
-
console.log(`Shown in ${outcome.profile}: ${
|
|
2286
|
+
console.log(`Shown in ${outcome.profile}: ${shown}`);
|
|
2097
2287
|
}
|
|
2098
2288
|
else {
|
|
2099
|
-
console.log(`Opened in the OS default browser: ${
|
|
2289
|
+
console.log(`Opened in the OS default browser: ${shown}`);
|
|
2100
2290
|
}
|
|
2101
2291
|
if (outcome.via === 'none')
|
|
2102
2292
|
process.exit(1);
|
|
@@ -2572,6 +2762,11 @@ function registerTaskCommands(browser) {
|
|
|
2572
2762
|
// instead of the raw `comet-local@endpoint-0` key (RUSH-2709).
|
|
2573
2763
|
const endpointLabel = profile.endpoint ? `endpoint: ${profile.endpoint}, ` : '';
|
|
2574
2764
|
console.log(`\n${profile.name} (${endpointLabel}${portLabel}, ${pidLabel})`);
|
|
2765
|
+
// A profile whose live state could not be read is still listed, and
|
|
2766
|
+
// says so — it is not silently dropped and not shown as healthy.
|
|
2767
|
+
if (profile.unavailable) {
|
|
2768
|
+
console.log(` Unavailable: ${profile.unavailable}`);
|
|
2769
|
+
}
|
|
2575
2770
|
if (profile.tasks.length === 0) {
|
|
2576
2771
|
console.log(' No active tasks');
|
|
2577
2772
|
}
|
|
@@ -2593,6 +2788,12 @@ function registerTaskCommands(browser) {
|
|
|
2593
2788
|
String(task.tabCount).padEnd(6) +
|
|
2594
2789
|
domains.slice(0, 20).padEnd(22) +
|
|
2595
2790
|
age);
|
|
2791
|
+
// The recorded tab count above is what the task owns on disk; this
|
|
2792
|
+
// says the live tabs behind it could not be read, so the count is
|
|
2793
|
+
// not mistaken for verified-live tabs.
|
|
2794
|
+
if (task.unavailable) {
|
|
2795
|
+
console.log(' '.padEnd(14) + `↳ tabs unreadable: ${task.unavailable}`);
|
|
2796
|
+
}
|
|
2596
2797
|
}
|
|
2597
2798
|
}
|
|
2598
2799
|
}
|
|
@@ -18,9 +18,8 @@
|
|
|
18
18
|
* Read-only always: doctor diagnoses, it never mutates. To fix the gaps it
|
|
19
19
|
* finds — install missing resources, repair Claude-invalid plugin manifests,
|
|
20
20
|
* refresh stale plugins, reconcile drift, repair hook runtime shims — run
|
|
21
|
-
* `agents sync <agent>@all` (the one fixer
|
|
22
|
-
*
|
|
23
|
-
* deprecated no-op that now errors with a pointer to `agents sync`.
|
|
21
|
+
* `agents sync <agent>@all` (the one fixer). Run `agents prune cleanup` to act
|
|
22
|
+
* on orphan readouts.
|
|
24
23
|
*/
|
|
25
24
|
import type { Command } from 'commander';
|
|
26
25
|
import { type FleetInventory } from '../lib/devices/fleet-divergence.js';
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1251,7 +1251,6 @@ export function registerDoctorCommand(program) {
|
|
|
1251
1251
|
.description('Diagnose CLI availability, sync status, and resource divergence (optionally for a specific agent[@version]).')
|
|
1252
1252
|
.option('--json', 'Output machine-readable JSON')
|
|
1253
1253
|
.option('--diff', 'In target mode, include unified diffs for divergent files')
|
|
1254
|
-
.option('--fix', '(deprecated) doctor is diagnose-only — this errors with a pointer to `agents sync`, the one fixer')
|
|
1255
1254
|
.option('--kind <kinds>', 'Restrict to comma-separated resource kinds (commands,skills,hooks,rules,mcp,permissions,subagents,plugins,workflows,memory)')
|
|
1256
1255
|
.option('--cwd <path>', 'Resolution cwd for project layer detection (default: process.cwd())')
|
|
1257
1256
|
.option('--adopt <agent>', "Take over the agent's native launcher that shadows the shim (symlink it to the version-managed shim; reversible with --release)")
|
|
@@ -1381,16 +1380,6 @@ export function registerDoctorCommand(program) {
|
|
|
1381
1380
|
}
|
|
1382
1381
|
return;
|
|
1383
1382
|
}
|
|
1384
|
-
// `doctor --fix` moved to `agents sync` — doctor is diagnose-only now.
|
|
1385
|
-
// Keep the flag for one release as a signpost: it never heals, it points at
|
|
1386
|
-
// the one fixer and exits non-zero so scripts and muscle memory get a clear
|
|
1387
|
-
// redirect instead of a silent no-op.
|
|
1388
|
-
if (opts.fix) {
|
|
1389
|
-
const where = target ? `agents sync ${target}` : 'agents sync <agent>@all';
|
|
1390
|
-
console.error(chalk.yellow('`agents doctor --fix` has moved to `agents sync`.'));
|
|
1391
|
-
console.error(chalk.gray(` doctor now only diagnoses. To fix, run: ${where}`));
|
|
1392
|
-
process.exit(1);
|
|
1393
|
-
}
|
|
1394
1383
|
if (!target) {
|
|
1395
1384
|
// Singleflight + short-TTL cache for the bare `doctor --json` overview.
|
|
1396
1385
|
// This overview probes every host CLI, every agent's sign-in, and every
|
|
@@ -42,13 +42,11 @@ export interface EventsOptions {
|
|
|
42
42
|
limit?: string;
|
|
43
43
|
json?: boolean;
|
|
44
44
|
follow?: boolean;
|
|
45
|
-
/** @deprecated Prefer --include ops / --exclude activity */
|
|
46
|
-
audit?: boolean;
|
|
47
45
|
include?: string;
|
|
48
46
|
exclude?: string;
|
|
49
47
|
}
|
|
50
48
|
/** Add the one canonical event-reader option surface to a command or alias. */
|
|
51
|
-
export declare function addEventsReadOptions(command: Command
|
|
49
|
+
export declare function addEventsReadOptions(command: Command): Command;
|
|
52
50
|
/** Canonical reader used by `agents events`, `agents events audit`, and `agents logs`. */
|
|
53
51
|
export declare function runEventsCommand(options: EventsOptions, forceAudit?: boolean): Promise<void>;
|
|
54
52
|
/** Shared by `events stats` and the `logs stats` alias. */
|
package/dist/commands/events.js
CHANGED
|
@@ -195,7 +195,7 @@ function registerEmitSubcommand(events) {
|
|
|
195
195
|
});
|
|
196
196
|
}
|
|
197
197
|
/** Add the one canonical event-reader option surface to a command or alias. */
|
|
198
|
-
export function addEventsReadOptions(command
|
|
198
|
+
export function addEventsReadOptions(command) {
|
|
199
199
|
command
|
|
200
200
|
.option('--include <families>', `Only these families (comma-sep): ${EVENT_FAMILIES.join(', ')}`)
|
|
201
201
|
.option('--exclude <families>', `Drop these families (comma-sep): ${EVENT_FAMILIES.join(', ')}`)
|
|
@@ -211,9 +211,6 @@ export function addEventsReadOptions(command, includeAuditFlag = true) {
|
|
|
211
211
|
.option('--limit <n>', 'Max records to show; 0 for no cap (default 50)', '50')
|
|
212
212
|
.option('--json', 'Output raw records as JSON')
|
|
213
213
|
.option('-f, --follow', "Tail today's operational log live");
|
|
214
|
-
if (includeAuditFlag) {
|
|
215
|
-
command.option('--audit', 'Deprecated: operational events only — prefer --include ops or --exclude activity');
|
|
216
|
-
}
|
|
217
214
|
return command;
|
|
218
215
|
}
|
|
219
216
|
/** Canonical reader used by `agents events`, `agents events audit`, and `agents logs`. */
|
|
@@ -242,11 +239,11 @@ export async function runEventsCommand(options, forceAudit = false) {
|
|
|
242
239
|
process.exitCode = 2;
|
|
243
240
|
return;
|
|
244
241
|
}
|
|
245
|
-
//
|
|
246
|
-
// source selection via applyFamilies — never override includeActivity after.
|
|
242
|
+
// forceAudit (`events audit` / `logs`) = ops-only when no family flags. Families
|
|
243
|
+
// own the source selection via applyFamilies — never override includeActivity after.
|
|
247
244
|
const includeActivity = (includeFamilies !== undefined || excludeFamilies !== undefined)
|
|
248
245
|
? true
|
|
249
|
-
: !
|
|
246
|
+
: !forceAudit;
|
|
250
247
|
const fetched = readUnifiedEvents({
|
|
251
248
|
startDate,
|
|
252
249
|
eventTypes: options.event && options.event.length ? options.event : undefined,
|
package/dist/commands/exec.d.ts
CHANGED