dodo-mcp 1.2.1 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +37 -6
  2. package/dist/cli/dataRecovery.js +38 -0
  3. package/dist/cli/dataRecovery.js.map +1 -0
  4. package/dist/cli/deployment.js +61 -0
  5. package/dist/cli/deployment.js.map +1 -0
  6. package/dist/cli/main.js +36 -1
  7. package/dist/cli/main.js.map +1 -1
  8. package/dist/config/globalConfig.js +3 -5
  9. package/dist/config/globalConfig.js.map +1 -1
  10. package/dist/errors.js +1 -1
  11. package/dist/errors.js.map +1 -1
  12. package/dist/ipc/server.js +2 -0
  13. package/dist/ipc/server.js.map +1 -1
  14. package/dist/ipc/timeouts.js +5 -0
  15. package/dist/ipc/timeouts.js.map +1 -0
  16. package/dist/platform/privateFs.js +32 -3
  17. package/dist/platform/privateFs.js.map +1 -1
  18. package/dist/platform/windowsPrivateAcl.js +90 -0
  19. package/dist/platform/windowsPrivateAcl.js.map +1 -0
  20. package/dist/security/outboundAddress.js +20 -0
  21. package/dist/security/outboundAddress.js.map +1 -0
  22. package/dist/server/aiAdmin.js +21 -24
  23. package/dist/server/aiAdmin.js.map +1 -1
  24. package/dist/server/appServer.js +3 -6
  25. package/dist/server/appServer.js.map +1 -1
  26. package/dist/server/bootstrap.js +14 -0
  27. package/dist/server/bootstrap.js.map +1 -1
  28. package/dist/server/configSession.js +21 -0
  29. package/dist/server/configSession.js.map +1 -0
  30. package/dist/server/configUi/index.html +3 -0
  31. package/dist/server/configUi/ui/dataRecovery.js +90 -0
  32. package/dist/server/configUi/ui/deployment.js +111 -0
  33. package/dist/server/configUi/ui/recovery.js +167 -0
  34. package/dist/server/configUi/workbench.js +23 -7
  35. package/dist/server/installationRuntime.js +3 -1
  36. package/dist/server/installationRuntime.js.map +1 -1
  37. package/dist/server/instructions.js.map +1 -1
  38. package/dist/server/ipcDispatch.js +219 -0
  39. package/dist/server/ipcDispatch.js.map +1 -1
  40. package/dist/server/localConfig.js +34 -11
  41. package/dist/server/localConfig.js.map +1 -1
  42. package/dist/server/remoteConfig.js +23 -13
  43. package/dist/server/remoteConfig.js.map +1 -1
  44. package/dist/server/stdioServer.js +2 -5
  45. package/dist/server/stdioServer.js.map +1 -1
  46. package/dist/services/ai/network.js +1 -18
  47. package/dist/services/ai/network.js.map +1 -1
  48. package/dist/services/ai/subagents.js +5 -2
  49. package/dist/services/ai/subagents.js.map +1 -1
  50. package/dist/services/assistance/verification.js +43 -10
  51. package/dist/services/assistance/verification.js.map +1 -1
  52. package/dist/services/changes/applier.js +472 -297
  53. package/dist/services/changes/applier.js.map +1 -1
  54. package/dist/services/git/gitService.js +150 -34
  55. package/dist/services/git/gitService.js.map +1 -1
  56. package/dist/services/jobs/jobManager.js +59 -2
  57. package/dist/services/jobs/jobManager.js.map +1 -1
  58. package/dist/services/jobs/spool.js +9 -3
  59. package/dist/services/jobs/spool.js.map +1 -1
  60. package/dist/services/multimodal/mediaService.js +6 -6
  61. package/dist/services/multimodal/mediaService.js.map +1 -1
  62. package/dist/services/recovery/configKeys.js +115 -0
  63. package/dist/services/recovery/configKeys.js.map +1 -0
  64. package/dist/services/recovery/configVault.js +361 -0
  65. package/dist/services/recovery/configVault.js.map +1 -0
  66. package/dist/services/recovery/contracts.js +24 -0
  67. package/dist/services/recovery/contracts.js.map +1 -0
  68. package/dist/services/recovery/databaseAwareness.js +179 -0
  69. package/dist/services/recovery/databaseAwareness.js.map +1 -0
  70. package/dist/services/recovery/deploymentArchive.js +129 -0
  71. package/dist/services/recovery/deploymentArchive.js.map +1 -0
  72. package/dist/services/recovery/deploymentContracts.js +70 -0
  73. package/dist/services/recovery/deploymentContracts.js.map +1 -0
  74. package/dist/services/recovery/deploymentHealth.js +143 -0
  75. package/dist/services/recovery/deploymentHealth.js.map +1 -0
  76. package/dist/services/recovery/deploymentMaintenance.js +197 -0
  77. package/dist/services/recovery/deploymentMaintenance.js.map +1 -0
  78. package/dist/services/recovery/deploymentService.js +429 -0
  79. package/dist/services/recovery/deploymentService.js.map +1 -0
  80. package/dist/services/recovery/deploymentSource.js +36 -0
  81. package/dist/services/recovery/deploymentSource.js.map +1 -0
  82. package/dist/services/recovery/dockerDeployment.js +172 -0
  83. package/dist/services/recovery/dockerDeployment.js.map +1 -0
  84. package/dist/services/recovery/drift.js +178 -0
  85. package/dist/services/recovery/drift.js.map +1 -0
  86. package/dist/services/recovery/evidence.js +127 -0
  87. package/dist/services/recovery/evidence.js.map +1 -0
  88. package/dist/services/recovery/gitCopies.js +219 -0
  89. package/dist/services/recovery/gitCopies.js.map +1 -0
  90. package/dist/services/recovery/history.js +423 -0
  91. package/dist/services/recovery/history.js.map +1 -0
  92. package/dist/services/recovery/recoveryService.js +521 -0
  93. package/dist/services/recovery/recoveryService.js.map +1 -0
  94. package/dist/services/recovery/storage.js +344 -0
  95. package/dist/services/recovery/storage.js.map +1 -0
  96. package/dist/services/schedules/scheduleService.js +9 -10
  97. package/dist/services/schedules/scheduleService.js.map +1 -1
  98. package/dist/store/db.js +71 -0
  99. package/dist/store/db.js.map +1 -1
  100. package/dist/tools/catalog.js +4 -0
  101. package/dist/tools/catalog.js.map +1 -1
  102. package/dist/tools/changeTools.js +1 -1
  103. package/dist/tools/changeTools.js.map +1 -1
  104. package/dist/tools/context.js +23 -3
  105. package/dist/tools/context.js.map +1 -1
  106. package/dist/tools/deploymentTools.js +58 -0
  107. package/dist/tools/deploymentTools.js.map +1 -0
  108. package/dist/tools/directTools.js +9 -5
  109. package/dist/tools/directTools.js.map +1 -1
  110. package/dist/tools/gitTools.js +7 -2
  111. package/dist/tools/gitTools.js.map +1 -1
  112. package/dist/tools/jobTools.js +4 -4
  113. package/dist/tools/jobTools.js.map +1 -1
  114. package/dist/tools/projectTools.js +1 -0
  115. package/dist/tools/projectTools.js.map +1 -1
  116. package/dist/tools/recoveryTools.js +79 -0
  117. package/dist/tools/recoveryTools.js.map +1 -0
  118. package/dist/tools/runtimeTools.js +1 -1
  119. package/dist/tools/runtimeTools.js.map +1 -1
  120. package/dist/tools/surface.js +6 -1
  121. package/dist/tools/surface.js.map +1 -1
  122. package/dist/tunnel/credentials.js +1 -1
  123. package/dist/tunnel/credentials.js.map +1 -1
  124. package/docs/ARCHITECTURE.md +92 -2
  125. package/docs/CI.md +118 -0
  126. package/docs/COMPATIBILITY.md +7 -6
  127. package/docs/EVALUATION.md +17 -12
  128. package/docs/MANUAL_ACCEPTANCE.md +72 -2
  129. package/docs/MCP_CONNECTIONS.md +238 -0
  130. package/docs/RECOVERY.md +448 -0
  131. package/docs/RELEASE_1.3.0.md +64 -0
  132. package/docs/RELEASE_1.3.1.md +49 -0
  133. package/docs/RELEASE_NOTES.md +31 -0
  134. package/docs/SECURITY.md +123 -0
  135. package/docs/TEST_REPORT.md +312 -5
  136. package/docs/TUNNEL.md +6 -0
  137. package/docs/WEB_CLIENTS.md +3 -0
  138. package/docs/adr/047-temporary-remote-config.md +11 -0
  139. package/docs/adr/051-source-recovery-foundation.md +45 -0
  140. package/docs/adr/052-reviewed-source-restore.md +50 -0
  141. package/docs/adr/053-content-drift-and-private-git-recovery.md +29 -0
  142. package/docs/adr/054-verified-source-checkpoints.md +58 -0
  143. package/docs/adr/055-reviewed-docker-deployments.md +42 -0
  144. package/docs/adr/056-owner-data-recovery.md +47 -0
  145. package/docs/adr/README.md +7 -0
  146. package/native/private-state-windows.cs +105 -0
  147. package/package.json +8 -1
  148. package/schemas/global-config.schema.json +34 -0
  149. package/schemas/tools.compact.json +175 -15
  150. package/schemas/tools.hybrid.json +350 -16
  151. package/schemas/tools.json +4883 -816
