akm-cli 0.3.0 → 0.4.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/dist/cli.js +366 -11
- package/dist/common.js +5 -0
- package/dist/config-cli.js +87 -0
- package/dist/config.js +197 -25
- package/dist/indexer.js +2 -2
- package/dist/init.js +2 -2
- package/dist/install-audit.js +324 -0
- package/dist/installed-kits.js +2 -2
- package/dist/matchers.js +25 -22
- package/dist/registry-install.js +46 -7
- package/dist/search-source.js +33 -6
- package/dist/setup.js +2 -2
- package/dist/stash-add.js +75 -3
- package/dist/stash-providers/index.js +1 -0
- package/dist/stash-providers/website.js +443 -0
- package/dist/stash-source-manage.js +3 -3
- package/package.json +1 -1
package/dist/matchers.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* - `extensionMatcher` (3) -- classifies any file by extension alone.
|
|
9
9
|
* Ensures every known file type is discoverable regardless of directory.
|
|
10
|
-
* - `directoryMatcher` (10) -- boosts specificity when
|
|
10
|
+
* - `directoryMatcher` (10) -- boosts specificity when an ancestor
|
|
11
11
|
* directory matches a known type name (e.g. `scripts/`, `agents/`).
|
|
12
12
|
* - `parentDirHintMatcher` (15) -- boosts specificity based on the
|
|
13
13
|
* immediate parent directory name.
|
|
@@ -43,31 +43,34 @@ export function extensionMatcher(ctx) {
|
|
|
43
43
|
}
|
|
44
44
|
// ── directoryMatcher (specificity: 10) ──────────────────────────────────────
|
|
45
45
|
/**
|
|
46
|
-
* Directory-based matcher that boosts specificity when
|
|
46
|
+
* Directory-based matcher that boosts specificity when an ancestor
|
|
47
47
|
* directory segment from the stash root matches a known type name.
|
|
48
|
+
*
|
|
49
|
+
* The first matching type-like ancestor wins. This preserves intuitive
|
|
50
|
+
* behavior for nested kit layouts such as `agent-stash/agents/blog/foo.md`
|
|
51
|
+
* while still honoring earlier type roots like `commands/agents/foo.md`.
|
|
48
52
|
*/
|
|
49
53
|
export function directoryMatcher(ctx) {
|
|
50
|
-
const topDir = ctx.ancestorDirs[0];
|
|
51
|
-
if (!topDir)
|
|
52
|
-
return null;
|
|
53
54
|
const ext = ctx.ext;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
55
|
+
for (const dir of ctx.ancestorDirs) {
|
|
56
|
+
if (dir === "scripts" && SCRIPT_EXTENSIONS.has(ext)) {
|
|
57
|
+
return { type: "script", specificity: 10, renderer: "script-source" };
|
|
58
|
+
}
|
|
59
|
+
if (dir === "skills" && ctx.fileName === "SKILL.md") {
|
|
60
|
+
return { type: "skill", specificity: 10, renderer: "skill-md" };
|
|
61
|
+
}
|
|
62
|
+
if (dir === "commands" && ext === ".md") {
|
|
63
|
+
return { type: "command", specificity: 10, renderer: "command-md" };
|
|
64
|
+
}
|
|
65
|
+
if (dir === "agents" && ext === ".md") {
|
|
66
|
+
return { type: "agent", specificity: 10, renderer: "agent-md" };
|
|
67
|
+
}
|
|
68
|
+
if (dir === "knowledge" && ext === ".md") {
|
|
69
|
+
return { type: "knowledge", specificity: 10, renderer: "knowledge-md" };
|
|
70
|
+
}
|
|
71
|
+
if (dir === "memories" && ext === ".md") {
|
|
72
|
+
return { type: "memory", specificity: 10, renderer: "memory-md" };
|
|
73
|
+
}
|
|
71
74
|
}
|
|
72
75
|
return null;
|
|
73
76
|
}
|
package/dist/registry-install.js
CHANGED
|
@@ -4,7 +4,8 @@ import fs from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { TYPE_DIRS } from "./asset-spec";
|
|
6
6
|
import { fetchWithRetry, isWithin } from "./common";
|
|
7
|
-
import { loadConfig, saveConfig } from "./config";
|
|
7
|
+
import { loadConfig, loadUserConfig, saveConfig } from "./config";
|
|
8
|
+
import { auditInstallCandidate, deriveRegistryLabels, enforceRegistryInstallPolicy, formatInstallAuditFailure, } from "./install-audit";
|
|
8
9
|
import { copyIncludedPaths, findNearestIncludeConfig } from "./kit-include";
|
|
9
10
|
import { getRegistryCacheDir as _getRegistryCacheDir } from "./paths";
|
|
10
11
|
import { parseRegistryRef, resolveRegistryArtifact, validateGitRef, validateGitUrl } from "./registry-resolve";
|
|
@@ -12,13 +13,20 @@ import { warn } from "./warn";
|
|
|
12
13
|
const REGISTRY_STASH_DIR_NAMES = new Set(Object.values(TYPE_DIRS));
|
|
13
14
|
export async function installRegistryRef(ref, options) {
|
|
14
15
|
const parsed = parseRegistryRef(ref);
|
|
16
|
+
const config = loadConfig();
|
|
15
17
|
if (parsed.source === "local") {
|
|
16
|
-
return installLocalRegistryRef(parsed, options);
|
|
18
|
+
return installLocalRegistryRef(parsed, config, options);
|
|
17
19
|
}
|
|
18
20
|
if (parsed.source === "git") {
|
|
19
|
-
return installGitRegistryRef(parsed, options);
|
|
21
|
+
return installGitRegistryRef(parsed, config, options);
|
|
20
22
|
}
|
|
21
23
|
const resolved = await resolveRegistryArtifact(parsed);
|
|
24
|
+
const registryLabels = deriveRegistryLabels({
|
|
25
|
+
source: resolved.source,
|
|
26
|
+
ref: resolved.ref,
|
|
27
|
+
artifactUrl: resolved.artifactUrl,
|
|
28
|
+
});
|
|
29
|
+
enforceRegistryInstallPolicy(registryLabels, config, ref);
|
|
22
30
|
const installedAt = (options?.now ?? new Date()).toISOString();
|
|
23
31
|
const cacheRootDir = options?.cacheRootDir ?? getRegistryCacheRootDir();
|
|
24
32
|
const cacheDir = buildInstallCacheDir(cacheRootDir, resolved.source, resolved.id, resolved.resolvedVersion ?? resolved.resolvedRevision);
|
|
@@ -30,6 +38,7 @@ export async function installRegistryRef(ref, options) {
|
|
|
30
38
|
const cachedStashRoot = detectStashRoot(extractedDir);
|
|
31
39
|
if (cachedStashRoot) {
|
|
32
40
|
const integrity = fs.existsSync(archivePath) ? await computeFileHash(archivePath) : undefined;
|
|
41
|
+
const audit = runInstallAuditOrThrow(extractedDir, resolved.source, resolved.ref, registryLabels, config);
|
|
33
42
|
return {
|
|
34
43
|
id: resolved.id,
|
|
35
44
|
source: resolved.source,
|
|
@@ -42,6 +51,7 @@ export async function installRegistryRef(ref, options) {
|
|
|
42
51
|
extractedDir,
|
|
43
52
|
stashRoot: cachedStashRoot,
|
|
44
53
|
integrity,
|
|
54
|
+
audit,
|
|
45
55
|
};
|
|
46
56
|
}
|
|
47
57
|
}
|
|
@@ -54,11 +64,13 @@ export async function installRegistryRef(ref, options) {
|
|
|
54
64
|
let provisionalKitRoot;
|
|
55
65
|
let installRoot;
|
|
56
66
|
let stashRoot;
|
|
67
|
+
let audit;
|
|
57
68
|
try {
|
|
58
69
|
await downloadArchive(resolved.artifactUrl, archivePath);
|
|
59
70
|
verifyArchiveIntegrity(archivePath, resolved.resolvedRevision, resolved.source);
|
|
60
71
|
integrity = await computeFileHash(archivePath);
|
|
61
72
|
extractTarGzSecure(archivePath, extractedDir);
|
|
73
|
+
audit = runInstallAuditOrThrow(extractedDir, resolved.source, resolved.ref, registryLabels, config);
|
|
62
74
|
provisionalKitRoot = detectStashRoot(extractedDir);
|
|
63
75
|
installRoot = applyAkmIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
|
|
64
76
|
stashRoot = detectStashRoot(installRoot);
|
|
@@ -86,11 +98,18 @@ export async function installRegistryRef(ref, options) {
|
|
|
86
98
|
extractedDir,
|
|
87
99
|
stashRoot,
|
|
88
100
|
integrity,
|
|
101
|
+
audit,
|
|
89
102
|
};
|
|
90
103
|
}
|
|
91
|
-
async function installLocalRegistryRef(parsed, options) {
|
|
104
|
+
async function installLocalRegistryRef(parsed, config, options) {
|
|
92
105
|
const resolved = await resolveRegistryArtifact(parsed);
|
|
93
106
|
const installedAt = (options?.now ?? new Date()).toISOString();
|
|
107
|
+
const registryLabels = deriveRegistryLabels({
|
|
108
|
+
source: resolved.source,
|
|
109
|
+
ref: resolved.ref,
|
|
110
|
+
artifactUrl: resolved.artifactUrl,
|
|
111
|
+
});
|
|
112
|
+
const audit = runInstallAuditOrThrow(parsed.sourcePath, resolved.source, resolved.ref, registryLabels, config);
|
|
94
113
|
// For local directories, detect the stash root within the source path.
|
|
95
114
|
// If no nested stash is found, the source path itself is used.
|
|
96
115
|
const stashRoot = detectStashRoot(parsed.sourcePath);
|
|
@@ -105,10 +124,18 @@ async function installLocalRegistryRef(parsed, options) {
|
|
|
105
124
|
cacheDir: parsed.sourcePath,
|
|
106
125
|
extractedDir: parsed.sourcePath,
|
|
107
126
|
stashRoot,
|
|
127
|
+
audit,
|
|
108
128
|
};
|
|
109
129
|
}
|
|
110
|
-
async function installGitRegistryRef(parsed, options) {
|
|
130
|
+
async function installGitRegistryRef(parsed, config, options) {
|
|
111
131
|
const resolved = await resolveRegistryArtifact(parsed);
|
|
132
|
+
const registryLabels = deriveRegistryLabels({
|
|
133
|
+
source: resolved.source,
|
|
134
|
+
ref: resolved.ref,
|
|
135
|
+
artifactUrl: resolved.artifactUrl,
|
|
136
|
+
gitUrl: parsed.url,
|
|
137
|
+
});
|
|
138
|
+
enforceRegistryInstallPolicy(registryLabels, config, parsed.ref);
|
|
112
139
|
const installedAt = (options?.now ?? new Date()).toISOString();
|
|
113
140
|
const cacheRootDir = options?.cacheRootDir ?? getRegistryCacheRootDir();
|
|
114
141
|
const cacheDir = buildInstallCacheDir(cacheRootDir, parsed.source, parsed.id, resolved.resolvedRevision);
|
|
@@ -121,6 +148,7 @@ async function installGitRegistryRef(parsed, options) {
|
|
|
121
148
|
const installRoot = applyAkmIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
|
|
122
149
|
const stashRoot = detectStashRoot(installRoot);
|
|
123
150
|
if (stashRoot) {
|
|
151
|
+
const audit = runInstallAuditOrThrow(extractedDir, resolved.source, resolved.ref, registryLabels, config);
|
|
124
152
|
return {
|
|
125
153
|
id: resolved.id,
|
|
126
154
|
source: resolved.source,
|
|
@@ -132,6 +160,7 @@ async function installGitRegistryRef(parsed, options) {
|
|
|
132
160
|
cacheDir,
|
|
133
161
|
extractedDir,
|
|
134
162
|
stashRoot,
|
|
163
|
+
audit,
|
|
135
164
|
};
|
|
136
165
|
}
|
|
137
166
|
}
|
|
@@ -147,6 +176,7 @@ async function installGitRegistryRef(parsed, options) {
|
|
|
147
176
|
let provisionalKitRoot;
|
|
148
177
|
let installRoot;
|
|
149
178
|
let stashRoot;
|
|
179
|
+
let audit;
|
|
150
180
|
try {
|
|
151
181
|
const cloneArgs = ["clone", "--depth", "1"];
|
|
152
182
|
if (parsed.requestedRef) {
|
|
@@ -163,6 +193,7 @@ async function installGitRegistryRef(parsed, options) {
|
|
|
163
193
|
copyDirectoryContents(cloneDir, extractedDir);
|
|
164
194
|
// Clean up the clone dir
|
|
165
195
|
fs.rmSync(cloneDir, { recursive: true, force: true });
|
|
196
|
+
audit = runInstallAuditOrThrow(extractedDir, resolved.source, resolved.ref, registryLabels, config);
|
|
166
197
|
provisionalKitRoot = detectStashRoot(extractedDir);
|
|
167
198
|
installRoot = applyAkmIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
|
|
168
199
|
stashRoot = detectStashRoot(installRoot);
|
|
@@ -189,10 +220,11 @@ async function installGitRegistryRef(parsed, options) {
|
|
|
189
220
|
cacheDir,
|
|
190
221
|
extractedDir,
|
|
191
222
|
stashRoot,
|
|
223
|
+
audit,
|
|
192
224
|
};
|
|
193
225
|
}
|
|
194
226
|
export function upsertInstalledRegistryEntry(entry) {
|
|
195
|
-
const current =
|
|
227
|
+
const current = loadUserConfig();
|
|
196
228
|
const currentInstalled = current.installed ?? [];
|
|
197
229
|
const withoutExisting = currentInstalled.filter((item) => item.id !== entry.id);
|
|
198
230
|
const nextInstalled = [...withoutExisting, normalizeInstalledEntry(entry)];
|
|
@@ -204,7 +236,7 @@ export function upsertInstalledRegistryEntry(entry) {
|
|
|
204
236
|
return nextConfig;
|
|
205
237
|
}
|
|
206
238
|
export function removeInstalledRegistryEntry(id) {
|
|
207
|
-
const current =
|
|
239
|
+
const current = loadUserConfig();
|
|
208
240
|
const currentInstalled = current.installed ?? [];
|
|
209
241
|
const nextInstalled = currentInstalled.filter((item) => item.id !== id);
|
|
210
242
|
const nextConfig = {
|
|
@@ -462,3 +494,10 @@ async function computeFileHash(filePath) {
|
|
|
462
494
|
const hash = createHash("sha256").update(data).digest("hex");
|
|
463
495
|
return `sha256:${hash}`;
|
|
464
496
|
}
|
|
497
|
+
function runInstallAuditOrThrow(rootDir, source, ref, registryLabels, config) {
|
|
498
|
+
const audit = auditInstallCandidate({ rootDir, source, ref, registryLabels, config });
|
|
499
|
+
if (audit.blocked) {
|
|
500
|
+
throw new Error(formatInstallAuditFailure(ref, audit));
|
|
501
|
+
}
|
|
502
|
+
return audit;
|
|
503
|
+
}
|
package/dist/search-source.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { resolveStashDir } from "./common";
|
|
4
4
|
import { loadConfig } from "./config";
|
|
5
5
|
import { ensureGitMirror, getCachePaths, parseGitRepoUrl } from "./stash-providers/git";
|
|
6
|
+
import { ensureWebsiteMirror, getCachePaths as getWebsiteCachePaths } from "./stash-providers/website";
|
|
6
7
|
import { warn } from "./warn";
|
|
7
8
|
// ── Resolution ──────────────────────────────────────────────────────────────
|
|
8
9
|
/**
|
|
@@ -54,6 +55,19 @@ export function resolveStashSources(overrideStashDir, existingConfig) {
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
}
|
|
58
|
+
// Website stash entries: resolve cache directory so the indexer can walk
|
|
59
|
+
// the scraped markdown snapshots.
|
|
60
|
+
for (const entry of config.stashes ?? []) {
|
|
61
|
+
if (entry.type === "website" && entry.url && entry.enabled !== false) {
|
|
62
|
+
try {
|
|
63
|
+
const cachePaths = getWebsiteCachePaths(entry.url);
|
|
64
|
+
addSource(cachePaths.stashDir, entry.name ?? entry.url);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
warn(`Warning: failed to resolve website stash cache for "${entry.url}": ${err instanceof Error ? err.message : String(err)}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
57
71
|
// Installed kits (registry and local)
|
|
58
72
|
for (const entry of config.installed ?? []) {
|
|
59
73
|
addSource(entry.stashRoot, entry.id);
|
|
@@ -153,11 +167,12 @@ function isValidDirectory(dir) {
|
|
|
153
167
|
// ── Git stash cache integration ──────────────────────────────────────────────
|
|
154
168
|
const GIT_STASH_TYPES = new Set(["context-hub", "github", "git"]);
|
|
155
169
|
/**
|
|
156
|
-
* Ensure all
|
|
157
|
-
* exist on disk. Must be called (async) before
|
|
158
|
-
* the content directories pass the
|
|
170
|
+
* Ensure all cache-backed stash providers are refreshed so their cache
|
|
171
|
+
* directories exist on disk. Must be called (async) before
|
|
172
|
+
* `resolveStashSources()` so the content directories pass the
|
|
173
|
+
* `isValidDirectory()` check.
|
|
159
174
|
*/
|
|
160
|
-
export async function
|
|
175
|
+
export async function ensureStashCaches(config) {
|
|
161
176
|
const cfg = config ?? loadConfig();
|
|
162
177
|
for (const entry of cfg.stashes ?? []) {
|
|
163
178
|
if (!GIT_STASH_TYPES.has(entry.type) || !entry.url || entry.enabled === false)
|
|
@@ -171,6 +186,18 @@ export async function ensureGitCaches(config) {
|
|
|
171
186
|
warn(`Warning: failed to refresh git mirror for "${entry.url}": ${err instanceof Error ? err.message : String(err)}`);
|
|
172
187
|
}
|
|
173
188
|
}
|
|
189
|
+
for (const entry of cfg.stashes ?? []) {
|
|
190
|
+
if (entry.type !== "website" || !entry.url || entry.enabled === false)
|
|
191
|
+
continue;
|
|
192
|
+
try {
|
|
193
|
+
await ensureWebsiteMirror(entry, { requireStashDir: true });
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
warn(`Warning: failed to refresh website stash for "${entry.url}": ${err instanceof Error ? err.message : String(err)}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
174
199
|
}
|
|
175
|
-
/** @deprecated Use
|
|
176
|
-
export const
|
|
200
|
+
/** @deprecated Use ensureStashCaches instead. */
|
|
201
|
+
export const ensureGitCaches = ensureStashCaches;
|
|
202
|
+
/** @deprecated Use ensureStashCaches instead. */
|
|
203
|
+
export const ensureContextHubCaches = ensureStashCaches;
|
package/dist/setup.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import * as p from "@clack/prompts";
|
|
10
10
|
import { isHttpUrl } from "./common";
|
|
11
|
-
import { DEFAULT_CONFIG, getConfigPath,
|
|
11
|
+
import { DEFAULT_CONFIG, getConfigPath, loadUserConfig, saveConfig } from "./config";
|
|
12
12
|
import { closeDatabase, isVecAvailable, openDatabase } from "./db";
|
|
13
13
|
import { detectAgentPlatforms, detectOllama, detectOpenViking } from "./detect";
|
|
14
14
|
import { checkEmbeddingAvailability, DEFAULT_LOCAL_MODEL, isTransformersAvailable } from "./embedder";
|
|
@@ -568,7 +568,7 @@ async function stepAgentPlatforms(current) {
|
|
|
568
568
|
// ── Main Wizard ─────────────────────────────────────────────────────────────
|
|
569
569
|
export async function runSetupWizard() {
|
|
570
570
|
p.intro("akm setup");
|
|
571
|
-
const current =
|
|
571
|
+
const current = loadUserConfig();
|
|
572
572
|
const configPath = getConfigPath();
|
|
573
573
|
// Step 1: Stash directory
|
|
574
574
|
p.log.step("Step 1: Stash Directory");
|
package/dist/stash-add.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { resolveStashDir } from "./common";
|
|
4
|
-
import { loadConfig, saveConfig } from "./config";
|
|
3
|
+
import { isHttpUrl, resolveStashDir } from "./common";
|
|
4
|
+
import { loadConfig, loadUserConfig, saveConfig } from "./config";
|
|
5
5
|
import { UsageError } from "./errors";
|
|
6
6
|
import { akmIndex } from "./indexer";
|
|
7
7
|
import { upsertLockEntry } from "./lockfile";
|
|
8
8
|
import { detectStashRoot, installRegistryRef, upsertInstalledRegistryEntry } from "./registry-install";
|
|
9
9
|
import { parseRegistryRef } from "./registry-resolve";
|
|
10
|
+
import { ensureWebsiteMirror, validateWebsiteInputUrl } from "./stash-providers/website";
|
|
10
11
|
export async function akmAdd(input) {
|
|
11
12
|
const ref = input.ref.trim();
|
|
12
13
|
if (!ref)
|
|
13
14
|
throw new UsageError("Install ref or local directory is required. " +
|
|
14
15
|
"Examples: `akm add @scope/kit`, `akm add github:owner/repo`, `akm add ./local/path`");
|
|
15
16
|
const stashDir = resolveStashDir();
|
|
17
|
+
if (shouldAddAsWebsiteUrl(ref)) {
|
|
18
|
+
return addWebsiteStashSource(ref, stashDir, input.name, input.options);
|
|
19
|
+
}
|
|
16
20
|
// Detect local directory refs and route them to stashes[] instead of installed[]
|
|
17
21
|
try {
|
|
18
22
|
const parsed = parseRegistryRef(ref);
|
|
@@ -32,7 +36,7 @@ export async function akmAdd(input) {
|
|
|
32
36
|
async function addLocalStashSource(ref, sourcePath, stashDir) {
|
|
33
37
|
const stashRoot = detectStashRoot(sourcePath);
|
|
34
38
|
const resolvedPath = path.resolve(stashRoot);
|
|
35
|
-
const config =
|
|
39
|
+
const config = loadUserConfig();
|
|
36
40
|
// Check for duplicates in stashes[]
|
|
37
41
|
const stashes = [...(config.stashes ?? [])];
|
|
38
42
|
const existing = stashes.find((s) => s.type === "filesystem" && s.path && path.resolve(s.path) === resolvedPath);
|
|
@@ -69,6 +73,50 @@ async function addLocalStashSource(ref, sourcePath, stashDir) {
|
|
|
69
73
|
},
|
|
70
74
|
};
|
|
71
75
|
}
|
|
76
|
+
async function addWebsiteStashSource(ref, stashDir, name, options) {
|
|
77
|
+
const normalizedUrl = validateWebsiteInputUrl(ref);
|
|
78
|
+
const config = loadUserConfig();
|
|
79
|
+
const stashes = [...(config.stashes ?? [])];
|
|
80
|
+
let entry = stashes.find((stash) => stash.type === "website" && stash.url === normalizedUrl);
|
|
81
|
+
if (!entry) {
|
|
82
|
+
entry = {
|
|
83
|
+
type: "website",
|
|
84
|
+
url: normalizedUrl,
|
|
85
|
+
name: name ?? toWebsiteName(normalizedUrl),
|
|
86
|
+
...(options && Object.keys(options).length > 0 ? { options } : {}),
|
|
87
|
+
};
|
|
88
|
+
stashes.push(entry);
|
|
89
|
+
saveConfig({ ...config, stashes });
|
|
90
|
+
}
|
|
91
|
+
else if (options && Object.keys(options).length > 0) {
|
|
92
|
+
entry.options = { ...entry.options, ...options };
|
|
93
|
+
saveConfig({ ...config, stashes });
|
|
94
|
+
}
|
|
95
|
+
const cachePaths = await ensureWebsiteMirror(entry, { requireStashDir: true });
|
|
96
|
+
const index = await akmIndex({ stashDir });
|
|
97
|
+
const updatedConfig = loadConfig();
|
|
98
|
+
return {
|
|
99
|
+
schemaVersion: 1,
|
|
100
|
+
stashDir,
|
|
101
|
+
ref,
|
|
102
|
+
stashSource: {
|
|
103
|
+
type: "website",
|
|
104
|
+
url: normalizedUrl,
|
|
105
|
+
name: entry.name,
|
|
106
|
+
stashRoot: cachePaths.stashDir,
|
|
107
|
+
},
|
|
108
|
+
config: {
|
|
109
|
+
stashCount: updatedConfig.stashes?.length ?? 0,
|
|
110
|
+
installedKitCount: updatedConfig.installed?.length ?? 0,
|
|
111
|
+
},
|
|
112
|
+
index: {
|
|
113
|
+
mode: index.mode,
|
|
114
|
+
totalEntries: index.totalEntries,
|
|
115
|
+
directoriesScanned: index.directoriesScanned,
|
|
116
|
+
directoriesSkipped: index.directoriesSkipped,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
72
120
|
/**
|
|
73
121
|
* Install a kit from a registry (npm, github, git).
|
|
74
122
|
*/
|
|
@@ -119,6 +167,7 @@ async function addRegistryKit(ref, stashDir) {
|
|
|
119
167
|
cacheDir: installed.cacheDir,
|
|
120
168
|
extractedDir: installed.extractedDir,
|
|
121
169
|
installedAt: installed.installedAt,
|
|
170
|
+
audit: installed.audit,
|
|
122
171
|
},
|
|
123
172
|
config: {
|
|
124
173
|
stashCount: config.stashes?.length ?? 0,
|
|
@@ -139,3 +188,26 @@ function toReadableId(resolvedPath) {
|
|
|
139
188
|
}
|
|
140
189
|
return resolvedPath;
|
|
141
190
|
}
|
|
191
|
+
// Keep this list limited to widely-used git hosts for the non-breaking
|
|
192
|
+
// "repo-like URL" fast-path; everything else continues to default to website snapshots.
|
|
193
|
+
const KNOWN_GIT_HOSTS = new Set(["github.com", "gitlab.com", "bitbucket.org", "codeberg.org", "git.sr.ht"]);
|
|
194
|
+
export function shouldAddAsWebsiteUrl(ref) {
|
|
195
|
+
return isHttpUrl(ref) && !isLikelyGitRepositoryUrl(ref);
|
|
196
|
+
}
|
|
197
|
+
function isLikelyGitRepositoryUrl(ref) {
|
|
198
|
+
try {
|
|
199
|
+
const parsed = new URL(ref);
|
|
200
|
+
return KNOWN_GIT_HOSTS.has(parsed.hostname.toLowerCase()) || parsed.pathname.endsWith(".git");
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function toWebsiteName(siteUrl) {
|
|
207
|
+
try {
|
|
208
|
+
return new URL(siteUrl).hostname;
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return siteUrl;
|
|
212
|
+
}
|
|
213
|
+
}
|