@longtable/cli 0.1.0

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 ADDED
@@ -0,0 +1,91 @@
1
+ # @longtable/cli
2
+
3
+ Researcher-facing Long Table CLI built on top of the legacy `@diverga/*` package surface.
4
+
5
+ 중요한 현재 상태:
6
+
7
+ - `@longtable/cli`는 repo 안에서는 구현되어 있음
8
+ - npm의 `@longtable` scope가 아직 준비되지 않아 실제 publish는 아직 안 됨
9
+ - 지금 공개 설치 경로는 여전히 `@diverga/setup`, `@diverga/provider-codex`
10
+
11
+ ## Install
12
+
13
+ 로컬 preview:
14
+
15
+ ```bash
16
+ npm install
17
+ npm run build
18
+ node packages/longtable/dist/cli.js --help
19
+ ```
20
+
21
+ 예상 공개 설치 경로:
22
+
23
+ ```bash
24
+ npm install -g @longtable/cli
25
+ ```
26
+
27
+ ## Recommended flow
28
+
29
+ Run setup once and install Codex prompt aliases:
30
+
31
+ ```bash
32
+ longtable init --install-prompts
33
+ ```
34
+
35
+ Then you can work in two ways.
36
+
37
+ From the terminal:
38
+
39
+ ```bash
40
+ longtable review --prompt "Review this claim critically."
41
+ longtable review --prompt "BJET 편집자 관점에서 봐줘." --role editor
42
+ longtable review --prompt "방법론적으로 어디가 취약한지 말해줘." --panel --show-conflicts
43
+ ```
44
+
45
+ Inside Codex after prompt aliases are installed:
46
+
47
+ - `/prompts:longtable-init`
48
+ - `/prompts:longtable-explore`
49
+ - `/prompts:longtable-review`
50
+ - `/prompts:longtable-critique`
51
+ - `/prompts:longtable-draft`
52
+ - `/prompts:longtable-commit`
53
+ - `/prompts:longtable-status`
54
+
55
+ ## Core commands
56
+
57
+ ```bash
58
+ longtable init
59
+ longtable show --json
60
+ longtable install --json
61
+ longtable explore --prompt "Help me stay exploratory."
62
+ longtable review --prompt "Review this claim critically."
63
+ longtable commit --prompt "Help me make this decision carefully."
64
+ ```
65
+
66
+ ## Codex overlay
67
+
68
+ Install Codex prompt aliases:
69
+
70
+ ```bash
71
+ longtable codex install-prompts
72
+ ```
73
+
74
+ Check whether setup and aliases are present:
75
+
76
+ ```bash
77
+ longtable codex status
78
+ ```
79
+
80
+ Remove the aliases:
81
+
82
+ ```bash
83
+ longtable codex remove-prompts
84
+ ```
85
+
86
+ ## Why this package exists
87
+
88
+ Long Table is the product name.
89
+ `Diverga` is still the legacy technical identifier for package names, CLI binaries, and runtime paths.
90
+
91
+ This package creates a researcher-facing command surface now, without forcing a breaking rename of the published package chain yet.
package/bin/longtable ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import("../dist/cli.js").catch((error) => {
3
+ console.error(error instanceof Error ? error.message : String(error));
4
+ process.exit(1);
5
+ });
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,353 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { createInterface } from "node:readline/promises";
4
+ import { stdin as input, stdout as output, cwd, exit } from "node:process";
5
+ import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput, installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultRuntimeConfigPath, resolveDefaultSetupPath, saveSetupAndRuntimeConfig, serializeSetupOutput } from "@diverga/setup";
6
+ import { buildCodexThinWrappedPrompt, runCodexThinWrapper } from "@diverga/provider-codex";
7
+ import { installCodexPromptAliases, listInstalledCodexPromptAliases, removeCodexPromptAliases, resolveCodexPromptsDir } from "./prompt-aliases.js";
8
+ import { buildPersonaGuidance } from "./persona-router.js";
9
+ const VALID_MODES = new Set([
10
+ "explore",
11
+ "review",
12
+ "critique",
13
+ "draft",
14
+ "commit",
15
+ "submit"
16
+ ]);
17
+ const VALID_STAGES = new Set([
18
+ "problem_framing",
19
+ "theory_selection",
20
+ "method_design",
21
+ "measurement_design",
22
+ "analysis_planning",
23
+ "writing",
24
+ "submission"
25
+ ]);
26
+ function usage() {
27
+ return [
28
+ "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 show [--json] [--path <file>]",
31
+ " longtable install [--json] [--path <file>] [--runtime-path <file>]",
32
+ " 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>]",
33
+ " longtable codex install-prompts [--dir <path>]",
34
+ " longtable codex remove-prompts [--dir <path>]",
35
+ " longtable codex status [--dir <path>] [--json]",
36
+ "",
37
+ "Examples:",
38
+ " longtable init --install-prompts",
39
+ " longtable review --prompt \"Review this claim critically.\" --panel --show-conflicts",
40
+ " longtable review --role editor --prompt \"BJET 편집자 관점에서 봐줘.\"",
41
+ " longtable codex install-prompts"
42
+ ].join("\n");
43
+ }
44
+ function parseArgs(argv) {
45
+ const [command, maybeSubcommand] = argv;
46
+ const values = {};
47
+ let subcommand = maybeSubcommand;
48
+ const modeCommand = command && VALID_MODES.has(command);
49
+ const directCommand = command && ["init", "show", "install", "codex"].includes(command);
50
+ let startIndex = 1;
51
+ if (modeCommand) {
52
+ subcommand = undefined;
53
+ startIndex = 1;
54
+ }
55
+ else if (command === "codex") {
56
+ startIndex = 2;
57
+ }
58
+ else if (directCommand) {
59
+ subcommand = undefined;
60
+ startIndex = 1;
61
+ }
62
+ else if (!command || command === "--help") {
63
+ return { values };
64
+ }
65
+ else {
66
+ startIndex = 1;
67
+ subcommand = undefined;
68
+ }
69
+ const tokens = argv.slice(startIndex);
70
+ for (let index = 0; index < tokens.length; index += 1) {
71
+ const token = tokens[index];
72
+ if (!token.startsWith("--")) {
73
+ continue;
74
+ }
75
+ const key = token.slice(2);
76
+ const next = tokens[index + 1];
77
+ if (!next || next.startsWith("--")) {
78
+ values[key] = true;
79
+ continue;
80
+ }
81
+ values[key] = next;
82
+ index += 1;
83
+ }
84
+ return { command, subcommand, values };
85
+ }
86
+ function renderChoices(choices) {
87
+ return choices
88
+ .map((choice, index) => `${index + 1}. ${choice.label} — ${choice.description}`)
89
+ .join("\n");
90
+ }
91
+ async function promptChoice(rl, prompt, choices) {
92
+ while (true) {
93
+ const answer = await rl.question(`${prompt}\n${renderChoices(choices)}\nSelect one number: `);
94
+ const numeric = Number(answer.trim());
95
+ if (!Number.isInteger(numeric) || numeric < 1 || numeric > choices.length) {
96
+ console.log("Invalid selection. Enter one of the listed numbers.");
97
+ continue;
98
+ }
99
+ const choice = choices[numeric - 1];
100
+ if (choice.fallbackToText) {
101
+ const freeText = await rl.question("Type your custom value: ");
102
+ if (!freeText.trim()) {
103
+ console.log("Custom value cannot be empty.");
104
+ continue;
105
+ }
106
+ return freeText.trim();
107
+ }
108
+ return choice.id;
109
+ }
110
+ }
111
+ function hasCompleteFlagInput(args) {
112
+ const required = ["provider", "field", "career-stage", "experience", "project-type", "checkpoint"];
113
+ return required.every((key) => typeof args[key] === "string" && String(args[key]).trim().length > 0);
114
+ }
115
+ function toSetupAnswers(args) {
116
+ return {
117
+ field: String(args.field),
118
+ careerStage: String(args["career-stage"]),
119
+ experienceLevel: String(args.experience),
120
+ currentProjectType: String(args["project-type"]),
121
+ preferredCheckpointIntensity: String(args.checkpoint),
122
+ humanAuthorshipSignal: typeof args["authorship-signal"] === "string" && args["authorship-signal"].trim().length > 0
123
+ ? args["authorship-signal"].trim()
124
+ : undefined
125
+ };
126
+ }
127
+ async function collectInteractiveAnswers() {
128
+ const rl = createInterface({ input, output });
129
+ try {
130
+ const provider = await promptChoice(rl, "Which provider do you want to configure?", buildProviderChoices());
131
+ const answers = {};
132
+ for (const question of buildQuickSetupFlow()) {
133
+ if (!question.choices) {
134
+ continue;
135
+ }
136
+ const value = await promptChoice(rl, question.prompt, question.choices);
137
+ if (question.id === "field")
138
+ answers.field = value;
139
+ if (question.id === "careerStage")
140
+ answers.careerStage = value;
141
+ if (question.id === "experienceLevel")
142
+ answers.experienceLevel = value;
143
+ if (question.id === "currentProjectType")
144
+ answers.currentProjectType = value;
145
+ if (question.id === "preferredCheckpointIntensity") {
146
+ answers.preferredCheckpointIntensity = value;
147
+ }
148
+ if (question.id === "humanAuthorshipSignal" && value !== "other") {
149
+ answers.humanAuthorshipSignal = value;
150
+ }
151
+ }
152
+ return {
153
+ provider,
154
+ answers: answers
155
+ };
156
+ }
157
+ finally {
158
+ rl.close();
159
+ }
160
+ }
161
+ async function runInit(args) {
162
+ const json = args.json === true;
163
+ const installRuntime = args["no-install"] !== true;
164
+ const installPrompts = args["install-prompts"] === true;
165
+ const customPath = typeof args.path === "string" ? args.path : undefined;
166
+ const runtimePath = typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined;
167
+ const promptsDir = typeof args.dir === "string" ? args.dir : undefined;
168
+ const { provider, answers } = hasCompleteFlagInput(args)
169
+ ? {
170
+ provider: String(args.provider) === "claude" ? "claude" : "codex",
171
+ answers: toSetupAnswers(args)
172
+ }
173
+ : await collectInteractiveAnswers();
174
+ const outputValue = createPersistedSetupOutput(answers, provider);
175
+ const result = await saveSetupAndRuntimeConfig(outputValue, {
176
+ setupPath: customPath,
177
+ runtimePath
178
+ });
179
+ let installedPrompts = [];
180
+ if (provider === "codex" && installPrompts) {
181
+ installedPrompts = await installCodexPromptAliases(promptsDir);
182
+ }
183
+ if (json) {
184
+ console.log(serializeSetupOutput(outputValue));
185
+ return;
186
+ }
187
+ console.log(renderSetupSummary(outputValue));
188
+ if (installRuntime) {
189
+ console.log("");
190
+ console.log(renderInstallSummary(result));
191
+ }
192
+ if (installedPrompts.length > 0) {
193
+ console.log("");
194
+ console.log("Installed Codex prompt aliases:");
195
+ for (const prompt of installedPrompts) {
196
+ console.log(`- /prompts:${prompt.name}`);
197
+ }
198
+ }
199
+ }
200
+ async function runShow(args) {
201
+ const outputValue = await loadSetupOutput(typeof args.path === "string" ? args.path : undefined);
202
+ if (args.json === true) {
203
+ console.log(serializeSetupOutput(outputValue));
204
+ return;
205
+ }
206
+ console.log(renderSetupSummary(outputValue));
207
+ }
208
+ async function runInstall(args) {
209
+ const result = await installRuntimeConfigFromStoredSetup({
210
+ setupPath: typeof args.path === "string" ? args.path : undefined,
211
+ runtimePath: typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined
212
+ });
213
+ if (args.json === true) {
214
+ console.log(JSON.stringify(result, null, 2));
215
+ return;
216
+ }
217
+ console.log(renderInstallSummary(result));
218
+ }
219
+ async function resolvePrompt(prompt) {
220
+ if (prompt?.trim()) {
221
+ return prompt.trim();
222
+ }
223
+ if (!process.stdin.isTTY) {
224
+ return readFileSync(0, "utf8").trim();
225
+ }
226
+ const rl = createInterface({ input, output });
227
+ try {
228
+ return (await rl.question("What should Long Table help with?\n> ")).trim();
229
+ }
230
+ finally {
231
+ rl.close();
232
+ }
233
+ }
234
+ async function runModeCommand(mode, args) {
235
+ const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
236
+ if (!prompt) {
237
+ throw new Error("A prompt is required.");
238
+ }
239
+ const stage = typeof args.stage === "string" ? args.stage : undefined;
240
+ if (stage && !VALID_STAGES.has(stage)) {
241
+ throw new Error(`Invalid stage: ${stage}`);
242
+ }
243
+ const { guidedPrompt } = buildPersonaGuidance({
244
+ prompt,
245
+ roleFlag: typeof args.role === "string" ? args.role : undefined,
246
+ panel: args.panel === true,
247
+ showConflicts: args["show-conflicts"] === true,
248
+ showDeliberation: args["show-deliberation"] === true
249
+ });
250
+ if (args.print === true) {
251
+ const wrapped = await buildCodexThinWrappedPrompt({
252
+ prompt: guidedPrompt,
253
+ mode,
254
+ researchStage: stage,
255
+ setupPath: typeof args.setup === "string" ? args.setup : undefined,
256
+ workingDirectory: typeof args.cwd === "string" ? args.cwd : cwd()
257
+ });
258
+ console.log(wrapped.wrappedPrompt);
259
+ return;
260
+ }
261
+ const exitCode = await runCodexThinWrapper({
262
+ prompt: guidedPrompt,
263
+ mode,
264
+ researchStage: stage,
265
+ setupPath: typeof args.setup === "string" ? args.setup : undefined,
266
+ workingDirectory: typeof args.cwd === "string" ? args.cwd : cwd(),
267
+ json: args.json === true
268
+ });
269
+ exit(exitCode);
270
+ }
271
+ async function runCodexSubcommand(subcommand, args) {
272
+ const customDir = typeof args.dir === "string" ? args.dir : undefined;
273
+ if (subcommand === "install-prompts") {
274
+ const installed = await installCodexPromptAliases(customDir);
275
+ console.log(`Installed ${installed.length} Long Table prompt aliases in ${resolveCodexPromptsDir(customDir)}`);
276
+ for (const prompt of installed) {
277
+ console.log(`- /prompts:${prompt.name}`);
278
+ }
279
+ return;
280
+ }
281
+ if (subcommand === "remove-prompts") {
282
+ const removed = await removeCodexPromptAliases(customDir);
283
+ console.log(`Removed ${removed.length} Long Table prompt aliases from ${resolveCodexPromptsDir(customDir)}`);
284
+ return;
285
+ }
286
+ if (subcommand === "status") {
287
+ const aliases = await listInstalledCodexPromptAliases(customDir);
288
+ const setupPath = resolveDefaultSetupPath(typeof args.path === "string" ? args.path : undefined).path;
289
+ const runtimePath = resolveDefaultRuntimeConfigPath("codex", typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined).path;
290
+ const status = {
291
+ setupPath,
292
+ setupExists: existsSync(setupPath),
293
+ runtimePath,
294
+ runtimeExists: existsSync(runtimePath),
295
+ promptsDir: resolveCodexPromptsDir(customDir),
296
+ promptAliasesInstalled: aliases.map((alias) => alias.name)
297
+ };
298
+ if (args.json === true) {
299
+ console.log(JSON.stringify(status, null, 2));
300
+ return;
301
+ }
302
+ console.log("Long Table Codex status");
303
+ console.log(`- setup: ${status.setupExists ? "present" : "missing"} (${setupPath})`);
304
+ console.log(`- codex runtime artifact: ${status.runtimeExists ? "present" : "missing"} (${runtimePath})`);
305
+ console.log(`- prompt aliases dir: ${status.promptsDir}`);
306
+ if (aliases.length === 0) {
307
+ console.log("- prompt aliases: none");
308
+ }
309
+ else {
310
+ console.log("- prompt aliases:");
311
+ for (const alias of aliases) {
312
+ console.log(` - /prompts:${alias.name}`);
313
+ }
314
+ }
315
+ return;
316
+ }
317
+ throw new Error("Unknown codex subcommand.");
318
+ }
319
+ async function main() {
320
+ const parsed = parseArgs(process.argv.slice(2));
321
+ const { command, subcommand, values } = parsed;
322
+ if (!command || command === "--help" || values.help === true) {
323
+ console.log(usage());
324
+ return;
325
+ }
326
+ if (command === "init") {
327
+ await runInit(values);
328
+ return;
329
+ }
330
+ if (command === "show") {
331
+ await runShow(values);
332
+ return;
333
+ }
334
+ if (command === "install") {
335
+ await runInstall(values);
336
+ return;
337
+ }
338
+ if (command === "codex") {
339
+ await runCodexSubcommand(subcommand, values);
340
+ return;
341
+ }
342
+ if (VALID_MODES.has(command)) {
343
+ await runModeCommand(command, values);
344
+ return;
345
+ }
346
+ throw new Error(`Unknown command: ${command}`);
347
+ }
348
+ main().catch((error) => {
349
+ console.error(error instanceof Error ? error.message : String(error));
350
+ console.error("");
351
+ console.error(usage());
352
+ exit(1);
353
+ });
@@ -0,0 +1,3 @@
1
+ export * from "./prompt-aliases.js";
2
+ export * from "./personas.js";
3
+ export * from "./persona-router.js";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./prompt-aliases.js";
2
+ export * from "./personas.js";
3
+ export * from "./persona-router.js";
@@ -0,0 +1,23 @@
1
+ import { type CanonicalPersona } from "./personas.js";
2
+ export type OutputLanguage = "ko" | "en";
3
+ export interface PersonaRoutingResult {
4
+ outputLanguage: OutputLanguage;
5
+ explicitRoles: CanonicalPersona[];
6
+ implicitRoles: CanonicalPersona[];
7
+ consultedRoles: CanonicalPersona[];
8
+ ambiguousSignal: string | null;
9
+ }
10
+ export declare function detectOutputLanguage(input: string): OutputLanguage;
11
+ export declare function parseRoleFlag(value?: string): CanonicalPersona[];
12
+ export declare function routePersonas(prompt: string, explicitRoleFlag?: string): PersonaRoutingResult;
13
+ export declare function renderDisclosure(roles: CanonicalPersona[], language: OutputLanguage): string | null;
14
+ export declare function buildPersonaGuidance(options: {
15
+ prompt: string;
16
+ roleFlag?: string;
17
+ panel?: boolean;
18
+ showConflicts?: boolean;
19
+ showDeliberation?: boolean;
20
+ }): {
21
+ guidedPrompt: string;
22
+ routing: PersonaRoutingResult;
23
+ };
@@ -0,0 +1,94 @@
1
+ import { getPersonaDefinition, parsePersonaKey, PERSONA_DEFINITIONS } from "./personas.js";
2
+ const AUTO_CALL_LIMIT = 3;
3
+ function unique(items) {
4
+ return [...new Set(items)];
5
+ }
6
+ export function detectOutputLanguage(input) {
7
+ return /[가-힣]/.test(input) ? "ko" : "en";
8
+ }
9
+ export function parseRoleFlag(value) {
10
+ if (!value?.trim()) {
11
+ return [];
12
+ }
13
+ return unique(value
14
+ .split(",")
15
+ .map((part) => parsePersonaKey(part))
16
+ .filter((part) => part !== null));
17
+ }
18
+ export function routePersonas(prompt, explicitRoleFlag) {
19
+ const normalizedPrompt = prompt.toLowerCase();
20
+ const explicitRoles = parseRoleFlag(explicitRoleFlag);
21
+ const naturalLanguageExplicit = PERSONA_DEFINITIONS.filter((persona) => persona.synonyms.some((synonym) => normalizedPrompt.includes(synonym.toLowerCase()))).map((persona) => persona.key);
22
+ const mergedExplicit = unique([...explicitRoles, ...naturalLanguageExplicit]);
23
+ const implicitRoles = PERSONA_DEFINITIONS.filter((persona) => {
24
+ if (persona.triggerMode !== "auto-callable") {
25
+ return false;
26
+ }
27
+ if (mergedExplicit.includes(persona.key)) {
28
+ return false;
29
+ }
30
+ return persona.synonyms.some((synonym) => normalizedPrompt.includes(synonym.toLowerCase()));
31
+ })
32
+ .map((persona) => persona.key)
33
+ .slice(0, AUTO_CALL_LIMIT);
34
+ let ambiguousSignal = null;
35
+ if (normalizedPrompt.includes("review") ||
36
+ normalizedPrompt.includes("봐줘") ||
37
+ normalizedPrompt.includes("검토") ||
38
+ normalizedPrompt.includes("판단")) {
39
+ const hasEditor = mergedExplicit.includes("editor") || implicitRoles.includes("editor");
40
+ const hasReviewer = mergedExplicit.includes("reviewer") || implicitRoles.includes("reviewer");
41
+ if (!hasEditor && !hasReviewer) {
42
+ ambiguousSignal = "editor_or_reviewer";
43
+ }
44
+ }
45
+ return {
46
+ outputLanguage: detectOutputLanguage(prompt),
47
+ explicitRoles: mergedExplicit,
48
+ implicitRoles,
49
+ consultedRoles: unique([...mergedExplicit, ...implicitRoles]),
50
+ ambiguousSignal
51
+ };
52
+ }
53
+ export function renderDisclosure(roles, language) {
54
+ if (roles.length === 0) {
55
+ return null;
56
+ }
57
+ const labels = roles.map((role) => getPersonaDefinition(role).label);
58
+ return language === "ko"
59
+ ? `Long Table consulted: ${labels.join(", ")}`
60
+ : `Long Table consulted: ${labels.join(", ")}`;
61
+ }
62
+ export function buildPersonaGuidance(options) {
63
+ const routing = routePersonas(options.prompt, options.roleFlag);
64
+ const disclosure = renderDisclosure(routing.consultedRoles, routing.outputLanguage);
65
+ const lines = [];
66
+ if (disclosure) {
67
+ lines.push(disclosure);
68
+ }
69
+ if (routing.ambiguousSignal === "editor_or_reviewer") {
70
+ lines.push(routing.outputLanguage === "ko"
71
+ ? "Ambiguity note: 편집자 관점인지 리뷰어 관점인지 애매합니다. 먼저 둘 중 무엇을 우선할지 짧게 확인하세요."
72
+ : "Ambiguity note: it is unclear whether the user wants an editor view or reviewer view. Ask briefly before closing.");
73
+ }
74
+ if (options.panel) {
75
+ lines.push(routing.outputLanguage === "ko"
76
+ ? "Return format: 1) Long Table synthesis 2) panel opinions by role 3) decision prompt to the researcher."
77
+ : "Return format: 1) Long Table synthesis 2) panel opinions by role 3) decision prompt to the researcher.");
78
+ }
79
+ if (options.showConflicts) {
80
+ lines.push(routing.outputLanguage === "ko"
81
+ ? "If roles disagree, show the conflict explicitly instead of forcing one answer."
82
+ : "If roles disagree, show the conflict explicitly instead of forcing one answer.");
83
+ }
84
+ if (options.showDeliberation) {
85
+ lines.push(routing.outputLanguage === "ko"
86
+ ? "Include a short deliberation trace showing why the roles diverged."
87
+ : "Include a short deliberation trace showing why the roles diverged.");
88
+ }
89
+ lines.push(options.prompt.trim());
90
+ return {
91
+ guidedPrompt: lines.join("\n\n"),
92
+ routing
93
+ };
94
+ }
@@ -0,0 +1,12 @@
1
+ export declare const CANONICAL_PERSONAS: readonly ["editor", "reviewer", "theory_critic", "methods_critic", "measurement_auditor", "ethics_reviewer", "voice_keeper", "venue_strategist"];
2
+ export type CanonicalPersona = (typeof CANONICAL_PERSONAS)[number];
3
+ export interface PersonaDefinition {
4
+ key: CanonicalPersona;
5
+ label: string;
6
+ shortDescription: string;
7
+ triggerMode: "auto-callable" | "explicit-only";
8
+ synonyms: string[];
9
+ }
10
+ export declare const PERSONA_DEFINITIONS: readonly PersonaDefinition[];
11
+ export declare function parsePersonaKey(value: string): CanonicalPersona | null;
12
+ export declare function getPersonaDefinition(key: CanonicalPersona): PersonaDefinition;
@@ -0,0 +1,87 @@
1
+ export const CANONICAL_PERSONAS = [
2
+ "editor",
3
+ "reviewer",
4
+ "theory_critic",
5
+ "methods_critic",
6
+ "measurement_auditor",
7
+ "ethics_reviewer",
8
+ "voice_keeper",
9
+ "venue_strategist"
10
+ ];
11
+ export const PERSONA_DEFINITIONS = [
12
+ {
13
+ key: "editor",
14
+ label: "Journal Editor",
15
+ shortDescription: "Assesses venue fit, framing strength, and editorial salience.",
16
+ triggerMode: "auto-callable",
17
+ synonyms: ["editor", "journal editor", "editorial", "편집자", "저널 편집자", "에디터"]
18
+ },
19
+ {
20
+ key: "reviewer",
21
+ label: "Reviewer",
22
+ shortDescription: "Surfaces likely peer-review objections and requests for clarification.",
23
+ triggerMode: "auto-callable",
24
+ synonyms: ["reviewer", "peer reviewer", "심사자", "리뷰어", "심사위원"]
25
+ },
26
+ {
27
+ key: "theory_critic",
28
+ label: "Theory Critic",
29
+ shortDescription: "Checks conceptual coherence, anchor theory fit, and overreach.",
30
+ triggerMode: "auto-callable",
31
+ synonyms: ["theory", "theoretical", "conceptual", "이론", "이론적", "개념적"]
32
+ },
33
+ {
34
+ key: "methods_critic",
35
+ label: "Methods Critic",
36
+ shortDescription: "Challenges design logic, methodological defensibility, and alignment.",
37
+ triggerMode: "auto-callable",
38
+ synonyms: ["method", "methods", "methodology", "research design", "방법론", "방법", "연구 설계"]
39
+ },
40
+ {
41
+ key: "measurement_auditor",
42
+ label: "Measurement Auditor",
43
+ shortDescription: "Looks for construct validity, scale choice, and evidence quality issues.",
44
+ triggerMode: "auto-callable",
45
+ synonyms: [
46
+ "measurement",
47
+ "measure",
48
+ "scale",
49
+ "validity",
50
+ "reliability",
51
+ "측정",
52
+ "척도",
53
+ "타당도",
54
+ "신뢰도"
55
+ ]
56
+ },
57
+ {
58
+ key: "ethics_reviewer",
59
+ label: "Ethics Reviewer",
60
+ shortDescription: "Flags consent, IRB, representation, and trust harms.",
61
+ triggerMode: "auto-callable",
62
+ synonyms: ["ethics", "ethical", "irb", "윤리", "윤리적", "irb"]
63
+ },
64
+ {
65
+ key: "voice_keeper",
66
+ label: "Voice Keeper",
67
+ shortDescription: "Protects narrative trace, authorship, and the researcher's own voice.",
68
+ triggerMode: "auto-callable",
69
+ synonyms: ["voice", "tone", "narrative", "authorship", "목소리", "서사", "저자성", "문체"]
70
+ },
71
+ {
72
+ key: "venue_strategist",
73
+ label: "Venue Strategist",
74
+ shortDescription: "Compares venue expectations and suggests positioning tradeoffs.",
75
+ triggerMode: "explicit-only",
76
+ synonyms: ["venue", "journal fit", "conference fit", "저널 적합성", "학회 적합성", "투고처"]
77
+ }
78
+ ];
79
+ export function parsePersonaKey(value) {
80
+ const normalized = value.trim().toLowerCase().replace(/\s+/g, "_");
81
+ return CANONICAL_PERSONAS.includes(normalized)
82
+ ? normalized
83
+ : null;
84
+ }
85
+ export function getPersonaDefinition(key) {
86
+ return PERSONA_DEFINITIONS.find((persona) => persona.key === key);
87
+ }
@@ -0,0 +1,9 @@
1
+ export interface InstalledPromptAlias {
2
+ name: string;
3
+ path: string;
4
+ description: string;
5
+ }
6
+ export declare function resolveCodexPromptsDir(customDir?: string): string;
7
+ export declare function installCodexPromptAliases(customDir?: string): Promise<InstalledPromptAlias[]>;
8
+ export declare function removeCodexPromptAliases(customDir?: string): Promise<string[]>;
9
+ export declare function listInstalledCodexPromptAliases(customDir?: string): Promise<InstalledPromptAlias[]>;
@@ -0,0 +1,188 @@
1
+ import { mkdir, readdir, rm, writeFile } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join, resolve } from "node:path";
5
+ export function resolveCodexPromptsDir(customDir) {
6
+ return customDir ? resolve(customDir) : join(homedir(), ".codex", "prompts");
7
+ }
8
+ function promptSpec() {
9
+ return [
10
+ {
11
+ name: "longtable-init",
12
+ description: "Run Long Table researcher onboarding inside Codex",
13
+ argumentHint: "[project context or current uncertainty]",
14
+ body: [
15
+ "You are Long Table onboarding inside Codex.",
16
+ "Ask exactly one setup question at a time.",
17
+ "Use numbered choices when possible and include a 'None of the above' option when needed.",
18
+ "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 tell the researcher the exact `longtable init ...` command to persist it.",
21
+ "If the researcher asks you to stay inside Codex, keep the conversation in numbered form and do not prematurely close.",
22
+ "Treat any slash-command arguments as context for why setup is being done now."
23
+ ]
24
+ },
25
+ {
26
+ name: "longtable-explore",
27
+ description: "Long Table explore mode for open research questions",
28
+ argumentHint: "<topic or research problem>",
29
+ body: [
30
+ "You are Long Table in explore mode.",
31
+ "Ask at least two clarifying or tension questions before any recommendation.",
32
+ "Keep unresolved tensions visible.",
33
+ "Do not rush to synthesis.",
34
+ "Treat any slash-command arguments as the current research object."
35
+ ]
36
+ },
37
+ {
38
+ name: "longtable-review",
39
+ description: "Long Table review mode for critical evaluation",
40
+ argumentHint: "<claim, paragraph, design, or plan>",
41
+ body: [
42
+ "You are Long Table in review mode.",
43
+ "Surface why this may be wrong before synthesis.",
44
+ "Preserve the researcher's own language where possible.",
45
+ "Treat any slash-command arguments as the object to review."
46
+ ]
47
+ },
48
+ {
49
+ name: "longtable-panel",
50
+ description: "Long Table panel mode with visible role disagreement",
51
+ argumentHint: "<claim, plan, or draft for multi-role review>",
52
+ body: [
53
+ "You are Long Table in panel mode.",
54
+ "Return 1) a Long Table synthesis 2) visible panel opinions by role 3) a decision prompt for the researcher.",
55
+ "If roles disagree, do not collapse them too early.",
56
+ "Disclose which roles were consulted.",
57
+ "Treat any slash-command arguments as the object under discussion."
58
+ ]
59
+ },
60
+ {
61
+ name: "longtable-editor",
62
+ description: "Long Table editor view",
63
+ argumentHint: "<claim, draft, or paper positioning>",
64
+ body: [
65
+ "You are Long Table with the Journal Editor role foregrounded.",
66
+ "Prioritize venue fit, framing clarity, contribution shape, and likely editorial concerns.",
67
+ "Disclose that the editor role was consulted.",
68
+ "Treat any slash-command arguments as the editorial object."
69
+ ]
70
+ },
71
+ {
72
+ name: "longtable-reviewer",
73
+ description: "Long Table reviewer view",
74
+ argumentHint: "<claim, method, or manuscript section>",
75
+ body: [
76
+ "You are Long Table with the Reviewer role foregrounded.",
77
+ "Prioritize likely objections, missing evidence, weak claims, and points needing clarification.",
78
+ "Disclose that the reviewer role was consulted.",
79
+ "Treat any slash-command arguments as the review object."
80
+ ]
81
+ },
82
+ {
83
+ name: "longtable-methods",
84
+ description: "Long Table methods-critic view",
85
+ argumentHint: "<study design, measure, or analysis plan>",
86
+ body: [
87
+ "You are Long Table with the Methods Critic role foregrounded.",
88
+ "Prioritize design fit, methodological defensibility, and mismatches between question, measure, and analysis.",
89
+ "Disclose that the methods critic role was consulted.",
90
+ "Treat any slash-command arguments as the methodological object."
91
+ ]
92
+ },
93
+ {
94
+ name: "longtable-critique",
95
+ description: "Long Table critique mode for stronger counterarguments",
96
+ argumentHint: "<claim or draft to challenge>",
97
+ body: [
98
+ "You are Long Table in critique mode.",
99
+ "Prioritize counterarguments, blind spots, and hidden assumptions.",
100
+ "Do not smooth over uncertainty.",
101
+ "Treat any slash-command arguments as the object to challenge."
102
+ ]
103
+ },
104
+ {
105
+ name: "longtable-draft",
106
+ description: "Long Table draft mode with narrative-trace preservation",
107
+ argumentHint: "<draft goal or section request>",
108
+ body: [
109
+ "You are Long Table in draft mode.",
110
+ "Preserve narrative trace and avoid generic fluency.",
111
+ "Keep the researcher's voice recognizable.",
112
+ "Treat any slash-command arguments as the drafting target."
113
+ ]
114
+ },
115
+ {
116
+ name: "longtable-commit",
117
+ description: "Long Table commit mode for explicit human decisions",
118
+ argumentHint: "<decision or choice that needs commitment>",
119
+ body: [
120
+ "You are Long Table in commit mode.",
121
+ "Before making any recommendation, ask for the human commitment that is actually at stake.",
122
+ "Make the trade-offs explicit.",
123
+ "Treat any slash-command arguments as the decision under consideration."
124
+ ]
125
+ },
126
+ {
127
+ name: "longtable-status",
128
+ description: "Inspect Long Table setup and Codex alias status",
129
+ argumentHint: "[optional concern]",
130
+ body: [
131
+ "You are Long Table status mode.",
132
+ "Inspect whether setup and runtime artifacts appear to exist under `~/.diverga/` and whether Long Table prompt aliases appear to be installed under `~/.codex/prompts/`.",
133
+ "Summarize what is configured, what is missing, and the next minimal action.",
134
+ "Treat any slash-command arguments as the user's concern."
135
+ ]
136
+ }
137
+ ];
138
+ }
139
+ function renderPromptFile(description, argumentHint, body) {
140
+ return [
141
+ "---",
142
+ `description: \"${description}\"`,
143
+ `argument-hint: \"${argumentHint}\"`,
144
+ "---",
145
+ ...body
146
+ ].join("\n");
147
+ }
148
+ export async function installCodexPromptAliases(customDir) {
149
+ const promptsDir = resolveCodexPromptsDir(customDir);
150
+ await mkdir(promptsDir, { recursive: true });
151
+ const installed = [];
152
+ for (const spec of promptSpec()) {
153
+ const path = join(promptsDir, `${spec.name}.md`);
154
+ await writeFile(path, renderPromptFile(spec.description, spec.argumentHint, spec.body), "utf8");
155
+ installed.push({
156
+ name: spec.name,
157
+ path,
158
+ description: spec.description
159
+ });
160
+ }
161
+ return installed;
162
+ }
163
+ export async function removeCodexPromptAliases(customDir) {
164
+ const promptsDir = resolveCodexPromptsDir(customDir);
165
+ const removed = [];
166
+ for (const spec of promptSpec()) {
167
+ const path = join(promptsDir, `${spec.name}.md`);
168
+ if (existsSync(path)) {
169
+ await rm(path);
170
+ removed.push(path);
171
+ }
172
+ }
173
+ return removed;
174
+ }
175
+ export async function listInstalledCodexPromptAliases(customDir) {
176
+ const promptsDir = resolveCodexPromptsDir(customDir);
177
+ if (!existsSync(promptsDir)) {
178
+ return [];
179
+ }
180
+ const files = new Set(await readdir(promptsDir));
181
+ return promptSpec()
182
+ .filter((spec) => files.has(`${spec.name}.md`))
183
+ .map((spec) => ({
184
+ name: spec.name,
185
+ path: join(promptsDir, `${spec.name}.md`),
186
+ description: spec.description
187
+ }));
188
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@longtable/cli",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Researcher-facing Long Table CLI on top of the legacy Diverga package surface",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "directories": {
16
+ "bin": "./bin"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "bin",
21
+ "README.md"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.json",
25
+ "typecheck": "tsc -p tsconfig.json --noEmit"
26
+ },
27
+ "dependencies": {
28
+ "@diverga/provider-codex": "0.1.1",
29
+ "@diverga/setup": "0.1.1"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^22.10.1",
33
+ "typescript": "^5.6.0"
34
+ },
35
+ "keywords": [
36
+ "long-table",
37
+ "research",
38
+ "codex",
39
+ "onboarding",
40
+ "checkpoints"
41
+ ],
42
+ "author": "Hosung You",
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/HosungYou/Diverga-Refactoring.git"
47
+ },
48
+ "homepage": "https://github.com/HosungYou/Diverga-Refactoring#readme",
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "engines": {
53
+ "node": ">=18.0.0"
54
+ }
55
+ }