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 +6 -3
- package/cli.js +382 -4
- package/package.json +1 -1
- package/web-ui/app.js +16 -2
- package/web-ui/index.html +1 -0
- package/web-ui/modules/app.computed.dashboard.mjs +10 -0
- package/web-ui/modules/app.methods.index.mjs +2 -0
- package/web-ui/modules/app.methods.install.mjs +14 -0
- package/web-ui/modules/app.methods.kilocode-config.mjs +164 -0
- package/web-ui/modules/app.methods.navigation.mjs +12 -0
- package/web-ui/modules/app.methods.startup-claude.mjs +3 -1
- package/web-ui/modules/app.methods.tool-config-permissions.mjs +4 -2
- package/web-ui/modules/app.methods.web-ui-preferences.mjs +3 -1
- package/web-ui/modules/config-mode.computed.mjs +11 -1
- package/web-ui/modules/i18n/locales/en.mjs +32 -6
- package/web-ui/modules/i18n/locales/ja.mjs +35 -10
- package/web-ui/modules/i18n/locales/vi.mjs +36 -11
- package/web-ui/modules/i18n/locales/zh-tw.mjs +37 -11
- package/web-ui/modules/i18n/locales/zh.mjs +37 -11
- package/web-ui/partials/index/layout-header.html +32 -21
- package/web-ui/partials/index/panel-config-kilocode.html +101 -0
- package/web-ui/partials/index/panel-config-openclaw.html +37 -8
- package/web-ui/partials/index/panel-config-opencode.html +1 -0
- package/web-ui/partials/index/panel-docs.html +1 -11
- package/web-ui/res/web-ui-render.precompiled.js +478 -171
- package/web-ui/styles/controls-forms.css +10 -0
- package/web-ui/styles/docs-panel.css +75 -39
- package/web-ui/styles/layout-shell.css +11 -0
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
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
|
|