akm-cli 0.9.0-beta.54 → 0.9.0-beta.56
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/dist/cli.js +5 -3
- package/dist/commands/agent/contribute-cli.js +2 -3
- package/dist/commands/env/env-cli.js +187 -202
- package/dist/commands/env/secret-cli.js +109 -121
- package/dist/commands/feedback-cli.js +152 -155
- package/dist/commands/health/advisories.js +151 -0
- package/dist/commands/health/improve-metrics.js +754 -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/task-runs.js +135 -0
- package/dist/commands/health/types.js +18 -0
- package/dist/commands/health/windows.js +196 -0
- package/dist/commands/health.js +14 -1624
- package/dist/commands/improve/anti-collapse.js +170 -0
- package/dist/commands/improve/collapse-detector.js +3 -2
- package/dist/commands/improve/consolidate.js +636 -633
- package/dist/commands/improve/dedup.js +1 -1
- package/dist/commands/improve/distill/content-repair.js +202 -0
- package/dist/commands/improve/distill/promote-memory.js +228 -0
- package/dist/commands/improve/distill/quality-gate.js +233 -0
- package/dist/commands/improve/distill-guards.js +127 -0
- package/dist/commands/improve/distill.js +49 -575
- package/dist/commands/improve/extract-cli.js +74 -76
- package/dist/commands/improve/extract.js +6 -4
- package/dist/commands/improve/hot-probation.js +45 -0
- package/dist/commands/improve/improve-auto-accept.js +3 -2
- package/dist/commands/improve/improve-cli.js +14 -13
- package/dist/commands/improve/improve-result-file.js +2 -1
- package/dist/commands/improve/improve.js +6 -5
- package/dist/commands/improve/loop-stages.js +19 -21
- package/dist/commands/improve/preparation.js +4 -2
- package/dist/commands/improve/procedural.js +10 -31
- package/dist/commands/improve/recombine.js +19 -43
- package/dist/commands/improve/reflect.js +1 -1
- package/dist/commands/improve/schema-similarity-gate.js +168 -0
- package/dist/commands/improve/shared.js +48 -0
- package/dist/commands/observability-cli.js +4 -4
- package/dist/commands/proposal/drain-policies.js +2 -2
- package/dist/commands/proposal/drain.js +1 -1
- package/dist/commands/proposal/legacy-import.js +115 -0
- package/dist/commands/proposal/proposal-cli.js +3 -3
- package/dist/commands/proposal/proposal.js +2 -1
- package/dist/commands/proposal/propose.js +1 -1
- package/dist/commands/proposal/repository.js +829 -0
- package/dist/commands/proposal/validators/proposals.js +5 -920
- package/dist/commands/read/remember-cli.js +132 -137
- package/dist/commands/read/search-cli.js +1 -1
- package/dist/commands/registry-cli.js +76 -87
- package/dist/commands/sources/add-cli.js +90 -94
- package/dist/commands/sources/history.js +1 -1
- package/dist/commands/sources/schema-repair.js +1 -1
- package/dist/commands/sources/sources-cli.js +3 -3
- package/dist/commands/sources/stash-cli.js +1 -1
- package/dist/commands/tasks/tasks-cli.js +1 -2
- package/dist/commands/wiki-cli.js +2 -3
- package/dist/core/common.js +3 -3
- package/dist/core/config/config-schema.js +6 -0
- package/dist/core/deep-merge.js +38 -0
- package/dist/core/events.js +2 -1
- package/dist/core/logs-db.js +8 -13
- package/dist/core/paths.js +14 -14
- package/dist/core/state-db.js +13 -1140
- package/dist/indexer/db/db.js +96 -723
- package/dist/indexer/db/entry-mapper.js +41 -0
- package/dist/indexer/db/schema.js +516 -0
- package/dist/indexer/feedback/utility-policy.js +75 -0
- package/dist/indexer/graph/graph-extraction.js +2 -1
- package/dist/indexer/index-writer-lock.js +9 -0
- package/dist/indexer/indexer.js +78 -23
- package/dist/indexer/search/fts-query.js +51 -0
- package/dist/integrations/agent/spawn.js +15 -66
- package/dist/llm/embedders/cache.js +3 -1
- package/dist/output/text/helpers.js +13 -0
- package/dist/registry/resolve.js +5 -0
- package/dist/scripts/migrate-storage.js +6908 -7447
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +44 -43
- 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/semantic-assets.js +124 -0
- package/dist/setup/setup.js +24 -1607
- 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/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 +130 -0
- package/dist/storage/repositories/index-db.js +4 -7
- package/dist/storage/repositories/proposals-repository.js +220 -0
- package/dist/storage/repositories/recombine-repository.js +213 -0
- package/dist/storage/repositories/task-history-repository.js +93 -0
- package/dist/storage/sqlite-pragmas.js +3 -3
- package/dist/tasks/runner.js +2 -1
- package/package.json +1 -1
- package/dist/commands/improve/homeostatic.js +0 -497
package/dist/cli.js
CHANGED
|
@@ -69,7 +69,9 @@ import { envCommand } from "./commands/env/env-cli.js";
|
|
|
69
69
|
import { secretCommand } from "./commands/env/secret-cli.js";
|
|
70
70
|
import { feedbackCommand } from "./commands/feedback-cli.js";
|
|
71
71
|
import { graphCommand } from "./commands/graph/graph-cli.js";
|
|
72
|
-
import { akmHealth
|
|
72
|
+
import { akmHealth } from "./commands/health.js";
|
|
73
|
+
import { renderRunsDetailMd, renderWindowCompareMd } from "./commands/health/md-report.js";
|
|
74
|
+
import { parseWindowSpec } from "./commands/health/windows.js";
|
|
73
75
|
import { extractCommand } from "./commands/improve/extract-cli.js";
|
|
74
76
|
import { improveCommand } from "./commands/improve/improve-cli.js";
|
|
75
77
|
import { hintsCommand, lessonsCommand, logCommand } from "./commands/observability-cli.js";
|
|
@@ -212,8 +214,8 @@ const setupCommand = defineCommand({
|
|
|
212
214
|
async run({ args }) {
|
|
213
215
|
await runWithJsonErrors(async () => {
|
|
214
216
|
const noInit = getHyphenatedBoolean(args, "no-init");
|
|
215
|
-
const detectOnly =
|
|
216
|
-
const resetRecommended =
|
|
217
|
+
const detectOnly = args["detect-only"];
|
|
218
|
+
const resetRecommended = args["reset-recommended"];
|
|
217
219
|
if (detectOnly) {
|
|
218
220
|
// Detection only: no prompts, no writes.
|
|
219
221
|
const { runDetectOnly } = await import("./setup/setup.js");
|
|
@@ -28,7 +28,6 @@ import { EXIT_CODES, output, runWithJsonErrors } from "../../cli/shared.js";
|
|
|
28
28
|
import { assertFlatAssetName, combineCreatePath, normalizeCreateSubPath } from "../../core/asset/asset-create.js";
|
|
29
29
|
import { loadConfig } from "../../core/config/config.js";
|
|
30
30
|
import { UsageError } from "../../core/errors.js";
|
|
31
|
-
import { getHyphenatedArg } from "../../output/context.js";
|
|
32
31
|
import { akmLint } from "../lint/index.js";
|
|
33
32
|
import { akmPropose } from "../proposal/propose.js";
|
|
34
33
|
import { akmAgentDispatch } from "./agent-dispatch.js";
|
|
@@ -63,7 +62,7 @@ export const agentCommand = defineCommand({
|
|
|
63
62
|
if (!args.profile) {
|
|
64
63
|
throw new UsageError("Usage: akm agent <profile> [<agent-ref>] [--prompt <text>] [--model <model>]", "MISSING_REQUIRED_ARGUMENT", "Provide the agent profile name. Available profiles are listed in profiles.agent.");
|
|
65
64
|
}
|
|
66
|
-
const timeoutMs = parsePositiveIntFlag(
|
|
65
|
+
const timeoutMs = parsePositiveIntFlag(args["timeout-ms"], "--timeout-ms");
|
|
67
66
|
const config = loadConfig();
|
|
68
67
|
const { getDefaultLlmConfig } = await import("../../core/config/config.js");
|
|
69
68
|
// After 0.8.0 the agent block IS the loaded AkmConfig.
|
|
@@ -196,7 +195,7 @@ export const proposeCommand = defineCommand({
|
|
|
196
195
|
assertFlatAssetName(String(args.name));
|
|
197
196
|
const proposedName = combineCreatePath(normalizeCreateSubPath(getStringArg(args, "path")), String(args.name));
|
|
198
197
|
const taskText = fileFromFlag ? fs.readFileSync(path.resolve(fileFromFlag), "utf8") : (taskFromFlag ?? "");
|
|
199
|
-
const timeoutMs = parsePositiveIntFlag(
|
|
198
|
+
const timeoutMs = parsePositiveIntFlag(args["timeout-ms"], "--timeout-ms");
|
|
200
199
|
const result = await akmPropose({
|
|
201
200
|
type: String(args.type),
|
|
202
201
|
name: proposedName,
|
|
@@ -19,9 +19,8 @@
|
|
|
19
19
|
import { spawnSync } from "node:child_process";
|
|
20
20
|
import fs from "node:fs";
|
|
21
21
|
import path from "node:path";
|
|
22
|
-
import { defineCommand } from "citty";
|
|
23
22
|
import { getStringArg } from "../../cli/parse-args.js";
|
|
24
|
-
import { defineGroupCommand,
|
|
23
|
+
import { defineGroupCommand, defineJsonCommand, output } from "../../cli/shared.js";
|
|
25
24
|
import { assertFlatAssetName, combineCreatePath, normalizeCreateSubPath } from "../../core/asset/asset-create.js";
|
|
26
25
|
import { deriveCanonicalAssetName, resolveAssetPathFromName } from "../../core/asset/asset-spec.js";
|
|
27
26
|
import { isWithin, writeFileAtomic } from "../../core/common.js";
|
|
@@ -31,7 +30,7 @@ import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
|
|
|
31
30
|
import { appendEvent } from "../../core/events.js";
|
|
32
31
|
import { isQuiet } from "../../core/warn.js";
|
|
33
32
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
34
|
-
import {
|
|
33
|
+
import { parseFlagValue } from "../../output/context.js";
|
|
35
34
|
import { readStdin } from "../../runtime.js";
|
|
36
35
|
import { buildChildEnv } from "./child-env.js";
|
|
37
36
|
/**
|
|
@@ -71,16 +70,14 @@ function listEnvsRecursive(listKeysFn) {
|
|
|
71
70
|
}
|
|
72
71
|
return result;
|
|
73
72
|
}
|
|
74
|
-
const envListCommand =
|
|
73
|
+
const envListCommand = defineJsonCommand({
|
|
75
74
|
meta: { name: "list", description: "List all env files across all stashes with their key names (no values)" },
|
|
76
|
-
run() {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
output("env-list", { envs: listEnvsRecursive(listKeys) });
|
|
80
|
-
});
|
|
75
|
+
async run() {
|
|
76
|
+
const { listKeys } = await import("./env.js");
|
|
77
|
+
output("env-list", { envs: listEnvsRecursive(listKeys) });
|
|
81
78
|
},
|
|
82
79
|
});
|
|
83
|
-
const envCreateCommand =
|
|
80
|
+
const envCreateCommand = defineJsonCommand({
|
|
84
81
|
meta: {
|
|
85
82
|
name: "create",
|
|
86
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.",
|
|
@@ -103,58 +100,56 @@ const envCreateCommand = defineCommand({
|
|
|
103
100
|
default: false,
|
|
104
101
|
},
|
|
105
102
|
},
|
|
106
|
-
run({ args }) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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");
|
|
124
125
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (fs.existsSync(
|
|
128
|
-
throw new
|
|
129
|
-
}
|
|
130
|
-
let content;
|
|
131
|
-
if (fromFile !== undefined) {
|
|
132
|
-
if (!fs.existsSync(fromFile)) {
|
|
133
|
-
throw new NotFoundError(`Source file not found: ${fromFile}`, "FILE_NOT_FOUND");
|
|
134
|
-
}
|
|
135
|
-
content = fs.readFileSync(fromFile, "utf8");
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
const MAX_ENV_BYTES = 1024 * 1024; // 1 MB
|
|
139
|
-
const buf = await readStdin(MAX_ENV_BYTES, () => new UsageError("Env file exceeds 1 MB limit.", "INVALID_FLAG_VALUE"));
|
|
140
|
-
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");
|
|
141
130
|
}
|
|
142
|
-
|
|
131
|
+
content = fs.readFileSync(fromFile, "utf8");
|
|
143
132
|
}
|
|
144
133
|
else {
|
|
145
|
-
|
|
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");
|
|
146
137
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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 });
|
|
152
147
|
}
|
|
153
|
-
|
|
154
|
-
});
|
|
148
|
+
}
|
|
149
|
+
output("env-create", { ref: makeEnvRef(parsed.name, source) });
|
|
155
150
|
},
|
|
156
151
|
});
|
|
157
|
-
const envPathCommand =
|
|
152
|
+
const envPathCommand = defineJsonCommand({
|
|
158
153
|
meta: {
|
|
159
154
|
name: "path",
|
|
160
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.",
|
|
@@ -163,24 +158,22 @@ const envPathCommand = defineCommand({
|
|
|
163
158
|
ref: { type: "positional", description: "Env ref", required: true },
|
|
164
159
|
quiet: { type: "boolean", alias: "q", description: "Suppress the unsafe-source warning", default: false },
|
|
165
160
|
},
|
|
166
|
-
run({ args }) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
process.stdout.write(`${absPath}\n`);
|
|
180
|
-
});
|
|
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`);
|
|
181
174
|
},
|
|
182
175
|
});
|
|
183
|
-
const envExportCommand =
|
|
176
|
+
const envExportCommand = defineJsonCommand({
|
|
184
177
|
meta: {
|
|
185
178
|
name: "export",
|
|
186
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>`.",
|
|
@@ -189,23 +182,21 @@ const envExportCommand = defineCommand({
|
|
|
189
182
|
ref: { type: "positional", description: "Env ref", required: true },
|
|
190
183
|
out: { type: "string", alias: "o", description: "Destination file (required). Written at mode 0600." },
|
|
191
184
|
},
|
|
192
|
-
run({ args }) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
output("env-export", { ref: makeEnvRef(name, source), out: resolvedOut });
|
|
208
|
-
});
|
|
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 });
|
|
209
200
|
},
|
|
210
201
|
});
|
|
211
202
|
/**
|
|
@@ -341,7 +332,7 @@ function parseKeyListFlag(raw) {
|
|
|
341
332
|
.filter(Boolean);
|
|
342
333
|
return keys.length > 0 ? keys : undefined;
|
|
343
334
|
}
|
|
344
|
-
const envRunCommand =
|
|
335
|
+
const envRunCommand = defineJsonCommand({
|
|
345
336
|
meta: {
|
|
346
337
|
name: "run",
|
|
347
338
|
description:
|
|
@@ -365,48 +356,46 @@ const envRunCommand = defineCommand({
|
|
|
365
356
|
description: "When used with --clean, also inherit these parent env vars (comma-separated). Ignored without --clean.",
|
|
366
357
|
},
|
|
367
358
|
},
|
|
368
|
-
run({ args }) {
|
|
369
|
-
|
|
370
|
-
only: parseKeyListFlag(
|
|
371
|
-
except: parseKeyListFlag(
|
|
372
|
-
clean:
|
|
373
|
-
inherit: parseKeyListFlag(
|
|
374
|
-
})
|
|
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
|
+
});
|
|
375
366
|
},
|
|
376
367
|
});
|
|
377
|
-
const envRemoveCommand =
|
|
368
|
+
const envRemoveCommand = defineJsonCommand({
|
|
378
369
|
meta: { name: "remove", description: "Remove an env file (and its .sensitive marker, if any)" },
|
|
379
370
|
args: {
|
|
380
371
|
ref: { type: "positional", description: "Env ref", required: true },
|
|
381
372
|
yes: { type: "boolean", alias: "y", description: "Skip confirmation prompt", default: false },
|
|
382
373
|
},
|
|
383
|
-
run({ args }) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
yes: args.yes === true,
|
|
395
|
-
});
|
|
396
|
-
if (!confirmed) {
|
|
397
|
-
process.stderr.write("Aborted.\n");
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
if (!fs.existsSync(absPath)) {
|
|
401
|
-
throw new NotFoundError(`Env not found: ${makeEnvRef(parsed.name, source)}`);
|
|
402
|
-
}
|
|
403
|
-
const { removeEnv } = await import("./env.js");
|
|
404
|
-
const removed = removeEnv(absPath);
|
|
405
|
-
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,
|
|
406
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 });
|
|
407
396
|
},
|
|
408
397
|
});
|
|
409
|
-
const envSetCommand =
|
|
398
|
+
const envSetCommand = defineJsonCommand({
|
|
410
399
|
meta: {
|
|
411
400
|
name: "set",
|
|
412
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.",
|
|
@@ -417,59 +406,57 @@ const envSetCommand = defineCommand({
|
|
|
417
406
|
"from-env": { type: "string", description: "Read the value from the named environment variable" },
|
|
418
407
|
"from-file": { type: "string", description: "Read the value from this file" },
|
|
419
408
|
},
|
|
420
|
-
run({ args }) {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if (fromFile
|
|
442
|
-
|
|
443
|
-
throw new NotFoundError(`File not found: ${fromFile}`, "FILE_NOT_FOUND");
|
|
444
|
-
}
|
|
445
|
-
const buf = fs.readFileSync(fromFile);
|
|
446
|
-
if (buf.byteLength > MAX_ENV_VALUE_BYTES)
|
|
447
|
-
throw new UsageError("Value exceeds the 1 MB limit.");
|
|
448
|
-
value = buf.toString("utf8");
|
|
449
|
-
}
|
|
450
|
-
else if (fromEnv !== undefined) {
|
|
451
|
-
const v = process.env[fromEnv];
|
|
452
|
-
if (v === undefined) {
|
|
453
|
-
throw new UsageError(`Environment variable "${fromEnv}" is not set.`, "INVALID_FLAG_VALUE");
|
|
454
|
-
}
|
|
455
|
-
value = v;
|
|
456
|
-
}
|
|
457
|
-
else {
|
|
458
|
-
const buf = await readStdin(MAX_ENV_VALUE_BYTES, () => new UsageError("Value exceeds the 1 MB limit."));
|
|
459
|
-
// Strip a single trailing newline so `echo "$VAL" | akm env set` is exact.
|
|
460
|
-
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");
|
|
461
432
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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");
|
|
467
442
|
}
|
|
468
|
-
|
|
469
|
-
}
|
|
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 });
|
|
470
457
|
},
|
|
471
458
|
});
|
|
472
|
-
const envUnsetCommand =
|
|
459
|
+
const envUnsetCommand = defineJsonCommand({
|
|
473
460
|
meta: {
|
|
474
461
|
name: "unset",
|
|
475
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`.",
|
|
@@ -480,39 +467,37 @@ const envUnsetCommand = defineCommand({
|
|
|
480
467
|
// non-required so citty doesn't block before we emit a structured error.
|
|
481
468
|
key: { type: "positional", description: "Key name(s) to remove (one or more)", required: false },
|
|
482
469
|
},
|
|
483
|
-
run({ args }) {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
output("env-unset", { ref: makeEnvRef(parsed.name, source), removed, missing });
|
|
515
|
-
});
|
|
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 });
|
|
516
501
|
},
|
|
517
502
|
});
|
|
518
503
|
export const envCommand = defineGroupCommand({
|