@drisp/cli 0.3.39

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.
@@ -0,0 +1,2621 @@
1
+ // src/ui/theme/themes.ts
2
+ var darkTheme = {
3
+ name: "dark",
4
+ // ── Text hierarchy ──────────────────────────────────────
5
+ text: "#c9d1d9",
6
+ // Primary text. Warm gray, not pure white.
7
+ textMuted: "#848d97",
8
+ // Dim text. Bumped from #6e7681 for AA compliance (~4.8:1).
9
+ textInverse: "#0d1117",
10
+ // Text on colored backgrounds.
11
+ // ── Accent ──────────────────────────────────────────────
12
+ border: "#2d333b",
13
+ // Bumped from #1e2a38 for visibility (~2.2:1 vs bg).
14
+ accent: "#58a6ff",
15
+ // Blue. Focus bar, branding, selection, links.
16
+ accentSecondary: "#bc8cff",
17
+ // Soft purple. Permission events.
18
+ // ── Status ──────────────────────────────────────────────
19
+ status: {
20
+ success: "#3fb950",
21
+ // Green. Completion, done glyphs.
22
+ error: "#f85149",
23
+ // Red. Failures, blocks, Tool Fail.
24
+ warning: "#d29922",
25
+ // Amber. Active stage, zero-result tint, caution.
26
+ info: "#58a6ff",
27
+ // Blue. Agent messages, Run OK.
28
+ working: "#d29922",
29
+ // Amber. Spinner state (same as warning).
30
+ neutral: "#9da5ae"
31
+ // Mid gray. Bumped from #8b949e for AA (~4.8:1).
32
+ },
33
+ // ── Context bar ─────────────────────────────────────────
34
+ contextBar: {
35
+ track: "#1e2a38",
36
+ // Empty segment track.
37
+ low: "#3fb950",
38
+ // Green. 0–50% budget used.
39
+ medium: "#d29922",
40
+ // Amber. 50–80% budget used.
41
+ high: "#f85149"
42
+ // Red. 80–100% budget used.
43
+ },
44
+ // ── Dialog borders ──────────────────────────────────────
45
+ dialog: {
46
+ borderPermission: "#d29922",
47
+ // Amber border for permission prompts.
48
+ borderQuestion: "#58a6ff"
49
+ // Blue border for question prompts.
50
+ },
51
+ // ── Input ───────────────────────────────────────────────
52
+ inputPrompt: "#388bfd",
53
+ // Blue "input" keyword in prompt.
54
+ inputChevron: "#484f58",
55
+ // Bumped from #30363d for visibility (~2.8:1).
56
+ inputBackground: "#141a21",
57
+ // Slight lift so the input row reads as its own surface.
58
+ // ── Feed ────────────────────────────────────────────────
59
+ feed: {
60
+ headerLabel: "#848d97",
61
+ // Matches textMuted.
62
+ stripeBackground: "#161b22",
63
+ // Bumped from #0d1521 for visible alternation.
64
+ focusBackground: "#1b2a3f"
65
+ // Blue tint for focused row.
66
+ },
67
+ // ── User messages ───────────────────────────────────────
68
+ userMessage: {
69
+ text: "#6e7681",
70
+ // Dimmed text for user messages in the message panel.
71
+ background: "#151d2b",
72
+ // Blue-tinted lift — differentiates user turns from agent.
73
+ border: "#30363d",
74
+ // Subtle border.
75
+ agentBorder: "#2a4a6a",
76
+ // Steel blue left border for agent messages.
77
+ focusBorder: "#58a6ff"
78
+ // Accent blue for focused message indicator.
79
+ },
80
+ // ── Badges ──────────────────────────────────────────────
81
+ badge: {
82
+ error: { bg: "#4b1014", fg: "#ff7b72" },
83
+ running: { bg: "#4a3a0c", fg: "#fbbf24" },
84
+ idle: { bg: "#10321d", fg: "#3fb950" },
85
+ search: { bg: "#1b2a3f" },
86
+ command: { bg: "#2a1b3f" }
87
+ },
88
+ // ── Detail view ─────────────────────────────────────────
89
+ detail: {
90
+ title: "#c9d1d9",
91
+ subject: "#58a6ff"
92
+ },
93
+ // ── Tool pills ──────────────────────────────────────────
94
+ toolPill: {
95
+ safe: { bg: "#0e2233", fg: "#5ba3cc" },
96
+ mutating: { bg: "#2a1d0a", fg: "#d4a44a" },
97
+ browser: { bg: "#0b2625", fg: "#5cc4ba" },
98
+ neutral: { bg: "#141a22", fg: "#7d8590" },
99
+ skill: { bg: "#2a0f24", fg: "#c98ab8" },
100
+ "subagent.spawn": { bg: "#0a2e22", fg: "#5cc4a0" },
101
+ "subagent.return": { bg: "#0a2e22", fg: "#56b492" }
102
+ }
103
+ };
104
+ var lightTheme = {
105
+ name: "light",
106
+ // ── Text hierarchy ──────────────────────────────────────
107
+ text: "#1f2328",
108
+ // Near-black. Strong contrast.
109
+ textMuted: "#656d76",
110
+ // Medium gray. Same role as dark textMuted.
111
+ textInverse: "#ffffff",
112
+ // White on colored backgrounds.
113
+ // ── Accent ──────────────────────────────────────────────
114
+ border: "#8c959f",
115
+ // Darker neutral border so frame lines stay visible on white.
116
+ accent: "#0969da",
117
+ // Darker blue for light backgrounds.
118
+ accentSecondary: "#8250df",
119
+ // Purple, darkened for readability.
120
+ // ── Status ──────────────────────────────────────────────
121
+ status: {
122
+ success: "#1a7f37",
123
+ // Dark green. Readable on white.
124
+ error: "#cf222e",
125
+ // Dark red.
126
+ warning: "#9a6700",
127
+ // Dark amber.
128
+ info: "#0969da",
129
+ // Dark blue. Matches accent.
130
+ working: "#9a6700",
131
+ // Dark amber.
132
+ neutral: "#656d76"
133
+ // Mid gray.
134
+ },
135
+ // ── Context bar ─────────────────────────────────────────
136
+ contextBar: {
137
+ track: "#d0d7de",
138
+ // Light gray track.
139
+ low: "#1a7f37",
140
+ medium: "#9a6700",
141
+ high: "#cf222e"
142
+ },
143
+ // ── Dialog borders ──────────────────────────────────────
144
+ dialog: {
145
+ borderPermission: "#9a6700",
146
+ borderQuestion: "#0969da"
147
+ },
148
+ // ── Input ───────────────────────────────────────────────
149
+ inputPrompt: "#0969da",
150
+ inputChevron: "#656d76",
151
+ inputBackground: "#f3f6f9",
152
+ // ── Feed ────────────────────────────────────────────────
153
+ feed: {
154
+ headerLabel: "#656d76",
155
+ stripeBackground: "#f6f8fa",
156
+ focusBackground: "#ddf4ff"
157
+ // Light blue tint for focused row.
158
+ },
159
+ // ── User messages ───────────────────────────────────────
160
+ userMessage: {
161
+ text: "#8b949e",
162
+ // Dimmed text for user messages in the message panel.
163
+ background: "#edf2fb",
164
+ // Blue-tinted lift — differentiates user turns from agent.
165
+ border: "#8c959f",
166
+ agentBorder: "#6cb0e0",
167
+ // Medium blue on white bg.
168
+ focusBorder: "#0969da"
169
+ // Accent blue for focused message indicator.
170
+ },
171
+ // ── Badges ──────────────────────────────────────────────
172
+ badge: {
173
+ error: { bg: "#ffebe9", fg: "#cf222e" },
174
+ running: { bg: "#fff8c5", fg: "#9a6700" },
175
+ idle: { bg: "#dafbe1", fg: "#1a7f37" },
176
+ search: { bg: "#ddf4ff" },
177
+ command: { bg: "#fbefff" }
178
+ },
179
+ // ── Detail view ─────────────────────────────────────────
180
+ detail: {
181
+ title: "#1f2328",
182
+ subject: "#0969da"
183
+ },
184
+ // ── Tool pills ──────────────────────────────────────────
185
+ toolPill: {
186
+ safe: { bg: "#ddf4ff", fg: "#0550ae" },
187
+ mutating: { bg: "#fff8c5", fg: "#7c5200" },
188
+ browser: { bg: "#dafbe1", fg: "#116329" },
189
+ neutral: { bg: "#f6f8fa", fg: "#424a53" },
190
+ skill: { bg: "#fbefff", fg: "#6639ba" },
191
+ "subagent.spawn": { bg: "#dafbe1", fg: "#116329" },
192
+ "subagent.return": { bg: "#dafbe1", fg: "#1a7f37" }
193
+ }
194
+ };
195
+ var highContrastTheme = {
196
+ name: "high-contrast",
197
+ // ── Text hierarchy ──────────────────────────────────────
198
+ text: "#f0f6fc",
199
+ // Near-white. Maximum brightness.
200
+ textMuted: "#9ea7b3",
201
+ // Bumped from #7d8590 for better contrast (~5.2:1).
202
+ textInverse: "#010409",
203
+ // Near-black.
204
+ // ── Accent ──────────────────────────────────────────────
205
+ border: "#71b7ff",
206
+ accent: "#71b7ff",
207
+ // Brighter blue. Punches through.
208
+ accentSecondary: "#d2a8ff",
209
+ // Bright purple.
210
+ // ── Status ──────────────────────────────────────────────
211
+ status: {
212
+ success: "#56d364",
213
+ // Bright green. Higher saturation.
214
+ error: "#ff7b72",
215
+ // Bright red. Softened for readability.
216
+ warning: "#e3b341",
217
+ // Bright amber.
218
+ info: "#71b7ff",
219
+ // Bright blue.
220
+ working: "#e3b341",
221
+ // Bright amber.
222
+ neutral: "#b1bac4"
223
+ // Bumped from #9ea7b3 for HC (~6.0:1).
224
+ },
225
+ // ── Context bar ─────────────────────────────────────────
226
+ contextBar: {
227
+ track: "#3d444d",
228
+ // HC track — brighter than dark.
229
+ low: "#56d364",
230
+ medium: "#e3b341",
231
+ high: "#ff7b72"
232
+ },
233
+ // ── Dialog borders ──────────────────────────────────────
234
+ dialog: {
235
+ borderPermission: "#e3b341",
236
+ borderQuestion: "#71b7ff"
237
+ },
238
+ // ── Input ───────────────────────────────────────────────
239
+ inputPrompt: "#71b7ff",
240
+ inputChevron: "#9ea7b3",
241
+ // Bumped from #7d8590 for visibility.
242
+ inputBackground: "#16202b",
243
+ // ── Feed ────────────────────────────────────────────────
244
+ feed: {
245
+ headerLabel: "#9ea7b3",
246
+ // Matches textMuted.
247
+ stripeBackground: "#0d1521",
248
+ // Bumped from #0b141f for visible alternation.
249
+ focusBackground: "#1a3350"
250
+ // Brighter blue tint for HC.
251
+ },
252
+ // ── User messages ───────────────────────────────────────
253
+ userMessage: {
254
+ text: "#7d8590",
255
+ // Dimmed text for user messages in the message panel.
256
+ background: "#1a2436",
257
+ // Blue-tinted lift — differentiates user turns from agent.
258
+ border: "#444c56",
259
+ // Bumped from #3d444d for HC visibility.
260
+ agentBorder: "#5599cc",
261
+ // Brighter blue for HC.
262
+ focusBorder: "#71b7ff"
263
+ // Accent blue for focused message indicator.
264
+ },
265
+ // ── Badges ──────────────────────────────────────────────
266
+ badge: {
267
+ error: { bg: "#6e1b16", fg: "#ff7b72" },
268
+ running: { bg: "#5c4813", fg: "#e3b341" },
269
+ idle: { bg: "#154228", fg: "#56d364" },
270
+ search: { bg: "#1a3350" },
271
+ command: { bg: "#301e50" }
272
+ },
273
+ // ── Detail view ─────────────────────────────────────────
274
+ detail: {
275
+ title: "#f0f6fc",
276
+ subject: "#71b7ff"
277
+ },
278
+ // ── Tool pills ──────────────────────────────────────────
279
+ toolPill: {
280
+ safe: { bg: "#122d42", fg: "#a2d2ff" },
281
+ mutating: { bg: "#3a2e10", fg: "#f0d070" },
282
+ browser: { bg: "#0f3520", fg: "#7ee28b" },
283
+ neutral: { bg: "#1c2128", fg: "#b1bac4" },
284
+ skill: { bg: "#2b1a40", fg: "#e0c0ff" },
285
+ "subagent.spawn": { bg: "#0f3520", fg: "#7ee28b" },
286
+ "subagent.return": { bg: "#0f3520", fg: "#6bcc79" }
287
+ }
288
+ };
289
+ var THEMES = {
290
+ dark: darkTheme,
291
+ light: lightTheme,
292
+ "high-contrast": highContrastTheme
293
+ };
294
+ function resolveTheme(name) {
295
+ if (name && name in THEMES) {
296
+ return THEMES[name];
297
+ }
298
+ return darkTheme;
299
+ }
300
+
301
+ // src/ui/theme/ThemeContext.tsx
302
+ import { createContext, useContext } from "react";
303
+ var ThemeContext = createContext(darkTheme);
304
+ var ThemeProvider = ThemeContext.Provider;
305
+ function useTheme() {
306
+ return useContext(ThemeContext);
307
+ }
308
+
309
+ // src/infra/plugins/workflowSourceResolution.ts
310
+ import fs2 from "fs";
311
+ import path2 from "path";
312
+
313
+ // src/infra/plugins/marketplaceShared.ts
314
+ import { execFileSync } from "child_process";
315
+ import fs from "fs";
316
+ import os from "os";
317
+ import path from "path";
318
+ var MARKETPLACE_REF_RE = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/;
319
+ var MARKETPLACE_SLUG_RE = /^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/;
320
+ function marketplacesCacheDir() {
321
+ return path.join(os.homedir(), ".config", "athena", "marketplaces");
322
+ }
323
+ function marketplaceRepoCacheDir(owner, repo) {
324
+ return path.join(marketplacesCacheDir(), owner, repo);
325
+ }
326
+ function resolvePluginManifestPath(repoDir) {
327
+ const preferredManifestPath = path.join(
328
+ repoDir,
329
+ ".agents",
330
+ "plugins",
331
+ "marketplace.json"
332
+ );
333
+ const legacyManifestPath = resolveLegacyPluginManifestPath(repoDir);
334
+ return fs.existsSync(preferredManifestPath) ? preferredManifestPath : legacyManifestPath;
335
+ }
336
+ function resolveLegacyPluginManifestPath(repoDir) {
337
+ return path.join(repoDir, ".claude-plugin", "marketplace.json");
338
+ }
339
+ function resolveWorkflowManifestPath(repoDir) {
340
+ const preferredManifestPath = path.join(
341
+ repoDir,
342
+ ".athena-workflow",
343
+ "marketplace.json"
344
+ );
345
+ const legacyManifestPath = resolveLegacyPluginManifestPath(repoDir);
346
+ return fs.existsSync(preferredManifestPath) ? preferredManifestPath : legacyManifestPath;
347
+ }
348
+ function readManifest(manifestPath) {
349
+ if (!fs.existsSync(manifestPath)) {
350
+ throw new Error(`Marketplace manifest not found: ${manifestPath}`);
351
+ }
352
+ return JSON.parse(
353
+ fs.readFileSync(manifestPath, "utf-8")
354
+ );
355
+ }
356
+ function entryRelativeSourcePath(entry, pluginName) {
357
+ let sourcePath;
358
+ if (typeof entry.source === "string") {
359
+ sourcePath = entry.source;
360
+ } else {
361
+ const sourceRecord = entry.source;
362
+ if (sourceRecord.source !== "local" || typeof sourceRecord.path !== "string") {
363
+ throw new Error(
364
+ `Plugin "${pluginName}" uses a remote source type which is not supported by athena-cli. Only local relative path sources are supported.`
365
+ );
366
+ }
367
+ sourcePath = sourceRecord.path;
368
+ }
369
+ if (!sourcePath.startsWith("./")) {
370
+ throw new Error(
371
+ `Plugin "${pluginName}" source must start with "./": ${sourcePath}`
372
+ );
373
+ }
374
+ const relativeSourcePath = sourcePath.slice(2);
375
+ if (relativeSourcePath.length === 0) {
376
+ throw new Error(`Plugin "${pluginName}" source must not be empty`);
377
+ }
378
+ if (relativeSourcePath.includes("\\")) {
379
+ throw new Error(
380
+ `Plugin "${pluginName}" source must stay within the marketplace root: ${sourcePath}`
381
+ );
382
+ }
383
+ const segments = relativeSourcePath.split("/");
384
+ if (segments.some(
385
+ (segment) => segment === "" || segment === "." || segment === ".."
386
+ )) {
387
+ throw new Error(
388
+ `Plugin "${pluginName}" source must stay within the marketplace root: ${sourcePath}`
389
+ );
390
+ }
391
+ return relativeSourcePath;
392
+ }
393
+ function resolvePluginDirFromManifest(pluginName, repoDir, manifestPath) {
394
+ const manifest = readManifest(manifestPath);
395
+ if (!Array.isArray(manifest.plugins)) {
396
+ throw new Error(
397
+ `Invalid marketplace manifest at ${manifestPath}: "plugins" must be an array`
398
+ );
399
+ }
400
+ const entry = manifest.plugins.find((p) => p.name === pluginName);
401
+ if (!entry) {
402
+ const available = manifest.plugins.map((p) => p.name).join(", ");
403
+ throw new Error(
404
+ `Plugin "${pluginName}" not found in marketplace manifest ${manifestPath}. Available plugins: ${available}`
405
+ );
406
+ }
407
+ const pluginDir = path.join(
408
+ repoDir,
409
+ entryRelativeSourcePath(entry, pluginName)
410
+ );
411
+ if (!fs.existsSync(pluginDir)) {
412
+ throw new Error(`Plugin source directory not found: ${pluginDir}`);
413
+ }
414
+ return pluginDir;
415
+ }
416
+ function resolveWorkflowEntryPath(entry, manifest, repoDir) {
417
+ if (typeof entry.source !== "string") {
418
+ throw new Error(
419
+ `Workflow "${entry.name}" uses a remote source type which is not supported.`
420
+ );
421
+ }
422
+ let sourcePath = entry.source;
423
+ const { workflowRoot } = manifest.metadata ?? {};
424
+ if (workflowRoot && !path.isAbsolute(sourcePath) && !sourcePath.startsWith("./") && !sourcePath.startsWith("../")) {
425
+ sourcePath = path.join(workflowRoot, sourcePath);
426
+ }
427
+ const workflowPath = path.resolve(repoDir, sourcePath);
428
+ if (!workflowPath.startsWith(repoDir + path.sep) && workflowPath !== repoDir) {
429
+ throw new Error(
430
+ `Workflow "${entry.name}" source resolves outside the marketplace repo: ${workflowPath}`
431
+ );
432
+ }
433
+ const resolvedWorkflowPath = preferCanonicalWorkflowPath(
434
+ repoDir,
435
+ workflowPath
436
+ );
437
+ if (!fs.existsSync(resolvedWorkflowPath)) {
438
+ throw new Error(`Workflow source not found: ${resolvedWorkflowPath}`);
439
+ }
440
+ return resolvedWorkflowPath;
441
+ }
442
+ function resolveWorkflowPathFromManifest(workflowName, repoDir, manifestPath) {
443
+ const manifest = readManifest(manifestPath);
444
+ const workflows = manifest.workflows ?? [];
445
+ const entry = workflows.find((w) => w.name === workflowName);
446
+ if (!entry) {
447
+ const available = workflows.map((w) => w.name).join(", ") || "(none)";
448
+ throw new Error(
449
+ `Workflow "${workflowName}" not found in marketplace manifest ${manifestPath}. Available workflows: ${available}`
450
+ );
451
+ }
452
+ return resolveWorkflowEntryPath(entry, manifest, repoDir);
453
+ }
454
+ function listWorkflowEntriesFromManifest(repoDir, manifestPath, source) {
455
+ const manifest = readManifest(manifestPath);
456
+ const workflows = manifest.workflows ?? [];
457
+ return workflows.filter(
458
+ (entry) => typeof entry.source === "string"
459
+ ).map((entry) => ({
460
+ name: entry.name,
461
+ description: entry.description,
462
+ version: entry.version,
463
+ workflowPath: resolveWorkflowEntryPath(entry, manifest, repoDir),
464
+ ref: source.kind === "remote" ? `${entry.name}@${source.owner}/${source.repo}` : void 0,
465
+ source
466
+ }));
467
+ }
468
+ function preferCanonicalWorkflowPath(repoDir, workflowPath) {
469
+ const relativePath = path.relative(repoDir, workflowPath);
470
+ const segments = relativePath.split(path.sep);
471
+ if (segments[0] !== ".workflows") {
472
+ return workflowPath;
473
+ }
474
+ const canonicalPath = path.join(repoDir, "workflows", ...segments.slice(1));
475
+ return fs.existsSync(canonicalPath) ? canonicalPath : workflowPath;
476
+ }
477
+ function isMarketplaceRef(entry) {
478
+ return MARKETPLACE_REF_RE.test(entry);
479
+ }
480
+ function isMarketplaceSlug(entry) {
481
+ return MARKETPLACE_SLUG_RE.test(entry);
482
+ }
483
+ function parseRef(ref) {
484
+ const atIdx = ref.indexOf("@");
485
+ const pluginName = ref.slice(0, atIdx);
486
+ const slug = ref.slice(atIdx + 1);
487
+ const slashIdx = slug.indexOf("/");
488
+ return {
489
+ pluginName,
490
+ owner: slug.slice(0, slashIdx),
491
+ repo: slug.slice(slashIdx + 1)
492
+ };
493
+ }
494
+ function buildMarketplacePluginResolution(ref, repoDir, manifestPath) {
495
+ const { pluginName } = parseRef(ref);
496
+ const pluginDir = resolvePluginDirFromManifest(
497
+ pluginName,
498
+ repoDir,
499
+ manifestPath
500
+ );
501
+ const version = resolvePluginVersionFromDir(pluginDir);
502
+ const artifactLayout = version ? resolvePackagedArtifactLayout(pluginDir, version) : void 0;
503
+ return {
504
+ ref,
505
+ pluginName,
506
+ marketplacePath: artifactLayout?.codexMarketplacePath ?? manifestPath,
507
+ pluginDir: artifactLayout?.claudeArtifactDir ?? pluginDir,
508
+ codexPluginDir: artifactLayout?.codexPluginDir ?? pluginDir
509
+ };
510
+ }
511
+ function isPathWithinRoot(rootDir, targetPath) {
512
+ const relative = path.relative(rootDir, targetPath);
513
+ return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
514
+ }
515
+ function resolvePluginVersionFromDir(pluginDir) {
516
+ const manifestPaths = [
517
+ path.join(pluginDir, ".codex-plugin", "plugin.json"),
518
+ path.join(pluginDir, ".claude-plugin", "plugin.json")
519
+ ];
520
+ for (const manifestPath of manifestPaths) {
521
+ if (!fs.existsSync(manifestPath)) continue;
522
+ try {
523
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
524
+ if (typeof manifest.version === "string" && manifest.version.length > 0) {
525
+ return manifest.version;
526
+ }
527
+ } catch {
528
+ }
529
+ }
530
+ return void 0;
531
+ }
532
+ function resolvePackagedArtifactLayout(pluginRoot, version) {
533
+ const releasePath = path.join(pluginRoot, "dist", version, "release.json");
534
+ if (!fs.existsSync(releasePath)) {
535
+ return void 0;
536
+ }
537
+ try {
538
+ const release = JSON.parse(
539
+ fs.readFileSync(releasePath, "utf-8")
540
+ );
541
+ if (release.version !== version) {
542
+ return void 0;
543
+ }
544
+ const claudePath = release.artifacts?.claude?.path;
545
+ const codexMarketplacePath = release.artifacts?.codex?.marketplacePath;
546
+ const codexPluginPath = release.artifacts?.codex?.pluginPath;
547
+ if (typeof claudePath !== "string" || typeof codexMarketplacePath !== "string" || typeof codexPluginPath !== "string") {
548
+ return void 0;
549
+ }
550
+ const releaseDir = path.dirname(releasePath);
551
+ const claudeArtifactDir = path.resolve(releaseDir, claudePath);
552
+ const resolvedCodexMarketplacePath = path.resolve(
553
+ releaseDir,
554
+ codexMarketplacePath
555
+ );
556
+ const codexPluginDir = path.resolve(releaseDir, codexPluginPath);
557
+ if (!isPathWithinRoot(releaseDir, claudeArtifactDir) || !isPathWithinRoot(releaseDir, resolvedCodexMarketplacePath) || !isPathWithinRoot(releaseDir, codexPluginDir)) {
558
+ return void 0;
559
+ }
560
+ if (!fs.existsSync(claudeArtifactDir) || !fs.existsSync(resolvedCodexMarketplacePath) || !fs.existsSync(codexPluginDir)) {
561
+ return void 0;
562
+ }
563
+ return {
564
+ claudeArtifactDir,
565
+ codexMarketplacePath: resolvedCodexMarketplacePath,
566
+ codexPluginDir
567
+ };
568
+ } catch {
569
+ return void 0;
570
+ }
571
+ }
572
+ function ensureRepo(owner, repo) {
573
+ const repoDir = marketplaceRepoCacheDir(owner, repo);
574
+ if (!fs.existsSync(repoDir)) {
575
+ const repoUrl = `https://github.com/${owner}/${repo}.git`;
576
+ fs.mkdirSync(repoDir, { recursive: true });
577
+ try {
578
+ execFileSync("git", ["clone", "--depth", "1", repoUrl, repoDir], {
579
+ stdio: "ignore"
580
+ });
581
+ } catch (error) {
582
+ fs.rmSync(repoDir, { recursive: true, force: true });
583
+ throw new Error(
584
+ `Failed to clone marketplace repo ${owner}/${repo}: ${error.message}`
585
+ );
586
+ }
587
+ } else {
588
+ try {
589
+ execFileSync("git", ["pull", "--ff-only"], {
590
+ cwd: repoDir,
591
+ stdio: "ignore"
592
+ });
593
+ } catch {
594
+ }
595
+ }
596
+ return repoDir;
597
+ }
598
+ function requireGitForMarketplace(kind) {
599
+ try {
600
+ execFileSync("git", ["--version"], { stdio: "ignore" });
601
+ } catch {
602
+ throw new Error(
603
+ `git is not installed. Install git to use marketplace ${kind}.`
604
+ );
605
+ }
606
+ }
607
+
608
+ // src/infra/plugins/workflowSourceErrors.ts
609
+ var WorkflowAmbiguityError = class extends Error {
610
+ workflowName;
611
+ candidates;
612
+ constructor(workflowName, candidates) {
613
+ const list = candidates.map((c) => ` - ${c.sourceLabel}: use ${c.disambiguator}`).join("\n");
614
+ super(
615
+ `Workflow "${workflowName}" is provided by multiple configured marketplaces:
616
+ ${list}
617
+ Run \`athena-flow workflow install <disambiguator>\` to pick one.`
618
+ );
619
+ this.name = "WorkflowAmbiguityError";
620
+ this.workflowName = workflowName;
621
+ this.candidates = candidates;
622
+ }
623
+ };
624
+ var WorkflowNotFoundError = class extends Error {
625
+ workflowName;
626
+ searchedSources;
627
+ constructor(workflowName, searchedSources) {
628
+ const sourceList = searchedSources.length ? searchedSources.join(", ") : "(no marketplaces configured)";
629
+ super(
630
+ `Workflow "${workflowName}" not found in any configured marketplace (searched: ${sourceList}).`
631
+ );
632
+ this.name = "WorkflowNotFoundError";
633
+ this.workflowName = workflowName;
634
+ this.searchedSources = searchedSources;
635
+ }
636
+ };
637
+
638
+ // src/infra/plugins/workflowSourceResolution.ts
639
+ function findMarketplaceRepoDir(startPath) {
640
+ let currentDir = path2.resolve(startPath);
641
+ for (; ; ) {
642
+ if (fs2.existsSync(resolveWorkflowManifestPath(currentDir)) || fs2.existsSync(resolvePluginManifestPath(currentDir))) {
643
+ return currentDir;
644
+ }
645
+ const parentDir = path2.dirname(currentDir);
646
+ if (parentDir === currentDir) {
647
+ return void 0;
648
+ }
649
+ currentDir = parentDir;
650
+ }
651
+ }
652
+ function resolveWorkflowMarketplaceSource(source) {
653
+ const trimmed = source.trim();
654
+ const resolvedPath = path2.resolve(trimmed);
655
+ if (!fs2.existsSync(resolvedPath) && isMarketplaceSlug(trimmed)) {
656
+ const slashIdx = trimmed.indexOf("/");
657
+ return {
658
+ kind: "remote",
659
+ slug: trimmed,
660
+ owner: trimmed.slice(0, slashIdx),
661
+ repo: trimmed.slice(slashIdx + 1)
662
+ };
663
+ }
664
+ const repoDir = findMarketplaceRepoDir(trimmed);
665
+ if (!repoDir) {
666
+ throw new Error(
667
+ `Local marketplace not found from source: ${trimmed}. Expected a marketplace repo root or a path inside one.`
668
+ );
669
+ }
670
+ return {
671
+ kind: "local",
672
+ path: resolvedPath,
673
+ repoDir
674
+ };
675
+ }
676
+ function listMarketplaceWorkflows(owner, repo) {
677
+ requireGitForMarketplace("workflows");
678
+ const repoDir = ensureRepo(owner, repo);
679
+ return listWorkflowEntriesFromManifest(
680
+ repoDir,
681
+ resolveWorkflowManifestPath(repoDir),
682
+ { kind: "remote", slug: `${owner}/${repo}`, owner, repo }
683
+ );
684
+ }
685
+ function listMarketplaceWorkflowsFromRepo(repoDir) {
686
+ return listWorkflowEntriesFromManifest(
687
+ repoDir,
688
+ resolveWorkflowManifestPath(repoDir),
689
+ { kind: "local", repoDir }
690
+ );
691
+ }
692
+ function resolveMarketplaceWorkflow(ref) {
693
+ requireGitForMarketplace("workflows");
694
+ const { pluginName: workflowName, owner, repo } = parseRef(ref);
695
+ const repoDir = ensureRepo(owner, repo);
696
+ return resolveWorkflowPathFromManifest(
697
+ workflowName,
698
+ repoDir,
699
+ resolveWorkflowManifestPath(repoDir)
700
+ );
701
+ }
702
+ var WorkflowVersionNotFoundError = class extends Error {
703
+ workflowName;
704
+ requestedVersion;
705
+ availableVersion;
706
+ sourceLabel;
707
+ constructor(workflowName, requestedVersion, availableVersion, sourceLabel) {
708
+ const availableText = availableVersion ? `found version ${availableVersion}` : "marketplace entry does not declare a version";
709
+ super(
710
+ `Workflow "${workflowName}" version ${requestedVersion} not found in ${sourceLabel} (${availableText}).`
711
+ );
712
+ this.name = "WorkflowVersionNotFoundError";
713
+ this.workflowName = workflowName;
714
+ this.requestedVersion = requestedVersion;
715
+ this.availableVersion = availableVersion;
716
+ this.sourceLabel = sourceLabel;
717
+ }
718
+ };
719
+ function parseBareWorkflowName(source) {
720
+ const atIdx = source.indexOf("@");
721
+ if (atIdx <= 0 || atIdx === source.length - 1) {
722
+ return { bareName: source, pinnedVersion: void 0 };
723
+ }
724
+ const suffix = source.slice(atIdx + 1);
725
+ if (suffix.includes("/")) {
726
+ return { bareName: source, pinnedVersion: void 0 };
727
+ }
728
+ return {
729
+ bareName: source.slice(0, atIdx),
730
+ pinnedVersion: suffix
731
+ };
732
+ }
733
+ function gatherMarketplaceWorkflowSources(source) {
734
+ const trimmed = source.trim();
735
+ const resolvedPath = path2.resolve(trimmed);
736
+ if (fs2.existsSync(resolvedPath) && fs2.statSync(resolvedPath).isFile()) {
737
+ return [
738
+ {
739
+ kind: "filesystem",
740
+ workflowPath: fs2.realpathSync(resolvedPath)
741
+ }
742
+ ];
743
+ }
744
+ if (!fs2.existsSync(resolvedPath) && isMarketplaceSlug(trimmed)) {
745
+ const slashIdx = trimmed.indexOf("/");
746
+ const owner = trimmed.slice(0, slashIdx);
747
+ const repo = trimmed.slice(slashIdx + 1);
748
+ requireGitForMarketplace("workflows");
749
+ const repoDir2 = ensureRepo(owner, repo);
750
+ const manifestPath2 = resolveWorkflowManifestPath(repoDir2);
751
+ return listWorkflowEntriesFromManifest(repoDir2, manifestPath2, {
752
+ kind: "remote",
753
+ slug: trimmed,
754
+ owner,
755
+ repo
756
+ }).map((entry) => ({
757
+ kind: "marketplace-remote",
758
+ slug: trimmed,
759
+ owner,
760
+ repo,
761
+ workflowName: entry.name,
762
+ version: entry.version,
763
+ ref: entry.ref,
764
+ manifestPath: manifestPath2,
765
+ workflowPath: entry.workflowPath
766
+ }));
767
+ }
768
+ const repoDir = findMarketplaceRepoDir(trimmed);
769
+ if (!repoDir) {
770
+ throw new Error(
771
+ `Marketplace source not found: ${trimmed}. Expected a marketplace repo root, a path inside one, or an owner/repo slug.`
772
+ );
773
+ }
774
+ const canonicalRepoDir = fs2.realpathSync(repoDir);
775
+ const manifestPath = resolveWorkflowManifestPath(canonicalRepoDir);
776
+ return listWorkflowEntriesFromManifest(canonicalRepoDir, manifestPath, {
777
+ kind: "local",
778
+ repoDir: canonicalRepoDir
779
+ }).map((entry) => ({
780
+ kind: "marketplace-local",
781
+ repoDir: canonicalRepoDir,
782
+ workflowName: entry.name,
783
+ version: entry.version,
784
+ manifestPath,
785
+ workflowPath: entry.workflowPath
786
+ }));
787
+ }
788
+ function resolvedSourceLabel(s) {
789
+ if (s.kind === "marketplace-remote") return `marketplace ${s.slug}`;
790
+ if (s.kind === "marketplace-local") return `local marketplace ${s.repoDir}`;
791
+ return `file ${s.workflowPath}`;
792
+ }
793
+ function resolvedSourceDisambiguator(s) {
794
+ if (s.kind === "marketplace-remote") return s.ref;
795
+ return s.workflowPath;
796
+ }
797
+ function resolveWorkflowInstall(sourceOrName, configuredSources) {
798
+ if (isMarketplaceRef(sourceOrName)) {
799
+ const { pluginName: workflowName, owner, repo } = parseRef(sourceOrName);
800
+ requireGitForMarketplace("workflows");
801
+ const repoDir = ensureRepo(owner, repo);
802
+ const manifestPath = resolveWorkflowManifestPath(repoDir);
803
+ const workflowPath = resolveWorkflowPathFromManifest(
804
+ workflowName,
805
+ repoDir,
806
+ manifestPath
807
+ );
808
+ const entry = listWorkflowEntriesFromManifest(repoDir, manifestPath, {
809
+ kind: "remote",
810
+ slug: `${owner}/${repo}`,
811
+ owner,
812
+ repo
813
+ }).find((e) => e.name === workflowName);
814
+ return {
815
+ kind: "marketplace-remote",
816
+ slug: `${owner}/${repo}`,
817
+ owner,
818
+ repo,
819
+ workflowName,
820
+ version: entry?.version,
821
+ ref: sourceOrName,
822
+ manifestPath,
823
+ workflowPath
824
+ };
825
+ }
826
+ const resolvedPath = path2.resolve(sourceOrName);
827
+ if (fs2.existsSync(resolvedPath) && fs2.statSync(resolvedPath).isFile()) {
828
+ return { kind: "filesystem", workflowPath: fs2.realpathSync(resolvedPath) };
829
+ }
830
+ const { bareName, pinnedVersion } = parseBareWorkflowName(sourceOrName);
831
+ if (bareName.includes("/") || bareName.includes("\\")) {
832
+ throw new Error(`Workflow source not found: ${sourceOrName}`);
833
+ }
834
+ const allListings = [];
835
+ let versionMismatch;
836
+ for (const configured of configuredSources) {
837
+ let sources;
838
+ try {
839
+ sources = gatherMarketplaceWorkflowSources(configured);
840
+ } catch {
841
+ continue;
842
+ }
843
+ for (const src of sources) {
844
+ if ((src.kind === "marketplace-remote" || src.kind === "marketplace-local") && src.workflowName === bareName) {
845
+ if (pinnedVersion !== void 0 && src.version !== pinnedVersion) {
846
+ versionMismatch ??= new WorkflowVersionNotFoundError(
847
+ bareName,
848
+ pinnedVersion,
849
+ src.version,
850
+ resolvedSourceLabel(src)
851
+ );
852
+ continue;
853
+ }
854
+ allListings.push(src);
855
+ }
856
+ }
857
+ }
858
+ if (allListings.length === 0) {
859
+ if (versionMismatch) throw versionMismatch;
860
+ throw new WorkflowNotFoundError(bareName, configuredSources);
861
+ }
862
+ if (allListings.length > 1) {
863
+ const candidates = allListings.map((s) => ({
864
+ sourceLabel: resolvedSourceLabel(s),
865
+ disambiguator: resolvedSourceDisambiguator(s)
866
+ }));
867
+ throw new WorkflowAmbiguityError(bareName, candidates);
868
+ }
869
+ return allListings[0];
870
+ }
871
+
872
+ // src/infra/plugins/marketplace.ts
873
+ import { execFileSync as execFileSync3 } from "child_process";
874
+ import fs4 from "fs";
875
+
876
+ // src/infra/plugins/versionedPluginResolution.ts
877
+ import { execFileSync as execFileSync2 } from "child_process";
878
+ import fs3 from "fs";
879
+ import os2 from "os";
880
+ import path3 from "path";
881
+ var PLUGIN_NPM_SCOPE = "@athenaflow";
882
+ function pluginNpmPackageName(pluginName) {
883
+ return `${PLUGIN_NPM_SCOPE}/plugin-${pluginName}`;
884
+ }
885
+ function versionedPluginCacheDir() {
886
+ return path3.join(os2.homedir(), ".config", "athena", "plugin-packages");
887
+ }
888
+ function resolveVersionedPluginDir(owner, repo, pluginName, version) {
889
+ const cacheDir = path3.join(
890
+ versionedPluginCacheDir(),
891
+ owner,
892
+ repo,
893
+ pluginName,
894
+ version
895
+ );
896
+ return fs3.existsSync(cacheDir) ? cacheDir : void 0;
897
+ }
898
+ function fetchPluginPackage(owner, repo, pluginName, version) {
899
+ const npmPkg = pluginNpmPackageName(pluginName);
900
+ const destDir = path3.join(
901
+ versionedPluginCacheDir(),
902
+ owner,
903
+ repo,
904
+ pluginName,
905
+ version
906
+ );
907
+ if (fs3.existsSync(destDir)) {
908
+ return destDir;
909
+ }
910
+ const cacheBase = versionedPluginCacheDir();
911
+ fs3.mkdirSync(cacheBase, { recursive: true });
912
+ const tmpDir = fs3.mkdtempSync(path3.join(cacheBase, ".tmp-"));
913
+ try {
914
+ const tarball = execFileSync2(
915
+ "npm",
916
+ ["pack", `${npmPkg}@${version}`, "--pack-destination", tmpDir],
917
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
918
+ ).trim();
919
+ const tarballPath = path3.join(tmpDir, tarball);
920
+ if (!fs3.existsSync(tarballPath)) {
921
+ throw new Error(`npm pack did not produce expected tarball: ${tarball}`);
922
+ }
923
+ const extractDir = path3.join(tmpDir, "extracted");
924
+ fs3.mkdirSync(extractDir, { recursive: true });
925
+ execFileSync2("tar", ["xzf", tarballPath, "-C", extractDir], {
926
+ stdio: "ignore"
927
+ });
928
+ const packageDir = path3.join(extractDir, "package");
929
+ if (!fs3.existsSync(packageDir)) {
930
+ throw new Error(
931
+ "Extracted tarball does not contain a package/ directory"
932
+ );
933
+ }
934
+ fs3.mkdirSync(path3.dirname(destDir), { recursive: true });
935
+ try {
936
+ fs3.renameSync(packageDir, destDir);
937
+ } catch (error) {
938
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
939
+ if ((code === "EEXIST" || code === "ENOTEMPTY") && fs3.existsSync(destDir)) {
940
+ return destDir;
941
+ }
942
+ throw error;
943
+ }
944
+ return destDir;
945
+ } finally {
946
+ fs3.rmSync(tmpDir, { recursive: true, force: true });
947
+ }
948
+ }
949
+ function resolvePackagedArtifactsOrThrow(pluginName, version, pluginRoot, stage) {
950
+ const artifactLayout = resolvePackagedArtifactLayout(pluginRoot, version);
951
+ if (!artifactLayout) {
952
+ throw new Error(
953
+ `${stage} for plugin "${pluginName}" version ${version} is missing packaged runtime artifacts`
954
+ );
955
+ }
956
+ return artifactLayout;
957
+ }
958
+ function readSourceFallbackEntry(repoDir, pluginName) {
959
+ const manifestPath = resolvePluginManifestPath(repoDir);
960
+ const manifest = readManifest(manifestPath);
961
+ const entry = manifest.plugins.find((plugin) => plugin.name === pluginName);
962
+ if (!entry) {
963
+ return void 0;
964
+ }
965
+ const pluginRoot = path3.join(
966
+ repoDir,
967
+ entryRelativeSourcePath(entry, pluginName)
968
+ );
969
+ const version = entry.version ?? resolvePluginVersionFromDir(pluginRoot);
970
+ return { pluginRoot, version };
971
+ }
972
+ function resolveMarketplaceSourceFallback(ref, pluginName, version, repoDir) {
973
+ const entry = readSourceFallbackEntry(repoDir, pluginName);
974
+ if (!entry || entry.version !== version) {
975
+ return void 0;
976
+ }
977
+ resolvePackagedArtifactsOrThrow(
978
+ pluginName,
979
+ version,
980
+ entry.pluginRoot,
981
+ "marketplace source fallback"
982
+ );
983
+ return buildMarketplacePluginResolution(
984
+ ref,
985
+ repoDir,
986
+ resolvePluginManifestPath(repoDir)
987
+ );
988
+ }
989
+ function describeSourceFallbackRejection(owner, repo, pluginName, version, repoDir) {
990
+ if (!fs3.existsSync(repoDir)) {
991
+ return `source fallback was unavailable because marketplace repo ${owner}/${repo} is not cached locally`;
992
+ }
993
+ let entry;
994
+ try {
995
+ entry = readSourceFallbackEntry(repoDir, pluginName);
996
+ } catch (error) {
997
+ return `source fallback could not determine the marketplace source version: ${error.message}`;
998
+ }
999
+ if (!entry?.version) {
1000
+ return `source fallback was considered, but marketplace repo ${owner}/${repo} does not expose a version for plugin "${pluginName}"`;
1001
+ }
1002
+ return `source fallback was considered, but marketplace repo ${owner}/${repo} exposes version ${entry.version} instead of requested ${version}`;
1003
+ }
1004
+ function resolveVersionedMarketplacePluginTarget(ref, version, sourceRepoDir, options) {
1005
+ const { pluginName, owner, repo } = parseRef(ref);
1006
+ const forceRefresh = options?.forceRefresh === true;
1007
+ const cachedDir = resolveVersionedPluginDir(owner, repo, pluginName, version);
1008
+ if (cachedDir && !forceRefresh) {
1009
+ const artifactLayout = resolvePackagedArtifactsOrThrow(
1010
+ pluginName,
1011
+ version,
1012
+ cachedDir,
1013
+ "cache"
1014
+ );
1015
+ return {
1016
+ ref,
1017
+ pluginName,
1018
+ marketplacePath: artifactLayout.codexMarketplacePath,
1019
+ pluginDir: artifactLayout.claudeArtifactDir,
1020
+ codexPluginDir: artifactLayout.codexPluginDir
1021
+ };
1022
+ }
1023
+ let npmError;
1024
+ try {
1025
+ const fetchedDir = fetchPluginPackage(owner, repo, pluginName, version);
1026
+ const artifactLayout = resolvePackagedArtifactsOrThrow(
1027
+ pluginName,
1028
+ version,
1029
+ fetchedDir,
1030
+ "npm package"
1031
+ );
1032
+ return {
1033
+ ref,
1034
+ pluginName,
1035
+ marketplacePath: artifactLayout.codexMarketplacePath,
1036
+ pluginDir: artifactLayout.claudeArtifactDir,
1037
+ codexPluginDir: artifactLayout.codexPluginDir
1038
+ };
1039
+ } catch (error) {
1040
+ npmError = error;
1041
+ }
1042
+ const repoDir = sourceRepoDir ?? ensureRepo(owner, repo);
1043
+ const fallbackTarget = fs3.existsSync(repoDir) ? resolveMarketplaceSourceFallback(ref, pluginName, version, repoDir) : void 0;
1044
+ if (fallbackTarget) {
1045
+ return fallbackTarget;
1046
+ }
1047
+ const npmStageMessage = `npm package resolution failed for ${pluginNpmPackageName(pluginName)}@${version}: ${npmError.message}`;
1048
+ const fallbackMessage = describeSourceFallbackRejection(
1049
+ owner,
1050
+ repo,
1051
+ pluginName,
1052
+ version,
1053
+ repoDir
1054
+ );
1055
+ throw new Error(
1056
+ `Plugin "${pluginName}" version ${version} could not be resolved. ${npmStageMessage}. ${fallbackMessage}.`
1057
+ );
1058
+ }
1059
+ function refreshVersionedMarketplacePluginTarget(ref, version, sourceRepoDir) {
1060
+ return resolveVersionedMarketplacePluginTarget(ref, version, sourceRepoDir, {
1061
+ forceRefresh: true
1062
+ });
1063
+ }
1064
+
1065
+ // src/infra/plugins/marketplace.ts
1066
+ function pullMarketplaceRepo(owner, repo) {
1067
+ const repoDir = marketplaceRepoCacheDir(owner, repo);
1068
+ if (!fs4.existsSync(repoDir)) {
1069
+ throw new Error(
1070
+ `Marketplace repo ${owner}/${repo} is not cached. It will be cloned on first use.`
1071
+ );
1072
+ }
1073
+ execFileSync3("git", ["pull", "--ff-only"], {
1074
+ cwd: repoDir,
1075
+ stdio: "ignore"
1076
+ });
1077
+ }
1078
+ function resolveMarketplacePlugin(ref) {
1079
+ requireGitForMarketplace("plugins");
1080
+ const { pluginName, owner, repo } = parseRef(ref);
1081
+ const repoDir = ensureRepo(owner, repo);
1082
+ return resolvePluginDirFromManifest(
1083
+ pluginName,
1084
+ repoDir,
1085
+ resolvePluginManifestPath(repoDir)
1086
+ );
1087
+ }
1088
+ function resolveMarketplacePluginTarget(ref) {
1089
+ requireGitForMarketplace("plugins");
1090
+ const { owner, repo } = parseRef(ref);
1091
+ const repoDir = ensureRepo(owner, repo);
1092
+ const directTarget = buildMarketplacePluginResolution(
1093
+ ref,
1094
+ repoDir,
1095
+ resolvePluginManifestPath(repoDir)
1096
+ );
1097
+ const pluginVersion = resolvePluginVersionFromDir(directTarget.pluginDir);
1098
+ if (!pluginVersion) {
1099
+ return directTarget;
1100
+ }
1101
+ return resolveVersionedMarketplacePluginTarget(ref, pluginVersion, repoDir);
1102
+ }
1103
+ function resolveMarketplacePluginTargetFromRepo(ref, repoDir) {
1104
+ return buildMarketplacePluginResolution(
1105
+ ref,
1106
+ repoDir,
1107
+ resolvePluginManifestPath(repoDir)
1108
+ );
1109
+ }
1110
+
1111
+ // src/infra/plugins/config.ts
1112
+ import fs5 from "fs";
1113
+ import os3 from "os";
1114
+ import path4 from "path";
1115
+ var EMPTY_CONFIG = { plugins: [], additionalDirectories: [] };
1116
+ function projectConfigPath(projectDir) {
1117
+ return path4.join(projectDir, ".athena", "config.json");
1118
+ }
1119
+ function readConfig(projectDir) {
1120
+ return readConfigFile(projectConfigPath(projectDir), projectDir);
1121
+ }
1122
+ function resolveActiveWorkflow(input) {
1123
+ const { globalConfig, projectConfig, override } = input;
1124
+ if (override !== void 0) {
1125
+ return {
1126
+ name: override,
1127
+ source: "override",
1128
+ selectionsLayer: projectConfig.activeWorkflow !== void 0 ? projectConfig : globalConfig
1129
+ };
1130
+ }
1131
+ if (projectConfig.activeWorkflow !== void 0) {
1132
+ return {
1133
+ name: projectConfig.activeWorkflow,
1134
+ source: "project",
1135
+ selectionsLayer: projectConfig
1136
+ };
1137
+ }
1138
+ if (globalConfig.activeWorkflow !== void 0) {
1139
+ return {
1140
+ name: globalConfig.activeWorkflow,
1141
+ source: "global",
1142
+ selectionsLayer: globalConfig
1143
+ };
1144
+ }
1145
+ return {
1146
+ name: "default",
1147
+ source: "default",
1148
+ selectionsLayer: globalConfig
1149
+ };
1150
+ }
1151
+ function readGlobalConfig() {
1152
+ const homeDir = os3.homedir();
1153
+ const configPath = path4.join(homeDir, ".config", "athena", "config.json");
1154
+ return readConfigFile(configPath, homeDir);
1155
+ }
1156
+ function readConfigFile(configPath, baseDir) {
1157
+ if (!fs5.existsSync(configPath)) {
1158
+ return EMPTY_CONFIG;
1159
+ }
1160
+ const raw = JSON.parse(fs5.readFileSync(configPath, "utf-8"));
1161
+ if ("workflowMarketplaceSource" in raw) {
1162
+ throw new Error(
1163
+ `Invalid config: "${configPath}" uses deprecated "workflowMarketplaceSource"; use "workflowMarketplaceSources"`
1164
+ );
1165
+ }
1166
+ if (raw.workflowMarketplaceSources !== void 0 && (!Array.isArray(raw.workflowMarketplaceSources) || !raw.workflowMarketplaceSources.every(
1167
+ (source) => typeof source === "string"
1168
+ ))) {
1169
+ throw new Error(
1170
+ `Invalid config: "${configPath}" field "workflowMarketplaceSources" must be an array of strings`
1171
+ );
1172
+ }
1173
+ if (raw.harness !== void 0 && raw.harness !== "claude-code" && raw.harness !== "openai-codex" && raw.harness !== "opencode") {
1174
+ throw new Error(
1175
+ `Invalid config: "${configPath}" field "harness" must be one of claude-code, openai-codex, opencode`
1176
+ );
1177
+ }
1178
+ if (raw.channels !== void 0 && (!Array.isArray(raw.channels) || !raw.channels.every((c) => typeof c === "string"))) {
1179
+ throw new Error(
1180
+ `Invalid config: "${configPath}" field "channels" must be an array of strings`
1181
+ );
1182
+ }
1183
+ const plugins = (raw.plugins ?? []).map((p) => {
1184
+ if (isMarketplaceRef(p)) {
1185
+ try {
1186
+ return resolveMarketplacePlugin(p);
1187
+ } catch (error) {
1188
+ console.error(
1189
+ `Warning: skipping plugin "${p}": ${error.message}`
1190
+ );
1191
+ return null;
1192
+ }
1193
+ }
1194
+ return path4.isAbsolute(p) ? p : path4.resolve(baseDir, p);
1195
+ }).filter((p) => p !== null);
1196
+ const additionalDirectories = (raw.additionalDirectories ?? []).map(
1197
+ (dir) => path4.isAbsolute(dir) ? dir : path4.resolve(baseDir, dir)
1198
+ );
1199
+ return {
1200
+ plugins,
1201
+ additionalDirectories,
1202
+ model: raw.model,
1203
+ theme: raw.theme,
1204
+ activeWorkflow: raw.activeWorkflow,
1205
+ workflowMarketplaceSources: raw.workflowMarketplaceSources,
1206
+ workflowSelections: raw.workflowSelections,
1207
+ setupComplete: raw.setupComplete,
1208
+ harness: raw.harness,
1209
+ telemetry: raw.telemetry,
1210
+ telemetryDiagnostics: raw.telemetryDiagnostics,
1211
+ deviceId: raw.deviceId,
1212
+ channels: raw.channels
1213
+ };
1214
+ }
1215
+ function readExistingConfig(configPath) {
1216
+ try {
1217
+ return JSON.parse(fs5.readFileSync(configPath, "utf-8"));
1218
+ } catch {
1219
+ return {};
1220
+ }
1221
+ }
1222
+ function writeConfigFile(configDir, configPath, updates, deleteKeys) {
1223
+ const existing = readExistingConfig(configPath);
1224
+ const merged = { ...existing, ...updates };
1225
+ if (updates.workflowSelections) {
1226
+ const existingSelections = existing["workflowSelections"] ?? {};
1227
+ merged["workflowSelections"] = {
1228
+ ...existingSelections,
1229
+ ...updates.workflowSelections
1230
+ };
1231
+ }
1232
+ if (deleteKeys) {
1233
+ for (const key of deleteKeys) {
1234
+ delete merged[key];
1235
+ }
1236
+ }
1237
+ fs5.mkdirSync(configDir, { recursive: true });
1238
+ fs5.writeFileSync(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
1239
+ }
1240
+ var GLOBAL_CONFIG_LEGACY_KEYS = [
1241
+ "workflow",
1242
+ "mcpServerOptions",
1243
+ "workflowMarketplaceSource"
1244
+ ];
1245
+ function writeGlobalConfig(updates) {
1246
+ const homeDir = os3.homedir();
1247
+ const configDir = path4.join(homeDir, ".config", "athena");
1248
+ const configPath = path4.join(configDir, "config.json");
1249
+ writeConfigFile(configDir, configPath, updates, GLOBAL_CONFIG_LEGACY_KEYS);
1250
+ }
1251
+ function writeProjectConfig(projectDir, updates) {
1252
+ const configDir = path4.join(projectDir, ".athena");
1253
+ const configPath = path4.join(configDir, "config.json");
1254
+ writeConfigFile(configDir, configPath, updates);
1255
+ }
1256
+ function hasActiveWorkflow(configPath) {
1257
+ try {
1258
+ const raw = JSON.parse(fs5.readFileSync(configPath, "utf-8"));
1259
+ return typeof raw.activeWorkflow === "string" && raw.activeWorkflow !== "";
1260
+ } catch {
1261
+ return false;
1262
+ }
1263
+ }
1264
+ function hasProjectWorkflow(projectDir) {
1265
+ return hasActiveWorkflow(projectConfigPath(projectDir));
1266
+ }
1267
+
1268
+ // src/setup/components/StepStatus.tsx
1269
+ import { Text, Box } from "ink";
1270
+ import { jsx, jsxs } from "react/jsx-runtime";
1271
+ function StepStatus({ status, message }) {
1272
+ const theme = useTheme();
1273
+ const icon = status === "success" ? "\u2713" : status === "error" ? "\u2717" : "\u280B";
1274
+ const color = status === "success" ? theme.status.success : status === "error" ? theme.status.error : theme.status.working;
1275
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", marginTop: 1, children: [
1276
+ /* @__PURE__ */ jsxs(Text, { color, bold: true, children: [
1277
+ icon,
1278
+ " "
1279
+ ] }),
1280
+ /* @__PURE__ */ jsx(Text, { color, children: message })
1281
+ ] });
1282
+ }
1283
+
1284
+ // src/setup/steps/McpOptionsStep.tsx
1285
+ import { useState as useState2, useCallback, useEffect as useEffect2, useRef as useRef2 } from "react";
1286
+ import { Box as Box3, Text as Text3 } from "ink";
1287
+
1288
+ // src/setup/components/StepSelector.tsx
1289
+ import { useEffect, useRef, useState } from "react";
1290
+ import { Box as Box2, Text as Text2, useInput } from "ink";
1291
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1292
+ function getInitialCursor(options, initialValue) {
1293
+ if (initialValue) {
1294
+ const initialIndex = options.findIndex(
1295
+ (option) => option.value === initialValue && !option.disabled
1296
+ );
1297
+ if (initialIndex >= 0) {
1298
+ return initialIndex;
1299
+ }
1300
+ }
1301
+ const firstEnabled = options.findIndex((option) => !option.disabled);
1302
+ return firstEnabled >= 0 ? firstEnabled : 0;
1303
+ }
1304
+ function StepSelector({
1305
+ options,
1306
+ onSelect,
1307
+ isActive = true,
1308
+ initialValue,
1309
+ onHighlight,
1310
+ gap = 0
1311
+ }) {
1312
+ const theme = useTheme();
1313
+ const [cursor, setCursor] = useState(
1314
+ () => getInitialCursor(options, initialValue)
1315
+ );
1316
+ const highlightedRef = useRef(void 0);
1317
+ const moveCursor = (direction) => {
1318
+ setCursor((prev) => {
1319
+ if (options.length <= 1) {
1320
+ return prev;
1321
+ }
1322
+ let next = prev;
1323
+ for (let i = 0; i < options.length; i += 1) {
1324
+ const candidate = Math.max(
1325
+ 0,
1326
+ Math.min(next + direction, options.length - 1)
1327
+ );
1328
+ if (candidate === next) {
1329
+ return prev;
1330
+ }
1331
+ next = candidate;
1332
+ if (!options[next]?.disabled) {
1333
+ return next;
1334
+ }
1335
+ }
1336
+ return prev;
1337
+ });
1338
+ };
1339
+ useInput(
1340
+ (_input, key) => {
1341
+ if (key.downArrow) {
1342
+ moveCursor(1);
1343
+ } else if (key.upArrow) {
1344
+ moveCursor(-1);
1345
+ } else if (key.return) {
1346
+ const opt = options[cursor];
1347
+ if (!opt.disabled) {
1348
+ onSelect(opt.value);
1349
+ }
1350
+ }
1351
+ },
1352
+ { isActive }
1353
+ );
1354
+ useEffect(() => {
1355
+ if (!onHighlight) {
1356
+ return;
1357
+ }
1358
+ const option = options[cursor];
1359
+ if (option.disabled) {
1360
+ return;
1361
+ }
1362
+ if (highlightedRef.current === option.value) {
1363
+ return;
1364
+ }
1365
+ highlightedRef.current = option.value;
1366
+ onHighlight(option.value);
1367
+ }, [cursor, options, onHighlight]);
1368
+ return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", gap, children: options.map((opt, i) => {
1369
+ const isCursor = i === cursor;
1370
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
1371
+ /* @__PURE__ */ jsxs2(
1372
+ Text2,
1373
+ {
1374
+ color: opt.disabled ? theme.textMuted : isCursor ? theme.accent : theme.text,
1375
+ bold: isCursor && !opt.disabled,
1376
+ inverse: isCursor && !opt.disabled,
1377
+ dimColor: opt.disabled,
1378
+ children: [
1379
+ isCursor ? " > " : " ",
1380
+ opt.label,
1381
+ isCursor ? " " : ""
1382
+ ]
1383
+ }
1384
+ ),
1385
+ opt.description && !opt.disabled ? /* @__PURE__ */ jsx2(Box2, { paddingLeft: 5, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, italic: true, children: [
1386
+ "\u21B3 ",
1387
+ opt.description
1388
+ ] }) }) : null
1389
+ ] }, opt.value);
1390
+ }) });
1391
+ }
1392
+
1393
+ // src/setup/steps/McpOptionsStep.tsx
1394
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1395
+ function McpOptionsStep({ servers, onComplete }) {
1396
+ const theme = useTheme();
1397
+ const [serverIndex, setServerIndex] = useState2(0);
1398
+ const choicesRef = useRef2({});
1399
+ const autoSkippedRef = useRef2(false);
1400
+ useEffect2(() => {
1401
+ if (servers.length === 0 && !autoSkippedRef.current) {
1402
+ autoSkippedRef.current = true;
1403
+ onComplete({});
1404
+ }
1405
+ }, [servers.length, onComplete]);
1406
+ const handleSelect = useCallback(
1407
+ (value) => {
1408
+ const server = servers[serverIndex];
1409
+ const selectedOption = server.options[Number(value)];
1410
+ choicesRef.current = {
1411
+ ...choicesRef.current,
1412
+ [server.serverName]: selectedOption.env
1413
+ };
1414
+ if (serverIndex + 1 < servers.length) {
1415
+ setServerIndex((prev) => prev + 1);
1416
+ } else {
1417
+ onComplete(choicesRef.current);
1418
+ }
1419
+ },
1420
+ [serverIndex, servers, onComplete]
1421
+ );
1422
+ if (servers.length === 0) {
1423
+ return null;
1424
+ }
1425
+ const currentServer = servers[serverIndex];
1426
+ const selectorOptions = currentServer.options.map((opt, i) => ({
1427
+ label: i === 0 ? `${opt.label} (default)` : opt.label,
1428
+ value: String(i)
1429
+ }));
1430
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
1431
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accent, children: "Configure MCP servers" }),
1432
+ /* @__PURE__ */ jsxs3(Text3, { color: theme.textMuted, children: [
1433
+ "Server ",
1434
+ serverIndex + 1,
1435
+ " of ",
1436
+ servers.length,
1437
+ ":",
1438
+ " ",
1439
+ /* @__PURE__ */ jsx3(Text3, { bold: true, children: currentServer.serverName })
1440
+ ] }),
1441
+ /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
1442
+ StepSelector,
1443
+ {
1444
+ options: selectorOptions,
1445
+ onSelect: handleSelect
1446
+ },
1447
+ currentServer.serverName
1448
+ ) })
1449
+ ] });
1450
+ }
1451
+
1452
+ // src/core/workflows/types.ts
1453
+ function pluginSpecRef(spec) {
1454
+ return typeof spec === "string" ? spec : spec.ref;
1455
+ }
1456
+ function pluginSpecVersion(spec) {
1457
+ return typeof spec === "string" ? void 0 : spec.version;
1458
+ }
1459
+
1460
+ // src/core/workflows/installer.ts
1461
+ function installWorkflowPlugins(workflow) {
1462
+ return resolveWorkflowPlugins(workflow).resolvedPlugins.map(
1463
+ (plugin) => plugin.claudeArtifactDir
1464
+ );
1465
+ }
1466
+ function marketplaceNameFromRef(ref) {
1467
+ return ref.slice(ref.indexOf("@") + 1);
1468
+ }
1469
+ function toResolvedWorkflowPlugin(target, ref, version) {
1470
+ return {
1471
+ ref: target.ref,
1472
+ pluginName: target.pluginName,
1473
+ marketplaceName: marketplaceNameFromRef(ref),
1474
+ ...version !== void 0 && { version },
1475
+ pluginDir: target.pluginDir,
1476
+ claudeArtifactDir: target.pluginDir,
1477
+ codexPluginDir: target.codexPluginDir,
1478
+ codexMarketplacePath: target.marketplacePath
1479
+ };
1480
+ }
1481
+ function resolveWorkflowPlugins(workflow) {
1482
+ const resolvedPlugins = workflow.plugins.map((spec) => {
1483
+ const ref = pluginSpecRef(spec);
1484
+ const version = pluginSpecVersion(spec);
1485
+ try {
1486
+ const source = "__source" in workflow ? workflow.__source : void 0;
1487
+ const repoDir = source?.kind === "marketplace-local" ? source.repoDir : void 0;
1488
+ if (version) {
1489
+ const target2 = resolveVersionedMarketplacePluginTarget(
1490
+ ref,
1491
+ version,
1492
+ repoDir
1493
+ );
1494
+ return toResolvedWorkflowPlugin(target2, ref, version);
1495
+ }
1496
+ if (repoDir) {
1497
+ const target2 = resolveMarketplacePluginTargetFromRepo(ref, repoDir);
1498
+ return toResolvedWorkflowPlugin(target2, ref);
1499
+ }
1500
+ const target = resolveMarketplacePluginTarget(ref);
1501
+ return toResolvedWorkflowPlugin(target, ref);
1502
+ } catch (error) {
1503
+ throw new Error(
1504
+ `Workflow "${workflow.name}": failed to install plugin "${ref}": ${error.message}`
1505
+ );
1506
+ }
1507
+ });
1508
+ return {
1509
+ resolvedPlugins,
1510
+ localPlugins: resolvedPlugins.map((p) => ({
1511
+ ref: p.ref,
1512
+ pluginDir: p.claudeArtifactDir
1513
+ })),
1514
+ codexPlugins: resolvedPlugins.map((p) => ({
1515
+ ref: p.ref,
1516
+ pluginName: p.pluginName,
1517
+ marketplacePath: p.codexMarketplacePath,
1518
+ ...p.version !== void 0 && { version: p.version }
1519
+ }))
1520
+ };
1521
+ }
1522
+ function refreshPinnedWorkflowPlugins(workflow) {
1523
+ const source = "__source" in workflow ? workflow.__source : void 0;
1524
+ for (const spec of workflow.plugins) {
1525
+ const ref = pluginSpecRef(spec);
1526
+ const version = pluginSpecVersion(spec);
1527
+ if (!version) {
1528
+ continue;
1529
+ }
1530
+ try {
1531
+ refreshVersionedMarketplacePluginTarget(
1532
+ ref,
1533
+ version,
1534
+ source?.kind === "marketplace-local" ? source.repoDir : void 0
1535
+ );
1536
+ } catch (error) {
1537
+ throw new Error(
1538
+ `Workflow "${workflow.name}": failed to refresh pinned plugin "${ref}" at version ${version}: ${error.message}`
1539
+ );
1540
+ }
1541
+ }
1542
+ }
1543
+
1544
+ // src/core/workflows/registry.ts
1545
+ import fs9 from "fs";
1546
+ import os5 from "os";
1547
+ import path7 from "path";
1548
+
1549
+ // src/core/workflows/builtins/index.ts
1550
+ import fs7 from "fs";
1551
+ import os4 from "os";
1552
+ import path5 from "path";
1553
+
1554
+ // src/core/workflows/loopManager.ts
1555
+ import fs6 from "fs";
1556
+
1557
+ // src/core/workflows/templateVars.ts
1558
+ function substituteVariables(text, ctx) {
1559
+ let result = text;
1560
+ if (ctx.input !== void 0) {
1561
+ result = result.replaceAll("{input}", ctx.input);
1562
+ }
1563
+ if (ctx.sessionId !== void 0) {
1564
+ result = result.replaceAll("{sessionId}", ctx.sessionId);
1565
+ result = result.replaceAll("<session_id>", ctx.sessionId);
1566
+ }
1567
+ if (ctx.trackerPath !== void 0) {
1568
+ result = result.replaceAll("{trackerPath}", ctx.trackerPath);
1569
+ }
1570
+ return result;
1571
+ }
1572
+
1573
+ // src/core/workflows/loopManager.ts
1574
+ var DEFAULT_COMPLETION_MARKER = "<!-- WORKFLOW_COMPLETE -->";
1575
+ var DEFAULT_BLOCKED_MARKER = "<!-- WORKFLOW_BLOCKED";
1576
+ var DEFAULT_TRACKER_PATH = ".athena/{sessionId}/tracker.md";
1577
+ var TRACKER_SKELETON_MARKER = "<!-- TRACKER_SKELETON -->";
1578
+ var DEFAULT_CONTINUE_PROMPT = "Continue the task. Read the tracker at {trackerPath} for current progress.";
1579
+ function createLoopManager(trackerPath, config) {
1580
+ let iteration = 0;
1581
+ let active = true;
1582
+ const completionMarker = config.completionMarker ?? DEFAULT_COMPLETION_MARKER;
1583
+ const blockedMarker = config.blockedMarker ?? DEFAULT_BLOCKED_MARKER;
1584
+ function readTracker() {
1585
+ try {
1586
+ return fs6.readFileSync(trackerPath, "utf-8");
1587
+ } catch {
1588
+ return "";
1589
+ }
1590
+ }
1591
+ function getTerminalLine(content) {
1592
+ const lines = content.trimEnd().split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
1593
+ return lines.at(-1);
1594
+ }
1595
+ function extractBlockedReason(line) {
1596
+ if (!line.startsWith(blockedMarker)) return void 0;
1597
+ const afterMarker = line.slice(blockedMarker.length);
1598
+ const match = afterMarker.match(/^:\s*(.+?)(?:\s*-->|$)/);
1599
+ return match?.[1]?.trim();
1600
+ }
1601
+ function getState() {
1602
+ const content = readTracker();
1603
+ const terminalLine = getTerminalLine(content);
1604
+ const completed = terminalLine === completionMarker;
1605
+ const blocked = terminalLine !== void 0 && (terminalLine === `${blockedMarker} -->` || terminalLine.startsWith(`${blockedMarker}:`));
1606
+ const blockedReason = blocked && terminalLine ? extractBlockedReason(terminalLine) : void 0;
1607
+ const reachedLimit = iteration >= config.maxIterations;
1608
+ const skeletonNotReplaced = content.includes(TRACKER_SKELETON_MARKER);
1609
+ return {
1610
+ active,
1611
+ iteration,
1612
+ maxIterations: config.maxIterations,
1613
+ completionMarker,
1614
+ blockedMarker,
1615
+ completed,
1616
+ blocked,
1617
+ blockedReason,
1618
+ reachedLimit,
1619
+ skeletonNotReplaced
1620
+ };
1621
+ }
1622
+ function incrementIteration() {
1623
+ iteration++;
1624
+ }
1625
+ function deactivate() {
1626
+ active = false;
1627
+ }
1628
+ return {
1629
+ getState,
1630
+ incrementIteration,
1631
+ deactivate,
1632
+ trackerPath
1633
+ };
1634
+ }
1635
+ function buildContinuePrompt(loop) {
1636
+ const template = loop.continuePrompt ?? DEFAULT_CONTINUE_PROMPT;
1637
+ return substituteVariables(template, {
1638
+ trackerPath: loop.trackerPath ?? DEFAULT_TRACKER_PATH
1639
+ });
1640
+ }
1641
+
1642
+ // src/core/workflows/builtins/index.ts
1643
+ var DEFAULT_BLOCKED_CLOSED_MARKER = `${DEFAULT_BLOCKED_MARKER} -->`;
1644
+ var DEFAULT_BLOCKED_REASON_MARKER = `${DEFAULT_BLOCKED_MARKER}: reason -->`;
1645
+ var SYSTEM_PROMPT = `You are working on a long-horizon task managed by Athena. A tracker file is used to persist progress across sessions.
1646
+
1647
+ ## Tracker File
1648
+
1649
+ At the start of each session, read the tracker file if it exists. It contains the task plan, completed steps, and current status from prior sessions.
1650
+
1651
+ If no tracker file exists, create one by:
1652
+ 1. Analyzing the user's request to understand the full scope
1653
+ 2. Breaking the task into concrete, actionable steps
1654
+ 3. Writing the plan to the tracker file
1655
+
1656
+ ### Tracker Format
1657
+
1658
+ Use this markdown format for the tracker:
1659
+
1660
+ \`\`\`
1661
+ # Task: <one-line summary>
1662
+
1663
+ ## Plan
1664
+ - [x] Step 1 description
1665
+ - [x] Step 2 description
1666
+ - [ ] Step 3 description (current)
1667
+ - [ ] Step 4 description
1668
+
1669
+ ## Current Status
1670
+ <brief description of where things stand and what to do next>
1671
+
1672
+ ## Notes
1673
+ <any important context, decisions, or blockers discovered along the way>
1674
+ \`\`\`
1675
+
1676
+ ### Updating the Tracker
1677
+
1678
+ After completing meaningful work, update the tracker:
1679
+ - Check off completed steps
1680
+ - Update the current status section
1681
+ - Add any important notes or decisions
1682
+
1683
+ ### Completion
1684
+
1685
+ When all steps are complete:
1686
+ 1. Update the tracker with all steps checked off
1687
+ 2. Add \`${DEFAULT_COMPLETION_MARKER}\` at the end of the tracker file
1688
+ 3. Provide a summary of what was accomplished
1689
+
1690
+ ### Blocked
1691
+
1692
+ If you are blocked and cannot make further progress:
1693
+ 1. Document what is blocking you in the Notes section
1694
+ 2. Add \`${DEFAULT_BLOCKED_CLOSED_MARKER}\` or \`${DEFAULT_BLOCKED_REASON_MARKER}\` at the end of the tracker file
1695
+ 3. Explain what needs to happen to unblock the task whenever possible
1696
+ `;
1697
+ function ensureSystemPromptFile() {
1698
+ const dir = path5.join(
1699
+ os4.homedir(),
1700
+ ".config",
1701
+ "athena",
1702
+ "builtins",
1703
+ "default"
1704
+ );
1705
+ const filePath = path5.join(dir, "system_prompt.md");
1706
+ if (!fs7.existsSync(filePath) || fs7.readFileSync(filePath, "utf-8") !== SYSTEM_PROMPT) {
1707
+ fs7.mkdirSync(dir, { recursive: true });
1708
+ fs7.writeFileSync(filePath, SYSTEM_PROMPT, "utf-8");
1709
+ }
1710
+ return filePath;
1711
+ }
1712
+ function resolveBuiltinWorkflow(name) {
1713
+ if (name !== "default") {
1714
+ return void 0;
1715
+ }
1716
+ return {
1717
+ name: "default",
1718
+ description: "General-purpose workflow for long-horizon tasks \u2014 breaks work into steps, tracks progress across sessions, and loops until complete",
1719
+ promptTemplate: "{input}",
1720
+ loop: {
1721
+ enabled: true,
1722
+ completionMarker: DEFAULT_COMPLETION_MARKER,
1723
+ blockedMarker: DEFAULT_BLOCKED_MARKER,
1724
+ maxIterations: 20
1725
+ },
1726
+ plugins: [],
1727
+ workflowFile: ensureSystemPromptFile()
1728
+ };
1729
+ }
1730
+ function listBuiltinWorkflows() {
1731
+ return ["default"];
1732
+ }
1733
+
1734
+ // src/core/workflows/sourceMetadata.ts
1735
+ import fs8 from "fs";
1736
+ import path6 from "path";
1737
+ function legacyLocalToNew(legacyPath, legacyRepoDir) {
1738
+ const repoDir = legacyRepoDir ?? findMarketplaceRepoDir(legacyPath);
1739
+ if (!repoDir) {
1740
+ return { kind: "filesystem", path: legacyPath };
1741
+ }
1742
+ try {
1743
+ const canonicalRepoDir = fs8.realpathSync(repoDir);
1744
+ const listings = listMarketplaceWorkflowsFromRepo(canonicalRepoDir);
1745
+ const absolutePath = fs8.realpathSync(legacyPath);
1746
+ const match = listings.find(
1747
+ (l) => fs8.realpathSync(l.workflowPath) === absolutePath
1748
+ );
1749
+ if (match) {
1750
+ return {
1751
+ kind: "marketplace-local",
1752
+ repoDir: canonicalRepoDir,
1753
+ workflowName: match.name,
1754
+ ...match.version ? { version: match.version } : {}
1755
+ };
1756
+ }
1757
+ } catch {
1758
+ }
1759
+ return { kind: "filesystem", path: legacyPath };
1760
+ }
1761
+ function readWorkflowSourceMetadata(workflowDir) {
1762
+ const sourceFile = path6.join(workflowDir, "source.json");
1763
+ if (!fs8.existsSync(sourceFile)) return void 0;
1764
+ let raw;
1765
+ try {
1766
+ raw = JSON.parse(fs8.readFileSync(sourceFile, "utf-8"));
1767
+ } catch {
1768
+ throw new Error(`Invalid source.json: ${sourceFile} is not valid JSON`);
1769
+ }
1770
+ if (!raw || typeof raw !== "object") {
1771
+ throw new Error(
1772
+ `Invalid source.json: ${sourceFile} must contain an object`
1773
+ );
1774
+ }
1775
+ const r = raw;
1776
+ if (r["v"] === 2) {
1777
+ if (r["kind"] === "marketplace-remote" && typeof r["ref"] === "string") {
1778
+ return {
1779
+ kind: "marketplace-remote",
1780
+ ref: r["ref"],
1781
+ ...typeof r["version"] === "string" ? { version: r["version"] } : {}
1782
+ };
1783
+ }
1784
+ if (r["kind"] === "marketplace-local" && typeof r["repoDir"] === "string" && typeof r["workflowName"] === "string") {
1785
+ return {
1786
+ kind: "marketplace-local",
1787
+ repoDir: r["repoDir"],
1788
+ workflowName: r["workflowName"],
1789
+ ...typeof r["version"] === "string" ? { version: r["version"] } : {}
1790
+ };
1791
+ }
1792
+ if (r["kind"] === "filesystem" && typeof r["path"] === "string") {
1793
+ return { kind: "filesystem", path: r["path"] };
1794
+ }
1795
+ }
1796
+ if (r["kind"] === "marketplace" && typeof r["ref"] === "string") {
1797
+ return { kind: "marketplace-remote", ref: r["ref"] };
1798
+ }
1799
+ if (r["kind"] === "local" && typeof r["path"] === "string") {
1800
+ return legacyLocalToNew(
1801
+ r["path"],
1802
+ typeof r["repoDir"] === "string" ? r["repoDir"] : void 0
1803
+ );
1804
+ }
1805
+ throw new Error(`Invalid source.json: ${sourceFile} kind is not supported`);
1806
+ }
1807
+ function writeWorkflowSourceMetadata(workflowDir, metadata) {
1808
+ fs8.mkdirSync(workflowDir, { recursive: true });
1809
+ fs8.writeFileSync(
1810
+ path6.join(workflowDir, "source.json"),
1811
+ JSON.stringify({ v: 2, ...metadata }),
1812
+ "utf-8"
1813
+ );
1814
+ }
1815
+
1816
+ // src/core/workflows/registry.ts
1817
+ function registryDir() {
1818
+ return path7.join(os5.homedir(), ".config", "athena", "workflows");
1819
+ }
1820
+ function ensurePathWithinRoot(rootDir, targetPath, label) {
1821
+ const relative = path7.relative(rootDir, targetPath);
1822
+ if (relative === "" || !relative.startsWith("..") && !path7.isAbsolute(relative)) {
1823
+ return;
1824
+ }
1825
+ throw new Error(`${label} resolves outside the workflow root: ${targetPath}`);
1826
+ }
1827
+ function resolveWorkflow(name) {
1828
+ const workflowDir = path7.join(registryDir(), name);
1829
+ const workflowPath = path7.join(workflowDir, "workflow.json");
1830
+ if (!fs9.existsSync(workflowPath)) {
1831
+ const builtin = resolveBuiltinWorkflow(name);
1832
+ if (builtin) {
1833
+ return builtin;
1834
+ }
1835
+ throw new Error(
1836
+ `Workflow "${name}" not found. Install with: athena workflow install <source> --name ${name}`
1837
+ );
1838
+ }
1839
+ const source = readWorkflowSourceMetadata(workflowDir);
1840
+ const raw = JSON.parse(fs9.readFileSync(workflowPath, "utf-8"));
1841
+ if (!Array.isArray(raw["plugins"])) {
1842
+ throw new Error(
1843
+ `Invalid workflow.json: "plugins" must be an array (got ${typeof raw["plugins"]})`
1844
+ );
1845
+ }
1846
+ for (const entry of raw["plugins"]) {
1847
+ if (typeof entry === "string") {
1848
+ if (isMarketplaceRef(entry.trim())) {
1849
+ continue;
1850
+ }
1851
+ throw new Error(
1852
+ `Invalid workflow.json: each plugin must be a valid marketplace ref string or {ref, version} object`
1853
+ );
1854
+ }
1855
+ if (typeof entry === "object" && entry !== null) {
1856
+ const r = entry["ref"];
1857
+ const v = entry["version"];
1858
+ if (typeof r === "string" && isMarketplaceRef(r.trim()) && r.trim().length > 0 && typeof v === "string" && v.trim().length > 0) {
1859
+ continue;
1860
+ }
1861
+ }
1862
+ throw new Error(
1863
+ `Invalid workflow.json: each plugin must be a valid marketplace ref string or {ref, version} object`
1864
+ );
1865
+ }
1866
+ if (typeof raw["promptTemplate"] !== "string") {
1867
+ throw new Error(`Invalid workflow.json: "promptTemplate" must be a string`);
1868
+ }
1869
+ if (raw["examplePrompts"] !== void 0 && (!Array.isArray(raw["examplePrompts"]) || !raw["examplePrompts"].every((e) => typeof e === "string"))) {
1870
+ throw new Error(
1871
+ `Invalid workflow.json: "examplePrompts" must be an array of strings`
1872
+ );
1873
+ }
1874
+ if (typeof raw["workflowFile"] !== "string" || raw["workflowFile"].length === 0) {
1875
+ throw new Error(
1876
+ 'Invalid workflow.json: "workflowFile" is required and must point to the workflow instructions file'
1877
+ );
1878
+ }
1879
+ const workflowFile = raw["workflowFile"];
1880
+ if (typeof workflowFile === "string" && !path7.isAbsolute(workflowFile)) {
1881
+ const workflowFilePath = path7.resolve(workflowDir, workflowFile);
1882
+ if (!fs9.existsSync(workflowFilePath)) {
1883
+ throw new Error(
1884
+ `Invalid workflow.json: workflowFile "${workflowFile}" not found at ${workflowFilePath}`
1885
+ );
1886
+ }
1887
+ raw["workflowFile"] = workflowFilePath;
1888
+ } else if (typeof workflowFile === "string" && !fs9.existsSync(workflowFile)) {
1889
+ throw new Error(
1890
+ `Invalid workflow.json: workflowFile "${workflowFile}" not found`
1891
+ );
1892
+ }
1893
+ return {
1894
+ ...raw,
1895
+ ...source ? { __source: source } : {}
1896
+ };
1897
+ }
1898
+ function readWorkflowSource(sourcePath) {
1899
+ const content = fs9.readFileSync(sourcePath, "utf-8");
1900
+ return { content, workflow: JSON.parse(content) };
1901
+ }
1902
+ function copyWorkflowFiles(sourcePath, destDir) {
1903
+ const { content, workflow } = readWorkflowSource(sourcePath);
1904
+ const absoluteSourcePath = path7.resolve(sourcePath);
1905
+ const sourceDir = path7.dirname(absoluteSourcePath);
1906
+ const absoluteDestDir = path7.resolve(destDir);
1907
+ fs9.mkdirSync(absoluteDestDir, { recursive: true });
1908
+ fs9.writeFileSync(
1909
+ path7.join(absoluteDestDir, "workflow.json"),
1910
+ content,
1911
+ "utf-8"
1912
+ );
1913
+ const copyRelativeAsset = (assetPath) => {
1914
+ if (!assetPath || path7.isAbsolute(assetPath)) return;
1915
+ const sourceAssetPath = path7.resolve(sourceDir, assetPath);
1916
+ ensurePathWithinRoot(sourceDir, sourceAssetPath, "Workflow asset");
1917
+ if (!fs9.existsSync(sourceAssetPath)) return;
1918
+ const destAssetPath = path7.resolve(absoluteDestDir, assetPath);
1919
+ ensurePathWithinRoot(absoluteDestDir, destAssetPath, "Workflow asset");
1920
+ fs9.mkdirSync(path7.dirname(destAssetPath), { recursive: true });
1921
+ fs9.copyFileSync(sourceAssetPath, destAssetPath);
1922
+ };
1923
+ const rawWorkflow = workflow;
1924
+ const promptAsset = rawWorkflow["workflowFile"];
1925
+ if (typeof promptAsset !== "string" || promptAsset.length === 0) {
1926
+ throw new Error(
1927
+ 'Invalid workflow.json: "workflowFile" is required and must point to the workflow instructions file'
1928
+ );
1929
+ }
1930
+ copyRelativeAsset(promptAsset);
1931
+ }
1932
+ function toStoredMetadata(source) {
1933
+ if (source.kind === "marketplace-remote") {
1934
+ return {
1935
+ kind: "marketplace-remote",
1936
+ ref: source.ref,
1937
+ ...source.version ? { version: source.version } : {}
1938
+ };
1939
+ }
1940
+ if (source.kind === "marketplace-local") {
1941
+ return {
1942
+ kind: "marketplace-local",
1943
+ repoDir: source.repoDir,
1944
+ workflowName: source.workflowName,
1945
+ ...source.version ? { version: source.version } : {}
1946
+ };
1947
+ }
1948
+ return { kind: "filesystem", path: source.workflowPath };
1949
+ }
1950
+ function installWorkflowFromSource(source, name) {
1951
+ const { workflow } = readWorkflowSource(source.workflowPath);
1952
+ const workflowName = name ?? workflow.name;
1953
+ if (!workflowName) {
1954
+ throw new Error(
1955
+ 'Workflow has no "name" field. Provide --name to specify one.'
1956
+ );
1957
+ }
1958
+ const destDir = path7.join(registryDir(), workflowName);
1959
+ copyWorkflowFiles(source.workflowPath, destDir);
1960
+ const metadata = toStoredMetadata(source);
1961
+ writeWorkflowSourceMetadata(destDir, metadata);
1962
+ return workflowName;
1963
+ }
1964
+ function reResolveFromMetadata(metadata) {
1965
+ if (metadata.kind === "marketplace-remote") {
1966
+ const slug = metadata.ref.slice(metadata.ref.indexOf("@") + 1);
1967
+ const slashIdx = slug.indexOf("/");
1968
+ const owner = slug.slice(0, slashIdx);
1969
+ const repo = slug.slice(slashIdx + 1);
1970
+ try {
1971
+ pullMarketplaceRepo(owner, repo);
1972
+ } catch (error) {
1973
+ if (!(error instanceof Error) || !error.message.includes("is not cached")) {
1974
+ throw error;
1975
+ }
1976
+ }
1977
+ return resolveWorkflowInstall(metadata.ref, []);
1978
+ }
1979
+ if (metadata.kind === "marketplace-local") {
1980
+ const manifestPath = resolveWorkflowManifestPath(metadata.repoDir);
1981
+ const listings = listMarketplaceWorkflowsFromRepo(metadata.repoDir);
1982
+ const entry = listings.find((l) => l.name === metadata.workflowName);
1983
+ if (!entry) {
1984
+ throw new Error(
1985
+ `Workflow "${metadata.workflowName}" is no longer in the local marketplace at ${metadata.repoDir}.`
1986
+ );
1987
+ }
1988
+ return {
1989
+ kind: "marketplace-local",
1990
+ repoDir: metadata.repoDir,
1991
+ workflowName: metadata.workflowName,
1992
+ ...entry.version ? { version: entry.version } : {},
1993
+ manifestPath,
1994
+ workflowPath: entry.workflowPath
1995
+ };
1996
+ }
1997
+ return { kind: "filesystem", workflowPath: metadata.path };
1998
+ }
1999
+ function updateWorkflow(name) {
2000
+ const workflowDir = path7.join(registryDir(), name);
2001
+ const metadata = readWorkflowSourceMetadata(workflowDir);
2002
+ if (!metadata) {
2003
+ throw new Error(
2004
+ `Workflow "${name}" has no recorded source. Reinstall it with: athena-flow workflow install <source>`
2005
+ );
2006
+ }
2007
+ const source = reResolveFromMetadata(metadata);
2008
+ const installedName = installWorkflowFromSource(source, name);
2009
+ refreshPinnedWorkflowPlugins(resolveWorkflow(installedName));
2010
+ return installedName;
2011
+ }
2012
+ function listWorkflows() {
2013
+ const dir = registryDir();
2014
+ const installed = fs9.existsSync(dir) ? fs9.readdirSync(dir, { withFileTypes: true }).filter(
2015
+ (entry) => entry.isDirectory() && fs9.existsSync(path7.join(dir, entry.name, "workflow.json"))
2016
+ ).map((entry) => entry.name) : [];
2017
+ const builtins = listBuiltinWorkflows().filter(
2018
+ (name) => !installed.includes(name)
2019
+ );
2020
+ return [...builtins, ...installed];
2021
+ }
2022
+ function removeWorkflow(name) {
2023
+ const dir = path7.join(registryDir(), name);
2024
+ if (!fs9.existsSync(dir)) {
2025
+ throw new Error(`Workflow "${name}" not found.`);
2026
+ }
2027
+ fs9.rmSync(dir, { recursive: true, force: true });
2028
+ }
2029
+
2030
+ // src/core/workflows/applyWorkflow.ts
2031
+ function applyPromptTemplate(template, input, ctx) {
2032
+ return substituteVariables(template, { input, ...ctx });
2033
+ }
2034
+
2035
+ // src/core/workflows/plan.ts
2036
+ function compileWorkflowPlan(input) {
2037
+ if (!input.workflow) {
2038
+ return void 0;
2039
+ }
2040
+ const resolved = !input.resolvedPlugins && (!input.localPlugins || !input.codexPlugins) ? resolveWorkflowPlugins(input.workflow) : void 0;
2041
+ const resolvedPlugins = input.resolvedPlugins ?? resolved?.resolvedPlugins ?? [];
2042
+ const localPlugins = input.localPlugins ?? resolved?.localPlugins ?? [];
2043
+ const codexPlugins = input.codexPlugins ?? resolved?.codexPlugins ?? [];
2044
+ return {
2045
+ workflow: input.workflow,
2046
+ resolvedPlugins: resolvedPlugins.filter(
2047
+ (plugin, index, array) => array.findIndex((candidate) => candidate.ref === plugin.ref) === index
2048
+ ),
2049
+ localPlugins: localPlugins.filter(
2050
+ (plugin, index, array) => array.findIndex((candidate) => candidate.ref === plugin.ref) === index
2051
+ ),
2052
+ agentRoots: resolvedPlugins.map((plugin) => `${plugin.claudeArtifactDir}/agents`).filter((root, index, array) => array.indexOf(root) === index),
2053
+ codexPlugins: codexPlugins.filter(
2054
+ (target, index, array) => array.findIndex((candidate) => candidate.ref === target.ref) === index
2055
+ ),
2056
+ pluginMcpConfig: input.pluginMcpConfig
2057
+ };
2058
+ }
2059
+
2060
+ // src/core/workflows/sessionPlan.ts
2061
+ import fs10 from "fs";
2062
+ import path8 from "path";
2063
+
2064
+ // src/core/workflows/stateMachine.md
2065
+ var stateMachine_default = "# Stateless Session Protocol\n\nYou operate in stateless sessions managed by a workflow runner. Each session is a fresh process with no memory of prior sessions. The **tracker file** is your only continuity \u2014 it's how you talk to your future self.\n\n## Execution Model\n\nThe runner spawns `claude -p` sessions in a loop:\n\n- **Session 1**: You receive the user's original request.\n- **Sessions 2+**: You receive a continuation prompt directing you to read the tracker.\n- **Between sessions**: The runner inspects the tracker for terminal markers. If found, or if the max iteration cap is reached, the loop ends. The tracker is preserved for resume, audit, and debugging.\n\n### Terminal Markers\n\nBy default, workflows use these tracker markers:\n\n- `<!-- WORKFLOW_COMPLETE -->`\n- `<!-- WORKFLOW_BLOCKED -->`\n- `<!-- WORKFLOW_BLOCKED: reason -->`\n\nWorkflows may override the default marker strings via configuration. Use the markers configured for the current workflow.\n\nRules:\n\n- Only the last non-empty line of the tracker is treated as authoritative\n- Marker-like text earlier in the tracker, including notes, examples, or quoted instructions, is ignored\n- Write `WORKFLOW_COMPLETE` only when the workflow's completion criteria have been fully verified\n- Write `WORKFLOW_BLOCKED` only when progress cannot continue in the current workflow without external intervention or a workflow-defined stop condition has been reached\n- Include a concrete reason after the colon whenever possible, but `<!-- WORKFLOW_BLOCKED -->` without a reason is still valid\n\n### Tracker Path\n\nBy default, the tracker file lives at `.athena/<session_id>/tracker.md` in the project root, where `<session_id>` is the current Athena session ID. This session-scoped path allows multiple workflows to run concurrently and makes resume reliable. The runner provides the session ID \u2014 do not generate one yourself.\n\nWorkflows may override the default tracker path via configuration. Read and write the tracker at the configured path for the current workflow.\n\n**Assume interruption.** Your context window can reset at any moment \u2014 the runner may kill a session that's taking too long, or you may hit token limits mid-task. Any progress not written to the tracker is gone. This isn't a theoretical risk; it's the normal operating mode.\n\n## Session Protocol\n\nEvery session follows four phases: **Read**, **Orient**, **Execute**, **End**.\n\n### Phase 1 \u2014 Read the Tracker\n\nRead the tracker file at the configured tracker path for the current workflow. By default this is `.athena/<session_id>/tracker.md`.\n\n- **Contains `<!-- TRACKER_SKELETON -->`**: This is session 1. The runner created a skeleton tracker with the goal and session metadata. Proceed to Phase 2 (Orient) \u2014 replace the skeleton with a real tracker. **You must do this even if the entire request can be satisfied in a single turn.** Write a minimal tracker (what was asked, what was done, the outcome) and then append `<!-- WORKFLOW_COMPLETE -->`. Leaving the skeleton in place causes the runner to classify the session as a failure.\n- **Otherwise**: This is a continuation session. The tracker contains everything prior sessions learned and decided. Skip to Phase 3 (Execute) using the tracker's context.\n\nWhy read first: without the tracker, you'll duplicate work already done or contradict decisions made in prior sessions. The tracker is the single source of truth across sessions.\n\n### Phase 2 \u2014 Orient (Session 1 Only)\n\n#### 2a. Create the tracker immediately\n\nWrite a skeleton tracker as your first write operation, before doing any domain work. Even a minimal tracker with just the goal and \"orientation in progress\" provides continuity if the session is interrupted during setup.\n\nThe tracker must always answer four questions for any future session:\n\n1. What are we trying to accomplish?\n2. What has been completed so far?\n3. What work is left?\n4. What should the next session do first?\n\nThese answers are the contract between sessions. The exact section headings may vary by workflow, but the tracker must make all four answers explicit and easy to find. A future session reading this tracker has no other context \u2014 if something isn't here, it doesn't exist.\n\n#### 2b. Workflow-specific orientation\n\nExecute the orientation steps defined by the workflow. These vary by domain \u2014 a test-writing workflow explores the product in a browser; a migration workflow audits the database schema. The workflow defines what orientation means.\n\n#### 2c. Create a task plan\n\nRefine the skeleton tracker into granular, verifiable checkpoints based on what orientation revealed. Each task should be a concrete unit of progress, not a vague phase. Include verification steps (running checks, reviewing output), not just implementation. Vague tasks like \"write tests\" can't be meaningfully resumed by a future session that has no idea what \"write tests\" means in this context.\n\n#### 2d. Update the tracker\n\nAfter orientation, ensure the tracker captures: the goal, what was discovered, what's planned, and what the next session should do first. Record concrete observations \u2014 what you actually saw, not what you assumed. Assumptions that turn out wrong waste entire future sessions on rework.\n\n### Phase 3 \u2014 Execute\n\nWork through tasks, advancing the plan step by step.\n\n#### Load skills before acting\n\nIf the workflow defines a skill table, load the relevant skill before each activity. Skills carry implementation details \u2014 scaffolding steps, authentication strategies, locator rules, anti-patterns, code templates \u2014 that would otherwise be lost between sessions. This prompt defines the protocol; skills define how to execute each step.\n\n#### Follow the workflow's sequence\n\nExecute in the order the workflow prescribes. Not every session covers all steps \u2014 pick up where the tracker says rather than restarting the flow.\n\n#### Delegate heavy work\n\nUse subagents via the Task tool to offload heavy exploration or generation, preserving your main context for orchestration. Pass file paths, conventions, and concrete output expectations. Instruct subagents to load the appropriate skill.\n\nRespect the workflow's **delegation constraints** \u2014 some operations must run in the main agent because their output serves as proof or because the main agent needs to interpret results in context.\n\n#### Execute quality gates\n\nIf the workflow defines quality gates, execute them in order. Do not skip gates \u2014 they exist because prior experience showed that skipping them leads to cascading rework. If a gate returns a failing verdict, address the issues and re-run the gate before proceeding.\n\nRespect the workflow's **retry limits** for failing steps. Repeated failures usually signal a deeper issue that another retry won't fix.\n\n#### Update the tracker as you work\n\nTreat tracker updates as defensive checkpoints against three failure modes: the runner killing your session, your context collapsing under tool-output load, and you simply forgetting an hour from now what you just learned. The right cadence sits between \"every tool call\" (noisy, wastes tokens, turns the tracker into a log) and \"at the end of the session\" (everything is lost if you die mid-task).\n\nUpdate the tracker whenever any of the following happens \u2014 these are the checkpoints, not \"felt like a good moment\":\n\n- **You finished a discrete unit of work.** A file written, a fix applied, a test run, a quality gate passed. The tracker should reflect the new reality before you start the next unit, not after several units have piled up.\n- **You learned something a future session can't cheaply rederive.** An API quirk, a config field that turned out to matter, a dead end you've now ruled out, a decision between two approaches. Insights are tracker-worthy even when no code changed \u2014 losing them costs the next session a full re-exploration. The tracker is your knowledge ledger, not just a task log.\n- **You're about to do something risky or long-running.** Dispatching a subagent, kicking off a long build, calling a flaky external service, starting a large refactor. If that operation kills your session, only what's already in the tracker survives. Write first, then act.\n- **Your plan changed.** A task got resequenced, a new task surfaced, a planned task turned out to be unnecessary. Stale plans poison continuation sessions \u2014 the tracker must reflect what you'll actually do next, not what you thought five steps ago.\n- **You've been working a while without writing.** If you can't remember when you last touched the tracker, you've gone too long. A short defensive update (\"currently doing X, last completed Y, next is Z\") beats nothing.\n\nWhat an update contains depends on the trigger, but always cover: what changed (work or knowledge), what's now next, and any caveat the next session needs to know. Avoid transcribing tool calls \u2014 the tracker is a contract with your future self, not a replay log.\n\nThe cost of one extra tracker update is a few tokens. The cost of dying without one is a whole wasted session rediscovering what you already knew. Bias toward writing.\n\n#### Task visibility\n\nThe tracker contains the authoritative task plan \u2014 it persists across sessions. Your harness's task UI is only a live projection of that plan, visible to the user in their CLI widget. It is session-scoped and does not survive process exit.\n\n**The relationship:** tracker is the source of truth, task tools are the display.\n\n{{TASK_TOOL_INSTRUCTIONS}}\n\n- **Session 1 (Orient):** After creating the task plan in the tracker, project each task into the task management tools so the user can see progress in real time.\n- **Session 2+ (Resume):** After reading the tracker, recreate the task projection from the tracker's plan. Set statuses to match what the tracker says is done, remaining, and next. The user sees consistent progress across sessions.\n- **During work:** Update both \u2014 the task tools for immediate UI feedback, the tracker for persistence. When a task completes, mark it done in the task tools and record it in the tracker in the same working phase.\n\nThis gives the user a consistent view of progress in their CLI regardless of which session they're in, while the tracker remains the durable contract between sessions.\n\n### Phase 4 \u2014 End of Session\n\n1. Ensure the tracker reflects all progress, discoveries, and blockers.\n2. Write clear instructions for what the next session should do first.\n3. If all work is complete and verified: write `<!-- WORKFLOW_COMPLETE -->` at the end of the tracker.\n4. If an unrecoverable blocker prevents progress: write `<!-- WORKFLOW_BLOCKED -->` or `<!-- WORKFLOW_BLOCKED: reason -->` at the end of the tracker.\n\nDo not write terminal markers prematurely. The runner trusts markers unconditionally \u2014 a premature marker kills the loop before work is done, and there's no automatic recovery.\n\n## Session Bounding\n\nEach fresh session starts with a clean context window and a compact tracker \u2014 effectively a self-compaction. As you work, your context fills with tool outputs, exploration results, and intermediate state. The longer you run, the more attention is spread across tokens that are no longer relevant, degrading your precision on the work that matters now.\n\nWork on a bounded chunk per session. Ending early and letting the next session pick up from a clean tracker is almost always better than pushing through with a heavy context.\n\nHeuristics for when to checkpoint and end:\n\n- After completing a quality gate \u2014 natural boundary\n- After crossing multiple phases (e.g., explored + planned + wrote specs) \u2014 stop before pushing into the next\n- When you notice your context is heavy with tool outputs from earlier work\n\n## Guardrails\n\nQuick-reference checklist \u2014 each of these is explained in detail above:\n\n- Read the tracker before doing anything else\n- Replace the skeleton immediately \u2014 even for simple requests. Write minimal tracker content (what was asked, what was done, outcome) and then the terminal marker before the session ends.\n- Update the tracker on concrete triggers (unit of work done, insight learned, risky operation pending, plan changed) \u2014 not on a vague sense of \"meaningful progress\"\n- Project the tracker's task plan into task management tools at session start\n- Update both task tools and tracker as milestones complete\n- Load the relevant skill before each activity\n- Do not write the completion marker until all work is verified\n- Respect the workflow's delegation constraints and retry limits\n";
2066
+
2067
+ // src/core/workflows/stateMachine.ts
2068
+ function buildTaskToolInstructions(harness) {
2069
+ switch (harness) {
2070
+ case "openai-codex":
2071
+ return `Use the \`update_plan\` tool to create and maintain the task list shown to the user for the full harness session.
2072
+
2073
+ - As soon as orientation yields a credible plan, create a detailed task list from the tracker; each item should be a concrete, verifiable unit of work, not a vague phase
2074
+ - At the start of every fresh harness session, recreate the full task list from the tracker before resuming execution
2075
+ - Do not carry forward prior session task IDs or assume prior session plan items still exist in the tool
2076
+ - Keep the task list accurate throughout the session: when scope, ordering, status, or completion changes, update \`update_plan\` immediately in the same working phase as the tracker
2077
+ - Keep task state consistent and non-stale: reconcile the task list against the tracker before moving on, and before ending the session
2078
+ - Maintain exactly one \`in_progress\` task unless the tracker explicitly records parallel active work`;
2079
+ case "claude-code":
2080
+ case "opencode":
2081
+ default:
2082
+ return `Use \`TaskCreate\` and \`TaskUpdate\` to create and maintain the task list shown to the user for the full harness session.
2083
+
2084
+ - As soon as orientation yields a credible plan, create a detailed task list from the tracker; each item should be a concrete, verifiable unit of work, not a vague phase
2085
+ - At the start of every fresh harness session, recreate the full task list from the tracker instead of trying to resume prior session task IDs
2086
+ - Do not refer to task IDs created in earlier sessions; they are session-scoped UI artifacts, not durable workflow state
2087
+ - Keep the task list accurate throughout the session: when scope, ordering, status, or completion changes, update the task list in the same working phase as the tracker
2088
+ - Keep task state consistent and non-stale: reconcile the task list against the tracker before moving on, and before ending the session
2089
+ - Maintain exactly one active task unless the tracker explicitly records parallel active work`;
2090
+ }
2091
+ }
2092
+ function buildStateMachineContent(harness = "claude-code") {
2093
+ return stateMachine_default.replace(
2094
+ "{{TASK_TOOL_INSTRUCTIONS}}",
2095
+ buildTaskToolInstructions(harness)
2096
+ );
2097
+ }
2098
+ var STATE_MACHINE_CONTENT = buildStateMachineContent();
2099
+
2100
+ // src/core/workflows/sessionPlan.ts
2101
+ function readWorkflowOverride(projectDir, workflow, sessionId, trackerPath, harness = "claude-code") {
2102
+ if (!workflow?.workflowFile) {
2103
+ return { workflowOverride: void 0, warnings: [] };
2104
+ }
2105
+ const resolvedPath = path8.isAbsolute(workflow.workflowFile) ? workflow.workflowFile : path8.resolve(projectDir, workflow.workflowFile);
2106
+ let workflowContent;
2107
+ try {
2108
+ workflowContent = fs10.readFileSync(resolvedPath, "utf-8");
2109
+ } catch {
2110
+ return {
2111
+ workflowOverride: void 0,
2112
+ warnings: [
2113
+ `Workflow file not found: ${workflow.workflowFile}. Continuing without workflow system instructions.`
2114
+ ]
2115
+ };
2116
+ }
2117
+ let composed = workflow.loop?.enabled ? buildStateMachineContent(harness) + "\n\n" + workflowContent : workflowContent;
2118
+ composed = substituteVariables(composed, {
2119
+ sessionId,
2120
+ trackerPath: trackerPath ?? void 0
2121
+ });
2122
+ const workflowDir = path8.dirname(resolvedPath);
2123
+ const composedPath = path8.join(workflowDir, ".composed-system-prompt.md");
2124
+ fs10.writeFileSync(composedPath, composed, "utf-8");
2125
+ return {
2126
+ workflowOverride: {
2127
+ appendSystemPromptFile: composedPath,
2128
+ developerInstructions: composed
2129
+ },
2130
+ warnings: []
2131
+ };
2132
+ }
2133
+ function mergeOverrides(base, workflowOverride) {
2134
+ if (!base) return workflowOverride;
2135
+ if (!workflowOverride) return base;
2136
+ return {
2137
+ ...base,
2138
+ ...workflowOverride
2139
+ };
2140
+ }
2141
+ function resolveTrackerPath(input) {
2142
+ const loop = input.workflow?.loop;
2143
+ if (!loop?.enabled) {
2144
+ return null;
2145
+ }
2146
+ const rawPath = loop.trackerPath ?? DEFAULT_TRACKER_PATH;
2147
+ if (!input.sessionId && rawPath.includes("{sessionId}")) {
2148
+ return null;
2149
+ }
2150
+ const promptPath = input.sessionId ? rawPath.replaceAll("{sessionId}", input.sessionId) : rawPath;
2151
+ const absolutePath = path8.isAbsolute(promptPath) ? promptPath : path8.resolve(input.projectDir, promptPath);
2152
+ return {
2153
+ absolutePath,
2154
+ promptPath
2155
+ };
2156
+ }
2157
+ function createWorkflowRunState(input) {
2158
+ const { projectDir, sessionId, workflow, harness } = input;
2159
+ const trackerResolved = resolveTrackerPath({ projectDir, sessionId, workflow });
2160
+ const loopManager = workflow?.loop?.enabled === true && trackerResolved ? createLoopManager(trackerResolved.absolutePath, workflow.loop) : null;
2161
+ const { workflowOverride, warnings } = readWorkflowOverride(
2162
+ projectDir,
2163
+ workflow,
2164
+ sessionId,
2165
+ trackerResolved?.promptPath,
2166
+ harness
2167
+ );
2168
+ return {
2169
+ workflow,
2170
+ loopManager,
2171
+ trackerPathForPrompt: trackerResolved?.promptPath,
2172
+ workflowOverride,
2173
+ warnings
2174
+ };
2175
+ }
2176
+ function prepareWorkflowTurn(state, input) {
2177
+ const { workflow, loopManager } = state;
2178
+ const prompt = workflow && loopManager && loopManager.getState().iteration > 0 ? buildContinuePrompt({
2179
+ ...workflow.loop,
2180
+ trackerPath: state.trackerPathForPrompt ?? workflow.loop?.trackerPath
2181
+ }) : workflow ? applyPromptTemplate(workflow.promptTemplate, input.prompt) : input.prompt;
2182
+ return {
2183
+ prompt,
2184
+ configOverride: mergeOverrides(
2185
+ input.configOverride,
2186
+ state.workflowOverride
2187
+ ),
2188
+ warnings: state.warnings
2189
+ };
2190
+ }
2191
+ function shouldContinueWorkflowRun(state) {
2192
+ const { workflow, loopManager } = state;
2193
+ if (!workflow?.loop?.enabled || !loopManager) {
2194
+ return null;
2195
+ }
2196
+ const loopState = loopManager.getState();
2197
+ if (!fs10.existsSync(loopManager.trackerPath)) {
2198
+ cleanupWorkflowRun(state);
2199
+ return {
2200
+ reason: "missing_tracker",
2201
+ maxIterations: loopState.maxIterations
2202
+ };
2203
+ }
2204
+ if (loopState.skeletonNotReplaced) {
2205
+ cleanupWorkflowRun(state);
2206
+ return {
2207
+ reason: "skeleton_not_replaced",
2208
+ maxIterations: loopState.maxIterations
2209
+ };
2210
+ }
2211
+ let reason;
2212
+ if (loopState.completed) {
2213
+ reason = "completed";
2214
+ } else if (loopState.blocked) {
2215
+ reason = "blocked";
2216
+ } else if (loopState.iteration + 1 >= loopState.maxIterations) {
2217
+ reason = "max_iterations";
2218
+ }
2219
+ if (reason) {
2220
+ cleanupWorkflowRun(state);
2221
+ return {
2222
+ reason,
2223
+ blockedReason: loopState.blockedReason,
2224
+ maxIterations: loopState.maxIterations
2225
+ };
2226
+ }
2227
+ loopManager.incrementIteration();
2228
+ return null;
2229
+ }
2230
+ function cleanupWorkflowRun(state) {
2231
+ if (!state.loopManager) {
2232
+ return;
2233
+ }
2234
+ state.loopManager.deactivate();
2235
+ state.loopManager = null;
2236
+ }
2237
+
2238
+ // src/core/workflows/useWorkflowSessionController.ts
2239
+ import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
2240
+
2241
+ // src/core/workflows/workflowRunner.ts
2242
+ import crypto from "crypto";
2243
+ import fs11 from "fs";
2244
+ import path9 from "path";
2245
+ var NULL_TOKENS = {
2246
+ input: null,
2247
+ output: null,
2248
+ cacheRead: null,
2249
+ cacheWrite: null,
2250
+ total: null,
2251
+ contextSize: null,
2252
+ contextWindowSize: null
2253
+ };
2254
+ var TRACKER_SKELETON_TEMPLATE = `${TRACKER_SKELETON_MARKER}
2255
+ # Workflow Tracker
2256
+
2257
+ **Session**: {sessionId}
2258
+ **Tracker**: {trackerPath}
2259
+ **Goal**: {input}
2260
+
2261
+ ---
2262
+
2263
+ > This tracker was created by the runner. Update it as you work.
2264
+ > See the Stateless Session Protocol for tracker conventions.
2265
+
2266
+ ## Status
2267
+
2268
+ Orientation in progress.
2269
+
2270
+ ## Plan
2271
+
2272
+ _To be created during orientation._
2273
+
2274
+ ## Progress
2275
+
2276
+ _No progress yet._
2277
+ `;
2278
+ function mergeTokens(base, next) {
2279
+ const input = (base.input ?? 0) + (next.input ?? 0);
2280
+ const output = (base.output ?? 0) + (next.output ?? 0);
2281
+ const cacheRead = (base.cacheRead ?? 0) + (next.cacheRead ?? 0);
2282
+ const cacheWrite = (base.cacheWrite ?? 0) + (next.cacheWrite ?? 0);
2283
+ const hasAny = base.input !== null || next.input !== null || base.output !== null || next.output !== null || base.cacheRead !== null || next.cacheRead !== null || base.cacheWrite !== null || next.cacheWrite !== null;
2284
+ if (!hasAny)
2285
+ return {
2286
+ ...NULL_TOKENS,
2287
+ contextSize: next.contextSize,
2288
+ contextWindowSize: next.contextWindowSize
2289
+ };
2290
+ return {
2291
+ input,
2292
+ output,
2293
+ cacheRead,
2294
+ cacheWrite,
2295
+ total: input + output + cacheRead + cacheWrite,
2296
+ contextSize: next.contextSize ?? base.contextSize,
2297
+ contextWindowSize: next.contextWindowSize ?? base.contextWindowSize
2298
+ };
2299
+ }
2300
+ function defaultCreateTracker(trackerPath, content) {
2301
+ fs11.mkdirSync(path9.dirname(trackerPath), { recursive: true });
2302
+ try {
2303
+ fs11.writeFileSync(trackerPath, content, { encoding: "utf-8", flag: "wx" });
2304
+ } catch (e) {
2305
+ if (e.code !== "EEXIST") throw e;
2306
+ }
2307
+ }
2308
+ function createWorkflowRunner(input) {
2309
+ const runId = crypto.randomUUID();
2310
+ let cancelled = false;
2311
+ let status = "running";
2312
+ let iterations = 0;
2313
+ let cumulativeTokens = { ...NULL_TOKENS };
2314
+ let stopReason;
2315
+ const trackerResolved = resolveTrackerPath({
2316
+ projectDir: input.projectDir,
2317
+ sessionId: input.sessionId,
2318
+ workflow: input.workflow
2319
+ });
2320
+ const trackerAbsPath = trackerResolved?.absolutePath ?? null;
2321
+ const trackerPromptPath = trackerResolved?.promptPath;
2322
+ function snapshot() {
2323
+ return {
2324
+ runId,
2325
+ sessionId: input.sessionId,
2326
+ workflowName: input.workflow?.name,
2327
+ iteration: iterations,
2328
+ maxIterations: input.workflow?.loop?.maxIterations ?? 1,
2329
+ status,
2330
+ stopReason,
2331
+ trackerPath: trackerPromptPath
2332
+ };
2333
+ }
2334
+ function persist() {
2335
+ try {
2336
+ input.persistRunState(snapshot());
2337
+ } catch {
2338
+ }
2339
+ }
2340
+ const result = (async () => {
2341
+ await Promise.resolve();
2342
+ if (trackerAbsPath && input.workflow?.loop?.enabled) {
2343
+ const content = substituteVariables(TRACKER_SKELETON_TEMPLATE, {
2344
+ sessionId: input.sessionId,
2345
+ trackerPath: trackerPromptPath,
2346
+ input: input.prompt
2347
+ });
2348
+ const write = input.createTracker ?? defaultCreateTracker;
2349
+ write(trackerAbsPath, content);
2350
+ }
2351
+ persist();
2352
+ const workflowState = createWorkflowRunState({
2353
+ projectDir: input.projectDir,
2354
+ sessionId: input.sessionId,
2355
+ workflow: input.workflow,
2356
+ harness: input.harness
2357
+ });
2358
+ let nextContinuation = input.initialContinuation ?? {
2359
+ mode: "fresh"
2360
+ };
2361
+ try {
2362
+ while (!cancelled) {
2363
+ iterations++;
2364
+ const prepared = prepareWorkflowTurn(workflowState, {
2365
+ prompt: input.prompt,
2366
+ configOverride: void 0
2367
+ });
2368
+ const turnResult = await input.startTurn({
2369
+ prompt: prepared.prompt,
2370
+ continuation: nextContinuation,
2371
+ configOverride: prepared.configOverride
2372
+ });
2373
+ if (cancelled) {
2374
+ status = "cancelled";
2375
+ persist();
2376
+ break;
2377
+ }
2378
+ cumulativeTokens = mergeTokens(cumulativeTokens, turnResult.tokens);
2379
+ if (turnResult.error || turnResult.exitCode !== null && turnResult.exitCode !== 0) {
2380
+ status = "failed";
2381
+ const parts = [];
2382
+ if (turnResult.error?.message) {
2383
+ parts.push(turnResult.error.message);
2384
+ } else if (turnResult.exitCode !== null) {
2385
+ parts.push(`Process exited with code ${turnResult.exitCode}`);
2386
+ }
2387
+ if (turnResult.lastStderr) {
2388
+ parts.push(turnResult.lastStderr);
2389
+ }
2390
+ stopReason = parts.join(": ") || "Turn failed";
2391
+ persist();
2392
+ break;
2393
+ }
2394
+ const transport = turnResult.diagnostics?.transport;
2395
+ if (transport && transport.streamToolUses > 0 && transport.preToolUseEvents === 0) {
2396
+ status = "failed";
2397
+ stopReason = `Hook transport broken: observed ${transport.streamToolUses} tool use(s) in Claude stream but received no PreToolUse events.`;
2398
+ persist();
2399
+ break;
2400
+ }
2401
+ if (!input.workflow?.loop?.enabled) {
2402
+ status = "completed";
2403
+ persist();
2404
+ break;
2405
+ }
2406
+ const loopStop = shouldContinueWorkflowRun(workflowState);
2407
+ if (loopStop) {
2408
+ if (loopStop.reason === "completed") {
2409
+ status = "completed";
2410
+ } else if (loopStop.reason === "blocked") {
2411
+ status = "blocked";
2412
+ stopReason = loopStop.blockedReason;
2413
+ } else if (loopStop.reason === "max_iterations") {
2414
+ status = "exhausted";
2415
+ } else if (loopStop.reason === "skeleton_not_replaced") {
2416
+ status = "failed";
2417
+ stopReason = "Agent did not replace the tracker skeleton. The session completed without engaging with the workflow tracker.";
2418
+ } else {
2419
+ status = "failed";
2420
+ stopReason = `Loop stopped: ${loopStop.reason}`;
2421
+ }
2422
+ persist();
2423
+ break;
2424
+ }
2425
+ persist();
2426
+ input.onIterationComplete?.(snapshot());
2427
+ nextContinuation = { mode: "fresh" };
2428
+ }
2429
+ if (cancelled && status === "running") {
2430
+ status = "cancelled";
2431
+ persist();
2432
+ }
2433
+ } finally {
2434
+ cleanupWorkflowRun(workflowState);
2435
+ }
2436
+ return {
2437
+ runId,
2438
+ status,
2439
+ iterations,
2440
+ stopReason,
2441
+ tokens: cumulativeTokens
2442
+ };
2443
+ })();
2444
+ return {
2445
+ runId,
2446
+ result,
2447
+ cancel() {
2448
+ cancelled = true;
2449
+ },
2450
+ kill() {
2451
+ cancelled = true;
2452
+ input.abortCurrentTurn?.();
2453
+ }
2454
+ };
2455
+ }
2456
+
2457
+ // src/core/workflows/useWorkflowSessionController.ts
2458
+ function useWorkflowSessionController(base, input) {
2459
+ const [isRunning, setIsRunning] = useState3(false);
2460
+ const runnerRef = useRef3(null);
2461
+ const activeRunIdRef = useRef3(null);
2462
+ const cancelCurrentRun = useCallback2(async () => {
2463
+ const runner = runnerRef.current;
2464
+ if (runner) {
2465
+ runner.kill();
2466
+ await runner.result.catch(() => {
2467
+ });
2468
+ runnerRef.current = null;
2469
+ activeRunIdRef.current = null;
2470
+ }
2471
+ }, []);
2472
+ const interrupt = useCallback2(() => {
2473
+ const runner = runnerRef.current;
2474
+ if (runner) {
2475
+ runner.kill();
2476
+ runnerRef.current = null;
2477
+ activeRunIdRef.current = null;
2478
+ } else {
2479
+ void base.kill().catch(() => {
2480
+ });
2481
+ }
2482
+ setIsRunning(false);
2483
+ }, [base]);
2484
+ const kill = useCallback2(async () => {
2485
+ if (runnerRef.current) {
2486
+ await cancelCurrentRun();
2487
+ } else {
2488
+ await base.kill();
2489
+ }
2490
+ setIsRunning(false);
2491
+ }, [base, cancelCurrentRun]);
2492
+ const spawn = useCallback2(
2493
+ async (prompt, continuation, _configOverride) => {
2494
+ await cancelCurrentRun();
2495
+ setIsRunning(true);
2496
+ const handle = createWorkflowRunner({
2497
+ sessionId: input.sessionId ?? "",
2498
+ projectDir: input.projectDir,
2499
+ harness: input.harness,
2500
+ workflow: input.workflow,
2501
+ prompt,
2502
+ initialContinuation: continuation,
2503
+ startTurn: (turnInput) => base.startTurn(
2504
+ turnInput.prompt,
2505
+ turnInput.continuation,
2506
+ turnInput.configOverride
2507
+ ),
2508
+ persistRunState: input.persistRunState ?? (() => {
2509
+ }),
2510
+ abortCurrentTurn: () => void base.kill().catch(() => {
2511
+ })
2512
+ });
2513
+ runnerRef.current = handle;
2514
+ activeRunIdRef.current = handle.runId;
2515
+ try {
2516
+ const runResult = await handle.result;
2517
+ return {
2518
+ exitCode: runResult.status === "failed" ? 1 : 0,
2519
+ error: runResult.status === "failed" ? new Error(runResult.stopReason ?? "Run failed") : null,
2520
+ tokens: runResult.tokens,
2521
+ streamMessage: null
2522
+ };
2523
+ } finally {
2524
+ if (runnerRef.current === handle) {
2525
+ runnerRef.current = null;
2526
+ activeRunIdRef.current = null;
2527
+ setIsRunning(false);
2528
+ }
2529
+ }
2530
+ },
2531
+ [
2532
+ base,
2533
+ cancelCurrentRun,
2534
+ input.projectDir,
2535
+ input.sessionId,
2536
+ input.harness,
2537
+ input.workflow,
2538
+ input.persistRunState
2539
+ ]
2540
+ );
2541
+ useEffect3(() => {
2542
+ return () => {
2543
+ runnerRef.current?.kill();
2544
+ runnerRef.current = null;
2545
+ activeRunIdRef.current = null;
2546
+ };
2547
+ }, []);
2548
+ return {
2549
+ ...base,
2550
+ startTurn: spawn,
2551
+ isRunning,
2552
+ interrupt,
2553
+ kill,
2554
+ get activeRunId() {
2555
+ return activeRunIdRef.current;
2556
+ }
2557
+ };
2558
+ }
2559
+
2560
+ // src/infra/plugins/mcpOptions.ts
2561
+ import fs12 from "fs";
2562
+ import path10 from "path";
2563
+ function collectMcpServersWithOptions(pluginDirs) {
2564
+ const result = [];
2565
+ const seen = /* @__PURE__ */ new Set();
2566
+ for (const dir of pluginDirs) {
2567
+ const mcpPath = path10.join(dir, ".mcp.json");
2568
+ if (!fs12.existsSync(mcpPath)) {
2569
+ continue;
2570
+ }
2571
+ const config = JSON.parse(fs12.readFileSync(mcpPath, "utf-8"));
2572
+ for (const [serverName, serverConfig] of Object.entries(
2573
+ config.mcpServers ?? {}
2574
+ )) {
2575
+ if (seen.has(serverName) || !Array.isArray(serverConfig.options) || serverConfig.options.length === 0) {
2576
+ continue;
2577
+ }
2578
+ seen.add(serverName);
2579
+ result.push({ serverName, options: serverConfig.options });
2580
+ }
2581
+ }
2582
+ return result;
2583
+ }
2584
+
2585
+ export {
2586
+ darkTheme,
2587
+ resolveTheme,
2588
+ ThemeProvider,
2589
+ useTheme,
2590
+ createWorkflowRunner,
2591
+ useWorkflowSessionController,
2592
+ isMarketplaceRef,
2593
+ findMarketplaceRepoDir,
2594
+ resolveWorkflowMarketplaceSource,
2595
+ listMarketplaceWorkflows,
2596
+ listMarketplaceWorkflowsFromRepo,
2597
+ resolveMarketplaceWorkflow,
2598
+ gatherMarketplaceWorkflowSources,
2599
+ resolveWorkflowInstall,
2600
+ projectConfigPath,
2601
+ readConfig,
2602
+ resolveActiveWorkflow,
2603
+ readGlobalConfig,
2604
+ writeGlobalConfig,
2605
+ writeProjectConfig,
2606
+ hasProjectWorkflow,
2607
+ StepSelector,
2608
+ StepStatus,
2609
+ McpOptionsStep,
2610
+ installWorkflowPlugins,
2611
+ resolveWorkflowPlugins,
2612
+ listBuiltinWorkflows,
2613
+ resolveWorkflow,
2614
+ installWorkflowFromSource,
2615
+ updateWorkflow,
2616
+ listWorkflows,
2617
+ removeWorkflow,
2618
+ compileWorkflowPlan,
2619
+ collectMcpServersWithOptions
2620
+ };
2621
+ //# sourceMappingURL=chunk-LPG5WBPV.js.map