akm-cli 0.0.0 → 0.0.17
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/LICENSE +385 -0
- package/README.md +249 -6
- package/dist/asset-spec.js +70 -0
- package/dist/cli.js +934 -0
- package/dist/common.js +192 -0
- package/dist/config-cli.js +233 -0
- package/dist/config.js +338 -0
- package/dist/db.js +371 -0
- package/dist/embedder.js +150 -0
- package/dist/errors.js +28 -0
- package/dist/file-context.js +162 -0
- package/dist/frontmatter.js +86 -0
- package/dist/github.js +17 -0
- package/dist/indexer.js +311 -0
- package/dist/init.js +43 -0
- package/dist/llm.js +87 -0
- package/dist/lockfile.js +60 -0
- package/dist/markdown.js +77 -0
- package/dist/matchers.js +159 -0
- package/dist/metadata.js +408 -0
- package/dist/origin-resolve.js +54 -0
- package/dist/paths.js +92 -0
- package/dist/registry-install.js +459 -0
- package/dist/registry-resolve.js +486 -0
- package/dist/registry-search.js +365 -0
- package/dist/registry-types.js +1 -0
- package/dist/renderers.js +386 -0
- package/dist/ripgrep-install.js +155 -0
- package/dist/ripgrep-resolve.js +78 -0
- package/dist/ripgrep.js +2 -0
- package/dist/self-update.js +226 -0
- package/dist/stash-add.js +71 -0
- package/dist/stash-clone.js +115 -0
- package/dist/stash-ref.js +73 -0
- package/dist/stash-registry.js +206 -0
- package/dist/stash-resolve.js +55 -0
- package/dist/stash-search.js +490 -0
- package/dist/stash-show.js +58 -0
- package/dist/stash-source.js +130 -0
- package/dist/stash-types.js +1 -0
- package/dist/walker.js +163 -0
- package/dist/warn.js +20 -0
- package/package.json +53 -7
- package/index.js +0 -4
package/dist/config.js
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getConfigDir as _getConfigDir, getConfigPath as _getConfigPath } from "./paths";
|
|
4
|
+
// ── Defaults ────────────────────────────────────────────────────────────────
|
|
5
|
+
export const DEFAULT_CONFIG = {
|
|
6
|
+
semanticSearch: true,
|
|
7
|
+
searchPaths: [],
|
|
8
|
+
registries: [{ url: "https://raw.githubusercontent.com/itlackey/akm-registry/main/index.json", name: "official" }],
|
|
9
|
+
output: {
|
|
10
|
+
format: "json",
|
|
11
|
+
detail: "brief",
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
// ── Paths ───────────────────────────────────────────────────────────────────
|
|
15
|
+
export function getConfigDir(env, platform) {
|
|
16
|
+
return _getConfigDir(env, platform);
|
|
17
|
+
}
|
|
18
|
+
export function getConfigPath() {
|
|
19
|
+
return _getConfigPath();
|
|
20
|
+
}
|
|
21
|
+
// ── Load / Save / Update ────────────────────────────────────────────────────
|
|
22
|
+
let cachedConfig;
|
|
23
|
+
export function loadConfig() {
|
|
24
|
+
const configPath = getConfigPath();
|
|
25
|
+
try {
|
|
26
|
+
const stat = fs.statSync(configPath);
|
|
27
|
+
if (cachedConfig && cachedConfig.path === configPath && cachedConfig.mtime === stat.mtimeMs) {
|
|
28
|
+
return cachedConfig.config;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// File doesn't exist — return defaults below
|
|
33
|
+
cachedConfig = undefined;
|
|
34
|
+
return { ...DEFAULT_CONFIG };
|
|
35
|
+
}
|
|
36
|
+
const raw = readConfigObject(configPath);
|
|
37
|
+
const config = raw ? pickKnownKeys(raw) : { ...DEFAULT_CONFIG };
|
|
38
|
+
// Inject API keys from environment variables.
|
|
39
|
+
// API keys should be provided via AKM_EMBED_API_KEY and AKM_LLM_API_KEY
|
|
40
|
+
// rather than stored in the config file.
|
|
41
|
+
if (config.embedding && !config.embedding.apiKey) {
|
|
42
|
+
const envKey = process.env.AKM_EMBED_API_KEY?.trim();
|
|
43
|
+
if (envKey)
|
|
44
|
+
config.embedding.apiKey = envKey;
|
|
45
|
+
}
|
|
46
|
+
if (config.llm && !config.llm.apiKey) {
|
|
47
|
+
const envKey = process.env.AKM_LLM_API_KEY?.trim();
|
|
48
|
+
if (envKey)
|
|
49
|
+
config.llm.apiKey = envKey;
|
|
50
|
+
}
|
|
51
|
+
// Cache the parsed config with its path and mtime for subsequent calls
|
|
52
|
+
try {
|
|
53
|
+
const stat = fs.statSync(configPath);
|
|
54
|
+
cachedConfig = { config, path: configPath, mtime: stat.mtimeMs };
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// If we can't stat (unlikely since we just read it), skip caching
|
|
58
|
+
}
|
|
59
|
+
return config;
|
|
60
|
+
}
|
|
61
|
+
export function saveConfig(config) {
|
|
62
|
+
cachedConfig = undefined;
|
|
63
|
+
const configPath = getConfigPath();
|
|
64
|
+
const dir = path.dirname(configPath);
|
|
65
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
66
|
+
const sanitized = sanitizeConfigForWrite(config);
|
|
67
|
+
const tmpPath = `${configPath}.tmp.${process.pid}`;
|
|
68
|
+
try {
|
|
69
|
+
fs.writeFileSync(tmpPath, `${JSON.stringify(sanitized, null, 2)}\n`, "utf8");
|
|
70
|
+
fs.renameSync(tmpPath, configPath);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
try {
|
|
74
|
+
fs.unlinkSync(tmpPath);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
/* ignore cleanup failure */
|
|
78
|
+
}
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Strip apiKey fields before writing config to disk.
|
|
84
|
+
* API keys should be provided via environment variables
|
|
85
|
+
* AKM_EMBED_API_KEY and AKM_LLM_API_KEY.
|
|
86
|
+
*/
|
|
87
|
+
function sanitizeConfigForWrite(config) {
|
|
88
|
+
const sanitized = { ...config };
|
|
89
|
+
if (sanitized.embedding) {
|
|
90
|
+
const { apiKey, ...rest } = sanitized.embedding;
|
|
91
|
+
sanitized.embedding = rest;
|
|
92
|
+
}
|
|
93
|
+
if (sanitized.llm) {
|
|
94
|
+
const { apiKey, ...rest } = sanitized.llm;
|
|
95
|
+
sanitized.llm = rest;
|
|
96
|
+
}
|
|
97
|
+
return sanitized;
|
|
98
|
+
}
|
|
99
|
+
export function updateConfig(partial) {
|
|
100
|
+
const current = loadConfig();
|
|
101
|
+
const merged = { ...current, ...partial };
|
|
102
|
+
saveConfig(merged);
|
|
103
|
+
return merged;
|
|
104
|
+
}
|
|
105
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
106
|
+
function pickKnownKeys(raw) {
|
|
107
|
+
const config = { ...DEFAULT_CONFIG };
|
|
108
|
+
if (typeof raw.stashDir === "string" && raw.stashDir.trim()) {
|
|
109
|
+
config.stashDir = raw.stashDir.trim();
|
|
110
|
+
}
|
|
111
|
+
if (typeof raw.semanticSearch === "boolean") {
|
|
112
|
+
config.semanticSearch = raw.semanticSearch;
|
|
113
|
+
}
|
|
114
|
+
if (Array.isArray(raw.searchPaths)) {
|
|
115
|
+
config.searchPaths = raw.searchPaths.filter((d) => typeof d === "string");
|
|
116
|
+
}
|
|
117
|
+
const embedding = parseEmbeddingConfig(raw.embedding);
|
|
118
|
+
if (embedding)
|
|
119
|
+
config.embedding = embedding;
|
|
120
|
+
const llm = parseLlmConfig(raw.llm);
|
|
121
|
+
if (llm)
|
|
122
|
+
config.llm = llm;
|
|
123
|
+
const installed = parseInstalledEntries(raw.installed);
|
|
124
|
+
if (installed)
|
|
125
|
+
config.installed = installed;
|
|
126
|
+
const registries = parseRegistriesConfig(raw.registries);
|
|
127
|
+
if (registries)
|
|
128
|
+
config.registries = registries;
|
|
129
|
+
const output = parseOutputConfig(raw.output);
|
|
130
|
+
if (output)
|
|
131
|
+
config.output = output;
|
|
132
|
+
return config;
|
|
133
|
+
}
|
|
134
|
+
function parseOutputConfig(value) {
|
|
135
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
136
|
+
return undefined;
|
|
137
|
+
const obj = value;
|
|
138
|
+
const output = {};
|
|
139
|
+
if (obj.format === "json" || obj.format === "yaml" || obj.format === "text") {
|
|
140
|
+
output.format = obj.format;
|
|
141
|
+
}
|
|
142
|
+
if (obj.detail === "brief" || obj.detail === "normal" || obj.detail === "full") {
|
|
143
|
+
output.detail = obj.detail;
|
|
144
|
+
}
|
|
145
|
+
return Object.keys(output).length > 0 ? output : undefined;
|
|
146
|
+
}
|
|
147
|
+
function readConfigObject(configPath) {
|
|
148
|
+
try {
|
|
149
|
+
const text = fs.readFileSync(configPath, "utf8");
|
|
150
|
+
const raw = JSON.parse(stripJsonComments(text));
|
|
151
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
152
|
+
return undefined;
|
|
153
|
+
return raw;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Strip JavaScript-style comments from a JSON string (JSONC support).
|
|
161
|
+
* Handles // line comments and /* block comments while preserving
|
|
162
|
+
* comment-like sequences inside quoted strings.
|
|
163
|
+
*/
|
|
164
|
+
export function stripJsonComments(text) {
|
|
165
|
+
let result = "";
|
|
166
|
+
let i = 0;
|
|
167
|
+
let inString = false;
|
|
168
|
+
let stringChar = "";
|
|
169
|
+
while (i < text.length) {
|
|
170
|
+
if (inString) {
|
|
171
|
+
if (text[i] === "\\") {
|
|
172
|
+
result += text[i] + (text[i + 1] ?? "");
|
|
173
|
+
i += 2;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (text[i] === stringChar) {
|
|
177
|
+
inString = false;
|
|
178
|
+
}
|
|
179
|
+
result += text[i];
|
|
180
|
+
i++;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (text[i] === '"' || text[i] === "'") {
|
|
184
|
+
inString = true;
|
|
185
|
+
stringChar = text[i];
|
|
186
|
+
result += text[i];
|
|
187
|
+
i++;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (text[i] === "/" && text[i + 1] === "/") {
|
|
191
|
+
while (i < text.length && text[i] !== "\n")
|
|
192
|
+
i++;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (text[i] === "/" && text[i + 1] === "*") {
|
|
196
|
+
i += 2;
|
|
197
|
+
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/"))
|
|
198
|
+
i++;
|
|
199
|
+
i += 2;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
result += text[i];
|
|
203
|
+
i++;
|
|
204
|
+
}
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
function parseEmbeddingConfig(value) {
|
|
208
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
209
|
+
return undefined;
|
|
210
|
+
const obj = value;
|
|
211
|
+
if (typeof obj.endpoint !== "string" || !obj.endpoint)
|
|
212
|
+
return undefined;
|
|
213
|
+
if (typeof obj.model !== "string" || !obj.model)
|
|
214
|
+
return undefined;
|
|
215
|
+
const result = {
|
|
216
|
+
endpoint: obj.endpoint,
|
|
217
|
+
model: obj.model,
|
|
218
|
+
};
|
|
219
|
+
if (typeof obj.provider === "string" && obj.provider) {
|
|
220
|
+
result.provider = obj.provider;
|
|
221
|
+
}
|
|
222
|
+
if ("dimension" in obj) {
|
|
223
|
+
if (typeof obj.dimension !== "number" ||
|
|
224
|
+
!Number.isFinite(obj.dimension) ||
|
|
225
|
+
!Number.isInteger(obj.dimension) ||
|
|
226
|
+
obj.dimension <= 0) {
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
result.dimension = obj.dimension;
|
|
230
|
+
}
|
|
231
|
+
if (typeof obj.apiKey === "string" && obj.apiKey) {
|
|
232
|
+
result.apiKey = obj.apiKey;
|
|
233
|
+
}
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
function parseLlmConfig(value) {
|
|
237
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
238
|
+
return undefined;
|
|
239
|
+
const obj = value;
|
|
240
|
+
if (typeof obj.endpoint !== "string" || !obj.endpoint)
|
|
241
|
+
return undefined;
|
|
242
|
+
if (typeof obj.model !== "string" || !obj.model)
|
|
243
|
+
return undefined;
|
|
244
|
+
const result = {
|
|
245
|
+
endpoint: obj.endpoint,
|
|
246
|
+
model: obj.model,
|
|
247
|
+
};
|
|
248
|
+
if (typeof obj.provider === "string" && obj.provider) {
|
|
249
|
+
result.provider = obj.provider;
|
|
250
|
+
}
|
|
251
|
+
if (typeof obj.temperature === "number" && Number.isFinite(obj.temperature)) {
|
|
252
|
+
result.temperature = obj.temperature;
|
|
253
|
+
}
|
|
254
|
+
if ("maxTokens" in obj) {
|
|
255
|
+
if (typeof obj.maxTokens !== "number" ||
|
|
256
|
+
!Number.isFinite(obj.maxTokens) ||
|
|
257
|
+
!Number.isInteger(obj.maxTokens) ||
|
|
258
|
+
obj.maxTokens <= 0) {
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
result.maxTokens = obj.maxTokens;
|
|
262
|
+
}
|
|
263
|
+
if (typeof obj.apiKey === "string" && obj.apiKey) {
|
|
264
|
+
result.apiKey = obj.apiKey;
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
function parseInstalledEntries(value) {
|
|
269
|
+
if (!Array.isArray(value))
|
|
270
|
+
return undefined;
|
|
271
|
+
const entries = value
|
|
272
|
+
.map((entry) => parseInstalledKitEntry(entry))
|
|
273
|
+
.filter((entry) => entry !== undefined);
|
|
274
|
+
return entries.length > 0 ? entries : undefined;
|
|
275
|
+
}
|
|
276
|
+
function parseInstalledKitEntry(value) {
|
|
277
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
278
|
+
return undefined;
|
|
279
|
+
const obj = value;
|
|
280
|
+
const id = asNonEmptyString(obj.id);
|
|
281
|
+
const source = asKitSource(obj.source);
|
|
282
|
+
const ref = asNonEmptyString(obj.ref);
|
|
283
|
+
const artifactUrl = asNonEmptyString(obj.artifactUrl);
|
|
284
|
+
const stashRoot = asNonEmptyString(obj.stashRoot);
|
|
285
|
+
const cacheDir = asNonEmptyString(obj.cacheDir);
|
|
286
|
+
const installedAt = asNonEmptyString(obj.installedAt);
|
|
287
|
+
if (!id || !source || !ref || !artifactUrl || !stashRoot || !cacheDir || !installedAt)
|
|
288
|
+
return undefined;
|
|
289
|
+
const entry = {
|
|
290
|
+
id,
|
|
291
|
+
source,
|
|
292
|
+
ref,
|
|
293
|
+
artifactUrl,
|
|
294
|
+
stashRoot,
|
|
295
|
+
cacheDir,
|
|
296
|
+
installedAt,
|
|
297
|
+
};
|
|
298
|
+
const resolvedVersion = asNonEmptyString(obj.resolvedVersion);
|
|
299
|
+
if (resolvedVersion)
|
|
300
|
+
entry.resolvedVersion = resolvedVersion;
|
|
301
|
+
const resolvedRevision = asNonEmptyString(obj.resolvedRevision);
|
|
302
|
+
if (resolvedRevision)
|
|
303
|
+
entry.resolvedRevision = resolvedRevision;
|
|
304
|
+
return entry;
|
|
305
|
+
}
|
|
306
|
+
function asNonEmptyString(value) {
|
|
307
|
+
return typeof value === "string" && value ? value : undefined;
|
|
308
|
+
}
|
|
309
|
+
function asKitSource(value) {
|
|
310
|
+
if (value === "npm" || value === "github" || value === "git" || value === "local")
|
|
311
|
+
return value;
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
function parseRegistriesConfig(value) {
|
|
315
|
+
if (!Array.isArray(value))
|
|
316
|
+
return undefined;
|
|
317
|
+
const entries = value
|
|
318
|
+
.map((entry) => parseRegistryConfigEntry(entry))
|
|
319
|
+
.filter((entry) => entry !== undefined);
|
|
320
|
+
// Return the array even if empty — an explicit empty array means "no registries"
|
|
321
|
+
// which overrides the default. Only return undefined if the field was not an array.
|
|
322
|
+
return entries;
|
|
323
|
+
}
|
|
324
|
+
function parseRegistryConfigEntry(value) {
|
|
325
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
326
|
+
return undefined;
|
|
327
|
+
const obj = value;
|
|
328
|
+
const url = asNonEmptyString(obj.url);
|
|
329
|
+
if (!url || !url.startsWith("http"))
|
|
330
|
+
return undefined;
|
|
331
|
+
const entry = { url };
|
|
332
|
+
const name = asNonEmptyString(obj.name);
|
|
333
|
+
if (name)
|
|
334
|
+
entry.name = name;
|
|
335
|
+
if (typeof obj.enabled === "boolean")
|
|
336
|
+
entry.enabled = obj.enabled;
|
|
337
|
+
return entry;
|
|
338
|
+
}
|