@withandeo/cli 0.19.14 → 0.19.16
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 +10 -4
- package/dist/api.d.ts +13 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +13 -0
- package/dist/api.js.map +1 -1
- package/dist/cloud-agent.d.ts.map +1 -1
- package/dist/cloud-agent.js +13 -13
- package/dist/cloud-agent.js.map +1 -1
- package/dist/contracts.d.ts +14 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/files.js +1 -1
- package/dist/files.js.map +1 -1
- package/dist/main.js +89 -9
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
- package/skill/publication.json +1 -1
- package/skill/tender-accounts/SKILL.md +29 -0
- package/skill/tender-accounts/references/authentication.md +2 -0
- package/skill/tender-accounts/references/delivery.md +19 -0
- package/skill/tender-accounts/references/managed-source.md +13 -1
package/dist/main.js
CHANGED
|
@@ -227,8 +227,10 @@ async function execute(topic, options, cwd, runtime) {
|
|
|
227
227
|
return { value: await linkProject(cwd, options, runtime) };
|
|
228
228
|
case "doctor":
|
|
229
229
|
return await doctor(cwd, options, runtime);
|
|
230
|
-
case "preview":
|
|
231
|
-
|
|
230
|
+
case "preview": {
|
|
231
|
+
const value = await preview(cwd, options, runtime);
|
|
232
|
+
return { value, exitCode: value.status === "timeout" ? 3 : 0 };
|
|
233
|
+
}
|
|
232
234
|
case "preview entries":
|
|
233
235
|
return { value: await previewEntries(cwd, options, runtime) };
|
|
234
236
|
case "preview open":
|
|
@@ -1468,6 +1470,12 @@ async function sourceMerge(cwd, options, runtime) {
|
|
|
1468
1470
|
throw new CliError("argument_invalid", "Administrator authorization flags require --validation-mode administrator_authorized.");
|
|
1469
1471
|
}
|
|
1470
1472
|
const access = await sourceReadAccess(cwd, options, runtime);
|
|
1473
|
+
if (access.repository.assignedFeatureBranch) {
|
|
1474
|
+
if (headBranch && headBranch !== access.repository.assignedFeatureBranch)
|
|
1475
|
+
throw new CliError("agent_branch_scope_mismatch", "This run may merge only its assigned feature branch.");
|
|
1476
|
+
if (validationMode === "administrator_authorized")
|
|
1477
|
+
throw new CliError("agent_merge_mode_invalid", "Persistent agents use the native preview merge workflow under source.merge.");
|
|
1478
|
+
}
|
|
1471
1479
|
const result = await access.client.sourceMerge(access.repository.id, { headBranch, title, changeId, idempotencyKey,
|
|
1472
1480
|
expectedHead, expectedSnapshot: expectedSnapshot ? Number(expectedSnapshot) : undefined, dryRun,
|
|
1473
1481
|
expectedBase, expectedPolicyRevision: policyRevision === undefined ? undefined : Number(policyRevision),
|
|
@@ -1487,7 +1495,7 @@ async function sourceMerge(cwd, options, runtime) {
|
|
|
1487
1495
|
if (merge.stage === "awaiting_approval")
|
|
1488
1496
|
return { ...result, merge };
|
|
1489
1497
|
await runtime.sleep(Math.min(5000, timeout * 1000 - poll * 5000));
|
|
1490
|
-
const current = merge.changeId ? await access.client.sourceChangeRequest(merge.changeId) : await access.client.sourceMergeStatus(merge.id);
|
|
1498
|
+
const current = merge.changeId && !access.repository.assignedFeatureBranch ? await access.client.sourceChangeRequest(merge.changeId) : await access.client.sourceMergeStatus(merge.id);
|
|
1491
1499
|
if ("changeRequest" in current)
|
|
1492
1500
|
requireLinkedSourceChange(access.projectId, current.changeRequest.projectId);
|
|
1493
1501
|
if (!current.merge || current.merge.id !== merge.id)
|
|
@@ -2120,6 +2128,11 @@ async function preview(cwd, options, runtime, uploadOnly = false) {
|
|
|
2120
2128
|
if (!offline && !context.link)
|
|
2121
2129
|
throw new CliError("project_not_linked", "This app is not linked to an Andeo project.", "andeo link --json");
|
|
2122
2130
|
const authorized = offline ? null : await authenticatedClient(cwd, context.link, options, runtime);
|
|
2131
|
+
if (authorized?.persistent) {
|
|
2132
|
+
if (uploadOnly)
|
|
2133
|
+
throw new CliError("agent_artifact_upload_unavailable", "Persistent agents create previews from their assigned managed source branch.", "andeo preview --json");
|
|
2134
|
+
return persistentSourcePreview(context, options, runtime, authorized.client);
|
|
2135
|
+
}
|
|
2123
2136
|
if (authorized && context.link && !uploadOnly) {
|
|
2124
2137
|
assertPreviewEntry(await authorized.client.previewEntries(context.link.projectId), options.flags.get("through"));
|
|
2125
2138
|
}
|
|
@@ -2158,6 +2171,7 @@ async function preview(cwd, options, runtime, uploadOnly = false) {
|
|
|
2158
2171
|
let delivery = await client.createDelivery(context.link.projectId, {
|
|
2159
2172
|
submissionId: finalized.id,
|
|
2160
2173
|
idempotencyKey: options.flags.get("delivery-key")
|
|
2174
|
+
?? options.flags.get("idempotency-key")
|
|
2161
2175
|
?? generatedDeliveryKey(context.link.projectId, finalized.id),
|
|
2162
2176
|
});
|
|
2163
2177
|
if (options.booleans.has("no-wait")) {
|
|
@@ -2190,6 +2204,55 @@ async function preview(cwd, options, runtime, uploadOnly = false) {
|
|
|
2190
2204
|
productionChanged: false,
|
|
2191
2205
|
};
|
|
2192
2206
|
}
|
|
2207
|
+
async function persistentSourcePreview(context, options, runtime, client) {
|
|
2208
|
+
const principal = await client.whoami();
|
|
2209
|
+
if (principal.actorKind !== "persistent_agent")
|
|
2210
|
+
throw new CliError("agent_authentication_failed", "The Cloud credential did not authenticate a persistent agent.");
|
|
2211
|
+
const projectId = context.link.projectId;
|
|
2212
|
+
if (!principal.projects.some(project => project.projectId === projectId))
|
|
2213
|
+
throw new CliError("project_not_found", "This run does not have access to the linked project.");
|
|
2214
|
+
if (options.flags.has("through"))
|
|
2215
|
+
throw new CliError("agent_preview_entry_unsupported", "Persistent previews use their isolated entry; --through is not supported.");
|
|
2216
|
+
const repository = (await client.sourceRepository(projectId)).repository;
|
|
2217
|
+
if (!repository?.assignedFeatureBranch || repository.status !== "active")
|
|
2218
|
+
throw new CliError("source_repository_missing", "An active run-assigned managed repository is required.");
|
|
2219
|
+
const source = await sourceState(context.root, runtime);
|
|
2220
|
+
if (source.dirty)
|
|
2221
|
+
throw new CliError("preview_committed_source_required", "Commit and push the exact source before requesting an isolated preview.", "andeo source push --json");
|
|
2222
|
+
const timeout = Number(options.flags.get("timeout-seconds") ?? "1200");
|
|
2223
|
+
if (!Number.isSafeInteger(timeout) || timeout < 1 || timeout > 3600)
|
|
2224
|
+
throw new CliError("argument_invalid", "--timeout-seconds must be between 1 and 3600.");
|
|
2225
|
+
const idempotencyKey = options.flags.get("idempotency-key") ?? options.flags.get("delivery-key") ?? `source-preview-${createHash("sha256").update(JSON.stringify({ repositoryId: repository.id, headBranch: repository.assignedFeatureBranch, headSha: source.revision })).digest("hex")}`;
|
|
2226
|
+
if (!/^[A-Za-z0-9:_-]{8,128}$/u.test(idempotencyKey))
|
|
2227
|
+
throw new CliError("argument_invalid", "--idempotency-key must contain 8–128 letters, numbers, colons, underscores or hyphens.");
|
|
2228
|
+
const result = await client.createSourcePreview(repository.id, { headBranch: repository.assignedFeatureBranch, expectedHeadSha: source.revision, idempotencyKey });
|
|
2229
|
+
let preview = result.preview;
|
|
2230
|
+
const assertExact = () => {
|
|
2231
|
+
if (!/^sbl_[A-Za-z0-9_-]+$/u.test(preview.id) || preview.buildId !== preview.id || preview.headBranch !== repository.assignedFeatureBranch || preview.headSha !== source.revision)
|
|
2232
|
+
throw new CliError("source_preview_changed", "The returned preview does not match the exact assigned source.");
|
|
2233
|
+
};
|
|
2234
|
+
assertExact();
|
|
2235
|
+
const summary = () => ({ created: result.created, preview, projectId, sourceRevision: source.revision, releaseId: preview.releaseId, compositionId: preview.previewCompositionId,
|
|
2236
|
+
from: preview.id, productionChanged: false, nextCommand: `andeo preview open --from ${preview.id} --json` });
|
|
2237
|
+
if (options.booleans.has("no-wait"))
|
|
2238
|
+
return { ...summary(), status: preview.status };
|
|
2239
|
+
for (let poll = 0; poll < Math.ceil(timeout / 5) && !["succeeded", "preview_ready", "failed"].includes(preview.status); poll++) {
|
|
2240
|
+
await runtime.sleep(Math.min(5000, timeout * 1000 - poll * 5000));
|
|
2241
|
+
const previousId = preview.id;
|
|
2242
|
+
preview = (await client.sourcePreview(previousId)).preview;
|
|
2243
|
+
assertExact();
|
|
2244
|
+
if (preview.id !== previousId)
|
|
2245
|
+
throw new CliError("source_preview_changed", "The preview operation changed while waiting.");
|
|
2246
|
+
}
|
|
2247
|
+
if (preview.status === "failed")
|
|
2248
|
+
throw new CliError(preview.error?.code ?? "source_preview_failed", preview.error?.message ?? "The isolated preview build failed.", undefined, { from: preview.id });
|
|
2249
|
+
if (!["succeeded", "preview_ready"].includes(preview.status))
|
|
2250
|
+
return { ...summary(), status: "timeout", waitTimedOut: true };
|
|
2251
|
+
const session = await client.openSourcePreview(preview.id, options.flags.get("return-path") ?? context.config.returnPath);
|
|
2252
|
+
if (session.projectId !== projectId || session.from !== preview.id || session.sourceRevision !== source.revision)
|
|
2253
|
+
throw new CliError("source_preview_changed", "The opened preview does not match the exact linked source.");
|
|
2254
|
+
return { ...summary(), status: "succeeded", previewUrl: session.url, previewGrantId: session.grantId, previewExpiresAt: session.expiresAt, sourceVerification: session.sourceVerification };
|
|
2255
|
+
}
|
|
2193
2256
|
async function deliveryStatus(cwd, options, runtime) {
|
|
2194
2257
|
const context = await loadProjectContext(cwd);
|
|
2195
2258
|
const { client } = await authenticatedClient(cwd, context.link, options, runtime);
|
|
@@ -2223,7 +2286,23 @@ async function previewOpen(cwd, options, runtime) {
|
|
|
2223
2286
|
const from = options.flags.get("from");
|
|
2224
2287
|
if (!from || !/^(dly|sbl)_[A-Za-z0-9_-]+$/.test(from))
|
|
2225
2288
|
throw new CliError("preview_source_invalid", "Use --from with an exact dly_ delivery or sbl_ source-build ID.");
|
|
2226
|
-
const
|
|
2289
|
+
const access = await authenticatedClient(cwd, context.link, options, runtime);
|
|
2290
|
+
const { client } = access;
|
|
2291
|
+
if (access.persistent) {
|
|
2292
|
+
const principal = await client.whoami();
|
|
2293
|
+
if (principal.actorKind !== "persistent_agent")
|
|
2294
|
+
throw new CliError("agent_authentication_failed", "The Cloud credential did not authenticate a persistent agent.");
|
|
2295
|
+
if (!principal.projects.some(project => project.projectId === context.link.projectId))
|
|
2296
|
+
throw new CliError("project_not_found", "This run does not have access to the linked project.");
|
|
2297
|
+
if (!/^sbl_[A-Za-z0-9_-]+$/u.test(from))
|
|
2298
|
+
throw new CliError("preview_source_invalid", "Persistent preview opening requires an exact sbl_ source-build ID.");
|
|
2299
|
+
if (options.flags.has("through"))
|
|
2300
|
+
throw new CliError("agent_preview_entry_unsupported", "Persistent previews use their isolated entry; --through is not supported.");
|
|
2301
|
+
const session = await client.openSourcePreview(from, options.flags.get("return-path") ?? context.config.returnPath);
|
|
2302
|
+
if (session.projectId !== context.link.projectId || session.from !== from)
|
|
2303
|
+
throw new CliError("source_preview_changed", "The opened preview belongs to another linked source.");
|
|
2304
|
+
return session;
|
|
2305
|
+
}
|
|
2227
2306
|
return client.openPreview(context.link.projectId, { from, returnPath: options.flags.get("return-path") ?? context.config.returnPath,
|
|
2228
2307
|
entryProjectId: options.flags.get("through") });
|
|
2229
2308
|
}
|
|
@@ -2238,7 +2317,7 @@ async function authenticatedClient(cwd, link, options, runtime) {
|
|
|
2238
2317
|
let apiUrl = selectedApiUrl(options, link, runtime.environment, runtime.defaultApiUrl);
|
|
2239
2318
|
const cloud = await cloudAgentClient(runtime.environment, cwd, runtime.fetcher, { tokenStdin: options.booleans.has("token-stdin"), profile: options.flags.has("profile"), apiUrl });
|
|
2240
2319
|
if (cloud)
|
|
2241
|
-
return { client: new AgentApiClient(apiUrl, "task-run", cloud.fetcher), apiUrl, source: "cloud-agent", accessTokenExpiresAt: cloud.accessTokenExpiresAt };
|
|
2320
|
+
return { client: new AgentApiClient(apiUrl, "task-run", cloud.fetcher), apiUrl, source: "cloud-agent", accessTokenExpiresAt: cloud.accessTokenExpiresAt, persistent: true };
|
|
2242
2321
|
const networkSecret = readNetworkSecret(runtime.environment, apiUrl);
|
|
2243
2322
|
if (networkSecret !== undefined) {
|
|
2244
2323
|
if (options.booleans.has("token-stdin") || options.flags.has("profile")) {
|
|
@@ -2254,7 +2333,7 @@ async function authenticatedClient(cwd, link, options, runtime) {
|
|
|
2254
2333
|
: runtime.environment.TENDER_ACCOUNTS_TOKEN
|
|
2255
2334
|
? "environment"
|
|
2256
2335
|
: "dev-vars";
|
|
2257
|
-
return { client: new AgentApiClient(apiUrl, explicit, runtime.fetcher), apiUrl, source };
|
|
2336
|
+
return { client: new AgentApiClient(apiUrl, explicit, runtime.fetcher), apiUrl, source, persistent: /^ta_run_agc_[a-f0-9]{24}\.[A-Za-z0-9_-]{43}$/u.test(explicit) };
|
|
2258
2337
|
}
|
|
2259
2338
|
const profile = await selectAuthProfile(cwd, options, runtime);
|
|
2260
2339
|
if (!options.flags.has("api-url") && !runtime.environment.TENDER_ACCOUNTS_API_URL && !link) {
|
|
@@ -2872,6 +2951,7 @@ function assertAllowedOptions(topic, options) {
|
|
|
2872
2951
|
"no-wait",
|
|
2873
2952
|
"submission-key",
|
|
2874
2953
|
"delivery-key",
|
|
2954
|
+
"idempotency-key",
|
|
2875
2955
|
"timeout-seconds",
|
|
2876
2956
|
"return-path",
|
|
2877
2957
|
"through",
|
|
@@ -3156,7 +3236,7 @@ Account workflow entitlement is required for new starts. Run status is authorita
|
|
|
3156
3236
|
check: "Usage: andeo check [--json]\n\nRun the app's declared validation command.",
|
|
3157
3237
|
build: "Usage: andeo build [--json]\n\nRun validation and packaging, then verify the complete portable artifact.",
|
|
3158
3238
|
upload: "Usage: andeo upload [--dry-run] [--submission-key KEY] [--json]\n\nValidate, package and retain an exact artifact for administrator review. Does not create a preview or publish production.",
|
|
3159
|
-
preview: "Usage: andeo preview [--through PROJECT_ID] [--return-path /account] [--dry-run] [--no-wait] [--json]\n\nBuild an exact app preview.
|
|
3239
|
+
preview: "Usage: andeo preview [--through PROJECT_ID] [--return-path /account] [--dry-run] [--no-wait] [--idempotency-key KEY] [--json]\n\nBuild an exact app preview. Persistent Cloud agents with preview.build/open request an isolated build of their assigned pushed branch; no local artifact upload is used. Unsupported bindings fail closed. Other credentials use local packaging and published entry selection. Use preview open --from sbl_... (persistent) or dly_... to reopen without rebuilding.",
|
|
3160
3240
|
production: "Usage: andeo production <releases|rollback> [options]",
|
|
3161
3241
|
"production releases": "Usage: andeo production releases [--project PROJECT_ID] [--json]\n\nList immutable production releases and rollback eligibility.",
|
|
3162
3242
|
"production rollback": "Usage: andeo production rollback [--project PROJECT_ID] --release RELEASE_ID --expected-release CURRENT_ID --expected-revision N --idempotency-key KEY [--json]\n\nMerchant administrators restore a previously successful app release without building or uploading code.",
|
|
@@ -3181,7 +3261,7 @@ Account workflow entitlement is required for new starts. Run status is authorita
|
|
|
3181
3261
|
"source change": "Usage: andeo source change <create|list|show|close|resume|cancel|take-over> [options]\n\nCreate and inspect exact preview-backed changes, or recover an accepted merge intent.",
|
|
3182
3262
|
"source change cancel": "Usage: andeo source change cancel --change scr_... [--json]\n\nWithdraw new work for an accepted intent. Already-issued effects require readback; source may already be merged.",
|
|
3183
3263
|
"source change take-over": "Usage: andeo source change take-over --change scr_... --idempotency-key KEY [--json]\n\nAdministrator only. Records a new bounded authorization for the same exact inputs; then use source change resume.",
|
|
3184
|
-
"source merge": "Usage: andeo source merge --head BRANCH --title TITLE --idempotency-key KEY [--dry-run] [--wait] [--timeout-seconds N] [--json]\n andeo source merge --change scr_... --idempotency-key KEY [--wait] [--json]\n andeo source merge --head BRANCH --title TITLE --validation-mode administrator_authorized --expected-base SHA --expected-head SHA --expected-policy-revision N --authorize --idempotency-key KEY [--continuation source_only] [--note TEXT] [--wait] [--json]\n andeo source merge <status|resume|cancel|take-over> --merge smi_... [options]\n\nRequest exact fast-forward landing.
|
|
3264
|
+
"source merge": "Usage: andeo source merge --head BRANCH --title TITLE --idempotency-key KEY [--dry-run] [--wait] [--timeout-seconds N] [--json]\n andeo source merge --change scr_... --idempotency-key KEY [--wait] [--json]\n andeo source merge --head BRANCH --title TITLE --validation-mode administrator_authorized --expected-base SHA --expected-head SHA --expected-policy-revision N --authorize --idempotency-key KEY [--continuation source_only] [--note TEXT] [--wait] [--json]\n andeo source merge <status|resume|cancel|take-over> --merge smi_... [options]\n\nRequest exact fast-forward landing. Persistent agents require source.merge for the assigned branch and review.prepare for implicit review creation. The grant delegates source approval without per-change administrator approval; a successful exact isolated preview is required. Landing may deploy under the saved publication policy. Human/legacy native review keeps its existing approval contract. Administrator-authorized source-only mode requires saved policy and a current administrator; Andeo does not independently verify checks. Inspect refs and blockers with --dry-run before authorizing. Waiting is bounded and never cancels hosted work.\n\nExamples:\n andeo source merge --head feature/copy --title \"Update copy\" --idempotency-key account-copy-v1 --dry-run --json\n andeo source merge --head feature/copy --title \"Update copy\" --idempotency-key account-copy-v1 --wait --json",
|
|
3185
3265
|
"source merge status": "Usage: andeo source merge status --merge smi_... [--json]\n\nRead the retained source, validation, authorization, continuation and cleanup outcomes. Source-only merged is successful completion.",
|
|
3186
3266
|
"source merge resume": "Usage: andeo source merge resume --merge smi_... [--json]\n\nResume the same exact eligible intent after revalidating authority. Never substitutes a new source snapshot.",
|
|
3187
3267
|
"source merge cancel": "Usage: andeo source merge cancel --merge smi_... [--json]\n\nWithdraw new work. Already-issued effects require readback; cancellation cannot undo a merge.",
|
|
@@ -3207,7 +3287,7 @@ Account workflow entitlement is required for new starts. Run status is authorita
|
|
|
3207
3287
|
delivery: "Usage: andeo delivery <status|retry> --delivery dly_... [--json]\n\nThese commands accept the developer-delivery ID returned by preview. A dwf_ ID is a production workflow managed by merchant administrators in Activity.",
|
|
3208
3288
|
"delivery status": "Usage: andeo delivery status --delivery dly_... [--json]\n\nRead one developer preview delivery. Do not pass a dwf_ production workflow ID.",
|
|
3209
3289
|
"preview entries": "Usage: andeo preview entries [--json]\n\nList eligible published entry apps and the selected default for this linked app.",
|
|
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.
|
|
3290
|
+
"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. Persistent agents require preview.open and an exact permitted sbl_ source preview; --through is unavailable and access remains bounded by the run/derived grant. Other private apps open through their published connections.",
|
|
3211
3291
|
"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.",
|
|
3212
3292
|
skill: "Usage: andeo skill <print|install|status|sync> [options]",
|
|
3213
3293
|
"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.",
|