@longtable/cli 0.1.1 → 0.1.3

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 CHANGED
@@ -1,101 +1,101 @@
1
1
  # @longtable/cli
2
2
 
3
- Researcher-facing Long Table CLI built on top of the legacy `@diverga/*` package surface.
3
+ Long Table researcher-facing CLI입니다.
4
4
 
5
- 현재 공개 설치 경로는 `@longtable/cli`입니다.
5
+ 패키지는 연구자가 아래처럼 시작할 수 있게 만드는 것이 목표입니다.
6
6
 
7
- ## Install
8
-
9
- 공개 설치:
7
+ - `longtable init`
8
+ - `longtable start`
9
+ - `longtable ask --prompt "..."`
10
10
 
11
- ```bash
12
- npm install -g @longtable/cli
13
- ```
11
+ 긴 내부 명령이나 legacy package 이름을 알 필요 없이,
12
+ 연구자가 `오늘 무엇이 필요한가`에서 바로 출발할 수 있게 하는 표면입니다.
14
13
 
15
- 로컬 preview:
14
+ ## Install
16
15
 
17
16
  ```bash
18
- npm install
19
- npm run build
20
- node packages/longtable/dist/cli.js --help
17
+ npm install -g @longtable/cli
21
18
  ```
22
19
 
23
20
  ## Recommended flow
24
21
 
25
- Run setup once and install Codex prompt aliases:
22
+ ### 1. Setup once
26
23
 
27
24
  ```bash
28
- longtable init --install-prompts
25
+ longtable init --flow interview
29
26
  ```
30
27
 
31
- `longtable init` now uses an arrow-key terminal menu instead of plain number-entry prompts.
28
+ 인터뷰 setup은 연구자의
29
+
30
+ - 현재 주제
31
+ - 현재 막힘
32
+ - 선호하는 첫 진입 모드
33
+ - 가장 많이 도전받고 싶은 영역
34
+ - 역할 간 의견 충돌 가시성
32
35
 
33
- Then you can work in two ways.
36
+ 까지 저장합니다.
34
37
 
35
- From the terminal:
38
+ ### 2. Start from today’s work
36
39
 
37
40
  ```bash
38
- longtable review --prompt "Review this claim critically."
39
- longtable review --prompt "BJET 편집자 관점에서 봐줘." --role editor
40
- longtable review --prompt "방법론적으로 어디가 취약한지 말해줘." --panel --show-conflicts
41
+ longtable start
41
42
  ```
42
43
 
43
- Inside Codex after prompt aliases are installed:
44
+ `longtable start`는 가장 단순한 진입점입니다.
45
+ 오늘 필요한 작업을 먼저 고르고, 그 다음 자연어로 상황을 말하면 됩니다.
44
46
 
45
- - `/prompts:longtable-init`
46
- - `/prompts:longtable-explore`
47
- - `/prompts:longtable-review`
48
- - `/prompts:longtable-critique`
49
- - `/prompts:longtable-draft`
50
- - `/prompts:longtable-commit`
51
- - `/prompts:longtable-status`
52
-
53
- ## Core commands
47
+ ### 3. Or jump straight in
54
48
 
55
49
  ```bash
56
- longtable init
57
- longtable show --json
58
- longtable install --json
59
- longtable explore --prompt "Help me stay exploratory."
60
- longtable review --prompt "Review this claim critically."
61
- longtable commit --prompt "Help me make this decision carefully."
50
+ longtable ask --prompt "연구를 시작하고 싶은데 어디서부터 좁혀야 할지 모르겠어."
62
51
  ```
63
52
 
64
- ## Codex overlay
65
-
66
- Install Codex prompt aliases:
53
+ ## Useful commands
67
54
 
68
55
  ```bash
69
- longtable codex install-prompts
56
+ longtable start
57
+ longtable roles
58
+ longtable ask --prompt "이 연구 아이디어를 어디서부터 열어야 할지 모르겠어."
59
+ longtable review --prompt "BJET 편집자 관점에서 봐줘." --role editor
60
+ longtable review --prompt "방법론적으로 어디가 취약한지 말해줘." --role methods_critic
61
+ longtable review --prompt "의견 충돌까지 보여줘." --panel --show-conflicts
62
+ longtable show --json
70
63
  ```
71
64
 
72
- If you finish onboarding inside Codex with `/prompts:longtable-init`, you can persist the collected answers with:
65
+ ## What `roles` means
73
66
 
74
67
  ```bash
75
- longtable codex persist-init --provider codex --field education --career-stage doctoral --experience intermediate --project-type "journal article" --checkpoint balanced --install-prompts
68
+ longtable roles
76
69
  ```
77
70
 
78
- Or from a JSON block:
71
+ Long Table이 어떤 관점을 호출할 수 있는지 짧게 보여줍니다.
79
72
 
80
- ```bash
81
- pbpaste | longtable codex persist-init --stdin --install-prompts
82
- ```
73
+ 예:
74
+
75
+ - `editor`
76
+ - `reviewer`
77
+ - `methods_critic`
78
+ - `voice_keeper`
83
79
 
84
- Check whether setup and aliases are present:
80
+ 기본값은 하나의 Long Table synthesis이지만, 필요하면 panel과 conflict도 드러낼 수 있습니다.
81
+
82
+ ## Codex integration
83
+
84
+ Long Table은 Codex prompt file 설치도 지원합니다.
85
85
 
86
86
  ```bash
87
+ longtable codex install-prompts
87
88
  longtable codex status
88
89
  ```
