cofluxd 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.
- package/README.md +9 -3
- package/cofluxd.mjs +113 -14
- package/package.json +1 -1
- package/skills/coflux/SKILL.md +299 -151
package/README.md
CHANGED
|
@@ -34,16 +34,22 @@ CLI 还会取自身与 worker 的持久 release floor 较大值拒绝远端降
|
|
|
34
34
|
跑在 coflux 终端里的 claude/codex 可以用下面几条,把工作外化成用户在 web/手机上**看得见、能接管**的东西——而不是在自己的 Bash 里后台起一个谁也看不见的进程:
|
|
35
35
|
|
|
36
36
|
```sh
|
|
37
|
-
cofluxd terminal new --title "跑单测" --cmd "pnpm test" #
|
|
37
|
+
cofluxd terminal new --title "跑单测" --cmd "pnpm test" # 作业终端:跑完即退,带退出码
|
|
38
|
+
cofluxd terminal new --title "调试 shell" # 会话终端:不带命令 = 常驻登录 shell
|
|
38
39
|
cofluxd terminal list # 本工作区的终端 + 状态/退出码
|
|
39
40
|
cofluxd terminal read <taskId> [--lines N] # 读终端内容(纯文本,已退出也能读)
|
|
41
|
+
cofluxd terminal wait <taskId> [--timeout <秒>] # 阻塞到退出,打印退出码
|
|
42
|
+
cofluxd terminal send <taskId> --text "y" --enter # 往终端输入;用户正在接管时被拒
|
|
43
|
+
cofluxd progress "复现了,正在定位" # 播报进度:显示在工作区卡片上
|
|
40
44
|
cofluxd notify "需要你定一下用哪个方案" # 叫人:侧栏转「等待交互」
|
|
41
45
|
cofluxd ports # 端口 + 可直接打开的预览 URL
|
|
42
46
|
```
|
|
43
47
|
|
|
44
|
-
|
|
48
|
+
`terminal new` 带不带 `--cmd` 是两种终端:带命令是**作业终端**,命令包成脚本交登录 shell 跑,跑完终端退出并带退出码,输出另落一份日志供 `read` 回读(代价是命令的 stdout 是管道而非 tty,颜色/进度条/全屏程序都没有);不带命令是**会话终端**,等价于用户在侧栏点「新建终端」——工作区目录下的默认登录 shell,stdin/stdout 都是真 tty,不会自己退出,直到 agent 或用户输入 `exit`。会话终端没有命令日志,`read` 读的是当前画面(一屏),首次 `send` 前要先 `read` 等提示符。
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
不需要任何凭证:daemon 用调用方 pid 反查进程树确认它属于哪个会话,**coflux 会话之外的进程一律拒绝**,权限也天然限定在该会话所属的工作区内。**local-first**:send/read/wait/notify/progress 在 daemon 本地闭环、不经中心(归属与退出码来自 daemon 自己的会话账本,内容来自本地命令日志或 sessiond 快照);只有 new/list/ports 由 daemon 代问中心——Task 要落库广播、预览 URL 由中心生成。早于 daemon 升级开出来的终端缺归属信息,本地命令会明确拒绝,重开即可。
|
|
51
|
+
|
|
52
|
+
每个 coflux 开出来的 PTY 会话里还注入了一组 `COFLUX_*` 环境变量(由 supervisor 组装,中心只下发 id):`COFLUX_DEVICE_ID` / `COFLUX_PROJECT_ID`(目录工作区为空串)/ `COFLUX_WORKSPACE_ID` / `COFLUX_TASK_ID` / `COFLUX_SESSION_ID` / `COFLUX_MCP_URL`。agent 读它们就知道自己在哪台设备、哪个项目/工作区/终端,值与中心 MCP `list_*` 返回的 id 完全一致,可直接传给 MCP tools。本地命令与 MCP 的分工只有一条规则:**本地能闭环的一律本地命令**(本工作区内的开终端/读/等/输入/播报/叫人/端口);只有跨出本工作区——开子工作区、跨工作区/跨设备读写、或从 coflux 之外接入——才用中心的 `coflux` MCP(`claude mcp add --transport http coflux "$COFLUX_MCP_URL"`,一次 OAuth 授权)。supervisor 不走热升级,旧机器要 `cofluxd update && cofluxd restart` 之后会话里才有这些变量;skill 里写了变量为空时的降级分支。
|
|
47
53
|
|
|
48
54
|
配套的 skill 在 `skills/coflux/SKILL.md`(随包分发),装给 Claude Code:
|
|
49
55
|
|
package/cofluxd.mjs
CHANGED
|
@@ -943,17 +943,41 @@ async function cmdHook() {
|
|
|
943
943
|
// **看得见、能接管**的 coflux 实体——而不是在自己的 Bash 里后台起一个谁也看不见的进程。
|
|
944
944
|
//
|
|
945
945
|
// 不需要任何凭证:daemon 用调用方 pid 反查进程树确认它属于哪个会话,树外一律拒。
|
|
946
|
+
// local-first(plan 094):send/read/wait/notify/progress 在 daemon 本地闭环,不经中心;只有
|
|
947
|
+
// new/list/ports 由 daemon 代问中心(Task 要落库广播、预览 URL 由中心生成)。
|
|
948
|
+
// 跟随 cwd(plan 102):请求都带 process.cwd(),agent 挪进同设备另一个 coflux 工作区后,这些
|
|
949
|
+
// 命令就对那个工作区办事(notify/progress/ports 除外,它们挂在本会话上,与工作区无关)。
|
|
946
950
|
// 与 `hook` 子命令的约定**相反**:这些命令必须写 stdout——输出就是给 agent 读的返回值。
|
|
947
951
|
// 也刻意不做自动重试:terminal new 有副作用,重试会开出两个终端,失败就把错误交给 agent。
|
|
948
952
|
|
|
949
953
|
const AGENT_TIMEOUT_MS = 30_000;
|
|
954
|
+
/** 调用方能收窄单次 `/agent` 等待的下限;再低就只够覆盖 node 自己的启动,等于必然超时。 */
|
|
955
|
+
const MIN_AGENT_TIMEOUT_MS = 200;
|
|
950
956
|
const DEFAULT_READ_LINES = 200;
|
|
951
|
-
// wait 的循环必须在 CLI 侧:单次 agentPost
|
|
952
|
-
//
|
|
953
|
-
//
|
|
957
|
+
// wait 的循环必须在 CLI 侧:单次 agentPost 有 25 秒的 loopback 应答上限。默认 30 分钟——编码任务
|
|
958
|
+
// 常跑很久;轮询走 terminal.status(daemon 本地账本直接答,不经中心),3 秒一次对本机 loopback
|
|
959
|
+
// 是零负担。
|
|
954
960
|
const DEFAULT_WAIT_TIMEOUT_S = 1800;
|
|
955
961
|
const WAIT_POLL_MS = 3000;
|
|
956
962
|
|
|
963
|
+
// 每条请求都带调用方 cwd(plan 102):agent 可以经 `/cd` 或 EnterWorktree 把活着的会话挪进同
|
|
964
|
+
// 设备的另一个 coflux 工作区,daemon 据此把本次请求的**目标**解析到 cwd 所在的工作区(会话的
|
|
965
|
+
// 归属工作区不变)。目录被删掉时 process.cwd() 会抛,按"报不出来"处理,daemon 退回归属工作区。
|
|
966
|
+
function callerCwd() {
|
|
967
|
+
try { return process.cwd(); } catch { return ""; }
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// 调用方可以用 COFLUX_AGENT_TIMEOUT_MS 收窄单次请求的等待上限(plan 104)。默认 30 秒是为
|
|
971
|
+
// agent 定的——它等得起;hook 脚本等不起:宿主按秒杀 hook(SessionStart 只给几秒),而经中心的
|
|
972
|
+
// 动作最坏要等 daemon 的 20 秒中心超时。被宿主杀在半路比拿不到答案坏得多(连坐标块都印不出来),
|
|
973
|
+
// 所以这类调用方自报一个更小的预算,到点干净失败、让脚本走回退。
|
|
974
|
+
// 只允许收窄不允许放宽:上限仍是 AGENT_TIMEOUT_MS,畸形值一律按默认处理。
|
|
975
|
+
function agentTimeoutMs() {
|
|
976
|
+
const raw = Number(process.env.COFLUX_AGENT_TIMEOUT_MS);
|
|
977
|
+
if (!Number.isFinite(raw) || raw <= 0) return AGENT_TIMEOUT_MS;
|
|
978
|
+
return Math.min(Math.max(Math.floor(raw), MIN_AGENT_TIMEOUT_MS), AGENT_TIMEOUT_MS);
|
|
979
|
+
}
|
|
980
|
+
|
|
957
981
|
async function agentPost(body) {
|
|
958
982
|
const portResult = localGatewayPort();
|
|
959
983
|
if (!portResult.ok) die(portResult.error);
|
|
@@ -962,8 +986,8 @@ async function agentPost(body) {
|
|
|
962
986
|
res = await fetch(`http://127.0.0.1:${portResult.port}/agent`, {
|
|
963
987
|
method: "POST",
|
|
964
988
|
headers: { "content-type": "application/json" },
|
|
965
|
-
body: JSON.stringify({ ...body, pid: process.pid, ppid: process.ppid }),
|
|
966
|
-
signal: AbortSignal.timeout(
|
|
989
|
+
body: JSON.stringify({ ...body, pid: process.pid, ppid: process.ppid, cwd: callerCwd() }),
|
|
990
|
+
signal: AbortSignal.timeout(agentTimeoutMs()),
|
|
967
991
|
});
|
|
968
992
|
} catch (error) {
|
|
969
993
|
die(`连不上本机 daemon:${error?.message || error}(daemon 没在跑?先看 cofluxd status)`);
|
|
@@ -994,11 +1018,20 @@ function tailLines(text, n) {
|
|
|
994
1018
|
async function cmdTerminal(values) {
|
|
995
1019
|
const sub = positionals[1];
|
|
996
1020
|
if (sub === "new") {
|
|
997
|
-
|
|
998
|
-
|
|
1021
|
+
// 两种终端只看「命令是否为空」(plan 101):--cmd 缺省与 --cmd= 空白等价,都开会话终端
|
|
1022
|
+
// ——工作区目录下的默认登录 shell,stdin/stdout 都是真 tty,不自动退出。带命令的仍是作业
|
|
1023
|
+
// 终端:命令跑完终端退出并带退出码,语义一字不变。空白在这里统一收敛成空串,好让中心的
|
|
1024
|
+
// 「默认标题取命令首行」落到它自己的兜底。
|
|
1025
|
+
const command = (values.cmd ?? "").trim() ? values.cmd : "";
|
|
999
1026
|
const result = await agentPost({ action: "terminal.new", title: values.title || "", command });
|
|
1000
1027
|
console.log(`已开终端 ${result.taskId}(用户可在 coflux 侧栏看到并随时接管)`);
|
|
1001
|
-
|
|
1028
|
+
if (command) {
|
|
1029
|
+
console.log(`看输出:cofluxd terminal read ${result.taskId}`);
|
|
1030
|
+
} else {
|
|
1031
|
+
console.log(`会话终端:常驻的登录 shell(全 tty),不会自己退出`);
|
|
1032
|
+
console.log(`先等提示符:cofluxd terminal read ${result.taskId}`);
|
|
1033
|
+
console.log(`再输命令:cofluxd terminal send ${result.taskId} --text "<命令>" --enter(送 exit 才结束)`);
|
|
1034
|
+
}
|
|
1002
1035
|
} else if (sub === "list") {
|
|
1003
1036
|
const { terminals } = await agentPost({ action: "terminal.list" });
|
|
1004
1037
|
if (!terminals.length) return void console.log("本工作区暂无终端");
|
|
@@ -1030,9 +1063,8 @@ async function cmdTerminal(values) {
|
|
|
1030
1063
|
const timeoutSec = Number.isFinite(requested) && requested > 0 ? requested : DEFAULT_WAIT_TIMEOUT_S;
|
|
1031
1064
|
const deadline = Date.now() + timeoutSec * 1000;
|
|
1032
1065
|
for (;;) {
|
|
1033
|
-
|
|
1034
|
-
const t =
|
|
1035
|
-
if (!t) die(`本工作区没有终端 ${taskId}(用 cofluxd terminal list 查)`);
|
|
1066
|
+
// 按 taskId 直接问本地账本;目标不存在/不在本工作区时 daemon 回可读错误,agentPost 直接 die。
|
|
1067
|
+
const t = await agentPost({ action: "terminal.status", taskId });
|
|
1036
1068
|
if (t.status === "exited") {
|
|
1037
1069
|
const exit = t.exitCode === undefined || t.exitCode === null ? "" : ` exit=${t.exitCode}`;
|
|
1038
1070
|
return void console.log(`# exited${exit}`);
|
|
@@ -1061,6 +1093,55 @@ async function cmdProgress() {
|
|
|
1061
1093
|
console.log("已更新进度(显示在工作区卡片上,被下一条覆盖)");
|
|
1062
1094
|
}
|
|
1063
1095
|
|
|
1096
|
+
// 「我在哪」与「跟着我搬」(plan 102 / 103)。三条都打一行 JSON,字段稳定——插件脚本按它比对,
|
|
1097
|
+
// agent 也直接读。
|
|
1098
|
+
//
|
|
1099
|
+
// cofluxd workspace 只读:cwd 所在的有效工作区 + 本终端的归属工作区
|
|
1100
|
+
// cofluxd workspace locate [path] 把本终端的**归属**搬到 path 所属的工作区(未登记先登记)
|
|
1101
|
+
// cofluxd workspace forget <path> 该 worktree 已被删掉:其下终端搬回主工作区、记录消失
|
|
1102
|
+
//
|
|
1103
|
+
// locate/forget 是插件在 SessionStart / PostToolUse(EnterWorktree|ExitWorktree) / WorktreeRemove
|
|
1104
|
+
// 上调的,同样零凭证(daemon 按进程树认身份)。daemon 旧到不认识这两个动作时它会回
|
|
1105
|
+
// 「未知 action …」,agentPost 原样报错并非零退出——脚本据此静默放弃,不干扰会话。
|
|
1106
|
+
async function cmdWorkspace() {
|
|
1107
|
+
const sub = positionals[1];
|
|
1108
|
+
if (!sub) {
|
|
1109
|
+
const result = await agentPost({ action: "workspace.current" });
|
|
1110
|
+
return void console.log(JSON.stringify({
|
|
1111
|
+
workspaceId: result.workspaceId,
|
|
1112
|
+
path: result.path,
|
|
1113
|
+
owningWorkspaceId: result.owningWorkspaceId,
|
|
1114
|
+
moved: Boolean(result.moved),
|
|
1115
|
+
}));
|
|
1116
|
+
}
|
|
1117
|
+
if (sub === "locate") {
|
|
1118
|
+
// 路径缺省取调用方 cwd;插件脚本一律显式传 hook 载荷里的 cwd(hook 在会话当前目录执行,
|
|
1119
|
+
// 与载荷里的 cwd 未必相同)。
|
|
1120
|
+
const path = positionals[2] || callerCwd();
|
|
1121
|
+
if (!path) die("workspace locate 需要 <path>(取不到当前目录)");
|
|
1122
|
+
const result = await agentPost({ action: "workspace.locate", path });
|
|
1123
|
+
return void console.log(JSON.stringify({
|
|
1124
|
+
workspaceId: result.workspaceId,
|
|
1125
|
+
path: result.path,
|
|
1126
|
+
branch: result.branch,
|
|
1127
|
+
created: Boolean(result.created),
|
|
1128
|
+
moved: Boolean(result.moved),
|
|
1129
|
+
}));
|
|
1130
|
+
}
|
|
1131
|
+
if (sub === "forget") {
|
|
1132
|
+
const path = positionals[2];
|
|
1133
|
+
if (!path) die("workspace forget 需要 <path>(被删掉的 worktree 目录)");
|
|
1134
|
+
const result = await agentPost({ action: "workspace.forget", path });
|
|
1135
|
+
return void console.log(JSON.stringify({
|
|
1136
|
+
workspaceId: result.workspaceId,
|
|
1137
|
+
fallbackWorkspaceId: result.fallbackWorkspaceId,
|
|
1138
|
+
movedTerminals: result.movedTerminals ?? 0,
|
|
1139
|
+
removed: Boolean(result.removed),
|
|
1140
|
+
}));
|
|
1141
|
+
}
|
|
1142
|
+
die(`workspace 的子命令只有 locate | forget(不带子命令 = 报出我在哪)`);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1064
1145
|
async function cmdPorts() {
|
|
1065
1146
|
const { ports } = await agentPost({ action: "ports" });
|
|
1066
1147
|
if (!ports.length) return void console.log("本工作区暂无监听端口");
|
|
@@ -1084,8 +1165,12 @@ const HELP = `cofluxd —— coflux daemon 管理
|
|
|
1084
1165
|
|
|
1085
1166
|
以下几条供**跑在 coflux 终端里的 agent** 调用,把工作变成用户看得见、能接管的东西:
|
|
1086
1167
|
|
|
1087
|
-
cofluxd terminal new --cmd "<命令>" [--title "<标题>"]
|
|
1088
|
-
|
|
1168
|
+
cofluxd terminal new [--cmd "<命令>"] [--title "<标题>"]
|
|
1169
|
+
开一个真实终端,用户在 coflux 侧栏能看到并随时接管
|
|
1170
|
+
带 --cmd = 作业终端:命令在登录 shell 里跑完即退出并带退出码,输出另
|
|
1171
|
+
落一份日志供 read 回读(代价:stdout 是管道,不是 tty)
|
|
1172
|
+
不带 --cmd = 会话终端:工作区目录下的常驻登录 shell,stdin/stdout 都是
|
|
1173
|
+
真 tty(能跑 vim/htop、有颜色),先 read 等提示符再 send,送 exit 才结束
|
|
1089
1174
|
cofluxd terminal list 列出本工作区的终端(含 status / 退出码)
|
|
1090
1175
|
cofluxd terminal read <taskId> [--lines N]
|
|
1091
1176
|
读某个终端的内容(纯文本,默认最后 200 行;终端已退出也能读)
|
|
@@ -1096,6 +1181,20 @@ const HELP = `cofluxd —— coflux daemon 管理
|
|
|
1096
1181
|
cofluxd notify "<一句话>" 叫人:工作区在侧栏转为「等待交互」并显示这句话
|
|
1097
1182
|
cofluxd progress "<一句话>" 播报进度:显示在工作区卡片上,被下一条覆盖(不打扰用户)
|
|
1098
1183
|
cofluxd ports 列出本工作区的监听端口及可直接打开的预览 URL
|
|
1184
|
+
cofluxd workspace 一行 JSON 报出「我在哪」:workspaceId(cwd 所在的有效工作区,本地命令
|
|
1185
|
+
都落在它上面)、path、owningWorkspaceId(本终端此刻归属哪个工作区)、
|
|
1186
|
+
moved。用 /cd 挪进另一个 coflux 工作区后用它确认目标,调 MCP 时也传这个
|
|
1187
|
+
workspaceId
|
|
1188
|
+
cofluxd workspace locate [path]
|
|
1189
|
+
把本终端的**归属**搬到 path(缺省=当前目录)所属的工作区:进入/离开
|
|
1190
|
+
worktree 后 coflux 跟着走,未登记的同仓库 worktree 先登记出一个子工作区。
|
|
1191
|
+
插件自动调,一般不用手敲
|
|
1192
|
+
cofluxd workspace forget <path>
|
|
1193
|
+
该 worktree 已被删掉:其下所有终端搬回项目主工作区、工作区记录消失
|
|
1194
|
+
(不执行 git worktree remove)
|
|
1195
|
+
|
|
1196
|
+
agent 命令的环境变量:COFLUX_AGENT_TIMEOUT_MS 收窄单次请求的等待上限(默认 30000,只能调小),
|
|
1197
|
+
供有硬超时的 hook 脚本用——到点干净失败,好过被宿主杀在半路。
|
|
1099
1198
|
|
|
1100
1199
|
up flags: --server <ws://.../daemon> --name <名> --shell <路径>
|
|
1101
1200
|
通用: --version <vX|latest>(不传时 up 沿用已有二进制,update 默认 latest) --bin-dir <dir>(用本地 cargo 产物) --no-start
|
|
@@ -1133,7 +1232,7 @@ let cmd = positionals[0];
|
|
|
1133
1232
|
if (values.help || cmd === "help") { console.log(HELP); process.exit(0); }
|
|
1134
1233
|
if (!cmd) cmd = fs.existsSync(SETTINGS) ? "status" : "up"; // 首次裸跑 → 引导
|
|
1135
1234
|
|
|
1136
|
-
const handlers = { up: cmdUp, update: cmdUpdate, restart: cmdRestart, down: cmdDown, status: cmdStatus, doctor: cmdDoctor, fda: cmdFda, logs: cmdLogs, uninstall: cmdUninstall, hook: cmdHook, terminal: cmdTerminal, notify: cmdNotify, progress: cmdProgress, ports: cmdPorts };
|
|
1235
|
+
const handlers = { up: cmdUp, update: cmdUpdate, restart: cmdRestart, down: cmdDown, status: cmdStatus, doctor: cmdDoctor, fda: cmdFda, logs: cmdLogs, uninstall: cmdUninstall, hook: cmdHook, terminal: cmdTerminal, notify: cmdNotify, progress: cmdProgress, ports: cmdPorts, workspace: cmdWorkspace };
|
|
1137
1236
|
const h = handlers[cmd];
|
|
1138
1237
|
if (!h) die(`未知命令: ${cmd}${MIGRATED[cmd] ? `\n${MIGRATED[cmd]}` : ""}\n\n${HELP}`);
|
|
1139
1238
|
await h(values);
|
package/package.json
CHANGED
package/skills/coflux/SKILL.md
CHANGED
|
@@ -1,222 +1,370 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: coflux
|
|
3
|
-
description:
|
|
3
|
+
description: When you run inside a coflux terminal, this skill documents the local cofluxd commands that open terminals the user can watch and take over from the coflux web/mobile app, report progress, call the user and hand out preview URLs, plus the center MCP for reaching other workspaces and devices. Your coordinates (device / project / workspace / terminal) arrive in a <coflux-session> block at session start, or via the COFLUX_* environment variables. For the workspace your cwd is in always use the zero-credential local cofluxd commands (open, read, wait, send, report progress, call the user, get preview URLs); use the center's coflux MCP only to reach beyond it (child workspaces, other workspaces or devices). Use when the user should be able to watch, step into or stop a command (interactive steps, dev servers, a job they are waiting on), when the user has to decide something, when you want to hand the user a clickable preview URL, or when you need an isolated child workspace for parallel work.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Working inside coflux
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
You may be running inside a coflux terminal. coflux lets the user watch agents working on many
|
|
9
|
+
machines from a browser or a phone and take over at any time. This skill gives you terminals the
|
|
10
|
+
user can see and take over, a progress line and a call button on the workspace card, preview URLs,
|
|
11
|
+
and a way to operate the other workspaces and devices under the account when you need to.
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
Two tracks, one rule: **whatever closes locally uses local commands; only reaching beyond the
|
|
14
|
+
workspace you are in goes through MCP.**
|
|
13
15
|
|
|
14
|
-
|
|
|
16
|
+
| Track | Credentials | Reach | Use for |
|
|
15
17
|
|---|---|---|---|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
+
| Local commands `cofluxd terminal/progress/notify/ports` | none (the daemon identifies you by process tree) | **the workspace your cwd is in** | open, read, wait, send, report progress, call the user, preview URLs: the default, fastest, no network dependency |
|
|
19
|
+
| Center MCP `coflux` | one OAuth authorization by the user in the host | **the whole account**: every device, project, workspace and terminal | child workspaces (git worktrees), cross-workspace / cross-device access, joining from outside coflux |
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
Of the local commands, `send`/`read`/`wait`/`notify`/`progress` complete entirely inside the
|
|
22
|
+
local daemon and never touch the center; `new`/`list`/`ports` are relayed to the center by the
|
|
23
|
+
daemon on your behalf (terminals must appear in the user's sidebar, preview URLs are minted by
|
|
24
|
+
the center). You only ever talk to the local daemon.
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
## Figure out where you are first
|
|
27
|
+
|
|
28
|
+
With the coflux plugin installed, Claude Code and Codex receive a `<coflux-session>` block at
|
|
29
|
+
session start (and again after context compaction). Your coordinates are in it; use them
|
|
30
|
+
directly. Without that block (no plugin, hand-wired hooks, hook not trusted yet), read the
|
|
31
|
+
environment:
|
|
22
32
|
|
|
23
33
|
```sh
|
|
24
34
|
env | grep '^COFLUX_'
|
|
25
35
|
```
|
|
26
36
|
|
|
27
|
-
- **`COFLUX_WORKSPACE_ID`
|
|
28
|
-
|
|
37
|
+
- **`COFLUX_WORKSPACE_ID` is non-empty** (or the `<coflux-session>` block is present) → you are in
|
|
38
|
+
a coflux terminal with an up-to-date daemon. The variables below are your coordinates; pass
|
|
39
|
+
these ids to MCP tools directly instead of guessing from `list_*`:
|
|
29
40
|
|
|
30
|
-
|
|
|
41
|
+
| Variable | Meaning |
|
|
31
42
|
|---|---|
|
|
32
|
-
| `COFLUX_DEVICE_ID` |
|
|
33
|
-
| `COFLUX_PROJECT_ID` |
|
|
34
|
-
| `COFLUX_WORKSPACE_ID` |
|
|
35
|
-
| `COFLUX_TASK_ID` |
|
|
36
|
-
| `COFLUX_SESSION_ID` |
|
|
37
|
-
| `COFLUX_MCP_URL` |
|
|
43
|
+
| `COFLUX_DEVICE_ID` | id of the device you run on (the id in `list_devices`) |
|
|
44
|
+
| `COFLUX_PROJECT_ID` | owning project id; empty string for a directory workspace without a repository |
|
|
45
|
+
| `COFLUX_WORKSPACE_ID` | the workspace this terminal was **opened** in (the id in `list_workspaces`). The variable is frozen when the terminal starts; the workspace the terminal *belongs to* can still change — see below. `cofluxd workspace` is the authority |
|
|
46
|
+
| `COFLUX_TASK_ID` | id of this terminal (the taskId / terminalId used by local commands and `read_terminal`) |
|
|
47
|
+
| `COFLUX_SESSION_ID` | id of this PTY session |
|
|
48
|
+
| `COFLUX_MCP_URL` | the center's MCP URL; the user configures MCP with it |
|
|
49
|
+
|
|
50
|
+
- **Variables empty or absent** → treat yourself as outside coflux: forget this skill and use your
|
|
51
|
+
own tools as usual (if the user configured the coflux MCP in the host, the MCP tools still work;
|
|
52
|
+
you just have no "where am I" coordinates). If the user insists you are inside a coflux terminal,
|
|
53
|
+
this machine's daemon has not been upgraded: tell the user to run `cofluxd update && cofluxd restart`;
|
|
54
|
+
after reopening the terminal the variables and the local commands are there.
|
|
55
|
+
|
|
56
|
+
### Two workspaces to keep apart: owning and effective
|
|
57
|
+
|
|
58
|
+
- **Owning workspace** = the workspace this terminal **belongs to**: what the user's sidebar shows it
|
|
59
|
+
under, what its turn state, branch and diff stats are attributed to. It starts out as
|
|
60
|
+
`COFLUX_WORKSPACE_ID` and moves with you when you enter or leave a git worktree (below).
|
|
61
|
+
- **Effective workspace** = the workspace **your current working directory is inside**. This is what
|
|
62
|
+
every local command acts on.
|
|
63
|
+
|
|
64
|
+
They are the same until your cwd wanders off. A plain `cd <path>` moves a *live* session — same
|
|
65
|
+
conversation, no restart — and a coflux child workspace is a normal registered git worktree, so a
|
|
66
|
+
session whose terminal belongs to workspace A can end up working inside workspace B. From that
|
|
67
|
+
moment, in B:
|
|
68
|
+
|
|
69
|
+
- `cofluxd terminal new` opens the terminal **in B**, under B in the user's sidebar, running in B's
|
|
70
|
+
directory, counting against B's terminal cap;
|
|
71
|
+
- `cofluxd terminal list` lists B's terminals, and A's terminals answer `read` / `wait` / `send`
|
|
72
|
+
with "not in this workspace or does not exist" (`cd` back to A to reach them again);
|
|
73
|
+
- MCP calls need **B's** id as `workspaceId`;
|
|
74
|
+
- the terminal itself stays under A, and `progress`, `notify` and `ports` still belong to it,
|
|
75
|
+
whatever your cwd is; `COFLUX_TASK_ID` and `COFLUX_SESSION_ID` never change.
|
|
76
|
+
|
|
77
|
+
If your cwd is outside every coflux workspace (say `/tmp`), local commands fall back to the owning
|
|
78
|
+
workspace.
|
|
79
|
+
|
|
80
|
+
A terminal opened before the daemon was upgraded is the one case with no owning workspace at all:
|
|
81
|
+
its local commands are refused with "predates the daemon upgrade" whatever your cwd is, because the
|
|
82
|
+
daemon never guesses ownership from a directory. Open a new terminal.
|
|
83
|
+
|
|
84
|
+
### coflux follows you into a git worktree
|
|
85
|
+
|
|
86
|
+
`EnterWorktree` switches this live session into a git worktree (its own, or an existing one you point
|
|
87
|
+
it at), `ExitWorktree` switches back, and resuming a session that had entered one puts you straight
|
|
88
|
+
back in it. **coflux comes along**: the terminal's *owning* workspace moves to the workspace that
|
|
89
|
+
worktree is, and if coflux has never seen that worktree it registers it as a child workspace of this
|
|
90
|
+
project first — a new card appears in the user's sidebar, with its branch and diff stats. Nothing is
|
|
91
|
+
interrupted: same terminal, same PTY, same conversation, and the user keeps watching it where it now
|
|
92
|
+
lives. When Claude Code cleans up its own worktree on exit, that workspace's terminals move back to
|
|
93
|
+
the project's main workspace and the record disappears by itself.
|
|
94
|
+
|
|
95
|
+
So, after entering or leaving a worktree, owning **and** effective are both the new workspace: pass
|
|
96
|
+
its id to MCP tools and everything local already acts on it. The plugin drops the new id next to the
|
|
97
|
+
tool result, and `cofluxd workspace` always tells you. Two things stay behind on purpose:
|
|
98
|
+
|
|
99
|
+
- `COFLUX_WORKSPACE_ID` (and the id in the `<coflux-session>` block from earlier in this session)
|
|
100
|
+
still names where the terminal was *opened*; it is frozen when the PTY starts and cannot be
|
|
101
|
+
rewritten. Never reuse it after a move.
|
|
102
|
+
- The shell inside this terminal keeps its own directory. That is only about the shell; it does not
|
|
103
|
+
affect where your work is attributed.
|
|
104
|
+
|
|
105
|
+
Nothing happens when coflux cannot follow, and nothing is blocked either: another repository, a
|
|
106
|
+
directory that is not a git repository, a terminal opened in a directory workspace (no project), or
|
|
107
|
+
a daemon that is down or too old — the session just carries on with the ownership it had.
|
|
108
|
+
|
|
109
|
+
### Ask where you are
|
|
38
110
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
只是你不知道自己的 id;要用 MCP 就先 `list_devices` / `list_workspaces` 按路径对一下
|
|
44
|
-
`pwd`,并顺手告诉用户 `cofluxd update && cofluxd restart` 之后就有环境变量了。
|
|
111
|
+
```sh
|
|
112
|
+
cofluxd workspace
|
|
113
|
+
{"workspaceId":"ws-b","path":"/Users/me/.coflux/worktrees/ws-b","owningWorkspaceId":"ws-a","moved":true}
|
|
114
|
+
```
|
|
45
115
|
|
|
46
|
-
|
|
116
|
+
One line of JSON: `workspaceId` (+ `path`) is the **effective** workspace, `owningWorkspaceId` is the
|
|
117
|
+
workspace this terminal belongs to right now, and `moved` says whether they differ. With the plugin
|
|
118
|
+
installed you also get a `<coflux-session-moved>` block at the start of every prompt while the two
|
|
119
|
+
differ — but that block only arrives with the **next** user prompt. **About to call an MCP tool right
|
|
120
|
+
after a `cd`? Run `cofluxd workspace` first** and use the `workspaceId` it prints; do not reuse
|
|
121
|
+
`COFLUX_WORKSPACE_ID`.
|
|
47
122
|
|
|
48
|
-
|
|
123
|
+
## When to open a terminal
|
|
49
124
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
125
|
+
A coflux terminal is a process the user can see: a titled entry in their sidebar that they can
|
|
126
|
+
open, take over and type into, whose output you can read back at any time.
|
|
127
|
+
Whether a command runs in your own Bash or in a coflux terminal is your call; a coflux terminal is
|
|
128
|
+
worth it when the user's view of the process matters:
|
|
53
129
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
130
|
+
- the user may want to step in: interactive steps, confirmations, something they may need to stop
|
|
131
|
+
midway or rescue when it fails
|
|
132
|
+
- it keeps running and the user will want to find it later (dev server, watch mode, log tailing)
|
|
133
|
+
- you want to hand the user something to look at (a test run they asked to watch, a build they are
|
|
134
|
+
waiting on)
|
|
57
135
|
|
|
58
|
-
|
|
59
|
-
|
|
136
|
+
**Do not use it** for quick one-shot commands (`ls`, `grep`, `git status`, reading files): your
|
|
137
|
+
own tools are faster, and a pile of one-second terminals is just noise to the user.
|
|
60
138
|
|
|
61
|
-
##
|
|
139
|
+
## Local commands
|
|
62
140
|
|
|
63
|
-
###
|
|
141
|
+
### Open a terminal
|
|
142
|
+
|
|
143
|
+
There are two kinds, told apart by one single thing: **whether you pass a command**.
|
|
64
144
|
|
|
65
145
|
```sh
|
|
66
|
-
cofluxd terminal new --title "
|
|
146
|
+
cofluxd terminal new --title="Run unit tests" --cmd="pnpm -C tests test" # job terminal
|
|
147
|
+
cofluxd terminal new --title="Debug shell" # session terminal
|
|
67
148
|
```
|
|
68
149
|
|
|
69
|
-
`--title`
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
150
|
+
`--title` is the name the user sees in the sidebar; **name it properly**: "Run unit tests",
|
|
151
|
+
"Start dev server", never "terminal 1". Either kind runs in the directory of the workspace your cwd
|
|
152
|
+
is in (which is not always the one this terminal was opened in — see "owning and effective").
|
|
153
|
+
|
|
154
|
+
Always write `--cmd=<value>` and `--title=<value>` with the `=`, never separated by a space: a
|
|
155
|
+
value that starts with `-` is otherwise taken for another option and the call fails outright.
|
|
156
|
+
|
|
157
|
+
**Job terminal — with `--cmd=...`.** It runs that one command under the login shell (command line
|
|
158
|
+
capped at 16 KB). The terminal exits when the command finishes and the task becomes `exited` with
|
|
159
|
+
the exit code: that is how you tell success from failure. So do not expect to run a second command
|
|
160
|
+
in the same terminal: write `a && b`, or open another one. The output is also written to a local
|
|
161
|
+
log for you to read back (roughly the last 1 MB is kept). The cost is that the command's stdout is
|
|
162
|
+
a pipe rather than a tty: most programs turn off colors and progress bars, full-screen programs
|
|
163
|
+
(vim, htop, less) do not work at all, and a few switch to a different "CI" behavior.
|
|
164
|
+
|
|
165
|
+
**Session terminal — no `--cmd` at all.** You get exactly what the user gets by clicking "new
|
|
166
|
+
terminal" in the sidebar: the default login shell in the workspace directory, with stdin **and**
|
|
167
|
+
stdout on a real tty. It runs nothing by itself and **never exits on its own** — it lives until
|
|
168
|
+
`exit` is typed into it (by you with `send`, or by the user), or the user stops it. Reach for it
|
|
169
|
+
when you need several commands in the same shell, a TUI or a program whose colors and progress
|
|
170
|
+
bars matter, or simply a terminal the user can step into and keep using. There is no command log
|
|
171
|
+
for it: `read` returns the current screen (one screenful, no history), so you judge how it went
|
|
172
|
+
from what is on screen, and `wait` is only meaningful after you have sent `exit`.
|
|
173
|
+
|
|
174
|
+
Driving a session terminal:
|
|
175
|
+
|
|
176
|
+
1. `cofluxd terminal new --title="Debug shell"` → prints a taskId.
|
|
177
|
+
2. `cofluxd terminal read <taskId>` until you see the shell prompt. The shell needs a moment to
|
|
178
|
+
start and the first read can come back empty — **never `send` before you have seen a prompt**.
|
|
179
|
+
3. `cofluxd terminal send <taskId> --text="pnpm build" --enter`, then `read` again to see what
|
|
180
|
+
happened. One send per command; nothing signals you when a command finished, so read until the
|
|
181
|
+
prompt is back. To make that unambiguous, end the command with a marker of your own
|
|
182
|
+
(`pnpm build; echo DONE-$?`) and read until the marker shows up.
|
|
183
|
+
4. `cofluxd terminal send <taskId> --text="exit" --enter` when you are done; the terminal then
|
|
184
|
+
becomes `exited` with the shell's exit code.
|
|
185
|
+
|
|
186
|
+
The new terminal has the same `COFLUX_*` variables (pointing at its own task/session ids, same
|
|
187
|
+
workspace as you).
|
|
188
|
+
|
|
189
|
+
### See how far it got
|
|
74
190
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
191
|
+
```sh
|
|
192
|
+
cofluxd terminal list # every terminal in the workspace your cwd is in: id, state, exit code, title
|
|
193
|
+
cofluxd terminal read <taskId> # a terminal's content (plain text, last 200 lines by default)
|
|
194
|
+
cofluxd terminal read <taskId> --lines 50
|
|
195
|
+
```
|
|
78
196
|
|
|
79
|
-
|
|
197
|
+
`list` states are `running` / `exited` / `idle`; `exited` carries `exit=<code>`.
|
|
198
|
+
**An exited terminal can still be read**: "the command finished, look at the output" is the most
|
|
199
|
+
common case. `read` reads the local log of a job terminal; terminals that have no log (session
|
|
200
|
+
terminals, and the ones the user opened) return the current screen instead — one screenful, no
|
|
201
|
+
history, and empty for the first moments after opening. Both are immediate.
|
|
80
202
|
|
|
81
|
-
###
|
|
203
|
+
### Wait for a command to finish
|
|
82
204
|
|
|
83
205
|
```sh
|
|
84
|
-
cofluxd terminal
|
|
85
|
-
cofluxd terminal
|
|
86
|
-
cofluxd terminal read <taskId> --lines 50
|
|
206
|
+
cofluxd terminal wait <taskId> # block until that terminal exits and print the exit code (default cap 30 minutes)
|
|
207
|
+
cofluxd terminal wait <taskId> --timeout 300 # custom timeout in seconds; a timeout fails loudly with a non-zero exit
|
|
87
208
|
```
|
|
88
209
|
|
|
89
|
-
|
|
90
|
-
|
|
210
|
+
To wait for a command use `wait`; **do not write your own polling loop**. One command blocks
|
|
211
|
+
until done and hands you the exit code. A timeout does not mean the command failed, only that it
|
|
212
|
+
is still running: `read` to see where it is, then decide whether to keep waiting or act.
|
|
91
213
|
|
|
92
|
-
|
|
214
|
+
`wait` **always exits 0** once the terminal is done: it reports that the command finished, not
|
|
215
|
+
whether it succeeded. Read the result off its output line `# exited exit=<code>` (`list` shows the
|
|
216
|
+
same). A non-zero exit from `wait` itself means the wait timed out or the id was wrong.
|
|
93
217
|
|
|
94
|
-
|
|
218
|
+
**Do not `wait` on a session terminal** unless you have already sent it `exit`: it never finishes
|
|
219
|
+
by itself, so the wait can only end in the 30-minute timeout — a timeout there means the shell is
|
|
220
|
+
still sitting at its prompt, nothing more. Its exit code, when it finally exits, is the shell's and
|
|
221
|
+
not any command's: check what a command did by reading the screen.
|
|
95
222
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
cofluxd terminal wait <taskId> --timeout 300 # 自定超时(秒);超时会明确报错并非零退出
|
|
99
|
-
```
|
|
223
|
+
**Keep working, and be woken up when it finishes.** `wait` blocks, so run it as a backgrounded Bash
|
|
224
|
+
call of your own:
|
|
100
225
|
|
|
101
|
-
|
|
102
|
-
|
|
226
|
+
1. `cofluxd terminal new --title="Run the test suite" --cmd="pnpm -C tests test"` → prints a taskId.
|
|
227
|
+
2. Run `cofluxd terminal wait <taskId>` as a backgrounded Bash call, then go do something else.
|
|
228
|
+
3. The host wakes you when that call exits. Check its output for `# exited exit=<code>`, then
|
|
229
|
+
`cofluxd terminal read <taskId>` to see what actually happened.
|
|
103
230
|
|
|
104
|
-
|
|
231
|
+
That gets you both halves at once: the user watches (and can take over) a real terminal, and you are
|
|
232
|
+
still told the moment it is over, instead of blocking or polling for it.
|
|
233
|
+
|
|
234
|
+
### Type into a terminal
|
|
105
235
|
|
|
106
236
|
```sh
|
|
107
|
-
cofluxd terminal send <taskId> --text "y" --enter #
|
|
108
|
-
cofluxd terminal send <taskId> --enter #
|
|
237
|
+
cofluxd terminal send <taskId> --text "y" --enter # type a line and press Enter
|
|
238
|
+
cofluxd terminal send <taskId> --enter # just press Enter
|
|
109
239
|
```
|
|
110
240
|
|
|
111
|
-
|
|
241
|
+
For interactive confirmations (y/N, menus), or to add a command in the same shell after the
|
|
242
|
+
previous one finished. Discipline:
|
|
112
243
|
|
|
113
|
-
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
244
|
+
- **`read` before `send`**: see what the terminal is waiting for before typing; never type blind.
|
|
245
|
+
On a freshly opened session terminal this also means waiting for the shell prompt to appear.
|
|
246
|
+
- **Refused while the user is taking over**: that is not an error, it is by design; humans always
|
|
247
|
+
win. Stop when refused; use `notify` to communicate, do not retry.
|
|
248
|
+
- **After a send timeout do not resend right away**: `read` first to check whether the input
|
|
249
|
+
actually landed; duplicated input is worse than lost input.
|
|
250
|
+
- A single text is capped at 64 KB; this is an interactive input channel, not a file transfer.
|
|
117
251
|
|
|
118
|
-
###
|
|
252
|
+
### Report progress
|
|
119
253
|
|
|
120
254
|
```sh
|
|
121
|
-
cofluxd progress "
|
|
255
|
+
cofluxd progress "Reproduced; narrowing down the relay reconnect timing"
|
|
122
256
|
```
|
|
123
257
|
|
|
124
|
-
|
|
125
|
-
|
|
258
|
+
One sentence telling the user how far you are, shown on the workspace card and replaced by the
|
|
259
|
+
next one. Update it at milestones: reproduced, located, fixed and verifying, stuck on X. It
|
|
260
|
+
**does not interrupt the user**; it is a different channel from `notify`:
|
|
126
261
|
|
|
127
|
-
- `progress` =
|
|
128
|
-
- `notify` =
|
|
262
|
+
- `progress` = broadcast (the user glances and knows the state, no response needed)
|
|
263
|
+
- `notify` = call the user (the workspace turns "waiting for interaction", the user should come and look)
|
|
129
264
|
|
|
130
|
-
|
|
265
|
+
If unsure: when the user does not have to do anything, use `progress`.
|
|
131
266
|
|
|
132
|
-
###
|
|
267
|
+
### Call the user
|
|
133
268
|
|
|
134
269
|
```sh
|
|
135
|
-
cofluxd notify "
|
|
270
|
+
cofluxd notify "Both approaches work; I need you to pick one"
|
|
136
271
|
```
|
|
137
272
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
273
|
+
The user's sidebar switches this workspace to "waiting for interaction" and shows this sentence;
|
|
274
|
+
they see it on the phone too. Use it when you are **really stuck**: a decision is needed, a
|
|
275
|
+
password or a permission, a problem only a human can judge. One sentence saying what you need;
|
|
276
|
+
do not write a log.
|
|
141
277
|
|
|
142
|
-
|
|
143
|
-
|
|
278
|
+
(Your normal questions and permission prompts already show up in the sidebar state; they need no
|
|
279
|
+
extra notify. This is for "what you have to say cannot be guessed from the status icon".)
|
|
144
280
|
|
|
145
|
-
###
|
|
281
|
+
### Hand the user a clickable preview
|
|
146
282
|
|
|
147
283
|
```sh
|
|
148
284
|
cofluxd ports
|
|
149
285
|
```
|
|
150
286
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
287
|
+
Lists every listening port in this workspace with its public preview URL. After starting a dev
|
|
288
|
+
server, use it to get the URL and tell the user directly; they click it and nobody has to dig.
|
|
289
|
+
|
|
290
|
+
### Errors from local commands
|
|
291
|
+
|
|
292
|
+
Errors are one readable sentence; do what they say: "not inside a coflux terminal" = you are not
|
|
293
|
+
in a coflux session; "terminal is not in this workspace or does not exist" = check the id with
|
|
294
|
+
`list`, and if you moved into another workspace that is exactly what a terminal of the other one
|
|
295
|
+
looks like (`cofluxd workspace` to confirm, `cd` back to reach it); "predates the daemon upgrade" =
|
|
296
|
+
that terminal was opened before the daemon upgrade, open a new one; a `new` without `--cmd` refused for a missing command = this machine's daemon is older
|
|
297
|
+
than session terminals, tell the user to run `cofluxd update && cofluxd restart` (or pass a command
|
|
298
|
+
and use a job terminal); "daemon is not connected to the center" only appears on
|
|
299
|
+
`new`/`list`/`ports`, retry once it reconnects.
|
|
300
|
+
|
|
301
|
+
## Center MCP: leaving this workspace
|
|
302
|
+
|
|
303
|
+
Local commands only see the workspace your cwd is in. Use the MCP server named `coflux` in the
|
|
304
|
+
host **only** for these:
|
|
305
|
+
|
|
306
|
+
- **Open an isolated child workspace to work in parallel**: `create_workspace` (project id from
|
|
307
|
+
`$COFLUX_PROJECT_ID`) really runs `git worktree add` on the device; then `create_terminal` runs
|
|
308
|
+
commands there. The same two kinds apply: `create_terminal` with a `command` opens a
|
|
309
|
+
job terminal, without one it opens a session terminal.
|
|
310
|
+
- **Look at or operate terminals in other workspaces or on other devices**: `list_*` →
|
|
311
|
+
`read_terminal` / `send_terminal_input`.
|
|
312
|
+
- **Join everything under the account when you are not inside a coflux terminal** (for example
|
|
313
|
+
Claude Code the user started on their own machine).
|
|
314
|
+
- **Deleting a workspace**: `remove_workspace` (it closes that workspace's terminals first, then
|
|
315
|
+
removes the worktree and the record). Inside a coflux project the plugin blocks
|
|
316
|
+
`git worktree remove|move` run by hand, because that leaves an orphan workspace record in the user's
|
|
317
|
+
sidebar. Creating a worktree is *not* blocked — coflux follows you into it (see above) — and Claude
|
|
318
|
+
Code's own worktrees need no cleanup from you at all.
|
|
319
|
+
|
|
320
|
+
Do not detour through MCP for work inside the workspace you are in — including one you moved into
|
|
321
|
+
with `cd` or EnterWorktree, where the local commands follow you: that is an extra round trip to the
|
|
322
|
+
center, while a local command does it in one step.
|
|
323
|
+
|
|
324
|
+
### When MCP is not configured
|
|
325
|
+
|
|
326
|
+
Run `claude mcp list` (Codex: `codex mcp list`) to see whether `coflux` is there. If not, give
|
|
327
|
+
the user the one-line setup, with the URL from `$COFLUX_MCP_URL` (it is the center's public URL
|
|
328
|
+
+ `/mcp`):
|
|
167
329
|
|
|
168
330
|
```sh
|
|
169
331
|
claude mcp add --transport http coflux "$COFLUX_MCP_URL" # Claude Code
|
|
170
332
|
codex mcp add coflux --url "$COFLUX_MCP_URL" # Codex
|
|
171
333
|
```
|
|
172
334
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
本地命令的纪律(先 read 再 send、被拒即停、`progress` 与 `notify` 分界、用 `wait` 别轮询)
|
|
210
|
-
在 MCP 里同样成立。
|
|
211
|
-
|
|
212
|
-
## 边界
|
|
213
|
-
|
|
214
|
-
- 你能开、读、等、输入,但**输入是人类优先的受限写权**:用户正在接管的终端你写不进去
|
|
215
|
-
(会被明确拒绝),用户随时接管也会把你顶掉。别和人抢终端。
|
|
216
|
-
- 本地命令只看得见**你自己所在的工作区**;别的工作区和别的机器要经 MCP,且限于同一账号。
|
|
217
|
-
- 一个工作区同时活着的终端有上限(默认 8,含用户自己开的)。撞上限先 `list` 看看,
|
|
218
|
-
多半是有跑完没收的;真是用户占满了,就 `notify` 告诉他,别硬试。
|
|
219
|
-
- 这些命令与 tools 都要 daemon 连着中心才能用——毕竟「让用户看见」就是它们的全部意义。
|
|
220
|
-
连不上时会明确报错,不会默默降级。
|
|
221
|
-
- `COFLUX_*` 变量只在 coflux 开出来的 PTY 里有;你自己 `export` 或改它们没有任何效果,
|
|
222
|
-
中心只认它自己下发的 id。
|
|
335
|
+
The host then guides the user through a one-time OAuth authorization in the browser (`/mcp` in
|
|
336
|
+
Claude Code). Authorization is the user's job; you only hand over the URL and the command. Until
|
|
337
|
+
it is set up, keep doing the work inside this workspace with local commands.
|
|
338
|
+
|
|
339
|
+
### Using the tools
|
|
340
|
+
|
|
341
|
+
The tool list and each tool's contract (parameters, limits, what an error means) come from the
|
|
342
|
+
MCP server itself: read the tool descriptions in the host, they are the source of truth and this
|
|
343
|
+
file does not repeat them. Take ids from the `COFLUX_*` variables first — except the workspace id
|
|
344
|
+
after you moved, which comes from `cofluxd workspace` (or the `<coflux-session-moved>` block); for
|
|
345
|
+
anything outside the workspace you are in, find ids with the `list_*` tools.
|
|
346
|
+
|
|
347
|
+
The local-command disciplines apply to MCP just the same: `read_terminal` before
|
|
348
|
+
`send_terminal_input`, stop when refused because the user is taking over (communicate with
|
|
349
|
+
`cofluxd notify` instead of retrying), `wait_terminal` instead of a polling loop around
|
|
350
|
+
`read_terminal`, and stop on "needs upgrade" (tell the user to run
|
|
351
|
+
`cofluxd update && cofluxd restart` on that device; do not retry or work around it).
|
|
352
|
+
|
|
353
|
+
## Boundaries
|
|
354
|
+
|
|
355
|
+
- You can open, read, wait and type, but **typing is a restricted write with humans first**: you
|
|
356
|
+
cannot write into a terminal the user is taking over (you are refused explicitly), and the user
|
|
357
|
+
taking over at any time displaces you. Do not fight a human for a terminal.
|
|
358
|
+
- Local commands only see **the workspace your cwd is in** (`cofluxd workspace` says which one);
|
|
359
|
+
other workspaces and other machines go through MCP and are limited to the same account.
|
|
360
|
+
- A workspace has a cap on concurrently live terminals (default 8, including the user's own).
|
|
361
|
+
On hitting the cap, `list` first: usually some finished terminals were never collected. If the
|
|
362
|
+
user really filled it up, `notify` them instead of forcing it.
|
|
363
|
+
- `new`/`list`/`ports` and every MCP tool need the daemon connected to the center; "letting the
|
|
364
|
+
user see" is their whole point. `send`/`read`/`wait`/`notify`/`progress` do not depend on the
|
|
365
|
+
center. When disconnected they fail loudly rather than degrade silently.
|
|
366
|
+
- `COFLUX_*` variables exist only in PTYs opened by coflux; exporting or changing them yourself
|
|
367
|
+
has no effect, the center only trusts the ids it issued. `COFLUX_WORKSPACE_ID` always means the
|
|
368
|
+
workspace this terminal was **opened** in and goes stale the moment coflux follows you into a
|
|
369
|
+
worktree; both "where does this terminal belong now" and "where am I acting" come from
|
|
370
|
+
`cofluxd workspace`.
|