patchcord 0.3.98 → 0.4.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.
package/bin/patchcord.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { existsSync, mkdirSync, cpSync, readdirSync } from "fs";
|
|
4
|
-
import { join, dirname } from "path";
|
|
4
|
+
import { join, dirname, basename } from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { execSync } from "child_process";
|
|
7
7
|
import { homedir } from "os";
|
|
@@ -80,6 +80,7 @@ if (cmd === "plugin-path") {
|
|
|
80
80
|
if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd === "--no-browser" || cmd === "--server") {
|
|
81
81
|
const flags = cmd?.startsWith("--") ? process.argv.slice(2) : process.argv.slice(3);
|
|
82
82
|
const fullStatusline = flags.includes("--full");
|
|
83
|
+
let wasPluginInstalled = false;
|
|
83
84
|
const { readFileSync, writeFileSync, unlinkSync } = await import("fs");
|
|
84
85
|
|
|
85
86
|
function safeReadJson(filePath) {
|
|
@@ -120,6 +121,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
120
121
|
// doesn't detect new versions from local sources (#37252).
|
|
121
122
|
run(`claude plugin marketplace add "${pluginRoot}"`);
|
|
122
123
|
const installed = run(`claude plugin list`)?.includes("patchcord");
|
|
124
|
+
wasPluginInstalled = !!installed;
|
|
123
125
|
if (installed) {
|
|
124
126
|
run(`claude plugin update patchcord@patchcord-marketplace`);
|
|
125
127
|
globalChanges.push("Claude Code plugin updated");
|
|
@@ -155,12 +157,16 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
155
157
|
}
|
|
156
158
|
}
|
|
157
159
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
// Statusline: enabled later in the Claude Code project-setup branch (prompts user).
|
|
161
|
+
// If --full flag passed non-interactively, install eagerly here so non-Claude-Code
|
|
162
|
+
// flows (e.g. setting up Codex on a machine with Claude Code) still get the upgrade.
|
|
163
|
+
if (fullStatusline) {
|
|
164
|
+
const enableScript = join(pluginRoot, "scripts", "enable-statusline.sh");
|
|
165
|
+
if (existsSync(enableScript)) {
|
|
166
|
+
const slResult = run(`bash "${enableScript}" --full`);
|
|
167
|
+
if (slResult !== null && slResult.includes("statusline")) {
|
|
168
|
+
globalChanges.push("Statusline (full) enabled");
|
|
169
|
+
}
|
|
164
170
|
}
|
|
165
171
|
}
|
|
166
172
|
}
|
|
@@ -218,10 +224,10 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
218
224
|
cpSync(join(pluginRoot, "skills", "patchcord-wait", "SKILL.md"), join(geminiWaitDir, "SKILL.md"));
|
|
219
225
|
geminiChanged = true;
|
|
220
226
|
}
|
|
221
|
-
if (!existsSync(join(geminiCmdDir, "
|
|
227
|
+
if (!existsSync(join(geminiCmdDir, "inbox.toml"))) {
|
|
222
228
|
mkdirSync(geminiCmdDir, { recursive: true });
|
|
223
|
-
cpSync(join(pluginRoot, "commands", "
|
|
224
|
-
cpSync(join(pluginRoot, "commands", "
|
|
229
|
+
cpSync(join(pluginRoot, "commands", "inbox.toml"), join(geminiCmdDir, "inbox.toml"));
|
|
230
|
+
cpSync(join(pluginRoot, "commands", "wait.toml"), join(geminiCmdDir, "wait.toml"));
|
|
225
231
|
geminiChanged = true;
|
|
226
232
|
}
|
|
227
233
|
if (geminiChanged) globalChanges.push("Gemini CLI skills + commands installed");
|
|
@@ -993,14 +999,71 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
993
999
|
}
|
|
994
1000
|
console.log(`\n ${green}✓${r} Claude Code configured: ${dim}${mcpPath}${r}`);
|
|
995
1001
|
|
|
996
|
-
//
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
+
// Statusline: ask whether to enable full mode (unless --full flag passed or already configured).
|
|
1003
|
+
const enableScript = join(pluginRoot, "scripts", "enable-statusline.sh");
|
|
1004
|
+
if (existsSync(enableScript)) {
|
|
1005
|
+
let currentStatusLine = "";
|
|
1006
|
+
const claudeSettingsPath = join(HOME, ".claude", "settings.json");
|
|
1007
|
+
if (existsSync(claudeSettingsPath)) {
|
|
1008
|
+
try {
|
|
1009
|
+
const s = JSON.parse(readFileSync(claudeSettingsPath, "utf-8"));
|
|
1010
|
+
currentStatusLine = s?.statusLine?.command || "";
|
|
1011
|
+
} catch {}
|
|
1002
1012
|
}
|
|
1003
|
-
|
|
1013
|
+
const hasPatchcordStatusLine = currentStatusLine.includes("patchcord");
|
|
1014
|
+
|
|
1015
|
+
let installFull = null; // true = install, null = skip
|
|
1016
|
+
if (fullStatusline) {
|
|
1017
|
+
installFull = true; // already handled in global setup, but re-run is idempotent
|
|
1018
|
+
} else if (!hasPatchcordStatusLine) {
|
|
1019
|
+
// Build preview with user's real values
|
|
1020
|
+
const dirName = basename(cwd) || "project";
|
|
1021
|
+
let branchPart = "";
|
|
1022
|
+
const gitBranch = run(`git -C "${cwd}" symbolic-ref --short HEAD 2>/dev/null`);
|
|
1023
|
+
if (gitBranch) {
|
|
1024
|
+
const dirty = run(`git -C "${cwd}" status --porcelain 2>/dev/null`);
|
|
1025
|
+
const star = dirty ? `${red}*${green}` : "";
|
|
1026
|
+
branchPart = ` ${green}(${gitBranch}${star})${r}`;
|
|
1027
|
+
}
|
|
1028
|
+
const idStr = identity || "your-agent@namespace";
|
|
1029
|
+
const idParts = idStr.split("@");
|
|
1030
|
+
const idPart = idParts.length === 2
|
|
1031
|
+
? `${white}${idParts[0]}${r}${dim}@${idParts[1]}${r}`
|
|
1032
|
+
: `${white}${idStr}${r}`;
|
|
1033
|
+
const machinePart = hostname ? ` ${dim}(${hostname})${r}` : "";
|
|
1034
|
+
const sep = `${dim} │ ${r}`;
|
|
1035
|
+
const blue = "\x1b[38;2;0;153;255m";
|
|
1036
|
+
const example = `${blue}Claude Opus 4.7${r}${sep}${green}0%${r}${sep}${cyan}${dirName}${r}${branchPart}${sep}${idPart}${machinePart}`;
|
|
1037
|
+
|
|
1038
|
+
console.log(``);
|
|
1039
|
+
console.log(` Patchcord can show live agent identity + context in Claude Code's status bar.`);
|
|
1040
|
+
console.log(``);
|
|
1041
|
+
console.log(` Full mode: ${example}`);
|
|
1042
|
+
console.log(``);
|
|
1043
|
+
|
|
1044
|
+
const defaultYes = !wasPluginInstalled;
|
|
1045
|
+
const promptStr = defaultYes
|
|
1046
|
+
? ` ${bold}Install full status bar?${r} ${dim}(Y/n):${r} `
|
|
1047
|
+
: ` ${bold}Install full status bar?${r} ${dim}(y/N):${r} `;
|
|
1048
|
+
const { createInterface: createRLS } = await import("readline");
|
|
1049
|
+
const rlS = createRLS({ input: process.stdin, output: process.stdout });
|
|
1050
|
+
const answer = await new Promise((resolve) => rlS.question(promptStr, resolve));
|
|
1051
|
+
rlS.close();
|
|
1052
|
+
const a = (answer || "").trim().toLowerCase();
|
|
1053
|
+
const yes = defaultYes
|
|
1054
|
+
? !(a === "n" || a === "no")
|
|
1055
|
+
: (a === "y" || a === "yes");
|
|
1056
|
+
if (yes) installFull = true;
|
|
1057
|
+
}
|
|
1058
|
+
// else: existing patchcord statusline — leave it alone
|
|
1059
|
+
|
|
1060
|
+
if (installFull === true) {
|
|
1061
|
+
try {
|
|
1062
|
+
execSync(`bash "${enableScript}" --full`, { stdio: "ignore" });
|
|
1063
|
+
console.log(` ${green}✓${r} Statusline enabled`);
|
|
1064
|
+
} catch {}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1004
1067
|
}
|
|
1005
1068
|
|
|
1006
1069
|
// Warn about gitignore for per-project configs with tokens
|
package/package.json
CHANGED
|
@@ -122,7 +122,9 @@ Send the returned `path` to the other agent in your message so they can download
|
|
|
122
122
|
## Rules
|
|
123
123
|
|
|
124
124
|
- Do the work first, reply second. Never reply before completing the task.
|
|
125
|
-
- Do not
|
|
125
|
+
- **Do not reply to acks.** "ok", "noted", "seen", "thanks", "good progress", "keep running" — read them and move on. If you must close the thread: `reply(id, resolve=true)` with NO content.
|
|
126
|
+
- **resolve=true with ack-only content is an anti-pattern.** `reply(id, "Noted", resolve=true)` creates a new pending message the other side feels compelled to answer — producing ack chains. Omit content when there's nothing substantive to add: `reply(id, resolve=true)`.
|
|
127
|
+
- **When you receive an ack**, close it silently: `reply(id, resolve=true)`. No content. This stops the chain.
|
|
126
128
|
- Do not show raw JSON to the user unless they explicitly ask for it.
|
|
127
129
|
- Use `agent@namespace` when the online list shows multiple namespaces for the same agent name.
|
|
128
130
|
- Keep Patchcord config project-local. Do not rely on global shell exports.
|
|
@@ -53,7 +53,7 @@ Use the dominant topic of your current conversation as the tag. Keep it short (1
|
|
|
53
53
|
|
|
54
54
|
6. **Never show raw JSON** - summarize naturally.
|
|
55
55
|
|
|
56
|
-
7. **Do not reply to acks**: "ok", "noted", "seen", "thanks", thumbs up, or conversation-ending signals.
|
|
56
|
+
7. **Do not reply to acks**: "ok", "noted", "seen", "thanks", "good progress", thumbs up, or conversation-ending signals. If you must close the thread: `reply(id, resolve=true)` with NO content. Never send a text reply to an ack. **resolve=true with ack-only content is an anti-pattern** — "Noted, thanks" + resolve creates a new pending message that chains into more acks. Omit content when there's nothing substantive. When you receive an ack, close it silently: `reply(id, resolve=true)`. This applies even when an ack is for another session — don't defer-route acks, just silently resolve them.
|
|
57
57
|
|
|
58
58
|
8. **Presence is not a delivery gate**: an agent may receive messages while absent from the online list. Always send regardless of online/offline status. Messages queue and deliver when the recipient checks inbox.
|
|
59
59
|
|
|
@@ -121,6 +121,8 @@ Named threads group related messages between a pair of agents. Use them for mult
|
|
|
121
121
|
- Never ask "want me to wait?" - check presence and wait or don't based on that.
|
|
122
122
|
- Never show raw JSON to the human - summarize naturally.
|
|
123
123
|
- Cross-namespace agents: use `agent@namespace` syntax in send_message when targeting a specific namespace.
|
|
124
|
-
- Do not reply to
|
|
124
|
+
- **Do not reply to acks.** "ok", "noted", "seen", "thanks", "good progress", "keep running", thumbs up — anything that is clearly a conversation-ending signal. Just read them and move on. If you must close the thread, use `reply(id, resolve=true)` with NO content. Never send a text reply to an ack.
|
|
125
|
+
- **resolve=true with ack-only content is an anti-pattern.** `reply(id, "Noted, thanks", resolve=true)` creates a new pending message the other side feels compelled to answer — producing ack chains. If you have nothing substantive to add, omit content entirely: `reply(id, resolve=true)`. Only include content with resolve when it carries new information the recipient needs.
|
|
126
|
+
- **When you receive an ack**, close it silently: `reply(id, resolve=true)`. No content. This stops the chain.
|
|
125
127
|
- MCP tools are cached at session start. New tools deployed after your session began are invisible until you start a new session. If a tool you expect is missing, this is why.
|
|
126
128
|
- Agent names change frequently. Do not memorize or hardcode them. Check inbox() for recent activity. When unsure which agent to message, ask the human.
|
|
@@ -22,3 +22,5 @@ Loop until timeout or the human interrupts.
|
|
|
22
22
|
If `wait_for_message()` errors, fall back to polling `inbox()` every 10-15 seconds instead of stopping the loop.
|
|
23
23
|
|
|
24
24
|
Do not ask the human for permission to reply - just do the work, reply with results, then report.
|
|
25
|
+
|
|
26
|
+
**No ack chains.** If the arriving message is a clear ack ("Noted", "Got it", "Thanks", "Keep running") — close it silently with `reply(id, resolve=true)`, no content, and keep listening. Never text-reply to an ack. Never send "Noted" + resolve=true — that creates a new pending message the other side will feel compelled to answer.
|