package/README.md CHANGED
@@ -2,12 +2,20 @@
2
2
 
3
3
  **DODO MCP** คือ MCP server แบบ local-first สำหรับให้ AI ช่วยพัฒนา software โดยทำงานกับ workspace ที่เจ้าของเลือก ค่าเริ่มต้นเป็นโหมดส่วนตัวแบบเพิ่มโปรเจกต์แล้วใช้ได้ทันที และยังมีโหมด managed สำหรับแยก workspace ACL/trust แบบละเอียด
4
4
 
5
+ ## เชื่อมต่อ AI ของคุณ
6
+
7
+ [คู่มือเชื่อม ChatGPT, Claude, Claude Code, Codex, Gemini CLI, Cursor และ VS Code](docs/MCP_CONNECTIONS.md)
8
+ มีคำสั่งลงทะเบียน OAuth, ตารางกรอกฟอร์ม และตัวอย่าง STDIO สำหรับแต่ละ client
9
+
10
+ **Config บนโดเมน:** รัน `dodo --web` เพื่อเปิด session ใหม่ได้ทุกครั้ง แม้ Local Config
11
+ 8 ชั่วโมงหมดอายุแล้ว ไม่ต้อง restart MCP เพื่อเปิดใหม่ แต่ละรอบเปิด 1 ชั่วโมงและใช้ pairing code ใหม่
12
+
5
13
  ## จุดเด่น
