@phnx-labs/agents-cli 1.22.47 → 1.22.49
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 +401 -17
- package/README.md +66 -32
- package/dist/bootstrap.js +4 -2
- package/dist/cli/command-registry.d.ts +1 -0
- package/dist/cli/command-registry.js +2 -0
- package/dist/commands/accounts.d.ts +58 -0
- package/dist/commands/accounts.js +206 -32
- package/dist/commands/artifacts.js +7 -6
- package/dist/commands/attach.js +1 -1
- package/dist/commands/browser.js +501 -103
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +0 -20
- package/dist/commands/daemon.d.ts +21 -0
- package/dist/commands/daemon.js +147 -12
- package/dist/commands/exec.js +52 -15
- package/dist/commands/focus.d.ts +14 -0
- package/dist/commands/focus.js +48 -5
- package/dist/commands/go.d.ts +3 -0
- package/dist/commands/go.js +16 -3
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/lease.d.ts +1 -1
- package/dist/commands/lease.js +4 -3
- package/dist/commands/mailboxes.js +4 -3
- package/dist/commands/menubar.js +11 -4
- package/dist/commands/projects.d.ts +16 -1
- package/dist/commands/projects.js +58 -64
- package/dist/commands/resume.js +1 -1
- package/dist/commands/route.js +4 -4
- package/dist/commands/routines.js +3 -2
- package/dist/commands/run-broadcast.d.ts +1 -1
- package/dist/commands/run-broadcast.js +1 -1
- package/dist/commands/sessions-picker.d.ts +22 -0
- package/dist/commands/sessions-picker.js +48 -9
- package/dist/commands/sessions-trace.js +1 -1
- package/dist/commands/sessions.js +1 -0
- package/dist/commands/setup-secrets.js +23 -0
- package/dist/commands/share.d.ts +20 -5
- package/dist/commands/share.js +152 -52
- package/dist/commands/ssh.js +54 -7
- package/dist/commands/traces.d.ts +2 -0
- package/dist/commands/traces.js +184 -0
- package/dist/commands/view.d.ts +1 -1
- package/dist/commands/view.js +17 -34
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/accounting/usage.d.ts +14 -3
- package/dist/lib/accounting/usage.js +64 -18
- package/dist/lib/agent-spec/agents.js +11 -9
- package/dist/lib/browser/drivers/ssh.d.ts +16 -2
- package/dist/lib/browser/drivers/ssh.js +36 -9
- package/dist/lib/browser/ipc.js +8 -3
- package/dist/lib/browser/profiles.d.ts +35 -101
- package/dist/lib/browser/profiles.js +130 -301
- package/dist/lib/browser/registry.d.ts +39 -0
- package/dist/lib/browser/registry.js +149 -0
- package/dist/lib/browser/remote-control.d.ts +8 -6
- package/dist/lib/browser/remote-control.js +8 -6
- package/dist/lib/browser/resolve-target.d.ts +68 -0
- package/dist/lib/browser/resolve-target.js +250 -0
- package/dist/lib/browser/runtime-state.d.ts +40 -16
- package/dist/lib/browser/runtime-state.js +92 -25
- package/dist/lib/browser/service.d.ts +28 -9
- package/dist/lib/browser/service.js +139 -62
- package/dist/lib/browser/task-index.d.ts +70 -0
- package/dist/lib/browser/task-index.js +180 -0
- package/dist/lib/byok-usage.js +2 -2
- package/dist/lib/claude-statusline.d.ts +29 -0
- package/dist/lib/claude-statusline.js +139 -0
- package/dist/lib/cloud/host.d.ts +1 -1
- package/dist/lib/cloud/host.js +1 -1
- package/dist/lib/cloud/rush.js +1 -1
- package/dist/lib/computer/computer-rpc.js +2 -2
- package/dist/lib/computer/download.d.ts +15 -15
- package/dist/lib/computer/download.js +35 -98
- package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
- package/dist/lib/computer/ssh-tunnel.js +12 -22
- package/dist/lib/config-keys.d.ts +2 -8
- package/dist/lib/config-keys.js +2 -13
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +2 -2
- package/dist/lib/crabbox/lease.d.ts +9 -0
- package/dist/lib/crabbox/lease.js +70 -15
- package/dist/lib/crabbox/runtimes.js +1 -1
- package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
- package/dist/lib/daemon/account-state-daemon-service.js +26 -0
- package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
- package/dist/lib/daemon/browser-ipc-service.js +46 -0
- package/dist/lib/daemon/daemon.d.ts +3 -0
- package/dist/lib/daemon/daemon.js +212 -444
- package/dist/lib/daemon/device-probe-service.d.ts +26 -0
- package/dist/lib/daemon/device-probe-service.js +47 -0
- package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
- package/dist/lib/daemon/keychain-reap-service.js +32 -0
- package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
- package/dist/lib/daemon/monitor-engine-service.js +29 -0
- package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
- package/dist/lib/daemon/secrets-broker-service.js +66 -0
- package/dist/lib/daemon/self-heal-service.d.ts +27 -0
- package/dist/lib/daemon/self-heal-service.js +48 -0
- package/dist/lib/daemon/service.d.ts +102 -0
- package/dist/lib/daemon/service.js +98 -0
- package/dist/lib/daemon/session-index-service.d.ts +22 -0
- package/dist/lib/daemon/session-index-service.js +40 -0
- package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
- package/dist/lib/daemon/state-dir-check-service.js +63 -0
- package/dist/lib/daemon/supervisor.d.ts +87 -0
- package/dist/lib/daemon/supervisor.js +298 -0
- package/dist/lib/daemon/watchdog-service.d.ts +19 -0
- package/dist/lib/daemon/watchdog-service.js +40 -0
- package/dist/lib/daemon-health.d.ts +15 -0
- package/dist/lib/daemon-health.js +29 -5
- package/dist/lib/daemon-services.d.ts +8 -1
- package/dist/lib/daemon-services.js +55 -0
- package/dist/lib/daemon-ticks.d.ts +13 -2
- package/dist/lib/daemon-ticks.js +20 -18
- package/dist/lib/device-config.d.ts +0 -2
- package/dist/lib/device-config.js +1 -23
- package/dist/lib/devices/registry.d.ts +1 -1
- package/dist/lib/devices/registry.js +1 -1
- package/dist/lib/devices/stats-cache.d.ts +20 -0
- package/dist/lib/devices/stats-cache.js +49 -5
- package/dist/lib/devices/worker-pick.d.ts +48 -0
- package/dist/lib/devices/worker-pick.js +113 -0
- package/dist/lib/exec.d.ts +67 -13
- package/dist/lib/exec.js +85 -29
- package/dist/lib/harness/adapters/cursor.js +8 -11
- package/dist/lib/helper-download.d.ts +108 -0
- package/dist/lib/helper-download.js +194 -0
- package/dist/lib/helper-versions.d.ts +52 -0
- package/dist/lib/helper-versions.js +58 -0
- package/dist/lib/hooks/cache.js +11 -0
- package/dist/lib/hooks/install.js +2 -2
- package/dist/lib/hosts/credentials.d.ts +1 -1
- package/dist/lib/hosts/credentials.js +1 -1
- package/dist/lib/hosts/dispatch.d.ts +7 -1
- package/dist/lib/hosts/dispatch.js +24 -5
- package/dist/lib/hosts/passthrough.d.ts +8 -15
- package/dist/lib/hosts/passthrough.js +19 -16
- package/dist/lib/hosts/providers/devices.d.ts +1 -1
- package/dist/lib/hosts/providers/devices.js +1 -1
- package/dist/lib/hosts/reconnect.d.ts +179 -21
- package/dist/lib/hosts/reconnect.js +266 -45
- package/dist/lib/installations/migrate.d.ts +4 -5
- package/dist/lib/installations/migrate.js +6 -7
- package/dist/lib/installations/shims.d.ts +1 -1
- package/dist/lib/installations/shims.js +23 -13
- package/dist/lib/installations/versions.js +25 -2
- package/dist/lib/linear-autoclose.d.ts +1 -1
- package/dist/lib/linear-autoclose.js +1 -1
- package/dist/lib/loop.d.ts +2 -2
- package/dist/lib/loop.js +1 -1
- package/dist/lib/menubar/download-menubar.d.ts +47 -0
- package/dist/lib/menubar/download-menubar.js +60 -0
- package/dist/lib/menubar/install-menubar.d.ts +31 -7
- package/dist/lib/menubar/install-menubar.js +79 -21
- package/dist/lib/menubar/notify-desktop.d.ts +3 -3
- package/dist/lib/menubar/notify-desktop.js +6 -6
- package/dist/lib/model-tiers.d.ts +1 -1
- package/dist/lib/project-probe.d.ts +1 -1
- package/dist/lib/project-probe.js +1 -1
- package/dist/lib/project-pull.d.ts +1 -1
- package/dist/lib/project-pull.js +2 -2
- package/dist/lib/projects.d.ts +1 -1
- package/dist/lib/projects.js +1 -1
- package/dist/lib/sandbox.js +6 -7
- package/dist/lib/secrets/download-keychain.d.ts +47 -0
- package/dist/lib/secrets/download-keychain.js +70 -0
- package/dist/lib/secrets/install-helper.d.ts +20 -1
- package/dist/lib/secrets/install-helper.js +44 -3
- package/dist/lib/session/active.js +17 -1
- package/dist/lib/session/db.d.ts +11 -1
- package/dist/lib/session/db.js +138 -6
- package/dist/lib/session/discover.d.ts +21 -1
- package/dist/lib/session/discover.js +33 -5
- package/dist/lib/session/highlights.d.ts +49 -1
- package/dist/lib/session/highlights.js +95 -0
- package/dist/lib/session/host-link.d.ts +16 -2
- package/dist/lib/session/host-link.js +7 -1
- package/dist/lib/session/parse.js +33 -1
- package/dist/lib/session/remote/remote-list.d.ts +9 -0
- package/dist/lib/session/remote/remote-list.js +19 -2
- package/dist/lib/session/remote/watch.js +11 -2
- package/dist/lib/session/session-cache.d.ts +53 -0
- package/dist/lib/session/session-cache.js +88 -0
- package/dist/lib/session/state.js +1 -1
- package/dist/lib/session/tool-calls.d.ts +2 -0
- package/dist/lib/session/tool-calls.js +1 -1
- package/dist/lib/session/trajectory-html.d.ts +5 -1
- package/dist/lib/session/trajectory-html.js +42 -22
- package/dist/lib/session/trajectory-text.js +9 -2
- package/dist/lib/session/trajectory.js +53 -4
- package/dist/lib/session/types.d.ts +15 -0
- package/dist/lib/sha256-asset.d.ts +26 -0
- package/dist/lib/sha256-asset.js +41 -0
- package/dist/lib/share/backend.d.ts +95 -0
- package/dist/lib/share/backend.js +139 -0
- package/dist/lib/share/delete.d.ts +6 -2
- package/dist/lib/share/delete.js +8 -9
- package/dist/lib/share/html.d.ts +22 -0
- package/dist/lib/share/html.js +88 -0
- package/dist/lib/share/provision.d.ts +18 -4
- package/dist/lib/share/provision.js +38 -4
- package/dist/lib/share/publish.d.ts +39 -15
- package/dist/lib/share/publish.js +55 -26
- package/dist/lib/share/worker-template.js +171 -11
- package/dist/lib/sqlite.js +16 -3
- package/dist/lib/ssh-exec.d.ts +65 -0
- package/dist/lib/ssh-exec.js +120 -4
- package/dist/lib/startup/command-registry.js +1 -1
- package/dist/lib/startup/dev-build.js +3 -3
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +7 -3
- package/dist/lib/subagents-registry.js +1 -1
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/terminal/types.js +1 -1
- package/dist/lib/tmux/orphan-reap.js +1 -1
- package/dist/lib/tmux/session.js +31 -0
- package/dist/lib/traces/backend.d.ts +29 -0
- package/dist/lib/traces/backend.js +46 -0
- package/dist/lib/traces/classify.d.ts +52 -0
- package/dist/lib/traces/classify.js +72 -0
- package/dist/lib/traces/config.d.ts +10 -0
- package/dist/lib/traces/config.js +2 -0
- package/dist/lib/traces/provision.d.ts +19 -0
- package/dist/lib/traces/provision.js +30 -0
- package/dist/lib/traces/sync.d.ts +150 -0
- package/dist/lib/traces/sync.js +447 -0
- package/dist/lib/traces/worker-template.d.ts +4 -0
- package/dist/lib/traces/worker-template.js +159 -0
- package/dist/lib/types.d.ts +18 -19
- package/dist/lib/types.js +14 -0
- package/dist/lib/usage-refresh.d.ts +1 -1
- package/dist/lib/usage-refresh.js +11 -8
- package/dist/lib/workflows.d.ts +1 -1
- package/package.json +4 -6
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -26
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -135
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/dist/lib/usage-fleet.d.ts +0 -32
- package/dist/lib/usage-fleet.js +0 -125
|
@@ -14,6 +14,15 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { formatDuration, formatTokenCount } from './render.js';
|
|
16
16
|
import { escapeHtml } from './share-html.js';
|
|
17
|
+
/**
|
|
18
|
+
* The honest footer prefix for a rendered trace: the redacted label only when
|
|
19
|
+
* something was actually redacted, else the "local only" disclaimer under
|
|
20
|
+
* `--no-redact`. One source for all three layouts (single / compare / lineage)
|
|
21
|
+
* so the label never drifts back to a hardcoded "Secret-redacted" claim.
|
|
22
|
+
*/
|
|
23
|
+
function redactionLabel(redacted) {
|
|
24
|
+
return redacted ? 'Secret-redacted' : 'Unredacted (local only)';
|
|
25
|
+
}
|
|
17
26
|
/** Color a tool bar by family; an error outcome overrides to red. */
|
|
18
27
|
function toolColor(step) {
|
|
19
28
|
if (step.outcome === 'error')
|
|
@@ -31,7 +40,7 @@ function toolColor(step) {
|
|
|
31
40
|
return '#b98cff';
|
|
32
41
|
return '#8b98a5';
|
|
33
42
|
}
|
|
34
|
-
/** Precise per-step duration for the waterfall: "8m04s", "1.6s", "320ms". */
|
|
43
|
+
/** Precise per-step duration for the waterfall: "2h01m", "8m04s", "1.6s", "320ms". */
|
|
35
44
|
function formatStepDuration(ms) {
|
|
36
45
|
if (ms <= 0)
|
|
37
46
|
return '0s';
|
|
@@ -40,6 +49,13 @@ function formatStepDuration(ms) {
|
|
|
40
49
|
if (ms < 60_000)
|
|
41
50
|
return `${(ms / 1000).toFixed(ms < 10_000 ? 1 : 0)}s`;
|
|
42
51
|
const totalSec = Math.round(ms / 1000);
|
|
52
|
+
// Beyond an hour, minutes-only reads as "1441m18s" for an overnight idle gap —
|
|
53
|
+
// roll into hours so a long stall is legible at a glance.
|
|
54
|
+
if (totalSec >= 3600) {
|
|
55
|
+
const h = Math.floor(totalSec / 3600);
|
|
56
|
+
const m = Math.floor((totalSec % 3600) / 60);
|
|
57
|
+
return `${h}h${String(m).padStart(2, '0')}m`;
|
|
58
|
+
}
|
|
43
59
|
const m = Math.floor(totalSec / 60);
|
|
44
60
|
const s = totalSec % 60;
|
|
45
61
|
return `${m}m${String(s).padStart(2, '0')}s`;
|
|
@@ -242,7 +258,7 @@ export function renderTrajectoryHtml(model) {
|
|
|
242
258
|
</div>
|
|
243
259
|
</main>
|
|
244
260
|
<footer>
|
|
245
|
-
${model.truncatedSteps > 0 ? 'Truncated · ' : ''}${model.redacted
|
|
261
|
+
${model.truncatedSteps > 0 ? 'Truncated · ' : ''}${redactionLabel(model.redacted)} trajectory rendered by agents-cli · <code>agents sessions trace</code>
|
|
246
262
|
</footer>
|
|
247
263
|
<script>${THEME_SCRIPT}</script>
|
|
248
264
|
</body>
|
|
@@ -335,23 +351,7 @@ const BASE_STYLE = `
|
|
|
335
351
|
color: var(--dim); font-size: 12px;
|
|
336
352
|
font-family: ui-monospace, "JetBrains Mono", Menlo, monospace;
|
|
337
353
|
}
|
|
338
|
-
|
|
339
|
-
/** Compare-only rules layered on top of {@link BASE_STYLE} — lanes, divergence marker, summary table. */
|
|
340
|
-
const COMPARE_STYLE = `
|
|
341
|
-
svg .diverge { stroke: #e0b341; stroke-width: 1.4; stroke-dasharray: 4 3; }
|
|
342
|
-
.diverge-note { color: #e0b341; font-size: 12.5px; margin: 6px 0; }
|
|
343
|
-
table.cmp-table { border-collapse: collapse; width: 100%; font-family: ui-monospace, "JetBrains Mono", Menlo, monospace; font-size: 12.5px; }
|
|
344
|
-
table.cmp-table th, table.cmp-table td { text-align: left; padding: 6px 10px; border-bottom: 1px solid var(--border); }
|
|
345
|
-
table.cmp-table th { color: var(--dim); font-weight: 600; text-transform: uppercase; font-size: 10.5px; letter-spacing: .5px; }
|
|
346
|
-
.diff-cols { display: flex; gap: 24px; flex-wrap: wrap; }
|
|
347
|
-
.diff-col { flex: 1; min-width: 260px; }
|
|
348
|
-
.diff-col h3 { font-size: 11px; color: var(--dim); text-transform: uppercase; letter-spacing: .5px; margin: 0 0 8px; }
|
|
349
|
-
.diff-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
|
|
350
|
-
.diff-list li {
|
|
351
|
-
font-family: ui-monospace, "JetBrains Mono", Menlo, monospace; font-size: 12px;
|
|
352
|
-
border: 1px solid var(--border); border-radius: 5px; padding: 5px 8px; background: var(--panel);
|
|
353
|
-
}
|
|
354
|
-
/* trace v2 — analysis hero + program-aware step list */
|
|
354
|
+
/* trace v2 — analysis hero + program-aware step list (single-session view; compare/lineage layer on top of BASE_STYLE, so this must live here) */
|
|
355
355
|
main { padding: 20px; }
|
|
356
356
|
.analysis { display: grid; grid-template-columns: 1.25fr 1fr; gap: 20px; margin-bottom: 26px; }
|
|
357
357
|
@media (max-width: 720px) { .analysis { grid-template-columns: 1fr; } }
|
|
@@ -391,6 +391,22 @@ const COMPARE_STYLE = `
|
|
|
391
391
|
.gap-divider { text-align: center; color: #7a5c3a; font-size: 11px; margin: 8px 0; letter-spacing: .06em; font-family: ui-monospace, "JetBrains Mono", Menlo, monospace; }
|
|
392
392
|
details.step .detail { white-space: pre-wrap; color: var(--dim); font-size: 11px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 8px 10px; margin: 2px 0 8px 54px; max-height: 220px; overflow: auto; }
|
|
393
393
|
`;
|
|
394
|
+
/** Compare-only rules layered on top of {@link BASE_STYLE} — lanes, divergence marker, summary table. */
|
|
395
|
+
const COMPARE_STYLE = `
|
|
396
|
+
svg .diverge { stroke: #e0b341; stroke-width: 1.4; stroke-dasharray: 4 3; }
|
|
397
|
+
.diverge-note { color: #e0b341; font-size: 12.5px; margin: 6px 0; }
|
|
398
|
+
table.cmp-table { border-collapse: collapse; width: 100%; font-family: ui-monospace, "JetBrains Mono", Menlo, monospace; font-size: 12.5px; }
|
|
399
|
+
table.cmp-table th, table.cmp-table td { text-align: left; padding: 6px 10px; border-bottom: 1px solid var(--border); }
|
|
400
|
+
table.cmp-table th { color: var(--dim); font-weight: 600; text-transform: uppercase; font-size: 10.5px; letter-spacing: .5px; }
|
|
401
|
+
.diff-cols { display: flex; gap: 24px; flex-wrap: wrap; }
|
|
402
|
+
.diff-col { flex: 1; min-width: 260px; }
|
|
403
|
+
.diff-col h3 { font-size: 11px; color: var(--dim); text-transform: uppercase; letter-spacing: .5px; margin: 0 0 8px; }
|
|
404
|
+
.diff-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
|
|
405
|
+
.diff-list li {
|
|
406
|
+
font-family: ui-monospace, "JetBrains Mono", Menlo, monospace; font-size: 12px;
|
|
407
|
+
border: 1px solid var(--border); border-radius: 5px; padding: 5px 8px; background: var(--panel);
|
|
408
|
+
}
|
|
409
|
+
`;
|
|
394
410
|
const THEME_SCRIPT = `
|
|
395
411
|
(function () {
|
|
396
412
|
var root = document.documentElement;
|
|
@@ -521,7 +537,7 @@ export function renderTrajectoryCompareHtml(cmp) {
|
|
|
521
537
|
${renderCompareDiffLists(cmp)}
|
|
522
538
|
</main>
|
|
523
539
|
<footer>
|
|
524
|
-
|
|
540
|
+
${redactionLabel(cmp.a.redacted || cmp.b.redacted)} compare rendered by agents-cli · <code>agents sessions trace</code>
|
|
525
541
|
</footer>
|
|
526
542
|
<script>${THEME_SCRIPT}</script>
|
|
527
543
|
</body>
|
|
@@ -696,8 +712,12 @@ const LINEAGE_SCRIPT = `
|
|
|
696
712
|
* auto-discovered from the team records) plus a per-node summary card revealed
|
|
697
713
|
* by clicking a node. Same shell, redaction, and no-CDN rule as the other two
|
|
698
714
|
* layouts; local paths (`filePath`/`cwd`) are deliberately never rendered.
|
|
715
|
+
*
|
|
716
|
+
* `redacted` follows the trace's `--no-redact` flag so the footer states the
|
|
717
|
+
* honest label, exactly as the single-trajectory and compare renderers do; it
|
|
718
|
+
* defaults to the redacted claim to keep the shared/safe default.
|
|
699
719
|
*/
|
|
700
|
-
export function renderLineageHtml(lineage) {
|
|
720
|
+
export function renderLineageHtml(lineage, redacted = true) {
|
|
701
721
|
const root = lineage.nodes[0];
|
|
702
722
|
const title = root ? `${root.agent} · ${root.shortId}` : 'lineage';
|
|
703
723
|
const spawned = Math.max(0, lineage.nodes.length - 1);
|
|
@@ -740,7 +760,7 @@ export function renderLineageHtml(lineage) {
|
|
|
740
760
|
</div>
|
|
741
761
|
</main>
|
|
742
762
|
<footer>
|
|
743
|
-
|
|
763
|
+
${redactionLabel(redacted)} lineage rendered by agents-cli · <code>agents sessions trace --tree</code>
|
|
744
764
|
</footer>
|
|
745
765
|
<script>${THEME_SCRIPT}</script>
|
|
746
766
|
<script>${LINEAGE_SCRIPT}</script>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { formatTokenCount } from './render.js';
|
|
13
13
|
const DEFAULT_MAX_STEPS = 80;
|
|
14
14
|
const LABEL_COL = 40;
|
|
15
|
-
/** Precise, compact per-step duration: "8m04s", "1.6s", "320ms", "—" for zero. */
|
|
15
|
+
/** Precise, compact per-step duration: "2h01m", "8m04s", "1.6s", "320ms", "—" for zero. */
|
|
16
16
|
function dur(ms, estimated) {
|
|
17
17
|
if (ms <= 0)
|
|
18
18
|
return estimated ? '~0s' : '0s';
|
|
@@ -23,7 +23,14 @@ function dur(ms, estimated) {
|
|
|
23
23
|
s = `${(ms / 1000).toFixed(ms < 10_000 ? 1 : 0)}s`;
|
|
24
24
|
else {
|
|
25
25
|
const totalSec = Math.round(ms / 1000);
|
|
26
|
-
|
|
26
|
+
// Beyond an hour, roll into hours so an overnight stall reads "24h01m", not
|
|
27
|
+
// "1441m18s" (matches the HTML renderer's formatStepDuration).
|
|
28
|
+
if (totalSec >= 3600) {
|
|
29
|
+
s = `${Math.floor(totalSec / 3600)}h${String(Math.floor((totalSec % 3600) / 60)).padStart(2, '0')}m`;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
s = `${Math.floor(totalSec / 60)}m${String(totalSec % 60).padStart(2, '0')}s`;
|
|
33
|
+
}
|
|
27
34
|
}
|
|
28
35
|
return estimated ? `~${s}` : s;
|
|
29
36
|
}
|
|
@@ -32,10 +32,12 @@ const INLINE_TASK_TOOLS = new Set(['Task', 'Agent']);
|
|
|
32
32
|
/**
|
|
33
33
|
* Shell tools whose command is worth resolving to an effective program — every
|
|
34
34
|
* harness's shell-exec tool, kept in lockstep with the canonical set in
|
|
35
|
-
* `state.ts` (`['Bash', 'exec_command', 'run_shell_command', 'shell', 'Execute']`)
|
|
36
|
-
* so Codex's `exec_command` and the rest are covered, not just Claude's `Bash`.
|
|
35
|
+
* `state.ts` (`['Bash', 'exec_command', 'exec', 'run_shell_command', 'shell', 'Execute']`)
|
|
36
|
+
* so Codex's `exec_command`/`exec` and the rest are covered, not just Claude's `Bash`.
|
|
37
|
+
* `exec` is newer Codex (gpt-5.6-sol) whose real command the parser unwraps from a
|
|
38
|
+
* JS cell into the event's `command` before this runs.
|
|
37
39
|
*/
|
|
38
|
-
const SHELL_TOOLS = new Set(['Bash', 'exec_command', 'run_shell_command', 'shell', 'Execute']);
|
|
40
|
+
const SHELL_TOOLS = new Set(['Bash', 'exec_command', 'exec', 'run_shell_command', 'shell', 'Execute']);
|
|
39
41
|
/**
|
|
40
42
|
* Shell builtins/assignments that are rarely the POINT of a command — the action
|
|
41
43
|
* is whatever runs after them (`export X=Y; git push` → `git`, `cd dir && bun test`
|
|
@@ -83,6 +85,52 @@ function stringArg(args, ...keys) {
|
|
|
83
85
|
}
|
|
84
86
|
return undefined;
|
|
85
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Strip a shell command's leading throwaway statements so its label reads by what
|
|
90
|
+
* it DID, not by its setup. Real agent commands overwhelmingly open with a
|
|
91
|
+
* `cd <repo> &&` (or a multi-line script whose first line is `cd <repo>`), plus the
|
|
92
|
+
* occasional `export X=Y;`, `set -e`, `source …`, or bare `VAR=val` assignment —
|
|
93
|
+
* exactly the {@link SHELL_NOISE_PROGRAMS} the badge already skips when resolving
|
|
94
|
+
* the effective program. Without this, every `cd /long/path && git …` row renders
|
|
95
|
+
* an identical `cd /long/path` and the trajectory is unreadable; here we drop that
|
|
96
|
+
* prefix so the label agrees with the badge (`git …`, `gh …`, `bun …`).
|
|
97
|
+
*
|
|
98
|
+
* Conservative on purpose: only a LEADING run of noise statements separated by
|
|
99
|
+
* `&&`, `;`, or a newline is removed (an assignment PREFIX like `VAR=val cmd` too),
|
|
100
|
+
* pipelines (`|`) are left intact, and a command that is nothing but noise (a lone
|
|
101
|
+
* `cd dir`) is returned unchanged rather than emptied. The separator match is a
|
|
102
|
+
* plain negated class, not a shell tokenizer, so a separator INSIDE a quoted arg
|
|
103
|
+
* (`cd "a; b" && x`) would cut mid-quote — guarded by refusing any strip that
|
|
104
|
+
* leaves the label with unbalanced quotes and falling back to the whole command.
|
|
105
|
+
* Display-only — the value is still clipped and secret-redacted downstream.
|
|
106
|
+
*/
|
|
107
|
+
function quotesBalanced(text) {
|
|
108
|
+
const dq = (text.match(/(?<!\\)"/g) ?? []).length;
|
|
109
|
+
const sq = (text.match(/(?<!\\)'/g) ?? []).length;
|
|
110
|
+
return dq % 2 === 0 && sq % 2 === 0;
|
|
111
|
+
}
|
|
112
|
+
function stripLeadingShellNoise(command) {
|
|
113
|
+
// A leading `cd`/`export`/`set`/`source`/`unset`/`local`/`eval` statement, or an
|
|
114
|
+
// assignment, terminated by a statement separator (`&&`, `;`, or newline).
|
|
115
|
+
const noiseStatement = /^\s*(?:cd|export|set|source|unset|local|eval)\b[^\n&;|]*(?:&&|;|\n)+/i;
|
|
116
|
+
// A bare `VAR=val` — either a full statement (with a separator) or a prefix
|
|
117
|
+
// (followed by whitespace before the real program: `FOO=bar git push`).
|
|
118
|
+
const assignment = /^\s*[A-Za-z_][A-Za-z0-9_]*=[^\s&;|]*(?:\s*(?:&&|;|\n)+|\s+)/;
|
|
119
|
+
let cmd = command;
|
|
120
|
+
let prev = '';
|
|
121
|
+
while (cmd !== prev) {
|
|
122
|
+
prev = cmd;
|
|
123
|
+
cmd = cmd.replace(noiseStatement, '').replace(assignment, '');
|
|
124
|
+
}
|
|
125
|
+
const trimmed = cmd.trim();
|
|
126
|
+
if (trimmed.length === 0)
|
|
127
|
+
return command.trim();
|
|
128
|
+
// A strip that severed a quoted argument (a separator lived inside quotes) leaves
|
|
129
|
+
// a dangling quote — keep the whole command rather than show a mangled fragment.
|
|
130
|
+
if (!quotesBalanced(trimmed) && quotesBalanced(command))
|
|
131
|
+
return command.trim();
|
|
132
|
+
return trimmed;
|
|
133
|
+
}
|
|
86
134
|
/**
|
|
87
135
|
* A redacted one-line label for a tool call, derived from the raw arguments.
|
|
88
136
|
*
|
|
@@ -94,7 +142,8 @@ function toolLabel(tool, args, command, redact, knownSecrets) {
|
|
|
94
142
|
let raw;
|
|
95
143
|
const lower = tool.toLowerCase();
|
|
96
144
|
if (lower === 'bash' || lower === 'shell' || lower === 'run_command' || lower.includes('exec')) {
|
|
97
|
-
|
|
145
|
+
const cmd = command ?? stringArg(args, 'command', 'cmd', 'script');
|
|
146
|
+
raw = cmd ? stripLeadingShellNoise(cmd) : cmd;
|
|
98
147
|
}
|
|
99
148
|
else if (lower === 'read' || lower === 'edit' || lower === 'write' || lower === 'notebookedit' || lower === 'multiedit') {
|
|
100
149
|
raw = stringArg(args, 'file_path', 'path', 'notebook_path', 'filePath');
|
|
@@ -247,6 +247,21 @@ export interface SessionMeta {
|
|
|
247
247
|
* `isTeamOrigin` (which marks sessions spawned BY a team).
|
|
248
248
|
*/
|
|
249
249
|
spawnedTeam?: string;
|
|
250
|
+
/**
|
|
251
|
+
* Fan-out this session left behind, captured at scan time so the preview can
|
|
252
|
+
* show it WITHOUT re-parsing the transcript — which is the only way a remote
|
|
253
|
+
* or unindexed row (rendered from `SessionMeta` alone) can show it at all.
|
|
254
|
+
*
|
|
255
|
+
* `undefined` and `0` are different claims and must render the same way (the
|
|
256
|
+
* segment omitted): `undefined` means the row predates the field or the
|
|
257
|
+
* harness cannot report it, `0` means scanned and none found. Rendering a
|
|
258
|
+
* literal "0 background shells" for a harness that cannot report them would
|
|
259
|
+
* assert "none running" where the truth is "unknown".
|
|
260
|
+
*
|
|
261
|
+
* Counts are "started", never "still running" — see `extractBackgroundShells`.
|
|
262
|
+
*/
|
|
263
|
+
subAgentCount?: number;
|
|
264
|
+
backgroundShellCount?: number;
|
|
250
265
|
/**
|
|
251
266
|
* The plan markdown from the LAST `ExitPlanMode` tool call in the transcript
|
|
252
267
|
* (Claude sessions only), captured at scan time. Present whenever the session
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sha256 helpers for verifying downloaded release assets.
|
|
3
|
+
*
|
|
4
|
+
* These live in their own LEAF module — importing only `node:crypto` and
|
|
5
|
+
* `node:fs` — on purpose. They used to sit in `computer/ssh-tunnel.ts`, whose
|
|
6
|
+
* own import graph reaches `browser/drivers/ssh.ts` -> `browser/chrome.ts` ->
|
|
7
|
+
* `secrets/*`. `helper-download.ts` needs nothing from that graph but these two
|
|
8
|
+
* pure functions, and importing them from there closed a module-initialization
|
|
9
|
+
* cycle:
|
|
10
|
+
*
|
|
11
|
+
* helper-download.ts
|
|
12
|
+
* -> computer/ssh-tunnel.ts (evaluated BEFORE `EXPECTED_TEAM_ID`)
|
|
13
|
+
* -> browser/drivers/ssh.ts -> browser/chrome.ts
|
|
14
|
+
* -> secrets/bundles.ts -> secrets/index.ts -> secrets/install-helper.ts
|
|
15
|
+
* -> secrets/download-keychain.ts
|
|
16
|
+
* -> helper-download.ts (still evaluating; const not yet bound)
|
|
17
|
+
*
|
|
18
|
+
* which threw `ReferenceError: Cannot access 'EXPECTED_TEAM_ID' before
|
|
19
|
+
* initialization` at `secrets/download-keychain.ts:45` for any entry point that
|
|
20
|
+
* reached `helper-download.ts` first (RUSH-3113). Keep this module a leaf: adding
|
|
21
|
+
* a local import here can reintroduce that cycle.
|
|
22
|
+
*/
|
|
23
|
+
/** Pull the digest out of a `<sha256> <filename>` release asset. */
|
|
24
|
+
export declare function parseSha256Asset(text: string): string;
|
|
25
|
+
/** Stream a file through sha256 — the exe is ~157MB, never read it whole. */
|
|
26
|
+
export declare function sha256File(file: string): Promise<string>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sha256 helpers for verifying downloaded release assets.
|
|
3
|
+
*
|
|
4
|
+
* These live in their own LEAF module — importing only `node:crypto` and
|
|
5
|
+
* `node:fs` — on purpose. They used to sit in `computer/ssh-tunnel.ts`, whose
|
|
6
|
+
* own import graph reaches `browser/drivers/ssh.ts` -> `browser/chrome.ts` ->
|
|
7
|
+
* `secrets/*`. `helper-download.ts` needs nothing from that graph but these two
|
|
8
|
+
* pure functions, and importing them from there closed a module-initialization
|
|
9
|
+
* cycle:
|
|
10
|
+
*
|
|
11
|
+
* helper-download.ts
|
|
12
|
+
* -> computer/ssh-tunnel.ts (evaluated BEFORE `EXPECTED_TEAM_ID`)
|
|
13
|
+
* -> browser/drivers/ssh.ts -> browser/chrome.ts
|
|
14
|
+
* -> secrets/bundles.ts -> secrets/index.ts -> secrets/install-helper.ts
|
|
15
|
+
* -> secrets/download-keychain.ts
|
|
16
|
+
* -> helper-download.ts (still evaluating; const not yet bound)
|
|
17
|
+
*
|
|
18
|
+
* which threw `ReferenceError: Cannot access 'EXPECTED_TEAM_ID' before
|
|
19
|
+
* initialization` at `secrets/download-keychain.ts:45` for any entry point that
|
|
20
|
+
* reached `helper-download.ts` first (RUSH-3113). Keep this module a leaf: adding
|
|
21
|
+
* a local import here can reintroduce that cycle.
|
|
22
|
+
*/
|
|
23
|
+
import { createHash } from 'node:crypto';
|
|
24
|
+
import * as fs from 'node:fs';
|
|
25
|
+
/** Pull the digest out of a `<sha256> <filename>` release asset. */
|
|
26
|
+
export function parseSha256Asset(text) {
|
|
27
|
+
const m = text.trim().match(/^([A-Fa-f0-9]{64})(\s|$)/);
|
|
28
|
+
if (!m)
|
|
29
|
+
throw new Error(`malformed .sha256 release asset: ${JSON.stringify(text.slice(0, 80))}`);
|
|
30
|
+
return m[1].toLowerCase();
|
|
31
|
+
}
|
|
32
|
+
/** Stream a file through sha256 — the exe is ~157MB, never read it whole. */
|
|
33
|
+
export function sha256File(file) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const hash = createHash('sha256');
|
|
36
|
+
fs.createReadStream(file)
|
|
37
|
+
.on('error', reject)
|
|
38
|
+
.on('data', (d) => hash.update(d))
|
|
39
|
+
.on('end', () => resolve(hash.digest('hex')));
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShareBackend — the token-source seam for `agents artifacts share`.
|
|
3
|
+
*
|
|
4
|
+
* Two legitimate principals, picked once at publish time:
|
|
5
|
+
* - **managed**: the caller is signed in to Phoenix (`readSession()`), no
|
|
6
|
+
* explicit BYO override. Token is the Phoenix access_token; endpoint is
|
|
7
|
+
* the already-live `share.agents-cli.sh`. Zero Cloudflare setup.
|
|
8
|
+
* - **byo**: the existing static WRITE_TOKEN path (`readShareConfig()` +
|
|
9
|
+
* `readWriteToken()`). Unchanged for power users / custom domains / fleet
|
|
10
|
+
* injection of `SHARE_WRITE_TOKEN`.
|
|
11
|
+
*
|
|
12
|
+
* Dispatch is not a fallback: signed-in AND no BYO override → managed;
|
|
13
|
+
* otherwise BYO. Fail loud when neither principal can authenticate.
|
|
14
|
+
*
|
|
15
|
+
* Explicit BYO override (any one is enough):
|
|
16
|
+
* - `opts.byo === true`
|
|
17
|
+
* - `opts.writeToken` (a caller-supplied static token, including tests)
|
|
18
|
+
* - `AGENTS_SHARE_BACKEND=byo`
|
|
19
|
+
*/
|
|
20
|
+
import { type PhoenixSession } from '../identity/client.js';
|
|
21
|
+
import { type ShareConfig } from './config.js';
|
|
22
|
+
export type ShareBackendKind = 'managed' | 'byo';
|
|
23
|
+
export interface ShareBackend {
|
|
24
|
+
kind: ShareBackendKind;
|
|
25
|
+
/** Public base URL of the share Worker, no trailing slash. */
|
|
26
|
+
baseUrl: string;
|
|
27
|
+
/** Bearer sent as `Authorization`. Phoenix access_token or WRITE_TOKEN. */
|
|
28
|
+
token: string;
|
|
29
|
+
/** URL namespace prefix (`<namespace>/<slug>`). Phoenix handle (email local-part) or GitHub username. */
|
|
30
|
+
namespace: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ResolveShareBackendOpts {
|
|
33
|
+
/** Force the BYO Cloudflare path even when signed in. */
|
|
34
|
+
byo?: boolean;
|
|
35
|
+
/** Override the namespace (BYO: GitHub username; ignored on managed — the
|
|
36
|
+
* worker 403s a Phoenix PUT whose first segment is not the verified handle). */
|
|
37
|
+
githubUser?: string;
|
|
38
|
+
/** DI seam — a static write token selects BYO. */
|
|
39
|
+
writeToken?: string;
|
|
40
|
+
/** DI seam — persisted BYO endpoint config. */
|
|
41
|
+
config?: ShareConfig;
|
|
42
|
+
/** DI seam — override `readSession()`. `null` means signed out. */
|
|
43
|
+
session?: PhoenixSession | null;
|
|
44
|
+
/**
|
|
45
|
+
* When false, BYO may resolve without a WRITE_TOKEN. Public GET routes
|
|
46
|
+
* (list / revisions / status) don't need one; publish and delete do.
|
|
47
|
+
* Default true.
|
|
48
|
+
*/
|
|
49
|
+
requireToken?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** Env var that forces the BYO path. Value must be exactly `byo`. */
|
|
52
|
+
export declare const SHARE_BACKEND_ENV = "AGENTS_SHARE_BACKEND";
|
|
53
|
+
/** URL-safe namespace: lowercase `[a-z0-9-]+`. Matches the Worker's sanitize.
|
|
54
|
+
*
|
|
55
|
+
* This is URL-safety, not collision-resistance: two values that differ only
|
|
56
|
+
* in case or punctuation share a prefix. */
|
|
57
|
+
export declare function sanitizeShareNamespace(s: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* Public managed-share handle from a verified email.
|
|
60
|
+
*
|
|
61
|
+
* `muqsitnawaz@gmail.com` → `muqsitnawaz`. Plus-tags are dropped
|
|
62
|
+
* (`muqsitnawaz+dev@gmail.com` → `muqsitnawaz`) so the URL stays the
|
|
63
|
+
* account's default name. Empty / missing email → `''` (caller falls
|
|
64
|
+
* back to userId). Must match the Worker `handleFromEmail`.
|
|
65
|
+
*/
|
|
66
|
+
export declare function handleFromEmail(email: string | undefined): string;
|
|
67
|
+
/** Managed URL namespace: email handle, else sanitized userId (never empty if either is set). */
|
|
68
|
+
export declare function managedShareHandle(session: Pick<PhoenixSession, 'email' | 'userId'>): string;
|
|
69
|
+
export declare function managedShareBaseUrl(): string;
|
|
70
|
+
/**
|
|
71
|
+
* True when this config is the platform managed endpoint (`share.agents-cli.sh`).
|
|
72
|
+
* The deploy path uses this — not "the caller is signed in" — to decide whether
|
|
73
|
+
* to bind `PHOENIX_ID_BASE` on the Worker.
|
|
74
|
+
*/
|
|
75
|
+
export declare function isManagedShareEndpoint(cfg: {
|
|
76
|
+
baseUrl?: string;
|
|
77
|
+
domain?: string;
|
|
78
|
+
}): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Phoenix ID URL to bind on a managed Worker deploy. `undefined` for a
|
|
81
|
+
* pure-BYO Worker (WRITE_TOKEN only). Fail loud if this is a managed deploy
|
|
82
|
+
* but the base is empty — never skip-set a blank secret.
|
|
83
|
+
*/
|
|
84
|
+
export declare function phoenixIdBaseForDeploy(opts?: {
|
|
85
|
+
managed?: boolean;
|
|
86
|
+
}, cfg?: {
|
|
87
|
+
baseUrl?: string;
|
|
88
|
+
domain?: string;
|
|
89
|
+
}): string | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* Pick the principal. Signed-in (`readSession() != null`) AND no explicit BYO
|
|
92
|
+
* override → managed; otherwise BYO.
|
|
93
|
+
*/
|
|
94
|
+
export declare function shouldUseManaged(opts?: ResolveShareBackendOpts): boolean;
|
|
95
|
+
export declare function resolveShareBackend(opts?: ResolveShareBackendOpts): ShareBackend;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShareBackend — the token-source seam for `agents artifacts share`.
|
|
3
|
+
*
|
|
4
|
+
* Two legitimate principals, picked once at publish time:
|
|
5
|
+
* - **managed**: the caller is signed in to Phoenix (`readSession()`), no
|
|
6
|
+
* explicit BYO override. Token is the Phoenix access_token; endpoint is
|
|
7
|
+
* the already-live `share.agents-cli.sh`. Zero Cloudflare setup.
|
|
8
|
+
* - **byo**: the existing static WRITE_TOKEN path (`readShareConfig()` +
|
|
9
|
+
* `readWriteToken()`). Unchanged for power users / custom domains / fleet
|
|
10
|
+
* injection of `SHARE_WRITE_TOKEN`.
|
|
11
|
+
*
|
|
12
|
+
* Dispatch is not a fallback: signed-in AND no BYO override → managed;
|
|
13
|
+
* otherwise BYO. Fail loud when neither principal can authenticate.
|
|
14
|
+
*
|
|
15
|
+
* Explicit BYO override (any one is enough):
|
|
16
|
+
* - `opts.byo === true`
|
|
17
|
+
* - `opts.writeToken` (a caller-supplied static token, including tests)
|
|
18
|
+
* - `AGENTS_SHARE_BACKEND=byo`
|
|
19
|
+
*/
|
|
20
|
+
import { PHOENIX_ID_BASE, readSession } from '../identity/client.js';
|
|
21
|
+
import { DEFAULT_SHARE_DOMAIN, readShareConfig, readWriteToken, readWriteTokenEnv, } from './config.js';
|
|
22
|
+
/** Env var that forces the BYO path. Value must be exactly `byo`. */
|
|
23
|
+
export const SHARE_BACKEND_ENV = 'AGENTS_SHARE_BACKEND';
|
|
24
|
+
/** URL-safe namespace: lowercase `[a-z0-9-]+`. Matches the Worker's sanitize.
|
|
25
|
+
*
|
|
26
|
+
* This is URL-safety, not collision-resistance: two values that differ only
|
|
27
|
+
* in case or punctuation share a prefix. */
|
|
28
|
+
export function sanitizeShareNamespace(s) {
|
|
29
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Public managed-share handle from a verified email.
|
|
33
|
+
*
|
|
34
|
+
* `muqsitnawaz@gmail.com` → `muqsitnawaz`. Plus-tags are dropped
|
|
35
|
+
* (`muqsitnawaz+dev@gmail.com` → `muqsitnawaz`) so the URL stays the
|
|
36
|
+
* account's default name. Empty / missing email → `''` (caller falls
|
|
37
|
+
* back to userId). Must match the Worker `handleFromEmail`.
|
|
38
|
+
*/
|
|
39
|
+
export function handleFromEmail(email) {
|
|
40
|
+
if (!email)
|
|
41
|
+
return '';
|
|
42
|
+
const local = email.split('@')[0] ?? '';
|
|
43
|
+
const beforePlus = local.split('+')[0] ?? local;
|
|
44
|
+
return sanitizeShareNamespace(beforePlus);
|
|
45
|
+
}
|
|
46
|
+
/** Managed URL namespace: email handle, else sanitized userId (never empty if either is set). */
|
|
47
|
+
export function managedShareHandle(session) {
|
|
48
|
+
return handleFromEmail(session.email) || sanitizeShareNamespace(session.userId ?? '');
|
|
49
|
+
}
|
|
50
|
+
export function managedShareBaseUrl() {
|
|
51
|
+
return `https://${DEFAULT_SHARE_DOMAIN}`;
|
|
52
|
+
}
|
|
53
|
+
function shareEndpointHost(value) {
|
|
54
|
+
return value.replace(/^https?:\/\//i, '').replace(/\/+$/, '').toLowerCase();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* True when this config is the platform managed endpoint (`share.agents-cli.sh`).
|
|
58
|
+
* The deploy path uses this — not "the caller is signed in" — to decide whether
|
|
59
|
+
* to bind `PHOENIX_ID_BASE` on the Worker.
|
|
60
|
+
*/
|
|
61
|
+
export function isManagedShareEndpoint(cfg) {
|
|
62
|
+
const managedHost = DEFAULT_SHARE_DOMAIN.toLowerCase();
|
|
63
|
+
if (cfg.domain && shareEndpointHost(cfg.domain) === managedHost)
|
|
64
|
+
return true;
|
|
65
|
+
if (cfg.baseUrl && shareEndpointHost(cfg.baseUrl) === managedHost)
|
|
66
|
+
return true;
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Phoenix ID URL to bind on a managed Worker deploy. `undefined` for a
|
|
71
|
+
* pure-BYO Worker (WRITE_TOKEN only). Fail loud if this is a managed deploy
|
|
72
|
+
* but the base is empty — never skip-set a blank secret.
|
|
73
|
+
*/
|
|
74
|
+
export function phoenixIdBaseForDeploy(opts = {}, cfg) {
|
|
75
|
+
const managed = opts.managed === true || (cfg != null && isManagedShareEndpoint(cfg));
|
|
76
|
+
if (!managed)
|
|
77
|
+
return undefined;
|
|
78
|
+
const base = PHOENIX_ID_BASE.replace(/\/+$/, '').trim();
|
|
79
|
+
if (!base) {
|
|
80
|
+
throw new Error('Managed share deploy requires PHOENIX_ID_BASE so the Worker can verify Phoenix bearers. Set the PHOENIX_ID_BASE env var.');
|
|
81
|
+
}
|
|
82
|
+
return base;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Pick the principal. Signed-in (`readSession() != null`) AND no explicit BYO
|
|
86
|
+
* override → managed; otherwise BYO.
|
|
87
|
+
*/
|
|
88
|
+
export function shouldUseManaged(opts = {}) {
|
|
89
|
+
if (opts.byo === true)
|
|
90
|
+
return false;
|
|
91
|
+
if (opts.writeToken)
|
|
92
|
+
return false;
|
|
93
|
+
if ((process.env[SHARE_BACKEND_ENV] ?? '').trim().toLowerCase() === 'byo')
|
|
94
|
+
return false;
|
|
95
|
+
const session = opts.session === undefined ? readSession() : opts.session;
|
|
96
|
+
return session != null;
|
|
97
|
+
}
|
|
98
|
+
export function resolveShareBackend(opts = {}) {
|
|
99
|
+
if (shouldUseManaged(opts)) {
|
|
100
|
+
const session = opts.session === undefined ? readSession() : opts.session;
|
|
101
|
+
if (!session) {
|
|
102
|
+
throw new Error("Not signed in. Run 'agents auth login'.");
|
|
103
|
+
}
|
|
104
|
+
return resolveManagedBackend(session);
|
|
105
|
+
}
|
|
106
|
+
return resolveByoBackend(opts);
|
|
107
|
+
}
|
|
108
|
+
function resolveManagedBackend(session) {
|
|
109
|
+
if (!session.access_token) {
|
|
110
|
+
throw new Error("Not signed in. Run 'agents auth login'.");
|
|
111
|
+
}
|
|
112
|
+
const namespace = managedShareHandle(session);
|
|
113
|
+
if (!namespace) {
|
|
114
|
+
throw new Error("Signed in but the session has no email or user id. Run 'agents auth login' again.");
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
kind: 'managed',
|
|
118
|
+
baseUrl: managedShareBaseUrl(),
|
|
119
|
+
token: session.access_token,
|
|
120
|
+
namespace,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function resolveByoBackend(opts) {
|
|
124
|
+
const cfg = opts.config ?? readShareConfig();
|
|
125
|
+
if (!cfg) {
|
|
126
|
+
throw new Error("Not set up yet. Run 'agents auth login' to publish on the managed endpoint (zero Cloudflare setup), or 'agents artifacts setup' / 'agents artifacts share join' for your own Cloudflare.");
|
|
127
|
+
}
|
|
128
|
+
const token = opts.writeToken ??
|
|
129
|
+
(opts.requireToken === false ? (readWriteTokenEnv() ?? '') : readWriteToken());
|
|
130
|
+
// Namespace is the GitHub username. An explicit --github-user is sanitized
|
|
131
|
+
// here; otherwise publish fills it via resolveShareUsername (gh/git config).
|
|
132
|
+
const namespace = opts.githubUser ? sanitizeShareNamespace(opts.githubUser) : '';
|
|
133
|
+
return {
|
|
134
|
+
kind: 'byo',
|
|
135
|
+
baseUrl: cfg.baseUrl.replace(/\/+$/, ''),
|
|
136
|
+
token,
|
|
137
|
+
namespace,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -68,7 +68,7 @@ export interface DeleteShareOptions {
|
|
|
68
68
|
/** Skip deleting the target's retained revisions (`<key>/rev-*`, RUSH-2683
|
|
69
69
|
* revision retention) — default: delete them too. A republished-then-deleted
|
|
70
70
|
* share would otherwise leave its prior world-readable versions live for up
|
|
71
|
-
* to the bucket's lifecycle max-age (see docs/
|
|
71
|
+
* to the bucket's lifecycle max-age (see docs/distribution.md). */
|
|
72
72
|
keepRevisions?: boolean;
|
|
73
73
|
/** Treat an already-missing target as a no-op success instead of an error
|
|
74
74
|
* (mirrors SQL's `DROP ... IF EXISTS`). Default: missing target is an error. */
|
|
@@ -77,8 +77,12 @@ export interface DeleteShareOptions {
|
|
|
77
77
|
githubUser?: string;
|
|
78
78
|
/** DI seam for tests — override the persisted share endpoint config. */
|
|
79
79
|
config?: ShareConfig;
|
|
80
|
-
/** DI seam for tests — override the keychain-backed write token. */
|
|
80
|
+
/** DI seam for tests — override the keychain-backed write token. Selects BYO. */
|
|
81
81
|
writeToken?: string;
|
|
82
|
+
/** DI seam for tests — override `readSession()`. `null` means signed out. */
|
|
83
|
+
session?: import('../identity/client.js').PhoenixSession | null;
|
|
84
|
+
/** Force the BYO Cloudflare path even when signed in. */
|
|
85
|
+
byo?: boolean;
|
|
82
86
|
/** DI seam for tests — override the real HTTP DELETE. */
|
|
83
87
|
deleter?: DeleteFn;
|
|
84
88
|
/** DI seam for tests — override the real HTTP existence check. */
|
package/dist/lib/share/delete.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// returns `{"ok":true}` — so `{"ok":true}` alone is never proof of a takedown.
|
|
8
8
|
// Every delete here is followed by a status check that must observe 404 before
|
|
9
9
|
// the operation is reported as successful.
|
|
10
|
-
import {
|
|
10
|
+
import { resolveShareBackend } from './backend.js';
|
|
11
11
|
import { buildShareKey, resolveShareUsername } from './publish.js';
|
|
12
12
|
/**
|
|
13
13
|
* Normalize any of the three accepted target forms to the R2 key that publish
|
|
@@ -103,7 +103,7 @@ export async function deleteObject(endpoint, key, opts = {}) {
|
|
|
103
103
|
const existedBefore = before.status !== 404;
|
|
104
104
|
const r = await del(url, { authorization: `Bearer ${endpoint.token}` });
|
|
105
105
|
if (!r.ok) {
|
|
106
|
-
throw new Error(`Delete failed (${r.status}) for ${url}. Check the
|
|
106
|
+
throw new Error(`Delete failed (${r.status}) for ${url}. Check the bearer (Phoenix session or WRITE_TOKEN), or that 'agents artifacts setup' completed.`);
|
|
107
107
|
}
|
|
108
108
|
const after = await check(url);
|
|
109
109
|
const verified404 = after.status === 404;
|
|
@@ -113,13 +113,12 @@ export async function deleteObject(endpoint, key, opts = {}) {
|
|
|
113
113
|
* are gone. Throws on an unverified takedown — never reports success for an
|
|
114
114
|
* object that still resolves. */
|
|
115
115
|
export async function deleteShare(target, opts = {}) {
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
const endpoint = { baseUrl: cfg.baseUrl, token };
|
|
116
|
+
const backend = resolveShareBackend(opts);
|
|
117
|
+
const githubUser = backend.kind === 'managed'
|
|
118
|
+
? backend.namespace
|
|
119
|
+
: opts.githubUser || backend.namespace || undefined;
|
|
120
|
+
const resolved = await resolveDeleteTarget(target, { githubUser });
|
|
121
|
+
const endpoint = { baseUrl: backend.baseUrl, token: backend.token };
|
|
123
122
|
const page = await deleteObject(endpoint, resolved.key, { deleter: opts.deleter, checker: opts.checker });
|
|
124
123
|
if (!page.existedBefore) {
|
|
125
124
|
if (opts.ifExists) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Make an HTML page self-contained enough to view on share.agents-cli.sh.
|
|
3
|
+
*
|
|
4
|
+
* Chrome "Save as" (and some renderers) leave `file:///…html#section` TOC
|
|
5
|
+
* links and sibling image paths. Those work on disk and 404 on the share
|
|
6
|
+
* Worker, which only stores the one PUT body. Rewrite same-document file
|
|
7
|
+
* anchors to `#section`, and inline local image files as data URIs.
|
|
8
|
+
*
|
|
9
|
+
* https citations, data: URIs already in the page, and missing files are
|
|
10
|
+
* left alone. This is not a bundler — it only fixes what would otherwise
|
|
11
|
+
* render as a broken page after a single-file publish.
|
|
12
|
+
*/
|
|
13
|
+
/** Skip inlining a sibling that would bloat the page past a reasonable PUT. */
|
|
14
|
+
export declare const MAX_INLINE_BYTES: number;
|
|
15
|
+
/**
|
|
16
|
+
* `href="file:///tmp/page.html#purpose"` → `href="#purpose"`.
|
|
17
|
+
* Bare `file:///tmp/page.html` (no hash) becomes `href="#"`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function rewriteFileAnchors(html: string): string;
|
|
20
|
+
/** Replace local image src/href with a data URI when the file exists next to the page. */
|
|
21
|
+
export declare function inlineLocalAssets(html: string, htmlPath: string): string;
|
|
22
|
+
export declare function prepareShareHtml(html: string, htmlPath: string): string;
|