agentikit 0.0.8 → 0.0.12

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 (112) hide show
  1. package/README.md +135 -117
  2. package/dist/index.d.ts +13 -3
  3. package/dist/index.js +7 -1
  4. package/dist/src/asset-spec.d.ts +2 -0
  5. package/dist/src/asset-spec.js +22 -3
  6. package/dist/src/asset-type-handler.d.ts +27 -0
  7. package/dist/src/asset-type-handler.js +33 -0
  8. package/dist/src/cli.js +335 -100
  9. package/dist/src/common.d.ts +6 -1
  10. package/dist/src/common.js +18 -4
  11. package/dist/src/config-cli.d.ts +9 -0
  12. package/dist/src/config-cli.js +473 -0
  13. package/dist/src/config.d.ts +25 -6
  14. package/dist/src/config.js +188 -28
  15. package/dist/src/db.d.ts +46 -0
  16. package/dist/src/db.js +299 -0
  17. package/dist/src/embedder.js +12 -7
  18. package/dist/src/github.d.ts +4 -0
  19. package/dist/src/github.js +19 -0
  20. package/dist/src/handlers/agent-handler.d.ts +2 -0
  21. package/dist/src/handlers/agent-handler.js +26 -0
  22. package/dist/src/handlers/command-handler.d.ts +2 -0
  23. package/dist/src/handlers/command-handler.js +23 -0
  24. package/dist/src/handlers/index.d.ts +6 -0
  25. package/dist/src/handlers/index.js +23 -0
  26. package/dist/src/handlers/knowledge-handler.d.ts +2 -0
  27. package/dist/src/handlers/knowledge-handler.js +56 -0
  28. package/dist/src/handlers/markdown-helpers.d.ts +7 -0
  29. package/dist/src/handlers/markdown-helpers.js +15 -0
  30. package/dist/src/handlers/script-handler.d.ts +2 -0
  31. package/dist/src/handlers/script-handler.js +78 -0
  32. package/dist/src/handlers/skill-handler.d.ts +2 -0
  33. package/dist/src/handlers/skill-handler.js +30 -0
  34. package/dist/src/handlers/tool-handler.d.ts +2 -0
  35. package/dist/src/handlers/tool-handler.js +58 -0
  36. package/dist/src/indexer.d.ts +1 -23
  37. package/dist/src/indexer.js +162 -155
  38. package/dist/src/init.d.ts +2 -2
  39. package/dist/src/init.js +21 -9
  40. package/dist/src/llm.js +4 -3
  41. package/dist/src/metadata.d.ts +1 -1
  42. package/dist/src/metadata.js +22 -64
  43. package/dist/src/origin-resolve.d.ts +19 -0
  44. package/dist/src/origin-resolve.js +53 -0
  45. package/dist/src/registry-install.d.ts +11 -0
  46. package/dist/src/registry-install.js +315 -0
  47. package/dist/src/registry-resolve.d.ts +3 -0
  48. package/dist/src/registry-resolve.js +299 -0
  49. package/dist/src/registry-search.d.ts +27 -0
  50. package/dist/src/registry-search.js +263 -0
  51. package/dist/src/registry-types.d.ts +62 -0
  52. package/dist/src/registry-types.js +1 -0
  53. package/dist/src/stash-add.d.ts +4 -0
  54. package/dist/src/stash-add.js +59 -0
  55. package/dist/src/stash-clone.d.ts +22 -0
  56. package/dist/src/stash-clone.js +83 -0
  57. package/dist/src/stash-ref.d.ts +27 -3
  58. package/dist/src/stash-ref.js +63 -24
  59. package/dist/src/stash-registry.d.ts +18 -0
  60. package/dist/src/stash-registry.js +221 -0
  61. package/dist/src/stash-resolve.js +3 -0
  62. package/dist/src/stash-search.d.ts +3 -1
  63. package/dist/src/stash-search.js +357 -138
  64. package/dist/src/stash-show.d.ts +1 -1
  65. package/dist/src/stash-show.js +28 -89
  66. package/dist/src/stash-source.d.ts +24 -0
  67. package/dist/src/stash-source.js +81 -0
  68. package/dist/src/stash-types.d.ts +175 -1
  69. package/dist/src/stash.d.ts +9 -1
  70. package/dist/src/stash.js +5 -0
  71. package/dist/src/tool-runner.d.ts +1 -1
  72. package/dist/src/tool-runner.js +18 -5
  73. package/package.json +7 -2
  74. package/src/asset-spec.ts +20 -4
  75. package/src/asset-type-handler.ts +77 -0
  76. package/src/cli.ts +354 -103
  77. package/src/common.ts +23 -5
  78. package/src/config-cli.ts +499 -0
  79. package/src/config.ts +218 -37
  80. package/src/db.ts +411 -0
  81. package/src/embedder.ts +22 -11
  82. package/src/github.ts +21 -0
  83. package/src/handlers/agent-handler.ts +32 -0
  84. package/src/handlers/command-handler.ts +29 -0
  85. package/src/handlers/index.ts +25 -0
  86. package/src/handlers/knowledge-handler.ts +62 -0
  87. package/src/handlers/markdown-helpers.ts +19 -0
  88. package/src/handlers/script-handler.ts +92 -0
  89. package/src/handlers/skill-handler.ts +37 -0
  90. package/src/handlers/tool-handler.ts +71 -0
  91. package/src/indexer.ts +208 -187
  92. package/src/init.ts +17 -9
  93. package/src/llm.ts +4 -3
  94. package/src/metadata.ts +21 -65
  95. package/src/origin-resolve.ts +67 -0
  96. package/src/registry-install.ts +361 -0
  97. package/src/registry-resolve.ts +341 -0
  98. package/src/registry-search.ts +335 -0
  99. package/src/registry-types.ts +72 -0
  100. package/src/stash-add.ts +63 -0
  101. package/src/stash-clone.ts +127 -0
  102. package/src/stash-ref.ts +84 -26
  103. package/src/stash-registry.ts +259 -0
  104. package/src/stash-resolve.ts +3 -0
  105. package/src/stash-search.ts +425 -155
  106. package/src/stash-show.ts +33 -82
  107. package/src/stash-source.ts +103 -0
  108. package/src/stash-types.ts +186 -1
  109. package/src/stash.ts +23 -0
  110. package/src/tool-runner.ts +18 -5
  111. package/dist/src/similarity.d.ts +0 -34
  112. package/src/similarity.ts +0 -271
