@hydra-acp/cli 0.1.27 → 0.1.29

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 (3) hide show
  1. package/README.md +10 -0
  2. package/dist/cli.js +84 -8
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -439,6 +439,16 @@ hydra-acp extension start hydra-acp-approver
439
439
 
440
440
  Without a config file the approver abstains on everything — installing it has no behavioral effect until you write a rule.
441
441
 
442
+ **[`@hydra-acp/archiver`](https://github.com/smagnuso/hydra-acp-archiver) — cross-machine session sync.** Uploads session bundles to a shared backend (Google Drive, plain filesystem) after every turn and imports peers' bundles in the background, so a session started on machine A shows up on machine B without manual export/import. Imported sessions carry an `importedFromMachine` breadcrumb that the picker, browser, slack, and `sessions list` honor for host filtering.
443
+
444
+ ```sh
445
+ npm install -g @hydra-acp/archiver
446
+ hydra-acp extension add hydra-acp-archiver
447
+ hydra-acp extension start hydra-acp-archiver
448
+ ```
449
+
450
+ See the [package README](https://github.com/smagnuso/hydra-acp-archiver#readme) for backend setup (Drive OAuth, filesystem path).
451
+
442
452
  Per-extension config (env vars, args, custom command paths) goes in the same `extensions` block in `~/.hydra-acp/config.json` — see the snippet above. `hydra-acp extension logs <name> -f` tails an extension's stdout/stderr if you need to debug.
443
453
 
444
454
  The service token (stored at `~/.hydra-acp/auth-token`, mode 0600) is generated on `hydra-acp init` and required as `Authorization: Bearer <token>` for every REST call and as a WebSocket subprotocol or query parameter for `wss://.../acp`. The token never leaves `~/.hydra-acp/`.
package/dist/cli.js CHANGED
@@ -4655,15 +4655,32 @@ async function runSessionsList(opts = {}) {
4655
4655
  process.exit(1);
4656
4656
  }
4657
4657
  const body = await response.json();
4658
+ const host = opts.host ?? "local";
4659
+ const hostFiltered = host === "all" ? body.sessions : host === "local" ? body.sessions.filter(
4660
+ (s) => !s.importedFromMachine || !!s.upstreamSessionId
4661
+ ) : body.sessions.filter(
4662
+ (s) => s.importedFromMachine === host && !s.upstreamSessionId
4663
+ );
4658
4664
  if (opts.json) {
4659
- process.stdout.write(JSON.stringify(body.sessions, null, 2) + "\n");
4665
+ process.stdout.write(JSON.stringify(hostFiltered, null, 2) + "\n");
4660
4666
  return;
4661
4667
  }
4662
- if (body.sessions.length === 0) {
4668
+ if (hostFiltered.length === 0) {
4669
+ if (host === "local" && body.sessions.length > 0) {
4670
+ process.stdout.write(
4671
+ "No local sessions. Use --host=all to include imported sessions.\n"
4672
+ );
4673
+ return;
4674
+ }
4675
+ if (host !== "local" && host !== "all") {
4676
+ process.stdout.write(`No sessions from ${host}.
4677
+ `);
4678
+ return;
4679
+ }
4663
4680
  process.stdout.write("No active sessions.\n");
4664
4681
  return;
4665
4682
  }
4666
- const sorted = body.sessions.slice().sort((a, b) => {
4683
+ const sorted = hostFiltered.slice().sort((a, b) => {
4667
4684
  const liveDiff = (b.status === "live" ? 1 : 0) - (a.status === "live" ? 1 : 0);
4668
4685
  if (liveDiff !== 0) {
4669
4686
  return liveDiff;
@@ -5414,8 +5431,18 @@ async function pickSession(term, opts) {
5414
5431
  return b.updatedAt.localeCompare(a.updatedAt);
5415
5432
  });
5416
5433
  };
5434
+ let cwdOnly = false;
5435
+ let hostFilter = "__local";
5436
+ if (opts.currentSessionId !== void 0) {
5437
+ const current = opts.sessions.find(
5438
+ (s) => s.sessionId === opts.currentSessionId
5439
+ );
5440
+ if (current?.importedFromMachine) {
5441
+ hostFilter = "__all";
5442
+ }
5443
+ }
5417
5444
  let allSessions = sortSessions(opts.sessions);
5418
- let visible = allSessions;
5445
+ let visible = filterByHost(allSessions, hostFilter);
5419
5446
  let rows = visible.map((s) => toRow(s, Date.now()));
5420
5447
  let widths = computeWidths(rows);
5421
5448
  let total = 1 + visible.length;
@@ -5429,7 +5456,6 @@ async function pickSession(term, opts) {
5429
5456
  }
5430
5457
  let searchActive = false;
5431
5458
  let searchTerm = "";
5432
- let cwdOnly = false;
5433
5459
  let mode = "normal";
5434
5460
  let pendingAction = null;
5435
5461
  let renameBuffer = "";
@@ -5463,6 +5489,7 @@ async function pickSession(term, opts) {
5463
5489
  if (cwdOnly) {
5464
5490
  base = base.filter((s) => s.cwd === opts.cwd);
5465
5491
  }
5492
+ base = filterByHost(base, hostFilter);
5466
5493
  if (searchActive && searchTerm.length > 0) {
5467
5494
  visible = base.filter((s) => matchesSearch(s, searchTerm));
5468
5495
  } else {
@@ -5515,6 +5542,11 @@ async function pickSession(term, opts) {
5515
5542
  if (cwdOnly) {
5516
5543
  parts.push("cwd-only");
5517
5544
  }
5545
+ if (hostFilter !== "__all") {
5546
+ parts.push(
5547
+ hostFilter === "__local" ? "host: local" : `host: ${hostFilter}`
5548
+ );
5549
+ }
5518
5550
  if (above > 0) {
5519
5551
  parts.push(`\u2191 ${above} above`);
5520
5552
  }
@@ -5896,6 +5928,20 @@ async function pickSession(term, opts) {
5896
5928
  renderFromScratch();
5897
5929
  return;
5898
5930
  }
5931
+ if (name === "h" || name === "H") {
5932
+ const keepId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
5933
+ hostFilter = nextHostFilter(hostFilter, allSessions);
5934
+ applyFilter();
5935
+ if (keepId !== void 0) {
5936
+ const idx = visible.findIndex((s) => s.sessionId === keepId);
5937
+ if (idx >= 0) {
5938
+ selectedIdx = idx + 1;
5939
+ adjustScroll();
5940
+ }
5941
+ }
5942
+ renderFromScratch();
5943
+ return;
5944
+ }
5899
5945
  if (name === "r" || name === "R") {
5900
5946
  const currentId = selectedIdx > 0 ? visible[selectedIdx - 1]?.sessionId : void 0;
5901
5947
  void refresh(currentId);
@@ -6026,6 +6072,33 @@ function formatNewSessionLabel(cwd, maxWidth) {
6026
6072
  const budget = Math.max(1, maxWidth - prefix.length);
6027
6073
  return prefix + truncateMiddle(shortenHomePath(cwd), budget);
6028
6074
  }
6075
+ function filterByHost(sessions, hostFilter) {
6076
+ if (hostFilter === "__all") {
6077
+ return sessions;
6078
+ }
6079
+ if (hostFilter === "__local") {
6080
+ return sessions.filter(
6081
+ (s) => !s.importedFromMachine || !!s.upstreamSessionId
6082
+ );
6083
+ }
6084
+ return sessions.filter(
6085
+ (s) => s.importedFromMachine === hostFilter && !s.upstreamSessionId
6086
+ );
6087
+ }
6088
+ function nextHostFilter(current, sessions) {
6089
+ const hosts = /* @__PURE__ */ new Set();
6090
+ for (const s of sessions) {
6091
+ if (s.importedFromMachine && !s.upstreamSessionId) {
6092
+ hosts.add(s.importedFromMachine);
6093
+ }
6094
+ }
6095
+ const ordered = ["__local", ...[...hosts].sort(), "__all"];
6096
+ const idx = ordered.indexOf(current);
6097
+ if (idx === -1) {
6098
+ return "__local";
6099
+ }
6100
+ return ordered[(idx + 1) % ordered.length] ?? "__local";
6101
+ }
6029
6102
  function matchesSearch(s, term) {
6030
6103
  if (term.length === 0) {
6031
6104
  return true;
@@ -6064,6 +6137,7 @@ var init_picker = __esm({
6064
6137
  null,
6065
6138
  ["/", "search sessions"],
6066
6139
  ["o", "toggle cwd-only filter"],
6140
+ ["h", "cycle host filter (local / <peer> / all)"],
6067
6141
  ["r", "refresh from daemon"],
6068
6142
  null,
6069
6143
  ["k", "kill the selected live session"],
@@ -18323,7 +18397,8 @@ async function main() {
18323
18397
  if (sub === void 0 || sub === "list") {
18324
18398
  await runSessionsList({
18325
18399
  all: flags.all === true,
18326
- json: flags.json === true
18400
+ json: flags.json === true,
18401
+ host: typeof flags.host === "string" ? flags.host : void 0
18327
18402
  });
18328
18403
  return;
18329
18404
  }
@@ -18516,8 +18591,9 @@ function printHelp() {
18516
18591
  " hydra-acp daemon start [--foreground] Start daemon (detached by default; --foreground to attach)",
18517
18592
  " hydra-acp daemon stop|restart|status",
18518
18593
  " hydra-acp daemon logs [-f] [-n N] Tail or follow the daemon log",
18519
- " hydra-acp session [list] [--all] [--json]",
18520
- " List sessions (live + 20 most-recent cold; --all for everything; --json emits the raw daemon response as JSON for scripts)",
18594
+ " hydra-acp session [list] [--all] [--json] [--host=<host>]",
18595
+ " List sessions (live + 20 most-recent cold; --all for everything; --json emits JSON for scripts).",
18596
+ " --host filters by origin machine: 'local' (default) shows only sessions created here, 'all' shows everything, or pass a hostname (e.g. machine-b) to show only imports from that peer.",
18521
18597
  " hydra-acp session kill <id> Demote a live session to cold (keeps the on-disk record)",
18522
18598
  " hydra-acp session remove <id> Remove a session entirely (live or cold)",
18523
18599
  " hydra-acp session export <id> [--out <file>|.]",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hydra-acp/cli",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
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",