@lawrence369/loop-cli 0.1.1 → 0.2.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,33 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.0] - 2026-03-10
9
+
10
+ ### Fixed
11
+
12
+ - Shared plan async/sync contract mismatch: all plan functions now properly awaited
13
+ - Shared plan field name drift: `reviewerScore`/`reviewerApproved` → `score`/`approved`
14
+ - Agent launcher IPC socket path: `daemon.sock` → `loop.sock` to match daemon
15
+ - Agent launcher IPC message format: aligned to UPPERCASE types + nested `data` object
16
+ - Daemon start/stop/status: real background daemon with PID file and IPC-based status
17
+ - Placeholder IPC handlers (LAUNCH_AGENT, RESUME_AGENTS, LAUNCH_GROUP, STOP_GROUP) now return explicit "not implemented" errors
18
+ - README: config field names, default values, and command list aligned with actual CLI
19
+ - CLI version string now matches package.json
20
+
21
+ ### Added
22
+
23
+ - Multi-turn manual mode: readline-based follow-up prompting between PTY sessions
24
+ - Daemon entry script for proper background process management
25
+ - 33 new regression tests (315 total across 26 test files)
26
+
27
+ ## [0.1.2] - 2026-03-10
28
+
29
+ ### Fixed
30
+
31
+ - Fix `posix_spawnp failed` crash: `@clack/prompts` placeholder text was leaking as actual CLI arguments
32
+ - Add try-catch around PTY spawn with clear error message when engine CLI is not found
33
+ - Add automatic fallback to non-interactive `engine.run()` when PTY spawn fails
34
+
8
35
  ## [0.1.0] - 2026-03-10
9
36
 
10
37
  ### Added
package/README.md CHANGED
@@ -60,15 +60,17 @@ loop [task] # Run iteration loop (interactive if no task)
60
60
  loop daemon start # Start background daemon
61
61
  loop daemon stop # Stop daemon
62
62
  loop daemon status # Check daemon status
63
- loop bus tail # Stream event bus
64
- loop bus stats # Show bus statistics
63
+ loop bus send <message> # Send a message on the event bus
64
+ loop bus check <id> # Check for pending bus messages
65
+ loop bus status # Show event bus status
65
66
  loop chat # Open real-time dashboard
66
67
  loop plan show # Show current iteration plan
67
68
  loop plan clear # Clear plan
68
- loop ctx add # Add architectural decision
69
+ loop ctx add <title> # Add architectural decision
69
70
  loop ctx list # List decisions
71
+ loop ctx resolve <id> # Resolve a decision
70
72
  loop skills list # List available skills
71
- loop skills show <name> # Show skill content
73
+ loop skills add <name> # Add a new skill
72
74
  ```
73
75
 
74
76
  ## Options
@@ -77,12 +79,12 @@ loop skills show <name> # Show skill content
77
79
  |------|-------------|
78
80
  | `-e, --executor <engine>` | Executor engine: `claude` \| `gemini` \| `codex` |
79
81
  | `-r, --reviewer <engine>` | Reviewer engine: `claude` \| `gemini` \| `codex` |
80
- | `-n, --iterations <num>` | Max iterations (default: 5) |
82
+ | `-n, --iterations <num>` | Max iterations (default: 3) |
81
83
  | `-d, --dir <path>` | Working directory |
82
84
  | `-v, --verbose` | Stream real-time output |
83
85
  | `--auto` | Auto mode — skip manual conversation |
84
86
  | `--pass <args...>` | Pass native flags to executor CLI |
85
- | `--threshold <num>` | Approval score threshold, 1-10 (default: 8) |
87
+ | `--threshold <num>` | Approval score threshold, 1-10 (default: 9) |
86
88
 
87
89
  ## How It Works
88
90
 
@@ -104,12 +106,15 @@ Create `.loop/config.json` in your project:
104
106
 
105
107
  ```json
