akm-cli 0.9.0-beta.6 → 0.9.0-rc.0
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 +663 -0
- package/README.md +12 -4
- package/dist/akm +38 -0
- package/dist/akm-migrate-storage +38 -0
- package/dist/assets/help/help-improve.md +9 -6
- package/dist/assets/hints/cli-hints-full.md +6 -5
- 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/proactive-maintenance.json +25 -0
- package/dist/assets/profiles/quick.json +1 -1
- package/dist/assets/profiles/recombine-only.json +21 -0
- package/dist/assets/profiles/reflect-distill.json +30 -0
- 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 +11 -3
- 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 +45 -16
- package/dist/assets/wiki/schema-template.md +4 -4
- package/dist/cli/clack.js +56 -0
- package/dist/cli/config-migrate.js +7 -1
- package/dist/cli/confirm.js +1 -1
- package/dist/cli/parse-args.js +46 -1
- package/dist/cli/shared.js +28 -0
- package/dist/cli.js +25 -14
- package/dist/commands/agent/agent-dispatch.js +3 -2
- package/dist/commands/agent/agent-support.js +0 -7
- package/dist/commands/agent/contribute-cli.js +26 -7
- package/dist/commands/config-cli.js +26 -13
- package/dist/commands/env/child-env.js +47 -0
- package/dist/commands/env/env-cli.js +220 -227
- package/dist/commands/env/env.js +14 -67
- package/dist/commands/env/secret-cli.js +140 -138
- package/dist/commands/feedback-cli.js +153 -147
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +76 -72
- package/dist/commands/health/advisories.js +151 -0
- package/dist/commands/health/checks.js +103 -16
- package/dist/commands/health/html-report.js +447 -81
- package/dist/commands/health/improve-metrics.js +771 -0
- package/dist/commands/health/llm-usage.js +65 -0
- package/dist/commands/health/md-report.js +103 -0
- package/dist/commands/health/metrics.js +278 -0
- package/dist/commands/health/stash-exposure.js +46 -0
- package/dist/commands/health/surfaces.js +216 -0
- package/dist/commands/health/task-runs.js +135 -0
- package/dist/commands/health/types.js +26 -0
- package/dist/commands/health/windows.js +195 -0
- package/dist/commands/health.js +91 -1083
- package/dist/commands/improve/anti-collapse.js +170 -0
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/collapse-detector.js +421 -0
- package/dist/commands/improve/consolidate/chunking.js +141 -0
- package/dist/commands/improve/consolidate/eligibility.js +64 -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 +1313 -1278
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill/content-repair.js +202 -0
- package/dist/commands/improve/distill/promote-memory.js +229 -0
- package/dist/commands/improve/distill/quality-gate.js +236 -0
- package/dist/commands/improve/distill-guards.js +127 -0
- package/dist/commands/improve/distill-promotion-policy.js +826 -167
- package/dist/commands/improve/distill.js +243 -599
- package/dist/commands/improve/eligibility.js +434 -0
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +179 -59
- package/dist/commands/improve/extract-prompt.js +55 -4
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +409 -43
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/hot-probation.js +45 -0
- package/dist/commands/improve/improve-auto-accept.js +160 -7
- package/dist/commands/improve/improve-cli.js +115 -73
- package/dist/commands/improve/improve-profiles.js +32 -8
- package/dist/commands/improve/improve-result-file.js +15 -25
- package/dist/commands/improve/improve-session.js +58 -0
- package/dist/commands/improve/improve.js +510 -2537
- package/dist/commands/improve/locks.js +154 -0
- package/dist/commands/improve/loop-stages.js +1100 -0
- package/dist/commands/improve/memory/memory-belief.js +14 -15
- package/dist/commands/improve/memory/memory-contradiction-detect.js +83 -60
- package/dist/commands/improve/memory/memory-improve.js +27 -27
- package/dist/commands/improve/outcome-loop.js +270 -0
- package/dist/commands/improve/preparation.js +2002 -0
- package/dist/commands/improve/proactive-maintenance.js +115 -0
- package/dist/commands/improve/procedural.js +398 -0
- package/dist/commands/improve/recombine.js +818 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +212 -45
- package/dist/commands/improve/salience.js +455 -0
- package/dist/commands/improve/schema-similarity-gate.js +168 -0
- package/dist/commands/improve/shared.js +51 -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/observability-cli.js +4 -4
- package/dist/commands/proposal/drain-policies.js +13 -4
- package/dist/commands/proposal/drain.js +45 -51
- package/dist/commands/proposal/legacy-import.js +115 -0
- package/dist/commands/proposal/proposal-cli.js +24 -34
- package/dist/commands/proposal/proposal.js +7 -1
- package/dist/commands/proposal/propose.js +8 -3
- package/dist/commands/proposal/repository.js +829 -0
- package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
- package/dist/commands/proposal/validators/proposals.js +93 -882
- package/dist/commands/read/curate.js +419 -103
- package/dist/commands/read/knowledge.js +10 -3
- package/dist/commands/read/remember-cli.js +133 -138
- package/dist/commands/read/search-cli.js +15 -8
- package/dist/commands/read/search.js +22 -11
- package/dist/commands/read/show.js +106 -14
- package/dist/commands/registry-cli.js +76 -87
- package/dist/commands/remember.js +11 -12
- package/dist/commands/sources/add-cli.js +91 -95
- package/dist/commands/sources/history.js +1 -1
- package/dist/commands/sources/init.js +66 -18
- package/dist/commands/sources/installed-stashes.js +11 -3
- package/dist/commands/sources/schema-repair.js +44 -46
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-add.js +7 -3
- package/dist/commands/sources/sources-cli.js +3 -3
- package/dist/commands/sources/stash-cli.js +29 -41
- package/dist/commands/sources/stash-skeleton.js +57 -8
- package/dist/commands/tasks/default-tasks.js +15 -2
- package/dist/commands/tasks/tasks-cli.js +20 -29
- package/dist/commands/tasks/tasks.js +39 -11
- package/dist/commands/wiki-cli.js +23 -38
- package/dist/commands/workflow-cli.js +15 -1
- package/dist/core/asset/asset-registry.js +3 -1
- package/dist/core/asset/asset-spec.js +21 -4
- package/dist/core/asset/frontmatter.js +188 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/authoring-rules.js +92 -0
- package/dist/core/common.js +4 -23
- package/dist/core/concurrent.js +10 -1
- package/dist/core/config/config-io.js +10 -1
- package/dist/core/config/config-migration.js +18 -40
- package/dist/core/config/config-schema.js +389 -58
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config.js +67 -22
- package/dist/core/deep-merge.js +38 -0
- package/dist/core/errors.js +1 -0
- package/dist/core/eval/rank-metrics.js +113 -0
- package/dist/core/events.js +4 -7
- package/dist/core/improve-types.js +47 -8
- package/dist/core/logs-db.js +14 -75
- package/dist/core/parse.js +36 -16
- package/dist/core/paths.js +21 -18
- 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 +770 -0
- package/dist/core/state-db.js +142 -1091
- package/dist/core/structured.js +69 -0
- package/dist/core/time.js +53 -0
- package/dist/core/warn.js +21 -0
- package/dist/core/write-source.js +37 -0
- package/dist/indexer/db/db.js +356 -780
- package/dist/indexer/db/entry-mapper.js +41 -0
- package/dist/indexer/db/graph-db.js +129 -86
- package/dist/indexer/db/llm-cache.js +2 -2
- package/dist/indexer/db/schema.js +516 -0
- package/dist/indexer/ensure-index.js +103 -24
- package/dist/indexer/feedback/utility-policy.js +75 -0
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/graph/graph-extraction.js +207 -4
- package/dist/indexer/index-writer-lock.js +106 -0
- package/dist/indexer/index-written-assets.js +105 -0
- package/dist/indexer/indexer.js +291 -310
- package/dist/indexer/passes/dir-staleness.js +114 -0
- package/dist/indexer/passes/memory-inference.js +13 -5
- package/dist/indexer/passes/metadata.js +20 -0
- package/dist/indexer/read-preflight.js +23 -0
- package/dist/indexer/search/db-search.js +89 -13
- package/dist/indexer/search/fts-query.js +51 -0
- package/dist/indexer/search/ranking-contributors.js +95 -9
- package/dist/indexer/search/ranking.js +79 -3
- package/dist/indexer/search/search-fields.js +6 -0
- package/dist/indexer/search/search-source.js +32 -21
- package/dist/indexer/search/semantic-status.js +4 -0
- package/dist/indexer/walk/matchers.js +9 -0
- package/dist/indexer/walk/walker.js +21 -13
- package/dist/integrations/agent/builders.js +39 -13
- package/dist/integrations/agent/config.js +20 -59
- package/dist/integrations/agent/detect.js +9 -0
- package/dist/integrations/agent/index.js +3 -19
- package/dist/integrations/agent/model-aliases.js +7 -2
- package/dist/integrations/agent/profiles.js +7 -1
- package/dist/integrations/agent/prompts.js +75 -9
- package/dist/integrations/agent/runner-dispatch.js +59 -0
- package/dist/integrations/agent/runner.js +13 -9
- package/dist/integrations/agent/spawn.js +69 -67
- package/dist/integrations/harnesses/claude/agent-builder.js +1 -1
- package/dist/integrations/harnesses/claude/index.js +2 -0
- package/dist/integrations/harnesses/claude/session-log.js +11 -1
- package/dist/integrations/harnesses/index.js +2 -3
- package/dist/integrations/harnesses/opencode/agent-builder.js +1 -1
- package/dist/integrations/harnesses/opencode/index.js +2 -0
- 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 +98 -17
- package/dist/integrations/harnesses/types.js +1 -0
- package/dist/integrations/session-logs/index.js +16 -0
- package/dist/llm/call-ai.js +2 -2
- package/dist/llm/client.js +57 -15
- package/dist/llm/embedder.js +67 -4
- package/dist/llm/embedders/cache.js +3 -1
- package/dist/llm/embedders/deterministic.js +66 -0
- package/dist/llm/embedders/local.js +73 -3
- package/dist/llm/feature-gate.js +16 -15
- 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 +85 -14
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/helpers.js +0 -3
- package/dist/output/shapes/passthrough.js +2 -1
- package/dist/output/text/helpers.js +29 -1
- package/dist/output/text/workflow.js +1 -0
- package/dist/registry/providers/skills-sh.js +21 -147
- package/dist/registry/providers/static-index.js +15 -157
- package/dist/registry/resolve.js +27 -9
- package/dist/runtime.js +25 -1
- package/dist/scripts/migrate-storage.js +2718 -2354
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +891 -597
- package/dist/setup/detect.js +9 -0
- package/dist/setup/legacy-config.js +106 -0
- package/dist/setup/prompt.js +57 -0
- package/dist/setup/providers.js +14 -0
- package/dist/setup/registry-stash-loader.js +12 -0
- package/dist/setup/semantic-assets.js +124 -0
- package/dist/setup/setup.js +52 -1614
- package/dist/setup/steps/connection.js +734 -0
- package/dist/setup/steps/output.js +31 -0
- package/dist/setup/steps/platforms.js +124 -0
- package/dist/setup/steps/semantic.js +27 -0
- package/dist/setup/steps/sources.js +222 -0
- package/dist/setup/steps/stashdir.js +42 -0
- package/dist/setup/steps/tasks.js +152 -0
- 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/canaries-repository.js +107 -0
- package/dist/storage/repositories/consolidation-repository.js +38 -0
- package/dist/storage/repositories/embeddings-repository.js +72 -0
- package/dist/storage/repositories/events-repository.js +187 -0
- package/dist/storage/repositories/extract-sessions-repository.js +96 -0
- package/dist/storage/repositories/improve-runs-repository.js +146 -0
- package/dist/storage/repositories/index-db.js +14 -8
- package/dist/storage/repositories/proposals-repository.js +220 -0
- package/dist/storage/repositories/recombine-repository.js +213 -0
- package/dist/storage/repositories/registry-cache.js +93 -0
- package/dist/storage/repositories/registry-index-cache-repository.js +46 -0
- package/dist/storage/repositories/task-history-repository.js +93 -0
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/tasks/backends/cron.js +1 -1
- package/dist/tasks/backends/index.js +9 -0
- 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 +15 -13
- package/dist/text-import-hook.mjs +0 -0
- package/dist/wiki/wiki.js +52 -11
- package/dist/workflows/cli.js +1 -0
- package/dist/workflows/db.js +3 -4
- package/dist/workflows/runtime/runs.js +43 -118
- package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/README.md +69 -18
- package/docs/data-and-telemetry.md +5 -4
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.9.0.md +39 -0
- package/package.json +10 -10
- package/dist/assets/tasks/core/update-stashes.yml +0 -4
- package/dist/commands/db-cli.js +0 -23
- package/dist/indexer/db/db-backup.js +0 -376
- package/dist/indexer/passes/staleness-detect.js +0 -488
|
@@ -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
|
+
}
|
|
@@ -10,17 +10,17 @@
|
|
|
10
10
|
* copy.
|
|
11
11
|
*
|
|
12
12
|
* `akm env` manages whole `.env` files under each stash's env/ directory.
|
|
13
|
-
* Values are NEVER written to stdout or structured output —
|
|
14
|
-
*
|
|
13
|
+
* Values and comment text are NEVER written to stdout or structured output —
|
|
14
|
+
* only key NAMES are surfaced (comments routinely contain commented-out
|
|
15
|
+
* credentials). akm does not manage individual entries;
|
|
15
16
|
* you edit the `.env` file yourself and akm loads it. Replaced the deprecated
|
|
16
17
|
* `vault` type (removed in 0.9.0).
|
|
17
18
|
*/
|
|
18
19
|
import { spawnSync } from "node:child_process";
|
|
19
20
|
import fs from "node:fs";
|
|
20
21
|
import path from "node:path";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import { output, runWithJsonErrors } from "../../cli/shared.js";
|
|
22
|
+
import { getStringArg } from "../../cli/parse-args.js";
|
|
23
|
+
import { defineGroupCommand, defineJsonCommand, output } 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";
|
|
@@ -30,8 +30,9 @@ import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
|
|
|
30
30
|
import { appendEvent } from "../../core/events.js";
|
|
31
31
|
import { isQuiet } from "../../core/warn.js";
|
|
32
32
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
33
|
-
import {
|
|
33
|
+
import { 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` →
|
|
@@ -69,16 +70,14 @@ function listEnvsRecursive(listKeysFn) {
|
|
|
69
70
|
}
|
|
70
71
|
return result;
|
|
71
72
|
}
|
|
72
|
-
const envListCommand =
|
|
73
|
+
const envListCommand = defineJsonCommand({
|
|
73
74
|
meta: { name: "list", description: "List all env files across all stashes with their key names (no values)" },
|
|
74
|
-
run() {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
output("env-list", { envs: listEnvsRecursive(listKeys) });
|
|
78
|
-
});
|
|
75
|
+
async run() {
|
|
76
|
+
const { listKeys } = await import("./env.js");
|
|
77
|
+
output("env-list", { envs: listEnvsRecursive(listKeys) });
|
|
79
78
|
},
|
|
80
79
|
});
|
|
81
|
-
const envCreateCommand =
|
|
80
|
+
const envCreateCommand = defineJsonCommand({
|
|
82
81
|
meta: {
|
|
83
82
|
name: "create",
|
|
84
83
|
description: "Create an env file (empty by default; seed an existing `.env` with --from-file or --from-stdin). No-op if it already exists and no source is given.",
|
|
@@ -101,58 +100,56 @@ const envCreateCommand = defineCommand({
|
|
|
101
100
|
default: false,
|
|
102
101
|
},
|
|
103
102
|
},
|
|
104
|
-
run({ args }) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
103
|
+
async run({ args }) {
|
|
104
|
+
const { createEnv, writeEnv } = await import("./env.js");
|
|
105
|
+
// `create` always targets env/, never the frozen vaults/ copy.
|
|
106
|
+
const parsed = parseEnvRef(args.name);
|
|
107
|
+
// `name` is flat; subdirectory placement is `--path`'s job.
|
|
108
|
+
assertFlatAssetName(parsed.name);
|
|
109
|
+
parsed.name = combineCreatePath(normalizeCreateSubPath(getStringArg(args, "path")), parsed.name);
|
|
110
|
+
const source = findEnvSource(parsed.origin);
|
|
111
|
+
const envRoot = path.join(source.path, "env");
|
|
112
|
+
const absPath = resolveAssetPathFromName("env", envRoot, parsed.name);
|
|
113
|
+
if (!isWithin(absPath, envRoot)) {
|
|
114
|
+
throw new UsageError(`Env name "${parsed.name}" escapes the env directory.`);
|
|
115
|
+
}
|
|
116
|
+
const fromFile = args["from-file"];
|
|
117
|
+
const fromStdin = args["from-stdin"] === true;
|
|
118
|
+
if (fromFile !== undefined && fromStdin) {
|
|
119
|
+
throw new UsageError("Pass only one of --from-file or --from-stdin.", "INVALID_FLAG_VALUE");
|
|
120
|
+
}
|
|
121
|
+
if (fromFile !== undefined || fromStdin) {
|
|
122
|
+
// Ingest path: never silently clobber an existing env file.
|
|
123
|
+
if (fs.existsSync(absPath)) {
|
|
124
|
+
throw new UsageError(`Env "${makeEnvRef(parsed.name, source)}" already exists. Remove it first (\`akm env remove\`) or edit the file directly.`, "RESOURCE_ALREADY_EXISTS");
|
|
122
125
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (fs.existsSync(
|
|
126
|
-
throw new
|
|
127
|
-
}
|
|
128
|
-
let content;
|
|
129
|
-
if (fromFile !== undefined) {
|
|
130
|
-
if (!fs.existsSync(fromFile)) {
|
|
131
|
-
throw new NotFoundError(`Source file not found: ${fromFile}`, "FILE_NOT_FOUND");
|
|
132
|
-
}
|
|
133
|
-
content = fs.readFileSync(fromFile, "utf8");
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
const MAX_ENV_BYTES = 1024 * 1024; // 1 MB
|
|
137
|
-
const buf = await readStdin(MAX_ENV_BYTES, () => new UsageError("Env file exceeds 1 MB limit.", "INVALID_FLAG_VALUE"));
|
|
138
|
-
content = buf.toString("utf8");
|
|
126
|
+
let content;
|
|
127
|
+
if (fromFile !== undefined) {
|
|
128
|
+
if (!fs.existsSync(fromFile)) {
|
|
129
|
+
throw new NotFoundError(`Source file not found: ${fromFile}`, "FILE_NOT_FOUND");
|
|
139
130
|
}
|
|
140
|
-
|
|
131
|
+
content = fs.readFileSync(fromFile, "utf8");
|
|
141
132
|
}
|
|
142
133
|
else {
|
|
143
|
-
|
|
134
|
+
const MAX_ENV_BYTES = 1024 * 1024; // 1 MB
|
|
135
|
+
const buf = await readStdin(MAX_ENV_BYTES, () => new UsageError("Env file exceeds 1 MB limit.", "INVALID_FLAG_VALUE"));
|
|
136
|
+
content = buf.toString("utf8");
|
|
144
137
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
138
|
+
writeEnv(absPath, content);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
createEnv(absPath);
|
|
142
|
+
}
|
|
143
|
+
if (args.sensitive) {
|
|
144
|
+
const markerPath = absPath.replace(/\.env$/, ".sensitive");
|
|
145
|
+
if (!fs.existsSync(markerPath)) {
|
|
146
|
+
fs.writeFileSync(markerPath, "", { mode: 0o600 });
|
|
150
147
|
}
|
|
151
|
-
|
|
152
|
-
});
|
|
148
|
+
}
|
|
149
|
+
output("env-create", { ref: makeEnvRef(parsed.name, source) });
|
|
153
150
|
},
|
|
154
151
|
});
|
|
155
|
-
const envPathCommand =
|
|
152
|
+
const envPathCommand = defineJsonCommand({
|
|
156
153
|
meta: {
|
|
157
154
|
name: "path",
|
|
158
155
|
description: "Print the absolute env file path (Docker `_FILE` convention / `--env-file`). To inject values, use `akm env run <ref> -- <cmd>` — do NOT `source` the raw file.",
|
|
@@ -161,24 +158,22 @@ const envPathCommand = defineCommand({
|
|
|
161
158
|
ref: { type: "positional", description: "Env ref", required: true },
|
|
162
159
|
quiet: { type: "boolean", alias: "q", description: "Suppress the unsafe-source warning", default: false },
|
|
163
160
|
},
|
|
164
|
-
run({ args }) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
process.stdout.write(`${absPath}\n`);
|
|
178
|
-
});
|
|
161
|
+
async run({ args }) {
|
|
162
|
+
const { name, absPath, source } = resolveEnvPath(args.ref);
|
|
163
|
+
if (!fs.existsSync(absPath)) {
|
|
164
|
+
throw new NotFoundError(`Env not found: ${makeEnvRef(name, source)}`);
|
|
165
|
+
}
|
|
166
|
+
// The raw `.env` may contain `X=$(cmd)`, which executes if `source`d.
|
|
167
|
+
// Warning goes to stderr (never contaminates the path on stdout) and is
|
|
168
|
+
// suppressed with --quiet for the legitimate `_FILE` / `--env-file` use.
|
|
169
|
+
if (args.quiet !== true) {
|
|
170
|
+
process.stderr.write(`warning: this is the raw file path. Do NOT \`source\` it (shell substitutions in the file would execute).\n` +
|
|
171
|
+
` To inject values run: akm env run ${args.ref} -- <command>\n`);
|
|
172
|
+
}
|
|
173
|
+
process.stdout.write(`${absPath}\n`);
|
|
179
174
|
},
|
|
180
175
|
});
|
|
181
|
-
const envExportCommand =
|
|
176
|
+
const envExportCommand = defineJsonCommand({
|
|
182
177
|
meta: {
|
|
183
178
|
name: "export",
|
|
184
179
|
description: "Write safe `export KEY='value'` lines to a file (mode 0600) for `source`-ing — requires --out <path>. Values are re-serialised single-quoted so a raw `.env` cannot execute on load, and are NEVER printed to stdout. To use values directly, prefer `akm env run <ref> -- <command>`.",
|
|
@@ -187,23 +182,21 @@ const envExportCommand = defineCommand({
|
|
|
187
182
|
ref: { type: "positional", description: "Env ref", required: true },
|
|
188
183
|
out: { type: "string", alias: "o", description: "Destination file (required). Written at mode 0600." },
|
|
189
184
|
},
|
|
190
|
-
run({ args }) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
output("env-export", { ref: makeEnvRef(name, source), out: resolvedOut });
|
|
206
|
-
});
|
|
185
|
+
async run({ args }) {
|
|
186
|
+
const outPath = args.out;
|
|
187
|
+
if (!outPath) {
|
|
188
|
+
throw new UsageError("`akm env export` writes to a file — pass --out <path>.\n" +
|
|
189
|
+
" To use values directly, run `akm env run <ref> -- <command>` (or `-- $SHELL` for an interactive\n" +
|
|
190
|
+
" session). export never prints values to stdout, to avoid leaking them into a captured context.", "MISSING_REQUIRED_ARGUMENT");
|
|
191
|
+
}
|
|
192
|
+
const { name, absPath, source } = resolveEnvPath(args.ref);
|
|
193
|
+
if (!fs.existsSync(absPath)) {
|
|
194
|
+
throw new NotFoundError(`Env not found: ${makeEnvRef(name, source)}`);
|
|
195
|
+
}
|
|
196
|
+
const { buildShellExportScript } = await import("./env.js");
|
|
197
|
+
const resolvedOut = path.resolve(outPath);
|
|
198
|
+
writeFileAtomic(resolvedOut, buildShellExportScript(absPath), 0o600);
|
|
199
|
+
output("env-export", { ref: makeEnvRef(name, source), out: resolvedOut });
|
|
207
200
|
},
|
|
208
201
|
});
|
|
209
202
|
/**
|
|
@@ -297,7 +290,10 @@ async function runEnvInjected(target, opts) {
|
|
|
297
290
|
}
|
|
298
291
|
process.stderr.write(`warning: ${detail} Injecting anyway (first-party stash).\n`);
|
|
299
292
|
}
|
|
300
|
-
const mergedEnv =
|
|
293
|
+
const mergedEnv = buildChildEnv(process.env, {
|
|
294
|
+
clean: opts.clean === true,
|
|
295
|
+
inherit: opts.inherit ?? [],
|
|
296
|
+
});
|
|
301
297
|
for (const [envKey, envValue] of Object.entries(envValues)) {
|
|
302
298
|
mergedEnv[envKey] = envValue;
|
|
303
299
|
}
|
|
@@ -336,12 +332,12 @@ function parseKeyListFlag(raw) {
|
|
|
336
332
|
.filter(Boolean);
|
|
337
333
|
return keys.length > 0 ? keys : undefined;
|
|
338
334
|
}
|
|
339
|
-
const envRunCommand =
|
|
335
|
+
const envRunCommand = defineJsonCommand({
|
|
340
336
|
meta: {
|
|
341
337
|
name: "run",
|
|
342
338
|
description:
|
|
343
339
|
// 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.",
|
|
340
|
+
"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
341
|
},
|
|
346
342
|
args: {
|
|
347
343
|
target: { type: "positional", description: "Env ref", required: true },
|
|
@@ -350,47 +346,56 @@ const envRunCommand = defineCommand({
|
|
|
350
346
|
description: "Inject ONLY these keys (comma-separated). Mutually exclusive with --except.",
|
|
351
347
|
},
|
|
352
348
|
except: { type: "string", description: "Inject all keys EXCEPT these (comma-separated)." },
|
|
349
|
+
clean: {
|
|
350
|
+
type: "boolean",
|
|
351
|
+
description: "Start the child with a minimal inherited environment (PATH/HOME/locale/terminal basics) instead of the full parent environment.",
|
|
352
|
+
default: false,
|
|
353
|
+
},
|
|
354
|
+
inherit: {
|
|
355
|
+
type: "string",
|
|
356
|
+
description: "When used with --clean, also inherit these parent env vars (comma-separated). Ignored without --clean.",
|
|
357
|
+
},
|
|
353
358
|
},
|
|
354
|
-
run({ args }) {
|
|
355
|
-
|
|
356
|
-
only: parseKeyListFlag(
|
|
357
|
-
except: parseKeyListFlag(
|
|
358
|
-
|
|
359
|
+
async run({ args }) {
|
|
360
|
+
await runEnvInjected(args.target, {
|
|
361
|
+
only: parseKeyListFlag(args.only),
|
|
362
|
+
except: parseKeyListFlag(args.except),
|
|
363
|
+
clean: args.clean === true,
|
|
364
|
+
inherit: parseKeyListFlag(args.inherit) ?? [],
|
|
365
|
+
});
|
|
359
366
|
},
|
|
360
367
|
});
|
|
361
|
-
const envRemoveCommand =
|
|
368
|
+
const envRemoveCommand = defineJsonCommand({
|
|
362
369
|
meta: { name: "remove", description: "Remove an env file (and its .sensitive marker, if any)" },
|
|
363
370
|
args: {
|
|
364
371
|
ref: { type: "positional", description: "Env ref", required: true },
|
|
365
372
|
yes: { type: "boolean", alias: "y", description: "Skip confirmation prompt", default: false },
|
|
366
373
|
},
|
|
367
|
-
run({ args }) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
yes: args.yes === true,
|
|
379
|
-
});
|
|
380
|
-
if (!confirmed) {
|
|
381
|
-
process.stderr.write("Aborted.\n");
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
if (!fs.existsSync(absPath)) {
|
|
385
|
-
throw new NotFoundError(`Env not found: ${makeEnvRef(parsed.name, source)}`);
|
|
386
|
-
}
|
|
387
|
-
const { removeEnv } = await import("./env.js");
|
|
388
|
-
const removed = removeEnv(absPath);
|
|
389
|
-
output("env-remove", { ref: makeEnvRef(parsed.name, source), removed });
|
|
374
|
+
async run({ args }) {
|
|
375
|
+
const parsed = parseEnvRef(args.ref);
|
|
376
|
+
const source = findEnvSource(parsed.origin);
|
|
377
|
+
const envRoot = path.join(source.path, "env");
|
|
378
|
+
const absPath = resolveAssetPathFromName("env", envRoot, parsed.name);
|
|
379
|
+
if (!isWithin(absPath, envRoot)) {
|
|
380
|
+
throw new UsageError(`Env name "${parsed.name}" escapes the env directory.`);
|
|
381
|
+
}
|
|
382
|
+
const { confirmDestructive } = await import("../../cli/confirm.js");
|
|
383
|
+
const confirmed = await confirmDestructive(`Remove env "${args.ref}"? This cannot be undone.`, {
|
|
384
|
+
yes: args.yes === true,
|
|
390
385
|
});
|
|
386
|
+
if (!confirmed) {
|
|
387
|
+
process.stderr.write("Aborted.\n");
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (!fs.existsSync(absPath)) {
|
|
391
|
+
throw new NotFoundError(`Env not found: ${makeEnvRef(parsed.name, source)}`);
|
|
392
|
+
}
|
|
393
|
+
const { removeEnv } = await import("./env.js");
|
|
394
|
+
const removed = removeEnv(absPath);
|
|
395
|
+
output("env-remove", { ref: makeEnvRef(parsed.name, source), removed });
|
|
391
396
|
},
|
|
392
397
|
});
|
|
393
|
-
const envSetCommand =
|
|
398
|
+
const envSetCommand = defineJsonCommand({
|
|
394
399
|
meta: {
|
|
395
400
|
name: "set",
|
|
396
401
|
description: "Set (create or update) a single KEY in an env file: `akm env set <ref> <KEY>`. The value is read from stdin by default (never via argv); use --from-env <VAR> or --from-file <path>. Preserves existing comments and key order; the value is never printed. Creates the env file if it does not exist.",
|
|
@@ -401,59 +406,57 @@ const envSetCommand = defineCommand({
|
|
|
401
406
|
"from-env": { type: "string", description: "Read the value from the named environment variable" },
|
|
402
407
|
"from-file": { type: "string", description: "Read the value from this file" },
|
|
403
408
|
},
|
|
404
|
-
run({ args }) {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if (fromFile
|
|
426
|
-
|
|
427
|
-
throw new NotFoundError(`File not found: ${fromFile}`, "FILE_NOT_FOUND");
|
|
428
|
-
}
|
|
429
|
-
const buf = fs.readFileSync(fromFile);
|
|
430
|
-
if (buf.byteLength > MAX_ENV_VALUE_BYTES)
|
|
431
|
-
throw new UsageError("Value exceeds the 1 MB limit.");
|
|
432
|
-
value = buf.toString("utf8");
|
|
433
|
-
}
|
|
434
|
-
else if (fromEnv !== undefined) {
|
|
435
|
-
const v = process.env[fromEnv];
|
|
436
|
-
if (v === undefined) {
|
|
437
|
-
throw new UsageError(`Environment variable "${fromEnv}" is not set.`, "INVALID_FLAG_VALUE");
|
|
438
|
-
}
|
|
439
|
-
value = v;
|
|
440
|
-
}
|
|
441
|
-
else {
|
|
442
|
-
const buf = await readStdin(MAX_ENV_VALUE_BYTES, () => new UsageError("Value exceeds the 1 MB limit."));
|
|
443
|
-
// Strip a single trailing newline so `echo "$VAL" | akm env set` is exact.
|
|
444
|
-
value = buf.toString("utf8").replace(/\n$/, "");
|
|
409
|
+
async run({ args }) {
|
|
410
|
+
const parsed = parseEnvRef(args.ref);
|
|
411
|
+
const source = findEnvSource(parsed.origin);
|
|
412
|
+
const envRoot = path.join(source.path, "env");
|
|
413
|
+
const absPath = resolveAssetPathFromName("env", envRoot, parsed.name);
|
|
414
|
+
if (!isWithin(absPath, envRoot)) {
|
|
415
|
+
throw new UsageError(`Env name "${parsed.name}" escapes the env directory.`);
|
|
416
|
+
}
|
|
417
|
+
const key = String(args.key);
|
|
418
|
+
const { ENV_KEY_RE, setEnvKey } = await import("./env.js");
|
|
419
|
+
if (!ENV_KEY_RE.test(key)) {
|
|
420
|
+
throw new UsageError(`Invalid env key "${key}". Keys match [A-Za-z_][A-Za-z0-9_]*.`, "INVALID_FLAG_VALUE");
|
|
421
|
+
}
|
|
422
|
+
const fromEnv = args["from-env"];
|
|
423
|
+
const fromFile = args["from-file"];
|
|
424
|
+
if (fromEnv !== undefined && fromFile !== undefined) {
|
|
425
|
+
throw new UsageError("Pass only one of --from-file or --from-env (or use stdin).", "INVALID_FLAG_VALUE");
|
|
426
|
+
}
|
|
427
|
+
const MAX_ENV_VALUE_BYTES = 1024 * 1024; // 1 MB
|
|
428
|
+
let value;
|
|
429
|
+
if (fromFile !== undefined) {
|
|
430
|
+
if (!fs.existsSync(fromFile)) {
|
|
431
|
+
throw new NotFoundError(`File not found: ${fromFile}`, "FILE_NOT_FOUND");
|
|
445
432
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
433
|
+
const buf = fs.readFileSync(fromFile);
|
|
434
|
+
if (buf.byteLength > MAX_ENV_VALUE_BYTES)
|
|
435
|
+
throw new UsageError("Value exceeds the 1 MB limit.");
|
|
436
|
+
value = buf.toString("utf8");
|
|
437
|
+
}
|
|
438
|
+
else if (fromEnv !== undefined) {
|
|
439
|
+
const v = process.env[fromEnv];
|
|
440
|
+
if (v === undefined) {
|
|
441
|
+
throw new UsageError(`Environment variable "${fromEnv}" is not set.`, "INVALID_FLAG_VALUE");
|
|
451
442
|
}
|
|
452
|
-
|
|
453
|
-
}
|
|
443
|
+
value = v;
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
const buf = await readStdin(MAX_ENV_VALUE_BYTES, () => new UsageError("Value exceeds the 1 MB limit."));
|
|
447
|
+
// Strip a single trailing newline so `echo "$VAL" | akm env set` is exact.
|
|
448
|
+
value = buf.toString("utf8").replace(/\n$/, "");
|
|
449
|
+
}
|
|
450
|
+
setEnvKey(absPath, key, value);
|
|
451
|
+
// Warn (never block) on process-hijacking key names, matching the env-run audit.
|
|
452
|
+
const { isDangerousEnvKey } = await import("../lint/env-key-rules.js");
|
|
453
|
+
if (isDangerousEnvKey(key) && !isQuiet()) {
|
|
454
|
+
process.stderr.write(`warning: "${key}" can influence process execution when this env is loaded via 'akm env run'.\n`);
|
|
455
|
+
}
|
|
456
|
+
output("env-set", { ref: makeEnvRef(parsed.name, source), key });
|
|
454
457
|
},
|
|
455
458
|
});
|
|
456
|
-
const envUnsetCommand =
|
|
459
|
+
const envUnsetCommand = defineJsonCommand({
|
|
457
460
|
meta: {
|
|
458
461
|
name: "unset",
|
|
459
462
|
description: "Remove one or more KEYs from an env file: `akm env unset <ref> <KEY...>`. Preserves other keys and comments. To remove the whole file, use `akm env remove`.",
|
|
@@ -464,66 +467,56 @@ const envUnsetCommand = defineCommand({
|
|
|
464
467
|
// non-required so citty doesn't block before we emit a structured error.
|
|
465
468
|
key: { type: "positional", description: "Key name(s) to remove (one or more)", required: false },
|
|
466
469
|
},
|
|
467
|
-
run({ args }) {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
output("env-unset", { ref: makeEnvRef(parsed.name, source), removed, missing });
|
|
499
|
-
});
|
|
470
|
+
async run({ args }) {
|
|
471
|
+
const parsed = parseEnvRef(args.ref);
|
|
472
|
+
const source = findEnvSource(parsed.origin);
|
|
473
|
+
const envRoot = path.join(source.path, "env");
|
|
474
|
+
const absPath = resolveAssetPathFromName("env", envRoot, parsed.name);
|
|
475
|
+
if (!isWithin(absPath, envRoot)) {
|
|
476
|
+
throw new UsageError(`Env name "${parsed.name}" escapes the env directory.`);
|
|
477
|
+
}
|
|
478
|
+
if (!fs.existsSync(absPath)) {
|
|
479
|
+
throw new NotFoundError(`Env not found: ${makeEnvRef(parsed.name, source)}`);
|
|
480
|
+
}
|
|
481
|
+
// citty puts every positional in `args._` (incl. the ref at [0]); the keys
|
|
482
|
+
// are the remaining positionals. citty also mis-captures the space-separated
|
|
483
|
+
// value of a global flag (`--format json`) as a positional, so drop any
|
|
484
|
+
// token that is actually a global flag's value (cli.ts:1335 documents this).
|
|
485
|
+
const globalFlagValues = new Set(["--format", "--shape", "--detail", "--scope", "--filter", "--target"]
|
|
486
|
+
.map((flag) => parseFlagValue(process.argv, flag))
|
|
487
|
+
.filter((v) => typeof v === "string"));
|
|
488
|
+
const keys = (Array.isArray(args._) ? args._.map(String) : [])
|
|
489
|
+
.slice(1)
|
|
490
|
+
.filter((k) => !globalFlagValues.has(k));
|
|
491
|
+
if (keys.length === 0) {
|
|
492
|
+
throw new UsageError("Usage: akm env unset <ref> <KEY...> (one or more keys).", "MISSING_REQUIRED_ARGUMENT");
|
|
493
|
+
}
|
|
494
|
+
const { ENV_KEY_RE, unsetEnvKeys } = await import("./env.js");
|
|
495
|
+
const invalid = keys.filter((k) => !ENV_KEY_RE.test(k));
|
|
496
|
+
if (invalid.length > 0) {
|
|
497
|
+
throw new UsageError(`Invalid env key(s): ${invalid.join(", ")}.`, "INVALID_FLAG_VALUE");
|
|
498
|
+
}
|
|
499
|
+
const { removed, missing } = unsetEnvKeys(absPath, keys);
|
|
500
|
+
output("env-unset", { ref: makeEnvRef(parsed.name, source), removed, missing });
|
|
500
501
|
},
|
|
501
502
|
});
|
|
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({
|
|
503
|
+
export const envCommand = defineGroupCommand({
|
|
516
504
|
meta: {
|
|
517
505
|
name: "env",
|
|
518
506
|
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
507
|
},
|
|
520
|
-
subCommands:
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
508
|
+
subCommands: {
|
|
509
|
+
list: envListCommand,
|
|
510
|
+
path: envPathCommand,
|
|
511
|
+
export: envExportCommand,
|
|
512
|
+
run: envRunCommand,
|
|
513
|
+
create: envCreateCommand,
|
|
514
|
+
set: envSetCommand,
|
|
515
|
+
unset: envUnsetCommand,
|
|
516
|
+
remove: envRemoveCommand,
|
|
517
|
+
},
|
|
518
|
+
async defaultRun() {
|
|
519
|
+
const { listKeys } = await import("./env.js");
|
|
520
|
+
output("env-list", { envs: listEnvsRecursive(listKeys) });
|
|
528
521
|
},
|
|
529
522
|
});
|