codexmate 0.1.1 → 0.1.3

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.
Files changed (38) hide show
  1. package/README.md +6 -3
  2. package/cli/openai-bridge-retry.js +17 -9
  3. package/cli/openai-bridge-runtime.js +14 -1
  4. package/cli/openai-bridge.js +20 -10
  5. package/cli.js +375 -4
  6. package/package.json +1 -1
  7. package/web-ui/app.js +18 -4
  8. package/web-ui/index.html +1 -0
  9. package/web-ui/modules/app.computed.dashboard.mjs +14 -0
  10. package/web-ui/modules/app.methods.agents.mjs +2 -2
  11. package/web-ui/modules/app.methods.index.mjs +2 -0
  12. package/web-ui/modules/app.methods.install.mjs +14 -0
  13. package/web-ui/modules/app.methods.kilocode-config.mjs +164 -0
  14. package/web-ui/modules/app.methods.navigation.mjs +12 -0
  15. package/web-ui/modules/app.methods.openclaw-core.mjs +87 -0
  16. package/web-ui/modules/app.methods.providers.mjs +5 -32
  17. package/web-ui/modules/app.methods.startup-claude.mjs +2 -1
  18. package/web-ui/modules/app.methods.tool-config-permissions.mjs +3 -2
  19. package/web-ui/modules/app.methods.web-ui-preferences.mjs +2 -1
  20. package/web-ui/modules/config-mode.computed.mjs +11 -1
  21. package/web-ui/modules/i18n/locales/en.mjs +31 -4
  22. package/web-ui/modules/i18n/locales/ja.mjs +34 -8
  23. package/web-ui/modules/i18n/locales/vi.mjs +35 -9
  24. package/web-ui/modules/i18n/locales/zh-tw.mjs +36 -9
  25. package/web-ui/modules/i18n/locales/zh.mjs +40 -13
  26. package/web-ui/partials/index/layout-header.html +32 -21
  27. package/web-ui/partials/index/modal-config-template-agents.html +8 -4
  28. package/web-ui/partials/index/modals-basic.html +0 -26
  29. package/web-ui/partials/index/panel-config-kilocode.html +101 -0
  30. package/web-ui/partials/index/panel-config-openclaw.html +42 -14
  31. package/web-ui/partials/index/panel-config-opencode.html +1 -0
  32. package/web-ui/partials/index/panel-docs.html +17 -1
  33. package/web-ui/res/web-ui-render.precompiled.js +400 -123
  34. package/web-ui/styles/controls-forms.css +10 -0
  35. package/web-ui/styles/docs-panel.css +51 -0
  36. package/web-ui/styles/layout-shell.css +12 -0
  37. package/web-ui/styles/modals-core.css +79 -0
  38. package/web-ui/styles/openclaw-structured.css +117 -71
package/cli.js CHANGED
@@ -193,6 +193,9 @@ const CONFIG_DIR = path.join(os.homedir(), '.codex');
193
193
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.toml');
194
194
  const AUTH_FILE = path.join(CONFIG_DIR, 'auth.json');
195
195
  const OPENCODE_CONFIG_DIR = path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'opencode');
196
+ const KILOCODE_CONFIG_DIR = path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'kilo');
197
+ const KILOCODE_GLOBAL_JSONC_CONFIG_FILE = path.join(KILOCODE_CONFIG_DIR, 'kilo.jsonc');
198
+ const KILOCODE_GLOBAL_JSON_CONFIG_FILE = path.join(KILOCODE_CONFIG_DIR, 'kilo.json');
196
199
  const OPENCODE_CONFIG_ENV_FILE = process.env.OPENCODE_CONFIG ? path.resolve(process.env.OPENCODE_CONFIG) : '';
197
200
  const OPENCODE_GLOBAL_JSONC_CONFIG_FILE = path.join(OPENCODE_CONFIG_DIR, 'opencode.jsonc');
198
201
  const OPENCODE_GLOBAL_JSON_CONFIG_FILE = path.join(OPENCODE_CONFIG_DIR, 'opencode.json');
@@ -362,6 +365,12 @@ const CLI_INSTALL_TARGETS = Object.freeze([
362
365
  name: 'Codex CLI',
363
366
  packageName: '@openai/codex',
364
367
  bins: ['codex']
368
+ },
369
+ {
370
+ id: 'kilocode',
371
+ name: 'KiloCode CLI',
372
+ packageName: '@kilocode/cli',
373
+ bins: ['kilo', 'kilocode']
365
374
  }
366
375
  ]);
