codexmate 0.1.2 → 0.1.4

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 CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # Codex Mate
6
6
 
7
- **One dashboard for all your local AI coding agents. Switch providers, manage sessions, and orchestrate tasks across Codex, Claude Code, OpenCode, and OpenClaw. Zero cloud, local-first control plane.**
7
+ **One dashboard for all your local AI coding agents. Switch providers, manage sessions, and orchestrate tasks across Codex, Claude Code, OpenCode, KiloCode, and OpenClaw. Zero cloud, local-first control plane.**
8
8
 
9
9
  <p>
10
10
  <a href="https://sakurabytecore.github.io/codexmate/">[Documentation]</a>
@@ -49,7 +49,7 @@
49
49
 
50
50
  Have you ever felt overwhelmed by managing multiple local AI agents? Each has its own config format, session storage, and skills directory.
51
51
 
52
- **Codex Mate** offers a unified control plane to bring order to the chaos. It's a local-first CLI + Web UI designed to manage [Codex](https://github.com/openai/codex), [Claude Code](https://github.com/anthropic-ai/claude-code), [OpenCode](https://opencode.ai/), and [OpenClaw](https://github.com/moeru-ai/openclaw) seamlessly.
52
+ **Codex Mate** offers a unified control plane to bring order to the chaos. It's a local-first CLI + Web UI designed to manage [Codex](https://github.com/openai/codex), [Claude Code](https://github.com/anthropic-ai/claude-code), [OpenCode](https://opencode.ai/), KiloCode, and [OpenClaw](https://github.com/moeru-ai/openclaw) seamlessly.
53
53
 
54
54
  ### What's So Special?
55
55
 
@@ -58,6 +58,7 @@ Unlike simple wrappers, Codex Mate acts as a **Local Agent Bridge**:
58
58
  - **OpenAI-Compatible Bridge**: Use Codex with any OpenAI-compatible UI by normalizing the Responses API; the built-in Codex conversion also fills and normalizes Codex fingerprint headers such as `User-Agent`, `Version`, `OpenAI-Beta`, and `Originator` so upstream providers see an official Codex CLI-shaped request.
59
59
  - **Claude Provider Bridge**: Connect Claude Code to OpenAI Chat Completions-compatible providers and Ollama through the built-in local Claude-compatible proxy.
60
60
  - **OpenCode Provider Control**: Manage OpenCode provider/model selection with a CodexMate-owned provider store under `~/.codexmate`, projecting only the active provider into native OpenCode config to avoid polluting or deleting user-owned settings.
61
+ - **KiloCode Config Bridge**: Configure KiloCode providers from the CLI or Web UI, writing to `~/.config/kilo/kilo.jsonc` while preserving stored API keys.
61
62
  - **Provider Health Cleanup**: Probe local Codex and Claude provider routes, surface failed configs in one modal, and bulk-clean selected broken providers without touching healthy or protected entries.
62
63
  - **Skills Marketplace**: A local-first market to share and import skills between different agent apps.
63
64
  - **Prompt File Editor**: Unified editor for global and project-level `CLAUDE.md` and `AGENTS.md` with auto-detection of project paths, plus a shared preset pool for reusable prompts.
@@ -69,7 +70,7 @@ Unlike simple wrappers, Codex Mate acts as a **Local Agent Bridge**:
69
70
 
70
71
  | Feature | Status | Description |
71
72
  | --- | --- | --- |
72
- | **Provider Management** | ✅ | Switch providers/models for Codex, Claude, OpenCode, and OpenClaw |
73
+ | **Provider Management** | ✅ | Switch providers/models for Codex, Claude, OpenCode, KiloCode, and OpenClaw |
73
74
  | **Live Agent Sync** | ✅ | Real-time monitoring of Codex/Claude config & status |
74
75
  | **Session Browser** | ✅ | Search, preview, filter, and export sessions across Codex, Claude Code, Gemini CLI, and CodeBuddy Code |
75
76
  | **Usage Analytics** | ✅ | Visualize message trends and top projects |
@@ -78,6 +79,7 @@ Unlike simple wrappers, Codex Mate acts as a **Local Agent Bridge**:
78
79
  | **OpenAI Bridge** | ✅ | Convert Codex Responses API to standard OpenAI format and attach/normalize Codex fingerprints in the built-in conversion |
79
80
  | **Claude Provider Bridge** | ✅ | Connect Claude Code to OpenAI Chat Completions-compatible providers and Ollama via the built-in Claude-compatible proxy |
