akm-cli 0.0.22 → 0.1.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/README.md +4 -4
- package/dist/asset-spec.js +1 -1
- package/dist/cli.js +99 -99
- package/dist/config-cli.js +0 -25
- package/dist/config.js +15 -96
- package/dist/github.js +22 -3
- package/dist/indexer.js +2 -2
- package/dist/init.js +1 -1
- package/dist/installed-kits.js +10 -10
- package/dist/kit-include.js +3 -3
- package/dist/local-search.js +1 -1
- package/dist/lockfile.js +42 -3
- package/dist/registry-build-index.js +4 -11
- package/dist/registry-install.js +12 -12
- package/dist/registry-resolve.js +1 -1
- package/dist/registry-search.js +1 -1
- package/dist/{stash-source.js → search-source.js} +4 -8
- package/dist/stash-add.js +26 -7
- package/dist/stash-clone.js +28 -2
- package/dist/stash-provider-factory.js +8 -23
- package/dist/stash-providers/filesystem.js +1 -1
- package/dist/stash-providers/openviking.js +1 -1
- package/dist/stash-search.js +3 -3
- package/dist/stash-show.js +8 -5
- package/dist/stash-source-manage.js +82 -0
- package/package.json +2 -2
package/dist/stash-clone.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { TYPE_DIRS } from "./asset-spec";
|
|
4
|
+
import { UsageError } from "./errors";
|
|
4
5
|
import { isRemoteOrigin, resolveSourcesForOrigin } from "./origin-resolve";
|
|
5
6
|
import { installRegistryRef } from "./registry-install";
|
|
7
|
+
import { findSourceForPath, getPrimarySource, resolveStashSources } from "./search-source";
|
|
6
8
|
import { makeAssetRef, parseAssetRef } from "./stash-ref";
|
|
7
9
|
import { resolveAssetPath } from "./stash-resolve";
|
|
8
|
-
|
|
9
|
-
export async function agentikitClone(options) {
|
|
10
|
+
export async function akmClone(options) {
|
|
10
11
|
const parsed = parseAssetRef(options.sourceRef);
|
|
11
12
|
// When --dest is provided, the working stash is optional
|
|
12
13
|
let allSources;
|
|
@@ -61,6 +62,31 @@ export async function agentikitClone(options) {
|
|
|
61
62
|
const sourceSource = findSourceForPath(sourcePath, allSources);
|
|
62
63
|
const destName = options.newName ?? parsed.name;
|
|
63
64
|
const typeDir = TYPE_DIRS[parsed.type];
|
|
65
|
+
// Validate destName to prevent path traversal (parsed.name is already
|
|
66
|
+
// validated by parseAssetRef, but newName comes directly from user input).
|
|
67
|
+
// Run whenever newName is provided, including empty string.
|
|
68
|
+
if (options.newName !== undefined) {
|
|
69
|
+
if (destName === "") {
|
|
70
|
+
throw new UsageError("Clone name must not be empty.");
|
|
71
|
+
}
|
|
72
|
+
const normalized = path.posix.normalize(destName.replace(/\\/g, "/"));
|
|
73
|
+
if (path.isAbsolute(destName) ||
|
|
74
|
+
normalized === "." ||
|
|
75
|
+
normalized.startsWith("../") ||
|
|
76
|
+
normalized === ".." ||
|
|
77
|
+
destName.includes("\0")) {
|
|
78
|
+
throw new UsageError(`Unsafe clone name "${destName}": must not contain path traversal or absolute paths.`);
|
|
79
|
+
}
|
|
80
|
+
// Ensure the resolved destination is strictly inside the type directory,
|
|
81
|
+
// not equal to it (which can happen with crafted multi-segment names).
|
|
82
|
+
// path.relative() is used instead of startsWith() for cross-platform safety.
|
|
83
|
+
const destTypeDir = path.resolve(path.join(destRoot, typeDir));
|
|
84
|
+
const resolvedDest = path.resolve(path.join(destRoot, typeDir, destName));
|
|
85
|
+
const rel = path.relative(destTypeDir, resolvedDest);
|
|
86
|
+
if (rel === "" || rel.startsWith("..")) {
|
|
87
|
+
throw new UsageError(`Unsafe clone name "${destName}": resolves outside the target type directory.`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
64
90
|
const destLabel = options.dest ? "at destination" : "in working stash";
|
|
65
91
|
// Guard against self-clone
|
|
66
92
|
if (parsed.type === "skill") {
|
|
@@ -19,33 +19,18 @@ export function resolveStashProviderFactory(type) {
|
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* Resolve all non-filesystem stash providers from config.
|
|
22
|
-
* Sources come from `stashes` (new) or `remoteStashSources` (legacy).
|
|
23
22
|
* Filesystem entries are excluded — they are handled by resolveStashSources().
|
|
24
23
|
*/
|
|
25
24
|
export function resolveStashProviders(config) {
|
|
26
25
|
const providers = [];
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (factory) {
|
|
36
|
-
providers.push(factory(entry));
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
// Legacy config: remoteStashSources[] → map to stash providers
|
|
41
|
-
if (!config.stashes && config.remoteStashSources) {
|
|
42
|
-
for (const entry of config.remoteStashSources) {
|
|
43
|
-
if (entry.enabled === false)
|
|
44
|
-
continue;
|
|
45
|
-
const factory = registry.resolve(entry.type ?? "openviking");
|
|
46
|
-
if (factory) {
|
|
47
|
-
providers.push(factory(entry));
|
|
48
|
-
}
|
|
26
|
+
for (const entry of config.stashes ?? []) {
|
|
27
|
+
if (entry.enabled === false)
|
|
28
|
+
continue;
|
|
29
|
+
if (entry.type === "filesystem")
|
|
30
|
+
continue;
|
|
31
|
+
const factory = registry.resolve(entry.type);
|
|
32
|
+
if (factory) {
|
|
33
|
+
providers.push(factory(entry));
|
|
49
34
|
}
|
|
50
35
|
}
|
|
51
36
|
return providers;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { resolveStashDir } from "../common";
|
|
2
2
|
import { loadConfig } from "../config";
|
|
3
3
|
import { searchLocal } from "../local-search";
|
|
4
|
+
import { resolveStashSources } from "../search-source";
|
|
4
5
|
import { registerStashProvider } from "../stash-provider-factory";
|
|
5
6
|
import { showLocal } from "../stash-show";
|
|
6
|
-
import { resolveStashSources } from "../stash-source";
|
|
7
7
|
class FilesystemStashProvider {
|
|
8
8
|
type = "filesystem";
|
|
9
9
|
name;
|
|
@@ -16,7 +16,7 @@ const QUERY_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
|
16
16
|
/** Maximum age before query cache is considered stale but still usable (1 hour). */
|
|
17
17
|
const QUERY_CACHE_STALE_MS = 60 * 60 * 1000;
|
|
18
18
|
/**
|
|
19
|
-
* Single source of truth for OpenViking type →
|
|
19
|
+
* Single source of truth for OpenViking type → akm asset type mapping.
|
|
20
20
|
* Used by both search hit mapping and show response mapping.
|
|
21
21
|
*/
|
|
22
22
|
const OV_TYPE_MAP = {
|
package/dist/stash-search.js
CHANGED
|
@@ -5,9 +5,9 @@ import { resolveStashProviders } from "./stash-provider-factory";
|
|
|
5
5
|
import "./stash-providers/index";
|
|
6
6
|
import { UsageError } from "./errors";
|
|
7
7
|
import { searchRegistry } from "./registry-search";
|
|
8
|
-
import { resolveStashSources } from "./
|
|
8
|
+
import { resolveStashSources } from "./search-source";
|
|
9
9
|
const DEFAULT_LIMIT = 20;
|
|
10
|
-
export async function
|
|
10
|
+
export async function akmSearch(input) {
|
|
11
11
|
const t0 = Date.now();
|
|
12
12
|
const query = input.query.trim();
|
|
13
13
|
const normalizedQuery = query.toLowerCase();
|
|
@@ -24,7 +24,7 @@ export async function agentikitSearch(input) {
|
|
|
24
24
|
stashDir: "",
|
|
25
25
|
source,
|
|
26
26
|
hits: [],
|
|
27
|
-
warnings: ["No
|
|
27
|
+
warnings: ["No stashes configured. Run `akm init` to create your working stash."],
|
|
28
28
|
timing: { totalMs: Date.now() - t0 },
|
|
29
29
|
};
|
|
30
30
|
}
|
package/dist/stash-show.js
CHANGED
|
@@ -2,17 +2,17 @@ import { loadConfig } from "./config";
|
|
|
2
2
|
import { NotFoundError, UsageError } from "./errors";
|
|
3
3
|
import { buildFileContext, buildRenderContext, getRenderer, runMatchers } from "./file-context";
|
|
4
4
|
import { resolveSourcesForOrigin } from "./origin-resolve";
|
|
5
|
+
import { buildEditHint, findSourceForPath, isEditable, resolveStashSources } from "./search-source";
|
|
5
6
|
import { resolveStashProviders } from "./stash-provider-factory";
|
|
6
7
|
import { parseAssetRef } from "./stash-ref";
|
|
7
8
|
import { resolveAssetPath } from "./stash-resolve";
|
|
8
|
-
import { buildEditHint, findSourceForPath, isEditable, resolveStashSources } from "./stash-source";
|
|
9
9
|
// Eagerly import stash providers to trigger self-registration
|
|
10
10
|
import "./stash-providers/index";
|
|
11
11
|
/**
|
|
12
12
|
* Unified show: routes to the first stash provider that can handle the ref.
|
|
13
13
|
* viking:// refs are handled by OpenViking provider; everything else by filesystem show.
|
|
14
14
|
*/
|
|
15
|
-
export async function
|
|
15
|
+
export async function akmShowUnified(input) {
|
|
16
16
|
const ref = input.ref.trim();
|
|
17
17
|
// Try stash providers first (e.g. OpenViking for viking:// URIs)
|
|
18
18
|
const config = loadConfig();
|
|
@@ -23,7 +23,7 @@ export async function agentikitShowUnified(input) {
|
|
|
23
23
|
// Default: local filesystem show
|
|
24
24
|
return showLocal(input);
|
|
25
25
|
}
|
|
26
|
-
/** @internal Use
|
|
26
|
+
/** @internal Use akmShowUnified() for all external callers. */
|
|
27
27
|
export async function showLocal(input) {
|
|
28
28
|
const parsed = parseAssetRef(input.ref);
|
|
29
29
|
const displayType = parsed.type;
|
|
@@ -48,12 +48,15 @@ export async function showLocal(input) {
|
|
|
48
48
|
`Kit "${parsed.origin}" is not installed. Run: ${installCmd}`);
|
|
49
49
|
}
|
|
50
50
|
if (!assetPath) {
|
|
51
|
-
throw lastError ??
|
|
51
|
+
throw (lastError ??
|
|
52
|
+
new NotFoundError(`Stash asset not found for ref: ${displayType}:${parsed.name}. ` +
|
|
53
|
+
"Check the name with `akm search` or verify the asset exists in your stash."));
|
|
52
54
|
}
|
|
53
55
|
const source = findSourceForPath(assetPath, allSources);
|
|
54
56
|
const sourceStashDir = source?.path ?? allStashDirs[0];
|
|
55
57
|
if (!sourceStashDir) {
|
|
56
|
-
throw new UsageError(`Could not determine stash root for asset: ${displayType}:${parsed.name}`
|
|
58
|
+
throw new UsageError(`Could not determine stash root for asset: ${displayType}:${parsed.name}. ` +
|
|
59
|
+
"Run `akm init` to create the stash directory, or check `akm stash list` for configured paths.");
|
|
57
60
|
}
|
|
58
61
|
const fileCtx = buildFileContext(sourceStashDir, assetPath);
|
|
59
62
|
const match = await runMatchers(fileCtx);
|
|
@@ -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 "./search-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 addStash(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 removeStash(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 listStashes() {
|
|
78
|
+
const config = loadConfig();
|
|
79
|
+
const localSources = resolveStashSources();
|
|
80
|
+
const stashes = config.stashes ?? [];
|
|
81
|
+
return { localSources, stashes };
|
|
82
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI tool to search, open, and run extension assets from an akm stash directory.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"akm",
|
|
8
|
-
"
|
|
8
|
+
"akm-kit",
|
|
9
9
|
"ai-agent",
|
|
10
10
|
"agent-framework",
|
|
11
11
|
"developer-tools",
|