@phnx-labs/agents-cli 1.22.75 → 1.22.76

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.
Files changed (132) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/README.md +20 -8
  3. package/dist/bootstrap.js +7 -7
  4. package/dist/cli/command-registry.js +5 -0
  5. package/dist/commands/artifacts-setup.js +1 -1
  6. package/dist/commands/artifacts.js +1 -1
  7. package/dist/commands/auth.js +7 -1
  8. package/dist/commands/browser.js +104 -10
  9. package/dist/commands/commands.js +7 -6
  10. package/dist/commands/config.js +27 -4
  11. package/dist/commands/cost.js +6 -4
  12. package/dist/commands/doctor.d.ts +6 -5
  13. package/dist/commands/doctor.js +32 -274
  14. package/dist/commands/exec.d.ts +2 -0
  15. package/dist/commands/exec.js +9 -2
  16. package/dist/commands/harness.d.ts +1 -0
  17. package/dist/commands/harness.js +11 -3
  18. package/dist/commands/hooks.js +7 -6
  19. package/dist/commands/mcp.js +7 -6
  20. package/dist/commands/memory.js +7 -7
  21. package/dist/commands/monitors.js +3 -2
  22. package/dist/commands/open.d.ts +25 -12
  23. package/dist/commands/open.js +24 -10
  24. package/dist/commands/permissions.js +7 -6
  25. package/dist/commands/plugins.js +21 -17
  26. package/dist/commands/route.js +33 -16
  27. package/dist/commands/rules.js +7 -12
  28. package/dist/commands/sessions-share.js +1 -1
  29. package/dist/commands/setup-watchdog.js +2 -2
  30. package/dist/commands/setup.js +22 -1
  31. package/dist/commands/share.js +26 -10
  32. package/dist/commands/skills.js +7 -6
  33. package/dist/commands/subagents.js +7 -6
  34. package/dist/commands/sync.js +81 -10
  35. package/dist/commands/view.js +4 -1
  36. package/dist/commands/watchdog.d.ts +1 -1
  37. package/dist/commands/watchdog.js +10 -10
  38. package/dist/commands/webhook.d.ts +4 -0
  39. package/dist/commands/webhook.js +22 -4
  40. package/dist/commands/workflows.js +7 -6
  41. package/dist/lib/accounting/rotate.d.ts +3 -1
  42. package/dist/lib/accounting/rotate.js +8 -4
  43. package/dist/lib/auth-health.d.ts +2 -0
  44. package/dist/lib/auth-health.js +2 -0
  45. package/dist/lib/browser/chrome.d.ts +21 -0
  46. package/dist/lib/browser/chrome.js +60 -3
  47. package/dist/lib/browser/drivers/local.d.ts +21 -0
  48. package/dist/lib/browser/drivers/local.js +102 -9
  49. package/dist/lib/browser/profiles.d.ts +29 -1
  50. package/dist/lib/browser/profiles.js +50 -1
  51. package/dist/lib/browser/types.d.ts +18 -0
  52. package/dist/lib/config-keys.d.ts +7 -2
  53. package/dist/lib/config-keys.js +17 -2
  54. package/dist/lib/daemon/daemon.js +8 -0
  55. package/dist/lib/daemon/session-summarizer-service.d.ts +24 -0
  56. package/dist/lib/daemon/session-summarizer-service.js +39 -0
  57. package/dist/lib/daemon-services.d.ts +1 -1
  58. package/dist/lib/daemon-services.js +5 -0
  59. package/dist/lib/daemon-ticks.d.ts +2 -2
  60. package/dist/lib/daemon-ticks.js +2 -1
  61. package/dist/lib/deeplink/register.js +10 -9
  62. package/dist/lib/deeplink/url.d.ts +4 -4
  63. package/dist/lib/deeplink/url.js +4 -4
  64. package/dist/lib/device-config.js +25 -0
  65. package/dist/lib/devices/doctor-findings.d.ts +4 -4
  66. package/dist/lib/devices/doctor-findings.js +14 -8
  67. package/dist/lib/devices/registry.js +2 -0
  68. package/dist/lib/devices/stats-cache.d.ts +4 -0
  69. package/dist/lib/devices/stats-cache.js +19 -0
  70. package/dist/lib/drift-sync.d.ts +3 -1
  71. package/dist/lib/drift-sync.js +16 -5
  72. package/dist/lib/exec.d.ts +2 -0
  73. package/dist/lib/exec.js +16 -1
  74. package/dist/lib/fleet-shared-state.d.ts +8 -0
  75. package/dist/lib/heal.d.ts +4 -3
  76. package/dist/lib/heal.js +5 -4
  77. package/dist/lib/hosts/ready.d.ts +1 -1
  78. package/dist/lib/hosts/ready.js +16 -4
  79. package/dist/lib/hosts/reconnect.js +4 -2
  80. package/dist/lib/identity/client.d.ts +6 -0
  81. package/dist/lib/identity/index.d.ts +16 -0
  82. package/dist/lib/identity/index.js +25 -1
  83. package/dist/lib/profiles.d.ts +2 -0
  84. package/dist/lib/profiles.js +28 -9
  85. package/dist/lib/reconcile-and-repair.d.ts +109 -0
  86. package/dist/lib/reconcile-and-repair.js +267 -0
  87. package/dist/lib/routers.d.ts +12 -1
  88. package/dist/lib/routers.js +30 -1
  89. package/dist/lib/scheduling/routines.js +8 -2
  90. package/dist/lib/session/active.d.ts +13 -0
  91. package/dist/lib/session/db.d.ts +47 -7
  92. package/dist/lib/session/db.js +114 -12
  93. package/dist/lib/session/mirror.js +58 -0
  94. package/dist/lib/session/remote/watch.js +22 -2
  95. package/dist/lib/session/session-cache.d.ts +19 -0
  96. package/dist/lib/session/session-cache.js +46 -0
  97. package/dist/lib/session/types.d.ts +34 -0
  98. package/dist/lib/share/backend.d.ts +6 -4
  99. package/dist/lib/share/backend.js +10 -8
  100. package/dist/lib/share/config.d.ts +4 -3
  101. package/dist/lib/share/config.js +10 -1
  102. package/dist/lib/share/delete.d.ts +1 -1
  103. package/dist/lib/share/delete.js +1 -1
  104. package/dist/lib/share/html.d.ts +1 -1
  105. package/dist/lib/share/html.js +1 -1
  106. package/dist/lib/share/provision.d.ts +1 -1
  107. package/dist/lib/share/provision.js +2 -2
  108. package/dist/lib/share/publish.d.ts +23 -7
  109. package/dist/lib/share/publish.js +58 -12
  110. package/dist/lib/share/worker-template.js +221 -60
  111. package/dist/lib/startup/command-registry.js +2 -2
  112. package/dist/lib/state.d.ts +15 -0
  113. package/dist/lib/state.js +29 -7
  114. package/dist/lib/summarizer/config.d.ts +46 -0
  115. package/dist/lib/summarizer/config.js +83 -0
  116. package/dist/lib/summarizer/pass.d.ts +45 -0
  117. package/dist/lib/summarizer/pass.js +112 -0
  118. package/dist/lib/summarizer/summarize.d.ts +68 -0
  119. package/dist/lib/summarizer/summarize.js +120 -0
  120. package/dist/lib/teams/agents.d.ts +4 -3
  121. package/dist/lib/teams/agents.js +12 -4
  122. package/dist/lib/teams/scheduler.d.ts +4 -2
  123. package/dist/lib/teams/scheduler.js +6 -6
  124. package/dist/lib/tmux/session.d.ts +2 -0
  125. package/dist/lib/tmux/session.js +7 -1
  126. package/dist/lib/types.d.ts +20 -0
  127. package/dist/lib/verbs.d.ts +23 -0
  128. package/dist/lib/verbs.js +24 -0
  129. package/dist/lib/view-types.d.ts +4 -0
  130. package/dist/lib/watchdog/rotate.d.ts +1 -1
  131. package/dist/lib/watchdog/rotate.js +1 -1
  132. package/package.json +1 -1
