agentikit 0.0.3 → 0.0.8

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.
Files changed (80) hide show
  1. package/README.md +113 -77
  2. package/dist/index.d.ts +15 -3
  3. package/dist/index.js +8 -2
  4. package/dist/src/asset-spec.d.ts +14 -0
  5. package/dist/src/asset-spec.js +46 -0
  6. package/dist/src/cli.js +154 -52
  7. package/dist/src/common.d.ts +8 -0
  8. package/dist/src/common.js +46 -0
  9. package/dist/src/config.d.ts +31 -0
  10. package/dist/src/config.js +74 -0
  11. package/dist/src/embedder.d.ts +10 -0
  12. package/dist/src/embedder.js +87 -0
  13. package/dist/src/frontmatter.d.ts +30 -0
  14. package/dist/src/frontmatter.js +86 -0
  15. package/dist/src/indexer.d.ts +20 -2
  16. package/dist/src/indexer.js +212 -80
  17. package/dist/src/init.d.ts +19 -0
  18. package/dist/src/init.js +87 -0
  19. package/dist/src/llm.d.ts +15 -0
  20. package/dist/src/llm.js +91 -0
  21. package/dist/src/markdown.d.ts +18 -0
  22. package/dist/src/markdown.js +77 -0
  23. package/dist/src/metadata.d.ts +10 -2
  24. package/dist/src/metadata.js +146 -30
  25. package/dist/src/ripgrep-install.d.ts +12 -0
  26. package/dist/src/ripgrep-install.js +169 -0
  27. package/dist/src/ripgrep-resolve.d.ts +13 -0
  28. package/dist/src/ripgrep-resolve.js +68 -0
  29. package/dist/src/ripgrep.d.ts +3 -0
  30. package/dist/src/ripgrep.js +2 -0
  31. package/dist/src/similarity.d.ts +1 -2
  32. package/dist/src/similarity.js +35 -9
  33. package/dist/src/stash-ref.d.ts +7 -0
  34. package/dist/src/stash-ref.js +33 -0
  35. package/dist/src/stash-resolve.d.ts +2 -0
  36. package/dist/src/stash-resolve.js +45 -0
  37. package/dist/src/stash-search.d.ts +6 -0
  38. package/dist/src/stash-search.js +269 -0
  39. package/dist/src/stash-show.d.ts +5 -0
  40. package/dist/src/stash-show.js +107 -0
  41. package/dist/src/stash-types.d.ts +53 -0
  42. package/dist/src/stash-types.js +1 -0
  43. package/dist/src/stash.d.ts +8 -58
  44. package/dist/src/stash.js +4 -580
  45. package/dist/src/tool-runner.d.ts +35 -0
  46. package/dist/src/tool-runner.js +100 -0
  47. package/dist/src/walker.d.ts +19 -0
  48. package/dist/src/walker.js +47 -0
  49. package/package.json +8 -14
  50. package/src/asset-spec.ts +69 -0
  51. package/src/cli.ts +164 -48
  52. package/src/common.ts +58 -0
  53. package/src/config.ts +124 -0
  54. package/src/embedder.ts +117 -0
  55. package/src/frontmatter.ts +95 -0
  56. package/src/indexer.ts +244 -84
  57. package/src/init.ts +106 -0
  58. package/src/llm.ts +124 -0
  59. package/src/markdown.ts +106 -0
  60. package/src/metadata.ts +157 -29
  61. package/src/ripgrep-install.ts +200 -0
  62. package/src/ripgrep-resolve.ts +72 -0
  63. package/src/ripgrep.ts +3 -0
  64. package/src/similarity.ts +33 -9
  65. package/src/stash-ref.ts +41 -0
  66. package/src/stash-resolve.ts +47 -0
  67. package/src/stash-search.ts +343 -0
  68. package/src/stash-show.ts +104 -0
  69. package/src/stash-types.ts +46 -0
  70. package/src/stash.ts +16 -695
  71. package/src/tool-runner.ts +129 -0
  72. package/src/walker.ts +53 -0
  73. package/.claude-plugin/plugin.json +0 -21
  74. package/commands/open.md +0 -11
  75. package/commands/run.md +0 -11
  76. package/commands/search.md +0 -11
  77. package/dist/src/plugin.d.ts +0 -2
  78. package/dist/src/plugin.js +0 -55
  79. package/skills/stash/SKILL.md +0 -68
  80. package/src/plugin.ts +0 -56
