@biaoo/tiangong-wiki 0.2.0 → 0.2.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/README.md +39 -50
- package/README.zh-CN.md +39 -50
- package/SKILL.md +75 -107
- package/assets/templates/achievement.md +8 -8
- package/assets/templates/bridge.md +8 -8
- package/assets/templates/concept.md +14 -18
- package/assets/templates/faq.md +8 -10
- package/assets/templates/lesson.md +8 -8
- package/assets/templates/method.md +16 -8
- package/assets/templates/misconception.md +10 -10
- package/assets/templates/person.md +8 -8
- package/assets/templates/research-note.md +10 -10
- package/assets/templates/resume.md +11 -10
- package/assets/templates/source-summary.md +8 -12
- package/assets/tiangong-wiki-framework.png +0 -0
- package/assets/wiki.config.default.json +6 -3
- package/dist/commands/asset.js +21 -0
- package/dist/commands/skill.js +78 -0
- package/dist/commands/template.js +30 -0
- package/dist/core/cli-env.js +34 -5
- package/dist/core/global-config.js +61 -0
- package/dist/core/onboarding.js +252 -102
- package/dist/core/workflow-context.js +58 -21
- package/dist/core/workspace-skills.js +496 -60
- package/dist/daemon/server.js +8 -0
- package/dist/index.js +36 -1
- package/dist/operations/asset.js +81 -0
- package/dist/operations/query.js +25 -1
- package/dist/operations/template-lint.js +160 -0
- package/dist/utils/asset.js +75 -0
- package/dist/utils/errors.js +6 -0
- package/package.json +2 -1
- package/references/cli-interface.md +32 -1
- package/references/template-design-guide.md +125 -113
- package/references/{env.md → troubleshooting.md} +64 -33
- package/references/vault-to-wiki-instruction.md +109 -51
- package/references/wiki-maintenance-instruction.md +15 -15
package/dist/core/onboarding.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { accessSync, constants, readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { createInterface } from "node:readline/promises";
|
|
4
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { confirm, input, password, select } from "@inquirer/prompts";
|
|
5
5
|
import { DEFAULT_WIKI_ENV_FILE, getCliEnvironmentInfo, parseEnvFile, serializeEnvEntries } from "./cli-env.js";
|
|
6
6
|
import { resolveTemplateFilePath, loadConfig } from "./config.js";
|
|
7
7
|
import { EmbeddingClient } from "./embedding.js";
|
|
8
|
+
import { writeGlobalConfig } from "./global-config.js";
|
|
8
9
|
import { parseVaultHashMode, resolveAgentSettings } from "./paths.js";
|
|
9
10
|
import { loadSynologyConfigFromEnv, normalizeSynologyRemotePath, withSynologyClient } from "./synology.js";
|
|
10
11
|
import { ensureWikiSkillInstall, formatParserSkills, inspectSkillInstall, installParserSkill, OPTIONAL_PARSER_SKILLS, parseParserSkillSelection, parseParserSkills, resolveWorkspaceRootFromWikiPath, resolveWorkspaceSkillPath, resolveWorkspaceSkillPaths, } from "./workspace-skills.js";
|
|
@@ -106,16 +107,62 @@ function validateWikiPath(rawValue) {
|
|
|
106
107
|
}
|
|
107
108
|
return null;
|
|
108
109
|
}
|
|
109
|
-
class
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
110
|
+
class InquirerPromptDriver {
|
|
111
|
+
inputStream;
|
|
112
|
+
outputStream;
|
|
113
|
+
constructor(inputStream, outputStream) {
|
|
114
|
+
this.inputStream = inputStream;
|
|
115
|
+
this.outputStream = outputStream;
|
|
116
|
+
}
|
|
117
|
+
input(options) {
|
|
118
|
+
return input({
|
|
119
|
+
message: options.message,
|
|
120
|
+
default: options.defaultValue,
|
|
121
|
+
required: options.required !== false,
|
|
122
|
+
validate: (value) => options.validator?.(value) ?? true,
|
|
123
|
+
}, this.getContext());
|
|
124
|
+
}
|
|
125
|
+
password(options) {
|
|
126
|
+
const defaultValue = options.defaultValue ?? "";
|
|
127
|
+
const hasDefault = defaultValue.length > 0;
|
|
128
|
+
return password({
|
|
129
|
+
message: hasDefault ? `${options.message} (press enter to keep current value)` : options.message,
|
|
130
|
+
mask: "*",
|
|
131
|
+
validate: (value) => {
|
|
132
|
+
const candidate = value.length === 0 && hasDefault ? defaultValue : value;
|
|
133
|
+
if (options.required !== false && candidate.length === 0) {
|
|
134
|
+
return `${options.message} is required.`;
|
|
135
|
+
}
|
|
136
|
+
return options.validator?.(candidate) ?? true;
|
|
137
|
+
},
|
|
138
|
+
}, this.getContext()).then((value) => (value.length === 0 && hasDefault ? defaultValue : value));
|
|
139
|
+
}
|
|
140
|
+
confirm(options) {
|
|
141
|
+
return confirm({
|
|
142
|
+
message: options.message,
|
|
143
|
+
default: options.defaultValue,
|
|
144
|
+
}, this.getContext());
|
|
145
|
+
}
|
|
146
|
+
select(options) {
|
|
147
|
+
return select({
|
|
148
|
+
message: options.message,
|
|
149
|
+
default: options.defaultValue,
|
|
150
|
+
choices: options.choices.map((choice) => ({
|
|
151
|
+
value: choice.value,
|
|
152
|
+
name: choice.label,
|
|
153
|
+
description: choice.description,
|
|
154
|
+
})),
|
|
155
|
+
}, this.getContext());
|
|
156
|
+
}
|
|
157
|
+
getContext() {
|
|
158
|
+
return {
|
|
159
|
+
input: this.inputStream,
|
|
160
|
+
output: this.outputStream,
|
|
161
|
+
clearPromptOnDone: false,
|
|
162
|
+
};
|
|
116
163
|
}
|
|
117
164
|
close() {
|
|
118
|
-
|
|
165
|
+
// Inquirer manages prompt lifecycle; no explicit teardown needed here.
|
|
119
166
|
}
|
|
120
167
|
}
|
|
121
168
|
class BufferedPromptDriver {
|
|
@@ -126,13 +173,87 @@ class BufferedPromptDriver {
|
|
|
126
173
|
this.answers = answers;
|
|
127
174
|
this.output = output;
|
|
128
175
|
}
|
|
129
|
-
async
|
|
176
|
+
async input(options) {
|
|
177
|
+
const defaultValue = options.defaultValue ?? "";
|
|
178
|
+
const label = formatBufferedPromptLabel(options.message, defaultValue);
|
|
179
|
+
while (true) {
|
|
180
|
+
const answer = this.readAnswer(label);
|
|
181
|
+
const candidate = (answer.trim() || defaultValue).trim();
|
|
182
|
+
if (options.required !== false && candidate.length === 0) {
|
|
183
|
+
this.output.write(`${options.message} is required.\n`);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const error = options.validator?.(candidate);
|
|
187
|
+
if (error) {
|
|
188
|
+
this.output.write(`${error}\n`);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
return candidate;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async password(options) {
|
|
195
|
+
const defaultValue = options.defaultValue ?? "";
|
|
196
|
+
const label = formatBufferedPromptLabel(options.message, defaultValue, {
|
|
197
|
+
defaultDisplay: defaultValue.length > 0 ? "(saved)" : "",
|
|
198
|
+
});
|
|
199
|
+
while (true) {
|
|
200
|
+
const answer = this.readAnswer(label);
|
|
201
|
+
const candidate = answer.length === 0 ? defaultValue : answer;
|
|
202
|
+
if (options.required !== false && candidate.length === 0) {
|
|
203
|
+
this.output.write(`${options.message} is required.\n`);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
const error = options.validator?.(candidate);
|
|
207
|
+
if (error) {
|
|
208
|
+
this.output.write(`${error}\n`);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
return candidate;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async confirm(options) {
|
|
215
|
+
const suffix = options.defaultValue ? "Y/n" : "y/N";
|
|
216
|
+
while (true) {
|
|
217
|
+
const answer = this.readAnswer(`${options.message} [${suffix}]: `).trim().toLowerCase();
|
|
218
|
+
if (!answer) {
|
|
219
|
+
return options.defaultValue;
|
|
220
|
+
}
|
|
221
|
+
if (["y", "yes"].includes(answer)) {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
if (["n", "no"].includes(answer)) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
this.output.write("Please answer yes or no.\n");
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async select(options) {
|
|
231
|
+
const choiceList = options.choices.map((choice) => choice.value).join("/");
|
|
232
|
+
while (true) {
|
|
233
|
+
const answer = this.readAnswer(`${options.message} [${choiceList}] (${options.defaultValue}): `).trim().toLowerCase();
|
|
234
|
+
if (!answer) {
|
|
235
|
+
return options.defaultValue;
|
|
236
|
+
}
|
|
237
|
+
const match = options.choices.find((choice) => {
|
|
238
|
+
return choice.value.toLowerCase() === answer || choice.label.trim().toLowerCase() === answer;
|
|
239
|
+
});
|
|
240
|
+
if (match) {
|
|
241
|
+
return match.value;
|
|
242
|
+
}
|
|
243
|
+
this.output.write(`Please choose one of: ${choiceList}.\n`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
close() { }
|
|
247
|
+
readAnswer(prompt) {
|
|
130
248
|
const answer = this.answers[this.index] ?? "";
|
|
131
249
|
this.index += 1;
|
|
132
250
|
this.output.write(`${prompt}${answer}\n`);
|
|
133
251
|
return answer;
|
|
134
252
|
}
|
|
135
|
-
|
|
253
|
+
}
|
|
254
|
+
function formatBufferedPromptLabel(message, defaultValue, options = {}) {
|
|
255
|
+
const display = options.defaultDisplay ?? defaultValue;
|
|
256
|
+
return display ? `${message} [${display}]: ` : `${message}: `;
|
|
136
257
|
}
|
|
137
258
|
async function readBufferedAnswers(input) {
|
|
138
259
|
const chunks = [];
|
|
@@ -147,77 +268,72 @@ async function readBufferedAnswers(input) {
|
|
|
147
268
|
}
|
|
148
269
|
async function createPromptDriver(input, output) {
|
|
149
270
|
if ("isTTY" in input && input.isTTY) {
|
|
150
|
-
return new
|
|
151
|
-
input,
|
|
152
|
-
output,
|
|
153
|
-
}));
|
|
271
|
+
return new InquirerPromptDriver(input, output);
|
|
154
272
|
}
|
|
155
273
|
return new BufferedPromptDriver(await readBufferedAnswers(input), output);
|
|
156
274
|
}
|
|
157
|
-
async function promptText(driver,
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const error = options.validator?.(candidate);
|
|
166
|
-
if (error) {
|
|
167
|
-
ctx.output.write(`${error}\n`);
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
return options.normalize ? options.normalize(candidate) : candidate;
|
|
171
|
-
}
|
|
275
|
+
async function promptText(driver, label, defaultValue, options = {}) {
|
|
276
|
+
const candidate = await driver.input({
|
|
277
|
+
message: label,
|
|
278
|
+
defaultValue,
|
|
279
|
+
required: options.required,
|
|
280
|
+
validator: options.validator,
|
|
281
|
+
});
|
|
282
|
+
return options.normalize ? options.normalize(candidate) : candidate;
|
|
172
283
|
}
|
|
173
|
-
async function
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
284
|
+
async function promptPassword(driver, label, defaultValue, options = {}) {
|
|
285
|
+
const candidate = await driver.password({
|
|
286
|
+
message: label,
|
|
287
|
+
defaultValue,
|
|
288
|
+
required: options.required,
|
|
289
|
+
validator: options.validator,
|
|
290
|
+
});
|
|
291
|
+
return options.normalize ? options.normalize(candidate) : candidate;
|
|
292
|
+
}
|
|
293
|
+
async function promptYesNo(driver, label, defaultValue) {
|
|
294
|
+
return driver.confirm({
|
|
295
|
+
message: label,
|
|
296
|
+
defaultValue,
|
|
297
|
+
});
|
|
188
298
|
}
|
|
189
299
|
function formatStep(index, total, title) {
|
|
190
300
|
return `Step ${index}/${total}: ${title}`;
|
|
191
301
|
}
|
|
192
|
-
async function promptVaultSource(driver,
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
302
|
+
async function promptVaultSource(driver, defaultValue) {
|
|
303
|
+
return driver.select({
|
|
304
|
+
message: "VAULT_SOURCE",
|
|
305
|
+
defaultValue,
|
|
306
|
+
choices: [
|
|
307
|
+
{
|
|
308
|
+
value: "local",
|
|
309
|
+
label: "local",
|
|
310
|
+
description: "Read the vault directly from the local filesystem.",
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
value: "synology",
|
|
314
|
+
label: "synology",
|
|
315
|
+
description: "Download the vault from Synology NAS into a local cache.",
|
|
316
|
+
},
|
|
317
|
+
],
|
|
204
318
|
});
|
|
205
|
-
return value;
|
|
206
319
|
}
|
|
207
|
-
async function promptVaultHashMode(driver,
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
320
|
+
async function promptVaultHashMode(driver, defaultValue) {
|
|
321
|
+
return driver.select({
|
|
322
|
+
message: "VAULT_HASH_MODE",
|
|
323
|
+
defaultValue,
|
|
324
|
+
choices: [
|
|
325
|
+
{
|
|
326
|
+
value: "content",
|
|
327
|
+
label: "content",
|
|
328
|
+
description: "Hash file content to detect changes.",
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
value: "mtime",
|
|
332
|
+
label: "mtime",
|
|
333
|
+
description: "Use modification time, recommended for remote Synology sync.",
|
|
334
|
+
},
|
|
335
|
+
],
|
|
219
336
|
});
|
|
220
|
-
return value;
|
|
221
337
|
}
|
|
222
338
|
function canReadWrite(targetPath) {
|
|
223
339
|
accessSync(targetPath, constants.R_OK | constants.W_OK);
|
|
@@ -269,7 +385,7 @@ function getPathDefaults(env, cwd) {
|
|
|
269
385
|
};
|
|
270
386
|
}
|
|
271
387
|
async function collectEmbeddingSettings(driver, ctx, defaults, env) {
|
|
272
|
-
const enabled = await promptYesNo(driver,
|
|
388
|
+
const enabled = await promptYesNo(driver, "Enable semantic search with embeddings?", defaults.embeddingEnabled);
|
|
273
389
|
if (!enabled) {
|
|
274
390
|
return {
|
|
275
391
|
embeddingEnabled: false,
|
|
@@ -280,11 +396,15 @@ async function collectEmbeddingSettings(driver, ctx, defaults, env) {
|
|
|
280
396
|
};
|
|
281
397
|
}
|
|
282
398
|
while (true) {
|
|
283
|
-
const embeddingBaseUrl = await promptText(driver,
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const
|
|
287
|
-
|
|
399
|
+
const embeddingBaseUrl = await promptText(driver, "EMBEDDING_BASE_URL", defaults.embeddingBaseUrl ?? "https://api.openai.com/v1", {
|
|
400
|
+
validator: (value) => validateUrl(value, "EMBEDDING_BASE_URL"),
|
|
401
|
+
});
|
|
402
|
+
const embeddingApiKey = await promptPassword(driver, "EMBEDDING_API_KEY", defaults.embeddingApiKey ?? "", {
|
|
403
|
+
required: true,
|
|
404
|
+
});
|
|
405
|
+
const embeddingModel = await promptText(driver, "EMBEDDING_MODEL", defaults.embeddingModel ?? "text-embedding-3-small", { required: true });
|
|
406
|
+
const embeddingDimensions = await promptText(driver, "EMBEDDING_DIMENSIONS", defaults.embeddingDimensions ?? "384", { validator: (value) => validateNonNegativeInteger(value, "EMBEDDING_DIMENSIONS") });
|
|
407
|
+
const shouldProbe = await promptYesNo(driver, "Probe the embedding endpoint now?", false);
|
|
288
408
|
if (shouldProbe) {
|
|
289
409
|
try {
|
|
290
410
|
const probeEnv = {
|
|
@@ -304,7 +424,7 @@ async function collectEmbeddingSettings(driver, ctx, defaults, env) {
|
|
|
304
424
|
catch (error) {
|
|
305
425
|
const message = error instanceof Error ? error.message : String(error);
|
|
306
426
|
ctx.output.write(`Embedding probe failed: ${message}\n`);
|
|
307
|
-
if (await promptYesNo(driver,
|
|
427
|
+
if (await promptYesNo(driver, "Re-enter embedding settings?", true)) {
|
|
308
428
|
continue;
|
|
309
429
|
}
|
|
310
430
|
}
|
|
@@ -319,7 +439,7 @@ async function collectEmbeddingSettings(driver, ctx, defaults, env) {
|
|
|
319
439
|
}
|
|
320
440
|
}
|
|
321
441
|
async function collectAgentSettings(driver, ctx, defaults) {
|
|
322
|
-
const enabled = await promptYesNo(driver,
|
|
442
|
+
const enabled = await promptYesNo(driver, "Enable automatic vault-to-wiki processing?", defaults.agentEnabled);
|
|
323
443
|
if (!enabled) {
|
|
324
444
|
return {
|
|
325
445
|
agentEnabled: false,
|
|
@@ -331,17 +451,19 @@ async function collectAgentSettings(driver, ctx, defaults) {
|
|
|
331
451
|
}
|
|
332
452
|
return {
|
|
333
453
|
agentEnabled: true,
|
|
334
|
-
agentBaseUrl: await promptText(driver,
|
|
335
|
-
agentApiKey: await
|
|
336
|
-
agentModel: await promptText(driver,
|
|
337
|
-
agentBatchSize: await promptText(driver,
|
|
454
|
+
agentBaseUrl: await promptText(driver, "WIKI_AGENT_BASE_URL", defaults.agentBaseUrl ?? "https://api.openai.com/v1", { validator: (value) => validateUrl(value, "WIKI_AGENT_BASE_URL") }),
|
|
455
|
+
agentApiKey: await promptPassword(driver, "WIKI_AGENT_API_KEY", defaults.agentApiKey ?? "", { required: true }),
|
|
456
|
+
agentModel: await promptText(driver, "WIKI_AGENT_MODEL", defaults.agentModel ?? "", { required: true }),
|
|
457
|
+
agentBatchSize: await promptText(driver, "WIKI_AGENT_BATCH_SIZE", defaults.agentBatchSize ?? "5", { validator: (value) => validateNonNegativeInteger(value, "WIKI_AGENT_BATCH_SIZE") }),
|
|
338
458
|
};
|
|
339
459
|
}
|
|
340
460
|
async function collectSynologySettings(driver, ctx, defaults) {
|
|
341
|
-
const synologyBaseUrl = await promptText(driver,
|
|
342
|
-
const synologyUsername = await promptText(driver,
|
|
343
|
-
const synologyPassword = await
|
|
344
|
-
|
|
461
|
+
const synologyBaseUrl = await promptText(driver, "SYNOLOGY_BASE_URL", defaults.synologyBaseUrl ?? "https://nas.example.com:5001", { validator: (value) => validateUrl(value, "SYNOLOGY_BASE_URL") });
|
|
462
|
+
const synologyUsername = await promptText(driver, "SYNOLOGY_USERNAME", defaults.synologyUsername ?? "", { required: true });
|
|
463
|
+
const synologyPassword = await promptPassword(driver, "SYNOLOGY_PASSWORD", defaults.synologyPassword ?? "", {
|
|
464
|
+
required: true,
|
|
465
|
+
});
|
|
466
|
+
const synologyRemotePath = await promptText(driver, "VAULT_SYNOLOGY_REMOTE_PATH", defaults.synologyRemotePath ?? "/homes/user/wiki-vault", {
|
|
345
467
|
validator: (value) => {
|
|
346
468
|
try {
|
|
347
469
|
normalizeSynologyRemotePath(value);
|
|
@@ -353,9 +475,9 @@ async function collectSynologySettings(driver, ctx, defaults) {
|
|
|
353
475
|
},
|
|
354
476
|
normalize: (value) => normalizeSynologyRemotePath(value),
|
|
355
477
|
});
|
|
356
|
-
const vaultHashMode = await promptVaultHashMode(driver,
|
|
357
|
-
const synologyVerifySsl = await promptYesNo(driver,
|
|
358
|
-
const synologyReadonly = await promptYesNo(driver,
|
|
478
|
+
const vaultHashMode = await promptVaultHashMode(driver, "mtime");
|
|
479
|
+
const synologyVerifySsl = await promptYesNo(driver, "SYNOLOGY_VERIFY_SSL", defaults.synologyVerifySsl);
|
|
480
|
+
const synologyReadonly = await promptYesNo(driver, "SYNOLOGY_READONLY", defaults.synologyReadonly);
|
|
359
481
|
return {
|
|
360
482
|
vaultHashMode,
|
|
361
483
|
synologyBaseUrl,
|
|
@@ -371,7 +493,7 @@ async function collectParserSkillSettings(driver, ctx, defaults, wikiPath) {
|
|
|
371
493
|
ctx.output.write(`tiangong-wiki-skill is required and will be installed at ${path.join(skillsRoot, "tiangong-wiki-skill")}.\n`);
|
|
372
494
|
const selected = new Set(defaults.parserSkills);
|
|
373
495
|
for (const skill of OPTIONAL_PARSER_SKILLS) {
|
|
374
|
-
const enabled = await promptYesNo(driver,
|
|
496
|
+
const enabled = await promptYesNo(driver, `Install parser skill ${skill.name} (${skill.summary})?`, selected.has(skill.name));
|
|
375
497
|
if (enabled) {
|
|
376
498
|
selected.add(skill.name);
|
|
377
499
|
}
|
|
@@ -465,27 +587,27 @@ export async function runSetupWizard(env = process.env, options = {}) {
|
|
|
465
587
|
const ctx = { cwd, output };
|
|
466
588
|
try {
|
|
467
589
|
writeSection(output, "Step 1: Configuration file");
|
|
468
|
-
const envFilePath = await promptText(driver,
|
|
590
|
+
const envFilePath = await promptText(driver, "Path for the generated .wiki.env file", defaults.envFilePath, {
|
|
469
591
|
normalize: (value) => resolveInputPath(value, cwd),
|
|
470
592
|
});
|
|
471
593
|
writeSection(output, "Step 2: Vault source");
|
|
472
|
-
const vaultSource = await promptVaultSource(driver,
|
|
594
|
+
const vaultSource = await promptVaultSource(driver, defaults.vaultSource);
|
|
473
595
|
const totalSteps = vaultSource === "synology" ? 9 : 8;
|
|
474
596
|
writeSection(output, formatStep(3, totalSteps, "Core paths"));
|
|
475
|
-
const wikiPath = await promptText(driver,
|
|
597
|
+
const wikiPath = await promptText(driver, "WIKI_PATH", defaults.wikiPath, {
|
|
476
598
|
normalize: (value) => resolveInputPath(value, cwd),
|
|
477
599
|
validator: (value) => validateWikiPath(resolveInputPath(value, cwd)),
|
|
478
600
|
});
|
|
479
|
-
const vaultPath = await promptText(driver,
|
|
601
|
+
const vaultPath = await promptText(driver, vaultSource === "synology" ? "VAULT_PATH (local cache directory)" : "VAULT_PATH", defaults.vaultPath, {
|
|
480
602
|
normalize: (value) => resolveInputPath(value, cwd),
|
|
481
603
|
});
|
|
482
|
-
const dbPath = await promptText(driver,
|
|
604
|
+
const dbPath = await promptText(driver, "WIKI_DB_PATH", defaults.dbPath, {
|
|
483
605
|
normalize: (value) => resolveInputPath(value, cwd),
|
|
484
606
|
});
|
|
485
|
-
const configPath = await promptText(driver,
|
|
607
|
+
const configPath = await promptText(driver, "WIKI_CONFIG_PATH", defaults.configPath, {
|
|
486
608
|
normalize: (value) => resolveInputPath(value, cwd),
|
|
487
609
|
});
|
|
488
|
-
const templatesPath = await promptText(driver,
|
|
610
|
+
const templatesPath = await promptText(driver, "WIKI_TEMPLATES_PATH", defaults.templatesPath, {
|
|
489
611
|
normalize: (value) => resolveInputPath(value, cwd),
|
|
490
612
|
});
|
|
491
613
|
let synologyValues;
|
|
@@ -506,7 +628,7 @@ export async function runSetupWizard(env = process.env, options = {}) {
|
|
|
506
628
|
};
|
|
507
629
|
}
|
|
508
630
|
writeSection(output, formatStep(vaultSource === "synology" ? 5 : 4, totalSteps, "Sync schedule"));
|
|
509
|
-
const syncInterval = await promptText(driver,
|
|
631
|
+
const syncInterval = await promptText(driver, "WIKI_SYNC_INTERVAL (seconds)", defaults.syncInterval, {
|
|
510
632
|
validator: (value) => validateNonNegativeInteger(value, "WIKI_SYNC_INTERVAL"),
|
|
511
633
|
});
|
|
512
634
|
writeSection(output, formatStep(vaultSource === "synology" ? 6 : 5, totalSteps, "Embedding configuration"));
|
|
@@ -531,7 +653,7 @@ export async function runSetupWizard(env = process.env, options = {}) {
|
|
|
531
653
|
};
|
|
532
654
|
writeSection(output, formatStep(totalSteps, totalSteps, "Confirm"));
|
|
533
655
|
output.write(`${buildSetupSummary(values)}\n`);
|
|
534
|
-
const confirmed = await promptYesNo(driver,
|
|
656
|
+
const confirmed = await promptYesNo(driver, "Write configuration and scaffold workspace assets?", true);
|
|
535
657
|
if (!confirmed) {
|
|
536
658
|
throw new AppError("Setup aborted before writing any files.", "runtime");
|
|
537
659
|
}
|
|
@@ -551,9 +673,12 @@ export async function runSetupWizard(env = process.env, options = {}) {
|
|
|
551
673
|
output,
|
|
552
674
|
}));
|
|
553
675
|
writeSetupEnvFile(values);
|
|
676
|
+
const globalConfig = writeGlobalConfig(values.envFilePath, env);
|
|
554
677
|
output.write([
|
|
555
678
|
"\ntiangong-wiki setup complete",
|
|
556
679
|
`configuration file: ${values.envFilePath}`,
|
|
680
|
+
`default workspace config: ${globalConfig.configPath}`,
|
|
681
|
+
`workspace root: ${workspaceRoot}`,
|
|
557
682
|
`skills root: ${skillsRoot}`,
|
|
558
683
|
`tiangong-wiki-skill: ${wikiSkillInstall.status}`,
|
|
559
684
|
`parser skills: ${values.parserSkills.length > 0 ? values.parserSkills.join(", ") : "(none)"}`,
|
|
@@ -565,6 +690,11 @@ export async function runSetupWizard(env = process.env, options = {}) {
|
|
|
565
690
|
: []),
|
|
566
691
|
"",
|
|
567
692
|
"Next steps:",
|
|
693
|
+
`- Commands inside ${JSON.stringify(workspaceRoot)} will auto-discover the local .wiki.env first.`,
|
|
694
|
+
`- Commands outside the workspace will fall back to the default workspace config at ${globalConfig.configPath}.`,
|
|
695
|
+
`- Example: cd ${JSON.stringify(workspaceRoot)} && tiangong-wiki doctor`,
|
|
696
|
+
`- Example: tiangong-wiki --env-file ${JSON.stringify(values.envFilePath)} doctor`,
|
|
697
|
+
`- Example: cd ${JSON.stringify(workspaceRoot)} && tiangong-wiki init`,
|
|
568
698
|
"- Run `tiangong-wiki doctor` to validate the generated configuration.",
|
|
569
699
|
"- Run `tiangong-wiki init` to create index.db and perform the first sync.",
|
|
570
700
|
...(values.vaultSource === "synology"
|
|
@@ -576,6 +706,7 @@ export async function runSetupWizard(env = process.env, options = {}) {
|
|
|
576
706
|
].join("\n"));
|
|
577
707
|
return {
|
|
578
708
|
envFilePath: values.envFilePath,
|
|
709
|
+
globalConfigPath: globalConfig.configPath,
|
|
579
710
|
createdDirectories: bootstrap.createdDirectories,
|
|
580
711
|
copiedConfig: bootstrap.copiedConfig,
|
|
581
712
|
copiedTemplates: bootstrap.copiedTemplates,
|
|
@@ -892,11 +1023,26 @@ export async function buildDoctorReport(env = process.env, options = {}) {
|
|
|
892
1023
|
if (envFile.missingRequestedPath && envFile.requestedPath) {
|
|
893
1024
|
collectDoctorCheck(checks, "error", "env-file", `Requested env file does not exist: ${envFile.requestedPath}`, "Create the env file or rerun `tiangong-wiki setup`.");
|
|
894
1025
|
}
|
|
1026
|
+
else if (envFile.missingDefaultPath && envFile.defaultPath) {
|
|
1027
|
+
collectDoctorCheck(checks, "error", "env-file", `The default workspace config points to a missing env file: ${envFile.defaultPath}`, envFile.globalConfigPath
|
|
1028
|
+
? `Fix or remove ${envFile.globalConfigPath}, rerun \`tiangong-wiki setup\`, or pass \`--env-file\` explicitly.`
|
|
1029
|
+
: "Fix the default workspace config, rerun `tiangong-wiki setup`, or pass `--env-file` explicitly.");
|
|
1030
|
+
}
|
|
895
1031
|
else if (envFile.loadedPath) {
|
|
896
|
-
|
|
1032
|
+
const sourceLabel = envFile.source === "explicit-env-file"
|
|
1033
|
+
? "from --env-file or WIKI_ENV_FILE."
|
|
1034
|
+
: envFile.source === "nearest-env-file"
|
|
1035
|
+
? "from the current workspace (auto-discovered)."
|
|
1036
|
+
: envFile.source === "global-default-env-file"
|
|
1037
|
+
? "from the global default workspace config."
|
|
1038
|
+
: ".";
|
|
1039
|
+
collectDoctorCheck(checks, "ok", "env-file", `Loaded configuration from ${envFile.loadedPath} ${sourceLabel}`);
|
|
1040
|
+
}
|
|
1041
|
+
else if (envFile.source === "process-env") {
|
|
1042
|
+
collectDoctorCheck(checks, "ok", "env-file", "Using runtime paths provided directly via process.env; no .wiki.env file was loaded.");
|
|
897
1043
|
}
|
|
898
1044
|
else {
|
|
899
|
-
collectDoctorCheck(checks, "warn", "env-file", "No
|
|
1045
|
+
collectDoctorCheck(checks, "warn", "env-file", "No workspace configuration was found from --env-file, WIKI_ENV_FILE, the current directory, or the global default workspace config.", "Run `tiangong-wiki setup`, set `WIKI_ENV_FILE`, or pass `--env-file` to point at a workspace explicitly.");
|
|
900
1046
|
}
|
|
901
1047
|
const wikiPath = env.WIKI_PATH ? path.resolve(env.WIKI_PATH) : null;
|
|
902
1048
|
const wikiRoot = wikiPath ? path.resolve(wikiPath, "..") : null;
|
|
@@ -930,6 +1076,10 @@ export async function buildDoctorReport(env = process.env, options = {}) {
|
|
|
930
1076
|
loadedPath: envFile.loadedPath,
|
|
931
1077
|
autoDiscovered: envFile.autoDiscovered,
|
|
932
1078
|
missingRequestedPath: envFile.missingRequestedPath,
|
|
1079
|
+
missingDefaultPath: envFile.missingDefaultPath,
|
|
1080
|
+
source: envFile.source,
|
|
1081
|
+
globalConfigPath: envFile.globalConfigPath,
|
|
1082
|
+
defaultPath: envFile.defaultPath,
|
|
933
1083
|
},
|
|
934
1084
|
effectivePaths: {
|
|
935
1085
|
wikiPath,
|