@agent-loom/loom 1.0.2 → 1.0.3

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 (145) hide show
  1. package/README.md +69 -0
  2. package/dist/acp/client.d.ts +182 -0
  3. package/dist/acp/client.d.ts.map +1 -0
  4. package/dist/acp/client.js +432 -0
  5. package/dist/acp/client.js.map +1 -0
  6. package/dist/acp/index.d.ts +5 -0
  7. package/dist/acp/index.d.ts.map +1 -0
  8. package/dist/acp/index.js +3 -0
  9. package/dist/acp/index.js.map +1 -0
  10. package/dist/acp/run.d.ts +41 -0
  11. package/dist/acp/run.d.ts.map +1 -0
  12. package/dist/acp/run.js +32 -0
  13. package/dist/acp/run.js.map +1 -0
  14. package/dist/apply.d.ts +15 -6
  15. package/dist/apply.d.ts.map +1 -1
  16. package/dist/apply.js +78 -49
  17. package/dist/apply.js.map +1 -1
  18. package/dist/chat/chat.d.ts +108 -0
  19. package/dist/chat/chat.d.ts.map +1 -0
  20. package/dist/chat/chat.js +221 -0
  21. package/dist/chat/chat.js.map +1 -0
  22. package/dist/chat/discovery.d.ts +30 -0
  23. package/dist/chat/discovery.d.ts.map +1 -0
  24. package/dist/chat/discovery.js +68 -0
  25. package/dist/chat/discovery.js.map +1 -0
  26. package/dist/chat/frontmatter.d.ts +12 -0
  27. package/dist/chat/frontmatter.d.ts.map +1 -0
  28. package/dist/chat/frontmatter.js +11 -0
  29. package/dist/chat/frontmatter.js.map +1 -0
  30. package/dist/chat/index.d.ts +16 -0
  31. package/dist/chat/index.d.ts.map +1 -0
  32. package/dist/chat/index.js +11 -0
  33. package/dist/chat/index.js.map +1 -0
  34. package/dist/chat/registry.d.ts +73 -0
  35. package/dist/chat/registry.d.ts.map +1 -0
  36. package/dist/chat/registry.js +118 -0
  37. package/dist/chat/registry.js.map +1 -0
  38. package/dist/chat/resolve-agent.d.ts +39 -0
  39. package/dist/chat/resolve-agent.d.ts.map +1 -0
  40. package/dist/chat/resolve-agent.js +36 -0
  41. package/dist/chat/resolve-agent.js.map +1 -0
  42. package/dist/chat/suggest.d.ts +20 -0
  43. package/dist/chat/suggest.d.ts.map +1 -0
  44. package/dist/chat/suggest.js +55 -0
  45. package/dist/chat/suggest.js.map +1 -0
  46. package/dist/cli.js +627 -75
  47. package/dist/cli.js.map +1 -1
  48. package/dist/clone.d.ts +21 -3
  49. package/dist/clone.d.ts.map +1 -1
  50. package/dist/clone.js +240 -12
  51. package/dist/clone.js.map +1 -1
  52. package/dist/copilot/mcp.d.ts +48 -0
  53. package/dist/copilot/mcp.d.ts.map +1 -0
  54. package/dist/copilot/mcp.js +146 -0
  55. package/dist/copilot/mcp.js.map +1 -0
  56. package/dist/copilot/resolve.d.ts +33 -0
  57. package/dist/copilot/resolve.d.ts.map +1 -0
  58. package/dist/copilot/resolve.js +96 -0
  59. package/dist/copilot/resolve.js.map +1 -0
  60. package/dist/copilot/spawn.d.ts +51 -0
  61. package/dist/copilot/spawn.d.ts.map +1 -0
  62. package/dist/copilot/spawn.js +132 -0
  63. package/dist/copilot/spawn.js.map +1 -0
  64. package/dist/index.d.ts +19 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +15 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/launch/index.d.ts +10 -0
  69. package/dist/launch/index.d.ts.map +1 -0
  70. package/dist/launch/index.js +9 -0
  71. package/dist/launch/index.js.map +1 -0
  72. package/dist/launch/stage.d.ts +62 -0
  73. package/dist/launch/stage.d.ts.map +1 -0
  74. package/dist/launch/stage.js +108 -0
  75. package/dist/launch/stage.js.map +1 -0
  76. package/dist/manifest.d.ts +165 -18
  77. package/dist/manifest.d.ts.map +1 -1
  78. package/dist/manifest.js +980 -225
  79. package/dist/manifest.js.map +1 -1
  80. package/dist/renderers/claude.d.ts +5 -0
  81. package/dist/renderers/claude.d.ts.map +1 -1
  82. package/dist/renderers/claude.js +17 -3
  83. package/dist/renderers/claude.js.map +1 -1
  84. package/dist/renderers/copilot.d.ts +1 -1
  85. package/dist/renderers/copilot.d.ts.map +1 -1
  86. package/dist/renderers/copilot.js +205 -22
  87. package/dist/renderers/copilot.js.map +1 -1
  88. package/dist/repo-clone.js +17 -11
  89. package/dist/repo-clone.js.map +1 -1
  90. package/dist/resolve-template.d.ts +12 -4
  91. package/dist/resolve-template.d.ts.map +1 -1
  92. package/dist/resolve-template.js +39 -8
  93. package/dist/resolve-template.js.map +1 -1
  94. package/dist/run/index.d.ts +4 -0
  95. package/dist/run/index.d.ts.map +1 -0
  96. package/dist/run/index.js +2 -0
  97. package/dist/run/index.js.map +1 -0
  98. package/dist/run/run.d.ts +143 -0
  99. package/dist/run/run.d.ts.map +1 -0
  100. package/dist/run/run.js +406 -0
  101. package/dist/run/run.js.map +1 -0
  102. package/dist/search-registry.d.ts +10 -3
  103. package/dist/search-registry.d.ts.map +1 -1
  104. package/dist/search-registry.js +16 -16
  105. package/dist/search-registry.js.map +1 -1
  106. package/dist/sessions/index.d.ts +16 -0
  107. package/dist/sessions/index.d.ts.map +1 -0
  108. package/dist/sessions/index.js +15 -0
  109. package/dist/sessions/index.js.map +1 -0
  110. package/dist/sessions/store.d.ts +56 -0
  111. package/dist/sessions/store.d.ts.map +1 -0
  112. package/dist/sessions/store.js +220 -0
  113. package/dist/sessions/store.js.map +1 -0
  114. package/dist/sessions/types.d.ts +62 -0
  115. package/dist/sessions/types.d.ts.map +1 -0
  116. package/dist/sessions/types.js +5 -0
  117. package/dist/sessions/types.js.map +1 -0
  118. package/dist/skill-fetcher.d.ts.map +1 -1
  119. package/dist/skill-fetcher.js +5 -6
  120. package/dist/skill-fetcher.js.map +1 -1
  121. package/dist/types.d.ts +123 -41
  122. package/dist/types.d.ts.map +1 -1
  123. package/dist/types.js +12 -0
  124. package/dist/types.js.map +1 -1
  125. package/dist/util/binary-cache.d.ts +53 -0
  126. package/dist/util/binary-cache.d.ts.map +1 -0
  127. package/dist/util/binary-cache.js +211 -0
  128. package/dist/util/binary-cache.js.map +1 -0
  129. package/dist/util/frontmatter.d.ts +53 -0
  130. package/dist/util/frontmatter.d.ts.map +1 -0
  131. package/dist/util/frontmatter.js +85 -0
  132. package/dist/util/frontmatter.js.map +1 -0
  133. package/dist/util/loom-home.d.ts +19 -0
  134. package/dist/util/loom-home.d.ts.map +1 -0
  135. package/dist/util/loom-home.js +37 -0
  136. package/dist/util/loom-home.js.map +1 -0
  137. package/dist/util/workspace-folder.d.ts +29 -0
  138. package/dist/util/workspace-folder.d.ts.map +1 -0
  139. package/dist/util/workspace-folder.js +43 -0
  140. package/dist/util/workspace-folder.js.map +1 -0
  141. package/dist/validate.d.ts +7 -1
  142. package/dist/validate.d.ts.map +1 -1
  143. package/dist/validate.js +90 -17
  144. package/dist/validate.js.map +1 -1
  145. package/package.json +31 -2
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA2CH,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,CAAC;AACxE,CAAC;AAOD,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,CAAC;AACxE,CAAC;AAQD,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,UAAU,IAAI,KAAK,CAAC;AAC5E,CAAC;AAmCD,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,MAAM,IAAI,KAAK,CAAC;AACzF,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA8CH,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,CAAC;AACxE,CAAC;AAOD,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,CAAC;AACxE,CAAC;AAQD,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,UAAU,IAAI,KAAK,CAAC;AAC5E,CAAC;AAkBD,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,UAAU,IAAI,KAAK;QACnB,KAAK,CAAC,OAAO,CAAE,KAA+B,CAAC,QAAQ,CAAC,CACzD,CAAC;AACJ,CAAC;AA2BD,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,UAAU,IAAI,KAAK;QACnB,KAAK,CAAC,OAAO,CAAE,KAA+B,CAAC,QAAQ,CAAC,CACzD,CAAC;AACJ,CAAC;AA6DD,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,MAAM,IAAI,KAAK,CAAC;AACzF,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Binary cache for MCP servers declared via dobby's `binary:` block.
3
+ *
4
+ * Lazy-downloads a release asset from a GitHub release into
5
+ * ~/.loom/mcp-binaries/<owner>_<repo>/<version>/<filename> on first use.
6
+ * Subsequent launches reuse the cached file.
7
+ *
8
+ * Resolves the {os}, {arch}, {ext} variables in the pattern using the
9
+ * modern Go-style convention that GitHub Action release uploaders typically
10
+ * use (windows / linux / darwin x amd64 / arm64). If a manifest in the
11
+ * wild uses a different convention, we try the alternatives via the
12
+ * release-asset listing before giving up.
13
+ *
14
+ * Auth: when the spec declares auth.type='github' we attach a Bearer
15
+ * token from `gh auth token`. Public release assets work without auth.
16
+ *
17
+ * Security: filename is validated as a basename only (alphanumerics,
18
+ * dot, underscore, hyphen). No directory separators, no shell metachars.
19
+ */
20
+ export interface BinarySpec {
21
+ source: 'github';
22
+ repo: string;
23
+ path: string;
24
+ pattern: string;
25
+ version: string;
26
+ auth?: {
27
+ type: string;
28
+ };
29
+ }
30
+ export interface BinaryResolveResult {
31
+ /** Absolute path to the cached binary file. */
32
+ cachedPath: string;
33
+ /** Whether the binary was downloaded this call (vs already cached). */
34
+ downloaded: boolean;
35
+ /** The resolved asset name on the GitHub release. */
36
+ assetName: string;
37
+ }
38
+ /**
39
+ * Resolve a `binary:` spec to a local cached path, downloading from the
40
+ * GitHub release on cache miss. Returns undefined if resolution fails for
41
+ * any reason (validation, network, missing asset). Errors are logged
42
+ * via the `log` callback so the caller can surface them in stderr.
43
+ */
44
+ export declare function resolveBinaryToCache(binary: BinarySpec, log?: (msg: string) => void): Promise<BinaryResolveResult | undefined>;
45
+ /**
46
+ * Expand {os}, {arch}, {ext} in a release-asset pattern into a small set
47
+ * of likely candidates. The canonical convention (windows/linux/darwin x
48
+ * amd64/arm64) is tried first; we fall back to a couple of legacy aliases
49
+ * (win, macos, x64) so manifests authored against older binaries still
50
+ * resolve.
51
+ */
52
+ export declare function expandPatternVariants(pattern: string): string[];
53
+ //# sourceMappingURL=binary-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binary-cache.d.ts","sourceRoot":"","sources":["../../src/util/binary-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AASH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,QAAQ,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,UAAU,EAAE,OAAO,CAAC;IACpB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;CACnB;AAID;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,UAAU,EAClB,GAAG,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAe,GACpC,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAwF1C;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CA2B/D"}
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Binary cache for MCP servers declared via dobby's `binary:` block.
3
+ *
4
+ * Lazy-downloads a release asset from a GitHub release into
5
+ * ~/.loom/mcp-binaries/<owner>_<repo>/<version>/<filename> on first use.
6
+ * Subsequent launches reuse the cached file.
7
+ *
8
+ * Resolves the {os}, {arch}, {ext} variables in the pattern using the
9
+ * modern Go-style convention that GitHub Action release uploaders typically
10
+ * use (windows / linux / darwin x amd64 / arm64). If a manifest in the
11
+ * wild uses a different convention, we try the alternatives via the
12
+ * release-asset listing before giving up.
13
+ *
14
+ * Auth: when the spec declares auth.type='github' we attach a Bearer
15
+ * token from `gh auth token`. Public release assets work without auth.
16
+ *
17
+ * Security: filename is validated as a basename only (alphanumerics,
18
+ * dot, underscore, hyphen). No directory separators, no shell metachars.
19
+ */
20
+ import { existsSync, mkdirSync, statSync } from 'node:fs';
21
+ import { writeFile, chmod } from 'node:fs/promises';
22
+ import { join } from 'node:path';
23
+ import { platform } from 'node:os';
24
+ import { execSync } from 'node:child_process';
25
+ import { loomHomeDir } from './loom-home.js';
26
+ const FILENAME_RE = /^[A-Za-z0-9._-]+$/;
27
+ /**
28
+ * Resolve a `binary:` spec to a local cached path, downloading from the
29
+ * GitHub release on cache miss. Returns undefined if resolution fails for
30
+ * any reason (validation, network, missing asset). Errors are logged
31
+ * via the `log` callback so the caller can surface them in stderr.
32
+ */
33
+ export async function resolveBinaryToCache(binary, log = () => { }) {
34
+ // Try the canonical Go-style convention first; fall back to legacy
35
+ // alternatives if the resolved filename doesn't exist as a release asset.
36
+ const candidates = expandPatternVariants(binary.pattern);
37
+ // Validate every candidate as a safe basename before attempting any
38
+ // network call. This closes the same shell-injection / path-traversal
39
+ // attack surface as the synchronous resolver.
40
+ for (const filename of candidates) {
41
+ if (!FILENAME_RE.test(filename)) {
42
+ log(` Binary ${binary.repo}: rejecting filename "${filename}" -- pattern must yield a basename (alphanumerics, dot, underscore, hyphen)`);
43
+ return undefined;
44
+ }
45
+ }
46
+ const repoSlug = binary.repo.replace(/[/\\]/g, '_');
47
+ const cacheDir = join(loomHomeDir(), 'mcp-binaries', repoSlug, binary.version);
48
+ mkdirSync(cacheDir, { recursive: true });
49
+ // Cache hit: any candidate filename present on disk wins.
50
+ for (const filename of candidates) {
51
+ const cachedPath = join(cacheDir, filename);
52
+ if (existsSync(cachedPath) && statSync(cachedPath).size > 0) {
53
+ log(` Binary ${binary.repo}: cache hit ${cachedPath}`);
54
+ return { cachedPath, downloaded: false, assetName: filename };
55
+ }
56
+ }
57
+ // Build candidate URLs. The `binary:` spec describes WHERE the asset lives;
58
+ // in the wild we see two conventions:
59
+ // 1. GitHub releases — typical for repos that ship via tagged releases.
60
+ // URL: https://github.com/<repo>/releases/download/v<version>/<filename>
61
+ // 2. Source-tree raw checkout — used when the repo checks binaries into
62
+ // a path like `tools/<name>/current/`. The `path:` field in the spec
63
+ // points at that directory; the file lives at
64
+ // https://raw.githubusercontent.com/<repo>/<ref>/<path>/<filename>
65
+ //
66
+ // We try both, with a few likely refs for the raw form. First successful
67
+ // download (200 status, non-HTML body, valid magic-bytes for .exe) wins.
68
+ const tag = binary.version.startsWith('v') ? binary.version : `v${binary.version}`;
69
+ const refsToTry = Array.from(new Set([
70
+ tag, // 'v1.0.0' (release tag)
71
+ binary.version, // '1.0.0' (branch / tag without v)
72
+ 'main', // default branch on most repos
73
+ 'master', // older repos
74
+ ]));
75
+ const urlCandidates = [];
76
+ for (const filename of candidates) {
77
+ // Releases URL: tag-style refs
78
+ urlCandidates.push({
79
+ url: `https://github.com/${binary.repo}/releases/download/${tag}/${filename}`,
80
+ kind: 'release',
81
+ });
82
+ // Raw-content URL: each ref + the spec's `path` subdir
83
+ if (binary.path) {
84
+ const pathPart = binary.path.replace(/^\/|\/$/g, '');
85
+ for (const ref of refsToTry) {
86
+ urlCandidates.push({
87
+ url: `https://raw.githubusercontent.com/${binary.repo}/${ref}/${pathPart}/${filename}`,
88
+ kind: 'raw',
89
+ });
90
+ }
91
+ }
92
+ }
93
+ // Try each candidate URL until one downloads successfully. Each failure
94
+ // is logged so the user has visibility if every attempt misses.
95
+ for (const { url } of urlCandidates) {
96
+ // Filename to cache as: pull from the URL
97
+ const filename = url.substring(url.lastIndexOf('/') + 1);
98
+ const cachedPath = join(cacheDir, filename);
99
+ try {
100
+ await downloadFile(url, cachedPath, binary.auth?.type === 'github');
101
+ if (platform() !== 'win32') {
102
+ await chmod(cachedPath, 0o755);
103
+ }
104
+ log(` Binary ${binary.repo}: downloaded ${filename} -> ${cachedPath}`);
105
+ return { cachedPath, downloaded: true, assetName: filename };
106
+ }
107
+ catch (err) {
108
+ log(` Binary ${binary.repo}: ${url} -> ${err.message}`);
109
+ // Try next URL
110
+ }
111
+ }
112
+ log(` Binary ${binary.repo}: failed to download from any of ${urlCandidates.length} candidate URLs (tried release tag ${tag} and raw paths under refs ${refsToTry.join(', ')}).`);
113
+ return undefined;
114
+ }
115
+ /**
116
+ * Expand {os}, {arch}, {ext} in a release-asset pattern into a small set
117
+ * of likely candidates. The canonical convention (windows/linux/darwin x
118
+ * amd64/arm64) is tried first; we fall back to a couple of legacy aliases
119
+ * (win, macos, x64) so manifests authored against older binaries still
120
+ * resolve.
121
+ */
122
+ export function expandPatternVariants(pattern) {
123
+ const variants = [];
124
+ const seen = new Set();
125
+ // Canonical (Go release-uploader) convention
126
+ const canonicalOs = canonical(['win32', 'windows'], ['darwin', 'darwin'], ['linux', 'linux']);
127
+ const canonicalArch = process.arch === 'arm64' ? 'arm64' : 'amd64';
128
+ const ext = platform() === 'win32' ? '.exe' : '';
129
+ // Legacy aliases (older dobby/loom releases use these)
130
+ const legacyOs = canonical(['win32', 'win'], ['darwin', 'macos'], ['linux', 'linux']);
131
+ const legacyArch = process.arch === 'arm64' ? 'arm64' : 'x64';
132
+ for (const os of [canonicalOs, legacyOs]) {
133
+ for (const arch of [canonicalArch, legacyArch]) {
134
+ const candidate = pattern
135
+ .replace(/\{os\}/g, os)
136
+ .replace(/\{arch\}/g, arch)
137
+ .replace(/\{ext\}/g, ext);
138
+ if (!seen.has(candidate)) {
139
+ seen.add(candidate);
140
+ variants.push(candidate);
141
+ }
142
+ }
143
+ }
144
+ return variants;
145
+ }
146
+ function canonical(...pairs) {
147
+ const p = platform();
148
+ for (const [match, value] of pairs) {
149
+ if (p === match)
150
+ return value;
151
+ }
152
+ return pairs[pairs.length - 1][1];
153
+ }
154
+ /**
155
+ * Download a URL to a file, optionally with a GitHub Bearer token. Uses
156
+ * Node's built-in fetch (Node 18+). Throws on non-2xx response or on
157
+ * network error.
158
+ */
159
+ async function downloadFile(url, destPath, useGithubAuth) {
160
+ // Note: do NOT set Accept: application/octet-stream. GitHub's release
161
+ // download URL redirects to a CDN that returns 406 for that header. */* is
162
+ // safe and matches what curl/wget send by default.
163
+ const headers = {
164
+ Accept: '*/*',
165
+ 'User-Agent': 'loom-cli',
166
+ };
167
+ if (useGithubAuth) {
168
+ try {
169
+ const token = execSync('gh auth token', { stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim();
170
+ if (token)
171
+ headers.Authorization = `Bearer ${token}`;
172
+ }
173
+ catch {
174
+ // gh CLI not installed or not signed in -- proceed without auth.
175
+ // Public releases will still work; private will fail with 404.
176
+ }
177
+ }
178
+ const res = await fetch(url, { headers, redirect: 'follow' });
179
+ if (!res.ok) {
180
+ throw new Error(`HTTP ${res.status} ${res.statusText}`);
181
+ }
182
+ // Validate the response is an actual binary, not an HTML 404 / login wall.
183
+ // GitHub serves text/html with a 200 status code when the release tag or
184
+ // asset doesn't exist (e.g. the manifest pinned an outdated version).
185
+ // Caching that HTML as <name>.exe leaves the user with an 'invalid
186
+ // application' error at MCP launch time -- a slow, confusing failure.
187
+ // Fail loud here so the resolver can try the next candidate or surface
188
+ // a clear error.
189
+ const contentType = res.headers.get('content-type') ?? '';
190
+ if (/^text\/html\b/i.test(contentType)) {
191
+ throw new Error(`server returned text/html (likely 404 page disguised as 200) -- ` +
192
+ `release tag or asset name probably doesn't exist`);
193
+ }
194
+ const buf = Buffer.from(await res.arrayBuffer());
195
+ // Sanity-check binary magic for the platform we're targeting. Catches
196
+ // the CDN-served-HTML case even if the server forgot the content-type
197
+ // header. Skipped for non-exe extensions (e.g. raw binary names on Linux).
198
+ if (destPath.toLowerCase().endsWith('.exe')) {
199
+ if (buf.length < 2 || buf[0] !== 0x4d || buf[1] !== 0x5a) {
200
+ throw new Error(`downloaded file is not a valid Windows PE executable (no MZ header). ` +
201
+ `Likely an HTML error page from GitHub.`);
202
+ }
203
+ }
204
+ // Atomic write via .tmp + rename to avoid leaving a partial cache file
205
+ // if the process is killed mid-write.
206
+ const tmp = destPath + '.tmp';
207
+ await writeFile(tmp, buf);
208
+ const fs = await import('node:fs/promises');
209
+ await fs.rename(tmp, destPath);
210
+ }
211
+ //# sourceMappingURL=binary-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binary-cache.js","sourceRoot":"","sources":["../../src/util/binary-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAoB7C,MAAM,WAAW,GAAG,mBAAmB,CAAC;AAExC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAAkB,EAClB,MAA6B,GAAG,EAAE,GAAE,CAAC;IAErC,mEAAmE;IACnE,0EAA0E;IAC1E,MAAM,UAAU,GAAG,qBAAqB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAEzD,oEAAoE;IACpE,sEAAsE;IACtE,8CAA8C;IAC9C,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;QAClC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YAChC,GAAG,CAAC,YAAY,MAAM,CAAC,IAAI,yBAAyB,QAAQ,6EAA6E,CAAC,CAAC;YAC3I,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/E,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzC,0DAA0D;IAC1D,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;QAClC,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC5C,IAAI,UAAU,CAAC,UAAU,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YAC5D,GAAG,CAAC,YAAY,MAAM,CAAC,IAAI,eAAe,UAAU,EAAE,CAAC,CAAC;YACxD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;QAChE,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,sCAAsC;IACtC,0EAA0E;IAC1E,8EAA8E;IAC9E,0EAA0E;IAC1E,0EAA0E;IAC1E,mDAAmD;IACnD,wEAAwE;IACxE,EAAE;IACF,yEAAyE;IACzE,yEAAyE;IACzE,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;IACnF,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC;QACnC,GAAG,EAAsC,yBAAyB;QAClE,MAAM,CAAC,OAAO,EAA2B,mCAAmC;QAC5E,MAAM,EAAmC,+BAA+B;QACxE,QAAQ,EAAiC,cAAc;KACxD,CAAC,CAAC,CAAC;IAGJ,MAAM,aAAa,GAAmB,EAAE,CAAC;IACzC,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;QAClC,+BAA+B;QAC/B,aAAa,CAAC,IAAI,CAAC;YACjB,GAAG,EAAE,sBAAsB,MAAM,CAAC,IAAI,sBAAsB,GAAG,IAAI,QAAQ,EAAE;YAC7E,IAAI,EAAE,SAAS;SAChB,CAAC,CAAC;QACH,uDAAuD;QACvD,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAChB,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;YACrD,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;gBAC5B,aAAa,CAAC,IAAI,CAAC;oBACjB,GAAG,EAAE,qCAAqC,MAAM,CAAC,IAAI,IAAI,GAAG,IAAI,QAAQ,IAAI,QAAQ,EAAE;oBACtF,IAAI,EAAE,KAAK;iBACZ,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,wEAAwE;IACxE,gEAAgE;IAChE,KAAK,MAAM,EAAE,GAAG,EAAE,IAAI,aAAa,EAAE,CAAC;QACpC,0CAA0C;QAC1C,MAAM,QAAQ,GAAG,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QACzD,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC;YACH,MAAM,YAAY,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC;YACpE,IAAI,QAAQ,EAAE,KAAK,OAAO,EAAE,CAAC;gBAC3B,MAAM,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;YACjC,CAAC;YACD,GAAG,CAAC,YAAY,MAAM,CAAC,IAAI,gBAAgB,QAAQ,OAAO,UAAU,EAAE,CAAC,CAAC;YACxE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;QAC/D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,YAAY,MAAM,CAAC,IAAI,KAAK,GAAG,OAAQ,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YACpE,eAAe;QACjB,CAAC;IACH,CAAC;IAED,GAAG,CAAC,YAAY,MAAM,CAAC,IAAI,oCAAoC,aAAa,CAAC,MAAM,sCAAsC,GAAG,6BAA6B,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnL,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAe;IACnD,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,6CAA6C;IAC7C,MAAM,WAAW,GAAG,SAAS,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAC9F,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;IACnE,MAAM,GAAG,GAAG,QAAQ,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAEjD,uDAAuD;IACvD,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IACtF,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAE9D,KAAK,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,CAAC;QACzC,KAAK,MAAM,IAAI,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,EAAE,CAAC;YAC/C,MAAM,SAAS,GAAG,OAAO;iBACtB,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;iBACtB,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC;iBAC1B,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;YAC5B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;gBACzB,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACpB,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,SAAS,CAAC,GAAG,KAAyB;IAC7C,MAAM,CAAC,GAAG,QAAQ,EAAE,CAAC;IACrB,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;QACnC,IAAI,CAAC,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC;IAChC,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,YAAY,CAAC,GAAW,EAAE,QAAgB,EAAE,aAAsB;IAC/E,sEAAsE;IACtE,2EAA2E;IAC3E,mDAAmD;IACnD,MAAM,OAAO,GAA2B;QACtC,MAAM,EAAE,KAAK;QACb,YAAY,EAAE,UAAU;KACzB,CAAC;IAEF,IAAI,aAAa,EAAE,CAAC;QAClB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,QAAQ,CAAC,eAAe,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;YACjG,IAAI,KAAK;gBAAE,OAAO,CAAC,aAAa,GAAG,UAAU,KAAK,EAAE,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC;YACP,iEAAiE;YACjE,+DAA+D;QACjE,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC9D,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,2EAA2E;IAC3E,yEAAyE;IACzE,sEAAsE;IACtE,mEAAmE;IACnE,sEAAsE;IACtE,uEAAuE;IACvE,iBAAiB;IACjB,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IAC1D,IAAI,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CACb,kEAAkE;YAClE,kDAAkD,CACnD,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;IAEjD,sEAAsE;IACtE,sEAAsE;IACtE,2EAA2E;IAC3E,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5C,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACzD,MAAM,IAAI,KAAK,CACb,uEAAuE;gBACvE,wCAAwC,CACzC,CAAC;QACJ,CAAC;IACH,CAAC;IAED,uEAAuE;IACvE,sCAAsC;IACtC,MAAM,GAAG,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC9B,MAAM,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC1B,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;IAC5C,MAAM,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AACjC,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * YAML frontmatter parsing for `.agent.md` (and other) markdown files.
3
+ *
4
+ * Lives under `src/util/` because frontmatter handling is needed by every
5
+ * layer (chat, run, renderers, manifest). Putting it under `src/chat/`
6
+ * forced renderers + util code to import a chat module — wrong dependency
7
+ * direction. `src/chat/frontmatter.ts` re-exports these for backwards
8
+ * compatibility with older imports.
9
+ *
10
+ * Minimal, tolerant parser: extracts the top-most `---` fenced block,
11
+ * hands the body to js-yaml, and returns a normalized view of the fields
12
+ * `loom <agent>` cares about. Files without frontmatter are not an error —
13
+ * they simply yield an empty metadata object.
14
+ *
15
+ * Tolerant of:
16
+ * - LF or CRLF line endings (Windows-authored agent files)
17
+ * - UTF-8 BOM at the start of the file (also common on Windows)
18
+ *
19
+ * Three exports cover every shape we need:
20
+ * - `parseAgentFrontmatter(path)` — read from disk, normalized view
21
+ * - `parseFrontmatterRaw(content)` — string in, raw dict out
22
+ * - `stripFrontmatter(content)` — body without the fenced block
23
+ *
24
+ * **Always use these instead of hand-rolling a `^---` regex** — the
25
+ * `repo-conventions.test.ts` lint enforces this.
26
+ */
27
+ export interface AgentMetadata {
28
+ /** Human-readable agent name (frontmatter `name:` or the file's agent id) */
29
+ name?: string;
30
+ /** One-line description from `description:` */
31
+ description?: string;
32
+ /** Model hint, e.g. `claude-sonnet-4.5` (may be absent in CLI-shaped agents) */
33
+ model?: string;
34
+ /** Any other frontmatter fields, preserved for future use */
35
+ raw: Record<string, unknown>;
36
+ }
37
+ /**
38
+ * Parse the top-most `---` fenced YAML block from `content` and return it
39
+ * as a plain object. Returns `{}` when there's no frontmatter or the YAML
40
+ * is malformed. Tolerant of CRLF and a leading UTF-8 BOM.
41
+ */
42
+ export declare function parseFrontmatterRaw(content: string): Record<string, unknown>;
43
+ /**
44
+ * Return `content` with the leading `---` fenced YAML block removed.
45
+ * No-op when there is no frontmatter. Tolerant of CRLF and a leading BOM.
46
+ */
47
+ export declare function stripFrontmatter(content: string): string;
48
+ /**
49
+ * Parse the frontmatter from an agent file. Returns an empty metadata object
50
+ * (with `raw: {}`) when no frontmatter is present.
51
+ */
52
+ export declare function parseAgentFrontmatter(agentFilePath: string): AgentMetadata;
53
+ //# sourceMappingURL=frontmatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../src/util/frontmatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAKH,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAaD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAW5E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,MAAM,GAAG,aAAa,CAe1E"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * YAML frontmatter parsing for `.agent.md` (and other) markdown files.
3
+ *
4
+ * Lives under `src/util/` because frontmatter handling is needed by every
5
+ * layer (chat, run, renderers, manifest). Putting it under `src/chat/`
6
+ * forced renderers + util code to import a chat module — wrong dependency
7
+ * direction. `src/chat/frontmatter.ts` re-exports these for backwards
8
+ * compatibility with older imports.
9
+ *
10
+ * Minimal, tolerant parser: extracts the top-most `---` fenced block,
11
+ * hands the body to js-yaml, and returns a normalized view of the fields
12
+ * `loom <agent>` cares about. Files without frontmatter are not an error —
13
+ * they simply yield an empty metadata object.
14
+ *
15
+ * Tolerant of:
16
+ * - LF or CRLF line endings (Windows-authored agent files)
17
+ * - UTF-8 BOM at the start of the file (also common on Windows)
18
+ *
19
+ * Three exports cover every shape we need:
20
+ * - `parseAgentFrontmatter(path)` — read from disk, normalized view
21
+ * - `parseFrontmatterRaw(content)` — string in, raw dict out
22
+ * - `stripFrontmatter(content)` — body without the fenced block
23
+ *
24
+ * **Always use these instead of hand-rolling a `^---` regex** — the
25
+ * `repo-conventions.test.ts` lint enforces this.
26
+ */
27
+ import { readFileSync } from 'node:fs';
28
+ import yaml from 'js-yaml';
29
+ const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
30
+ /**
31
+ * Strip a leading UTF-8 BOM (U+FEFF) if present. Windows editors and
32
+ * PowerShell `>` redirection often add one, which would otherwise prevent
33
+ * `^---` from matching the frontmatter fence.
34
+ */
35
+ function stripBom(content) {
36
+ return content.charCodeAt(0) === 0xfeff ? content.slice(1) : content;
37
+ }
38
+ /**
39
+ * Parse the top-most `---` fenced YAML block from `content` and return it
40
+ * as a plain object. Returns `{}` when there's no frontmatter or the YAML
41
+ * is malformed. Tolerant of CRLF and a leading UTF-8 BOM.
42
+ */
43
+ export function parseFrontmatterRaw(content) {
44
+ const match = FRONTMATTER_RE.exec(stripBom(content));
45
+ if (!match)
46
+ return {};
47
+ let parsed;
48
+ try {
49
+ parsed = yaml.load(match[1]);
50
+ }
51
+ catch {
52
+ return {};
53
+ }
54
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
55
+ return {};
56
+ return parsed;
57
+ }
58
+ /**
59
+ * Return `content` with the leading `---` fenced YAML block removed.
60
+ * No-op when there is no frontmatter. Tolerant of CRLF and a leading BOM.
61
+ */
62
+ export function stripFrontmatter(content) {
63
+ return stripBom(content).replace(FRONTMATTER_RE, '').trim();
64
+ }
65
+ /**
66
+ * Parse the frontmatter from an agent file. Returns an empty metadata object
67
+ * (with `raw: {}`) when no frontmatter is present.
68
+ */
69
+ export function parseAgentFrontmatter(agentFilePath) {
70
+ let content;
71
+ try {
72
+ content = readFileSync(agentFilePath, 'utf-8');
73
+ }
74
+ catch {
75
+ return { raw: {} };
76
+ }
77
+ const raw = parseFrontmatterRaw(content);
78
+ return {
79
+ name: typeof raw.name === 'string' ? raw.name : undefined,
80
+ description: typeof raw.description === 'string' ? raw.description : undefined,
81
+ model: typeof raw.model === 'string' ? raw.model : undefined,
82
+ raw,
83
+ };
84
+ }
85
+ //# sourceMappingURL=frontmatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../src/util/frontmatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,IAAI,MAAM,SAAS,CAAC;AAa3B,MAAM,cAAc,GAAG,mCAAmC,CAAC;AAE3D;;;;GAIG;AACH,SAAS,QAAQ,CAAC,OAAe;IAC/B,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;AACvE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACtB,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC;IAC9E,OAAO,MAAiC,CAAC;AAC3C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AAC9D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,aAAqB;IACzD,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;IACrB,CAAC;IAED,MAAM,GAAG,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACzC,OAAO;QACL,IAAI,EAAE,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QACzD,WAAW,EAAE,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;QAC9E,KAAK,EAAE,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;QAC5D,GAAG;KACJ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Resolve the loom home directory.
3
+ *
4
+ * Default: `~/.loom`. Overridable via the `LOOM_HOME` env var. The
5
+ * env var must be an absolute path; relative paths are rejected to
6
+ * prevent surprising behavior when callers set it once and then
7
+ * change cwd.
8
+ *
9
+ * Why a single helper: this path is the root for staged dirs, repo
10
+ * cache, MCP binaries, sessions, run artifacts, repos.yaml, and the
11
+ * top-level config.yaml. Hardcoding `~/.loom` in each module makes
12
+ * test sandboxing painful and forecloses on a future dobby-CLI rebrand
13
+ * (where the same code might write to `~/.dobby/`).
14
+ *
15
+ * The function is LAZY -- never cached -- because tests need to be
16
+ * able to set HOME / USERPROFILE / LOOM_HOME on a per-test basis.
17
+ */
18
+ export declare function loomHomeDir(): string;
19
+ //# sourceMappingURL=loom-home.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loom-home.d.ts","sourceRoot":"","sources":["../../src/util/loom-home.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH,wBAAgB,WAAW,IAAI,MAAM,CAiBpC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Resolve the loom home directory.
3
+ *
4
+ * Default: `~/.loom`. Overridable via the `LOOM_HOME` env var. The
5
+ * env var must be an absolute path; relative paths are rejected to
6
+ * prevent surprising behavior when callers set it once and then
7
+ * change cwd.
8
+ *
9
+ * Why a single helper: this path is the root for staged dirs, repo
10
+ * cache, MCP binaries, sessions, run artifacts, repos.yaml, and the
11
+ * top-level config.yaml. Hardcoding `~/.loom` in each module makes
12
+ * test sandboxing painful and forecloses on a future dobby-CLI rebrand
13
+ * (where the same code might write to `~/.dobby/`).
14
+ *
15
+ * The function is LAZY -- never cached -- because tests need to be
16
+ * able to set HOME / USERPROFILE / LOOM_HOME on a per-test basis.
17
+ */
18
+ import { homedir } from 'node:os';
19
+ import { join, isAbsolute } from 'node:path';
20
+ let warnedAboutRelativeOverride = false;
21
+ export function loomHomeDir() {
22
+ const override = process.env.LOOM_HOME;
23
+ if (override && override.length > 0) {
24
+ if (isAbsolute(override)) {
25
+ return override;
26
+ }
27
+ // Reject relative paths but don't crash the caller. The caller is
28
+ // typically a path getter and breaking it would surface as a
29
+ // confusing ENOENT downstream. Warn once, fall back to default.
30
+ if (!warnedAboutRelativeOverride) {
31
+ warnedAboutRelativeOverride = true;
32
+ process.stderr.write(` warning: LOOM_HOME='${override}' is not absolute; falling back to default ~/.loom.\n`);
33
+ }
34
+ }
35
+ return join(homedir(), '.loom');
36
+ }
37
+ //# sourceMappingURL=loom-home.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loom-home.js","sourceRoot":"","sources":["../../src/util/loom-home.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE7C,IAAI,2BAA2B,GAAG,KAAK,CAAC;AAExC,MAAM,UAAU,WAAW;IACzB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;IACvC,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpC,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YACzB,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,kEAAkE;QAClE,6DAA6D;QAC7D,gEAAgE;QAChE,IAAI,CAAC,2BAA2B,EAAE,CAAC;YACjC,2BAA2B,GAAG,IAAI,CAAC;YACnC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,yBAAyB,QAAQ,uDAAuD,CACzF,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shared helper: expand `${workspaceFolder:X}` placeholders in MCP
3
+ * args and cwd. Used by apply.ts after manifest resolution so the
4
+ * placeholder semantics are identical regardless of the source
5
+ * manifest layout (canonical contents:-wrapped or flat).
6
+ *
7
+ * The placeholder is resolved against a repo-name -> dir-name map
8
+ * built from the manifest's repos: block. Both repo.name and repo.id
9
+ * are entries in the map (pointing to the dir loom will place the
10
+ * clone at), so manifests can use either form.
11
+ */
12
+ export declare function buildRepoNameMap<T extends {
13
+ id?: string;
14
+ name?: string;
15
+ }>(repos: ReadonlyArray<T>,
16
+ /**
17
+ * Function that returns the directory name a repo will be placed at
18
+ * in the workspace. Concrete callers wire this to deriveRepoDir or
19
+ * an equivalent so the map matches what cloneManifestRepos / lazy-
20
+ * stage actually does.
21
+ */
22
+ dirNameFor: (repo: T) => string): Map<string, string>;
23
+ /**
24
+ * Replace every `${workspaceFolder:X}` in the input with
25
+ * `<workDir>/<resolved>` where resolved is the repoNameMap lookup
26
+ * (falling back to X verbatim).
27
+ */
28
+ export declare function expandWorkspaceFolder(value: string, repoNameMap: Map<string, string>, workDir: string): string;
29
+ //# sourceMappingURL=workspace-folder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-folder.d.ts","sourceRoot":"","sources":["../../src/util/workspace-folder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EACvE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC;AACvB;;;;;GAKG;AACH,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAC9B,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CASrB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,OAAO,EAAE,MAAM,GACd,MAAM,CAKR"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Shared helper: expand `${workspaceFolder:X}` placeholders in MCP
3
+ * args and cwd. Used by apply.ts after manifest resolution so the
4
+ * placeholder semantics are identical regardless of the source
5
+ * manifest layout (canonical contents:-wrapped or flat).
6
+ *
7
+ * The placeholder is resolved against a repo-name -> dir-name map
8
+ * built from the manifest's repos: block. Both repo.name and repo.id
9
+ * are entries in the map (pointing to the dir loom will place the
10
+ * clone at), so manifests can use either form.
11
+ */
12
+ import { join } from 'node:path';
13
+ export function buildRepoNameMap(repos,
14
+ /**
15
+ * Function that returns the directory name a repo will be placed at
16
+ * in the workspace. Concrete callers wire this to deriveRepoDir or
17
+ * an equivalent so the map matches what cloneManifestRepos / lazy-
18
+ * stage actually does.
19
+ */
20
+ dirNameFor) {
21
+ const map = new Map();
22
+ for (const r of repos) {
23
+ const dirName = dirNameFor(r);
24
+ if (r.name)
25
+ map.set(r.name, dirName);
26
+ if (r.id)
27
+ map.set(r.id, dirName);
28
+ map.set(dirName, dirName); // identity, in case the placeholder uses the dir name
29
+ }
30
+ return map;
31
+ }
32
+ /**
33
+ * Replace every `${workspaceFolder:X}` in the input with
34
+ * `<workDir>/<resolved>` where resolved is the repoNameMap lookup
35
+ * (falling back to X verbatim).
36
+ */
37
+ export function expandWorkspaceFolder(value, repoNameMap, workDir) {
38
+ return value.replace(/\$\{workspaceFolder:([^}]+)\}/g, (_match, name) => {
39
+ const dirName = repoNameMap.get(name) ?? name;
40
+ return join(workDir, dirName);
41
+ });
42
+ }
43
+ //# sourceMappingURL=workspace-folder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-folder.js","sourceRoot":"","sources":["../../src/util/workspace-folder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,UAAU,gBAAgB,CAC9B,KAAuB;AACvB;;;;;GAKG;AACH,UAA+B;IAE/B,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;QAC9B,IAAI,CAAC,CAAC,IAAI;YAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC,CAAC,EAAE;YAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACjC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,sDAAsD;IACnF,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAAa,EACb,WAAgC,EAChC,OAAe;IAEf,OAAO,KAAK,CAAC,OAAO,CAAC,gCAAgC,EAAE,CAAC,MAAM,EAAE,IAAY,EAAE,EAAE;QAC9E,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;QAC9C,OAAO,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -1,5 +1,11 @@
1
1
  /**
2
- * Manifest validation schema, reference, and registry checks.
2
+ * Manifest validation -- schema, reference, and registry checks.
3
+ *
4
+ * Per U6, this validator probes both `manifest.yaml` (canonical) and
5
+ * `dobby.yaml` (legacy alias) inside the template dir, and runs the
6
+ * raw YAML through `normalizeManifestAliases` so dobby-style field
7
+ * names (`mcpServers:`, `sparseCheckout:`, `team:`) and flat layout
8
+ * are validated against the same schema as canonical manifests.
3
9
  */
4
10
  export interface ValidateResult {
5
11
  templateId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,OAAO,EAAE;QACP,YAAY,EAAE,MAAM,CAAC;QACrB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;CACH;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,QAAQ,GAAG,WAAW,GAAG,UAAU,CAAC;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,cAAc,CAAC,CA+GzB;AAMD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,cAAc,EAAE,CAAC,CAyC3B"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,OAAO,EAAE;QACP,YAAY,EAAE,MAAM,CAAC;QACrB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;CACH;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,QAAQ,GAAG,WAAW,GAAG,UAAU,CAAC;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,cAAc,CAAC,CAgKzB;AAMD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,cAAc,EAAE,CAAC,CA4D3B"}