agentikit 0.0.13 → 0.0.14

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 (145) hide show
  1. package/LICENSE +385 -0
  2. package/README.md +180 -110
  3. package/dist/cli.js +671 -0
  4. package/dist/common.js +192 -0
  5. package/dist/{src/config-cli.js → config-cli.js} +14 -6
  6. package/dist/{src/config.js → config.js} +92 -24
  7. package/dist/{src/db.js → db.js} +109 -35
  8. package/dist/{src/embedder.js → embedder.js} +57 -2
  9. package/dist/file-context.js +158 -0
  10. package/dist/{src/handlers → handlers}/command-handler.js +2 -0
  11. package/dist/{src/handlers → handlers}/index.js +0 -6
  12. package/dist/{src/indexer.js → indexer.js} +34 -10
  13. package/dist/init.js +43 -0
  14. package/dist/lockfile.js +55 -0
  15. package/dist/matchers.js +157 -0
  16. package/dist/{src/metadata.js → metadata.js} +12 -1
  17. package/dist/{src/origin-resolve.js → origin-resolve.js} +10 -9
  18. package/dist/paths.js +82 -0
  19. package/dist/{src/registry-install.js → registry-install.js} +145 -17
  20. package/dist/{src/registry-resolve.js → registry-resolve.js} +178 -18
  21. package/dist/{src/registry-search.js → registry-search.js} +8 -16
  22. package/dist/renderers.js +276 -0
  23. package/dist/{src/ripgrep-install.js → ripgrep-install.js} +5 -5
  24. package/dist/{src/ripgrep-resolve.js → ripgrep-resolve.js} +21 -11
  25. package/dist/self-update.js +220 -0
  26. package/dist/{src/stash-add.js → stash-add.js} +11 -2
  27. package/dist/stash-clone.js +115 -0
  28. package/dist/{src/stash-registry.js → stash-registry.js} +15 -41
  29. package/dist/{src/stash-search.js → stash-search.js} +67 -55
  30. package/dist/{src/stash-show.js → stash-show.js} +30 -3
  31. package/dist/{src/stash-source.js → stash-source.js} +56 -9
  32. package/dist/submit.js +552 -0
  33. package/dist/{src/walker.js → walker.js} +38 -0
  34. package/package.json +7 -16
  35. package/dist/index.d.ts +0 -28
  36. package/dist/index.js +0 -15
  37. package/dist/src/asset-spec.d.ts +0 -16
  38. package/dist/src/asset-type-handler.d.ts +0 -27
  39. package/dist/src/cli.d.ts +0 -2
  40. package/dist/src/cli.js +0 -399
  41. package/dist/src/common.d.ts +0 -13
  42. package/dist/src/common.js +0 -60
  43. package/dist/src/config-cli.d.ts +0 -9
  44. package/dist/src/config.d.ts +0 -50
  45. package/dist/src/db.d.ts +0 -46
  46. package/dist/src/embedder.d.ts +0 -10
  47. package/dist/src/frontmatter.d.ts +0 -30
  48. package/dist/src/github.d.ts +0 -4
  49. package/dist/src/handlers/agent-handler.d.ts +0 -2
  50. package/dist/src/handlers/command-handler.d.ts +0 -2
  51. package/dist/src/handlers/index.d.ts +0 -6
  52. package/dist/src/handlers/knowledge-handler.d.ts +0 -2
  53. package/dist/src/handlers/markdown-helpers.d.ts +0 -7
  54. package/dist/src/handlers/script-handler.d.ts +0 -2
  55. package/dist/src/handlers/skill-handler.d.ts +0 -2
  56. package/dist/src/handlers/tool-handler.d.ts +0 -2
  57. package/dist/src/indexer.d.ts +0 -22
  58. package/dist/src/init.d.ts +0 -19
  59. package/dist/src/init.js +0 -99
  60. package/dist/src/llm.d.ts +0 -15
  61. package/dist/src/markdown.d.ts +0 -18
  62. package/dist/src/metadata.d.ts +0 -41
  63. package/dist/src/origin-resolve.d.ts +0 -19
  64. package/dist/src/registry-install.d.ts +0 -11
  65. package/dist/src/registry-resolve.d.ts +0 -3
  66. package/dist/src/registry-search.d.ts +0 -27
  67. package/dist/src/registry-types.d.ts +0 -62
  68. package/dist/src/ripgrep-install.d.ts +0 -12
  69. package/dist/src/ripgrep-resolve.d.ts +0 -13
  70. package/dist/src/ripgrep.d.ts +0 -3
  71. package/dist/src/stash-add.d.ts +0 -4
  72. package/dist/src/stash-clone.d.ts +0 -22
  73. package/dist/src/stash-clone.js +0 -83
  74. package/dist/src/stash-ref.d.ts +0 -31
  75. package/dist/src/stash-registry.d.ts +0 -18
  76. package/dist/src/stash-resolve.d.ts +0 -2
  77. package/dist/src/stash-search.d.ts +0 -8
  78. package/dist/src/stash-show.d.ts +0 -5
  79. package/dist/src/stash-source.d.ts +0 -24
  80. package/dist/src/stash-types.d.ts +0 -227
  81. package/dist/src/stash.d.ts +0 -16
  82. package/dist/src/stash.js +0 -9
  83. package/dist/src/tool-runner.d.ts +0 -35
  84. package/dist/src/walker.d.ts +0 -19
  85. package/src/asset-spec.ts +0 -85
  86. package/src/asset-type-handler.ts +0 -77
  87. package/src/cli.ts +0 -427
  88. package/src/common.ts +0 -76
  89. package/src/config-cli.ts +0 -499
  90. package/src/config.ts +0 -305
  91. package/src/db.ts +0 -411
  92. package/src/embedder.ts +0 -128
  93. package/src/frontmatter.ts +0 -95
  94. package/src/github.ts +0 -21
  95. package/src/handlers/agent-handler.ts +0 -32
  96. package/src/handlers/command-handler.ts +0 -29
  97. package/src/handlers/index.ts +0 -25
  98. package/src/handlers/knowledge-handler.ts +0 -62
  99. package/src/handlers/markdown-helpers.ts +0 -19
  100. package/src/handlers/script-handler.ts +0 -92
  101. package/src/handlers/skill-handler.ts +0 -37
  102. package/src/handlers/tool-handler.ts +0 -71
  103. package/src/indexer.ts +0 -392
  104. package/src/init.ts +0 -114
  105. package/src/llm.ts +0 -125
  106. package/src/markdown.ts +0 -106
  107. package/src/metadata.ts +0 -333
  108. package/src/origin-resolve.ts +0 -67
  109. package/src/registry-install.ts +0 -361
  110. package/src/registry-resolve.ts +0 -341
  111. package/src/registry-search.ts +0 -335
  112. package/src/registry-types.ts +0 -72
  113. package/src/ripgrep-install.ts +0 -200
  114. package/src/ripgrep-resolve.ts +0 -72
  115. package/src/ripgrep.ts +0 -3
  116. package/src/stash-add.ts +0 -63
  117. package/src/stash-clone.ts +0 -127
  118. package/src/stash-ref.ts +0 -99
  119. package/src/stash-registry.ts +0 -259
  120. package/src/stash-resolve.ts +0 -50
  121. package/src/stash-search.ts +0 -613
  122. package/src/stash-show.ts +0 -55
  123. package/src/stash-source.ts +0 -103
  124. package/src/stash-types.ts +0 -231
  125. package/src/stash.ts +0 -39
  126. package/src/tool-runner.ts +0 -142
  127. package/src/walker.ts +0 -53
  128. /package/dist/{src/asset-spec.js → asset-spec.js} +0 -0
  129. /package/dist/{src/asset-type-handler.js → asset-type-handler.js} +0 -0
  130. /package/dist/{src/frontmatter.js → frontmatter.js} +0 -0
  131. /package/dist/{src/github.js → github.js} +0 -0
  132. /package/dist/{src/handlers → handlers}/agent-handler.js +0 -0
  133. /package/dist/{src/handlers → handlers}/knowledge-handler.js +0 -0
  134. /package/dist/{src/handlers → handlers}/markdown-helpers.js +0 -0
  135. /package/dist/{src/handlers → handlers}/script-handler.js +0 -0
  136. /package/dist/{src/handlers → handlers}/skill-handler.js +0 -0
  137. /package/dist/{src/handlers → handlers}/tool-handler.js +0 -0
  138. /package/dist/{src/llm.js → llm.js} +0 -0
  139. /package/dist/{src/markdown.js → markdown.js} +0 -0
  140. /package/dist/{src/registry-types.js → registry-types.js} +0 -0
  141. /package/dist/{src/ripgrep.js → ripgrep.js} +0 -0
  142. /package/dist/{src/stash-ref.js → stash-ref.js} +0 -0
  143. /package/dist/{src/stash-resolve.js → stash-resolve.js} +0 -0
  144. /package/dist/{src/stash-types.js → stash-types.js} +0 -0
  145. /package/dist/{src/tool-runner.js → tool-runner.js} +0 -0
