@longtable/cli 0.1.8 → 0.1.10

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/dist/cli.js CHANGED
@@ -1,17 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  import { existsSync, readFileSync, statSync } from "node:fs";
3
3
  import { mkdtemp, rm } from "node:fs/promises";
4
+ import { execSync } from "node:child_process";
4
5
  import { emitKeypressEvents } from "node:readline";
5
6
  import { createInterface } from "node:readline/promises";
6
7
  import { stdin as input, stdout as output, cwd, exit } from "node:process";
7
8
  import { dirname, resolve } from "node:path";
8
9
  import { homedir } from "node:os";
9
- import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput, installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultRuntimeConfigPath, resolveDefaultSetupPath, saveSetupAndRuntimeConfig, serializeSetupOutput } from "@diverga/setup";
10
- import { buildCodexThinWrappedPrompt, runCodexThinWrapper } from "@diverga/provider-codex";
10
+ import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput, installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultRuntimeConfigPath, resolveDefaultSetupPath, saveSetupAndRuntimeConfig, serializeSetupOutput, writeRuntimeConfig } from "@longtable/setup";
11
+ import { buildCodexSkillSpecs, buildCodexThinWrappedPrompt, installCodexSkills, listInstalledCodexSkills, renderQuestionRecordPrompt, removeCodexSkills, resolveCodexSkillsDir, runCodexThinWrapper } from "@longtable/provider-codex";
12
+ import { buildClaudeSkillSpecs, installClaudeSkills, listInstalledClaudeSkills, renderQuestionRecordInput, removeClaudeSkills, resolveClaudeSkillsDir } from "@longtable/provider-claude";
11
13
  import { installCodexPromptAliases, listInstalledCodexPromptAliases, removeCodexPromptAliases, resolveCodexPromptsDir } from "./prompt-aliases.js";
12
14
  import { buildPersonaGuidance, parseInvocationDirective } from "./persona-router.js";
