dgxay-code-cli 1.0.0 → 1.1.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-zh.md +50 -8
- package/README.md +50 -9
- package/package.json +1 -1
- package/src/cli/ansi.js +38 -8
- package/src/cli/approval.js +11 -11
- package/src/cli/editor.js +456 -202
- package/src/cli/i18n.js +276 -0
- package/src/cli/index.js +34 -34
- package/src/cli/print.js +5 -5
- package/src/cli/render.js +147 -76
- package/src/cli/repl.js +205 -141
- package/src/cli/slash.js +29 -28
- package/src/cli/ui.js +8 -8
- package/src/commands/chat.js +45 -45
- package/src/commands/config.js +81 -81
- package/src/commands/context.js +5 -5
- package/src/commands/doctor.js +34 -34
- package/src/commands/export.js +13 -13
- package/src/commands/mcp.js +51 -51
- package/src/commands/model.js +38 -38
- package/src/commands/oauth.js +61 -61
- package/src/commands/permission.js +19 -19
- package/src/commands/provider.js +70 -70
- package/src/commands/run.js +20 -20
- package/src/commands/skill.js +15 -15
- package/src/commands/workspace.js +18 -18
- package/src/core/agent.js +19 -18
- package/src/core/llm.js +10 -10
- package/src/core/mcp.js +5 -5
- package/src/core/oauth.js +36 -36
- package/src/core/prompt.js +46 -46
- package/src/core/sessions.js +7 -7
- package/src/core/tools.js +24 -24
- package/src/services/input.js +1 -1
- package/src/services/settings.js +28 -28
- package/src/services/skills.js +1 -1
- package/src/services/terminal.js +3 -3
package/src/commands/config.js
CHANGED
|
@@ -28,19 +28,19 @@ const PRESETS = [
|
|
|
28
28
|
{ name: 'DeepSeek', protocol: 'openai', baseUrl: 'https://api.deepseek.com/v1', model: 'deepseek-chat' },
|
|
29
29
|
{ name: 'Claude', protocol: 'anthropic', baseUrl: 'https://api.anthropic.com', model: 'claude-sonnet-4-5' },
|
|
30
30
|
{ name: 'OpenAI Responses', protocol: 'responses', baseUrl: 'https://api.openai.com/v1', model: 'gpt-5' },
|
|
31
|
-
{ name: '
|
|
31
|
+
{ name: 'Qwen (DashScope)', protocol: 'openai', baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', model: 'qwen-plus' },
|
|
32
32
|
{ name: 'Moonshot', protocol: 'openai', baseUrl: 'https://api.moonshot.cn/v1', model: 'moonshot-v1-8k' },
|
|
33
|
-
{ name: 'Ollama
|
|
33
|
+
{ name: 'Ollama (local)', protocol: 'openai', baseUrl: 'http://localhost:11434/v1', model: 'llama3.1' }
|
|
34
34
|
];
|
|
35
35
|
|
|
36
36
|
const options = [
|
|
37
37
|
{ name: 'protocol', type: 'string', valueName: 'p', desc: 'openai / anthropic / responses' },
|
|
38
|
-
{ name: 'name', type: 'string', valueName: 'text', desc: '
|
|
39
|
-
{ name: 'base-url', type: 'string', valueName: 'url', desc: '
|
|
40
|
-
{ name: 'api-key', type: 'string', valueName: 'key', desc: 'API Key
|
|
41
|
-
{ name: 'model', type: 'list', valueName: 'id', desc: '
|
|
42
|
-
{ name: 'oauth-provider', type: 'string', valueName: 'id', desc: '
|
|
43
|
-
{ name: 'force', alias: 'f', type: 'boolean', desc: '
|
|
38
|
+
{ name: 'name', type: 'string', valueName: 'text', desc: 'Provider name' },
|
|
39
|
+
{ name: 'base-url', type: 'string', valueName: 'url', desc: 'Base URL' },
|
|
40
|
+
{ name: 'api-key', type: 'string', valueName: 'key', desc: 'API Key (DGXAY_API_KEY env var also works)' },
|
|
41
|
+
{ name: 'model', type: 'list', valueName: 'id', desc: 'Model ID; repeatable or comma-separated' },
|
|
42
|
+
{ name: 'oauth-provider', type: 'string', valueName: 'id', desc: 'Use a connected OAuth account instead: openai/anthropic/xai/google' },
|
|
43
|
+
{ name: 'force', alias: 'f', type: 'boolean', desc: 'Overwrite even when config already exists' }
|
|
44
44
|
];
|
|
45
45
|
|
|
46
46
|
/* ------------------------------ init ------------------------------ */
|
|
@@ -49,13 +49,13 @@ async function promptProtocol(config) {
|
|
|
49
49
|
const interactive = process.stdin.isTTY;
|
|
50
50
|
if (!interactive) return 'openai';
|
|
51
51
|
print.blank();
|
|
52
|
-
print.heading('
|
|
52
|
+
print.heading('API protocol');
|
|
53
53
|
const list = ['openai', 'anthropic', 'responses'];
|
|
54
54
|
list.forEach((key, index) => {
|
|
55
55
|
const meta = settings.PROTOCOL_META[key];
|
|
56
56
|
print.write(` ${COLORS.accent(String(index + 1))}. ${meta.full} ${COLORS.faint(meta.path)}`);
|
|
57
57
|
});
|
|
58
|
-
const answer = await ask(
|
|
58
|
+
const answer = await ask(`Choose [1-3, default 1]: `);
|
|
59
59
|
const index = Number(String(answer || '').trim());
|
|
60
60
|
if (Number.isInteger(index) && index >= 1 && index <= 3) return list[index - 1];
|
|
61
61
|
return 'openai';
|
|
@@ -65,13 +65,13 @@ async function promptPreset(protocol) {
|
|
|
65
65
|
const interactive = process.stdin.isTTY;
|
|
66
66
|
if (!interactive) return null;
|
|
67
67
|
print.blank();
|
|
68
|
-
print.heading('
|
|
68
|
+
print.heading('Quick presets');
|
|
69
69
|
const matches = PRESETS.filter((preset) => preset.protocol === protocol);
|
|
70
70
|
matches.forEach((preset, index) => {
|
|
71
71
|
print.write(` ${COLORS.accent(String(index + 1))}. ${preset.name} ${COLORS.faint(preset.baseUrl)}`);
|
|
72
72
|
});
|
|
73
|
-
print.write(` ${COLORS.faint('
|
|
74
|
-
const answer = await ask('
|
|
73
|
+
print.write(` ${COLORS.faint('Press Enter to skip and fill in manually')}`);
|
|
74
|
+
const answer = await ask('Choose a preset: ');
|
|
75
75
|
const index = Number(String(answer || '').trim());
|
|
76
76
|
if (Number.isInteger(index) && index >= 1 && index <= matches.length) return matches[index - 1];
|
|
77
77
|
return null;
|
|
@@ -80,8 +80,8 @@ async function promptPreset(protocol) {
|
|
|
80
80
|
async function runInit(opts, runtime) {
|
|
81
81
|
const config = settings.readConfig();
|
|
82
82
|
if (config.providers?.length && !opts.force) {
|
|
83
|
-
print.warn(
|
|
84
|
-
print.hint('
|
|
83
|
+
print.warn(`${config.providers.length} provider(s) already configured. Use dgxay provider add to append a new provider`);
|
|
84
|
+
print.hint('To re-initialize the first provider, add --force');
|
|
85
85
|
return 1;
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -95,9 +95,9 @@ async function runInit(opts, runtime) {
|
|
|
95
95
|
|
|
96
96
|
if (interactive) {
|
|
97
97
|
print.write('');
|
|
98
|
-
print.write(`${COLORS.bold('DgxayCode
|
|
99
|
-
print.write(COLORS.faint('
|
|
100
|
-
print.write(COLORS.faint(
|
|
98
|
+
print.write(`${COLORS.bold('DgxayCode first-time setup')} ${COLORS.faint(`v${VERSION}`)}`);
|
|
99
|
+
print.write(COLORS.faint('Config is written to the local data directory; the API key stays on this machine and is never uploaded.'));
|
|
100
|
+
print.write(COLORS.faint(`Data directory: ${cfg.DCB_DIR}`));
|
|
101
101
|
|
|
102
102
|
protocol = protocol || (await promptProtocol(config));
|
|
103
103
|
const preset = await promptPreset(protocol);
|
|
@@ -105,46 +105,46 @@ async function runInit(opts, runtime) {
|
|
|
105
105
|
name = name || preset.name;
|
|
106
106
|
baseUrl = baseUrl || preset.baseUrl;
|
|
107
107
|
models = models.length ? models : [preset.model];
|
|
108
|
-
print.write(COLORS.faint(
|
|
108
|
+
print.write(COLORS.faint(`Selected ${preset.name}, Base URL pre-filled with ${preset.baseUrl}`));
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
if (!name) {
|
|
112
|
-
const answer = await ask(
|
|
113
|
-
name = String(answer || '').trim() || preset?.name || '
|
|
112
|
+
const answer = await ask(`Provider name [${preset?.name || 'My provider'}]: `);
|
|
113
|
+
name = String(answer || '').trim() || preset?.name || 'My provider';
|
|
114
114
|
}
|
|
115
115
|
if (!baseUrl) {
|
|
116
116
|
const answer = await ask(`Base URL [${settings.PROTOCOL_META[protocol].basePlaceholder}]: `);
|
|
117
117
|
baseUrl = String(answer || '').trim() || settings.PROTOCOL_META[protocol].basePlaceholder;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
const useOauth = await confirm('
|
|
120
|
+
const useOauth = await confirm('Use a connected OAuth account (instead of an API Key)?', false);
|
|
121
121
|
if (useOauth) {
|
|
122
122
|
const connected = oauth.listProviders().filter((item) => item.connection && !item.connection.expired);
|
|
123
123
|
if (!connected.length) {
|
|
124
|
-
print.warn('
|
|
124
|
+
print.warn('No accounts available yet; continue with an API Key. You can run dgxay oauth connect <platform> later');
|
|
125
125
|
} else {
|
|
126
126
|
connected.forEach((item, index) =>
|
|
127
127
|
print.write(` ${COLORS.accent(String(index + 1))}. ${item.name} ${COLORS.faint(item.connection.account)}`)
|
|
128
128
|
);
|
|
129
|
-
const answer = await ask('
|
|
129
|
+
const answer = await ask('Choose an account [1]: ');
|
|
130
130
|
const index = Number(String(answer || '').trim()) || 1;
|
|
131
131
|
oauthProvider = connected[index - 1]?.id || '';
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
if (!oauthProvider && !apiKey) {
|
|
136
|
-
const secret = await askSecret('API Key
|
|
136
|
+
const secret = await askSecret('API Key (may be empty for local models): ');
|
|
137
137
|
apiKey = secret !== null ? secret : '';
|
|
138
138
|
}
|
|
139
139
|
if (!models.length) {
|
|
140
|
-
const answer = await ask(
|
|
140
|
+
const answer = await ask(`Model ID [${settings.PROTOCOL_META[protocol].modelPlaceholder}]: `);
|
|
141
141
|
models = String(answer || '')
|
|
142
142
|
.split(/[,,\s]+/)
|
|
143
143
|
.map((item) => item.trim())
|
|
144
144
|
.filter(Boolean);
|
|
145
145
|
}
|
|
146
146
|
if (!models.length) {
|
|
147
|
-
print.fail('
|
|
147
|
+
print.fail('Model ID cannot be empty');
|
|
148
148
|
return 1;
|
|
149
149
|
}
|
|
150
150
|
}
|
|
@@ -152,10 +152,10 @@ async function runInit(opts, runtime) {
|
|
|
152
152
|
if (!protocol) protocol = 'openai';
|
|
153
153
|
if (!baseUrl) baseUrl = settings.PROTOCOL_META[protocol].basePlaceholder;
|
|
154
154
|
if (!models.length) {
|
|
155
|
-
throw Object.assign(new Error('
|
|
155
|
+
throw Object.assign(new Error('Missing --model, e.g. --model deepseek-chat'), { userError: true });
|
|
156
156
|
}
|
|
157
157
|
if (!apiKey && !oauthProvider) {
|
|
158
|
-
print.warn('
|
|
158
|
+
print.warn('No API Key provided: local models (Ollama, etc.) work without one, cloud endpoints return 401');
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
const result = settings.upsertProvider({
|
|
@@ -170,16 +170,16 @@ async function runInit(opts, runtime) {
|
|
|
170
170
|
settings.saveConfig({ onboarded: true });
|
|
171
171
|
|
|
172
172
|
print.blank();
|
|
173
|
-
print.ok(
|
|
173
|
+
print.ok(`Provider saved: '${result.provider.name}'(${settings.PROTOCOL_META[result.provider.protocol].full})`);
|
|
174
174
|
print.keyValue([
|
|
175
175
|
{ label: 'Base URL', value: result.provider.baseUrl },
|
|
176
|
-
{ label: '
|
|
177
|
-
{ label: '
|
|
178
|
-
{ label: '
|
|
179
|
-
{ label: '
|
|
176
|
+
{ label: 'Models', value: result.provider.models.map((m) => m.id).join(', ') },
|
|
177
|
+
{ label: 'Review model', value: result.provider.reviewModelId || 'not configured' },
|
|
178
|
+
{ label: 'Auth', value: oauthProvider ? `OAuth · ${oauthProvider}` : apiKey ? 'API Key' : 'none (local model)' },
|
|
179
|
+
{ label: 'Config file', value: settings.homeRelative(cfg.CONFIG_PATH) }
|
|
180
180
|
]);
|
|
181
181
|
print.blank();
|
|
182
|
-
print.hint('
|
|
182
|
+
print.hint('Next: run dgxay doctor to check connectivity, then run dgxay to start chatting');
|
|
183
183
|
void interactive;
|
|
184
184
|
void runtime;
|
|
185
185
|
return 0;
|
|
@@ -191,32 +191,32 @@ function runShow() {
|
|
|
191
191
|
const config = settings.readConfig();
|
|
192
192
|
const provider = settings.activeProvider(config);
|
|
193
193
|
const model = settings.activeModel(config);
|
|
194
|
-
print.heading('
|
|
194
|
+
print.heading('Current config');
|
|
195
195
|
print.keyValue(
|
|
196
196
|
[
|
|
197
|
-
{ label: '
|
|
198
|
-
{ label: '
|
|
199
|
-
{ label: '
|
|
197
|
+
{ label: 'Data directory', value: settings.homeRelative(cfg.DCB_DIR) },
|
|
198
|
+
{ label: 'Config file', value: settings.homeRelative(cfg.CONFIG_PATH) },
|
|
199
|
+
{ label: 'Mode', value: config.ui?.mode === 'work' ? 'work mode' : 'chat mode' },
|
|
200
200
|
{
|
|
201
|
-
label: '
|
|
201
|
+
label: 'Permission',
|
|
202
202
|
value: `${settings.PERMISSIONS[settings.currentPermission(config)].label}${
|
|
203
|
-
config.ui?.permission === 'auto' && !settings.hasReviewModel(config) ? '
|
|
203
|
+
config.ui?.permission === 'auto' && !settings.hasReviewModel(config) ? ' (review model missing, fell back to normal permission)' : ''
|
|
204
204
|
}`
|
|
205
205
|
},
|
|
206
|
-
{ label: '
|
|
207
|
-
{ label: '
|
|
208
|
-
{ label: '
|
|
209
|
-
{ label: '
|
|
210
|
-
{ label: '
|
|
211
|
-
{ label: '
|
|
212
|
-
{ label: '
|
|
206
|
+
{ label: 'Workspace', value: config.work?.workspace || '(not selected)' },
|
|
207
|
+
{ label: 'Active provider', value: provider ? `${provider.name} (${provider.id})` : 'not configured' },
|
|
208
|
+
{ label: 'Active model', value: model ? `${model.name}${model.id !== model.name ? ` (${model.id})` : ''}` : 'not configured' },
|
|
209
|
+
{ label: 'Streaming', value: config.chat?.stream === false ? 'off' : 'on' },
|
|
210
|
+
{ label: 'Temperature', value: String(config.chat?.temperature ?? 0.7) },
|
|
211
|
+
{ label: 'Max output', value: String(config.chat?.maxTokens ?? 4096) },
|
|
212
|
+
{ label: 'Custom instructions', value: config.chat?.systemPrompt ? 'set' : 'not set' }
|
|
213
213
|
],
|
|
214
214
|
{}
|
|
215
215
|
);
|
|
216
216
|
print.blank();
|
|
217
|
-
print.heading('
|
|
217
|
+
print.heading('Providers');
|
|
218
218
|
if (!config.providers?.length) {
|
|
219
|
-
print.hint('
|
|
219
|
+
print.hint(' No providers yet, run dgxay config init');
|
|
220
220
|
return 0;
|
|
221
221
|
}
|
|
222
222
|
const rows = config.providers.map((item, index) => [
|
|
@@ -224,24 +224,24 @@ function runShow() {
|
|
|
224
224
|
item.name,
|
|
225
225
|
settings.PROTOCOL_META[item.protocol || 'openai'].short,
|
|
226
226
|
item.baseUrl,
|
|
227
|
-
item.authMode === 'oauth' ? `OAuth(${item.oauthProvider || '?'})` : item.apiKey ? cfg.maskKey(item.apiKey) : '
|
|
227
|
+
item.authMode === 'oauth' ? `OAuth(${item.oauthProvider || '?'})` : item.apiKey ? cfg.maskKey(item.apiKey) : 'none',
|
|
228
228
|
(item.models || []).length,
|
|
229
229
|
item.reviewModelId || '-'
|
|
230
230
|
]);
|
|
231
231
|
print.table(
|
|
232
232
|
[
|
|
233
233
|
{ title: '#', max: 3 },
|
|
234
|
-
{ title: '
|
|
235
|
-
{ title: '
|
|
234
|
+
{ title: 'Name', max: 18 },
|
|
235
|
+
{ title: 'Protocol', max: 16 },
|
|
236
236
|
{ title: 'Base URL', max: 40 },
|
|
237
|
-
{ title: '
|
|
238
|
-
{ title: '
|
|
239
|
-
{ title: '
|
|
237
|
+
{ title: 'Auth', max: 16 },
|
|
238
|
+
{ title: 'Models', max: 5 },
|
|
239
|
+
{ title: 'Review model', max: 20 }
|
|
240
240
|
],
|
|
241
241
|
rows
|
|
242
242
|
);
|
|
243
243
|
print.blank();
|
|
244
|
-
print.hint('
|
|
244
|
+
print.hint('Add/modify providers: dgxay provider --help');
|
|
245
245
|
return 0;
|
|
246
246
|
}
|
|
247
247
|
|
|
@@ -265,10 +265,10 @@ async function run({ argv, runtime }) {
|
|
|
265
265
|
if (action === 'show' || action === 'list' || !action) return runShow();
|
|
266
266
|
|
|
267
267
|
if (action === 'get') {
|
|
268
|
-
if (!rest[0]) throw Object.assign(new Error('
|
|
268
|
+
if (!rest[0]) throw Object.assign(new Error('Specify a config key, e.g. dgxay config get ui.permission'), { userError: true });
|
|
269
269
|
const value = getPath(settings.readConfig(), rest[0]);
|
|
270
270
|
if (value === undefined) {
|
|
271
|
-
print.fail(
|
|
271
|
+
print.fail(`No such config key: ${rest[0]}`);
|
|
272
272
|
return 1;
|
|
273
273
|
}
|
|
274
274
|
process.stdout.write(`${typeof value === 'string' ? value : JSON.stringify(value)}\n`);
|
|
@@ -279,22 +279,22 @@ async function run({ argv, runtime }) {
|
|
|
279
279
|
const key = rest[0];
|
|
280
280
|
const raw = rest.slice(1).join(' ');
|
|
281
281
|
if (!key || !raw) {
|
|
282
|
-
throw Object.assign(new Error('
|
|
282
|
+
throw Object.assign(new Error('Usage: dgxay config set <key> <value>, e.g. dgxay config set chat.temperature 0.3'), {
|
|
283
283
|
userError: true
|
|
284
284
|
});
|
|
285
285
|
}
|
|
286
286
|
const allowed = ['ui.permission', 'ui.mode', 'chat.temperature', 'chat.maxTokens', 'chat.stream', 'chat.systemPrompt'];
|
|
287
287
|
if (!allowed.includes(key)) {
|
|
288
|
-
throw Object.assign(new Error(
|
|
288
|
+
throw Object.assign(new Error(`Cannot modify ${key}. Editable keys: ${allowed.join(', ')}`), { userError: true });
|
|
289
289
|
}
|
|
290
290
|
let value = raw;
|
|
291
291
|
if (key === 'chat.temperature' || key === 'chat.maxTokens') value = Number(raw);
|
|
292
292
|
if (!Number.isFinite(value) && typeof value === 'number') {
|
|
293
|
-
throw Object.assign(new Error('
|
|
293
|
+
throw Object.assign(new Error('A number is required'), { userError: true });
|
|
294
294
|
}
|
|
295
295
|
if (key === 'chat.stream') value = !/^(false|0|no|off)$/i.test(raw);
|
|
296
296
|
if (key === 'ui.permission' && !settings.PERMISSIONS[raw]) {
|
|
297
|
-
throw Object.assign(new Error('
|
|
297
|
+
throw Object.assign(new Error('Permission must be one of none / normal / auto / full'), { userError: true });
|
|
298
298
|
}
|
|
299
299
|
const [section, name] = key.split('.');
|
|
300
300
|
settings.saveConfig({ [section]: { [name]: value } });
|
|
@@ -305,12 +305,12 @@ async function run({ argv, runtime }) {
|
|
|
305
305
|
if (action === 'path') {
|
|
306
306
|
const paths = settings.paths();
|
|
307
307
|
print.keyValue([
|
|
308
|
-
{ label: '
|
|
309
|
-
{ label: '
|
|
310
|
-
{ label: '
|
|
308
|
+
{ label: 'Data directory', value: paths.dataDir },
|
|
309
|
+
{ label: 'Config file', value: paths.config },
|
|
310
|
+
{ label: 'Conversations', value: paths.chats },
|
|
311
311
|
{ label: 'OAuth', value: paths.oauth },
|
|
312
312
|
{ label: 'MCP', value: paths.mcp },
|
|
313
|
-
{ label: '
|
|
313
|
+
{ label: 'Skills directory', value: paths.skills }
|
|
314
314
|
]);
|
|
315
315
|
return 0;
|
|
316
316
|
}
|
|
@@ -318,9 +318,9 @@ async function run({ argv, runtime }) {
|
|
|
318
318
|
if (action === 'reset') {
|
|
319
319
|
const yes =
|
|
320
320
|
argv.includes('--yes') ||
|
|
321
|
-
(await confirm('
|
|
321
|
+
(await confirm('This resets all config (providers, keys, preferences) and clears conversation history. Continue?', false));
|
|
322
322
|
if (!yes) {
|
|
323
|
-
print.hint('
|
|
323
|
+
print.hint('Cancelled');
|
|
324
324
|
return 1;
|
|
325
325
|
}
|
|
326
326
|
const backup = `${cfg.CONFIG_PATH}.bak-${Date.now()}`;
|
|
@@ -331,30 +331,30 @@ async function run({ argv, runtime }) {
|
|
|
331
331
|
}
|
|
332
332
|
cfg.resetConfig();
|
|
333
333
|
cfg.saveChats([]);
|
|
334
|
-
print.ok('
|
|
335
|
-
print.hint('oauth.json
|
|
336
|
-
if (fs.existsSync(backup)) print.hint(
|
|
334
|
+
print.ok('Config reset');
|
|
335
|
+
print.hint('oauth.json, mcp.json and skills/ are not deleted; remove the data directory manually for a full clean-up');
|
|
336
|
+
if (fs.existsSync(backup)) print.hint(`Previous config backed up to ${path.basename(backup)}`);
|
|
337
337
|
return 0;
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
-
print.fail(
|
|
341
|
-
print.hint('
|
|
340
|
+
print.fail(`Unknown config subcommand: ${action}`);
|
|
341
|
+
print.hint('Available: init / show / get / set / path / reset');
|
|
342
342
|
return 1;
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
module.exports = {
|
|
346
346
|
name: 'config',
|
|
347
347
|
aliases: ['cfg'],
|
|
348
|
-
summary: '
|
|
348
|
+
summary: 'Config: initialize, view, modify data directory',
|
|
349
349
|
usage: 'dgxay config <init|show|get|set|path|reset>',
|
|
350
350
|
options,
|
|
351
351
|
subcommands: [
|
|
352
|
-
['init', '
|
|
353
|
-
['show', '
|
|
354
|
-
['get
|
|
355
|
-
['set
|
|
356
|
-
['path', '
|
|
357
|
-
['reset', '
|
|
352
|
+
['init', 'Initial config of provider and model'],
|
|
353
|
+
['show', 'View current config (keys masked)'],
|
|
354
|
+
['get <key>', 'Read a single config key'],
|
|
355
|
+
['set <key> <value>', 'Set a single config key'],
|
|
356
|
+
['path', 'Show data directory and file paths'],
|
|
357
|
+
['reset', 'Reset config (keeps OAuth / MCP / skills)']
|
|
358
358
|
],
|
|
359
359
|
examples: ['dgxay config init', 'dgxay config show', 'dgxay config set chat.temperature 0.3', 'dgxay config path'],
|
|
360
360
|
run,
|
package/src/commands/context.js
CHANGED
|
@@ -19,15 +19,15 @@ async function run({ argv }) {
|
|
|
19
19
|
const model = settings.activeModel(config);
|
|
20
20
|
const chat = ref ? sessions.resolveChat(ref) : sessions.listChats()[0] || null;
|
|
21
21
|
if (ref && !chat) {
|
|
22
|
-
print.fail(
|
|
22
|
+
print.fail(`Conversation not found: ${ref}`);
|
|
23
23
|
return 1;
|
|
24
24
|
}
|
|
25
25
|
if (!chat) {
|
|
26
|
-
print.hint('
|
|
26
|
+
print.hint('No conversation history yet');
|
|
27
27
|
return 0;
|
|
28
28
|
}
|
|
29
29
|
process.stdout.write(`\n${contextReport(contextInfo(chat, model))}\n`);
|
|
30
|
-
print.hint(
|
|
30
|
+
print.hint(`Conversation: ${chat.title} (${chat.id})`);
|
|
31
31
|
print.blank();
|
|
32
32
|
return 0;
|
|
33
33
|
}
|
|
@@ -35,8 +35,8 @@ async function run({ argv }) {
|
|
|
35
35
|
module.exports = {
|
|
36
36
|
name: 'context',
|
|
37
37
|
aliases: ['ctx'],
|
|
38
|
-
summary: '
|
|
39
|
-
usage: 'dgxay context [
|
|
38
|
+
summary: 'Show the estimated context usage',
|
|
39
|
+
usage: 'dgxay context [n|id]',
|
|
40
40
|
options,
|
|
41
41
|
examples: ['dgxay context', 'dgxay context 1'],
|
|
42
42
|
run
|
package/src/commands/doctor.js
CHANGED
|
@@ -19,8 +19,8 @@ const { resolveProviderAuth } = require('../core/prompt');
|
|
|
19
19
|
const { VERSION } = require('../services/version');
|
|
20
20
|
|
|
21
21
|
const options = [
|
|
22
|
-
{ name: 'offline', type: 'boolean', desc: '
|
|
23
|
-
{ name: 'timeout', type: 'number', valueName: 'ms', desc: '
|
|
22
|
+
{ name: 'offline', type: 'boolean', desc: 'Skip network connectivity test' },
|
|
23
|
+
{ name: 'timeout', type: 'number', valueName: 'ms', desc: 'Connectivity test timeout, default 12000 ms' }
|
|
24
24
|
];
|
|
25
25
|
|
|
26
26
|
function line(status, label, detail = '') {
|
|
@@ -47,7 +47,7 @@ async function checkConnectivity(provider, modelId, timeout) {
|
|
|
47
47
|
apiKey: auth.apiKey,
|
|
48
48
|
authType: auth.authType,
|
|
49
49
|
model: modelId,
|
|
50
|
-
messages: [{ role: 'user', content: '
|
|
50
|
+
messages: [{ role: 'user', content: 'Reply with just OK.' }],
|
|
51
51
|
stream: false,
|
|
52
52
|
temperature: 0,
|
|
53
53
|
maxTokens: 16,
|
|
@@ -67,77 +67,77 @@ async function run({ argv }) {
|
|
|
67
67
|
const timeout = Number(opts.timeout) > 0 ? Number(opts.timeout) : 12000;
|
|
68
68
|
|
|
69
69
|
print.write('');
|
|
70
|
-
print.heading(`DgxayCode CLI
|
|
70
|
+
print.heading(`DgxayCode CLI self-check v${VERSION}`);
|
|
71
71
|
print.write('');
|
|
72
72
|
|
|
73
73
|
const major = Number(process.versions.node.split('.')[0]);
|
|
74
|
-
line(major >= 20 ? 'ok' : major >= 18 ? 'warn' : 'fail', `Node.js ${process.versions.node}`, major >= 18 ? '' : '
|
|
75
|
-
line('ok',
|
|
76
|
-
if (typeof fetch !== 'function') line('fail', '
|
|
74
|
+
line(major >= 20 ? 'ok' : major >= 18 ? 'warn' : 'fail', `Node.js ${process.versions.node}`, major >= 18 ? '' : 'requires 18+');
|
|
75
|
+
line('ok', `Platform ${process.platform} ${process.arch}`, `Home directory ${os.homedir()}`);
|
|
76
|
+
if (typeof fetch !== 'function') line('fail', 'Global fetch unavailable', 'requires Node.js 18+');
|
|
77
77
|
|
|
78
78
|
// 数据目录
|
|
79
79
|
const dir = cfg.DCB_DIR;
|
|
80
80
|
try {
|
|
81
81
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
82
82
|
fs.accessSync(dir, fs.constants.W_OK);
|
|
83
|
-
line('ok', '
|
|
83
|
+
line('ok', 'Data directory writable', settings.homeRelative(dir));
|
|
84
84
|
} catch (err) {
|
|
85
|
-
line('fail', '
|
|
85
|
+
line('fail', 'Data directory not writable', `${dir} · ${err.message}`);
|
|
86
86
|
}
|
|
87
|
-
if (fs.existsSync(cfg.CONFIG_PATH)) line('ok', '
|
|
88
|
-
else line('warn', '
|
|
87
|
+
if (fs.existsSync(cfg.CONFIG_PATH)) line('ok', 'config.json present', settings.homeRelative(cfg.CONFIG_PATH));
|
|
88
|
+
else line('warn', 'No config.json yet', 'run dgxay config init');
|
|
89
89
|
|
|
90
90
|
const config = settings.readConfig();
|
|
91
91
|
const mode = config.ui?.mode === 'work' ? 'work' : 'chat';
|
|
92
92
|
const permission = settings.currentPermission(config);
|
|
93
|
-
line('ok',
|
|
93
|
+
line('ok', `Mode / Permission`, `${mode === 'work' ? 'work mode' : 'chat mode'} · ${settings.PERMISSIONS[permission].label}`);
|
|
94
94
|
if (config.ui?.permission === 'auto' && !settings.hasReviewModel(config)) {
|
|
95
|
-
line('warn', '
|
|
95
|
+
line('warn', 'Auto review fell back', 'the current/default provider has no valid review model');
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
const workspace = config.work?.workspace || '';
|
|
99
99
|
if (workspace) {
|
|
100
100
|
try {
|
|
101
101
|
const isDir = fs.statSync(workspace).isDirectory();
|
|
102
|
-
line(isDir ? 'ok' : 'fail', '
|
|
102
|
+
line(isDir ? 'ok' : 'fail', 'Workspace', workspace);
|
|
103
103
|
} catch {
|
|
104
|
-
line('warn', '
|
|
104
|
+
line('warn', 'Workspace no longer exists', workspace);
|
|
105
105
|
}
|
|
106
106
|
} else {
|
|
107
|
-
line('warn', '
|
|
107
|
+
line('warn', 'No workspace selected', 'work mode cannot read or write files; set one with /cd or dgxay workspace set');
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
// 供应商
|
|
111
111
|
print.write('');
|
|
112
|
-
print.heading('
|
|
112
|
+
print.heading('Providers');
|
|
113
113
|
const providers = config.providers || [];
|
|
114
114
|
if (!providers.length) {
|
|
115
|
-
line('fail', '
|
|
115
|
+
line('fail', 'No providers', 'run dgxay config init');
|
|
116
116
|
}
|
|
117
117
|
for (const provider of providers) {
|
|
118
118
|
const models = provider.models || [];
|
|
119
119
|
const reviewOk = models.some((m) => m.id === provider.reviewModelId);
|
|
120
|
-
line('ok', provider.name, `${provider.protocol || 'openai'} · ${provider.baseUrl} · ${models.length}
|
|
121
|
-
if (!reviewOk) line('warn', ` ${provider.name}
|
|
120
|
+
line('ok', provider.name, `${provider.protocol || 'openai'} · ${provider.baseUrl} · ${models.length} models`);
|
|
121
|
+
if (!reviewOk) line('warn', ` ${provider.name} invalid review model`, provider.reviewModelId || 'not configured');
|
|
122
122
|
if (provider.authMode === 'oauth') {
|
|
123
123
|
const account = oauth.listProviders().find((item) => item.id === provider.oauthProvider);
|
|
124
124
|
if (account?.connection && !account.connection.expired) {
|
|
125
|
-
line('ok', ` OAuth
|
|
125
|
+
line('ok', ` OAuth account`, `${provider.oauthProvider} · ${account.connection.account}`);
|
|
126
126
|
} else {
|
|
127
|
-
line('fail', ` OAuth
|
|
127
|
+
line('fail', ` OAuth account unavailable`, `dgxay oauth connect ${provider.oauthProvider || '<provider>'}`);
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
if (!opts.offline && providers.length) {
|
|
133
133
|
print.write('');
|
|
134
|
-
print.heading('
|
|
134
|
+
print.heading('Connectivity test');
|
|
135
135
|
const active = settings.activeProvider(config);
|
|
136
136
|
const model = settings.activeModel(config);
|
|
137
137
|
if (!active || !model) {
|
|
138
|
-
line('warn', '
|
|
138
|
+
line('warn', 'Skipped: no current model');
|
|
139
139
|
} else {
|
|
140
|
-
process.stdout.write(` ${COLORS.faint(`${SIGN.run}
|
|
140
|
+
process.stdout.write(` ${COLORS.faint(`${SIGN.run} Requesting ${active.name} / ${model.id}…`)}`);
|
|
141
141
|
const result = await checkConnectivity(active, model.id, timeout);
|
|
142
142
|
process.stdout.write('\r\x1b[2K');
|
|
143
143
|
if (result.ok) line('ok', `${active.name} / ${model.id}`, `${result.ms}ms${result.text ? ` · ${result.text}` : ''}`);
|
|
@@ -147,33 +147,33 @@ async function run({ argv }) {
|
|
|
147
147
|
|
|
148
148
|
// OAuth / MCP / 技能
|
|
149
149
|
print.write('');
|
|
150
|
-
print.heading('
|
|
150
|
+
print.heading('Accounts and extensions');
|
|
151
151
|
const accounts = oauth.listProviders();
|
|
152
152
|
const connected = accounts.filter((item) => item.connection && !item.connection.expired);
|
|
153
153
|
if (connected.length) {
|
|
154
|
-
for (const account of connected) line('ok', `${account.name}
|
|
154
|
+
for (const account of connected) line('ok', `${account.name} account`, account.connection.account);
|
|
155
155
|
} else {
|
|
156
|
-
line('-', '
|
|
156
|
+
line('-', 'No connected OAuth accounts', 'optional: dgxay oauth connect openai');
|
|
157
157
|
}
|
|
158
158
|
const servers = mcp.listServers();
|
|
159
159
|
if (servers.length) {
|
|
160
160
|
for (const server of servers) {
|
|
161
161
|
line('ok', `MCP ${server.name}`, `${server.command} ${(server.args || []).join(' ')}`.trim());
|
|
162
162
|
}
|
|
163
|
-
print.hint('
|
|
163
|
+
print.hint(' Note: this version only manages MCP processes; MCP tools are not yet exposed to the model');
|
|
164
164
|
} else {
|
|
165
|
-
line('-', '
|
|
165
|
+
line('-', 'No MCP servers');
|
|
166
166
|
}
|
|
167
167
|
const skillList = skills.listSkills();
|
|
168
|
-
line(skillList.length ? 'ok' : '-',
|
|
168
|
+
line(skillList.length ? 'ok' : '-', `Skills: ${skillList.length}`, settings.homeRelative(cfg.SKILLS_DIR));
|
|
169
169
|
|
|
170
170
|
const chatsPath = path.join(dir, 'chats.json');
|
|
171
171
|
if (fs.existsSync(chatsPath)) {
|
|
172
172
|
try {
|
|
173
173
|
const chats = JSON.parse(fs.readFileSync(chatsPath, 'utf8'));
|
|
174
|
-
line('ok', '
|
|
174
|
+
line('ok', 'Conversation history', `${Array.isArray(chats) ? chats.length : 0} entries`);
|
|
175
175
|
} catch {
|
|
176
|
-
line('warn', '
|
|
176
|
+
line('warn', 'Conversation history could not be parsed', chatsPath);
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
|
|
@@ -184,7 +184,7 @@ async function run({ argv }) {
|
|
|
184
184
|
module.exports = {
|
|
185
185
|
name: 'doctor',
|
|
186
186
|
aliases: ['check'],
|
|
187
|
-
summary: '
|
|
187
|
+
summary: 'Self-check: environment, config, model connectivity',
|
|
188
188
|
usage: 'dgxay doctor [--offline]',
|
|
189
189
|
options,
|
|
190
190
|
examples: ['dgxay doctor', 'dgxay doctor --offline'],
|