6
14
 
7
15
  - MCP ผ่าน HTTP ที่ `127.0.0.1:21730/mcp` พร้อม OAuth และ PKCE
8
16
  - Local Config แบบ loopback ที่ `127.0.0.1:21731` และ Remote Config ชั่วคราวผ่าน tunnel เมื่อเจ้าของสั่ง `dodo --web`
9
17
  - HTTP ใช้ Compact Tool Surface 20 tools เพื่อลดภาระการโหลด schema
10
- - STDIO ใช้ Full Tool Surface; มี capability definitions ทั้งหมด 138 รายการ แต่ค่าเริ่มต้นซ่อน Sub-agent 4 operations จาก MCP จึงเห็น 134 tools
18
+ - STDIO ใช้ Full Tool Surface; มี capability definitions ทั้งหมด 158 รายการ แต่ค่าเริ่มต้นซ่อน Sub-agent 4 operations จาก MCP จึงเห็น 154 tools
11
19
  - Hybrid Surface 49 tools สำหรับ client ที่รับ catalog ขนาดกลาง
12
20
  - อ่าน ค้นหา สร้าง แก้ ย้าย ลบไฟล์ พร้อม expected hash, journal และ rollback
13
21
  - รันคำสั่ง งานแบบขนาน jobs, Git, TypeScript/JavaScript intelligence, LSP และ task assistance
@@ -567,19 +575,23 @@ dodo desktop allow --app com.google.Chrome --mode control --persist --yes
567
575
 
568
576
  ## Development
569
577
 
570
- Platform CI ใช้ dedicated self-hosted runners ที่เจ้าของควบคุม: Linux X64 รันผ่าน
571
- Docker พร้อม Playwright Chromium และ Windows X64 รัน native candidate gate ส่วน
578
+ Platform CI ใช้ dedicated self-hosted runners ที่เจ้าของควบคุม: Linux X64 และ
579
+ Windows X64 รัน native candidate gate บนเครื่อง runner โดยตรง ไม่ใช้ Docker ส่วน
572
580
  macOS ยังรัน release gate บนเครื่องพัฒนา Workflow รับเฉพาะ push ที่ `main` และ manual