80
81
  | **OpenCode Provider Store** | ✅ | Keep multiple OpenCode providers in `~/.codexmate` while projecting only the selected provider to native OpenCode config |
82
+ | **KiloCode Config Bridge** | ✅ | Configure KiloCode through `codexmate kilo config` or the Web UI, writing provider URL/model/API key data to `~/.config/kilo/kilo.jsonc` |
81
83
  | **Provider Health Check** | ✅ | Probe local Codex/Claude provider routes, highlight failed configs, and bulk-remove selected broken providers safely |
82
84
  | **Prompt Templates** | ✅ | Reusable prompt plugins with variables |
83
85
  | **Prompt File Editor** | ✅ | Edit global and project-level CLAUDE.md / AGENTS.md with auto-detect, path switching, and a shared preset pool. Applying a preset only updates the editor; save manually to write the file. |
@@ -128,6 +130,7 @@ curl -fsSL https://raw.githubusercontent.com/SakuraByteCore/codexmate/main/scrip
128
130
  - **Claude Code**: `npm install -g @anthropic-ai/claude-code`
129
131
  - **Gemini CLI**: `npm install -g @google/gemini-cli`
130
132
  - **CodeBuddy**: `npm install -g @tencent-ai/codebuddy-code`
133
+ - **KiloCode**: `npm install -g @kilocode/cli` (`kilo` / `kilocode`)
131
134
  - **OpenCode**: install from the [official OpenCode docs](https://opencode.ai/)
132
135
 
133
136
  ---
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', 'openclaw']);
913
+ const TOOL_CONFIG_PERMISSION_DEFAULTS = Object.freeze({ codex: false, claude: false, opencode: false, kilocode: false, openclaw: false });
905
914
  let toolConfigWriteGuardDepth = 0;
906
915
 
907
916
  function enterToolConfigWriteGuard() {
@@ -928,7 +937,9 @@ 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,
942
+ openclaw: source.openclaw === true
932
943
  };
933
944
  }
934
945
 
@@ -986,7 +997,7 @@ function normalizeMainTabPreference(value) {
986
997
 
987
998
  function normalizeConfigModePreference(value) {
988
999
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
989
- return ['codex', 'claude', 'openclaw', 'opencode'].includes(normalized) ? normalized : 'codex';
1000
+ return ['codex', 'claude', 'openclaw', 'opencode', 'kilocode'].includes(normalized) ? normalized : 'codex';
990
1001
  }
991
1002
 
992
1003
  function normalizeUsageTimeRangePreference(value) {
@@ -1183,9 +1194,20 @@ function getApiToolConfigWriteTarget(action) {
1183
1194
  'apply-opencode-config',
1184
1195
  'update-opencode-selection'
1185
1196
  ]);
1197
+ const kilocodeWriteActions = new Set([
1198
+ 'apply-kilocode-config',
1199
+ 'start-kilocode'
1200
+ ]);
1201
+ const openclawWriteActions = new Set([
1202
+ 'apply-openclaw-config',
1203
+ 'apply-openclaw-agents-file',
1204
+ 'apply-openclaw-workspace-file'
1205
+ ]);
1186
1206
  if (codexWriteActions.has(name)) return 'codex';
1187
1207
  if (claudeWriteActions.has(name)) return 'claude';
1188
1208
  if (opencodeWriteActions.has(name)) return 'opencode';
1209
+ if (kilocodeWriteActions.has(name)) return 'kilocode';
1210
+ if (openclawWriteActions.has(name)) return 'openclaw';
1189
1211
  return '';
1190
1212
  }
1191
1213
 
@@ -10169,6 +10191,347 @@ function cmdUpdate(name, baseUrl, apiKey, silent = false, options = {}) {
10169
10191
  }
10170
10192
  }
10171
10193
 
