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.
- package/LICENSE +385 -0
- package/README.md +180 -110
- package/dist/cli.js +671 -0
- package/dist/common.js +192 -0
- package/dist/{src/config-cli.js → config-cli.js} +14 -6
- package/dist/{src/config.js → config.js} +92 -24
- package/dist/{src/db.js → db.js} +109 -35
- package/dist/{src/embedder.js → embedder.js} +57 -2
- package/dist/file-context.js +158 -0
- package/dist/{src/handlers → handlers}/command-handler.js +2 -0
- package/dist/{src/handlers → handlers}/index.js +0 -6
- package/dist/{src/indexer.js → indexer.js} +34 -10
- package/dist/init.js +43 -0
- package/dist/lockfile.js +55 -0
- package/dist/matchers.js +157 -0
- package/dist/{src/metadata.js → metadata.js} +12 -1
- package/dist/{src/origin-resolve.js → origin-resolve.js} +10 -9
- package/dist/paths.js +82 -0
- package/dist/{src/registry-install.js → registry-install.js} +145 -17
- package/dist/{src/registry-resolve.js → registry-resolve.js} +178 -18
- package/dist/{src/registry-search.js → registry-search.js} +8 -16
- package/dist/renderers.js +276 -0
- package/dist/{src/ripgrep-install.js → ripgrep-install.js} +5 -5
- package/dist/{src/ripgrep-resolve.js → ripgrep-resolve.js} +21 -11
- package/dist/self-update.js +220 -0
- package/dist/{src/stash-add.js → stash-add.js} +11 -2
- package/dist/stash-clone.js +115 -0
- package/dist/{src/stash-registry.js → stash-registry.js} +15 -41
- package/dist/{src/stash-search.js → stash-search.js} +67 -55
- package/dist/{src/stash-show.js → stash-show.js} +30 -3
- package/dist/{src/stash-source.js → stash-source.js} +56 -9
- package/dist/submit.js +552 -0
- package/dist/{src/walker.js → walker.js} +38 -0
- package/package.json +7 -16
- package/dist/index.d.ts +0 -28
- package/dist/index.js +0 -15
- package/dist/src/asset-spec.d.ts +0 -16
- package/dist/src/asset-type-handler.d.ts +0 -27
- package/dist/src/cli.d.ts +0 -2
- package/dist/src/cli.js +0 -399
- package/dist/src/common.d.ts +0 -13
- package/dist/src/common.js +0 -60
- package/dist/src/config-cli.d.ts +0 -9
- package/dist/src/config.d.ts +0 -50
- package/dist/src/db.d.ts +0 -46
- package/dist/src/embedder.d.ts +0 -10
- package/dist/src/frontmatter.d.ts +0 -30
- package/dist/src/github.d.ts +0 -4
- package/dist/src/handlers/agent-handler.d.ts +0 -2
- package/dist/src/handlers/command-handler.d.ts +0 -2
- package/dist/src/handlers/index.d.ts +0 -6
- package/dist/src/handlers/knowledge-handler.d.ts +0 -2
- package/dist/src/handlers/markdown-helpers.d.ts +0 -7
- package/dist/src/handlers/script-handler.d.ts +0 -2
- package/dist/src/handlers/skill-handler.d.ts +0 -2
- package/dist/src/handlers/tool-handler.d.ts +0 -2
- package/dist/src/indexer.d.ts +0 -22
- package/dist/src/init.d.ts +0 -19
- package/dist/src/init.js +0 -99
- package/dist/src/llm.d.ts +0 -15
- package/dist/src/markdown.d.ts +0 -18
- package/dist/src/metadata.d.ts +0 -41
- package/dist/src/origin-resolve.d.ts +0 -19
- package/dist/src/registry-install.d.ts +0 -11
- package/dist/src/registry-resolve.d.ts +0 -3
- package/dist/src/registry-search.d.ts +0 -27
- package/dist/src/registry-types.d.ts +0 -62
- package/dist/src/ripgrep-install.d.ts +0 -12
- package/dist/src/ripgrep-resolve.d.ts +0 -13
- package/dist/src/ripgrep.d.ts +0 -3
- package/dist/src/stash-add.d.ts +0 -4
- package/dist/src/stash-clone.d.ts +0 -22
- package/dist/src/stash-clone.js +0 -83
- package/dist/src/stash-ref.d.ts +0 -31
- package/dist/src/stash-registry.d.ts +0 -18
- package/dist/src/stash-resolve.d.ts +0 -2
- package/dist/src/stash-search.d.ts +0 -8
- package/dist/src/stash-show.d.ts +0 -5
- package/dist/src/stash-source.d.ts +0 -24
- package/dist/src/stash-types.d.ts +0 -227
- package/dist/src/stash.d.ts +0 -16
- package/dist/src/stash.js +0 -9
- package/dist/src/tool-runner.d.ts +0 -35
- package/dist/src/walker.d.ts +0 -19
- package/src/asset-spec.ts +0 -85
- package/src/asset-type-handler.ts +0 -77
- package/src/cli.ts +0 -427
- package/src/common.ts +0 -76
- package/src/config-cli.ts +0 -499
- package/src/config.ts +0 -305
- package/src/db.ts +0 -411
- package/src/embedder.ts +0 -128
- package/src/frontmatter.ts +0 -95
- package/src/github.ts +0 -21
- package/src/handlers/agent-handler.ts +0 -32
- package/src/handlers/command-handler.ts +0 -29
- package/src/handlers/index.ts +0 -25
- package/src/handlers/knowledge-handler.ts +0 -62
- package/src/handlers/markdown-helpers.ts +0 -19
- package/src/handlers/script-handler.ts +0 -92
- package/src/handlers/skill-handler.ts +0 -37
- package/src/handlers/tool-handler.ts +0 -71
- package/src/indexer.ts +0 -392
- package/src/init.ts +0 -114
- package/src/llm.ts +0 -125
- package/src/markdown.ts +0 -106
- package/src/metadata.ts +0 -333
- package/src/origin-resolve.ts +0 -67
- package/src/registry-install.ts +0 -361
- package/src/registry-resolve.ts +0 -341
- package/src/registry-search.ts +0 -335
- package/src/registry-types.ts +0 -72
- package/src/ripgrep-install.ts +0 -200
- package/src/ripgrep-resolve.ts +0 -72
- package/src/ripgrep.ts +0 -3
- package/src/stash-add.ts +0 -63
- package/src/stash-clone.ts +0 -127
- package/src/stash-ref.ts +0 -99
- package/src/stash-registry.ts +0 -259
- package/src/stash-resolve.ts +0 -50
- package/src/stash-search.ts +0 -613
- package/src/stash-show.ts +0 -55
- package/src/stash-source.ts +0 -103
- package/src/stash-types.ts +0 -231
- package/src/stash.ts +0 -39
- package/src/tool-runner.ts +0 -142
- package/src/walker.ts +0 -53
- /package/dist/{src/asset-spec.js → asset-spec.js} +0 -0
- /package/dist/{src/asset-type-handler.js → asset-type-handler.js} +0 -0
- /package/dist/{src/frontmatter.js → frontmatter.js} +0 -0
- /package/dist/{src/github.js → github.js} +0 -0
- /package/dist/{src/handlers → handlers}/agent-handler.js +0 -0
- /package/dist/{src/handlers → handlers}/knowledge-handler.js +0 -0
- /package/dist/{src/handlers → handlers}/markdown-helpers.js +0 -0
- /package/dist/{src/handlers → handlers}/script-handler.js +0 -0
- /package/dist/{src/handlers → handlers}/skill-handler.js +0 -0
- /package/dist/{src/handlers → handlers}/tool-handler.js +0 -0
- /package/dist/{src/llm.js → llm.js} +0 -0
- /package/dist/{src/markdown.js → markdown.js} +0 -0
- /package/dist/{src/registry-types.js → registry-types.js} +0 -0
- /package/dist/{src/ripgrep.js → ripgrep.js} +0 -0
- /package/dist/{src/stash-ref.js → stash-ref.js} +0 -0
- /package/dist/{src/stash-resolve.js → stash-resolve.js} +0 -0
- /package/dist/{src/stash-types.js → stash-types.js} +0 -0
- /package/dist/{src/tool-runner.js → tool-runner.js} +0 -0
package/src/registry-install.ts
DELETED
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process"
|
|
2
|
-
import fs from "node:fs"
|
|
3
|
-
import path from "node:path"
|
|
4
|
-
import { fetchWithTimeout, isWithin, TYPE_DIRS } from "./common"
|
|
5
|
-
import { loadConfig, saveConfig, type AgentikitConfig } from "./config"
|
|
6
|
-
import { parseRegistryRef, resolveRegistryArtifact } from "./registry-resolve"
|
|
7
|
-
import type { ParsedGitRef, RegistryInstallResult, RegistryInstalledEntry, RegistrySource } from "./registry-types"
|
|
8
|
-
|
|
9
|
-
const REGISTRY_STASH_DIR_NAMES = new Set<string>(Object.values(TYPE_DIRS))
|
|
10
|
-
|
|
11
|
-
export interface InstallRegistryRefOptions {
|
|
12
|
-
cacheRootDir?: string
|
|
13
|
-
now?: Date
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function installRegistryRef(ref: string, options?: InstallRegistryRefOptions): Promise<RegistryInstallResult> {
|
|
17
|
-
const parsed = parseRegistryRef(ref)
|
|
18
|
-
if (parsed.source === "git") {
|
|
19
|
-
return installGitRegistryRef(parsed, options)
|
|
20
|
-
}
|
|
21
|
-
const resolved = await resolveRegistryArtifact(parsed)
|
|
22
|
-
|
|
23
|
-
const installedAt = (options?.now ?? new Date()).toISOString()
|
|
24
|
-
const cacheRootDir = options?.cacheRootDir ?? getRegistryCacheRootDir()
|
|
25
|
-
const cacheDir = buildInstallCacheDir(cacheRootDir, resolved.source, resolved.id)
|
|
26
|
-
const archivePath = path.join(cacheDir, "artifact.tar.gz")
|
|
27
|
-
const extractedDir = path.join(cacheDir, "extracted")
|
|
28
|
-
|
|
29
|
-
fs.mkdirSync(cacheDir, { recursive: true })
|
|
30
|
-
|
|
31
|
-
await downloadArchive(resolved.artifactUrl, archivePath)
|
|
32
|
-
extractTarGzSecure(archivePath, extractedDir)
|
|
33
|
-
|
|
34
|
-
const provisionalKitRoot = detectStashRoot(extractedDir)
|
|
35
|
-
const installRoot = applyAgentikitIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot
|
|
36
|
-
const stashRoot = detectStashRoot(installRoot)
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
id: resolved.id,
|
|
40
|
-
source: resolved.source,
|
|
41
|
-
ref: resolved.ref,
|
|
42
|
-
artifactUrl: resolved.artifactUrl,
|
|
43
|
-
resolvedVersion: resolved.resolvedVersion,
|
|
44
|
-
resolvedRevision: resolved.resolvedRevision,
|
|
45
|
-
installedAt,
|
|
46
|
-
cacheDir,
|
|
47
|
-
extractedDir,
|
|
48
|
-
stashRoot,
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function installGitRegistryRef(parsed: ParsedGitRef, options?: InstallRegistryRefOptions): Promise<RegistryInstallResult> {
|
|
53
|
-
const resolved = await resolveRegistryArtifact(parsed)
|
|
54
|
-
const installedAt = (options?.now ?? new Date()).toISOString()
|
|
55
|
-
const cacheRootDir = options?.cacheRootDir ?? getRegistryCacheRootDir()
|
|
56
|
-
const cacheDir = buildInstallCacheDir(cacheRootDir, parsed.source, parsed.id)
|
|
57
|
-
const extractedDir = path.join(cacheDir, "extracted")
|
|
58
|
-
|
|
59
|
-
fs.mkdirSync(cacheDir, { recursive: true })
|
|
60
|
-
fs.rmSync(extractedDir, { recursive: true, force: true })
|
|
61
|
-
fs.mkdirSync(extractedDir, { recursive: true })
|
|
62
|
-
|
|
63
|
-
const includeConfig = findNearestAgentikitIncludeConfig(parsed.sourcePath, parsed.repoRoot)
|
|
64
|
-
if (includeConfig) {
|
|
65
|
-
copyIncludedPaths(includeConfig.baseDir, includeConfig.include, extractedDir)
|
|
66
|
-
} else {
|
|
67
|
-
copyDirectoryContents(parsed.sourcePath, extractedDir)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const stashRoot = detectStashRoot(extractedDir)
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
id: resolved.id,
|
|
74
|
-
source: resolved.source,
|
|
75
|
-
ref: resolved.ref,
|
|
76
|
-
artifactUrl: resolved.artifactUrl,
|
|
77
|
-
resolvedVersion: resolved.resolvedVersion,
|
|
78
|
-
resolvedRevision: resolved.resolvedRevision,
|
|
79
|
-
installedAt,
|
|
80
|
-
cacheDir,
|
|
81
|
-
extractedDir,
|
|
82
|
-
stashRoot,
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function upsertInstalledRegistryEntry(entry: RegistryInstalledEntry): AgentikitConfig {
|
|
87
|
-
const current = loadConfig()
|
|
88
|
-
const currentInstalled = current.registry?.installed ?? []
|
|
89
|
-
const withoutExisting = currentInstalled.filter((item) => item.id !== entry.id)
|
|
90
|
-
const nextInstalled = [...withoutExisting, normalizeInstalledEntry(entry)]
|
|
91
|
-
|
|
92
|
-
const nextConfig: AgentikitConfig = {
|
|
93
|
-
...current,
|
|
94
|
-
registry: { installed: nextInstalled },
|
|
95
|
-
}
|
|
96
|
-
saveConfig(nextConfig)
|
|
97
|
-
return nextConfig
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function removeInstalledRegistryEntry(id: string): AgentikitConfig {
|
|
101
|
-
const current = loadConfig()
|
|
102
|
-
const currentInstalled = current.registry?.installed ?? []
|
|
103
|
-
const nextInstalled = currentInstalled.filter((item) => item.id !== id)
|
|
104
|
-
|
|
105
|
-
const nextConfig: AgentikitConfig = {
|
|
106
|
-
...current,
|
|
107
|
-
registry: nextInstalled.length > 0 ? { installed: nextInstalled } : undefined,
|
|
108
|
-
}
|
|
109
|
-
saveConfig(nextConfig)
|
|
110
|
-
return nextConfig
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function getRegistryCacheRootDir(): string {
|
|
114
|
-
const xdgCache = process.env.XDG_CACHE_HOME?.trim()
|
|
115
|
-
if (xdgCache) {
|
|
116
|
-
return path.join(path.resolve(xdgCache), "agentikit", "registry")
|
|
117
|
-
}
|
|
118
|
-
const home = process.env.HOME?.trim()
|
|
119
|
-
if (!home) {
|
|
120
|
-
throw new Error("Unable to determine cache directory. Set XDG_CACHE_HOME or HOME.")
|
|
121
|
-
}
|
|
122
|
-
return path.join(path.resolve(home), ".cache", "agentikit", "registry")
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function detectStashRoot(extractedDir: string): string {
|
|
126
|
-
const root = path.resolve(extractedDir)
|
|
127
|
-
|
|
128
|
-
const rootDotStash = path.join(root, ".stash")
|
|
129
|
-
if (isDirectory(rootDotStash)) {
|
|
130
|
-
return root
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (hasStashDirs(root)) {
|
|
134
|
-
return root
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const opencodeDir = path.join(root, "opencode")
|
|
138
|
-
if (hasStashDirs(opencodeDir)) {
|
|
139
|
-
return opencodeDir
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const shallowest = findShallowestDotStashRoot(root)
|
|
143
|
-
if (shallowest) return shallowest
|
|
144
|
-
|
|
145
|
-
return root
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function buildInstallCacheDir(cacheRootDir: string, source: RegistrySource, id: string): string {
|
|
149
|
-
const slug = `${source}-${id.replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "")}`
|
|
150
|
-
const stamp = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`
|
|
151
|
-
return path.join(cacheRootDir, slug || source, stamp)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function applyAgentikitIncludeConfig(
|
|
155
|
-
sourceRoot: string,
|
|
156
|
-
cacheDir: string,
|
|
157
|
-
searchRoot: string = sourceRoot,
|
|
158
|
-
): string | undefined {
|
|
159
|
-
const includeConfig = findNearestAgentikitIncludeConfig(sourceRoot, searchRoot)
|
|
160
|
-
if (!includeConfig) return undefined
|
|
161
|
-
|
|
162
|
-
const selectedDir = path.join(cacheDir, "selected")
|
|
163
|
-
fs.rmSync(selectedDir, { recursive: true, force: true })
|
|
164
|
-
fs.mkdirSync(selectedDir, { recursive: true })
|
|
165
|
-
copyIncludedPaths(includeConfig.baseDir, includeConfig.include, selectedDir)
|
|
166
|
-
return selectedDir
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async function downloadArchive(url: string, destination: string): Promise<void> {
|
|
170
|
-
const response = await fetchWithTimeout(url, undefined, 120_000)
|
|
171
|
-
if (!response.ok) {
|
|
172
|
-
throw new Error(`Failed to download archive (${response.status}) from ${url}`)
|
|
173
|
-
}
|
|
174
|
-
// Stream response to disk instead of buffering the entire archive in memory.
|
|
175
|
-
// Uses Bun.write which handles Response streaming natively.
|
|
176
|
-
const BunRuntime: { write(path: string, body: Response): Promise<number> } =
|
|
177
|
-
(globalThis as Record<string, unknown>).Bun as typeof BunRuntime
|
|
178
|
-
if (BunRuntime?.write) {
|
|
179
|
-
await BunRuntime.write(destination, response)
|
|
180
|
-
} else {
|
|
181
|
-
// Fallback for non-Bun environments (e.g., tests)
|
|
182
|
-
const arrayBuffer = await response.arrayBuffer()
|
|
183
|
-
fs.writeFileSync(destination, Buffer.from(arrayBuffer))
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function extractTarGzSecure(archivePath: string, destinationDir: string): void {
|
|
188
|
-
const listResult = spawnSync("tar", ["tzf", archivePath], { encoding: "utf8" })
|
|
189
|
-
if (listResult.status !== 0) {
|
|
190
|
-
const err = listResult.stderr?.trim() || listResult.error?.message || "unknown error"
|
|
191
|
-
throw new Error(`Failed to inspect archive ${archivePath}: ${err}`)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
validateTarEntries(listResult.stdout)
|
|
195
|
-
|
|
196
|
-
fs.rmSync(destinationDir, { recursive: true, force: true })
|
|
197
|
-
fs.mkdirSync(destinationDir, { recursive: true })
|
|
198
|
-
|
|
199
|
-
const extractResult = spawnSync("tar", ["xzf", archivePath, "--strip-components=1", "-C", destinationDir], {
|
|
200
|
-
encoding: "utf8",
|
|
201
|
-
})
|
|
202
|
-
if (extractResult.status !== 0) {
|
|
203
|
-
const err = extractResult.stderr?.trim() || extractResult.error?.message || "unknown error"
|
|
204
|
-
throw new Error(`Failed to extract archive ${archivePath}: ${err}`)
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function validateTarEntries(listOutput: string): void {
|
|
209
|
-
const lines = listOutput.split(/\r?\n/).filter(Boolean)
|
|
210
|
-
for (const rawLine of lines) {
|
|
211
|
-
const entry = rawLine.trim()
|
|
212
|
-
if (!entry || entry.includes("\0")) {
|
|
213
|
-
throw new Error(`Archive contains an invalid entry: ${JSON.stringify(rawLine)}`)
|
|
214
|
-
}
|
|
215
|
-
if (entry.startsWith("/")) {
|
|
216
|
-
throw new Error(`Archive contains an absolute path entry: ${entry}`)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const normalized = path.posix.normalize(entry)
|
|
220
|
-
if (normalized === ".." || normalized.startsWith("../")) {
|
|
221
|
-
throw new Error(`Archive contains a path traversal entry: ${entry}`)
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const parts = normalized.split("/").filter(Boolean)
|
|
225
|
-
const stripped = parts.slice(1).join("/")
|
|
226
|
-
if (!stripped) continue
|
|
227
|
-
const normalizedStripped = path.posix.normalize(stripped)
|
|
228
|
-
if (normalizedStripped === ".." || normalizedStripped.startsWith("../") || path.posix.isAbsolute(normalizedStripped)) {
|
|
229
|
-
throw new Error(`Archive contains an unsafe entry after strip-components: ${entry}`)
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function isDirectory(target: string): boolean {
|
|
235
|
-
try {
|
|
236
|
-
return fs.statSync(target).isDirectory()
|
|
237
|
-
} catch {
|
|
238
|
-
return false
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function readAgentikitIncludeConfigAtDir(dirPath: string): { baseDir: string; include: string[] } | undefined {
|
|
243
|
-
const packageJsonPath = path.join(dirPath, "package.json")
|
|
244
|
-
if (!fs.existsSync(packageJsonPath)) return undefined
|
|
245
|
-
|
|
246
|
-
let pkg: unknown
|
|
247
|
-
try {
|
|
248
|
-
pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"))
|
|
249
|
-
} catch {
|
|
250
|
-
return undefined
|
|
251
|
-
}
|
|
252
|
-
if (typeof pkg !== "object" || pkg === null || Array.isArray(pkg)) return undefined
|
|
253
|
-
|
|
254
|
-
const agentikit = (pkg as Record<string, unknown>).agentikit
|
|
255
|
-
if (typeof agentikit !== "object" || agentikit === null || Array.isArray(agentikit)) return undefined
|
|
256
|
-
|
|
257
|
-
const include = (agentikit as Record<string, unknown>).include
|
|
258
|
-
if (!Array.isArray(include)) return undefined
|
|
259
|
-
|
|
260
|
-
const parsedInclude = include
|
|
261
|
-
.filter((value): value is string => typeof value === "string")
|
|
262
|
-
.map((value) => value.trim())
|
|
263
|
-
.filter(Boolean)
|
|
264
|
-
|
|
265
|
-
return parsedInclude.length > 0 ? { baseDir: dirPath, include: parsedInclude } : undefined
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function findNearestAgentikitIncludeConfig(
|
|
269
|
-
startDir: string,
|
|
270
|
-
stopDir: string,
|
|
271
|
-
): { baseDir: string; include: string[] } | undefined {
|
|
272
|
-
let current = path.resolve(startDir)
|
|
273
|
-
const boundary = path.resolve(stopDir)
|
|
274
|
-
|
|
275
|
-
while (isWithin(current, boundary)) {
|
|
276
|
-
const config = readAgentikitIncludeConfigAtDir(current)
|
|
277
|
-
if (config) return config
|
|
278
|
-
if (current === boundary) break
|
|
279
|
-
const parent = path.dirname(current)
|
|
280
|
-
if (parent === current) break
|
|
281
|
-
current = parent
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return undefined
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function copyIncludedPaths(baseDir: string, include: string[], destinationDir: string): void {
|
|
288
|
-
for (const entry of include) {
|
|
289
|
-
const resolvedSource = path.resolve(baseDir, entry)
|
|
290
|
-
if (!isWithin(resolvedSource, baseDir)) {
|
|
291
|
-
throw new Error(`Path in agentikit.include escapes the package root: ${entry}`)
|
|
292
|
-
}
|
|
293
|
-
if (!fs.existsSync(resolvedSource)) {
|
|
294
|
-
throw new Error(`Path in agentikit.include does not exist: ${entry}`)
|
|
295
|
-
}
|
|
296
|
-
if (path.basename(resolvedSource) === ".git") {
|
|
297
|
-
continue
|
|
298
|
-
}
|
|
299
|
-
const relativePath = path.relative(baseDir, resolvedSource)
|
|
300
|
-
if (!relativePath || relativePath === ".") {
|
|
301
|
-
copyDirectoryContents(baseDir, destinationDir)
|
|
302
|
-
continue
|
|
303
|
-
}
|
|
304
|
-
copyPath(resolvedSource, path.join(destinationDir, relativePath))
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function copyDirectoryContents(sourceDir: string, destinationDir: string): void {
|
|
309
|
-
for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
|
|
310
|
-
if (entry.name === ".git") continue
|
|
311
|
-
copyPath(path.join(sourceDir, entry.name), path.join(destinationDir, entry.name))
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function copyPath(sourcePath: string, destinationPath: string): void {
|
|
316
|
-
const stat = fs.statSync(sourcePath)
|
|
317
|
-
fs.mkdirSync(path.dirname(destinationPath), { recursive: true })
|
|
318
|
-
if (stat.isDirectory()) {
|
|
319
|
-
fs.cpSync(sourcePath, destinationPath, { recursive: true, force: true })
|
|
320
|
-
return
|
|
321
|
-
}
|
|
322
|
-
fs.copyFileSync(sourcePath, destinationPath)
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function hasStashDirs(dirPath: string): boolean {
|
|
326
|
-
if (!isDirectory(dirPath)) return false
|
|
327
|
-
const entries = fs.readdirSync(dirPath, { withFileTypes: true })
|
|
328
|
-
return entries.some((entry) => entry.isDirectory() && REGISTRY_STASH_DIR_NAMES.has(entry.name))
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function findShallowestDotStashRoot(root: string): string | undefined {
|
|
332
|
-
const queue: string[] = [root]
|
|
333
|
-
while (queue.length > 0) {
|
|
334
|
-
const current = queue.shift()!
|
|
335
|
-
const dotStash = path.join(current, ".stash")
|
|
336
|
-
if (isDirectory(dotStash)) {
|
|
337
|
-
return current
|
|
338
|
-
}
|
|
339
|
-
let children: fs.Dirent[]
|
|
340
|
-
try {
|
|
341
|
-
children = fs.readdirSync(current, { withFileTypes: true })
|
|
342
|
-
} catch {
|
|
343
|
-
continue
|
|
344
|
-
}
|
|
345
|
-
for (const child of children) {
|
|
346
|
-
if (!child.isDirectory()) continue
|
|
347
|
-
if (child.name === ".git" || child.name === "node_modules") continue
|
|
348
|
-
queue.push(path.join(current, child.name))
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
return undefined
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function normalizeInstalledEntry(entry: RegistryInstalledEntry): RegistryInstalledEntry {
|
|
355
|
-
return {
|
|
356
|
-
...entry,
|
|
357
|
-
stashRoot: path.resolve(entry.stashRoot),
|
|
358
|
-
cacheDir: path.resolve(entry.cacheDir),
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
package/src/registry-resolve.ts
DELETED
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process"
|
|
2
|
-
import fs from "node:fs"
|
|
3
|
-
import path from "node:path"
|
|
4
|
-
import { pathToFileURL } from "node:url"
|
|
5
|
-
import { fetchWithTimeout } from "./common"
|
|
6
|
-
import type { ParsedGitRef, ParsedGithubRef, ParsedNpmRef, ParsedRegistryRef, ResolvedRegistryArtifact } from "./registry-types"
|
|
7
|
-
import { GITHUB_API_BASE, githubHeaders, asRecord, asString } from "./github"
|
|
8
|
-
|
|
9
|
-
export function parseRegistryRef(rawRef: string): ParsedRegistryRef {
|
|
10
|
-
const ref = rawRef.trim()
|
|
11
|
-
if (!ref) throw new Error("Registry ref is required.")
|
|
12
|
-
|
|
13
|
-
if (ref.startsWith("npm:")) {
|
|
14
|
-
return parseNpmRef(ref.slice(4), ref)
|
|
15
|
-
}
|
|
16
|
-
if (ref.startsWith("github:")) {
|
|
17
|
-
return parseGithubShorthand(ref.slice(7), ref)
|
|
18
|
-
}
|
|
19
|
-
if (ref.startsWith("http://") || ref.startsWith("https://")) {
|
|
20
|
-
return parseGithubUrl(ref)
|
|
21
|
-
}
|
|
22
|
-
const localGitRef = tryParseLocalGitRef(ref, isPathLikeRef(ref))
|
|
23
|
-
if (localGitRef) {
|
|
24
|
-
return localGitRef
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (ref.startsWith("@") || !looksLikeGithubOwnerRepo(ref)) {
|
|
28
|
-
return parseNpmRef(ref, ref)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return parseGithubShorthand(ref, ref)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function resolveRegistryArtifact(parsed: ParsedRegistryRef): Promise<ResolvedRegistryArtifact> {
|
|
35
|
-
if (parsed.source === "npm") {
|
|
36
|
-
return resolveNpmArtifact(parsed)
|
|
37
|
-
}
|
|
38
|
-
if (parsed.source === "git") {
|
|
39
|
-
return resolveGitArtifact(parsed)
|
|
40
|
-
}
|
|
41
|
-
return resolveGithubArtifact(parsed)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function parseNpmRef(input: string, originalRef: string): ParsedNpmRef {
|
|
45
|
-
const trimmed = input.trim()
|
|
46
|
-
if (!trimmed) throw new Error("Invalid npm ref.")
|
|
47
|
-
|
|
48
|
-
const parsed = splitNpmNameAndVersion(trimmed)
|
|
49
|
-
validateNpmPackageName(parsed.packageName)
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
source: "npm",
|
|
53
|
-
ref: originalRef,
|
|
54
|
-
id: `npm:${parsed.packageName}`,
|
|
55
|
-
packageName: parsed.packageName,
|
|
56
|
-
requestedVersionOrTag: parsed.requestedVersionOrTag,
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function parseGithubShorthand(input: string, originalRef: string): ParsedGithubRef {
|
|
61
|
-
const [repoPart, requestedRef] = splitRefSuffix(input.trim())
|
|
62
|
-
const segments = repoPart.split("/").filter(Boolean)
|
|
63
|
-
if (segments.length !== 2) {
|
|
64
|
-
throw new Error("Invalid GitHub ref. Expected owner/repo or owner/repo#ref.")
|
|
65
|
-
}
|
|
66
|
-
const owner = segments[0]
|
|
67
|
-
const repo = segments[1].replace(/\.git$/i, "")
|
|
68
|
-
if (!owner || !repo) {
|
|
69
|
-
throw new Error("Invalid GitHub ref. Expected owner/repo.")
|
|
70
|
-
}
|
|
71
|
-
return {
|
|
72
|
-
source: "github",
|
|
73
|
-
ref: originalRef,
|
|
74
|
-
id: `github:${owner}/${repo}`,
|
|
75
|
-
owner,
|
|
76
|
-
repo,
|
|
77
|
-
requestedRef,
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function parseGithubUrl(rawUrl: string): ParsedGithubRef {
|
|
82
|
-
let url: URL
|
|
83
|
-
try {
|
|
84
|
-
url = new URL(rawUrl)
|
|
85
|
-
} catch {
|
|
86
|
-
throw new Error("Invalid registry URL.")
|
|
87
|
-
}
|
|
88
|
-
if (url.hostname !== "github.com") {
|
|
89
|
-
throw new Error("Only GitHub URLs are currently supported for URL refs.")
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const segments = url.pathname.split("/").filter(Boolean)
|
|
93
|
-
if (segments.length < 2) {
|
|
94
|
-
throw new Error("Invalid GitHub URL. Expected https://github.com/owner/repo.")
|
|
95
|
-
}
|
|
96
|
-
const owner = segments[0]
|
|
97
|
-
const repo = segments[1].replace(/\.git$/i, "")
|
|
98
|
-
const requestedRef = url.hash ? decodeURIComponent(url.hash.slice(1)) : undefined
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
source: "github",
|
|
102
|
-
ref: rawUrl,
|
|
103
|
-
id: `github:${owner}/${repo}`,
|
|
104
|
-
owner,
|
|
105
|
-
repo,
|
|
106
|
-
requestedRef,
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function tryParseLocalGitRef(rawRef: string, explicitPath: boolean): ParsedGitRef | undefined {
|
|
111
|
-
if (!explicitPath) {
|
|
112
|
-
return undefined
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const resolvedPath = path.resolve(rawRef)
|
|
116
|
-
let stat: fs.Stats
|
|
117
|
-
try {
|
|
118
|
-
stat = fs.statSync(resolvedPath)
|
|
119
|
-
} catch {
|
|
120
|
-
throw new Error(`Local path not found: ${resolvedPath}`)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (!stat.isDirectory()) {
|
|
124
|
-
throw new Error("Local add path must be a directory, but the provided path is not one.")
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const repoRoot = findGitRepoRoot(resolvedPath)
|
|
128
|
-
if (!repoRoot) {
|
|
129
|
-
throw new Error("Local add path must be inside a git repository.")
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
source: "git",
|
|
134
|
-
ref: rawRef,
|
|
135
|
-
id: `git:${encodeURIComponent(resolvedPath)}`,
|
|
136
|
-
repoRoot,
|
|
137
|
-
sourcePath: resolvedPath,
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function isPathLikeRef(ref: string): boolean {
|
|
142
|
-
if (path.isAbsolute(ref)) return true
|
|
143
|
-
if (ref.startsWith("./") || ref.startsWith("../") || ref.startsWith(".\\") || ref.startsWith("..\\")) {
|
|
144
|
-
return true
|
|
145
|
-
}
|
|
146
|
-
return ref.includes("/") || ref.includes("\\")
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async function resolveNpmArtifact(parsed: ParsedNpmRef): Promise<ResolvedRegistryArtifact> {
|
|
150
|
-
const encodedName = encodeURIComponent(parsed.packageName)
|
|
151
|
-
const metadata = await fetchJson<Record<string, unknown>>(`https://registry.npmjs.org/${encodedName}`)
|
|
152
|
-
|
|
153
|
-
const versions = asRecord(metadata.versions)
|
|
154
|
-
const distTags = asRecord(metadata["dist-tags"])
|
|
155
|
-
|
|
156
|
-
const requested = parsed.requestedVersionOrTag
|
|
157
|
-
let resolvedVersion: string | undefined
|
|
158
|
-
if (!requested) {
|
|
159
|
-
resolvedVersion = asString(distTags.latest)
|
|
160
|
-
} else if (requested in versions) {
|
|
161
|
-
resolvedVersion = requested
|
|
162
|
-
} else {
|
|
163
|
-
resolvedVersion = asString(distTags[requested])
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (!resolvedVersion || !(resolvedVersion in versions)) {
|
|
167
|
-
throw new Error(`Unable to resolve npm ref \"${parsed.ref}\".`)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const versionMeta = asRecord(versions[resolvedVersion])
|
|
171
|
-
const dist = asRecord(versionMeta.dist)
|
|
172
|
-
const tarballUrl = asString(dist.tarball)
|
|
173
|
-
if (!tarballUrl) {
|
|
174
|
-
throw new Error(`npm package ${parsed.packageName}@${resolvedVersion} does not expose a tarball URL.`)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const resolvedRevision = asString(dist.shasum) ?? asString(dist.integrity)
|
|
178
|
-
|
|
179
|
-
return {
|
|
180
|
-
id: parsed.id,
|
|
181
|
-
source: parsed.source,
|
|
182
|
-
ref: parsed.ref,
|
|
183
|
-
artifactUrl: tarballUrl,
|
|
184
|
-
resolvedVersion,
|
|
185
|
-
resolvedRevision,
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
async function resolveGithubArtifact(parsed: ParsedGithubRef): Promise<ResolvedRegistryArtifact> {
|
|
190
|
-
const headers = githubHeaders()
|
|
191
|
-
|
|
192
|
-
if (parsed.requestedRef) {
|
|
193
|
-
const commit = await tryFetchJson<Record<string, unknown>>(
|
|
194
|
-
`${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}/commits/${encodeURIComponent(parsed.requestedRef)}`,
|
|
195
|
-
headers,
|
|
196
|
-
)
|
|
197
|
-
const resolvedRevision = asString(commit?.sha) ?? parsed.requestedRef
|
|
198
|
-
return {
|
|
199
|
-
id: parsed.id,
|
|
200
|
-
source: parsed.source,
|
|
201
|
-
ref: parsed.ref,
|
|
202
|
-
artifactUrl: `${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}/tarball/${encodeURIComponent(parsed.requestedRef)}`,
|
|
203
|
-
resolvedRevision,
|
|
204
|
-
resolvedVersion: parsed.requestedRef,
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const latestRelease = await tryFetchJson<Record<string, unknown>>(
|
|
209
|
-
`${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}/releases/latest`,
|
|
210
|
-
headers,
|
|
211
|
-
)
|
|
212
|
-
if (latestRelease) {
|
|
213
|
-
const tarballUrl = asString(latestRelease.tarball_url)
|
|
214
|
-
if (tarballUrl) {
|
|
215
|
-
return {
|
|
216
|
-
id: parsed.id,
|
|
217
|
-
source: parsed.source,
|
|
218
|
-
ref: parsed.ref,
|
|
219
|
-
artifactUrl: tarballUrl,
|
|
220
|
-
resolvedVersion: asString(latestRelease.tag_name),
|
|
221
|
-
resolvedRevision: asString(latestRelease.target_commitish),
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const repoMeta = await fetchJson<Record<string, unknown>>(
|
|
227
|
-
`${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}`,
|
|
228
|
-
headers,
|
|
229
|
-
)
|
|
230
|
-
const defaultBranch = asString(repoMeta.default_branch)
|
|
231
|
-
if (!defaultBranch) {
|
|
232
|
-
throw new Error(`Unable to resolve default branch for ${parsed.owner}/${parsed.repo}.`)
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const commit = await tryFetchJson<Record<string, unknown>>(
|
|
236
|
-
`${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}/commits/${encodeURIComponent(defaultBranch)}`,
|
|
237
|
-
headers,
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
return {
|
|
241
|
-
id: parsed.id,
|
|
242
|
-
source: parsed.source,
|
|
243
|
-
ref: parsed.ref,
|
|
244
|
-
artifactUrl: `${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}/tarball/${encodeURIComponent(defaultBranch)}`,
|
|
245
|
-
resolvedVersion: defaultBranch,
|
|
246
|
-
resolvedRevision: asString(commit?.sha) ?? defaultBranch,
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
async function resolveGitArtifact(parsed: ParsedGitRef): Promise<ResolvedRegistryArtifact> {
|
|
251
|
-
return {
|
|
252
|
-
id: parsed.id,
|
|
253
|
-
source: parsed.source,
|
|
254
|
-
ref: parsed.ref,
|
|
255
|
-
artifactUrl: pathToFileURL(parsed.sourcePath).toString(),
|
|
256
|
-
resolvedRevision: readGitValue(parsed.repoRoot, "rev-parse", "HEAD"),
|
|
257
|
-
resolvedVersion: readGitValue(parsed.repoRoot, "rev-parse", "--abbrev-ref", "HEAD"),
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function splitNpmNameAndVersion(input: string): { packageName: string; requestedVersionOrTag?: string } {
|
|
262
|
-
if (input.startsWith("@")) {
|
|
263
|
-
const secondAt = input.indexOf("@", 1)
|
|
264
|
-
if (secondAt > 0) {
|
|
265
|
-
return {
|
|
266
|
-
packageName: input.slice(0, secondAt),
|
|
267
|
-
requestedVersionOrTag: input.slice(secondAt + 1) || undefined,
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
return { packageName: input }
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const at = input.lastIndexOf("@")
|
|
274
|
-
if (at > 0) {
|
|
275
|
-
return {
|
|
276
|
-
packageName: input.slice(0, at),
|
|
277
|
-
requestedVersionOrTag: input.slice(at + 1) || undefined,
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
return { packageName: input }
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function validateNpmPackageName(name: string): void {
|
|
284
|
-
if (!name) throw new Error('Invalid npm package name: name is required.')
|
|
285
|
-
if (name.length > 214) throw new Error(`Invalid npm package name: "${name}" exceeds 214 characters.`)
|
|
286
|
-
if (name !== name.toLowerCase() && !name.startsWith('@')) {
|
|
287
|
-
throw new Error(`Invalid npm package name: "${name}" must be lowercase.`)
|
|
288
|
-
}
|
|
289
|
-
if (name.startsWith('.') || name.startsWith('_')) {
|
|
290
|
-
throw new Error(`Invalid npm package name: "${name}" cannot start with . or _.`)
|
|
291
|
-
}
|
|
292
|
-
if (/[~'!()*]/.test(name) || name.includes(' ') || encodeURIComponent(name).replace(/%40/g, '@').replace(/%2[Ff]/g, '/') !== name) {
|
|
293
|
-
throw new Error(`Invalid npm package name: "${name}" contains invalid characters.`)
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function looksLikeGithubOwnerRepo(ref: string): boolean {
|
|
298
|
-
const [repoPart] = splitRefSuffix(ref)
|
|
299
|
-
const parts = repoPart.split("/").filter(Boolean)
|
|
300
|
-
return parts.length === 2
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function splitRefSuffix(value: string): [string, string | undefined] {
|
|
304
|
-
const hash = value.indexOf("#")
|
|
305
|
-
if (hash < 0) return [value, undefined]
|
|
306
|
-
return [value.slice(0, hash), value.slice(hash + 1) || undefined]
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function findGitRepoRoot(startDir: string): string | undefined {
|
|
310
|
-
let current = path.resolve(startDir)
|
|
311
|
-
while (true) {
|
|
312
|
-
if (fs.existsSync(path.join(current, ".git"))) {
|
|
313
|
-
return current
|
|
314
|
-
}
|
|
315
|
-
const parent = path.dirname(current)
|
|
316
|
-
if (parent === current) return undefined
|
|
317
|
-
current = parent
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function readGitValue(repoRoot: string, ...args: string[]): string | undefined {
|
|
322
|
-
const result = spawnSync("git", ["-C", repoRoot, ...args], { encoding: "utf8" })
|
|
323
|
-
if (result.status !== 0) return undefined
|
|
324
|
-
const value = result.stdout.trim()
|
|
325
|
-
return value || undefined
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
async function fetchJson<T>(url: string, headers?: HeadersInit): Promise<T> {
|
|
329
|
-
const response = await fetchWithTimeout(url, { headers })
|
|
330
|
-
if (!response.ok) {
|
|
331
|
-
throw new Error(`Request failed (${response.status}) for ${url}`)
|
|
332
|
-
}
|
|
333
|
-
return await response.json() as T
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
async function tryFetchJson<T>(url: string, headers?: HeadersInit): Promise<T | null> {
|
|
337
|
-
const response = await fetchWithTimeout(url, { headers })
|
|
338
|
-
if (!response.ok) return null
|
|
339
|
-
return await response.json() as T
|
|
340
|
-
}
|
|
341
|
-
|