@useshifu/coding-harness 0.3.16 → 0.3.17
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 +19 -3
- package/bin/shifu-harness.js +78 -16
- package/package.json +1 -1
- package/skills/shifu-context/SKILL.md +2 -2
package/README.md
CHANGED
|
@@ -104,7 +104,7 @@ Missing local turns are reported in `unavailableSegments`. A partial overlap wit
|
|
|
104
104
|
The context branch adds a remote MCP connection using the same saved Codex key:
|
|
105
105
|
|
|
106
106
|
```sh
|
|
107
|
-
npx --yes @useshifu/coding-harness@0.3.
|
|
107
|
+
npx --yes @useshifu/coding-harness@0.3.17 mcp-setup --harness codex --mcp-url https://YOUR-BRANCH-HOST/mcp
|
|
108
108
|
```
|
|
109
109
|
|
|
110
110
|
Before this release is published, install dependencies and run from the checkout root:
|
|
@@ -133,13 +133,29 @@ Official configuration reference: https://learn.chatgpt.com/docs/config-file/con
|
|
|
133
133
|
OpenCode uses the same `/mcp` endpoint and three tools, authenticated with its own saved OpenCode connection key:
|
|
134
134
|
|
|
135
135
|
```sh
|
|
136
|
-
npx --yes @useshifu/coding-harness@0.3.
|
|
136
|
+
npx --yes @useshifu/coding-harness@0.3.17 mcp-setup --harness opencode --mcp-url https://YOUR-SHIFU-HOST/mcp
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
-
Until 0.3.
|
|
139
|
+
Until 0.3.17 is published, install dependencies with `npm ci --prefix packages/shifu-coding-harness --ignore-scripts` from the checkout root, then run the checkout CLI with the same arguments. Setup preserves existing sync settings and other MCP servers, checks authenticated initialization and tool discovery, and installs the `shifu-context` usage instructions. A missing OpenCode connection prompts for its key in the terminal. A Codex key cannot authorize an OpenCode connection.
|
|
140
140
|
|
|
141
141
|
The installer adds a remote `shifu` entry with `oauth: false` to OpenCode's JSON or JSONC configuration, preserving existing content and comments. Its Authorization header uses OpenCode's `{file:...}` substitution to read a protected local credential file; the key stays out of configuration, command arguments and chat. Replacing the saved connection key refreshes that file; disconnect removes it. Restart OpenCode and start a new session after setup. Project-specific configuration can override global MCP settings; inspect `opencode mcp list` in the project if tools do not appear.
|
|
142
142
|
|
|
143
143
|
The API derives the harness and tenant from the authenticated connector key. Memory updates from OpenCode carry `opencode` provenance; personal eligibility, general retrieval and evidence verification use the same rules as Codex. Coding-session sync remains separate. No additional local MCP server or OpenCode plugin is required.
|
|
144
144
|
|
|
145
145
|
Official references: [MCP servers](https://docs.opencode.ai/docs/mcp-servers/) and [configuration](https://docs.opencode.ai/docs/config/).
|
|
146
|
+
|
|
147
|
+
## Claude Code context and memory
|
|
148
|
+
|
|
149
|
+
Claude Code connects to the same `/mcp` endpoint and three tools with its own saved connection key:
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
npx --yes @useshifu/coding-harness@0.3.17 mcp-setup --harness claude_code --mcp-url https://YOUR-SHIFU-HOST/mcp
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Until 0.3.17 is published, install checkout dependencies as above and run the checkout CLI with these arguments. Setup checks the existing Claude Code key, authenticated initialization and tool discovery, and installs the `shifu-context` skill. It preserves the sync endpoint, schedule, checkpoints and other MCP servers. A missing connection prompts for a Claude Code key in the terminal; do not substitute a Codex or OpenCode key.
|
|
156
|
+
|
|
157
|
+
The user-scope HTTP connection uses Claude Code's `headersHelper` to read the protected local Shifu connection file. The key is absent from MCP configuration and command arguments. Replacing the saved key is picked up when the helper runs on reconnection. Restart Claude Code or start a new session after setup; use `/mcp` to inspect or reconnect Shifu. Local or project MCP configuration can override a user-scope server with the same name.
|
|
158
|
+
|
|
159
|
+
The API records memory provenance as `claude_code` from the authenticated key. Context retrieval and memory behavior match Codex and OpenCode; ordinary session sync continues independently. This setup requires local macOS/Linux and a Claude Code CLI supporting HTTP `headersHelper`, verified with Claude Code 2.1.295. Unsupported or broken older clients need an update using Claude Code's official installation instructions; there is no plaintext credential fallback. `CLAUDE_CONFIG_DIR` is honored for MCP configuration, installed skills and session discovery.
|
|
160
|
+
|
|
161
|
+
Official reference: [Claude Code MCP connections](https://code.claude.com/docs/en/mcp).
|
package/bin/shifu-harness.js
CHANGED
|
@@ -40,7 +40,7 @@ const MAX_TURNS_PER_SEGMENT = 12;
|
|
|
40
40
|
const MAX_TITLE_LENGTH = 160;
|
|
41
41
|
const MAX_WORK_DETAIL_LENGTH = 600;
|
|
42
42
|
const CONFIG_VERSION = 2;
|
|
43
|
-
const RUNNER_VERSION = "0.3.
|
|
43
|
+
const RUNNER_VERSION = "0.3.17";
|
|
44
44
|
const REDACTION_VERSION = 4;
|
|
45
45
|
const DEFAULT_INTERVAL_HOURS = 1;
|
|
46
46
|
const LEGACY_INTERVAL_HOURS = 4;
|
|
@@ -94,7 +94,7 @@ function codexSessionsRoot() {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
function claudeSessionsRoot() {
|
|
97
|
-
return process.env.SHIFU_CLAUDE_SESSIONS_ROOT || path.join(
|
|
97
|
+
return process.env.SHIFU_CLAUDE_SESSIONS_ROOT || path.join(claudeHome(), "projects");
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
function opencodeQuery(query) {
|
|
@@ -1085,7 +1085,7 @@ function removeCodexHooks() {
|
|
|
1085
1085
|
|
|
1086
1086
|
function installDestination(harness) {
|
|
1087
1087
|
if (harness === "codex") return path.join(codexHome(), "skills", "shifu-sync");
|
|
1088
|
-
if (harness === "claude_code") return path.join(
|
|
1088
|
+
if (harness === "claude_code") return path.join(claudeHome(), "skills", "shifu-sync");
|
|
1089
1089
|
return path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "opencode", "skills", "shifu-sync");
|
|
1090
1090
|
}
|
|
1091
1091
|
|
|
@@ -1120,7 +1120,7 @@ function install(harness, quiet = false) {
|
|
|
1120
1120
|
.replaceAll("npx --yes @useshifu/coding-harness", `"${process.execPath}" "${installedRunnerPath()}"`);
|
|
1121
1121
|
fs.writeFileSync(skill, content, { mode: 0o600 });
|
|
1122
1122
|
}
|
|
1123
|
-
if (
|
|
1123
|
+
if (Object.hasOwn(HARNESS_NAMES, harness)) {
|
|
1124
1124
|
const contextDestination = path.join(path.dirname(installDestination(harness)), "shifu-context");
|
|
1125
1125
|
copyDirectory(path.join(__dirname, "..", "skills", "shifu-context"), contextDestination);
|
|
1126
1126
|
const contextFile = path.join(contextDestination, "SKILL.md");
|
|
@@ -1310,8 +1310,12 @@ function codexHome() {
|
|
|
1310
1310
|
return process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
|
|
1311
1311
|
}
|
|
1312
1312
|
|
|
1313
|
+
function claudeHome() {
|
|
1314
|
+
return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1313
1317
|
function mcpHeaders(harness) {
|
|
1314
|
-
|
|
1318
|
+
requireHarness(harness);
|
|
1315
1319
|
const file = configPath(harness);
|
|
1316
1320
|
const stat = fs.lstatSync(file);
|
|
1317
1321
|
if (!stat.isFile() || (process.platform !== "win32" && ((stat.mode & 0o077) || stat.uid !== process.getuid()))) {
|
|
@@ -1322,8 +1326,8 @@ function mcpHeaders(harness) {
|
|
|
1322
1326
|
return { Authorization: `Bearer ${config.token}` };
|
|
1323
1327
|
}
|
|
1324
1328
|
|
|
1325
|
-
function helperCommand() {
|
|
1326
|
-
const args = [process.execPath, installedRunnerPath(), "mcp-headers", "--harness",
|
|
1329
|
+
function helperCommand(harness = "codex") {
|
|
1330
|
+
const args = [process.execPath, path.resolve(installedRunnerPath()), "mcp-headers", "--harness", harness, "--config-root", path.resolve(configRoot())];
|
|
1327
1331
|
if (process.platform === "win32") throw new Error("Prompt-driven MCP credential setup currently supports macOS and Linux. Windows requires a supported local credential-helper launcher.");
|
|
1328
1332
|
return args.map((value) => `'${value.replaceAll("'", "'\\''")}'`).join(" ");
|
|
1329
1333
|
}
|
|
@@ -1415,7 +1419,8 @@ async function verifyMcp(mcpUrl, token) {
|
|
|
1415
1419
|
|
|
1416
1420
|
function prepareCodexMcpConfig(mcpUrl, helper) {
|
|
1417
1421
|
const file = path.join(codexHome(), "config.toml");
|
|
1418
|
-
const
|
|
1422
|
+
const exists = fs.existsSync(file);
|
|
1423
|
+
const original = exists ? fs.readFileSync(file, "utf8") : "";
|
|
1419
1424
|
const temporaryHome = fs.mkdtempSync(path.join(os.tmpdir(), "shifu-codex-mcp-"));
|
|
1420
1425
|
const candidateFile = path.join(temporaryHome, "config.toml");
|
|
1421
1426
|
const environment = { ...process.env, CODEX_HOME: temporaryHome };
|
|
@@ -1432,7 +1437,7 @@ function prepareCodexMcpConfig(mcpUrl, helper) {
|
|
|
1432
1437
|
if (!parsed.transport?.http_headers_helper || parsed.transport.type !== "streamable_http" || parsed.transport.url !== mcpUrl) {
|
|
1433
1438
|
throw new Error("This Codex version does not support local HTTP header helpers. Update Codex before MCP setup.");
|
|
1434
1439
|
}
|
|
1435
|
-
return { file, original, candidate };
|
|
1440
|
+
return { file, exists, original, candidate };
|
|
1436
1441
|
} catch (error) {
|
|
1437
1442
|
if (error?.code === "ENOENT") throw new Error("The codex command is unavailable. Install Codex CLI before MCP setup.");
|
|
1438
1443
|
if (error instanceof Error && !error.stdout && !error.stderr) throw error;
|
|
@@ -1542,10 +1547,66 @@ function removeOpenCodeMcpConfig() {
|
|
|
1542
1547
|
fs.rmSync(openCodeTokenPath(), { force: true });
|
|
1543
1548
|
}
|
|
1544
1549
|
|
|
1550
|
+
function claudeMcpConfig() {
|
|
1551
|
+
const file = process.env.CLAUDE_CONFIG_DIR ? path.resolve(claudeHome(), ".claude.json") : path.join(os.homedir(), ".claude.json");
|
|
1552
|
+
const exists = fs.existsSync(file);
|
|
1553
|
+
if (exists && !fs.lstatSync(file).isFile()) throw new Error("Claude Code configuration must be a regular file, not a link. Nothing was changed.");
|
|
1554
|
+
const original = exists ? fs.readFileSync(file, "utf8") : "";
|
|
1555
|
+
let value;
|
|
1556
|
+
try { value = JSON.parse(original || "{}"); }
|
|
1557
|
+
catch { throw new Error("Claude Code configuration is invalid. Fix its JSON before MCP setup; nothing was changed."); }
|
|
1558
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || (value.mcpServers !== undefined && (!value.mcpServers || typeof value.mcpServers !== "object" || Array.isArray(value.mcpServers)))) {
|
|
1559
|
+
throw new Error("Claude Code configuration is invalid. Nothing was changed.");
|
|
1560
|
+
}
|
|
1561
|
+
return { file, exists, original, value };
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
function editClaudeMcp(original, entry) {
|
|
1565
|
+
return jsonc.applyEdits(original || "{}\n", jsonc.modify(original || "{}\n", ["mcpServers", "shifu"], entry, { formattingOptions: { insertSpaces: true, tabSize: 2, eol: "\n" } }));
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
function prepareClaudeMcpConfig(mcpUrl, helper) {
|
|
1569
|
+
const config = claudeMcpConfig();
|
|
1570
|
+
const entry = { type: "http", url: mcpUrl, headersHelper: helper, timeout: 120_000 };
|
|
1571
|
+
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "shifu-claude-mcp-"));
|
|
1572
|
+
const environment = { ...process.env, HOME: temporary, CLAUDE_CONFIG_DIR: temporary, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" };
|
|
1573
|
+
delete environment.CLAUDECODE;
|
|
1574
|
+
delete environment.CLAUDE_CODE_SESSION_ID;
|
|
1575
|
+
try {
|
|
1576
|
+
const claude = process.env.SHIFU_CLAUDE_BIN || "claude";
|
|
1577
|
+
const version = execFileSync(claude, ["--version"], { cwd: temporary, env: environment, stdio: ["ignore", "pipe", "pipe"], encoding: "utf8", timeout: 10_000, maxBuffer: 1024 * 1024 }).match(/^(\d+\.\d+\.\d+)\b/m)?.[1];
|
|
1578
|
+
if (!version || olderPackageVersion(version, "1.0.119")) throw new Error("Claude Code predates HTTP credential-helper support.");
|
|
1579
|
+
const temporaryConfig = path.join(temporary, ".claude.json");
|
|
1580
|
+
// Validate through Claude without letting its incidental settings writes replace ours.
|
|
1581
|
+
fs.writeFileSync(temporaryConfig, editClaudeMcp(config.original, undefined), { mode: 0o600 });
|
|
1582
|
+
execFileSync(claude, ["mcp", "add-json", "shifu", JSON.stringify(entry), "--scope", "user"], { cwd: temporary, env: environment, stdio: ["ignore", "pipe", "pipe"], timeout: 20_000, maxBuffer: 1024 * 1024 });
|
|
1583
|
+
const accepted = JSON.parse(fs.readFileSync(temporaryConfig, "utf8")).mcpServers?.shifu;
|
|
1584
|
+
if (accepted?.type !== entry.type || accepted.url !== mcpUrl || accepted.headersHelper !== helper || accepted.timeout !== entry.timeout) throw new Error("Claude Code did not accept the expected MCP credential helper.");
|
|
1585
|
+
return { ...config, candidate: editClaudeMcp(config.original, entry) };
|
|
1586
|
+
} catch (error) {
|
|
1587
|
+
if (error?.code === "ENOENT") throw new Error("The claude command is unavailable. Install Claude Code before MCP setup.");
|
|
1588
|
+
// Native diagnostics can include settings and credentials from other servers.
|
|
1589
|
+
throw new Error("Claude Code could not validate its HTTP MCP credential helper. Update Claude Code or fix its existing config; nothing was changed.");
|
|
1590
|
+
} finally {
|
|
1591
|
+
fs.rmSync(temporary, { recursive: true, force: true });
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
function removeClaudeMcpConfig(expectedHelper) {
|
|
1596
|
+
const config = claudeMcpConfig();
|
|
1597
|
+
if (config.value.mcpServers?.shifu?.headersHelper !== expectedHelper) return;
|
|
1598
|
+
if (!unchangedConfig(config)) throw new Error("Claude Code configuration changed during disconnect. Retry to preserve those changes.");
|
|
1599
|
+
writePrivateText(config.file, editClaudeMcp(config.original, undefined));
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
function unchangedMcpConfig(prepared) {
|
|
1603
|
+
return (prepared.configs || [prepared]).every(unchangedConfig);
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1545
1606
|
async function setupMcp(harness) {
|
|
1546
|
-
|
|
1607
|
+
requireHarness(harness);
|
|
1547
1608
|
if (process.platform !== "darwin" && process.platform !== "linux") throw new Error("Prompt-driven MCP credential setup currently supports local macOS and Linux.");
|
|
1548
|
-
const helper = harness === "
|
|
1609
|
+
const helper = harness === "opencode" ? undefined : helperCommand(harness);
|
|
1549
1610
|
if (fs.existsSync(configPath(harness)) && !fs.lstatSync(configPath(harness)).isFile()) {
|
|
1550
1611
|
throw new Error(`The saved ${HARNESS_NAMES[harness]} connection must be a regular private file, not a link.`);
|
|
1551
1612
|
}
|
|
@@ -1562,8 +1623,8 @@ async function setupMcp(harness) {
|
|
|
1562
1623
|
if (!TOKEN_PATTERN.test(config.token)) throw new Error(`The saved ${HARNESS_NAMES[harness]} connection key is invalid. Run connect again.`);
|
|
1563
1624
|
await validateConnection(config.apiUrl, config.token, harness);
|
|
1564
1625
|
const tools = await verifyMcp(mcpUrl, config.token);
|
|
1565
|
-
const prepared = harness === "codex" ? prepareCodexMcpConfig(mcpUrl, helper) : prepareOpenCodeMcpConfig(mcpUrl);
|
|
1566
|
-
if (
|
|
1626
|
+
const prepared = harness === "codex" ? prepareCodexMcpConfig(mcpUrl, helper) : harness === "opencode" ? prepareOpenCodeMcpConfig(mcpUrl) : prepareClaudeMcpConfig(mcpUrl, helper);
|
|
1627
|
+
if (!unchangedMcpConfig(prepared)) {
|
|
1567
1628
|
throw new Error(`${HARNESS_NAMES[harness]} configuration changed during setup. Run mcp-setup again to preserve those changes.`);
|
|
1568
1629
|
}
|
|
1569
1630
|
fs.chmodSync(configPath(harness), 0o600);
|
|
@@ -1573,12 +1634,12 @@ async function setupMcp(harness) {
|
|
|
1573
1634
|
throw new Error("The shifu-context skill is missing. Run mcp-setup from the package or branch checkout to install it.");
|
|
1574
1635
|
}
|
|
1575
1636
|
if (fs.readFileSync(configPath(harness), "utf8") !== originalConnection) throw new Error("The saved Shifu connection changed during setup. Run mcp-setup again to preserve those changes.");
|
|
1576
|
-
if (
|
|
1637
|
+
if (!unchangedMcpConfig(prepared)) throw new Error(`${HARNESS_NAMES[harness]} configuration changed during setup. Run mcp-setup again to preserve those changes.`);
|
|
1577
1638
|
if (harness === "opencode") writePrivateText(openCodeTokenPath(), config.token);
|
|
1578
1639
|
writePrivateText(prepared.file, prepared.candidate);
|
|
1579
|
-
writeJSON(configPath(harness), { ...config, mcpUrl });
|
|
1640
|
+
writeJSON(configPath(harness), { ...config, mcpUrl, ...(harness === "claude_code" ? { mcpHeadersHelper: helper } : {}) });
|
|
1580
1641
|
console.error(`Verified Shifu MCP tools: ${tools.join(", ")}. ${HARNESS_NAMES[harness]} configuration and usage instructions are installed.`);
|
|
1581
|
-
console.error(`The existing connection key is read ${harness === "
|
|
1642
|
+
console.error(`The existing connection key is read ${harness === "opencode" ? "from a private credential file" : "by a local credential helper"}; it was not copied into MCP configuration. The sync endpoint and schedule were preserved.`);
|
|
1582
1643
|
console.error(`Reload ${HARNESS_NAMES[harness]} or start a new chat to load the MCP tools and shifu-context skill. This setup check does not prove the current chat has loaded them.`);
|
|
1583
1644
|
}
|
|
1584
1645
|
|
|
@@ -1586,6 +1647,7 @@ function disconnect(harness) {
|
|
|
1586
1647
|
const config = readConfig(harness);
|
|
1587
1648
|
if (fs.existsSync(lockPath(harness))) throw new Error("A Shifu sync is running. Try disconnecting again after it finishes.");
|
|
1588
1649
|
if (harness === "opencode" && (config.mcpUrl || fs.existsSync(openCodeTokenPath()))) removeOpenCodeMcpConfig();
|
|
1650
|
+
if (harness === "claude_code" && config.mcpUrl) removeClaudeMcpConfig(config.mcpHeadersHelper || helperCommand("claude_code"));
|
|
1589
1651
|
removeSchedule(harness);
|
|
1590
1652
|
for (const file of [configPath(harness), statePath(harness), pendingPath(harness)]) {
|
|
1591
1653
|
try { fs.unlinkSync(file); } catch (error) { if (error?.code !== "ENOENT") throw error; }
|
package/package.json
CHANGED
|
@@ -13,9 +13,9 @@ When asked to connect Shifu context, run:
|
|
|
13
13
|
npx --yes @useshifu/coding-harness mcp-setup --harness codex
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
For a branch deployment, append `--mcp-url` with the user's supplied HTTPS endpoint ending in `/mcp`. Keep the existing sync API endpoint. The CLI reuses this harness's saved key, checks the authenticated tools, and preserves existing settings. Codex
|
|
16
|
+
For a branch deployment, append `--mcp-url` with the user's supplied HTTPS endpoint ending in `/mcp`. Keep the existing sync API endpoint. The CLI reuses this harness's saved key, checks the authenticated tools, and preserves existing settings. Codex and Claude Code use local credential helpers; OpenCode uses its native `{file:...}` substitution pointing to a private credential file. The key is never placed literally in MCP configuration. If no connection exists, the CLI asks for this harness's key in the terminal. Never request or print the key in chat or place it in command arguments. When input is unavailable, have the user run the command in an interactive terminal. Do not substitute plaintext credentials for an unsupported credential mechanism.
|
|
17
17
|
|
|
18
|
-
Reload the harness or start a new chat after setup. Check that the tools are actually available before claiming this chat can use them. Setup supports local macOS/Linux connections; remote execution environments cannot read this device's credential helper or file. OpenCode requires the v1 config schema (verified with 1.18.30);
|
|
18
|
+
Reload the harness or start a new chat after setup. Check that the tools are actually available before claiming this chat can use them. Setup supports local macOS/Linux connections; remote execution environments cannot read this device's credential helper or file. OpenCode requires the v1 config schema (verified with 1.18.30). Claude Code requires HTTP MCP with `headersHelper` (verified with 2.1.295); setup validates it through the installed CLI. If Claude Code is outdated, help the user update it using official installation instructions before retrying. The connection uses user scope and honors `CLAUDE_CONFIG_DIR`. Restart Claude Code and start a new session, or use `/mcp` to reconnect after rotating the saved key. Unsupported versions or config schemas fail before MCP settings change.
|
|
19
19
|
|
|
20
20
|
## Consult context
|
|
21
21
|
|