@khalilgharbaoui/opencode-claude-code-plugin 0.48.0 → 0.50.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/dist/index.d.ts +36 -1
- package/dist/index.js +366 -194
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +18 -4
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/claude-code-language-model.ts
|
|
2
|
-
import { join as
|
|
2
|
+
import { join as join13 } from "path";
|
|
3
3
|
|
|
4
4
|
// src/ids.ts
|
|
5
5
|
import { randomInt } from "crypto";
|
|
@@ -991,7 +991,10 @@ function formatUnattendedReplayNote(dropped) {
|
|
|
991
991
|
var UNRECOGNIZED_MODEL_STDERR_MARKER = "[claude-code:unrecognized_model]";
|
|
992
992
|
var MODEL_CLI_FLOORS = {
|
|
993
993
|
// "Added Claude Sonnet 5.5 (`claude-sonnet-5-5`)" (CHANGELOG, 2.1.284).
|
|
994
|
-
"claude-sonnet-5-5": "2.1.284"
|
|
994
|
+
"claude-sonnet-5-5": "2.1.284",
|
|
995
|
+
// "Added Claude Haiku 5.5 (`claude-haiku-5-5`), now the default Haiku model
|
|
996
|
+
// on the Anthropic API" (CHANGELOG, 2.1.293). Absent from 2.1.288's catalog.
|
|
997
|
+
"claude-haiku-5-5": "2.1.293"
|
|
995
998
|
};
|
|
996
999
|
function parseUnrecognizedModel(stderr) {
|
|
997
1000
|
const at = stderr.indexOf(UNRECOGNIZED_MODEL_STDERR_MARKER);
|
|
@@ -1885,8 +1888,8 @@ function cliSupportsInteractiveBypass(v) {
|
|
|
1885
1888
|
if (!v) return false;
|
|
1886
1889
|
return gte(v, { major: 2, minor: 1, patch: 263 });
|
|
1887
1890
|
}
|
|
1888
|
-
var INTERACTIVE_MEASURED_CLI = "2.1.
|
|
1889
|
-
var INTERACTIVE_MEASURED = { major: 2, minor: 1, patch:
|
|
1891
|
+
var INTERACTIVE_MEASURED_CLI = "2.1.293";
|
|
1892
|
+
var INTERACTIVE_MEASURED = { major: 2, minor: 1, patch: 293 };
|
|
1890
1893
|
function isUnmeasuredInteractiveCli(v) {
|
|
1891
1894
|
if (!v) return false;
|
|
1892
1895
|
return gte(v, INTERACTIVE_MEASURED) && !(v.major === INTERACTIVE_MEASURED.major && v.minor === INTERACTIVE_MEASURED.minor && v.patch === INTERACTIVE_MEASURED.patch);
|
|
@@ -3511,33 +3514,97 @@ function clearCompression(sessionKey2) {
|
|
|
3511
3514
|
}
|
|
3512
3515
|
|
|
3513
3516
|
// src/mcp-bridge.ts
|
|
3514
|
-
import * as
|
|
3515
|
-
import * as
|
|
3516
|
-
import * as
|
|
3517
|
+
import * as fs5 from "fs";
|
|
3518
|
+
import * as path6 from "path";
|
|
3519
|
+
import * as os3 from "os";
|
|
3517
3520
|
import * as crypto2 from "crypto";
|
|
3518
3521
|
import {
|
|
3519
3522
|
parse as parseJsonc,
|
|
3520
3523
|
printParseErrorCode
|
|
3521
3524
|
} from "jsonc-parser";
|
|
3525
|
+
|
|
3526
|
+
// src/mcp-auth.ts
|
|
3527
|
+
import * as fs4 from "fs";
|
|
3528
|
+
import * as os2 from "os";
|
|
3529
|
+
import * as path5 from "path";
|
|
3530
|
+
var EXPIRY_SKEW_SECONDS = 30;
|
|
3531
|
+
function opencodeStateDir() {
|
|
3532
|
+
const dataHome = process.env.XDG_DATA_HOME || path5.join(os2.homedir(), ".local", "share");
|
|
3533
|
+
return path5.join(dataHome, "opencode");
|
|
3534
|
+
}
|
|
3535
|
+
function readMcpAuthStore(stateDir) {
|
|
3536
|
+
const file = path5.join(stateDir, "mcp-auth.json");
|
|
3537
|
+
try {
|
|
3538
|
+
const stat = fs4.lstatSync(file);
|
|
3539
|
+
if (!stat.isFile()) {
|
|
3540
|
+
log.debug("ignoring opencode MCP auth store that is not a regular file", { path: file });
|
|
3541
|
+
return {};
|
|
3542
|
+
}
|
|
3543
|
+
if (typeof stat.uid === "number" && typeof process.getuid === "function" && stat.uid !== process.getuid()) {
|
|
3544
|
+
log.debug("ignoring opencode MCP auth store owned by another user", { path: file });
|
|
3545
|
+
return {};
|
|
3546
|
+
}
|
|
3547
|
+
const parsed = JSON.parse(fs4.readFileSync(file, "utf8"));
|
|
3548
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3549
|
+
log.debug("opencode MCP auth store has an unexpected shape", { path: file });
|
|
3550
|
+
return {};
|
|
3551
|
+
}
|
|
3552
|
+
return parsed;
|
|
3553
|
+
} catch {
|
|
3554
|
+
return {};
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
function selectBearerToken(store, serverName, serverUrl, now = Date.now()) {
|
|
3558
|
+
const entry = Object.prototype.hasOwnProperty.call(store, serverName) ? store[serverName] : void 0;
|
|
3559
|
+
if (!entry || typeof entry !== "object") return { token: null, refusal: "no-entry" };
|
|
3560
|
+
if (!entry.serverUrl || entry.serverUrl !== serverUrl) {
|
|
3561
|
+
return { token: null, refusal: "url-mismatch" };
|
|
3562
|
+
}
|
|
3563
|
+
const accessToken = entry.tokens?.accessToken;
|
|
3564
|
+
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
3565
|
+
return { token: null, refusal: "no-token" };
|
|
3566
|
+
}
|
|
3567
|
+
const expiresAt = entry.tokens?.expiresAt;
|
|
3568
|
+
if (typeof expiresAt === "number" && expiresAt - now / 1e3 < EXPIRY_SKEW_SECONDS) {
|
|
3569
|
+
return { token: null, refusal: "expired" };
|
|
3570
|
+
}
|
|
3571
|
+
return { token: accessToken, expiresAt: typeof expiresAt === "number" ? expiresAt : void 0 };
|
|
3572
|
+
}
|
|
3573
|
+
function buildFreshnessKey(expiresAt) {
|
|
3574
|
+
return expiresAt === void 0 ? "no-expiry" : String(expiresAt);
|
|
3575
|
+
}
|
|
3576
|
+
function hasAuthorizationHeader(headers) {
|
|
3577
|
+
return Object.keys(headers).some((key) => key.toLowerCase() === "authorization");
|
|
3578
|
+
}
|
|
3579
|
+
function injectBearerHeaders(server2, accessToken) {
|
|
3580
|
+
const existing = server2.headers && typeof server2.headers === "object" && !Array.isArray(server2.headers) ? server2.headers : {};
|
|
3581
|
+
if (hasAuthorizationHeader(existing)) return { ...server2 };
|
|
3582
|
+
return {
|
|
3583
|
+
...server2,
|
|
3584
|
+
headers: { ...existing, Authorization: `Bearer ${accessToken}` }
|
|
3585
|
+
};
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
// src/mcp-bridge.ts
|
|
3522
3589
|
var FILE_NAMES = ["opencode.jsonc", "opencode.json", "config.json"];
|
|
3523
3590
|
var PROJECT_FILE_NAMES = ["opencode.json", "opencode.jsonc"];
|
|
3524
3591
|
function fileExists(p) {
|
|
3525
3592
|
try {
|
|
3526
|
-
return
|
|
3593
|
+
return fs5.statSync(p).isFile();
|
|
3527
3594
|
} catch {
|
|
3528
3595
|
return false;
|
|
3529
3596
|
}
|
|
3530
3597
|
}
|
|
3531
3598
|
function dirExists(p) {
|
|
3532
3599
|
try {
|
|
3533
|
-
return
|
|
3600
|
+
return fs5.statSync(p).isDirectory();
|
|
3534
3601
|
} catch {
|
|
3535
3602
|
return false;
|
|
3536
3603
|
}
|
|
3537
3604
|
}
|
|
3538
3605
|
function readAndParse(file) {
|
|
3539
3606
|
try {
|
|
3540
|
-
const raw =
|
|
3607
|
+
const raw = fs5.readFileSync(file, "utf8");
|
|
3541
3608
|
const errors = [];
|
|
3542
3609
|
const parsed = parseJsonc(raw, errors, { allowTrailingComma: true });
|
|
3543
3610
|
if (errors.length > 0) {
|
|
@@ -3573,14 +3640,14 @@ function deepMerge(target, source) {
|
|
|
3573
3640
|
}
|
|
3574
3641
|
function walkUp(opts) {
|
|
3575
3642
|
const out = [];
|
|
3576
|
-
let current =
|
|
3643
|
+
let current = path6.resolve(opts.start);
|
|
3577
3644
|
while (true) {
|
|
3578
3645
|
for (const target of opts.targets) {
|
|
3579
|
-
const candidate =
|
|
3646
|
+
const candidate = path6.join(current, target);
|
|
3580
3647
|
if (opts.predicate(candidate)) out.push(candidate);
|
|
3581
3648
|
}
|
|
3582
|
-
if (opts.stop && current ===
|
|
3583
|
-
const parent =
|
|
3649
|
+
if (opts.stop && current === path6.resolve(opts.stop)) break;
|
|
3650
|
+
const parent = path6.dirname(current);
|
|
3584
3651
|
if (parent === current) break;
|
|
3585
3652
|
current = parent;
|
|
3586
3653
|
}
|
|
@@ -3588,28 +3655,28 @@ function walkUp(opts) {
|
|
|
3588
3655
|
}
|
|
3589
3656
|
function detectWorktree(cwd) {
|
|
3590
3657
|
const override = process.env.OPENCODE_WORKTREE;
|
|
3591
|
-
if (override) return
|
|
3592
|
-
let current =
|
|
3658
|
+
if (override) return path6.resolve(override);
|
|
3659
|
+
let current = path6.resolve(cwd);
|
|
3593
3660
|
while (true) {
|
|
3594
|
-
const gitPath =
|
|
3661
|
+
const gitPath = path6.join(current, ".git");
|
|
3595
3662
|
try {
|
|
3596
|
-
if (
|
|
3663
|
+
if (fs5.existsSync(gitPath)) return current;
|
|
3597
3664
|
} catch {
|
|
3598
3665
|
}
|
|
3599
|
-
const parent =
|
|
3666
|
+
const parent = path6.dirname(current);
|
|
3600
3667
|
if (parent === current) return void 0;
|
|
3601
3668
|
current = parent;
|
|
3602
3669
|
}
|
|
3603
3670
|
}
|
|
3604
3671
|
function globalConfigDir() {
|
|
3605
|
-
const xdg = process.env.XDG_CONFIG_HOME ??
|
|
3606
|
-
return
|
|
3672
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? path6.join(os3.homedir(), ".config");
|
|
3673
|
+
return path6.join(xdg, "opencode");
|
|
3607
3674
|
}
|
|
3608
3675
|
function loadGlobalConfig(hostApi = "v1") {
|
|
3609
3676
|
const dir = globalConfigDir();
|
|
3610
3677
|
let merged = {};
|
|
3611
3678
|
for (const name of FILE_NAMES.slice().reverse()) {
|
|
3612
|
-
const file =
|
|
3679
|
+
const file = path6.join(dir, name);
|
|
3613
3680
|
if (!fileExists(file)) continue;
|
|
3614
3681
|
const parsed = readAndParse(file);
|
|
3615
3682
|
if (parsed) merged = mergeConfigLayer(merged, parsed, hostApi);
|
|
@@ -3619,7 +3686,7 @@ function loadGlobalConfig(hostApi = "v1") {
|
|
|
3619
3686
|
function loadProjectFilesInDir(dir, hostApi = "v1") {
|
|
3620
3687
|
let merged = {};
|
|
3621
3688
|
for (const name of PROJECT_FILE_NAMES) {
|
|
3622
|
-
const file =
|
|
3689
|
+
const file = path6.join(dir, name);
|
|
3623
3690
|
if (!fileExists(file)) continue;
|
|
3624
3691
|
const parsed = readAndParse(file);
|
|
3625
3692
|
if (parsed) merged = mergeConfigLayer(merged, parsed, hostApi);
|
|
@@ -3630,7 +3697,7 @@ function dotOpencodeDirs(cwd, worktree) {
|
|
|
3630
3697
|
const dirs = [];
|
|
3631
3698
|
const seen = /* @__PURE__ */ new Set();
|
|
3632
3699
|
const push = (p) => {
|
|
3633
|
-
const abs =
|
|
3700
|
+
const abs = path6.resolve(p);
|
|
3634
3701
|
if (!seen.has(abs) && dirExists(abs)) {
|
|
3635
3702
|
seen.add(abs);
|
|
3636
3703
|
dirs.push(abs);
|
|
@@ -3644,9 +3711,9 @@ function dotOpencodeDirs(cwd, worktree) {
|
|
|
3644
3711
|
})) {
|
|
3645
3712
|
push(dir);
|
|
3646
3713
|
}
|
|
3647
|
-
const home =
|
|
3714
|
+
const home = os3.homedir();
|
|
3648
3715
|
if (home) {
|
|
3649
|
-
const homeDot =
|
|
3716
|
+
const homeDot = path6.join(home, ".opencode");
|
|
3650
3717
|
if (dirExists(homeDot)) push(homeDot);
|
|
3651
3718
|
}
|
|
3652
3719
|
const envDir = process.env.OPENCODE_CONFIG_DIR;
|
|
@@ -3664,7 +3731,33 @@ function substituteEnvPlaceholders(source) {
|
|
|
3664
3731
|
}
|
|
3665
3732
|
return out;
|
|
3666
3733
|
}
|
|
3667
|
-
|
|
3734
|
+
var warnedExpiredBearer = /* @__PURE__ */ new Set();
|
|
3735
|
+
function withBearerToken(name, spec, url, auth) {
|
|
3736
|
+
if (!auth) return spec;
|
|
3737
|
+
const lookup = selectBearerToken(auth.store, name, url);
|
|
3738
|
+
if (lookup.token === null) {
|
|
3739
|
+
if (lookup.refusal === "expired" && !warnedExpiredBearer.has(name)) {
|
|
3740
|
+
warnedExpiredBearer.add(name);
|
|
3741
|
+
log.warn(
|
|
3742
|
+
"opencode's stored OAuth token for this MCP server has expired, so the bridged server carries no credential; re-authenticate it in opencode",
|
|
3743
|
+
{ server: name }
|
|
3744
|
+
);
|
|
3745
|
+
} else {
|
|
3746
|
+
log.debug("no opencode OAuth token for bridged MCP server", {
|
|
3747
|
+
server: name,
|
|
3748
|
+
reason: lookup.refusal
|
|
3749
|
+
});
|
|
3750
|
+
}
|
|
3751
|
+
return spec;
|
|
3752
|
+
}
|
|
3753
|
+
log.info("bridged MCP server carries opencode's stored OAuth token", { server: name });
|
|
3754
|
+
return injectBearerHeaders(spec, lookup.token);
|
|
3755
|
+
}
|
|
3756
|
+
function bearerAuthContext(options) {
|
|
3757
|
+
if (!options?.oauthTokens) return void 0;
|
|
3758
|
+
return { store: readMcpAuthStore(options.stateDir ?? opencodeStateDir()) };
|
|
3759
|
+
}
|
|
3760
|
+
function translateServer(name, spec, auth) {
|
|
3668
3761
|
if (spec.enabled === false) return null;
|
|
3669
3762
|
const type = spec.type;
|
|
3670
3763
|
if (type === "local") {
|
|
@@ -3690,7 +3783,7 @@ function translateServer(name, spec) {
|
|
|
3690
3783
|
log.warn("skipping remote MCP server with no url", { name });
|
|
3691
3784
|
return null;
|
|
3692
3785
|
}
|
|
3693
|
-
|
|
3786
|
+
let out = {
|
|
3694
3787
|
type: "http",
|
|
3695
3788
|
url: spec.url
|
|
3696
3789
|
};
|
|
@@ -3699,7 +3792,7 @@ function translateServer(name, spec) {
|
|
|
3699
3792
|
spec.headers
|
|
3700
3793
|
);
|
|
3701
3794
|
}
|
|
3702
|
-
return out;
|
|
3795
|
+
return withBearerToken(name, out, spec.url, auth);
|
|
3703
3796
|
}
|
|
3704
3797
|
log.warn("skipping MCP server with unknown type", {
|
|
3705
3798
|
name,
|
|
@@ -3750,18 +3843,19 @@ function mergeMcp(target, source) {
|
|
|
3750
3843
|
return out;
|
|
3751
3844
|
}
|
|
3752
3845
|
var MCP_PENDING_STATUS = "pending";
|
|
3753
|
-
function bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers, hostApi = "v1") {
|
|
3846
|
+
function bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers, hostApi = "v1", auth) {
|
|
3754
3847
|
const {
|
|
3755
3848
|
servers: merged,
|
|
3756
3849
|
enabledServerNames: allEnabledServerNames,
|
|
3757
3850
|
hash
|
|
3758
|
-
} = mergeOpencodeMcp(cwd, runtimeStatus, hostApi);
|
|
3851
|
+
} = mergeOpencodeMcp(cwd, runtimeStatus, hostApi, auth);
|
|
3852
|
+
const authContext = bearerAuthContext(auth);
|
|
3759
3853
|
const servers = {};
|
|
3760
3854
|
const bridgedServerNames = [];
|
|
3761
3855
|
for (const [name, spec] of Object.entries(merged)) {
|
|
3762
3856
|
if (!spec || typeof spec !== "object") continue;
|
|
3763
3857
|
if (excludeServers?.has(name)) continue;
|
|
3764
|
-
const translated = translateServer(name, spec);
|
|
3858
|
+
const translated = translateServer(name, spec, authContext);
|
|
3765
3859
|
if (translated) {
|
|
3766
3860
|
servers[name] = translated;
|
|
3767
3861
|
bridgedServerNames.push(name);
|
|
@@ -3793,7 +3887,7 @@ function opencodeConfigLayers(cwd, hostApi = "v1") {
|
|
|
3793
3887
|
const projectDirs = [];
|
|
3794
3888
|
const seenProjectDirs = /* @__PURE__ */ new Set();
|
|
3795
3889
|
for (const f of projectFiles) {
|
|
3796
|
-
const d =
|
|
3890
|
+
const d = path6.dirname(f);
|
|
3797
3891
|
if (!seenProjectDirs.has(d)) {
|
|
3798
3892
|
seenProjectDirs.add(d);
|
|
3799
3893
|
projectDirs.push(d);
|
|
@@ -3819,7 +3913,7 @@ function loadMergedOpencodeConfig(cwd, hostApi = "v1") {
|
|
|
3819
3913
|
for (const layer of opencodeConfigLayers(cwd, hostApi)) merged = mergeConfigLayer(merged, layer, hostApi);
|
|
3820
3914
|
return merged;
|
|
3821
3915
|
}
|
|
3822
|
-
function mergeOpencodeMcp(cwd, runtimeStatus, hostApi = "v1") {
|
|
3916
|
+
function mergeOpencodeMcp(cwd, runtimeStatus, hostApi = "v1", auth) {
|
|
3823
3917
|
let merged = {};
|
|
3824
3918
|
for (const layer of opencodeConfigLayers(cwd, hostApi)) {
|
|
3825
3919
|
const servers = extractMcpBlock(layer);
|
|
@@ -3843,7 +3937,19 @@ function mergeOpencodeMcp(cwd, runtimeStatus, hostApi = "v1") {
|
|
|
3843
3937
|
enabledServerNames.push(name);
|
|
3844
3938
|
}
|
|
3845
3939
|
const mergedBody = JSON.stringify({ mcpServers: merged }, null, 2);
|
|
3846
|
-
const
|
|
3940
|
+
const hasher = crypto2.createHash("sha256").update(mergedBody);
|
|
3941
|
+
const authContext = bearerAuthContext(auth);
|
|
3942
|
+
if (authContext) {
|
|
3943
|
+
for (const [name, spec] of Object.entries(merged)) {
|
|
3944
|
+
if (!spec || typeof spec !== "object") continue;
|
|
3945
|
+
const server2 = spec;
|
|
3946
|
+
if (server2.type !== "remote" || typeof server2.url !== "string") continue;
|
|
3947
|
+
const lookup = selectBearerToken(authContext.store, name, server2.url);
|
|
3948
|
+
if (lookup.token === null) continue;
|
|
3949
|
+
hasher.update(`\0bearer:${name}:${buildFreshnessKey(lookup.expiresAt)}`);
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
const hash = hasher.digest("hex").slice(0, 12);
|
|
3847
3953
|
return { servers: merged, enabledServerNames, hash };
|
|
3848
3954
|
}
|
|
3849
3955
|
function finishBridge(input) {
|
|
@@ -3860,13 +3966,13 @@ function finishBridge(input) {
|
|
|
3860
3966
|
}
|
|
3861
3967
|
const body = JSON.stringify({ mcpServers: servers }, null, 2);
|
|
3862
3968
|
const bodyDigest = crypto2.createHash("sha256").update(body).digest("hex").slice(0, 12);
|
|
3863
|
-
const outPath =
|
|
3969
|
+
const outPath = path6.join(
|
|
3864
3970
|
pluginTmpDir(),
|
|
3865
3971
|
`mcp-${bodyDigest}.json`
|
|
3866
3972
|
);
|
|
3867
3973
|
try {
|
|
3868
3974
|
if (!fileExists(outPath)) {
|
|
3869
|
-
|
|
3975
|
+
fs5.writeFileSync(outPath, body, { encoding: "utf8", mode: 384 });
|
|
3870
3976
|
}
|
|
3871
3977
|
} catch (e) {
|
|
3872
3978
|
log.warn("failed to write bridged MCP config", {
|
|
@@ -4533,17 +4639,17 @@ function tailIsReplyOnly(history, from) {
|
|
|
4533
4639
|
}
|
|
4534
4640
|
|
|
4535
4641
|
// src/session-resume-store.ts
|
|
4536
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as
|
|
4537
|
-
import { homedir as
|
|
4538
|
-
import { join as
|
|
4642
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
4643
|
+
import { homedir as homedir5 } from "os";
|
|
4644
|
+
import { join as join6 } from "path";
|
|
4539
4645
|
var MAX_RESUME_RECORDS = 256;
|
|
4540
4646
|
var RESUME_RECORD_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
4541
4647
|
var records = null;
|
|
4542
4648
|
var pathOverride = null;
|
|
4543
4649
|
function resumeStorePath() {
|
|
4544
4650
|
if (pathOverride) return pathOverride;
|
|
4545
|
-
const stateRoot = process.env.XDG_STATE_HOME ||
|
|
4546
|
-
return
|
|
4651
|
+
const stateRoot = process.env.XDG_STATE_HOME || join6(homedir5(), ".local", "state");
|
|
4652
|
+
return join6(stateRoot, "opencode-claude-code-plugin", "claude-sessions.json");
|
|
4547
4653
|
}
|
|
4548
4654
|
function isDigest(value) {
|
|
4549
4655
|
const entry = value;
|
|
@@ -4557,7 +4663,7 @@ function load() {
|
|
|
4557
4663
|
if (records) return records;
|
|
4558
4664
|
records = /* @__PURE__ */ new Map();
|
|
4559
4665
|
try {
|
|
4560
|
-
const parsed = JSON.parse(
|
|
4666
|
+
const parsed = JSON.parse(readFileSync3(resumeStorePath(), "utf8"));
|
|
4561
4667
|
const entries = parsed && typeof parsed === "object" ? parsed.sessions : null;
|
|
4562
4668
|
if (entries && typeof entries === "object") {
|
|
4563
4669
|
const cutoff = Date.now() - RESUME_RECORD_MAX_AGE_MS;
|
|
@@ -4572,7 +4678,7 @@ function save(update) {
|
|
|
4572
4678
|
const file = resumeStorePath();
|
|
4573
4679
|
const merged = /* @__PURE__ */ new Map();
|
|
4574
4680
|
try {
|
|
4575
|
-
const onDisk = JSON.parse(
|
|
4681
|
+
const onDisk = JSON.parse(readFileSync3(file, "utf8"))?.sessions;
|
|
4576
4682
|
if (onDisk && typeof onDisk === "object") {
|
|
4577
4683
|
for (const [key, record] of Object.entries(onDisk)) {
|
|
4578
4684
|
if (isRecord2(record)) merged.set(key, record);
|
|
@@ -4585,7 +4691,7 @@ function save(update) {
|
|
|
4585
4691
|
const kept = [...merged.entries()].filter(([, record]) => record.updatedAt >= cutoff).sort((a, b) => a[1].updatedAt - b[1].updatedAt).slice(-MAX_RESUME_RECORDS);
|
|
4586
4692
|
records = new Map(kept);
|
|
4587
4693
|
try {
|
|
4588
|
-
mkdirSync3(
|
|
4694
|
+
mkdirSync3(join6(file, ".."), { recursive: true, mode: 448 });
|
|
4589
4695
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
4590
4696
|
writeFileSync3(tmp, JSON.stringify({ version: 1, sessions: Object.fromEntries(kept) }), {
|
|
4591
4697
|
mode: 384
|
|
@@ -6034,7 +6140,7 @@ async function handleBtwCommand(client, input, options = {}) {
|
|
|
6034
6140
|
// src/diagnostic-bundle.ts
|
|
6035
6141
|
import { createHash as createHash4, randomBytes as randomBytes2 } from "crypto";
|
|
6036
6142
|
import { openSync, readSync, closeSync, fstatSync, realpathSync } from "fs";
|
|
6037
|
-
import { homedir as
|
|
6143
|
+
import { homedir as homedir6 } from "os";
|
|
6038
6144
|
|
|
6039
6145
|
// src/log-messages.ts
|
|
6040
6146
|
var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
|
|
@@ -6130,6 +6236,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
|
|
|
6130
6236
|
"no proxyTools entry was recognised; nothing will be proxied this turn",
|
|
6131
6237
|
"no stdout after envelope write; respawning claude process to resume conversation",
|
|
6132
6238
|
"opencode 2 event subscription ended; deleted sessions keep their claude process until idle eviction",
|
|
6239
|
+
"opencode's stored OAuth token for this MCP server has expired, so the bridged server carries no credential; re-authenticate it in opencode",
|
|
6133
6240
|
"pending proxy call arrived after stream close; rejecting",
|
|
6134
6241
|
'permissionMode "plan" is enforced: claude cannot edit files or run commands, and --dangerously-skip-permissions is deliberately not passed so it stays that way. Headless Claude Code is not offered an ExitPlanMode tool, so nothing releases plan mode mid-session; approving a plan in chat does not unlock writes. Leaving plan mode means changing the config and restarting opencode.',
|
|
6135
6242
|
`permissionPreset "read-only": this claude CLI has no --permission-prompts (2.1.263+), so permission requests are denied by the plugin's own can_use_tool handler instead of by the CLI. Same outcome, one layer fewer.`,
|
|
@@ -6395,7 +6502,7 @@ function createIdHasher(salt = randomBytes2(16).toString("hex")) {
|
|
|
6395
6502
|
return (raw) => createHash4("sha256").update(salt).update("\0").update(raw).digest("hex").slice(0, 8);
|
|
6396
6503
|
}
|
|
6397
6504
|
function createRedactionContext(options = {}) {
|
|
6398
|
-
const home = options.home ??
|
|
6505
|
+
const home = options.home ?? homedir6();
|
|
6399
6506
|
const homes = /* @__PURE__ */ new Set();
|
|
6400
6507
|
if (home) homes.add(home);
|
|
6401
6508
|
if (home) {
|
|
@@ -6580,8 +6687,8 @@ function redactLogLine(raw, context) {
|
|
|
6580
6687
|
if (!parsed.data || Object.keys(parsed.data).length === 0) return head;
|
|
6581
6688
|
return `${head} ${JSON.stringify(sanitizeLogData(parsed.data, context))}`;
|
|
6582
6689
|
}
|
|
6583
|
-
function readLogTail(
|
|
6584
|
-
const fd = openSync(
|
|
6690
|
+
function readLogTail(path15, maxBytes = BUNDLE_TAIL_BYTES) {
|
|
6691
|
+
const fd = openSync(path15, "r");
|
|
6585
6692
|
try {
|
|
6586
6693
|
const size = fstatSync(fd).size;
|
|
6587
6694
|
const length = Math.min(size, maxBytes);
|
|
@@ -6786,14 +6893,14 @@ async function fetchPlanUsage(cliPath, options = {}) {
|
|
|
6786
6893
|
}
|
|
6787
6894
|
|
|
6788
6895
|
// src/stale-build.ts
|
|
6789
|
-
import * as
|
|
6790
|
-
import * as
|
|
6896
|
+
import * as fs7 from "fs";
|
|
6897
|
+
import * as path8 from "path";
|
|
6791
6898
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6792
6899
|
|
|
6793
6900
|
// src/startup-diagnostics.ts
|
|
6794
6901
|
import { execFile as execFile3 } from "child_process";
|
|
6795
|
-
import * as
|
|
6796
|
-
import * as
|
|
6902
|
+
import * as fs6 from "fs";
|
|
6903
|
+
import * as path7 from "path";
|
|
6797
6904
|
import { promisify as promisify2 } from "util";
|
|
6798
6905
|
import { fileURLToPath } from "url";
|
|
6799
6906
|
|
|
@@ -6828,8 +6935,8 @@ var cachedPluginVersion;
|
|
|
6828
6935
|
function pluginVersion() {
|
|
6829
6936
|
if (cachedPluginVersion) return cachedPluginVersion;
|
|
6830
6937
|
try {
|
|
6831
|
-
const here =
|
|
6832
|
-
const raw =
|
|
6938
|
+
const here = path7.dirname(fileURLToPath(import.meta.url));
|
|
6939
|
+
const raw = fs6.readFileSync(path7.join(here, "..", "package.json"), "utf8");
|
|
6833
6940
|
const version = JSON.parse(raw).version;
|
|
6834
6941
|
cachedPluginVersion = typeof version === "string" ? version : "unknown";
|
|
6835
6942
|
} catch {
|
|
@@ -6853,7 +6960,7 @@ var opencodeVersionProbe;
|
|
|
6853
6960
|
function detectOpencodeVersion(execPath = process.execPath) {
|
|
6854
6961
|
if (opencodeVersionProbe) return opencodeVersionProbe;
|
|
6855
6962
|
opencodeVersionProbe = (async () => {
|
|
6856
|
-
if (!
|
|
6963
|
+
if (!path7.basename(execPath).toLowerCase().includes("opencode")) {
|
|
6857
6964
|
log.debug("skipping opencode version probe: execPath is not opencode", { execPath });
|
|
6858
6965
|
return void 0;
|
|
6859
6966
|
}
|
|
@@ -7082,10 +7189,10 @@ function defaultEntryPath() {
|
|
|
7082
7189
|
}
|
|
7083
7190
|
function readOnDiskBuild(entryPath) {
|
|
7084
7191
|
try {
|
|
7085
|
-
const raw =
|
|
7192
|
+
const raw = fs7.readFileSync(path8.join(path8.dirname(entryPath), "..", "package.json"), "utf8");
|
|
7086
7193
|
const version = JSON.parse(raw).version;
|
|
7087
7194
|
if (typeof version !== "string" || version.length === 0) return void 0;
|
|
7088
|
-
const stat =
|
|
7195
|
+
const stat = fs7.statSync(entryPath);
|
|
7089
7196
|
return { version, mtimeMs: stat.mtimeMs, size: stat.size };
|
|
7090
7197
|
} catch {
|
|
7091
7198
|
return void 0;
|
|
@@ -7096,7 +7203,7 @@ function recordLoadedBuild() {
|
|
|
7096
7203
|
let mtimeMs;
|
|
7097
7204
|
let size;
|
|
7098
7205
|
try {
|
|
7099
|
-
const stat =
|
|
7206
|
+
const stat = fs7.statSync(entryPath);
|
|
7100
7207
|
mtimeMs = stat.mtimeMs;
|
|
7101
7208
|
size = stat.size;
|
|
7102
7209
|
} catch {
|
|
@@ -7534,7 +7641,7 @@ Could not build the report: ${message}`;
|
|
|
7534
7641
|
|
|
7535
7642
|
// src/agent-models.ts
|
|
7536
7643
|
import { readFile, readdir } from "fs/promises";
|
|
7537
|
-
import
|
|
7644
|
+
import path9 from "path";
|
|
7538
7645
|
|
|
7539
7646
|
// src/models.ts
|
|
7540
7647
|
var PROVIDER_ID = "claude-code";
|
|
@@ -7593,6 +7700,8 @@ function passthroughModel(id) {
|
|
|
7593
7700
|
};
|
|
7594
7701
|
}
|
|
7595
7702
|
var haikuCost = { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 };
|
|
7703
|
+
var haiku55Cost = { input: 0.1, output: 0.5, cacheRead: 0.01, cacheWrite: 0.125 };
|
|
7704
|
+
var haiku55LongPromptCost = { input: 0.5, output: 2.5, cacheRead: 0.05, cacheWrite: 0.625 };
|
|
7596
7705
|
var sonnetCost = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 };
|
|
7597
7706
|
var sonnet5Cost = { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 };
|
|
7598
7707
|
var opusCost = { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 };
|
|
@@ -7633,6 +7742,9 @@ function toConfigModel(model) {
|
|
|
7633
7742
|
variants: model.variants
|
|
7634
7743
|
};
|
|
7635
7744
|
}
|
|
7745
|
+
var LONG_PROMPT_COSTS = {
|
|
7746
|
+
"claude-haiku-5-5": { aboveTokens: 1e5, cost: haiku55LongPromptCost }
|
|
7747
|
+
};
|
|
7636
7748
|
var defaultModels = {
|
|
7637
7749
|
"claude-haiku-4-5": defineModel({
|
|
7638
7750
|
id: "claude-haiku-4-5",
|
|
@@ -7645,6 +7757,32 @@ var defaultModels = {
|
|
|
7645
7757
|
multiplier: 1,
|
|
7646
7758
|
releaseDate: "2025-10-01"
|
|
7647
7759
|
}),
|
|
7760
|
+
// Haiku 5.5 needs Claude Code 2.1.293+, the release that added it and made
|
|
7761
|
+
// it the default Haiku; an older CLI runs it on fallback limits and says so
|
|
7762
|
+
// on stderr, which `reportUnrecognizedModel` turns into a warning. Unlike
|
|
7763
|
+
// Haiku 4.5 it reasons: the CLI's catalog gives it `effort`, `max_effort`,
|
|
7764
|
+
// `xhigh_effort` and `adaptive_thinking`, and Anthropic's migration note says
|
|
7765
|
+
// manual `budget_tokens` is a 400 and adaptive thinking is on by default. The
|
|
7766
|
+
// plugin never sends `budget_tokens`, so nothing here special-cases it, the
|
|
7767
|
+
// same as Opus 5.5's `rejects_disabled_thinking`. No `-fast` entry: the
|
|
7768
|
+
// catalog gives it no `fast_mode` capability.
|
|
7769
|
+
//
|
|
7770
|
+
// The multiplier is 0.1 because it is exactly a tenth of Haiku 4.5 on input,
|
|
7771
|
+
// output, cache read and cache write alike, and Haiku 4.5's $1/$5 is the 1x
|
|
7772
|
+
// anchor every other entry here is already measured against. Re-anchoring the
|
|
7773
|
+
// whole table on Haiku 5.5 would change every model's displayed number for no
|
|
7774
|
+
// gain in accuracy.
|
|
7775
|
+
"claude-haiku-5-5": defineModel({
|
|
7776
|
+
id: "claude-haiku-5-5",
|
|
7777
|
+
name: "Claude Haiku 5.5",
|
|
7778
|
+
family: "haiku",
|
|
7779
|
+
reasoning: true,
|
|
7780
|
+
context: 1e6,
|
|
7781
|
+
output: 128e3,
|
|
7782
|
+
cost: haiku55Cost,
|
|
7783
|
+
multiplier: 0.1,
|
|
7784
|
+
releaseDate: "2026-10-07"
|
|
7785
|
+
}),
|
|
7648
7786
|
"claude-sonnet-4-5": defineModel({
|
|
7649
7787
|
id: "claude-sonnet-4-5",
|
|
7650
7788
|
name: "Claude Sonnet 4.5",
|
|
@@ -7873,14 +8011,25 @@ function apiCallCostUsd(model, usage) {
|
|
|
7873
8011
|
const base = model.replace(/@.*$/, "").replace(/\[1m\]$/i, "").replace(/-\d{8}$/, "");
|
|
7874
8012
|
const entry = (usage.speed === "fast" ? defaultModels[`${base}${FAST_SUFFIX}`] : void 0) ?? defaultModels[base];
|
|
7875
8013
|
if (!entry) return null;
|
|
7876
|
-
const cost = entry.cost;
|
|
7877
8014
|
const count = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
8015
|
+
const cost = longPromptCost(base, usage, count) ?? entry.cost;
|
|
7878
8016
|
const split = usage.cache_creation;
|
|
7879
8017
|
const write1h = split ? count(split.ephemeral_1h_input_tokens) : 0;
|
|
7880
8018
|
const write5m = split ? count(split.ephemeral_5m_input_tokens) : count(usage.cache_creation_input_tokens);
|
|
7881
8019
|
const perMillion = count(usage.input_tokens) * cost.input + count(usage.output_tokens) * cost.output + count(usage.cache_read_input_tokens) * cost.cache.read + write5m * cost.cache.write + write1h * cost.input * 2;
|
|
7882
8020
|
return perMillion / 1e6 + count(usage.server_tool_use?.web_search_requests) * WEB_SEARCH_USD;
|
|
7883
8021
|
}
|
|
8022
|
+
function longPromptCost(base, usage, count) {
|
|
8023
|
+
const tier = LONG_PROMPT_COSTS[base];
|
|
8024
|
+
if (!tier) return null;
|
|
8025
|
+
const promptTokens = count(usage.input_tokens) + count(usage.cache_read_input_tokens) + count(usage.cache_creation_input_tokens);
|
|
8026
|
+
if (promptTokens <= tier.aboveTokens) return null;
|
|
8027
|
+
return {
|
|
8028
|
+
input: tier.cost.input,
|
|
8029
|
+
output: tier.cost.output,
|
|
8030
|
+
cache: { read: tier.cost.cacheRead, write: tier.cost.cacheWrite }
|
|
8031
|
+
};
|
|
8032
|
+
}
|
|
7884
8033
|
|
|
7885
8034
|
// src/agent-models.ts
|
|
7886
8035
|
var AGENT_DIR_NAMES = ["agents", "agent"];
|
|
@@ -8058,13 +8207,13 @@ async function readAgentMarkdownRecords(directories) {
|
|
|
8058
8207
|
const name = entry.slice(0, -3);
|
|
8059
8208
|
if (records2[name]) continue;
|
|
8060
8209
|
try {
|
|
8061
|
-
const text = await readFile(
|
|
8210
|
+
const text = await readFile(path9.join(directory, entry), "utf8");
|
|
8062
8211
|
records2[name] = parseAgentFrontmatter(text);
|
|
8063
8212
|
const prompt = agentMarkdownBody(text);
|
|
8064
8213
|
if (prompt) records2[name].prompt = prompt;
|
|
8065
8214
|
} catch (err) {
|
|
8066
8215
|
log.debug("failed to read agent markdown", {
|
|
8067
|
-
file:
|
|
8216
|
+
file: path9.join(directory, entry),
|
|
8068
8217
|
error: String(err)
|
|
8069
8218
|
});
|
|
8070
8219
|
}
|
|
@@ -8076,12 +8225,12 @@ function agentDirectories(home, projectDirectory) {
|
|
|
8076
8225
|
const directories = [];
|
|
8077
8226
|
if (projectDirectory) {
|
|
8078
8227
|
for (const name of AGENT_DIR_NAMES) {
|
|
8079
|
-
directories.push(
|
|
8228
|
+
directories.push(path9.join(projectDirectory, ".opencode", name));
|
|
8080
8229
|
}
|
|
8081
8230
|
}
|
|
8082
8231
|
if (home) {
|
|
8083
8232
|
for (const name of AGENT_DIR_NAMES) {
|
|
8084
|
-
directories.push(
|
|
8233
|
+
directories.push(path9.join(home, ".config", "opencode", name));
|
|
8085
8234
|
}
|
|
8086
8235
|
}
|
|
8087
8236
|
return directories;
|
|
@@ -8397,6 +8546,17 @@ function clipWithMarker(text, max) {
|
|
|
8397
8546
|
return `${text.slice(0, max)}
|
|
8398
8547
|
\u2026[truncated ${text.length - max} chars]`;
|
|
8399
8548
|
}
|
|
8549
|
+
var MAX_REPLAY_MESSAGE_CHARS = 2e3;
|
|
8550
|
+
var MAX_REPLAY_LAST_REPLY_CHARS = 12e3;
|
|
8551
|
+
var MAX_REPLAY_HEAD_CHARS = 1e3;
|
|
8552
|
+
function clipMiddle(text, max) {
|
|
8553
|
+
if (text.length <= max) return text;
|
|
8554
|
+
const head = Math.min(Math.floor(max / 4), MAX_REPLAY_HEAD_CHARS);
|
|
8555
|
+
const tail = max - head;
|
|
8556
|
+
return `${text.slice(0, head)}
|
|
8557
|
+
\u2026[${text.length - max} chars omitted]\u2026
|
|
8558
|
+
${text.slice(-tail)}`;
|
|
8559
|
+
}
|
|
8400
8560
|
function renderToolInput(input) {
|
|
8401
8561
|
let raw;
|
|
8402
8562
|
try {
|
|
@@ -8465,13 +8625,20 @@ function compactConversationHistory(prompt, opts = {}) {
|
|
|
8465
8625
|
return null;
|
|
8466
8626
|
}
|
|
8467
8627
|
const historyParts = [];
|
|
8628
|
+
let lastAssistant = -1;
|
|
8629
|
+
for (let i = conversationMessages.length - 2; i >= 0; i--) {
|
|
8630
|
+
if (conversationMessages[i].role === "assistant") {
|
|
8631
|
+
lastAssistant = i;
|
|
8632
|
+
break;
|
|
8633
|
+
}
|
|
8634
|
+
}
|
|
8468
8635
|
for (let i = 0; i < conversationMessages.length - 1; i++) {
|
|
8469
8636
|
const msg = conversationMessages[i];
|
|
8470
8637
|
const role = msg.role === "user" ? "User" : msg.role === "assistant" ? "Assistant" : "Tool";
|
|
8471
8638
|
const { text } = renderMessageContentForCompaction(msg);
|
|
8472
8639
|
if (text.trim()) {
|
|
8473
|
-
const
|
|
8474
|
-
historyParts.push(`${role}: ${
|
|
8640
|
+
const max = i === lastAssistant ? MAX_REPLAY_LAST_REPLY_CHARS : MAX_REPLAY_MESSAGE_CHARS;
|
|
8641
|
+
historyParts.push(`${role}: ${clipMiddle(text, max)}`);
|
|
8475
8642
|
}
|
|
8476
8643
|
}
|
|
8477
8644
|
if (historyParts.length === 0) {
|
|
@@ -8681,14 +8848,14 @@ function getTrailingUserMessages(prompt, opts = {}) {
|
|
|
8681
8848
|
|
|
8682
8849
|
// src/skill-bridge.ts
|
|
8683
8850
|
import * as crypto3 from "crypto";
|
|
8684
|
-
import * as
|
|
8685
|
-
import * as
|
|
8686
|
-
import * as
|
|
8851
|
+
import * as fs8 from "fs";
|
|
8852
|
+
import * as os4 from "os";
|
|
8853
|
+
import * as path10 from "path";
|
|
8687
8854
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8688
8855
|
function bundledSkillsDir() {
|
|
8689
8856
|
try {
|
|
8690
8857
|
const here = fileURLToPath3(import.meta.url);
|
|
8691
|
-
const dir =
|
|
8858
|
+
const dir = path10.resolve(path10.dirname(here), "..", "skills");
|
|
8692
8859
|
return dirExists2(dir) ? dir : null;
|
|
8693
8860
|
} catch {
|
|
8694
8861
|
return null;
|
|
@@ -8696,16 +8863,16 @@ function bundledSkillsDir() {
|
|
|
8696
8863
|
}
|
|
8697
8864
|
function dirExists2(p) {
|
|
8698
8865
|
try {
|
|
8699
|
-
return
|
|
8866
|
+
return fs8.statSync(p).isDirectory();
|
|
8700
8867
|
} catch {
|
|
8701
8868
|
return false;
|
|
8702
8869
|
}
|
|
8703
8870
|
}
|
|
8704
8871
|
function realPath(p) {
|
|
8705
8872
|
try {
|
|
8706
|
-
return
|
|
8873
|
+
return fs8.realpathSync(p);
|
|
8707
8874
|
} catch {
|
|
8708
|
-
return
|
|
8875
|
+
return path10.resolve(p);
|
|
8709
8876
|
}
|
|
8710
8877
|
}
|
|
8711
8878
|
function envEnabled(name) {
|
|
@@ -8726,10 +8893,10 @@ function declaredSkillName(source) {
|
|
|
8726
8893
|
return SAFE_SKILL_NAME.test(name) ? name : null;
|
|
8727
8894
|
}
|
|
8728
8895
|
function readSkill(root, entry) {
|
|
8729
|
-
const dir =
|
|
8896
|
+
const dir = path10.join(root, entry);
|
|
8730
8897
|
let source;
|
|
8731
8898
|
try {
|
|
8732
|
-
source =
|
|
8899
|
+
source = fs8.readFileSync(path10.join(dir, "SKILL.md"), "utf8");
|
|
8733
8900
|
} catch {
|
|
8734
8901
|
return null;
|
|
8735
8902
|
}
|
|
@@ -8747,47 +8914,47 @@ function skillRoots(cwd) {
|
|
|
8747
8914
|
const roots = [];
|
|
8748
8915
|
const seen = /* @__PURE__ */ new Set();
|
|
8749
8916
|
const push = (p) => {
|
|
8750
|
-
const abs =
|
|
8917
|
+
const abs = path10.resolve(p);
|
|
8751
8918
|
if (seen.has(abs)) return;
|
|
8752
8919
|
seen.add(abs);
|
|
8753
8920
|
if (dirExists2(abs)) roots.push(abs);
|
|
8754
8921
|
};
|
|
8755
|
-
const home =
|
|
8756
|
-
const homeDir = home ?
|
|
8757
|
-
let current =
|
|
8922
|
+
const home = os4.homedir();
|
|
8923
|
+
const homeDir = home ? path10.resolve(home) : null;
|
|
8924
|
+
let current = path10.resolve(cwd);
|
|
8758
8925
|
while (true) {
|
|
8759
|
-
push(
|
|
8926
|
+
push(path10.join(current, ".opencode", "skills"));
|
|
8760
8927
|
if (current !== homeDir) {
|
|
8761
|
-
push(
|
|
8762
|
-
push(
|
|
8928
|
+
push(path10.join(current, ".claude", "skills"));
|
|
8929
|
+
push(path10.join(current, ".agents", "skills"));
|
|
8763
8930
|
}
|
|
8764
|
-
const parent =
|
|
8931
|
+
const parent = path10.dirname(current);
|
|
8765
8932
|
if (parent === current) break;
|
|
8766
8933
|
current = parent;
|
|
8767
8934
|
}
|
|
8768
|
-
if (homeDir) push(
|
|
8935
|
+
if (homeDir) push(path10.join(homeDir, ".opencode", "skills"));
|
|
8769
8936
|
const envDir = process.env.OPENCODE_CONFIG_DIR;
|
|
8770
8937
|
if (envDir) {
|
|
8771
|
-
push(
|
|
8772
|
-
push(
|
|
8938
|
+
push(path10.join(envDir, "skills"));
|
|
8939
|
+
push(path10.join(envDir, "skill"));
|
|
8773
8940
|
}
|
|
8774
|
-
const xdg = process.env.XDG_CONFIG_HOME ?? (homeDir ?
|
|
8941
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? (homeDir ? path10.join(homeDir, ".config") : null);
|
|
8775
8942
|
if (xdg) {
|
|
8776
|
-
push(
|
|
8777
|
-
push(
|
|
8943
|
+
push(path10.join(xdg, "opencode", "skills"));
|
|
8944
|
+
push(path10.join(xdg, "opencode", "skill"));
|
|
8778
8945
|
}
|
|
8779
8946
|
if (homeDir && !envEnabled("OPENCODE_DISABLE_EXTERNAL_SKILLS")) {
|
|
8780
8947
|
if (!envEnabled("OPENCODE_DISABLE_CLAUDE_CODE_SKILLS")) {
|
|
8781
|
-
push(
|
|
8948
|
+
push(path10.join(homeDir, ".claude", "skills"));
|
|
8782
8949
|
}
|
|
8783
|
-
push(
|
|
8950
|
+
push(path10.join(homeDir, ".agents", "skills"));
|
|
8784
8951
|
}
|
|
8785
8952
|
return roots;
|
|
8786
8953
|
}
|
|
8787
8954
|
function collectSkills(root, claimed, found) {
|
|
8788
8955
|
let entries;
|
|
8789
8956
|
try {
|
|
8790
|
-
entries =
|
|
8957
|
+
entries = fs8.readdirSync(root, { withFileTypes: true });
|
|
8791
8958
|
} catch {
|
|
8792
8959
|
return;
|
|
8793
8960
|
}
|
|
@@ -8827,23 +8994,23 @@ function discoverBundledSkills() {
|
|
|
8827
8994
|
}
|
|
8828
8995
|
function claudeConfigDir(configDir) {
|
|
8829
8996
|
const explicit = configDir ?? process.env.CLAUDE_CONFIG_DIR;
|
|
8830
|
-
if (explicit) return
|
|
8831
|
-
const home =
|
|
8832
|
-
return home ?
|
|
8997
|
+
if (explicit) return path10.resolve(expandHome(explicit));
|
|
8998
|
+
const home = os4.homedir();
|
|
8999
|
+
return home ? path10.resolve(home, ".claude") : null;
|
|
8833
9000
|
}
|
|
8834
9001
|
function installedPluginSkillRoots(configDir, cwd) {
|
|
8835
9002
|
if (!configDir) return [];
|
|
8836
9003
|
let parsed;
|
|
8837
9004
|
try {
|
|
8838
9005
|
parsed = JSON.parse(
|
|
8839
|
-
|
|
9006
|
+
fs8.readFileSync(path10.join(configDir, "plugins", "installed_plugins.json"), "utf8")
|
|
8840
9007
|
);
|
|
8841
9008
|
} catch {
|
|
8842
9009
|
return [];
|
|
8843
9010
|
}
|
|
8844
9011
|
const plugins = parsed?.plugins;
|
|
8845
9012
|
if (!plugins || typeof plugins !== "object") return [];
|
|
8846
|
-
const workspace =
|
|
9013
|
+
const workspace = path10.resolve(cwd);
|
|
8847
9014
|
const roots = [];
|
|
8848
9015
|
for (const installs of Object.values(plugins)) {
|
|
8849
9016
|
if (!Array.isArray(installs)) continue;
|
|
@@ -8851,28 +9018,28 @@ function installedPluginSkillRoots(configDir, cwd) {
|
|
|
8851
9018
|
const entry = install;
|
|
8852
9019
|
if (typeof entry?.installPath !== "string" || !entry.installPath) continue;
|
|
8853
9020
|
if (entry.scope !== "user" && !coversWorkspace(entry.projectPath, workspace)) continue;
|
|
8854
|
-
roots.push(
|
|
9021
|
+
roots.push(path10.join(entry.installPath, "skills"));
|
|
8855
9022
|
}
|
|
8856
9023
|
}
|
|
8857
9024
|
return roots;
|
|
8858
9025
|
}
|
|
8859
9026
|
function coversWorkspace(projectPath, workspace) {
|
|
8860
9027
|
if (typeof projectPath !== "string" || !projectPath) return false;
|
|
8861
|
-
const root =
|
|
9028
|
+
const root = path10.resolve(projectPath);
|
|
8862
9029
|
if (root === workspace) return true;
|
|
8863
|
-
const rel =
|
|
8864
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
9030
|
+
const rel = path10.relative(root, workspace);
|
|
9031
|
+
return rel !== "" && !rel.startsWith("..") && !path10.isAbsolute(rel);
|
|
8865
9032
|
}
|
|
8866
9033
|
function discoverNativeClaudeSkills(opts) {
|
|
8867
9034
|
const found = [];
|
|
8868
9035
|
const scanned = /* @__PURE__ */ new Set();
|
|
8869
9036
|
const scan = (root, scope) => {
|
|
8870
|
-
const abs =
|
|
9037
|
+
const abs = path10.resolve(root);
|
|
8871
9038
|
if (scanned.has(abs)) return;
|
|
8872
9039
|
scanned.add(abs);
|
|
8873
9040
|
let entries;
|
|
8874
9041
|
try {
|
|
8875
|
-
entries =
|
|
9042
|
+
entries = fs8.readdirSync(abs, { withFileTypes: true });
|
|
8876
9043
|
} catch {
|
|
8877
9044
|
return;
|
|
8878
9045
|
}
|
|
@@ -8884,13 +9051,13 @@ function discoverNativeClaudeSkills(opts) {
|
|
|
8884
9051
|
}
|
|
8885
9052
|
};
|
|
8886
9053
|
const configDir = claudeConfigDir(opts.configDir);
|
|
8887
|
-
if (configDir) scan(
|
|
8888
|
-
const home =
|
|
8889
|
-
const homeDir = home ?
|
|
8890
|
-
let current =
|
|
9054
|
+
if (configDir) scan(path10.join(configDir, "skills"), "user");
|
|
9055
|
+
const home = os4.homedir();
|
|
9056
|
+
const homeDir = home ? path10.resolve(home) : null;
|
|
9057
|
+
let current = path10.resolve(opts.cwd);
|
|
8891
9058
|
while (true) {
|
|
8892
|
-
if (current !== homeDir) scan(
|
|
8893
|
-
const parent =
|
|
9059
|
+
if (current !== homeDir) scan(path10.join(current, ".claude", "skills"), "project");
|
|
9060
|
+
const parent = path10.dirname(current);
|
|
8894
9061
|
if (parent === current) break;
|
|
8895
9062
|
current = parent;
|
|
8896
9063
|
}
|
|
@@ -8924,10 +9091,10 @@ function dropNativelyLoadedSkills(skills, native) {
|
|
|
8924
9091
|
}
|
|
8925
9092
|
function readBundledSkillInfos() {
|
|
8926
9093
|
return discoverBundledSkills().flatMap((skill) => {
|
|
8927
|
-
const file =
|
|
9094
|
+
const file = path10.join(skill.dir, "SKILL.md");
|
|
8928
9095
|
let text;
|
|
8929
9096
|
try {
|
|
8930
|
-
text =
|
|
9097
|
+
text = fs8.readFileSync(file, "utf8");
|
|
8931
9098
|
} catch {
|
|
8932
9099
|
return [];
|
|
8933
9100
|
}
|
|
@@ -8951,15 +9118,15 @@ function readBundledSkillInfos() {
|
|
|
8951
9118
|
}
|
|
8952
9119
|
function linkSkill(source, target) {
|
|
8953
9120
|
try {
|
|
8954
|
-
|
|
9121
|
+
fs8.symlinkSync(source, target, process.platform === "win32" ? "junction" : "dir");
|
|
8955
9122
|
return;
|
|
8956
9123
|
} catch {
|
|
8957
|
-
|
|
9124
|
+
fs8.cpSync(source, target, { recursive: true, dereference: true });
|
|
8958
9125
|
}
|
|
8959
9126
|
}
|
|
8960
9127
|
function fileExists2(p) {
|
|
8961
9128
|
try {
|
|
8962
|
-
return
|
|
9129
|
+
return fs8.statSync(p).isFile();
|
|
8963
9130
|
} catch {
|
|
8964
9131
|
return false;
|
|
8965
9132
|
}
|
|
@@ -8968,14 +9135,14 @@ function buildSkillPluginDir(skills) {
|
|
|
8968
9135
|
if (skills.length === 0) return null;
|
|
8969
9136
|
const fingerprint = skills.map((s) => `${s.name}\0${s.dir}`).join("\n");
|
|
8970
9137
|
const hash = crypto3.createHash("sha256").update(fingerprint).digest("hex").slice(0, 12);
|
|
8971
|
-
const root =
|
|
8972
|
-
const manifest =
|
|
9138
|
+
const root = path10.join(pluginTmpDir(), `skills-${hash}`);
|
|
9139
|
+
const manifest = path10.join(root, ".claude-plugin", "plugin.json");
|
|
8973
9140
|
if (fileExists2(manifest)) return root;
|
|
8974
9141
|
try {
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
9142
|
+
fs8.rmSync(root, { recursive: true, force: true });
|
|
9143
|
+
fs8.mkdirSync(path10.join(root, ".claude-plugin"), { recursive: true });
|
|
9144
|
+
fs8.mkdirSync(path10.join(root, "skills"), { recursive: true });
|
|
9145
|
+
fs8.writeFileSync(
|
|
8979
9146
|
manifest,
|
|
8980
9147
|
JSON.stringify(
|
|
8981
9148
|
{
|
|
@@ -8988,7 +9155,7 @@ function buildSkillPluginDir(skills) {
|
|
|
8988
9155
|
{ encoding: "utf8", mode: 384 }
|
|
8989
9156
|
);
|
|
8990
9157
|
for (const skill of skills) {
|
|
8991
|
-
linkSkill(skill.dir,
|
|
9158
|
+
linkSkill(skill.dir, path10.join(root, "skills", skill.name));
|
|
8992
9159
|
}
|
|
8993
9160
|
} catch (err) {
|
|
8994
9161
|
log.warn("failed to stage opencode skill plugin dir", {
|
|
@@ -9004,7 +9171,7 @@ function registerBundledSkillPath(config) {
|
|
|
9004
9171
|
if (!dir) return false;
|
|
9005
9172
|
config.skills ??= {};
|
|
9006
9173
|
const paths = config.skills.paths ??= [];
|
|
9007
|
-
if (paths.some((p) =>
|
|
9174
|
+
if (paths.some((p) => path10.resolve(p) === dir)) return false;
|
|
9008
9175
|
paths.push(dir);
|
|
9009
9176
|
return true;
|
|
9010
9177
|
}
|
|
@@ -9054,36 +9221,36 @@ import { EventEmitter as EventEmitter4 } from "events";
|
|
|
9054
9221
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
9055
9222
|
import { existsSync as existsSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
9056
9223
|
import { unlink as unlink2 } from "fs/promises";
|
|
9057
|
-
import { join as
|
|
9224
|
+
import { join as join11 } from "path";
|
|
9058
9225
|
|
|
9059
9226
|
// src/claude-session-bun.ts
|
|
9060
|
-
import * as
|
|
9061
|
-
import * as
|
|
9062
|
-
import * as
|
|
9227
|
+
import * as os5 from "os";
|
|
9228
|
+
import * as fs9 from "fs";
|
|
9229
|
+
import * as path11 from "path";
|
|
9063
9230
|
import { execFileSync } from "child_process";
|
|
9064
9231
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
9065
9232
|
import { StringDecoder } from "string_decoder";
|
|
9066
9233
|
function resolveClaude(cmd = "claude") {
|
|
9067
|
-
if (
|
|
9234
|
+
if (path11.isAbsolute(cmd) && fs9.existsSync(cmd)) return cmd;
|
|
9068
9235
|
const viaBun = Bun.which(cmd);
|
|
9069
9236
|
if (viaBun) return viaBun;
|
|
9070
|
-
const isWin =
|
|
9237
|
+
const isWin = os5.platform() === "win32";
|
|
9071
9238
|
try {
|
|
9072
9239
|
const out = execFileSync(isWin ? "where" : "which", [cmd], {
|
|
9073
9240
|
encoding: "utf8",
|
|
9074
9241
|
stdio: ["ignore", "pipe", "ignore"]
|
|
9075
9242
|
});
|
|
9076
|
-
const first = out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).find((p) =>
|
|
9243
|
+
const first = out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).find((p) => fs9.existsSync(p));
|
|
9077
9244
|
if (first) return first;
|
|
9078
9245
|
} catch {
|
|
9079
9246
|
}
|
|
9080
9247
|
throw new Error(`Could not resolve command on PATH: ${cmd}`);
|
|
9081
9248
|
}
|
|
9082
9249
|
function encodeCwd(cwd) {
|
|
9083
|
-
const resolved =
|
|
9250
|
+
const resolved = path11.resolve(cwd);
|
|
9084
9251
|
let real = resolved;
|
|
9085
9252
|
try {
|
|
9086
|
-
real =
|
|
9253
|
+
real = fs9.realpathSync.native(resolved);
|
|
9087
9254
|
} catch {
|
|
9088
9255
|
}
|
|
9089
9256
|
return real.replace(/[^a-zA-Z0-9]/g, "-");
|
|
@@ -9091,7 +9258,7 @@ function encodeCwd(cwd) {
|
|
|
9091
9258
|
var bunPtySpawner = (argv, opts) => {
|
|
9092
9259
|
const [command, ...args] = argv;
|
|
9093
9260
|
const resolved = resolveClaude(command ?? "claude");
|
|
9094
|
-
if (
|
|
9261
|
+
if (os5.platform() === "win32" && isBatchFile(resolved)) {
|
|
9095
9262
|
throw new Error(
|
|
9096
9263
|
`The interactive transport cannot run a Windows batch shim (${resolved}). Point \`cliPath\` at claude.exe, or use the default headless transport.`
|
|
9097
9264
|
);
|
|
@@ -9199,10 +9366,10 @@ function classifyScreen(text) {
|
|
|
9199
9366
|
return null;
|
|
9200
9367
|
}
|
|
9201
9368
|
function interactiveTranscriptPath(opts) {
|
|
9202
|
-
return
|
|
9369
|
+
return path11.join(
|
|
9203
9370
|
resolveConfigDir(opts.configDir),
|
|
9204
9371
|
"projects",
|
|
9205
|
-
encodeCwd(
|
|
9372
|
+
encodeCwd(path11.resolve(opts.cwd)),
|
|
9206
9373
|
`${opts.sessionId}.jsonl`
|
|
9207
9374
|
);
|
|
9208
9375
|
}
|
|
@@ -9380,7 +9547,7 @@ var TranscriptCursor = class {
|
|
|
9380
9547
|
const filePath = this.resolvePath();
|
|
9381
9548
|
let stat;
|
|
9382
9549
|
try {
|
|
9383
|
-
stat =
|
|
9550
|
+
stat = fs9.statSync(filePath);
|
|
9384
9551
|
} catch {
|
|
9385
9552
|
return;
|
|
9386
9553
|
}
|
|
@@ -9396,13 +9563,13 @@ var TranscriptCursor = class {
|
|
|
9396
9563
|
if (stat.size <= this.offset) return;
|
|
9397
9564
|
let fd = null;
|
|
9398
9565
|
try {
|
|
9399
|
-
fd =
|
|
9566
|
+
fd = fs9.openSync(filePath, "r");
|
|
9400
9567
|
const buffer = Buffer.allocUnsafe(
|
|
9401
9568
|
Math.min(TRANSCRIPT_CHUNK_BYTES, stat.size - this.offset)
|
|
9402
9569
|
);
|
|
9403
9570
|
while (this.offset < stat.size) {
|
|
9404
9571
|
const want = Math.min(buffer.length, stat.size - this.offset);
|
|
9405
|
-
const read =
|
|
9572
|
+
const read = fs9.readSync(fd, buffer, 0, want, this.offset);
|
|
9406
9573
|
this.stats.reads++;
|
|
9407
9574
|
if (read <= 0) break;
|
|
9408
9575
|
this.offset += read;
|
|
@@ -9413,7 +9580,7 @@ var TranscriptCursor = class {
|
|
|
9413
9580
|
} finally {
|
|
9414
9581
|
if (fd !== null) {
|
|
9415
9582
|
try {
|
|
9416
|
-
|
|
9583
|
+
fs9.closeSync(fd);
|
|
9417
9584
|
} catch {
|
|
9418
9585
|
}
|
|
9419
9586
|
}
|
|
@@ -9443,12 +9610,12 @@ function isOwnPromptRecord(rec, normalized) {
|
|
|
9443
9610
|
}
|
|
9444
9611
|
function resolveConfigDir(configDir) {
|
|
9445
9612
|
const value = configDir ?? process.env.CLAUDE_CONFIG_DIR;
|
|
9446
|
-
if (!value) return
|
|
9447
|
-
if (value === "~") return
|
|
9613
|
+
if (!value) return path11.join(os5.homedir(), ".claude");
|
|
9614
|
+
if (value === "~") return os5.homedir();
|
|
9448
9615
|
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
9449
|
-
return
|
|
9616
|
+
return path11.join(os5.homedir(), value.slice(2));
|
|
9450
9617
|
}
|
|
9451
|
-
return
|
|
9618
|
+
return path11.resolve(value);
|
|
9452
9619
|
}
|
|
9453
9620
|
function fatalScreenMessage(kind, configDir, cwd) {
|
|
9454
9621
|
if (kind === "trust") {
|
|
@@ -9512,7 +9679,7 @@ var ClaudeSession = class {
|
|
|
9512
9679
|
abandonTurn = false;
|
|
9513
9680
|
o;
|
|
9514
9681
|
constructor(opts = {}) {
|
|
9515
|
-
this.cwd =
|
|
9682
|
+
this.cwd = path11.resolve(opts.cwd ?? process.cwd());
|
|
9516
9683
|
this.configDir = resolveConfigDir(opts.configDir);
|
|
9517
9684
|
this.explicitConfigDir = opts.configDir ? this.configDir : void 0;
|
|
9518
9685
|
this.signal = opts.signal;
|
|
@@ -9524,7 +9691,7 @@ var ClaudeSession = class {
|
|
|
9524
9691
|
this.onExit = opts.onExit;
|
|
9525
9692
|
this.spawnPty = opts.spawnPty ?? bunPtySpawner;
|
|
9526
9693
|
this.sessionId = opts.resumeSessionId ?? randomUUID4();
|
|
9527
|
-
this.jsonlPath =
|
|
9694
|
+
this.jsonlPath = path11.join(
|
|
9528
9695
|
this.configDir,
|
|
9529
9696
|
"projects",
|
|
9530
9697
|
encodeCwd(this.cwd),
|
|
@@ -10235,7 +10402,7 @@ ${text2}`
|
|
|
10235
10402
|
}
|
|
10236
10403
|
function stageImage(data, extension) {
|
|
10237
10404
|
try {
|
|
10238
|
-
const file =
|
|
10405
|
+
const file = join11(pluginTmpDir(), `attachment-${randomUUID5()}.${extension}`);
|
|
10239
10406
|
writeFileSync5(file, data, { mode: 384, flag: "wx" });
|
|
10240
10407
|
return file;
|
|
10241
10408
|
} catch (err) {
|
|
@@ -10720,7 +10887,7 @@ function spawnInteractiveProcess(opts) {
|
|
|
10720
10887
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
10721
10888
|
import { constants as fsConstants } from "fs";
|
|
10722
10889
|
import { chmod as chmod2, copyFile as copyFile2, lstat as lstat2, mkdir as mkdir2 } from "fs/promises";
|
|
10723
|
-
import
|
|
10890
|
+
import path12 from "path";
|
|
10724
10891
|
var transcriptPath = interactiveTranscriptPath;
|
|
10725
10892
|
function configDirForAccount(account) {
|
|
10726
10893
|
const normalized = normalizeAccountName(account || DEFAULT_ACCOUNT);
|
|
@@ -10748,7 +10915,7 @@ async function carryTranscriptToAccount(input) {
|
|
|
10748
10915
|
const carriedId = renamed ? (input.newSessionId ?? randomUUID6)() : sessionId;
|
|
10749
10916
|
const target = renamed ? transcriptPath({ configDir: targetConfigDir, cwd, sessionId: carriedId }) : targetOwn;
|
|
10750
10917
|
try {
|
|
10751
|
-
await mkdir2(
|
|
10918
|
+
await mkdir2(path12.dirname(target), { recursive: true, mode: 448 });
|
|
10752
10919
|
await copyFile2(live.file, target, fsConstants.COPYFILE_EXCL);
|
|
10753
10920
|
await chmod2(target, 384);
|
|
10754
10921
|
} catch (err) {
|
|
@@ -10768,7 +10935,7 @@ function dedupe2(dirs) {
|
|
|
10768
10935
|
const seen = /* @__PURE__ */ new Set();
|
|
10769
10936
|
const out = [];
|
|
10770
10937
|
for (const dir of dirs) {
|
|
10771
|
-
const resolved =
|
|
10938
|
+
const resolved = path12.resolve(dir);
|
|
10772
10939
|
if (seen.has(resolved)) continue;
|
|
10773
10940
|
seen.add(resolved);
|
|
10774
10941
|
out.push(resolved);
|
|
@@ -10777,13 +10944,13 @@ function dedupe2(dirs) {
|
|
|
10777
10944
|
}
|
|
10778
10945
|
|
|
10779
10946
|
// src/prompts.ts
|
|
10780
|
-
import { readFileSync as
|
|
10781
|
-
import { homedir as
|
|
10947
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
10948
|
+
import { homedir as homedir9 } from "os";
|
|
10782
10949
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
10783
|
-
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as
|
|
10784
|
-
function readPromptFileIfPresent(
|
|
10950
|
+
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join12 } from "path";
|
|
10951
|
+
function readPromptFileIfPresent(path15) {
|
|
10785
10952
|
try {
|
|
10786
|
-
const content =
|
|
10953
|
+
const content = readFileSync7(path15, "utf8").trim();
|
|
10787
10954
|
return content || void 0;
|
|
10788
10955
|
} catch {
|
|
10789
10956
|
return void 0;
|
|
@@ -10792,7 +10959,7 @@ function readPromptFileIfPresent(path14) {
|
|
|
10792
10959
|
function nearestWorkspaceAgentsPrompt(cwd) {
|
|
10793
10960
|
let dir = cwd;
|
|
10794
10961
|
while (true) {
|
|
10795
|
-
const content = readPromptFileIfPresent(
|
|
10962
|
+
const content = readPromptFileIfPresent(join12(dir, "AGENTS.md"));
|
|
10796
10963
|
if (content) return content;
|
|
10797
10964
|
const parent = dirname6(dir);
|
|
10798
10965
|
if (parent === dir) return void 0;
|
|
@@ -10888,9 +11055,9 @@ function extractSystemMessages(prompt) {
|
|
|
10888
11055
|
}
|
|
10889
11056
|
var CLAUDE_NATIVE_INSTRUCTION_FILES = /* @__PURE__ */ new Set(["CLAUDE.md", "CLAUDE.local.md"]);
|
|
10890
11057
|
function userAuthoredInstructions(forwarded, opts = {}) {
|
|
10891
|
-
const readFile2 = opts.readFile ?? ((
|
|
11058
|
+
const readFile2 = opts.readFile ?? ((path15) => {
|
|
10892
11059
|
try {
|
|
10893
|
-
return
|
|
11060
|
+
return readFileSync7(path15, "utf8");
|
|
10894
11061
|
} catch {
|
|
10895
11062
|
return null;
|
|
10896
11063
|
}
|
|
@@ -10902,16 +11069,16 @@ function userAuthoredInstructions(forwarded, opts = {}) {
|
|
|
10902
11069
|
}
|
|
10903
11070
|
const seen = /* @__PURE__ */ new Set();
|
|
10904
11071
|
for (const match of forwarded.matchAll(/^Instructions from: (.+)$/gm)) {
|
|
10905
|
-
const
|
|
10906
|
-
if (seen.has(
|
|
10907
|
-
if (CLAUDE_NATIVE_INSTRUCTION_FILES.has(basename2(
|
|
10908
|
-
seen.add(
|
|
10909
|
-
const file = readFile2(
|
|
11072
|
+
const path15 = match[1].trim();
|
|
11073
|
+
if (seen.has(path15) || !isAbsolute3(path15)) continue;
|
|
11074
|
+
if (CLAUDE_NATIVE_INSTRUCTION_FILES.has(basename2(path15))) continue;
|
|
11075
|
+
seen.add(path15);
|
|
11076
|
+
const file = readFile2(path15);
|
|
10910
11077
|
if (!file?.trim()) continue;
|
|
10911
11078
|
const start = match.index + match[0].length + 1;
|
|
10912
11079
|
const content = forwarded.startsWith(file, start) ? file : forwarded.startsWith(file.trimEnd(), start) ? file.trimEnd() : null;
|
|
10913
11080
|
if (content === null) continue;
|
|
10914
|
-
out.push({ text: `Instructions from: ${
|
|
11081
|
+
out.push({ text: `Instructions from: ${path15}
|
|
10915
11082
|
${content}`, content });
|
|
10916
11083
|
}
|
|
10917
11084
|
return out;
|
|
@@ -10931,8 +11098,8 @@ ${options.compressionSummary.trim()}`
|
|
|
10931
11098
|
for (const s of extraSystemContent) {
|
|
10932
11099
|
if (s.trim()) parts.push(s.trim());
|
|
10933
11100
|
}
|
|
10934
|
-
const configRoot = process.env.XDG_CONFIG_HOME ??
|
|
10935
|
-
const globalAgents = readPromptFileIfPresent(
|
|
11101
|
+
const configRoot = process.env.XDG_CONFIG_HOME ?? join12(homedir9(), ".config");
|
|
11102
|
+
const globalAgents = readPromptFileIfPresent(join12(configRoot, "opencode", "AGENTS.md"));
|
|
10936
11103
|
const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
|
|
10937
11104
|
const forwarded = extraSystemContent.join("\n\n");
|
|
10938
11105
|
const pushGlobal = !!globalAgents && !forwarded.includes(globalAgents);
|
|
@@ -10948,10 +11115,10 @@ ${options.compressionSummary.trim()}`
|
|
|
10948
11115
|
if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
|
|
10949
11116
|
const content = parts.join("\n\n");
|
|
10950
11117
|
if (!content) return void 0;
|
|
10951
|
-
const
|
|
11118
|
+
const path15 = join12(pluginTmpDir(), `opencode-cc-sys-${randomUUID7()}.md`);
|
|
10952
11119
|
try {
|
|
10953
|
-
writeFileSync6(
|
|
10954
|
-
return
|
|
11120
|
+
writeFileSync6(path15, content, { encoding: "utf8", mode: 384 });
|
|
11121
|
+
return path15;
|
|
10955
11122
|
} catch (err) {
|
|
10956
11123
|
log.warn("failed to write system prompt file", { error: String(err) });
|
|
10957
11124
|
return void 0;
|
|
@@ -11378,7 +11545,9 @@ function effectiveMcpConfig(config, cwd, proxyConfigPath, runtimeStatus, exclude
|
|
|
11378
11545
|
let bridgedHash = null;
|
|
11379
11546
|
let allEnabledServerNames = [];
|
|
11380
11547
|
if (config.bridgeOpencodeMcp !== false) {
|
|
11381
|
-
const bridged = bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers, config.hostApi
|
|
11548
|
+
const bridged = bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers, config.hostApi, {
|
|
11549
|
+
oauthTokens: config.bridgeMcpOauthTokens === true
|
|
11550
|
+
});
|
|
11382
11551
|
if (bridged) {
|
|
11383
11552
|
if (bridged.path) paths.push(bridged.path);
|
|
11384
11553
|
bridgedHash = bridged.hash;
|
|
@@ -14082,7 +14251,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
14082
14251
|
let replayReason = "no-session-for-key";
|
|
14083
14252
|
if (this.config.resumeAfterRestart !== false && includeHistoryContext && !compactionMode && !forkFromClaudeSessionId && failoverAnswer?.kind !== "switch") {
|
|
14084
14253
|
const configDir = resolveConfigDir(this.config.configDir);
|
|
14085
|
-
const transcriptPath2 = (id) =>
|
|
14254
|
+
const transcriptPath2 = (id) => join13(configDir, "projects", encodeCwd(cwd), `${id}.jsonl`);
|
|
14086
14255
|
const resumePoint = findResumePoint({
|
|
14087
14256
|
sessionKey: sk,
|
|
14088
14257
|
prompt: options.prompt,
|
|
@@ -15338,8 +15507,8 @@ var ClaudeCodeLanguageModel = class {
|
|
|
15338
15507
|
};
|
|
15339
15508
|
|
|
15340
15509
|
// src/account-tier.ts
|
|
15341
|
-
import { readFileSync as
|
|
15342
|
-
import
|
|
15510
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
15511
|
+
import path13 from "path";
|
|
15343
15512
|
var ORGANIZATION_TYPE_LABELS = {
|
|
15344
15513
|
claude_max: "Max",
|
|
15345
15514
|
claude_pro: "Pro",
|
|
@@ -15368,7 +15537,7 @@ function readAccountTier(account) {
|
|
|
15368
15537
|
return label;
|
|
15369
15538
|
}
|
|
15370
15539
|
function readTierFromConfigDir(configDir, allowLegacyFallback) {
|
|
15371
|
-
const candidates = [
|
|
15540
|
+
const candidates = [path13.join(configDir, ".claude.json")];
|
|
15372
15541
|
if (allowLegacyFallback) candidates.push(expandHome("~/.claude.json"));
|
|
15373
15542
|
for (const candidate of candidates) {
|
|
15374
15543
|
const oauth = readOauthAccount(candidate);
|
|
@@ -15381,7 +15550,7 @@ function readTierFromConfigDir(configDir, allowLegacyFallback) {
|
|
|
15381
15550
|
function readOauthAccount(file) {
|
|
15382
15551
|
let raw;
|
|
15383
15552
|
try {
|
|
15384
|
-
raw =
|
|
15553
|
+
raw = readFileSync8(file, "utf8");
|
|
15385
15554
|
} catch {
|
|
15386
15555
|
return void 0;
|
|
15387
15556
|
}
|
|
@@ -15410,13 +15579,13 @@ function tierLabel(oauthAccount) {
|
|
|
15410
15579
|
import {
|
|
15411
15580
|
existsSync as existsSync5,
|
|
15412
15581
|
mkdirSync as mkdirSync5,
|
|
15413
|
-
readFileSync as
|
|
15582
|
+
readFileSync as readFileSync9,
|
|
15414
15583
|
realpathSync as realpathSync4,
|
|
15415
15584
|
rmSync as rmSync3,
|
|
15416
15585
|
writeFileSync as writeFileSync7
|
|
15417
15586
|
} from "fs";
|
|
15418
|
-
import { homedir as
|
|
15419
|
-
import { dirname as dirname7, join as
|
|
15587
|
+
import { homedir as homedir10 } from "os";
|
|
15588
|
+
import { dirname as dirname7, join as join14, resolve as resolve5 } from "path";
|
|
15420
15589
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
15421
15590
|
var STALE_PACKAGE_NAME = "opencode-claude-code-plugin";
|
|
15422
15591
|
var SUSPECT_DESCRIPTION_TOKEN = "Claude Code";
|
|
@@ -15424,20 +15593,20 @@ var alreadyRan = false;
|
|
|
15424
15593
|
function candidateCacheRoots() {
|
|
15425
15594
|
const xdg = process.env.XDG_CACHE_HOME;
|
|
15426
15595
|
return [
|
|
15427
|
-
xdg ?
|
|
15428
|
-
|
|
15429
|
-
|
|
15596
|
+
xdg ? join14(xdg, "opencode") : null,
|
|
15597
|
+
join14(homedir10(), ".cache", "opencode"),
|
|
15598
|
+
join14(homedir10(), "Library", "Caches", "opencode")
|
|
15430
15599
|
].filter((p) => Boolean(p));
|
|
15431
15600
|
}
|
|
15432
15601
|
function userOpencodeJsonPath() {
|
|
15433
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME ??
|
|
15434
|
-
return
|
|
15602
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME ?? join14(homedir10(), ".config");
|
|
15603
|
+
return join14(xdgConfig, "opencode", "opencode.json");
|
|
15435
15604
|
}
|
|
15436
15605
|
function userIntendsToUseUnscoped() {
|
|
15437
15606
|
const cfg = userOpencodeJsonPath();
|
|
15438
15607
|
if (!existsSync5(cfg)) return false;
|
|
15439
15608
|
try {
|
|
15440
|
-
const json = JSON.parse(
|
|
15609
|
+
const json = JSON.parse(readFileSync9(cfg, "utf8"));
|
|
15441
15610
|
const plugins = json.plugin;
|
|
15442
15611
|
if (!Array.isArray(plugins)) return false;
|
|
15443
15612
|
return plugins.some(
|
|
@@ -15458,19 +15627,19 @@ function ourLoadedDir() {
|
|
|
15458
15627
|
function ourVersion(dir) {
|
|
15459
15628
|
if (!dir) return null;
|
|
15460
15629
|
try {
|
|
15461
|
-
const pkg = JSON.parse(
|
|
15630
|
+
const pkg = JSON.parse(readFileSync9(join14(dir, "package.json"), "utf8"));
|
|
15462
15631
|
return typeof pkg.version === "string" && pkg.version ? pkg.version : null;
|
|
15463
15632
|
} catch {
|
|
15464
15633
|
return null;
|
|
15465
15634
|
}
|
|
15466
15635
|
}
|
|
15467
15636
|
function markerPath() {
|
|
15468
|
-
const stateRoot = process.env.XDG_STATE_HOME ??
|
|
15469
|
-
return
|
|
15637
|
+
const stateRoot = process.env.XDG_STATE_HOME ?? join14(homedir10(), ".local", "state");
|
|
15638
|
+
return join14(stateRoot, "opencode-claude-code-plugin", "cleanup-stale.json");
|
|
15470
15639
|
}
|
|
15471
15640
|
function markerVersion() {
|
|
15472
15641
|
try {
|
|
15473
|
-
const marker = JSON.parse(
|
|
15642
|
+
const marker = JSON.parse(readFileSync9(markerPath(), "utf8"));
|
|
15474
15643
|
return typeof marker.version === "string" ? marker.version : null;
|
|
15475
15644
|
} catch {
|
|
15476
15645
|
return null;
|
|
@@ -15515,7 +15684,7 @@ function cleanupStaleUnscopedInstall() {
|
|
|
15515
15684
|
}
|
|
15516
15685
|
function cleanupOne(cacheRoot, ourDir) {
|
|
15517
15686
|
if (!existsSync5(cacheRoot)) return;
|
|
15518
|
-
const stalePath =
|
|
15687
|
+
const stalePath = join14(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
|
|
15519
15688
|
if (!existsSync5(stalePath)) return;
|
|
15520
15689
|
let realStalePath = stalePath;
|
|
15521
15690
|
try {
|
|
@@ -15523,11 +15692,11 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
15523
15692
|
} catch {
|
|
15524
15693
|
}
|
|
15525
15694
|
if (ourDir && realStalePath === ourDir) return;
|
|
15526
|
-
const pkgJsonPath =
|
|
15695
|
+
const pkgJsonPath = join14(stalePath, "package.json");
|
|
15527
15696
|
if (!existsSync5(pkgJsonPath)) return;
|
|
15528
15697
|
let pkg = {};
|
|
15529
15698
|
try {
|
|
15530
|
-
pkg = JSON.parse(
|
|
15699
|
+
pkg = JSON.parse(readFileSync9(pkgJsonPath, "utf8"));
|
|
15531
15700
|
} catch {
|
|
15532
15701
|
return;
|
|
15533
15702
|
}
|
|
@@ -15543,10 +15712,10 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
15543
15712
|
});
|
|
15544
15713
|
return;
|
|
15545
15714
|
}
|
|
15546
|
-
const cachePkgJson =
|
|
15715
|
+
const cachePkgJson = join14(cacheRoot, "package.json");
|
|
15547
15716
|
if (!existsSync5(cachePkgJson)) return;
|
|
15548
15717
|
try {
|
|
15549
|
-
const cfg = JSON.parse(
|
|
15718
|
+
const cfg = JSON.parse(readFileSync9(cachePkgJson, "utf8"));
|
|
15550
15719
|
if (cfg?.dependencies?.[STALE_PACKAGE_NAME]) {
|
|
15551
15720
|
delete cfg.dependencies[STALE_PACKAGE_NAME];
|
|
15552
15721
|
writeFileSync7(cachePkgJson, JSON.stringify(cfg, null, 2) + "\n");
|
|
@@ -15626,7 +15795,7 @@ function registerOpencodeLogSink(client) {
|
|
|
15626
15795
|
}
|
|
15627
15796
|
|
|
15628
15797
|
// src/v2-client.ts
|
|
15629
|
-
import
|
|
15798
|
+
import path14 from "path";
|
|
15630
15799
|
function formatAgentTypeList(agents) {
|
|
15631
15800
|
const lines = agents.flatMap((agent) => {
|
|
15632
15801
|
if (agent.hidden === true || agent.mode === "primary") return [];
|
|
@@ -15655,7 +15824,7 @@ function toV1Session(info) {
|
|
|
15655
15824
|
if (!info || typeof info !== "object") return void 0;
|
|
15656
15825
|
const session = info;
|
|
15657
15826
|
const root = session.location?.directory;
|
|
15658
|
-
const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ?
|
|
15827
|
+
const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ? path14.resolve(root, session.subpath) : root : void 0;
|
|
15659
15828
|
return { ...info, directory, parentID: session.parentID };
|
|
15660
15829
|
}
|
|
15661
15830
|
function toV1Messages(messages) {
|
|
@@ -16190,6 +16359,9 @@ function createClaudeCode(settings = {}) {
|
|
|
16190
16359
|
mcpConfig: settings.mcpConfig,
|
|
16191
16360
|
strictMcpConfig: settings.strictMcpConfig,
|
|
16192
16361
|
bridgeOpencodeMcp: settings.bridgeOpencodeMcp ?? true,
|
|
16362
|
+
// No `?? true` twin: reading opencode's credential store is opt-in, so
|
|
16363
|
+
// the absent value has to stay falsy.
|
|
16364
|
+
bridgeMcpOauthTokens: settings.bridgeMcpOauthTokens,
|
|
16193
16365
|
controlRequestBehavior: preset?.controlRequestBehavior ?? settings.controlRequestBehavior ?? "allow",
|
|
16194
16366
|
controlRequestToolBehaviors: preset ? void 0 : settings.controlRequestToolBehaviors,
|
|
16195
16367
|
controlRequestDenyMessage: settings.controlRequestDenyMessage,
|