@longtable/cli 0.1.3 → 0.1.4

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,14 +2,16 @@
2
2
 
3
3
  Long Table의 researcher-facing CLI입니다.
4
4
 
5
- 패키지는 연구자가 아래처럼 시작할 있게 만드는 것이 목표입니다.
5
+ 패키지의 핵심은 명령을 많이 외우게 하는 것이 아니라, 아래 두 단계를 명확하게 만드는 것입니다.
6
6
 
7
- - `longtable init`
8
- - `longtable start`
9
- - `longtable ask --prompt "..."`
7
+ 1. `longtable init`
8
+ 2. `longtable start`
9
+
10
+ 중요한 점:
10
11
 
11
- 내부 명령이나 legacy package 이름을 필요 없이,
12
- 연구자가 `오늘 무엇이 필요한가`에서 바로 출발할 있게 하는 표면입니다.
12
+ - 명령은 **터미널에서 실행하는 명령**입니다.
13
+ - Codex 채팅창에 `longtable start`를 입력하는 것이 아닙니다.
14
+ - `longtable start`로 프로젝트 작업공간을 만든 뒤, 그 디렉토리에서 `codex`를 여는 것이 기본 경로입니다.
13
15
 
14
16
  ## Install
15
17
 
@@ -17,59 +19,74 @@ Long Table의 researcher-facing CLI입니다.
17
19
  npm install -g @longtable/cli
18
20
  ```
19
21
 
20
- ## Recommended flow
21
-
22
- ### 1. Setup once
22
+ ## 1. Global setup
23
23
 
24
24
  ```bash
25
25
  longtable init --flow interview
26
26
  ```
27
27
 
28
- 인터뷰 setup은 연구자의
29
-
30
- - 현재 주제
31
- - 현재 막힘
32
- - 선호하는 첫 진입 모드
33
- - 가장 많이 도전받고 싶은 영역
34
- - 역할 간 의견 충돌 가시성
28
+ 여기서는 연구자 프로필과 기본 선호를 묻습니다.
35
29
 
36
- 까지 저장합니다.
30
+ - 연구 분야
31
+ - 연구자 역할
32
+ - challenge 강도
33
+ - 저자성/서사 관련 기본값
34
+ - Long Table이 보통 어디서부터 시작해야 하는지
37
35
 
38
- ### 2. Start from today’s work
36
+ ## 2. Project start
39
37
 
40
38
  ```bash
41
39
  longtable start
42
40
  ```
43
41
 
44
- `longtable start`는 가장 단순한 진입점입니다.
45
- 오늘 필요한 작업을 먼저 고르고, 그 다음 자연어로 상황을 말하면 됩니다.
42
+ 여기서는 실제 작업공간을 만듭니다.
46
43
 
47
- ### 3. Or jump straight in
44
+ - 프로젝트 이름
45
+ - 프로젝트 디렉토리 위치
46
+ - 현재 세션 목표
47
+ - 현재 blocker
48
+ - 필요한 관점
49
+ - disagreement 가시성
50
+
51
+ 그리고 Long Table은:
52
+
53
+ - 프로젝트 디렉토리 생성
54
+ - `.longtable/` 메모리 파일 생성
55
+ - 프로젝트용 `AGENTS.md` 생성
56
+
57
+ 을 수행합니다.
58
+
59
+ ## Recommended flow
48
60
 
49
61
  ```bash
50
- longtable ask --prompt "연구를 시작하고 싶은데 어디서부터 좁혀야 할지 모르겠어."
62
+ longtable init --flow interview
63
+ longtable start
64
+ cd "<project-path>"
65
+ codex
51
66
  ```
52
67
 
53
- ## Useful commands
68
+ 이게 현재 가장 신뢰할 수 있는 Long Table 사용 경로입니다.
69
+
70
+ ## After the project starts
71
+
72
+ 프로젝트 디렉토리 안에서는 보통 그냥 `codex`를 열고 자연어로 연구를 시작하면 됩니다.
73
+
74
+ CLI를 계속 쓰고 싶다면 아래 명령은 보조 경로입니다.
75
+
76
+ ## Advanced commands
54
77
 
55
78
  ```bash
56
- longtable start
57
79
  longtable roles
58
- longtable ask --prompt "연구 아이디어를 어디서부터 열어야 할지 모르겠어."
59
- longtable review --prompt "BJET 편집자 관점에서 봐줘." --role editor
60
- longtable review --prompt "방법론적으로 어디가 취약한지 말해줘." --role methods_critic
61
- longtable review --prompt "의견 충돌까지 보여줘." --panel --show-conflicts
62
- longtable show --json
80
+ longtable ask --cwd "<project-path>" --prompt "연구 질문을 어디서부터 좁혀야 할지 모르겠어."
81
+ longtable review --cwd "<project-path>" --prompt "방법론적으로 어디가 취약한지 말해줘." --role methods_critic
63
82
  ```
64
83
 
65
- ## What `roles` means
84
+ ## Roles
66
85
 
67
86
  ```bash
68
87
  longtable roles
69
88
  ```
70
89
 
71
- Long Table이 어떤 관점을 호출할 수 있는지 짧게 보여줍니다.
72
-
73
90
  예:
74
91
 
75
92
  - `editor`
@@ -77,25 +94,20 @@ Long Table이 어떤 관점을 호출할 수 있는지 짧게 보여줍니다.
77
94
  - `methods_critic`
78
95
  - `voice_keeper`
79
96
 
80
- 기본값은 하나의 Long Table synthesis이지만, 필요하면 panel과 conflict도 드러낼 수 있습니다.
81
-
82
- ## Codex integration
83
-
84
- Long Table은 Codex prompt file 설치도 지원합니다.
97
+ ## Codex prompt-file integration
85
98
 
86
99
  ```bash
87
100
  longtable codex install-prompts
88
101
  longtable codex status
