@khalilgharbaoui/opencode-claude-code-plugin 0.47.0 → 0.49.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 +16 -0
- package/dist/index.js +264 -55
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +39 -10
package/dist/index.js
CHANGED
|
@@ -154,7 +154,16 @@ function translateStreamForHost(stream, dialect = "v1") {
|
|
|
154
154
|
import { homedir as homedir2 } from "os";
|
|
155
155
|
|
|
156
156
|
// src/accounts.ts
|
|
157
|
-
import {
|
|
157
|
+
import {
|
|
158
|
+
chmod,
|
|
159
|
+
copyFile,
|
|
160
|
+
link,
|
|
161
|
+
lstat,
|
|
162
|
+
mkdir,
|
|
163
|
+
readlink,
|
|
164
|
+
symlink,
|
|
165
|
+
writeFile
|
|
166
|
+
} from "fs/promises";
|
|
158
167
|
import path from "path";
|
|
159
168
|
|
|
160
169
|
// src/logger.ts
|
|
@@ -401,8 +410,9 @@ function resolveAccounts(value) {
|
|
|
401
410
|
function accountProviderId(account) {
|
|
402
411
|
return `${BASE_PROVIDER_ID}-${normalizeAccountName(account)}`;
|
|
403
412
|
}
|
|
404
|
-
function accountDisplayName(account) {
|
|
405
|
-
|
|
413
|
+
function accountDisplayName(account, tier) {
|
|
414
|
+
const name = titleizeAccount(account);
|
|
415
|
+
return `Claude Code (${tier ? `${name}, ${tier}` : name})`;
|
|
406
416
|
}
|
|
407
417
|
function accountModelSuffix(account) {
|
|
408
418
|
const normalized = normalizeAccountName(account);
|
|
@@ -425,13 +435,14 @@ function expandHome(value) {
|
|
|
425
435
|
}
|
|
426
436
|
return value;
|
|
427
437
|
}
|
|
428
|
-
async function ensureAccountRuntime(account, baseCliPath) {
|
|
438
|
+
async function ensureAccountRuntime(account, baseCliPath, deps = {}) {
|
|
439
|
+
const platform2 = deps.platform ?? process.platform;
|
|
429
440
|
const configDir = accountConfigDir(account);
|
|
430
441
|
if (!configDir) return { cliPath: baseCliPath };
|
|
431
442
|
const expandedConfigDir = expandHome(configDir);
|
|
432
443
|
await mkdir(expandedConfigDir, { recursive: true });
|
|
433
444
|
try {
|
|
434
|
-
await ensureSharedCapabilities(expandedConfigDir);
|
|
445
|
+
await ensureSharedCapabilities(expandedConfigDir, platform2);
|
|
435
446
|
} catch (err) {
|
|
436
447
|
log.warn("failed to symlink shared capabilities; continuing anyway", {
|
|
437
448
|
account,
|
|
@@ -439,6 +450,13 @@ async function ensureAccountRuntime(account, baseCliPath) {
|
|
|
439
450
|
error: String(err)
|
|
440
451
|
});
|
|
441
452
|
}
|
|
453
|
+
if (platform2 === "win32") {
|
|
454
|
+
return {
|
|
455
|
+
cliPath: baseCliPath,
|
|
456
|
+
configDir: expandedConfigDir,
|
|
457
|
+
accountInProcess: true
|
|
458
|
+
};
|
|
459
|
+
}
|
|
442
460
|
const cliPath = await writeAccountWrapper(
|
|
443
461
|
normalizeAccountName(account),
|
|
444
462
|
baseCliPath,
|
|
@@ -446,13 +464,21 @@ async function ensureAccountRuntime(account, baseCliPath) {
|
|
|
446
464
|
);
|
|
447
465
|
return { cliPath, configDir: expandedConfigDir };
|
|
448
466
|
}
|
|
449
|
-
async function ensureSharedCapabilities(targetRoot) {
|
|
467
|
+
async function ensureSharedCapabilities(targetRoot, platform2) {
|
|
450
468
|
const sourceRoot = expandHome("~/.claude");
|
|
451
469
|
for (const item of SHARED_CAPABILITY_ITEMS) {
|
|
452
|
-
|
|
470
|
+
try {
|
|
471
|
+
await ensureSharedCapabilityItem(sourceRoot, targetRoot, item, platform2);
|
|
472
|
+
} catch (err) {
|
|
473
|
+
log.warn("could not share a Claude capability with the account", {
|
|
474
|
+
item,
|
|
475
|
+
target: path.join(targetRoot, item),
|
|
476
|
+
error: String(err)
|
|
477
|
+
});
|
|
478
|
+
}
|
|
453
479
|
}
|
|
454
480
|
}
|
|
455
|
-
async function ensureSharedCapabilityItem(sourceRoot, targetRoot, item) {
|
|
481
|
+
async function ensureSharedCapabilityItem(sourceRoot, targetRoot, item, platform2) {
|
|
456
482
|
const source = path.join(sourceRoot, item);
|
|
457
483
|
const target = path.join(targetRoot, item);
|
|
458
484
|
let sourceStat;
|
|
@@ -477,8 +503,29 @@ async function ensureSharedCapabilityItem(sourceRoot, targetRoot, item) {
|
|
|
477
503
|
return;
|
|
478
504
|
} catch {
|
|
479
505
|
}
|
|
480
|
-
|
|
481
|
-
|
|
506
|
+
if (sourceStat.isDirectory()) {
|
|
507
|
+
await symlink(source, target, platform2 === "win32" ? "junction" : "dir");
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (platform2 !== "win32") {
|
|
511
|
+
await symlink(source, target, "file");
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
try {
|
|
515
|
+
await symlink(source, target, "file");
|
|
516
|
+
return;
|
|
517
|
+
} catch {
|
|
518
|
+
}
|
|
519
|
+
try {
|
|
520
|
+
await link(source, target);
|
|
521
|
+
return;
|
|
522
|
+
} catch {
|
|
523
|
+
}
|
|
524
|
+
await copyFile(source, target);
|
|
525
|
+
log.warn(
|
|
526
|
+
"copied a shared Claude capability into the account config dir instead of linking it; later edits to the original will not be picked up",
|
|
527
|
+
{ item, target }
|
|
528
|
+
);
|
|
482
529
|
}
|
|
483
530
|
async function writeAccountWrapper(account, baseCliPath, configDir) {
|
|
484
531
|
const cacheRoot = path.join(
|
|
@@ -944,7 +991,10 @@ function formatUnattendedReplayNote(dropped) {
|
|
|
944
991
|
var UNRECOGNIZED_MODEL_STDERR_MARKER = "[claude-code:unrecognized_model]";
|
|
945
992
|
var MODEL_CLI_FLOORS = {
|
|
946
993
|
// "Added Claude Sonnet 5.5 (`claude-sonnet-5-5`)" (CHANGELOG, 2.1.284).
|
|
947
|
-
"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"
|
|
948
998
|
};
|
|
949
999
|
function parseUnrecognizedModel(stderr) {
|
|
950
1000
|
const at = stderr.indexOf(UNRECOGNIZED_MODEL_STDERR_MARKER);
|
|
@@ -1838,8 +1888,8 @@ function cliSupportsInteractiveBypass(v) {
|
|
|
1838
1888
|
if (!v) return false;
|
|
1839
1889
|
return gte(v, { major: 2, minor: 1, patch: 263 });
|
|
1840
1890
|
}
|
|
1841
|
-
var INTERACTIVE_MEASURED_CLI = "2.1.
|
|
1842
|
-
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 };
|
|
1843
1893
|
function isUnmeasuredInteractiveCli(v) {
|
|
1844
1894
|
if (!v) return false;
|
|
1845
1895
|
return gte(v, INTERACTIVE_MEASURED) && !(v.major === INTERACTIVE_MEASURED.major && v.minor === INTERACTIVE_MEASURED.minor && v.patch === INTERACTIVE_MEASURED.patch);
|
|
@@ -5067,6 +5117,9 @@ function claudeSpawnEnv(opts) {
|
|
|
5067
5117
|
if (opts?.promptCacheTtl) {
|
|
5068
5118
|
env.CLAUDE_CODE_PROMPT_CACHE_TTL = opts.promptCacheTtl;
|
|
5069
5119
|
}
|
|
5120
|
+
if (opts?.configDir) {
|
|
5121
|
+
env.CLAUDE_CONFIG_DIR = opts.configDir;
|
|
5122
|
+
}
|
|
5070
5123
|
if (opts?.ignoreAnthropicApiKey) {
|
|
5071
5124
|
delete env.ANTHROPIC_API_KEY;
|
|
5072
5125
|
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
@@ -5184,6 +5237,11 @@ function getActiveProcess(key) {
|
|
|
5184
5237
|
}
|
|
5185
5238
|
return ap;
|
|
5186
5239
|
}
|
|
5240
|
+
function processBelongsToAnotherAccount(active, cliPath, configDir) {
|
|
5241
|
+
if (!active) return false;
|
|
5242
|
+
if (active.cliPath && active.cliPath !== cliPath) return true;
|
|
5243
|
+
return !!active.configDir && active.configDir !== configDir;
|
|
5244
|
+
}
|
|
5187
5245
|
function setActiveProcess(key, ap) {
|
|
5188
5246
|
cancelIdleProcessEviction(key);
|
|
5189
5247
|
activeProcesses.set(key, ap);
|
|
@@ -5399,7 +5457,7 @@ function invalidateOtherEffortSessions(baseKey, effort) {
|
|
|
5399
5457
|
clearBackgroundTasks(key);
|
|
5400
5458
|
}
|
|
5401
5459
|
}
|
|
5402
|
-
function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcpHash, systemPromptFile, ignoreAnthropicApiKey, effort, promptCacheTtl) {
|
|
5460
|
+
function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcpHash, systemPromptFile, ignoreAnthropicApiKey, effort, promptCacheTtl, configDir) {
|
|
5403
5461
|
evictIfNeeded();
|
|
5404
5462
|
log.info("spawning new claude process", {
|
|
5405
5463
|
cliPath,
|
|
@@ -5407,13 +5465,14 @@ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcp
|
|
|
5407
5465
|
cwd,
|
|
5408
5466
|
sessionKey: sessionKey2,
|
|
5409
5467
|
effort,
|
|
5410
|
-
promptCacheTtl
|
|
5468
|
+
promptCacheTtl,
|
|
5469
|
+
configDir
|
|
5411
5470
|
});
|
|
5412
5471
|
const plan = planClaudeSpawn(cliPath, cliArgs);
|
|
5413
5472
|
const proc = spawn(plan.file, plan.args, {
|
|
5414
5473
|
cwd,
|
|
5415
5474
|
stdio: ["pipe", "pipe", "pipe"],
|
|
5416
|
-
env: claudeSpawnEnv({ ignoreAnthropicApiKey, effort, promptCacheTtl }),
|
|
5475
|
+
env: claudeSpawnEnv({ ignoreAnthropicApiKey, effort, promptCacheTtl, configDir }),
|
|
5417
5476
|
windowsVerbatimArguments: plan.windowsVerbatimArguments
|
|
5418
5477
|
});
|
|
5419
5478
|
const lineEmitter = new EventEmitter3();
|
|
@@ -5428,6 +5487,7 @@ function spawnClaudeProcess(cliPath, cliArgs, cwd, sessionKey2, proxyServer, mcp
|
|
|
5428
5487
|
startedAt: Date.now(),
|
|
5429
5488
|
cliPath,
|
|
5430
5489
|
cliArgs: [...cliArgs],
|
|
5490
|
+
configDir,
|
|
5431
5491
|
unattendedLines: [],
|
|
5432
5492
|
unattendedDropped: 0
|
|
5433
5493
|
};
|
|
@@ -5553,7 +5613,8 @@ function respawnActiveProcess(sessionKey2, cliPath, cliArgs, cwd, ignoreAnthropi
|
|
|
5553
5613
|
old.systemPromptFile,
|
|
5554
5614
|
ignoreAnthropicApiKey,
|
|
5555
5615
|
old.effort,
|
|
5556
|
-
old.promptCacheTtl
|
|
5616
|
+
old.promptCacheTtl,
|
|
5617
|
+
old.configDir
|
|
5557
5618
|
);
|
|
5558
5619
|
replacement.mcpServers = old.mcpServers;
|
|
5559
5620
|
replacement.pendingProxyCompletions = old.pendingProxyCompletions;
|
|
@@ -5984,6 +6045,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
|
|
|
5984
6045
|
"ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN detected; stripping it from claude spawns (ignoreAnthropicApiKey) so requests use your subscription auth, not pay-as-you-go API billing.",
|
|
5985
6046
|
"Code Mode execute not forwarded: no model-visible input schema",
|
|
5986
6047
|
"Code Mode execute not forwarded: read-only permission preset",
|
|
6048
|
+
"MCP server asked for operator input and was declined: a headless Claude Code session cannot prompt, so its elicitation can only be refused. Run that server's flow in Claude Code directly, or configure it not to elicit.",
|
|
5987
6049
|
"MCP tool not routed through the proxy: another proxy tool already holds that name, and it keeps it",
|
|
5988
6050
|
"account failover declined; ending the turn",
|
|
5989
6051
|
"agent effort override refused: unknown level",
|
|
@@ -6020,6 +6082,8 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
|
|
|
6020
6082
|
"cleanup-stale: error processing cache root",
|
|
6021
6083
|
"cleanup-stale: rmSync failed",
|
|
6022
6084
|
"continuing this conversation's claude session under the new model or effort instead of replaying it",
|
|
6085
|
+
"copied a shared Claude capability into the account config dir instead of linking it; later edits to the original will not be picked up",
|
|
6086
|
+
"could not share a Claude capability with the account",
|
|
6023
6087
|
"delivering proxy results after interrupted continuation",
|
|
6024
6088
|
"draining proxy calls queued between turns",
|
|
6025
6089
|
"empty user content; sending sentinel to satisfy CLI",
|
|
@@ -6208,6 +6272,8 @@ var BUNDLE_DATA_ALLOWLIST = {
|
|
|
6208
6272
|
finishReason: "enum",
|
|
6209
6273
|
hash: "enum",
|
|
6210
6274
|
hostApi: "enum",
|
|
6275
|
+
// One of `SHARED_CAPABILITY_ITEMS`: a fixed list this package defines.
|
|
6276
|
+
item: "enum",
|
|
6211
6277
|
kind: "enum",
|
|
6212
6278
|
level: "enum",
|
|
6213
6279
|
mode: "enum",
|
|
@@ -6517,8 +6583,8 @@ function redactLogLine(raw, context) {
|
|
|
6517
6583
|
if (!parsed.data || Object.keys(parsed.data).length === 0) return head;
|
|
6518
6584
|
return `${head} ${JSON.stringify(sanitizeLogData(parsed.data, context))}`;
|
|
6519
6585
|
}
|
|
6520
|
-
function readLogTail(
|
|
6521
|
-
const fd = openSync(
|
|
6586
|
+
function readLogTail(path14, maxBytes = BUNDLE_TAIL_BYTES) {
|
|
6587
|
+
const fd = openSync(path14, "r");
|
|
6522
6588
|
try {
|
|
6523
6589
|
const size = fstatSync(fd).size;
|
|
6524
6590
|
const length = Math.min(size, maxBytes);
|
|
@@ -6698,7 +6764,10 @@ async function fetchPlanUsage(cliPath, options = {}) {
|
|
|
6698
6764
|
if (support && await support(cliPath) === "unsupported") {
|
|
6699
6765
|
return { status: "failed", error: "Plan usage is unavailable: this Claude CLI does not support headless output. Check usage in the Claude CLI directly." };
|
|
6700
6766
|
}
|
|
6701
|
-
const env = claudeSpawnEnv({
|
|
6767
|
+
const env = claudeSpawnEnv({
|
|
6768
|
+
ignoreAnthropicApiKey: options.ignoreAnthropicApiKey,
|
|
6769
|
+
configDir: options.configDir
|
|
6770
|
+
});
|
|
6702
6771
|
const stdout = await run(
|
|
6703
6772
|
cliPath,
|
|
6704
6773
|
["-p", "/cost", "--output-format", "json"],
|
|
@@ -7369,7 +7438,8 @@ async function gatherDoctorReport(options) {
|
|
|
7369
7438
|
});
|
|
7370
7439
|
}
|
|
7371
7440
|
const planUsage = wantsPlanUsage(options.argument ?? "") ? await (options.planUsageImpl ?? fetchPlanUsage)(cliPath, {
|
|
7372
|
-
ignoreAnthropicApiKey: options.ignoreAnthropicApiKey
|
|
7441
|
+
ignoreAnthropicApiKey: options.ignoreAnthropicApiKey,
|
|
7442
|
+
configDir: options.configDir
|
|
7373
7443
|
}) : { status: "not-requested" };
|
|
7374
7444
|
return {
|
|
7375
7445
|
plugin: base.plugin,
|
|
@@ -7526,6 +7596,8 @@ function passthroughModel(id) {
|
|
|
7526
7596
|
};
|
|
7527
7597
|
}
|
|
7528
7598
|
var haikuCost = { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 };
|
|
7599
|
+
var haiku55Cost = { input: 0.1, output: 0.5, cacheRead: 0.01, cacheWrite: 0.125 };
|
|
7600
|
+
var haiku55LongPromptCost = { input: 0.5, output: 2.5, cacheRead: 0.05, cacheWrite: 0.625 };
|
|
7529
7601
|
var sonnetCost = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 };
|
|
7530
7602
|
var sonnet5Cost = { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 };
|
|
7531
7603
|
var opusCost = { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 };
|
|
@@ -7566,6 +7638,9 @@ function toConfigModel(model) {
|
|
|
7566
7638
|
variants: model.variants
|
|
7567
7639
|
};
|
|
7568
7640
|
}
|
|
7641
|
+
var LONG_PROMPT_COSTS = {
|
|
7642
|
+
"claude-haiku-5-5": { aboveTokens: 1e5, cost: haiku55LongPromptCost }
|
|
7643
|
+
};
|
|
7569
7644
|
var defaultModels = {
|
|
7570
7645
|
"claude-haiku-4-5": defineModel({
|
|
7571
7646
|
id: "claude-haiku-4-5",
|
|
@@ -7578,6 +7653,32 @@ var defaultModels = {
|
|
|
7578
7653
|
multiplier: 1,
|
|
7579
7654
|
releaseDate: "2025-10-01"
|
|
7580
7655
|
}),
|
|
7656
|
+
// Haiku 5.5 needs Claude Code 2.1.293+, the release that added it and made
|
|
7657
|
+
// it the default Haiku; an older CLI runs it on fallback limits and says so
|
|
7658
|
+
// on stderr, which `reportUnrecognizedModel` turns into a warning. Unlike
|
|
7659
|
+
// Haiku 4.5 it reasons: the CLI's catalog gives it `effort`, `max_effort`,
|
|
7660
|
+
// `xhigh_effort` and `adaptive_thinking`, and Anthropic's migration note says
|
|
7661
|
+
// manual `budget_tokens` is a 400 and adaptive thinking is on by default. The
|
|
7662
|
+
// plugin never sends `budget_tokens`, so nothing here special-cases it, the
|
|
7663
|
+
// same as Opus 5.5's `rejects_disabled_thinking`. No `-fast` entry: the
|
|
7664
|
+
// catalog gives it no `fast_mode` capability.
|
|
7665
|
+
//
|
|
7666
|
+
// The multiplier is 0.1 because it is exactly a tenth of Haiku 4.5 on input,
|
|
7667
|
+
// output, cache read and cache write alike, and Haiku 4.5's $1/$5 is the 1x
|
|
7668
|
+
// anchor every other entry here is already measured against. Re-anchoring the
|
|
7669
|
+
// whole table on Haiku 5.5 would change every model's displayed number for no
|
|
7670
|
+
// gain in accuracy.
|
|
7671
|
+
"claude-haiku-5-5": defineModel({
|
|
7672
|
+
id: "claude-haiku-5-5",
|
|
7673
|
+
name: "Claude Haiku 5.5",
|
|
7674
|
+
family: "haiku",
|
|
7675
|
+
reasoning: true,
|
|
7676
|
+
context: 1e6,
|
|
7677
|
+
output: 128e3,
|
|
7678
|
+
cost: haiku55Cost,
|
|
7679
|
+
multiplier: 0.1,
|
|
7680
|
+
releaseDate: "2026-10-07"
|
|
7681
|
+
}),
|
|
7581
7682
|
"claude-sonnet-4-5": defineModel({
|
|
7582
7683
|
id: "claude-sonnet-4-5",
|
|
7583
7684
|
name: "Claude Sonnet 4.5",
|
|
@@ -7806,14 +7907,25 @@ function apiCallCostUsd(model, usage) {
|
|
|
7806
7907
|
const base = model.replace(/@.*$/, "").replace(/\[1m\]$/i, "").replace(/-\d{8}$/, "");
|
|
7807
7908
|
const entry = (usage.speed === "fast" ? defaultModels[`${base}${FAST_SUFFIX}`] : void 0) ?? defaultModels[base];
|
|
7808
7909
|
if (!entry) return null;
|
|
7809
|
-
const cost = entry.cost;
|
|
7810
7910
|
const count = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
7911
|
+
const cost = longPromptCost(base, usage, count) ?? entry.cost;
|
|
7811
7912
|
const split = usage.cache_creation;
|
|
7812
7913
|
const write1h = split ? count(split.ephemeral_1h_input_tokens) : 0;
|
|
7813
7914
|
const write5m = split ? count(split.ephemeral_5m_input_tokens) : count(usage.cache_creation_input_tokens);
|
|
7814
7915
|
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;
|
|
7815
7916
|
return perMillion / 1e6 + count(usage.server_tool_use?.web_search_requests) * WEB_SEARCH_USD;
|
|
7816
7917
|
}
|
|
7918
|
+
function longPromptCost(base, usage, count) {
|
|
7919
|
+
const tier = LONG_PROMPT_COSTS[base];
|
|
7920
|
+
if (!tier) return null;
|
|
7921
|
+
const promptTokens = count(usage.input_tokens) + count(usage.cache_read_input_tokens) + count(usage.cache_creation_input_tokens);
|
|
7922
|
+
if (promptTokens <= tier.aboveTokens) return null;
|
|
7923
|
+
return {
|
|
7924
|
+
input: tier.cost.input,
|
|
7925
|
+
output: tier.cost.output,
|
|
7926
|
+
cache: { read: tier.cost.cacheRead, write: tier.cost.cacheWrite }
|
|
7927
|
+
};
|
|
7928
|
+
}
|
|
7817
7929
|
|
|
7818
7930
|
// src/agent-models.ts
|
|
7819
7931
|
var AGENT_DIR_NAMES = ["agents", "agent"];
|
|
@@ -10652,7 +10764,7 @@ function spawnInteractiveProcess(opts) {
|
|
|
10652
10764
|
// src/account-transcript.ts
|
|
10653
10765
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
10654
10766
|
import { constants as fsConstants } from "fs";
|
|
10655
|
-
import { chmod as chmod2, copyFile, lstat as lstat2, mkdir as mkdir2 } from "fs/promises";
|
|
10767
|
+
import { chmod as chmod2, copyFile as copyFile2, lstat as lstat2, mkdir as mkdir2 } from "fs/promises";
|
|
10656
10768
|
import path11 from "path";
|
|
10657
10769
|
var transcriptPath = interactiveTranscriptPath;
|
|
10658
10770
|
function configDirForAccount(account) {
|
|
@@ -10682,7 +10794,7 @@ async function carryTranscriptToAccount(input) {
|
|
|
10682
10794
|
const target = renamed ? transcriptPath({ configDir: targetConfigDir, cwd, sessionId: carriedId }) : targetOwn;
|
|
10683
10795
|
try {
|
|
10684
10796
|
await mkdir2(path11.dirname(target), { recursive: true, mode: 448 });
|
|
10685
|
-
await
|
|
10797
|
+
await copyFile2(live.file, target, fsConstants.COPYFILE_EXCL);
|
|
10686
10798
|
await chmod2(target, 384);
|
|
10687
10799
|
} catch (err) {
|
|
10688
10800
|
return { kind: "refused", reason: "copy-failed", detail: String(err) };
|
|
@@ -10714,9 +10826,9 @@ import { readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "
|
|
|
10714
10826
|
import { homedir as homedir8 } from "os";
|
|
10715
10827
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
10716
10828
|
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join11 } from "path";
|
|
10717
|
-
function readPromptFileIfPresent(
|
|
10829
|
+
function readPromptFileIfPresent(path14) {
|
|
10718
10830
|
try {
|
|
10719
|
-
const content = readFileSync6(
|
|
10831
|
+
const content = readFileSync6(path14, "utf8").trim();
|
|
10720
10832
|
return content || void 0;
|
|
10721
10833
|
} catch {
|
|
10722
10834
|
return void 0;
|
|
@@ -10821,9 +10933,9 @@ function extractSystemMessages(prompt) {
|
|
|
10821
10933
|
}
|
|
10822
10934
|
var CLAUDE_NATIVE_INSTRUCTION_FILES = /* @__PURE__ */ new Set(["CLAUDE.md", "CLAUDE.local.md"]);
|
|
10823
10935
|
function userAuthoredInstructions(forwarded, opts = {}) {
|
|
10824
|
-
const readFile2 = opts.readFile ?? ((
|
|
10936
|
+
const readFile2 = opts.readFile ?? ((path14) => {
|
|
10825
10937
|
try {
|
|
10826
|
-
return readFileSync6(
|
|
10938
|
+
return readFileSync6(path14, "utf8");
|
|
10827
10939
|
} catch {
|
|
10828
10940
|
return null;
|
|
10829
10941
|
}
|
|
@@ -10835,16 +10947,16 @@ function userAuthoredInstructions(forwarded, opts = {}) {
|
|
|
10835
10947
|
}
|
|
10836
10948
|
const seen = /* @__PURE__ */ new Set();
|
|
10837
10949
|
for (const match of forwarded.matchAll(/^Instructions from: (.+)$/gm)) {
|
|
10838
|
-
const
|
|
10839
|
-
if (seen.has(
|
|
10840
|
-
if (CLAUDE_NATIVE_INSTRUCTION_FILES.has(basename2(
|
|
10841
|
-
seen.add(
|
|
10842
|
-
const file = readFile2(
|
|
10950
|
+
const path14 = match[1].trim();
|
|
10951
|
+
if (seen.has(path14) || !isAbsolute3(path14)) continue;
|
|
10952
|
+
if (CLAUDE_NATIVE_INSTRUCTION_FILES.has(basename2(path14))) continue;
|
|
10953
|
+
seen.add(path14);
|
|
10954
|
+
const file = readFile2(path14);
|
|
10843
10955
|
if (!file?.trim()) continue;
|
|
10844
10956
|
const start = match.index + match[0].length + 1;
|
|
10845
10957
|
const content = forwarded.startsWith(file, start) ? file : forwarded.startsWith(file.trimEnd(), start) ? file.trimEnd() : null;
|
|
10846
10958
|
if (content === null) continue;
|
|
10847
|
-
out.push({ text: `Instructions from: ${
|
|
10959
|
+
out.push({ text: `Instructions from: ${path14}
|
|
10848
10960
|
${content}`, content });
|
|
10849
10961
|
}
|
|
10850
10962
|
return out;
|
|
@@ -10881,10 +10993,10 @@ ${options.compressionSummary.trim()}`
|
|
|
10881
10993
|
if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
|
|
10882
10994
|
const content = parts.join("\n\n");
|
|
10883
10995
|
if (!content) return void 0;
|
|
10884
|
-
const
|
|
10996
|
+
const path14 = join11(pluginTmpDir(), `opencode-cc-sys-${randomUUID7()}.md`);
|
|
10885
10997
|
try {
|
|
10886
|
-
writeFileSync6(
|
|
10887
|
-
return
|
|
10998
|
+
writeFileSync6(path14, content, { encoding: "utf8", mode: 384 });
|
|
10999
|
+
return path14;
|
|
10888
11000
|
} catch (err) {
|
|
10889
11001
|
log.warn("failed to write system prompt file", { error: String(err) });
|
|
10890
11002
|
return void 0;
|
|
@@ -11215,6 +11327,8 @@ _Reply with your answer to continue._
|
|
|
11215
11327
|
}
|
|
11216
11328
|
|
|
11217
11329
|
// src/control-request.ts
|
|
11330
|
+
var ELICITATION_DECLINE = { action: "decline" };
|
|
11331
|
+
var warnedElicitationServers = /* @__PURE__ */ new Set();
|
|
11218
11332
|
function controlRequestBehaviorForTool(config, toolName) {
|
|
11219
11333
|
const configured = config.controlRequestToolBehaviors;
|
|
11220
11334
|
if (configured && toolName) {
|
|
@@ -11283,6 +11397,18 @@ function handleControlRequest(config, msg, proc) {
|
|
|
11283
11397
|
}
|
|
11284
11398
|
return true;
|
|
11285
11399
|
}
|
|
11400
|
+
if (request.subtype === "elicitation") {
|
|
11401
|
+
const server2 = request.mcp_server_name || "unknown";
|
|
11402
|
+
writeControlResponse(proc, requestId, { ...ELICITATION_DECLINE });
|
|
11403
|
+
if (!warnedElicitationServers.has(server2)) {
|
|
11404
|
+
warnedElicitationServers.add(server2);
|
|
11405
|
+
log.warn(
|
|
11406
|
+
"MCP server asked for operator input and was declined: a headless Claude Code session cannot prompt, so its elicitation can only be refused. Run that server's flow in Claude Code directly, or configure it not to elicit.",
|
|
11407
|
+
{ server: server2, mode: request.mode ?? "form" }
|
|
11408
|
+
);
|
|
11409
|
+
}
|
|
11410
|
+
return true;
|
|
11411
|
+
}
|
|
11286
11412
|
writeControlResponse(proc, requestId, {});
|
|
11287
11413
|
log.debug("control request acknowledged", {
|
|
11288
11414
|
requestId,
|
|
@@ -12001,14 +12127,18 @@ var FAST_MODE_REASONS = {
|
|
|
12001
12127
|
pending: "the CLI is still checking availability"
|
|
12002
12128
|
};
|
|
12003
12129
|
var warnedFastModeReasons = /* @__PURE__ */ new Set();
|
|
12004
|
-
function reportFastModeState(msg, requested) {
|
|
12130
|
+
function reportFastModeState(msg, requested, phase = "result") {
|
|
12005
12131
|
const state = msg.fast_mode_state;
|
|
12006
12132
|
if (!state) return;
|
|
12007
12133
|
if (!requested) {
|
|
12008
|
-
log.debug("fast mode state", { state });
|
|
12134
|
+
log.debug("fast mode state", { state, phase });
|
|
12009
12135
|
return;
|
|
12010
12136
|
}
|
|
12011
12137
|
if (state === "on") {
|
|
12138
|
+
if (phase === "init") {
|
|
12139
|
+
log.debug("fast mode reported on at session start", { state, phase });
|
|
12140
|
+
return;
|
|
12141
|
+
}
|
|
12012
12142
|
log.info("fast mode active", { state });
|
|
12013
12143
|
return;
|
|
12014
12144
|
}
|
|
@@ -12503,7 +12633,7 @@ function createLineHandler(state, ctx) {
|
|
|
12503
12633
|
claudeSessionId: msg.session_id
|
|
12504
12634
|
});
|
|
12505
12635
|
}
|
|
12506
|
-
reportFastModeState(msg, ctx.fastMode);
|
|
12636
|
+
reportFastModeState(msg, ctx.fastMode, "init");
|
|
12507
12637
|
reportSystemInit(msg, {
|
|
12508
12638
|
ignoreAnthropicApiKey: ctx.config.ignoreAnthropicApiKey
|
|
12509
12639
|
});
|
|
@@ -13069,6 +13199,7 @@ ${plan}
|
|
|
13069
13199
|
}
|
|
13070
13200
|
if (msg.type === "result") {
|
|
13071
13201
|
clearFallbackTimer(state);
|
|
13202
|
+
reportFastModeState(msg, ctx.fastMode, "result");
|
|
13072
13203
|
if (msg.session_id) {
|
|
13073
13204
|
setClaudeSessionId(state.sessionKey, msg.session_id);
|
|
13074
13205
|
}
|
|
@@ -13691,6 +13822,11 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13691
13822
|
interactive: !!useInteractive,
|
|
13692
13823
|
turnStats: this.config.turnStats === true,
|
|
13693
13824
|
ignoreAnthropicApiKey: this.config.ignoreAnthropicApiKey === true,
|
|
13825
|
+
// Where the plugin applies the account itself rather than through a
|
|
13826
|
+
// wrapper (`accountInProcess`, Windows), the `/cost` spawn needs the
|
|
13827
|
+
// same config dir a turn gets, or it reports the default account's
|
|
13828
|
+
// plan usage instead of this provider's.
|
|
13829
|
+
configDir: this.config.accountInProcess ? this.config.configDir : void 0,
|
|
13694
13830
|
// `/claude-code-doctor usage` opts into the CLI's own plan-usage
|
|
13695
13831
|
// report; anything else here is ignored, as it always has been.
|
|
13696
13832
|
argument: doctor.rest
|
|
@@ -13891,11 +14027,12 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13891
14027
|
resetsAt: failoverAnswer.resetsAt
|
|
13892
14028
|
});
|
|
13893
14029
|
}
|
|
14030
|
+
const spawnConfigDir = this.config.accountInProcess ? this.skillBridgeSpawn(failover).configDir : void 0;
|
|
13894
14031
|
const processForAccount = getActiveProcess(sk);
|
|
13895
|
-
if (!compactionMode && processForAccount
|
|
14032
|
+
if (!compactionMode && processBelongsToAnotherAccount(processForAccount, cliPath, spawnConfigDir)) {
|
|
13896
14033
|
log.notice("claude process belongs to another account; starting fresh", {
|
|
13897
14034
|
sessionKey: sk,
|
|
13898
|
-
was: processForAccount
|
|
14035
|
+
was: processForAccount?.cliPath,
|
|
13899
14036
|
now: cliPath,
|
|
13900
14037
|
failedOver: failover.failedOver
|
|
13901
14038
|
});
|
|
@@ -13938,7 +14075,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13938
14075
|
});
|
|
13939
14076
|
deleteClaudeSessionId(sk);
|
|
13940
14077
|
}
|
|
13941
|
-
} else if (!compactionMode && sessionBeforeAccountCheck && processForAccount
|
|
14078
|
+
} else if (!compactionMode && sessionBeforeAccountCheck && processBelongsToAnotherAccount(processForAccount, cliPath, spawnConfigDir)) {
|
|
13942
14079
|
deleteClaudeSessionId(sk);
|
|
13943
14080
|
}
|
|
13944
14081
|
const hasExistingSession = !!getClaudeSessionId(sk);
|
|
@@ -14051,7 +14188,9 @@ var ClaudeCodeLanguageModel = class {
|
|
|
14051
14188
|
if (this.config.forkSessions === true && !compactionMode) {
|
|
14052
14189
|
recordForkFingerprint(sk, options.prompt, cliPath);
|
|
14053
14190
|
}
|
|
14054
|
-
const { model: spawnModelId, fast: fastMode } = parseModelId(
|
|
14191
|
+
const { model: spawnModelId, fast: fastMode } = parseModelId(
|
|
14192
|
+
this.config.accountInProcess ? stripAccountSuffix(failover.modelId) : failover.modelId
|
|
14193
|
+
);
|
|
14055
14194
|
const exitPlanModeQuestionResult = compactionMode ? null : consumeExitPlanModeQuestionResult(sk, options.prompt);
|
|
14056
14195
|
if (exitPlanModeQuestionResult) {
|
|
14057
14196
|
log.info("sending plan approval decision to claude", { sk });
|
|
@@ -14681,7 +14820,8 @@ var ClaudeCodeLanguageModel = class {
|
|
|
14681
14820
|
spawnSystemPromptFile,
|
|
14682
14821
|
self.config.ignoreAnthropicApiKey,
|
|
14683
14822
|
reasoningEffort,
|
|
14684
|
-
promptCacheTtl
|
|
14823
|
+
promptCacheTtl,
|
|
14824
|
+
spawnConfigDir
|
|
14685
14825
|
);
|
|
14686
14826
|
ap.mcpServers = spawnMcpServers;
|
|
14687
14827
|
state.proc = ap.proc;
|
|
@@ -15242,11 +15382,80 @@ var ClaudeCodeLanguageModel = class {
|
|
|
15242
15382
|
}
|
|
15243
15383
|
};
|
|
15244
15384
|
|
|
15385
|
+
// src/account-tier.ts
|
|
15386
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
15387
|
+
import path12 from "path";
|
|
15388
|
+
var ORGANIZATION_TYPE_LABELS = {
|
|
15389
|
+
claude_max: "Max",
|
|
15390
|
+
claude_pro: "Pro",
|
|
15391
|
+
claude_team: "Team",
|
|
15392
|
+
claude_enterprise: "Enterprise",
|
|
15393
|
+
claude_free: "Free"
|
|
15394
|
+
};
|
|
15395
|
+
var RATE_LIMIT_TIER_LABELS = {
|
|
15396
|
+
default_claude_max_5x: "5x",
|
|
15397
|
+
default_claude_max_20x: "20x"
|
|
15398
|
+
};
|
|
15399
|
+
var tierCache = /* @__PURE__ */ new Map();
|
|
15400
|
+
function accountTierConfigDir(account) {
|
|
15401
|
+
const configured = accountConfigDirPath(account);
|
|
15402
|
+
if (configured) return configured;
|
|
15403
|
+
const fromEnv = process.env.CLAUDE_CONFIG_DIR;
|
|
15404
|
+
return fromEnv && fromEnv.trim() ? expandHome(fromEnv.trim()) : expandHome("~/.claude");
|
|
15405
|
+
}
|
|
15406
|
+
function readAccountTier(account) {
|
|
15407
|
+
const normalized = normalizeAccountName(account) || DEFAULT_ACCOUNT;
|
|
15408
|
+
const configDir = accountTierConfigDir(normalized);
|
|
15409
|
+
const cached = tierCache.get(configDir);
|
|
15410
|
+
if (cached !== void 0) return cached ?? void 0;
|
|
15411
|
+
const label = readTierFromConfigDir(configDir, normalized === DEFAULT_ACCOUNT);
|
|
15412
|
+
tierCache.set(configDir, label ?? null);
|
|
15413
|
+
return label;
|
|
15414
|
+
}
|
|
15415
|
+
function readTierFromConfigDir(configDir, allowLegacyFallback) {
|
|
15416
|
+
const candidates = [path12.join(configDir, ".claude.json")];
|
|
15417
|
+
if (allowLegacyFallback) candidates.push(expandHome("~/.claude.json"));
|
|
15418
|
+
for (const candidate of candidates) {
|
|
15419
|
+
const oauth = readOauthAccount(candidate);
|
|
15420
|
+
if (!oauth) continue;
|
|
15421
|
+
const label = tierLabel(oauth);
|
|
15422
|
+
if (label) return label;
|
|
15423
|
+
}
|
|
15424
|
+
return void 0;
|
|
15425
|
+
}
|
|
15426
|
+
function readOauthAccount(file) {
|
|
15427
|
+
let raw;
|
|
15428
|
+
try {
|
|
15429
|
+
raw = readFileSync7(file, "utf8");
|
|
15430
|
+
} catch {
|
|
15431
|
+
return void 0;
|
|
15432
|
+
}
|
|
15433
|
+
try {
|
|
15434
|
+
const parsed = JSON.parse(raw);
|
|
15435
|
+
if (!parsed || typeof parsed !== "object") return void 0;
|
|
15436
|
+
const oauth = parsed.oauthAccount;
|
|
15437
|
+
return oauth && typeof oauth === "object" ? oauth : void 0;
|
|
15438
|
+
} catch {
|
|
15439
|
+
log.debug("could not parse a Claude config file for its plan tier", { file });
|
|
15440
|
+
return void 0;
|
|
15441
|
+
}
|
|
15442
|
+
}
|
|
15443
|
+
function tierLabel(oauthAccount) {
|
|
15444
|
+
const organizationType = oauthAccount.organizationType;
|
|
15445
|
+
if (typeof organizationType !== "string") return void 0;
|
|
15446
|
+
const base = ORGANIZATION_TYPE_LABELS[organizationType];
|
|
15447
|
+
if (!base) return void 0;
|
|
15448
|
+
if (base !== "Max") return base;
|
|
15449
|
+
const rateLimitTier = oauthAccount.organizationRateLimitTier;
|
|
15450
|
+
const multiple = typeof rateLimitTier === "string" ? RATE_LIMIT_TIER_LABELS[rateLimitTier] : void 0;
|
|
15451
|
+
return multiple ? `${base} ${multiple}` : base;
|
|
15452
|
+
}
|
|
15453
|
+
|
|
15245
15454
|
// src/cleanup-stale.ts
|
|
15246
15455
|
import {
|
|
15247
15456
|
existsSync as existsSync5,
|
|
15248
15457
|
mkdirSync as mkdirSync5,
|
|
15249
|
-
readFileSync as
|
|
15458
|
+
readFileSync as readFileSync8,
|
|
15250
15459
|
realpathSync as realpathSync4,
|
|
15251
15460
|
rmSync as rmSync3,
|
|
15252
15461
|
writeFileSync as writeFileSync7
|
|
@@ -15273,7 +15482,7 @@ function userIntendsToUseUnscoped() {
|
|
|
15273
15482
|
const cfg = userOpencodeJsonPath();
|
|
15274
15483
|
if (!existsSync5(cfg)) return false;
|
|
15275
15484
|
try {
|
|
15276
|
-
const json = JSON.parse(
|
|
15485
|
+
const json = JSON.parse(readFileSync8(cfg, "utf8"));
|
|
15277
15486
|
const plugins = json.plugin;
|
|
15278
15487
|
if (!Array.isArray(plugins)) return false;
|
|
15279
15488
|
return plugins.some(
|
|
@@ -15294,7 +15503,7 @@ function ourLoadedDir() {
|
|
|
15294
15503
|
function ourVersion(dir) {
|
|
15295
15504
|
if (!dir) return null;
|
|
15296
15505
|
try {
|
|
15297
|
-
const pkg = JSON.parse(
|
|
15506
|
+
const pkg = JSON.parse(readFileSync8(join13(dir, "package.json"), "utf8"));
|
|
15298
15507
|
return typeof pkg.version === "string" && pkg.version ? pkg.version : null;
|
|
15299
15508
|
} catch {
|
|
15300
15509
|
return null;
|
|
@@ -15306,7 +15515,7 @@ function markerPath() {
|
|
|
15306
15515
|
}
|
|
15307
15516
|
function markerVersion() {
|
|
15308
15517
|
try {
|
|
15309
|
-
const marker = JSON.parse(
|
|
15518
|
+
const marker = JSON.parse(readFileSync8(markerPath(), "utf8"));
|
|
15310
15519
|
return typeof marker.version === "string" ? marker.version : null;
|
|
15311
15520
|
} catch {
|
|
15312
15521
|
return null;
|
|
@@ -15363,7 +15572,7 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
15363
15572
|
if (!existsSync5(pkgJsonPath)) return;
|
|
15364
15573
|
let pkg = {};
|
|
15365
15574
|
try {
|
|
15366
|
-
pkg = JSON.parse(
|
|
15575
|
+
pkg = JSON.parse(readFileSync8(pkgJsonPath, "utf8"));
|
|
15367
15576
|
} catch {
|
|
15368
15577
|
return;
|
|
15369
15578
|
}
|
|
@@ -15382,7 +15591,7 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
15382
15591
|
const cachePkgJson = join13(cacheRoot, "package.json");
|
|
15383
15592
|
if (!existsSync5(cachePkgJson)) return;
|
|
15384
15593
|
try {
|
|
15385
|
-
const cfg = JSON.parse(
|
|
15594
|
+
const cfg = JSON.parse(readFileSync8(cachePkgJson, "utf8"));
|
|
15386
15595
|
if (cfg?.dependencies?.[STALE_PACKAGE_NAME]) {
|
|
15387
15596
|
delete cfg.dependencies[STALE_PACKAGE_NAME];
|
|
15388
15597
|
writeFileSync7(cachePkgJson, JSON.stringify(cfg, null, 2) + "\n");
|
|
@@ -15462,7 +15671,7 @@ function registerOpencodeLogSink(client) {
|
|
|
15462
15671
|
}
|
|
15463
15672
|
|
|
15464
15673
|
// src/v2-client.ts
|
|
15465
|
-
import
|
|
15674
|
+
import path13 from "path";
|
|
15466
15675
|
function formatAgentTypeList(agents) {
|
|
15467
15676
|
const lines = agents.flatMap((agent) => {
|
|
15468
15677
|
if (agent.hidden === true || agent.mode === "primary") return [];
|
|
@@ -15491,7 +15700,7 @@ function toV1Session(info) {
|
|
|
15491
15700
|
if (!info || typeof info !== "object") return void 0;
|
|
15492
15701
|
const session = info;
|
|
15493
15702
|
const root = session.location?.directory;
|
|
15494
|
-
const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ?
|
|
15703
|
+
const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ? path13.resolve(root, session.subpath) : root : void 0;
|
|
15495
15704
|
return { ...info, directory, parentID: session.parentID };
|
|
15496
15705
|
}
|
|
15497
15706
|
function toV1Messages(messages) {
|
|
@@ -15683,7 +15892,7 @@ function planV2Providers(seedSettings, defaultProxyTools) {
|
|
|
15683
15892
|
return {
|
|
15684
15893
|
info: {
|
|
15685
15894
|
id: providerID,
|
|
15686
|
-
name: accountDisplayName(account),
|
|
15895
|
+
name: accountDisplayName(account, readAccountTier(account)),
|
|
15687
15896
|
activation: "enabled",
|
|
15688
15897
|
package: V2_PLUGIN_PACKAGE,
|
|
15689
15898
|
settings: {
|
|
@@ -16221,7 +16430,7 @@ async function expandAccountProviders(config) {
|
|
|
16221
16430
|
// stripped by cleanProviderOptions.
|
|
16222
16431
|
failoverAccounts: accounts
|
|
16223
16432
|
},
|
|
16224
|
-
accountDisplayName(account)
|
|
16433
|
+
accountDisplayName(account, readAccountTier(account))
|
|
16225
16434
|
),
|
|
16226
16435
|
models: configModelsForProvider(
|
|
16227
16436
|
existing?.models ?? seed?.models ?? {},
|