@shipers-dev/multi 0.13.0 → 0.15.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.
Files changed (2) hide show
  1. package/dist/index.js +85 -62
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -15575,7 +15575,26 @@ class RequestError extends Error {
15575
15575
 
15576
15576
  // src/acp-runner.ts
15577
15577
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
15578
- import { dirname } from "path";
15578
+ import { dirname, join } from "path";
15579
+ function ensureBypassPermissions(cwd) {
15580
+ try {
15581
+ const dir = join(cwd, ".claude");
15582
+ if (!existsSync(dir))
15583
+ mkdirSync(dir, { recursive: true });
15584
+ const path = join(dir, "settings.local.json");
15585
+ let json2 = {};
15586
+ if (existsSync(path)) {
15587
+ try {
15588
+ json2 = JSON.parse(readFileSync(path, "utf8"));
15589
+ } catch {
15590
+ json2 = {};
15591
+ }
15592
+ }
15593
+ json2.permissions = { ...json2.permissions || {}, defaultMode: "bypassPermissions" };
15594
+ writeFileSync(path, JSON.stringify(json2, null, 2) + `
15595
+ `, "utf8");
15596
+ } catch {}
15597
+ }
15579
15598
  function fmtErr(e) {
15580
15599
  if (e == null)
15581
15600
  return "unknown error";
@@ -15605,11 +15624,13 @@ async function runAcp(opts) {
15605
15624
  cleanEnv[k] = v;
15606
15625
  }
15607
15626
  const argv = Array.isArray(opts.adapterBin) ? opts.adapterBin : [opts.adapterBin];
15608
- const permMode = process.env.MULTI_CLAUDE_SAFE === "1" ? "default" : "bypassPermissions";
15627
+ const cwd = opts.cwd || process.cwd();
15628
+ if (process.env.MULTI_CLAUDE_SAFE !== "1")
15629
+ ensureBypassPermissions(cwd);
15609
15630
  const child = Bun.spawn(argv, {
15610
15631
  stdio: ["pipe", "pipe", "inherit"],
15611
- cwd: opts.cwd || process.cwd(),
15612
- env: { ...cleanEnv, ACP_PERMISSION_MODE: permMode }
15632
+ cwd,
15633
+ env: cleanEnv
15613
15634
  });
15614
15635
  try {
15615
15636
  opts.onSpawn?.(child);
@@ -15845,9 +15866,9 @@ function extractText(content) {
15845
15866
 
15846
15867
  // src/acpx-runner.ts
15847
15868
  import { appendFileSync } from "fs";
15848
- import { join } from "path";
15869
+ import { join as join2 } from "path";
15849
15870
  var HOME = process.env.HOME || process.env.USERPROFILE || ".";
15850
- var LOG_PATH = join(HOME, ".multi", "logs", "agent.log");
15871
+ var LOG_PATH = join2(HOME, ".multi", "logs", "agent.log");
15851
15872
  function dlog(msg) {
15852
15873
  try {
15853
15874
  appendFileSync(LOG_PATH, `[${new Date().toISOString()}] ${msg}
@@ -16053,7 +16074,7 @@ var StreamEventInputSchema = exports_external.object({
16053
16074
  // src/worktree.ts
16054
16075
  import { spawn } from "child_process";
16055
16076
  import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
16056
- import { join as join2 } from "path";
16077
+ import { join as join3 } from "path";
16057
16078
  async function run(cwd, cmd, args) {
16058
16079
  return await new Promise((resolve) => {
16059
16080
  const p = spawn(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
@@ -16080,7 +16101,7 @@ async function branchExists(dir, branch) {
16080
16101
  return r.code === 0;
16081
16102
  }
16082
16103
  function ensureGitignoreEntry(workingDir, entry) {
16083
- const gip = join2(workingDir, ".gitignore");
16104
+ const gip = join3(workingDir, ".gitignore");
16084
16105
  let body = "";
16085
16106
  try {
16086
16107
  body = existsSync2(gip) ? readFileSync2(gip, "utf8") : "";
@@ -16109,8 +16130,8 @@ async function ensureWorktree(workingDir, issueKey) {
16109
16130
  ensureGitignoreEntry(workingDir, ".multi/");
16110
16131
  const key = normalizeKey(issueKey);
16111
16132
  const branch = `multi/${key}`;
16112
- const wtDir = join2(workingDir, ".multi", "worktrees");
16113
- const wtPath = join2(wtDir, key);
16133
+ const wtDir = join3(workingDir, ".multi", "worktrees");
16134
+ const wtPath = join3(wtDir, key);
16114
16135
  if (existsSync2(wtPath)) {
16115
16136
  return { path: wtPath, branch, created: false };
16116
16137
  }
@@ -16128,15 +16149,15 @@ async function ensureWorktree(workingDir, issueKey) {
16128
16149
 
16129
16150
  // src/materializer.ts
16130
16151
  import { mkdirSync as mkdirSync3, existsSync as existsSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync3, rmSync, symlinkSync, lstatSync } from "fs";
16131
- import { join as join3, dirname as dirname2 } from "path";
16152
+ import { join as join4, dirname as dirname2 } from "path";
16132
16153
  var HOME2 = process.env.HOME || process.env.USERPROFILE || ".";
16133
- var MULTI_DIR = join3(HOME2, ".multi");
16134
- var MULTI_SKILLS = join3(MULTI_DIR, "skills");
16135
- var MULTI_AGENTS = join3(MULTI_DIR, "agents");
16136
- var STATE_PATH = join3(MULTI_DIR, "materialized.json");
16137
- var CLAUDE_DIR = join3(HOME2, ".claude");
16138
- var CLAUDE_SKILLS = join3(CLAUDE_DIR, "skills");
16139
- var CLAUDE_AGENTS = join3(CLAUDE_DIR, "agents");
16154
+ var MULTI_DIR = join4(HOME2, ".multi");
16155
+ var MULTI_SKILLS = join4(MULTI_DIR, "skills");
16156
+ var MULTI_AGENTS = join4(MULTI_DIR, "agents");
16157
+ var STATE_PATH = join4(MULTI_DIR, "materialized.json");
16158
+ var CLAUDE_DIR = join4(HOME2, ".claude");
16159
+ var CLAUDE_SKILLS = join4(CLAUDE_DIR, "skills");
16160
+ var CLAUDE_AGENTS = join4(CLAUDE_DIR, "agents");
16140
16161
  var MARKER = ".multi-managed";
16141
16162
  function slugify2(s) {
16142
16163
  return s.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "unnamed";
@@ -16161,7 +16182,7 @@ function safeRmManaged(path) {
16161
16182
  rmSync(path);
16162
16183
  return;
16163
16184
  }
16164
- if (st.isDirectory() && existsSync3(join3(path, MARKER))) {
16185
+ if (st.isDirectory() && existsSync3(join4(path, MARKER))) {
16165
16186
  rmSync(path, { recursive: true, force: true });
16166
16187
  return;
16167
16188
  }
@@ -16173,24 +16194,24 @@ function safeRmManaged(path) {
16173
16194
  } catch {}
16174
16195
  }
16175
16196
  function writeManagedSkill(slug, skill) {
16176
- const dir = join3(MULTI_SKILLS, slug);
16197
+ const dir = join4(MULTI_SKILLS, slug);
16177
16198
  mkdirSync3(dir, { recursive: true });
16178
- writeFileSync3(join3(dir, MARKER), `skill_id=${skill.id}
16199
+ writeFileSync3(join4(dir, MARKER), `skill_id=${skill.id}
16179
16200
  revision=${Date.now()}
16180
16201
  `);
16181
16202
  if (skill.body)
16182
- writeFileSync3(join3(dir, "SKILL.md"), skill.body);
16203
+ writeFileSync3(join4(dir, "SKILL.md"), skill.body);
16183
16204
  for (const f of skill.files || []) {
16184
16205
  if (f.path.includes("..") || f.path.startsWith("/"))
16185
16206
  continue;
16186
16207
  if (f.path === MARKER)
16187
16208
  continue;
16188
- const out = join3(dir, f.path);
16209
+ const out = join4(dir, f.path);
16189
16210
  mkdirSync3(dirname2(out), { recursive: true });
16190
16211
  writeFileSync3(out, f.content);
16191
16212
  }
16192
16213
  mkdirSync3(CLAUDE_SKILLS, { recursive: true });
16193
- const link = join3(CLAUDE_SKILLS, slug);
16214
+ const link = join4(CLAUDE_SKILLS, slug);
16194
16215
  safeRmManaged(link);
16195
16216
  if (!existsSync3(link)) {
16196
16217
  try {
@@ -16202,7 +16223,7 @@ function writeManagedAgent(slug, agent, skillsForAgent) {
16202
16223
  if (agent.type !== "claude-code")
16203
16224
  return;
16204
16225
  mkdirSync3(CLAUDE_AGENTS, { recursive: true });
16205
- const out = join3(CLAUDE_AGENTS, `${slug}.md`);
16226
+ const out = join4(CLAUDE_AGENTS, `${slug}.md`);
16206
16227
  safeRmManaged(out);
16207
16228
  const tools = agent.allowed_tools ? `
16208
16229
  tools: ${agent.allowed_tools}` : "";
@@ -16248,13 +16269,13 @@ async function materializeBundle(apiUrl, deviceId, log) {
16248
16269
  }
16249
16270
  for (const old of prev.skill_slugs) {
16250
16271
  if (!newSkillSlugs.includes(old)) {
16251
- safeRmManaged(join3(MULTI_SKILLS, old));
16252
- safeRmManaged(join3(CLAUDE_SKILLS, old));
16272
+ safeRmManaged(join4(MULTI_SKILLS, old));
16273
+ safeRmManaged(join4(CLAUDE_SKILLS, old));
16253
16274
  }
16254
16275
  }
16255
16276
  for (const old of prev.agent_slugs) {
16256
16277
  if (!newAgentSlugs.includes(old)) {
16257
- safeRmManaged(join3(CLAUDE_AGENTS, `${old}.md`));
16278
+ safeRmManaged(join4(CLAUDE_AGENTS, `${old}.md`));
16258
16279
  }
16259
16280
  }
16260
16281
  saveState({ revision: bundle.revision, skill_slugs: newSkillSlugs, agent_slugs: newAgentSlugs });
@@ -16268,11 +16289,11 @@ function lastMaterializedRevision() {
16268
16289
  // src/index.ts
16269
16290
  import { parseArgs } from "util";
16270
16291
  import { mkdirSync as mkdirSync4, existsSync as existsSync4, writeFileSync as writeFileSync4, readFileSync as readFileSync4, appendFileSync as appendFileSync2, unlinkSync, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
16271
- import { join as join4, dirname as dirname3 } from "path";
16292
+ import { join as join5, dirname as dirname3 } from "path";
16272
16293
  // package.json
16273
16294
  var package_default = {
16274
16295
  name: "@shipers-dev/multi",
16275
- version: "0.13.0",
16296
+ version: "0.14.0",
16276
16297
  type: "module",
16277
16298
  bin: {
16278
16299
  "multi-agent": "./dist/index.js"
@@ -16295,14 +16316,14 @@ var package_default = {
16295
16316
 
16296
16317
  // src/index.ts
16297
16318
  var HOME3 = process.env.HOME || process.env.USERPROFILE || ".";
16298
- var MULTI_DIR2 = join4(HOME3, ".multi");
16299
- var CONFIG_PATH = join4(MULTI_DIR2, "config.json");
16300
- var PID_PATH = join4(MULTI_DIR2, "agent.pid");
16301
- var PORT_PATH = join4(MULTI_DIR2, "agent.port");
16302
- var LOG_PATH2 = join4(MULTI_DIR2, "logs", "agent.log");
16303
- var SKILLS_DIR = join4(MULTI_DIR2, "skills");
16304
- var STOP_PATH = join4(MULTI_DIR2, "stop.flag");
16305
- var TASKS_DB_PATH = join4(MULTI_DIR2, "tasks.db");
16319
+ var MULTI_DIR2 = join5(HOME3, ".multi");
16320
+ var CONFIG_PATH = join5(MULTI_DIR2, "config.json");
16321
+ var PID_PATH = join5(MULTI_DIR2, "agent.pid");
16322
+ var PORT_PATH = join5(MULTI_DIR2, "agent.port");
16323
+ var LOG_PATH2 = join5(MULTI_DIR2, "logs", "agent.log");
16324
+ var SKILLS_DIR = join5(MULTI_DIR2, "skills");
16325
+ var STOP_PATH = join5(MULTI_DIR2, "stop.flag");
16326
+ var TASKS_DB_PATH = join5(MULTI_DIR2, "tasks.db");
16306
16327
  var VERSION = package_default.version;
16307
16328
  var COMMANDS = {
16308
16329
  setup: "Register this device with a workspace",
@@ -16315,7 +16336,7 @@ var COMMANDS = {
16315
16336
  reset: "Reset acpx session for an issue (--issue <id>)"
16316
16337
  };
16317
16338
  function ensureDirs() {
16318
- for (const d of [MULTI_DIR2, join4(MULTI_DIR2, "logs"), SKILLS_DIR]) {
16339
+ for (const d of [MULTI_DIR2, join5(MULTI_DIR2, "logs"), SKILLS_DIR]) {
16319
16340
  if (!existsSync4(d))
16320
16341
  mkdirSync4(d, { recursive: true });
16321
16342
  }
@@ -17030,13 +17051,13 @@ async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
17030
17051
  await postStream(apiUrl, issueId, "progress", { message: `Device ${deviceId} picked up ${isFollowup ? "follow-up" : "task"}` });
17031
17052
  let attachmentRefs = [];
17032
17053
  if (task.from_comment_id) {
17033
- const baseDir = workingDir || join4(MULTI_DIR2, "tmp", issueId);
17034
- const inDir = join4(baseDir, ".multi-in", task.from_comment_id);
17054
+ const baseDir = workingDir || join5(MULTI_DIR2, "tmp", issueId);
17055
+ const inDir = join5(baseDir, ".multi-in", task.from_comment_id);
17035
17056
  attachmentRefs = await downloadCommentAttachments(apiUrl, task.from_comment_id, inDir);
17036
17057
  if (attachmentRefs.length)
17037
17058
  log(` fetched ${attachmentRefs.length} attachment(s) \u2192 ${inDir}`);
17038
17059
  }
17039
- const outDir = join4(workingDir || join4(MULTI_DIR2, "tmp", issueId), ".multi-out");
17060
+ const outDir = join5(workingDir || join5(MULTI_DIR2, "tmp", issueId), ".multi-out");
17040
17061
  let liveCommentId;
17041
17062
  let liveBody = "";
17042
17063
  let hadError = false;
@@ -17762,23 +17783,25 @@ async function resolveAcpAdapter(agentType, detectedPath) {
17762
17783
  if (agentType === "pi" && detectedPath && existsSync4(detectedPath)) {
17763
17784
  return [detectedPath, "--mode", "rpc"];
17764
17785
  }
17765
- const adapterName = "claude-agent-acp";
17766
- const candidates = [
17767
- join4(HOME3, ".bun", "install", "global", "node_modules", ".bin", adapterName)
17768
- ];
17769
- try {
17770
- const here = new URL(import.meta.url).pathname;
17771
- let dir = here;
17772
- for (let i = 0;i < 8; i++) {
17773
- dir = dirname3(dir);
17774
- const bin = join4(dir, "node_modules", ".bin", adapterName);
17775
- if (existsSync4(bin))
17776
- return [bin];
17777
- }
17778
- } catch {}
17779
- for (const c of candidates)
17780
- if (existsSync4(c))
17781
- return [c];
17786
+ const override = process.env.MULTI_ACP_ADAPTER?.trim();
17787
+ const adapterNames = override ? [override] : ["claude-code-acp", "claude-agent-acp"];
17788
+ for (const name of adapterNames) {
17789
+ if (name.startsWith("/") && existsSync4(name))
17790
+ return [name];
17791
+ try {
17792
+ const here = new URL(import.meta.url).pathname;
17793
+ let dir = here;
17794
+ for (let i = 0;i < 8; i++) {
17795
+ dir = dirname3(dir);
17796
+ const bin = join5(dir, "node_modules", ".bin", name);
17797
+ if (existsSync4(bin))
17798
+ return [bin];
17799
+ }
17800
+ } catch {}
17801
+ const global = join5(HOME3, ".bun", "install", "global", "node_modules", ".bin", name);
17802
+ if (existsSync4(global))
17803
+ return [global];
17804
+ }
17782
17805
  return null;
17783
17806
  }
17784
17807
  async function ackDispatch(apiUrl, dispatchId, secret) {
@@ -17829,7 +17852,7 @@ async function postStream(apiUrl, issueId, event_type, payload) {
17829
17852
  try {
17830
17853
  ensureDirs();
17831
17854
  const date5 = new Date().toISOString().slice(0, 10);
17832
- const path = join4(MULTI_DIR2, "logs", `events-${date5}.ndjson`);
17855
+ const path = join5(MULTI_DIR2, "logs", `events-${date5}.ndjson`);
17833
17856
  appendFileSync2(path, JSON.stringify({ ts: Date.now(), issue_id: issueId, event_type, payload }) + `
17834
17857
  `);
17835
17858
  } catch {}
@@ -17852,7 +17875,7 @@ async function downloadCommentAttachments(apiUrl, commentId, destDir) {
17852
17875
  continue;
17853
17876
  const buf = new Uint8Array(await res.arrayBuffer());
17854
17877
  const safe = it.filename.replace(/[^A-Za-z0-9._-]/g, "_");
17855
- const p = join4(destDir, safe);
17878
+ const p = join5(destDir, safe);
17856
17879
  writeFileSync4(p, buf);
17857
17880
  out.push({ filename: it.filename, path: p });
17858
17881
  }
@@ -17873,7 +17896,7 @@ async function uploadOutputDir(apiUrl, commentId, dir) {
17873
17896
  if (depth > 3)
17874
17897
  return;
17875
17898
  for (const name of readdirSync2(d)) {
17876
- const p = join4(d, name);
17899
+ const p = join5(d, name);
17877
17900
  try {
17878
17901
  const st = statSync2(p);
17879
17902
  if (st.isDirectory())
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipers-dev/multi",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "multi-agent": "./dist/index.js"