@phnx-labs/agents-cli 1.20.72 → 1.20.73

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 (219) hide show
  1. package/CHANGELOG.md +299 -0
  2. package/README.md +40 -4
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/activity.d.ts +12 -0
  5. package/dist/commands/activity.js +68 -0
  6. package/dist/commands/apply.d.ts +13 -0
  7. package/dist/commands/apply.js +28 -3
  8. package/dist/commands/browser.js +1 -1
  9. package/dist/commands/cloud.js +21 -11
  10. package/dist/commands/doctor.js +8 -4
  11. package/dist/commands/events.d.ts +11 -10
  12. package/dist/commands/events.js +30 -21
  13. package/dist/commands/exec.d.ts +34 -0
  14. package/dist/commands/exec.js +337 -39
  15. package/dist/commands/feed.d.ts +13 -0
  16. package/dist/commands/feed.js +130 -29
  17. package/dist/commands/hq.d.ts +2 -0
  18. package/dist/commands/hq.js +58 -0
  19. package/dist/commands/lease.d.ts +19 -0
  20. package/dist/commands/lease.js +138 -11
  21. package/dist/commands/login.d.ts +2 -0
  22. package/dist/commands/login.js +123 -0
  23. package/dist/commands/logs.js +1 -1
  24. package/dist/commands/mine.d.ts +27 -0
  25. package/dist/commands/mine.js +207 -0
  26. package/dist/commands/monitors.js +79 -49
  27. package/dist/commands/plugins.js +1 -0
  28. package/dist/commands/profiles.js +158 -0
  29. package/dist/commands/repo.js +2 -60
  30. package/dist/commands/resources.d.ts +5 -0
  31. package/dist/commands/resources.js +95 -0
  32. package/dist/commands/routines.d.ts +2 -0
  33. package/dist/commands/routines.js +139 -37
  34. package/dist/commands/secrets.js +234 -47
  35. package/dist/commands/send.d.ts +15 -0
  36. package/dist/commands/send.js +62 -0
  37. package/dist/commands/sessions-tail.d.ts +1 -0
  38. package/dist/commands/sessions-tail.js +20 -9
  39. package/dist/commands/sessions.js +18 -3
  40. package/dist/commands/setup-mine.d.ts +18 -0
  41. package/dist/commands/setup-mine.js +106 -0
  42. package/dist/commands/setup-share.js +2 -2
  43. package/dist/commands/setup.js +3 -1
  44. package/dist/commands/share.d.ts +8 -1
  45. package/dist/commands/share.js +122 -37
  46. package/dist/commands/ssh.d.ts +9 -0
  47. package/dist/commands/ssh.js +196 -26
  48. package/dist/commands/sync.js +1 -1
  49. package/dist/commands/teams.js +11 -2
  50. package/dist/index.js +79 -10
  51. package/dist/lib/activity.d.ts +124 -0
  52. package/dist/lib/activity.js +542 -0
  53. package/dist/lib/agents.js +16 -5
  54. package/dist/lib/artifact-actions.d.ts +1 -1
  55. package/dist/lib/artifact-actions.js +1 -1
  56. package/dist/lib/ask-classifier.d.ts +2 -1
  57. package/dist/lib/ask-classifier.js +3 -3
  58. package/dist/lib/auth-health.d.ts +32 -4
  59. package/dist/lib/auth-health.js +40 -1
  60. package/dist/lib/auto-pull.js +8 -1
  61. package/dist/lib/brand.d.ts +40 -0
  62. package/dist/lib/brand.js +122 -0
  63. package/dist/lib/browser/drivers/ssh.js +4 -7
  64. package/dist/lib/browser/service.js +6 -8
  65. package/dist/lib/channels/providers/index.d.ts +2 -0
  66. package/dist/lib/channels/providers/index.js +20 -0
  67. package/dist/lib/channels/providers/mailbox.d.ts +2 -0
  68. package/dist/lib/channels/providers/mailbox.js +26 -0
  69. package/dist/lib/channels/providers/openclaw-telegram.d.ts +2 -0
  70. package/dist/lib/channels/providers/openclaw-telegram.js +34 -0
  71. package/dist/lib/channels/providers/rush.d.ts +6 -0
  72. package/dist/lib/channels/providers/rush.js +55 -0
  73. package/dist/lib/channels/registry.d.ts +42 -0
  74. package/dist/lib/channels/registry.js +20 -0
  75. package/dist/lib/channels/resolve.d.ts +11 -0
  76. package/dist/lib/channels/resolve.js +12 -0
  77. package/dist/lib/cli-resources.d.ts +18 -0
  78. package/dist/lib/cli-resources.js +53 -4
  79. package/dist/lib/cloud/codex.js +6 -3
  80. package/dist/lib/cloud/factory.d.ts +1 -0
  81. package/dist/lib/cloud/factory.js +10 -4
  82. package/dist/lib/cloud/rush.d.ts +3 -1
  83. package/dist/lib/cloud/rush.js +6 -1
  84. package/dist/lib/cloud/types.d.ts +2 -0
  85. package/dist/lib/codex-home.d.ts +35 -0
  86. package/dist/lib/codex-home.js +136 -0
  87. package/dist/lib/crabbox/cli.d.ts +44 -0
  88. package/dist/lib/crabbox/cli.js +120 -20
  89. package/dist/lib/crabbox/lease.d.ts +52 -1
  90. package/dist/lib/crabbox/lease.js +117 -16
  91. package/dist/lib/crabbox/progress.d.ts +31 -0
  92. package/dist/lib/crabbox/progress.js +76 -0
  93. package/dist/lib/crabbox/runtimes.d.ts +4 -0
  94. package/dist/lib/crabbox/runtimes.js +33 -8
  95. package/dist/lib/crabbox/setup-copy.d.ts +87 -0
  96. package/dist/lib/crabbox/setup-copy.js +127 -0
  97. package/dist/lib/daemon.d.ts +5 -5
  98. package/dist/lib/daemon.js +9 -9
  99. package/dist/lib/drive-sync.js +6 -3
  100. package/dist/lib/event-stream.d.ts +36 -0
  101. package/dist/lib/event-stream.js +68 -0
  102. package/dist/lib/events.d.ts +1 -1
  103. package/dist/lib/exec.d.ts +11 -0
  104. package/dist/lib/exec.js +23 -2
  105. package/dist/lib/feed-policy.d.ts +1 -1
  106. package/dist/lib/feed-policy.js +14 -9
  107. package/dist/lib/feed-ranking.d.ts +32 -0
  108. package/dist/lib/feed-ranking.js +224 -0
  109. package/dist/lib/feed.d.ts +35 -2
  110. package/dist/lib/feed.js +66 -3
  111. package/dist/lib/fleet/auth-sync.d.ts +53 -0
  112. package/dist/lib/fleet/auth-sync.js +92 -0
  113. package/dist/lib/fleet/remote-login.d.ts +187 -0
  114. package/dist/lib/fleet/remote-login.js +556 -0
  115. package/dist/lib/format.d.ts +30 -3
  116. package/dist/lib/format.js +34 -5
  117. package/dist/lib/fs-atomic.d.ts +7 -2
  118. package/dist/lib/fs-atomic.js +8 -12
  119. package/dist/lib/git.d.ts +16 -0
  120. package/dist/lib/git.js +79 -2
  121. package/dist/lib/heal.js +11 -3
  122. package/dist/lib/hosts/progress.d.ts +28 -10
  123. package/dist/lib/hosts/progress.js +79 -22
  124. package/dist/lib/hosts/remote-cmd.js +4 -0
  125. package/dist/lib/hq/floor.d.ts +87 -0
  126. package/dist/lib/hq/floor.js +226 -0
  127. package/dist/lib/menubar/install-menubar.js +14 -20
  128. package/dist/lib/notify.d.ts +1 -0
  129. package/dist/lib/notify.js +3 -3
  130. package/dist/lib/open-url.d.ts +2 -0
  131. package/dist/lib/open-url.js +19 -0
  132. package/dist/lib/openclaw-keychain.d.ts +56 -0
  133. package/dist/lib/openclaw-keychain.js +236 -0
  134. package/dist/lib/overdue.js +10 -0
  135. package/dist/lib/permissions.d.ts +44 -1
  136. package/dist/lib/permissions.js +284 -7
  137. package/dist/lib/project-launch.d.ts +6 -12
  138. package/dist/lib/project-launch.js +13 -228
  139. package/dist/lib/project-resources.d.ts +7 -0
  140. package/dist/lib/project-resources.js +291 -0
  141. package/dist/lib/pty-client.d.ts +27 -0
  142. package/dist/lib/pty-client.js +136 -10
  143. package/dist/lib/refresh.js +14 -10
  144. package/dist/lib/resource-profiles.d.ts +26 -0
  145. package/dist/lib/resource-profiles.js +157 -0
  146. package/dist/lib/resources/permissions.js +7 -1
  147. package/dist/lib/resources/types.d.ts +1 -1
  148. package/dist/lib/resources.d.ts +1 -1
  149. package/dist/lib/resources.js +32 -3
  150. package/dist/lib/routines.d.ts +11 -0
  151. package/dist/lib/routines.js +56 -15
  152. package/dist/lib/runner.d.ts +1 -0
  153. package/dist/lib/runner.js +66 -5
  154. package/dist/lib/secrets/bundles.d.ts +16 -3
  155. package/dist/lib/secrets/bundles.js +206 -37
  156. package/dist/lib/secrets/icloud-import.d.ts +2 -2
  157. package/dist/lib/secrets/icloud-import.js +9 -6
  158. package/dist/lib/secrets/mcp.js +1 -1
  159. package/dist/lib/secrets/vault-age-helper.d.ts +1 -0
  160. package/dist/lib/secrets/vault-age-helper.js +34 -0
  161. package/dist/lib/secrets/vault.d.ts +49 -0
  162. package/dist/lib/secrets/vault.js +397 -0
  163. package/dist/lib/self-heal/checks/path.js +3 -1
  164. package/dist/lib/self-heal/checks/shadowing.js +10 -2
  165. package/dist/lib/self-heal/checks/shims.js +19 -11
  166. package/dist/lib/session/cloud.d.ts +2 -2
  167. package/dist/lib/session/cloud.js +2 -2
  168. package/dist/lib/session/db.d.ts +4 -0
  169. package/dist/lib/session/db.js +44 -7
  170. package/dist/lib/session/discover.d.ts +2 -0
  171. package/dist/lib/session/discover.js +114 -3
  172. package/dist/lib/session/stream-render.d.ts +3 -0
  173. package/dist/lib/session/stream-render.js +130 -0
  174. package/dist/lib/session/types.d.ts +6 -0
  175. package/dist/lib/share/analytics.d.ts +13 -0
  176. package/dist/lib/share/analytics.js +45 -0
  177. package/dist/lib/share/config.d.ts +19 -5
  178. package/dist/lib/share/config.js +44 -8
  179. package/dist/lib/share/provision.d.ts +58 -6
  180. package/dist/lib/share/provision.js +97 -22
  181. package/dist/lib/share/publish.d.ts +36 -13
  182. package/dist/lib/share/publish.js +55 -8
  183. package/dist/lib/share/worker-template.js +83 -17
  184. package/dist/lib/shims.d.ts +9 -0
  185. package/dist/lib/shims.js +91 -13
  186. package/dist/lib/ssh-exec.d.ts +14 -0
  187. package/dist/lib/ssh-exec.js +57 -0
  188. package/dist/lib/staleness/detectors/hooks.js +25 -1
  189. package/dist/lib/staleness/detectors/permissions.js +66 -0
  190. package/dist/lib/staleness/detectors/workflows.js +20 -0
  191. package/dist/lib/staleness/writers/hooks.js +58 -4
  192. package/dist/lib/staleness/writers/permissions.js +2 -2
  193. package/dist/lib/staleness/writers/skills.js +1 -1
  194. package/dist/lib/staleness/writers/sources.d.ts +10 -1
  195. package/dist/lib/staleness/writers/sources.js +68 -1
  196. package/dist/lib/star-nudge.d.ts +45 -0
  197. package/dist/lib/star-nudge.js +91 -0
  198. package/dist/lib/startup/command-registry.d.ts +6 -1
  199. package/dist/lib/startup/command-registry.js +17 -3
  200. package/dist/lib/state.d.ts +2 -0
  201. package/dist/lib/state.js +3 -0
  202. package/dist/lib/subagents-registry.d.ts +2 -0
  203. package/dist/lib/subagents-registry.js +3 -0
  204. package/dist/lib/teams/parsers.js +214 -6
  205. package/dist/lib/teams/supervisor.d.ts +7 -0
  206. package/dist/lib/teams/supervisor.js +2 -1
  207. package/dist/lib/template.d.ts +1 -1
  208. package/dist/lib/template.js +1 -1
  209. package/dist/lib/triggers/webhook.js +36 -3
  210. package/dist/lib/types.d.ts +73 -0
  211. package/dist/lib/version.d.ts +40 -0
  212. package/dist/lib/version.js +94 -16
  213. package/dist/lib/versions.d.ts +11 -0
  214. package/dist/lib/versions.js +208 -76
  215. package/dist/lib/workflows.d.ts +2 -0
  216. package/dist/lib/workflows.js +88 -0
  217. package/package.json +2 -1
  218. package/dist/commands/daemon.d.ts +0 -10
  219. package/dist/commands/daemon.js +0 -121
