@longtable/cli 0.1.0 → 0.1.1
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 +23 -13
- package/bin/longtable +0 -0
- package/dist/cli.js +155 -1
- package/dist/prompt-aliases.js +2 -1
- package/package.json +3 -3
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
|
|
@@ -32,6 +28,8 @@ Run setup once and install Codex prompt aliases:
|
|
|
32
28
|
longtable init --install-prompts
|
|
33
29
|
```
|
|
34
30
|
|
|
31
|
+
`longtable init` now uses an arrow-key terminal menu instead of plain number-entry prompts.
|
|
32
|
+
|
|
35
33
|
Then you can work in two ways.
|
|
36
34
|
|
|
37
35
|
From the terminal:
|
|
@@ -71,6 +69,18 @@ Install Codex prompt aliases:
|
|
|
71
69
|
longtable codex install-prompts
|
|
72
70
|
```
|
|
73
71
|
|
|
72
|
+
If you finish onboarding inside Codex with `/prompts:longtable-init`, you can persist the collected answers with:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
longtable codex persist-init --provider codex --field education --career-stage doctoral --experience intermediate --project-type "journal article" --checkpoint balanced --install-prompts
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Or from a JSON block:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pbpaste | longtable codex persist-init --stdin --install-prompts
|
|
82
|
+
```
|
|
83
|
+
|
|
74
84
|
Check whether setup and aliases are present:
|
|
75
85
|
|
|
76
86
|
```bash
|
|
@@ -86,6 +96,6 @@ longtable codex remove-prompts
|
|
|
86
96
|
## Why this package exists
|
|
87
97
|
|
|
88
98
|
Long Table is the product name.
|
|
89
|
-
`Diverga`
|
|
99
|
+
`Diverga` remains an internal compatibility layer for some lower-level packages and runtime paths.
|
|
90
100
|
|
|
91
|
-
This package
|
|
101
|
+
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";
|
|
@@ -30,6 +31,7 @@ function usage() {
|
|
|
30
31
|
" longtable show [--json] [--path <file>]",
|
|
31
32
|
" longtable install [--json] [--path <file>] [--runtime-path <file>]",
|
|
32
33
|
" 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
|
+
" longtable codex persist-init [--answers-json <json> | --stdin | full setup flags] [--install-prompts] [--json]",
|
|
33
35
|
" longtable codex install-prompts [--dir <path>]",
|
|
34
36
|
" longtable codex remove-prompts [--dir <path>]",
|
|
35
37
|
" longtable codex status [--dir <path>] [--json]",
|
|
@@ -38,6 +40,7 @@ function usage() {
|
|
|
38
40
|
" longtable init --install-prompts",
|
|
39
41
|
" longtable review --prompt \"Review this claim critically.\" --panel --show-conflicts",
|
|
40
42
|
" longtable review --role editor --prompt \"BJET 편집자 관점에서 봐줘.\"",
|
|
43
|
+
" printf '{\"provider\":\"codex\",...}' | longtable codex persist-init --stdin --install-prompts",
|
|
41
44
|
" longtable codex install-prompts"
|
|
42
45
|
].join("\n");
|
|
43
46
|
}
|
|
@@ -88,7 +91,21 @@ function renderChoices(choices) {
|
|
|
88
91
|
.map((choice, index) => `${index + 1}. ${choice.label} — ${choice.description}`)
|
|
89
92
|
.join("\n");
|
|
90
93
|
}
|
|
91
|
-
|
|
94
|
+
function moveCursorUp(lines) {
|
|
95
|
+
return lines > 0 ? `\u001B[${lines}A` : "";
|
|
96
|
+
}
|
|
97
|
+
function clearLine() {
|
|
98
|
+
return "\u001B[2K\r";
|
|
99
|
+
}
|
|
100
|
+
function renderArrowMenu(prompt, choices, selectedIndex) {
|
|
101
|
+
const lines = [prompt, "Use ↑/↓ and Enter."];
|
|
102
|
+
for (let index = 0; index < choices.length; index += 1) {
|
|
103
|
+
const prefix = index === selectedIndex ? ">" : " ";
|
|
104
|
+
lines.push(`${prefix} ${choices[index].label} - ${choices[index].description}`);
|
|
105
|
+
}
|
|
106
|
+
return lines.join("\n");
|
|
107
|
+
}
|
|
108
|
+
async function promptChoiceByNumber(rl, prompt, choices) {
|
|
92
109
|
while (true) {
|
|
93
110
|
const answer = await rl.question(`${prompt}\n${renderChoices(choices)}\nSelect one number: `);
|
|
94
111
|
const numeric = Number(answer.trim());
|
|
@@ -108,6 +125,75 @@ async function promptChoice(rl, prompt, choices) {
|
|
|
108
125
|
return choice.id;
|
|
109
126
|
}
|
|
110
127
|
}
|
|
128
|
+
async function promptChoiceWithArrows(rl, prompt, choices) {
|
|
129
|
+
const stream = input;
|
|
130
|
+
if (!stream.isTTY || !output.isTTY) {
|
|
131
|
+
return promptChoiceByNumber(rl, prompt, choices);
|
|
132
|
+
}
|
|
133
|
+
const previousRawMode = stream.isRaw;
|
|
134
|
+
let selectedIndex = 0;
|
|
135
|
+
const renderedLineCount = choices.length + 2;
|
|
136
|
+
return await new Promise((resolve, reject) => {
|
|
137
|
+
function draw(first = false) {
|
|
138
|
+
if (!first) {
|
|
139
|
+
output.write(moveCursorUp(renderedLineCount));
|
|
140
|
+
}
|
|
141
|
+
const rendered = renderArrowMenu(prompt, choices, selectedIndex).split("\n");
|
|
142
|
+
for (const line of rendered) {
|
|
143
|
+
output.write(clearLine());
|
|
144
|
+
output.write(`${line}\n`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function cleanup() {
|
|
148
|
+
stream.off("keypress", onKeypress);
|
|
149
|
+
if (stream.isTTY) {
|
|
150
|
+
stream.setRawMode(previousRawMode ?? false);
|
|
151
|
+
}
|
|
152
|
+
output.write("\u001B[?25h");
|
|
153
|
+
}
|
|
154
|
+
async function handleChoice(choice) {
|
|
155
|
+
cleanup();
|
|
156
|
+
if (choice.fallbackToText) {
|
|
157
|
+
const freeText = await rl.question("Type your custom value: ");
|
|
158
|
+
if (!freeText.trim()) {
|
|
159
|
+
resolve(await promptChoiceWithArrows(rl, prompt, choices));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
resolve(freeText.trim());
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
resolve(choice.id);
|
|
166
|
+
}
|
|
167
|
+
function onKeypress(_, key) {
|
|
168
|
+
if (key.ctrl && key.name === "c") {
|
|
169
|
+
cleanup();
|
|
170
|
+
reject(new Error("Setup cancelled."));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (key.name === "up") {
|
|
174
|
+
selectedIndex = selectedIndex === 0 ? choices.length - 1 : selectedIndex - 1;
|
|
175
|
+
draw();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (key.name === "down") {
|
|
179
|
+
selectedIndex = selectedIndex === choices.length - 1 ? 0 : selectedIndex + 1;
|
|
180
|
+
draw();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (key.name === "return") {
|
|
184
|
+
void handleChoice(choices[selectedIndex]);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
emitKeypressEvents(stream);
|
|
188
|
+
stream.setRawMode(true);
|
|
189
|
+
output.write("\u001B[?25l");
|
|
190
|
+
draw(true);
|
|
191
|
+
stream.on("keypress", onKeypress);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
async function promptChoice(rl, prompt, choices) {
|
|
195
|
+
return promptChoiceWithArrows(rl, prompt, choices);
|
|
196
|
+
}
|
|
111
197
|
function hasCompleteFlagInput(args) {
|
|
112
198
|
const required = ["provider", "field", "career-stage", "experience", "project-type", "checkpoint"];
|
|
113
199
|
return required.every((key) => typeof args[key] === "string" && String(args[key]).trim().length > 0);
|
|
@@ -158,6 +244,40 @@ async function collectInteractiveAnswers() {
|
|
|
158
244
|
rl.close();
|
|
159
245
|
}
|
|
160
246
|
}
|
|
247
|
+
function normalizePersistAnswers(raw) {
|
|
248
|
+
return {
|
|
249
|
+
provider: raw.provider === "claude" ? "claude" : "codex",
|
|
250
|
+
answers: {
|
|
251
|
+
field: raw.field,
|
|
252
|
+
careerStage: raw.careerStage,
|
|
253
|
+
experienceLevel: raw.experienceLevel,
|
|
254
|
+
currentProjectType: raw.currentProjectType,
|
|
255
|
+
preferredCheckpointIntensity: raw.preferredCheckpointIntensity,
|
|
256
|
+
...(raw.humanAuthorshipSignal?.trim()
|
|
257
|
+
? { humanAuthorshipSignal: raw.humanAuthorshipSignal.trim() }
|
|
258
|
+
: {})
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
async function readPersistAnswers(args) {
|
|
263
|
+
if (typeof args["answers-json"] === "string") {
|
|
264
|
+
return normalizePersistAnswers(JSON.parse(args["answers-json"]));
|
|
265
|
+
}
|
|
266
|
+
if (args.stdin === true) {
|
|
267
|
+
const raw = readFileSync(0, "utf8").trim();
|
|
268
|
+
if (!raw) {
|
|
269
|
+
throw new Error("No JSON was provided on stdin.");
|
|
270
|
+
}
|
|
271
|
+
return normalizePersistAnswers(JSON.parse(raw));
|
|
272
|
+
}
|
|
273
|
+
if (hasCompleteFlagInput(args)) {
|
|
274
|
+
return {
|
|
275
|
+
provider: String(args.provider) === "claude" ? "claude" : "codex",
|
|
276
|
+
answers: toSetupAnswers(args)
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
throw new Error("persist-init requires either --answers-json, --stdin, or the full set of setup flags.");
|
|
280
|
+
}
|
|
161
281
|
async function runInit(args) {
|
|
162
282
|
const json = args.json === true;
|
|
163
283
|
const installRuntime = args["no-install"] !== true;
|
|
@@ -216,6 +336,36 @@ async function runInstall(args) {
|
|
|
216
336
|
}
|
|
217
337
|
console.log(renderInstallSummary(result));
|
|
218
338
|
}
|
|
339
|
+
async function runCodexPersistInit(args) {
|
|
340
|
+
const { provider, answers } = await readPersistAnswers(args);
|
|
341
|
+
const outputValue = createPersistedSetupOutput(answers, provider);
|
|
342
|
+
const result = await saveSetupAndRuntimeConfig(outputValue, {
|
|
343
|
+
setupPath: typeof args.path === "string" ? args.path : undefined,
|
|
344
|
+
runtimePath: typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined
|
|
345
|
+
});
|
|
346
|
+
let installedPrompts = [];
|
|
347
|
+
if (provider === "codex" && args["install-prompts"] === true) {
|
|
348
|
+
installedPrompts = await installCodexPromptAliases(typeof args.dir === "string" ? args.dir : undefined);
|
|
349
|
+
}
|
|
350
|
+
if (args.json === true) {
|
|
351
|
+
console.log(JSON.stringify({
|
|
352
|
+
setup: outputValue,
|
|
353
|
+
install: result,
|
|
354
|
+
installedPrompts: installedPrompts.map((prompt) => prompt.name)
|
|
355
|
+
}, null, 2));
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
console.log(renderSetupSummary(outputValue));
|
|
359
|
+
console.log("");
|
|
360
|
+
console.log(renderInstallSummary(result));
|
|
361
|
+
if (installedPrompts.length > 0) {
|
|
362
|
+
console.log("");
|
|
363
|
+
console.log("Installed Codex prompt aliases:");
|
|
364
|
+
for (const prompt of installedPrompts) {
|
|
365
|
+
console.log(`- /prompts:${prompt.name}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
219
369
|
async function resolvePrompt(prompt) {
|
|
220
370
|
if (prompt?.trim()) {
|
|
221
371
|
return prompt.trim();
|
|
@@ -278,6 +428,10 @@ async function runCodexSubcommand(subcommand, args) {
|
|
|
278
428
|
}
|
|
279
429
|
return;
|
|
280
430
|
}
|
|
431
|
+
if (subcommand === "persist-init") {
|
|
432
|
+
await runCodexPersistInit(args);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
281
435
|
if (subcommand === "remove-prompts") {
|
|
282
436
|
const removed = await removeCodexPromptAliases(customDir);
|
|
283
437
|
console.log(`Removed ${removed.length} Long Table prompt aliases from ${resolveCodexPromptsDir(customDir)}`);
|
package/dist/prompt-aliases.js
CHANGED
|
@@ -17,7 +17,8 @@ function promptSpec() {
|
|
|
17
17
|
"Use numbered choices when possible and include a 'None of the above' option when needed.",
|
|
18
18
|
"Do not move to the next question until the researcher answers the current one.",
|
|
19
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
|
|
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.",
|
|
21
|
+
"If the user prefers paste-based setup, tell them they can pipe the JSON into `longtable codex persist-init --stdin --install-prompts`.",
|
|
21
22
|
"If the researcher asks you to stay inside Codex, keep the conversation in numbered form and do not prematurely close.",
|
|
22
23
|
"Treat any slash-command arguments as context for why setup is being done now."
|
|
23
24
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longtable/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Researcher-facing Long Table CLI on top of the legacy Diverga package surface",
|
|
6
6
|
"type": "module",
|
|
@@ -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
|
},
|