10194
+ function stripJsoncComments(input) {
10195
+ const text = String(input || '');
10196
+ let output = '';
10197
+ let inString = false;
10198
+ let quote = '';
10199
+ let escaped = false;
10200
+ for (let i = 0; i < text.length; i++) {
10201
+ const ch = text[i];
10202
+ const next = text[i + 1];
10203
+ if (inString) {
10204
+ output += ch;
10205
+ if (escaped) {
10206
+ escaped = false;
10207
+ } else if (ch === '\\') {
10208
+ escaped = true;
10209
+ } else if (ch === quote) {
10210
+ inString = false;
10211
+ quote = '';
10212
+ }
10213
+ continue;
10214
+ }
10215
+ if (ch === '"' || ch === "'") {
10216
+ inString = true;
10217
+ quote = ch;
10218
+ output += ch;
10219
+ continue;
10220
+ }
10221
+ if (ch === '/' && next === '/') {
10222
+ while (i < text.length && text[i] !== '\n') i++;
10223
+ output += '\n';
10224
+ continue;
10225
+ }
10226
+ if (ch === '/' && next === '*') {
10227
+ i += 2;
10228
+ while (i < text.length && !(text[i] === '*' && text[i + 1] === '/')) i++;
10229
+ i += 1;
10230
+ continue;
10231
+ }
10232
+ output += ch;
10233
+ }
10234
+ return output.replace(/,\s*([}\]])/g, '$1');
10235
+ }
10236
+
10237
+ function readKilocodeGlobalConfig() {
10238
+ const candidates = [KILOCODE_GLOBAL_JSONC_CONFIG_FILE, KILOCODE_GLOBAL_JSON_CONFIG_FILE];
10239
+ const filePath = candidates.find((candidate) => fs.existsSync(candidate)) || KILOCODE_GLOBAL_JSONC_CONFIG_FILE;
10240
+ if (!fs.existsSync(filePath)) {
10241
+ return { filePath, config: {} };
10242
+ }
10243
+ const raw = fs.readFileSync(filePath, 'utf-8');
10244
+ if (!raw.trim()) {
10245
+ return { filePath, config: {} };
10246
+ }
10247
+ try {
10248
+ const parsed = JSON.parse(stripJsoncComments(raw));
10249
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
10250
+ throw new Error('配置根节点必须是对象');
10251
+ }
10252
+ return { filePath, config: parsed };
10253
+ } catch (e) {
10254
+ throw new Error(`读取 KiloCode 配置失败: ${e.message}`);
10255
+ }
10256
+ }
10257
+
10258
+ function normalizeKilocodeProviderName(value) {
10259
+ const name = typeof value === 'string' && value.trim() ? value.trim() : 'codexmate';
10260
+ if (!/^[A-Za-z0-9._-]+$/.test(name)) {
10261
+ throw new Error('provider 仅支持字母/数字/._-');
10262
+ }
10263
+ return name;
10264
+ }
10265
+
10266
+ function writeKilocodeProviderConfig(params = {}) {
10267
+ const provider = normalizeKilocodeProviderName(params.provider);
10268
+ const url = normalizeBaseUrl(params.url || '');
10269
+ const incomingKey = typeof params.key === 'string' ? params.key.trim() : '';
10270
+ const model = typeof params.model === 'string' ? params.model.trim() : '';
10271
+ if (!url) throw new Error('URL 必填');
10272
+ if (!model) throw new Error('模型名称必填');
10273
+ if (!isValidHttpUrl(url)) throw new Error('URL 仅支持 http/https');
10274
+
10275
+ const { filePath, config } = readKilocodeGlobalConfig();
10276
+ const existingProvider = config.provider
10277
+ && typeof config.provider === 'object'
10278
+ && !Array.isArray(config.provider)
10279
+ && config.provider[provider]
10280
+ && typeof config.provider[provider] === 'object'
10281
+ && !Array.isArray(config.provider[provider])
10282
+ ? config.provider[provider]
10283
+ : {};
10284
+ const existingOptions = existingProvider.options && typeof existingProvider.options === 'object' && !Array.isArray(existingProvider.options)
10285
+ ? existingProvider.options
10286
+ : {};
10287
+ const existingKey = typeof existingOptions.apiKey === 'string' ? existingOptions.apiKey.trim() : '';
10288
+ const key = incomingKey || existingKey;
10289
+ if (!key) throw new Error('API Key 必填');
10290
+
10291
+ const next = { ...config };
10292
+ if (!next.$schema) {
10293
+ next.$schema = 'https://app.kilo.ai/config.json';
10294
+ }
10295
+ next.provider = next.provider && typeof next.provider === 'object' && !Array.isArray(next.provider)
10296
+ ? { ...next.provider }
10297
+ : {};
10298
+ next.provider[provider] = {
10299
+ ...(next.provider[provider] && typeof next.provider[provider] === 'object' && !Array.isArray(next.provider[provider])
10300
+ ? next.provider[provider]
10301
+ : {}),
10302
+ name: provider,
10303
+ npm: '@ai-sdk/openai-compatible',
10304
+ api: url,
10305
+ env: [],
10306
+ models: {
10307
+ [model]: {
10308
+ name: model,
10309
+ tool_call: true
10310
+ }
10311
+ },
10312
+ options: {
10313
+ ...(next.provider[provider]
10314
+ && typeof next.provider[provider] === 'object'
10315
+ && next.provider[provider].options
10316
+ && typeof next.provider[provider].options === 'object'
10317
+ && !Array.isArray(next.provider[provider].options)
10318
+ ? next.provider[provider].options
10319
+ : {}),
10320
+ apiKey: key,
10321
+ baseURL: url
10322
+ }
10323
+ };
10324
+ next.model = `${provider}/${model}`;
10325
+ const enabled = Array.isArray(next.enabled_providers) ? next.enabled_providers.filter((item) => typeof item === 'string' && item.trim()) : [];
10326
+ next.enabled_providers = enabled.includes(provider) ? enabled : [...enabled, provider];
10327
+
10328
+ ensureDir(path.dirname(filePath));
10329
+ fs.writeFileSync(filePath, JSON.stringify(next, null, 2) + '\n', 'utf-8');
10330
+ return { filePath, provider, model, url };
10331
+ }
10332
+
10333
+ function parseKilocodeCommandArgs(argv = []) {
10334
+ const options = { provider: 'codexmate', command: '' };
10335
+ const positional = [];
10336
+ let passthrough = [];
10337
+ for (let i = 0; i < argv.length; i++) {
10338
+ const token = String(argv[i] || '');
10339
+ if (token === '--') {
10340
+ passthrough = argv.slice(i + 1).map(item => String(item));
10341
+ break;
10342
+ }
10343
+ if (token === '--provider') {
10344
+ const value = String(argv[i + 1] || '');
10345
+ if (!value || value.startsWith('--')) throw new Error('错误: --provider 需要一个值');
10346
+ options.provider = value;
10347
+ i += 1;
10348
+ continue;
10349
+ }
10350
+ if (token.startsWith('--provider=')) {
10351
+ options.provider = token.slice('--provider='.length);
10352
+ continue;
10353
+ }
10354
+ if (token === '--help' || token === '-h') {
10355
+ options.help = true;
10356
+ continue;
10357
+ }
10358
+ positional.push(token);
10359
+ }
10360
+ if (positional[0] === 'config' || positional[0] === 'setup') {
10361
+ options.command = positional.shift();
10362
+ }
10363
+ return {
10364
+ command: options.command,
10365
+ url: positional[0],
10366
+ key: positional[1],
10367
+ model: positional[2],
10368
+ provider: options.provider,
10369
+ help: !!options.help,
10370
+ passthrough: passthrough.length ? passthrough : positional.slice(options.command ? 3 : 0)
10371
+ };
10372
+ }
10373
+
10374
+ function printKilocodeUsage() {
10375
+ console.log('\n用法:');
10376
+ console.log(' codexmate kilo [KiloCode参数...]');
10377
+ console.log(' codexmate kilo <URL> <API密钥> <模型> [--provider <id>] [-- KiloCode参数...]');
10378
+ console.log(' codexmate kilo config <URL> <API密钥> <模型> [--provider <id>]');
10379
+ console.log('\n说明:');
10380
+ console.log(' codexmate kilo 默认启动 KiloCode;带 URL/API密钥/模型时会先写入 KiloCode provider 配置再启动。');
10381
+ console.log(' 纯配置请使用 codexmate kilo config ...');
10382
+ console.log(' 配置写入 ~/.config/kilo/kilo.jsonc(或已存在的 kilo.json)。');
10383
+ }
10384
+
10385
+ function resolveKilocodeBinary() {
10386
+ for (const bin of ['kilo', 'kilocode']) {
10387
+ if (commandExists(bin, '--version')) return bin;
10388
+ }
10389
+ return '';
10390
+ }
10391
+
10392
+ function runKilocodeCommand(args = [], options = {}) {
10393
+ const bin = resolveKilocodeBinary();
10394
+ if (!bin) {
10395
+ throw new Error('无法启动 KiloCode,请确认已安装 KiloCode CLI,并且 kilo 或 kilocode 在 PATH 中。');
10396
+ }
10397
+ const finalArgs = Array.isArray(args) ? args.filter(item => item !== undefined).map(item => String(item)) : [];
10398
+ if (options.detached === true) {
10399
+ const child = spawn(bin, finalArgs, {
10400
+ detached: true,
10401
+ stdio: 'ignore',
10402
+ windowsHide: true
10403
+ });
10404
+ child.unref();
10405
+ return { success: true, bin, args: finalArgs, pid: child.pid };
10406
+ }
10407
+ return new Promise((resolve, reject) => {
10408
+ const child = spawn(bin, finalArgs, {
10409
+ stdio: 'inherit',
10410
+ windowsHide: false
10411
+ });
10412
+ child.on('error', reject);
10413
+ child.on('exit', (code, signal) => {
10414
+ if (signal) {
10415
+ reject(new Error(`KiloCode 已终止: ${signal}`));
10416
+ return;
10417
+ }
10418
+ resolve(code || 0);
10419
+ });
10420
+ });
10421
+ }
10422
+
10423
+ async function cmdKilocode(argv = []) {
10424
+ const parsed = parseKilocodeCommandArgs(argv);
10425
+ if (parsed.help) {
10426
+ printKilocodeUsage();
10427
+ return 0;
10428
+ }
10429
+ if (parsed.command === 'config') {
10430
+ if (!parsed.url || !parsed.key || !parsed.model) {
10431
+ printKilocodeUsage();
10432
+ throw new Error('URL、API密钥和模型名称必填');
10433
+ }
10434
+ const result = writeKilocodeProviderConfig(parsed);
10435
+ console.log('✓ 已写入 KiloCode 配置');
10436
+ console.log(' 文件:', result.filePath);
10437
+ console.log(' provider:', result.provider);
10438
+ console.log(' URL:', result.url);
10439
+ console.log(' 模型:', `${result.provider}/${result.model}`);
10440
+ console.log();
10441
+ return 0;
10442
+ }
10443
+ let passthrough = parsed.passthrough;
10444
+ if (parsed.url && parsed.key && parsed.model) {
10445
+ const result = writeKilocodeProviderConfig(parsed);
10446
+ console.log('✓ 已写入 KiloCode 配置,正在启动 KiloCode');
10447
+ console.log(' 文件:', result.filePath);
10448
+ console.log(' 模型:', `${result.provider}/${result.model}`);
10449
+ console.log();
10450
+ passthrough = parsed.passthrough;
10451
+ } else if (parsed.url || parsed.key || parsed.model) {
10452
+ passthrough = [parsed.url, parsed.key, parsed.model, ...parsed.passthrough].filter(item => item !== undefined && item !== '');
10453
+ }
10454
+ return runKilocodeCommand(passthrough);
10455
+ }
10456
+
10457
+ function summarizeKilocodeConfig(config = {}, targetPath = KILOCODE_GLOBAL_JSONC_CONFIG_FILE, exists = false) {
10458
+ const providers = getRecord(config.provider);
10459
+ const modelRef = typeof config.model === 'string' ? config.model.trim() : '';
10460
+ const slash = modelRef.indexOf('/');
10461
+ const currentProvider = slash > 0 ? modelRef.slice(0, slash) : '';
10462
+ const currentModel = slash > 0 ? modelRef.slice(slash + 1) : modelRef;
10463
+ const providerNames = [...new Set([...Object.keys(providers), currentProvider].filter(Boolean))];
10464
+ const redactedConfig = JSON.parse(JSON.stringify(config && typeof config === 'object' ? config : {}));
10465
+ const redactedProviders = getRecord(redactedConfig.provider);
10466
+ for (const provider of Object.values(redactedProviders)) {
10467
+ const options = getRecord(provider && provider.options);
10468
+ if (typeof options.apiKey === 'string' && options.apiKey) {
10469
+ options.apiKey = maskKey(options.apiKey);
10470
+ }
10471
+ }
10472
+ return {
10473
+ exists: !!exists,
10474
+ targetPath,
10475
+ currentProvider,
10476
+ currentModel,
10477
+ providers: providerNames.map((name) => {
10478
+ const provider = getRecord(providers[name]);
10479
+ const options = getRecord(provider.options);
10480
+ const apiKey = typeof options.apiKey === 'string' ? options.apiKey : '';
10481
+ const modelNames = Object.keys(getRecord(provider.models));
10482
+ return {
10483
+ name,
10484
+ api: typeof provider.api === 'string' ? provider.api : '',
10485
+ baseURL: typeof options.baseURL === 'string' ? options.baseURL : '',
10486
+ hasKey: apiKey.trim().length > 0,
10487
+ apiKey: maskKey(apiKey),
10488
+ models: modelNames
10489
+ };
10490
+ }),
10491
+ content: JSON.stringify(redactedConfig, null, 2) + '\n',
10492
+ redacted: true
10493
+ };
10494
+ }
10495
+
10496
+ function readKilocodeConfigInfo() {
10497
+ try {
10498
+ const { filePath, config } = readKilocodeGlobalConfig();
10499
+ return summarizeKilocodeConfig(config, filePath, fs.existsSync(filePath));
10500
+ } catch (e) {
10501
+ return { error: e.message || '读取 KiloCode 配置失败', targetPath: KILOCODE_GLOBAL_JSONC_CONFIG_FILE };
10502
+ }
10503
+ }
10504
+
10505
+ function applyKilocodeConfig(params = {}) {
10506
+ assertToolConfigWriteAllowed('kilocode');
10507
+ try {
10508
+ const result = writeKilocodeProviderConfig({
10509
+ provider: params.provider,
10510
+ url: params.url,
10511
+ key: params.apiKey,
10512
+ model: params.model
10513
+ });
10514
+ const info = readKilocodeConfigInfo();
10515
+ return { success: true, ...result, ...info };
10516
+ } catch (e) {
10517
+ return { error: e.message || '写入 KiloCode 配置失败' };
10518
+ }
10519
+ }
10520
+
10521
+ function startKilocodeFromWeb(params = {}) {
10522
+ assertToolConfigWriteAllowed('kilocode');
10523
+ try {
10524
+ if (params && params.configure === true) {
10525
+ const saved = applyKilocodeConfig(params);
10526
+ if (saved && saved.error) return saved;
10527
+ }
10528
+ const args = Array.isArray(params.args) ? params.args.map(item => String(item)) : [];
10529
+ return runKilocodeCommand(args, { detached: true });
10530
+ } catch (e) {
10531
+ return { error: e.message || '启动 KiloCode 失败' };
10532
+ }
10533
+ }
10534
+
10172
10535
  // 添加模型
