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.
Files changed (55) hide show
  1. package/README.md +8 -7
  2. package/dist/api-6OX4ICXN.js +9 -0
  3. package/dist/auto-improve-skills-2COKTU5C.js +8 -0
  4. package/dist/autoresearch-Y7WW6L4O.js +24 -0
  5. package/dist/chunk-2YHUDOJL.js +54 -0
  6. package/dist/chunk-3TXNZINH.js +775 -0
  7. package/dist/chunk-5MHNQAV4.js +317 -0
  8. package/dist/chunk-7AIZTHHZ.js +152 -0
  9. package/dist/chunk-D4IM3YRX.js +166 -0
  10. package/dist/chunk-DJJHS7KK.js +62 -0
  11. package/dist/chunk-DKZBQRR3.js +91 -0
  12. package/dist/chunk-FLSHJZLC.js +613 -0
  13. package/dist/chunk-H2ZKCXMJ.js +202 -0
  14. package/dist/chunk-ILMOSMD3.js +83 -0
  15. package/dist/chunk-KYTHKH6V.js +79 -0
  16. package/dist/chunk-LTKHLRM4.js +272 -0
  17. package/dist/chunk-T32AEP3O.js +146 -0
  18. package/dist/chunk-T73Z5UMA.js +14437 -0
  19. package/dist/chunk-TFKT7V7H.js +1545 -0
  20. package/dist/chunk-US4CYDNS.js +524 -0
  21. package/dist/chunk-ZLRN7Q7C.js +27 -0
  22. package/dist/claude-code-6DF4YARB.js +8 -0
  23. package/dist/config-CS7734SA.js +24 -0
  24. package/dist/correction-classifier-TLPKRNLI.js +93 -0
  25. package/dist/cursor-Z4XXDCAM.js +8 -0
  26. package/dist/daemon/autoresearch-2MAEM2YI.js +272 -0
  27. package/dist/daemon/chunk-545XA5CB.js +77 -0
  28. package/dist/daemon/chunk-HEYFAUHL.js +90 -0
  29. package/dist/daemon/chunk-MLKGABMK.js +9 -0
  30. package/dist/daemon/chunk-NJICGNCK.js +150 -0
  31. package/dist/daemon/chunk-OD5NUFH2.js +181 -0
  32. package/dist/daemon/chunk-SM2FSXIP.js +60 -0
  33. package/dist/daemon/chunk-UMDJFPN6.js +163 -0
  34. package/dist/daemon/config-F7HE3JRY.js +23 -0
  35. package/dist/daemon/db-QEXVVTAL.js +15 -0
  36. package/dist/daemon/eval-generator-OR2FAYLB.js +316 -0
  37. package/dist/daemon/improver-TGEK6MPE.js +186 -0
  38. package/dist/daemon/llm-FUJ2TBYT.js +11 -0
  39. package/dist/daemon/nudge-detector-NFRHWZY6.js +140 -0
  40. package/dist/daemon/platform-7N3LQDIB.js +16381 -0
  41. package/dist/daemon/registry-FI4GTO3H.js +20 -0
  42. package/dist/daemon/server.js +356 -0
  43. package/dist/daemon/trace-store-T7XFGQSX.js +19 -0
  44. package/dist/daemon-UXYMG46V.js +85 -0
  45. package/dist/db-TLNRIXLK.js +18 -0
  46. package/dist/eval-generator-GGMRPO3K.js +21 -0
  47. package/dist/eval-runner-EF4K6T5Y.js +15 -0
  48. package/dist/index.js +8033 -568
  49. package/dist/llm-3UUZX6PX.js +12 -0
  50. package/dist/platform-52NREMBS.js +33 -0
  51. package/dist/repo-installer-K6ADOW3E.js +25 -0
  52. package/dist/setup-P744STZE.js +16 -0
  53. package/dist/test-loop-Y7QQE55P.js +127 -0
  54. package/dist/trace-store-FVLMNNDK.js +20 -0
  55. 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
+ };