agentikit 0.0.7 → 0.0.9
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 +215 -76
- package/dist/index.d.ts +17 -3
- package/dist/index.js +10 -2
- package/dist/src/asset-spec.d.ts +14 -0
- package/dist/src/asset-spec.js +46 -0
- package/dist/src/cli.js +268 -57
- package/dist/src/common.d.ts +8 -0
- package/dist/src/common.js +46 -0
- package/dist/src/config.d.ts +37 -0
- package/dist/src/config.js +124 -0
- package/dist/src/embedder.d.ts +10 -0
- package/dist/src/embedder.js +87 -0
- package/dist/src/frontmatter.d.ts +30 -0
- package/dist/src/frontmatter.js +86 -0
- package/dist/src/indexer.d.ts +20 -2
- package/dist/src/indexer.js +212 -80
- package/dist/src/init.d.ts +19 -0
- package/dist/src/init.js +87 -0
- package/dist/src/llm.d.ts +15 -0
- package/dist/src/llm.js +91 -0
- package/dist/src/markdown.d.ts +18 -0
- package/dist/src/markdown.js +77 -0
- package/dist/src/metadata.d.ts +11 -2
- package/dist/src/metadata.js +161 -29
- package/dist/src/registry-install.d.ts +11 -0
- package/dist/src/registry-install.js +208 -0
- package/dist/src/registry-resolve.d.ts +3 -0
- package/dist/src/registry-resolve.js +231 -0
- package/dist/src/registry-search.d.ts +5 -0
- package/dist/src/registry-search.js +129 -0
- package/dist/src/registry-types.d.ts +55 -0
- package/dist/src/registry-types.js +1 -0
- package/dist/src/ripgrep-install.d.ts +12 -0
- package/dist/src/ripgrep-install.js +169 -0
- package/dist/src/ripgrep-resolve.d.ts +13 -0
- package/dist/src/ripgrep-resolve.js +68 -0
- package/dist/src/ripgrep.d.ts +3 -36
- package/dist/src/ripgrep.js +2 -262
- package/dist/src/similarity.d.ts +1 -2
- package/dist/src/similarity.js +11 -0
- package/dist/src/stash-add.d.ts +4 -0
- package/dist/src/stash-add.js +59 -0
- package/dist/src/stash-ref.d.ts +7 -0
- package/dist/src/stash-ref.js +33 -0
- package/dist/src/stash-registry.d.ts +18 -0
- package/dist/src/stash-registry.js +221 -0
- package/dist/src/stash-resolve.d.ts +2 -0
- package/dist/src/stash-resolve.js +45 -0
- package/dist/src/stash-search.d.ts +8 -0
- package/dist/src/stash-search.js +484 -0
- package/dist/src/stash-show.d.ts +5 -0
- package/dist/src/stash-show.js +114 -0
- package/dist/src/stash-types.d.ts +217 -0
- package/dist/src/stash-types.js +1 -0
- package/dist/src/stash.d.ts +10 -63
- package/dist/src/stash.js +6 -633
- package/dist/src/tool-runner.d.ts +35 -0
- package/dist/src/tool-runner.js +100 -0
- package/dist/src/walker.d.ts +19 -0
- package/dist/src/walker.js +47 -0
- package/package.json +8 -14
- package/src/asset-spec.ts +69 -0
- package/src/cli.ts +282 -46
- package/src/common.ts +58 -0
- package/src/config.ts +183 -0
- package/src/embedder.ts +117 -0
- package/src/frontmatter.ts +95 -0
- package/src/indexer.ts +244 -84
- package/src/init.ts +106 -0
- package/src/llm.ts +124 -0
- package/src/markdown.ts +106 -0
- package/src/metadata.ts +171 -27
- package/src/registry-install.ts +245 -0
- package/src/registry-resolve.ts +272 -0
- package/src/registry-search.ts +145 -0
- package/src/registry-types.ts +64 -0
- package/src/ripgrep-install.ts +200 -0
- package/src/ripgrep-resolve.ts +72 -0
- package/src/ripgrep.ts +3 -315
- package/src/similarity.ts +13 -1
- package/src/stash-add.ts +66 -0
- package/src/stash-ref.ts +41 -0
- package/src/stash-registry.ts +259 -0
- package/src/stash-resolve.ts +47 -0
- package/src/stash-search.ts +595 -0
- package/src/stash-show.ts +112 -0
- package/src/stash-types.ts +221 -0
- package/src/stash.ts +31 -760
- package/src/tool-runner.ts +129 -0
- package/src/walker.ts +53 -0
- package/.claude-plugin/plugin.json +0 -21
- package/commands/open.md +0 -11
- package/commands/run.md +0 -11
- package/commands/search.md +0 -11
- package/dist/src/plugin.d.ts +0 -2
- package/dist/src/plugin.js +0 -55
- package/skills/stash/SKILL.md +0 -73
- package/src/plugin.ts +0 -56
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared frontmatter parsing utilities.
|
|
3
|
+
*
|
|
4
|
+
* Provides a single, canonical YAML-subset frontmatter parser used by both
|
|
5
|
+
* the stash open logic and the metadata generator.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Parse YAML-subset frontmatter from a Markdown (or similar) string.
|
|
9
|
+
*
|
|
10
|
+
* Returns the parsed key-value data and the remaining body content.
|
|
11
|
+
*/
|
|
12
|
+
export declare function parseFrontmatter(raw: string): {
|
|
13
|
+
data: Record<string, unknown>;
|
|
14
|
+
content: string;
|
|
15
|
+
frontmatter: string | null;
|
|
16
|
+
bodyStartLine: number;
|
|
17
|
+
};
|
|
18
|
+
export declare function parseFrontmatterBlock(raw: string): {
|
|
19
|
+
frontmatter: string;
|
|
20
|
+
content: string;
|
|
21
|
+
bodyStartLine: number;
|
|
22
|
+
} | null;
|
|
23
|
+
/**
|
|
24
|
+
* Parse a simple YAML scalar value (string, boolean, or number).
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseYamlScalar(value: string): unknown;
|
|
27
|
+
/**
|
|
28
|
+
* Coerce an unknown value to a trimmed string, or return undefined if empty/non-string.
|
|
29
|
+
*/
|
|
30
|
+
export declare function toStringOrUndefined(value: unknown): string | undefined;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared frontmatter parsing utilities.
|
|
3
|
+
*
|
|
4
|
+
* Provides a single, canonical YAML-subset frontmatter parser used by both
|
|
5
|
+
* the stash open logic and the metadata generator.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Parse YAML-subset frontmatter from a Markdown (or similar) string.
|
|
9
|
+
*
|
|
10
|
+
* Returns the parsed key-value data and the remaining body content.
|
|
11
|
+
*/
|
|
12
|
+
export function parseFrontmatter(raw) {
|
|
13
|
+
const parsedBlock = parseFrontmatterBlock(raw);
|
|
14
|
+
if (!parsedBlock) {
|
|
15
|
+
return { data: {}, content: raw, frontmatter: null, bodyStartLine: 1 };
|
|
16
|
+
}
|
|
17
|
+
const data = {};
|
|
18
|
+
let currentKey = null;
|
|
19
|
+
let nested = null;
|
|
20
|
+
for (const line of parsedBlock.frontmatter.split(/\r?\n/)) {
|
|
21
|
+
const indented = line.match(/^ (\w[\w-]*):\s*(.+)$/);
|
|
22
|
+
if (indented && currentKey && nested) {
|
|
23
|
+
nested[indented[1]] = parseYamlScalar(indented[2].trim());
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const top = line.match(/^(\w[\w-]*):\s*(.*)$/);
|
|
27
|
+
if (!top) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
currentKey = top[1];
|
|
31
|
+
const value = top[2].trim();
|
|
32
|
+
if (value === "") {
|
|
33
|
+
nested = {};
|
|
34
|
+
data[currentKey] = nested;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
nested = null;
|
|
38
|
+
data[currentKey] = parseYamlScalar(value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
data,
|
|
43
|
+
content: parsedBlock.content,
|
|
44
|
+
frontmatter: parsedBlock.frontmatter,
|
|
45
|
+
bodyStartLine: parsedBlock.bodyStartLine,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function parseFrontmatterBlock(raw) {
|
|
49
|
+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
50
|
+
if (!match)
|
|
51
|
+
return null;
|
|
52
|
+
return {
|
|
53
|
+
frontmatter: match[1],
|
|
54
|
+
content: match[2],
|
|
55
|
+
bodyStartLine: countLines(raw.slice(0, match[0].length - match[2].length)) + 1,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function countLines(text) {
|
|
59
|
+
if (text.length === 0)
|
|
60
|
+
return 0;
|
|
61
|
+
return text.split(/\r?\n/).length - 1;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Parse a simple YAML scalar value (string, boolean, or number).
|
|
65
|
+
*/
|
|
66
|
+
export function parseYamlScalar(value) {
|
|
67
|
+
if (value === "")
|
|
68
|
+
return "";
|
|
69
|
+
if (value === "true")
|
|
70
|
+
return true;
|
|
71
|
+
if (value === "false")
|
|
72
|
+
return false;
|
|
73
|
+
const asNumber = Number(value);
|
|
74
|
+
if (!Number.isNaN(asNumber))
|
|
75
|
+
return asNumber;
|
|
76
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
77
|
+
return value.slice(1, -1);
|
|
78
|
+
}
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Coerce an unknown value to a trimmed string, or return undefined if empty/non-string.
|
|
83
|
+
*/
|
|
84
|
+
export function toStringOrUndefined(value) {
|
|
85
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
86
|
+
}
|
package/dist/src/indexer.d.ts
CHANGED
|
@@ -1,26 +1,44 @@
|
|
|
1
1
|
import { type StashEntry } from "./metadata";
|
|
2
|
+
import { type SerializedTfIdf } from "./similarity";
|
|
3
|
+
import type { EmbeddingVector } from "./embedder";
|
|
2
4
|
export interface IndexedEntry {
|
|
3
5
|
entry: StashEntry;
|
|
4
6
|
path: string;
|
|
5
7
|
dirPath: string;
|
|
8
|
+
embedding?: EmbeddingVector;
|
|
6
9
|
}
|
|
7
10
|
export interface SearchIndex {
|
|
8
11
|
version: number;
|
|
9
12
|
builtAt: string;
|
|
10
13
|
stashDir: string;
|
|
14
|
+
/** All stash directories that were indexed (primary + additional) */
|
|
15
|
+
stashDirs?: string[];
|
|
11
16
|
entries: IndexedEntry[];
|
|
12
17
|
/** Serialized TF-IDF state (term frequencies, idf values) */
|
|
13
|
-
tfidf?:
|
|
18
|
+
tfidf?: SerializedTfIdf;
|
|
19
|
+
/** Whether embeddings are included in entries */
|
|
20
|
+
hasEmbeddings?: boolean;
|
|
14
21
|
}
|
|
15
22
|
export interface IndexResponse {
|
|
16
23
|
stashDir: string;
|
|
17
24
|
totalEntries: number;
|
|
18
25
|
generatedMetadata: number;
|
|
19
26
|
indexPath: string;
|
|
27
|
+
mode: "full" | "incremental";
|
|
28
|
+
directoriesScanned: number;
|
|
29
|
+
directoriesSkipped: number;
|
|
30
|
+
/** Timing counters in milliseconds */
|
|
31
|
+
timing?: {
|
|
32
|
+
totalMs: number;
|
|
33
|
+
walkMs: number;
|
|
34
|
+
embedMs: number;
|
|
35
|
+
tfidfMs: number;
|
|
36
|
+
};
|
|
20
37
|
}
|
|
21
38
|
export declare function getIndexPath(): string;
|
|
22
39
|
export declare function loadSearchIndex(): SearchIndex | null;
|
|
23
40
|
export declare function agentikitIndex(options?: {
|
|
24
41
|
stashDir?: string;
|
|
25
|
-
|
|
42
|
+
full?: boolean;
|
|
43
|
+
}): Promise<IndexResponse>;
|
|
26
44
|
export declare function buildSearchText(entry: StashEntry): string;
|
package/dist/src/indexer.js
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { resolveStashDir } from "./common";
|
|
4
|
+
import { ASSET_TYPES, TYPE_DIRS, deriveCanonicalAssetName } from "./asset-spec";
|
|
3
5
|
import { loadStashFile, writeStashFile, generateMetadata, } from "./metadata";
|
|
4
6
|
import { TfIdfAdapter } from "./similarity";
|
|
7
|
+
import { walkStash } from "./walker";
|
|
5
8
|
// ── Constants ───────────────────────────────────────────────────────────────
|
|
6
|
-
const INDEX_VERSION =
|
|
7
|
-
const SCRIPT_EXTENSIONS = new Set([".sh", ".ts", ".js", ".ps1", ".cmd", ".bat"]);
|
|
8
|
-
const TYPE_DIRS = {
|
|
9
|
-
tool: "tools",
|
|
10
|
-
skill: "skills",
|
|
11
|
-
command: "commands",
|
|
12
|
-
agent: "agents",
|
|
13
|
-
};
|
|
9
|
+
const INDEX_VERSION = 4;
|
|
14
10
|
// ── Index Path ──────────────────────────────────────────────────────────────
|
|
15
11
|
export function getIndexPath() {
|
|
16
12
|
const cacheDir = process.env.XDG_CACHE_HOME
|
|
@@ -32,37 +28,98 @@ export function loadSearchIndex() {
|
|
|
32
28
|
}
|
|
33
29
|
}
|
|
34
30
|
// ── Indexer ──────────────────────────────────────────────────────────────────
|
|
35
|
-
export function agentikitIndex(options) {
|
|
36
|
-
const stashDir = options?.stashDir ||
|
|
31
|
+
export async function agentikitIndex(options) {
|
|
32
|
+
const stashDir = options?.stashDir || resolveStashDir();
|
|
33
|
+
// Load config to get additional stash dirs and semantic search setting
|
|
34
|
+
const { loadConfig } = await import("./config.js");
|
|
35
|
+
const config = loadConfig(stashDir);
|
|
36
|
+
const allStashDirs = [stashDir];
|
|
37
|
+
for (const d of config.additionalStashDirs) {
|
|
38
|
+
try {
|
|
39
|
+
if (fs.statSync(d).isDirectory() && !allStashDirs.includes(path.resolve(d))) {
|
|
40
|
+
allStashDirs.push(path.resolve(d));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch { /* skip nonexistent dirs */ }
|
|
44
|
+
}
|
|
45
|
+
const t0 = Date.now();
|
|
37
46
|
const allEntries = [];
|
|
38
47
|
let generatedCount = 0;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
let scannedDirs = 0;
|
|
49
|
+
let skippedDirs = 0;
|
|
50
|
+
// Load previous index for incremental mode
|
|
51
|
+
const previousIndex = !options?.full ? loadSearchIndex() : null;
|
|
52
|
+
const isIncremental = previousIndex !== null && previousIndex.stashDir === stashDir;
|
|
53
|
+
const builtAtMs = isIncremental ? new Date(previousIndex.builtAt).getTime() : 0;
|
|
54
|
+
// Build lookup of previous entries by dirPath
|
|
55
|
+
const previousEntriesByDir = new Map();
|
|
56
|
+
if (isIncremental) {
|
|
57
|
+
for (const ie of previousIndex.entries) {
|
|
58
|
+
const list = previousEntriesByDir.get(ie.dirPath) || [];
|
|
59
|
+
list.push(ie);
|
|
60
|
+
previousEntriesByDir.set(ie.dirPath, list);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const seenPaths = new Set();
|
|
64
|
+
const tWalkStart = Date.now();
|
|
65
|
+
for (const currentStashDir of allStashDirs) {
|
|
66
|
+
for (const assetType of ASSET_TYPES) {
|
|
67
|
+
const typeRoot = path.join(currentStashDir, TYPE_DIRS[assetType]);
|
|
68
|
+
try {
|
|
69
|
+
if (!fs.statSync(typeRoot).isDirectory())
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
continue;
|
|
55
74
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
75
|
+
// Group files by their immediate parent directory
|
|
76
|
+
const dirGroups = walkStash(typeRoot, assetType);
|
|
77
|
+
for (const { dirPath, files } of dirGroups) {
|
|
78
|
+
// Deduplicate by dirPath across stash dirs
|
|
79
|
+
if (seenPaths.has(path.resolve(dirPath)))
|
|
80
|
+
continue;
|
|
81
|
+
seenPaths.add(path.resolve(dirPath));
|
|
82
|
+
// Incremental: skip directories that haven't changed
|
|
83
|
+
const prevEntries = previousEntriesByDir.get(dirPath);
|
|
84
|
+
if (isIncremental && prevEntries && !isDirStale(dirPath, files, prevEntries, builtAtMs)) {
|
|
85
|
+
allEntries.push(...prevEntries);
|
|
86
|
+
skippedDirs++;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
scannedDirs++;
|
|
90
|
+
// Try loading existing .stash.json
|
|
91
|
+
let stash = loadStashFile(dirPath);
|
|
92
|
+
if (stash) {
|
|
93
|
+
const migration = migrateGeneratedSkillMetadata(stash, files, typeRoot);
|
|
94
|
+
if (migration.changed) {
|
|
95
|
+
stash = migration.stash;
|
|
96
|
+
writeStashFile(dirPath, stash);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (!stash) {
|
|
100
|
+
// Generate metadata
|
|
101
|
+
stash = generateMetadata(dirPath, assetType, files, typeRoot);
|
|
102
|
+
// Enhance with LLM if configured
|
|
103
|
+
if (config.llm && stash.entries.length > 0) {
|
|
104
|
+
stash = await enhanceStashWithLlm(config.llm, stash, dirPath, files);
|
|
105
|
+
}
|
|
106
|
+
if (stash.entries.length > 0) {
|
|
107
|
+
writeStashFile(dirPath, stash);
|
|
108
|
+
generatedCount += stash.entries.length;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (stash) {
|
|
112
|
+
for (const entry of stash.entries) {
|
|
113
|
+
const entryPath = entry.entry
|
|
114
|
+
? path.join(dirPath, entry.entry)
|
|
115
|
+
: files[0] || dirPath;
|
|
116
|
+
allEntries.push({ entry, path: entryPath, dirPath });
|
|
117
|
+
}
|
|
62
118
|
}
|
|
63
119
|
}
|
|
64
120
|
}
|
|
65
121
|
}
|
|
122
|
+
const tWalkEnd = Date.now();
|
|
66
123
|
// Build TF-IDF index
|
|
67
124
|
const adapter = new TfIdfAdapter();
|
|
68
125
|
const scoredEntries = allEntries.map((ie) => ({
|
|
@@ -72,6 +129,25 @@ export function agentikitIndex(options) {
|
|
|
72
129
|
path: ie.path,
|
|
73
130
|
}));
|
|
74
131
|
adapter.buildIndex(scoredEntries);
|
|
132
|
+
const tTfidfEnd = Date.now();
|
|
133
|
+
// Generate embeddings if semantic search is enabled
|
|
134
|
+
let hasEmbeddings = false;
|
|
135
|
+
if (config.semanticSearch) {
|
|
136
|
+
try {
|
|
137
|
+
const { embed } = await import("./embedder.js");
|
|
138
|
+
for (const ie of allEntries) {
|
|
139
|
+
if (!ie.embedding) {
|
|
140
|
+
const text = buildSearchText(ie.entry);
|
|
141
|
+
ie.embedding = await embed(text, config.embedding);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
hasEmbeddings = true;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Embedding provider not available, continue without embeddings
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const tEmbedEnd = Date.now();
|
|
75
151
|
// Persist index
|
|
76
152
|
const indexPath = getIndexPath();
|
|
77
153
|
const indexDir = path.dirname(indexPath);
|
|
@@ -82,59 +158,119 @@ export function agentikitIndex(options) {
|
|
|
82
158
|
version: INDEX_VERSION,
|
|
83
159
|
builtAt: new Date().toISOString(),
|
|
84
160
|
stashDir,
|
|
161
|
+
stashDirs: allStashDirs,
|
|
85
162
|
entries: allEntries,
|
|
86
163
|
tfidf: adapter.serialize(),
|
|
164
|
+
hasEmbeddings,
|
|
87
165
|
};
|
|
88
166
|
fs.writeFileSync(indexPath, JSON.stringify(index) + "\n", "utf8");
|
|
167
|
+
const tEnd = Date.now();
|
|
89
168
|
return {
|
|
90
169
|
stashDir,
|
|
91
170
|
totalEntries: allEntries.length,
|
|
92
171
|
generatedMetadata: generatedCount,
|
|
93
172
|
indexPath,
|
|
173
|
+
mode: isIncremental ? "incremental" : "full",
|
|
174
|
+
directoriesScanned: scannedDirs,
|
|
175
|
+
directoriesSkipped: skippedDirs,
|
|
176
|
+
timing: {
|
|
177
|
+
totalMs: tEnd - t0,
|
|
178
|
+
walkMs: tWalkEnd - tWalkStart, // includes metadata generation (interleaved)
|
|
179
|
+
embedMs: tEmbedEnd - tTfidfEnd,
|
|
180
|
+
tfidfMs: tTfidfEnd - tWalkEnd,
|
|
181
|
+
},
|
|
94
182
|
};
|
|
95
183
|
}
|
|
96
184
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
97
|
-
function
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
groups.set(parentDir, [fullPath]);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
185
|
+
function isDirStale(dirPath, currentFiles, previousEntries, builtAtMs) {
|
|
186
|
+
// Check if file set changed (additions or deletions)
|
|
187
|
+
const prevFileNames = new Set(previousEntries
|
|
188
|
+
.map((ie) => ie.entry.entry)
|
|
189
|
+
.filter((e) => !!e));
|
|
190
|
+
const currFileNames = new Set(currentFiles.map((f) => path.basename(f)));
|
|
191
|
+
if (prevFileNames.size !== currFileNames.size)
|
|
192
|
+
return true;
|
|
193
|
+
for (const name of currFileNames) {
|
|
194
|
+
if (!prevFileNames.has(name))
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
// Check modification times of current files
|
|
198
|
+
for (const file of currentFiles) {
|
|
199
|
+
try {
|
|
200
|
+
if (fs.statSync(file).mtimeMs > builtAtMs)
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return true;
|
|
120
205
|
}
|
|
206
|
+
}
|
|
207
|
+
// Check .stash.json modification time
|
|
208
|
+
const stashPath = path.join(dirPath, ".stash.json");
|
|
209
|
+
try {
|
|
210
|
+
if (fs.existsSync(stashPath) && fs.statSync(stashPath).mtimeMs > builtAtMs)
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// ignore
|
|
215
|
+
}
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
function migrateGeneratedSkillMetadata(stash, files, typeRoot) {
|
|
219
|
+
const fileByBaseName = new Map(files.map((filePath) => [path.basename(filePath), filePath]));
|
|
220
|
+
let changed = false;
|
|
221
|
+
const entries = stash.entries.map((entry) => {
|
|
222
|
+
if (entry.type !== "skill" || entry.generated !== true)
|
|
223
|
+
return entry;
|
|
224
|
+
const hintedFilePath = entry.entry ? fileByBaseName.get(path.basename(entry.entry)) : undefined;
|
|
225
|
+
const skillFilePath = hintedFilePath ?? fileByBaseName.get("SKILL.md");
|
|
226
|
+
if (!skillFilePath)
|
|
227
|
+
return entry;
|
|
228
|
+
const canonicalName = deriveCanonicalAssetName("skill", typeRoot, skillFilePath);
|
|
229
|
+
if (!canonicalName || canonicalName === entry.name)
|
|
230
|
+
return entry;
|
|
231
|
+
changed = true;
|
|
232
|
+
return { ...entry, name: canonicalName };
|
|
233
|
+
});
|
|
234
|
+
if (!changed) {
|
|
235
|
+
return { stash, changed: false };
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
stash: { entries },
|
|
239
|
+
changed: true,
|
|
121
240
|
};
|
|
122
|
-
walk(typeRoot);
|
|
123
|
-
return groups;
|
|
124
241
|
}
|
|
125
|
-
function
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
242
|
+
async function enhanceStashWithLlm(llmConfig, stash, dirPath, files) {
|
|
243
|
+
const { enhanceMetadata } = await import("./llm.js");
|
|
244
|
+
const enhanced = [];
|
|
245
|
+
for (const entry of stash.entries) {
|
|
246
|
+
try {
|
|
247
|
+
// Find the file matching this entry for content context
|
|
248
|
+
const entryFile = entry.entry
|
|
249
|
+
? files.find((f) => path.basename(f) === entry.entry) ?? files[0]
|
|
250
|
+
: files[0];
|
|
251
|
+
let fileContent;
|
|
252
|
+
if (entryFile) {
|
|
253
|
+
try {
|
|
254
|
+
fileContent = fs.readFileSync(entryFile, "utf8");
|
|
255
|
+
}
|
|
256
|
+
catch { /* ignore unreadable files */ }
|
|
257
|
+
}
|
|
258
|
+
const improvements = await enhanceMetadata(llmConfig, entry, fileContent);
|
|
259
|
+
const updated = { ...entry };
|
|
260
|
+
if (improvements.description)
|
|
261
|
+
updated.description = improvements.description;
|
|
262
|
+
if (improvements.intents?.length)
|
|
263
|
+
updated.intents = improvements.intents;
|
|
264
|
+
if (improvements.tags?.length)
|
|
265
|
+
updated.tags = improvements.tags;
|
|
266
|
+
enhanced.push(updated);
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
// LLM enhancement failed for this entry, keep original
|
|
270
|
+
enhanced.push(entry);
|
|
271
|
+
}
|
|
137
272
|
}
|
|
273
|
+
return { entries: enhanced };
|
|
138
274
|
}
|
|
139
275
|
export function buildSearchText(entry) {
|
|
140
276
|
const parts = [entry.name.replace(/[-_]/g, " ")];
|
|
@@ -144,6 +280,10 @@ export function buildSearchText(entry) {
|
|
|
144
280
|
parts.push(entry.tags.join(" "));
|
|
145
281
|
if (entry.examples)
|
|
146
282
|
parts.push(entry.examples.join(" "));
|
|
283
|
+
if (entry.aliases)
|
|
284
|
+
parts.push(entry.aliases.join(" "));
|
|
285
|
+
if (entry.intents)
|
|
286
|
+
parts.push(entry.intents.join(" "));
|
|
147
287
|
if (entry.intent) {
|
|
148
288
|
if (entry.intent.when)
|
|
149
289
|
parts.push(entry.intent.when);
|
|
@@ -152,16 +292,8 @@ export function buildSearchText(entry) {
|
|
|
152
292
|
if (entry.intent.output)
|
|
153
293
|
parts.push(entry.intent.output);
|
|
154
294
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
function resolveStashDirForIndex() {
|
|
158
|
-
const raw = process.env.AGENTIKIT_STASH_DIR?.trim();
|
|
159
|
-
if (!raw) {
|
|
160
|
-
throw new Error("AGENTIKIT_STASH_DIR is not set. Run 'agentikit init' first.");
|
|
295
|
+
if (entry.toc) {
|
|
296
|
+
parts.push(entry.toc.map((h) => h.text).join(" "));
|
|
161
297
|
}
|
|
162
|
-
|
|
163
|
-
if (!fs.existsSync(stashDir) || !fs.statSync(stashDir).isDirectory()) {
|
|
164
|
-
throw new Error(`AGENTIKIT_STASH_DIR does not exist or is not a directory: "${stashDir}"`);
|
|
165
|
-
}
|
|
166
|
-
return stashDir;
|
|
298
|
+
return parts.join(" ").toLowerCase();
|
|
167
299
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentikit initialization logic.
|
|
3
|
+
*
|
|
4
|
+
* Creates the stash directory structure, sets the AGENTIKIT_STASH_DIR
|
|
5
|
+
* environment variable, and ensures ripgrep is available.
|
|
6
|
+
*/
|
|
7
|
+
export interface InitResponse {
|
|
8
|
+
stashDir: string;
|
|
9
|
+
created: boolean;
|
|
10
|
+
envSet: boolean;
|
|
11
|
+
profileUpdated?: string;
|
|
12
|
+
configPath: string;
|
|
13
|
+
ripgrep?: {
|
|
14
|
+
rgPath: string;
|
|
15
|
+
installed: boolean;
|
|
16
|
+
version: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export declare function agentikitInit(): InitResponse;
|
package/dist/src/init.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentikit initialization logic.
|
|
3
|
+
*
|
|
4
|
+
* Creates the stash directory structure, sets the AGENTIKIT_STASH_DIR
|
|
5
|
+
* environment variable, and ensures ripgrep is available.
|
|
6
|
+
*/
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { IS_WINDOWS, TYPE_DIRS } from "./common";
|
|
11
|
+
import { ensureRg } from "./ripgrep-install";
|
|
12
|
+
import { getConfigPath, saveConfig, DEFAULT_CONFIG } from "./config";
|
|
13
|
+
export function agentikitInit() {
|
|
14
|
+
let stashDir;
|
|
15
|
+
if (IS_WINDOWS) {
|
|
16
|
+
const userProfile = process.env.USERPROFILE?.trim();
|
|
17
|
+
if (!userProfile) {
|
|
18
|
+
throw new Error("Unable to determine Documents folder. Ensure USERPROFILE is set.");
|
|
19
|
+
}
|
|
20
|
+
const docs = path.join(userProfile, "Documents");
|
|
21
|
+
stashDir = path.join(docs, "agentikit");
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const home = process.env.HOME?.trim();
|
|
25
|
+
if (!home) {
|
|
26
|
+
throw new Error("Unable to determine home directory. Set HOME.");
|
|
27
|
+
}
|
|
28
|
+
stashDir = path.join(home, "agentikit");
|
|
29
|
+
}
|
|
30
|
+
let created = false;
|
|
31
|
+
if (!fs.existsSync(stashDir)) {
|
|
32
|
+
fs.mkdirSync(stashDir, { recursive: true });
|
|
33
|
+
created = true;
|
|
34
|
+
}
|
|
35
|
+
for (const sub of Object.values(TYPE_DIRS)) {
|
|
36
|
+
const subDir = path.join(stashDir, sub);
|
|
37
|
+
if (!fs.existsSync(subDir)) {
|
|
38
|
+
fs.mkdirSync(subDir, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
let envSet = false;
|
|
42
|
+
let profileUpdated;
|
|
43
|
+
if (IS_WINDOWS) {
|
|
44
|
+
const result = spawnSync("setx", ["AGENTIKIT_STASH_DIR", stashDir], {
|
|
45
|
+
encoding: "utf8",
|
|
46
|
+
timeout: 10_000,
|
|
47
|
+
});
|
|
48
|
+
envSet = result.status === 0;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const shell = process.env.SHELL || "";
|
|
52
|
+
const homeDir = process.env.HOME; // already validated non-empty above
|
|
53
|
+
let profile;
|
|
54
|
+
if (shell.endsWith("/zsh")) {
|
|
55
|
+
profile = path.join(homeDir, ".zshrc");
|
|
56
|
+
}
|
|
57
|
+
else if (shell.endsWith("/bash")) {
|
|
58
|
+
profile = path.join(homeDir, ".bashrc");
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
profile = path.join(homeDir, ".profile");
|
|
62
|
+
}
|
|
63
|
+
const exportLine = `export AGENTIKIT_STASH_DIR="${stashDir}"`;
|
|
64
|
+
const existing = fs.existsSync(profile) ? fs.readFileSync(profile, "utf8") : "";
|
|
65
|
+
if (!existing.includes("AGENTIKIT_STASH_DIR")) {
|
|
66
|
+
fs.appendFileSync(profile, `\n# Agentikit stash directory\n${exportLine}\n`);
|
|
67
|
+
envSet = true;
|
|
68
|
+
profileUpdated = profile;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Create default config.json if it doesn't exist
|
|
72
|
+
const configPath = getConfigPath(stashDir);
|
|
73
|
+
if (!fs.existsSync(configPath)) {
|
|
74
|
+
saveConfig(DEFAULT_CONFIG, stashDir);
|
|
75
|
+
}
|
|
76
|
+
process.env.AGENTIKIT_STASH_DIR = stashDir;
|
|
77
|
+
// Ensure ripgrep is available (install to stash/bin if needed)
|
|
78
|
+
let ripgrep;
|
|
79
|
+
try {
|
|
80
|
+
const rgResult = ensureRg(stashDir);
|
|
81
|
+
ripgrep = rgResult;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Non-fatal: ripgrep is optional, search works without it
|
|
85
|
+
}
|
|
86
|
+
return { stashDir, created, envSet, profileUpdated, configPath, ripgrep };
|
|
87
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { LlmConnectionConfig } from "./config";
|
|
2
|
+
import type { StashEntry } from "./metadata";
|
|
3
|
+
/**
|
|
4
|
+
* Use an LLM to enhance a stash entry's metadata: improve description,
|
|
5
|
+
* generate intents, and suggest tags.
|
|
6
|
+
*/
|
|
7
|
+
export declare function enhanceMetadata(config: LlmConnectionConfig, entry: StashEntry, fileContent?: string): Promise<{
|
|
8
|
+
description?: string;
|
|
9
|
+
intents?: string[];
|
|
10
|
+
tags?: string[];
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Check if the LLM endpoint is reachable.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isLlmAvailable(config: LlmConnectionConfig): Promise<boolean>;
|