akm-cli 0.9.0-beta.5 → 0.9.0-beta.51
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 +711 -0
- package/README.md +12 -4
- package/dist/akm +38 -0
- package/dist/akm-migrate-storage +38 -0
- package/dist/assets/profiles/default.json +9 -4
- package/dist/assets/profiles/frequent.json +1 -1
- package/dist/assets/profiles/memory-focus.json +1 -1
- package/dist/assets/profiles/quick.json +1 -1
- package/dist/assets/profiles/synthesize.json +15 -0
- package/dist/assets/profiles/thorough.json +1 -1
- package/dist/assets/prompts/consolidate-system.md +23 -0
- package/dist/assets/prompts/contradiction-judge.md +33 -0
- package/dist/assets/prompts/distill-knowledge-system.md +22 -0
- package/dist/assets/prompts/distill-lesson-system.md +36 -0
- package/dist/assets/prompts/extract-session.md +6 -2
- package/dist/assets/prompts/graph-extract-system.md +1 -0
- package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
- package/dist/assets/prompts/memory-infer-system.md +1 -0
- package/dist/assets/prompts/memory-infer-user.md +5 -0
- package/dist/assets/prompts/metadata-enhance-system.md +1 -0
- package/dist/assets/prompts/procedural-system.md +44 -0
- package/dist/assets/prompts/recombine-system.md +40 -0
- package/dist/assets/prompts/staleness-detect-system.md +6 -0
- package/dist/assets/prompts/validate-summary-judge.md +1 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
- package/dist/assets/templates/html/health.html +281 -111
- package/dist/assets/wiki/ingest-workflow-template.md +38 -10
- package/dist/cli/parse-args.js +46 -1
- package/dist/cli/shared.js +28 -0
- package/dist/cli.js +27 -11
- package/dist/commands/agent/agent-dispatch.js +2 -2
- package/dist/commands/agent/agent-support.js +0 -7
- package/dist/commands/agent/contribute-cli.js +17 -4
- package/dist/commands/config-cli.js +18 -2
- package/dist/commands/env/child-env.js +47 -0
- package/dist/commands/env/env-cli.js +33 -26
- package/dist/commands/env/secret-cli.js +36 -22
- package/dist/commands/feedback-cli.js +15 -6
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +76 -72
- package/dist/commands/health/checks.js +49 -1
- package/dist/commands/health/html-report.js +422 -80
- package/dist/commands/health.js +386 -9
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/consolidate/chunking.js +141 -0
- package/dist/commands/improve/consolidate/eligibility.js +81 -0
- package/dist/commands/improve/consolidate/merge.js +145 -0
- package/dist/commands/improve/consolidate/sanitize.js +231 -0
- package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
- package/dist/commands/improve/consolidate.js +635 -660
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill.js +159 -69
- package/dist/commands/improve/eligibility.js +434 -0
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +124 -2
- package/dist/commands/improve/extract-prompt.js +39 -2
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +389 -40
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/homeostatic.js +467 -0
- package/dist/commands/improve/improve-auto-accept.js +138 -7
- package/dist/commands/improve/improve-cli.js +36 -61
- package/dist/commands/improve/improve-profiles.js +14 -0
- package/dist/commands/improve/improve-result-file.js +14 -25
- package/dist/commands/improve/improve-session.js +58 -0
- package/dist/commands/improve/improve.js +485 -2498
- package/dist/commands/improve/locks.js +154 -0
- package/dist/commands/improve/loop-stages.js +1083 -0
- package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
- package/dist/commands/improve/outcome-loop.js +256 -0
- package/dist/commands/improve/preparation.js +1966 -0
- package/dist/commands/improve/proactive-maintenance.js +115 -0
- package/dist/commands/improve/procedural.js +418 -0
- package/dist/commands/improve/recombine.js +850 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +183 -40
- package/dist/commands/improve/salience.js +438 -0
- package/dist/commands/improve/triage.js +93 -0
- package/dist/commands/lint/agent-linter.js +19 -24
- package/dist/commands/lint/base-linter.js +173 -60
- package/dist/commands/lint/command-linter.js +19 -24
- package/dist/commands/lint/env-key-rules.js +38 -1
- package/dist/commands/lint/fact-linter.js +39 -0
- package/dist/commands/lint/index.js +31 -13
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +7 -2
- package/dist/commands/lint/task-linter.js +3 -3
- package/dist/commands/lint/workflow-linter.js +26 -1
- package/dist/commands/proposal/drain-policies.js +5 -0
- package/dist/commands/proposal/drain.js +43 -50
- package/dist/commands/proposal/proposal-cli.js +21 -31
- package/dist/commands/proposal/proposal.js +5 -0
- package/dist/commands/proposal/propose.js +7 -2
- package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
- package/dist/commands/proposal/validators/proposals.js +189 -63
- package/dist/commands/read/curate.js +414 -94
- package/dist/commands/read/knowledge.js +6 -3
- package/dist/commands/read/search-cli.js +9 -4
- package/dist/commands/read/search.js +10 -6
- package/dist/commands/read/show.js +86 -7
- package/dist/commands/sources/init.js +49 -17
- package/dist/commands/sources/installed-stashes.js +11 -3
- package/dist/commands/sources/schema-repair.js +43 -45
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-add.js +7 -3
- package/dist/commands/sources/stash-cli.js +28 -40
- package/dist/commands/sources/stash-skeleton.js +23 -8
- package/dist/commands/tasks/tasks-cli.js +19 -27
- package/dist/commands/tasks/tasks.js +39 -11
- package/dist/commands/wiki-cli.js +21 -35
- package/dist/core/asset/asset-registry.js +3 -1
- package/dist/core/asset/asset-spec.js +18 -2
- package/dist/core/asset/frontmatter.js +166 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/authoring-rules.js +92 -0
- package/dist/core/common.js +0 -5
- package/dist/core/config/config-migration.js +12 -11
- package/dist/core/config/config-schema.js +340 -56
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config.js +28 -7
- package/dist/core/events.js +3 -7
- package/dist/core/improve-types.js +11 -8
- package/dist/core/logs-db.js +10 -66
- package/dist/core/parse.js +36 -16
- package/dist/core/paths.js +3 -0
- package/dist/core/standards/resolve-standards-context.js +87 -0
- package/dist/core/standards/resolve-stash-standards.js +99 -0
- package/dist/core/standards/resolve-type-conventions.js +66 -0
- package/dist/core/state/migrations.js +714 -0
- package/dist/core/state-db.js +525 -474
- package/dist/indexer/db/db.js +439 -247
- package/dist/indexer/db/graph-db.js +129 -86
- package/dist/indexer/ensure-index.js +152 -17
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/graph/graph-extraction.js +218 -4
- package/dist/indexer/index-writer-lock.js +99 -0
- package/dist/indexer/indexer.js +123 -221
- package/dist/indexer/passes/dir-staleness.js +114 -0
- package/dist/indexer/passes/memory-inference.js +13 -5
- package/dist/indexer/passes/staleness-detect.js +2 -5
- package/dist/indexer/search/db-search.js +19 -6
- package/dist/indexer/search/ranking-contributors.js +22 -0
- package/dist/indexer/search/ranking.js +4 -0
- package/dist/indexer/search/search-source.js +17 -18
- package/dist/indexer/search/semantic-status.js +4 -0
- package/dist/indexer/walk/matchers.js +9 -0
- package/dist/integrations/agent/config.js +6 -53
- package/dist/integrations/agent/index.js +2 -18
- package/dist/integrations/agent/prompts.js +75 -9
- package/dist/integrations/agent/runner-dispatch.js +59 -0
- package/dist/integrations/harnesses/claude/session-log.js +11 -1
- package/dist/integrations/harnesses/index.js +2 -3
- package/dist/integrations/harnesses/opencode/session-log.js +173 -3
- package/dist/integrations/harnesses/opencode-sdk/index.js +2 -2
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +0 -2
- package/dist/integrations/session-logs/index.js +16 -0
- package/dist/llm/client.js +45 -15
- package/dist/llm/embedder.js +42 -3
- package/dist/llm/embedders/deterministic.js +66 -0
- package/dist/llm/embedders/local.js +66 -2
- package/dist/llm/feature-gate.js +8 -4
- package/dist/llm/graph-extract.js +67 -44
- package/dist/llm/memory-infer-impl.js +138 -0
- package/dist/llm/memory-infer.js +1 -127
- package/dist/llm/metadata-enhance.js +44 -31
- package/dist/llm/structured-call.js +49 -0
- package/dist/migrate-storage-node.mjs +8 -0
- package/dist/output/context.js +5 -5
- package/dist/output/renderers.js +74 -2
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/passthrough.js +0 -1
- package/dist/output/text/helpers.js +16 -1
- package/dist/registry/providers/skills-sh.js +21 -147
- package/dist/registry/providers/static-index.js +15 -157
- package/dist/registry/resolve.js +22 -9
- package/dist/runtime.js +25 -1
- package/dist/scripts/migrate-storage.js +2617 -1961
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +759 -510
- package/dist/setup/setup.js +29 -8
- package/dist/sources/include.js +6 -2
- package/dist/sources/providers/filesystem.js +0 -1
- package/dist/sources/providers/git-install.js +210 -0
- package/dist/sources/providers/git-provider.js +234 -0
- package/dist/sources/providers/git-stash.js +248 -0
- package/dist/sources/providers/git.js +10 -661
- package/dist/sources/providers/npm.js +2 -6
- package/dist/sources/providers/provider-utils.js +13 -7
- package/dist/sources/providers/sync-from-ref.js +9 -1
- package/dist/sources/providers/tar-utils.js +16 -8
- package/dist/sources/providers/website.js +9 -5
- package/dist/sources/website-ingest.js +187 -29
- package/dist/sources/wiki-fetchers/registry.js +53 -0
- package/dist/sources/wiki-fetchers/youtube.js +239 -0
- package/dist/storage/database.js +45 -10
- package/dist/storage/managed-db.js +82 -0
- package/dist/storage/repositories/registry-cache.js +92 -0
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/tasks/backends/cron.js +1 -1
- package/dist/tasks/backends/launchd.js +1 -1
- package/dist/tasks/backends/schtasks.js +1 -1
- package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
- package/dist/tasks/runner.js +5 -13
- package/dist/text-import-hook.mjs +0 -0
- package/dist/wiki/wiki.js +37 -0
- package/dist/workflows/db.js +3 -4
- package/dist/workflows/runtime/runs.js +1 -117
- package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/data-and-telemetry.md +3 -2
- package/docs/migration/release-notes/0.9.0.md +39 -0
- package/package.json +13 -11
- package/dist/commands/db-cli.js +0 -23
- package/dist/indexer/db/db-backup.js +0 -376
|
@@ -6,20 +6,47 @@ Schema: {{SCHEMA_PATH}}
|
|
|
6
6
|
Follow these steps. akm commands handle the invariants; use your native
|
|
7
7
|
Read/Write/Edit tools for page edits.
|
|
8
8
|
|
|
9
|
+
This workflow is for ingesting sources that are ALREADY present under
|
|
10
|
+
`{{WIKI_DIR}}/raw/`. Do not ask the user for a source unless the raw queue is
|
|
11
|
+
empty and the caller explicitly asked for interactive ingest.
|
|
12
|
+
|
|
9
13
|
1. **Read the schema.** Open `{{SCHEMA_PATH}}`. It defines the voice, page
|
|
10
14
|
kinds, contradiction policy, and any wiki-specific conventions. Do not
|
|
11
15
|
skip this step even on familiar wikis — the schema may have changed.
|
|
12
16
|
|
|
13
|
-
2. **
|
|
17
|
+
2. **Discover the pending raw queue.**
|
|
14
18
|
```sh
|
|
15
|
-
akm wiki
|
|
16
|
-
# or: cat <source> | akm wiki stash {{WIKI_NAME}} -
|
|
19
|
+
akm wiki lint {{WIKI_NAME}}
|
|
17
20
|
```
|
|
18
|
-
|
|
21
|
+
Focus on `uncited-raw` findings: those raw files exist under `raw/` but are
|
|
22
|
+
not yet cited by any authored page. Treat each `uncited-raw` finding as a
|
|
23
|
+
pending ingest item, and sort the queue **oldest raw file first** (by
|
|
24
|
+
filename/mtime). Processing oldest-first keeps backlog age bounded even
|
|
25
|
+
when the queue is larger than one run can finish. If there are no
|
|
26
|
+
`uncited-raw` findings, exit cleanly after a final `akm index` +
|
|
27
|
+
`akm wiki lint {{WIKI_NAME}}` verification.
|
|
28
|
+
|
|
29
|
+
Do not read or classify the whole backlog upfront. Work the queue as a
|
|
30
|
+
bounded loop, one raw file at a time: fully finish a raw (read → decide →
|
|
31
|
+
edit → log entry, steps 3-7 below) before looking at the next one. If you
|
|
32
|
+
run low on time partway through a large backlog, stop after finishing your
|
|
33
|
+
current raw — do not start a new one you can't complete. This is expected
|
|
34
|
+
and fine: whatever you already merged is committed to the pages and
|
|
35
|
+
`log.md`, so the next scheduled run picks up where you left off instead of
|
|
36
|
+
redoing this run's work.
|
|
37
|
+
|
|
38
|
+
3. **For the current raw file, read the source and find related pages.**
|
|
39
|
+
Open the raw file directly from `{{WIKI_DIR}}/raw/`.
|
|
40
|
+
|
|
41
|
+
If the file does not read as text (binary content, garbled bytes, e.g. a
|
|
42
|
+
raw PDF byte-dump that was never text-extracted), do not attempt deep
|
|
43
|
+
forensic recovery (no web searches, no `strings`-style dumps). Append a
|
|
44
|
+
one-line `log.md` entry noting it as skipped/unprocessable with a short
|
|
45
|
+
reason, then move on to the next raw in the queue.
|
|
19
46
|
|
|
20
|
-
|
|
47
|
+
Otherwise, search for related pages:
|
|
21
48
|
```sh
|
|
22
|
-
akm wiki search {{WIKI_NAME}} "<key terms from the source>"
|
|
49
|
+
akm wiki search {{WIKI_NAME}} "<key terms from the raw source>"
|
|
23
50
|
```
|
|
24
51
|
Read the top hits with `akm show wiki:{{WIKI_NAME}}/<page>`. Use
|
|
25
52
|
`akm show wiki:{{WIKI_NAME}}/<page> toc` for large pages.
|
|
@@ -39,8 +66,9 @@ Read/Write/Edit tools for page edits.
|
|
|
39
66
|
6. **Update xrefs both ways.** If page A now xrefs page B, page B must xref
|
|
40
67
|
page A. `akm wiki lint {{WIKI_NAME}}` will flag violations.
|
|
41
68
|
|
|
42
|
-
7. **Append to `log.md`.** One entry per
|
|
43
|
-
|
|
69
|
+
7. **Append to `log.md`.** One entry per processed raw source (ingested or
|
|
70
|
+
skipped-as-unprocessable): date, raw slug, one-line summary, refs to
|
|
71
|
+
created/edited pages. Newest at the top.
|
|
44
72
|
|
|
45
73
|
8. **Regenerate the index + verify.**
|
|
46
74
|
```sh
|
|
@@ -50,5 +78,5 @@ Read/Write/Edit tools for page edits.
|
|
|
50
78
|
Resolve any lint findings before calling the ingest done.
|
|
51
79
|
|
|
52
80
|
That's it. `akm` never calls an LLM — reasoning is your job; it just owns
|
|
53
|
-
the invariants (raw immutability,
|
|
54
|
-
|
|
81
|
+
the invariants (raw immutability, ref validation, index regeneration,
|
|
82
|
+
structural lint).
|
package/dist/cli/parse-args.js
CHANGED
|
@@ -8,7 +8,52 @@
|
|
|
8
8
|
* main CLI file focused on command definitions and routing.
|
|
9
9
|
*/
|
|
10
10
|
import { UsageError } from "../core/errors.js";
|
|
11
|
-
|
|
11
|
+
function cittyComparableName(name) {
|
|
12
|
+
return name.replace(/[-_]+([a-zA-Z0-9])/g, (_match, char) => char.toUpperCase());
|
|
13
|
+
}
|
|
14
|
+
function toAliasArray(alias) {
|
|
15
|
+
if (Array.isArray(alias))
|
|
16
|
+
return alias;
|
|
17
|
+
return typeof alias === "string" ? [alias] : [];
|
|
18
|
+
}
|
|
19
|
+
function isCittyValueFlag(flag, argsDef) {
|
|
20
|
+
const name = flag.replace(/^-{1,2}/, "");
|
|
21
|
+
const normalized = cittyComparableName(name);
|
|
22
|
+
for (const [key, def] of Object.entries(argsDef)) {
|
|
23
|
+
if (def.type !== "string" && def.type !== "enum")
|
|
24
|
+
continue;
|
|
25
|
+
if (normalized === cittyComparableName(key))
|
|
26
|
+
return true;
|
|
27
|
+
if (toAliasArray(def.alias).includes(name))
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Match citty's top-level subcommand scan (`findSubCommandIndex`).
|
|
34
|
+
*
|
|
35
|
+
* Citty does not assume `rawArgs[0]` is the command: global string flags may
|
|
36
|
+
* appear first and consume the following token. The CLI startup guard uses this
|
|
37
|
+
* to classify the requested command before any command handler can run.
|
|
38
|
+
*/
|
|
39
|
+
export function findCittyTopLevelCommandIndex(rawArgs, argsDef) {
|
|
40
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
41
|
+
const arg = rawArgs[i];
|
|
42
|
+
if (arg === "--")
|
|
43
|
+
return -1;
|
|
44
|
+
if (arg.startsWith("-")) {
|
|
45
|
+
if (!arg.includes("=") && isCittyValueFlag(arg, argsDef))
|
|
46
|
+
i += 1;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
return i;
|
|
50
|
+
}
|
|
51
|
+
return -1;
|
|
52
|
+
}
|
|
53
|
+
export function findCittyTopLevelCommand(rawArgs, argsDef) {
|
|
54
|
+
const index = findCittyTopLevelCommandIndex(rawArgs, argsDef);
|
|
55
|
+
return index >= 0 ? rawArgs[index] : undefined;
|
|
56
|
+
}
|
|
12
57
|
/**
|
|
13
58
|
* Return true when `args._[0]` is a member of `validSet`.
|
|
14
59
|
*
|
package/dist/cli/shared.js
CHANGED
|
@@ -15,6 +15,7 @@ import { getOutputMode } from "../output/context.js";
|
|
|
15
15
|
import { DEFAULT_TEMPLATE, deliverRendered, escapeHtml, renderHtml, resolveTemplatePath } from "../output/html-render.js";
|
|
16
16
|
import { shapeForCommand } from "../output/shapes.js";
|
|
17
17
|
import { formatPlain, outputJsonl } from "../output/text.js";
|
|
18
|
+
import { hasSubcommand } from "./parse-args.js";
|
|
18
19
|
// ── Exit codes ───────────────────────────────────────────────────────────────
|
|
19
20
|
/**
|
|
20
21
|
* Canonical process exit-code table for the akm CLI. Single source of truth —
|
|
@@ -111,6 +112,33 @@ export function defineJsonCommand(def) {
|
|
|
111
112
|
run: (context) => runWithJsonErrors(() => run(context)),
|
|
112
113
|
});
|
|
113
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Define a citty subcommand-group command (env, secret, proposal, tasks, wiki,
|
|
117
|
+
* graph, …) that shares one wiring shape: a `subCommands` map, a routing set
|
|
118
|
+
* DERIVED from that map's keys (so the set can never silently desync from the
|
|
119
|
+
* registered subcommands), and a default body that fires ONLY for the bare
|
|
120
|
+
* group invocation — citty still runs the group body after dispatching a
|
|
121
|
+
* subcommand, so the shared guard short-circuits when `args._[0]` names a
|
|
122
|
+
* registered subcommand.
|
|
123
|
+
*
|
|
124
|
+
* The `defaultRun` body is wrapped in `runWithJsonErrors`, so it emits a
|
|
125
|
+
* byte-identical JSON error envelope on throw — exactly the per-site
|
|
126
|
+
* `run() { return runWithJsonErrors(() => { if (hasSubcommand(...)) return; … }); }`
|
|
127
|
+
* boilerplate this replaces.
|
|
128
|
+
*/
|
|
129
|
+
export function defineGroupCommand(def) {
|
|
130
|
+
const subcommandSet = new Set(Object.keys(def.subCommands));
|
|
131
|
+
return defineCommand({
|
|
132
|
+
meta: def.meta,
|
|
133
|
+
...(def.args ? { args: def.args } : {}),
|
|
134
|
+
subCommands: def.subCommands,
|
|
135
|
+
run: (context) => runWithJsonErrors(() => {
|
|
136
|
+
if (hasSubcommand(context.args, subcommandSet))
|
|
137
|
+
return;
|
|
138
|
+
return def.defaultRun(context);
|
|
139
|
+
}),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
114
142
|
/**
|
|
115
143
|
* Render a command result according to the active output mode
|
|
116
144
|
* (json/jsonl/yaml/text/md/html). When `--output <path>` is set, the rendered
|
package/dist/cli.js
CHANGED
|
@@ -8,18 +8,21 @@
|
|
|
8
8
|
// `dist/cli-node.mjs` wrapper, which registers the text-import loader hook
|
|
9
9
|
// before this module graph loads; running `node dist/cli.js` directly still
|
|
10
10
|
// works for code paths that touch no embedded text asset, but the wrapper is
|
|
11
|
-
// the supported entry. The hard floor is Node 20: `@clack/core` (prompts) imports
|
|
11
|
+
// the supported entry. The hard floor is Node 20.12: `@clack/core` (prompts) imports
|
|
12
12
|
// `node:util`'s `styleText` (added in Node 20.12) — Node 18 (EOL) throws at import.
|
|
13
13
|
{
|
|
14
14
|
const isBun = typeof globalThis.Bun !== "undefined";
|
|
15
15
|
if (!isBun) {
|
|
16
|
-
const major =
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const [major = 0, minor = 0, patch = 0] = (process.versions.node ?? "0")
|
|
17
|
+
.split(".")
|
|
18
|
+
.map((part) => Number.parseInt(part, 10) || 0);
|
|
19
|
+
const nodeOk = major > 20 || (major === 20 && (minor > 12 || (minor === 12 && patch >= 0)));
|
|
20
|
+
if (!nodeOk) {
|
|
21
|
+
console.error("\n ERROR: akm-cli requires the Bun runtime (https://bun.sh) or Node.js >= 20.12.\n" +
|
|
19
22
|
` Detected Node.js ${process.versions.node ?? "unknown"}.\n` +
|
|
20
23
|
" Install options:\n" +
|
|
21
24
|
" 1. Bun: curl -fsSL https://bun.sh/install | bash && bun install -g akm-cli\n" +
|
|
22
|
-
" 2. Node: upgrade to Node.js 20 or newer (https://nodejs.org)\n" +
|
|
25
|
+
" 2. Node: upgrade to Node.js 20.12 or newer (https://nodejs.org)\n" +
|
|
23
26
|
" 3. Binary: curl -fsSL https://github.com/itlackey/akm/releases/latest/download/install.sh | bash\n");
|
|
24
27
|
process.exit(1);
|
|
25
28
|
}
|
|
@@ -57,6 +60,7 @@ process.on("uncaughtException", (err) => {
|
|
|
57
60
|
import fs from "node:fs";
|
|
58
61
|
import path from "node:path";
|
|
59
62
|
import { defineCommand, runMain } from "citty";
|
|
63
|
+
import { findCittyTopLevelCommand } from "./cli/parse-args.js";
|
|
60
64
|
import { EXIT_CODES, emitJsonError, output, parseAllFlagValues, runWithJsonErrors } from "./cli/shared.js";
|
|
61
65
|
import { agentCommand, lintCommand, proposeCommand } from "./commands/agent/contribute-cli.js";
|
|
62
66
|
import { generateBashCompletions, installBashCompletions } from "./commands/completions.js";
|
|
@@ -77,7 +81,7 @@ import { registryCommand } from "./commands/registry-cli.js";
|
|
|
77
81
|
import { addCommand } from "./commands/sources/add-cli.js";
|
|
78
82
|
import { renderMigrationHelp } from "./commands/sources/migration-help.js";
|
|
79
83
|
import { cloneCommand, historyCommand, listCommand, removeCommand, syncCommand, updateCommand, upgradeCommand, } from "./commands/sources/sources-cli.js";
|
|
80
|
-
import {
|
|
84
|
+
import { importKnowledgeCommand, indexCommand, infoCommand, initCommand } from "./commands/sources/stash-cli.js";
|
|
81
85
|
import { tasksCommand } from "./commands/tasks/tasks-cli.js";
|
|
82
86
|
import { wikiCommand } from "./commands/wiki-cli.js";
|
|
83
87
|
import { workflowCommand } from "./commands/workflow-cli.js";
|
|
@@ -320,10 +324,14 @@ const healthCommand = defineCommand({
|
|
|
320
324
|
// window-compare read for the trend deltas (defaults to 24h,
|
|
321
325
|
// overridable via --compare).
|
|
322
326
|
if (mode.format === "html") {
|
|
323
|
-
|
|
324
|
-
|
|
327
|
+
// Default the compare window to the report's own `--since` window so the
|
|
328
|
+
// trend deltas are like-for-like (e.g. last 7d vs the prior 7d). A fixed
|
|
329
|
+
// 24h default made a `--since 7d` report compare its 7-day totals against
|
|
330
|
+
// a 24-hour prior window, producing meaningless deltas.
|
|
331
|
+
const compare = args.compare ?? windowCompareRaw ?? args.since ?? "24h";
|
|
332
|
+
const result = akmHealth({ since: args.since, groupBy: "run", windowCompare: compare });
|
|
325
333
|
resultStatus = result.status;
|
|
326
|
-
const deltas =
|
|
334
|
+
const deltas = result.deltas;
|
|
327
335
|
const { buildHealthHtmlReplacements } = await import("./commands/health/html-report.js");
|
|
328
336
|
const { listPendingProposals } = await import("./commands/proposal/proposal.js");
|
|
329
337
|
const replacements = buildHealthHtmlReplacements(result, {
|
|
@@ -482,7 +490,6 @@ export const main = defineCommand({
|
|
|
482
490
|
health: healthCommand,
|
|
483
491
|
info: infoCommand,
|
|
484
492
|
graph: graphCommand,
|
|
485
|
-
db: dbCommand,
|
|
486
493
|
add: addCommand,
|
|
487
494
|
list: listCommand,
|
|
488
495
|
remove: removeCommand,
|
|
@@ -517,6 +524,7 @@ export const main = defineCommand({
|
|
|
517
524
|
tasks: tasksCommand,
|
|
518
525
|
},
|
|
519
526
|
});
|
|
527
|
+
const MAIN_TOP_LEVEL_ARGS = main.args;
|
|
520
528
|
// ── Exit codes ──────────────────────────────────────────────────────────────
|
|
521
529
|
// Canonical table lives in `src/cli/shared.ts` (EXIT_CODES). These aliases keep
|
|
522
530
|
// the local call sites terse. EXIT_HEALTH_WARN (4) is the `akm health` "warn"
|
|
@@ -536,6 +544,13 @@ const EXIT_HEALTH_WARN = EXIT_CODES.HEALTH_WARN;
|
|
|
536
544
|
// The wrapper sets `AKM_NODE_ENTRY=1` to opt into the startup block. The test
|
|
537
545
|
// harness never sets it, so importing cli.ts under Bun stays inert as before.
|
|
538
546
|
if (import.meta.main || process.env.AKM_NODE_ENTRY === "1") {
|
|
547
|
+
// Mark that this process is the real akm CLI: its `process.argv[1]` is the
|
|
548
|
+
// akm entrypoint, so the background auto-reindex may safely re-invoke it as a
|
|
549
|
+
// detached child. Hosts that merely import this module (the in-process test
|
|
550
|
+
// harness, library embeddings) never reach this block, so they fall back to
|
|
551
|
+
// an inline reindex instead of spawning the wrong program. See
|
|
552
|
+
// `ensureIndex` in src/indexer/ensure-index.ts.
|
|
553
|
+
process.env.AKM_CLI_ENTRY = "1";
|
|
539
554
|
// citty reads process.argv directly and does not accept a custom argv array,
|
|
540
555
|
// so we must replace process.argv with the normalized version before runMain.
|
|
541
556
|
process.argv = normalizeShowArgv(process.argv);
|
|
@@ -557,7 +572,8 @@ if (import.meta.main || process.env.AKM_NODE_ENTRY === "1") {
|
|
|
557
572
|
// output-shaping time after the side effect has already happened. The
|
|
558
573
|
// shape-registry gate in shapeForCommand() remains as defense-in-depth (and
|
|
559
574
|
// covers the in-process test harness, which skips this startup block).
|
|
560
|
-
|
|
575
|
+
const topLevelCommand = findCittyTopLevelCommand(process.argv.slice(2), MAIN_TOP_LEVEL_ARGS);
|
|
576
|
+
if (getOutputMode().shape === "summary" && topLevelCommand !== "show") {
|
|
561
577
|
emitJsonError(new UsageError("'--shape summary' is only valid on 'akm show'.", "INVALID_SHAPE_VALUE"));
|
|
562
578
|
}
|
|
563
579
|
// One-time cleanup of stale 0.7.x index file at the old cache location.
|
|
@@ -18,7 +18,7 @@ import { parseAssetRef } from "../../core/asset/asset-ref.js";
|
|
|
18
18
|
import { NotFoundError, UsageError } from "../../core/errors.js";
|
|
19
19
|
import { requireAgentProfile } from "../../integrations/agent/config.js";
|
|
20
20
|
import { runAgent } from "../../integrations/agent/spawn.js";
|
|
21
|
-
import {
|
|
21
|
+
import { runOpencodeSdk } from "../../integrations/harnesses/opencode-sdk/index.js";
|
|
22
22
|
/**
|
|
23
23
|
* Fill `{{0}}`, `{{1}}`, ... placeholders in `template` with the
|
|
24
24
|
* corresponding entries in `args`. Any placeholder index that exceeds the
|
|
@@ -93,7 +93,7 @@ export async function akmAgentDispatch(options) {
|
|
|
93
93
|
...(dispatchRequest !== undefined ? { dispatch: dispatchRequest } : {}),
|
|
94
94
|
};
|
|
95
95
|
const result = profile.sdkMode
|
|
96
|
-
? await
|
|
96
|
+
? await runOpencodeSdk(profile, prompt ?? "", runOptions, options.llmConfig)
|
|
97
97
|
: await runAgent(profile, prompt, runOptions);
|
|
98
98
|
return {
|
|
99
99
|
schemaVersion: 1,
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
/**
|
|
5
|
-
* Shared helpers for agent-based commands (reflect, propose, etc.).
|
|
6
|
-
*
|
|
7
|
-
* Consolidates utility functions that were duplicated byte-for-byte across
|
|
8
|
-
* `reflect.ts` and `propose.ts`. Any command that shells out to an agent
|
|
9
|
-
* profile can import from here rather than copy-pasting.
|
|
10
|
-
*/
|
|
11
4
|
import { loadConfig } from "../../core/config/config.js";
|
|
12
5
|
import { requireAgentProfile, } from "../../integrations/agent/index.js";
|
|
13
6
|
// ── Config helpers ───────────────────────────────────────────────────────────
|
|
@@ -29,7 +29,7 @@ import { assertFlatAssetName, combineCreatePath, normalizeCreateSubPath } from "
|
|
|
29
29
|
import { loadConfig } from "../../core/config/config.js";
|
|
30
30
|
import { UsageError } from "../../core/errors.js";
|
|
31
31
|
import { getHyphenatedArg } from "../../output/context.js";
|
|
32
|
-
import { akmLint } from "../lint.js";
|
|
32
|
+
import { akmLint } from "../lint/index.js";
|
|
33
33
|
import { akmPropose } from "../proposal/propose.js";
|
|
34
34
|
import { akmAgentDispatch } from "./agent-dispatch.js";
|
|
35
35
|
const EXIT_GENERAL = EXIT_CODES.GENERAL;
|
|
@@ -122,23 +122,36 @@ export const lintCommand = defineCommand({
|
|
|
122
122
|
description: "Scan stash .md files for structural issues (unquoted colons, missing updated field, orphaned stubs, placeholder stubs, missing name/type, stale paths). Use --fix to auto-fix Tier 1 issues. Exits 0 on success regardless of findings; use --fail-on-flagged for CI fail-on-finding behavior.",
|
|
123
123
|
},
|
|
124
124
|
args: {
|
|
125
|
-
fix: {
|
|
125
|
+
fix: {
|
|
126
|
+
type: "boolean",
|
|
127
|
+
alias: "auto-fix",
|
|
128
|
+
description: "Apply auto-fixes in place (alias: --auto-fix)",
|
|
129
|
+
default: false,
|
|
130
|
+
},
|
|
126
131
|
dir: { type: "string", description: "Override stash root directory (default: from config)" },
|
|
127
132
|
"fail-on-flagged": {
|
|
128
133
|
type: "boolean",
|
|
129
134
|
description: "Exit non-zero when summary.flagged > 0 (CI-friendly). Default: exit 0 regardless of findings.",
|
|
130
135
|
default: false,
|
|
131
136
|
},
|
|
137
|
+
type: {
|
|
138
|
+
type: "string",
|
|
139
|
+
description: "Only lint assets of this type (e.g. workflows, tasks, memories)",
|
|
140
|
+
default: undefined,
|
|
141
|
+
},
|
|
132
142
|
},
|
|
133
143
|
async run({ args }) {
|
|
134
144
|
await runWithJsonErrors(async () => {
|
|
135
145
|
const result = akmLint({
|
|
136
146
|
fix: args.fix ?? false,
|
|
137
147
|
dir: getStringArg(args, "dir"),
|
|
148
|
+
typeFilter: getStringArg(args, "type"),
|
|
138
149
|
});
|
|
139
150
|
output("lint", result);
|
|
140
|
-
if (args["fail-on-flagged"] && result.summary.flagged > 0)
|
|
141
|
-
process.
|
|
151
|
+
if (args["fail-on-flagged"] && result.summary.flagged > 0) {
|
|
152
|
+
process.exitCode = EXIT_GENERAL;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
142
155
|
});
|
|
143
156
|
},
|
|
144
157
|
});
|
|
@@ -64,7 +64,10 @@ function rewriteKey(config, key) {
|
|
|
64
64
|
// ── Public API ──────────────────────────────────────────────────────────────
|
|
65
65
|
export function getConfigValue(config, key) {
|
|
66
66
|
const k = rewriteKey(config, key);
|
|
67
|
-
|
|
67
|
+
const value = configGet(config, k);
|
|
68
|
+
if (k.split(".").at(-1) === "apiKey")
|
|
69
|
+
return null;
|
|
70
|
+
return omitApiKeysForOutput(value);
|
|
68
71
|
}
|
|
69
72
|
export function setConfigValue(config, key, rawValue) {
|
|
70
73
|
// #454: reject the legacy aliases up front so the error message names the
|
|
@@ -154,7 +157,20 @@ export function listConfig(config) {
|
|
|
154
157
|
result.archiveRetentionDays = config.archiveRetentionDays;
|
|
155
158
|
if (config.configVersion !== undefined)
|
|
156
159
|
result.configVersion = config.configVersion;
|
|
157
|
-
return result;
|
|
160
|
+
return omitApiKeysForOutput(result);
|
|
161
|
+
}
|
|
162
|
+
function omitApiKeysForOutput(value) {
|
|
163
|
+
if (Array.isArray(value))
|
|
164
|
+
return value.map(omitApiKeysForOutput);
|
|
165
|
+
if (!value || typeof value !== "object")
|
|
166
|
+
return value;
|
|
167
|
+
const out = {};
|
|
168
|
+
for (const [key, child] of Object.entries(value)) {
|
|
169
|
+
if (key === "apiKey")
|
|
170
|
+
continue;
|
|
171
|
+
out[key] = omitApiKeysForOutput(child);
|
|
172
|
+
}
|
|
173
|
+
return out;
|
|
158
174
|
}
|
|
159
175
|
export { unknownKeyHint };
|
|
160
176
|
// ── `akm config` command surface ────────────────────────────────────────────
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
const CLEAN_ENV_ALLOWLIST = [
|
|
5
|
+
"HOME",
|
|
6
|
+
"PATH",
|
|
7
|
+
"PWD",
|
|
8
|
+
"SHELL",
|
|
9
|
+
"TERM",
|
|
10
|
+
"TMPDIR",
|
|
11
|
+
"TEMP",
|
|
12
|
+
"TMP",
|
|
13
|
+
"USER",
|
|
14
|
+
"LOGNAME",
|
|
15
|
+
"LANG",
|
|
16
|
+
"LANGUAGE",
|
|
17
|
+
"LC_ALL",
|
|
18
|
+
"LC_CTYPE",
|
|
19
|
+
"LC_COLLATE",
|
|
20
|
+
"LC_MESSAGES",
|
|
21
|
+
"LC_MONETARY",
|
|
22
|
+
"LC_NUMERIC",
|
|
23
|
+
"LC_TIME",
|
|
24
|
+
"LC_PAPER",
|
|
25
|
+
"LC_NAME",
|
|
26
|
+
"LC_ADDRESS",
|
|
27
|
+
"LC_TELEPHONE",
|
|
28
|
+
"LC_MEASUREMENT",
|
|
29
|
+
"LC_IDENTIFICATION",
|
|
30
|
+
"TZ",
|
|
31
|
+
"NO_COLOR",
|
|
32
|
+
"COLORTERM",
|
|
33
|
+
];
|
|
34
|
+
export function buildChildEnv(parentEnv, options) {
|
|
35
|
+
const base = options.clean ? {} : { ...parentEnv };
|
|
36
|
+
if (options.clean) {
|
|
37
|
+
for (const key of CLEAN_ENV_ALLOWLIST) {
|
|
38
|
+
if (parentEnv[key] !== undefined)
|
|
39
|
+
base[key] = parentEnv[key];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const key of options.inherit) {
|
|
43
|
+
if (parentEnv[key] !== undefined)
|
|
44
|
+
base[key] = parentEnv[key];
|
|
45
|
+
}
|
|
46
|
+
return base;
|
|
47
|
+
}
|
|
@@ -19,8 +19,8 @@ import { spawnSync } from "node:child_process";
|
|
|
19
19
|
import fs from "node:fs";
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import { defineCommand } from "citty";
|
|
22
|
-
import { getStringArg
|
|
23
|
-
import { output, runWithJsonErrors } from "../../cli/shared.js";
|
|
22
|
+
import { getStringArg } from "../../cli/parse-args.js";
|
|
23
|
+
import { defineGroupCommand, output, runWithJsonErrors } from "../../cli/shared.js";
|
|
24
24
|
import { assertFlatAssetName, combineCreatePath, normalizeCreateSubPath } from "../../core/asset/asset-create.js";
|
|
25
25
|
import { deriveCanonicalAssetName, resolveAssetPathFromName } from "../../core/asset/asset-spec.js";
|
|
26
26
|
import { isWithin, writeFileAtomic } from "../../core/common.js";
|
|
@@ -32,6 +32,7 @@ import { isQuiet } from "../../core/warn.js";
|
|
|
32
32
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
33
33
|
import { getHyphenatedArg, parseFlagValue } from "../../output/context.js";
|
|
34
34
|
import { readStdin } from "../../runtime.js";
|
|
35
|
+
import { buildChildEnv } from "./child-env.js";
|
|
35
36
|
/**
|
|
36
37
|
* Walk each stash's env files and return one entry per `.env` file, using the
|
|
37
38
|
* env asset spec's canonical-name logic (e.g. `env/team/prod.env` →
|
|
@@ -297,7 +298,10 @@ async function runEnvInjected(target, opts) {
|
|
|
297
298
|
}
|
|
298
299
|
process.stderr.write(`warning: ${detail} Injecting anyway (first-party stash).\n`);
|
|
299
300
|
}
|
|
300
|
-
const mergedEnv =
|
|
301
|
+
const mergedEnv = buildChildEnv(process.env, {
|
|
302
|
+
clean: opts.clean === true,
|
|
303
|
+
inherit: opts.inherit ?? [],
|
|
304
|
+
});
|
|
301
305
|
for (const [envKey, envValue] of Object.entries(envValues)) {
|
|
302
306
|
mergedEnv[envKey] = envValue;
|
|
303
307
|
}
|
|
@@ -341,7 +345,7 @@ const envRunCommand = defineCommand({
|
|
|
341
345
|
name: "run",
|
|
342
346
|
description:
|
|
343
347
|
// biome-ignore lint/suspicious/noTemplateCurlyInString: literal `${secret:NAME}` token syntax documented for users, not interpolation
|
|
344
|
-
"Run a command with the env file injected into its environment: `akm env run <ref> -- <command>`. Use `-- $SHELL` for an interactive session. Restrict which variables are injected with --only / --except. Values may embed `${secret:NAME}` tokens, replaced at run time with the sibling `secret:NAME` value from the same stash.",
|
|
348
|
+
"Run a command with the env file injected into its environment: `akm env run <ref> -- <command>`. Use `-- $SHELL` for an interactive session. Restrict which variables are injected with --only / --except. Values may embed `${secret:NAME}` tokens, replaced at run time with the sibling `secret:NAME` value from the same stash. Pass --clean to start the child with a minimal inherited environment instead of the full parent environment.",
|
|
345
349
|
},
|
|
346
350
|
args: {
|
|
347
351
|
target: { type: "positional", description: "Env ref", required: true },
|
|
@@ -350,11 +354,22 @@ const envRunCommand = defineCommand({
|
|
|
350
354
|
description: "Inject ONLY these keys (comma-separated). Mutually exclusive with --except.",
|
|
351
355
|
},
|
|
352
356
|
except: { type: "string", description: "Inject all keys EXCEPT these (comma-separated)." },
|
|
357
|
+
clean: {
|
|
358
|
+
type: "boolean",
|
|
359
|
+
description: "Start the child with a minimal inherited environment (PATH/HOME/locale/terminal basics) instead of the full parent environment.",
|
|
360
|
+
default: false,
|
|
361
|
+
},
|
|
362
|
+
inherit: {
|
|
363
|
+
type: "string",
|
|
364
|
+
description: "When used with --clean, also inherit these parent env vars (comma-separated). Ignored without --clean.",
|
|
365
|
+
},
|
|
353
366
|
},
|
|
354
367
|
run({ args }) {
|
|
355
368
|
return runWithJsonErrors(() => runEnvInjected(args.target, {
|
|
356
369
|
only: parseKeyListFlag(getHyphenatedArg(args, "only")),
|
|
357
370
|
except: parseKeyListFlag(getHyphenatedArg(args, "except")),
|
|
371
|
+
clean: getHyphenatedArg(args, "clean") === true,
|
|
372
|
+
inherit: parseKeyListFlag(getHyphenatedArg(args, "inherit")) ?? [],
|
|
358
373
|
}));
|
|
359
374
|
},
|
|
360
375
|
});
|
|
@@ -499,31 +514,23 @@ const envUnsetCommand = defineCommand({
|
|
|
499
514
|
});
|
|
500
515
|
},
|
|
501
516
|
});
|
|
502
|
-
|
|
503
|
-
// (M10) so adding a subcommand can never silently desync from `hasSubcommand`.
|
|
504
|
-
const envSubCommands = {
|
|
505
|
-
list: envListCommand,
|
|
506
|
-
path: envPathCommand,
|
|
507
|
-
export: envExportCommand,
|
|
508
|
-
run: envRunCommand,
|
|
509
|
-
create: envCreateCommand,
|
|
510
|
-
set: envSetCommand,
|
|
511
|
-
unset: envUnsetCommand,
|
|
512
|
-
remove: envRemoveCommand,
|
|
513
|
-
};
|
|
514
|
-
const ENV_SUBCOMMAND_SET = new Set(Object.keys(envSubCommands));
|
|
515
|
-
export const envCommand = defineCommand({
|
|
517
|
+
export const envCommand = defineGroupCommand({
|
|
516
518
|
meta: {
|
|
517
519
|
name: "env",
|
|
518
520
|
description: "Manage `.env` files — a group of related CONFIGURATION values for an app or service (URLs, flags, plus any credentials it needs), loaded together. Values may or may not be sensitive; akm protects them all the same (key names visible, values never in structured output). For a single sensitive value used on its own (an auth token, key, or cert), use `akm secret`.",
|
|
519
521
|
},
|
|
520
|
-
subCommands:
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
522
|
+
subCommands: {
|
|
523
|
+
list: envListCommand,
|
|
524
|
+
path: envPathCommand,
|
|
525
|
+
export: envExportCommand,
|
|
526
|
+
run: envRunCommand,
|
|
527
|
+
create: envCreateCommand,
|
|
528
|
+
set: envSetCommand,
|
|
529
|
+
unset: envUnsetCommand,
|
|
530
|
+
remove: envRemoveCommand,
|
|
531
|
+
},
|
|
532
|
+
async defaultRun() {
|
|
533
|
+
const { listKeys } = await import("./env.js");
|
|
534
|
+
output("env-list", { envs: listEnvsRecursive(listKeys) });
|
|
528
535
|
},
|
|
529
536
|
});
|
|
@@ -19,8 +19,8 @@ import { spawnSync } from "node:child_process";
|
|
|
19
19
|
import fs from "node:fs";
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import { defineCommand } from "citty";
|
|
22
|
-
import { getStringArg
|
|
23
|
-
import { output, runWithJsonErrors } from "../../cli/shared.js";
|
|
22
|
+
import { getStringArg } from "../../cli/parse-args.js";
|
|
23
|
+
import { defineGroupCommand, output, runWithJsonErrors } from "../../cli/shared.js";
|
|
24
24
|
import { deriveCanonicalAssetName } from "../../core/asset/asset-spec.js";
|
|
25
25
|
import { loadConfig } from "../../core/config/config.js";
|
|
26
26
|
import { makeSecretRef, resolveSecretPath } from "../../core/env-secret-ref.js";
|
|
@@ -29,6 +29,16 @@ import { appendEvent } from "../../core/events.js";
|
|
|
29
29
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
30
30
|
import { getHyphenatedArg } from "../../output/context.js";
|
|
31
31
|
import { readStdin } from "../../runtime.js";
|
|
32
|
+
import { buildChildEnv } from "./child-env.js";
|
|
33
|
+
function parseKeyListFlag(raw) {
|
|
34
|
+
if (raw === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
const keys = raw
|
|
37
|
+
.split(/[,\s]+/)
|
|
38
|
+
.map((k) => k.trim())
|
|
39
|
+
.filter(Boolean);
|
|
40
|
+
return keys.length > 0 ? keys : undefined;
|
|
41
|
+
}
|
|
32
42
|
/** Walk `secrets/` across all stashes, returning one entry per secret file. */
|
|
33
43
|
function listSecretsRecursive() {
|
|
34
44
|
const result = [];
|
|
@@ -152,11 +162,20 @@ const secretPathCommand = defineCommand({
|
|
|
152
162
|
const secretRunCommand = defineCommand({
|
|
153
163
|
meta: {
|
|
154
164
|
name: "run",
|
|
155
|
-
description: "Run a command with a secret's value injected into an env var: `akm secret run <ref> <VAR> -- <command>`. The value is set as $VAR in the child process only.",
|
|
165
|
+
description: "Run a command with a secret's value injected into an env var: `akm secret run <ref> <VAR> -- <command>`. The value is set as $VAR in the child process only. Pass --clean to start the child with a minimal inherited environment instead of the full parent environment.",
|
|
156
166
|
},
|
|
157
167
|
args: {
|
|
158
168
|
ref: { type: "positional", description: "Secret ref", required: true },
|
|
159
169
|
var: { type: "positional", description: "Environment variable name to inject the value into", required: true },
|
|
170
|
+
clean: {
|
|
171
|
+
type: "boolean",
|
|
172
|
+
description: "Start the child with a minimal inherited environment (PATH/HOME/locale/terminal basics) instead of the full parent environment.",
|
|
173
|
+
default: false,
|
|
174
|
+
},
|
|
175
|
+
inherit: {
|
|
176
|
+
type: "string",
|
|
177
|
+
description: "When used with --clean, also inherit these parent env vars (comma-separated). Ignored without --clean.",
|
|
178
|
+
},
|
|
160
179
|
},
|
|
161
180
|
run({ args }) {
|
|
162
181
|
return runWithJsonErrors(async () => {
|
|
@@ -181,7 +200,10 @@ const secretRunCommand = defineCommand({
|
|
|
181
200
|
throw new NotFoundError(`Secret not found: ${makeSecretRef(name, source)}`);
|
|
182
201
|
}
|
|
183
202
|
const { readValue } = await import("./secret.js");
|
|
184
|
-
const mergedEnv =
|
|
203
|
+
const mergedEnv = buildChildEnv(process.env, {
|
|
204
|
+
clean: getHyphenatedArg(args, "clean") === true,
|
|
205
|
+
inherit: parseKeyListFlag(getHyphenatedArg(args, "inherit")) ?? [],
|
|
206
|
+
});
|
|
185
207
|
mergedEnv[varName] = readValue(absPath).toString("utf8");
|
|
186
208
|
// Audit trail: record access by ref + var name only — never the value.
|
|
187
209
|
appendEvent({
|
|
@@ -233,27 +255,19 @@ const secretRemoveCommand = defineCommand({
|
|
|
233
255
|
});
|
|
234
256
|
},
|
|
235
257
|
});
|
|
236
|
-
|
|
237
|
-
// (M10) so adding a subcommand can never silently desync from `hasSubcommand`.
|
|
238
|
-
const secretSubCommands = {
|
|
239
|
-
list: secretListCommand,
|
|
240
|
-
path: secretPathCommand,
|
|
241
|
-
run: secretRunCommand,
|
|
242
|
-
set: secretSetCommand,
|
|
243
|
-
remove: secretRemoveCommand,
|
|
244
|
-
};
|
|
245
|
-
const SECRET_SUBCOMMAND_SET = new Set(Object.keys(secretSubCommands));
|
|
246
|
-
export const secretCommand = defineCommand({
|
|
258
|
+
export const secretCommand = defineGroupCommand({
|
|
247
259
|
meta: {
|
|
248
260
|
name: "secret",
|
|
249
261
|
description: "Manage secrets — a single sensitive value used on its own for authentication (an API token, a PEM private key, a TLS cert), one value per file. Names are visible; the file contents are the value and never appear in structured output. For a group of related configuration loaded together, use `akm env`.",
|
|
250
262
|
},
|
|
251
|
-
subCommands:
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
263
|
+
subCommands: {
|
|
264
|
+
list: secretListCommand,
|
|
265
|
+
path: secretPathCommand,
|
|
266
|
+
run: secretRunCommand,
|
|
267
|
+
set: secretSetCommand,
|
|
268
|
+
remove: secretRemoveCommand,
|
|
269
|
+
},
|
|
270
|
+
defaultRun() {
|
|
271
|
+
output("secret-list", { secrets: listSecretsRecursive() });
|
|
258
272
|
},
|
|
259
273
|
});
|