@longtable/cli 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -14
- package/bin/longtable +0 -0
- package/dist/cli.js +397 -11
- package/dist/persona-router.d.ts +2 -0
- package/dist/persona-router.js +6 -0
- package/dist/prompt-aliases.js +26 -2
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -2,26 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
Researcher-facing Long Table CLI built on top of the legacy `@diverga/*` package surface.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- `@longtable/cli`는 repo 안에서는 구현되어 있음
|
|
8
|
-
- npm의 `@longtable` scope가 아직 준비되지 않아 실제 publish는 아직 안 됨
|
|
9
|
-
- 지금 공개 설치 경로는 여전히 `@diverga/setup`, `@diverga/provider-codex`
|
|
5
|
+
현재 공개 설치 경로는 `@longtable/cli`입니다.
|
|
10
6
|
|
|
11
7
|
## Install
|
|
12
8
|
|
|
13
|
-
|
|
9
|
+
공개 설치:
|
|
14
10
|
|
|
15
11
|
```bash
|
|
16
|
-
npm install
|
|
17
|
-
npm run build
|
|
18
|
-
node packages/longtable/dist/cli.js --help
|
|
12
|
+
npm install -g @longtable/cli
|
|
19
13
|
```
|
|
20
14
|
|
|
21
|
-
|
|
15
|
+
로컬 preview:
|
|
22
16
|
|
|
23
17
|
```bash
|
|
24
|
-
npm install
|
|
18
|
+
npm install
|
|
19
|
+
npm run build
|
|
20
|
+
node packages/longtable/dist/cli.js --help
|
|
25
21
|
```
|
|
26
22
|
|
|
27
23
|
## Recommended flow
|
|
@@ -29,14 +25,20 @@ npm install -g @longtable/cli
|
|
|
29
25
|
Run setup once and install Codex prompt aliases:
|
|
30
26
|
|
|
31
27
|
```bash
|
|
32
|
-
longtable init --install-prompts
|
|
28
|
+
longtable init --flow interview --install-prompts
|
|
33
29
|
```
|
|
34
30
|
|
|
31
|
+
`longtable init` now uses an arrow-key terminal menu instead of plain number-entry prompts and supports:
|
|
32
|
+
|
|
33
|
+
- `--flow quickstart`
|
|
34
|
+
- `--flow interview`
|
|
35
|
+
|
|
35
36
|
Then you can work in two ways.
|
|
36
37
|
|
|
37
38
|
From the terminal:
|
|
38
39
|
|
|
39
40
|
```bash
|
|
41
|
+
longtable ask --prompt "연구를 시작하고 싶어. 지금 어디서부터 좁혀야 할지 모르겠어."
|
|
40
42
|
longtable review --prompt "Review this claim critically."
|
|
41
43
|
longtable review --prompt "BJET 편집자 관점에서 봐줘." --role editor
|
|
42
44
|
longtable review --prompt "방법론적으로 어디가 취약한지 말해줘." --panel --show-conflicts
|
|
@@ -44,6 +46,7 @@ longtable review --prompt "방법론적으로 어디가 취약한지 말해줘."
|
|
|
44
46
|
|
|
45
47
|
Inside Codex after prompt aliases are installed:
|
|
46
48
|
|
|
49
|
+
- `/prompts:longtable`
|
|
47
50
|
- `/prompts:longtable-init`
|
|
48
51
|
- `/prompts:longtable-explore`
|
|
49
52
|
- `/prompts:longtable-review`
|
|
@@ -55,6 +58,7 @@ Inside Codex after prompt aliases are installed:
|
|
|
55
58
|
## Core commands
|
|
56
59
|
|
|
57
60
|
```bash
|
|
61
|
+
longtable ask --prompt "Help me open this research problem carefully."
|
|
58
62
|
longtable init
|
|
59
63
|
longtable show --json
|
|
60
64
|
longtable install --json
|
|
@@ -71,6 +75,18 @@ Install Codex prompt aliases:
|
|
|
71
75
|
longtable codex install-prompts
|
|
72
76
|
```
|
|
73
77
|
|
|
78
|
+
If you finish onboarding inside Codex with `/prompts:longtable-init`, you can persist the collected answers with:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
longtable codex persist-init --flow interview --provider codex --field education --career-stage doctoral --experience intermediate --project-type "journal article" --checkpoint balanced --topic "AI adoption in workplace settings" --blocker "기업 맥락으로 범위를 다시 잡아야 하는데 어디서부터 포함/제외 기준을 세울지 모르겠어." --entry-mode explore --weakest-domain methodology --panel-preference show_on_conflict --install-prompts
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Or from a JSON block:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pbpaste | longtable codex persist-init --stdin --install-prompts
|
|
88
|
+
```
|
|
89
|
+
|
|
74
90
|
Check whether setup and aliases are present:
|
|
75
91
|
|
|
76
92
|
```bash
|
|
@@ -86,6 +102,6 @@ longtable codex remove-prompts
|
|
|
86
102
|
## Why this package exists
|
|
87
103
|
|
|
88
104
|
Long Table is the product name.
|
|
89
|
-
`Diverga`
|
|
105
|
+
`Diverga` remains an internal compatibility layer for some lower-level packages and runtime paths.
|
|
90
106
|
|
|
91
|
-
This package
|
|
107
|
+
This package exists so researchers can install and run `longtable` directly while that compatibility layer remains underneath.
|
package/bin/longtable
CHANGED
|
File without changes
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { emitKeypressEvents } from "node:readline";
|
|
3
4
|
import { createInterface } from "node:readline/promises";
|
|
4
5
|
import { stdin as input, stdout as output, cwd, exit } from "node:process";
|
|
5
6
|
import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput, installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultRuntimeConfigPath, resolveDefaultSetupPath, saveSetupAndRuntimeConfig, serializeSetupOutput } from "@diverga/setup";
|
|
@@ -26,18 +27,22 @@ const VALID_STAGES = new Set([
|
|
|
26
27
|
function usage() {
|
|
27
28
|
return [
|
|
28
29
|
"Usage:",
|
|
29
|
-
" 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]",
|
|
30
|
+
" 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]",
|
|
30
31
|
" longtable show [--json] [--path <file>]",
|
|
31
32
|
" longtable install [--json] [--path <file>] [--runtime-path <file>]",
|
|
33
|
+
" longtable ask [--prompt <text>] [--print] [--json] [--setup <path>] [--cwd <path>]",
|
|
32
34
|
" 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>]",
|
|
35
|
+
" longtable codex persist-init [--answers-json <json> | --stdin | full setup flags] [--install-prompts] [--json]",
|
|
33
36
|
" longtable codex install-prompts [--dir <path>]",
|
|
34
37
|
" longtable codex remove-prompts [--dir <path>]",
|
|
35
38
|
" longtable codex status [--dir <path>] [--json]",
|
|
36
39
|
"",
|
|
37
40
|
"Examples:",
|
|
38
|
-
" longtable init --install-prompts",
|
|
41
|
+
" longtable init --flow interview --install-prompts",
|
|
42
|
+
" longtable ask --prompt \"연구를 시작하고 싶어. 지금 어디서부터 좁혀야 할지 모르겠어.\"",
|
|
39
43
|
" longtable review --prompt \"Review this claim critically.\" --panel --show-conflicts",
|
|
40
44
|
" longtable review --role editor --prompt \"BJET 편집자 관점에서 봐줘.\"",
|
|
45
|
+
" printf '{\"provider\":\"codex\",...}' | longtable codex persist-init --stdin --install-prompts",
|
|
41
46
|
" longtable codex install-prompts"
|
|
42
47
|
].join("\n");
|
|
43
48
|
}
|
|
@@ -46,7 +51,7 @@ function parseArgs(argv) {
|
|
|
46
51
|
const values = {};
|
|
47
52
|
let subcommand = maybeSubcommand;
|
|
48
53
|
const modeCommand = command && VALID_MODES.has(command);
|
|
49
|
-
const directCommand = command && ["init", "show", "install", "codex"].includes(command);
|
|
54
|
+
const directCommand = command && ["init", "show", "install", "codex", "ask"].includes(command);
|
|
50
55
|
let startIndex = 1;
|
|
51
56
|
if (modeCommand) {
|
|
52
57
|
subcommand = undefined;
|
|
@@ -88,7 +93,65 @@ function renderChoices(choices) {
|
|
|
88
93
|
.map((choice, index) => `${index + 1}. ${choice.label} — ${choice.description}`)
|
|
89
94
|
.join("\n");
|
|
90
95
|
}
|
|
91
|
-
|
|
96
|
+
function buildSetupFlowChoices() {
|
|
97
|
+
return [
|
|
98
|
+
{
|
|
99
|
+
id: "quickstart",
|
|
100
|
+
label: "Quickstart",
|
|
101
|
+
description: "Minimal setup for the fastest first win."
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "interview",
|
|
105
|
+
label: "Interview",
|
|
106
|
+
description: "A more detailed researcher profile interview for better first guidance."
|
|
107
|
+
}
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
function renderSetupHeader(flow) {
|
|
111
|
+
const title = flow === "interview" ? "Long Table Setup Interview" : "Long Table Quickstart";
|
|
112
|
+
const subtitle = flow === "interview"
|
|
113
|
+
? "We will ask about your research persona, current topic, blocker, and preferred kind of challenge."
|
|
114
|
+
: "We will capture the minimum profile needed to start using Long Table.";
|
|
115
|
+
return [
|
|
116
|
+
"┌──────────────────────────────────────────────┐",
|
|
117
|
+
`│ ${title.padEnd(44, " ")}│`,
|
|
118
|
+
"└──────────────────────────────────────────────┘",
|
|
119
|
+
subtitle
|
|
120
|
+
].join("\n");
|
|
121
|
+
}
|
|
122
|
+
function renderQuestionHeader(index, total, section, prompt) {
|
|
123
|
+
return [``, `[${index}/${total}] ${section}`, prompt].join("\n");
|
|
124
|
+
}
|
|
125
|
+
function questionSection(questionId) {
|
|
126
|
+
if (questionId === "field" || questionId === "careerStage" || questionId === "experienceLevel") {
|
|
127
|
+
return "Researcher profile";
|
|
128
|
+
}
|
|
129
|
+
if (questionId === "currentProjectType" || questionId === "currentResearchTopic" || questionId === "currentBlocker") {
|
|
130
|
+
return "Current work";
|
|
131
|
+
}
|
|
132
|
+
if (questionId === "preferredCheckpointIntensity" || questionId === "preferredEntryMode") {
|
|
133
|
+
return "Interaction style";
|
|
134
|
+
}
|
|
135
|
+
if (questionId === "weakestDomain" || questionId === "panelPreference") {
|
|
136
|
+
return "How Long Table should challenge you";
|
|
137
|
+
}
|
|
138
|
+
return "Authorship and voice";
|
|
139
|
+
}
|
|
140
|
+
function moveCursorUp(lines) {
|
|
141
|
+
return lines > 0 ? `\u001B[${lines}A` : "";
|
|
142
|
+
}
|
|
143
|
+
function clearLine() {
|
|
144
|
+
return "\u001B[2K\r";
|
|
145
|
+
}
|
|
146
|
+
function renderArrowMenu(prompt, choices, selectedIndex) {
|
|
147
|
+
const lines = [prompt, "Use ↑/↓ and Enter."];
|
|
148
|
+
for (let index = 0; index < choices.length; index += 1) {
|
|
149
|
+
const prefix = index === selectedIndex ? ">" : " ";
|
|
150
|
+
lines.push(`${prefix} ${choices[index].label} - ${choices[index].description}`);
|
|
151
|
+
}
|
|
152
|
+
return lines.join("\n");
|
|
153
|
+
}
|
|
154
|
+
async function promptChoiceByNumber(rl, prompt, choices) {
|
|
92
155
|
while (true) {
|
|
93
156
|
const answer = await rl.question(`${prompt}\n${renderChoices(choices)}\nSelect one number: `);
|
|
94
157
|
const numeric = Number(answer.trim());
|
|
@@ -108,10 +171,94 @@ async function promptChoice(rl, prompt, choices) {
|
|
|
108
171
|
return choice.id;
|
|
109
172
|
}
|
|
110
173
|
}
|
|
174
|
+
async function promptText(rl, prompt, required) {
|
|
175
|
+
while (true) {
|
|
176
|
+
const answer = (await rl.question(`${prompt}\n> `)).trim();
|
|
177
|
+
if (!required) {
|
|
178
|
+
return answer || undefined;
|
|
179
|
+
}
|
|
180
|
+
if (answer) {
|
|
181
|
+
return answer;
|
|
182
|
+
}
|
|
183
|
+
console.log("This answer cannot be empty.");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async function promptChoiceWithArrows(rl, prompt, choices) {
|
|
187
|
+
const stream = input;
|
|
188
|
+
if (!stream.isTTY || !output.isTTY) {
|
|
189
|
+
return promptChoiceByNumber(rl, prompt, choices);
|
|
190
|
+
}
|
|
191
|
+
const previousRawMode = stream.isRaw;
|
|
192
|
+
let selectedIndex = 0;
|
|
193
|
+
const renderedLineCount = choices.length + 2;
|
|
194
|
+
return await new Promise((resolve, reject) => {
|
|
195
|
+
function draw(first = false) {
|
|
196
|
+
if (!first) {
|
|
197
|
+
output.write(moveCursorUp(renderedLineCount));
|
|
198
|
+
}
|
|
199
|
+
const rendered = renderArrowMenu(prompt, choices, selectedIndex).split("\n");
|
|
200
|
+
for (const line of rendered) {
|
|
201
|
+
output.write(clearLine());
|
|
202
|
+
output.write(`${line}\n`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function cleanup() {
|
|
206
|
+
stream.off("keypress", onKeypress);
|
|
207
|
+
if (stream.isTTY) {
|
|
208
|
+
stream.setRawMode(previousRawMode ?? false);
|
|
209
|
+
}
|
|
210
|
+
output.write("\u001B[?25h");
|
|
211
|
+
}
|
|
212
|
+
async function handleChoice(choice) {
|
|
213
|
+
cleanup();
|
|
214
|
+
if (choice.fallbackToText) {
|
|
215
|
+
const freeText = await rl.question("Type your custom value: ");
|
|
216
|
+
if (!freeText.trim()) {
|
|
217
|
+
resolve(await promptChoiceWithArrows(rl, prompt, choices));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
resolve(freeText.trim());
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
resolve(choice.id);
|
|
224
|
+
}
|
|
225
|
+
function onKeypress(_, key) {
|
|
226
|
+
if (key.ctrl && key.name === "c") {
|
|
227
|
+
cleanup();
|
|
228
|
+
reject(new Error("Setup cancelled."));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (key.name === "up") {
|
|
232
|
+
selectedIndex = selectedIndex === 0 ? choices.length - 1 : selectedIndex - 1;
|
|
233
|
+
draw();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (key.name === "down") {
|
|
237
|
+
selectedIndex = selectedIndex === choices.length - 1 ? 0 : selectedIndex + 1;
|
|
238
|
+
draw();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (key.name === "return") {
|
|
242
|
+
void handleChoice(choices[selectedIndex]);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
emitKeypressEvents(stream);
|
|
246
|
+
stream.setRawMode(true);
|
|
247
|
+
output.write("\u001B[?25l");
|
|
248
|
+
draw(true);
|
|
249
|
+
stream.on("keypress", onKeypress);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
async function promptChoice(rl, prompt, choices) {
|
|
253
|
+
return promptChoiceWithArrows(rl, prompt, choices);
|
|
254
|
+
}
|
|
111
255
|
function hasCompleteFlagInput(args) {
|
|
112
256
|
const required = ["provider", "field", "career-stage", "experience", "project-type", "checkpoint"];
|
|
113
257
|
return required.every((key) => typeof args[key] === "string" && String(args[key]).trim().length > 0);
|
|
114
258
|
}
|
|
259
|
+
function resolveSetupFlow(args) {
|
|
260
|
+
return String(args.flow) === "interview" ? "interview" : "quickstart";
|
|
261
|
+
}
|
|
115
262
|
function toSetupAnswers(args) {
|
|
116
263
|
return {
|
|
117
264
|
field: String(args.field),
|
|
@@ -121,19 +268,43 @@ function toSetupAnswers(args) {
|
|
|
121
268
|
preferredCheckpointIntensity: String(args.checkpoint),
|
|
122
269
|
humanAuthorshipSignal: typeof args["authorship-signal"] === "string" && args["authorship-signal"].trim().length > 0
|
|
123
270
|
? args["authorship-signal"].trim()
|
|
271
|
+
: undefined,
|
|
272
|
+
currentResearchTopic: typeof args.topic === "string" && args.topic.trim().length > 0 ? args.topic.trim() : undefined,
|
|
273
|
+
currentBlocker: typeof args.blocker === "string" && args.blocker.trim().length > 0 ? args.blocker.trim() : undefined,
|
|
274
|
+
preferredEntryMode: typeof args["entry-mode"] === "string" && VALID_MODES.has(String(args["entry-mode"]))
|
|
275
|
+
? String(args["entry-mode"])
|
|
276
|
+
: undefined,
|
|
277
|
+
weakestDomain: typeof args["weakest-domain"] === "string"
|
|
278
|
+
? String(args["weakest-domain"])
|
|
279
|
+
: undefined,
|
|
280
|
+
panelPreference: typeof args["panel-preference"] === "string"
|
|
281
|
+
? String(args["panel-preference"])
|
|
124
282
|
: undefined
|
|
125
283
|
};
|
|
126
284
|
}
|
|
127
285
|
async function collectInteractiveAnswers() {
|
|
128
286
|
const rl = createInterface({ input, output });
|
|
129
287
|
try {
|
|
288
|
+
const flow = (await promptChoice(rl, "How would you like to set up Long Table?", buildSetupFlowChoices()));
|
|
289
|
+
console.log("");
|
|
290
|
+
console.log(renderSetupHeader(flow));
|
|
291
|
+
console.log("");
|
|
130
292
|
const provider = await promptChoice(rl, "Which provider do you want to configure?", buildProviderChoices());
|
|
131
293
|
const answers = {};
|
|
132
|
-
|
|
133
|
-
|
|
294
|
+
const questions = buildQuickSetupFlow(flow);
|
|
295
|
+
for (let index = 0; index < questions.length; index += 1) {
|
|
296
|
+
const question = questions[index];
|
|
297
|
+
const prompt = renderQuestionHeader(index + 1, questions.length, questionSection(question.id), question.prompt);
|
|
298
|
+
let value;
|
|
299
|
+
if (question.kind === "text") {
|
|
300
|
+
value = await promptText(rl, prompt, question.required);
|
|
301
|
+
}
|
|
302
|
+
else if (question.choices) {
|
|
303
|
+
value = await promptChoice(rl, prompt, question.choices);
|
|
304
|
+
}
|
|
305
|
+
if (!value) {
|
|
134
306
|
continue;
|
|
135
307
|
}
|
|
136
|
-
const value = await promptChoice(rl, question.prompt, question.choices);
|
|
137
308
|
if (question.id === "field")
|
|
138
309
|
answers.field = value;
|
|
139
310
|
if (question.id === "careerStage")
|
|
@@ -148,8 +319,19 @@ async function collectInteractiveAnswers() {
|
|
|
148
319
|
if (question.id === "humanAuthorshipSignal" && value !== "other") {
|
|
149
320
|
answers.humanAuthorshipSignal = value;
|
|
150
321
|
}
|
|
322
|
+
if (question.id === "currentResearchTopic")
|
|
323
|
+
answers.currentResearchTopic = value;
|
|
324
|
+
if (question.id === "currentBlocker")
|
|
325
|
+
answers.currentBlocker = value;
|
|
326
|
+
if (question.id === "preferredEntryMode")
|
|
327
|
+
answers.preferredEntryMode = value;
|
|
328
|
+
if (question.id === "weakestDomain")
|
|
329
|
+
answers.weakestDomain = value;
|
|
330
|
+
if (question.id === "panelPreference")
|
|
331
|
+
answers.panelPreference = value;
|
|
151
332
|
}
|
|
152
333
|
return {
|
|
334
|
+
flow,
|
|
153
335
|
provider,
|
|
154
336
|
answers: answers
|
|
155
337
|
};
|
|
@@ -158,6 +340,61 @@ async function collectInteractiveAnswers() {
|
|
|
158
340
|
rl.close();
|
|
159
341
|
}
|
|
160
342
|
}
|
|
343
|
+
function normalizePersistAnswers(raw) {
|
|
344
|
+
return {
|
|
345
|
+
flow: raw.flow === "interview" ? "interview" : "quickstart",
|
|
346
|
+
provider: raw.provider === "claude" ? "claude" : "codex",
|
|
347
|
+
answers: {
|
|
348
|
+
field: raw.field,
|
|
349
|
+
careerStage: raw.careerStage,
|
|
350
|
+
experienceLevel: raw.experienceLevel,
|
|
351
|
+
currentProjectType: raw.currentProjectType,
|
|
352
|
+
preferredCheckpointIntensity: raw.preferredCheckpointIntensity,
|
|
353
|
+
...(raw.humanAuthorshipSignal?.trim()
|
|
354
|
+
? { humanAuthorshipSignal: raw.humanAuthorshipSignal.trim() }
|
|
355
|
+
: {}),
|
|
356
|
+
...(raw.currentResearchTopic?.trim()
|
|
357
|
+
? { currentResearchTopic: raw.currentResearchTopic.trim() }
|
|
358
|
+
: {}),
|
|
359
|
+
...(raw.currentBlocker?.trim()
|
|
360
|
+
? { currentBlocker: raw.currentBlocker.trim() }
|
|
361
|
+
: {}),
|
|
362
|
+
...(raw.preferredEntryMode
|
|
363
|
+
? { preferredEntryMode: raw.preferredEntryMode }
|
|
364
|
+
: {}),
|
|
365
|
+
...(raw.weakestDomain
|
|
366
|
+
? { weakestDomain: raw.weakestDomain }
|
|
367
|
+
: {}),
|
|
368
|
+
...(raw.panelPreference
|
|
369
|
+
? { panelPreference: raw.panelPreference }
|
|
370
|
+
: {})
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
async function readPersistAnswers(args) {
|
|
375
|
+
if (typeof args["answers-json"] === "string") {
|
|
376
|
+
const normalized = normalizePersistAnswers(JSON.parse(args["answers-json"]));
|
|
377
|
+
return {
|
|
378
|
+
...normalized,
|
|
379
|
+
flow: typeof args.flow === "string" ? resolveSetupFlow(args) : normalized.flow
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
if (args.stdin === true) {
|
|
383
|
+
const raw = readFileSync(0, "utf8").trim();
|
|
384
|
+
if (!raw) {
|
|
385
|
+
throw new Error("No JSON was provided on stdin.");
|
|
386
|
+
}
|
|
387
|
+
return normalizePersistAnswers(JSON.parse(raw));
|
|
388
|
+
}
|
|
389
|
+
if (hasCompleteFlagInput(args)) {
|
|
390
|
+
return {
|
|
391
|
+
flow: resolveSetupFlow(args),
|
|
392
|
+
provider: String(args.provider) === "claude" ? "claude" : "codex",
|
|
393
|
+
answers: toSetupAnswers(args)
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
throw new Error("persist-init requires either --answers-json, --stdin, or the full set of setup flags.");
|
|
397
|
+
}
|
|
161
398
|
async function runInit(args) {
|
|
162
399
|
const json = args.json === true;
|
|
163
400
|
const installRuntime = args["no-install"] !== true;
|
|
@@ -165,13 +402,14 @@ async function runInit(args) {
|
|
|
165
402
|
const customPath = typeof args.path === "string" ? args.path : undefined;
|
|
166
403
|
const runtimePath = typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined;
|
|
167
404
|
const promptsDir = typeof args.dir === "string" ? args.dir : undefined;
|
|
168
|
-
const { provider, answers } = hasCompleteFlagInput(args)
|
|
405
|
+
const { flow, provider, answers } = hasCompleteFlagInput(args)
|
|
169
406
|
? {
|
|
407
|
+
flow: resolveSetupFlow(args),
|
|
170
408
|
provider: String(args.provider) === "claude" ? "claude" : "codex",
|
|
171
409
|
answers: toSetupAnswers(args)
|
|
172
410
|
}
|
|
173
411
|
: await collectInteractiveAnswers();
|
|
174
|
-
const outputValue = createPersistedSetupOutput(answers, provider);
|
|
412
|
+
const outputValue = createPersistedSetupOutput(answers, provider, flow);
|
|
175
413
|
const result = await saveSetupAndRuntimeConfig(outputValue, {
|
|
176
414
|
setupPath: customPath,
|
|
177
415
|
runtimePath
|
|
@@ -196,6 +434,20 @@ async function runInit(args) {
|
|
|
196
434
|
console.log(`- /prompts:${prompt.name}`);
|
|
197
435
|
}
|
|
198
436
|
}
|
|
437
|
+
if (provider === "codex") {
|
|
438
|
+
console.log("");
|
|
439
|
+
console.log("Next step:");
|
|
440
|
+
console.log("- Open Codex and start with `/prompts:longtable`.");
|
|
441
|
+
if (answers.currentBlocker) {
|
|
442
|
+
console.log(`- Suggested first message: ${answers.currentBlocker}`);
|
|
443
|
+
}
|
|
444
|
+
else if (answers.currentResearchTopic) {
|
|
445
|
+
console.log(`- Suggested first message: Help me open the problem around ${answers.currentResearchTopic}.`);
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
console.log("- Suggested first message: I want to start my research and need help opening the problem.");
|
|
449
|
+
}
|
|
450
|
+
}
|
|
199
451
|
}
|
|
200
452
|
async function runShow(args) {
|
|
201
453
|
const outputValue = await loadSetupOutput(typeof args.path === "string" ? args.path : undefined);
|
|
@@ -216,6 +468,50 @@ async function runInstall(args) {
|
|
|
216
468
|
}
|
|
217
469
|
console.log(renderInstallSummary(result));
|
|
218
470
|
}
|
|
471
|
+
async function runCodexPersistInit(args) {
|
|
472
|
+
const { flow, provider, answers } = await readPersistAnswers(args);
|
|
473
|
+
const outputValue = createPersistedSetupOutput(answers, provider, flow);
|
|
474
|
+
const result = await saveSetupAndRuntimeConfig(outputValue, {
|
|
475
|
+
setupPath: typeof args.path === "string" ? args.path : undefined,
|
|
476
|
+
runtimePath: typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined
|
|
477
|
+
});
|
|
478
|
+
let installedPrompts = [];
|
|
479
|
+
if (provider === "codex" && args["install-prompts"] === true) {
|
|
480
|
+
installedPrompts = await installCodexPromptAliases(typeof args.dir === "string" ? args.dir : undefined);
|
|
481
|
+
}
|
|
482
|
+
if (args.json === true) {
|
|
483
|
+
console.log(JSON.stringify({
|
|
484
|
+
setup: outputValue,
|
|
485
|
+
install: result,
|
|
486
|
+
installedPrompts: installedPrompts.map((prompt) => prompt.name)
|
|
487
|
+
}, null, 2));
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
console.log(renderSetupSummary(outputValue));
|
|
491
|
+
console.log("");
|
|
492
|
+
console.log(renderInstallSummary(result));
|
|
493
|
+
if (installedPrompts.length > 0) {
|
|
494
|
+
console.log("");
|
|
495
|
+
console.log("Installed Codex prompt aliases:");
|
|
496
|
+
for (const prompt of installedPrompts) {
|
|
497
|
+
console.log(`- /prompts:${prompt.name}`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (provider === "codex") {
|
|
501
|
+
console.log("");
|
|
502
|
+
console.log("Next step:");
|
|
503
|
+
console.log("- Start Codex and use `/prompts:longtable` for the most natural entry.");
|
|
504
|
+
if (answers.currentBlocker) {
|
|
505
|
+
console.log(`- Suggested first message: ${answers.currentBlocker}`);
|
|
506
|
+
}
|
|
507
|
+
else if (answers.currentResearchTopic) {
|
|
508
|
+
console.log(`- Suggested first message: Help me think through ${answers.currentResearchTopic}.`);
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
console.log("- Suggested first message: I want to start my research and I am not sure where to open the problem.");
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
219
515
|
async function resolvePrompt(prompt) {
|
|
220
516
|
if (prompt?.trim()) {
|
|
221
517
|
return prompt.trim();
|
|
@@ -231,6 +527,57 @@ async function resolvePrompt(prompt) {
|
|
|
231
527
|
rl.close();
|
|
232
528
|
}
|
|
233
529
|
}
|
|
530
|
+
function inferModeFromPrompt(prompt) {
|
|
531
|
+
const normalized = prompt.toLowerCase();
|
|
532
|
+
if (normalized.includes("status") ||
|
|
533
|
+
normalized.includes("설정") ||
|
|
534
|
+
normalized.includes("상태") ||
|
|
535
|
+
normalized.includes("롱테이블 상태")) {
|
|
536
|
+
return "status";
|
|
537
|
+
}
|
|
538
|
+
if (normalized.includes("패널") ||
|
|
539
|
+
normalized.includes("의견 충돌") ||
|
|
540
|
+
normalized.includes("conflict") ||
|
|
541
|
+
normalized.includes("disagree") ||
|
|
542
|
+
normalized.includes("panel")) {
|
|
543
|
+
return "panel";
|
|
544
|
+
}
|
|
545
|
+
if (normalized.includes("결정") ||
|
|
546
|
+
normalized.includes("고를") ||
|
|
547
|
+
normalized.includes("선택") ||
|
|
548
|
+
normalized.includes("commit")) {
|
|
549
|
+
return "commit";
|
|
550
|
+
}
|
|
551
|
+
if (normalized.includes("초안") ||
|
|
552
|
+
normalized.includes("써줘") ||
|
|
553
|
+
normalized.includes("문단") ||
|
|
554
|
+
normalized.includes("draft") ||
|
|
555
|
+
normalized.includes("write")) {
|
|
556
|
+
return "draft";
|
|
557
|
+
}
|
|
558
|
+
if (normalized.includes("비판") ||
|
|
559
|
+
normalized.includes("반론") ||
|
|
560
|
+
normalized.includes("critique") ||
|
|
561
|
+
normalized.includes("challenge")) {
|
|
562
|
+
return "critique";
|
|
563
|
+
}
|
|
564
|
+
if (normalized.includes("검토") ||
|
|
565
|
+
normalized.includes("review") ||
|
|
566
|
+
normalized.includes("편집자") ||
|
|
567
|
+
normalized.includes("리뷰어") ||
|
|
568
|
+
normalized.includes("judge")) {
|
|
569
|
+
return "review";
|
|
570
|
+
}
|
|
571
|
+
return "explore";
|
|
572
|
+
}
|
|
573
|
+
async function loadOptionalSetup(path) {
|
|
574
|
+
try {
|
|
575
|
+
return await loadSetupOutput(path);
|
|
576
|
+
}
|
|
577
|
+
catch {
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
234
581
|
async function runModeCommand(mode, args) {
|
|
235
582
|
const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
|
|
236
583
|
if (!prompt) {
|
|
@@ -240,10 +587,16 @@ async function runModeCommand(mode, args) {
|
|
|
240
587
|
if (stage && !VALID_STAGES.has(stage)) {
|
|
241
588
|
throw new Error(`Invalid stage: ${stage}`);
|
|
242
589
|
}
|
|
590
|
+
const setup = await loadOptionalSetup(typeof args.setup === "string" ? args.setup : undefined);
|
|
591
|
+
const panelPreference = setup?.profileSeed.panelPreference;
|
|
592
|
+
const panelRequested = args.panel === true ||
|
|
593
|
+
panelPreference === "always_visible" ||
|
|
594
|
+
(panelPreference === "show_on_conflict" && args["show-conflicts"] === true);
|
|
243
595
|
const { guidedPrompt } = buildPersonaGuidance({
|
|
596
|
+
mode,
|
|
244
597
|
prompt,
|
|
245
598
|
roleFlag: typeof args.role === "string" ? args.role : undefined,
|
|
246
|
-
panel:
|
|
599
|
+
panel: panelRequested,
|
|
247
600
|
showConflicts: args["show-conflicts"] === true,
|
|
248
601
|
showDeliberation: args["show-deliberation"] === true
|
|
249
602
|
});
|
|
@@ -268,6 +621,27 @@ async function runModeCommand(mode, args) {
|
|
|
268
621
|
});
|
|
269
622
|
exit(exitCode);
|
|
270
623
|
}
|
|
624
|
+
async function runAsk(args) {
|
|
625
|
+
const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
|
|
626
|
+
if (!prompt) {
|
|
627
|
+
throw new Error("A prompt is required.");
|
|
628
|
+
}
|
|
629
|
+
const inferred = inferModeFromPrompt(prompt);
|
|
630
|
+
if (inferred === "status") {
|
|
631
|
+
await runCodexSubcommand("status", args);
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
const mode = inferred === "panel" ? "review" : inferred;
|
|
635
|
+
const delegatedArgs = {
|
|
636
|
+
...args,
|
|
637
|
+
prompt
|
|
638
|
+
};
|
|
639
|
+
if (inferred === "panel" && delegatedArgs.panel !== true) {
|
|
640
|
+
delegatedArgs.panel = true;
|
|
641
|
+
delegatedArgs["show-conflicts"] = true;
|
|
642
|
+
}
|
|
643
|
+
await runModeCommand(mode, delegatedArgs);
|
|
644
|
+
}
|
|
271
645
|
async function runCodexSubcommand(subcommand, args) {
|
|
272
646
|
const customDir = typeof args.dir === "string" ? args.dir : undefined;
|
|
273
647
|
if (subcommand === "install-prompts") {
|
|
@@ -278,6 +652,10 @@ async function runCodexSubcommand(subcommand, args) {
|
|
|
278
652
|
}
|
|
279
653
|
return;
|
|
280
654
|
}
|
|
655
|
+
if (subcommand === "persist-init") {
|
|
656
|
+
await runCodexPersistInit(args);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
281
659
|
if (subcommand === "remove-prompts") {
|
|
282
660
|
const removed = await removeCodexPromptAliases(customDir);
|
|
283
661
|
console.log(`Removed ${removed.length} Long Table prompt aliases from ${resolveCodexPromptsDir(customDir)}`);
|
|
@@ -335,6 +713,10 @@ async function main() {
|
|
|
335
713
|
await runInstall(values);
|
|
336
714
|
return;
|
|
337
715
|
}
|
|
716
|
+
if (command === "ask") {
|
|
717
|
+
await runAsk(values);
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
338
720
|
if (command === "codex") {
|
|
339
721
|
await runCodexSubcommand(subcommand, values);
|
|
340
722
|
return;
|
|
@@ -346,7 +728,11 @@ async function main() {
|
|
|
346
728
|
throw new Error(`Unknown command: ${command}`);
|
|
347
729
|
}
|
|
348
730
|
main().catch((error) => {
|
|
349
|
-
|
|
731
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
732
|
+
console.error(message);
|
|
733
|
+
if (message === "Setup cancelled.") {
|
|
734
|
+
exit(1);
|
|
735
|
+
}
|
|
350
736
|
console.error("");
|
|
351
737
|
console.error(usage());
|
|
352
738
|
exit(1);
|
package/dist/persona-router.d.ts
CHANGED
|
@@ -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;
|
package/dist/persona-router.js
CHANGED
|
@@ -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"),
|
package/dist/prompt-aliases.js
CHANGED
|
@@ -7,18 +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
|
-
"
|
|
20
|
-
"
|
|
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.",
|
|
39
|
+
"If the user prefers paste-based setup, tell them they can pipe the JSON into `longtable codex persist-init --stdin --install-prompts`.",
|
|
21
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.",
|
|
22
42
|
"Treat any slash-command arguments as context for why setup is being done now."
|
|
23
43
|
]
|
|
24
44
|
},
|
|
@@ -28,9 +48,11 @@ function promptSpec() {
|
|
|
28
48
|
argumentHint: "<topic or research problem>",
|
|
29
49
|
body: [
|
|
30
50
|
"You are Long Table in explore mode.",
|
|
51
|
+
"Always begin with `Long Table mode: Explore`.",
|
|
31
52
|
"Ask at least two clarifying or tension questions before any recommendation.",
|
|
32
53
|
"Keep unresolved tensions visible.",
|
|
33
54
|
"Do not rush to synthesis.",
|
|
55
|
+
"Do not expose internal process notes or file-search summaries.",
|
|
34
56
|
"Treat any slash-command arguments as the current research object."
|
|
35
57
|
]
|
|
36
58
|
},
|
|
@@ -40,6 +62,7 @@ function promptSpec() {
|
|
|
40
62
|
argumentHint: "<claim, paragraph, design, or plan>",
|
|
41
63
|
body: [
|
|
42
64
|
"You are Long Table in review mode.",
|
|
65
|
+
"Always begin with `Long Table mode: Review`.",
|
|
43
66
|
"Surface why this may be wrong before synthesis.",
|
|
44
67
|
"Preserve the researcher's own language where possible.",
|
|
45
68
|
"Treat any slash-command arguments as the object to review."
|
|
@@ -51,6 +74,7 @@ function promptSpec() {
|
|
|
51
74
|
argumentHint: "<claim, plan, or draft for multi-role review>",
|
|
52
75
|
body: [
|
|
53
76
|
"You are Long Table in panel mode.",
|
|
77
|
+
"Always begin with `Long Table mode: Panel`.",
|
|
54
78
|
"Return 1) a Long Table synthesis 2) visible panel opinions by role 3) a decision prompt for the researcher.",
|
|
55
79
|
"If roles disagree, do not collapse them too early.",
|
|
56
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.
|
|
3
|
+
"version": "0.1.2",
|
|
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.
|
|
29
|
+
"@diverga/setup": "0.1.2"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^22.10.1",
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"license": "MIT",
|
|
44
44
|
"repository": {
|
|
45
45
|
"type": "git",
|
|
46
|
-
"url": "git+https://github.com/HosungYou/
|
|
46
|
+
"url": "git+https://github.com/HosungYou/Long-Table-Refactoring.git"
|
|
47
47
|
},
|
|
48
|
-
"homepage": "https://github.com/HosungYou/
|
|
48
|
+
"homepage": "https://github.com/HosungYou/Long-Table-Refactoring#readme",
|
|
49
49
|
"publishConfig": {
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|