arcie 0.1.8 → 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.
@@ -25,10 +25,12 @@ import {
25
25
  } from "./chunk-SGWQZZ2A.js";
26
26
  import {
27
27
  streamAgent
28
- } from "./chunk-4WSILP75.js";
28
+ } from "./chunk-3R65GQAE.js";
29
29
  import {
30
- loadAgent
31
- } from "./chunk-KVSX4MLK.js";
30
+ discoverAgents,
31
+ loadAgent,
32
+ loadAgentById
33
+ } from "./chunk-XZPC3JSU.js";
32
34
  import {
33
35
  discoverAgent
34
36
  } from "./chunk-6XETPLIF.js";
@@ -36,8 +38,10 @@ import "./chunk-QE46QFID.js";
36
38
  import "./chunk-PHTJLJOV.js";
37
39
 
38
40
  // src/cli/dev.ts
41
+ import { spawn } from "child_process";
39
42
  import { createServer } from "http";
40
- import { resolve } from "path";
43
+ import { existsSync as existsSync2 } from "fs";
44
+ import { join as join2, resolve } from "path";
41
45
 
42
46
  // src/cli/tui/renderer/start-block-chat.ts
43
47
  import { watch } from "fs";
@@ -769,6 +773,8 @@ function renderBody(block, width, theme, context) {
769
773
  return renderWarning(block, width, theme);
770
774
  case "result":
771
775
  return renderResult(block, width, theme);
776
+ case "command":
777
+ return renderCommand(block, theme);
772
778
  case "subagent":
773
779
  return renderSubagentHeader(block, width, theme);
774
780
  case "agent-header":
@@ -931,6 +937,11 @@ function paintCommands(line, theme) {
931
937
  (token) => isPromptControlCommand(token) ? theme.colors.blue(token) : token
932
938
  );
933
939
  }
