@hydra-acp/cli 0.1.13 → 0.1.15

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 (2) hide show
  1. package/dist/cli.js +137 -23
  2. package/package.json +3 -1
package/dist/cli.js CHANGED
@@ -3887,7 +3887,7 @@ function readTermWidth(term) {
3887
3887
  return term.width ?? 80;
3888
3888
  }
3889
3889
  function formatNewSessionLabel(cwd, maxWidth) {
3890
- const prefix = "+ New session in ";
3890
+ const prefix = "New session in ";
3891
3891
  const budget = Math.max(1, maxWidth - prefix.length);
3892
3892
  return prefix + truncateMiddle(shortenHomePath(cwd), budget);
3893
3893
  }
@@ -3931,6 +3931,23 @@ function mimeFromExtension(p) {
3931
3931
  function isSupportedImagePath(p) {
3932
3932
  return mimeFromExtension(p) !== null;
3933
3933
  }
3934
+ function parseDataUriImage(uri) {
3935
+ const match = uri.match(/^data:(image\/[a-z0-9.+\-]+);base64,([A-Za-z0-9+/=]+)$/);
3936
+ if (!match) {
3937
+ return null;
3938
+ }
3939
+ const mimeType = match[1].toLowerCase();
3940
+ if (!SUPPORTED_MIMES.has(mimeType)) {
3941
+ return null;
3942
+ }
3943
+ const data = match[2];
3944
+ const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
3945
+ const sizeBytes = Math.floor(data.length * 3 / 4) - padding;
3946
+ return { mimeType, data, sizeBytes };
3947
+ }
3948
+ function isSupportedDataUriImage(uri) {
3949
+ return parseDataUriImage(uri) !== null;
3950
+ }
3934
3951
  function formatSize(bytes) {
3935
3952
  if (bytes >= 1024 * 1024) {
3936
3953
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
@@ -3978,6 +3995,13 @@ function parseImageDropPaste(raw) {
3978
3995
  }
3979
3996
  }
3980
3997
  }
3998
+ if (token.startsWith("data:")) {
3999
+ if (!isSupportedDataUriImage(token)) {
4000
+ return null;
4001
+ }
4002
+ tokens.push(token);
4003
+ continue;
4004
+ }
3981
4005
  let normalized = token;
3982
4006
  if (normalized.startsWith("file://")) {
3983
4007
  normalized = decodeURI(normalized.slice("file://".length));
@@ -3992,7 +4016,7 @@ function parseImageDropPaste(raw) {
3992
4016
  }
3993
4017
  return tokens.length > 0 ? tokens : null;
3994
4018
  }
3995
- var MAX_ATTACHMENT_BYTES, EXTENSION_TO_MIME;
4019
+ var MAX_ATTACHMENT_BYTES, EXTENSION_TO_MIME, SUPPORTED_MIMES;
3996
4020
  var init_attachments = __esm({
3997
4021
  "src/tui/attachments.ts"() {
3998
4022
  "use strict";
@@ -4004,6 +4028,7 @@ var init_attachments = __esm({
4004
4028
  ".gif": "image/gif",
4005
4029
  ".webp": "image/webp"
4006
4030
  };
4031
+ SUPPORTED_MIMES = new Set(Object.values(EXTENSION_TO_MIME));
4007
4032
  }
4008
4033
  });
4009
4034
 
@@ -6093,9 +6118,9 @@ var init_screen = __esm({
6093
6118
  wrapOne(line, width) {
6094
6119
  const id = this.lineIds.get(line);
6095
6120
  if (id !== void 0) {
6096
- const cached = this.wrapCache.get(id);
6097
- if (cached) {
6098
- return cached;
6121
+ const cached2 = this.wrapCache.get(id);
6122
+ if (cached2) {
6123
+ return cached2;
6099
6124
  }
6100
6125
  }
6101
6126
  const prefix = line.prefix ?? "";
@@ -6879,21 +6904,20 @@ var init_input = __esm({
6879
6904
  return [{ type: "scroll-to-bottom" }];
6880
6905
  }
6881
6906
  handleCtrlC() {
6907
+ if (this.queueIndex >= 0) {
6908
+ const index = this.queueIndex;
6909
+ this.queueIndex = -1;
6910
+ this.restoreDraft();
6911
+ return [{ type: "queue-remove", index }];
6912
+ }
6882
6913
  if (!this.bufferIsEmpty() || this.attachments.length > 0) {
6883
6914
  this.buffer = [""];
6884
6915
  this.row = 0;
6885
6916
  this.col = 0;
6886
6917
  this.attachments = [];
6887
- if (this.queueIndex === -1) {
6888
- this.historyIndex = -1;
6889
- this.savedDraft = null;
6890
- this.savedAttachments = null;
6891
- }
6892
- return [];
6893
- }
6894
- if (this.queueIndex >= 0) {
6895
- this.queueIndex = -1;
6896
- this.restoreDraft();
6918
+ this.historyIndex = -1;
6919
+ this.savedDraft = null;
6920
+ this.savedAttachments = null;
6897
6921
  return [];
6898
6922
  }
6899
6923
  if (this.turnRunning) {
@@ -8573,20 +8597,41 @@ async function runSession(term, config, opts, exitHint) {
8573
8597
  return;
8574
8598
  }
8575
8599
  };
8576
- const handleAttachmentPaths = async (paths2) => {
8600
+ const handleAttachmentPaths = async (tokens) => {
8577
8601
  if (!agentAcceptsImages) {
8578
8602
  screen.notify("agent does not accept image attachments");
8579
8603
  return;
8580
8604
  }
8581
8605
  let added = 0;
8582
- for (const p of paths2) {
8583
- const mimeType = mimeFromExtension(p);
8606
+ for (const token of tokens) {
8607
+ if (token.startsWith("data:")) {
8608
+ const parsed = parseDataUriImage(token);
8609
+ if (!parsed) {
8610
+ screen.notify("unsupported data: URI");
8611
+ continue;
8612
+ }
8613
+ if (parsed.sizeBytes > MAX_ATTACHMENT_BYTES) {
8614
+ screen.notify(
8615
+ `image too large (${formatSize(parsed.sizeBytes)}, max ${formatSize(MAX_ATTACHMENT_BYTES)})`
8616
+ );
8617
+ continue;
8618
+ }
8619
+ dispatcher.addAttachment({
8620
+ mimeType: parsed.mimeType,
8621
+ data: parsed.data,
8622
+ name: "pasted image",
8623
+ sizeBytes: parsed.sizeBytes
8624
+ });
8625
+ added++;
8626
+ continue;
8627
+ }
8628
+ const mimeType = mimeFromExtension(token);
8584
8629
  if (!mimeType) {
8585
- screen.notify(`unsupported image type: ${path11.basename(p)}`);
8630
+ screen.notify(`unsupported image type: ${path11.basename(token)}`);
8586
8631
  continue;
8587
8632
  }
8588
8633
  try {
8589
- const buf = await fs15.readFile(p);
8634
+ const buf = await fs15.readFile(token);
8590
8635
  if (buf.length > MAX_ATTACHMENT_BYTES) {
8591
8636
  screen.notify(
8592
8637
  `image too large (${formatSize(buf.length)}, max ${formatSize(MAX_ATTACHMENT_BYTES)})`
@@ -8596,12 +8641,14 @@ async function runSession(term, config, opts, exitHint) {
8596
8641
  dispatcher.addAttachment({
8597
8642
  mimeType,
8598
8643
  data: buf.toString("base64"),
8599
- name: path11.basename(p),
8644
+ name: path11.basename(token),
8600
8645
  sizeBytes: buf.length
8601
8646
  });
8602
8647
  added++;
8603
8648
  } catch (err) {
8604
- screen.notify(`cannot read ${path11.basename(p)}: ${err.message}`);
8649
+ screen.notify(
8650
+ `cannot read ${path11.basename(token)}: ${err.message}`
8651
+ );
8605
8652
  }
8606
8653
  }
8607
8654
  if (added > 0) {
@@ -13667,7 +13714,55 @@ function injectHydraMeta(msg, additions) {
13667
13714
  };
13668
13715
  }
13669
13716
 
13717
+ // src/core/update-check.ts
13718
+ init_hydra_version();
13719
+ var PKG_NAME = "@hydra-acp/cli";
13720
+ var cached;
13721
+ function disabled() {
13722
+ if (process.env.NO_UPDATE_NOTIFIER === "1") {
13723
+ return true;
13724
+ }
13725
+ if (process.argv.includes("--no-update-notifier")) {
13726
+ return true;
13727
+ }
13728
+ return false;
13729
+ }
13730
+ async function getPendingUpdate() {
13731
+ if (cached !== void 0) {
13732
+ return cached;
13733
+ }
13734
+ if (disabled()) {
13735
+ cached = null;
13736
+ return cached;
13737
+ }
13738
+ try {
13739
+ const mod = await import("update-notifier");
13740
+ const updateNotifier = mod.default ?? mod;
13741
+ const notifier = updateNotifier({
13742
+ pkg: { name: PKG_NAME, version: HYDRA_VERSION },
13743
+ updateCheckInterval: 1e3 * 60 * 60 * 24
13744
+ });
13745
+ const u = notifier.update;
13746
+ if (u && typeof u.latest === "string" && typeof u.current === "string" && u.latest !== u.current) {
13747
+ cached = {
13748
+ current: u.current,
13749
+ latest: u.latest,
13750
+ type: typeof u.type === "string" ? u.type : "unknown"
13751
+ };
13752
+ } else {
13753
+ cached = null;
13754
+ }
13755
+ } catch {
13756
+ cached = null;
13757
+ }
13758
+ return cached;
13759
+ }
13760
+ function formatUpdateNoticeLine(info) {
13761
+ return `hydra-acp ${info.latest} available (current ${info.current}) \xB7 run: npm update -g ${PKG_NAME}`;
13762
+ }
13763
+
13670
13764
  // src/cli.ts
13765
+ var suppressUpdateNotice = false;
13671
13766
  async function main() {
13672
13767
  const argv = process.argv.slice(2);
13673
13768
  const launchIdx = argv.indexOf("launch");
@@ -13699,6 +13794,7 @@ async function main() {
13699
13794
  const sessionId2 = typeof flags2.resume === "string" ? flags2.resume : resolveOption(flags2, "session-id");
13700
13795
  const name2 = resolveOption(flags2, "name");
13701
13796
  const model2 = resolveOption(flags2, "model");
13797
+ suppressUpdateNotice = true;
13702
13798
  await runShim({ sessionId: sessionId2, agentId, agentArgs, name: name2, model: model2 });
13703
13799
  return;
13704
13800
  }
@@ -13723,6 +13819,7 @@ async function main() {
13723
13819
  const model = resolveOption(flags, "model");
13724
13820
  if (!subcommand) {
13725
13821
  if (process.stdout.isTTY) {
13822
+ suppressUpdateNotice = true;
13726
13823
  await dispatchTui(flags, {
13727
13824
  sessionId,
13728
13825
  agentId: agentIdFromFlag,
@@ -13731,11 +13828,13 @@ async function main() {
13731
13828
  });
13732
13829
  return;
13733
13830
  }
13831
+ suppressUpdateNotice = true;
13734
13832
  await runShim({ sessionId, name, agentId: agentIdFromFlag, model });
13735
13833
  return;
13736
13834
  }
13737
13835
  switch (subcommand) {
13738
13836
  case "shim":
13837
+ suppressUpdateNotice = true;
13739
13838
  await runShim({ sessionId, name, agentId: agentIdFromFlag, model });
13740
13839
  return;
13741
13840
  case "init":
@@ -13858,6 +13957,7 @@ async function main() {
13858
13957
  return;
13859
13958
  }
13860
13959
  case "tui":
13960
+ suppressUpdateNotice = true;
13861
13961
  await dispatchTui(flags, {
13862
13962
  sessionId,
13863
13963
  agentId: agentIdFromFlag,
@@ -13960,8 +14060,22 @@ function printHelp() {
13960
14060
  ].join("\n")
13961
14061
  );
13962
14062
  }
13963
- main().catch((err) => {
14063
+ async function maybePrintUpdateNotice() {
14064
+ if (suppressUpdateNotice) {
14065
+ return;
14066
+ }
14067
+ try {
14068
+ const info = await getPendingUpdate();
14069
+ if (info) {
14070
+ process.stderr.write(`\u2728 ${formatUpdateNoticeLine(info)}
14071
+ `);
14072
+ }
14073
+ } catch {
14074
+ }
14075
+ }
14076
+ main().then(maybePrintUpdateNotice).catch(async (err) => {
13964
14077
  process.stderr.write(`hydra-acp: ${err.message}
13965
14078
  `);
14079
+ await maybePrintUpdateNotice();
13966
14080
  process.exit(1);
13967
14081
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hydra-acp/cli",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
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",
@@ -61,6 +61,7 @@
61
61
  "string-width": "^8.2.1",
62
62
  "strip-ansi": "^7.2.0",
63
63
  "terminal-kit": "^3.1.2",
64
+ "update-notifier": "^7.3.1",
64
65
  "wrap-ansi": "^10.0.0",
65
66
  "ws": "^8.18.0",
66
67
  "zod": "^3.23.0"
@@ -68,6 +69,7 @@
68
69
  "devDependencies": {
69
70
  "@types/node": "^22.0.0",
70
71
  "@types/terminal-kit": "^2.5.7",
72
+ "@types/update-notifier": "^6.0.8",
71
73
  "@types/ws": "^8.5.0",
72
74
  "tsup": "^8.0.0",
73
75
  "tsx": "^4.0.0",