@@ -1,8 +1,12 @@
1
1
  // Cloudflare provisioning for `agents share setup` — plain `fetch` against the CF
2
- // REST API (the repo has no CF wrapper). Creates the R2 bucket, uploads the Worker
3
- // (with an R2 binding + the WRITE_TOKEN as an inline secret), enables the free
4
- // `*.workers.dev` subdomain, and — when the token owns the zone — maps a custom domain.
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.
5
6
  const CF_API = 'https://api.cloudflare.com/client/v4';
7
+ export const SHARE_LIFECYCLE_RULE_ID = 'agents-share-expire-objects';
8
+ export const SHARE_LIFECYCLE_RETENTION_DAYS = 366;
9
+ const SECONDS_PER_DAY = 86400;
6
10
  async function cf(apiToken, method, pathname, body, form) {
7
11
  const headers = { authorization: `Bearer ${apiToken}` };
8
12
  let payload;
@@ -22,66 +26,137 @@ async function cf(apiToken, method, pathname, body, form) {
22
26
  }
23
27
  return json.result;
24
28
  }
29
+ const defaultCloudflareRequester = (request) => cf(request.apiToken, request.method, request.pathname, request.body, request.form);
25
30
  /** True if the CF error looks like "the thing already exists" (idempotent create). */
26
31
  function isAlreadyExists(e) {
27
32
  return /already exists|duplicate|10004|10014/i.test(String(e));
28
33
  }
