akm-cli 0.0.21 → 0.0.23
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/README.md +8 -5
- package/dist/asset-spec.js +91 -10
- package/dist/cli.js +172 -57
- package/dist/common.js +15 -2
- package/dist/config-cli.js +55 -6
- package/dist/config.js +118 -22
- package/dist/create-provider-registry.js +18 -0
- package/dist/db.js +156 -53
- package/dist/embedder.js +36 -18
- package/dist/errors.js +6 -0
- package/dist/file-context.js +18 -19
- package/dist/frontmatter.js +19 -3
- package/dist/indexer.js +126 -89
- package/dist/{stash-registry.js → installed-kits.js} +16 -24
- package/dist/kit-include.js +108 -0
- package/dist/local-search.js +429 -0
- package/dist/lockfile.js +47 -5
- package/dist/matchers.js +6 -0
- package/dist/metadata.js +20 -10
- package/dist/paths.js +4 -0
- package/dist/providers/skills-sh.js +3 -2
- package/dist/providers/static-index.js +4 -9
- package/dist/registry-build-index.js +356 -0
- package/dist/registry-factory.js +19 -0
- package/dist/registry-install.js +114 -109
- package/dist/registry-resolve.js +44 -9
- package/dist/registry-search.js +14 -9
- package/dist/renderers.js +23 -7
- package/dist/ripgrep-install.js +9 -4
- package/dist/self-update.js +31 -4
- package/dist/stash-add.js +75 -6
- package/dist/stash-clone.js +1 -1
- package/dist/stash-provider-factory.js +37 -0
- package/dist/stash-provider.js +1 -0
- package/dist/stash-providers/filesystem.js +42 -0
- package/dist/stash-providers/index.js +9 -0
- package/dist/stash-providers/openviking.js +337 -0
- package/dist/stash-resolve.js +4 -4
- package/dist/stash-search.js +70 -401
- package/dist/stash-show.js +24 -5
- package/dist/stash-source-manage.js +82 -0
- package/dist/stash-source.js +19 -11
- package/dist/walker.js +15 -10
- package/dist/warn.js +7 -0
- package/package.json +1 -1
- package/dist/provider-registry.js +0 -8
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { loadConfig, saveConfig } from "./config";
|
|
3
|
+
import { UsageError } from "./errors";
|
|
4
|
+
import { resolveStashSources } from "./stash-source";
|
|
5
|
+
// ── Operations ──────────────────────────────────────────────────────────────
|
|
6
|
+
/**
|
|
7
|
+
* Add a stash source (filesystem path or remote provider URL) to config.
|
|
8
|
+
*
|
|
9
|
+
* Filesystem paths are auto-detected when `target` does not start with
|
|
10
|
+
* `http://` or `https://`. URL sources require a `providerType` option
|
|
11
|
+
* (e.g. "openviking").
|
|
12
|
+
*/
|
|
13
|
+
export function addStashSource(opts) {
|
|
14
|
+
const { target, name, providerType, options: providerOptions } = opts;
|
|
15
|
+
const config = loadConfig();
|
|
16
|
+
const stashes = [...(config.stashes ?? [])];
|
|
17
|
+
const isUrl = target.startsWith("http://") || target.startsWith("https://");
|
|
18
|
+
let entry;
|
|
19
|
+
if (isUrl) {
|
|
20
|
+
if (!providerType) {
|
|
21
|
+
throw new UsageError("--provider is required for URL sources (e.g. --provider openviking)");
|
|
22
|
+
}
|
|
23
|
+
// Deduplicate by URL
|
|
24
|
+
if (stashes.some((s) => s.url === target)) {
|
|
25
|
+
return { stashes, added: false, message: "Source URL already configured" };
|
|
26
|
+
}
|
|
27
|
+
entry = { type: providerType, url: target };
|
|
28
|
+
if (name)
|
|
29
|
+
entry.name = name;
|
|
30
|
+
if (providerOptions)
|
|
31
|
+
entry.options = providerOptions;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// Filesystem path
|
|
35
|
+
const resolvedPath = path.resolve(target);
|
|
36
|
+
if (stashes.some((s) => s.path && path.resolve(s.path) === resolvedPath)) {
|
|
37
|
+
return { stashes, added: false, message: "Source path already configured" };
|
|
38
|
+
}
|
|
39
|
+
entry = { type: "filesystem", path: resolvedPath };
|
|
40
|
+
if (name)
|
|
41
|
+
entry.name = name;
|
|
42
|
+
}
|
|
43
|
+
stashes.push(entry);
|
|
44
|
+
saveConfig({ ...config, stashes });
|
|
45
|
+
return { stashes, added: true, entry };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Remove a stash source by URL, path, or name.
|
|
49
|
+
* Match priority: URL > path > name (most specific first).
|
|
50
|
+
*/
|
|
51
|
+
export function removeStashSource(target) {
|
|
52
|
+
const config = loadConfig();
|
|
53
|
+
const stashes = [...(config.stashes ?? [])];
|
|
54
|
+
const isUrl = target.startsWith("http://") || target.startsWith("https://");
|
|
55
|
+
const resolvedPath = !isUrl ? path.resolve(target) : undefined;
|
|
56
|
+
// Try URL match first, then path, then name (most specific → least specific)
|
|
57
|
+
let idx = -1;
|
|
58
|
+
if (isUrl) {
|
|
59
|
+
idx = stashes.findIndex((s) => s.url === target);
|
|
60
|
+
}
|
|
61
|
+
if (idx === -1 && resolvedPath) {
|
|
62
|
+
idx = stashes.findIndex((s) => s.path && path.resolve(s.path) === resolvedPath);
|
|
63
|
+
}
|
|
64
|
+
if (idx === -1) {
|
|
65
|
+
idx = stashes.findIndex((s) => s.name === target);
|
|
66
|
+
}
|
|
67
|
+
if (idx === -1) {
|
|
68
|
+
return { stashes, removed: false, message: "No matching source found" };
|
|
69
|
+
}
|
|
70
|
+
const removed = stashes.splice(idx, 1)[0];
|
|
71
|
+
saveConfig({ ...config, stashes });
|
|
72
|
+
return { stashes, removed: true, entry: removed };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* List all stash sources (local filesystem + configured stashes).
|
|
76
|
+
*/
|
|
77
|
+
export function listStashSources() {
|
|
78
|
+
const config = loadConfig();
|
|
79
|
+
const localSources = resolveStashSources();
|
|
80
|
+
const stashes = config.stashes ?? [];
|
|
81
|
+
return { localSources, stashes };
|
|
82
|
+
}
|
package/dist/stash-source.js
CHANGED
|
@@ -17,25 +17,33 @@ export function resolveStashSources(overrideStashDir, existingConfig) {
|
|
|
17
17
|
const stashDir = overrideStashDir ?? resolveStashDir();
|
|
18
18
|
const config = existingConfig ?? loadConfig();
|
|
19
19
|
const sources = [{ path: stashDir }];
|
|
20
|
-
|
|
20
|
+
const seen = new Set([path.resolve(stashDir)]);
|
|
21
|
+
const addSource = (dir, registryId) => {
|
|
22
|
+
const resolved = path.resolve(dir);
|
|
23
|
+
if (seen.has(resolved))
|
|
24
|
+
return;
|
|
25
|
+
seen.add(resolved);
|
|
21
26
|
if (isSuspiciousStashRoot(dir)) {
|
|
22
27
|
warn(`Warning: stash root "${dir}" appears to be a system directory. This may be unintentional.`);
|
|
23
28
|
}
|
|
24
29
|
if (isValidDirectory(dir)) {
|
|
25
|
-
sources.push({ path:
|
|
30
|
+
sources.push({ path: resolved, ...(registryId ? { registryId } : {}) });
|
|
26
31
|
}
|
|
32
|
+
};
|
|
33
|
+
// Legacy: searchPaths[]
|
|
34
|
+
for (const dir of config.searchPaths) {
|
|
35
|
+
addSource(dir);
|
|
27
36
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (isValidDirectory(entry.stashRoot)) {
|
|
33
|
-
sources.push({
|
|
34
|
-
path: entry.stashRoot,
|
|
35
|
-
registryId: entry.id,
|
|
36
|
-
});
|
|
37
|
+
// Filesystem entries from stashes[]
|
|
38
|
+
for (const entry of config.stashes ?? []) {
|
|
39
|
+
if (entry.type === "filesystem" && entry.path && entry.enabled !== false) {
|
|
40
|
+
addSource(entry.path, entry.name);
|
|
37
41
|
}
|
|
38
42
|
}
|
|
43
|
+
// Installed kits (registry and local)
|
|
44
|
+
for (const entry of config.installed ?? []) {
|
|
45
|
+
addSource(entry.stashRoot, entry.id);
|
|
46
|
+
}
|
|
39
47
|
return sources;
|
|
40
48
|
}
|
|
41
49
|
/**
|
package/dist/walker.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* (stash.ts) and the indexer (indexer.ts) to walk type-specific asset
|
|
6
6
|
* directories and group files by parent directory.
|
|
7
7
|
*/
|
|
8
|
-
import { spawnSync } from "node:child_process";
|
|
9
8
|
import fs from "node:fs";
|
|
10
9
|
import path from "node:path";
|
|
11
10
|
import { isRelevantAssetFile } from "./asset-spec";
|
|
@@ -77,17 +76,16 @@ function walkStashGit(stashRoot) {
|
|
|
77
76
|
if (!isInsideGitRepo(stashRoot))
|
|
78
77
|
return null;
|
|
79
78
|
// Get tracked + untracked (non-ignored) files
|
|
80
|
-
const result = spawnSync("git",
|
|
79
|
+
const result = Bun.spawnSync(["git", "ls-files", "--cached", "--others", "--exclude-standard", "-z", "--", "."], {
|
|
81
80
|
cwd: stashRoot,
|
|
82
|
-
encoding: "utf8",
|
|
83
|
-
timeout: 30_000,
|
|
84
|
-
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
85
81
|
});
|
|
86
|
-
|
|
82
|
+
// result.success is false if the process exited non-zero OR git was not found
|
|
83
|
+
if (!result.success)
|
|
87
84
|
return null;
|
|
88
85
|
const SKIP_DIRS = new Set([".git", "node_modules", "bin", ".cache"]);
|
|
89
86
|
const SKIP_FILES = new Set([".stash.json", ".gitignore", ".gitattributes"]);
|
|
90
|
-
const
|
|
87
|
+
const stdout = Buffer.isBuffer(result.stdout) ? result.stdout.toString("utf8") : String(result.stdout ?? "");
|
|
88
|
+
const files = stdout
|
|
91
89
|
.split("\0")
|
|
92
90
|
.filter((f) => f.length > 0)
|
|
93
91
|
.filter((f) => !f.startsWith("..") && !path.isAbsolute(f))
|
|
@@ -98,8 +96,7 @@ function walkStashGit(stashRoot) {
|
|
|
98
96
|
.filter(Boolean);
|
|
99
97
|
return !dirParts.some((part) => SKIP_DIRS.has(part) || part.startsWith("."));
|
|
100
98
|
})
|
|
101
|
-
.filter((f) => !SKIP_FILES.has(path.basename(f)))
|
|
102
|
-
.filter((f) => !f.includes("/.") && !f.startsWith(".")); // skip dot-dirs/files
|
|
99
|
+
.filter((f) => !SKIP_FILES.has(path.basename(f)));
|
|
103
100
|
const results = [];
|
|
104
101
|
for (const relFile of files) {
|
|
105
102
|
const absPath = path.join(stashRoot, relFile);
|
|
@@ -114,7 +111,11 @@ function walkStashGit(stashRoot) {
|
|
|
114
111
|
}
|
|
115
112
|
return results;
|
|
116
113
|
}
|
|
117
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* Check if a directory is inside a git repository by walking up to find .git.
|
|
116
|
+
* Intentionally walks above stashRoot so that parent repo .gitignore rules
|
|
117
|
+
* apply when the stash is nested inside a larger git repository.
|
|
118
|
+
*/
|
|
118
119
|
function isInsideGitRepo(dir) {
|
|
119
120
|
let current = path.resolve(dir);
|
|
120
121
|
const root = path.parse(current).root;
|
|
@@ -149,6 +150,10 @@ function walkStashManual(stashRoot) {
|
|
|
149
150
|
if (entry.name === ".stash.json")
|
|
150
151
|
continue;
|
|
151
152
|
const fullPath = path.join(current, entry.name);
|
|
153
|
+
if (entry.isSymbolicLink()) {
|
|
154
|
+
// Skip symlinks entirely to prevent potential path traversal outside stashRoot
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
152
157
|
if (entry.isDirectory()) {
|
|
153
158
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith("."))
|
|
154
159
|
continue;
|
package/dist/warn.js
CHANGED
|
@@ -6,6 +6,13 @@ let quiet = false;
|
|
|
6
6
|
export function setQuiet(value) {
|
|
7
7
|
quiet = value;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Reset the quiet flag to false.
|
|
11
|
+
* Intended for test teardown to prevent quiet state from leaking between tests.
|
|
12
|
+
*/
|
|
13
|
+
export function resetQuiet() {
|
|
14
|
+
quiet = false;
|
|
15
|
+
}
|
|
9
16
|
export function isQuiet() {
|
|
10
17
|
return quiet;
|
|
11
18
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// ── Factory map ─────────────────────────────────────────────────────────────
|
|
2
|
-
const providers = new Map();
|
|
3
|
-
export function registerProvider(type, factory) {
|
|
4
|
-
providers.set(type, factory);
|
|
5
|
-
}
|
|
6
|
-
export function resolveProviderFactory(type) {
|
|
7
|
-
return providers.get(type) ?? null;
|
|
8
|
-
}
|