akm-cli 0.4.1 → 0.5.0-rc2
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 +232 -0
- package/dist/asset-registry.js +7 -0
- package/dist/asset-spec.js +35 -0
- package/dist/cli.js +1153 -32
- package/dist/completions.js +2 -2
- package/dist/config-cli.js +41 -0
- package/dist/config.js +62 -0
- package/dist/file-context.js +2 -1
- package/dist/github.js +20 -1
- package/dist/indexer.js +60 -6
- package/dist/init.js +11 -0
- package/dist/install-audit.js +53 -8
- package/dist/installed-kits.js +2 -0
- package/dist/llm.js +64 -23
- package/dist/local-search.js +3 -1
- package/dist/matchers.js +56 -4
- package/dist/metadata.js +102 -4
- package/dist/migration-help.js +110 -0
- package/dist/paths.js +3 -0
- package/dist/registry-install.js +36 -7
- package/dist/registry-resolve.js +25 -0
- package/dist/renderers.js +182 -2
- package/dist/search-fields.js +4 -0
- package/dist/search-source.js +12 -8
- package/dist/self-update.js +86 -10
- package/dist/setup.js +158 -33
- package/dist/stash-add.js +84 -11
- package/dist/stash-providers/git.js +182 -44
- package/dist/stash-show.js +56 -1
- package/dist/stash-source-manage.js +14 -4
- package/dist/templates/wiki-templates.js +100 -0
- package/dist/vault.js +290 -0
- package/dist/wiki.js +886 -0
- package/dist/workflow-authoring.js +131 -0
- package/dist/workflow-cli.js +44 -0
- package/dist/workflow-db.js +55 -0
- package/dist/workflow-markdown.js +251 -0
- package/dist/workflow-runs.js +364 -0
- package/package.json +3 -1
package/dist/renderers.js
CHANGED
|
@@ -9,10 +9,14 @@
|
|
|
9
9
|
import fs from "node:fs";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { hasErrnoCode } from "./common";
|
|
12
|
+
import { UsageError } from "./errors";
|
|
12
13
|
import { registerRenderer } from "./file-context";
|
|
13
14
|
import { parseFrontmatter, toStringOrUndefined } from "./frontmatter";
|
|
14
15
|
import { extractFrontmatterOnly, extractLineRange, extractSection, formatToc, parseMarkdownToc } from "./markdown";
|
|
15
16
|
import { extractDescriptionFromComments, loadStashFile } from "./metadata";
|
|
17
|
+
import { makeAssetRef } from "./stash-ref";
|
|
18
|
+
import { listKeys as listVaultKeys } from "./vault";
|
|
19
|
+
import { parseWorkflowMarkdown, WorkflowValidationError } from "./workflow-markdown";
|
|
16
20
|
// ── Interpreter auto-detection map ───────────────────────────────────────────
|
|
17
21
|
const INTERPRETER_MAP = {
|
|
18
22
|
".sh": "bash",
|
|
@@ -149,6 +153,12 @@ function deriveName(ctx) {
|
|
|
149
153
|
const ext = path.extname(ctx.relPath);
|
|
150
154
|
return ext ? ctx.relPath.slice(0, -ext.length) : ctx.relPath;
|
|
151
155
|
}
|
|
156
|
+
function shellQuote(value) {
|
|
157
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
158
|
+
}
|
|
159
|
+
export function buildWorkflowAction(ref) {
|
|
160
|
+
return `Resume the active run or start a new run with \`akm workflow next ${shellQuote(ref)}\`.`;
|
|
161
|
+
}
|
|
152
162
|
/**
|
|
153
163
|
* Load the matching StashEntry for a file path from the directory's .stash.json.
|
|
154
164
|
*/
|
|
@@ -309,6 +319,85 @@ const knowledgeMdRenderer = {
|
|
|
309
319
|
}
|
|
310
320
|
},
|
|
311
321
|
};
|
|
322
|
+
// ── 4b. wiki-md ──────────────────────────────────────────────────────────────
|
|
323
|
+
const WIKI_PAGE_ACTION = "Wiki page — read below. Use 'toc' to scan, 'section <heading>' for depth.";
|
|
324
|
+
const wikiMdRenderer = {
|
|
325
|
+
name: "wiki-md",
|
|
326
|
+
buildShowResponse(ctx) {
|
|
327
|
+
const name = deriveName(ctx);
|
|
328
|
+
const v = ctx.matchResult.meta?.view ?? { mode: "full" };
|
|
329
|
+
const content = ctx.content();
|
|
330
|
+
switch (v.mode) {
|
|
331
|
+
case "toc": {
|
|
332
|
+
const toc = parseMarkdownToc(content);
|
|
333
|
+
return {
|
|
334
|
+
type: "wiki",
|
|
335
|
+
name,
|
|
336
|
+
path: ctx.absPath,
|
|
337
|
+
action: WIKI_PAGE_ACTION,
|
|
338
|
+
content: formatToc(toc),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
case "frontmatter": {
|
|
342
|
+
const fm = extractFrontmatterOnly(content);
|
|
343
|
+
return {
|
|
344
|
+
type: "wiki",
|
|
345
|
+
name,
|
|
346
|
+
path: ctx.absPath,
|
|
347
|
+
action: WIKI_PAGE_ACTION,
|
|
348
|
+
content: fm ?? "(no frontmatter)",
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
case "section": {
|
|
352
|
+
const section = extractSection(content, v.heading);
|
|
353
|
+
if (!section) {
|
|
354
|
+
return {
|
|
355
|
+
type: "wiki",
|
|
356
|
+
name,
|
|
357
|
+
path: ctx.absPath,
|
|
358
|
+
action: WIKI_PAGE_ACTION,
|
|
359
|
+
content: `Section "${v.heading}" not found in ${name}. Try \`akm show wiki:${name} toc\` to discover available headings.`,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
type: "wiki",
|
|
364
|
+
name,
|
|
365
|
+
path: ctx.absPath,
|
|
366
|
+
action: WIKI_PAGE_ACTION,
|
|
367
|
+
content: section.content,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
case "lines": {
|
|
371
|
+
return {
|
|
372
|
+
type: "wiki",
|
|
373
|
+
name,
|
|
374
|
+
path: ctx.absPath,
|
|
375
|
+
action: WIKI_PAGE_ACTION,
|
|
376
|
+
content: extractLineRange(content, v.start, v.end),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
default: {
|
|
380
|
+
return {
|
|
381
|
+
type: "wiki",
|
|
382
|
+
name,
|
|
383
|
+
path: ctx.absPath,
|
|
384
|
+
action: WIKI_PAGE_ACTION,
|
|
385
|
+
content,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
extractMetadata(entry, ctx) {
|
|
391
|
+
try {
|
|
392
|
+
const toc = parseMarkdownToc(ctx.content());
|
|
393
|
+
if (toc.headings.length > 0)
|
|
394
|
+
entry.toc = toc.headings;
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
// Non-fatal: skip TOC if file can't be read
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
};
|
|
312
401
|
// ── 5. memory-md ─────────────────────────────────────────────────────────────
|
|
313
402
|
const memoryMdRenderer = {
|
|
314
403
|
name: "memory-md",
|
|
@@ -323,7 +412,58 @@ const memoryMdRenderer = {
|
|
|
323
412
|
};
|
|
324
413
|
},
|
|
325
414
|
};
|
|
326
|
-
// ── 6.
|
|
415
|
+
// ── 6. workflow-md ───────────────────────────────────────────────────────────
|
|
416
|
+
const workflowMdRenderer = {
|
|
417
|
+
name: "workflow-md",
|
|
418
|
+
buildShowResponse(ctx) {
|
|
419
|
+
const name = deriveName(ctx);
|
|
420
|
+
const workflow = parseWorkflowForRendering(ctx.content());
|
|
421
|
+
const ref = makeAssetRef("workflow", name, ctx.origin);
|
|
422
|
+
return {
|
|
423
|
+
type: "workflow",
|
|
424
|
+
name,
|
|
425
|
+
path: ctx.absPath,
|
|
426
|
+
action: buildWorkflowAction(ref),
|
|
427
|
+
description: workflow.description,
|
|
428
|
+
workflowTitle: workflow.title,
|
|
429
|
+
parameters: workflow.parameters?.map((parameter) => parameter.name),
|
|
430
|
+
workflowParameters: workflow.parameters,
|
|
431
|
+
steps: workflow.steps,
|
|
432
|
+
};
|
|
433
|
+
},
|
|
434
|
+
extractMetadata(entry, ctx) {
|
|
435
|
+
const workflow = parseWorkflowForRendering(ctx.content());
|
|
436
|
+
const hints = new Set(entry.searchHints ?? []);
|
|
437
|
+
hints.add(workflow.title);
|
|
438
|
+
for (const step of workflow.steps) {
|
|
439
|
+
hints.add(step.title);
|
|
440
|
+
hints.add(step.id);
|
|
441
|
+
hints.add(step.instructions);
|
|
442
|
+
for (const criterion of step.completionCriteria ?? []) {
|
|
443
|
+
hints.add(criterion);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
entry.searchHints = Array.from(hints).filter(Boolean);
|
|
447
|
+
if (workflow.parameters?.length) {
|
|
448
|
+
entry.parameters = workflow.parameters.map((parameter) => ({
|
|
449
|
+
name: parameter.name,
|
|
450
|
+
...(parameter.description ? { description: parameter.description } : {}),
|
|
451
|
+
}));
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
};
|
|
455
|
+
function parseWorkflowForRendering(content) {
|
|
456
|
+
try {
|
|
457
|
+
return parseWorkflowMarkdown(content);
|
|
458
|
+
}
|
|
459
|
+
catch (error) {
|
|
460
|
+
if (error instanceof WorkflowValidationError) {
|
|
461
|
+
throw new UsageError(error.message);
|
|
462
|
+
}
|
|
463
|
+
throw error;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
// ── 7. script-source ─────────────────────────────────────────────────────────
|
|
327
467
|
const scriptSourceRenderer = {
|
|
328
468
|
name: "script-source",
|
|
329
469
|
buildShowResponse(ctx) {
|
|
@@ -379,6 +519,43 @@ const scriptSourceRenderer = {
|
|
|
379
519
|
}
|
|
380
520
|
},
|
|
381
521
|
};
|
|
522
|
+
// ── 8. vault-env ─────────────────────────────────────────────────────────────
|
|
523
|
+
/**
|
|
524
|
+
* Vault renderer. Returns ONLY key names and start-of-line comments — never
|
|
525
|
+
* values. Deliberately omits content/template/prompt so vault values cannot
|
|
526
|
+
* leak through `akm show`.
|
|
527
|
+
*/
|
|
528
|
+
const vaultEnvRenderer = {
|
|
529
|
+
name: "vault-env",
|
|
530
|
+
buildShowResponse(ctx) {
|
|
531
|
+
const name = deriveName(ctx);
|
|
532
|
+
const { keys, comments } = listVaultKeys(ctx.absPath);
|
|
533
|
+
return {
|
|
534
|
+
type: "vault",
|
|
535
|
+
name,
|
|
536
|
+
path: ctx.absPath,
|
|
537
|
+
action: 'Vault — keys + comments only. Use `eval "$(akm vault load <ref>)"` to load values into the current shell. Values stay on disk and are never written to akm\'s stdout.',
|
|
538
|
+
description: comments.length > 0 ? comments.join("\n") : undefined,
|
|
539
|
+
keys,
|
|
540
|
+
comments,
|
|
541
|
+
};
|
|
542
|
+
},
|
|
543
|
+
extractMetadata(entry, ctx) {
|
|
544
|
+
// Re-derive from the file directly to guarantee no value ever transits
|
|
545
|
+
// through any other code path. Caller already short-circuits in
|
|
546
|
+
// generateMetadata{,Flat}, but this is defense in depth.
|
|
547
|
+
const { keys, comments } = listVaultKeys(ctx.absPath);
|
|
548
|
+
if (comments.length > 0 && !entry.description) {
|
|
549
|
+
entry.description = comments.join(" ").slice(0, 500);
|
|
550
|
+
entry.source = "comments";
|
|
551
|
+
entry.confidence = 0.7;
|
|
552
|
+
}
|
|
553
|
+
if (keys.length > 0) {
|
|
554
|
+
entry.searchHints = keys;
|
|
555
|
+
}
|
|
556
|
+
entry.tags = Array.from(new Set([...(entry.tags ?? []), "vault", "secrets"]));
|
|
557
|
+
},
|
|
558
|
+
};
|
|
382
559
|
// ── Registration ─────────────────────────────────────────────────────────────
|
|
383
560
|
/** All built-in renderers. */
|
|
384
561
|
const builtinRenderers = [
|
|
@@ -386,8 +563,11 @@ const builtinRenderers = [
|
|
|
386
563
|
commandMdRenderer,
|
|
387
564
|
agentMdRenderer,
|
|
388
565
|
knowledgeMdRenderer,
|
|
566
|
+
wikiMdRenderer,
|
|
389
567
|
memoryMdRenderer,
|
|
568
|
+
workflowMdRenderer,
|
|
390
569
|
scriptSourceRenderer,
|
|
570
|
+
vaultEnvRenderer,
|
|
391
571
|
];
|
|
392
572
|
/**
|
|
393
573
|
* Register all built-in renderers with the file-context registry.
|
|
@@ -399,4 +579,4 @@ export function registerBuiltinRenderers() {
|
|
|
399
579
|
}
|
|
400
580
|
}
|
|
401
581
|
// ── Named exports for testing ────────────────────────────────────────────────
|
|
402
|
-
export { agentMdRenderer, commandMdRenderer, INTERPRETER_MAP, knowledgeMdRenderer, memoryMdRenderer, SETUP_SIGNALS, scriptSourceRenderer, skillMdRenderer, };
|
|
582
|
+
export { agentMdRenderer, commandMdRenderer, INTERPRETER_MAP, knowledgeMdRenderer, memoryMdRenderer, SETUP_SIGNALS, scriptSourceRenderer, skillMdRenderer, vaultEnvRenderer, wikiMdRenderer, workflowMdRenderer, };
|
package/dist/search-fields.js
CHANGED
|
@@ -41,6 +41,10 @@ export function buildSearchFields(entry) {
|
|
|
41
41
|
if (entry.intent.output)
|
|
42
42
|
hintParts.push(entry.intent.output);
|
|
43
43
|
}
|
|
44
|
+
if (entry.xrefs)
|
|
45
|
+
hintParts.push(entry.xrefs.join(" "));
|
|
46
|
+
if (entry.pageKind)
|
|
47
|
+
hintParts.push(entry.pageKind);
|
|
44
48
|
const hints = hintParts.join(" ").toLowerCase();
|
|
45
49
|
const contentParts = [];
|
|
46
50
|
if (entry.toc) {
|
package/dist/search-source.js
CHANGED
|
@@ -20,7 +20,7 @@ export function resolveStashSources(overrideStashDir, existingConfig) {
|
|
|
20
20
|
const config = existingConfig ?? loadConfig();
|
|
21
21
|
const sources = [{ path: stashDir }];
|
|
22
22
|
const seen = new Set([path.resolve(stashDir)]);
|
|
23
|
-
const addSource = (dir, registryId) => {
|
|
23
|
+
const addSource = (dir, registryId, wikiName) => {
|
|
24
24
|
const resolved = path.resolve(dir);
|
|
25
25
|
if (seen.has(resolved))
|
|
26
26
|
return;
|
|
@@ -29,17 +29,21 @@ export function resolveStashSources(overrideStashDir, existingConfig) {
|
|
|
29
29
|
warn(`Warning: stash root "${dir}" appears to be a system directory. This may be unintentional.`);
|
|
30
30
|
}
|
|
31
31
|
if (isValidDirectory(dir)) {
|
|
32
|
-
sources.push({
|
|
32
|
+
sources.push({
|
|
33
|
+
path: resolved,
|
|
34
|
+
...(registryId ? { registryId } : {}),
|
|
35
|
+
...(wikiName ? { wikiName } : {}),
|
|
36
|
+
});
|
|
33
37
|
}
|
|
34
38
|
};
|
|
35
39
|
// Filesystem entries from stashes[]
|
|
36
40
|
for (const entry of config.stashes ?? []) {
|
|
37
41
|
if (entry.type === "filesystem" && entry.path && entry.enabled !== false) {
|
|
38
|
-
addSource(entry.path, entry.name);
|
|
42
|
+
addSource(entry.path, entry.name, entry.wikiName);
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
// Git stash entries: resolve cache directory so the indexer can walk it.
|
|
42
|
-
// "context-hub", "github"
|
|
46
|
+
// "git" provider type (and its legacy aliases "context-hub", "github") are handled.
|
|
43
47
|
for (const entry of config.stashes ?? []) {
|
|
44
48
|
if (GIT_STASH_TYPES.has(entry.type) && entry.url && entry.enabled !== false) {
|
|
45
49
|
try {
|
|
@@ -48,7 +52,7 @@ export function resolveStashSources(overrideStashDir, existingConfig) {
|
|
|
48
52
|
// The content/ subdirectory inside the extracted repo is the actual
|
|
49
53
|
// stash root containing DOC.md / SKILL.md files that the walker indexes.
|
|
50
54
|
const contentDir = path.join(cachePaths.repoDir, "content");
|
|
51
|
-
addSource(contentDir, entry.name);
|
|
55
|
+
addSource(contentDir, entry.name, entry.wikiName);
|
|
52
56
|
}
|
|
53
57
|
catch (err) {
|
|
54
58
|
warn(`Warning: failed to resolve git stash cache for "${entry.url}": ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -61,7 +65,7 @@ export function resolveStashSources(overrideStashDir, existingConfig) {
|
|
|
61
65
|
if (entry.type === "website" && entry.url && entry.enabled !== false) {
|
|
62
66
|
try {
|
|
63
67
|
const cachePaths = getWebsiteCachePaths(entry.url);
|
|
64
|
-
addSource(cachePaths.stashDir, entry.name ?? entry.url);
|
|
68
|
+
addSource(cachePaths.stashDir, entry.name ?? entry.url, entry.wikiName);
|
|
65
69
|
}
|
|
66
70
|
catch (err) {
|
|
67
71
|
warn(`Warning: failed to resolve website stash cache for "${entry.url}": ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -70,7 +74,7 @@ export function resolveStashSources(overrideStashDir, existingConfig) {
|
|
|
70
74
|
}
|
|
71
75
|
// Installed kits (registry and local)
|
|
72
76
|
for (const entry of config.installed ?? []) {
|
|
73
|
-
addSource(entry.stashRoot, entry.id);
|
|
77
|
+
addSource(entry.stashRoot, entry.id, entry.wikiName);
|
|
74
78
|
}
|
|
75
79
|
return sources;
|
|
76
80
|
}
|
|
@@ -180,7 +184,7 @@ export async function ensureStashCaches(config) {
|
|
|
180
184
|
try {
|
|
181
185
|
const repo = parseGitRepoUrl(entry.url);
|
|
182
186
|
const cachePaths = getCachePaths(repo.canonicalUrl);
|
|
183
|
-
await ensureGitMirror(repo, cachePaths, { requireRepoDir: true });
|
|
187
|
+
await ensureGitMirror(repo, cachePaths, { requireRepoDir: true, writable: entry.writable === true });
|
|
184
188
|
}
|
|
185
189
|
catch (err) {
|
|
186
190
|
warn(`Warning: failed to refresh git mirror for "${entry.url}": ${err instanceof Error ? err.message : String(err)}`);
|
package/dist/self-update.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import * as childProcess from "node:child_process";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { fetchWithRetry, IS_WINDOWS } from "./common";
|
|
5
6
|
import { githubHeaders } from "./github";
|
|
6
7
|
const REPO = "itlackey/akm";
|
|
8
|
+
const DEFAULT_PACKAGE_NAME = "akm-cli";
|
|
9
|
+
const NODE_MODULES_SEGMENT = "/node_modules/";
|
|
10
|
+
const BUN_GLOBAL_INSTALL_PATTERN = /(^|\/)\.bun\/(?:[^/]+\/)+node_modules\//;
|
|
11
|
+
const PNPM_GLOBAL_INSTALL_PATTERN = /(^|\/)(?:pnpm\/global|\.pnpm-global)(?:\/\d+)?\/node_modules\//;
|
|
7
12
|
/** Read live runtime signals. */
|
|
8
13
|
export function getInstallSignals() {
|
|
9
14
|
return {
|
|
@@ -15,8 +20,14 @@ export function getInstallSignals() {
|
|
|
15
20
|
// AKM_VERSION ambient type is declared in globals.d.ts
|
|
16
21
|
export function detectInstallMethod(signals) {
|
|
17
22
|
const s = signals ?? getInstallSignals();
|
|
18
|
-
|
|
19
|
-
if (
|
|
23
|
+
const normalizedImportMetaDir = normalizePathSeparators(s.importMetaDir);
|
|
24
|
+
if (normalizedImportMetaDir.includes(NODE_MODULES_SEGMENT)) {
|
|
25
|
+
if (BUN_GLOBAL_INSTALL_PATTERN.test(normalizedImportMetaDir)) {
|
|
26
|
+
return "bun";
|
|
27
|
+
}
|
|
28
|
+
if (PNPM_GLOBAL_INSTALL_PATTERN.test(normalizedImportMetaDir)) {
|
|
29
|
+
return "pnpm";
|
|
30
|
+
}
|
|
20
31
|
return "npm";
|
|
21
32
|
}
|
|
22
33
|
// Bun-compiled binaries: Bun.main points to a virtual /$bunfs/ path,
|
|
@@ -69,34 +80,51 @@ export async function checkForUpdate(currentVersion) {
|
|
|
69
80
|
export async function performUpgrade(check, opts) {
|
|
70
81
|
const { currentVersion, latestVersion, installMethod } = check;
|
|
71
82
|
const force = opts?.force === true;
|
|
72
|
-
|
|
83
|
+
// All install methods can short-circuit here unless the user explicitly forces an upgrade.
|
|
84
|
+
if (!check.updateAvailable && !force) {
|
|
73
85
|
return {
|
|
74
86
|
currentVersion,
|
|
75
87
|
newVersion: latestVersion,
|
|
76
88
|
upgraded: false,
|
|
77
89
|
installMethod,
|
|
78
|
-
message: `akm
|
|
90
|
+
message: `akm v${currentVersion} is already the latest version`,
|
|
79
91
|
};
|
|
80
92
|
}
|
|
81
|
-
|
|
93
|
+
const packageManagerCommand = getPackageManagerUpgradeCommand(installMethod);
|
|
94
|
+
if (packageManagerCommand) {
|
|
95
|
+
if (!latestVersion) {
|
|
96
|
+
throw new Error("Unable to determine latest version from GitHub releases. Check https://github.com/itlackey/akm/releases");
|
|
97
|
+
}
|
|
98
|
+
const result = childProcess.spawnSync(packageManagerCommand.command, packageManagerCommand.args, {
|
|
99
|
+
encoding: "utf8",
|
|
100
|
+
env: process.env,
|
|
101
|
+
stdio: "pipe",
|
|
102
|
+
});
|
|
103
|
+
if (result.error) {
|
|
104
|
+
throw new Error(`Failed to run '${packageManagerCommand.displayCommand}': ${result.error.message}`);
|
|
105
|
+
}
|
|
106
|
+
if (result.status !== 0) {
|
|
107
|
+
const details = (result.stderr ?? "").trim() || (result.stdout ?? "").trim() || `exit code ${result.status}`;
|
|
108
|
+
throw new Error(`Failed to upgrade akm via ${installMethod}: ${details}\nRun manually: ${packageManagerCommand.displayCommand}`);
|
|
109
|
+
}
|
|
82
110
|
return {
|
|
83
111
|
currentVersion,
|
|
84
112
|
newVersion: latestVersion,
|
|
85
|
-
upgraded:
|
|
113
|
+
upgraded: true,
|
|
86
114
|
installMethod,
|
|
87
|
-
message: `
|
|
115
|
+
message: `akm upgraded via ${installMethod}`,
|
|
88
116
|
};
|
|
89
117
|
}
|
|
90
|
-
|
|
91
|
-
if (!check.updateAvailable && !force) {
|
|
118
|
+
if (installMethod === "unknown") {
|
|
92
119
|
return {
|
|
93
120
|
currentVersion,
|
|
94
121
|
newVersion: latestVersion,
|
|
95
122
|
upgraded: false,
|
|
96
123
|
installMethod,
|
|
97
|
-
message: `
|
|
124
|
+
message: `Unable to detect install method. Upgrade manually from https://github.com/${REPO}/releases`,
|
|
98
125
|
};
|
|
99
126
|
}
|
|
127
|
+
// Binary install
|
|
100
128
|
if (!latestVersion) {
|
|
101
129
|
throw new Error("Unable to determine latest version from GitHub releases. Check https://github.com/itlackey/akm/releases");
|
|
102
130
|
}
|
|
@@ -270,3 +298,51 @@ function parseChecksumForFile(checksumsText, filename) {
|
|
|
270
298
|
}
|
|
271
299
|
return undefined;
|
|
272
300
|
}
|
|
301
|
+
function normalizePathSeparators(value) {
|
|
302
|
+
return (value ?? "").replaceAll("\\", "/");
|
|
303
|
+
}
|
|
304
|
+
function getInstalledPackageName() {
|
|
305
|
+
try {
|
|
306
|
+
const pkgPath = path.resolve(import.meta.dir ?? __dirname, "../package.json");
|
|
307
|
+
if (fs.existsSync(pkgPath)) {
|
|
308
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
309
|
+
if (typeof pkg.name === "string" && pkg.name.trim()) {
|
|
310
|
+
return pkg.name.trim();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
// Swallow and fall back to default package name.
|
|
316
|
+
}
|
|
317
|
+
return DEFAULT_PACKAGE_NAME;
|
|
318
|
+
}
|
|
319
|
+
function resolveNodePackageManagerCommand(name) {
|
|
320
|
+
const extension = IS_WINDOWS ? ".cmd" : "";
|
|
321
|
+
const adjacent = path.join(path.dirname(process.execPath), `${name}${extension}`);
|
|
322
|
+
return fs.existsSync(adjacent) ? adjacent : name;
|
|
323
|
+
}
|
|
324
|
+
export function getPackageManagerUpgradeCommand(installMethod, packageName = getInstalledPackageName()) {
|
|
325
|
+
const pkgRef = `${packageName}@latest`;
|
|
326
|
+
if (installMethod === "bun") {
|
|
327
|
+
return {
|
|
328
|
+
command: "bun",
|
|
329
|
+
args: ["install", "-g", pkgRef],
|
|
330
|
+
displayCommand: `bun install -g ${pkgRef}`,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
if (installMethod === "pnpm") {
|
|
334
|
+
return {
|
|
335
|
+
command: resolveNodePackageManagerCommand("pnpm"),
|
|
336
|
+
args: ["add", "-g", pkgRef],
|
|
337
|
+
displayCommand: `pnpm add -g ${pkgRef}`,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
if (installMethod === "npm") {
|
|
341
|
+
return {
|
|
342
|
+
command: resolveNodePackageManagerCommand("npm"),
|
|
343
|
+
args: ["install", "-g", pkgRef],
|
|
344
|
+
displayCommand: `npm install -g ${pkgRef}`,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|