@doquflow/cli 1.7.0 → 2.0.0

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.
@@ -0,0 +1,270 @@
1
+ "use strict";
2
+ /**
3
+ * docuflow doctor
4
+ *
5
+ * Diagnostic command that reports installed DocuFlow packages, MCP server
6
+ * registrations, detected workflow, and actionable recommendations.
7
+ *
8
+ * Usage:
9
+ * docuflow doctor # human-readable report
10
+ * docuflow doctor --json # machine-readable JSON
11
+ * docuflow doctor --quiet # recommendations only
12
+ */
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.run = run;
18
+ const node_fs_1 = __importDefault(require("node:fs"));
19
+ const node_path_1 = __importDefault(require("node:path"));
20
+ const node_os_1 = __importDefault(require("node:os"));
21
+ // ── Package version resolution ────────────────────────────────────────────────
22
+ function resolvePackageVersion(pkgName) {
23
+ const candidates = [
24
+ () => require.resolve(`${pkgName}/package.json`),
25
+ () => node_path_1.default.resolve(__dirname, "../../../", pkgName.replace("@doquflow/", ""), "package.json"),
26
+ () => node_path_1.default.resolve(__dirname, "../../", pkgName.replace("@doquflow/", ""), "package.json"),
27
+ ];
28
+ for (const c of candidates) {
29
+ try {
30
+ const pkgPath = c();
31
+ const raw = node_fs_1.default.readFileSync(pkgPath, "utf8");
32
+ return JSON.parse(raw).version;
33
+ }
34
+ catch { /* try next */ }
35
+ }
36
+ return null;
37
+ }
38
+ function getInstalledPackages() {
39
+ const packages = ["@doquflow/cli", "@doquflow/core", "@doquflow/studio", "@doquflow/server"];
40
+ return packages.map(name => ({ name, version: resolvePackageVersion(name) }));
41
+ }
42
+ function getClaudeDesktopConfigPath() {
43
+ const p = process.platform;
44
+ if (p === "darwin")
45
+ return node_path_1.default.join(node_os_1.default.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
46
+ if (p === "win32")
47
+ return node_path_1.default.join(node_os_1.default.homedir(), "AppData", "Roaming", "Claude", "claude_desktop_config.json");
48
+ return node_path_1.default.join(node_os_1.default.homedir(), ".config", "Claude", "claude_desktop_config.json");
49
+ }
50
+ function getMcpRegistrations(projectPath) {
51
+ const results = [];
52
+ // Project-level .mcp.json
53
+ const projectMcp = node_path_1.default.join(projectPath, ".mcp.json");
54
+ try {
55
+ const cfg = JSON.parse(node_fs_1.default.readFileSync(projectMcp, "utf8"));
56
+ const entry = cfg.mcpServers?.docuflow;
57
+ results.push({ source: ".mcp.json", registered: !!entry, command: entry?.command, args: entry?.args });
58
+ }
59
+ catch {
60
+ results.push({ source: ".mcp.json", registered: false });
61
+ }
62
+ // Claude Desktop
63
+ try {
64
+ const cfg = JSON.parse(node_fs_1.default.readFileSync(getClaudeDesktopConfigPath(), "utf8"));
65
+ const entry = cfg.mcpServers?.docuflow;
66
+ results.push({ source: "Claude Desktop", registered: !!entry, command: entry?.command, args: entry?.args });
67
+ }
68
+ catch {
69
+ results.push({ source: "Claude Desktop", registered: false });
70
+ }
71
+ // VS Code user MCP
72
+ const vscodePath = process.platform === "darwin"
73
+ ? node_path_1.default.join(node_os_1.default.homedir(), "Library", "Application Support", "Code", "User", "mcp.json")
74
+ : process.platform === "win32"
75
+ ? node_path_1.default.join(node_os_1.default.homedir(), "AppData", "Roaming", "Code", "User", "mcp.json")
76
+ : node_path_1.default.join(node_os_1.default.homedir(), ".config", "Code", "User", "mcp.json");
77
+ try {
78
+ const cfg = JSON.parse(node_fs_1.default.readFileSync(vscodePath, "utf8"));
79
+ const entry = cfg.servers?.docuflow;
80
+ results.push({ source: "VS Code (user)", registered: !!entry, command: entry?.command, args: entry?.args });
81
+ }
82
+ catch {
83
+ results.push({ source: "VS Code (user)", registered: false });
84
+ }
85
+ // Copilot CLI
86
+ const copilotPath = node_path_1.default.join(node_os_1.default.homedir(), ".copilot", "mcp-config.json");
87
+ try {
88
+ const cfg = JSON.parse(node_fs_1.default.readFileSync(copilotPath, "utf8"));
89
+ const entry = cfg.mcpServers?.docuflow;
90
+ results.push({ source: "Copilot CLI", registered: !!entry, command: entry?.command, args: entry?.args });
91
+ }
92
+ catch {
93
+ results.push({ source: "Copilot CLI", registered: false });
94
+ }
95
+ return results;
96
+ }
97
+ function detectWorkflow(projectPath) {
98
+ const docuDir = node_path_1.default.join(projectPath, ".docuflow");
99
+ const hasDocuflow = node_fs_1.default.existsSync(docuDir);
100
+ let hasSources = false;
101
+ let hasSpecs = false;
102
+ let hasQueryUsage = false;
103
+ let watchRunning = false;
104
+ if (hasDocuflow) {
105
+ try {
106
+ hasSources = node_fs_1.default.readdirSync(node_path_1.default.join(docuDir, "sources")).some(f => f.endsWith(".md"));
107
+ }
108
+ catch { /* */ }
109
+ try {
110
+ hasSpecs = node_fs_1.default.readdirSync(node_path_1.default.join(docuDir, "specs")).some(f => f.endsWith(".md"));
111
+ }
112
+ catch { /* */ }
113
+ try {
114
+ const log = node_fs_1.default.readFileSync(node_path_1.default.join(docuDir, "log.md"), "utf8");
115
+ hasQueryUsage = /query-wiki|queryWiki/i.test(log);
116
+ }
117
+ catch { /* */ }
118
+ // Check watch daemon PID file
119
+ const pidFile = node_path_1.default.join(docuDir, "watch.pid.json");
120
+ try {
121
+ const data = JSON.parse(node_fs_1.default.readFileSync(pidFile, "utf8"));
122
+ try {
123
+ process.kill(data.pid, 0);
124
+ watchRunning = true;
125
+ }
126
+ catch { /* not running */ }
127
+ }
128
+ catch { /* no pid file */ }
129
+ }
130
+ return { hasDocuflow, hasSources, hasSpecs, hasQueryUsage, watchRunning };
131
+ }
132
+ function getHealthSummary(projectPath) {
133
+ const docuDir = node_path_1.default.join(projectPath, ".docuflow");
134
+ const byCategory = { entities: 0, concepts: 0, timelines: 0, syntheses: 0 };
135
+ for (const cat of Object.keys(byCategory)) {
136
+ try {
137
+ const files = node_fs_1.default.readdirSync(node_path_1.default.join(docuDir, "wiki", cat));
138
+ byCategory[cat] = files.filter(f => f.endsWith(".md")).length;
139
+ }
140
+ catch { /* dir may not exist */ }
141
+ }
142
+ const totalPages = Object.values(byCategory).reduce((a, b) => a + b, 0);
143
+ let lastUpdate = "never";
144
+ try {
145
+ const log = node_fs_1.default.readFileSync(node_path_1.default.join(docuDir, "log.md"), "utf8");
146
+ const match = log.match(/\[(\d{4}-\d{2}-\d{2}[^\]]*)\]/);
147
+ if (match) {
148
+ const d = new Date(match[1]);
149
+ if (!isNaN(d.getTime())) {
150
+ const diffH = Math.floor((Date.now() - d.getTime()) / 3_600_000);
151
+ lastUpdate = diffH < 1 ? "just now" : diffH < 24 ? `${diffH}h ago` : `${Math.floor(diffH / 24)}d ago`;
152
+ }
153
+ }
154
+ }
155
+ catch { /* */ }
156
+ return { totalPages, byCategory, lastUpdate, healthScore: null };
157
+ }
158
+ function buildRecommendations(packages, registrations, workflow, health) {
159
+ const recs = [];
160
+ const pkgMap = Object.fromEntries(packages.map(p => [p.name, p.version]));
161
+ // Core/Studio installed?
162
+ if (!pkgMap["@doquflow/core"] && !pkgMap["@doquflow/studio"]) {
163
+ if (pkgMap["@doquflow/cli"]) {
164
+ // CLI installs core+studio transitively — this just means they weren't directly required
165
+ }
166
+ else {
167
+ recs.push({ severity: "error", message: "No DocuFlow packages detected", action: 'Run: npm i -g @doquflow/cli' });
168
+ }
169
+ }
170
+ // Suggest minimal install if only using query/ingest
171
+ const hasCli = !!pkgMap["@doquflow/cli"];
172
+ const hasCore = !!pkgMap["@doquflow/core"];
173
+ const hasStudio = !!pkgMap["@doquflow/studio"];
174
+ if (hasCli && !workflow.watchRunning && !workflow.hasQueryUsage && hasCore && !hasStudio) {
175
+ recs.push({ severity: "info", message: "You only use core tools (query/ingest)", action: "Consider: npm i -g @doquflow/core for a smaller install" });
176
+ }
177
+ // Not initialised
178
+ if (!workflow.hasDocuflow) {
179
+ recs.push({ severity: "error", message: ".docuflow/ not found in this directory", action: "Run: docuflow init" });
180
+ return recs; // no point checking further
181
+ }
182
+ // No MCP registration anywhere
183
+ const anyRegistered = registrations.some(r => r.registered);
184
+ if (!anyRegistered) {
185
+ recs.push({ severity: "error", message: "DocuFlow is not registered with any MCP host", action: "Run: docuflow init (re-run is safe)" });
186
+ }
187
+ // No sources
188
+ if (!workflow.hasSources) {
189
+ recs.push({ severity: "warn", message: "No source documents in .docuflow/sources/", action: "Drop a markdown file there, then run: docuflow ingest" });
190
+ }
191
+ // Empty wiki
192
+ if (health.totalPages === 0 && workflow.hasSources) {
193
+ recs.push({ severity: "warn", message: "Wiki is empty but sources exist", action: "Run: docuflow rewiki" });
194
+ }
195
+ // Old @doquflow/server pointing at dist/index.js instead of dist/mcp/index.js
196
+ for (const reg of registrations.filter(r => r.registered)) {
197
+ const argStr = reg.args?.join(" ") ?? "";
198
+ if (argStr.includes("@doquflow/server") && argStr.includes("dist/index.js")) {
199
+ recs.push({ severity: "info", message: `${reg.source}: MCP registration uses old server path`, action: "Run: docuflow init to refresh to @doquflow/studio/dist/mcp/index.js" });
200
+ }
201
+ }
202
+ if (recs.length === 0) {
203
+ recs.push({ severity: "info", message: "Everything looks good", action: "No action needed" });
204
+ }
205
+ return recs;
206
+ }
207
+ // ── Output formatters ─────────────────────────────────────────────────────────
208
+ function icon(severity) {
209
+ return severity === "error" ? "🔴" : severity === "warn" ? "🟡" : "🟢";
210
+ }
211
+ function printHuman(packages, registrations, workflow, health, recs, quiet) {
212
+ if (!quiet) {
213
+ // 1. Installed packages
214
+ console.log("\n── 1. Installed packages ────────────────────────────────");
215
+ for (const pkg of packages) {
216
+ const mark = pkg.version ? "✓" : "✗";
217
+ const ver = pkg.version ?? "not found";
218
+ console.log(` ${mark} ${pkg.name.padEnd(22)} ${ver}`);
219
+ }
220
+ // 2. MCP registrations
221
+ console.log("\n── 2. MCP server registrations ──────────────────────────");
222
+ for (const reg of registrations) {
223
+ const mark = reg.registered ? "✓" : "·";
224
+ const detail = reg.registered && reg.command
225
+ ? ` ${reg.command} ${(reg.args ?? []).slice(-1)[0] ?? ""}`
226
+ : "";
227
+ console.log(` ${mark} ${reg.source.padEnd(18)}${reg.registered ? "registered" : "not registered"}${detail}`);
228
+ }
229
+ // 3. Workflow detection
230
+ console.log("\n── 3. Workflow detection ─────────────────────────────────");
231
+ console.log(` ${workflow.hasDocuflow ? "✓" : "✗"} .docuflow/ initialised`);
232
+ console.log(` ${workflow.hasSources ? "✓" : "·"} source documents present`);
233
+ console.log(` ${workflow.hasSpecs ? "✓" : "·"} specs written`);
234
+ console.log(` ${workflow.hasQueryUsage ? "✓" : "·"} query_wiki usage in logs`);
235
+ console.log(` ${workflow.watchRunning ? "✓" : "·"} watch daemon running`);
236
+ // 4. Health summary
237
+ console.log("\n── 5. Wiki health summary ───────────────────────────────");
238
+ console.log(` Pages total: ${health.totalPages}`);
239
+ if (health.totalPages > 0) {
240
+ for (const [cat, n] of Object.entries(health.byCategory)) {
241
+ if (n > 0)
242
+ console.log(` ${cat.padEnd(14)} ${n}`);
243
+ }
244
+ }
245
+ console.log(` Last updated: ${health.lastUpdate}`);
246
+ console.log("");
247
+ }
248
+ // 4. Recommendations (always shown)
249
+ if (!quiet)
250
+ console.log("── 4. Recommendations ───────────────────────────────────");
251
+ for (const rec of recs) {
252
+ console.log(` ${icon(rec.severity)} ${rec.message}`);
253
+ console.log(` → ${rec.action}`);
254
+ }
255
+ console.log("");
256
+ }
257
+ // ── Entry point ───────────────────────────────────────────────────────────────
258
+ async function run(opts = {}) {
259
+ const projectPath = process.cwd();
260
+ const packages = getInstalledPackages();
261
+ const registrations = getMcpRegistrations(projectPath);
262
+ const workflow = detectWorkflow(projectPath);
263
+ const health = getHealthSummary(projectPath);
264
+ const recs = buildRecommendations(packages, registrations, workflow, health);
265
+ if (opts.json) {
266
+ console.log(JSON.stringify({ packages, registrations, workflow, health, recommendations: recs }, null, 2));
267
+ return;
268
+ }
269
+ printHuman(packages, registrations, workflow, health, recs, opts.quiet ?? false);
270
+ }
@@ -13,6 +13,7 @@ function printCoreHelp() {
13
13
  console.log(' docuflow query "<question>" Ask the wiki — answer with citations');
14
14
  console.log(' docuflow status Show wiki health and counts');
15
15
  console.log(' docuflow rewiki Migrate / re-ingest with current rules');
16
+ console.log(' docuflow doctor Diagnose install, MCP registration, and wiki health');
16
17
  console.log('');
17
18
  console.log('ADVANCED');
18
19
  console.log(' docuflow advanced --help See watch / sync / ui / review / recent / suggest / update');
@@ -16,9 +16,12 @@ const c = {
16
16
  };
17
17
  function loadServerTool(toolFile) {
18
18
  const candidates = [
19
- () => require(`@doquflow/server/dist/tools/${toolFile}`),
20
- () => require(node_path_1.default.resolve(__dirname, "../../../server/dist/tools", toolFile)),
21
- () => require(node_path_1.default.resolve(__dirname, "../../server/dist/tools", toolFile)),
19
+ () => require(`@doquflow/core/dist/tools/${toolFile}`),
20
+ () => require(node_path_1.default.resolve(__dirname, "../../../core/dist/tools", toolFile)),
21
+ () => require(node_path_1.default.resolve(__dirname, "../../core/dist/tools", toolFile)),
22
+ () => require(`@doquflow/studio/dist/tools/${toolFile}`),
23
+ () => require(node_path_1.default.resolve(__dirname, "../../../studio/dist/tools", toolFile)),
24
+ () => require(node_path_1.default.resolve(__dirname, "../../studio/dist/tools", toolFile)),
22
25
  ];
23
26
  for (const attempt of candidates) {
24
27
  try {
@@ -62,13 +62,13 @@ function getCodexConfigPath() {
62
62
  return node_path_1.default.join(node_os_1.default.homedir(), ".codex", "config.toml");
63
63
  }
64
64
  function resolveServerBin() {
65
- // Try npm-installed package first
65
+ // Try npm-installed studio MCP binary first
66
66
  try {
67
- return require.resolve("@doquflow/server/dist/index.js");
67
+ return require.resolve("@doquflow/studio/dist/mcp/index.js");
68
68
  }
69
69
  catch {
70
70
  // Fallback: monorepo sibling path (dev environment)
71
- return node_path_1.default.resolve(__dirname, "..", "..", "server", "dist", "index.js");
71
+ return node_path_1.default.resolve(__dirname, "..", "..", "studio", "dist", "mcp", "index.js");
72
72
  }
73
73
  }
74
74
  async function copyTemplateFile(templateName, destPath) {
@@ -36,9 +36,12 @@ const c = {
36
36
  // ─── Dynamic server tool loader (mirrors rewiki.ts pattern) ───────────────────
37
37
  function loadServerTool(toolFile) {
38
38
  const candidates = [
39
- () => require(`@doquflow/server/dist/tools/${toolFile}`),
40
- () => require(node_path_1.default.resolve(__dirname, "../../../server/dist/tools", toolFile)),
41
- () => require(node_path_1.default.resolve(__dirname, "../../server/dist/tools", toolFile)),
39
+ () => require(`@doquflow/core/dist/tools/${toolFile}`),
40
+ () => require(node_path_1.default.resolve(__dirname, "../../../core/dist/tools", toolFile)),
41
+ () => require(node_path_1.default.resolve(__dirname, "../../core/dist/tools", toolFile)),
42
+ () => require(`@doquflow/studio/dist/tools/${toolFile}`),
43
+ () => require(node_path_1.default.resolve(__dirname, "../../../studio/dist/tools", toolFile)),
44
+ () => require(node_path_1.default.resolve(__dirname, "../../studio/dist/tools", toolFile)),
42
45
  ];
43
46
  for (const attempt of candidates) {
44
47
  try {
@@ -36,9 +36,12 @@ const c = {
36
36
  // ─── Dynamic server tool loader (mirrors sync.ts pattern) ─────────────────────
37
37
  function loadServerTool(toolFile) {
38
38
  const candidates = [
39
- () => require(`@doquflow/server/dist/tools/${toolFile}`),
40
- () => require(node_path_1.default.resolve(__dirname, "../../../server/dist/tools", toolFile)),
41
- () => require(node_path_1.default.resolve(__dirname, "../../server/dist/tools", toolFile)),
39
+ () => require(`@doquflow/core/dist/tools/${toolFile}`),
40
+ () => require(node_path_1.default.resolve(__dirname, "../../../core/dist/tools", toolFile)),
41
+ () => require(node_path_1.default.resolve(__dirname, "../../core/dist/tools", toolFile)),
42
+ () => require(`@doquflow/studio/dist/tools/${toolFile}`),
43
+ () => require(node_path_1.default.resolve(__dirname, "../../../studio/dist/tools", toolFile)),
44
+ () => require(node_path_1.default.resolve(__dirname, "../../studio/dist/tools", toolFile)),
42
45
  ];
43
46
  for (const attempt of candidates) {
44
47
  try {
@@ -39,9 +39,14 @@ const c = {
39
39
  // ─── Dynamic server tool loader ────────────────────────────────────────────────
40
40
  function loadServerTool(toolFile) {
41
41
  const candidates = [
42
- () => require(`@doquflow/server/dist/tools/${toolFile}`),
43
- () => require(node_path_1.default.resolve(__dirname, "../../../server/dist/tools", toolFile)),
44
- () => require(node_path_1.default.resolve(__dirname, "../../server/dist/tools", toolFile)),
42
+ // v2.0 — 4 core tools live in @doquflow/core
43
+ () => require(`@doquflow/core/dist/tools/${toolFile}`),
44
+ () => require(node_path_1.default.resolve(__dirname, "../../../core/dist/tools", toolFile)),
45
+ () => require(node_path_1.default.resolve(__dirname, "../../core/dist/tools", toolFile)),
46
+ // v2.0 — 11 advanced tools live in @doquflow/studio
47
+ () => require(`@doquflow/studio/dist/tools/${toolFile}`),
48
+ () => require(node_path_1.default.resolve(__dirname, "../../../studio/dist/tools", toolFile)),
49
+ () => require(node_path_1.default.resolve(__dirname, "../../studio/dist/tools", toolFile)),
45
50
  ];
46
51
  for (const attempt of candidates) {
47
52
  try {
@@ -135,10 +140,10 @@ function runClaudeSync(prompt, projectPath, allowDangerousPermissions = false) {
135
140
  // Build the MCP config pointing to the local server binary
136
141
  let serverBin;
137
142
  try {
138
- serverBin = require.resolve("@doquflow/server/dist/index.js");
143
+ serverBin = require.resolve("@doquflow/studio/dist/mcp/index.js");
139
144
  }
140
145
  catch {
141
- serverBin = node_path_1.default.resolve(__dirname, "../../server/dist/index.js");
146
+ serverBin = node_path_1.default.resolve(__dirname, "../../studio/dist/mcp/index.js");
142
147
  }
143
148
  const mcpConfig = JSON.stringify({
144
149
  mcpServers: {
@@ -27,7 +27,7 @@ const init_1 = require("./init");
27
27
  const watch_1 = require("./watch");
28
28
  function loadTool(file, exportName) {
29
29
  // eslint-disable-next-line @typescript-eslint/no-require-imports
30
- return require(`@doquflow/server/dist/tools/${file}`)[exportName];
30
+ return require(`@doquflow/studio/dist/tools/${file}`)[exportName];
31
31
  }
32
32
  // ── Helpers ───────────────────────────────────────────────────────────────────
33
33
  async function findDocuflowProjects(scanRoot) {
@@ -102,7 +102,7 @@ async function run(opts = {}) {
102
102
  process.exit(result.status ?? 1);
103
103
  }
104
104
  console.log(`\n✓ Updated to ${latest}.`);
105
- console.log(` This refreshed the CLI, bundled UI (ui-dist), and the @doquflow/server dependency.`);
105
+ console.log(` This refreshed the CLI, bundled UI (ui-dist), and the @doquflow/core + @doquflow/studio dependencies.`);
106
106
  const stale = await isPortInUse(DEFAULT_PORT);
107
107
  if (stale) {
108
108
  console.log('');
@@ -54,9 +54,12 @@ const node_child_process_1 = require("node:child_process");
54
54
  // ─── Dynamic server tool loader ────────────────────────────────────────────────
55
55
  function loadServerTool(toolFile) {
56
56
  const candidates = [
57
- () => require(`@doquflow/server/dist/tools/${toolFile}`),
58
- () => require(node_path_1.default.resolve(__dirname, "../../../server/dist/tools", toolFile)),
59
- () => require(node_path_1.default.resolve(__dirname, "../../server/dist/tools", toolFile)),
57
+ () => require(`@doquflow/core/dist/tools/${toolFile}`),
58
+ () => require(node_path_1.default.resolve(__dirname, "../../../core/dist/tools", toolFile)),
59
+ () => require(node_path_1.default.resolve(__dirname, "../../core/dist/tools", toolFile)),
60
+ () => require(`@doquflow/studio/dist/tools/${toolFile}`),
61
+ () => require(node_path_1.default.resolve(__dirname, "../../../studio/dist/tools", toolFile)),
62
+ () => require(node_path_1.default.resolve(__dirname, "../../studio/dist/tools", toolFile)),
60
63
  ];
61
64
  for (const attempt of candidates) {
62
65
  try {
@@ -202,10 +205,10 @@ let _allowDangerousPermissions = false;
202
205
  function runClaudeCLI(prompt, timeoutMs = 120_000) {
203
206
  let serverBin;
204
207
  try {
205
- serverBin = require.resolve("@doquflow/server/dist/index.js");
208
+ serverBin = require.resolve("@doquflow/studio/dist/mcp/index.js");
206
209
  }
207
210
  catch {
208
- serverBin = node_path_1.default.resolve(__dirname, "../../server/dist/index.js");
211
+ serverBin = node_path_1.default.resolve(__dirname, "../../studio/dist/mcp/index.js");
209
212
  }
210
213
  const mcpConfig = JSON.stringify({
211
214
  mcpServers: { docuflow: { type: "stdio", command: process.execPath, args: [serverBin] } }
package/dist/index.js CHANGED
@@ -100,6 +100,13 @@ function dispatch(c, r) {
100
100
  noBackup: hasFlagIn(r, '--no-backup'),
101
101
  quiet: hasFlagIn(r, '--quiet', '-q'),
102
102
  }));
103
+ // ── doctor — diagnose install, MCP registration, and wiki health ──────────
104
+ }
105
+ else if (c === 'doctor') {
106
+ Promise.resolve().then(() => __importStar(require('./commands/doctor'))).then(m => m.run({
107
+ json: hasFlagIn(r, '--json'),
108
+ quiet: hasFlagIn(r, '--quiet', '-q'),
109
+ }));
103
110
  // ── ADVANCED ──────────────────────────────────────────────────────────────
104
111
  }
105
112
  else if (c === 'suggest') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doquflow/cli",
3
- "version": "1.7.0",
3
+ "version": "2.0.0",
4
4
  "description": "CLI for setting up Docuflow in your project",
5
5
  "author": "Docuflow <hello@doquflows.dev>",
6
6
  "license": "MIT",
@@ -28,10 +28,11 @@
28
28
  "README.md"
29
29
  ],
30
30
  "scripts": {
31
- "build": "tsc && node -e \"const fs=require('fs'),p=require('path'),src=p.join(process.cwd(),'../ui/dist'),dst=p.join(process.cwd(),'ui-dist');if(!fs.existsSync(src)){console.log('Warning: packages/ui/dist not found — run npm run build:ui first');process.exit(0)}fs.mkdirSync(dst,{recursive:true});fs.cpSync(src,dst,{recursive:true,force:true});console.log(' ✓ ui-dist synced from packages/ui/dist ('+(fs.readdirSync(dst).length)+' files at root)')\""
31
+ "build": "tsc && node -e \"const fs=require('fs'),p=require('path'),src=p.join(process.cwd(),'../studio/ui-dist'),dst=p.join(process.cwd(),'ui-dist');if(!fs.existsSync(src)){console.log('Warning: packages/studio/ui-dist not found — run npm run build:ui -w packages/studio first');process.exit(0)}fs.mkdirSync(dst,{recursive:true});fs.cpSync(src,dst,{recursive:true,force:true});console.log(' ✓ ui-dist synced from packages/studio/ui-dist ('+(fs.readdirSync(dst).length)+' files at root)')\""
32
32
  },
33
33
  "dependencies": {
34
- "@doquflow/server": "1.7.0",
34
+ "@doquflow/core": "2.0.0",
35
+ "@doquflow/studio": "2.0.0",
35
36
  "cors": "^2.8.5",
36
37
  "express": "^4.19.2"
37
38
  },