@phnx-labs/agents-cli 1.22.47 → 1.22.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +401 -17
- package/README.md +66 -32
- package/dist/bootstrap.js +4 -2
- package/dist/cli/command-registry.d.ts +1 -0
- package/dist/cli/command-registry.js +2 -0
- package/dist/commands/accounts.d.ts +58 -0
- package/dist/commands/accounts.js +206 -32
- package/dist/commands/artifacts.js +7 -6
- package/dist/commands/attach.js +1 -1
- package/dist/commands/browser.js +501 -103
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +0 -20
- package/dist/commands/daemon.d.ts +21 -0
- package/dist/commands/daemon.js +147 -12
- package/dist/commands/exec.js +52 -15
- package/dist/commands/focus.d.ts +14 -0
- package/dist/commands/focus.js +48 -5
- package/dist/commands/go.d.ts +3 -0
- package/dist/commands/go.js +16 -3
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/lease.d.ts +1 -1
- package/dist/commands/lease.js +4 -3
- package/dist/commands/mailboxes.js +4 -3
- package/dist/commands/menubar.js +11 -4
- package/dist/commands/projects.d.ts +16 -1
- package/dist/commands/projects.js +58 -64
- package/dist/commands/resume.js +1 -1
- package/dist/commands/route.js +4 -4
- package/dist/commands/routines.js +3 -2
- package/dist/commands/run-broadcast.d.ts +1 -1
- package/dist/commands/run-broadcast.js +1 -1
- package/dist/commands/sessions-picker.d.ts +22 -0
- package/dist/commands/sessions-picker.js +48 -9
- package/dist/commands/sessions-trace.js +1 -1
- package/dist/commands/sessions.js +1 -0
- package/dist/commands/setup-secrets.js +23 -0
- package/dist/commands/share.d.ts +20 -5
- package/dist/commands/share.js +152 -52
- package/dist/commands/ssh.js +54 -7
- package/dist/commands/traces.d.ts +2 -0
- package/dist/commands/traces.js +184 -0
- package/dist/commands/view.d.ts +1 -1
- package/dist/commands/view.js +17 -34
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/accounting/usage.d.ts +14 -3
- package/dist/lib/accounting/usage.js +64 -18
- package/dist/lib/agent-spec/agents.js +11 -9
- package/dist/lib/browser/drivers/ssh.d.ts +16 -2
- package/dist/lib/browser/drivers/ssh.js +36 -9
- package/dist/lib/browser/ipc.js +8 -3
- package/dist/lib/browser/profiles.d.ts +35 -101
- package/dist/lib/browser/profiles.js +130 -301
- package/dist/lib/browser/registry.d.ts +39 -0
- package/dist/lib/browser/registry.js +149 -0
- package/dist/lib/browser/remote-control.d.ts +8 -6
- package/dist/lib/browser/remote-control.js +8 -6
- package/dist/lib/browser/resolve-target.d.ts +68 -0
- package/dist/lib/browser/resolve-target.js +250 -0
- package/dist/lib/browser/runtime-state.d.ts +40 -16
- package/dist/lib/browser/runtime-state.js +92 -25
- package/dist/lib/browser/service.d.ts +28 -9
- package/dist/lib/browser/service.js +139 -62
- package/dist/lib/browser/task-index.d.ts +70 -0
- package/dist/lib/browser/task-index.js +180 -0
- package/dist/lib/byok-usage.js +2 -2
- package/dist/lib/claude-statusline.d.ts +29 -0
- package/dist/lib/claude-statusline.js +139 -0
- package/dist/lib/cloud/host.d.ts +1 -1
- package/dist/lib/cloud/host.js +1 -1
- package/dist/lib/cloud/rush.js +1 -1
- package/dist/lib/computer/computer-rpc.js +2 -2
- package/dist/lib/computer/download.d.ts +15 -15
- package/dist/lib/computer/download.js +35 -98
- package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
- package/dist/lib/computer/ssh-tunnel.js +12 -22
- package/dist/lib/config-keys.d.ts +2 -8
- package/dist/lib/config-keys.js +2 -13
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +2 -2
- package/dist/lib/crabbox/lease.d.ts +9 -0
- package/dist/lib/crabbox/lease.js +70 -15
- package/dist/lib/crabbox/runtimes.js +1 -1
- package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
- package/dist/lib/daemon/account-state-daemon-service.js +26 -0
- package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
- package/dist/lib/daemon/browser-ipc-service.js +46 -0
- package/dist/lib/daemon/daemon.d.ts +3 -0
- package/dist/lib/daemon/daemon.js +212 -444
- package/dist/lib/daemon/device-probe-service.d.ts +26 -0
- package/dist/lib/daemon/device-probe-service.js +47 -0
- package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
- package/dist/lib/daemon/keychain-reap-service.js +32 -0
- package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
- package/dist/lib/daemon/monitor-engine-service.js +29 -0
- package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
- package/dist/lib/daemon/secrets-broker-service.js +66 -0
- package/dist/lib/daemon/self-heal-service.d.ts +27 -0
- package/dist/lib/daemon/self-heal-service.js +48 -0
- package/dist/lib/daemon/service.d.ts +102 -0
- package/dist/lib/daemon/service.js +98 -0
- package/dist/lib/daemon/session-index-service.d.ts +22 -0
- package/dist/lib/daemon/session-index-service.js +40 -0
- package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
- package/dist/lib/daemon/state-dir-check-service.js +63 -0
- package/dist/lib/daemon/supervisor.d.ts +87 -0
- package/dist/lib/daemon/supervisor.js +298 -0
- package/dist/lib/daemon/watchdog-service.d.ts +19 -0
- package/dist/lib/daemon/watchdog-service.js +40 -0
- package/dist/lib/daemon-health.d.ts +15 -0
- package/dist/lib/daemon-health.js +29 -5
- package/dist/lib/daemon-services.d.ts +8 -1
- package/dist/lib/daemon-services.js +55 -0
- package/dist/lib/daemon-ticks.d.ts +13 -2
- package/dist/lib/daemon-ticks.js +20 -18
- package/dist/lib/device-config.d.ts +0 -2
- package/dist/lib/device-config.js +1 -23
- package/dist/lib/devices/registry.d.ts +1 -1
- package/dist/lib/devices/registry.js +1 -1
- package/dist/lib/devices/stats-cache.d.ts +20 -0
- package/dist/lib/devices/stats-cache.js +49 -5
- package/dist/lib/devices/worker-pick.d.ts +48 -0
- package/dist/lib/devices/worker-pick.js +113 -0
- package/dist/lib/exec.d.ts +67 -13
- package/dist/lib/exec.js +85 -29
- package/dist/lib/harness/adapters/cursor.js +8 -11
- package/dist/lib/helper-download.d.ts +108 -0
- package/dist/lib/helper-download.js +194 -0
- package/dist/lib/helper-versions.d.ts +52 -0
- package/dist/lib/helper-versions.js +58 -0
- package/dist/lib/hooks/cache.js +11 -0
- package/dist/lib/hooks/install.js +2 -2
- package/dist/lib/hosts/credentials.d.ts +1 -1
- package/dist/lib/hosts/credentials.js +1 -1
- package/dist/lib/hosts/dispatch.d.ts +7 -1
- package/dist/lib/hosts/dispatch.js +24 -5
- package/dist/lib/hosts/passthrough.d.ts +8 -15
- package/dist/lib/hosts/passthrough.js +19 -16
- package/dist/lib/hosts/providers/devices.d.ts +1 -1
- package/dist/lib/hosts/providers/devices.js +1 -1
- package/dist/lib/hosts/reconnect.d.ts +179 -21
- package/dist/lib/hosts/reconnect.js +266 -45
- package/dist/lib/installations/migrate.d.ts +4 -5
- package/dist/lib/installations/migrate.js +6 -7
- package/dist/lib/installations/shims.d.ts +1 -1
- package/dist/lib/installations/shims.js +23 -13
- package/dist/lib/installations/versions.js +25 -2
- package/dist/lib/linear-autoclose.d.ts +1 -1
- package/dist/lib/linear-autoclose.js +1 -1
- package/dist/lib/loop.d.ts +2 -2
- package/dist/lib/loop.js +1 -1
- package/dist/lib/menubar/download-menubar.d.ts +47 -0
- package/dist/lib/menubar/download-menubar.js +60 -0
- package/dist/lib/menubar/install-menubar.d.ts +31 -7
- package/dist/lib/menubar/install-menubar.js +79 -21
- package/dist/lib/menubar/notify-desktop.d.ts +3 -3
- package/dist/lib/menubar/notify-desktop.js +6 -6
- package/dist/lib/model-tiers.d.ts +1 -1
- package/dist/lib/project-probe.d.ts +1 -1
- package/dist/lib/project-probe.js +1 -1
- package/dist/lib/project-pull.d.ts +1 -1
- package/dist/lib/project-pull.js +2 -2
- package/dist/lib/projects.d.ts +1 -1
- package/dist/lib/projects.js +1 -1
- package/dist/lib/sandbox.js +6 -7
- package/dist/lib/secrets/download-keychain.d.ts +47 -0
- package/dist/lib/secrets/download-keychain.js +70 -0
- package/dist/lib/secrets/install-helper.d.ts +20 -1
- package/dist/lib/secrets/install-helper.js +44 -3
- package/dist/lib/session/active.js +17 -1
- package/dist/lib/session/db.d.ts +11 -1
- package/dist/lib/session/db.js +138 -6
- package/dist/lib/session/discover.d.ts +21 -1
- package/dist/lib/session/discover.js +33 -5
- package/dist/lib/session/highlights.d.ts +49 -1
- package/dist/lib/session/highlights.js +95 -0
- package/dist/lib/session/host-link.d.ts +16 -2
- package/dist/lib/session/host-link.js +7 -1
- package/dist/lib/session/parse.js +33 -1
- package/dist/lib/session/remote/remote-list.d.ts +9 -0
- package/dist/lib/session/remote/remote-list.js +19 -2
- package/dist/lib/session/remote/watch.js +11 -2
- package/dist/lib/session/session-cache.d.ts +53 -0
- package/dist/lib/session/session-cache.js +88 -0
- package/dist/lib/session/state.js +1 -1
- package/dist/lib/session/tool-calls.d.ts +2 -0
- package/dist/lib/session/tool-calls.js +1 -1
- package/dist/lib/session/trajectory-html.d.ts +5 -1
- package/dist/lib/session/trajectory-html.js +42 -22
- package/dist/lib/session/trajectory-text.js +9 -2
- package/dist/lib/session/trajectory.js +53 -4
- package/dist/lib/session/types.d.ts +15 -0
- package/dist/lib/sha256-asset.d.ts +26 -0
- package/dist/lib/sha256-asset.js +41 -0
- package/dist/lib/share/backend.d.ts +95 -0
- package/dist/lib/share/backend.js +139 -0
- package/dist/lib/share/delete.d.ts +6 -2
- package/dist/lib/share/delete.js +8 -9
- package/dist/lib/share/html.d.ts +22 -0
- package/dist/lib/share/html.js +88 -0
- package/dist/lib/share/provision.d.ts +18 -4
- package/dist/lib/share/provision.js +38 -4
- package/dist/lib/share/publish.d.ts +39 -15
- package/dist/lib/share/publish.js +55 -26
- package/dist/lib/share/worker-template.js +171 -11
- package/dist/lib/sqlite.js +16 -3
- package/dist/lib/ssh-exec.d.ts +65 -0
- package/dist/lib/ssh-exec.js +120 -4
- package/dist/lib/startup/command-registry.js +1 -1
- package/dist/lib/startup/dev-build.js +3 -3
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +7 -3
- package/dist/lib/subagents-registry.js +1 -1
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/terminal/types.js +1 -1
- package/dist/lib/tmux/orphan-reap.js +1 -1
- package/dist/lib/tmux/session.js +31 -0
- package/dist/lib/traces/backend.d.ts +29 -0
- package/dist/lib/traces/backend.js +46 -0
- package/dist/lib/traces/classify.d.ts +52 -0
- package/dist/lib/traces/classify.js +72 -0
- package/dist/lib/traces/config.d.ts +10 -0
- package/dist/lib/traces/config.js +2 -0
- package/dist/lib/traces/provision.d.ts +19 -0
- package/dist/lib/traces/provision.js +30 -0
- package/dist/lib/traces/sync.d.ts +150 -0
- package/dist/lib/traces/sync.js +447 -0
- package/dist/lib/traces/worker-template.d.ts +4 -0
- package/dist/lib/traces/worker-template.js +159 -0
- package/dist/lib/types.d.ts +18 -19
- package/dist/lib/types.js +14 -0
- package/dist/lib/usage-refresh.d.ts +1 -1
- package/dist/lib/usage-refresh.js +11 -8
- package/dist/lib/workflows.d.ts +1 -1
- package/package.json +4 -6
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -26
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -135
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/dist/lib/usage-fleet.d.ts +0 -32
- package/dist/lib/usage-fleet.js +0 -125
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Make an HTML page self-contained enough to view on share.agents-cli.sh.
|
|
3
|
+
*
|
|
4
|
+
* Chrome "Save as" (and some renderers) leave `file:///…html#section` TOC
|
|
5
|
+
* links and sibling image paths. Those work on disk and 404 on the share
|
|
6
|
+
* Worker, which only stores the one PUT body. Rewrite same-document file
|
|
7
|
+
* anchors to `#section`, and inline local image files as data URIs.
|
|
8
|
+
*
|
|
9
|
+
* https citations, data: URIs already in the page, and missing files are
|
|
10
|
+
* left alone. This is not a bundler — it only fixes what would otherwise
|
|
11
|
+
* render as a broken page after a single-file publish.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
14
|
+
import { dirname, extname, resolve } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
/** Skip inlining a sibling that would bloat the page past a reasonable PUT. */
|
|
17
|
+
export const MAX_INLINE_BYTES = 4 * 1024 * 1024;
|
|
18
|
+
const IMAGE_MIME = {
|
|
19
|
+
'.png': 'image/png',
|
|
20
|
+
'.jpg': 'image/jpeg',
|
|
21
|
+
'.jpeg': 'image/jpeg',
|
|
22
|
+
'.gif': 'image/gif',
|
|
23
|
+
'.webp': 'image/webp',
|
|
24
|
+
'.svg': 'image/svg+xml',
|
|
25
|
+
'.ico': 'image/x-icon',
|
|
26
|
+
'.avif': 'image/avif',
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* `href="file:///tmp/page.html#purpose"` → `href="#purpose"`.
|
|
30
|
+
* Bare `file:///tmp/page.html` (no hash) becomes `href="#"`.
|
|
31
|
+
*/
|
|
32
|
+
export function rewriteFileAnchors(html) {
|
|
33
|
+
return html.replace(/\b(href\s*=\s*["'])file:\/\/[^"'#]*(#[^"']*)?(["'])/gi, (_m, attr, hash, quote) => `${attr}${hash ?? '#'}${quote}`);
|
|
34
|
+
}
|
|
35
|
+
function isRemoteOrSpecial(url) {
|
|
36
|
+
return (url.startsWith('data:') ||
|
|
37
|
+
url.startsWith('http://') ||
|
|
38
|
+
url.startsWith('https://') ||
|
|
39
|
+
url.startsWith('//') ||
|
|
40
|
+
url.startsWith('#') ||
|
|
41
|
+
url.startsWith('mailto:') ||
|
|
42
|
+
url.startsWith('javascript:') ||
|
|
43
|
+
url.startsWith('agents:'));
|
|
44
|
+
}
|
|
45
|
+
function localImagePath(url, htmlDir) {
|
|
46
|
+
if (!url || isRemoteOrSpecial(url))
|
|
47
|
+
return null;
|
|
48
|
+
const withoutHash = url.split('#')[0] ?? url;
|
|
49
|
+
let fsPath;
|
|
50
|
+
if (withoutHash.startsWith('file:')) {
|
|
51
|
+
try {
|
|
52
|
+
fsPath = fileURLToPath(withoutHash);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
fsPath = resolve(htmlDir, withoutHash);
|
|
60
|
+
}
|
|
61
|
+
if (!existsSync(fsPath))
|
|
62
|
+
return null;
|
|
63
|
+
const mime = IMAGE_MIME[extname(fsPath).toLowerCase()];
|
|
64
|
+
if (!mime)
|
|
65
|
+
return null;
|
|
66
|
+
return { path: fsPath, mime };
|
|
67
|
+
}
|
|
68
|
+
/** Replace local image src/href with a data URI when the file exists next to the page. */
|
|
69
|
+
export function inlineLocalAssets(html, htmlPath) {
|
|
70
|
+
const dir = dirname(htmlPath);
|
|
71
|
+
return html.replace(/\b((?:src|href)\s*=\s*["'])([^"']+)(["'])/gi, (full, pre, url, post) => {
|
|
72
|
+
const local = localImagePath(url, dir);
|
|
73
|
+
if (!local)
|
|
74
|
+
return full;
|
|
75
|
+
try {
|
|
76
|
+
const buf = readFileSync(local.path);
|
|
77
|
+
if (buf.length > MAX_INLINE_BYTES)
|
|
78
|
+
return full;
|
|
79
|
+
return `${pre}data:${local.mime};base64,${buf.toString('base64')}${post}`;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return full;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
export function prepareShareHtml(html, htmlPath) {
|
|
87
|
+
return inlineLocalAssets(rewriteFileAnchors(html), htmlPath);
|
|
88
|
+
}
|
|
@@ -61,6 +61,18 @@ export interface UpdateWorkerResult {
|
|
|
61
61
|
/** True when the upload was skipped because `previousHash` already matched. */
|
|
62
62
|
skipped: boolean;
|
|
63
63
|
}
|
|
64
|
+
/** Cloudflare `secret_text` name the Worker reads as `env.PHOENIX_ID_BASE`. */
|
|
65
|
+
export declare const WORKER_PHOENIX_ID_BASE_SECRET = "PHOENIX_ID_BASE";
|
|
66
|
+
export type UpdateWorkerOpts = ProvisionOptions & {
|
|
67
|
+
force?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Phoenix ID base URL to bind as `PHOENIX_ID_BASE` after the script upload.
|
|
70
|
+
* Pass this for a managed deployment; omit it for a pure-BYO Worker (which
|
|
71
|
+
* authenticates only via WRITE_TOKEN). An empty string is a caller bug and
|
|
72
|
+
* fails loud — do not pass a blank to "skip".
|
|
73
|
+
*/
|
|
74
|
+
phoenixIdBase?: string;
|
|
75
|
+
};
|
|
64
76
|
/**
|
|
65
77
|
* Re-deploy the Worker script against an ALREADY-provisioned endpoint (same
|
|
66
78
|
* account/worker/bucket `deployWorker` was first called with) and idempotently
|
|
@@ -78,7 +90,9 @@ export interface UpdateWorkerResult {
|
|
|
78
90
|
* API right after the upload — the same two-call sequence first-time
|
|
79
91
|
* provisioning already uses (`deployWorker` then `setWorkerSecret`), except
|
|
80
92
|
* `writeToken` here is the caller's EXISTING token, never a freshly generated
|
|
81
|
-
* one.
|
|
93
|
+
* one. A managed deploy also re-applies `PHOENIX_ID_BASE` the same way (the
|
|
94
|
+
* Worker reads it to verify Phoenix bearers); a pure-BYO deploy omits it.
|
|
95
|
+
* Cloudflare's own docs describe exactly this "secrets survive a
|
|
82
96
|
* subsequent write" contract for the sibling `wrangler versions upload
|
|
83
97
|
* --secrets-file` flow ("Secrets not included in the file are preserved from
|
|
84
98
|
* the previous version" —
|
|
@@ -86,9 +100,9 @@ export interface UpdateWorkerResult {
|
|
|
86
100
|
* re-setting the secret to its own value after every deploy gets the same
|
|
87
101
|
* outcome without depending on an unverified upload-time flag.
|
|
88
102
|
*/
|
|
89
|
-
export declare function updateWorker(apiToken: string, accountId: string, workerName: string, bucketName: string, script: string, writeToken: string, previousHash: string | undefined, opts?:
|
|
90
|
-
|
|
91
|
-
|
|
103
|
+
export declare function updateWorker(apiToken: string, accountId: string, workerName: string, bucketName: string, script: string, writeToken: string, previousHash: string | undefined, opts?: UpdateWorkerOpts): Promise<UpdateWorkerResult>;
|
|
104
|
+
/** Add/update a secret_text binding using Cloudflare's Workers Secrets API. */
|
|
105
|
+
export declare function putWorkerSecret(apiToken: string, accountId: string, workerName: string, name: string, text: string, opts?: ProvisionOptions): Promise<void>;
|
|
92
106
|
/** Add/update the WRITE_TOKEN binding using Cloudflare's Workers Secrets API. */
|
|
93
107
|
export declare function setWorkerSecret(apiToken: string, accountId: string, workerName: string, writeToken: string, opts?: ProvisionOptions): Promise<void>;
|
|
94
108
|
/** Enable the free `*.workers.dev` route for the script, and return the account subdomain. */
|
|
@@ -101,6 +101,8 @@ export async function deployWorker(apiToken, accountId, workerName, script, buck
|
|
|
101
101
|
export function hashWorkerScript(script) {
|
|
102
102
|
return createHash('sha256').update(script, 'utf8').digest('hex');
|
|
103
103
|
}
|
|
104
|
+
/** Cloudflare `secret_text` name the Worker reads as `env.PHOENIX_ID_BASE`. */
|
|
105
|
+
export const WORKER_PHOENIX_ID_BASE_SECRET = 'PHOENIX_ID_BASE';
|
|
104
106
|
/**
|
|
105
107
|
* Re-deploy the Worker script against an ALREADY-provisioned endpoint (same
|
|
106
108
|
* account/worker/bucket `deployWorker` was first called with) and idempotently
|
|
@@ -118,7 +120,9 @@ export function hashWorkerScript(script) {
|
|
|
118
120
|
* API right after the upload — the same two-call sequence first-time
|
|
119
121
|
* provisioning already uses (`deployWorker` then `setWorkerSecret`), except
|
|
120
122
|
* `writeToken` here is the caller's EXISTING token, never a freshly generated
|
|
121
|
-
* one.
|
|
123
|
+
* one. A managed deploy also re-applies `PHOENIX_ID_BASE` the same way (the
|
|
124
|
+
* Worker reads it to verify Phoenix bearers); a pure-BYO deploy omits it.
|
|
125
|
+
* Cloudflare's own docs describe exactly this "secrets survive a
|
|
122
126
|
* subsequent write" contract for the sibling `wrangler versions upload
|
|
123
127
|
* --secrets-file` flow ("Secrets not included in the file are preserved from
|
|
124
128
|
* the previous version" —
|
|
@@ -129,6 +133,13 @@ export function hashWorkerScript(script) {
|
|
|
129
133
|
export async function updateWorker(apiToken, accountId, workerName, bucketName, script, writeToken, previousHash, opts = {}) {
|
|
130
134
|
const templateHash = hashWorkerScript(script);
|
|
131
135
|
if (!opts.force && previousHash === templateHash) {
|
|
136
|
+
// Script is current so we skip the upload (which would wipe secrets). A
|
|
137
|
+
// managed Worker still needs PHOENIX_ID_BASE present — a prior upload from
|
|
138
|
+
// a CLI that never set it left the secret missing, and this is the heal
|
|
139
|
+
// that does not require --force or a dummy template bump.
|
|
140
|
+
if (opts.phoenixIdBase !== undefined) {
|
|
141
|
+
await applyPhoenixIdBaseSecret(apiToken, accountId, workerName, opts.phoenixIdBase, opts);
|
|
142
|
+
}
|
|
132
143
|
return { templateHash, skipped: true };
|
|
133
144
|
}
|
|
134
145
|
await deployWorker(apiToken, accountId, workerName, script, bucketName, opts);
|
|
@@ -143,18 +154,41 @@ export async function updateWorker(apiToken, accountId, workerName, bucketName,
|
|
|
143
154
|
const detail = e instanceof Error ? e.message : String(e);
|
|
144
155
|
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})`);
|
|
145
156
|
}
|
|
157
|
+
if (opts.phoenixIdBase !== undefined) {
|
|
158
|
+
try {
|
|
159
|
+
await applyPhoenixIdBaseSecret(apiToken, accountId, workerName, opts.phoenixIdBase, opts);
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
163
|
+
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})`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
146
166
|
return { templateHash, skipped: false };
|
|
147
167
|
}
|
|
148
|
-
/** Add/update
|
|
149
|
-
export async function
|
|
168
|
+
/** Add/update a secret_text binding using Cloudflare's Workers Secrets API. */
|
|
169
|
+
export async function putWorkerSecret(apiToken, accountId, workerName, name, text, opts = {}) {
|
|
150
170
|
const request = opts.request ?? defaultCloudflareRequester;
|
|
151
171
|
await request({
|
|
152
172
|
apiToken,
|
|
153
173
|
method: 'PUT',
|
|
154
174
|
pathname: `/accounts/${accountId}/workers/scripts/${workerName}/secrets`,
|
|
155
|
-
body: { name
|
|
175
|
+
body: { name, text, type: 'secret_text' },
|
|
156
176
|
});
|
|
157
177
|
}
|
|
178
|
+
/** Add/update the WRITE_TOKEN binding using Cloudflare's Workers Secrets API. */
|
|
179
|
+
export async function setWorkerSecret(apiToken, accountId, workerName, writeToken, opts = {}) {
|
|
180
|
+
await putWorkerSecret(apiToken, accountId, workerName, 'WRITE_TOKEN', writeToken, opts);
|
|
181
|
+
}
|
|
182
|
+
function normalizePhoenixIdBase(phoenixIdBase) {
|
|
183
|
+
const base = phoenixIdBase.replace(/\/+$/, '').trim();
|
|
184
|
+
if (!base) {
|
|
185
|
+
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.');
|
|
186
|
+
}
|
|
187
|
+
return base;
|
|
188
|
+
}
|
|
189
|
+
async function applyPhoenixIdBaseSecret(apiToken, accountId, workerName, phoenixIdBase, opts = {}) {
|
|
190
|
+
await putWorkerSecret(apiToken, accountId, workerName, WORKER_PHOENIX_ID_BASE_SECRET, normalizePhoenixIdBase(phoenixIdBase), opts);
|
|
191
|
+
}
|
|
158
192
|
/** Enable the free `*.workers.dev` route for the script, and return the account subdomain. */
|
|
159
193
|
export async function enableWorkersDev(apiToken, accountId, workerName, opts = {}) {
|
|
160
194
|
const request = opts.request ?? defaultCloudflareRequester;
|
|
@@ -18,10 +18,18 @@ export interface PublishOptions {
|
|
|
18
18
|
*/
|
|
19
19
|
expire?: string;
|
|
20
20
|
contentType?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Server-enforced visibility (RUSH-3135). `public` (default) is listed in
|
|
23
|
+
* the gallery; `unlisted` is a capability URL (GET still 200, X-Robots-Tag:
|
|
24
|
+
* noindex, hidden from gallery/list). `org` is Phase 2 — the Worker 400s it.
|
|
25
|
+
*/
|
|
26
|
+
visibility?: ShareVisibility;
|
|
21
27
|
/**
|
|
22
28
|
* Hide this page from the public `/<user>` gallery and `agents artifacts share list`
|
|
23
29
|
* (metadata `visibility=unlisted`). The direct URL is still world-readable —
|
|
24
|
-
* unlisted, not secret (RUSH-2443). Alias of `--
|
|
30
|
+
* unlisted, not secret (RUSH-2443). Alias of `--visibility unlisted` /
|
|
31
|
+
* `--private` on the CLI. Kept so `sessions share` and existing callers do
|
|
32
|
+
* not break.
|
|
25
33
|
*/
|
|
26
34
|
unlisted?: boolean;
|
|
27
35
|
/**
|
|
@@ -39,8 +47,12 @@ export interface PublishOptions {
|
|
|
39
47
|
githubUser?: string;
|
|
40
48
|
/** DI seam for tests — override the persisted share endpoint config. */
|
|
41
49
|
config?: ShareConfig;
|
|
42
|
-
/** DI seam for tests — override the keychain-backed write token. */
|
|
50
|
+
/** DI seam for tests — override the keychain-backed write token. Selects BYO. */
|
|
43
51
|
writeToken?: string;
|
|
52
|
+
/** DI seam for tests — override `readSession()`. `null` means signed out. */
|
|
53
|
+
session?: import('../identity/client.js').PhoenixSession | null;
|
|
54
|
+
/** Force the BYO Cloudflare path even when signed in. */
|
|
55
|
+
byo?: boolean;
|
|
44
56
|
/** DI seam for tests — override the real HTTP PUT. */
|
|
45
57
|
uploader?: PutFn;
|
|
46
58
|
/** DI seam for tests — override cover capture (returns a PNG buffer or null). */
|
|
@@ -55,22 +67,26 @@ export interface PublishOptions {
|
|
|
55
67
|
/**
|
|
56
68
|
* Structured metadata (`--meta key=value`, repeatable). Keys are validated by
|
|
57
69
|
* {@link parseMetaEntries} — lowercase `[a-z0-9-]`, and may not collide with
|
|
58
|
-
* {@link RESERVED_META_KEYS}
|
|
70
|
+
* {@link RESERVED_META_KEYS} (Worker-stamped owner/visibility/expires-at,
|
|
71
|
+
* plus provenance the CLI sets automatically).
|
|
59
72
|
*/
|
|
60
73
|
meta?: Record<string, string>;
|
|
61
74
|
/**
|
|
62
75
|
* Skip revision retention on this publish — overwrite an existing slug's
|
|
63
76
|
* object in place with no `<slug>/rev-<ts>` backup of the version it replaces
|
|
64
|
-
* (default: keep it; see docs/
|
|
77
|
+
* (default: keep it; see docs/distribution.md).
|
|
65
78
|
*/
|
|
66
79
|
noRevision?: boolean;
|
|
67
80
|
/** DI seam for tests — override provenance auto-capture (agent/session/host/repo/date). */
|
|
68
81
|
provenance?: ShareProvenance;
|
|
69
82
|
}
|
|
83
|
+
export type ShareVisibility = 'public' | 'unlisted';
|
|
70
84
|
export interface PublishResult {
|
|
71
85
|
url: string;
|
|
72
86
|
expiresAt?: string;
|
|
73
87
|
coverUrl?: string;
|
|
88
|
+
/** Server-enforced visibility stamped on the object. */
|
|
89
|
+
visibility?: ShareVisibility;
|
|
74
90
|
/** True when the page was published with `visibility=unlisted`. */
|
|
75
91
|
unlisted?: boolean;
|
|
76
92
|
/** The label stored with this share — explicit (`--label`) or derived. */
|
|
@@ -78,6 +94,11 @@ export interface PublishResult {
|
|
|
78
94
|
/** Whether `label` came from `--label` or was auto-derived. */
|
|
79
95
|
labelSource?: 'explicit' | 'derived';
|
|
80
96
|
}
|
|
97
|
+
/** `--unlisted` / `{ unlisted: true }` map to `unlisted`; otherwise `visibility` (default public). */
|
|
98
|
+
export declare function resolveShareVisibility(opts?: {
|
|
99
|
+
visibility?: ShareVisibility;
|
|
100
|
+
unlisted?: boolean;
|
|
101
|
+
}): ShareVisibility;
|
|
81
102
|
export interface ShareProvenance {
|
|
82
103
|
/** Harness/agent name (`AGENTS_AGENT_NAME`), when publishing from an agent run. */
|
|
83
104
|
agent?: string;
|
|
@@ -91,11 +112,12 @@ export interface ShareProvenance {
|
|
|
91
112
|
date?: string;
|
|
92
113
|
}
|
|
93
114
|
/**
|
|
94
|
-
* Reserved `customMetadata` keys
|
|
95
|
-
*
|
|
96
|
-
*
|
|
115
|
+
* Reserved `customMetadata` keys a `--meta key=value` may not target (see
|
|
116
|
+
* {@link parseMetaEntries}). Matches the Worker's `RESERVED_METADATA_KEYS`:
|
|
117
|
+
* provenance the CLI sets automatically, plus `expires-at` / `visibility` /
|
|
118
|
+
* `owner` which the Worker stamps itself.
|
|
97
119
|
*/
|
|
98
|
-
export declare const RESERVED_META_KEYS: readonly ["agent", "session", "host", "repo", "date", "label", "label-source"];
|
|
120
|
+
export declare const RESERVED_META_KEYS: readonly ["expires-at", "visibility", "owner", "agent", "session", "host", "repo", "date", "label", "label-source"];
|
|
99
121
|
/**
|
|
100
122
|
* Auto-capture publish provenance from the exec env, git, and the local clock.
|
|
101
123
|
* Every field is present only when the environment genuinely carries it — a
|
|
@@ -113,8 +135,9 @@ export declare function resolveShareProvenance(opts?: {
|
|
|
113
135
|
/**
|
|
114
136
|
* Parse repeated `--meta key=value` CLI args into a validated metadata record.
|
|
115
137
|
* Keys are lowercase `[a-z0-9-]`, up to 64 characters, and may not collide with
|
|
116
|
-
* {@link RESERVED_META_KEYS} (
|
|
117
|
-
* label/label-source) — throws
|
|
138
|
+
* {@link RESERVED_META_KEYS} (Worker-stamped owner/visibility/expires-at, plus
|
|
139
|
+
* the provenance the CLI sets automatically, plus label/label-source) — throws
|
|
140
|
+
* naming the offending pair on any violation.
|
|
118
141
|
*/
|
|
119
142
|
export declare function parseMetaEntries(pairs: string[]): Record<string, string>;
|
|
120
143
|
/** Throws when the combined `customMetadata` payload would exceed {@link MAX_METADATA_BYTES}. */
|
|
@@ -201,16 +224,17 @@ export declare function slugify(name: string): string;
|
|
|
201
224
|
/** The project the file belongs to — git repo name, else the cwd's basename. */
|
|
202
225
|
export declare function detectProject(dir?: string): string;
|
|
203
226
|
/**
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
227
|
+
* Readable slug + view id: `<feature>-<16hex>`. A leading `plan-` on the
|
|
228
|
+
* filename is dropped. The project no longer prefixes the slug — the URL
|
|
229
|
+
* namespace is already the publisher's handle (`/<you>/<slug>`).
|
|
207
230
|
*
|
|
208
231
|
* The tail is 8 random bytes (64-bit, 16 hex chars). Reads are public — the URL is
|
|
209
232
|
* the only capability — so the nonce must be genuinely infeasible to brute-force,
|
|
210
|
-
* not merely unlisted; 64 bits puts a blind guess out of reach. (See docs/
|
|
233
|
+
* not merely unlisted; 64 bits puts a blind guess out of reach. (See docs/distribution.md
|
|
211
234
|
* §Security for the threat model and `--expire` for sensitive content.)
|
|
235
|
+
* `--slug` still publishes that exact slug, no view-id appended.
|
|
212
236
|
*/
|
|
213
|
-
export declare function defaultSlug(filePath: string,
|
|
237
|
+
export declare function defaultSlug(filePath: string, _dir?: string): string;
|
|
214
238
|
/**
|
|
215
239
|
* Best-effort OG cover: capture a screenshot, upload it as `<slug>.png`, and return
|
|
216
240
|
* the page body with og:image meta injected (+ the cover URL). All IO is injected
|
|
@@ -10,17 +10,39 @@ import { basename } from 'node:path';
|
|
|
10
10
|
import { execFileSync } from 'node:child_process';
|
|
11
11
|
import { randomBytes } from 'node:crypto';
|
|
12
12
|
import { hostname as osHostname } from 'node:os';
|
|
13
|
-
import { readShareConfig
|
|
13
|
+
import { readShareConfig } from './config.js';
|
|
14
14
|
import { resolveGitHubUsername } from '../git.js';
|
|
15
|
+
import { resolveShareBackend, sanitizeShareNamespace } from './backend.js';
|
|
15
16
|
import { captureCover, OG_WIDTH, OG_HEIGHT, OG_SCALE } from './capture.js';
|
|
16
17
|
import { deriveMeta, injectOgMeta } from './og.js';
|
|
17
18
|
import { injectAnalyticsBeacon } from './analytics.js';
|
|
19
|
+
import { prepareShareHtml } from './html.js';
|
|
20
|
+
/** `--unlisted` / `{ unlisted: true }` map to `unlisted`; otherwise `visibility` (default public). */
|
|
21
|
+
export function resolveShareVisibility(opts = {}) {
|
|
22
|
+
if (opts.unlisted === true)
|
|
23
|
+
return 'unlisted';
|
|
24
|
+
if (opts.visibility === 'unlisted')
|
|
25
|
+
return 'unlisted';
|
|
26
|
+
return 'public';
|
|
27
|
+
}
|
|
18
28
|
/**
|
|
19
|
-
* Reserved `customMetadata` keys
|
|
20
|
-
*
|
|
21
|
-
*
|
|
29
|
+
* Reserved `customMetadata` keys a `--meta key=value` may not target (see
|
|
30
|
+
* {@link parseMetaEntries}). Matches the Worker's `RESERVED_METADATA_KEYS`:
|
|
31
|
+
* provenance the CLI sets automatically, plus `expires-at` / `visibility` /
|
|
32
|
+
* `owner` which the Worker stamps itself.
|
|
22
33
|
*/
|
|
23
|
-
export const RESERVED_META_KEYS = [
|
|
34
|
+
export const RESERVED_META_KEYS = [
|
|
35
|
+
'expires-at',
|
|
36
|
+
'visibility',
|
|
37
|
+
'owner',
|
|
38
|
+
'agent',
|
|
39
|
+
'session',
|
|
40
|
+
'host',
|
|
41
|
+
'repo',
|
|
42
|
+
'date',
|
|
43
|
+
'label',
|
|
44
|
+
'label-source',
|
|
45
|
+
];
|
|
24
46
|
const META_KEY_RE = /^[a-z0-9-]{1,64}$/;
|
|
25
47
|
/**
|
|
26
48
|
* Auto-capture publish provenance from the exec env, git, and the local clock.
|
|
@@ -43,8 +65,9 @@ export function resolveShareProvenance(opts = {}) {
|
|
|
43
65
|
/**
|
|
44
66
|
* Parse repeated `--meta key=value` CLI args into a validated metadata record.
|
|
45
67
|
* Keys are lowercase `[a-z0-9-]`, up to 64 characters, and may not collide with
|
|
46
|
-
* {@link RESERVED_META_KEYS} (
|
|
47
|
-
* label/label-source) — throws
|
|
68
|
+
* {@link RESERVED_META_KEYS} (Worker-stamped owner/visibility/expires-at, plus
|
|
69
|
+
* the provenance the CLI sets automatically, plus label/label-source) — throws
|
|
70
|
+
* naming the offending pair on any violation.
|
|
48
71
|
*/
|
|
49
72
|
export function parseMetaEntries(pairs) {
|
|
50
73
|
const meta = {};
|
|
@@ -59,7 +82,7 @@ export function parseMetaEntries(pairs) {
|
|
|
59
82
|
throw new Error(`Bad --meta key '${key}'. Keys are lowercase letters, digits, and hyphens, up to 64 characters.`);
|
|
60
83
|
}
|
|
61
84
|
if (RESERVED_META_KEYS.includes(key)) {
|
|
62
|
-
throw new Error(`--meta ${key}=… is reserved (set automatically from your session/git) — pass a different key.`);
|
|
85
|
+
throw new Error(`--meta ${key}=… is reserved (Worker-stamped, or set automatically from your session/git) — pass a different key.`);
|
|
63
86
|
}
|
|
64
87
|
meta[key] = value;
|
|
65
88
|
}
|
|
@@ -293,7 +316,7 @@ export function slugify(name) {
|
|
|
293
316
|
.slice(0, 60) || 'page');
|
|
294
317
|
}
|
|
295
318
|
function sanitizeSlugPart(s) {
|
|
296
|
-
return s
|
|
319
|
+
return sanitizeShareNamespace(s);
|
|
297
320
|
}
|
|
298
321
|
/** The current repo's name, or undefined outside a git checkout — never a
|
|
299
322
|
* fallback guess, since callers that want one (detectProject) supply it themselves. */
|
|
@@ -317,18 +340,19 @@ export function detectProject(dir = process.cwd()) {
|
|
|
317
340
|
return gitRepoName(dir) ?? (sanitizeSlugPart(basename(dir)) || 'share');
|
|
318
341
|
}
|
|
319
342
|
/**
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
343
|
+
* Readable slug + view id: `<feature>-<16hex>`. A leading `plan-` on the
|
|
344
|
+
* filename is dropped. The project no longer prefixes the slug — the URL
|
|
345
|
+
* namespace is already the publisher's handle (`/<you>/<slug>`).
|
|
323
346
|
*
|
|
324
347
|
* The tail is 8 random bytes (64-bit, 16 hex chars). Reads are public — the URL is
|
|
325
348
|
* the only capability — so the nonce must be genuinely infeasible to brute-force,
|
|
326
|
-
* not merely unlisted; 64 bits puts a blind guess out of reach. (See docs/
|
|
349
|
+
* not merely unlisted; 64 bits puts a blind guess out of reach. (See docs/distribution.md
|
|
327
350
|
* §Security for the threat model and `--expire` for sensitive content.)
|
|
351
|
+
* `--slug` still publishes that exact slug, no view-id appended.
|
|
328
352
|
*/
|
|
329
|
-
export function defaultSlug(filePath,
|
|
353
|
+
export function defaultSlug(filePath, _dir) {
|
|
330
354
|
const feature = slugify(filePath).replace(/^plan-/, '') || 'page';
|
|
331
|
-
return `${
|
|
355
|
+
return `${feature}-${randomBytes(8).toString('hex')}`;
|
|
332
356
|
}
|
|
333
357
|
function guessContentType(filePath) {
|
|
334
358
|
if (/\.html?$/i.test(filePath))
|
|
@@ -419,14 +443,12 @@ export function buildShareKey(username, slugPart) {
|
|
|
419
443
|
return `${user}/${part}`;
|
|
420
444
|
}
|
|
421
445
|
export async function publishFile(filePath, opts = {}) {
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
const analyticsToken = opts.analyticsToken ?? cfg.analyticsToken;
|
|
429
|
-
return publishToEndpoint(filePath, { baseUrl: cfg.baseUrl, token }, {
|
|
446
|
+
const backend = resolveShareBackend(opts);
|
|
447
|
+
const username = backend.kind === 'managed'
|
|
448
|
+
? backend.namespace
|
|
449
|
+
: await resolveShareUsername({ githubUser: opts.githubUser || backend.namespace || undefined });
|
|
450
|
+
const analyticsToken = opts.analyticsToken ?? (backend.kind === 'byo' ? (opts.config ?? readShareConfig())?.analyticsToken : undefined);
|
|
451
|
+
return publishToEndpoint(filePath, { baseUrl: backend.baseUrl, token: backend.token }, {
|
|
430
452
|
...opts,
|
|
431
453
|
githubUser: username,
|
|
432
454
|
analyticsToken,
|
|
@@ -437,7 +459,8 @@ export async function publishToEndpoint(filePath, endpoint, opts = {}) {
|
|
|
437
459
|
const slugPart = (opts.slug ?? defaultSlug(filePath)).replace(/^\/+/, '');
|
|
438
460
|
const key = buildShareKey(username, slugPart);
|
|
439
461
|
const expiresAt = resolveExpire(opts.expire);
|
|
440
|
-
const
|
|
462
|
+
const visibility = resolveShareVisibility(opts);
|
|
463
|
+
const unlisted = visibility === 'unlisted';
|
|
441
464
|
const pageUrl = `${endpoint.baseUrl.replace(/\/+$/, '')}/${key}`;
|
|
442
465
|
const provenance = opts.provenance ?? resolveShareProvenance();
|
|
443
466
|
const meta = opts.meta ?? {};
|
|
@@ -449,6 +472,9 @@ export async function publishToEndpoint(filePath, endpoint, opts = {}) {
|
|
|
449
472
|
let body = readFileSync(filePath);
|
|
450
473
|
let coverUrl;
|
|
451
474
|
const isHtml = /\.html?$/i.test(filePath);
|
|
475
|
+
if (isHtml) {
|
|
476
|
+
body = Buffer.from(prepareShareHtml(body.toString('utf8'), filePath), 'utf8');
|
|
477
|
+
}
|
|
452
478
|
const explicitLabel = opts.label?.trim();
|
|
453
479
|
// sanitizeLabel here (not just inside deriveLabel) covers an explicit
|
|
454
480
|
// --label/--title the caller typed with an embedded newline — deriveLabel
|
|
@@ -492,8 +518,7 @@ export async function publishToEndpoint(filePath, endpoint, opts = {}) {
|
|
|
492
518
|
const h = { authorization: `Bearer ${endpoint.token}`, 'content-type': contentType };
|
|
493
519
|
if (expiresAt)
|
|
494
520
|
h['x-share-expires-at'] = expiresAt;
|
|
495
|
-
|
|
496
|
-
h['x-share-visibility'] = 'unlisted';
|
|
521
|
+
h['x-share-visibility'] = visibility;
|
|
497
522
|
// Every free-text header goes through toHeaderValue: a non-latin1 code point
|
|
498
523
|
// anywhere in a label, a repo name, or a --meta value throws inside fetch and
|
|
499
524
|
// crashes the publish outright.
|
|
@@ -541,6 +566,9 @@ export async function publishToEndpoint(filePath, endpoint, opts = {}) {
|
|
|
541
566
|
}
|
|
542
567
|
const r = await put(pageUrl, body, authHeaders(opts.contentType ?? guessContentType(filePath)));
|
|
543
568
|
if (!r.ok) {
|
|
569
|
+
if (r.status === 409) {
|
|
570
|
+
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.`);
|
|
571
|
+
}
|
|
544
572
|
throw new Error(`Publish failed (${r.status}) for ${pageUrl}. Check the write token, or that 'agents artifacts setup' completed.`);
|
|
545
573
|
}
|
|
546
574
|
return {
|
|
@@ -549,6 +577,7 @@ export async function publishToEndpoint(filePath, endpoint, opts = {}) {
|
|
|
549
577
|
coverUrl,
|
|
550
578
|
label,
|
|
551
579
|
labelSource,
|
|
580
|
+
visibility,
|
|
552
581
|
...(unlisted ? { unlisted: true } : {}),
|
|
553
582
|
};
|
|
554
583
|
}
|