codexmate 0.0.2 → 0.0.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/cli.js CHANGED
@@ -2,7 +2,8 @@
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
4
  const os = require('os');
5
- const toml = require('@iarna/toml');
5
+ const toml = require('@iarna/toml');
6
+ const JSON5 = require('json5');
6
7
  const { exec, execSync } = require('child_process');
7
8
  const http = require('http');
8
9
  const https = require('https');
@@ -13,16 +14,19 @@ const PORT = 3737;
13
14
  // ============================================================================
14
15
  // 配置
15
16
  // ============================================================================
16
- const CONFIG_DIR = path.join(os.homedir(), '.codex');
17
- const CONFIG_FILE = path.join(CONFIG_DIR, 'config.toml');
18
- const AUTH_FILE = path.join(CONFIG_DIR, 'auth.json');
19
- const MODELS_FILE = path.join(CONFIG_DIR, 'models.json');
20
- const CURRENT_MODELS_FILE = path.join(CONFIG_DIR, 'provider-current-models.json');
21
- const INIT_MARK_FILE = path.join(CONFIG_DIR, 'codexmate-init.json');
22
- const CODEX_SESSIONS_DIR = path.join(CONFIG_DIR, 'sessions');
23
- const CLAUDE_DIR = path.join(os.homedir(), '.claude');
24
- const CLAUDE_SETTINGS_FILE = path.join(CLAUDE_DIR, 'settings.json');
25
- const CLAUDE_PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects');
17
+ const CONFIG_DIR = path.join(os.homedir(), '.codex');
18
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.toml');
19
+ const AUTH_FILE = path.join(CONFIG_DIR, 'auth.json');
20
+ const MODELS_FILE = path.join(CONFIG_DIR, 'models.json');
21
+ const CURRENT_MODELS_FILE = path.join(CONFIG_DIR, 'provider-current-models.json');
22
+ const INIT_MARK_FILE = path.join(CONFIG_DIR, 'codexmate-init.json');
23
+ const CODEX_SESSIONS_DIR = path.join(CONFIG_DIR, 'sessions');
24
+ const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw');
25
+ const OPENCLAW_CONFIG_FILE = path.join(OPENCLAW_DIR, 'openclaw.json');
26
+ const OPENCLAW_WORKSPACE_DIR = path.join(OPENCLAW_DIR, 'workspace');
27
+ const CLAUDE_DIR = path.join(os.homedir(), '.claude');
28
+ const CLAUDE_SETTINGS_FILE = path.join(CLAUDE_DIR, 'settings.json');
29
+ const CLAUDE_PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects');
26
30
 
27
31
  const DEFAULT_MODELS = ['gpt-5.3-codex', 'gpt-5.1-codex-max', 'gpt-4-turbo', 'gpt-4'];
28
32
  const SPEED_TEST_TIMEOUT_MS = 8000;
@@ -34,8 +38,8 @@ const SESSION_TITLE_READ_BYTES = 64 * 1024;
34
38
  const CODEXMATE_MANAGED_MARKER = '# codexmate-managed: true';
35
39
  const SESSION_LIST_CACHE_TTL_MS = 4000;
36
40
  const SESSION_SUMMARY_READ_BYTES = 256 * 1024;
37
- const SESSION_CONTENT_READ_BYTES = SESSION_SUMMARY_READ_BYTES;
38
- const DEFAULT_CONTENT_SCAN_LIMIT = 10;
41
+ const SESSION_CONTENT_READ_BYTES = SESSION_SUMMARY_READ_BYTES;
42
+ const DEFAULT_CONTENT_SCAN_LIMIT = 10;
39
43
  const SESSION_SCAN_FACTOR = 4;
40
44
  const SESSION_SCAN_MIN_FILES = 800;
41
45
  const MAX_SESSION_PATH_LIST_SIZE = 2000;
@@ -228,11 +232,11 @@ function readAgentsFile(params = {}) {
228
232
  }
229
233
  }
230
234
 
