@phnx-labs/agents-cli 1.22.114 → 1.22.115

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 (176) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README.md +22 -102
  3. package/dist/browser.js +0 -0
  4. package/dist/cli/command-registry.js +0 -7
  5. package/dist/commands/accounts.d.ts +3 -77
  6. package/dist/commands/accounts.js +13 -366
  7. package/dist/commands/audit.js +1 -1
  8. package/dist/commands/auth.js +0 -2
  9. package/dist/commands/browser.d.ts +53 -0
  10. package/dist/commands/browser.js +199 -9
  11. package/dist/commands/doctor.d.ts +2 -3
  12. package/dist/commands/doctor.js +0 -11
  13. package/dist/commands/events.d.ts +1 -3
  14. package/dist/commands/events.js +4 -7
  15. package/dist/commands/exec.d.ts +0 -1
  16. package/dist/commands/exec.js +2 -7
  17. package/dist/commands/feed-watch.js +52 -8
  18. package/dist/commands/focus.js +1 -1
  19. package/dist/commands/go.d.ts +0 -17
  20. package/dist/commands/go.js +2 -19
  21. package/dist/commands/logs.js +1 -1
  22. package/dist/commands/mcp.js +8 -83
  23. package/dist/commands/memory.js +4 -47
  24. package/dist/commands/message.js +4 -4
  25. package/dist/commands/plugins.js +1 -93
  26. package/dist/commands/repo.js +0 -44
  27. package/dist/commands/resume.js +4 -1
  28. package/dist/commands/secrets-passthrough.js +2 -2
  29. package/dist/commands/send.d.ts +4 -4
  30. package/dist/commands/send.js +6 -51
  31. package/dist/commands/sessions-backup-setup.js +1 -1
  32. package/dist/commands/sessions-resume.js +0 -1
  33. package/dist/commands/sessions-share.d.ts +5 -7
  34. package/dist/commands/sessions-share.js +98 -49
  35. package/dist/commands/sessions.js +1 -8
  36. package/dist/commands/setup-browser.js +18 -2
  37. package/dist/commands/setup-computer.js +20 -6
  38. package/dist/commands/setup-secrets.js +24 -4
  39. package/dist/commands/setup-terminal.d.ts +3 -0
  40. package/dist/commands/setup-terminal.js +22 -0
  41. package/dist/commands/setup.d.ts +1 -1
  42. package/dist/commands/setup.js +20 -10
  43. package/dist/commands/skills.js +0 -8
  44. package/dist/commands/ssh.d.ts +6 -0
  45. package/dist/commands/ssh.js +92 -233
  46. package/dist/commands/sync.js +14 -5
  47. package/dist/commands/teams.js +1 -1
  48. package/dist/commands/traces.js +1 -1
  49. package/dist/lib/accounts/add.d.ts +0 -5
  50. package/dist/lib/accounts/add.js +1 -7
  51. package/dist/lib/artifacts-client.d.ts +20 -0
  52. package/dist/lib/artifacts-client.js +46 -0
  53. package/dist/lib/auth-mint.js +2 -2
  54. package/dist/lib/browser/runtime-state.d.ts +55 -0
  55. package/dist/lib/browser/runtime-state.js +99 -18
  56. package/dist/lib/browser/service.js +21 -1
  57. package/dist/lib/cli-resources.js +3 -1
  58. package/dist/lib/cloud/dispatch.js +1 -1
  59. package/dist/lib/cloudflare/creds.d.ts +10 -0
  60. package/dist/lib/cloudflare/creds.js +46 -0
  61. package/dist/lib/cloudflare/provision.d.ts +35 -0
  62. package/dist/lib/cloudflare/provision.js +144 -0
  63. package/dist/lib/computer/sessions-list.d.ts +55 -0
  64. package/dist/lib/computer/sessions-list.js +168 -1
  65. package/dist/lib/daemon/daemon.js +8 -1
  66. package/dist/lib/daemon/feed-stream-service.d.ts +23 -0
  67. package/dist/lib/daemon/feed-stream-service.js +40 -0
  68. package/dist/lib/daemon-services.d.ts +1 -1
  69. package/dist/lib/daemon-services.js +5 -0
  70. package/dist/lib/devices/connect.d.ts +49 -5
  71. package/dist/lib/devices/connect.js +169 -21
  72. package/dist/lib/feed/envelope.d.ts +79 -0
  73. package/dist/lib/feed/envelope.js +23 -0
  74. package/dist/lib/feed/events.d.ts +6 -0
  75. package/dist/lib/feed/events.js +8 -0
  76. package/dist/lib/feed/hub-server.d.ts +86 -0
  77. package/dist/lib/feed/hub-server.js +334 -0
  78. package/dist/lib/feed/hub.d.ts +95 -0
  79. package/dist/lib/feed/hub.js +255 -0
  80. package/dist/lib/feed/tool-activity.d.ts +108 -0
  81. package/dist/lib/feed/tool-activity.js +313 -0
  82. package/dist/lib/feed/tools.d.ts +198 -0
  83. package/dist/lib/feed/tools.js +265 -0
  84. package/dist/lib/feed/watch.d.ts +50 -50
  85. package/dist/lib/feed/watch.js +147 -16
  86. package/dist/lib/format.d.ts +1 -1
  87. package/dist/lib/format.js +1 -1
  88. package/dist/lib/git.d.ts +0 -16
  89. package/dist/lib/git.js +0 -58
  90. package/dist/lib/helper-versions.js +1 -1
  91. package/dist/lib/hosts/remote-cmd.d.ts +51 -1
  92. package/dist/lib/hosts/remote-cmd.js +125 -8
  93. package/dist/lib/hosts/remote-cmd.test-fixture.d.ts +2 -0
  94. package/dist/lib/hosts/remote-cmd.test-fixture.js +22 -0
  95. package/dist/lib/mcp.js +17 -11
  96. package/dist/lib/probe.d.ts +4 -1
  97. package/dist/lib/probe.js +5 -2
  98. package/dist/lib/pwsh.d.ts +33 -0
  99. package/dist/lib/pwsh.js +56 -0
  100. package/dist/lib/redact.d.ts +8 -0
  101. package/dist/lib/redact.js +11 -0
  102. package/dist/lib/refresh.d.ts +6 -2
  103. package/dist/lib/refresh.js +92 -72
  104. package/dist/lib/secrets-cli.d.ts +11 -0
  105. package/dist/lib/secrets-cli.js +30 -0
  106. package/dist/lib/secrets-client.js +3 -2
  107. package/dist/lib/session/detached.d.ts +7 -0
  108. package/dist/lib/session/detached.js +29 -0
  109. package/dist/lib/session/remote/peer-stream.d.ts +24 -2
  110. package/dist/lib/session/remote/peer-stream.js +33 -6
  111. package/dist/lib/session/sync/backend.d.ts +3 -3
  112. package/dist/lib/session/sync/backend.js +3 -3
  113. package/dist/lib/session/sync/provision.d.ts +1 -1
  114. package/dist/lib/session/sync/provision.js +2 -2
  115. package/dist/lib/sessions-client.js +0 -3
  116. package/dist/lib/setup-tool-install.d.ts +3 -0
  117. package/dist/lib/setup-tool-install.js +26 -0
  118. package/dist/lib/setup-tool-status.d.ts +22 -0
  119. package/dist/lib/setup-tool-status.js +215 -0
  120. package/dist/lib/share-runtime.d.ts +11 -0
  121. package/dist/lib/share-runtime.js +63 -0
  122. package/dist/lib/smart-launch.d.ts +1 -5
  123. package/dist/lib/smart-launch.js +3 -11
  124. package/dist/lib/ssh-exec.d.ts +44 -0
  125. package/dist/lib/ssh-exec.js +119 -0
  126. package/dist/lib/startup/command-registry.d.ts +6 -4
  127. package/dist/lib/startup/command-registry.js +10 -7
  128. package/dist/lib/state.js +2 -2
  129. package/dist/lib/storage/selection.d.ts +2 -2
  130. package/dist/lib/storage/selection.js +2 -2
  131. package/dist/lib/sync-umbrella.d.ts +5 -0
  132. package/dist/lib/sync-umbrella.js +18 -10
  133. package/dist/lib/traces/backend.d.ts +1 -2
  134. package/dist/lib/traces/backend.js +1 -2
  135. package/dist/lib/traces/provision.d.ts +1 -1
  136. package/dist/lib/traces/provision.js +2 -2
  137. package/dist/lib/types.d.ts +8 -6
  138. package/package.json +2 -3
  139. package/dist/commands/artifacts-setup.d.ts +0 -53
  140. package/dist/commands/artifacts-setup.js +0 -161
  141. package/dist/commands/artifacts.d.ts +0 -18
  142. package/dist/commands/artifacts.js +0 -58
  143. package/dist/commands/attach.d.ts +0 -12
  144. package/dist/commands/attach.js +0 -86
  145. package/dist/commands/auth-mint.d.ts +0 -12
  146. package/dist/commands/auth-mint.js +0 -108
  147. package/dist/commands/reconnect.d.ts +0 -46
  148. package/dist/commands/reconnect.js +0 -115
  149. package/dist/commands/share.d.ts +0 -293
  150. package/dist/commands/share.js +0 -1424
  151. package/dist/lib/share/analytics.d.ts +0 -13
  152. package/dist/lib/share/analytics.js +0 -45
  153. package/dist/lib/share/backend.d.ts +0 -120
  154. package/dist/lib/share/backend.js +0 -176
  155. package/dist/lib/share/capture.d.ts +0 -31
  156. package/dist/lib/share/capture.js +0 -174
  157. package/dist/lib/share/config.d.ts +0 -72
  158. package/dist/lib/share/config.js +0 -211
  159. package/dist/lib/share/delete.d.ts +0 -123
  160. package/dist/lib/share/delete.js +0 -173
  161. package/dist/lib/share/html.d.ts +0 -20
  162. package/dist/lib/share/html.js +0 -88
  163. package/dist/lib/share/http-error.d.ts +0 -53
  164. package/dist/lib/share/http-error.js +0 -65
  165. package/dist/lib/share/og.d.ts +0 -26
  166. package/dist/lib/share/og.js +0 -84
  167. package/dist/lib/share/provision.d.ts +0 -127
  168. package/dist/lib/share/provision.js +0 -285
  169. package/dist/lib/share/publish.d.ts +0 -379
  170. package/dist/lib/share/publish.js +0 -818
  171. package/dist/lib/share/worker-template.d.ts +0 -27
  172. package/dist/lib/share/worker-template.js +0 -2424
  173. package/dist/lib/storage/index.d.ts +0 -14
  174. package/dist/lib/storage/index.js +0 -14
  175. package/dist/lib/storage/visibility.d.ts +0 -82
  176. package/dist/lib/storage/visibility.js +0 -99