89
90
 
90
- Remove the aliases:
91
+ 하지만 경로는 현재 `실험적`입니다.
91
92
 
92
- ```bash
93
- longtable codex remove-prompts
94
- ```
93
+ - Codex 버전/빌드에 따라 prompt file이 slash command로 바로 노출되지 않을 수 있습니다.
94
+ - 그래서 기본 사용자 경로는 `/prompts:...`가 아니라 `longtable start`와 `longtable ask`입니다.
95
95
 
96
96
  ## Why this package exists
97
97
 
98
- Long Table is the product name.
99
- `Diverga` remains an internal compatibility layer for some lower-level packages and runtime paths.
98
+ Long Table 제품명입니다.
99
+ `Diverga`는 아직 일부 하위 패키지와 runtime path에서만 남아 있는 호환성 레이어입니다.
100
100
 
101
- This package exists so researchers can install and run `longtable` directly while that compatibility layer remains underneath.
101
+ 패키지는 연구자가 `longtable`만 알면 되게 하기 위해 존재합니다.
package/dist/cli.js CHANGED
@@ -7,6 +7,7 @@ import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput,
7
7
  import { buildCodexThinWrappedPrompt, runCodexThinWrapper } from "@diverga/provider-codex";
8
8
  import { installCodexPromptAliases, listInstalledCodexPromptAliases, removeCodexPromptAliases, resolveCodexPromptsDir } from "./prompt-aliases.js";
9
9
  import { buildPersonaGuidance } from "./persona-router.js";