231
- function applyAgentsFile(params = {}) {
232
- const filePath = resolveAgentsFilePath(params);
233
- const dirCheck = validateAgentsBaseDir(filePath);
234
- if (dirCheck.error) {
235
- return { error: dirCheck.error };
235
+ function applyAgentsFile(params = {}) {
236
+ const filePath = resolveAgentsFilePath(params);
237
+ const dirCheck = validateAgentsBaseDir(filePath);
238
+ if (dirCheck.error) {
239
+ return { error: dirCheck.error };
236
240
  }
237
241
 
238
242
  const content = typeof params.content === 'string' ? params.content : '';
@@ -243,10 +247,165 @@ function applyAgentsFile(params = {}) {
243
247
  try {
244
248
  fs.writeFileSync(filePath, finalContent, 'utf-8');
245
249
  return { success: true, path: filePath };
246
- } catch (e) {
247
- return { error: `写入 AGENTS.md 失败: ${e.message}` };
248
- }
249
- }
250
+ } catch (e) {
251
+ return { error: `写入 AGENTS.md 失败: ${e.message}` };
252
+ }
253
+ }
254
+
255
+ function resolveHomePath(input) {
256
+ const raw = typeof input === 'string' ? input.trim() : '';
257
+ if (!raw) return '';
258
+ if (raw === '~') return os.homedir();
259
+ if (raw.startsWith('~/') || raw.startsWith('~\\')) {
260
+ return path.join(os.homedir(), raw.slice(2));
261
+ }
262
+ return raw;
263
+ }
264
+
265
+ function resolveOpenclawWorkspaceDir(config) {
266
+ const workspace = config
267
+ && config.agents
268
+ && config.agents.defaults
269
+ && typeof config.agents.defaults.workspace === 'string'
270
+ ? config.agents.defaults.workspace
271
+ : '';
272
+ const resolved = resolveHomePath(workspace);
273
+ if (!resolved) {
274
+ return OPENCLAW_WORKSPACE_DIR;
275
+ }
276
+ if (path.isAbsolute(resolved)) {
277
+ return resolved;
278
+ }
279
+ return path.join(OPENCLAW_DIR, resolved);
280
+ }
281
+
282
+ function readOpenclawConfigFile() {
283
+ const filePath = OPENCLAW_CONFIG_FILE;
284
+ if (!fs.existsSync(filePath)) {
285
+ return {
286
+ exists: false,
287
+ path: filePath,
288
+ content: '',
289
+ lineEnding: os.EOL === '\r\n' ? '\r\n' : '\n'
290
+ };
291
+ }
292
+
293
+ try {
294
+ const raw = fs.readFileSync(filePath, 'utf-8');
295
+ return {
296
+ exists: true,
297
+ path: filePath,
298
+ content: stripUtf8Bom(raw),
299
+ lineEnding: detectLineEnding(raw)
300
+ };
301
+ } catch (e) {
302
+ return { error: `读取 OpenClaw 配置失败: ${e.message}` };
303
+ }
304
+ }
305
+
306
+ function parseOpenclawConfigText(content) {
307
+ const raw = stripUtf8Bom(typeof content === 'string' ? content : '');
308
+ if (!raw.trim()) {
309
+ return { ok: false, error: 'OpenClaw 配置内容不能为空' };
310
+ }
311
+ try {
312
+ const parsed = JSON5.parse(raw);
313
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
314
+ return { ok: false, error: '配置格式错误(根节点必须是对象)' };
315
+ }
316
+ return { ok: true, data: parsed };
317
+ } catch (e) {
318
+ return { ok: false, error: `配置解析失败: ${e.message}` };
319
+ }
320
+ }
321
+
322
+ function getOpenclawWorkspaceInfo() {
323
+ const readResult = readOpenclawConfigFile();
324
+ let workspaceDir = OPENCLAW_WORKSPACE_DIR;
325
+ let configError = readResult.error || '';
326
+ if (!configError && readResult.exists && readResult.content.trim()) {
327
+ const parsed = parseOpenclawConfigText(readResult.content);
328
+ if (parsed.ok) {
329
+ workspaceDir = resolveOpenclawWorkspaceDir(parsed.data);
330
+ } else {
331
+ configError = parsed.error || '';
332
+ }
333
+ }
334
+ return {
335
+ workspaceDir,
336
+ configError,
337
+ configPath: readResult.path || OPENCLAW_CONFIG_FILE
338
+ };
339
+ }
340
+
341
+ function readOpenclawAgentsFile() {
342
+ const workspaceInfo = getOpenclawWorkspaceInfo();
343
+ const baseDir = workspaceInfo.workspaceDir;
344
+ const filePath = path.join(baseDir, AGENTS_FILE_NAME);
345
+
346
+ if (!fs.existsSync(baseDir)) {
347
+ return {
348
+ exists: false,
349
+ path: filePath,
350
+ content: '',
351
+ lineEnding: os.EOL === '\r\n' ? '\r\n' : '\n',
352
+ workspaceDir: baseDir,
353
+ configError: workspaceInfo.configError,
354
+ baseDirMissing: true
355
+ };
356
+ }
357
+
358
+ const readResult = readAgentsFile({ baseDir });
359
+ return {
360
+ ...readResult,
361
+ workspaceDir: baseDir,
362
+ configError: workspaceInfo.configError
363
+ };
364
+ }
365
+
366
+ function applyOpenclawAgentsFile(params = {}) {
367
+ const workspaceInfo = getOpenclawWorkspaceInfo();
368
+ const baseDir = workspaceInfo.workspaceDir;
369
+ ensureDir(baseDir);
370
+ const result = applyAgentsFile({
371
+ ...params,
372
+ baseDir
373
+ });
374
+ return {
375
+ ...result,
376
+ workspaceDir: baseDir,
377
+ configError: workspaceInfo.configError
378
+ };
379
+ }
380
+
381
+ function applyOpenclawConfig(params = {}) {
382
+ const content = typeof params.content === 'string' ? params.content : '';
383
+ const lineEnding = params.lineEnding === '\r\n' ? '\r\n' : '\n';
384
+ const normalized = normalizeLineEnding(content, lineEnding);
385
+ const parsed = parseOpenclawConfigText(normalized);
386
+ if (!parsed.ok) {
387
+ return { success: false, error: parsed.error };
388
+ }
389
+
390
+ try {
391
+ ensureDir(OPENCLAW_DIR);
392
+ const backupPath = backupFileIfNeededOnce(OPENCLAW_CONFIG_FILE);
393
+ fs.writeFileSync(OPENCLAW_CONFIG_FILE, normalized, 'utf-8');
394
+ const result = {
395
+ success: true,
396
+ targetPath: OPENCLAW_CONFIG_FILE
397
+ };
398
+ if (backupPath) {
399
+ result.backupPath = backupPath;
400
+ }
401
+ return result;
402
+ } catch (e) {
403
+ return {
404
+ success: false,
405
+ error: e.message || '写入 OpenClaw 配置失败'
406
+ };
407
+ }
408
+ }
250
409
 
