patchcord 0.5.100 → 0.5.103

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/bin/patchcord.mjs CHANGED
@@ -869,7 +869,7 @@ if (cmd === "main" || cmd === "master" || cmd === "provision" || cmd === "team")
869
869
  const sub = process.argv[3];
870
870
  if (sub === "connect") {
871
871
  const harness = flagVal("harness", flagVal("tool", ""));
872
- const create = run(`curl -sf --max-time 10 -X POST "${DEFAULT_API}/api/master/session" -H "Content-Type: application/json" -d '{"harness":"${harness}"}'`);
872
+ const create = run(`curl -sf --max-time 10 -X POST "${DEFAULT_API}/api/main/session" -H "Content-Type: application/json" -d '{"harness":"${harness}"}'`);
873
873
  let sessionId = "";
874
874
  try { sessionId = (JSON.parse(create).session_id) || ""; } catch {}
875
875
  if (!sessionId) { console.error("Could not start main-auth session."); process.exit(1); }
@@ -904,7 +904,7 @@ if (cmd === "main" || cmd === "master" || cmd === "provision" || cmd === "team")
904
904
  }
905
905
  if (sub === "whoami") {
906
906
  const m = requireMain();
907
- const { status, json } = await _httpJSON("GET", `${m.baseUrl}/api/master/whoami`, m.token);
907
+ const { status, json } = await _httpJSON("GET", `${m.baseUrl}/api/main/whoami`, m.token);
908
908
  if (status !== "200" || !json) { console.error(`main whoami failed (HTTP ${status})`); process.exit(1); }
909
909
  console.log(`main · user ${json.user_id} · ${json.agents}/${json.quota} agents`);
910
910
  process.exit(0);
@@ -1458,22 +1458,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1458
1458
  // Kimi CLI
1459
1459
  const hasKimi = run("which kimi");
1460
1460
  if (hasKimi) {
1461
- // Clean up old skill names (hyphen-style combined skill)
1462
- const oldKimiSkillDir = join(HOME, ".kimi", "skills", "patchcord");
1463
- const oldKimiWaitDir = join(HOME, ".kimi", "skills", "patchcord-wait");
1464
- if (existsSync(oldKimiSkillDir)) {
1465
- rmSync(oldKimiSkillDir, { recursive: true, force: true });
1466
- }
1467
- if (existsSync(oldKimiWaitDir)) {
1468
- rmSync(oldKimiWaitDir, { recursive: true, force: true });
1469
- }
1470
-
1471
- // Install three focused Kimi skills
1472
- const kimiInboxDir = join(HOME, ".kimi", "skills", "patchcord:inbox");
1473
- const kimiWaitDir = join(HOME, ".kimi", "skills", "patchcord:wait");
1474
- const kimiSubDir = join(HOME, ".kimi", "skills", "patchcord:subscribe");
1475
1461
  let kimiChanged = false;
1476
-
1477
1462
  const installKimiSkill = (destDir, relSrc) => {
1478
1463
  const src = join(pluginRoot, "per-project-skills", "kimi", relSrc, "SKILL.md");
1479
1464
  const dest = join(destDir, "SKILL.md");
@@ -1482,9 +1467,24 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
1482
1467
  copyFileSync(src, dest);
1483
1468
  return changed;
1484
1469
  };
1485
- kimiChanged = installKimiSkill(kimiInboxDir, "inbox") || kimiChanged;
1486
- kimiChanged = installKimiSkill(kimiWaitDir, "wait") || kimiChanged;
1487
- kimiChanged = installKimiSkill(kimiSubDir, "subscribe") || kimiChanged;
1470
+ // Kimi CLI (Python) reads ~/.kimi/skills; Kimi Code (Node) reads
1471
+ // ~/.kimi-code/skills. Install into BOTH user dirs that exist — a global
1472
+ // `npx patchcord` must fix whichever Kimi the user runs. Clean up the old
1473
+ // combined skill AND colon-named flow skills (Kimi requires names to be
1474
+ // lowercase/digits/hyphens only — a colon kept them out of /flow:, showing
1475
+ // them only under /skill:). Renamed to patchcord-inbox etc.
1476
+ const kimiCodeHome = process.env.KIMI_CODE_HOME || join(HOME, ".kimi-code");
1477
+ const kimiSkillRoots = [join(HOME, ".kimi", "skills")];
1478
+ if (existsSync(kimiCodeHome)) kimiSkillRoots.push(join(kimiCodeHome, "skills"));
1479
+ for (const root of kimiSkillRoots) {
1480
+ for (const stale of ["patchcord", "patchcord-wait", "patchcord:inbox", "patchcord:wait", "patchcord:subscribe"]) {
1481
+ const d = join(root, stale);
1482
+ if (existsSync(d)) { try { rmSync(d, { recursive: true, force: true }); } catch {} }
1483
+ }
1484
+ kimiChanged = installKimiSkill(join(root, "patchcord-inbox"), "inbox") || kimiChanged;
1485
+ kimiChanged = installKimiSkill(join(root, "patchcord-wait"), "wait") || kimiChanged;
1486
+ kimiChanged = installKimiSkill(join(root, "patchcord-subscribe"), "subscribe") || kimiChanged;
1487
+ }
1488
1488
 
1489
1489
  // Install/update stop hook — fires after each Kimi turn to check inbox
1490
1490
  let hookChanged = false;
@@ -2333,14 +2333,20 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
2333
2333
  console.log(`\n ${green}✓${r} Kimi Code configured: ${dim}${kcPath}${r}`);
2334
2334
  console.log(` ${dim}Kimi Code auto-loads project-local .kimi-code/mcp.json — just run ${r}${bold}kimi${r}${dim} in this project (no wrapper needed).${r}`);
2335
2335
  }
2336
- // Skills → user-level Kimi Code skills dir
2336
+ // Skills → user-level Kimi Code skills dir. Hyphen names so they register
2337
+ // as /flow:patchcord-inbox (a colon kept them out of /flow:). Remove any
2338
+ // stale colon-named dirs from prior installs.
2337
2339
  try {
2338
- for (const [label, rel] of [["patchcord:inbox", "inbox"], ["patchcord:wait", "wait"], ["patchcord:subscribe", "subscribe"]]) {
2340
+ for (const stale of ["patchcord:inbox", "patchcord:wait", "patchcord:subscribe", "patchcord"]) {
2341
+ const d = join(kimiCodeHome, "skills", stale);
2342
+ if (existsSync(d)) { try { rmSync(d, { recursive: true, force: true }); } catch {} }
2343
+ }
2344
+ for (const [label, rel] of [["patchcord-inbox", "inbox"], ["patchcord-wait", "wait"], ["patchcord-subscribe", "subscribe"]]) {
2339
2345
  const dest = join(kimiCodeHome, "skills", label);
2340
2346
  mkdirSync(dest, { recursive: true });
2341
2347
  cpSync(join(pluginRoot, "per-project-skills", "kimi", rel, "SKILL.md"), join(dest, "SKILL.md"));
2342
2348
  }
2343
- console.log(` ${green}✓${r} Skills installed: ${dim}${join(kimiCodeHome, "skills")}${r}`);
2349
+ console.log(` ${green}✓${r} Skills installed: ${dim}${join(kimiCodeHome, "skills")}${r} ${dim}(/flow:patchcord-inbox)${r}`);
2344
2350
  } catch {}
2345
2351
  } else if (isKimi) {
2346
2352
  // Kimi CLI (Python): per-project .kimi/mcp.json + shell wrapper for --mcp-config-file
@@ -2360,22 +2366,22 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
2360
2366
  console.log(`\n ${green}✓${r} Kimi CLI configured: ${dim}${kimiPath}${r}`);
2361
2367
  console.log(` ${dim}Per-project — use the kimi-pc wrapper (see below) so Kimi loads this config.${r}`);
2362
2368
  }
2363
- // Install/update global skills (remove old combined skill, install three focused ones)
2364
- const oldKimiSkillDir = join(HOME, ".kimi", "skills", "patchcord");
2365
- const oldKimiWaitDir = join(HOME, ".kimi", "skills", "patchcord-wait");
2366
- if (existsSync(oldKimiSkillDir)) rmSync(oldKimiSkillDir, { recursive: true, force: true });
2367
- if (existsSync(oldKimiWaitDir)) rmSync(oldKimiWaitDir, { recursive: true, force: true });
2368
-
2369
- const kimiInboxDir = join(HOME, ".kimi", "skills", "patchcord:inbox");
2370
- const kimiWaitDir = join(HOME, ".kimi", "skills", "patchcord:wait");
2371
- const kimiSubDir = join(HOME, ".kimi", "skills", "patchcord:subscribe");
2369
+ // Install/update global skills. Hyphen names /flow:patchcord-inbox
2370
+ // (colon names never registered as flows). Remove old combined + colon dirs.
2371
+ for (const stale of ["patchcord", "patchcord-wait", "patchcord:inbox", "patchcord:wait", "patchcord:subscribe"]) {
2372
+ const d = join(HOME, ".kimi", "skills", stale);
2373
+ if (existsSync(d)) { try { rmSync(d, { recursive: true, force: true }); } catch {} }
2374
+ }
2375
+ const kimiInboxDir = join(HOME, ".kimi", "skills", "patchcord-inbox");
2376
+ const kimiWaitDir = join(HOME, ".kimi", "skills", "patchcord-wait");
2377
+ const kimiSubDir = join(HOME, ".kimi", "skills", "patchcord-subscribe");
2372
2378
  mkdirSync(kimiInboxDir, { recursive: true });
2373
2379
  mkdirSync(kimiWaitDir, { recursive: true });
2374
2380
  mkdirSync(kimiSubDir, { recursive: true });
2375
2381
  cpSync(join(pluginRoot, "per-project-skills", "kimi", "inbox", "SKILL.md"), join(kimiInboxDir, "SKILL.md"));
2376
2382
  cpSync(join(pluginRoot, "per-project-skills", "kimi", "wait", "SKILL.md"), join(kimiWaitDir, "SKILL.md"));
2377
2383
  cpSync(join(pluginRoot, "per-project-skills", "kimi", "subscribe", "SKILL.md"), join(kimiSubDir, "SKILL.md"));
2378
- console.log(` ${green}✓${r} Skills installed: ${dim}patchcord:inbox${r}, ${dim}patchcord:wait${r}, ${dim}patchcord:subscribe${r}`);
2384
+ console.log(` ${green}✓${r} Skills installed: ${dim}/flow:patchcord-inbox${r}, ${dim}-wait${r}, ${dim}-subscribe${r}`);
2379
2385
 
2380
2386
  // Install alias for per-project --mcp-config-file
2381
2387
  const aliasLine = `alias kimi-pc='kimi --mcp-config-file .kimi/mcp.json'`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.5.100",
3
+ "version": "0.5.103",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",
@@ -1,14 +1,14 @@
1
1
  ---
2
- name: patchcord:inbox
2
+ name: patchcord-inbox
3
3
  description: Read Patchcord inbox and reply to messages
4
4
  type: flow
5
5
  ---
6
6
 
7
- > Use `/flow:patchcord:inbox` to run this. `/skill:` only loads it as docs (Kimi design).
7
+ > Use `/flow:patchcord-inbox` to run this. `/skill:` only loads it as docs (Kimi design).
8
8
 
9
9
  ```mermaid
10
10
  flowchart TD
11
- A([BEGIN]) --> B[Call the inbox MCP tool. Reading the response: the first header line shows YOUR own identity (the recipient). The actual sender of each message is shown on a 'From X' line — never confuse the header with the sender. For each pending message, classify it: (1) ACK — short signals like 'thanks', 'noted', 'works', 'great', 'good', '👍', 'ok' with no task. Close silently with reply(message_id, resolve=true) and NO content. Never send a text reply to an ack — it creates infinite ack chains. (2) BLOCKED — you cannot do the work right now (busy, missing credentials, ambiguous target). Use reply(message_id, 'reason', defer=true) — the message stays in your inbox as a reminder. Never silently skip a message. (3) ACTIONABLE — do the work the message asks for first (edit the file, run the command, write the code), THEN call reply(message_id, 'concrete summary of what you did') with file paths and line numbers. Never reply 'will do' / 'understood' / 'ready' before doing the work.]
12
- B --> C[Call the Shell tool with these exact parameters to re-arm the listener: command='patchcord subscribe --kimi 5', run_in_background=true, timeout=86400, description='Patchcord listener'.]
11
+ A([BEGIN]) --> B["Call the inbox MCP tool. Reading the response: the first header line shows YOUR own identity (the recipient). The actual sender of each message is on a 'From X' line — never confuse the header with the sender. For each pending message, classify it: (1) ACK — short signals like thanks, noted, works, great, ok, 👍 with no task. Close silently with reply(message_id, resolve=true) and NO content. Never send a text reply to an ack — it creates infinite ack chains. (2) BLOCKED — you cannot do the work right now (busy, missing credentials, ambiguous target). Use reply(message_id, reason, defer=true) — the message stays in your inbox as a reminder. Never silently skip a message. (3) ACTIONABLE — do the work the message asks for first (edit the file, run the command, write the code), THEN call reply(message_id, concrete summary) with file paths and line numbers. Never reply will-do or understood before doing the work."]
12
+ B --> C["Call the Shell tool with these exact parameters to re-arm the listener: command=patchcord subscribe --kimi 5, run_in_background=true, timeout=86400, description=Patchcord listener."]
13
13
  C --> D([END])
14
14
  ```
@@ -1,14 +1,14 @@
1
1
  ---
2
- name: patchcord:subscribe
2
+ name: patchcord-subscribe
3
3
  description: Start the Patchcord background polling listener
4
4
  type: flow
5
5
  ---
6
6
 
7
- > Use `/flow:patchcord:subscribe` to run this. `/skill:` only loads it as docs (Kimi design).
7
+ > Use `/flow:patchcord-subscribe` to run this. `/skill:` only loads it as docs (Kimi design).
8
8
 
9
9
  ```mermaid
10
10
  flowchart TD
11
- A([BEGIN]) --> B[First, call the inbox MCP tool. Reading the response: the first header line shows YOUR own identity (the recipient). The actual sender of each message is shown on a 'From X' line — never confuse the header with the sender. Drain each pending message by classifying it: ACK ('thanks', 'noted', 'works', '👍', 'ok') → close silently with reply(message_id, resolve=true), no content; BLOCKED (you cannot do the work right now) → reply(message_id, 'reason', defer=true); ACTIONABLE → do the work first, then reply(message_id, 'concrete summary'). Do NOT skip the drain — the listener exits immediately if messages are already waiting.]
12
- B --> C[Now call the Shell tool with these exact parameters: command='patchcord subscribe --kimi 5', run_in_background=true, timeout=86400, description='Patchcord listener'. Then tell the user in one sentence: Patchcord listener active.]
11
+ A([BEGIN]) --> B["First, call the inbox MCP tool. Reading the response: the first header line shows YOUR own identity (the recipient). The actual sender of each message is on a 'From X' line — never confuse the header with the sender. Drain each pending message by classifying it: ACK (thanks, noted, works, ok, 👍) → close silently with reply(message_id, resolve=true), no content; BLOCKED (you cannot do the work right now) → reply(message_id, reason, defer=true); ACTIONABLE → do the work first, then reply(message_id, concrete summary). Do NOT skip the drain — the listener exits immediately if messages are already waiting."]
12
+ B --> C["Now call the Shell tool with these exact parameters: command=patchcord subscribe --kimi 5, run_in_background=true, timeout=86400, description=Patchcord listener. Then tell the user in one sentence: Patchcord listener active."]
13
13
  C --> D([END])
14
14
  ```
@@ -1,17 +1,17 @@
1
1
  ---
2
- name: patchcord:wait
2
+ name: patchcord-wait
3
3
  description: Wait for one incoming Patchcord message
4
4
  type: flow
5
5
  ---
6
6
 
7
- > Use `/flow:patchcord:wait` to run this. `/skill:` only loads it as docs (Kimi design).
7
+ > Use `/flow:patchcord-wait` to run this. `/skill:` only loads it as docs (Kimi design).
8
8
 
9
9
  ```mermaid
10
10
  flowchart TD
11
- A([BEGIN]) --> B[Call the wait_for_message MCP tool to block until a message arrives or 5 minutes elapse.]
12
- B --> C{Message arrived?}
13
- C -->|Yes| D[Classify the message: ACK ('thanks', 'noted', 'works', '👍', 'ok') → close silently with reply(message_id, resolve=true), no content; BLOCKED (cannot do work right now) → reply(message_id, 'reason', defer=true); ACTIONABLE → do the work first, then reply(message_id, 'concrete summary'). Never reply to an ack with text.]
11
+ A([BEGIN]) --> B["Call the wait_for_message MCP tool to block until a message arrives or 5 minutes elapse."]
12
+ B --> C{"Message arrived?"}
13
+ C -->|Yes| D["Classify the message: ACK (thanks, noted, works, ok, 👍) → close silently with reply(message_id, resolve=true), no content; BLOCKED (cannot do work right now) → reply(message_id, reason, defer=true); ACTIONABLE → do the work first, then reply(message_id, concrete summary). Never reply to an ack with text."]
14
14
  C -->|No| E([END])
15
- D --> F[Call the Shell tool with these exact parameters to re-arm the listener: command='patchcord subscribe --kimi 5', run_in_background=true, timeout=86400, description='Patchcord listener'.]
15
+ D --> F["Call the Shell tool with these exact parameters to re-arm the listener: command=patchcord subscribe --kimi 5, run_in_background=true, timeout=86400, description=Patchcord listener."]
16
16
  F --> E
17
17
  ```