@withandeo/cli 0.19.11 → 0.19.13
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/README.md +130 -2
- package/dist/api.d.ts +4 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +6 -0
- package/dist/api.js.map +1 -1
- package/dist/cli.js +4 -1
- package/dist/cli.js.map +1 -1
- package/dist/cloud-agent.d.ts +16 -0
- package/dist/cloud-agent.d.ts.map +1 -0
- package/dist/cloud-agent.js +292 -0
- package/dist/cloud-agent.js.map +1 -0
- package/dist/contracts.d.ts +3 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/documentation.d.ts +23 -0
- package/dist/documentation.d.ts.map +1 -1
- package/dist/documentation.js +157 -13
- package/dist/documentation.js.map +1 -1
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +204 -16
- package/dist/main.js.map +1 -1
- package/dist/network-secret.d.ts +2 -0
- package/dist/network-secret.d.ts.map +1 -0
- package/dist/network-secret.js +22 -0
- package/dist/network-secret.js.map +1 -0
- package/dist/oauth.d.ts +2 -0
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js.map +1 -1
- package/dist/update-storage.d.ts +3 -0
- package/dist/update-storage.d.ts.map +1 -0
- package/dist/update-storage.js +69 -0
- package/dist/update-storage.js.map +1 -0
- package/dist/updates.d.ts +52 -0
- package/dist/updates.d.ts.map +1 -0
- package/dist/updates.js +297 -0
- package/dist/updates.js.map +1 -0
- package/package.json +3 -1
- package/skill/publication.json +1 -1
- package/skill/tender-accounts/SKILL.md +9 -1
- package/skill/tender-accounts/references/skills.md +5 -2
package/dist/main.js
CHANGED
|
@@ -18,7 +18,10 @@ import { captureCommand, runProjectCommand } from "./processes.js";
|
|
|
18
18
|
import { createDefaultProfileBindings, } from "./profile-bindings.js";
|
|
19
19
|
import { initializeStarter } from "./starter.js";
|
|
20
20
|
import { normalizeAndeoEnvironment } from "./andeo-environment.js";
|
|
21
|
-
import {
|
|
21
|
+
import { readNetworkSecret } from "./network-secret.js";
|
|
22
|
+
import { cloudAgentClient } from "./cloud-agent.js";
|
|
23
|
+
import { DOC_TOPICS, readDocumentation, installDocumentation, installedSkillCheck, skillDocumentationStatus, syncDocumentation } from "./documentation.js";
|
|
24
|
+
import { CLI_PACKAGE_DIRECTORY, configureAutoUpgrade, runUpdateCommand, upgradeCli } from "./updates.js";
|
|
22
25
|
import { capabilityPreflight } from "./capability-preflight.js";
|
|
23
26
|
import { flagEnvironment, flagKey, validateFlagDefinition, flagContext, FLAGSHIP_SCHEMA, FLAG_DELETION_GUIDANCE, FlagCompatibilityError } from "./flagship-contract.js";
|
|
24
27
|
const packageMetadata = createRequire(import.meta.url)("../package.json");
|
|
@@ -38,6 +41,8 @@ const DEFAULT_RUNTIME = {
|
|
|
38
41
|
sleep: async (milliseconds) => await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds)),
|
|
39
42
|
runProjectCommand,
|
|
40
43
|
captureCommand,
|
|
44
|
+
updateCommand: runUpdateCommand,
|
|
45
|
+
packageDirectory: CLI_PACKAGE_DIRECTORY,
|
|
41
46
|
authStore: createDefaultAuthStore(process.env),
|
|
42
47
|
profileBindings: createDefaultProfileBindings(process.env),
|
|
43
48
|
openBrowser: openSystemBrowser,
|
|
@@ -112,9 +117,21 @@ async function execute(topic, options, cwd, runtime) {
|
|
|
112
117
|
return { value: await workflowCommand(topic.slice(10), cwd, options, runtime) };
|
|
113
118
|
if (topic.startsWith("email "))
|
|
114
119
|
return { value: await emailCommand(topic.slice(6), cwd, options, runtime) };
|
|
120
|
+
if (topic.startsWith("agents "))
|
|
121
|
+
return { value: await agentsCommand(topic.slice(7), cwd, options, runtime) };
|
|
115
122
|
if (topic.startsWith("flagship "))
|
|
116
123
|
return { value: await flagshipCommand(topic.slice(9), cwd, options, runtime) };
|
|
117
124
|
switch (topic) {
|
|
125
|
+
case "upgrade":
|
|
126
|
+
if (options.positionals.length)
|
|
127
|
+
throw new CliError("argument_invalid", "upgrade does not accept positional arguments.");
|
|
128
|
+
return { value: await upgradeCli(updateRuntime(runtime)) };
|
|
129
|
+
case "config autoupgrade on":
|
|
130
|
+
case "config autoupgrade off":
|
|
131
|
+
case "config autoupgrade status":
|
|
132
|
+
if (options.positionals.length)
|
|
133
|
+
throw new CliError("argument_invalid", "autoupgrade does not accept positional arguments.");
|
|
134
|
+
return { value: await configureAutoUpgrade(topic.split(" ")[2], updateRuntime(runtime)) };
|
|
118
135
|
case "docs": {
|
|
119
136
|
const name = options.positionals[0];
|
|
120
137
|
if (!name)
|
|
@@ -144,6 +161,14 @@ async function execute(topic, options, cwd, runtime) {
|
|
|
144
161
|
: { raw: await readSkill() };
|
|
145
162
|
case "skill install":
|
|
146
163
|
return { value: await installSkill(cwd, options, runtime) };
|
|
164
|
+
case "skill status":
|
|
165
|
+
case "skill sync": {
|
|
166
|
+
if (options.positionals.length)
|
|
167
|
+
throw new CliError("argument_invalid", "skill status/sync do not accept positional arguments.");
|
|
168
|
+
const { agent, base } = skillDestination(cwd, options, runtime);
|
|
169
|
+
const action = topic === "skill sync" ? syncDocumentation : skillDocumentationStatus;
|
|
170
|
+
return { value: { agent, ...await action(base, CLI_VERSION, { fetcher: runtime.fetcher }) } };
|
|
171
|
+
}
|
|
147
172
|
case "skills list": {
|
|
148
173
|
if (options.positionals.length)
|
|
149
174
|
throw new CliError("skills_arguments_invalid", "skills list does not accept positional arguments.");
|
|
@@ -173,6 +198,17 @@ async function execute(topic, options, cwd, runtime) {
|
|
|
173
198
|
return { value: await preview(cwd, options, runtime, true) };
|
|
174
199
|
case "auth status":
|
|
175
200
|
return { value: await authStatus(cwd, options, runtime) };
|
|
201
|
+
case "agent status":
|
|
202
|
+
if (runtime.environment.ANDEO_CLOUD_BOOTSTRAP === undefined)
|
|
203
|
+
throw new CliError("cloud_agent_required", "This command requires Cloud task-run authentication.");
|
|
204
|
+
return { value: await authStatus(cwd, options, runtime) };
|
|
205
|
+
case "agent complete":
|
|
206
|
+
case "agent cancel": {
|
|
207
|
+
const client = await cloudAgentClient(runtime.environment, cwd, runtime.fetcher, { tokenStdin: options.booleans.has("token-stdin"), profile: options.flags.has("profile"), apiUrl: selectedApiUrl(options, null, runtime.environment, runtime.defaultApiUrl) });
|
|
208
|
+
if (!client)
|
|
209
|
+
throw new CliError("cloud_agent_required", "This command requires Cloud task-run authentication.");
|
|
210
|
+
return { value: await client.finish(topic === "agent complete" ? "complete" : "cancel") };
|
|
211
|
+
}
|
|
176
212
|
case "auth list":
|
|
177
213
|
return { value: await authList(runtime) };
|
|
178
214
|
case "auth create":
|
|
@@ -351,6 +387,45 @@ async function execute(topic, options, cwd, runtime) {
|
|
|
351
387
|
throw new CliError("command_unknown", `Unknown command: ${topic}.`, "tender-accounts --help");
|
|
352
388
|
}
|
|
353
389
|
}
|
|
390
|
+
async function agentsCommand(action, cwd, options, runtime) {
|
|
391
|
+
if (!["list", "show", "suspend", "revoke", "resume", "cancel", "complete"].includes(action))
|
|
392
|
+
throw new CliError("argument_unknown", "Unknown agents command.");
|
|
393
|
+
if (runtime.environment.ANDEO_CLOUD_BOOTSTRAP !== undefined || runtime.environment.ANDEO_NETWORK_TOKEN !== undefined
|
|
394
|
+
|| runtime.environment.TENDER_ACCOUNTS_TOKEN !== undefined || options.booleans.has("token-stdin"))
|
|
395
|
+
throw new CliError("human_cli_required", "Agent management requires a human administrator login profile.", "andeo auth login");
|
|
396
|
+
const id = options.positionals[0];
|
|
397
|
+
if (id !== undefined && !/^agt_[A-Za-z0-9_-]+$/u.test(id))
|
|
398
|
+
throw new CliError("argument_invalid", "Use an agent ID returned by agents list.");
|
|
399
|
+
let revision;
|
|
400
|
+
if (!["list", "show"].includes(action)) {
|
|
401
|
+
const value = requiredFlag(options, "expected-revision");
|
|
402
|
+
revision = Number(value);
|
|
403
|
+
if (!/^\d+$/u.test(value) || !Number.isSafeInteger(revision))
|
|
404
|
+
throw new CliError("argument_invalid", "Use a nonnegative observed agent revision.");
|
|
405
|
+
}
|
|
406
|
+
const targets = ["grant", "installation", "bootstrap", "run"].filter(kind => options.flags.has(kind));
|
|
407
|
+
if (targets.length > 1 || ["cancel", "complete"].includes(action) && targets[0] !== "run"
|
|
408
|
+
|| ["suspend", "resume"].includes(action) && ["bootstrap", "run"].includes(targets[0] ?? ""))
|
|
409
|
+
throw new CliError("argument_invalid", "Choose one target compatible with this lifecycle action.");
|
|
410
|
+
const target = targets.length ? { kind: { grant: "grants", installation: "installations", bootstrap: "bootstraps", run: "runs" }[targets[0]], id: options.flags.get(targets[0]) } : undefined;
|
|
411
|
+
if (target && !/^[A-Za-z0-9_-]{3,180}$/u.test(target.id))
|
|
412
|
+
throw new CliError("argument_invalid", "Use a target ID returned by agents show.");
|
|
413
|
+
const access = await authenticatedClient(cwd, (await optionalContext(cwd))?.link ?? null, options, runtime);
|
|
414
|
+
if (access.source !== "login")
|
|
415
|
+
throw new CliError("human_cli_required", "Agent management requires a human administrator login profile.", "andeo auth login");
|
|
416
|
+
const result = await access.client.agents(id, ["list", "show"].includes(action) ? undefined : action, revision, target);
|
|
417
|
+
if (action === "resume") {
|
|
418
|
+
const response = result;
|
|
419
|
+
if (response.status !== "human_confirmation_required" || typeof response.approvalUrl !== "string")
|
|
420
|
+
throw new CliApiError("invalid_api_response", "Fresh browser confirmation instructions are required.");
|
|
421
|
+
const url = new URL(response.approvalUrl);
|
|
422
|
+
if (url.protocol !== "https:" || url.username || url.password)
|
|
423
|
+
throw new CliApiError("invalid_api_response", "The approval URL must be HTTPS.");
|
|
424
|
+
if (!options.booleans.has("no-open"))
|
|
425
|
+
await runtime.openBrowser(url.href);
|
|
426
|
+
}
|
|
427
|
+
return result;
|
|
428
|
+
}
|
|
354
429
|
async function emailCommand(action, cwd, options, runtime) {
|
|
355
430
|
if (options.positionals.length)
|
|
356
431
|
throw new CliError("email_arguments_invalid", "Use named email command options.");
|
|
@@ -1180,7 +1255,11 @@ async function sourcePush(cwd, options, runtime) {
|
|
|
1180
1255
|
const remote = options.flags.get("remote") ?? "tender";
|
|
1181
1256
|
await ensureGitRemote(gitRoot, remote, source.repository.remoteUrl, runtime);
|
|
1182
1257
|
const currentBranch = (await runtime.captureCommand("git", ["branch", "--show-current"], gitRoot)).stdout;
|
|
1183
|
-
const branch = options.flags.get("branch") ?? (currentBranch || source.repository.defaultBranch);
|
|
1258
|
+
const branch = options.flags.get("branch") ?? source.repository.assignedFeatureBranch ?? (currentBranch || source.repository.defaultBranch);
|
|
1259
|
+
if (source.repository.assignedFeatureBranch && branch !== source.repository.assignedFeatureBranch)
|
|
1260
|
+
throw new CliError("agent_branch_scope_mismatch", "This run may push only its assigned feature branch.");
|
|
1261
|
+
if (source.repository.assignedFeatureBranch && source.repository.changeRequestProtection !== "enabled")
|
|
1262
|
+
throw new CliError("agent_source_protection_required", "A human administrator must enable reviewed source protection before agent writes.");
|
|
1184
1263
|
assertGitBranchName(branch);
|
|
1185
1264
|
const expectedCurrentSha = options.flags.has("expected-current")
|
|
1186
1265
|
? requiredSourceSha(options, "expected-current")
|
|
@@ -1429,7 +1508,9 @@ async function sourceChangeCreate(cwd, options, runtime) {
|
|
|
1429
1508
|
throw new CliError("source_review_not_enabled", "A merchant administrator must enable reviewed changes for this managed repository first.");
|
|
1430
1509
|
}
|
|
1431
1510
|
const baseBranch = options.flags.get("base") ?? access.repository.defaultBranch;
|
|
1432
|
-
const headBranch = requiredFlag(options, "head");
|
|
1511
|
+
const headBranch = options.flags.get("head") ?? access.repository.assignedFeatureBranch ?? requiredFlag(options, "head");
|
|
1512
|
+
if (access.repository.assignedFeatureBranch && headBranch !== access.repository.assignedFeatureBranch)
|
|
1513
|
+
throw new CliError("agent_branch_scope_mismatch", "This run may prepare review only for its assigned feature branch.");
|
|
1433
1514
|
const title = requiredFlag(options, "title");
|
|
1434
1515
|
assertGitBranchName(baseBranch);
|
|
1435
1516
|
assertGitBranchName(headBranch);
|
|
@@ -1673,6 +1754,9 @@ async function authList(runtime) {
|
|
|
1673
1754
|
};
|
|
1674
1755
|
}
|
|
1675
1756
|
async function authLogin(cwd, options, runtime) {
|
|
1757
|
+
if (runtime.environment.ANDEO_NETWORK_TOKEN !== undefined || runtime.environment.ANDEO_CLOUD_BOOTSTRAP !== undefined) {
|
|
1758
|
+
assertNoEnvironmentCredentialForProfileManagement(runtime.environment);
|
|
1759
|
+
}
|
|
1676
1760
|
return await authenticateProfile(cwd, options, runtime, DEFAULT_AUTH_PROFILE, options.booleans.has("force"));
|
|
1677
1761
|
}
|
|
1678
1762
|
async function authCreate(cwd, options, runtime) {
|
|
@@ -1734,8 +1818,8 @@ async function authenticateProfile(cwd, options, runtime, profile, replaceExisti
|
|
|
1734
1818
|
};
|
|
1735
1819
|
}
|
|
1736
1820
|
const token = await waitForDeviceToken(oauth, pending, profile, runtime);
|
|
1737
|
-
await saveTokenResponse(runtime.authStore, profile, apiUrl, token, oauth, pending);
|
|
1738
|
-
return await authenticatedLoginSummary(apiUrl, token, profile, runtime);
|
|
1821
|
+
const stored = await saveTokenResponse(runtime.authStore, profile, apiUrl, token, oauth, pending);
|
|
1822
|
+
return await authenticatedLoginSummary(apiUrl, token, profile, runtime, stored);
|
|
1739
1823
|
}
|
|
1740
1824
|
const pkce = createPkcePair();
|
|
1741
1825
|
const callback = await runtime.waitForLoopbackAuthorization({
|
|
@@ -1759,8 +1843,8 @@ async function authenticateProfile(cwd, options, runtime, profile, replaceExisti
|
|
|
1759
1843
|
codeVerifier: pkce.verifier,
|
|
1760
1844
|
redirectUri: callback.redirectUri,
|
|
1761
1845
|
});
|
|
1762
|
-
await saveTokenResponse(runtime.authStore, profile, apiUrl, token, oauth, null);
|
|
1763
|
-
return await authenticatedLoginSummary(apiUrl, token, profile, runtime);
|
|
1846
|
+
const stored = await saveTokenResponse(runtime.authStore, profile, apiUrl, token, oauth, null);
|
|
1847
|
+
return await authenticatedLoginSummary(apiUrl, token, profile, runtime, stored);
|
|
1764
1848
|
}
|
|
1765
1849
|
async function authActivate(cwd, options, runtime) {
|
|
1766
1850
|
assertNoEnvironmentCredentialForProfileManagement(runtime.environment);
|
|
@@ -1811,11 +1895,19 @@ async function authDelete(_cwd, options, runtime) {
|
|
|
1811
1895
|
};
|
|
1812
1896
|
}
|
|
1813
1897
|
function assertNoEnvironmentCredentialForProfileManagement(environment) {
|
|
1898
|
+
if (environment.ANDEO_CLOUD_BOOTSTRAP !== undefined)
|
|
1899
|
+
throw new CliError("auth_profile_environment_override", "Cloud agent mode disables human profile management.", "Manage the installation in Andeo and the bootstrap in the Cloud network-secret vault.");
|
|
1900
|
+
if (environment.ANDEO_NETWORK_TOKEN !== undefined) {
|
|
1901
|
+
throw new CliError("auth_profile_environment_override", "ANDEO_NETWORK_TOKEN selects proxy-supplied authentication; local profile management is disabled.", "Manage the network secret in the cloud environment and revoke its credential in Andeo.");
|
|
1902
|
+
}
|
|
1814
1903
|
if (!environment.TENDER_ACCOUNTS_TOKEN)
|
|
1815
1904
|
return;
|
|
1816
1905
|
throw new CliError("auth_profile_environment_override", "TENDER_ACCOUNTS_TOKEN overrides local auth profiles, so profile management is disabled for this command.", "Unset TENDER_ACCOUNTS_TOKEN, then retry the auth profile command.");
|
|
1817
1906
|
}
|
|
1818
1907
|
async function authLogout(_cwd, options, runtime) {
|
|
1908
|
+
if (runtime.environment.ANDEO_NETWORK_TOKEN !== undefined || runtime.environment.ANDEO_CLOUD_BOOTSTRAP !== undefined) {
|
|
1909
|
+
assertNoEnvironmentCredentialForProfileManagement(runtime.environment);
|
|
1910
|
+
}
|
|
1819
1911
|
if (options.booleans.has("all") && options.booleans.has("local")) {
|
|
1820
1912
|
await runtime.authStore.clearAll();
|
|
1821
1913
|
await runtime.profileBindings.clearAll();
|
|
@@ -2144,6 +2236,16 @@ async function deliveryRetry(cwd, options, runtime) {
|
|
|
2144
2236
|
}
|
|
2145
2237
|
async function authenticatedClient(cwd, link, options, runtime) {
|
|
2146
2238
|
let apiUrl = selectedApiUrl(options, link, runtime.environment, runtime.defaultApiUrl);
|
|
2239
|
+
const cloud = await cloudAgentClient(runtime.environment, cwd, runtime.fetcher, { tokenStdin: options.booleans.has("token-stdin"), profile: options.flags.has("profile"), apiUrl });
|
|
2240
|
+
if (cloud)
|
|
2241
|
+
return { client: new AgentApiClient(apiUrl, "task-run", cloud.fetcher), apiUrl, source: "cloud-agent", accessTokenExpiresAt: cloud.accessTokenExpiresAt };
|
|
2242
|
+
const networkSecret = readNetworkSecret(runtime.environment, apiUrl);
|
|
2243
|
+
if (networkSecret !== undefined) {
|
|
2244
|
+
if (options.booleans.has("token-stdin") || options.flags.has("profile")) {
|
|
2245
|
+
throw new CliError("network_secret_auth_conflict", "ANDEO_NETWORK_TOKEN cannot be combined with --token-stdin or --profile.");
|
|
2246
|
+
}
|
|
2247
|
+
return { client: new AgentApiClient(apiUrl, networkSecret, runtime.fetcher), apiUrl, source: "network-secret" };
|
|
2248
|
+
}
|
|
2147
2249
|
const stdin = options.booleans.has("token-stdin") ? runtime.stdin : undefined;
|
|
2148
2250
|
const explicit = await readOptionalToken(cwd, runtime.environment, stdin);
|
|
2149
2251
|
if (explicit) {
|
|
@@ -2247,13 +2349,23 @@ function authCommandForProfile(profile) {
|
|
|
2247
2349
|
}
|
|
2248
2350
|
async function saveTokenResponse(store, profile, apiUrl, token, oauth, previous, acceptConcurrentRefreshWinner = false) {
|
|
2249
2351
|
const now = Date.now();
|
|
2352
|
+
const accessDeadline = Date.parse(tokenDeadline(token.expires_at, token.expires_in, now));
|
|
2353
|
+
let sessionDeadline = Date.parse(tokenDeadline(token.session_expires_at, token.refresh_expires_in, now));
|
|
2354
|
+
if (previous?.refreshToken && previous.refreshTokenExpiresAt) {
|
|
2355
|
+
// Rotation cannot extend a human session, including against older servers
|
|
2356
|
+
// that advertise the original full duration again on every refresh.
|
|
2357
|
+
const previousDeadline = Date.parse(previous.refreshTokenExpiresAt);
|
|
2358
|
+
if (Number.isFinite(previousDeadline)) {
|
|
2359
|
+
sessionDeadline = Math.min(previousDeadline, sessionDeadline);
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2250
2362
|
const stored = {
|
|
2251
2363
|
schema: CLI_AUTH_SCHEMA,
|
|
2252
2364
|
apiUrl,
|
|
2253
2365
|
accessToken: token.access_token,
|
|
2254
|
-
accessTokenExpiresAt: new Date(
|
|
2366
|
+
accessTokenExpiresAt: new Date(Math.min(accessDeadline, sessionDeadline)).toISOString(),
|
|
2255
2367
|
refreshToken: token.refresh_token,
|
|
2256
|
-
refreshTokenExpiresAt: new Date(
|
|
2368
|
+
refreshTokenExpiresAt: new Date(sessionDeadline).toISOString(),
|
|
2257
2369
|
...(previous?.principal ? { principal: previous.principal } : {}),
|
|
2258
2370
|
};
|
|
2259
2371
|
if (!await store.compareAndSwap(profile, previous, stored)) {
|
|
@@ -2272,6 +2384,21 @@ async function saveTokenResponse(store, profile, apiUrl, token, oauth, previous,
|
|
|
2272
2384
|
}
|
|
2273
2385
|
return stored;
|
|
2274
2386
|
}
|
|
2387
|
+
function tokenDeadline(absolute, seconds, now) {
|
|
2388
|
+
if (absolute !== undefined) {
|
|
2389
|
+
if (typeof absolute !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(absolute)
|
|
2390
|
+
|| !Number.isFinite(Date.parse(absolute)) || new Date(absolute).toISOString() !== absolute) {
|
|
2391
|
+
throw new CliApiError("invalid_api_response", "Andeo returned an invalid credential deadline.");
|
|
2392
|
+
}
|
|
2393
|
+
return absolute;
|
|
2394
|
+
}
|
|
2395
|
+
// Compatibility with APIs released before absolute deadline reporting.
|
|
2396
|
+
if (!Number.isSafeInteger(seconds) || seconds < 0 || !Number.isFinite(now + seconds * 1000)
|
|
2397
|
+
|| now + seconds * 1000 > 8.64e15) {
|
|
2398
|
+
throw new CliApiError("invalid_api_response", "Andeo returned an invalid credential lifetime.");
|
|
2399
|
+
}
|
|
2400
|
+
return new Date(now + seconds * 1000).toISOString();
|
|
2401
|
+
}
|
|
2275
2402
|
async function concurrentRefreshWinner(store, profile, previous) {
|
|
2276
2403
|
const current = await store.read(profile);
|
|
2277
2404
|
if (!current
|
|
@@ -2320,7 +2447,7 @@ async function waitForDeviceToken(oauth, pending, profile, runtime) {
|
|
|
2320
2447
|
await clearStoredProfile(runtime.authStore, profile, pending);
|
|
2321
2448
|
throw new CliError("device_authorization_expired", "The device authorization expired.", `${authCommandForProfile(profile)} --device --json`);
|
|
2322
2449
|
}
|
|
2323
|
-
async function authenticatedLoginSummary(apiUrl, token, profile, runtime) {
|
|
2450
|
+
async function authenticatedLoginSummary(apiUrl, token, profile, runtime, stored) {
|
|
2324
2451
|
const principal = await new AgentApiClient(apiUrl, token.access_token, runtime.fetcher).whoami();
|
|
2325
2452
|
await savePrincipalSummary(runtime.authStore, profile, principal);
|
|
2326
2453
|
return {
|
|
@@ -2329,9 +2456,10 @@ async function authenticatedLoginSummary(apiUrl, token, profile, runtime) {
|
|
|
2329
2456
|
apiUrl,
|
|
2330
2457
|
profile,
|
|
2331
2458
|
ownerEmail: principal.ownerEmail,
|
|
2459
|
+
...(principal.ownerPrincipalId ? { ownerPrincipalId: principal.ownerPrincipalId } : {}),
|
|
2332
2460
|
tenantId: principal.tenantId,
|
|
2333
2461
|
projects: principal.projects,
|
|
2334
|
-
accessTokenExpiresAt:
|
|
2462
|
+
accessTokenExpiresAt: stored.accessTokenExpiresAt,
|
|
2335
2463
|
storage: runtime.authStore.description(),
|
|
2336
2464
|
containsCredential: false,
|
|
2337
2465
|
};
|
|
@@ -2434,6 +2562,14 @@ async function optionalContext(cwd) {
|
|
|
2434
2562
|
}
|
|
2435
2563
|
}
|
|
2436
2564
|
async function installSkill(cwd, options, runtime) {
|
|
2565
|
+
const { agent, base } = skillDestination(cwd, options, runtime);
|
|
2566
|
+
return { status: "installed", agent, ...await installDocumentation(base, options.booleans.has("force"), CLI_VERSION, { fetcher: runtime.fetcher, bundled: options.booleans.has("bundled") }) };
|
|
2567
|
+
}
|
|
2568
|
+
function updateRuntime(runtime) {
|
|
2569
|
+
return { ...runtime, version: CLI_VERSION, runner: runtime.updateCommand, now: Date.now,
|
|
2570
|
+
notify: message => { runtime.stderr.write(`${message}\n`); } };
|
|
2571
|
+
}
|
|
2572
|
+
function skillDestination(cwd, options, runtime) {
|
|
2437
2573
|
const agent = options.flags.get("agent") ?? "codex";
|
|
2438
2574
|
const requestedDirectory = options.flags.get("directory");
|
|
2439
2575
|
const base = requestedDirectory
|
|
@@ -2446,7 +2582,7 @@ async function installSkill(cwd, options, runtime) {
|
|
|
2446
2582
|
if (!base) {
|
|
2447
2583
|
throw new CliError("skill_target_invalid", "--agent must be codex or claude unless --directory is provided.");
|
|
2448
2584
|
}
|
|
2449
|
-
return {
|
|
2585
|
+
return { agent, base };
|
|
2450
2586
|
}
|
|
2451
2587
|
async function readSkill() {
|
|
2452
2588
|
return await readFile(skillPath(), "utf8");
|
|
@@ -2570,10 +2706,16 @@ function commandTopic(argv) {
|
|
|
2570
2706
|
const topic = `preview ${argv[1]}`;
|
|
2571
2707
|
return argv.includes("--help") || argv.includes("-h") ? { topic: "help", values: [topic] } : { topic, values: argv.slice(2) };
|
|
2572
2708
|
}
|
|
2573
|
-
if (parent === "artifact" || parent === "email" || parent === "auth" || parent === "delivery" || parent === "source" || parent === "vars" || parent === "skill" || parent === "skills" || parent === "config" || parent === "production" || parent === "workflows") {
|
|
2709
|
+
if (parent === "agents" || parent === "agent" || parent === "artifact" || parent === "email" || parent === "auth" || parent === "delivery" || parent === "source" || parent === "vars" || parent === "skill" || parent === "skills" || parent === "config" || parent === "production" || parent === "workflows") {
|
|
2574
2710
|
const child = argv[1];
|
|
2575
2711
|
if (!child || child.startsWith("--"))
|
|
2576
2712
|
return { topic: "help", values: [parent] };
|
|
2713
|
+
if (parent === "config" && child === "autoupgrade") {
|
|
2714
|
+
const action = argv[2];
|
|
2715
|
+
if (!action || action.startsWith("-") || argv.includes("--help") || argv.includes("-h"))
|
|
2716
|
+
return { topic: "help", values: ["config autoupgrade"] };
|
|
2717
|
+
return { topic: `config autoupgrade ${action}`, values: argv.slice(3) };
|
|
2718
|
+
}
|
|
2577
2719
|
if (parent === "source" && child === "merge" && argv[2] && !argv[2].startsWith("-")) {
|
|
2578
2720
|
const topic = `source merge ${argv[2]}`;
|
|
2579
2721
|
return argv.includes("--help") || argv.includes("-h") ? { topic: "help", values: [topic] } : { topic, values: argv.slice(3) };
|
|
@@ -2661,6 +2803,10 @@ function assertAllowedOptions(topic, options) {
|
|
|
2661
2803
|
const authentication = ["api-url", "token-stdin", "profile"];
|
|
2662
2804
|
const allowed = {
|
|
2663
2805
|
"artifact finalize": [...common, "artifact-directory"],
|
|
2806
|
+
upgrade: common,
|
|
2807
|
+
"config autoupgrade on": common,
|
|
2808
|
+
"config autoupgrade off": common,
|
|
2809
|
+
"config autoupgrade status": common,
|
|
2664
2810
|
docs: common,
|
|
2665
2811
|
capabilities: [...common, ...authentication, "project"],
|
|
2666
2812
|
"email capabilities": [...common, ...authentication, "tenant", "project"],
|
|
@@ -2690,6 +2836,8 @@ function assertAllowedOptions(topic, options) {
|
|
|
2690
2836
|
"config example": common,
|
|
2691
2837
|
"skill print": common,
|
|
2692
2838
|
"skill install": [...common, "agent", "directory", "force", "bundled"],
|
|
2839
|
+
"skill status": [...common, "agent", "directory"],
|
|
2840
|
+
"skill sync": [...common, "agent", "directory"],
|
|
2693
2841
|
"skills list": [...common, ...authentication],
|
|
2694
2842
|
"skills get": [...common, ...authentication],
|
|
2695
2843
|
"skills file": [...common, ...authentication, "path"],
|
|
@@ -2705,6 +2853,16 @@ function assertAllowedOptions(topic, options) {
|
|
|
2705
2853
|
"auth deactivate": common,
|
|
2706
2854
|
"auth login": [...common, "api-url", "project", "client-name", "timeout-seconds", "device", "wait", "no-open", "force"],
|
|
2707
2855
|
"auth logout": [...common, "local", "all"],
|
|
2856
|
+
"agent status": [...common, ...authentication],
|
|
2857
|
+
"agent complete": [...common],
|
|
2858
|
+
"agent cancel": [...common],
|
|
2859
|
+
"agents list": [...common, ...authentication],
|
|
2860
|
+
"agents show": [...common, ...authentication],
|
|
2861
|
+
"agents suspend": [...common, ...authentication, "expected-revision", "grant", "installation"],
|
|
2862
|
+
"agents revoke": [...common, ...authentication, "expected-revision", "grant", "installation", "bootstrap", "run"],
|
|
2863
|
+
"agents resume": [...common, ...authentication, "expected-revision", "no-open", "grant", "installation"],
|
|
2864
|
+
"agents cancel": [...common, ...authentication, "expected-revision", "run"],
|
|
2865
|
+
"agents complete": [...common, ...authentication, "expected-revision", "run"],
|
|
2708
2866
|
link: [...common, ...authentication, "project"],
|
|
2709
2867
|
doctor: [...common, ...authentication],
|
|
2710
2868
|
preview: [
|
|
@@ -2811,6 +2969,12 @@ function assertAllowedOptions(topic, options) {
|
|
|
2811
2969
|
"auth delete": { minimum: 1, maximum: 1 },
|
|
2812
2970
|
"auth activate": { minimum: 1, maximum: 2 },
|
|
2813
2971
|
"auth deactivate": { minimum: 0, maximum: 1 },
|
|
2972
|
+
"agents show": { minimum: 1, maximum: 1 },
|
|
2973
|
+
"agents suspend": { minimum: 1, maximum: 1 },
|
|
2974
|
+
"agents revoke": { minimum: 1, maximum: 1 },
|
|
2975
|
+
"agents resume": { minimum: 1, maximum: 1 },
|
|
2976
|
+
"agents cancel": { minimum: 1, maximum: 1 },
|
|
2977
|
+
"agents complete": { minimum: 1, maximum: 1 },
|
|
2814
2978
|
};
|
|
2815
2979
|
const limit = positionalLimits[topic] ?? { minimum: 0, maximum: 0 };
|
|
2816
2980
|
if (options.positionals.length < limit.minimum) {
|
|
@@ -2899,6 +3063,26 @@ function writeOutput(value, options, runtime) {
|
|
|
2899
3063
|
runtime.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
2900
3064
|
}
|
|
2901
3065
|
function helpText(topic) {
|
|
3066
|
+
if (topic === "agents" || topic?.startsWith("agents "))
|
|
3067
|
+
return `Usage: andeo agents list [--profile HUMAN_PROFILE] [--json]
|
|
3068
|
+
andeo agents show AGENT_ID [--profile HUMAN_PROFILE] [--json]
|
|
3069
|
+
andeo agents <suspend|revoke|resume> AGENT_ID --expected-revision N [--profile HUMAN_PROFILE] [--json]
|
|
3070
|
+
andeo agents <cancel|complete> AGENT_ID --run RUN_ID --expected-revision N [--profile HUMAN_PROFILE] [--json]
|
|
3071
|
+
|
|
3072
|
+
Human administrators inspect and manage persistent agents within their CLI project's ceiling.
|
|
3073
|
+
Use authorization_epoch from agents show as --expected-revision. Suspend and revoke commit immediately.
|
|
3074
|
+
Use --grant ID or --installation ID for one descendant; revoke also accepts --bootstrap ID or --run ID.
|
|
3075
|
+
Resume opens the organization's Persistent agents panel for fresh browser review and confirmation;
|
|
3076
|
+
it reports human_confirmation_required and does not resume authority itself. Use --no-open for instructions.
|
|
3077
|
+
Resuming permits new runs only. Lost or compromised credentials require grant replacement in the panel.
|
|
3078
|
+
Agents are separate from human login profiles; agent task credentials cannot manage grants.`;
|
|
3079
|
+
if (topic === "agent" || topic?.startsWith("agent "))
|
|
3080
|
+
return `Usage: andeo agent <status|complete|cancel> [--json]
|
|
3081
|
+
|
|
3082
|
+
Inspect or end this Cloud task's exact admitted run. Complete and cancel remove local task keys only after Andeo confirms the outcome. A completed, cancelled or expired task cannot re-admit with the same state directory.
|
|
3083
|
+
Requires the Cloud bootstrap placeholder, dedicated admission origin, private task state directory, explicit scope and run duration. Human profiles and legacy tokens cannot be combined with Cloud mode.`;
|
|
3084
|
+
if (topic?.startsWith("config autoupgrade "))
|
|
3085
|
+
topic = "config autoupgrade";
|
|
2902
3086
|
if (topic === "flagship")
|
|
2903
3087
|
return `Usage: andeo flagship apps ensure|list --env ENV [--collection NAME] [--project ID] [--json]
|
|
2904
3088
|
andeo flagship apps get|rename|disconnect|delete|reconcile APP_ID --env ENV [--expected-revision N] [--name NAME] [--confirm EXACT_ID]
|
|
@@ -2945,6 +3129,8 @@ Choose background preview routing with preview-service-set --target-project PROJ
|
|
|
2945
3129
|
Only non-secret JSON belongs in workflow params/events. Keep credentials in the connected service's native secrets.
|
|
2946
3130
|
Account workflow entitlement is required for new starts. Run status is authoritative; list results are a timestamped projection.`;
|
|
2947
3131
|
const topics = {
|
|
3132
|
+
upgrade: "Usage: andeo upgrade [--json]\n\nUpgrade a verified global npm, pnpm or Yarn 1 installation to the latest stable CLI. Explicit upgrades can cross compatibility boundaries. CI, temporary npx and project-local installs must use their package manager. The next invocation uses the new CLI.",
|
|
3133
|
+
"config autoupgrade": "Usage: andeo config autoupgrade <on|off|status> [--json]\n\nGlobal CLI upgrades default on and run after successful commands at most once daily. Before 1.0, only patches within the current minor are automatic; afterward, updates stay in the current major. Off retains daily reminders and independent global skill refresh. Help, version, CI and explicit maintenance commands skip automatic maintenance.",
|
|
2948
3134
|
vars: "Usage: andeo vars <list|set|unset> --environment preview|production [options]\n\nManage saved non-secret runtime variables. Changes apply on the next delivery.",
|
|
2949
3135
|
"source reconnect": "Usage: andeo source reconnect <prepare|status|complete|cancel> [options]\n\nMove a legacy managed repository to its tenant namespace by preparing, pushing, and completing.",
|
|
2950
3136
|
"source reconnect prepare": "Usage: andeo source reconnect prepare --repository SRC_ID --idempotency-key KEY [--project PROJECT_ID] [--json]\n\nPause the old source and prepare its tenant destination. Merchant administrator required.",
|
|
@@ -3023,19 +3209,21 @@ Account workflow entitlement is required for new starts. Run status is authorita
|
|
|
3023
3209
|
"preview entries": "Usage: andeo preview entries [--json]\n\nList eligible published entry apps and the selected default for this linked app.",
|
|
3024
3210
|
"preview open": "Usage: andeo preview open --from dly_...|sbl_... [--through PROJECT_ID] [--return-path /account] [--json]\n\nOpen the exact deployed preview without rebuilding or uploading. Private apps open through their published connections. Opening again renews access.",
|
|
3025
3211
|
"delivery retry": "Usage: andeo delivery retry --delivery dly_... [--reauthorize] [--json]\n\nRetry one requested or failed developer preview idempotently. --reauthorize explicitly authorizes a legacy preview for the same owner and linked project using the current CLI login; it cannot replace another login's recorded grant or publish production. Normal credential refresh needs no flag. A dwf_ production workflow is resumed by a merchant administrator from Activity.",
|
|
3026
|
-
skill: "Usage: andeo skill <print|install> [options]",
|
|
3212
|
+
skill: "Usage: andeo skill <print|install|status|sync> [options]",
|
|
3213
|
+
"skill status": "Usage: andeo skill status [--agent codex|claude] [--directory PATH] [--json]\n\nCheck the installed guide against the compatible public revision. Reports local_changes, unmanaged or not_installed without overwriting files. Offline and incompatible checks fail explicitly.",
|
|
3214
|
+
"skill sync": "Usage: andeo skill sync [--agent codex|claude] [--directory PATH] [--json]\n\nRefresh an existing, unchanged Andeo-managed guide. Preserve local edits and unrelated files. Outages and incompatible bundles retain installed guidance. After status=updated, reread SKILL.md and relevant references in active chats. Use skill install for a missing guide.",
|
|
3027
3215
|
"skill print": "Usage: andeo skill print [--json]",
|
|
3028
3216
|
"skill install": "Usage: andeo skill install [--agent codex|claude] [--directory PATH] [--force] [--bundled] [--json]\n\nFetch the latest compatible public Andeo guide without login. A fresh install falls back to the bundled guide during an outage; existing installations are preserved. --bundled explicitly selects this CLI's offline guide. --force replaces owned files, preserving unrelated files. Installs globally: ~/.agents/skills for Codex or ~/.claude/skills for Claude. --directory overrides the location.\n\nExamples:\n andeo skill install --agent codex --json\n andeo skill install --agent codex --force --json\n andeo skill install --bundled --directory ./skills --json",
|
|
3029
3217
|
skills: "Usage: andeo skills <list|get|file> [options]\n\nAuthenticated, session-only access to Andeo practice skills. Content is not installed into the repository.\n\nExamples:\n andeo skills list --json\n andeo skills get recharge-client\n andeo skills file recharge-customer-operations --path references/subscriptions.md",
|
|
3030
3218
|
"skills list": "Usage: andeo skills list [--profile NAME] [--api-url URL] [--json]\n\nList the practice skills available to the authenticated Andeo user.",
|
|
3031
3219
|
"skills get": "Usage: andeo skills get SKILL_ID [--profile NAME] [--api-url URL] [--json]\n\nRead one skill entrypoint for the current invocation.\n\nExample:\n andeo skills get guardrailed-design-system",
|
|
3032
3220
|
"skills file": "Usage: andeo skills file SKILL_ID --path PATH [--profile NAME] [--api-url URL] [--json]\n\nRead one catalog-listed supporting file for the current invocation.\n\nExample:\n andeo skills file recharge-customer-operations --path references/subscriptions.md",
|
|
3033
|
-
config: "Usage: andeo config example [--json]",
|
|
3221
|
+
config: "Usage: andeo config example [--json]\n andeo config autoupgrade <on|off|status> [--json]",
|
|
3034
3222
|
"config example": "Usage: andeo config example [--json]\n\nPrint the committed tender-accounts.json contract.",
|
|
3035
3223
|
};
|
|
3036
3224
|
if (topic && topics[topic])
|
|
3037
3225
|
return topics[topic];
|
|
3038
|
-
return `Andeo merchant developer CLI ${CLI_VERSION}\n\nCommands:\n docs Read version-matched offline guides\n capabilities Inspect scoped support, enablement and permissions\n init Create a new Shopify account starter\n auth login Create or refresh the default login\n auth create Create or re-authorize a named login\n auth activate Bind a named login to a directory tree\n auth deactivate Remove an exact directory binding\n auth status Validate one login or scoped credential\n auth list List local login profiles and bindings\n auth delete Revoke one named login\n auth logout Revoke the default or every login\n link Link this checkout to one permitted project\n doctor Check developer readiness\n dev Run the project-owned local dev command\n check Run project validation\n build Package and verify a portable artifact\n upload Retain a build for administrator publication\n preview Build and deploy an exact preview\n preview entries List eligible connected entry apps\n preview open Reopen an exact deployed preview\n source status Inspect Andeo-managed Git and recent builds\n source branches List exact managed branches and default policy\n source compare Compare two managed branches\n source connect Connect this linked project to managed Git\n source reconnect Prepare, inspect, complete or cancel a tenant namespace cutover\n source pull Clone or fast-forward the exact connected repository\n source token Issue a short-lived repository credential\n source push Push the exact commit without persisting a token\n source promote Fast-forward the default after successful preview checks\n source history Run guarded, archived source-history migration\n email Create and reuse merchant email identities\n workflows Inspect and control app-defined background runs\n vars Manage saved per-environment runtime variables\n source policy Inspect or change automatic publication\n tail Stream logs for one exact linked project\n delivery status Read preview workflow state\n delivery retry Retry a failed preview workflow\n skill install Install the Andeo agent skill\n skills list Discover authenticated practice skills\n skills get Read an authenticated practice skill\n skills file Read one authenticated skill resource\n config example Print the project configuration contract\n\nThere is intentionally no production publish command.\nRun andeo <command> --help for examples.`;
|
|
3226
|
+
return `Andeo merchant developer CLI ${CLI_VERSION}\n\nCommands:\n docs Read version-matched offline guides\n capabilities Inspect scoped support, enablement and permissions\n init Create a new Shopify account starter\n auth login Create or refresh the default login\n auth create Create or re-authorize a named login\n auth activate Bind a named login to a directory tree\n auth deactivate Remove an exact directory binding\n auth status Validate one login or scoped credential\n auth list List local login profiles and bindings\n auth delete Revoke one named login\n auth logout Revoke the default or every login\n agents Human administration of persistent agents\n agent Inspect or end this Cloud task's run\n link Link this checkout to one permitted project\n doctor Check developer readiness\n dev Run the project-owned local dev command\n check Run project validation\n build Package and verify a portable artifact\n upload Retain a build for administrator publication\n preview Build and deploy an exact preview\n preview entries List eligible connected entry apps\n preview open Reopen an exact deployed preview\n source status Inspect Andeo-managed Git and recent builds\n source branches List exact managed branches and default policy\n source compare Compare two managed branches\n source connect Connect this linked project to managed Git\n source reconnect Prepare, inspect, complete or cancel a tenant namespace cutover\n source pull Clone or fast-forward the exact connected repository\n source token Issue a short-lived repository credential\n source push Push the exact commit without persisting a token\n source promote Fast-forward the default after successful preview checks\n source history Run guarded, archived source-history migration\n email Create and reuse merchant email identities\n workflows Inspect and control app-defined background runs\n vars Manage saved per-environment runtime variables\n source policy Inspect or change automatic publication\n tail Stream logs for one exact linked project\n delivery status Read preview workflow state\n delivery retry Retry a failed preview workflow\n upgrade Upgrade a verified global CLI installation\n config autoupgrade Configure automatic CLI upgrades\n skill install Install the Andeo agent skill\n skill status Inspect installed guidance freshness\n skill sync Refresh unchanged managed guidance\n skills list Discover authenticated practice skills\n skills get Read an authenticated practice skill\n skills file Read one authenticated skill resource\n config example Print the project configuration contract\n\nThere is intentionally no production publish command.\nRun andeo <command> --help for examples.`;
|
|
3039
3227
|
}
|
|
3040
3228
|
function normalizeError(error) {
|
|
3041
3229
|
if (error instanceof FlagCompatibilityError)
|