dodo-mcp 1.1.0 → 1.2.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.
- package/README.md +71 -14
- package/dist/cli/main.js +92 -28
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/menu.js +2 -2
- package/dist/cli/menu.js.map +1 -1
- package/dist/config/globalConfig.js +16 -2
- package/dist/config/globalConfig.js.map +1 -1
- package/dist/config/tunnelConfig.js +2 -1
- package/dist/config/tunnelConfig.js.map +1 -1
- package/dist/projects/ownerAdmin.js +86 -0
- package/dist/projects/ownerAdmin.js.map +1 -0
- package/dist/projects/registry.js +90 -9
- package/dist/projects/registry.js.map +1 -1
- package/dist/security/projectAccess.js +69 -0
- package/dist/security/projectAccess.js.map +1 -0
- package/dist/security/projectAuthority.js +5 -1
- package/dist/security/projectAuthority.js.map +1 -1
- package/dist/server/aiAdmin.js +32 -8
- package/dist/server/aiAdmin.js.map +1 -1
- package/dist/server/appServer.js +37 -12
- package/dist/server/appServer.js.map +1 -1
- package/dist/server/bootstrap.js +5 -1
- package/dist/server/bootstrap.js.map +1 -1
- package/dist/server/configUi/app.css +8 -0
- package/dist/server/configUi/app.js +93 -18
- package/dist/server/configUi/index.html +22 -13
- package/dist/server/configUi/workbench.css +28 -1
- package/dist/server/configUi/workbench.js +113 -18
- package/dist/server/installationRuntime.js +6 -2
- package/dist/server/installationRuntime.js.map +1 -1
- package/dist/server/instructions.js +7 -3
- package/dist/server/instructions.js.map +1 -1
- package/dist/server/ipcDispatch.js.map +1 -1
- package/dist/server/localConfig.js +55 -13
- package/dist/server/localConfig.js.map +1 -1
- package/dist/server/stdioServer.js +14 -3
- package/dist/server/stdioServer.js.map +1 -1
- package/dist/services/ai/subagents.js +1 -1
- package/dist/services/ai/subagents.js.map +1 -1
- package/dist/services/multimodal/storage.js +10 -1
- package/dist/services/multimodal/storage.js.map +1 -1
- package/dist/services/schedules/scheduleService.js +8 -1
- package/dist/services/schedules/scheduleService.js.map +1 -1
- package/dist/store/db.js +14 -0
- package/dist/store/db.js.map +1 -1
- package/dist/tools/agentRuntimeTools.js +2 -3
- package/dist/tools/agentRuntimeTools.js.map +1 -1
- package/dist/tools/context.js +85 -5
- package/dist/tools/context.js.map +1 -1
- package/dist/tools/surface.js +97 -46
- package/dist/tools/surface.js.map +1 -1
- package/dist/tunnel/control.js +11 -4
- package/dist/tunnel/control.js.map +1 -1
- package/dist/tunnel/log.js +32 -0
- package/dist/tunnel/log.js.map +1 -1
- package/dist/tunnel/supervisor.js +103 -24
- package/dist/tunnel/supervisor.js.map +1 -1
- package/docs/AI_PROVIDERS.md +6 -3
- package/docs/ARCHITECTURE.md +18 -6
- package/docs/AUTH.md +6 -0
- package/docs/COMPATIBILITY.md +3 -2
- package/docs/MANUAL_ACCEPTANCE.md +22 -6
- package/docs/PROJECTS.md +73 -2
- package/docs/RELEASE_1.2.0.md +75 -0
- package/docs/RELEASE_1.2.1.md +47 -0
- package/docs/RELEASE_NOTES.md +33 -0
- package/docs/SECURITY.md +37 -6
- package/docs/TEST_REPORT.md +101 -0
- package/docs/TUNNEL.md +79 -16
- package/docs/WEB_CLIENTS.md +6 -1
- package/docs/adr/048-persistent-connection-mode.md +3 -1
- package/docs/adr/050-cloudflare-local-and-project-access.md +67 -0
- package/docs/adr/README.md +1 -0
- package/package.json +3 -1
- package/schemas/global-config.schema.json +11 -0
- package/schemas/tools.compact.json +123 -3
- package/schemas/tools.hybrid.json +297 -3
- package/schemas/tools.json +828 -0
package/README.md
CHANGED
|
@@ -162,6 +162,11 @@ Chat & Tasks · Runs & Jobs · Approvals · Knowledge · Settings) หน้า
|
|
|
162
162
|
ไม่มี CDN, CSP `'self'` เท่าเดิม) การลบ/ยกเลิก/เปลี่ยนโหมดต้องยืนยันก่อนเสมอ
|
|
163
163
|
และงานที่มีค่าใช้จ่ายหรือผลไม่แน่นอนจะบอกชัดว่า "ไม่ retry อัตโนมัติ"
|
|
164
164
|
|
|
165
|
+
การสร้าง Agent ใช้หน้า **Providers & Profiles**: เลือกประเภท Coding/Review/Research,
|
|
166
|
+
Provider connection และ Model ID แล้วกด **สร้าง Agent** รายชื่อโมเดลที่โหลดจาก
|
|
167
|
+
Provider จะเข้าแบบฟอร์มให้อัตโนมัติ ส่วน permission, tool calling, token limits และราคา
|
|
168
|
+
อยู่ในหัวข้อ **ตั้งค่าขั้นสูง** หาก endpoint ไม่รองรับการลิสต์โมเดลยังกรอก Model ID เองได้
|
|
169
|
+
|
|
165
170
|
หน้า **Settings → Sub-agent tools ใน MCP** มีสวิตช์เปิด/ปิดการ expose
|
|
166
171
|
`subagent_spawn`, `subagent_status`, `subagent_result` และ `subagent_control` ให้ AI
|
|
167
172
|
ภายนอก ค่าเริ่มต้นปิดเพื่อให้ catalog สำหรับงานทั่วไปกระชับขึ้น แต่หน้า Chat & Tasks
|
|
@@ -169,6 +174,42 @@ Chat & Tasks · Runs & Jobs · Approvals · Knowledge · Settings) หน้า
|
|
|
169
174
|
สร้าง MCP app ใหม่ตามพฤติกรรมของ client การตั้งค่านี้เปลี่ยนเฉพาะการมองเห็น tools
|
|
170
175
|
และไม่เพิ่ม OAuth scope, trust, approval หรือสิทธิ์ของ profile
|
|
171
176
|
|
|
177
|
+
หน้า **Settings → Tools ที่ AI มองเห็น** เลือกเปิด/ปิดแต่ละ operation ได้ พร้อมค้นหา
|
|
178
|
+
และเปิด/ปิดทั้งหมวด ค่าใช้กับ Compact/Hybrid: operation ที่ปิดจะหายจาก
|
|
179
|
+
`dodo_discover`, gateway enum และ direct duplicate ใน Hybrid หากปิดครบทั้งหมวด gateway
|
|
180
|
+
นั้นจะไม่ถูกส่งให้ client จึงลดทั้งจำนวน schema และ context ที่ AI ต้องอ่าน Full/STDIO
|
|
181
|
+
ยังคง definitions รายตัวครบตามเดิม เมื่อบันทึกต้อง restart DODO แล้ว rescan หรือสร้าง
|
|
182
|
+
MCP app ใหม่ การตั้งค่านี้เป็น visibility เท่านั้นและไม่ grant สิทธิ์ใด ๆ
|
|
183
|
+
|
|
184
|
+
### เพิ่มโปรเจกต์ครั้งเดียว แล้วสั่งงานด้วยชื่อ
|
|
185
|
+
|
|
186
|
+
เพิ่มโปรเจกต์จากหน้า Projects (หรือ CLI) โดยกรอกแค่ **ชื่อ**, **absolute path** และ
|
|
187
|
+
**ระดับการเข้าถึง** หนึ่งค่า:
|
|
188
|
+
|
|
189
|
+
| ระดับ | AI ทำอะไรได้ |
|
|
190
|
+
|---|---|
|
|
191
|
+
| อ่านอย่างเดียว | อ่าน ค้นหา วิเคราะห์ |
|
|
192
|
+
| แก้ไข | อ่านและแก้ไฟล์ |
|
|
193
|
+
| ทำงานเต็มรูปแบบ | อ่าน แก้ไฟล์ และรันคำสั่ง/ทดสอบ |
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
dodo project add "C:\Users\User\Desktop\auto-upload" --name "auto-upload" --access full
|
|
197
|
+
dodo project access "auto-upload" --mode edit
|
|
198
|
+
dodo project list
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
ในโหมดส่วนตัว (ค่าเริ่มต้น) เท่านี้ก็พร้อมใช้ — ไม่ต้องตั้ง Client ACL, Trust หรือ
|
|
202
|
+
profile allowlist ซ้ำอีกหลายหน้า ระดับที่เลือกเป็น **เพดาน** สิทธิ์จริงคือส่วนที่ทับกับ
|
|
203
|
+
OAuth scope ของ client นั้น (token ที่อ่านได้อย่างเดียวจะยังอ่านได้อย่างเดียวเสมอ)
|
|
204
|
+
|
|
205
|
+
จากนั้นสั่ง AI ด้วยชื่อโปรเจกต์ได้ตรง ๆ:
|
|
206
|
+
|
|
207
|
+
> "ใช้ DODO แก้โปรเจกต์ auto-upload"
|
|
208
|
+
|
|
209
|
+
AI จะ resolve ชื่อเอง (`project_overview` ด้วย `targetProject: "auto-upload"`) รับ
|
|
210
|
+
`workspaceId`/`workspaceEpoch` ของโปรเจกต์นั้น แล้วส่ง `targetProject` ไปกับทุกคำสั่งถัดไป
|
|
211
|
+
เจ้าของไม่ต้องรู้หรือพิมพ์ project ID เลย ดู [docs/PROJECTS.md](docs/PROJECTS.md)
|
|
212
|
+
|
|
172
213
|
ลงทะเบียนโปรเจกต์ที่ต้องการใช้งานบ่อยได้โดยไม่เริ่ม server:
|
|
173
214
|
|
|
174
215
|
```bash
|
|
@@ -341,14 +382,22 @@ command sandbox, idempotency และ audit เดิม `agent_exec` ใช้
|
|
|
341
382
|
|
|
342
383
|
### เชื่อม Remote MCP ผ่าน Cloudflare Tunnel
|
|
343
384
|
|
|
344
|
-
DODO เลือกวิธีเชื่อมต่อระดับ installation ได้หนึ่งแบบ: `local`
|
|
345
|
-
|
|
346
|
-
|
|
385
|
+
DODO เลือกวิธีเชื่อมต่อระดับ installation ได้หนึ่งแบบ: `local` (loopback เท่านั้น),
|
|
386
|
+
`external` (Cloudflare ที่เจ้าของติดตั้ง/รันเอง) หรือ `tunnel` (DODO ดูแล cloudflared)
|
|
387
|
+
ค่าที่เลือกมีผลกับทุก `dodo start` และไม่มี fallback อัตโนมัติ
|
|
388
|
+
|
|
389
|
+
เฉพาะเครื่อง:
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
dodo tunnel configure --loopback
|
|
393
|
+
dodo start
|
|
394
|
+
```
|
|
347
395
|
|
|
348
|
-
Local
|
|
396
|
+
Cloudflare Local (ติดตั้งในเครื่อง) — ผู้ใช้ดูแล process/token เอง:
|
|
349
397
|
|
|
350
398
|
```bash
|
|
351
|
-
dodo tunnel configure --local
|
|
399
|
+
dodo tunnel configure --cloudflare-local --public-url https://mcp.example.com
|
|
400
|
+
# เริ่ม cloudflared ที่ติดตั้งในเครื่องด้วยวิธีของผู้ใช้
|
|
352
401
|
dodo start
|
|
353
402
|
```
|
|
354
403
|
|
|
@@ -365,7 +414,8 @@ dodo start
|
|
|
365
414
|
|
|
366
415
|
เจ้าของสร้าง remotely-managed Tunnel, public hostname และ DNS ใน Cloudflare และ route
|
|
367
416
|
**ทุก path** มาที่ `http://127.0.0.1:21730` DODO ไม่จัดการ Cloudflare account/DNS
|
|
368
|
-
|
|
417
|
+
เฉพาะ DODO Tunnel เท่านั้นที่ใช้ token ซึ่งถูกบันทึกใน macOS Keychain, Windows
|
|
418
|
+
Credential Manager หรือ Linux Secret Service;
|
|
369
419
|
global config เก็บเพียง opaque reference ทุกครั้งที่เปิด DODO ระบบจะเริ่ม
|
|
370
420
|
`cloudflared` เป็น child และหยุด child พร้อม DODO Token ไม่อยู่ใน argv, config, log,
|
|
371
421
|
audit, browser storage, MCP response หรือ environment ของ MCP jobs
|
|
@@ -382,14 +432,17 @@ dodo web --close # ปิดหน้า Remote Config แต่ MCP/Tunnel
|
|
|
382
432
|
terminal หลังจับคู่ browser จะใช้ cookie ที่เป็น `Secure`, `HttpOnly`,
|
|
383
433
|
`SameSite=Strict` และจำกัด path ที่ `/config` หน้า Remote Config ปิดอัตโนมัติภายใน
|
|
384
434
|
1 ชั่วโมง การเปิดใหม่ออก code/session ใหม่และไม่ต้อง restart MCP ไม่มี token หรือ
|
|
385
|
-
credential อยู่ใน URL
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
435
|
+
credential อยู่ใน URL ใช้ได้ทั้งโหมด **Cloudflare Local** ที่เจ้าของรัน
|
|
436
|
+
`cloudflared` เอง และ **DODO Tunnel** ที่ DODO ดูแล process; โหมดเฉพาะเครื่องไม่มี
|
|
437
|
+
public route จึงเปิด Remote Config ไม่ได้
|
|
438
|
+
|
|
439
|
+
หน้า Local Config ที่ `127.0.0.1:21731` แยกตัวเลือกชัดเจนเป็น
|
|
440
|
+
**เฉพาะเครื่อง (Loopback)**, **Cloudflare Local (ติดตั้งในเครื่อง)** และ
|
|
441
|
+
**DODO Tunnel** ตั้ง public origin ได้ และรับ token แบบ write-only เฉพาะ DODO Tunnel
|
|
442
|
+
ค่าใหม่มีผลหลัง restart หน้าเว็บแสดง Active MCP URL จาก runtime จริงและไม่กล่าวว่า
|
|
443
|
+
Tunnel หรือ AI client เชื่อมแล้วหากไม่มีหลักฐาน ดูสถานะด้วย `dodo tunnel status`,
|
|
444
|
+
ตรวจ connectivity ด้วย `dodo tunnel doctor` และดู log ที่ redacted ด้วย
|
|
445
|
+
`dodo tunnel logs` (log/supervisor ใช้กับ DODO Tunnel เท่านั้น)
|
|
393
446
|
|
|
394
447
|
บน macOS `dodo setup --yes` เลือก component `cloudflared` อยู่ในชุด `all` แล้ว
|
|
395
448
|
Tunnel route ต้องชี้เฉพาะ MCP/OAuth listener `21730` ห้ามชี้ Local Config `21731`
|
|
@@ -429,6 +482,10 @@ dodo_read(operation="read_files", args={...})
|
|
|
429
482
|
|
|
430
483
|
`dodo_discover` คืน schema ของ operation ที่เลือกและ hash ที่ deterministic ส่วน gateway จะส่ง request ผ่าน policy pipeline เดียวกับ tool รายตัว จึงยังตรวจ scope, ACL, workspace ID/epoch, trust, approval, path guard, secret guard, hash และ audit ครบทุกชั้น
|
|
431
484
|
|
|
485
|
+
เจ้าของลดรายการที่ AI เห็นได้จาก **Local Config → Settings → Tools ที่ AI มองเห็น**
|
|
486
|
+
โดยไม่แตะ permission เมื่อปิด operation แล้ว schema ของ Compact/Hybrid จะตัดรายการนั้น
|
|
487
|
+
ออกจริงหลัง restart/rescan ส่วน Full surface ไม่เปลี่ยน
|
|
488
|
+
|
|
432
489
|
ชื่อ gateway `dodo_*` เป็นชื่อ MCP protocol ที่คงไว้เพื่อ compatibility กับ client ที่เชื่อมต่ออยู่ การเปลี่ยนชื่อ gateway ต้องทำเป็น protocol migration แยกต่างหาก
|
|
433
490
|
|
|
434
491
|
## การเปลี่ยน workspace
|
package/dist/cli/main.js
CHANGED
|
@@ -25,6 +25,8 @@ import { DesktopPolicyInputSchema, saveDesktopPolicy } from '../services/desktop
|
|
|
25
25
|
import { AndroidPolicyInputSchema, saveAndroidPolicy } from '../services/android/androidPolicy.js';
|
|
26
26
|
import { INPUT_LIMIT_PROFILES } from '../config/limits.js';
|
|
27
27
|
import { ProjectRegistry } from '../projects/registry.js';
|
|
28
|
+
import { ProjectAdmin } from '../projects/ownerAdmin.js';
|
|
29
|
+
import { LEVEL_SUMMARY, parseProjectAccessLevel } from '../security/projectAccess.js';
|
|
28
30
|
import { runCliMenu } from './menu.js';
|
|
29
31
|
import { TunnelRuntime } from '../tunnel/runtime.js';
|
|
30
32
|
import { INSTALLATION_AUTHORITY_ID } from '../auth/constants.js';
|
|
@@ -85,11 +87,16 @@ function withProjectRegistry(fn) {
|
|
|
85
87
|
db.close();
|
|
86
88
|
}
|
|
87
89
|
}
|
|
90
|
+
/** The CLI and Local Config share ONE owner-administration service. */
|
|
91
|
+
function withProjectAdmin(fn) {
|
|
92
|
+
return withProjectRegistry((_registry, store) => fn(new ProjectAdmin(store), store));
|
|
93
|
+
}
|
|
88
94
|
function projectLines(project) {
|
|
89
95
|
return [
|
|
90
96
|
`${project.displayName} ${project.projectId}`,
|
|
91
97
|
` Path: ${project.root}`,
|
|
92
98
|
` Workspace: ${project.workspaceId}`,
|
|
99
|
+
` Access: ${project.accessLevel} — ${LEVEL_SUMMARY[project.accessLevel]}`,
|
|
93
100
|
` Status: ${project.availability} — ${project.statusText}`,
|
|
94
101
|
` Updated: ${new Date(project.updatedAt).toISOString()}`,
|
|
95
102
|
...(project.removedAt === null ? [] : [` Removed: ${new Date(project.removedAt).toISOString()}`]),
|
|
@@ -146,20 +153,22 @@ async function launchHttp(opts) {
|
|
|
146
153
|
const connectionMode = config.tunnel.connectionMode;
|
|
147
154
|
const publicUrl = config.publicUrl;
|
|
148
155
|
const selectedPort = opts.port ?? config.port;
|
|
149
|
-
if (connectionMode
|
|
156
|
+
if (connectionMode !== 'local') {
|
|
150
157
|
if (!publicUrl)
|
|
151
|
-
fail('
|
|
158
|
+
fail('Cloudflare mode requires a public HTTPS origin; configure Cloudflare Local or DODO Tunnel with --public-url https://your-host');
|
|
152
159
|
validatePublicUrl(publicUrl, false);
|
|
160
|
+
}
|
|
161
|
+
if (connectionMode === 'tunnel') {
|
|
153
162
|
if (!config.tunnel.credentialRef)
|
|
154
163
|
fail('Tunnel mode requires a saved credential; run dodo tunnel configure --tunnel --os-credential --public-url https://your-host');
|
|
155
164
|
}
|
|
156
|
-
else {
|
|
165
|
+
else if (connectionMode === 'local') {
|
|
157
166
|
if (opts.web)
|
|
158
|
-
fail('Remote Config requires
|
|
167
|
+
fail('Remote Config requires a public Cloudflare connection; select Cloudflare Local or DODO Tunnel, restart DODO, then run dodo --web');
|
|
159
168
|
if (selectedPort === 0)
|
|
160
169
|
fail('local connection mode requires a concrete MCP port');
|
|
161
170
|
}
|
|
162
|
-
const endpointOrigin = connectionMode === '
|
|
171
|
+
const endpointOrigin = connectionMode === 'local' ? `http://127.0.0.1:${selectedPort}` : publicUrl;
|
|
163
172
|
const startOpts = {
|
|
164
173
|
configPort: config.configPort,
|
|
165
174
|
...(opts.bypass ? { runMode: 'bypass' } : opts.allow ? { runMode: 'allow-all' } : {}),
|
|
@@ -204,8 +213,13 @@ async function launchHttp(opts) {
|
|
|
204
213
|
throw error;
|
|
205
214
|
}
|
|
206
215
|
}
|
|
207
|
-
else
|
|
208
|
-
|
|
216
|
+
else {
|
|
217
|
+
if (connectionMode === 'external' && opts.web)
|
|
218
|
+
printRemoteConfig(await ipcForInstallation('remoteConfig.open'));
|
|
219
|
+
if (!opts.quiet)
|
|
220
|
+
console.log(connectionMode === 'external'
|
|
221
|
+
? `[dodo] Cloudflare Local selected: ${endpointOrigin}/mcp (cloudflared is owner-managed)`
|
|
222
|
+
: `[dodo] Loopback-only connection selected: ${endpointOrigin}/mcp`);
|
|
209
223
|
}
|
|
210
224
|
async function stop(signal) {
|
|
211
225
|
if (shutdownPromise)
|
|
@@ -288,16 +302,40 @@ program.command('setup')
|
|
|
288
302
|
// trust/client access; those remain keyed by the referenced workspace ID.
|
|
289
303
|
const project = program.command('project').description('manage the local owner-only project registry');
|
|
290
304
|
project.command('add <path>')
|
|
291
|
-
.description('register an absolute project directory
|
|
305
|
+
.description('register an absolute project directory and choose what AI clients may do in it')
|
|
292
306
|
.option('--name <display-name>', 'local display name (defaults to the directory name)')
|
|
307
|
+
.option('--access <level>', 'read | edit | full (read = read/search only, edit = also change files, full = also run commands)')
|
|
293
308
|
.option('--json', 'machine-readable result', false)
|
|
294
309
|
.action((projectPath, opts) => {
|
|
295
310
|
try {
|
|
296
|
-
const
|
|
311
|
+
const access = opts.access === undefined ? undefined : parseProjectAccessLevel(opts.access);
|
|
312
|
+
const result = withProjectAdmin((admin) => admin.add({ path: projectPath, ...(opts.name ? { name: opts.name } : {}), ...(access ? { access } : {}) }));
|
|
297
313
|
printJsonOrLines(opts.json, result, () => [
|
|
298
314
|
result.relocated ? 'Updated the location of an existing project identity.' : result.changed ? 'Project registered.' : 'Project was already registered; nothing changed.',
|
|
299
315
|
...projectLines(result.project),
|
|
300
|
-
|
|
316
|
+
result.project.requiresClientAcl
|
|
317
|
+
? 'Managed mode: also allow a client for this project in Local Config before it can be used.'
|
|
318
|
+
: 'Ready to use. A client may do at most what this level allows, still bounded by its own OAuth scopes.',
|
|
319
|
+
]);
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
if (error instanceof DodoError)
|
|
323
|
+
fail(`${error.code}: ${error.message}${error.recovery ? `\n → ${error.recovery}` : ''}`);
|
|
324
|
+
throw error;
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
project.command('access <project>')
|
|
328
|
+
.description('set what AI clients may do in a registered project (by name or ID)')
|
|
329
|
+
.requiredOption('--mode <level>', 'read | edit | full')
|
|
330
|
+
.option('--json', 'machine-readable result', false)
|
|
331
|
+
.action((reference, opts) => {
|
|
332
|
+
try {
|
|
333
|
+
const level = parseProjectAccessLevel(opts.mode);
|
|
334
|
+
const updated = withProjectAdmin((admin) => admin.setAccess(admin.resolve(reference).projectId, level));
|
|
335
|
+
printJsonOrLines(opts.json, updated, () => [
|
|
336
|
+
`Access level for ${updated.displayName} is now ${updated.accessLevel}.`,
|
|
337
|
+
...projectLines(updated),
|
|
338
|
+
'OAuth scopes, trust, approvals, path/secret guards and the command sandbox are unchanged.',
|
|
301
339
|
]);
|
|
302
340
|
}
|
|
303
341
|
catch (error) {
|
|
@@ -307,7 +345,7 @@ project.command('add <path>')
|
|
|
307
345
|
}
|
|
308
346
|
});
|
|
309
347
|
project.command('list')
|
|
310
|
-
.description('list registered projects and current path readiness')
|
|
348
|
+
.description('list registered projects, their access level and current path readiness')
|
|
311
349
|
.option('--all', 'include reviewed removals', false)
|
|
312
350
|
.option('--json', 'machine-readable output', false)
|
|
313
351
|
.action((opts) => {
|
|
@@ -366,12 +404,14 @@ project.command('remove <projectId>')
|
|
|
366
404
|
// --------------------------------------------------------------- tunnel ----
|
|
367
405
|
const tunnel = program.command('tunnel').description('manage or inspect a Cloudflare remotely-managed Tunnel without managing DNS or the Cloudflare account');
|
|
368
406
|
tunnel.command('configure')
|
|
369
|
-
.description('select
|
|
407
|
+
.description('select loopback, owner-managed Cloudflare Local, or DODO-owned Tunnel mode; tokens are never written to config')
|
|
370
408
|
.option('--tunnel', 'select the DODO-owned Tunnel for every dodo start', false)
|
|
371
|
-
.option('--local', '
|
|
409
|
+
.option('--cloudflare-local', 'use cloudflared installed and run by the owner; DODO advertises only the saved public origin', false)
|
|
410
|
+
.option('--loopback', 'select loopback-only MCP for every dodo start', false)
|
|
411
|
+
.option('--local', 'deprecated alias for --loopback', false)
|
|
372
412
|
.option('--managed', 'deprecated alias for --tunnel', false)
|
|
373
|
-
.option('--external', '
|
|
374
|
-
.option('--public-url <url>', 'public HTTPS origin used by
|
|
413
|
+
.option('--external', 'alias for --cloudflare-local', false)
|
|
414
|
+
.option('--public-url <url>', 'public HTTPS origin used by Cloudflare Local or DODO Tunnel')
|
|
375
415
|
.option('--os-credential', 'prompt through the reviewed OS credential provider', false)
|
|
376
416
|
.option('--token-env <name>', 'reference an uppercase environment variable that already contains the token')
|
|
377
417
|
.option('--token-file <absolute-path>', 'reference an owner-private absolute token file')
|
|
@@ -384,9 +424,10 @@ tunnel.command('configure')
|
|
|
384
424
|
.action(async (opts) => {
|
|
385
425
|
try {
|
|
386
426
|
const chooseTunnel = opts.tunnel || opts.managed;
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
427
|
+
const chooseExternal = opts.cloudflareLocal || opts.external;
|
|
428
|
+
const chooseLoopback = opts.loopback || opts.local;
|
|
429
|
+
if ([chooseTunnel, chooseExternal, chooseLoopback].filter(Boolean).length > 1)
|
|
430
|
+
fail('choose only one of --loopback, --cloudflare-local or --tunnel');
|
|
390
431
|
const methods = [opts.osCredential, opts.tokenEnv !== undefined, opts.tokenFile !== undefined].filter(Boolean).length;
|
|
391
432
|
if (methods > 1)
|
|
392
433
|
fail('choose only one of --os-credential, --token-env or --token-file');
|
|
@@ -397,16 +438,18 @@ tunnel.command('configure')
|
|
|
397
438
|
const { dir } = resolveConfigDir(process.env);
|
|
398
439
|
ensureConfigDir(dir);
|
|
399
440
|
const paths = statePaths(dir), current = loadGlobalConfig(paths.configFile);
|
|
400
|
-
|
|
441
|
+
if (chooseExternal && methods > 0)
|
|
442
|
+
fail('Cloudflare Local is owner-managed and does not accept a Tunnel credential in DODO');
|
|
443
|
+
const connectionMode = chooseTunnel ? 'tunnel' : chooseExternal ? 'external' : chooseLoopback ? 'local' : methods > 0 ? 'tunnel' : current.tunnel.connectionMode;
|
|
401
444
|
const publicUrl = opts.publicUrl !== undefined
|
|
402
445
|
? validatePublicUrl(opts.publicUrl, false).origin
|
|
403
446
|
: current.publicUrl;
|
|
404
|
-
if (connectionMode
|
|
405
|
-
fail('
|
|
406
|
-
if (connectionMode
|
|
447
|
+
if (connectionMode !== 'local' && !publicUrl)
|
|
448
|
+
fail('Cloudflare mode requires --public-url https://your-host (or a previously saved public origin)');
|
|
449
|
+
if (connectionMode !== 'local' && publicUrl)
|
|
407
450
|
validatePublicUrl(publicUrl, false);
|
|
408
451
|
if (connectionMode === 'local' && opts.publicUrl !== undefined)
|
|
409
|
-
fail('--public-url is used only with --tunnel');
|
|
452
|
+
fail('--public-url is used only with --cloudflare-local or --tunnel');
|
|
410
453
|
const credentials = await import('../tunnel/credentials.js');
|
|
411
454
|
const { resolveCloudflared } = await import('../tunnel/supervisor.js');
|
|
412
455
|
const previousCredentialRef = current.tunnel.credentialRef;
|
|
@@ -446,14 +489,33 @@ tunnel.command('configure')
|
|
|
446
489
|
saveGlobalConfig(paths.configFile, next);
|
|
447
490
|
if (opts.removeCredential && previousCredentialRef)
|
|
448
491
|
credentials.deleteOsTunnelCredential(previousCredentialRef);
|
|
449
|
-
const result = {
|
|
492
|
+
const result = {
|
|
493
|
+
connectionMode: next.tunnel.connectionMode,
|
|
494
|
+
publicOrigin: next.tunnel.connectionMode === 'local' ? null : next.publicUrl,
|
|
495
|
+
credential: next.tunnel.connectionMode === 'tunnel'
|
|
496
|
+
? next.tunnel.credentialRef ? 'configured' : 'not-configured'
|
|
497
|
+
: 'not-used',
|
|
498
|
+
metricsPort: next.tunnel.metricsPort,
|
|
499
|
+
maxRestarts: next.tunnel.maxRestarts,
|
|
500
|
+
cloudflared: next.tunnel.connectionMode === 'local'
|
|
501
|
+
? 'not-used'
|
|
502
|
+
: next.tunnel.connectionMode === 'external'
|
|
503
|
+
? 'owner-managed'
|
|
504
|
+
: next.tunnel.executable ? 'owner-selected' : 'trusted-PATH',
|
|
505
|
+
started: false,
|
|
506
|
+
};
|
|
450
507
|
if (opts.json)
|
|
451
508
|
console.log(JSON.stringify(result, null, 2));
|
|
452
509
|
else {
|
|
453
510
|
console.log(`Connection mode: ${result.connectionMode}`);
|
|
454
|
-
console.log(`Credential: ${result.credential} (value is not stored in config or displayed)`);
|
|
455
|
-
|
|
456
|
-
|
|
511
|
+
console.log(`Credential: ${result.credential}${result.connectionMode === 'tunnel' ? ' (value is not stored in config or displayed)' : ''}`);
|
|
512
|
+
if (result.connectionMode === 'tunnel')
|
|
513
|
+
console.log(`Readiness: 127.0.0.1:${result.metricsPort} Restarts: ${result.maxRestarts}`);
|
|
514
|
+
console.log(result.connectionMode === 'tunnel'
|
|
515
|
+
? 'Saved. Every dodo start now owns this Tunnel and fails closed if it cannot start.'
|
|
516
|
+
: result.connectionMode === 'external'
|
|
517
|
+
? 'Saved. DODO advertises the public origin; you must start and maintain cloudflared on this machine.'
|
|
518
|
+
: 'Saved. Every dodo start now uses only the loopback MCP endpoint.');
|
|
457
519
|
}
|
|
458
520
|
}
|
|
459
521
|
catch (error) {
|
|
@@ -1498,9 +1560,11 @@ Android (ADB host authorization and exact-device permission):
|
|
|
1498
1560
|
dodo android disable revoke DODO Android access
|
|
1499
1561
|
Remote setup:
|
|
1500
1562
|
dodo init --public-url https://... configure public origin once
|
|
1563
|
+
dodo tunnel configure --cloudflare-local --public-url https://...
|
|
1564
|
+
use cloudflared you install/run yourself
|
|
1501
1565
|
dodo tunnel configure --tunnel --os-credential --public-url https://...
|
|
1502
1566
|
select the persistent DODO Tunnel
|
|
1503
|
-
dodo tunnel configure --
|
|
1567
|
+
dodo tunnel configure --loopback select loopback-only MCP
|
|
1504
1568
|
dodo start use exactly the saved connection mode
|
|
1505
1569
|
dodo --web start/renew Remote Config through that tunnel for 1 hour
|
|
1506
1570
|
dodo web --close close Remote Config; keep MCP/Tunnel running`);
|