akm-cli 0.6.0-rc1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +9 -9
- package/dist/cli.js +199 -114
- package/dist/{completions.js → commands/completions.js} +1 -1
- package/dist/{config-cli.js → commands/config-cli.js} +109 -11
- package/dist/{curate.js → commands/curate.js} +8 -3
- package/dist/{info.js → commands/info.js} +15 -9
- package/dist/{init.js → commands/init.js} +4 -4
- package/dist/{install-audit.js → commands/install-audit.js} +4 -7
- package/dist/{installed-stashes.js → commands/installed-stashes.js} +77 -31
- package/dist/{migration-help.js → commands/migration-help.js} +2 -2
- package/dist/{registry-search.js → commands/registry-search.js} +8 -6
- package/dist/{remember.js → commands/remember.js} +55 -49
- package/dist/{stash-search.js → commands/search.js} +28 -69
- package/dist/{self-update.js → commands/self-update.js} +69 -3
- package/dist/{stash-show.js → commands/show.js} +104 -84
- package/dist/{stash-add.js → commands/source-add.js} +42 -32
- package/dist/{stash-clone.js → commands/source-clone.js} +12 -10
- package/dist/{stash-source-manage.js → commands/source-manage.js} +24 -24
- package/dist/{vault.js → commands/vault.js} +43 -0
- package/dist/{stash-ref.js → core/asset-ref.js} +4 -4
- package/dist/{asset-registry.js → core/asset-registry.js} +1 -1
- package/dist/{asset-spec.js → core/asset-spec.js} +1 -1
- package/dist/{config.js → core/config.js} +133 -56
- package/dist/core/errors.js +90 -0
- package/dist/{frontmatter.js → core/frontmatter.js} +5 -3
- package/dist/core/write-source.js +280 -0
- package/dist/{db-search.js → indexer/db-search.js} +25 -19
- package/dist/{db.js → indexer/db.js} +79 -47
- package/dist/{file-context.js → indexer/file-context.js} +3 -3
- package/dist/{indexer.js → indexer/indexer.js} +132 -33
- package/dist/{manifest.js → indexer/manifest.js} +10 -10
- package/dist/{matchers.js → indexer/matchers.js} +3 -6
- package/dist/{metadata.js → indexer/metadata.js} +9 -5
- package/dist/{search-source.js → indexer/search-source.js} +52 -41
- package/dist/{semantic-status.js → indexer/semantic-status.js} +2 -2
- package/dist/{walker.js → indexer/walker.js} +1 -1
- package/dist/{lockfile.js → integrations/lockfile.js} +1 -1
- package/dist/{llm-client.js → llm/client.js} +1 -1
- package/dist/{embedders → llm/embedders}/local.js +2 -2
- package/dist/{embedders → llm/embedders}/remote.js +1 -1
- package/dist/{embedders → llm/embedders}/types.js +1 -1
- package/dist/{metadata-enhance.js → llm/metadata-enhance.js} +2 -2
- package/dist/{cli-hints.js → output/cli-hints.js} +3 -0
- package/dist/{output-context.js → output/context.js} +21 -3
- package/dist/{renderers.js → output/renderers.js} +9 -65
- package/dist/{output-shapes.js → output/shapes.js} +18 -4
- package/dist/{output-text.js → output/text.js} +2 -2
- package/dist/{registry-build-index.js → registry/build-index.js} +16 -7
- package/dist/{create-provider-registry.js → registry/create-provider-registry.js} +6 -2
- package/dist/registry/factory.js +33 -0
- package/dist/{origin-resolve.js → registry/origin-resolve.js} +1 -1
- package/dist/{providers → registry/providers}/index.js +1 -1
- package/dist/{providers → registry/providers}/skills-sh.js +59 -3
- package/dist/{providers → registry/providers}/static-index.js +80 -12
- package/dist/registry/providers/types.js +25 -0
- package/dist/{registry-resolve.js → registry/resolve.js} +3 -3
- package/dist/{detect.js → setup/detect.js} +0 -27
- package/dist/{ripgrep-install.js → setup/ripgrep-install.js} +1 -1
- package/dist/{ripgrep-resolve.js → setup/ripgrep-resolve.js} +2 -2
- package/dist/{setup.js → setup/setup.js} +16 -56
- package/dist/{stash-include.js → sources/include.js} +1 -1
- package/dist/sources/provider-factory.js +36 -0
- package/dist/sources/provider.js +21 -0
- package/dist/sources/providers/filesystem.js +35 -0
- package/dist/{stash-providers → sources/providers}/git.js +53 -64
- package/dist/{stash-providers → sources/providers}/index.js +3 -4
- package/dist/sources/providers/install-types.js +14 -0
- package/dist/{stash-providers → sources/providers}/npm.js +42 -41
- package/dist/{stash-providers → sources/providers}/provider-utils.js +3 -3
- package/dist/{stash-providers → sources/providers}/sync-from-ref.js +2 -2
- package/dist/{stash-providers → sources/providers}/tar-utils.js +11 -8
- package/dist/{stash-providers → sources/providers}/website.js +29 -65
- package/dist/{stash-resolve.js → sources/resolve.js} +8 -7
- package/dist/{wiki.js → wiki/wiki.js} +34 -18
- package/dist/{workflow-authoring.js → workflows/authoring.js} +37 -14
- package/dist/{workflow-cli.js → workflows/cli.js} +2 -1
- package/dist/{workflow-db.js → workflows/db.js} +1 -1
- package/dist/workflows/document-cache.js +20 -0
- package/dist/workflows/parser.js +379 -0
- package/dist/workflows/renderer.js +78 -0
- package/dist/{workflow-runs.js → workflows/runs.js} +72 -28
- package/dist/workflows/schema.js +11 -0
- package/dist/workflows/validator.js +48 -0
- package/docs/migration/release-notes/0.6.0.md +91 -23
- package/package.json +1 -1
- package/dist/errors.js +0 -45
- package/dist/llm.js +0 -16
- package/dist/registry-factory.js +0 -19
- package/dist/ripgrep.js +0 -2
- package/dist/stash-provider-factory.js +0 -35
- package/dist/stash-provider.js +0 -3
- package/dist/stash-providers/filesystem.js +0 -71
- package/dist/stash-providers/openviking.js +0 -348
- package/dist/stash-types.js +0 -1
- package/dist/workflow-markdown.js +0 -260
- /package/dist/{common.js → core/common.js} +0 -0
- /package/dist/{markdown.js → core/markdown.js} +0 -0
- /package/dist/{paths.js → core/paths.js} +0 -0
- /package/dist/{warn.js → core/warn.js} +0 -0
- /package/dist/{search-fields.js → indexer/search-fields.js} +0 -0
- /package/dist/{usage-events.js → indexer/usage-events.js} +0 -0
- /package/dist/{github.js → integrations/github.js} +0 -0
- /package/dist/{embedder.js → llm/embedder.js} +0 -0
- /package/dist/{embedders → llm/embedders}/cache.js +0 -0
- /package/dist/{registry-provider.js → registry/types.js} +0 -0
- /package/dist/{setup-steps.js → setup/steps.js} +0 -0
- /package/dist/{registry-types.js → sources/types.js} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { getAssetTypes } from "
|
|
4
|
+
import { getAssetTypes } from "../core/asset-spec";
|
|
5
5
|
// ── Known flag values ────────────────────────────────────────────────────────
|
|
6
6
|
const FLAG_VALUES = {
|
|
7
7
|
"--format": ["json", "text", "yaml", "jsonl"],
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
import { DEFAULT_CONFIG, } from "
|
|
2
|
-
import { UsageError } from "
|
|
1
|
+
import { DEFAULT_CONFIG, } from "../core/config";
|
|
2
|
+
import { UsageError } from "../core/errors";
|
|
3
|
+
// ── Merge helpers for LLM/embedding subkey set ───────────────────────────────
|
|
4
|
+
function mergeLlmLike(base, patch) {
|
|
5
|
+
return { endpoint: "", model: "", ...(base ?? {}), ...patch };
|
|
6
|
+
}
|
|
7
|
+
function mergeLlmLikeEmbedding(base, patch) {
|
|
8
|
+
return { endpoint: "", model: "", ...(base ?? {}), ...patch };
|
|
9
|
+
}
|
|
3
10
|
export function parseConfigValue(key, value) {
|
|
4
11
|
switch (key) {
|
|
5
12
|
case "stashDir":
|
|
6
13
|
return { stashDir: requireNonEmptyString(value, key) };
|
|
14
|
+
case "defaultWriteTarget":
|
|
15
|
+
return { defaultWriteTarget: requireNonEmptyString(value, key) };
|
|
7
16
|
case "semanticSearchMode":
|
|
8
17
|
// Accept legacy boolean-style strings from CLI
|
|
9
18
|
if (value === "true")
|
|
@@ -16,12 +25,26 @@ export function parseConfigValue(key, value) {
|
|
|
16
25
|
return { semanticSearchMode: value };
|
|
17
26
|
case "embedding":
|
|
18
27
|
return { embedding: parseEmbeddingConnectionValue(value) };
|
|
28
|
+
case "embedding.endpoint":
|
|
29
|
+
return { embedding: mergeLlmLikeEmbedding(undefined, { endpoint: requireNonEmptyString(value, key) }) };
|
|
30
|
+
case "embedding.model":
|
|
31
|
+
return { embedding: mergeLlmLikeEmbedding(undefined, { model: requireNonEmptyString(value, key) }) };
|
|
32
|
+
case "embedding.apiKey":
|
|
33
|
+
return { embedding: mergeLlmLikeEmbedding(undefined, { apiKey: requireNonEmptyString(value, key) }) };
|
|
19
34
|
case "llm":
|
|
20
35
|
return { llm: parseLlmConnectionValue(value) };
|
|
36
|
+
case "llm.endpoint":
|
|
37
|
+
return { llm: mergeLlmLike(undefined, { endpoint: requireNonEmptyString(value, key) }) };
|
|
38
|
+
case "llm.model":
|
|
39
|
+
return { llm: mergeLlmLike(undefined, { model: requireNonEmptyString(value, key) }) };
|
|
40
|
+
case "llm.apiKey":
|
|
41
|
+
return { llm: mergeLlmLike(undefined, { apiKey: requireNonEmptyString(value, key) }) };
|
|
21
42
|
case "registries":
|
|
22
43
|
return { registries: parseRegistriesValue(value) };
|
|
44
|
+
case "sources":
|
|
23
45
|
case "stashes":
|
|
24
|
-
|
|
46
|
+
// "stashes" is kept as an alias for backwards-compat; both write to `sources`.
|
|
47
|
+
return { sources: parseStashesValue(value) };
|
|
25
48
|
case "output.format":
|
|
26
49
|
return { output: { format: parseOutputFormat(value) } };
|
|
27
50
|
case "output.detail":
|
|
@@ -46,16 +69,32 @@ export function getConfigValue(config, key) {
|
|
|
46
69
|
switch (key) {
|
|
47
70
|
case "stashDir":
|
|
48
71
|
return config.stashDir ?? null;
|
|
72
|
+
case "defaultWriteTarget":
|
|
73
|
+
return config.defaultWriteTarget ?? null;
|
|
49
74
|
case "semanticSearchMode":
|
|
50
75
|
return config.semanticSearchMode;
|
|
51
76
|
case "embedding":
|
|
52
77
|
return config.embedding ?? null;
|
|
78
|
+
case "embedding.endpoint":
|
|
79
|
+
return config.embedding?.endpoint ?? null;
|
|
80
|
+
case "embedding.model":
|
|
81
|
+
return config.embedding?.model ?? null;
|
|
82
|
+
case "embedding.apiKey":
|
|
83
|
+
return config.embedding?.apiKey ?? null;
|
|
53
84
|
case "llm":
|
|
54
85
|
return config.llm ?? null;
|
|
86
|
+
case "llm.endpoint":
|
|
87
|
+
return config.llm?.endpoint ?? null;
|
|
88
|
+
case "llm.model":
|
|
89
|
+
return config.llm?.model ?? null;
|
|
90
|
+
case "llm.apiKey":
|
|
91
|
+
return config.llm?.apiKey ?? null;
|
|
55
92
|
case "registries":
|
|
56
93
|
return config.registries ?? DEFAULT_CONFIG.registries ?? [];
|
|
94
|
+
case "sources":
|
|
57
95
|
case "stashes":
|
|
58
|
-
|
|
96
|
+
// "stashes" is an alias for "sources" for backwards-compat.
|
|
97
|
+
return config.sources ?? config.stashes ?? [];
|
|
59
98
|
case "output.format":
|
|
60
99
|
return config.output?.format ?? null;
|
|
61
100
|
case "output.detail":
|
|
@@ -85,6 +124,7 @@ export function setConfigValue(config, key, rawValue) {
|
|
|
85
124
|
case "embedding":
|
|
86
125
|
case "llm":
|
|
87
126
|
case "registries":
|
|
127
|
+
case "sources":
|
|
88
128
|
case "stashes":
|
|
89
129
|
case "output.format":
|
|
90
130
|
case "output.detail":
|
|
@@ -95,6 +135,38 @@ export function setConfigValue(config, key, rawValue) {
|
|
|
95
135
|
case "security.installAudit.registryWhitelist":
|
|
96
136
|
case "security.installAudit.allowedFindings":
|
|
97
137
|
return mergeConfigValue(config, parseConfigValue(key, rawValue));
|
|
138
|
+
// Subkey setters use deep-merge so sibling fields are preserved
|
|
139
|
+
case "embedding.endpoint":
|
|
140
|
+
return {
|
|
141
|
+
...config,
|
|
142
|
+
embedding: mergeLlmLikeEmbedding(config.embedding, { endpoint: requireNonEmptyString(rawValue, key) }),
|
|
143
|
+
};
|
|
144
|
+
case "embedding.model":
|
|
145
|
+
return {
|
|
146
|
+
...config,
|
|
147
|
+
embedding: mergeLlmLikeEmbedding(config.embedding, { model: requireNonEmptyString(rawValue, key) }),
|
|
148
|
+
};
|
|
149
|
+
case "embedding.apiKey":
|
|
150
|
+
return {
|
|
151
|
+
...config,
|
|
152
|
+
embedding: mergeLlmLikeEmbedding(config.embedding, { apiKey: requireNonEmptyString(rawValue, key) }),
|
|
153
|
+
};
|
|
154
|
+
case "llm.endpoint":
|
|
155
|
+
return { ...config, llm: mergeLlmLike(config.llm, { endpoint: requireNonEmptyString(rawValue, key) }) };
|
|
156
|
+
case "llm.model":
|
|
157
|
+
return { ...config, llm: mergeLlmLike(config.llm, { model: requireNonEmptyString(rawValue, key) }) };
|
|
158
|
+
case "llm.apiKey":
|
|
159
|
+
return { ...config, llm: mergeLlmLike(config.llm, { apiKey: requireNonEmptyString(rawValue, key) }) };
|
|
160
|
+
case "defaultWriteTarget": {
|
|
161
|
+
const name = requireNonEmptyString(rawValue, key);
|
|
162
|
+
const knownNames = (config.sources ?? config.stashes ?? [])
|
|
163
|
+
.map((s) => s.name)
|
|
164
|
+
.filter((n) => typeof n === "string");
|
|
165
|
+
if (knownNames.length > 0 && !knownNames.includes(name)) {
|
|
166
|
+
throw new UsageError(`Unknown source name "${name}" for defaultWriteTarget; configured source names: ${knownNames.map((n) => `"${n}"`).join(", ")}`);
|
|
167
|
+
}
|
|
168
|
+
return { ...config, defaultWriteTarget: name };
|
|
169
|
+
}
|
|
98
170
|
default:
|
|
99
171
|
throw new UsageError(`Unknown config key: ${key}`);
|
|
100
172
|
}
|
|
@@ -103,14 +175,38 @@ export function unsetConfigValue(config, key) {
|
|
|
103
175
|
switch (key) {
|
|
104
176
|
case "stashDir":
|
|
105
177
|
return { ...config, stashDir: undefined };
|
|
178
|
+
case "defaultWriteTarget":
|
|
179
|
+
return { ...config, defaultWriteTarget: undefined };
|
|
106
180
|
case "embedding":
|
|
107
181
|
return { ...config, embedding: undefined };
|
|
182
|
+
case "embedding.endpoint":
|
|
183
|
+
return { ...config, embedding: mergeLlmLikeEmbedding(config.embedding, { endpoint: "" }) };
|
|
184
|
+
case "embedding.model":
|
|
185
|
+
return { ...config, embedding: mergeLlmLikeEmbedding(config.embedding, { model: "" }) };
|
|
186
|
+
case "embedding.apiKey": {
|
|
187
|
+
if (!config.embedding)
|
|
188
|
+
return config;
|
|
189
|
+
const { apiKey: _a, ...rest } = config.embedding;
|
|
190
|
+
return { ...config, embedding: rest };
|
|
191
|
+
}
|
|
108
192
|
case "llm":
|
|
109
193
|
return { ...config, llm: undefined };
|
|
194
|
+
case "llm.endpoint":
|
|
195
|
+
return { ...config, llm: mergeLlmLike(config.llm, { endpoint: "" }) };
|
|
196
|
+
case "llm.model":
|
|
197
|
+
return { ...config, llm: mergeLlmLike(config.llm, { model: "" }) };
|
|
198
|
+
case "llm.apiKey": {
|
|
199
|
+
if (!config.llm)
|
|
200
|
+
return config;
|
|
201
|
+
const { apiKey: _b, ...restLlm } = config.llm;
|
|
202
|
+
return { ...config, llm: restLlm };
|
|
203
|
+
}
|
|
110
204
|
case "registries":
|
|
111
205
|
return { ...config, registries: undefined };
|
|
206
|
+
case "sources":
|
|
112
207
|
case "stashes":
|
|
113
|
-
|
|
208
|
+
// "stashes" is kept as an alias for backwards-compat; both clear `sources`.
|
|
209
|
+
return { ...config, sources: undefined, stashes: undefined };
|
|
114
210
|
case "output.format":
|
|
115
211
|
return { ...config, output: mergeOutputConfig(config.output, { format: undefined }) };
|
|
116
212
|
case "output.detail":
|
|
@@ -155,8 +251,10 @@ export function listConfig(config) {
|
|
|
155
251
|
output: mergeOutputConfig(DEFAULT_CONFIG.output, config.output) ?? null,
|
|
156
252
|
stashDir: config.stashDir ?? null,
|
|
157
253
|
installed: config.installed ?? [],
|
|
158
|
-
|
|
254
|
+
sources: config.sources ?? config.stashes ?? [],
|
|
159
255
|
};
|
|
256
|
+
if (config.defaultWriteTarget)
|
|
257
|
+
result.defaultWriteTarget = config.defaultWriteTarget;
|
|
160
258
|
if (config.embedding)
|
|
161
259
|
result.embedding = config.embedding;
|
|
162
260
|
if (config.llm)
|
|
@@ -349,7 +447,7 @@ function parseJsonObject(value, key, example) {
|
|
|
349
447
|
}
|
|
350
448
|
catch {
|
|
351
449
|
throw new UsageError(`Invalid value for ${key}: expected JSON object with endpoint and model` +
|
|
352
|
-
` (e.g. '{"endpoint":"${example.endpoint}","model":"${example.model}"}')
|
|
450
|
+
` (e.g. '{"endpoint":"${example.endpoint}","model":"${example.model}"}')`, "INVALID_JSON_CONFIG_VALUE");
|
|
353
451
|
}
|
|
354
452
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
355
453
|
throw new UsageError(`Invalid value for ${key}: expected a JSON object`);
|
|
@@ -388,18 +486,18 @@ function parseStashesValue(value) {
|
|
|
388
486
|
parsed = JSON.parse(value);
|
|
389
487
|
}
|
|
390
488
|
catch {
|
|
391
|
-
throw new UsageError(`Invalid value for
|
|
489
|
+
throw new UsageError(`Invalid value for sources: expected JSON array of {type, path?, url?, name?, enabled?, options?} objects`);
|
|
392
490
|
}
|
|
393
491
|
if (!Array.isArray(parsed)) {
|
|
394
|
-
throw new UsageError(`Invalid value for
|
|
492
|
+
throw new UsageError(`Invalid value for sources: expected a JSON array`);
|
|
395
493
|
}
|
|
396
494
|
return parsed.map((entry, i) => {
|
|
397
495
|
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
398
|
-
throw new UsageError(`Invalid value for
|
|
496
|
+
throw new UsageError(`Invalid value for sources[${i}]: expected an object with a "type" field`);
|
|
399
497
|
}
|
|
400
498
|
const obj = entry;
|
|
401
499
|
if (typeof obj.type !== "string" || !obj.type) {
|
|
402
|
-
throw new UsageError(`Invalid value for
|
|
500
|
+
throw new UsageError(`Invalid value for sources[${i}]: "type" is required`);
|
|
403
501
|
}
|
|
404
502
|
const result = { type: obj.type };
|
|
405
503
|
if (typeof obj.path === "string" && obj.path)
|
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
* pure helpers (`curateSearchResults`, `orderCuratedTypes`,
|
|
11
11
|
* `deriveCurateFallbackQueries`) by importing them directly.
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
13
|
+
import { UsageError } from "../core/errors";
|
|
14
|
+
import { truncateDescription } from "../output/shapes";
|
|
15
|
+
import { akmSearch, parseSearchSource } from "./search";
|
|
16
|
+
import { akmShowUnified } from "./show";
|
|
16
17
|
const CURATE_FALLBACK_FILTER_WORDS = new Set([
|
|
17
18
|
"a",
|
|
18
19
|
"an",
|
|
@@ -39,6 +40,10 @@ const DEFAULT_CURATE_LIMIT = 4;
|
|
|
39
40
|
* `options.searchResponse` is not supplied.
|
|
40
41
|
*/
|
|
41
42
|
export async function akmCurate(options) {
|
|
43
|
+
const trimmedQuery = options.query.trim();
|
|
44
|
+
if (!trimmedQuery) {
|
|
45
|
+
throw new UsageError('A curation query is required. Usage: akm curate "<task or prompt>" [--type <type>] [--limit <n>]', "MISSING_REQUIRED_ARGUMENT");
|
|
46
|
+
}
|
|
42
47
|
const limit = options.limit && options.limit > 0 ? options.limit : DEFAULT_CURATE_LIMIT;
|
|
43
48
|
const source = options.source ?? parseSearchSource("stash");
|
|
44
49
|
const searchResponse = options.searchResponse ??
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import { getAssetTypes } from "
|
|
3
|
-
import { loadConfig } from "
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { getEffectiveSemanticStatus, readSemanticStatus } from "
|
|
7
|
-
import { pkgVersion } from "
|
|
2
|
+
import { getAssetTypes } from "../core/asset-spec";
|
|
3
|
+
import { loadConfig } from "../core/config";
|
|
4
|
+
import { getDbPath } from "../core/paths";
|
|
5
|
+
import { closeDatabase, getEntryCount, getMeta, isVecAvailable, openDatabase } from "../indexer/db";
|
|
6
|
+
import { getEffectiveSemanticStatus, readSemanticStatus } from "../indexer/semantic-status";
|
|
7
|
+
import { pkgVersion } from "../version";
|
|
8
8
|
/**
|
|
9
9
|
* Assemble system info describing the current capabilities, configuration,
|
|
10
10
|
* and index state. Used by `akm info`.
|
|
@@ -29,8 +29,14 @@ export function assembleInfo(options) {
|
|
|
29
29
|
...(r.provider ? { provider: r.provider } : {}),
|
|
30
30
|
...(r.enabled !== undefined ? { enabled: r.enabled } : {}),
|
|
31
31
|
}));
|
|
32
|
-
// Stash providers
|
|
33
|
-
|
|
32
|
+
// Stash providers — prefer `sources[]`; fall back to `stashDir` when the
|
|
33
|
+
// user has not yet migrated to the sources[] config shape so that info
|
|
34
|
+
// always reflects at least one provider when a stash is configured.
|
|
35
|
+
const configuredSources = config.sources ?? config.stashes ?? [];
|
|
36
|
+
const stashesList = configuredSources.length === 0 && config.stashDir
|
|
37
|
+
? [{ type: "filesystem", path: config.stashDir, name: "primary" }]
|
|
38
|
+
: configuredSources;
|
|
39
|
+
const sourceProviders = stashesList.map((s) => ({
|
|
34
40
|
type: s.type,
|
|
35
41
|
...(s.name ? { name: s.name } : {}),
|
|
36
42
|
...(s.path ? { path: s.path } : {}),
|
|
@@ -51,7 +57,7 @@ export function assembleInfo(options) {
|
|
|
51
57
|
...(semanticRuntime?.message ? { message: semanticRuntime.message } : {}),
|
|
52
58
|
},
|
|
53
59
|
registries,
|
|
54
|
-
|
|
60
|
+
sourceProviders,
|
|
55
61
|
indexStats,
|
|
56
62
|
};
|
|
57
63
|
}
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
import { spawnSync } from "node:child_process";
|
|
8
8
|
import fs from "node:fs";
|
|
9
9
|
import path from "node:path";
|
|
10
|
-
import { TYPE_DIRS } from "
|
|
11
|
-
import { getConfigPath, loadUserConfig, saveConfig } from "
|
|
12
|
-
import { getBinDir, getDefaultStashDir } from "
|
|
13
|
-
import { ensureRg } from "
|
|
10
|
+
import { TYPE_DIRS } from "../core/asset-spec";
|
|
11
|
+
import { getConfigPath, loadUserConfig, saveConfig } from "../core/config";
|
|
12
|
+
import { getBinDir, getDefaultStashDir } from "../core/paths";
|
|
13
|
+
import { ensureRg } from "../setup/ripgrep-install";
|
|
14
14
|
export async function akmInit(options) {
|
|
15
15
|
const stashDir = options?.dir ? path.resolve(options.dir) : getDefaultStashDir();
|
|
16
16
|
let created = false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { filterNonEmptyStrings } from "
|
|
3
|
+
import { filterNonEmptyStrings, toPosix } from "../core/common";
|
|
4
4
|
const DEFAULT_INSTALL_AUDIT_CONFIG = {
|
|
5
5
|
enabled: true,
|
|
6
6
|
blockOnCritical: true,
|
|
@@ -363,12 +363,9 @@ function matchesAllowedFinding(finding, ref, allowedFindings) {
|
|
|
363
363
|
function normalizeWaiverPath(value) {
|
|
364
364
|
if (!value)
|
|
365
365
|
return value;
|
|
366
|
-
// Strip a leading
|
|
367
|
-
//
|
|
368
|
-
const normalized = path
|
|
369
|
-
.normalize(value)
|
|
370
|
-
.replace(/\\/g, "/")
|
|
371
|
-
.replace(/^\.\/+/, "");
|
|
366
|
+
// Strip a leading `./` and POSIX-ify after path.normalize so Windows path
|
|
367
|
+
// separators don't trigger spurious mismatches.
|
|
368
|
+
const normalized = toPosix(path.normalize(value)).replace(/^\.\/+/, "");
|
|
372
369
|
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
373
370
|
}
|
|
374
371
|
function addUrlLabels(labels, rawUrl) {
|
|
@@ -6,36 +6,38 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import path from "node:path";
|
|
9
|
-
import { resolveStashDir } from "
|
|
10
|
-
import { loadConfig } from "
|
|
11
|
-
import { NotFoundError, UsageError } from "
|
|
12
|
-
import { akmIndex } from "
|
|
9
|
+
import { resolveStashDir } from "../core/common";
|
|
10
|
+
import { loadConfig } from "../core/config";
|
|
11
|
+
import { NotFoundError, UsageError } from "../core/errors";
|
|
12
|
+
import { akmIndex } from "../indexer/indexer";
|
|
13
|
+
import { removeLockEntry, upsertLockEntry } from "../integrations/lockfile";
|
|
14
|
+
import { parseRegistryRef } from "../registry/resolve";
|
|
15
|
+
import { syncFromRef } from "../sources/providers/sync-from-ref";
|
|
16
|
+
import { ensureWebsiteMirror } from "../sources/providers/website";
|
|
13
17
|
import { auditInstallCandidate, deriveRegistryLabels, enforceRegistryInstallPolicy, formatInstallAuditFailure, } from "./install-audit";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { removeInstalledRegistryEntry, upsertInstalledRegistryEntry } from "./stash-add";
|
|
17
|
-
import { syncFromRef } from "./stash-providers/sync-from-ref";
|
|
18
|
-
import { removeStash } from "./stash-source-manage";
|
|
18
|
+
import { removeInstalledRegistryEntry, upsertInstalledRegistryEntry } from "./source-add";
|
|
19
|
+
import { removeStash } from "./source-manage";
|
|
19
20
|
export async function akmListSources(input) {
|
|
20
21
|
const stashDir = input?.stashDir ?? resolveStashDir();
|
|
21
22
|
const config = loadConfig();
|
|
22
23
|
const kindFilter = input?.kind;
|
|
23
24
|
const sources = [];
|
|
24
|
-
// Stash entries
|
|
25
|
-
for
|
|
26
|
-
|
|
27
|
-
const kind =
|
|
25
|
+
// Stash entries — each entry exposes its provider type as kind (spec §2.1).
|
|
26
|
+
// Writable defaults: true for filesystem, false for git/npm/website (CLAUDE.md "Writes").
|
|
27
|
+
for (const stash of config.sources ?? config.stashes ?? []) {
|
|
28
|
+
const kind = stash.type ?? "filesystem";
|
|
28
29
|
if (kindFilter && !kindFilter.includes(kind))
|
|
29
30
|
continue;
|
|
31
|
+
const isFilesystem = kind === "filesystem";
|
|
32
|
+
const writableDefault = isFilesystem;
|
|
30
33
|
const name = stash.name ?? stash.path ?? stash.url ?? "unknown";
|
|
31
34
|
sources.push({
|
|
32
35
|
name,
|
|
33
36
|
kind,
|
|
34
37
|
wiki: stash.wikiName,
|
|
35
38
|
path: stash.path,
|
|
36
|
-
provider:
|
|
37
|
-
|
|
38
|
-
writable: stash.writable === true,
|
|
39
|
+
provider: stash.url != null ? stash.type : undefined,
|
|
40
|
+
writable: stash.writable !== undefined ? stash.writable : writableDefault,
|
|
39
41
|
status: { exists: stash.path ? directoryExists(stash.path) : true },
|
|
40
42
|
});
|
|
41
43
|
}
|
|
@@ -51,7 +53,6 @@ export async function akmListSources(input) {
|
|
|
51
53
|
path: entry.stashRoot,
|
|
52
54
|
ref: entry.ref,
|
|
53
55
|
version: entry.resolvedVersion,
|
|
54
|
-
updatable: true,
|
|
55
56
|
writable: entry.writable === true,
|
|
56
57
|
status: { exists: directoryExists(entry.stashRoot) },
|
|
57
58
|
});
|
|
@@ -91,7 +92,7 @@ export async function akmRemove(input) {
|
|
|
91
92
|
stashRoot: entry.stashRoot,
|
|
92
93
|
},
|
|
93
94
|
config: {
|
|
94
|
-
|
|
95
|
+
sourceCount: (updatedConfig.sources ?? updatedConfig.stashes ?? []).length,
|
|
95
96
|
installedKitCount: updatedConfig.installed?.length ?? 0,
|
|
96
97
|
},
|
|
97
98
|
index: {
|
|
@@ -105,7 +106,7 @@ export async function akmRemove(input) {
|
|
|
105
106
|
// Fall through to stashes[] (local/remote sources)
|
|
106
107
|
const stashResult = removeStash(target);
|
|
107
108
|
if (!stashResult.removed || !stashResult.entry) {
|
|
108
|
-
throw new NotFoundError(`No matching source for target: ${target}
|
|
109
|
+
throw new NotFoundError(`No matching source for target: ${target}`, "SOURCE_NOT_FOUND");
|
|
109
110
|
}
|
|
110
111
|
const removedEntry = stashResult.entry;
|
|
111
112
|
const index = await akmIndex({ stashDir });
|
|
@@ -122,7 +123,7 @@ export async function akmRemove(input) {
|
|
|
122
123
|
stashRoot: removedEntry.path ?? "",
|
|
123
124
|
},
|
|
124
125
|
config: {
|
|
125
|
-
|
|
126
|
+
sourceCount: (updatedConfig.sources ?? updatedConfig.stashes ?? []).length,
|
|
126
127
|
installedKitCount: updatedConfig.installed?.length ?? 0,
|
|
127
128
|
},
|
|
128
129
|
index: {
|
|
@@ -138,9 +139,51 @@ export async function akmUpdate(input) {
|
|
|
138
139
|
const target = input?.target?.trim();
|
|
139
140
|
const all = input?.all === true;
|
|
140
141
|
const force = input?.force === true;
|
|
141
|
-
const
|
|
142
|
+
const config = loadConfig();
|
|
143
|
+
const installedEntries = config.installed ?? [];
|
|
144
|
+
// Check if the target refers to a website source — those are syncable via
|
|
145
|
+
// ensureWebsiteMirror and are stored in sources[] not installed[].
|
|
146
|
+
if (target && !all) {
|
|
147
|
+
const stashes = config.sources ?? config.stashes ?? [];
|
|
148
|
+
const isUrl = target.startsWith("http://") || target.startsWith("https://");
|
|
149
|
+
const resolvedPath = !isUrl ? path.resolve(target) : undefined;
|
|
150
|
+
const websiteMatch = stashes.find((s) => {
|
|
151
|
+
if (s.type !== "website")
|
|
152
|
+
return false;
|
|
153
|
+
if (isUrl && s.url === target)
|
|
154
|
+
return true;
|
|
155
|
+
if (s.name === target)
|
|
156
|
+
return true;
|
|
157
|
+
if (resolvedPath && s.path && path.resolve(s.path) === resolvedPath)
|
|
158
|
+
return true;
|
|
159
|
+
return false;
|
|
160
|
+
});
|
|
161
|
+
if (websiteMatch) {
|
|
162
|
+
// TODO: full incremental re-crawl with delta tracking (#19)
|
|
163
|
+
await ensureWebsiteMirror(websiteMatch, { requireStashDir: true, force: true });
|
|
164
|
+
const index = await akmIndex({ stashDir });
|
|
165
|
+
const updatedConfig = loadConfig();
|
|
166
|
+
return {
|
|
167
|
+
schemaVersion: 1,
|
|
168
|
+
stashDir,
|
|
169
|
+
target,
|
|
170
|
+
all,
|
|
171
|
+
processed: [],
|
|
172
|
+
config: {
|
|
173
|
+
sourceCount: (updatedConfig.sources ?? updatedConfig.stashes ?? []).length,
|
|
174
|
+
installedKitCount: updatedConfig.installed?.length ?? 0,
|
|
175
|
+
},
|
|
176
|
+
index: {
|
|
177
|
+
mode: index.mode,
|
|
178
|
+
totalEntries: index.totalEntries,
|
|
179
|
+
directoriesScanned: index.directoriesScanned,
|
|
180
|
+
directoriesSkipped: index.directoriesSkipped,
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
142
185
|
const selectedEntries = selectTargets(installedEntries, target, all);
|
|
143
|
-
const auditConfig =
|
|
186
|
+
const auditConfig = config;
|
|
144
187
|
const processed = [];
|
|
145
188
|
for (const entry of selectedEntries) {
|
|
146
189
|
if (force && shouldCleanupCache(entry)) {
|
|
@@ -210,7 +253,7 @@ export async function akmUpdate(input) {
|
|
|
210
253
|
});
|
|
211
254
|
}
|
|
212
255
|
const index = await akmIndex({ stashDir });
|
|
213
|
-
const
|
|
256
|
+
const finalConfig = loadConfig();
|
|
214
257
|
return {
|
|
215
258
|
schemaVersion: 1,
|
|
216
259
|
stashDir,
|
|
@@ -218,8 +261,8 @@ export async function akmUpdate(input) {
|
|
|
218
261
|
all,
|
|
219
262
|
processed,
|
|
220
263
|
config: {
|
|
221
|
-
|
|
222
|
-
installedKitCount:
|
|
264
|
+
sourceCount: (finalConfig.sources ?? finalConfig.stashes ?? []).length,
|
|
265
|
+
installedKitCount: finalConfig.installed?.length ?? 0,
|
|
223
266
|
},
|
|
224
267
|
index: {
|
|
225
268
|
mode: index.mode,
|
|
@@ -231,19 +274,19 @@ export async function akmUpdate(input) {
|
|
|
231
274
|
}
|
|
232
275
|
function selectTargets(installed, target, all) {
|
|
233
276
|
if (all && target) {
|
|
234
|
-
throw new UsageError("Specify either <target> or --all, not both.");
|
|
277
|
+
throw new UsageError("Specify either <target> or --all, not both.", "MISSING_OR_AMBIGUOUS_TARGET");
|
|
235
278
|
}
|
|
236
279
|
if (all)
|
|
237
280
|
return installed;
|
|
238
281
|
if (!target) {
|
|
239
|
-
throw new UsageError("Either <target> or --all is required.");
|
|
282
|
+
throw new UsageError("Either <target> or --all is required.", "MISSING_OR_AMBIGUOUS_TARGET");
|
|
240
283
|
}
|
|
241
284
|
const found = tryResolveInstalledTarget(installed, target);
|
|
242
285
|
if (found)
|
|
243
286
|
return [found];
|
|
244
287
|
// Check if target matches a stash source and give a helpful message
|
|
245
288
|
const config = loadConfig();
|
|
246
|
-
const stashes = config.stashes ?? [];
|
|
289
|
+
const stashes = config.sources ?? config.stashes ?? [];
|
|
247
290
|
const isUrl = target.startsWith("http://") || target.startsWith("https://");
|
|
248
291
|
const resolvedPath = !isUrl ? path.resolve(target) : undefined;
|
|
249
292
|
const stashMatch = stashes.find((s) => {
|
|
@@ -256,10 +299,13 @@ function selectTargets(installed, target, all) {
|
|
|
256
299
|
return false;
|
|
257
300
|
});
|
|
258
301
|
if (stashMatch) {
|
|
259
|
-
if (stashMatch.
|
|
260
|
-
|
|
302
|
+
if (stashMatch.type === "website") {
|
|
303
|
+
// Website sources should be handled before reaching selectTargets.
|
|
304
|
+
// This path should not be reached; surface a clear message if it is.
|
|
305
|
+
throw new UsageError(`"${target}" is a website source — website caching not yet implemented for --all. ` +
|
|
306
|
+
`Run \`akm update ${target}\` to re-mirror this source individually.`, "TARGET_NOT_UPDATABLE");
|
|
261
307
|
}
|
|
262
|
-
throw new UsageError(`"${target}" is a local directory — it reflects your files in place. To refresh the search index, run: akm index
|
|
308
|
+
throw new UsageError(`"${target}" is a local directory — it reflects your files in place. To refresh the search index, run: akm index`, "TARGET_NOT_UPDATABLE");
|
|
263
309
|
}
|
|
264
310
|
throw new NotFoundError(`No matching source for target: ${target}`);
|
|
265
311
|
}
|
|
@@ -10,11 +10,11 @@ const MIGRATION_DOC_URL = "https://github.com/itlackey/akm/blob/main/docs/migrat
|
|
|
10
10
|
* `files[]` array in `package.json`.
|
|
11
11
|
*/
|
|
12
12
|
function releaseNotesDir() {
|
|
13
|
-
return path.resolve(import.meta.dir, "
|
|
13
|
+
return path.resolve(import.meta.dir, "../../docs/migration/release-notes");
|
|
14
14
|
}
|
|
15
15
|
function loadChangelog() {
|
|
16
16
|
try {
|
|
17
|
-
const changelogPath = path.resolve(import.meta.dir, "
|
|
17
|
+
const changelogPath = path.resolve(import.meta.dir, "../../CHANGELOG.md");
|
|
18
18
|
if (fs.existsSync(changelogPath)) {
|
|
19
19
|
return fs.readFileSync(changelogPath, "utf8");
|
|
20
20
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { toErrorMessage } from "
|
|
2
|
-
import { DEFAULT_CONFIG, loadConfig } from "
|
|
3
|
-
import { resolveProviderFactory } from "
|
|
1
|
+
import { toErrorMessage } from "../core/common";
|
|
2
|
+
import { DEFAULT_CONFIG, loadConfig } from "../core/config";
|
|
3
|
+
import { resolveProviderFactory } from "../registry/factory";
|
|
4
4
|
// ── Eagerly import providers to trigger self-registration ───────────────────
|
|
5
|
-
import "
|
|
5
|
+
import "../registry/providers/index";
|
|
6
6
|
// ── Public API ──────────────────────────────────────────────────────────────
|
|
7
7
|
export async function searchRegistry(query, options) {
|
|
8
8
|
const trimmed = query.trim();
|
|
@@ -24,7 +24,8 @@ export async function searchRegistry(query, options) {
|
|
|
24
24
|
// Merge results grouped by provider
|
|
25
25
|
const allHits = [];
|
|
26
26
|
const allAssetHits = [];
|
|
27
|
-
for (
|
|
27
|
+
for (let i = 0; i < results.length; i++) {
|
|
28
|
+
const result = results[i];
|
|
28
29
|
if (result.status === "rejected") {
|
|
29
30
|
warnings.push(toErrorMessage(result.reason));
|
|
30
31
|
continue;
|
|
@@ -32,6 +33,7 @@ export async function searchRegistry(query, options) {
|
|
|
32
33
|
const value = result.value;
|
|
33
34
|
if (!value)
|
|
34
35
|
continue;
|
|
36
|
+
const registryLabel = entries[i].name ? `"${entries[i].name}"` : entries[i].url;
|
|
35
37
|
let dropped = 0;
|
|
36
38
|
for (const hit of value.hits) {
|
|
37
39
|
if (isCompleteHit(hit)) {
|
|
@@ -52,7 +54,7 @@ export async function searchRegistry(query, options) {
|
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
if (dropped > 0) {
|
|
55
|
-
warnings.push(`Registry returned ${dropped} incomplete hit(s); dropped from response.`);
|
|
57
|
+
warnings.push(`Registry ${registryLabel} returned ${dropped} incomplete hit(s); dropped from response.`);
|
|
56
58
|
}
|
|
57
59
|
if (value.warnings)
|
|
58
60
|
warnings.push(...value.warnings);
|