29
34
  /** Create the R2 bucket (idempotent). */
30
- export async function createBucket(apiToken, accountId, name) {
35
+ export async function createBucket(apiToken, accountId, name, opts = {}) {
36
+ const request = opts.request ?? defaultCloudflareRequester;
31
37
  try {
32
- await cf(apiToken, 'POST', `/accounts/${accountId}/r2/buckets`, { name });
38
+ await request({
39
+ apiToken,
40
+ method: 'POST',
41
+ pathname: `/accounts/${accountId}/r2/buckets`,
42
+ body: { name },
43
+ });
33
44
  }
34
45
  catch (e) {
35
46
  if (!isAlreadyExists(e))
36
47
  throw e;
37
48
  }
38
49
  }
39
- /** Upload the module Worker with an R2 binding (`BUCKET`) + inline `WRITE_TOKEN` secret. */
40
- export async function deployWorker(apiToken, accountId, workerName, script, bucketName, writeToken) {
50
+ export function buildShareLifecycleRule(days = SHARE_LIFECYCLE_RETENTION_DAYS) {
51
+ return {
52
+ id: SHARE_LIFECYCLE_RULE_ID,
53
+ enabled: true,
54
+ conditions: { prefix: '' },
55
+ deleteObjectsTransition: {
56
+ condition: { type: 'Age', maxAge: days * SECONDS_PER_DAY },
57
+ },
58
+ };
59
+ }
60
+ export function mergeShareLifecycleRule(existing = [], rule = buildShareLifecycleRule()) {
61
+ return [...existing.filter((r) => r.id !== SHARE_LIFECYCLE_RULE_ID), rule];
62
+ }
63
+ /** Ensure the share bucket self-cleans old objects. Exact per-link expiry is enforced by the Worker. */
64
+ export async function configureBucketLifecycle(apiToken, accountId, bucketName, opts = {}) {
65
+ const request = opts.request ?? defaultCloudflareRequester;
66
+ const lifecycle = await request({
67
+ apiToken,
68
+ method: 'GET',
69
+ pathname: `/accounts/${accountId}/r2/buckets/${bucketName}/lifecycle`,
70
+ });
71
+ await request({
72
+ apiToken,
73
+ method: 'PUT',
74
+ pathname: `/accounts/${accountId}/r2/buckets/${bucketName}/lifecycle`,
75
+ body: { rules: mergeShareLifecycleRule(lifecycle.rules ?? []) },
76
+ });
77
+ }
78
+ /** Upload the module Worker with an R2 binding (`BUCKET`). Secrets are set via the Workers Secrets API. */
79
+ export async function deployWorker(apiToken, accountId, workerName, script, bucketName, opts = {}) {
80
+ const request = opts.request ?? defaultCloudflareRequester;
41
81
  const metadata = {
42
82
  main_module: 'worker.js',
43
83
  compatibility_date: '2024-11-06',
44
84
  bindings: [
45
85
  { type: 'r2_bucket', name: 'BUCKET', bucket_name: bucketName },
46
- { type: 'secret_text', name: 'WRITE_TOKEN', text: writeToken },
47
86
  ],
48
87
  };
49
88
  const form = new FormData();
50
89
  form.set('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
51
90
  form.set('worker.js', new Blob([script], { type: 'application/javascript+module' }), 'worker.js');
52
- await cf(apiToken, 'PUT', `/accounts/${accountId}/workers/scripts/${workerName}`, undefined, form);
91
+ await request({
92
+ apiToken,
93
+ method: 'PUT',
94
+ pathname: `/accounts/${accountId}/workers/scripts/${workerName}`,
95
+ form,
96
+ });
97
+ }
98
+ /** Add/update the WRITE_TOKEN binding using Cloudflare's Workers Secrets API. */
99
+ export async function setWorkerSecret(apiToken, accountId, workerName, writeToken, opts = {}) {
100
+ const request = opts.request ?? defaultCloudflareRequester;
101
+ await request({
102
+ apiToken,
103
+ method: 'PUT',
104
+ pathname: `/accounts/${accountId}/workers/scripts/${workerName}/secrets`,
105
+ body: { name: 'WRITE_TOKEN', text: writeToken, type: 'secret_text' },
106
+ });
53
107
  }
54
108
  /** Enable the free `*.workers.dev` route for the script, and return the account subdomain. */
55
- export async function enableWorkersDev(apiToken, accountId, workerName) {
56
- await cf(apiToken, 'POST', `/accounts/${accountId}/workers/scripts/${workerName}/subdomain`, {
57
- enabled: true,
58
- previews_enabled: false,
109
+ export async function enableWorkersDev(apiToken, accountId, workerName, opts = {}) {
110
+ const request = opts.request ?? defaultCloudflareRequester;
111
+ await request({
112
+ apiToken,
113
+ method: 'POST',
114
+ pathname: `/accounts/${accountId}/workers/scripts/${workerName}/subdomain`,
115
+ body: {
116
+ enabled: true,
117
+ previews_enabled: false,
118
+ },
119
+ });
120
+ const sub = await request({
121
+ apiToken,
122
+ method: 'GET',
123
+ pathname: `/accounts/${accountId}/workers/subdomain`,
59
124
  });
60
- const sub = await cf(apiToken, 'GET', `/accounts/${accountId}/workers/subdomain`);
61
125
  if (!sub?.subdomain) {
62
126
  throw new Error('No workers.dev subdomain on this account yet — register one at dash.cloudflare.com → Workers → Subdomain, then re-run.');
63
127
  }
64
128
  return sub.subdomain;
65
129
  }
66
130
  /** Resolve a zone id for a domain the token can see, or null if not owned/visible. */
67
- export async function findZoneId(apiToken, domain) {
131
+ export async function findZoneId(apiToken, domain, opts = {}) {
132
+ const request = opts.request ?? defaultCloudflareRequester;
68
133
  // Try the exact name, then the registrable parent (share.agents-cli.sh -> agents-cli.sh).
69
134
  const candidates = [domain, domain.split('.').slice(-2).join('.')];
70
135
  for (const name of candidates) {
71
- const zones = await cf(apiToken, 'GET', `/zones?name=${encodeURIComponent(name)}`).catch(() => []);
136
+ const zones = await request({
137
+ apiToken,
138
+ method: 'GET',
139
+ pathname: `/zones?name=${encodeURIComponent(name)}`,
140
+ }).catch(() => []);
72
141
  if (zones?.length)
73
142
  return zones[0].id;
74
143
  }
75
144
  return null;
76
145
  }
77
146
  /** Map a custom hostname (e.g. `share.agents-cli.sh`) to the Worker via Workers Custom Domains. */
78
- export async function addCustomDomain(apiToken, accountId, workerName, zoneId, hostname) {
147
+ export async function addCustomDomain(apiToken, accountId, workerName, zoneId, hostname, opts = {}) {
148
+ const request = opts.request ?? defaultCloudflareRequester;
79
149
  try {
80
- await cf(apiToken, 'PUT', `/accounts/${accountId}/workers/domains`, {
81
- zone_id: zoneId,
82
- hostname,
83
- service: workerName,
84
- environment: 'production',
150
+ await request({
151
+ apiToken,
152
+ method: 'PUT',
153
+ pathname: `/accounts/${accountId}/workers/domains`,
154
+ body: {
155
+ zone_id: zoneId,
156
+ hostname,
157
+ service: workerName,
158
+ environment: 'production',
159
+ },
85
160
  });
86
161
  }
87
162
  catch (e) {
@@ -1,8 +1,13 @@
1
- type PutFn = (url: string, body: Buffer, headers: Record<string, string>) => Promise<{
1
+ import { type ShareConfig } from './config.js';
2
+ export type PutFn = (url: string, body: Buffer, headers: Record<string, string>) => Promise<{
2
3
  ok: boolean;
3
4
  status: number;
4
5
  url?: string;
5
6
  }>;
7
+ export interface PublishEndpoint {
8
+ baseUrl: string;
9
+ token: string;
10
+ }
6
11
  export interface PublishOptions {
7
12
  slug?: string;
8
13
  /** e.g. `30d`, `12h`, or an absolute date like `2026-08-01`. */
@@ -10,15 +15,26 @@ export interface PublishOptions {
10
15
  contentType?: string;
11
16
  /** Generate + attach an OG cover for HTML pages (default true). */
12
17
  cover?: boolean;
18
+ /** Inject the Cloudflare Web Analytics beacon (default true for HTML). */
19
+ analytics?: boolean;
20
+ /** Override the analytics token from share config. */
21
+ analyticsToken?: string;
22
+ /** Override the GitHub username used for the URL namespace. */
23
+ githubUser?: string;
24
+ /** DI seam for tests — override the persisted share endpoint config. */
25
+ config?: ShareConfig;
26
+ /** DI seam for tests — override the keychain-backed write token. */
27
+ writeToken?: string;
13
28
  /** DI seam for tests — override the real HTTP PUT. */
14
- uploader?: (url: string, body: Buffer, headers: Record<string, string>) => Promise<{
15
- ok: boolean;
16
- status: number;
17
- url?: string;
18
- }>;
29
+ uploader?: PutFn;
19
30
  /** DI seam for tests — override cover capture (returns a PNG buffer or null). */
20
31
  capturer?: (htmlPath: string) => Promise<Buffer | null>;
21
32
  }
33
+ export interface PublishResult {
34
+ url: string;
35
+ expiresAt?: string;
36
+ coverUrl?: string;
37
+ }
22
38
  /** `30d` / `12h` / `2026-08-01` → an absolute ISO timestamp (or undefined). */
23
39
  export declare function parseExpire(spec: string | undefined): string | undefined;
24
40
  /** Derive a URL-safe slug from a filename (or pass one through). */
@@ -26,9 +42,14 @@ export declare function slugify(name: string): string;
26
42
  /** The project the file belongs to — git repo name, else the cwd's basename. */
27
43
  export declare function detectProject(dir?: string): string;
28
44
  /**
29
- * Notion-style default slug: `<project>-<feature>-<6hex>`. Project scopes the link
45
+ * Notion-style default slug: `<project>-<feature>-<16hex>`. Project scopes the link
30
46
  * to the repo the agent is in; the random tail keeps it unguessable + collision-free.
31
47
  * A leading `plan-` on the filename is dropped (it's redundant under the project).
48
+ *
49
+ * The tail is 8 random bytes (64-bit, 16 hex chars). Reads are public — the URL is
50
+ * the only capability — so the nonce must be genuinely infeasible to brute-force,
51
+ * not merely unlisted; 64 bits puts a blind guess out of reach. (See docs/share.md
52
+ * §Security for the threat model and `--expire` for sensitive content.)
32
53
  */
33
54
  export declare function defaultSlug(filePath: string, dir?: string): string;
34
55
  /**
@@ -49,9 +70,11 @@ export declare function attachOgCover(filePath: string, body: Buffer, ctx: {
49
70
  body: Buffer;
50
71
  coverUrl?: string;
51
72
  }>;
52
- export declare function publishFile(filePath: string, opts?: PublishOptions): Promise<{
53
- url: string;
54
- expiresAt?: string;
55
- coverUrl?: string;
56
- }>;
57
- export {};
73
+ /** Resolve the publisher's GitHub username, with an explicit override winning first. */
74
+ export declare function resolveShareUsername(opts?: {
75
+ githubUser?: string;
76
+ }): Promise<string>;
77
+ /** Build the R2 object key from a namespace username and a slug part. */
78
+ export declare function buildShareKey(username: string, slugPart: string): string;
79
+ export declare function publishFile(filePath: string, opts?: PublishOptions): Promise<PublishResult>;
80
+ export declare function publishToEndpoint(filePath: string, endpoint: PublishEndpoint, opts?: PublishOptions): Promise<PublishResult>;
@@ -10,8 +10,10 @@ import { basename } from 'node:path';
10
10
  import { execFileSync } from 'node:child_process';
11
11
  import { randomBytes } from 'node:crypto';
12
12
  import { readShareConfig, readWriteToken } from './config.js';
13
+ import { resolveGitHubUsername } from '../git.js';
13
14
  import { captureCover, OG_WIDTH, OG_HEIGHT, OG_SCALE } from './capture.js';
14
15
  import { deriveMeta, injectOgMeta } from './og.js';
16
+ import { injectAnalyticsBeacon } from './analytics.js';
15
17
  const UNIT_MS = { s: 1e3, m: 6e4, h: 36e5, d: 864e5, w: 6048e5 };
16
18
  /** `30d` / `12h` / `2026-08-01` → an absolute ISO timestamp (or undefined). */
17
19
  export function parseExpire(spec) {
@@ -55,13 +57,18 @@ export function detectProject(dir = process.cwd()) {
55
57
  return sanitizeSlugPart(basename(dir)) || 'share';
56
58
  }
57
59
  /**
58
- * Notion-style default slug: `<project>-<feature>-<6hex>`. Project scopes the link
60
+ * Notion-style default slug: `<project>-<feature>-<16hex>`. Project scopes the link
59
61
  * to the repo the agent is in; the random tail keeps it unguessable + collision-free.
60
62
  * A leading `plan-` on the filename is dropped (it's redundant under the project).
63
+ *
64
+ * The tail is 8 random bytes (64-bit, 16 hex chars). Reads are public — the URL is
65
+ * the only capability — so the nonce must be genuinely infeasible to brute-force,
66
+ * not merely unlisted; 64 bits puts a blind guess out of reach. (See docs/share.md
67
+ * §Security for the threat model and `--expire` for sensitive content.)
61
68
  */
62
69
  export function defaultSlug(filePath, dir) {
63
70
  const feature = slugify(filePath).replace(/^plan-/, '') || 'page';
64
- return `${detectProject(dir)}-${feature}-${randomBytes(3).toString('hex')}`;
71
+ return `${detectProject(dir)}-${feature}-${randomBytes(8).toString('hex')}`;
65
72
  }
66
73
  function guessContentType(filePath) {
67
74
  if (/\.html?$/i.test(filePath))
@@ -103,30 +110,70 @@ export async function attachOgCover(filePath, body, ctx) {
103
110
  });
104
111
  return { body: Buffer.from(injected, 'utf8'), coverUrl: ctx.pngUrl };
105
112
  }
113
+ /** Resolve the publisher's GitHub username, with an explicit override winning first. */
114
+ export async function resolveShareUsername(opts = {}) {
115
+ if (opts.githubUser) {
116
+ const sanitized = sanitizeSlugPart(opts.githubUser);
117
+ if (sanitized)
118
+ return sanitized;
119
+ }
120
+ const resolved = await resolveGitHubUsername();
121
+ if (resolved)
122
+ return sanitizeSlugPart(resolved);
123
+ throw new Error("Could not determine your GitHub username for the share URL namespace. " +
124
+ "Authenticate with `gh auth login`, set `git config --global github.user <user>`, " +
125
+ "or pass `--github-user <user>`.");
126
+ }
127
+ /** Build the R2 object key from a namespace username and a slug part. */
128
+ export function buildShareKey(username, slugPart) {
129
+ const user = sanitizeSlugPart(username);
130
+ const part = sanitizeSlugPart(slugPart.replace(/\//g, '-'));
131
+ if (!user)
132
+ throw new Error('GitHub username is required for the share URL namespace.');
133
+ if (!part)
134
+ throw new Error('Share slug is empty.');
135
+ return `${user}/${part}`;
136
+ }
106
137
  export async function publishFile(filePath, opts = {}) {
107
- const cfg = readShareConfig();
138
+ const cfg = opts.config ?? readShareConfig();
108
139
  if (!cfg) {
109
140
  throw new Error("Not set up yet. Run 'agents share setup' (provision your own endpoint) or 'agents share join' (use an existing one).");
110
141
  }
111
- const token = readWriteToken();
112
- const slug = (opts.slug ?? defaultSlug(filePath)).replace(/^\/+/, '');
142
+ const token = opts.writeToken ?? readWriteToken();
143
+ const username = await resolveShareUsername(opts);
144
+ const analyticsToken = opts.analyticsToken ?? cfg.analyticsToken;
145
+ return publishToEndpoint(filePath, { baseUrl: cfg.baseUrl, token }, {
146
+ ...opts,
147
+ githubUser: username,
148
+ analyticsToken,
149
+ });
150
+ }
151
+ export async function publishToEndpoint(filePath, endpoint, opts = {}) {
152
+ const username = await resolveShareUsername(opts);
153
+ const slugPart = (opts.slug ?? defaultSlug(filePath)).replace(/^\/+/, '');
154
+ const key = buildShareKey(username, slugPart);
113
155
  const expiresAt = parseExpire(opts.expire);
114
- const pageUrl = `${cfg.baseUrl}/${slug}`;
156
+ const pageUrl = `${endpoint.baseUrl.replace(/\/+$/, '')}/${key}`;
115
157
  const put = opts.uploader ??
116
158
  (async (u, b, h) => {
117
159
  const res = await fetch(u, { method: 'PUT', headers: h, body: new Uint8Array(b) });
118
160
  return { ok: res.ok, status: res.status, url: u };
119
161
  });
120
162
  const authHeaders = (contentType) => {
121
- const h = { authorization: `Bearer ${token}`, 'content-type': contentType };
163
+ const h = { authorization: `Bearer ${endpoint.token}`, 'content-type': contentType };
122
164
  if (expiresAt)
123
165
  h['x-share-expires-at'] = expiresAt;
124
166
  return h;
125
167
  };
126
168
  let body = readFileSync(filePath);
127
169
  let coverUrl;
170
+ const isHtml = /\.html?$/i.test(filePath);
171
+ // Analytics: cookieless CF Web Analytics beacon, injected for HTML by default.
172
+ if (isHtml && opts.analytics !== false && opts.analyticsToken) {
173
+ body = Buffer.from(injectAnalyticsBeacon(body.toString('utf8'), opts.analyticsToken), 'utf8');
174
+ }
128
175
  // Cover: screenshot the page's hero → upload <slug>.png → inject og:image meta.
129
- if (/\.html?$/i.test(filePath) && opts.cover !== false) {
176
+ if (isHtml && opts.cover !== false) {
130
177
  const res = await attachOgCover(filePath, body, {
131
178
  pngUrl: `${pageUrl}.png`,
132
179
  pageUrl,
@@ -1,26 +1,30 @@
1
1
  // The Cloudflare Worker that fronts the R2 share bucket.
2
2
  //
3
3
  // One tiny Worker does both sides:
4
- // - PUT /<slug> — bearer-gated (WRITE_TOKEN secret); writes the body to R2 via
5
- // the BUCKET binding, storing an optional expiry in object metadata.
6
- // - GET /<slug> — public; streams the object from R2, 410s (and lazily deletes)
7
- // once its expiry has passed. A bucket lifecycle rule is the
8
- // durable sweeper; this is the immediate gate.
4
+ // - PUT /<username>/<slug> — bearer-gated (WRITE_TOKEN secret); writes the body
5
+ // to R2 via the BUCKET binding, storing an optional expires-at value in
6
+ // object metadata.
7
+ // - GET /<username>/<slug> — public; streams the object from R2, 410s (and lazily
8
+ // deletes) once its expiry has passed. A bucket lifecycle rule is the durable
9
+ // sweeper; this is the immediate gate.
10
+ // - GET /<username> — public gallery of that user's shares.
11
+ // - GET /<slug> — backward-compat flat slug (legacy shares before
12
+ // per-user namespaces).
9
13
  //
10
14
  // Emitted as a string (mirrors src/lib/serve/page.ts `renderPage()`), so it compiles
11
15
  // into `dist/**` and ships with no package.json#files change. `provision.ts` uploads
12
16
  // this verbatim as an ES-module Worker with a BUCKET (R2) binding + a WRITE_TOKEN secret.
13
17
  /** Render the Worker source. Pure — the R2 binding + token are wired at deploy time. */
14
18
  export function renderWorkerScript() {
15
- return `// GENERATED by agents-cli \`agents share setup\` — do not edit here; edit
19
+ return `// GENERATED by agents-cli agents share setup — do not edit here; edit
16
20
  // src/lib/share/worker-template.ts and re-run setup.
17
21
  export default {
18
22
  async fetch(request, env) {
19
23
  const url = new URL(request.url);
20
- const key = decodeURIComponent(url.pathname.replace(/^\\/+/, ''));
24
+ const path = decodeURIComponent(url.pathname.replace(/^\\/+/, ''));
21
25
 
22
- if (!key) {
23
- return new Response('agents share — POST is not it; PUT /<slug> to publish, GET /<slug> to view.', {
26
+ if (!path) {
27
+ return new Response('agents share — POST is not it; PUT /<username>/<slug> to publish, GET /<username>/<slug> to view.', {
24
28
  status: 200, headers: { 'content-type': 'text/plain; charset=utf-8' },
25
29
  });
26
30
  }
@@ -32,19 +36,28 @@ export default {
32
36
  }
33
37
  const expiresAt = request.headers.get('x-share-expires-at') || '';
34
38
  const contentType = request.headers.get('content-type') || 'text/html; charset=utf-8';
35
- await env.BUCKET.put(key, request.body, {
39
+ await env.BUCKET.put(path, request.body, {
36
40
  httpMetadata: { contentType },
37
- customMetadata: expiresAt ? { expiresAt } : {},
41
+ customMetadata: expiresAt ? { 'expires-at': expiresAt } : {},
38
42
  });
39
- return json({ ok: true, url: url.origin + '/' + key, expiresAt: expiresAt || null }, 200);
43
+ return json({ ok: true, url: url.origin + '/' + path, expiresAt: expiresAt || null }, 200);
40
44
  }
41
45
 
42
46
  if (request.method === 'GET' || request.method === 'HEAD') {
43
- const obj = await env.BUCKET.get(key);
47
+ // Single-segment path may be a user gallery OR a legacy flat slug.
48
+ const segments = path.split('/').filter(Boolean);
49
+ if (segments.length === 1) {
50
+ const list = await env.BUCKET.list({ prefix: segments[0] + '/', limit: 1 });
51
+ if (list.objects && list.objects.length > 0) {
52
+ return renderGallery(env.BUCKET, url.origin, segments[0], request.method);
53
+ }
54
+ }
55
+
56
+ const obj = await env.BUCKET.get(path);
44
57
  if (!obj) return new Response('not found', { status: 404, headers: { 'content-type': 'text/plain' } });
45
- const expiresAt = obj.customMetadata && obj.customMetadata.expiresAt;
58
+ const expiresAt = obj.customMetadata && obj.customMetadata['expires-at'];
46
59
  if (expiresAt && Date.now() > Date.parse(expiresAt)) {
47
- await env.BUCKET.delete(key);
60
+ await env.BUCKET.delete(path);
48
61
  return new Response('gone — this link has expired', { status: 410, headers: { 'content-type': 'text/plain' } });
49
62
  }
50
63
  const headers = new Headers();
@@ -58,14 +71,67 @@ export default {
58
71
  if (request.method === 'DELETE') {
59
72
  const presented = (request.headers.get('authorization') || '').replace(/^Bearer\\s+/i, '');
60
73
  if (!env.WRITE_TOKEN || !safeEqual(presented, env.WRITE_TOKEN)) return json({ error: 'unauthorized' }, 401);
61
- await env.BUCKET.delete(key);
62
- return json({ ok: true, deleted: key }, 200);
74
+ await env.BUCKET.delete(path);
75
+ return json({ ok: true, deleted: path }, 200);
63
76
  }
64
77
 
65
78
  return new Response('method not allowed', { status: 405 });
66
79
  },
67
80
  };
68
81
 
82
+ async function renderGallery(bucket, origin, user, method) {
83
+ const objects = [];
84
+ let cursor;
85
+ do {
86
+ const list = await bucket.list({ prefix: user + '/', limit: 1000, cursor, include: ['customMetadata'] });
87
+ objects.push(...(list.objects || []));
88
+ cursor = list.truncated ? list.cursor : undefined;
89
+ } while (cursor);
90
+
91
+ const activeObjects = objects.filter(o => {
92
+ if (o.key.endsWith('.png')) return false;
93
+ const expiresAt = o.customMetadata && o.customMetadata['expires-at'];
94
+ return !(expiresAt && Date.now() > Date.parse(expiresAt));
95
+ });
96
+ const items = activeObjects.map(o => {
97
+ const slug = o.key.slice(o.key.indexOf('/') + 1);
98
+ const url = origin + '/' + o.key;
99
+ return { slug, url, updated: o.uploaded };
100
+ });
101
+
102
+ if (method === 'HEAD') {
103
+ return new Response(null, { status: 200, headers: { 'content-type': 'text/html; charset=utf-8' } });
104
+ }
105
+
106
+ const html =
107
+ '<!doctype html><html><head>' +
108
+ '<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">' +
109
+ '<title>' + escapeHtml(user) + ' — agents share</title>' +
110
+ '<style>' +
111
+ 'body{font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;max-width:720px;margin:48px auto;padding:0 24px;color:#111;background:#fafafa}' +
112
+ 'h1{font-size:28px;margin-bottom:8px}a{color:#0a0a0a;text-decoration:none;border-bottom:1px solid #999}' +
113
+ 'a:hover{border-color:#111}ul{list-style:none;padding:0}li{margin:16px 0}' +
114
+ '.slug{font-weight:600}.meta{color:#666;font-size:14px}' +
115
+ '</style></head><body>' +
116
+ '<h1>@' + escapeHtml(user) + '</h1>' +
117
+ '<p class="meta">' + items.length + ' shared ' + (items.length === 1 ? 'page' : 'pages') + '</p>' +
118
+ '<ul>' +
119
+ items.map(i =>
120
+ '<li><a class="slug" href="' + escapeHtml(i.url) + '">' + escapeHtml(i.slug) + '</a>' +
121
+ '<br><span class="meta">' + new Date(i.updated).toISOString().slice(0, 10) + '</span></li>'
122
+ ).join('') +
123
+ '</ul></body></html>';
124
+ return new Response(html, { status: 200, headers: { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'public, max-age=60' } });
125
+ }
126
+
127
+ function escapeHtml(s) {
128
+ return String(s)
129
+ .replace(/&/g, '&amp;')
130
+ .replace(/</g, '&lt;')
131
+ .replace(/>/g, '&gt;')
132
+ .replace(/"/g, '&quot;');
133
+ }
134
+
69
135
  function json(body, status) {
70
136
  return new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } });
71
137
  }
@@ -99,6 +99,14 @@ export declare function shimTargetsFor(platform: NodeJS.Platform): {
99
99
  * Create a shim for an agent.
100
100
  */
101
101
  export declare function createShim(agent: AgentId): string;
102
+ /** The POSIX pass-through shim for a brand. */
103
+ export declare function generateBrandShim(name: string): string;
104
+ /** Write a brand's pass-through shim(s) onto PATH; returns the shim path. */
105
+ export declare function createBrandShim(name: string): string;
106
+ /** True when the file at the given path is an agents-cli brand shim. */
107
+ export declare function isBrandShim(filePath: string): boolean;
108
+ /** Remove a brand's shim companions. Returns true if anything was removed. */
109
+ export declare function removeBrandShim(name: string): boolean;
102
110
  /**
103
111
  * Remove the shim for an agent.
104
112
  */
@@ -500,6 +508,7 @@ export declare function addShimsToPath(overrides?: {
500
508
  shimsDir?: string;
501
509
  }): ShimPathResult;
502
510
  export declare function listAgentsWithInstalledVersions(): AgentId[];
511
+ export declare function listAgentsWithNonIsolatedInstalledVersions(): AgentId[];
503
512
  /**
504
513
  * Resource diff between two versions. Each field lists resources present in
505
514
  * the current version but missing from the target.