akm-cli 0.7.4 → 0.8.0-rc.3
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 → .github/CHANGELOG.md} +34 -1
- package/.github/LICENSE +374 -0
- package/dist/cli/parse-args.js +86 -0
- package/dist/cli.js +1223 -650
- package/dist/commands/agent-dispatch.js +107 -0
- package/dist/commands/agent-support.js +62 -0
- package/dist/commands/config-cli.js +68 -84
- package/dist/commands/consolidate.js +812 -0
- package/dist/commands/curate.js +1 -0
- package/dist/commands/distill-promotion-policy.js +658 -0
- package/dist/commands/distill.js +224 -39
- package/dist/commands/eval-cases.js +40 -0
- package/dist/commands/events.js +12 -24
- package/dist/commands/graph.js +222 -0
- package/dist/commands/health.js +376 -0
- package/dist/commands/help/help-accept.md +9 -0
- package/dist/commands/help/help-improve.md +53 -0
- package/dist/commands/help/help-proposals.md +15 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +8 -0
- package/dist/commands/history.js +3 -30
- package/dist/commands/improve.js +1161 -0
- package/dist/commands/info.js +2 -2
- package/dist/commands/init.js +2 -2
- package/dist/commands/install-audit.js +5 -1
- package/dist/commands/installed-stashes.js +118 -138
- package/dist/commands/knowledge.js +133 -0
- package/dist/commands/lint/agent-linter.js +46 -0
- package/dist/commands/lint/base-linter.js +291 -0
- package/dist/commands/lint/command-linter.js +46 -0
- package/dist/commands/lint/default-linter.js +13 -0
- package/dist/commands/lint/index.js +145 -0
- package/dist/commands/lint/knowledge-linter.js +13 -0
- package/dist/commands/lint/memory-linter.js +58 -0
- package/dist/commands/lint/registry.js +33 -0
- package/dist/commands/lint/skill-linter.js +42 -0
- package/dist/commands/lint/task-linter.js +47 -0
- package/dist/commands/lint/types.js +1 -0
- package/dist/commands/lint/vault-key-rules.js +67 -0
- package/dist/commands/lint/workflow-linter.js +53 -0
- package/dist/commands/lint.js +1 -0
- package/dist/commands/migration-help.js +2 -2
- package/dist/commands/proposal.js +8 -7
- package/dist/commands/propose.js +106 -43
- package/dist/commands/reflect.js +167 -41
- package/dist/commands/registry-search.js +2 -2
- package/dist/commands/remember.js +55 -1
- package/dist/commands/schema-repair.js +130 -0
- package/dist/commands/search.js +21 -5
- package/dist/commands/show.js +135 -55
- package/dist/commands/source-add.js +10 -10
- package/dist/commands/source-manage.js +11 -19
- package/dist/commands/tasks.js +385 -0
- package/dist/commands/url-checker.js +39 -0
- package/dist/commands/vault.js +173 -87
- package/dist/core/action-contributors.js +25 -0
- package/dist/core/asset-ref.js +4 -0
- package/dist/core/asset-registry.js +5 -17
- package/dist/core/asset-spec.js +11 -1
- package/dist/core/common.js +100 -0
- package/dist/core/concurrent.js +22 -0
- package/dist/core/config.js +240 -127
- package/dist/core/events.js +87 -123
- package/dist/core/frontmatter.js +0 -6
- package/dist/core/markdown.js +17 -0
- package/dist/core/memory-improve.js +678 -0
- package/dist/core/parse.js +155 -0
- package/dist/core/paths.js +101 -3
- package/dist/core/proposal-validators.js +61 -0
- package/dist/core/proposals.js +49 -38
- package/dist/core/state-db.js +731 -0
- package/dist/core/time.js +51 -0
- package/dist/core/warn.js +59 -1
- package/dist/indexer/db-search.js +86 -472
- package/dist/indexer/db.js +418 -59
- package/dist/indexer/ensure-index.js +133 -0
- package/dist/indexer/graph-boost.js +247 -94
- package/dist/indexer/graph-db.js +201 -0
- package/dist/indexer/graph-dedup.js +99 -0
- package/dist/indexer/graph-extraction.js +417 -74
- package/dist/indexer/index-context.js +10 -0
- package/dist/indexer/indexer.js +480 -298
- package/dist/indexer/llm-cache.js +47 -0
- package/dist/indexer/matchers.js +124 -160
- package/dist/indexer/memory-inference.js +63 -29
- package/dist/indexer/metadata-contributors.js +26 -0
- package/dist/indexer/metadata.js +196 -197
- package/dist/indexer/path-resolver.js +89 -0
- package/dist/indexer/ranking-contributors.js +204 -0
- package/dist/indexer/ranking.js +74 -0
- package/dist/indexer/search-hit-enrichers.js +22 -0
- package/dist/indexer/search-source.js +24 -9
- package/dist/indexer/semantic-status.js +2 -16
- package/dist/indexer/walker.js +25 -0
- package/dist/integrations/agent/builders.js +109 -0
- package/dist/integrations/agent/config.js +203 -3
- package/dist/integrations/agent/index.js +5 -2
- package/dist/integrations/agent/model-aliases.js +63 -0
- package/dist/integrations/agent/profiles.js +67 -5
- package/dist/integrations/agent/prompts.js +114 -29
- package/dist/integrations/agent/sdk-runner.js +120 -0
- package/dist/integrations/agent/spawn.js +158 -34
- package/dist/integrations/lockfile.js +10 -18
- package/dist/integrations/session-logs/index.js +65 -0
- package/dist/integrations/session-logs/providers/claude-code.js +56 -0
- package/dist/integrations/session-logs/providers/opencode.js +52 -0
- package/dist/integrations/session-logs/types.js +1 -0
- package/dist/llm/call-ai.js +74 -0
- package/dist/llm/client.js +63 -86
- package/dist/llm/feature-gate.js +27 -16
- package/dist/llm/graph-extract.js +297 -64
- package/dist/llm/memory-infer.js +52 -71
- package/dist/llm/metadata-enhance.js +39 -22
- package/dist/llm/prompts/graph-extract-user-prompt.md +12 -0
- package/dist/output/cli-hints-full.md +277 -0
- package/dist/output/cli-hints-short.md +65 -0
- package/dist/output/cli-hints.js +2 -309
- package/dist/output/renderers.js +226 -257
- package/dist/output/shapes.js +109 -96
- package/dist/output/text.js +274 -36
- package/dist/registry/providers/skills-sh.js +61 -49
- package/dist/registry/providers/static-index.js +44 -48
- package/dist/registry/resolve.js +8 -16
- package/dist/setup/setup.js +510 -11
- package/dist/sources/provider-factory.js +2 -1
- package/dist/sources/providers/filesystem.js +16 -23
- package/dist/sources/providers/git.js +45 -4
- package/dist/sources/providers/website.js +15 -22
- package/dist/sources/website-ingest.js +4 -0
- package/dist/tasks/backends/cron.js +200 -0
- package/dist/tasks/backends/exec-utils.js +25 -0
- package/dist/tasks/backends/index.js +32 -0
- package/dist/tasks/backends/launchd-template.xml +19 -0
- package/dist/tasks/backends/launchd.js +184 -0
- package/dist/tasks/backends/schtasks-template.xml +29 -0
- package/dist/tasks/backends/schtasks.js +212 -0
- package/dist/tasks/parser.js +198 -0
- package/dist/tasks/resolveAkmBin.js +84 -0
- package/dist/tasks/runner.js +432 -0
- package/dist/tasks/schedule.js +208 -0
- package/dist/tasks/schema.js +13 -0
- package/dist/tasks/validator.js +59 -0
- package/dist/wiki/index-template.md +12 -0
- package/dist/wiki/ingest-workflow-template.md +54 -0
- package/dist/wiki/log-template.md +8 -0
- package/dist/wiki/schema-template.md +61 -0
- package/dist/wiki/wiki-templates.js +12 -0
- package/dist/wiki/wiki.js +10 -61
- package/dist/workflows/authoring.js +5 -25
- package/dist/workflows/db.js +9 -0
- package/dist/workflows/renderer.js +8 -3
- package/dist/workflows/runs.js +73 -88
- package/dist/workflows/scope-key.js +76 -0
- package/dist/workflows/validator.js +1 -1
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +5 -2
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.4.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +20 -0
- package/docs/migration/release-notes/0.8.0.md +43 -0
- package/package.json +4 -3
- package/dist/templates/wiki-templates.js +0 -100
package/dist/commands/show.js
CHANGED
|
@@ -9,33 +9,31 @@
|
|
|
9
9
|
* edit-hints, summary-detail truncation) lives below in this file. The flow:
|
|
10
10
|
*
|
|
11
11
|
* 1. Special-case wiki-root refs (`wiki:<name>` with no page path).
|
|
12
|
-
* 2.
|
|
13
|
-
* 3.
|
|
14
|
-
* no matching row — covers the "indexed yet?" gap when the user has
|
|
15
|
-
* just added a file and not run `akm index`.
|
|
12
|
+
* 2. Auto-index when stale so the index is current.
|
|
13
|
+
* 3. Ask `indexer.lookup(ref)` for the row in the FTS index.
|
|
16
14
|
* 4. Render the file via the matcher/renderer pipeline.
|
|
17
|
-
*
|
|
18
|
-
* Step (2) is the v1 spec change: reading is the indexer's job. Step (3) is a
|
|
19
|
-
* pragmatic safety net (NOT remote provider fallback, which the spec
|
|
20
|
-
* forbids — "Show: Local FTS5 index only. No remote provider fallback.").
|
|
21
15
|
*/
|
|
22
16
|
import fs from "node:fs";
|
|
23
17
|
import path from "node:path";
|
|
24
18
|
import { parseAssetRef } from "../core/asset-ref";
|
|
19
|
+
import { asNonEmptyString } from "../core/common";
|
|
25
20
|
import { loadConfig } from "../core/config";
|
|
26
21
|
import { NotFoundError, UsageError } from "../core/errors";
|
|
27
22
|
import { appendEvent, readEvents } from "../core/events";
|
|
28
|
-
import { parseFrontmatter
|
|
23
|
+
import { parseFrontmatter } from "../core/frontmatter";
|
|
29
24
|
import { closeDatabase, findEntryIdByRef, openExistingDatabase } from "../indexer/db";
|
|
25
|
+
import { ensureIndex } from "../indexer/ensure-index";
|
|
30
26
|
import { buildFileContext, buildRenderContext, getRenderer, runMatchers } from "../indexer/file-context";
|
|
27
|
+
import { listRelatedPathsForFile } from "../indexer/graph-boost";
|
|
31
28
|
import { lookup } from "../indexer/indexer";
|
|
29
|
+
import { resolveAssetPath } from "../indexer/path-resolver";
|
|
32
30
|
import { buildEditHint, findSourceForPath, isEditable, resolveSourceEntries } from "../indexer/search-source";
|
|
33
31
|
import { insertUsageEvent } from "../indexer/usage-events";
|
|
34
32
|
import { resolveSourcesForOrigin } from "../registry/origin-resolve";
|
|
35
33
|
// Eagerly import source providers to trigger self-registration.
|
|
36
34
|
import "../sources/providers/index";
|
|
37
|
-
import { resolveAssetPath } from "../sources/resolve";
|
|
38
35
|
import { getActiveWorkflowRun } from "../workflows/runs";
|
|
36
|
+
import { getCurrentWorkflowScopeKey } from "../workflows/scope-key";
|
|
39
37
|
/**
|
|
40
38
|
* Show a wiki root (no page path) — returns the same payload as
|
|
41
39
|
* `akm wiki show <name>`.
|
|
@@ -128,7 +126,12 @@ export async function akmShowUnified(input) {
|
|
|
128
126
|
new NotFoundError(`Wiki not found: ${parsed.name}. Run \`akm wiki create ${parsed.name}\` to create it.`));
|
|
129
127
|
}
|
|
130
128
|
}
|
|
131
|
-
//
|
|
129
|
+
// Auto-index when stale so the index is current before lookup.
|
|
130
|
+
const allSources = resolveSourceEntries();
|
|
131
|
+
if (allSources.length > 0) {
|
|
132
|
+
await ensureIndex(allSources[0].path);
|
|
133
|
+
}
|
|
134
|
+
// Try local filesystem (FTS5 index lookup)
|
|
132
135
|
const result = await showLocal(input);
|
|
133
136
|
// Scope filter narrows resolution: if --scope was supplied, the asset's
|
|
134
137
|
// frontmatter scope must satisfy every supplied key. We re-read the file
|
|
@@ -177,7 +180,7 @@ function enforceScopeOrThrow(filePath, ref, scope) {
|
|
|
177
180
|
for (const [key, expectedValue] of expected) {
|
|
178
181
|
if (expectedValue === undefined)
|
|
179
182
|
continue;
|
|
180
|
-
const actual =
|
|
183
|
+
const actual = asNonEmptyString(fm[`scope_${key}`]);
|
|
181
184
|
if (actual !== expectedValue) {
|
|
182
185
|
throw new NotFoundError(`Asset "${ref}" exists but is out of scope (expected scope_${key}="${expectedValue}").`);
|
|
183
186
|
}
|
|
@@ -201,6 +204,31 @@ function logShowEvent(ref, existingDb) {
|
|
|
201
204
|
// detect akm show invocations without relying on stdout scraping.
|
|
202
205
|
const parsed = parseAssetRef(ref);
|
|
203
206
|
appendEvent({ eventType: "show", ref, metadata: { type: parsed.type, name: parsed.name } });
|
|
207
|
+
// Detect if this show is a selection from a recent search result.
|
|
208
|
+
try {
|
|
209
|
+
const { events: recentSearches } = readEvents({ type: "search" });
|
|
210
|
+
const cutoffMs = Date.now() - 60_000;
|
|
211
|
+
const matchingSearch = [...recentSearches].reverse().find((e) => {
|
|
212
|
+
if (!e.ts || new Date(e.ts).getTime() < cutoffMs)
|
|
213
|
+
return false;
|
|
214
|
+
const refs = e.metadata?.resultRefs ?? [];
|
|
215
|
+
return refs.includes(ref);
|
|
216
|
+
});
|
|
217
|
+
if (matchingSearch) {
|
|
218
|
+
appendEvent({
|
|
219
|
+
eventType: "select",
|
|
220
|
+
ref,
|
|
221
|
+
metadata: {
|
|
222
|
+
query: matchingSearch.metadata?.query,
|
|
223
|
+
searchTs: matchingSearch.ts,
|
|
224
|
+
rankPosition: (matchingSearch.metadata?.resultRefs ?? []).indexOf(ref),
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
/* fire-and-forget — select is best-effort */
|
|
231
|
+
}
|
|
204
232
|
try {
|
|
205
233
|
const db = existingDb ?? openExistingDatabase();
|
|
206
234
|
try {
|
|
@@ -219,40 +247,6 @@ function logShowEvent(ref, existingDb) {
|
|
|
219
247
|
/* fire-and-forget */
|
|
220
248
|
}
|
|
221
249
|
}
|
|
222
|
-
/**
|
|
223
|
-
* Resolve an asset path to a file via:
|
|
224
|
-
* 1. `indexer.lookup(ref)` — the spec's primary path (§6.2).
|
|
225
|
-
* 2. On-disk type-dir traversal — fallback for files not yet indexed.
|
|
226
|
-
*
|
|
227
|
-
* Returns `undefined` if neither path finds a match.
|
|
228
|
-
*/
|
|
229
|
-
async function resolvePathViaIndexThenDisk(parsed, searchSourceDirs) {
|
|
230
|
-
// Step 1: indexer
|
|
231
|
-
try {
|
|
232
|
-
const entry = await lookup(parsed);
|
|
233
|
-
if (entry) {
|
|
234
|
-
return { assetPath: entry.filePath };
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
catch (err) {
|
|
238
|
-
// Index unavailable (e.g. DB doesn't exist yet) — fall back to disk walk.
|
|
239
|
-
if (!(err instanceof NotFoundError)) {
|
|
240
|
-
// continue to disk fallback
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
// Step 2: on-disk type-dir traversal
|
|
244
|
-
let lastError;
|
|
245
|
-
for (const dir of searchSourceDirs) {
|
|
246
|
-
try {
|
|
247
|
-
const assetPath = await resolveAssetPath(dir, parsed.type, parsed.name);
|
|
248
|
-
return { assetPath, lastError };
|
|
249
|
-
}
|
|
250
|
-
catch (err) {
|
|
251
|
-
lastError = err instanceof Error ? err : new Error(String(err));
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
return lastError ? { assetPath: "", lastError } : undefined;
|
|
255
|
-
}
|
|
256
250
|
/** @internal Use akmShowUnified() for all external callers. */
|
|
257
251
|
export async function showLocal(input) {
|
|
258
252
|
const parsed = parseAssetRef(input.ref);
|
|
@@ -273,13 +267,11 @@ export async function showLocal(input) {
|
|
|
273
267
|
}
|
|
274
268
|
}
|
|
275
269
|
if (!assetPath) {
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
lastError = resolved.lastError;
|
|
282
|
-
}
|
|
270
|
+
const resolvedAssetPath = await resolveAssetPath(parsed, {
|
|
271
|
+
stashDir: input.stashDir,
|
|
272
|
+
mode: "index-first",
|
|
273
|
+
});
|
|
274
|
+
assetPath = resolvedAssetPath ?? undefined;
|
|
283
275
|
}
|
|
284
276
|
if (!assetPath && parsed.origin && searchSources.length === 0) {
|
|
285
277
|
const installCmd = `akm add ${parsed.origin}`;
|
|
@@ -318,8 +310,23 @@ export async function showLocal(input) {
|
|
|
318
310
|
origin: source?.registryId ?? null,
|
|
319
311
|
editable,
|
|
320
312
|
...(!editable ? { editHint: buildEditHint(assetPath, parsed.type, parsed.name, source?.registryId) } : {}),
|
|
313
|
+
related: (() => {
|
|
314
|
+
let db;
|
|
315
|
+
try {
|
|
316
|
+
db = openExistingDatabase();
|
|
317
|
+
const related = listRelatedPathsForFile(sourceStashDir, assetPath, 5, db);
|
|
318
|
+
return { total: related.length, hits: related };
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
return { total: 0, hits: [] };
|
|
322
|
+
}
|
|
323
|
+
finally {
|
|
324
|
+
if (db)
|
|
325
|
+
closeDatabase(db);
|
|
326
|
+
}
|
|
327
|
+
})(),
|
|
321
328
|
};
|
|
322
|
-
const activeRun = getActiveWorkflowRun();
|
|
329
|
+
const activeRun = await getActiveWorkflowRun(getCurrentWorkflowScopeKey());
|
|
323
330
|
if (activeRun) {
|
|
324
331
|
fullResponse.activeRun = activeRun;
|
|
325
332
|
}
|
|
@@ -379,7 +386,7 @@ function buildSummaryResponse(full, assetPath) {
|
|
|
379
386
|
const textContent = full.content ?? full.template ?? full.prompt;
|
|
380
387
|
if (textContent && !description) {
|
|
381
388
|
const parsed = parseFrontmatter(textContent);
|
|
382
|
-
description =
|
|
389
|
+
description = asNonEmptyString(parsed.data.description);
|
|
383
390
|
}
|
|
384
391
|
}
|
|
385
392
|
const summary = {
|
|
@@ -396,3 +403,76 @@ function buildSummaryResponse(full, assetPath) {
|
|
|
396
403
|
};
|
|
397
404
|
return summary;
|
|
398
405
|
}
|
|
406
|
+
// ── argv normalisation ───────────────────────────────────────────────────────
|
|
407
|
+
const SHOW_VIEW_MODES = new Set(["toc", "frontmatter", "full", "section", "lines"]);
|
|
408
|
+
/**
|
|
409
|
+
* Normalize argv so positional view-mode arguments after the asset ref
|
|
410
|
+
* are rewritten into internal flags that citty can parse.
|
|
411
|
+
*
|
|
412
|
+
* Converts:
|
|
413
|
+
* akm show knowledge:guide.md toc → akm show knowledge:guide.md --akmView toc
|
|
414
|
+
* akm show knowledge:guide.md section Auth → akm show knowledge:guide.md --akmView section --akmHeading Auth
|
|
415
|
+
* akm show knowledge:guide.md lines 1 50 → akm show knowledge:guide.md --akmView lines --akmStart 1 --akmEnd 50
|
|
416
|
+
*
|
|
417
|
+
* Legacy `--view` is intentionally unsupported.
|
|
418
|
+
* Returns a new array; the input is never modified.
|
|
419
|
+
*/
|
|
420
|
+
export function normalizeShowArgv(argv) {
|
|
421
|
+
// argv[0]=bun argv[1]=script argv[2]=subcommand argv[3]=ref argv[4..]=rest
|
|
422
|
+
if (argv[2] !== "show")
|
|
423
|
+
return argv;
|
|
424
|
+
if (argv[3] === "proposal")
|
|
425
|
+
return argv;
|
|
426
|
+
if (argv.includes("--view") || argv.includes("--heading") || argv.includes("--start") || argv.includes("--end")) {
|
|
427
|
+
throw new UsageError('Legacy show flags are no longer supported. Use positional syntax like `akm show knowledge:guide toc` or `akm show knowledge:guide section "Auth"`.');
|
|
428
|
+
}
|
|
429
|
+
// Separate global flags from positional/show-specific args
|
|
430
|
+
const prefix = argv.slice(0, 3); // [bun, script, show]
|
|
431
|
+
const rest = argv.slice(3);
|
|
432
|
+
const globalFlags = [];
|
|
433
|
+
const showArgs = [];
|
|
434
|
+
for (let i = 0; i < rest.length; i++) {
|
|
435
|
+
const arg = rest[i];
|
|
436
|
+
if (arg === "--quiet" || arg === "-q" || arg === "--for-agent" || arg === "--for-agent=true") {
|
|
437
|
+
globalFlags.push(arg);
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (arg.startsWith("--format=") || arg.startsWith("--detail=")) {
|
|
441
|
+
globalFlags.push(arg);
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
if (arg === "--format" || arg === "--detail") {
|
|
445
|
+
globalFlags.push(arg);
|
|
446
|
+
if (rest[i + 1] !== undefined) {
|
|
447
|
+
globalFlags.push(rest[i + 1]);
|
|
448
|
+
i++;
|
|
449
|
+
}
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
showArgs.push(arg);
|
|
453
|
+
}
|
|
454
|
+
// showArgs[0] = ref, showArgs[1] = potential view mode, showArgs[2..] = view params
|
|
455
|
+
const ref = showArgs[0];
|
|
456
|
+
const viewMode = showArgs[1];
|
|
457
|
+
if (!ref || !viewMode || !SHOW_VIEW_MODES.has(viewMode)) {
|
|
458
|
+
return argv;
|
|
459
|
+
}
|
|
460
|
+
const result = [...prefix, ref, "--akmView", viewMode];
|
|
461
|
+
if (viewMode === "section") {
|
|
462
|
+
// Next arg is the heading name; pass empty string when missing so the
|
|
463
|
+
// show handler can produce a clear "section not found" error.
|
|
464
|
+
const heading = showArgs[2] ?? "";
|
|
465
|
+
result.push("--akmHeading", heading);
|
|
466
|
+
}
|
|
467
|
+
else if (viewMode === "lines") {
|
|
468
|
+
// Next two args are start and end
|
|
469
|
+
const start = showArgs[2];
|
|
470
|
+
const end = showArgs[3];
|
|
471
|
+
if (start)
|
|
472
|
+
result.push("--akmStart", start);
|
|
473
|
+
if (end)
|
|
474
|
+
result.push("--akmEnd", end);
|
|
475
|
+
}
|
|
476
|
+
result.push(...globalFlags);
|
|
477
|
+
return result;
|
|
478
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { isHttpUrl, resolveStashDir } from "../core/common";
|
|
4
|
-
import { loadConfig, loadUserConfig, saveConfig } from "../core/config";
|
|
4
|
+
import { getSources, loadConfig, loadUserConfig, saveConfig } from "../core/config";
|
|
5
5
|
import { ConfigError, UsageError } from "../core/errors";
|
|
6
6
|
import { warn } from "../core/warn";
|
|
7
7
|
import { akmIndex } from "../indexer/indexer";
|
|
@@ -74,7 +74,7 @@ async function addLocalSource(ref, sourcePath, stashDir, wikiName, explicitName)
|
|
|
74
74
|
// Derive the canonical name: explicit --name wins, then wiki name, then readable path.
|
|
75
75
|
const derivedName = explicitName ?? wikiName ?? toReadableId(resolvedPath);
|
|
76
76
|
// Check for duplicates in sources[]
|
|
77
|
-
const sources = [...(config
|
|
77
|
+
const sources = [...getSources(config)];
|
|
78
78
|
const existing = sources.find((s) => s.type === "filesystem" && s.path && path.resolve(s.path) === resolvedPath);
|
|
79
79
|
let persistedEntry;
|
|
80
80
|
if (!existing) {
|
|
@@ -85,7 +85,7 @@ async function addLocalSource(ref, sourcePath, stashDir, wikiName, explicitName)
|
|
|
85
85
|
...(wikiName ? { wikiName } : {}),
|
|
86
86
|
};
|
|
87
87
|
sources.push(persistedEntry);
|
|
88
|
-
saveConfig({ ...config, sources
|
|
88
|
+
saveConfig({ ...config, sources });
|
|
89
89
|
}
|
|
90
90
|
else {
|
|
91
91
|
let changed = false;
|
|
@@ -99,7 +99,7 @@ async function addLocalSource(ref, sourcePath, stashDir, wikiName, explicitName)
|
|
|
99
99
|
changed = true;
|
|
100
100
|
}
|
|
101
101
|
if (changed)
|
|
102
|
-
saveConfig({ ...config, sources
|
|
102
|
+
saveConfig({ ...config, sources });
|
|
103
103
|
persistedEntry = existing;
|
|
104
104
|
}
|
|
105
105
|
const index = await akmIndex({ stashDir });
|
|
@@ -116,7 +116,7 @@ async function addLocalSource(ref, sourcePath, stashDir, wikiName, explicitName)
|
|
|
116
116
|
...(persistedEntry.wikiName ? { wiki: persistedEntry.wikiName } : {}),
|
|
117
117
|
},
|
|
118
118
|
config: {
|
|
119
|
-
sourceCount: (updatedConfig
|
|
119
|
+
sourceCount: getSources(updatedConfig).length,
|
|
120
120
|
installedKitCount: updatedConfig.installed?.length ?? 0,
|
|
121
121
|
},
|
|
122
122
|
index: {
|
|
@@ -131,7 +131,7 @@ async function addLocalSource(ref, sourcePath, stashDir, wikiName, explicitName)
|
|
|
131
131
|
async function addWebsiteSource(ref, stashDir, name, options, wikiName) {
|
|
132
132
|
const normalizedUrl = validateWebsiteInputUrl(ref);
|
|
133
133
|
const config = loadUserConfig();
|
|
134
|
-
const sources = [...(config
|
|
134
|
+
const sources = [...getSources(config)];
|
|
135
135
|
let entry = sources.find((stash) => stash.type === "website" && stash.url === normalizedUrl);
|
|
136
136
|
if (!entry) {
|
|
137
137
|
entry = {
|
|
@@ -142,7 +142,7 @@ async function addWebsiteSource(ref, stashDir, name, options, wikiName) {
|
|
|
142
142
|
...(wikiName ? { wikiName } : {}),
|
|
143
143
|
};
|
|
144
144
|
sources.push(entry);
|
|
145
|
-
saveConfig({ ...config, sources
|
|
145
|
+
saveConfig({ ...config, sources });
|
|
146
146
|
}
|
|
147
147
|
else {
|
|
148
148
|
let changed = false;
|
|
@@ -155,7 +155,7 @@ async function addWebsiteSource(ref, stashDir, name, options, wikiName) {
|
|
|
155
155
|
changed = true;
|
|
156
156
|
}
|
|
157
157
|
if (changed)
|
|
158
|
-
saveConfig({ ...config, sources
|
|
158
|
+
saveConfig({ ...config, sources });
|
|
159
159
|
}
|
|
160
160
|
const cachePaths = await ensureWebsiteMirror(entry, { requireStashDir: true });
|
|
161
161
|
const index = await akmIndex({ stashDir });
|
|
@@ -172,7 +172,7 @@ async function addWebsiteSource(ref, stashDir, name, options, wikiName) {
|
|
|
172
172
|
...(entry.wikiName ? { wiki: entry.wikiName } : {}),
|
|
173
173
|
},
|
|
174
174
|
config: {
|
|
175
|
-
sourceCount: (updatedConfig
|
|
175
|
+
sourceCount: getSources(updatedConfig).length,
|
|
176
176
|
installedKitCount: updatedConfig.installed?.length ?? 0,
|
|
177
177
|
},
|
|
178
178
|
index: {
|
|
@@ -268,7 +268,7 @@ async function addRegistryStash(ref, stashDir, trustThisInstall, writable, wikiN
|
|
|
268
268
|
audit,
|
|
269
269
|
},
|
|
270
270
|
config: {
|
|
271
|
-
sourceCount: (updatedConfig
|
|
271
|
+
sourceCount: getSources(updatedConfig).length,
|
|
272
272
|
installedKitCount: updatedConfig.installed?.length ?? 0,
|
|
273
273
|
},
|
|
274
274
|
index: {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { isRemoteUrl } from "../core/common";
|
|
3
|
+
import { getSources, loadConfig, loadUserConfig, saveConfig } from "../core/config";
|
|
3
4
|
import { ConfigError, UsageError } from "../core/errors";
|
|
4
5
|
import { resolveSourceEntries } from "../indexer/search-source";
|
|
5
6
|
// ── Operations ──────────────────────────────────────────────────────────────
|
|
@@ -19,14 +20,9 @@ export function addStash(opts) {
|
|
|
19
20
|
throw new ConfigError("writable: true is only supported on filesystem and git sources", "INVALID_CONFIG_FILE");
|
|
20
21
|
}
|
|
21
22
|
const config = loadUserConfig();
|
|
22
|
-
const sources = [...(config
|
|
23
|
-
const isRemoteUrl = target.startsWith("http://") ||
|
|
24
|
-
target.startsWith("https://") ||
|
|
25
|
-
target.startsWith("git@") ||
|
|
26
|
-
target.startsWith("ssh://") ||
|
|
27
|
-
target.startsWith("git://");
|
|
23
|
+
const sources = [...getSources(config)];
|
|
28
24
|
let entry;
|
|
29
|
-
if (isRemoteUrl) {
|
|
25
|
+
if (isRemoteUrl(target)) {
|
|
30
26
|
if (!providerType) {
|
|
31
27
|
throw new UsageError("--provider is required for URL sources (e.g. --provider git --provider website)");
|
|
32
28
|
}
|
|
@@ -53,7 +49,7 @@ export function addStash(opts) {
|
|
|
53
49
|
entry.name = name;
|
|
54
50
|
}
|
|
55
51
|
sources.push(entry);
|
|
56
|
-
saveConfig({ ...config, sources
|
|
52
|
+
saveConfig({ ...config, sources });
|
|
57
53
|
return { sources, added: true, entry };
|
|
58
54
|
}
|
|
59
55
|
/**
|
|
@@ -62,16 +58,12 @@ export function addStash(opts) {
|
|
|
62
58
|
*/
|
|
63
59
|
export function removeStash(target) {
|
|
64
60
|
const config = loadUserConfig();
|
|
65
|
-
const sources = [...(config
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
target.startsWith("git@") ||
|
|
69
|
-
target.startsWith("ssh://") ||
|
|
70
|
-
target.startsWith("git://");
|
|
71
|
-
const resolvedPath = !isUrl ? path.resolve(target) : undefined;
|
|
61
|
+
const sources = [...getSources(config)];
|
|
62
|
+
const isUrlTarget = isRemoteUrl(target);
|
|
63
|
+
const resolvedPath = !isUrlTarget ? path.resolve(target) : undefined;
|
|
72
64
|
// Try URL match first, then path, then name (most specific → least specific)
|
|
73
65
|
let idx = -1;
|
|
74
|
-
if (
|
|
66
|
+
if (isUrlTarget) {
|
|
75
67
|
idx = sources.findIndex((s) => s.url === target);
|
|
76
68
|
}
|
|
77
69
|
if (idx === -1 && resolvedPath) {
|
|
@@ -84,7 +76,7 @@ export function removeStash(target) {
|
|
|
84
76
|
return { sources, removed: false, message: "No matching source found" };
|
|
85
77
|
}
|
|
86
78
|
const removed = sources.splice(idx, 1)[0];
|
|
87
|
-
saveConfig({ ...config, sources
|
|
79
|
+
saveConfig({ ...config, sources });
|
|
88
80
|
return { sources, removed: true, entry: removed };
|
|
89
81
|
}
|
|
90
82
|
/**
|
|
@@ -93,6 +85,6 @@ export function removeStash(target) {
|
|
|
93
85
|
export function listStashes() {
|
|
94
86
|
const config = loadConfig();
|
|
95
87
|
const localSources = resolveSourceEntries();
|
|
96
|
-
const sources = config
|
|
88
|
+
const sources = getSources(config);
|
|
97
89
|
return { localSources, sources };
|
|
98
90
|
}
|