13
- import { PERSONA_DEFINITIONS } from "./personas.js";
14
- import { createOrUpdateProjectWorkspace, loadProjectContextFromDirectory, renderProjectWorkspaceSummary } from "./project-session.js";
15
+ import { PERSONA_DEFINITIONS, listRoleDefinitions } from "./personas.js";
16
+ import { buildPanelFallback, renderPanelSummary } from "./panel.js";
17
+ import { appendInvocationRecordToWorkspace, assertWorkspaceNotBlocked, answerWorkspaceQuestion, createWorkspaceQuestion, createOrUpdateProjectWorkspace, inspectProjectWorkspace, loadProjectContextFromDirectory, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
15
18
  const VALID_MODES = new Set([
16
19
  "explore",
17
20
  "review",
@@ -69,28 +72,40 @@ function usage() {
69
72
  " Run `longtable ...` in your terminal, not inside the Codex chat box.",
70
73
  " After `longtable start`, move into the created project directory and open `codex` there.",
71
74
  "",
72
- " 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]",
75
+ " 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-skills] [--install-prompts]",
73
76
  " longtable start [--path <dir>] [--name <project>] [--goal <text>] [--blocker <text>] [--perspectives <role[,role]>] [--disagreement synthesis_only|show_on_conflict|always_visible] [--setup <path>] [--json]",
74
77
  " longtable resume [--cwd <path>] [--json]",
78
+ " longtable doctor [--cwd <path>] [--fix] [--json] [--codex-dir <path>] [--claude-dir <path>] [--codex-prompts-dir <path>] [--codex-runtime-path <file>] [--claude-runtime-path <file>]",
79
+ " longtable status [--cwd <path>] [--fix] [--json] [--codex-dir <path>] [--claude-dir <path>] [--codex-prompts-dir <path>] [--codex-runtime-path <file>] [--claude-runtime-path <file>]",
75
80
  " longtable roles [--json]",
76
81
  " longtable show [--json] [--path <file>]",
77
82
  " longtable install [--json] [--path <file>] [--runtime-path <file>]",
78
83
  " longtable ask [--prompt <text>] [--print] [--json] [--setup <path>] [--cwd <path>]",
84
+ " longtable question --prompt <decision-context> [--title <text>] [--text <question>] [--provider codex|claude] [--required|--advisory] [--print] [--cwd <path>] [--json]",
85
+ " longtable panel [--prompt <text>] [--role <role[,role]>] [--mode review|critique|draft|commit] [--visibility synthesis_only|show_on_conflict|always_visible] [--print] [--json] [--setup <path>] [--cwd <path>]",
86
+ " longtable decide [--question <id>] --answer <value-or-text> [--rationale <text>] [--provider codex|claude] [--cwd <path>] [--json]",
79
87
  " 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>]",
80
- " longtable codex persist-init [--answers-json <json> | --stdin | full setup flags] [--install-prompts] [--json]",
88
+ " longtable codex persist-init [--answers-json <json> | --stdin | full setup flags] [--install-skills] [--install-prompts] [--json]",
89
+ " longtable codex install-skills [--dir <path>]",
90
+ " longtable codex remove-skills [--dir <path>]",
81
91
  " longtable codex install-prompts [--dir <path>]",
82
92
  " longtable codex remove-prompts [--dir <path>]",
83
93
  " longtable codex status [--dir <path>] [--json]",
94
+ " longtable claude install-skills [--dir <path>]",
95
+ " longtable claude remove-skills [--dir <path>]",
96
+ " longtable claude status [--dir <path>] [--json]",
84
97
  "",
85
98
  "Examples:",
86
- " longtable init --flow interview --install-prompts",
99
+ " longtable init --flow interview --provider codex --install-skills",
87
100
  " longtable start",
88
101
  " longtable start --path ~/Research/My-Project --name \"AI Adoption Meta-Analysis\" --goal \"Narrow the review question\"",
89
102
  " cd \"<project-path>\" && codex",
103
+ " longtable doctor",
90
104
  " longtable roles",
91
105
  " longtable ask --prompt \"연구를 시작하고 싶어. 지금 어디서부터 좁혀야 할지 모르겠어.\"",
92
- " printf '{\"provider\":\"codex\",...}' | longtable codex persist-init --stdin --install-prompts",
93
- " longtable codex install-prompts"
106
+ " printf '{\"provider\":\"codex\",...}' | longtable codex persist-init --stdin --install-skills",
107
+ " longtable codex install-skills",
108
+ " longtable claude install-skills"
94
109
  ].join("\n");
95
110
  }
96
111
  function parseArgs(argv) {
@@ -98,13 +113,13 @@ function parseArgs(argv) {
98
113
  const values = {};
99
114
  let subcommand = maybeSubcommand;
100
115
  const modeCommand = command && VALID_MODES.has(command);
101
- const directCommand = command && ["init", "start", "resume", "roles", "show", "install", "codex", "ask"].includes(command);
116
+ const directCommand = command && ["init", "start", "resume", "doctor", "status", "roles", "show", "install", "codex", "claude", "ask", "question", "panel", "decide"].includes(command);
102
117
  let startIndex = 1;
103
118
  if (modeCommand) {
104
119
  subcommand = undefined;
105
120
  startIndex = 1;
106
121
  }
107
- else if (command === "codex") {
122
+ else if (command === "codex" || command === "claude") {
108
123
  startIndex = 2;
109
124
  }
110
125
  else if (directCommand) {
@@ -155,11 +170,11 @@ function buildSetupFlowChoices() {
155
170
  ];
156
171
  }
157
172
  function renderSetupHeader(flow) {
158
- const title = flow === "interview" ? "Long Table Setup Interview" : "Long Table Quickstart";
173
+ const title = flow === "interview" ? "LongTable Setup Interview" : "LongTable Quickstart";
159
174
  const subtitle = flow === "interview"
160
175
  ? "We will ask about your research persona, challenge preferences, and authorship defaults."
161
- : "We will capture the minimum profile needed to start using Long Table.";
162
- return [renderBrandBanner("Long Table", "Research workspace setup"), "", renderSectionCard(title, [subtitle])].join("\n");
176
+ : "We will capture the minimum profile needed to start using LongTable.";
177
+ return [renderBrandBanner("LongTable", "Research workspace setup"), "", renderSectionCard(title, [subtitle])].join("\n");
163
178
  }
164
179
  function renderQuestionHeader(index, total, section, prompt) {
165
180
  return [
@@ -176,7 +191,7 @@ function questionSection(questionId) {
176
191
  return "Interaction style";
177
192
  }
178
193
  if (questionId === "weakestDomain" || questionId === "panelPreference") {
179
- return "How Long Table should challenge you";
194
+ return "How LongTable should challenge you";
180
195
  }
181
196
  return "Authorship and voice";
182
197
  }
@@ -253,14 +268,14 @@ async function verifyWritableWorkspaceParent(projectPath) {
253
268
  catch (error) {
254
269
  const message = error instanceof Error ? error.message : String(error);
255
270
  throw new Error([
256
- `Long Table could not create a project workspace under: ${parentDir}`,
271
+ `LongTable could not create a project workspace under: ${parentDir}`,
257
272
  "Reason: your current shell process cannot write to that parent directory.",
258
273
  "",
259
274
  "What to try next:",
260
275
  `- test it directly: mkdir -p "${resolve(parentDir, "_longtable_write_test")}"`,
261
- "- if that fails too, this is an OS/disk permission problem rather than a Long Table command problem",
276
+ "- if that fails too, this is an OS/disk permission problem rather than a LongTable command problem",
262
277
  "- try a known-writable path such as ~/Research",
263
- "- or choose a different existing parent directory when Long Table asks where the project should live",
278
+ "- or choose a different existing parent directory when LongTable asks where the project should live",
264
279
  "",
265
280
  `Original error: ${message}`
266
281
  ].join("\n"));
@@ -481,7 +496,7 @@ async function collectInteractiveAnswers(initialFlow) {
481
496
  const rl = createInterface({ input, output });
482
497
  try {
483
498
  const flow = initialFlow ??
484
- (await promptChoice(rl, "How would you like to set up Long Table?", buildSetupFlowChoices()));
499
+ (await promptChoice(rl, "How would you like to set up LongTable?", buildSetupFlowChoices()));
485
500
  console.log("");
486
501
  console.log(renderSetupHeader(flow));
487
502
  console.log("");
@@ -559,11 +574,11 @@ async function collectProjectInterview(setup, args) {
559
574
  try {
560
575
  if (needsInteractivePrompts) {
561
576
  console.log("");
562
- console.log(renderBrandBanner("Long Table", "Project workspace interview"));
577
+ console.log(renderBrandBanner("LongTable", "Project workspace interview"));
563
578
  console.log("");
564
- console.log(renderSectionCard("Long Table Project Start", [
579
+ console.log(renderSectionCard("LongTable Project Start", [
565
580
  "We will create a project workspace and a session memory seed for today's work.",
566
- "At the end, Long Table will tell you exactly which directory to open in Codex."
581
+ "At the end, LongTable will tell you exactly which directory to open in Codex."
567
582
  ]));
568
583
  console.log("");
569
584
  }
@@ -575,7 +590,7 @@ async function collectProjectInterview(setup, args) {
575
590
  const suggestedPath = resolveInteractiveProjectPath(suggestedParentDir, projectName);
576
591
  const projectPath = (typeof args.path === "string" && args.path.trim()
577
592
  ? normalizeUserPath(args.path.trim())
578
- : resolveInteractiveProjectPath((await promptText(rl, renderQuestionHeader(2, 6, "Project interview", `Which parent directory should contain this project?\nLong Table will create this folder:\n${suggestedPath}`), true)), projectName));
593
+ : resolveInteractiveProjectPath((await promptText(rl, renderQuestionHeader(2, 6, "Project interview", `Which parent directory should contain this project?\nLongTable will create this folder:\n${suggestedPath}`), true)), projectName));
579
594
  const currentGoal = (typeof args.goal === "string" && args.goal.trim()) ||
580
595
  (await promptText(rl, renderQuestionHeader(3, 6, "Current session", "What are you trying to accomplish in this session?"), true));
581
596
  const currentBlocker = (typeof args.blocker === "string" && args.blocker.trim()) ||
@@ -588,7 +603,7 @@ async function collectProjectInterview(setup, args) {
588
603
  {
589
604
  id: "synthesis_only",
590
605
  label: "Synthesis only",
591
- description: "Show one Long Table answer unless I ask for more."
606
+ description: "Show one LongTable answer unless I ask for more."
592
607
  },
593
608
  {
594
609
  id: "show_on_conflict",
@@ -667,9 +682,11 @@ async function runInit(args) {
667
682
  const json = args.json === true;
668
683
  const installRuntime = args["no-install"] !== true;
669
684
  const installPrompts = args["install-prompts"] === true;
685
+ const installSkills = args["install-skills"] === true;
670
686
  const customPath = typeof args.path === "string" ? args.path : undefined;
671
687
  const runtimePath = typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined;
672
688
  const promptsDir = typeof args.dir === "string" ? args.dir : undefined;
689
+ const skillsDir = typeof args["skills-dir"] === "string" ? args["skills-dir"] : promptsDir;
673
690
  const { flow, provider, answers } = hasCompleteFlagInput(args)
674
691
  ? {
675
692
  flow: resolveSetupFlow(args),
@@ -686,8 +703,23 @@ async function runInit(args) {
686
703
  if (provider === "codex" && installPrompts) {
687
704
  installedPrompts = await installCodexPromptAliases(promptsDir);
688
705
  }
706
+ let installedSkills = [];
707
+ if (provider === "codex" && installSkills) {
708
+ installedSkills = await installCodexSkills(listRoleDefinitions(), skillsDir);
709
+ }
710
+ if (provider === "claude" && installSkills) {
711
+ installedSkills = await installClaudeSkills(listRoleDefinitions(), skillsDir);
712
+ }
689
713
  if (json) {
690
- console.log(serializeSetupOutput(outputValue));
714
+ if (installedPrompts.length === 0 && installedSkills.length === 0) {
715
+ console.log(serializeSetupOutput(outputValue));
716
+ return;
717
+ }
718
+ console.log(JSON.stringify({
719
+ setup: outputValue,
720
+ installedPrompts: installedPrompts.map((prompt) => prompt.name),
721
+ installedSkills: installedSkills.map((skill) => skill.name)
722
+ }, null, 2));
691
723
  return;
692
724
  }
693
725
  console.log(renderSetupSummary(outputValue));
@@ -699,17 +731,32 @@ async function runInit(args) {
699
731
  console.log("");
700
732
  console.log("Installed Codex prompt files:");
701
733
  for (const prompt of installedPrompts) {
702
- console.log(`- /prompts:${prompt.name}`);
734
+ console.log(`- ${prompt.name}`);
735
+ }
736
+ console.log(" Note: prompt files are legacy and may not be exposed by your Codex build.");
737
+ }
738
+ if (installedSkills.length > 0) {
739
+ console.log("");
740
+ console.log(`Installed ${provider === "codex" ? "Codex" : "Claude"} skill files:`);
741
+ for (const skill of installedSkills) {
742
+ console.log(`- ${skill.name}`);
703
743
  }
704
- console.log(" Note: whether Codex exposes these as slash commands depends on your Codex build.");
744
+ console.log(" Use these by naming LongTable naturally, e.g. `lt panel: ...`.");
705
745
  }
706
746
  if (provider === "codex") {
707
747
  console.log("");
708
748
  console.log("Next step:");
709
749
  console.log("- Start here: `longtable start`.");
710
750
  console.log("- If you want a direct natural-language entry: `longtable ask --prompt \"...\"`.");
711
- console.log("- Codex prompt files are available as an experimental integration, not the primary path.");
712
- console.log("- Suggested next action: create a project workspace and let Long Table interview the current session.");
751
+ console.log("- Codex skills are the preferred native surface. Prompt files are legacy and may not expose slash commands.");
752
+ console.log("- Suggested next action: create a project workspace and let LongTable interview the current session.");
753
+ }
754
+ if (provider === "claude") {
755
+ console.log("");
756
+ console.log("Next step:");
757
+ console.log("- Start here: `longtable start`.");
758
+ console.log("- In Claude Code, use natural language such as `lt explore: ...` or `lt panel: ...`.");
759
+ console.log("- Claude skills are adapter output from LongTable roles, not the source of truth.");
713
760
  }
714
761
  }
715
762
  async function runShow(args) {
@@ -731,6 +778,300 @@ async function runInstall(args) {
731
778
  }
732
779
  console.log(renderInstallSummary(result));
733
780
  }
781
+ function commandOnPath(command) {
782
+ try {
783
+ execSync(`command -v ${command}`, { stdio: "ignore" });
784
+ return true;
785
+ }
786
+ catch {
787
+ return false;
788
+ }
789
+ }
790
+ function missingNames(expected, installed) {
791
+ const installedSet = new Set(installed);
792
+ return expected.filter((name) => !installedSet.has(name));
793
+ }
794
+ function setupForProvider(setup, provider) {
795
+ return {
796
+ ...setup,
797
+ providerSelection: provider === "claude"
798
+ ? {
799
+ provider,
800
+ checkpointProtocol: "native_structured",
801
+ supportsStructuredQuestions: true
802
+ }
803
+ : {
804
+ provider,
805
+ checkpointProtocol: "numbered",
806
+ supportsStructuredQuestions: false
807
+ }
808
+ };
809
+ }
810
+ async function collectDoctorStatus(args) {
811
+ const roles = listRoleDefinitions();
812
+ const setupOverride = typeof args.setup === "string"
813
+ ? args.setup
814
+ : typeof args.path === "string"
815
+ ? args.path
816
+ : undefined;
817
+ const setupPath = resolveDefaultSetupPath(setupOverride).path;
818
+ const codexRuntimeOverride = typeof args["codex-runtime-path"] === "string"
819
+ ? args["codex-runtime-path"]
820
+ : undefined;
821
+ const claudeRuntimeOverride = typeof args["claude-runtime-path"] === "string"
822
+ ? args["claude-runtime-path"]
823
+ : undefined;
824
+ const codexDir = typeof args["codex-dir"] === "string"
825
+ ? args["codex-dir"]
826
+ : typeof args.dir === "string"
827
+ ? args.dir
828
+ : undefined;
829
+ const codexPromptsDir = typeof args["codex-prompts-dir"] === "string"
830
+ ? args["codex-prompts-dir"]
831
+ : typeof args["prompts-dir"] === "string"
832
+ ? args["prompts-dir"]
833
+ : typeof args.dir === "string"
834
+ ? args.dir
835
+ : undefined;
836
+ const claudeDir = typeof args["claude-dir"] === "string"
837
+ ? args["claude-dir"]
838
+ : typeof args.dir === "string"
839
+ ? args.dir
840
+ : undefined;
841
+ const codexRuntimePath = resolveDefaultRuntimeConfigPath("codex", codexRuntimeOverride).path;
842
+ const claudeRuntimePath = resolveDefaultRuntimeConfigPath("claude", claudeRuntimeOverride).path;
843
+ const expectedCodexSkills = buildCodexSkillSpecs(roles).map((skill) => skill.name);
844
+ const expectedClaudeSkills = buildClaudeSkillSpecs(roles).map((skill) => skill.name);
845
+ const [codexSkills, claudeSkills, codexAliases, workspace] = await Promise.all([
846
+ listInstalledCodexSkills(roles, codexDir),
847
+ listInstalledClaudeSkills(roles, claudeDir),
848
+ listInstalledCodexPromptAliases(codexPromptsDir),
849
+ inspectProjectWorkspace(typeof args.cwd === "string" ? args.cwd : cwd())
850
+ ]);
851
+ const installedCodexSkills = codexSkills.map((skill) => skill.name);
852
+ const installedClaudeSkills = claudeSkills.map((skill) => skill.name);
853
+ return {
854
+ setupPath,
855
+ setupExists: existsSync(setupPath),
856
+ providers: {
857
+ codex: {
858
+ command: "codex",
859
+ commandOnPath: commandOnPath("codex"),
860
+ runtimePath: codexRuntimePath,
861
+ runtimeExists: existsSync(codexRuntimePath),
862
+ skillsDir: resolveCodexSkillsDir(codexDir),
863
+ expectedSkills: expectedCodexSkills,
864
+ installedSkills: installedCodexSkills,
865
+ missingSkills: missingNames(expectedCodexSkills, installedCodexSkills),
866
+ promptsDir: resolveCodexPromptsDir(codexPromptsDir),
867
+ legacyPromptFilesInstalled: codexAliases.map((alias) => alias.name)
868
+ },
869
+ claude: {
870
+ command: "claude",
871
+ commandOnPath: commandOnPath("claude"),
872
+ runtimePath: claudeRuntimePath,
873
+ runtimeExists: existsSync(claudeRuntimePath),
874
+ skillsDir: resolveClaudeSkillsDir(claudeDir),
875
+ expectedSkills: expectedClaudeSkills,
876
+ installedSkills: installedClaudeSkills,
877
+ missingSkills: missingNames(expectedClaudeSkills, installedClaudeSkills)
878
+ }
879
+ },
880
+ workspace
881
+ };
882
+ }
883
+ function renderProviderDoctorBlock(label, provider) {
884
+ const expectedCount = provider.expectedSkills.length;
885
+ const installedCount = provider.installedSkills.length;
886
+ return [
887
+ `${label}:`,
888
+ `- command: ${provider.commandOnPath ? "present" : "missing"} (${provider.command})`,
889
+ `- runtime artifact: ${provider.runtimeExists ? "present" : "missing"} (${provider.runtimePath})`,
890
+ `- skills: ${installedCount}/${expectedCount} installed (${provider.skillsDir})`,
891
+ ...(provider.missingSkills.length > 0
892
+ ? [`- missing skills: ${provider.missingSkills.join(", ")}`]
893
+ : ["- missing skills: none"])
894
+ ];
895
+ }
896
+ function renderDoctorStatus(status) {
897
+ const lines = [
898
+ "LongTable doctor",
899
+ `- setup: ${status.setupExists ? "present" : "missing"} (${status.setupPath})`,
900
+ "",
901
+ ...renderProviderDoctorBlock("Codex", status.providers.codex),
902
+ `- legacy prompt files: ${status.providers.codex.legacyPromptFilesInstalled.length}`,
903
+ ...(status.providers.codex.legacyPromptFilesInstalled.length > 0
904
+ ? [`- legacy prompt names: ${status.providers.codex.legacyPromptFilesInstalled.join(", ")}`]
905
+ : []),
906
+ "",
907
+ ...renderProviderDoctorBlock("Claude", status.providers.claude),
908
+ "",
909
+ "Workspace:"
910
+ ];
911
+ if (!status.workspace.found) {
912
+ lines.push("- project: not found from current directory");
913
+ }
914
+ else {
915
+ const workspace = status.workspace;
916
+ lines.push(`- project: ${workspace.project?.name ?? "unknown"}`, `- root: ${workspace.rootPath ?? "unknown"}`, `- goal: ${workspace.session?.currentGoal ?? "unknown"}`, `- invocations: ${workspace.counts?.invocations ?? 0}`, `- questions: ${workspace.counts?.questions ?? 0} (${workspace.counts?.pendingQuestions ?? 0} pending, ${workspace.counts?.answeredQuestions ?? 0} answered)`, `- decisions: ${workspace.counts?.decisions ?? 0}`);
917
+ if ((workspace.recentInvocations ?? []).length > 0) {
918
+ lines.push("- recent invocations:");
919
+ for (const invocation of workspace.recentInvocations ?? []) {
920
+ const roles = invocation.roles.length > 0 ? invocation.roles.join(",") : "auto";
921
+ lines.push(` - ${invocation.kind}/${invocation.mode} via ${invocation.surface}: ${roles} (${invocation.status})`);
922
+ }
923
+ }
924
+ if ((workspace.pendingQuestions ?? []).length > 0) {
925
+ lines.push("- pending questions:");
926
+ for (const question of workspace.pendingQuestions ?? []) {
927
+ lines.push(` - ${question.id}: ${question.question} (${question.options.join("/")})`);
928
+ }
929
+ }
930
+ }
931
+ const nextActions = [];
932
+ const canFix = status.providers.codex.missingSkills.length > 0 ||
933
+ status.providers.claude.missingSkills.length > 0 ||
934
+ status.providers.codex.legacyPromptFilesInstalled.length > 0 ||
935
+ (status.setupExists &&
936
+ (!status.providers.codex.runtimeExists || !status.providers.claude.runtimeExists));
937
+ if (canFix) {
938
+ nextActions.push("longtable doctor --fix");
939
+ }
940
+ if (!status.setupExists) {
941
+ nextActions.push("longtable init --flow interview --provider codex --install-skills");
942
+ }
943
+ if (!status.workspace.found) {
944
+ nextActions.push("longtable start");
945
+ }
946
+ const firstQuestion = status.workspace.pendingQuestions?.[0];
947
+ if (firstQuestion) {
948
+ nextActions.push(`longtable decide --question ${firstQuestion.id} --answer <value>`);
949
+ }
950
+ if (nextActions.length > 0) {
951
+ lines.push("", "Next actions:");
952
+ for (const action of nextActions) {
953
+ lines.push(`- ${action}`);
954
+ }
955
+ }
956
+ return lines.join("\n");
957
+ }
958
+ function renderRepairSummary(repair) {
959
+ const lines = ["LongTable doctor repair"];
960
+ if (repair.installedCodexSkills.length > 0) {
961
+ lines.push(`- installed Codex skills: ${repair.installedCodexSkills.length}`);
962
+ }
963
+ if (repair.installedClaudeSkills.length > 0) {
964
+ lines.push(`- installed Claude skills: ${repair.installedClaudeSkills.length}`);
965
+ }
966
+ if (repair.removedLegacyPromptFiles.length > 0) {
967
+ lines.push(`- removed legacy prompt files: ${repair.removedLegacyPromptFiles.length}`);
968
+ }
969
+ if (repair.writtenRuntimeConfigs.length > 0) {
970
+ lines.push("- wrote runtime configs:");
971
+ for (const target of repair.writtenRuntimeConfigs) {
972
+ lines.push(` - ${target.provider}: ${target.path}`);
973
+ }
974
+ }
975
+ if (repair.skipped.length > 0) {
976
+ lines.push("- skipped:");
977
+ for (const item of repair.skipped) {
978
+ lines.push(` - ${item}`);
979
+ }
980
+ }
981
+ if (lines.length === 1) {
982
+ lines.push("- no repairs needed");
983
+ }
984
+ return lines.join("\n");
985
+ }
986
+ async function repairDoctorStatus(args, status) {
987
+ const roles = listRoleDefinitions();
988
+ const codexDir = typeof args["codex-dir"] === "string"
989
+ ? args["codex-dir"]
990
+ : typeof args.dir === "string"
991
+ ? args.dir
992
+ : undefined;
993
+ const codexPromptsDir = typeof args["codex-prompts-dir"] === "string"
994
+ ? args["codex-prompts-dir"]
995
+ : typeof args["prompts-dir"] === "string"
996
+ ? args["prompts-dir"]
997
+ : typeof args.dir === "string"
998
+ ? args.dir
999
+ : undefined;
1000
+ const claudeDir = typeof args["claude-dir"] === "string"
1001
+ ? args["claude-dir"]
1002
+ : typeof args.dir === "string"
1003
+ ? args.dir
1004
+ : undefined;
1005
+ const setupOverride = typeof args.setup === "string"
1006
+ ? args.setup
1007
+ : typeof args.path === "string"
1008
+ ? args.path
1009
+ : undefined;
1010
+ const codexRuntimeOverride = typeof args["codex-runtime-path"] === "string"
1011
+ ? args["codex-runtime-path"]
1012
+ : undefined;
1013
+ const claudeRuntimeOverride = typeof args["claude-runtime-path"] === "string"
1014
+ ? args["claude-runtime-path"]
1015
+ : undefined;
1016
+ const repair = {
1017
+ installedCodexSkills: [],
1018
+ installedClaudeSkills: [],
1019
+ removedLegacyPromptFiles: [],
1020
+ writtenRuntimeConfigs: [],
1021
+ skipped: []
1022
+ };
1023
+ if (status.providers.codex.missingSkills.length > 0) {
1024
+ repair.installedCodexSkills = (await installCodexSkills(roles, codexDir)).map((skill) => skill.name);
1025
+ }
1026
+ if (status.providers.claude.missingSkills.length > 0) {
1027
+ repair.installedClaudeSkills = (await installClaudeSkills(roles, claudeDir)).map((skill) => skill.name);
1028
+ }
1029
+ if (status.providers.codex.legacyPromptFilesInstalled.length > 0) {
1030
+ repair.removedLegacyPromptFiles = await removeCodexPromptAliases(codexPromptsDir);
1031
+ }
1032
+ if (!status.setupExists) {
1033
+ repair.skipped.push("runtime configs require a researcher setup; run `longtable init --flow interview --provider codex` first");
1034
+ return repair;
1035
+ }
1036
+ const setup = await loadSetupOutput(setupOverride);
1037
+ if (!status.providers.codex.runtimeExists) {
1038
+ const target = await writeRuntimeConfig(setupForProvider(setup, "codex"), status.setupPath, codexRuntimeOverride);
1039
+ repair.writtenRuntimeConfigs.push({
1040
+ provider: target.provider,
1041
+ path: target.path,
1042
+ format: target.format
1043
+ });
1044
+ }
1045
+ if (!status.providers.claude.runtimeExists) {
1046
+ const target = await writeRuntimeConfig(setupForProvider(setup, "claude"), status.setupPath, claudeRuntimeOverride);
1047
+ repair.writtenRuntimeConfigs.push({
1048
+ provider: target.provider,
1049
+ path: target.path,
1050
+ format: target.format
1051
+ });
1052
+ }
1053
+ return repair;
1054
+ }
1055
+ async function runDoctor(args) {
1056
+ const status = await collectDoctorStatus(args);
1057
+ if (args.fix === true) {
1058
+ const repair = await repairDoctorStatus(args, status);
1059
+ const updatedStatus = await collectDoctorStatus(args);
1060
+ if (args.json === true) {
1061
+ console.log(JSON.stringify({ repair, status: updatedStatus }, null, 2));
1062
+ return;
1063
+ }
1064
+ console.log(renderRepairSummary(repair));
1065
+ console.log("");
1066
+ console.log(renderDoctorStatus(updatedStatus));
1067
+ return;
1068
+ }
1069
+ if (args.json === true) {
1070
+ console.log(JSON.stringify(status, null, 2));
1071
+ return;
1072
+ }
1073
+ console.log(renderDoctorStatus(status));
1074
+ }
734
1075
  async function runCodexPersistInit(args) {
735
1076
  const { flow, provider, answers } = await readPersistAnswers(args);
736
1077
  const outputValue = createPersistedSetupOutput(answers, provider, flow);
@@ -742,11 +1083,16 @@ async function runCodexPersistInit(args) {
742
1083
  if (provider === "codex" && args["install-prompts"] === true) {
743
1084
  installedPrompts = await installCodexPromptAliases(typeof args.dir === "string" ? args.dir : undefined);
744
1085
  }
1086
+ let installedSkills = [];
1087
+ if (provider === "codex" && args["install-skills"] === true) {
1088
+ installedSkills = await installCodexSkills(listRoleDefinitions(), typeof args.dir === "string" ? args.dir : undefined);
1089
+ }
745
1090
  if (args.json === true) {
746
1091
  console.log(JSON.stringify({
747
1092
  setup: outputValue,
748
1093
  install: result,
749
- installedPrompts: installedPrompts.map((prompt) => prompt.name)
1094
+ installedPrompts: installedPrompts.map((prompt) => prompt.name),
1095
+ installedSkills: installedSkills.map((skill) => skill.name)
750
1096
  }, null, 2));
751
1097
  return;
752
1098
  }
@@ -757,17 +1103,25 @@ async function runCodexPersistInit(args) {
757
1103
  console.log("");
758
1104
  console.log("Installed Codex prompt files:");
759
1105
  for (const prompt of installedPrompts) {
760
- console.log(`- /prompts:${prompt.name}`);
1106
+ console.log(`- ${prompt.name}`);
1107
+ }
1108
+ console.log(" Note: prompt files are legacy and may not be exposed by your Codex build.");
1109
+ }
1110
+ if (installedSkills.length > 0) {
1111
+ console.log("");
1112
+ console.log("Installed Codex skill files:");
1113
+ for (const skill of installedSkills) {
1114
+ console.log(`- ${skill.name}`);
761
1115
  }
762
- console.log(" Note: whether Codex exposes these as slash commands depends on your Codex build.");
1116
+ console.log(" Use these inside Codex by naming LongTable naturally, e.g. `lt panel: ...`.");
763
1117
  }
764
1118
  if (provider === "codex") {
765
1119
  console.log("");
766
1120
  console.log("Next step:");
767
1121
  console.log("- Start here: `longtable start`.");
768
1122
  console.log("- If you want a direct natural-language entry: `longtable ask --prompt \"...\"`.");
769
- console.log("- Codex prompt files are available as an experimental integration, not the primary path.");
770
- console.log("- Suggested next action: create a project workspace and let Long Table interview the current session.");
1123
+ console.log("- Codex skills are the preferred native surface. Prompt files are legacy and may not expose slash commands.");
1124
+ console.log("- Suggested next action: create a project workspace and let LongTable interview the current session.");
771
1125
  }
772
1126
  }
773
1127
  async function resolvePrompt(prompt) {
@@ -779,7 +1133,7 @@ async function resolvePrompt(prompt) {
779
1133
  }
780
1134
  const rl = createInterface({ input, output });
781
1135
  try {
782
- return (await rl.question("What should Long Table help with?\n> ")).trim();
1136
+ return (await rl.question("What should LongTable help with?\n> ")).trim();
783
1137
  }
784
1138
  finally {
785
1139
  rl.close();
@@ -828,6 +1182,20 @@ function inferModeFromPrompt(prompt) {
828
1182
  }
829
1183
  return "explore";
830
1184
  }
1185
+ function parsePanelVisibility(value) {
1186
+ if (value === "synthesis_only" ||
1187
+ value === "show_on_conflict" ||
1188
+ value === "always_visible") {
1189
+ return value;
1190
+ }
1191
+ return undefined;
1192
+ }
1193
+ function parsePanelMode(value) {
1194
+ if (value && VALID_MODES.has(value) && value !== "explore" && value !== "submit") {
1195
+ return value;
1196
+ }
1197
+ return "review";
1198
+ }
831
1199
  async function loadOptionalSetup(path) {
832
1200
  try {
833
1201
  return await loadSetupOutput(path);
@@ -842,7 +1210,7 @@ async function buildProjectAwarePrompt(prompt, workingDirectory) {
842
1210
  return { prompt, projectContextFound: false };
843
1211
  }
844
1212
  const lines = [
845
- "Long Table project context",
1213
+ "LongTable project context",
846
1214
  `project: ${context.project.projectName}`,
847
1215
  `current session goal: ${context.session.currentGoal}`,
848
1216
  ...(context.session.currentBlocker ? [`current blocker: ${context.session.currentBlocker}`] : []),
@@ -864,6 +1232,10 @@ async function runModeCommand(mode, args) {
864
1232
  throw new Error(`Invalid stage: ${stage}`);
865
1233
  }
866
1234
  const setup = await loadOptionalSetup(typeof args.setup === "string" ? args.setup : undefined);
1235
+ const projectContext = await loadProjectContextFromDirectory(workingDirectory);
1236
+ if (projectContext) {
1237
+ await assertWorkspaceNotBlocked(projectContext);
1238
+ }
867
1239
  const projectAware = await buildProjectAwarePrompt(prompt, workingDirectory);
868
1240
  const panelPreference = setup?.profileSeed.panelPreference;
869
1241
  const panelRequested = args.panel === true ||
@@ -898,6 +1270,125 @@ async function runModeCommand(mode, args) {
898
1270
  });
899
1271
  exit(exitCode);
900
1272
  }
1273
+ async function runPanelCommand(args) {
1274
+ const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
1275
+ const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
1276
+ if (!prompt) {
1277
+ throw new Error("A prompt is required.");
1278
+ }
1279
+ const setup = await loadOptionalSetup(typeof args.setup === "string" ? args.setup : undefined);
1280
+ const existingContext = await loadProjectContextFromDirectory(workingDirectory);
1281
+ if (existingContext) {
1282
+ await assertWorkspaceNotBlocked(existingContext);
1283
+ }
1284
+ const projectAware = await buildProjectAwarePrompt(prompt, workingDirectory);
1285
+ const provider = setup?.providerSelection.provider;
1286
+ const visibility = parsePanelVisibility(typeof args.visibility === "string" ? args.visibility : undefined) ??
1287
+ parsePanelVisibility(setup?.profileSeed.panelPreference) ??
1288
+ "always_visible";
1289
+ const mode = parsePanelMode(typeof args.mode === "string" ? args.mode : undefined);
1290
+ const fallback = buildPanelFallback({
1291
+ prompt: projectAware.prompt,
1292
+ mode,
1293
+ roleFlag: typeof args.role === "string" ? args.role : undefined,
1294
+ provider,
1295
+ visibility
1296
+ });
1297
+ if (projectAware.projectContextFound) {
1298
+ const context = await loadProjectContextFromDirectory(workingDirectory);
1299
+ if (context) {
1300
+ await appendInvocationRecordToWorkspace(context, fallback.invocationRecord, [fallback.questionRecord]);
1301
+ }
1302
+ }
1303
+ if (args.json === true) {
1304
+ console.log(JSON.stringify({
1305
+ intent: fallback.intent,
1306
+ plan: fallback.plan,
1307
+ result: fallback.result,
1308
+ invocationRecord: fallback.invocationRecord,
1309
+ questionRecord: fallback.questionRecord,
1310
+ execution: {
1311
+ status: "planned",
1312
+ stableSurface: "sequential_fallback",
1313
+ nativeParallel: "not_required_for_option_a",
1314
+ projectContextFound: projectAware.projectContextFound,
1315
+ invocationLogged: projectAware.projectContextFound
1316
+ },
1317
+ fallbackPrompt: fallback.prompt
1318
+ }, null, 2));
1319
+ return;
1320
+ }
1321
+ if (args.print === true) {
1322
+ console.log(fallback.prompt);
1323
+ return;
1324
+ }
1325
+ console.log(renderPanelSummary(fallback.plan));
1326
+ console.log("");
1327
+ const exitCode = await runCodexThinWrapper({
1328
+ prompt: fallback.prompt,
1329
+ mode,
1330
+ setupPath: typeof args.setup === "string" ? args.setup : undefined,
1331
+ workingDirectory,
1332
+ json: false
1333
+ });
1334
+ exit(exitCode);
1335
+ }
1336
+ async function runQuestion(args) {
1337
+ const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
1338
+ const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
1339
+ if (!prompt) {
1340
+ throw new Error("A decision context is required. Pass --prompt <text>.");
1341
+ }
1342
+ const context = await loadProjectContextFromDirectory(workingDirectory);
1343
+ if (!context) {
1344
+ throw new Error("No LongTable project workspace was found here. Run this inside a project or pass --cwd.");
1345
+ }
1346
+ const provider = args.provider === "claude" ? "claude" : args.provider === "codex" ? "codex" : undefined;
1347
+ const required = args.required === true ? true : args.advisory === true ? false : undefined;
1348
+ const result = await createWorkspaceQuestion({
1349
+ context,
1350
+ prompt,
1351
+ title: typeof args.title === "string" ? args.title : undefined,
1352
+ question: typeof args.text === "string" ? args.text : undefined,
1353
+ provider,
1354
+ required
1355
+ });
1356
+ const transport = provider === "claude"
1357
+ ? renderQuestionRecordInput(result.question)
1358
+ : renderQuestionRecordPrompt(result.question);
1359
+ if (args.json === true) {
1360
+ console.log(JSON.stringify({
1361
+ question: result.question,
1362
+ transport,
1363
+ files: {
1364
+ state: context.stateFilePath,
1365
+ current: context.currentFilePath
1366
+ },
1367
+ nextAction: `longtable decide --question ${result.question.id} --answer <value>`
1368
+ }, null, 2));
1369
+ return;
1370
+ }
1371
+ if (args.print === true) {
1372
+ if (provider === "claude") {
1373
+ console.log(JSON.stringify(transport, null, 2));
1374
+ }
1375
+ else {
1376
+ console.log("prompt" in transport ? transport.prompt : JSON.stringify(transport, null, 2));
1377
+ }
1378
+ return;
1379
+ }
1380
+ const optionValues = [
1381
+ ...result.question.prompt.options.map((option) => option.value),
1382
+ ...(result.question.prompt.allowOther ? ["other"] : [])
1383
+ ];
1384
+ console.log(result.question.prompt.required ? "LongTable required Researcher Checkpoint recorded" : "LongTable advisory Researcher Checkpoint recorded");
1385
+ console.log(`- question: ${result.question.id}`);
1386
+ console.log(`- checkpoint: ${result.question.prompt.checkpointKey ?? "manual"}`);
1387
+ console.log(`- prompt: ${result.question.prompt.question}`);
1388
+ console.log(`- options: ${optionValues.join("/")}`);
1389
+ console.log(`- answer: longtable decide --question ${result.question.id} --answer <value>`);
1390
+ console.log(`- current: ${context.currentFilePath}`);
1391
+ }
901
1392
  async function runAsk(args) {
902
1393
  const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
903
1394
  if (!prompt) {
@@ -907,7 +1398,7 @@ async function runAsk(args) {
907
1398
  const effectivePrompt = directive.cleanedPrompt;
908
1399
  const inferred = directive.mode ?? inferModeFromPrompt(effectivePrompt);
909
1400
  if (inferred === "status") {
910
- await runCodexSubcommand("status", args);
1401
+ await runDoctor(args);
911
1402
  return;
912
1403
  }
913
1404
  const mode = inferred === "panel" ? "review" : inferred;
@@ -918,32 +1409,76 @@ async function runAsk(args) {
918
1409
  if (directive.roles.length > 0 && typeof delegatedArgs.role !== "string") {
919
1410
  delegatedArgs.role = directive.roles.join(",");
920
1411
  }
921
- if ((inferred === "panel" || directive.panel) && delegatedArgs.panel !== true) {
922
- delegatedArgs.panel = true;
923
- delegatedArgs["show-conflicts"] = true;
1412
+ if (inferred === "panel" || directive.panel || delegatedArgs.panel === true) {
1413
+ await runPanelCommand({
1414
+ ...delegatedArgs,
1415
+ visibility: "always_visible"
1416
+ });
1417
+ return;
924
1418
  }
925
1419
  await runModeCommand(mode, delegatedArgs);
926
1420
  }
1421
+ async function runDecide(args) {
1422
+ const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
1423
+ const answer = typeof args.answer === "string" ? args.answer.trim() : "";
1424
+ if (!answer) {
1425
+ throw new Error("A decision answer is required. Pass --answer <value-or-text>.");
1426
+ }
1427
+ const context = await loadProjectContextFromDirectory(workingDirectory);
1428
+ if (!context) {
1429
+ throw new Error("No LongTable project workspace was found here. Run this inside a project or pass --cwd.");
1430
+ }
1431
+ const provider = args.provider === "claude" ? "claude" : args.provider === "codex" ? "codex" : undefined;
1432
+ const result = await answerWorkspaceQuestion({
1433
+ context,
1434
+ questionId: typeof args.question === "string" ? args.question : undefined,
1435
+ answer,
1436
+ rationale: typeof args.rationale === "string" ? args.rationale : undefined,
1437
+ provider
1438
+ });
1439
+ if (args.json === true) {
1440
+ console.log(JSON.stringify({
1441
+ question: result.question,
1442
+ decision: result.decision,
1443
+ files: {
1444
+ state: context.stateFilePath,
1445
+ current: context.currentFilePath
1446
+ }
1447
+ }, null, 2));
1448
+ return;
1449
+ }
1450
+ console.log("LongTable decision recorded");
1451
+ console.log(`- question: ${result.question.id}`);
1452
+ console.log(`- decision: ${result.decision.id}`);
1453
+ console.log(`- answer: ${result.decision.selectedOption ?? answer}`);
1454
+ console.log(`- state: ${context.stateFilePath}`);
1455
+ console.log(`- current: ${context.currentFilePath}`);
1456
+ }
927
1457
  async function runRoles(args) {
928
1458
  const payload = PERSONA_DEFINITIONS.map((persona) => ({
929
1459
  key: persona.key,
930
1460
  label: persona.label,
931
1461
  description: persona.shortDescription,
932
1462
  triggerMode: persona.triggerMode,
1463
+ defaultPanelMember: persona.defaultPanelMember,
1464
+ checkpointSensitivity: persona.checkpointSensitivity,
1465
+ supportedModes: persona.supportedModes,
933
1466
  exampleTriggers: persona.synonyms.slice(0, 4)
934
1467
  }));
935
1468
  if (args.json === true) {
936
1469
  console.log(JSON.stringify(payload, null, 2));
937
1470
  return;
938
1471
  }
939
- console.log("Long Table roles");
940
- console.log("These are perspectives Long Table can consult when relevant.");
1472
+ console.log("LongTable roles");
1473
+ console.log("These are perspectives LongTable can consult when relevant.");
941
1474
  console.log("Inside Codex, explicit forms like `lt editor: ...` and `lt methods: ...` are stronger than plain natural language.");
942
1475
  console.log("");
943
1476
  for (const persona of payload) {
944
1477
  console.log(`- ${persona.label} (${persona.key})`);
945
1478
  console.log(` ${persona.description}`);
946
1479
  console.log(` Trigger: ${persona.triggerMode === "auto-callable" ? "auto-callable when your language strongly implies it" : "explicit request only"}`);
1480
+ console.log(` Panel: ${persona.defaultPanelMember ? "default member" : "contextual member"}`);
1481
+ console.log(` Checkpoint sensitivity: ${persona.checkpointSensitivity}`);
947
1482
  console.log(` Examples: ${persona.exampleTriggers.join(", ")}`);
948
1483
  }
949
1484
  }
@@ -951,7 +1486,7 @@ async function runStart(args) {
951
1486
  const setupPath = typeof args.setup === "string" ? args.setup : undefined;
952
1487
  const existingSetup = await loadOptionalSetup(setupPath);
953
1488
  if (!existingSetup) {
954
- throw new Error("Long Table global setup is missing. Run `longtable init --flow interview` first.");
1489
+ throw new Error("LongTable global setup is missing. Run `longtable init --flow interview` first.");
955
1490
  }
956
1491
  const interview = await collectProjectInterview(existingSetup, args);
957
1492
  await verifyWritableWorkspaceParent(interview.projectPath);
@@ -968,8 +1503,12 @@ async function runStart(args) {
968
1503
  console.log(JSON.stringify({
969
1504
  project: context.project,
970
1505
  session: context.session,
971
- projectFilePath: context.projectFilePath,
972
- sessionFilePath: context.sessionFilePath
1506
+ files: {
1507
+ project: context.projectFilePath,
1508
+ session: context.sessionFilePath,
1509
+ state: context.stateFilePath,
1510
+ current: context.currentFilePath
1511
+ }
973
1512
  }, null, 2));
974
1513
  return;
975
1514
  }
@@ -979,7 +1518,7 @@ async function runStart(args) {
979
1518
  `1. cd "${context.project.projectPath}"`,
980
1519
  "2. run `codex` in that directory",
981
1520
  "3. begin with your current goal in natural language",
982
- "4. if you return later, read `START-HERE.md`, `NEXT-STEPS.md`, or run `longtable resume`",
1521
+ "4. if you return later, open `CURRENT.md` or run `longtable resume`",
983
1522
  "",
984
1523
  `Suggested first message: ${context.session.currentBlocker ? `"I want to work on ${context.session.currentGoal}. My current blocker is ${context.session.currentBlocker}."` : `"I want to work on ${context.session.currentGoal}."`}`,
985
1524
  "",
@@ -990,22 +1529,24 @@ async function runResume(args) {
990
1529
  const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
991
1530
  const context = await loadProjectContextFromDirectory(workingDirectory);
992
1531
  if (!context) {
993
- throw new Error("No Long Table project workspace was found here. Run `longtable start` first or pass --cwd.");
1532
+ throw new Error("No LongTable project workspace was found here. Run `longtable start` first or pass --cwd.");
994
1533
  }
1534
+ await syncCurrentWorkspaceView(context);
995
1535
  const payload = {
996
1536
  project: context.project,
997
1537
  session: context.session,
998
1538
  files: {
999
- startHere: resolve(context.project.projectPath, "START-HERE.md"),
1000
- nextSteps: resolve(context.project.projectPath, "NEXT-STEPS.md"),
1001
- sessionSnapshot: resolve(context.project.projectPath, "SESSION-SNAPSHOT.md")
1539
+ current: resolve(context.project.projectPath, "CURRENT.md"),
1540
+ project: context.projectFilePath,
1541
+ session: context.sessionFilePath,
1542
+ state: context.stateFilePath
1002
1543
  }
1003
1544
  };
1004
1545
  if (args.json === true) {
1005
1546
  console.log(JSON.stringify(payload, null, 2));
1006
1547
  return;
1007
1548
  }
1008
- console.log(renderSectionCard("Long Table Resume", [
1549
+ console.log(renderSectionCard("LongTable Resume", [
1009
1550
  `Project: ${context.project.projectName}`,
1010
1551
  `Path: ${context.project.projectPath}`,
1011
1552
  `Current goal: ${context.session.currentGoal}`,
@@ -1013,22 +1554,36 @@ async function runResume(args) {
1013
1554
  `Requested perspectives: ${context.session.requestedPerspectives.length > 0 ? context.session.requestedPerspectives.join(", ") : "auto"}`,
1014
1555
  `Disagreement: ${context.session.disagreementPreference}`,
1015
1556
  "",
1016
- "Resume files:",
1017
- `- ${payload.files.startHere}`,
1018
- `- ${payload.files.nextSteps}`,
1019
- `- ${payload.files.sessionSnapshot}`,
1557
+ "Current file:",
1558
+ `- ${payload.files.current}`,
1020
1559
  "",
1021
- `Suggested restart message: ${context.session.currentBlocker ? `"I want to continue ${context.session.currentGoal}. The unresolved blocker is ${context.session.currentBlocker}."` : `"I want to continue ${context.session.currentGoal}."`}`
1560
+ `Suggested restart message: "${context.session.resumeHint ?? (context.session.currentBlocker ? `I want to continue ${context.session.currentGoal}. The unresolved blocker is ${context.session.currentBlocker}.` : `I want to continue ${context.session.currentGoal}.`)}"`
1022
1561
  ]));
1023
1562
  }
1024
1563
  async function runCodexSubcommand(subcommand, args) {
1025
1564
  const customDir = typeof args.dir === "string" ? args.dir : undefined;
1565
+ const roles = listRoleDefinitions();
1566
+ if (subcommand === "install-skills") {
1567
+ const installed = await installCodexSkills(roles, customDir);
1568
+ console.log(`Installed ${installed.length} LongTable Codex skills in ${resolveCodexSkillsDir(customDir)}`);
1569
+ console.log("Use them inside Codex with natural-language triggers such as `lt explore: ...` or `lt panel: ...`.");
1570
+ console.log("If you want an explicit trigger, use `$longtable` when your Codex build exposes skills that way.");
1571
+ for (const skill of installed) {
1572
+ console.log(`- ${skill.name}`);
1573
+ }
1574
+ return;
1575
+ }
1576
+ if (subcommand === "remove-skills") {
1577
+ const removed = await removeCodexSkills(roles, customDir);
1578
+ console.log(`Removed ${removed.length} LongTable Codex skills from ${resolveCodexSkillsDir(customDir)}`);
1579
+ return;
1580
+ }
1026
1581
  if (subcommand === "install-prompts") {
1027
1582
  const installed = await installCodexPromptAliases(customDir);
1028
- console.log(`Installed ${installed.length} Long Table prompt aliases in ${resolveCodexPromptsDir(customDir)}`);
1029
- console.log("Note: prompt-file discovery depends on the Codex build. Treat this as an experimental integration.");
1583
+ console.log(`Installed ${installed.length} legacy LongTable prompt files in ${resolveCodexPromptsDir(customDir)}`);
1584
+ console.log("Note: current Codex builds may not expose these files as slash commands. Prefer `longtable codex install-skills`.");
1030
1585
  for (const prompt of installed) {
1031
- console.log(`- /prompts:${prompt.name}`);
1586
+ console.log(`- ${prompt.name}`);
1032
1587
  }
1033
1588
  return;
1034
1589
  }
@@ -1038,11 +1593,12 @@ async function runCodexSubcommand(subcommand, args) {
1038
1593
  }
1039
1594
  if (subcommand === "remove-prompts") {
1040
1595
  const removed = await removeCodexPromptAliases(customDir);
1041
- console.log(`Removed ${removed.length} Long Table prompt aliases from ${resolveCodexPromptsDir(customDir)}`);
1596
+ console.log(`Removed ${removed.length} legacy LongTable prompt files from ${resolveCodexPromptsDir(customDir)}`);
1042
1597
  return;
1043
1598
  }
1044
1599
  if (subcommand === "status") {
1045
1600
  const aliases = await listInstalledCodexPromptAliases(customDir);
1601
+ const skills = await listInstalledCodexSkills(roles, customDir);
1046
1602
  const setupPath = resolveDefaultSetupPath(typeof args.path === "string" ? args.path : undefined).path;
1047
1603
  const runtimePath = resolveDefaultRuntimeConfigPath("codex", typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined).path;
1048
1604
  const status = {
@@ -1050,31 +1606,93 @@ async function runCodexSubcommand(subcommand, args) {
1050
1606
  setupExists: existsSync(setupPath),
1051
1607
  runtimePath,
1052
1608
  runtimeExists: existsSync(runtimePath),
1609
+ skillsDir: resolveCodexSkillsDir(customDir),
1610
+ skillsInstalled: skills.map((skill) => skill.name),
1053
1611
  promptsDir: resolveCodexPromptsDir(customDir),
1054
- promptAliasesInstalled: aliases.map((alias) => alias.name)
1612
+ legacyPromptFilesInstalled: aliases.map((alias) => alias.name)
1055
1613
  };
1056
1614
  if (args.json === true) {
1057
1615
  console.log(JSON.stringify(status, null, 2));
1058
1616
  return;
1059
1617
  }
1060
- console.log("Long Table Codex status");
1618
+ console.log("LongTable Codex status");
1061
1619
  console.log(`- setup: ${status.setupExists ? "present" : "missing"} (${setupPath})`);
1062
1620
  console.log(`- codex runtime artifact: ${status.runtimeExists ? "present" : "missing"} (${runtimePath})`);
1621
+ console.log(`- skills dir: ${status.skillsDir}`);
1622
+ if (skills.length === 0) {
1623
+ console.log("- skills: none");
1624
+ }
1625
+ else {
1626
+ console.log("- skills:");
1627
+ for (const skill of skills) {
1628
+ console.log(` - ${skill.name}`);
1629
+ }
1630
+ }
1063
1631
  console.log(`- prompt aliases dir: ${status.promptsDir}`);
1064
- console.log("- prompt-file integration: experimental (your Codex build may not expose these as slash commands)");
1632
+ console.log("- prompt files: legacy; current Codex builds may not expose these as slash commands");
1065
1633
  if (aliases.length === 0) {
1066
- console.log("- prompt aliases: none");
1634
+ console.log("- legacy prompt files: none");
1067
1635
  }
1068
1636
  else {
1069
- console.log("- prompt aliases:");
1637
+ console.log("- legacy prompt files:");
1070
1638
  for (const alias of aliases) {
1071
- console.log(` - /prompts:${alias.name}`);
1639
+ console.log(` - ${alias.name}`);
1072
1640
  }
1073
1641
  }
1074
1642
  return;
1075
1643
  }
1076
1644
  throw new Error("Unknown codex subcommand.");
1077
1645
  }
1646
+ async function runClaudeSubcommand(subcommand, args) {
1647
+ const customDir = typeof args.dir === "string" ? args.dir : undefined;
1648
+ const roles = listRoleDefinitions();
1649
+ if (subcommand === "install-skills") {
1650
+ const installed = await installClaudeSkills(roles, customDir);
1651
+ console.log(`Installed ${installed.length} LongTable Claude skills in ${resolveClaudeSkillsDir(customDir)}`);
1652
+ console.log("Use them inside Claude Code with natural-language triggers such as `lt explore: ...` or `lt panel: ...`.");
1653
+ for (const skill of installed) {
1654
+ console.log(`- ${skill.name}`);
1655
+ }
1656
+ return;
1657
+ }
1658
+ if (subcommand === "remove-skills") {
1659
+ const removed = await removeClaudeSkills(roles, customDir);
1660
+ console.log(`Removed ${removed.length} LongTable Claude skills from ${resolveClaudeSkillsDir(customDir)}`);
1661
+ return;
1662
+ }
1663
+ if (subcommand === "status") {
1664
+ const skills = await listInstalledClaudeSkills(roles, customDir);
1665
+ const setupPath = resolveDefaultSetupPath(typeof args.path === "string" ? args.path : undefined).path;
1666
+ const runtimePath = resolveDefaultRuntimeConfigPath("claude", typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined).path;
1667
+ const status = {
1668
+ setupPath,
1669
+ setupExists: existsSync(setupPath),
1670
+ runtimePath,
1671
+ runtimeExists: existsSync(runtimePath),
1672
+ skillsDir: resolveClaudeSkillsDir(customDir),
1673
+ skillsInstalled: skills.map((skill) => skill.name)
1674
+ };
1675
+ if (args.json === true) {
1676
+ console.log(JSON.stringify(status, null, 2));
1677
+ return;
1678
+ }
1679
+ console.log("LongTable Claude status");
1680
+ console.log(`- setup: ${status.setupExists ? "present" : "missing"} (${setupPath})`);
1681
+ console.log(`- claude runtime artifact: ${status.runtimeExists ? "present" : "missing"} (${runtimePath})`);
1682
+ console.log(`- skills dir: ${status.skillsDir}`);
1683
+ if (skills.length === 0) {
1684
+ console.log("- skills: none");
1685
+ }
1686
+ else {
1687
+ console.log("- skills:");
1688
+ for (const skill of skills) {
1689
+ console.log(` - ${skill.name}`);
1690
+ }
1691
+ }
1692
+ return;
1693
+ }
1694
+ throw new Error("Unknown claude subcommand.");
1695
+ }
1078
1696
  async function main() {
1079
1697
  const parsed = parseArgs(process.argv.slice(2));
1080
1698
  const { command, subcommand, values } = parsed;
@@ -1094,6 +1712,10 @@ async function main() {
1094
1712
  await runResume(values);
1095
1713
  return;
1096
1714
  }
1715
+ if (command === "doctor" || command === "status") {
1716
+ await runDoctor(values);
1717
+ return;
1718
+ }
1097
1719
  if (command === "roles") {
1098
1720
  await runRoles(values);
1099
1721
  return;
@@ -1110,11 +1732,34 @@ async function main() {
1110
1732
  await runAsk(values);
1111
1733
  return;
1112
1734
  }
1735
+ if (command === "question") {
1736
+ await runQuestion(values);
1737
+ return;
1738
+ }
1739
+ if (command === "panel") {
1740
+ await runPanelCommand(values);
1741
+ return;
1742
+ }
1743
+ if (command === "decide") {
1744
+ await runDecide(values);
1745
+ return;
1746
+ }
1113
1747
  if (command === "codex") {
1114
1748
  await runCodexSubcommand(subcommand, values);
1115
1749
  return;
1116
1750
  }
1751
+ if (command === "claude") {
1752
+ await runClaudeSubcommand(subcommand, values);
1753
+ return;
1754
+ }
1117
1755
  if (VALID_MODES.has(command)) {
1756
+ if (values.panel === true) {
1757
+ await runPanelCommand({
1758
+ ...values,
1759
+ mode: command
1760
+ });
1761
+ return;
1762
+ }
1118
1763
  await runModeCommand(command, values);
1119
1764
  return;
1120
1765
  }