89
102
  ```
90
103
 
91
- 하지만 이 경로는 현재 `실험적`입니다.
104
+ 이 경로는 현재 실험적입니다.
105
+ 사용자에게 약속하는 주 경로는 아닙니다.
92
106
 
93
- - Codex 버전/빌드에 따라 prompt file이 slash command로 바로 노출되지 않을 수 있습니다.
94
- - 그래서 기본 사용자 경로는 `/prompts:...`가 아니라 `longtable start`와 `longtable ask`입니다.
107
+ 기본 경로는 여전히:
95
108
 
96
- ## Why this package exists
97
-
98
- Long Table은 제품명입니다.
99
- `Diverga`는 아직 일부 하위 패키지와 runtime path에서만 남아 있는 호환성 레이어입니다.
109
+ - `longtable init`
110
+ - `longtable start`
111
+ - 프로젝트 디렉토리에서 `codex`
100
112
 
101
- 이 패키지는 연구자가 `longtable`만 알면 되게 하기 위해 존재합니다.
113
+ 입니다.
package/dist/cli.js CHANGED
@@ -3,11 +3,13 @@ import { existsSync, readFileSync } from "node:fs";
3
3
  import { emitKeypressEvents } from "node:readline";
4
4
  import { createInterface } from "node:readline/promises";
5
5
  import { stdin as input, stdout as output, cwd, exit } from "node:process";
6
+ import { resolve } from "node:path";
6
7
  import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput, installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultRuntimeConfigPath, resolveDefaultSetupPath, saveSetupAndRuntimeConfig, serializeSetupOutput } from "@diverga/setup";
7
8
  import { buildCodexThinWrappedPrompt, runCodexThinWrapper } from "@diverga/provider-codex";
8
9
  import { installCodexPromptAliases, listInstalledCodexPromptAliases, removeCodexPromptAliases, resolveCodexPromptsDir } from "./prompt-aliases.js";
9
10
  import { buildPersonaGuidance } from "./persona-router.js";
10
11
  import { PERSONA_DEFINITIONS } from "./personas.js";
12
+ import { createOrUpdateProjectWorkspace, loadProjectContextFromDirectory, renderProjectWorkspaceSummary } from "./project-session.js";
11
13
  const VALID_MODES = new Set([
12
14
  "explore",
13
15
  "review",
@@ -28,8 +30,11 @@ const VALID_STAGES = new Set([
28
30
  function usage() {
29
31
  return [
30
32
  "Usage:",
31
- " longtable init [--flow quickstart|interview] [--provider codex|claude] [--field <field>] [--career-stage <stage>] [--experience novice|intermediate|advanced] [--project-type <type>] [--checkpoint low|balanced|high] [--authorship-signal <text>] [--topic <text>] [--blocker <text>] [--entry-mode explore|review|critique|draft|commit] [--weakest-domain theory|methodology|measurement|analysis|writing] [--panel-preference synthesis_only|show_on_conflict|always_visible] [--json] [--no-install] [--install-prompts]",
32
- " longtable start [--prompt <text>] [--setup <path>] [--cwd <path>] [--print] [--json]",
33
+ " Run `longtable ...` in your terminal, not inside the Codex chat box.",
34
+ " After `longtable start`, move into the created project directory and open `codex` there.",
35
+ "",
36
+ " longtable init [--flow quickstart|interview] [--provider codex|claude] [--field <field>] [--career-stage <stage>] [--experience novice|intermediate|advanced] [--checkpoint low|balanced|high] [--authorship-signal <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]",
37
+ " longtable start [--path <dir>] [--name <project>] [--goal <text>] [--blocker <text>] [--perspectives <role[,role]>] [--disagreement synthesis_only|show_on_conflict|always_visible] [--setup <path>] [--json]",
33
38
  " longtable roles [--json]",
34
39
  " longtable show [--json] [--path <file>]",
35
40
  " longtable install [--json] [--path <file>] [--runtime-path <file>]",
@@ -43,10 +48,10 @@ function usage() {
43
48
  "Examples:",
44
49
  " longtable init --flow interview --install-prompts",
45
50
  " longtable start",
51
+ " longtable start --path ~/Research/My-Project --name \"AI Adoption Meta-Analysis\" --goal \"Narrow the review question\"",
52
+ " cd \"<project-path>\" && codex",
46
53
  " longtable roles",
47
54
  " longtable ask --prompt \"연구를 시작하고 싶어. 지금 어디서부터 좁혀야 할지 모르겠어.\"",
48
- " longtable review --prompt \"Review this claim critically.\" --panel --show-conflicts",
49
- " longtable review --role editor --prompt \"BJET 편집자 관점에서 봐줘.\"",
50
55
  " printf '{\"provider\":\"codex\",...}' | longtable codex persist-init --stdin --install-prompts",
51
56
  " longtable codex install-prompts"
52
57
  ].join("\n");
@@ -115,7 +120,7 @@ function buildSetupFlowChoices() {
115
120
  function renderSetupHeader(flow) {
116
121
  const title = flow === "interview" ? "Long Table Setup Interview" : "Long Table Quickstart";
117
122
  const subtitle = flow === "interview"
118
- ? "We will ask about your research persona, current topic, blocker, and preferred kind of challenge."
123
+ ? "We will ask about your research persona, challenge preferences, and authorship defaults."
119
124
  : "We will capture the minimum profile needed to start using Long Table.";
120
125
  return [
121
126
  "┌──────────────────────────────────────────────┐",
@@ -131,9 +136,6 @@ function questionSection(questionId) {
131
136
  if (questionId === "field" || questionId === "careerStage" || questionId === "experienceLevel") {
132
137
  return "Researcher profile";
133
138
  }
134
- if (questionId === "currentProjectType" || questionId === "currentResearchTopic" || questionId === "currentBlocker") {
135
- return "Current work";
136
- }
137
139
  if (questionId === "preferredCheckpointIntensity" || questionId === "preferredEntryMode") {
138
140
  return "Interaction style";
139
141
  }
@@ -260,54 +262,84 @@ async function promptChoiceWithArrows(rl, prompt, choices) {
260
262
  async function promptChoice(rl, prompt, choices) {
261
263
  return promptChoiceWithArrows(rl, prompt, choices);
262
264
  }
263
- async function promptStartAction(rl, blocker) {
264
- return (await promptChoice(rl, blocker?.trim()
265
- ? `What do you want to do with this first?\nCurrent blocker: ${blocker.trim()}`
266
- : "What do you want Long Table to help you do first?", [
267
- {
268
- id: "explore",
269
- label: "Open the problem",
270
- description: "Start with questions, tensions, and a clearer research direction."
271
- },
272
- {
273
- id: "review",
274
- label: "Review something",
275
- description: "Critically inspect a claim, outline, or draft."
276
- },
277
- {
278
- id: "critique",
279
- label: "Challenge me hard",
280
- description: "Push back, stress-test assumptions, and surface hidden risks."
281
- },
282
- {
283
- id: "editor",
284
- label: "Journal editor view",
285
- description: "See how an editor might judge framing, fit, and contribution."
286
- },
287
- {
288
- id: "methods",
289
- label: "Methods check",
290
- description: "Focus on design fit, defensibility, and methodological weakness."
291
- },
292
- {
293
- id: "panel",
294
- label: "Show disagreement",
295
- description: "Let multiple roles disagree instead of forcing one answer."
296
- },
297
- {
298
- id: "status",
299
- label: "Check my setup",
300
- description: "See what Long Table knows and what is installed."
301
- },
302
- {
303
- id: "roles",
304
- label: "What roles exist?",
305
- description: "See what perspectives Long Table can bring into the conversation."
265
+ async function promptMultiChoice(rl, prompt, choices) {
266
+ const stream = input;
267
+ if (!stream.isTTY || !output.isTTY) {
268
+ const answer = await rl.question(`${prompt}\nType comma-separated ids or leave blank for auto.\n> `);
269
+ return answer
270
+ .split(",")
271
+ .map((part) => part.trim())
272
+ .filter(Boolean);
273
+ }
274
+ const previousRawMode = stream.isRaw;
275
+ let selectedIndex = 0;
276
+ const selected = new Set();
277
+ const renderedLineCount = choices.length + 2;
278
+ return await new Promise((resolvePromise, reject) => {
279
+ function draw(first = false) {
280
+ if (!first) {
281
+ output.write(moveCursorUp(renderedLineCount));
282
+ }
283
+ const lines = [prompt, "Use ↑/↓, Space to toggle, and Enter to confirm."];
284
+ for (let index = 0; index < choices.length; index += 1) {
285
+ const choice = choices[index];
286
+ const pointer = index === selectedIndex ? ">" : " ";
287
+ const marker = selected.has(choice.id) ? "[x]" : "[ ]";
288
+ lines.push(`${pointer} ${marker} ${choice.label} - ${choice.description}`);
289
+ }
290
+ for (const line of lines) {
291
+ output.write(clearLine());
292
+ output.write(`${line}\n`);
293
+ }
294
+ }
295
+ function cleanup() {
296
+ stream.off("keypress", onKeypress);
297
+ if (stream.isTTY) {
298
+ stream.setRawMode(previousRawMode ?? false);
299
+ }
300
+ output.write("\u001B[?25h");
301
+ }
302
+ function onKeypress(_, key) {
303
+ if (key.ctrl && key.name === "c") {
304
+ cleanup();
305
+ reject(new Error("Setup cancelled."));
306
+ return;
307
+ }
308
+ if (key.name === "up") {
309
+ selectedIndex = selectedIndex === 0 ? choices.length - 1 : selectedIndex - 1;
310
+ draw();
311
+ return;
312
+ }
313
+ if (key.name === "down") {
314
+ selectedIndex = selectedIndex === choices.length - 1 ? 0 : selectedIndex + 1;
315
+ draw();
316
+ return;
317
+ }
318
+ if (key.name === "space") {
319
+ const id = choices[selectedIndex].id;
320
+ if (selected.has(id)) {
321
+ selected.delete(id);
322
+ }
323
+ else {
324
+ selected.add(id);
325
+ }
326
+ draw();
327
+ return;
328
+ }
329
+ if (key.name === "return") {
330
+ cleanup();
331
+ resolvePromise([...selected]);
332
+ }
306
333
  }
307
- ]));
334
+ emitKeypressEvents(stream);
335
+ stream.setRawMode(true);
336
+ output.write("\u001B[?25l");
337
+ draw(true);
338
+ stream.on("keypress", onKeypress);
339
+ });
308
340
  }
309
341
  function hasCompleteFlagInput(args) {
310
- const required = ["provider", "field", "career-stage", "experience", "project-type", "checkpoint"];
342
+ const required = ["provider", "field", "career-stage", "experience", "checkpoint"];
311
343
  return required.every((key) => typeof args[key] === "string" && String(args[key]).trim().length > 0);
312
344
  }
313
345
  function resolveSetupFlow(args) {
@@ -318,13 +350,13 @@ function toSetupAnswers(args) {
318
350
  field: String(args.field),
319
351
  careerStage: String(args["career-stage"]),
320
352
  experienceLevel: String(args.experience),
321
- currentProjectType: String(args["project-type"]),
353
+ currentProjectType: typeof args["project-type"] === "string" && args["project-type"].trim().length > 0
354
+ ? String(args["project-type"])
355
+ : "unspecified research task",
322
356
  preferredCheckpointIntensity: String(args.checkpoint),
323
357
  humanAuthorshipSignal: typeof args["authorship-signal"] === "string" && args["authorship-signal"].trim().length > 0
324
358
  ? args["authorship-signal"].trim()
325
359
  : undefined,
326
- currentResearchTopic: typeof args.topic === "string" && args.topic.trim().length > 0 ? args.topic.trim() : undefined,
327
- currentBlocker: typeof args.blocker === "string" && args.blocker.trim().length > 0 ? args.blocker.trim() : undefined,
328
360
  preferredEntryMode: typeof args["entry-mode"] === "string" && VALID_MODES.has(String(args["entry-mode"]))
329
361
  ? String(args["entry-mode"])
330
362
  : undefined,
@@ -336,15 +368,18 @@ function toSetupAnswers(args) {
336
368
  : undefined
337
369
  };
338
370
  }
339
- async function collectInteractiveAnswers() {
371
+ async function collectInteractiveAnswers(initialFlow) {
340
372
  const rl = createInterface({ input, output });
341
373
  try {
342
- const flow = (await promptChoice(rl, "How would you like to set up Long Table?", buildSetupFlowChoices()));
374
+ const flow = initialFlow ??
375
+ (await promptChoice(rl, "How would you like to set up Long Table?", buildSetupFlowChoices()));
343
376
  console.log("");
344
377
  console.log(renderSetupHeader(flow));
345
378
  console.log("");
346
379
  const provider = await promptChoice(rl, "Which provider do you want to configure?", buildProviderChoices());
347
- const answers = {};
380
+ const answers = {
381
+ currentProjectType: "unspecified research task"
382
+ };
348
383
  const questions = buildQuickSetupFlow(flow);
349
384
  for (let index = 0; index < questions.length; index += 1) {
350
385
  const question = questions[index];
@@ -365,18 +400,12 @@ async function collectInteractiveAnswers() {
365
400
  answers.careerStage = value;
366
401
  if (question.id === "experienceLevel")
367
402
  answers.experienceLevel = value;
368
- if (question.id === "currentProjectType")
369
- answers.currentProjectType = value;
370
403
  if (question.id === "preferredCheckpointIntensity") {
371
404
  answers.preferredCheckpointIntensity = value;
372
405
  }
373
406
  if (question.id === "humanAuthorshipSignal" && value !== "other") {
374
407
  answers.humanAuthorshipSignal = value;
375
408
  }
376
- if (question.id === "currentResearchTopic")
377
- answers.currentResearchTopic = value;
378
- if (question.id === "currentBlocker")
379
- answers.currentBlocker = value;
380
409
  if (question.id === "preferredEntryMode")
381
410
  answers.preferredEntryMode = value;
382
411
  if (question.id === "weakestDomain")
@@ -394,6 +423,84 @@ async function collectInteractiveAnswers() {
394
423
  rl.close();
395
424
  }
396
425
  }
426
+ function perspectiveChoices() {
427
+ return PERSONA_DEFINITIONS.map((persona) => ({
428
+ id: persona.key,
429
+ label: persona.label,
430
+ description: persona.shortDescription
431
+ }));
432
+ }
433
+ function normalizePerspectiveList(value) {
434
+ if (!value?.trim()) {
435
+ return [];
436
+ }
437
+ return value
438
+ .split(",")
439
+ .map((part) => part.trim())
440
+ .filter(Boolean);
441
+ }
442
+ async function collectProjectInterview(setup, args) {
443
+ const needsInteractivePrompts = !(typeof args.name === "string" && args.name.trim()) ||
444
+ !(typeof args.path === "string" && args.path.trim()) ||
445
+ !(typeof args.goal === "string" && args.goal.trim()) ||
446
+ typeof args.blocker !== "string" ||
447
+ normalizePerspectiveList(typeof args.perspectives === "string" ? args.perspectives : undefined).length === 0 ||
448
+ !(typeof args.disagreement === "string" && args.disagreement.trim());
449
+ const rl = createInterface({ input, output });
450
+ try {
451
+ if (needsInteractivePrompts) {
452
+ console.log("");
453
+ console.log("┌──────────────────────────────────────────────┐");
454
+ console.log("│ Long Table Project Start │");
455
+ console.log("└──────────────────────────────────────────────┘");
456
+ console.log("We will create a project workspace and a session memory seed for today's work.");
457
+ console.log("");
458
+ }
459
+ const projectName = (typeof args.name === "string" && args.name.trim()) ||
460
+ (await promptText(rl, renderQuestionHeader(1, 6, "Project interview", "What should this project be called?"), true));
461
+ const suggestedPath = typeof args.path === "string" && args.path.trim()
462
+ ? args.path.trim()
463
+ : resolve(projectName.replace(/\s+/g, "-"));
464
+ const projectPath = (typeof args.path === "string" && args.path.trim()) ||
465
+ (await promptText(rl, renderQuestionHeader(2, 6, "Project interview", `Where should this project live on your machine?\nSuggested path: ${suggestedPath}`), true));
466
+ const currentGoal = (typeof args.goal === "string" && args.goal.trim()) ||
467
+ (await promptText(rl, renderQuestionHeader(3, 6, "Current session", "What are you trying to accomplish in this session?"), true));
468
+ const currentBlocker = (typeof args.blocker === "string" && args.blocker.trim()) ||
469
+ (await promptText(rl, renderQuestionHeader(4, 6, "Current session", "What is the main blocker or uncertainty right now?"), false));
470
+ const requestedPerspectives = normalizePerspectiveList(typeof args.perspectives === "string" ? args.perspectives : undefined).length > 0
471
+ ? normalizePerspectiveList(typeof args.perspectives === "string" ? args.perspectives : undefined)
472
+ : await promptMultiChoice(rl, renderQuestionHeader(5, 6, "Perspectives", "Which perspectives do you already know you want at the table? Leave everything unchecked for auto."), perspectiveChoices());
473
+ const disagreementPreference = (typeof args.disagreement === "string" && args.disagreement.trim()) ||
474
+ (await promptChoice(rl, renderQuestionHeader(6, 6, "Disagreement", "How visible should disagreement between perspectives be in this project by default?"), [
475
+ {
476
+ id: "synthesis_only",
477
+ label: "Synthesis only",
478
+ description: "Show one Long Table answer unless I ask for more."
479
+ },
480
+ {
481
+ id: "show_on_conflict",
482
+ label: "Show on conflict",
483
+ description: "Surface disagreement when the perspectives materially diverge."
484
+ },
485
+ {
486
+ id: "always_visible",
487
+ label: "Always visible",
488
+ description: "Keep panel opinions visible by default."
489
+ }
490
+ ]));
491
+ return {
492
+ projectName: projectName.trim(),
493
+ projectPath: projectPath.trim(),
494
+ currentGoal: currentGoal.trim(),
495
+ ...(currentBlocker?.trim() ? { currentBlocker: currentBlocker.trim() } : {}),
496
+ requestedPerspectives,
497
+ disagreementPreference: disagreementPreference
498
+ };
499
+ }
500
+ finally {
501
+ rl.close();
502
+ }
503
+ }
397
504
  function normalizePersistAnswers(raw) {
398
505
  return {
399
506
  flow: raw.flow === "interview" ? "interview" : "quickstart",
@@ -402,17 +509,11 @@ function normalizePersistAnswers(raw) {
402
509
  field: raw.field,
403
510
  careerStage: raw.careerStage,
404
511
  experienceLevel: raw.experienceLevel,
405
- currentProjectType: raw.currentProjectType,
512
+ currentProjectType: "unspecified research task",
406
513
  preferredCheckpointIntensity: raw.preferredCheckpointIntensity,
407
514
  ...(raw.humanAuthorshipSignal?.trim()
408
515
  ? { humanAuthorshipSignal: raw.humanAuthorshipSignal.trim() }
409
516
  : {}),
410
- ...(raw.currentResearchTopic?.trim()
411
- ? { currentResearchTopic: raw.currentResearchTopic.trim() }
412
- : {}),
413
- ...(raw.currentBlocker?.trim()
414
- ? { currentBlocker: raw.currentBlocker.trim() }
415
- : {}),
416
517
  ...(raw.preferredEntryMode
417
518
  ? { preferredEntryMode: raw.preferredEntryMode }
418
519
  : {}),
@@ -462,7 +563,7 @@ async function runInit(args) {
462
563
  provider: String(args.provider) === "claude" ? "claude" : "codex",
463
564
  answers: toSetupAnswers(args)
464
565
  }
465
- : await collectInteractiveAnswers();
566
+ : await collectInteractiveAnswers(typeof args.flow === "string" ? resolveSetupFlow(args) : undefined);
466
567
  const outputValue = createPersistedSetupOutput(answers, provider, flow);
467
568
  const result = await saveSetupAndRuntimeConfig(outputValue, {
468
569
  setupPath: customPath,
@@ -495,15 +596,7 @@ async function runInit(args) {
495
596
  console.log("- Start here: `longtable start`.");
496
597
  console.log("- If you want a direct natural-language entry: `longtable ask --prompt \"...\"`.");