940
+ function renderCommand(block, theme) {
941
+ const c = theme.colors;
942
+ const status = block.status === "error" ? `${c.red(theme.glyph.error)} ` : "";
943
+ return [`${c.cyan(theme.glyph.user)} ${status}${c.blue(block.body ?? "")}`];
944
+ }
934
945
  function renderResult(block, width, theme) {
935
946
  const marker = theme.colors.dim(theme.glyph.elbow);
936
947
  const lines = wrap(block.body ?? "", width - 7);
@@ -1005,6 +1016,24 @@ var TerminalRenderer = class {
1005
1016
  writeAgentHeader(body) {
1006
1017
  this.apply([{ type: "commit", block: { kind: "agent-header", body } }]);
1007
1018
  }
1019
+ /**
1020
+ * Echoes a slash-command invocation to scrollback so it chains with the
1021
+ * transcript above (blue `▌ /model`) and the {@link writeCommandResult}
1022
+ * that hangs beneath it can share visual context.
1023
+ */
1024
+ writeCommandInvocation(text, status) {
1025
+ const block = { kind: "command", body: text };
1026
+ if (status === "error") block.status = "error";
1027
+ this.apply([{ type: "commit", block }]);
1028
+ }
1029
+ /**
1030
+ * Writes a command outcome hung under the previous invocation with the
1031
+ * elbow connector (`⎿ message`). Multi-line messages soft-wrap under the
1032
+ * elbow.
1033
+ */
1034
+ writeCommandResult(text) {
1035
+ this.apply([{ type: "commit", block: { kind: "result", body: text } }]);
1036
+ }
1008
1037
  readPrompt() {
1009
1038
  return new Promise((resolve2) => {
1010
1039
  this.#pendingResolve = resolve2;
@@ -1232,28 +1261,35 @@ async function startBlockChat(options) {
1232
1261
  if (trimmed.startsWith("/")) {
1233
1262
  const command = parsePromptCommand(trimmed);
1234
1263
  if (command === null) {
1235
- renderer.writeError("Unknown command", `${trimmed} \u2014 try /help`);
1264
+ renderer.writeCommandInvocation(trimmed, "error");
1265
+ renderer.writeCommandResult("Unknown command \u2014 try /help");
1236
1266
  continue;
1237
1267
  }
1268
+ if (command.type === "exit") {
1269
+ renderer.writeCommandInvocation(trimmed);
1270
+ return;
1271
+ }
1272
+ renderer.writeCommandInvocation(trimmed);
1238
1273
  switch (command.type) {
1239
- case "exit":
1240
- return;
1241
1274
  case "help":
1242
- for (const line of formatPromptCommandHelp().split("\n")) {
1243
- renderer.writeNotice(line);
1244
- }
1275
+ renderer.writeCommandResult(formatPromptCommandHelp());
1245
1276
  continue;
1246
1277
  case "clear":
1247
1278
  case "new":
1248
1279
  for (const op of translator.reset()) renderer.apply([op]);
1249
1280
  await commitHeader();
1250
1281
  continue;
1251
- case "extension":
1252
- await handleExtension(command, renderer, agentDir, commitHeader);
1282
+ case "extension": {
1283
+ const outcome = await handleExtension(command, agentDir);
1284
+ if (outcome.message.length > 0) renderer.writeCommandResult(outcome.message);
1285
+ if (outcome.refresh) await commitHeader();
1253
1286
  continue;
1254
- case "loglevel":
1255
- handleLogLevel(command, renderer, logLevelState);
1287
+ }
1288
+ case "loglevel": {
1289
+ const outcome = handleLogLevel(command, logLevelState);
1290
+ renderer.writeCommandResult(outcome.message);
1256
1291
  continue;
1292
+ }
1257
1293
  }
1258
1294
  continue;
1259
1295
  }
@@ -1265,120 +1301,122 @@ async function startBlockChat(options) {
1265
1301
  renderer.stop();
1266
1302
  }
1267
1303
  }
1268
- async function handleExtension(command, renderer, agentDir, commitHeader) {
1304
+ async function handleExtension(command, agentDir) {
1269
1305
  const argument = command.argument.trim();
1270
1306
  switch (command.name) {
1271
1307
  case "model":
1272
- await handleModel(argument, renderer, agentDir, commitHeader);
1273
- return;
1308
+ return handleModel(argument, agentDir);
1274
1309
  case "provider":
1275
- handleProvider(renderer, agentDir);
1276
- return;
1310
+ return handleProvider(agentDir);
1277
1311
  case "channels":
1278
- handleChannels(argument, renderer, agentDir);
1279
- return;
1312
+ return handleChannels(argument, agentDir);
1280
1313
  default:
1281
- renderer.writeNotice(`/${command.name} is not supported here`);
1314
+ return { message: `/${command.name} is not supported here` };
1282
1315
  }
1283
1316
  }
1284
- async function handleModel(argument, renderer, agentDir, commitHeader) {
1317
+ function handleModel(argument, agentDir) {
1285
1318
  const current = readAgentModel(agentDir);
1286
1319
  if (argument.length === 0) {
1287
- renderer.writeNotice(
1288
- current ? `current model: ${current}` : "no model configured in agent/agent.ts"
1289
- );
1290
- renderer.writeNotice("change it with: /model <provider/slug>");
1291
- return;
1320
+ const lines = [
1321
+ current ? `Current model: ${current}` : "No model configured in agent/agent.ts",
1322
+ "Change with: /model <provider/slug>"
1323
+ ];
1324
+ return { message: lines.join("\n") };
1292
1325
  }
1293
1326
  if (current === argument) {
1294
- renderer.writeNotice(`model is already ${argument}`);
1295
- return;
1327
+ return { message: `Model is already ${argument}` };
1296
1328
  }
1297
1329
  const changed = writeAgentModel(agentDir, argument);
1298
1330
  if (!changed) {
1299
- renderer.writeError(
1300
- "Model not changed",
1301
- "Could not update agent/agent.ts \u2014 file missing or model field not found."
1302
- );
1303
- return;
1331
+ return {
1332
+ message: "Could not update agent/agent.ts \u2014 file missing or model field not found."
1333
+ };
1304
1334
  }
1305
- renderer.writeNotice(`model set to ${argument}`);
1306
- await commitHeader();
1335
+ return { message: `Model set to ${argument}`, refresh: true };
1307
1336
  }
1308
- function handleProvider(renderer, agentDir) {
1337
+ function handleProvider(agentDir) {
1309
1338
  const status = providerKeyStatus(agentDir);
1310
1339
  const configured = status.filter((row) => row.set);
1311
1340
  if (configured.length === 0) {
1312
- renderer.writeNotice("no provider keys configured");
1313
- renderer.writeNotice("edit .env.local to add CENCORI_API_KEY or a direct provider key");
1314
- return;
1341
+ return {
1342
+ message: [
1343
+ "No provider keys configured.",
1344
+ "Edit .env.local to add CENCORI_API_KEY or a direct provider key."
1345
+ ].join("\n")
1346
+ };
1315
1347
  }
1316
- renderer.writeNotice("provider keys:");
1348
+ const lines = ["Provider keys:"];
1317
1349
  for (const row of status) {
1318
1350
  if (row.set) {
1319
- renderer.writeNotice(` ${row.key} = ${row.masked} (${row.source})`);
1351
+ lines.push(` ${row.key} = ${row.masked} (${row.source})`);
1320
1352
  } else {
1321
- renderer.writeNotice(` ${row.key} \u2014 not set`);
1353
+ lines.push(` ${row.key} \u2014 not set`);
1322
1354
  }
1323
1355
  }
1356
+ return { message: lines.join("\n") };
1324
1357
  }
1325
- function handleChannels(argument, renderer, agentDir) {
1358
+ function handleChannels(argument, agentDir) {
1326
1359
  if (argument.length === 0) {
1327
1360
  const channels = listChannels(agentDir);
1328
1361
  if (channels.length === 0) {
1329
- renderer.writeNotice("no channels scaffolded");
1330
- renderer.writeNotice("add one with: /channels add web");
1331
- return;
1362
+ return {
1363
+ message: ["No channels scaffolded.", "Add one with: /channels add web"].join("\n")
1364
+ };
1332
1365
  }
1333
- renderer.writeNotice("channels:");
1334
- for (const channel of channels) renderer.writeNotice(` ${channel.name} \u2014 ${channel.path}`);
1335
- renderer.writeNotice("add another with: /channels add <kind>");
1336
- return;
1366
+ const lines = ["Channels:"];
1367
+ for (const channel of channels) lines.push(` ${channel.name} \u2014 ${channel.path}`);
1368
+ lines.push("Add another with: /channels add <kind>");
1369
+ return { message: lines.join("\n") };
1337
1370
  }
1338
1371
  const parts = argument.split(/\s+/);
1339
1372
  const subcommand = parts[0];
1340
1373
  const kind = parts[1];
1341
1374
  if (subcommand !== "add") {
1342
- renderer.writeError("Unknown /channels subcommand", `try: /channels add web`);
1343
- return;
1375
+ return { message: `Unknown subcommand '${subcommand}' \u2014 try: /channels add web` };
1344
1376
  }
1345
1377
  if (kind !== "web") {
1346
- renderer.writeError("Unsupported channel kind", `${kind ?? "(none)"} \u2014 only 'web' is supported`);
1347
- return;
1378
+ return {
1379
+ message: `Unsupported channel kind '${kind ?? ""}' \u2014 only 'web' is supported`
1380
+ };
1348
1381
  }
1349
1382
  try {
1350
1383
  const result = scaffoldWebChat(agentDir);
1351
1384
  if (result.alreadyExisted) {
1352
- renderer.writeNotice(`channels/web already exists \u2014 left it untouched`);
1353
- return;
1385
+ return { message: `channels/web already exists at ${result.targetPath}` };
1354
1386
  }
1355
- renderer.writeNotice(`scaffolded ${result.targetPath}`);
1356
- renderer.writeNotice("next: cd into it, npm install, npm run dev");
1387
+ return {
1388
+ message: [
1389
+ `Scaffolded ${result.targetPath}`,
1390
+ "Next: cd into it, npm install, npm run dev"
1391
+ ].join("\n")
1392
+ };
1357
1393
  } catch (err) {
1358
- renderer.writeError(
1359
- "Channel scaffold failed",
1360
- err instanceof Error ? err.message : String(err)
1361
- );
1394
+ return {
1395
+ message: `Channel scaffold failed: ${err instanceof Error ? err.message : String(err)}`
1396
+ };
1362
1397
  }
1363
1398
  }
1364
- function handleLogLevel(command, renderer, state) {
1399
+ function handleLogLevel(command, state) {
1365
1400
  const argument = command.argument.trim();
1366
1401
  if (argument.length === 0) {
1367
- renderer.writeNotice(`log level: ${state.mode}`);
1368
- renderer.writeNotice("change with: /loglevel all | stderr | none");
1369
- renderer.writeNotice("note: arcie does not currently capture subprocess logs.");
1370
- return;
1402
+ return {
1403
+ message: [
1404
+ `Log level: ${state.mode}`,
1405
+ "Change with: /loglevel all | stderr | none",
1406
+ "Note: arcie does not currently capture subprocess logs."
1407
+ ].join("\n")
1408
+ };
1371
1409
  }
1372
1410
  if (!VALID_LOG_MODES.has(argument)) {
1373
- renderer.writeError(
1374
- "Invalid log level",
1375
- `${argument} \u2014 pick one of: all, stderr, none`
1376
- );
1377
- return;
1411
+ return { message: `Invalid log level '${argument}' \u2014 pick one of: all, stderr, none` };
1378
1412
  }
1379
1413
  state.mode = argument;
1380
- renderer.writeNotice(`log level: ${state.mode}`);
1381
- renderer.writeNotice("note: arcie does not currently capture subprocess logs.");
1414
+ return {
1415
+ message: [
1416
+ `Log level: ${state.mode}`,
1417
+ "Note: arcie does not currently capture subprocess logs."
1418
+ ].join("\n")
1419
+ };
1382
1420
  }
1383
1421
  async function streamOneTurn(renderer, translator, agentDir, message) {
1384
1422
  let sawApproval = false;
@@ -2094,42 +2132,165 @@ async function listenWithFallback(server, startPort) {
2094
2132
  `Could not find a free port in ${startPort}..${startPort + MAX_PORT_ATTEMPTS - 1}`
2095
2133
  );
2096
2134
  }
2135
+ function isPortFree(port) {
2136
+ return new Promise((resolve2) => {
2137
+ const probe = createServer();
2138
+ probe.once("error", () => resolve2(false));
2139
+ probe.once("listening", () => {
2140
+ probe.close(() => resolve2(true));
2141
+ });
2142
+ probe.listen(port);
2143
+ });
2144
+ }
2145
+ async function findFreePort(startPort, maxAttempts = MAX_PORT_ATTEMPTS) {
2146
+ for (let offset = 0; offset < maxAttempts; offset += 1) {
2147
+ const port = startPort + offset;
2148
+ if (await isPortFree(port)) return port;
2149
+ }
2150
+ throw new Error(`No free port in ${startPort}..${startPort + maxAttempts - 1}`);
2151
+ }
2152
+ function openBrowser(url) {
2153
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
2154
+ try {
2155
+ const child = spawn(command, [url], {
2156
+ detached: true,
2157
+ stdio: "ignore",
2158
+ shell: process.platform === "win32"
2159
+ });
2160
+ child.unref();
2161
+ } catch {
2162
+ }
2163
+ }
2164
+ async function waitForHttp(url, timeoutMs = 3e4) {
2165
+ const deadline = Date.now() + timeoutMs;
2166
+ while (Date.now() < deadline) {
2167
+ try {
2168
+ const res = await fetch(url, { method: "GET" });
2169
+ if (res.status < 500) return true;
2170
+ } catch {
2171
+ }
2172
+ await new Promise((r) => setTimeout(r, 500));
2173
+ }
2174
+ return false;
2175
+ }
2176
+ async function installWebDeps(webDir) {
2177
+ return new Promise((resolve2) => {
2178
+ const child = spawn("npm", ["install", "--no-fund", "--no-audit"], {
2179
+ cwd: webDir,
2180
+ stdio: "ignore"
2181
+ });
2182
+ child.once("exit", (code) => resolve2(code === 0));
2183
+ child.once("error", () => resolve2(false));
2184
+ });
2185
+ }
2186
+ async function startWebChannel(agentDir, arcieUrl, webPort) {
2187
+ const webDir = join2(agentDir, "channels", "web");
2188
+ if (!existsSync2(join2(webDir, "package.json"))) return void 0;
2189
+ if (!existsSync2(join2(webDir, "node_modules"))) {
2190
+ console.log(` ${dimmed(`web installing channels/web deps (first run)\u2026`)}`);
2191
+ const installed = await installWebDeps(webDir);
2192
+ if (!installed) {
2193
+ console.log();
2194
+ console.log(` ${grey("\u26A0")} channels/web install failed \u2014 try manually:`);
2195
+ console.log(` ${dimmed(` cd ${webDir} && npm install`)}`);
2196
+ console.log();
2197
+ return void 0;
2198
+ }
2199
+ }
2200
+ const child = spawn("npm", ["run", "dev", "--", "--port", String(webPort)], {
2201
+ cwd: webDir,
2202
+ env: { ...process.env, ARCIE_URL: arcieUrl },
2203
+ stdio: "ignore",
2204
+ detached: false
2205
+ });
2206
+ const url = `http://localhost:${webPort}`;
2207
+ const ready = await waitForHttp(url, 45e3);
2208
+ if (!ready) {
2209
+ console.log();
2210
+ console.log(` ${grey("\u26A0")} channels/web didn't come up within 45s`);
2211
+ child.kill();
2212
+ return void 0;
2213
+ }
2214
+ return { url, process: child };
2215
+ }
2097
2216
  async function devCommand(options) {
2098
2217
  const agentDirPath = resolve(process.cwd(), options.agentDir);
2099
2218
  const requestedPort = parseInt(options.port, 10);
2219
+ const streamTurn = async (res, body, agentId) => {
2220
+ try {
2221
+ const { message, stream } = JSON.parse(body);
2222
+ if (typeof message !== "string" || message.length === 0) {
2223
+ res.writeHead(400, { "Content-Type": "application/json" });
2224
+ res.end(JSON.stringify({ error: "message is required" }));
2225
+ return;
2226
+ }
2227
+ if (stream) {
2228
+ res.writeHead(200, {
2229
+ "Content-Type": "application/x-ndjson",
2230
+ "Cache-Control": "no-cache",
2231
+ Connection: "keep-alive"
2232
+ });
2233
+ const runOpts = agentId !== void 0 ? { agentId } : {};
2234
+ for await (const event of streamAgent(agentDirPath, message, runOpts)) {
2235
+ res.write(JSON.stringify(event) + "\n");
2236
+ }
2237
+ res.end();
2238
+ } else {
2239
+ res.writeHead(200, { "Content-Type": "application/json" });
2240
+ const runOpts = agentId !== void 0 ? { agentId } : {};
2241
+ for await (const _event of streamAgent(agentDirPath, message, runOpts)) {
2242
+ }
2243
+ res.end(JSON.stringify({ status: "ok" }));
2244
+ }
2245
+ } catch (err) {
2246
+ res.writeHead(500, { "Content-Type": "application/json" });
2247
+ res.end(JSON.stringify({ error: String(err) }));
2248
+ }
2249
+ };
2250
+ const listAgents = async () => {
2251
+ const discovered = discoverAgents(agentDirPath);
2252
+ const summaries = await Promise.all(
2253
+ discovered.map(async ({ id }) => {
2254
+ try {
2255
+ const loaded = await loadAgentById(agentDirPath, id);
2256
+ const { config } = loaded.manifest;
2257
+ return {
2258
+ id,
2259
+ name: config.name ?? id,
2260
+ model: config.model,
2261
+ description: config.description ?? ""
2262
+ };
2263
+ } catch {
2264
+ return { id, name: id, model: "", description: "" };
2265
+ }
2266
+ })
2267
+ );
2268
+ return summaries;
2269
+ };
2100
2270
  const server = createServer(async (req, res) => {
2101
- if (await handleSessionsRequest(req, res)) {
2271
+ if (await handleSessionsRequest(req, res)) return;
2272
+ const method = req.method ?? "GET";
2273
+ const url = req.url ?? "/";
2274
+ if (method === "GET" && url === "/agents") {
2275
+ res.writeHead(200, { "Content-Type": "application/json" });
2276
+ res.end(JSON.stringify(await listAgents()));
2102
2277
  return;
2103
2278
  }
2104
- if (req.method === "POST" && req.url === "/") {
2279
+ const agentsMatch = url.match(/^\/agents\/([^/?]+)(?:\?.*)?$/);
2280
+ if (method === "POST" && agentsMatch !== null) {
2105
2281
  let body = "";
2106
2282
  for await (const chunk of req) body += chunk;
2107
- try {
2108
- const { message, stream } = JSON.parse(body);
2109
- if (stream) {
2110
- res.writeHead(200, {
2111
- "Content-Type": "application/x-ndjson",
2112
- "Cache-Control": "no-cache",
2113
- Connection: "keep-alive"
2114
- });
2115
- for await (const event of streamAgent(agentDirPath, message)) {
2116
- res.write(JSON.stringify(event) + "\n");
2117
- }
2118
- res.end();
2119
- } else {
2120
- res.writeHead(200, { "Content-Type": "application/json" });
2121
- for await (const _event of streamAgent(agentDirPath, message)) {
2122
- }
2123
- res.end(JSON.stringify({ status: "ok" }));
2124
- }
2125
- } catch (err) {
2126
- res.writeHead(500, { "Content-Type": "application/json" });
2127
- res.end(JSON.stringify({ error: String(err) }));
2128
- }
2129
- } else {
2130
- res.writeHead(404);
2131
- res.end("Not found");
2283
+ await streamTurn(res, body, decodeURIComponent(agentsMatch[1]));
2284
+ return;
2132
2285
  }
2286
+ if (method === "POST" && url === "/") {
2287
+ let body = "";
2288
+ for await (const chunk of req) body += chunk;
2289
+ await streamTurn(res, body, void 0);
2290
+ return;
2291
+ }
2292
+ res.writeHead(404);
2293
+ res.end("Not found");
2133
2294
  });
2134
2295
  let boundPort;
2135
2296
  try {
@@ -2166,25 +2327,50 @@ async function devCommand(options) {
2166
2327
  );
2167
2328
  console.log();
2168
2329
  }
2169
- console.log(` ${dimmed(`http://localhost:${boundPort}`)}`);
2170
- console.log();
2171
- console.log(` ${dimmed(`$ curl -X POST http://localhost:${boundPort} \\`)}`);
2172
- console.log(` ${dimmed(` -H "Content-Type: application/json" \\`)}`);
2173
- console.log(` ${dimmed(` -d '{"message": "hello"}'`)}`);
2174
- console.log();
2330
+ console.log(` ${dimmed(`agent http://localhost:${boundPort}`)}`);
2175
2331
  const missing = checkProviderKeys(agent.manifest.config.model);
2176
2332
  if (missing.length > 0) {
2333
+ console.log();
2177
2334
  console.log(` ${grey("\u26A0")} Missing API keys: ${missing.join(", ")}`);
2178
2335
  console.log(` ${dimmed(" Set them in .env.local or your environment")}`);
2179
- console.log();
2180
2336
  }
2181
2337
  } catch {
2182
2338
  console.log(` ${agentDirPath}`);
2183
2339
  console.log();
2184
- console.log(` ${dimmed(`http://localhost:${boundPort}`)}`);
2185
- console.log();
2340
+ console.log(` ${dimmed(`agent http://localhost:${boundPort}`)}`);
2341
+ }
2342
+ }
2343
+ let webChannel;
2344
+ if (!options.input && options.noWeb !== true) {
2345
+ const webDir = join2(agentDirPath, "channels", "web");
2346
+ if (existsSync2(webDir)) {
2347
+ try {
2348
+ const webPort = await findFreePort(3001);
2349
+ console.log(` ${dimmed(`web starting on http://localhost:${webPort}\u2026`)}`);
2350
+ webChannel = await startWebChannel(agentDirPath, `http://localhost:${boundPort}`, webPort);
2351
+ if (webChannel !== void 0) {
2352
+ console.log(` ${dimmed(`web http://localhost:${webPort}`)}`);
2353
+ if (options.noOpen !== true) openBrowser(webChannel.url);
2354
+ }
2355
+ } catch (err) {
2356
+ console.log(
2357
+ ` ${grey("\u26A0")} could not start web channel: ${err instanceof Error ? err.message : String(err)}`
2358
+ );
2359
+ }
2186
2360
  }
2187
2361
  }
2362
+ if (!options.input) {
2363
+ console.log();
2364
+ console.log(` ${dimmed("Ctrl+C to stop")}`);
2365
+ console.log();
2366
+ }
2367
+ const shutdown = () => {
2368
+ if (webChannel !== void 0) webChannel.process.kill();
2369
+ server.close();
2370
+ process.exit(0);
2371
+ };
2372
+ process.once("SIGINT", shutdown);
2373
+ process.once("SIGTERM", shutdown);
2188
2374
  if (options.input) {
2189
2375
  void startBlockChat({ agentDir: agentDirPath });
2190
2376
  }
@@ -2192,4 +2378,4 @@ async function devCommand(options) {
2192
2378
  export {
2193
2379
  devCommand
2194
2380
  };
2195
- //# sourceMappingURL=dev-DCPOO54H.js.map
2381
+ //# sourceMappingURL=dev-XBXOVOVE.js.map