agentikit 0.0.3

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.
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: stash
3
+ description: Search, open, and run extension assets from an Agentikit stash directory. Use when the user wants to find tools, skills, commands, or agents in their stash, view asset contents, or execute stash tools.
4
+ ---
5
+
6
+ # Agentikit Stash
7
+
8
+ You have access to the `agentikit` CLI to manage extension assets from a stash directory.
9
+
10
+ The stash directory is configured via the `AGENTIKIT_STASH_DIR` environment variable and contains:
11
+
12
+ - **tools/** — executable scripts (.sh, .ts, .js, .ps1, .cmd, .bat)
13
+ - **skills/** — skill directories containing SKILL.md
14
+ - **commands/** — markdown template files
15
+ - **agents/** — markdown agent definition files
16
+
17
+ ## Commands
18
+
19
+ ### Build the search index
20
+
21
+ Scan stash directories, auto-generate missing `.stash.json` metadata, and build a semantic search index.
22
+
23
+ ```bash
24
+ agentikit index
25
+ ```
26
+
27
+ Run this after adding new extensions to enable semantic search ranking.
28
+
29
+ ### Search the stash
30
+
31
+ Find assets by semantic similarity (if indexed) or name substring. Returns JSON with matching hits including `openRef` identifiers.
32
+
33
+ ```bash
34
+ agentikit search [query] [--type tool|skill|command|agent|any] [--limit N]
35
+ ```
36
+
37
+ ### Open an asset
38
+
39
+ Retrieve the full content/payload of an asset using its `openRef` from search results.
40
+
41
+ ```bash
42
+ agentikit open <openRef>
43
+ ```
44
+
45
+ Returns type-specific payloads:
46
+ - **skill** → full SKILL.md content
47
+ - **command** → markdown template + description
48
+ - **agent** → prompt + description, toolPolicy, modelHint
49
+ - **tool** → execution command and kind
50
+
51
+ ### Run a tool
52
+
53
+ Execute a tool asset by its `openRef`. Only tool refs are supported.
54
+
55
+ ```bash
56
+ agentikit run <openRef>
57
+ ```
58
+
59
+ Returns the tool's stdout/stderr output and exit code.
60
+
61
+ ## Workflow
62
+
63
+ 1. Build the index: `agentikit index`
64
+ 2. Search for assets: `agentikit search "deploy" --type tool`
65
+ 3. Inspect a result: `agentikit open <openRef>`
66
+ 4. Run a tool: `agentikit run <openRef>`
67
+
68
+ All output is JSON for easy parsing.
package/src/cli.ts ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ import { agentikitSearch, agentikitOpen, agentikitRun, agentikitInit } from "./stash"
3
+ import { agentikitIndex } from "./indexer"
4
+
5
+ const args = process.argv.slice(2)
6
+ const command = args[0]
7
+
8
+ function flag(name: string): string | undefined {
9
+ const idx = args.indexOf(name)
10
+ return idx >= 0 && idx + 1 < args.length ? args[idx + 1] : undefined
11
+ }
12
+
13
+ function usage(): never {
14
+ console.error("Usage: agentikit <init|search|open|run> [options]")
15
+ console.error("")
16
+ console.error("Commands:")
17
+ console.error(" init Initialize agentikit stash directory and set AGENTIKIT_STASH_DIR")
18
+ console.error(" index Build search index with metadata generation")
19
+ console.error(" search [query] Search the stash (--type tool|skill|command|agent|any) (--limit N)")
20
+ console.error(" open <type:name> Open a stash asset by ref")
21
+ console.error(" run <type:name> Run a tool by ref")
22
+ process.exit(1)
23
+ }
24
+
25
+ switch (command) {
26
+ case "init": {
27
+ const result = agentikitInit()
28
+ console.log(JSON.stringify(result, null, 2))
29
+ break
30
+ }
31
+ case "index": {
32
+ const result = agentikitIndex()
33
+ console.log(JSON.stringify(result, null, 2))
34
+ break
35
+ }
36
+ case "search": {
37
+ const query = args.find((a, i) => i > 0 && !a.startsWith("--") && args[i - 1] !== "--type" && args[i - 1] !== "--limit") ?? ""
38
+ const type = flag("--type") as "tool" | "skill" | "command" | "agent" | "any" | undefined
39
+ const limitStr = flag("--limit")
40
+ const limit = limitStr ? parseInt(limitStr, 10) : undefined
41
+ console.log(JSON.stringify(agentikitSearch({ query, type, limit }), null, 2))
42
+ break
43
+ }
44
+ case "open": {
45
+ const ref = args[1]
46
+ if (!ref) { console.error("Error: missing ref argument\n"); usage() }
47
+ console.log(JSON.stringify(agentikitOpen({ ref }), null, 2))
48
+ break
49
+ }
50
+ case "run": {
51
+ const ref = args[1]
52
+ if (!ref) { console.error("Error: missing ref argument\n"); usage() }
53
+ const result = agentikitRun({ ref })
54
+ console.log(JSON.stringify(result, null, 2))
55
+ process.exit(result.exitCode)
56
+ break
57
+ }
58
+ default:
59
+ usage()
60
+ }
package/src/indexer.ts ADDED
@@ -0,0 +1,211 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+ import type { AgentikitAssetType } from "./stash"
4
+ import {
5
+ type StashFile,
6
+ type StashEntry,
7
+ loadStashFile,
8
+ writeStashFile,
9
+ generateMetadata,
10
+ } from "./metadata"
11
+ import { TfIdfAdapter, type ScoredEntry } from "./similarity"
12
+
13
+ // ── Types ───────────────────────────────────────────────────────────────────
14
+
15
+ export interface IndexedEntry {
16
+ entry: StashEntry
17
+ path: string
18
+ dirPath: string
19
+ }
20
+
21
+ export interface SearchIndex {
22
+ version: number
23
+ builtAt: string
24
+ stashDir: string
25
+ entries: IndexedEntry[]
26
+ /** Serialized TF-IDF state (term frequencies, idf values) */
27
+ tfidf?: unknown
28
+ }
29
+
30
+ export interface IndexResponse {
31
+ stashDir: string
32
+ totalEntries: number
33
+ generatedMetadata: number
34
+ indexPath: string
35
+ }
36
+
37
+ // ── Constants ───────────────────────────────────────────────────────────────
38
+
39
+ const INDEX_VERSION = 1
40
+ const SCRIPT_EXTENSIONS = new Set([".sh", ".ts", ".js", ".ps1", ".cmd", ".bat"])
41
+
42
+ const TYPE_DIRS: Record<AgentikitAssetType, string> = {
43
+ tool: "tools",
44
+ skill: "skills",
45
+ command: "commands",
46
+ agent: "agents",
47
+ }
48
+
49
+ // ── Index Path ──────────────────────────────────────────────────────────────
50
+
51
+ export function getIndexPath(): string {
52
+ const cacheDir = process.env.XDG_CACHE_HOME
53
+ || path.join(process.env.HOME || process.env.USERPROFILE || "", ".cache")
54
+ return path.join(cacheDir, "agentikit", "index.json")
55
+ }
56
+
57
+ export function loadSearchIndex(): SearchIndex | null {
58
+ const indexPath = getIndexPath()
59
+ if (!fs.existsSync(indexPath)) return null
60
+ try {
61
+ const raw = JSON.parse(fs.readFileSync(indexPath, "utf8"))
62
+ if (raw?.version !== INDEX_VERSION) return null
63
+ return raw as SearchIndex
64
+ } catch {
65
+ return null
66
+ }
67
+ }
68
+
69
+ // ── Indexer ──────────────────────────────────────────────────────────────────
70
+
71
+ export function agentikitIndex(options?: { stashDir?: string }): IndexResponse {
72
+ const stashDir = options?.stashDir || resolveStashDirForIndex()
73
+ const allEntries: IndexedEntry[] = []
74
+ let generatedCount = 0
75
+
76
+ for (const assetType of Object.keys(TYPE_DIRS) as AgentikitAssetType[]) {
77
+ const typeRoot = path.join(stashDir, TYPE_DIRS[assetType])
78
+ if (!fs.existsSync(typeRoot) || !fs.statSync(typeRoot).isDirectory()) continue
79
+
80
+ // Group files by their immediate parent directory
81
+ const dirGroups = collectDirectoryGroups(typeRoot, assetType)
82
+
83
+ for (const [dirPath, files] of dirGroups) {
84
+ // Try loading existing .stash.json
85
+ let stash = loadStashFile(dirPath)
86
+
87
+ if (!stash) {
88
+ // Generate metadata
89
+ stash = generateMetadata(dirPath, assetType, files)
90
+ if (stash.entries.length > 0) {
91
+ writeStashFile(dirPath, stash)
92
+ generatedCount += stash.entries.length
93
+ }
94
+ }
95
+
96
+ if (stash) {
97
+ for (const entry of stash.entries) {
98
+ const entryPath = entry.entry
99
+ ? path.join(dirPath, entry.entry)
100
+ : files[0] || dirPath
101
+ allEntries.push({ entry, path: entryPath, dirPath })
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ // Build TF-IDF index
108
+ const adapter = new TfIdfAdapter()
109
+ const scoredEntries: ScoredEntry[] = allEntries.map((ie) => ({
110
+ id: `${ie.entry.type}:${ie.entry.name}`,
111
+ text: buildSearchText(ie.entry),
112
+ entry: ie.entry,
113
+ path: ie.path,
114
+ }))
115
+ adapter.buildIndex(scoredEntries)
116
+
117
+ // Persist index
118
+ const indexPath = getIndexPath()
119
+ const indexDir = path.dirname(indexPath)
120
+ if (!fs.existsSync(indexDir)) {
121
+ fs.mkdirSync(indexDir, { recursive: true })
122
+ }
123
+
124
+ const index: SearchIndex = {
125
+ version: INDEX_VERSION,
126
+ builtAt: new Date().toISOString(),
127
+ stashDir,
128
+ entries: allEntries,
129
+ tfidf: adapter.serialize(),
130
+ }
131
+ fs.writeFileSync(indexPath, JSON.stringify(index) + "\n", "utf8")
132
+
133
+ return {
134
+ stashDir,
135
+ totalEntries: allEntries.length,
136
+ generatedMetadata: generatedCount,
137
+ indexPath,
138
+ }
139
+ }
140
+
141
+ // ── Helpers ─────────────────────────────────────────────────────────────────
142
+
143
+ function collectDirectoryGroups(
144
+ typeRoot: string,
145
+ assetType: AgentikitAssetType,
146
+ ): Map<string, string[]> {
147
+ const groups = new Map<string, string[]>()
148
+
149
+ const walk = (dir: string): void => {
150
+ if (!fs.existsSync(dir)) return
151
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
152
+ for (const entry of entries) {
153
+ if (entry.name === ".stash.json") continue
154
+ const fullPath = path.join(dir, entry.name)
155
+ if (entry.isDirectory()) {
156
+ walk(fullPath)
157
+ } else if (entry.isFile() && isRelevantFile(entry.name, assetType)) {
158
+ const parentDir = path.dirname(fullPath)
159
+ const existing = groups.get(parentDir)
160
+ if (existing) {
161
+ existing.push(fullPath)
162
+ } else {
163
+ groups.set(parentDir, [fullPath])
164
+ }
165
+ }
166
+ }
167
+ }
168
+
169
+ walk(typeRoot)
170
+ return groups
171
+ }
172
+
173
+ function isRelevantFile(fileName: string, assetType: AgentikitAssetType): boolean {
174
+ const ext = path.extname(fileName).toLowerCase()
175
+ switch (assetType) {
176
+ case "tool":
177
+ return SCRIPT_EXTENSIONS.has(ext)
178
+ case "skill":
179
+ return fileName === "SKILL.md"
180
+ case "command":
181
+ case "agent":
182
+ return ext === ".md"
183
+ default:
184
+ return false
185
+ }
186
+ }
187
+
188
+ export function buildSearchText(entry: StashEntry): string {
189
+ const parts: string[] = [entry.name.replace(/[-_]/g, " ")]
190
+ if (entry.description) parts.push(entry.description)
191
+ if (entry.tags) parts.push(entry.tags.join(" "))
192
+ if (entry.examples) parts.push(entry.examples.join(" "))
193
+ if (entry.intent) {
194
+ if (entry.intent.when) parts.push(entry.intent.when)
195
+ if (entry.intent.input) parts.push(entry.intent.input)
196
+ if (entry.intent.output) parts.push(entry.intent.output)
197
+ }
198
+ return parts.join(" ").toLowerCase()
199
+ }
200
+
201
+ function resolveStashDirForIndex(): string {
202
+ const raw = process.env.AGENTIKIT_STASH_DIR?.trim()
203
+ if (!raw) {
204
+ throw new Error("AGENTIKIT_STASH_DIR is not set. Run 'agentikit init' first.")
205
+ }
206
+ const stashDir = path.resolve(raw)
207
+ if (!fs.existsSync(stashDir) || !fs.statSync(stashDir).isDirectory()) {
208
+ throw new Error(`AGENTIKIT_STASH_DIR does not exist or is not a directory: "${stashDir}"`)
209
+ }
210
+ return stashDir
211
+ }
@@ -0,0 +1,249 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+ import type { AgentikitAssetType } from "./stash"
4
+
5
+ // ── Schema ──────────────────────────────────────────────────────────────────
6
+
7
+ export interface StashIntent {
8
+ when?: string
9
+ input?: string
10
+ output?: string
11
+ }
12
+
13
+ export interface StashEntry {
14
+ name: string
15
+ type: AgentikitAssetType
16
+ description?: string
17
+ tags?: string[]
18
+ examples?: string[]
19
+ intent?: StashIntent
20
+ entry?: string
21
+ generated?: boolean
22
+ }
23
+
24
+ export interface StashFile {
25
+ entries: StashEntry[]
26
+ }
27
+
28
+ // ── Load / Write ────────────────────────────────────────────────────────────
29
+
30
+ const STASH_FILENAME = ".stash.json"
31
+
32
+ export function stashFilePath(dirPath: string): string {
33
+ return path.join(dirPath, STASH_FILENAME)
34
+ }
35
+
36
+ export function loadStashFile(dirPath: string): StashFile | null {
37
+ const filePath = stashFilePath(dirPath)
38
+ if (!fs.existsSync(filePath)) return null
39
+ try {
40
+ const raw = JSON.parse(fs.readFileSync(filePath, "utf8"))
41
+ if (!raw || !Array.isArray(raw.entries)) return null
42
+ const entries: StashEntry[] = []
43
+ for (const e of raw.entries) {
44
+ const validated = validateStashEntry(e)
45
+ if (validated) entries.push(validated)
46
+ }
47
+ return entries.length > 0 ? { entries } : null
48
+ } catch {
49
+ return null
50
+ }
51
+ }
52
+
53
+ export function writeStashFile(dirPath: string, stash: StashFile): void {
54
+ const filePath = stashFilePath(dirPath)
55
+ fs.writeFileSync(filePath, JSON.stringify(stash, null, 2) + "\n", "utf8")
56
+ }
57
+
58
+ export function validateStashEntry(entry: unknown): StashEntry | null {
59
+ if (typeof entry !== "object" || entry === null) return null
60
+ const e = entry as Record<string, unknown>
61
+ if (typeof e.name !== "string" || !e.name) return null
62
+ if (typeof e.type !== "string" || !isValidType(e.type)) return null
63
+
64
+ const result: StashEntry = {
65
+ name: e.name,
66
+ type: e.type as AgentikitAssetType,
67
+ }
68
+ if (typeof e.description === "string" && e.description) result.description = e.description
69
+ if (Array.isArray(e.tags)) result.tags = e.tags.filter((t): t is string => typeof t === "string")
70
+ if (Array.isArray(e.examples)) result.examples = e.examples.filter((x): x is string => typeof x === "string")
71
+ if (typeof e.intent === "object" && e.intent !== null) {
72
+ const intent = e.intent as Record<string, unknown>
73
+ result.intent = {}
74
+ if (typeof intent.when === "string") result.intent.when = intent.when
75
+ if (typeof intent.input === "string") result.intent.input = intent.input
76
+ if (typeof intent.output === "string") result.intent.output = intent.output
77
+ }
78
+ if (typeof e.entry === "string" && e.entry) result.entry = e.entry
79
+ if (e.generated === true) result.generated = true
80
+
81
+ return result
82
+ }
83
+
84
+ function isValidType(type: string): boolean {
85
+ return type === "tool" || type === "skill" || type === "command" || type === "agent"
86
+ }
87
+
88
+ // ── Metadata Generation ─────────────────────────────────────────────────────
89
+
90
+ const SCRIPT_EXTENSIONS = new Set([".sh", ".ts", ".js", ".ps1", ".cmd", ".bat"])
91
+
92
+ export function generateMetadata(
93
+ dirPath: string,
94
+ assetType: AgentikitAssetType,
95
+ files: string[],
96
+ ): StashFile {
97
+ const entries: StashEntry[] = []
98
+
99
+ for (const file of files) {
100
+ const ext = path.extname(file).toLowerCase()
101
+ const baseName = path.basename(file, ext)
102
+
103
+ // Skip non-relevant files
104
+ if (assetType === "tool" && !SCRIPT_EXTENSIONS.has(ext)) continue
105
+ if ((assetType === "command" || assetType === "agent") && ext !== ".md") continue
106
+ if (assetType === "skill" && path.basename(file) !== "SKILL.md") continue
107
+
108
+ const entry: StashEntry = {
109
+ name: baseName,
110
+ type: assetType,
111
+ generated: true,
112
+ }
113
+
114
+ // Priority 3: package.json metadata
115
+ const pkgMeta = extractPackageMetadata(dirPath)
116
+ if (pkgMeta) {
117
+ if (pkgMeta.description && !entry.description) entry.description = pkgMeta.description
118
+ if (pkgMeta.keywords && pkgMeta.keywords.length > 0) entry.tags = pkgMeta.keywords
119
+ }
120
+
121
+ // Priority 2: Frontmatter (for .md files)
122
+ if (ext === ".md") {
123
+ const fm = extractFrontmatterDescription(file)
124
+ if (fm) entry.description = fm
125
+ }
126
+
127
+ // Priority 4: Code comments (for script files)
128
+ if (SCRIPT_EXTENSIONS.has(ext) && ext !== ".md") {
129
+ const commentDesc = extractDescriptionFromComments(file)
130
+ if (commentDesc && !entry.description) entry.description = commentDesc
131
+ }
132
+
133
+ // Priority 5: Filename heuristics (fallback)
134
+ if (!entry.description) {
135
+ entry.description = fileNameToDescription(baseName)
136
+ }
137
+ if (!entry.tags || entry.tags.length === 0) {
138
+ entry.tags = extractTagsFromPath(file, dirPath)
139
+ }
140
+
141
+ entry.entry = path.basename(file)
142
+ entries.push(entry)
143
+ }
144
+
145
+ return { entries }
146
+ }
147
+
148
+ export function extractDescriptionFromComments(filePath: string): string | null {
149
+ let content: string
150
+ try {
151
+ content = fs.readFileSync(filePath, "utf8")
152
+ } catch {
153
+ return null
154
+ }
155
+
156
+ const lines = content.split(/\r?\n/).slice(0, 50)
157
+
158
+ // Try JSDoc-style block comment: /** ... */
159
+ const blockStart = lines.findIndex((l) => /^\s*\/\*\*/.test(l))
160
+ if (blockStart >= 0) {
161
+ const desc: string[] = []
162
+ for (let i = blockStart; i < lines.length; i++) {
163
+ const line = lines[i]
164
+ if (i > blockStart && /\*\//.test(line)) break
165
+ const cleaned = line.replace(/^\s*\/?\*\*?\s?/, "").replace(/\*\/\s*$/, "").trim()
166
+ if (cleaned) desc.push(cleaned)
167
+ }
168
+ if (desc.length > 0) return desc.join(" ")
169
+ }
170
+
171
+ // Try hash comments at start of file (skip shebang)
172
+ let start = 0
173
+ if (lines[0]?.startsWith("#!")) start = 1
174
+ const hashLines: string[] = []
175
+ for (let i = start; i < lines.length; i++) {
176
+ const line = lines[i].trim()
177
+ if (line.startsWith("#") && !line.startsWith("#!")) {
178
+ hashLines.push(line.replace(/^#+\s*/, "").trim())
179
+ } else if (line === "") {
180
+ continue
181
+ } else {
182
+ break
183
+ }
184
+ }
185
+ if (hashLines.length > 0) return hashLines.join(" ")
186
+
187
+ return null
188
+ }
189
+
190
+ export function extractFrontmatterDescription(filePath: string): string | null {
191
+ let content: string
192
+ try {
193
+ content = fs.readFileSync(filePath, "utf8")
194
+ } catch {
195
+ return null
196
+ }
197
+
198
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)
199
+ if (!match) return null
200
+
201
+ for (const line of match[1].split(/\r?\n/)) {
202
+ const m = line.match(/^description:\s*"?(.+?)"?\s*$/)
203
+ if (m) return m[1]
204
+ }
205
+ return null
206
+ }
207
+
208
+ export function extractPackageMetadata(
209
+ dirPath: string,
210
+ ): { name?: string; description?: string; keywords?: string[] } | null {
211
+ const pkgPath = path.join(dirPath, "package.json")
212
+ if (!fs.existsSync(pkgPath)) return null
213
+ try {
214
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"))
215
+ const result: { name?: string; description?: string; keywords?: string[] } = {}
216
+ if (typeof pkg.name === "string") result.name = pkg.name
217
+ if (typeof pkg.description === "string") result.description = pkg.description
218
+ if (Array.isArray(pkg.keywords)) {
219
+ result.keywords = pkg.keywords.filter((k: unknown): k is string => typeof k === "string")
220
+ }
221
+ return Object.keys(result).length > 0 ? result : null
222
+ } catch {
223
+ return null
224
+ }
225
+ }
226
+
227
+ export function fileNameToDescription(fileName: string): string {
228
+ return fileName
229
+ .replace(/[-_]+/g, " ")
230
+ .replace(/([a-z])([A-Z])/g, "$1 $2")
231
+ .toLowerCase()
232
+ .trim()
233
+ }
234
+
235
+ export function extractTagsFromPath(filePath: string, rootDir: string): string[] {
236
+ const rel = path.relative(rootDir, filePath)
237
+ const parts = rel.split(path.sep)
238
+ const tags = new Set<string>()
239
+
240
+ for (const part of parts) {
241
+ const name = part.replace(path.extname(part), "")
242
+ for (const token of name.split(/[-_./\\]+/)) {
243
+ const clean = token.toLowerCase().trim()
244
+ if (clean && clean.length > 1) tags.add(clean)
245
+ }
246
+ }
247
+
248
+ return Array.from(tags)
249
+ }
package/src/plugin.ts ADDED
@@ -0,0 +1,56 @@
1
+ import { type Plugin, tool } from "@opencode-ai/plugin"
2
+ import { agentikitOpen, agentikitRun, agentikitSearch } from "./stash"
3
+ import { agentikitIndex } from "./indexer"
4
+
5
+ function tryJson(fn: () => unknown, action: string): string {
6
+ try {
7
+ return JSON.stringify(fn())
8
+ } catch (error: unknown) {
9
+ const message = error instanceof Error ? error.message : String(error)
10
+ return JSON.stringify({ ok: false, error: `Failed to ${action}: ${message}` })
11
+ }
12
+ }
13
+
14
+ export const plugin: Plugin = async () => ({
15
+ tool: {
16
+ agentikit_search: tool({
17
+ description: "Search the Agentikit stash for tools, skills, commands, and agents.",
18
+ args: {
19
+ query: tool.schema.string().describe("Case-insensitive substring search."),
20
+ type: tool.schema
21
+ .enum(["tool", "skill", "command", "agent", "any"])
22
+ .optional()
23
+ .describe("Optional type filter. Defaults to 'any'."),
24
+ limit: tool.schema.number().optional().describe("Maximum number of hits to return. Defaults to 20."),
25
+ },
26
+ async execute({ query, type, limit }) {
27
+ return tryJson(() => agentikitSearch({ query, type, limit }), "search Agentikit stash")
28
+ },
29
+ }),
30
+ agentikit_open: tool({
31
+ description: "Open a stash asset from an openRef returned by agentikit_search.",
32
+ args: {
33
+ ref: tool.schema.string().describe("Open reference returned by agentikit_search."),
34
+ },
35
+ async execute({ ref }) {
36
+ return tryJson(() => agentikitOpen({ ref }), "open stash asset")
37
+ },
38
+ }),
39
+ agentikit_run: tool({
40
+ description: "Run a tool from the Agentikit stash by its openRef. Only tool refs are supported.",
41
+ args: {
42
+ ref: tool.schema.string().describe("Open reference of a tool returned by agentikit_search."),
43
+ },
44
+ async execute({ ref }) {
45
+ return tryJson(() => agentikitRun({ ref }), "run stash tool")
46
+ },
47
+ }),
48
+ agentikit_index: tool({
49
+ description: "Build or rebuild the Agentikit search index. Scans stash directories, generates missing .stash.json metadata, and builds a semantic search index.",
50
+ args: {},
51
+ async execute() {
52
+ return tryJson(() => agentikitIndex(), "build Agentikit index")
53
+ },
54
+ }),
55
+ },
56
+ })