caik-cli 0.1.1 → 0.6.0
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/README.md +8 -7
- package/dist/api-6OX4ICXN.js +9 -0
- package/dist/auto-improve-skills-2COKTU5C.js +8 -0
- package/dist/autoresearch-Y7WW6L4O.js +24 -0
- package/dist/chunk-2YHUDOJL.js +54 -0
- package/dist/chunk-3TXNZINH.js +775 -0
- package/dist/chunk-5MHNQAV4.js +317 -0
- package/dist/chunk-7AIZTHHZ.js +152 -0
- package/dist/chunk-D4IM3YRX.js +166 -0
- package/dist/chunk-DJJHS7KK.js +62 -0
- package/dist/chunk-DKZBQRR3.js +91 -0
- package/dist/chunk-FLSHJZLC.js +613 -0
- package/dist/chunk-H2ZKCXMJ.js +202 -0
- package/dist/chunk-ILMOSMD3.js +83 -0
- package/dist/chunk-KYTHKH6V.js +79 -0
- package/dist/chunk-LTKHLRM4.js +272 -0
- package/dist/chunk-T32AEP3O.js +146 -0
- package/dist/chunk-T73Z5UMA.js +14437 -0
- package/dist/chunk-TFKT7V7H.js +1545 -0
- package/dist/chunk-US4CYDNS.js +524 -0
- package/dist/chunk-ZLRN7Q7C.js +27 -0
- package/dist/claude-code-6DF4YARB.js +8 -0
- package/dist/config-CS7734SA.js +24 -0
- package/dist/correction-classifier-TLPKRNLI.js +93 -0
- package/dist/cursor-Z4XXDCAM.js +8 -0
- package/dist/daemon/autoresearch-2MAEM2YI.js +272 -0
- package/dist/daemon/chunk-545XA5CB.js +77 -0
- package/dist/daemon/chunk-HEYFAUHL.js +90 -0
- package/dist/daemon/chunk-MLKGABMK.js +9 -0
- package/dist/daemon/chunk-NJICGNCK.js +150 -0
- package/dist/daemon/chunk-OD5NUFH2.js +181 -0
- package/dist/daemon/chunk-SM2FSXIP.js +60 -0
- package/dist/daemon/chunk-UMDJFPN6.js +163 -0
- package/dist/daemon/config-F7HE3JRY.js +23 -0
- package/dist/daemon/db-QEXVVTAL.js +15 -0
- package/dist/daemon/eval-generator-OR2FAYLB.js +316 -0
- package/dist/daemon/improver-TGEK6MPE.js +186 -0
- package/dist/daemon/llm-FUJ2TBYT.js +11 -0
- package/dist/daemon/nudge-detector-NFRHWZY6.js +140 -0
- package/dist/daemon/platform-7N3LQDIB.js +16381 -0
- package/dist/daemon/registry-FI4GTO3H.js +20 -0
- package/dist/daemon/server.js +356 -0
- package/dist/daemon/trace-store-T7XFGQSX.js +19 -0
- package/dist/daemon-UXYMG46V.js +85 -0
- package/dist/db-TLNRIXLK.js +18 -0
- package/dist/eval-generator-GGMRPO3K.js +21 -0
- package/dist/eval-runner-EF4K6T5Y.js +15 -0
- package/dist/index.js +8033 -568
- package/dist/llm-3UUZX6PX.js +12 -0
- package/dist/platform-52NREMBS.js +33 -0
- package/dist/repo-installer-K6ADOW3E.js +25 -0
- package/dist/setup-P744STZE.js +16 -0
- package/dist/test-loop-Y7QQE55P.js +127 -0
- package/dist/trace-store-FVLMNNDK.js +20 -0
- package/package.json +9 -3
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getConfigDir
|
|
4
|
+
} from "./chunk-KYTHKH6V.js";
|
|
5
|
+
|
|
6
|
+
// src/platform/registry.ts
|
|
7
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
var REGISTRY_VERSION = 1;
|
|
10
|
+
function getRegistryPath() {
|
|
11
|
+
return join(getConfigDir(), "registry.json");
|
|
12
|
+
}
|
|
13
|
+
function emptyRegistry() {
|
|
14
|
+
return { version: REGISTRY_VERSION, entries: [] };
|
|
15
|
+
}
|
|
16
|
+
function readRegistry() {
|
|
17
|
+
const path = getRegistryPath();
|
|
18
|
+
if (!existsSync(path)) return emptyRegistry();
|
|
19
|
+
try {
|
|
20
|
+
const raw = readFileSync(path, "utf-8");
|
|
21
|
+
const parsed = JSON.parse(raw);
|
|
22
|
+
if (!parsed.entries || !Array.isArray(parsed.entries)) return emptyRegistry();
|
|
23
|
+
return parsed;
|
|
24
|
+
} catch {
|
|
25
|
+
return emptyRegistry();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function writeRegistry(registry) {
|
|
29
|
+
const dir = getConfigDir();
|
|
30
|
+
if (!existsSync(dir)) {
|
|
31
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
32
|
+
}
|
|
33
|
+
const path = getRegistryPath();
|
|
34
|
+
writeFileSync(path, JSON.stringify(registry, null, 2) + "\n", "utf-8");
|
|
35
|
+
}
|
|
36
|
+
function upsertRegistryEntry(entry) {
|
|
37
|
+
const registry = readRegistry();
|
|
38
|
+
const idx = registry.entries.findIndex(
|
|
39
|
+
(e) => e.slug === entry.slug && e.platform === entry.platform
|
|
40
|
+
);
|
|
41
|
+
if (idx >= 0) {
|
|
42
|
+
registry.entries[idx] = entry;
|
|
43
|
+
} else {
|
|
44
|
+
registry.entries.push(entry);
|
|
45
|
+
}
|
|
46
|
+
writeRegistry(registry);
|
|
47
|
+
}
|
|
48
|
+
function removeRegistryEntry(slug, platform) {
|
|
49
|
+
const registry = readRegistry();
|
|
50
|
+
const idx = registry.entries.findIndex(
|
|
51
|
+
(e) => e.slug === slug && e.platform === platform
|
|
52
|
+
);
|
|
53
|
+
if (idx < 0) return null;
|
|
54
|
+
const [removed] = registry.entries.splice(idx, 1);
|
|
55
|
+
writeRegistry(registry);
|
|
56
|
+
return removed;
|
|
57
|
+
}
|
|
58
|
+
function findRegistryEntry(slug, platform) {
|
|
59
|
+
const registry = readRegistry();
|
|
60
|
+
return registry.entries.find(
|
|
61
|
+
(e) => e.slug === slug && (!platform || e.platform === platform)
|
|
62
|
+
) ?? null;
|
|
63
|
+
}
|
|
64
|
+
function listRegistryEntries(platform) {
|
|
65
|
+
const registry = readRegistry();
|
|
66
|
+
if (!platform) return registry.entries;
|
|
67
|
+
return registry.entries.filter((e) => e.platform === platform);
|
|
68
|
+
}
|
|
69
|
+
function cleanupFiles(entry) {
|
|
70
|
+
const failed = [];
|
|
71
|
+
for (const file of entry.files) {
|
|
72
|
+
try {
|
|
73
|
+
if (existsSync(file)) {
|
|
74
|
+
unlinkSync(file);
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
failed.push(file);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return failed;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
readRegistry,
|
|
85
|
+
writeRegistry,
|
|
86
|
+
upsertRegistryEntry,
|
|
87
|
+
removeRegistryEntry,
|
|
88
|
+
findRegistryEntry,
|
|
89
|
+
listRegistryEntries,
|
|
90
|
+
cleanupFiles
|
|
91
|
+
};
|
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CaikError
|
|
4
|
+
} from "./chunk-2YHUDOJL.js";
|
|
5
|
+
|
|
6
|
+
// src/repo-installer.ts
|
|
7
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync, existsSync } from "fs";
|
|
8
|
+
import { dirname, join } from "path";
|
|
9
|
+
import { homedir } from "os";
|
|
10
|
+
|
|
11
|
+
// src/utils/github-fetch.ts
|
|
12
|
+
var GITHUB_API = "https://api.github.com";
|
|
13
|
+
var RAW_BASE = "https://raw.githubusercontent.com";
|
|
14
|
+
function parseSource(input) {
|
|
15
|
+
let skill;
|
|
16
|
+
const atIdx = input.lastIndexOf("@");
|
|
17
|
+
if (atIdx > 0 && !input.slice(0, atIdx).includes("://")) {
|
|
18
|
+
skill = input.slice(atIdx + 1);
|
|
19
|
+
input = input.slice(0, atIdx);
|
|
20
|
+
} else if (atIdx > 0 && input.includes("://")) {
|
|
21
|
+
const urlWithoutProtocol = input.replace(/^https?:\/\//, "");
|
|
22
|
+
const urlAtIdx = urlWithoutProtocol.lastIndexOf("@");
|
|
23
|
+
if (urlAtIdx > 0) {
|
|
24
|
+
skill = urlWithoutProtocol.slice(urlAtIdx + 1);
|
|
25
|
+
input = input.slice(0, input.length - skill.length - 1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (input.includes("github.com")) {
|
|
29
|
+
const url = input.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
30
|
+
const parts = url.replace("github.com/", "").split("/");
|
|
31
|
+
const owner = parts[0];
|
|
32
|
+
const repo = parts[1]?.replace(/\.git$/, "");
|
|
33
|
+
if (owner && repo) {
|
|
34
|
+
return { type: "github", owner, repo, skill };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (/^[a-zA-Z0-9][a-zA-Z0-9_.-]*\/[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(input)) {
|
|
38
|
+
const [owner, repo] = input.split("/");
|
|
39
|
+
return { type: "github", owner, repo, skill };
|
|
40
|
+
}
|
|
41
|
+
return { type: "local", path: input, skill };
|
|
42
|
+
}
|
|
43
|
+
function getHeaders() {
|
|
44
|
+
const headers = {
|
|
45
|
+
Accept: "application/vnd.github.v3+json"
|
|
46
|
+
};
|
|
47
|
+
const token = process.env.GITHUB_TOKEN;
|
|
48
|
+
if (token) {
|
|
49
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
50
|
+
}
|
|
51
|
+
return headers;
|
|
52
|
+
}
|
|
53
|
+
async function listContents(owner, repo, path = "") {
|
|
54
|
+
const url = path ? `${GITHUB_API}/repos/${owner}/${repo}/contents/${path}` : `${GITHUB_API}/repos/${owner}/${repo}/contents`;
|
|
55
|
+
const response = await fetch(url, { headers: getHeaders() });
|
|
56
|
+
if (response.status === 404) return null;
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
|
|
59
|
+
}
|
|
60
|
+
const data = await response.json();
|
|
61
|
+
if (!Array.isArray(data)) return [data];
|
|
62
|
+
return data;
|
|
63
|
+
}
|
|
64
|
+
async function fetchRawFile(owner, repo, path, ref) {
|
|
65
|
+
if (ref) {
|
|
66
|
+
const refUrl = `${RAW_BASE}/${owner}/${repo}/${ref}/${path}`;
|
|
67
|
+
const refResponse = await fetch(refUrl, {
|
|
68
|
+
headers: process.env.GITHUB_TOKEN ? { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` } : {}
|
|
69
|
+
});
|
|
70
|
+
if (refResponse.ok) {
|
|
71
|
+
return refResponse.text();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const mainUrl = `${RAW_BASE}/${owner}/${repo}/main/${path}`;
|
|
75
|
+
const mainResponse = await fetch(mainUrl, {
|
|
76
|
+
headers: process.env.GITHUB_TOKEN ? { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` } : {}
|
|
77
|
+
});
|
|
78
|
+
if (mainResponse.ok) {
|
|
79
|
+
return mainResponse.text();
|
|
80
|
+
}
|
|
81
|
+
const masterUrl = `${RAW_BASE}/${owner}/${repo}/master/${path}`;
|
|
82
|
+
const masterResponse = await fetch(masterUrl, {
|
|
83
|
+
headers: process.env.GITHUB_TOKEN ? { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` } : {}
|
|
84
|
+
});
|
|
85
|
+
if (masterResponse.ok) {
|
|
86
|
+
return masterResponse.text();
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
async function fetchJsonFile(owner, repo, path, ref) {
|
|
91
|
+
const raw = await fetchRawFile(owner, repo, path, ref);
|
|
92
|
+
if (!raw) return null;
|
|
93
|
+
return JSON.parse(raw);
|
|
94
|
+
}
|
|
95
|
+
async function fetchRepoTree(owner, repo, ref = "main") {
|
|
96
|
+
const response = await fetch(
|
|
97
|
+
`${GITHUB_API}/repos/${owner}/${repo}/git/trees/${encodeURIComponent(ref)}?recursive=1`,
|
|
98
|
+
{ headers: getHeaders() }
|
|
99
|
+
);
|
|
100
|
+
if (response.status === 404) return [];
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
|
|
103
|
+
}
|
|
104
|
+
const data = await response.json();
|
|
105
|
+
return Array.isArray(data.tree) ? data.tree : [];
|
|
106
|
+
}
|
|
107
|
+
var listTree = fetchRepoTree;
|
|
108
|
+
function parseStringArrayLiteral(raw) {
|
|
109
|
+
return [...raw.matchAll(/'([^']+)'/g)].map((match) => match[1]).filter(Boolean);
|
|
110
|
+
}
|
|
111
|
+
function parseTargetAdapterScript(content) {
|
|
112
|
+
const id = content.match(/id:\s*'([^']+)'/)?.[1];
|
|
113
|
+
const target = content.match(/target:\s*'([^']+)'/)?.[1];
|
|
114
|
+
const kind = content.match(/kind:\s*'([^']+)'/)?.[1];
|
|
115
|
+
const rootSegmentsRaw = content.match(/rootSegments:\s*\[([^\]]*)\]/s)?.[1];
|
|
116
|
+
const installStateRaw = content.match(/installStatePathSegments:\s*\[([^\]]*)\]/s)?.[1];
|
|
117
|
+
if (!id || !target || !kind || !rootSegmentsRaw || !installStateRaw) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const pathMappings = [...content.matchAll(
|
|
121
|
+
/sourceRelativePath === '([^']+)'[\s\S]*?path\.join\(targetRoot,\s*([^)]+)\)[\s\S]*?'(preserve-relative-path|flatten-copy|sync-root-children)'/g
|
|
122
|
+
)].map((match) => ({
|
|
123
|
+
source: match[1],
|
|
124
|
+
destinationSegments: parseStringArrayLiteral(match[2]),
|
|
125
|
+
strategy: match[3]
|
|
126
|
+
}));
|
|
127
|
+
return {
|
|
128
|
+
id,
|
|
129
|
+
target,
|
|
130
|
+
kind,
|
|
131
|
+
rootSegments: parseStringArrayLiteral(rootSegmentsRaw),
|
|
132
|
+
installStatePathSegments: parseStringArrayLiteral(installStateRaw),
|
|
133
|
+
nativeRootRelativePath: content.match(/nativeRootRelativePath:\s*'([^']+)'/)?.[1],
|
|
134
|
+
rulesMode: content.includes("createFlatRuleOperations") ? "flatten-copy" : "preserve-relative-path",
|
|
135
|
+
pathMappings: pathMappings.length > 0 ? pathMappings : void 0
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
async function fetchRepoInstallerConfig(owner, repo, defaultBranch = "main") {
|
|
139
|
+
const [profilesData, modulesData, componentsData, tree, installStateSource] = await Promise.all([
|
|
140
|
+
fetchJsonFile(
|
|
141
|
+
owner,
|
|
142
|
+
repo,
|
|
143
|
+
"manifests/install-profiles.json",
|
|
144
|
+
defaultBranch
|
|
145
|
+
),
|
|
146
|
+
fetchJsonFile(
|
|
147
|
+
owner,
|
|
148
|
+
repo,
|
|
149
|
+
"manifests/install-modules.json",
|
|
150
|
+
defaultBranch
|
|
151
|
+
),
|
|
152
|
+
fetchJsonFile(
|
|
153
|
+
owner,
|
|
154
|
+
repo,
|
|
155
|
+
"manifests/install-components.json",
|
|
156
|
+
defaultBranch
|
|
157
|
+
),
|
|
158
|
+
fetchRepoTree(owner, repo, defaultBranch),
|
|
159
|
+
fetchRawFile(owner, repo, "scripts/lib/install-state.js", defaultBranch)
|
|
160
|
+
]);
|
|
161
|
+
if (!profilesData || !modulesData) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const adapterPaths = tree.filter(
|
|
165
|
+
(item) => item.type === "blob" && item.path.startsWith("scripts/lib/install-targets/") && item.path.endsWith(".js") && !item.path.endsWith("/helpers.js") && !item.path.endsWith("/registry.js")
|
|
166
|
+
).map((item) => item.path);
|
|
167
|
+
const adapterSources = await Promise.all(
|
|
168
|
+
adapterPaths.map((path) => fetchRawFile(owner, repo, path, defaultBranch))
|
|
169
|
+
);
|
|
170
|
+
const targetAdapters = adapterSources.map((content) => content ? parseTargetAdapterScript(content) : null).filter((adapter) => adapter !== null);
|
|
171
|
+
const installStateVersion = installStateSource?.match(/schemaVersion:\s*'([^']+)'/)?.[1] ?? "ecc.install.v1";
|
|
172
|
+
const targets = [...new Set(modulesData.modules.flatMap((module) => module.targets))];
|
|
173
|
+
return {
|
|
174
|
+
version: profilesData.version ?? modulesData.version ?? 1,
|
|
175
|
+
targets,
|
|
176
|
+
defaultTarget: targets.includes("claude") ? "claude" : targets[0],
|
|
177
|
+
defaultProfile: profilesData.profiles.developer ? "developer" : Object.keys(profilesData.profiles)[0],
|
|
178
|
+
profiles: profilesData.profiles,
|
|
179
|
+
modules: modulesData.modules,
|
|
180
|
+
components: componentsData?.components ?? [],
|
|
181
|
+
targetAdapters,
|
|
182
|
+
installStateVersion
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
async function fetchRepoMetadata(owner, repo) {
|
|
186
|
+
const response = await fetch(`${GITHUB_API}/repos/${owner}/${repo}`, {
|
|
187
|
+
headers: getHeaders()
|
|
188
|
+
});
|
|
189
|
+
if (response.status === 404) return null;
|
|
190
|
+
if (!response.ok) {
|
|
191
|
+
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
|
|
192
|
+
}
|
|
193
|
+
const data = await response.json();
|
|
194
|
+
return {
|
|
195
|
+
name: data.name,
|
|
196
|
+
fullName: data.full_name,
|
|
197
|
+
description: data.description ?? null,
|
|
198
|
+
htmlUrl: data.html_url,
|
|
199
|
+
homepage: data.homepage ?? null,
|
|
200
|
+
defaultBranch: data.default_branch,
|
|
201
|
+
stars: data.stargazers_count ?? 0,
|
|
202
|
+
topics: Array.isArray(data.topics) ? data.topics : []
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/repo-installer.ts
|
|
207
|
+
function isRepoInstallerManifest(manifest) {
|
|
208
|
+
return Boolean(
|
|
209
|
+
manifest && typeof manifest === "object" && manifest.type === "repo-installer" && "repoInstall" in manifest
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
function getRepoInstallerManifest(manifest) {
|
|
213
|
+
return isRepoInstallerManifest(manifest) ? manifest : null;
|
|
214
|
+
}
|
|
215
|
+
function ensureRepoInstallerManifest(artifact) {
|
|
216
|
+
if (!isRepoInstallerManifest(artifact.manifest)) {
|
|
217
|
+
throw new CaikError(
|
|
218
|
+
`${artifact.slug} is not a repo-native installer artifact.`,
|
|
219
|
+
"Use `caik install` for leaf artifacts and stacks."
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
return artifact.manifest;
|
|
223
|
+
}
|
|
224
|
+
function dedupe(values) {
|
|
225
|
+
return [...new Set(values.filter(Boolean))];
|
|
226
|
+
}
|
|
227
|
+
function expandComponentModules(components, ids) {
|
|
228
|
+
const modules = [];
|
|
229
|
+
for (const id of dedupe(ids)) {
|
|
230
|
+
const component = components.find((candidate) => candidate.id === id);
|
|
231
|
+
if (!component) {
|
|
232
|
+
throw new CaikError(`Unknown repo install component: ${id}`);
|
|
233
|
+
}
|
|
234
|
+
modules.push(...component.modules);
|
|
235
|
+
}
|
|
236
|
+
return dedupe(modules);
|
|
237
|
+
}
|
|
238
|
+
function resolveRequestedModuleIds(manifest, request) {
|
|
239
|
+
const profileId = request.profile ?? manifest.repoInstall.defaultProfile;
|
|
240
|
+
const profileModules = profileId ? manifest.repoInstall.profiles[profileId]?.modules : void 0;
|
|
241
|
+
if (profileId && !profileModules) {
|
|
242
|
+
throw new CaikError(`Unknown repo install profile: ${profileId}`);
|
|
243
|
+
}
|
|
244
|
+
const requestedModuleIds = dedupe([
|
|
245
|
+
...profileModules ?? [],
|
|
246
|
+
...request.modules ?? [],
|
|
247
|
+
...expandComponentModules(manifest.repoInstall.components, request.includeComponents ?? [])
|
|
248
|
+
]);
|
|
249
|
+
const excludedModuleIds = new Set(
|
|
250
|
+
expandComponentModules(manifest.repoInstall.components, request.excludeComponents ?? [])
|
|
251
|
+
);
|
|
252
|
+
return {
|
|
253
|
+
profileId,
|
|
254
|
+
requestedModuleIds: requestedModuleIds.filter((id) => !excludedModuleIds.has(id)),
|
|
255
|
+
includedComponentIds: dedupe(request.includeComponents ?? []),
|
|
256
|
+
excludedComponentIds: dedupe(request.excludeComponents ?? [])
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function resolveSelectedModules(manifest, target, requestedModuleIds) {
|
|
260
|
+
const byId = new Map(manifest.repoInstall.modules.map((module) => [module.id, module]));
|
|
261
|
+
const resolved = /* @__PURE__ */ new Set();
|
|
262
|
+
const visit = (moduleId) => {
|
|
263
|
+
const module = byId.get(moduleId);
|
|
264
|
+
if (!module) {
|
|
265
|
+
throw new CaikError(`Unknown repo install module: ${moduleId}`);
|
|
266
|
+
}
|
|
267
|
+
if (resolved.has(moduleId)) return;
|
|
268
|
+
resolved.add(moduleId);
|
|
269
|
+
for (const dep of module.dependencies ?? []) {
|
|
270
|
+
visit(dep);
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
for (const moduleId of requestedModuleIds) {
|
|
274
|
+
visit(moduleId);
|
|
275
|
+
}
|
|
276
|
+
const selectedModules = [];
|
|
277
|
+
const skippedModules = [];
|
|
278
|
+
for (const moduleId of resolved) {
|
|
279
|
+
const module = byId.get(moduleId);
|
|
280
|
+
if (module.targets.includes(target)) {
|
|
281
|
+
selectedModules.push(module);
|
|
282
|
+
} else {
|
|
283
|
+
skippedModules.push(module);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return { selectedModules, skippedModules };
|
|
287
|
+
}
|
|
288
|
+
function defaultTargetAdapter(target) {
|
|
289
|
+
switch (target) {
|
|
290
|
+
case "claude":
|
|
291
|
+
return {
|
|
292
|
+
id: "claude-home",
|
|
293
|
+
target: "claude",
|
|
294
|
+
kind: "home",
|
|
295
|
+
rootSegments: [".claude"],
|
|
296
|
+
installStatePathSegments: ["ecc", "install-state.json"],
|
|
297
|
+
nativeRootRelativePath: ".claude-plugin",
|
|
298
|
+
rulesMode: "preserve-relative-path"
|
|
299
|
+
};
|
|
300
|
+
case "cursor":
|
|
301
|
+
return {
|
|
302
|
+
id: "cursor-project",
|
|
303
|
+
target: "cursor",
|
|
304
|
+
kind: "project",
|
|
305
|
+
rootSegments: [".cursor"],
|
|
306
|
+
installStatePathSegments: ["ecc-install-state.json"],
|
|
307
|
+
nativeRootRelativePath: ".cursor",
|
|
308
|
+
rulesMode: "flatten-copy"
|
|
309
|
+
};
|
|
310
|
+
case "codex":
|
|
311
|
+
return {
|
|
312
|
+
id: "codex-home",
|
|
313
|
+
target: "codex",
|
|
314
|
+
kind: "home",
|
|
315
|
+
rootSegments: [".codex"],
|
|
316
|
+
installStatePathSegments: ["ecc-install-state.json"],
|
|
317
|
+
nativeRootRelativePath: ".codex",
|
|
318
|
+
rulesMode: "preserve-relative-path"
|
|
319
|
+
};
|
|
320
|
+
case "opencode":
|
|
321
|
+
return {
|
|
322
|
+
id: "opencode-home",
|
|
323
|
+
target: "opencode",
|
|
324
|
+
kind: "home",
|
|
325
|
+
rootSegments: [".opencode"],
|
|
326
|
+
installStatePathSegments: ["ecc-install-state.json"],
|
|
327
|
+
nativeRootRelativePath: ".opencode",
|
|
328
|
+
rulesMode: "preserve-relative-path"
|
|
329
|
+
};
|
|
330
|
+
case "antigravity":
|
|
331
|
+
return {
|
|
332
|
+
id: "antigravity-project",
|
|
333
|
+
target: "antigravity",
|
|
334
|
+
kind: "project",
|
|
335
|
+
rootSegments: [".agent"],
|
|
336
|
+
installStatePathSegments: ["ecc-install-state.json"],
|
|
337
|
+
rulesMode: "flatten-copy",
|
|
338
|
+
pathMappings: [
|
|
339
|
+
{ source: "commands", destinationSegments: ["workflows"] },
|
|
340
|
+
{ source: "agents", destinationSegments: ["skills"] }
|
|
341
|
+
]
|
|
342
|
+
};
|
|
343
|
+
default:
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function resolveAdapter(manifest, target) {
|
|
348
|
+
const adapter = (manifest.repoInstall.targetAdapters ?? []).find(
|
|
349
|
+
(candidate) => candidate.target === target || candidate.id === target
|
|
350
|
+
) ?? defaultTargetAdapter(target);
|
|
351
|
+
if (!adapter) {
|
|
352
|
+
throw new CaikError(
|
|
353
|
+
`Unsupported repo install target: ${target}`,
|
|
354
|
+
`Supported targets: ${manifest.repoInstall.targets.join(", ")}`
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
return adapter;
|
|
358
|
+
}
|
|
359
|
+
function resolveTargetRoot(adapter, projectRoot) {
|
|
360
|
+
const baseRoot = adapter.kind === "home" ? homedir() : projectRoot ?? process.cwd();
|
|
361
|
+
return join(baseRoot, ...adapter.rootSegments);
|
|
362
|
+
}
|
|
363
|
+
function getRepoInstallStatePath(manifest, target, projectRoot) {
|
|
364
|
+
const adapter = resolveAdapter(
|
|
365
|
+
manifest,
|
|
366
|
+
target || manifest.repoInstall.defaultTarget || manifest.repoInstall.targets[0]
|
|
367
|
+
);
|
|
368
|
+
const targetRoot = resolveTargetRoot(adapter, projectRoot);
|
|
369
|
+
return {
|
|
370
|
+
adapter,
|
|
371
|
+
targetRoot,
|
|
372
|
+
installStatePath: join(targetRoot, ...adapter.installStatePathSegments)
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
function relativeFilePathsForSource(sourceRelativePath, treeFiles) {
|
|
376
|
+
if (treeFiles.includes(sourceRelativePath)) {
|
|
377
|
+
return [sourceRelativePath];
|
|
378
|
+
}
|
|
379
|
+
const prefix = `${sourceRelativePath}/`;
|
|
380
|
+
return treeFiles.filter((path) => path.startsWith(prefix));
|
|
381
|
+
}
|
|
382
|
+
function toFlattenedRuleDestination(targetRoot, sourceRelativePath) {
|
|
383
|
+
const [, namespace, ...rest] = sourceRelativePath.split("/");
|
|
384
|
+
const flattened = [namespace, ...rest].filter(Boolean).join("-");
|
|
385
|
+
return join(targetRoot, "rules", flattened);
|
|
386
|
+
}
|
|
387
|
+
function mapDestinationPath(adapter, targetRoot, sourceRoot, sourceRelativePath) {
|
|
388
|
+
if (adapter.rulesMode === "flatten-copy" && sourceRoot === "rules") {
|
|
389
|
+
return {
|
|
390
|
+
destinationPath: toFlattenedRuleDestination(targetRoot, sourceRelativePath),
|
|
391
|
+
strategy: "flatten-copy"
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
const mapping = adapter.pathMappings?.find((candidate) => candidate.source === sourceRoot);
|
|
395
|
+
if (mapping) {
|
|
396
|
+
const relativeSuffix = sourceRelativePath === sourceRoot ? "" : sourceRelativePath.slice(sourceRoot.length + 1);
|
|
397
|
+
const destinationPath = relativeSuffix ? join(targetRoot, ...mapping.destinationSegments, relativeSuffix) : join(targetRoot, ...mapping.destinationSegments);
|
|
398
|
+
return {
|
|
399
|
+
destinationPath,
|
|
400
|
+
strategy: mapping.strategy ?? "preserve-relative-path"
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
if (adapter.nativeRootRelativePath && sourceRoot === adapter.nativeRootRelativePath) {
|
|
404
|
+
const relativeSuffix = sourceRelativePath === sourceRoot ? "" : sourceRelativePath.slice(sourceRoot.length + 1);
|
|
405
|
+
return {
|
|
406
|
+
destinationPath: relativeSuffix ? join(targetRoot, relativeSuffix) : targetRoot,
|
|
407
|
+
strategy: "sync-root-children"
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
destinationPath: join(targetRoot, sourceRelativePath),
|
|
412
|
+
strategy: "preserve-relative-path"
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
async function createRepoInstallPlan(artifact, request) {
|
|
416
|
+
const manifest = ensureRepoInstallerManifest(artifact);
|
|
417
|
+
const { adapter, targetRoot, installStatePath } = getRepoInstallStatePath(
|
|
418
|
+
manifest,
|
|
419
|
+
request.target,
|
|
420
|
+
request.projectRoot
|
|
421
|
+
);
|
|
422
|
+
const tree = await listTree(
|
|
423
|
+
manifest.source.owner,
|
|
424
|
+
manifest.source.repo,
|
|
425
|
+
manifest.source.defaultBranch
|
|
426
|
+
);
|
|
427
|
+
const treeFiles = tree.filter((item) => item.type === "blob").map((item) => item.path);
|
|
428
|
+
const { profileId, requestedModuleIds, includedComponentIds, excludedComponentIds } = resolveRequestedModuleIds(manifest, request);
|
|
429
|
+
const { selectedModules, skippedModules } = resolveSelectedModules(
|
|
430
|
+
manifest,
|
|
431
|
+
adapter.target,
|
|
432
|
+
requestedModuleIds
|
|
433
|
+
);
|
|
434
|
+
const operations = [];
|
|
435
|
+
for (const module of selectedModules) {
|
|
436
|
+
for (const sourceRoot of module.paths) {
|
|
437
|
+
const matchedFiles = relativeFilePathsForSource(sourceRoot, treeFiles);
|
|
438
|
+
for (const sourceRelativePath of matchedFiles) {
|
|
439
|
+
const { destinationPath, strategy } = mapDestinationPath(
|
|
440
|
+
adapter,
|
|
441
|
+
targetRoot,
|
|
442
|
+
sourceRoot,
|
|
443
|
+
sourceRelativePath
|
|
444
|
+
);
|
|
445
|
+
operations.push({
|
|
446
|
+
moduleId: module.id,
|
|
447
|
+
sourcePath: sourceRelativePath,
|
|
448
|
+
sourceRelativePath,
|
|
449
|
+
destinationPath,
|
|
450
|
+
strategy
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
slug: artifact.slug,
|
|
457
|
+
name: artifact.name,
|
|
458
|
+
manifest,
|
|
459
|
+
adapter,
|
|
460
|
+
targetRoot,
|
|
461
|
+
installStatePath,
|
|
462
|
+
profileId,
|
|
463
|
+
requestedModuleIds,
|
|
464
|
+
selectedModules,
|
|
465
|
+
skippedModules,
|
|
466
|
+
includedComponentIds,
|
|
467
|
+
excludedComponentIds,
|
|
468
|
+
operations
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
function buildInstallState(plan) {
|
|
472
|
+
return {
|
|
473
|
+
schemaVersion: plan.manifest.repoInstall.installStateVersion,
|
|
474
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
475
|
+
slug: plan.slug,
|
|
476
|
+
target: plan.adapter.target,
|
|
477
|
+
targetInfo: {
|
|
478
|
+
id: plan.adapter.id,
|
|
479
|
+
target: plan.adapter.target,
|
|
480
|
+
kind: plan.adapter.kind,
|
|
481
|
+
root: plan.targetRoot,
|
|
482
|
+
installStatePath: plan.installStatePath
|
|
483
|
+
},
|
|
484
|
+
request: {
|
|
485
|
+
profile: plan.profileId ?? null,
|
|
486
|
+
modules: plan.requestedModuleIds,
|
|
487
|
+
includeComponents: plan.includedComponentIds,
|
|
488
|
+
excludeComponents: plan.excludedComponentIds
|
|
489
|
+
},
|
|
490
|
+
resolution: {
|
|
491
|
+
selectedModules: plan.selectedModules.map((module) => module.id),
|
|
492
|
+
skippedModules: plan.skippedModules.map((module) => module.id)
|
|
493
|
+
},
|
|
494
|
+
source: {
|
|
495
|
+
repoUrl: plan.manifest.url,
|
|
496
|
+
owner: plan.manifest.source.owner,
|
|
497
|
+
repo: plan.manifest.source.repo,
|
|
498
|
+
defaultBranch: plan.manifest.source.defaultBranch
|
|
499
|
+
},
|
|
500
|
+
operations: plan.operations
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
async function applyRepoInstallPlan(plan) {
|
|
504
|
+
const writtenFiles = [];
|
|
505
|
+
try {
|
|
506
|
+
for (const operation of plan.operations) {
|
|
507
|
+
const content = await fetchRawFile(
|
|
508
|
+
plan.manifest.source.owner,
|
|
509
|
+
plan.manifest.source.repo,
|
|
510
|
+
operation.sourcePath,
|
|
511
|
+
plan.manifest.source.defaultBranch
|
|
512
|
+
);
|
|
513
|
+
if (content == null) {
|
|
514
|
+
throw new CaikError(`Missing source file in repo install: ${operation.sourcePath}`);
|
|
515
|
+
}
|
|
516
|
+
mkdirSync(dirname(operation.destinationPath), { recursive: true });
|
|
517
|
+
writeFileSync(operation.destinationPath, content, "utf-8");
|
|
518
|
+
writtenFiles.push(operation.destinationPath);
|
|
519
|
+
}
|
|
520
|
+
mkdirSync(dirname(plan.installStatePath), { recursive: true });
|
|
521
|
+
writeFileSync(plan.installStatePath, JSON.stringify(buildInstallState(plan), null, 2), "utf-8");
|
|
522
|
+
writtenFiles.push(plan.installStatePath);
|
|
523
|
+
return { writtenFiles, installStatePath: plan.installStatePath };
|
|
524
|
+
} catch (error) {
|
|
525
|
+
for (const filePath of writtenFiles.reverse()) {
|
|
526
|
+
try {
|
|
527
|
+
rmSync(filePath, { force: true });
|
|
528
|
+
} catch {
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
throw error;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function readRepoInstallState(statePath) {
|
|
535
|
+
if (!existsSync(statePath)) {
|
|
536
|
+
throw new CaikError(`Repo install state not found: ${statePath}`);
|
|
537
|
+
}
|
|
538
|
+
return JSON.parse(readFileSync(statePath, "utf-8"));
|
|
539
|
+
}
|
|
540
|
+
async function uninstallRepoInstall(plan) {
|
|
541
|
+
const state = readRepoInstallState(plan.installStatePath);
|
|
542
|
+
const removed = [];
|
|
543
|
+
for (const operation of [...state.operations].reverse()) {
|
|
544
|
+
if (existsSync(operation.destinationPath)) {
|
|
545
|
+
rmSync(operation.destinationPath, { force: true });
|
|
546
|
+
removed.push(operation.destinationPath);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (existsSync(plan.installStatePath)) {
|
|
550
|
+
rmSync(plan.installStatePath, { force: true });
|
|
551
|
+
removed.push(plan.installStatePath);
|
|
552
|
+
}
|
|
553
|
+
return removed;
|
|
554
|
+
}
|
|
555
|
+
async function resolveRepoInstallPlan(options) {
|
|
556
|
+
return createRepoInstallPlan({
|
|
557
|
+
id: options.slug,
|
|
558
|
+
slug: options.slug,
|
|
559
|
+
name: options.name,
|
|
560
|
+
description: "",
|
|
561
|
+
primitive: "reference",
|
|
562
|
+
tags: [],
|
|
563
|
+
platforms: [],
|
|
564
|
+
qualityScore: 0,
|
|
565
|
+
qualitySignals: null,
|
|
566
|
+
installCount: 0,
|
|
567
|
+
ecosystemInstalls: 0,
|
|
568
|
+
githubStars: 0,
|
|
569
|
+
sourceCount: 1,
|
|
570
|
+
trustLevel: "pending",
|
|
571
|
+
authorHandle: null,
|
|
572
|
+
authorDisplayName: null,
|
|
573
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
574
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
575
|
+
content: null,
|
|
576
|
+
manifest: options.manifest,
|
|
577
|
+
sourceUrl: options.manifest.url,
|
|
578
|
+
installRef: null,
|
|
579
|
+
familyId: null,
|
|
580
|
+
parentId: null,
|
|
581
|
+
forkRationale: null,
|
|
582
|
+
params: null,
|
|
583
|
+
icon: null,
|
|
584
|
+
author: null
|
|
585
|
+
}, {
|
|
586
|
+
target: options.target,
|
|
587
|
+
profile: options.profileId,
|
|
588
|
+
modules: options.modules,
|
|
589
|
+
includeComponents: options.includeComponents,
|
|
590
|
+
excludeComponents: options.excludeComponents,
|
|
591
|
+
projectRoot: options.cwd
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
function uninstallRepoInstallState(state) {
|
|
595
|
+
void uninstallRepoInstall(state);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
export {
|
|
599
|
+
parseSource,
|
|
600
|
+
listContents,
|
|
601
|
+
fetchRawFile,
|
|
602
|
+
fetchRepoInstallerConfig,
|
|
603
|
+
fetchRepoMetadata,
|
|
604
|
+
isRepoInstallerManifest,
|
|
605
|
+
getRepoInstallerManifest,
|
|
606
|
+
getRepoInstallStatePath,
|
|
607
|
+
createRepoInstallPlan,
|
|
608
|
+
applyRepoInstallPlan,
|
|
609
|
+
readRepoInstallState,
|
|
610
|
+
uninstallRepoInstall,
|
|
611
|
+
resolveRepoInstallPlan,
|
|
612
|
+
uninstallRepoInstallState
|
|
613
|
+
};
|