package/src/markdown.ts DELETED
@@ -1,106 +0,0 @@
1
- import { parseFrontmatter } from "./frontmatter"
2
-
3
- // ── Types ───────────────────────────────────────────────────────────────────
4
-
5
- export interface TocHeading {
6
- level: number
7
- text: string
8
- line: number
9
- }
10
-
11
- export interface KnowledgeToc {
12
- headings: TocHeading[]
13
- totalLines: number
14
- }
15
-
16
- // ── Parsing ─────────────────────────────────────────────────────────────────
17
-
18
- export function parseMarkdownToc(content: string): KnowledgeToc {
19
- const lines = content.split(/\r?\n/)
20
- const headings: TocHeading[] = []
21
-
22
- const parsed = parseFrontmatter(content)
23
- const start = parsed.frontmatter ? parsed.bodyStartLine - 1 : 0
24
-
25
- for (let i = start; i < lines.length; i++) {
26
- const match = lines[i].match(/^(#{1,6})\s+(.+)$/)
27
- if (match) {
28
- headings.push({
29
- level: match[1].length,
30
- text: match[2].replace(/\s+#+\s*$/, "").trim(),
31
- line: i + 1,
32
- })
33
- }
34
- }
35
-
36
- return { headings, totalLines: lines.length }
37
- }
38
-
39
- // ── Extraction ──────────────────────────────────────────────────────────────
40
-
41
- export function extractSection(
42
- content: string,
43
- heading: string,
44
- ): { content: string; startLine: number; endLine: number } | null {
45
- const lines = content.split(/\r?\n/)
46
- const target = heading.toLowerCase()
47
-
48
- let startIdx = -1
49
- let startLevel = 0
50
-
51
- for (let i = 0; i < lines.length; i++) {
52
- const match = lines[i].match(/^(#{1,6})\s+(.+)$/)
53
- if (!match) continue
54
- const text = match[2].replace(/\s+#+\s*$/, "").trim()
55
- if (text.toLowerCase() === target && startIdx === -1) {
56
- startIdx = i
57
- startLevel = match[1].length
58
- } else if (startIdx !== -1 && match[1].length <= startLevel) {
59
- return {
60
- content: lines.slice(startIdx, i).join("\n"),
61
- startLine: startIdx + 1,
62
- endLine: i,
63
- }
64
- }
65
- }
66
-
67
- if (startIdx === -1) return null
68
-
69
- return {
70
- content: lines.slice(startIdx).join("\n"),
71
- startLine: startIdx + 1,
72
- endLine: lines.length,
73
- }
74
- }
75
-
76
- export function extractLineRange(content: string, start: number, end: number): string {
77
- const lines = content.split(/\r?\n/)
78
- if (end < start) return ""
79
- const s = Math.max(1, Math.min(start, lines.length))
80
- const e = Math.min(end, lines.length)
81
- return lines.slice(s - 1, e).join("\n")
82
- }
83
-
84
- export function extractFrontmatterOnly(content: string): string | null {
85
- const parsed = parseFrontmatter(content)
86
- return parsed.frontmatter
87
- }
88
-
89
- // ── Formatting ──────────────────────────────────────────────────────────────
90
-
91
- export function formatToc(toc: KnowledgeToc): string {
92
- if (toc.headings.length === 0) {
93
- return `(no headings found — ${toc.totalLines} lines total)`
94
- }
95
-
96
- const lineWidth = String(toc.totalLines).length
97
- const parts = toc.headings.map((h) => {
98
- const lineNum = `L${String(h.line).padStart(lineWidth)}`
99
- const indent = " ".repeat(h.level - 1)
100
- const prefix = "#".repeat(h.level)
101
- return `${lineNum} ${indent}${prefix} ${h.text}`
102
- })
103
-
104
- parts.push(`\n${toc.totalLines} lines total`)
105
- return parts.join("\n")
106
- }
package/src/metadata.ts DELETED
@@ -1,333 +0,0 @@
1
- import fs from "node:fs"
2
- import path from "node:path"
3
- import { type AgentikitAssetType, isAssetType } from "./common"
4
- import { SCRIPT_EXTENSIONS, isRelevantAssetFile, deriveCanonicalAssetName } from "./asset-spec"
5
- import { parseFrontmatter, toStringOrUndefined } from "./frontmatter"
6
- import { parseMarkdownToc, type TocHeading } from "./markdown"
7
- import { tryGetHandler } from "./asset-type-handler"
8
-
9
- // ── Schema ──────────────────────────────────────────────────────────────────
10
-
11
- export interface StashIntent {
12
- when?: string
13
- input?: string
14
- output?: string
15
- }
16
-
17
- export interface StashEntry {
18
- name: string
19
- type: AgentikitAssetType
20
- description?: string
21
- tags?: string[]
22
- examples?: string[]
23
- intents?: string[]
24
- intent?: StashIntent
25
- entry?: string
26
- generated?: boolean
27
- quality?: "generated" | "curated"
28
- confidence?: number
29
- source?: "package" | "frontmatter" | "comments" | "filename" | "manual" | "llm"
30
- aliases?: string[]
31
- toc?: TocHeading[]
32
- usage?: string[]
33
- }
34
-
35
- export interface StashFile {
36
- entries: StashEntry[]
37
- }
38
-
39
- // ── Load / Write ────────────────────────────────────────────────────────────
40
-
41
- const STASH_FILENAME = ".stash.json"
42
-
43
- export function stashFilePath(dirPath: string): string {
44
- return path.join(dirPath, STASH_FILENAME)
45
- }
46
-
47
- export function loadStashFile(dirPath: string): StashFile | null {
48
- const filePath = stashFilePath(dirPath)
49
- if (!fs.existsSync(filePath)) return null
50
- try {
51
- const raw = JSON.parse(fs.readFileSync(filePath, "utf8"))
52
- if (!raw || !Array.isArray(raw.entries)) return null
53
- const entries: StashEntry[] = []
54
- for (const e of raw.entries) {
55
- const validated = validateStashEntry(e)
56
- if (validated) entries.push(validated)
57
- }
58
- return entries.length > 0 ? { entries } : null
59
- } catch {
60
- return null
61
- }
62
- }
63
-
64
- export function writeStashFile(dirPath: string, stash: StashFile): void {
65
- const filePath = stashFilePath(dirPath)
66
- fs.writeFileSync(filePath, JSON.stringify(stash, null, 2) + "\n", "utf8")
67
- }
68
-
69
- export function validateStashEntry(entry: unknown): StashEntry | null {
70
- if (typeof entry !== "object" || entry === null) return null
71
- const e = entry as Record<string, unknown>
72
- if (typeof e.name !== "string" || !e.name) return null
73
- if (typeof e.type !== "string" || !isAssetType(e.type)) return null
74
-
75
- const result: StashEntry = {
76
- name: e.name,
77
- type: e.type as AgentikitAssetType,
78
- }
79
- if (typeof e.description === "string" && e.description) result.description = e.description
80
- if (Array.isArray(e.tags)) result.tags = e.tags.filter((t): t is string => typeof t === "string")
81
- if (Array.isArray(e.examples)) result.examples = e.examples.filter((x): x is string => typeof x === "string")
82
- if (Array.isArray(e.intents)) {
83
- const filtered = e.intents.filter((s): s is string => typeof s === "string" && s.trim().length > 0)
84
- if (filtered.length > 0) result.intents = filtered
85
- }
86
- if (typeof e.intent === "object" && e.intent !== null) {
87
- const intent = e.intent as Record<string, unknown>
88
- result.intent = {}
89
- if (typeof intent.when === "string") result.intent.when = intent.when
90
- if (typeof intent.input === "string") result.intent.input = intent.input
91
- if (typeof intent.output === "string") result.intent.output = intent.output
92
- }
93
- if (typeof e.entry === "string" && e.entry) result.entry = e.entry
94
- if (e.generated === true) result.generated = true
95
- if (e.quality === "generated" || e.quality === "curated") result.quality = e.quality
96
- if (typeof e.confidence === "number" && Number.isFinite(e.confidence)) result.confidence = Math.max(0, Math.min(1, e.confidence))
97
- if (typeof e.source === "string" && ["package", "frontmatter", "comments", "filename", "manual", "llm"].includes(e.source)) {
98
- result.source = e.source as StashEntry["source"]
99
- }
100
- if (Array.isArray(e.aliases)) {
101
- const filtered = e.aliases.filter((a): a is string => typeof a === "string" && a.trim().length > 0)
102
- if (filtered.length > 0) result.aliases = normalizeTerms(filtered)
103
- }
104
- if (Array.isArray(e.toc)) {
105
- const validated = e.toc.filter(
106
- (h: unknown): h is TocHeading => {
107
- if (typeof h !== "object" || h === null) return false
108
- const rec = h as Record<string, unknown>
109
- return typeof rec.level === "number"
110
- && typeof rec.text === "string"
111
- && typeof rec.line === "number"
112
- },
113
- )
114
- if (validated.length > 0) result.toc = validated
115
- }
116
- const usage = normalizeNonEmptyStringList(e.usage)
117
- if (usage) result.usage = usage
118
-
119
- return result
120
- }
121
-
122
- function normalizeNonEmptyStringList(value: unknown): string[] | undefined {
123
- if (typeof value === "string") {
124
- const trimmed = value.trim()
125
- return trimmed ? [trimmed] : undefined
126
- }
127
- if (!Array.isArray(value)) return undefined
128
- const filtered = value
129
- .filter((item): item is string => typeof item === "string")
130
- .map((item) => item.trim())
131
- .filter((item) => item.length > 0)
132
- return filtered.length > 0 ? filtered : undefined
133
- }
134
-
135
- // ── Metadata Generation ─────────────────────────────────────────────────────
136
-
137
- export function generateMetadata(
138
- dirPath: string,
139
- assetType: AgentikitAssetType,
140
- files: string[],
141
- typeRoot = dirPath,
142
- ): StashFile {
143
- const entries: StashEntry[] = []
144
- const pkgMeta = extractPackageMetadata(dirPath)
145
-
146
- for (const file of files) {
147
- const ext = path.extname(file).toLowerCase()
148
- const baseName = path.basename(file, ext)
149
- const fileName = path.basename(file)
150
-
151
- // Skip non-relevant files
152
- if (!isRelevantAssetFile(assetType, fileName)) continue
153
-
154
- const canonicalName = assetType === "skill"
155
- ? deriveCanonicalAssetName(assetType, typeRoot, file) ?? baseName
156
- : baseName
157
-
158
- const entry: StashEntry = {
159
- name: canonicalName,
160
- type: assetType,
161
- generated: true,
162
- quality: "generated",
163
- confidence: 0.55,
164
- source: "filename",
165
- }
166
-
167
- // Priority 1: package.json metadata
168
- if (pkgMeta) {
169
- if (pkgMeta.description && !entry.description) {
170
- entry.description = pkgMeta.description
171
- entry.source = "package"
172
- entry.confidence = 0.8
173
- }
174
- if (pkgMeta.keywords && pkgMeta.keywords.length > 0) entry.tags = normalizeTerms(pkgMeta.keywords)
175
- }
176
-
177
- // Priority 2: Frontmatter (for .md files — overrides package.json description)
178
- if (ext === ".md") {
179
- const fm = extractFrontmatterDescription(file)
180
- if (fm) {
181
- entry.description = fm
182
- entry.source = "frontmatter"
183
- entry.confidence = 0.9
184
- }
185
- }
186
-
187
- // Type-specific metadata extraction (e.g. TOC for knowledge, comments for tools/scripts)
188
- const handler = tryGetHandler(assetType)
189
- if (handler?.extractTypeMetadata) {
190
- handler.extractTypeMetadata(entry, file, ext)
191
- }
192
-
193
- // Priority 4: Filename heuristics (fallback)
194
- if (!entry.description) {
195
- entry.description = fileNameToDescription(baseName)
196
- entry.source = "filename"
197
- entry.confidence = Math.min(entry.confidence ?? 0.55, 0.55)
198
- }
199
- if (!entry.tags || entry.tags.length === 0) {
200
- entry.tags = extractTagsFromPath(file, dirPath)
201
- }
202
-
203
- entry.tags = normalizeTerms(entry.tags ?? [])
204
- entry.aliases = buildAliases(canonicalName, entry.tags)
205
-
206
- // Intents are only generated when LLM is configured (via enhanceStashWithLlm)
207
- // Heuristic intents are too noisy to be useful for search quality
208
-
209
- entry.entry = path.basename(file)
210
- entries.push(entry)
211
- }
212
-
213
- return { entries }
214
- }
215
-
216
-
217
- function normalizeTerms(values: string[]): string[] {
218
- const normalized = new Set<string>()
219
- for (const value of values) {
220
- const cleaned = value.toLowerCase().replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim()
221
- if (!cleaned) continue
222
- normalized.add(cleaned)
223
- if (cleaned.endsWith("s") && cleaned.length > 3) {
224
- normalized.add(cleaned.slice(0, -1))
225
- }
226
- }
227
- return Array.from(normalized)
228
- }
229
-
230
- function buildAliases(name: string, tags: string[]): string[] {
231
- const aliases = new Set<string>()
232
- const spaced = name.replace(/[-_]+/g, " ").trim().toLowerCase()
233
- if (spaced && spaced !== name.toLowerCase()) aliases.add(spaced)
234
- if (tags.length > 1) aliases.add(tags.join(" "))
235
- return Array.from(aliases)
236
- }
237
-
238
- export function extractDescriptionFromComments(filePath: string): string | null {
239
- let content: string
240
- try {
241
- content = fs.readFileSync(filePath, "utf8")
242
- } catch {
243
- return null
244
- }
245
-
246
- const lines = content.split(/\r?\n/).slice(0, 50)
247
-
248
- // Try JSDoc-style block comment: /** ... */
249
- const blockStart = lines.findIndex((l) => /^\s*\/\*\*/.test(l))
250
- if (blockStart >= 0) {
251
- const desc: string[] = []
252
- for (let i = blockStart; i < lines.length; i++) {
253
- const line = lines[i]
254
- if (i > blockStart && /\*\//.test(line)) break
255
- const cleaned = line.replace(/^\s*\/?\*\*?\s?/, "").replace(/\*\/\s*$/, "").trim()
256
- if (cleaned) desc.push(cleaned)
257
- }
258
- if (desc.length > 0) return desc.join(" ")
259
- }
260
-
261
- // Try hash comments at start of file (skip shebang)
262
- let start = 0
263
- if (lines[0]?.startsWith("#!")) start = 1
264
- const hashLines: string[] = []
265
- for (let i = start; i < lines.length; i++) {
266
- const line = lines[i].trim()
267
- if (line.startsWith("#") && !line.startsWith("#!")) {
268
- hashLines.push(line.replace(/^#+\s*/, "").trim())
269
- } else if (line === "") {
270
- continue
271
- } else {
272
- break
273
- }
274
- }
275
- if (hashLines.length > 0) return hashLines.join(" ")
276
-
277
- return null
278
- }
279
-
280
- export function extractFrontmatterDescription(filePath: string): string | null {
281
- let content: string
282
- try {
283
- content = fs.readFileSync(filePath, "utf8")
284
- } catch {
285
- return null
286
- }
287
-
288
- const parsed = parseFrontmatter(content)
289
- return toStringOrUndefined(parsed.data.description) ?? null
290
- }
291
-
292
- export function extractPackageMetadata(
293
- dirPath: string,
294
- ): { name?: string; description?: string; keywords?: string[] } | null {
295
- const pkgPath = path.join(dirPath, "package.json")
296
- if (!fs.existsSync(pkgPath)) return null
297
- try {
298
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"))
299
- const result: { name?: string; description?: string; keywords?: string[] } = {}
300
- if (typeof pkg.name === "string") result.name = pkg.name
301
- if (typeof pkg.description === "string") result.description = pkg.description
302
- if (Array.isArray(pkg.keywords)) {
303
- result.keywords = pkg.keywords.filter((k: unknown): k is string => typeof k === "string")
304
- }
305
- return Object.keys(result).length > 0 ? result : null
306
- } catch {
307
- return null
308
- }
309
- }
310
-
311
- export function fileNameToDescription(fileName: string): string {
312
- return fileName
313
- .replace(/[-_]+/g, " ")
314
- .replace(/([a-z])([A-Z])/g, "$1 $2")
315
- .toLowerCase()
316
- .trim()
317
- }
318
-
319
- export function extractTagsFromPath(filePath: string, rootDir: string): string[] {
320
- const rel = path.relative(rootDir, filePath)
321
- const parts = rel.split(path.sep)
322
- const tags = new Set<string>()
323
-
324
- for (const part of parts) {
325
- const name = part.replace(path.extname(part), "")
326
- for (const token of name.split(/[-_./\\]+/)) {
327
- const clean = token.toLowerCase().trim()
328
- if (clean && clean.length > 1) tags.add(clean)
329
- }
330
- }
331
-
332
- return Array.from(tags)
333
- }
@@ -1,67 +0,0 @@
1
- import path from "node:path"
2
- import type { StashSource } from "./stash-source"
3
- import { parseRegistryRef } from "./registry-resolve"
4
-
5
- /**
6
- * Given an origin string (from an AssetRef) and the full list of stash
7
- * sources, return the subset of sources to search.
8
- *
9
- * Resolution order:
10
- * 1. undefined → all sources (working → mounted → installed)
11
- * 2. "local" → working stash only
12
- * 3. exact match → installed source whose registryId matches verbatim
13
- * 4. parsed match → parse origin as a registry ref, match by parsed ID
14
- * 5. path match → mounted source whose path matches
15
- * 6. empty → indicates a remote/uninstalled origin (caller decides)
16
- */
17
- export function resolveSourcesForOrigin(
18
- origin: string | undefined,
19
- allSources: StashSource[],
20
- ): StashSource[] {
21
- if (!origin) return allSources
22
-
23
- if (origin === "local") {
24
- return allSources.filter((s) => s.kind === "working")
25
- }
26
-
27
- // Exact registryId match (e.g. origin is "npm:@scope/pkg")
28
- const byExactId = allSources.filter(
29
- (s) => s.kind === "installed" && s.registryId === origin,
30
- )
31
- if (byExactId.length > 0) return byExactId
32
-
33
- // Parse origin as a registry ref and match by parsed ID.
34
- // Allows shorthand: "owner/repo" matches "github:owner/repo",
35
- // "@scope/pkg" matches "npm:@scope/pkg".
36
- try {
37
- const parsed = parseRegistryRef(origin)
38
- const byParsedId = allSources.filter(
39
- (s) => s.kind === "installed" && s.registryId === parsed.id,
40
- )
41
- if (byParsedId.length > 0) return byParsedId
42
- } catch {
43
- // Not a valid registry ref — continue to path matching
44
- }
45
-
46
- // Mounted stash by resolved path
47
- const resolvedOrigin = path.resolve(origin)
48
- const byPath = allSources.filter(
49
- (s) => s.kind === "mounted" && path.resolve(s.path) === resolvedOrigin,
50
- )
51
- if (byPath.length > 0) return byPath
52
-
53
- // No match — origin may be remote/uninstalled
54
- return []
55
- }
56
-
57
- /**
58
- * Check whether an origin refers to something that could be fetched remotely
59
- * (i.e. it looks like a registry ref but isn't installed locally).
60
- */
61
- export function isRemoteOrigin(
62
- origin: string,
63
- allSources: StashSource[],
64
- ): boolean {
65
- if (origin === "local") return false
66
- return resolveSourcesForOrigin(origin, allSources).length === 0
67
- }