367
376
 
@@ -900,8 +909,8 @@ function isPlainObject(value) {
900
909
  return !!value && typeof value === 'object' && !Array.isArray(value);
901
910
  }
902
911
 
903
- const TOOL_CONFIG_PERMISSION_TARGETS = new Set(['codex', 'claude', 'opencode']);
904
- const TOOL_CONFIG_PERMISSION_DEFAULTS = Object.freeze({ codex: false, claude: false, opencode: false });
912
+ const TOOL_CONFIG_PERMISSION_TARGETS = new Set(['codex', 'claude', 'opencode', 'kilocode']);
913
+ const TOOL_CONFIG_PERMISSION_DEFAULTS = Object.freeze({ codex: false, claude: false, opencode: false, kilocode: false });
905
914
  let toolConfigWriteGuardDepth = 0;
906
915
 
907
916
  function enterToolConfigWriteGuard() {
@@ -928,7 +937,8 @@ function normalizeToolConfigPermissions(value) {
928
937
  return {
929
938
  codex: source.codex === true,
930
939
  claude: source.claude === true,
931
- opencode: source.opencode === true
940
+ opencode: source.opencode === true,
941
+ kilocode: source.kilocode === true
932
942
  };
933
943
  }
934
944
 
@@ -986,7 +996,7 @@ function normalizeMainTabPreference(value) {
986
996
 
987
997
  function normalizeConfigModePreference(value) {
988
998
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
989
- return ['codex', 'claude', 'openclaw', 'opencode'].includes(normalized) ? normalized : 'codex';
999
+ return ['codex', 'claude', 'openclaw', 'opencode', 'kilocode'].includes(normalized) ? normalized : 'codex';
990
1000
  }
991
1001
 
992
1002
  function normalizeUsageTimeRangePreference(value) {
@@ -1183,9 +1193,14 @@ function getApiToolConfigWriteTarget(action) {
1183
1193
  'apply-opencode-config',
1184
1194
  'update-opencode-selection'
1185
1195
  ]);
1196
+ const kilocodeWriteActions = new Set([
1197
+ 'apply-kilocode-config',
1198
+ 'start-kilocode'
1199
+ ]);
1186
1200
  if (codexWriteActions.has(name)) return 'codex';
1187
1201
  if (claudeWriteActions.has(name)) return 'claude';
1188
1202
  if (opencodeWriteActions.has(name)) return 'opencode';
1203
+ if (kilocodeWriteActions.has(name)) return 'kilocode';
1189
1204
  return '';
1190
1205
  }
1191
1206
 
@@ -10169,6 +10184,347 @@ function cmdUpdate(name, baseUrl, apiKey, silent = false, options = {}) {
10169
10184
  }
10170
10185
  }
10171
10186
 