497
598
  console.log("- Codex prompt files are available as an experimental integration, not the primary path.");
498
- if (answers.currentBlocker) {
499
- console.log(`- Suggested first message: ${answers.currentBlocker}`);
500
- }
501
- else if (answers.currentResearchTopic) {
502
- console.log(`- Suggested first message: Help me open the problem around ${answers.currentResearchTopic}.`);
503
- }
504
- else {
505
- console.log("- Suggested first message: I want to start my research and need help opening the problem.");
506
- }
599
+ console.log("- Suggested next action: create a project workspace and let Long Table interview the current session.");
507
600
  }
508
601
  }
509
602
  async function runShow(args) {
@@ -561,15 +654,7 @@ async function runCodexPersistInit(args) {
561
654
  console.log("- Start here: `longtable start`.");
562
655
  console.log("- If you want a direct natural-language entry: `longtable ask --prompt \"...\"`.");
563
656
  console.log("- Codex prompt files are available as an experimental integration, not the primary path.");
564
- if (answers.currentBlocker) {
565
- console.log(`- Suggested first message: ${answers.currentBlocker}`);
566
- }
567
- else if (answers.currentResearchTopic) {
568
- console.log(`- Suggested first message: Help me think through ${answers.currentResearchTopic}.`);
569
- }
570
- else {
571
- console.log("- Suggested first message: I want to start my research and I am not sure where to open the problem.");
572
- }
657
+ console.log("- Suggested next action: create a project workspace and let Long Table interview the current session.");
573
658
  }
574
659
  }
