@doquflow/cli 1.6.0 → 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 +43 -47
- package/dist/commands/query.js +141 -0
- package/dist/commands/recent.js +72 -205
- package/dist/index.js +147 -153
- package/package.json +2 -2
- 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,64 +101,55 @@ 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
|
|
104
|
+
return `<!-- BEGIN DOCUFLOW -->
|
|
105
|
+
# DocuFlow — AI Documentation Assistant
|
|
110
106
|
|
|
111
|
-
|
|
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}" })\`
|
|
107
|
+
DocuFlow preserves decision context for AI agents. Intent in, value out.
|
|
119
108
|
|
|
120
|
-
##
|
|
121
|
-
|
|
122
|
-
- **ingest_source** — Ingest a markdown file from \`.docuflow/sources/\` and generate wiki pages (entities, concepts).
|
|
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).
|
|
109
|
+
## Core tools (use these first)
|
|
129
110
|
|
|
130
|
-
|
|
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.
|
|
131
115
|
|
|
132
|
-
|
|
133
|
-
- **get_schema_guidance** — Analyse what wiki pages should exist based on the schema and current state.
|
|
134
|
-
- **preview_generation** — Preview what a tool will do before running it.
|
|
116
|
+
## CLI — Core Commands
|
|
135
117
|
|
|
136
|
-
## Common Workflows
|
|
137
|
-
|
|
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
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
|
162
153
|
|
|
163
154
|
## Storage Layout
|
|
164
155
|
|
|
@@ -175,20 +166,25 @@ docuflow rewiki # apply (backs up wiki first)
|
|
|
175
166
|
├── index.md Auto-maintained catalog
|
|
176
167
|
└── log.md Operation log
|
|
177
168
|
\`\`\`
|
|
178
|
-
|
|
169
|
+
<!-- END DOCUFLOW -->`;
|
|
179
170
|
}
|
|
180
171
|
async function writeClaudeMd(projectDir) {
|
|
181
172
|
const claudeMdPath = node_path_1.default.join(projectDir, "CLAUDE.md");
|
|
182
173
|
const newSection = buildClaudeMd(projectDir);
|
|
183
174
|
if (node_fs_1.default.existsSync(claudeMdPath)) {
|
|
184
175
|
const existing = await promises_1.default.readFile(claudeMdPath, "utf8");
|
|
185
|
-
if (existing.includes("
|
|
186
|
-
//
|
|
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
|
|
187
183
|
const withoutDocuflow = existing.replace(/\n?# DocuFlow[\s\S]*/, "").trimEnd();
|
|
188
184
|
await promises_1.default.writeFile(claudeMdPath, withoutDocuflow + "\n\n" + newSection, "utf8");
|
|
189
185
|
}
|
|
190
186
|
else {
|
|
191
|
-
//
|
|
187
|
+
// No DocuFlow section yet — append
|
|
192
188
|
await promises_1.default.appendFile(claudeMdPath, "\n\n" + newSection, "utf8");
|
|
193
189
|
}
|
|
194
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
|
+
}
|