10187
+ function stripJsoncComments(input) {
10188
+ const text = String(input || '');
10189
+ let output = '';
10190
+ let inString = false;
10191
+ let quote = '';
10192
+ let escaped = false;
10193
+ for (let i = 0; i < text.length; i++) {
10194
+ const ch = text[i];
10195
+ const next = text[i + 1];
10196
+ if (inString) {
10197
+ output += ch;
10198
+ if (escaped) {
10199
+ escaped = false;
10200
+ } else if (ch === '\\') {
10201
+ escaped = true;
10202
+ } else if (ch === quote) {
10203
+ inString = false;
10204
+ quote = '';
10205
+ }
10206
+ continue;
10207
+ }
10208
+ if (ch === '"' || ch === "'") {
10209
+ inString = true;
10210
+ quote = ch;
10211
+ output += ch;
10212
+ continue;
10213
+ }
10214
+ if (ch === '/' && next === '/') {
10215
+ while (i < text.length && text[i] !== '\n') i++;
10216
+ output += '\n';
10217
+ continue;
10218
+ }
10219
+ if (ch === '/' && next === '*') {
10220
+ i += 2;
10221
+ while (i < text.length && !(text[i] === '*' && text[i + 1] === '/')) i++;
10222
+ i += 1;
10223
+ continue;
10224
+ }
10225
+ output += ch;
10226
+ }
10227
+ return output.replace(/,\s*([}\]])/g, '$1');
10228
+ }
10229
+
10230
+ function readKilocodeGlobalConfig() {
10231
+ const candidates = [KILOCODE_GLOBAL_JSONC_CONFIG_FILE, KILOCODE_GLOBAL_JSON_CONFIG_FILE];
10232
+ const filePath = candidates.find((candidate) => fs.existsSync(candidate)) || KILOCODE_GLOBAL_JSONC_CONFIG_FILE;
10233
+ if (!fs.existsSync(filePath)) {
10234
+ return { filePath, config: {} };
10235
+ }
10236
+ const raw = fs.readFileSync(filePath, 'utf-8');
10237
+ if (!raw.trim()) {
10238
+ return { filePath, config: {} };
10239
+ }
10240
+ try {
10241
+ const parsed = JSON.parse(stripJsoncComments(raw));
10242
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
10243
+ throw new Error('配置根节点必须是对象');
10244
+ }
10245
+ return { filePath, config: parsed };
10246
+ } catch (e) {
10247
+ throw new Error(`读取 KiloCode 配置失败: ${e.message}`);
10248
+ }
10249
+ }
10250
+
10251
+ function normalizeKilocodeProviderName(value) {
10252
+ const name = typeof value === 'string' && value.trim() ? value.trim() : 'codexmate';
10253
+ if (!/^[A-Za-z0-9._-]+$/.test(name)) {
10254
+ throw new Error('provider 仅支持字母/数字/._-');
10255
+ }
10256
+ return name;
10257
+ }
10258
+
10259
+ function writeKilocodeProviderConfig(params = {}) {
10260
+ const provider = normalizeKilocodeProviderName(params.provider);
10261
+ const url = normalizeBaseUrl(params.url || '');
10262
+ const incomingKey = typeof params.key === 'string' ? params.key.trim() : '';
10263
+ const model = typeof params.model === 'string' ? params.model.trim() : '';
10264
+ if (!url) throw new Error('URL 必填');
10265
+ if (!model) throw new Error('模型名称必填');
10266
+ if (!isValidHttpUrl(url)) throw new Error('URL 仅支持 http/https');
10267
+
10268
+ const { filePath, config } = readKilocodeGlobalConfig();
10269
+ const existingProvider = config.provider
10270
+ && typeof config.provider === 'object'
10271
+ && !Array.isArray(config.provider)
10272
+ && config.provider[provider]
10273
+ && typeof config.provider[provider] === 'object'
10274
+ && !Array.isArray(config.provider[provider])
10275
+ ? config.provider[provider]
10276
+ : {};
10277
+ const existingOptions = existingProvider.options && typeof existingProvider.options === 'object' && !Array.isArray(existingProvider.options)
10278
+ ? existingProvider.options
10279
+ : {};
10280
+ const existingKey = typeof existingOptions.apiKey === 'string' ? existingOptions.apiKey.trim() : '';
10281
+ const key = incomingKey || existingKey;
10282
+ if (!key) throw new Error('API Key 必填');
10283
+
10284
+ const next = { ...config };
10285
+ if (!next.$schema) {
10286
+ next.$schema = 'https://app.kilo.ai/config.json';
10287
+ }
10288
+ next.provider = next.provider && typeof next.provider === 'object' && !Array.isArray(next.provider)
10289
+ ? { ...next.provider }
10290
+ : {};
10291
+ next.provider[provider] = {
10292
+ ...(next.provider[provider] && typeof next.provider[provider] === 'object' && !Array.isArray(next.provider[provider])
10293
+ ? next.provider[provider]
10294
+ : {}),
10295
+ name: provider,
10296
+ npm: '@ai-sdk/openai-compatible',
10297
+ api: url,
10298
+ env: [],
10299
+ models: {
10300
+ [model]: {
10301
+ name: model,
10302
+ tool_call: true
10303
+ }
10304
+ },
10305
+ options: {
10306
+ ...(next.provider[provider]
10307
+ && typeof next.provider[provider] === 'object'
10308
+ && next.provider[provider].options
10309
+ && typeof next.provider[provider].options === 'object'
10310
+ && !Array.isArray(next.provider[provider].options)
10311
+ ? next.provider[provider].options
10312
+ : {}),
10313
+ apiKey: key,
10314
+ baseURL: url
10315
+ }
10316
+ };
10317
+ next.model = `${provider}/${model}`;
10318
+ const enabled = Array.isArray(next.enabled_providers) ? next.enabled_providers.filter((item) => typeof item === 'string' && item.trim()) : [];
10319
+ next.enabled_providers = enabled.includes(provider) ? enabled : [...enabled, provider];
10320
+
10321
+ ensureDir(path.dirname(filePath));
10322
+ fs.writeFileSync(filePath, JSON.stringify(next, null, 2) + '\n', 'utf-8');
10323
+ return { filePath, provider, model, url };
10324
+ }
10325
+
10326
+ function parseKilocodeCommandArgs(argv = []) {
10327
+ const options = { provider: 'codexmate', command: '' };
10328
+ const positional = [];
10329
+ let passthrough = [];
10330
+ for (let i = 0; i < argv.length; i++) {
10331
+ const token = String(argv[i] || '');
10332
+ if (token === '--') {
10333
+ passthrough = argv.slice(i + 1).map(item => String(item));
10334
+ break;
10335
+ }
10336
+ if (token === '--provider') {
10337
+ const value = String(argv[i + 1] || '');
10338
+ if (!value || value.startsWith('--')) throw new Error('错误: --provider 需要一个值');
10339
+ options.provider = value;
10340
+ i += 1;
10341
+ continue;
10342
+ }
10343
+ if (token.startsWith('--provider=')) {
10344
+ options.provider = token.slice('--provider='.length);
10345
+ continue;
10346
+ }
10347
+ if (token === '--help' || token === '-h') {
10348
+ options.help = true;
10349
+ continue;
10350
+ }
10351
+ positional.push(token);
10352
+ }
10353
+ if (positional[0] === 'config' || positional[0] === 'setup') {
10354
+ options.command = positional.shift();
10355
+ }
10356
+ return {
10357
+ command: options.command,
10358
+ url: positional[0],
10359
+ key: positional[1],
10360
+ model: positional[2],
10361
+ provider: options.provider,
10362
+ help: !!options.help,
10363
+ passthrough: passthrough.length ? passthrough : positional.slice(options.command ? 3 : 0)
10364
+ };
10365
+ }
10366
+
10367
+ function printKilocodeUsage() {
10368
+ console.log('\n用法:');
10369
+ console.log(' codexmate kilo [KiloCode参数...]');
10370
+ console.log(' codexmate kilo <URL> <API密钥> <模型> [--provider <id>] [-- KiloCode参数...]');
10371
+ console.log(' codexmate kilo config <URL> <API密钥> <模型> [--provider <id>]');
10372
+ console.log('\n说明:');
10373
+ console.log(' codexmate kilo 默认启动 KiloCode;带 URL/API密钥/模型时会先写入 KiloCode provider 配置再启动。');
10374
+ console.log(' 纯配置请使用 codexmate kilo config ...');
10375
+ console.log(' 配置写入 ~/.config/kilo/kilo.jsonc(或已存在的 kilo.json)。');
10376
+ }
10377
+
10378
+ function resolveKilocodeBinary() {
10379
+ for (const bin of ['kilo', 'kilocode']) {
10380
+ if (commandExists(bin, '--version')) return bin;
10381
+ }
10382
+ return '';
10383
+ }
10384
+
10385
+ function runKilocodeCommand(args = [], options = {}) {
10386
+ const bin = resolveKilocodeBinary();
10387
+ if (!bin) {
10388
+ throw new Error('无法启动 KiloCode,请确认已安装 KiloCode CLI,并且 kilo 或 kilocode 在 PATH 中。');
10389
+ }
10390
+ const finalArgs = Array.isArray(args) ? args.filter(item => item !== undefined).map(item => String(item)) : [];
10391
+ if (options.detached === true) {
10392
+ const child = spawn(bin, finalArgs, {
10393
+ detached: true,
10394
+ stdio: 'ignore',
10395
+ windowsHide: true
10396
+ });
10397
+ child.unref();
10398
+ return { success: true, bin, args: finalArgs, pid: child.pid };
10399
+ }
10400
+ return new Promise((resolve, reject) => {
10401
+ const child = spawn(bin, finalArgs, {
10402
+ stdio: 'inherit',
10403
+ windowsHide: false
10404
+ });
10405
+ child.on('error', reject);
10406
+ child.on('exit', (code, signal) => {
10407
+ if (signal) {
10408
+ reject(new Error(`KiloCode 已终止: ${signal}`));
10409
+ return;
10410
+ }
10411
+ resolve(code || 0);
10412
+ });
10413
+ });
10414
+ }
10415
+
10416
+ async function cmdKilocode(argv = []) {
10417
+ const parsed = parseKilocodeCommandArgs(argv);
10418
+ if (parsed.help) {
10419
+ printKilocodeUsage();
10420
+ return 0;
10421
+ }
10422
+ if (parsed.command === 'config') {
10423
+ if (!parsed.url || !parsed.key || !parsed.model) {
10424
+ printKilocodeUsage();
10425
+ throw new Error('URL、API密钥和模型名称必填');
10426
+ }
10427
+ const result = writeKilocodeProviderConfig(parsed);
10428
+ console.log('✓ 已写入 KiloCode 配置');
10429
+ console.log(' 文件:', result.filePath);
10430
+ console.log(' provider:', result.provider);
10431
+ console.log(' URL:', result.url);
10432
+ console.log(' 模型:', `${result.provider}/${result.model}`);
10433
+ console.log();
10434
+ return 0;
10435
+ }
10436
+ let passthrough = parsed.passthrough;
10437
+ if (parsed.url && parsed.key && parsed.model) {
10438
+ const result = writeKilocodeProviderConfig(parsed);
10439
+ console.log('✓ 已写入 KiloCode 配置,正在启动 KiloCode');
10440
+ console.log(' 文件:', result.filePath);
10441
+ console.log(' 模型:', `${result.provider}/${result.model}`);
10442
+ console.log();
10443
+ passthrough = parsed.passthrough;
10444
+ } else if (parsed.url || parsed.key || parsed.model) {
10445
+ passthrough = [parsed.url, parsed.key, parsed.model, ...parsed.passthrough].filter(item => item !== undefined && item !== '');
10446
+ }
10447
+ return runKilocodeCommand(passthrough);
10448
+ }
10449
+
10450
+ function summarizeKilocodeConfig(config = {}, targetPath = KILOCODE_GLOBAL_JSONC_CONFIG_FILE, exists = false) {
10451
+ const providers = getRecord(config.provider);
10452
+ const modelRef = typeof config.model === 'string' ? config.model.trim() : '';
10453
+ const slash = modelRef.indexOf('/');
10454
+ const currentProvider = slash > 0 ? modelRef.slice(0, slash) : '';
10455
+ const currentModel = slash > 0 ? modelRef.slice(slash + 1) : modelRef;
10456
+ const providerNames = [...new Set([...Object.keys(providers), currentProvider].filter(Boolean))];
10457
+ const redactedConfig = JSON.parse(JSON.stringify(config && typeof config === 'object' ? config : {}));
10458
+ const redactedProviders = getRecord(redactedConfig.provider);
10459
+ for (const provider of Object.values(redactedProviders)) {
10460
+ const options = getRecord(provider && provider.options);
10461
+ if (typeof options.apiKey === 'string' && options.apiKey) {
10462
+ options.apiKey = maskKey(options.apiKey);
10463
+ }
10464
+ }
10465
+ return {
10466
+ exists: !!exists,
10467
+ targetPath,
10468
+ currentProvider,
10469
+ currentModel,
10470
+ providers: providerNames.map((name) => {
10471
+ const provider = getRecord(providers[name]);
10472
+ const options = getRecord(provider.options);
10473
+ const apiKey = typeof options.apiKey === 'string' ? options.apiKey : '';
10474
+ const modelNames = Object.keys(getRecord(provider.models));
10475
+ return {
10476
+ name,
10477
+ api: typeof provider.api === 'string' ? provider.api : '',
10478
+ baseURL: typeof options.baseURL === 'string' ? options.baseURL : '',
10479
+ hasKey: apiKey.trim().length > 0,
10480
+ apiKey: maskKey(apiKey),
10481
+ models: modelNames
10482
+ };
10483
+ }),
10484
+ content: JSON.stringify(redactedConfig, null, 2) + '\n',
10485
+ redacted: true
10486
+ };
10487
+ }
10488
+
10489
+ function readKilocodeConfigInfo() {
10490
+ try {
10491
+ const { filePath, config } = readKilocodeGlobalConfig();
10492
+ return summarizeKilocodeConfig(config, filePath, fs.existsSync(filePath));
10493
+ } catch (e) {
10494
+ return { error: e.message || '读取 KiloCode 配置失败', targetPath: KILOCODE_GLOBAL_JSONC_CONFIG_FILE };
10495
+ }
10496
+ }
10497
+
10498
+ function applyKilocodeConfig(params = {}) {
10499
+ assertToolConfigWriteAllowed('kilocode');
10500
+ try {
10501
+ const result = writeKilocodeProviderConfig({
10502
+ provider: params.provider,
10503
+ url: params.url,
10504
+ key: params.apiKey,
10505
+ model: params.model
10506
+ });
10507
+ const info = readKilocodeConfigInfo();
10508
+ return { success: true, ...result, ...info };
10509
+ } catch (e) {
10510
+ return { error: e.message || '写入 KiloCode 配置失败' };
10511
+ }
10512
+ }
10513
+
10514
+ function startKilocodeFromWeb(params = {}) {
10515
+ assertToolConfigWriteAllowed('kilocode');
10516
+ try {
10517
+ if (params && params.configure === true) {
10518
+ const saved = applyKilocodeConfig(params);
10519
+ if (saved && saved.error) return saved;
10520
+ }
10521
+ const args = Array.isArray(params.args) ? params.args.map(item => String(item)) : [];
10522
+ return runKilocodeCommand(args, { detached: true });
10523
+ } catch (e) {
10524
+ return { error: e.message || '启动 KiloCode 失败' };
10525
+ }
10526
+ }
10527
+
10172
10528
  // 添加模型
