patchcord 0.5.29 → 0.5.31
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 +126 -0
- package/package.json +1 -1
package/bin/patchcord.mjs
CHANGED
|
@@ -67,6 +67,7 @@ Usage:
|
|
|
67
67
|
npx patchcord@latest --token <token> Self-hosted / CI setup
|
|
68
68
|
npx patchcord@latest --token <token> --server <url> Self-hosted with custom server
|
|
69
69
|
npx patchcord@latest --full Same + full statusline
|
|
70
|
+
npx patchcord@latest --rename <new-name> Rename this agent (paste from dashboard)
|
|
70
71
|
npx patchcord@latest skill apply Fetch custom skill from web console`);
|
|
71
72
|
process.exit(0);
|
|
72
73
|
}
|
|
@@ -76,6 +77,131 @@ if (cmd === "plugin-path") {
|
|
|
76
77
|
process.exit(0);
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
// ── --rename <new> [--expect-token <prefix>] ────────────────────
|
|
81
|
+
// Renames the agent in cwd's .mcp.json. Dashboard generates the full
|
|
82
|
+
// pasted command including --expect-token to prevent wrong-machine paste.
|
|
83
|
+
{
|
|
84
|
+
const renameIdx = process.argv.indexOf("--rename");
|
|
85
|
+
if (renameIdx !== -1) {
|
|
86
|
+
const newName = (process.argv[renameIdx + 1] || "").trim().toLowerCase();
|
|
87
|
+
const expectIdx = process.argv.indexOf("--expect-token");
|
|
88
|
+
const expectPrefix = expectIdx !== -1 ? (process.argv[expectIdx + 1] || "").trim() : "";
|
|
89
|
+
|
|
90
|
+
if (!newName || !/^[a-z][a-z0-9-]{1,49}$/.test(newName)) {
|
|
91
|
+
console.error("Usage: npx patchcord@latest --rename <new-name> [--expect-token <prefix>]");
|
|
92
|
+
console.error(" <new-name> must be lowercase letters, digits, dashes; 2-50 chars");
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const cwd = process.cwd();
|
|
97
|
+
let mcpJson = null;
|
|
98
|
+
let dir = cwd;
|
|
99
|
+
while (dir && dir !== "/") {
|
|
100
|
+
const p = join(dir, ".mcp.json");
|
|
101
|
+
if (existsSync(p)) { mcpJson = p; break; }
|
|
102
|
+
dir = dirname(dir);
|
|
103
|
+
}
|
|
104
|
+
if (!mcpJson) {
|
|
105
|
+
console.error(`No .mcp.json found above ${cwd}. Run from your patchcord project folder.`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const { readFileSync } = await import("fs");
|
|
110
|
+
let config;
|
|
111
|
+
try {
|
|
112
|
+
config = JSON.parse(readFileSync(mcpJson, "utf-8"));
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.error(`Cannot parse ${mcpJson}: ${e.message}`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
const mcpUrl = config?.mcpServers?.patchcord?.url || "";
|
|
118
|
+
const auth = config?.mcpServers?.patchcord?.headers?.Authorization || "";
|
|
119
|
+
const baseUrl = mcpUrl.replace(/\/mcp(\/bearer)?$/, "");
|
|
120
|
+
const token = auth.replace(/^Bearer\s+/, "");
|
|
121
|
+
|
|
122
|
+
if (!baseUrl || !token || !isSafeToken(token) || !isSafeUrl(baseUrl)) {
|
|
123
|
+
console.error(`Cannot read patchcord URL/token from ${mcpJson}.`);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (expectPrefix) {
|
|
128
|
+
// Compare sha256(token) prefix, not raw token prefix. The dashboard
|
|
129
|
+
// never sees the plaintext token (we don't store it server-side
|
|
130
|
+
// anymore) — it has token_hash from the bearer_tokens row, slices
|
|
131
|
+
// the first N chars of that, and we hash locally to compare.
|
|
132
|
+
const { createHash } = await import("node:crypto");
|
|
133
|
+
const tokenHash = createHash("sha256").update(token).digest("hex");
|
|
134
|
+
if (!tokenHash.startsWith(expectPrefix)) {
|
|
135
|
+
console.error("✗ Token mismatch.");
|
|
136
|
+
console.error(` The dashboard issued this rename for a different agent than what's`);
|
|
137
|
+
console.error(` installed in ${dirname(mcpJson)}.`);
|
|
138
|
+
console.error(` Maybe you clicked Rename while looking at a different machine?`);
|
|
139
|
+
process.exit(2);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Call the server. Use curl + -w for status code so we can distinguish
|
|
144
|
+
// 409 (taken) from generic 500.
|
|
145
|
+
const tmpResp = `/tmp/patchcord-rename-${process.pid}.json`;
|
|
146
|
+
const status = run(
|
|
147
|
+
`curl -s -o "${tmpResp}" -w "%{http_code}" -X POST ` +
|
|
148
|
+
`-H "Authorization: Bearer ${token}" ` +
|
|
149
|
+
`-H "Content-Type: application/json" ` +
|
|
150
|
+
`-d '{"new":"${newName}"}' ` +
|
|
151
|
+
`"${baseUrl}/api/agent/rename"`
|
|
152
|
+
);
|
|
153
|
+
let respBody = "";
|
|
154
|
+
try { respBody = readFileSync(tmpResp, "utf-8"); } catch {}
|
|
155
|
+
try { (await import("fs")).unlinkSync(tmpResp); } catch {}
|
|
156
|
+
|
|
157
|
+
if (status !== "200") {
|
|
158
|
+
let msg = "rename failed";
|
|
159
|
+
try { msg = (JSON.parse(respBody).error || msg); } catch {}
|
|
160
|
+
console.error(`✗ HTTP ${status}: ${msg}`);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let payload = {};
|
|
165
|
+
try { payload = JSON.parse(respBody); } catch {}
|
|
166
|
+
const oldName = payload.old_agent_id || "";
|
|
167
|
+
const ns = payload.namespace_id || "";
|
|
168
|
+
const aliasUntil = payload.alias_expires_at || "";
|
|
169
|
+
|
|
170
|
+
// Cache wipe + subscribe kill (best-effort; failures non-fatal).
|
|
171
|
+
const { unlinkSync, readdirSync: rd } = await import("fs");
|
|
172
|
+
const tmpClaude = "/tmp/claude";
|
|
173
|
+
if (existsSync(tmpClaude)) {
|
|
174
|
+
try {
|
|
175
|
+
for (const f of rd(tmpClaude)) {
|
|
176
|
+
if (f.startsWith("patchcord-statusline-") || f === "patchcord-sl-resp.json") {
|
|
177
|
+
try { unlinkSync(join(tmpClaude, f)); } catch {}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
} catch {}
|
|
181
|
+
}
|
|
182
|
+
if (ns && oldName) {
|
|
183
|
+
const pidfile = `/tmp/patchcord_subscribe_${ns}_${oldName}.pid`;
|
|
184
|
+
if (existsSync(pidfile)) {
|
|
185
|
+
try {
|
|
186
|
+
const pid = Number(readFileSync(pidfile, "utf-8").trim());
|
|
187
|
+
if (pid > 0) {
|
|
188
|
+
try { process.kill(pid, "SIGTERM"); } catch {}
|
|
189
|
+
}
|
|
190
|
+
} catch {}
|
|
191
|
+
try { unlinkSync(pidfile); } catch {}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const ttlNote = aliasUntil
|
|
196
|
+
? ` Senders using "${oldName}" will be redirected until ${aliasUntil.slice(0, 10)}.`
|
|
197
|
+
: "";
|
|
198
|
+
console.log(`✓ Renamed: ${oldName} → ${newName}${ttlNote}`);
|
|
199
|
+
console.log(` Restart open Claude Code / Codex sessions for this agent to pick up the new name.`);
|
|
200
|
+
console.log(` If you were running /patchcord:subscribe, run it again to reconnect.`);
|
|
201
|
+
process.exit(0);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
79
205
|
// ── main flow: global setup + project setup (or just install/agent for back-compat) ──
|
|
80
206
|
// Any --flag enters this branch so equals-form (--token=foo, --tool=foo, --server=foo)
|
|
81
207
|
// works the same as the space-form (--token foo). The internal flag parsing below
|