akm-cli 0.9.10 → 0.9.12
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 +226 -0
- package/STABILITY.md +28 -15
- package/dist/assets/hints/cli-hints-full.md +1 -1
- package/dist/assets/improve-strategies/consolidate.json +1 -1
- package/dist/assets/improve-strategies/default.json +1 -1
- package/dist/assets/improve-strategies/thorough.json +1 -2
- package/dist/cli/shared.js +16 -4
- package/dist/cli.js +15 -13
- package/dist/commands/agent/agent-dispatch.js +8 -0
- package/dist/commands/command/execution-source-loader.js +25 -22
- package/dist/commands/command/portable-template.js +4 -26
- package/dist/commands/config-cli.js +10 -4
- package/dist/commands/env/env-binding.js +10 -3
- package/dist/commands/env/env-cli.js +7 -0
- package/dist/commands/env/secret-cli.js +15 -4
- package/dist/commands/health/checks.js +209 -78
- package/dist/commands/health/improve-metrics.js +12 -0
- package/dist/commands/health.js +16 -4
- package/dist/commands/improve/distill/quality-gate.js +15 -7
- package/dist/commands/improve/distill.js +28 -12
- package/dist/commands/improve/eval-cases.js +9 -2
- package/dist/commands/improve/execution.js +1 -2
- package/dist/commands/improve/extract.js +82 -56
- package/dist/commands/improve/improve-strategies.js +26 -8
- package/dist/commands/improve/improve.js +32 -4
- package/dist/commands/improve/loop-stages.js +13 -3
- package/dist/commands/improve/preparation.js +9 -6
- package/dist/commands/improve/reflect.js +61 -77
- package/dist/commands/lint/index.js +3 -1
- package/dist/commands/migrate-cli.js +6 -4
- package/dist/commands/proposal/drain-policies.js +22 -2
- package/dist/commands/proposal/repository.js +4 -4
- package/dist/commands/proposal/validators/proposal-quality-validators.js +23 -2
- package/dist/commands/proposal/validators/proposals.js +10 -19
- package/dist/commands/read/show.js +42 -31
- package/dist/commands/registry-cli.js +4 -2
- package/dist/commands/sources/init.js +4 -8
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-clone.js +5 -7
- package/dist/commands/sources/sources-cli.js +3 -5
- package/dist/commands/tasks/tasks-cli.js +36 -12
- package/dist/commands/tasks/tasks.js +38 -35
- package/dist/commands/tasks/validate.js +186 -0
- package/dist/commands/url-checker.js +75 -16
- package/dist/commands/workflow-cli.js +17 -15
- package/dist/core/activation-policy.js +31 -3
- package/dist/core/adapter/execution-source.js +39 -11
- package/dist/core/asset/stash-meta.js +7 -41
- package/dist/core/bundle-id.js +7 -1
- package/dist/core/common.js +8 -17
- package/dist/core/config/config-schema.js +3 -23
- package/dist/core/config/config-walker.js +50 -4
- package/dist/core/config/config.js +21 -5
- package/dist/core/config/legacy-source-shape-shim.js +79 -0
- package/dist/core/config/schema/embedding.js +2 -2
- package/dist/core/config/schema/engines.js +19 -2
- package/dist/core/config/schema/index-config.js +19 -21
- package/dist/core/config/schema/primitives.js +21 -10
- package/dist/core/config/schema/sources-bundles.js +1 -6
- package/dist/core/errors.js +2 -3
- package/dist/core/improve-result.js +8 -0
- package/dist/core/improve-types.js +17 -0
- package/dist/core/json-schema.js +1 -11
- package/dist/core/maintenance-barrier.js +17 -2
- package/dist/core/paths.js +124 -15
- package/dist/core/state/migrations.js +28 -0
- package/dist/core/state-db.js +28 -1
- package/dist/core/write-source.js +6 -6
- package/dist/indexer/bundle-identity-guard.js +3 -0
- package/dist/indexer/ensure-index.js +5 -0
- package/dist/indexer/indexer.js +11 -3
- package/dist/indexer/lookup/adapter-concept-owner.js +14 -3
- package/dist/indexer/passes/metadata.js +16 -5
- package/dist/indexer/search/search-fields.js +1 -30
- package/dist/indexer/search/search-source.js +3 -2
- package/dist/integrations/agent/engine-resolution.js +107 -4
- package/dist/integrations/agent/execution-lowering.js +15 -2
- package/dist/integrations/agent/model-map.js +16 -10
- package/dist/integrations/agent/prompts.js +13 -6
- package/dist/integrations/agent/runner-dispatch.js +16 -3
- package/dist/integrations/agent/runner.js +2 -0
- package/dist/integrations/lockfile.js +22 -7
- package/dist/llm/client.js +16 -0
- package/dist/llm/index-passes.js +3 -2
- package/dist/output/shapes/passthrough.js +10 -3
- package/dist/output/shapes.js +5 -3
- package/dist/output/text/workflow-format.js +8 -1
- package/dist/scripts/akm-migrate-node.js +2028 -1613
- package/dist/scripts/akm-migrate.js +2027 -1612
- package/dist/setup/setup.js +14 -21
- package/dist/sources/include.js +150 -20
- package/dist/sources/providers/git-install.js +14 -12
- package/dist/sources/providers/git-provider.js +3 -3
- package/dist/sources/snapshot-fetchers/website-ingest.js +54 -16
- package/dist/sources/website-url.js +12 -4
- package/dist/storage/engines/sqlite-migrations.js +40 -10
- package/dist/storage/like-pattern.js +7 -0
- package/dist/storage/repositories/extract-sessions-repository.js +23 -0
- package/dist/storage/repositories/index-connection.js +27 -10
- package/dist/storage/repositories/index-entry-schema.js +19 -2
- package/dist/storage/repositories/index-schema.js +30 -9
- package/dist/storage/repositories/proposals-repository.js +2 -1
- package/dist/storage/repositories/task-history-repository.js +14 -7
- package/dist/storage/repositories/workflow-runs-repository.js +15 -1
- package/dist/storage/sqlite-read-snapshot.js +11 -9
- package/dist/tasks/backends/cron.js +34 -5
- package/dist/tasks/backends/launchd.js +23 -26
- package/dist/tasks/backends/schtasks.js +50 -3
- package/dist/tasks/frozen-script.js +2 -0
- package/dist/tasks/prepare/prepare.js +2 -7
- package/dist/tasks/prepare/script-capture.js +38 -6
- package/dist/tasks/schedule.js +154 -13
- package/dist/tasks/scheduler-sync.js +51 -25
- package/dist/tasks/source/task-source-v3-frozen.js +0 -1
- package/dist/tasks/source/task-source-v4.js +0 -1
- package/dist/workflows/exec/child-workflow.js +2 -3
- package/dist/workflows/exec/dispatch-redaction.js +21 -7
- package/dist/workflows/exec/exec-unit.js +3 -4
- package/dist/workflows/exec/run-workflow.js +19 -10
- package/dist/workflows/exec/step-work.js +35 -56
- package/dist/workflows/freeze/resolve-steps.js +19 -11
- package/dist/workflows/freeze/source-freeze.js +7 -0
- package/dist/workflows/freeze/targets/child-workflow.js +12 -18
- package/dist/workflows/freeze/targets/command.js +14 -2
- package/dist/workflows/ir/environment-v4.js +4 -2
- package/dist/workflows/ir/freeze-v4.js +2 -5
- package/dist/workflows/ir/plan-hash.js +0 -3
- package/dist/workflows/ir/schema-v4.js +14 -9
- package/dist/workflows/ir/schema.js +1 -3
- package/dist/workflows/resource-limits.js +35 -48
- package/dist/workflows/runtime/plan-classifier.js +89 -41
- package/dist/workflows/runtime/run-outputs.js +1 -21
- package/dist/workflows/runtime/runs.js +76 -150
- package/dist/workflows/source-files.js +28 -54
- package/dist/workflows/source-ir/program.js +2 -2
- package/dist/workflows/source-ir/semantics.js +5 -23
- package/docs/integration/bundling-akm.md +1 -1
- package/docs/migration/v0.8-to-v0.9.md +32 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +20 -0
- package/docs/reference/cli.md +105 -22
- package/docs/reference/configuration.md +12 -2
- package/docs/reference/data-and-telemetry.md +1 -1
- package/docs/reference/tasks.md +8 -0
- package/package.json +1 -1
- package/schemas/akm-config.json +11 -8
|
@@ -26,14 +26,18 @@ export function validateProposal(proposal) {
|
|
|
26
26
|
* structure and applies {@link repairTruncatedDescription} to a truncated
|
|
27
27
|
* description when one is detected.
|
|
28
28
|
*
|
|
29
|
-
* Repairs performed
|
|
30
|
-
* 1.
|
|
31
|
-
* (e.g. `**description**: …` or `when_to_use: …` in the body).
|
|
32
|
-
* 2. Remove stray body `---` horizontal-rule lines (leaving exactly the two
|
|
33
|
-
* frontmatter fences when the content has a valid frontmatter block).
|
|
34
|
-
* 3. Apply {@link repairTruncatedDescription} to a truncated/hanging
|
|
29
|
+
* Repairs performed:
|
|
30
|
+
* 1. Apply {@link repairTruncatedDescription} to a truncated/hanging
|
|
35
31
|
* `description` field in the frontmatter.
|
|
36
32
|
*
|
|
33
|
+
* It deliberately does NOT delete body lines. Two earlier repairs dropped
|
|
34
|
+
* every body line that restated a frontmatter key and every `---` in a body
|
|
35
|
+
* with frontmatter. Both fired inside fenced code blocks, so any asset
|
|
36
|
+
* documenting frontmatter — a note about akm, Claude Code skills, Jekyll,
|
|
37
|
+
* Hugo — was silently gutted on `proposal accept`, and the repaired bytes
|
|
38
|
+
* were written back over the original in the proposals database. A repair
|
|
39
|
+
* that can destroy content is not a repair.
|
|
40
|
+
*
|
|
37
41
|
* Returns the repaired content string. When no repairs apply the input is
|
|
38
42
|
* returned byte-identical so callers can use strict equality to detect
|
|
39
43
|
* whether a repair actually happened.
|
|
@@ -81,19 +85,6 @@ export function repairProposalContent(content) {
|
|
|
81
85
|
repairedLines.push(line);
|
|
82
86
|
continue;
|
|
83
87
|
}
|
|
84
|
-
// Repair 1: Strip pseudo-frontmatter restatements in the body.
|
|
85
|
-
// Matches lines like `**description**: …` or `when_to_use: …`.
|
|
86
|
-
if (/^\s*(\*\*|__)?\s*(description|when_to_use)\s*(\*\*|__)?\s*:/i.test(line)) {
|
|
87
|
-
// Drop the line — it is a structural defect, not user content.
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
// Repair 2: Remove stray `---` horizontal-rule lines in the body.
|
|
91
|
-
// We keep these only when the content has NO frontmatter (in that case
|
|
92
|
-
// `---` is a legitimate thematic break in plain-body content).
|
|
93
|
-
if (isFence && hasFrontmatter) {
|
|
94
|
-
// Drop: these are extra `---` fences beyond the two frontmatter delimiters.
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
88
|
repairedLines.push(line);
|
|
98
89
|
}
|
|
99
90
|
let repaired = repairedLines.join("\n");
|
|
@@ -31,7 +31,7 @@ import { NotFoundError, rethrowIfDataDirUnreadable, rethrowIfTestIsolationError,
|
|
|
31
31
|
import { appendEvent } from "../../core/events.js";
|
|
32
32
|
import { SCRIPT_EXTENSIONS } from "../../core/recognition-util.js";
|
|
33
33
|
import { presentationFor } from "../../core/type-presentation.js";
|
|
34
|
-
import { warn } from "../../core/warn.js";
|
|
34
|
+
import { warn, warnOnce } from "../../core/warn.js";
|
|
35
35
|
import { hasGraphData } from "../../indexer/db/graph-db.js";
|
|
36
36
|
import { listRelatedPathsForFile } from "../../indexer/graph/graph-boost.js";
|
|
37
37
|
import { extractGraphForSingleFile } from "../../indexer/graph/graph-extraction.js";
|
|
@@ -71,12 +71,11 @@ export async function akmShowUnified(input) {
|
|
|
71
71
|
if (metaRef)
|
|
72
72
|
return showStashMeta(metaRef);
|
|
73
73
|
}
|
|
74
|
-
// Env/secret bodies have no safe fragment surface
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
assertSensitiveFragmentUnsupported(parseBundleRef(ref));
|
|
74
|
+
// Env/secret bodies have no safe fragment surface, and a fragment cannot
|
|
75
|
+
// widen what the env/secret renderers expose: both always omit the body
|
|
76
|
+
// (env — key names only; secret — never rendered), fragment or not. Warn
|
|
77
|
+
// and ignore the fragment rather than refusing the whole show.
|
|
78
|
+
warnSensitiveFragmentUnsupported(parseBundleRef(ref));
|
|
80
79
|
// Auto-index when stale so the index is current before lookup.
|
|
81
80
|
const { primarySource } = resolveReadSources();
|
|
82
81
|
await ensurePrimaryIndexForRead(primarySource);
|
|
@@ -177,7 +176,7 @@ function enforceScopeOrThrow(filePath, ref, scope) {
|
|
|
177
176
|
/** @internal Use akmShowUnified() for all external callers. */
|
|
178
177
|
export async function showLocal(input) {
|
|
179
178
|
const parsed = parseBundleRef(input.ref);
|
|
180
|
-
|
|
179
|
+
warnSensitiveFragmentUnsupported(parsed);
|
|
181
180
|
const assetParts = typeNameFromConceptId(parsed.conceptId);
|
|
182
181
|
const config = loadConfig();
|
|
183
182
|
const allSources = resolveSourceEntries(input.stashDir);
|
|
@@ -194,12 +193,19 @@ export async function showLocal(input) {
|
|
|
194
193
|
`Stash "${parsed.bundle}" is not installed. Run: ${installCmd}`);
|
|
195
194
|
}
|
|
196
195
|
if (!indexedEntry && !resolution.owner) {
|
|
197
|
-
const
|
|
198
|
-
if (
|
|
199
|
-
const displayExtension =
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
196
|
+
const unrecognized = findUnrecognizedScriptSource(assetParts, searchSources);
|
|
197
|
+
if (unrecognized) {
|
|
198
|
+
const displayExtension = unrecognized.extension || "no extension";
|
|
199
|
+
warn(`Script ref "${makeBundleRef(parsed.bundle, parsed.conceptId)}" has extension "${displayExtension}", which is outside the recognized set used for indexing (${[...SCRIPT_EXTENSIONS].join(", ")}); showing it as plain text.`);
|
|
200
|
+
const fileCtx = buildFileContext(unrecognized.sourceRoot, unrecognized.path);
|
|
201
|
+
const renderer = await getRenderer("script-source");
|
|
202
|
+
if (renderer) {
|
|
203
|
+
const match = { type: "script", specificity: 0, renderer: "script-source" };
|
|
204
|
+
const renderCtx = buildRenderContext(fileCtx, match, allSourceDirs);
|
|
205
|
+
const response = renderer.buildShowResponse(renderCtx);
|
|
206
|
+
response.name = assetParts?.name ?? response.name;
|
|
207
|
+
return response;
|
|
208
|
+
}
|
|
203
209
|
}
|
|
204
210
|
}
|
|
205
211
|
if (!indexedEntry || !assetPath) {
|
|
@@ -242,9 +248,11 @@ export async function showLocal(input) {
|
|
|
242
248
|
response = renderer.buildShowResponse(renderCtx);
|
|
243
249
|
if (parsed.fragment !== undefined) {
|
|
244
250
|
if (!match.renderer.endsWith("-md")) {
|
|
245
|
-
|
|
251
|
+
warn(`Fragment "#${parsed.fragment}" was ignored: ${makeBundleRef(parsed.bundle, parsed.conceptId)} is not a Markdown document, so heading fragments do not apply. Showing the whole asset.`);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
applyMarkdownFragment(response, fileCtx.content(), parsed.fragment, presentedName);
|
|
246
255
|
}
|
|
247
|
-
applyMarkdownFragment(response, fileCtx.content(), parsed.fragment, presentedName);
|
|
248
256
|
}
|
|
249
257
|
}
|
|
250
258
|
}
|
|
@@ -318,22 +326,22 @@ export async function showLocal(input) {
|
|
|
318
326
|
}
|
|
319
327
|
return fullResponse;
|
|
320
328
|
}
|
|
321
|
-
/**
|
|
322
|
-
|
|
329
|
+
/**
|
|
330
|
+
* Warn and ignore body fragments for namespaces whose authored bytes are
|
|
331
|
+
* sensitive. `warnOnce`-keyed on the exact ref: `akmShowUnified` calls this
|
|
332
|
+
* before delegating to `showLocal`, which calls it again as its own
|
|
333
|
+
* defense-in-depth for callers that use `showLocal` directly — a single
|
|
334
|
+
* request must not print the same warning twice.
|
|
335
|
+
*/
|
|
336
|
+
function warnSensitiveFragmentUnsupported(ref) {
|
|
323
337
|
if (ref.fragment === undefined)
|
|
324
338
|
return;
|
|
325
339
|
const type = typeNameFromConceptId(ref.conceptId)?.type;
|
|
326
340
|
if (type !== "env" && type !== "secret")
|
|
327
341
|
return;
|
|
328
|
-
|
|
342
|
+
warnOnce(`sensitive-fragment:${makeBundleRef(ref.bundle, ref.conceptId)}#${ref.fragment}`, `Fragment "#${ref.fragment}" was ignored: sensitive ${type} assets do not expose body fragments. Showing ${makeBundleRef(ref.bundle, ref.conceptId)} in full.`);
|
|
329
343
|
}
|
|
330
|
-
|
|
331
|
-
* Return the unsupported extension only when the exact canonical AKM script
|
|
332
|
-
* path exists as a contained regular file. Physical owner arbitration runs
|
|
333
|
-
* first; this is a diagnostic for its miss, never an alternate owner or
|
|
334
|
-
* runnable-file classifier. No authored bytes are read.
|
|
335
|
-
*/
|
|
336
|
-
function existingUnsupportedScriptExtension(assetParts, sources) {
|
|
344
|
+
function findUnrecognizedScriptSource(assetParts, sources) {
|
|
337
345
|
if (assetParts?.type !== "script")
|
|
338
346
|
return undefined;
|
|
339
347
|
const extension = path.extname(assetParts.name);
|
|
@@ -357,7 +365,7 @@ function existingUnsupportedScriptExtension(assetParts, sources) {
|
|
|
357
365
|
const realCandidate = fs.realpathSync(candidate);
|
|
358
366
|
if (!isWithin(realCandidate, realRoot) || !fs.statSync(realCandidate).isFile())
|
|
359
367
|
continue;
|
|
360
|
-
return extension;
|
|
368
|
+
return { extension, path: realCandidate, sourceRoot: realRoot };
|
|
361
369
|
}
|
|
362
370
|
catch {
|
|
363
371
|
// Missing, unreadable, dangling, or otherwise unsafe paths remain normal
|
|
@@ -447,7 +455,7 @@ async function maybeExtractGraphInline(config, sourceStashDir, assetPath) {
|
|
|
447
455
|
export async function showByRef(ref) {
|
|
448
456
|
const parsed = parseBundleRef(ref);
|
|
449
457
|
if (parsed.fragment !== undefined) {
|
|
450
|
-
|
|
458
|
+
warn(`Fragment "#${parsed.fragment}" was ignored by raw show: ${ref}. Returning the whole file.`);
|
|
451
459
|
}
|
|
452
460
|
const entry = await lookupBundleRef(parsed);
|
|
453
461
|
if (!entry) {
|
|
@@ -488,12 +496,15 @@ function indexedMatch(entry, renderer) {
|
|
|
488
496
|
return { type: entry.type, specificity: Number.MAX_SAFE_INTEGER, renderer, meta: { name: entry.name } };
|
|
489
497
|
}
|
|
490
498
|
function buildIndexedProjectionResponse(entry, assetPath, fragment) {
|
|
491
|
-
|
|
492
|
-
|
|
499
|
+
const isMarkdown = path.extname(assetPath).toLowerCase() === ".md";
|
|
500
|
+
if (fragment !== undefined && !isMarkdown) {
|
|
501
|
+
warn(`Fragment "#${fragment}" was ignored: ${entry.conceptId} is not a Markdown document, so heading fragments do not apply. Showing the whole asset.`);
|
|
493
502
|
}
|
|
494
503
|
const raw = fs.readFileSync(assetPath, "utf8");
|
|
495
504
|
const parsed = parseFrontmatter(raw);
|
|
496
|
-
const content = fragment
|
|
505
|
+
const content = fragment !== undefined && isMarkdown
|
|
506
|
+
? requireMarkdownSection(parsed.content, fragment, entry.name).content
|
|
507
|
+
: parsed.content;
|
|
497
508
|
const description = entry.document?.description ?? asNonEmptyString(parsed.data.description);
|
|
498
509
|
const tags = entry.document?.tags ??
|
|
499
510
|
(Array.isArray(parsed.data.tags)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { defineGroupCommand, defineJsonCommand, output } from "../cli/shared.js";
|
|
5
5
|
import { getEffectiveRegistries, loadUserConfig, mutateConfig } from "../core/config/config.js";
|
|
6
6
|
import { NotFoundError, UsageError } from "../core/errors.js";
|
|
7
|
-
import { formatRegistryUrl, hasRegistryUrlCredentials,
|
|
7
|
+
import { formatRegistryLabel, formatRegistryUrl, hasRegistryUrlCredentials, registryEntryForOutput, } from "../core/registry-url.js";
|
|
8
8
|
import { warn } from "../core/warn.js";
|
|
9
9
|
export const registryCommand = defineGroupCommand({
|
|
10
10
|
meta: { name: "registry", description: "Manage bundle registries" },
|
|
@@ -33,7 +33,9 @@ export const registryCommand = defineGroupCommand({
|
|
|
33
33
|
},
|
|
34
34
|
async run({ args }) {
|
|
35
35
|
if (hasRegistryUrlCredentials(args.url)) {
|
|
36
|
-
|
|
36
|
+
warn(`Registry ${formatRegistryLabel({ url: args.url, name: args.name })} was added, but its URL's ` +
|
|
37
|
+
"username/password is ignored: authenticated registries are not supported by the built-in " +
|
|
38
|
+
"static-index or skills-sh providers.");
|
|
37
39
|
}
|
|
38
40
|
if (!args.url.startsWith("http")) {
|
|
39
41
|
throw new UsageError("Registry URL must start with http:// or https://");
|
|
@@ -12,8 +12,8 @@ import fs from "node:fs";
|
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import { stashDirNames } from "../../core/asset/asset-placement.js";
|
|
14
14
|
import { mutateConfig } from "../../core/config/config.js";
|
|
15
|
-
import { ConfigError } from "../../core/errors.js";
|
|
16
15
|
import { assertSafeStashDir, getConfigPath, getDefaultStashDir } from "../../core/paths.js";
|
|
16
|
+
import { warnOnce } from "../../core/warn.js";
|
|
17
17
|
import { primaryBundlePath, withPrimaryBundle } from "./bundle-config-ops.js";
|
|
18
18
|
import { copyStashSkeleton, ensureStashGitignore, scaffoldStashMeta } from "./stash-skeleton.js";
|
|
19
19
|
/**
|
|
@@ -37,11 +37,9 @@ import { copyStashSkeleton, ensureStashGitignore, scaffoldStashMeta } from "./st
|
|
|
37
37
|
function assertInitSandbox(stashDir, dirExplicitlyProvided) {
|
|
38
38
|
if (!dirExplicitlyProvided)
|
|
39
39
|
return; // Only guard explicit --dir, not default HOME resolution.
|
|
40
|
-
const isUnderTest =
|
|
40
|
+
const isUnderTest = process.env.BUN_TEST === "1" || process.env.NODE_ENV === "test";
|
|
41
41
|
if (!isUnderTest)
|
|
42
42
|
return;
|
|
43
|
-
if (process.env.AKM_FORCE_INIT_TMP_STASH === "1")
|
|
44
|
-
return;
|
|
45
43
|
const isTmp = stashDir.startsWith("/tmp/") ||
|
|
46
44
|
stashDir === "/tmp" ||
|
|
47
45
|
stashDir.startsWith("/var/tmp/") ||
|
|
@@ -50,10 +48,8 @@ function assertInitSandbox(stashDir, dirExplicitlyProvided) {
|
|
|
50
48
|
stashDir.startsWith("/private/tmp/");
|
|
51
49
|
if (!isTmp)
|
|
52
50
|
return;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
function isUnderTestRunner() {
|
|
56
|
-
return process.env.BUN_TEST === "1" || process.env.NODE_ENV === "test";
|
|
51
|
+
warnOnce(`init-tmp-stash:${stashDir}`, `Persisting --dir stashDir to a temporary path (${stashDir}) while a test-runner env var (BUN_TEST/NODE_ENV=test) is set; ` +
|
|
52
|
+
"the OS may reap this path, leaving the next run pointing at a deleted bundle.");
|
|
57
53
|
}
|
|
58
54
|
// ── Test seam ────────────────────────────────────────────────────────────────
|
|
59
55
|
// Swap-and-restore override. Inert in production; only tests call the setter.
|
|
@@ -346,8 +346,8 @@ export async function performUpgrade(check, opts, dependencies) {
|
|
|
346
346
|
}
|
|
347
347
|
}
|
|
348
348
|
if (fs.existsSync(backupPath)) {
|
|
349
|
-
|
|
350
|
-
|
|
349
|
+
warn(`A previous upgrade left a stale backup at ${backupPath}; overwriting it.`);
|
|
350
|
+
removeFileBestEffort(backupPath);
|
|
351
351
|
}
|
|
352
352
|
try {
|
|
353
353
|
if (IS_WINDOWS)
|
|
@@ -7,6 +7,7 @@ import path from "node:path";
|
|
|
7
7
|
import { stashDirFor } from "../../core/asset/asset-placement.js";
|
|
8
8
|
import { isBundleSlug } from "../../core/asset/asset-ref.js";
|
|
9
9
|
import { displayRef, parseQualifiedRefInput } from "../../core/asset/resolve-ref.js";
|
|
10
|
+
import { isWithin } from "../../core/common.js";
|
|
10
11
|
import { loadConfig } from "../../core/config/config.js";
|
|
11
12
|
import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
|
|
12
13
|
import { defaultBundleForTarget } from "../../core/mutation-target.js";
|
|
@@ -224,18 +225,15 @@ function lstatIfExists(filePath) {
|
|
|
224
225
|
}
|
|
225
226
|
function assertNoDestinationSymlinkParent(root, destination) {
|
|
226
227
|
const resolvedRoot = path.resolve(root);
|
|
227
|
-
const
|
|
228
|
+
const parentDir = path.dirname(path.resolve(destination));
|
|
229
|
+
const relativeParent = path.relative(resolvedRoot, parentDir);
|
|
228
230
|
if (relativeParent === "" || relativeParent === ".")
|
|
229
231
|
return;
|
|
230
232
|
if (relativeParent.startsWith("..") || path.isAbsolute(relativeParent)) {
|
|
231
233
|
throw new UsageError(`Clone destination escapes the selected target: ${destination}.`, "PATH_ESCAPE_VIOLATION");
|
|
232
234
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
current = path.join(current, segment);
|
|
236
|
-
if (lstatIfExists(current)?.isSymbolicLink()) {
|
|
237
|
-
throw new UsageError(`Clone destination has a symbolic-link parent outside the selected target boundary: ${current}.`, "PATH_ESCAPE_VIOLATION");
|
|
238
|
-
}
|
|
235
|
+
if (!isWithin(parentDir, resolvedRoot)) {
|
|
236
|
+
throw new UsageError(`Clone destination has a symbolic-link parent outside the selected target boundary: ${parentDir}.`, "PATH_ESCAPE_VIOLATION");
|
|
239
237
|
}
|
|
240
238
|
}
|
|
241
239
|
function listTreeFiles(root) {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import { defineCommand } from "citty";
|
|
29
29
|
import { getParsedInvocation } from "../../cli/invocation.js";
|
|
30
|
-
import { defineJsonCommand, EXIT_CODES, GLOBAL_OUTPUT_ARGS, output, runWithJsonErrors } from "../../cli/shared.js";
|
|
30
|
+
import { defineJsonCommand, EXIT_CODES, GLOBAL_OUTPUT_ARGS, output, outputWithExitCode, runWithJsonErrors, } from "../../cli/shared.js";
|
|
31
31
|
import { loadConfig } from "../../core/config/config.js";
|
|
32
32
|
import { UsageError } from "../../core/errors.js";
|
|
33
33
|
import { appendEvent } from "../../core/events.js";
|
|
@@ -54,12 +54,10 @@ export const upgradeCommand = defineJsonCommand({
|
|
|
54
54
|
}
|
|
55
55
|
const skipPostUpgrade = args["skip-post-upgrade"];
|
|
56
56
|
const result = await performUpgrade(check, { force: args.force, skipPostUpgrade });
|
|
57
|
-
output("upgrade", result);
|
|
58
57
|
// The install may have succeeded, but an upgrade whose migration is
|
|
59
58
|
// blocked or could not run is not done: exit like `akm migrate apply` does.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
59
|
+
const migrationFailed = result.migration?.status === "blocked" || result.migration?.status === "failed";
|
|
60
|
+
outputWithExitCode("upgrade", result, migrationFailed ? EXIT_CODES.GENERAL : undefined);
|
|
63
61
|
},
|
|
64
62
|
});
|
|
65
63
|
// `sync` body, standalone so the git-commit/push logic stays in one place.
|
|
@@ -27,11 +27,12 @@
|
|
|
27
27
|
import { defineCommand } from "citty";
|
|
28
28
|
import { getParsedInvocation } from "../../cli/invocation.js";
|
|
29
29
|
import { parsePositiveIntFlag } from "../../cli/parse-args.js";
|
|
30
|
-
import { defineGroupCommand, defineJsonCommand, EXIT_CODES, GLOBAL_OUTPUT_ARGS, output, runWithJsonErrors, } from "../../cli/shared.js";
|
|
30
|
+
import { defineGroupCommand, defineJsonCommand, EXIT_CODES, GLOBAL_OUTPUT_ARGS, output, outputWithExitCode, runWithJsonErrors, } from "../../cli/shared.js";
|
|
31
31
|
import { UsageError } from "../../core/errors.js";
|
|
32
32
|
import { TASK_RUN_BOOLEAN_FLAGS, TASK_RUN_VALUE_FLAGS } from "../../tasks/task-run-reserved-flags.js";
|
|
33
33
|
import { akmTaskExplain } from "./explain.js";
|
|
34
34
|
import { akmTasksAdd, akmTasksDoctor, akmTasksHistory, akmTasksPrune, akmTasksRun, akmTasksSync, akmTasksSyncPlan, } from "./tasks.js";
|
|
35
|
+
import { akmTaskValidate } from "./validate.js";
|
|
35
36
|
/** Shared `--bundle <bundle>` arg wired onto every task subcommand. */
|
|
36
37
|
const bundleArg = {
|
|
37
38
|
bundle: {
|
|
@@ -355,21 +356,16 @@ const tasksSyncCommand = defineJsonCommand({
|
|
|
355
356
|
const rebind = args.rebind === true;
|
|
356
357
|
if (args["dry-run"] === true) {
|
|
357
358
|
const preview = await akmTasksSyncPlan({}, args.bundle, { rebind });
|
|
358
|
-
|
|
359
|
-
const exitCode = taskSyncDryRunExitCode(preview);
|
|
360
|
-
if (exitCode !== undefined)
|
|
361
|
-
process.exitCode = exitCode;
|
|
359
|
+
outputWithExitCode("task-sync-dry-run", preview, taskSyncDryRunExitCode(preview));
|
|
362
360
|
return;
|
|
363
361
|
}
|
|
364
362
|
const result = await akmTasksSync({}, args.bundle, { rebind });
|
|
365
|
-
output("task-sync", result);
|
|
366
363
|
// #867: sync degrades — sources that failed to parse/prepare are
|
|
367
364
|
// excluded from reconciliation and reported in `result.failures` rather
|
|
368
365
|
// than poisoning the whole sync, but their presence must still fail
|
|
369
366
|
// the command's exit code so the breakage stays visible. (#906: this key
|
|
370
367
|
// matches the `--dry-run` preview's `failures` field — no separate name.)
|
|
371
|
-
|
|
372
|
-
process.exitCode = EXIT_CODES.GENERAL;
|
|
368
|
+
outputWithExitCode("task-sync", result, result.failures.length > 0 ? EXIT_CODES.GENERAL : undefined);
|
|
373
369
|
},
|
|
374
370
|
});
|
|
375
371
|
// ── `akm task explain` — read-only introspection (P2b Lane B, spec
|
|
@@ -414,6 +410,36 @@ const tasksDoctorCommand = defineJsonCommand({
|
|
|
414
410
|
output("task-doctor", result);
|
|
415
411
|
},
|
|
416
412
|
});
|
|
413
|
+
/**
|
|
414
|
+
* #907: `akm task validate`'s exit-code contract — `valid`/`converts` are
|
|
415
|
+
* successful outcomes (exit 0); `blocked`/`invalid`/`not-a-task` are
|
|
416
|
+
* diagnosed defects the caller must act on (exit 1, mirroring `task sync`'s
|
|
417
|
+
* own `failures.length > 0 -> EXIT_CODES.GENERAL`). A missing path or an
|
|
418
|
+
* unreadable file never reaches this function at all — `akmTaskValidate`
|
|
419
|
+
* throws a `UsageError` for those, which `defineJsonCommand`'s wrapping
|
|
420
|
+
* already maps to exit 2.
|
|
421
|
+
*/
|
|
422
|
+
export function taskValidateExitCode(result) {
|
|
423
|
+
return result.outcome === "valid" || result.outcome === "converts" ? undefined : EXIT_CODES.GENERAL;
|
|
424
|
+
}
|
|
425
|
+
const tasksValidateCommand = defineJsonCommand({
|
|
426
|
+
meta: {
|
|
427
|
+
name: "validate",
|
|
428
|
+
description: "Parse a single task file by filesystem path — not a concept ref, and the file need not live in a " +
|
|
429
|
+
"configured bundle — and report the same diagnostic `akm task sync` would produce for it. Read-only; " +
|
|
430
|
+
"never touches the scheduler.",
|
|
431
|
+
},
|
|
432
|
+
args: {
|
|
433
|
+
path: { type: "positional", description: "Filesystem path to a task source YAML file", required: true },
|
|
434
|
+
},
|
|
435
|
+
async run({ args }) {
|
|
436
|
+
const result = await akmTaskValidate(args.path);
|
|
437
|
+
output("task-validate", result);
|
|
438
|
+
const exitCode = taskValidateExitCode(result);
|
|
439
|
+
if (exitCode !== undefined)
|
|
440
|
+
process.exitCode = exitCode;
|
|
441
|
+
},
|
|
442
|
+
});
|
|
417
443
|
/**
|
|
418
444
|
* #851: `akm task prune`'s exit-code contract mirrors `task sync --dry-run`'s
|
|
419
445
|
* (`taskSyncDryRunExitCode` above) — non-zero whenever the preview lists
|
|
@@ -451,10 +477,7 @@ const tasksPruneCommand = defineJsonCommand({
|
|
|
451
477
|
.filter(Boolean)
|
|
452
478
|
: undefined;
|
|
453
479
|
const result = await akmTasksPrune({}, { yes: args.yes === true, id });
|
|
454
|
-
|
|
455
|
-
const exitCode = taskPruneExitCode(result);
|
|
456
|
-
if (exitCode !== undefined)
|
|
457
|
-
process.exitCode = exitCode;
|
|
480
|
+
outputWithExitCode("task-prune", result, taskPruneExitCode(result));
|
|
458
481
|
},
|
|
459
482
|
});
|
|
460
483
|
export const taskCommand = defineGroupCommand({
|
|
@@ -466,6 +489,7 @@ export const taskCommand = defineGroupCommand({
|
|
|
466
489
|
add: tasksAddCommand,
|
|
467
490
|
run: tasksRunCommand,
|
|
468
491
|
explain: tasksExplainCommand,
|
|
492
|
+
validate: tasksValidateCommand,
|
|
469
493
|
history: tasksHistoryCommand,
|
|
470
494
|
sync: tasksSyncCommand,
|
|
471
495
|
prune: tasksPruneCommand,
|
|
@@ -22,6 +22,7 @@ import { resolveConfiguredSources } from "../../core/config/config-sources.js";
|
|
|
22
22
|
import { IMPROVE_AUTONOMY_CONFIG_KEY, isImproveAutonomyEnabled } from "../../core/config/experimental.js";
|
|
23
23
|
import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
|
|
24
24
|
import { getTaskHistoryDir, getTaskLogDir } from "../../core/paths.js";
|
|
25
|
+
import { warn } from "../../core/warn.js";
|
|
25
26
|
import { commitWriteTargetBoundary, deleteAssetFromSource, prepareWriteTargetForMutation, resolveWorkingStashTarget, resolveWriteTarget, writeAssetToSource, } from "../../core/write-source.js";
|
|
26
27
|
import { withEngineFallback } from "../../integrations/agent/engine-fallback.js";
|
|
27
28
|
import { resolveAssetPath } from "../../sources/resolve.js";
|
|
@@ -391,7 +392,7 @@ async function buildSchedulerSyncPlan(deps, bundleTarget, options) {
|
|
|
391
392
|
const expectedSignature = sched.expectedSignature?.bind(sched);
|
|
392
393
|
const needsRuntime = preflight.operations.some((operation) => operation.kind !== "remove" && operation.options?.binding === undefined);
|
|
393
394
|
const prepared = needsRuntime
|
|
394
|
-
? prepareSchedulerSyncRuntime(syncTarget ? { target: syncTarget } : undefined, deps,
|
|
395
|
+
? prepareSchedulerSyncRuntime(syncTarget ? { target: syncTarget } : undefined, deps, warnings, allEntries.map((entry) => entry.binding))
|
|
395
396
|
: undefined;
|
|
396
397
|
const plan = finalizeSchedulerSyncPlan({
|
|
397
398
|
...common,
|
|
@@ -768,22 +769,24 @@ async function prepareTaskAddSchedulerTransaction(input) {
|
|
|
768
769
|
contextPath: installedEntry.contextPath,
|
|
769
770
|
},
|
|
770
771
|
}
|
|
771
|
-
: prepareSchedulerSyncRuntime(input.installOpts, input.deps,
|
|
772
|
+
: prepareSchedulerSyncRuntime(input.installOpts, input.deps, []);
|
|
772
773
|
const runtimeOpts = preparedRuntime.options;
|
|
773
774
|
const removals = taskEntries.map((entry) => {
|
|
774
775
|
const invocation = entry.invocation;
|
|
775
|
-
if (!invocation) {
|
|
776
|
-
throw new UsageError(`Installed scheduler binding ${JSON.stringify(entry.id)} has no exact parsed owner; refusing replacement.`, "RESOURCE_ALREADY_EXISTS");
|
|
777
|
-
}
|
|
778
776
|
const nativeId = entry.nativeId ?? schedulerNativeBindingId(entry.id);
|
|
779
777
|
const artifact = assertSchedulerNativeArtifactCardinality(nativeArtifacts, nativeId, 1);
|
|
780
778
|
if (!artifact?.fingerprint || artifact.bindingId !== entry.id) {
|
|
781
779
|
throw new UsageError(`Installed scheduler binding ${JSON.stringify(entry.id)} has no exact coherent fingerprint.`, "RESOURCE_ALREADY_EXISTS");
|
|
782
780
|
}
|
|
783
781
|
const logicalSource = primary.logicalSource;
|
|
784
|
-
const ordinal = schedulerBindingOrdinal(entry.id, logicalSource, invocation);
|
|
785
|
-
if (ordinal === undefined) {
|
|
786
|
-
|
|
782
|
+
const ordinal = invocation ? schedulerBindingOrdinal(entry.id, logicalSource, invocation) : undefined;
|
|
783
|
+
if (!invocation || ordinal === undefined) {
|
|
784
|
+
warn(`Installed scheduler binding ${JSON.stringify(entry.id)} (native id ${JSON.stringify(nativeId)}) could not be exactly parsed — likely a hand-edited entry; replacing it without a compare-and-swap guard.`);
|
|
785
|
+
return Object.freeze({
|
|
786
|
+
kind: "remove",
|
|
787
|
+
id: entry.id,
|
|
788
|
+
nativeId,
|
|
789
|
+
});
|
|
787
790
|
}
|
|
788
791
|
return Object.freeze({
|
|
789
792
|
kind: "remove",
|
|
@@ -824,6 +827,8 @@ async function prepareTaskAddSchedulerTransaction(input) {
|
|
|
824
827
|
for (const removal of removals) {
|
|
825
828
|
if (removal.kind !== "remove")
|
|
826
829
|
continue;
|
|
830
|
+
if (!removal.expected)
|
|
831
|
+
continue;
|
|
827
832
|
initialByKey.set(schedulerNativeArtifactKey(removal.nativeId), Object.freeze({ ...removal.expected, state: "present" }));
|
|
828
833
|
}
|
|
829
834
|
for (const install of installs) {
|
|
@@ -841,16 +846,16 @@ async function prepareTaskAddSchedulerTransaction(input) {
|
|
|
841
846
|
publishOperationIndex: removals.length,
|
|
842
847
|
});
|
|
843
848
|
}
|
|
844
|
-
function prepareSchedulerSyncRuntime(base, deps,
|
|
849
|
+
function prepareSchedulerSyncRuntime(base, deps, warnings, installedBindings = []) {
|
|
845
850
|
if (deps.backend && !deps.schedulerRuntime)
|
|
846
851
|
return base ? { options: base } : {};
|
|
847
852
|
if (deps.schedulerRuntime) {
|
|
848
853
|
const runtime = deps.schedulerRuntime();
|
|
849
|
-
warnIneligibleRebind(runtime,
|
|
854
|
+
warnIneligibleRebind(runtime, warnings, installedBindings);
|
|
850
855
|
return { options: { ...base, binding: runtime.binding, contextPath: runtime.contextPath } };
|
|
851
856
|
}
|
|
852
|
-
const invocation = resolveAndValidateSchedulerInvocation(
|
|
853
|
-
warnIneligibleRebind(invocation,
|
|
857
|
+
const invocation = resolveAndValidateSchedulerInvocation();
|
|
858
|
+
warnIneligibleRebind(invocation, warnings, installedBindings);
|
|
854
859
|
const descriptor = schedulerContextDescriptor();
|
|
855
860
|
const contextPath = schedulerContextPath(descriptor);
|
|
856
861
|
return {
|
|
@@ -863,24 +868,21 @@ function prepareSchedulerSyncRuntime(base, deps, explicitRebind, operation, warn
|
|
|
863
868
|
},
|
|
864
869
|
};
|
|
865
870
|
}
|
|
866
|
-
function resolveAndValidateSchedulerInvocation(
|
|
871
|
+
function resolveAndValidateSchedulerInvocation() {
|
|
867
872
|
const invocation = resolveAkmInvocation();
|
|
868
|
-
if (!invocation.eligible && !explicitRebind) {
|
|
869
|
-
throw new UsageError(`Refusing to ${operation} from an ineligible ${invocation.kind ?? "unknown"} invocation (${invocation.argv.join(" ")}).`, "INVALID_FLAG_VALUE", "npm-global ownership could not be verified. Run `npm install --global akm-cli` and use that launcher, use a standalone installation, or explicitly repeat the operation with --rebind.");
|
|
870
|
-
}
|
|
871
873
|
return { binding: invocation.argv, contextPath: "", eligible: invocation.eligible, kind: invocation.kind };
|
|
872
874
|
}
|
|
873
|
-
function warnIneligibleRebind(runtime,
|
|
874
|
-
if (
|
|
875
|
+
function warnIneligibleRebind(runtime, warnings, installedBindings) {
|
|
876
|
+
if (runtime.eligible !== false || warnings.length > 0)
|
|
875
877
|
return;
|
|
876
|
-
// #868 residue:
|
|
877
|
-
//
|
|
878
|
-
//
|
|
879
|
-
//
|
|
880
|
-
//
|
|
878
|
+
// #868 residue: binding every currently-installed entry to the SAME
|
|
879
|
+
// invocation it already carries changes nothing — this is the steady
|
|
880
|
+
// state of an image-baked install re-running `task sync` on a timer.
|
|
881
|
+
// Only warn when the bind actually moves an entry to a different
|
|
882
|
+
// invocation.
|
|
881
883
|
if (installedBindings.length > 0 && installedBindings.every((bound) => sameArgv(bound, runtime.binding)))
|
|
882
884
|
return;
|
|
883
|
-
warnings.push(
|
|
885
|
+
warnings.push(`Scheduled tasks are bound to an ineligible ${runtime.kind ?? "unknown"} invocation (${runtime.binding.join(" ")}); scheduled runs will invoke a mutable, unproven binary. Install akm via \`npm install --global akm-cli\` or a standalone release, then re-run \`akm task sync --rebind\`.`);
|
|
884
886
|
}
|
|
885
887
|
function groupInstalledBindings(entries, invocation) {
|
|
886
888
|
const groups = new Map();
|
|
@@ -972,16 +974,20 @@ function captureTaskSourceExpectation(filePathInput, rootInput) {
|
|
|
972
974
|
const common = { filePath, rootRealPath };
|
|
973
975
|
let descriptor;
|
|
974
976
|
try {
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
const before = fs.fstatSync(descriptor, { bigint: true });
|
|
977
|
+
descriptor = fs.openSync(filePath, fs.constants.O_RDONLY);
|
|
978
|
+
let before = fs.fstatSync(descriptor, { bigint: true });
|
|
978
979
|
if (!before.isFile()) {
|
|
979
980
|
throw new UsageError(`${filePath} is not a regular task source.`, "INVALID_FLAG_VALUE");
|
|
980
981
|
}
|
|
981
|
-
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
|
|
982
|
+
let bytes = fs.readFileSync(descriptor);
|
|
983
|
+
const torn = !sameTaskSourceStat(before, fs.fstatSync(descriptor, { bigint: true })) ||
|
|
984
|
+
BigInt(bytes.byteLength) !== before.size;
|
|
985
|
+
if (torn) {
|
|
986
|
+
fs.closeSync(descriptor);
|
|
987
|
+
descriptor = fs.openSync(filePath, fs.constants.O_RDONLY);
|
|
988
|
+
before = fs.fstatSync(descriptor, { bigint: true });
|
|
989
|
+
bytes = fs.readFileSync(descriptor);
|
|
990
|
+
warn(`${filePath} changed while its guarded bytes were read; retried once and proceeding with the latest read (its SHA-256 is re-verified before anything is published).`);
|
|
985
991
|
}
|
|
986
992
|
const realPath = fs.realpathSync(filePath);
|
|
987
993
|
const physicalRelative = path.relative(rootRealPath, realPath);
|
|
@@ -1011,9 +1017,6 @@ function captureTaskSourceExpectation(filePathInput, rootInput) {
|
|
|
1011
1017
|
}
|
|
1012
1018
|
if (cause instanceof UsageError)
|
|
1013
1019
|
throw cause;
|
|
1014
|
-
if (cause.code === "ELOOP") {
|
|
1015
|
-
throw new UsageError(`${filePath} must not be a symbolic task source.`, "RESOURCE_ALREADY_EXISTS");
|
|
1016
|
-
}
|
|
1017
1020
|
throw new UsageError(`${filePath} could not be guarded as a contained regular task source: ${errorMessage(cause)}`, "PATH_ESCAPE_VIOLATION");
|
|
1018
1021
|
}
|
|
1019
1022
|
finally {
|
|
@@ -1264,7 +1267,7 @@ function assertInlineTaskPrompt(input) {
|
|
|
1264
1267
|
(!/\s/.test(value) && /[\\/]/.test(value) && path.extname(value) !== "");
|
|
1265
1268
|
if (!isFullRefInput(value) && !pathShaped)
|
|
1266
1269
|
return;
|
|
1267
|
-
|
|
1270
|
+
warn(`--prompt "${input}" looks like an asset ref or file path; --prompt sends it as literal text, not a reference. Did you mean --workflow?`);
|
|
1268
1271
|
}
|
|
1269
1272
|
function parseJsonObjectArg(raw) {
|
|
1270
1273
|
let parsed;
|