patchcord 0.5.129 → 0.5.130
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/.claude-plugin/plugin.json +1 -1
- package/bin/patchcord.mjs +84 -10
- package/package.json +1 -1
- package/skills/inbox/SKILL.md +4 -1
package/bin/patchcord.mjs
CHANGED
|
@@ -98,13 +98,18 @@ ACCOUNT — log in to the CLI (commands below log in on demand)
|
|
|
98
98
|
patchcord login Authenticate the CLI (your account)
|
|
99
99
|
|
|
100
100
|
ORCHESTRATOR — set up the orchestrator agent (the shepherd) in this folder
|
|
101
|
-
patchcord orchestrator [--namespace <ns>] [--tool <harness>]
|
|
102
|
-
Provision the orchestrator here
|
|
103
|
-
|
|
101
|
+
patchcord orchestrator [--namespace <ns>] [--tool <harness>] [--project <name>]
|
|
102
|
+
Provision the orchestrator here (always a
|
|
103
|
+
lead — see PROJECTS below); launch it to
|
|
104
|
+
adopt this project or build a new team
|
|
104
105
|
|
|
105
106
|
TEAM — the orchestrator's tools (run from the project root)
|
|
106
|
-
patchcord provision <agent> --tool <X> --role <Y> --namespace <ns> [--dir <sub/>]
|
|
107
|
-
|
|
107
|
+
patchcord provision <agent> --tool <X> --role <Y> --namespace <ns> [--dir <sub/>] \\
|
|
108
|
+
[--project <name>] [--lead]
|
|
109
|
+
Create a worker agent (identity + config).
|
|
110
|
+
--lead marks it as a cross-namespace lead
|
|
111
|
+
(--role lead does the same); --project
|
|
112
|
+
assigns its namespace (see PROJECTS below)
|
|
108
113
|
patchcord pull <agent> --tool <X> --namespace <ns> [--dir <sub/>]
|
|
109
114
|
Place an EXISTING agent into a folder
|
|
110
115
|
(same identity, new token; nothing overwritten)
|
|
@@ -113,6 +118,15 @@ TEAM — the orchestrator's tools (run from the project root)
|
|
|
113
118
|
patchcord team status Reconcile folder ↔ identity ↔ tmux ↔ token
|
|
114
119
|
patchcord team launch Launch each worker in mux
|
|
115
120
|
|
|
121
|
+
PROJECTS — cloud-only. Group your OWN namespaces so their leads may cross-message
|
|
122
|
+
(same user + same project + BOTH ends provisioned as leads → agent@namespace works;
|
|
123
|
+
anything else is always denied, no exceptions). Namespace names are unique across
|
|
124
|
+
ALL your projects — you can't reuse a name in a different project.
|
|
125
|
+
patchcord namespace set-project <namespace> <project>
|
|
126
|
+
Assign a namespace to a project
|
|
127
|
+
patchcord namespace set-project <namespace>
|
|
128
|
+
Clear it (namespace becomes isolated again)
|
|
129
|
+
|
|
116
130
|
SCHEDULES — timed / recurring messages (user-level)
|
|
117
131
|
patchcord schedule create <name> --namespace <ns> --to <agent> --content "..." \\
|
|
118
132
|
(--at <ISO> | --cron "<expr>" | --every <sec>) \\
|
|
@@ -845,7 +859,7 @@ if (cmd === "subscribe") {
|
|
|
845
859
|
// account token (user-level, tied to NO agent) lives at ~/.patchcord/auth.json
|
|
846
860
|
// (legacy: main.json / master.json) or $PATCHCORD_TOKEN (legacy: *_MAIN/MASTER).
|
|
847
861
|
// `patchcord login` authenticates; everything else logs in on demand.
|
|
848
|
-
if (cmd === "login" || cmd === "orchestrator" || cmd === "teamlead" || cmd === "provision" || cmd === "pull" || cmd === "team" || cmd === "schedule") {
|
|
862
|
+
if (cmd === "login" || cmd === "orchestrator" || cmd === "teamlead" || cmd === "provision" || cmd === "pull" || cmd === "team" || cmd === "schedule" || cmd === "namespace") {
|
|
849
863
|
const M = { cyan: "\x1b[36m", green: "\x1b[32m", dim: "\x1b[2m", rst: "\x1b[0m" };
|
|
850
864
|
const AUTH_CONFIG = join(HOME, ".patchcord", "auth.json");
|
|
851
865
|
const LEGACY_CONFIGS = [join(HOME, ".patchcord", "main.json"), join(HOME, ".patchcord", "master.json")];
|
|
@@ -1042,7 +1056,7 @@ if (cmd === "login" || cmd === "orchestrator" || cmd === "teamlead" || cmd === "
|
|
|
1042
1056
|
|
|
1043
1057
|
if (cmd === "provision") {
|
|
1044
1058
|
const arg = process.argv[3];
|
|
1045
|
-
if (!arg || arg.startsWith("-")) { console.error("Usage: patchcord provision <agent> --tool X --role Y --namespace ns [--dir sub/]"); process.exit(1); }
|
|
1059
|
+
if (!arg || arg.startsWith("-")) { console.error("Usage: patchcord provision <agent> --tool X --role Y --namespace ns [--dir sub/] [--project name] [--lead]"); process.exit(1); }
|
|
1046
1060
|
if (arg === "revoke") {
|
|
1047
1061
|
const ra = process.argv[4];
|
|
1048
1062
|
const ns = flagVal("namespace");
|
|
@@ -1056,8 +1070,18 @@ if (cmd === "login" || cmd === "orchestrator" || cmd === "teamlead" || cmd === "
|
|
|
1056
1070
|
const role = flagVal("role", "");
|
|
1057
1071
|
const ns = flagVal("namespace");
|
|
1058
1072
|
const subdir = flagVal("dir", arg);
|
|
1073
|
+
const project = flagVal("project", "");
|
|
1074
|
+
// --lead marks this token as a project-scoped cross-namespace lead (server
|
|
1075
|
+
// trusts ONLY this explicit, main-token-authenticated request — see
|
|
1076
|
+
// patchcord-cloud's api_provision). `--role lead` also sets it server-side
|
|
1077
|
+
// (role==="lead" is treated the same as is_lead:true), but `--lead` is the
|
|
1078
|
+
// explicit, discoverable way to ask for it without overloading the
|
|
1079
|
+
// free-text --role field.
|
|
1080
|
+
const lead = process.argv.includes("--lead");
|
|
1059
1081
|
if (!ns) { console.error("--namespace <project-namespace> required"); process.exit(1); }
|
|
1060
|
-
const
|
|
1082
|
+
const provisionBody = { namespace_id: ns, agent_id: arg, tool, role, label: `main:${tool}` };
|
|
1083
|
+
if (lead) provisionBody.is_lead = true;
|
|
1084
|
+
const { status, json, m } = await accountCall("POST", "/api/provision", provisionBody);
|
|
1061
1085
|
if (status !== "200" || !json?.token) { console.error(`provision failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
1062
1086
|
const base = String(json.url || `${m.baseUrl}/mcp`).replace(/\/mcp$/, "");
|
|
1063
1087
|
const dir = join(process.cwd(), subdir);
|
|
@@ -1076,10 +1100,48 @@ if (cmd === "login" || cmd === "orchestrator" || cmd === "teamlead" || cmd === "
|
|
|
1076
1100
|
writeFileSync(tj, JSON.stringify(man, null, 2) + "\n");
|
|
1077
1101
|
}
|
|
1078
1102
|
} catch {}
|
|
1079
|
-
|
|
1103
|
+
// --project assigns this namespace to a project — the cross-namespace lead
|
|
1104
|
+
// chat boundary (namespaces in the same project may link leads; different
|
|
1105
|
+
// projects, or no project, never can). Separate call: provisioning creates
|
|
1106
|
+
// the agent identity, project assignment groups the NAMESPACE — safe to
|
|
1107
|
+
// retry independently, and a namespace already in a project just gets
|
|
1108
|
+
// reassigned (last call wins, server-side upsert).
|
|
1109
|
+
if (project) {
|
|
1110
|
+
const { status: pStatus, json: pJson } = await accountCall("POST", "/api/namespace/set-project", { namespace_id: ns, project });
|
|
1111
|
+
if (pStatus !== "200") {
|
|
1112
|
+
console.error(` ${M.dim}warning: provisioned OK, but --project failed (HTTP ${pStatus}): ${pJson?.error || ""}${M.rst}`);
|
|
1113
|
+
} else {
|
|
1114
|
+
console.log(` ${M.dim}namespace ${ns} assigned to project "${project}"${M.rst}`);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
console.log(`✓ provisioned ${M.green}${ns}:${arg}${M.rst} [${tool}${role ? "/" + role : ""}${lead ? "/lead" : ""}] → ${dir}`);
|
|
1080
1118
|
process.exit(0);
|
|
1081
1119
|
}
|
|
1082
1120
|
|
|
1121
|
+
if (cmd === "namespace") {
|
|
1122
|
+
// Cloud-only: group namespaces into a "project" — the boundary
|
|
1123
|
+
// cross-namespace lead chat is gated on. Same user + same project + both
|
|
1124
|
+
// ends are provisioned leads (--lead / --role lead) → their leads may
|
|
1125
|
+
// message each other with `agent@namespace`. Different project, or no
|
|
1126
|
+
// project, or a non-lead agent → always denied, no exceptions.
|
|
1127
|
+
const sub = process.argv[3];
|
|
1128
|
+
if (sub === "set-project") {
|
|
1129
|
+
const ns = process.argv[4];
|
|
1130
|
+
const project = process.argv[5];
|
|
1131
|
+
if (!ns) { console.error("Usage: patchcord namespace set-project <namespace> [project-name]\n Omit project-name to clear (un-groups the namespace, reverting it to isolated)."); process.exit(1); }
|
|
1132
|
+
const { status, json } = await accountCall("POST", "/api/namespace/set-project", { namespace_id: ns, project: project || "" });
|
|
1133
|
+
if (status !== "200") { console.error(`set-project failed (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
1134
|
+
if (json.status === "cleared") {
|
|
1135
|
+
console.log(`✓ ${ns} un-grouped — no project, isolated (no cross-namespace lead peers)`);
|
|
1136
|
+
} else {
|
|
1137
|
+
console.log(`✓ ${ns} assigned to project "${json.project}"`);
|
|
1138
|
+
}
|
|
1139
|
+
process.exit(0);
|
|
1140
|
+
}
|
|
1141
|
+
console.error("Usage: patchcord namespace set-project <namespace> [project-name]");
|
|
1142
|
+
process.exit(1);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1083
1145
|
if (cmd === "pull") {
|
|
1084
1146
|
// Inverse of provision: place an EXISTING agent identity into a folder.
|
|
1085
1147
|
// Mints another token bound to the same agent_id@namespace (its original
|
|
@@ -1123,11 +1185,23 @@ if (cmd === "login" || cmd === "orchestrator" || cmd === "teamlead" || cmd === "
|
|
|
1123
1185
|
const root = process.cwd();
|
|
1124
1186
|
const ns = (flagVal("namespace", basename(root)) || "team").replace(/[^a-z0-9-]/gi, "-").toLowerCase();
|
|
1125
1187
|
const tool = flagVal("tool", "claude_code");
|
|
1188
|
+
const project = flagVal("project", "");
|
|
1126
1189
|
const hostname = run("hostname -s") || run("hostname") || "unknown";
|
|
1127
|
-
|
|
1190
|
+
// is_lead: true always — the orchestrator IS the one identity per namespace
|
|
1191
|
+
// meant to coordinate across teams. Harmless without --project: a lead with
|
|
1192
|
+
// no project assignment has zero cross-namespace peers (fails closed).
|
|
1193
|
+
const { status, json, m } = await accountCall("POST", "/api/provision", { namespace_id: ns, agent_id: "orchestrator", tool, role: "orchestrator", label: "orchestrator:self", is_lead: true });
|
|
1128
1194
|
if (status !== "200" || !json?.token) { console.error(`could not set up orchestrator (HTTP ${status}): ${json?.error || ""}`); process.exit(1); }
|
|
1129
1195
|
const base = String(json.url || `${m.baseUrl}/mcp`).replace(/\/mcp$/, "");
|
|
1130
1196
|
writeWorkerConfig(tool, root, base, json.token, hostname);
|
|
1197
|
+
if (project) {
|
|
1198
|
+
const { status: pStatus, json: pJson } = await accountCall("POST", "/api/namespace/set-project", { namespace_id: ns, project });
|
|
1199
|
+
if (pStatus !== "200") {
|
|
1200
|
+
console.error(` ${M.dim}warning: orchestrator set up OK, but --project failed (HTTP ${pStatus}): ${pJson?.error || ""}${M.rst}`);
|
|
1201
|
+
} else {
|
|
1202
|
+
console.log(` ${M.dim}namespace ${ns} assigned to project "${project}"${M.rst}`);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1131
1205
|
mkdirSync(join(root, ".patchcord"), { recursive: true });
|
|
1132
1206
|
const tj = join(root, ".patchcord", "team.json");
|
|
1133
1207
|
let manifest = { project: basename(root), namespace: ns, orchestrator: { agent: "orchestrator", tool }, agents: [] };
|
package/package.json
CHANGED
package/skills/inbox/SKILL.md
CHANGED
|
@@ -182,7 +182,10 @@ Named threads group related messages between a pair of agents. Use them for mult
|
|
|
182
182
|
- Never ask "should I do this?" - just do it. User can undo in 3 seconds.
|
|
183
183
|
- Never ask "want me to wait?" - check presence and wait or don't based on that.
|
|
184
184
|
- Never show raw JSON to the human - summarize naturally.
|
|
185
|
-
- Cross-namespace
|
|
185
|
+
- **Cross-namespace addressing (`agent@namespace`)**: the syntax always exists in `send_message`/`reply`, but what it actually reaches depends on who you are:
|
|
186
|
+
- **Ordinary agents (the default):** `agent@namespace` only ever works for YOUR OWN namespace — same as a bare name. Targeting any other namespace is rejected. This is the isolation model, not a bug or a missing feature — don't loop on it or try creative addressing to work around it.
|
|
187
|
+
- **Lead agents (cloud only — a small number of specially-provisioned identities per namespace, usually the orchestrator):** may reach a peer lead in a DIFFERENT namespace, but only if a human explicitly grouped both namespaces into the same "project" (a cloud-side setting you cannot set yourself from inside a session) — different project, or no project, is always rejected, no exceptions. If you don't know whether you're a lead, you're not one — this isn't discoverable by trial and error; ask the human.
|
|
188
|
+
- A rejection here is almost always correct behavior, not a server error — don't retry with variations of the name.
|
|
186
189
|
- **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.
|
|
187
190
|
- **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.
|
|
188
191
|
- **When you receive an ack**, close it silently: `reply(id, resolve=true)`. No content. This stops the chain.
|