@doquflow/cli 1.5.2 → 1.7.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.
- package/dist/commands/help.js +37 -0
- package/dist/commands/ingest.js +115 -0
- package/dist/commands/init.js +44 -42
- package/dist/commands/query.js +141 -0
- package/dist/commands/recent.js +72 -205
- package/dist/commands/rewiki.js +339 -0
- package/dist/index.js +147 -141
- package/package.json +2 -2
- package/ui-dist/assets/index-B20T-7YT.js +44 -0
- package/ui-dist/assets/index-Cnq2PhDd.js +44 -0
- package/ui-dist/index.html +1 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Help text for `docuflow --help` and `docuflow advanced --help`.
|
|
3
|
+
// No external help library — plain console.log for zero-dependency output.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.printCoreHelp = printCoreHelp;
|
|
6
|
+
exports.printAdvancedHelp = printAdvancedHelp;
|
|
7
|
+
function printCoreHelp() {
|
|
8
|
+
console.log('DocuFlow — preserve decision context for AI agents.');
|
|
9
|
+
console.log('');
|
|
10
|
+
console.log('CORE');
|
|
11
|
+
console.log(' docuflow init Initialise .docuflow/ in this project');
|
|
12
|
+
console.log(' docuflow ingest <file> Add a source document to the wiki');
|
|
13
|
+
console.log(' docuflow query "<question>" Ask the wiki — answer with citations');
|
|
14
|
+
console.log(' docuflow status Show wiki health and counts');
|
|
15
|
+
console.log(' docuflow rewiki Migrate / re-ingest with current rules');
|
|
16
|
+
console.log('');
|
|
17
|
+
console.log('ADVANCED');
|
|
18
|
+
console.log(' docuflow advanced --help See watch / sync / ui / review / recent / suggest / update');
|
|
19
|
+
console.log('');
|
|
20
|
+
console.log(' docuflow --version Print version');
|
|
21
|
+
console.log(' docuflow --help Show this help');
|
|
22
|
+
}
|
|
23
|
+
function printAdvancedHelp() {
|
|
24
|
+
console.log('DocuFlow — advanced surface (sync daemons, UI, audit commands).');
|
|
25
|
+
console.log('');
|
|
26
|
+
console.log(' docuflow advanced watch [stop|status|restart] Auto-sync daemon');
|
|
27
|
+
console.log(' docuflow advanced sync [--ai] One-shot sync for CI / git hooks');
|
|
28
|
+
console.log(' docuflow advanced ui [--port N] Launch web UI dashboard');
|
|
29
|
+
console.log(' docuflow advanced start Alias for ui');
|
|
30
|
+
console.log(' docuflow advanced review Review uncommitted changes');
|
|
31
|
+
console.log(' docuflow advanced recent Recent work dashboard');
|
|
32
|
+
console.log(' docuflow advanced suggest First-steps guidance');
|
|
33
|
+
console.log(' docuflow advanced update Self-upgrade DocuFlow');
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log('Note: every "advanced" command also works at its old top-level path');
|
|
36
|
+
console.log('(e.g. `docuflow watch` is still valid). The "advanced" prefix is optional.');
|
|
37
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.run = run;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const c = {
|
|
10
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
11
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
12
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
13
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
14
|
+
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
15
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
16
|
+
};
|
|
17
|
+
function loadServerTool(toolFile) {
|
|
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)),
|
|
22
|
+
];
|
|
23
|
+
for (const attempt of candidates) {
|
|
24
|
+
try {
|
|
25
|
+
return attempt();
|
|
26
|
+
}
|
|
27
|
+
catch { }
|
|
28
|
+
}
|
|
29
|
+
throw new Error(`Cannot load server tool "${toolFile}". Run "npm run build" first.`);
|
|
30
|
+
}
|
|
31
|
+
async function run(options) {
|
|
32
|
+
const { sourceFile, all = false, dryRun = false, quiet = false } = options;
|
|
33
|
+
const projectPath = node_path_1.default.resolve(process.cwd());
|
|
34
|
+
const docuDir = node_path_1.default.join(projectPath, ".docuflow");
|
|
35
|
+
const sourcesDir = node_path_1.default.join(docuDir, "sources");
|
|
36
|
+
function info(msg) { if (!quiet)
|
|
37
|
+
process.stdout.write(msg + "\n"); }
|
|
38
|
+
if (!all && !sourceFile) {
|
|
39
|
+
console.error(c.red(" ✗ Provide a source filename or pass --all."));
|
|
40
|
+
console.error(` Usage: docuflow ingest <source.md>`);
|
|
41
|
+
console.error(` docuflow ingest --all`);
|
|
42
|
+
process.exit(2);
|
|
43
|
+
}
|
|
44
|
+
if (!node_fs_1.default.existsSync(docuDir)) {
|
|
45
|
+
console.error(c.red(` ✗ .docuflow/ not found at ${projectPath}`));
|
|
46
|
+
console.error(` Run "docuflow init" first.`);
|
|
47
|
+
process.exit(2);
|
|
48
|
+
}
|
|
49
|
+
if (dryRun) {
|
|
50
|
+
info(c.yellow(" dry-run not supported for ingest_source; use `docuflow rewiki --dry-run` for a full simulation"));
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
let ingestSource;
|
|
54
|
+
try {
|
|
55
|
+
({ ingestSource } = loadServerTool("ingest-source"));
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
console.error(c.red(` ✗ ${e.message}`));
|
|
59
|
+
process.exit(2);
|
|
60
|
+
}
|
|
61
|
+
// Collect files to ingest
|
|
62
|
+
let files;
|
|
63
|
+
if (all) {
|
|
64
|
+
if (!node_fs_1.default.existsSync(sourcesDir)) {
|
|
65
|
+
console.error(c.yellow(" No .docuflow/sources/ directory found — nothing to ingest."));
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
files = node_fs_1.default.readdirSync(sourcesDir).filter(f => f.endsWith(".md"));
|
|
69
|
+
if (files.length === 0) {
|
|
70
|
+
info(c.yellow(" No source files found in .docuflow/sources/"));
|
|
71
|
+
process.exit(0);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
// Single file — verify it exists
|
|
76
|
+
const filename = sourceFile;
|
|
77
|
+
const fullPath = node_path_1.default.join(sourcesDir, filename);
|
|
78
|
+
if (!node_fs_1.default.existsSync(fullPath)) {
|
|
79
|
+
console.error(c.red(` ✗ Source file not found: .docuflow/sources/${filename}`));
|
|
80
|
+
process.exit(3);
|
|
81
|
+
}
|
|
82
|
+
files = [filename];
|
|
83
|
+
}
|
|
84
|
+
info(c.bold(`DocuFlow ingest`));
|
|
85
|
+
info(` Project : ${projectPath}`);
|
|
86
|
+
info(` Files : ${files.length}`);
|
|
87
|
+
info("");
|
|
88
|
+
let totalCreated = 0;
|
|
89
|
+
let totalUpdated = 0;
|
|
90
|
+
let errored = 0;
|
|
91
|
+
for (const filename of files) {
|
|
92
|
+
info(c.dim(` Ingesting ${filename}…`));
|
|
93
|
+
try {
|
|
94
|
+
const result = await ingestSource({ project_path: projectPath, source_filename: filename });
|
|
95
|
+
totalCreated += result.pages_created ?? 0;
|
|
96
|
+
totalUpdated += result.pages_updated ?? 0;
|
|
97
|
+
info(` ${c.green("✓")} ${filename}` +
|
|
98
|
+
` ${c.dim(`+${result.pages_created ?? 0} created, ~${result.pages_updated ?? 0} updated`)}`);
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
errored++;
|
|
102
|
+
console.error(c.red(` ✗ ${filename}: ${e.message}`));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
info("");
|
|
106
|
+
if (errored === 0) {
|
|
107
|
+
info(c.green(" ✓ Done.") +
|
|
108
|
+
` Pages created: ${totalCreated} updated: ${totalUpdated}`);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
info(c.yellow(` Done with ${errored} error(s).`) +
|
|
112
|
+
` Pages created: ${totalCreated} updated: ${totalUpdated}`);
|
|
113
|
+
process.exit(2);
|
|
114
|
+
}
|
|
115
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -101,59 +101,56 @@ async function copyTemplateFile(templateName, destPath) {
|
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
function buildClaudeMd(projectDir) {
|
|
104
|
-
return
|
|
105
|
-
|
|
106
|
-
DocuFlow is an MCP server that gives you structured access to this codebase and maintains a living wiki.
|
|
107
|
-
It is registered in your Claude Desktop config and available as MCP tools in every session.
|
|
108
|
-
|
|
109
|
-
## Codebase Scanner Tools
|
|
110
|
-
|
|
111
|
-
- **read_module** — Analyse a single source file. Returns language, classes, functions, dependencies, DB tables, endpoints, config refs, and raw content (first 8 KB).
|
|
112
|
-
- Example: \`read_module({ path: "src/UserService.cs" })\`
|
|
113
|
-
- **list_modules** — Walk a directory and extract facts for every non-binary file. Use this to understand the full project in one call.
|
|
114
|
-
- Example: \`list_modules({ path: "${projectDir}" })\`
|
|
115
|
-
- **write_spec** — Persist a markdown spec to \`.docuflow/specs/<filename>.md\` and update the index.
|
|
116
|
-
- Example: \`write_spec({ project_path: "${projectDir}", filename: "UserService", content: "# UserService\\n..." })\`
|
|
117
|
-
- **read_specs** — Read previously written specs, optionally filtered by name.
|
|
118
|
-
- Example: \`read_specs({ project_path: "${projectDir}" })\`
|
|
104
|
+
return `<!-- BEGIN DOCUFLOW -->
|
|
105
|
+
# DocuFlow — AI Documentation Assistant
|
|
119
106
|
|
|
120
|
-
|
|
107
|
+
DocuFlow preserves decision context for AI agents. Intent in, value out.
|
|
121
108
|
|
|
122
|
-
|
|
123
|
-
- **update_index** — Rebuild \`.docuflow/index.md\` from all wiki pages.
|
|
124
|
-
- **list_wiki** — List all wiki pages, optionally filtered by category (entity/concept/timeline/synthesis).
|
|
125
|
-
- **wiki_search** — BM25 search across all wiki pages. Returns ranked results with previews.
|
|
126
|
-
- **query_wiki** — One-stop Q&A: searches wiki, synthesises an answer, returns source citations.
|
|
127
|
-
- **synthesize_answer** — Generate a markdown synthesis from a list of specific wiki page IDs.
|
|
128
|
-
- **save_answer_as_page** — Persist a synthesised answer back into the wiki (knowledge compounding).
|
|
129
|
-
|
|
130
|
-
## Health & Guidance Tools
|
|
109
|
+
## Core tools (use these first)
|
|
131
110
|
|
|
132
|
-
- **
|
|
133
|
-
- **
|
|
134
|
-
- **
|
|
111
|
+
- **query_wiki({ project_path, question })** — Ask the wiki. Returns an answer with citations.
|
|
112
|
+
- **ingest_source({ project_path, source_filename })** — Fold a markdown source into the wiki.
|
|
113
|
+
- **wiki_search({ project_path, query })** — BM25 search across all pages.
|
|
114
|
+
- **read_module({ path })** — Read and extract facts from a single source file.
|
|
135
115
|
|
|
136
|
-
##
|
|
116
|
+
## CLI — Core Commands
|
|
137
117
|
|
|
138
|
-
### First time — understand the codebase
|
|
139
118
|
\`\`\`
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
119
|
+
docuflow query "<question>" # ask the wiki from the shell
|
|
120
|
+
docuflow ingest <source.md> # add a source doc to the wiki
|
|
121
|
+
docuflow status # wiki health and counts
|
|
122
|
+
docuflow rewiki # re-ingest with current rules
|
|
123
|
+
docuflow init # initialise .docuflow/ in this project
|
|
143
124
|
\`\`\`
|
|
144
125
|
|
|
145
|
-
|
|
126
|
+
## Workflows
|
|
127
|
+
|
|
128
|
+
### Answer a question
|
|
146
129
|
\`\`\`
|
|
147
130
|
query_wiki({ project_path: "${projectDir}", question: "How does authentication work?" })
|
|
148
|
-
→ save_answer_as_page if the answer is worth keeping
|
|
149
131
|
\`\`\`
|
|
150
132
|
|
|
151
|
-
###
|
|
133
|
+
### Add new context
|
|
152
134
|
\`\`\`
|
|
153
|
-
|
|
154
|
-
|
|
135
|
+
# drop a markdown file in .docuflow/sources/
|
|
136
|
+
ingest_source({ project_path: "${projectDir}", source_filename: "auth-design.md" })
|
|
155
137
|
\`\`\`
|
|
156
138
|
|
|
139
|
+
## Advanced tools
|
|
140
|
+
|
|
141
|
+
Use when the core tools don't cover the workflow. Each has more parameters and side effects.
|
|
142
|
+
|
|
143
|
+
- **list_modules** — Walk a directory tree and extract facts in bulk
|
|
144
|
+
- **list_wiki** — Inventory pages by category, with staleness flags
|
|
145
|
+
- **write_spec / read_specs** — Persistent agent-written specs
|
|
146
|
+
- **save_answer_as_page** — Promote a synthesised answer into the wiki
|
|
147
|
+
- **synthesize_answer** — Combine multiple pages into a markdown synthesis
|
|
148
|
+
- **update_index** — Rebuild \`.docuflow/index.md\`
|
|
149
|
+
- **lint_wiki** — Health checks: orphans, broken refs, stale content
|
|
150
|
+
- **get_schema_guidance** — Recommend what pages should exist
|
|
151
|
+
- **preview_generation** — Show what a tool will do before running
|
|
152
|
+
- **generate_dependency_graph** — Build the import/shared-table graph
|
|
153
|
+
|
|
157
154
|
## Storage Layout
|
|
158
155
|
|
|
159
156
|
\`\`\`
|
|
@@ -169,20 +166,25 @@ lint_wiki({ project_path: "${projectDir}" })
|
|
|
169
166
|
├── index.md Auto-maintained catalog
|
|
170
167
|
└── log.md Operation log
|
|
171
168
|
\`\`\`
|
|
172
|
-
|
|
169
|
+
<!-- END DOCUFLOW -->`;
|
|
173
170
|
}
|
|
174
171
|
async function writeClaudeMd(projectDir) {
|
|
175
172
|
const claudeMdPath = node_path_1.default.join(projectDir, "CLAUDE.md");
|
|
176
173
|
const newSection = buildClaudeMd(projectDir);
|
|
177
174
|
if (node_fs_1.default.existsSync(claudeMdPath)) {
|
|
178
175
|
const existing = await promises_1.default.readFile(claudeMdPath, "utf8");
|
|
179
|
-
if (existing.includes("
|
|
180
|
-
//
|
|
176
|
+
if (existing.includes("<!-- BEGIN DOCUFLOW -->") && existing.includes("<!-- END DOCUFLOW -->")) {
|
|
177
|
+
// Marker-based replacement — idempotent re-runs preserve surrounding content
|
|
178
|
+
const replaced = existing.replace(/<!-- BEGIN DOCUFLOW -->[\s\S]*?<!-- END DOCUFLOW -->/, newSection.trimEnd());
|
|
179
|
+
await promises_1.default.writeFile(claudeMdPath, replaced, "utf8");
|
|
180
|
+
}
|
|
181
|
+
else if (existing.includes("DocuFlow")) {
|
|
182
|
+
// Old format without markers — replace old DocuFlow section, add markers this time
|
|
181
183
|
const withoutDocuflow = existing.replace(/\n?# DocuFlow[\s\S]*/, "").trimEnd();
|
|
182
184
|
await promises_1.default.writeFile(claudeMdPath, withoutDocuflow + "\n\n" + newSection, "utf8");
|
|
183
185
|
}
|
|
184
186
|
else {
|
|
185
|
-
//
|
|
187
|
+
// No DocuFlow section yet — append
|
|
186
188
|
await promises_1.default.appendFile(claudeMdPath, "\n\n" + newSection, "utf8");
|
|
187
189
|
}
|
|
188
190
|
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* docuflow query
|
|
4
|
+
*
|
|
5
|
+
* Calls query_wiki and renders the answer with citations.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* docuflow query "<question>"
|
|
9
|
+
* docuflow query "<question>" --max-sources 5
|
|
10
|
+
* docuflow query "<question>" --json
|
|
11
|
+
* docuflow query "<question>" --no-cite
|
|
12
|
+
* docuflow query "<question>" --save-as "<title>"
|
|
13
|
+
* docuflow query "<question>" --quiet
|
|
14
|
+
*
|
|
15
|
+
* Exit codes:
|
|
16
|
+
* 0 — answer produced
|
|
17
|
+
* 2 — fatal (.docuflow missing, server tool not found)
|
|
18
|
+
* 3 — query produced no matching sources
|
|
19
|
+
*/
|
|
20
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
21
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.run = run;
|
|
25
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
26
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
27
|
+
// ─── Colour helpers ────────────────────────────────────────────────────────────
|
|
28
|
+
const c = {
|
|
29
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
30
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
31
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
32
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
33
|
+
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
34
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
35
|
+
};
|
|
36
|
+
// ─── Dynamic server tool loader (mirrors rewiki.ts pattern) ───────────────────
|
|
37
|
+
function loadServerTool(toolFile) {
|
|
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)),
|
|
42
|
+
];
|
|
43
|
+
for (const attempt of candidates) {
|
|
44
|
+
try {
|
|
45
|
+
return attempt();
|
|
46
|
+
}
|
|
47
|
+
catch { }
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`Cannot load server tool "${toolFile}". Run "npm run build" first.`);
|
|
50
|
+
}
|
|
51
|
+
async function run(options) {
|
|
52
|
+
const { question, maxSources = 5, json = false, noCite = false, saveAs, quiet = false } = options;
|
|
53
|
+
const projectPath = node_path_1.default.resolve(process.cwd());
|
|
54
|
+
const docuDir = node_path_1.default.join(projectPath, ".docuflow");
|
|
55
|
+
function info(msg) { if (!quiet)
|
|
56
|
+
console.log(msg); }
|
|
57
|
+
// ── Validate question ───────────────────────────────────────────────────────
|
|
58
|
+
if (!question || question.trim() === "") {
|
|
59
|
+
console.error(c.red(" ✗ Question is required."));
|
|
60
|
+
console.error(` Usage: docuflow query "<question>"`);
|
|
61
|
+
process.exit(2);
|
|
62
|
+
}
|
|
63
|
+
// ── Pre-flight ──────────────────────────────────────────────────────────────
|
|
64
|
+
if (!node_fs_1.default.existsSync(docuDir)) {
|
|
65
|
+
console.error(c.red(` ✗ .docuflow/ not found at ${projectPath}`));
|
|
66
|
+
console.error(` Run "docuflow init" first.`);
|
|
67
|
+
process.exit(2);
|
|
68
|
+
}
|
|
69
|
+
// ── Load tools ──────────────────────────────────────────────────────────────
|
|
70
|
+
let queryWiki;
|
|
71
|
+
let saveAnswerAsPage;
|
|
72
|
+
try {
|
|
73
|
+
({ queryWiki } = loadServerTool("query-wiki"));
|
|
74
|
+
if (saveAs) {
|
|
75
|
+
({ saveAnswerAsPage } = loadServerTool("save-answer-as-page"));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
console.error(c.red(` ✗ ${e.message}`));
|
|
80
|
+
process.exit(2);
|
|
81
|
+
}
|
|
82
|
+
// ── Execute query ───────────────────────────────────────────────────────────
|
|
83
|
+
info(c.dim(` Searching wiki for: "${question}"`));
|
|
84
|
+
let result;
|
|
85
|
+
try {
|
|
86
|
+
result = await queryWiki({ project_path: projectPath, question, max_sources: maxSources });
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
console.error(c.red(` ✗ Query failed: ${e.message}`));
|
|
90
|
+
process.exit(2);
|
|
91
|
+
}
|
|
92
|
+
if (result.error) {
|
|
93
|
+
console.error(c.red(` ✗ ${result.error}`));
|
|
94
|
+
process.exit(2);
|
|
95
|
+
}
|
|
96
|
+
// ── No sources found ────────────────────────────────────────────────────────
|
|
97
|
+
if (!result.source_pages || result.source_pages.length === 0) {
|
|
98
|
+
if (!json) {
|
|
99
|
+
console.error(c.yellow(" No matching sources found in the wiki."));
|
|
100
|
+
console.error(` Try running "docuflow rewiki" to rebuild the wiki first.`);
|
|
101
|
+
}
|
|
102
|
+
process.exit(3);
|
|
103
|
+
}
|
|
104
|
+
// ── Output ──────────────────────────────────────────────────────────────────
|
|
105
|
+
if (json) {
|
|
106
|
+
console.log(JSON.stringify({
|
|
107
|
+
question: result.question,
|
|
108
|
+
answer: result.answer,
|
|
109
|
+
source_pages: result.source_pages,
|
|
110
|
+
search_results: result.search_results,
|
|
111
|
+
confidence: result.confidence,
|
|
112
|
+
}, null, 2));
|
|
113
|
+
}
|
|
114
|
+
else if (noCite) {
|
|
115
|
+
console.log(result.answer);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
console.log(result.answer);
|
|
119
|
+
console.log("");
|
|
120
|
+
console.log(c.bold("## Sources"));
|
|
121
|
+
for (const page of result.source_pages) {
|
|
122
|
+
console.log(` - ${page.title} ${c.dim("(" + page.page_id + ")")}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// ── Save ────────────────────────────────────────────────────────────────────
|
|
126
|
+
if (saveAs && saveAnswerAsPage) {
|
|
127
|
+
try {
|
|
128
|
+
const saved = await saveAnswerAsPage({
|
|
129
|
+
project_path: projectPath,
|
|
130
|
+
question,
|
|
131
|
+
answer: result.answer,
|
|
132
|
+
page_title: saveAs,
|
|
133
|
+
source_page_ids: (result.source_pages ?? []).map((p) => p.page_id),
|
|
134
|
+
});
|
|
135
|
+
process.stderr.write(`page-id: ${saved?.page_id ?? saveAs}\n`);
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
process.stderr.write(c.red(`Warning: could not save page: ${e.message}\n`));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|