@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 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
- 로컬 preview:
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 -g @longtable/cli
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` is still the legacy technical identifier for package names, CLI binaries, and runtime paths.
99
+ `Diverga` remains an internal compatibility layer for some lower-level packages and runtime paths.
90
100
 
91
- This package creates a researcher-facing command surface now, without forcing a breaking rename of the published package chain yet.
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
- async function promptChoice(rl, prompt, choices) {
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)}`);
@@ -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 tell the researcher the exact `longtable init ...` command to persist it.",
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.0",
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/Diverga-Refactoring.git"
46
+ "url": "git+https://github.com/HosungYou/Long-Table-Refactoring.git"
47
47
  },
48
- "homepage": "https://github.com/HosungYou/Diverga-Refactoring#readme",
48
+ "homepage": "https://github.com/HosungYou/Long-Table-Refactoring#readme",
49
49
  "publishConfig": {
50
50
  "access": "public"
51
51
  },