573
581
  dispatch ไม่รัน pull request จากภายนอกบน self-hosted runner
574
582
 
575
- รัน regression benchmark, macOS candidate gate และ Linux Docker gate ได้ด้วย:
583
+ รัน regression benchmark และ macOS candidate gate ได้ด้วย:
576
584
 
577
585
  ```bash
578
586
  npm run bench
579
587
  npm run release:gate
580
- npm run test:linux:docker
581
588
  ```
582
589
 
590
+ สั่ง Linux/Windows CI ผ่าน GitHub Actions → `platform-gates` → Run workflow →
591
+ เลือก `all` หรือใช้ `gh workflow run platform-gates.yml --ref main -f platform=all`
592
+ Linux รันเมื่อสั่ง workflow; Windows รันทั้งเมื่อ push ไป main และเมื่อสั่ง workflow
593
+ อ่าน [การเตรียม runner และวิธีแก้ CI](docs/CI.md) ก่อนรันครั้งแรก
594
+
583
595
  GitHub workflow รัน Node 22 และ 24 ทั้ง Linux/Windows โดยไม่ publish npm Windows จะ
584
596
  ยังไม่ถูกประกาศเป็น supported platform จนกว่า automated native gate และ manual
585
597
  Windows 11 acceptance จะผ่านตามเอกสาร
@@ -588,8 +600,9 @@ Release gate สร้างหลักฐาน non-secret ใน ignored `rel
588
600
  exact-tarball ผ่าน STDIO Full กับ HTTP/OAuth Compact โดยไม่ publish npm ดูรายละเอียดที่
589
601
  [DodoBench และ Release Gate](docs/EVALUATION.md)
590
602
 
591
- เมื่อทั้งสอง report มาจาก Git revision และ `package-lock.json` เดียวกัน ให้รวมหลักฐาน
603
+ เมื่อทั้งสอง report มาจาก clean Git revision, source fingerprint และ `package-lock.json` เดียวกัน ให้รวมหลักฐาน
592
604
  บน macOS ด้วย `node scripts/release-gate.mjs --release --platform-evidence /absolute/path/to/linux/gate-report.json`
605
+ Linux report ต้องมาจาก native GitHub Actions; หลักฐาน Docker เก่าไม่แทน CI รอบใหม่
593
606
  คำสั่งนี้ไม่ publish package
594
607
 
595
608
  ```bash