10
+ import { PERSONA_DEFINITIONS } from "./personas.js";
10
11
  const VALID_MODES = new Set([
11
12
  "explore",
12
13
  "review",
@@ -27,9 +28,12 @@ const VALID_STAGES = new Set([
27
28
  function usage() {
28
29
  return [
29
30
  "Usage:",
30
- " longtable init [--provider codex|claude] [--field <field>] [--career-stage <stage>] [--experience novice|intermediate|advanced] [--project-type <type>] [--checkpoint low|balanced|high] [--authorship-signal <text>] [--json] [--no-install] [--install-prompts]",
31
+ " longtable init [--flow quickstart|interview] [--provider codex|claude] [--field <field>] [--career-stage <stage>] [--experience novice|intermediate|advanced] [--project-type <type>] [--checkpoint low|balanced|high] [--authorship-signal <text>] [--topic <text>] [--blocker <text>] [--entry-mode explore|review|critique|draft|commit] [--weakest-domain theory|methodology|measurement|analysis|writing] [--panel-preference synthesis_only|show_on_conflict|always_visible] [--json] [--no-install] [--install-prompts]",
32
+ " longtable start [--prompt <text>] [--setup <path>] [--cwd <path>] [--print] [--json]",
33
+ " longtable roles [--json]",
31
34
  " longtable show [--json] [--path <file>]",
32
35
  " longtable install [--json] [--path <file>] [--runtime-path <file>]",
36
+ " longtable ask [--prompt <text>] [--print] [--json] [--setup <path>] [--cwd <path>]",
33
37
  " longtable explore|review|critique|draft|commit|submit [--prompt <text>] [--role <role[,role]>] [--panel] [--show-conflicts] [--show-deliberation] [--print] [--json] [--stage <stage>] [--setup <path>] [--cwd <path>]",
34
38
  " longtable codex persist-init [--answers-json <json> | --stdin | full setup flags] [--install-prompts] [--json]",
35
39
  " longtable codex install-prompts [--dir <path>]",
@@ -37,7 +41,10 @@ function usage() {
37
41
  " longtable codex status [--dir <path>] [--json]",
38
42
  "",
39
43
  "Examples:",
40
- " longtable init --install-prompts",
44
+ " longtable init --flow interview --install-prompts",
45
+ " longtable start",
46
+ " longtable roles",
47
+ " longtable ask --prompt \"연구를 시작하고 싶어. 지금 어디서부터 좁혀야 할지 모르겠어.\"",
41
48
  " longtable review --prompt \"Review this claim critically.\" --panel --show-conflicts",
42
49
  " longtable review --role editor --prompt \"BJET 편집자 관점에서 봐줘.\"",
43
50
  " printf '{\"provider\":\"codex\",...}' | longtable codex persist-init --stdin --install-prompts",
@@ -49,7 +56,7 @@ function parseArgs(argv) {
49
56
  const values = {};
50
57
  let subcommand = maybeSubcommand;
51
58
  const modeCommand = command && VALID_MODES.has(command);
52
- const directCommand = command && ["init", "show", "install", "codex"].includes(command);
59
+ const directCommand = command && ["init", "start", "roles", "show", "install", "codex", "ask"].includes(command);
53
60
  let startIndex = 1;
54
61
  if (modeCommand) {
55
62
  subcommand = undefined;
@@ -91,6 +98,53 @@ function renderChoices(choices) {
91
98
  .map((choice, index) => `${index + 1}. ${choice.label} — ${choice.description}`)
92
99
  .join("\n");
93
100
  }
101
+ function buildSetupFlowChoices() {
102
+ return [
103
+ {
104
+ id: "quickstart",
105
+ label: "Quickstart",
106
+ description: "Minimal setup for the fastest first win."
107
+ },
108
+ {
109
+ id: "interview",
110
+ label: "Interview",
111
+ description: "A more detailed researcher profile interview for better first guidance."
112
+ }
113
+ ];
114
+ }
115
+ function renderSetupHeader(flow) {
116
+ const title = flow === "interview" ? "Long Table Setup Interview" : "Long Table Quickstart";
117
+ const subtitle = flow === "interview"
118
+ ? "We will ask about your research persona, current topic, blocker, and preferred kind of challenge."
119
+ : "We will capture the minimum profile needed to start using Long Table.";
120
+ return [
121
+ "┌──────────────────────────────────────────────┐",
122
+ `│ ${title.padEnd(44, " ")}│`,
123
+ "└──────────────────────────────────────────────┘",
124
+ subtitle
125
+ ].join("\n");
126
+ }
127
+ function renderQuestionHeader(index, total, section, prompt) {
128
+ return [``, `[${index}/${total}] ${section}`, prompt].join("\n");
129
+ }
130
+ function questionSection(questionId) {
131
+ if (questionId === "field" || questionId === "careerStage" || questionId === "experienceLevel") {
132
+ return "Researcher profile";
133
+ }
134
+ if (questionId === "currentProjectType" || questionId === "currentResearchTopic" || questionId === "currentBlocker") {
135
+ return "Current work";
136
+ }
137
+ if (questionId === "preferredCheckpointIntensity" || questionId === "preferredEntryMode") {
138
+ return "Interaction style";
139
+ }
140
+ if (questionId === "weakestDomain" || questionId === "panelPreference") {
141
+ return "How Long Table should challenge you";
142
+ }
143
+ return "Authorship and voice";
144
+ }
145
+ function formatModeLabel(mode) {
146
+ return `${mode[0].toUpperCase()}${mode.slice(1)}`;
147
+ }
94
148
  function moveCursorUp(lines) {
95
149
  return lines > 0 ? `\u001B[${lines}A` : "";
96
150
  }
@@ -125,6 +179,18 @@ async function promptChoiceByNumber(rl, prompt, choices) {
125
179
  return choice.id;
126
180
  }
127
181
  }
182
+ async function promptText(rl, prompt, required) {
183
+ while (true) {
184
+ const answer = (await rl.question(`${prompt}\n> `)).trim();
185
+ if (!required) {
186
+ return answer || undefined;
187
+ }
188
+ if (answer) {
189
+ return answer;
190
+ }
191
+ console.log("This answer cannot be empty.");
192
+ }
193
+ }
128
194
  async function promptChoiceWithArrows(rl, prompt, choices) {
129
195
  const stream = input;
130
196
  if (!stream.isTTY || !output.isTTY) {
@@ -194,10 +260,59 @@ async function promptChoiceWithArrows(rl, prompt, choices) {
194
260
  async function promptChoice(rl, prompt, choices) {
195
261
  return promptChoiceWithArrows(rl, prompt, choices);
196
262
  }
263
+ async function promptStartAction(rl, blocker) {
264
+ return (await promptChoice(rl, blocker?.trim()
265
+ ? `What do you want to do with this first?\nCurrent blocker: ${blocker.trim()}`
266
+ : "What do you want Long Table to help you do first?", [
267
+ {
268
+ id: "explore",
269
+ label: "Open the problem",
270
+ description: "Start with questions, tensions, and a clearer research direction."
271
+ },
272
+ {
273
+ id: "review",
274
+ label: "Review something",
275
+ description: "Critically inspect a claim, outline, or draft."
276
+ },
277
+ {
278
+ id: "critique",
279
+ label: "Challenge me hard",
280
+ description: "Push back, stress-test assumptions, and surface hidden risks."
281
+ },
282
+ {
283
+ id: "editor",
284
+ label: "Journal editor view",
285
+ description: "See how an editor might judge framing, fit, and contribution."
286
+ },
287
+ {
288
+ id: "methods",
289
+ label: "Methods check",
290
+ description: "Focus on design fit, defensibility, and methodological weakness."
291
+ },
292
+ {
293
+ id: "panel",
294
+ label: "Show disagreement",
295
+ description: "Let multiple roles disagree instead of forcing one answer."
296
+ },
297
+ {
298
+ id: "status",
299
+ label: "Check my setup",
300
+ description: "See what Long Table knows and what is installed."
301
+ },
302
+ {
303
+ id: "roles",
304
+ label: "What roles exist?",
305
+ description: "See what perspectives Long Table can bring into the conversation."
306
+ }
307
+ ]));
308
+ }
197
309
  function hasCompleteFlagInput(args) {
198
310
  const required = ["provider", "field", "career-stage", "experience", "project-type", "checkpoint"];
199
311
  return required.every((key) => typeof args[key] === "string" && String(args[key]).trim().length > 0);
200
312
  }
313
+ function resolveSetupFlow(args) {
314
+ return String(args.flow) === "interview" ? "interview" : "quickstart";
315
+ }
201
316
  function toSetupAnswers(args) {
202
317
  return {
203
318
  field: String(args.field),
@@ -207,19 +322,43 @@ function toSetupAnswers(args) {
207
322
  preferredCheckpointIntensity: String(args.checkpoint),
208
323
  humanAuthorshipSignal: typeof args["authorship-signal"] === "string" && args["authorship-signal"].trim().length > 0
209
324
  ? args["authorship-signal"].trim()
325
+ : undefined,
326
+ currentResearchTopic: typeof args.topic === "string" && args.topic.trim().length > 0 ? args.topic.trim() : undefined,
327
+ currentBlocker: typeof args.blocker === "string" && args.blocker.trim().length > 0 ? args.blocker.trim() : undefined,
328
+ preferredEntryMode: typeof args["entry-mode"] === "string" && VALID_MODES.has(String(args["entry-mode"]))
329
+ ? String(args["entry-mode"])
330
+ : undefined,
331
+ weakestDomain: typeof args["weakest-domain"] === "string"
332
+ ? String(args["weakest-domain"])
333
+ : undefined,
334
+ panelPreference: typeof args["panel-preference"] === "string"
335
+ ? String(args["panel-preference"])
210
336
  : undefined
211
337
  };
212
338
  }
213
339
  async function collectInteractiveAnswers() {
214
340
  const rl = createInterface({ input, output });
215
341
  try {
342
+ const flow = (await promptChoice(rl, "How would you like to set up Long Table?", buildSetupFlowChoices()));
343
+ console.log("");
344
+ console.log(renderSetupHeader(flow));
345
+ console.log("");
216
346
  const provider = await promptChoice(rl, "Which provider do you want to configure?", buildProviderChoices());
217
347
  const answers = {};
218
- for (const question of buildQuickSetupFlow()) {
219
- if (!question.choices) {
348
+ const questions = buildQuickSetupFlow(flow);
349
+ for (let index = 0; index < questions.length; index += 1) {
350
+ const question = questions[index];
351
+ const prompt = renderQuestionHeader(index + 1, questions.length, questionSection(question.id), question.prompt);
352
+ let value;
353
+ if (question.kind === "text") {
354
+ value = await promptText(rl, prompt, question.required);
355
+ }
356
+ else if (question.choices) {
357
+ value = await promptChoice(rl, prompt, question.choices);
358
+ }
359
+ if (!value) {
220
360
  continue;
221
361
  }
222
- const value = await promptChoice(rl, question.prompt, question.choices);
223
362
  if (question.id === "field")
224
363
  answers.field = value;
225
364
  if (question.id === "careerStage")
@@ -234,8 +373,19 @@ async function collectInteractiveAnswers() {
234
373
  if (question.id === "humanAuthorshipSignal" && value !== "other") {
235
374
  answers.humanAuthorshipSignal = value;
236
375
  }
376
+ if (question.id === "currentResearchTopic")
377
+ answers.currentResearchTopic = value;
378
+ if (question.id === "currentBlocker")
379
+ answers.currentBlocker = value;
380
+ if (question.id === "preferredEntryMode")
381
+ answers.preferredEntryMode = value;
382
+ if (question.id === "weakestDomain")
383
+ answers.weakestDomain = value;
384
+ if (question.id === "panelPreference")
385
+ answers.panelPreference = value;
237
386
  }
238
387
  return {
388
+ flow,
239
389
  provider,
240
390
  answers: answers
241
391
  };
@@ -246,6 +396,7 @@ async function collectInteractiveAnswers() {
246
396
  }
247
397
  function normalizePersistAnswers(raw) {
248
398
  return {
399
+ flow: raw.flow === "interview" ? "interview" : "quickstart",
249
400
  provider: raw.provider === "claude" ? "claude" : "codex",
250
401
  answers: {
251
402
  field: raw.field,
@@ -255,13 +406,32 @@ function normalizePersistAnswers(raw) {
255
406
  preferredCheckpointIntensity: raw.preferredCheckpointIntensity,
256
407
  ...(raw.humanAuthorshipSignal?.trim()
257
408
  ? { humanAuthorshipSignal: raw.humanAuthorshipSignal.trim() }
409
+ : {}),
410
+ ...(raw.currentResearchTopic?.trim()
411
+ ? { currentResearchTopic: raw.currentResearchTopic.trim() }
412
+ : {}),
413
+ ...(raw.currentBlocker?.trim()
414
+ ? { currentBlocker: raw.currentBlocker.trim() }
415
+ : {}),
416
+ ...(raw.preferredEntryMode
417
+ ? { preferredEntryMode: raw.preferredEntryMode }
418
+ : {}),
419
+ ...(raw.weakestDomain
420
+ ? { weakestDomain: raw.weakestDomain }
421
+ : {}),
422
+ ...(raw.panelPreference
423
+ ? { panelPreference: raw.panelPreference }
258
424
  : {})
259
425
  }
260
426
  };
261
427
  }
262
428
  async function readPersistAnswers(args) {
263
429
  if (typeof args["answers-json"] === "string") {
264
- return normalizePersistAnswers(JSON.parse(args["answers-json"]));
430
+ const normalized = normalizePersistAnswers(JSON.parse(args["answers-json"]));
431
+ return {
432
+ ...normalized,
433
+ flow: typeof args.flow === "string" ? resolveSetupFlow(args) : normalized.flow
434
+ };
265
435
  }
266
436
  if (args.stdin === true) {
267
437
  const raw = readFileSync(0, "utf8").trim();
@@ -272,6 +442,7 @@ async function readPersistAnswers(args) {
272
442
  }
273
443
  if (hasCompleteFlagInput(args)) {
274
444
  return {
445
+ flow: resolveSetupFlow(args),
275
446
  provider: String(args.provider) === "claude" ? "claude" : "codex",
276
447
  answers: toSetupAnswers(args)
277
448
  };
@@ -285,13 +456,14 @@ async function runInit(args) {
285
456
  const customPath = typeof args.path === "string" ? args.path : undefined;
286
457
  const runtimePath = typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined;
287
458
  const promptsDir = typeof args.dir === "string" ? args.dir : undefined;
288
- const { provider, answers } = hasCompleteFlagInput(args)
459
+ const { flow, provider, answers } = hasCompleteFlagInput(args)
289
460
  ? {
461
+ flow: resolveSetupFlow(args),
290
462
  provider: String(args.provider) === "claude" ? "claude" : "codex",
291
463
  answers: toSetupAnswers(args)
292
464
  }
293
465
  : await collectInteractiveAnswers();
294
- const outputValue = createPersistedSetupOutput(answers, provider);
466
+ const outputValue = createPersistedSetupOutput(answers, provider, flow);
295
467
  const result = await saveSetupAndRuntimeConfig(outputValue, {
296
468
  setupPath: customPath,
297
469
  runtimePath
@@ -311,10 +483,27 @@ async function runInit(args) {
311
483
  }
312
484
  if (installedPrompts.length > 0) {
313
485
  console.log("");
314
- console.log("Installed Codex prompt aliases:");
486
+ console.log("Installed Codex prompt files:");
315
487
  for (const prompt of installedPrompts) {
316
488
  console.log(`- /prompts:${prompt.name}`);
317
489
  }
490
+ console.log(" Note: whether Codex exposes these as slash commands depends on your Codex build.");
491
+ }
492
+ if (provider === "codex") {
493
+ console.log("");
494
+ console.log("Next step:");
495
+ console.log("- Start here: `longtable start`.");
496
+ console.log("- If you want a direct natural-language entry: `longtable ask --prompt \"...\"`.");
497
+ console.log("- Codex prompt files are available as an experimental integration, not the primary path.");
498
+ if (answers.currentBlocker) {
499
+ console.log(`- Suggested first message: ${answers.currentBlocker}`);
500
+ }
501
+ else if (answers.currentResearchTopic) {
502
+ console.log(`- Suggested first message: Help me open the problem around ${answers.currentResearchTopic}.`);
503
+ }
504
+ else {
505
+ console.log("- Suggested first message: I want to start my research and need help opening the problem.");
506
+ }
318
507
  }
319
508
  }
320
509
  async function runShow(args) {
@@ -337,8 +526,8 @@ async function runInstall(args) {
337
526
  console.log(renderInstallSummary(result));
338
527
  }
339
528
  async function runCodexPersistInit(args) {
340
- const { provider, answers } = await readPersistAnswers(args);
341
- const outputValue = createPersistedSetupOutput(answers, provider);
529
+ const { flow, provider, answers } = await readPersistAnswers(args);
530
+ const outputValue = createPersistedSetupOutput(answers, provider, flow);
342
531
  const result = await saveSetupAndRuntimeConfig(outputValue, {
343
532
  setupPath: typeof args.path === "string" ? args.path : undefined,
344
533
  runtimePath: typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined
@@ -360,10 +549,27 @@ async function runCodexPersistInit(args) {
360
549
  console.log(renderInstallSummary(result));
361
550
  if (installedPrompts.length > 0) {
362
551
  console.log("");
363
- console.log("Installed Codex prompt aliases:");
552
+ console.log("Installed Codex prompt files:");
364
553
  for (const prompt of installedPrompts) {
365
554
  console.log(`- /prompts:${prompt.name}`);
366
555
  }
556
+ console.log(" Note: whether Codex exposes these as slash commands depends on your Codex build.");
557
+ }
558
+ if (provider === "codex") {
559
+ console.log("");
560
+ console.log("Next step:");
561
+ console.log("- Start here: `longtable start`.");
562
+ console.log("- If you want a direct natural-language entry: `longtable ask --prompt \"...\"`.");
563
+ console.log("- Codex prompt files are available as an experimental integration, not the primary path.");
564
+ if (answers.currentBlocker) {
565
+ console.log(`- Suggested first message: ${answers.currentBlocker}`);
566
+ }
567
+ else if (answers.currentResearchTopic) {
568
+ console.log(`- Suggested first message: Help me think through ${answers.currentResearchTopic}.`);
569
+ }
570
+ else {
571
+ console.log("- Suggested first message: I want to start my research and I am not sure where to open the problem.");
572
+ }
367
573
  }
368
574
  }
369
575
  async function resolvePrompt(prompt) {
@@ -381,6 +587,57 @@ async function resolvePrompt(prompt) {
381
587
  rl.close();
382
588
  }
383
589
  }
590
+ function inferModeFromPrompt(prompt) {
591
+ const normalized = prompt.toLowerCase();
592
+ if (normalized.includes("status") ||
593
+ normalized.includes("설정") ||
594
+ normalized.includes("상태") ||
595
+ normalized.includes("롱테이블 상태")) {
596
+ return "status";
597
+ }
598
+ if (normalized.includes("패널") ||
599
+ normalized.includes("의견 충돌") ||
600
+ normalized.includes("conflict") ||
601
+ normalized.includes("disagree") ||
602
+ normalized.includes("panel")) {
603
+ return "panel";
604
+ }
605
+ if (normalized.includes("결정") ||
606
+ normalized.includes("고를") ||
607
+ normalized.includes("선택") ||
608
+ normalized.includes("commit")) {
609
+ return "commit";
610
+ }
611
+ if (normalized.includes("초안") ||
612
+ normalized.includes("써줘") ||
613
+ normalized.includes("문단") ||
614
+ normalized.includes("draft") ||
615
+ normalized.includes("write")) {
616
+ return "draft";
617
+ }
618
+ if (normalized.includes("비판") ||
619
+ normalized.includes("반론") ||
620
+ normalized.includes("critique") ||
621
+ normalized.includes("challenge")) {
622
+ return "critique";
623
+ }
624
+ if (normalized.includes("검토") ||
625
+ normalized.includes("review") ||
626
+ normalized.includes("편집자") ||
627
+ normalized.includes("리뷰어") ||
628
+ normalized.includes("judge")) {
629
+ return "review";
630
+ }
631
+ return "explore";
632
+ }
633
+ async function loadOptionalSetup(path) {
634
+ try {
635
+ return await loadSetupOutput(path);
636
+ }
637
+ catch {
638
+ return null;
639
+ }
640
+ }
384
641
  async function runModeCommand(mode, args) {
385
642
  const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
386
643
  if (!prompt) {
@@ -390,10 +647,16 @@ async function runModeCommand(mode, args) {
390
647
  if (stage && !VALID_STAGES.has(stage)) {
391
648
  throw new Error(`Invalid stage: ${stage}`);
392
649
  }
650
+ const setup = await loadOptionalSetup(typeof args.setup === "string" ? args.setup : undefined);
651
+ const panelPreference = setup?.profileSeed.panelPreference;
652
+ const panelRequested = args.panel === true ||
653
+ panelPreference === "always_visible" ||
654
+ (panelPreference === "show_on_conflict" && args["show-conflicts"] === true);
393
655
  const { guidedPrompt } = buildPersonaGuidance({
656
+ mode,
394
657
  prompt,
395
658
  roleFlag: typeof args.role === "string" ? args.role : undefined,
396
- panel: args.panel === true,
659
+ panel: panelRequested,
397
660
  showConflicts: args["show-conflicts"] === true,
398
661
  showDeliberation: args["show-deliberation"] === true
399
662
  });
@@ -418,11 +681,139 @@ async function runModeCommand(mode, args) {
418
681
  });
419
682
  exit(exitCode);
420
683
  }
684
+ async function runAsk(args) {
685
+ const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
686
+ if (!prompt) {
687
+ throw new Error("A prompt is required.");
688
+ }
689
+ const inferred = inferModeFromPrompt(prompt);
690
+ if (inferred === "status") {
691
+ await runCodexSubcommand("status", args);
692
+ return;
693
+ }
694
+ const mode = inferred === "panel" ? "review" : inferred;
695
+ const delegatedArgs = {
696
+ ...args,
697
+ prompt
698
+ };
699
+ if (inferred === "panel" && delegatedArgs.panel !== true) {
700
+ delegatedArgs.panel = true;
701
+ delegatedArgs["show-conflicts"] = true;
702
+ }
703
+ await runModeCommand(mode, delegatedArgs);
704
+ }
705
+ async function runRoles(args) {
706
+ const payload = PERSONA_DEFINITIONS.map((persona) => ({
707
+ key: persona.key,
708
+ label: persona.label,
709
+ description: persona.shortDescription,
710
+ triggerMode: persona.triggerMode,
711
+ exampleTriggers: persona.synonyms.slice(0, 4)
712
+ }));
713
+ if (args.json === true) {
714
+ console.log(JSON.stringify(payload, null, 2));
715
+ return;
716
+ }
717
+ console.log("Long Table roles");
718
+ console.log("These are perspectives Long Table can consult when relevant.");
719
+ console.log("");
720
+ for (const persona of payload) {
721
+ console.log(`- ${persona.label} (${persona.key})`);
722
+ console.log(` ${persona.description}`);
723
+ console.log(` Trigger: ${persona.triggerMode === "auto-callable" ? "auto-callable when your language strongly implies it" : "explicit request only"}`);
724
+ console.log(` Examples: ${persona.exampleTriggers.join(", ")}`);
725
+ }
726
+ }
727
+ async function runStart(args) {
728
+ const setupPath = typeof args.setup === "string" ? args.setup : undefined;
729
+ const existingSetup = await loadOptionalSetup(setupPath);
730
+ if (!existingSetup) {
731
+ console.log("Long Table is not set up yet. Starting the interview flow first.");
732
+ console.log("");
733
+ await runInit({
734
+ ...args,
735
+ flow: typeof args.flow === "string" ? args.flow : "interview",
736
+ "install-prompts": true
737
+ });
738
+ return;
739
+ }
740
+ const directPrompt = typeof args.prompt === "string" ? args.prompt.trim() : "";
741
+ if (directPrompt) {
742
+ await runAsk({
743
+ ...args,
744
+ prompt: directPrompt,
745
+ ...(setupPath ? { setup: setupPath } : {})
746
+ });
747
+ return;
748
+ }
749
+ const rl = createInterface({ input, output });
750
+ try {
751
+ const action = await promptStartAction(rl, existingSetup.profileSeed.currentBlocker ?? existingSetup.profileSeed.currentResearchTopic);
752
+ if (action === "status") {
753
+ rl.close();
754
+ await runCodexSubcommand("status", args);
755
+ return;
756
+ }
757
+ if (action === "roles") {
758
+ rl.close();
759
+ await runRoles(args);
760
+ return;
761
+ }
762
+ const defaultPrompt = existingSetup.profileSeed.currentBlocker ??
763
+ existingSetup.profileSeed.currentResearchTopic ??
764
+ "";
765
+ const prompt = (await rl.question(defaultPrompt
766
+ ? `What are you working on today?\nPress Enter to use: ${defaultPrompt}\n> `
767
+ : "What are you working on today?\n> ")).trim() || defaultPrompt;
768
+ rl.close();
769
+ if (!prompt) {
770
+ throw new Error("A prompt is required.");
771
+ }
772
+ if (action === "editor") {
773
+ await runModeCommand("review", {
774
+ ...args,
775
+ prompt,
776
+ role: "editor",
777
+ ...(setupPath ? { setup: setupPath } : {})
778
+ });
779
+ return;
780
+ }
781
+ if (action === "methods") {
782
+ await runModeCommand("review", {
783
+ ...args,
784
+ prompt,
785
+ role: "methods_critic",
786
+ ...(setupPath ? { setup: setupPath } : {})
787
+ });
788
+ return;
789
+ }
790
+ if (action === "panel") {
791
+ await runModeCommand("review", {
792
+ ...args,
793
+ prompt,
794
+ panel: true,
795
+ "show-conflicts": true,
796
+ ...(setupPath ? { setup: setupPath } : {})
797
+ });
798
+ return;
799
+ }
800
+ const mode = action;
801
+ await runModeCommand(mode, {
802
+ ...args,
803
+ prompt,
804
+ ...(setupPath ? { setup: setupPath } : {})
805
+ });
806
+ }
807
+ finally {
808
+ rl.close();
809
+ }
810
+ }
421
811
  async function runCodexSubcommand(subcommand, args) {
422
812
  const customDir = typeof args.dir === "string" ? args.dir : undefined;
423
813
  if (subcommand === "install-prompts") {
424
814
  const installed = await installCodexPromptAliases(customDir);
425
815
  console.log(`Installed ${installed.length} Long Table prompt aliases in ${resolveCodexPromptsDir(customDir)}`);
816
+ console.log("Note: prompt-file discovery depends on the Codex build. Treat this as an experimental integration.");
426
817
  for (const prompt of installed) {
427
818
  console.log(`- /prompts:${prompt.name}`);
428
819
  }
@@ -457,6 +848,7 @@ async function runCodexSubcommand(subcommand, args) {
457
848
  console.log(`- setup: ${status.setupExists ? "present" : "missing"} (${setupPath})`);
458
849
  console.log(`- codex runtime artifact: ${status.runtimeExists ? "present" : "missing"} (${runtimePath})`);
459
850
  console.log(`- prompt aliases dir: ${status.promptsDir}`);
851
+ console.log("- prompt-file integration: experimental (your Codex build may not expose these as slash commands)");
460
852
  if (aliases.length === 0) {
461
853
  console.log("- prompt aliases: none");
462
854
  }
@@ -481,6 +873,14 @@ async function main() {
481
873
  await runInit(values);
482
874
  return;
483
875
  }
876
+ if (command === "start") {
877
+ await runStart(values);
878
+ return;
879
+ }
880
+ if (command === "roles") {
881
+ await runRoles(values);
882
+ return;
883
+ }
484
884
  if (command === "show") {
485
885
  await runShow(values);
486
886
  return;
@@ -489,6 +889,10 @@ async function main() {
489
889
  await runInstall(values);
490
890
  return;
491
891
  }
892
+ if (command === "ask") {
893
+ await runAsk(values);
894
+ return;
895
+ }
492
896
  if (command === "codex") {
493
897
  await runCodexSubcommand(subcommand, values);
494
898
  return;
@@ -500,7 +904,11 @@ async function main() {
500
904
  throw new Error(`Unknown command: ${command}`);
501
905
  }
502
906
  main().catch((error) => {
503
- console.error(error instanceof Error ? error.message : String(error));
907
+ const message = error instanceof Error ? error.message : String(error);
908
+ console.error(message);
909
+ if (message === "Setup cancelled.") {
910
+ exit(1);
911
+ }
504
912
  console.error("");
505
913
  console.error(usage());
506
914
  exit(1);
@@ -1,4 +1,5 @@
1
1
  import { type CanonicalPersona } from "./personas.js";
2
+ import type { InteractionMode } from "@diverga/core";
2
3
  export type OutputLanguage = "ko" | "en";
3
4
  export interface PersonaRoutingResult {
4
5
  outputLanguage: OutputLanguage;
@@ -12,6 +13,7 @@ export declare function parseRoleFlag(value?: string): CanonicalPersona[];
12
13
  export declare function routePersonas(prompt: string, explicitRoleFlag?: string): PersonaRoutingResult;
13
14
  export declare function renderDisclosure(roles: CanonicalPersona[], language: OutputLanguage): string | null;
14
15
  export declare function buildPersonaGuidance(options: {
16
+ mode: InteractionMode;
15
17
  prompt: string;
16
18
  roleFlag?: string;
17
19
  panel?: boolean;
@@ -63,6 +63,9 @@ export function buildPersonaGuidance(options) {
63
63
  const routing = routePersonas(options.prompt, options.roleFlag);
64
64
  const disclosure = renderDisclosure(routing.consultedRoles, routing.outputLanguage);
65
65
  const lines = [];
66
+ lines.push(routing.outputLanguage === "ko"
67
+ ? `Long Table mode: ${options.mode[0].toUpperCase()}${options.mode.slice(1)}`
68
+ : `Long Table mode: ${options.mode[0].toUpperCase()}${options.mode.slice(1)}`);
66
69
  if (disclosure) {
67
70
  lines.push(disclosure);
68
71
  }
@@ -86,6 +89,9 @@ export function buildPersonaGuidance(options) {
86
89
  ? "Include a short deliberation trace showing why the roles diverged."
87
90
  : "Include a short deliberation trace showing why the roles diverged.");
88
91
  }
92
+ lines.push(routing.outputLanguage === "ko"
93
+ ? "Do not show internal file-search logs, tool traces, or process commentary in the researcher-facing answer."
94
+ : "Do not show internal file-search logs, tool traces, or process commentary in the researcher-facing answer.");
89
95
  lines.push(options.prompt.trim());
90
96
  return {
91
97
  guidedPrompt: lines.join("\n\n"),
@@ -7,19 +7,38 @@ export function resolveCodexPromptsDir(customDir) {
7
7
  }
8
8
  function promptSpec() {
9
9
  return [
10
+ {
11
+ name: "longtable",
12
+ description: "Single-entry Long Table router for research conversations",
13
+ argumentHint: "<natural language research request>",
14
+ body: [
15
+ "You are Long Table.",
16
+ "Classify the user's request into one of these modes: explore, review, critique, draft, commit, panel, or status.",
17
+ "If the request is ambiguous, ask one short clarifying question before closing.",
18
+ "Always begin with `Long Table mode: <Mode>`.",
19
+ "Always disclose consulted roles with `Long Table consulted: ...` when any role is foregrounded.",
20
+ "In explore mode, ask at least two clarifying or tension questions before any recommendation.",
21
+ "In panel mode, return 1) Long Table synthesis 2) visible panel opinions by role 3) conflict summary if needed 4) a decision prompt for the researcher.",
22
+ "Do not expose internal tool logs, file searches, or process notes in the researcher-facing answer.",
23
+ "Treat any slash-command arguments as the current research object."
24
+ ]
25
+ },
10
26
  {
11
27
  name: "longtable-init",
12
28
  description: "Run Long Table researcher onboarding inside Codex",
13
29
  argumentHint: "[project context or current uncertainty]",
14
30
  body: [
15
31
  "You are Long Table onboarding inside Codex.",
32
+ "First ask whether the researcher wants Quickstart or Interview setup.",
16
33
  "Ask exactly one setup question at a time.",
17
34
  "Use numbered choices when possible and include a 'None of the above' option when needed.",
18
35
  "Do not move to the next question until the researcher answers the current one.",
19
- "Cover these fields: provider, field, career stage, experience level, current project type, checkpoint intensity, and human authorship signal.",
20
- "After collecting all answers, summarize the proposed setup and then output both: 1) the exact `longtable codex persist-init ... --install-prompts` command and 2) a strict JSON object with keys provider, field, careerStage, experienceLevel, currentProjectType, preferredCheckpointIntensity, and optional humanAuthorshipSignal.",
36
+ "Quickstart covers: provider, field, career stage, experience level, current project type, checkpoint intensity, and human authorship signal.",
37
+ "Interview also covers: current research topic, current blocker, preferred entry mode, weakest domain, and panel visibility preference.",
38
+ "After collecting all answers, summarize the proposed setup and then output both: 1) the exact `longtable codex persist-init ... --install-prompts` command and 2) a strict JSON object with keys provider, flow, field, careerStage, experienceLevel, currentProjectType, preferredCheckpointIntensity, and optional humanAuthorshipSignal, currentResearchTopic, currentBlocker, preferredEntryMode, weakestDomain, panelPreference.",
21
39
  "If the user prefers paste-based setup, tell them they can pipe the JSON into `longtable codex persist-init --stdin --install-prompts`.",
22
40
  "If the researcher asks you to stay inside Codex, keep the conversation in numbered form and do not prematurely close.",
41
+ "Frame the setup like a short researcher interview, not a bare config form.",
23
42
  "Treat any slash-command arguments as context for why setup is being done now."
24
43
  ]
25
44
  },
@@ -29,9 +48,11 @@ function promptSpec() {
29
48
  argumentHint: "<topic or research problem>",
30
49
  body: [
31
50
  "You are Long Table in explore mode.",
51
+ "Always begin with `Long Table mode: Explore`.",
32
52
  "Ask at least two clarifying or tension questions before any recommendation.",
33
53
  "Keep unresolved tensions visible.",
34
54
  "Do not rush to synthesis.",
55
+ "Do not expose internal process notes or file-search summaries.",
35
56
  "Treat any slash-command arguments as the current research object."
36
57
  ]
37
58
  },
@@ -41,6 +62,7 @@ function promptSpec() {
41
62
  argumentHint: "<claim, paragraph, design, or plan>",
42
63
  body: [
43
64
  "You are Long Table in review mode.",
65
+ "Always begin with `Long Table mode: Review`.",
44
66
  "Surface why this may be wrong before synthesis.",
45
67
  "Preserve the researcher's own language where possible.",
46
68
  "Treat any slash-command arguments as the object to review."
@@ -52,6 +74,7 @@ function promptSpec() {
52
74
  argumentHint: "<claim, plan, or draft for multi-role review>",
53
75
  body: [
54
76
  "You are Long Table in panel mode.",
77
+ "Always begin with `Long Table mode: Panel`.",
55
78
  "Return 1) a Long Table synthesis 2) visible panel opinions by role 3) a decision prompt for the researcher.",
56
79
  "If roles disagree, do not collapse them too early.",
57
80
  "Disclose which roles were consulted.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longtable/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "private": false,
5
5
  "description": "Researcher-facing Long Table CLI on top of the legacy Diverga package surface",
6
6
  "type": "module",
@@ -26,7 +26,7 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@diverga/provider-codex": "0.1.1",
29
- "@diverga/setup": "0.1.1"
29
+ "@diverga/setup": "0.1.3"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^22.10.1",