@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,156 @@
1
+ // ─── Connectors ───────────────────────────────────
2
+
3
+ import type { GraphTheme } from "./graph-theme.ts";
4
+ import { NODE_W, NODE_H, type LayoutNode } from "./layout.ts";
5
+
6
+ export interface ConnectorResult {
7
+ text: string;
8
+ col: number;
9
+ row: number;
10
+ width: number;
11
+ height: number;
12
+ color: string;
13
+ }
14
+
15
+ /** Fan-out connector: one parent branching down to one or more tree children. */
16
+ export function buildConnector(
17
+ parent: LayoutNode,
18
+ rowH: Record<number, number>,
19
+ theme: GraphTheme,
20
+ ): ConnectorResult | null {
21
+ if (parent.children.length === 0) return null;
22
+
23
+ const pcx = parent.x + Math.floor(NODE_W / 2);
24
+ const parentBottom = parent.y + (rowH[parent.depth] ?? NODE_H);
25
+ const firstChildRow = Math.min(...parent.children.map((c: LayoutNode) => c.y));
26
+ const numRows = firstChildRow - parentBottom;
27
+ if (numRows < 1) return null;
28
+
29
+ const childCxs = parent.children.map((c: LayoutNode) => c.x + Math.floor(NODE_W / 2));
30
+ const isStraight = parent.children.length === 1 && childCxs[0] === pcx;
31
+
32
+ // Straight drop: single child directly below
33
+ if (isStraight) {
34
+ const text = Array(numRows).fill("│").join("\n");
35
+ return {
36
+ text,
37
+ col: pcx,
38
+ row: parentBottom,
39
+ width: 1,
40
+ height: numRows,
41
+ color: theme.borderActive,
42
+ };
43
+ }
44
+
45
+ // Branching: horizontal bar connecting all children
46
+ const allCols = [pcx, ...childCxs];
47
+ const minCol = Math.min(...allCols);
48
+ const maxCol = Math.max(...allCols);
49
+ const width = maxCol - minCol + 1;
50
+ const toL = (c: number) => c - minCol;
51
+
52
+ const barRow = numRows - 1;
53
+ const grid: string[][] = Array.from({ length: numRows }, () => Array(width).fill(" "));
54
+
55
+ // Vertical stem from parent center down to bar
56
+ for (let r = 0; r < barRow; r++) grid[r]![toL(pcx)] = "│";
57
+
58
+ // Horizontal bar
59
+ for (let c = 0; c < width; c++) grid[barRow]![c] = "─";
60
+
61
+ // Parent junction on bar
62
+ const childAtParent = childCxs.includes(pcx);
63
+ const pl = toL(pcx);
64
+ if (pcx === minCol) {
65
+ grid[barRow]![pl] = childAtParent ? "├" : "╰";
66
+ } else if (pcx === maxCol) {
67
+ grid[barRow]![pl] = childAtParent ? "┤" : "╯";
68
+ } else {
69
+ grid[barRow]![pl] = childAtParent ? "┼" : "┴";
70
+ }
71
+
72
+ // Child junctions on bar
73
+ for (const cx of childCxs) {
74
+ if (cx === pcx) continue;
75
+ const cl = toL(cx);
76
+ if (cx === minCol) grid[barRow]![cl] = "╭";
77
+ else if (cx === maxCol) grid[barRow]![cl] = "╮";
78
+ else grid[barRow]![cl] = "┬";
79
+ }
80
+
81
+ return {
82
+ text: grid.map((row) => row.join("")).join("\n"),
83
+ col: minCol,
84
+ row: parentBottom,
85
+ width,
86
+ height: numRows,
87
+ color: theme.borderActive,
88
+ };
89
+ }
90
+
91
+ /** Fan-in connector: multiple parents converging down to a single merge child. */
92
+ export function buildMergeConnector(
93
+ child: LayoutNode,
94
+ rowH: Record<number, number>,
95
+ allNodes: Record<string, LayoutNode>,
96
+ theme: GraphTheme,
97
+ ): ConnectorResult | null {
98
+ if (child.parents.length < 2) return null;
99
+
100
+ const parentNodes = child.parents
101
+ .map((p) => allNodes[p])
102
+ .filter((n): n is LayoutNode => n != null);
103
+ if (parentNodes.length < 2) return null;
104
+
105
+ const parentCxs = parentNodes.map((p) => p.x + Math.floor(NODE_W / 2));
106
+ const childCx = child.x + Math.floor(NODE_W / 2);
107
+
108
+ const parentBottom = Math.max(
109
+ ...parentNodes.map((p) => p.y + (rowH[p.depth] ?? NODE_H)),
110
+ );
111
+ const childTop = child.y;
112
+ const numRows = childTop - parentBottom;
113
+ if (numRows < 1) return null;
114
+
115
+ const allCols = [...parentCxs, childCx];
116
+ const minCol = Math.min(...allCols);
117
+ const maxCol = Math.max(...allCols);
118
+ const width = maxCol - minCol + 1;
119
+ const toL = (c: number) => c - minCol;
120
+
121
+ const grid: string[][] = Array.from({ length: numRows }, () => Array(width).fill(" "));
122
+
123
+ // Bar at the top row — parents converge here
124
+ const barRow = 0;
125
+ for (let c = 0; c < width; c++) grid[barRow]![c] = "─";
126
+
127
+ // Vertical stem from bar down to child
128
+ for (let r = barRow + 1; r < numRows; r++) grid[r]![toL(childCx)] = "│";
129
+
130
+ // Junction characters on the bar
131
+ const parentSet = new Set(parentCxs);
132
+ for (const cx of allCols) {
133
+ const cl = toL(cx);
134
+ const hasUp = parentSet.has(cx);
135
+ const hasDown = cx === childCx;
136
+ const isLeft = cx === minCol;
137
+ const isRight = cx === maxCol;
138
+
139
+ if (hasUp && hasDown) {
140
+ grid[barRow]![cl] = isLeft ? "├" : isRight ? "┤" : "┼";
141
+ } else if (hasUp) {
142
+ grid[barRow]![cl] = isLeft ? "╰" : isRight ? "╯" : "┴";
143
+ } else if (hasDown) {
144
+ grid[barRow]![cl] = isLeft ? "╭" : isRight ? "╮" : "┬";
145
+ }
146
+ }
147
+
148
+ return {
149
+ text: grid.map((row) => row.join("")).join("\n"),
150
+ col: minCol,
151
+ row: parentBottom,
152
+ width,
153
+ height: numRows,
154
+ color: theme.borderActive,
155
+ };
156
+ }
@@ -0,0 +1,11 @@
1
+ /** @jsxImportSource @opentui/react */
2
+
3
+ import type { ConnectorResult } from "./connectors.ts";
4
+
5
+ export function Edge({ text, col, row, width, height, color: edgeColor }: ConnectorResult) {
6
+ return (
7
+ <box position="absolute" left={col} top={row} width={width} height={height}>
8
+ <text fg={edgeColor}>{text}</text>
9
+ </box>
10
+ );
11
+ }
@@ -0,0 +1,38 @@
1
+ /** @jsxImportSource @opentui/react */
2
+ /**
3
+ * React Error Boundary for the orchestrator panel.
4
+ *
5
+ * Catches render-time errors in the component tree and displays a
6
+ * static fallback so the rest of the TUI doesn't crash.
7
+ */
8
+
9
+ import { Component, type ReactNode, type ErrorInfo } from "react";
10
+
11
+ interface ErrorBoundaryProps {
12
+ fallback: (error: Error) => ReactNode;
13
+ children: ReactNode;
14
+ }
15
+
16
+ interface ErrorBoundaryState {
17
+ error: Error | null;
18
+ }
19
+
20
+ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
21
+ override state: ErrorBoundaryState = { error: null };
22
+
23
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
24
+ return { error };
25
+ }
26
+
27
+ override componentDidCatch(error: Error, info: ErrorInfo): void {
28
+ // Log to stderr so it lands in the orchestrator log file
29
+ console.error("[ErrorBoundary]", error, info.componentStack);
30
+ }
31
+
32
+ override render(): ReactNode {
33
+ if (this.state.error) {
34
+ return this.props.fallback(this.state.error);
35
+ }
36
+ return this.props.children;
37
+ }
38
+ }
@@ -0,0 +1,36 @@
1
+ // ─── Graph Theme ──────────────────────────────────
2
+
3
+ import type { TerminalTheme } from "../runtime/theme.ts";
4
+ import { lerpColor } from "./color-utils.ts";
5
+
6
+ export interface GraphTheme {
7
+ background: string;
8
+ backgroundElement: string;
9
+ text: string;
10
+ textMuted: string;
11
+ textDim: string;
12
+ primary: string;
13
+ success: string;
14
+ error: string;
15
+ warning: string;
16
+ info: string;
17
+ border: string;
18
+ borderActive: string;
19
+ }
20
+
21
+ export function deriveGraphTheme(t: TerminalTheme): GraphTheme {
22
+ return {
23
+ background: t.bg,
24
+ backgroundElement: t.surface,
25
+ text: t.text,
26
+ textMuted: lerpColor(t.text, t.bg, 0.3),
27
+ textDim: t.dim,
28
+ primary: t.accent,
29
+ success: t.success,
30
+ error: t.error,
31
+ warning: t.warning,
32
+ info: t.accent,
33
+ border: t.borderDim,
34
+ borderActive: t.border,
35
+ };
36
+ }
@@ -0,0 +1,60 @@
1
+ /** @jsxImportSource @opentui/react */
2
+
3
+ import type { SessionStatus } from "./orchestrator-panel-types.ts";
4
+ import { useStore, useGraphTheme } from "./orchestrator-panel-contexts.ts";
5
+
6
+ export function Header() {
7
+ const store = useStore();
8
+ const theme = useGraphTheme();
9
+
10
+ const counts: Record<SessionStatus, number> = { complete: 0, running: 0, pending: 0, error: 0 };
11
+ for (const s of store.sessions) counts[s.status]++;
12
+
13
+ const isFailed = store.fatalError !== null;
14
+ const isDone = store.completionInfo !== null;
15
+ const badgeColor = isFailed ? theme.error : isDone ? theme.success : theme.info;
16
+ const badgeText = isFailed
17
+ ? " \u2717 Failed "
18
+ : isDone
19
+ ? ` \u2713 ${store.workflowName} `
20
+ : " Orchestrator ";
21
+
22
+ return (
23
+ <box
24
+ height={1}
25
+ backgroundColor={theme.backgroundElement}
26
+ flexDirection="row"
27
+ paddingRight={2}
28
+ alignItems="center"
29
+ >
30
+ <text>
31
+ <span fg={theme.backgroundElement} bg={badgeColor}>
32
+ <strong>{badgeText}</strong>
33
+ </span>
34
+ </text>
35
+
36
+ <box flexGrow={1} justifyContent="flex-end" flexDirection="row" gap={2}>
37
+ {counts.complete > 0 ? (
38
+ <text>
39
+ <span fg={theme.success}>{"\u2713"} {counts.complete}</span>
40
+ </text>
41
+ ) : null}
42
+ {counts.running > 0 ? (
43
+ <text>
44
+ <span fg={theme.warning}>{"\u25CF"} {counts.running}</span>
45
+ </text>
46
+ ) : null}
47
+ {counts.pending > 0 ? (
48
+ <text>
49
+ <span fg={theme.textDim}>{"\u25CB"} {counts.pending}</span>
50
+ </text>
51
+ ) : null}
52
+ {counts.error > 0 ? (
53
+ <text>
54
+ <span fg={theme.error}>{"\u2717"} {counts.error}</span>
55
+ </text>
56
+ ) : null}
57
+ </box>
58
+ </box>
59
+ );
60
+ }