@@ -17,7 +17,16 @@ export const SHARE_TOKEN_ENV_KEY = 'SHARE_WRITE_TOKEN';
17
17
  export const DEFAULT_CF_BUNDLE = 'cloudflare';
18
18
  export const DEFAULT_WORKER_NAME = 'agents-share';
19
19
  export const DEFAULT_BUCKET_NAME = 'agents-share';
20
- export const DEFAULT_SHARE_DOMAIN = 'share.agents-cli.sh';
20
+ // The Rush product share endpoint (PHNX-3960): share.getrush.ai on the product
21
+ // domain. Publishing prints this host; bound to the `agents-share` Worker in the
22
+ // Phoenix CF account. share.agents-cli.sh + artifacts.prix.dev stay bound as legacy aliases.
23
+ export const DEFAULT_SHARE_DOMAIN = 'share.getrush.ai';
24
+ // Managed hosts still recognized as "the platform endpoint" for backward
25
+ // compatibility. `share.agents-cli.sh` is the pre-PHNX-3960 default: it stays
26
+ // attached to the same Worker, so every link already shared on it keeps
27
+ // resolving, and a persisted config pointing at it is still treated as managed
28
+ // (e.g. for deploy-time PHOENIX_ID_BASE binding). Compared lowercased.
29
+ export const LEGACY_MANAGED_SHARE_DOMAINS = ['share.agents-cli.sh', 'artifacts.prix.dev'];
21
30
  /** The write token may be injected ephemerally into fleet/cloud agents. */