@@ -602,3 +615,21 @@ npm pack
602
615
  ```
603
616
 
604
617
  โปรเจกต์นี้ใช้ GitHub repository [arthittakun/dodo-mcp](https://github.com/arthittakun/dodo-mcp) และ package `dodo-mcp`
618
+
619
+ ### Recovery — สำรองและกู้คืนงาน
620
+
621
+ โปรเจกต์ที่ลงทะเบียนเปิด source backup เป็นค่าเริ่มต้น ก่อนแก้ไฟล์หรือรันคำสั่ง
622
+ หน้า **Projects → Recovery** ใช้ตรวจประวัติ เปรียบเทียบ และยืนยันแผนกู้คืนได้จริง
623
+ หรือใช้ `dodo recovery --help` ผ่าน terminal สำเนาอยู่ใน private state แยกจาก Git
624
+ ของโปรเจกต์ และไม่ push ขึ้น GitHub อัตโนมัติ
625
+
626
+ - กู้คืน checkpoint หรือ session ที่เลือก พร้อม expected hashes, สำเนาก่อนกู้ และ receipt หลัง reconnect
627
+ - ตรวจไฟล์ที่ถูกแก้จากภายนอก และแยก `SAVED`, `VERIFIED`, `FAILED`, `INCONCLUSIVE`, `STALE` ให้เห็นตามหลักฐาน
628
+ - ตั้งชื่อ/pin จุดกู้คืน และตรวจ retention ก่อนล้าง โดยรักษางานที่ไม่เกี่ยวข้อง
629
+ - เพิ่ม Docker deployment target ได้โดยเจ้าของ: source ที่ตรวจแล้ว → image → health/stabilization → known-good พร้อม reviewed rollback
630
+ - ตรวจ SQLite migration compatibility แบบอ่านอย่างเดียว และสำรอง private config แบบเข้ารหัสเมื่อเจ้าของเปิดแยก
631
+
632
+ **Source restore ไม่ย้อนฐานข้อมูล, secrets, volumes หรือระบบภายนอก** การสำรอง config
633
+ ใช้กุญแจจาก OS credential store และไม่ส่งเนื้อหาให้ AI; ไม่มี generic database rollback
634
+ หรือการ restart service อัตโนมัติ ดู [คู่มือ Recovery](docs/RECOVERY.md),
635
+ [การเปลี่ยนแปลง 1.3.0](docs/RELEASE_1.3.0.md) และ [ผลตรวจรับจริง](docs/TEST_REPORT.md)
@@ -0,0 +1,38 @@
1
+ import fs from 'node:fs';
2
+ import { DodoError } from '../errors.js';
3
+ export function registerDataRecoveryCommands(recovery, ipc) {
4
+ const database = recovery.command('database').description('owner-only read-only migration compatibility; never database rollback');
5
+ const print = (value) => console.log(JSON.stringify(value, null, 2));
6
+ database.command('targets').action(async () => print(await ipc('recovery.database.list')));
7
+ const review = (cmd) => cmd.requiredOption('--workspace <id>', 'current workspace ID').requiredOption('--epoch <epoch>', 'current epoch').option('--yes', 'confirm the exact owner operation', false);
8
+ const envelope = (opts, input) => {
9
+ if (!opts.yes)
10
+ throw new DodoError('INVALID_INPUT', 'review the owner operation and add --yes');
11
+ return { workspaceId: opts.workspace, workspaceEpoch: opts.epoch, confirm: true, input };
12
+ };
13
+ review(database.command('inspect <targetId>')).action(async (targetId, opts) => print(await ipc('recovery.database.inspect', envelope(opts, { targetId }))));
14
+ const jsonOperation = (group, operation, method) => review(group.command(operation).requiredOption('--file <path>', 'owner-reviewed bounded JSON; never secret values'))
15
+ .action(async (opts) => {
16
+ const st = fs.lstatSync(opts.file);
17
+ if (!st.isFile() || st.isSymbolicLink() || st.nlink !== 1 || st.size > 128 * 1024)
18
+ throw new DodoError('INVALID_INPUT', 'expected a bounded regular JSON file');
19
+ let input;
20
+ try {
21
+ input = JSON.parse(fs.readFileSync(opts.file, 'utf8'));
22
+ }
23
+ catch {
24
+ throw new DodoError('INVALID_INPUT', 'invalid owner JSON');
25
+ }
26
+ print(await ipc(method, envelope(opts, input)));
27
+ });
28
+ for (const op of ['configure', 'bind', 'unbind'])
29
+ jsonOperation(database, op, 'recovery.database.' + op);
30
+ const vault = recovery.command('private-config').description('owner-only encrypted secret-file recovery; explicit opt-in and OS key store required');
31
+ vault.command('list').action(async () => print(await ipc('recovery.config.list')));
32
+ jsonOperation(vault, 'configure', 'recovery.config.configure');
33
+ review(vault.command('backup <targetId>')).action(async (targetId, opts) => print(await ipc('recovery.config.backup', envelope(opts, { targetId }))));
34
+ review(vault.command('preview <backupId>')).action(async (backupId, opts) => print(await ipc('recovery.config.preview', envelope(opts, { backupId }))));
35
+ review(vault.command('apply <planId>').requiredOption('--hash <hash>', 'exact reviewed plan hash')).action(async (planId, opts) => print(await ipc('recovery.config.apply', envelope(opts, { planId, planHash: opts.hash }))));
36
+ review(vault.command('rotate <targetId>').requiredOption('--revision <n>', 'current target revision', Number)).action(async (targetId, opts) => print(await ipc('recovery.config.rotate', envelope(opts, { targetId, expectedRevision: opts.revision }))));
37
+ }
38
+ //# sourceMappingURL=dataRecovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataRecovery.js","sourceRoot":"","sources":["../../src/cli/dataRecovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ import fs from 'node:fs';
2
+ import { DodoError } from '../errors.js';
3
+ /** Private owner IPC only; reviewed files are bounded JSON, never executable repository configuration. */
4
+ export function registerDeploymentCommands(program, ipc) {
5
+ const group = program.command('deployment').description('review registered Docker deployment targets and durable build/deploy outcomes');
6
+ const print = (value) => console.log(JSON.stringify(value, null, 2));
7
+ for (const op of ['targets', 'list'])
8
+ group.command(op).action(async () => print(await ipc(`deployment.${op}`)));
9
+ for (const op of ['inspect', 'compare'])
10
+ group.command(`${op} <deploymentId>`).action(async (deploymentId) => print(await ipc(`deployment.${op}`, { deploymentId })));
11
+ const reviewed = (cmd) => cmd.requiredOption('--workspace <id>', 'reviewed workspace ID')
12
+ .requiredOption('--epoch <epoch>', 'reviewed workspace epoch').option('--yes', 'confirm the exact reviewed operation', false);
13
+ const envelope = (opts, input) => {
14
+ if (!opts.yes)
15
+ throw new DodoError('INVALID_INPUT', 'review target/source and add --yes; Docker commands may affect resources outside the workspace');
16
+ return { workspaceId: opts.workspace, workspaceEpoch: opts.epoch, confirm: true, input };
17
+ };
18
+ reviewed(group.command('configure').requiredOption('--file <path>', 'owner-reviewed target JSON: expectedRevision, enabled, definition, confirmDaemonAccess'))
19
+ .action(async (opts) => {
20
+ const st = fs.lstatSync(opts.file);
21
+ if (!st.isFile() || st.isSymbolicLink() || st.size > 128 * 1024)
22
+ throw new DodoError('INVALID_INPUT', 'target configuration must be a regular JSON file at most 128 KiB');
23
+ let input;
24
+ try {
25
+ input = JSON.parse(fs.readFileSync(opts.file, 'utf8'));
26
+ }
27
+ catch {
28
+ throw new DodoError('INVALID_INPUT', 'invalid target JSON');
29
+ }
30
+ print(await ipc('deployment.configure', envelope(opts, input)));
31
+ });
32
+ reviewed(group.command('prepare').requiredOption('--target <id>', 'owner-registered target ID').requiredOption('--revision <n>', 'target revision')
33
+ .requiredOption('--verification <id>', 'current VERIFIED verification ID').requiredOption('--key <key>', 'idempotency key; reuse for retries'))
34
+ .action(async (opts) => print(await ipc('deployment.prepare', envelope(opts, {
35
+ targetId: opts.target, expectedTargetRevision: Number(opts.revision), verificationId: opts.verification, idempotencyKey: opts.key,
36
+ }))));
37
+ reviewed(group.command('build <deploymentId>').requiredOption('--hash <hash>', 'reviewed plan hash'))
38
+ .action(async (deploymentId, opts) => print(await ipc('deployment.build', envelope(opts, { deploymentId, planHash: opts.hash }))));
39
+ reviewed(group.command('apply <deploymentId>').requiredOption('--hash <hash>', 'reviewed plan hash').requiredOption('--image <digest>', 'reviewed immutable image digest'))
40
+ .action(async (deploymentId, opts) => print(await ipc('deployment.apply', envelope(opts, { deploymentId, planHash: opts.hash, imageDigest: opts.image }))));
41
+ for (const op of ['observe', 'source-preview'])
42
+ reviewed(group.command(`${op} <deploymentId>`))
43
+ .action(async (deploymentId, opts) => print(await ipc('deployment.' + op.replaceAll('-', '_'), envelope(opts, { deploymentId }))));
44
+ reviewed(group.command('maintenance').requiredOption('--file <path>', 'owner-reviewed maintenance JSON; preview returns a review ID/hash for apply'))
45
+ .action(async (opts) => {
46
+ const st = fs.lstatSync(opts.file);
47
+ if (!st.isFile() || st.isSymbolicLink() || st.nlink !== 1 || st.size > 65536)
48
+ throw new DodoError('INVALID_INPUT', 'maintenance JSON must be a bounded regular file');
49
+ let input;
50
+ try {
51
+ input = JSON.parse(fs.readFileSync(opts.file, 'utf8'));
52
+ }
53
+ catch {
54
+ throw new DodoError('INVALID_INPUT', 'invalid maintenance JSON');
55
+ }
56
+ print(await ipc('deployment.maintenance', envelope(opts, input)));
57
+ });
58
+ reviewed(group.command('rollback-prepare <deploymentId>').requiredOption('--key <key>', 'idempotency key for this rollback review'))
59
+ .action(async (deploymentId, opts) => print(await ipc('deployment.rollback_prepare', envelope(opts, { deploymentId, idempotencyKey: opts.key }))));
60
+ }
61
+ //# sourceMappingURL=deployment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployment.js","sourceRoot":"","sources":["../../src/cli/deployment.ts"],"names":[],"mappings":"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"}
package/dist/cli/main.js CHANGED
@@ -7,6 +7,8 @@
7
7
  const invokedCwd = process.cwd();
8
8
  import fs from 'node:fs';
9
9
  import { Command } from 'commander';
10
+ import { registerDataRecoveryCommands } from './dataRecovery.js';
11
+ import { registerDeploymentCommands } from './deployment.js';
10
12
  import { startServer, DODO_VERSION } from '../server/appServer.js';
11
13
  import { startStdioServer } from '../server/stdioServer.js';
12
14
  import { resolveConfigDir, ensureConfigDir, statePaths, ipcSocketPath } from '../config/paths.js';
@@ -16,6 +18,7 @@ import { mintWorkspaceId } from '../workspace/identity.js';
16
18
  import { openDatabase, openDatabaseReadonly } from '../store/db.js';
17
19
  import { Store } from '../store/store.js';
18
20
  import { ipcCall, IpcError } from '../ipc/client.js';
21
+ import { ownerCommandTimeout } from '../ipc/timeouts.js';
19
22
  import { installationIpcCall } from '../ipc/installationClient.js';
20
23
  import { formatTerminalLine } from './terminal.js';
21
24
  import { TRUST_MODE_DESCRIPTIONS } from '../security/policy.js';
@@ -31,6 +34,7 @@ import { runCliMenu } from './menu.js';
31
34
  import { TunnelRuntime } from '../tunnel/runtime.js';
32
35
  import { INSTALLATION_AUTHORITY_ID } from '../auth/constants.js';
33
36
  const program = new Command();
37
+ registerDeploymentCommands(program, ipcForCwd);
34
38
  program.name('dodo').description('DODO — local-first, single-owner, project-scoped coding MCP server').version(DODO_VERSION);
35
39
  function fail(message, code = 1) {
36
40
  console.error(`dodo: ${message}`);
@@ -61,7 +65,7 @@ function workspaceIpcPath(root) {
61
65
  }
62
66
  async function ipcForCwd(cmd, args = {}) {
63
67
  const { ipcPath } = workspaceIpcPath();
64
- return ipcCall(ipcPath, cmd, args);
68
+ return ipcCall(ipcPath, cmd, args, ownerCommandTimeout(cmd));
65
69
  }
66
70
  /** OAuth identity belongs to the installation, not the shell's CWD. */
67
71
  async function ipcForInstallation(cmd, args = {}) {
@@ -662,6 +666,7 @@ function printRemoteConfig(result) {
662
666
  console.log(`Remote Config: ${result.url}`);
663
667
  console.log(`Pairing code (shown once): ${result.pairingCode}`);
664
668
  console.log(`Expires: ${new Date(result.expiresAt).toISOString()} (ไม่เกิน 1 ชั่วโมง)`);
669
+ console.log('Session นี้แยกจาก Local Config 8 ชั่วโมง — เปิดรอบใหม่ด้วย dodo --web ได้โดยไม่ต้อง restart MCP');
665
670
  console.log('เปิด URL แล้วกรอก code นี้ ระบบจะแลกเป็น Secure/HttpOnly cookie; ไม่มี secret อยู่ใน URL');
666
671
  }
667
672
  async function openRemoteConfigFromCli() {
@@ -1268,6 +1273,36 @@ auth
1268
1273
  throw err;
1269
1274
  }
1270
1275
  });
1276
+ // -------------------------------------------------------------- recovery ---
1277
+ const recovery = program.command('recovery').description('inspect or configure automatic source backups for the current registered project');
1278
+ registerDataRecoveryCommands(recovery, ipcForCwd);
1279
+ recovery.command('status').action(async () => console.log(JSON.stringify(await ipcForCwd('recovery.status'), null, 2)));
1280
+ recovery.command('scan').option('--cursor <n>', 'page offset', '0').option('--limit <n>', 'page size (1–100)', '50').action(async (opts) => console.log(JSON.stringify(await ipcForCwd('recovery.drift.scan', { cursor: Number(opts.cursor), limit: Number(opts.limit) }), null, 2)));
1281
+ recovery.command('acknowledge <digest>').requiredOption('--workspace <id>', 'reviewed workspace ID').requiredOption('--epoch <epoch>', 'reviewed workspace epoch').option('--yes', 'accept exactly the reviewed observed state', false)
1282
+ .action(async (digest, opts) => { if (!opts.yes)
1283
+ throw new DodoError('INVALID_INPUT', 'review drift then add --yes'); console.log(JSON.stringify(await ipcForCwd('recovery.drift.acknowledge', { digest, workspaceId: opts.workspace, workspaceEpoch: opts.epoch, confirm: true }), null, 2)); });
1284
+ recovery.command('evidence').option('--id <verificationId>', 'refresh evidence for a specific verification').action(async (opts) => console.log(JSON.stringify(await ipcForCwd(opts.id ? 'recovery.evidence.inspect' : 'recovery.evidence.list', opts.id ? { verificationId: opts.id } : {}), null, 2)));
1285
+ recovery.command('cleanup-preview').action(async () => console.log(JSON.stringify(await ipcForCwd('recovery.cleanup.preview'), null, 2)));
1286
+ recovery.command('mark <name> <checkpointId>').requiredOption('--revision <n>', 'reviewed pointer revision; 0 for a new name').requiredOption('--workspace <id>', 'reviewed workspace ID').requiredOption('--epoch <epoch>', 'reviewed epoch').option('--yes', 'confirm owner label (does not certify tests)', false)
1287
+ .action(async (name, checkpointId, opts) => { if (!opts.yes)
1288
+ throw new DodoError('INVALID_INPUT', 'review checkpoint and add --yes'); console.log(JSON.stringify(await ipcForCwd('recovery.mark', { name, snapshotId: checkpointId, expectedRevision: Number(opts.revision), workspaceId: opts.workspace, workspaceEpoch: opts.epoch, confirm: true }), null, 2)); });
1289
+ recovery.command('checkpoint').action(async () => console.log(JSON.stringify(await ipcForCwd('recovery.checkpoint'), null, 2)));
1290
+ recovery.command('list').option('--sessions', 'list sessions instead of checkpoints', false).action(async (opts) => console.log(JSON.stringify(await ipcForCwd(opts.sessions ? 'recovery.recovery_session_list' : 'recovery.checkpoint_list'), null, 2)));
1291
+ recovery.command('preview <id>').option('--session', 'undo caller session receipts', false).option('--path <relative>', 'select a file or directory').option('--exact-mirror', 'explicitly include extra source-file deletions', false)
1292
+ .action(async (id, opts) => console.log(JSON.stringify(await ipcForCwd('recovery.restore_preview', { ...(opts.session ? { sessionId: id } : { checkpointId: id }), ...(opts.path ? { paths: [opts.path] } : {}), exactMirror: opts.exactMirror }), null, 2)));
1293
+ recovery.command('apply <planId>').requiredOption('--hash <hash>', 'exact reviewed planHash').requiredOption('--key <key>', 'durable idempotency key; reuse for retries').requiredOption('--workspace <id>', 'reviewed workspace ID').requiredOption('--epoch <epoch>', 'reviewed workspace epoch').option('--yes', 'confirm exactly this reviewed plan', false)
1294
+ .action(async (planId, opts) => {
1295
+ if (!opts.yes)
1296
+ fail('review the preview, then pass --yes with the same plan/hash and workspace context');
1297
+ console.log(JSON.stringify(await ipcForCwd('recovery.restore_apply', { planId, planHash: opts.hash, idempotencyKey: opts.key, workspaceId: opts.workspace, workspaceEpoch: opts.epoch, confirm: true }), null, 2));
1298
+ });
1299
+ recovery.command('configure').option('--enabled <value>', 'true or false').option('--yes', 'confirm the owner policy change', false)
1300
+ .action(async (opts) => {
1301
+ if (!opts.yes || !['true', 'false'].includes(opts.enabled ?? ''))
1302
+ fail('use --enabled true|false --yes to confirm');
1303
+ const current = await ipcForCwd('recovery.status');
1304
+ console.log(JSON.stringify(await ipcForCwd('recovery.configure', { policy: { ...current.policy, enabled: opts.enabled === 'true' }, confirm: true }), null, 2));
1305
+ });
1271
1306
  // --------------------------------------------------------------- recover ---
1272
1307
  program
1273
1308
  .command('recover')