demian-cli 1.1.1 → 1.1.2
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.mjs +1278 -429
- package/dist/vscode-worker.mjs +978 -429
- package/docs/ko/README.md +39 -3
- package/package.json +1 -1
package/dist/tui.mjs
CHANGED
|
@@ -147,6 +147,382 @@ var init_path_expansion = __esm({
|
|
|
147
147
|
}
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
+
// src/config-scaffold.ts
|
|
151
|
+
import { chmod, mkdir, readFile as readFile2, rename, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
152
|
+
import os3 from "node:os";
|
|
153
|
+
import path3 from "node:path";
|
|
154
|
+
function defaultUserConfigPath() {
|
|
155
|
+
return path3.join(os3.homedir(), ".demian", "config.json");
|
|
156
|
+
}
|
|
157
|
+
function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
|
|
158
|
+
return {
|
|
159
|
+
version: 2,
|
|
160
|
+
defaultProvider,
|
|
161
|
+
providers: {
|
|
162
|
+
openai: {
|
|
163
|
+
type: "openai-compatible",
|
|
164
|
+
baseURL: "https://api.openai.com/v1",
|
|
165
|
+
apiKey: "",
|
|
166
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
167
|
+
catalog: { type: "openai-models", endpoint: "https://api.openai.com/v1/models" }
|
|
168
|
+
},
|
|
169
|
+
anthropic: {
|
|
170
|
+
type: "anthropic",
|
|
171
|
+
baseURL: "https://api.anthropic.com/v1",
|
|
172
|
+
apiKey: "",
|
|
173
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
174
|
+
catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
|
|
175
|
+
},
|
|
176
|
+
gemini: {
|
|
177
|
+
type: "openai-compatible",
|
|
178
|
+
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
179
|
+
apiKey: "",
|
|
180
|
+
apiKeyEnv: "GEMINI_API_KEY",
|
|
181
|
+
apiKeyEnvAliases: ["GOOGLE_API_KEY"],
|
|
182
|
+
catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
|
|
183
|
+
},
|
|
184
|
+
groq: {
|
|
185
|
+
type: "openai-compatible",
|
|
186
|
+
baseURL: "https://api.groq.com/openai/v1",
|
|
187
|
+
apiKey: "",
|
|
188
|
+
apiKeyEnv: "GROQ_API_KEY",
|
|
189
|
+
catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
|
|
190
|
+
},
|
|
191
|
+
azure: {
|
|
192
|
+
type: "openai-compatible",
|
|
193
|
+
auth: { type: "api-key", header: "api-key" },
|
|
194
|
+
modelProfiles: [
|
|
195
|
+
{
|
|
196
|
+
name: "azure-example",
|
|
197
|
+
displayName: "Azure example",
|
|
198
|
+
model: "azure-deployment-name",
|
|
199
|
+
baseURL: "https://example.openai.azure.com/openai/v1",
|
|
200
|
+
apiKey: "",
|
|
201
|
+
apiKeyEnv: "AZURE_OPENAI_API_KEY"
|
|
202
|
+
}
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
lmstudio: {
|
|
206
|
+
type: "openai-compatible",
|
|
207
|
+
baseURL: "http://localhost:1234/v1",
|
|
208
|
+
apiKey: "lm-studio",
|
|
209
|
+
modelProfiles: [],
|
|
210
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
|
|
211
|
+
},
|
|
212
|
+
"ollama-local": {
|
|
213
|
+
type: "openai-compatible",
|
|
214
|
+
baseURL: "http://localhost:11434/v1",
|
|
215
|
+
apiKey: "ollama",
|
|
216
|
+
modelProfiles: [],
|
|
217
|
+
quirks: { omitTemperature: true },
|
|
218
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
|
|
219
|
+
},
|
|
220
|
+
"ollama-cloud": {
|
|
221
|
+
type: "ollama",
|
|
222
|
+
baseURL: "https://ollama.com/api",
|
|
223
|
+
apiKey: "",
|
|
224
|
+
apiKeyEnv: "OLLAMA_API_KEY",
|
|
225
|
+
modelProfiles: [],
|
|
226
|
+
catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
|
|
227
|
+
},
|
|
228
|
+
llamacpp: {
|
|
229
|
+
type: "openai-compatible",
|
|
230
|
+
baseURL: "http://localhost:8080/v1",
|
|
231
|
+
apiKey: "llama.cpp",
|
|
232
|
+
modelProfiles: [],
|
|
233
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
|
|
234
|
+
},
|
|
235
|
+
vllm: {
|
|
236
|
+
type: "openai-compatible",
|
|
237
|
+
baseURL: "http://localhost:8000/v1",
|
|
238
|
+
apiKey: "vllm",
|
|
239
|
+
modelProfiles: [],
|
|
240
|
+
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
|
|
241
|
+
},
|
|
242
|
+
codex: {
|
|
243
|
+
type: "codex",
|
|
244
|
+
baseURL: "https://chatgpt.com/backend-api/codex",
|
|
245
|
+
authStore: "auto",
|
|
246
|
+
allowApiKeyFallback: false,
|
|
247
|
+
promptCacheKey: "root-session",
|
|
248
|
+
catalog: { type: "codex-oauth-models" }
|
|
249
|
+
},
|
|
250
|
+
claudecode: {
|
|
251
|
+
type: "claudecode",
|
|
252
|
+
runtime: "agent-sdk",
|
|
253
|
+
cliPath: "~/.local/bin/claude",
|
|
254
|
+
cwdMode: "session",
|
|
255
|
+
historyPolicy: "passthrough-resume",
|
|
256
|
+
onInvalidResume: "fresh",
|
|
257
|
+
attachmentFallback: "block",
|
|
258
|
+
allowSubagents: false,
|
|
259
|
+
sanitizeApiKeyEnv: true,
|
|
260
|
+
authPreflight: true,
|
|
261
|
+
useBareMode: false,
|
|
262
|
+
usageLedgerScope: "process",
|
|
263
|
+
sessionLock: true,
|
|
264
|
+
abortPolicy: "record-only",
|
|
265
|
+
catalog: { type: "claudecode-supported-models" }
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
async function createUserConfig(options = {}) {
|
|
271
|
+
const filePath = expandHome(options.path ?? defaultUserConfigPath());
|
|
272
|
+
const content = `${JSON.stringify(defaultUserConfig(options.defaultProvider), null, 2)}
|
|
273
|
+
`;
|
|
274
|
+
if (options.print) return { path: filePath, created: false, content };
|
|
275
|
+
const existed = await exists(filePath);
|
|
276
|
+
if (existed && !options.force) return { path: filePath, created: false, content: await readFile2(filePath, "utf8"), existed: true };
|
|
277
|
+
await writeJsonAtomic(filePath, content);
|
|
278
|
+
return { path: filePath, created: true, content, existed };
|
|
279
|
+
}
|
|
280
|
+
async function addProvider(options) {
|
|
281
|
+
const filePath = expandHome(options.path ?? defaultUserConfigPath());
|
|
282
|
+
const config = await readConfigObject(filePath);
|
|
283
|
+
const providers = objectValue(config.providers);
|
|
284
|
+
const name = options.name;
|
|
285
|
+
if (providers[name] && !options.force) throw new Error(`Provider ${name} already exists. Use --force to overwrite it.`);
|
|
286
|
+
providers[name] = providerPreset(options);
|
|
287
|
+
config.providers = providers;
|
|
288
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
289
|
+
`;
|
|
290
|
+
await writeJsonAtomic(filePath, content);
|
|
291
|
+
return { path: filePath, created: true, content };
|
|
292
|
+
}
|
|
293
|
+
async function addModelProfile(options) {
|
|
294
|
+
const filePath = expandHome(options.path ?? defaultUserConfigPath());
|
|
295
|
+
const config = await readConfigObject(filePath);
|
|
296
|
+
const providers = objectValue(config.providers);
|
|
297
|
+
const provider = objectValue(providers[options.provider]);
|
|
298
|
+
const profiles = Array.isArray(provider.modelProfiles) ? [...provider.modelProfiles] : [];
|
|
299
|
+
const displayName = options.displayName ?? options.name;
|
|
300
|
+
const previousName = normalizeOptionalName(options.previousName);
|
|
301
|
+
const existingByPreviousName = previousName ? profiles.findIndex((entry) => entry.name === previousName) : -1;
|
|
302
|
+
const existingByName = profiles.findIndex((entry) => entry.name === options.name);
|
|
303
|
+
const existingByDisplay = profiles.findIndex((entry) => entry.displayName === displayName);
|
|
304
|
+
const updateIndex = existingByPreviousName >= 0 ? existingByPreviousName : existingByName;
|
|
305
|
+
if (existingByName >= 0 && existingByName !== updateIndex) throw new Error(`Profile name "${options.name}" already exists on provider ${options.provider}. Pick a different profile name.`);
|
|
306
|
+
if (existingByName >= 0 && existingByPreviousName < 0 && !options.force) throw new Error(`Profile name "${options.name}" already exists on provider ${options.provider}. Use --force to overwrite it.`);
|
|
307
|
+
if (existingByDisplay >= 0 && existingByDisplay !== updateIndex && !options.force) {
|
|
308
|
+
throw new Error(`Profile displayName "${displayName}" already exists on provider ${options.provider} (profile name: ${profiles[existingByDisplay].name}). Use --force or pick a different --display-name.`);
|
|
309
|
+
}
|
|
310
|
+
const next = {
|
|
311
|
+
name: options.name,
|
|
312
|
+
displayName,
|
|
313
|
+
model: options.model,
|
|
314
|
+
...options.baseURL ? { baseURL: options.baseURL } : {},
|
|
315
|
+
...options.apiKey !== void 0 || options.apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
|
|
316
|
+
...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {}
|
|
317
|
+
};
|
|
318
|
+
if (updateIndex >= 0) profiles[updateIndex] = next;
|
|
319
|
+
else profiles.push(next);
|
|
320
|
+
provider.modelProfiles = profiles;
|
|
321
|
+
providers[options.provider] = provider;
|
|
322
|
+
config.providers = providers;
|
|
323
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
324
|
+
`;
|
|
325
|
+
await writeJsonAtomic(filePath, content);
|
|
326
|
+
return { path: filePath, created: true, content };
|
|
327
|
+
}
|
|
328
|
+
async function updateConfigDefaults(options) {
|
|
329
|
+
const filePath = expandHome(options.path ?? defaultUserConfigPath());
|
|
330
|
+
const config = await readConfigObject(filePath);
|
|
331
|
+
const defaultProvider = normalizeOptionalName(options.defaultProvider);
|
|
332
|
+
const defaultAgent = normalizeOptionalName(options.defaultAgent);
|
|
333
|
+
if (!defaultProvider && !defaultAgent) throw new Error("At least one of defaultProvider or defaultAgent is required.");
|
|
334
|
+
if (defaultProvider) config.defaultProvider = defaultProvider;
|
|
335
|
+
if (defaultAgent) config.defaultAgent = defaultAgent;
|
|
336
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
337
|
+
`;
|
|
338
|
+
await writeJsonAtomic(filePath, content);
|
|
339
|
+
return { path: filePath, created: true, content };
|
|
340
|
+
}
|
|
341
|
+
async function setDefaultModelProfile(options) {
|
|
342
|
+
const filePath = expandHome(options.path ?? defaultUserConfigPath());
|
|
343
|
+
const config = await readConfigObject(filePath);
|
|
344
|
+
const providers = objectValue(config.providers);
|
|
345
|
+
const provider = providers[options.provider];
|
|
346
|
+
if (!provider || typeof provider !== "object" || Array.isArray(provider)) throw new Error(`Provider ${options.provider} does not exist.`);
|
|
347
|
+
const providerConfig = { ...provider };
|
|
348
|
+
const profiles = Array.isArray(providerConfig.modelProfiles) ? [...providerConfig.modelProfiles] : [];
|
|
349
|
+
const requestedName = normalizeOptionalName(options.profileName ?? options.name);
|
|
350
|
+
const requestedModel = normalizeOptionalName(options.model);
|
|
351
|
+
const requestedDisplayName = normalizeOptionalName(options.displayName) ?? requestedModel ?? requestedName;
|
|
352
|
+
let index = profiles.findIndex((entry) => requestedName && entry.name === requestedName);
|
|
353
|
+
if (index < 0 && requestedModel) index = profiles.findIndex((entry) => entry.model === requestedModel);
|
|
354
|
+
if (index < 0 && requestedDisplayName) index = profiles.findIndex((entry) => entry.displayName === requestedDisplayName);
|
|
355
|
+
const profile = index >= 0 ? {
|
|
356
|
+
...profiles[index],
|
|
357
|
+
...requestedName ? { name: requestedName } : {},
|
|
358
|
+
...requestedDisplayName ? { displayName: requestedDisplayName } : {},
|
|
359
|
+
...requestedModel ? { model: requestedModel } : {},
|
|
360
|
+
...options.baseURL ? { baseURL: options.baseURL } : {},
|
|
361
|
+
...options.apiKey !== void 0 || options.apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
|
|
362
|
+
...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {}
|
|
363
|
+
} : newModelProfile({
|
|
364
|
+
name: requestedName,
|
|
365
|
+
displayName: requestedDisplayName,
|
|
366
|
+
model: requestedModel ?? requestedName,
|
|
367
|
+
baseURL: options.baseURL,
|
|
368
|
+
apiKey: options.apiKey,
|
|
369
|
+
apiKeyEnv: options.apiKeyEnv
|
|
370
|
+
});
|
|
371
|
+
if (!profile.name || typeof profile.name !== "string") throw new Error("Model profile name is required.");
|
|
372
|
+
if (!profile.model || typeof profile.model !== "string") throw new Error("Model ID is required.");
|
|
373
|
+
if (index >= 0) profiles.splice(index, 1);
|
|
374
|
+
profiles.unshift(profile);
|
|
375
|
+
providerConfig.modelProfiles = profiles;
|
|
376
|
+
providers[options.provider] = providerConfig;
|
|
377
|
+
config.providers = providers;
|
|
378
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
379
|
+
`;
|
|
380
|
+
await writeJsonAtomic(filePath, content);
|
|
381
|
+
return { path: filePath, created: true, content };
|
|
382
|
+
}
|
|
383
|
+
async function updateProviderSettings(options) {
|
|
384
|
+
const filePath = expandHome(options.path ?? defaultUserConfigPath());
|
|
385
|
+
const config = await readConfigObject(filePath);
|
|
386
|
+
const providers = objectValue(config.providers);
|
|
387
|
+
const provider = providers[options.provider];
|
|
388
|
+
if (!provider || typeof provider !== "object" || Array.isArray(provider)) throw new Error(`Provider ${options.provider} does not exist.`);
|
|
389
|
+
const providerConfig = { ...provider };
|
|
390
|
+
if (options.baseURL !== void 0) setOrDelete(providerConfig, "baseURL", options.baseURL);
|
|
391
|
+
if (options.apiKey !== void 0) setOrDelete(providerConfig, "apiKey", options.apiKey);
|
|
392
|
+
if (options.apiKeyEnv !== void 0) setOrDelete(providerConfig, "apiKeyEnv", options.apiKeyEnv);
|
|
393
|
+
if (options.authHeader !== void 0) {
|
|
394
|
+
const header = options.authHeader.trim();
|
|
395
|
+
if (header) providerConfig.auth = { type: "api-key", header };
|
|
396
|
+
else delete providerConfig.auth;
|
|
397
|
+
}
|
|
398
|
+
providers[options.provider] = providerConfig;
|
|
399
|
+
config.providers = providers;
|
|
400
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
401
|
+
`;
|
|
402
|
+
await writeJsonAtomic(filePath, content);
|
|
403
|
+
return { path: filePath, created: true, content };
|
|
404
|
+
}
|
|
405
|
+
async function readConfigObject(filePath) {
|
|
406
|
+
if (!await exists(filePath)) await createUserConfig({ path: filePath });
|
|
407
|
+
const raw = JSON.parse(await readFile2(filePath, "utf8"));
|
|
408
|
+
raw.version ??= 2;
|
|
409
|
+
raw.providers = objectValue(raw.providers);
|
|
410
|
+
return raw;
|
|
411
|
+
}
|
|
412
|
+
function providerPreset(options) {
|
|
413
|
+
const preset = options.preset ?? options.name;
|
|
414
|
+
const auth = apiKeyAuthFields(options);
|
|
415
|
+
const openAIAuth = options.authHeader ? { auth: { type: "api-key", header: options.authHeader } } : {};
|
|
416
|
+
if (preset === "openai") return { type: "openai-compatible", baseURL: options.baseURL ?? "https://api.openai.com/v1", ...apiKeyAuthFields(options, "OPENAI_API_KEY"), ...openAIAuth, catalog: { type: "openai-models", endpoint: `${(options.baseURL ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/models` } };
|
|
417
|
+
if (preset === "anthropic") return { type: "anthropic", baseURL: options.baseURL ?? "https://api.anthropic.com/v1", ...apiKeyAuthFields(options, "ANTHROPIC_API_KEY"), catalog: { type: "anthropic-models", endpoint: `${(options.baseURL ?? "https://api.anthropic.com/v1").replace(/\/+$/, "")}/models` } };
|
|
418
|
+
if (preset === "gemini") {
|
|
419
|
+
const geminiBase = options.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
|
|
420
|
+
return { type: "openai-compatible", baseURL: geminiBase, ...apiKeyAuthFields(options, "GEMINI_API_KEY"), apiKeyEnvAliases: ["GOOGLE_API_KEY"], ...openAIAuth, catalog: { type: "gemini-openai-models", endpoint: `${geminiBase.replace(/\/+$/, "")}/models` } };
|
|
421
|
+
}
|
|
422
|
+
if (preset === "groq") return { type: "openai-compatible", baseURL: options.baseURL ?? "https://api.groq.com/openai/v1", ...apiKeyAuthFields(options, "GROQ_API_KEY"), ...openAIAuth, catalog: { type: "groq-models", endpoint: `${(options.baseURL ?? "https://api.groq.com/openai/v1").replace(/\/+$/, "")}/models` } };
|
|
423
|
+
if (preset === "azure") {
|
|
424
|
+
return {
|
|
425
|
+
type: "openai-compatible",
|
|
426
|
+
auth: { type: "api-key", header: options.authHeader ?? "api-key" },
|
|
427
|
+
...auth,
|
|
428
|
+
modelProfiles: [
|
|
429
|
+
{
|
|
430
|
+
name: "azure-example",
|
|
431
|
+
displayName: "Azure example",
|
|
432
|
+
model: "azure-deployment-name",
|
|
433
|
+
baseURL: options.baseURL ?? "https://example.openai.azure.com/openai/v1",
|
|
434
|
+
...options.apiKey ? {} : { apiKey: "" },
|
|
435
|
+
apiKeyEnv: options.apiKeyEnv ?? "AZURE_OPENAI_API_KEY"
|
|
436
|
+
}
|
|
437
|
+
]
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
if (preset === "lmstudio") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:1234/v1", apiKey: options.apiKey ?? "lm-studio", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:1234/v1").replace(/\/+$/, "")}/models` } };
|
|
441
|
+
if (preset === "ollama-local" || preset === "ollama") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:11434/v1", apiKey: options.apiKey ?? "ollama", modelProfiles: [], quirks: { omitTemperature: true }, catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:11434/v1").replace(/\/+$/, "")}/models` } };
|
|
442
|
+
if (preset === "ollama-cloud") return { type: "ollama", baseURL: options.baseURL ?? "https://ollama.com/api", ...apiKeyAuthFields(options, "OLLAMA_API_KEY"), modelProfiles: [], catalog: { type: "ollama-tags", endpoint: `${(options.baseURL ?? "https://ollama.com/api").replace(/\/+$/, "")}/tags` } };
|
|
443
|
+
if (preset === "llamacpp") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:8080/v1", apiKey: options.apiKey ?? "llama.cpp", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:8080/v1").replace(/\/+$/, "")}/models` } };
|
|
444
|
+
if (preset === "vllm") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:8000/v1", apiKey: options.apiKey ?? "vllm", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:8000/v1").replace(/\/+$/, "")}/models` } };
|
|
445
|
+
if (preset === "codex") return { type: "codex", baseURL: options.baseURL ?? "https://chatgpt.com/backend-api/codex", authStore: "auto", allowApiKeyFallback: false, promptCacheKey: "root-session", catalog: { type: "codex-oauth-models" } };
|
|
446
|
+
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" } };
|
|
447
|
+
if ((options.type ?? preset) === "openai-compatible") {
|
|
448
|
+
const baseURL = options.baseURL;
|
|
449
|
+
if (!baseURL) throw new Error("openai-compatible provider requires --base-url.");
|
|
450
|
+
return {
|
|
451
|
+
type: "openai-compatible",
|
|
452
|
+
baseURL,
|
|
453
|
+
...auth,
|
|
454
|
+
...openAIAuth,
|
|
455
|
+
catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
throw new Error(`Unknown provider preset: ${preset}`);
|
|
459
|
+
}
|
|
460
|
+
function apiKeyAuthFields(options, defaultApiKeyEnv) {
|
|
461
|
+
const apiKeyEnv = options.apiKeyEnv ?? defaultApiKeyEnv;
|
|
462
|
+
return {
|
|
463
|
+
...options.apiKey !== void 0 || apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
|
|
464
|
+
...apiKeyEnv ? { apiKeyEnv } : {}
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
async function writeJsonAtomic(filePath, content) {
|
|
468
|
+
await mkdir(path3.dirname(filePath), { recursive: true, mode: 448 });
|
|
469
|
+
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
470
|
+
await writeFile2(temp, content, { mode: 384 });
|
|
471
|
+
await rename(temp, filePath);
|
|
472
|
+
await chmod(filePath, 384).catch(() => void 0);
|
|
473
|
+
}
|
|
474
|
+
function detectDefaultProvider() {
|
|
475
|
+
if (process.env.OPENAI_API_KEY) return "openai";
|
|
476
|
+
if (process.env.ANTHROPIC_API_KEY) return "anthropic";
|
|
477
|
+
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) return "gemini";
|
|
478
|
+
if (process.env.GROQ_API_KEY) return "groq";
|
|
479
|
+
return "openai";
|
|
480
|
+
}
|
|
481
|
+
function objectValue(value) {
|
|
482
|
+
return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
|
|
483
|
+
}
|
|
484
|
+
function normalizeOptionalName(value) {
|
|
485
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
486
|
+
}
|
|
487
|
+
function newModelProfile(options) {
|
|
488
|
+
const model = options.model?.trim();
|
|
489
|
+
if (!model) throw new Error("Model ID is required.");
|
|
490
|
+
const name = options.name?.trim() || modelProfileNameFromModel(model);
|
|
491
|
+
return {
|
|
492
|
+
name,
|
|
493
|
+
displayName: options.displayName?.trim() || name,
|
|
494
|
+
model,
|
|
495
|
+
...options.baseURL ? { baseURL: options.baseURL } : {},
|
|
496
|
+
...options.apiKey !== void 0 || options.apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
|
|
497
|
+
...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {}
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
function modelProfileNameFromModel(model) {
|
|
501
|
+
return model.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "model";
|
|
502
|
+
}
|
|
503
|
+
function setOrDelete(target, key, value) {
|
|
504
|
+
const trimmed = value.trim();
|
|
505
|
+
if (trimmed) target[key] = trimmed;
|
|
506
|
+
else delete target[key];
|
|
507
|
+
}
|
|
508
|
+
async function exists(filePath) {
|
|
509
|
+
try {
|
|
510
|
+
await stat(filePath);
|
|
511
|
+
return true;
|
|
512
|
+
} catch {
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function expandHome(value) {
|
|
517
|
+
return resolveExpandedPath(value);
|
|
518
|
+
}
|
|
519
|
+
var init_config_scaffold = __esm({
|
|
520
|
+
"src/config-scaffold.ts"() {
|
|
521
|
+
"use strict";
|
|
522
|
+
init_path_expansion();
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
|
|
150
526
|
// src/providers/retry.ts
|
|
151
527
|
async function chatWithRetry(operation, options) {
|
|
152
528
|
const maxRetries = options.maxRetries ?? 5;
|
|
@@ -25628,10 +26004,30 @@ var init_tool_summary = __esm({
|
|
|
25628
26004
|
// src/ui/tui/store.ts
|
|
25629
26005
|
var store_exports = {};
|
|
25630
26006
|
__export(store_exports, {
|
|
26007
|
+
TUI_CONFIG_ACTION_PREFIX: () => TUI_CONFIG_ACTION_PREFIX,
|
|
26008
|
+
TUI_SESSION_ACTION_PREFIX: () => TUI_SESSION_ACTION_PREFIX,
|
|
25631
26009
|
TuiStore: () => TuiStore,
|
|
25632
26010
|
buildClaudeCodePlanExecutionPrompt: () => buildClaudeCodePlanExecutionPrompt,
|
|
26011
|
+
parseTuiConfigAction: () => parseTuiConfigAction,
|
|
26012
|
+
parseTuiSessionAction: () => parseTuiSessionAction,
|
|
25633
26013
|
streamingLines: () => streamingLines
|
|
25634
26014
|
});
|
|
26015
|
+
function parseTuiConfigAction(prompt) {
|
|
26016
|
+
if (!prompt.startsWith(TUI_CONFIG_ACTION_PREFIX)) return void 0;
|
|
26017
|
+
try {
|
|
26018
|
+
return JSON.parse(prompt.slice(TUI_CONFIG_ACTION_PREFIX.length));
|
|
26019
|
+
} catch {
|
|
26020
|
+
return void 0;
|
|
26021
|
+
}
|
|
26022
|
+
}
|
|
26023
|
+
function parseTuiSessionAction(prompt) {
|
|
26024
|
+
if (!prompt.startsWith(TUI_SESSION_ACTION_PREFIX)) return void 0;
|
|
26025
|
+
try {
|
|
26026
|
+
return JSON.parse(prompt.slice(TUI_SESSION_ACTION_PREFIX.length));
|
|
26027
|
+
} catch {
|
|
26028
|
+
return void 0;
|
|
26029
|
+
}
|
|
26030
|
+
}
|
|
25635
26031
|
function toTuiWorkPlan(plan) {
|
|
25636
26032
|
return {
|
|
25637
26033
|
planId: plan.planId,
|
|
@@ -25791,6 +26187,25 @@ function cloneContextEfficiency(context) {
|
|
|
25791
26187
|
lastCompaction: context.lastCompaction ? { ...context.lastCompaction } : void 0
|
|
25792
26188
|
};
|
|
25793
26189
|
}
|
|
26190
|
+
function configProviderFieldValue(provider, field) {
|
|
26191
|
+
if (field === "baseURL") return provider.baseURL ?? "";
|
|
26192
|
+
if (field === "apiKeyEnv") return provider.apiKeyEnv ?? "";
|
|
26193
|
+
return provider.auth?.header ?? "";
|
|
26194
|
+
}
|
|
26195
|
+
function defaultConfigProviderPresets() {
|
|
26196
|
+
return [
|
|
26197
|
+
{ label: "OpenAI", preset: "openai", name: "openai", apiKeyEnv: "OPENAI_API_KEY", detail: "OpenAI API" },
|
|
26198
|
+
{ label: "Anthropic", preset: "anthropic", name: "anthropic", apiKeyEnv: "ANTHROPIC_API_KEY", detail: "Anthropic API" },
|
|
26199
|
+
{ label: "Gemini", preset: "gemini", name: "gemini", apiKeyEnv: "GEMINI_API_KEY", detail: "Google Gemini OpenAI-compatible API" },
|
|
26200
|
+
{ label: "Groq", preset: "groq", name: "groq", apiKeyEnv: "GROQ_API_KEY", detail: "Groq Cloud" },
|
|
26201
|
+
{ label: "Codex", preset: "codex", name: "codex", detail: "ChatGPT Codex OAuth" },
|
|
26202
|
+
{ label: "Claude Code", preset: "claudecode", name: "claudecode", detail: "Local Claude Code runtime" },
|
|
26203
|
+
{ label: "Ollama local", preset: "ollama-local", name: "ollama-local", detail: "http://localhost:11434" },
|
|
26204
|
+
{ label: "LM Studio", preset: "lmstudio", name: "lmstudio", detail: "http://localhost:1234" },
|
|
26205
|
+
{ label: "llama.cpp", preset: "llamacpp", name: "llamacpp", detail: "http://localhost:8080" },
|
|
26206
|
+
{ label: "vLLM", preset: "vllm", name: "vllm", detail: "http://localhost:8000" }
|
|
26207
|
+
];
|
|
26208
|
+
}
|
|
25794
26209
|
function cloneToolRunDetails(details) {
|
|
25795
26210
|
if (!details) return void 0;
|
|
25796
26211
|
return {
|
|
@@ -25892,7 +26307,7 @@ function buildClaudeCodePlanExecutionPrompt(planText, requestText) {
|
|
|
25892
26307
|
function streamingLines(markdown) {
|
|
25893
26308
|
return renderMarkdownFallback(markdown, { width: 100 }).lines;
|
|
25894
26309
|
}
|
|
25895
|
-
var TuiStore;
|
|
26310
|
+
var TUI_CONFIG_ACTION_PREFIX, TUI_SESSION_ACTION_PREFIX, TuiStore;
|
|
25896
26311
|
var init_store = __esm({
|
|
25897
26312
|
"src/ui/tui/store.ts"() {
|
|
25898
26313
|
"use strict";
|
|
@@ -25900,6 +26315,8 @@ var init_store = __esm({
|
|
|
25900
26315
|
init_commands();
|
|
25901
26316
|
init_render();
|
|
25902
26317
|
init_tool_summary();
|
|
26318
|
+
TUI_CONFIG_ACTION_PREFIX = "demian-config:";
|
|
26319
|
+
TUI_SESSION_ACTION_PREFIX = "demian-session:";
|
|
25903
26320
|
TuiStore = class {
|
|
25904
26321
|
#state = {
|
|
25905
26322
|
status: {},
|
|
@@ -25908,8 +26325,14 @@ var init_store = __esm({
|
|
|
25908
26325
|
inputMode: "starting",
|
|
25909
26326
|
sessionOptions: [],
|
|
25910
26327
|
sessionCursor: 0,
|
|
26328
|
+
sessionCancelBehavior: "exit",
|
|
25911
26329
|
providerOptions: [],
|
|
25912
26330
|
providerCursor: 0,
|
|
26331
|
+
configModelCursor: 0,
|
|
26332
|
+
configAddProviderCursor: 0,
|
|
26333
|
+
configModelInput: "",
|
|
26334
|
+
configProviderInput: "",
|
|
26335
|
+
configProviderPresets: defaultConfigProviderPresets(),
|
|
25913
26336
|
agentOptions: [],
|
|
25914
26337
|
agentCursor: 0,
|
|
25915
26338
|
permissionPreset: "auto",
|
|
@@ -25945,6 +26368,7 @@ var init_store = __esm({
|
|
|
25945
26368
|
#pendingPermissionTimeout;
|
|
25946
26369
|
#syntheticPermissionId = 0;
|
|
25947
26370
|
#activeClaudeCodePlan;
|
|
26371
|
+
#notifyTimer;
|
|
25948
26372
|
snapshot() {
|
|
25949
26373
|
return {
|
|
25950
26374
|
...this.#state,
|
|
@@ -25952,6 +26376,7 @@ var init_store = __esm({
|
|
|
25952
26376
|
selection: this.#state.selection ? { ...this.#state.selection } : void 0,
|
|
25953
26377
|
sessionOptions: this.#state.sessionOptions.map((option) => ({ ...option })),
|
|
25954
26378
|
providerOptions: this.#state.providerOptions.map((option) => ({ ...option })),
|
|
26379
|
+
configProviderPresets: this.#state.configProviderPresets.map((option) => ({ ...option })),
|
|
25955
26380
|
agentOptions: this.#state.agentOptions.map((option) => ({ ...option })),
|
|
25956
26381
|
blocks: this.#state.blocks.map((block) => ({ ...block, lines: [...block.lines], toolDetails: cloneToolRunDetails(block.toolDetails), goalWork: cloneGoalWork(block.goalWork), cowork: cloneCoworkGroup(block.cowork) })),
|
|
25957
26382
|
warnings: [...this.#state.warnings],
|
|
@@ -26173,6 +26598,10 @@ var init_store = __esm({
|
|
|
26173
26598
|
agent: context.agent,
|
|
26174
26599
|
cwd: context.cwd
|
|
26175
26600
|
};
|
|
26601
|
+
this.#state.configPath = context.configPath;
|
|
26602
|
+
this.#state.configCreated = context.configCreated;
|
|
26603
|
+
this.#state.configDefaultProvider = context.configDefaultProvider;
|
|
26604
|
+
this.#state.configMessage = context.configMessage;
|
|
26176
26605
|
if (context.contextEfficiency) {
|
|
26177
26606
|
this.#state.contextEfficiency = {
|
|
26178
26607
|
...this.#state.contextEfficiency ?? { selectedTools: [] },
|
|
@@ -26204,7 +26633,7 @@ var init_store = __esm({
|
|
|
26204
26633
|
currentPermissionPreset() {
|
|
26205
26634
|
return this.#state.permissionPreset;
|
|
26206
26635
|
}
|
|
26207
|
-
requestSessionSelection(options) {
|
|
26636
|
+
requestSessionSelection(options, behavior = {}) {
|
|
26208
26637
|
if (this.#exitRequested) return Promise.resolve({ kind: "exit" });
|
|
26209
26638
|
const normalized = options.length > 0 ? options.map((option) => ({ ...option })) : [{ kind: "new", title: "New session", status: "ready", currentWorkspace: true }];
|
|
26210
26639
|
return new Promise((resolve) => {
|
|
@@ -26212,6 +26641,7 @@ var init_store = __esm({
|
|
|
26212
26641
|
this.#state.inputMode = "session";
|
|
26213
26642
|
this.#state.sessionOptions = normalized;
|
|
26214
26643
|
this.#state.sessionCursor = 0;
|
|
26644
|
+
this.#state.sessionCancelBehavior = behavior.cancel ?? "exit";
|
|
26215
26645
|
this.#state.promptInput = "";
|
|
26216
26646
|
this.#state.promptError = void 0;
|
|
26217
26647
|
this.#state.streamingText = "";
|
|
@@ -26221,6 +26651,14 @@ var init_store = __esm({
|
|
|
26221
26651
|
this.#notify();
|
|
26222
26652
|
});
|
|
26223
26653
|
}
|
|
26654
|
+
cancelSessionSelection() {
|
|
26655
|
+
if (this.#state.inputMode !== "session") return false;
|
|
26656
|
+
if (this.#state.sessionCancelBehavior === "prompt") {
|
|
26657
|
+
this.#resolveSession({ kind: "cancel" });
|
|
26658
|
+
return false;
|
|
26659
|
+
}
|
|
26660
|
+
return true;
|
|
26661
|
+
}
|
|
26224
26662
|
moveSessionCursor(delta) {
|
|
26225
26663
|
if (this.#state.inputMode !== "session" || this.#state.sessionOptions.length === 0) return;
|
|
26226
26664
|
const count = this.#state.sessionOptions.length;
|
|
@@ -26241,6 +26679,10 @@ var init_store = __esm({
|
|
|
26241
26679
|
if (this.#state.inputMode !== "session") return;
|
|
26242
26680
|
this.#resolveSession({ kind: "new" });
|
|
26243
26681
|
}
|
|
26682
|
+
submitSessionSelectionShortcut() {
|
|
26683
|
+
if (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim()) return;
|
|
26684
|
+
this.#resolvePrompt(`${TUI_SESSION_ACTION_PREFIX}${JSON.stringify({ type: "select" })}`);
|
|
26685
|
+
}
|
|
26244
26686
|
requestPrompt(initialPrompt) {
|
|
26245
26687
|
if (this.#exitRequested) return Promise.resolve("");
|
|
26246
26688
|
const prompt = (initialPrompt ?? "").trim();
|
|
@@ -26338,6 +26780,143 @@ var init_store = __esm({
|
|
|
26338
26780
|
this.#state.activity = `provider ${option.name} selected`;
|
|
26339
26781
|
this.#notify();
|
|
26340
26782
|
}
|
|
26783
|
+
openConfigManager(message) {
|
|
26784
|
+
const canOpenFromConfig = this.#state.inputMode === "config" || this.#state.inputMode === "configModel" || this.#state.inputMode === "configModelInput" || this.#state.inputMode === "configProviderInput" || this.#state.inputMode === "configAddProvider";
|
|
26785
|
+
if (!canOpenFromConfig && (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim())) return;
|
|
26786
|
+
this.#state.inputMode = "config";
|
|
26787
|
+
const current = this.#state.selection?.providerName ?? this.#state.status.provider;
|
|
26788
|
+
this.#state.providerCursor = Math.max(0, this.#state.providerOptions.findIndex((option) => option.name === current));
|
|
26789
|
+
this.#state.configMessage = message ?? this.#state.configMessage;
|
|
26790
|
+
this.#state.settingsError = void 0;
|
|
26791
|
+
this.#state.activity = "configure providers and models";
|
|
26792
|
+
this.#notify();
|
|
26793
|
+
}
|
|
26794
|
+
openConfigProviderInput(field) {
|
|
26795
|
+
if (this.#state.inputMode !== "config") return;
|
|
26796
|
+
const provider = this.#selectedConfigProvider();
|
|
26797
|
+
if (!provider) return;
|
|
26798
|
+
this.#state.inputMode = "configProviderInput";
|
|
26799
|
+
this.#state.configProviderInputField = field;
|
|
26800
|
+
this.#state.configProviderInput = configProviderFieldValue(provider, field);
|
|
26801
|
+
this.#state.settingsError = void 0;
|
|
26802
|
+
this.#state.activity = `edit ${field} for ${provider.name}`;
|
|
26803
|
+
this.#notify();
|
|
26804
|
+
}
|
|
26805
|
+
appendConfigProviderInput(input2) {
|
|
26806
|
+
if (this.#state.inputMode !== "configProviderInput") return;
|
|
26807
|
+
this.#state.configProviderInput += input2;
|
|
26808
|
+
this.#state.settingsError = void 0;
|
|
26809
|
+
this.#notify();
|
|
26810
|
+
}
|
|
26811
|
+
backspaceConfigProviderInput() {
|
|
26812
|
+
if (this.#state.inputMode !== "configProviderInput") return;
|
|
26813
|
+
this.#state.configProviderInput = Array.from(this.#state.configProviderInput).slice(0, -1).join("");
|
|
26814
|
+
this.#state.settingsError = void 0;
|
|
26815
|
+
this.#notify();
|
|
26816
|
+
}
|
|
26817
|
+
submitConfigProviderInput() {
|
|
26818
|
+
if (this.#state.inputMode !== "configProviderInput") return;
|
|
26819
|
+
const provider = this.#selectedConfigProvider();
|
|
26820
|
+
const field = this.#state.configProviderInputField;
|
|
26821
|
+
if (!provider || !field) return;
|
|
26822
|
+
this.#submitConfigAction({ type: "updateProvider", provider: provider.name, field, value: this.#state.configProviderInput });
|
|
26823
|
+
}
|
|
26824
|
+
moveConfigProviderCursor(delta) {
|
|
26825
|
+
if (this.#state.inputMode !== "config" || this.#state.providerOptions.length === 0) return;
|
|
26826
|
+
const count = this.#state.providerOptions.length;
|
|
26827
|
+
this.#state.providerCursor = (this.#state.providerCursor + delta + count) % count;
|
|
26828
|
+
this.#notify();
|
|
26829
|
+
}
|
|
26830
|
+
submitConfigDefaultProvider() {
|
|
26831
|
+
if (this.#state.inputMode !== "config") return;
|
|
26832
|
+
const option = this.#selectedConfigProvider();
|
|
26833
|
+
if (!option) return;
|
|
26834
|
+
this.#submitConfigAction({ type: "setDefaultProvider", provider: option.name });
|
|
26835
|
+
}
|
|
26836
|
+
openConfigModelSelector() {
|
|
26837
|
+
if (this.#state.inputMode !== "config" && this.#state.inputMode !== "configModelInput") return;
|
|
26838
|
+
const option = this.#selectedConfigProvider();
|
|
26839
|
+
if (!option) return;
|
|
26840
|
+
this.#state.inputMode = "configModel";
|
|
26841
|
+
this.#state.configModelCursor = 0;
|
|
26842
|
+
this.#state.settingsError = void 0;
|
|
26843
|
+
this.#state.activity = `choose default model for ${option.name}`;
|
|
26844
|
+
this.#notify();
|
|
26845
|
+
}
|
|
26846
|
+
moveConfigModelCursor(delta) {
|
|
26847
|
+
if (this.#state.inputMode !== "configModel") return;
|
|
26848
|
+
const profiles = this.#selectedConfigProvider()?.modelProfiles ?? [];
|
|
26849
|
+
if (profiles.length === 0) return;
|
|
26850
|
+
this.#state.configModelCursor = (this.#state.configModelCursor + delta + profiles.length) % profiles.length;
|
|
26851
|
+
this.#notify();
|
|
26852
|
+
}
|
|
26853
|
+
submitConfigDefaultModel() {
|
|
26854
|
+
if (this.#state.inputMode !== "configModel") return;
|
|
26855
|
+
const provider = this.#selectedConfigProvider();
|
|
26856
|
+
const profile = provider?.modelProfiles?.[this.#state.configModelCursor];
|
|
26857
|
+
if (!provider || !profile?.model) return;
|
|
26858
|
+
this.#submitConfigAction({
|
|
26859
|
+
type: "setDefaultModel",
|
|
26860
|
+
provider: provider.name,
|
|
26861
|
+
profileName: profile.name,
|
|
26862
|
+
name: profile.name,
|
|
26863
|
+
displayName: profile.displayName,
|
|
26864
|
+
model: profile.model
|
|
26865
|
+
});
|
|
26866
|
+
}
|
|
26867
|
+
openConfigModelInput() {
|
|
26868
|
+
if (this.#state.inputMode !== "configModel" && this.#state.inputMode !== "config") return;
|
|
26869
|
+
if (!this.#selectedConfigProvider()) return;
|
|
26870
|
+
this.#state.inputMode = "configModelInput";
|
|
26871
|
+
this.#state.configModelInput = "";
|
|
26872
|
+
this.#state.settingsError = void 0;
|
|
26873
|
+
this.#state.activity = "add a model profile";
|
|
26874
|
+
this.#notify();
|
|
26875
|
+
}
|
|
26876
|
+
appendConfigModelInput(input2) {
|
|
26877
|
+
if (this.#state.inputMode !== "configModelInput") return;
|
|
26878
|
+
this.#state.configModelInput += input2;
|
|
26879
|
+
this.#state.settingsError = void 0;
|
|
26880
|
+
this.#notify();
|
|
26881
|
+
}
|
|
26882
|
+
backspaceConfigModelInput() {
|
|
26883
|
+
if (this.#state.inputMode !== "configModelInput") return;
|
|
26884
|
+
this.#state.configModelInput = Array.from(this.#state.configModelInput).slice(0, -1).join("");
|
|
26885
|
+
this.#state.settingsError = void 0;
|
|
26886
|
+
this.#notify();
|
|
26887
|
+
}
|
|
26888
|
+
submitConfigModelInput() {
|
|
26889
|
+
if (this.#state.inputMode !== "configModelInput") return;
|
|
26890
|
+
const provider = this.#selectedConfigProvider();
|
|
26891
|
+
const model = this.#state.configModelInput.trim();
|
|
26892
|
+
if (!provider || !model) {
|
|
26893
|
+
this.#state.settingsError = "Model ID is required.";
|
|
26894
|
+
this.#notify();
|
|
26895
|
+
return;
|
|
26896
|
+
}
|
|
26897
|
+
this.#submitConfigAction({ type: "setDefaultModel", provider: provider.name, model, displayName: model });
|
|
26898
|
+
}
|
|
26899
|
+
openConfigAddProviderSelector() {
|
|
26900
|
+
if (this.#state.inputMode !== "config") return;
|
|
26901
|
+
this.#state.inputMode = "configAddProvider";
|
|
26902
|
+
this.#state.configAddProviderCursor = 0;
|
|
26903
|
+
this.#state.settingsError = void 0;
|
|
26904
|
+
this.#state.activity = "add provider preset";
|
|
26905
|
+
this.#notify();
|
|
26906
|
+
}
|
|
26907
|
+
moveConfigAddProviderCursor(delta) {
|
|
26908
|
+
if (this.#state.inputMode !== "configAddProvider") return;
|
|
26909
|
+
const count = this.#state.configProviderPresets.length;
|
|
26910
|
+
if (count === 0) return;
|
|
26911
|
+
this.#state.configAddProviderCursor = (this.#state.configAddProviderCursor + delta + count) % count;
|
|
26912
|
+
this.#notify();
|
|
26913
|
+
}
|
|
26914
|
+
submitConfigAddProvider() {
|
|
26915
|
+
if (this.#state.inputMode !== "configAddProvider") return;
|
|
26916
|
+
const preset = this.#state.configProviderPresets[this.#state.configAddProviderCursor];
|
|
26917
|
+
if (!preset) return;
|
|
26918
|
+
this.#submitConfigAction({ type: "addProvider", preset: preset.preset, name: preset.name, apiKeyEnv: preset.apiKeyEnv });
|
|
26919
|
+
}
|
|
26341
26920
|
openAgentSelector() {
|
|
26342
26921
|
if (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim()) return;
|
|
26343
26922
|
if (this.#state.agentOptions.length === 0) {
|
|
@@ -26436,9 +27015,12 @@ var init_store = __esm({
|
|
|
26436
27015
|
this.#notify();
|
|
26437
27016
|
}
|
|
26438
27017
|
closeSettings() {
|
|
26439
|
-
if (
|
|
27018
|
+
if (!["provider", "model", "agent", "permissionPreset", "config", "configModel", "configModelInput", "configProviderInput", "configAddProvider"].includes(this.#state.inputMode)) return;
|
|
26440
27019
|
this.#state.inputMode = "prompt";
|
|
26441
27020
|
this.#state.modelInput = "";
|
|
27021
|
+
this.#state.configModelInput = "";
|
|
27022
|
+
this.#state.configProviderInput = "";
|
|
27023
|
+
this.#state.configProviderInputField = void 0;
|
|
26442
27024
|
this.#state.settingsError = void 0;
|
|
26443
27025
|
this.#state.activity = "waiting for first message";
|
|
26444
27026
|
this.#notify();
|
|
@@ -26567,9 +27149,12 @@ var init_store = __esm({
|
|
|
26567
27149
|
this.#state.streamingFinalized = false;
|
|
26568
27150
|
this.#activeClaudeCodePlan = this.#isClaudeCodePlanRun(event.provider) ? { text: "", providerName: event.provider, model: event.model, createdAt: event.ts } : void 0;
|
|
26569
27151
|
}
|
|
26570
|
-
if (event.type === "model.text.delta"
|
|
27152
|
+
if (event.type === "model.text.delta") {
|
|
27153
|
+
if (isNestedInvocationEvent(event)) return;
|
|
26571
27154
|
this.#state.streamingText += event.text;
|
|
26572
27155
|
this.#state.streamingFinalized = false;
|
|
27156
|
+
this.#notify({ deferMs: 50 });
|
|
27157
|
+
return;
|
|
26573
27158
|
}
|
|
26574
27159
|
if (event.type === "model.text" && !isNestedInvocationEvent(event)) {
|
|
26575
27160
|
this.#state.streamingText = event.text;
|
|
@@ -27283,14 +27868,28 @@ var init_store = __esm({
|
|
|
27283
27868
|
resolve(prompt);
|
|
27284
27869
|
this.#notify();
|
|
27285
27870
|
}
|
|
27871
|
+
#selectedConfigProvider() {
|
|
27872
|
+
return this.#state.providerOptions[this.#state.providerCursor];
|
|
27873
|
+
}
|
|
27874
|
+
#submitConfigAction(action) {
|
|
27875
|
+
this.#state.settingsError = void 0;
|
|
27876
|
+
this.#state.configModelInput = "";
|
|
27877
|
+
this.#state.configProviderInput = "";
|
|
27878
|
+
this.#state.configProviderInputField = void 0;
|
|
27879
|
+
this.#resolvePrompt(`${TUI_CONFIG_ACTION_PREFIX}${JSON.stringify(action)}`);
|
|
27880
|
+
}
|
|
27286
27881
|
#resolveSession(selection) {
|
|
27287
27882
|
const resolve = this.#sessionResolve;
|
|
27288
27883
|
if (!resolve) return;
|
|
27289
27884
|
this.#sessionResolve = void 0;
|
|
27885
|
+
this.#state.sessionCancelBehavior = "exit";
|
|
27290
27886
|
if (selection.kind === "exit") {
|
|
27291
27887
|
this.#state.inputMode = "done";
|
|
27292
27888
|
this.#state.done = true;
|
|
27293
27889
|
this.#state.activity = "exiting";
|
|
27890
|
+
} else if (selection.kind === "cancel") {
|
|
27891
|
+
this.#state.inputMode = "prompt";
|
|
27892
|
+
this.#state.activity = "waiting for next message";
|
|
27294
27893
|
} else {
|
|
27295
27894
|
this.#state.inputMode = "starting";
|
|
27296
27895
|
this.#state.activity = selection.kind === "new" ? "creating session" : "opening session";
|
|
@@ -27402,7 +28001,23 @@ var init_store = __esm({
|
|
|
27402
28001
|
}
|
|
27403
28002
|
};
|
|
27404
28003
|
}
|
|
27405
|
-
#notify() {
|
|
28004
|
+
#notify(options = {}) {
|
|
28005
|
+
const deferMs = options.deferMs ?? 0;
|
|
28006
|
+
if (deferMs > 0) {
|
|
28007
|
+
if (this.#notifyTimer) return;
|
|
28008
|
+
this.#notifyTimer = setTimeout(() => {
|
|
28009
|
+
this.#notifyTimer = void 0;
|
|
28010
|
+
this.#emitNotify();
|
|
28011
|
+
}, deferMs);
|
|
28012
|
+
return;
|
|
28013
|
+
}
|
|
28014
|
+
if (this.#notifyTimer) {
|
|
28015
|
+
clearTimeout(this.#notifyTimer);
|
|
28016
|
+
this.#notifyTimer = void 0;
|
|
28017
|
+
}
|
|
28018
|
+
this.#emitNotify();
|
|
28019
|
+
}
|
|
28020
|
+
#emitNotify() {
|
|
27406
28021
|
for (const listener of this.#listeners) listener();
|
|
27407
28022
|
}
|
|
27408
28023
|
};
|
|
@@ -27416,7 +28031,6 @@ __export(app_exports, {
|
|
|
27416
28031
|
commandPaletteMatches: () => commandPaletteMatches,
|
|
27417
28032
|
inputFrameRows: () => inputFrameRows,
|
|
27418
28033
|
isCtrlCKeypress: () => isCtrlCKeypress,
|
|
27419
|
-
isCtrlPKeypress: () => isCtrlPKeypress,
|
|
27420
28034
|
isPromptNewlineKeypress: () => isPromptNewlineKeypress,
|
|
27421
28035
|
sessionHeaderLine: () => sessionHeaderLine,
|
|
27422
28036
|
sessionOptionLine: () => sessionOptionLine,
|
|
@@ -27428,13 +28042,16 @@ __export(app_exports, {
|
|
|
27428
28042
|
});
|
|
27429
28043
|
import React, { useEffect, useState } from "react";
|
|
27430
28044
|
import { Box, Text, useApp, useInput } from "ink";
|
|
28045
|
+
function usePromptDraftSnapshot(promptDraft) {
|
|
28046
|
+
const [snapshot, setSnapshot] = useState(() => promptDraft.snapshot());
|
|
28047
|
+
useEffect(() => promptDraft.subscribe(() => setSnapshot(promptDraft.snapshot())), [promptDraft]);
|
|
28048
|
+
return snapshot;
|
|
28049
|
+
}
|
|
27431
28050
|
function TuiApp({ store, version = "dev" }) {
|
|
27432
28051
|
const [state, setState] = useState(() => store.snapshot());
|
|
27433
|
-
const [
|
|
28052
|
+
const [promptDraft] = useState(() => new PromptDraftController(store.snapshot().promptInput));
|
|
27434
28053
|
const [now2, setNow] = useState(() => Date.now());
|
|
27435
28054
|
const [shortcutMode, setShortcutMode] = useState(false);
|
|
27436
|
-
const [slashCommandCursor, setSlashCommandCursor] = useState(0);
|
|
27437
|
-
const [slashCommandClosedPrefix, setSlashCommandClosedPrefix] = useState();
|
|
27438
28055
|
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
|
|
27439
28056
|
const [commandPaletteQuery, setCommandPaletteQuery] = useState("");
|
|
27440
28057
|
const [commandPaletteCursor, setCommandPaletteCursor] = useState(0);
|
|
@@ -27443,47 +28060,43 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27443
28060
|
const update = () => {
|
|
27444
28061
|
const next = store.snapshot();
|
|
27445
28062
|
setState(next);
|
|
27446
|
-
|
|
28063
|
+
promptDraft.setValue(next.promptInput);
|
|
27447
28064
|
};
|
|
27448
28065
|
const unsubscribe = store.subscribe(update);
|
|
27449
28066
|
update();
|
|
27450
28067
|
return unsubscribe;
|
|
27451
|
-
}, [store]);
|
|
28068
|
+
}, [store, promptDraft]);
|
|
27452
28069
|
useEffect(() => enableEnhancedKeyboardInput(), []);
|
|
27453
28070
|
useEffect(() => {
|
|
27454
28071
|
if (!state.workPlan && state.inputMode !== "running" && state.inputMode !== "starting") return;
|
|
27455
|
-
const timer = setInterval(() => setNow(Date.now()),
|
|
28072
|
+
const timer = setInterval(() => setNow(Date.now()), 1e3);
|
|
27456
28073
|
return () => clearInterval(timer);
|
|
27457
28074
|
}, [state.workPlan?.planId, state.inputMode]);
|
|
27458
28075
|
useEffect(() => {
|
|
27459
28076
|
setShortcutMode(false);
|
|
27460
28077
|
}, [state.inputMode, state.permission !== void 0]);
|
|
27461
|
-
useEffect(() => {
|
|
27462
|
-
setSlashCommandCursor(0);
|
|
27463
|
-
if (slashCommandClosedPrefix && !isSlashCommandClosedForPrompt(promptInput, slashCommandClosedPrefix)) setSlashCommandClosedPrefix(void 0);
|
|
27464
|
-
}, [promptInput, state.inputMode, slashCommandClosedPrefix]);
|
|
27465
28078
|
useEffect(() => {
|
|
27466
28079
|
setCommandPaletteCursor(0);
|
|
27467
28080
|
}, [commandPaletteQuery, state.inputMode]);
|
|
27468
28081
|
useEffect(() => {
|
|
27469
28082
|
if (commandPaletteOpen && !canUseCommandPalette(state)) setCommandPaletteOpen(false);
|
|
27470
28083
|
}, [commandPaletteOpen, state.inputMode, state.permission]);
|
|
27471
|
-
const commandPaletteItems = commandPaletteOpen ? commandPaletteMatches(commandPaletteQuery, state.inputMode, commandPaletteAvailability(state,
|
|
28084
|
+
const commandPaletteItems = commandPaletteOpen ? commandPaletteMatches(commandPaletteQuery, state.inputMode, commandPaletteAvailability(state, promptDraft.value())) : [];
|
|
27472
28085
|
const commandPaletteItem = commandPaletteItems[Math.min(commandPaletteCursor, Math.max(0, commandPaletteItems.length - 1))];
|
|
28086
|
+
const openCommandPalette = () => {
|
|
28087
|
+
if (!canUseCommandPalette(state)) return;
|
|
28088
|
+
setCommandPaletteOpen((open4) => !open4);
|
|
28089
|
+
setCommandPaletteQuery("");
|
|
28090
|
+
setCommandPaletteCursor(0);
|
|
28091
|
+
setShortcutMode(false);
|
|
28092
|
+
promptDraft.closeSlashCommandsForCurrentPrompt();
|
|
28093
|
+
};
|
|
27473
28094
|
useInput((input2, key) => {
|
|
27474
28095
|
if (isCtrlCKeypress(input2, key)) {
|
|
27475
28096
|
store.requestExit();
|
|
27476
28097
|
app.exit();
|
|
27477
28098
|
return;
|
|
27478
28099
|
}
|
|
27479
|
-
if (isCtrlPKeypress(input2, key) && canUseCommandPalette(state)) {
|
|
27480
|
-
setCommandPaletteOpen((open4) => !open4);
|
|
27481
|
-
setCommandPaletteQuery("");
|
|
27482
|
-
setCommandPaletteCursor(0);
|
|
27483
|
-
setShortcutMode(false);
|
|
27484
|
-
setSlashCommandClosedPrefix(promptInput);
|
|
27485
|
-
return;
|
|
27486
|
-
}
|
|
27487
28100
|
const textInput = textInputForKeypress(input2, key);
|
|
27488
28101
|
if (commandPaletteOpen) {
|
|
27489
28102
|
if (key.escape) {
|
|
@@ -27499,7 +28112,7 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27499
28112
|
return;
|
|
27500
28113
|
}
|
|
27501
28114
|
if (key.return) {
|
|
27502
|
-
if (commandPaletteItem) executeCommandPaletteItem({ item: commandPaletteItem, store, app,
|
|
28115
|
+
if (commandPaletteItem) executeCommandPaletteItem({ item: commandPaletteItem, store, app, promptDraft, setState, close: () => closeCommandPalette(setCommandPaletteOpen, setCommandPaletteQuery, setCommandPaletteCursor) });
|
|
27503
28116
|
return;
|
|
27504
28117
|
}
|
|
27505
28118
|
if (key.backspace || key.delete) {
|
|
@@ -27525,6 +28138,14 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27525
28138
|
store.stopActiveTask();
|
|
27526
28139
|
return;
|
|
27527
28140
|
}
|
|
28141
|
+
if (shortcut === "k" && (state.inputMode === "prompt" || state.inputMode === "running")) {
|
|
28142
|
+
openCommandPalette();
|
|
28143
|
+
return;
|
|
28144
|
+
}
|
|
28145
|
+
if (shortcut === "s" && state.inputMode === "prompt") {
|
|
28146
|
+
store.submitSessionSelectionShortcut();
|
|
28147
|
+
return;
|
|
28148
|
+
}
|
|
27528
28149
|
if (shortcut === "p" && state.inputMode === "prompt") {
|
|
27529
28150
|
store.openProviderSelector();
|
|
27530
28151
|
return;
|
|
@@ -27541,9 +28162,13 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27541
28162
|
store.openPermissionPresetSelector();
|
|
27542
28163
|
return;
|
|
27543
28164
|
}
|
|
28165
|
+
if (shortcut === "c" && state.inputMode === "prompt") {
|
|
28166
|
+
store.openConfigManager();
|
|
28167
|
+
return;
|
|
28168
|
+
}
|
|
27544
28169
|
if (shortcut === "u" && state.inputMode === "prompt") {
|
|
27545
28170
|
store.clearPromptInput({ notify: false });
|
|
27546
|
-
|
|
28171
|
+
promptDraft.setValue("");
|
|
27547
28172
|
clearPromptErrorPreview(setState);
|
|
27548
28173
|
return;
|
|
27549
28174
|
}
|
|
@@ -27578,7 +28203,14 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27578
28203
|
}
|
|
27579
28204
|
if (state.inputMode === "session") {
|
|
27580
28205
|
const normalized = input2.toLowerCase();
|
|
27581
|
-
if (key.escape
|
|
28206
|
+
if (key.escape) {
|
|
28207
|
+
if (store.cancelSessionSelection()) {
|
|
28208
|
+
store.requestExit();
|
|
28209
|
+
app.exit();
|
|
28210
|
+
}
|
|
28211
|
+
return;
|
|
28212
|
+
}
|
|
28213
|
+
if (normalized === "q") {
|
|
27582
28214
|
store.requestExit();
|
|
27583
28215
|
app.exit();
|
|
27584
28216
|
return;
|
|
@@ -27658,6 +28290,126 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27658
28290
|
}
|
|
27659
28291
|
return;
|
|
27660
28292
|
}
|
|
28293
|
+
if (state.inputMode === "config") {
|
|
28294
|
+
if (key.escape) {
|
|
28295
|
+
store.closeSettings();
|
|
28296
|
+
return;
|
|
28297
|
+
}
|
|
28298
|
+
if (key.upArrow) {
|
|
28299
|
+
store.moveConfigProviderCursor(-1);
|
|
28300
|
+
return;
|
|
28301
|
+
}
|
|
28302
|
+
if (key.downArrow) {
|
|
28303
|
+
store.moveConfigProviderCursor(1);
|
|
28304
|
+
return;
|
|
28305
|
+
}
|
|
28306
|
+
const normalized = input2.toLowerCase();
|
|
28307
|
+
if (key.return || normalized === "d") {
|
|
28308
|
+
store.submitConfigDefaultProvider();
|
|
28309
|
+
return;
|
|
28310
|
+
}
|
|
28311
|
+
if (normalized === "m") {
|
|
28312
|
+
store.openConfigModelSelector();
|
|
28313
|
+
return;
|
|
28314
|
+
}
|
|
28315
|
+
if (normalized === "n") {
|
|
28316
|
+
store.openConfigModelInput();
|
|
28317
|
+
return;
|
|
28318
|
+
}
|
|
28319
|
+
if (normalized === "a") {
|
|
28320
|
+
store.openConfigAddProviderSelector();
|
|
28321
|
+
return;
|
|
28322
|
+
}
|
|
28323
|
+
if (normalized === "b") {
|
|
28324
|
+
store.openConfigProviderInput("baseURL");
|
|
28325
|
+
return;
|
|
28326
|
+
}
|
|
28327
|
+
if (normalized === "e") {
|
|
28328
|
+
store.openConfigProviderInput("apiKeyEnv");
|
|
28329
|
+
return;
|
|
28330
|
+
}
|
|
28331
|
+
if (normalized === "h") {
|
|
28332
|
+
store.openConfigProviderInput("authHeader");
|
|
28333
|
+
return;
|
|
28334
|
+
}
|
|
28335
|
+
return;
|
|
28336
|
+
}
|
|
28337
|
+
if (state.inputMode === "configModel") {
|
|
28338
|
+
if (key.escape) {
|
|
28339
|
+
store.openConfigManager();
|
|
28340
|
+
return;
|
|
28341
|
+
}
|
|
28342
|
+
if (key.upArrow) {
|
|
28343
|
+
store.moveConfigModelCursor(-1);
|
|
28344
|
+
return;
|
|
28345
|
+
}
|
|
28346
|
+
if (key.downArrow) {
|
|
28347
|
+
store.moveConfigModelCursor(1);
|
|
28348
|
+
return;
|
|
28349
|
+
}
|
|
28350
|
+
if (input2.toLowerCase() === "n") {
|
|
28351
|
+
store.openConfigModelInput();
|
|
28352
|
+
return;
|
|
28353
|
+
}
|
|
28354
|
+
if (key.return) {
|
|
28355
|
+
store.submitConfigDefaultModel();
|
|
28356
|
+
return;
|
|
28357
|
+
}
|
|
28358
|
+
return;
|
|
28359
|
+
}
|
|
28360
|
+
if (state.inputMode === "configModelInput") {
|
|
28361
|
+
if (key.escape) {
|
|
28362
|
+
store.openConfigModelSelector();
|
|
28363
|
+
return;
|
|
28364
|
+
}
|
|
28365
|
+
if (key.return) {
|
|
28366
|
+
if (textInput) store.appendConfigModelInput(textInput);
|
|
28367
|
+
store.submitConfigModelInput();
|
|
28368
|
+
return;
|
|
28369
|
+
}
|
|
28370
|
+
if (key.backspace || key.delete) {
|
|
28371
|
+
store.backspaceConfigModelInput();
|
|
28372
|
+
return;
|
|
28373
|
+
}
|
|
28374
|
+
if (textInput) store.appendConfigModelInput(textInput);
|
|
28375
|
+
return;
|
|
28376
|
+
}
|
|
28377
|
+
if (state.inputMode === "configProviderInput") {
|
|
28378
|
+
if (key.escape) {
|
|
28379
|
+
store.openConfigManager();
|
|
28380
|
+
return;
|
|
28381
|
+
}
|
|
28382
|
+
if (key.return) {
|
|
28383
|
+
if (textInput) store.appendConfigProviderInput(textInput);
|
|
28384
|
+
store.submitConfigProviderInput();
|
|
28385
|
+
return;
|
|
28386
|
+
}
|
|
28387
|
+
if (key.backspace || key.delete) {
|
|
28388
|
+
store.backspaceConfigProviderInput();
|
|
28389
|
+
return;
|
|
28390
|
+
}
|
|
28391
|
+
if (textInput) store.appendConfigProviderInput(textInput);
|
|
28392
|
+
return;
|
|
28393
|
+
}
|
|
28394
|
+
if (state.inputMode === "configAddProvider") {
|
|
28395
|
+
if (key.escape) {
|
|
28396
|
+
store.openConfigManager();
|
|
28397
|
+
return;
|
|
28398
|
+
}
|
|
28399
|
+
if (key.upArrow) {
|
|
28400
|
+
store.moveConfigAddProviderCursor(-1);
|
|
28401
|
+
return;
|
|
28402
|
+
}
|
|
28403
|
+
if (key.downArrow) {
|
|
28404
|
+
store.moveConfigAddProviderCursor(1);
|
|
28405
|
+
return;
|
|
28406
|
+
}
|
|
28407
|
+
if (key.return) {
|
|
28408
|
+
store.submitConfigAddProvider();
|
|
28409
|
+
return;
|
|
28410
|
+
}
|
|
28411
|
+
return;
|
|
28412
|
+
}
|
|
27661
28413
|
if (state.inputMode === "model") {
|
|
27662
28414
|
if (key.escape) {
|
|
27663
28415
|
store.closeSettings();
|
|
@@ -27676,22 +28428,24 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27676
28428
|
return;
|
|
27677
28429
|
}
|
|
27678
28430
|
if (state.inputMode === "prompt" || state.inputMode === "running") {
|
|
27679
|
-
const
|
|
27680
|
-
const
|
|
27681
|
-
|
|
27682
|
-
|
|
28431
|
+
const promptSnapshot = promptDraft.snapshot();
|
|
28432
|
+
const promptInput = promptSnapshot.value;
|
|
28433
|
+
const slashCommands = visibleSlashCommandMatches(promptInput, state.inputMode, promptSnapshot.slashCommandClosedPrefix);
|
|
28434
|
+
const slashCommand = slashCommands[promptSnapshot.slashCommandCursor] ?? slashCommands[0];
|
|
28435
|
+
if (slashCommands.length > 0 && key.escape) {
|
|
28436
|
+
promptDraft.closeSlashCommandsForCurrentPrompt();
|
|
27683
28437
|
return;
|
|
27684
28438
|
}
|
|
27685
|
-
if (
|
|
27686
|
-
|
|
28439
|
+
if (slashCommands.length > 0 && key.upArrow) {
|
|
28440
|
+
promptDraft.moveSlashCommandCursor(-1, slashCommands.length);
|
|
27687
28441
|
return;
|
|
27688
28442
|
}
|
|
27689
|
-
if (
|
|
27690
|
-
|
|
28443
|
+
if (slashCommands.length > 0 && key.downArrow) {
|
|
28444
|
+
promptDraft.moveSlashCommandCursor(1, slashCommands.length);
|
|
27691
28445
|
return;
|
|
27692
28446
|
}
|
|
27693
|
-
if (
|
|
27694
|
-
applySlashCommandDraft(store,
|
|
28447
|
+
if (slashCommands.length > 0 && key.tab && slashCommand) {
|
|
28448
|
+
applySlashCommandDraft(store, promptDraft, slashCommand);
|
|
27695
28449
|
clearPromptErrorPreview(setState);
|
|
27696
28450
|
return;
|
|
27697
28451
|
}
|
|
@@ -27708,37 +28462,35 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27708
28462
|
return;
|
|
27709
28463
|
}
|
|
27710
28464
|
if (isPromptNewlineKeypress(input2, key)) {
|
|
27711
|
-
appendPromptDraft(store,
|
|
28465
|
+
appendPromptDraft(store, promptDraft, `${textInput}
|
|
27712
28466
|
`);
|
|
27713
28467
|
clearPromptErrorPreview(setState);
|
|
27714
28468
|
return;
|
|
27715
28469
|
}
|
|
27716
28470
|
if (isPromptSubmitKeypress(input2, key)) {
|
|
27717
28471
|
if (!textInput && slashCommand && shouldSelectSlashCommand(promptInput, slashCommand)) {
|
|
27718
|
-
applySlashCommandDraft(store,
|
|
28472
|
+
applySlashCommandDraft(store, promptDraft, slashCommand);
|
|
27719
28473
|
clearPromptErrorPreview(setState);
|
|
27720
28474
|
return;
|
|
27721
28475
|
}
|
|
27722
|
-
if (textInput) appendPromptDraft(store,
|
|
28476
|
+
if (textInput) appendPromptDraft(store, promptDraft, textInput);
|
|
27723
28477
|
store.submitPromptInput();
|
|
27724
28478
|
return;
|
|
27725
28479
|
}
|
|
27726
28480
|
if (key.backspace || key.delete) {
|
|
27727
28481
|
store.backspacePromptInput({ notify: false });
|
|
27728
|
-
|
|
28482
|
+
promptDraft.backspace();
|
|
27729
28483
|
clearPromptErrorPreview(setState);
|
|
27730
28484
|
return;
|
|
27731
28485
|
}
|
|
27732
28486
|
if (textInput) {
|
|
27733
|
-
appendPromptDraft(store,
|
|
28487
|
+
appendPromptDraft(store, promptDraft, textInput);
|
|
27734
28488
|
clearPromptErrorPreview(setState);
|
|
27735
28489
|
}
|
|
27736
28490
|
return;
|
|
27737
28491
|
}
|
|
27738
28492
|
});
|
|
27739
28493
|
const shellProps = { flexDirection: "column", paddingX: 1, minHeight: terminalHeight() };
|
|
27740
|
-
const slashCommands = commandPaletteOpen ? [] : visibleSlashCommandMatches(promptInput, state.inputMode, slashCommandClosedPrefix);
|
|
27741
|
-
const slashCommandPalette = slashCommands.length > 0 ? { items: slashCommands, cursor: Math.min(slashCommandCursor, slashCommands.length - 1) } : void 0;
|
|
27742
28494
|
const commandPalette = commandPaletteOpen ? { items: commandPaletteItems, query: commandPaletteQuery, cursor: Math.min(commandPaletteCursor, Math.max(0, commandPaletteItems.length - 1)) } : void 0;
|
|
27743
28495
|
if (shouldShowSessionStart(state)) {
|
|
27744
28496
|
return React.createElement(
|
|
@@ -27751,7 +28503,7 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27751
28503
|
return React.createElement(
|
|
27752
28504
|
Box,
|
|
27753
28505
|
shellProps,
|
|
27754
|
-
React.createElement(EmptyState, { state, version, shortcutMode,
|
|
28506
|
+
React.createElement(EmptyState, { state, version, shortcutMode, promptDraft, commandPalette })
|
|
27755
28507
|
);
|
|
27756
28508
|
}
|
|
27757
28509
|
return React.createElement(
|
|
@@ -27764,24 +28516,24 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27764
28516
|
React.createElement(shouldShowLoading(state) ? LoadingView : TranscriptView, { state }),
|
|
27765
28517
|
React.createElement(Box, { flexGrow: 1 }),
|
|
27766
28518
|
React.createElement(BottomStatusStack, { state, now: now2 }),
|
|
27767
|
-
React.createElement(InteractionPanel, { state, shortcutMode,
|
|
28519
|
+
React.createElement(InteractionPanel, { state, shortcutMode, promptDraft, commandPalette })
|
|
27768
28520
|
)
|
|
27769
28521
|
);
|
|
27770
28522
|
}
|
|
27771
|
-
function appendPromptDraft(store,
|
|
28523
|
+
function appendPromptDraft(store, promptDraft, value) {
|
|
27772
28524
|
if (!value) return;
|
|
27773
28525
|
store.appendPromptInput(value, { notify: false });
|
|
27774
|
-
|
|
28526
|
+
promptDraft.append(value);
|
|
27775
28527
|
}
|
|
27776
28528
|
function clearPromptErrorPreview(setState) {
|
|
27777
28529
|
setState((current) => current.promptError ? { ...current, promptError: void 0 } : current);
|
|
27778
28530
|
}
|
|
27779
|
-
function applySlashCommandDraft(store,
|
|
28531
|
+
function applySlashCommandDraft(store, promptDraft, item) {
|
|
27780
28532
|
const next = item.argument ? `${item.insert} ` : item.insert;
|
|
27781
28533
|
store.clearPromptInput({ notify: false });
|
|
27782
28534
|
store.appendPromptInput(next, { notify: false });
|
|
27783
|
-
|
|
27784
|
-
|
|
28535
|
+
promptDraft.setValue(next);
|
|
28536
|
+
promptDraft.closeSlashCommandsForCurrentPrompt();
|
|
27785
28537
|
}
|
|
27786
28538
|
function shouldSelectSlashCommand(promptInput, item) {
|
|
27787
28539
|
const value = promptInput.trim();
|
|
@@ -27793,6 +28545,14 @@ function visibleSlashCommandMatches(promptInput, mode, closedPrefix) {
|
|
|
27793
28545
|
if (closedPrefix && isSlashCommandClosedForPrompt(promptInput, closedPrefix)) return [];
|
|
27794
28546
|
return slashCommandMatches(promptInput, mode).slice(0, 7);
|
|
27795
28547
|
}
|
|
28548
|
+
function slashCommandPaletteForDraft(snapshot, mode) {
|
|
28549
|
+
const slashCommands = visibleSlashCommandMatches(snapshot.value, mode, snapshot.slashCommandClosedPrefix);
|
|
28550
|
+
if (slashCommands.length === 0) return void 0;
|
|
28551
|
+
return {
|
|
28552
|
+
items: slashCommands,
|
|
28553
|
+
cursor: Math.min(snapshot.slashCommandCursor, slashCommands.length - 1)
|
|
28554
|
+
};
|
|
28555
|
+
}
|
|
27796
28556
|
function isSlashCommandClosedForPrompt(promptInput, closedPrefix) {
|
|
27797
28557
|
return promptInput === closedPrefix || closedPrefix.endsWith(" ") && promptInput.startsWith(closedPrefix);
|
|
27798
28558
|
}
|
|
@@ -27861,6 +28621,8 @@ function buildCommandPaletteItems(mode, availability) {
|
|
|
27861
28621
|
}
|
|
27862
28622
|
if (promptReadyForSettings) {
|
|
27863
28623
|
items.push({ id: "permission-preset", title: "Permission preset", description: "Choose the next-turn permission behavior", group: "Suggested", shortcut: "esc o", action: "open-permissions", keywords: ["permission", "policy", "approval"] });
|
|
28624
|
+
items.push({ id: "configure-providers", title: "Configure providers", description: "Persist default providers and model profiles", group: "Suggested", shortcut: "esc c", action: "open-config", keywords: ["config", "settings", "provider", "model"] });
|
|
28625
|
+
items.push({ id: "select-session", title: "Select session", description: "Open the saved session picker", group: "Session", shortcut: "esc s", action: "open-session", keywords: ["session", "sessions", "conversation", "switch"] });
|
|
27864
28626
|
}
|
|
27865
28627
|
for (const command of SLASH_COMMANDS) {
|
|
27866
28628
|
if (!slashCommandActiveInMode(command, mode)) continue;
|
|
@@ -27954,13 +28716,13 @@ function executeCommandPaletteItem({
|
|
|
27954
28716
|
item,
|
|
27955
28717
|
store,
|
|
27956
28718
|
app,
|
|
27957
|
-
|
|
28719
|
+
promptDraft,
|
|
27958
28720
|
setState,
|
|
27959
28721
|
close
|
|
27960
28722
|
}) {
|
|
27961
28723
|
close();
|
|
27962
28724
|
if (item.slashCommand) {
|
|
27963
|
-
applyCommandPaletteSlashCommand(store,
|
|
28725
|
+
applyCommandPaletteSlashCommand(store, promptDraft, item.slashCommand);
|
|
27964
28726
|
clearPromptErrorPreview(setState);
|
|
27965
28727
|
return;
|
|
27966
28728
|
}
|
|
@@ -27977,9 +28739,15 @@ function executeCommandPaletteItem({
|
|
|
27977
28739
|
case "open-permissions":
|
|
27978
28740
|
store.openPermissionPresetSelector();
|
|
27979
28741
|
return;
|
|
28742
|
+
case "open-config":
|
|
28743
|
+
store.openConfigManager();
|
|
28744
|
+
return;
|
|
28745
|
+
case "open-session":
|
|
28746
|
+
store.submitSessionSelectionShortcut();
|
|
28747
|
+
return;
|
|
27980
28748
|
case "clear-prompt":
|
|
27981
28749
|
store.clearPromptInput({ notify: false });
|
|
27982
|
-
|
|
28750
|
+
promptDraft.setValue("");
|
|
27983
28751
|
clearPromptErrorPreview(setState);
|
|
27984
28752
|
return;
|
|
27985
28753
|
case "retry":
|
|
@@ -28006,12 +28774,13 @@ function executeCommandPaletteItem({
|
|
|
28006
28774
|
return;
|
|
28007
28775
|
}
|
|
28008
28776
|
}
|
|
28009
|
-
function applyCommandPaletteSlashCommand(store,
|
|
28777
|
+
function applyCommandPaletteSlashCommand(store, promptDraft, item) {
|
|
28010
28778
|
const shouldInsert = item.argument && item.command !== "/session new";
|
|
28011
28779
|
const next = shouldInsert ? `${item.insert} ` : item.insert;
|
|
28012
28780
|
store.clearPromptInput({ notify: false });
|
|
28013
28781
|
store.appendPromptInput(next, { notify: false });
|
|
28014
|
-
|
|
28782
|
+
promptDraft.setValue(next);
|
|
28783
|
+
promptDraft.closeSlashCommandsForCurrentPrompt();
|
|
28015
28784
|
if (!shouldInsert) store.submitPromptInput();
|
|
28016
28785
|
}
|
|
28017
28786
|
function textInputForKeypress(input2, key) {
|
|
@@ -28038,15 +28807,6 @@ function isCtrlCKeypress(input2, key) {
|
|
|
28038
28807
|
const modifier = Number(csi[2] ?? 1);
|
|
28039
28808
|
return (codePoint === 3 || codePoint === 99) && hasControlModifier(modifier);
|
|
28040
28809
|
}
|
|
28041
|
-
function isCtrlPKeypress(input2, key) {
|
|
28042
|
-
if (input2 === "") return true;
|
|
28043
|
-
if (key.ctrl && input2.toLowerCase() === "p") return true;
|
|
28044
|
-
const csi = CSI_U_INPUT.exec(input2);
|
|
28045
|
-
if (!csi) return false;
|
|
28046
|
-
const codePoint = Number(csi[1]);
|
|
28047
|
-
const modifier = Number(csi[2] ?? 1);
|
|
28048
|
-
return (codePoint === 16 || codePoint === 112) && hasControlModifier(modifier);
|
|
28049
|
-
}
|
|
28050
28810
|
function isPromptSubmitKeypress(input2, key) {
|
|
28051
28811
|
return Boolean(key.return) || input2 === "\n";
|
|
28052
28812
|
}
|
|
@@ -28129,12 +28889,13 @@ function EmptyState({
|
|
|
28129
28889
|
state,
|
|
28130
28890
|
version,
|
|
28131
28891
|
shortcutMode,
|
|
28132
|
-
|
|
28133
|
-
slashCommandPalette,
|
|
28892
|
+
promptDraft,
|
|
28134
28893
|
commandPalette
|
|
28135
28894
|
}) {
|
|
28895
|
+
const promptSnapshot = usePromptDraftSnapshot(promptDraft);
|
|
28896
|
+
const slashCommandPalette = commandPalette ? void 0 : slashCommandPaletteForDraft(promptSnapshot, state.inputMode);
|
|
28136
28897
|
const placeholder = 'Ask anything... "\uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uAD6C\uC870\uB97C \uC54C\uB824\uC918"';
|
|
28137
|
-
const hint = shortcutMode ? shortcutHelp(state) : "enter send \xB7 shift/option+enter newline \xB7
|
|
28898
|
+
const hint = shortcutMode ? shortcutHelp(state) : "enter send \xB7 shift/option+enter newline \xB7 esc k commands \xB7 esc s sessions";
|
|
28138
28899
|
const hasOverlay = Boolean(slashCommandPalette || commandPalette);
|
|
28139
28900
|
const topGap = hasOverlay ? 0 : Math.max(1, Math.min(6, Math.floor(terminalHeight() * 0.12)));
|
|
28140
28901
|
return React.createElement(
|
|
@@ -28148,7 +28909,7 @@ function EmptyState({
|
|
|
28148
28909
|
React.createElement(InputFrame, {
|
|
28149
28910
|
borderColor: shortcutMode ? "cyan" : "blue",
|
|
28150
28911
|
cursorColor: "cyan",
|
|
28151
|
-
value:
|
|
28912
|
+
value: promptSnapshot.value,
|
|
28152
28913
|
placeholder,
|
|
28153
28914
|
marginTop: 0
|
|
28154
28915
|
}),
|
|
@@ -28160,39 +28921,6 @@ function EmptyState({
|
|
|
28160
28921
|
)
|
|
28161
28922
|
);
|
|
28162
28923
|
}
|
|
28163
|
-
function SessionMetaBox({ state, version, marginTop = 1 }) {
|
|
28164
|
-
const width = promptPanelWidth();
|
|
28165
|
-
const compact = width < 68;
|
|
28166
|
-
const agent = shorten(state.selectedAgent ?? state.status.agent ?? "general", compact ? 14 : 18);
|
|
28167
|
-
const model = shorten(state.selection?.model ?? state.status.model ?? "model", compact ? Math.max(18, width - 14) : 30);
|
|
28168
|
-
const workspace = shorten(state.status.cwd ?? process.cwd(), compact ? Math.max(18, width - 18) : 46);
|
|
28169
|
-
const rows = compact ? [
|
|
28170
|
-
`agent ${agent} \xB7 perm ${state.permissionPreset}`,
|
|
28171
|
-
`model ${model}`,
|
|
28172
|
-
`workspace ${workspace}`,
|
|
28173
|
-
version ? `version v${version}` : void 0
|
|
28174
|
-
].filter(Boolean) : [
|
|
28175
|
-
`agent ${agent} \xB7 model ${model} \xB7 perm ${state.permissionPreset}`,
|
|
28176
|
-
`workspace ${workspace}${version ? ` \xB7 version v${version}` : ""}`
|
|
28177
|
-
];
|
|
28178
|
-
return React.createElement(
|
|
28179
|
-
Box,
|
|
28180
|
-
{ alignSelf: "center", width, flexDirection: "column", borderStyle: "single", borderColor: "#334155", paddingX: 1, marginTop },
|
|
28181
|
-
...rows.map((line, index) => React.createElement(SurfaceLine, { key: index, text: line ?? "", width: width - 4, backgroundColor: SURFACE.status, color: "white" }))
|
|
28182
|
-
);
|
|
28183
|
-
}
|
|
28184
|
-
function DemianWordmark() {
|
|
28185
|
-
if (terminalWidth() < 58) {
|
|
28186
|
-
return React.createElement(Box, { alignSelf: "center" }, React.createElement(Text, { color: "gray", bold: true }, "DEMIAN"));
|
|
28187
|
-
}
|
|
28188
|
-
return React.createElement(
|
|
28189
|
-
Box,
|
|
28190
|
-
{ alignSelf: "center", flexDirection: "column" },
|
|
28191
|
-
...DEMIAN_WORDMARK_LINES.map(
|
|
28192
|
-
(line, index) => React.createElement(Text, { key: index, color: index < 2 ? "gray" : "white", bold: true }, line)
|
|
28193
|
-
)
|
|
28194
|
-
);
|
|
28195
|
-
}
|
|
28196
28924
|
function LoadingView({ state }) {
|
|
28197
28925
|
const title = state.inputMode === "starting" ? "Demian runtime" : "Preparing session";
|
|
28198
28926
|
const message = state.activity || (state.inputMode === "starting" ? "Loading configuration" : "Starting session");
|
|
@@ -28262,34 +28990,26 @@ function tuiMode(state) {
|
|
|
28262
28990
|
if (state.inputMode === "session") return { label: "sessions", color: "magenta" };
|
|
28263
28991
|
if (state.inputMode === "running") return { label: "running", color: "cyan" };
|
|
28264
28992
|
if (state.inputMode === "provider" || state.inputMode === "agent" || state.inputMode === "model" || state.inputMode === "permissionPreset") return { label: "settings", color: "magenta" };
|
|
28993
|
+
if (state.inputMode === "config" || state.inputMode === "configModel" || state.inputMode === "configModelInput" || state.inputMode === "configProviderInput" || state.inputMode === "configAddProvider") return { label: "config", color: "magenta" };
|
|
28265
28994
|
if (state.inputMode === "prompt") return { label: "ready", color: "green" };
|
|
28266
28995
|
return { label: state.inputMode, color: "gray" };
|
|
28267
28996
|
}
|
|
28268
|
-
function
|
|
28269
|
-
|
|
28270
|
-
|
|
28271
|
-
|
|
28272
|
-
|
|
28273
|
-
|
|
28274
|
-
|
|
28275
|
-
|
|
28276
|
-
|
|
28277
|
-
|
|
28278
|
-
|
|
28279
|
-
|
|
28280
|
-
|
|
28281
|
-
|
|
28282
|
-
|
|
28283
|
-
|
|
28284
|
-
if (block.kind === "user") return React.createElement(UserBlockView, { block, width });
|
|
28285
|
-
const color = block.kind === "user" ? "green" : block.kind === "assistant" ? "white" : block.kind === "warning" ? "yellow" : "magenta";
|
|
28286
|
-
const lines = wrapBlockLines(block.lines, width, 80);
|
|
28287
|
-
return React.createElement(
|
|
28288
|
-
Box,
|
|
28289
|
-
{ flexDirection: "column", marginBottom: 1 },
|
|
28290
|
-
React.createElement(Text, { color, bold: true }, fitToWidth(block.title, width)),
|
|
28291
|
-
...lines.map((line, index) => React.createElement(Text, { key: index }, line || " "))
|
|
28292
|
-
);
|
|
28997
|
+
function sameBlockViewProps(previous, next) {
|
|
28998
|
+
return previous.width === next.width && blockRenderSignature(previous.block) === blockRenderSignature(next.block);
|
|
28999
|
+
}
|
|
29000
|
+
function blockRenderSignature(block) {
|
|
29001
|
+
return JSON.stringify({
|
|
29002
|
+
id: "id" in block ? block.id : void 0,
|
|
29003
|
+
kind: block.kind,
|
|
29004
|
+
title: block.title,
|
|
29005
|
+
lines: block.lines,
|
|
29006
|
+
meta: block.meta,
|
|
29007
|
+
toolStatus: block.toolStatus,
|
|
29008
|
+
expanded: block.expanded,
|
|
29009
|
+
toolDetails: block.expanded ? block.toolDetails : void 0,
|
|
29010
|
+
goalWork: block.kind === "goal-work" ? block.goalWork : void 0,
|
|
29011
|
+
cowork: block.kind === "cowork" ? block.cowork : void 0
|
|
29012
|
+
});
|
|
28293
29013
|
}
|
|
28294
29014
|
function UserBlockView({ block, width }) {
|
|
28295
29015
|
const lines = normalizedBlockLines(block.lines).slice(0, 80);
|
|
@@ -28753,16 +29473,21 @@ function formatSessionAge(updatedAt) {
|
|
|
28753
29473
|
if (hours < 48) return `${hours}h ago`;
|
|
28754
29474
|
return `${Math.floor(hours / 24)}d ago`;
|
|
28755
29475
|
}
|
|
28756
|
-
function InteractionPanel({ state, shortcutMode,
|
|
29476
|
+
function InteractionPanel({ state, shortcutMode, promptDraft, commandPalette }) {
|
|
28757
29477
|
if (state.inputMode === "starting") return React.createElement(LoadingBar);
|
|
28758
29478
|
if (state.inputMode === "session") return React.createElement(SessionSelector, { state });
|
|
28759
29479
|
if (state.permission) return React.createElement(PermissionBar, { state });
|
|
28760
29480
|
if (state.inputMode === "provider") return React.createElement(ProviderSelector, { state });
|
|
28761
29481
|
if (state.inputMode === "agent") return React.createElement(AgentSelector, { state });
|
|
28762
29482
|
if (state.inputMode === "permissionPreset") return React.createElement(PermissionPresetSelector, { state });
|
|
29483
|
+
if (state.inputMode === "config") return React.createElement(ConfigManager, { state });
|
|
29484
|
+
if (state.inputMode === "configModel") return React.createElement(ConfigModelSelector, { state });
|
|
29485
|
+
if (state.inputMode === "configModelInput") return React.createElement(ConfigModelInput, { state });
|
|
29486
|
+
if (state.inputMode === "configProviderInput") return React.createElement(ConfigProviderInput, { state });
|
|
29487
|
+
if (state.inputMode === "configAddProvider") return React.createElement(ConfigAddProviderSelector, { state });
|
|
28763
29488
|
if (state.inputMode === "model") return React.createElement(ModelEditor, { state });
|
|
28764
|
-
if (state.inputMode === "running") return React.createElement(CommandBar, { state, shortcutMode,
|
|
28765
|
-
if (state.inputMode === "prompt") return React.createElement(PromptBar, { state, shortcutMode,
|
|
29489
|
+
if (state.inputMode === "running") return React.createElement(CommandBar, { state, shortcutMode, promptDraft, commandPalette });
|
|
29490
|
+
if (state.inputMode === "prompt") return React.createElement(PromptBar, { state, shortcutMode, promptDraft, commandPalette });
|
|
28766
29491
|
return React.createElement(PermissionBar, { state });
|
|
28767
29492
|
}
|
|
28768
29493
|
function LoadingBar() {
|
|
@@ -28775,8 +29500,8 @@ function LoadingBar() {
|
|
|
28775
29500
|
}
|
|
28776
29501
|
function shortcutHelp(state) {
|
|
28777
29502
|
const parts = ["esc cancel"];
|
|
28778
|
-
if (state.inputMode === "prompt") parts.push("p provider", "a agent", "m model", "o permissions", "u clear");
|
|
28779
|
-
if (state.inputMode === "running") parts.push("s stop");
|
|
29503
|
+
if (state.inputMode === "prompt") parts.push("k commands", "s sessions", "p provider", "a agent", "m model", "o permissions", "c config", "u clear");
|
|
29504
|
+
if (state.inputMode === "running") parts.push("k commands", "s stop");
|
|
28780
29505
|
if (state.turnDiff) parts.push("d diff");
|
|
28781
29506
|
if (state.blocks.some((block) => block.kind === "tool")) parts.push("t tools");
|
|
28782
29507
|
if (state.workPlan || state.inputMode === "running") parts.push("w plan");
|
|
@@ -28785,8 +29510,10 @@ function shortcutHelp(state) {
|
|
|
28785
29510
|
parts.push("q quit");
|
|
28786
29511
|
return `shortcut: ${parts.join(" | ")}`;
|
|
28787
29512
|
}
|
|
28788
|
-
function PromptBar({ state, shortcutMode,
|
|
28789
|
-
const
|
|
29513
|
+
function PromptBar({ state, shortcutMode, promptDraft, commandPalette }) {
|
|
29514
|
+
const promptSnapshot = usePromptDraftSnapshot(promptDraft);
|
|
29515
|
+
const slashCommandPalette = commandPalette ? void 0 : slashCommandPaletteForDraft(promptSnapshot, state.inputMode);
|
|
29516
|
+
const hint = shortcutMode ? shortcutHelp(state) : "enter send | shift/option+enter newline | esc k commands | esc s sessions | esc shortcuts | up/down history | /compact | /retry | /exit";
|
|
28790
29517
|
return React.createElement(
|
|
28791
29518
|
Box,
|
|
28792
29519
|
{ alignSelf: "center", width: promptPanelWidth(), flexDirection: "column", marginTop: 1 },
|
|
@@ -28796,15 +29523,17 @@ function PromptBar({ state, shortcutMode, promptInput, slashCommandPalette, comm
|
|
|
28796
29523
|
React.createElement(InputFrame, {
|
|
28797
29524
|
borderColor: shortcutMode ? "cyan" : "blue",
|
|
28798
29525
|
cursorColor: "cyan",
|
|
28799
|
-
value:
|
|
29526
|
+
value: promptSnapshot.value,
|
|
28800
29527
|
placeholder: "Type first message and press Enter",
|
|
28801
29528
|
marginTop: 1
|
|
28802
29529
|
}),
|
|
28803
29530
|
React.createElement(CommandGuide, { hint, color: shortcutMode ? "cyan" : "gray", error: state.promptError })
|
|
28804
29531
|
);
|
|
28805
29532
|
}
|
|
28806
|
-
function CommandBar({ state, shortcutMode,
|
|
28807
|
-
const
|
|
29533
|
+
function CommandBar({ state, shortcutMode, promptDraft, commandPalette }) {
|
|
29534
|
+
const promptSnapshot = usePromptDraftSnapshot(promptDraft);
|
|
29535
|
+
const slashCommandPalette = commandPalette ? void 0 : slashCommandPaletteForDraft(promptSnapshot, state.inputMode);
|
|
29536
|
+
const hint = shortcutMode ? shortcutHelp(state) : "running | shift/option+enter newline | esc k commands | /stop stop | /exit quit | esc shortcuts";
|
|
28808
29537
|
return React.createElement(
|
|
28809
29538
|
Box,
|
|
28810
29539
|
{ alignSelf: "center", width: promptPanelWidth(), flexDirection: "column", marginTop: 1 },
|
|
@@ -28814,7 +29543,7 @@ function CommandBar({ state, shortcutMode, promptInput, slashCommandPalette, com
|
|
|
28814
29543
|
React.createElement(InputFrame, {
|
|
28815
29544
|
borderColor: shortcutMode ? "cyan" : "yellow",
|
|
28816
29545
|
cursorColor: "yellow",
|
|
28817
|
-
value:
|
|
29546
|
+
value: promptSnapshot.value,
|
|
28818
29547
|
placeholder: "Type /stop to stop or /exit to quit",
|
|
28819
29548
|
marginTop: 1
|
|
28820
29549
|
}),
|
|
@@ -28972,18 +29701,6 @@ function InputFrameLine({ row, width, cursorColor }) {
|
|
|
28972
29701
|
function CursorCell({ cursorColor }) {
|
|
28973
29702
|
return React.createElement(Text, { color: "black", backgroundColor: cursorColor, bold: true }, "\x1B[5m \x1B[25m");
|
|
28974
29703
|
}
|
|
28975
|
-
function CommandGuide({ hint, color, error }) {
|
|
28976
|
-
return React.createElement(
|
|
28977
|
-
Box,
|
|
28978
|
-
{ alignSelf: "center", width: promptPanelWidth(), flexDirection: "column", marginTop: 1 },
|
|
28979
|
-
error ? React.createElement(Text, { color: "yellow" }, error) : null,
|
|
28980
|
-
React.createElement(
|
|
28981
|
-
Box,
|
|
28982
|
-
{ justifyContent: "flex-end" },
|
|
28983
|
-
React.createElement(Text, { color }, hint)
|
|
28984
|
-
)
|
|
28985
|
-
);
|
|
28986
|
-
}
|
|
28987
29704
|
function SessionSelector({ state }) {
|
|
28988
29705
|
const items = state.sessionOptions;
|
|
28989
29706
|
const width = sessionPanelWidth();
|
|
@@ -29103,6 +29820,95 @@ function ModelEditor({ state }) {
|
|
|
29103
29820
|
state.settingsError ? React.createElement(Text, { color: "yellow" }, state.settingsError) : React.createElement(Text, { color: "gray" }, "enter apply | backspace edit | esc cancel")
|
|
29104
29821
|
);
|
|
29105
29822
|
}
|
|
29823
|
+
function ConfigManager({ state }) {
|
|
29824
|
+
const items = state.providerOptions;
|
|
29825
|
+
const selected = items[state.providerCursor];
|
|
29826
|
+
const defaultProvider = state.configDefaultProvider ?? state.status.provider;
|
|
29827
|
+
const contentWidth = Math.max(40, promptPanelWidth() - 8);
|
|
29828
|
+
return React.createElement(
|
|
29829
|
+
DialogFrame,
|
|
29830
|
+
{ title: "Config", accent: "magenta" },
|
|
29831
|
+
React.createElement(Text, { color: state.configCreated ? "green" : "gray" }, state.configPath ? `config: ${shorten(state.configPath, contentWidth)}` : "config: ~/.demian/config.json"),
|
|
29832
|
+
state.configMessage ? React.createElement(Text, { color: "cyan" }, state.configMessage) : null,
|
|
29833
|
+
React.createElement(Text, { color: "gray" }, `${"Provider".padEnd(20)} ${"Default model".padEnd(28)} Status`),
|
|
29834
|
+
...items.map((item, index) => {
|
|
29835
|
+
const isDefault = item.name === defaultProvider;
|
|
29836
|
+
const pointer = index === state.providerCursor ? ">" : " ";
|
|
29837
|
+
const marker = isDefault ? "*" : " ";
|
|
29838
|
+
return React.createElement(
|
|
29839
|
+
Text,
|
|
29840
|
+
{ key: item.name, color: index === state.providerCursor ? "cyan" : item.available === false ? "gray" : "white", bold: index === state.providerCursor },
|
|
29841
|
+
`${pointer}${marker} ${shorten(item.label ?? item.name, 18).padEnd(18)} ${shorten((item.modelLabel ?? item.model) || "-", 28).padEnd(28)} ${providerStatusLabel(item)}`
|
|
29842
|
+
);
|
|
29843
|
+
}),
|
|
29844
|
+
selected ? React.createElement(Text, { color: "gray" }, `${selected.type ?? "provider"}${selected.baseURL ? ` | base ${shorten(selected.baseURL, 42)}` : " | base -"}${selected.apiKeyEnv ? ` | env ${selected.apiKeyEnv}` : " | env -"}${selected.auth?.header ? ` | auth ${selected.auth.header}` : ""}`) : null,
|
|
29845
|
+
React.createElement(Text, { color: "gray" }, "enter/d default | m models | b baseURL | e env | h auth | n new model | a add provider | esc close")
|
|
29846
|
+
);
|
|
29847
|
+
}
|
|
29848
|
+
function ConfigModelSelector({ state }) {
|
|
29849
|
+
const provider = state.providerOptions[state.providerCursor];
|
|
29850
|
+
const profiles = provider?.modelProfiles ?? [];
|
|
29851
|
+
return React.createElement(
|
|
29852
|
+
DialogFrame,
|
|
29853
|
+
{ title: provider ? `Models: ${provider.name}` : "Models", accent: "magenta" },
|
|
29854
|
+
profiles.length ? React.createElement(Text, { color: "gray" }, `${"Model".padEnd(32)} Source`) : React.createElement(Text, { color: "yellow" }, "No configured or discoverable models. Press n to add one."),
|
|
29855
|
+
...profiles.slice(0, 12).map(
|
|
29856
|
+
(item, index) => React.createElement(
|
|
29857
|
+
Text,
|
|
29858
|
+
{ key: `${item.name}-${item.model}`, color: index === state.configModelCursor ? "cyan" : item.available === false ? "gray" : "white", bold: index === state.configModelCursor },
|
|
29859
|
+
`${index === state.configModelCursor ? ">" : " "} ${shorten(item.label ?? item.displayName ?? item.model, 30).padEnd(30)} ${item.source ?? "config"}`
|
|
29860
|
+
)
|
|
29861
|
+
),
|
|
29862
|
+
React.createElement(Text, { color: "gray" }, "enter set default model | n add custom model | esc back")
|
|
29863
|
+
);
|
|
29864
|
+
}
|
|
29865
|
+
function ConfigModelInput({ state }) {
|
|
29866
|
+
const provider = state.providerOptions[state.providerCursor];
|
|
29867
|
+
const value = state.configModelInput || "Model ID";
|
|
29868
|
+
return React.createElement(
|
|
29869
|
+
DialogFrame,
|
|
29870
|
+
{ title: provider ? `Add model: ${provider.name}` : "Add model", accent: "magenta" },
|
|
29871
|
+
React.createElement(
|
|
29872
|
+
Box,
|
|
29873
|
+
null,
|
|
29874
|
+
React.createElement(Text, { color: "cyan", bold: true }, "> "),
|
|
29875
|
+
React.createElement(Text, { color: state.configModelInput ? "white" : "gray" }, value)
|
|
29876
|
+
),
|
|
29877
|
+
state.settingsError ? React.createElement(Text, { color: "yellow" }, state.settingsError) : React.createElement(Text, { color: "gray" }, "enter add and make default | backspace edit | esc back")
|
|
29878
|
+
);
|
|
29879
|
+
}
|
|
29880
|
+
function ConfigProviderInput({ state }) {
|
|
29881
|
+
const provider = state.providerOptions[state.providerCursor];
|
|
29882
|
+
const field = state.configProviderInputField ?? "baseURL";
|
|
29883
|
+
const label = field === "baseURL" ? "Base URL" : field === "apiKeyEnv" ? "API key env" : "Auth header";
|
|
29884
|
+
const placeholder = field === "baseURL" ? "empty removes provider baseURL" : field === "apiKeyEnv" ? "empty removes provider apiKeyEnv" : "empty removes custom auth header";
|
|
29885
|
+
const value = state.configProviderInput || placeholder;
|
|
29886
|
+
return React.createElement(
|
|
29887
|
+
DialogFrame,
|
|
29888
|
+
{ title: provider ? `Edit ${label}: ${provider.name}` : `Edit ${label}`, accent: "magenta" },
|
|
29889
|
+
React.createElement(
|
|
29890
|
+
Box,
|
|
29891
|
+
null,
|
|
29892
|
+
React.createElement(Text, { color: "cyan", bold: true }, "> "),
|
|
29893
|
+
React.createElement(Text, { color: state.configProviderInput ? "white" : "gray" }, value)
|
|
29894
|
+
),
|
|
29895
|
+
state.settingsError ? React.createElement(Text, { color: "yellow" }, state.settingsError) : React.createElement(Text, { color: "gray" }, "enter save | backspace edit/clear | esc back")
|
|
29896
|
+
);
|
|
29897
|
+
}
|
|
29898
|
+
function ConfigAddProviderSelector({ state }) {
|
|
29899
|
+
return React.createElement(
|
|
29900
|
+
DialogFrame,
|
|
29901
|
+
{ title: "Add Provider", accent: "magenta" },
|
|
29902
|
+
...state.configProviderPresets.map(
|
|
29903
|
+
(item, index) => React.createElement(
|
|
29904
|
+
Text,
|
|
29905
|
+
{ key: item.preset, color: index === state.configAddProviderCursor ? "cyan" : "white", bold: index === state.configAddProviderCursor },
|
|
29906
|
+
`${index === state.configAddProviderCursor ? ">" : " "} ${shorten(item.label, 18).padEnd(18)} ${item.detail ?? item.preset}`
|
|
29907
|
+
)
|
|
29908
|
+
),
|
|
29909
|
+
React.createElement(Text, { color: "gray" }, "enter add preset | esc back")
|
|
29910
|
+
);
|
|
29911
|
+
}
|
|
29106
29912
|
function PermissionBar({ state }) {
|
|
29107
29913
|
const pending = state.permission;
|
|
29108
29914
|
if (!pending) {
|
|
@@ -29328,7 +30134,7 @@ function clamp01(value) {
|
|
|
29328
30134
|
if (!Number.isFinite(value)) return 0;
|
|
29329
30135
|
return Math.max(0, Math.min(1, value));
|
|
29330
30136
|
}
|
|
29331
|
-
var SURFACE, CSI_U_INPUT, XTERM_MODIFIED_ENTER_INPUT, SLASH_COMMANDS, DEMIAN_WORDMARK_LINES;
|
|
30137
|
+
var SURFACE, CSI_U_INPUT, XTERM_MODIFIED_ENTER_INPUT, SLASH_COMMANDS, PromptDraftController, SessionMetaBox, DEMIAN_WORDMARK_LINES, DemianWordmark, TranscriptView, BlockView, CommandGuide;
|
|
29332
30138
|
var init_app = __esm({
|
|
29333
30139
|
"src/ui/tui/app.ts"() {
|
|
29334
30140
|
"use strict";
|
|
@@ -29362,6 +30168,86 @@ var init_app = __esm({
|
|
|
29362
30168
|
{ command: "/exit", insert: "/exit", mode: "both", description: "Exit Demian", aliases: ["quit", "close"] },
|
|
29363
30169
|
{ command: "/quit", insert: "/quit", mode: "both", description: "Exit Demian", aliases: ["exit", "close"] }
|
|
29364
30170
|
];
|
|
30171
|
+
PromptDraftController = class {
|
|
30172
|
+
#snapshot;
|
|
30173
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
30174
|
+
constructor(initialValue) {
|
|
30175
|
+
this.#snapshot = {
|
|
30176
|
+
value: initialValue,
|
|
30177
|
+
slashCommandCursor: 0
|
|
30178
|
+
};
|
|
30179
|
+
}
|
|
30180
|
+
snapshot() {
|
|
30181
|
+
return this.#snapshot;
|
|
30182
|
+
}
|
|
30183
|
+
value() {
|
|
30184
|
+
return this.#snapshot.value;
|
|
30185
|
+
}
|
|
30186
|
+
subscribe(listener) {
|
|
30187
|
+
this.#listeners.add(listener);
|
|
30188
|
+
return () => {
|
|
30189
|
+
this.#listeners.delete(listener);
|
|
30190
|
+
};
|
|
30191
|
+
}
|
|
30192
|
+
setValue(value) {
|
|
30193
|
+
const sameValue = value === this.#snapshot.value;
|
|
30194
|
+
const slashCommandClosedPrefix = this.#snapshot.slashCommandClosedPrefix && isSlashCommandClosedForPrompt(value, this.#snapshot.slashCommandClosedPrefix) ? this.#snapshot.slashCommandClosedPrefix : void 0;
|
|
30195
|
+
this.#set({
|
|
30196
|
+
value,
|
|
30197
|
+
slashCommandCursor: sameValue ? this.#snapshot.slashCommandCursor : 0,
|
|
30198
|
+
slashCommandClosedPrefix
|
|
30199
|
+
});
|
|
30200
|
+
}
|
|
30201
|
+
append(value) {
|
|
30202
|
+
if (!value) return;
|
|
30203
|
+
this.setValue(`${this.#snapshot.value}${value}`);
|
|
30204
|
+
}
|
|
30205
|
+
backspace() {
|
|
30206
|
+
this.setValue(Array.from(this.#snapshot.value).slice(0, -1).join(""));
|
|
30207
|
+
}
|
|
30208
|
+
closeSlashCommandsForCurrentPrompt() {
|
|
30209
|
+
this.#set({
|
|
30210
|
+
...this.#snapshot,
|
|
30211
|
+
slashCommandCursor: 0,
|
|
30212
|
+
slashCommandClosedPrefix: this.#snapshot.value
|
|
30213
|
+
});
|
|
30214
|
+
}
|
|
30215
|
+
moveSlashCommandCursor(delta, count) {
|
|
30216
|
+
if (count <= 0) return;
|
|
30217
|
+
this.#set({
|
|
30218
|
+
...this.#snapshot,
|
|
30219
|
+
slashCommandCursor: (this.#snapshot.slashCommandCursor + delta + count) % count
|
|
30220
|
+
});
|
|
30221
|
+
}
|
|
30222
|
+
#set(next) {
|
|
30223
|
+
if (next.value === this.#snapshot.value && next.slashCommandCursor === this.#snapshot.slashCommandCursor && next.slashCommandClosedPrefix === this.#snapshot.slashCommandClosedPrefix) {
|
|
30224
|
+
return;
|
|
30225
|
+
}
|
|
30226
|
+
this.#snapshot = next;
|
|
30227
|
+
for (const listener of this.#listeners) listener();
|
|
30228
|
+
}
|
|
30229
|
+
};
|
|
30230
|
+
SessionMetaBox = React.memo(function SessionMetaBox2({ state, version, marginTop = 1 }) {
|
|
30231
|
+
const width = promptPanelWidth();
|
|
30232
|
+
const compact = width < 68;
|
|
30233
|
+
const agent = shorten(state.selectedAgent ?? state.status.agent ?? "general", compact ? 14 : 18);
|
|
30234
|
+
const model = shorten(state.selection?.model ?? state.status.model ?? "model", compact ? Math.max(18, width - 14) : 30);
|
|
30235
|
+
const workspace = shorten(state.status.cwd ?? process.cwd(), compact ? Math.max(18, width - 18) : 46);
|
|
30236
|
+
const rows = compact ? [
|
|
30237
|
+
`agent ${agent} \xB7 perm ${state.permissionPreset}`,
|
|
30238
|
+
`model ${model}`,
|
|
30239
|
+
`workspace ${workspace}`,
|
|
30240
|
+
version ? `version v${version}` : void 0
|
|
30241
|
+
].filter(Boolean) : [
|
|
30242
|
+
`agent ${agent} \xB7 model ${model} \xB7 perm ${state.permissionPreset}`,
|
|
30243
|
+
`workspace ${workspace}${version ? ` \xB7 version v${version}` : ""}`
|
|
30244
|
+
];
|
|
30245
|
+
return React.createElement(
|
|
30246
|
+
Box,
|
|
30247
|
+
{ alignSelf: "center", width, flexDirection: "column", borderStyle: "single", borderColor: "#334155", paddingX: 1, marginTop },
|
|
30248
|
+
...rows.map((line, index) => React.createElement(SurfaceLine, { key: index, text: line ?? "", width: width - 4, backgroundColor: SURFACE.status, color: "white" }))
|
|
30249
|
+
);
|
|
30250
|
+
});
|
|
29365
30251
|
DEMIAN_WORDMARK_LINES = [
|
|
29366
30252
|
" _ _ ",
|
|
29367
30253
|
" __| | ___ _ __ ___ (_) __ _ _ __ ",
|
|
@@ -29369,6 +30255,56 @@ var init_app = __esm({
|
|
|
29369
30255
|
"| (_| | __/ | | | | | | (_| | | | |",
|
|
29370
30256
|
" \\__,_|\\___|_| |_| |_|_|\\__,_|_| |_|"
|
|
29371
30257
|
];
|
|
30258
|
+
DemianWordmark = React.memo(function DemianWordmark2() {
|
|
30259
|
+
if (terminalWidth() < 58) {
|
|
30260
|
+
return React.createElement(Box, { alignSelf: "center" }, React.createElement(Text, { color: "gray", bold: true }, "DEMIAN"));
|
|
30261
|
+
}
|
|
30262
|
+
return React.createElement(
|
|
30263
|
+
Box,
|
|
30264
|
+
{ alignSelf: "center", flexDirection: "column" },
|
|
30265
|
+
...DEMIAN_WORDMARK_LINES.map(
|
|
30266
|
+
(line, index) => React.createElement(Text, { key: index, color: index < 2 ? "gray" : "white", bold: true }, line)
|
|
30267
|
+
)
|
|
30268
|
+
);
|
|
30269
|
+
});
|
|
30270
|
+
TranscriptView = React.memo(function TranscriptView2({ state }) {
|
|
30271
|
+
const blocks = state.blocks.slice(-12);
|
|
30272
|
+
const streaming = !state.streamingFinalized && state.streamingText ? { kind: "assistant", title: "Assistant streaming", lines: streamingLines(state.streamingText) } : void 0;
|
|
30273
|
+
const width = transcriptPanelWidth();
|
|
30274
|
+
const contentWidth = Math.max(24, width - 2);
|
|
30275
|
+
return React.createElement(
|
|
30276
|
+
Box,
|
|
30277
|
+
{ alignSelf: "center", width, flexDirection: "column", marginTop: 1 },
|
|
30278
|
+
...blocks.map((block) => React.createElement(BlockView, { key: block.id, block, width: contentWidth })),
|
|
30279
|
+
streaming ? React.createElement(BlockView, { key: "streaming", block: streaming, width: contentWidth }) : null
|
|
30280
|
+
);
|
|
30281
|
+
});
|
|
30282
|
+
BlockView = React.memo(function BlockView2({ block, width }) {
|
|
30283
|
+
if (block.kind === "tool") return React.createElement(ToolBlockView, { block, width });
|
|
30284
|
+
if (block.kind === "goal-work") return React.createElement(GoalWorkBlockView, { block, width });
|
|
30285
|
+
if (block.kind === "cowork") return React.createElement(CoworkBlockView, { block, width });
|
|
30286
|
+
if (block.kind === "user") return React.createElement(UserBlockView, { block, width });
|
|
30287
|
+
const color = block.kind === "user" ? "green" : block.kind === "assistant" ? "white" : block.kind === "warning" ? "yellow" : "magenta";
|
|
30288
|
+
const lines = wrapBlockLines(block.lines, width, 80);
|
|
30289
|
+
return React.createElement(
|
|
30290
|
+
Box,
|
|
30291
|
+
{ flexDirection: "column", marginBottom: 1 },
|
|
30292
|
+
React.createElement(Text, { color, bold: true }, fitToWidth(block.title, width)),
|
|
30293
|
+
...lines.map((line, index) => React.createElement(Text, { key: index }, line || " "))
|
|
30294
|
+
);
|
|
30295
|
+
}, sameBlockViewProps);
|
|
30296
|
+
CommandGuide = React.memo(function CommandGuide2({ hint, color, error }) {
|
|
30297
|
+
return React.createElement(
|
|
30298
|
+
Box,
|
|
30299
|
+
{ alignSelf: "center", width: promptPanelWidth(), flexDirection: "column", marginTop: 1 },
|
|
30300
|
+
error ? React.createElement(Text, { color: "yellow" }, error) : null,
|
|
30301
|
+
React.createElement(
|
|
30302
|
+
Box,
|
|
30303
|
+
{ justifyContent: "flex-end" },
|
|
30304
|
+
React.createElement(Text, { color }, hint)
|
|
30305
|
+
)
|
|
30306
|
+
);
|
|
30307
|
+
});
|
|
29372
30308
|
}
|
|
29373
30309
|
});
|
|
29374
30310
|
|
|
@@ -35406,6 +36342,9 @@ function providerOptions(config, catalogs = {}) {
|
|
|
35406
36342
|
permissionMode: provider.type === "claudecode" ? provider.permissionMode : void 0,
|
|
35407
36343
|
ga: provider.type === "claudecode" ? claudeCodeGaEnabled(provider) : void 0,
|
|
35408
36344
|
available,
|
|
36345
|
+
baseURL: typeof provider.baseURL === "string" ? provider.baseURL : void 0,
|
|
36346
|
+
apiKeyEnv: typeof provider.apiKeyEnv === "string" ? provider.apiKeyEnv : void 0,
|
|
36347
|
+
auth: sanitizeProviderAuth(provider.auth),
|
|
35409
36348
|
catalog: catalog ? { status: catalog.status, source: catalog.source, message: catalog.message } : void 0
|
|
35410
36349
|
};
|
|
35411
36350
|
}).sort((left, right) => {
|
|
@@ -35476,6 +36415,13 @@ function providerCatalogAvailable(catalog) {
|
|
|
35476
36415
|
function unavailableLabel(value) {
|
|
35477
36416
|
return `(n/a) ${value}`;
|
|
35478
36417
|
}
|
|
36418
|
+
function sanitizeProviderAuth(auth) {
|
|
36419
|
+
if (!auth || typeof auth !== "object" || Array.isArray(auth)) return void 0;
|
|
36420
|
+
const record = auth;
|
|
36421
|
+
const type = typeof record.type === "string" ? record.type : void 0;
|
|
36422
|
+
const header = typeof record.header === "string" ? record.header : void 0;
|
|
36423
|
+
return type || header ? { type, header } : void 0;
|
|
36424
|
+
}
|
|
35479
36425
|
function errorMessage4(error) {
|
|
35480
36426
|
return error instanceof Error ? error.message : String(error);
|
|
35481
36427
|
}
|
|
@@ -35706,12 +36652,14 @@ __export(controller_exports, {
|
|
|
35706
36652
|
runTuiSession: () => runTuiSession
|
|
35707
36653
|
});
|
|
35708
36654
|
import path36 from "node:path";
|
|
36655
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
35709
36656
|
async function runTuiSession(flags, store) {
|
|
35710
36657
|
const cwd = path36.resolve(flags.cwd ?? process.cwd());
|
|
35711
36658
|
const eventBus = new EventBus();
|
|
35712
36659
|
eventBus.subscribe((event) => store.handleEvent(event));
|
|
35713
|
-
const
|
|
35714
|
-
|
|
36660
|
+
const configInit = await ensureTuiConfigExists(flags);
|
|
36661
|
+
let loadedConfig = await loadConfig({ cwd, configPath: flags.configPath });
|
|
36662
|
+
let config = flags.context ? mergeConfig(loadedConfig, {
|
|
35715
36663
|
context: {
|
|
35716
36664
|
main: flags.context
|
|
35717
36665
|
}
|
|
@@ -35731,6 +36679,10 @@ async function runTuiSession(flags, store) {
|
|
|
35731
36679
|
store.configureSettings(initialSelection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
|
|
35732
36680
|
agent: agentName,
|
|
35733
36681
|
cwd,
|
|
36682
|
+
configPath: configMutationPath(flags),
|
|
36683
|
+
configCreated: configInit.created,
|
|
36684
|
+
configDefaultProvider: config.defaultProvider,
|
|
36685
|
+
configMessage: configInit.created ? `Created config at ${configInit.path}` : void 0,
|
|
35734
36686
|
agentOptions,
|
|
35735
36687
|
contextEfficiency: {
|
|
35736
36688
|
maxContextTokens: config.context.main.maxContextTokens,
|
|
@@ -35739,6 +36691,7 @@ async function runTuiSession(flags, store) {
|
|
|
35739
36691
|
}
|
|
35740
36692
|
});
|
|
35741
36693
|
let nextPrompt = flags.prompt;
|
|
36694
|
+
let openConfigOnFirstPrompt = configInit.created && !flags.prompt.trim();
|
|
35742
36695
|
const conversationsEnabled = flags.conversationManagement === true;
|
|
35743
36696
|
const conversationState = conversationsEnabled ? await initializeConversationState(cwd, flags) : void 0;
|
|
35744
36697
|
let currentConversation = conversationState?.current;
|
|
@@ -35779,13 +36732,28 @@ async function runTuiSession(flags, store) {
|
|
|
35779
36732
|
await applyStartupSessionSelection(selected);
|
|
35780
36733
|
}
|
|
35781
36734
|
for (; ; ) {
|
|
35782
|
-
const
|
|
36735
|
+
const promptPromise = store.requestPrompt(nextPrompt);
|
|
36736
|
+
if (openConfigOnFirstPrompt) {
|
|
36737
|
+
openConfigOnFirstPrompt = false;
|
|
36738
|
+
store.openConfigManager(`Created config at ${configInit.path}`);
|
|
36739
|
+
}
|
|
36740
|
+
const prompt = await promptPromise;
|
|
35783
36741
|
nextPrompt = void 0;
|
|
35784
36742
|
if (store.exitRequested()) {
|
|
35785
36743
|
await saveCurrentSelection();
|
|
35786
36744
|
return 0;
|
|
35787
36745
|
}
|
|
35788
36746
|
if (!prompt) return 0;
|
|
36747
|
+
const configAction = parseTuiConfigAction(prompt);
|
|
36748
|
+
if (configAction) {
|
|
36749
|
+
await handleConfigAction(configAction);
|
|
36750
|
+
continue;
|
|
36751
|
+
}
|
|
36752
|
+
const sessionAction = parseTuiSessionAction(prompt);
|
|
36753
|
+
if (sessionAction) {
|
|
36754
|
+
await handleSessionSelectionShortcut();
|
|
36755
|
+
continue;
|
|
36756
|
+
}
|
|
35789
36757
|
const sessionCommand = parseSessionCommand(prompt);
|
|
35790
36758
|
if (sessionCommand) {
|
|
35791
36759
|
await handleSessionCommand(sessionCommand);
|
|
@@ -36062,6 +37030,25 @@ ${sessionListMessage()}`);
|
|
|
36062
37030
|
await persistCurrentConversation();
|
|
36063
37031
|
}
|
|
36064
37032
|
}
|
|
37033
|
+
async function handleSessionSelectionShortcut() {
|
|
37034
|
+
if (!conversationsEnabled) {
|
|
37035
|
+
store.showSystemMessage("Sessions", "Session management is available in demian-cli TUI, but this embedded runtime lets the host manage sessions.");
|
|
37036
|
+
store.prepareForPrompt("waiting for next message");
|
|
37037
|
+
return;
|
|
37038
|
+
}
|
|
37039
|
+
if (!currentConversation) {
|
|
37040
|
+
currentConversation = createConversationRecord({ id: flags.sessionId ?? createRootSessionId(), cwd });
|
|
37041
|
+
conversationRecords.set(currentConversation.id, currentConversation);
|
|
37042
|
+
}
|
|
37043
|
+
await persistCurrentConversation();
|
|
37044
|
+
const selection = await store.requestSessionSelection(startupSessionOptions(), { cancel: "prompt" });
|
|
37045
|
+
if (selection.kind === "cancel") {
|
|
37046
|
+
store.prepareForPrompt("waiting for next message");
|
|
37047
|
+
return;
|
|
37048
|
+
}
|
|
37049
|
+
if (selection.kind === "exit" || store.exitRequested()) return;
|
|
37050
|
+
await applyStartupSessionSelection(selection);
|
|
37051
|
+
}
|
|
36065
37052
|
function startupSessionOptions() {
|
|
36066
37053
|
const records = sortedConversations();
|
|
36067
37054
|
const sessionOptions = records.map((record) => ({
|
|
@@ -36076,6 +37063,10 @@ ${sessionListMessage()}`);
|
|
|
36076
37063
|
return [...sessionOptions, { kind: "new", title: "New session", cwd, status: "ready", currentWorkspace: true }];
|
|
36077
37064
|
}
|
|
36078
37065
|
async function applyStartupSessionSelection(selection) {
|
|
37066
|
+
if (selection.kind === "cancel") {
|
|
37067
|
+
store.prepareForPrompt("waiting for next message");
|
|
37068
|
+
return;
|
|
37069
|
+
}
|
|
36079
37070
|
if (selection.kind === "new") {
|
|
36080
37071
|
const next = createConversationRecord({ id: createRootSessionId(), cwd });
|
|
36081
37072
|
conversationRecords.set(next.id, next);
|
|
@@ -36132,6 +37123,87 @@ ${sessionListMessage()}`);
|
|
|
36132
37123
|
await preferenceStore.save(selection);
|
|
36133
37124
|
savedSelectionKey = key;
|
|
36134
37125
|
}
|
|
37126
|
+
async function handleConfigAction(action) {
|
|
37127
|
+
const path37 = configMutationPath(flags);
|
|
37128
|
+
try {
|
|
37129
|
+
let message = "Config updated.";
|
|
37130
|
+
let nextSelection = {};
|
|
37131
|
+
if (action.type === "setDefaultProvider") {
|
|
37132
|
+
await updateConfigDefaults({ path: path37, defaultProvider: action.provider });
|
|
37133
|
+
message = `Default provider saved: ${action.provider}`;
|
|
37134
|
+
nextSelection = { provider: action.provider };
|
|
37135
|
+
} else if (action.type === "setDefaultModel") {
|
|
37136
|
+
await setDefaultModelProfile({
|
|
37137
|
+
path: path37,
|
|
37138
|
+
provider: action.provider,
|
|
37139
|
+
profileName: action.profileName,
|
|
37140
|
+
name: action.name,
|
|
37141
|
+
displayName: action.displayName,
|
|
37142
|
+
model: action.model
|
|
37143
|
+
});
|
|
37144
|
+
message = `Default model saved for ${action.provider}: ${action.displayName ?? action.model}`;
|
|
37145
|
+
nextSelection = { provider: action.provider, model: action.displayName ?? action.model };
|
|
37146
|
+
} else if (action.type === "addProvider") {
|
|
37147
|
+
await addProvider({ path: path37, name: action.name, preset: action.preset, apiKeyEnv: action.apiKeyEnv });
|
|
37148
|
+
message = `Provider added: ${action.name}`;
|
|
37149
|
+
nextSelection = { provider: action.name };
|
|
37150
|
+
} else if (action.type === "updateProvider") {
|
|
37151
|
+
await updateProviderSettings({
|
|
37152
|
+
path: path37,
|
|
37153
|
+
provider: action.provider,
|
|
37154
|
+
...action.field === "baseURL" ? { baseURL: action.value } : {},
|
|
37155
|
+
...action.field === "apiKeyEnv" ? { apiKeyEnv: action.value } : {},
|
|
37156
|
+
...action.field === "authHeader" ? { authHeader: action.value } : {}
|
|
37157
|
+
});
|
|
37158
|
+
message = `Provider ${action.field} saved: ${action.provider}`;
|
|
37159
|
+
nextSelection = { provider: action.provider };
|
|
37160
|
+
}
|
|
37161
|
+
await reloadConfigSettings(message, nextSelection);
|
|
37162
|
+
} catch (error) {
|
|
37163
|
+
store.prepareForPrompt("config action failed");
|
|
37164
|
+
store.showSystemMessage("Config", errorMessage(error));
|
|
37165
|
+
store.openConfigManager(errorMessage(error));
|
|
37166
|
+
}
|
|
37167
|
+
}
|
|
37168
|
+
async function reloadConfigSettings(message, selectionFlags = {}) {
|
|
37169
|
+
loadedConfig = await loadConfig({ cwd, configPath: flags.configPath });
|
|
37170
|
+
config = flags.context ? mergeConfig(loadedConfig, {
|
|
37171
|
+
context: {
|
|
37172
|
+
main: flags.context
|
|
37173
|
+
}
|
|
37174
|
+
}) : loadedConfig;
|
|
37175
|
+
const currentSelection = store.currentSelection();
|
|
37176
|
+
const selection = createInteractiveModelSelection(
|
|
37177
|
+
config,
|
|
37178
|
+
selectionFlags.provider || selectionFlags.model ? selectionFlags : { provider: currentSelection.providerName, model: currentSelection.model }
|
|
37179
|
+
);
|
|
37180
|
+
store.configureSettings(selection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
|
|
37181
|
+
agent: store.currentAgentName(),
|
|
37182
|
+
cwd,
|
|
37183
|
+
configPath: configMutationPath(flags),
|
|
37184
|
+
configDefaultProvider: config.defaultProvider,
|
|
37185
|
+
configMessage: message,
|
|
37186
|
+
agentOptions,
|
|
37187
|
+
contextEfficiency: {
|
|
37188
|
+
maxContextTokens: config.context.main.maxContextTokens,
|
|
37189
|
+
compactAtRatio: config.context.main.compactAtRatio ?? config.context.main.compressAtRatio ?? 0.5,
|
|
37190
|
+
compactAtTokens: config.context.main.maxContextTokens ? Math.floor(config.context.main.maxContextTokens * (config.context.main.compactAtRatio ?? config.context.main.compressAtRatio ?? 0.5)) : void 0
|
|
37191
|
+
}
|
|
37192
|
+
});
|
|
37193
|
+
store.openConfigManager(message);
|
|
37194
|
+
}
|
|
37195
|
+
}
|
|
37196
|
+
async function ensureTuiConfigExists(flags) {
|
|
37197
|
+
const target = configMutationPath(flags);
|
|
37198
|
+
if (!flags.configPath) {
|
|
37199
|
+
const jsondPath = target.replace(/\.json$/i, ".jsond");
|
|
37200
|
+
if (existsSync5(target) || existsSync5(jsondPath)) return { path: target, created: false };
|
|
37201
|
+
}
|
|
37202
|
+
const result = await createUserConfig({ path: target });
|
|
37203
|
+
return { path: result.path, created: result.created };
|
|
37204
|
+
}
|
|
37205
|
+
function configMutationPath(flags) {
|
|
37206
|
+
return flags.configPath ? path36.resolve(flags.configPath) : defaultUserConfigPath();
|
|
36135
37207
|
}
|
|
36136
37208
|
function isGoalStateClearingAction(action) {
|
|
36137
37209
|
return action === "status" || action === "pause" || action === "resume";
|
|
@@ -36170,6 +37242,7 @@ var init_controller = __esm({
|
|
|
36170
37242
|
init_registry();
|
|
36171
37243
|
init_types();
|
|
36172
37244
|
init_config();
|
|
37245
|
+
init_config_scaffold();
|
|
36173
37246
|
init_execution();
|
|
36174
37247
|
init_events();
|
|
36175
37248
|
init_runtime();
|
|
@@ -36184,6 +37257,7 @@ var init_controller = __esm({
|
|
|
36184
37257
|
init_history();
|
|
36185
37258
|
init_preferences();
|
|
36186
37259
|
init_settings();
|
|
37260
|
+
init_store();
|
|
36187
37261
|
init_conversations();
|
|
36188
37262
|
}
|
|
36189
37263
|
});
|
|
@@ -36417,283 +37491,16 @@ Flags:
|
|
|
36417
37491
|
}
|
|
36418
37492
|
|
|
36419
37493
|
// src/config-command.ts
|
|
37494
|
+
init_config_scaffold();
|
|
37495
|
+
init_config();
|
|
37496
|
+
init_catalog();
|
|
37497
|
+
init_claudecode_auth_preflight();
|
|
37498
|
+
init_claudecode_paths();
|
|
36420
37499
|
import { spawn as spawn4 } from "node:child_process";
|
|
36421
37500
|
import { stat as stat4 } from "node:fs/promises";
|
|
36422
37501
|
import os11 from "node:os";
|
|
36423
37502
|
import path17 from "node:path";
|
|
36424
37503
|
import readline3 from "node:readline/promises";
|
|
36425
|
-
|
|
36426
|
-
// src/config-scaffold.ts
|
|
36427
|
-
init_path_expansion();
|
|
36428
|
-
import { chmod, mkdir, readFile as readFile2, rename, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
36429
|
-
import os3 from "node:os";
|
|
36430
|
-
import path3 from "node:path";
|
|
36431
|
-
function defaultUserConfigPath() {
|
|
36432
|
-
return path3.join(os3.homedir(), ".demian", "config.json");
|
|
36433
|
-
}
|
|
36434
|
-
function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
|
|
36435
|
-
return {
|
|
36436
|
-
version: 2,
|
|
36437
|
-
defaultProvider,
|
|
36438
|
-
providers: {
|
|
36439
|
-
openai: {
|
|
36440
|
-
type: "openai-compatible",
|
|
36441
|
-
baseURL: "https://api.openai.com/v1",
|
|
36442
|
-
apiKey: "",
|
|
36443
|
-
apiKeyEnv: "OPENAI_API_KEY",
|
|
36444
|
-
catalog: { type: "openai-models", endpoint: "https://api.openai.com/v1/models" }
|
|
36445
|
-
},
|
|
36446
|
-
anthropic: {
|
|
36447
|
-
type: "anthropic",
|
|
36448
|
-
baseURL: "https://api.anthropic.com/v1",
|
|
36449
|
-
apiKey: "",
|
|
36450
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
36451
|
-
catalog: { type: "anthropic-models", endpoint: "https://api.anthropic.com/v1/models" }
|
|
36452
|
-
},
|
|
36453
|
-
gemini: {
|
|
36454
|
-
type: "openai-compatible",
|
|
36455
|
-
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
36456
|
-
apiKey: "",
|
|
36457
|
-
apiKeyEnv: "GEMINI_API_KEY",
|
|
36458
|
-
apiKeyEnvAliases: ["GOOGLE_API_KEY"],
|
|
36459
|
-
catalog: { type: "gemini-openai-models", endpoint: "https://generativelanguage.googleapis.com/v1beta/openai/models" }
|
|
36460
|
-
},
|
|
36461
|
-
groq: {
|
|
36462
|
-
type: "openai-compatible",
|
|
36463
|
-
baseURL: "https://api.groq.com/openai/v1",
|
|
36464
|
-
apiKey: "",
|
|
36465
|
-
apiKeyEnv: "GROQ_API_KEY",
|
|
36466
|
-
catalog: { type: "groq-models", endpoint: "https://api.groq.com/openai/v1/models" }
|
|
36467
|
-
},
|
|
36468
|
-
azure: {
|
|
36469
|
-
type: "openai-compatible",
|
|
36470
|
-
auth: { type: "api-key", header: "api-key" },
|
|
36471
|
-
modelProfiles: [
|
|
36472
|
-
{
|
|
36473
|
-
name: "azure-example",
|
|
36474
|
-
displayName: "Azure example",
|
|
36475
|
-
model: "azure-deployment-name",
|
|
36476
|
-
baseURL: "https://example.openai.azure.com/openai/v1",
|
|
36477
|
-
apiKey: "",
|
|
36478
|
-
apiKeyEnv: "AZURE_OPENAI_API_KEY"
|
|
36479
|
-
}
|
|
36480
|
-
]
|
|
36481
|
-
},
|
|
36482
|
-
lmstudio: {
|
|
36483
|
-
type: "openai-compatible",
|
|
36484
|
-
baseURL: "http://localhost:1234/v1",
|
|
36485
|
-
apiKey: "lm-studio",
|
|
36486
|
-
modelProfiles: [],
|
|
36487
|
-
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:1234/v1/models" }
|
|
36488
|
-
},
|
|
36489
|
-
"ollama-local": {
|
|
36490
|
-
type: "openai-compatible",
|
|
36491
|
-
baseURL: "http://localhost:11434/v1",
|
|
36492
|
-
apiKey: "ollama",
|
|
36493
|
-
modelProfiles: [],
|
|
36494
|
-
quirks: { omitTemperature: true },
|
|
36495
|
-
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:11434/v1/models" }
|
|
36496
|
-
},
|
|
36497
|
-
"ollama-cloud": {
|
|
36498
|
-
type: "ollama",
|
|
36499
|
-
baseURL: "https://ollama.com/api",
|
|
36500
|
-
apiKey: "",
|
|
36501
|
-
apiKeyEnv: "OLLAMA_API_KEY",
|
|
36502
|
-
modelProfiles: [],
|
|
36503
|
-
catalog: { type: "ollama-tags", endpoint: "https://ollama.com/api/tags" }
|
|
36504
|
-
},
|
|
36505
|
-
llamacpp: {
|
|
36506
|
-
type: "openai-compatible",
|
|
36507
|
-
baseURL: "http://localhost:8080/v1",
|
|
36508
|
-
apiKey: "llama.cpp",
|
|
36509
|
-
modelProfiles: [],
|
|
36510
|
-
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8080/v1/models" }
|
|
36511
|
-
},
|
|
36512
|
-
vllm: {
|
|
36513
|
-
type: "openai-compatible",
|
|
36514
|
-
baseURL: "http://localhost:8000/v1",
|
|
36515
|
-
apiKey: "vllm",
|
|
36516
|
-
modelProfiles: [],
|
|
36517
|
-
catalog: { type: "openai-compatible-models", endpoint: "http://localhost:8000/v1/models" }
|
|
36518
|
-
},
|
|
36519
|
-
codex: {
|
|
36520
|
-
type: "codex",
|
|
36521
|
-
baseURL: "https://chatgpt.com/backend-api/codex",
|
|
36522
|
-
authStore: "auto",
|
|
36523
|
-
allowApiKeyFallback: false,
|
|
36524
|
-
promptCacheKey: "root-session",
|
|
36525
|
-
catalog: { type: "codex-oauth-models" }
|
|
36526
|
-
},
|
|
36527
|
-
claudecode: {
|
|
36528
|
-
type: "claudecode",
|
|
36529
|
-
runtime: "agent-sdk",
|
|
36530
|
-
cliPath: "~/.local/bin/claude",
|
|
36531
|
-
cwdMode: "session",
|
|
36532
|
-
historyPolicy: "passthrough-resume",
|
|
36533
|
-
onInvalidResume: "fresh",
|
|
36534
|
-
attachmentFallback: "block",
|
|
36535
|
-
allowSubagents: false,
|
|
36536
|
-
sanitizeApiKeyEnv: true,
|
|
36537
|
-
authPreflight: true,
|
|
36538
|
-
useBareMode: false,
|
|
36539
|
-
usageLedgerScope: "process",
|
|
36540
|
-
sessionLock: true,
|
|
36541
|
-
abortPolicy: "record-only",
|
|
36542
|
-
catalog: { type: "claudecode-supported-models" }
|
|
36543
|
-
}
|
|
36544
|
-
}
|
|
36545
|
-
};
|
|
36546
|
-
}
|
|
36547
|
-
async function createUserConfig(options = {}) {
|
|
36548
|
-
const filePath = expandHome(options.path ?? defaultUserConfigPath());
|
|
36549
|
-
const content = `${JSON.stringify(defaultUserConfig(options.defaultProvider), null, 2)}
|
|
36550
|
-
`;
|
|
36551
|
-
if (options.print) return { path: filePath, created: false, content };
|
|
36552
|
-
const existed = await exists(filePath);
|
|
36553
|
-
if (existed && !options.force) return { path: filePath, created: false, content: await readFile2(filePath, "utf8"), existed: true };
|
|
36554
|
-
await writeJsonAtomic(filePath, content);
|
|
36555
|
-
return { path: filePath, created: true, content, existed };
|
|
36556
|
-
}
|
|
36557
|
-
async function addProvider(options) {
|
|
36558
|
-
const filePath = expandHome(options.path ?? defaultUserConfigPath());
|
|
36559
|
-
const config = await readConfigObject(filePath);
|
|
36560
|
-
const providers = objectValue(config.providers);
|
|
36561
|
-
const name = options.name;
|
|
36562
|
-
if (providers[name] && !options.force) throw new Error(`Provider ${name} already exists. Use --force to overwrite it.`);
|
|
36563
|
-
providers[name] = providerPreset(options);
|
|
36564
|
-
config.providers = providers;
|
|
36565
|
-
const content = `${JSON.stringify(config, null, 2)}
|
|
36566
|
-
`;
|
|
36567
|
-
await writeJsonAtomic(filePath, content);
|
|
36568
|
-
return { path: filePath, created: true, content };
|
|
36569
|
-
}
|
|
36570
|
-
async function addModelProfile(options) {
|
|
36571
|
-
const filePath = expandHome(options.path ?? defaultUserConfigPath());
|
|
36572
|
-
const config = await readConfigObject(filePath);
|
|
36573
|
-
const providers = objectValue(config.providers);
|
|
36574
|
-
const provider = objectValue(providers[options.provider]);
|
|
36575
|
-
const profiles = Array.isArray(provider.modelProfiles) ? [...provider.modelProfiles] : [];
|
|
36576
|
-
const displayName = options.displayName ?? options.name;
|
|
36577
|
-
const existingByName = profiles.findIndex((entry) => entry.name === options.name);
|
|
36578
|
-
const existingByDisplay = profiles.findIndex((entry) => entry.displayName === displayName);
|
|
36579
|
-
if (existingByName >= 0 && !options.force) throw new Error(`Profile name "${options.name}" already exists on provider ${options.provider}. Use --force to overwrite it.`);
|
|
36580
|
-
if (existingByDisplay >= 0 && existingByDisplay !== existingByName && !options.force) {
|
|
36581
|
-
throw new Error(`Profile displayName "${displayName}" already exists on provider ${options.provider} (profile name: ${profiles[existingByDisplay].name}). Use --force or pick a different --display-name.`);
|
|
36582
|
-
}
|
|
36583
|
-
const next = {
|
|
36584
|
-
name: options.name,
|
|
36585
|
-
displayName,
|
|
36586
|
-
model: options.model,
|
|
36587
|
-
...options.baseURL ? { baseURL: options.baseURL } : {},
|
|
36588
|
-
...options.apiKey !== void 0 || options.apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
|
|
36589
|
-
...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {}
|
|
36590
|
-
};
|
|
36591
|
-
if (existingByName >= 0) profiles[existingByName] = next;
|
|
36592
|
-
else profiles.push(next);
|
|
36593
|
-
provider.modelProfiles = profiles;
|
|
36594
|
-
providers[options.provider] = provider;
|
|
36595
|
-
config.providers = providers;
|
|
36596
|
-
const content = `${JSON.stringify(config, null, 2)}
|
|
36597
|
-
`;
|
|
36598
|
-
await writeJsonAtomic(filePath, content);
|
|
36599
|
-
return { path: filePath, created: true, content };
|
|
36600
|
-
}
|
|
36601
|
-
async function readConfigObject(filePath) {
|
|
36602
|
-
if (!await exists(filePath)) await createUserConfig({ path: filePath });
|
|
36603
|
-
const raw = JSON.parse(await readFile2(filePath, "utf8"));
|
|
36604
|
-
raw.version ??= 2;
|
|
36605
|
-
raw.providers = objectValue(raw.providers);
|
|
36606
|
-
return raw;
|
|
36607
|
-
}
|
|
36608
|
-
function providerPreset(options) {
|
|
36609
|
-
const preset = options.preset ?? options.name;
|
|
36610
|
-
const auth = apiKeyAuthFields(options);
|
|
36611
|
-
const openAIAuth = options.authHeader ? { auth: { type: "api-key", header: options.authHeader } } : {};
|
|
36612
|
-
if (preset === "openai") return { type: "openai-compatible", baseURL: options.baseURL ?? "https://api.openai.com/v1", ...apiKeyAuthFields(options, "OPENAI_API_KEY"), ...openAIAuth, catalog: { type: "openai-models", endpoint: `${(options.baseURL ?? "https://api.openai.com/v1").replace(/\/+$/, "")}/models` } };
|
|
36613
|
-
if (preset === "anthropic") return { type: "anthropic", baseURL: options.baseURL ?? "https://api.anthropic.com/v1", ...apiKeyAuthFields(options, "ANTHROPIC_API_KEY"), catalog: { type: "anthropic-models", endpoint: `${(options.baseURL ?? "https://api.anthropic.com/v1").replace(/\/+$/, "")}/models` } };
|
|
36614
|
-
if (preset === "gemini") {
|
|
36615
|
-
const geminiBase = options.baseURL ?? "https://generativelanguage.googleapis.com/v1beta/openai/";
|
|
36616
|
-
return { type: "openai-compatible", baseURL: geminiBase, ...apiKeyAuthFields(options, "GEMINI_API_KEY"), apiKeyEnvAliases: ["GOOGLE_API_KEY"], ...openAIAuth, catalog: { type: "gemini-openai-models", endpoint: `${geminiBase.replace(/\/+$/, "")}/models` } };
|
|
36617
|
-
}
|
|
36618
|
-
if (preset === "groq") return { type: "openai-compatible", baseURL: options.baseURL ?? "https://api.groq.com/openai/v1", ...apiKeyAuthFields(options, "GROQ_API_KEY"), ...openAIAuth, catalog: { type: "groq-models", endpoint: `${(options.baseURL ?? "https://api.groq.com/openai/v1").replace(/\/+$/, "")}/models` } };
|
|
36619
|
-
if (preset === "azure") {
|
|
36620
|
-
return {
|
|
36621
|
-
type: "openai-compatible",
|
|
36622
|
-
auth: { type: "api-key", header: options.authHeader ?? "api-key" },
|
|
36623
|
-
...auth,
|
|
36624
|
-
modelProfiles: [
|
|
36625
|
-
{
|
|
36626
|
-
name: "azure-example",
|
|
36627
|
-
displayName: "Azure example",
|
|
36628
|
-
model: "azure-deployment-name",
|
|
36629
|
-
baseURL: options.baseURL ?? "https://example.openai.azure.com/openai/v1",
|
|
36630
|
-
...options.apiKey ? {} : { apiKey: "" },
|
|
36631
|
-
apiKeyEnv: options.apiKeyEnv ?? "AZURE_OPENAI_API_KEY"
|
|
36632
|
-
}
|
|
36633
|
-
]
|
|
36634
|
-
};
|
|
36635
|
-
}
|
|
36636
|
-
if (preset === "lmstudio") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:1234/v1", apiKey: options.apiKey ?? "lm-studio", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:1234/v1").replace(/\/+$/, "")}/models` } };
|
|
36637
|
-
if (preset === "ollama-local" || preset === "ollama") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:11434/v1", apiKey: options.apiKey ?? "ollama", modelProfiles: [], quirks: { omitTemperature: true }, catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:11434/v1").replace(/\/+$/, "")}/models` } };
|
|
36638
|
-
if (preset === "ollama-cloud") return { type: "ollama", baseURL: options.baseURL ?? "https://ollama.com/api", ...apiKeyAuthFields(options, "OLLAMA_API_KEY"), modelProfiles: [], catalog: { type: "ollama-tags", endpoint: `${(options.baseURL ?? "https://ollama.com/api").replace(/\/+$/, "")}/tags` } };
|
|
36639
|
-
if (preset === "llamacpp") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:8080/v1", apiKey: options.apiKey ?? "llama.cpp", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:8080/v1").replace(/\/+$/, "")}/models` } };
|
|
36640
|
-
if (preset === "vllm") return { type: "openai-compatible", baseURL: options.baseURL ?? "http://localhost:8000/v1", apiKey: options.apiKey ?? "vllm", modelProfiles: [], catalog: { type: "openai-compatible-models", endpoint: `${(options.baseURL ?? "http://localhost:8000/v1").replace(/\/+$/, "")}/models` } };
|
|
36641
|
-
if (preset === "codex") return { type: "codex", baseURL: options.baseURL ?? "https://chatgpt.com/backend-api/codex", authStore: "auto", allowApiKeyFallback: false, promptCacheKey: "root-session", catalog: { type: "codex-oauth-models" } };
|
|
36642
|
-
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" } };
|
|
36643
|
-
if ((options.type ?? preset) === "openai-compatible") {
|
|
36644
|
-
const baseURL = options.baseURL;
|
|
36645
|
-
if (!baseURL) throw new Error("openai-compatible provider requires --base-url.");
|
|
36646
|
-
return {
|
|
36647
|
-
type: "openai-compatible",
|
|
36648
|
-
baseURL,
|
|
36649
|
-
...auth,
|
|
36650
|
-
...openAIAuth,
|
|
36651
|
-
catalog: { type: "openai-compatible-models", endpoint: `${baseURL.replace(/\/+$/, "")}/models` }
|
|
36652
|
-
};
|
|
36653
|
-
}
|
|
36654
|
-
throw new Error(`Unknown provider preset: ${preset}`);
|
|
36655
|
-
}
|
|
36656
|
-
function apiKeyAuthFields(options, defaultApiKeyEnv) {
|
|
36657
|
-
const apiKeyEnv = options.apiKeyEnv ?? defaultApiKeyEnv;
|
|
36658
|
-
return {
|
|
36659
|
-
...options.apiKey !== void 0 || apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
|
|
36660
|
-
...apiKeyEnv ? { apiKeyEnv } : {}
|
|
36661
|
-
};
|
|
36662
|
-
}
|
|
36663
|
-
async function writeJsonAtomic(filePath, content) {
|
|
36664
|
-
await mkdir(path3.dirname(filePath), { recursive: true, mode: 448 });
|
|
36665
|
-
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
36666
|
-
await writeFile2(temp, content, { mode: 384 });
|
|
36667
|
-
await rename(temp, filePath);
|
|
36668
|
-
await chmod(filePath, 384).catch(() => void 0);
|
|
36669
|
-
}
|
|
36670
|
-
function detectDefaultProvider() {
|
|
36671
|
-
if (process.env.OPENAI_API_KEY) return "openai";
|
|
36672
|
-
if (process.env.ANTHROPIC_API_KEY) return "anthropic";
|
|
36673
|
-
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) return "gemini";
|
|
36674
|
-
if (process.env.GROQ_API_KEY) return "groq";
|
|
36675
|
-
return "openai";
|
|
36676
|
-
}
|
|
36677
|
-
function objectValue(value) {
|
|
36678
|
-
return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
|
|
36679
|
-
}
|
|
36680
|
-
async function exists(filePath) {
|
|
36681
|
-
try {
|
|
36682
|
-
await stat(filePath);
|
|
36683
|
-
return true;
|
|
36684
|
-
} catch {
|
|
36685
|
-
return false;
|
|
36686
|
-
}
|
|
36687
|
-
}
|
|
36688
|
-
function expandHome(value) {
|
|
36689
|
-
return resolveExpandedPath(value);
|
|
36690
|
-
}
|
|
36691
|
-
|
|
36692
|
-
// src/config-command.ts
|
|
36693
|
-
init_config();
|
|
36694
|
-
init_catalog();
|
|
36695
|
-
init_claudecode_auth_preflight();
|
|
36696
|
-
init_claudecode_paths();
|
|
36697
37504
|
async function maybeRunConfigCommand(argv) {
|
|
36698
37505
|
if (argv[0] === "config") return runConfigCommand(argv.slice(1));
|
|
36699
37506
|
if (argv[0] === "auth") return runAuthCommand(argv.slice(1));
|
|
@@ -36807,6 +37614,41 @@ async function runConfigCommand(argv) {
|
|
|
36807
37614
|
force
|
|
36808
37615
|
});
|
|
36809
37616
|
process.stdout.write(`Updated config: ${result.path}
|
|
37617
|
+
`);
|
|
37618
|
+
return 0;
|
|
37619
|
+
}
|
|
37620
|
+
if (command === "set-default-model") {
|
|
37621
|
+
const [provider, ...tail2] = rest;
|
|
37622
|
+
if (!provider) throw new Error("config set-default-model requires a provider name.");
|
|
37623
|
+
const flags = parseFlags(tail2);
|
|
37624
|
+
const result = await setDefaultModelProfile({
|
|
37625
|
+
path: stringFlag(flags, "path"),
|
|
37626
|
+
provider,
|
|
37627
|
+
profileName: stringFlag(flags, "profile"),
|
|
37628
|
+
name: stringFlag(flags, "name"),
|
|
37629
|
+
displayName: stringFlag(flags, "display-name"),
|
|
37630
|
+
model: stringFlag(flags, "model"),
|
|
37631
|
+
baseURL: stringFlag(flags, "base-url"),
|
|
37632
|
+
apiKey: await apiKeyFromFlags(flags),
|
|
37633
|
+
apiKeyEnv: stringFlag(flags, "api-key-env")
|
|
37634
|
+
});
|
|
37635
|
+
process.stdout.write(`Updated config: ${result.path}
|
|
37636
|
+
`);
|
|
37637
|
+
return 0;
|
|
37638
|
+
}
|
|
37639
|
+
if (command === "update-provider") {
|
|
37640
|
+
const [provider, ...tail2] = rest;
|
|
37641
|
+
if (!provider) throw new Error("config update-provider requires a provider name.");
|
|
37642
|
+
const flags = parseFlags(tail2);
|
|
37643
|
+
const result = await updateProviderSettings({
|
|
37644
|
+
path: stringFlag(flags, "path"),
|
|
37645
|
+
provider,
|
|
37646
|
+
baseURL: stringFlagOrEmpty(flags, "base-url"),
|
|
37647
|
+
apiKey: await apiKeyFromFlags(flags),
|
|
37648
|
+
apiKeyEnv: stringFlagOrEmpty(flags, "api-key-env"),
|
|
37649
|
+
authHeader: stringFlagOrEmpty(flags, "auth-header")
|
|
37650
|
+
});
|
|
37651
|
+
process.stdout.write(`Updated config: ${result.path}
|
|
36810
37652
|
`);
|
|
36811
37653
|
return 0;
|
|
36812
37654
|
}
|
|
@@ -36904,6 +37746,10 @@ function stringFlag(flags, name) {
|
|
|
36904
37746
|
const value = flags.get(name);
|
|
36905
37747
|
return typeof value === "string" && value.trim() ? value : void 0;
|
|
36906
37748
|
}
|
|
37749
|
+
function stringFlagOrEmpty(flags, name) {
|
|
37750
|
+
const value = flags.get(name);
|
|
37751
|
+
return typeof value === "string" ? value : void 0;
|
|
37752
|
+
}
|
|
36907
37753
|
function requiredStringFlag(flags, name) {
|
|
36908
37754
|
const value = stringFlag(flags, name);
|
|
36909
37755
|
if (!value) throw new Error(`--${name} is required.`);
|
|
@@ -36946,6 +37792,8 @@ Usage:
|
|
|
36946
37792
|
demian config setup interactive multi-step provider wizard
|
|
36947
37793
|
demian config add-provider <preset|openai-compatible> [--name <name>] [--base-url <url>] [--api-key-env <env>] [--api-key-stdin] [--auth-header <header>] [--force]
|
|
36948
37794
|
demian config add-model <provider> --name <name> --model <model> [--display-name <label>] [--base-url <url>] [--api-key-env <env>] [--api-key-stdin] [--force]
|
|
37795
|
+
demian config set-default-model <provider> [--profile <name> | --model <model>] [--display-name <label>]
|
|
37796
|
+
demian config update-provider <provider> [--base-url <url>] [--api-key-env <env>] [--auth-header <header>]
|
|
36949
37797
|
demian config models <provider> [--refresh] [--config <path>]
|
|
36950
37798
|
demian config list [--config <path>]
|
|
36951
37799
|
demian config path
|
|
@@ -37129,7 +37977,6 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
37129
37977
|
process.stderr.write("demian TUI requires an interactive terminal. Use demian-plain for pipes or non-interactive use.\n");
|
|
37130
37978
|
return 1;
|
|
37131
37979
|
}
|
|
37132
|
-
if (!flags.noWizard) await runFirstRunWizard().catch(() => void 0);
|
|
37133
37980
|
try {
|
|
37134
37981
|
const [{ render }, ReactModule, { TuiApp: TuiApp2 }, { TuiStore: TuiStore2 }, { runTuiSession: runTuiSession2 }] = await Promise.all([
|
|
37135
37982
|
dynamicImport4("ink"),
|
|
@@ -37290,6 +38137,8 @@ Default:
|
|
|
37290
38137
|
Press Esc then p to select provider, Esc then m to edit model, then Enter to send.
|
|
37291
38138
|
Press Esc then a to select the main agent before sending a message.
|
|
37292
38139
|
Press Esc then o to select the default permission preset: deny, ask, auto, or full.
|
|
38140
|
+
Press Esc then c to configure provider defaults and model profiles.
|
|
38141
|
+
Press Esc then k to open the command palette, or Esc then s to select a saved session.
|
|
37293
38142
|
Press up/down in the message composer to recall previous prompts.
|
|
37294
38143
|
Provider/model selections are remembered in ~/.demian/preferences.json.
|
|
37295
38144
|
After each answer, the TUI returns to standby for the next message.
|
|
@@ -37297,7 +38146,7 @@ Default:
|
|
|
37297
38146
|
Use /compact to compact current history, /stop to stop active work, and /exit or /quit to close the TUI.
|
|
37298
38147
|
Use /session list, /session new, /session switch <number|id|title>, /session rename <title>,
|
|
37299
38148
|
and /session delete <number|id|title> to manage saved CLI conversations.
|
|
37300
|
-
Press Esc then q to quit, Esc then s to stop
|
|
38149
|
+
Press Esc then q to quit, Esc then s while running to stop the task, and Esc then u to clear the composer.
|
|
37301
38150
|
Tool approvals show the requested command/path in a permission dialog.
|
|
37302
38151
|
Press y to allow once, a to always allow the grant scope, or n/Enter to deny.
|
|
37303
38152
|
Prompt arguments are still accepted as a compatibility shortcut.
|