@doquflow/cli 1.5.1 → 1.5.2
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/dist/commands/init.js +119 -103
- package/dist/commands/recent.js +276 -0
- package/dist/commands/ui.js +156 -1
- package/dist/index.js +10 -0
- package/package.json +2 -2
- package/ui-dist/assets/index-DsJuaoK7.js +44 -0
- package/ui-dist/assets/index-lXOzEPMP.js +44 -0
- package/ui-dist/index.html +1 -1
package/dist/commands/ui.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* docuflow ui / docuflow start
|
|
4
4
|
*
|
|
5
5
|
* Starts the DocuFlow web interface — a single Express server on port 48821 that serves:
|
|
6
|
-
* • All /api/* routes (projects, wiki, health, activity, ask, search)
|
|
6
|
+
* • All /api/* routes (projects, wiki, health, activity, ask, search, sync, init, watch)
|
|
7
7
|
* • Static Vite build from ui-dist/ (bundled with this CLI package)
|
|
8
8
|
* • SPA fallback — any non-API, non-asset route returns index.html
|
|
9
9
|
*
|
|
@@ -23,6 +23,8 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
23
23
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
24
24
|
const express_1 = __importDefault(require("express"));
|
|
25
25
|
const cors_1 = __importDefault(require("cors"));
|
|
26
|
+
const init_1 = require("./init");
|
|
27
|
+
const watch_1 = require("./watch");
|
|
26
28
|
function loadTool(file, exportName) {
|
|
27
29
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
28
30
|
return require(`@doquflow/server/dist/tools/${file}`)[exportName];
|
|
@@ -203,6 +205,8 @@ async function run(opts = {}) {
|
|
|
203
205
|
const queryWikiTool = loadTool('query-wiki', 'queryWiki');
|
|
204
206
|
const wikiSearchTool = loadTool('wiki-search', 'wikiSearch');
|
|
205
207
|
const buildGraphTool = loadTool('build-graph', 'buildGraph');
|
|
208
|
+
const ingestSourceTool = loadTool('ingest-source', 'ingestSource');
|
|
209
|
+
const updateIndexTool = loadTool('update-index', 'updateIndex');
|
|
206
210
|
// ── 3. Express app ──────────────────────────────────────────────────────
|
|
207
211
|
const app = (0, express_1.default)();
|
|
208
212
|
app.use((0, cors_1.default)());
|
|
@@ -359,6 +363,157 @@ async function run(opts = {}) {
|
|
|
359
363
|
return res.status(500).json({ error: e.message });
|
|
360
364
|
}
|
|
361
365
|
});
|
|
366
|
+
// ── Sync: ingest all sources → rebuild index → lint ──────────────────────
|
|
367
|
+
app.post('/api/sync', async (req, res) => {
|
|
368
|
+
const { path: projectPath } = req.body;
|
|
369
|
+
if (!projectPath)
|
|
370
|
+
return res.status(400).json({ error: 'path required' });
|
|
371
|
+
const docuDir = node_path_1.default.join(projectPath, '.docuflow');
|
|
372
|
+
const sourcesDir = node_path_1.default.join(docuDir, 'sources');
|
|
373
|
+
if (!node_fs_1.default.existsSync(docuDir)) {
|
|
374
|
+
return res.status(400).json({ error: '.docuflow not found — run init first' });
|
|
375
|
+
}
|
|
376
|
+
try {
|
|
377
|
+
// Collect source files
|
|
378
|
+
let sourceFiles = [];
|
|
379
|
+
try {
|
|
380
|
+
sourceFiles = (await promises_1.default.readdir(sourcesDir)).filter(f => f.endsWith('.md'));
|
|
381
|
+
}
|
|
382
|
+
catch { /* sources/ may not exist yet */ }
|
|
383
|
+
// Ingest each source file
|
|
384
|
+
let pagesCreated = 0;
|
|
385
|
+
const errors = [];
|
|
386
|
+
for (const filename of sourceFiles) {
|
|
387
|
+
try {
|
|
388
|
+
const r = await ingestSourceTool({ project_path: projectPath, source_filename: filename });
|
|
389
|
+
pagesCreated += r.pages_created?.length ?? 0;
|
|
390
|
+
}
|
|
391
|
+
catch (e) {
|
|
392
|
+
errors.push(`${filename}: ${e.message}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// Rebuild index and lint — wiki dir may be empty on a fresh/blank project
|
|
396
|
+
let health_score = 0;
|
|
397
|
+
try {
|
|
398
|
+
await updateIndexTool({ project_path: projectPath });
|
|
399
|
+
const lint = await lintWikiTool({ project_path: projectPath });
|
|
400
|
+
health_score = lint.health_score ?? 0;
|
|
401
|
+
}
|
|
402
|
+
catch { /* empty wiki dir — health_score stays 0 */ }
|
|
403
|
+
return res.json({
|
|
404
|
+
sources_processed: sourceFiles.length,
|
|
405
|
+
pages_created: pagesCreated,
|
|
406
|
+
health_score,
|
|
407
|
+
errors,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
catch (e) {
|
|
411
|
+
return res.status(500).json({ error: e.message });
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
// ── Init: create .docuflow/ structure and register project ────────────────
|
|
415
|
+
app.post('/api/init', async (req, res) => {
|
|
416
|
+
const { path: projectPath } = req.body;
|
|
417
|
+
if (!projectPath)
|
|
418
|
+
return res.status(400).json({ error: 'path required' });
|
|
419
|
+
if (!node_fs_1.default.existsSync(projectPath)) {
|
|
420
|
+
return res.status(400).json({ error: 'path does not exist' });
|
|
421
|
+
}
|
|
422
|
+
try {
|
|
423
|
+
const result = await (0, init_1.runInit)(projectPath);
|
|
424
|
+
return res.json(result);
|
|
425
|
+
}
|
|
426
|
+
catch (e) {
|
|
427
|
+
return res.status(500).json({ error: e.message });
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
// ── Watch daemon status / stop / start ────────────────────────────────────
|
|
431
|
+
app.get('/api/watch/status', async (req, res) => {
|
|
432
|
+
const projectPath = req.query.path;
|
|
433
|
+
if (!projectPath)
|
|
434
|
+
return res.status(400).json({ error: 'path required' });
|
|
435
|
+
try {
|
|
436
|
+
const data = await (0, watch_1.readPidFile)(projectPath);
|
|
437
|
+
if (!data)
|
|
438
|
+
return res.json({ running: false });
|
|
439
|
+
const alive = (0, watch_1.isProcessAlive)(data.pid);
|
|
440
|
+
if (!alive)
|
|
441
|
+
return res.json({ running: false });
|
|
442
|
+
const uptimeMs = Date.now() - new Date(data.started_at).getTime();
|
|
443
|
+
const uptimeMin = Math.floor(uptimeMs / 60_000);
|
|
444
|
+
return res.json({
|
|
445
|
+
running: true,
|
|
446
|
+
pid: data.pid,
|
|
447
|
+
bridge: data.bridge,
|
|
448
|
+
started_at: data.started_at,
|
|
449
|
+
uptime: uptimeMin < 60 ? `${uptimeMin}m` : `${Math.floor(uptimeMin / 60)}h ${uptimeMin % 60}m`,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
catch (e) {
|
|
453
|
+
return res.status(500).json({ error: e.message });
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
app.post('/api/watch/stop', async (req, res) => {
|
|
457
|
+
const { path: projectPath } = req.body;
|
|
458
|
+
if (!projectPath)
|
|
459
|
+
return res.status(400).json({ error: 'path required' });
|
|
460
|
+
try {
|
|
461
|
+
const data = await (0, watch_1.readPidFile)(projectPath);
|
|
462
|
+
if (!data || !(0, watch_1.isProcessAlive)(data.pid)) {
|
|
463
|
+
return res.json({ ok: true, message: 'Daemon not running' });
|
|
464
|
+
}
|
|
465
|
+
process.kill(data.pid, 'SIGTERM');
|
|
466
|
+
// Remove stale PID file — daemon will also try to clean it up on exit
|
|
467
|
+
setTimeout(async () => {
|
|
468
|
+
try {
|
|
469
|
+
await promises_1.default.unlink((0, watch_1.getPidFilePath)(projectPath));
|
|
470
|
+
}
|
|
471
|
+
catch { /* already removed */ }
|
|
472
|
+
}, 2000);
|
|
473
|
+
return res.json({ ok: true, message: `Stopped watch daemon (PID ${data.pid})` });
|
|
474
|
+
}
|
|
475
|
+
catch (e) {
|
|
476
|
+
return res.status(500).json({ error: e.message });
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
app.post('/api/watch/start', async (req, res) => {
|
|
480
|
+
const { path: projectPath } = req.body;
|
|
481
|
+
if (!projectPath)
|
|
482
|
+
return res.status(400).json({ error: 'path required' });
|
|
483
|
+
if (!node_fs_1.default.existsSync(projectPath)) {
|
|
484
|
+
return res.status(400).json({ error: 'path does not exist' });
|
|
485
|
+
}
|
|
486
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.join(projectPath, '.docuflow'))) {
|
|
487
|
+
return res.status(400).json({ error: '.docuflow not found — run init first' });
|
|
488
|
+
}
|
|
489
|
+
try {
|
|
490
|
+
// Check if already running
|
|
491
|
+
const existing = await (0, watch_1.readPidFile)(projectPath);
|
|
492
|
+
if (existing && (0, watch_1.isProcessAlive)(existing.pid)) {
|
|
493
|
+
return res.json({ ok: true, message: 'Watch daemon already running', pid: existing.pid });
|
|
494
|
+
}
|
|
495
|
+
// require.resolve fails in compiled ESM dist/ — fall back to the running entry point
|
|
496
|
+
let cliBin;
|
|
497
|
+
try {
|
|
498
|
+
cliBin = require.resolve('./index')
|
|
499
|
+
.replace(/\.ts$/, '.js')
|
|
500
|
+
.replace('/src/', '/dist/');
|
|
501
|
+
}
|
|
502
|
+
catch {
|
|
503
|
+
cliBin = process.argv[1];
|
|
504
|
+
}
|
|
505
|
+
const child = (0, node_child_process_1.spawn)(process.execPath, [cliBin, 'watch'], {
|
|
506
|
+
cwd: projectPath,
|
|
507
|
+
detached: true,
|
|
508
|
+
stdio: 'ignore',
|
|
509
|
+
});
|
|
510
|
+
child.unref();
|
|
511
|
+
return res.json({ ok: true, message: 'Watch daemon spawned' });
|
|
512
|
+
}
|
|
513
|
+
catch (e) {
|
|
514
|
+
return res.status(500).json({ error: e.message });
|
|
515
|
+
}
|
|
516
|
+
});
|
|
362
517
|
// ── Static UI (must come after all /api/* routes) ─────────────────────
|
|
363
518
|
app.use(express_1.default.static(uiDist));
|
|
364
519
|
// SPA fallback — any unmatched route returns index.html so React Router
|
package/dist/index.js
CHANGED
|
@@ -130,6 +130,15 @@ else if (cmd === 'update' || cmd === 'upgrade') {
|
|
|
130
130
|
check: hasFlag('--check'),
|
|
131
131
|
force: hasFlag('--force'),
|
|
132
132
|
}));
|
|
133
|
+
// ── recent — show recent DevLoop task activity ────────────────────────────────
|
|
134
|
+
}
|
|
135
|
+
else if (cmd === 'recent') {
|
|
136
|
+
const daysFlag = getFlagValue('--days');
|
|
137
|
+
const fmt = getFlagValue('--format');
|
|
138
|
+
Promise.resolve().then(() => __importStar(require('./commands/recent'))).then(m => m.run({
|
|
139
|
+
days: daysFlag ? (isNaN(parseInt(daysFlag, 10)) ? 7 : parseInt(daysFlag, 10)) : 7,
|
|
140
|
+
format: fmt ?? 'table',
|
|
141
|
+
}));
|
|
133
142
|
}
|
|
134
143
|
else {
|
|
135
144
|
console.log(`DocuFlow v${version}`);
|
|
@@ -175,6 +184,7 @@ else {
|
|
|
175
184
|
console.log(' update --check Check whether a newer version is published (no install)');
|
|
176
185
|
console.log(' update --force Reinstall even when already on the latest version');
|
|
177
186
|
console.log(' upgrade Alias for "update"');
|
|
187
|
+
console.log(' recent [--days N] [--format table|md] Show recent DevLoop task activity');
|
|
178
188
|
console.log('');
|
|
179
189
|
console.log('Options:');
|
|
180
190
|
console.log(' --version, -v Print version number');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doquflow/cli",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "CLI for setting up Docuflow in your project",
|
|
5
5
|
"author": "Docuflow <hello@doquflows.dev>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
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)')\""
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@doquflow/server": "1.5.
|
|
34
|
+
"@doquflow/server": "1.5.2",
|
|
35
35
|
"cors": "^2.8.5",
|
|
36
36
|
"express": "^4.19.2"
|
|
37
37
|
},
|