22
31
  export function readWriteTokenEnv(env = process.env) {
23
32
  const token = env[SHARE_TOKEN_ENV_KEY]?.trim();
@@ -31,7 +31,7 @@ export interface ResolvedShareTarget {
31
31
  /**
32
32
  * Normalize any of the three accepted target forms to the R2 key that publish
33
33
  * would have written:
34
- * - a full share URL: `https://share.agents-cli.sh/<user>/<slug>`
34
+ * - a full share URL: `https://share.getrush.ai/<user>/<slug>`
35
35
  * - `<user>/<slug>`
36
36
  * - a bare `<slug>` — resolved against the caller's own namespace exactly as
37
37
  * `publishToEndpoint` resolves it at publish time (resolveShareUsername +
@@ -13,7 +13,7 @@ import { extractShareHttpError, formatShareHttpErrorDetail } from './http-error.
13
13
  /**
14
14
  * Normalize any of the three accepted target forms to the R2 key that publish
15
15
  * would have written:
16
- * - a full share URL: `https://share.agents-cli.sh/<user>/<slug>`
16
+ * - a full share URL: `https://share.getrush.ai/<user>/<slug>`
17
17
  * - `<user>/<slug>`
18
18
  * - a bare `<slug>` — resolved against the caller's own namespace exactly as
19
19
  * `publishToEndpoint` resolves it at publish time (resolveShareUsername +
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Make an HTML page self-contained enough to view on share.agents-cli.sh.
2
+ * Make an HTML page self-contained enough to view on share.getrush.ai.
3
3
  *
4
4
  * Chrome "Save as" (and some renderers) leave `file:///…html#section` TOC
5
5
  * links and sibling image paths. Those work on disk and 404 on the share
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Make an HTML page self-contained enough to view on share.agents-cli.sh.
2
+ * Make an HTML page self-contained enough to view on share.getrush.ai.
3
3
  *
4
4
  * Chrome "Save as" (and some renderers) leave `file:///…html#section` TOC
5
5
  * links and sibling image paths. Those work on disk and 404 on the share
@@ -110,6 +110,6 @@ export declare function setWorkerSecret(apiToken: string, accountId: string, wor
110
110
  export declare function enableWorkersDev(apiToken: string, accountId: string, workerName: string, opts?: ProvisionOptions): Promise<string>;
111
111
  /** Resolve a zone id for a domain the token can see, or null if not owned/visible. */
112
112
  export declare function findZoneId(apiToken: string, domain: string, opts?: ProvisionOptions): Promise<string | null>;
113
- /** Map a custom hostname (e.g. `share.agents-cli.sh`) to the Worker via Workers Custom Domains. */
113
+ /** Map a custom hostname (e.g. `share.getrush.ai`) to the Worker via Workers Custom Domains. */
114
114
  export declare function addCustomDomain(apiToken: string, accountId: string, workerName: string, zoneId: string, hostname: string, opts?: ProvisionOptions): Promise<void>;
115
115
  export {};
@@ -219,7 +219,7 @@ export async function enableWorkersDev(apiToken, accountId, workerName, opts = {
219
219
  /** Resolve a zone id for a domain the token can see, or null if not owned/visible. */
220
220
  export async function findZoneId(apiToken, domain, opts = {}) {
221
221
  const request = opts.request ?? defaultCloudflareRequester;
222
- // Try the exact name, then the registrable parent (share.agents-cli.sh -> agents-cli.sh).
222
+ // Try the exact name, then the registrable parent (share.getrush.ai -> getrush.ai).
223
223
  const candidates = [domain, domain.split('.').slice(-2).join('.')];
224
224
  for (const name of candidates) {
225
225
  const zones = await request({
@@ -232,7 +232,7 @@ export async function findZoneId(apiToken, domain, opts = {}) {
232
232
  }
233
233
  return null;
234
234
  }
235
- /** Map a custom hostname (e.g. `share.agents-cli.sh`) to the Worker via Workers Custom Domains. */
235
+ /** Map a custom hostname (e.g. `share.getrush.ai`) to the Worker via Workers Custom Domains. */
236
236
  export async function addCustomDomain(apiToken, accountId, workerName, zoneId, hostname, opts = {}) {
237
237
  const request = opts.request ?? defaultCloudflareRequester;
238
238
  try {
@@ -69,8 +69,17 @@ export interface PublishOptions {
69
69
  analytics?: boolean;
70
70
  /** Override the analytics token from share config. */
71
71
  analyticsToken?: string;
72
- /** Override the GitHub username used for the URL namespace. */
72
+ /** Override the GitHub username used for the URL namespace (BYO path). */
73
73
  githubUser?: string;
74
+ /**
75
+ * Override the managed public handle (PHNX-3547). Managed publishes normally
76
+ * namespace under the email local-part; when that handle is taken — or a
77
+ * vanity namespace is wanted — pass an alternate. Sanitized to
78
+ * `[a-z0-9-]` (max 63 chars, matching the Worker); the Worker binds it with
79
+ * the same first-writer claim as a derived handle. Ignored on BYO (use
80
+ * `githubUser` there).
81
+ */
82
+ handle?: string;
74
83
  /** DI seam for tests — override the persisted share endpoint config. */
75
84
  config?: ShareConfig;
76
85
  /** DI seam for tests — override the keychain-backed write token. Selects BYO. */
@@ -209,12 +218,15 @@ export declare function resolveShareProvenance(opts?: {
209
218
  }): ShareProvenance;
210
219
  /**
211
220
  * The sharer's avatar URL, stamped so the share bar can show a real profile
212
- * picture instead of only the initials circle. We key a Gravatar on the SHA-256
213
- * of the signed-in user's lowercased email (Gravatar resolves either MD5 or
214
- * SHA-256), with `d=404` so Gravatar returns 404 for a user who has none — the
215
- * bar's `<img>` onerror then falls back to the initials circle. Only the hash
216
- * lands in public metadata, never the raw email. Returns '' when signed out
217
- * (BYO without a Phoenix session), leaving the bar on the initials circle.
221
+ * picture instead of only the initials circle. A hosted OAuth profile image
222
+ * already known to identity (PhoenixSession.avatarUrl — captured at login and
223
+ * refreshed from `/api/v1/auth/me`) wins outright; only when none exists do we
224
+ * fall back to a Gravatar keyed on the SHA-256 of the signed-in user's
225
+ * lowercased email (Gravatar resolves either MD5 or SHA-256), with `d=404` so
226
+ * Gravatar returns 404 for a user who has none — the bar's `<img>` onerror
227
+ * then falls back to the initials circle. Only the hash lands in public
228
+ * metadata, never the raw email. Returns '' when signed out (BYO without a
229
+ * Phoenix session), leaving the bar on the initials circle.
218
230
  *
219
231
  * `opts.session === null` means "explicitly signed out" (a test seam / BYO) and
220
232
  * yields ''; `undefined` reads the real persisted session.
@@ -369,4 +381,8 @@ export declare function resolveShareUsername(opts?: {
369
381
  /** Build the R2 object key from a namespace username and a slug part. */
370
382
  export declare function buildShareKey(username: string, slugPart: string): string;
371
383
  export declare function publishFile(filePath: string, opts?: PublishOptions): Promise<PublishResult>;
384
+ /** Sanitize a caller-chosen managed handle to the Worker's namespace shape.
385
+ * Returns '' when the result is empty or over the Worker's 63-char cap (the
386
+ * caller then falls back to the derived handle / errors). */
387
+ export declare function resolveManagedHandle(handle: string | undefined): string;
372
388
  export declare function publishToEndpoint(filePath: string, endpoint: PublishEndpoint, opts?: PublishOptions): Promise<PublishResult>;
@@ -129,18 +129,24 @@ export function resolveShareProvenance(opts = {}) {
129
129
  }
130
130
  /**
131
131
  * The sharer's avatar URL, stamped so the share bar can show a real profile
132
- * picture instead of only the initials circle. We key a Gravatar on the SHA-256
133
- * of the signed-in user's lowercased email (Gravatar resolves either MD5 or
134
- * SHA-256), with `d=404` so Gravatar returns 404 for a user who has none — the
135
- * bar's `<img>` onerror then falls back to the initials circle. Only the hash
136
- * lands in public metadata, never the raw email. Returns '' when signed out
137
- * (BYO without a Phoenix session), leaving the bar on the initials circle.
132
+ * picture instead of only the initials circle. A hosted OAuth profile image
133
+ * already known to identity (PhoenixSession.avatarUrl — captured at login and
134
+ * refreshed from `/api/v1/auth/me`) wins outright; only when none exists do we
135
+ * fall back to a Gravatar keyed on the SHA-256 of the signed-in user's
136
+ * lowercased email (Gravatar resolves either MD5 or SHA-256), with `d=404` so
137
+ * Gravatar returns 404 for a user who has none — the bar's `<img>` onerror
138
+ * then falls back to the initials circle. Only the hash lands in public
139
+ * metadata, never the raw email. Returns '' when signed out (BYO without a
140
+ * Phoenix session), leaving the bar on the initials circle.
138
141
  *
139
142
  * `opts.session === null` means "explicitly signed out" (a test seam / BYO) and
140
143
  * yields ''; `undefined` reads the real persisted session.
141
144
  */
142
145
  export function resolveShareAvatar(opts = {}) {
143
146
  const session = opts.session !== undefined ? opts.session : readSession();
147
+ const hosted = session?.avatarUrl?.trim();
148
+ if (hosted && /^https:\/\//i.test(hosted))
149
+ return hosted;
144
150
  const email = session?.email?.trim().toLowerCase();
145
151
  if (!email)
146
152
  return '';
@@ -553,8 +559,9 @@ export function buildShareKey(username, slugPart) {
553
559
  }
554
560
  export async function publishFile(filePath, opts = {}) {
555
561
  const backend = resolveShareBackend(opts);
562
+ const managedHandle = backend.kind === 'managed' ? requireManagedHandle(opts.handle) : '';
556
563
  const username = backend.kind === 'managed'
557
- ? backend.namespace
564
+ ? managedHandle || backend.namespace
558
565
  : await resolveShareUsername({ githubUser: opts.githubUser || backend.namespace || undefined });
559
566
  const analyticsToken = opts.analyticsToken ?? (backend.kind === 'byo' ? (opts.config ?? readShareConfig())?.analyticsToken : undefined);
560
567
  return publishToEndpoint(filePath, { baseUrl: backend.baseUrl, token: backend.token }, {
@@ -564,8 +571,28 @@ export async function publishFile(filePath, opts = {}) {
564
571
  backendKind: backend.kind,
565
572
  });
566
573
  }
574
+ /** Sanitize a caller-chosen managed handle to the Worker's namespace shape.
575
+ * Returns '' when the result is empty or over the Worker's 63-char cap (the
576
+ * caller then falls back to the derived handle / errors). */
577
+ export function resolveManagedHandle(handle) {
578
+ if (!handle)
579
+ return '';
580
+ const sanitized = sanitizeShareNamespace(handle);
581
+ return sanitized && sanitized.length <= 63 ? sanitized : '';
582
+ }
583
+ function requireManagedHandle(handle) {
584
+ const resolved = resolveManagedHandle(handle);
585
+ if (handle && !resolved) {
586
+ throw new Error(`Invalid --handle '${handle}': must sanitize to 1-63 [a-z0-9-] characters`);
587
+ }
588
+ return resolved;
589
+ }
567
590
  export async function publishToEndpoint(filePath, endpoint, opts = {}) {
568
- const username = await resolveShareUsername(opts);
591
+ // A managed publish may carry an explicit --handle: it namespaces the URL and
592
+ // rides the x-share-handle header so the Worker binds the claim to it (a
593
+ // derived handle is proven by the email; an alternate one must be declared).
594
+ const managedHandle = opts.backendKind === 'managed' ? requireManagedHandle(opts.handle) : '';
595
+ const username = managedHandle || (await resolveShareUsername(opts));
569
596
  let body = readFileSync(filePath);
570
597
  const expiresAt = resolveExpire(opts.expire);
571
598
  const visibility = resolveShareVisibility(opts);
@@ -666,6 +693,8 @@ export async function publishToEndpoint(filePath, endpoint, opts = {}) {
666
693
  assertMetadataSize(metadataPreview);
667
694
  const authHeaders = (contentType) => {
668
695
  const h = { authorization: `Bearer ${endpoint.token}`, 'content-type': contentType };
696
+ if (managedHandle)
697
+ h['x-share-handle'] = managedHandle;
669
698
  if (expiresAt)
670
699
  h['x-share-expires-at'] = expiresAt;
671
700
  h['x-share-visibility'] = visibility;
@@ -748,11 +777,28 @@ export async function publishToEndpoint(filePath, endpoint, opts = {}) {
748
777
  }
749
778
  const r = await put(pageUrl, body, authHeaders(opts.contentType ?? guessContentType(filePath)));
750
779
  if (!r.ok) {
751
- if (r.status === 409) {
752
- throw new Error(`Handle '${username}' is already claimed by another account. The public URL namespace is the email local-part; two Phoenix users cannot share it.`);
780
+ // 409 has two distinct shapes on the managed Worker: 'handle taken' (the
781
+ // caller's namespace belongs to another account) and 'publish conflict'
782
+ // (a concurrent write won the republish race — retry, per PHNX-3547).
783
+ const httpError = extractShareHttpError({ status: r.status, body: r.body, retryAfter: r.retryAfter });
784
+ if (r.status === 409 && httpError.serverMessage === 'handle taken' && opts.backendKind === 'managed') {
785
+ throw new Error(`Handle '${username}' is already claimed by another account. ` +
786
+ 'If you signed in again and got a new account id, republishing with the same email re-binds your handle automatically; ' +
787
+ 'otherwise pick a different public namespace with --handle <name>.');
788
+ }
789
+ const detail = formatShareHttpErrorDetail(httpError);
790
+ // The write-token/setup advice is only meaningful for an auth failure — a
791
+ // 413 (quota/size) or 429 (rate) rejection has nothing to do with the
792
+ // token, and 'Check the write token' there is plain wrong (PHNX-3579).
793
+ // Managed endpoints carry a Phoenix bearer, so the recovery is re-login.
794
+ let advice = '';
795
+ if (r.status === 401 || r.status === 403) {
796
+ advice =
797
+ opts.backendKind === 'managed'
798
+ ? ". Check that you're signed in — run 'agents auth login'."
799
+ : ". Check the write token, or that 'agents artifacts setup' completed.";
753
800
  }
754
- const detail = formatShareHttpErrorDetail(extractShareHttpError({ status: r.status, body: r.body, retryAfter: r.retryAfter }));
755
- throw new Error(`Publish failed (${r.status}) for ${pageUrl}${detail}. Check the write token, or that 'agents artifacts setup' completed.`);
801
+ throw new Error(`Publish failed (${r.status}) for ${pageUrl}${detail}${advice}`);
756
802
  }
757
803
  // A token-gated page is only reachable WITH its key, so the URL we hand back
758
804
  // (and store nowhere) carries it — https://<host>/<user>/<slug>?k=<token>.