@phnx-labs/agents-cli 1.22.53 → 1.22.55
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 +208 -0
- package/README.md +59 -9
- package/dist/bootstrap.js +55 -154
- package/dist/cli/command-registry.d.ts +5 -0
- package/dist/cli/command-registry.js +8 -1
- package/dist/commands/accounts.js +219 -173
- package/dist/commands/apply.js +6 -3
- package/dist/commands/auth-mint.d.ts +8 -0
- package/dist/commands/auth-mint.js +96 -0
- package/dist/commands/auth.js +5 -1
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cost.js +8 -2
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/doctor.js +6 -1
- package/dist/commands/exec.js +10 -8
- package/dist/commands/focus.d.ts +1 -0
- package/dist/commands/focus.js +2 -2
- package/dist/commands/go.d.ts +5 -4
- package/dist/commands/go.js +7 -7
- package/dist/commands/insights.js +9 -0
- package/dist/commands/monitors.js +85 -30
- package/dist/commands/output.js +8 -2
- package/dist/commands/repo.js +18 -0
- package/dist/commands/routines.js +31 -2
- package/dist/commands/secrets.js +33 -14
- package/dist/commands/sessions.d.ts +20 -12
- package/dist/commands/sessions.js +64 -20
- package/dist/commands/setup-accounts.d.ts +8 -0
- package/dist/commands/setup-accounts.js +47 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +11 -2
- package/dist/commands/share.d.ts +79 -3
- package/dist/commands/share.js +347 -18
- package/dist/commands/ssh.d.ts +7 -0
- package/dist/commands/ssh.js +18 -2
- package/dist/commands/status.js +14 -0
- package/dist/commands/view.d.ts +11 -1
- package/dist/commands/view.js +35 -7
- package/dist/lib/account-registry.js +15 -3
- package/dist/lib/accounting/rotate.d.ts +20 -6
- package/dist/lib/accounting/rotate.js +38 -7
- package/dist/lib/accounting/usage.d.ts +68 -1
- package/dist/lib/accounting/usage.js +116 -10
- package/dist/lib/agent-spec/agents.d.ts +5 -2
- package/dist/lib/agent-spec/agents.js +25 -7
- package/dist/lib/analytics/mix-commands.js +12 -6
- package/dist/lib/auth-mint.d.ts +150 -0
- package/dist/lib/auth-mint.js +434 -0
- package/dist/lib/browser/cdp.d.ts +1 -1
- package/dist/lib/browser/cdp.js +1 -1
- package/dist/lib/browser/ffmpeg.d.ts +12 -0
- package/dist/lib/browser/ffmpeg.js +184 -0
- package/dist/lib/browser/remote-control.d.ts +9 -7
- package/dist/lib/browser/remote-control.js +9 -7
- package/dist/lib/browser/service.js +119 -25
- package/dist/lib/claude-account-token.d.ts +10 -0
- package/dist/lib/claude-account-token.js +14 -4
- package/dist/lib/config-drift.d.ts +37 -0
- package/dist/lib/config-drift.js +72 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +19 -0
- package/dist/lib/daemon/auth-sync-service.js +34 -0
- package/dist/lib/daemon/browser-task-reap-service.d.ts +14 -0
- package/dist/lib/daemon/browser-task-reap-service.js +26 -0
- package/dist/lib/daemon/daemon.js +87 -176
- package/dist/lib/daemon/heartbeat-service.d.ts +13 -0
- package/dist/lib/daemon/heartbeat-service.js +26 -0
- package/dist/lib/daemon/monitor-engine-service.d.ts +9 -5
- package/dist/lib/daemon/monitor-engine-service.js +15 -7
- package/dist/lib/daemon/runner.d.ts +15 -0
- package/dist/lib/daemon/runner.js +23 -0
- package/dist/lib/daemon/secrets-broker-service.d.ts +5 -4
- package/dist/lib/daemon/secrets-broker-service.js +17 -32
- package/dist/lib/daemon/service.d.ts +2 -2
- package/dist/lib/daemon/service.js +1 -1
- package/dist/lib/daemon/session-state-service.d.ts +21 -0
- package/dist/lib/daemon/session-state-service.js +34 -0
- package/dist/lib/daemon/supervisor.d.ts +17 -7
- package/dist/lib/daemon/supervisor.js +87 -14
- package/dist/lib/daemon/tmux-reap-service.d.ts +11 -0
- package/dist/lib/daemon/tmux-reap-service.js +28 -0
- package/dist/lib/daemon/webhook-receiver-service.d.ts +9 -0
- package/dist/lib/daemon/webhook-receiver-service.js +17 -0
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +25 -0
- package/dist/lib/device-config.d.ts +3 -3
- package/dist/lib/device-config.js +5 -5
- package/dist/lib/devices/connect.d.ts +26 -0
- package/dist/lib/devices/connect.js +48 -1
- package/dist/lib/devices/doctor-findings.d.ts +5 -1
- package/dist/lib/devices/doctor-findings.js +19 -1
- package/dist/lib/devices/harness-inventory.js +5 -2
- package/dist/lib/exec.d.ts +28 -0
- package/dist/lib/exec.js +73 -7
- package/dist/lib/feed/feed.d.ts +1 -1
- package/dist/lib/feed/feed.js +23 -1
- package/dist/lib/feed-broadcast.js +1 -1
- package/dist/lib/fleet/apply.d.ts +11 -0
- package/dist/lib/fleet/apply.js +23 -3
- package/dist/lib/fleet/auth-sync.js +5 -3
- package/dist/lib/help.d.ts +9 -0
- package/dist/lib/help.js +29 -1
- package/dist/lib/hosts/passthrough.d.ts +1 -10
- package/dist/lib/hosts/passthrough.js +1 -13
- package/dist/lib/installations/versions.js +9 -1
- package/dist/lib/linux-userns.d.ts +58 -0
- package/dist/lib/linux-userns.js +116 -0
- package/dist/lib/memory.d.ts +26 -0
- package/dist/lib/memory.js +80 -1
- package/dist/lib/monitors/config.d.ts +11 -0
- package/dist/lib/monitors/config.js +8 -0
- package/dist/lib/monitors/engine.d.ts +5 -1
- package/dist/lib/monitors/engine.js +13 -4
- package/dist/lib/monitors/state.d.ts +37 -1
- package/dist/lib/monitors/state.js +79 -4
- package/dist/lib/permissions-registry.d.ts +2 -0
- package/dist/lib/permissions-registry.js +116 -14
- package/dist/lib/permissions.d.ts +5 -3
- package/dist/lib/permissions.js +25 -27
- package/dist/lib/profiles.d.ts +8 -7
- package/dist/lib/profiles.js +12 -0
- package/dist/lib/project-key.d.ts +9 -0
- package/dist/lib/project-key.js +11 -0
- package/dist/lib/scheduling/routines.d.ts +47 -0
- package/dist/lib/scheduling/routines.js +70 -1
- package/dist/lib/secrets/bundles.d.ts +35 -0
- package/dist/lib/secrets/bundles.js +78 -1
- package/dist/lib/secrets/push.d.ts +3 -8
- package/dist/lib/secrets/push.js +18 -14
- package/dist/lib/secrets/remote.d.ts +9 -18
- package/dist/lib/secrets/remote.js +11 -26
- package/dist/lib/secrets/reserved-sync.d.ts +65 -0
- package/dist/lib/secrets/reserved-sync.js +129 -0
- package/dist/lib/self-heal/checks/hook-manifest.d.ts +2 -0
- package/dist/lib/self-heal/checks/hook-manifest.js +56 -0
- package/dist/lib/self-heal/registry.js +4 -0
- package/dist/lib/self-heal/types.d.ts +1 -1
- package/dist/lib/session/active.js +1 -4
- package/dist/lib/session/db.d.ts +41 -5
- package/dist/lib/session/db.js +132 -30
- package/dist/lib/session/discover.d.ts +32 -4
- package/dist/lib/session/discover.js +119 -25
- package/dist/lib/session/insights.d.ts +14 -0
- package/dist/lib/session/insights.js +25 -2
- package/dist/lib/session/linear.js +1 -1
- package/dist/lib/session/shell-programs.d.ts +17 -0
- package/dist/lib/session/shell-programs.js +21 -0
- package/dist/lib/session/state.js +2 -1
- package/dist/lib/session/stream-render.js +2 -1
- package/dist/lib/session/tool-calls.js +2 -5
- package/dist/lib/session/trajectory-html.js +2 -1
- package/dist/lib/session/trajectory.js +3 -12
- package/dist/lib/session/types.d.ts +8 -0
- package/dist/lib/share/capture.js +11 -2
- package/dist/lib/share/publish.d.ts +56 -5
- package/dist/lib/share/publish.js +126 -18
- package/dist/lib/share/worker-template.d.ts +3 -12
- package/dist/lib/share/worker-template.js +860 -59
- package/dist/lib/startup/root-command.js +2 -1
- package/dist/lib/state.d.ts +16 -0
- package/dist/lib/state.js +178 -46
- package/dist/lib/sync-status.d.ts +4 -0
- package/dist/lib/sync-status.js +3 -0
- package/dist/lib/traces/classify.js +24 -19
- package/dist/lib/usage-refresh.js +2 -1
- package/dist/lib/view-types.d.ts +7 -0
- package/package.json +9 -2
package/dist/commands/share.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Command } from 'commander';
|
|
2
2
|
import { type ShareConfig } from '../lib/share/config.js';
|
|
3
3
|
import { type CloudflareRequester } from '../lib/share/provision.js';
|
|
4
|
-
import { type PublishResult } from '../lib/share/publish.js';
|
|
4
|
+
import { type PublishResult, type ShareVisibility } from '../lib/share/publish.js';
|
|
5
5
|
import { deleteShare, type DeleteShareResult } from '../lib/share/delete.js';
|
|
6
6
|
import type { PhoenixSession } from '../lib/identity/client.js';
|
|
7
7
|
export declare function formatSharePublishResult(result: PublishResult, json?: boolean): string;
|
|
@@ -36,6 +36,9 @@ export interface ShareListItem {
|
|
|
36
36
|
repo: string | null;
|
|
37
37
|
/** Count of retained prior versions under this slug (see `share revisions`). */
|
|
38
38
|
revisionCount: number;
|
|
39
|
+
/** The visibility stamped at publish time: public | unlisted | me | org.
|
|
40
|
+
* Missing on Workers that predate this field → treated as public. */
|
|
41
|
+
visibility: ShareVisibility;
|
|
39
42
|
/** Arbitrary `--meta key=value` entries attached at publish time (RUSH-2683)
|
|
40
43
|
* — everything that isn't a reserved provenance/label key. `{}` when none
|
|
41
44
|
* were set, or when the deployed Worker predates this field. */
|
|
@@ -47,12 +50,41 @@ export interface ShareListResult {
|
|
|
47
50
|
count: number;
|
|
48
51
|
objects: ShareListItem[];
|
|
49
52
|
}
|
|
50
|
-
/** DI seam for tests — override the real HTTP GET of the JSON listing route.
|
|
51
|
-
|
|
53
|
+
/** DI seam for tests — override the real HTTP GET of the JSON listing route.
|
|
54
|
+
* The optional `headers` carry the owner bearer for hidden-visibility listings. */
|
|
55
|
+
export type ListingFetchFn = (url: string, headers?: Record<string, string>) => Promise<{
|
|
52
56
|
status: number;
|
|
53
57
|
contentType: string;
|
|
54
58
|
body: string;
|
|
55
59
|
}>;
|
|
60
|
+
export interface ShareEditResult {
|
|
61
|
+
ok: true;
|
|
62
|
+
url: string;
|
|
63
|
+
label: string | null;
|
|
64
|
+
meta: Record<string, string>;
|
|
65
|
+
/** The visibility the page now carries (present on Workers that support the
|
|
66
|
+
* visibility edit; absent from an older deployed template). */
|
|
67
|
+
visibility?: ShareVisibility;
|
|
68
|
+
/** The visibility the page had before this edit, when the Worker reports it. */
|
|
69
|
+
previousVisibility?: ShareVisibility;
|
|
70
|
+
}
|
|
71
|
+
export declare function runShareEdit(target: string, opts: {
|
|
72
|
+
githubUser?: string;
|
|
73
|
+
config?: ShareConfig;
|
|
74
|
+
writeToken?: string;
|
|
75
|
+
byo?: boolean;
|
|
76
|
+
session?: PhoenixSession | null;
|
|
77
|
+
label?: string | null;
|
|
78
|
+
meta?: Record<string, string>;
|
|
79
|
+
metaMode?: 'merge' | 'replace';
|
|
80
|
+
removeMeta?: string[];
|
|
81
|
+
/** Change the page's visibility in place (public | unlisted | me | org).
|
|
82
|
+
* A metadata-only rewrite like label/meta — the body is untouched, so no
|
|
83
|
+
* revision is created. me/org require a Phoenix session. */
|
|
84
|
+
visibility?: ShareVisibility;
|
|
85
|
+
force?: boolean;
|
|
86
|
+
fetchEdit?: typeof fetch;
|
|
87
|
+
}): Promise<ShareEditResult>;
|
|
56
88
|
/** Parse the Worker's listing JSON into a validated result, failing loud (with the
|
|
57
89
|
* outdated-template hint) on any body that isn't the expected shape — an old Worker
|
|
58
90
|
* serves the HTML gallery for a non-empty namespace, which must not be silently
|
|
@@ -79,7 +111,24 @@ export declare function runShareList(opts?: {
|
|
|
79
111
|
session?: string;
|
|
80
112
|
/** Only shares whose label contains this text (case-insensitive substring). */
|
|
81
113
|
label?: string;
|
|
114
|
+
/** Only shares matching every provided `--meta key=value` entry. */
|
|
115
|
+
meta?: Record<string, string>;
|
|
116
|
+
/** Visibility filter. `public` (default) lists only public gallery pages;
|
|
117
|
+
* `unlisted`/`me`/`org` include that owner's hidden pages; `all` includes
|
|
118
|
+
* every active page. Hidden scopes send the owner's bearer and `scope=mine`. */
|
|
119
|
+
scope?: 'public' | 'unlisted' | 'me' | 'org' | 'all';
|
|
82
120
|
}): Promise<ShareListResult>;
|
|
121
|
+
/** Client-side filtering over an already-fetched listing (RUSH-2683) — the
|
|
122
|
+
* Worker has no query surface for this, so it narrows the fetched set instead
|
|
123
|
+
* of a second round trip. `count` reflects the FILTERED set, matching what the
|
|
124
|
+
* caller actually sees. */
|
|
125
|
+
export declare function applyShareListFilters(result: ShareListResult, filters: {
|
|
126
|
+
agent?: string;
|
|
127
|
+
session?: string;
|
|
128
|
+
label?: string;
|
|
129
|
+
meta?: Record<string, string>;
|
|
130
|
+
scope?: 'public' | 'unlisted' | 'me' | 'org' | 'all';
|
|
131
|
+
}): ShareListResult;
|
|
83
132
|
export declare function formatShareList(result: ShareListResult, json?: boolean): string;
|
|
84
133
|
/** One retained prior version of a slug, as reported by the Worker's
|
|
85
134
|
* `?revisions=json` route. */
|
|
@@ -123,6 +172,33 @@ export declare function runShareRevisions(target: string, opts?: {
|
|
|
123
172
|
session?: PhoenixSession | null;
|
|
124
173
|
fetchListing?: ListingFetchFn;
|
|
125
174
|
}): Promise<ShareRevisionsResult>;
|
|
175
|
+
/** The resolved page URL plus a signed-in-owner URL that carries a one-time
|
|
176
|
+
* login ticket, so opening it activates the shared page's inline visibility
|
|
177
|
+
* control (PHNX-3370). */
|
|
178
|
+
export interface ShareOpenResult {
|
|
179
|
+
/** `<user>/<slug>` key of the target page. */
|
|
180
|
+
key: string;
|
|
181
|
+
/** The plain page URL (no ticket). */
|
|
182
|
+
url: string;
|
|
183
|
+
/** The page URL with `?phoenix_ticket=<t>` — open THIS to be the owner. */
|
|
184
|
+
ownerUrl: string;
|
|
185
|
+
}
|
|
186
|
+
/** Mint a one-time login ticket for the signed-in owner and build the URL that
|
|
187
|
+
* opens their own published page **as the owner**, so the inline visibility
|
|
188
|
+
* control is live in the browser. Managed backend only — the control is gated on
|
|
189
|
+
* the Phoenix identity, and BYO (WRITE_TOKEN) endpoints have no per-viewer login.
|
|
190
|
+
* `target` accepts the same three forms as `visibility`/`unshare` (a full URL,
|
|
191
|
+
* `<user>/<slug>`, or a bare slug in the caller's namespace). */
|
|
192
|
+
export declare function runShareOpen(target: string, opts?: {
|
|
193
|
+
githubUser?: string;
|
|
194
|
+
config?: ShareConfig;
|
|
195
|
+
session?: PhoenixSession | null;
|
|
196
|
+
/** DI seam — override the ticket-mint POST. */
|
|
197
|
+
fetchTicket?: (url: string, bearer: string) => Promise<{
|
|
198
|
+
status: number;
|
|
199
|
+
body: string;
|
|
200
|
+
}>;
|
|
201
|
+
}): Promise<ShareOpenResult>;
|
|
126
202
|
export declare function formatShareRevisions(result: ShareRevisionsResult, json?: boolean): string;
|
|
127
203
|
export declare function formatShareDeleteResult(result: DeleteShareResult, json?: boolean): string;
|
|
128
204
|
interface ShareDeleteCliOpts {
|
package/dist/commands/share.js
CHANGED
|
@@ -7,17 +7,18 @@
|
|
|
7
7
|
// (commands/artifacts-setup.ts), which calls `runShareProvision` below.
|
|
8
8
|
import { existsSync } from 'node:fs';
|
|
9
9
|
import { formatBytes } from '../lib/format.js';
|
|
10
|
-
import { Option } from 'commander';
|
|
10
|
+
import { Argument, Option } from 'commander';
|
|
11
11
|
import chalk from 'chalk';
|
|
12
12
|
import { DEFAULT_BUCKET_NAME, DEFAULT_CF_BUNDLE, DEFAULT_SHARE_DOMAIN, DEFAULT_WORKER_NAME, generateWriteToken, readCloudflareCreds, readShareConfig, readWriteToken, readWriteTokenEnv, readWriteTokenFromBundle, storeWriteToken, writeShareConfig, } from '../lib/share/config.js';
|
|
13
13
|
import { addCustomDomain, configureBucketLifecycle, createBucket, deployWorker, enableWorkersDev, findZoneId, hashWorkerScript, putWorkerSecret, updateWorker, WORKER_PHOENIX_ID_BASE_SECRET, setWorkerSecret, } from '../lib/share/provision.js';
|
|
14
|
-
import { publishFile, resolveShareUsername, parseMetaEntries, resolveShareVisibility, } from '../lib/share/publish.js';
|
|
14
|
+
import { publishFile, resolveShareUsername, parseMetaEntries, sanitizeLabel, resolveShareVisibility, scanShareContent, formatSensitiveContentError, SHARE_VISIBILITY_LEVELS, } from '../lib/share/publish.js';
|
|
15
15
|
import { deleteShare, resolveDeleteTarget } from '../lib/share/delete.js';
|
|
16
16
|
import { renderWorkerScript } from '../lib/share/worker-template.js';
|
|
17
17
|
import { analyticsEnabled } from '../lib/share/analytics.js';
|
|
18
18
|
import { phoenixIdBaseForDeploy, resolveShareBackend, } from '../lib/share/backend.js';
|
|
19
19
|
import { resolveGitHubUsername } from '../lib/git.js';
|
|
20
20
|
import { setHelpSections } from '../lib/help.js';
|
|
21
|
+
import { showUrl } from '../lib/open-url.js';
|
|
21
22
|
export function formatSharePublishResult(result, json = false) {
|
|
22
23
|
if (json)
|
|
23
24
|
return JSON.stringify(result, null, 2);
|
|
@@ -55,14 +56,63 @@ export function shareTemplateStatus(cfg) {
|
|
|
55
56
|
return 'unknown';
|
|
56
57
|
return cfg.templateHash === hashWorkerScript(renderWorkerScript()) ? 'current' : 'outdated';
|
|
57
58
|
}
|
|
59
|
+
export async function runShareEdit(target, opts) {
|
|
60
|
+
// Same public-listing gate as publish: label + every metadata value are
|
|
61
|
+
// world-readable in the gallery and `share list --list-json`.
|
|
62
|
+
if (opts.force !== true) {
|
|
63
|
+
const hits = [
|
|
64
|
+
...(typeof opts.label === 'string' ? scanShareContent(opts.label) : []),
|
|
65
|
+
...Object.values(opts.meta ?? {}).flatMap((value) => scanShareContent(value)),
|
|
66
|
+
];
|
|
67
|
+
if (hits.length > 0)
|
|
68
|
+
throw new Error(formatSensitiveContentError(hits));
|
|
69
|
+
}
|
|
70
|
+
const backend = resolveShareBackend({ githubUser: opts.githubUser, config: opts.config, writeToken: opts.writeToken, byo: opts.byo, session: opts.session });
|
|
71
|
+
// me/org are Phoenix-only — surface a crisp login hint here rather than the
|
|
72
|
+
// Worker's generic 400 for a signed-out caller (BYO WRITE_TOKEN can only set
|
|
73
|
+
// public/unlisted).
|
|
74
|
+
if ((opts.visibility === 'me' || opts.visibility === 'org') && backend.kind !== 'managed') {
|
|
75
|
+
throw new Error(`Changing visibility to '${opts.visibility}' requires a Phoenix session. Run 'agents auth login'.`);
|
|
76
|
+
}
|
|
77
|
+
const { key } = await resolveDeleteTarget(target, { githubUser: backend.kind === 'managed' ? backend.namespace : opts.githubUser || backend.namespace });
|
|
78
|
+
const res = await (opts.fetchEdit ?? fetch)(`${backend.baseUrl.replace(/\/+$/, '')}/${key}`, {
|
|
79
|
+
method: 'PATCH',
|
|
80
|
+
headers: { authorization: `Bearer ${backend.token}`, 'content-type': 'application/json' },
|
|
81
|
+
body: JSON.stringify({
|
|
82
|
+
...(opts.label !== undefined ? { label: opts.label } : {}),
|
|
83
|
+
...(opts.visibility !== undefined ? { visibility: opts.visibility } : {}),
|
|
84
|
+
meta: opts.meta ?? {}, metaMode: opts.metaMode ?? 'merge', removeMeta: opts.removeMeta ?? [],
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
const body = await res.text();
|
|
88
|
+
if (!res.ok) {
|
|
89
|
+
let detail = body;
|
|
90
|
+
try {
|
|
91
|
+
detail = String(JSON.parse(body).error ?? body);
|
|
92
|
+
}
|
|
93
|
+
catch { /* keep body */ }
|
|
94
|
+
throw new Error(`Share metadata edit failed (${res.status}): ${detail || 'unknown error'}`);
|
|
95
|
+
}
|
|
96
|
+
const result = JSON.parse(body);
|
|
97
|
+
// A Worker template that predates the visibility edit ignores the `visibility`
|
|
98
|
+
// field and 200s without echoing it back — which would otherwise report a
|
|
99
|
+
// silent success while the page's visibility never changed. Fail loud with the
|
|
100
|
+
// update path instead (visibility rode in on RUSH-3135's metadata-edit route).
|
|
101
|
+
if (opts.visibility !== undefined && result.visibility === undefined) {
|
|
102
|
+
throw new Error("This share endpoint's Worker doesn't support in-place visibility changes yet — it predates them. " +
|
|
103
|
+
"Run 'agents artifacts share update' to deploy the current Worker template, then retry " +
|
|
104
|
+
"('agents artifacts share status' shows whether an update is due).");
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
58
108
|
/** Shown whenever the deployed Worker has no `?format=json` listing route — an
|
|
59
109
|
* endpoint provisioned before this feature. Points at the RUSH-2449 update path
|
|
60
110
|
* (`agents artifacts share update`) instead of letting the caller hit a 404 or an
|
|
61
111
|
* HTML body and get a confusing parse error. */
|
|
62
112
|
const OUTDATED_TEMPLATE_HINT = 'Your deployed share Worker has no machine-readable listing route — it predates `agents artifacts share list`. ' +
|
|
63
113
|
'Run `agents artifacts share update` to deploy the current Worker template, then retry (`agents artifacts share status` shows whether an update is due).';
|
|
64
|
-
async function defaultListingFetch(url) {
|
|
65
|
-
const res = await fetch(url, { headers: { accept: 'application/json' } });
|
|
114
|
+
async function defaultListingFetch(url, headers) {
|
|
115
|
+
const res = await fetch(url, { headers: { accept: 'application/json', ...(headers || {}) } });
|
|
66
116
|
return { status: res.status, contentType: res.headers.get('content-type') ?? '', body: await res.text() };
|
|
67
117
|
}
|
|
68
118
|
/** Normalize the Worker's `meta` field (arbitrary `--meta key=value` entries,
|
|
@@ -108,6 +158,9 @@ export function parseShareListing(user, body) {
|
|
|
108
158
|
host: item.host == null ? null : String(item.host),
|
|
109
159
|
repo: item.repo == null ? null : String(item.repo),
|
|
110
160
|
revisionCount: typeof item.revisionCount === 'number' ? item.revisionCount : 0,
|
|
161
|
+
visibility: item.visibility === 'unlisted' || item.visibility === 'me' || item.visibility === 'org'
|
|
162
|
+
? item.visibility
|
|
163
|
+
: 'public',
|
|
111
164
|
meta: parseMetaField(item.meta),
|
|
112
165
|
};
|
|
113
166
|
});
|
|
@@ -148,9 +201,21 @@ export async function runShareList(opts = {}) {
|
|
|
148
201
|
}
|
|
149
202
|
}
|
|
150
203
|
const user = await namespaceForBackend(backend, opts.githubUser);
|
|
151
|
-
const
|
|
204
|
+
const scope = opts.scope ?? 'public';
|
|
205
|
+
const includeHidden = scope !== 'public';
|
|
206
|
+
const query = new URLSearchParams({ format: 'json' });
|
|
207
|
+
if (includeHidden)
|
|
208
|
+
query.set('scope', 'mine');
|
|
209
|
+
const listUrl = `${backend.baseUrl.replace(/\/+$/, '')}/${encodeURIComponent(user)}?${query.toString()}`;
|
|
210
|
+
const listingHeaders = {};
|
|
211
|
+
if (includeHidden) {
|
|
212
|
+
if (!backend.token) {
|
|
213
|
+
throw new Error(`Listing ${scope} shares requires owner authentication. Run 'agents auth login' for the managed endpoint, or ensure SHARE_WRITE_TOKEN / 'agents artifacts share join' is configured for BYO.`);
|
|
214
|
+
}
|
|
215
|
+
listingHeaders.authorization = `Bearer ${backend.token}`;
|
|
216
|
+
}
|
|
152
217
|
const fetchListing = opts.fetchListing ?? defaultListingFetch;
|
|
153
|
-
const res = await fetchListing(listUrl);
|
|
218
|
+
const res = await fetchListing(listUrl, listingHeaders);
|
|
154
219
|
if (res.status === 404) {
|
|
155
220
|
// A single-segment path 404s either because the namespace is genuinely empty
|
|
156
221
|
// (current template — the listing route gates on the namespace holding objects,
|
|
@@ -177,14 +242,23 @@ export async function runShareList(opts = {}) {
|
|
|
177
242
|
throw new Error(OUTDATED_TEMPLATE_HINT);
|
|
178
243
|
}
|
|
179
244
|
const parsed = parseShareListing(user, res.body);
|
|
180
|
-
return applyShareListFilters(parsed,
|
|
245
|
+
return applyShareListFilters(parsed, {
|
|
246
|
+
agent: opts.agent,
|
|
247
|
+
session: opts.session,
|
|
248
|
+
label: opts.label,
|
|
249
|
+
meta: opts.meta,
|
|
250
|
+
scope,
|
|
251
|
+
});
|
|
181
252
|
}
|
|
182
253
|
/** Client-side filtering over an already-fetched listing (RUSH-2683) — the
|
|
183
254
|
* Worker has no query surface for this, so it narrows the fetched set instead
|
|
184
255
|
* of a second round trip. `count` reflects the FILTERED set, matching what the
|
|
185
256
|
* caller actually sees. */
|
|
186
|
-
function applyShareListFilters(result, filters) {
|
|
257
|
+
export function applyShareListFilters(result, filters) {
|
|
187
258
|
let objects = result.objects;
|
|
259
|
+
if (filters.scope && filters.scope !== 'all') {
|
|
260
|
+
objects = objects.filter((o) => o.visibility === filters.scope);
|
|
261
|
+
}
|
|
188
262
|
if (filters.agent) {
|
|
189
263
|
const needle = filters.agent.toLowerCase();
|
|
190
264
|
objects = objects.filter((o) => (o.agent ?? '').toLowerCase() === needle);
|
|
@@ -196,6 +270,9 @@ function applyShareListFilters(result, filters) {
|
|
|
196
270
|
const needle = filters.label.toLowerCase();
|
|
197
271
|
objects = objects.filter((o) => (o.label ?? '').toLowerCase().includes(needle));
|
|
198
272
|
}
|
|
273
|
+
if (filters.meta) {
|
|
274
|
+
objects = objects.filter((o) => Object.entries(filters.meta).every(([key, value]) => o.meta[key] === value));
|
|
275
|
+
}
|
|
199
276
|
return { user: result.user, count: objects.length, objects };
|
|
200
277
|
}
|
|
201
278
|
/** Human-readable bytes, e.g. `1.2 KB`, `640 B`. */
|
|
@@ -219,7 +296,9 @@ export function formatShareList(result, json = false) {
|
|
|
219
296
|
chalk.dim(` ${result.count} published ${result.count === 1 ? 'page' : 'pages'}`);
|
|
220
297
|
const rows = result.objects.map((o) => {
|
|
221
298
|
const when = o.publishedAt ? o.publishedAt.slice(0, 10) : 'unknown';
|
|
222
|
-
const
|
|
299
|
+
const visibility = o.visibility ?? 'public';
|
|
300
|
+
const visibilityBit = visibility === 'public' ? chalk.green('public') : chalk.yellow(visibility);
|
|
301
|
+
const bits = [visibilityBit, when, formatBytes(o.size)];
|
|
223
302
|
if (o.agent)
|
|
224
303
|
bits.push(o.agent);
|
|
225
304
|
if (o.revisionCount > 0)
|
|
@@ -305,6 +384,49 @@ export async function runShareRevisions(target, opts = {}) {
|
|
|
305
384
|
}
|
|
306
385
|
return parseShareRevisions(key, res.body);
|
|
307
386
|
}
|
|
387
|
+
/** Mint a one-time login ticket for the signed-in owner and build the URL that
|
|
388
|
+
* opens their own published page **as the owner**, so the inline visibility
|
|
389
|
+
* control is live in the browser. Managed backend only — the control is gated on
|
|
390
|
+
* the Phoenix identity, and BYO (WRITE_TOKEN) endpoints have no per-viewer login.
|
|
391
|
+
* `target` accepts the same three forms as `visibility`/`unshare` (a full URL,
|
|
392
|
+
* `<user>/<slug>`, or a bare slug in the caller's namespace). */
|
|
393
|
+
export async function runShareOpen(target, opts = {}) {
|
|
394
|
+
const backend = resolveShareBackend({
|
|
395
|
+
githubUser: opts.githubUser,
|
|
396
|
+
config: opts.config,
|
|
397
|
+
session: opts.session,
|
|
398
|
+
requireToken: false,
|
|
399
|
+
});
|
|
400
|
+
if (backend.kind !== 'managed') {
|
|
401
|
+
throw new Error("'agents artifacts share open' needs a managed (Phoenix) endpoint — the page's inline " +
|
|
402
|
+
"visibility control is gated on your signed-in identity. Run 'agents auth login' first.");
|
|
403
|
+
}
|
|
404
|
+
const baseUrl = backend.baseUrl.replace(/\/+$/, '');
|
|
405
|
+
const { key } = await resolveDeleteTarget(target, { githubUser: backend.namespace });
|
|
406
|
+
const url = `${baseUrl}/${key}`;
|
|
407
|
+
const doFetch = opts.fetchTicket ??
|
|
408
|
+
(async (u, bearer) => {
|
|
409
|
+
const res = await fetch(u, { method: 'POST', headers: { authorization: `Bearer ${bearer}` } });
|
|
410
|
+
return { status: res.status, body: await res.text() };
|
|
411
|
+
});
|
|
412
|
+
const res = await doFetch(`${baseUrl}/__ticket`, backend.token);
|
|
413
|
+
if (res.status !== 200) {
|
|
414
|
+
throw new Error(`Could not mint a login ticket (${res.status}) from ${baseUrl}/__ticket. ` +
|
|
415
|
+
"The endpoint may predate this feature — run 'agents artifacts share update' to redeploy the Worker.");
|
|
416
|
+
}
|
|
417
|
+
let ticket;
|
|
418
|
+
try {
|
|
419
|
+
const parsed = JSON.parse(res.body);
|
|
420
|
+
if (typeof parsed.ticket !== 'string' || !parsed.ticket)
|
|
421
|
+
throw new Error('no ticket in response');
|
|
422
|
+
ticket = parsed.ticket;
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
throw new Error(`Ticket endpoint at ${baseUrl}/__ticket did not return a usable ticket.`);
|
|
426
|
+
}
|
|
427
|
+
const ownerUrl = `${url}?phoenix_ticket=${encodeURIComponent(ticket)}`;
|
|
428
|
+
return { key, url, ownerUrl };
|
|
429
|
+
}
|
|
308
430
|
export function formatShareRevisions(result, json = false) {
|
|
309
431
|
if (json)
|
|
310
432
|
return JSON.stringify(result, null, 2);
|
|
@@ -341,6 +463,18 @@ export function formatShareDeleteResult(result, json = false) {
|
|
|
341
463
|
}
|
|
342
464
|
return lines.join('\n');
|
|
343
465
|
}
|
|
466
|
+
/** Human summary of an in-place visibility change. */
|
|
467
|
+
function formatShareVisibilityResult(result, requested) {
|
|
468
|
+
const now = result.visibility ?? requested;
|
|
469
|
+
const from = result.previousVisibility && result.previousVisibility !== now
|
|
470
|
+
? `${chalk.dim(result.previousVisibility)} → `
|
|
471
|
+
: '';
|
|
472
|
+
return [
|
|
473
|
+
`${chalk.green('✓')} visibility ${from}${chalk.bold(now)}`,
|
|
474
|
+
chalk.dim(` ${result.url}`),
|
|
475
|
+
chalk.dim(' body unchanged — no revision created'),
|
|
476
|
+
].join('\n');
|
|
477
|
+
}
|
|
344
478
|
/** Shared handler for `agents artifacts share delete <targets...>` and the
|
|
345
479
|
* nested `agents artifacts unshare <targets...>` alias. Deletes each target independently and
|
|
346
480
|
* continues past a failed one (rm-style), reporting all results and exiting
|
|
@@ -426,6 +560,36 @@ const SHARE_DELETE_NOTES = `
|
|
|
426
560
|
|
|
427
561
|
agents artifacts share delete === agents artifacts unshare (same command, different name).
|
|
428
562
|
`;
|
|
563
|
+
const SHARE_VISIBILITY_EXAMPLES = `
|
|
564
|
+
# Take a public page private to just you (Phoenix session required for me/org)
|
|
565
|
+
agents artifacts share visibility https://share.agents-cli.sh/octocat/q3-plan me
|
|
566
|
+
|
|
567
|
+
# Make it public again — by <user>/<slug> or a bare slug in your namespace
|
|
568
|
+
agents artifacts share visibility octocat/q3-plan public
|
|
569
|
+
agents artifacts share visibility q3-plan unlisted
|
|
570
|
+
|
|
571
|
+
# Share with your Phoenix org (rejected on a public-inbox email domain)
|
|
572
|
+
agents artifacts share visibility q3-plan org
|
|
573
|
+
|
|
574
|
+
# Machine-readable result
|
|
575
|
+
agents artifacts share visibility q3-plan me --visibility-json
|
|
576
|
+
`;
|
|
577
|
+
const SHARE_VISIBILITY_NOTES = `
|
|
578
|
+
Changes an ALREADY-published page's visibility in place: the slug — and so the
|
|
579
|
+
URL — is preserved. It re-stamps only the visibility on the stored object; the
|
|
580
|
+
body, provenance, label, and --meta are untouched, so — like 'agents artifacts
|
|
581
|
+
share edit' — this is a metadata-only rewrite and creates no revision.
|
|
582
|
+
|
|
583
|
+
public is listed in the gallery; unlisted is a capability URL (GET still 200,
|
|
584
|
+
X-Robots-Tag: noindex, hidden from gallery/list); me is visible only to the
|
|
585
|
+
signed-in owner; org is visible to members of the same Phoenix organization.
|
|
586
|
+
me and org require a Phoenix session — run 'agents auth login' first — and org
|
|
587
|
+
is refused on a public-inbox email domain (gmail.com, outlook.com, …).
|
|
588
|
+
|
|
589
|
+
This hits the live endpoint via the owner's Phoenix session (or the BYO
|
|
590
|
+
WRITE_TOKEN for public/unlisted). It does not re-run the pre-publish
|
|
591
|
+
sensitive-content scan — the body is unchanged.
|
|
592
|
+
`;
|
|
429
593
|
/**
|
|
430
594
|
* Register the `share` subtree under its parent group — `agents artifacts share`
|
|
431
595
|
* (see commands/artifacts.ts). `agents artifacts unshare` is the nested alias
|
|
@@ -436,6 +600,10 @@ const SHARE_DELETE_NOTES = `
|
|
|
436
600
|
export function registerShareCommands(artifactsCmd) {
|
|
437
601
|
const shareCmd = artifactsCmd
|
|
438
602
|
.command('share')
|
|
603
|
+
// Child task commands intentionally reuse publish vocabulary (`edit --label`,
|
|
604
|
+
// `list --meta`). Keep option ownership positional within this subtree so
|
|
605
|
+
// Commander does not silently resolve those flags to the parent publisher.
|
|
606
|
+
.enablePositionalOptions()
|
|
439
607
|
.description('Publish an HTML file to a shareable link — managed if signed in, otherwise your Cloudflare R2.')
|
|
440
608
|
.argument('[file]', 'file to publish (HTML or any static asset)')
|
|
441
609
|
.option('--slug <slug>', 'URL slug override (default: stable slug of the artifact title, then filename)')
|
|
@@ -553,6 +721,84 @@ ${SHARE_DELETE_NOTES}
|
|
|
553
721
|
shareDeleteCmd.action(async (targets, opts) => {
|
|
554
722
|
await runShareDelete(targets, opts);
|
|
555
723
|
});
|
|
724
|
+
// `agents artifacts share visibility <target> <level>` — change an
|
|
725
|
+
// already-published page's visibility in place. Kept in its own block; it
|
|
726
|
+
// reuses the delete target parser and the same PATCH metadata-edit route as
|
|
727
|
+
// `share edit` (visibility is metadata), so it re-stamps the stored object
|
|
728
|
+
// rather than re-publishing — a re-publish over the PUT path can't express an
|
|
729
|
+
// in-place change (GET injects the attribution bar at serve time and never
|
|
730
|
+
// strips it, and the served body carries no provenance/label/--meta back to
|
|
731
|
+
// re-send). --for-user / --visibility-json (not --github-user / --json): the
|
|
732
|
+
// parent `share <file>` already owns both long names, and commander resolves a
|
|
733
|
+
// child option's long name against the whole ancestor chain, silently dropping
|
|
734
|
+
// a same-named child (RUSH-2687) — the same rename the delete/list blocks make.
|
|
735
|
+
const shareVisibilityCmd = shareCmd
|
|
736
|
+
.command('visibility')
|
|
737
|
+
.description("Change an already-published page's visibility in place (public | unlisted | me | org). The slug/URL is preserved; the body is untouched, so no revision is created.")
|
|
738
|
+
.addArgument(new Argument('<target>', 'the published page: a full URL, <user>/<slug>, or a bare slug in your namespace'))
|
|
739
|
+
.addArgument(new Argument('<level>', 'new visibility').choices([...SHARE_VISIBILITY_LEVELS]))
|
|
740
|
+
.option('--for-user <user>', 'GitHub username for resolving a bare-slug target (default: resolved from gh/git config)')
|
|
741
|
+
.option('--visibility-json', 'emit a machine-readable result')
|
|
742
|
+
.action(async (target, level, opts) => {
|
|
743
|
+
try {
|
|
744
|
+
const visibility = level;
|
|
745
|
+
const result = await runShareEdit(target, { visibility, githubUser: opts.forUser });
|
|
746
|
+
if (opts.visibilityJson) {
|
|
747
|
+
console.log(JSON.stringify(result, null, 2));
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
console.log(formatShareVisibilityResult(result, visibility));
|
|
751
|
+
}
|
|
752
|
+
catch (e) {
|
|
753
|
+
console.error(chalk.red(e.message));
|
|
754
|
+
process.exitCode = 1;
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
setHelpSections(shareVisibilityCmd, {
|
|
758
|
+
examples: SHARE_VISIBILITY_EXAMPLES,
|
|
759
|
+
notes: SHARE_VISIBILITY_NOTES,
|
|
760
|
+
});
|
|
761
|
+
const shareOpenCmd = shareCmd
|
|
762
|
+
.command('open')
|
|
763
|
+
.description("Open your own published page in the browser signed in as the owner, so the page's inline visibility control is live. Mints a one-time login ticket and appends it to the URL.")
|
|
764
|
+
.addArgument(new Argument('<target>', 'the published page: a full URL, <user>/<slug>, or a bare slug in your namespace'))
|
|
765
|
+
.option('--for-user <user>', 'GitHub username for resolving a bare-slug target (default: resolved from your Phoenix handle)')
|
|
766
|
+
.option('--no-open', 'print the owner URL instead of opening a browser')
|
|
767
|
+
.option('--json', 'emit a machine-readable result (includes the ticketed owner URL)')
|
|
768
|
+
.action(async (target, opts) => {
|
|
769
|
+
try {
|
|
770
|
+
const result = await runShareOpen(target, { githubUser: opts.forUser });
|
|
771
|
+
if (opts.json) {
|
|
772
|
+
console.log(JSON.stringify(result, null, 2));
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
if (opts.open === false) {
|
|
776
|
+
console.log(chalk.green(result.ownerUrl));
|
|
777
|
+
console.log(chalk.dim(' open this to land on the page as the owner (the visibility chip becomes a control)'));
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
const shown = await showUrl(result.ownerUrl);
|
|
781
|
+
if (shown.via === 'none') {
|
|
782
|
+
console.log(chalk.green(result.ownerUrl));
|
|
783
|
+
console.error(chalk.dim(' could not open a browser — open the URL above yourself'));
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
console.log(chalk.green(`Opened ${result.url} as owner — the visibility chip is now a live control.`));
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
catch (e) {
|
|
790
|
+
console.error(chalk.red(e.message));
|
|
791
|
+
process.exitCode = 1;
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
setHelpSections(shareOpenCmd, {
|
|
795
|
+
examples: `agents artifacts share open q3-plan # open your page, signed in, ready to re-scope
|
|
796
|
+
agents artifacts share open octocat/q3-plan
|
|
797
|
+
agents artifacts share open https://share.agents-cli.sh/octocat/q3-plan --no-open`,
|
|
798
|
+
notes: `The shared page's visibility chip is an interactive control only for the signed-in owner; a plain link has no login, so the chip renders as a static cue. This mints a short-lived, single-use login ticket (self-signed by your share Worker) and opens the page with it, so the chip becomes a dropdown you can switch in place.
|
|
799
|
+
Managed (Phoenix) endpoints only — run 'agents auth login' first. If the mint 501s/404s, your Worker predates the feature: run 'agents artifacts share update' to redeploy it.
|
|
800
|
+
Prefer to change visibility without a browser? Use 'agents artifacts share visibility <target> <level>'.`,
|
|
801
|
+
});
|
|
556
802
|
shareCmd
|
|
557
803
|
.command('join')
|
|
558
804
|
.description('Use an existing synced share endpoint and write token (no provisioning).')
|
|
@@ -664,20 +910,33 @@ ${SHARE_DELETE_NOTES}
|
|
|
664
910
|
// share.test.ts). Matches the `revisions` precedent (--for-user/
|
|
665
911
|
// --revisions-json) and the `delete`/`unshare` precedent (--for-user/
|
|
666
912
|
// --delete-json) below.
|
|
913
|
+
//
|
|
914
|
+
// `--visibility` is also owned by the parent `share <file>` command (publish),
|
|
915
|
+
// so the listing filter is named `--scope` here; `--all` is the convenience
|
|
916
|
+
// alias for `--scope all`.
|
|
667
917
|
.option('--for-user <user>', 'GitHub username whose namespace to list (default: resolved from gh/git config)')
|
|
918
|
+
.addOption(new Option('--scope <level>', 'visibility filter: public (default), unlisted, me, org, or all')
|
|
919
|
+
.choices(['public', 'unlisted', 'me', 'org', 'all'])
|
|
920
|
+
.default('public'))
|
|
921
|
+
.option('--all', "list every page including hidden unlisted/me/org shares (alias for --scope all)")
|
|
668
922
|
.option('--agent <name>', 'filter to shares published by this agent/harness (case-insensitive)')
|
|
669
923
|
.option('--session <id>', 'filter to shares published from this session id')
|
|
670
924
|
// Named --label-contains, not --label: `share <file>` (the parent) already owns
|
|
671
925
|
// `--label`/`--title`, same collision class as --for-user/--list-json above.
|
|
672
926
|
.option('--label-contains <substr>', 'filter to shares whose label contains this text (case-insensitive)')
|
|
673
|
-
.option('--
|
|
927
|
+
.option('--meta <key=value>', 'filter by an exact arbitrary metadata entry (repeatable)', (v, p) => [...p, v], [])
|
|
928
|
+
.option('--list-json', 'emit the machine-readable listing (slug, url, size, contentType, publishedAt, expiresAt, label, visibility, agent, session, host, repo, revisionCount, meta)')
|
|
674
929
|
.action(async (opts) => {
|
|
675
930
|
try {
|
|
931
|
+
const scope = opts.all ? 'all' : (opts.scope ?? 'public');
|
|
932
|
+
const parentMeta = shareCmd.opts().meta ?? [];
|
|
676
933
|
const result = await runShareList({
|
|
677
934
|
githubUser: opts.forUser,
|
|
935
|
+
scope,
|
|
678
936
|
agent: opts.agent,
|
|
679
937
|
session: opts.session,
|
|
680
938
|
label: opts.labelContains,
|
|
939
|
+
meta: parseMetaEntries(opts.meta.length ? opts.meta : parentMeta),
|
|
681
940
|
});
|
|
682
941
|
console.log(formatShareList(result, Boolean(opts.listJson)));
|
|
683
942
|
}
|
|
@@ -691,6 +950,11 @@ ${SHARE_DELETE_NOTES}
|
|
|
691
950
|
# Everything you've published, newest first
|
|
692
951
|
agents artifacts share list
|
|
693
952
|
|
|
953
|
+
# Include your hidden pages (unlisted / me / org) so you can see everything
|
|
954
|
+
agents artifacts share list --all
|
|
955
|
+
agents artifacts share list --scope me
|
|
956
|
+
agents artifacts share list --scope unlisted
|
|
957
|
+
|
|
694
958
|
# Machine-readable — e.g. pull every still-public URL with jq
|
|
695
959
|
agents artifacts share list --list-json | jq -r '.objects[].url'
|
|
696
960
|
|
|
@@ -700,18 +964,83 @@ ${SHARE_DELETE_NOTES}
|
|
|
700
964
|
# Narrow by who/what published it
|
|
701
965
|
agents artifacts share list --agent claude
|
|
702
966
|
agents artifacts share list --label-contains "fleet plan"
|
|
967
|
+
agents artifacts share list --meta kind=plan --meta status=final
|
|
703
968
|
`,
|
|
704
969
|
notes: `
|
|
705
970
|
Lists the ACTIVE pages in your namespace — expired links and the sibling .png OG
|
|
706
|
-
covers are omitted
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
971
|
+
covers are omitted. By default this mirrors the public gallery (public pages only).
|
|
972
|
+
Use --scope unlisted|me|org or --all to include your hidden pages; those scopes
|
|
973
|
+
send the owner's bearer and a 'scope=mine' hint to the Worker's JSON listing route,
|
|
974
|
+
which returns hidden pages ONLY after verifying the caller owns the namespace.
|
|
975
|
+
Signed-in users list the managed endpoint (share.agents-cli.sh/<handle>); otherwise
|
|
976
|
+
BYO. It reads the endpoint's JSON listing route, which ships with the current
|
|
977
|
+
Worker template. If a BYO Worker predates this feature the command says so and
|
|
978
|
+
points you at 'agents artifacts share update' (RUSH-2449) rather than returning a
|
|
979
|
+
wrong or empty result — see 'agents artifacts share status' for whether an update
|
|
980
|
+
is due.
|
|
981
|
+
|
|
982
|
+
--agent/--session/--label-contains/--meta filter the fetched listing client-side;
|
|
983
|
+
--list-json's count reflects the filtered set. Each human row shows the page's
|
|
984
|
+
visibility so public vs hidden is obvious at a glance.
|
|
985
|
+
`,
|
|
986
|
+
});
|
|
987
|
+
const shareEditCmd = shareCmd
|
|
988
|
+
.command('edit <target>')
|
|
989
|
+
.description('Edit a published share\'s label or arbitrary metadata without republishing its body.')
|
|
990
|
+
.option('--for-user <user>', 'namespace for resolving a bare slug (default: your namespace)')
|
|
991
|
+
.option('--label <text>', 'set or replace the display label')
|
|
992
|
+
.option('--remove-label', 'remove the display label')
|
|
993
|
+
.option('--meta <key=value>', 'merge a metadata entry (repeatable)', (v, p) => [...p, v], [])
|
|
994
|
+
.option('--replace-meta <key=value>', 'replace all arbitrary metadata with these entries (repeatable)', (v, p) => [...p, v], [])
|
|
995
|
+
.option('--remove-meta <key>', 'remove an arbitrary metadata key (repeatable)', (v, p) => [...p, v], [])
|
|
996
|
+
.option('--edit-json', 'emit the machine-readable edit result')
|
|
997
|
+
.action(async (target, opts) => {
|
|
998
|
+
try {
|
|
999
|
+
const parent = shareCmd.opts();
|
|
1000
|
+
const label = opts.label ?? parent.label;
|
|
1001
|
+
const mergeMeta = opts.meta.length ? opts.meta : (parent.meta ?? []);
|
|
1002
|
+
if (label !== undefined && opts.removeLabel)
|
|
1003
|
+
throw new Error('--label and --remove-label are mutually exclusive.');
|
|
1004
|
+
if (mergeMeta.length && opts.replaceMeta.length)
|
|
1005
|
+
throw new Error('--meta and --replace-meta are mutually exclusive.');
|
|
1006
|
+
if (opts.replaceMeta.length && opts.removeMeta.length)
|
|
1007
|
+
throw new Error('--replace-meta and --remove-meta are mutually exclusive.');
|
|
1008
|
+
if (label === undefined && !opts.removeLabel && !mergeMeta.length && !opts.replaceMeta.length && !opts.removeMeta.length)
|
|
1009
|
+
throw new Error('Nothing to edit. Pass --label, --remove-label, --meta, --replace-meta, or --remove-meta.');
|
|
1010
|
+
const removeMeta = opts.removeMeta.map((key) => Object.keys(parseMetaEntries([`${key}=x`]))[0]);
|
|
1011
|
+
const result = await runShareEdit(target, {
|
|
1012
|
+
githubUser: opts.forUser,
|
|
1013
|
+
label: opts.removeLabel ? null : label === undefined ? undefined : sanitizeLabel(label),
|
|
1014
|
+
meta: parseMetaEntries(opts.replaceMeta.length ? opts.replaceMeta : mergeMeta),
|
|
1015
|
+
metaMode: opts.replaceMeta.length ? 'replace' : 'merge',
|
|
1016
|
+
removeMeta,
|
|
1017
|
+
// Parent `share --force` (RUSH-2687: a child --force is silently dropped).
|
|
1018
|
+
force: parent.force,
|
|
1019
|
+
});
|
|
1020
|
+
console.log(opts.editJson ? JSON.stringify(result, null, 2) : chalk.green(`updated ${result.url}`));
|
|
1021
|
+
}
|
|
1022
|
+
catch (e) {
|
|
1023
|
+
console.error(chalk.red(e.message));
|
|
1024
|
+
process.exitCode = 1;
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
setHelpSections(shareEditCmd, {
|
|
1028
|
+
examples: `
|
|
1029
|
+
# Change only the gallery/list title
|
|
1030
|
+
agents artifacts share edit q3-plan --label "Q3 fleet plan"
|
|
712
1031
|
|
|
713
|
-
|
|
714
|
-
|
|
1032
|
+
# Merge metadata, or deliberately replace/remove it
|
|
1033
|
+
agents artifacts share edit q3-plan --meta status=final --meta ticket=PHNX-3278
|
|
1034
|
+
agents artifacts share edit q3-plan --replace-meta kind=plan --replace-meta status=final
|
|
1035
|
+
agents artifacts share edit q3-plan --remove-meta status
|
|
1036
|
+
`,
|
|
1037
|
+
notes: `
|
|
1038
|
+
Metadata edits preserve the exact published body, content type/HTTP metadata,
|
|
1039
|
+
publication time, visibility, expiry, provenance, cover, and retained revisions. --meta merges;
|
|
1040
|
+
--replace-meta replaces all arbitrary metadata; --remove-meta deletes named keys.
|
|
1041
|
+
Reserved Worker/provenance keys cannot be changed through metadata flags.
|
|
1042
|
+
Edited labels and metadata values are scanned for emails and credential-shaped
|
|
1043
|
+
strings the same way publish is — pass --force to write them anyway.
|
|
715
1044
|
`,
|
|
716
1045
|
});
|
|
717
1046
|
const shareRevisionsCmd = shareCmd
|
package/dist/commands/ssh.d.ts
CHANGED
|
@@ -48,6 +48,13 @@ export declare function showLeasedBoxesSection(opts: {
|
|
|
48
48
|
all?: boolean;
|
|
49
49
|
stats?: boolean;
|
|
50
50
|
}): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Remote argv for `agents ssh <slug>` into a leased crabbox. Crabbox ssh does
|
|
53
|
+
* not go through {@link buildSshInvocation}, so this stamps the same
|
|
54
|
+
* AGENTS_FLEET_REMOTE consent marker a registered-device ssh would (PHNX-3065).
|
|
55
|
+
* Exported so the branch is unit-testable without a live crabbox.
|
|
56
|
+
*/
|
|
57
|
+
export declare function leasedBoxRemoteCmd(cmd: string[]): string[];
|
|
51
58
|
/**
|
|
52
59
|
* Race `fanOut` against an overall wall-clock deadline (RUSH-2041).
|
|
53
60
|
*
|