@agent-loom/loom 1.0.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 (62) hide show
  1. package/README.md +144 -0
  2. package/dist/apply.d.ts +56 -0
  3. package/dist/apply.d.ts.map +1 -0
  4. package/dist/apply.js +97 -0
  5. package/dist/apply.js.map +1 -0
  6. package/dist/cli.d.ts +9 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +503 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/clone.d.ts +38 -0
  11. package/dist/clone.d.ts.map +1 -0
  12. package/dist/clone.js +68 -0
  13. package/dist/clone.js.map +1 -0
  14. package/dist/config.d.ts +14 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +55 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/manifest.d.ts +41 -0
  19. package/dist/manifest.d.ts.map +1 -0
  20. package/dist/manifest.js +454 -0
  21. package/dist/manifest.js.map +1 -0
  22. package/dist/renderers/claude.d.ts +15 -0
  23. package/dist/renderers/claude.d.ts.map +1 -0
  24. package/dist/renderers/claude.js +180 -0
  25. package/dist/renderers/claude.js.map +1 -0
  26. package/dist/renderers/copilot.d.ts +16 -0
  27. package/dist/renderers/copilot.d.ts.map +1 -0
  28. package/dist/renderers/copilot.js +191 -0
  29. package/dist/renderers/copilot.js.map +1 -0
  30. package/dist/repo-clone.d.ts +72 -0
  31. package/dist/repo-clone.d.ts.map +1 -0
  32. package/dist/repo-clone.js +197 -0
  33. package/dist/repo-clone.js.map +1 -0
  34. package/dist/resolve-template.d.ts +32 -0
  35. package/dist/resolve-template.d.ts.map +1 -0
  36. package/dist/resolve-template.js +75 -0
  37. package/dist/resolve-template.js.map +1 -0
  38. package/dist/search-registry.d.ts +31 -0
  39. package/dist/search-registry.d.ts.map +1 -0
  40. package/dist/search-registry.js +96 -0
  41. package/dist/search-registry.js.map +1 -0
  42. package/dist/search.d.ts +20 -0
  43. package/dist/search.d.ts.map +1 -0
  44. package/dist/search.js +51 -0
  45. package/dist/search.js.map +1 -0
  46. package/dist/skill-fetcher.d.ts +40 -0
  47. package/dist/skill-fetcher.d.ts.map +1 -0
  48. package/dist/skill-fetcher.js +99 -0
  49. package/dist/skill-fetcher.js.map +1 -0
  50. package/dist/types.d.ts +297 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +18 -0
  53. package/dist/types.js.map +1 -0
  54. package/dist/validate.d.ts +42 -0
  55. package/dist/validate.d.ts.map +1 -0
  56. package/dist/validate.js +191 -0
  57. package/dist/validate.js.map +1 -0
  58. package/dist/workspaces.d.ts +17 -0
  59. package/dist/workspaces.d.ts.map +1 -0
  60. package/dist/workspaces.js +72 -0
  61. package/dist/workspaces.js.map +1 -0
  62. package/package.json +42 -0
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Repo Clone — Clones manifest repos into the workspace directory.
3
+ *
4
+ * Handles different repo types with appropriate strategies:
5
+ * - Normal GitHub repos → `gh repo clone` (parallel, up to concurrency limit)
6
+ * - ADO repos → `git clone` (interactive prompt, sequential)
7
+ * - Large repos → prompt user (clone / provide existing path / defer to agent)
8
+ * - Sparse repos → `git clone --filter=blob:none --sparse` + sparse-checkout
9
+ * - Skip strategy → skip with message
10
+ *
11
+ * Normal repos are cloned in parallel (default 4 concurrent).
12
+ * Large/ADO repos that require user interaction are processed sequentially.
13
+ * Directory junctions are only created when a user provides an external path
14
+ * for a large/ADO repo — not for repos cloned directly into the workspace.
15
+ */
16
+ import { stat, mkdir } from 'node:fs/promises';
17
+ import { join } from 'node:path';
18
+ import { exec as execCb } from 'node:child_process';
19
+ import { promisify } from 'node:util';
20
+ const execPromised = promisify(execCb);
21
+ // =============================================================================
22
+ // Public API
23
+ // =============================================================================
24
+ /**
25
+ * Derive a directory name from a repo URL (or override with id/name).
26
+ */
27
+ export function deriveRepoDir(url, id) {
28
+ if (id)
29
+ return id;
30
+ const match = url.match(/\/([^/]+?)(?:\.git)?$/);
31
+ return match ? match[1] : 'repo';
32
+ }
33
+ /**
34
+ * Detect whether a repo is a GitHub repo based on URL or repoType.
35
+ */
36
+ function isGitHub(repo) {
37
+ if (repo.repoType === 'github')
38
+ return true;
39
+ if (repo.repoType === 'ado')
40
+ return false;
41
+ return repo.url.includes('github.com');
42
+ }
43
+ /**
44
+ * Check whether a repo should be treated as large / needs prompting.
45
+ */
46
+ function needsPrompt(repo) {
47
+ if (repo.cloneStrategy === 'partial')
48
+ return false;
49
+ if (repo.sparse && repo.paths && repo.paths.length > 0)
50
+ return false;
51
+ return !!(repo.large || repo.repoType === 'ado');
52
+ }
53
+ /**
54
+ * Clone all manifest repos into the workspace directory.
55
+ *
56
+ * Phase 1: Categorize repos (skip, auto-clone, interactive).
57
+ * Phase 2: Clone auto-clone repos in parallel (batched by concurrency limit).
58
+ * Phase 3: Process interactive repos sequentially (user prompts).
59
+ */
60
+ export async function cloneManifestRepos(options) {
61
+ const { repos, workspaceDir, mode, concurrency = 4, execFn = defaultExec, promptFn, linkFn, } = options;
62
+ const result = {
63
+ cloned: [],
64
+ skipped: [],
65
+ deferred: [],
66
+ };
67
+ // --- Phase 1: Categorize repos ---
68
+ const autoClone = [];
69
+ const interactive = [];
70
+ for (const repo of repos) {
71
+ if (repo.cloneStrategy === 'skip') {
72
+ result.skipped.push({ repo, reason: 'cloneStrategy is "skip"' });
73
+ continue;
74
+ }
75
+ if (mode === 'none') {
76
+ result.skipped.push({ repo, reason: 'mode is "none" — skipping all repos' });
77
+ continue;
78
+ }
79
+ const dirName = deriveRepoDir(repo.url, repo.id);
80
+ const targetDir = join(workspaceDir, dirName);
81
+ if (await dirExists(targetDir)) {
82
+ result.skipped.push({ repo, reason: `Directory "${dirName}" already exists` });
83
+ continue;
84
+ }
85
+ if (mode === 'auto' && needsPrompt(repo)) {
86
+ interactive.push({ repo, dirName, targetDir });
87
+ }
88
+ else {
89
+ autoClone.push({ repo, dirName, targetDir });
90
+ }
91
+ }
92
+ // --- Phase 2: Clone auto-clone repos in parallel ---
93
+ for (let i = 0; i < autoClone.length; i += concurrency) {
94
+ const batch = autoClone.slice(i, i + concurrency);
95
+ await Promise.all(batch.map(async ({ repo, targetDir }) => {
96
+ await executeClone(repo, targetDir, execFn);
97
+ result.cloned.push({ repo, path: targetDir });
98
+ }));
99
+ }
100
+ // --- Phase 3: Process interactive repos sequentially ---
101
+ for (const { repo, dirName, targetDir } of interactive) {
102
+ if (!promptFn) {
103
+ result.deferred.push({
104
+ repo,
105
+ reason: `Large/ADO repo — deferred to agent (no interactive prompt available)`,
106
+ });
107
+ continue;
108
+ }
109
+ const repoLabel = repo.name ?? repo.id ?? dirName;
110
+ const message = repo.large
111
+ ? `${repoLabel} is a large repository. Clone, provide existing path, or defer to agent?`
112
+ : `${repoLabel} is an ADO repository. Clone, provide existing path, or defer to agent?`;
113
+ const answer = await promptFn(message, ['clone', 'skip']);
114
+ if (answer === 'clone') {
115
+ await executeClone(repo, targetDir, execFn);
116
+ result.cloned.push({ repo, path: targetDir });
117
+ }
118
+ else if (answer === 'skip') {
119
+ result.deferred.push({ repo, reason: `User chose to defer to agent` });
120
+ }
121
+ else {
122
+ // User provided an external path → validate and create junction
123
+ if (await dirExists(answer)) {
124
+ const linkPath = await createRepoLink(answer, dirName, workspaceDir, linkFn);
125
+ result.cloned.push({ repo, path: answer, linkPath });
126
+ }
127
+ else {
128
+ result.deferred.push({ repo, reason: `User-provided path not found: ${answer}` });
129
+ }
130
+ }
131
+ }
132
+ return result;
133
+ }
134
+ // =============================================================================
135
+ // Internal helpers
136
+ // =============================================================================
137
+ /**
138
+ * Execute the appropriate clone command for a repo.
139
+ */
140
+ async function executeClone(repo, targetDir, execFn) {
141
+ if (repo.sparse && repo.paths && repo.paths.length > 0) {
142
+ // Sparse checkout
143
+ await execFn(`git clone --filter=blob:none --sparse "${repo.url}" "${targetDir}"`);
144
+ for (const p of repo.paths) {
145
+ await execFn(`git -C "${targetDir}" sparse-checkout add "${p}"`);
146
+ }
147
+ }
148
+ else if (repo.cloneStrategy === 'partial') {
149
+ // Blobless clone (all paths, blobs fetched on demand)
150
+ await execFn(`git clone --filter=blob:none "${repo.url}" "${targetDir}"`);
151
+ }
152
+ else if (isGitHub(repo)) {
153
+ // GitHub → use gh repo clone (handles auth)
154
+ await execFn(`gh repo clone "${extractGitHubSlug(repo.url)}" "${targetDir}"`);
155
+ }
156
+ else {
157
+ // ADO or other → use git clone
158
+ await execFn(`git clone "${repo.url}" "${targetDir}"`);
159
+ }
160
+ }
161
+ /**
162
+ * Extract "org/repo" slug from a GitHub URL.
163
+ */
164
+ function extractGitHubSlug(url) {
165
+ const match = url.match(/github\.com\/(.+?)(?:\.git)?$/);
166
+ return match ? match[1] : url;
167
+ }
168
+ async function dirExists(path) {
169
+ try {
170
+ const s = await stat(path);
171
+ return s.isDirectory();
172
+ }
173
+ catch {
174
+ return false;
175
+ }
176
+ }
177
+ /**
178
+ * Create a directory junction/symlink at `repos/<dirName>` pointing to the target.
179
+ * Returns the link path if created, or undefined if linkFn is not provided or link already exists.
180
+ */
181
+ async function createRepoLink(target, dirName, workspaceDir, linkFn) {
182
+ if (!linkFn)
183
+ return undefined;
184
+ const reposDir = join(workspaceDir, 'repos');
185
+ const linkPath = join(reposDir, dirName);
186
+ // Skip if link already exists
187
+ if (await dirExists(linkPath))
188
+ return linkPath;
189
+ // Ensure repos/ directory exists
190
+ await mkdir(reposDir, { recursive: true });
191
+ await linkFn(target, linkPath);
192
+ return linkPath;
193
+ }
194
+ async function defaultExec(cmd) {
195
+ await execPromised(cmd, { timeout: 300_000 });
196
+ }
197
+ //# sourceMappingURL=repo-clone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-clone.js","sourceRoot":"","sources":["../src/repo-clone.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,IAAI,IAAI,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;AAyCvC,gFAAgF;AAChF,aAAa;AACb,gFAAgF;AAEhF;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW,EAAE,EAAW;IACpD,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC;IAClB,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IACjD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,SAAS,QAAQ,CAAC,IAAkB;IAClC,IAAI,IAAI,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC5C,IAAI,IAAI,CAAC,QAAQ,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IAC1C,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;AACzC,CAAC;AAED;;GAEG;AACH,SAAS,WAAW,CAAC,IAAkB;IACrC,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACnD,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACrE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC;AACnD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,OAAyB;IAChE,MAAM,EACJ,KAAK,EACL,YAAY,EACZ,IAAI,EACJ,WAAW,GAAG,CAAC,EACf,MAAM,GAAG,WAAW,EACpB,QAAQ,EACR,MAAM,GACP,GAAG,OAAO,CAAC;IAEZ,MAAM,MAAM,GAAoB;QAC9B,MAAM,EAAE,EAAE;QACV,OAAO,EAAE,EAAE;QACX,QAAQ,EAAE,EAAE;KACb,CAAC;IAEF,oCAAoC;IACpC,MAAM,SAAS,GAAsE,EAAE,CAAC;IACxF,MAAM,WAAW,GAAsE,EAAE,CAAC;IAE1F,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,aAAa,KAAK,MAAM,EAAE,CAAC;YAClC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC,CAAC;YACjE,SAAS;QACX,CAAC;QAED,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;YACpB,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,qCAAqC,EAAE,CAAC,CAAC;YAC7E,SAAS;QACX,CAAC;QAED,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QACjD,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAE9C,IAAI,MAAM,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;YAC/B,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,OAAO,kBAAkB,EAAE,CAAC,CAAC;YAC/E,SAAS;QACX,CAAC;QAED,IAAI,IAAI,KAAK,MAAM,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;YACzC,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;QACjD,CAAC;aAAM,CAAC;YACN,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,sDAAsD;IACtD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,IAAI,WAAW,EAAE,CAAC;QACvD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC;QAClD,MAAM,OAAO,CAAC,GAAG,CACf,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE;YACtC,MAAM,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;YAC5C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAChD,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;IAED,0DAA0D;IAC1D,KAAK,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,WAAW,EAAE,CAAC;QACvD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;gBACnB,IAAI;gBACJ,MAAM,EAAE,sEAAsE;aAC/E,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,IAAI,OAAO,CAAC;QAClD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK;YACxB,CAAC,CAAC,GAAG,SAAS,0EAA0E;YACxF,CAAC,CAAC,GAAG,SAAS,yEAAyE,CAAC;QAE1F,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;QAE1D,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YACvB,MAAM,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;YAC5C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAChD,CAAC;aAAM,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YAC7B,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,8BAA8B,EAAE,CAAC,CAAC;QACzE,CAAC;aAAM,CAAC;YACN,gEAAgE;YAChE,IAAI,MAAM,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5B,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;gBAC7E,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;YACvD,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,iCAAiC,MAAM,EAAE,EAAE,CAAC,CAAC;YACpF,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,gFAAgF;AAChF,mBAAmB;AACnB,gFAAgF;AAEhF;;GAEG;AACH,KAAK,UAAU,YAAY,CACzB,IAAkB,EAClB,SAAiB,EACjB,MAA6C;IAE7C,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvD,kBAAkB;QAClB,MAAM,MAAM,CAAC,0CAA0C,IAAI,CAAC,GAAG,MAAM,SAAS,GAAG,CAAC,CAAC;QACnF,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC3B,MAAM,MAAM,CAAC,WAAW,SAAS,0BAA0B,CAAC,GAAG,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;SAAM,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QAC5C,sDAAsD;QACtD,MAAM,MAAM,CAAC,iCAAiC,IAAI,CAAC,GAAG,MAAM,SAAS,GAAG,CAAC,CAAC;IAC5E,CAAC;SAAM,IAAI,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,4CAA4C;QAC5C,MAAM,MAAM,CAAC,kBAAkB,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC;IAChF,CAAC;SAAM,CAAC;QACN,+BAA+B;QAC/B,MAAM,MAAM,CAAC,cAAc,IAAI,CAAC,GAAG,MAAM,SAAS,GAAG,CAAC,CAAC;IACzD,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB,CAAC,GAAW;IACpC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACzD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AAChC,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,IAAY;IACnC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3B,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,cAAc,CAC3B,MAAc,EACd,OAAe,EACf,YAAoB,EACpB,MAA4D;IAE5D,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAE9B,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAEzC,8BAA8B;IAC9B,IAAI,MAAM,SAAS,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IAE/C,iCAAiC;IACjC,MAAM,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE3C,MAAM,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,GAAW;IACpC,MAAM,YAAY,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AAChD,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Resolve Template Source — auto-resolves a template argument to a local path.
3
+ *
4
+ * When `loom apply <template>` is called without --registry:
5
+ * 1. Check if <template> is a local path containing manifest.yaml → use directly
6
+ * 2. If not, search ~/.loom/config.yaml registries for a matching template
7
+ * 3. Clone the matching registry and resolve the template path
8
+ */
9
+ import type { RegistryConfig } from './types.js';
10
+ export interface ResolveResult {
11
+ templateDir: string;
12
+ registryRoot: string;
13
+ /** Whether the template was resolved from a configured registry (vs local path) */
14
+ fromRegistry: boolean;
15
+ /** Whether the registry was already cached locally */
16
+ alreadyCloned?: boolean;
17
+ }
18
+ export interface ResolveOptions {
19
+ /** Load config override (for testing) */
20
+ loadConfigFn?: () => Promise<{
21
+ registries: RegistryConfig[];
22
+ }>;
23
+ /** Exec override for git clone (for testing) */
24
+ execFn?: (cmd: string) => void;
25
+ }
26
+ /**
27
+ * Resolve a template argument to a local directory path.
28
+ *
29
+ * Falls back to configured registries when the template is not a local path.
30
+ */
31
+ export declare function resolveTemplateSource(template: string, workspaceDir: string, registryRootOverride?: string, options?: ResolveOptions): Promise<ResolveResult>;
32
+ //# sourceMappingURL=resolve-template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-template.d.ts","sourceRoot":"","sources":["../src/resolve-template.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,YAAY,EAAE,OAAO,CAAC;IACtB,sDAAsD;IACtD,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,yCAAyC;IACzC,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,UAAU,EAAE,cAAc,EAAE,CAAA;KAAE,CAAC,CAAC;IAC/D,gDAAgD;IAChD,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,oBAAoB,CAAC,EAAE,MAAM,EAC7B,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,aAAa,CAAC,CAuDxB"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Resolve Template Source — auto-resolves a template argument to a local path.
3
+ *
4
+ * When `loom apply <template>` is called without --registry:
5
+ * 1. Check if <template> is a local path containing manifest.yaml → use directly
6
+ * 2. If not, search ~/.loom/config.yaml registries for a matching template
7
+ * 3. Clone the matching registry and resolve the template path
8
+ */
9
+ import { stat } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ import { loadConfig } from './config.js';
12
+ import { cloneRegistry, resolveTemplatePath } from './clone.js';
13
+ /**
14
+ * Resolve a template argument to a local directory path.
15
+ *
16
+ * Falls back to configured registries when the template is not a local path.
17
+ */
18
+ export async function resolveTemplateSource(template, workspaceDir, registryRootOverride, options) {
19
+ // 1. Check if template is a local path with manifest.yaml
20
+ if (await fileExists(join(template, 'manifest.yaml'))) {
21
+ return {
22
+ templateDir: template,
23
+ registryRoot: registryRootOverride ?? template.replace(/[/\\]templates[/\\].*$/, ''),
24
+ fromRegistry: false,
25
+ };
26
+ }
27
+ // 2. Search configured registries
28
+ const config = options?.loadConfigFn
29
+ ? await options.loadConfigFn()
30
+ : await loadConfig();
31
+ if (config.registries.length === 0) {
32
+ throw new Error(`Template "${template}" is not a local path and no registries are configured.\n` +
33
+ ` Add a registry: loom registry add <name> <url>\n` +
34
+ ` Or provide a URL: loom apply ${template} --registry <url>`);
35
+ }
36
+ // Try to find and clone a registry that contains this template
37
+ for (const reg of config.registries) {
38
+ try {
39
+ const cloneResult = await cloneRegistry({
40
+ workspaceDir,
41
+ url: reg.url,
42
+ name: reg.name,
43
+ execFn: options?.execFn,
44
+ });
45
+ const candidatePath = resolveTemplatePath(cloneResult.registryDir, template);
46
+ if (await fileExists(join(candidatePath, 'manifest.yaml'))) {
47
+ return {
48
+ templateDir: candidatePath,
49
+ registryRoot: cloneResult.registryDir,
50
+ fromRegistry: true,
51
+ alreadyCloned: cloneResult.alreadyExists,
52
+ };
53
+ }
54
+ }
55
+ catch {
56
+ // Clone failed for this registry — try next
57
+ continue;
58
+ }
59
+ }
60
+ // 3. No registry has this template
61
+ const regNames = config.registries.map((r) => r.name).join(', ');
62
+ throw new Error(`Template "${template}" not found locally or in any configured registry (${regNames}).\n` +
63
+ ` Check available templates: loom search ${template}\n` +
64
+ ` Or provide a registry URL: loom apply ${template} --registry <url>`);
65
+ }
66
+ async function fileExists(path) {
67
+ try {
68
+ const s = await stat(path);
69
+ return s.isFile() || s.isDirectory();
70
+ }
71
+ catch {
72
+ return false;
73
+ }
74
+ }
75
+ //# sourceMappingURL=resolve-template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-template.js","sourceRoot":"","sources":["../src/resolve-template.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAmBhE;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,QAAgB,EAChB,YAAoB,EACpB,oBAA6B,EAC7B,OAAwB;IAExB,0DAA0D;IAC1D,IAAI,MAAM,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC;QACtD,OAAO;YACL,WAAW,EAAE,QAAQ;YACrB,YAAY,EAAE,oBAAoB,IAAI,QAAQ,CAAC,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC;YACpF,YAAY,EAAE,KAAK;SACpB,CAAC;IACJ,CAAC;IAED,kCAAkC;IAClC,MAAM,MAAM,GAAG,OAAO,EAAE,YAAY;QAClC,CAAC,CAAC,MAAM,OAAO,CAAC,YAAY,EAAE;QAC9B,CAAC,CAAC,MAAM,UAAU,EAAE,CAAC;IAEvB,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CACb,aAAa,QAAQ,2DAA2D;YAChF,oDAAoD;YACpD,kCAAkC,QAAQ,mBAAmB,CAC9D,CAAC;IACJ,CAAC;IAED,+DAA+D;IAC/D,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,MAAM,aAAa,CAAC;gBACtC,YAAY;gBACZ,GAAG,EAAE,GAAG,CAAC,GAAG;gBACZ,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,MAAM,EAAE,OAAO,EAAE,MAAM;aACxB,CAAC,CAAC;YAEH,MAAM,aAAa,GAAG,mBAAmB,CAAC,WAAW,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;YAC7E,IAAI,MAAM,UAAU,CAAC,IAAI,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC;gBAC3D,OAAO;oBACL,WAAW,EAAE,aAAa;oBAC1B,YAAY,EAAE,WAAW,CAAC,WAAW;oBACrC,YAAY,EAAE,IAAI;oBAClB,aAAa,EAAE,WAAW,CAAC,aAAa;iBACzC,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,4CAA4C;YAC5C,SAAS;QACX,CAAC;IACH,CAAC;IAED,mCAAmC;IACnC,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjE,MAAM,IAAI,KAAK,CACb,aAAa,QAAQ,sDAAsD,QAAQ,MAAM;QACzF,4CAA4C,QAAQ,IAAI;QACxD,2CAA2C,QAAQ,mBAAmB,CACvE,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,IAAY;IACpC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3B,OAAO,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Search Registry Resolution — resolves registry names to local index.yaml paths.
3
+ *
4
+ * Workflow:
5
+ * 1. Read ~/.loom/config.yaml for registered registries
6
+ * 2. Check .loom/registries/<name>/index.yaml for cached clones
7
+ * 3. If not cached, auto-clone (shallow) and cache
8
+ * 4. Return paths for search
9
+ */
10
+ export interface RegistryResolutionOptions {
11
+ /** Search a specific registry by name (from config.yaml). If omitted, resolves all registries. */
12
+ registryName?: string;
13
+ /** Workspace directory (for .loom/registries/ cache) */
14
+ workspaceDir?: string;
15
+ /** Override config directory (for testing — defaults to ~/.loom) */
16
+ configDir?: string;
17
+ /** Inject exec function for testing (used for auto-clone) */
18
+ execFn?: (cmd: string) => void;
19
+ /** Also check workspace root for index.yaml (workspace is itself a registry) */
20
+ includeWorkspaceRoot?: boolean;
21
+ }
22
+ /**
23
+ * Resolve registry index paths from config + local cache.
24
+ *
25
+ * Returns an array of { name, path } suitable for passing to searchTemplates().
26
+ */
27
+ export declare function resolveRegistryIndexPaths(options: RegistryResolutionOptions): Promise<Array<{
28
+ name: string;
29
+ path: string;
30
+ }>>;
31
+ //# sourceMappingURL=search-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-registry.d.ts","sourceRoot":"","sources":["../src/search-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH,MAAM,WAAW,yBAAyB;IACxC,kGAAkG;IAClG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,gFAAgF;IAChF,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAMD;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAiChD"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Search Registry Resolution — resolves registry names to local index.yaml paths.
3
+ *
4
+ * Workflow:
5
+ * 1. Read ~/.loom/config.yaml for registered registries
6
+ * 2. Check .loom/registries/<name>/index.yaml for cached clones
7
+ * 3. If not cached, auto-clone (shallow) and cache
8
+ * 4. Return paths for search
9
+ */
10
+ import { readFile, stat, mkdir } from 'node:fs/promises';
11
+ import { join } from 'node:path';
12
+ import { homedir } from 'node:os';
13
+ import { execSync } from 'node:child_process';
14
+ import yaml from 'js-yaml';
15
+ // =============================================================================
16
+ // Public API
17
+ // =============================================================================
18
+ /**
19
+ * Resolve registry index paths from config + local cache.
20
+ *
21
+ * Returns an array of { name, path } suitable for passing to searchTemplates().
22
+ */
23
+ export async function resolveRegistryIndexPaths(options) {
24
+ const { registryName, workspaceDir = process.cwd(), configDir = join(homedir(), '.loom'), execFn, includeWorkspaceRoot, } = options;
25
+ const results = [];
26
+ // Load config
27
+ const config = await loadConfigFrom(configDir);
28
+ const registries = registryName
29
+ ? config.registries.filter((r) => r.name === registryName)
30
+ : config.registries;
31
+ for (const reg of registries) {
32
+ const indexPath = await resolveOneRegistry(reg, workspaceDir, execFn);
33
+ if (indexPath) {
34
+ results.push({ name: reg.name, path: indexPath });
35
+ }
36
+ }
37
+ // Also check workspace root if requested
38
+ if (includeWorkspaceRoot) {
39
+ const wsIndexPath = join(workspaceDir, 'index.yaml');
40
+ if (await fileExists(wsIndexPath)) {
41
+ results.push({ name: '__workspace__', path: wsIndexPath });
42
+ }
43
+ }
44
+ return results;
45
+ }
46
+ // =============================================================================
47
+ // Internal helpers
48
+ // =============================================================================
49
+ /**
50
+ * Try to resolve a single registry to a local index.yaml path.
51
+ * Returns the path if found/cloned, or undefined if unavailable.
52
+ */
53
+ async function resolveOneRegistry(reg, workspaceDir, execFn) {
54
+ // Check cached clone in .loom/registries/<name>/
55
+ const cachedDir = join(workspaceDir, '.loom', 'registries', reg.name);
56
+ const cachedIndex = join(cachedDir, 'index.yaml');
57
+ if (await fileExists(cachedIndex)) {
58
+ return cachedIndex;
59
+ }
60
+ // Not cached — try to auto-clone
61
+ const exec = execFn ?? ((cmd) => { execSync(cmd, { stdio: 'pipe' }); });
62
+ if (reg.url) {
63
+ try {
64
+ await mkdir(join(workspaceDir, '.loom', 'registries'), { recursive: true });
65
+ exec(`git clone --depth 1 "${reg.url}" "${cachedDir}"`);
66
+ if (await fileExists(cachedIndex)) {
67
+ return cachedIndex;
68
+ }
69
+ }
70
+ catch {
71
+ // Clone failed — skip silently
72
+ }
73
+ }
74
+ return undefined;
75
+ }
76
+ async function loadConfigFrom(configDir) {
77
+ try {
78
+ const configPath = join(configDir, 'config.yaml');
79
+ const raw = await readFile(configPath, 'utf-8');
80
+ const parsed = yaml.load(raw);
81
+ return parsed ?? { registries: [] };
82
+ }
83
+ catch {
84
+ return { registries: [] };
85
+ }
86
+ }
87
+ async function fileExists(path) {
88
+ try {
89
+ const s = await stat(path);
90
+ return s.isFile();
91
+ }
92
+ catch {
93
+ return false;
94
+ }
95
+ }
96
+ //# sourceMappingURL=search-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-registry.js","sourceRoot":"","sources":["../src/search-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Search — searches index.yaml files across configured registries.
3
+ */
4
+ import type { RegistryTemplateEntry, AgentRegistryEntry } from './types.js';
5
+ export interface SearchResult extends RegistryTemplateEntry {
6
+ registry: string;
7
+ /** If this result matched at agent level, the matching agent info */
8
+ matchedAgent?: AgentRegistryEntry;
9
+ /** Parent team ID (set when matchedAgent is present) */
10
+ teamId?: string;
11
+ }
12
+ /**
13
+ * Search templates across local registry index files.
14
+ * Searches both team-level and agent-level entries.
15
+ */
16
+ export declare function searchTemplates(indexPaths: Array<{
17
+ name: string;
18
+ path: string;
19
+ }>, keyword: string): Promise<SearchResult[]>;
20
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAiB,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE3F,MAAM,WAAW,YAAa,SAAQ,qBAAqB;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EACjD,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,EAAE,CAAC,CA+CzB"}
package/dist/search.js ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Search — searches index.yaml files across configured registries.
3
+ */
4
+ import { readFile } from 'node:fs/promises';
5
+ import yaml from 'js-yaml';
6
+ /**
7
+ * Search templates across local registry index files.
8
+ * Searches both team-level and agent-level entries.
9
+ */
10
+ export async function searchTemplates(indexPaths, keyword) {
11
+ const results = [];
12
+ const lower = keyword.toLowerCase();
13
+ for (const { name, path } of indexPaths) {
14
+ try {
15
+ const raw = await readFile(path, 'utf-8');
16
+ const index = yaml.load(raw);
17
+ for (const tmpl of index.templates) {
18
+ const teamMatches = tmpl.id.toLowerCase().includes(lower) ||
19
+ tmpl.name.toLowerCase().includes(lower) ||
20
+ tmpl.description.toLowerCase().includes(lower) ||
21
+ tmpl.tags.some((t) => t.toLowerCase().includes(lower));
22
+ if (teamMatches) {
23
+ results.push({ ...tmpl, registry: name });
24
+ }
25
+ // Also search inside nested agents
26
+ if (tmpl.agents) {
27
+ for (const agent of tmpl.agents) {
28
+ const agentMatches = agent.id.toLowerCase().includes(lower) ||
29
+ agent.name.toLowerCase().includes(lower) ||
30
+ agent.description.toLowerCase().includes(lower) ||
31
+ agent.tags.some((t) => t.toLowerCase().includes(lower));
32
+ // Only add agent result if the team itself didn't already match
33
+ if (agentMatches && !teamMatches) {
34
+ results.push({
35
+ ...tmpl,
36
+ registry: name,
37
+ matchedAgent: agent,
38
+ teamId: tmpl.id,
39
+ });
40
+ }
41
+ }
42
+ }
43
+ }
44
+ }
45
+ catch {
46
+ // Skip unreadable registries
47
+ }
48
+ }
49
+ return results;
50
+ }
51
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,IAAI,MAAM,SAAS,CAAC;AAW3B;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,UAAiD,EACjD,OAAe;IAEf,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAEpC,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,UAAU,EAAE,CAAC;QACxC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAkB,CAAC;YAE9C,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;gBACnC,MAAM,WAAW,GACf,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;oBACrC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;oBACvC,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;oBAC9C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;gBAEzD,IAAI,WAAW,EAAE,CAAC;oBAChB,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC5C,CAAC;gBAED,mCAAmC;gBACnC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;oBAChB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;wBAChC,MAAM,YAAY,GAChB,KAAK,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;4BACtC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;4BACxC,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;4BAC/C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;wBAE1D,gEAAgE;wBAChE,IAAI,YAAY,IAAI,CAAC,WAAW,EAAE,CAAC;4BACjC,OAAO,CAAC,IAAI,CAAC;gCACX,GAAG,IAAI;gCACP,QAAQ,EAAE,IAAI;gCACd,YAAY,EAAE,KAAK;gCACnB,MAAM,EAAE,IAAI,CAAC,EAAE;6BAChB,CAAC,CAAC;wBACL,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,6BAA6B;QAC/B,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Skill Fetcher — Resolves registry skills to local content
3
+ *
4
+ * Downloads/fetches skill content from external registries (craft, skills.sh)
5
+ * and converts them to local skills that can be placed in the workspace.
6
+ */
7
+ import type { ResolvedSkill } from './types.js';
8
+ /**
9
+ * A function that fetches skill content from a registry.
10
+ * Returns the SKILL.md content as a string.
11
+ */
12
+ export type SkillFetcher = (registry: string, ref: string) => Promise<string>;
13
+ /**
14
+ * Create a mock fetcher backed by a static map (for testing and offline use).
15
+ */
16
+ export declare function createMockFetcher(skills: Record<string, string>): SkillFetcher;
17
+ /**
18
+ * Create a fetcher for craft skills.
19
+ *
20
+ * Tries to fetch SKILL.md from the craft-skills GitHub repo.
21
+ * Falls back to a stub SKILL.md if the fetch fails.
22
+ *
23
+ * Supports optional authentication via tokenFn for private repos.
24
+ */
25
+ export declare function createCraftFetcher(options?: {
26
+ baseUrl?: string;
27
+ tokenFn?: () => string | undefined;
28
+ fetchFn?: typeof globalThis.fetch;
29
+ }): SkillFetcher;
30
+ /**
31
+ * Resolve registry skills by fetching their content and converting
32
+ * them to local skills. If no fetcher is provided, registry skills
33
+ * are passed through unchanged.
34
+ *
35
+ * @param skills - Array of resolved skills (may include registry type)
36
+ * @param fetcher - Optional function to fetch skill content
37
+ * @returns Updated skills array with registry skills converted to local
38
+ */
39
+ export declare function resolveRegistrySkills(skills: ResolvedSkill[], fetcher?: SkillFetcher): Promise<ResolvedSkill[]>;
40
+ //# sourceMappingURL=skill-fetcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-fetcher.d.ts","sourceRoot":"","sources":["../src/skill-fetcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE9E;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,YAAY,CAQd;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACnC,OAAO,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACnC,GACA,YAAY,CA+Bd;AAkBD;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,aAAa,EAAE,EACvB,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,aAAa,EAAE,CAAC,CAqB1B"}