575
660
  async function resolvePrompt(prompt) {
@@ -638,7 +723,25 @@ async function loadOptionalSetup(path) {
638
723
  return null;
639
724
  }
640
725
  }
726
+ async function buildProjectAwarePrompt(prompt, workingDirectory) {
727
+ const context = await loadProjectContextFromDirectory(workingDirectory);
728
+ if (!context) {
729
+ return { prompt, projectContextFound: false };
730
+ }
731
+ const lines = [
732
+ "Long Table project context",
733
+ `project: ${context.project.projectName}`,
734
+ `current session goal: ${context.session.currentGoal}`,
735
+ ...(context.session.currentBlocker ? [`current blocker: ${context.session.currentBlocker}`] : []),
736
+ `requested perspectives: ${context.session.requestedPerspectives.length > 0 ? context.session.requestedPerspectives.join(", ") : "auto"}`,
737
+ `disagreement preference: ${context.session.disagreementPreference}`,
738
+ "",
739
+ prompt
740
+ ];
741
+ return { prompt: lines.join("\n"), projectContextFound: true };
742
+ }
641
743
  async function runModeCommand(mode, args) {
744
+ const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
642
745
  const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
643
746
  if (!prompt) {
644
747
  throw new Error("A prompt is required.");
@@ -648,13 +751,14 @@ async function runModeCommand(mode, args) {
648
751
  throw new Error(`Invalid stage: ${stage}`);
649
752
  }
650
753
  const setup = await loadOptionalSetup(typeof args.setup === "string" ? args.setup : undefined);
754
+ const projectAware = await buildProjectAwarePrompt(prompt, workingDirectory);
651
755
  const panelPreference = setup?.profileSeed.panelPreference;
652
756
  const panelRequested = args.panel === true ||
653
757
  panelPreference === "always_visible" ||
654
758
  (panelPreference === "show_on_conflict" && args["show-conflicts"] === true);
655
759
  const { guidedPrompt } = buildPersonaGuidance({
656
760
  mode,
657
- prompt,
761
+ prompt: projectAware.prompt,
658
762
  roleFlag: typeof args.role === "string" ? args.role : undefined,
659
763
  panel: panelRequested,
660
764
  showConflicts: args["show-conflicts"] === true,
@@ -666,7 +770,7 @@ async function runModeCommand(mode, args) {
666
770
  mode,
667
771
  researchStage: stage,
668
772
  setupPath: typeof args.setup === "string" ? args.setup : undefined,
669
- workingDirectory: typeof args.cwd === "string" ? args.cwd : cwd()
773
+ workingDirectory
670
774
  });
671
775
  console.log(wrapped.wrappedPrompt);
672
776
  return;
@@ -676,7 +780,7 @@ async function runModeCommand(mode, args) {
676
780
  mode,
677
781
  researchStage: stage,
678
782
  setupPath: typeof args.setup === "string" ? args.setup : undefined,
679
- workingDirectory: typeof args.cwd === "string" ? args.cwd : cwd(),
783
+ workingDirectory,
680
784
  json: args.json === true
681
785
  });
682
786
  exit(exitCode);
@@ -728,85 +832,34 @@ async function runStart(args) {
728
832
  const setupPath = typeof args.setup === "string" ? args.setup : undefined;
729
833
  const existingSetup = await loadOptionalSetup(setupPath);
730
834
  if (!existingSetup) {
731
- console.log("Long Table is not set up yet. Starting the interview flow first.");
732
- console.log("");
733
- await runInit({
734
- ...args,
735
- flow: typeof args.flow === "string" ? args.flow : "interview",
736
- "install-prompts": true
737
- });
738
- return;
739
- }
740
- const directPrompt = typeof args.prompt === "string" ? args.prompt.trim() : "";
741
- if (directPrompt) {
742
- await runAsk({
743
- ...args,
744
- prompt: directPrompt,
745
- ...(setupPath ? { setup: setupPath } : {})
746
- });
835
+ throw new Error("Long Table global setup is missing. Run `longtable init --flow interview` first.");
836
+ }
837
+ const interview = await collectProjectInterview(existingSetup, args);
838
+ const context = await createOrUpdateProjectWorkspace({
839
+ projectName: interview.projectName,
840
+ projectPath: interview.projectPath,
841
+ currentGoal: interview.currentGoal,
842
+ currentBlocker: interview.currentBlocker,
843
+ requestedPerspectives: interview.requestedPerspectives,
844
+ disagreementPreference: interview.disagreementPreference,
845
+ setup: existingSetup
846
+ });
847
+ if (args.json === true) {
848
+ console.log(JSON.stringify({
849
+ project: context.project,
850
+ session: context.session,
851
+ projectFilePath: context.projectFilePath,
852
+ sessionFilePath: context.sessionFilePath
853
+ }, null, 2));
747
854
  return;
748
855
  }
749
- const rl = createInterface({ input, output });
750
- try {
751
- const action = await promptStartAction(rl, existingSetup.profileSeed.currentBlocker ?? existingSetup.profileSeed.currentResearchTopic);
752
- if (action === "status") {
753
- rl.close();
754
- await runCodexSubcommand("status", args);
755
- return;
756
- }
757
- if (action === "roles") {
758
- rl.close();
759
- await runRoles(args);
760
- return;
761
- }
762
- const defaultPrompt = existingSetup.profileSeed.currentBlocker ??
763
- existingSetup.profileSeed.currentResearchTopic ??
764
- "";
765
- const prompt = (await rl.question(defaultPrompt
766
- ? `What are you working on today?\nPress Enter to use: ${defaultPrompt}\n> `
767
- : "What are you working on today?\n> ")).trim() || defaultPrompt;
768
- rl.close();
769
- if (!prompt) {
770
- throw new Error("A prompt is required.");
771
- }
772
- if (action === "editor") {
773
- await runModeCommand("review", {
774
- ...args,
775
- prompt,
776
- role: "editor",
777
- ...(setupPath ? { setup: setupPath } : {})
778
- });
779
- return;
780
- }
781
- if (action === "methods") {
782
- await runModeCommand("review", {
783
- ...args,
784
- prompt,
785
- role: "methods_critic",
786
- ...(setupPath ? { setup: setupPath } : {})
787
- });
788
- return;
789
- }
790
- if (action === "panel") {
791
- await runModeCommand("review", {
792
- ...args,
793
- prompt,
794
- panel: true,
795
- "show-conflicts": true,
796
- ...(setupPath ? { setup: setupPath } : {})
797
- });
798
- return;
799
- }
800
- const mode = action;
801
- await runModeCommand(mode, {
802
- ...args,
803
- prompt,
804
- ...(setupPath ? { setup: setupPath } : {})
805
- });
806
- }
807
- finally {
808
- rl.close();
809
- }
856
+ console.log(renderProjectWorkspaceSummary(context));
857
+ console.log("");
858
+ console.log("Next step:");
859
+ console.log(`- cd "${context.project.projectPath}"`);
860
+ console.log("- start Codex in that directory if you want a plain Codex session that inherits Long Table project instructions");
861
+ console.log(`- or run: longtable ask --cwd "${context.project.projectPath}" --prompt "${context.session.currentGoal.replaceAll("\"", "\\\"")}"`);
862
+ console.log("- the workspace now includes `.longtable/` memory files and a project-scoped `AGENTS.md`");
810
863
  }
811
864
  async function runCodexSubcommand(subcommand, args) {
812
865
  const customDir = typeof args.dir === "string" ? args.dir : undefined;
@@ -0,0 +1,47 @@
1
+ import type { SetupPersistedOutput } from "@diverga/setup";
2
+ export type ProjectDisagreementPreference = "synthesis_only" | "show_on_conflict" | "always_visible";
3
+ export interface LongTableProjectRecord {
4
+ schemaVersion: 1;
5
+ product: "Long Table";
6
+ projectName: string;
7
+ projectPath: string;
8
+ createdAt: string;
9
+ globalSetupSummary: {
10
+ field: string;
11
+ careerStage: string;
12
+ experienceLevel: string;
13
+ checkpointIntensity: string;
14
+ humanAuthorshipSignal?: string;
15
+ weakestDomain?: string;
16
+ defaultPanelPreference?: ProjectDisagreementPreference;
17
+ };
18
+ }
19
+ export interface LongTableSessionRecord {
20
+ schemaVersion: 1;
21
+ id: string;
22
+ createdAt: string;
23
+ projectName: string;
24
+ projectPath: string;
25
+ currentGoal: string;
26
+ currentBlocker?: string;
27
+ requestedPerspectives: string[];
28
+ disagreementPreference: ProjectDisagreementPreference;
29
+ }
30
+ export interface LongTableProjectContext {
31
+ project: LongTableProjectRecord;
32
+ session: LongTableSessionRecord;
33
+ projectFilePath: string;
34
+ sessionFilePath: string;
35
+ metaDir: string;
36
+ }
37
+ export declare function createOrUpdateProjectWorkspace(options: {
38
+ projectName: string;
39
+ projectPath: string;
40
+ currentGoal: string;
41
+ currentBlocker?: string;
42
+ requestedPerspectives: string[];
43
+ disagreementPreference: ProjectDisagreementPreference;
44
+ setup: SetupPersistedOutput;
45
+ }): Promise<LongTableProjectContext>;
46
+ export declare function loadProjectContextFromDirectory(startPath: string): Promise<LongTableProjectContext | null>;
47
+ export declare function renderProjectWorkspaceSummary(context: LongTableProjectContext): string;
@@ -0,0 +1,207 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { createEmptyResearchState } from "@diverga/memory";
5
+ function nowIso() {
6
+ return new Date().toISOString();
7
+ }
8
+ function slugify(input) {
9
+ return input
10
+ .trim()
11
+ .toLowerCase()
12
+ .replace(/[^a-z0-9가-힣]+/g, "-")
13
+ .replace(/^-+|-+$/g, "")
14
+ .slice(0, 80);
15
+ }
16
+ function resolveMetaDir(projectPath) {
17
+ return join(projectPath, ".longtable");
18
+ }
19
+ function buildWorkspaceGuide(project, session) {
20
+ const lines = [
21
+ "# Long Table Workspace",
22
+ "",
23
+ "This directory is a Long Table research workspace.",
24
+ "",
25
+ "## Current project",
26
+ `- Project: ${project.projectName}`,
27
+ `- Goal right now: ${session.currentGoal}`,
28
+ ...(session.currentBlocker ? [`- Current blocker: ${session.currentBlocker}`] : []),
29
+ `- Disagreement visibility: ${session.disagreementPreference}`,
30
+ `- Requested perspectives: ${session.requestedPerspectives.length > 0 ? session.requestedPerspectives.join(", ") : "auto"}`,
31
+ "",
32
+ "## How Codex should behave here",
33
+ "- Treat this as a researcher-facing Long Table session, not a generic coding task.",
34
+ "- Ask clarifying or tension questions before closing too early.",
35
+ "- If you foreground specific roles, disclose them with `Long Table consulted: ...`.",
36
+ "- Keep one accountable synthesis, but keep disagreement visible by default when it matters.",
37
+ "- Do not expose internal tool logs or process commentary in researcher-facing answers.",
38
+ "",
39
+ "## Session files",
40
+ "- `.longtable/project.json` contains project-level metadata.",
41
+ "- `.longtable/current-session.json` contains the current session goal and blocker."
42
+ ];
43
+ return lines.join("\n");
44
+ }
45
+ function buildProjectAgentsMd(project, session) {
46
+ return [
47
+ "# AGENTS.md",
48
+ "",
49
+ "This directory is a Long Table research workspace.",
50
+ "",
51
+ "## Purpose",
52
+ `- Project name: ${project.projectName}`,
53
+ `- Current goal: ${session.currentGoal}`,
54
+ ...(session.currentBlocker ? [`- Current blocker: ${session.currentBlocker}`] : []),
55
+ `- Requested perspectives: ${session.requestedPerspectives.length > 0 ? session.requestedPerspectives.join(", ") : "auto"}`,
56
+ `- Disagreement visibility: ${session.disagreementPreference}`,
57
+ "",
58
+ "## Research-facing behavior",
59
+ "- Treat researcher interaction as the primary task.",
60
+ "- Begin exploratory work with clarifying or tension questions before recommending a direction.",
61
+ "- If you foreground role perspectives, disclose them with `Long Table consulted: ...`.",
62
+ "- Keep one accountable synthesis, but do not hide meaningful disagreement.",
63
+ "- Do not expose internal tool logs, file-search traces, or process commentary in the researcher-facing answer.",
64
+ "",
65
+ "## Session memory",
66
+ "- Read `.longtable/current-session.json` before giving substantial guidance.",
67
+ "- Use `.longtable/project.json` as the project-level context.",
68
+ "- Prefer the current goal and blocker over generic assumptions.",
69
+ "",
70
+ "## Scope",
71
+ "- These instructions apply to this directory and its children."
72
+ ].join("\n");
73
+ }
74
+ function buildStateSeed(project, session, setup) {
75
+ const state = createEmptyResearchState();
76
+ state.explicitState = {
77
+ field: setup.profileSeed.field,
78
+ careerStage: setup.profileSeed.careerStage,
79
+ experienceLevel: setup.profileSeed.experienceLevel,
80
+ projectName: project.projectName,
81
+ currentGoal: session.currentGoal,
82
+ disagreementPreference: session.disagreementPreference,
83
+ requestedPerspectives: session.requestedPerspectives
84
+ };
85
+ if (session.currentBlocker) {
86
+ state.explicitState.currentBlocker = session.currentBlocker;
87
+ state.openTensions.push(session.currentBlocker);
88
+ }
89
+ if (setup.profileSeed.humanAuthorshipSignal) {
90
+ state.explicitState.humanAuthorshipSignal = setup.profileSeed.humanAuthorshipSignal;
91
+ }
92
+ state.narrativeTraces.push({
93
+ id: "project-session-goal",
94
+ timestamp: nowIso(),
95
+ source: "longtable-start",
96
+ traceType: "judgment",
97
+ summary: `Current session goal: ${session.currentGoal}.`,
98
+ visibility: "explicit",
99
+ importance: "high"
100
+ });
101
+ if (session.currentBlocker) {
102
+ state.narrativeTraces.push({
103
+ id: "project-session-blocker",
104
+ timestamp: nowIso(),
105
+ source: "longtable-start",
106
+ traceType: "tension",
107
+ summary: `Current session blocker: ${session.currentBlocker}.`,
108
+ visibility: "explicit",
109
+ importance: "high"
110
+ });
111
+ }
112
+ return JSON.stringify(state, null, 2);
113
+ }
114
+ export async function createOrUpdateProjectWorkspace(options) {
115
+ const projectPath = resolve(options.projectPath);
116
+ const metaDir = resolveMetaDir(projectPath);
117
+ const sessionsDir = join(metaDir, "sessions");
118
+ const projectFilePath = join(metaDir, "project.json");
119
+ const sessionFilePath = join(metaDir, "current-session.json");
120
+ const sessionId = slugify(`${options.projectName}-${Date.now()}`);
121
+ await mkdir(projectPath, { recursive: true });
122
+ await mkdir(metaDir, { recursive: true });
123
+ await mkdir(sessionsDir, { recursive: true });
124
+ const project = existsSync(projectFilePath)
125
+ ? JSON.parse(await readFile(projectFilePath, "utf8"))
126
+ : {
127
+ schemaVersion: 1,
128
+ product: "Long Table",
129
+ projectName: options.projectName,
130
+ projectPath,
131
+ createdAt: nowIso(),
132
+ globalSetupSummary: {
133
+ field: options.setup.profileSeed.field,
134
+ careerStage: options.setup.profileSeed.careerStage,
135
+ experienceLevel: options.setup.profileSeed.experienceLevel,
136
+ checkpointIntensity: options.setup.profileSeed.preferredCheckpointIntensity,
137
+ ...(options.setup.profileSeed.humanAuthorshipSignal
138
+ ? { humanAuthorshipSignal: options.setup.profileSeed.humanAuthorshipSignal }
139
+ : {}),
140
+ ...(options.setup.profileSeed.weakestDomain
141
+ ? { weakestDomain: options.setup.profileSeed.weakestDomain }
142
+ : {}),
143
+ ...(options.setup.profileSeed.panelPreference
144
+ ? { defaultPanelPreference: options.setup.profileSeed.panelPreference }
145
+ : {})
146
+ }
147
+ };
148
+ const session = {
149
+ schemaVersion: 1,
150
+ id: sessionId,
151
+ createdAt: nowIso(),
152
+ projectName: project.projectName,
153
+ projectPath,
154
+ currentGoal: options.currentGoal,
155
+ ...(options.currentBlocker ? { currentBlocker: options.currentBlocker } : {}),
156
+ requestedPerspectives: options.requestedPerspectives,
157
+ disagreementPreference: options.disagreementPreference
158
+ };
159
+ await writeFile(projectFilePath, JSON.stringify(project, null, 2), "utf8");
160
+ await writeFile(sessionFilePath, JSON.stringify(session, null, 2), "utf8");
161
+ await writeFile(join(sessionsDir, `${sessionId}.json`), JSON.stringify(session, null, 2), "utf8");
162
+ await writeFile(join(metaDir, "state.json"), buildStateSeed(project, session, options.setup), "utf8");
163
+ await writeFile(join(projectPath, "LONGTABLE.md"), buildWorkspaceGuide(project, session), "utf8");
164
+ await writeFile(join(projectPath, "AGENTS.md"), buildProjectAgentsMd(project, session), "utf8");
165
+ return {
166
+ project,
167
+ session,
168
+ projectFilePath,
169
+ sessionFilePath,
170
+ metaDir
171
+ };
172
+ }
173
+ export async function loadProjectContextFromDirectory(startPath) {
174
+ let current = resolve(startPath);
175
+ while (true) {
176
+ const metaDir = resolveMetaDir(current);
177
+ const projectFilePath = join(metaDir, "project.json");
178
+ const sessionFilePath = join(metaDir, "current-session.json");
179
+ if (existsSync(projectFilePath) && existsSync(sessionFilePath)) {
180
+ return {
181
+ project: JSON.parse(await readFile(projectFilePath, "utf8")),
182
+ session: JSON.parse(await readFile(sessionFilePath, "utf8")),
183
+ projectFilePath,
184
+ sessionFilePath,
185
+ metaDir
186
+ };
187
+ }
188
+ const parent = dirname(current);
189
+ if (parent === current) {
190
+ return null;
191
+ }
192
+ current = parent;
193
+ }
194
+ }
195
+ export function renderProjectWorkspaceSummary(context) {
196
+ return [
197
+ "Long Table project workspace",
198
+ `project: ${context.project.projectName}`,
199
+ `path: ${context.project.projectPath}`,
200
+ `goal: ${context.session.currentGoal}`,
201
+ ...(context.session.currentBlocker ? [`blocker: ${context.session.currentBlocker}`] : []),
202
+ `perspectives: ${context.session.requestedPerspectives.length > 0 ? context.session.requestedPerspectives.join(", ") : "auto"}`,
203
+ `disagreement: ${context.session.disagreementPreference}`,
204
+ `project file: ${context.projectFilePath}`,
205
+ `session file: ${context.sessionFilePath}`
206
+ ].join("\n");
207
+ }
@@ -33,12 +33,13 @@ function promptSpec() {
33
33
  "Ask exactly one setup question at a time.",
34
34
  "Use numbered choices when possible and include a 'None of the above' option when needed.",
35
35
  "Do not move to the next question until the researcher answers the current one.",
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.",
36
+ "Quickstart covers: provider, field, career stage, experience level, checkpoint intensity, and human authorship signal.",
37
+ "Interview also covers: 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, preferredCheckpointIntensity, and optional humanAuthorshipSignal, preferredEntryMode, weakestDomain, panelPreference.",
39
39
  "If the user prefers paste-based setup, tell them they can pipe the JSON into `longtable codex persist-init --stdin --install-prompts`.",
40
40
  "If the researcher asks you to stay inside Codex, keep the conversation in numbered form and do not prematurely close.",
41
41
  "Frame the setup like a short researcher interview, not a bare config form.",
42
+ "Do not pretend that this is the full project-start interview. The real project-start interview happens in `longtable start`.",
42
43
  "Treat any slash-command arguments as context for why setup is being done now."
43
44
  ]
44
45
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longtable/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "description": "Researcher-facing Long Table CLI on top of the legacy Diverga package surface",
6
6
  "type": "module",
@@ -25,8 +25,9 @@
25
25
  "typecheck": "tsc -p tsconfig.json --noEmit"
26
26
  },
27
27
  "dependencies": {
28
+ "@diverga/memory": "0.1.0",
28
29
  "@diverga/provider-codex": "0.1.1",
29
- "@diverga/setup": "0.1.3"
30
+ "@diverga/setup": "0.1.4"
30
31
  },
31
32
  "devDependencies": {
32
33
  "@types/node": "^22.10.1",