akm-cli 0.5.0 → 0.6.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 +53 -5
- package/README.md +9 -9
- package/dist/cli.js +379 -1448
- package/dist/{completions.js → commands/completions.js} +1 -1
- package/dist/{config-cli.js → commands/config-cli.js} +109 -11
- package/dist/commands/curate.js +263 -0
- package/dist/{info.js → commands/info.js} +17 -11
- package/dist/{init.js → commands/init.js} +4 -4
- package/dist/{install-audit.js → commands/install-audit.js} +14 -2
- package/dist/{installed-kits.js → commands/installed-stashes.js} +122 -50
- package/dist/commands/migration-help.js +141 -0
- package/dist/{registry-search.js → commands/registry-search.js} +68 -9
- package/dist/commands/remember.js +178 -0
- package/dist/{stash-search.js → commands/search.js} +28 -69
- package/dist/{self-update.js → commands/self-update.js} +3 -3
- package/dist/{stash-show.js → commands/show.js} +106 -81
- package/dist/{stash-add.js → commands/source-add.js} +133 -67
- package/dist/{stash-clone.js → commands/source-clone.js} +15 -13
- package/dist/{stash-source-manage.js → commands/source-manage.js} +24 -24
- package/dist/{vault.js → commands/vault.js} +43 -0
- package/dist/{stash-ref.js → core/asset-ref.js} +4 -4
- package/dist/{asset-registry.js → core/asset-registry.js} +30 -6
- package/dist/{asset-spec.js → core/asset-spec.js} +13 -6
- package/dist/{common.js → core/common.js} +147 -50
- package/dist/{config.js → core/config.js} +288 -29
- package/dist/core/errors.js +90 -0
- package/dist/{frontmatter.js → core/frontmatter.js} +64 -8
- package/dist/{paths.js → core/paths.js} +4 -4
- package/dist/core/write-source.js +280 -0
- package/dist/{local-search.js → indexer/db-search.js} +49 -32
- package/dist/{db.js → indexer/db.js} +210 -81
- package/dist/{file-context.js → indexer/file-context.js} +3 -3
- package/dist/{indexer.js → indexer/indexer.js} +153 -30
- package/dist/{manifest.js → indexer/manifest.js} +10 -10
- package/dist/{matchers.js → indexer/matchers.js} +4 -7
- package/dist/{metadata.js → indexer/metadata.js} +9 -5
- package/dist/{search-source.js → indexer/search-source.js} +97 -55
- package/dist/{semantic-status.js → indexer/semantic-status.js} +2 -2
- package/dist/{walker.js → indexer/walker.js} +1 -1
- package/dist/{lockfile.js → integrations/lockfile.js} +29 -2
- package/dist/{llm.js → llm/client.js} +12 -48
- package/dist/llm/embedder.js +127 -0
- package/dist/llm/embedders/cache.js +47 -0
- package/dist/llm/embedders/local.js +152 -0
- package/dist/llm/embedders/remote.js +121 -0
- package/dist/llm/embedders/types.js +39 -0
- package/dist/llm/metadata-enhance.js +53 -0
- package/dist/output/cli-hints.js +301 -0
- package/dist/output/context.js +95 -0
- package/dist/{renderers.js → output/renderers.js} +57 -61
- package/dist/output/shapes.js +212 -0
- package/dist/output/text.js +520 -0
- package/dist/{registry-build-index.js → registry/build-index.js} +48 -32
- package/dist/{create-provider-registry.js → registry/create-provider-registry.js} +6 -2
- package/dist/registry/factory.js +33 -0
- package/dist/{origin-resolve.js → registry/origin-resolve.js} +1 -1
- package/dist/registry/providers/index.js +11 -0
- package/dist/{providers → registry/providers}/skills-sh.js +60 -4
- package/dist/{providers → registry/providers}/static-index.js +126 -56
- package/dist/registry/providers/types.js +25 -0
- package/dist/{registry-resolve.js → registry/resolve.js} +10 -6
- package/dist/{detect.js → setup/detect.js} +0 -27
- package/dist/{ripgrep-install.js → setup/ripgrep-install.js} +1 -1
- package/dist/{ripgrep-resolve.js → setup/ripgrep-resolve.js} +2 -2
- package/dist/{setup.js → setup/setup.js} +162 -129
- package/dist/setup/steps.js +45 -0
- package/dist/{kit-include.js → sources/include.js} +1 -1
- package/dist/sources/provider-factory.js +36 -0
- package/dist/sources/provider.js +21 -0
- package/dist/sources/providers/filesystem.js +35 -0
- package/dist/{stash-providers → sources/providers}/git.js +218 -28
- package/dist/{stash-providers → sources/providers}/index.js +4 -4
- package/dist/sources/providers/install-types.js +14 -0
- package/dist/sources/providers/npm.js +160 -0
- package/dist/sources/providers/provider-utils.js +173 -0
- package/dist/sources/providers/sync-from-ref.js +45 -0
- package/dist/sources/providers/tar-utils.js +154 -0
- package/dist/{stash-providers → sources/providers}/website.js +60 -20
- package/dist/{stash-resolve.js → sources/resolve.js} +13 -12
- package/dist/{wiki.js → wiki/wiki.js} +18 -17
- package/dist/{workflow-authoring.js → workflows/authoring.js} +48 -17
- package/dist/{workflow-cli.js → workflows/cli.js} +2 -1
- package/dist/{workflow-db.js → workflows/db.js} +1 -1
- package/dist/workflows/document-cache.js +20 -0
- package/dist/workflows/parser.js +379 -0
- package/dist/workflows/renderer.js +78 -0
- package/dist/{workflow-runs.js → workflows/runs.js} +84 -30
- package/dist/workflows/schema.js +11 -0
- package/dist/workflows/validator.js +48 -0
- package/docs/README.md +30 -0
- package/docs/migration/release-notes/0.0.13.md +4 -0
- package/docs/migration/release-notes/0.1.0.md +6 -0
- package/docs/migration/release-notes/0.2.0.md +6 -0
- package/docs/migration/release-notes/0.3.0.md +5 -0
- package/docs/migration/release-notes/0.5.0.md +6 -0
- package/docs/migration/release-notes/0.6.0.md +75 -0
- package/docs/migration/release-notes/README.md +21 -0
- package/package.json +3 -2
- package/dist/embedder.js +0 -351
- package/dist/errors.js +0 -34
- package/dist/migration-help.js +0 -110
- package/dist/registry-factory.js +0 -19
- package/dist/registry-install.js +0 -532
- package/dist/ripgrep.js +0 -2
- package/dist/stash-provider-factory.js +0 -35
- package/dist/stash-provider.js +0 -1
- package/dist/stash-providers/filesystem.js +0 -41
- package/dist/stash-providers/openviking.js +0 -348
- package/dist/stash-providers/provider-utils.js +0 -11
- package/dist/stash-types.js +0 -1
- package/dist/workflow-markdown.js +0 -251
- /package/dist/{markdown.js → core/markdown.js} +0 -0
- /package/dist/{warn.js → core/warn.js} +0 -0
- /package/dist/{search-fields.js → indexer/search-fields.js} +0 -0
- /package/dist/{usage-events.js → indexer/usage-events.js} +0 -0
- /package/dist/{github.js → integrations/github.js} +0 -0
- /package/dist/{registry-provider.js → registry/types.js} +0 -0
- /package/dist/{registry-types.js → sources/types.js} +0 -0
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Stash provider factory map.
|
|
3
|
-
*
|
|
4
|
-
* Maps stash source type identifiers (e.g. "filesystem", "openviking") to
|
|
5
|
-
* factory functions that create StashProvider instances from a StashConfigEntry.
|
|
6
|
-
*
|
|
7
|
-
* "Stash providers" are runtime data sources for the search and show commands —
|
|
8
|
-
* distinct from the kit-discovery registries (registry-factory.ts) and the
|
|
9
|
-
* installed-kit operations (installed-kits.ts).
|
|
10
|
-
*/
|
|
11
|
-
import { createProviderRegistry } from "./create-provider-registry";
|
|
12
|
-
// ── Factory map ─────────────────────────────────────────────────────────────
|
|
13
|
-
const registry = createProviderRegistry();
|
|
14
|
-
export function registerStashProvider(type, factory) {
|
|
15
|
-
registry.register(type, factory);
|
|
16
|
-
}
|
|
17
|
-
export function resolveStashProviderFactory(type) {
|
|
18
|
-
return registry.resolve(type);
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Resolve all non-filesystem stash providers from config.
|
|
22
|
-
* Filesystem entries are excluded — they are handled by resolveStashSources().
|
|
23
|
-
*/
|
|
24
|
-
export function resolveStashProviders(config) {
|
|
25
|
-
const providers = [];
|
|
26
|
-
for (const entry of config.stashes ?? []) {
|
|
27
|
-
if (entry.enabled === false)
|
|
28
|
-
continue;
|
|
29
|
-
const factory = registry.resolve(entry.type);
|
|
30
|
-
if (factory) {
|
|
31
|
-
providers.push(factory(entry));
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return providers;
|
|
35
|
-
}
|
package/dist/stash-provider.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { resolveStashDir } from "../common";
|
|
2
|
-
import { loadConfig } from "../config";
|
|
3
|
-
import { searchLocal } from "../local-search";
|
|
4
|
-
import { resolveStashSources } from "../search-source";
|
|
5
|
-
import { registerStashProvider } from "../stash-provider-factory";
|
|
6
|
-
import { showLocal } from "../stash-show";
|
|
7
|
-
class FilesystemStashProvider {
|
|
8
|
-
type = "filesystem";
|
|
9
|
-
name;
|
|
10
|
-
stashDir;
|
|
11
|
-
constructor(entry) {
|
|
12
|
-
this.stashDir = entry.path ?? resolveStashDir();
|
|
13
|
-
this.name = entry.name ?? this.stashDir;
|
|
14
|
-
}
|
|
15
|
-
async search(options) {
|
|
16
|
-
const config = loadConfig();
|
|
17
|
-
const sources = resolveStashSources(this.stashDir, config);
|
|
18
|
-
const result = await searchLocal({
|
|
19
|
-
query: options.query.toLowerCase(),
|
|
20
|
-
searchType: options.type ?? "any",
|
|
21
|
-
limit: options.limit,
|
|
22
|
-
stashDir: this.stashDir,
|
|
23
|
-
sources,
|
|
24
|
-
config,
|
|
25
|
-
});
|
|
26
|
-
return {
|
|
27
|
-
hits: result.hits,
|
|
28
|
-
warnings: result.warnings,
|
|
29
|
-
embedMs: result.embedMs,
|
|
30
|
-
rankMs: result.rankMs,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
async show(ref, view) {
|
|
34
|
-
return showLocal({ ref, view });
|
|
35
|
-
}
|
|
36
|
-
canShow(ref) {
|
|
37
|
-
return !ref.includes("://");
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
// ── Self-register ───────────────────────────────────────────────────────────
|
|
41
|
-
registerStashProvider("filesystem", (config) => new FilesystemStashProvider(config));
|
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { fetchWithRetry } from "../common";
|
|
5
|
-
import { ConfigError, NotFoundError } from "../errors";
|
|
6
|
-
import { getRegistryIndexCacheDir } from "../paths";
|
|
7
|
-
import { registerStashProvider } from "../stash-provider-factory";
|
|
8
|
-
import { isExpired, sanitizeString } from "./provider-utils";
|
|
9
|
-
/** Per-query cache TTL in milliseconds (5 minutes). */
|
|
10
|
-
const QUERY_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
11
|
-
/** Maximum age before query cache is considered stale but still usable (1 hour). */
|
|
12
|
-
const QUERY_CACHE_STALE_MS = 60 * 60 * 1000;
|
|
13
|
-
/**
|
|
14
|
-
* Single source of truth for OpenViking type → akm asset type mapping.
|
|
15
|
-
* Used by both search hit mapping and show response mapping.
|
|
16
|
-
*/
|
|
17
|
-
const OV_TYPE_MAP = {
|
|
18
|
-
skill: "skill",
|
|
19
|
-
skills: "skill",
|
|
20
|
-
memory: "memory",
|
|
21
|
-
memories: "memory",
|
|
22
|
-
resource: "knowledge",
|
|
23
|
-
resources: "knowledge",
|
|
24
|
-
knowledge: "knowledge",
|
|
25
|
-
agent: "agent",
|
|
26
|
-
agents: "agent",
|
|
27
|
-
command: "command",
|
|
28
|
-
commands: "command",
|
|
29
|
-
script: "script",
|
|
30
|
-
scripts: "script",
|
|
31
|
-
};
|
|
32
|
-
class OpenVikingStashProvider {
|
|
33
|
-
type = "openviking";
|
|
34
|
-
name;
|
|
35
|
-
config;
|
|
36
|
-
constructor(config) {
|
|
37
|
-
this.config = config;
|
|
38
|
-
this.name = config.name ?? "openviking";
|
|
39
|
-
// Validate baseUrl scheme to prevent SSRF via file:// or other non-HTTP schemes
|
|
40
|
-
const rawUrl = config.url ?? "";
|
|
41
|
-
if (rawUrl) {
|
|
42
|
-
try {
|
|
43
|
-
const parsed = new URL(rawUrl);
|
|
44
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
45
|
-
throw new ConfigError(`OpenViking baseUrl must use http:// or https://, got "${parsed.protocol}" in "${rawUrl}"`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
catch (err) {
|
|
49
|
-
if (err instanceof ConfigError)
|
|
50
|
-
throw err;
|
|
51
|
-
throw new ConfigError(`OpenViking baseUrl is not a valid URL: "${rawUrl}"`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
async search(options) {
|
|
56
|
-
try {
|
|
57
|
-
const entries = await this.fetchResults(options.query, options.limit);
|
|
58
|
-
const limited = entries.slice(0, options.limit);
|
|
59
|
-
const hits = this.mapToStashHits(limited);
|
|
60
|
-
return { hits };
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
64
|
-
return { hits: [], warnings: [`Stash ${this.name}: ${message}`] };
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
async show(ref, _view) {
|
|
68
|
-
const trimmed = ref.trim();
|
|
69
|
-
// Accept both viking:// URIs (legacy/internal) and type:name refs
|
|
70
|
-
const uri = trimmed.startsWith("viking://") ? trimmed : refToVikingUri(trimmed);
|
|
71
|
-
const baseUrl = this.baseUrl;
|
|
72
|
-
const headers = this.authHeaders;
|
|
73
|
-
const [statResult, contentResult] = await Promise.all([
|
|
74
|
-
fetchOVJson(`${baseUrl}/api/v1/fs/stat?uri=${encodeURIComponent(uri)}`, headers),
|
|
75
|
-
fetchOVJson(`${baseUrl}/api/v1/content/read?uri=${encodeURIComponent(uri)}&offset=0&limit=-1`, headers),
|
|
76
|
-
]);
|
|
77
|
-
if (statResult == null && contentResult == null) {
|
|
78
|
-
throw new NotFoundError(`Could not fetch remote asset "${trimmed}". The OpenViking server at ${baseUrl} may be unreachable or the resource does not exist.`);
|
|
79
|
-
}
|
|
80
|
-
if (contentResult == null) {
|
|
81
|
-
throw new NotFoundError(`Content not found for remote asset "${trimmed}". The server returned metadata but no content.`);
|
|
82
|
-
}
|
|
83
|
-
const stat = (typeof statResult === "object" && statResult !== null ? statResult : {});
|
|
84
|
-
const uriPath = uri.replace(/^viking:\/\//, "");
|
|
85
|
-
// Sanitize untrusted fields to strip terminal control characters
|
|
86
|
-
const name = sanitizeString(stat.name) || uriPath.split("/").pop() || "unknown";
|
|
87
|
-
const ovType = sanitizeString(stat.type) || inferTypeFromUri(uri);
|
|
88
|
-
const assetType = OV_TYPE_MAP[ovType] ?? "knowledge";
|
|
89
|
-
const content = typeof contentResult === "string" ? contentResult : "";
|
|
90
|
-
const description = sanitizeString(stat.abstract, 1000) || undefined;
|
|
91
|
-
const assetRef = `${assetType}:${name}`;
|
|
92
|
-
return {
|
|
93
|
-
type: assetType,
|
|
94
|
-
name,
|
|
95
|
-
path: assetRef,
|
|
96
|
-
action: `Remote content from OpenViking — ${assetRef}`,
|
|
97
|
-
content,
|
|
98
|
-
description,
|
|
99
|
-
editable: false,
|
|
100
|
-
origin: "remote",
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
canShow(_ref) {
|
|
104
|
-
return !!(this.config.url ?? "").trim();
|
|
105
|
-
}
|
|
106
|
-
get baseUrl() {
|
|
107
|
-
return (this.config.url ?? "").replace(/\/+$/, "");
|
|
108
|
-
}
|
|
109
|
-
get authHeaders() {
|
|
110
|
-
const headers = {};
|
|
111
|
-
const apiKey = this.config.options?.apiKey ?? undefined;
|
|
112
|
-
if (apiKey)
|
|
113
|
-
headers.Authorization = `Bearer ${apiKey}`;
|
|
114
|
-
return headers;
|
|
115
|
-
}
|
|
116
|
-
async fetchResults(query, limit) {
|
|
117
|
-
const cachePath = this.queryCachePath(query, limit);
|
|
118
|
-
const cached = this.readQueryCache(cachePath);
|
|
119
|
-
if (cached && !isExpired(cached.mtime, QUERY_CACHE_TTL_MS)) {
|
|
120
|
-
return cached.entries;
|
|
121
|
-
}
|
|
122
|
-
const baseUrl = this.baseUrl;
|
|
123
|
-
const searchType = this.config.options?.searchType ?? "semantic";
|
|
124
|
-
try {
|
|
125
|
-
let url;
|
|
126
|
-
let body;
|
|
127
|
-
if (searchType === "text") {
|
|
128
|
-
url = `${baseUrl}/api/v1/search/grep`;
|
|
129
|
-
body = JSON.stringify({ uri: "viking://", pattern: query, case_insensitive: true });
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
url = `${baseUrl}/api/v1/search/find`;
|
|
133
|
-
body = JSON.stringify({ query, limit });
|
|
134
|
-
}
|
|
135
|
-
const headers = { "Content-Type": "application/json", ...this.authHeaders };
|
|
136
|
-
const response = await fetchWithRetry(url, { method: "POST", headers, body }, { timeout: 10_000, retries: 1 });
|
|
137
|
-
if (!response.ok) {
|
|
138
|
-
throw new Error(`HTTP ${response.status}`);
|
|
139
|
-
}
|
|
140
|
-
const data = (await response.json());
|
|
141
|
-
if (data.status !== "ok") {
|
|
142
|
-
throw new Error(data.error ?? "OpenViking returned error status");
|
|
143
|
-
}
|
|
144
|
-
const entries = parseOVSearchResponse(data.result);
|
|
145
|
-
this.writeQueryCache(cachePath, entries);
|
|
146
|
-
return entries;
|
|
147
|
-
}
|
|
148
|
-
catch (err) {
|
|
149
|
-
if (cached && !isExpired(cached.mtime, QUERY_CACHE_STALE_MS)) {
|
|
150
|
-
return cached.entries;
|
|
151
|
-
}
|
|
152
|
-
throw err;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
mapToStashHits(entries) {
|
|
156
|
-
if (entries.length === 0)
|
|
157
|
-
return [];
|
|
158
|
-
const maxScore = entries.reduce((max, e) => Math.max(max, e.score), 0.01);
|
|
159
|
-
return entries.map((entry) => {
|
|
160
|
-
const name = sanitizeString(entry.name);
|
|
161
|
-
const abstract = sanitizeString(entry.abstract, 1000);
|
|
162
|
-
const type = sanitizeString(entry.type);
|
|
163
|
-
const assetType = OV_TYPE_MAP[type] ?? "knowledge";
|
|
164
|
-
const ref = `${assetType}:${name}`;
|
|
165
|
-
return {
|
|
166
|
-
type: assetType,
|
|
167
|
-
name,
|
|
168
|
-
path: ref,
|
|
169
|
-
ref,
|
|
170
|
-
origin: this.type,
|
|
171
|
-
editable: false,
|
|
172
|
-
description: abstract || undefined,
|
|
173
|
-
action: `akm show ${ref}`,
|
|
174
|
-
score: Math.round((entry.score / maxScore) * 1000) / 1000,
|
|
175
|
-
};
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
queryCachePath(query, limit) {
|
|
179
|
-
const cacheDir = getRegistryIndexCacheDir();
|
|
180
|
-
const hasher = createHash("md5");
|
|
181
|
-
hasher.update(this.config.url ?? "");
|
|
182
|
-
hasher.update("\0");
|
|
183
|
-
hasher.update(query.trim().toLowerCase());
|
|
184
|
-
hasher.update("\0");
|
|
185
|
-
hasher.update(String(limit));
|
|
186
|
-
hasher.update("\0");
|
|
187
|
-
const searchType = this.config.options?.searchType ?? "semantic";
|
|
188
|
-
hasher.update(searchType);
|
|
189
|
-
hasher.update("\0");
|
|
190
|
-
const apiKey = this.config.options?.apiKey ?? "";
|
|
191
|
-
hasher.update(apiKey);
|
|
192
|
-
const hash = hasher.digest("hex");
|
|
193
|
-
return path.join(cacheDir, `openviking-search-${hash}.json`);
|
|
194
|
-
}
|
|
195
|
-
readQueryCache(cachePath) {
|
|
196
|
-
try {
|
|
197
|
-
const stat = fs.statSync(cachePath);
|
|
198
|
-
const raw = JSON.parse(fs.readFileSync(cachePath, "utf8"));
|
|
199
|
-
if (!Array.isArray(raw))
|
|
200
|
-
return null;
|
|
201
|
-
const entries = raw.filter(isValidOVEntry);
|
|
202
|
-
return { entries, mtime: stat.mtimeMs };
|
|
203
|
-
}
|
|
204
|
-
catch {
|
|
205
|
-
return null;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
writeQueryCache(cachePath, entries) {
|
|
209
|
-
try {
|
|
210
|
-
const dir = path.dirname(cachePath);
|
|
211
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
212
|
-
const tmpPath = `${cachePath}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
|
|
213
|
-
// 0o600: owner read/write only — cache may contain search terms tied to API keys
|
|
214
|
-
fs.writeFileSync(tmpPath, JSON.stringify(entries), { encoding: "utf8", mode: 0o600 });
|
|
215
|
-
fs.renameSync(tmpPath, cachePath);
|
|
216
|
-
}
|
|
217
|
-
catch {
|
|
218
|
-
// Best-effort caching
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
// ── Self-register ───────────────────────────────────────────────────────────
|
|
223
|
-
registerStashProvider("openviking", (config) => new OpenVikingStashProvider(config));
|
|
224
|
-
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
225
|
-
/**
|
|
226
|
-
* Convert a type:name ref to a viking:// URI for the OpenViking API.
|
|
227
|
-
* Maps the akm asset type back to the OV plural form (e.g. "skill" -> "skills").
|
|
228
|
-
*/
|
|
229
|
-
function refToVikingUri(ref) {
|
|
230
|
-
const colon = ref.indexOf(":");
|
|
231
|
-
if (colon <= 0)
|
|
232
|
-
return `viking://${ref}`;
|
|
233
|
-
const name = ref.slice(colon + 1);
|
|
234
|
-
const type = ref.slice(0, colon);
|
|
235
|
-
const ovDir = AKM_TO_OV_DIR[type] ?? type;
|
|
236
|
-
return `viking://${ovDir}/${name}`;
|
|
237
|
-
}
|
|
238
|
-
/** Reverse map: akm asset type → OpenViking directory name (plural). */
|
|
239
|
-
const AKM_TO_OV_DIR = {
|
|
240
|
-
skill: "skills",
|
|
241
|
-
memory: "memories",
|
|
242
|
-
knowledge: "resources",
|
|
243
|
-
agent: "agents",
|
|
244
|
-
command: "commands",
|
|
245
|
-
script: "scripts",
|
|
246
|
-
};
|
|
247
|
-
function parseOVSearchResponse(result) {
|
|
248
|
-
if (Array.isArray(result))
|
|
249
|
-
return result.filter(isValidOVEntry);
|
|
250
|
-
if (typeof result !== "object" || result === null)
|
|
251
|
-
return [];
|
|
252
|
-
const grouped = result;
|
|
253
|
-
if (Array.isArray(grouped.matches)) {
|
|
254
|
-
return deduplicateGrepMatches(grouped.matches);
|
|
255
|
-
}
|
|
256
|
-
const entries = [];
|
|
257
|
-
for (const [category, items] of Object.entries(grouped)) {
|
|
258
|
-
if (category === "total")
|
|
259
|
-
continue;
|
|
260
|
-
if (!Array.isArray(items))
|
|
261
|
-
continue;
|
|
262
|
-
for (const item of items) {
|
|
263
|
-
if (!isValidOVSearchItem(item))
|
|
264
|
-
continue;
|
|
265
|
-
entries.push({
|
|
266
|
-
uri: item.uri,
|
|
267
|
-
name: extractNameFromUri(item.uri),
|
|
268
|
-
score: item.score,
|
|
269
|
-
type: item.context_type ?? category,
|
|
270
|
-
abstract: item.abstract ?? undefined,
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
return entries;
|
|
275
|
-
}
|
|
276
|
-
function isValidGrepMatch(item) {
|
|
277
|
-
if (typeof item !== "object" || item === null)
|
|
278
|
-
return false;
|
|
279
|
-
const obj = item;
|
|
280
|
-
return typeof obj.uri === "string" && typeof obj.content === "string";
|
|
281
|
-
}
|
|
282
|
-
function deduplicateGrepMatches(matches) {
|
|
283
|
-
const byUri = new Map();
|
|
284
|
-
for (const m of matches) {
|
|
285
|
-
if (!isValidGrepMatch(m))
|
|
286
|
-
continue;
|
|
287
|
-
const existing = byUri.get(m.uri);
|
|
288
|
-
if (existing) {
|
|
289
|
-
existing.count++;
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
const pathSegment = m.uri.replace(/^viking:\/\//, "").split("/")[0] ?? "";
|
|
293
|
-
byUri.set(m.uri, { content: m.content, count: 1, type: pathSegment });
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
const maxCount = Math.max(...[...byUri.values()].map((v) => v.count), 1);
|
|
297
|
-
const entries = [];
|
|
298
|
-
for (const [uri, { content, count, type }] of byUri) {
|
|
299
|
-
entries.push({
|
|
300
|
-
uri,
|
|
301
|
-
name: extractNameFromUri(uri),
|
|
302
|
-
score: count / maxCount,
|
|
303
|
-
type,
|
|
304
|
-
abstract: content.slice(0, 200),
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
entries.sort((a, b) => b.score - a.score);
|
|
308
|
-
return entries;
|
|
309
|
-
}
|
|
310
|
-
function isValidOVEntry(entry) {
|
|
311
|
-
if (typeof entry !== "object" || entry === null || Array.isArray(entry))
|
|
312
|
-
return false;
|
|
313
|
-
const obj = entry;
|
|
314
|
-
return typeof obj.uri === "string" && typeof obj.name === "string" && typeof obj.score === "number";
|
|
315
|
-
}
|
|
316
|
-
function isValidOVSearchItem(item) {
|
|
317
|
-
if (typeof item !== "object" || item === null || Array.isArray(item))
|
|
318
|
-
return false;
|
|
319
|
-
const obj = item;
|
|
320
|
-
return typeof obj.uri === "string" && typeof obj.score === "number";
|
|
321
|
-
}
|
|
322
|
-
function extractNameFromUri(uri) {
|
|
323
|
-
const uriPath = uri.replace(/^viking:\/\//, "");
|
|
324
|
-
const segments = uriPath.split("/").filter(Boolean);
|
|
325
|
-
const last = segments[segments.length - 1] ?? "unknown";
|
|
326
|
-
return last.replace(/\.[^.]+$/, "");
|
|
327
|
-
}
|
|
328
|
-
async function fetchOVJson(url, headers) {
|
|
329
|
-
try {
|
|
330
|
-
const response = await fetchWithRetry(url, { headers }, { timeout: 10_000, retries: 1 });
|
|
331
|
-
if (!response.ok)
|
|
332
|
-
return null;
|
|
333
|
-
const data = (await response.json());
|
|
334
|
-
if (data.status !== "ok")
|
|
335
|
-
return null;
|
|
336
|
-
return data.result ?? null;
|
|
337
|
-
}
|
|
338
|
-
catch {
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
function inferTypeFromUri(uri) {
|
|
343
|
-
const uriPath = uri.replace(/^viking:\/\//, "");
|
|
344
|
-
const firstSegment = uriPath.split("/")[0] ?? "";
|
|
345
|
-
return OV_TYPE_MAP[firstSegment] ?? "knowledge";
|
|
346
|
-
}
|
|
347
|
-
// ── Exports for testing ─────────────────────────────────────────────────────
|
|
348
|
-
export { OpenVikingStashProvider, parseOVSearchResponse, refToVikingUri };
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/** Strip terminal control characters from untrusted strings. */
|
|
2
|
-
export function sanitizeString(value, maxLength = 255) {
|
|
3
|
-
if (typeof value !== "string")
|
|
4
|
-
return "";
|
|
5
|
-
// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional — strip control chars from untrusted remote data
|
|
6
|
-
return value.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, maxLength);
|
|
7
|
-
}
|
|
8
|
-
/** Check whether a cached timestamp has exceeded its TTL. */
|
|
9
|
-
export function isExpired(mtimeMs, ttlMs) {
|
|
10
|
-
return Date.now() - mtimeMs > ttlMs;
|
|
11
|
-
}
|
package/dist/stash-types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|