@@ -0,0 +1,72 @@
1
+ export type RegistrySource = "npm" | "github" | "git"
2
+
3
+ export interface RegistryRefBase {
4
+ source: RegistrySource
5
+ ref: string
6
+ id: string
7
+ }
8
+
9
+ export interface ParsedNpmRef extends RegistryRefBase {
10
+ source: "npm"
11
+ packageName: string
12
+ requestedVersionOrTag?: string
13
+ }
14
+
15
+ export interface ParsedGithubRef extends RegistryRefBase {
16
+ source: "github"
17
+ owner: string
18
+ repo: string
19
+ requestedRef?: string
20
+ }
21
+
22
+ export interface ParsedGitRef extends RegistryRefBase {
23
+ source: "git"
24
+ repoRoot: string
25
+ sourcePath: string
26
+ }
27
+
28
+ export type ParsedRegistryRef = ParsedNpmRef | ParsedGithubRef | ParsedGitRef
29
+
30
+ export interface ResolvedRegistryArtifact {
31
+ id: string
32
+ source: RegistrySource
33
+ ref: string
34
+ artifactUrl: string
35
+ resolvedVersion?: string
36
+ resolvedRevision?: string
37
+ }
38
+
39
+ export interface RegistryInstalledEntry {
40
+ id: string
41
+ source: RegistrySource
42
+ ref: string
43
+ resolvedVersion?: string
44
+ resolvedRevision?: string
45
+ artifactUrl: string
46
+ stashRoot: string
47
+ cacheDir: string
48
+ installedAt: string
49
+ }
50
+
51
+ export interface RegistryInstallResult extends RegistryInstalledEntry {
52
+ extractedDir: string
53
+ }
54
+
55
+ export interface RegistrySearchHit {
56
+ source: RegistrySource
57
+ id: string
58
+ title: string
59
+ description?: string
60
+ ref: string
61
+ homepage?: string
62
+ score?: number
63
+ metadata?: Record<string, string>
64
+ /** Whether this entry was manually reviewed and approved */
65
+ curated?: boolean
66
+ }
67
+
68
+ export interface RegistrySearchResponse {
69
+ query: string
70
+ hits: RegistrySearchHit[]
71
+ warnings: string[]
72
+ }
@@ -0,0 +1,63 @@
1
+ import { agentikitIndex } from "./indexer"
2
+ import fs from "node:fs"
3
+ import { resolveStashDir } from "./common"
4
+ import { loadConfig } from "./config"
5
+ import { upsertInstalledRegistryEntry, installRegistryRef } from "./registry-install"
6
+ import type { AddResponse } from "./stash-types"
7
+
8
+ export async function agentikitAdd(input: { ref: string }): Promise<AddResponse> {
9
+ const ref = input.ref.trim()
10
+ if (!ref) throw new Error("Install ref or local git directory is required.")
11
+
12
+ const stashDir = resolveStashDir()
13
+ const installed = await installRegistryRef(ref)
14
+ const replaced = loadConfig().registry?.installed.find((entry) => entry.id === installed.id)
15
+ const config = upsertInstalledRegistryEntry({
16
+ id: installed.id,
17
+ source: installed.source,
18
+ ref: installed.ref,
19
+ artifactUrl: installed.artifactUrl,
20
+ resolvedVersion: installed.resolvedVersion,
21
+ resolvedRevision: installed.resolvedRevision,
22
+ stashRoot: installed.stashRoot,
23
+ cacheDir: installed.cacheDir,
24
+ installedAt: installed.installedAt,
25
+ })
26
+
27
+ if (replaced && replaced.cacheDir !== installed.cacheDir) {
28
+ try {
29
+ fs.rmSync(replaced.cacheDir, { recursive: true, force: true })
30
+ } catch {
31
+ // Best-effort cleanup only.
32
+ }
33
+ }
34
+
35
+ const index = await agentikitIndex({ stashDir })
36
+
37
+ return {
38
+ stashDir,
39
+ ref,
40
+ installed: {
41
+ id: installed.id,
42
+ source: installed.source,
43
+ ref: installed.ref,
44
+ artifactUrl: installed.artifactUrl,
45
+ resolvedVersion: installed.resolvedVersion,
46
+ resolvedRevision: installed.resolvedRevision,
47
+ stashRoot: installed.stashRoot,
48
+ cacheDir: installed.cacheDir,
49
+ extractedDir: installed.extractedDir,
50
+ installedAt: installed.installedAt,
51
+ },
52
+ config: {
53
+ mountedStashDirs: config.mountedStashDirs,
54
+ installedRegistryCount: config.registry?.installed.length ?? 0,
55
+ },
56
+ index: {
57
+ mode: index.mode,
58
+ totalEntries: index.totalEntries,
59
+ directoriesScanned: index.directoriesScanned,
60
+ directoriesSkipped: index.directoriesSkipped,
61
+ },
62
+ }
63
+ }
@@ -0,0 +1,127 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+ import { TYPE_DIRS } from "./asset-spec"
4
+ import { parseAssetRef, makeAssetRef } from "./stash-ref"
5
+ import { resolveSourcesForOrigin } from "./origin-resolve"
6
+ import { resolveAssetPath } from "./stash-resolve"
7
+ import { resolveStashSources, findSourceForPath, type StashSource, type StashSourceKind } from "./stash-source"
8
+
9
+ export interface CloneOptions {
10
+ /** Source ref (e.g., npm:@scope/pkg//tool:deploy.sh) */
11
+ sourceRef: string
12
+ /** Optional new name for the cloned asset */
13
+ newName?: string
14
+ /** If true, overwrite existing asset in working stash */
15
+ force?: boolean
16
+ }
17
+
18
+ export interface CloneResponse {
19
+ source: {
20
+ path: string
21
+ sourceKind: StashSourceKind
22
+ registryId?: string
23
+ }
24
+ destination: {
25
+ path: string
26
+ ref: string
27
+ }
28
+ overwritten: boolean
29
+ }
30
+
31
+ export async function agentikitClone(options: CloneOptions): Promise<CloneResponse> {
32
+ const parsed = parseAssetRef(options.sourceRef)
33
+ const allSources = resolveStashSources()
34
+ const workingSource = allSources.find((s) => s.kind === "working")
35
+ if (!workingSource) {
36
+ throw new Error("No working stash configured. Run `akm init` first.")
37
+ }
38
+
39
+ const searchSources = resolveSourcesForOrigin(parsed.origin, allSources)
40
+
41
+ let sourcePath: string | undefined
42
+ let lastError: Error | undefined
43
+ for (const source of searchSources) {
44
+ try {
45
+ sourcePath = resolveAssetPath(source.path, parsed.type, parsed.name)
46
+ break
47
+ } catch (err) {
48
+ lastError = err instanceof Error ? err : new Error(String(err))
49
+ }
50
+ }
51
+ if (!sourcePath) {
52
+ throw lastError ?? new Error(`Source asset not found for ref: ${options.sourceRef}`)
53
+ }
54
+
55
+ const sourceSource = findSourceForPath(sourcePath, allSources)
56
+ const sourceKind = sourceSource?.kind ?? "working"
57
+
58
+ const destName = options.newName ?? parsed.name
59
+ const typeDir = TYPE_DIRS[parsed.type]
60
+ const workingDir = workingSource.path
61
+
62
+ // Guard against self-clone
63
+ if (parsed.type === "skill") {
64
+ const sourceSkillDir = path.resolve(path.dirname(sourcePath))
65
+ const destSkillDir = path.resolve(path.join(workingDir, typeDir, destName))
66
+ if (sourceSkillDir === destSkillDir) {
67
+ throw new Error(
68
+ `Source and destination are the same path. Use --name to provide a new name for the clone.`,
69
+ )
70
+ }
71
+ } else {
72
+ const resolvedSource = path.resolve(sourcePath)
73
+ const resolvedDest = path.resolve(path.join(workingDir, typeDir, destName))
74
+ if (resolvedSource === resolvedDest) {
75
+ throw new Error(
76
+ `Source and destination are the same path. Use --name to provide a new name for the clone.`,
77
+ )
78
+ }
79
+ }
80
+
81
+ let destPath: string
82
+ if (parsed.type === "skill") {
83
+ const sourceSkillDir = path.dirname(sourcePath)
84
+ const destSkillDir = path.join(workingDir, typeDir, destName)
85
+ const overwritten = fs.existsSync(destSkillDir)
86
+
87
+ if (overwritten && !options.force) {
88
+ throw new Error(
89
+ `Asset already exists in working stash: ${destSkillDir}. Use --force to overwrite.`,
90
+ )
91
+ }
92
+
93
+ if (overwritten) {
94
+ fs.rmSync(destSkillDir, { recursive: true, force: true })
95
+ }
96
+ fs.cpSync(sourceSkillDir, destSkillDir, { recursive: true })
97
+
98
+ destPath = path.join(destSkillDir, "SKILL.md")
99
+ const ref = makeAssetRef(parsed.type, destName, "local")
100
+
101
+ return {
102
+ source: { path: sourcePath, sourceKind, registryId: sourceSource?.registryId },
103
+ destination: { path: destPath, ref },
104
+ overwritten,
105
+ }
106
+ }
107
+
108
+ destPath = path.join(workingDir, typeDir, destName)
109
+ const overwritten = fs.existsSync(destPath)
110
+
111
+ if (overwritten && !options.force) {
112
+ throw new Error(
113
+ `Asset already exists in working stash: ${destPath}. Use --force to overwrite.`,
114
+ )
115
+ }
116
+
117
+ fs.mkdirSync(path.dirname(destPath), { recursive: true })
118
+ fs.copyFileSync(sourcePath, destPath)
119
+
120
+ const ref = makeAssetRef(parsed.type, destName, "local")
121
+
122
+ return {
123
+ source: { path: sourcePath, sourceKind, registryId: sourceSource?.registryId },
124
+ destination: { path: destPath, ref },
125
+ overwritten,
126
+ }
127
+ }
package/src/stash-ref.ts CHANGED
@@ -1,41 +1,99 @@
1
1
  import path from "node:path"