@@ -0,0 +1,31 @@
1
+ export interface EmbeddingConnectionConfig {
2
+ /** OpenAI-compatible embeddings endpoint (e.g. "http://localhost:11434/v1/embeddings") */
3
+ endpoint: string;
4
+ /** Model name to use for embeddings (e.g. "nomic-embed-text") */
5
+ model: string;
6
+ /** Optional API key for authenticated endpoints */
7
+ apiKey?: string;
8
+ }
9
+ export interface LlmConnectionConfig {
10
+ /** OpenAI-compatible chat completions endpoint (e.g. "http://localhost:11434/v1/chat/completions") */
11
+ endpoint: string;
12
+ /** Model name to use (e.g. "llama3.2") */
13
+ model: string;
14
+ /** Optional API key for authenticated endpoints */
15
+ apiKey?: string;
16
+ }
17
+ export interface AgentikitConfig {
18
+ /** Whether semantic search is enabled. Default: true */
19
+ semanticSearch: boolean;
20
+ /** Additional stash directories to search alongside the primary one */
21
+ additionalStashDirs: string[];
22
+ /** OpenAI-compatible embedding endpoint config. If not set, uses local @xenova/transformers */
23
+ embedding?: EmbeddingConnectionConfig;
24
+ /** OpenAI-compatible LLM endpoint config for metadata generation. If not set, uses heuristic generation */
25
+ llm?: LlmConnectionConfig;
26
+ }
27
+ export declare const DEFAULT_CONFIG: AgentikitConfig;
28
+ export declare function getConfigPath(stashDir: string): string;
29
+ export declare function loadConfig(stashDir?: string): AgentikitConfig;
30
+ export declare function saveConfig(config: AgentikitConfig, stashDir?: string): void;
31
+ export declare function updateConfig(partial: Partial<AgentikitConfig>, stashDir?: string): AgentikitConfig;
@@ -0,0 +1,74 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { resolveStashDir } from "./common";
4
+ // ── Defaults ────────────────────────────────────────────────────────────────
5
+ export const DEFAULT_CONFIG = {
6
+ semanticSearch: true,
7
+ additionalStashDirs: [],
8
+ };
9
+ // ── Paths ───────────────────────────────────────────────────────────────────
10
+ export function getConfigPath(stashDir) {
11
+ return path.join(stashDir, "config.json");
12
+ }
13
+ // ── Load / Save / Update ────────────────────────────────────────────────────
14
+ export function loadConfig(stashDir) {
15
+ const dir = stashDir ?? resolveStashDir();
16
+ const configPath = getConfigPath(dir);
17
+ let raw;
18
+ try {
19
+ raw = JSON.parse(fs.readFileSync(configPath, "utf8"));
20
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
21
+ return { ...DEFAULT_CONFIG };
22
+ }
23
+ }
24
+ catch {
25
+ return { ...DEFAULT_CONFIG };
26
+ }
27
+ return pickKnownKeys(raw);
28
+ }
29
+ export function saveConfig(config, stashDir) {
30
+ const dir = stashDir ?? resolveStashDir();
31
+ const configPath = getConfigPath(dir);
32
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
33
+ }
34
+ export function updateConfig(partial, stashDir) {
35
+ const dir = stashDir ?? resolveStashDir();
36
+ const current = loadConfig(dir);
37
+ const merged = { ...current, ...partial };
38
+ saveConfig(merged, dir);
39
+ return merged;
40
+ }
41
+ // ── Helpers ─────────────────────────────────────────────────────────────────
42
+ function pickKnownKeys(raw) {
43
+ const config = { ...DEFAULT_CONFIG };
44
+ if (typeof raw.semanticSearch === "boolean") {
45
+ config.semanticSearch = raw.semanticSearch;
46
+ }
47
+ if (Array.isArray(raw.additionalStashDirs)) {
48
+ config.additionalStashDirs = raw.additionalStashDirs.filter((d) => typeof d === "string");
49
+ }
50
+ const embedding = parseConnectionConfig(raw.embedding);
51
+ if (embedding)
52
+ config.embedding = embedding;
53
+ const llm = parseConnectionConfig(raw.llm);
54
+ if (llm)
55
+ config.llm = llm;
56
+ return config;
57
+ }
58
+ function parseConnectionConfig(value) {
59
+ if (typeof value !== "object" || value === null || Array.isArray(value))
60
+ return undefined;
61
+ const obj = value;
62
+ if (typeof obj.endpoint !== "string" || !obj.endpoint)
63
+ return undefined;
64
+ if (typeof obj.model !== "string" || !obj.model)
65
+ return undefined;
66
+ const result = {
67
+ endpoint: obj.endpoint,
68
+ model: obj.model,
69
+ };
70
+ if (typeof obj.apiKey === "string" && obj.apiKey) {
71
+ result.apiKey = obj.apiKey;
72
+ }
73
+ return result;
74
+ }
@@ -0,0 +1,10 @@
1
+ import type { EmbeddingConnectionConfig } from "./config";
2
+ export type EmbeddingVector = number[];
3
+ /**
4
+ * Generate an embedding for the given text.
5
+ * If embeddingConfig is provided, uses the configured OpenAI-compatible endpoint.
6
+ * Otherwise falls back to local @xenova/transformers.
7
+ */
8
+ export declare function embed(text: string, embeddingConfig?: EmbeddingConnectionConfig): Promise<EmbeddingVector>;
9
+ export declare function cosineSimilarity(a: EmbeddingVector, b: EmbeddingVector): number;
10
+ export declare function isEmbeddingAvailable(embeddingConfig?: EmbeddingConnectionConfig): Promise<boolean>;
@@ -0,0 +1,87 @@
1
+ // ── Singleton local embedder ────────────────────────────────────────────────
2
+ let localEmbedder;
3
+ async function getLocalEmbedder() {
4
+ if (!localEmbedder) {
5
+ let pipeline;
6
+ try {
7
+ const mod = await import("@xenova/transformers");
8
+ pipeline = mod.pipeline;
9
+ }
10
+ catch {
11
+ throw new Error("Semantic search requires @xenova/transformers. Install it with: npm install @xenova/transformers");
12
+ }
13
+ localEmbedder = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
14
+ }
15
+ return localEmbedder;
16
+ }
17
+ async function embedLocal(text) {
18
+ const model = await getLocalEmbedder();
19
+ const result = await model(text, { pooling: "mean", normalize: true });
20
+ return Array.from(result.data);
21
+ }
22
+ // ── OpenAI-compatible remote embedder ───────────────────────────────────────
23
+ async function embedRemote(text, config) {
24
+ const headers = { "Content-Type": "application/json" };
25
+ if (config.apiKey) {
26
+ headers["Authorization"] = `Bearer ${config.apiKey}`;
27
+ }
28
+ const response = await fetch(config.endpoint, {
29
+ method: "POST",
30
+ headers,
31
+ body: JSON.stringify({
32
+ input: text,
33
+ model: config.model,
34
+ }),
35
+ });
36
+ if (!response.ok) {
37
+ const body = await response.text().catch(() => "");
38
+ throw new Error(`Embedding request failed (${response.status}): ${body}`);
39
+ }
40
+ const json = (await response.json());
41
+ if (!json.data?.[0]?.embedding) {
42
+ throw new Error("Unexpected embedding response format: missing data[0].embedding");
43
+ }
44
+ return json.data[0].embedding;
45
+ }
46
+ // ── Public API ──────────────────────────────────────────────────────────────
47
+ /**
48
+ * Generate an embedding for the given text.
49
+ * If embeddingConfig is provided, uses the configured OpenAI-compatible endpoint.
50
+ * Otherwise falls back to local @xenova/transformers.
51
+ */
52
+ export async function embed(text, embeddingConfig) {
53
+ if (embeddingConfig) {
54
+ return embedRemote(text, embeddingConfig);
55
+ }
56
+ return embedLocal(text);
57
+ }
58
+ // ── Similarity ──────────────────────────────────────────────────────────────
59
+ export function cosineSimilarity(a, b) {
60
+ const len = Math.min(a.length, b.length);
61
+ if (len === 0)
62
+ return 0;
63
+ let dot = 0;
64
+ for (let i = 0; i < len; i++) {
65
+ dot += a[i] * b[i];
66
+ }
67
+ return dot;
68
+ }
69
+ // ── Availability check ──────────────────────────────────────────────────────
70
+ export async function isEmbeddingAvailable(embeddingConfig) {
71
+ if (embeddingConfig) {
72
+ try {
73
+ await embedRemote("test", embeddingConfig);
74
+ return true;
75
+ }
76
+ catch {
77
+ return false;
78
+ }
79
+ }
80
+ try {
81
+ await getLocalEmbedder();
82
+ return true;
83
+ }
84
+ catch {
85
+ return false;
86
+ }
87
+ }
@@ -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
+ }
@@ -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?: unknown;
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
- }): IndexResponse;
42
+ full?: boolean;
43
+ }): Promise<IndexResponse>;
26
44
  export declare function buildSearchText(entry: StashEntry): string;