@lawrence369/loop-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +136 -0
  4. package/dist/agent/activity.d.ts +64 -0
  5. package/dist/agent/activity.js +265 -0
  6. package/dist/agent/launcher.d.ts +42 -0
  7. package/dist/agent/launcher.js +243 -0
  8. package/dist/agent/pty-session.d.ts +113 -0
  9. package/dist/agent/pty-session.js +490 -0
  10. package/dist/agent/ready-detector.d.ts +46 -0
  11. package/dist/agent/ready-detector.js +86 -0
  12. package/dist/agent/wrapper.d.ts +18 -0
  13. package/dist/agent/wrapper.js +110 -0
  14. package/dist/bin/lclaude.d.ts +3 -0
  15. package/dist/bin/lclaude.js +7 -0
  16. package/dist/bin/lcodex.d.ts +3 -0
  17. package/dist/bin/lcodex.js +7 -0
  18. package/dist/bin/lgemini.d.ts +3 -0
  19. package/dist/bin/lgemini.js +7 -0
  20. package/dist/bus/daemon.d.ts +56 -0
  21. package/dist/bus/daemon.js +135 -0
  22. package/dist/bus/event-bus.d.ts +105 -0
  23. package/dist/bus/event-bus.js +157 -0
  24. package/dist/bus/message.d.ts +48 -0
  25. package/dist/bus/message.js +129 -0
  26. package/dist/bus/queue.d.ts +50 -0
  27. package/dist/bus/queue.js +100 -0
  28. package/dist/bus/store.d.ts +88 -0
  29. package/dist/bus/store.js +212 -0
  30. package/dist/bus/subscriber.d.ts +76 -0
  31. package/dist/bus/subscriber.js +187 -0
  32. package/dist/config/index.d.ts +8 -0
  33. package/dist/config/index.js +72 -0
  34. package/dist/config/schema.d.ts +18 -0
  35. package/dist/config/schema.js +58 -0
  36. package/dist/core/conversation.d.ts +34 -0
  37. package/dist/core/conversation.js +289 -0
  38. package/dist/core/engine.d.ts +40 -0
  39. package/dist/core/engine.js +288 -0
  40. package/dist/core/loop.d.ts +33 -0
  41. package/dist/core/loop.js +209 -0
  42. package/dist/core/protocol.d.ts +60 -0
  43. package/dist/core/protocol.js +162 -0
  44. package/dist/core/scoring.d.ts +34 -0
  45. package/dist/core/scoring.js +69 -0
  46. package/dist/index.d.ts +3 -0
  47. package/dist/index.js +408 -0
  48. package/dist/orchestrator/daemon.d.ts +74 -0
  49. package/dist/orchestrator/daemon.js +294 -0
  50. package/dist/orchestrator/group.d.ts +73 -0
  51. package/dist/orchestrator/group.js +166 -0
  52. package/dist/orchestrator/ipc-server.d.ts +60 -0
  53. package/dist/orchestrator/ipc-server.js +166 -0
  54. package/dist/orchestrator/scheduler.d.ts +32 -0
  55. package/dist/orchestrator/scheduler.js +95 -0
  56. package/dist/plan/context.d.ts +8 -0
  57. package/dist/plan/context.js +42 -0
  58. package/dist/plan/decisions.d.ts +18 -0
  59. package/dist/plan/decisions.js +143 -0
  60. package/dist/plan/shared-plan.d.ts +33 -0
  61. package/dist/plan/shared-plan.js +211 -0
  62. package/dist/skills/executor.d.ts +7 -0
  63. package/dist/skills/executor.js +11 -0
  64. package/dist/skills/loader.d.ts +16 -0
  65. package/dist/skills/loader.js +80 -0
  66. package/dist/skills/registry.d.ts +13 -0
  67. package/dist/skills/registry.js +54 -0
  68. package/dist/terminal/adapter.d.ts +61 -0
  69. package/dist/terminal/adapter.js +42 -0
  70. package/dist/terminal/detect.d.ts +30 -0
  71. package/dist/terminal/detect.js +77 -0
  72. package/dist/terminal/iterm2-adapter.d.ts +19 -0
  73. package/dist/terminal/iterm2-adapter.js +120 -0
  74. package/dist/terminal/pty-adapter.d.ts +18 -0
  75. package/dist/terminal/pty-adapter.js +84 -0
  76. package/dist/terminal/terminal-adapter.d.ts +17 -0
  77. package/dist/terminal/terminal-adapter.js +94 -0
  78. package/dist/terminal/tmux-adapter.d.ts +18 -0
  79. package/dist/terminal/tmux-adapter.js +127 -0
  80. package/dist/ui/banner.d.ts +3 -0
  81. package/dist/ui/banner.js +145 -0
  82. package/dist/ui/colors.d.ts +41 -0
  83. package/dist/ui/colors.js +65 -0
  84. package/dist/ui/dashboard.d.ts +32 -0
  85. package/dist/ui/dashboard.js +138 -0
  86. package/dist/ui/input.d.ts +10 -0
  87. package/dist/ui/input.js +96 -0
  88. package/dist/ui/interactive.d.ts +13 -0
  89. package/dist/ui/interactive.js +230 -0
  90. package/dist/ui/renderer.d.ts +33 -0
  91. package/dist/ui/renderer.js +106 -0
  92. package/dist/utils/ansi.d.ts +11 -0
  93. package/dist/utils/ansi.js +16 -0
  94. package/dist/utils/fs.d.ts +34 -0
  95. package/dist/utils/fs.js +115 -0
  96. package/dist/utils/lock.d.ts +12 -0
  97. package/dist/utils/lock.js +116 -0
  98. package/dist/utils/process.d.ts +31 -0
  99. package/dist/utils/process.js +111 -0
  100. package/dist/utils/pty-filter.d.ts +31 -0
  101. package/dist/utils/pty-filter.js +187 -0
  102. package/package.json +71 -0
  103. package/skills/loop/SKILL.md +19 -0
  104. package/skills/plan/SKILL.md +9 -0
  105. package/skills/review/SKILL.md +14 -0
