demian-cli 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -3
- package/dist/cli.mjs +146 -3
- package/dist/tui.mjs +3192 -588
- package/dist/vscode-worker.mjs +1600 -439
- package/docs/ko/README.md +44 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,8 @@ Additional locale: [한국어](./docs/ko/README.md)
|
|
|
28
28
|
grants.
|
|
29
29
|
- Project and user configuration through `.demian/config.json` or
|
|
30
30
|
`.demian/config.jsond`.
|
|
31
|
+
- First-run setup plus terminal config screens and `demian config` commands for
|
|
32
|
+
provider defaults, connection fields, and model profiles.
|
|
31
33
|
|
|
32
34
|
## Requirements
|
|
33
35
|
|
|
@@ -68,9 +70,12 @@ npx demian
|
|
|
68
70
|
|
|
69
71
|
In the TUI:
|
|
70
72
|
|
|
71
|
-
- Press `
|
|
72
|
-
- Press `
|
|
73
|
-
- Press `
|
|
73
|
+
- Press `Esc` then `k` to open the command palette.
|
|
74
|
+
- Press `Esc` then `s` to select a saved session.
|
|
75
|
+
- Press `Esc` then `p` to choose a provider.
|
|
76
|
+
- Press `Esc` then `m` to edit the model.
|
|
77
|
+
- Press `Esc` then `a` to choose the main agent.
|
|
78
|
+
- Press `Esc` then `c` to open the config screen for persistent provider/model setup.
|
|
74
79
|
- Type your request and press `Enter`.
|
|
75
80
|
|
|
76
81
|
3. Try a first prompt:
|
|
@@ -105,6 +110,12 @@ Demian loads configuration from user and project locations:
|
|
|
105
110
|
|
|
106
111
|
`.jsond` files support comments and trailing commas.
|
|
107
112
|
|
|
113
|
+
The terminal UI creates `~/.demian/config.json` automatically when no user
|
|
114
|
+
config exists. Press `Esc` then `c` to open the config screen. From there you
|
|
115
|
+
can set the default provider, choose or add a default model profile for a
|
|
116
|
+
provider, edit the selected provider's `baseURL`, `apiKeyEnv`, and auth header,
|
|
117
|
+
and add common provider presets without hand-editing JSON.
|
|
118
|
+
|
|
108
119
|
### OpenAI-Compatible Example
|
|
109
120
|
|
|
110
121
|
Create `~/.demian/config.jsond`:
|
|
@@ -272,12 +283,42 @@ demian-plain "Give me a release checklist"
|
|
|
272
283
|
|
|
273
284
|
Inside the TUI:
|
|
274
285
|
|
|
286
|
+
- `Esc` then `k`: open the command palette.
|
|
287
|
+
- `Esc` then `s`: select a saved session.
|
|
288
|
+
- `Esc` then `p`: choose a provider.
|
|
289
|
+
- `Esc` then `m`: edit the model.
|
|
290
|
+
- `Esc` then `a`: choose the main agent.
|
|
291
|
+
- `Esc` then `o`: choose the default permission preset.
|
|
292
|
+
- `Esc` then `c`: configure provider defaults and model profiles.
|
|
293
|
+
- `Esc` then `u`: clear the composer.
|
|
294
|
+
- `Esc` then `q`: quit.
|
|
275
295
|
- `/compact`: summarize older conversation history.
|
|
296
|
+
- `/session list`: show saved CLI conversations.
|
|
297
|
+
- `/session new [title]`: start a fresh conversation.
|
|
298
|
+
- `/session switch <number|id|title>`: switch to a saved conversation.
|
|
299
|
+
- `/session rename <title>` and `/session delete <number|id|title>`: manage conversations.
|
|
276
300
|
- `/stop`: stop active work.
|
|
277
301
|
- `/exit` or `/quit`: close Demian.
|
|
278
302
|
- `/goal ...`: start a verified long-running objective.
|
|
279
303
|
- `/cowork ...`: explicitly coordinate cowork sub agents.
|
|
280
304
|
|
|
305
|
+
Config and auth helpers:
|
|
306
|
+
|
|
307
|
+
```sh
|
|
308
|
+
demian config setup
|
|
309
|
+
demian config init [--path ~/.demian/config.json] [--provider openai]
|
|
310
|
+
demian config list
|
|
311
|
+
demian config models <provider> [--refresh]
|
|
312
|
+
demian config add-provider <preset|openai-compatible> [--name <name>]
|
|
313
|
+
demian config add-model <provider> --name <name> --model <model>
|
|
314
|
+
demian config set-default-model <provider> [--profile <name> | --model <model>]
|
|
315
|
+
demian config update-provider <provider> [--base-url <url>] [--api-key-env <env>]
|
|
316
|
+
demian config path
|
|
317
|
+
demian auth login codex
|
|
318
|
+
demian auth login claudecode
|
|
319
|
+
demian auth status
|
|
320
|
+
```
|
|
321
|
+
|
|
281
322
|
## Tools and Permissions
|
|
282
323
|
|
|
283
324
|
Demian can use local tools for code work:
|
|
@@ -338,6 +379,8 @@ Web search asks for permission because it may call paid third-party APIs.
|
|
|
338
379
|
Demian uses both user-level and project-level storage:
|
|
339
380
|
|
|
340
381
|
- `~/.demian/config.json` or `~/.demian/config.jsond`: user config.
|
|
382
|
+
- `~/.demian/conversations.json` and `~/.demian/conversations/`: saved CLI and
|
|
383
|
+
VS Code conversations.
|
|
341
384
|
- `.demian/config.json` or `.demian/config.jsond`: project config.
|
|
342
385
|
- `.demian/preferences.json`: remembered provider/model choice for the current
|
|
343
386
|
workspace.
|
package/dist/cli.mjs
CHANGED
|
@@ -24598,10 +24598,14 @@ async function addModelProfile(options) {
|
|
|
24598
24598
|
const provider = objectValue(providers[options.provider]);
|
|
24599
24599
|
const profiles = Array.isArray(provider.modelProfiles) ? [...provider.modelProfiles] : [];
|
|
24600
24600
|
const displayName = options.displayName ?? options.name;
|
|
24601
|
+
const previousName = normalizeOptionalName(options.previousName);
|
|
24602
|
+
const existingByPreviousName = previousName ? profiles.findIndex((entry) => entry.name === previousName) : -1;
|
|
24601
24603
|
const existingByName = profiles.findIndex((entry) => entry.name === options.name);
|
|
24602
24604
|
const existingByDisplay = profiles.findIndex((entry) => entry.displayName === displayName);
|
|
24603
|
-
|
|
24604
|
-
if (
|
|
24605
|
+
const updateIndex = existingByPreviousName >= 0 ? existingByPreviousName : existingByName;
|
|
24606
|
+
if (existingByName >= 0 && existingByName !== updateIndex) throw new Error(`Profile name "${options.name}" already exists on provider ${options.provider}. Pick a different profile name.`);
|
|
24607
|
+
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.`);
|
|
24608
|
+
if (existingByDisplay >= 0 && existingByDisplay !== updateIndex && !options.force) {
|
|
24605
24609
|
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.`);
|
|
24606
24610
|
}
|
|
24607
24611
|
const next = {
|
|
@@ -24612,7 +24616,7 @@ async function addModelProfile(options) {
|
|
|
24612
24616
|
...options.apiKey !== void 0 || options.apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
|
|
24613
24617
|
...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {}
|
|
24614
24618
|
};
|
|
24615
|
-
if (
|
|
24619
|
+
if (updateIndex >= 0) profiles[updateIndex] = next;
|
|
24616
24620
|
else profiles.push(next);
|
|
24617
24621
|
provider.modelProfiles = profiles;
|
|
24618
24622
|
providers[options.provider] = provider;
|
|
@@ -24622,6 +24626,70 @@ async function addModelProfile(options) {
|
|
|
24622
24626
|
await writeJsonAtomic(filePath, content);
|
|
24623
24627
|
return { path: filePath, created: true, content };
|
|
24624
24628
|
}
|
|
24629
|
+
async function setDefaultModelProfile(options) {
|
|
24630
|
+
const filePath = expandHome2(options.path ?? defaultUserConfigPath());
|
|
24631
|
+
const config = await readConfigObject(filePath);
|
|
24632
|
+
const providers = objectValue(config.providers);
|
|
24633
|
+
const provider = providers[options.provider];
|
|
24634
|
+
if (!provider || typeof provider !== "object" || Array.isArray(provider)) throw new Error(`Provider ${options.provider} does not exist.`);
|
|
24635
|
+
const providerConfig = { ...provider };
|
|
24636
|
+
const profiles = Array.isArray(providerConfig.modelProfiles) ? [...providerConfig.modelProfiles] : [];
|
|
24637
|
+
const requestedName = normalizeOptionalName(options.profileName ?? options.name);
|
|
24638
|
+
const requestedModel = normalizeOptionalName(options.model);
|
|
24639
|
+
const requestedDisplayName = normalizeOptionalName(options.displayName) ?? requestedModel ?? requestedName;
|
|
24640
|
+
let index = profiles.findIndex((entry) => requestedName && entry.name === requestedName);
|
|
24641
|
+
if (index < 0 && requestedModel) index = profiles.findIndex((entry) => entry.model === requestedModel);
|
|
24642
|
+
if (index < 0 && requestedDisplayName) index = profiles.findIndex((entry) => entry.displayName === requestedDisplayName);
|
|
24643
|
+
const profile = index >= 0 ? {
|
|
24644
|
+
...profiles[index],
|
|
24645
|
+
...requestedName ? { name: requestedName } : {},
|
|
24646
|
+
...requestedDisplayName ? { displayName: requestedDisplayName } : {},
|
|
24647
|
+
...requestedModel ? { model: requestedModel } : {},
|
|
24648
|
+
...options.baseURL ? { baseURL: options.baseURL } : {},
|
|
24649
|
+
...options.apiKey !== void 0 || options.apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
|
|
24650
|
+
...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {}
|
|
24651
|
+
} : newModelProfile({
|
|
24652
|
+
name: requestedName,
|
|
24653
|
+
displayName: requestedDisplayName,
|
|
24654
|
+
model: requestedModel ?? requestedName,
|
|
24655
|
+
baseURL: options.baseURL,
|
|
24656
|
+
apiKey: options.apiKey,
|
|
24657
|
+
apiKeyEnv: options.apiKeyEnv
|
|
24658
|
+
});
|
|
24659
|
+
if (!profile.name || typeof profile.name !== "string") throw new Error("Model profile name is required.");
|
|
24660
|
+
if (!profile.model || typeof profile.model !== "string") throw new Error("Model ID is required.");
|
|
24661
|
+
if (index >= 0) profiles.splice(index, 1);
|
|
24662
|
+
profiles.unshift(profile);
|
|
24663
|
+
providerConfig.modelProfiles = profiles;
|
|
24664
|
+
providers[options.provider] = providerConfig;
|
|
24665
|
+
config.providers = providers;
|
|
24666
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
24667
|
+
`;
|
|
24668
|
+
await writeJsonAtomic(filePath, content);
|
|
24669
|
+
return { path: filePath, created: true, content };
|
|
24670
|
+
}
|
|
24671
|
+
async function updateProviderSettings(options) {
|
|
24672
|
+
const filePath = expandHome2(options.path ?? defaultUserConfigPath());
|
|
24673
|
+
const config = await readConfigObject(filePath);
|
|
24674
|
+
const providers = objectValue(config.providers);
|
|
24675
|
+
const provider = providers[options.provider];
|
|
24676
|
+
if (!provider || typeof provider !== "object" || Array.isArray(provider)) throw new Error(`Provider ${options.provider} does not exist.`);
|
|
24677
|
+
const providerConfig = { ...provider };
|
|
24678
|
+
if (options.baseURL !== void 0) setOrDelete(providerConfig, "baseURL", options.baseURL);
|
|
24679
|
+
if (options.apiKey !== void 0) setOrDelete(providerConfig, "apiKey", options.apiKey);
|
|
24680
|
+
if (options.apiKeyEnv !== void 0) setOrDelete(providerConfig, "apiKeyEnv", options.apiKeyEnv);
|
|
24681
|
+
if (options.authHeader !== void 0) {
|
|
24682
|
+
const header = options.authHeader.trim();
|
|
24683
|
+
if (header) providerConfig.auth = { type: "api-key", header };
|
|
24684
|
+
else delete providerConfig.auth;
|
|
24685
|
+
}
|
|
24686
|
+
providers[options.provider] = providerConfig;
|
|
24687
|
+
config.providers = providers;
|
|
24688
|
+
const content = `${JSON.stringify(config, null, 2)}
|
|
24689
|
+
`;
|
|
24690
|
+
await writeJsonAtomic(filePath, content);
|
|
24691
|
+
return { path: filePath, created: true, content };
|
|
24692
|
+
}
|
|
24625
24693
|
async function readConfigObject(filePath) {
|
|
24626
24694
|
if (!await exists(filePath)) await createUserConfig({ path: filePath });
|
|
24627
24695
|
const raw = JSON.parse(await readFile7(filePath, "utf8"));
|
|
@@ -24701,6 +24769,30 @@ function detectDefaultProvider() {
|
|
|
24701
24769
|
function objectValue(value) {
|
|
24702
24770
|
return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
|
|
24703
24771
|
}
|
|
24772
|
+
function normalizeOptionalName(value) {
|
|
24773
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
24774
|
+
}
|
|
24775
|
+
function newModelProfile(options) {
|
|
24776
|
+
const model = options.model?.trim();
|
|
24777
|
+
if (!model) throw new Error("Model ID is required.");
|
|
24778
|
+
const name = options.name?.trim() || modelProfileNameFromModel(model);
|
|
24779
|
+
return {
|
|
24780
|
+
name,
|
|
24781
|
+
displayName: options.displayName?.trim() || name,
|
|
24782
|
+
model,
|
|
24783
|
+
...options.baseURL ? { baseURL: options.baseURL } : {},
|
|
24784
|
+
...options.apiKey !== void 0 || options.apiKeyEnv ? { apiKey: options.apiKey ?? "" } : {},
|
|
24785
|
+
...options.apiKeyEnv ? { apiKeyEnv: options.apiKeyEnv } : {}
|
|
24786
|
+
};
|
|
24787
|
+
}
|
|
24788
|
+
function modelProfileNameFromModel(model) {
|
|
24789
|
+
return model.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "model";
|
|
24790
|
+
}
|
|
24791
|
+
function setOrDelete(target, key, value) {
|
|
24792
|
+
const trimmed = value.trim();
|
|
24793
|
+
if (trimmed) target[key] = trimmed;
|
|
24794
|
+
else delete target[key];
|
|
24795
|
+
}
|
|
24704
24796
|
async function exists(filePath) {
|
|
24705
24797
|
try {
|
|
24706
24798
|
await stat3(filePath);
|
|
@@ -25235,6 +25327,41 @@ async function runConfigCommand(argv) {
|
|
|
25235
25327
|
force
|
|
25236
25328
|
});
|
|
25237
25329
|
process.stdout.write(`Updated config: ${result.path}
|
|
25330
|
+
`);
|
|
25331
|
+
return 0;
|
|
25332
|
+
}
|
|
25333
|
+
if (command === "set-default-model") {
|
|
25334
|
+
const [provider, ...tail2] = rest;
|
|
25335
|
+
if (!provider) throw new Error("config set-default-model requires a provider name.");
|
|
25336
|
+
const flags = parseFlags(tail2);
|
|
25337
|
+
const result = await setDefaultModelProfile({
|
|
25338
|
+
path: stringFlag(flags, "path"),
|
|
25339
|
+
provider,
|
|
25340
|
+
profileName: stringFlag(flags, "profile"),
|
|
25341
|
+
name: stringFlag(flags, "name"),
|
|
25342
|
+
displayName: stringFlag(flags, "display-name"),
|
|
25343
|
+
model: stringFlag(flags, "model"),
|
|
25344
|
+
baseURL: stringFlag(flags, "base-url"),
|
|
25345
|
+
apiKey: await apiKeyFromFlags(flags),
|
|
25346
|
+
apiKeyEnv: stringFlag(flags, "api-key-env")
|
|
25347
|
+
});
|
|
25348
|
+
process.stdout.write(`Updated config: ${result.path}
|
|
25349
|
+
`);
|
|
25350
|
+
return 0;
|
|
25351
|
+
}
|
|
25352
|
+
if (command === "update-provider") {
|
|
25353
|
+
const [provider, ...tail2] = rest;
|
|
25354
|
+
if (!provider) throw new Error("config update-provider requires a provider name.");
|
|
25355
|
+
const flags = parseFlags(tail2);
|
|
25356
|
+
const result = await updateProviderSettings({
|
|
25357
|
+
path: stringFlag(flags, "path"),
|
|
25358
|
+
provider,
|
|
25359
|
+
baseURL: stringFlagOrEmpty(flags, "base-url"),
|
|
25360
|
+
apiKey: await apiKeyFromFlags(flags),
|
|
25361
|
+
apiKeyEnv: stringFlagOrEmpty(flags, "api-key-env"),
|
|
25362
|
+
authHeader: stringFlagOrEmpty(flags, "auth-header")
|
|
25363
|
+
});
|
|
25364
|
+
process.stdout.write(`Updated config: ${result.path}
|
|
25238
25365
|
`);
|
|
25239
25366
|
return 0;
|
|
25240
25367
|
}
|
|
@@ -25332,6 +25459,10 @@ function stringFlag(flags, name) {
|
|
|
25332
25459
|
const value = flags.get(name);
|
|
25333
25460
|
return typeof value === "string" && value.trim() ? value : void 0;
|
|
25334
25461
|
}
|
|
25462
|
+
function stringFlagOrEmpty(flags, name) {
|
|
25463
|
+
const value = flags.get(name);
|
|
25464
|
+
return typeof value === "string" ? value : void 0;
|
|
25465
|
+
}
|
|
25335
25466
|
function requiredStringFlag(flags, name) {
|
|
25336
25467
|
const value = stringFlag(flags, name);
|
|
25337
25468
|
if (!value) throw new Error(`--${name} is required.`);
|
|
@@ -25374,6 +25505,8 @@ Usage:
|
|
|
25374
25505
|
demian config setup interactive multi-step provider wizard
|
|
25375
25506
|
demian config add-provider <preset|openai-compatible> [--name <name>] [--base-url <url>] [--api-key-env <env>] [--api-key-stdin] [--auth-header <header>] [--force]
|
|
25376
25507
|
demian config add-model <provider> --name <name> --model <model> [--display-name <label>] [--base-url <url>] [--api-key-env <env>] [--api-key-stdin] [--force]
|
|
25508
|
+
demian config set-default-model <provider> [--profile <name> | --model <model>] [--display-name <label>]
|
|
25509
|
+
demian config update-provider <provider> [--base-url <url>] [--api-key-env <env>] [--auth-header <header>]
|
|
25377
25510
|
demian config models <provider> [--refresh] [--config <path>]
|
|
25378
25511
|
demian config list [--config <path>]
|
|
25379
25512
|
demian config path
|
|
@@ -31150,6 +31283,9 @@ function providerOptions(config, catalogs = {}) {
|
|
|
31150
31283
|
permissionMode: provider.type === "claudecode" ? provider.permissionMode : void 0,
|
|
31151
31284
|
ga: provider.type === "claudecode" ? claudeCodeGaEnabled(provider) : void 0,
|
|
31152
31285
|
available,
|
|
31286
|
+
baseURL: typeof provider.baseURL === "string" ? provider.baseURL : void 0,
|
|
31287
|
+
apiKeyEnv: typeof provider.apiKeyEnv === "string" ? provider.apiKeyEnv : void 0,
|
|
31288
|
+
auth: sanitizeProviderAuth(provider.auth),
|
|
31153
31289
|
catalog: catalog ? { status: catalog.status, source: catalog.source, message: catalog.message } : void 0
|
|
31154
31290
|
};
|
|
31155
31291
|
}).sort((left, right) => {
|
|
@@ -31224,6 +31360,13 @@ function providerCatalogAvailable(catalog) {
|
|
|
31224
31360
|
function unavailableLabel(value) {
|
|
31225
31361
|
return `(n/a) ${value}`;
|
|
31226
31362
|
}
|
|
31363
|
+
function sanitizeProviderAuth(auth) {
|
|
31364
|
+
if (!auth || typeof auth !== "object" || Array.isArray(auth)) return void 0;
|
|
31365
|
+
const record = auth;
|
|
31366
|
+
const type = typeof record.type === "string" ? record.type : void 0;
|
|
31367
|
+
const header = typeof record.header === "string" ? record.header : void 0;
|
|
31368
|
+
return type || header ? { type, header } : void 0;
|
|
31369
|
+
}
|
|
31227
31370
|
|
|
31228
31371
|
// src/ui/plain/interactive.ts
|
|
31229
31372
|
var PlainInteractivePrompt = class {
|