106
108
  {
107
- "executor": "claude",
108
- "reviewer": "gemini",
109
- "maxIterations": 5,
110
- "threshold": 8,
111
- "verbose": false,
112
- "auto": false
109
+ "defaultExecutor": "claude",
110
+ "defaultReviewer": "gemini",
111
+ "maxIterations": 3,
112
+ "threshold": 9,
113
+ "mode": "manual",
114
+ "launchMode": "auto",
115
+ "autoResume": false,
116
+ "skillsDir": ".loop/skills",
117
+ "verbose": false
113
118
  }
114
119
  ```
115
120
 
@@ -29,7 +29,7 @@ function runDir(projectRoot) {
29
29
  return path.join(loopDir(projectRoot), "run");
30
30
  }
31
31
  function daemonSocketPath(projectRoot) {
32
- return path.join(runDir(projectRoot), "daemon.sock");
32
+ return path.join(runDir(projectRoot), "loop.sock");
33
33
  }
34
34
  function connectSocket(sockPath) {
35
35
  return new Promise((resolve, reject) => {
@@ -99,12 +99,13 @@ async function registerWithDaemon(projectRoot, agentType, subscriberId, nickname
99
99
  catch {
100
100
  continue;
101
101
  }
102
- if (payload.type === "register_ok" && typeof payload.subscriberId === "string") {
102
+ const responseData = payload.data;
103
+ if (payload.success === true && responseData?.subscriber_id) {
103
104
  if (settled)
104
105
  return;
105
106
  settled = true;
106
107
  cleanup();
107
- resolve(payload.subscriberId);
108
+ resolve(String(responseData.subscriber_id));
108
109
  return;
109
110
  }
110
111
  if (payload.type === "error") {
@@ -118,10 +119,8 @@ async function registerWithDaemon(projectRoot, agentType, subscriberId, nickname
118
119
  }
119
120
  });
120
121
  const req = {
121
- type: "register_agent",
122
- agentType,
123
- nickname,
124
- parentPid: process.pid,
122
+ type: "REGISTER_AGENT",
123
+ data: { agent_type: agentType, nickname, pid: process.pid },
125
124
  };
126
125
  client.write(JSON.stringify(req) + "\n");
127
126
  });
@@ -197,8 +196,8 @@ export class AgentLauncher {
197
196
  if (!client)
198
197
  return;
199
198
  client.write(JSON.stringify({
200
- type: "agent_ready",
201
- subscriberId,
199
+ type: "AGENT_READY",
200
+ data: { subscriber_id: subscriberId },
202
201
  }) + "\n");
203
202
  client.end();
204
203
  }).catch(() => {
@@ -76,13 +76,20 @@ export class PtySession extends EventEmitter {
76
76
  const rows = opts?.rows ?? process.stdout.rows ?? 24;
77
77
  this._engine = opts?.engine;
78
78
  this._promptPattern = DEFAULT_PROMPT_PATTERN;
79
- this._pty = pty.spawn(command, args, {
80
- name: "xterm-256color",
81
- cols,
82
- rows,
83
- cwd,
84
- env: { ...process.env, ...(opts?.env ?? {}) },
85
- });
79
+ try {
80
+ this._pty = pty.spawn(command, args, {
81
+ name: "xterm-256color",
82
+ cols,
83
+ rows,
84
+ cwd,
85
+ env: { ...process.env, ...(opts?.env ?? {}) },
86
+ });
87
+ }
88
+ catch (err) {
89
+ const msg = err instanceof Error ? err.message : String(err);
90
+ throw new Error(`Failed to spawn PTY for "${command}": ${msg}\n` +
91
+ `Ensure "${command}" is installed and available in your PATH.`);
92
+ }
86
93
  // ── PTY data handler ──────────────────────────────────────────────
87
94
  this._pty.onData((data) => {
88
95
  if (!this._alive)
@@ -4,6 +4,7 @@
4
4
  * Ported from iterloop's conversation.ts. Runs an interactive PTY session
5
5
  * with idle detection, mode toggling, and user-controlled continuation.
6
6
  */
7
+ import * as readline from "node:readline";
7
8
  import { dim, formatBytes, brandColor } from "../ui/colors.js";
8
9
  // ── Idle detection constants ─────────────────────────
9
10
  /** Time to wait after detecting idle prompt before auto-proceeding (ms) */
@@ -109,14 +110,38 @@ async function runPtySession(engine, initialPrompt, opts) {
109
110
  // Set up renderer before spawning the PTY so the first frame is not missed
110
111
  const renderer = new PtyRenderer(engine.name, engine.label);
111
112
  renderer.start();
112
- // Create PTY session via engine.interactive()
113
- const session = engine.interactive({
114
- cwd: opts.cwd,
115
- passthroughArgs: opts.passthroughArgs,
116
- onData(data) {
117
- renderer.write(data);
118
- },
119
- });
113
+ // Create PTY session via engine.interactive(), with fallback to engine.run()
114
+ let session;
115
+ try {
116
+ session = engine.interactive({
117
+ cwd: opts.cwd,
118
+ passthroughArgs: opts.passthroughArgs,
119
+ onData(data) {
120
+ renderer.write(data);
121
+ },
122
+ });
123
+ }
124
+ catch (err) {
125
+ // PTY spawn failed — fall back to non-interactive engine.run()
126
+ const msg = err instanceof Error ? err.message : String(err);
127
+ renderer.stop({ elapsed: "0.0s", bytes: "0 B" });
128
+ console.log(dim(` PTY spawn failed: ${msg}`));
129
+ console.log(dim(` Falling back to non-interactive mode...\n`));
130
+ const start = Date.now();
131
+ const output = await engine.run(initialPrompt, {
132
+ cwd: opts.cwd,
133
+ verbose: opts.verbose,
134
+ passthroughArgs: opts.passthroughArgs,
135
+ onData(chunk) {
136
+ process.stdout.write(chunk);
137
+ },
138
+ });
139
+ return {
140
+ output,
141
+ bytes: Buffer.byteLength(output),
142
+ durationMs: Date.now() - start,
143
+ };
144
+ }
120
145
  return new Promise((resolve, reject) => {
121
146
  let done = false;
122
147
  let idleTimer = null;
@@ -252,6 +277,35 @@ async function runPtySession(engine, initialPrompt, opts) {
252
277
  });
253
278
  });
254
279
  }
280
+ // ── User prompt helper (readline-based) ──────────────
281
+ /**
282
+ * Prompt the user for a follow-up message using Node's readline module.
283
+ * Returns the trimmed user input, or `null` on Ctrl+D / EOF / empty input / "/done".
284
+ */
285
+ function promptUser() {
286
+ return new Promise((resolve) => {
287
+ const rl = readline.createInterface({
288
+ input: process.stdin,
289
+ output: process.stdout,
290
+ });
291
+ rl.question(dim(" Follow-up (empty or /done to submit): "), (answer) => {
292
+ rl.close();
293
+ const trimmed = answer.trim();
294
+ if (trimmed === "" || trimmed === "/done") {
295
+ resolve(null);
296
+ }
297
+ else {
298
+ resolve(trimmed);
299
+ }
300
+ });
301
+ // Handle Ctrl+D (EOF) — readline emits 'close' without calling the callback
302
+ rl.on("close", () => {
303
+ // If the question callback already resolved, this is a no-op.
304
+ // If Ctrl+D was pressed, resolve with null to signal "submit".
305
+ resolve(null);
306
+ });
307
+ });
308
+ }
255
309
  // ── Main conversation loop ───────────────────────────
256
310
  /**
257
311
  * Run a multi-turn conversation with an AI engine.
@@ -268,22 +322,53 @@ export async function runConversation(opts) {
268
322
  console.log(dim(" Ctrl+D to submit for review, double Ctrl+C to abort\n"));
269
323
  }
270
324
  // Run first interactive PTY session
271
- const { output, bytes, durationMs } = await runPtySession(engine, initialPrompt, { cwd, verbose, mode, passthroughArgs });
272
- // Auto mode: done, submit to reviewer
325
+ let currentPrompt = initialPrompt;
326
+ let accumulatedOutput = "";
327
+ let totalBytes = 0;
328
+ let totalDurationMs = 0;
329
+ const firstResult = await runPtySession(engine, currentPrompt, {
330
+ cwd,
331
+ verbose,
332
+ mode,
333
+ passthroughArgs,
334
+ });
335
+ accumulatedOutput += firstResult.output;
336
+ totalBytes += firstResult.bytes;
337
+ totalDurationMs += firstResult.durationMs;
338
+ // Auto mode (or switched to auto during first session): done, submit to reviewer
273
339
  if (mode.current === "auto") {
274
340
  return {
275
- finalOutput: output,
276
- duration_ms: durationMs,
277
- bytes_received: bytes,
341
+ finalOutput: accumulatedOutput,
342
+ duration_ms: totalDurationMs,
343
+ bytes_received: totalBytes,
278
344
  };
279
345
  }
280
- // Manual mode: the user can continue interacting or submit
281
- // For now, return the first session output. Full multi-turn manual flow
282
- // (promptUser loop) is handled at a higher layer.
346
+ // Manual mode: multi-turn loop — prompt user after each PTY session
347
+ while (mode.current === "manual") {
348
+ console.log(dim(` Session complete. Accumulated ${formatBytes(totalBytes)} over ${(totalDurationMs / 1000).toFixed(1)}s.`));
349
+ const followUp = await promptUser();
350
+ // null means user wants to submit (empty input, /done, or Ctrl+D)
351
+ if (followUp === null) {
352
+ break;
353
+ }
354
+ // Run another PTY session with the follow-up prompt
355
+ currentPrompt = followUp;
356
+ const result = await runPtySession(engine, currentPrompt, {
357
+ cwd,
358
+ verbose,
359
+ mode,
360
+ passthroughArgs,
361
+ });
362
+ accumulatedOutput += "\n" + result.output;
363
+ totalBytes += result.bytes;
364
+ totalDurationMs += result.durationMs;
365
+ // If the mode was switched to auto during the session (via Shift+Tab),
366
+ // the while condition will handle it — no explicit break needed.
367
+ }
283
368
  return {
284
- finalOutput: output,
285
- duration_ms: durationMs,
286
- bytes_received: bytes,
369
+ finalOutput: accumulatedOutput,
370
+ duration_ms: totalDurationMs,
371
+ bytes_received: totalBytes,
287
372
  };
288
373
  }
289
374
  //# sourceMappingURL=conversation.js.map
package/dist/core/loop.js CHANGED
@@ -12,10 +12,10 @@ import { createExecutorMessage, parseReviewerOutput, formatForReviewer, } from "
12
12
  import { evaluateReview } from "./scoring.js";
13
13
  import { bold, dim, success, warn, brandColor } from "../ui/colors.js";
14
14
  const NO_OP_PLAN = {
15
- initSharedPlan: () => { },
16
- updateSharedPlan: () => { },
17
- getExecutorContext: () => "",
18
- getReviewerContext: () => "",
15
+ initSharedPlan: async () => { },
16
+ updateSharedPlan: async () => { },
17
+ getExecutorContext: async () => "",
18
+ getReviewerContext: async () => "",
19
19
  };
20
20
  // Lazy-loaded promise — awaited before first use in runLoop()
21
21
  let _planModulePromise = null;
@@ -67,7 +67,7 @@ export async function runLoop(options) {
67
67
  };
68
68
  // Load shared plan module (awaited — no race condition)
69
69
  const plan = await loadPlanModule();
70
- plan.initSharedPlan(cwd, task);
70
+ await plan.initSharedPlan(cwd, task);
71
71
  let executorOutput = "";
72
72
  let reviewerFeedback = "";
73
73
  for (let i = 1; i <= maxIterations; i++) {
@@ -78,7 +78,7 @@ export async function runLoop(options) {
78
78
  initialPrompt = task;
79
79
  }
80
80
  else {
81
- const executorContext = plan.getExecutorContext(cwd);
81
+ const executorContext = await plan.getExecutorContext(cwd);
82
82
  initialPrompt = [
83
83
  "Please revise your previous work based on the following review feedback.",
84
84
  "",
@@ -119,7 +119,7 @@ export async function runLoop(options) {
119
119
  });
120
120
  history.push(executorMsg);
121
121
  // ── Reviewer ──
122
- const reviewerContext = plan.getReviewerContext(cwd);
122
+ const reviewerContext = await plan.getReviewerContext(cwd);
123
123
  const reviewPrompt = [
124
124
  "You are a code review expert. Please review the following task completion.",
125
125
  "",
@@ -169,14 +169,14 @@ export async function runLoop(options) {
169
169
  // Evaluate review using scoring module
170
170
  const scoringResult = evaluateReview(reviewerMsg.review, scoringConfig);
171
171
  // Update shared plan with iteration data
172
- plan.updateSharedPlan(cwd, {
172
+ await plan.updateSharedPlan(cwd, {
173
173
  iteration: i,
174
174
  timestamp: new Date().toISOString(),
175
175
  executor: executor.name,
176
176
  reviewer: reviewer.name,
177
177
  executorSummary: executorOutput.slice(0, 500),
178
- reviewerScore: reviewerMsg.review?.score ?? 0,
179
- reviewerApproved: scoringResult.approved,
178
+ score: reviewerMsg.review?.score ?? 0,
179
+ approved: scoringResult.approved,
180
180
  reviewerFeedback: reviewerFeedback.slice(0, 500),
181
181
  }, executorMsg.output.files_changed);
182
182
  // ── Check approval ──
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
3
  import { existsSync } from "node:fs";
4
- import { resolve } from "node:path";
4
+ import { resolve, join, dirname } from "node:path";
5
5
  import { bold, red, yellow, green } from "./ui/colors.js";
6
6
  import { ENGINE_NAMES } from "./config/schema.js";
7
7
  import { loadConfig } from "./config/index.js";
@@ -12,12 +12,14 @@ import { SkillRegistry } from "./skills/registry.js";
12
12
  import { runLoop } from "./core/loop.js";
13
13
  import { createEngine } from "./core/engine.js";
14
14
  import { EventBus } from "./bus/event-bus.js";
15
- import { OrchestratorDaemon } from "./orchestrator/daemon.js";
15
+ import { daemonize, readPidFile, isProcessAlive, removePidFile, } from "./utils/process.js";
16
+ import { createConnection } from "node:net";
17
+ import { fileURLToPath } from "node:url";
16
18
  const program = new Command();
17
19
  program
18
20
  .name("loop")
19
21
  .description("Iterative multi-engine AI orchestration CLI — Claude, Gemini, Codex")
20
- .version("0.1.0")
22
+ .version("0.2.0")
21
23
  .argument("[task]", "Task description (omit to enter interactive mode)")
22
24
  .option("-e, --executor <engine>", "Executor engine: claude | gemini | codex")
23
25
  .option("-r, --reviewer <engine>", "Reviewer engine: claude | gemini | codex")
@@ -124,10 +126,22 @@ daemon
124
126
  .action(async () => {
125
127
  try {
126
128
  const cwd = process.cwd();
127
- const mgr = new OrchestratorDaemon(cwd);
129
+ const runDirPath = join(cwd, ".loop", "run");
130
+ const pidPath = join(runDirPath, "loop-daemon.pid");
131
+ const logPath = join(runDirPath, "loop-daemon.log");
132
+ // Check if already running
133
+ const existingPid = readPidFile(pidPath);
134
+ if (existingPid !== null && isProcessAlive(existingPid)) {
135
+ console.log(yellow(` Daemon already running (pid=${existingPid})`));
136
+ return;
137
+ }
138
+ // Resolve daemon entry script relative to this compiled module
139
+ const thisFile = fileURLToPath(import.meta.url);
140
+ const thisDir = dirname(thisFile);
141
+ const entryScript = join(thisDir, "orchestrator", "daemon-entry.js");
128
142
  console.log(bold(" Starting daemon..."));
129
- await mgr.start();
130
- console.log(green(" Daemon started (pid=" + process.pid + ")"));
143
+ const pid = daemonize(entryScript, [], logPath);
144
+ console.log(green(` Daemon started (pid=${pid})`));
131
145
  }
132
146
  catch (err) {
133
147
  const msg = err instanceof Error ? err.message : String(err);
@@ -141,13 +155,17 @@ daemon
141
155
  .action(async () => {
142
156
  try {
143
157
  const cwd = process.cwd();
144
- const mgr = new OrchestratorDaemon(cwd);
145
- if (!mgr.isRunning()) {
158
+ const pidPath = join(cwd, ".loop", "run", "loop-daemon.pid");
159
+ const pid = readPidFile(pidPath);
160
+ if (pid === null || !isProcessAlive(pid)) {
146
161
  console.log(yellow(" Daemon is not running."));
162
+ removePidFile(pidPath);
147
163
  return;
148
164
  }
149
- await mgr.stop();
150
- console.log(green(" Daemon stopped."));
165
+ process.kill(pid, "SIGTERM");
166
+ console.log(green(` Daemon stopped (pid=${pid}).`));
167
+ // Clean up PID file after kill
168
+ removePidFile(pidPath);
151
169
  }
152
170
  catch (err) {
153
171
  const msg = err instanceof Error ? err.message : String(err);
@@ -161,17 +179,53 @@ daemon
161
179
  .action(async () => {
162
180
  try {
163
181
  const cwd = process.cwd();
164
- const mgr = new OrchestratorDaemon(cwd);
165
- if (!mgr.isRunning()) {
182
+ const pidPath = join(cwd, ".loop", "run", "loop-daemon.pid");
183
+ const socketPath = join(cwd, ".loop", "run", "loop.sock");
184
+ const pid = readPidFile(pidPath);
185
+ if (pid === null || !isProcessAlive(pid)) {
166
186
  console.log(yellow(" Daemon is not running."));
167
187
  return;
168
188
  }
169
- const status = await mgr.getStatus();
189
+ // Connect to the daemon's IPC socket and request STATUS
190
+ const status = await new Promise((resolvePromise, rejectPromise) => {
191
+ const client = createConnection(socketPath, () => {
192
+ client.write(JSON.stringify({ type: "STATUS", data: {} }) + "\n");
193
+ });
194
+ let buffer = "";
195
+ const timeout = setTimeout(() => {
196
+ client.destroy();
197
+ rejectPromise(new Error("Timed out waiting for daemon status"));
198
+ }, 5_000);
199
+ client.on("data", (data) => {
200
+ buffer += data.toString("utf8");
201
+ const lines = buffer.split("\n");
202
+ buffer = lines.pop() ?? "";
203
+ for (const line of lines) {
204
+ if (!line.trim())
205
+ continue;
206
+ try {
207
+ const payload = JSON.parse(line);
208
+ clearTimeout(timeout);
209
+ client.end();
210
+ resolvePromise(payload);
211
+ return;
212
+ }
213
+ catch {
214
+ // skip malformed lines
215
+ }
216
+ }
217
+ });
218
+ client.on("error", (err) => {
219
+ clearTimeout(timeout);
220
+ rejectPromise(err);
221
+ });
222
+ });
223
+ const statusData = (status.data ?? {});
170
224
  console.log(bold(" Daemon status:"));
171
- console.log(` PID: ${status.pid}`);
172
- console.log(` Uptime: ${status.uptime}s`);
173
- console.log(` Agents: ${status.agents}`);
174
- console.log(` Events: ${status.busEvents}`);
225
+ console.log(` PID: ${statusData.pid ?? pid}`);
226
+ console.log(` Uptime: ${statusData.uptime ?? "?"}s`);
227
+ console.log(` Agents: ${statusData.agents ?? 0}`);
228
+ console.log(` Events: ${statusData.busEvents ?? 0}`);
175
229
  }
176
230
  catch (err) {
177
231
  const msg = err instanceof Error ? err.message : String(err);
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Daemon entry point — spawned as a detached background process by
4
+ * `loop daemon start`. Creates an OrchestratorDaemon and starts it.
5
+ * The IPC server keeps the process alive.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=daemon-entry.d.ts.map
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Daemon entry point — spawned as a detached background process by
4
+ * `loop daemon start`. Creates an OrchestratorDaemon and starts it.
5
+ * The IPC server keeps the process alive.
6
+ */
7
+ import { OrchestratorDaemon } from "./daemon.js";
8
+ const projectRoot = process.cwd();
9
+ const daemon = new OrchestratorDaemon(projectRoot);
10
+ daemon.start().catch((err) => {
11
+ const message = err instanceof Error ? err.message : String(err);
12
+ process.stderr.write(`daemon-entry: failed to start: ${message}\n`);
13
+ process.exit(1);
14
+ });
15
+ //# sourceMappingURL=daemon-entry.js.map
@@ -211,11 +211,10 @@ export class OrchestratorDaemon {
211
211
  };
212
212
  }
213
213
  case "LAUNCH_AGENT": {
214
- // Placeholder - actual agent launching is handled by the agent launcher
215
214
  return {
216
- success: true,
215
+ success: false,
217
216
  type: "LAUNCH_AGENT",
218
- data: { message: "Agent launch request received" },
217
+ error: "Not implemented",
219
218
  };
220
219
  }
221
220
  case "CLOSE_AGENT": {
@@ -229,23 +228,23 @@ export class OrchestratorDaemon {
229
228
  }
230
229
  case "RESUME_AGENTS": {
231
230
  return {
232
- success: true,
231
+ success: false,
233
232
  type: "RESUME_AGENTS",
234
- data: { message: "Resume request received" },
233
+ error: "Not implemented",
235
234
  };
236
235
  }
237
236
  case "LAUNCH_GROUP": {
238
237
  return {
239
- success: true,
238
+ success: false,
240
239
  type: "LAUNCH_GROUP",
241
- data: { message: "Group launch request received" },
240
+ error: "Not implemented",
242
241
  };
243
242
  }
244
243
  case "STOP_GROUP": {
245
244
  return {
246
- success: true,
245
+ success: false,
247
246
  type: "STOP_GROUP",
248
- data: { message: "Group stop request received" },
247
+ error: "Not implemented",
249
248
  };
250
249
  }
251
250
  default: {
@@ -93,17 +93,20 @@ export async function interactive() {
93
93
  return null;
94
94
  }
95
95
  // Native CLI flags (optional)
96
+ const PASS_ARGS_PLACEHOLDER = "e.g., --model claude-sonnet-4-20250514";
96
97
  const passArgsInput = await p.text({
97
98
  message: "Native CLI flags for executor (optional)",
98
- placeholder: "e.g., --model claude-sonnet-4-20250514",
99
+ placeholder: PASS_ARGS_PLACEHOLDER,
99
100
  defaultValue: "",
100
101
  });
101
102
  if (p.isCancel(passArgsInput)) {
102
103
  p.cancel("Cancelled.");
103
104
  return null;
104
105
  }
105
- const passthroughArgs = passArgsInput.trim()
106
- ? passArgsInput.split(/\s+/).filter(Boolean)
106
+ // Guard against @clack/prompts returning placeholder text as the value
107
+ const rawPassArgs = typeof passArgsInput === "string" ? passArgsInput : "";
108
+ const passthroughArgs = rawPassArgs.trim() && rawPassArgs.trim() !== PASS_ARGS_PLACEHOLDER
109
+ ? rawPassArgs.split(/\s+/).filter(Boolean)
107
110
  : [];
108
111
  // Task
109
112
  const task = await p.text({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lawrence369/loop-cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Iterative Multi-Engine AI Orchestration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",