@bastani/atomic 0.5.0-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +956 -0
  3. package/assets/settings.schema.json +52 -0
  4. package/package.json +68 -0
  5. package/src/cli.ts +197 -0
  6. package/src/commands/cli/chat/client.ts +18 -0
  7. package/src/commands/cli/chat/index.ts +247 -0
  8. package/src/commands/cli/chat.ts +8 -0
  9. package/src/commands/cli/config.ts +55 -0
  10. package/src/commands/cli/init/index.ts +452 -0
  11. package/src/commands/cli/init/onboarding.ts +45 -0
  12. package/src/commands/cli/init/scm.ts +190 -0
  13. package/src/commands/cli/init.ts +8 -0
  14. package/src/commands/cli/update.ts +46 -0
  15. package/src/commands/cli/workflow.ts +164 -0
  16. package/src/lib/merge.ts +65 -0
  17. package/src/lib/path-root-guard.ts +38 -0
  18. package/src/lib/spawn.ts +467 -0
  19. package/src/scripts/bump-version.ts +94 -0
  20. package/src/scripts/constants-base.ts +14 -0
  21. package/src/scripts/constants.ts +34 -0
  22. package/src/sdk/components/color-utils.ts +20 -0
  23. package/src/sdk/components/connectors.test.ts +661 -0
  24. package/src/sdk/components/connectors.ts +156 -0
  25. package/src/sdk/components/edge.tsx +11 -0
  26. package/src/sdk/components/error-boundary.tsx +38 -0
  27. package/src/sdk/components/graph-theme.ts +36 -0
  28. package/src/sdk/components/header.tsx +60 -0
  29. package/src/sdk/components/layout.test.ts +924 -0
  30. package/src/sdk/components/layout.ts +186 -0
  31. package/src/sdk/components/node-card.tsx +68 -0
  32. package/src/sdk/components/orchestrator-panel-contexts.ts +26 -0
  33. package/src/sdk/components/orchestrator-panel-store.test.ts +561 -0
  34. package/src/sdk/components/orchestrator-panel-store.ts +118 -0
  35. package/src/sdk/components/orchestrator-panel-types.ts +21 -0
  36. package/src/sdk/components/orchestrator-panel.tsx +143 -0
  37. package/src/sdk/components/session-graph-panel.tsx +364 -0
  38. package/src/sdk/components/status-helpers.ts +32 -0
  39. package/src/sdk/components/statusline.tsx +63 -0
  40. package/src/sdk/define-workflow.ts +98 -0
  41. package/src/sdk/errors.ts +39 -0
  42. package/src/sdk/index.ts +38 -0
  43. package/src/sdk/providers/claude.ts +316 -0
  44. package/src/sdk/providers/copilot.ts +43 -0
  45. package/src/sdk/providers/opencode.ts +43 -0
  46. package/src/sdk/runtime/discovery.ts +172 -0
  47. package/src/sdk/runtime/executor.test.ts +415 -0
  48. package/src/sdk/runtime/executor.ts +695 -0
  49. package/src/sdk/runtime/loader.ts +372 -0
  50. package/src/sdk/runtime/panel.tsx +9 -0
  51. package/src/sdk/runtime/theme.ts +76 -0
  52. package/src/sdk/runtime/tmux.ts +542 -0
  53. package/src/sdk/types.ts +114 -0
  54. package/src/sdk/workflows.ts +85 -0
  55. package/src/services/config/atomic-config.ts +124 -0
  56. package/src/services/config/atomic-global-config.ts +361 -0
  57. package/src/services/config/config-path.ts +19 -0
  58. package/src/services/config/definitions.ts +176 -0
  59. package/src/services/config/index.ts +7 -0
  60. package/src/services/config/settings-schema.ts +2 -0
  61. package/src/services/config/settings.ts +149 -0
  62. package/src/services/system/copy.ts +381 -0
  63. package/src/services/system/detect.ts +161 -0
  64. package/src/services/system/download.ts +325 -0
  65. package/src/services/system/file-lock.ts +289 -0
  66. package/src/services/system/skills.ts +67 -0
  67. package/src/theme/colors.ts +25 -0
  68. package/src/version.ts +7 -0
