sisyphi 1.1.35 → 1.1.36

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
@@ -539,14 +539,14 @@ __export(creds_exports, {
539
539
  readTailscaleEnv: () => readTailscaleEnv,
540
540
  writeTailscaleEnv: () => writeTailscaleEnv
541
541
  });
542
- import { chmodSync as chmodSync3, existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync30 } from "fs";
542
+ import { chmodSync as chmodSync3, existsSync as existsSync27, mkdirSync as mkdirSync15, readFileSync as readFileSync31 } from "fs";
543
543
  import { createInterface as createInterface4 } from "readline";
544
544
  function isValidProvider(value) {
545
545
  return PROVIDERS.includes(value);
546
546
  }
547
547
  function ensureDeployDir() {
548
548
  const dir = deployDir();
549
- if (!existsSync27(dir)) mkdirSync14(dir, { recursive: true, mode: 448 });
549
+ if (!existsSync27(dir)) mkdirSync15(dir, { recursive: true, mode: 448 });
550
550
  }
551
551
  function parseEnvFile(text) {
552
552
  const out = {};
@@ -573,7 +573,7 @@ function serializeEnvFile(values) {
573
573
  }
574
574
  function readEnvFile(path) {
575
575
  if (!existsSync27(path)) return null;
576
- return parseEnvFile(readFileSync30(path, "utf-8"));
576
+ return parseEnvFile(readFileSync31(path, "utf-8"));
577
577
  }
578
578
  function writeEnvFile(path, values) {
579
579
  ensureDeployDir();
@@ -668,8 +668,8 @@ var init_creds = __esm({
668
668
 
669
669
  // src/cli/index.ts
670
670
  import { Command } from "commander";
671
- import { existsSync as existsSync33, mkdirSync as mkdirSync16, readFileSync as readFileSync34 } from "fs";
672
- import { dirname as dirname12, join as join30 } from "path";
671
+ import { existsSync as existsSync33, mkdirSync as mkdirSync17, readFileSync as readFileSync35 } from "fs";
672
+ import { dirname as dirname13, join as join31 } from "path";
673
673
  import { fileURLToPath as fileURLToPath5 } from "url";
674
674
 
675
675
  // src/cli/commands/start.ts
@@ -4559,16 +4559,55 @@ function readFileSafe(filePath) {
4559
4559
  function escapeXml(s) {
4560
4560
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
4561
4561
  }
4562
- function buildCompanionContext(cwd) {
4562
+ function buildSessionBlock(cwd, session2) {
4563
+ const lines = [];
4564
+ const nameAttr = session2.name ? ` name="${escapeXml(session2.name)}"` : "";
4565
+ lines.push(` <session id="${escapeXml(session2.id)}"${nameAttr} status="${escapeXml(session2.status)}">`);
4566
+ lines.push(` <task>${escapeXml(session2.task)}</task>`);
4567
+ lines.push(` <created>${escapeXml(session2.createdAt)}</created>`);
4568
+ lines.push(` <cycles>${session2.orchestratorCycles.length}</cycles>`);
4569
+ if (session2.status === "completed") {
4570
+ if (session2.completionReport) {
4571
+ const snippet = session2.completionReport.slice(0, 300).replace(/\n+/g, " ").trim();
4572
+ lines.push(` <completion-report>${escapeXml(snippet)}${session2.completionReport.length > 300 ? "\u2026" : ""}</completion-report>`);
4573
+ }
4574
+ } else {
4575
+ if (session2.agents.length > 0) {
4576
+ const counts = /* @__PURE__ */ new Map();
4577
+ for (const agent2 of session2.agents) {
4578
+ counts.set(agent2.status, (counts.get(agent2.status) ?? 0) + 1);
4579
+ }
4580
+ const summary = [...counts.entries()].map(([status, n]) => `${n} ${status}`).join(", ");
4581
+ lines.push(` <agents>${escapeXml(summary)}</agents>`);
4582
+ }
4583
+ const goalContent = readFileSafe(goalPath(cwd, session2.id));
4584
+ if (goalContent) {
4585
+ const firstLine = goalContent.split("\n").map((l) => l.trim()).find((l) => l.length > 0 && !l.startsWith("#"));
4586
+ if (firstLine) lines.push(` <goal>${escapeXml(firstLine)}</goal>`);
4587
+ }
4588
+ const roadmapContent = readFileSafe(roadmapPath(cwd, session2.id));
4589
+ if (roadmapContent) {
4590
+ const todos = roadmapContent.split("\n").filter((l) => l.includes("- [ ]")).slice(0, 5).map((l) => l.trim());
4591
+ if (todos.length > 0) {
4592
+ lines.push(" <todos>");
4593
+ for (const todo of todos) lines.push(` ${escapeXml(todo)}`);
4594
+ lines.push(" </todos>");
4595
+ }
4596
+ }
4597
+ }
4598
+ lines.push(" </session>");
4599
+ return lines.join("\n");
4600
+ }
4601
+ function buildCompanionContextBlocks(cwd) {
4563
4602
  let sessionDirs;
4564
4603
  try {
4565
4604
  sessionDirs = readdirSync4(sessionsDir(cwd));
4566
4605
  } catch {
4567
- return "<sessions>No sessions found.</sessions>";
4606
+ return {};
4568
4607
  }
4569
4608
  const now = Date.now();
4570
4609
  const sevenDaysMs = 7 * 24 * 60 * 60 * 1e3;
4571
- const sessionBlocks = [];
4610
+ const blocks = {};
4572
4611
  for (const sessionId of sessionDirs) {
4573
4612
  const stateRaw = readFileSafe(statePath(cwd, sessionId));
4574
4613
  if (!stateRaw) continue;
@@ -4581,48 +4620,31 @@ function buildCompanionContext(cwd) {
4581
4620
  if (session2.status === "completed" && session2.completedAt) {
4582
4621
  if (now - new Date(session2.completedAt).getTime() > sevenDaysMs) continue;
4583
4622
  }
4584
- const lines = [];
4585
- const nameAttr = session2.name ? ` name="${escapeXml(session2.name)}"` : "";
4586
- lines.push(` <session id="${escapeXml(session2.id)}"${nameAttr} status="${escapeXml(session2.status)}">`);
4587
- lines.push(` <task>${escapeXml(session2.task)}</task>`);
4588
- lines.push(` <created>${escapeXml(session2.createdAt)}</created>`);
4589
- lines.push(` <cycles>${session2.orchestratorCycles.length}</cycles>`);
4590
- if (session2.status === "completed") {
4591
- if (session2.completionReport) {
4592
- const snippet = session2.completionReport.slice(0, 300).replace(/\n+/g, " ").trim();
4593
- lines.push(` <completion-report>${escapeXml(snippet)}${session2.completionReport.length > 300 ? "\u2026" : ""}</completion-report>`);
4594
- }
4595
- } else {
4596
- if (session2.agents.length > 0) {
4597
- const counts = /* @__PURE__ */ new Map();
4598
- for (const agent2 of session2.agents) {
4599
- counts.set(agent2.status, (counts.get(agent2.status) ?? 0) + 1);
4600
- }
4601
- const summary = [...counts.entries()].map(([status, n]) => `${n} ${status}`).join(", ");
4602
- lines.push(` <agents>${escapeXml(summary)}</agents>`);
4603
- }
4604
- const goalContent = readFileSafe(goalPath(cwd, session2.id));
4605
- if (goalContent) {
4606
- const firstLine = goalContent.split("\n").map((l) => l.trim()).find((l) => l.length > 0 && !l.startsWith("#"));
4607
- if (firstLine) lines.push(` <goal>${escapeXml(firstLine)}</goal>`);
4608
- }
4609
- const roadmapContent = readFileSafe(roadmapPath(cwd, session2.id));
4610
- if (roadmapContent) {
4611
- const todos = roadmapContent.split("\n").filter((l) => l.includes("- [ ]")).slice(0, 5).map((l) => l.trim());
4612
- if (todos.length > 0) {
4613
- lines.push(" <todos>");
4614
- for (const todo of todos) lines.push(` ${escapeXml(todo)}`);
4615
- lines.push(" </todos>");
4616
- }
4617
- }
4618
- }
4619
- lines.push(" </session>");
4620
- sessionBlocks.push(lines.join("\n"));
4623
+ blocks[session2.id] = buildSessionBlock(cwd, session2);
4621
4624
  }
4622
- if (sessionBlocks.length === 0) {
4623
- return "<sessions>No sessions found.</sessions>";
4625
+ return blocks;
4626
+ }
4627
+ function renderFullContext(blocks) {
4628
+ const entries = Object.values(blocks);
4629
+ if (entries.length === 0) return "<sessions>No sessions found.</sessions>";
4630
+ return ["<sessions>", ...entries, "</sessions>"].join("\n");
4631
+ }
4632
+ function renderContextDelta(prev, next) {
4633
+ const ids = /* @__PURE__ */ new Set([...Object.keys(prev), ...Object.keys(next)]);
4634
+ const entries = [];
4635
+ for (const id of ids) {
4636
+ const p = prev[id];
4637
+ const n = next[id];
4638
+ if (p === void 0 && n !== void 0) {
4639
+ entries.push(n.replace(/^(\s*)<session /, `$1<session change="added" `));
4640
+ } else if (p !== void 0 && n === void 0) {
4641
+ entries.push(` <session id="${escapeXml(id)}" change="removed" />`);
4642
+ } else if (p !== n && n !== void 0) {
4643
+ entries.push(n.replace(/^(\s*)<session /, `$1<session change="updated" `));
4644
+ }
4624
4645
  }
4625
- return ["<sessions>", ...sessionBlocks, "</sessions>"].join("\n");
4646
+ if (entries.length === 0) return null;
4647
+ return ["<sessions-changed-since-last-prompt>", ...entries, "</sessions-changed-since-last-prompt>"].join("\n");
4626
4648
  }
4627
4649
  function buildSessionContext(session2, cwd) {
4628
4650
  const goal = readFileSafe(goalPath(cwd, session2.id));
@@ -8996,7 +9018,9 @@ function renderRequirementsMarkdown(json) {
8996
9018
  }
8997
9019
 
8998
9020
  // src/cli/commands/companion.ts
8999
- import { basename as basename5 } from "path";
9021
+ import { basename as basename5, dirname as dirname11, join as join28 } from "path";
9022
+ import { mkdirSync as mkdirSync14, readFileSync as readFileSync30, writeFileSync as writeFileSync17 } from "fs";
9023
+ init_paths();
9000
9024
 
9001
9025
  // src/shared/companion-render.ts
9002
9026
  import stringWidth from "string-width";
@@ -10678,9 +10702,25 @@ function registerCompanion(program2) {
10678
10702
  companion.command("memory").description("Show accumulated companion observations grouped by category").option("--repo <path>", "Filter observations by repo path").action(async (opts) => {
10679
10703
  await runCompanionMemory(opts);
10680
10704
  });
10681
- companion.command("context").description("Output session context JSON for companion hook").option("--cwd <path>", "Project directory", process.cwd()).action((opts) => {
10682
- const context = buildCompanionContext(opts.cwd);
10683
- process.stdout.write(JSON.stringify({ additionalContext: context }));
10705
+ companion.command("context").description("Emit per-prompt context for the companion plugin hook. Caches the last emission per claude session and writes only the delta on subsequent calls (or nothing, when unchanged).").requiredOption("--cwd <path>", "Project directory whose sessions to summarise").requiredOption("--session-id <id>", "Claude session id (from the UserPromptSubmit stdin payload) \u2014 keys the per-session cache").action((opts) => {
10706
+ const cachePath = join28(globalDir(), "companion-context-cache", `${opts.sessionId}.json`);
10707
+ let prev = {};
10708
+ try {
10709
+ prev = JSON.parse(readFileSync30(cachePath, "utf-8"));
10710
+ } catch {
10711
+ prev = {};
10712
+ }
10713
+ const next = buildCompanionContextBlocks(opts.cwd);
10714
+ const hadPrev = Object.keys(prev).length > 0;
10715
+ if (hadPrev) {
10716
+ const delta = renderContextDelta(prev, next);
10717
+ if (delta === null) return;
10718
+ process.stdout.write(delta);
10719
+ } else {
10720
+ process.stdout.write(renderFullContext(next));
10721
+ }
10722
+ mkdirSync14(dirname11(cachePath), { recursive: true });
10723
+ writeFileSync17(cachePath, JSON.stringify(next), "utf-8");
10684
10724
  });
10685
10725
  companion.command("pane").description("Open (or focus) a side claude pane next to the dashboard").option("--cwd <path>", "Project directory", process.cwd()).action(async (opts) => {
10686
10726
  const { openCompanionPane: openCompanionPane2 } = await Promise.resolve().then(() => (init_tmux(), tmux_exports));
@@ -10703,14 +10743,14 @@ function registerCompanion(program2) {
10703
10743
 
10704
10744
  // src/cli/commands/deploy.ts
10705
10745
  import { homedir as homedir13 } from "os";
10706
- import { join as join28 } from "path";
10746
+ import { join as join29 } from "path";
10707
10747
 
10708
10748
  // src/cli/deploy/runner.ts
10709
10749
  init_paths();
10710
10750
  init_exec();
10711
10751
  init_creds();
10712
10752
  import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
10713
- import { copyFileSync as copyFileSync2, existsSync as existsSync30, mkdirSync as mkdirSync15, readFileSync as readFileSync32 } from "fs";
10753
+ import { copyFileSync as copyFileSync2, existsSync as existsSync30, mkdirSync as mkdirSync16, readFileSync as readFileSync33 } from "fs";
10714
10754
 
10715
10755
  // src/cli/deploy/pricing.ts
10716
10756
  var LAST_VERIFIED = "2026-05-06";
@@ -10745,12 +10785,12 @@ function formatCostLine(provider, instanceType) {
10745
10785
  // src/cli/deploy/runtime.ts
10746
10786
  init_atomic();
10747
10787
  init_paths();
10748
- import { existsSync as existsSync28, readFileSync as readFileSync31, unlinkSync as unlinkSync4 } from "fs";
10788
+ import { existsSync as existsSync28, readFileSync as readFileSync32, unlinkSync as unlinkSync4 } from "fs";
10749
10789
  function readRuntimeState(provider) {
10750
10790
  const path = deployRuntimePath(provider);
10751
10791
  if (!existsSync28(path)) return null;
10752
10792
  try {
10753
- return JSON.parse(readFileSync31(path, "utf-8"));
10793
+ return JSON.parse(readFileSync32(path, "utf-8"));
10754
10794
  } catch {
10755
10795
  return null;
10756
10796
  }
@@ -10811,10 +10851,10 @@ function isTailscaleAvailable() {
10811
10851
 
10812
10852
  // src/cli/deploy/templates.ts
10813
10853
  import { existsSync as existsSync29 } from "fs";
10814
- import { dirname as dirname11, resolve as resolve10 } from "path";
10854
+ import { dirname as dirname12, resolve as resolve10 } from "path";
10815
10855
  import { fileURLToPath as fileURLToPath4 } from "url";
10816
10856
  function deployRoot() {
10817
- const here = dirname11(fileURLToPath4(import.meta.url));
10857
+ const here = dirname12(fileURLToPath4(import.meta.url));
10818
10858
  const bundled = resolve10(here, "..", "deploy");
10819
10859
  if (existsSync29(bundled)) return bundled;
10820
10860
  const sourceRoot = resolve10(here, "..", "..", "..", "deploy");
@@ -11002,7 +11042,7 @@ function ensureTerraformInstalled() {
11002
11042
  function ensureProviderStateDir(provider) {
11003
11043
  ensureDeployDir();
11004
11044
  const dir = deployProviderDir(provider);
11005
- if (!existsSync30(dir)) mkdirSync15(dir, { recursive: true, mode: 448 });
11045
+ if (!existsSync30(dir)) mkdirSync16(dir, { recursive: true, mode: 448 });
11006
11046
  }
11007
11047
  function backupState(provider) {
11008
11048
  const src = deployStatePath(provider);
@@ -11017,7 +11057,7 @@ function readSshPubkey(path) {
11017
11057
  or pass --ssh-key <path>.`
11018
11058
  );
11019
11059
  }
11020
- return readFileSync32(path, "utf-8").trim();
11060
+ return readFileSync33(path, "utf-8").trim();
11021
11061
  }
11022
11062
  function readOutputs(provider) {
11023
11063
  const result = spawnSync3("terraform", ["output", "-json", `-state=${deployStatePath(provider)}`], {
@@ -11295,7 +11335,7 @@ function registerDeploy(program2) {
11295
11335
  });
11296
11336
  for (const provider of PROVIDERS) {
11297
11337
  const sub = deploy.command(provider).description(`${provider} commands.`);
11298
- sub.command("up").description(`Provision the ${provider} box (terraform init \u2192 plan \u2192 apply).`).option("--region <region>", `Provider region (defaults: hetzner=nbg1, aws=us-east-1).`).option("--arch <arch>", "'arm' (default) or 'x86'. Picks the default --size and image.", "arm").option("--size <size>", "Instance type override (defaults follow --arch).").option("--ssh-key <path>", "Path to SSH public key.", join28(homedir13(), ".ssh", "id_ed25519.pub")).option("--no-chromium", "Skip headless Chromium install.").option("--no-auto-update", "Skip the daily auto-update systemd timer.").option("--name <name>", "Box hostname / Tailscale node name.", "sisyphus").option("-y, --yes", "Skip the re-provision confirmation prompt when state already exists.").action(async (raw) => {
11338
+ sub.command("up").description(`Provision the ${provider} box (terraform init \u2192 plan \u2192 apply).`).option("--region <region>", `Provider region (defaults: hetzner=nbg1, aws=us-east-1).`).option("--arch <arch>", "'arm' (default) or 'x86'. Picks the default --size and image.", "arm").option("--size <size>", "Instance type override (defaults follow --arch).").option("--ssh-key <path>", "Path to SSH public key.", join29(homedir13(), ".ssh", "id_ed25519.pub")).option("--no-chromium", "Skip headless Chromium install.").option("--no-auto-update", "Skip the daily auto-update systemd timer.").option("--name <name>", "Box hostname / Tailscale node name.", "sisyphus").option("-y, --yes", "Skip the re-provision confirmation prompt when state already exists.").action(async (raw) => {
11299
11339
  const opts = resolveUpOptions(provider, raw);
11300
11340
  await deployUp(provider, opts);
11301
11341
  });
@@ -11380,7 +11420,7 @@ function ensureGroveRegistered(provider, repo, instancePath) {
11380
11420
  init_exec();
11381
11421
  import { spawnSync as spawnSync5 } from "child_process";
11382
11422
  import { existsSync as existsSync31 } from "fs";
11383
- import { basename as basename6, join as join29 } from "path";
11423
+ import { basename as basename6, join as join30 } from "path";
11384
11424
  function captureGit(args2) {
11385
11425
  const result = spawnSync5("git", args2, {
11386
11426
  encoding: "utf-8",
@@ -11431,10 +11471,10 @@ function buildRsyncArgs(localDir, remoteTarget) {
11431
11471
  ];
11432
11472
  }
11433
11473
  function detectPackageManager(toplevel) {
11434
- if (existsSync31(join29(toplevel, "pnpm-lock.yaml"))) return "pnpm";
11435
- if (existsSync31(join29(toplevel, "bun.lockb"))) return "bun";
11436
- if (existsSync31(join29(toplevel, "yarn.lock"))) return "yarn";
11437
- if (existsSync31(join29(toplevel, "package-lock.json"))) return "npm";
11474
+ if (existsSync31(join30(toplevel, "pnpm-lock.yaml"))) return "pnpm";
11475
+ if (existsSync31(join30(toplevel, "bun.lockb"))) return "bun";
11476
+ if (existsSync31(join30(toplevel, "yarn.lock"))) return "yarn";
11477
+ if (existsSync31(join30(toplevel, "package-lock.json"))) return "npm";
11438
11478
  return null;
11439
11479
  }
11440
11480
  function packageManagerInstallCmd(pm) {
@@ -11719,7 +11759,7 @@ function attachNotify(diagnostic2) {
11719
11759
  // src/cli/commands/tmux-sessions.ts
11720
11760
  init_paths();
11721
11761
  import { execSync as execSync18 } from "child_process";
11722
- import { readFileSync as readFileSync33, existsSync as existsSync32 } from "fs";
11762
+ import { readFileSync as readFileSync34, existsSync as existsSync32 } from "fs";
11723
11763
  var DOT_MAP = {
11724
11764
  "orchestrator:processing": { icon: "\u25CF", color: "#d4ad6a" },
11725
11765
  "orchestrator:idle": { icon: "\u25CF", color: "#d47766" },
@@ -11732,7 +11772,7 @@ function readManifest() {
11732
11772
  const p = sessionsManifestPath();
11733
11773
  if (!existsSync32(p)) return null;
11734
11774
  try {
11735
- return JSON.parse(readFileSync33(p, "utf-8"));
11775
+ return JSON.parse(readFileSync34(p, "utf-8"));
11736
11776
  } catch {
11737
11777
  return null;
11738
11778
  }
@@ -11778,7 +11818,7 @@ if (nodeVersion < 22) {
11778
11818
  var program = new Command();
11779
11819
  program.name("sis").description("tmux-integrated orchestration daemon for Claude Code").version(
11780
11820
  JSON.parse(
11781
- readFileSync34(join30(dirname12(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf-8")
11821
+ readFileSync35(join31(dirname13(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf-8")
11782
11822
  ).version
11783
11823
  );
11784
11824
  program.configureHelp({
@@ -11853,7 +11893,7 @@ var args = process.argv.slice(2);
11853
11893
  var firstArg = args[0];
11854
11894
  var skipWelcome = ["admin", "help", "--help", "-h", "--version", "-V"];
11855
11895
  if (!existsSync33(globalDir()) && firstArg && !skipWelcome.includes(firstArg)) {
11856
- mkdirSync16(globalDir(), { recursive: true });
11896
+ mkdirSync17(globalDir(), { recursive: true });
11857
11897
  console.log("");
11858
11898
  console.log(" Welcome to Sisyphus. Run 'sis admin setup' to get started.");
11859
11899
  console.log("");