demian-cli 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -3
- package/dist/cli.mjs +146 -3
- package/dist/tui-sidecar/darwin-arm64/demian-tui +0 -0
- package/dist/tui-sidecar/linux-x64/demian-tui +0 -0
- package/dist/tui.mjs +1554 -469
- package/dist/vscode-worker.mjs +1013 -441
- package/docs/ko/README.md +39 -3
- package/package.json +7 -4
package/dist/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;
|
|
@@ -22813,8 +23189,8 @@ function permissionLabel(req) {
|
|
|
22813
23189
|
if ((req.tool === "write_file" || req.tool === "edit_file" || req.tool === "read_file") && typeof inputObject.path === "string") {
|
|
22814
23190
|
return `${req.tool}: ${inputObject.path}`;
|
|
22815
23191
|
}
|
|
22816
|
-
const
|
|
22817
|
-
if (
|
|
23192
|
+
const path38 = typeof inputObject.path === "string" ? inputObject.path : typeof inputObject.file_path === "string" ? inputObject.file_path : void 0;
|
|
23193
|
+
if (path38) return `${tool}: ${path38}`;
|
|
22818
23194
|
return tool;
|
|
22819
23195
|
}
|
|
22820
23196
|
var init_prompt = __esm({
|
|
@@ -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;
|
|
@@ -26237,10 +26675,20 @@ var init_store = __esm({
|
|
|
26237
26675
|
}
|
|
26238
26676
|
if (option.id) this.#resolveSession({ kind: "session", id: option.id });
|
|
26239
26677
|
}
|
|
26678
|
+
submitDeleteSessionSelection() {
|
|
26679
|
+
if (this.#state.inputMode !== "session") return;
|
|
26680
|
+
const option = this.#state.sessionOptions[this.#state.sessionCursor];
|
|
26681
|
+
if (!option || option.kind === "new" || !option.id) return;
|
|
26682
|
+
this.#resolveSession({ kind: "delete", id: option.id });
|
|
26683
|
+
}
|
|
26240
26684
|
submitNewSessionSelection() {
|
|
26241
26685
|
if (this.#state.inputMode !== "session") return;
|
|
26242
26686
|
this.#resolveSession({ kind: "new" });
|
|
26243
26687
|
}
|
|
26688
|
+
submitSessionSelectionShortcut() {
|
|
26689
|
+
if (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim()) return;
|
|
26690
|
+
this.#resolvePrompt(`${TUI_SESSION_ACTION_PREFIX}${JSON.stringify({ type: "select" })}`);
|
|
26691
|
+
}
|
|
26244
26692
|
requestPrompt(initialPrompt) {
|
|
26245
26693
|
if (this.#exitRequested) return Promise.resolve("");
|
|
26246
26694
|
const prompt = (initialPrompt ?? "").trim();
|
|
@@ -26338,6 +26786,143 @@ var init_store = __esm({
|
|
|
26338
26786
|
this.#state.activity = `provider ${option.name} selected`;
|
|
26339
26787
|
this.#notify();
|
|
26340
26788
|
}
|
|
26789
|
+
openConfigManager(message) {
|
|
26790
|
+
const canOpenFromConfig = this.#state.inputMode === "config" || this.#state.inputMode === "configModel" || this.#state.inputMode === "configModelInput" || this.#state.inputMode === "configProviderInput" || this.#state.inputMode === "configAddProvider";
|
|
26791
|
+
if (!canOpenFromConfig && (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim())) return;
|
|
26792
|
+
this.#state.inputMode = "config";
|
|
26793
|
+
const current = this.#state.selection?.providerName ?? this.#state.status.provider;
|
|
26794
|
+
this.#state.providerCursor = Math.max(0, this.#state.providerOptions.findIndex((option) => option.name === current));
|
|
26795
|
+
this.#state.configMessage = message ?? this.#state.configMessage;
|
|
26796
|
+
this.#state.settingsError = void 0;
|
|
26797
|
+
this.#state.activity = "configure providers and models";
|
|
26798
|
+
this.#notify();
|
|
26799
|
+
}
|
|
26800
|
+
openConfigProviderInput(field) {
|
|
26801
|
+
if (this.#state.inputMode !== "config") return;
|
|
26802
|
+
const provider = this.#selectedConfigProvider();
|
|
26803
|
+
if (!provider) return;
|
|
26804
|
+
this.#state.inputMode = "configProviderInput";
|
|
26805
|
+
this.#state.configProviderInputField = field;
|
|
26806
|
+
this.#state.configProviderInput = configProviderFieldValue(provider, field);
|
|
26807
|
+
this.#state.settingsError = void 0;
|
|
26808
|
+
this.#state.activity = `edit ${field} for ${provider.name}`;
|
|
26809
|
+
this.#notify();
|
|
26810
|
+
}
|
|
26811
|
+
appendConfigProviderInput(input2) {
|
|
26812
|
+
if (this.#state.inputMode !== "configProviderInput") return;
|
|
26813
|
+
this.#state.configProviderInput += input2;
|
|
26814
|
+
this.#state.settingsError = void 0;
|
|
26815
|
+
this.#notify();
|
|
26816
|
+
}
|
|
26817
|
+
backspaceConfigProviderInput() {
|
|
26818
|
+
if (this.#state.inputMode !== "configProviderInput") return;
|
|
26819
|
+
this.#state.configProviderInput = Array.from(this.#state.configProviderInput).slice(0, -1).join("");
|
|
26820
|
+
this.#state.settingsError = void 0;
|
|
26821
|
+
this.#notify();
|
|
26822
|
+
}
|
|
26823
|
+
submitConfigProviderInput() {
|
|
26824
|
+
if (this.#state.inputMode !== "configProviderInput") return;
|
|
26825
|
+
const provider = this.#selectedConfigProvider();
|
|
26826
|
+
const field = this.#state.configProviderInputField;
|
|
26827
|
+
if (!provider || !field) return;
|
|
26828
|
+
this.#submitConfigAction({ type: "updateProvider", provider: provider.name, field, value: this.#state.configProviderInput });
|
|
26829
|
+
}
|
|
26830
|
+
moveConfigProviderCursor(delta) {
|
|
26831
|
+
if (this.#state.inputMode !== "config" || this.#state.providerOptions.length === 0) return;
|
|
26832
|
+
const count = this.#state.providerOptions.length;
|
|
26833
|
+
this.#state.providerCursor = (this.#state.providerCursor + delta + count) % count;
|
|
26834
|
+
this.#notify();
|
|
26835
|
+
}
|
|
26836
|
+
submitConfigDefaultProvider() {
|
|
26837
|
+
if (this.#state.inputMode !== "config") return;
|
|
26838
|
+
const option = this.#selectedConfigProvider();
|
|
26839
|
+
if (!option) return;
|
|
26840
|
+
this.#submitConfigAction({ type: "setDefaultProvider", provider: option.name });
|
|
26841
|
+
}
|
|
26842
|
+
openConfigModelSelector() {
|
|
26843
|
+
if (this.#state.inputMode !== "config" && this.#state.inputMode !== "configModelInput") return;
|
|
26844
|
+
const option = this.#selectedConfigProvider();
|
|
26845
|
+
if (!option) return;
|
|
26846
|
+
this.#state.inputMode = "configModel";
|
|
26847
|
+
this.#state.configModelCursor = 0;
|
|
26848
|
+
this.#state.settingsError = void 0;
|
|
26849
|
+
this.#state.activity = `choose default model for ${option.name}`;
|
|
26850
|
+
this.#notify();
|
|
26851
|
+
}
|
|
26852
|
+
moveConfigModelCursor(delta) {
|
|
26853
|
+
if (this.#state.inputMode !== "configModel") return;
|
|
26854
|
+
const profiles = this.#selectedConfigProvider()?.modelProfiles ?? [];
|
|
26855
|
+
if (profiles.length === 0) return;
|
|
26856
|
+
this.#state.configModelCursor = (this.#state.configModelCursor + delta + profiles.length) % profiles.length;
|
|
26857
|
+
this.#notify();
|
|
26858
|
+
}
|
|
26859
|
+
submitConfigDefaultModel() {
|
|
26860
|
+
if (this.#state.inputMode !== "configModel") return;
|
|
26861
|
+
const provider = this.#selectedConfigProvider();
|
|
26862
|
+
const profile = provider?.modelProfiles?.[this.#state.configModelCursor];
|
|
26863
|
+
if (!provider || !profile?.model) return;
|
|
26864
|
+
this.#submitConfigAction({
|
|
26865
|
+
type: "setDefaultModel",
|
|
26866
|
+
provider: provider.name,
|
|
26867
|
+
profileName: profile.name,
|
|
26868
|
+
name: profile.name,
|
|
26869
|
+
displayName: profile.displayName,
|
|
26870
|
+
model: profile.model
|
|
26871
|
+
});
|
|
26872
|
+
}
|
|
26873
|
+
openConfigModelInput() {
|
|
26874
|
+
if (this.#state.inputMode !== "configModel" && this.#state.inputMode !== "config") return;
|
|
26875
|
+
if (!this.#selectedConfigProvider()) return;
|
|
26876
|
+
this.#state.inputMode = "configModelInput";
|
|
26877
|
+
this.#state.configModelInput = "";
|
|
26878
|
+
this.#state.settingsError = void 0;
|
|
26879
|
+
this.#state.activity = "add a model profile";
|
|
26880
|
+
this.#notify();
|
|
26881
|
+
}
|
|
26882
|
+
appendConfigModelInput(input2) {
|
|
26883
|
+
if (this.#state.inputMode !== "configModelInput") return;
|
|
26884
|
+
this.#state.configModelInput += input2;
|
|
26885
|
+
this.#state.settingsError = void 0;
|
|
26886
|
+
this.#notify();
|
|
26887
|
+
}
|
|
26888
|
+
backspaceConfigModelInput() {
|
|
26889
|
+
if (this.#state.inputMode !== "configModelInput") return;
|
|
26890
|
+
this.#state.configModelInput = Array.from(this.#state.configModelInput).slice(0, -1).join("");
|
|
26891
|
+
this.#state.settingsError = void 0;
|
|
26892
|
+
this.#notify();
|
|
26893
|
+
}
|
|
26894
|
+
submitConfigModelInput() {
|
|
26895
|
+
if (this.#state.inputMode !== "configModelInput") return;
|
|
26896
|
+
const provider = this.#selectedConfigProvider();
|
|
26897
|
+
const model = this.#state.configModelInput.trim();
|
|
26898
|
+
if (!provider || !model) {
|
|
26899
|
+
this.#state.settingsError = "Model ID is required.";
|
|
26900
|
+
this.#notify();
|
|
26901
|
+
return;
|
|
26902
|
+
}
|
|
26903
|
+
this.#submitConfigAction({ type: "setDefaultModel", provider: provider.name, model, displayName: model });
|
|
26904
|
+
}
|
|
26905
|
+
openConfigAddProviderSelector() {
|
|
26906
|
+
if (this.#state.inputMode !== "config") return;
|
|
26907
|
+
this.#state.inputMode = "configAddProvider";
|
|
26908
|
+
this.#state.configAddProviderCursor = 0;
|
|
26909
|
+
this.#state.settingsError = void 0;
|
|
26910
|
+
this.#state.activity = "add provider preset";
|
|
26911
|
+
this.#notify();
|
|
26912
|
+
}
|
|
26913
|
+
moveConfigAddProviderCursor(delta) {
|
|
26914
|
+
if (this.#state.inputMode !== "configAddProvider") return;
|
|
26915
|
+
const count = this.#state.configProviderPresets.length;
|
|
26916
|
+
if (count === 0) return;
|
|
26917
|
+
this.#state.configAddProviderCursor = (this.#state.configAddProviderCursor + delta + count) % count;
|
|
26918
|
+
this.#notify();
|
|
26919
|
+
}
|
|
26920
|
+
submitConfigAddProvider() {
|
|
26921
|
+
if (this.#state.inputMode !== "configAddProvider") return;
|
|
26922
|
+
const preset = this.#state.configProviderPresets[this.#state.configAddProviderCursor];
|
|
26923
|
+
if (!preset) return;
|
|
26924
|
+
this.#submitConfigAction({ type: "addProvider", preset: preset.preset, name: preset.name, apiKeyEnv: preset.apiKeyEnv });
|
|
26925
|
+
}
|
|
26341
26926
|
openAgentSelector() {
|
|
26342
26927
|
if (this.#state.inputMode !== "prompt" || this.#state.promptInput.trim()) return;
|
|
26343
26928
|
if (this.#state.agentOptions.length === 0) {
|
|
@@ -26436,9 +27021,12 @@ var init_store = __esm({
|
|
|
26436
27021
|
this.#notify();
|
|
26437
27022
|
}
|
|
26438
27023
|
closeSettings() {
|
|
26439
|
-
if (
|
|
27024
|
+
if (!["provider", "model", "agent", "permissionPreset", "config", "configModel", "configModelInput", "configProviderInput", "configAddProvider"].includes(this.#state.inputMode)) return;
|
|
26440
27025
|
this.#state.inputMode = "prompt";
|
|
26441
27026
|
this.#state.modelInput = "";
|
|
27027
|
+
this.#state.configModelInput = "";
|
|
27028
|
+
this.#state.configProviderInput = "";
|
|
27029
|
+
this.#state.configProviderInputField = void 0;
|
|
26442
27030
|
this.#state.settingsError = void 0;
|
|
26443
27031
|
this.#state.activity = "waiting for first message";
|
|
26444
27032
|
this.#notify();
|
|
@@ -26567,9 +27155,12 @@ var init_store = __esm({
|
|
|
26567
27155
|
this.#state.streamingFinalized = false;
|
|
26568
27156
|
this.#activeClaudeCodePlan = this.#isClaudeCodePlanRun(event.provider) ? { text: "", providerName: event.provider, model: event.model, createdAt: event.ts } : void 0;
|
|
26569
27157
|
}
|
|
26570
|
-
if (event.type === "model.text.delta"
|
|
27158
|
+
if (event.type === "model.text.delta") {
|
|
27159
|
+
if (isNestedInvocationEvent(event)) return;
|
|
26571
27160
|
this.#state.streamingText += event.text;
|
|
26572
27161
|
this.#state.streamingFinalized = false;
|
|
27162
|
+
this.#notify({ deferMs: 50 });
|
|
27163
|
+
return;
|
|
26573
27164
|
}
|
|
26574
27165
|
if (event.type === "model.text" && !isNestedInvocationEvent(event)) {
|
|
26575
27166
|
this.#state.streamingText = event.text;
|
|
@@ -27283,14 +27874,28 @@ var init_store = __esm({
|
|
|
27283
27874
|
resolve(prompt);
|
|
27284
27875
|
this.#notify();
|
|
27285
27876
|
}
|
|
27877
|
+
#selectedConfigProvider() {
|
|
27878
|
+
return this.#state.providerOptions[this.#state.providerCursor];
|
|
27879
|
+
}
|
|
27880
|
+
#submitConfigAction(action) {
|
|
27881
|
+
this.#state.settingsError = void 0;
|
|
27882
|
+
this.#state.configModelInput = "";
|
|
27883
|
+
this.#state.configProviderInput = "";
|
|
27884
|
+
this.#state.configProviderInputField = void 0;
|
|
27885
|
+
this.#resolvePrompt(`${TUI_CONFIG_ACTION_PREFIX}${JSON.stringify(action)}`);
|
|
27886
|
+
}
|
|
27286
27887
|
#resolveSession(selection) {
|
|
27287
27888
|
const resolve = this.#sessionResolve;
|
|
27288
27889
|
if (!resolve) return;
|
|
27289
27890
|
this.#sessionResolve = void 0;
|
|
27891
|
+
this.#state.sessionCancelBehavior = "exit";
|
|
27290
27892
|
if (selection.kind === "exit") {
|
|
27291
27893
|
this.#state.inputMode = "done";
|
|
27292
27894
|
this.#state.done = true;
|
|
27293
27895
|
this.#state.activity = "exiting";
|
|
27896
|
+
} else if (selection.kind === "cancel") {
|
|
27897
|
+
this.#state.inputMode = "prompt";
|
|
27898
|
+
this.#state.activity = "waiting for next message";
|
|
27294
27899
|
} else {
|
|
27295
27900
|
this.#state.inputMode = "starting";
|
|
27296
27901
|
this.#state.activity = selection.kind === "new" ? "creating session" : "opening session";
|
|
@@ -27402,7 +28007,23 @@ var init_store = __esm({
|
|
|
27402
28007
|
}
|
|
27403
28008
|
};
|
|
27404
28009
|
}
|
|
27405
|
-
#notify() {
|
|
28010
|
+
#notify(options = {}) {
|
|
28011
|
+
const deferMs = options.deferMs ?? 0;
|
|
28012
|
+
if (deferMs > 0) {
|
|
28013
|
+
if (this.#notifyTimer) return;
|
|
28014
|
+
this.#notifyTimer = setTimeout(() => {
|
|
28015
|
+
this.#notifyTimer = void 0;
|
|
28016
|
+
this.#emitNotify();
|
|
28017
|
+
}, deferMs);
|
|
28018
|
+
return;
|
|
28019
|
+
}
|
|
28020
|
+
if (this.#notifyTimer) {
|
|
28021
|
+
clearTimeout(this.#notifyTimer);
|
|
28022
|
+
this.#notifyTimer = void 0;
|
|
28023
|
+
}
|
|
28024
|
+
this.#emitNotify();
|
|
28025
|
+
}
|
|
28026
|
+
#emitNotify() {
|
|
27406
28027
|
for (const listener of this.#listeners) listener();
|
|
27407
28028
|
}
|
|
27408
28029
|
};
|
|
@@ -27414,9 +28035,9 @@ var app_exports = {};
|
|
|
27414
28035
|
__export(app_exports, {
|
|
27415
28036
|
TuiApp: () => TuiApp,
|
|
27416
28037
|
commandPaletteMatches: () => commandPaletteMatches,
|
|
28038
|
+
initialSessionCommandLines: () => initialSessionCommandLines,
|
|
27417
28039
|
inputFrameRows: () => inputFrameRows,
|
|
27418
28040
|
isCtrlCKeypress: () => isCtrlCKeypress,
|
|
27419
|
-
isCtrlPKeypress: () => isCtrlPKeypress,
|
|
27420
28041
|
isPromptNewlineKeypress: () => isPromptNewlineKeypress,
|
|
27421
28042
|
sessionHeaderLine: () => sessionHeaderLine,
|
|
27422
28043
|
sessionOptionLine: () => sessionOptionLine,
|
|
@@ -27428,13 +28049,16 @@ __export(app_exports, {
|
|
|
27428
28049
|
});
|
|
27429
28050
|
import React, { useEffect, useState } from "react";
|
|
27430
28051
|
import { Box, Text, useApp, useInput } from "ink";
|
|
28052
|
+
function usePromptDraftSnapshot(promptDraft) {
|
|
28053
|
+
const [snapshot, setSnapshot] = useState(() => promptDraft.snapshot());
|
|
28054
|
+
useEffect(() => promptDraft.subscribe(() => setSnapshot(promptDraft.snapshot())), [promptDraft]);
|
|
28055
|
+
return snapshot;
|
|
28056
|
+
}
|
|
27431
28057
|
function TuiApp({ store, version = "dev" }) {
|
|
27432
28058
|
const [state, setState] = useState(() => store.snapshot());
|
|
27433
|
-
const [
|
|
28059
|
+
const [promptDraft] = useState(() => new PromptDraftController(store.snapshot().promptInput));
|
|
27434
28060
|
const [now2, setNow] = useState(() => Date.now());
|
|
27435
28061
|
const [shortcutMode, setShortcutMode] = useState(false);
|
|
27436
|
-
const [slashCommandCursor, setSlashCommandCursor] = useState(0);
|
|
27437
|
-
const [slashCommandClosedPrefix, setSlashCommandClosedPrefix] = useState();
|
|
27438
28062
|
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
|
|
27439
28063
|
const [commandPaletteQuery, setCommandPaletteQuery] = useState("");
|
|
27440
28064
|
const [commandPaletteCursor, setCommandPaletteCursor] = useState(0);
|
|
@@ -27443,47 +28067,43 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27443
28067
|
const update = () => {
|
|
27444
28068
|
const next = store.snapshot();
|
|
27445
28069
|
setState(next);
|
|
27446
|
-
|
|
28070
|
+
promptDraft.setValue(next.promptInput);
|
|
27447
28071
|
};
|
|
27448
28072
|
const unsubscribe = store.subscribe(update);
|
|
27449
28073
|
update();
|
|
27450
28074
|
return unsubscribe;
|
|
27451
|
-
}, [store]);
|
|
28075
|
+
}, [store, promptDraft]);
|
|
27452
28076
|
useEffect(() => enableEnhancedKeyboardInput(), []);
|
|
27453
28077
|
useEffect(() => {
|
|
27454
28078
|
if (!state.workPlan && state.inputMode !== "running" && state.inputMode !== "starting") return;
|
|
27455
|
-
const timer = setInterval(() => setNow(Date.now()),
|
|
28079
|
+
const timer = setInterval(() => setNow(Date.now()), 1e3);
|
|
27456
28080
|
return () => clearInterval(timer);
|
|
27457
28081
|
}, [state.workPlan?.planId, state.inputMode]);
|
|
27458
28082
|
useEffect(() => {
|
|
27459
28083
|
setShortcutMode(false);
|
|
27460
28084
|
}, [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
28085
|
useEffect(() => {
|
|
27466
28086
|
setCommandPaletteCursor(0);
|
|
27467
28087
|
}, [commandPaletteQuery, state.inputMode]);
|
|
27468
28088
|
useEffect(() => {
|
|
27469
28089
|
if (commandPaletteOpen && !canUseCommandPalette(state)) setCommandPaletteOpen(false);
|
|
27470
28090
|
}, [commandPaletteOpen, state.inputMode, state.permission]);
|
|
27471
|
-
const commandPaletteItems = commandPaletteOpen ? commandPaletteMatches(commandPaletteQuery, state.inputMode, commandPaletteAvailability(state,
|
|
28091
|
+
const commandPaletteItems = commandPaletteOpen ? commandPaletteMatches(commandPaletteQuery, state.inputMode, commandPaletteAvailability(state, promptDraft.value())) : [];
|
|
27472
28092
|
const commandPaletteItem = commandPaletteItems[Math.min(commandPaletteCursor, Math.max(0, commandPaletteItems.length - 1))];
|
|
28093
|
+
const openCommandPalette = () => {
|
|
28094
|
+
if (!canUseCommandPalette(state)) return;
|
|
28095
|
+
setCommandPaletteOpen((open4) => !open4);
|
|
28096
|
+
setCommandPaletteQuery("");
|
|
28097
|
+
setCommandPaletteCursor(0);
|
|
28098
|
+
setShortcutMode(false);
|
|
28099
|
+
promptDraft.closeSlashCommandsForCurrentPrompt();
|
|
28100
|
+
};
|
|
27473
28101
|
useInput((input2, key) => {
|
|
27474
28102
|
if (isCtrlCKeypress(input2, key)) {
|
|
27475
28103
|
store.requestExit();
|
|
27476
28104
|
app.exit();
|
|
27477
28105
|
return;
|
|
27478
28106
|
}
|
|
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
28107
|
const textInput = textInputForKeypress(input2, key);
|
|
27488
28108
|
if (commandPaletteOpen) {
|
|
27489
28109
|
if (key.escape) {
|
|
@@ -27499,7 +28119,7 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27499
28119
|
return;
|
|
27500
28120
|
}
|
|
27501
28121
|
if (key.return) {
|
|
27502
|
-
if (commandPaletteItem) executeCommandPaletteItem({ item: commandPaletteItem, store, app,
|
|
28122
|
+
if (commandPaletteItem) executeCommandPaletteItem({ item: commandPaletteItem, store, app, promptDraft, setState, close: () => closeCommandPalette(setCommandPaletteOpen, setCommandPaletteQuery, setCommandPaletteCursor) });
|
|
27503
28123
|
return;
|
|
27504
28124
|
}
|
|
27505
28125
|
if (key.backspace || key.delete) {
|
|
@@ -27525,6 +28145,14 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27525
28145
|
store.stopActiveTask();
|
|
27526
28146
|
return;
|
|
27527
28147
|
}
|
|
28148
|
+
if (shortcut === "k" && (state.inputMode === "prompt" || state.inputMode === "running")) {
|
|
28149
|
+
openCommandPalette();
|
|
28150
|
+
return;
|
|
28151
|
+
}
|
|
28152
|
+
if (shortcut === "s" && state.inputMode === "prompt") {
|
|
28153
|
+
store.submitSessionSelectionShortcut();
|
|
28154
|
+
return;
|
|
28155
|
+
}
|
|
27528
28156
|
if (shortcut === "p" && state.inputMode === "prompt") {
|
|
27529
28157
|
store.openProviderSelector();
|
|
27530
28158
|
return;
|
|
@@ -27541,9 +28169,13 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27541
28169
|
store.openPermissionPresetSelector();
|
|
27542
28170
|
return;
|
|
27543
28171
|
}
|
|
28172
|
+
if (shortcut === "c" && state.inputMode === "prompt") {
|
|
28173
|
+
store.openConfigManager();
|
|
28174
|
+
return;
|
|
28175
|
+
}
|
|
27544
28176
|
if (shortcut === "u" && state.inputMode === "prompt") {
|
|
27545
28177
|
store.clearPromptInput({ notify: false });
|
|
27546
|
-
|
|
28178
|
+
promptDraft.setValue("");
|
|
27547
28179
|
clearPromptErrorPreview(setState);
|
|
27548
28180
|
return;
|
|
27549
28181
|
}
|
|
@@ -27578,7 +28210,14 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27578
28210
|
}
|
|
27579
28211
|
if (state.inputMode === "session") {
|
|
27580
28212
|
const normalized = input2.toLowerCase();
|
|
27581
|
-
if (key.escape
|
|
28213
|
+
if (key.escape) {
|
|
28214
|
+
if (store.cancelSessionSelection()) {
|
|
28215
|
+
store.requestExit();
|
|
28216
|
+
app.exit();
|
|
28217
|
+
}
|
|
28218
|
+
return;
|
|
28219
|
+
}
|
|
28220
|
+
if (normalized === "q") {
|
|
27582
28221
|
store.requestExit();
|
|
27583
28222
|
app.exit();
|
|
27584
28223
|
return;
|
|
@@ -27587,6 +28226,10 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27587
28226
|
store.submitNewSessionSelection();
|
|
27588
28227
|
return;
|
|
27589
28228
|
}
|
|
28229
|
+
if (normalized === "d") {
|
|
28230
|
+
store.submitDeleteSessionSelection();
|
|
28231
|
+
return;
|
|
28232
|
+
}
|
|
27590
28233
|
if (key.upArrow) {
|
|
27591
28234
|
store.moveSessionCursor(-1);
|
|
27592
28235
|
return;
|
|
@@ -27658,6 +28301,126 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27658
28301
|
}
|
|
27659
28302
|
return;
|
|
27660
28303
|
}
|
|
28304
|
+
if (state.inputMode === "config") {
|
|
28305
|
+
if (key.escape) {
|
|
28306
|
+
store.closeSettings();
|
|
28307
|
+
return;
|
|
28308
|
+
}
|
|
28309
|
+
if (key.upArrow) {
|
|
28310
|
+
store.moveConfigProviderCursor(-1);
|
|
28311
|
+
return;
|
|
28312
|
+
}
|
|
28313
|
+
if (key.downArrow) {
|
|
28314
|
+
store.moveConfigProviderCursor(1);
|
|
28315
|
+
return;
|
|
28316
|
+
}
|
|
28317
|
+
const normalized = input2.toLowerCase();
|
|
28318
|
+
if (key.return || normalized === "d") {
|
|
28319
|
+
store.submitConfigDefaultProvider();
|
|
28320
|
+
return;
|
|
28321
|
+
}
|
|
28322
|
+
if (normalized === "m") {
|
|
28323
|
+
store.openConfigModelSelector();
|
|
28324
|
+
return;
|
|
28325
|
+
}
|
|
28326
|
+
if (normalized === "n") {
|
|
28327
|
+
store.openConfigModelInput();
|
|
28328
|
+
return;
|
|
28329
|
+
}
|
|
28330
|
+
if (normalized === "a") {
|
|
28331
|
+
store.openConfigAddProviderSelector();
|
|
28332
|
+
return;
|
|
28333
|
+
}
|
|
28334
|
+
if (normalized === "b") {
|
|
28335
|
+
store.openConfigProviderInput("baseURL");
|
|
28336
|
+
return;
|
|
28337
|
+
}
|
|
28338
|
+
if (normalized === "e") {
|
|
28339
|
+
store.openConfigProviderInput("apiKeyEnv");
|
|
28340
|
+
return;
|
|
28341
|
+
}
|
|
28342
|
+
if (normalized === "h") {
|
|
28343
|
+
store.openConfigProviderInput("authHeader");
|
|
28344
|
+
return;
|
|
28345
|
+
}
|
|
28346
|
+
return;
|
|
28347
|
+
}
|
|
28348
|
+
if (state.inputMode === "configModel") {
|
|
28349
|
+
if (key.escape) {
|
|
28350
|
+
store.openConfigManager();
|
|
28351
|
+
return;
|
|
28352
|
+
}
|
|
28353
|
+
if (key.upArrow) {
|
|
28354
|
+
store.moveConfigModelCursor(-1);
|
|
28355
|
+
return;
|
|
28356
|
+
}
|
|
28357
|
+
if (key.downArrow) {
|
|
28358
|
+
store.moveConfigModelCursor(1);
|
|
28359
|
+
return;
|
|
28360
|
+
}
|
|
28361
|
+
if (input2.toLowerCase() === "n") {
|
|
28362
|
+
store.openConfigModelInput();
|
|
28363
|
+
return;
|
|
28364
|
+
}
|
|
28365
|
+
if (key.return) {
|
|
28366
|
+
store.submitConfigDefaultModel();
|
|
28367
|
+
return;
|
|
28368
|
+
}
|
|
28369
|
+
return;
|
|
28370
|
+
}
|
|
28371
|
+
if (state.inputMode === "configModelInput") {
|
|
28372
|
+
if (key.escape) {
|
|
28373
|
+
store.openConfigModelSelector();
|
|
28374
|
+
return;
|
|
28375
|
+
}
|
|
28376
|
+
if (key.return) {
|
|
28377
|
+
if (textInput) store.appendConfigModelInput(textInput);
|
|
28378
|
+
store.submitConfigModelInput();
|
|
28379
|
+
return;
|
|
28380
|
+
}
|
|
28381
|
+
if (key.backspace || key.delete) {
|
|
28382
|
+
store.backspaceConfigModelInput();
|
|
28383
|
+
return;
|
|
28384
|
+
}
|
|
28385
|
+
if (textInput) store.appendConfigModelInput(textInput);
|
|
28386
|
+
return;
|
|
28387
|
+
}
|
|
28388
|
+
if (state.inputMode === "configProviderInput") {
|
|
28389
|
+
if (key.escape) {
|
|
28390
|
+
store.openConfigManager();
|
|
28391
|
+
return;
|
|
28392
|
+
}
|
|
28393
|
+
if (key.return) {
|
|
28394
|
+
if (textInput) store.appendConfigProviderInput(textInput);
|
|
28395
|
+
store.submitConfigProviderInput();
|
|
28396
|
+
return;
|
|
28397
|
+
}
|
|
28398
|
+
if (key.backspace || key.delete) {
|
|
28399
|
+
store.backspaceConfigProviderInput();
|
|
28400
|
+
return;
|
|
28401
|
+
}
|
|
28402
|
+
if (textInput) store.appendConfigProviderInput(textInput);
|
|
28403
|
+
return;
|
|
28404
|
+
}
|
|
28405
|
+
if (state.inputMode === "configAddProvider") {
|
|
28406
|
+
if (key.escape) {
|
|
28407
|
+
store.openConfigManager();
|
|
28408
|
+
return;
|
|
28409
|
+
}
|
|
28410
|
+
if (key.upArrow) {
|
|
28411
|
+
store.moveConfigAddProviderCursor(-1);
|
|
28412
|
+
return;
|
|
28413
|
+
}
|
|
28414
|
+
if (key.downArrow) {
|
|
28415
|
+
store.moveConfigAddProviderCursor(1);
|
|
28416
|
+
return;
|
|
28417
|
+
}
|
|
28418
|
+
if (key.return) {
|
|
28419
|
+
store.submitConfigAddProvider();
|
|
28420
|
+
return;
|
|
28421
|
+
}
|
|
28422
|
+
return;
|
|
28423
|
+
}
|
|
27661
28424
|
if (state.inputMode === "model") {
|
|
27662
28425
|
if (key.escape) {
|
|
27663
28426
|
store.closeSettings();
|
|
@@ -27676,22 +28439,24 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27676
28439
|
return;
|
|
27677
28440
|
}
|
|
27678
28441
|
if (state.inputMode === "prompt" || state.inputMode === "running") {
|
|
27679
|
-
const
|
|
27680
|
-
const
|
|
27681
|
-
|
|
27682
|
-
|
|
28442
|
+
const promptSnapshot = promptDraft.snapshot();
|
|
28443
|
+
const promptInput = promptSnapshot.value;
|
|
28444
|
+
const slashCommands = visibleSlashCommandMatches(promptInput, state.inputMode, promptSnapshot.slashCommandClosedPrefix);
|
|
28445
|
+
const slashCommand = slashCommands[promptSnapshot.slashCommandCursor] ?? slashCommands[0];
|
|
28446
|
+
if (slashCommands.length > 0 && key.escape) {
|
|
28447
|
+
promptDraft.closeSlashCommandsForCurrentPrompt();
|
|
27683
28448
|
return;
|
|
27684
28449
|
}
|
|
27685
|
-
if (
|
|
27686
|
-
|
|
28450
|
+
if (slashCommands.length > 0 && key.upArrow) {
|
|
28451
|
+
promptDraft.moveSlashCommandCursor(-1, slashCommands.length);
|
|
27687
28452
|
return;
|
|
27688
28453
|
}
|
|
27689
|
-
if (
|
|
27690
|
-
|
|
28454
|
+
if (slashCommands.length > 0 && key.downArrow) {
|
|
28455
|
+
promptDraft.moveSlashCommandCursor(1, slashCommands.length);
|
|
27691
28456
|
return;
|
|
27692
28457
|
}
|
|
27693
|
-
if (
|
|
27694
|
-
applySlashCommandDraft(store,
|
|
28458
|
+
if (slashCommands.length > 0 && key.tab && slashCommand) {
|
|
28459
|
+
applySlashCommandDraft(store, promptDraft, slashCommand);
|
|
27695
28460
|
clearPromptErrorPreview(setState);
|
|
27696
28461
|
return;
|
|
27697
28462
|
}
|
|
@@ -27708,37 +28473,35 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27708
28473
|
return;
|
|
27709
28474
|
}
|
|
27710
28475
|
if (isPromptNewlineKeypress(input2, key)) {
|
|
27711
|
-
appendPromptDraft(store,
|
|
28476
|
+
appendPromptDraft(store, promptDraft, `${textInput}
|
|
27712
28477
|
`);
|
|
27713
28478
|
clearPromptErrorPreview(setState);
|
|
27714
28479
|
return;
|
|
27715
28480
|
}
|
|
27716
28481
|
if (isPromptSubmitKeypress(input2, key)) {
|
|
27717
28482
|
if (!textInput && slashCommand && shouldSelectSlashCommand(promptInput, slashCommand)) {
|
|
27718
|
-
applySlashCommandDraft(store,
|
|
28483
|
+
applySlashCommandDraft(store, promptDraft, slashCommand);
|
|
27719
28484
|
clearPromptErrorPreview(setState);
|
|
27720
28485
|
return;
|
|
27721
28486
|
}
|
|
27722
|
-
if (textInput) appendPromptDraft(store,
|
|
28487
|
+
if (textInput) appendPromptDraft(store, promptDraft, textInput);
|
|
27723
28488
|
store.submitPromptInput();
|
|
27724
28489
|
return;
|
|
27725
28490
|
}
|
|
27726
28491
|
if (key.backspace || key.delete) {
|
|
27727
28492
|
store.backspacePromptInput({ notify: false });
|
|
27728
|
-
|
|
28493
|
+
promptDraft.backspace();
|
|
27729
28494
|
clearPromptErrorPreview(setState);
|
|
27730
28495
|
return;
|
|
27731
28496
|
}
|
|
27732
28497
|
if (textInput) {
|
|
27733
|
-
appendPromptDraft(store,
|
|
28498
|
+
appendPromptDraft(store, promptDraft, textInput);
|
|
27734
28499
|
clearPromptErrorPreview(setState);
|
|
27735
28500
|
}
|
|
27736
28501
|
return;
|
|
27737
28502
|
}
|
|
27738
28503
|
});
|
|
27739
28504
|
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
28505
|
const commandPalette = commandPaletteOpen ? { items: commandPaletteItems, query: commandPaletteQuery, cursor: Math.min(commandPaletteCursor, Math.max(0, commandPaletteItems.length - 1)) } : void 0;
|
|
27743
28506
|
if (shouldShowSessionStart(state)) {
|
|
27744
28507
|
return React.createElement(
|
|
@@ -27751,7 +28514,7 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27751
28514
|
return React.createElement(
|
|
27752
28515
|
Box,
|
|
27753
28516
|
shellProps,
|
|
27754
|
-
React.createElement(EmptyState, { state, version, shortcutMode,
|
|
28517
|
+
React.createElement(EmptyState, { state, version, shortcutMode, promptDraft, commandPalette })
|
|
27755
28518
|
);
|
|
27756
28519
|
}
|
|
27757
28520
|
return React.createElement(
|
|
@@ -27764,24 +28527,24 @@ function TuiApp({ store, version = "dev" }) {
|
|
|
27764
28527
|
React.createElement(shouldShowLoading(state) ? LoadingView : TranscriptView, { state }),
|
|
27765
28528
|
React.createElement(Box, { flexGrow: 1 }),
|
|
27766
28529
|
React.createElement(BottomStatusStack, { state, now: now2 }),
|
|
27767
|
-
React.createElement(InteractionPanel, { state, shortcutMode,
|
|
28530
|
+
React.createElement(InteractionPanel, { state, shortcutMode, promptDraft, commandPalette })
|
|
27768
28531
|
)
|
|
27769
28532
|
);
|
|
27770
28533
|
}
|
|
27771
|
-
function appendPromptDraft(store,
|
|
28534
|
+
function appendPromptDraft(store, promptDraft, value) {
|
|
27772
28535
|
if (!value) return;
|
|
27773
28536
|
store.appendPromptInput(value, { notify: false });
|
|
27774
|
-
|
|
28537
|
+
promptDraft.append(value);
|
|
27775
28538
|
}
|
|
27776
28539
|
function clearPromptErrorPreview(setState) {
|
|
27777
28540
|
setState((current) => current.promptError ? { ...current, promptError: void 0 } : current);
|
|
27778
28541
|
}
|
|
27779
|
-
function applySlashCommandDraft(store,
|
|
28542
|
+
function applySlashCommandDraft(store, promptDraft, item) {
|
|
27780
28543
|
const next = item.argument ? `${item.insert} ` : item.insert;
|
|
27781
28544
|
store.clearPromptInput({ notify: false });
|
|
27782
28545
|
store.appendPromptInput(next, { notify: false });
|
|
27783
|
-
|
|
27784
|
-
|
|
28546
|
+
promptDraft.setValue(next);
|
|
28547
|
+
promptDraft.closeSlashCommandsForCurrentPrompt();
|
|
27785
28548
|
}
|
|
27786
28549
|
function shouldSelectSlashCommand(promptInput, item) {
|
|
27787
28550
|
const value = promptInput.trim();
|
|
@@ -27793,6 +28556,14 @@ function visibleSlashCommandMatches(promptInput, mode, closedPrefix) {
|
|
|
27793
28556
|
if (closedPrefix && isSlashCommandClosedForPrompt(promptInput, closedPrefix)) return [];
|
|
27794
28557
|
return slashCommandMatches(promptInput, mode).slice(0, 7);
|
|
27795
28558
|
}
|
|
28559
|
+
function slashCommandPaletteForDraft(snapshot, mode) {
|
|
28560
|
+
const slashCommands = visibleSlashCommandMatches(snapshot.value, mode, snapshot.slashCommandClosedPrefix);
|
|
28561
|
+
if (slashCommands.length === 0) return void 0;
|
|
28562
|
+
return {
|
|
28563
|
+
items: slashCommands,
|
|
28564
|
+
cursor: Math.min(snapshot.slashCommandCursor, slashCommands.length - 1)
|
|
28565
|
+
};
|
|
28566
|
+
}
|
|
27796
28567
|
function isSlashCommandClosedForPrompt(promptInput, closedPrefix) {
|
|
27797
28568
|
return promptInput === closedPrefix || closedPrefix.endsWith(" ") && promptInput.startsWith(closedPrefix);
|
|
27798
28569
|
}
|
|
@@ -27861,6 +28632,9 @@ function buildCommandPaletteItems(mode, availability) {
|
|
|
27861
28632
|
}
|
|
27862
28633
|
if (promptReadyForSettings) {
|
|
27863
28634
|
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"] });
|
|
28635
|
+
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"] });
|
|
28636
|
+
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"] });
|
|
28637
|
+
items.push({ id: "delete-current-session", title: "Delete current session", description: "Remove the current saved conversation", group: "Session", shortcut: "/session delete", action: "delete-session", keywords: ["delete", "remove", "session", "conversation"] });
|
|
27864
28638
|
}
|
|
27865
28639
|
for (const command of SLASH_COMMANDS) {
|
|
27866
28640
|
if (!slashCommandActiveInMode(command, mode)) continue;
|
|
@@ -27954,13 +28728,13 @@ function executeCommandPaletteItem({
|
|
|
27954
28728
|
item,
|
|
27955
28729
|
store,
|
|
27956
28730
|
app,
|
|
27957
|
-
|
|
28731
|
+
promptDraft,
|
|
27958
28732
|
setState,
|
|
27959
28733
|
close
|
|
27960
28734
|
}) {
|
|
27961
28735
|
close();
|
|
27962
28736
|
if (item.slashCommand) {
|
|
27963
|
-
applyCommandPaletteSlashCommand(store,
|
|
28737
|
+
applyCommandPaletteSlashCommand(store, promptDraft, item.slashCommand);
|
|
27964
28738
|
clearPromptErrorPreview(setState);
|
|
27965
28739
|
return;
|
|
27966
28740
|
}
|
|
@@ -27977,9 +28751,21 @@ function executeCommandPaletteItem({
|
|
|
27977
28751
|
case "open-permissions":
|
|
27978
28752
|
store.openPermissionPresetSelector();
|
|
27979
28753
|
return;
|
|
28754
|
+
case "open-config":
|
|
28755
|
+
store.openConfigManager();
|
|
28756
|
+
return;
|
|
28757
|
+
case "open-session":
|
|
28758
|
+
store.submitSessionSelectionShortcut();
|
|
28759
|
+
return;
|
|
28760
|
+
case "delete-session":
|
|
28761
|
+
store.clearPromptInput({ notify: false });
|
|
28762
|
+
store.appendPromptInput("/session delete", { notify: false });
|
|
28763
|
+
promptDraft.setValue("/session delete");
|
|
28764
|
+
store.submitPromptInput();
|
|
28765
|
+
return;
|
|
27980
28766
|
case "clear-prompt":
|
|
27981
28767
|
store.clearPromptInput({ notify: false });
|
|
27982
|
-
|
|
28768
|
+
promptDraft.setValue("");
|
|
27983
28769
|
clearPromptErrorPreview(setState);
|
|
27984
28770
|
return;
|
|
27985
28771
|
case "retry":
|
|
@@ -28006,12 +28792,13 @@ function executeCommandPaletteItem({
|
|
|
28006
28792
|
return;
|
|
28007
28793
|
}
|
|
28008
28794
|
}
|
|
28009
|
-
function applyCommandPaletteSlashCommand(store,
|
|
28795
|
+
function applyCommandPaletteSlashCommand(store, promptDraft, item) {
|
|
28010
28796
|
const shouldInsert = item.argument && item.command !== "/session new";
|
|
28011
28797
|
const next = shouldInsert ? `${item.insert} ` : item.insert;
|
|
28012
28798
|
store.clearPromptInput({ notify: false });
|
|
28013
28799
|
store.appendPromptInput(next, { notify: false });
|
|
28014
|
-
|
|
28800
|
+
promptDraft.setValue(next);
|
|
28801
|
+
promptDraft.closeSlashCommandsForCurrentPrompt();
|
|
28015
28802
|
if (!shouldInsert) store.submitPromptInput();
|
|
28016
28803
|
}
|
|
28017
28804
|
function textInputForKeypress(input2, key) {
|
|
@@ -28038,15 +28825,6 @@ function isCtrlCKeypress(input2, key) {
|
|
|
28038
28825
|
const modifier = Number(csi[2] ?? 1);
|
|
28039
28826
|
return (codePoint === 3 || codePoint === 99) && hasControlModifier(modifier);
|
|
28040
28827
|
}
|
|
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
28828
|
function isPromptSubmitKeypress(input2, key) {
|
|
28051
28829
|
return Boolean(key.return) || input2 === "\n";
|
|
28052
28830
|
}
|
|
@@ -28119,7 +28897,7 @@ function SessionStart({ state, version }) {
|
|
|
28119
28897
|
React.createElement(
|
|
28120
28898
|
Box,
|
|
28121
28899
|
{ alignSelf: "center", width, justifyContent: "space-between", marginTop: 1 },
|
|
28122
|
-
React.createElement(Text, { color: "white" }, "enter open \xB7 n new \xB7 q quit"),
|
|
28900
|
+
React.createElement(Text, { color: "white" }, "enter open \xB7 n new \xB7 d delete \xB7 esc cancel \xB7 q quit"),
|
|
28123
28901
|
React.createElement(Text, { color: "white" }, `v${version}`)
|
|
28124
28902
|
),
|
|
28125
28903
|
React.createElement(Box, { flexGrow: 1 })
|
|
@@ -28129,12 +28907,13 @@ function EmptyState({
|
|
|
28129
28907
|
state,
|
|
28130
28908
|
version,
|
|
28131
28909
|
shortcutMode,
|
|
28132
|
-
|
|
28133
|
-
slashCommandPalette,
|
|
28910
|
+
promptDraft,
|
|
28134
28911
|
commandPalette
|
|
28135
28912
|
}) {
|
|
28913
|
+
const promptSnapshot = usePromptDraftSnapshot(promptDraft);
|
|
28914
|
+
const slashCommandPalette = commandPalette ? void 0 : slashCommandPaletteForDraft(promptSnapshot, state.inputMode);
|
|
28136
28915
|
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
|
|
28916
|
+
const hint = shortcutMode ? shortcutHelp(state) : "enter send \xB7 shift/option+enter newline \xB7 esc k commands \xB7 esc s sessions";
|
|
28138
28917
|
const hasOverlay = Boolean(slashCommandPalette || commandPalette);
|
|
28139
28918
|
const topGap = hasOverlay ? 0 : Math.max(1, Math.min(6, Math.floor(terminalHeight() * 0.12)));
|
|
28140
28919
|
return React.createElement(
|
|
@@ -28148,50 +28927,46 @@ function EmptyState({
|
|
|
28148
28927
|
React.createElement(InputFrame, {
|
|
28149
28928
|
borderColor: shortcutMode ? "cyan" : "blue",
|
|
28150
28929
|
cursorColor: "cyan",
|
|
28151
|
-
value:
|
|
28930
|
+
value: promptSnapshot.value,
|
|
28152
28931
|
placeholder,
|
|
28153
28932
|
marginTop: 0
|
|
28154
28933
|
}),
|
|
28155
28934
|
React.createElement(CommandGuide, { hint, color: shortcutMode ? "cyan" : "gray", error: state.promptError }),
|
|
28156
28935
|
hasOverlay ? null : React.createElement(SessionMetaBox, { state, version, marginTop: 2 }),
|
|
28936
|
+
hasOverlay ? null : React.createElement(SessionCommandsBox, { marginTop: 1 }),
|
|
28157
28937
|
React.createElement(
|
|
28158
28938
|
Box,
|
|
28159
28939
|
{ flexGrow: 1 }
|
|
28160
28940
|
)
|
|
28161
28941
|
);
|
|
28162
28942
|
}
|
|
28163
|
-
function
|
|
28164
|
-
const
|
|
28165
|
-
|
|
28166
|
-
|
|
28167
|
-
|
|
28168
|
-
|
|
28169
|
-
|
|
28170
|
-
|
|
28171
|
-
|
|
28172
|
-
|
|
28173
|
-
|
|
28174
|
-
|
|
28175
|
-
|
|
28176
|
-
|
|
28943
|
+
function initialSessionCommandLines(width) {
|
|
28944
|
+
const target = Math.max(24, width);
|
|
28945
|
+
if (target < 48) {
|
|
28946
|
+
return [
|
|
28947
|
+
"Session commands",
|
|
28948
|
+
"esc s sessions",
|
|
28949
|
+
"/session list",
|
|
28950
|
+
"/session new [title]",
|
|
28951
|
+
"/session switch <target>",
|
|
28952
|
+
"/session rename <title>",
|
|
28953
|
+
"/session delete [target]"
|
|
28954
|
+
].map((line) => fitToWidth(line, target));
|
|
28955
|
+
}
|
|
28956
|
+
const commandWidth = Math.min(28, Math.max(24, Math.floor(target * 0.42)));
|
|
28957
|
+
const descriptionWidth = Math.max(0, target - commandWidth - 1);
|
|
28958
|
+
const rows = [
|
|
28959
|
+
["esc s", "open saved sessions"],
|
|
28960
|
+
["/session list", "list saved sessions"],
|
|
28961
|
+
["/session new [title]", "create a session"],
|
|
28962
|
+
["/session switch <target>", "switch by number, id, or title"],
|
|
28963
|
+
["/session rename <title>", "rename current session"],
|
|
28964
|
+
["/session delete [target]", "delete current or saved session"]
|
|
28965
|
+
];
|
|
28966
|
+
return [
|
|
28967
|
+
fitToWidth("Session commands", target),
|
|
28968
|
+
...rows.map(([command, description]) => `${padCell(command, commandWidth)} ${truncateEndToWidth(description, descriptionWidth)}`)
|
|
28177
28969
|
];
|
|
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
28970
|
}
|
|
28196
28971
|
function LoadingView({ state }) {
|
|
28197
28972
|
const title = state.inputMode === "starting" ? "Demian runtime" : "Preparing session";
|
|
@@ -28262,34 +29037,26 @@ function tuiMode(state) {
|
|
|
28262
29037
|
if (state.inputMode === "session") return { label: "sessions", color: "magenta" };
|
|
28263
29038
|
if (state.inputMode === "running") return { label: "running", color: "cyan" };
|
|
28264
29039
|
if (state.inputMode === "provider" || state.inputMode === "agent" || state.inputMode === "model" || state.inputMode === "permissionPreset") return { label: "settings", color: "magenta" };
|
|
29040
|
+
if (state.inputMode === "config" || state.inputMode === "configModel" || state.inputMode === "configModelInput" || state.inputMode === "configProviderInput" || state.inputMode === "configAddProvider") return { label: "config", color: "magenta" };
|
|
28265
29041
|
if (state.inputMode === "prompt") return { label: "ready", color: "green" };
|
|
28266
29042
|
return { label: state.inputMode, color: "gray" };
|
|
28267
29043
|
}
|
|
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
|
-
);
|
|
29044
|
+
function sameBlockViewProps(previous, next) {
|
|
29045
|
+
return previous.width === next.width && blockRenderSignature(previous.block) === blockRenderSignature(next.block);
|
|
29046
|
+
}
|
|
29047
|
+
function blockRenderSignature(block) {
|
|
29048
|
+
return JSON.stringify({
|
|
29049
|
+
id: "id" in block ? block.id : void 0,
|
|
29050
|
+
kind: block.kind,
|
|
29051
|
+
title: block.title,
|
|
29052
|
+
lines: block.lines,
|
|
29053
|
+
meta: block.meta,
|
|
29054
|
+
toolStatus: block.toolStatus,
|
|
29055
|
+
expanded: block.expanded,
|
|
29056
|
+
toolDetails: block.expanded ? block.toolDetails : void 0,
|
|
29057
|
+
goalWork: block.kind === "goal-work" ? block.goalWork : void 0,
|
|
29058
|
+
cowork: block.kind === "cowork" ? block.cowork : void 0
|
|
29059
|
+
});
|
|
28293
29060
|
}
|
|
28294
29061
|
function UserBlockView({ block, width }) {
|
|
28295
29062
|
const lines = normalizedBlockLines(block.lines).slice(0, 80);
|
|
@@ -28753,16 +29520,21 @@ function formatSessionAge(updatedAt) {
|
|
|
28753
29520
|
if (hours < 48) return `${hours}h ago`;
|
|
28754
29521
|
return `${Math.floor(hours / 24)}d ago`;
|
|
28755
29522
|
}
|
|
28756
|
-
function InteractionPanel({ state, shortcutMode,
|
|
29523
|
+
function InteractionPanel({ state, shortcutMode, promptDraft, commandPalette }) {
|
|
28757
29524
|
if (state.inputMode === "starting") return React.createElement(LoadingBar);
|
|
28758
29525
|
if (state.inputMode === "session") return React.createElement(SessionSelector, { state });
|
|
28759
29526
|
if (state.permission) return React.createElement(PermissionBar, { state });
|
|
28760
29527
|
if (state.inputMode === "provider") return React.createElement(ProviderSelector, { state });
|
|
28761
29528
|
if (state.inputMode === "agent") return React.createElement(AgentSelector, { state });
|
|
28762
29529
|
if (state.inputMode === "permissionPreset") return React.createElement(PermissionPresetSelector, { state });
|
|
29530
|
+
if (state.inputMode === "config") return React.createElement(ConfigManager, { state });
|
|
29531
|
+
if (state.inputMode === "configModel") return React.createElement(ConfigModelSelector, { state });
|
|
29532
|
+
if (state.inputMode === "configModelInput") return React.createElement(ConfigModelInput, { state });
|
|
29533
|
+
if (state.inputMode === "configProviderInput") return React.createElement(ConfigProviderInput, { state });
|
|
29534
|
+
if (state.inputMode === "configAddProvider") return React.createElement(ConfigAddProviderSelector, { state });
|
|
28763
29535
|
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,
|
|
29536
|
+
if (state.inputMode === "running") return React.createElement(CommandBar, { state, shortcutMode, promptDraft, commandPalette });
|
|
29537
|
+
if (state.inputMode === "prompt") return React.createElement(PromptBar, { state, shortcutMode, promptDraft, commandPalette });
|
|
28766
29538
|
return React.createElement(PermissionBar, { state });
|
|
28767
29539
|
}
|
|
28768
29540
|
function LoadingBar() {
|
|
@@ -28775,8 +29547,8 @@ function LoadingBar() {
|
|
|
28775
29547
|
}
|
|
28776
29548
|
function shortcutHelp(state) {
|
|
28777
29549
|
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");
|
|
29550
|
+
if (state.inputMode === "prompt") parts.push("k commands", "s sessions", "p provider", "a agent", "m model", "o permissions", "c config", "u clear");
|
|
29551
|
+
if (state.inputMode === "running") parts.push("k commands", "s stop");
|
|
28780
29552
|
if (state.turnDiff) parts.push("d diff");
|
|
28781
29553
|
if (state.blocks.some((block) => block.kind === "tool")) parts.push("t tools");
|
|
28782
29554
|
if (state.workPlan || state.inputMode === "running") parts.push("w plan");
|
|
@@ -28785,8 +29557,10 @@ function shortcutHelp(state) {
|
|
|
28785
29557
|
parts.push("q quit");
|
|
28786
29558
|
return `shortcut: ${parts.join(" | ")}`;
|
|
28787
29559
|
}
|
|
28788
|
-
function PromptBar({ state, shortcutMode,
|
|
28789
|
-
const
|
|
29560
|
+
function PromptBar({ state, shortcutMode, promptDraft, commandPalette }) {
|
|
29561
|
+
const promptSnapshot = usePromptDraftSnapshot(promptDraft);
|
|
29562
|
+
const slashCommandPalette = commandPalette ? void 0 : slashCommandPaletteForDraft(promptSnapshot, state.inputMode);
|
|
29563
|
+
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
29564
|
return React.createElement(
|
|
28791
29565
|
Box,
|
|
28792
29566
|
{ alignSelf: "center", width: promptPanelWidth(), flexDirection: "column", marginTop: 1 },
|
|
@@ -28796,15 +29570,17 @@ function PromptBar({ state, shortcutMode, promptInput, slashCommandPalette, comm
|
|
|
28796
29570
|
React.createElement(InputFrame, {
|
|
28797
29571
|
borderColor: shortcutMode ? "cyan" : "blue",
|
|
28798
29572
|
cursorColor: "cyan",
|
|
28799
|
-
value:
|
|
29573
|
+
value: promptSnapshot.value,
|
|
28800
29574
|
placeholder: "Type first message and press Enter",
|
|
28801
29575
|
marginTop: 1
|
|
28802
29576
|
}),
|
|
28803
29577
|
React.createElement(CommandGuide, { hint, color: shortcutMode ? "cyan" : "gray", error: state.promptError })
|
|
28804
29578
|
);
|
|
28805
29579
|
}
|
|
28806
|
-
function CommandBar({ state, shortcutMode,
|
|
28807
|
-
const
|
|
29580
|
+
function CommandBar({ state, shortcutMode, promptDraft, commandPalette }) {
|
|
29581
|
+
const promptSnapshot = usePromptDraftSnapshot(promptDraft);
|
|
29582
|
+
const slashCommandPalette = commandPalette ? void 0 : slashCommandPaletteForDraft(promptSnapshot, state.inputMode);
|
|
29583
|
+
const hint = shortcutMode ? shortcutHelp(state) : "running | shift/option+enter newline | esc k commands | /stop stop | /exit quit | esc shortcuts";
|
|
28808
29584
|
return React.createElement(
|
|
28809
29585
|
Box,
|
|
28810
29586
|
{ alignSelf: "center", width: promptPanelWidth(), flexDirection: "column", marginTop: 1 },
|
|
@@ -28814,7 +29590,7 @@ function CommandBar({ state, shortcutMode, promptInput, slashCommandPalette, com
|
|
|
28814
29590
|
React.createElement(InputFrame, {
|
|
28815
29591
|
borderColor: shortcutMode ? "cyan" : "yellow",
|
|
28816
29592
|
cursorColor: "yellow",
|
|
28817
|
-
value:
|
|
29593
|
+
value: promptSnapshot.value,
|
|
28818
29594
|
placeholder: "Type /stop to stop or /exit to quit",
|
|
28819
29595
|
marginTop: 1
|
|
28820
29596
|
}),
|
|
@@ -28972,18 +29748,6 @@ function InputFrameLine({ row, width, cursorColor }) {
|
|
|
28972
29748
|
function CursorCell({ cursorColor }) {
|
|
28973
29749
|
return React.createElement(Text, { color: "black", backgroundColor: cursorColor, bold: true }, "\x1B[5m \x1B[25m");
|
|
28974
29750
|
}
|
|
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
29751
|
function SessionSelector({ state }) {
|
|
28988
29752
|
const items = state.sessionOptions;
|
|
28989
29753
|
const width = sessionPanelWidth();
|
|
@@ -29001,7 +29765,8 @@ function SessionSelector({ state }) {
|
|
|
29001
29765
|
sessionOptionLine(item, index, selected, contentWidth)
|
|
29002
29766
|
);
|
|
29003
29767
|
}),
|
|
29004
|
-
items.length === 0 ? React.createElement(Text, { color: "gray" }, "No saved sessions. Press n to create one.") : null
|
|
29768
|
+
items.length === 0 ? React.createElement(Text, { color: "gray" }, "No saved sessions. Press n to create one.") : null,
|
|
29769
|
+
React.createElement(Text, { color: "gray" }, "enter switch | n new | d delete | esc cancel")
|
|
29005
29770
|
);
|
|
29006
29771
|
}
|
|
29007
29772
|
function sessionSelectorColumns(width) {
|
|
@@ -29103,6 +29868,95 @@ function ModelEditor({ state }) {
|
|
|
29103
29868
|
state.settingsError ? React.createElement(Text, { color: "yellow" }, state.settingsError) : React.createElement(Text, { color: "gray" }, "enter apply | backspace edit | esc cancel")
|
|
29104
29869
|
);
|
|
29105
29870
|
}
|
|
29871
|
+
function ConfigManager({ state }) {
|
|
29872
|
+
const items = state.providerOptions;
|
|
29873
|
+
const selected = items[state.providerCursor];
|
|
29874
|
+
const defaultProvider = state.configDefaultProvider ?? state.status.provider;
|
|
29875
|
+
const contentWidth = Math.max(40, promptPanelWidth() - 8);
|
|
29876
|
+
return React.createElement(
|
|
29877
|
+
DialogFrame,
|
|
29878
|
+
{ title: "Config", accent: "magenta" },
|
|
29879
|
+
React.createElement(Text, { color: state.configCreated ? "green" : "gray" }, state.configPath ? `config: ${shorten(state.configPath, contentWidth)}` : "config: ~/.demian/config.json"),
|
|
29880
|
+
state.configMessage ? React.createElement(Text, { color: "cyan" }, state.configMessage) : null,
|
|
29881
|
+
React.createElement(Text, { color: "gray" }, `${"Provider".padEnd(20)} ${"Default model".padEnd(28)} Status`),
|
|
29882
|
+
...items.map((item, index) => {
|
|
29883
|
+
const isDefault = item.name === defaultProvider;
|
|
29884
|
+
const pointer = index === state.providerCursor ? ">" : " ";
|
|
29885
|
+
const marker = isDefault ? "*" : " ";
|
|
29886
|
+
return React.createElement(
|
|
29887
|
+
Text,
|
|
29888
|
+
{ key: item.name, color: index === state.providerCursor ? "cyan" : item.available === false ? "gray" : "white", bold: index === state.providerCursor },
|
|
29889
|
+
`${pointer}${marker} ${shorten(item.label ?? item.name, 18).padEnd(18)} ${shorten((item.modelLabel ?? item.model) || "-", 28).padEnd(28)} ${providerStatusLabel(item)}`
|
|
29890
|
+
);
|
|
29891
|
+
}),
|
|
29892
|
+
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,
|
|
29893
|
+
React.createElement(Text, { color: "gray" }, "enter/d default | m models | b baseURL | e env | h auth | n new model | a add provider | esc close")
|
|
29894
|
+
);
|
|
29895
|
+
}
|
|
29896
|
+
function ConfigModelSelector({ state }) {
|
|
29897
|
+
const provider = state.providerOptions[state.providerCursor];
|
|
29898
|
+
const profiles = provider?.modelProfiles ?? [];
|
|
29899
|
+
return React.createElement(
|
|
29900
|
+
DialogFrame,
|
|
29901
|
+
{ title: provider ? `Models: ${provider.name}` : "Models", accent: "magenta" },
|
|
29902
|
+
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."),
|
|
29903
|
+
...profiles.slice(0, 12).map(
|
|
29904
|
+
(item, index) => React.createElement(
|
|
29905
|
+
Text,
|
|
29906
|
+
{ key: `${item.name}-${item.model}`, color: index === state.configModelCursor ? "cyan" : item.available === false ? "gray" : "white", bold: index === state.configModelCursor },
|
|
29907
|
+
`${index === state.configModelCursor ? ">" : " "} ${shorten(item.label ?? item.displayName ?? item.model, 30).padEnd(30)} ${item.source ?? "config"}`
|
|
29908
|
+
)
|
|
29909
|
+
),
|
|
29910
|
+
React.createElement(Text, { color: "gray" }, "enter set default model | n add custom model | esc back")
|
|
29911
|
+
);
|
|
29912
|
+
}
|
|
29913
|
+
function ConfigModelInput({ state }) {
|
|
29914
|
+
const provider = state.providerOptions[state.providerCursor];
|
|
29915
|
+
const value = state.configModelInput || "Model ID";
|
|
29916
|
+
return React.createElement(
|
|
29917
|
+
DialogFrame,
|
|
29918
|
+
{ title: provider ? `Add model: ${provider.name}` : "Add model", accent: "magenta" },
|
|
29919
|
+
React.createElement(
|
|
29920
|
+
Box,
|
|
29921
|
+
null,
|
|
29922
|
+
React.createElement(Text, { color: "cyan", bold: true }, "> "),
|
|
29923
|
+
React.createElement(Text, { color: state.configModelInput ? "white" : "gray" }, value)
|
|
29924
|
+
),
|
|
29925
|
+
state.settingsError ? React.createElement(Text, { color: "yellow" }, state.settingsError) : React.createElement(Text, { color: "gray" }, "enter add and make default | backspace edit | esc back")
|
|
29926
|
+
);
|
|
29927
|
+
}
|
|
29928
|
+
function ConfigProviderInput({ state }) {
|
|
29929
|
+
const provider = state.providerOptions[state.providerCursor];
|
|
29930
|
+
const field = state.configProviderInputField ?? "baseURL";
|
|
29931
|
+
const label = field === "baseURL" ? "Base URL" : field === "apiKeyEnv" ? "API key env" : "Auth header";
|
|
29932
|
+
const placeholder = field === "baseURL" ? "empty removes provider baseURL" : field === "apiKeyEnv" ? "empty removes provider apiKeyEnv" : "empty removes custom auth header";
|
|
29933
|
+
const value = state.configProviderInput || placeholder;
|
|
29934
|
+
return React.createElement(
|
|
29935
|
+
DialogFrame,
|
|
29936
|
+
{ title: provider ? `Edit ${label}: ${provider.name}` : `Edit ${label}`, accent: "magenta" },
|
|
29937
|
+
React.createElement(
|
|
29938
|
+
Box,
|
|
29939
|
+
null,
|
|
29940
|
+
React.createElement(Text, { color: "cyan", bold: true }, "> "),
|
|
29941
|
+
React.createElement(Text, { color: state.configProviderInput ? "white" : "gray" }, value)
|
|
29942
|
+
),
|
|
29943
|
+
state.settingsError ? React.createElement(Text, { color: "yellow" }, state.settingsError) : React.createElement(Text, { color: "gray" }, "enter save | backspace edit/clear | esc back")
|
|
29944
|
+
);
|
|
29945
|
+
}
|
|
29946
|
+
function ConfigAddProviderSelector({ state }) {
|
|
29947
|
+
return React.createElement(
|
|
29948
|
+
DialogFrame,
|
|
29949
|
+
{ title: "Add Provider", accent: "magenta" },
|
|
29950
|
+
...state.configProviderPresets.map(
|
|
29951
|
+
(item, index) => React.createElement(
|
|
29952
|
+
Text,
|
|
29953
|
+
{ key: item.preset, color: index === state.configAddProviderCursor ? "cyan" : "white", bold: index === state.configAddProviderCursor },
|
|
29954
|
+
`${index === state.configAddProviderCursor ? ">" : " "} ${shorten(item.label, 18).padEnd(18)} ${item.detail ?? item.preset}`
|
|
29955
|
+
)
|
|
29956
|
+
),
|
|
29957
|
+
React.createElement(Text, { color: "gray" }, "enter add preset | esc back")
|
|
29958
|
+
);
|
|
29959
|
+
}
|
|
29106
29960
|
function PermissionBar({ state }) {
|
|
29107
29961
|
const pending = state.permission;
|
|
29108
29962
|
if (!pending) {
|
|
@@ -29328,7 +30182,7 @@ function clamp01(value) {
|
|
|
29328
30182
|
if (!Number.isFinite(value)) return 0;
|
|
29329
30183
|
return Math.max(0, Math.min(1, value));
|
|
29330
30184
|
}
|
|
29331
|
-
var SURFACE, CSI_U_INPUT, XTERM_MODIFIED_ENTER_INPUT, SLASH_COMMANDS, DEMIAN_WORDMARK_LINES;
|
|
30185
|
+
var SURFACE, CSI_U_INPUT, XTERM_MODIFIED_ENTER_INPUT, SLASH_COMMANDS, PromptDraftController, SessionMetaBox, SessionCommandsBox, DEMIAN_WORDMARK_LINES, DemianWordmark, TranscriptView, BlockView, CommandGuide;
|
|
29332
30186
|
var init_app = __esm({
|
|
29333
30187
|
"src/ui/tui/app.ts"() {
|
|
29334
30188
|
"use strict";
|
|
@@ -29362,6 +30216,105 @@ var init_app = __esm({
|
|
|
29362
30216
|
{ command: "/exit", insert: "/exit", mode: "both", description: "Exit Demian", aliases: ["quit", "close"] },
|
|
29363
30217
|
{ command: "/quit", insert: "/quit", mode: "both", description: "Exit Demian", aliases: ["exit", "close"] }
|
|
29364
30218
|
];
|
|
30219
|
+
PromptDraftController = class {
|
|
30220
|
+
#snapshot;
|
|
30221
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
30222
|
+
constructor(initialValue) {
|
|
30223
|
+
this.#snapshot = {
|
|
30224
|
+
value: initialValue,
|
|
30225
|
+
slashCommandCursor: 0
|
|
30226
|
+
};
|
|
30227
|
+
}
|
|
30228
|
+
snapshot() {
|
|
30229
|
+
return this.#snapshot;
|
|
30230
|
+
}
|
|
30231
|
+
value() {
|
|
30232
|
+
return this.#snapshot.value;
|
|
30233
|
+
}
|
|
30234
|
+
subscribe(listener) {
|
|
30235
|
+
this.#listeners.add(listener);
|
|
30236
|
+
return () => {
|
|
30237
|
+
this.#listeners.delete(listener);
|
|
30238
|
+
};
|
|
30239
|
+
}
|
|
30240
|
+
setValue(value) {
|
|
30241
|
+
const sameValue = value === this.#snapshot.value;
|
|
30242
|
+
const slashCommandClosedPrefix = this.#snapshot.slashCommandClosedPrefix && isSlashCommandClosedForPrompt(value, this.#snapshot.slashCommandClosedPrefix) ? this.#snapshot.slashCommandClosedPrefix : void 0;
|
|
30243
|
+
this.#set({
|
|
30244
|
+
value,
|
|
30245
|
+
slashCommandCursor: sameValue ? this.#snapshot.slashCommandCursor : 0,
|
|
30246
|
+
slashCommandClosedPrefix
|
|
30247
|
+
});
|
|
30248
|
+
}
|
|
30249
|
+
append(value) {
|
|
30250
|
+
if (!value) return;
|
|
30251
|
+
this.setValue(`${this.#snapshot.value}${value}`);
|
|
30252
|
+
}
|
|
30253
|
+
backspace() {
|
|
30254
|
+
this.setValue(Array.from(this.#snapshot.value).slice(0, -1).join(""));
|
|
30255
|
+
}
|
|
30256
|
+
closeSlashCommandsForCurrentPrompt() {
|
|
30257
|
+
this.#set({
|
|
30258
|
+
...this.#snapshot,
|
|
30259
|
+
slashCommandCursor: 0,
|
|
30260
|
+
slashCommandClosedPrefix: this.#snapshot.value
|
|
30261
|
+
});
|
|
30262
|
+
}
|
|
30263
|
+
moveSlashCommandCursor(delta, count) {
|
|
30264
|
+
if (count <= 0) return;
|
|
30265
|
+
this.#set({
|
|
30266
|
+
...this.#snapshot,
|
|
30267
|
+
slashCommandCursor: (this.#snapshot.slashCommandCursor + delta + count) % count
|
|
30268
|
+
});
|
|
30269
|
+
}
|
|
30270
|
+
#set(next) {
|
|
30271
|
+
if (next.value === this.#snapshot.value && next.slashCommandCursor === this.#snapshot.slashCommandCursor && next.slashCommandClosedPrefix === this.#snapshot.slashCommandClosedPrefix) {
|
|
30272
|
+
return;
|
|
30273
|
+
}
|
|
30274
|
+
this.#snapshot = next;
|
|
30275
|
+
for (const listener of this.#listeners) listener();
|
|
30276
|
+
}
|
|
30277
|
+
};
|
|
30278
|
+
SessionMetaBox = React.memo(function SessionMetaBox2({ state, version, marginTop = 1 }) {
|
|
30279
|
+
const width = promptPanelWidth();
|
|
30280
|
+
const compact = width < 68;
|
|
30281
|
+
const agent = shorten(state.selectedAgent ?? state.status.agent ?? "general", compact ? 14 : 18);
|
|
30282
|
+
const model = shorten(state.selection?.model ?? state.status.model ?? "model", compact ? Math.max(18, width - 14) : 30);
|
|
30283
|
+
const workspace = shorten(state.status.cwd ?? process.cwd(), compact ? Math.max(18, width - 18) : 46);
|
|
30284
|
+
const rows = compact ? [
|
|
30285
|
+
`agent ${agent} \xB7 perm ${state.permissionPreset}`,
|
|
30286
|
+
`model ${model}`,
|
|
30287
|
+
`workspace ${workspace}`,
|
|
30288
|
+
version ? `version v${version}` : void 0
|
|
30289
|
+
].filter(Boolean) : [
|
|
30290
|
+
`agent ${agent} \xB7 model ${model} \xB7 perm ${state.permissionPreset}`,
|
|
30291
|
+
`workspace ${workspace}${version ? ` \xB7 version v${version}` : ""}`
|
|
30292
|
+
];
|
|
30293
|
+
return React.createElement(
|
|
30294
|
+
Box,
|
|
30295
|
+
{ alignSelf: "center", width, flexDirection: "column", borderStyle: "single", borderColor: "#334155", paddingX: 1, marginTop },
|
|
30296
|
+
...rows.map((line, index) => React.createElement(SurfaceLine, { key: index, text: line ?? "", width: width - 4, backgroundColor: SURFACE.status, color: "white" }))
|
|
30297
|
+
);
|
|
30298
|
+
});
|
|
30299
|
+
SessionCommandsBox = React.memo(function SessionCommandsBox2({ marginTop = 1 }) {
|
|
30300
|
+
const width = promptPanelWidth();
|
|
30301
|
+
const contentWidth = Math.max(24, width - 4);
|
|
30302
|
+
const lines = initialSessionCommandLines(contentWidth);
|
|
30303
|
+
return React.createElement(
|
|
30304
|
+
Box,
|
|
30305
|
+
{ alignSelf: "center", width, flexDirection: "column", borderStyle: "single", borderColor: "#334155", paddingX: 1, marginTop },
|
|
30306
|
+
...lines.map(
|
|
30307
|
+
(line, index) => React.createElement(SurfaceLine, {
|
|
30308
|
+
key: index,
|
|
30309
|
+
text: line,
|
|
30310
|
+
width: contentWidth,
|
|
30311
|
+
backgroundColor: SURFACE.status,
|
|
30312
|
+
color: index === 0 ? "magenta" : "gray",
|
|
30313
|
+
bold: index === 0
|
|
30314
|
+
})
|
|
30315
|
+
)
|
|
30316
|
+
);
|
|
30317
|
+
});
|
|
29365
30318
|
DEMIAN_WORDMARK_LINES = [
|
|
29366
30319
|
" _ _ ",
|
|
29367
30320
|
" __| | ___ _ __ ___ (_) __ _ _ __ ",
|
|
@@ -29369,6 +30322,56 @@ var init_app = __esm({
|
|
|
29369
30322
|
"| (_| | __/ | | | | | | (_| | | | |",
|
|
29370
30323
|
" \\__,_|\\___|_| |_| |_|_|\\__,_|_| |_|"
|
|
29371
30324
|
];
|
|
30325
|
+
DemianWordmark = React.memo(function DemianWordmark2() {
|
|
30326
|
+
if (terminalWidth() < 58) {
|
|
30327
|
+
return React.createElement(Box, { alignSelf: "center" }, React.createElement(Text, { color: "gray", bold: true }, "DEMIAN"));
|
|
30328
|
+
}
|
|
30329
|
+
return React.createElement(
|
|
30330
|
+
Box,
|
|
30331
|
+
{ alignSelf: "center", flexDirection: "column" },
|
|
30332
|
+
...DEMIAN_WORDMARK_LINES.map(
|
|
30333
|
+
(line, index) => React.createElement(Text, { key: index, color: index < 2 ? "gray" : "white", bold: true }, line)
|
|
30334
|
+
)
|
|
30335
|
+
);
|
|
30336
|
+
});
|
|
30337
|
+
TranscriptView = React.memo(function TranscriptView2({ state }) {
|
|
30338
|
+
const blocks = state.blocks.slice(-12);
|
|
30339
|
+
const streaming = !state.streamingFinalized && state.streamingText ? { kind: "assistant", title: "Assistant streaming", lines: streamingLines(state.streamingText) } : void 0;
|
|
30340
|
+
const width = transcriptPanelWidth();
|
|
30341
|
+
const contentWidth = Math.max(24, width - 2);
|
|
30342
|
+
return React.createElement(
|
|
30343
|
+
Box,
|
|
30344
|
+
{ alignSelf: "center", width, flexDirection: "column", marginTop: 1 },
|
|
30345
|
+
...blocks.map((block) => React.createElement(BlockView, { key: block.id, block, width: contentWidth })),
|
|
30346
|
+
streaming ? React.createElement(BlockView, { key: "streaming", block: streaming, width: contentWidth }) : null
|
|
30347
|
+
);
|
|
30348
|
+
});
|
|
30349
|
+
BlockView = React.memo(function BlockView2({ block, width }) {
|
|
30350
|
+
if (block.kind === "tool") return React.createElement(ToolBlockView, { block, width });
|
|
30351
|
+
if (block.kind === "goal-work") return React.createElement(GoalWorkBlockView, { block, width });
|
|
30352
|
+
if (block.kind === "cowork") return React.createElement(CoworkBlockView, { block, width });
|
|
30353
|
+
if (block.kind === "user") return React.createElement(UserBlockView, { block, width });
|
|
30354
|
+
const color = block.kind === "user" ? "green" : block.kind === "assistant" ? "white" : block.kind === "warning" ? "yellow" : "magenta";
|
|
30355
|
+
const lines = wrapBlockLines(block.lines, width, 80);
|
|
30356
|
+
return React.createElement(
|
|
30357
|
+
Box,
|
|
30358
|
+
{ flexDirection: "column", marginBottom: 1 },
|
|
30359
|
+
React.createElement(Text, { color, bold: true }, fitToWidth(block.title, width)),
|
|
30360
|
+
...lines.map((line, index) => React.createElement(Text, { key: index }, line || " "))
|
|
30361
|
+
);
|
|
30362
|
+
}, sameBlockViewProps);
|
|
30363
|
+
CommandGuide = React.memo(function CommandGuide2({ hint, color, error }) {
|
|
30364
|
+
return React.createElement(
|
|
30365
|
+
Box,
|
|
30366
|
+
{ alignSelf: "center", width: promptPanelWidth(), flexDirection: "column", marginTop: 1 },
|
|
30367
|
+
error ? React.createElement(Text, { color: "yellow" }, error) : null,
|
|
30368
|
+
React.createElement(
|
|
30369
|
+
Box,
|
|
30370
|
+
{ justifyContent: "flex-end" },
|
|
30371
|
+
React.createElement(Text, { color }, hint)
|
|
30372
|
+
)
|
|
30373
|
+
);
|
|
30374
|
+
});
|
|
29372
30375
|
}
|
|
29373
30376
|
});
|
|
29374
30377
|
|
|
@@ -34096,17 +35099,17 @@ function findDependencyCycle(group) {
|
|
|
34096
35099
|
const byId = new Map(group.map((member) => [member.memberId, member]));
|
|
34097
35100
|
const visiting = /* @__PURE__ */ new Set();
|
|
34098
35101
|
const visited = /* @__PURE__ */ new Set();
|
|
34099
|
-
const
|
|
35102
|
+
const path38 = [];
|
|
34100
35103
|
const visit = (id) => {
|
|
34101
|
-
if (visiting.has(id)) return [...
|
|
35104
|
+
if (visiting.has(id)) return [...path38.slice(path38.indexOf(id)), id];
|
|
34102
35105
|
if (visited.has(id)) return void 0;
|
|
34103
35106
|
visiting.add(id);
|
|
34104
|
-
|
|
35107
|
+
path38.push(id);
|
|
34105
35108
|
for (const dep of byId.get(id)?.dependsOn ?? []) {
|
|
34106
35109
|
const cycle = visit(dep);
|
|
34107
35110
|
if (cycle) return cycle;
|
|
34108
35111
|
}
|
|
34109
|
-
|
|
35112
|
+
path38.pop();
|
|
34110
35113
|
visiting.delete(id);
|
|
34111
35114
|
visited.add(id);
|
|
34112
35115
|
return void 0;
|
|
@@ -35406,6 +36409,9 @@ function providerOptions(config, catalogs = {}) {
|
|
|
35406
36409
|
permissionMode: provider.type === "claudecode" ? provider.permissionMode : void 0,
|
|
35407
36410
|
ga: provider.type === "claudecode" ? claudeCodeGaEnabled(provider) : void 0,
|
|
35408
36411
|
available,
|
|
36412
|
+
baseURL: typeof provider.baseURL === "string" ? provider.baseURL : void 0,
|
|
36413
|
+
apiKeyEnv: typeof provider.apiKeyEnv === "string" ? provider.apiKeyEnv : void 0,
|
|
36414
|
+
auth: sanitizeProviderAuth(provider.auth),
|
|
35409
36415
|
catalog: catalog ? { status: catalog.status, source: catalog.source, message: catalog.message } : void 0
|
|
35410
36416
|
};
|
|
35411
36417
|
}).sort((left, right) => {
|
|
@@ -35476,6 +36482,13 @@ function providerCatalogAvailable(catalog) {
|
|
|
35476
36482
|
function unavailableLabel(value) {
|
|
35477
36483
|
return `(n/a) ${value}`;
|
|
35478
36484
|
}
|
|
36485
|
+
function sanitizeProviderAuth(auth) {
|
|
36486
|
+
if (!auth || typeof auth !== "object" || Array.isArray(auth)) return void 0;
|
|
36487
|
+
const record = auth;
|
|
36488
|
+
const type = typeof record.type === "string" ? record.type : void 0;
|
|
36489
|
+
const header = typeof record.header === "string" ? record.header : void 0;
|
|
36490
|
+
return type || header ? { type, header } : void 0;
|
|
36491
|
+
}
|
|
35479
36492
|
function errorMessage4(error) {
|
|
35480
36493
|
return error instanceof Error ? error.message : String(error);
|
|
35481
36494
|
}
|
|
@@ -35706,12 +36719,14 @@ __export(controller_exports, {
|
|
|
35706
36719
|
runTuiSession: () => runTuiSession
|
|
35707
36720
|
});
|
|
35708
36721
|
import path36 from "node:path";
|
|
36722
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
35709
36723
|
async function runTuiSession(flags, store) {
|
|
35710
36724
|
const cwd = path36.resolve(flags.cwd ?? process.cwd());
|
|
35711
36725
|
const eventBus = new EventBus();
|
|
35712
36726
|
eventBus.subscribe((event) => store.handleEvent(event));
|
|
35713
|
-
const
|
|
35714
|
-
|
|
36727
|
+
const configInit = await ensureTuiConfigExists(flags);
|
|
36728
|
+
let loadedConfig = await loadConfig({ cwd, configPath: flags.configPath });
|
|
36729
|
+
let config = flags.context ? mergeConfig(loadedConfig, {
|
|
35715
36730
|
context: {
|
|
35716
36731
|
main: flags.context
|
|
35717
36732
|
}
|
|
@@ -35731,6 +36746,10 @@ async function runTuiSession(flags, store) {
|
|
|
35731
36746
|
store.configureSettings(initialSelection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
|
|
35732
36747
|
agent: agentName,
|
|
35733
36748
|
cwd,
|
|
36749
|
+
configPath: configMutationPath(flags),
|
|
36750
|
+
configCreated: configInit.created,
|
|
36751
|
+
configDefaultProvider: config.defaultProvider,
|
|
36752
|
+
configMessage: configInit.created ? `Created config at ${configInit.path}` : void 0,
|
|
35734
36753
|
agentOptions,
|
|
35735
36754
|
contextEfficiency: {
|
|
35736
36755
|
maxContextTokens: config.context.main.maxContextTokens,
|
|
@@ -35739,6 +36758,7 @@ async function runTuiSession(flags, store) {
|
|
|
35739
36758
|
}
|
|
35740
36759
|
});
|
|
35741
36760
|
let nextPrompt = flags.prompt;
|
|
36761
|
+
let openConfigOnFirstPrompt = configInit.created && !flags.prompt.trim();
|
|
35742
36762
|
const conversationsEnabled = flags.conversationManagement === true;
|
|
35743
36763
|
const conversationState = conversationsEnabled ? await initializeConversationState(cwd, flags) : void 0;
|
|
35744
36764
|
let currentConversation = conversationState?.current;
|
|
@@ -35779,13 +36799,28 @@ async function runTuiSession(flags, store) {
|
|
|
35779
36799
|
await applyStartupSessionSelection(selected);
|
|
35780
36800
|
}
|
|
35781
36801
|
for (; ; ) {
|
|
35782
|
-
const
|
|
36802
|
+
const promptPromise = store.requestPrompt(nextPrompt);
|
|
36803
|
+
if (openConfigOnFirstPrompt) {
|
|
36804
|
+
openConfigOnFirstPrompt = false;
|
|
36805
|
+
store.openConfigManager(`Created config at ${configInit.path}`);
|
|
36806
|
+
}
|
|
36807
|
+
const prompt = await promptPromise;
|
|
35783
36808
|
nextPrompt = void 0;
|
|
35784
36809
|
if (store.exitRequested()) {
|
|
35785
36810
|
await saveCurrentSelection();
|
|
35786
36811
|
return 0;
|
|
35787
36812
|
}
|
|
35788
36813
|
if (!prompt) return 0;
|
|
36814
|
+
const configAction = parseTuiConfigAction(prompt);
|
|
36815
|
+
if (configAction) {
|
|
36816
|
+
await handleConfigAction(configAction);
|
|
36817
|
+
continue;
|
|
36818
|
+
}
|
|
36819
|
+
const sessionAction = parseTuiSessionAction(prompt);
|
|
36820
|
+
if (sessionAction) {
|
|
36821
|
+
await handleSessionSelectionShortcut();
|
|
36822
|
+
continue;
|
|
36823
|
+
}
|
|
35789
36824
|
const sessionCommand = parseSessionCommand(prompt);
|
|
35790
36825
|
if (sessionCommand) {
|
|
35791
36826
|
await handleSessionCommand(sessionCommand);
|
|
@@ -36020,22 +37055,13 @@ ${sessionListMessage()}`);
|
|
|
36020
37055
|
return;
|
|
36021
37056
|
}
|
|
36022
37057
|
if (command.action === "delete") {
|
|
36023
|
-
const target = findConversation(sortedConversations(), command.target);
|
|
37058
|
+
const target = command.target ? findConversation(sortedConversations(), command.target) : currentConversation;
|
|
36024
37059
|
if (!target) {
|
|
36025
37060
|
store.showSystemMessage("Sessions", `No session matched "${command.target ?? ""}".`);
|
|
36026
37061
|
store.prepareForPrompt("waiting for next message");
|
|
36027
37062
|
return;
|
|
36028
37063
|
}
|
|
36029
|
-
|
|
36030
|
-
runtimeByConversation.delete(target.id);
|
|
36031
|
-
await deleteConversationRecord(flags.conversationStorageDir, target.id);
|
|
36032
|
-
if (currentConversation?.id === target.id) {
|
|
36033
|
-
const next = sortedConversations()[0] ?? createConversationRecord({ id: createRootSessionId(), cwd });
|
|
36034
|
-
conversationRecords.set(next.id, next);
|
|
36035
|
-
await switchConversation(next);
|
|
36036
|
-
}
|
|
36037
|
-
store.prepareForPrompt(`deleted session: ${target.title}`);
|
|
36038
|
-
await persistCurrentConversation();
|
|
37064
|
+
await deleteConversationById(target.id);
|
|
36039
37065
|
return;
|
|
36040
37066
|
}
|
|
36041
37067
|
if (command.action === "rename") {
|
|
@@ -36062,6 +37088,29 @@ ${sessionListMessage()}`);
|
|
|
36062
37088
|
await persistCurrentConversation();
|
|
36063
37089
|
}
|
|
36064
37090
|
}
|
|
37091
|
+
async function handleSessionSelectionShortcut() {
|
|
37092
|
+
if (!conversationsEnabled) {
|
|
37093
|
+
store.showSystemMessage("Sessions", "Session management is available in demian-cli TUI, but this embedded runtime lets the host manage sessions.");
|
|
37094
|
+
store.prepareForPrompt("waiting for next message");
|
|
37095
|
+
return;
|
|
37096
|
+
}
|
|
37097
|
+
if (!currentConversation) {
|
|
37098
|
+
currentConversation = createConversationRecord({ id: flags.sessionId ?? createRootSessionId(), cwd });
|
|
37099
|
+
conversationRecords.set(currentConversation.id, currentConversation);
|
|
37100
|
+
}
|
|
37101
|
+
await persistCurrentConversation();
|
|
37102
|
+
const selection = await store.requestSessionSelection(startupSessionOptions(), { cancel: "prompt" });
|
|
37103
|
+
if (selection.kind === "cancel") {
|
|
37104
|
+
store.prepareForPrompt("waiting for next message");
|
|
37105
|
+
return;
|
|
37106
|
+
}
|
|
37107
|
+
if (selection.kind === "exit" || store.exitRequested()) return;
|
|
37108
|
+
if (selection.kind === "delete") {
|
|
37109
|
+
await deleteConversationById(selection.id);
|
|
37110
|
+
return;
|
|
37111
|
+
}
|
|
37112
|
+
await applyStartupSessionSelection(selection);
|
|
37113
|
+
}
|
|
36065
37114
|
function startupSessionOptions() {
|
|
36066
37115
|
const records = sortedConversations();
|
|
36067
37116
|
const sessionOptions = records.map((record) => ({
|
|
@@ -36076,6 +37125,14 @@ ${sessionListMessage()}`);
|
|
|
36076
37125
|
return [...sessionOptions, { kind: "new", title: "New session", cwd, status: "ready", currentWorkspace: true }];
|
|
36077
37126
|
}
|
|
36078
37127
|
async function applyStartupSessionSelection(selection) {
|
|
37128
|
+
if (selection.kind === "cancel") {
|
|
37129
|
+
store.prepareForPrompt("waiting for next message");
|
|
37130
|
+
return;
|
|
37131
|
+
}
|
|
37132
|
+
if (selection.kind === "delete") {
|
|
37133
|
+
await deleteConversationById(selection.id);
|
|
37134
|
+
return;
|
|
37135
|
+
}
|
|
36079
37136
|
if (selection.kind === "new") {
|
|
36080
37137
|
const next = createConversationRecord({ id: createRootSessionId(), cwd });
|
|
36081
37138
|
conversationRecords.set(next.id, next);
|
|
@@ -36097,6 +37154,24 @@ ${sessionListMessage()}`);
|
|
|
36097
37154
|
store.prepareForPrompt(`session ready: ${target.title}`);
|
|
36098
37155
|
await saveConversationRecords(flags.conversationStorageDir, [...conversationRecords.values()], target.id);
|
|
36099
37156
|
}
|
|
37157
|
+
async function deleteConversationById(id) {
|
|
37158
|
+
const target = conversationRecords.get(id) ?? findConversation(sortedConversations(), id);
|
|
37159
|
+
if (!target) {
|
|
37160
|
+
store.showSystemMessage("Sessions", `No session matched "${id}".`);
|
|
37161
|
+
store.prepareForPrompt("waiting for next message");
|
|
37162
|
+
return;
|
|
37163
|
+
}
|
|
37164
|
+
conversationRecords.delete(target.id);
|
|
37165
|
+
runtimeByConversation.delete(target.id);
|
|
37166
|
+
await deleteConversationRecord(flags.conversationStorageDir, target.id);
|
|
37167
|
+
if (currentConversation?.id === target.id) {
|
|
37168
|
+
const next = sortedConversations()[0] ?? createConversationRecord({ id: createRootSessionId(), cwd });
|
|
37169
|
+
conversationRecords.set(next.id, next);
|
|
37170
|
+
await switchConversation(next);
|
|
37171
|
+
}
|
|
37172
|
+
store.prepareForPrompt(`deleted session: ${target.title}`);
|
|
37173
|
+
await persistCurrentConversation();
|
|
37174
|
+
}
|
|
36100
37175
|
async function switchConversation(record) {
|
|
36101
37176
|
currentConversation = record;
|
|
36102
37177
|
rootSessionId = record.id;
|
|
@@ -36132,6 +37207,87 @@ ${sessionListMessage()}`);
|
|
|
36132
37207
|
await preferenceStore.save(selection);
|
|
36133
37208
|
savedSelectionKey = key;
|
|
36134
37209
|
}
|
|
37210
|
+
async function handleConfigAction(action) {
|
|
37211
|
+
const path38 = configMutationPath(flags);
|
|
37212
|
+
try {
|
|
37213
|
+
let message = "Config updated.";
|
|
37214
|
+
let nextSelection = {};
|
|
37215
|
+
if (action.type === "setDefaultProvider") {
|
|
37216
|
+
await updateConfigDefaults({ path: path38, defaultProvider: action.provider });
|
|
37217
|
+
message = `Default provider saved: ${action.provider}`;
|
|
37218
|
+
nextSelection = { provider: action.provider };
|
|
37219
|
+
} else if (action.type === "setDefaultModel") {
|
|
37220
|
+
await setDefaultModelProfile({
|
|
37221
|
+
path: path38,
|
|
37222
|
+
provider: action.provider,
|
|
37223
|
+
profileName: action.profileName,
|
|
37224
|
+
name: action.name,
|
|
37225
|
+
displayName: action.displayName,
|
|
37226
|
+
model: action.model
|
|
37227
|
+
});
|
|
37228
|
+
message = `Default model saved for ${action.provider}: ${action.displayName ?? action.model}`;
|
|
37229
|
+
nextSelection = { provider: action.provider, model: action.displayName ?? action.model };
|
|
37230
|
+
} else if (action.type === "addProvider") {
|
|
37231
|
+
await addProvider({ path: path38, name: action.name, preset: action.preset, apiKeyEnv: action.apiKeyEnv });
|
|
37232
|
+
message = `Provider added: ${action.name}`;
|
|
37233
|
+
nextSelection = { provider: action.name };
|
|
37234
|
+
} else if (action.type === "updateProvider") {
|
|
37235
|
+
await updateProviderSettings({
|
|
37236
|
+
path: path38,
|
|
37237
|
+
provider: action.provider,
|
|
37238
|
+
...action.field === "baseURL" ? { baseURL: action.value } : {},
|
|
37239
|
+
...action.field === "apiKeyEnv" ? { apiKeyEnv: action.value } : {},
|
|
37240
|
+
...action.field === "authHeader" ? { authHeader: action.value } : {}
|
|
37241
|
+
});
|
|
37242
|
+
message = `Provider ${action.field} saved: ${action.provider}`;
|
|
37243
|
+
nextSelection = { provider: action.provider };
|
|
37244
|
+
}
|
|
37245
|
+
await reloadConfigSettings(message, nextSelection);
|
|
37246
|
+
} catch (error) {
|
|
37247
|
+
store.prepareForPrompt("config action failed");
|
|
37248
|
+
store.showSystemMessage("Config", errorMessage(error));
|
|
37249
|
+
store.openConfigManager(errorMessage(error));
|
|
37250
|
+
}
|
|
37251
|
+
}
|
|
37252
|
+
async function reloadConfigSettings(message, selectionFlags = {}) {
|
|
37253
|
+
loadedConfig = await loadConfig({ cwd, configPath: flags.configPath });
|
|
37254
|
+
config = flags.context ? mergeConfig(loadedConfig, {
|
|
37255
|
+
context: {
|
|
37256
|
+
main: flags.context
|
|
37257
|
+
}
|
|
37258
|
+
}) : loadedConfig;
|
|
37259
|
+
const currentSelection = store.currentSelection();
|
|
37260
|
+
const selection = createInteractiveModelSelection(
|
|
37261
|
+
config,
|
|
37262
|
+
selectionFlags.provider || selectionFlags.model ? selectionFlags : { provider: currentSelection.providerName, model: currentSelection.model }
|
|
37263
|
+
);
|
|
37264
|
+
store.configureSettings(selection, await providerOptionsWithCatalog(config, { timeoutMs: 1500 }), {
|
|
37265
|
+
agent: store.currentAgentName(),
|
|
37266
|
+
cwd,
|
|
37267
|
+
configPath: configMutationPath(flags),
|
|
37268
|
+
configDefaultProvider: config.defaultProvider,
|
|
37269
|
+
configMessage: message,
|
|
37270
|
+
agentOptions,
|
|
37271
|
+
contextEfficiency: {
|
|
37272
|
+
maxContextTokens: config.context.main.maxContextTokens,
|
|
37273
|
+
compactAtRatio: config.context.main.compactAtRatio ?? config.context.main.compressAtRatio ?? 0.5,
|
|
37274
|
+
compactAtTokens: config.context.main.maxContextTokens ? Math.floor(config.context.main.maxContextTokens * (config.context.main.compactAtRatio ?? config.context.main.compressAtRatio ?? 0.5)) : void 0
|
|
37275
|
+
}
|
|
37276
|
+
});
|
|
37277
|
+
store.openConfigManager(message);
|
|
37278
|
+
}
|
|
37279
|
+
}
|
|
37280
|
+
async function ensureTuiConfigExists(flags) {
|
|
37281
|
+
const target = configMutationPath(flags);
|
|
37282
|
+
if (!flags.configPath) {
|
|
37283
|
+
const jsondPath = target.replace(/\.json$/i, ".jsond");
|
|
37284
|
+
if (existsSync5(target) || existsSync5(jsondPath)) return { path: target, created: false };
|
|
37285
|
+
}
|
|
37286
|
+
const result = await createUserConfig({ path: target });
|
|
37287
|
+
return { path: result.path, created: result.created };
|
|
37288
|
+
}
|
|
37289
|
+
function configMutationPath(flags) {
|
|
37290
|
+
return flags.configPath ? path36.resolve(flags.configPath) : defaultUserConfigPath();
|
|
36135
37291
|
}
|
|
36136
37292
|
function isGoalStateClearingAction(action) {
|
|
36137
37293
|
return action === "status" || action === "pause" || action === "resume";
|
|
@@ -36158,7 +37314,7 @@ function sessionUsage() {
|
|
|
36158
37314
|
" /session new [title]",
|
|
36159
37315
|
" /session switch <number|id|title>",
|
|
36160
37316
|
" /session rename <title>",
|
|
36161
|
-
" /session delete
|
|
37317
|
+
" /session delete [number|id|title]",
|
|
36162
37318
|
" /session clear",
|
|
36163
37319
|
"",
|
|
36164
37320
|
"Aliases: /sessions, /conversation, /conversations, /conv"
|
|
@@ -36170,6 +37326,7 @@ var init_controller = __esm({
|
|
|
36170
37326
|
init_registry();
|
|
36171
37327
|
init_types();
|
|
36172
37328
|
init_config();
|
|
37329
|
+
init_config_scaffold();
|
|
36173
37330
|
init_execution();
|
|
36174
37331
|
init_events();
|
|
36175
37332
|
init_runtime();
|
|
@@ -36184,10 +37341,156 @@ var init_controller = __esm({
|
|
|
36184
37341
|
init_history();
|
|
36185
37342
|
init_preferences();
|
|
36186
37343
|
init_settings();
|
|
37344
|
+
init_store();
|
|
36187
37345
|
init_conversations();
|
|
36188
37346
|
}
|
|
36189
37347
|
});
|
|
36190
37348
|
|
|
37349
|
+
// src/ui/tui/engine/ink-engine.ts
|
|
37350
|
+
var ink_engine_exports = {};
|
|
37351
|
+
__export(ink_engine_exports, {
|
|
37352
|
+
runInkTuiEngine: () => runInkTuiEngine
|
|
37353
|
+
});
|
|
37354
|
+
async function runInkTuiEngine(options) {
|
|
37355
|
+
const [{ render }, ReactModule, { TuiApp: TuiApp2 }, { TuiStore: TuiStore2 }, { runTuiSession: runTuiSession2 }] = await Promise.all([
|
|
37356
|
+
dynamicImport4("ink"),
|
|
37357
|
+
dynamicImport4("react"),
|
|
37358
|
+
Promise.resolve().then(() => (init_app(), app_exports)),
|
|
37359
|
+
Promise.resolve().then(() => (init_store(), store_exports)),
|
|
37360
|
+
Promise.resolve().then(() => (init_controller(), controller_exports))
|
|
37361
|
+
]);
|
|
37362
|
+
const React2 = ReactModule.default;
|
|
37363
|
+
const store = new TuiStore2();
|
|
37364
|
+
const instance = render(React2.createElement(TuiApp2, { store, version: options.version }));
|
|
37365
|
+
try {
|
|
37366
|
+
const code = await runTuiSession2({ ...options.flags, conversationManagement: true }, store);
|
|
37367
|
+
setTimeout(() => instance.unmount(), 100);
|
|
37368
|
+
await instance.waitUntilExit();
|
|
37369
|
+
return code;
|
|
37370
|
+
} catch (error) {
|
|
37371
|
+
instance.unmount();
|
|
37372
|
+
await instance.waitUntilExit().catch(() => void 0);
|
|
37373
|
+
throw error;
|
|
37374
|
+
}
|
|
37375
|
+
}
|
|
37376
|
+
var dynamicImport4;
|
|
37377
|
+
var init_ink_engine = __esm({
|
|
37378
|
+
"src/ui/tui/engine/ink-engine.ts"() {
|
|
37379
|
+
"use strict";
|
|
37380
|
+
dynamicImport4 = new Function("specifier", "return import(specifier)");
|
|
37381
|
+
}
|
|
37382
|
+
});
|
|
37383
|
+
|
|
37384
|
+
// src/ui/tui/engine/bubbletea-engine.ts
|
|
37385
|
+
var bubbletea_engine_exports = {};
|
|
37386
|
+
__export(bubbletea_engine_exports, {
|
|
37387
|
+
BubbleTeaSidecarUnavailableError: () => BubbleTeaSidecarUnavailableError,
|
|
37388
|
+
bubbleTeaSidecarCandidates: () => bubbleTeaSidecarCandidates,
|
|
37389
|
+
bubbleTeaSidecarPlatformKey: () => bubbleTeaSidecarPlatformKey,
|
|
37390
|
+
resolveBubbleTeaSidecarBinary: () => resolveBubbleTeaSidecarBinary,
|
|
37391
|
+
runBubbleTeaTuiEngine: () => runBubbleTeaTuiEngine
|
|
37392
|
+
});
|
|
37393
|
+
import { access } from "node:fs/promises";
|
|
37394
|
+
import { constants } from "node:fs";
|
|
37395
|
+
import { spawn as spawn8 } from "node:child_process";
|
|
37396
|
+
import path37 from "node:path";
|
|
37397
|
+
import { fileURLToPath } from "node:url";
|
|
37398
|
+
function bubbleTeaSidecarPlatformKey(platform = process.platform, arch = process.arch) {
|
|
37399
|
+
if (platform === "darwin" && arch === "arm64") return "darwin-arm64";
|
|
37400
|
+
if (platform === "linux" && arch === "x64") return "linux-x64";
|
|
37401
|
+
return void 0;
|
|
37402
|
+
}
|
|
37403
|
+
function bubbleTeaSidecarCandidates(platformKey = bubbleTeaSidecarPlatformKey(), moduleUrl = import.meta.url) {
|
|
37404
|
+
if (!platformKey) return [];
|
|
37405
|
+
const names = [
|
|
37406
|
+
new URL(`./tui-sidecar/${platformKey}/demian-tui`, moduleUrl),
|
|
37407
|
+
new URL(`../../../../dist/tui-sidecar/${platformKey}/demian-tui`, moduleUrl)
|
|
37408
|
+
].map((url) => fileURLToPath(url));
|
|
37409
|
+
return [...new Set(names.map((name) => path37.resolve(name)))];
|
|
37410
|
+
}
|
|
37411
|
+
async function resolveBubbleTeaSidecarBinary(options = {}) {
|
|
37412
|
+
const platformKey = options.platformKey ?? bubbleTeaSidecarPlatformKey();
|
|
37413
|
+
if (!platformKey) {
|
|
37414
|
+
throw new BubbleTeaSidecarUnavailableError(`Bubble Tea sidecar is not packaged for ${process.platform}-${process.arch}.`);
|
|
37415
|
+
}
|
|
37416
|
+
const candidates = options.candidates ?? bubbleTeaSidecarCandidates(platformKey);
|
|
37417
|
+
for (const candidate of candidates) {
|
|
37418
|
+
try {
|
|
37419
|
+
await access(candidate, constants.X_OK);
|
|
37420
|
+
return candidate;
|
|
37421
|
+
} catch {
|
|
37422
|
+
}
|
|
37423
|
+
}
|
|
37424
|
+
throw new BubbleTeaSidecarUnavailableError(`Bubble Tea sidecar binary was not found for ${platformKey}. Run npm run build:tui-sidecar.`);
|
|
37425
|
+
}
|
|
37426
|
+
async function runBubbleTeaTuiEngine(options) {
|
|
37427
|
+
try {
|
|
37428
|
+
const binaryPath = await resolveBubbleTeaSidecarBinary();
|
|
37429
|
+
const probe = await runSidecarProbe(binaryPath);
|
|
37430
|
+
process.stderr.write(`Bubble Tea sidecar ${probe.version ?? "unknown"} is available, but IPC rendering is still gated; falling back to Ink for this run.
|
|
37431
|
+
`);
|
|
37432
|
+
} catch (error) {
|
|
37433
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)} Falling back to Ink TUI.
|
|
37434
|
+
`);
|
|
37435
|
+
}
|
|
37436
|
+
return runInkTuiEngine(options);
|
|
37437
|
+
}
|
|
37438
|
+
async function runSidecarProbe(binaryPath, timeoutMs = 1500) {
|
|
37439
|
+
return await new Promise((resolve, reject) => {
|
|
37440
|
+
const child = spawn8(binaryPath, ["--probe"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
37441
|
+
let stdout = "";
|
|
37442
|
+
let stderr = "";
|
|
37443
|
+
let settled = false;
|
|
37444
|
+
const timer = setTimeout(() => {
|
|
37445
|
+
if (settled) return;
|
|
37446
|
+
settled = true;
|
|
37447
|
+
child.kill("SIGTERM");
|
|
37448
|
+
reject(new BubbleTeaSidecarUnavailableError(`Bubble Tea sidecar startup timed out after ${timeoutMs}ms.`));
|
|
37449
|
+
}, timeoutMs);
|
|
37450
|
+
child.stdout?.setEncoding("utf8");
|
|
37451
|
+
child.stderr?.setEncoding("utf8");
|
|
37452
|
+
child.stdout?.on("data", (chunk) => {
|
|
37453
|
+
stdout += chunk;
|
|
37454
|
+
});
|
|
37455
|
+
child.stderr?.on("data", (chunk) => {
|
|
37456
|
+
stderr += chunk;
|
|
37457
|
+
});
|
|
37458
|
+
child.on("error", (error) => {
|
|
37459
|
+
if (settled) return;
|
|
37460
|
+
settled = true;
|
|
37461
|
+
clearTimeout(timer);
|
|
37462
|
+
reject(new BubbleTeaSidecarUnavailableError(`Bubble Tea sidecar failed to start: ${error.message}`, { cause: error }));
|
|
37463
|
+
});
|
|
37464
|
+
child.on("close", (code) => {
|
|
37465
|
+
if (settled) return;
|
|
37466
|
+
settled = true;
|
|
37467
|
+
clearTimeout(timer);
|
|
37468
|
+
if (code !== 0) {
|
|
37469
|
+
reject(new BubbleTeaSidecarUnavailableError(`Bubble Tea sidecar probe exited with ${code ?? "unknown"}${stderr ? `: ${stderr.trim()}` : ""}`));
|
|
37470
|
+
return;
|
|
37471
|
+
}
|
|
37472
|
+
try {
|
|
37473
|
+
resolve(JSON.parse(stdout));
|
|
37474
|
+
} catch (error) {
|
|
37475
|
+
reject(new BubbleTeaSidecarUnavailableError("Bubble Tea sidecar probe returned invalid JSON.", { cause: error }));
|
|
37476
|
+
}
|
|
37477
|
+
});
|
|
37478
|
+
});
|
|
37479
|
+
}
|
|
37480
|
+
var BubbleTeaSidecarUnavailableError;
|
|
37481
|
+
var init_bubbletea_engine = __esm({
|
|
37482
|
+
"src/ui/tui/engine/bubbletea-engine.ts"() {
|
|
37483
|
+
"use strict";
|
|
37484
|
+
init_ink_engine();
|
|
37485
|
+
BubbleTeaSidecarUnavailableError = class extends Error {
|
|
37486
|
+
constructor(message, options) {
|
|
37487
|
+
super(message, options);
|
|
37488
|
+
this.name = "BubbleTeaSidecarUnavailableError";
|
|
37489
|
+
}
|
|
37490
|
+
};
|
|
37491
|
+
}
|
|
37492
|
+
});
|
|
37493
|
+
|
|
36191
37494
|
// src/tui.ts
|
|
36192
37495
|
init_parser();
|
|
36193
37496
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
@@ -36417,283 +37720,16 @@ Flags:
|
|
|
36417
37720
|
}
|
|
36418
37721
|
|
|
36419
37722
|
// src/config-command.ts
|
|
37723
|
+
init_config_scaffold();
|
|
37724
|
+
init_config();
|
|
37725
|
+
init_catalog();
|
|
37726
|
+
init_claudecode_auth_preflight();
|
|
37727
|
+
init_claudecode_paths();
|
|
36420
37728
|
import { spawn as spawn4 } from "node:child_process";
|
|
36421
37729
|
import { stat as stat4 } from "node:fs/promises";
|
|
36422
37730
|
import os11 from "node:os";
|
|
36423
37731
|
import path17 from "node:path";
|
|
36424
37732
|
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
37733
|
async function maybeRunConfigCommand(argv) {
|
|
36698
37734
|
if (argv[0] === "config") return runConfigCommand(argv.slice(1));
|
|
36699
37735
|
if (argv[0] === "auth") return runAuthCommand(argv.slice(1));
|
|
@@ -36807,6 +37843,41 @@ async function runConfigCommand(argv) {
|
|
|
36807
37843
|
force
|
|
36808
37844
|
});
|
|
36809
37845
|
process.stdout.write(`Updated config: ${result.path}
|
|
37846
|
+
`);
|
|
37847
|
+
return 0;
|
|
37848
|
+
}
|
|
37849
|
+
if (command === "set-default-model") {
|
|
37850
|
+
const [provider, ...tail2] = rest;
|
|
37851
|
+
if (!provider) throw new Error("config set-default-model requires a provider name.");
|
|
37852
|
+
const flags = parseFlags(tail2);
|
|
37853
|
+
const result = await setDefaultModelProfile({
|
|
37854
|
+
path: stringFlag(flags, "path"),
|
|
37855
|
+
provider,
|
|
37856
|
+
profileName: stringFlag(flags, "profile"),
|
|
37857
|
+
name: stringFlag(flags, "name"),
|
|
37858
|
+
displayName: stringFlag(flags, "display-name"),
|
|
37859
|
+
model: stringFlag(flags, "model"),
|
|
37860
|
+
baseURL: stringFlag(flags, "base-url"),
|
|
37861
|
+
apiKey: await apiKeyFromFlags(flags),
|
|
37862
|
+
apiKeyEnv: stringFlag(flags, "api-key-env")
|
|
37863
|
+
});
|
|
37864
|
+
process.stdout.write(`Updated config: ${result.path}
|
|
37865
|
+
`);
|
|
37866
|
+
return 0;
|
|
37867
|
+
}
|
|
37868
|
+
if (command === "update-provider") {
|
|
37869
|
+
const [provider, ...tail2] = rest;
|
|
37870
|
+
if (!provider) throw new Error("config update-provider requires a provider name.");
|
|
37871
|
+
const flags = parseFlags(tail2);
|
|
37872
|
+
const result = await updateProviderSettings({
|
|
37873
|
+
path: stringFlag(flags, "path"),
|
|
37874
|
+
provider,
|
|
37875
|
+
baseURL: stringFlagOrEmpty(flags, "base-url"),
|
|
37876
|
+
apiKey: await apiKeyFromFlags(flags),
|
|
37877
|
+
apiKeyEnv: stringFlagOrEmpty(flags, "api-key-env"),
|
|
37878
|
+
authHeader: stringFlagOrEmpty(flags, "auth-header")
|
|
37879
|
+
});
|
|
37880
|
+
process.stdout.write(`Updated config: ${result.path}
|
|
36810
37881
|
`);
|
|
36811
37882
|
return 0;
|
|
36812
37883
|
}
|
|
@@ -36904,6 +37975,10 @@ function stringFlag(flags, name) {
|
|
|
36904
37975
|
const value = flags.get(name);
|
|
36905
37976
|
return typeof value === "string" && value.trim() ? value : void 0;
|
|
36906
37977
|
}
|
|
37978
|
+
function stringFlagOrEmpty(flags, name) {
|
|
37979
|
+
const value = flags.get(name);
|
|
37980
|
+
return typeof value === "string" ? value : void 0;
|
|
37981
|
+
}
|
|
36907
37982
|
function requiredStringFlag(flags, name) {
|
|
36908
37983
|
const value = stringFlag(flags, name);
|
|
36909
37984
|
if (!value) throw new Error(`--${name} is required.`);
|
|
@@ -36946,6 +38021,8 @@ Usage:
|
|
|
36946
38021
|
demian config setup interactive multi-step provider wizard
|
|
36947
38022
|
demian config add-provider <preset|openai-compatible> [--name <name>] [--base-url <url>] [--api-key-env <env>] [--api-key-stdin] [--auth-header <header>] [--force]
|
|
36948
38023
|
demian config add-model <provider> --name <name> --model <model> [--display-name <label>] [--base-url <url>] [--api-key-env <env>] [--api-key-stdin] [--force]
|
|
38024
|
+
demian config set-default-model <provider> [--profile <name> | --model <model>] [--display-name <label>]
|
|
38025
|
+
demian config update-provider <provider> [--base-url <url>] [--api-key-env <env>] [--auth-header <header>]
|
|
36949
38026
|
demian config models <provider> [--refresh] [--config <path>]
|
|
36950
38027
|
demian config list [--config <path>]
|
|
36951
38028
|
demian config path
|
|
@@ -37112,8 +38189,23 @@ async function checkCodexAuth() {
|
|
|
37112
38189
|
return { ok: false, source: "none", message: "no codex auth file or OPENAI_API_KEY; run `demian auth login codex`" };
|
|
37113
38190
|
}
|
|
37114
38191
|
|
|
38192
|
+
// src/ui/tui/engine/index.ts
|
|
38193
|
+
var TUI_ENGINE_NAMES = /* @__PURE__ */ new Set(["ink", "bubbletea"]);
|
|
38194
|
+
function resolveTuiEngineName(options = {}) {
|
|
38195
|
+
const raw = (options.cliEngine ?? options.env?.DEMIAN_TUI_ENGINE ?? "ink").trim().toLowerCase();
|
|
38196
|
+
if (TUI_ENGINE_NAMES.has(raw)) return raw;
|
|
38197
|
+
throw new Error(`Unsupported TUI engine "${raw}". Use "ink" or "bubbletea".`);
|
|
38198
|
+
}
|
|
38199
|
+
async function runTuiEngine(engine, options) {
|
|
38200
|
+
if (engine === "ink") {
|
|
38201
|
+
const { runInkTuiEngine: runInkTuiEngine2 } = await Promise.resolve().then(() => (init_ink_engine(), ink_engine_exports));
|
|
38202
|
+
return runInkTuiEngine2(options);
|
|
38203
|
+
}
|
|
38204
|
+
const { runBubbleTeaTuiEngine: runBubbleTeaTuiEngine2 } = await Promise.resolve().then(() => (init_bubbletea_engine(), bubbletea_engine_exports));
|
|
38205
|
+
return runBubbleTeaTuiEngine2(options);
|
|
38206
|
+
}
|
|
38207
|
+
|
|
37115
38208
|
// src/tui.ts
|
|
37116
|
-
var dynamicImport4 = new Function("specifier", "return import(specifier)");
|
|
37117
38209
|
async function main(argv = process.argv.slice(2)) {
|
|
37118
38210
|
const configCommandResult = await maybeRunConfigCommand(argv);
|
|
37119
38211
|
if (configCommandResult !== void 0) return configCommandResult;
|
|
@@ -37129,28 +38221,9 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
37129
38221
|
process.stderr.write("demian TUI requires an interactive terminal. Use demian-plain for pipes or non-interactive use.\n");
|
|
37130
38222
|
return 1;
|
|
37131
38223
|
}
|
|
37132
|
-
if (!flags.noWizard) await runFirstRunWizard().catch(() => void 0);
|
|
37133
38224
|
try {
|
|
37134
|
-
const
|
|
37135
|
-
|
|
37136
|
-
dynamicImport4("react"),
|
|
37137
|
-
Promise.resolve().then(() => (init_app(), app_exports)),
|
|
37138
|
-
Promise.resolve().then(() => (init_store(), store_exports)),
|
|
37139
|
-
Promise.resolve().then(() => (init_controller(), controller_exports))
|
|
37140
|
-
]);
|
|
37141
|
-
const React2 = ReactModule.default;
|
|
37142
|
-
const store = new TuiStore2();
|
|
37143
|
-
const instance = render(React2.createElement(TuiApp2, { store, version: packageVersion() }));
|
|
37144
|
-
try {
|
|
37145
|
-
const code = await runTuiSession2({ ...flags, conversationManagement: true }, store);
|
|
37146
|
-
setTimeout(() => instance.unmount(), 100);
|
|
37147
|
-
await instance.waitUntilExit();
|
|
37148
|
-
return code;
|
|
37149
|
-
} catch (error) {
|
|
37150
|
-
instance.unmount();
|
|
37151
|
-
await instance.waitUntilExit().catch(() => void 0);
|
|
37152
|
-
throw error;
|
|
37153
|
-
}
|
|
38225
|
+
const engine = resolveTuiEngineName({ cliEngine: flags.engine, env: process.env });
|
|
38226
|
+
return await runTuiEngine(engine, { flags, version: packageVersion() });
|
|
37154
38227
|
} catch (error) {
|
|
37155
38228
|
if (isMissingDependency(error)) {
|
|
37156
38229
|
process.stderr.write("TUI dependencies are not installed. Run `npm install` in demian, or use `demian-plain`.\n");
|
|
@@ -37212,6 +38285,14 @@ function parseArgs(argv) {
|
|
|
37212
38285
|
out.goal = true;
|
|
37213
38286
|
continue;
|
|
37214
38287
|
}
|
|
38288
|
+
if (arg === "--engine") {
|
|
38289
|
+
out.engine = takeValue2(argv, ++i, arg);
|
|
38290
|
+
continue;
|
|
38291
|
+
}
|
|
38292
|
+
if (arg.startsWith("--engine=")) {
|
|
38293
|
+
out.engine = arg.slice("--engine=".length);
|
|
38294
|
+
continue;
|
|
38295
|
+
}
|
|
37215
38296
|
if (arg === "--image") {
|
|
37216
38297
|
out.images.push(takeValue2(argv, ++i, arg));
|
|
37217
38298
|
continue;
|
|
@@ -37290,14 +38371,16 @@ Default:
|
|
|
37290
38371
|
Press Esc then p to select provider, Esc then m to edit model, then Enter to send.
|
|
37291
38372
|
Press Esc then a to select the main agent before sending a message.
|
|
37292
38373
|
Press Esc then o to select the default permission preset: deny, ask, auto, or full.
|
|
38374
|
+
Press Esc then c to configure provider defaults and model profiles.
|
|
38375
|
+
Press Esc then k to open the command palette, or Esc then s to select a saved session.
|
|
37293
38376
|
Press up/down in the message composer to recall previous prompts.
|
|
37294
38377
|
Provider/model selections are remembered in ~/.demian/preferences.json.
|
|
37295
38378
|
After each answer, the TUI returns to standby for the next message.
|
|
37296
38379
|
Use /cowork <task> to explicitly ask Demian to coordinate cowork sub agents in multi-agent mode.
|
|
37297
38380
|
Use /compact to compact current history, /stop to stop active work, and /exit or /quit to close the TUI.
|
|
37298
38381
|
Use /session list, /session new, /session switch <number|id|title>, /session rename <title>,
|
|
37299
|
-
and /session delete
|
|
37300
|
-
Press Esc then q to quit, Esc then s to stop
|
|
38382
|
+
and /session delete [number|id|title] to manage saved CLI conversations.
|
|
38383
|
+
Press Esc then q to quit, Esc then s while running to stop the task, and Esc then u to clear the composer.
|
|
37301
38384
|
Tool approvals show the requested command/path in a permission dialog.
|
|
37302
38385
|
Press y to allow once, a to always allow the grant scope, or n/Enter to deny.
|
|
37303
38386
|
Prompt arguments are still accepted as a compatibility shortcut.
|
|
@@ -37325,6 +38408,8 @@ Flags:
|
|
|
37325
38408
|
--no-wizard skip the first-run config wizard
|
|
37326
38409
|
--goal run the initial prompt as an explicit /goal objective
|
|
37327
38410
|
/ralph-loop --fresh-context is single-agent only
|
|
38411
|
+
--engine <ink|bubbletea>
|
|
38412
|
+
select the TUI renderer; defaults to ink during migration
|
|
37328
38413
|
--config <path> load an additional config file
|
|
37329
38414
|
--help, -h show this help
|
|
37330
38415
|
|