@@ -0,0 +1,372 @@
1
+ /**
2
+ * Workflow Loader — multi-stage pipeline for resolving and loading workflows.
3
+ *
4
+ * Pipeline: Discover → Resolve → Validate → Load
5
+ *
6
+ * Each stage returns a typed discriminated result so callers get structured
7
+ * error information without try/catch guesswork.
8
+ *
9
+ * Discovery (finding workflow files on disk) remains in `discovery.ts`.
10
+ * This module handles everything after a workflow is discovered.
11
+ */
12
+
13
+ import { join, dirname } from "path";
14
+ import { existsSync } from "fs";
15
+ import type { WorkflowDefinition, AgentType } from "../types.ts";
16
+ import type { DiscoveredWorkflow } from "./discovery.ts";
17
+ import { validateCopilotWorkflow } from "../providers/copilot.ts";
18
+ import { validateOpenCodeWorkflow } from "../providers/opencode.ts";
19
+ import { validateClaudeWorkflow } from "../providers/claude.ts";
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // SDK module resolver
23
+ // ---------------------------------------------------------------------------
24
+
25
+ // Absolute path to the currently-running atomic CLI's own SDK source tree
26
+ // (i.e. `<install_root>/src/sdk`). Computed from this file's URL so it always
27
+ // points at the actual installed atomic, regardless of how it was launched
28
+ // (dev checkout, global `bun install -g atomic`, `bunx atomic`, etc).
29
+ const ATOMIC_SDK_DIR = Bun.fileURLToPath(new URL("..", import.meta.url));
30
+
31
+ // Directory of this loader file. Used as the parent for `Bun.resolveSync`
32
+ // when delegating non-`atomic/*` bare specifiers, so workflows resolve them
33
+ // against atomic's own `node_modules` tree.
34
+ const LOADER_DIR = Bun.fileURLToPath(new URL(".", import.meta.url));
35
+
36
+ // Common TypeScript/JavaScript extensions tried for `atomic/<subpath>`
37
+ // imports when the specifier doesn't already include one.
38
+ const ATOMIC_SUBPATH_EXTS = [".ts", ".tsx", ".js", ".jsx"];
39
+
40
+ // Workflow roots for which a Bun.plugin onLoad hook has already been
41
+ // registered. Deduped so repeated `load()` calls don't stack plugins.
42
+ const registeredRoots = new Set<string>();
43
+
44
+ function escapeRegex(s: string): string {
45
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
46
+ }
47
+
48
+ /**
49
+ * Resolve `atomic/<subpath>` (or bare `atomic`) to an absolute file path in
50
+ * the running CLI's `src/sdk/` tree. Returns `null` if no matching file
51
+ * exists so the caller can fall back to the original specifier.
52
+ */
53
+ function resolveAtomicSubpath(subpath: string): string | null {
54
+ const direct = join(ATOMIC_SDK_DIR, subpath);
55
+ if (existsSync(direct)) return direct;
56
+ for (const ext of ATOMIC_SUBPATH_EXTS) {
57
+ const candidate = join(ATOMIC_SDK_DIR, subpath + ext);
58
+ if (existsSync(candidate)) return candidate;
59
+ }
60
+ return null;
61
+ }
62
+
63
+ /**
64
+ * Resolve a bare import specifier to an absolute file path. `atomic/<x>`
65
+ * maps onto the SDK source tree; everything else delegates to atomic's own
66
+ * module resolution context (so `@github/copilot-sdk`, `zod`, etc. resolve
67
+ * from atomic's installed `node_modules`).
68
+ */
69
+ const ATOMIC_PKG = "@bastani/atomic";
70
+ const ATOMIC_PKG_PREFIX = `${ATOMIC_PKG}/`;
71
+
72
+ function resolveBareSpecifier(spec: string): string | null {
73
+ if (spec === ATOMIC_PKG) return resolveAtomicSubpath("index");
74
+ if (spec.startsWith(ATOMIC_PKG_PREFIX)) {
75
+ return resolveAtomicSubpath(spec.slice(ATOMIC_PKG_PREFIX.length));
76
+ }
77
+ try {
78
+ return Bun.resolveSync(spec, LOADER_DIR);
79
+ } catch {
80
+ return null;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Rewrite bare `import`/`from` specifiers in workflow source to absolute
86
+ * file paths resolved from atomic's own context. Relative and absolute
87
+ * paths are left untouched. Unresolvable specifiers are also left alone so
88
+ * Bun produces its standard "module not found" error instead of a silent
89
+ * miss.
90
+ */
91
+ function rewriteBareImports(source: string): string {
92
+ // Matches: from "spec" | from 'spec' | import "spec" | import 'spec'
93
+ // The backreference (\2) ensures matched quotes are balanced.
94
+ const importRe = /(\bfrom\s*|\bimport\s*)(["'])([^"']+)\2/g;
95
+ return source.replace(importRe, (match, kw: string, quote: string, spec: string) => {
96
+ if (spec.startsWith(".") || spec.startsWith("/")) return match;
97
+ const resolved = resolveBareSpecifier(spec);
98
+ return resolved ? `${kw}${quote}${resolved}${quote}` : match;
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Register a Bun `onLoad` plugin that rewrites bare imports inside any TS
104
+ * file under the given workflow root. This lets workflow authors write
105
+ * `import { defineWorkflow } from "@bastani/atomic/workflows"` (and import
106
+ * atomic's transitive deps like `@github/copilot-sdk`) without maintaining
107
+ * their own `package.json` / `node_modules`.
108
+ *
109
+ * Why source rewriting via `onLoad` instead of `onResolve`?
110
+ * Bun's runtime plugin API honors `onLoad` but silently ignores `onResolve`
111
+ * hooks for dynamic `await import()` calls — `onResolve` only fires during
112
+ * `Bun.build`. Source rewriting is the only mechanism that actually changes
113
+ * how the runtime loader resolves imports inside workflow files today.
114
+ *
115
+ * Each distinct workflow root installs its own plugin (deduped via
116
+ * `registeredRoots`). The plugin's filter is a path-prefix regex so we
117
+ * don't incur the cost of reading every `.ts` file in the process — only
118
+ * files under active workflow roots are intercepted.
119
+ */
120
+ function installAtomicLoader(workflowRoot: string): void {
121
+ if (registeredRoots.has(workflowRoot)) return;
122
+ registeredRoots.add(workflowRoot);
123
+
124
+ // Match `<workflowRoot>/<...>/*.ts(x)`. Uses a character class for the
125
+ // separator so the same filter works on both POSIX and Windows paths.
126
+ const filter = new RegExp(
127
+ "^" + escapeRegex(workflowRoot) + "[/\\\\].*\\.tsx?$",
128
+ );
129
+
130
+ Bun.plugin({
131
+ name: `atomic-sdk-rewriter:${workflowRoot}`,
132
+ setup(build) {
133
+ build.onLoad({ filter }, async (args) => {
134
+ const source = await Bun.file(args.path).text();
135
+ const contents = rewriteBareImports(source);
136
+ const loader = args.path.endsWith("x") ? "tsx" : "ts";
137
+ return { contents, loader };
138
+ });
139
+ },
140
+ });
141
+ }
142
+
143
+ export namespace WorkflowLoader {
144
+ // ---------------------------------------------------------------------------
145
+ // Result types
146
+ // ---------------------------------------------------------------------------
147
+
148
+ /** Successful pipeline result. */
149
+ export type Ok<T> = { ok: true; value: T };
150
+
151
+ /** Failed pipeline result with stage and error context. */
152
+ export type StageError<S extends string> = {
153
+ ok: false;
154
+ stage: S;
155
+ error: unknown;
156
+ message: string;
157
+ };
158
+
159
+ export type StageResult<T, S extends string> = Ok<T> | StageError<S>;
160
+
161
+ // ---------------------------------------------------------------------------
162
+ // Stage data types
163
+ // ---------------------------------------------------------------------------
164
+
165
+ /** Input to the pipeline — a discovered workflow from disk. */
166
+ export type Plan = DiscoveredWorkflow;
167
+
168
+ /** Output of the resolve stage. */
169
+ export type Resolved = Plan;
170
+
171
+ /** A source validation warning (agent-specific). */
172
+ export interface ValidationWarning {
173
+ rule: string;
174
+ message: string;
175
+ }
176
+
177
+ /** Output of the validate stage. */
178
+ export type Validated = Resolved & {
179
+ warnings: ValidationWarning[];
180
+ };
181
+
182
+ /** Output of the load stage — the final result. */
183
+ export type Loaded = Validated & {
184
+ definition: WorkflowDefinition;
185
+ };
186
+
187
+ // ---------------------------------------------------------------------------
188
+ // Report callbacks — callers provide these for logging/UI
189
+ // ---------------------------------------------------------------------------
190
+
191
+ export interface Report {
192
+ /** Called when a stage begins. */
193
+ start?: (stage: "resolve" | "validate" | "load") => void;
194
+ /** Called when source validation produces warnings. */
195
+ warn?: (warnings: ValidationWarning[]) => void;
196
+ /** Called when a stage fails. */
197
+ error?: (stage: "resolve" | "validate" | "load", error: unknown, message: string) => void;
198
+ }
199
+
200
+ // ---------------------------------------------------------------------------
201
+ // Stage 1: Resolve
202
+ // ---------------------------------------------------------------------------
203
+
204
+ /** Verify the workflow file exists. */
205
+ export async function resolve(
206
+ plan: Plan,
207
+ ): Promise<StageResult<Resolved, "resolve">> {
208
+ try {
209
+ const file = Bun.file(plan.path);
210
+ if (!(await file.exists())) {
211
+ return {
212
+ ok: false,
213
+ stage: "resolve",
214
+ error: new Error(`Workflow file not found: ${plan.path}`),
215
+ message: `Workflow file not found: ${plan.path}`,
216
+ };
217
+ }
218
+ return { ok: true, value: plan };
219
+ } catch (error) {
220
+ return {
221
+ ok: false,
222
+ stage: "resolve",
223
+ error,
224
+ message: error instanceof Error ? error.message : String(error),
225
+ };
226
+ }
227
+ }
228
+
229
+ // ---------------------------------------------------------------------------
230
+ // Stage 2: Validate
231
+ // ---------------------------------------------------------------------------
232
+
233
+ /** Run agent-specific source validation. */
234
+ function validateSource(source: string, agent: AgentType): ValidationWarning[] {
235
+ switch (agent) {
236
+ case "copilot":
237
+ return validateCopilotWorkflow(source);
238
+ case "opencode":
239
+ return validateOpenCodeWorkflow(source);
240
+ case "claude":
241
+ return validateClaudeWorkflow(source);
242
+ default:
243
+ return [];
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Read the workflow source and run agent-specific validation checks.
249
+ * Validation warnings are non-fatal — the pipeline continues.
250
+ */
251
+ export async function validate(
252
+ resolved: Resolved,
253
+ ): Promise<StageResult<Validated, "validate">> {
254
+ try {
255
+ const source = await Bun.file(resolved.path).text();
256
+ const warnings = validateSource(source, resolved.agent);
257
+
258
+ return {
259
+ ok: true,
260
+ value: { ...resolved, warnings },
261
+ };
262
+ } catch (error) {
263
+ return {
264
+ ok: false,
265
+ stage: "validate",
266
+ error,
267
+ message: error instanceof Error ? error.message : String(error),
268
+ };
269
+ }
270
+ }
271
+
272
+ // ---------------------------------------------------------------------------
273
+ // Stage 3: Load
274
+ // ---------------------------------------------------------------------------
275
+
276
+ /**
277
+ * Import the workflow module and extract the WorkflowDefinition.
278
+ * Checks for common authoring mistakes (missing `.compile()`, wrong export).
279
+ */
280
+ export async function load(
281
+ validated: Validated,
282
+ ): Promise<StageResult<Loaded, "load">> {
283
+ try {
284
+ // The workflow root is two levels up from <root>/<agent>/index.ts,
285
+ // which covers agent dirs and any sibling `helpers/` folders.
286
+ installAtomicLoader(dirname(dirname(validated.path)));
287
+ const mod = await import(validated.path);
288
+ const definition = mod.default ?? mod;
289
+
290
+ if (!definition || definition.__brand !== "WorkflowDefinition") {
291
+ if (definition && definition.__brand === "WorkflowBuilder") {
292
+ return {
293
+ ok: false,
294
+ stage: "load",
295
+ error: new Error("Workflow not compiled"),
296
+ message:
297
+ `Workflow at ${validated.path} was defined but not compiled.\n` +
298
+ ` Add .compile() at the end of your defineWorkflow() chain:\n\n` +
299
+ ` export default defineWorkflow({ ... })\n` +
300
+ ` .session({ ... })\n` +
301
+ ` .compile();`,
302
+ };
303
+ }
304
+
305
+ return {
306
+ ok: false,
307
+ stage: "load",
308
+ error: new Error("Invalid workflow export"),
309
+ message:
310
+ `${validated.path} does not export a valid WorkflowDefinition.\n` +
311
+ ` Make sure it exports defineWorkflow(...).compile() as the default export.`,
312
+ };
313
+ }
314
+
315
+ return {
316
+ ok: true,
317
+ value: { ...validated, definition: definition as WorkflowDefinition },
318
+ };
319
+ } catch (error) {
320
+ return {
321
+ ok: false,
322
+ stage: "load",
323
+ error,
324
+ message: error instanceof Error ? error.message : String(error),
325
+ };
326
+ }
327
+ }
328
+
329
+ // ---------------------------------------------------------------------------
330
+ // Full pipeline
331
+ // ---------------------------------------------------------------------------
332
+
333
+ /**
334
+ * Run the full pipeline: resolve → validate → load.
335
+ *
336
+ * Returns a structured result with the loaded WorkflowDefinition on success,
337
+ * or a stage-specific error on failure.
338
+ */
339
+ export async function loadWorkflow(
340
+ plan: Plan,
341
+ report?: Report,
342
+ ): Promise<StageResult<Loaded, "resolve" | "validate" | "load">> {
343
+ // Stage 1: Resolve
344
+ report?.start?.("resolve");
345
+ const resolved = await resolve(plan);
346
+ if (!resolved.ok) {
347
+ report?.error?.("resolve", resolved.error, resolved.message);
348
+ return resolved;
349
+ }
350
+
351
+ // Stage 2: Validate
352
+ report?.start?.("validate");
353
+ const validated = await validate(resolved.value);
354
+ if (!validated.ok) {
355
+ report?.error?.("validate", validated.error, validated.message);
356
+ return validated;
357
+ }
358
+ if (validated.value.warnings.length > 0) {
359
+ report?.warn?.(validated.value.warnings);
360
+ }
361
+
362
+ // Stage 3: Load
363
+ report?.start?.("load");
364
+ const loaded = await load(validated.value);
365
+ if (!loaded.ok) {
366
+ report?.error?.("load", loaded.error, loaded.message);
367
+ return loaded;
368
+ }
369
+
370
+ return loaded;
371
+ }
372
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * OpenTUI React-based orchestrator panel with session graph view.
3
+ *
4
+ * This module re-exports the public API from the components/ directory.
5
+ * Internal implementation is modularized under ../components/.
6
+ */
7
+
8
+ export { OrchestratorPanel } from "../components/orchestrator-panel.tsx";
9
+ export type { PanelSession, PanelOptions } from "../components/orchestrator-panel-types.ts";
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Terminal color theme using Catppuccin palettes.
3
+ *
4
+ * Uses OpenTUI's built-in dark/light mode detection (via the renderer's
5
+ * themeMode property) to select the appropriate palette:
6
+ * - Mocha for dark terminals (and as fallback)
7
+ * - Latte for light terminals
8
+ */
9
+
10
+ import type { ThemeMode } from "@opentui/core";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Theme type
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export interface TerminalTheme {
17
+ bg: string;
18
+ surface: string;
19
+ selection: string;
20
+ border: string;
21
+ borderDim: string;
22
+ accent: string;
23
+ text: string;
24
+ dim: string;
25
+ success: string;
26
+ error: string;
27
+ warning: string;
28
+ }
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Catppuccin Mocha (dark)
32
+ // ---------------------------------------------------------------------------
33
+
34
+ const CATPPUCCIN_MOCHA: TerminalTheme = {
35
+ bg: "#1e1e2e", // Base
36
+ surface: "#313244", // Surface0
37
+ selection: "#45475a", // Surface1
38
+ border: "#6c7086", // Overlay0
39
+ borderDim: "#585b70", // Surface2
40
+ accent: "#89b4fa", // Blue
41
+ text: "#cdd6f4", // Text
42
+ dim: "#7f849c", // Overlay1
43
+ success: "#a6e3a1", // Green
44
+ error: "#f38ba8", // Red
45
+ warning: "#f9e2af", // Yellow
46
+ };
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Catppuccin Latte (light)
50
+ // ---------------------------------------------------------------------------
51
+
52
+ const CATPPUCCIN_LATTE: TerminalTheme = {
53
+ bg: "#eff1f5", // Base
54
+ surface: "#ccd0da", // Surface0
55
+ selection: "#bcc0cc", // Surface1
56
+ border: "#9ca0b0", // Overlay0
57
+ borderDim: "#acb0be", // Surface2
58
+ accent: "#1e66f5", // Blue
59
+ text: "#4c4f69", // Text
60
+ dim: "#8c8fa1", // Overlay1
61
+ success: "#40a02b", // Green
62
+ error: "#d20f39", // Red
63
+ warning: "#df8e1d", // Yellow
64
+ };
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Public API
68
+ // ---------------------------------------------------------------------------
69
+
70
+ /**
71
+ * Resolve the terminal theme from the renderer's detected theme mode.
72
+ * Returns Catppuccin Latte for light terminals, Mocha for dark or unknown.
73
+ */
74
+ export function resolveTheme(mode: ThemeMode | null): TerminalTheme {
75
+ return mode === "light" ? CATPPUCCIN_LATTE : CATPPUCCIN_MOCHA;
76
+ }