demian-cli 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -3
- package/dist/cli.mjs +146 -3
- package/dist/tui-sidecar/darwin-arm64/demian-tui +0 -0
- package/dist/tui-sidecar/linux-x64/demian-tui +0 -0
- package/dist/tui.mjs +1554 -469
- package/dist/vscode-worker.mjs +1013 -441
- package/docs/ko/README.md +39 -3
- package/package.json +7 -4
package/dist/vscode-worker.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
|
|
2
2
|
|
|
3
3
|
// src/vscode-worker.ts
|
|
4
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
4
5
|
import { mkdir as mkdir16, rm as rm5, writeFile as writeFile15 } from "node:fs/promises";
|
|
5
6
|
import path36 from "node:path";
|
|
6
7
|
|
|
@@ -762,6 +763,24 @@ function isPathLikeKey(key) {
|
|
|
762
763
|
}
|
|
763
764
|
|
|
764
765
|
// src/ui/tui/store.ts
|
|
766
|
+
var TUI_CONFIG_ACTION_PREFIX = "demian-config:";
|
|
767
|
+
var TUI_SESSION_ACTION_PREFIX = "demian-session:";
|
|
768
|
+
function parseTuiConfigAction(prompt) {
|
|
769
|
+
if (!prompt.startsWith(TUI_CONFIG_ACTION_PREFIX)) return void 0;
|
|
770
|
+
try {
|
|
771
|
+
return JSON.parse(prompt.slice(TUI_CONFIG_ACTION_PREFIX.length));
|
|
772
|
+
} catch {
|
|
773
|
+
return void 0;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function parseTuiSessionAction(prompt) {
|
|
777
|
+
if (!prompt.startsWith(TUI_SESSION_ACTION_PREFIX)) return void 0;
|
|
778
|
+
try {
|
|
779
|
+
return JSON.parse(prompt.slice(TUI_SESSION_ACTION_PREFIX.length));
|
|
780
|
+
} catch {
|
|
781
|
+
return void 0;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
765
784
|
var TuiStore = class {
|
|
766
785
|
#state = {
|
|
767
786
|
status: {},
|
|
@@ -770,8 +789,14 @@ var TuiStore = class {
|
|
|
770
789
|
inputMode: "starting",
|
|
771
790
|
sessionOptions: [],
|
|
772
791
|
sessionCursor: 0,
|
|
792
|
+
sessionCancelBehavior: "exit",
|
|
773
793
|
providerOptions: [],
|
|
774
794
|
providerCursor: 0,
|
|
795
|
+
configModelCursor: 0,
|
|
796
|
+
configAddProviderCursor: 0,
|
|
797
|
+
configModelInput: "",
|
|
798
|
+
configProviderInput: "",
|
|
799
|
+
configProviderPresets: defaultConfigProviderPresets(),
|
|
775
800
|
agentOptions: [],
|
|
776
801
|
agentCursor: 0,
|
|
777
802
|
permissionPreset: "auto",
|
|
@@ -807,6 +832,7 @@ var TuiStore = class {
|
|
|
807
832
|
#pendingPermissionTimeout;
|
|
808
833
|
#syntheticPermissionId = 0;
|
|
809
834
|
#activeClaudeCodePlan;
|
|
835
|
+
#notifyTimer;
|
|
810
836
|
snapshot() {
|
|
811
837
|
return {
|
|
812
838
|
...this.#state,
|
|
@@ -814,6 +840,7 @@ var TuiStore = class {
|
|
|
814
840
|
selection: this.#state.selection ? { ...this.#state.selection } : void 0,
|
|
815
841
|
sessionOptions: this.#state.sessionOptions.map((option) => ({ ...option })),
|
|
816
842
|
providerOptions: this.#state.providerOptions.map((option) => ({ ...option })),
|
|
843
|
+
configProviderPresets: this.#state.configProviderPresets.map((option) => ({ ...option })),
|
|
817
844
|
agentOptions: this.#state.agentOptions.map((option) => ({ ...option })),
|
|
818
845
|
blocks: this.#state.blocks.map((block) => ({ ...block, lines: [...block.lines], toolDetails: cloneToolRunDetails(block.toolDetails), goalWork: cloneGoalWork(block.goalWork), cowork: cloneCoworkGroup(block.cowork) })),
|
|
819
846
|
warnings: [...this.#state.warnings],
|
|
@@ -1035,6 +1062,10 @@ var TuiStore = class {
|
|
|
1035
1062
|
agent: context.agent,
|
|
1036
1063
|
cwd: context.cwd
|
|
1037
1064
|
};
|
|
1065
|
+
this.#state.configPath = context.configPath;
|
|
1066
|
+
this.#state.configCreated = context.configCreated;
|
|
1067
|
+
this.#state.configDefaultProvider = context.configDefaultProvider;
|
|
1068
|
+
this.#state.configMessage = context.configMessage;
|
|
1038
1069
|
if (context.contextEfficiency) {
|
|
1039
1070
|
this.#state.contextEfficiency = {
|
|
1040
1071
|
...this.#state.contextEfficiency ?? { selectedTools: [] },
|
|
@@ -1066,7 +1097,7 @@ var TuiStore = class {
|
|
|
1066
1097
|
currentPermissionPreset() {
|
|
1067
1098
|
return this.#state.permissionPreset;
|
|
1068
1099
|
}
|
|
1069
|
-
requestSessionSelection(options2) {
|
|
1100
|
+
requestSessionSelection(options2, behavior = {}) {
|
|
1070
1101
|
if (this.#exitRequested) return Promise.resolve({ kind: "exit" });
|
|
1071
1102
|
const normalized = options2.length > 0 ? options2.map((option) => ({ ...option })) : [{ kind: "new", title: "New session", status: "ready", currentWorkspace: true }];
|
|
1072
1103
|
return new Promise((resolve) => {
|
|
@@ -1074,6 +1105,7 @@ var TuiStore = class {
|
|
|
1074
1105
|
this.#state.inputMode = "session";
|
|
1075
1106
|
this.#state.sessionOptions = normalized;
|
|
1076
1107
|
this.#state.sessionCursor = 0;
|
|
1108
|
+
this.#state.sessionCancelBehavior = behavior.cancel ?? "exit";
|
|
1077
1109
|
this.#state.promptInput = "";
|
|
1078
1110
|
this.#state.promptError = void 0;
|
|
1079
1111
|
this.#state.streamingText = "";
|
|
@@ -1083,6 +1115,14 @@ var TuiStore = class {
|
|
|
1083
1115
|
this.#notify();
|
|
1084
1116
|
});
|
|
1085
1117
|
}
|
|
1118
|
+
cancelSessionSelection() {
|
|
1119
|
+
if (this.#state.inputMode !== "session") return false;
|
|
1120
|
+
if (this.#state.sessionCancelBehavior === "prompt") {
|
|
1121
|
+
this.#resolveSession({ kind: "cancel" });
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1124
|
+
return true;
|
|
1125
|
+
}
|
|
1086
1126
|
moveSessionCursor(delta) {
|
|
1087
1127
|
if (this.#state.inputMode !== "session" || this.#state.sessionOptions.length === 0) return;
|
|
1088
1128
|
const count = this.#state.sessionOptions.length;
|
|
@@ -1099,10 +1139,20 @@ var TuiStore = class {
|
|
|
1099
1139
|
}
|
|
1100
1140
|
if (option.id) this.#resolveSession({ kind: "session", id: option.id });
|
|
1101
1141
|
}
|
|
1142
|
+
submitDeleteSessionSelection() {
|
|
1143
|
+
if (this.#state.inputMode !== "session") return;
|
|
1144
|
+
const option = this.#state.sessionOptions[this.#state.sessionCursor];
|
|
1145
|
+
if (!option || option.kind === "new" || !option.id) return;
|
|
1146
|
+
this.#resolveSession({ kind: "delete", id: option.id });
|
|
1147
|
+
}
|
|
1102
1148
|
submitNewSessionSelection() {
|
|
1103
1149
|
if (this.#state.inputMode !== "session") return;
|
|
1104
1150
|
this.#resolveSession({ kind: "new" });
|
|
1105
1151
|
}
|
|
1152
|
+
submitSessionSelectionShortcut() {
|
|
1153
|
+
if (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim()) return;
|
|
1154
|
+
this.#resolvePrompt(`${TUI_SESSION_ACTION_PREFIX}${JSON.stringify({ type: "select" })}`);
|
|
1155
|
+
}
|
|
1106
1156
|
requestPrompt(initialPrompt) {
|
|
1107
1157
|
if (this.#exitRequested) return Promise.resolve("");
|
|
1108
1158
|
const prompt = (initialPrompt ?? "").trim();
|
|
@@ -1200,6 +1250,143 @@ var TuiStore = class {
|
|
|
1200
1250
|
this.#state.activity = `provider ${option.name} selected`;
|
|
1201
1251
|
this.#notify();
|
|
1202
1252
|
}
|
|
1253
|
+
openConfigManager(message) {
|
|
1254
|
+
const canOpenFromConfig = this.#state.inputMode === "config" || this.#state.inputMode === "configModel" || this.#state.inputMode === "configModelInput" || this.#state.inputMode === "configProviderInput" || this.#state.inputMode === "configAddProvider";
|
|
1255
|
+
if (!canOpenFromConfig && (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim())) return;
|
|
1256
|
+
this.#state.inputMode = "config";
|
|
1257
|
+
const current = this.#state.selection?.providerName ?? this.#state.status.provider;
|
|
1258
|
+
this.#state.providerCursor = Math.max(0, this.#state.providerOptions.findIndex((option) => option.name === current));
|
|
1259
|
+
this.#state.configMessage = message ?? this.#state.configMessage;
|
|
1260
|
+
this.#state.settingsError = void 0;
|
|
1261
|
+
this.#state.activity = "configure providers and models";
|
|
1262
|
+
this.#notify();
|
|
1263
|
+
}
|
|
1264
|
+
openConfigProviderInput(field) {
|
|
1265
|
+
if (this.#state.inputMode !== "config") return;
|
|
1266
|
+
const provider = this.#selectedConfigProvider();
|
|
1267
|
+
if (!provider) return;
|
|
1268
|
+
this.#state.inputMode = "configProviderInput";
|
|
1269
|
+
this.#state.configProviderInputField = field;
|
|
1270
|
+
this.#state.configProviderInput = configProviderFieldValue(provider, field);
|
|
1271
|
+
this.#state.settingsError = void 0;
|
|
1272
|
+
this.#state.activity = `edit ${field} for ${provider.name}`;
|
|
1273
|
+
this.#notify();
|
|
1274
|
+
}
|
|
1275
|
+
appendConfigProviderInput(input2) {
|
|
1276
|
+
if (this.#state.inputMode !== "configProviderInput") return;
|
|
1277
|
+
this.#state.configProviderInput += input2;
|
|
1278
|
+
this.#state.settingsError = void 0;
|
|
1279
|
+
this.#notify();
|
|
1280
|
+
}
|
|
1281
|
+
backspaceConfigProviderInput() {
|
|
1282
|
+
if (this.#state.inputMode !== "configProviderInput") return;
|
|
1283
|
+
this.#state.configProviderInput = Array.from(this.#state.configProviderInput).slice(0, -1).join("");
|
|
1284
|
+
this.#state.settingsError = void 0;
|
|
1285
|
+
this.#notify();
|
|
1286
|
+
}
|
|
1287
|
+
submitConfigProviderInput() {
|
|
1288
|
+
if (this.#state.inputMode !== "configProviderInput") return;
|
|
1289
|
+
const provider = this.#selectedConfigProvider();
|
|
1290
|
+
const field = this.#state.configProviderInputField;
|
|
1291
|
+
if (!provider || !field) return;
|
|
1292
|
+
this.#submitConfigAction({ type: "updateProvider", provider: provider.name, field, value: this.#state.configProviderInput });
|
|
1293
|
+
}
|
|
1294
|
+
moveConfigProviderCursor(delta) {
|
|
1295
|
+
if (this.#state.inputMode !== "config" || this.#state.providerOptions.length === 0) return;
|
|
1296
|
+
const count = this.#state.providerOptions.length;
|
|
1297
|
+
this.#state.providerCursor = (this.#state.providerCursor + delta + count) % count;
|
|
1298
|
+
this.#notify();
|
|
1299
|
+
}
|
|
1300
|
+
submitConfigDefaultProvider() {
|
|
1301
|
+
if (this.#state.inputMode !== "config") return;
|
|
1302
|
+
const option = this.#selectedConfigProvider();
|
|
1303
|
+
if (!option) return;
|
|
1304
|
+
this.#submitConfigAction({ type: "setDefaultProvider", provider: option.name });
|
|
1305
|
+
}
|
|
1306
|
+
openConfigModelSelector() {
|
|
1307
|
+
if (this.#state.inputMode !== "config" && this.#state.inputMode !== "configModelInput") return;
|
|
1308
|
+
const option = this.#selectedConfigProvider();
|
|
1309
|
+
if (!option) return;
|
|
1310
|
+
this.#state.inputMode = "configModel";
|
|
1311
|
+
this.#state.configModelCursor = 0;
|
|
1312
|
+
this.#state.settingsError = void 0;
|
|
1313
|
+
this.#state.activity = `choose default model for ${option.name}`;
|
|
1314
|
+
this.#notify();
|
|
1315
|
+
}
|
|
1316
|
+
moveConfigModelCursor(delta) {
|
|
1317
|
+
if (this.#state.inputMode !== "configModel") return;
|
|
1318
|
+
const profiles = this.#selectedConfigProvider()?.modelProfiles ?? [];
|
|
1319
|
+
if (profiles.length === 0) return;
|
|
1320
|
+
this.#state.configModelCursor = (this.#state.configModelCursor + delta + profiles.length) % profiles.length;
|
|
1321
|
+
this.#notify();
|
|
1322
|
+
}
|
|
1323
|
+
submitConfigDefaultModel() {
|
|
1324
|
+
if (this.#state.inputMode !== "configModel") return;
|
|
1325
|
+
const provider = this.#selectedConfigProvider();
|
|
1326
|
+
const profile = provider?.modelProfiles?.[this.#state.configModelCursor];
|
|
1327
|
+
if (!provider || !profile?.model) return;
|
|
1328
|
+
this.#submitConfigAction({
|
|
1329
|
+
type: "setDefaultModel",
|
|
1330
|
+
provider: provider.name,
|
|
1331
|
+
profileName: profile.name,
|
|
1332
|
+
name: profile.name,
|
|
1333
|
+
displayName: profile.displayName,
|
|
1334
|
+
model: profile.model
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
openConfigModelInput() {
|
|
1338
|
+
if (this.#state.inputMode !== "configModel" && this.#state.inputMode !== "config") return;
|
|
1339
|
+
if (!this.#selectedConfigProvider()) return;
|
|
1340
|
+
this.#state.inputMode = "configModelInput";
|
|
1341
|
+
this.#state.configModelInput = "";
|
|
1342
|
+
this.#state.settingsError = void 0;
|
|
1343
|
+
this.#state.activity = "add a model profile";
|
|
1344
|
+
this.#notify();
|
|
1345
|
+
}
|
|
1346
|
+
appendConfigModelInput(input2) {
|
|
1347
|
+
if (this.#state.inputMode !== "configModelInput") return;
|
|
1348
|
+
this.#state.configModelInput += input2;
|
|
1349
|
+
this.#state.settingsError = void 0;
|
|
1350
|
+
this.#notify();
|
|
1351
|
+
}
|
|
1352
|
+
backspaceConfigModelInput() {
|
|
1353
|
+
if (this.#state.inputMode !== "configModelInput") return;
|
|
1354
|
+
this.#state.configModelInput = Array.from(this.#state.configModelInput).slice(0, -1).join("");
|
|
1355
|
+
this.#state.settingsError = void 0;
|
|
1356
|
+
this.#notify();
|
|
1357
|
+
}
|
|
1358
|
+
submitConfigModelInput() {
|
|
1359
|
+
if (this.#state.inputMode !== "configModelInput") return;
|
|
1360
|
+
const provider = this.#selectedConfigProvider();
|
|
1361
|
+
const model = this.#state.configModelInput.trim();
|
|
1362
|
+
if (!provider || !model) {
|
|
1363
|
+
this.#state.settingsError = "Model ID is required.";
|
|
1364
|
+
this.#notify();
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
this.#submitConfigAction({ type: "setDefaultModel", provider: provider.name, model, displayName: model });
|
|
1368
|
+
}
|
|
1369
|
+
openConfigAddProviderSelector() {
|
|
1370
|
+
if (this.#state.inputMode !== "config") return;
|
|
1371
|
+
this.#state.inputMode = "configAddProvider";
|
|
1372
|
+
this.#state.configAddProviderCursor = 0;
|
|
1373
|
+
this.#state.settingsError = void 0;
|
|
1374
|
+
this.#state.activity = "add provider preset";
|
|
1375
|
+
this.#notify();
|
|
1376
|
+
}
|
|
1377
|
+
moveConfigAddProviderCursor(delta) {
|
|
1378
|
+
if (this.#state.inputMode !== "configAddProvider") return;
|
|
1379
|
+
const count = this.#state.configProviderPresets.length;
|
|
1380
|
+
if (count === 0) return;
|
|
1381
|
+
this.#state.configAddProviderCursor = (this.#state.configAddProviderCursor + delta + count) % count;
|
|
1382
|
+
this.#notify();
|
|
1383
|
+
}
|
|
1384
|
+
submitConfigAddProvider() {
|
|
1385
|
+
if (this.#state.inputMode !== "configAddProvider") return;
|
|
1386
|
+
const preset = this.#state.configProviderPresets[this.#state.configAddProviderCursor];
|
|
1387
|
+
if (!preset) return;
|
|
1388
|
+
this.#submitConfigAction({ type: "addProvider", preset: preset.preset, name: preset.name, apiKeyEnv: preset.apiKeyEnv });
|
|
1389
|
+
}
|
|
1203
1390
|
openAgentSelector() {
|
|
1204
1391
|
if (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim()) return;
|
|
1205
1392
|
if (this.#state.agentOptions.length === 0) {
|
|
@@ -1298,9 +1485,12 @@ var TuiStore = class {
|
|
|
1298
1485
|
this.#notify();
|
|
1299
1486
|
}
|
|
1300
1487
|
closeSettings() {
|
|
1301
|
-
if (
|
|
1488
|
+
if (!["provider", "model", "agent", "permissionPreset", "config", "configModel", "configModelInput", "configProviderInput", "configAddProvider"].includes(this.#state.inputMode)) return;
|
|
1302
1489
|
this.#state.inputMode = "prompt";
|
|
1303
1490
|
this.#state.modelInput = "";
|
|
1491
|
+
this.#state.configModelInput = "";
|
|
1492
|
+
this.#state.configProviderInput = "";
|
|
1493
|
+
this.#state.configProviderInputField = void 0;
|
|
1304
1494
|
this.#state.settingsError = void 0;
|
|
1305
1495
|
this.#state.activity = "waiting for first message";
|
|
1306
1496
|
this.#notify();
|
|
@@ -1429,9 +1619,12 @@ var TuiStore = class {
|
|
|
1429
1619
|
this.#state.streamingFinalized = false;
|
|
1430
1620
|
this.#activeClaudeCodePlan = this.#isClaudeCodePlanRun(event.provider) ? { text: "", providerName: event.provider, model: event.model, createdAt: event.ts } : void 0;
|
|
1431
1621
|
}
|
|
1432
|
-
if (event.type === "model.text.delta"
|
|
1622
|
+
if (event.type === "model.text.delta") {
|
|
1623
|
+
if (isNestedInvocationEvent(event)) return;
|
|
1433
1624
|
this.#state.streamingText += event.text;
|
|
1434
1625
|
this.#state.streamingFinalized = false;
|
|
1626
|
+
this.#notify({ deferMs: 50 });
|
|
1627
|
+
return;
|
|
1435
1628
|
}
|
|
1436
1629
|
if (event.type === "model.text" && !isNestedInvocationEvent(event)) {
|
|
1437
1630
|
this.#state.streamingText = event.text;
|
|
@@ -2145,14 +2338,28 @@ var TuiStore = class {
|
|
|
2145
2338
|
resolve(prompt);
|
|
2146
2339
|
this.#notify();
|
|
2147
2340
|
}
|
|
2341
|
+
#selectedConfigProvider() {
|
|
2342
|
+
return this.#state.providerOptions[this.#state.providerCursor];
|
|
2343
|
+
}
|
|
2344
|
+
#submitConfigAction(action) {
|
|
2345
|
+
this.#state.settingsError = void 0;
|
|
2346
|
+
this.#state.configModelInput = "";
|
|
2347
|
+
this.#state.configProviderInput = "";
|
|
2348
|
+
this.#state.configProviderInputField = void 0;
|
|
2349
|
+
this.#resolvePrompt(`${TUI_CONFIG_ACTION_PREFIX}${JSON.stringify(action)}`);
|
|
2350
|
+
}
|
|
2148
2351
|
#resolveSession(selection) {
|
|
2149
2352
|
const resolve = this.#sessionResolve;
|
|
2150
2353
|
if (!resolve) return;
|
|
2151
2354
|
this.#sessionResolve = void 0;
|
|
2355
|
+
this.#state.sessionCancelBehavior = "exit";
|
|
2152
2356
|
if (selection.kind === "exit") {
|
|
2153
2357
|
this.#state.inputMode = "done";
|
|
2154
2358
|
this.#state.done = true;
|
|
2155
2359
|
this.#state.activity = "exiting";
|
|
2360
|
+
} else if (selection.kind === "cancel") {
|
|
2361
|
+
this.#state.inputMode = "prompt";
|
|
2362
|
+
this.#state.activity = "waiting for next message";
|
|
2156
2363
|
} else {
|
|
2157
2364
|
this.#state.inputMode = "starting";
|
|
2158
2365
|
this.#state.activity = selection.kind === "new" ? "creating session" : "opening session";
|
|
@@ -2264,7 +2471,23 @@ var TuiStore = class {
|
|
|
2264
2471
|
}
|
|
2265
2472
|
};
|
|
2266
2473
|
}
|
|
2267
|
-
#notify() {
|
|
2474
|
+
#notify(options2 = {}) {
|
|
2475
|
+
const deferMs = options2.deferMs ?? 0;
|
|
2476
|
+
if (deferMs > 0) {
|
|
2477
|
+
if (this.#notifyTimer) return;
|
|
2478
|
+
this.#notifyTimer = setTimeout(() => {
|
|
2479
|
+
this.#notifyTimer = void 0;
|
|
2480
|
+
this.#emitNotify();
|
|
2481
|
+
}, deferMs);
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
2484
|
+
if (this.#notifyTimer) {
|
|
2485
|
+
clearTimeout(this.#notifyTimer);
|
|
2486
|
+
this.#notifyTimer = void 0;
|
|
2487
|
+
}
|
|
2488
|
+
this.#emitNotify();
|
|
2489
|
+
}
|
|
2490
|
+
#emitNotify() {
|
|
2268
2491
|
for (const listener of this.#listeners) listener();
|
|
2269
2492
|
}
|
|
2270
2493
|
};
|
|
@@ -2427,6 +2650,25 @@ function cloneContextEfficiency(context) {
|
|
|
2427
2650
|
lastCompaction: context.lastCompaction ? { ...context.lastCompaction } : void 0
|
|
2428
2651
|
};
|
|
2429
2652
|
}
|
|
2653
|
+
function configProviderFieldValue(provider, field) {
|
|
2654
|
+
if (field === "baseURL") return provider.baseURL ?? "";
|
|
2655
|
+
if (field === "apiKeyEnv") return provider.apiKeyEnv ?? "";
|
|
2656
|
+
return provider.auth?.header ?? "";
|
|
2657
|
+
}
|
|
2658
|
+
function defaultConfigProviderPresets() {
|
|
2659
|
+
return [
|
|
2660
|
+
{ label: "OpenAI", preset: "openai", name: "openai", apiKeyEnv: "OPENAI_API_KEY", detail: "OpenAI API" },
|
|
2661
|
+
{ label: "Anthropic", preset: "anthropic", name: "anthropic", apiKeyEnv: "ANTHROPIC_API_KEY", detail: "Anthropic API" },
|
|
2662
|
+
{ label: "Gemini", preset: "gemini", name: "gemini", apiKeyEnv: "GEMINI_API_KEY", detail: "Google Gemini OpenAI-compatible API" },
|
|
2663
|
+
{ label: "Groq", preset: "groq", name: "groq", apiKeyEnv: "GROQ_API_KEY", detail: "Groq Cloud" },
|
|
2664
|
+
{ label: "Codex", preset: "codex", name: "codex", detail: "ChatGPT Codex OAuth" },
|
|
2665
|
+
{ label: "Claude Code", preset: "claudecode", name: "claudecode", detail: "Local Claude Code runtime" },
|
|
2666
|
+
{ label: "Ollama local", preset: "ollama-local", name: "ollama-local", detail: "http://localhost:11434" },
|
|
2667
|
+
{ label: "LM Studio", preset: "lmstudio", name: "lmstudio", detail: "http://localhost:1234" },
|
|
2668
|
+
{ label: "llama.cpp", preset: "llamacpp", name: "llamacpp", detail: "http://localhost:8080" },
|
|
2669
|
+
{ label: "vLLM", preset: "vllm", name: "vllm", detail: "http://localhost:8000" }
|
|
2670
|
+
];
|
|
2671
|
+
}
|
|
2430
2672
|
function cloneToolRunDetails(details) {
|
|
2431
2673
|
if (!details) return void 0;
|
|
2432
2674
|
return {
|
|
@@ -2527,7 +2769,8 @@ function buildClaudeCodePlanExecutionPrompt(planText, requestText) {
|
|
|
2527
2769
|
}
|
|
2528
2770
|
|
|
2529
2771
|
// src/ui/tui/controller.ts
|
|
2530
|
-
import
|
|
2772
|
+
import path34 from "node:path";
|
|
2773
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
2531
2774
|
|
|
2532
2775
|
// src/agents/types.ts
|
|
2533
2776
|
function normalizeAgent(input2) {
|
|
@@ -26966,10 +27209,380 @@ function claudeCodeRuntimePolicyHash(config) {
|
|
|
26966
27209
|
}
|
|
26967
27210
|
var CLAUDE_CODE_PERMISSION_PROFILE_KEYS = ["permissionMode", "defaultDecision", "allowedTools", "disallowedTools", "tools", "allowSubagents"];
|
|
26968
27211
|
|
|
26969
|
-
// src/
|
|
26970
|
-
import { mkdir as mkdir6,
|
|
27212
|
+
// src/config-scaffold.ts
|
|
27213
|
+
import { chmod as chmod3, mkdir as mkdir6, readFile as readFile7, rename as rename4, stat as stat3, writeFile as writeFile5 } from "node:fs/promises";
|
|
26971
27214
|
import os8 from "node:os";
|
|
26972
27215
|
import path14 from "node:path";
|
|
27216
|
+
function defaultUserConfigPath() {
|
|
27217
|
+
return path14.join(os8.homedir(), ".demian", "config.json");
|
|
27218
|
+
}
|
|
27219
|
+
function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
|
|
27220
|
+
return {
|
|
27221
|
+
version: 2,
|
|
27222
|
+
defaultProvider,
|
|
27223
|
+
providers: {
|
|
27224
|
+
openai: {
|
|
27225
|
+
type: "openai-compatible",
|
|
27226
|
+
baseURL: "https://api.openai.com/v1",
|
|
27227
|
+
apiKey: "",
|
|
27228
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
27229
|
+
catalog: { type: "openai-models", endpoint: "https://api.openai.com/v1/models" }
|
|
27230
|
+
},
|
|
27231
|
+
anthropic: {
|
|
27232
|
+
type: "anthropic",
|
|
27233
|
+
baseURL: "https://api.anthropic.com/v1",
|
|
27234
|
+
apiKey: "",
|
|
27235
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
27236
|
+
catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
|
|
27237
|
+
},
|
|
27238
|
+
gemini: {
|
|
27239
|
+
type: "openai-compatible",
|
|
27240
|
+
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
27241
|
+
apiKey: "",
|
|
27242
|
+
apiKeyEnv: "GEMINI_API_KEY",
|
|
27243
|
+
apiKeyEnvAliases: ["GOOGLE_API_KEY"],
|
|
27244
|
+
catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
|
|
27245
|
+
},
|
|
27246
|
+
groq: {
|
|
27247
|
+
type: "openai-compatible",
|
|
27248
|
+
baseURL: "https://api.groq.com/openai/v1",
|
|
27249
|
+
apiKey: "",
|
|
27250
|
+
apiKeyEnv: "GROQ_API_KEY",
|
|
27251
|
+
catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
|
|
27252
|
+
},
|
|
27253
|
+
azure: {
|
|
27254
|
+
type: "openai-compatible",
|
|
27255
|
+
auth: { type: "api-key", header: "api-key" },
|
|
27256
|
+
modelProfiles: [
|
|
27257
|
+
{
|
|
27258
|
+
name: "azure-example",
|
|
27259
|
+
displayName: "Azure example",
|
|
27260
|
+
model: "azure-deployment-name",
|
|
27261
|
+
baseURL: "https://example.openai.azure.com/openai/v1",
|
|
27262
|
+
apiKey: "",
|
|
27263
|
+
apiKeyEnv: "AZURE_OPENAI_API_KEY"
|
|
27264
|
+
}
|
|
27265
|
+
]
|
|
27266
|
+
},
|
|
27267
|
+
lmstudio: {
|
|
27268
|
+
type: "openai-compatible",
|
|
27269
|
+
baseURL: "http://localhost:1234/v1",
|
|
27270
|
+
apiKey: "lm-studio",
|
|
27271
|
+
modelProfiles: [],
|
|
27272
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
|
|
27273
|
+
},
|
|
27274
|
+
"ollama-local": {
|
|
27275
|
+
type: "openai-compatible",
|
|
27276
|
+
baseURL: "http://localhost:11434/v1",
|
|
27277
|
+
apiKey: "ollama",
|
|
27278
|
+
modelProfiles: [],
|
|
27279
|
+
quirks: { omitTemperature: true },
|
|
27280
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
|
|
27281
|
+
},
|
|
27282
|
+
"ollama-cloud": {
|
|
27283
|
+
type: "ollama",
|
|
27284
|
+
baseURL: "https://ollama.com/api",
|
|
27285
|
+
apiKey: "",
|
|
27286
|
+
apiKeyEnv: "OLLAMA_API_KEY",
|
|
27287
|
+
modelProfiles: [],
|
|
27288
|
+
catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
|
|
27289
|
+
},
|
|
27290
|
+
llamacpp: {
|
|
27291
|
+
type: "openai-compatible",
|
|
27292
|
+
baseURL: "http://localhost:8080/v1",
|
|
27293
|
+
apiKey: "llama.cpp",
|
|
27294
|
+
modelProfiles: [],
|
|
27295
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
|
|
27296
|
+
},
|
|
27297
|
+
vllm: {
|
|
27298
|
+
type: "openai-compatible",
|
|
27299
|
+
baseURL: "http://localhost:8000/v1",
|
|
27300
|
+
apiKey: "vllm",
|
|
27301
|
+
modelProfiles: [],
|
|
27302
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
|
|
27303
|
+
},
|
|
27304
|
+
codex: {
|
|
27305
|
+
type: "codex",
|
|
27306
|
+
baseURL: "https://chatgpt.com/backend-api/codex",
|
|
27307
|
+
authStore: "auto",
|
|
27308
|
+
allowApiKeyFallback: false,
|
|
27309
|
+
promptCacheKey: "root-session",
|
|
27310
|
+
catalog: { type: "codex-oauth-models" }
|
|
27311
|
+
},
|
|
27312
|
+
claudecode: {
|
|
27313
|
+
type: "claudecode",
|
|
27314
|
+
runtime: "agent-sdk",
|
|
27315
|
+
cliPath: "~/.local/bin/claude",
|
|
27316
|
+
cwdMode: "session",
|
|
27317
|
+
historyPolicy: "passthrough-resume",
|
|
27318
|
+
onInvalidResume: "fresh",
|
|
27319
|
+
attachmentFallback: "block",
|
|
27320
|
+
allowSubagents: false,
|
|
27321
|
+
sanitizeApiKeyEnv: true,
|
|
27322
|
+
authPreflight: true,
|
|
27323
|
+
useBareMode: false,
|
|
27324
|
+
usageLedgerScope: "process",
|
|
27325
|
+
sessionLock: true,
|
|
27326
|
+
abortPolicy: "record-only",
|
|
27327
|
+
catalog: { type: "claudecode-supported-models" }
|
|
27328
|
+
}
|
|
27329
|
+
}
|
|
27330
|
+
};
|
|
27331
|
+
}
|
|
27332
|
+
async function createUserConfig(options2 = {}) {
|
|
27333
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
27334
|
+
const content = `${JSON.stringify(defaultUserConfig(options2.defaultProvider), null, 2)}
|
|
27335
|
+
`;
|
|
27336
|
+
if (options2.print) return { path: filePath, created: false, content };
|
|
27337
|
+
const existed = await exists(filePath);
|
|
27338
|
+
if (existed && !options2.force) return { path: filePath, created: false, content: await readFile7(filePath, "utf8"), existed: true };
|
|
27339
|
+
await writeJsonAtomic(filePath, content);
|
|
27340
|
+
return { path: filePath, created: true, content, existed };
|
|
27341
|
+
}
|
|
27342
|
+
async function addProvider(options2) {
|
|
27343
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
27344
|
+
const config = await readConfigObject(filePath);
|
|
27345
|
+
const providers = objectValue(config.providers);
|
|
27346
|
+
const name = options2.name;
|
|
27347
|
+
if (providers[name] && !options2.force) throw new Error(`Provider ${name} already exists. Use --force to overwrite it.`);
|
|
27348
|
+
providers[name] = providerPreset(options2);
|
|
27349
|
+
config.providers = providers;
|
|
27350
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
27351
|
+
`;
|
|
27352
|
+
await writeJsonAtomic(filePath, content);
|
|
27353
|
+
return { path: filePath, created: true, content };
|
|
27354
|
+
}
|
|
27355
|
+
async function addModelProfile(options2) {
|
|
27356
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
27357
|
+
const config = await readConfigObject(filePath);
|
|
27358
|
+
const providers = objectValue(config.providers);
|
|
27359
|
+
const provider = objectValue(providers[options2.provider]);
|
|
27360
|
+
const profiles = Array.isArray(provider.modelProfiles) ? [...provider.modelProfiles] : [];
|
|
27361
|
+
const displayName = options2.displayName ?? options2.name;
|
|
27362
|
+
const previousName = normalizeOptionalName(options2.previousName);
|
|
27363
|
+
const existingByPreviousName = previousName ? profiles.findIndex((entry) => entry.name === previousName) : -1;
|
|
27364
|
+
const existingByName = profiles.findIndex((entry) => entry.name === options2.name);
|
|
27365
|
+
const existingByDisplay = profiles.findIndex((entry) => entry.displayName === displayName);
|
|
27366
|
+
const updateIndex = existingByPreviousName >= 0 ? existingByPreviousName : existingByName;
|
|
27367
|
+
if (existingByName >= 0 && existingByName !== updateIndex) throw new Error(`Profile name "${options2.name}" already exists on provider ${options2.provider}. Pick a different profile name.`);
|
|
27368
|
+
if (existingByName >= 0 && existingByPreviousName < 0 && !options2.force) throw new Error(`Profile name "${options2.name}" already exists on provider ${options2.provider}. Use --force to overwrite it.`);
|
|
27369
|
+
if (existingByDisplay >= 0 && existingByDisplay !== updateIndex && !options2.force) {
|
|
27370
|
+
throw new Error(`Profile displayName "${displayName}" already exists on provider ${options2.provider} (profile name: ${profiles[existingByDisplay].name}). Use --force or pick a different --display-name.`);
|
|
27371
|
+
}
|
|
27372
|
+
const next = {
|
|
27373
|
+
name: options2.name,
|
|
27374
|
+
displayName,
|
|
27375
|
+
model: options2.model,
|
|
27376
|
+
...options2.baseURL ? { baseURL: options2.baseURL } : {},
|
|
27377
|
+
...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
27378
|
+
...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
|
|
27379
|
+
};
|
|
27380
|
+
if (updateIndex >= 0) profiles[updateIndex] = next;
|
|
27381
|
+
else profiles.push(next);
|
|
27382
|
+
provider.modelProfiles = profiles;
|
|
27383
|
+
providers[options2.provider] = provider;
|
|
27384
|
+
config.providers = providers;
|
|
27385
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
27386
|
+
`;
|
|
27387
|
+
await writeJsonAtomic(filePath, content);
|
|
27388
|
+
return { path: filePath, created: true, content };
|
|
27389
|
+
}
|
|
27390
|
+
async function updateConfigDefaults(options2) {
|
|
27391
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
27392
|
+
const config = await readConfigObject(filePath);
|
|
27393
|
+
const defaultProvider = normalizeOptionalName(options2.defaultProvider);
|
|
27394
|
+
const defaultAgent = normalizeOptionalName(options2.defaultAgent);
|
|
27395
|
+
if (!defaultProvider && !defaultAgent) throw new Error("At least one of defaultProvider or defaultAgent is required.");
|
|
27396
|
+
if (defaultProvider) config.defaultProvider = defaultProvider;
|
|
27397
|
+
if (defaultAgent) config.defaultAgent = defaultAgent;
|
|
27398
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
27399
|
+
`;
|
|
27400
|
+
await writeJsonAtomic(filePath, content);
|
|
27401
|
+
return { path: filePath, created: true, content };
|
|
27402
|
+
}
|
|
27403
|
+
async function setDefaultModelProfile(options2) {
|
|
27404
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
27405
|
+
const config = await readConfigObject(filePath);
|
|
27406
|
+
const providers = objectValue(config.providers);
|
|
27407
|
+
const provider = providers[options2.provider];
|
|
27408
|
+
if (!provider || typeof provider !== "object" || Array.isArray(provider)) throw new Error(`Provider ${options2.provider} does not exist.`);
|
|
27409
|
+
const providerConfig = { ...provider };
|
|
27410
|
+
const profiles = Array.isArray(providerConfig.modelProfiles) ? [...providerConfig.modelProfiles] : [];
|
|
27411
|
+
const requestedName = normalizeOptionalName(options2.profileName ?? options2.name);
|
|
27412
|
+
const requestedModel = normalizeOptionalName(options2.model);
|
|
27413
|
+
const requestedDisplayName = normalizeOptionalName(options2.displayName) ?? requestedModel ?? requestedName;
|
|
27414
|
+
let index = profiles.findIndex((entry) => requestedName && entry.name === requestedName);
|
|
27415
|
+
if (index < 0 && requestedModel) index = profiles.findIndex((entry) => entry.model === requestedModel);
|
|
27416
|
+
if (index < 0 && requestedDisplayName) index = profiles.findIndex((entry) => entry.displayName === requestedDisplayName);
|
|
27417
|
+
const profile = index >= 0 ? {
|
|
27418
|
+
...profiles[index],
|
|
27419
|
+
...requestedName ? { name: requestedName } : {},
|
|
27420
|
+
...requestedDisplayName ? { displayName: requestedDisplayName } : {},
|
|
27421
|
+
...requestedModel ? { model: requestedModel } : {},
|
|
27422
|
+
...options2.baseURL ? { baseURL: options2.baseURL } : {},
|
|
27423
|
+
...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
27424
|
+
...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
|
|
27425
|
+
} : newModelProfile({
|
|
27426
|
+
name: requestedName,
|
|
27427
|
+
displayName: requestedDisplayName,
|
|
27428
|
+
model: requestedModel ?? requestedName,
|
|
27429
|
+
baseURL: options2.baseURL,
|
|
27430
|
+
apiKey: options2.apiKey,
|
|
27431
|
+
apiKeyEnv: options2.apiKeyEnv
|
|
27432
|
+
});
|
|
27433
|
+
if (!profile.name || typeof profile.name !== "string") throw new Error("Model profile name is required.");
|
|
27434
|
+
if (!profile.model || typeof profile.model !== "string") throw new Error("Model ID is required.");
|
|
27435
|
+
if (index >= 0) profiles.splice(index, 1);
|
|
27436
|
+
profiles.unshift(profile);
|
|
27437
|
+
providerConfig.modelProfiles = profiles;
|
|
27438
|
+
providers[options2.provider] = providerConfig;
|
|
27439
|
+
config.providers = providers;
|
|
27440
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
27441
|
+
`;
|
|
27442
|
+
await writeJsonAtomic(filePath, content);
|
|
27443
|
+
return { path: filePath, created: true, content };
|
|
27444
|
+
}
|
|
27445
|
+
async function updateProviderSettings(options2) {
|
|
27446
|
+
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
27447
|
+
const config = await readConfigObject(filePath);
|
|
27448
|
+
const providers = objectValue(config.providers);
|
|
27449
|
+
const provider = providers[options2.provider];
|
|
27450
|
+
if (!provider || typeof provider !== "object" || Array.isArray(provider)) throw new Error(`Provider ${options2.provider} does not exist.`);
|
|
27451
|
+
const providerConfig = { ...provider };
|
|
27452
|
+
if (options2.baseURL !== void 0) setOrDelete(providerConfig, "baseURL", options2.baseURL);
|
|
27453
|
+
if (options2.apiKey !== void 0) setOrDelete(providerConfig, "apiKey", options2.apiKey);
|
|
27454
|
+
if (options2.apiKeyEnv !== void 0) setOrDelete(providerConfig, "apiKeyEnv", options2.apiKeyEnv);
|
|
27455
|
+
if (options2.authHeader !== void 0) {
|
|
27456
|
+
const header = options2.authHeader.trim();
|
|
27457
|
+
if (header) providerConfig.auth = { type: "api-key", header };
|
|
27458
|
+
else delete providerConfig.auth;
|
|
27459
|
+
}
|
|
27460
|
+
providers[options2.provider] = providerConfig;
|
|
27461
|
+
config.providers = providers;
|
|
27462
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
27463
|
+
`;
|
|
27464
|
+
await writeJsonAtomic(filePath, content);
|
|
27465
|
+
return { path: filePath, created: true, content };
|
|
27466
|
+
}
|
|
27467
|
+
async function readConfigObject(filePath) {
|
|
27468
|
+
if (!await exists(filePath)) await createUserConfig({ path: filePath });
|
|
27469
|
+
const raw = JSON.parse(await readFile7(filePath, "utf8"));
|
|
27470
|
+
raw.version ??= 2;
|
|
27471
|
+
raw.providers = objectValue(raw.providers);
|
|
27472
|
+
return raw;
|
|
27473
|
+
}
|
|
27474
|
+
function providerPreset(options2) {
|
|
27475
|
+
const preset = options2.preset ?? options2.name;
|
|
27476
|
+
const auth = apiKeyAuthFields(options2);
|
|
27477
|
+
const openAIAuth = options2.authHeader ? { auth: { type: "api-key", header: options2.authHeader } } : {};
|
|
27478
|
+
if (preset === "openai") return { type: "openai-compatible", baseURL: options2.baseURL ?? "https://api.openai.com/v1", ...apiKeyAuthFields(options2, "OPENAI_API_KEY"), ...openAIAuth, catalog: { type: "openai-models", endpoint: `${(options2.baseURL ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/models` } };
|
|
27479
|
+
if (preset === "anthropic") return { type: "anthropic", baseURL: options2.baseURL ?? "https://api.anthropic.com/v1", ...apiKeyAuthFields(options2, "ANTHROPIC_API_KEY"), catalog: { type: "anthropic-models", endpoint: `${(options2.baseURL ?? "https://api.anthropic.com/v1").replace(/\/+$/, "")}/models` } };
|
|
27480
|
+
if (preset === "gemini") {
|
|
27481
|
+
const geminiBase = options2.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
|
|
27482
|
+
return { type: "openai-compatible", baseURL: geminiBase, ...apiKeyAuthFields(options2, "GEMINI_API_KEY"), apiKeyEnvAliases: ["GOOGLE_API_KEY"], ...openAIAuth, catalog: { type: "gemini-openai-models", endpoint: `${geminiBase.replace(/\/+$/, "")}/models` } };
|
|
27483
|
+
}
|
|
27484
|
+
if (preset === "groq") return { type: "openai-compatible", baseURL: options2.baseURL ?? "https://api.groq.com/openai/v1", ...apiKeyAuthFields(options2, "GROQ_API_KEY"), ...openAIAuth, catalog: { type: "groq-models", endpoint: `${(options2.baseURL ?? "https://api.groq.com/openai/v1").replace(/\/+$/, "")}/models` } };
|
|
27485
|
+
if (preset === "azure") {
|
|
27486
|
+
return {
|
|
27487
|
+
type: "openai-compatible",
|
|
27488
|
+
auth: { type: "api-key", header: options2.authHeader ?? "api-key" },
|
|
27489
|
+
...auth,
|
|
27490
|
+
modelProfiles: [
|
|
27491
|
+
{
|
|
27492
|
+
name: "azure-example",
|
|
27493
|
+
displayName: "Azure example",
|
|
27494
|
+
model: "azure-deployment-name",
|
|
27495
|
+
baseURL: options2.baseURL ?? "https://example.openai.azure.com/openai/v1",
|
|
27496
|
+
...options2.apiKey ? {} : { apiKey: "" },
|
|
27497
|
+
apiKeyEnv: options2.apiKeyEnv ?? "AZURE_OPENAI_API_KEY"
|
|
27498
|
+
}
|
|
27499
|
+
]
|
|
27500
|
+
};
|
|
27501
|
+
}
|
|
27502
|
+
if (preset === "lmstudio") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:1234/v1", apiKey: options2.apiKey ?? "lm-studio", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:1234/v1").replace(/\/+$/, "")}/models` } };
|
|
27503
|
+
if (preset === "ollama-local" || preset === "ollama") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:11434/v1", apiKey: options2.apiKey ?? "ollama", modelProfiles: [], quirks: { omitTemperature: true }, catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:11434/v1").replace(/\/+$/, "")}/models` } };
|
|
27504
|
+
if (preset === "ollama-cloud") return { type: "ollama", baseURL: options2.baseURL ?? "https://ollama.com/api", ...apiKeyAuthFields(options2, "OLLAMA_API_KEY"), modelProfiles: [], catalog: { type: "ollama-tags", endpoint: `${(options2.baseURL ?? "https://ollama.com/api").replace(/\/+$/, "")}/tags` } };
|
|
27505
|
+
if (preset === "llamacpp") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:8080/v1", apiKey: options2.apiKey ?? "llama.cpp", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:8080/v1").replace(/\/+$/, "")}/models` } };
|
|
27506
|
+
if (preset === "vllm") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:8000/v1", apiKey: options2.apiKey ?? "vllm", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:8000/v1").replace(/\/+$/, "")}/models` } };
|
|
27507
|
+
if (preset === "codex") return { type: "codex", baseURL: options2.baseURL ?? "https://chatgpt.com/backend-api/codex", authStore: "auto", allowApiKeyFallback: false, promptCacheKey: "root-session", catalog: { type: "codex-oauth-models" } };
|
|
27508
|
+
if (preset === "claudecode") return { type: "claudecode", runtime: "agent-sdk", cliPath: "~/.local/bin/claude", cwdMode: "session", historyPolicy: "passthrough-resume", onInvalidResume: "fresh", attachmentFallback: "block", allowSubagents: false, sanitizeApiKeyEnv: true, authPreflight: true, useBareMode: false, usageLedgerScope: "process", sessionLock: true, abortPolicy: "record-only", catalog: { type: "claudecode-supported-models" } };
|
|
27509
|
+
if ((options2.type ?? preset) === "openai-compatible") {
|
|
27510
|
+
const baseURL = options2.baseURL;
|
|
27511
|
+
if (!baseURL) throw new Error("openai-compatible provider requires --base-url.");
|
|
27512
|
+
return {
|
|
27513
|
+
type: "openai-compatible",
|
|
27514
|
+
baseURL,
|
|
27515
|
+
...auth,
|
|
27516
|
+
...openAIAuth,
|
|
27517
|
+
catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
|
|
27518
|
+
};
|
|
27519
|
+
}
|
|
27520
|
+
throw new Error(`Unknown provider preset: ${preset}`);
|
|
27521
|
+
}
|
|
27522
|
+
function apiKeyAuthFields(options2, defaultApiKeyEnv) {
|
|
27523
|
+
const apiKeyEnv = options2.apiKeyEnv ?? defaultApiKeyEnv;
|
|
27524
|
+
return {
|
|
27525
|
+
...options2.apiKey !== void 0 || apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
27526
|
+
...apiKeyEnv ? { apiKeyEnv } : {}
|
|
27527
|
+
};
|
|
27528
|
+
}
|
|
27529
|
+
async function writeJsonAtomic(filePath, content) {
|
|
27530
|
+
await mkdir6(path14.dirname(filePath), { recursive: true, mode: 448 });
|
|
27531
|
+
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
27532
|
+
await writeFile5(temp, content, { mode: 384 });
|
|
27533
|
+
await rename4(temp, filePath);
|
|
27534
|
+
await chmod3(filePath, 384).catch(() => void 0);
|
|
27535
|
+
}
|
|
27536
|
+
function detectDefaultProvider() {
|
|
27537
|
+
if (process.env.OPENAI_API_KEY) return "openai";
|
|
27538
|
+
if (process.env.ANTHROPIC_API_KEY) return "anthropic";
|
|
27539
|
+
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) return "gemini";
|
|
27540
|
+
if (process.env.GROQ_API_KEY) return "groq";
|
|
27541
|
+
return "openai";
|
|
27542
|
+
}
|
|
27543
|
+
function objectValue(value) {
|
|
27544
|
+
return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
|
|
27545
|
+
}
|
|
27546
|
+
function normalizeOptionalName(value) {
|
|
27547
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
27548
|
+
}
|
|
27549
|
+
function newModelProfile(options2) {
|
|
27550
|
+
const model = options2.model?.trim();
|
|
27551
|
+
if (!model) throw new Error("Model ID is required.");
|
|
27552
|
+
const name = options2.name?.trim() || modelProfileNameFromModel(model);
|
|
27553
|
+
return {
|
|
27554
|
+
name,
|
|
27555
|
+
displayName: options2.displayName?.trim() || name,
|
|
27556
|
+
model,
|
|
27557
|
+
...options2.baseURL ? { baseURL: options2.baseURL } : {},
|
|
27558
|
+
...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
27559
|
+
...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
|
|
27560
|
+
};
|
|
27561
|
+
}
|
|
27562
|
+
function modelProfileNameFromModel(model) {
|
|
27563
|
+
return model.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "model";
|
|
27564
|
+
}
|
|
27565
|
+
function setOrDelete(target, key, value) {
|
|
27566
|
+
const trimmed = value.trim();
|
|
27567
|
+
if (trimmed) target[key] = trimmed;
|
|
27568
|
+
else delete target[key];
|
|
27569
|
+
}
|
|
27570
|
+
async function exists(filePath) {
|
|
27571
|
+
try {
|
|
27572
|
+
await stat3(filePath);
|
|
27573
|
+
return true;
|
|
27574
|
+
} catch {
|
|
27575
|
+
return false;
|
|
27576
|
+
}
|
|
27577
|
+
}
|
|
27578
|
+
function expandHome2(value) {
|
|
27579
|
+
return resolveExpandedPath(value);
|
|
27580
|
+
}
|
|
27581
|
+
|
|
27582
|
+
// src/transcript.ts
|
|
27583
|
+
import { mkdir as mkdir7, appendFile, writeFile as writeFile6 } from "node:fs/promises";
|
|
27584
|
+
import os9 from "node:os";
|
|
27585
|
+
import path15 from "node:path";
|
|
26973
27586
|
var TranscriptWriter = class {
|
|
26974
27587
|
filePath;
|
|
26975
27588
|
#enabled;
|
|
@@ -26977,9 +27590,9 @@ var TranscriptWriter = class {
|
|
|
26977
27590
|
#queue = Promise.resolve();
|
|
26978
27591
|
constructor(options2) {
|
|
26979
27592
|
this.#enabled = options2.enabled !== false;
|
|
26980
|
-
const dir =
|
|
26981
|
-
this.filePath =
|
|
26982
|
-
this.#ready = this.#enabled ?
|
|
27593
|
+
const dir = path15.join(options2.storageDir ?? defaultDemianStorageDir(), "transcripts", options2.sessionId);
|
|
27594
|
+
this.filePath = path15.join(dir, "session.jsonl");
|
|
27595
|
+
this.#ready = this.#enabled ? mkdir7(dir, { recursive: true }).then(() => writeFile6(this.filePath, "", { flag: "a" })) : Promise.resolve();
|
|
26983
27596
|
}
|
|
26984
27597
|
write(event) {
|
|
26985
27598
|
if (!this.#enabled) return Promise.resolve();
|
|
@@ -26995,14 +27608,14 @@ var TranscriptWriter = class {
|
|
|
26995
27608
|
}
|
|
26996
27609
|
};
|
|
26997
27610
|
function defaultDemianStorageDir() {
|
|
26998
|
-
return
|
|
27611
|
+
return path15.join(os9.homedir(), ".demian");
|
|
26999
27612
|
}
|
|
27000
27613
|
|
|
27001
27614
|
// src/external-runtime/snapshot-diff.ts
|
|
27002
27615
|
import { createHash as createHash2 } from "node:crypto";
|
|
27003
27616
|
import { createReadStream } from "node:fs";
|
|
27004
|
-
import { readdir, readFile as
|
|
27005
|
-
import
|
|
27617
|
+
import { readdir, readFile as readFile8, stat as stat4 } from "node:fs/promises";
|
|
27618
|
+
import path16 from "node:path";
|
|
27006
27619
|
|
|
27007
27620
|
// src/workspace/diff.ts
|
|
27008
27621
|
var CONTEXT_LINES = 3;
|
|
@@ -27212,7 +27825,7 @@ async function diffWorkspaceSnapshot(before) {
|
|
|
27212
27825
|
}
|
|
27213
27826
|
async function walk(root, relativeDir, entries, options2) {
|
|
27214
27827
|
if (entries.size >= options2.maxFiles) return;
|
|
27215
|
-
const absoluteDir =
|
|
27828
|
+
const absoluteDir = path16.join(root, relativeDir);
|
|
27216
27829
|
let items;
|
|
27217
27830
|
try {
|
|
27218
27831
|
items = await readdir(absoluteDir, { withFileTypes: true });
|
|
@@ -27223,8 +27836,8 @@ async function walk(root, relativeDir, entries, options2) {
|
|
|
27223
27836
|
if (entries.size >= options2.maxFiles) return;
|
|
27224
27837
|
if (item.name.startsWith(".") && item.name !== ".env.example" && SKIP_DIRS.has(item.name)) continue;
|
|
27225
27838
|
if (SKIP_DIRS.has(item.name)) continue;
|
|
27226
|
-
const relativePath = normalizeRelativePath(
|
|
27227
|
-
const absolutePath =
|
|
27839
|
+
const relativePath = normalizeRelativePath(path16.join(relativeDir, item.name));
|
|
27840
|
+
const absolutePath = path16.join(root, relativePath);
|
|
27228
27841
|
if (item.isDirectory()) {
|
|
27229
27842
|
await walk(root, relativePath, entries, options2);
|
|
27230
27843
|
continue;
|
|
@@ -27235,7 +27848,7 @@ async function walk(root, relativeDir, entries, options2) {
|
|
|
27235
27848
|
}
|
|
27236
27849
|
}
|
|
27237
27850
|
async function snapshotFile(filePath, relativePath, maxTextBytes) {
|
|
27238
|
-
const info = await
|
|
27851
|
+
const info = await stat4(filePath);
|
|
27239
27852
|
const sha2562 = await sha256File(filePath);
|
|
27240
27853
|
const content = info.size <= maxTextBytes ? await readTextContent(filePath) : void 0;
|
|
27241
27854
|
return { path: relativePath, size: info.size, sha256: sha2562, content };
|
|
@@ -27246,7 +27859,7 @@ function snapshotDiffText(entry) {
|
|
|
27246
27859
|
`;
|
|
27247
27860
|
}
|
|
27248
27861
|
async function readTextContent(filePath) {
|
|
27249
|
-
const buffer = await
|
|
27862
|
+
const buffer = await readFile8(filePath);
|
|
27250
27863
|
if (buffer.includes(0)) return void 0;
|
|
27251
27864
|
return buffer.toString("utf8");
|
|
27252
27865
|
}
|
|
@@ -27260,7 +27873,7 @@ function sha256File(filePath) {
|
|
|
27260
27873
|
});
|
|
27261
27874
|
}
|
|
27262
27875
|
function normalizeRelativePath(value) {
|
|
27263
|
-
return value.split(
|
|
27876
|
+
return value.split(path16.sep).join("/");
|
|
27264
27877
|
}
|
|
27265
27878
|
|
|
27266
27879
|
// src/external-runtime/session-runner.ts
|
|
@@ -27695,7 +28308,7 @@ function safeJson(value) {
|
|
|
27695
28308
|
}
|
|
27696
28309
|
|
|
27697
28310
|
// src/hooks/dispatcher.ts
|
|
27698
|
-
import
|
|
28311
|
+
import path18 from "node:path";
|
|
27699
28312
|
|
|
27700
28313
|
// src/hooks/command.ts
|
|
27701
28314
|
import { spawn as spawn4 } from "node:child_process";
|
|
@@ -27788,7 +28401,7 @@ var blockDangerousBashHook = {
|
|
|
27788
28401
|
};
|
|
27789
28402
|
|
|
27790
28403
|
// src/hooks/builtin/protect-env-files.ts
|
|
27791
|
-
import
|
|
28404
|
+
import path17 from "node:path";
|
|
27792
28405
|
var protectEnvFilesHook = {
|
|
27793
28406
|
name: "protect-env-files",
|
|
27794
28407
|
event: "PreToolUse",
|
|
@@ -27796,7 +28409,7 @@ var protectEnvFilesHook = {
|
|
|
27796
28409
|
run(ctx) {
|
|
27797
28410
|
if (ctx.toolName !== "write_file" && ctx.toolName !== "edit_file") return { decision: "allow" };
|
|
27798
28411
|
const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
|
|
27799
|
-
const filePath = typeof input2.path === "string" ?
|
|
28412
|
+
const filePath = typeof input2.path === "string" ? path17.resolve(ctx.cwd, input2.path) : "";
|
|
27800
28413
|
if (filePath && isEnvFile(filePath)) {
|
|
27801
28414
|
return {
|
|
27802
28415
|
decision: "block",
|
|
@@ -27833,7 +28446,7 @@ var maskSecretsHook = {
|
|
|
27833
28446
|
};
|
|
27834
28447
|
|
|
27835
28448
|
// src/hooks/builtin/inject-env-info.ts
|
|
27836
|
-
import
|
|
28449
|
+
import os10 from "node:os";
|
|
27837
28450
|
var injectEnvInfoHook = {
|
|
27838
28451
|
name: "inject-env-info",
|
|
27839
28452
|
event: "SessionStart",
|
|
@@ -27842,7 +28455,7 @@ var injectEnvInfoHook = {
|
|
|
27842
28455
|
decision: "allow",
|
|
27843
28456
|
patch: {
|
|
27844
28457
|
systemNote: [
|
|
27845
|
-
`Environment: ${
|
|
28458
|
+
`Environment: ${os10.type()} ${os10.release()} (${os10.platform()}/${os10.arch()})`,
|
|
27846
28459
|
`cwd: ${ctx.cwd}`,
|
|
27847
28460
|
`provider: ${ctx.provider ?? "unknown"}`,
|
|
27848
28461
|
`model: ${ctx.model ?? "unknown"}`,
|
|
@@ -27911,14 +28524,14 @@ function matchesHook(match, ctx) {
|
|
|
27911
28524
|
const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
|
|
27912
28525
|
const filePath = typeof input2.path === "string" ? input2.path : typeof input2.workdir === "string" ? input2.workdir : void 0;
|
|
27913
28526
|
if (!filePath) return false;
|
|
27914
|
-
if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd,
|
|
28527
|
+
if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd, path18.resolve(ctx.cwd, filePath)))) return false;
|
|
27915
28528
|
}
|
|
27916
28529
|
return true;
|
|
27917
28530
|
}
|
|
27918
28531
|
|
|
27919
28532
|
// src/multimodal.ts
|
|
27920
|
-
import { readFile as
|
|
27921
|
-
import
|
|
28533
|
+
import { readFile as readFile9, stat as stat5 } from "node:fs/promises";
|
|
28534
|
+
import path19 from "node:path";
|
|
27922
28535
|
var DEFAULT_MAX_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
27923
28536
|
async function buildUserContent(prompt, images = [], options2) {
|
|
27924
28537
|
if (images.length === 0) return prompt;
|
|
@@ -27937,16 +28550,16 @@ async function buildUserContent(prompt, images = [], options2) {
|
|
|
27937
28550
|
async function imageToUrl(input2, options2) {
|
|
27938
28551
|
if (/^https?:\/\//i.test(input2) || /^data:image\//i.test(input2)) return input2;
|
|
27939
28552
|
const filePath = resolveInsideCwd(options2.cwd, input2);
|
|
27940
|
-
const info = await
|
|
28553
|
+
const info = await stat5(filePath);
|
|
27941
28554
|
if (!info.isFile()) throw new Error(`Image input is not a file: ${input2}`);
|
|
27942
28555
|
const maxImageBytes = options2.maxImageBytes ?? DEFAULT_MAX_IMAGE_BYTES;
|
|
27943
28556
|
if (info.size > maxImageBytes) throw new Error(`Image is larger than ${maxImageBytes} bytes: ${input2}`);
|
|
27944
28557
|
const mime = mimeFromPath(filePath);
|
|
27945
|
-
const bytes = await
|
|
28558
|
+
const bytes = await readFile9(filePath);
|
|
27946
28559
|
return `data:${mime};base64,${bytes.toString("base64")}`;
|
|
27947
28560
|
}
|
|
27948
28561
|
function mimeFromPath(filePath) {
|
|
27949
|
-
switch (
|
|
28562
|
+
switch (path19.extname(filePath).toLowerCase()) {
|
|
27950
28563
|
case ".jpg":
|
|
27951
28564
|
case ".jpeg":
|
|
27952
28565
|
return "image/jpeg";
|
|
@@ -27957,15 +28570,15 @@ function mimeFromPath(filePath) {
|
|
|
27957
28570
|
case ".webp":
|
|
27958
28571
|
return "image/webp";
|
|
27959
28572
|
default:
|
|
27960
|
-
throw new Error(`Unsupported image extension: ${
|
|
28573
|
+
throw new Error(`Unsupported image extension: ${path19.extname(filePath) || "(none)"}`);
|
|
27961
28574
|
}
|
|
27962
28575
|
}
|
|
27963
28576
|
|
|
27964
28577
|
// src/permissions/persistent-grants.ts
|
|
27965
|
-
import { mkdir as
|
|
28578
|
+
import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile7 } from "node:fs/promises";
|
|
27966
28579
|
import fs8 from "node:fs";
|
|
27967
|
-
import
|
|
27968
|
-
import
|
|
28580
|
+
import os11 from "node:os";
|
|
28581
|
+
import path20 from "node:path";
|
|
27969
28582
|
var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
27970
28583
|
var PersistentGrantStore = class {
|
|
27971
28584
|
filePath;
|
|
@@ -27996,22 +28609,22 @@ var PersistentGrantStore = class {
|
|
|
27996
28609
|
}
|
|
27997
28610
|
async #read() {
|
|
27998
28611
|
if (!fs8.existsSync(this.filePath)) return { version: 1, grants: [] };
|
|
27999
|
-
const data = JSON.parse(await
|
|
28612
|
+
const data = JSON.parse(await readFile10(this.filePath, "utf8"));
|
|
28000
28613
|
return {
|
|
28001
28614
|
version: 1,
|
|
28002
28615
|
grants: Array.isArray(data.grants) ? data.grants.filter(isGrantRecord) : []
|
|
28003
28616
|
};
|
|
28004
28617
|
}
|
|
28005
28618
|
async #write(file) {
|
|
28006
|
-
await
|
|
28007
|
-
await
|
|
28619
|
+
await mkdir8(path20.dirname(this.filePath), { recursive: true });
|
|
28620
|
+
await writeFile7(this.filePath, `${JSON.stringify(file, null, 2)}
|
|
28008
28621
|
`, { mode: 384 });
|
|
28009
28622
|
}
|
|
28010
28623
|
};
|
|
28011
28624
|
function resolveGrantPath(cwd, config) {
|
|
28012
|
-
if (config.path) return
|
|
28013
|
-
if ((config.scope ?? "project") === "user") return
|
|
28014
|
-
return
|
|
28625
|
+
if (config.path) return path20.resolve(cwd, config.path);
|
|
28626
|
+
if ((config.scope ?? "project") === "user") return path20.join(os11.homedir(), ".demian", "grants.json");
|
|
28627
|
+
return path20.join(cwd, ".demian", "grants.json");
|
|
28015
28628
|
}
|
|
28016
28629
|
function isGrantRecord(value) {
|
|
28017
28630
|
if (!value || typeof value !== "object") return false;
|
|
@@ -28444,25 +29057,25 @@ function fail(content, metadata) {
|
|
|
28444
29057
|
}
|
|
28445
29058
|
|
|
28446
29059
|
// src/tools/output.ts
|
|
28447
|
-
import { mkdir as
|
|
28448
|
-
import
|
|
29060
|
+
import { mkdir as mkdir9, writeFile as writeFile8 } from "node:fs/promises";
|
|
29061
|
+
import path21 from "node:path";
|
|
28449
29062
|
var DEFAULT_CAP_BYTES = 32 * 1024;
|
|
28450
29063
|
var HALF_PREVIEW_BYTES = 16 * 1024;
|
|
28451
29064
|
async function capToolOutput(result, options2) {
|
|
28452
29065
|
const capBytes = options2.capBytes ?? DEFAULT_CAP_BYTES;
|
|
28453
29066
|
const bytes = Buffer.byteLength(result.content, "utf8");
|
|
28454
29067
|
if (bytes <= capBytes) return result;
|
|
28455
|
-
const dir =
|
|
28456
|
-
await
|
|
28457
|
-
const outputPath =
|
|
28458
|
-
await
|
|
29068
|
+
const dir = path21.join(options2.cwd, ".demian", "tmp");
|
|
29069
|
+
await mkdir9(dir, { recursive: true });
|
|
29070
|
+
const outputPath = path21.join(dir, `output-${safeCallId(options2.callId)}.txt`);
|
|
29071
|
+
await writeFile8(outputPath, result.content, "utf8");
|
|
28459
29072
|
return {
|
|
28460
29073
|
...result,
|
|
28461
29074
|
content: [
|
|
28462
29075
|
sliceUtf8(result.content, 0, HALF_PREVIEW_BYTES),
|
|
28463
29076
|
`
|
|
28464
29077
|
|
|
28465
|
-
[Full output saved to ${
|
|
29078
|
+
[Full output saved to ${path21.relative(options2.cwd, outputPath)}; showing head and tail because output exceeded ${capBytes} bytes]
|
|
28466
29079
|
|
|
28467
29080
|
`,
|
|
28468
29081
|
sliceUtf8(result.content, Math.max(0, bytes - HALF_PREVIEW_BYTES), bytes)
|
|
@@ -28487,8 +29100,8 @@ function sliceUtf8(text, startByte, endByte) {
|
|
|
28487
29100
|
}
|
|
28488
29101
|
|
|
28489
29102
|
// src/tools/read-file.ts
|
|
28490
|
-
import { open as open2, stat as
|
|
28491
|
-
import
|
|
29103
|
+
import { open as open2, stat as stat6 } from "node:fs/promises";
|
|
29104
|
+
import path22 from "node:path";
|
|
28492
29105
|
|
|
28493
29106
|
// src/tools/validation.ts
|
|
28494
29107
|
function assertObject(input2, toolName) {
|
|
@@ -28558,7 +29171,7 @@ var readFileTool = {
|
|
|
28558
29171
|
const filePath = resolveInsideCwd(ctx.cwd, stringField(object2, "path"));
|
|
28559
29172
|
const offset = positiveInteger(optionalNumberField(object2, "offset"), 1, "offset");
|
|
28560
29173
|
const limit = positiveInteger(optionalNumberField(object2, "limit"), DEFAULT_LIMIT, "limit");
|
|
28561
|
-
const info = await
|
|
29174
|
+
const info = await stat6(filePath);
|
|
28562
29175
|
if (info.isDirectory()) throw new Error(`read_file expected a file but got a directory: ${relativeToCwd(ctx.cwd, filePath)}`);
|
|
28563
29176
|
if (info.size > MAX_BYTES) throw new Error(`File is larger than ${MAX_BYTES} bytes: ${relativeToCwd(ctx.cwd, filePath)}`);
|
|
28564
29177
|
const sample = await readBytes(filePath, Math.min(SAMPLE_BYTES, info.size));
|
|
@@ -28572,7 +29185,7 @@ var readFileTool = {
|
|
|
28572
29185
|
const truncated = start + sliced.length < lines.length;
|
|
28573
29186
|
return ok(content + (truncated ? `
|
|
28574
29187
|
... (${lines.length - start - sliced.length} more lines)` : ""), {
|
|
28575
|
-
path:
|
|
29188
|
+
path: path22.relative(ctx.cwd, filePath),
|
|
28576
29189
|
lines: sliced.length,
|
|
28577
29190
|
totalLines: lines.length,
|
|
28578
29191
|
truncated
|
|
@@ -28610,8 +29223,8 @@ function looksBinary(buffer) {
|
|
|
28610
29223
|
}
|
|
28611
29224
|
|
|
28612
29225
|
// src/tools/write-file.ts
|
|
28613
|
-
import { mkdir as
|
|
28614
|
-
import
|
|
29226
|
+
import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
|
|
29227
|
+
import path23 from "node:path";
|
|
28615
29228
|
var writeFileTool = {
|
|
28616
29229
|
name: "write_file",
|
|
28617
29230
|
description: "Create or replace a text file inside the workspace.",
|
|
@@ -28629,8 +29242,8 @@ var writeFileTool = {
|
|
|
28629
29242
|
const filePath = resolveInsideCwd(ctx.cwd, stringField(object2, "path"));
|
|
28630
29243
|
const content = stringField(object2, "content", { allowEmpty: true });
|
|
28631
29244
|
const before = await readOptionalTextFile(filePath);
|
|
28632
|
-
await
|
|
28633
|
-
await
|
|
29245
|
+
await mkdir10(path23.dirname(filePath), { recursive: true });
|
|
29246
|
+
await writeFile9(filePath, content, "utf8");
|
|
28634
29247
|
const relative = relativeToCwd(ctx.cwd, filePath);
|
|
28635
29248
|
const diff = createTextDiff(before, content, relative);
|
|
28636
29249
|
return ok(`Wrote ${relativeToCwd(ctx.cwd, filePath)} (${Buffer.byteLength(content, "utf8")} bytes).`, {
|
|
@@ -28643,7 +29256,7 @@ var writeFileTool = {
|
|
|
28643
29256
|
};
|
|
28644
29257
|
async function readOptionalTextFile(filePath) {
|
|
28645
29258
|
try {
|
|
28646
|
-
return await
|
|
29259
|
+
return await readFile11(filePath, "utf8");
|
|
28647
29260
|
} catch (error) {
|
|
28648
29261
|
if (isNotFound(error)) return void 0;
|
|
28649
29262
|
throw error;
|
|
@@ -28654,7 +29267,7 @@ function isNotFound(error) {
|
|
|
28654
29267
|
}
|
|
28655
29268
|
|
|
28656
29269
|
// src/tools/edit-file.ts
|
|
28657
|
-
import { readFile as
|
|
29270
|
+
import { readFile as readFile12, writeFile as writeFile10 } from "node:fs/promises";
|
|
28658
29271
|
var editFileTool = {
|
|
28659
29272
|
name: "edit_file",
|
|
28660
29273
|
description: "Replace an exact string in a text file inside the workspace.",
|
|
@@ -28676,12 +29289,12 @@ var editFileTool = {
|
|
|
28676
29289
|
const newString = stringField(object2, "newString", { allowEmpty: true });
|
|
28677
29290
|
const replaceAll = optionalBooleanField(object2, "replaceAll") ?? false;
|
|
28678
29291
|
if (oldString === newString) throw new Error("oldString and newString must be different");
|
|
28679
|
-
const before = await
|
|
29292
|
+
const before = await readFile12(filePath, "utf8");
|
|
28680
29293
|
const matches = countMatches(before, oldString);
|
|
28681
29294
|
if (matches === 0) throw new Error("oldString was not found");
|
|
28682
29295
|
if (matches > 1 && !replaceAll) throw new Error(`oldString matched ${matches} times; set replaceAll=true to replace all matches`);
|
|
28683
29296
|
const after = replaceAll ? before.split(oldString).join(newString) : before.replace(oldString, newString);
|
|
28684
|
-
await
|
|
29297
|
+
await writeFile10(filePath, after, "utf8");
|
|
28685
29298
|
const relative = relativeToCwd(ctx.cwd, filePath);
|
|
28686
29299
|
const diff = createTextDiff(before, after, relative);
|
|
28687
29300
|
return ok(`Edited ${relative} (${replaceAll ? matches : 1} replacement${matches === 1 ? "" : "s"}).`, {
|
|
@@ -28705,7 +29318,7 @@ function countMatches(text, needle) {
|
|
|
28705
29318
|
|
|
28706
29319
|
// src/tools/bash.ts
|
|
28707
29320
|
import { spawn as spawn5 } from "node:child_process";
|
|
28708
|
-
import
|
|
29321
|
+
import path25 from "node:path";
|
|
28709
29322
|
|
|
28710
29323
|
// src/sandbox/env-only.ts
|
|
28711
29324
|
function buildEnvOnlyLaunch(command, config) {
|
|
@@ -28765,7 +29378,7 @@ function bwrapPath() {
|
|
|
28765
29378
|
|
|
28766
29379
|
// src/sandbox/macos.ts
|
|
28767
29380
|
import { existsSync as existsSync3 } from "node:fs";
|
|
28768
|
-
import
|
|
29381
|
+
import path24 from "node:path";
|
|
28769
29382
|
function canUseMacOSSandbox() {
|
|
28770
29383
|
return process.platform === "darwin" && existsSync3("/usr/bin/sandbox-exec");
|
|
28771
29384
|
}
|
|
@@ -28791,7 +29404,7 @@ function macosProfile(cwd, config) {
|
|
|
28791
29404
|
}
|
|
28792
29405
|
if (mode === "workspace-write") {
|
|
28793
29406
|
lines.push("(deny file-write*)");
|
|
28794
|
-
lines.push(`(allow file-write* (subpath "${escapeProfilePath(
|
|
29407
|
+
lines.push(`(allow file-write* (subpath "${escapeProfilePath(path24.resolve(cwd))}"))`);
|
|
28795
29408
|
lines.push('(allow file-write* (subpath "/tmp"))');
|
|
28796
29409
|
lines.push('(allow file-write* (subpath "/private/tmp"))');
|
|
28797
29410
|
lines.push('(allow file-write* (subpath "/private/var/folders"))');
|
|
@@ -28853,7 +29466,7 @@ ${result.stderr}` : ""
|
|
|
28853
29466
|
].filter(Boolean).join("\n");
|
|
28854
29467
|
return ok(content, {
|
|
28855
29468
|
command,
|
|
28856
|
-
workdir:
|
|
29469
|
+
workdir: path25.relative(ctx.cwd, workdir) || ".",
|
|
28857
29470
|
exitCode: result.exitCode,
|
|
28858
29471
|
signal: result.signal,
|
|
28859
29472
|
timedOut: result.timedOut,
|
|
@@ -28905,8 +29518,8 @@ function runCommand(command, cwd, timeoutMs, signal, sandbox = { mode: "workspac
|
|
|
28905
29518
|
|
|
28906
29519
|
// src/tools/grep.ts
|
|
28907
29520
|
import { spawn as spawn6 } from "node:child_process";
|
|
28908
|
-
import { readdir as readdir2, readFile as
|
|
28909
|
-
import
|
|
29521
|
+
import { readdir as readdir2, readFile as readFile13, stat as stat7 } from "node:fs/promises";
|
|
29522
|
+
import path26 from "node:path";
|
|
28910
29523
|
var MAX_MATCHES = 200;
|
|
28911
29524
|
var grepTool = {
|
|
28912
29525
|
name: "grep",
|
|
@@ -28981,7 +29594,7 @@ function runRg(cwd, base, pattern, glob, signal) {
|
|
|
28981
29594
|
}
|
|
28982
29595
|
function rewriteRgPath(cwd, line) {
|
|
28983
29596
|
const [file, rest] = splitFirst(line, ":");
|
|
28984
|
-
if (!
|
|
29597
|
+
if (!path26.isAbsolute(file)) return line;
|
|
28985
29598
|
return `${relativeToCwd(cwd, file)}:${rest}`;
|
|
28986
29599
|
}
|
|
28987
29600
|
function splitFirst(value, delimiter) {
|
|
@@ -28996,13 +29609,13 @@ async function fallbackSearch(cwd, base, pattern, glob) {
|
|
|
28996
29609
|
if (out.length > MAX_MATCHES) return;
|
|
28997
29610
|
const relative = relativeToCwd(cwd, item);
|
|
28998
29611
|
if (isIgnoredPath(relative)) return;
|
|
28999
|
-
const info = await
|
|
29612
|
+
const info = await stat7(item);
|
|
29000
29613
|
if (info.isDirectory()) {
|
|
29001
|
-
for (const entry of await readdir2(item)) await walk2(
|
|
29614
|
+
for (const entry of await readdir2(item)) await walk2(path26.join(item, entry));
|
|
29002
29615
|
return;
|
|
29003
29616
|
}
|
|
29004
29617
|
if (glob && !matchGlob(glob, relative)) return;
|
|
29005
|
-
const buffer = await
|
|
29618
|
+
const buffer = await readFile13(item);
|
|
29006
29619
|
if (buffer.includes(0)) return;
|
|
29007
29620
|
const lines = buffer.toString("utf8").split(/\r?\n/);
|
|
29008
29621
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -29016,8 +29629,8 @@ async function fallbackSearch(cwd, base, pattern, glob) {
|
|
|
29016
29629
|
}
|
|
29017
29630
|
|
|
29018
29631
|
// src/tools/glob.ts
|
|
29019
|
-
import { readdir as readdir3, stat as
|
|
29020
|
-
import
|
|
29632
|
+
import { readdir as readdir3, stat as stat8 } from "node:fs/promises";
|
|
29633
|
+
import path27 from "node:path";
|
|
29021
29634
|
var MAX_PATHS = 1e3;
|
|
29022
29635
|
var globTool = {
|
|
29023
29636
|
name: "glob",
|
|
@@ -29049,10 +29662,10 @@ async function collectPaths(cwd, root, pattern) {
|
|
|
29049
29662
|
async function walk2(dir) {
|
|
29050
29663
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
29051
29664
|
for (const entry of entries) {
|
|
29052
|
-
const absolute =
|
|
29665
|
+
const absolute = path27.join(dir, entry.name);
|
|
29053
29666
|
const relative = relativeToCwd(cwd, absolute);
|
|
29054
29667
|
if (isIgnoredPath(relative)) continue;
|
|
29055
|
-
const info = await
|
|
29668
|
+
const info = await stat8(absolute);
|
|
29056
29669
|
if (matchGlob(pattern, relative)) out.push({ relative, mtimeMs: info.mtimeMs });
|
|
29057
29670
|
if (entry.isDirectory()) await walk2(absolute);
|
|
29058
29671
|
}
|
|
@@ -30051,14 +30664,14 @@ async function runExecutionSession(options2) {
|
|
|
30051
30664
|
}
|
|
30052
30665
|
|
|
30053
30666
|
// src/goals/lock.ts
|
|
30054
|
-
import { mkdir as
|
|
30055
|
-
import
|
|
30667
|
+
import { mkdir as mkdir12, open as open3, rm as rm2 } from "node:fs/promises";
|
|
30668
|
+
import path29 from "node:path";
|
|
30056
30669
|
|
|
30057
30670
|
// src/goals/storage.ts
|
|
30058
30671
|
import { createHash as createHash3 } from "node:crypto";
|
|
30059
|
-
import { mkdir as
|
|
30672
|
+
import { mkdir as mkdir11, readFile as readFile14, rename as rename5, writeFile as writeFile11 } from "node:fs/promises";
|
|
30060
30673
|
import fs9 from "node:fs";
|
|
30061
|
-
import
|
|
30674
|
+
import path28 from "node:path";
|
|
30062
30675
|
var GoalStore = class {
|
|
30063
30676
|
cwd;
|
|
30064
30677
|
dir;
|
|
@@ -30069,12 +30682,12 @@ var GoalStore = class {
|
|
|
30069
30682
|
this.cwd = cwd;
|
|
30070
30683
|
this.scope = normalizeGoalStoreScope(options2.scope);
|
|
30071
30684
|
this.dir = goalStoreDir(cwd, this.scope);
|
|
30072
|
-
this.activePath =
|
|
30073
|
-
this.archiveDir =
|
|
30685
|
+
this.activePath = path28.join(this.dir, "active.json");
|
|
30686
|
+
this.archiveDir = path28.join(this.dir, "archive");
|
|
30074
30687
|
}
|
|
30075
30688
|
async loadActive() {
|
|
30076
30689
|
if (!fs9.existsSync(this.activePath)) return void 0;
|
|
30077
|
-
const text = await
|
|
30690
|
+
const text = await readFile14(this.activePath, "utf8");
|
|
30078
30691
|
try {
|
|
30079
30692
|
return JSON.parse(text);
|
|
30080
30693
|
} catch {
|
|
@@ -30083,36 +30696,36 @@ var GoalStore = class {
|
|
|
30083
30696
|
}
|
|
30084
30697
|
}
|
|
30085
30698
|
async saveActive(state) {
|
|
30086
|
-
await
|
|
30087
|
-
const tmp =
|
|
30088
|
-
await
|
|
30699
|
+
await mkdir11(this.dir, { recursive: true });
|
|
30700
|
+
const tmp = path28.join(this.dir, `active.${process.pid}.${Date.now()}.tmp`);
|
|
30701
|
+
await writeFile11(tmp, `${JSON.stringify(state, null, 2)}
|
|
30089
30702
|
`, "utf8");
|
|
30090
|
-
await
|
|
30703
|
+
await rename5(tmp, this.activePath);
|
|
30091
30704
|
}
|
|
30092
30705
|
async archiveActive(status = "cleared") {
|
|
30093
30706
|
const state = await this.loadActive();
|
|
30094
30707
|
if (!state) return void 0;
|
|
30095
|
-
await
|
|
30708
|
+
await mkdir11(this.archiveDir, { recursive: true });
|
|
30096
30709
|
const archived = {
|
|
30097
30710
|
...state,
|
|
30098
30711
|
status,
|
|
30099
30712
|
updatedAt: Date.now()
|
|
30100
30713
|
};
|
|
30101
|
-
await
|
|
30714
|
+
await writeFile11(path28.join(this.archiveDir, `${archived.id}.json`), `${JSON.stringify(archived, null, 2)}
|
|
30102
30715
|
`, "utf8");
|
|
30103
30716
|
await fs9.promises.rm(this.activePath, { force: true });
|
|
30104
30717
|
return archived;
|
|
30105
30718
|
}
|
|
30106
30719
|
async backupCorrupted(text) {
|
|
30107
|
-
await
|
|
30108
|
-
const backup =
|
|
30109
|
-
await
|
|
30720
|
+
await mkdir11(this.dir, { recursive: true });
|
|
30721
|
+
const backup = path28.join(this.dir, `active.corrupt.${Date.now()}.json`);
|
|
30722
|
+
await writeFile11(backup, text, "utf8");
|
|
30110
30723
|
await fs9.promises.rm(this.activePath, { force: true });
|
|
30111
30724
|
}
|
|
30112
30725
|
};
|
|
30113
30726
|
function goalStoreDir(cwd, scope) {
|
|
30114
30727
|
const safeScope = normalizeGoalStoreScope(scope);
|
|
30115
|
-
return safeScope ?
|
|
30728
|
+
return safeScope ? path28.join(cwd, ".demian", "goals", "sessions", safeScope) : path28.join(cwd, ".demian", "goals");
|
|
30116
30729
|
}
|
|
30117
30730
|
function normalizeGoalStoreScope(scope) {
|
|
30118
30731
|
const trimmed = scope?.trim();
|
|
@@ -30126,10 +30739,10 @@ function normalizeGoalStoreScope(scope) {
|
|
|
30126
30739
|
var GoalLock = class {
|
|
30127
30740
|
path;
|
|
30128
30741
|
constructor(cwd, scope) {
|
|
30129
|
-
this.path =
|
|
30742
|
+
this.path = path29.join(goalStoreDir(cwd, scope), "active.lock");
|
|
30130
30743
|
}
|
|
30131
30744
|
async acquire() {
|
|
30132
|
-
await
|
|
30745
|
+
await mkdir12(path29.dirname(this.path), { recursive: true });
|
|
30133
30746
|
const handle = await open3(this.path, "wx").catch((error) => {
|
|
30134
30747
|
const code = typeof error === "object" && error && "code" in error ? String(error.code) : "";
|
|
30135
30748
|
if (code === "EEXIST") throw new Error("Another goal is already active in this workspace.");
|
|
@@ -30717,8 +31330,8 @@ function sha256(value) {
|
|
|
30717
31330
|
|
|
30718
31331
|
// src/root-session.ts
|
|
30719
31332
|
import { cp, mkdtemp, rm as rm3 } from "node:fs/promises";
|
|
30720
|
-
import
|
|
30721
|
-
import
|
|
31333
|
+
import os12 from "node:os";
|
|
31334
|
+
import path30 from "node:path";
|
|
30722
31335
|
|
|
30723
31336
|
// src/tools/cowork.ts
|
|
30724
31337
|
function createCoworkTool(options2) {
|
|
@@ -31541,8 +32154,8 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
31541
32154
|
}
|
|
31542
32155
|
}
|
|
31543
32156
|
async createCoworkIsolatedWorkspace(groupId, memberId) {
|
|
31544
|
-
const root = await mkdtemp(
|
|
31545
|
-
const cwd =
|
|
32157
|
+
const root = await mkdtemp(path30.join(os12.tmpdir(), `demian-cowork-${groupId}-${memberId}-`));
|
|
32158
|
+
const cwd = path30.join(root, "workspace");
|
|
31546
32159
|
await cp(this.#options.cwd, cwd, {
|
|
31547
32160
|
recursive: true,
|
|
31548
32161
|
filter: (source) => shouldCopyIntoCoworkWorkspace(this.#options.cwd, source)
|
|
@@ -31897,7 +32510,7 @@ function scopeStaticPrefix(pattern) {
|
|
|
31897
32510
|
return prefix.replace(/\/+$/, "").split("/").filter(Boolean).join("/") || ".";
|
|
31898
32511
|
}
|
|
31899
32512
|
function shouldCopyIntoCoworkWorkspace(root, source) {
|
|
31900
|
-
const relative =
|
|
32513
|
+
const relative = path30.relative(root, source).split(path30.sep).join("/");
|
|
31901
32514
|
if (!relative) return true;
|
|
31902
32515
|
const parts = relative.split("/");
|
|
31903
32516
|
return !parts.includes(".git") && !parts.includes("node_modules") && !parts.includes(".demian");
|
|
@@ -32109,13 +32722,13 @@ function finitePositiveInteger(value) {
|
|
|
32109
32722
|
}
|
|
32110
32723
|
|
|
32111
32724
|
// src/ui/preferences.ts
|
|
32112
|
-
import { mkdir as
|
|
32725
|
+
import { mkdir as mkdir13, readFile as readFile15, writeFile as writeFile12 } from "node:fs/promises";
|
|
32113
32726
|
import fs10 from "node:fs";
|
|
32114
|
-
import
|
|
32727
|
+
import path31 from "node:path";
|
|
32115
32728
|
var UiPreferenceStore = class {
|
|
32116
32729
|
filePath;
|
|
32117
32730
|
constructor(cwd, filePath) {
|
|
32118
|
-
this.filePath = filePath ?
|
|
32731
|
+
this.filePath = filePath ? path31.resolve(cwd, filePath) : path31.join(cwd, ".demian", "preferences.json");
|
|
32119
32732
|
}
|
|
32120
32733
|
async load() {
|
|
32121
32734
|
if (!fs10.existsSync(this.filePath)) return void 0;
|
|
@@ -32133,13 +32746,13 @@ var UiPreferenceStore = class {
|
|
|
32133
32746
|
model: selection.model,
|
|
32134
32747
|
updatedAt: Date.now()
|
|
32135
32748
|
};
|
|
32136
|
-
await
|
|
32137
|
-
await
|
|
32749
|
+
await mkdir13(path31.dirname(this.filePath), { recursive: true });
|
|
32750
|
+
await writeFile12(this.filePath, `${JSON.stringify(file, null, 2)}
|
|
32138
32751
|
`, { mode: 384 });
|
|
32139
32752
|
}
|
|
32140
32753
|
async #read() {
|
|
32141
32754
|
try {
|
|
32142
|
-
return JSON.parse(await
|
|
32755
|
+
return JSON.parse(await readFile15(this.filePath, "utf8"));
|
|
32143
32756
|
} catch {
|
|
32144
32757
|
return void 0;
|
|
32145
32758
|
}
|
|
@@ -32155,9 +32768,9 @@ function preferenceKey(selection) {
|
|
|
32155
32768
|
|
|
32156
32769
|
// src/models/catalog.ts
|
|
32157
32770
|
import crypto6 from "node:crypto";
|
|
32158
|
-
import { mkdir as
|
|
32159
|
-
import
|
|
32160
|
-
import
|
|
32771
|
+
import { mkdir as mkdir14, readFile as readFile16, rename as rename6, writeFile as writeFile13 } from "node:fs/promises";
|
|
32772
|
+
import os13 from "node:os";
|
|
32773
|
+
import path32 from "node:path";
|
|
32161
32774
|
var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
32162
32775
|
var PING_TIMEOUT_MS = 1500;
|
|
32163
32776
|
var DEFAULT_CODEX_CLIENT_VERSION = "0.0.0";
|
|
@@ -32469,12 +33082,12 @@ async function fetchJson2(url, options2) {
|
|
|
32469
33082
|
return text ? JSON.parse(text) : {};
|
|
32470
33083
|
}
|
|
32471
33084
|
function cachePath(cacheKey) {
|
|
32472
|
-
return
|
|
33085
|
+
return path32.join(os13.homedir(), ".demian", "cache", "models", `${safeCacheName(cacheKey)}.json`);
|
|
32473
33086
|
}
|
|
32474
33087
|
async function readCatalogCache(cacheKey, ttlMs, allow) {
|
|
32475
33088
|
if (!allow) return void 0;
|
|
32476
33089
|
try {
|
|
32477
|
-
const raw = JSON.parse(await
|
|
33090
|
+
const raw = JSON.parse(await readFile16(cachePath(cacheKey), "utf8"));
|
|
32478
33091
|
if (!raw.result) return void 0;
|
|
32479
33092
|
if (ttlMs !== Number.POSITIVE_INFINITY && Date.now() - (raw.savedAt ?? 0) > ttlMs) return void 0;
|
|
32480
33093
|
return { ...raw.result, source: "cache", models: raw.result.models.map((model) => ({ ...model, source: "cache" })) };
|
|
@@ -32484,10 +33097,10 @@ async function readCatalogCache(cacheKey, ttlMs, allow) {
|
|
|
32484
33097
|
}
|
|
32485
33098
|
async function writeCatalogCache(cacheKey, result) {
|
|
32486
33099
|
const filePath = cachePath(cacheKey);
|
|
32487
|
-
await
|
|
33100
|
+
await mkdir14(path32.dirname(filePath), { recursive: true, mode: 448 });
|
|
32488
33101
|
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
32489
|
-
await
|
|
32490
|
-
await
|
|
33102
|
+
await writeFile13(temp, JSON.stringify({ savedAt: Date.now(), result }, null, 2), { mode: 384 });
|
|
33103
|
+
await rename6(temp, filePath);
|
|
32491
33104
|
}
|
|
32492
33105
|
function safeCacheName(providerName) {
|
|
32493
33106
|
return providerName.replace(/[^a-zA-Z0-9_.-]+/g, "_");
|
|
@@ -32538,7 +33151,7 @@ async function codexClientVersion(override) {
|
|
|
32538
33151
|
}
|
|
32539
33152
|
async function readPackageVersion() {
|
|
32540
33153
|
try {
|
|
32541
|
-
const raw = JSON.parse(await
|
|
33154
|
+
const raw = JSON.parse(await readFile16(new URL("../package.json", import.meta.url), "utf8"));
|
|
32542
33155
|
return semverLike(raw.version) ?? DEFAULT_CODEX_CLIENT_VERSION;
|
|
32543
33156
|
} catch {
|
|
32544
33157
|
return DEFAULT_CODEX_CLIENT_VERSION;
|
|
@@ -32602,6 +33215,9 @@ function providerOptions(config, catalogs = {}) {
|
|
|
32602
33215
|
permissionMode: provider.type === "claudecode" ? provider.permissionMode : void 0,
|
|
32603
33216
|
ga: provider.type === "claudecode" ? claudeCodeGaEnabled(provider) : void 0,
|
|
32604
33217
|
available,
|
|
33218
|
+
baseURL: typeof provider.baseURL === "string" ? provider.baseURL : void 0,
|
|
33219
|
+
apiKeyEnv: typeof provider.apiKeyEnv === "string" ? provider.apiKeyEnv : void 0,
|
|
33220
|
+
auth: sanitizeProviderAuth(provider.auth),
|
|
32605
33221
|
catalog: catalog ? { status: catalog.status, source: catalog.source, message: catalog.message } : void 0
|
|
32606
33222
|
};
|
|
32607
33223
|
}).sort((left, right) => {
|
|
@@ -32673,13 +33289,20 @@ function providerCatalogAvailable(catalog) {
|
|
|
32673
33289
|
function unavailableLabel(value) {
|
|
32674
33290
|
return `(n/a) ${value}`;
|
|
32675
33291
|
}
|
|
33292
|
+
function sanitizeProviderAuth(auth) {
|
|
33293
|
+
if (!auth || typeof auth !== "object" || Array.isArray(auth)) return void 0;
|
|
33294
|
+
const record = auth;
|
|
33295
|
+
const type = typeof record.type === "string" ? record.type : void 0;
|
|
33296
|
+
const header = typeof record.header === "string" ? record.header : void 0;
|
|
33297
|
+
return type || header ? { type, header } : void 0;
|
|
33298
|
+
}
|
|
32676
33299
|
function errorMessage3(error) {
|
|
32677
33300
|
return error instanceof Error ? error.message : String(error);
|
|
32678
33301
|
}
|
|
32679
33302
|
|
|
32680
33303
|
// src/ui/conversations.ts
|
|
32681
|
-
import { mkdir as
|
|
32682
|
-
import
|
|
33304
|
+
import { mkdir as mkdir15, readFile as readFile17, readdir as readdir4, rm as rm4, writeFile as writeFile14 } from "node:fs/promises";
|
|
33305
|
+
import path33 from "node:path";
|
|
32683
33306
|
var CONVERSATIONS_DIR = "conversations";
|
|
32684
33307
|
var CONVERSATION_INDEX_FILE = "conversations.json";
|
|
32685
33308
|
var CONVERSATION_FILE = "conversation.json";
|
|
@@ -32692,13 +33315,13 @@ function createConversationRecord(input2) {
|
|
|
32692
33315
|
title: input2.title?.trim() || "New session",
|
|
32693
33316
|
createdAt: timestamp,
|
|
32694
33317
|
updatedAt: timestamp,
|
|
32695
|
-
cwd:
|
|
33318
|
+
cwd: path33.resolve(input2.cwd),
|
|
32696
33319
|
modelHistory: cleanModelHistory(input2.history ?? []),
|
|
32697
33320
|
snapshot: input2.snapshot
|
|
32698
33321
|
};
|
|
32699
33322
|
}
|
|
32700
33323
|
async function loadConversationIndex(storageDir = defaultDemianStorageDir()) {
|
|
32701
|
-
const index = await readJson(
|
|
33324
|
+
const index = await readJson(path33.join(storageDir, CONVERSATION_INDEX_FILE));
|
|
32702
33325
|
const conversations = Array.isArray(index?.conversations) ? index.conversations.map((item) => {
|
|
32703
33326
|
if (!item || typeof item !== "object") return void 0;
|
|
32704
33327
|
const object2 = item;
|
|
@@ -32732,7 +33355,7 @@ async function loadConversationRecords(storageDir = defaultDemianStorageDir()) {
|
|
|
32732
33355
|
async function saveConversationRecords(storageDir, records, selectedSessionId) {
|
|
32733
33356
|
const root = storageDir ?? defaultDemianStorageDir();
|
|
32734
33357
|
const kept = [...records].sort((a, b2) => b2.updatedAt - a.updatedAt).slice(0, MAX_STORED_CONVERSATIONS).map((record) => ({ ...record, id: sanitizeConversationId(record.id), modelHistory: cleanModelHistory(record.modelHistory) }));
|
|
32735
|
-
await
|
|
33358
|
+
await mkdir15(path33.join(root, CONVERSATIONS_DIR), { recursive: true });
|
|
32736
33359
|
for (const record of kept) {
|
|
32737
33360
|
await writeJson(conversationRecordPath(root, record.id), {
|
|
32738
33361
|
version: 1,
|
|
@@ -32745,7 +33368,7 @@ async function saveConversationRecords(storageDir, records, selectedSessionId) {
|
|
|
32745
33368
|
snapshot: record.snapshot
|
|
32746
33369
|
});
|
|
32747
33370
|
}
|
|
32748
|
-
await writeJson(
|
|
33371
|
+
await writeJson(path33.join(root, CONVERSATION_INDEX_FILE), {
|
|
32749
33372
|
version: 1,
|
|
32750
33373
|
selectedSessionId,
|
|
32751
33374
|
conversations: kept.map((record) => ({
|
|
@@ -32770,7 +33393,7 @@ function findConversation(records, target) {
|
|
|
32770
33393
|
return records.find((record) => record.id === value || record.id.startsWith(value)) ?? records.find((record) => record.title.toLowerCase() === normalized) ?? records.find((record) => record.title.toLowerCase().includes(normalized));
|
|
32771
33394
|
}
|
|
32772
33395
|
function sortConversationRecords(records, cwd) {
|
|
32773
|
-
const resolvedCwd =
|
|
33396
|
+
const resolvedCwd = path33.resolve(cwd);
|
|
32774
33397
|
return [...records].sort((a, b2) => {
|
|
32775
33398
|
const aCurrent = isConversationForCwd(a, resolvedCwd) ? 0 : 1;
|
|
32776
33399
|
const bCurrent = isConversationForCwd(b2, resolvedCwd) ? 0 : 1;
|
|
@@ -32785,7 +33408,7 @@ function conversationRuntimeStatus(record, cwd) {
|
|
|
32785
33408
|
return isConversationForCwd(record, cwd) ? "ready" : "stored";
|
|
32786
33409
|
}
|
|
32787
33410
|
function isConversationForCwd(record, cwd) {
|
|
32788
|
-
return
|
|
33411
|
+
return path33.resolve(record.cwd) === path33.resolve(cwd);
|
|
32789
33412
|
}
|
|
32790
33413
|
function conversationSummaryLine(record, index, activeId) {
|
|
32791
33414
|
const active = record.id === activeId ? "*" : " ";
|
|
@@ -32805,14 +33428,14 @@ function cleanModelHistory(history) {
|
|
|
32805
33428
|
return history.filter((message) => !isInternalModelHistoryMessage(message)).slice(-MAX_MODEL_HISTORY);
|
|
32806
33429
|
}
|
|
32807
33430
|
function conversationDir(storageDir, id) {
|
|
32808
|
-
return
|
|
33431
|
+
return path33.join(storageDir, CONVERSATIONS_DIR, sanitizeConversationId(id));
|
|
32809
33432
|
}
|
|
32810
33433
|
function conversationRecordPath(storageDir, id) {
|
|
32811
|
-
return
|
|
33434
|
+
return path33.join(conversationDir(storageDir, id), CONVERSATION_FILE);
|
|
32812
33435
|
}
|
|
32813
33436
|
async function listConversationRecordIds(storageDir) {
|
|
32814
33437
|
try {
|
|
32815
|
-
const entries = await readdir4(
|
|
33438
|
+
const entries = await readdir4(path33.join(storageDir, CONVERSATIONS_DIR), { withFileTypes: true });
|
|
32816
33439
|
return entries.filter((entry) => entry.isDirectory()).map((entry) => sanitizeConversationId(entry.name)).filter(Boolean);
|
|
32817
33440
|
} catch {
|
|
32818
33441
|
return [];
|
|
@@ -32820,14 +33443,14 @@ async function listConversationRecordIds(storageDir) {
|
|
|
32820
33443
|
}
|
|
32821
33444
|
async function readJson(filePath) {
|
|
32822
33445
|
try {
|
|
32823
|
-
return JSON.parse(await
|
|
33446
|
+
return JSON.parse(await readFile17(filePath, "utf8"));
|
|
32824
33447
|
} catch {
|
|
32825
33448
|
return void 0;
|
|
32826
33449
|
}
|
|
32827
33450
|
}
|
|
32828
33451
|
async function writeJson(filePath, value) {
|
|
32829
|
-
await
|
|
32830
|
-
await
|
|
33452
|
+
await mkdir15(path33.dirname(filePath), { recursive: true });
|
|
33453
|
+
await writeFile14(filePath, `${JSON.stringify(value, null, 2)}
|
|
32831
33454
|
`, "utf8");
|
|
32832
33455
|
}
|
|
32833
33456
|
function normalizeConversationRecord(value, fallbackId) {
|
|
@@ -32882,11 +33505,12 @@ function formatRelativeAge(ms2) {
|
|
|
32882
33505
|
|
|
32883
33506
|
// src/ui/tui/controller.ts
|
|
32884
33507
|
async function runTuiSession(flags, store2) {
|
|
32885
|
-
const cwd =
|
|
33508
|
+
const cwd = path34.resolve(flags.cwd ?? process.cwd());
|
|
32886
33509
|
const eventBus = new EventBus();
|
|
32887
33510
|
eventBus.subscribe((event) => store2.handleEvent(event));
|
|
32888
|
-
const
|
|
32889
|
-
|
|
33511
|
+
const configInit = await ensureTuiConfigExists(flags);
|
|
33512
|
+
let loadedConfig2 = await loadConfig({ cwd, configPath: flags.configPath });
|
|
33513
|
+
let config = flags.context ? mergeConfig(loadedConfig2, {
|
|
32890
33514
|
context: {
|
|
32891
33515
|
main: flags.context
|
|
32892
33516
|
}
|
|
@@ -32906,6 +33530,10 @@ async function runTuiSession(flags, store2) {
|
|
|
32906
33530
|
store2.configureSettings(initialSelection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
|
|
32907
33531
|
agent: agentName,
|
|
32908
33532
|
cwd,
|
|
33533
|
+
configPath: configMutationPath(flags),
|
|
33534
|
+
configCreated: configInit.created,
|
|
33535
|
+
configDefaultProvider: config.defaultProvider,
|
|
33536
|
+
configMessage: configInit.created ? `Created config at ${configInit.path}` : void 0,
|
|
32909
33537
|
agentOptions,
|
|
32910
33538
|
contextEfficiency: {
|
|
32911
33539
|
maxContextTokens: config.context.main.maxContextTokens,
|
|
@@ -32914,6 +33542,7 @@ async function runTuiSession(flags, store2) {
|
|
|
32914
33542
|
}
|
|
32915
33543
|
});
|
|
32916
33544
|
let nextPrompt = flags.prompt;
|
|
33545
|
+
let openConfigOnFirstPrompt = configInit.created && !flags.prompt.trim();
|
|
32917
33546
|
const conversationsEnabled = flags.conversationManagement === true;
|
|
32918
33547
|
const conversationState = conversationsEnabled ? await initializeConversationState(cwd, flags) : void 0;
|
|
32919
33548
|
let currentConversation = conversationState?.current;
|
|
@@ -32954,13 +33583,28 @@ async function runTuiSession(flags, store2) {
|
|
|
32954
33583
|
await applyStartupSessionSelection(selected);
|
|
32955
33584
|
}
|
|
32956
33585
|
for (; ; ) {
|
|
32957
|
-
const
|
|
33586
|
+
const promptPromise = store2.requestPrompt(nextPrompt);
|
|
33587
|
+
if (openConfigOnFirstPrompt) {
|
|
33588
|
+
openConfigOnFirstPrompt = false;
|
|
33589
|
+
store2.openConfigManager(`Created config at ${configInit.path}`);
|
|
33590
|
+
}
|
|
33591
|
+
const prompt = await promptPromise;
|
|
32958
33592
|
nextPrompt = void 0;
|
|
32959
33593
|
if (store2.exitRequested()) {
|
|
32960
33594
|
await saveCurrentSelection();
|
|
32961
33595
|
return 0;
|
|
32962
33596
|
}
|
|
32963
33597
|
if (!prompt) return 0;
|
|
33598
|
+
const configAction = parseTuiConfigAction(prompt);
|
|
33599
|
+
if (configAction) {
|
|
33600
|
+
await handleConfigAction(configAction);
|
|
33601
|
+
continue;
|
|
33602
|
+
}
|
|
33603
|
+
const sessionAction = parseTuiSessionAction(prompt);
|
|
33604
|
+
if (sessionAction) {
|
|
33605
|
+
await handleSessionSelectionShortcut();
|
|
33606
|
+
continue;
|
|
33607
|
+
}
|
|
32964
33608
|
const sessionCommand = parseSessionCommand(prompt);
|
|
32965
33609
|
if (sessionCommand) {
|
|
32966
33610
|
await handleSessionCommand(sessionCommand);
|
|
@@ -33195,22 +33839,13 @@ ${sessionListMessage()}`);
|
|
|
33195
33839
|
return;
|
|
33196
33840
|
}
|
|
33197
33841
|
if (command.action === "delete") {
|
|
33198
|
-
const target = findConversation(sortedConversations(), command.target);
|
|
33842
|
+
const target = command.target ? findConversation(sortedConversations(), command.target) : currentConversation;
|
|
33199
33843
|
if (!target) {
|
|
33200
33844
|
store2.showSystemMessage("Sessions", `No session matched "${command.target ?? ""}".`);
|
|
33201
33845
|
store2.prepareForPrompt("waiting for next message");
|
|
33202
33846
|
return;
|
|
33203
33847
|
}
|
|
33204
|
-
|
|
33205
|
-
runtimeByConversation.delete(target.id);
|
|
33206
|
-
await deleteConversationRecord(flags.conversationStorageDir, target.id);
|
|
33207
|
-
if (currentConversation?.id === target.id) {
|
|
33208
|
-
const next = sortedConversations()[0] ?? createConversationRecord({ id: createRootSessionId(), cwd });
|
|
33209
|
-
conversationRecords.set(next.id, next);
|
|
33210
|
-
await switchConversation(next);
|
|
33211
|
-
}
|
|
33212
|
-
store2.prepareForPrompt(`deleted session: ${target.title}`);
|
|
33213
|
-
await persistCurrentConversation();
|
|
33848
|
+
await deleteConversationById(target.id);
|
|
33214
33849
|
return;
|
|
33215
33850
|
}
|
|
33216
33851
|
if (command.action === "rename") {
|
|
@@ -33237,6 +33872,29 @@ ${sessionListMessage()}`);
|
|
|
33237
33872
|
await persistCurrentConversation();
|
|
33238
33873
|
}
|
|
33239
33874
|
}
|
|
33875
|
+
async function handleSessionSelectionShortcut() {
|
|
33876
|
+
if (!conversationsEnabled) {
|
|
33877
|
+
store2.showSystemMessage("Sessions", "Session management is available in demian-cli TUI, but this embedded runtime lets the host manage sessions.");
|
|
33878
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33879
|
+
return;
|
|
33880
|
+
}
|
|
33881
|
+
if (!currentConversation) {
|
|
33882
|
+
currentConversation = createConversationRecord({ id: flags.sessionId ?? createRootSessionId(), cwd });
|
|
33883
|
+
conversationRecords.set(currentConversation.id, currentConversation);
|
|
33884
|
+
}
|
|
33885
|
+
await persistCurrentConversation();
|
|
33886
|
+
const selection = await store2.requestSessionSelection(startupSessionOptions(), { cancel: "prompt" });
|
|
33887
|
+
if (selection.kind === "cancel") {
|
|
33888
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33889
|
+
return;
|
|
33890
|
+
}
|
|
33891
|
+
if (selection.kind === "exit" || store2.exitRequested()) return;
|
|
33892
|
+
if (selection.kind === "delete") {
|
|
33893
|
+
await deleteConversationById(selection.id);
|
|
33894
|
+
return;
|
|
33895
|
+
}
|
|
33896
|
+
await applyStartupSessionSelection(selection);
|
|
33897
|
+
}
|
|
33240
33898
|
function startupSessionOptions() {
|
|
33241
33899
|
const records = sortedConversations();
|
|
33242
33900
|
const sessionOptions = records.map((record) => ({
|
|
@@ -33246,11 +33904,19 @@ ${sessionListMessage()}`);
|
|
|
33246
33904
|
cwd: record.cwd,
|
|
33247
33905
|
status: conversationRuntimeStatus(record, cwd),
|
|
33248
33906
|
updatedAt: record.updatedAt,
|
|
33249
|
-
currentWorkspace:
|
|
33907
|
+
currentWorkspace: path34.resolve(record.cwd) === cwd
|
|
33250
33908
|
}));
|
|
33251
33909
|
return [...sessionOptions, { kind: "new", title: "New session", cwd, status: "ready", currentWorkspace: true }];
|
|
33252
33910
|
}
|
|
33253
33911
|
async function applyStartupSessionSelection(selection) {
|
|
33912
|
+
if (selection.kind === "cancel") {
|
|
33913
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33914
|
+
return;
|
|
33915
|
+
}
|
|
33916
|
+
if (selection.kind === "delete") {
|
|
33917
|
+
await deleteConversationById(selection.id);
|
|
33918
|
+
return;
|
|
33919
|
+
}
|
|
33254
33920
|
if (selection.kind === "new") {
|
|
33255
33921
|
const next = createConversationRecord({ id: createRootSessionId(), cwd });
|
|
33256
33922
|
conversationRecords.set(next.id, next);
|
|
@@ -33272,6 +33938,24 @@ ${sessionListMessage()}`);
|
|
|
33272
33938
|
store2.prepareForPrompt(`session ready: ${target.title}`);
|
|
33273
33939
|
await saveConversationRecords(flags.conversationStorageDir, [...conversationRecords.values()], target.id);
|
|
33274
33940
|
}
|
|
33941
|
+
async function deleteConversationById(id) {
|
|
33942
|
+
const target = conversationRecords.get(id) ?? findConversation(sortedConversations(), id);
|
|
33943
|
+
if (!target) {
|
|
33944
|
+
store2.showSystemMessage("Sessions", `No session matched "${id}".`);
|
|
33945
|
+
store2.prepareForPrompt("waiting for next message");
|
|
33946
|
+
return;
|
|
33947
|
+
}
|
|
33948
|
+
conversationRecords.delete(target.id);
|
|
33949
|
+
runtimeByConversation.delete(target.id);
|
|
33950
|
+
await deleteConversationRecord(flags.conversationStorageDir, target.id);
|
|
33951
|
+
if (currentConversation?.id === target.id) {
|
|
33952
|
+
const next = sortedConversations()[0] ?? createConversationRecord({ id: createRootSessionId(), cwd });
|
|
33953
|
+
conversationRecords.set(next.id, next);
|
|
33954
|
+
await switchConversation(next);
|
|
33955
|
+
}
|
|
33956
|
+
store2.prepareForPrompt(`deleted session: ${target.title}`);
|
|
33957
|
+
await persistCurrentConversation();
|
|
33958
|
+
}
|
|
33275
33959
|
async function switchConversation(record) {
|
|
33276
33960
|
currentConversation = record;
|
|
33277
33961
|
rootSessionId = record.id;
|
|
@@ -33307,6 +33991,87 @@ ${sessionListMessage()}`);
|
|
|
33307
33991
|
await preferenceStore.save(selection);
|
|
33308
33992
|
savedSelectionKey = key;
|
|
33309
33993
|
}
|
|
33994
|
+
async function handleConfigAction(action) {
|
|
33995
|
+
const path37 = configMutationPath(flags);
|
|
33996
|
+
try {
|
|
33997
|
+
let message = "Config updated.";
|
|
33998
|
+
let nextSelection = {};
|
|
33999
|
+
if (action.type === "setDefaultProvider") {
|
|
34000
|
+
await updateConfigDefaults({ path: path37, defaultProvider: action.provider });
|
|
34001
|
+
message = `Default provider saved: ${action.provider}`;
|
|
34002
|
+
nextSelection = { provider: action.provider };
|
|
34003
|
+
} else if (action.type === "setDefaultModel") {
|
|
34004
|
+
await setDefaultModelProfile({
|
|
34005
|
+
path: path37,
|
|
34006
|
+
provider: action.provider,
|
|
34007
|
+
profileName: action.profileName,
|
|
34008
|
+
name: action.name,
|
|
34009
|
+
displayName: action.displayName,
|
|
34010
|
+
model: action.model
|
|
34011
|
+
});
|
|
34012
|
+
message = `Default model saved for ${action.provider}: ${action.displayName ?? action.model}`;
|
|
34013
|
+
nextSelection = { provider: action.provider, model: action.displayName ?? action.model };
|
|
34014
|
+
} else if (action.type === "addProvider") {
|
|
34015
|
+
await addProvider({ path: path37, name: action.name, preset: action.preset, apiKeyEnv: action.apiKeyEnv });
|
|
34016
|
+
message = `Provider added: ${action.name}`;
|
|
34017
|
+
nextSelection = { provider: action.name };
|
|
34018
|
+
} else if (action.type === "updateProvider") {
|
|
34019
|
+
await updateProviderSettings({
|
|
34020
|
+
path: path37,
|
|
34021
|
+
provider: action.provider,
|
|
34022
|
+
...action.field === "baseURL" ? { baseURL: action.value } : {},
|
|
34023
|
+
...action.field === "apiKeyEnv" ? { apiKeyEnv: action.value } : {},
|
|
34024
|
+
...action.field === "authHeader" ? { authHeader: action.value } : {}
|
|
34025
|
+
});
|
|
34026
|
+
message = `Provider ${action.field} saved: ${action.provider}`;
|
|
34027
|
+
nextSelection = { provider: action.provider };
|
|
34028
|
+
}
|
|
34029
|
+
await reloadConfigSettings(message, nextSelection);
|
|
34030
|
+
} catch (error) {
|
|
34031
|
+
store2.prepareForPrompt("config action failed");
|
|
34032
|
+
store2.showSystemMessage("Config", errorMessage(error));
|
|
34033
|
+
store2.openConfigManager(errorMessage(error));
|
|
34034
|
+
}
|
|
34035
|
+
}
|
|
34036
|
+
async function reloadConfigSettings(message, selectionFlags = {}) {
|
|
34037
|
+
loadedConfig2 = await loadConfig({ cwd, configPath: flags.configPath });
|
|
34038
|
+
config = flags.context ? mergeConfig(loadedConfig2, {
|
|
34039
|
+
context: {
|
|
34040
|
+
main: flags.context
|
|
34041
|
+
}
|
|
34042
|
+
}) : loadedConfig2;
|
|
34043
|
+
const currentSelection = store2.currentSelection();
|
|
34044
|
+
const selection = createInteractiveModelSelection(
|
|
34045
|
+
config,
|
|
34046
|
+
selectionFlags.provider || selectionFlags.model ? selectionFlags : { provider: currentSelection.providerName, model: currentSelection.model }
|
|
34047
|
+
);
|
|
34048
|
+
store2.configureSettings(selection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
|
|
34049
|
+
agent: store2.currentAgentName(),
|
|
34050
|
+
cwd,
|
|
34051
|
+
configPath: configMutationPath(flags),
|
|
34052
|
+
configDefaultProvider: config.defaultProvider,
|
|
34053
|
+
configMessage: message,
|
|
34054
|
+
agentOptions,
|
|
34055
|
+
contextEfficiency: {
|
|
34056
|
+
maxContextTokens: config.context.main.maxContextTokens,
|
|
34057
|
+
compactAtRatio: config.context.main.compactAtRatio ?? config.context.main.compressAtRatio ?? 0.5,
|
|
34058
|
+
compactAtTokens: config.context.main.maxContextTokens ? Math.floor(config.context.main.maxContextTokens * (config.context.main.compactAtRatio ?? config.context.main.compressAtRatio ?? 0.5)) : void 0
|
|
34059
|
+
}
|
|
34060
|
+
});
|
|
34061
|
+
store2.openConfigManager(message);
|
|
34062
|
+
}
|
|
34063
|
+
}
|
|
34064
|
+
async function ensureTuiConfigExists(flags) {
|
|
34065
|
+
const target = configMutationPath(flags);
|
|
34066
|
+
if (!flags.configPath) {
|
|
34067
|
+
const jsondPath = target.replace(/\.json$/i, ".jsond");
|
|
34068
|
+
if (existsSync4(target) || existsSync4(jsondPath)) return { path: target, created: false };
|
|
34069
|
+
}
|
|
34070
|
+
const result = await createUserConfig({ path: target });
|
|
34071
|
+
return { path: result.path, created: result.created };
|
|
34072
|
+
}
|
|
34073
|
+
function configMutationPath(flags) {
|
|
34074
|
+
return flags.configPath ? path34.resolve(flags.configPath) : defaultUserConfigPath();
|
|
33310
34075
|
}
|
|
33311
34076
|
function isGoalStateClearingAction(action) {
|
|
33312
34077
|
return action === "status" || action === "pause" || action === "resume";
|
|
@@ -33333,7 +34098,7 @@ function sessionUsage() {
|
|
|
33333
34098
|
" /session new [title]",
|
|
33334
34099
|
" /session switch <number|id|title>",
|
|
33335
34100
|
" /session rename <title>",
|
|
33336
|
-
" /session delete
|
|
34101
|
+
" /session delete [number|id|title]",
|
|
33337
34102
|
" /session clear",
|
|
33338
34103
|
"",
|
|
33339
34104
|
"Aliases: /sessions, /conversation, /conversations, /conv"
|
|
@@ -33344,289 +34109,6 @@ function sessionUsage() {
|
|
|
33344
34109
|
import { EventEmitter } from "node:events";
|
|
33345
34110
|
import fs11 from "node:fs";
|
|
33346
34111
|
import path35 from "node:path";
|
|
33347
|
-
|
|
33348
|
-
// src/config-scaffold.ts
|
|
33349
|
-
import { chmod as chmod3, mkdir as mkdir15, readFile as readFile17, rename as rename6, stat as stat8, writeFile as writeFile14 } from "node:fs/promises";
|
|
33350
|
-
import os13 from "node:os";
|
|
33351
|
-
import path34 from "node:path";
|
|
33352
|
-
function defaultUserConfigPath() {
|
|
33353
|
-
return path34.join(os13.homedir(), ".demian", "config.json");
|
|
33354
|
-
}
|
|
33355
|
-
function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
|
|
33356
|
-
return {
|
|
33357
|
-
version: 2,
|
|
33358
|
-
defaultProvider,
|
|
33359
|
-
providers: {
|
|
33360
|
-
openai: {
|
|
33361
|
-
type: "openai-compatible",
|
|
33362
|
-
baseURL: "https://api.openai.com/v1",
|
|
33363
|
-
apiKey: "",
|
|
33364
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
33365
|
-
catalog: { type: "openai-models", endpoint: "https://api.openai.com/v1/models" }
|
|
33366
|
-
},
|
|
33367
|
-
anthropic: {
|
|
33368
|
-
type: "anthropic",
|
|
33369
|
-
baseURL: "https://api.anthropic.com/v1",
|
|
33370
|
-
apiKey: "",
|
|
33371
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
33372
|
-
catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
|
|
33373
|
-
},
|
|
33374
|
-
gemini: {
|
|
33375
|
-
type: "openai-compatible",
|
|
33376
|
-
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
33377
|
-
apiKey: "",
|
|
33378
|
-
apiKeyEnv: "GEMINI_API_KEY",
|
|
33379
|
-
apiKeyEnvAliases: ["GOOGLE_API_KEY"],
|
|
33380
|
-
catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
|
|
33381
|
-
},
|
|
33382
|
-
groq: {
|
|
33383
|
-
type: "openai-compatible",
|
|
33384
|
-
baseURL: "https://api.groq.com/openai/v1",
|
|
33385
|
-
apiKey: "",
|
|
33386
|
-
apiKeyEnv: "GROQ_API_KEY",
|
|
33387
|
-
catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
|
|
33388
|
-
},
|
|
33389
|
-
azure: {
|
|
33390
|
-
type: "openai-compatible",
|
|
33391
|
-
auth: { type: "api-key", header: "api-key" },
|
|
33392
|
-
modelProfiles: [
|
|
33393
|
-
{
|
|
33394
|
-
name: "azure-example",
|
|
33395
|
-
displayName: "Azure example",
|
|
33396
|
-
model: "azure-deployment-name",
|
|
33397
|
-
baseURL: "https://example.openai.azure.com/openai/v1",
|
|
33398
|
-
apiKey: "",
|
|
33399
|
-
apiKeyEnv: "AZURE_OPENAI_API_KEY"
|
|
33400
|
-
}
|
|
33401
|
-
]
|
|
33402
|
-
},
|
|
33403
|
-
lmstudio: {
|
|
33404
|
-
type: "openai-compatible",
|
|
33405
|
-
baseURL: "http://localhost:1234/v1",
|
|
33406
|
-
apiKey: "lm-studio",
|
|
33407
|
-
modelProfiles: [],
|
|
33408
|
-
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
|
|
33409
|
-
},
|
|
33410
|
-
"ollama-local": {
|
|
33411
|
-
type: "openai-compatible",
|
|
33412
|
-
baseURL: "http://localhost:11434/v1",
|
|
33413
|
-
apiKey: "ollama",
|
|
33414
|
-
modelProfiles: [],
|
|
33415
|
-
quirks: { omitTemperature: true },
|
|
33416
|
-
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
|
|
33417
|
-
},
|
|
33418
|
-
"ollama-cloud": {
|
|
33419
|
-
type: "ollama",
|
|
33420
|
-
baseURL: "https://ollama.com/api",
|
|
33421
|
-
apiKey: "",
|
|
33422
|
-
apiKeyEnv: "OLLAMA_API_KEY",
|
|
33423
|
-
modelProfiles: [],
|
|
33424
|
-
catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
|
|
33425
|
-
},
|
|
33426
|
-
llamacpp: {
|
|
33427
|
-
type: "openai-compatible",
|
|
33428
|
-
baseURL: "http://localhost:8080/v1",
|
|
33429
|
-
apiKey: "llama.cpp",
|
|
33430
|
-
modelProfiles: [],
|
|
33431
|
-
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
|
|
33432
|
-
},
|
|
33433
|
-
vllm: {
|
|
33434
|
-
type: "openai-compatible",
|
|
33435
|
-
baseURL: "http://localhost:8000/v1",
|
|
33436
|
-
apiKey: "vllm",
|
|
33437
|
-
modelProfiles: [],
|
|
33438
|
-
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
|
|
33439
|
-
},
|
|
33440
|
-
codex: {
|
|
33441
|
-
type: "codex",
|
|
33442
|
-
baseURL: "https://chatgpt.com/backend-api/codex",
|
|
33443
|
-
authStore: "auto",
|
|
33444
|
-
allowApiKeyFallback: false,
|
|
33445
|
-
promptCacheKey: "root-session",
|
|
33446
|
-
catalog: { type: "codex-oauth-models" }
|
|
33447
|
-
},
|
|
33448
|
-
claudecode: {
|
|
33449
|
-
type: "claudecode",
|
|
33450
|
-
runtime: "agent-sdk",
|
|
33451
|
-
cliPath: "~/.local/bin/claude",
|
|
33452
|
-
cwdMode: "session",
|
|
33453
|
-
historyPolicy: "passthrough-resume",
|
|
33454
|
-
onInvalidResume: "fresh",
|
|
33455
|
-
attachmentFallback: "block",
|
|
33456
|
-
allowSubagents: false,
|
|
33457
|
-
sanitizeApiKeyEnv: true,
|
|
33458
|
-
authPreflight: true,
|
|
33459
|
-
useBareMode: false,
|
|
33460
|
-
usageLedgerScope: "process",
|
|
33461
|
-
sessionLock: true,
|
|
33462
|
-
abortPolicy: "record-only",
|
|
33463
|
-
catalog: { type: "claudecode-supported-models" }
|
|
33464
|
-
}
|
|
33465
|
-
}
|
|
33466
|
-
};
|
|
33467
|
-
}
|
|
33468
|
-
async function createUserConfig(options2 = {}) {
|
|
33469
|
-
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
33470
|
-
const content = `${JSON.stringify(defaultUserConfig(options2.defaultProvider), null, 2)}
|
|
33471
|
-
`;
|
|
33472
|
-
if (options2.print) return { path: filePath, created: false, content };
|
|
33473
|
-
const existed = await exists(filePath);
|
|
33474
|
-
if (existed && !options2.force) return { path: filePath, created: false, content: await readFile17(filePath, "utf8"), existed: true };
|
|
33475
|
-
await writeJsonAtomic(filePath, content);
|
|
33476
|
-
return { path: filePath, created: true, content, existed };
|
|
33477
|
-
}
|
|
33478
|
-
async function addProvider(options2) {
|
|
33479
|
-
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
33480
|
-
const config = await readConfigObject(filePath);
|
|
33481
|
-
const providers = objectValue(config.providers);
|
|
33482
|
-
const name = options2.name;
|
|
33483
|
-
if (providers[name] && !options2.force) throw new Error(`Provider ${name} already exists. Use --force to overwrite it.`);
|
|
33484
|
-
providers[name] = providerPreset(options2);
|
|
33485
|
-
config.providers = providers;
|
|
33486
|
-
const content = `${JSON.stringify(config, null, 2)}
|
|
33487
|
-
`;
|
|
33488
|
-
await writeJsonAtomic(filePath, content);
|
|
33489
|
-
return { path: filePath, created: true, content };
|
|
33490
|
-
}
|
|
33491
|
-
async function addModelProfile(options2) {
|
|
33492
|
-
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
33493
|
-
const config = await readConfigObject(filePath);
|
|
33494
|
-
const providers = objectValue(config.providers);
|
|
33495
|
-
const provider = objectValue(providers[options2.provider]);
|
|
33496
|
-
const profiles = Array.isArray(provider.modelProfiles) ? [...provider.modelProfiles] : [];
|
|
33497
|
-
const displayName = options2.displayName ?? options2.name;
|
|
33498
|
-
const existingByName = profiles.findIndex((entry) => entry.name === options2.name);
|
|
33499
|
-
const existingByDisplay = profiles.findIndex((entry) => entry.displayName === displayName);
|
|
33500
|
-
if (existingByName >= 0 && !options2.force) throw new Error(`Profile name "${options2.name}" already exists on provider ${options2.provider}. Use --force to overwrite it.`);
|
|
33501
|
-
if (existingByDisplay >= 0 && existingByDisplay !== existingByName && !options2.force) {
|
|
33502
|
-
throw new Error(`Profile displayName "${displayName}" already exists on provider ${options2.provider} (profile name: ${profiles[existingByDisplay].name}). Use --force or pick a different --display-name.`);
|
|
33503
|
-
}
|
|
33504
|
-
const next = {
|
|
33505
|
-
name: options2.name,
|
|
33506
|
-
displayName,
|
|
33507
|
-
model: options2.model,
|
|
33508
|
-
...options2.baseURL ? { baseURL: options2.baseURL } : {},
|
|
33509
|
-
...options2.apiKey !== void 0 || options2.apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
33510
|
-
...options2.apiKeyEnv ? { apiKeyEnv: options2.apiKeyEnv } : {}
|
|
33511
|
-
};
|
|
33512
|
-
if (existingByName >= 0) profiles[existingByName] = next;
|
|
33513
|
-
else profiles.push(next);
|
|
33514
|
-
provider.modelProfiles = profiles;
|
|
33515
|
-
providers[options2.provider] = provider;
|
|
33516
|
-
config.providers = providers;
|
|
33517
|
-
const content = `${JSON.stringify(config, null, 2)}
|
|
33518
|
-
`;
|
|
33519
|
-
await writeJsonAtomic(filePath, content);
|
|
33520
|
-
return { path: filePath, created: true, content };
|
|
33521
|
-
}
|
|
33522
|
-
async function updateConfigDefaults(options2) {
|
|
33523
|
-
const filePath = expandHome2(options2.path ?? defaultUserConfigPath());
|
|
33524
|
-
const config = await readConfigObject(filePath);
|
|
33525
|
-
const defaultProvider = normalizeOptionalName(options2.defaultProvider);
|
|
33526
|
-
const defaultAgent = normalizeOptionalName(options2.defaultAgent);
|
|
33527
|
-
if (!defaultProvider && !defaultAgent) throw new Error("At least one of defaultProvider or defaultAgent is required.");
|
|
33528
|
-
if (defaultProvider) config.defaultProvider = defaultProvider;
|
|
33529
|
-
if (defaultAgent) config.defaultAgent = defaultAgent;
|
|
33530
|
-
const content = `${JSON.stringify(config, null, 2)}
|
|
33531
|
-
`;
|
|
33532
|
-
await writeJsonAtomic(filePath, content);
|
|
33533
|
-
return { path: filePath, created: true, content };
|
|
33534
|
-
}
|
|
33535
|
-
async function readConfigObject(filePath) {
|
|
33536
|
-
if (!await exists(filePath)) await createUserConfig({ path: filePath });
|
|
33537
|
-
const raw = JSON.parse(await readFile17(filePath, "utf8"));
|
|
33538
|
-
raw.version ??= 2;
|
|
33539
|
-
raw.providers = objectValue(raw.providers);
|
|
33540
|
-
return raw;
|
|
33541
|
-
}
|
|
33542
|
-
function providerPreset(options2) {
|
|
33543
|
-
const preset = options2.preset ?? options2.name;
|
|
33544
|
-
const auth = apiKeyAuthFields(options2);
|
|
33545
|
-
const openAIAuth = options2.authHeader ? { auth: { type: "api-key", header: options2.authHeader } } : {};
|
|
33546
|
-
if (preset === "openai") return { type: "openai-compatible", baseURL: options2.baseURL ?? "https://api.openai.com/v1", ...apiKeyAuthFields(options2, "OPENAI_API_KEY"), ...openAIAuth, catalog: { type: "openai-models", endpoint: `${(options2.baseURL ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/models` } };
|
|
33547
|
-
if (preset === "anthropic") return { type: "anthropic", baseURL: options2.baseURL ?? "https://api.anthropic.com/v1", ...apiKeyAuthFields(options2, "ANTHROPIC_API_KEY"), catalog: { type: "anthropic-models", endpoint: `${(options2.baseURL ?? "https://api.anthropic.com/v1").replace(/\/+$/, "")}/models` } };
|
|
33548
|
-
if (preset === "gemini") {
|
|
33549
|
-
const geminiBase = options2.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
|
|
33550
|
-
return { type: "openai-compatible", baseURL: geminiBase, ...apiKeyAuthFields(options2, "GEMINI_API_KEY"), apiKeyEnvAliases: ["GOOGLE_API_KEY"], ...openAIAuth, catalog: { type: "gemini-openai-models", endpoint: `${geminiBase.replace(/\/+$/, "")}/models` } };
|
|
33551
|
-
}
|
|
33552
|
-
if (preset === "groq") return { type: "openai-compatible", baseURL: options2.baseURL ?? "https://api.groq.com/openai/v1", ...apiKeyAuthFields(options2, "GROQ_API_KEY"), ...openAIAuth, catalog: { type: "groq-models", endpoint: `${(options2.baseURL ?? "https://api.groq.com/openai/v1").replace(/\/+$/, "")}/models` } };
|
|
33553
|
-
if (preset === "azure") {
|
|
33554
|
-
return {
|
|
33555
|
-
type: "openai-compatible",
|
|
33556
|
-
auth: { type: "api-key", header: options2.authHeader ?? "api-key" },
|
|
33557
|
-
...auth,
|
|
33558
|
-
modelProfiles: [
|
|
33559
|
-
{
|
|
33560
|
-
name: "azure-example",
|
|
33561
|
-
displayName: "Azure example",
|
|
33562
|
-
model: "azure-deployment-name",
|
|
33563
|
-
baseURL: options2.baseURL ?? "https://example.openai.azure.com/openai/v1",
|
|
33564
|
-
...options2.apiKey ? {} : { apiKey: "" },
|
|
33565
|
-
apiKeyEnv: options2.apiKeyEnv ?? "AZURE_OPENAI_API_KEY"
|
|
33566
|
-
}
|
|
33567
|
-
]
|
|
33568
|
-
};
|
|
33569
|
-
}
|
|
33570
|
-
if (preset === "lmstudio") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:1234/v1", apiKey: options2.apiKey ?? "lm-studio", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:1234/v1").replace(/\/+$/, "")}/models` } };
|
|
33571
|
-
if (preset === "ollama-local" || preset === "ollama") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:11434/v1", apiKey: options2.apiKey ?? "ollama", modelProfiles: [], quirks: { omitTemperature: true }, catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:11434/v1").replace(/\/+$/, "")}/models` } };
|
|
33572
|
-
if (preset === "ollama-cloud") return { type: "ollama", baseURL: options2.baseURL ?? "https://ollama.com/api", ...apiKeyAuthFields(options2, "OLLAMA_API_KEY"), modelProfiles: [], catalog: { type: "ollama-tags", endpoint: `${(options2.baseURL ?? "https://ollama.com/api").replace(/\/+$/, "")}/tags` } };
|
|
33573
|
-
if (preset === "llamacpp") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:8080/v1", apiKey: options2.apiKey ?? "llama.cpp", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:8080/v1").replace(/\/+$/, "")}/models` } };
|
|
33574
|
-
if (preset === "vllm") return { type: "openai-compatible", baseURL: options2.baseURL ?? "http://localhost:8000/v1", apiKey: options2.apiKey ?? "vllm", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options2.baseURL ?? "http://localhost:8000/v1").replace(/\/+$/, "")}/models` } };
|
|
33575
|
-
if (preset === "codex") return { type: "codex", baseURL: options2.baseURL ?? "https://chatgpt.com/backend-api/codex", authStore: "auto", allowApiKeyFallback: false, promptCacheKey: "root-session", catalog: { type: "codex-oauth-models" } };
|
|
33576
|
-
if (preset === "claudecode") return { type: "claudecode", runtime: "agent-sdk", cliPath: "~/.local/bin/claude", cwdMode: "session", historyPolicy: "passthrough-resume", onInvalidResume: "fresh", attachmentFallback: "block", allowSubagents: false, sanitizeApiKeyEnv: true, authPreflight: true, useBareMode: false, usageLedgerScope: "process", sessionLock: true, abortPolicy: "record-only", catalog: { type: "claudecode-supported-models" } };
|
|
33577
|
-
if ((options2.type ?? preset) === "openai-compatible") {
|
|
33578
|
-
const baseURL = options2.baseURL;
|
|
33579
|
-
if (!baseURL) throw new Error("openai-compatible provider requires --base-url.");
|
|
33580
|
-
return {
|
|
33581
|
-
type: "openai-compatible",
|
|
33582
|
-
baseURL,
|
|
33583
|
-
...auth,
|
|
33584
|
-
...openAIAuth,
|
|
33585
|
-
catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
|
|
33586
|
-
};
|
|
33587
|
-
}
|
|
33588
|
-
throw new Error(`Unknown provider preset: ${preset}`);
|
|
33589
|
-
}
|
|
33590
|
-
function apiKeyAuthFields(options2, defaultApiKeyEnv) {
|
|
33591
|
-
const apiKeyEnv = options2.apiKeyEnv ?? defaultApiKeyEnv;
|
|
33592
|
-
return {
|
|
33593
|
-
...options2.apiKey !== void 0 || apiKeyEnv ? { apiKey: options2.apiKey ?? "" } : {},
|
|
33594
|
-
...apiKeyEnv ? { apiKeyEnv } : {}
|
|
33595
|
-
};
|
|
33596
|
-
}
|
|
33597
|
-
async function writeJsonAtomic(filePath, content) {
|
|
33598
|
-
await mkdir15(path34.dirname(filePath), { recursive: true, mode: 448 });
|
|
33599
|
-
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
33600
|
-
await writeFile14(temp, content, { mode: 384 });
|
|
33601
|
-
await rename6(temp, filePath);
|
|
33602
|
-
await chmod3(filePath, 384).catch(() => void 0);
|
|
33603
|
-
}
|
|
33604
|
-
function detectDefaultProvider() {
|
|
33605
|
-
if (process.env.OPENAI_API_KEY) return "openai";
|
|
33606
|
-
if (process.env.ANTHROPIC_API_KEY) return "anthropic";
|
|
33607
|
-
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) return "gemini";
|
|
33608
|
-
if (process.env.GROQ_API_KEY) return "groq";
|
|
33609
|
-
return "openai";
|
|
33610
|
-
}
|
|
33611
|
-
function objectValue(value) {
|
|
33612
|
-
return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
|
|
33613
|
-
}
|
|
33614
|
-
function normalizeOptionalName(value) {
|
|
33615
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
33616
|
-
}
|
|
33617
|
-
async function exists(filePath) {
|
|
33618
|
-
try {
|
|
33619
|
-
await stat8(filePath);
|
|
33620
|
-
return true;
|
|
33621
|
-
} catch {
|
|
33622
|
-
return false;
|
|
33623
|
-
}
|
|
33624
|
-
}
|
|
33625
|
-
function expandHome2(value) {
|
|
33626
|
-
return resolveExpandedPath(value);
|
|
33627
|
-
}
|
|
33628
|
-
|
|
33629
|
-
// src/config-watcher.ts
|
|
33630
34112
|
var ConfigWatcher = class extends EventEmitter {
|
|
33631
34113
|
#filePath;
|
|
33632
34114
|
#debounceMs;
|
|
@@ -33715,6 +34197,20 @@ if (process.argv.includes("--config-template")) {
|
|
|
33715
34197
|
`);
|
|
33716
34198
|
process.exit(0);
|
|
33717
34199
|
}
|
|
34200
|
+
if (process.argv.includes("--config-read")) {
|
|
34201
|
+
await readConfigMeta(jsonArg("--config-read")).then(
|
|
34202
|
+
(result) => {
|
|
34203
|
+
process.stdout.write(`${JSON.stringify({ ok: true, ...result })}
|
|
34204
|
+
`);
|
|
34205
|
+
process.exit(0);
|
|
34206
|
+
},
|
|
34207
|
+
(error) => {
|
|
34208
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
34209
|
+
`);
|
|
34210
|
+
process.exit(1);
|
|
34211
|
+
}
|
|
34212
|
+
);
|
|
34213
|
+
}
|
|
33718
34214
|
if (process.argv.includes("--config-init")) {
|
|
33719
34215
|
const force = process.argv.includes("--force");
|
|
33720
34216
|
await createUserConfig({ force }).then(
|
|
@@ -33772,6 +34268,34 @@ if (process.argv.includes("--config-set-defaults")) {
|
|
|
33772
34268
|
}
|
|
33773
34269
|
);
|
|
33774
34270
|
}
|
|
34271
|
+
if (process.argv.includes("--config-set-default-model")) {
|
|
34272
|
+
await setDefaultModelProfile(jsonArg("--config-set-default-model")).then(
|
|
34273
|
+
(result) => {
|
|
34274
|
+
process.stdout.write(`${JSON.stringify({ ok: true, path: result.path })}
|
|
34275
|
+
`);
|
|
34276
|
+
process.exit(0);
|
|
34277
|
+
},
|
|
34278
|
+
(error) => {
|
|
34279
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
34280
|
+
`);
|
|
34281
|
+
process.exit(1);
|
|
34282
|
+
}
|
|
34283
|
+
);
|
|
34284
|
+
}
|
|
34285
|
+
if (process.argv.includes("--config-update-provider")) {
|
|
34286
|
+
await updateProviderSettings(jsonArg("--config-update-provider")).then(
|
|
34287
|
+
(result) => {
|
|
34288
|
+
process.stdout.write(`${JSON.stringify({ ok: true, path: result.path })}
|
|
34289
|
+
`);
|
|
34290
|
+
process.exit(0);
|
|
34291
|
+
},
|
|
34292
|
+
(error) => {
|
|
34293
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
34294
|
+
`);
|
|
34295
|
+
process.exit(1);
|
|
34296
|
+
}
|
|
34297
|
+
);
|
|
34298
|
+
}
|
|
33775
34299
|
if (process.argv.includes("--provider-auth-check")) {
|
|
33776
34300
|
await checkProviderAuth(jsonArg("--provider-auth-check")).then(
|
|
33777
34301
|
(result) => {
|
|
@@ -34346,30 +34870,43 @@ function setApiKey(providerName, apiKey, opts = {}) {
|
|
|
34346
34870
|
}
|
|
34347
34871
|
async function sendConfigMeta() {
|
|
34348
34872
|
if (!loadedConfig) return;
|
|
34873
|
+
send({ type: "configMeta", config: await buildConfigMeta(loadedConfig) });
|
|
34874
|
+
}
|
|
34875
|
+
async function readConfigMeta(options2) {
|
|
34876
|
+
const cwd = typeof options2.cwd === "string" && options2.cwd.trim() ? options2.cwd : process.cwd();
|
|
34877
|
+
const configPath = typeof options2.configPath === "string" && options2.configPath.trim() ? options2.configPath : defaultUserConfigPath();
|
|
34878
|
+
let created = false;
|
|
34879
|
+
if (options2.create && !existsSync5(configPath)) {
|
|
34880
|
+
const result = await createUserConfig({ path: configPath });
|
|
34881
|
+
created = result.created;
|
|
34882
|
+
}
|
|
34883
|
+
const config = await loadConfig({ cwd, configPath: options2.configPath });
|
|
34884
|
+
return { path: configPath, created, config: await buildConfigMeta(config) };
|
|
34885
|
+
}
|
|
34886
|
+
async function buildConfigMeta(config) {
|
|
34349
34887
|
const registry = new AgentRegistry();
|
|
34350
|
-
for (const agentDefinition of Object.values(
|
|
34351
|
-
const providerEntries = sortProviderNames(Object.keys(
|
|
34888
|
+
for (const agentDefinition of Object.values(config.agents ?? {})) registry.register(agentDefinition, { scope: "user", trusted: true });
|
|
34889
|
+
const providerEntries = sortProviderNames(Object.keys(config.providers)).map((name) => [name, config.providers[name]]).filter(([, provider]) => provider && !provider.hidden);
|
|
34352
34890
|
const providerOrder = new Map(providerEntries.map(([name], index) => [name, index]));
|
|
34353
34891
|
const providers = (await Promise.all(providerEntries.map(([name, provider]) => providerMeta(name, provider)))).sort((left, right) => {
|
|
34354
34892
|
if (left.available !== right.available) return left.available ? -1 : 1;
|
|
34355
34893
|
return (providerOrder.get(left.name) ?? 0) - (providerOrder.get(right.name) ?? 0);
|
|
34356
34894
|
});
|
|
34357
|
-
|
|
34358
|
-
|
|
34359
|
-
|
|
34360
|
-
|
|
34361
|
-
|
|
34362
|
-
|
|
34363
|
-
|
|
34364
|
-
|
|
34365
|
-
|
|
34366
|
-
|
|
34367
|
-
agents: registry.list().filter((agent) => !agent.hidden && isPrimaryAgent(agent)).map((agent) => ({ name: agent.name, description: agent.description }))
|
|
34368
|
-
}
|
|
34369
|
-
});
|
|
34895
|
+
return {
|
|
34896
|
+
defaultProvider: config.defaultProvider,
|
|
34897
|
+
defaultAgent: config.defaultAgent,
|
|
34898
|
+
context: {
|
|
34899
|
+
main: config.context?.main
|
|
34900
|
+
},
|
|
34901
|
+
providerOrder: providers.map((provider) => provider.name),
|
|
34902
|
+
providers,
|
|
34903
|
+
agents: registry.list().filter((agent) => !agent.hidden && isPrimaryAgent(agent)).map((agent) => ({ name: agent.name, description: agent.description }))
|
|
34904
|
+
};
|
|
34370
34905
|
}
|
|
34371
34906
|
async function providerMeta(name, provider) {
|
|
34372
34907
|
const apiKeyEnv = typeof provider.apiKeyEnv === "string" ? provider.apiKeyEnv : void 0;
|
|
34908
|
+
const configuredProfiles = configuredModelProfiles(provider);
|
|
34909
|
+
const auth = sanitizeProviderAuth2(provider.auth);
|
|
34373
34910
|
const catalog = await listProviderModelCatalog(name, provider).catch((error) => ({ status: "unavailable", providerName: name, models: [], source: "fallback", message: errorMessage4(error) }));
|
|
34374
34911
|
const available = providerCatalogAvailable2(catalog);
|
|
34375
34912
|
const modelProfiles = catalog.models.length ? catalog.models.map((model) => ({
|
|
@@ -34402,7 +34939,16 @@ async function providerMeta(name, provider) {
|
|
|
34402
34939
|
modelLabel: available || !defaultModel ? defaultModel : unavailableLabel2(defaultModel),
|
|
34403
34940
|
models,
|
|
34404
34941
|
modelProfiles: labeledProfiles,
|
|
34942
|
+
configuredModelProfiles: configuredProfiles,
|
|
34943
|
+
configured: {
|
|
34944
|
+
baseURL: provider.baseURL,
|
|
34945
|
+
apiKeyEnv,
|
|
34946
|
+
apiKeyEnvAliases: asStringArray(provider.apiKeyEnvAliases),
|
|
34947
|
+
auth,
|
|
34948
|
+
hasInlineApiKey: typeof provider.apiKey === "string" && provider.apiKey.length > 0
|
|
34949
|
+
},
|
|
34405
34950
|
baseURL: provider.baseURL,
|
|
34951
|
+
auth,
|
|
34406
34952
|
apiKeyEnv,
|
|
34407
34953
|
apiKeyEnvAliases: asStringArray(provider.apiKeyEnvAliases),
|
|
34408
34954
|
catalog: { status: catalog.status, source: catalog.source, message: catalog.message },
|
|
@@ -34411,6 +34957,26 @@ async function providerMeta(name, provider) {
|
|
|
34411
34957
|
requiresApiKey: provider.type === "openai-compatible" || provider.type === "anthropic" || provider.type === "ollama"
|
|
34412
34958
|
};
|
|
34413
34959
|
}
|
|
34960
|
+
function configuredModelProfiles(provider) {
|
|
34961
|
+
if (!Array.isArray(provider.modelProfiles)) return [];
|
|
34962
|
+
return provider.modelProfiles.filter((profile) => profile && typeof profile === "object" && typeof profile.model === "string" && profile.model.trim()).map((profile, index) => {
|
|
34963
|
+
const name = typeof profile.name === "string" && profile.name.trim() ? profile.name.trim() : typeof profile.displayName === "string" && profile.displayName.trim() ? profile.displayName.trim() : profile.model.trim();
|
|
34964
|
+
const displayName = typeof profile.displayName === "string" && profile.displayName.trim() ? profile.displayName.trim() : name;
|
|
34965
|
+
return {
|
|
34966
|
+
name,
|
|
34967
|
+
displayName,
|
|
34968
|
+
model: profile.model.trim(),
|
|
34969
|
+
description: typeof profile.description === "string" ? profile.description : void 0,
|
|
34970
|
+
baseURL: typeof profile.baseURL === "string" ? profile.baseURL : void 0,
|
|
34971
|
+
apiKeyEnv: typeof profile.apiKeyEnv === "string" ? profile.apiKeyEnv : void 0,
|
|
34972
|
+
apiKeyEnvAliases: asStringArray(profile.apiKeyEnvAliases),
|
|
34973
|
+
auth: sanitizeProviderAuth2(profile.auth),
|
|
34974
|
+
hasInlineApiKey: typeof profile.apiKey === "string" && profile.apiKey.length > 0,
|
|
34975
|
+
source: "config",
|
|
34976
|
+
isDefault: index === 0
|
|
34977
|
+
};
|
|
34978
|
+
});
|
|
34979
|
+
}
|
|
34414
34980
|
function providerCatalogAvailable2(catalog) {
|
|
34415
34981
|
return catalog?.status === "ready" && Array.isArray(catalog.models) && catalog.models.length > 0;
|
|
34416
34982
|
}
|
|
@@ -34420,6 +34986,12 @@ function unavailableLabel2(value) {
|
|
|
34420
34986
|
function asStringArray(value) {
|
|
34421
34987
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
34422
34988
|
}
|
|
34989
|
+
function sanitizeProviderAuth2(auth) {
|
|
34990
|
+
if (!auth || typeof auth !== "object" || Array.isArray(auth)) return void 0;
|
|
34991
|
+
const type = typeof auth.type === "string" ? auth.type : void 0;
|
|
34992
|
+
const header = typeof auth.header === "string" ? auth.header : void 0;
|
|
34993
|
+
return type || header ? { type, header } : void 0;
|
|
34994
|
+
}
|
|
34423
34995
|
function contextOptions(value) {
|
|
34424
34996
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
34425
34997
|
const context = {};
|