@phnx-labs/agents-cli 1.22.53 → 1.22.54
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 +190 -0
- package/README.md +41 -8
- 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/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 +52 -3
- package/dist/commands/share.js +262 -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 +3 -1
- package/dist/commands/view.js +5 -4
- 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 +37 -1
- package/dist/lib/accounting/usage.js +71 -6
- 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/remote-control.d.ts +9 -7
- package/dist/lib/browser/remote-control.js +9 -7
- 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/daemon.js +30 -4
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -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/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.js +8 -1
- 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/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 +39 -4
- package/dist/lib/session/db.js +130 -29
- 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/publish.d.ts +53 -5
- package/dist/lib/share/publish.js +99 -17
- package/dist/lib/share/worker-template.js +582 -57
- 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 +2 -0
- package/package.json +2 -1
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. */
|
package/dist/commands/share.js
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
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';
|
|
@@ -55,14 +55,63 @@ export function shareTemplateStatus(cfg) {
|
|
|
55
55
|
return 'unknown';
|
|
56
56
|
return cfg.templateHash === hashWorkerScript(renderWorkerScript()) ? 'current' : 'outdated';
|
|
57
57
|
}
|
|
58
|
+
export async function runShareEdit(target, opts) {
|
|
59
|
+
// Same public-listing gate as publish: label + every metadata value are
|
|
60
|
+
// world-readable in the gallery and `share list --list-json`.
|
|
61
|
+
if (opts.force !== true) {
|
|
62
|
+
const hits = [
|
|
63
|
+
...(typeof opts.label === 'string' ? scanShareContent(opts.label) : []),
|
|
64
|
+
...Object.values(opts.meta ?? {}).flatMap((value) => scanShareContent(value)),
|
|
65
|
+
];
|
|
66
|
+
if (hits.length > 0)
|
|
67
|
+
throw new Error(formatSensitiveContentError(hits));
|
|
68
|
+
}
|
|
69
|
+
const backend = resolveShareBackend({ githubUser: opts.githubUser, config: opts.config, writeToken: opts.writeToken, byo: opts.byo, session: opts.session });
|
|
70
|
+
// me/org are Phoenix-only — surface a crisp login hint here rather than the
|
|
71
|
+
// Worker's generic 400 for a signed-out caller (BYO WRITE_TOKEN can only set
|
|
72
|
+
// public/unlisted).
|
|
73
|
+
if ((opts.visibility === 'me' || opts.visibility === 'org') && backend.kind !== 'managed') {
|
|
74
|
+
throw new Error(`Changing visibility to '${opts.visibility}' requires a Phoenix session. Run 'agents auth login'.`);
|
|
75
|
+
}
|
|
76
|
+
const { key } = await resolveDeleteTarget(target, { githubUser: backend.kind === 'managed' ? backend.namespace : opts.githubUser || backend.namespace });
|
|
77
|
+
const res = await (opts.fetchEdit ?? fetch)(`${backend.baseUrl.replace(/\/+$/, '')}/${key}`, {
|
|
78
|
+
method: 'PATCH',
|
|
79
|
+
headers: { authorization: `Bearer ${backend.token}`, 'content-type': 'application/json' },
|
|
80
|
+
body: JSON.stringify({
|
|
81
|
+
...(opts.label !== undefined ? { label: opts.label } : {}),
|
|
82
|
+
...(opts.visibility !== undefined ? { visibility: opts.visibility } : {}),
|
|
83
|
+
meta: opts.meta ?? {}, metaMode: opts.metaMode ?? 'merge', removeMeta: opts.removeMeta ?? [],
|
|
84
|
+
}),
|
|
85
|
+
});
|
|
86
|
+
const body = await res.text();
|
|
87
|
+
if (!res.ok) {
|
|
88
|
+
let detail = body;
|
|
89
|
+
try {
|
|
90
|
+
detail = String(JSON.parse(body).error ?? body);
|
|
91
|
+
}
|
|
92
|
+
catch { /* keep body */ }
|
|
93
|
+
throw new Error(`Share metadata edit failed (${res.status}): ${detail || 'unknown error'}`);
|
|
94
|
+
}
|
|
95
|
+
const result = JSON.parse(body);
|
|
96
|
+
// A Worker template that predates the visibility edit ignores the `visibility`
|
|
97
|
+
// field and 200s without echoing it back — which would otherwise report a
|
|
98
|
+
// silent success while the page's visibility never changed. Fail loud with the
|
|
99
|
+
// update path instead (visibility rode in on RUSH-3135's metadata-edit route).
|
|
100
|
+
if (opts.visibility !== undefined && result.visibility === undefined) {
|
|
101
|
+
throw new Error("This share endpoint's Worker doesn't support in-place visibility changes yet — it predates them. " +
|
|
102
|
+
"Run 'agents artifacts share update' to deploy the current Worker template, then retry " +
|
|
103
|
+
"('agents artifacts share status' shows whether an update is due).");
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
58
107
|
/** Shown whenever the deployed Worker has no `?format=json` listing route — an
|
|
59
108
|
* endpoint provisioned before this feature. Points at the RUSH-2449 update path
|
|
60
109
|
* (`agents artifacts share update`) instead of letting the caller hit a 404 or an
|
|
61
110
|
* HTML body and get a confusing parse error. */
|
|
62
111
|
const OUTDATED_TEMPLATE_HINT = 'Your deployed share Worker has no machine-readable listing route — it predates `agents artifacts share list`. ' +
|
|
63
112
|
'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' } });
|
|
113
|
+
async function defaultListingFetch(url, headers) {
|
|
114
|
+
const res = await fetch(url, { headers: { accept: 'application/json', ...(headers || {}) } });
|
|
66
115
|
return { status: res.status, contentType: res.headers.get('content-type') ?? '', body: await res.text() };
|
|
67
116
|
}
|
|
68
117
|
/** Normalize the Worker's `meta` field (arbitrary `--meta key=value` entries,
|
|
@@ -108,6 +157,9 @@ export function parseShareListing(user, body) {
|
|
|
108
157
|
host: item.host == null ? null : String(item.host),
|
|
109
158
|
repo: item.repo == null ? null : String(item.repo),
|
|
110
159
|
revisionCount: typeof item.revisionCount === 'number' ? item.revisionCount : 0,
|
|
160
|
+
visibility: item.visibility === 'unlisted' || item.visibility === 'me' || item.visibility === 'org'
|
|
161
|
+
? item.visibility
|
|
162
|
+
: 'public',
|
|
111
163
|
meta: parseMetaField(item.meta),
|
|
112
164
|
};
|
|
113
165
|
});
|
|
@@ -148,9 +200,21 @@ export async function runShareList(opts = {}) {
|
|
|
148
200
|
}
|
|
149
201
|
}
|
|
150
202
|
const user = await namespaceForBackend(backend, opts.githubUser);
|
|
151
|
-
const
|
|
203
|
+
const scope = opts.scope ?? 'public';
|
|
204
|
+
const includeHidden = scope !== 'public';
|
|
205
|
+
const query = new URLSearchParams({ format: 'json' });
|
|
206
|
+
if (includeHidden)
|
|
207
|
+
query.set('scope', 'mine');
|
|
208
|
+
const listUrl = `${backend.baseUrl.replace(/\/+$/, '')}/${encodeURIComponent(user)}?${query.toString()}`;
|
|
209
|
+
const listingHeaders = {};
|
|
210
|
+
if (includeHidden) {
|
|
211
|
+
if (!backend.token) {
|
|
212
|
+
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.`);
|
|
213
|
+
}
|
|
214
|
+
listingHeaders.authorization = `Bearer ${backend.token}`;
|
|
215
|
+
}
|
|
152
216
|
const fetchListing = opts.fetchListing ?? defaultListingFetch;
|
|
153
|
-
const res = await fetchListing(listUrl);
|
|
217
|
+
const res = await fetchListing(listUrl, listingHeaders);
|
|
154
218
|
if (res.status === 404) {
|
|
155
219
|
// A single-segment path 404s either because the namespace is genuinely empty
|
|
156
220
|
// (current template — the listing route gates on the namespace holding objects,
|
|
@@ -177,14 +241,23 @@ export async function runShareList(opts = {}) {
|
|
|
177
241
|
throw new Error(OUTDATED_TEMPLATE_HINT);
|
|
178
242
|
}
|
|
179
243
|
const parsed = parseShareListing(user, res.body);
|
|
180
|
-
return applyShareListFilters(parsed,
|
|
244
|
+
return applyShareListFilters(parsed, {
|
|
245
|
+
agent: opts.agent,
|
|
246
|
+
session: opts.session,
|
|
247
|
+
label: opts.label,
|
|
248
|
+
meta: opts.meta,
|
|
249
|
+
scope,
|
|
250
|
+
});
|
|
181
251
|
}
|
|
182
252
|
/** Client-side filtering over an already-fetched listing (RUSH-2683) — the
|
|
183
253
|
* Worker has no query surface for this, so it narrows the fetched set instead
|
|
184
254
|
* of a second round trip. `count` reflects the FILTERED set, matching what the
|
|
185
255
|
* caller actually sees. */
|
|
186
|
-
function applyShareListFilters(result, filters) {
|
|
256
|
+
export function applyShareListFilters(result, filters) {
|
|
187
257
|
let objects = result.objects;
|
|
258
|
+
if (filters.scope && filters.scope !== 'all') {
|
|
259
|
+
objects = objects.filter((o) => o.visibility === filters.scope);
|
|
260
|
+
}
|
|
188
261
|
if (filters.agent) {
|
|
189
262
|
const needle = filters.agent.toLowerCase();
|
|
190
263
|
objects = objects.filter((o) => (o.agent ?? '').toLowerCase() === needle);
|
|
@@ -196,6 +269,9 @@ function applyShareListFilters(result, filters) {
|
|
|
196
269
|
const needle = filters.label.toLowerCase();
|
|
197
270
|
objects = objects.filter((o) => (o.label ?? '').toLowerCase().includes(needle));
|
|
198
271
|
}
|
|
272
|
+
if (filters.meta) {
|
|
273
|
+
objects = objects.filter((o) => Object.entries(filters.meta).every(([key, value]) => o.meta[key] === value));
|
|
274
|
+
}
|
|
199
275
|
return { user: result.user, count: objects.length, objects };
|
|
200
276
|
}
|
|
201
277
|
/** Human-readable bytes, e.g. `1.2 KB`, `640 B`. */
|
|
@@ -219,7 +295,9 @@ export function formatShareList(result, json = false) {
|
|
|
219
295
|
chalk.dim(` ${result.count} published ${result.count === 1 ? 'page' : 'pages'}`);
|
|
220
296
|
const rows = result.objects.map((o) => {
|
|
221
297
|
const when = o.publishedAt ? o.publishedAt.slice(0, 10) : 'unknown';
|
|
222
|
-
const
|
|
298
|
+
const visibility = o.visibility ?? 'public';
|
|
299
|
+
const visibilityBit = visibility === 'public' ? chalk.green('public') : chalk.yellow(visibility);
|
|
300
|
+
const bits = [visibilityBit, when, formatBytes(o.size)];
|
|
223
301
|
if (o.agent)
|
|
224
302
|
bits.push(o.agent);
|
|
225
303
|
if (o.revisionCount > 0)
|
|
@@ -341,6 +419,18 @@ export function formatShareDeleteResult(result, json = false) {
|
|
|
341
419
|
}
|
|
342
420
|
return lines.join('\n');
|
|
343
421
|
}
|
|
422
|
+
/** Human summary of an in-place visibility change. */
|
|
423
|
+
function formatShareVisibilityResult(result, requested) {
|
|
424
|
+
const now = result.visibility ?? requested;
|
|
425
|
+
const from = result.previousVisibility && result.previousVisibility !== now
|
|
426
|
+
? `${chalk.dim(result.previousVisibility)} → `
|
|
427
|
+
: '';
|
|
428
|
+
return [
|
|
429
|
+
`${chalk.green('✓')} visibility ${from}${chalk.bold(now)}`,
|
|
430
|
+
chalk.dim(` ${result.url}`),
|
|
431
|
+
chalk.dim(' body unchanged — no revision created'),
|
|
432
|
+
].join('\n');
|
|
433
|
+
}
|
|
344
434
|
/** Shared handler for `agents artifacts share delete <targets...>` and the
|
|
345
435
|
* nested `agents artifacts unshare <targets...>` alias. Deletes each target independently and
|
|
346
436
|
* continues past a failed one (rm-style), reporting all results and exiting
|
|
@@ -426,6 +516,36 @@ const SHARE_DELETE_NOTES = `
|
|
|
426
516
|
|
|
427
517
|
agents artifacts share delete === agents artifacts unshare (same command, different name).
|
|
428
518
|
`;
|
|
519
|
+
const SHARE_VISIBILITY_EXAMPLES = `
|
|
520
|
+
# Take a public page private to just you (Phoenix session required for me/org)
|
|
521
|
+
agents artifacts share visibility https://share.agents-cli.sh/octocat/q3-plan me
|
|
522
|
+
|
|
523
|
+
# Make it public again — by <user>/<slug> or a bare slug in your namespace
|
|
524
|
+
agents artifacts share visibility octocat/q3-plan public
|
|
525
|
+
agents artifacts share visibility q3-plan unlisted
|
|
526
|
+
|
|
527
|
+
# Share with your Phoenix org (rejected on a public-inbox email domain)
|
|
528
|
+
agents artifacts share visibility q3-plan org
|
|
529
|
+
|
|
530
|
+
# Machine-readable result
|
|
531
|
+
agents artifacts share visibility q3-plan me --visibility-json
|
|
532
|
+
`;
|
|
533
|
+
const SHARE_VISIBILITY_NOTES = `
|
|
534
|
+
Changes an ALREADY-published page's visibility in place: the slug — and so the
|
|
535
|
+
URL — is preserved. It re-stamps only the visibility on the stored object; the
|
|
536
|
+
body, provenance, label, and --meta are untouched, so — like 'agents artifacts
|
|
537
|
+
share edit' — this is a metadata-only rewrite and creates no revision.
|
|
538
|
+
|
|
539
|
+
public is listed in the gallery; unlisted is a capability URL (GET still 200,
|
|
540
|
+
X-Robots-Tag: noindex, hidden from gallery/list); me is visible only to the
|
|
541
|
+
signed-in owner; org is visible to members of the same Phoenix organization.
|
|
542
|
+
me and org require a Phoenix session — run 'agents auth login' first — and org
|
|
543
|
+
is refused on a public-inbox email domain (gmail.com, outlook.com, …).
|
|
544
|
+
|
|
545
|
+
This hits the live endpoint via the owner's Phoenix session (or the BYO
|
|
546
|
+
WRITE_TOKEN for public/unlisted). It does not re-run the pre-publish
|
|
547
|
+
sensitive-content scan — the body is unchanged.
|
|
548
|
+
`;
|
|
429
549
|
/**
|
|
430
550
|
* Register the `share` subtree under its parent group — `agents artifacts share`
|
|
431
551
|
* (see commands/artifacts.ts). `agents artifacts unshare` is the nested alias
|
|
@@ -436,6 +556,10 @@ const SHARE_DELETE_NOTES = `
|
|
|
436
556
|
export function registerShareCommands(artifactsCmd) {
|
|
437
557
|
const shareCmd = artifactsCmd
|
|
438
558
|
.command('share')
|
|
559
|
+
// Child task commands intentionally reuse publish vocabulary (`edit --label`,
|
|
560
|
+
// `list --meta`). Keep option ownership positional within this subtree so
|
|
561
|
+
// Commander does not silently resolve those flags to the parent publisher.
|
|
562
|
+
.enablePositionalOptions()
|
|
439
563
|
.description('Publish an HTML file to a shareable link — managed if signed in, otherwise your Cloudflare R2.')
|
|
440
564
|
.argument('[file]', 'file to publish (HTML or any static asset)')
|
|
441
565
|
.option('--slug <slug>', 'URL slug override (default: stable slug of the artifact title, then filename)')
|
|
@@ -553,6 +677,43 @@ ${SHARE_DELETE_NOTES}
|
|
|
553
677
|
shareDeleteCmd.action(async (targets, opts) => {
|
|
554
678
|
await runShareDelete(targets, opts);
|
|
555
679
|
});
|
|
680
|
+
// `agents artifacts share visibility <target> <level>` — change an
|
|
681
|
+
// already-published page's visibility in place. Kept in its own block; it
|
|
682
|
+
// reuses the delete target parser and the same PATCH metadata-edit route as
|
|
683
|
+
// `share edit` (visibility is metadata), so it re-stamps the stored object
|
|
684
|
+
// rather than re-publishing — a re-publish over the PUT path can't express an
|
|
685
|
+
// in-place change (GET injects the attribution bar at serve time and never
|
|
686
|
+
// strips it, and the served body carries no provenance/label/--meta back to
|
|
687
|
+
// re-send). --for-user / --visibility-json (not --github-user / --json): the
|
|
688
|
+
// parent `share <file>` already owns both long names, and commander resolves a
|
|
689
|
+
// child option's long name against the whole ancestor chain, silently dropping
|
|
690
|
+
// a same-named child (RUSH-2687) — the same rename the delete/list blocks make.
|
|
691
|
+
const shareVisibilityCmd = shareCmd
|
|
692
|
+
.command('visibility')
|
|
693
|
+
.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.")
|
|
694
|
+
.addArgument(new Argument('<target>', 'the published page: a full URL, <user>/<slug>, or a bare slug in your namespace'))
|
|
695
|
+
.addArgument(new Argument('<level>', 'new visibility').choices([...SHARE_VISIBILITY_LEVELS]))
|
|
696
|
+
.option('--for-user <user>', 'GitHub username for resolving a bare-slug target (default: resolved from gh/git config)')
|
|
697
|
+
.option('--visibility-json', 'emit a machine-readable result')
|
|
698
|
+
.action(async (target, level, opts) => {
|
|
699
|
+
try {
|
|
700
|
+
const visibility = level;
|
|
701
|
+
const result = await runShareEdit(target, { visibility, githubUser: opts.forUser });
|
|
702
|
+
if (opts.visibilityJson) {
|
|
703
|
+
console.log(JSON.stringify(result, null, 2));
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
console.log(formatShareVisibilityResult(result, visibility));
|
|
707
|
+
}
|
|
708
|
+
catch (e) {
|
|
709
|
+
console.error(chalk.red(e.message));
|
|
710
|
+
process.exitCode = 1;
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
setHelpSections(shareVisibilityCmd, {
|
|
714
|
+
examples: SHARE_VISIBILITY_EXAMPLES,
|
|
715
|
+
notes: SHARE_VISIBILITY_NOTES,
|
|
716
|
+
});
|
|
556
717
|
shareCmd
|
|
557
718
|
.command('join')
|
|
558
719
|
.description('Use an existing synced share endpoint and write token (no provisioning).')
|
|
@@ -664,20 +825,33 @@ ${SHARE_DELETE_NOTES}
|
|
|
664
825
|
// share.test.ts). Matches the `revisions` precedent (--for-user/
|
|
665
826
|
// --revisions-json) and the `delete`/`unshare` precedent (--for-user/
|
|
666
827
|
// --delete-json) below.
|
|
828
|
+
//
|
|
829
|
+
// `--visibility` is also owned by the parent `share <file>` command (publish),
|
|
830
|
+
// so the listing filter is named `--scope` here; `--all` is the convenience
|
|
831
|
+
// alias for `--scope all`.
|
|
667
832
|
.option('--for-user <user>', 'GitHub username whose namespace to list (default: resolved from gh/git config)')
|
|
833
|
+
.addOption(new Option('--scope <level>', 'visibility filter: public (default), unlisted, me, org, or all')
|
|
834
|
+
.choices(['public', 'unlisted', 'me', 'org', 'all'])
|
|
835
|
+
.default('public'))
|
|
836
|
+
.option('--all', "list every page including hidden unlisted/me/org shares (alias for --scope all)")
|
|
668
837
|
.option('--agent <name>', 'filter to shares published by this agent/harness (case-insensitive)')
|
|
669
838
|
.option('--session <id>', 'filter to shares published from this session id')
|
|
670
839
|
// Named --label-contains, not --label: `share <file>` (the parent) already owns
|
|
671
840
|
// `--label`/`--title`, same collision class as --for-user/--list-json above.
|
|
672
841
|
.option('--label-contains <substr>', 'filter to shares whose label contains this text (case-insensitive)')
|
|
673
|
-
.option('--
|
|
842
|
+
.option('--meta <key=value>', 'filter by an exact arbitrary metadata entry (repeatable)', (v, p) => [...p, v], [])
|
|
843
|
+
.option('--list-json', 'emit the machine-readable listing (slug, url, size, contentType, publishedAt, expiresAt, label, visibility, agent, session, host, repo, revisionCount, meta)')
|
|
674
844
|
.action(async (opts) => {
|
|
675
845
|
try {
|
|
846
|
+
const scope = opts.all ? 'all' : (opts.scope ?? 'public');
|
|
847
|
+
const parentMeta = shareCmd.opts().meta ?? [];
|
|
676
848
|
const result = await runShareList({
|
|
677
849
|
githubUser: opts.forUser,
|
|
850
|
+
scope,
|
|
678
851
|
agent: opts.agent,
|
|
679
852
|
session: opts.session,
|
|
680
853
|
label: opts.labelContains,
|
|
854
|
+
meta: parseMetaEntries(opts.meta.length ? opts.meta : parentMeta),
|
|
681
855
|
});
|
|
682
856
|
console.log(formatShareList(result, Boolean(opts.listJson)));
|
|
683
857
|
}
|
|
@@ -691,6 +865,11 @@ ${SHARE_DELETE_NOTES}
|
|
|
691
865
|
# Everything you've published, newest first
|
|
692
866
|
agents artifacts share list
|
|
693
867
|
|
|
868
|
+
# Include your hidden pages (unlisted / me / org) so you can see everything
|
|
869
|
+
agents artifacts share list --all
|
|
870
|
+
agents artifacts share list --scope me
|
|
871
|
+
agents artifacts share list --scope unlisted
|
|
872
|
+
|
|
694
873
|
# Machine-readable — e.g. pull every still-public URL with jq
|
|
695
874
|
agents artifacts share list --list-json | jq -r '.objects[].url'
|
|
696
875
|
|
|
@@ -700,18 +879,83 @@ ${SHARE_DELETE_NOTES}
|
|
|
700
879
|
# Narrow by who/what published it
|
|
701
880
|
agents artifacts share list --agent claude
|
|
702
881
|
agents artifacts share list --label-contains "fleet plan"
|
|
882
|
+
agents artifacts share list --meta kind=plan --meta status=final
|
|
703
883
|
`,
|
|
704
884
|
notes: `
|
|
705
885
|
Lists the ACTIVE pages in your namespace — expired links and the sibling .png OG
|
|
706
|
-
covers are omitted
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
886
|
+
covers are omitted. By default this mirrors the public gallery (public pages only).
|
|
887
|
+
Use --scope unlisted|me|org or --all to include your hidden pages; those scopes
|
|
888
|
+
send the owner's bearer and a 'scope=mine' hint to the Worker's JSON listing route,
|
|
889
|
+
which returns hidden pages ONLY after verifying the caller owns the namespace.
|
|
890
|
+
Signed-in users list the managed endpoint (share.agents-cli.sh/<handle>); otherwise
|
|
891
|
+
BYO. It reads the endpoint's JSON listing route, which ships with the current
|
|
892
|
+
Worker template. If a BYO Worker predates this feature the command says so and
|
|
893
|
+
points you at 'agents artifacts share update' (RUSH-2449) rather than returning a
|
|
894
|
+
wrong or empty result — see 'agents artifacts share status' for whether an update
|
|
895
|
+
is due.
|
|
896
|
+
|
|
897
|
+
--agent/--session/--label-contains/--meta filter the fetched listing client-side;
|
|
898
|
+
--list-json's count reflects the filtered set. Each human row shows the page's
|
|
899
|
+
visibility so public vs hidden is obvious at a glance.
|
|
900
|
+
`,
|
|
901
|
+
});
|
|
902
|
+
const shareEditCmd = shareCmd
|
|
903
|
+
.command('edit <target>')
|
|
904
|
+
.description('Edit a published share\'s label or arbitrary metadata without republishing its body.')
|
|
905
|
+
.option('--for-user <user>', 'namespace for resolving a bare slug (default: your namespace)')
|
|
906
|
+
.option('--label <text>', 'set or replace the display label')
|
|
907
|
+
.option('--remove-label', 'remove the display label')
|
|
908
|
+
.option('--meta <key=value>', 'merge a metadata entry (repeatable)', (v, p) => [...p, v], [])
|
|
909
|
+
.option('--replace-meta <key=value>', 'replace all arbitrary metadata with these entries (repeatable)', (v, p) => [...p, v], [])
|
|
910
|
+
.option('--remove-meta <key>', 'remove an arbitrary metadata key (repeatable)', (v, p) => [...p, v], [])
|
|
911
|
+
.option('--edit-json', 'emit the machine-readable edit result')
|
|
912
|
+
.action(async (target, opts) => {
|
|
913
|
+
try {
|
|
914
|
+
const parent = shareCmd.opts();
|
|
915
|
+
const label = opts.label ?? parent.label;
|
|
916
|
+
const mergeMeta = opts.meta.length ? opts.meta : (parent.meta ?? []);
|
|
917
|
+
if (label !== undefined && opts.removeLabel)
|
|
918
|
+
throw new Error('--label and --remove-label are mutually exclusive.');
|
|
919
|
+
if (mergeMeta.length && opts.replaceMeta.length)
|
|
920
|
+
throw new Error('--meta and --replace-meta are mutually exclusive.');
|
|
921
|
+
if (opts.replaceMeta.length && opts.removeMeta.length)
|
|
922
|
+
throw new Error('--replace-meta and --remove-meta are mutually exclusive.');
|
|
923
|
+
if (label === undefined && !opts.removeLabel && !mergeMeta.length && !opts.replaceMeta.length && !opts.removeMeta.length)
|
|
924
|
+
throw new Error('Nothing to edit. Pass --label, --remove-label, --meta, --replace-meta, or --remove-meta.');
|
|
925
|
+
const removeMeta = opts.removeMeta.map((key) => Object.keys(parseMetaEntries([`${key}=x`]))[0]);
|
|
926
|
+
const result = await runShareEdit(target, {
|
|
927
|
+
githubUser: opts.forUser,
|
|
928
|
+
label: opts.removeLabel ? null : label === undefined ? undefined : sanitizeLabel(label),
|
|
929
|
+
meta: parseMetaEntries(opts.replaceMeta.length ? opts.replaceMeta : mergeMeta),
|
|
930
|
+
metaMode: opts.replaceMeta.length ? 'replace' : 'merge',
|
|
931
|
+
removeMeta,
|
|
932
|
+
// Parent `share --force` (RUSH-2687: a child --force is silently dropped).
|
|
933
|
+
force: parent.force,
|
|
934
|
+
});
|
|
935
|
+
console.log(opts.editJson ? JSON.stringify(result, null, 2) : chalk.green(`updated ${result.url}`));
|
|
936
|
+
}
|
|
937
|
+
catch (e) {
|
|
938
|
+
console.error(chalk.red(e.message));
|
|
939
|
+
process.exitCode = 1;
|
|
940
|
+
}
|
|
941
|
+
});
|
|
942
|
+
setHelpSections(shareEditCmd, {
|
|
943
|
+
examples: `
|
|
944
|
+
# Change only the gallery/list title
|
|
945
|
+
agents artifacts share edit q3-plan --label "Q3 fleet plan"
|
|
712
946
|
|
|
713
|
-
|
|
714
|
-
|
|
947
|
+
# Merge metadata, or deliberately replace/remove it
|
|
948
|
+
agents artifacts share edit q3-plan --meta status=final --meta ticket=PHNX-3278
|
|
949
|
+
agents artifacts share edit q3-plan --replace-meta kind=plan --replace-meta status=final
|
|
950
|
+
agents artifacts share edit q3-plan --remove-meta status
|
|
951
|
+
`,
|
|
952
|
+
notes: `
|
|
953
|
+
Metadata edits preserve the exact published body, content type/HTTP metadata,
|
|
954
|
+
publication time, visibility, expiry, provenance, cover, and retained revisions. --meta merges;
|
|
955
|
+
--replace-meta replaces all arbitrary metadata; --remove-meta deletes named keys.
|
|
956
|
+
Reserved Worker/provenance keys cannot be changed through metadata flags.
|
|
957
|
+
Edited labels and metadata values are scanned for emails and credential-shaped
|
|
958
|
+
strings the same way publish is — pass --force to write them anyway.
|
|
715
959
|
`,
|
|
716
960
|
});
|
|
717
961
|
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
|
*
|
package/dist/commands/ssh.js
CHANGED
|
@@ -34,7 +34,7 @@ import { clearPendingSentinel } from '../lib/devices/pending.js';
|
|
|
34
34
|
import { getDeviceDiscoveryStatus, setDeviceDiscoveryStatus } from '../lib/devices/discovery-policy.js';
|
|
35
35
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
36
36
|
import { hostNameFor, renderSshConfig } from '../lib/devices/ssh-config.js';
|
|
37
|
-
import { ASKPASS_BUNDLE_ENV, ASKPASS_KEY_ENV, buildSshInvocation, deviceIdentityArgs, fleetDialTarget, writeAskpassShim, } from '../lib/devices/connect.js';
|
|
37
|
+
import { ASKPASS_BUNDLE_ENV, ASKPASS_KEY_ENV, buildSshInvocation, deviceIdentityArgs, fleetDialTarget, isAgentsBrowserDrive, markFleetRemote, writeAskpassShim, } from '../lib/devices/connect.js';
|
|
38
38
|
import { ensureManagedKnownHostsDir, isHostPinned } from '../lib/devices/known-hosts.js';
|
|
39
39
|
import { shouldSyncTerminfo, syncTerminfoToDevice, terminfoHostKey } from '../lib/devices/terminfo.js';
|
|
40
40
|
import { fanOutDevices, fleetHealthSkip, planFleetTargets, remoteFleetTargets, runFleet, skipLabel, upgradeCommand, } from '../lib/devices/fleet.js';
|
|
@@ -351,9 +351,21 @@ function trySshLeasedBox(name, cmd) {
|
|
|
351
351
|
console.error(chalk.red(`Leased box '${name}' is not reachable yet (status: ${boxStatus(box)}).`));
|
|
352
352
|
process.exit(1);
|
|
353
353
|
}
|
|
354
|
-
|
|
354
|
+
// Crabbox ssh does not go through buildSshInvocation; stamp the same
|
|
355
|
+
// consent marker so a browser drive on a leased box is gated too.
|
|
356
|
+
const remoteCmd = leasedBoxRemoteCmd(cmd);
|
|
357
|
+
const res = spawnSync(sshArgv[0], [...sshArgv.slice(1), ...remoteCmd], { stdio: 'inherit' });
|
|
355
358
|
process.exit(res.status ?? 1);
|
|
356
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* Remote argv for `agents ssh <slug>` into a leased crabbox. Crabbox ssh does
|
|
362
|
+
* not go through {@link buildSshInvocation}, so this stamps the same
|
|
363
|
+
* AGENTS_FLEET_REMOTE consent marker a registered-device ssh would (PHNX-3065).
|
|
364
|
+
* Exported so the branch is unit-testable without a live crabbox.
|
|
365
|
+
*/
|
|
366
|
+
export function leasedBoxRemoteCmd(cmd) {
|
|
367
|
+
return isAgentsBrowserDrive(cmd) ? markFleetRemote(cmd, { shell: 'posix' }) : cmd;
|
|
368
|
+
}
|
|
357
369
|
/** Resolve a device or exit with a clear error. */
|
|
358
370
|
async function mustGetDevice(name) {
|
|
359
371
|
const d = await getDevice(name);
|
|
@@ -2412,6 +2424,10 @@ An interactive login with no command mirrors the home-relative directory you
|
|
|
2412
2424
|
launched from — 'agents ssh yosemite-s0' from ~/src/app lands in ~/src/app on
|
|
2413
2425
|
the target when it exists, else the remote home. Same portable-cwd rule as
|
|
2414
2426
|
'agents run --device'. Passing a command keeps the remote home.
|
|
2427
|
+
|
|
2428
|
+
An 'agents browser …', 'ag browser …', or standalone 'browser …' command is
|
|
2429
|
+
stamped AGENTS_FLEET_REMOTE so the target's browser.remote-control consent
|
|
2430
|
+
gate applies, same as 'agents browser <verb> --device <name>'.
|
|
2415
2431
|
`)
|
|
2416
2432
|
.action(async (name, cmd) => {
|
|
2417
2433
|
// Hidden askpass bridge: ssh execs the shim, which re-invokes us here.
|
package/dist/commands/status.js
CHANGED
|
@@ -97,6 +97,20 @@ export function registerStatusCommand(syncCmd) {
|
|
|
97
97
|
if (status.totals.orphan > 0) {
|
|
98
98
|
console.log(chalk.gray(` (${status.totals.orphan} orphan${status.totals.orphan === 1 ? '' : 's'} — run \`agents prune cleanup\`)`));
|
|
99
99
|
}
|
|
100
|
+
// Config drift is its OWN class, not a per-agent "N missing": a box that has
|
|
101
|
+
// not folded its device-scoped state still carries a stale top-level header or
|
|
102
|
+
// a lingering central fleet/hosts/accounts/browser block. Surface it distinctly
|
|
103
|
+
// so an un-drained box is SEEN here instead of via a mystery pull conflict
|
|
104
|
+
// (PHNX-3315).
|
|
105
|
+
if (status.config.staleHeader || status.config.centralLeaks.length > 0) {
|
|
106
|
+
console.log(` ${'config (device-scoped)'.padEnd(28)} ${chalk.yellow('not drained — folds automatically on normal `agents` use here (idempotent)')}`);
|
|
107
|
+
if (status.config.staleHeader) {
|
|
108
|
+
console.log(chalk.gray(' · top-level agents.yaml header is stale (pre-rename)'));
|
|
109
|
+
}
|
|
110
|
+
for (const leak of status.config.centralLeaks) {
|
|
111
|
+
console.log(chalk.gray(` · central ${leak} should be device-scoped`));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
100
114
|
// Hand off to the shared interactive/apply flow (summary already printed above).
|
|
101
115
|
await promptDriftSync({ cwd, yes: opts.yes, status, quiet: true });
|
|
102
116
|
});
|
package/dist/commands/view.d.ts
CHANGED
|
@@ -81,7 +81,9 @@ export declare function parseResourceSections(options: {
|
|
|
81
81
|
* agents-cli extension's "resume current session in best available version"
|
|
82
82
|
* command).
|
|
83
83
|
*/
|
|
84
|
-
export declare function collectAgentsJson(filterAgentId?: AgentId, resourceSections?: Set<ResourceSection
|
|
84
|
+
export declare function collectAgentsJson(filterAgentId?: AgentId, resourceSections?: Set<ResourceSection>, opts?: {
|
|
85
|
+
forceRefresh?: boolean;
|
|
86
|
+
}): Promise<ViewJsonAgent[]>;
|
|
85
87
|
/**
|
|
86
88
|
* Identity key for duplicate-install detection. Prefers accountKey — which
|
|
87
89
|
* encodes account AND org — over the bare email: two installs can share an
|
package/dist/commands/view.js
CHANGED
|
@@ -1209,7 +1209,7 @@ export function parseResourceSections(options, jsonMode) {
|
|
|
1209
1209
|
* agents-cli extension's "resume current session in best available version"
|
|
1210
1210
|
* command).
|
|
1211
1211
|
*/
|
|
1212
|
-
export async function collectAgentsJson(filterAgentId, resourceSections) {
|
|
1212
|
+
export async function collectAgentsJson(filterAgentId, resourceSections, opts) {
|
|
1213
1213
|
const agentsToShow = filterAgentId ? [filterAgentId] : ALL_AGENT_IDS;
|
|
1214
1214
|
const authCache = readAuthHealthCache();
|
|
1215
1215
|
const host = machineId();
|
|
@@ -1230,7 +1230,7 @@ export async function collectAgentsJson(filterAgentId, resourceSections) {
|
|
|
1230
1230
|
home,
|
|
1231
1231
|
cliVersion: version,
|
|
1232
1232
|
info,
|
|
1233
|
-
})));
|
|
1233
|
+
})), { forceRefresh: opts?.forceRefresh === true });
|
|
1234
1234
|
const mergeCanonical = (info) => {
|
|
1235
1235
|
const key = getUsageLookupKey(info);
|
|
1236
1236
|
if (!key)
|
|
@@ -1273,6 +1273,7 @@ export async function collectAgentsJson(filterAgentId, resourceSections) {
|
|
|
1273
1273
|
plan: info.plan,
|
|
1274
1274
|
usageStatus: info.usageStatus,
|
|
1275
1275
|
overageCredits: info.overageCredits,
|
|
1276
|
+
usageError: usageInfo?.error ?? null,
|
|
1276
1277
|
windows: snapshot
|
|
1277
1278
|
? snapshot.windows.map((w) => ({
|
|
1278
1279
|
key: w.key,
|
|
@@ -1562,7 +1563,7 @@ export async function viewAction(agentArg, options) {
|
|
|
1562
1563
|
return;
|
|
1563
1564
|
}
|
|
1564
1565
|
if (json) {
|
|
1565
|
-
const data = await collectAgentsJson(undefined, resourceSections);
|
|
1566
|
+
const data = await collectAgentsJson(undefined, resourceSections, { forceRefresh });
|
|
1566
1567
|
console.log(JSON.stringify(data, null, 2));
|
|
1567
1568
|
return;
|
|
1568
1569
|
}
|
|
@@ -1624,7 +1625,7 @@ export async function viewAction(agentArg, options) {
|
|
|
1624
1625
|
if (json) {
|
|
1625
1626
|
// --json ignores the @version suffix, but --resources/--detailed (or a
|
|
1626
1627
|
// section flag) now attach each version's resource inventory + sync-state.
|
|
1627
|
-
const data = await collectAgentsJson(agentId, resourceSections);
|
|
1628
|
+
const data = await collectAgentsJson(agentId, resourceSections, { forceRefresh });
|
|
1628
1629
|
console.log(JSON.stringify(data[0] ?? { agent: agentId, versions: [], harnesses: [] }, null, 2));
|
|
1629
1630
|
return;
|
|
1630
1631
|
}
|