2
2
  import { type AgentikitAssetType, isAssetType } from "./common"
3
3
 
4
- export interface OpenRef {
4
+ // ── Types ───────────────────────────────────────────────────────────────────
5
+
6
+ export interface AssetRef {
5
7
  type: AgentikitAssetType
6
8
  name: string
9
+ /**
10
+ * Where to find this asset.
11
+ * - undefined: search all sources (working → mounted → installed)
12
+ * - "local": working stash only
13
+ * - registry ref: e.g. "npm:@scope/pkg", "owner/repo", "github:owner/repo#v1"
14
+ * - filesystem path: e.g. "/mnt/shared-stash"
15
+ */
16
+ origin?: string
7
17
  }
8
18
 
9
- export function parseOpenRef(ref: string): OpenRef {
10
- const separator = ref.indexOf(":")
11
- if (separator <= 0) {
12
- throw new Error("Invalid open ref. Expected format '<type>:<name>'.")
19
+ // ── Construction ────────────────────────────────────────────────────────────
20
+
21
+ /**
22
+ * Build a ref string from components.
23
+ *
24
+ * Examples:
25
+ * makeAssetRef("tool", "deploy.sh")
26
+ * → "tool:deploy.sh"
27
+ * makeAssetRef("tool", "deploy.sh", "npm:@scope/pkg")
28
+ * → "npm:@scope/pkg//tool:deploy.sh"
29
+ * makeAssetRef("skill", "code-review", "local")
30
+ * → "local//skill:code-review"
31
+ * makeAssetRef("tool", "db/migrate/run.sh", "owner/repo")
32
+ * → "owner/repo//tool:db/migrate/run.sh"
33
+ */
34
+ export function makeAssetRef(
35
+ type: AgentikitAssetType,
36
+ name: string,
37
+ origin?: string,
38
+ ): string {
39
+ validateName(name)
40
+ const normalized = normalizeName(name)
41
+ const asset = `${type}:${normalized}`
42
+ if (!origin) return asset
43
+ return `${origin}//${asset}`
44
+ }
45
+
46
+ // ── Parsing ─────────────────────────────────────────────────────────────────
47
+
48
+ /**
49
+ * Parse a ref string in the format `[origin//]type:name`.
50
+ */
51
+ export function parseAssetRef(ref: string): AssetRef {
52
+ const trimmed = ref.trim()
53
+ if (!trimmed) throw new Error("Empty ref.")
54
+
55
+ let origin: string | undefined
56
+ let body = trimmed
57
+
58
+ const boundary = trimmed.indexOf("//")
59
+ if (boundary >= 0) {
60
+ origin = trimmed.slice(0, boundary)
61
+ body = trimmed.slice(boundary + 2)
62
+ if (!origin) throw new Error("Empty origin in ref.")
13
63
  }
14
- const rawType = ref.slice(0, separator)
15
- const rawName = ref.slice(separator + 1)
16
- if (!isAssetType(rawType)) {
17
- throw new Error(`Invalid open ref type: "${rawType}".`)
64
+
65
+ const colon = body.indexOf(":")
66
+ if (colon <= 0) {
67
+ throw new Error(`Invalid ref "${trimmed}". Expected [origin//]type:name`)
18
68
  }
19
- let name: string
20
- try {
21
- name = decodeURIComponent(rawName)
22
- } catch {
23
- throw new Error("Invalid open ref encoding.")
69
+
70
+ const rawType = body.slice(0, colon)
71
+ const rawName = body.slice(colon + 1)
72
+
73
+ if (!isAssetType(rawType)) {
74
+ throw new Error(`Invalid asset type: "${rawType}".`)
24
75
  }
76
+
77
+ validateName(rawName)
78
+ const name = normalizeName(rawName)
79
+
80
+ return { type: rawType, name, origin: origin || undefined }
81
+ }
82
+
83
+ // ── Validation ──────────────────────────────────────────────────────────────
84
+
85
+ function validateName(name: string): void {
86
+ if (!name) throw new Error("Empty asset name.")
87
+ if (name.includes("\0")) throw new Error("Null byte in asset name.")
88
+ if (/^[A-Za-z]:/.test(name)) throw new Error("Windows drive path in asset name.")
89
+
25
90
  const normalized = path.posix.normalize(name.replace(/\\/g, "/"))
26
- if (
27
- !name
28
- || name.includes("\0")
29
- || /^[A-Za-z]:/.test(name)
30
- || path.posix.isAbsolute(normalized)
31
- || normalized === ".."
32
- || normalized.startsWith("../")
33
- ) {
34
- throw new Error("Invalid open ref name.")
91
+ if (path.posix.isAbsolute(normalized)) throw new Error("Absolute path in asset name.")
92
+ if (normalized === ".." || normalized.startsWith("../")) {
93
+ throw new Error("Path traversal in asset name.")
35
94
  }
36
- return { type: rawType, name: normalized }
37
95
  }
38
96
 
39
- export function makeOpenRef(type: AgentikitAssetType, name: string): string {
40
- return `${type}:${encodeURIComponent(name)}`
97
+ function normalizeName(name: string): string {
98
+ return path.posix.normalize(name.replace(/\\/g, "/"))
41
99
  }
@@ -0,0 +1,259 @@
1
+ import fs from "node:fs"
2
+ import { resolveStashDir } from "./common"
3
+ import { loadConfig } from "./config"
4
+ import { agentikitIndex } from "./indexer"
5
+ import {
6
+ installRegistryRef,
7
+ removeInstalledRegistryEntry,
8
+ upsertInstalledRegistryEntry,
9
+ } from "./registry-install"
10
+ import { parseRegistryRef } from "./registry-resolve"
11
+ import type { RegistryInstalledEntry } from "./registry-types"
12
+ import type {
13
+ ListResponse,
14
+ RegistryInstallStatus,
15
+ RemoveResponse,
16
+ ReinstallResponse,
17
+ UpdateResponse,
18
+ } from "./stash-types"
19
+
20
+ export async function agentikitList(input?: { stashDir?: string }): Promise<ListResponse> {
21
+ const stashDir = input?.stashDir ?? resolveStashDir()
22
+ const config = loadConfig()
23
+ const installed = config.registry?.installed ?? []
24
+
25
+ return {
26
+ stashDir,
27
+ installed: installed.map((entry) => ({
28
+ ...entry,
29
+ status: {
30
+ cacheDirExists: directoryExists(entry.cacheDir),
31
+ stashRootExists: directoryExists(entry.stashRoot),
32
+ },
33
+ })),
34
+ totalInstalled: installed.length,
35
+ }
36
+ }
37
+
38
+ export async function agentikitRemove(input: { target: string; stashDir?: string }): Promise<RemoveResponse> {
39
+ const target = input.target.trim()
40
+ if (!target) throw new Error("Target is required.")
41
+
42
+ const stashDir = input.stashDir ?? resolveStashDir()
43
+ const config = loadConfig()
44
+ const installed = config.registry?.installed ?? []
45
+ const entry = resolveInstalledTarget(installed, target)
46
+
47
+ const updatedConfig = removeInstalledRegistryEntry(entry.id)
48
+ cleanupDirectoryBestEffort(entry.cacheDir)
49
+ const index = await agentikitIndex({ stashDir })
50
+
51
+ return {
52
+ stashDir,
53
+ target,
54
+ removed: {
55
+ id: entry.id,
56
+ source: entry.source,
57
+ ref: entry.ref,
58
+ cacheDir: entry.cacheDir,
59
+ stashRoot: entry.stashRoot,
60
+ },
61
+ config: {
62
+ mountedStashDirs: updatedConfig.mountedStashDirs,
63
+ installedRegistryCount: updatedConfig.registry?.installed.length ?? 0,
64
+ },
65
+ index: {
66
+ mode: index.mode,
67
+ totalEntries: index.totalEntries,
68
+ directoriesScanned: index.directoriesScanned,
69
+ directoriesSkipped: index.directoriesSkipped,
70
+ },
71
+ }
72
+ }
73
+
74
+ export async function agentikitReinstall(input?: {
75
+ target?: string
76
+ all?: boolean
77
+ stashDir?: string
78
+ }): Promise<ReinstallResponse> {
79
+ const stashDir = input?.stashDir ?? resolveStashDir()
80
+ const target = input?.target?.trim()
81
+ const all = input?.all === true
82
+ const installedEntries = loadConfig().registry?.installed ?? []
83
+ const selectedEntries = selectTargets(installedEntries, target, all)
84
+
85
+ const processed: ReinstallResponse["processed"] = []
86
+ for (const entry of selectedEntries) {
87
+ const installed = await installRegistryRef(entry.ref)
88
+ upsertInstalledRegistryEntry(toInstalledEntry(installed))
89
+ if (entry.cacheDir !== installed.cacheDir) {
90
+ cleanupDirectoryBestEffort(entry.cacheDir)
91
+ }
92
+
93
+ processed.push({
94
+ id: entry.id,
95
+ source: entry.source,
96
+ ref: entry.ref,
97
+ previousCacheDir: entry.cacheDir,
98
+ installed: toInstallStatus(installed),
99
+ })
100
+ }
101
+
102
+ const index = await agentikitIndex({ stashDir })
103
+ const config = loadConfig()
104
+
105
+ return {
106
+ stashDir,
107
+ target,
108
+ all,
109
+ processed,
110
+ config: {
111
+ mountedStashDirs: config.mountedStashDirs,
112
+ installedRegistryCount: config.registry?.installed.length ?? 0,
113
+ },
114
+ index: {
115
+ mode: index.mode,
116
+ totalEntries: index.totalEntries,
117
+ directoriesScanned: index.directoriesScanned,
118
+ directoriesSkipped: index.directoriesSkipped,
119
+ },
120
+ }
121
+ }
122
+
123
+ export async function agentikitUpdate(input?: {
124
+ target?: string
125
+ all?: boolean
126
+ stashDir?: string
127
+ }): Promise<UpdateResponse> {
128
+ const stashDir = input?.stashDir ?? resolveStashDir()
129
+ const target = input?.target?.trim()
130
+ const all = input?.all === true
131
+ const installedEntries = loadConfig().registry?.installed ?? []
132
+ const selectedEntries = selectTargets(installedEntries, target, all)
133
+
134
+ const processed: UpdateResponse["processed"] = []
135
+ for (const entry of selectedEntries) {
136
+ const installed = await installRegistryRef(entry.ref)
137
+ upsertInstalledRegistryEntry(toInstalledEntry(installed))
138
+ if (entry.cacheDir !== installed.cacheDir) {
139
+ cleanupDirectoryBestEffort(entry.cacheDir)
140
+ }
141
+
142
+ const versionChanged = (entry.resolvedVersion ?? "") !== (installed.resolvedVersion ?? "")
143
+ const revisionChanged = (entry.resolvedRevision ?? "") !== (installed.resolvedRevision ?? "")
144
+
145
+ processed.push({
146
+ id: entry.id,
147
+ source: entry.source,
148
+ ref: entry.ref,
149
+ previous: {
150
+ resolvedVersion: entry.resolvedVersion,
151
+ resolvedRevision: entry.resolvedRevision,
152
+ cacheDir: entry.cacheDir,
153
+ },
154
+ installed: toInstallStatus(installed),
155
+ changed: {
156
+ version: versionChanged,
157
+ revision: revisionChanged,
158
+ any: versionChanged || revisionChanged,
159
+ },
160
+ })
161
+ }
162
+
163
+ const index = await agentikitIndex({ stashDir })
164
+ const config = loadConfig()
165
+
166
+ return {
167
+ stashDir,
168
+ target,
169
+ all,
170
+ processed,
171
+ config: {
172
+ mountedStashDirs: config.mountedStashDirs,
173
+ installedRegistryCount: config.registry?.installed.length ?? 0,
174
+ },
175
+ index: {
176
+ mode: index.mode,
177
+ totalEntries: index.totalEntries,
178
+ directoriesScanned: index.directoriesScanned,
179
+ directoriesSkipped: index.directoriesSkipped,
180
+ },
181
+ }
182
+ }
183
+
184
+ function selectTargets(installed: RegistryInstalledEntry[], target: string | undefined, all: boolean): RegistryInstalledEntry[] {
185
+ if (all && target) {
186
+ throw new Error("Specify either <target> or --all, not both.")
187
+ }
188
+ if (all) return installed
189
+ if (!target) {
190
+ throw new Error("Either <target> or --all is required.")
191
+ }
192
+ return [resolveInstalledTarget(installed, target)]
193
+ }
194
+
195
+ function resolveInstalledTarget(installed: RegistryInstalledEntry[], target: string): RegistryInstalledEntry {
196
+ const byId = installed.find((entry) => entry.id === target)
197
+ if (byId) return byId
198
+
199
+ const byRef = installed.find((entry) => entry.ref === target)
200
+ if (byRef) return byRef
201
+
202
+ let parsedId: string | undefined
203
+ try {
204
+ parsedId = parseRegistryRef(target).id
205
+ } catch {
206
+ parsedId = undefined
207
+ }
208
+ if (parsedId) {
209
+ const byParsedId = installed.find((entry) => entry.id === parsedId)
210
+ if (byParsedId) return byParsedId
211
+ }
212
+
213
+ throw new Error(`No installed registry entry matched target: ${target}`)
214
+ }
215
+
216
+ function toInstalledEntry(status: RegistryInstallStatus): RegistryInstalledEntry {
217
+ return {
218
+ id: status.id,
219
+ source: status.source,
220
+ ref: status.ref,
221
+ artifactUrl: status.artifactUrl,
222
+ resolvedVersion: status.resolvedVersion,
223
+ resolvedRevision: status.resolvedRevision,
224
+ stashRoot: status.stashRoot,
225
+ cacheDir: status.cacheDir,
226
+ installedAt: status.installedAt,
227
+ }
228
+ }
229
+
230
+ function toInstallStatus(status: RegistryInstallStatus): RegistryInstallStatus {
231
+ return {
232
+ id: status.id,
233
+ source: status.source,
234
+ ref: status.ref,
235
+ artifactUrl: status.artifactUrl,
236
+ resolvedVersion: status.resolvedVersion,
237
+ resolvedRevision: status.resolvedRevision,
238
+ stashRoot: status.stashRoot,
239
+ cacheDir: status.cacheDir,
240
+ extractedDir: status.extractedDir,
241
+ installedAt: status.installedAt,
242
+ }
243
+ }
244
+
245
+ function cleanupDirectoryBestEffort(target: string): void {
246
+ try {
247
+ fs.rmSync(target, { recursive: true, force: true })
248
+ } catch {
249
+ // Best-effort cleanup only.
250
+ }
251
+ }
252
+
253
+ function directoryExists(target: string): boolean {
254
+ try {
255
+ return fs.statSync(target).isDirectory()
256
+ } catch {
257
+ return false
258
+ }
259
+ }
@@ -22,6 +22,9 @@ export function resolveAssetPath(stashDir: string, type: AgentikitAssetType, nam
22
22
  if (type === "tool") {
23
23
  throw new Error("Tool ref must resolve to a .sh, .ts, .js, .ps1, .cmd, or .bat file.")
24
24
  }
25
+ if (type === "script") {
26
+ throw new Error("Script ref must resolve to a file with a supported script extension. Refer to the Agentikit documentation for the complete list of supported script extensions.");
27
+ }
25
28
  throw new Error(`Stash asset not found for ref: ${type}:${name}`)
26
29
  }
27
30
  return realTarget