@@ -1,65 +0,0 @@
1
- /**
2
- * ONE bounded extractor for the share Worker's error responses.
3
- *
4
- * Every share HTTP call (publish PUT, list/revisions GET, delete DELETE)
5
- * previously threw on `!ok` with only the status code, discarding the JSON error
6
- * body the Worker actually returns (`{"error":"…"}` for a 400/413/429, plus a
7
- * `Retry-After` on a 429). The result: a rate-limited or quota-exceeded publish
8
- * surfaced as a bare "Publish failed (429)" with no reason. This pulls the
9
- * server's own `error` string and preserves the status + `Retry-After`, so the
10
- * user sees why.
11
- *
12
- * Bounded and safe by construction: it parses at most {@link MAX_ERROR_BODY_BYTES}
13
- * of the body, only lifts a string `error` field, caps its length, and NEVER
14
- * surfaces an arbitrary body — a non-JSON or oversized body yields no message,
15
- * so an HTML error page or a huge payload can't leak into a CLI error string.
16
- */
17
- /** Never parse more than this many chars of a body — a legitimate `{"error":"…"}`
18
- * is tiny; a larger body is an HTML page or noise we deliberately don't surface. */
19
- export const MAX_ERROR_BODY_BYTES = 4096;
20
- /** Cap the surfaced message so even a pathological (but valid-JSON) `error`
21
- * string can't blow up the CLI output. */
22
- export const MAX_ERROR_MESSAGE_CHARS = 300;
23
- /**
24
- * Extract the bounded error facts from a share Worker response. Pulls a string
25
- * `error` field from a small JSON body, preserves status + `Retry-After`, and
26
- * never surfaces an arbitrary body.
27
- */
28
- export function extractShareHttpError(res) {
29
- const out = { status: res.status };
30
- const retryAfter = res.retryAfter?.trim();
31
- if (retryAfter)
32
- out.retryAfter = retryAfter;
33
- const raw = res.body;
34
- if (raw && raw.length <= MAX_ERROR_BODY_BYTES) {
35
- try {
36
- const parsed = JSON.parse(raw);
37
- if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
38
- const err = parsed.error;
39
- if (typeof err === 'string' && err.trim()) {
40
- out.serverMessage = err.trim().slice(0, MAX_ERROR_MESSAGE_CHARS);
41
- }
42
- }
43
- }
44
- catch {
45
- // Not JSON (an HTML error page, a proxy body, truncated bytes) — deliberately
46
- // surface nothing rather than dump an arbitrary body into the CLI error.
47
- }
48
- }
49
- return out;
50
- }
51
- /**
52
- * A one-line suffix summarizing the server's structured error, or `''` when the
53
- * response carried nothing extractable. Appended after the status-bearing prefix
54
- * a caller already builds, e.g.:
55
- * `Publish failed (429) for <url>` + `formatShareHttpErrorDetail(err)`
56
- * → `Publish failed (429) for <url> — rate limit exceeded; retry after 37s`
57
- */
58
- export function formatShareHttpErrorDetail(err) {
59
- const parts = [];
60
- if (err.serverMessage)
61
- parts.push(err.serverMessage);
62
- if (err.retryAfter)
63
- parts.push(`retry after ${err.retryAfter}s`);
64
- return parts.length ? ` — ${parts.join('; ')}` : '';
65
- }
@@ -1,26 +0,0 @@
1
- /**
2
- * Open Graph / Twitter card meta for a shared HTML page.
3
- *
4
- * `deriveMeta` pulls a title + description out of the document (its `<title>`/`<h1>`
5
- * and first real paragraph or existing `<meta name=description>`); `injectOgMeta`
6
- * splices the social tags into `<head>` (idempotent — it strips any tags we
7
- * previously added before re-inserting). Pure string work so it's unit-testable
8
- * without a browser or network.
9
- */
10
- interface OgFields {
11
- title: string;
12
- description: string;
13
- imageUrl: string;
14
- pageUrl: string;
15
- /** Actual pixel dimensions of the served image (defaults to the 1200×630 OG card). */
16
- imageWidth?: number;
17
- imageHeight?: number;
18
- }
19
- /** Pull a sensible title + description out of a plan/document's HTML. */
20
- export declare function deriveMeta(html: string, fallbackTitle?: string): {
21
- title: string;
22
- description: string;
23
- };
24
- /** Splice OG + Twitter tags into `<head>` (or prepend if there's no head). Idempotent. */
25
- export declare function injectOgMeta(html: string, f: OgFields): string;
26
- export {};
@@ -1,84 +0,0 @@
1
- /**
2
- * Open Graph / Twitter card meta for a shared HTML page.
3
- *
4
- * `deriveMeta` pulls a title + description out of the document (its `<title>`/`<h1>`
5
- * and first real paragraph or existing `<meta name=description>`); `injectOgMeta`
6
- * splices the social tags into `<head>` (idempotent — it strips any tags we
7
- * previously added before re-inserting). Pure string work so it's unit-testable
8
- * without a browser or network.
9
- */
10
- const OG_MARK_OPEN = '<!-- agents-share:og -->';
11
- const OG_MARK_CLOSE = '<!-- /agents-share:og -->';
12
- function escapeAttr(s) {
13
- return s
14
- .replace(/&/g, '&amp;')
15
- .replace(/"/g, '&quot;')
16
- .replace(/</g, '&lt;')
17
- .replace(/>/g, '&gt;');
18
- }
19
- function stripTags(s) {
20
- return s
21
- .replace(/<[^>]+>/g, ' ')
22
- .replace(/&nbsp;/g, ' ')
23
- .replace(/\s+/g, ' ')
24
- .trim();
25
- }
26
- /** Pull a sensible title + description out of a plan/document's HTML. */
27
- export function deriveMeta(html, fallbackTitle = 'agents-cli') {
28
- const titleMatch = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(html);
29
- const h1Match = /<h1[^>]*>([\s\S]*?)<\/h1>/i.exec(html);
30
- const title = stripTags(titleMatch?.[1] ?? h1Match?.[1] ?? '') || fallbackTitle;
31
- let description = '';
32
- const metaDesc = /<meta[^>]+name=["']description["'][^>]+content=["']([^"']*)["']/i.exec(html);
33
- if (metaDesc) {
34
- description = metaDesc[1].trim();
35
- }
36
- else {
37
- for (const m of html.matchAll(/<p[^>]*>([\s\S]*?)<\/p>/gi)) {
38
- const t = stripTags(m[1]);
39
- if (t.length > 40) {
40
- description = t;
41
- break;
42
- }
43
- }
44
- }
45
- if (description.length > 198)
46
- description = description.slice(0, 197).trimEnd() + '…';
47
- return { title, description };
48
- }
49
- /** Remove a previously-injected block so re-publishing doesn't stack duplicate tags. */
50
- function stripPrevious(html) {
51
- const re = new RegExp(`${OG_MARK_OPEN}[\\s\\S]*?${OG_MARK_CLOSE}\\n?`, 'g');
52
- return html.replace(re, '');
53
- }
54
- /** Splice OG + Twitter tags into `<head>` (or prepend if there's no head). Idempotent. */
55
- export function injectOgMeta(html, f) {
56
- const cleaned = stripPrevious(html);
57
- const t = escapeAttr(f.title);
58
- const d = escapeAttr(f.description);
59
- const w = f.imageWidth ?? 1200;
60
- const h = f.imageHeight ?? 630;
61
- const block = `${OG_MARK_OPEN}\n` +
62
- `<meta property="og:type" content="website">\n` +
63
- `<meta property="og:site_name" content="agents-cli">\n` +
64
- `<meta property="og:title" content="${t}">\n` +
65
- `<meta property="og:description" content="${d}">\n` +
66
- `<meta property="og:url" content="${escapeAttr(f.pageUrl)}">\n` +
67
- `<meta property="og:image" content="${escapeAttr(f.imageUrl)}">\n` +
68
- `<meta property="og:image:width" content="${w}">\n` +
69
- `<meta property="og:image:height" content="${h}">\n` +
70
- `<meta name="twitter:card" content="summary_large_image">\n` +
71
- `<meta name="twitter:title" content="${t}">\n` +
72
- `<meta name="twitter:description" content="${d}">\n` +
73
- `<meta name="twitter:image" content="${escapeAttr(f.imageUrl)}">\n` +
74
- `${OG_MARK_CLOSE}\n`;
75
- const headClose = cleaned.search(/<\/head>/i);
76
- if (headClose !== -1)
77
- return cleaned.slice(0, headClose) + block + cleaned.slice(headClose);
78
- const headOpen = /<head[^>]*>/i.exec(cleaned);
79
- if (headOpen) {
80
- const at = headOpen.index + headOpen[0].length;
81
- return cleaned.slice(0, at) + '\n' + block + cleaned.slice(at);
82
- }
83
- return block + cleaned;
84
- }
@@ -1,127 +0,0 @@
1
- import type { WorkerBundle } from './worker-template.js';
2
- export declare const SHARE_LIFECYCLE_RULE_ID = "agents-share-expire-objects";
3
- export declare const SHARE_LIFECYCLE_RETENTION_DAYS = 366;
4
- interface R2LifecycleRule {
5
- id: string;
6
- enabled: boolean;
7
- conditions: {
8
- prefix: string;
9
- };
10
- deleteObjectsTransition?: {
11
- condition: {
12
- type: 'Age';
13
- maxAge: number;
14
- } | {
15
- type: 'Date';
16
- date: string;
17
- };
18
- };
19
- abortMultipartUploadsTransition?: {
20
- condition?: {
21
- type: 'Age';
22
- maxAge: number;
23
- };
24
- };
25
- storageClassTransitions?: Array<{
26
- condition: {
27
- type: 'Age';
28
- maxAge: number;
29
- } | {
30
- type: 'Date';
31
- date: string;
32
- };
33
- storageClass: 'InfrequentAccess';
34
- }>;
35
- }
36
- export interface CloudflareRequest {
37
- apiToken: string;
38
- method: string;
39
- pathname: string;
40
- body?: unknown;
41
- form?: FormData;
42
- }
43
- export type CloudflareRequester = <T = unknown>(request: CloudflareRequest) => Promise<T>;
44
- interface ProvisionOptions {
45
- request?: CloudflareRequester;
46
- }
47
- /** Create the R2 bucket (idempotent). */
48
- export declare function createBucket(apiToken: string, accountId: string, name: string, opts?: ProvisionOptions): Promise<void>;
49
- export declare function buildShareLifecycleRule(days?: number): R2LifecycleRule;
50
- export declare function mergeShareLifecycleRule(existing?: R2LifecycleRule[], rule?: R2LifecycleRule): R2LifecycleRule[];
51
- /** Ensure the share bucket self-cleans old objects. Exact per-link expiry is enforced by the Worker. */
52
- export declare function configureBucketLifecycle(apiToken: string, accountId: string, bucketName: string, opts?: ProvisionOptions): Promise<void>;
53
- /** Upload the module Worker with an R2 binding (`BUCKET`). Secrets are set via the Workers Secrets API. */
54
- export declare function deployWorker(apiToken: string, accountId: string, workerName: string, worker: string | WorkerBundle, bucketName: string, opts?: ProvisionOptions): Promise<void>;
55
- /** sha256 of the rendered Worker script, so a deployed endpoint can be compared
56
- * against the current `worker-template.ts` without redeploying to find out. */
57
- export declare function hashWorkerScript(script: string): string;
58
- interface UpdateWorkerResult {
59
- /** sha256 of `script` (the hash the endpoint now matches, whether or not this
60
- * call actually redeployed). */
61
- templateHash: string;
62
- /** True when the upload was skipped because `previousHash` already matched. */
63
- skipped: boolean;
64
- }
65
- /** Cloudflare `secret_text` name the Worker reads as `env.PHOENIX_ID_BASE`. */
66
- export declare const WORKER_PHOENIX_ID_BASE_SECRET = "PHOENIX_ID_BASE";
67
- /** Cloudflare `secret_text` name the Worker reads as `env.PRIX_ARTIFACT_COLLAB_BASE`. */
68
- export declare const WORKER_COLLAB_BASE_SECRET = "PRIX_ARTIFACT_COLLAB_BASE";
69
- /** Cloudflare `secret_text` name the Worker reads as `env.ARTIFACT_COLLAB_SERVICE_TOKEN`. */
70
- export declare const WORKER_COLLAB_SERVICE_TOKEN_SECRET = "ARTIFACT_COLLAB_SERVICE_TOKEN";
71
- type UpdateWorkerOpts = ProvisionOptions & {
72
- force?: boolean;
73
- /**
74
- * Phoenix ID base URL to bind as `PHOENIX_ID_BASE` after the script upload.
75
- * Pass this for a managed deployment; omit it for a pure-BYO Worker (which
76
- * authenticates only via WRITE_TOKEN). An empty string is a caller bug and
77
- * fails loud — do not pass a blank to "skip".
78
- */
79
- phoenixIdBase?: string;
80
- /**
81
- * Managed collaboration backend (PHNX-3835). BOTH must be present to enable
82
- * `/__collab`; pass neither to leave the surface disabled (the Worker fails it
83
- * closed). The service token rides Cloudflare's Secrets API, never rendered
84
- * HTML or browser JS. Applied like `phoenixIdBase` — only when defined.
85
- */
86
- collabBase?: string;
87
- collabServiceToken?: string;
88
- };
89
- /**
90
- * Re-deploy the Worker script against an ALREADY-provisioned endpoint (same
91
- * account/worker/bucket `deployWorker` was first called with) and idempotently
92
- * no-op when the template hasn't changed.
93
- *
94
- * Cloudflare's script-upload endpoint (`deployWorker`, above) replaces the
95
- * Worker's bindings/secrets wholesale on every call — there is no documented,
96
- * reliable way to tell it "keep the existing secret" (the `keep_bindings`
97
- * metadata field some third-party guides mention is absent from Cloudflare's
98
- * current Multipart upload metadata reference, and the community has reported
99
- * it not preventing binding loss even when present:
100
- * https://developers.cloudflare.com/workers/configuration/multipart-upload-metadata/,
101
- * https://community.cloudflare.com/t/upload-worker-module-endpoint-removes-existing-bindings-despite-keep-bindings/766447).
102
- * So instead this immediately re-applies WRITE_TOKEN via the documented Secrets
103
- * API right after the upload — the same two-call sequence first-time
104
- * provisioning already uses (`deployWorker` then `setWorkerSecret`), except
105
- * `writeToken` here is the caller's EXISTING token, never a freshly generated
106
- * one. A managed deploy also re-applies `PHOENIX_ID_BASE` the same way (the
107
- * Worker reads it to verify Phoenix bearers); a pure-BYO deploy omits it.
108
- * Cloudflare's own docs describe exactly this "secrets survive a
109
- * subsequent write" contract for the sibling `wrangler versions upload
110
- * --secrets-file` flow ("Secrets not included in the file are preserved from
111
- * the previous version" —
112
- * https://developers.cloudflare.com/workers/configuration/secrets/#upload-secrets-alongside-code);
113
- * re-setting the secret to its own value after every deploy gets the same
114
- * outcome without depending on an unverified upload-time flag.
115
- */
116
- export declare function updateWorker(apiToken: string, accountId: string, workerName: string, bucketName: string, worker: string | WorkerBundle, writeToken: string, previousHash: string | undefined, opts?: UpdateWorkerOpts): Promise<UpdateWorkerResult>;
117
- /** Add/update a secret_text binding using Cloudflare's Workers Secrets API. */
118
- export declare function putWorkerSecret(apiToken: string, accountId: string, workerName: string, name: string, text: string, opts?: ProvisionOptions): Promise<void>;
119
- /** Add/update the WRITE_TOKEN binding using Cloudflare's Workers Secrets API. */
120
- export declare function setWorkerSecret(apiToken: string, accountId: string, workerName: string, writeToken: string, opts?: ProvisionOptions): Promise<void>;
121
- /** Enable the free `*.workers.dev` route for the script, and return the account subdomain. */
122
- export declare function enableWorkersDev(apiToken: string, accountId: string, workerName: string, opts?: ProvisionOptions): Promise<string>;
123
- /** Resolve a zone id for a domain the token can see, or null if not owned/visible. */
124
- export declare function findZoneId(apiToken: string, domain: string, opts?: ProvisionOptions): Promise<string | null>;
125
- /** Map a custom hostname (e.g. `share.getrush.ai`) to the Worker via Workers Custom Domains. */
126
- export declare function addCustomDomain(apiToken: string, accountId: string, workerName: string, zoneId: string, hostname: string, opts?: ProvisionOptions): Promise<void>;
127
- export {};
@@ -1,285 +0,0 @@
1
- // Cloudflare provisioning for `agents artifacts setup` — plain `fetch` against the CF
2
- // REST API (the repo has no CF wrapper). Creates the R2 bucket, configures its
3
- // lifecycle, uploads the Worker (with an R2 binding), sets the WRITE_TOKEN secret,
4
- // enables the free `*.workers.dev` subdomain, and — when the token owns the zone —
5
- // maps a custom domain.
6
- import { createHash } from 'node:crypto';
7
- const CF_API = 'https://api.cloudflare.com/client/v4';
8
- export const SHARE_LIFECYCLE_RULE_ID = 'agents-share-expire-objects';
9
- export const SHARE_LIFECYCLE_RETENTION_DAYS = 366;
10
- const SECONDS_PER_DAY = 86400;
11
- async function cf(apiToken, method, pathname, body, form) {
12
- const headers = { authorization: `Bearer ${apiToken}` };
13
- let payload;
14
- if (form) {
15
- payload = form; // fetch sets multipart boundary
16
- }
17
- else if (body !== undefined) {
18
- headers['content-type'] = 'application/json';
19
- payload = JSON.stringify(body);
20
- }
21
- const res = await fetch(`${CF_API}${pathname}`, { method, headers, body: payload });
22
- const json = (await res.json().catch(() => ({})));
23
- if (!res.ok || json.success === false) {
24
- const msg = (json.errors ?? []).map((e) => `${e.code ?? ''} ${e.message ?? ''}`.trim()).join('; ') ||
25
- res.statusText;
26
- throw new Error(`Cloudflare ${method} ${pathname} failed (${res.status}): ${msg}`);
27
- }
28
- return json.result;
29
- }
30
- const defaultCloudflareRequester = (request) => cf(request.apiToken, request.method, request.pathname, request.body, request.form);
31
- /** True if the CF error looks like "the thing already exists" (idempotent create). */
32
- function isAlreadyExists(e) {
33
- return /already exists|duplicate|10004|10014/i.test(String(e));
34
- }
35
- /** Create the R2 bucket (idempotent). */
36
- export async function createBucket(apiToken, accountId, name, opts = {}) {
37
- const request = opts.request ?? defaultCloudflareRequester;
38
- try {
39
- await request({
40
- apiToken,
41
- method: 'POST',
42
- pathname: `/accounts/${accountId}/r2/buckets`,
43
- body: { name },
44
- });
45
- }
46
- catch (e) {
47
- if (!isAlreadyExists(e))
48
- throw e;
49
- }
50
- }
51
- export function buildShareLifecycleRule(days = SHARE_LIFECYCLE_RETENTION_DAYS) {
52
- return {
53
- id: SHARE_LIFECYCLE_RULE_ID,
54
- enabled: true,
55
- conditions: { prefix: '' },
56
- deleteObjectsTransition: {
57
- condition: { type: 'Age', maxAge: days * SECONDS_PER_DAY },
58
- },
59
- };
60
- }
61
- export function mergeShareLifecycleRule(existing = [], rule = buildShareLifecycleRule()) {
62
- return [...existing.filter((r) => r.id !== SHARE_LIFECYCLE_RULE_ID), rule];
63
- }
64
- /** Ensure the share bucket self-cleans old objects. Exact per-link expiry is enforced by the Worker. */
65
- export async function configureBucketLifecycle(apiToken, accountId, bucketName, opts = {}) {
66
- const request = opts.request ?? defaultCloudflareRequester;
67
- const lifecycle = await request({
68
- apiToken,
69
- method: 'GET',
70
- pathname: `/accounts/${accountId}/r2/buckets/${bucketName}/lifecycle`,
71
- });
72
- await request({
73
- apiToken,
74
- method: 'PUT',
75
- pathname: `/accounts/${accountId}/r2/buckets/${bucketName}/lifecycle`,
76
- body: { rules: mergeShareLifecycleRule(lifecycle.rules ?? []) },
77
- });
78
- }
79
- /** Upload the module Worker with an R2 binding (`BUCKET`). Secrets are set via the Workers Secrets API. */
80
- export async function deployWorker(apiToken, accountId, workerName, worker, bucketName, opts = {}) {
81
- const request = opts.request ?? defaultCloudflareRequester;
82
- const metadata = {
83
- main_module: 'worker.js',
84
- compatibility_date: '2024-11-06',
85
- bindings: [
86
- { type: 'r2_bucket', name: 'BUCKET', bucket_name: bucketName },
87
- ],
88
- };
89
- const form = new FormData();
90
- form.set('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
91
- const bundle = typeof worker === 'string' ? { script: worker, modules: [] } : worker;
92
- form.set('worker.js', new Blob([bundle.script], { type: 'application/javascript+module' }), 'worker.js');
93
- for (const module of bundle.modules) {
94
- form.set(module.name, new Blob([module.contents], { type: module.contentType }), module.name);
95
- }
96
- await request({
97
- apiToken,
98
- method: 'PUT',
99
- pathname: `/accounts/${accountId}/workers/scripts/${workerName}`,
100
- form,
101
- });
102
- }
103
- /** sha256 of the rendered Worker script, so a deployed endpoint can be compared
104
- * against the current `worker-template.ts` without redeploying to find out. */
105
- export function hashWorkerScript(script) {
106
- return createHash('sha256').update(script, 'utf8').digest('hex');
107
- }
108
- /** Cloudflare `secret_text` name the Worker reads as `env.PHOENIX_ID_BASE`. */
109
- export const WORKER_PHOENIX_ID_BASE_SECRET = 'PHOENIX_ID_BASE';
110
- /** Cloudflare `secret_text` name the Worker reads as `env.PRIX_ARTIFACT_COLLAB_BASE`. */
111
- export const WORKER_COLLAB_BASE_SECRET = 'PRIX_ARTIFACT_COLLAB_BASE';
112
- /** Cloudflare `secret_text` name the Worker reads as `env.ARTIFACT_COLLAB_SERVICE_TOKEN`. */
113
- export const WORKER_COLLAB_SERVICE_TOKEN_SECRET = 'ARTIFACT_COLLAB_SERVICE_TOKEN';
114
- /**
115
- * Re-deploy the Worker script against an ALREADY-provisioned endpoint (same
116
- * account/worker/bucket `deployWorker` was first called with) and idempotently
117
- * no-op when the template hasn't changed.
118
- *
119
- * Cloudflare's script-upload endpoint (`deployWorker`, above) replaces the
120
- * Worker's bindings/secrets wholesale on every call — there is no documented,
121
- * reliable way to tell it "keep the existing secret" (the `keep_bindings`
122
- * metadata field some third-party guides mention is absent from Cloudflare's
123
- * current Multipart upload metadata reference, and the community has reported
124
- * it not preventing binding loss even when present:
125
- * https://developers.cloudflare.com/workers/configuration/multipart-upload-metadata/,
126
- * https://community.cloudflare.com/t/upload-worker-module-endpoint-removes-existing-bindings-despite-keep-bindings/766447).
127
- * So instead this immediately re-applies WRITE_TOKEN via the documented Secrets
128
- * API right after the upload — the same two-call sequence first-time
129
- * provisioning already uses (`deployWorker` then `setWorkerSecret`), except
130
- * `writeToken` here is the caller's EXISTING token, never a freshly generated
131
- * one. A managed deploy also re-applies `PHOENIX_ID_BASE` the same way (the
132
- * Worker reads it to verify Phoenix bearers); a pure-BYO deploy omits it.
133
- * Cloudflare's own docs describe exactly this "secrets survive a
134
- * subsequent write" contract for the sibling `wrangler versions upload
135
- * --secrets-file` flow ("Secrets not included in the file are preserved from
136
- * the previous version" —
137
- * https://developers.cloudflare.com/workers/configuration/secrets/#upload-secrets-alongside-code);
138
- * re-setting the secret to its own value after every deploy gets the same
139
- * outcome without depending on an unverified upload-time flag.
140
- */
141
- export async function updateWorker(apiToken, accountId, workerName, bucketName, worker, writeToken, previousHash, opts = {}) {
142
- const script = typeof worker === 'string' ? worker : worker.script;
143
- const templateHash = hashWorkerScript(script);
144
- if (!opts.force && previousHash === templateHash) {
145
- // Script is current so we skip the upload (which would wipe secrets). A
146
- // managed Worker still needs PHOENIX_ID_BASE present — a prior upload from
147
- // a CLI that never set it left the secret missing, and this is the heal
148
- // that does not require --force or a dummy template bump.
149
- if (opts.phoenixIdBase !== undefined) {
150
- await applyPhoenixIdBaseSecret(apiToken, accountId, workerName, opts.phoenixIdBase, opts);
151
- }
152
- await applyCollabSecrets(apiToken, accountId, workerName, opts);
153
- return { templateHash, skipped: true };
154
- }
155
- await deployWorker(apiToken, accountId, workerName, worker, bucketName, opts);
156
- // Script upload clears bindings/secrets (see JSDoc above). If re-applying
157
- // WRITE_TOKEN fails here, the live Worker has no write token — every
158
- // `agents artifacts share` publish/delete 401s until a re-run of `agents artifacts share update`
159
- // completes both steps. Surface that explicitly instead of the raw CF error.
160
- try {
161
- await setWorkerSecret(apiToken, accountId, workerName, writeToken, opts);
162
- }
163
- catch (e) {
164
- const detail = e instanceof Error ? e.message : String(e);
165
- throw new Error(`Worker deployed but the write token failed to re-apply — re-run \`agents artifacts share update\` to fix this before publishing/deleting anything. (${detail})`);
166
- }
167
- if (opts.phoenixIdBase !== undefined) {
168
- try {
169
- await applyPhoenixIdBaseSecret(apiToken, accountId, workerName, opts.phoenixIdBase, opts);
170
- }
171
- catch (e) {
172
- const detail = e instanceof Error ? e.message : String(e);
173
- throw new Error(`Worker deployed but PHOENIX_ID_BASE failed to re-apply — Phoenix-bearer publishes will 401 until you re-run \`agents artifacts share update\`. (${detail})`);
174
- }
175
- }
176
- // The script upload wiped bindings/secrets (see JSDoc above), so re-apply the
177
- // collaboration secrets too — but only when the caller provided them. An
178
- // ordinary deploy with collaboration still dormant passes neither and the
179
- // Worker keeps failing `/__collab` closed.
180
- try {
181
- await applyCollabSecrets(apiToken, accountId, workerName, opts);
182
- }
183
- catch (e) {
184
- const detail = e instanceof Error ? e.message : String(e);
185
- throw new Error(`Worker deployed but the collaboration secrets failed to re-apply — \`/__collab\` stays disabled until you re-run \`agents artifacts share update\`. (${detail})`);
186
- }
187
- return { templateHash, skipped: false };
188
- }
189
- /**
190
- * Bind the managed collaboration secrets (PHNX-3835) — both or neither. A no-op
191
- * when the caller passed neither (collaboration dormant), so a plain managed
192
- * deploy never touches them; fails loud on a half-config (one without the other).
193
- */
194
- async function applyCollabSecrets(apiToken, accountId, workerName, opts) {
195
- if (opts.collabBase === undefined && opts.collabServiceToken === undefined)
196
- return;
197
- if (opts.collabBase === undefined || opts.collabServiceToken === undefined) {
198
- throw new Error('Managed collaboration needs BOTH PRIX_ARTIFACT_COLLAB_BASE and ARTIFACT_COLLAB_SERVICE_TOKEN — set both or neither.');
199
- }
200
- await putWorkerSecret(apiToken, accountId, workerName, WORKER_COLLAB_BASE_SECRET, opts.collabBase, opts);
201
- await putWorkerSecret(apiToken, accountId, workerName, WORKER_COLLAB_SERVICE_TOKEN_SECRET, opts.collabServiceToken, opts);
202
- }
203
- /** Add/update a secret_text binding using Cloudflare's Workers Secrets API. */
204
- export async function putWorkerSecret(apiToken, accountId, workerName, name, text, opts = {}) {
205
- const request = opts.request ?? defaultCloudflareRequester;
206
- await request({
207
- apiToken,
208
- method: 'PUT',
209
- pathname: `/accounts/${accountId}/workers/scripts/${workerName}/secrets`,
210
- body: { name, text, type: 'secret_text' },
211
- });
212
- }
213
- /** Add/update the WRITE_TOKEN binding using Cloudflare's Workers Secrets API. */
214
- export async function setWorkerSecret(apiToken, accountId, workerName, writeToken, opts = {}) {
215
- await putWorkerSecret(apiToken, accountId, workerName, 'WRITE_TOKEN', writeToken, opts);
216
- }
217
- function normalizePhoenixIdBase(phoenixIdBase) {
218
- const base = phoenixIdBase.replace(/\/+$/, '').trim();
219
- if (!base) {
220
- throw new Error('Managed share deploy requires a non-empty PHOENIX_ID_BASE so the Worker can verify Phoenix bearers. Set the PHOENIX_ID_BASE env var.');
221
- }
222
- return base;
223
- }
224
- async function applyPhoenixIdBaseSecret(apiToken, accountId, workerName, phoenixIdBase, opts = {}) {
225
- await putWorkerSecret(apiToken, accountId, workerName, WORKER_PHOENIX_ID_BASE_SECRET, normalizePhoenixIdBase(phoenixIdBase), opts);
226
- }
227
- /** Enable the free `*.workers.dev` route for the script, and return the account subdomain. */
228
- export async function enableWorkersDev(apiToken, accountId, workerName, opts = {}) {
229
- const request = opts.request ?? defaultCloudflareRequester;
230
- await request({
231
- apiToken,
232
- method: 'POST',
233
- pathname: `/accounts/${accountId}/workers/scripts/${workerName}/subdomain`,
234
- body: {
235
- enabled: true,
236
- previews_enabled: false,
237
- },
238
- });
239
- const sub = await request({
240
- apiToken,
241
- method: 'GET',
242
- pathname: `/accounts/${accountId}/workers/subdomain`,
243
- });
244
- if (!sub?.subdomain) {
245
- throw new Error('No workers.dev subdomain on this account yet — register one at dash.cloudflare.com → Workers → Subdomain, then re-run.');
246
- }
247
- return sub.subdomain;
248
- }
249
- /** Resolve a zone id for a domain the token can see, or null if not owned/visible. */
250
- export async function findZoneId(apiToken, domain, opts = {}) {
251
- const request = opts.request ?? defaultCloudflareRequester;
252
- // Try the exact name, then the registrable parent (share.getrush.ai -> getrush.ai).
253
- const candidates = [domain, domain.split('.').slice(-2).join('.')];
254
- for (const name of candidates) {
255
- const zones = await request({
256
- apiToken,
257
- method: 'GET',
258
- pathname: `/zones?name=${encodeURIComponent(name)}`,
259
- }).catch(() => []);
260
- if (zones?.length)
261
- return zones[0].id;
262
- }
263
- return null;
264
- }
265
- /** Map a custom hostname (e.g. `share.getrush.ai`) to the Worker via Workers Custom Domains. */
266
- export async function addCustomDomain(apiToken, accountId, workerName, zoneId, hostname, opts = {}) {
267
- const request = opts.request ?? defaultCloudflareRequester;
268
- try {
269
- await request({
270
- apiToken,
271
- method: 'PUT',
272
- pathname: `/accounts/${accountId}/workers/domains`,
273
- body: {
274
- zone_id: zoneId,
275
- hostname,
276
- service: workerName,
277
- environment: 'production',
278
- },
279
- });
280
- }
281
- catch (e) {
282
- if (!isAlreadyExists(e))
283
- throw e;
284
- }
285
- }