agentikit 0.0.12 → 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 +186 -100
- 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/similarity.js +0 -211
- 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/dist/paths.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized path resolution for all agentikit directories.
|
|
3
|
+
*
|
|
4
|
+
* Provides platform-aware paths for config, cache, and stash directories,
|
|
5
|
+
* following XDG Base Directory conventions on Unix and standard locations
|
|
6
|
+
* on Windows.
|
|
7
|
+
*/
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
const IS_WINDOWS = process.platform === "win32";
|
|
10
|
+
// ── Config directory ─────────────────────────────────────────────────────────
|
|
11
|
+
export function getConfigDir(env = process.env, platform = process.platform) {
|
|
12
|
+
if (platform === "win32") {
|
|
13
|
+
const appData = env.APPDATA?.trim();
|
|
14
|
+
if (appData)
|
|
15
|
+
return path.join(appData, "agentikit");
|
|
16
|
+
const userProfile = env.USERPROFILE?.trim();
|
|
17
|
+
if (!userProfile) {
|
|
18
|
+
throw new Error("Unable to determine config directory. Set APPDATA or USERPROFILE.");
|
|
19
|
+
}
|
|
20
|
+
return path.join(userProfile, "AppData", "Roaming", "agentikit");
|
|
21
|
+
}
|
|
22
|
+
const xdgConfigHome = env.XDG_CONFIG_HOME?.trim();
|
|
23
|
+
if (xdgConfigHome)
|
|
24
|
+
return path.join(xdgConfigHome, "agentikit");
|
|
25
|
+
const home = env.HOME?.trim();
|
|
26
|
+
if (!home) {
|
|
27
|
+
throw new Error("Unable to determine config directory. Set XDG_CONFIG_HOME or HOME.");
|
|
28
|
+
}
|
|
29
|
+
return path.join(home, ".config", "agentikit");
|
|
30
|
+
}
|
|
31
|
+
export function getConfigPath() {
|
|
32
|
+
return path.join(getConfigDir(), "config.json");
|
|
33
|
+
}
|
|
34
|
+
// ── Cache directory ──────────────────────────────────────────────────────────
|
|
35
|
+
export function getCacheDir() {
|
|
36
|
+
if (IS_WINDOWS) {
|
|
37
|
+
const localAppData = process.env.LOCALAPPDATA?.trim();
|
|
38
|
+
if (localAppData)
|
|
39
|
+
return path.join(localAppData, "agentikit");
|
|
40
|
+
const userProfile = process.env.USERPROFILE?.trim();
|
|
41
|
+
if (userProfile)
|
|
42
|
+
return path.join(userProfile, "AppData", "Local", "agentikit");
|
|
43
|
+
const appData = process.env.APPDATA?.trim();
|
|
44
|
+
if (!appData) {
|
|
45
|
+
throw new Error("Unable to determine cache directory. Set LOCALAPPDATA, USERPROFILE, or APPDATA.");
|
|
46
|
+
}
|
|
47
|
+
return path.join(appData, "..", "Local", "agentikit");
|
|
48
|
+
}
|
|
49
|
+
const xdgCacheHome = process.env.XDG_CACHE_HOME?.trim();
|
|
50
|
+
if (xdgCacheHome)
|
|
51
|
+
return path.join(xdgCacheHome, "agentikit");
|
|
52
|
+
const home = process.env.HOME?.trim();
|
|
53
|
+
if (!home)
|
|
54
|
+
return path.join("/tmp", "agentikit-cache");
|
|
55
|
+
return path.join(home, ".cache", "agentikit");
|
|
56
|
+
}
|
|
57
|
+
export function getDbPath() {
|
|
58
|
+
return path.join(getCacheDir(), "index.db");
|
|
59
|
+
}
|
|
60
|
+
export function getRegistryCacheDir() {
|
|
61
|
+
return path.join(getCacheDir(), "registry");
|
|
62
|
+
}
|
|
63
|
+
export function getRegistryIndexCacheDir() {
|
|
64
|
+
return path.join(getCacheDir(), "registry-index");
|
|
65
|
+
}
|
|
66
|
+
export function getBinDir() {
|
|
67
|
+
return path.join(getCacheDir(), "bin");
|
|
68
|
+
}
|
|
69
|
+
// ── Default stash directory ──────────────────────────────────────────────────
|
|
70
|
+
export function getDefaultStashDir() {
|
|
71
|
+
if (IS_WINDOWS) {
|
|
72
|
+
const userProfile = process.env.USERPROFILE?.trim();
|
|
73
|
+
if (userProfile)
|
|
74
|
+
return path.join(userProfile, "Documents", "agentikit");
|
|
75
|
+
return path.join("C:\\", "agentikit");
|
|
76
|
+
}
|
|
77
|
+
const home = process.env.HOME?.trim();
|
|
78
|
+
if (!home) {
|
|
79
|
+
throw new Error("Unable to determine default stash directory. Set HOME.");
|
|
80
|
+
}
|
|
81
|
+
return path.join(home, "agentikit");
|
|
82
|
+
}
|
|
@@ -1,23 +1,55 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { spawnSync } from "node:child_process";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
4
|
-
import {
|
|
5
|
+
import { fetchWithRetry, isWithin, TYPE_DIRS } from "./common";
|
|
5
6
|
import { loadConfig, saveConfig } from "./config";
|
|
6
7
|
import { parseRegistryRef, resolveRegistryArtifact } from "./registry-resolve";
|
|
8
|
+
import { getRegistryCacheDir as _getRegistryCacheDir } from "./paths";
|
|
7
9
|
const REGISTRY_STASH_DIR_NAMES = new Set(Object.values(TYPE_DIRS));
|
|
8
10
|
export async function installRegistryRef(ref, options) {
|
|
9
11
|
const parsed = parseRegistryRef(ref);
|
|
12
|
+
if (parsed.source === "local") {
|
|
13
|
+
return installLocalRegistryRef(parsed, options);
|
|
14
|
+
}
|
|
10
15
|
if (parsed.source === "git") {
|
|
11
16
|
return installGitRegistryRef(parsed, options);
|
|
12
17
|
}
|
|
13
18
|
const resolved = await resolveRegistryArtifact(parsed);
|
|
14
19
|
const installedAt = (options?.now ?? new Date()).toISOString();
|
|
15
20
|
const cacheRootDir = options?.cacheRootDir ?? getRegistryCacheRootDir();
|
|
16
|
-
const cacheDir = buildInstallCacheDir(cacheRootDir, resolved.source, resolved.id);
|
|
21
|
+
const cacheDir = buildInstallCacheDir(cacheRootDir, resolved.source, resolved.id, resolved.resolvedVersion ?? resolved.resolvedRevision);
|
|
17
22
|
const archivePath = path.join(cacheDir, "artifact.tar.gz");
|
|
18
23
|
const extractedDir = path.join(cacheDir, "extracted");
|
|
24
|
+
// Check for cache hit: if extracted dir already exists and has a valid stash root, reuse it
|
|
25
|
+
if (isDirectory(extractedDir)) {
|
|
26
|
+
try {
|
|
27
|
+
const cachedStashRoot = detectStashRoot(extractedDir);
|
|
28
|
+
if (cachedStashRoot) {
|
|
29
|
+
const integrity = fs.existsSync(archivePath) ? await computeFileHash(archivePath) : undefined;
|
|
30
|
+
return {
|
|
31
|
+
id: resolved.id,
|
|
32
|
+
source: resolved.source,
|
|
33
|
+
ref: resolved.ref,
|
|
34
|
+
artifactUrl: resolved.artifactUrl,
|
|
35
|
+
resolvedVersion: resolved.resolvedVersion,
|
|
36
|
+
resolvedRevision: resolved.resolvedRevision,
|
|
37
|
+
installedAt,
|
|
38
|
+
cacheDir,
|
|
39
|
+
extractedDir,
|
|
40
|
+
stashRoot: cachedStashRoot,
|
|
41
|
+
integrity,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Cache invalid, re-download
|
|
47
|
+
}
|
|
48
|
+
}
|
|
19
49
|
fs.mkdirSync(cacheDir, { recursive: true });
|
|
20
50
|
await downloadArchive(resolved.artifactUrl, archivePath);
|
|
51
|
+
verifyArchiveIntegrity(archivePath, resolved.resolvedRevision, resolved.source);
|
|
52
|
+
const integrity = await computeFileHash(archivePath);
|
|
21
53
|
extractTarGzSecure(archivePath, extractedDir);
|
|
22
54
|
const provisionalKitRoot = detectStashRoot(extractedDir);
|
|
23
55
|
const installRoot = applyAgentikitIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
|
|
@@ -33,9 +65,10 @@ export async function installRegistryRef(ref, options) {
|
|
|
33
65
|
cacheDir,
|
|
34
66
|
extractedDir,
|
|
35
67
|
stashRoot,
|
|
68
|
+
integrity,
|
|
36
69
|
};
|
|
37
70
|
}
|
|
38
|
-
async function
|
|
71
|
+
async function installLocalRegistryRef(parsed, options) {
|
|
39
72
|
const resolved = await resolveRegistryArtifact(parsed);
|
|
40
73
|
const installedAt = (options?.now ?? new Date()).toISOString();
|
|
41
74
|
const cacheRootDir = options?.cacheRootDir ?? getRegistryCacheRootDir();
|
|
@@ -44,7 +77,8 @@ async function installGitRegistryRef(parsed, options) {
|
|
|
44
77
|
fs.mkdirSync(cacheDir, { recursive: true });
|
|
45
78
|
fs.rmSync(extractedDir, { recursive: true, force: true });
|
|
46
79
|
fs.mkdirSync(extractedDir, { recursive: true });
|
|
47
|
-
const
|
|
80
|
+
const searchRoot = parsed.repoRoot ?? parsed.sourcePath;
|
|
81
|
+
const includeConfig = findNearestAgentikitIncludeConfig(parsed.sourcePath, searchRoot);
|
|
48
82
|
if (includeConfig) {
|
|
49
83
|
copyIncludedPaths(includeConfig.baseDir, includeConfig.include, extractedDir);
|
|
50
84
|
}
|
|
@@ -65,6 +99,70 @@ async function installGitRegistryRef(parsed, options) {
|
|
|
65
99
|
stashRoot,
|
|
66
100
|
};
|
|
67
101
|
}
|
|
102
|
+
async function installGitRegistryRef(parsed, options) {
|
|
103
|
+
const resolved = await resolveRegistryArtifact(parsed);
|
|
104
|
+
const installedAt = (options?.now ?? new Date()).toISOString();
|
|
105
|
+
const cacheRootDir = options?.cacheRootDir ?? getRegistryCacheRootDir();
|
|
106
|
+
const cacheDir = buildInstallCacheDir(cacheRootDir, parsed.source, parsed.id, resolved.resolvedRevision);
|
|
107
|
+
const cloneDir = path.join(cacheDir, "clone");
|
|
108
|
+
const extractedDir = path.join(cacheDir, "extracted");
|
|
109
|
+
// Check for cache hit
|
|
110
|
+
if (isDirectory(extractedDir)) {
|
|
111
|
+
try {
|
|
112
|
+
const provisionalKitRoot = detectStashRoot(extractedDir);
|
|
113
|
+
const installRoot = applyAgentikitIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
|
|
114
|
+
const stashRoot = detectStashRoot(installRoot);
|
|
115
|
+
if (stashRoot) {
|
|
116
|
+
return {
|
|
117
|
+
id: resolved.id,
|
|
118
|
+
source: resolved.source,
|
|
119
|
+
ref: resolved.ref,
|
|
120
|
+
artifactUrl: resolved.artifactUrl,
|
|
121
|
+
resolvedVersion: resolved.resolvedVersion,
|
|
122
|
+
resolvedRevision: resolved.resolvedRevision,
|
|
123
|
+
installedAt,
|
|
124
|
+
cacheDir,
|
|
125
|
+
extractedDir,
|
|
126
|
+
stashRoot,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// Cache invalid, re-clone
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
135
|
+
const cloneArgs = ["clone", "--depth", "1"];
|
|
136
|
+
if (parsed.requestedRef) {
|
|
137
|
+
cloneArgs.push("--branch", parsed.requestedRef);
|
|
138
|
+
}
|
|
139
|
+
cloneArgs.push(parsed.url, cloneDir);
|
|
140
|
+
const cloneResult = spawnSync("git", cloneArgs, { encoding: "utf8", timeout: 120_000 });
|
|
141
|
+
if (cloneResult.status !== 0) {
|
|
142
|
+
const err = cloneResult.stderr?.trim() || cloneResult.error?.message || "unknown error";
|
|
143
|
+
throw new Error(`Failed to clone ${parsed.url}: ${err}`);
|
|
144
|
+
}
|
|
145
|
+
// Copy contents to extracted dir without .git
|
|
146
|
+
fs.mkdirSync(extractedDir, { recursive: true });
|
|
147
|
+
copyDirectoryContents(cloneDir, extractedDir);
|
|
148
|
+
// Clean up the clone dir
|
|
149
|
+
fs.rmSync(cloneDir, { recursive: true, force: true });
|
|
150
|
+
const provisionalKitRoot = detectStashRoot(extractedDir);
|
|
151
|
+
const installRoot = applyAgentikitIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
|
|
152
|
+
const stashRoot = detectStashRoot(installRoot);
|
|
153
|
+
return {
|
|
154
|
+
id: resolved.id,
|
|
155
|
+
source: resolved.source,
|
|
156
|
+
ref: resolved.ref,
|
|
157
|
+
artifactUrl: resolved.artifactUrl,
|
|
158
|
+
resolvedVersion: resolved.resolvedVersion,
|
|
159
|
+
resolvedRevision: resolved.resolvedRevision,
|
|
160
|
+
installedAt,
|
|
161
|
+
cacheDir,
|
|
162
|
+
extractedDir,
|
|
163
|
+
stashRoot,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
68
166
|
export function upsertInstalledRegistryEntry(entry) {
|
|
69
167
|
const current = loadConfig();
|
|
70
168
|
const currentInstalled = current.registry?.installed ?? [];
|
|
@@ -89,15 +187,7 @@ export function removeInstalledRegistryEntry(id) {
|
|
|
89
187
|
return nextConfig;
|
|
90
188
|
}
|
|
91
189
|
export function getRegistryCacheRootDir() {
|
|
92
|
-
|
|
93
|
-
if (xdgCache) {
|
|
94
|
-
return path.join(path.resolve(xdgCache), "agentikit", "registry");
|
|
95
|
-
}
|
|
96
|
-
const home = process.env.HOME?.trim();
|
|
97
|
-
if (!home) {
|
|
98
|
-
throw new Error("Unable to determine cache directory. Set XDG_CACHE_HOME or HOME.");
|
|
99
|
-
}
|
|
100
|
-
return path.join(path.resolve(home), ".cache", "agentikit", "registry");
|
|
190
|
+
return _getRegistryCacheDir();
|
|
101
191
|
}
|
|
102
192
|
export function detectStashRoot(extractedDir) {
|
|
103
193
|
const root = path.resolve(extractedDir);
|
|
@@ -117,10 +207,12 @@ export function detectStashRoot(extractedDir) {
|
|
|
117
207
|
return shallowest;
|
|
118
208
|
return root;
|
|
119
209
|
}
|
|
120
|
-
function buildInstallCacheDir(cacheRootDir, source, id) {
|
|
210
|
+
function buildInstallCacheDir(cacheRootDir, source, id, version) {
|
|
121
211
|
const slug = `${source}-${id.replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "")}`;
|
|
122
|
-
const
|
|
123
|
-
|
|
212
|
+
const versionSlug = source === "local"
|
|
213
|
+
? `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`
|
|
214
|
+
: (version?.replace(/[^a-zA-Z0-9_.-]+/g, "-") ?? `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`);
|
|
215
|
+
return path.join(cacheRootDir, slug || source, versionSlug);
|
|
124
216
|
}
|
|
125
217
|
function applyAgentikitIncludeConfig(sourceRoot, cacheDir, searchRoot = sourceRoot) {
|
|
126
218
|
const includeConfig = findNearestAgentikitIncludeConfig(sourceRoot, searchRoot);
|
|
@@ -133,7 +225,7 @@ function applyAgentikitIncludeConfig(sourceRoot, cacheDir, searchRoot = sourceRo
|
|
|
133
225
|
return selectedDir;
|
|
134
226
|
}
|
|
135
227
|
async function downloadArchive(url, destination) {
|
|
136
|
-
const response = await
|
|
228
|
+
const response = await fetchWithRetry(url, undefined, { timeout: 120_000 });
|
|
137
229
|
if (!response.ok) {
|
|
138
230
|
throw new Error(`Failed to download archive (${response.status}) from ${url}`);
|
|
139
231
|
}
|
|
@@ -149,6 +241,37 @@ async function downloadArchive(url, destination) {
|
|
|
149
241
|
fs.writeFileSync(destination, Buffer.from(arrayBuffer));
|
|
150
242
|
}
|
|
151
243
|
}
|
|
244
|
+
export function verifyArchiveIntegrity(archivePath, expected, source) {
|
|
245
|
+
if (!expected)
|
|
246
|
+
return;
|
|
247
|
+
// For GitHub and git sources, resolvedRevision is a commit SHA, not a content hash.
|
|
248
|
+
// Content integrity cannot be verified from a commit hash, so skip verification.
|
|
249
|
+
if (source === "github" || source === "git")
|
|
250
|
+
return;
|
|
251
|
+
const fileBuffer = fs.readFileSync(archivePath);
|
|
252
|
+
// SRI hash format: sha256-<base64> or sha512-<base64>
|
|
253
|
+
if (expected.startsWith("sha256-") || expected.startsWith("sha512-")) {
|
|
254
|
+
const dashIndex = expected.indexOf("-");
|
|
255
|
+
const algorithm = expected.slice(0, dashIndex);
|
|
256
|
+
const expectedBase64 = expected.slice(dashIndex + 1);
|
|
257
|
+
const actualBase64 = createHash(algorithm).update(fileBuffer).digest("base64");
|
|
258
|
+
if (actualBase64 !== expectedBase64) {
|
|
259
|
+
fs.unlinkSync(archivePath);
|
|
260
|
+
throw new Error(`Integrity check failed for ${archivePath}: expected ${algorithm} digest ${expectedBase64}, got ${actualBase64}`);
|
|
261
|
+
}
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
// Hex shasum (SHA-1 from npm)
|
|
265
|
+
if (/^[0-9a-f]{40}$/i.test(expected)) {
|
|
266
|
+
const actualHex = createHash("sha1").update(fileBuffer).digest("hex");
|
|
267
|
+
if (actualHex.toLowerCase() !== expected.toLowerCase()) {
|
|
268
|
+
fs.unlinkSync(archivePath);
|
|
269
|
+
throw new Error(`Integrity check failed for ${archivePath}: expected sha1 ${expected}, got ${actualHex}`);
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
// Unrecognized format — skip verification
|
|
274
|
+
}
|
|
152
275
|
function extractTarGzSecure(archivePath, destinationDir) {
|
|
153
276
|
const listResult = spawnSync("tar", ["tzf", archivePath], { encoding: "utf8" });
|
|
154
277
|
if (listResult.status !== 0) {
|
|
@@ -313,3 +436,8 @@ function normalizeInstalledEntry(entry) {
|
|
|
313
436
|
cacheDir: path.resolve(entry.cacheDir),
|
|
314
437
|
};
|
|
315
438
|
}
|
|
439
|
+
async function computeFileHash(filePath) {
|
|
440
|
+
const data = fs.readFileSync(filePath);
|
|
441
|
+
const hash = createHash("sha256").update(data).digest("hex");
|
|
442
|
+
return `sha256:${hash}`;
|
|
443
|
+
}
|
|
@@ -2,7 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
|
-
import {
|
|
5
|
+
import { fetchWithRetry } from "./common";
|
|
6
6
|
import { GITHUB_API_BASE, githubHeaders, asRecord, asString } from "./github";
|
|
7
7
|
export function parseRegistryRef(rawRef) {
|
|
8
8
|
const ref = rawRef.trim();
|
|
@@ -14,12 +14,18 @@ export function parseRegistryRef(rawRef) {
|
|
|
14
14
|
if (ref.startsWith("github:")) {
|
|
15
15
|
return parseGithubShorthand(ref.slice(7), ref);
|
|
16
16
|
}
|
|
17
|
+
if (ref.startsWith("git+")) {
|
|
18
|
+
return parseGitUrl(stripGitTransport(ref), ref);
|
|
19
|
+
}
|
|
20
|
+
if (ref.startsWith("file:")) {
|
|
21
|
+
return tryParseLocalRef(fileUriToPath(ref), true);
|
|
22
|
+
}
|
|
17
23
|
if (ref.startsWith("http://") || ref.startsWith("https://")) {
|
|
18
|
-
return
|
|
24
|
+
return parseRemoteUrl(ref);
|
|
19
25
|
}
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
22
|
-
return
|
|
26
|
+
const localRef = tryParseLocalRef(ref, isPathLikeRef(ref));
|
|
27
|
+
if (localRef) {
|
|
28
|
+
return localRef;
|
|
23
29
|
}
|
|
24
30
|
if (ref.startsWith("@") || !looksLikeGithubOwnerRepo(ref)) {
|
|
25
31
|
return parseNpmRef(ref, ref);
|
|
@@ -30,6 +36,9 @@ export async function resolveRegistryArtifact(parsed) {
|
|
|
30
36
|
if (parsed.source === "npm") {
|
|
31
37
|
return resolveNpmArtifact(parsed);
|
|
32
38
|
}
|
|
39
|
+
if (parsed.source === "local") {
|
|
40
|
+
return resolveLocalArtifact(parsed);
|
|
41
|
+
}
|
|
33
42
|
if (parsed.source === "git") {
|
|
34
43
|
return resolveGitArtifact(parsed);
|
|
35
44
|
}
|
|
@@ -69,7 +78,7 @@ function parseGithubShorthand(input, originalRef) {
|
|
|
69
78
|
requestedRef,
|
|
70
79
|
};
|
|
71
80
|
}
|
|
72
|
-
function
|
|
81
|
+
function parseRemoteUrl(rawUrl) {
|
|
73
82
|
let url;
|
|
74
83
|
try {
|
|
75
84
|
url = new URL(rawUrl);
|
|
@@ -77,9 +86,12 @@ function parseGithubUrl(rawUrl) {
|
|
|
77
86
|
catch {
|
|
78
87
|
throw new Error("Invalid registry URL.");
|
|
79
88
|
}
|
|
80
|
-
if (url.hostname
|
|
81
|
-
|
|
89
|
+
if (url.hostname === "github.com") {
|
|
90
|
+
return parseGithubUrl(url, rawUrl);
|
|
82
91
|
}
|
|
92
|
+
return parseGitUrl(rawUrl, rawUrl);
|
|
93
|
+
}
|
|
94
|
+
function parseGithubUrl(url, rawUrl) {
|
|
83
95
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
84
96
|
if (segments.length < 2) {
|
|
85
97
|
throw new Error("Invalid GitHub URL. Expected https://github.com/owner/repo.");
|
|
@@ -96,7 +108,21 @@ function parseGithubUrl(rawUrl) {
|
|
|
96
108
|
requestedRef,
|
|
97
109
|
};
|
|
98
110
|
}
|
|
99
|
-
function
|
|
111
|
+
function parseGitUrl(input, originalRef) {
|
|
112
|
+
const [urlPart, requestedRef] = splitRefSuffix(input.trim());
|
|
113
|
+
if (!urlPart)
|
|
114
|
+
throw new Error("Invalid git ref. A URL is required.");
|
|
115
|
+
// Normalize the URL for the id (strip .git suffix, fragment)
|
|
116
|
+
const normalized = urlPart.replace(/\.git$/i, "");
|
|
117
|
+
return {
|
|
118
|
+
source: "git",
|
|
119
|
+
ref: originalRef,
|
|
120
|
+
id: `git:${normalized}`,
|
|
121
|
+
url: urlPart,
|
|
122
|
+
requestedRef,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function tryParseLocalRef(rawRef, explicitPath) {
|
|
100
126
|
if (!explicitPath) {
|
|
101
127
|
return undefined;
|
|
102
128
|
}
|
|
@@ -112,13 +138,10 @@ function tryParseLocalGitRef(rawRef, explicitPath) {
|
|
|
112
138
|
throw new Error("Local add path must be a directory, but the provided path is not one.");
|
|
113
139
|
}
|
|
114
140
|
const repoRoot = findGitRepoRoot(resolvedPath);
|
|
115
|
-
if (!repoRoot) {
|
|
116
|
-
throw new Error("Local add path must be inside a git repository.");
|
|
117
|
-
}
|
|
118
141
|
return {
|
|
119
|
-
source: "
|
|
142
|
+
source: "local",
|
|
120
143
|
ref: rawRef,
|
|
121
|
-
id: `
|
|
144
|
+
id: `local:${encodeURIComponent(resolvedPath)}`,
|
|
122
145
|
repoRoot,
|
|
123
146
|
sourcePath: resolvedPath,
|
|
124
147
|
};
|
|
@@ -145,7 +168,13 @@ async function resolveNpmArtifact(parsed) {
|
|
|
145
168
|
resolvedVersion = requested;
|
|
146
169
|
}
|
|
147
170
|
else {
|
|
171
|
+
// Try dist-tag first
|
|
148
172
|
resolvedVersion = asString(distTags[requested]);
|
|
173
|
+
// If not a dist-tag, try semver range resolution
|
|
174
|
+
if (!resolvedVersion && isSemverRange(requested)) {
|
|
175
|
+
const versionKeys = Object.keys(versions).filter(isExactSemver);
|
|
176
|
+
resolvedVersion = maxSatisfying(versionKeys, requested);
|
|
177
|
+
}
|
|
149
178
|
}
|
|
150
179
|
if (!resolvedVersion || !(resolvedVersion in versions)) {
|
|
151
180
|
throw new Error(`Unable to resolve npm ref \"${parsed.ref}\".`);
|
|
@@ -210,13 +239,30 @@ async function resolveGithubArtifact(parsed) {
|
|
|
210
239
|
};
|
|
211
240
|
}
|
|
212
241
|
async function resolveGitArtifact(parsed) {
|
|
242
|
+
const ref = parsed.requestedRef ?? "HEAD";
|
|
243
|
+
const result = spawnSync("git", ["ls-remote", parsed.url, ref], { encoding: "utf8", timeout: 30_000 });
|
|
244
|
+
let resolvedRevision;
|
|
245
|
+
if (result.status === 0) {
|
|
246
|
+
const firstLine = result.stdout.trim().split(/\r?\n/)[0];
|
|
247
|
+
resolvedRevision = firstLine?.split(/\s/)[0] || undefined;
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
id: parsed.id,
|
|
251
|
+
source: parsed.source,
|
|
252
|
+
ref: parsed.ref,
|
|
253
|
+
artifactUrl: parsed.url,
|
|
254
|
+
resolvedVersion: parsed.requestedRef,
|
|
255
|
+
resolvedRevision,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
async function resolveLocalArtifact(parsed) {
|
|
213
259
|
return {
|
|
214
260
|
id: parsed.id,
|
|
215
261
|
source: parsed.source,
|
|
216
262
|
ref: parsed.ref,
|
|
217
263
|
artifactUrl: pathToFileURL(parsed.sourcePath).toString(),
|
|
218
|
-
resolvedRevision: readGitValue(parsed.repoRoot, "rev-parse", "HEAD"),
|
|
219
|
-
resolvedVersion: readGitValue(parsed.repoRoot, "rev-parse", "--abbrev-ref", "HEAD"),
|
|
264
|
+
resolvedRevision: parsed.repoRoot ? readGitValue(parsed.repoRoot, "rev-parse", "HEAD") : undefined,
|
|
265
|
+
resolvedVersion: parsed.repoRoot ? readGitValue(parsed.repoRoot, "rev-parse", "--abbrev-ref", "HEAD") : undefined,
|
|
220
266
|
};
|
|
221
267
|
}
|
|
222
268
|
function splitNpmNameAndVersion(input) {
|
|
@@ -265,6 +311,30 @@ function splitRefSuffix(value) {
|
|
|
265
311
|
return [value, undefined];
|
|
266
312
|
return [value.slice(0, hash), value.slice(hash + 1) || undefined];
|
|
267
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Strip the `git+` transport prefix from a ref, returning the inner URL.
|
|
316
|
+
* Handles `git+https://...`, `git+ssh://...`, `git+http://...`, etc.
|
|
317
|
+
*/
|
|
318
|
+
function stripGitTransport(ref) {
|
|
319
|
+
return ref.slice(4); // strip "git+"
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Convert a `file:` URI to a local filesystem path.
|
|
323
|
+
* Supports `file:./relative`, `file:../relative`, and `file:///absolute`.
|
|
324
|
+
*/
|
|
325
|
+
function fileUriToPath(ref) {
|
|
326
|
+
const after = ref.slice(5); // strip "file:"
|
|
327
|
+
// file:///absolute/path or file:///C:/path
|
|
328
|
+
if (after.startsWith("///")) {
|
|
329
|
+
return after.slice(2); // keep one leading /
|
|
330
|
+
}
|
|
331
|
+
// file://hostname/path (rare, treat hostname/path as absolute)
|
|
332
|
+
if (after.startsWith("//")) {
|
|
333
|
+
return after.slice(1);
|
|
334
|
+
}
|
|
335
|
+
// file:./relative or file:../relative or file:/absolute
|
|
336
|
+
return after;
|
|
337
|
+
}
|
|
268
338
|
function findGitRepoRoot(startDir) {
|
|
269
339
|
let current = path.resolve(startDir);
|
|
270
340
|
while (true) {
|
|
@@ -284,15 +354,105 @@ function readGitValue(repoRoot, ...args) {
|
|
|
284
354
|
const value = result.stdout.trim();
|
|
285
355
|
return value || undefined;
|
|
286
356
|
}
|
|
357
|
+
function parseSemver(version) {
|
|
358
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
|
|
359
|
+
if (!match)
|
|
360
|
+
return undefined;
|
|
361
|
+
return {
|
|
362
|
+
major: parseInt(match[1], 10),
|
|
363
|
+
minor: parseInt(match[2], 10),
|
|
364
|
+
patch: parseInt(match[3], 10),
|
|
365
|
+
prerelease: match[4],
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
function isExactSemver(version) {
|
|
369
|
+
return /^\d+\.\d+\.\d+(?:-[a-zA-Z0-9.+-]+)?$/.test(version);
|
|
370
|
+
}
|
|
371
|
+
function isSemverRange(input) {
|
|
372
|
+
return /^[~^>=<*]/.test(input) || /^\d+\.(\d+|\*)/.test(input);
|
|
373
|
+
}
|
|
374
|
+
function compareSemver(a, b) {
|
|
375
|
+
if (a.major !== b.major)
|
|
376
|
+
return a.major - b.major;
|
|
377
|
+
if (a.minor !== b.minor)
|
|
378
|
+
return a.minor - b.minor;
|
|
379
|
+
if (a.patch !== b.patch)
|
|
380
|
+
return a.patch - b.patch;
|
|
381
|
+
// Versions with prerelease are lower than release
|
|
382
|
+
if (a.prerelease && !b.prerelease)
|
|
383
|
+
return -1;
|
|
384
|
+
if (!a.prerelease && b.prerelease)
|
|
385
|
+
return 1;
|
|
386
|
+
return 0;
|
|
387
|
+
}
|
|
388
|
+
function semverGte(a, b) {
|
|
389
|
+
return compareSemver(a, b) >= 0;
|
|
390
|
+
}
|
|
391
|
+
function satisfiesRange(version, range) {
|
|
392
|
+
// Skip pre-release versions unless range specifically mentions one
|
|
393
|
+
if (version.prerelease && !range.includes("-"))
|
|
394
|
+
return false;
|
|
395
|
+
// ^1.2.3 — compatible with version: same major, >= minor.patch
|
|
396
|
+
const caretMatch = range.match(/^\^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
|
|
397
|
+
if (caretMatch) {
|
|
398
|
+
const rMajor = parseInt(caretMatch[1], 10);
|
|
399
|
+
const rMinor = parseInt(caretMatch[2], 10);
|
|
400
|
+
const rPatch = parseInt(caretMatch[3], 10);
|
|
401
|
+
if (version.major !== rMajor)
|
|
402
|
+
return false;
|
|
403
|
+
// ^0.x has special behavior: ^0.2.3 means >=0.2.3 <0.3.0
|
|
404
|
+
if (rMajor === 0) {
|
|
405
|
+
if (version.minor !== rMinor)
|
|
406
|
+
return false;
|
|
407
|
+
return version.patch >= rPatch;
|
|
408
|
+
}
|
|
409
|
+
return semverGte(version, { major: rMajor, minor: rMinor, patch: rPatch });
|
|
410
|
+
}
|
|
411
|
+
// ~1.2.3 — same major.minor, patch >= specified
|
|
412
|
+
const tildeMatch = range.match(/^~(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
|
|
413
|
+
if (tildeMatch) {
|
|
414
|
+
const rMajor = parseInt(tildeMatch[1], 10);
|
|
415
|
+
const rMinor = parseInt(tildeMatch[2], 10);
|
|
416
|
+
const rPatch = parseInt(tildeMatch[3], 10);
|
|
417
|
+
return version.major === rMajor && version.minor === rMinor && version.patch >= rPatch;
|
|
418
|
+
}
|
|
419
|
+
// >=1.2.3
|
|
420
|
+
const gteMatch = range.match(/^>=(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
|
|
421
|
+
if (gteMatch) {
|
|
422
|
+
const rMajor = parseInt(gteMatch[1], 10);
|
|
423
|
+
const rMinor = parseInt(gteMatch[2], 10);
|
|
424
|
+
const rPatch = parseInt(gteMatch[3], 10);
|
|
425
|
+
return semverGte(version, { major: rMajor, minor: rMinor, patch: rPatch });
|
|
426
|
+
}
|
|
427
|
+
// * or latest
|
|
428
|
+
if (range === "*" || range === "latest")
|
|
429
|
+
return true;
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
export function maxSatisfying(versions, range) {
|
|
433
|
+
const candidates = [];
|
|
434
|
+
for (const v of versions) {
|
|
435
|
+
const parsed = parseSemver(v);
|
|
436
|
+
if (!parsed)
|
|
437
|
+
continue;
|
|
438
|
+
if (satisfiesRange(parsed, range)) {
|
|
439
|
+
candidates.push({ version: v, parsed });
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (candidates.length === 0)
|
|
443
|
+
return undefined;
|
|
444
|
+
candidates.sort((a, b) => compareSemver(b.parsed, a.parsed));
|
|
445
|
+
return candidates[0].version;
|
|
446
|
+
}
|
|
287
447
|
async function fetchJson(url, headers) {
|
|
288
|
-
const response = await
|
|
448
|
+
const response = await fetchWithRetry(url, { headers });
|
|
289
449
|
if (!response.ok) {
|
|
290
450
|
throw new Error(`Request failed (${response.status}) for ${url}`);
|
|
291
451
|
}
|
|
292
452
|
return await response.json();
|
|
293
453
|
}
|
|
294
454
|
async function tryFetchJson(url, headers) {
|
|
295
|
-
const response = await
|
|
455
|
+
const response = await fetchWithRetry(url, { headers });
|
|
296
456
|
if (!response.ok)
|
|
297
457
|
return null;
|
|
298
458
|
return await response.json();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { fetchWithRetry } from "./common";
|
|
4
|
+
import { getRegistryIndexCacheDir } from "./paths";
|
|
4
5
|
// ── Constants ───────────────────────────────────────────────────────────────
|
|
5
6
|
/** Default registry index URL. Override via config or AKM_REGISTRY_URL env var. */
|
|
6
7
|
const DEFAULT_REGISTRY_URL = "https://raw.githubusercontent.com/itlackey/agentikit-registry/main/index.json";
|
|
@@ -44,7 +45,7 @@ async function loadIndex(url) {
|
|
|
44
45
|
}
|
|
45
46
|
// Try to fetch fresh index
|
|
46
47
|
try {
|
|
47
|
-
const response = await
|
|
48
|
+
const response = await fetchWithRetry(url, undefined, { timeout: 10_000 });
|
|
48
49
|
if (!response.ok) {
|
|
49
50
|
throw new Error(`HTTP ${response.status}`);
|
|
50
51
|
}
|
|
@@ -90,22 +91,13 @@ function writeCachedIndex(cachePath, index) {
|
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
93
|
function indexCachePath(url) {
|
|
93
|
-
const
|
|
94
|
+
const indexDir = getRegistryIndexCacheDir();
|
|
94
95
|
// Deterministic filename from URL
|
|
95
96
|
const slug = url
|
|
96
97
|
.replace(/[^a-zA-Z0-9]+/g, "-")
|
|
97
98
|
.replace(/^-+|-+$/g, "")
|
|
98
99
|
.slice(0, 120);
|
|
99
|
-
return path.join(
|
|
100
|
-
}
|
|
101
|
-
function resolveCacheDir() {
|
|
102
|
-
const xdgCache = process.env.XDG_CACHE_HOME?.trim();
|
|
103
|
-
if (xdgCache)
|
|
104
|
-
return path.join(path.resolve(xdgCache), "agentikit");
|
|
105
|
-
const home = process.env.HOME?.trim();
|
|
106
|
-
if (!home)
|
|
107
|
-
return path.join("/tmp", "agentikit-cache");
|
|
108
|
-
return path.join(path.resolve(home), ".cache", "agentikit");
|
|
100
|
+
return path.join(indexDir, `${slug}.json`);
|
|
109
101
|
}
|
|
110
102
|
function isCacheExpired(mtimeMs) {
|
|
111
103
|
return Date.now() - mtimeMs > CACHE_TTL_MS;
|
|
@@ -248,9 +240,9 @@ function asString(value) {
|
|
|
248
240
|
return typeof value === "string" && value ? value : undefined;
|
|
249
241
|
}
|
|
250
242
|
function asSource(value) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
243
|
+
if (value === "npm" || value === "github" || value === "git" || value === "local")
|
|
244
|
+
return value;
|
|
245
|
+
return undefined;
|
|
254
246
|
}
|
|
255
247
|
function asStringArray(value) {
|
|
256
248
|
if (!Array.isArray(value))
|