10173
10536
  function cmdAddModel(modelName, silent = false) {
10174
10537
  if (!modelName) {
@@ -12776,12 +13139,21 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
12776
13139
  case 'get-opencode-config':
12777
13140
  result = readOpencodeConfigInfo();
12778
13141
  break;
13142
+ case 'get-kilocode-config':
13143
+ result = readKilocodeConfigInfo();
13144
+ break;
12779
13145
  case 'apply-opencode-config':
12780
13146
  result = applyOpencodeConfigRaw(params || {});
12781
13147
  break;
12782
13148
  case 'update-opencode-selection':
12783
13149
  result = updateOpencodeSelection(params || {});
12784
13150
  break;
13151
+ case 'apply-kilocode-config':
13152
+ result = applyKilocodeConfig(params || {});
13153
+ break;
13154
+ case 'start-kilocode':
13155
+ result = startKilocodeFromWeb(params || {});
13156
+ break;
12785
13157
  case 'apply-claude-config':
12786
13158
  result = await applyToClaudeSettings(params.config);
12787
13159
  if (result && !result.error) {
@@ -18322,6 +18694,7 @@ function printMainHelp() {
18322
18694
  console.log(' codexmate delete <名称> 删除提供商');
18323
18695
  console.log(' codexmate claude 等同于 claude --dangerously-skip-permissions');
18324
18696
  console.log(' codexmate claude <BaseURL> <API密钥> [模型] [--target-api responses|chat_completions|ollama] 写入 Claude Code 配置');
18697
+ console.log(' codexmate kilo [URL API密钥 模型] [--provider <id>] 配置后启动 KiloCode');
18325
18698
  console.log(' codexmate auth <list|import|switch|delete|status> 认证管理');
18326
18699
  console.log(' codexmate add-model <模型> 添加模型');
18327
18700
  console.log(' codexmate delete-model <模型> 删除模型');
@@ -18417,6 +18790,11 @@ async function main() {
18417
18790
  const exitCode = await cmdClaude(args.slice(1));
18418
18791
  process.exit(exitCode);
18419
18792
  }
18793
+ case 'kilo':
18794
+ case 'kilocode': {
18795
+ const exitCode = await cmdKilocode(args.slice(1));
18796
+ process.exit(exitCode || 0);
18797
+ }
18420
18798
  case 'add-model': cmdAddModel(args[1]); break;
18421
18799
  case 'delete-model': cmdDeleteModel(args[1]); break;
18422
18800
  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.2",
3
+ "version": "0.1.4",
4
4
  "description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
5
5
  "main": "cli.js",
6
6
  "bin": {
package/web-ui/app.js CHANGED
@@ -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, openclaw: false },
400
+ toolConfigPermissionSaving: { codex: false, claude: false, opencode: false, kilocode: false, openclaw: 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);
@@ -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