@hydra-acp/cli 0.1.32 → 0.1.33

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/dist/cli.js CHANGED
@@ -12948,6 +12948,7 @@ import { dirname as dirname6, resolve as resolve5 } from "path";
12948
12948
  // src/cli/parse-args.ts
12949
12949
  var KNOWN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
12950
12950
  "all",
12951
+ "detach",
12951
12952
  "foreground",
12952
12953
  "help",
12953
12954
  "info",
@@ -17575,6 +17576,7 @@ async function runDaemonStart(flags = {}) {
17575
17576
  return;
17576
17577
  }
17577
17578
  if (flagBool(flags, "foreground")) {
17579
+ process.title = "hydra-daemon";
17578
17580
  const handle = await startDaemon(config, serviceToken);
17579
17581
  process.stdout.write(
17580
17582
  `hydra-acp daemon listening on ${config.daemon.host}:${config.daemon.port}
@@ -18655,8 +18657,34 @@ function isResponse2(msg) {
18655
18657
  return !("method" in msg) && "id" in msg;
18656
18658
  }
18657
18659
 
18660
+ // src/core/process-title.ts
18661
+ import { writeFileSync as writeFileSync2 } from "fs";
18662
+ var COMM_ANCHOR = "hydra";
18663
+ var defaultWriteComm = (text) => {
18664
+ writeFileSync2("/proc/self/comm", text);
18665
+ };
18666
+ function setHydraProcessTitle(fullTitle, deps = {}) {
18667
+ process.title = fullTitle;
18668
+ const platform = deps.platform ?? process.platform;
18669
+ if (platform !== "linux") {
18670
+ return;
18671
+ }
18672
+ const writeComm = deps.writeComm ?? defaultWriteComm;
18673
+ try {
18674
+ writeComm(COMM_ANCHOR);
18675
+ } catch {
18676
+ }
18677
+ }
18678
+ function buildTitleFromArgv(argv) {
18679
+ if (argv.length === 0) {
18680
+ return COMM_ANCHOR;
18681
+ }
18682
+ return `${COMM_ANCHOR} ${argv.join(" ")}`;
18683
+ }
18684
+
18658
18685
  // src/shim/proxy.ts
18659
18686
  async function runShim(opts) {
18687
+ setHydraProcessTitle(buildTitleFromArgv(process.argv.slice(2)));
18660
18688
  const config = await loadConfig();
18661
18689
  const serviceToken = await ensureServiceToken();
18662
18690
  await ensureDaemonReachable(config);
@@ -18896,6 +18924,309 @@ function injectHydraMeta(msg, additions) {
18896
18924
  };
18897
18925
  }
18898
18926
 
18927
+ // src/cli/commands/cat.ts
18928
+ init_connection();
18929
+ init_ws_stream();
18930
+ init_config();
18931
+ init_service_token();
18932
+ init_daemon_bootstrap();
18933
+ init_render_update();
18934
+ init_types();
18935
+ init_hydra_version();
18936
+ import { WebSocket as WebSocket2 } from "ws";
18937
+
18938
+ // src/cli/commands/cat-chunker.ts
18939
+ function createChunker(opts) {
18940
+ let buffer = "";
18941
+ let dataArrivedSinceSchedule = false;
18942
+ let scheduled = false;
18943
+ let ended = false;
18944
+ const flush = () => {
18945
+ if (buffer.length === 0) {
18946
+ return;
18947
+ }
18948
+ const out = buffer;
18949
+ buffer = "";
18950
+ opts.onChunk(out);
18951
+ };
18952
+ const scheduleCheck = () => {
18953
+ if (scheduled) {
18954
+ return;
18955
+ }
18956
+ scheduled = true;
18957
+ dataArrivedSinceSchedule = false;
18958
+ opts.scheduleFlushCheck(() => {
18959
+ scheduled = false;
18960
+ if (dataArrivedSinceSchedule) {
18961
+ scheduleCheck();
18962
+ return;
18963
+ }
18964
+ flush();
18965
+ });
18966
+ };
18967
+ return {
18968
+ feed(data) {
18969
+ if (ended || data.length === 0) {
18970
+ return;
18971
+ }
18972
+ buffer += data;
18973
+ if (scheduled) {
18974
+ dataArrivedSinceSchedule = true;
18975
+ } else {
18976
+ scheduleCheck();
18977
+ }
18978
+ },
18979
+ eof() {
18980
+ if (ended) {
18981
+ return;
18982
+ }
18983
+ ended = true;
18984
+ flush();
18985
+ }
18986
+ };
18987
+ }
18988
+
18989
+ // src/cli/commands/cat.ts
18990
+ async function runCat(opts) {
18991
+ setHydraProcessTitle(buildTitleFromArgv(process.argv.slice(2)));
18992
+ if (process.stdin.isTTY && !opts.prompt && !opts.sessionId) {
18993
+ process.stderr.write(
18994
+ "hydra-acp cat: nothing to send. Pipe input on stdin, pass -p <text>, or attach to an existing session with --session-id.\n"
18995
+ );
18996
+ process.exit(2);
18997
+ return;
18998
+ }
18999
+ const config = await loadConfig();
19000
+ const serviceToken = await ensureServiceToken();
19001
+ await ensureDaemonReachable(config);
19002
+ const protocol = config.daemon.tls ? "wss" : "ws";
19003
+ const url = `${protocol}://${config.daemon.host}:${config.daemon.port}/acp`;
19004
+ const subprotocols = ["acp.v1", `hydra-acp-token.${serviceToken}`];
19005
+ const ws = await openWs2(url, subprotocols);
19006
+ const stream = wsToMessageStream(ws);
19007
+ const conn = new JsonRpcConnection(stream);
19008
+ const result = await runCatLoop({
19009
+ conn,
19010
+ opts,
19011
+ stdin: process.stdin,
19012
+ stdinIsTty: process.stdin.isTTY === true,
19013
+ stdout: (chunk) => process.stdout.write(chunk),
19014
+ stderr: (chunk) => {
19015
+ process.stderr.write(chunk);
19016
+ }
19017
+ });
19018
+ process.exit(result.exitCode);
19019
+ }
19020
+ async function runCatLoop(args) {
19021
+ const { conn, opts, stdin, stdinIsTty, stdout, stderr } = args;
19022
+ conn.setDefaultHandler(async () => {
19023
+ return { error: { code: -32601, message: "method not implemented" } };
19024
+ });
19025
+ try {
19026
+ await conn.request("initialize", {
19027
+ protocolVersion: ACP_PROTOCOL_VERSION,
19028
+ clientCapabilities: {
19029
+ fs: { readTextFile: false, writeTextFile: false },
19030
+ terminal: false
19031
+ },
19032
+ clientInfo: { name: "hydra-acp-cat", version: HYDRA_VERSION }
19033
+ });
19034
+ } catch {
19035
+ }
19036
+ const sessionId = await openOrAttachSession(conn, opts);
19037
+ let turnHadOutput = false;
19038
+ let lastCharWasNewline = true;
19039
+ const writeStdout = (text) => {
19040
+ if (text.length === 0) {
19041
+ return;
19042
+ }
19043
+ stdout(text);
19044
+ lastCharWasNewline = text.charCodeAt(text.length - 1) === 10;
19045
+ };
19046
+ const finalizeTurn = () => {
19047
+ if (turnHadOutput && !lastCharWasNewline) {
19048
+ writeStdout("\n");
19049
+ }
19050
+ turnHadOutput = false;
19051
+ };
19052
+ conn.onNotification("session/update", (params) => {
19053
+ const update = params?.update;
19054
+ const event = mapUpdate(update);
19055
+ if (!event) {
19056
+ return;
19057
+ }
19058
+ if (event.kind === "agent-text") {
19059
+ turnHadOutput = true;
19060
+ writeStdout(event.text);
19061
+ } else if (event.kind === "turn-complete") {
19062
+ finalizeTurn();
19063
+ }
19064
+ });
19065
+ const sendChunk = async (text) => {
19066
+ const promptBlocks = [];
19067
+ if (opts.prompt) {
19068
+ promptBlocks.push({ type: "text", text: opts.prompt });
19069
+ }
19070
+ if (text.length > 0) {
19071
+ promptBlocks.push({ type: "text", text });
19072
+ }
19073
+ if (promptBlocks.length === 0) {
19074
+ return;
19075
+ }
19076
+ try {
19077
+ await conn.request("session/prompt", {
19078
+ sessionId,
19079
+ prompt: promptBlocks
19080
+ });
19081
+ } catch (err) {
19082
+ stderr(`hydra-acp cat: prompt failed: ${err.message}
19083
+ `);
19084
+ return;
19085
+ }
19086
+ finalizeTurn();
19087
+ };
19088
+ let exitCode = 0;
19089
+ let resolveDone;
19090
+ const done = new Promise((resolve6) => {
19091
+ resolveDone = resolve6;
19092
+ });
19093
+ let settled = false;
19094
+ const settle = async (code) => {
19095
+ if (settled) {
19096
+ return;
19097
+ }
19098
+ settled = true;
19099
+ if (!opts.detach) {
19100
+ conn.request("session/detach", { sessionId }).catch(() => void 0);
19101
+ }
19102
+ await conn.close().catch(() => void 0);
19103
+ resolveDone({ exitCode: code });
19104
+ };
19105
+ conn.onClose((err) => {
19106
+ if (err) {
19107
+ stderr(`hydra-acp cat: ${err.message}
19108
+ `);
19109
+ exitCode = 1;
19110
+ }
19111
+ if (!settled) {
19112
+ settled = true;
19113
+ resolveDone({ exitCode });
19114
+ }
19115
+ });
19116
+ const chunkQueue = [];
19117
+ let draining = false;
19118
+ let stdinEnded = false;
19119
+ const drainQueue = async () => {
19120
+ if (draining) {
19121
+ return;
19122
+ }
19123
+ draining = true;
19124
+ try {
19125
+ while (chunkQueue.length > 0) {
19126
+ const next = chunkQueue.shift();
19127
+ if (next === void 0) {
19128
+ break;
19129
+ }
19130
+ await sendChunk(next);
19131
+ }
19132
+ } finally {
19133
+ draining = false;
19134
+ if (stdinEnded && chunkQueue.length === 0) {
19135
+ await settle(exitCode);
19136
+ }
19137
+ }
19138
+ };
19139
+ const chunker = createChunker({
19140
+ // setImmediate fires in the libuv "check" phase, after pending
19141
+ // I/O has been polled and any back-to-back "data" events have
19142
+ // been emitted. That makes it the natural hook for "the writer
19143
+ // has paused, time to flush": if more bytes were sitting in the
19144
+ // pipe buffer, Node would have emitted another "data" event
19145
+ // before this fires, and the chunker would detect that and defer.
19146
+ scheduleFlushCheck: (cb) => {
19147
+ const h = setImmediate(cb);
19148
+ return () => clearImmediate(h);
19149
+ },
19150
+ onChunk: (text) => {
19151
+ chunkQueue.push(text);
19152
+ void drainQueue();
19153
+ }
19154
+ });
19155
+ if (stdinIsTty && !opts.sessionId) {
19156
+ if (opts.prompt) {
19157
+ await sendChunk("");
19158
+ }
19159
+ await settle(0);
19160
+ return done;
19161
+ }
19162
+ if (typeof stdin.setEncoding === "function") {
19163
+ stdin.setEncoding("utf8");
19164
+ }
19165
+ stdin.on("data", (data) => {
19166
+ chunker.feed(typeof data === "string" ? data : data.toString("utf8"));
19167
+ });
19168
+ stdin.on("end", () => {
19169
+ chunker.eof();
19170
+ stdinEnded = true;
19171
+ if (!draining && chunkQueue.length === 0) {
19172
+ void settle(exitCode);
19173
+ }
19174
+ });
19175
+ stdin.on("error", (err) => {
19176
+ stderr(`hydra-acp cat: stdin error: ${err.message}
19177
+ `);
19178
+ exitCode = 1;
19179
+ stdinEnded = true;
19180
+ if (!draining && chunkQueue.length === 0) {
19181
+ void settle(exitCode);
19182
+ }
19183
+ });
19184
+ return done;
19185
+ }
19186
+ async function openOrAttachSession(conn, opts) {
19187
+ if (opts.sessionId) {
19188
+ const attached = await conn.request("session/attach", {
19189
+ sessionId: opts.sessionId,
19190
+ historyPolicy: "pending_only",
19191
+ clientInfo: { name: "hydra-acp-cat", version: HYDRA_VERSION }
19192
+ });
19193
+ return attached.sessionId;
19194
+ }
19195
+ const hydraMeta = {};
19196
+ if (opts.name) {
19197
+ hydraMeta.name = opts.name;
19198
+ }
19199
+ if (opts.model) {
19200
+ hydraMeta.model = opts.model;
19201
+ }
19202
+ const cwd = opts.cwd ?? process.cwd();
19203
+ const params = { cwd };
19204
+ if (opts.agentId) {
19205
+ params.agentId = opts.agentId;
19206
+ }
19207
+ if (Object.keys(hydraMeta).length > 0) {
19208
+ params._meta = { [HYDRA_META_KEY]: hydraMeta };
19209
+ }
19210
+ const created = await conn.request("session/new", params);
19211
+ void extractHydraMeta(created._meta);
19212
+ return created.sessionId;
19213
+ }
19214
+ async function openWs2(url, subprotocols) {
19215
+ return new Promise((resolve6, reject) => {
19216
+ const ws = new WebSocket2(url, subprotocols);
19217
+ const onOpen = () => {
19218
+ ws.off("error", onError);
19219
+ resolve6(ws);
19220
+ };
19221
+ const onError = (err) => {
19222
+ ws.off("open", onOpen);
19223
+ reject(err);
19224
+ };
19225
+ ws.once("open", onOpen);
19226
+ ws.once("error", onError);
19227
+ });
19228
+ }
19229
+
18899
19230
  // src/cli.ts
18900
19231
  init_update_check();
18901
19232
  var suppressUpdateNotice = false;
@@ -18973,6 +19304,26 @@ async function main() {
18973
19304
  suppressUpdateNotice = true;
18974
19305
  await runShim({ sessionId, name, agentId: agentIdFromFlag, model });
18975
19306
  return;
19307
+ case "cat": {
19308
+ const promptFromShort = readShortPrompt(argv);
19309
+ const longPrompt = typeof flags.prompt === "string" ? flags.prompt : void 0;
19310
+ const prompt = promptFromShort ?? longPrompt;
19311
+ const cwd = resolveOption(flags, "cwd");
19312
+ const catOpts = {
19313
+ prompt,
19314
+ sessionId,
19315
+ name,
19316
+ model,
19317
+ agentId: agentIdFromFlag,
19318
+ detach: flags.detach === true
19319
+ };
19320
+ if (cwd !== void 0) {
19321
+ catOpts.cwd = cwd;
19322
+ }
19323
+ suppressUpdateNotice = true;
19324
+ await runCat(catOpts);
19325
+ return;
19326
+ }
18976
19327
  case "init":
18977
19328
  await runInit(flags);
18978
19329
  return;
@@ -19157,6 +19508,7 @@ async function dispatchTui(flags, base) {
19157
19508
  );
19158
19509
  process.exit(2);
19159
19510
  }
19511
+ setHydraProcessTitle(buildTitleFromArgv(process.argv.slice(2)));
19160
19512
  const { runTui } = await Promise.resolve().then(() => (init_tui(), tui_exports));
19161
19513
  const tuiOpts = { resume, forceNew, readonly };
19162
19514
  if (base.sessionId !== void 0) {
@@ -19176,6 +19528,21 @@ async function dispatchTui(flags, base) {
19176
19528
  }
19177
19529
  await runTui(tuiOpts);
19178
19530
  }
19531
+ function readShortPrompt(argv) {
19532
+ for (let i = 0; i < argv.length; i += 1) {
19533
+ const tok = argv[i];
19534
+ if (tok === void 0) {
19535
+ continue;
19536
+ }
19537
+ if (tok === "-p") {
19538
+ return argv[i + 1];
19539
+ }
19540
+ if (tok.startsWith("-p") && !tok.startsWith("--")) {
19541
+ return tok.slice(2);
19542
+ }
19543
+ }
19544
+ return void 0;
19545
+ }
19179
19546
  function bareResumeError() {
19180
19547
  process.stderr.write(
19181
19548
  "hydra-acp: --resume requires a session id. Use --resume <id> to attach to a specific session, or --reattach to pick the most recent one in cwd.\n"
@@ -19202,6 +19569,19 @@ function printHelp() {
19202
19569
  " hydra-acp Auto: TUI when stdout is a TTY, shim otherwise (the editor-spawned case)",
19203
19570
  " hydra-acp shim Run as ACP shim explicitly (forces shim mode regardless of TTY)",
19204
19571
  " hydra-acp tui [opts] Run the terminal UI explicitly (see below for opts)",
19572
+ " hydra-acp cat [-p <prompt>] [--session-id <id>] [--detach] [--agent <id>] [--model <id>] [--name <label>]",
19573
+ " Pipe-friendly headless mode. Reads stdin and sends it",
19574
+ " as a prompt to a fresh session, streams the agent's",
19575
+ " response to stdout, exits when stdin closes. A bounded",
19576
+ ' input (e.g. `cat file.log | hydra cat -p "..."`) goes in',
19577
+ " as one turn; a streaming input (e.g. `tail -f`) is",
19578
+ " chunked by the natural pauses in the writer. -p is an",
19579
+ " optional standing instruction prepended to every chunk;",
19580
+ " if stdin already contains the question, -p is not needed.",
19581
+ " With --session-id, attach to an existing session instead",
19582
+ " of creating a new one. With --detach, the session",
19583
+ " survives in the daemon for slack/browser/notifier",
19584
+ " extensions.",
19205
19585
  " hydra-acp launch <agent> [agent-args...]",
19206
19586
  " Shim mode, force daemon to spawn <agent>",
19207
19587
  " from the registry. Args after <agent>",
package/dist/index.d.ts CHANGED
@@ -1410,8 +1410,8 @@ declare const SessionListEntry: z.ZodObject<{
1410
1410
  status: z.ZodDefault<z.ZodEnum<["live", "cold"]>>;
1411
1411
  _meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1412
1412
  }, "strip", z.ZodTypeAny, {
1413
- status: "live" | "cold";
1414
1413
  cwd: string;
1414
+ status: "live" | "cold";
1415
1415
  sessionId: string;
1416
1416
  updatedAt: string;
1417
1417
  attachedClients: number;
@@ -1480,8 +1480,8 @@ declare const SessionListResult: z.ZodObject<{
1480
1480
  status: z.ZodDefault<z.ZodEnum<["live", "cold"]>>;
1481
1481
  _meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1482
1482
  }, "strip", z.ZodTypeAny, {
1483
- status: "live" | "cold";
1484
1483
  cwd: string;
1484
+ status: "live" | "cold";
1485
1485
  sessionId: string;
1486
1486
  updatedAt: string;
1487
1487
  attachedClients: number;
@@ -1521,8 +1521,8 @@ declare const SessionListResult: z.ZodObject<{
1521
1521
  nextCursor: z.ZodOptional<z.ZodString>;
1522
1522
  }, "strip", z.ZodTypeAny, {
1523
1523
  sessions: {
1524
- status: "live" | "cold";
1525
1524
  cwd: string;
1525
+ status: "live" | "cold";
1526
1526
  sessionId: string;
1527
1527
  updatedAt: string;
1528
1528
  attachedClients: number;
@@ -1594,14 +1594,14 @@ declare const AmendPromptParams: z.ZodObject<{
1594
1594
  replaceQueue: z.ZodOptional<z.ZodBoolean>;
1595
1595
  onTargetCompleted: z.ZodOptional<z.ZodEnum<["reject", "send_anyway"]>>;
1596
1596
  }, "strip", z.ZodTypeAny, {
1597
- sessionId: string;
1598
1597
  prompt: unknown[];
1598
+ sessionId: string;
1599
1599
  targetMessageId: string;
1600
1600
  replaceQueue?: boolean | undefined;
1601
1601
  onTargetCompleted?: "reject" | "send_anyway" | undefined;
1602
1602
  }, {
1603
- sessionId: string;
1604
1603
  prompt: unknown[];
1604
+ sessionId: string;
1605
1605
  targetMessageId: string;
1606
1606
  replaceQueue?: boolean | undefined;
1607
1607
  onTargetCompleted?: "reject" | "send_anyway" | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hydra-acp/cli",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "description": "Multi-client ACP session daemon: spawn agents, attach over WSS, multiplex sessions across editors.",
5
5
  "license": "MIT",
6
6
  "type": "module",