@openturtle/cli 0.3.1 → 0.3.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.
- package/README.md +27 -148
- package/dist/commands/resources.js +2 -5
- package/dist/core/auto-update.js +18 -1
- package/dist/core/installations.js +41 -0
- package/dist/core/{mcp-config.js → legacy-agent-cleanup.js} +10 -42
- package/dist/core/store.js +1 -20
- package/dist/index.js +33 -84
- package/dist/installers/agents.js +84 -195
- package/dist/installers/legacy-git-cleanup.js +29 -0
- package/package.json +2 -2
- package/dist/commands/hook.js +0 -162
- package/dist/installers/git.js +0 -63
- package/dist/mcp/server.js +0 -185
- package/dist/watchers/importers.js +0 -69
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ OpenTurtle CLI 是一个本地命令行工具。它让你不安装 Desktop,也
|
|
|
12
12
|
npm install --global @openturtle/cli
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
CLI 启动时会在后台检查 npm 新版本,最多每 24 小时检查一次。检查不会阻塞当前命令;发现新版本后会通过 npm
|
|
15
|
+
CLI 启动时会在后台检查 npm 新版本,最多每 24 小时检查一次。检查不会阻塞当前命令;发现新版本后会通过 npm 自动完成全局升级,并刷新此前通过 `ot install` 安装的 Skill、Rule 和 Steering。
|
|
16
16
|
|
|
17
17
|
也可以手动检查或立即升级:
|
|
18
18
|
|
|
@@ -27,7 +27,7 @@ ot update
|
|
|
27
27
|
export OPENTURTLE_DISABLE_AUTO_UPDATE=1
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
源码运行、`pnpm link` 等开发安装不会被自动覆盖。自动更新状态记录在 `~/.openturtle/cli/update.json`。
|
|
30
|
+
源码运行、`pnpm link` 等开发安装不会被自动覆盖。自动更新状态记录在 `~/.openturtle/cli/update.json`,Agent 安装记录保存在 `~/.openturtle/cli/installations.json`。
|
|
31
31
|
|
|
32
32
|
临时使用、不全局安装时:
|
|
33
33
|
|
|
@@ -58,13 +58,7 @@ ot worklogs record --summary "完成联调" --type progress
|
|
|
58
58
|
ot report draft --today
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
ot install --target all --with-agent-hooks
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
hooks 只负责记录,不会阻止提交代码,也不会改变 AI 工具原本的行为。
|
|
61
|
+
`ot install` 只安装 CLI 使用指引。Agent 直接执行 `ot ... --json`,不需要 MCP 或 Hook。
|
|
68
62
|
|
|
69
63
|
## 常用场景
|
|
70
64
|
|
|
@@ -83,7 +77,7 @@ ot todos progress <todo-id> --message "完成接口联调,等待验收"
|
|
|
83
77
|
### 记录一条工作日志
|
|
84
78
|
|
|
85
79
|
```bash
|
|
86
|
-
ot worklogs record --summary "完成 Qoder
|
|
80
|
+
ot worklogs record --summary "完成 Qoder CLI 接入验证" --type progress
|
|
87
81
|
```
|
|
88
82
|
|
|
89
83
|
### 搜索知识
|
|
@@ -182,7 +176,6 @@ ot auth logout
|
|
|
182
176
|
| ----------------- | -------------------- |
|
|
183
177
|
| CLI command | `ot` |
|
|
184
178
|
| Package | `@openturtle/cli` |
|
|
185
|
-
| MCP server name | `ot-cli` |
|
|
186
179
|
| Project state dir | `.openturtle-cli/` |
|
|
187
180
|
| Global state dir | `~/.openturtle/cli/` |
|
|
188
181
|
|
|
@@ -239,7 +232,6 @@ ot init --update-gitignore
|
|
|
239
232
|
.openturtle-cli/
|
|
240
233
|
├── config.json
|
|
241
234
|
├── state.db
|
|
242
|
-
├── hooks/
|
|
243
235
|
├── drafts/
|
|
244
236
|
└── cache/
|
|
245
237
|
```
|
|
@@ -289,22 +281,15 @@ ot auth status
|
|
|
289
281
|
|
|
290
282
|
### 5. 安装 Agent 接入
|
|
291
283
|
|
|
292
|
-
|
|
284
|
+
安装 Agent 的 CLI Skill、Rule 或 Steering:
|
|
293
285
|
|
|
294
286
|
```bash
|
|
295
287
|
ot install --target all
|
|
296
288
|
```
|
|
297
289
|
|
|
298
|
-
安装 Agent hooks:
|
|
299
|
-
|
|
300
|
-
```bash
|
|
301
|
-
ot install --target all --with-agent-hooks
|
|
302
|
-
```
|
|
303
|
-
|
|
304
290
|
只安装某个目标:
|
|
305
291
|
|
|
306
292
|
```bash
|
|
307
|
-
ot install --target git
|
|
308
293
|
ot install --target claude
|
|
309
294
|
ot install --target codex
|
|
310
295
|
ot install --target cursor
|
|
@@ -336,92 +321,25 @@ ot uninstall --target kiro
|
|
|
336
321
|
ot uninstall --target all
|
|
337
322
|
```
|
|
338
323
|
|
|
339
|
-
卸载只会删除
|
|
324
|
+
卸载只会删除 CLI 写入的 rule/skill/steering/managed note,以及 `.openturtle-cli/` 本地状态目录。它会保留用户自己的 Agent 配置。
|
|
340
325
|
|
|
341
|
-
|
|
326
|
+
安装时会迁移旧版本:删除旧 `ot-cli` MCP 和 `OPENTURTLE-CLI` 托管 Hook 条目,同时保留用户自己的 MCP server 和 Hook。`ot uninstall --target git` 仅作为旧版 Git Hook 的清理入口保留,已经不能再安装 Git Hook。
|
|
342
327
|
|
|
343
328
|
### 6. Target 安装矩阵
|
|
344
329
|
|
|
345
|
-
| Target |
|
|
346
|
-
| ------ |
|
|
347
|
-
|
|
|
348
|
-
|
|
|
349
|
-
|
|
|
350
|
-
|
|
|
351
|
-
|
|
|
352
|
-
| Kiro | MCP、Steering | `agentSpawn`、`userPromptSubmit`、`preToolUse`、`postToolUse`、`stop` |
|
|
353
|
-
|
|
354
|
-
Qoder 和 Kiro 支持 native hook。CLI 仍保留 `import-history` 和 `watch`,用于补齐旧会话或未启用 hook 的环境。
|
|
355
|
-
|
|
356
|
-
### 7. Hooks 的作用
|
|
357
|
-
|
|
358
|
-
hooks 是本地记录入口。它们把开发过程写入 `.openturtle-cli/state.db`,用于工作日志、待办进展证据和日报草稿。
|
|
359
|
-
|
|
360
|
-
会记录:
|
|
361
|
-
|
|
362
|
-
- Git commit
|
|
363
|
-
- 用户提交给 Agent 的 prompt
|
|
364
|
-
- Agent tool call 前后
|
|
365
|
-
- Agent tool call 失败
|
|
366
|
-
- Agent session stop
|
|
367
|
-
- history import 记录
|
|
368
|
-
|
|
369
|
-
不会做:
|
|
370
|
-
|
|
371
|
-
- 不阻止 Git commit
|
|
372
|
-
- 不阻止 Agent tool call
|
|
373
|
-
- 不替用户批准危险操作
|
|
374
|
-
- 不上传私有文件内容
|
|
375
|
-
- 不修改 Desktop MCP server
|
|
376
|
-
|
|
377
|
-
Git hook 会保留已有 hook。原 hook 的退出码会保留,OpenTurtle 采集失败不会改变 commit 结果。
|
|
378
|
-
|
|
379
|
-
### 8. MCP server
|
|
380
|
-
|
|
381
|
-
MCP server 名固定为:
|
|
382
|
-
|
|
383
|
-
```text
|
|
384
|
-
ot-cli
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
启动:
|
|
388
|
-
|
|
389
|
-
```bash
|
|
390
|
-
ot mcp serve
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
tools:
|
|
394
|
-
|
|
395
|
-
```text
|
|
396
|
-
list_my_todos
|
|
397
|
-
list_goals
|
|
398
|
-
list_workspace_knowledge
|
|
399
|
-
search_workspace_knowledge
|
|
400
|
-
list_knowledge_source_files
|
|
401
|
-
read_knowledge_source_file
|
|
402
|
-
record_worklog
|
|
403
|
-
list_worklogs
|
|
404
|
-
draft_daily_report
|
|
405
|
-
update_todo_progress
|
|
406
|
-
update_todo_status
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
`list_workspace_knowledge` / `search_workspace_knowledge` 查询可复用知识对象。`list_knowledge_source_files` / `read_knowledge_source_file` 只查看知识背后的来源文件或原文文件,不把来源文件当成第二套知识库。
|
|
410
|
-
|
|
411
|
-
MCP tools 优先访问 OpenTurtle API。云端不可用时,尽量返回本地缓存或本地草稿信息,并标记 `source: "local_cache"`。
|
|
330
|
+
| Target | 安装内容 |
|
|
331
|
+
| ------ | --------------------------------- |
|
|
332
|
+
| Claude | Skill |
|
|
333
|
+
| Codex | Skill、`AGENTS.md` managed note |
|
|
334
|
+
| Cursor | Cursor Rule |
|
|
335
|
+
| Qoder | Skill |
|
|
336
|
+
| Kiro | Steering |
|
|
412
337
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
```bash
|
|
416
|
-
printf '%s\n' \
|
|
417
|
-
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
|
|
418
|
-
'{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
|
|
419
|
-
| ot mcp serve
|
|
420
|
-
```
|
|
338
|
+
### 7. CLI 接入模型
|
|
421
339
|
|
|
422
|
-
|
|
340
|
+
所有 Agent 都直接执行 `ot ... --json`。读取命令直接运行;写命令先加 `--dry-run` 检查请求,再执行正式写入。CLI 本身负责认证、后端访问、稳定 JSON、自动更新,以及升级后刷新已安装的 Agent 指引。
|
|
423
341
|
|
|
424
|
-
###
|
|
342
|
+
### 8. 资源命令
|
|
425
343
|
|
|
426
344
|
项目、团队成员和项目上下文:
|
|
427
345
|
|
|
@@ -508,8 +426,8 @@ ot meetings updates ignore <meeting-id> <update-id> --dry-run
|
|
|
508
426
|
|
|
509
427
|
```bash
|
|
510
428
|
ot worklogs list --today
|
|
511
|
-
ot worklogs record --summary "完成 Qoder
|
|
512
|
-
ot worklogs record --summary "Codex
|
|
429
|
+
ot worklogs record --summary "完成 Qoder CLI 接入验证" --type progress
|
|
430
|
+
ot worklogs record --summary "Codex CLI 登录需要用户确认" --type blocker
|
|
513
431
|
ot worklogs create --body-file worklog.json --dry-run
|
|
514
432
|
```
|
|
515
433
|
|
|
@@ -529,26 +447,7 @@ ot report draft --today
|
|
|
529
447
|
ot report draft --today --output .openturtle-cli/drafts/today.md
|
|
530
448
|
```
|
|
531
449
|
|
|
532
|
-
###
|
|
533
|
-
|
|
534
|
-
一次性导入:
|
|
535
|
-
|
|
536
|
-
```bash
|
|
537
|
-
ot import-history --target qoder
|
|
538
|
-
ot import-history --target kiro
|
|
539
|
-
ot import-history --target all
|
|
540
|
-
```
|
|
541
|
-
|
|
542
|
-
查看 watcher 路径:
|
|
543
|
-
|
|
544
|
-
```bash
|
|
545
|
-
ot watch --target qoder
|
|
546
|
-
ot watch --target kiro
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
v1 优先使用 native hook。`import-history` 和 `watch` 是兜底能力。
|
|
550
|
-
|
|
551
|
-
### 11. AI Agent 推荐流程
|
|
450
|
+
### 9. AI Agent 推荐流程
|
|
552
451
|
|
|
553
452
|
当你需要在一个新工作区启用 CLI:
|
|
554
453
|
|
|
@@ -560,12 +459,6 @@ ot install --target all
|
|
|
560
459
|
ot --json doctor
|
|
561
460
|
```
|
|
562
461
|
|
|
563
|
-
只有用户明确要求自动记录 Agent 行为时,才执行:
|
|
564
|
-
|
|
565
|
-
```bash
|
|
566
|
-
ot install --target all --with-agent-hooks
|
|
567
|
-
```
|
|
568
|
-
|
|
569
462
|
然后优先使用高层命令:
|
|
570
463
|
|
|
571
464
|
```bash
|
|
@@ -580,7 +473,7 @@ ot report draft --today
|
|
|
580
473
|
|
|
581
474
|
不要直接改 `.openturtle-cli/state.db`,除非用户明确要求底层排查。
|
|
582
475
|
|
|
583
|
-
###
|
|
476
|
+
### 10. AI Agent 禁止事项
|
|
584
477
|
|
|
585
478
|
- 不要写项目 `.openturtle/`
|
|
586
479
|
- 不要覆盖 `openturtle-knowledge`
|
|
@@ -588,13 +481,12 @@ ot report draft --today
|
|
|
588
481
|
- 不要覆盖旧 `openturtle-todo`
|
|
589
482
|
- 不要删除用户已有 Agent 配置
|
|
590
483
|
- 不要把 token 写入代码、README、commit 或日志
|
|
591
|
-
-
|
|
592
|
-
- 不要伪造 Codex hook trust state
|
|
484
|
+
- 不要安装或调用旧版 OpenTurtle MCP/Hook
|
|
593
485
|
- 不要在会议 Knowledge 尚未发布时确认项目更新
|
|
594
486
|
- 不要跳过 `meetings updates list` 直接批量确认会议草稿
|
|
595
487
|
- 不要修改 Qoder/Kiro 私有未知格式文件;只写本文列出的配置面
|
|
596
488
|
|
|
597
|
-
###
|
|
489
|
+
### 11. JSON 输出约定
|
|
598
490
|
|
|
599
491
|
CLI 默认输出 JSON,便于 AI Agent 读取。
|
|
600
492
|
|
|
@@ -624,7 +516,7 @@ CLI 默认输出 JSON,便于 AI Agent 读取。
|
|
|
624
516
|
|
|
625
517
|
token 不应该被完整打印,`auth status` 只显示脱敏 token。
|
|
626
518
|
|
|
627
|
-
###
|
|
519
|
+
### 12. 开发验证
|
|
628
520
|
|
|
629
521
|
在 `frontend` 目录执行:
|
|
630
522
|
|
|
@@ -642,7 +534,7 @@ npm pack --dry-run
|
|
|
642
534
|
|
|
643
535
|
期望包内只包含 `dist/`、`README.md` 和 `package.json` 等运行所需文件,不包含 `src/` 或 `test/`。
|
|
644
536
|
|
|
645
|
-
###
|
|
537
|
+
### 13. npm 自动发布
|
|
646
538
|
|
|
647
539
|
`.github/workflows/cli-npm-release.yml` 会在 `master` 的 CLI 文件发生变化时运行测试和构建,
|
|
648
540
|
然后以仓库版本为最低版本、基于 npm 已发布版本自动递增 patch,发布公开包 `@openturtle/cli`。
|
|
@@ -659,28 +551,15 @@ npm pack --dry-run
|
|
|
659
551
|
创建包并配置 Trusted Publisher 后,后续 `master` 发布只使用短期 OIDC 凭证。发布固定使用
|
|
660
552
|
`https://registry.npmjs.org/`,不受开发机 `npmmirror` 配置影响。
|
|
661
553
|
|
|
662
|
-
构建后 MCP smoke test:
|
|
663
|
-
|
|
664
|
-
```bash
|
|
665
|
-
printf '%s\n' \
|
|
666
|
-
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
|
|
667
|
-
'{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
|
|
668
|
-
| node apps/cli/dist/index.js mcp serve
|
|
669
|
-
```
|
|
670
|
-
|
|
671
554
|
## 常见问题
|
|
672
555
|
|
|
673
556
|
### 我没有安装 Desktop,可以用吗?
|
|
674
557
|
|
|
675
558
|
可以。CLI 的目标就是让没有 Desktop 的用户也能使用 OpenTurtle。
|
|
676
559
|
|
|
677
|
-
###
|
|
678
|
-
|
|
679
|
-
不会。它只记录信息,记录失败也不会改变提交结果。
|
|
680
|
-
|
|
681
|
-
### Qoder 和 Kiro 支持 hook 吗?
|
|
560
|
+
### 为什么升级 CLI 后 Skill 也会更新?
|
|
682
561
|
|
|
683
|
-
|
|
562
|
+
`ot install` 会记录安装过的 target、scope 和工作区。手动执行 `ot update` 或启动时自动更新 npm 包后,新版 CLI 会重新写入这些 Skill、Rule 和 Steering,并清理旧的 OpenTurtle MCP/Hook 配置。
|
|
684
563
|
|
|
685
564
|
### 为什么不用 `.openturtle/`?
|
|
686
565
|
|
|
@@ -103,16 +103,13 @@ export function doctor(workspace, target) {
|
|
|
103
103
|
project_dir: store.dir,
|
|
104
104
|
project_dir_exists: projectDirExists,
|
|
105
105
|
global_dir: path.join(process.env.HOME || '', '.openturtle', 'cli'),
|
|
106
|
-
|
|
106
|
+
integration: 'cli-skill',
|
|
107
107
|
},
|
|
108
108
|
auth: {
|
|
109
109
|
server: auth.server || null,
|
|
110
110
|
token_configured: Boolean(auth.token),
|
|
111
111
|
},
|
|
112
|
-
coexistence: {
|
|
113
|
-
avoids_project_dot_openturtle: true,
|
|
114
|
-
desktop_mcp_names_reserved: ['openturtle-knowledge', 'openturtle-automations', 'openturtle-todo'],
|
|
115
|
-
},
|
|
112
|
+
coexistence: { avoids_project_dot_openturtle: true },
|
|
116
113
|
target: target || 'all',
|
|
117
114
|
};
|
|
118
115
|
}
|
package/dist/core/auto-update.js
CHANGED
|
@@ -64,7 +64,8 @@ export async function updateCli(currentVersion, options = {}, dependencies = {})
|
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
await (dependencies.runNpmInstall || runNpmInstall)(npmUpdateArgs());
|
|
67
|
-
|
|
67
|
+
await (dependencies.refreshInstalledSkills || refreshInstalledSkills)();
|
|
68
|
+
return { current: currentVersion, latest, available: true, updated: true, skills_refreshed: true };
|
|
68
69
|
}
|
|
69
70
|
export function scheduleAutomaticUpdate(currentVersion, dependencies = {}) {
|
|
70
71
|
try {
|
|
@@ -166,6 +167,22 @@ function runNpmInstall(args) {
|
|
|
166
167
|
});
|
|
167
168
|
});
|
|
168
169
|
}
|
|
170
|
+
function refreshInstalledSkills() {
|
|
171
|
+
const entryPath = fileURLToPath(new URL('../index.js', import.meta.url));
|
|
172
|
+
return new Promise((resolve, reject) => {
|
|
173
|
+
const child = spawn(process.execPath, [entryPath, '_refresh-installs'], {
|
|
174
|
+
env: { ...process.env, OPENTURTLE_AUTO_UPDATE_WORKER: '1' },
|
|
175
|
+
stdio: 'ignore',
|
|
176
|
+
});
|
|
177
|
+
child.once('error', reject);
|
|
178
|
+
child.once('exit', (code, signal) => {
|
|
179
|
+
if (code === 0)
|
|
180
|
+
resolve();
|
|
181
|
+
else
|
|
182
|
+
reject(new Error(`Skill refresh failed (${signal || `exit ${code ?? 'unknown'}`})`));
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
}
|
|
169
186
|
function spawnUpdateWorker(workerPath, env) {
|
|
170
187
|
const child = spawn(process.execPath, [workerPath], {
|
|
171
188
|
detached: true,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { cliGlobalDir, ensureFileDir } from './paths.js';
|
|
4
|
+
export function installationsPath(homeDir) {
|
|
5
|
+
return path.join(cliGlobalDir(homeDir), 'installations.json');
|
|
6
|
+
}
|
|
7
|
+
export function readInstallations(filePath = installationsPath()) {
|
|
8
|
+
try {
|
|
9
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
10
|
+
return Array.isArray(parsed.installations) ? parsed.installations : [];
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function recordInstallation(installation, filePath = installationsPath()) {
|
|
17
|
+
const normalized = normalizeInstallation(installation);
|
|
18
|
+
const installations = readInstallations(filePath).filter((item) => installationKey(item) !== installationKey(normalized));
|
|
19
|
+
writeInstallations([...installations, normalized], filePath);
|
|
20
|
+
}
|
|
21
|
+
export function forgetInstallation(installation, filePath = installationsPath()) {
|
|
22
|
+
const key = installationKey(normalizeInstallation(installation));
|
|
23
|
+
writeInstallations(readInstallations(filePath).filter((item) => installationKey(item) !== key), filePath);
|
|
24
|
+
}
|
|
25
|
+
export function replaceInstallations(installations, filePath = installationsPath()) {
|
|
26
|
+
writeInstallations(installations.map(normalizeInstallation), filePath);
|
|
27
|
+
}
|
|
28
|
+
function normalizeInstallation(installation) {
|
|
29
|
+
return { ...installation, workspace: path.resolve(installation.workspace) };
|
|
30
|
+
}
|
|
31
|
+
function installationKey(installation) {
|
|
32
|
+
return `${installation.target}\0${installation.scope}\0${path.resolve(installation.workspace)}`;
|
|
33
|
+
}
|
|
34
|
+
function writeInstallations(installations, filePath) {
|
|
35
|
+
if (installations.length === 0) {
|
|
36
|
+
fs.rmSync(filePath, { force: true });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
ensureFileDir(filePath);
|
|
40
|
+
fs.writeFileSync(filePath, `${JSON.stringify({ installations }, null, 2)}\n`, { mode: 0o600 });
|
|
41
|
+
}
|
|
@@ -2,27 +2,15 @@ import fs from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { readJsonFile, writeJsonFile } from './json.js';
|
|
5
|
-
|
|
6
|
-
export
|
|
7
|
-
export function otMcpConfig() {
|
|
8
|
-
return { command: 'ot', args: ['mcp', 'serve'], enabled: true };
|
|
9
|
-
}
|
|
10
|
-
export function mergeJsonMcpServer(filePath, name = MCP_SERVER_NAME) {
|
|
11
|
-
const parsed = readJsonFile(filePath, {});
|
|
12
|
-
const servers = parsed.mcpServers && typeof parsed.mcpServers === 'object'
|
|
13
|
-
? parsed.mcpServers
|
|
14
|
-
: {};
|
|
15
|
-
servers[name] = otMcpConfig();
|
|
16
|
-
writeJsonFile(filePath, { ...parsed, mcpServers: servers });
|
|
17
|
-
}
|
|
18
|
-
export function removeJsonMcpServer(filePath, name = MCP_SERVER_NAME, pruneStopDir) {
|
|
5
|
+
const LEGACY_SERVER_NAME = 'ot-cli';
|
|
6
|
+
export function removeLegacyJsonServer(filePath, pruneStopDir) {
|
|
19
7
|
if (!fs.existsSync(filePath))
|
|
20
8
|
return;
|
|
21
9
|
const parsed = readJsonFile(filePath, {});
|
|
22
10
|
const servers = parsed.mcpServers && typeof parsed.mcpServers === 'object'
|
|
23
11
|
? { ...parsed.mcpServers }
|
|
24
12
|
: {};
|
|
25
|
-
delete servers[
|
|
13
|
+
delete servers[LEGACY_SERVER_NAME];
|
|
26
14
|
const next = { ...parsed, mcpServers: servers };
|
|
27
15
|
if (Object.keys(servers).length === 0)
|
|
28
16
|
delete next.mcpServers;
|
|
@@ -33,7 +21,7 @@ export function removeJsonMcpServer(filePath, name = MCP_SERVER_NAME, pruneStopD
|
|
|
33
21
|
}
|
|
34
22
|
writeJsonFile(filePath, next);
|
|
35
23
|
}
|
|
36
|
-
export function
|
|
24
|
+
export function claudeLegacyServerPath(scope, workspace, homeDir = os.homedir()) {
|
|
37
25
|
return scope === 'user'
|
|
38
26
|
? path.join(homeDir, '.claude', 'mcp_servers.json')
|
|
39
27
|
: path.join(workspace, '.claude', 'mcp_servers.json');
|
|
@@ -46,42 +34,23 @@ export function claudeSettingsPath(scope, workspace, homeDir = os.homedir()) {
|
|
|
46
34
|
export function codexConfigPath(scope, workspace, homeDir = os.homedir()) {
|
|
47
35
|
return scope === 'user' ? path.join(homeDir, '.codex', 'config.toml') : path.join(workspace, '.codex', 'config.toml');
|
|
48
36
|
}
|
|
49
|
-
export function
|
|
37
|
+
export function cursorLegacyServerPath(workspace) {
|
|
50
38
|
return path.join(workspace, '.mcp.json');
|
|
51
39
|
}
|
|
52
|
-
export function
|
|
40
|
+
export function qoderLegacyServerPath(scope, workspace, homeDir = os.homedir()) {
|
|
53
41
|
return scope === 'user' ? path.join(homeDir, '.qoder.json') : path.join(workspace, '.mcp.json');
|
|
54
42
|
}
|
|
55
|
-
export function
|
|
43
|
+
export function kiroLegacyServerPath(scope, workspace, homeDir = os.homedir()) {
|
|
56
44
|
return scope === 'user'
|
|
57
45
|
? path.join(homeDir, '.kiro', 'settings', 'mcp.json')
|
|
58
46
|
: path.join(workspace, '.kiro', 'settings', 'mcp.json');
|
|
59
47
|
}
|
|
60
|
-
export function
|
|
61
|
-
ensureFileDir(filePath);
|
|
62
|
-
const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : '';
|
|
63
|
-
const withoutServer = stripTomlTable(existing, `mcp_servers.${MCP_SERVER_NAME}`);
|
|
64
|
-
const block = [
|
|
65
|
-
`[mcp_servers.${MCP_SERVER_NAME}]`,
|
|
66
|
-
`command = "ot"`,
|
|
67
|
-
`args = ["mcp", "serve"]`,
|
|
68
|
-
`enabled = true`,
|
|
69
|
-
].join('\n');
|
|
70
|
-
fs.writeFileSync(filePath, `${[withoutServer.trimEnd(), block].filter(Boolean).join('\n\n')}\n`, 'utf-8');
|
|
71
|
-
}
|
|
72
|
-
export function appendCodexHookBlocks(filePath, hookBlocks) {
|
|
73
|
-
ensureFileDir(filePath);
|
|
74
|
-
const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : '';
|
|
75
|
-
const cleaned = removeManagedTomlBlock(existing, 'hooks');
|
|
76
|
-
const block = [`# >>> OPENTURTLE-CLI:hooks`, hookBlocks.trimEnd(), `# <<< OPENTURTLE-CLI:hooks`].join('\n');
|
|
77
|
-
fs.writeFileSync(filePath, `${[cleaned.trimEnd(), block].filter(Boolean).join('\n\n')}\n`, 'utf-8');
|
|
78
|
-
}
|
|
79
|
-
export function removeCodexManagedConfig(filePath, pruneStopDir) {
|
|
48
|
+
export function removeLegacyCodexConfig(filePath, pruneStopDir) {
|
|
80
49
|
if (!fs.existsSync(filePath))
|
|
81
50
|
return;
|
|
82
51
|
const existing = fs.readFileSync(filePath, 'utf-8');
|
|
83
52
|
const withoutHooks = removeManagedTomlBlock(existing, 'hooks');
|
|
84
|
-
const withoutServer = stripTomlTable(withoutHooks, `mcp_servers.${
|
|
53
|
+
const withoutServer = stripTomlTable(withoutHooks, `mcp_servers.${LEGACY_SERVER_NAME}`);
|
|
85
54
|
const next = withoutServer.trim();
|
|
86
55
|
if (!next) {
|
|
87
56
|
fs.rmSync(filePath, { force: true });
|
|
@@ -99,9 +68,8 @@ function stripTomlTable(content, tableName) {
|
|
|
99
68
|
let skipping = false;
|
|
100
69
|
const tablePattern = new RegExp(`^\\s*\\[${escapeRegExp(tableName)}\\]\\s*$`);
|
|
101
70
|
for (const line of lines) {
|
|
102
|
-
if (/^\s*\[/.test(line))
|
|
71
|
+
if (/^\s*\[/.test(line))
|
|
103
72
|
skipping = tablePattern.test(line);
|
|
104
|
-
}
|
|
105
73
|
if (!skipping)
|
|
106
74
|
kept.push(line);
|
|
107
75
|
}
|
package/dist/core/store.js
CHANGED
|
@@ -5,14 +5,13 @@ import { projectWorklogDir } from './paths.js';
|
|
|
5
5
|
export function initProjectStore(workspacePath) {
|
|
6
6
|
const workspace = path.resolve(workspacePath);
|
|
7
7
|
const dir = projectWorklogDir(workspace);
|
|
8
|
-
fs.mkdirSync(path.join(dir, 'hooks'), { recursive: true });
|
|
9
8
|
fs.mkdirSync(path.join(dir, 'drafts'), { recursive: true });
|
|
10
9
|
fs.mkdirSync(path.join(dir, 'cache'), { recursive: true });
|
|
11
10
|
const configPath = path.join(dir, 'config.json');
|
|
12
11
|
if (!fs.existsSync(configPath)) {
|
|
13
12
|
fs.writeFileSync(configPath, `${JSON.stringify({
|
|
14
13
|
version: 1,
|
|
15
|
-
|
|
14
|
+
integration: 'cli-skill',
|
|
16
15
|
createdAt: new Date().toISOString(),
|
|
17
16
|
}, null, 2)}\n`, 'utf-8');
|
|
18
17
|
}
|
|
@@ -37,17 +36,6 @@ function migrate(db) {
|
|
|
37
36
|
dedupe_key TEXT NOT NULL UNIQUE
|
|
38
37
|
);
|
|
39
38
|
|
|
40
|
-
CREATE TABLE IF NOT EXISTS hook_runs (
|
|
41
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
42
|
-
target TEXT NOT NULL,
|
|
43
|
-
event_type TEXT NOT NULL,
|
|
44
|
-
exit_code INTEGER NOT NULL,
|
|
45
|
-
duration_ms INTEGER NOT NULL,
|
|
46
|
-
raw_input_json TEXT,
|
|
47
|
-
error TEXT,
|
|
48
|
-
occurred_at TEXT NOT NULL
|
|
49
|
-
);
|
|
50
|
-
|
|
51
39
|
CREATE TABLE IF NOT EXISTS commits (
|
|
52
40
|
hash TEXT PRIMARY KEY,
|
|
53
41
|
message TEXT,
|
|
@@ -81,13 +69,6 @@ export function upsertEvent(store, event) {
|
|
|
81
69
|
.run(event.type, event.source, event.sourceId || null, event.workspacePath || null, event.summary || null, event.rawJson == null ? null : JSON.stringify(event.rawJson), occurredAt, event.dedupeKey);
|
|
82
70
|
return { inserted: result.changes > 0 };
|
|
83
71
|
}
|
|
84
|
-
export function recordHookRun(store, run) {
|
|
85
|
-
store.db
|
|
86
|
-
.prepare(`INSERT INTO hook_runs
|
|
87
|
-
(target, event_type, exit_code, duration_ms, raw_input_json, error, occurred_at)
|
|
88
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`)
|
|
89
|
-
.run(run.target, run.eventType, run.exitCode, run.durationMs, run.rawInputJson || null, run.error || null, run.occurredAt || new Date().toISOString());
|
|
90
|
-
}
|
|
91
72
|
export function listEvents(store, limit = 100) {
|
|
92
73
|
return store.db
|
|
93
74
|
.prepare('SELECT * FROM events ORDER BY occurred_at ASC, id ASC LIMIT ?')
|