@@ -0,0 +1,120 @@
1
+ /**
2
+ * iTerm2 adapter.
3
+ *
4
+ * Uses osascript with iTerm2's AppleScript API to create new sessions
5
+ * (tabs / splits) and run commands. Supports activation (bring to front)
6
+ * and text injection via `write text`.
7
+ *
8
+ * Reference: https://iterm2.com/documentation-scripting.html
9
+ */
10
+ import { execFile, spawn } from "node:child_process";
11
+ // ---------------------------------------------------------------------------
12
+ // Helpers
13
+ // ---------------------------------------------------------------------------
14
+ function osascript(script) {
15
+ return new Promise((resolve, reject) => {
16
+ execFile("osascript", ["-e", script], { timeout: 10_000 }, (err, stdout) => {
17
+ if (err)
18
+ return reject(err);
19
+ resolve((stdout ?? "").trim());
20
+ });
21
+ });
22
+ }
23
+ // ---------------------------------------------------------------------------
24
+ // Implementation
25
+ // ---------------------------------------------------------------------------
26
+ export class ITerm2Adapter {
27
+ mode = "iterm2";
28
+ capabilities = {
29
+ supportsActivate: true,
30
+ supportsInjection: true,
31
+ supportsSessionReuse: true,
32
+ supportsResize: false,
33
+ };
34
+ async launch(command, args, opts) {
35
+ // Sanitize for AppleScript: escape backslashes, quotes, AND newlines
36
+ // to prevent AppleScript injection via crafted arguments.
37
+ const escapeAS = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "").replace(/\r/g, "");
38
+ const escaped = [command, ...args].map(escapeAS).join(" ");
39
+ const cwd = escapeAS(opts.cwd);
40
+ // Create a new iTerm2 tab and run the command there.
41
+ const script = `
42
+ tell application "iTerm2"
43
+ activate
44
+ tell current window
45
+ set newTab to (create tab with default profile)
46
+ tell current session of newTab
47
+ write text "cd \\"${cwd}\\" && ${escaped}"
48
+ end tell
49
+ end tell
50
+ end tell
51
+ `;
52
+ await osascript(script);
53
+ // Spawn a local child process for lifecycle tracking, same pattern as
54
+ // NativeTerminalAdapter.
55
+ const child = spawn(command, args, {
56
+ cwd: opts.cwd,
57
+ env: {
58
+ ...process.env,
59
+ ...(opts.env ?? {}),
60
+ // Preserve iTerm2 session id so child processes can detect the terminal
61
+ ITERM_SESSION_ID: process.env.ITERM_SESSION_ID ?? "",
62
+ TERM_PROGRAM: process.env.TERM_PROGRAM ?? "",
63
+ TERM_PROGRAM_VERSION: process.env.TERM_PROGRAM_VERSION ?? "",
64
+ },
65
+ stdio: ["pipe", "pipe", "pipe"],
66
+ });
67
+ const dataHandlers = [];
68
+ const exitHandlers = [];
69
+ child.stdout?.on("data", (chunk) => {
70
+ const text = chunk.toString("utf8");
71
+ for (const h of dataHandlers)
72
+ h(text);
73
+ });
74
+ child.stderr?.on("data", (chunk) => {
75
+ const text = chunk.toString("utf8");
76
+ for (const h of dataHandlers)
77
+ h(text);
78
+ });
79
+ child.on("exit", (code) => {
80
+ const exitCode = code ?? 1;
81
+ for (const h of exitHandlers)
82
+ h(exitCode);
83
+ });
84
+ return {
85
+ pid: child.pid ?? 0,
86
+ write(data) {
87
+ child.stdin?.write(data);
88
+ },
89
+ resize(_cols, _rows) {
90
+ // iTerm2 resize via AppleScript is unreliable; no-op for now
91
+ },
92
+ kill() {
93
+ child.kill();
94
+ },
95
+ onData(handler) {
96
+ dataHandlers.push(handler);
97
+ },
98
+ onExit(handler) {
99
+ exitHandlers.push(handler);
100
+ },
101
+ };
102
+ }
103
+ async inject(processOrId, command) {
104
+ const escaped = command.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "").replace(/\r/g, "");
105
+ // Write text to the current iTerm2 session
106
+ const script = `
107
+ tell application "iTerm2"
108
+ tell current session of current window
109
+ write text "${escaped}"
110
+ end tell
111
+ end tell
112
+ `;
113
+ await osascript(script);
114
+ void processOrId;
115
+ }
116
+ async activate(_processOrId) {
117
+ await osascript('tell application "iTerm2" to activate');
118
+ }
119
+ }
120
+ //# sourceMappingURL=iterm2-adapter.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Internal PTY adapter (default fallback).
3
+ *
4
+ * Uses `node-pty` to spawn a pseudo-terminal without any visible terminal
5
+ * window — fully headless. This is the most commonly used adapter and serves
6
+ * as the default when no specific terminal environment is detected.
7
+ *
8
+ * Ported from ufoo's adapters/internalPtyAdapter.js.
9
+ */
10
+ import type { AdapterLaunchOptions, LaunchedProcess, TerminalAdapter, TerminalCapabilities } from "./adapter.js";
11
+ import type { LaunchMode } from "./detect.js";
12
+ export declare class PtyAdapter implements TerminalAdapter {
13
+ readonly mode: LaunchMode;
14
+ readonly capabilities: TerminalCapabilities;
15
+ launch(command: string, args: string[], opts: AdapterLaunchOptions): Promise<LaunchedProcess>;
16
+ inject(_processOrId: number | string, command: string): Promise<void>;
17
+ }
18
+ //# sourceMappingURL=pty-adapter.d.ts.map
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Internal PTY adapter (default fallback).
3
+ *
4
+ * Uses `node-pty` to spawn a pseudo-terminal without any visible terminal
5
+ * window — fully headless. This is the most commonly used adapter and serves
6
+ * as the default when no specific terminal environment is detected.
7
+ *
8
+ * Ported from ufoo's adapters/internalPtyAdapter.js.
9
+ */
10
+ import pty from "node-pty";
11
+ // ---------------------------------------------------------------------------
12
+ // Implementation
13
+ // ---------------------------------------------------------------------------
14
+ export class PtyAdapter {
15
+ mode = "pty";
16
+ capabilities = {
17
+ supportsActivate: false,
18
+ supportsInjection: true,
19
+ supportsSessionReuse: false,
20
+ supportsResize: true,
21
+ };
22
+ async launch(command, args, opts) {
23
+ const cols = opts.cols ?? process.stdout.columns ?? 80;
24
+ const rows = opts.rows ?? process.stdout.rows ?? 24;
25
+ const ptyProcess = pty.spawn(command, args, {
26
+ name: "xterm-256color",
27
+ cols,
28
+ rows,
29
+ cwd: opts.cwd,
30
+ env: { ...process.env, ...(opts.env ?? {}) },
31
+ });
32
+ const dataHandlers = [];
33
+ const exitHandlers = [];
34
+ ptyProcess.onData((data) => {
35
+ for (const h of dataHandlers)
36
+ h(data);
37
+ });
38
+ ptyProcess.onExit(({ exitCode }) => {
39
+ for (const h of exitHandlers)
40
+ h(exitCode);
41
+ });
42
+ return {
43
+ pid: ptyProcess.pid,
44
+ write(data) {
45
+ try {
46
+ ptyProcess.write(data);
47
+ }
48
+ catch {
49
+ // Process may have exited
50
+ }
51
+ },
52
+ resize(c, r) {
53
+ try {
54
+ ptyProcess.resize(c, r);
55
+ }
56
+ catch {
57
+ // Process may have exited
58
+ }
59
+ },
60
+ kill() {
61
+ try {
62
+ ptyProcess.kill();
63
+ }
64
+ catch {
65
+ // Already dead
66
+ }
67
+ },
68
+ onData(handler) {
69
+ dataHandlers.push(handler);
70
+ },
71
+ onExit(handler) {
72
+ exitHandlers.push(handler);
73
+ },
74
+ };
75
+ }
76
+ async inject(_processOrId, command) {
77
+ // For the internal PTY, injection is just writing to the PTY stdin.
78
+ // The caller must hold a reference to the LaunchedProcess to use write().
79
+ // This method exists for symmetry but cannot reach an arbitrary process.
80
+ void command;
81
+ throw new Error("PtyAdapter.inject requires direct LaunchedProcess.write()");
82
+ }
83
+ }
84
+ //# sourceMappingURL=pty-adapter.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Native Terminal.app adapter (macOS).
3
+ *
4
+ * Opens a new Terminal.app window via `osascript` and runs the given command
5
+ * inside it. Supports bringing the terminal to the foreground.
6
+ *
7
+ * Ported from ufoo's adapters/terminalAdapter.js.
8
+ */
9
+ import type { AdapterLaunchOptions, LaunchedProcess, TerminalAdapter, TerminalCapabilities } from "./adapter.js";
10
+ import type { LaunchMode } from "./detect.js";
11
+ export declare class NativeTerminalAdapter implements TerminalAdapter {
12
+ readonly mode: LaunchMode;
13
+ readonly capabilities: TerminalCapabilities;
14
+ launch(command: string, args: string[], opts: AdapterLaunchOptions): Promise<LaunchedProcess>;
15
+ activate(_processOrId: number | string): Promise<void>;
16
+ }
17
+ //# sourceMappingURL=terminal-adapter.d.ts.map
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Native Terminal.app adapter (macOS).
3
+ *
4
+ * Opens a new Terminal.app window via `osascript` and runs the given command
5
+ * inside it. Supports bringing the terminal to the foreground.
6
+ *
7
+ * Ported from ufoo's adapters/terminalAdapter.js.
8
+ */
9
+ import { execFile, spawn } from "node:child_process";
10
+ // ---------------------------------------------------------------------------
11
+ // Helpers
12
+ // ---------------------------------------------------------------------------
13
+ function osascript(script) {
14
+ return new Promise((resolve, reject) => {
15
+ execFile("osascript", ["-e", script], { timeout: 10_000 }, (err, stdout) => {
16
+ if (err)
17
+ return reject(err);
18
+ resolve((stdout ?? "").trim());
19
+ });
20
+ });
21
+ }
22
+ // ---------------------------------------------------------------------------
23
+ // Implementation
24
+ // ---------------------------------------------------------------------------
25
+ export class NativeTerminalAdapter {
26
+ mode = "terminal";
27
+ capabilities = {
28
+ supportsActivate: true,
29
+ supportsInjection: false,
30
+ supportsSessionReuse: true,
31
+ supportsResize: false,
32
+ };
33
+ async launch(command, args, opts) {
34
+ // Build full shell command string (escaped for AppleScript)
35
+ const escaped = [command, ...args]
36
+ .map((a) => a.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/'/g, "'\\''"))
37
+ .join(" ");
38
+ const cwd = opts.cwd.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/'/g, "'\\''");
39
+ // Use osascript to open a new Terminal window and run the command
40
+ const script = `
41
+ tell application "Terminal"
42
+ activate
43
+ set newTab to do script "cd '${cwd}' && ${escaped}"
44
+ end tell
45
+ `;
46
+ await osascript(script);
47
+ // Terminal.app does not expose the child PID easily via AppleScript.
48
+ // We fall back to spawning the process ourselves so we can track I/O.
49
+ const child = spawn(command, args, {
50
+ cwd: opts.cwd,
51
+ env: { ...process.env, ...(opts.env ?? {}) },
52
+ stdio: ["pipe", "pipe", "pipe"],
53
+ });
54
+ const dataHandlers = [];
55
+ const exitHandlers = [];
56
+ child.stdout?.on("data", (chunk) => {
57
+ const text = chunk.toString("utf8");
58
+ for (const h of dataHandlers)
59
+ h(text);
60
+ });
61
+ child.stderr?.on("data", (chunk) => {
62
+ const text = chunk.toString("utf8");
63
+ for (const h of dataHandlers)
64
+ h(text);
65
+ });
66
+ child.on("exit", (code) => {
67
+ const exitCode = code ?? 1;
68
+ for (const h of exitHandlers)
69
+ h(exitCode);
70
+ });
71
+ return {
72
+ pid: child.pid ?? 0,
73
+ write(data) {
74
+ child.stdin?.write(data);
75
+ },
76
+ resize(_cols, _rows) {
77
+ // Terminal.app does not support programmatic resize
78
+ },
79
+ kill() {
80
+ child.kill();
81
+ },
82
+ onData(handler) {
83
+ dataHandlers.push(handler);
84
+ },
85
+ onExit(handler) {
86
+ exitHandlers.push(handler);
87
+ },
88
+ };
89
+ }
90
+ async activate(_processOrId) {
91
+ await osascript('tell application "Terminal" to activate');
92
+ }
93
+ }
94
+ //# sourceMappingURL=terminal-adapter.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * tmux pane adapter.
3
+ *
4
+ * Spawns commands inside tmux panes using `tmux split-window` and supports
5
+ * text injection via `tmux send-keys`.
6
+ *
7
+ * Ported from ufoo's adapters/tmuxAdapter.js.
8
+ */
9
+ import type { AdapterLaunchOptions, LaunchedProcess, TerminalAdapter, TerminalCapabilities } from "./adapter.js";
10
+ import type { LaunchMode } from "./detect.js";
11
+ export declare class TmuxAdapter implements TerminalAdapter {
12
+ readonly mode: LaunchMode;
13
+ readonly capabilities: TerminalCapabilities;
14
+ launch(command: string, args: string[], opts: AdapterLaunchOptions): Promise<LaunchedProcess>;
15
+ inject(processOrId: number | string, command: string): Promise<void>;
16
+ activate(processOrId: number | string): Promise<void>;
17
+ }
18
+ //# sourceMappingURL=tmux-adapter.d.ts.map
@@ -0,0 +1,127 @@
1
+ /**
2
+ * tmux pane adapter.
3
+ *
4
+ * Spawns commands inside tmux panes using `tmux split-window` and supports
5
+ * text injection via `tmux send-keys`.
6
+ *
7
+ * Ported from ufoo's adapters/tmuxAdapter.js.
8
+ */
9
+ import { execFile, spawn } from "node:child_process";
10
+ // ---------------------------------------------------------------------------
11
+ // Helpers
12
+ // ---------------------------------------------------------------------------
13
+ function tmuxCommand(args) {
14
+ return new Promise((resolve, reject) => {
15
+ execFile("tmux", args, { timeout: 10_000 }, (err, stdout) => {
16
+ if (err)
17
+ return reject(err);
18
+ resolve((stdout ?? "").trim());
19
+ });
20
+ });
21
+ }
22
+ // ---------------------------------------------------------------------------
23
+ // Implementation
24
+ // ---------------------------------------------------------------------------
25
+ export class TmuxAdapter {
26
+ mode = "tmux";
27
+ capabilities = {
28
+ supportsActivate: true,
29
+ supportsInjection: true,
30
+ supportsSessionReuse: true,
31
+ supportsResize: true,
32
+ };
33
+ async launch(command, args, opts) {
34
+ // Shell-quote each argument to prevent injection when tmux interprets
35
+ // the command string via the shell.
36
+ const shellQuote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
37
+ const fullCommand = [command, ...args].map(shellQuote).join(" ");
38
+ // Split a new pane and run the command. tmux split-window returns the
39
+ // pane ID (e.g. %5) when using -P -F.
40
+ const paneId = await tmuxCommand([
41
+ "split-window",
42
+ "-h",
43
+ "-c",
44
+ opts.cwd,
45
+ "-P",
46
+ "-F",
47
+ "#{pane_id}",
48
+ fullCommand,
49
+ ]);
50
+ // Resolve the PID of the initial command running inside the new pane.
51
+ let pid = 0;
52
+ try {
53
+ const pidStr = await tmuxCommand([
54
+ "display-message",
55
+ "-t",
56
+ paneId,
57
+ "-p",
58
+ "#{pane_pid}",
59
+ ]);
60
+ pid = Number.parseInt(pidStr, 10) || 0;
61
+ }
62
+ catch {
63
+ // Non-fatal — PID tracking is best-effort
64
+ }
65
+ // We also spawn the command locally so we can attach I/O handlers.
66
+ // This mirrors what the terminal-adapter does: osascript opens the
67
+ // window, but a local child_process tracks the lifecycle.
68
+ const child = spawn(command, args, {
69
+ cwd: opts.cwd,
70
+ env: { ...process.env, ...(opts.env ?? {}) },
71
+ stdio: ["pipe", "pipe", "pipe"],
72
+ });
73
+ const dataHandlers = [];
74
+ const exitHandlers = [];
75
+ child.stdout?.on("data", (chunk) => {
76
+ const text = chunk.toString("utf8");
77
+ for (const h of dataHandlers)
78
+ h(text);
79
+ });
80
+ child.stderr?.on("data", (chunk) => {
81
+ const text = chunk.toString("utf8");
82
+ for (const h of dataHandlers)
83
+ h(text);
84
+ });
85
+ child.on("exit", (code) => {
86
+ const exitCode = code ?? 1;
87
+ for (const h of exitHandlers)
88
+ h(exitCode);
89
+ });
90
+ const effectivePid = child.pid ?? pid;
91
+ return {
92
+ pid: effectivePid,
93
+ write(data) {
94
+ child.stdin?.write(data);
95
+ },
96
+ resize(cols, rows) {
97
+ // Resize the tmux pane
98
+ tmuxCommand(["resize-pane", "-t", paneId, "-x", String(cols), "-y", String(rows)]).catch(() => {
99
+ // Resize failure is non-fatal
100
+ });
101
+ },
102
+ kill() {
103
+ child.kill();
104
+ // Also kill the tmux pane to avoid orphans
105
+ tmuxCommand(["kill-pane", "-t", paneId]).catch(() => {
106
+ // Best-effort cleanup
107
+ });
108
+ },
109
+ onData(handler) {
110
+ dataHandlers.push(handler);
111
+ },
112
+ onExit(handler) {
113
+ exitHandlers.push(handler);
114
+ },
115
+ };
116
+ }
117
+ async inject(processOrId, command) {
118
+ const target = String(processOrId);
119
+ // Use tmux send-keys to inject text. We send the text first, then Enter.
120
+ await tmuxCommand(["send-keys", "-t", target, command, "Enter"]);
121
+ }
122
+ async activate(processOrId) {
123
+ const target = String(processOrId);
124
+ await tmuxCommand(["select-pane", "-t", target]);
125
+ }
126
+ }
127
+ //# sourceMappingURL=tmux-adapter.js.map
@@ -0,0 +1,3 @@
1
+ export declare function renderBanner(): string;
2
+ export declare function renderEngineStatus(): void;
3
+ //# sourceMappingURL=banner.d.ts.map
@@ -0,0 +1,145 @@
1
+ import gradient from "gradient-string";
2
+ import { execFileSync } from "node:child_process";
3
+ import { readFileSync } from "node:fs";
4
+ import { fileURLToPath } from "node:url";
5
+ import { dirname, join } from "node:path";
6
+ import { orange, gBlue, gGreen, dim, bold } from "./colors.js";
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ function getVersion() {
9
+ try {
10
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "..", "package.json"), "utf-8"));
11
+ return `v${pkg.version}`;
12
+ }
13
+ catch {
14
+ return "v0.0.0";
15
+ }
16
+ }
17
+ // 5-line block-letter logo for "loop"
18
+ const LARGE_LOGO = [
19
+ " ██╗ ██████╗ ██████╗ ██████╗ ",
20
+ " ██║ ██╔═══██╗██╔═══██╗██╔══██╗",
21
+ " ██║ ██║ ██║██║ ██║██████╔╝",
22
+ " ██║ ██║ ██║██║ ██║██╔═══╝ ",
23
+ " ███████╗╚██████╔╝╚██████╔╝██║ ",
24
+ " ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ",
25
+ ];
26
+ const loopGradient = gradient(["#F07623", "#4285F4", "#10A37F"]);
27
+ function detectEngines() {
28
+ const engines = [
29
+ { cmd: "claude", label: "Claude", colorFn: orange },
30
+ { cmd: "gemini", label: "Gemini", colorFn: gBlue },
31
+ { cmd: "codex", label: "Codex", colorFn: gGreen },
32
+ ];
33
+ return engines.map(({ cmd, label, colorFn }) => {
34
+ let version = null;
35
+ try {
36
+ version = execFileSync(cmd, ["--version"], {
37
+ encoding: "utf-8",
38
+ timeout: 5000,
39
+ }).trim();
40
+ }
41
+ catch {
42
+ // not installed
43
+ }
44
+ return { name: cmd, label, version, colorFn };
45
+ });
46
+ }
47
+ export function renderBanner() {
48
+ const cols = process.stdout.columns || 80;
49
+ const engines = detectEngines();
50
+ if (cols >= 50) {
51
+ return renderLarge(cols, engines);
52
+ }
53
+ else {
54
+ return renderCompact(cols, engines);
55
+ }
56
+ }
57
+ export function renderEngineStatus() {
58
+ const engines = detectEngines();
59
+ for (const e of engines) {
60
+ const dot = e.version ? e.colorFn("\u25CF") : dim("\u25CB");
61
+ const label = e.version ? e.colorFn(e.label) : dim(e.label);
62
+ const ver = e.version ? dim(` (${e.version})`) : dim(" (not found)");
63
+ console.log(` ${dot} ${label}${ver}`);
64
+ }
65
+ }
66
+ function renderLarge(cols, engines) {
67
+ const VERSION = getVersion();
68
+ const maxLogoWidth = Math.max(...LARGE_LOGO.map((l) => l.length));
69
+ const boxWidth = maxLogoWidth + 6;
70
+ const hBar = "\u2550".repeat(boxWidth - 2);
71
+ const frameLine = (content, visibleLen) => {
72
+ const vLen = visibleLen ?? content.replace(/\x1b\[[0-9;]*m/g, "").length;
73
+ const padding = Math.max(0, boxWidth - 4 - vLen);
74
+ return ` \u2551 ${content}${" ".repeat(padding)}\u2551`;
75
+ };
76
+ const emptyLine = frameLine("", 0);
77
+ // Apply gradient to logo lines
78
+ const logoLines = LARGE_LOGO.map((line) => {
79
+ const padded = line + " ".repeat(maxLogoWidth - line.length);
80
+ const gradientLine = loopGradient(padded);
81
+ return frameLine(gradientLine, maxLogoWidth);
82
+ });
83
+ // Tagline + version on same line
84
+ const tagline = "Iterative multi-engine AI orchestration";
85
+ const version = VERSION;
86
+ const innerWidth = boxWidth - 4;
87
+ const tagVersionGap = Math.max(1, innerWidth - tagline.length - version.length);
88
+ const tagVersionLine = ` \u2551 ${dim(tagline)}${" ".repeat(tagVersionGap)}${bold(version)}\u2551`;
89
+ // Engine status lines
90
+ const engineLines = engines.map((e) => {
91
+ const dot = e.version ? e.colorFn("\u25CF") : dim("\u25CB");
92
+ const label = e.version ? e.colorFn(e.label) : dim(e.label);
93
+ const ver = e.version ? dim(` (${e.version})`) : dim(" (not found)");
94
+ const content = ` ${dot} ${label}${ver}`;
95
+ const visLen = 5 + e.label.length + (e.version ? ` (${e.version})`.length : " (not found)".length);
96
+ return frameLine(content, visLen);
97
+ });
98
+ // Build engine header
99
+ const engLabel = " Engines";
100
+ const engLabelLine = ` \u2551${dim(engLabel)}${" ".repeat(Math.max(0, boxWidth - 2 - engLabel.length))}\u2551`;
101
+ // Adapt to small columns by not rendering if too narrow
102
+ if (cols < boxWidth + 4) {
103
+ return renderCompact(cols, engines);
104
+ }
105
+ return [
106
+ "",
107
+ ` \u2554${hBar}\u2557`,
108
+ emptyLine,
109
+ ...logoLines,
110
+ emptyLine,
111
+ tagVersionLine,
112
+ emptyLine,
113
+ ` \u2560${hBar}\u2563`,
114
+ engLabelLine,
115
+ emptyLine,
116
+ ...engineLines,
117
+ emptyLine,
118
+ ` \u255A${hBar}\u255D`,
119
+ "",
120
+ ].join("\n");
121
+ }
122
+ function renderCompact(cols, engines) {
123
+ const VERSION = getVersion();
124
+ const boxWidth = Math.min(cols - 2, 40);
125
+ const hBar = "\u2550".repeat(Math.max(0, boxWidth - 2));
126
+ const engineDots = engines
127
+ .map((e) => {
128
+ const dot = e.version ? e.colorFn("\u25CF") : dim("\u25CB");
129
+ return `${dot} ${e.version ? e.colorFn(e.label) : dim(e.label)}`;
130
+ })
131
+ .join(" ");
132
+ const title = loopGradient("\u25C8 loop");
133
+ const version = bold(VERSION);
134
+ return [
135
+ "",
136
+ ` \u2554${hBar}\u2557`,
137
+ ` \u2551 ${title} ${version}${" ".repeat(Math.max(0, boxWidth - 20))}\u2551`,
138
+ ` \u2551 ${dim("AI orchestration")}${" ".repeat(Math.max(0, boxWidth - 22))}\u2551`,
139
+ ` \u2560${hBar}\u2563`,
140
+ ` \u2551 ${engineDots}${" ".repeat(Math.max(0, boxWidth - 30))}\u2551`,
141
+ ` \u255A${hBar}\u255D`,
142
+ "",
143
+ ].join("\n");
144
+ }
145
+ //# sourceMappingURL=banner.js.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Brand colors and ANSI formatting helpers.
3
+ *
4
+ * Uses raw ANSI escape codes for zero external dependencies.
5
+ */
6
+ /** Claude brand orange (#F07623) */
7
+ export declare const claude: (s: string) => string;
8
+ /** Gemini brand blue (#4285F4) */
9
+ export declare const gemini: (s: string) => string;
10
+ /** Codex brand green (#10A37F) */
11
+ export declare const codex: (s: string) => string;
12
+ /** Loop brand cyan (#00D4FF) */
13
+ export declare const loop: (s: string) => string;
14
+ /** Green for success messages */
15
+ export declare const success: (s: string) => string;
16
+ /** Red for error messages */
17
+ export declare const error: (s: string) => string;
18
+ /** Yellow for warnings */
19
+ export declare const warn: (s: string) => string;
20
+ /** Gray / dim text */
21
+ export declare const dim: (s: string) => string;
22
+ /** Bold text */
23
+ export declare const bold: (s: string) => string;
24
+ export declare const orange: (s: string) => string;
25
+ export declare const gBlue: (s: string) => string;
26
+ export declare const gGreen: (s: string) => string;
27
+ export declare const red: (s: string) => string;
28
+ export declare const green: (s: string) => string;
29
+ export declare const yellow: (s: string) => string;
30
+ export declare const cyan: (s: string) => string;
31
+ export declare const blue: (s: string) => string;
32
+ /**
33
+ * Return the brand-color function for an engine name.
34
+ * Falls back to `loop` cyan for unknown engines.
35
+ */
36
+ export declare function brandColor(engine: string): (s: string) => string;
37
+ /**
38
+ * Format a byte count for human-readable display.
39
+ */
40
+ export declare function formatBytes(bytes: number): string;
41
+ //# sourceMappingURL=colors.d.ts.map