@longtable/cli 0.1.29 → 0.1.31
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 +7 -0
- package/dist/cli.js +615 -491
- package/dist/project-session.d.ts +20 -0
- package/dist/project-session.js +55 -7
- package/dist/prompt-renderer.d.ts +11 -0
- package/dist/prompt-renderer.js +130 -0
- package/dist/search/index.d.ts +1 -0
- package/dist/search/index.js +1 -0
- package/dist/search/publisher-access.d.ts +23 -0
- package/dist/search/publisher-access.js +577 -0
- package/dist/search/rank.js +30 -2
- package/dist/search/run.js +9 -1
- package/dist/search/sources.js +29 -2
- package/dist/search/types.d.ts +64 -0
- package/dist/search/types.js +6 -0
- package/package.json +8 -7
package/dist/cli.js
CHANGED
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
3
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import { execSync } from "node:child_process";
|
|
5
|
-
import { emitKeypressEvents } from "node:readline";
|
|
6
5
|
import { createInterface } from "node:readline/promises";
|
|
7
6
|
import { stdin as input, stdout as output, cwd, env, exit } from "node:process";
|
|
8
7
|
import { dirname, join, resolve } from "node:path";
|
|
9
8
|
import { homedir } from "node:os";
|
|
10
9
|
import { classifyCheckpointTrigger } from "@longtable/checkpoints";
|
|
11
|
-
import { assessSearchSourceCapabilities, buildResearchSearchIntent, runResearchSearch } from "./search/index.js";
|
|
10
|
+
import { assessSearchSourceCapabilities, buildResearchSearchIntent, buildSearchCapabilitySnapshot, parsePublisherTarget, probePublisherAccess, publisherConfigs, runResearchSearch, searchCapabilitySnapshotPath, summarizeConfiguredPublisherAccess } from "./search/index.js";
|
|
12
11
|
import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput, installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultRuntimeConfigPath, resolveDefaultSetupPath, saveSetupOutput, saveSetupAndRuntimeConfig, serializeSetupOutput, writeRuntimeConfig } from "@longtable/setup";
|
|
13
12
|
import { buildCodexSkillSpecs, buildCodexThinWrappedPrompt, installCodexSkills, listInstalledCodexSkills, renderQuestionRecordPrompt, removeCodexSkills, resolveCodexSkillsDir, runCodexThinWrapper } from "@longtable/provider-codex";
|
|
14
13
|
import { buildClaudeSkillSpecs, installClaudeSkills, listInstalledClaudeSkills, renderQuestionRecordInput, removeClaudeSkills, resolveClaudeSkillsDir } from "@longtable/provider-claude";
|
|
@@ -18,6 +17,7 @@ import { PERSONA_DEFINITIONS, listRoleDefinitions } from "./personas.js";
|
|
|
18
17
|
import { buildPanelFallback, renderPanelSummary } from "./panel.js";
|
|
19
18
|
import { appendInvocationRecordToWorkspace, assertWorkspaceNotBlocked, answerWorkspaceQuestion, createWorkspaceFollowUpQuestions, createWorkspaceQuestion, createOrUpdateProjectWorkspace, inspectProjectWorkspace, loadWorkspaceState, loadProjectContextFromDirectory, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
|
|
20
19
|
import { buildTeamDebate, buildTeamReview, renderTeamDebateSummary } from "./debate.js";
|
|
20
|
+
import { createPromptRenderer } from "./prompt-renderer.js";
|
|
21
21
|
const VALID_MODES = new Set([
|
|
22
22
|
"explore",
|
|
23
23
|
"review",
|
|
@@ -43,7 +43,7 @@ const ANSI = {
|
|
|
43
43
|
green: "\u001B[32m"
|
|
44
44
|
};
|
|
45
45
|
const LONGTABLE_MCP_SERVER_NAME = "longtable-state";
|
|
46
|
-
const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.
|
|
46
|
+
const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.31";
|
|
47
47
|
const LONGTABLE_MCP_MARKER_START = "# LongTable state MCP START";
|
|
48
48
|
const LONGTABLE_MCP_MARKER_END = "# LongTable state MCP END";
|
|
49
49
|
function style(text, prefix) {
|
|
@@ -89,7 +89,10 @@ function usage() {
|
|
|
89
89
|
" longtable show [--json] [--path <file>]",
|
|
90
90
|
" longtable install [--json] [--path <file>] [--runtime-path <file>]",
|
|
91
91
|
" longtable mcp install [--provider codex|claude|all] [--write] [--checkpoint-ui off|interactive|strong] [--json] [--codex-config <path>] [--claude-settings <path>] [--package <spec>]",
|
|
92
|
-
" longtable search --query <text> [--intent literature|theory|measurement|citation|metadata|venue] [--field <text>] [--source all|crossref,arxiv,openalex,semantic_scholar,pubmed,eric,doaj,unpaywall] [--must <term[,term]>] [--exclude <term[,term]>] [--limit <n>] [--allow-partial] [--record] [--cwd <path>] [--json]",
|
|
92
|
+
" longtable search --query <text> [--intent literature|theory|measurement|citation|metadata|venue] [--field <text>] [--source all|crossref,arxiv,openalex,semantic_scholar,pubmed,eric,doaj,unpaywall] [--must <term[,term]>] [--exclude <term[,term]>] [--limit <n>] [--allow-partial] [--publisher-access] [--record] [--cwd <path>] [--json]",
|
|
93
|
+
" longtable search setup [--doi <doi>] [--json]",
|
|
94
|
+
" longtable search doctor [--doi <doi>] [--publisher auto|elsevier|springer_nature|wiley|taylor_francis|all] [--json]",
|
|
95
|
+
" longtable search probe --doi <doi> [--publisher auto|elsevier|springer_nature|wiley|taylor_francis] [--json]",
|
|
93
96
|
" longtable sentinel --prompt <text> [--cwd <path>] [--json] [--record]",
|
|
94
97
|
" longtable team --prompt <text> [--role <role[,role]>] [--debate] [--rounds 3|5] [--cwd <path>] [--json]",
|
|
95
98
|
" longtable ask [--prompt <text>] [--print] [--json] [--setup <path>] [--cwd <path>]",
|
|
@@ -136,6 +139,10 @@ function parseArgs(argv) {
|
|
|
136
139
|
else if (command === "codex" || command === "claude" || command === "mcp") {
|
|
137
140
|
startIndex = 2;
|
|
138
141
|
}
|
|
142
|
+
else if (command === "search" && maybeSubcommand && !maybeSubcommand.startsWith("--")) {
|
|
143
|
+
subcommand = maybeSubcommand;
|
|
144
|
+
startIndex = 2;
|
|
145
|
+
}
|
|
139
146
|
else if (directCommand) {
|
|
140
147
|
subcommand = undefined;
|
|
141
148
|
startIndex = 1;
|
|
@@ -164,11 +171,6 @@ function parseArgs(argv) {
|
|
|
164
171
|
}
|
|
165
172
|
return { command, subcommand, values };
|
|
166
173
|
}
|
|
167
|
-
function renderChoices(choices) {
|
|
168
|
-
return choices
|
|
169
|
-
.map((choice, index) => `${index + 1}. ${choice.label} — ${choice.description}`)
|
|
170
|
-
.join("\n");
|
|
171
|
-
}
|
|
172
174
|
function buildSetupFlowChoices() {
|
|
173
175
|
return [
|
|
174
176
|
{
|
|
@@ -212,23 +214,6 @@ function questionSection(questionId) {
|
|
|
212
214
|
function formatModeLabel(mode) {
|
|
213
215
|
return `${mode[0].toUpperCase()}${mode.slice(1)}`;
|
|
214
216
|
}
|
|
215
|
-
function moveCursorUp(lines) {
|
|
216
|
-
return lines > 0 ? `\u001B[${lines}A` : "";
|
|
217
|
-
}
|
|
218
|
-
function clearLine() {
|
|
219
|
-
return "\u001B[2K\r";
|
|
220
|
-
}
|
|
221
|
-
function renderArrowMenu(prompt, choices, selectedIndex) {
|
|
222
|
-
const lines = [style(prompt, ANSI.bold), style("Use ↑/↓ and Enter.", ANSI.dim)];
|
|
223
|
-
for (let index = 0; index < choices.length; index += 1) {
|
|
224
|
-
const prefix = index === selectedIndex ? style(">", `${ANSI.bold}${ANSI.green}`) : " ";
|
|
225
|
-
lines.push(`${prefix} ${choices[index].label} - ${choices[index].description}`);
|
|
226
|
-
}
|
|
227
|
-
return lines.join("\n");
|
|
228
|
-
}
|
|
229
|
-
function countRenderedLines(text) {
|
|
230
|
-
return text.split("\n").length;
|
|
231
|
-
}
|
|
232
217
|
function stripWrappingQuotes(value) {
|
|
233
218
|
const trimmed = value.trim();
|
|
234
219
|
if ((trimmed.startsWith("\"") && trimmed.endsWith("\"")) ||
|
|
@@ -295,186 +280,14 @@ async function verifyWritableWorkspaceParent(projectPath) {
|
|
|
295
280
|
].join("\n"));
|
|
296
281
|
}
|
|
297
282
|
}
|
|
298
|
-
async function
|
|
299
|
-
|
|
300
|
-
const answer = await rl.question(`${prompt}\n${renderChoices(choices)}\nSelect one number: `);
|
|
301
|
-
const numeric = Number(answer.trim());
|
|
302
|
-
if (!Number.isInteger(numeric) || numeric < 1 || numeric > choices.length) {
|
|
303
|
-
console.log("Invalid selection. Enter one of the listed numbers.");
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
const choice = choices[numeric - 1];
|
|
307
|
-
if (choice.fallbackToText) {
|
|
308
|
-
const freeText = await rl.question("Type your custom value: ");
|
|
309
|
-
if (!freeText.trim()) {
|
|
310
|
-
console.log("Custom value cannot be empty.");
|
|
311
|
-
continue;
|
|
312
|
-
}
|
|
313
|
-
return freeText.trim();
|
|
314
|
-
}
|
|
315
|
-
return choice.id;
|
|
316
|
-
}
|
|
283
|
+
async function promptText(prompt, required) {
|
|
284
|
+
return createPromptRenderer().text(prompt, { required });
|
|
317
285
|
}
|
|
318
|
-
async function
|
|
319
|
-
|
|
320
|
-
const answer = (await rl.question(`${prompt}\n> `)).trim();
|
|
321
|
-
if (!required) {
|
|
322
|
-
return answer || undefined;
|
|
323
|
-
}
|
|
324
|
-
if (answer) {
|
|
325
|
-
return answer;
|
|
326
|
-
}
|
|
327
|
-
console.log("This answer cannot be empty.");
|
|
328
|
-
}
|
|
286
|
+
async function promptChoice(prompt, choices) {
|
|
287
|
+
return createPromptRenderer().select(prompt, choices);
|
|
329
288
|
}
|
|
330
|
-
async function
|
|
331
|
-
|
|
332
|
-
if (!stream.isTTY || !output.isTTY) {
|
|
333
|
-
return promptChoiceByNumber(rl, prompt, choices);
|
|
334
|
-
}
|
|
335
|
-
const previousRawMode = stream.isRaw;
|
|
336
|
-
let selectedIndex = 0;
|
|
337
|
-
let lastRenderLineCount = 0;
|
|
338
|
-
return await new Promise((resolve, reject) => {
|
|
339
|
-
function draw(first = false) {
|
|
340
|
-
const renderedText = renderArrowMenu(prompt, choices, selectedIndex);
|
|
341
|
-
const rendered = renderedText.split("\n");
|
|
342
|
-
if (!first && lastRenderLineCount > 0) {
|
|
343
|
-
output.write(moveCursorUp(lastRenderLineCount));
|
|
344
|
-
}
|
|
345
|
-
for (const line of rendered) {
|
|
346
|
-
output.write(clearLine());
|
|
347
|
-
output.write(`${line}\n`);
|
|
348
|
-
}
|
|
349
|
-
lastRenderLineCount = countRenderedLines(renderedText);
|
|
350
|
-
}
|
|
351
|
-
function cleanup() {
|
|
352
|
-
stream.off("keypress", onKeypress);
|
|
353
|
-
if (stream.isTTY) {
|
|
354
|
-
stream.setRawMode(previousRawMode ?? false);
|
|
355
|
-
}
|
|
356
|
-
output.write("\u001B[?25h");
|
|
357
|
-
}
|
|
358
|
-
async function handleChoice(choice) {
|
|
359
|
-
cleanup();
|
|
360
|
-
if (choice.fallbackToText) {
|
|
361
|
-
const freeText = await rl.question("Type your custom value: ");
|
|
362
|
-
if (!freeText.trim()) {
|
|
363
|
-
resolve(await promptChoiceWithArrows(rl, prompt, choices));
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
resolve(freeText.trim());
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
resolve(choice.id);
|
|
370
|
-
}
|
|
371
|
-
function onKeypress(_, key) {
|
|
372
|
-
if (key.ctrl && key.name === "c") {
|
|
373
|
-
cleanup();
|
|
374
|
-
reject(new Error("Setup cancelled."));
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
if (key.name === "up") {
|
|
378
|
-
selectedIndex = selectedIndex === 0 ? choices.length - 1 : selectedIndex - 1;
|
|
379
|
-
draw();
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
if (key.name === "down") {
|
|
383
|
-
selectedIndex = selectedIndex === choices.length - 1 ? 0 : selectedIndex + 1;
|
|
384
|
-
draw();
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
if (key.name === "return") {
|
|
388
|
-
void handleChoice(choices[selectedIndex]);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
emitKeypressEvents(stream);
|
|
392
|
-
stream.setRawMode(true);
|
|
393
|
-
output.write("\u001B[?25l");
|
|
394
|
-
draw(true);
|
|
395
|
-
stream.on("keypress", onKeypress);
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
async function promptChoice(rl, prompt, choices) {
|
|
399
|
-
return promptChoiceWithArrows(rl, prompt, choices);
|
|
400
|
-
}
|
|
401
|
-
async function promptMultiChoice(rl, prompt, choices) {
|
|
402
|
-
const stream = input;
|
|
403
|
-
if (!stream.isTTY || !output.isTTY) {
|
|
404
|
-
const answer = await rl.question(`${prompt}\nType comma-separated ids or leave blank for auto.\n> `);
|
|
405
|
-
return answer
|
|
406
|
-
.split(",")
|
|
407
|
-
.map((part) => part.trim())
|
|
408
|
-
.filter(Boolean);
|
|
409
|
-
}
|
|
410
|
-
const previousRawMode = stream.isRaw;
|
|
411
|
-
let selectedIndex = 0;
|
|
412
|
-
const selected = new Set();
|
|
413
|
-
let lastRenderLineCount = 0;
|
|
414
|
-
return await new Promise((resolvePromise, reject) => {
|
|
415
|
-
function draw(first = false) {
|
|
416
|
-
const lines = [prompt, "Use ↑/↓, Space to toggle, and Enter to confirm."];
|
|
417
|
-
for (let index = 0; index < choices.length; index += 1) {
|
|
418
|
-
const choice = choices[index];
|
|
419
|
-
const pointer = index === selectedIndex ? ">" : " ";
|
|
420
|
-
const marker = selected.has(choice.id) ? "[x]" : "[ ]";
|
|
421
|
-
lines.push(`${pointer} ${marker} ${choice.label} - ${choice.description}`);
|
|
422
|
-
}
|
|
423
|
-
const renderedText = lines.join("\n");
|
|
424
|
-
if (!first && lastRenderLineCount > 0) {
|
|
425
|
-
output.write(moveCursorUp(lastRenderLineCount));
|
|
426
|
-
}
|
|
427
|
-
for (const line of lines) {
|
|
428
|
-
output.write(clearLine());
|
|
429
|
-
output.write(`${line}\n`);
|
|
430
|
-
}
|
|
431
|
-
lastRenderLineCount = countRenderedLines(renderedText);
|
|
432
|
-
}
|
|
433
|
-
function cleanup() {
|
|
434
|
-
stream.off("keypress", onKeypress);
|
|
435
|
-
if (stream.isTTY) {
|
|
436
|
-
stream.setRawMode(previousRawMode ?? false);
|
|
437
|
-
}
|
|
438
|
-
output.write("\u001B[?25h");
|
|
439
|
-
}
|
|
440
|
-
function onKeypress(_, key) {
|
|
441
|
-
if (key.ctrl && key.name === "c") {
|
|
442
|
-
cleanup();
|
|
443
|
-
reject(new Error("Setup cancelled."));
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
if (key.name === "up") {
|
|
447
|
-
selectedIndex = selectedIndex === 0 ? choices.length - 1 : selectedIndex - 1;
|
|
448
|
-
draw();
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
if (key.name === "down") {
|
|
452
|
-
selectedIndex = selectedIndex === choices.length - 1 ? 0 : selectedIndex + 1;
|
|
453
|
-
draw();
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
if (key.name === "space") {
|
|
457
|
-
const id = choices[selectedIndex].id;
|
|
458
|
-
if (selected.has(id)) {
|
|
459
|
-
selected.delete(id);
|
|
460
|
-
}
|
|
461
|
-
else {
|
|
462
|
-
selected.add(id);
|
|
463
|
-
}
|
|
464
|
-
draw();
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
if (key.name === "return") {
|
|
468
|
-
cleanup();
|
|
469
|
-
resolvePromise([...selected]);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
emitKeypressEvents(stream);
|
|
473
|
-
stream.setRawMode(true);
|
|
474
|
-
output.write("\u001B[?25l");
|
|
475
|
-
draw(true);
|
|
476
|
-
stream.on("keypress", onKeypress);
|
|
477
|
-
});
|
|
289
|
+
async function promptMultiChoice(prompt, choices) {
|
|
290
|
+
return createPromptRenderer().multiselect(prompt, choices);
|
|
478
291
|
}
|
|
479
292
|
function hasCompleteFlagInput(args) {
|
|
480
293
|
const required = ["provider", "career-stage", "experience", "checkpoint"];
|
|
@@ -509,58 +322,52 @@ function toSetupAnswers(args) {
|
|
|
509
322
|
};
|
|
510
323
|
}
|
|
511
324
|
async function collectInteractiveAnswers(initialFlow) {
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if (!value) {
|
|
536
|
-
continue;
|
|
537
|
-
}
|
|
538
|
-
if (question.id === "careerStage")
|
|
539
|
-
answers.careerStage = value;
|
|
540
|
-
if (question.id === "experienceLevel")
|
|
541
|
-
answers.experienceLevel = value;
|
|
542
|
-
if (question.id === "preferredCheckpointIntensity") {
|
|
543
|
-
answers.preferredCheckpointIntensity = value;
|
|
544
|
-
}
|
|
545
|
-
if (question.id === "humanAuthorshipSignal" && value !== "other") {
|
|
546
|
-
answers.humanAuthorshipSignal = value;
|
|
547
|
-
}
|
|
548
|
-
if (question.id === "preferredEntryMode")
|
|
549
|
-
answers.preferredEntryMode = value;
|
|
550
|
-
if (question.id === "weakestDomain")
|
|
551
|
-
answers.weakestDomain = value;
|
|
552
|
-
if (question.id === "panelPreference")
|
|
553
|
-
answers.panelPreference = value;
|
|
325
|
+
const flow = initialFlow ??
|
|
326
|
+
(await promptChoice("How would you like to set up LongTable?", buildSetupFlowChoices()));
|
|
327
|
+
console.log("");
|
|
328
|
+
console.log(renderSetupHeader(flow));
|
|
329
|
+
console.log("");
|
|
330
|
+
const provider = await promptChoice("Which provider do you want to configure?", buildProviderChoices());
|
|
331
|
+
const answers = {
|
|
332
|
+
field: "unspecified",
|
|
333
|
+
currentProjectType: "unspecified research task"
|
|
334
|
+
};
|
|
335
|
+
const questions = buildQuickSetupFlow(flow);
|
|
336
|
+
for (let index = 0; index < questions.length; index += 1) {
|
|
337
|
+
const question = questions[index];
|
|
338
|
+
const prompt = renderQuestionHeader(index + 1, questions.length, questionSection(question.id), question.prompt);
|
|
339
|
+
let value;
|
|
340
|
+
if (question.kind === "text") {
|
|
341
|
+
value = await promptText(prompt, question.required);
|
|
342
|
+
}
|
|
343
|
+
else if (question.choices) {
|
|
344
|
+
value = await promptChoice(prompt, question.choices);
|
|
345
|
+
}
|
|
346
|
+
if (!value) {
|
|
347
|
+
continue;
|
|
554
348
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
answers
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
349
|
+
if (question.id === "careerStage")
|
|
350
|
+
answers.careerStage = value;
|
|
351
|
+
if (question.id === "experienceLevel")
|
|
352
|
+
answers.experienceLevel = value;
|
|
353
|
+
if (question.id === "preferredCheckpointIntensity") {
|
|
354
|
+
answers.preferredCheckpointIntensity = value;
|
|
355
|
+
}
|
|
356
|
+
if (question.id === "humanAuthorshipSignal" && value !== "other") {
|
|
357
|
+
answers.humanAuthorshipSignal = value;
|
|
358
|
+
}
|
|
359
|
+
if (question.id === "preferredEntryMode")
|
|
360
|
+
answers.preferredEntryMode = value;
|
|
361
|
+
if (question.id === "weakestDomain")
|
|
362
|
+
answers.weakestDomain = value;
|
|
363
|
+
if (question.id === "panelPreference")
|
|
364
|
+
answers.panelPreference = value;
|
|
563
365
|
}
|
|
366
|
+
return {
|
|
367
|
+
flow,
|
|
368
|
+
provider,
|
|
369
|
+
answers: answers
|
|
370
|
+
};
|
|
564
371
|
}
|
|
565
372
|
function buildPermissionSetupChoices() {
|
|
566
373
|
return {
|
|
@@ -728,129 +535,123 @@ function checkpointUiIntervention(intervention, checkpointUi) {
|
|
|
728
535
|
}
|
|
729
536
|
async function runSetup(args) {
|
|
730
537
|
const json = args.json === true;
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
provider,
|
|
788
|
-
setupTarget: await saveSetupOutput(outputValue, setupPath)
|
|
789
|
-
}
|
|
790
|
-
: await saveSetupAndRuntimeConfig(outputValue, {
|
|
791
|
-
setupPath,
|
|
792
|
-
runtimePath
|
|
793
|
-
});
|
|
794
|
-
const scopedInstallDir = setupInstallDir(provider, installScope, typeof args["skills-dir"] === "string" ? args["skills-dir"] : typeof args.dir === "string" ? args.dir : undefined, projectRoot);
|
|
795
|
-
const installedSkills = !shouldInstallSkills(installScope, surfaces)
|
|
796
|
-
? []
|
|
797
|
-
: provider === "codex"
|
|
798
|
-
? await installCodexSkills(listRoleDefinitions(), scopedInstallDir)
|
|
799
|
-
: await installClaudeSkills(listRoleDefinitions(), scopedInstallDir);
|
|
800
|
-
let mcpInstall;
|
|
801
|
-
if (shouldInstallMcp(installScope, surfaces)) {
|
|
802
|
-
mcpInstall = await installMcpForSetup(provider, {
|
|
803
|
-
...mcpArgsForScope(provider, installScope, args, projectRoot),
|
|
804
|
-
...(provider === "codex" && checkpointUiRequiresMcp(checkpointUi)
|
|
805
|
-
? { "checkpoint-ui": checkpointUi }
|
|
806
|
-
: {})
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
|
-
if (json) {
|
|
810
|
-
console.log(JSON.stringify({
|
|
811
|
-
setup: outputValue,
|
|
812
|
-
runtime: result,
|
|
813
|
-
installedSkills: installedSkills.map((skill) => skill.name),
|
|
814
|
-
mcpInstall,
|
|
815
|
-
workspacePreference
|
|
816
|
-
}, null, 2));
|
|
817
|
-
return;
|
|
538
|
+
const provider = (typeof args.provider === "string"
|
|
539
|
+
? (args.provider === "claude" ? "claude" : "codex")
|
|
540
|
+
: await promptChoice("Which provider should LongTable configure?", buildProviderChoices()));
|
|
541
|
+
const choices = buildPermissionSetupChoices();
|
|
542
|
+
const installScope = parseSetupInstallScope(args["install-scope"]) ?? await promptChoice("Where may LongTable install runtime support?", choices.installScope);
|
|
543
|
+
const surfaces = parseSetupSurface(args.surfaces) ?? await promptChoice([
|
|
544
|
+
"Which LongTable runtime surfaces should be enabled?",
|
|
545
|
+
"This is a permission choice because skills, MCP, and sentinel support write provider-facing runtime files."
|
|
546
|
+
].join("\n"), choices.surfaces);
|
|
547
|
+
const intervention = parseSetupIntervention(args.intervention) ?? await promptChoice("How strongly may LongTable interrupt research decisions?", choices.intervention);
|
|
548
|
+
const parsedCheckpointUi = parseSetupCheckpointUi(args["checkpoint-ui"]);
|
|
549
|
+
const checkpointUiEligible = provider === "codex" && installScope !== "none" && shouldInstallMcp(installScope, surfaces);
|
|
550
|
+
if (parsedCheckpointUi && checkpointUiRequiresMcp(parsedCheckpointUi) && !checkpointUiEligible) {
|
|
551
|
+
throw new Error("`--checkpoint-ui interactive|strong` requires Codex with an MCP runtime surface.");
|
|
552
|
+
}
|
|
553
|
+
const checkpointUi = parsedCheckpointUi ?? (checkpointUiEligible
|
|
554
|
+
? await promptChoice([
|
|
555
|
+
"Should Codex use UI Researcher Checkpoints when MCP elicitation is available?",
|
|
556
|
+
"This writes Codex MCP elicitation approval only when you choose an interactive mode."
|
|
557
|
+
].join("\n"), choices.checkpointUi)
|
|
558
|
+
: "off");
|
|
559
|
+
const workspacePreference = parseSetupWorkspace(args.workspace) ?? await promptChoice("Should LongTable create a project workspace now?", choices.workspace);
|
|
560
|
+
const projectRoot = setupProjectRoot(args);
|
|
561
|
+
const effectiveIntervention = checkpointUiIntervention(intervention, checkpointUi);
|
|
562
|
+
const outputValue = createPersistedSetupOutput({
|
|
563
|
+
field: "unspecified",
|
|
564
|
+
careerStage: "unspecified",
|
|
565
|
+
experienceLevel: "advanced",
|
|
566
|
+
preferredCheckpointIntensity: checkpointIntensityFromIntervention(effectiveIntervention),
|
|
567
|
+
checkpointUiMode: checkpointUi,
|
|
568
|
+
preferredEntryMode: "explore",
|
|
569
|
+
panelPreference: "show_on_conflict"
|
|
570
|
+
}, provider, "quickstart");
|
|
571
|
+
outputValue.initialState.explicitState = {
|
|
572
|
+
...outputValue.initialState.explicitState,
|
|
573
|
+
installScope,
|
|
574
|
+
runtimeSurfaces: surfaces,
|
|
575
|
+
interventionPosture: effectiveIntervention,
|
|
576
|
+
checkpointUiMode: checkpointUi,
|
|
577
|
+
workspaceCreationPreference: workspacePreference,
|
|
578
|
+
teamMode: "panel"
|
|
579
|
+
};
|
|
580
|
+
if (surfaces === "skills_mcp_sentinel") {
|
|
581
|
+
outputValue.initialState.inferredHypotheses.push({
|
|
582
|
+
hypothesis: "Researcher approved advisory Gap/Tacit Sentinel setup.",
|
|
583
|
+
confidence: 0.95,
|
|
584
|
+
evidence: ["Selected Skills + MCP + Sentinel during permission-first setup."],
|
|
585
|
+
status: "confirmed"
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
const setupPath = setupPathForScope(installScope, args, projectRoot);
|
|
589
|
+
const runtimePath = runtimePathForScope(provider, installScope, args, projectRoot);
|
|
590
|
+
const result = installScope === "none"
|
|
591
|
+
? {
|
|
592
|
+
provider,
|
|
593
|
+
setupTarget: await saveSetupOutput(outputValue, setupPath)
|
|
818
594
|
}
|
|
595
|
+
: await saveSetupAndRuntimeConfig(outputValue, {
|
|
596
|
+
setupPath,
|
|
597
|
+
runtimePath
|
|
598
|
+
});
|
|
599
|
+
const scopedInstallDir = setupInstallDir(provider, installScope, typeof args["skills-dir"] === "string" ? args["skills-dir"] : typeof args.dir === "string" ? args.dir : undefined, projectRoot);
|
|
600
|
+
const installedSkills = !shouldInstallSkills(installScope, surfaces)
|
|
601
|
+
? []
|
|
602
|
+
: provider === "codex"
|
|
603
|
+
? await installCodexSkills(listRoleDefinitions(), scopedInstallDir)
|
|
604
|
+
: await installClaudeSkills(listRoleDefinitions(), scopedInstallDir);
|
|
605
|
+
let mcpInstall;
|
|
606
|
+
if (shouldInstallMcp(installScope, surfaces)) {
|
|
607
|
+
mcpInstall = await installMcpForSetup(provider, {
|
|
608
|
+
...mcpArgsForScope(provider, installScope, args, projectRoot),
|
|
609
|
+
...(provider === "codex" && checkpointUiRequiresMcp(checkpointUi)
|
|
610
|
+
? { "checkpoint-ui": checkpointUi }
|
|
611
|
+
: {})
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
if (json) {
|
|
615
|
+
console.log(JSON.stringify({
|
|
616
|
+
setup: outputValue,
|
|
617
|
+
runtime: result,
|
|
618
|
+
installedSkills: installedSkills.map((skill) => skill.name),
|
|
619
|
+
mcpInstall,
|
|
620
|
+
workspacePreference
|
|
621
|
+
}, null, 2));
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
console.log("");
|
|
625
|
+
console.log(renderSetupSummary(outputValue));
|
|
626
|
+
console.log("");
|
|
627
|
+
if ("runtimeTarget" in result) {
|
|
628
|
+
console.log(renderInstallSummary(result));
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
console.log("LongTable setup summary");
|
|
632
|
+
console.log(`setup path: ${result.setupTarget.path}`);
|
|
633
|
+
console.log("provider files: not installed by researcher choice");
|
|
634
|
+
}
|
|
635
|
+
console.log(`Installed skills: ${installedSkills.length}`);
|
|
636
|
+
if (mcpInstall) {
|
|
819
637
|
console.log("");
|
|
820
|
-
console.log(
|
|
821
|
-
|
|
822
|
-
if ("runtimeTarget" in result) {
|
|
823
|
-
console.log(renderInstallSummary(result));
|
|
824
|
-
}
|
|
825
|
-
else {
|
|
826
|
-
console.log("LongTable setup summary");
|
|
827
|
-
console.log(`setup path: ${result.setupTarget.path}`);
|
|
828
|
-
console.log("provider files: not installed by researcher choice");
|
|
829
|
-
}
|
|
830
|
-
console.log(`Installed skills: ${installedSkills.length}`);
|
|
831
|
-
if (mcpInstall) {
|
|
832
|
-
console.log("");
|
|
833
|
-
console.log(renderMcpInstallSummary(mcpInstall));
|
|
834
|
-
if (provider === "codex" && checkpointUiRequiresMcp(checkpointUi)) {
|
|
835
|
-
console.log("");
|
|
836
|
-
console.log("Restart Codex after this config change, then run `longtable doctor` in the project workspace.");
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
if (surfaces === "skills_mcp_sentinel") {
|
|
638
|
+
console.log(renderMcpInstallSummary(mcpInstall));
|
|
639
|
+
if (provider === "codex" && checkpointUiRequiresMcp(checkpointUi)) {
|
|
840
640
|
console.log("");
|
|
841
|
-
console.log("
|
|
842
|
-
console.log("Hook installation remains opt-in; LongTable will not install hooks without an explicit hook command.");
|
|
843
|
-
}
|
|
844
|
-
if (workspacePreference === "create") {
|
|
845
|
-
console.log("");
|
|
846
|
-
console.log("Project workspace requested. LongTable will now run `longtable start` with research-object and gap-risk prompts.");
|
|
847
|
-
await runStart({
|
|
848
|
-
setup: result.setupTarget.path
|
|
849
|
-
});
|
|
641
|
+
console.log("Restart Codex after this config change, then run `longtable doctor` in the project workspace.");
|
|
850
642
|
}
|
|
851
643
|
}
|
|
852
|
-
|
|
853
|
-
|
|
644
|
+
if (surfaces === "skills_mcp_sentinel") {
|
|
645
|
+
console.log("");
|
|
646
|
+
console.log("Background sentinel approval recorded.");
|
|
647
|
+
console.log("Hook installation remains opt-in; LongTable will not install hooks without an explicit hook command.");
|
|
648
|
+
}
|
|
649
|
+
if (workspacePreference === "create") {
|
|
650
|
+
console.log("");
|
|
651
|
+
console.log("Project workspace requested. LongTable will now run `longtable start` with an adaptive start interview.");
|
|
652
|
+
await runStart({
|
|
653
|
+
setup: result.setupTarget.path
|
|
654
|
+
});
|
|
854
655
|
}
|
|
855
656
|
}
|
|
856
657
|
function perspectiveChoices() {
|
|
@@ -887,6 +688,157 @@ function protectedDecisionChoices() {
|
|
|
887
688
|
{ id: "submission_public_sharing", label: "Submission/public sharing", description: "Do not let public-facing commitments settle quietly." }
|
|
888
689
|
];
|
|
889
690
|
}
|
|
691
|
+
function compactAnswer(value, maxLength = 180) {
|
|
692
|
+
const firstLine = value
|
|
693
|
+
.split(/\r?\n/)
|
|
694
|
+
.map((line) => line.trim())
|
|
695
|
+
.find(Boolean) ?? "";
|
|
696
|
+
return firstLine.length > maxLength ? `${firstLine.slice(0, maxLength - 1)}…` : firstLine;
|
|
697
|
+
}
|
|
698
|
+
function answerIncludes(answer, patterns) {
|
|
699
|
+
return patterns.some((pattern) => pattern.test(answer));
|
|
700
|
+
}
|
|
701
|
+
function classifyStartInterviewSignal(answer) {
|
|
702
|
+
const normalized = answer.toLowerCase();
|
|
703
|
+
if (answerIncludes(normalized, [/\bvoice\b/, /\bauthor(ship)?\b/, /\bwriting\b/, /저자|목소리|문체|글쓰기/])) {
|
|
704
|
+
return "voice";
|
|
705
|
+
}
|
|
706
|
+
if (answerIncludes(normalized, [/\breader\b/, /\breviewer\b/, /\bvenue\b/, /\bjournal\b/, /\baudience\b/, /독자|심사자|저널|학회|대상/])) {
|
|
707
|
+
return "audience";
|
|
708
|
+
}
|
|
709
|
+
if (answerIncludes(normalized, [/\bpaper\b/, /\bproposal\b/, /\bmanuscript\b/, /\bdraft\b/, /\bdeliverable\b/, /논문|제안서|원고|초안|산출물/])) {
|
|
710
|
+
return "artifact";
|
|
711
|
+
}
|
|
712
|
+
if (answerIncludes(normalized, [/\bevidence\b/, /\bdata\b/, /\bsource\b/, /\bcitation\b/, /\bliterature\b/, /\bmeasure\b/, /\bscale\b/, /\binstrument\b/, /근거|자료|데이터|문헌|인용|측정|척도|도구/])) {
|
|
713
|
+
return "evidence";
|
|
714
|
+
}
|
|
715
|
+
if (answerIncludes(normalized, [/\bassum/, /\btacit\b/, /\bimplicit\b/, /\bpremise\b/, /전제|암묵|가정|숨겨진/])) {
|
|
716
|
+
return "assumption";
|
|
717
|
+
}
|
|
718
|
+
if (answerIncludes(normalized, [/\bdecid/, /\bchoose\b/, /\bcommit\b/, /\bsettle\b/, /\block\b/, /결정|선택|확정|고정/])) {
|
|
719
|
+
return "decision_risk";
|
|
720
|
+
}
|
|
721
|
+
return "phenomenon";
|
|
722
|
+
}
|
|
723
|
+
function inferResearchObjectFromAnswers(answers) {
|
|
724
|
+
const joined = answers.join("\n").toLowerCase();
|
|
725
|
+
if (answerIncludes(joined, [/\bmeasure\b/, /\bscale\b/, /\binstrument\b/, /\bvariable\b/, /\bconstruct\b/, /측정|척도|변수|구성개념|도구/])) {
|
|
726
|
+
return "measurement_instrument";
|
|
727
|
+
}
|
|
728
|
+
if (answerIncludes(joined, [/\bmethod\b/, /\bdesign\b/, /\bsample\b/, /\binterview\b/, /\bexperiment\b/, /\bparticipant\b/, /방법|설계|표본|인터뷰|실험|참여자/])) {
|
|
729
|
+
return "study_design";
|
|
730
|
+
}
|
|
731
|
+
if (answerIncludes(joined, [/\btheory\b/, /\bframework\b/, /\bmodel\b/, /\bconcept\b/, /이론|프레임워크|모형|개념/])) {
|
|
732
|
+
return "theory_framework";
|
|
733
|
+
}
|
|
734
|
+
if (answerIncludes(joined, [/\banalysis\b/, /\bmodel\b/, /\bcoding\b/, /\bstatistic\b/, /분석|모델|코딩|통계/])) {
|
|
735
|
+
return "analysis_plan";
|
|
736
|
+
}
|
|
737
|
+
if (answerIncludes(joined, [/\bpaper\b/, /\bmanuscript\b/, /\bdraft\b/, /\bwriting\b/, /논문|원고|초안|글쓰기/])) {
|
|
738
|
+
return "manuscript";
|
|
739
|
+
}
|
|
740
|
+
return "research_question";
|
|
741
|
+
}
|
|
742
|
+
function inferGapRiskFromSignals(signals) {
|
|
743
|
+
if (signals.includes("assumption") || signals.includes("decision_risk")) {
|
|
744
|
+
return "suspected_tacit_assumptions";
|
|
745
|
+
}
|
|
746
|
+
if (signals.includes("evidence")) {
|
|
747
|
+
return "known_gap";
|
|
748
|
+
}
|
|
749
|
+
return "diagnose";
|
|
750
|
+
}
|
|
751
|
+
function inferProtectedDecisionFromAnswers(answers) {
|
|
752
|
+
const joined = answers.join("\n").toLowerCase();
|
|
753
|
+
if (answerIncludes(joined, [/\bmeasure\b/, /\bscale\b/, /\binstrument\b/, /\bvariable\b/, /측정|척도|변수|도구/])) {
|
|
754
|
+
return "measurement";
|
|
755
|
+
}
|
|
756
|
+
if (answerIncludes(joined, [/\bmethod\b/, /\bdesign\b/, /\bsample\b/, /\binterview\b/, /\bexperiment\b/, /방법|설계|표본|인터뷰|실험/])) {
|
|
757
|
+
return "method";
|
|
758
|
+
}
|
|
759
|
+
if (answerIncludes(joined, [/\bevidence\b/, /\bsource\b/, /\bcitation\b/, /\bliterature\b/, /근거|출처|인용|문헌/])) {
|
|
760
|
+
return "evidence_citation";
|
|
761
|
+
}
|
|
762
|
+
if (answerIncludes(joined, [/\bvoice\b/, /\bauthor(ship)?\b/, /\bwriting\b/, /저자|목소리|문체|글쓰기/])) {
|
|
763
|
+
return "authorship_voice";
|
|
764
|
+
}
|
|
765
|
+
if (answerIncludes(joined, [/\bsubmit\b/, /\bpublication\b/, /\bjournal\b/, /\bvenue\b/, /투고|출판|저널|학회|공개/])) {
|
|
766
|
+
return "submission_public_sharing";
|
|
767
|
+
}
|
|
768
|
+
if (answerIncludes(joined, [/\btheory\b/, /\bframework\b/, /\bconstruct\b/, /이론|프레임워크|구성개념/])) {
|
|
769
|
+
return "theory";
|
|
770
|
+
}
|
|
771
|
+
return undefined;
|
|
772
|
+
}
|
|
773
|
+
function uniqueSignals(turns) {
|
|
774
|
+
return [...new Set(turns.map((turn) => turn.signal))];
|
|
775
|
+
}
|
|
776
|
+
function renderStartInterviewPrompt(turn, total, question, context) {
|
|
777
|
+
return [
|
|
778
|
+
`LongTable Start Interview Turn ${turn}/${total}`,
|
|
779
|
+
context ? `Context: ${context}` : undefined,
|
|
780
|
+
question
|
|
781
|
+
].filter(Boolean).join("\n");
|
|
782
|
+
}
|
|
783
|
+
async function collectAdaptiveStartInterview(args) {
|
|
784
|
+
if (!args.needsResearchSeed) {
|
|
785
|
+
return {};
|
|
786
|
+
}
|
|
787
|
+
const createdAt = new Date().toISOString();
|
|
788
|
+
const turns = [];
|
|
789
|
+
async function ask(question, purpose, context) {
|
|
790
|
+
const answer = await promptText(renderStartInterviewPrompt(turns.length + 1, 5, question, context), true);
|
|
791
|
+
if (!answer?.trim()) {
|
|
792
|
+
return undefined;
|
|
793
|
+
}
|
|
794
|
+
turns.push({
|
|
795
|
+
index: turns.length + 1,
|
|
796
|
+
question,
|
|
797
|
+
answer: answer.trim(),
|
|
798
|
+
signal: classifyStartInterviewSignal(answer),
|
|
799
|
+
purpose
|
|
800
|
+
});
|
|
801
|
+
return answer.trim();
|
|
802
|
+
}
|
|
803
|
+
let currentGoal = args.currentGoal;
|
|
804
|
+
let currentBlocker = args.currentBlocker;
|
|
805
|
+
if (!currentGoal) {
|
|
806
|
+
const opening = await ask("What scene, problem, or moment made you want to start this research?", "Open from the researcher's lived entry point instead of a taxonomy.");
|
|
807
|
+
currentGoal = opening ? compactAnswer(opening) : undefined;
|
|
808
|
+
}
|
|
809
|
+
const openingContext = currentGoal ? compactAnswer(currentGoal, 120) : undefined;
|
|
810
|
+
if (!currentBlocker) {
|
|
811
|
+
const blocker = await ask("In that scene, what still feels least explained or hardest to justify?", "Name the first uncertainty without asking the researcher to classify it as theory, method, or measurement.", openingContext);
|
|
812
|
+
currentBlocker = blocker ? compactAnswer(blocker) : undefined;
|
|
813
|
+
}
|
|
814
|
+
const readerContext = currentBlocker ? compactAnswer(currentBlocker, 120) : openingContext;
|
|
815
|
+
await ask("If this research succeeds, what should a reader or reviewer understand differently?", "Locate the audience-facing contribution before proposing a direction.", readerContext);
|
|
816
|
+
await ask("What material would you inspect first to make this research concrete: a case, dataset, text, instrument, draft, or literature trail?", "Convert the opening story into a first inspectable research move.", readerContext);
|
|
817
|
+
const answers = turns.map((turn) => turn.answer);
|
|
818
|
+
const inferredSignals = uniqueSignals(turns);
|
|
819
|
+
const summary = [
|
|
820
|
+
currentGoal ? `Opening: ${compactAnswer(currentGoal, 120)}.` : "",
|
|
821
|
+
currentBlocker ? `First uncertainty: ${compactAnswer(currentBlocker, 120)}.` : "",
|
|
822
|
+
inferredSignals.length > 0 ? `Early lenses: ${inferredSignals.join(", ")}.` : ""
|
|
823
|
+
].filter(Boolean).join(" ");
|
|
824
|
+
return {
|
|
825
|
+
currentGoal,
|
|
826
|
+
currentBlocker,
|
|
827
|
+
startInterview: {
|
|
828
|
+
mode: "adaptive",
|
|
829
|
+
openingStyle: "scene_problem",
|
|
830
|
+
createdAt,
|
|
831
|
+
completedAt: new Date().toISOString(),
|
|
832
|
+
turnCount: turns.length,
|
|
833
|
+
turns,
|
|
834
|
+
inferredSignals,
|
|
835
|
+
summary: summary || "Adaptive start interview completed."
|
|
836
|
+
},
|
|
837
|
+
inferredResearchObject: inferResearchObjectFromAnswers(answers),
|
|
838
|
+
inferredGapRisk: inferGapRiskFromSignals(inferredSignals),
|
|
839
|
+
inferredProtectedDecision: inferProtectedDecisionFromAnswers(answers)
|
|
840
|
+
};
|
|
841
|
+
}
|
|
890
842
|
function normalizePerspectiveList(value) {
|
|
891
843
|
if (!value?.trim()) {
|
|
892
844
|
return [];
|
|
@@ -897,82 +849,76 @@ function normalizePerspectiveList(value) {
|
|
|
897
849
|
.filter(Boolean);
|
|
898
850
|
}
|
|
899
851
|
async function collectProjectInterview(setup, args) {
|
|
852
|
+
const providedPerspectives = normalizePerspectiveList(typeof args.perspectives === "string" ? args.perspectives : undefined);
|
|
853
|
+
const providedGoal = typeof args.goal === "string" && args.goal.trim() ? args.goal.trim() : undefined;
|
|
854
|
+
const providedBlocker = typeof args.blocker === "string" && args.blocker.trim() ? args.blocker.trim() : undefined;
|
|
855
|
+
const providedResearchObject = typeof args["research-object"] === "string" && args["research-object"].trim()
|
|
856
|
+
? args["research-object"].trim()
|
|
857
|
+
: undefined;
|
|
858
|
+
const providedGapRisk = typeof args["gap-risk"] === "string" && args["gap-risk"].trim()
|
|
859
|
+
? args["gap-risk"].trim()
|
|
860
|
+
: undefined;
|
|
861
|
+
const providedProtectedDecision = typeof args["protected-decision"] === "string" && args["protected-decision"].trim()
|
|
862
|
+
? args["protected-decision"].trim()
|
|
863
|
+
: undefined;
|
|
900
864
|
const needsInteractivePrompts = !(typeof args.name === "string" && args.name.trim()) ||
|
|
901
865
|
!(typeof args.path === "string" && args.path.trim()) ||
|
|
902
|
-
!
|
|
903
|
-
|
|
904
|
-
!
|
|
905
|
-
!
|
|
906
|
-
!
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
"We will create a project workspace and a session memory seed for today's work.",
|
|
917
|
-
"At the end, LongTable will tell you exactly which directory to open in Codex."
|
|
918
|
-
]));
|
|
919
|
-
console.log("");
|
|
920
|
-
}
|
|
921
|
-
const projectName = (typeof args.name === "string" && args.name.trim()) ||
|
|
922
|
-
(await promptText(rl, renderQuestionHeader(1, 9, "Project interview", "What should this project be called?"), true));
|
|
923
|
-
const suggestedParentDir = typeof args.path === "string" && args.path.trim()
|
|
924
|
-
? normalizeUserPath(args.path.trim())
|
|
925
|
-
: homedir();
|
|
926
|
-
const suggestedPath = resolveInteractiveProjectPath(suggestedParentDir, projectName);
|
|
927
|
-
const projectPath = (typeof args.path === "string" && args.path.trim()
|
|
928
|
-
? normalizeUserPath(args.path.trim())
|
|
929
|
-
: resolveInteractiveProjectPath((await promptText(rl, renderQuestionHeader(2, 9, "Project interview", `Which parent directory should contain this project?\nLongTable will create this folder:\n${suggestedPath}`), true)), projectName));
|
|
930
|
-
const currentGoal = (typeof args.goal === "string" && args.goal.trim()) ||
|
|
931
|
-
(await promptText(rl, renderQuestionHeader(3, 9, "Current session", "What are you trying to accomplish in this session?"), true));
|
|
932
|
-
const currentBlocker = (typeof args.blocker === "string" && args.blocker.trim()) ||
|
|
933
|
-
(await promptText(rl, renderQuestionHeader(4, 9, "Current session", "What is the main blocker or uncertainty right now?"), false));
|
|
934
|
-
const researchObject = (typeof args["research-object"] === "string" && args["research-object"].trim()) ||
|
|
935
|
-
await promptChoice(rl, renderQuestionHeader(5, 9, "Research object", "What kind of research object are we protecting right now?"), researchObjectChoices());
|
|
936
|
-
const gapRisk = (typeof args["gap-risk"] === "string" && args["gap-risk"].trim()) ||
|
|
937
|
-
await promptChoice(rl, renderQuestionHeader(6, 9, "Gap/tacit risk", "What is the most likely gap risk at the start of this workspace?"), gapRiskChoices());
|
|
938
|
-
const protectedDecision = (typeof args["protected-decision"] === "string" && args["protected-decision"].trim()) ||
|
|
939
|
-
await promptChoice(rl, renderQuestionHeader(7, 9, "Protected decision", "Which decision should LongTable not let you settle quietly?"), protectedDecisionChoices());
|
|
940
|
-
const requestedPerspectives = normalizePerspectiveList(typeof args.perspectives === "string" ? args.perspectives : undefined).length > 0
|
|
941
|
-
? normalizePerspectiveList(typeof args.perspectives === "string" ? args.perspectives : undefined)
|
|
942
|
-
: await promptMultiChoice(rl, renderQuestionHeader(8, 9, "Perspectives", "Which perspectives do you already know you want at the table? Leave everything unchecked for auto."), perspectiveChoices());
|
|
943
|
-
const disagreementPreference = (typeof args.disagreement === "string" && args.disagreement.trim()) ||
|
|
944
|
-
(await promptChoice(rl, renderQuestionHeader(9, 9, "Disagreement", "How visible should disagreement between perspectives be in this project by default?"), [
|
|
945
|
-
{
|
|
946
|
-
id: "synthesis_only",
|
|
947
|
-
label: "Synthesis only",
|
|
948
|
-
description: "Show one LongTable answer unless I ask for more."
|
|
949
|
-
},
|
|
950
|
-
{
|
|
951
|
-
id: "show_on_conflict",
|
|
952
|
-
label: "Show on conflict",
|
|
953
|
-
description: "Surface disagreement when the perspectives materially diverge."
|
|
954
|
-
},
|
|
955
|
-
{
|
|
956
|
-
id: "always_visible",
|
|
957
|
-
label: "Always visible",
|
|
958
|
-
description: "Keep panel opinions visible by default."
|
|
959
|
-
}
|
|
960
|
-
]));
|
|
961
|
-
return {
|
|
962
|
-
projectName: projectName.trim(),
|
|
963
|
-
projectPath: projectPath.trim(),
|
|
964
|
-
currentGoal: currentGoal.trim(),
|
|
965
|
-
...(currentBlocker?.trim() ? { currentBlocker: currentBlocker.trim() } : {}),
|
|
966
|
-
researchObject: researchObject.trim(),
|
|
967
|
-
gapRisk: gapRisk.trim(),
|
|
968
|
-
protectedDecision: protectedDecision.trim(),
|
|
969
|
-
requestedPerspectives,
|
|
970
|
-
disagreementPreference: disagreementPreference
|
|
971
|
-
};
|
|
972
|
-
}
|
|
973
|
-
finally {
|
|
974
|
-
rl.close();
|
|
866
|
+
!providedGoal ||
|
|
867
|
+
!providedBlocker ||
|
|
868
|
+
!providedResearchObject ||
|
|
869
|
+
!providedGapRisk ||
|
|
870
|
+
!providedProtectedDecision;
|
|
871
|
+
if (needsInteractivePrompts) {
|
|
872
|
+
console.log("");
|
|
873
|
+
console.log(renderBrandBanner("LongTable", "Project workspace interview"));
|
|
874
|
+
console.log("");
|
|
875
|
+
console.log(renderSectionCard("LongTable Project Start", [
|
|
876
|
+
"LongTable will create a workspace and seed today's research memory.",
|
|
877
|
+
"The start interview begins from the scene or problem, then LongTable quietly infers the research shape."
|
|
878
|
+
]));
|
|
879
|
+
console.log("");
|
|
975
880
|
}
|
|
881
|
+
const projectName = (typeof args.name === "string" && args.name.trim()) ||
|
|
882
|
+
(await promptText(renderQuestionHeader(1, 2, "Workspace", "What should this project be called?"), true));
|
|
883
|
+
const suggestedParentDir = typeof args.path === "string" && args.path.trim()
|
|
884
|
+
? normalizeUserPath(args.path.trim())
|
|
885
|
+
: homedir();
|
|
886
|
+
const suggestedPath = resolveInteractiveProjectPath(suggestedParentDir, projectName);
|
|
887
|
+
const projectPath = (typeof args.path === "string" && args.path.trim()
|
|
888
|
+
? normalizeUserPath(args.path.trim())
|
|
889
|
+
: resolveInteractiveProjectPath((await promptText(renderQuestionHeader(2, 2, "Workspace", `Which parent directory should contain this project?\nLongTable will create this folder:\n${suggestedPath}`), true)), projectName));
|
|
890
|
+
const adaptive = await collectAdaptiveStartInterview({
|
|
891
|
+
currentGoal: providedGoal,
|
|
892
|
+
currentBlocker: providedBlocker,
|
|
893
|
+
needsResearchSeed: !providedGoal ||
|
|
894
|
+
!providedBlocker ||
|
|
895
|
+
!providedResearchObject ||
|
|
896
|
+
!providedGapRisk ||
|
|
897
|
+
!providedProtectedDecision
|
|
898
|
+
});
|
|
899
|
+
const currentGoal = providedGoal ?? adaptive.currentGoal;
|
|
900
|
+
if (!currentGoal?.trim()) {
|
|
901
|
+
throw new Error("LongTable start needs a current research goal or an opening interview answer.");
|
|
902
|
+
}
|
|
903
|
+
const currentBlocker = providedBlocker ?? adaptive.currentBlocker;
|
|
904
|
+
const researchObject = providedResearchObject ?? adaptive.inferredResearchObject;
|
|
905
|
+
const gapRisk = providedGapRisk ?? adaptive.inferredGapRisk;
|
|
906
|
+
const protectedDecision = providedProtectedDecision ?? adaptive.inferredProtectedDecision;
|
|
907
|
+
const disagreementPreference = (typeof args.disagreement === "string" && args.disagreement.trim()
|
|
908
|
+
? args.disagreement.trim()
|
|
909
|
+
: setup.profileSeed.panelPreference ?? "show_on_conflict");
|
|
910
|
+
return {
|
|
911
|
+
projectName: projectName.trim(),
|
|
912
|
+
projectPath: projectPath.trim(),
|
|
913
|
+
currentGoal: currentGoal.trim(),
|
|
914
|
+
...(currentBlocker?.trim() ? { currentBlocker: currentBlocker.trim() } : {}),
|
|
915
|
+
...(researchObject?.trim() ? { researchObject: researchObject.trim() } : {}),
|
|
916
|
+
...(gapRisk?.trim() ? { gapRisk: gapRisk.trim() } : {}),
|
|
917
|
+
...(protectedDecision?.trim() ? { protectedDecision: protectedDecision.trim() } : {}),
|
|
918
|
+
...(adaptive.startInterview ? { startInterview: adaptive.startInterview } : {}),
|
|
919
|
+
requestedPerspectives: providedPerspectives,
|
|
920
|
+
disagreementPreference
|
|
921
|
+
};
|
|
976
922
|
}
|
|
977
923
|
function normalizePersistAnswers(raw) {
|
|
978
924
|
return {
|
|
@@ -2013,6 +1959,7 @@ function renderEvidenceRunSummary(run, recordedPath) {
|
|
|
2013
1959
|
].filter(Boolean).join(", ");
|
|
2014
1960
|
lines.push(` - ${card.title}`);
|
|
2015
1961
|
lines.push(` score: ${card.relevanceScore}; support: ${card.citationSupportStatus}; depth: ${card.evidenceDepth}; sources: ${card.sourceRoutes.join(", ")}`);
|
|
1962
|
+
lines.push(` access: ${card.accessStatus}; verification: ${card.verificationNote}`);
|
|
2016
1963
|
if (identifiers) {
|
|
2017
1964
|
lines.push(` ids: ${identifiers}`);
|
|
2018
1965
|
}
|
|
@@ -2056,7 +2003,194 @@ async function recordEvidenceRun(run, workingDirectory) {
|
|
|
2056
2003
|
await syncCurrentWorkspaceView(context);
|
|
2057
2004
|
return evidencePath;
|
|
2058
2005
|
}
|
|
2059
|
-
|
|
2006
|
+
function renderPublisherAccessRecord(record) {
|
|
2007
|
+
const envSummary = record.missingEnv.length > 0
|
|
2008
|
+
? `missing ${record.missingEnv.join(", ")}`
|
|
2009
|
+
: `configured ${record.presentEnv.join(", ") || "none"}`;
|
|
2010
|
+
const lines = [
|
|
2011
|
+
`${record.publisher}:`,
|
|
2012
|
+
` credential: ${record.credentialStatus} (${envSummary})`,
|
|
2013
|
+
` entitlement: ${record.entitlementStatus}; tdm: ${record.tdmStatus}; depth: ${record.collectionDepth}`,
|
|
2014
|
+
` verification: ${record.verificationNote}`
|
|
2015
|
+
];
|
|
2016
|
+
if (record.testedDoi) {
|
|
2017
|
+
lines.push(` tested doi: ${record.testedDoi}`);
|
|
2018
|
+
}
|
|
2019
|
+
if (record.endpoint) {
|
|
2020
|
+
lines.push(` endpoint: ${record.endpoint}`);
|
|
2021
|
+
}
|
|
2022
|
+
if (record.licenseNote) {
|
|
2023
|
+
lines.push(` license: ${record.licenseNote}`);
|
|
2024
|
+
}
|
|
2025
|
+
if (record.setupHint) {
|
|
2026
|
+
lines.push(` setup: ${record.setupHint}`);
|
|
2027
|
+
}
|
|
2028
|
+
return lines.join("\n");
|
|
2029
|
+
}
|
|
2030
|
+
function renderPublisherAccessRecords(title, records, capabilityPath) {
|
|
2031
|
+
const lines = [title];
|
|
2032
|
+
if (capabilityPath) {
|
|
2033
|
+
lines.push(`- capability file: ${capabilityPath}`);
|
|
2034
|
+
}
|
|
2035
|
+
for (const record of records) {
|
|
2036
|
+
lines.push(renderPublisherAccessRecord(record));
|
|
2037
|
+
}
|
|
2038
|
+
return lines.join("\n");
|
|
2039
|
+
}
|
|
2040
|
+
async function saveSearchCapabilityRecords(records) {
|
|
2041
|
+
const snapshotPath = searchCapabilitySnapshotPath();
|
|
2042
|
+
await mkdir(dirname(snapshotPath), { recursive: true });
|
|
2043
|
+
await writeJsonFile(snapshotPath, buildSearchCapabilitySnapshot(records, env));
|
|
2044
|
+
return snapshotPath;
|
|
2045
|
+
}
|
|
2046
|
+
async function probeAllPublishers(doi) {
|
|
2047
|
+
const records = [];
|
|
2048
|
+
for (const publisher of publisherConfigs()) {
|
|
2049
|
+
records.push(await probePublisherAccess({
|
|
2050
|
+
doi,
|
|
2051
|
+
publisher: publisher.publisher,
|
|
2052
|
+
env
|
|
2053
|
+
}));
|
|
2054
|
+
}
|
|
2055
|
+
return records;
|
|
2056
|
+
}
|
|
2057
|
+
async function runSearchProbe(args) {
|
|
2058
|
+
if (typeof args.doi !== "string" || !args.doi.trim()) {
|
|
2059
|
+
throw new Error("`longtable search probe` requires --doi <doi>.");
|
|
2060
|
+
}
|
|
2061
|
+
const publisher = parsePublisherTarget(args.publisher);
|
|
2062
|
+
const record = await probePublisherAccess({
|
|
2063
|
+
doi: args.doi,
|
|
2064
|
+
publisher,
|
|
2065
|
+
env
|
|
2066
|
+
});
|
|
2067
|
+
if (args.json === true) {
|
|
2068
|
+
console.log(JSON.stringify({ record }, null, 2));
|
|
2069
|
+
}
|
|
2070
|
+
else {
|
|
2071
|
+
console.log(renderPublisherAccessRecord(record));
|
|
2072
|
+
}
|
|
2073
|
+
return [record];
|
|
2074
|
+
}
|
|
2075
|
+
async function runSearchDoctor(args) {
|
|
2076
|
+
let records;
|
|
2077
|
+
if (typeof args.doi === "string" && args.doi.trim()) {
|
|
2078
|
+
if (args.publisher === "all") {
|
|
2079
|
+
records = await probeAllPublishers(args.doi);
|
|
2080
|
+
}
|
|
2081
|
+
else {
|
|
2082
|
+
const publisher = parsePublisherTarget(args.publisher);
|
|
2083
|
+
records = [await probePublisherAccess({
|
|
2084
|
+
doi: args.doi,
|
|
2085
|
+
publisher,
|
|
2086
|
+
env
|
|
2087
|
+
})];
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
else {
|
|
2091
|
+
records = summarizeConfiguredPublisherAccess(env);
|
|
2092
|
+
}
|
|
2093
|
+
const snapshotPath = searchCapabilitySnapshotPath();
|
|
2094
|
+
const snapshotExists = existsSync(snapshotPath);
|
|
2095
|
+
if (args.json === true) {
|
|
2096
|
+
console.log(JSON.stringify({
|
|
2097
|
+
capabilityFile: snapshotPath,
|
|
2098
|
+
capabilityFileExists: snapshotExists,
|
|
2099
|
+
records
|
|
2100
|
+
}, null, 2));
|
|
2101
|
+
}
|
|
2102
|
+
else {
|
|
2103
|
+
console.log(renderPublisherAccessRecords("LongTable Search Publisher Access Doctor", records, snapshotPath));
|
|
2104
|
+
if (!snapshotExists) {
|
|
2105
|
+
console.log("- saved capabilities: none yet; run `longtable search setup` to record non-secret capability status.");
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
return records;
|
|
2109
|
+
}
|
|
2110
|
+
async function promptPublisherDoi(rl, label, defaultDoi) {
|
|
2111
|
+
const prompt = defaultDoi
|
|
2112
|
+
? `${label} test DOI [${defaultDoi}, Enter to reuse, skip to skip]: `
|
|
2113
|
+
: `${label} test DOI (Enter to skip): `;
|
|
2114
|
+
const answer = (await rl.question(prompt)).trim();
|
|
2115
|
+
if (!answer && defaultDoi) {
|
|
2116
|
+
return defaultDoi;
|
|
2117
|
+
}
|
|
2118
|
+
if (!answer || /^skip$/i.test(answer)) {
|
|
2119
|
+
return undefined;
|
|
2120
|
+
}
|
|
2121
|
+
return answer;
|
|
2122
|
+
}
|
|
2123
|
+
async function runInteractiveSearchSetup(defaultDoi) {
|
|
2124
|
+
const rl = createInterface({ input, output });
|
|
2125
|
+
const records = [];
|
|
2126
|
+
try {
|
|
2127
|
+
console.log("LongTable publisher access setup");
|
|
2128
|
+
console.log("LongTable does not store API keys or TDM tokens. It reads environment variables and records only non-secret capability results.");
|
|
2129
|
+
console.log("");
|
|
2130
|
+
for (const publisher of publisherConfigs()) {
|
|
2131
|
+
console.log(`${publisher.label}`);
|
|
2132
|
+
console.log(` required env: ${publisher.requiredEnv.join(", ")}`);
|
|
2133
|
+
if (publisher.optionalEnv.length > 0) {
|
|
2134
|
+
console.log(` optional env: ${publisher.optionalEnv.join(", ")}`);
|
|
2135
|
+
}
|
|
2136
|
+
console.log(` ${publisher.setupHint}`);
|
|
2137
|
+
const doi = await promptPublisherDoi(rl, publisher.label, defaultDoi);
|
|
2138
|
+
if (doi) {
|
|
2139
|
+
records.push(await probePublisherAccess({
|
|
2140
|
+
doi,
|
|
2141
|
+
publisher: publisher.publisher,
|
|
2142
|
+
env
|
|
2143
|
+
}));
|
|
2144
|
+
}
|
|
2145
|
+
else {
|
|
2146
|
+
const summary = summarizeConfiguredPublisherAccess(env)
|
|
2147
|
+
.find((record) => record.publisher === publisher.publisher);
|
|
2148
|
+
if (summary) {
|
|
2149
|
+
records.push(summary);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
console.log(renderPublisherAccessRecord(records[records.length - 1]));
|
|
2153
|
+
console.log("");
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
finally {
|
|
2157
|
+
rl.close();
|
|
2158
|
+
}
|
|
2159
|
+
return records;
|
|
2160
|
+
}
|
|
2161
|
+
async function runSearchSetup(args) {
|
|
2162
|
+
const defaultDoi = typeof args.doi === "string" ? args.doi : undefined;
|
|
2163
|
+
const records = input.isTTY && output.isTTY && args.json !== true
|
|
2164
|
+
? await runInteractiveSearchSetup(defaultDoi)
|
|
2165
|
+
: defaultDoi
|
|
2166
|
+
? await probeAllPublishers(defaultDoi)
|
|
2167
|
+
: summarizeConfiguredPublisherAccess(env);
|
|
2168
|
+
const snapshotPath = await saveSearchCapabilityRecords(records);
|
|
2169
|
+
if (args.json === true) {
|
|
2170
|
+
console.log(JSON.stringify({
|
|
2171
|
+
capabilityFile: snapshotPath,
|
|
2172
|
+
snapshot: buildSearchCapabilitySnapshot(records, env)
|
|
2173
|
+
}, null, 2));
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
console.log(renderPublisherAccessRecords("LongTable Search Publisher Access Setup", records, snapshotPath));
|
|
2177
|
+
}
|
|
2178
|
+
async function runSearch(subcommand, args) {
|
|
2179
|
+
if (subcommand === "probe") {
|
|
2180
|
+
await runSearchProbe(args);
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
if (subcommand === "doctor" || subcommand === "status") {
|
|
2184
|
+
await runSearchDoctor(args);
|
|
2185
|
+
return;
|
|
2186
|
+
}
|
|
2187
|
+
if (subcommand === "setup") {
|
|
2188
|
+
await runSearchSetup(args);
|
|
2189
|
+
return;
|
|
2190
|
+
}
|
|
2191
|
+
if (subcommand) {
|
|
2192
|
+
throw new Error(`Unknown search subcommand: ${subcommand}`);
|
|
2193
|
+
}
|
|
2060
2194
|
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
2061
2195
|
const projectContext = await loadProjectContextFromDirectory(workingDirectory);
|
|
2062
2196
|
const searchInput = {
|
|
@@ -2082,7 +2216,8 @@ async function runSearch(args) {
|
|
|
2082
2216
|
const run = await runResearchSearch({
|
|
2083
2217
|
...searchInput,
|
|
2084
2218
|
env,
|
|
2085
|
-
allowPartial
|
|
2219
|
+
allowPartial,
|
|
2220
|
+
publisherAccess: args["publisher-access"] === true
|
|
2086
2221
|
});
|
|
2087
2222
|
let recordedPath;
|
|
2088
2223
|
if (args.record === true && run.status !== "blocked") {
|
|
@@ -2142,30 +2277,24 @@ async function runQuestion(args) {
|
|
|
2142
2277
|
return;
|
|
2143
2278
|
}
|
|
2144
2279
|
if (isInteractiveTerminal()) {
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
console.log(`- current: ${context.currentFilePath}`);
|
|
2164
|
-
return;
|
|
2165
|
-
}
|
|
2166
|
-
finally {
|
|
2167
|
-
rl.close();
|
|
2168
|
-
}
|
|
2280
|
+
console.log(renderBrandBanner("LongTable", "Researcher Checkpoint"));
|
|
2281
|
+
console.log("");
|
|
2282
|
+
const answer = await promptChoice(renderQuestionHeader(1, 1, result.question.prompt.title, result.question.prompt.question), questionRecordToChoices(result.question));
|
|
2283
|
+
const decision = await answerWorkspaceQuestion({
|
|
2284
|
+
context,
|
|
2285
|
+
questionId: result.question.id,
|
|
2286
|
+
answer,
|
|
2287
|
+
provider,
|
|
2288
|
+
surface: "terminal_selector"
|
|
2289
|
+
});
|
|
2290
|
+
console.log("");
|
|
2291
|
+
console.log("LongTable checkpoint decision recorded");
|
|
2292
|
+
console.log(`- question: ${decision.question.id}`);
|
|
2293
|
+
console.log(`- decision: ${decision.decision.id}`);
|
|
2294
|
+
console.log(`- answer: ${decision.decision.selectedOption ?? answer}`);
|
|
2295
|
+
console.log(`- state: ${context.stateFilePath}`);
|
|
2296
|
+
console.log(`- current: ${context.currentFilePath}`);
|
|
2297
|
+
return;
|
|
2169
2298
|
}
|
|
2170
2299
|
const optionValues = [
|
|
2171
2300
|
...result.question.prompt.options.map((option) => option.value),
|
|
@@ -2256,25 +2385,19 @@ async function answerFollowUpQuestionsInTerminal(context, questions, provider) {
|
|
|
2256
2385
|
if (questions.length === 0) {
|
|
2257
2386
|
return;
|
|
2258
2387
|
}
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
surface: "terminal_selector"
|
|
2273
|
-
});
|
|
2274
|
-
}
|
|
2275
|
-
}
|
|
2276
|
-
finally {
|
|
2277
|
-
rl.close();
|
|
2388
|
+
console.log(renderBrandBanner("LongTable", "Follow-up Questions"));
|
|
2389
|
+
console.log("");
|
|
2390
|
+
for (let index = 0; index < questions.length; index += 1) {
|
|
2391
|
+
const question = questions[index];
|
|
2392
|
+
const prompt = renderQuestionHeader(index + 1, questions.length, question.prompt.title, question.prompt.question);
|
|
2393
|
+
const answer = await promptChoice(prompt, questionRecordToChoices(question));
|
|
2394
|
+
await answerWorkspaceQuestion({
|
|
2395
|
+
context,
|
|
2396
|
+
questionId: question.id,
|
|
2397
|
+
answer,
|
|
2398
|
+
provider,
|
|
2399
|
+
surface: "terminal_selector"
|
|
2400
|
+
});
|
|
2278
2401
|
}
|
|
2279
2402
|
}
|
|
2280
2403
|
async function runClarify(args) {
|
|
@@ -2665,6 +2788,7 @@ async function runStart(args) {
|
|
|
2665
2788
|
researchObject: interview.researchObject,
|
|
2666
2789
|
gapRisk: interview.gapRisk,
|
|
2667
2790
|
protectedDecision: interview.protectedDecision,
|
|
2791
|
+
startInterview: interview.startInterview,
|
|
2668
2792
|
requestedPerspectives: interview.requestedPerspectives,
|
|
2669
2793
|
disagreementPreference: interview.disagreementPreference,
|
|
2670
2794
|
setup: existingSetup
|
|
@@ -2907,7 +3031,7 @@ async function main() {
|
|
|
2907
3031
|
return;
|
|
2908
3032
|
}
|
|
2909
3033
|
if (command === "search") {
|
|
2910
|
-
await runSearch(values);
|
|
3034
|
+
await runSearch(subcommand, values);
|
|
2911
3035
|
return;
|
|
2912
3036
|
}
|
|
2913
3037
|
if (command === "ask") {
|