10173
10529
  function cmdAddModel(modelName, silent = false) {
10174
10530
  if (!modelName) {
@@ -12776,12 +13132,21 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
12776
13132
  case 'get-opencode-config':
12777
13133
  result = readOpencodeConfigInfo();
12778
13134
  break;
13135
+ case 'get-kilocode-config':
13136
+ result = readKilocodeConfigInfo();
13137
+ break;
12779
13138
  case 'apply-opencode-config':
12780
13139
  result = applyOpencodeConfigRaw(params || {});
12781
13140
  break;
12782
13141
  case 'update-opencode-selection':
12783
13142
  result = updateOpencodeSelection(params || {});
12784
13143
  break;
13144
+ case 'apply-kilocode-config':
13145
+ result = applyKilocodeConfig(params || {});
13146
+ break;
13147
+ case 'start-kilocode':
13148
+ result = startKilocodeFromWeb(params || {});
13149
+ break;
12785
13150
  case 'apply-claude-config':
12786
13151
  result = await applyToClaudeSettings(params.config);
12787
13152
  if (result && !result.error) {
@@ -18322,6 +18687,7 @@ function printMainHelp() {
18322
18687
  console.log(' codexmate delete <名称> 删除提供商');
18323
18688
  console.log(' codexmate claude 等同于 claude --dangerously-skip-permissions');
18324
18689
  console.log(' codexmate claude <BaseURL> <API密钥> [模型] [--target-api responses|chat_completions|ollama] 写入 Claude Code 配置');
18690
+ console.log(' codexmate kilo [URL API密钥 模型] [--provider <id>] 配置后启动 KiloCode');
18325
18691
  console.log(' codexmate auth <list|import|switch|delete|status> 认证管理');
18326
18692
  console.log(' codexmate add-model <模型> 添加模型');
18327
18693
  console.log(' codexmate delete-model <模型> 删除模型');
@@ -18417,6 +18783,11 @@ async function main() {
18417
18783
  const exitCode = await cmdClaude(args.slice(1));
18418
18784
  process.exit(exitCode);
18419
18785
  }
18786
+ case 'kilo':
18787
+ case 'kilocode': {
18788
+ const exitCode = await cmdKilocode(args.slice(1));
18789
+ process.exit(exitCode || 0);
18790
+ }
18420
18791
  case 'add-model': cmdAddModel(args[1]); break;
18421
18792
  case 'delete-model': cmdDeleteModel(args[1]); break;
18422
18793
  case 'auth': cmdAuth(args.slice(1)); break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexmate",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
5
5
  "main": "cli.js",
6
6
  "bin": {
package/web-ui/app.js CHANGED
@@ -290,9 +290,9 @@ document.addEventListener('DOMContentLoaded', () => {
290
290
  appVersionStatusChecked: false,
291
291
  appVersionStatusCheckedAt: '',
292
292
  appVersionStatusSource: '',
293
- newProvider: { name: '', url: '', key: '', model: '', useTransform: false, openaiBridgeMaxRetries: 2 },
293
+ newProvider: { name: '', url: '', key: '', model: '', useTransform: false },
294
294
  resetConfigLoading: false,
295
- editingProvider: { name: '', url: '', key: '', readOnly: false, nonEditable: false, useTransform: false, openaiBridgeMaxRetries: 2 },
295
+ editingProvider: { name: '', url: '', key: '', readOnly: false, nonEditable: false, useTransform: false },
296
296
  newModelName: '',
297
297
  currentClaudeConfig: '',
298
298
  currentClaudeModel: '',
@@ -396,8 +396,8 @@ document.addEventListener('DOMContentLoaded', () => {
396
396
  providerCacheError: '',
397
397
  providerCacheRequestSeq: 0,
398
398
  settingsTab: 'general',
399
- toolConfigPermissions: { codex: false, claude: false, opencode: false },
400
- toolConfigPermissionSaving: { codex: false, claude: false, opencode: false },
399
+ toolConfigPermissions: { codex: false, claude: false, opencode: false, kilocode: false },
400
+ toolConfigPermissionSaving: { codex: false, claude: false, opencode: false, kilocode: false },
401
401
  sessionTrashEnabled: true,
402
402
  sessionTrashItems: [],
403
403
  sessionTrashVisibleCount: SESSION_TRASH_PAGE_SIZE,
@@ -440,6 +440,20 @@ document.addEventListener('DOMContentLoaded', () => {
440
440
  opencodeAutoCompact: true,
441
441
  opencodeMaxTokens: '',
442
442
  opencodeReasoningEffort: '',
443
+ kilocodeConfigPath: '',
444
+ kilocodeConfigExists: false,
445
+ kilocodeContent: '{}\n',
446
+ kilocodeLoading: false,
447
+ kilocodeSaving: false,
448
+ kilocodeStarting: false,
449
+ kilocodeError: '',
450
+ kilocodeProviders: [],
451
+ kilocodeProvider: 'codexmate',
452
+ kilocodeBaseUrl: '',
453
+ kilocodeModel: 'gpt-5.3',
454
+ kilocodeApiKey: '',
455
+ kilocodeShowKey: false,
456
+ kilocodeAutoSaveSignature: '',
443
457
  forceCompactLayout: false,
444
458
  sidebarCollapsed: false,
445
459
  sessionLoadNativeDialog: false,
package/web-ui/index.html CHANGED
@@ -17,6 +17,7 @@
17
17
  <!-- @include ./partials/index/panel-config-claude.html -->
18
18
  <!-- @include ./partials/index/panel-config-openclaw.html -->
19
19
  <!-- @include ./partials/index/panel-config-opencode.html -->
20
+ <!-- @include ./partials/index/panel-config-kilocode.html -->
20
21
  <!-- @include ./partials/index/panel-sessions.html -->
21
22
  <!-- @include ./partials/index/panel-usage.html -->
22
23
  <!-- @include ./partials/index/panel-orchestration.html -->
@@ -149,6 +149,16 @@ export function createDashboardComputed() {
149
149
  version: '',
150
150
  commandPath: '',
151
151
  error: ''
152
+ },
153
+ {
154
+ id: 'kilocode',
155
+ name: 'KiloCode CLI',
156
+ packageName: '@kilocode/cli',
157
+ installed: false,
158
+ bin: 'kilo',
159
+ version: '',
160
+ commandPath: '',
161
+ error: ''
152
162
  }
153
163
  ];
154
164
  const action = this.normalizeInstallAction(this.installCommandAction);
@@ -164,6 +174,10 @@ export function createDashboardComputed() {
164
174
  };
165
175
  });
166
176
  },
177
+ currentInstalledCommandCards() {
178
+ const targets = Array.isArray(this.installTargetCards) ? this.installTargetCards : [];
179
+ return targets.filter((target) => target && target.installed === true);
180
+ },
167
181
  installRegistryPreview() {
168
182
  return this.resolveInstallRegistryUrl(this.installRegistryPreset, this.installRegistryCustom);
169
183
  },
@@ -206,13 +206,13 @@ export function createAgentsMethods(options = {}) {
206
206
  this.agentsContext = 'openclaw-workspace';
207
207
  this.agentsWorkspaceFileName = fileName;
208
208
  this.agentsModalTitle = tr('modal.agents.title.openclawWorkspaceFile', `OpenClaw 工作区文件: ${fileName}`, { fileName });
209
- this.agentsModalHint = tr('modal.agents.hint.openclawWorkspaceFile', `保存后会写入 OpenClaw Workspace 下的 ${fileName}。`, { fileName });
209
+ this.agentsModalHint = tr('modal.agents.hint.openclawWorkspaceFile', `Workspace / ${fileName}`, { fileName });
210
210
  return;
211
211
  }
212
212
  this.agentsContext = context === 'openclaw' ? 'openclaw' : 'codex';
213
213
  if (this.agentsContext === 'openclaw') {
214
214
  this.agentsModalTitle = tr('modal.agents.title.openclaw', 'OpenClaw AGENTS.md 编辑器');
215
- this.agentsModalHint = tr('modal.agents.hint.openclaw', '保存后会写入 OpenClaw Workspace 下的 AGENTS.md。');
215
+ this.agentsModalHint = tr('modal.agents.hint.openclaw', 'Workspace / AGENTS.md');
216
216
  } else {
217
217
  this.agentsModalTitle = tr('modal.agents.title.default', 'AGENTS.md 编辑器');
218
218
  this.agentsModalHint = tr('modal.agents.hint.default', '保存后会写入目标 AGENTS.md(与 config.toml 同级)。');
@@ -20,6 +20,7 @@ import { createOpenclawCoreMethods } from './app.methods.openclaw-core.mjs';
20
20
  import { createOpenclawEditingMethods } from './app.methods.openclaw-editing.mjs';
21
21
  import { createOpenclawPersistMethods } from './app.methods.openclaw-persist.mjs';
22
22
  import { createOpencodeConfigMethods } from './app.methods.opencode-config.mjs';
23
+ import { createKilocodeConfigMethods } from './app.methods.kilocode-config.mjs';
23
24
  import { createProvidersMethods } from './app.methods.providers.mjs';
24
25
  import { createProviderCacheMethods } from './app.methods.provider-cache.mjs';
25
26
  import { createRuntimeMethods } from './app.methods.runtime.mjs';
@@ -99,6 +100,7 @@ export function createAppMethods() {
99
100
  api,
100
101
  modelCatalog: OPENCODE_MODEL_CATALOG
101
102
  }),
103
+ ...createKilocodeConfigMethods({ api }),
102
104
  ...createInstallMethods({ api }),
103
105
  ...createRuntimeMethods({ api }),
104
106
  ...createTaskOrchestrationMethods({ api })
@@ -246,6 +246,11 @@ export function createInstallMethods(options = {}) {
246
246
  install: '',
247
247
  update: '',
248
248
  uninstall: ''
249
+ },
250
+ kilocode: {
251
+ install: '',
252
+ update: '',
253
+ uninstall: ''
249
254
  }
250
255
  };
251
256
  if (manager === 'pnpm') {
@@ -261,6 +266,9 @@ export function createInstallMethods(options = {}) {
261
266
  matrix.codex.install = `pnpm add -g ${codexInstallPackage}`;
262
267
  matrix.codex.update = `pnpm up -g ${codexPackage}`;
263
268
  matrix.codex.uninstall = `pnpm remove -g ${codexPackage}`;
269
+ matrix.kilocode.install = 'pnpm add -g @kilocode/cli';
270
+ matrix.kilocode.update = 'pnpm up -g @kilocode/cli';
271
+ matrix.kilocode.uninstall = 'pnpm remove -g @kilocode/cli';
264
272
  return matrix;
265
273
  }
266
274
  if (manager === 'bun') {
@@ -276,6 +284,9 @@ export function createInstallMethods(options = {}) {
276
284
  matrix.codex.install = `bun add -g ${codexInstallPackage}`;
277
285
  matrix.codex.update = `bun update -g ${codexPackage}`;
278
286
  matrix.codex.uninstall = `bun remove -g ${codexPackage}`;
287
+ matrix.kilocode.install = 'bun add -g @kilocode/cli';
288
+ matrix.kilocode.update = 'bun update -g @kilocode/cli';
289
+ matrix.kilocode.uninstall = 'bun remove -g @kilocode/cli';
279
290
  return matrix;
280
291
  }
281
292
  matrix.claude.install = 'npm install -g @anthropic-ai/claude-code';
@@ -292,6 +303,9 @@ export function createInstallMethods(options = {}) {
292
303
  ? `npm install -g ${codexInstallPackage}`
293
304
  : `npm update -g ${codexPackage}`;
294
305
  matrix.codex.uninstall = `npm uninstall -g ${codexPackage}`;
306
+ matrix.kilocode.install = 'npm install -g @kilocode/cli';
307
+ matrix.kilocode.update = 'npm update -g @kilocode/cli';
308
+ matrix.kilocode.uninstall = 'npm uninstall -g @kilocode/cli';
295
309
  return matrix;
296
310
  },
297
311