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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
import {
|
|
5
|
-
import { output, parseAllFlagValues
|
|
4
|
+
import { getStringArg } from "../../cli/parse-args.js";
|
|
5
|
+
import { defineJsonCommand, output, parseAllFlagValues } from "../../cli/shared.js";
|
|
6
6
|
import { UsageError } from "../../core/errors.js";
|
|
7
7
|
import { appendEvent } from "../../core/events.js";
|
|
8
8
|
import { buildMemoryFrontmatter, parseDuration, readMemoryContent, resolveRememberContentArg, runAutoHeuristics, runLlmEnrich, } from "../remember.js";
|
|
@@ -26,7 +26,7 @@ async function fetchSimilarMemories(query, excludeRef) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
// ── Command definition ────────────────────────────────────────────────────────
|
|
29
|
-
export const rememberCommand =
|
|
29
|
+
export const rememberCommand = defineJsonCommand({
|
|
30
30
|
meta: {
|
|
31
31
|
name: "remember",
|
|
32
32
|
description: "Record a memory in the default stash",
|
|
@@ -102,141 +102,42 @@ export const rememberCommand = defineCommand({
|
|
|
102
102
|
},
|
|
103
103
|
},
|
|
104
104
|
async run({ args }) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (!hasStructuredArgs) {
|
|
129
|
-
// Phase 1B / Rec 7: even the zero-flag hot-path emits
|
|
130
|
-
// `captureMode: hot` + `beliefState: asserted` so user-supplied
|
|
131
|
-
// memories outrank background-derived ones during ranking.
|
|
132
|
-
const frontmatterBlock = buildMemoryFrontmatter({
|
|
133
|
-
captureMode: "hot",
|
|
134
|
-
beliefState: "asserted",
|
|
135
|
-
});
|
|
136
|
-
const contentWithFrontmatter = `${frontmatterBlock}\n${body}`;
|
|
137
|
-
// Derive the asset slug from the body (not the frontmatter block);
|
|
138
|
-
// otherwise inferAssetName would key off the leading `---` delimiter.
|
|
139
|
-
const result = await writeMarkdownAsset({
|
|
140
|
-
type: "memory",
|
|
141
|
-
content: contentWithFrontmatter,
|
|
142
|
-
name: args.name,
|
|
143
|
-
fallbackPrefix: "memory",
|
|
144
|
-
preferredName: inferAssetName(body, "memory"),
|
|
145
|
-
force: args.force,
|
|
146
|
-
target: args.target,
|
|
147
|
-
path: args.path,
|
|
148
|
-
});
|
|
149
|
-
appendEvent({
|
|
150
|
-
eventType: "remember",
|
|
151
|
-
ref: result.ref,
|
|
152
|
-
metadata: { path: result.path, force: args.force === true },
|
|
153
|
-
});
|
|
154
|
-
if (args.showSimilar) {
|
|
155
|
-
const similar = await fetchSimilarMemories(body.slice(0, 500), result.ref);
|
|
156
|
-
output("remember", { ok: true, ...result, similar });
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
output("remember", { ok: true, ...result });
|
|
160
|
-
}
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
// ── Accumulate metadata from all three modes ──────────────────────────
|
|
164
|
-
// Start with CLI args (Mode 1: always)
|
|
165
|
-
const tags = [...rawTags];
|
|
166
|
-
// --description is persisted as-is; LLM enrichment may fill it if absent.
|
|
167
|
-
let description = args.description || undefined;
|
|
168
|
-
let source = args.source;
|
|
169
|
-
let observed_at;
|
|
170
|
-
let expires;
|
|
171
|
-
let subjective;
|
|
172
|
-
// Resolve --expires to an ISO date string
|
|
173
|
-
if (args.expires) {
|
|
174
|
-
const durationMs = parseDuration(args.expires);
|
|
175
|
-
const expiresDate = new Date(Date.now() + durationMs);
|
|
176
|
-
expires = expiresDate.toISOString().slice(0, 10);
|
|
177
|
-
}
|
|
178
|
-
// Mode 2: --auto heuristics
|
|
179
|
-
if (args.auto) {
|
|
180
|
-
const auto = runAutoHeuristics(body);
|
|
181
|
-
for (const t of auto.tags) {
|
|
182
|
-
if (!tags.includes(t))
|
|
183
|
-
tags.push(t);
|
|
184
|
-
}
|
|
185
|
-
if (!source && auto.source)
|
|
186
|
-
source = auto.source;
|
|
187
|
-
if (!observed_at && auto.observed_at)
|
|
188
|
-
observed_at = auto.observed_at;
|
|
189
|
-
if (!subjective && auto.subjective)
|
|
190
|
-
subjective = auto.subjective;
|
|
191
|
-
}
|
|
192
|
-
// Mode 3: --enrich LLM (fail-soft)
|
|
193
|
-
if (args.enrich) {
|
|
194
|
-
const enriched = await runLlmEnrich(body);
|
|
195
|
-
for (const t of enriched.tags) {
|
|
196
|
-
if (!tags.includes(t))
|
|
197
|
-
tags.push(t);
|
|
198
|
-
}
|
|
199
|
-
if (!description && enriched.description)
|
|
200
|
-
description = enriched.description;
|
|
201
|
-
if (!observed_at && enriched.observed_at)
|
|
202
|
-
observed_at = enriched.observed_at;
|
|
203
|
-
}
|
|
204
|
-
// ── Required-field check (before any write) ───────────────────────────
|
|
205
|
-
// Tags remain required when the user explicitly asked for tag-bearing
|
|
206
|
-
// metadata (--tag / --enrich / --description / --source / --expires).
|
|
207
|
-
// `--auto` alone is allowed even when its heuristics derive zero tags.
|
|
208
|
-
// Scope-only writes (`akm remember "..." --user u1`) also skip this
|
|
209
|
-
// check — scope is independent metadata and a memory with only scope is
|
|
210
|
-
// valid.
|
|
211
|
-
const missing = [];
|
|
212
|
-
if (hasTagRequiringArgs && tags.length === 0)
|
|
213
|
-
missing.push("tags");
|
|
214
|
-
if (missing.length > 0) {
|
|
215
|
-
throw new UsageError(`Memory is missing required frontmatter field(s): ${missing.join(", ")}. ` +
|
|
216
|
-
"Provide them via --tag <value>, --auto (heuristics), or --enrich (LLM).");
|
|
217
|
-
}
|
|
218
|
-
// ── Build frontmatter and write ───────────────────────────────────────
|
|
219
|
-
// Phase 1B / Rec 7: the hot-path CLI write always marks the memory as
|
|
220
|
-
// `captureMode: hot` and `beliefState: asserted`. Ranking applies a
|
|
221
|
-
// hot-capture boost so user-supplied memories outrank otherwise-equal
|
|
222
|
-
// background-derived ones.
|
|
105
|
+
const body = readMemoryContent(resolveRememberContentArg(args.content));
|
|
106
|
+
// `--name` is a flat name; subdirectory placement is `--path`'s job.
|
|
107
|
+
assertFlatAssetName(args.name);
|
|
108
|
+
// Determine if the user has requested any structured metadata mode.
|
|
109
|
+
// Collect all --tag occurrences directly from process.argv because citty
|
|
110
|
+
// only exposes the last value for repeated string flags.
|
|
111
|
+
const rawTags = parseAllFlagValues("--tag");
|
|
112
|
+
// Collect scope flags. Scope alone counts as structured metadata so we
|
|
113
|
+
// emit frontmatter, but it does NOT trigger the "tags required" check —
|
|
114
|
+
// memory + scope (no tags) is a valid combination for multi-tenant use.
|
|
115
|
+
const scopeFields = {};
|
|
116
|
+
for (const k of ["user", "agent", "run", "channel"]) {
|
|
117
|
+
const v = getStringArg(args, k);
|
|
118
|
+
if (v)
|
|
119
|
+
scopeFields[k] = v;
|
|
120
|
+
}
|
|
121
|
+
const hasScope = Object.keys(scopeFields).length > 0;
|
|
122
|
+
const hasTagRequiringArgs = rawTags.length > 0 || !!args.expires || !!args.source || !!args.description;
|
|
123
|
+
const hasStructuredArgs = hasTagRequiringArgs || hasScope || args.auto;
|
|
124
|
+
if (!hasStructuredArgs) {
|
|
125
|
+
// Phase 1B / Rec 7: even the zero-flag hot-path emits
|
|
126
|
+
// `captureMode: hot` + `beliefState: asserted` so user-supplied
|
|
127
|
+
// memories outrank background-derived ones during ranking.
|
|
223
128
|
const frontmatterBlock = buildMemoryFrontmatter({
|
|
224
|
-
description,
|
|
225
|
-
tags,
|
|
226
|
-
source,
|
|
227
|
-
observed_at,
|
|
228
|
-
expires,
|
|
229
|
-
subjective,
|
|
230
129
|
captureMode: "hot",
|
|
231
130
|
beliefState: "asserted",
|
|
232
|
-
...(hasScope ? { scope: scopeFields } : {}),
|
|
233
131
|
});
|
|
234
132
|
const contentWithFrontmatter = `${frontmatterBlock}\n${body}`;
|
|
133
|
+
// Derive the asset slug from the body (not the frontmatter block);
|
|
134
|
+
// otherwise inferAssetName would key off the leading `---` delimiter.
|
|
235
135
|
const result = await writeMarkdownAsset({
|
|
236
136
|
type: "memory",
|
|
237
137
|
content: contentWithFrontmatter,
|
|
238
138
|
name: args.name,
|
|
239
139
|
fallbackPrefix: "memory",
|
|
140
|
+
preferredName: inferAssetName(body, "memory"),
|
|
240
141
|
force: args.force,
|
|
241
142
|
target: args.target,
|
|
242
143
|
path: args.path,
|
|
@@ -244,22 +145,116 @@ export const rememberCommand = defineCommand({
|
|
|
244
145
|
appendEvent({
|
|
245
146
|
eventType: "remember",
|
|
246
147
|
ref: result.ref,
|
|
247
|
-
metadata: {
|
|
248
|
-
path: result.path,
|
|
249
|
-
force: args.force === true,
|
|
250
|
-
tagCount: tags.length,
|
|
251
|
-
enriched: args.enrich === true,
|
|
252
|
-
auto: args.auto === true,
|
|
253
|
-
...(hasScope ? { scope: scopeFields } : {}),
|
|
254
|
-
},
|
|
148
|
+
metadata: { path: result.path, force: args.force === true },
|
|
255
149
|
});
|
|
256
150
|
if (args.showSimilar) {
|
|
257
|
-
const similar = await fetchSimilarMemories(
|
|
151
|
+
const similar = await fetchSimilarMemories(body.slice(0, 500), result.ref);
|
|
258
152
|
output("remember", { ok: true, ...result, similar });
|
|
259
153
|
}
|
|
260
154
|
else {
|
|
261
155
|
output("remember", { ok: true, ...result });
|
|
262
156
|
}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// ── Accumulate metadata from all three modes ──────────────────────────
|
|
160
|
+
// Start with CLI args (Mode 1: always)
|
|
161
|
+
const tags = [...rawTags];
|
|
162
|
+
// --description is persisted as-is; LLM enrichment may fill it if absent.
|
|
163
|
+
let description = args.description || undefined;
|
|
164
|
+
let source = args.source;
|
|
165
|
+
let observed_at;
|
|
166
|
+
let expires;
|
|
167
|
+
let subjective;
|
|
168
|
+
// Resolve --expires to an ISO date string
|
|
169
|
+
if (args.expires) {
|
|
170
|
+
const durationMs = parseDuration(args.expires);
|
|
171
|
+
const expiresDate = new Date(Date.now() + durationMs);
|
|
172
|
+
expires = expiresDate.toISOString().slice(0, 10);
|
|
173
|
+
}
|
|
174
|
+
// Mode 2: --auto heuristics
|
|
175
|
+
if (args.auto) {
|
|
176
|
+
const auto = runAutoHeuristics(body);
|
|
177
|
+
for (const t of auto.tags) {
|
|
178
|
+
if (!tags.includes(t))
|
|
179
|
+
tags.push(t);
|
|
180
|
+
}
|
|
181
|
+
if (!source && auto.source)
|
|
182
|
+
source = auto.source;
|
|
183
|
+
if (!observed_at && auto.observed_at)
|
|
184
|
+
observed_at = auto.observed_at;
|
|
185
|
+
if (!subjective && auto.subjective)
|
|
186
|
+
subjective = auto.subjective;
|
|
187
|
+
}
|
|
188
|
+
// Mode 3: --enrich LLM (fail-soft)
|
|
189
|
+
if (args.enrich) {
|
|
190
|
+
const enriched = await runLlmEnrich(body);
|
|
191
|
+
for (const t of enriched.tags) {
|
|
192
|
+
if (!tags.includes(t))
|
|
193
|
+
tags.push(t);
|
|
194
|
+
}
|
|
195
|
+
if (!description && enriched.description)
|
|
196
|
+
description = enriched.description;
|
|
197
|
+
if (!observed_at && enriched.observed_at)
|
|
198
|
+
observed_at = enriched.observed_at;
|
|
199
|
+
}
|
|
200
|
+
// ── Required-field check (before any write) ───────────────────────────
|
|
201
|
+
// Tags remain required when the user explicitly asked for tag-bearing
|
|
202
|
+
// metadata (--tag / --enrich / --description / --source / --expires).
|
|
203
|
+
// `--auto` alone is allowed even when its heuristics derive zero tags.
|
|
204
|
+
// Scope-only writes (`akm remember "..." --user u1`) also skip this
|
|
205
|
+
// check — scope is independent metadata and a memory with only scope is
|
|
206
|
+
// valid.
|
|
207
|
+
const missing = [];
|
|
208
|
+
if (hasTagRequiringArgs && tags.length === 0)
|
|
209
|
+
missing.push("tags");
|
|
210
|
+
if (missing.length > 0) {
|
|
211
|
+
throw new UsageError(`Memory is missing required frontmatter field(s): ${missing.join(", ")}. ` +
|
|
212
|
+
"Provide them via --tag <value>, --auto (heuristics), or --enrich (LLM).");
|
|
213
|
+
}
|
|
214
|
+
// ── Build frontmatter and write ───────────────────────────────────────
|
|
215
|
+
// Phase 1B / Rec 7: the hot-path CLI write always marks the memory as
|
|
216
|
+
// `captureMode: hot` and `beliefState: asserted`. Ranking applies a
|
|
217
|
+
// hot-capture boost so user-supplied memories outrank otherwise-equal
|
|
218
|
+
// background-derived ones.
|
|
219
|
+
const frontmatterBlock = buildMemoryFrontmatter({
|
|
220
|
+
description,
|
|
221
|
+
tags,
|
|
222
|
+
source,
|
|
223
|
+
observed_at,
|
|
224
|
+
expires,
|
|
225
|
+
subjective,
|
|
226
|
+
captureMode: "hot",
|
|
227
|
+
beliefState: "asserted",
|
|
228
|
+
...(hasScope ? { scope: scopeFields } : {}),
|
|
229
|
+
});
|
|
230
|
+
const contentWithFrontmatter = `${frontmatterBlock}\n${body}`;
|
|
231
|
+
const result = await writeMarkdownAsset({
|
|
232
|
+
type: "memory",
|
|
233
|
+
content: contentWithFrontmatter,
|
|
234
|
+
name: args.name,
|
|
235
|
+
fallbackPrefix: "memory",
|
|
236
|
+
force: args.force,
|
|
237
|
+
target: args.target,
|
|
238
|
+
path: args.path,
|
|
239
|
+
});
|
|
240
|
+
appendEvent({
|
|
241
|
+
eventType: "remember",
|
|
242
|
+
ref: result.ref,
|
|
243
|
+
metadata: {
|
|
244
|
+
path: result.path,
|
|
245
|
+
force: args.force === true,
|
|
246
|
+
tagCount: tags.length,
|
|
247
|
+
enriched: args.enrich === true,
|
|
248
|
+
auto: args.auto === true,
|
|
249
|
+
...(hasScope ? { scope: scopeFields } : {}),
|
|
250
|
+
},
|
|
263
251
|
});
|
|
252
|
+
if (args.showSimilar) {
|
|
253
|
+
const similar = await fetchSimilarMemories((body ?? args.content ?? "").slice(0, 500), result.ref);
|
|
254
|
+
output("remember", { ok: true, ...result, similar });
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
output("remember", { ok: true, ...result });
|
|
258
|
+
}
|
|
264
259
|
},
|
|
265
260
|
});
|
|
@@ -86,7 +86,7 @@ export const searchCommand = defineJsonCommand({
|
|
|
86
86
|
const includeProposed = args["include-proposed"] === true;
|
|
87
87
|
const belief = parseBeliefFilterMode(typeof args.belief === "string" ? args.belief : undefined);
|
|
88
88
|
const noProjectContext = getHyphenatedBoolean(args, "no-project-context");
|
|
89
|
-
const includeSessions =
|
|
89
|
+
const includeSessions = args["include-sessions"];
|
|
90
90
|
const result = await akmSearch({
|
|
91
91
|
query,
|
|
92
92
|
type,
|
|
@@ -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
|
}),
|