251
410
  function readJsonObjectFromFile(filePath, fallback = {}) {
252
411
  if (!fs.existsSync(filePath)) {
@@ -996,23 +1155,23 @@ function scanSessionContentForQuery(session, tokens, options = {}) {
996
1155
  ? Math.max(0, Number(options.snippetLimit))
997
1156
  : 0;
998
1157
 
999
- const messages = [];
1000
- for (const record of records) {
1001
- const message = extractMessageFromRecord(record, session.source);
1002
- if (!message || !message.text) {
1003
- continue;
1004
- }
1005
- messages.push(message);
1006
- }
1007
-
1008
- const filteredMessages = roleFilter === 'system'
1009
- ? messages
1010
- : removeLeadingSystemMessage(messages);
1011
-
1012
- let count = 0;
1013
- const snippets = [];
1014
-
1015
- for (const message of filteredMessages) {
1158
+ const messages = [];
1159
+ for (const record of records) {
1160
+ const message = extractMessageFromRecord(record, session.source);
1161
+ if (!message || !message.text) {
1162
+ continue;
1163
+ }
1164
+ messages.push(message);
1165
+ }
1166
+
1167
+ const filteredMessages = roleFilter === 'system'
1168
+ ? messages
1169
+ : removeLeadingSystemMessage(messages);
1170
+
1171
+ let count = 0;
1172
+ const snippets = [];
1173
+
1174
+ for (const message of filteredMessages) {
1016
1175
  if (roleFilter !== 'all' && message.role !== roleFilter) {
1017
1176
  continue;
1018
1177
  }
@@ -1039,11 +1198,11 @@ function applySessionQueryFilter(sessions, options = {}) {
1039
1198
  }
1040
1199
 
1041
1200
  const mode = normalizeQueryMode(options.queryMode);
1042
- const scope = normalizeQueryScope(options.queryScope);
1043
- const roleFilter = normalizeRoleFilter(options.roleFilter);
1044
- const contentScanLimit = Number.isFinite(Number(options.contentScanLimit))
1045
- ? Math.max(1, Number(options.contentScanLimit))
1046
- : DEFAULT_CONTENT_SCAN_LIMIT;
1201
+ const scope = normalizeQueryScope(options.queryScope);
1202
+ const roleFilter = normalizeRoleFilter(options.roleFilter);
1203
+ const contentScanLimit = Number.isFinite(Number(options.contentScanLimit))
1204
+ ? Math.max(1, Number(options.contentScanLimit))
1205
+ : DEFAULT_CONTENT_SCAN_LIMIT;
1047
1206
  const contentScanBytes = Number.isFinite(Number(options.contentScanBytes))
1048
1207
  ? Math.max(1024, Number(options.contentScanBytes))
1049
1208
  : SESSION_CONTENT_READ_BYTES;
@@ -1095,7 +1254,7 @@ function applySessionQueryFilter(sessions, options = {}) {
1095
1254
 
1096
1255
  return results;
1097
1256
  }
1098
- function collectRecentJsonlFiles(rootDir, options = {}) {
1257
+ function collectRecentJsonlFiles(rootDir, options = {}) {
1099
1258
  if (!fs.existsSync(rootDir)) {
1100
1259
  return [];
1101
1260
  }
@@ -1553,18 +1712,18 @@ function listAllSessions(params = {}) {
1553
1712
  sessions = sessions.filter(item => matchesSessionPathFilter(item, normalizedPathFilter));
1554
1713
  }
1555
1714
 
1556
- let result = sessions;
1557
- if (hasQuery) {
1558
- result = applySessionQueryFilter(result, {
1559
- tokens: queryTokens,
1560
- queryMode: params.queryMode,
1561
- queryScope: params.queryScope,
1562
- roleFilter: params.roleFilter,
1563
- contentScanLimit: params.contentScanLimit,
1564
- contentScanBytes: params.contentScanBytes
1565
- });
1566
- }
1567
- result = mergeAndLimitSessions(result, limit);
1715
+ let result = sessions;
1716
+ if (hasQuery) {
1717
+ result = applySessionQueryFilter(result, {
1718
+ tokens: queryTokens,
1719
+ queryMode: params.queryMode,
1720
+ queryScope: params.queryScope,
1721
+ roleFilter: params.roleFilter,
1722
+ contentScanLimit: params.contentScanLimit,
1723
+ contentScanBytes: params.contentScanBytes
1724
+ });
1725
+ }
1726
+ result = mergeAndLimitSessions(result, limit);
1568
1727
  if (!hasQuery) {
1569
1728
  setSessionListCache(cacheKey, result);
1570
1729
  }
@@ -2853,15 +3012,27 @@ function cmdStart() {
2853
3012
  case 'apply-config-template':
2854
3013
  result = applyConfigTemplate(params || {});
2855
3014
  break;
2856
- case 'get-agents-file':
2857
- result = readAgentsFile(params || {});
2858
- break;
2859
- case 'apply-agents-file':
2860
- result = applyAgentsFile(params || {});
2861
- break;
2862
- case 'switch':
2863
- case 'use':
2864
- case 'add':
3015
+ case 'get-agents-file':
3016
+ result = readAgentsFile(params || {});
3017
+ break;
3018
+ case 'apply-agents-file':
3019
+ result = applyAgentsFile(params || {});
3020
+ break;
3021
+ case 'get-openclaw-config':
3022
+ result = readOpenclawConfigFile();
3023
+ break;
3024
+ case 'apply-openclaw-config':
3025
+ result = applyOpenclawConfig(params || {});
3026
+ break;
3027
+ case 'get-openclaw-agents-file':
3028
+ result = readOpenclawAgentsFile();
3029
+ break;
3030
+ case 'apply-openclaw-agents-file':
3031
+ result = applyOpenclawAgentsFile(params || {});
3032
+ break;
3033
+ case 'switch':
3034
+ case 'use':
3035
+ case 'add':
2865
3036
  case 'delete':
2866
3037
  case 'update':
2867
3038
  result = { error: 'Codex 配置改动已切换为模板确认模式,请使用模板编辑器并手动确认应用。' };
@@ -0,0 +1,65 @@
1
+ @echo off
2
+ setlocal enableextensions
3
+ set "VERBOSE_ECHO=0"
4
+ if /i "%VERBOSE%"=="1" (
5
+ set "VERBOSE_ECHO=1"
6
+ @echo on
7
+ set "NPM_CONFIG_LOGLEVEL=notice"
8
+ )
9
+
10
+ set "REGISTRY=https://registry.npmjs.org/"
11
+ set "PUBLISH_RC=1"
12
+ for %%I in ("%~dp0..") do set "ROOT_DIR=%%~fI"
13
+ set "LOCAL_NPMRC=%ROOT_DIR%\.npmrc"
14
+
15
+ where npm >nul 2>&1
16
+ if errorlevel 1 (
17
+ echo npm not found in PATH.
18
+ exit /b 1
19
+ )
20
+
21
+ if "%NPM_TOKEN%"=="" (
22
+ if exist "%LOCAL_NPMRC%" (
23
+ if "%VERBOSE_ECHO%"=="1" @echo off
24
+ for /f "usebackq tokens=2,* delims==" %%A in (`findstr /i /c:"_authToken=" "%LOCAL_NPMRC%"`) do (
25
+ if "%%B"=="" (@set "NPM_TOKEN=%%A") else (@set "NPM_TOKEN=%%A=%%B")
26
+ )
27
+ if "%VERBOSE_ECHO%"=="1" @echo on
28
+ )
29
+ )
30
+ if "%VERBOSE_ECHO%"=="1" @echo off
31
+ if "%NPM_TOKEN%"=="" (
32
+ echo NPM_TOKEN is not set and no token found in %LOCAL_NPMRC%.
33
+ exit /b 1
34
+ )
35
+ if "%VERBOSE_ECHO%"=="1" @echo on
36
+
37
+ set "TMP_NPMRC=%TEMP%\npmrc-codexmate-publish-%RANDOM%.tmp"
38
+ if "%VERBOSE_ECHO%"=="1" @echo off
39
+ > "%TMP_NPMRC%" echo //registry.npmjs.org/:_authToken=%NPM_TOKEN%
40
+ if "%VERBOSE_ECHO%"=="1" @echo on
41
+ set "NPM_CONFIG_USERCONFIG=%TMP_NPMRC%"
42
+ set "NPM_CONFIG_REGISTRY=%REGISTRY%"
43
+
44
+ call npm whoami --registry %REGISTRY%
45
+ if errorlevel 1 goto cleanup
46
+
47
+ echo [step] npm pack --dry-run
48
+ call npm pack --dry-run --registry %REGISTRY%
49
+ if errorlevel 1 goto cleanup
50
+
51
+ echo [step] npm publish
52
+ if not "%~1"=="" (
53
+ call npm publish --registry %REGISTRY% --otp %~1
54
+ ) else if not "%NPM_OTP%"=="" (
55
+ call npm publish --registry %REGISTRY% --otp %NPM_OTP%
56
+ ) else (
57
+ call npm publish --registry %REGISTRY%
58
+ )
59
+ set "PUBLISH_RC=%ERRORLEVEL%"
60
+
61
+ goto cleanup
62
+
63
+ :cleanup
64
+ if exist "%TMP_NPMRC%" del /f /q "%TMP_NPMRC%"
65
+ exit /b %PUBLISH_RC%
package/package.json CHANGED
@@ -1,25 +1,27 @@
1
- {
2
- "name": "codexmate",
3
- "version": "0.0.2",
4
- "description": "Codex 提供商管理 CLI 工具",
5
- "bin": {
6
- "codexmate": "./cli.js"
7
- },
8
- "scripts": {
9
- "start": "node cli.js"
10
- },
11
- "dependencies": {
12
- "@iarna/toml": "^2.2.5"
13
- },
14
- "engines": {
15
- "node": ">=14"
16
- },
17
- "keywords": [
18
- "codex",
19
- "ai",
20
- "llm",
21
- "cli"
22
- ],
23
- "author": "ymkiux",
24
- "license": "Apache-2.0"
25
- }
1
+ {
2
+ "name": "codexmate",
3
+ "version": "0.0.4",
4
+ "description": "Codex 提供商管理 CLI 工具",
5
+ "bin": {
6
+ "codexmate": "./cli.js"
7
+ },
8
+ "scripts": {
9
+ "start": "node cli.js"
10
+ },
11
+ "dependencies": {
12
+ "@iarna/toml": "^2.2.5",
13
+ "json5": "^2.2.3"
14
+ },
15
+ "engines": {
16
+ "node": ">=14"
17
+ },
18
+ "keywords": [
19
+ "codex",
20
+ "ai",
21
+ "llm",
22
+ "cli"
23
+ ],
24
+ "author": "ymkiux",
25
+ "license": "Apache-2.0"
26
+ }
27
+
Binary file