akm-cli 0.9.0-beta.53 → 0.9.0-beta.55
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/clack.js +56 -0
- package/dist/cli/confirm.js +1 -1
- 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/html-report.js +33 -10
- 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 +15 -1492
- 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/outcome-loop.js +18 -16
- package/dist/commands/improve/preparation.js +23 -5
- 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/curate.js +4 -4
- package/dist/commands/read/remember-cli.js +132 -137
- package/dist/commands/read/search-cli.js +7 -5
- package/dist/commands/read/search.js +7 -3
- package/dist/commands/read/show.js +3 -5
- package/dist/commands/registry-cli.js +76 -87
- package/dist/commands/sources/add-cli.js +91 -95
- package/dist/commands/sources/history.js +1 -1
- package/dist/commands/sources/init.js +12 -0
- 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 +2 -2
- package/dist/commands/tasks/default-tasks.js +12 -0
- 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/config/config.js +12 -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/core/warn.js +21 -0
- package/dist/indexer/db/db.js +72 -709
- package/dist/indexer/db/entry-mapper.js +41 -0
- package/dist/indexer/db/schema.js +516 -0
- package/dist/indexer/ensure-index.js +3 -2
- package/dist/indexer/feedback/utility-policy.js +85 -0
- package/dist/indexer/graph/graph-extraction.js +2 -1
- package/dist/indexer/index-writer-lock.js +18 -0
- package/dist/indexer/indexer.js +94 -27
- package/dist/indexer/read-preflight.js +23 -0
- package/dist/indexer/search/fts-query.js +51 -0
- package/dist/indexer/walk/walker.js +21 -13
- package/dist/integrations/agent/detect.js +9 -0
- package/dist/integrations/agent/index.js +1 -1
- package/dist/integrations/agent/spawn.js +15 -66
- package/dist/llm/client.js +12 -0
- package/dist/llm/embedder.js +26 -2
- package/dist/llm/embedders/local.js +7 -1
- package/dist/output/text/helpers.js +13 -0
- package/dist/scripts/migrate-storage.js +6903 -7424
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +49 -44
- 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 +25 -1608
- 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/backends/index.js +9 -0
- package/dist/tasks/runner.js +11 -1
- package/package.json +2 -2
- package/dist/commands/improve/homeostatic.js +0 -497
|
@@ -3,27 +3,24 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { parsePositiveIntFlag } from "../cli/parse-args.js";
|
|
6
|
-
import {
|
|
6
|
+
import { defineJsonCommand, output } from "../cli/shared.js";
|
|
7
7
|
import { DEFAULT_CONFIG, loadUserConfig, saveConfig } from "../core/config/config.js";
|
|
8
8
|
import { UsageError } from "../core/errors.js";
|
|
9
9
|
import { warn } from "../core/warn.js";
|
|
10
|
-
import { getHyphenatedArg, getHyphenatedBoolean } from "../output/context.js";
|
|
11
10
|
import { buildRegistryIndex, writeRegistryIndex } from "../registry/build-index.js";
|
|
12
11
|
import { searchRegistry } from "./read/registry-search.js";
|
|
13
12
|
export const registryCommand = defineCommand({
|
|
14
13
|
meta: { name: "registry", description: "Manage stash registries" },
|
|
15
14
|
subCommands: {
|
|
16
|
-
list:
|
|
15
|
+
list: defineJsonCommand({
|
|
17
16
|
meta: { name: "list", description: "List configured registries" },
|
|
18
|
-
run() {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
output("registry-list", { registries });
|
|
23
|
-
});
|
|
17
|
+
async run() {
|
|
18
|
+
const config = loadUserConfig();
|
|
19
|
+
const registries = config.registries ?? DEFAULT_CONFIG.registries;
|
|
20
|
+
output("registry-list", { registries });
|
|
24
21
|
},
|
|
25
22
|
}),
|
|
26
|
-
add:
|
|
23
|
+
add: defineJsonCommand({
|
|
27
24
|
meta: { name: "add", description: "Add a registry by URL" },
|
|
28
25
|
args: {
|
|
29
26
|
url: { type: "positional", description: "Registry index URL", required: true },
|
|
@@ -36,75 +33,71 @@ export const registryCommand = defineCommand({
|
|
|
36
33
|
default: false,
|
|
37
34
|
},
|
|
38
35
|
},
|
|
39
|
-
run({ args }) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"Use https:// or pass --allow-insecure if you have explicitly accepted the risk.");
|
|
49
|
-
}
|
|
50
|
-
warn("Warning: registry URL uses plain HTTP (not HTTPS). --allow-insecure was set; an on-path attacker could substitute a malicious index.");
|
|
36
|
+
async run({ args }) {
|
|
37
|
+
if (!args.url.startsWith("http")) {
|
|
38
|
+
throw new UsageError("Registry URL must start with http:// or https://");
|
|
39
|
+
}
|
|
40
|
+
if (args.url.startsWith("http://")) {
|
|
41
|
+
const allowInsecure = args["allow-insecure"];
|
|
42
|
+
if (!allowInsecure) {
|
|
43
|
+
throw new UsageError("Registry URL uses plain HTTP (not HTTPS). An on-path attacker could substitute a malicious index. " +
|
|
44
|
+
"Use https:// or pass --allow-insecure if you have explicitly accepted the risk.");
|
|
51
45
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
46
|
+
warn("Warning: registry URL uses plain HTTP (not HTTPS). --allow-insecure was set; an on-path attacker could substitute a malicious index.");
|
|
47
|
+
}
|
|
48
|
+
const config = loadUserConfig();
|
|
49
|
+
const registries = [...(config.registries ?? [])];
|
|
50
|
+
// Deduplicate by URL
|
|
51
|
+
if (registries.some((r) => r.url === args.url)) {
|
|
52
|
+
output("registry-add", { registries, added: false, message: "Registry URL already configured" });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const entry = { url: args.url };
|
|
56
|
+
if (args.name)
|
|
57
|
+
entry.name = args.name;
|
|
58
|
+
if (args.provider)
|
|
59
|
+
entry.provider = args.provider;
|
|
60
|
+
if (args.options) {
|
|
61
|
+
try {
|
|
62
|
+
entry.options = JSON.parse(args.options);
|
|
58
63
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
entry.name = args.name;
|
|
62
|
-
if (args.provider)
|
|
63
|
-
entry.provider = args.provider;
|
|
64
|
-
if (args.options) {
|
|
65
|
-
try {
|
|
66
|
-
entry.options = JSON.parse(args.options);
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
throw new UsageError("--options must be valid JSON");
|
|
70
|
-
}
|
|
64
|
+
catch {
|
|
65
|
+
throw new UsageError("--options must be valid JSON");
|
|
71
66
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
});
|
|
67
|
+
}
|
|
68
|
+
registries.push(entry);
|
|
69
|
+
saveConfig({ ...config, registries });
|
|
70
|
+
output("registry-add", { registries, added: true });
|
|
76
71
|
},
|
|
77
72
|
}),
|
|
78
|
-
remove:
|
|
73
|
+
remove: defineJsonCommand({
|
|
79
74
|
meta: { name: "remove", description: "Remove a registry by URL or name" },
|
|
80
75
|
args: {
|
|
81
76
|
target: { type: "positional", description: "Registry URL or name to remove", required: true },
|
|
82
77
|
yes: { type: "boolean", alias: "y", description: "Skip confirmation prompt", default: false },
|
|
83
78
|
},
|
|
84
|
-
run({ args }) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
yes: args.yes === true,
|
|
96
|
-
});
|
|
97
|
-
if (!confirmed) {
|
|
98
|
-
process.stderr.write("Aborted.\n");
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
const removed = registries.splice(idx, 1)[0];
|
|
102
|
-
saveConfig({ ...config, registries });
|
|
103
|
-
output("registry-remove", { registries, removed: true, entry: removed });
|
|
79
|
+
async run({ args }) {
|
|
80
|
+
const config = loadUserConfig();
|
|
81
|
+
const registries = [...(config.registries ?? [])];
|
|
82
|
+
const idx = registries.findIndex((r) => r.url === args.target || r.name === args.target);
|
|
83
|
+
if (idx === -1) {
|
|
84
|
+
output("registry-remove", { registries, removed: false, message: "No matching registry found" });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const { confirmDestructive } = await import("../cli/confirm.js");
|
|
88
|
+
const confirmed = await confirmDestructive(`Remove registry "${args.target}"? This cannot be undone.`, {
|
|
89
|
+
yes: args.yes === true,
|
|
104
90
|
});
|
|
91
|
+
if (!confirmed) {
|
|
92
|
+
process.stderr.write("Aborted.\n");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const removed = registries.splice(idx, 1)[0];
|
|
96
|
+
saveConfig({ ...config, registries });
|
|
97
|
+
output("registry-remove", { registries, removed: true, entry: removed });
|
|
105
98
|
},
|
|
106
99
|
}),
|
|
107
|
-
search:
|
|
100
|
+
search: defineJsonCommand({
|
|
108
101
|
meta: { name: "search", description: "Search enabled registries for stashes" },
|
|
109
102
|
args: {
|
|
110
103
|
query: { type: "positional", description: "Search query", required: true },
|
|
@@ -112,14 +105,12 @@ export const registryCommand = defineCommand({
|
|
|
112
105
|
assets: { type: "boolean", description: "Include asset-level search results", default: false },
|
|
113
106
|
},
|
|
114
107
|
async run({ args }) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
output("registry-search", result);
|
|
119
|
-
});
|
|
108
|
+
const limitRaw = parsePositiveIntFlag(args.limit ?? undefined);
|
|
109
|
+
const result = await searchRegistry(args.query, { limit: limitRaw, includeAssets: args.assets });
|
|
110
|
+
output("registry-search", result);
|
|
120
111
|
},
|
|
121
112
|
}),
|
|
122
|
-
"build-index":
|
|
113
|
+
"build-index": defineJsonCommand({
|
|
123
114
|
meta: { name: "build-index", description: "Build a v2 registry index from discovery and manual entries" },
|
|
124
115
|
args: {
|
|
125
116
|
out: { type: "string", description: "Output path for the generated index" },
|
|
@@ -128,21 +119,19 @@ export const registryCommand = defineCommand({
|
|
|
128
119
|
"github-api": { type: "string", description: "Override GitHub API base URL" },
|
|
129
120
|
},
|
|
130
121
|
async run({ args }) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
manualEntriesPath: result.paths.manualEntriesPath,
|
|
145
|
-
});
|
|
122
|
+
const result = await buildRegistryIndex({
|
|
123
|
+
manualEntriesPath: args.manual,
|
|
124
|
+
npmRegistryBase: args["npm-registry"],
|
|
125
|
+
githubApiBase: args["github-api"],
|
|
126
|
+
});
|
|
127
|
+
const outPath = writeRegistryIndex(result.index, args.out);
|
|
128
|
+
output("registry-build-index", {
|
|
129
|
+
outPath,
|
|
130
|
+
version: result.index.version,
|
|
131
|
+
updatedAt: result.index.updatedAt,
|
|
132
|
+
totalKits: result.counts.total,
|
|
133
|
+
counts: result.counts,
|
|
134
|
+
manualEntriesPath: result.paths.manualEntriesPath,
|
|
146
135
|
});
|
|
147
136
|
},
|
|
148
137
|
}),
|
|
@@ -3,13 +3,11 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
import * as p from "
|
|
7
|
-
import {
|
|
8
|
-
import { output, runWithJsonErrors } from "../../cli/shared.js";
|
|
6
|
+
import * as p from "../../cli/clack.js";
|
|
7
|
+
import { defineJsonCommand, output } from "../../cli/shared.js";
|
|
9
8
|
import { UsageError } from "../../core/errors.js";
|
|
10
9
|
import { appendEvent } from "../../core/events.js";
|
|
11
10
|
import { warn } from "../../core/warn.js";
|
|
12
|
-
import { getHyphenatedBoolean } from "../../output/context.js";
|
|
13
11
|
import { akmRemove } from "./installed-stashes.js";
|
|
14
12
|
import { akmAdd } from "./source-add.js";
|
|
15
13
|
import { addStash } from "./source-manage.js";
|
|
@@ -149,7 +147,7 @@ export async function auditInstalledStashForDangerousKeys(opts) {
|
|
|
149
147
|
return { blocked: true, exitCode: 1 };
|
|
150
148
|
}
|
|
151
149
|
// ── Command definition ────────────────────────────────────────────────────────
|
|
152
|
-
export const addCommand =
|
|
150
|
+
export const addCommand = defineJsonCommand({
|
|
153
151
|
meta: {
|
|
154
152
|
name: "add",
|
|
155
153
|
description: "Add a source (local directory, website, npm package, GitHub repo, git URL, or remote provider)",
|
|
@@ -181,48 +179,11 @@ export const addCommand = defineCommand({
|
|
|
181
179
|
},
|
|
182
180
|
},
|
|
183
181
|
async run({ args }) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (args.provider) {
|
|
190
|
-
if (shouldWarnOnPlainHttp(ref)) {
|
|
191
|
-
if (!allowInsecure) {
|
|
192
|
-
throw new UsageError("Source URL uses plain HTTP (not HTTPS). An on-path attacker could substitute a malicious payload. " +
|
|
193
|
-
"Use https:// or pass --allow-insecure if you have explicitly accepted the risk.", "INVALID_FLAG_VALUE", "Re-run with `--allow-insecure` only after confirming the URL is trusted.");
|
|
194
|
-
}
|
|
195
|
-
warn("Warning: source URL uses plain HTTP (not HTTPS). --allow-insecure was set; an on-path attacker could substitute a malicious payload.");
|
|
196
|
-
}
|
|
197
|
-
let parsedOptions;
|
|
198
|
-
if (args.options) {
|
|
199
|
-
try {
|
|
200
|
-
const parsed = JSON.parse(args.options);
|
|
201
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
202
|
-
throw new UsageError("--options must be a JSON object");
|
|
203
|
-
}
|
|
204
|
-
parsedOptions = parsed;
|
|
205
|
-
}
|
|
206
|
-
catch (err) {
|
|
207
|
-
if (err instanceof UsageError)
|
|
208
|
-
throw err;
|
|
209
|
-
throw new UsageError("--options must be valid JSON");
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
const result = addStash({
|
|
213
|
-
target: ref,
|
|
214
|
-
name: args.name,
|
|
215
|
-
providerType: args.provider,
|
|
216
|
-
options: parsedOptions,
|
|
217
|
-
writable: args.writable,
|
|
218
|
-
});
|
|
219
|
-
appendEvent({
|
|
220
|
-
eventType: "add",
|
|
221
|
-
metadata: { target: ref, provider: args.provider, name: args.name ?? null, writable: args.writable === true },
|
|
222
|
-
});
|
|
223
|
-
output("add", result);
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
182
|
+
const ref = args.ref.trim();
|
|
183
|
+
const allowInsecure = args["allow-insecure"];
|
|
184
|
+
const allowDangerousKeys = allowInsecure;
|
|
185
|
+
// URL with --provider → stash source (remote or git provider)
|
|
186
|
+
if (args.provider) {
|
|
226
187
|
if (shouldWarnOnPlainHttp(ref)) {
|
|
227
188
|
if (!allowInsecure) {
|
|
228
189
|
throw new UsageError("Source URL uses plain HTTP (not HTTPS). An on-path attacker could substitute a malicious payload. " +
|
|
@@ -230,64 +191,99 @@ export const addCommand = defineCommand({
|
|
|
230
191
|
}
|
|
231
192
|
warn("Warning: source URL uses plain HTTP (not HTTPS). --allow-insecure was set; an on-path attacker could substitute a malicious payload.");
|
|
232
193
|
}
|
|
233
|
-
|
|
234
|
-
if (args.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return;
|
|
194
|
+
let parsedOptions;
|
|
195
|
+
if (args.options) {
|
|
196
|
+
try {
|
|
197
|
+
const parsed = JSON.parse(args.options);
|
|
198
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
199
|
+
throw new UsageError("--options must be a JSON object");
|
|
200
|
+
}
|
|
201
|
+
parsedOptions = parsed;
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
if (err instanceof UsageError)
|
|
205
|
+
throw err;
|
|
206
|
+
throw new UsageError("--options must be valid JSON");
|
|
207
|
+
}
|
|
248
208
|
}
|
|
249
|
-
const result =
|
|
209
|
+
const result = addStash({
|
|
210
|
+
target: ref,
|
|
211
|
+
name: args.name,
|
|
212
|
+
providerType: args.provider,
|
|
213
|
+
options: parsedOptions,
|
|
214
|
+
writable: args.writable,
|
|
215
|
+
});
|
|
216
|
+
appendEvent({
|
|
217
|
+
eventType: "add",
|
|
218
|
+
metadata: { target: ref, provider: args.provider, name: args.name ?? null, writable: args.writable === true },
|
|
219
|
+
});
|
|
220
|
+
output("add", result);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (shouldWarnOnPlainHttp(ref)) {
|
|
224
|
+
if (!allowInsecure) {
|
|
225
|
+
throw new UsageError("Source URL uses plain HTTP (not HTTPS). An on-path attacker could substitute a malicious payload. " +
|
|
226
|
+
"Use https:// or pass --allow-insecure if you have explicitly accepted the risk.", "INVALID_FLAG_VALUE", "Re-run with `--allow-insecure` only after confirming the URL is trusted.");
|
|
227
|
+
}
|
|
228
|
+
warn("Warning: source URL uses plain HTTP (not HTTPS). --allow-insecure was set; an on-path attacker could substitute a malicious payload.");
|
|
229
|
+
}
|
|
230
|
+
const websiteOptions = buildWebsiteOptions(args);
|
|
231
|
+
if (args.type === "wiki") {
|
|
232
|
+
const { registerWikiSource } = await import("./source-add.js");
|
|
233
|
+
const result = await registerWikiSource({
|
|
250
234
|
ref,
|
|
251
235
|
name: args.name,
|
|
252
|
-
overrideType: args.type,
|
|
253
236
|
options: Object.keys(websiteOptions).length > 0 ? websiteOptions : undefined,
|
|
254
237
|
writable: args.writable,
|
|
255
238
|
});
|
|
256
239
|
appendEvent({
|
|
257
240
|
eventType: "add",
|
|
258
|
-
metadata: {
|
|
259
|
-
target: ref,
|
|
260
|
-
name: args.name ?? null,
|
|
261
|
-
overrideType: args.type ?? null,
|
|
262
|
-
writable: args.writable === true,
|
|
263
|
-
},
|
|
241
|
+
metadata: { target: ref, type: "wiki", name: args.name ?? null, writable: args.writable === true },
|
|
264
242
|
});
|
|
265
|
-
// ── Post-install env key audit ──────────────────────────────────────────
|
|
266
|
-
// Resolve the stash root from the install result and scan any env files
|
|
267
|
-
// for dangerous env var keys. When findings are present the install is
|
|
268
|
-
// gated: TTY → interactive confirmation prompt; non-TTY without
|
|
269
|
-
// --allow-insecure → hard failure (exit 1). Pass
|
|
270
|
-
// --allow-insecure to skip the prompt non-interactively.
|
|
271
|
-
const installedStashRoot = result.installed?.stashRoot ??
|
|
272
|
-
(result.sourceAdded && "stashRoot" in result.sourceAdded ? result.sourceAdded.stashRoot : undefined);
|
|
273
|
-
if (installedStashRoot) {
|
|
274
|
-
// Use the canonical installed id (most reliably resolved by akmRemove) rather
|
|
275
|
-
// than the raw user-supplied ref which may not match after URL normalisation.
|
|
276
|
-
const rollbackTarget = result.installed?.id ?? result.sourceAdded?.stashRoot ?? ref;
|
|
277
|
-
// The audit RETURNS its decision; we decide `process.exit` here, OUTSIDE
|
|
278
|
-
// any catch, so the abort cannot be lost to a swallowed exception (C3).
|
|
279
|
-
const decision = await auditInstalledStashForDangerousKeys({
|
|
280
|
-
installedStashRoot,
|
|
281
|
-
ref,
|
|
282
|
-
allowDangerousKeys,
|
|
283
|
-
rollbackTarget,
|
|
284
|
-
isTTY: process.stdin.isTTY === true,
|
|
285
|
-
});
|
|
286
|
-
if (decision.blocked) {
|
|
287
|
-
process.exit(decision.exitCode);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
243
|
output("add", result);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const result = await akmAdd({
|
|
247
|
+
ref,
|
|
248
|
+
name: args.name,
|
|
249
|
+
overrideType: args.type,
|
|
250
|
+
options: Object.keys(websiteOptions).length > 0 ? websiteOptions : undefined,
|
|
251
|
+
writable: args.writable,
|
|
252
|
+
});
|
|
253
|
+
appendEvent({
|
|
254
|
+
eventType: "add",
|
|
255
|
+
metadata: {
|
|
256
|
+
target: ref,
|
|
257
|
+
name: args.name ?? null,
|
|
258
|
+
overrideType: args.type ?? null,
|
|
259
|
+
writable: args.writable === true,
|
|
260
|
+
},
|
|
291
261
|
});
|
|
262
|
+
// ── Post-install env key audit ──────────────────────────────────────────
|
|
263
|
+
// Resolve the stash root from the install result and scan any env files
|
|
264
|
+
// for dangerous env var keys. When findings are present the install is
|
|
265
|
+
// gated: TTY → interactive confirmation prompt; non-TTY without
|
|
266
|
+
// --allow-insecure → hard failure (exit 1). Pass
|
|
267
|
+
// --allow-insecure to skip the prompt non-interactively.
|
|
268
|
+
const installedStashRoot = result.installed?.stashRoot ??
|
|
269
|
+
(result.sourceAdded && "stashRoot" in result.sourceAdded ? result.sourceAdded.stashRoot : undefined);
|
|
270
|
+
if (installedStashRoot) {
|
|
271
|
+
// Use the canonical installed id (most reliably resolved by akmRemove) rather
|
|
272
|
+
// than the raw user-supplied ref which may not match after URL normalisation.
|
|
273
|
+
const rollbackTarget = result.installed?.id ?? result.sourceAdded?.stashRoot ?? ref;
|
|
274
|
+
// The audit RETURNS its decision; we decide `process.exit` here, OUTSIDE
|
|
275
|
+
// any catch, so the abort cannot be lost to a swallowed exception (C3).
|
|
276
|
+
const decision = await auditInstalledStashForDangerousKeys({
|
|
277
|
+
installedStashRoot,
|
|
278
|
+
ref,
|
|
279
|
+
allowDangerousKeys,
|
|
280
|
+
rollbackTarget,
|
|
281
|
+
isTTY: process.stdin.isTTY === true,
|
|
282
|
+
});
|
|
283
|
+
if (decision.blocked) {
|
|
284
|
+
process.exit(decision.exitCode);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
output("add", result);
|
|
292
288
|
},
|
|
293
289
|
});
|
|
@@ -22,7 +22,7 @@ import { readEvents } from "../../core/events.js";
|
|
|
22
22
|
import { isoToSqlite, parseSinceToIso } from "../../core/time.js";
|
|
23
23
|
import { closeDatabase, openExistingDatabase } from "../../indexer/db/db.js";
|
|
24
24
|
import { getUsageEvents } from "../../indexer/usage/usage-events.js";
|
|
25
|
-
import { listProposals } from "../proposal/
|
|
25
|
+
import { listProposals } from "../proposal/repository.js";
|
|
26
26
|
// Proposal lifecycle event types emitted by the proposal substrate (#225).
|
|
27
27
|
const PROPOSAL_EVENT_TYPES = new Set(["promoted", "rejected"]);
|
|
28
28
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
@@ -55,7 +55,19 @@ function assertInitSandbox(stashDir, dirExplicitlyProvided) {
|
|
|
55
55
|
function isUnderTestRunner() {
|
|
56
56
|
return process.env.BUN_TEST === "1" || process.env.NODE_ENV === "test";
|
|
57
57
|
}
|
|
58
|
+
// ── Test seam ────────────────────────────────────────────────────────────────
|
|
59
|
+
// Swap-and-restore override. Inert in production; only tests call the setter.
|
|
60
|
+
let akmInitOverride;
|
|
61
|
+
/** TEST-ONLY. Swap the implementation of `akmInit`; pass undefined to restore. */
|
|
62
|
+
export function _setAkmInitForTests(fake) {
|
|
63
|
+
akmInitOverride = fake;
|
|
64
|
+
}
|
|
58
65
|
export async function akmInit(options) {
|
|
66
|
+
if (akmInitOverride)
|
|
67
|
+
return akmInitOverride(options);
|
|
68
|
+
return akmInitReal(options);
|
|
69
|
+
}
|
|
70
|
+
async function akmInitReal(options) {
|
|
59
71
|
const dirExplicitlyProvided = options?.dir != null;
|
|
60
72
|
const setDefault = options?.setDefault === true;
|
|
61
73
|
const stashDir = options?.dir ? path.resolve(options.dir) : getDefaultStashDir();
|
|
@@ -23,7 +23,7 @@ import { resolveStandardsContext } from "../../core/standards/resolve-standards-
|
|
|
23
23
|
import { info } from "../../core/warn.js";
|
|
24
24
|
import { resolveAssetPath } from "../../indexer/walk/path-resolver.js";
|
|
25
25
|
import { chatCompletion, parseEmbeddedJsonResponse } from "../../llm/client.js";
|
|
26
|
-
import { createProposal, isProposalSkipped } from "../proposal/
|
|
26
|
+
import { createProposal, isProposalSkipped } from "../proposal/repository.js";
|
|
27
27
|
// ── Constants ────────────────────────────────────────────────────────────────
|
|
28
28
|
/** Minimum gap between schema-repair attempts on the same asset. */
|
|
29
29
|
const SCHEMA_REPAIR_COOLDOWN_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
@@ -23,7 +23,7 @@ import { loadConfig } from "../../core/config/config.js";
|
|
|
23
23
|
import { UsageError } from "../../core/errors.js";
|
|
24
24
|
import { appendEvent } from "../../core/events.js";
|
|
25
25
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
26
|
-
import {
|
|
26
|
+
import { parseFlagValue } from "../../output/context.js";
|
|
27
27
|
import { resolveWritableOverride, saveGitStash } from "../../sources/providers/git.js";
|
|
28
28
|
import { pkgVersion } from "../../version.js";
|
|
29
29
|
import { akmHistory } from "./history.js";
|
|
@@ -125,8 +125,8 @@ export const upgradeCommand = defineJsonCommand({
|
|
|
125
125
|
output("upgrade", check);
|
|
126
126
|
return;
|
|
127
127
|
}
|
|
128
|
-
const skipChecksum =
|
|
129
|
-
const skipPostUpgrade =
|
|
128
|
+
const skipChecksum = args["skip-checksum"];
|
|
129
|
+
const skipPostUpgrade = args["skip-post-upgrade"];
|
|
130
130
|
const result = await performUpgrade(check, { force: args.force, skipChecksum, skipPostUpgrade });
|
|
131
131
|
output("upgrade", result);
|
|
132
132
|
},
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
* SIGINT/SIGTERM handlers in a try/finally — left byte-for-byte untouched.
|
|
26
26
|
*/
|
|
27
27
|
import path from "node:path";
|
|
28
|
-
import * as p from "@clack/prompts";
|
|
29
28
|
import { defineCommand } from "citty";
|
|
29
|
+
import * as p from "../../cli/clack.js";
|
|
30
30
|
import { defineJsonCommand, output, runWithJsonErrors } from "../../cli/shared.js";
|
|
31
31
|
import { assertFlatAssetName } from "../../core/asset/asset-create.js";
|
|
32
32
|
import { isHttpUrl } from "../../core/common.js";
|
|
@@ -60,7 +60,7 @@ export const initCommand = defineJsonCommand({
|
|
|
60
60
|
const legacyDir = parseFlagValue(process.argv, "--stashDir") ?? parseFlagValue(process.argv, "--stash-dir");
|
|
61
61
|
const result = await akmInit({
|
|
62
62
|
dir: args.dir ?? legacyDir,
|
|
63
|
-
setDefault:
|
|
63
|
+
setDefault: args["set-default"],
|
|
64
64
|
});
|
|
65
65
|
output("init", result);
|
|
66
66
|
},
|
|
@@ -76,12 +76,19 @@ const DEFAULT_DEPS = {
|
|
|
76
76
|
list: akmTasksList,
|
|
77
77
|
add: akmTasksAdd,
|
|
78
78
|
};
|
|
79
|
+
let defaultTasksOverrides;
|
|
80
|
+
/** TEST-ONLY. Swap the CI/server/register functions; pass undefined to restore. */
|
|
81
|
+
export function _setDefaultTasksForTests(fakes) {
|
|
82
|
+
defaultTasksOverrides = fakes;
|
|
83
|
+
}
|
|
79
84
|
/**
|
|
80
85
|
* Decide whether `akm setup` is running in a CI environment, where it must
|
|
81
86
|
* register NO scheduled tasks. Mirrors the common `CI=true` convention used by
|
|
82
87
|
* GitHub Actions, GitLab CI, CircleCI, etc.
|
|
83
88
|
*/
|
|
84
89
|
export function isCiEnvironment(env = process.env) {
|
|
90
|
+
if (defaultTasksOverrides?.isCiEnvironment)
|
|
91
|
+
return defaultTasksOverrides.isCiEnvironment(env);
|
|
85
92
|
const ci = env.CI;
|
|
86
93
|
if (ci === undefined || ci === null)
|
|
87
94
|
return false;
|
|
@@ -95,6 +102,8 @@ export function isCiEnvironment(env = process.env) {
|
|
|
95
102
|
* Used as the default when setup is non-interactive (no TTY / --yes / CI).
|
|
96
103
|
*/
|
|
97
104
|
export function detectServerDefault() {
|
|
105
|
+
if (defaultTasksOverrides?.detectServerDefault)
|
|
106
|
+
return defaultTasksOverrides.detectServerDefault();
|
|
98
107
|
if (os.platform() !== "linux")
|
|
99
108
|
return false;
|
|
100
109
|
// A laptop exposes a battery under /sys/class/power_supply/BAT*. Absence of
|
|
@@ -121,6 +130,9 @@ export function detectServerDefault() {
|
|
|
121
130
|
* never re-disable a user-enabled task).
|
|
122
131
|
*/
|
|
123
132
|
export async function registerDefaultTasks(options = {}) {
|
|
133
|
+
if (defaultTasksOverrides?.registerDefaultTasks) {
|
|
134
|
+
return defaultTasksOverrides.registerDefaultTasks(options);
|
|
135
|
+
}
|
|
124
136
|
if (isCiEnvironment()) {
|
|
125
137
|
return { skipped: true, reason: "ci", created: [], existing: [], toggled: [] };
|
|
126
138
|
}
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
import { defineCommand } from "citty";
|
|
16
16
|
import { parsePositiveIntFlag } from "../../cli/parse-args.js";
|
|
17
17
|
import { defineGroupCommand, defineJsonCommand, output, runWithJsonErrors } from "../../cli/shared.js";
|
|
18
|
-
import { getHyphenatedArg } from "../../output/context.js";
|
|
19
18
|
import { detectServerDefault, registerDefaultTasks } from "./default-tasks.js";
|
|
20
19
|
import { akmTasksAdd, akmTasksDoctor, akmTasksHistory, akmTasksList, akmTasksRemove, akmTasksRun, akmTasksSetEnabled, akmTasksShow, akmTasksSync, parseTaskRef, } from "./tasks.js";
|
|
21
20
|
const tasksAddCommand = defineJsonCommand({
|
|
@@ -51,7 +50,7 @@ const tasksAddCommand = defineJsonCommand({
|
|
|
51
50
|
profile: args.profile,
|
|
52
51
|
params: args.params,
|
|
53
52
|
name: args.name,
|
|
54
|
-
when_to_use:
|
|
53
|
+
when_to_use: args["when-to-use"],
|
|
55
54
|
description: args.description,
|
|
56
55
|
tags: args.tags
|
|
57
56
|
? args.tags
|
|
@@ -16,7 +16,6 @@ import { defineGroupCommand, defineJsonCommand, output, runWithJsonErrors } from
|
|
|
16
16
|
import { resolveStashDir } from "../core/common.js";
|
|
17
17
|
import { loadConfig, resolveConfiguredSources } from "../core/config/config.js";
|
|
18
18
|
import { ConfigError, UsageError } from "../core/errors.js";
|
|
19
|
-
import { getHyphenatedArg, getHyphenatedBoolean } from "../output/context.js";
|
|
20
19
|
import { akmAgentDispatch } from "./agent/agent-dispatch.js";
|
|
21
20
|
import { readKnowledgeInput } from "./read/knowledge.js";
|
|
22
21
|
import { buildWebsiteOptions } from "./sources/add-cli.js";
|
|
@@ -108,7 +107,7 @@ const wikiRemoveCommand = defineJsonCommand({
|
|
|
108
107
|
process.stderr.write("Aborted.\n");
|
|
109
108
|
return;
|
|
110
109
|
}
|
|
111
|
-
const withSources =
|
|
110
|
+
const withSources = args["with-sources"];
|
|
112
111
|
const { removeWiki } = await import("../wiki/wiki.js");
|
|
113
112
|
const { akmIndex } = await import("../indexer/indexer.js");
|
|
114
113
|
const stashDir = resolveStashDir();
|
|
@@ -244,7 +243,7 @@ const wikiIngestCommand = defineJsonCommand({
|
|
|
244
243
|
if (!profileName) {
|
|
245
244
|
throw new UsageError("akm wiki ingest requires an agent profile. Pass --profile <name> or set defaults.agent in config.", "MISSING_REQUIRED_ARGUMENT", "Available profiles are listed under profiles.agent in your config. Run `akm config get profiles.agent` to inspect.");
|
|
246
245
|
}
|
|
247
|
-
const timeoutMs = parsePositiveIntFlag(
|
|
246
|
+
const timeoutMs = parsePositiveIntFlag(args["timeout-ms"], "--timeout-ms");
|
|
248
247
|
const model = getStringArg(args, "model");
|
|
249
248
|
const { getDefaultLlmConfig } = await import("../core/config/config.js");
|
|
250
249
|
const dispatchResult = await akmAgentDispatch({
|
package/dist/core/common.js
CHANGED
|
@@ -132,9 +132,9 @@ export function writeFileAtomic(target, content, mode) {
|
|
|
132
132
|
*
|
|
133
133
|
* Throws if no valid stash directory is found.
|
|
134
134
|
*/
|
|
135
|
-
export function resolveStashDir(_options) {
|
|
135
|
+
export function resolveStashDir(_options, env = process.env) {
|
|
136
136
|
// 1. Env var override (for CI, scripts, testing)
|
|
137
|
-
const envDir =
|
|
137
|
+
const envDir = env.AKM_STASH_DIR?.trim();
|
|
138
138
|
if (envDir) {
|
|
139
139
|
return validateStashDir(envDir);
|
|
140
140
|
}
|
|
@@ -143,7 +143,7 @@ export function resolveStashDir(_options) {
|
|
|
143
143
|
if (configStashDir)
|
|
144
144
|
return validateStashDir(configStashDir);
|
|
145
145
|
// 3. Platform default — use it if it exists
|
|
146
|
-
const defaultDir = getDefaultStashDir();
|
|
146
|
+
const defaultDir = getDefaultStashDir(env);
|
|
147
147
|
if (isValidDirectory(defaultDir)) {
|
|
148
148
|
return defaultDir;
|
|
149
149
|
}
|