@longtable/cli 0.1.9 → 0.1.11

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
- import { mkdtemp, rm } from "node:fs/promises";
3
+ import { mkdir, mkdtemp, readFile, rm, writeFile } 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 "@longtable/setup";
10
- import { buildCodexThinWrappedPrompt, runCodexThinWrapper } from "@longtable/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, syncCurrentWorkspaceView } 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",
@@ -36,6 +39,10 @@ const ANSI = {
36
39
  cyan: "\u001B[36m",
37
40
  green: "\u001B[32m"
38
41
  };
42
+ const LONGTABLE_MCP_SERVER_NAME = "longtable-state";
43
+ const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.11";
44
+ const LONGTABLE_MCP_MARKER_START = "# LongTable state MCP START";
45
+ const LONGTABLE_MCP_MARKER_END = "# LongTable state MCP END";
39
46
  function style(text, prefix) {
40
47
  return `${prefix}${text}${ANSI.reset}`;
41
48
  }
@@ -69,28 +76,42 @@ function usage() {
69
76
  " Run `longtable ...` in your terminal, not inside the Codex chat box.",
70
77
  " After `longtable start`, move into the created project directory and open `codex` there.",
71
78
  "",
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]",
79
+ " 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
80
  " 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
81
  " longtable resume [--cwd <path>] [--json]",
82
+ " longtable doctor [--cwd <path>] [--fix] [--json] [--codex-dir <path>] [--claude-dir <path>] [--codex-prompts-dir <path>] [--codex-runtime-path <file>] [--claude-runtime-path <file>]",
83
+ " 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
84
  " longtable roles [--json]",
76
85
  " longtable show [--json] [--path <file>]",
77
86
  " longtable install [--json] [--path <file>] [--runtime-path <file>]",
87
+ " longtable mcp install [--provider codex|claude|all] [--write] [--json] [--codex-config <path>] [--claude-settings <path>] [--package <spec>]",
78
88
  " longtable ask [--prompt <text>] [--print] [--json] [--setup <path>] [--cwd <path>]",
89
+ " longtable question --prompt <decision-context> [--title <text>] [--text <question>] [--provider codex|claude] [--required|--advisory] [--print] [--cwd <path>] [--json]",
90
+ " 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>]",
91
+ " longtable decide [--question <id>] --answer <value-or-text> [--rationale <text>] [--provider codex|claude] [--cwd <path>] [--json]",
79
92
  " 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]",
93
+ " longtable codex persist-init [--answers-json <json> | --stdin | full setup flags] [--install-skills] [--install-prompts] [--json]",
94
+ " longtable codex install-skills [--dir <path>]",
95
+ " longtable codex remove-skills [--dir <path>]",
81
96
  " longtable codex install-prompts [--dir <path>]",
82
97
  " longtable codex remove-prompts [--dir <path>]",
83
98
  " longtable codex status [--dir <path>] [--json]",
99
+ " longtable claude install-skills [--dir <path>]",
100
+ " longtable claude remove-skills [--dir <path>]",
101
+ " longtable claude status [--dir <path>] [--json]",
102
+ " longtable mcp install --provider all",
84
103
  "",
85
104
  "Examples:",
86
- " longtable init --flow interview --install-prompts",
105
+ " longtable init --flow interview --provider codex --install-skills",
87
106
  " longtable start",
88
107
  " longtable start --path ~/Research/My-Project --name \"AI Adoption Meta-Analysis\" --goal \"Narrow the review question\"",
89
108
  " cd \"<project-path>\" && codex",
109
+ " longtable doctor",
90
110
  " longtable roles",
91
111
  " longtable ask --prompt \"연구를 시작하고 싶어. 지금 어디서부터 좁혀야 할지 모르겠어.\"",
92
- " printf '{\"provider\":\"codex\",...}' | longtable codex persist-init --stdin --install-prompts",
93
- " longtable codex install-prompts"
112
+ " printf '{\"provider\":\"codex\",...}' | longtable codex persist-init --stdin --install-skills",
113
+ " longtable codex install-skills",
114
+ " longtable claude install-skills"
94
115
  ].join("\n");
95
116
  }
96
117
  function parseArgs(argv) {
@@ -98,13 +119,13 @@ function parseArgs(argv) {
98
119
  const values = {};
99
120
  let subcommand = maybeSubcommand;
100
121
  const modeCommand = command && VALID_MODES.has(command);
101
- const directCommand = command && ["init", "start", "resume", "roles", "show", "install", "codex", "ask"].includes(command);
122
+ const directCommand = command && ["init", "start", "resume", "doctor", "status", "roles", "show", "install", "mcp", "codex", "claude", "ask", "question", "panel", "decide"].includes(command);
102
123
  let startIndex = 1;
103
124
  if (modeCommand) {
104
125
  subcommand = undefined;
105
126
  startIndex = 1;
106
127
  }
107
- else if (command === "codex") {
128
+ else if (command === "codex" || command === "claude" || command === "mcp") {
108
129
  startIndex = 2;
109
130
  }
110
131
  else if (directCommand) {
@@ -667,9 +688,11 @@ async function runInit(args) {
667
688
  const json = args.json === true;
668
689
  const installRuntime = args["no-install"] !== true;
669
690
  const installPrompts = args["install-prompts"] === true;
691
+ const installSkills = args["install-skills"] === true;
670
692
  const customPath = typeof args.path === "string" ? args.path : undefined;
671
693
  const runtimePath = typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined;
672
694
  const promptsDir = typeof args.dir === "string" ? args.dir : undefined;
695
+ const skillsDir = typeof args["skills-dir"] === "string" ? args["skills-dir"] : promptsDir;
673
696
  const { flow, provider, answers } = hasCompleteFlagInput(args)
674
697
  ? {
675
698
  flow: resolveSetupFlow(args),
@@ -686,8 +709,23 @@ async function runInit(args) {
686
709
  if (provider === "codex" && installPrompts) {
687
710
  installedPrompts = await installCodexPromptAliases(promptsDir);
688
711
  }
712
+ let installedSkills = [];
713
+ if (provider === "codex" && installSkills) {
714
+ installedSkills = await installCodexSkills(listRoleDefinitions(), skillsDir);
715
+ }
716
+ if (provider === "claude" && installSkills) {
717
+ installedSkills = await installClaudeSkills(listRoleDefinitions(), skillsDir);
718
+ }
689
719
  if (json) {
690
- console.log(serializeSetupOutput(outputValue));
720
+ if (installedPrompts.length === 0 && installedSkills.length === 0) {
721
+ console.log(serializeSetupOutput(outputValue));
722
+ return;
723
+ }
724
+ console.log(JSON.stringify({
725
+ setup: outputValue,
726
+ installedPrompts: installedPrompts.map((prompt) => prompt.name),
727
+ installedSkills: installedSkills.map((skill) => skill.name)
728
+ }, null, 2));
691
729
  return;
692
730
  }
693
731
  console.log(renderSetupSummary(outputValue));
@@ -699,18 +737,33 @@ async function runInit(args) {
699
737
  console.log("");
700
738
  console.log("Installed Codex prompt files:");
701
739
  for (const prompt of installedPrompts) {
702
- console.log(`- /prompts:${prompt.name}`);
740
+ console.log(`- ${prompt.name}`);
703
741
  }
704
- console.log(" Note: whether Codex exposes these as slash commands depends on your Codex build.");
742
+ console.log(" Note: prompt files are legacy and may not be exposed by your Codex build.");
743
+ }
744
+ if (installedSkills.length > 0) {
745
+ console.log("");
746
+ console.log(`Installed ${provider === "codex" ? "Codex" : "Claude"} skill files:`);
747
+ for (const skill of installedSkills) {
748
+ console.log(`- ${skill.name}`);
749
+ }
750
+ console.log(" Use these by naming LongTable naturally, e.g. `lt panel: ...`.");
705
751
  }
706
752
  if (provider === "codex") {
707
753
  console.log("");
708
754
  console.log("Next step:");
709
755
  console.log("- Start here: `longtable start`.");
710
756
  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.");
757
+ console.log("- Codex skills are the preferred native surface. Prompt files are legacy and may not expose slash commands.");
712
758
  console.log("- Suggested next action: create a project workspace and let LongTable interview the current session.");
713
759
  }
760
+ if (provider === "claude") {
761
+ console.log("");
762
+ console.log("Next step:");
763
+ console.log("- Start here: `longtable start`.");
764
+ console.log("- In Claude Code, use natural language such as `lt explore: ...` or `lt panel: ...`.");
765
+ console.log("- Claude skills are adapter output from LongTable roles, not the source of truth.");
766
+ }
714
767
  }
715
768
  async function runShow(args) {
716
769
  const outputValue = await loadSetupOutput(typeof args.path === "string" ? args.path : undefined);
@@ -731,6 +784,441 @@ async function runInstall(args) {
731
784
  }
732
785
  console.log(renderInstallSummary(result));
733
786
  }
787
+ function resolveMcpProviders(value) {
788
+ if (value === "codex" || value === "claude") {
789
+ return [value];
790
+ }
791
+ return ["codex", "claude"];
792
+ }
793
+ function resolveMcpPackageSpec(args) {
794
+ return typeof args.package === "string" && args.package.trim()
795
+ ? args.package.trim()
796
+ : `@longtable/mcp@${LONGTABLE_MCP_PACKAGE_VERSION}`;
797
+ }
798
+ function resolveCodexMcpConfigPath(args) {
799
+ return resolve(normalizeUserPath(typeof args["codex-config"] === "string" && args["codex-config"].trim()
800
+ ? args["codex-config"].trim()
801
+ : "~/.codex/config.toml"));
802
+ }
803
+ function resolveClaudeMcpSettingsPath(args) {
804
+ return resolve(normalizeUserPath(typeof args["claude-settings"] === "string" && args["claude-settings"].trim()
805
+ ? args["claude-settings"].trim()
806
+ : "~/.claude/settings.json"));
807
+ }
808
+ function escapeTomlString(value) {
809
+ return JSON.stringify(value);
810
+ }
811
+ function renderCodexMcpBlock(serverName, command, mcpArgs) {
812
+ return [
813
+ LONGTABLE_MCP_MARKER_START,
814
+ `[mcp_servers.${serverName}]`,
815
+ `command = ${escapeTomlString(command)}`,
816
+ `args = [${mcpArgs.map((arg) => escapeTomlString(arg)).join(", ")}]`,
817
+ LONGTABLE_MCP_MARKER_END
818
+ ].join("\n");
819
+ }
820
+ function replaceMarkedCodexMcpBlock(existing, block, serverName) {
821
+ const markerPattern = new RegExp(`${LONGTABLE_MCP_MARKER_START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${LONGTABLE_MCP_MARKER_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`, "m");
822
+ const serverPattern = new RegExp(`\\n?\\[mcp_servers\\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\][\\s\\S]*?(?=\\n\\[|$)`, "m");
823
+ const trimmed = existing.replace(markerPattern, "").replace(serverPattern, "").trimEnd();
824
+ return trimmed ? `${trimmed}\n\n${block}\n` : `${block}\n`;
825
+ }
826
+ async function writeCodexMcpConfig(path, block, serverName) {
827
+ const existing = existsSync(path) ? await readFile(path, "utf8") : "";
828
+ const updated = replaceMarkedCodexMcpBlock(existing, block, serverName);
829
+ await mkdir(dirname(path), { recursive: true });
830
+ await writeFile(path, updated, "utf8");
831
+ return updated;
832
+ }
833
+ function renderClaudeMcpJson(serverName, command, mcpArgs) {
834
+ return JSON.stringify({
835
+ mcpServers: {
836
+ [serverName]: {
837
+ command,
838
+ args: mcpArgs
839
+ }
840
+ }
841
+ }, null, 2);
842
+ }
843
+ async function writeClaudeMcpSettings(path, serverName, command, mcpArgs) {
844
+ let settings = {};
845
+ if (existsSync(path)) {
846
+ const raw = await readFile(path, "utf8");
847
+ settings = raw.trim() ? JSON.parse(raw) : {};
848
+ }
849
+ const existingServers = typeof settings.mcpServers === "object" && settings.mcpServers !== null && !Array.isArray(settings.mcpServers)
850
+ ? settings.mcpServers
851
+ : {};
852
+ settings.mcpServers = {
853
+ ...existingServers,
854
+ [serverName]: {
855
+ command,
856
+ args: mcpArgs
857
+ }
858
+ };
859
+ const updated = JSON.stringify(settings, null, 2);
860
+ await mkdir(dirname(path), { recursive: true });
861
+ await writeFile(path, `${updated}\n`, "utf8");
862
+ return `${updated}\n`;
863
+ }
864
+ function renderMcpInstallSummary(result) {
865
+ const lines = [
866
+ "LongTable MCP transport",
867
+ `- server: ${result.serverName}`,
868
+ `- package: ${result.packageSpec}`,
869
+ `- command: ${result.command} ${result.args.join(" ")}`,
870
+ `- mode: ${result.write ? "wrote provider config" : "printed config only"}`,
871
+ ""
872
+ ];
873
+ for (const target of result.targets) {
874
+ lines.push(`${target.provider} (${target.path})`);
875
+ lines.push("```" + target.format);
876
+ lines.push(target.content.trimEnd());
877
+ lines.push("```");
878
+ lines.push("");
879
+ }
880
+ if (!result.write) {
881
+ lines.push("Run again with `--write` to update these provider config files.");
882
+ }
883
+ return lines.join("\n").trimEnd();
884
+ }
885
+ async function runMcpSubcommand(subcommand, args) {
886
+ if (!subcommand || subcommand === "install" || subcommand === "print-config") {
887
+ const serverName = typeof args.name === "string" && args.name.trim()
888
+ ? args.name.trim()
889
+ : LONGTABLE_MCP_SERVER_NAME;
890
+ const packageSpec = resolveMcpPackageSpec(args);
891
+ const command = typeof args.command === "string" && args.command.trim() ? args.command.trim() : "npx";
892
+ const mcpArgs = command === "npx" ? ["-y", packageSpec] : [packageSpec];
893
+ const providers = resolveMcpProviders(args.provider);
894
+ const write = args.write === true;
895
+ const targets = [];
896
+ for (const provider of providers) {
897
+ if (provider === "codex") {
898
+ const path = resolveCodexMcpConfigPath(args);
899
+ const block = renderCodexMcpBlock(serverName, command, mcpArgs);
900
+ const content = write ? await writeCodexMcpConfig(path, block, serverName) : block;
901
+ targets.push({ provider, path, format: "toml", content });
902
+ }
903
+ if (provider === "claude") {
904
+ const path = resolveClaudeMcpSettingsPath(args);
905
+ const content = write
906
+ ? await writeClaudeMcpSettings(path, serverName, command, mcpArgs)
907
+ : renderClaudeMcpJson(serverName, command, mcpArgs);
908
+ targets.push({ provider, path, format: "json", content });
909
+ }
910
+ }
911
+ const result = {
912
+ serverName,
913
+ packageSpec,
914
+ command,
915
+ args: mcpArgs,
916
+ write,
917
+ targets
918
+ };
919
+ if (args.json === true) {
920
+ console.log(JSON.stringify(result, null, 2));
921
+ return;
922
+ }
923
+ console.log(renderMcpInstallSummary(result));
924
+ return;
925
+ }
926
+ throw new Error("Unknown mcp subcommand.");
927
+ }
928
+ function commandOnPath(command) {
929
+ try {
930
+ execSync(`command -v ${command}`, { stdio: "ignore" });
931
+ return true;
932
+ }
933
+ catch {
934
+ return false;
935
+ }
936
+ }
937
+ function missingNames(expected, installed) {
938
+ const installedSet = new Set(installed);
939
+ return expected.filter((name) => !installedSet.has(name));
940
+ }
941
+ function setupForProvider(setup, provider) {
942
+ return {
943
+ ...setup,
944
+ providerSelection: provider === "claude"
945
+ ? {
946
+ provider,
947
+ checkpointProtocol: "native_structured",
948
+ supportsStructuredQuestions: true
949
+ }
950
+ : {
951
+ provider,
952
+ checkpointProtocol: "numbered",
953
+ supportsStructuredQuestions: false
954
+ }
955
+ };
956
+ }
957
+ async function collectDoctorStatus(args) {
958
+ const roles = listRoleDefinitions();
959
+ const setupOverride = typeof args.setup === "string"
960
+ ? args.setup
961
+ : typeof args.path === "string"
962
+ ? args.path
963
+ : undefined;
964
+ const setupPath = resolveDefaultSetupPath(setupOverride).path;
965
+ const codexRuntimeOverride = typeof args["codex-runtime-path"] === "string"
966
+ ? args["codex-runtime-path"]
967
+ : undefined;
968
+ const claudeRuntimeOverride = typeof args["claude-runtime-path"] === "string"
969
+ ? args["claude-runtime-path"]
970
+ : undefined;
971
+ const codexDir = typeof args["codex-dir"] === "string"
972
+ ? args["codex-dir"]
973
+ : typeof args.dir === "string"
974
+ ? args.dir
975
+ : undefined;
976
+ const codexPromptsDir = typeof args["codex-prompts-dir"] === "string"
977
+ ? args["codex-prompts-dir"]
978
+ : typeof args["prompts-dir"] === "string"
979
+ ? args["prompts-dir"]
980
+ : typeof args.dir === "string"
981
+ ? args.dir
982
+ : undefined;
983
+ const claudeDir = typeof args["claude-dir"] === "string"
984
+ ? args["claude-dir"]
985
+ : typeof args.dir === "string"
986
+ ? args.dir
987
+ : undefined;
988
+ const codexRuntimePath = resolveDefaultRuntimeConfigPath("codex", codexRuntimeOverride).path;
989
+ const claudeRuntimePath = resolveDefaultRuntimeConfigPath("claude", claudeRuntimeOverride).path;
990
+ const expectedCodexSkills = buildCodexSkillSpecs(roles).map((skill) => skill.name);
991
+ const expectedClaudeSkills = buildClaudeSkillSpecs(roles).map((skill) => skill.name);
992
+ const [codexSkills, claudeSkills, codexAliases, workspace] = await Promise.all([
993
+ listInstalledCodexSkills(roles, codexDir),
994
+ listInstalledClaudeSkills(roles, claudeDir),
995
+ listInstalledCodexPromptAliases(codexPromptsDir),
996
+ inspectProjectWorkspace(typeof args.cwd === "string" ? args.cwd : cwd())
997
+ ]);
998
+ const installedCodexSkills = codexSkills.map((skill) => skill.name);
999
+ const installedClaudeSkills = claudeSkills.map((skill) => skill.name);
1000
+ return {
1001
+ setupPath,
1002
+ setupExists: existsSync(setupPath),
1003
+ providers: {
1004
+ codex: {
1005
+ command: "codex",
1006
+ commandOnPath: commandOnPath("codex"),
1007
+ runtimePath: codexRuntimePath,
1008
+ runtimeExists: existsSync(codexRuntimePath),
1009
+ skillsDir: resolveCodexSkillsDir(codexDir),
1010
+ expectedSkills: expectedCodexSkills,
1011
+ installedSkills: installedCodexSkills,
1012
+ missingSkills: missingNames(expectedCodexSkills, installedCodexSkills),
1013
+ promptsDir: resolveCodexPromptsDir(codexPromptsDir),
1014
+ legacyPromptFilesInstalled: codexAliases.map((alias) => alias.name)
1015
+ },
1016
+ claude: {
1017
+ command: "claude",
1018
+ commandOnPath: commandOnPath("claude"),
1019
+ runtimePath: claudeRuntimePath,
1020
+ runtimeExists: existsSync(claudeRuntimePath),
1021
+ skillsDir: resolveClaudeSkillsDir(claudeDir),
1022
+ expectedSkills: expectedClaudeSkills,
1023
+ installedSkills: installedClaudeSkills,
1024
+ missingSkills: missingNames(expectedClaudeSkills, installedClaudeSkills)
1025
+ }
1026
+ },
1027
+ workspace
1028
+ };
1029
+ }
1030
+ function renderProviderDoctorBlock(label, provider) {
1031
+ const expectedCount = provider.expectedSkills.length;
1032
+ const installedCount = provider.installedSkills.length;
1033
+ return [
1034
+ `${label}:`,
1035
+ `- command: ${provider.commandOnPath ? "present" : "missing"} (${provider.command})`,
1036
+ `- runtime artifact: ${provider.runtimeExists ? "present" : "missing"} (${provider.runtimePath})`,
1037
+ `- skills: ${installedCount}/${expectedCount} installed (${provider.skillsDir})`,
1038
+ ...(provider.missingSkills.length > 0
1039
+ ? [`- missing skills: ${provider.missingSkills.join(", ")}`]
1040
+ : ["- missing skills: none"])
1041
+ ];
1042
+ }
1043
+ function renderDoctorStatus(status) {
1044
+ const lines = [
1045
+ "LongTable doctor",
1046
+ `- setup: ${status.setupExists ? "present" : "missing"} (${status.setupPath})`,
1047
+ "",
1048
+ ...renderProviderDoctorBlock("Codex", status.providers.codex),
1049
+ `- legacy prompt files: ${status.providers.codex.legacyPromptFilesInstalled.length}`,
1050
+ ...(status.providers.codex.legacyPromptFilesInstalled.length > 0
1051
+ ? [`- legacy prompt names: ${status.providers.codex.legacyPromptFilesInstalled.join(", ")}`]
1052
+ : []),
1053
+ "",
1054
+ ...renderProviderDoctorBlock("Claude", status.providers.claude),
1055
+ "",
1056
+ "Workspace:"
1057
+ ];
1058
+ if (!status.workspace.found) {
1059
+ lines.push("- project: not found from current directory");
1060
+ }
1061
+ else {
1062
+ const workspace = status.workspace;
1063
+ 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}`);
1064
+ if ((workspace.recentInvocations ?? []).length > 0) {
1065
+ lines.push("- recent invocations:");
1066
+ for (const invocation of workspace.recentInvocations ?? []) {
1067
+ const roles = invocation.roles.length > 0 ? invocation.roles.join(",") : "auto";
1068
+ lines.push(` - ${invocation.kind}/${invocation.mode} via ${invocation.surface}: ${roles} (${invocation.status})`);
1069
+ }
1070
+ }
1071
+ if ((workspace.pendingQuestions ?? []).length > 0) {
1072
+ lines.push("- pending questions:");
1073
+ for (const question of workspace.pendingQuestions ?? []) {
1074
+ lines.push(` - ${question.id}: ${question.question} (${question.options.join("/")})`);
1075
+ }
1076
+ }
1077
+ }
1078
+ const nextActions = [];
1079
+ const canFix = status.providers.codex.missingSkills.length > 0 ||
1080
+ status.providers.claude.missingSkills.length > 0 ||
1081
+ status.providers.codex.legacyPromptFilesInstalled.length > 0 ||
1082
+ (status.setupExists &&
1083
+ (!status.providers.codex.runtimeExists || !status.providers.claude.runtimeExists));
1084
+ if (canFix) {
1085
+ nextActions.push("longtable doctor --fix");
1086
+ }
1087
+ if (!status.setupExists) {
1088
+ nextActions.push("longtable init --flow interview --provider codex --install-skills");
1089
+ }
1090
+ if (!status.workspace.found) {
1091
+ nextActions.push("longtable start");
1092
+ }
1093
+ const firstQuestion = status.workspace.pendingQuestions?.[0];
1094
+ if (firstQuestion) {
1095
+ nextActions.push(`longtable decide --question ${firstQuestion.id} --answer <value>`);
1096
+ }
1097
+ if (nextActions.length > 0) {
1098
+ lines.push("", "Next actions:");
1099
+ for (const action of nextActions) {
1100
+ lines.push(`- ${action}`);
1101
+ }
1102
+ }
1103
+ return lines.join("\n");
1104
+ }
1105
+ function renderRepairSummary(repair) {
1106
+ const lines = ["LongTable doctor repair"];
1107
+ if (repair.installedCodexSkills.length > 0) {
1108
+ lines.push(`- installed Codex skills: ${repair.installedCodexSkills.length}`);
1109
+ }
1110
+ if (repair.installedClaudeSkills.length > 0) {
1111
+ lines.push(`- installed Claude skills: ${repair.installedClaudeSkills.length}`);
1112
+ }
1113
+ if (repair.removedLegacyPromptFiles.length > 0) {
1114
+ lines.push(`- removed legacy prompt files: ${repair.removedLegacyPromptFiles.length}`);
1115
+ }
1116
+ if (repair.writtenRuntimeConfigs.length > 0) {
1117
+ lines.push("- wrote runtime configs:");
1118
+ for (const target of repair.writtenRuntimeConfigs) {
1119
+ lines.push(` - ${target.provider}: ${target.path}`);
1120
+ }
1121
+ }
1122
+ if (repair.skipped.length > 0) {
1123
+ lines.push("- skipped:");
1124
+ for (const item of repair.skipped) {
1125
+ lines.push(` - ${item}`);
1126
+ }
1127
+ }
1128
+ if (lines.length === 1) {
1129
+ lines.push("- no repairs needed");
1130
+ }
1131
+ return lines.join("\n");
1132
+ }
1133
+ async function repairDoctorStatus(args, status) {
1134
+ const roles = listRoleDefinitions();
1135
+ const codexDir = typeof args["codex-dir"] === "string"
1136
+ ? args["codex-dir"]
1137
+ : typeof args.dir === "string"
1138
+ ? args.dir
1139
+ : undefined;
1140
+ const codexPromptsDir = typeof args["codex-prompts-dir"] === "string"
1141
+ ? args["codex-prompts-dir"]
1142
+ : typeof args["prompts-dir"] === "string"
1143
+ ? args["prompts-dir"]
1144
+ : typeof args.dir === "string"
1145
+ ? args.dir
1146
+ : undefined;
1147
+ const claudeDir = typeof args["claude-dir"] === "string"
1148
+ ? args["claude-dir"]
1149
+ : typeof args.dir === "string"
1150
+ ? args.dir
1151
+ : undefined;
1152
+ const setupOverride = typeof args.setup === "string"
1153
+ ? args.setup
1154
+ : typeof args.path === "string"
1155
+ ? args.path
1156
+ : undefined;
1157
+ const codexRuntimeOverride = typeof args["codex-runtime-path"] === "string"
1158
+ ? args["codex-runtime-path"]
1159
+ : undefined;
1160
+ const claudeRuntimeOverride = typeof args["claude-runtime-path"] === "string"
1161
+ ? args["claude-runtime-path"]
1162
+ : undefined;
1163
+ const repair = {
1164
+ installedCodexSkills: [],
1165
+ installedClaudeSkills: [],
1166
+ removedLegacyPromptFiles: [],
1167
+ writtenRuntimeConfigs: [],
1168
+ skipped: []
1169
+ };
1170
+ if (status.providers.codex.missingSkills.length > 0) {
1171
+ repair.installedCodexSkills = (await installCodexSkills(roles, codexDir)).map((skill) => skill.name);
1172
+ }
1173
+ if (status.providers.claude.missingSkills.length > 0) {
1174
+ repair.installedClaudeSkills = (await installClaudeSkills(roles, claudeDir)).map((skill) => skill.name);
1175
+ }
1176
+ if (status.providers.codex.legacyPromptFilesInstalled.length > 0) {
1177
+ repair.removedLegacyPromptFiles = await removeCodexPromptAliases(codexPromptsDir);
1178
+ }
1179
+ if (!status.setupExists) {
1180
+ repair.skipped.push("runtime configs require a researcher setup; run `longtable init --flow interview --provider codex` first");
1181
+ return repair;
1182
+ }
1183
+ const setup = await loadSetupOutput(setupOverride);
1184
+ if (!status.providers.codex.runtimeExists) {
1185
+ const target = await writeRuntimeConfig(setupForProvider(setup, "codex"), status.setupPath, codexRuntimeOverride);
1186
+ repair.writtenRuntimeConfigs.push({
1187
+ provider: target.provider,
1188
+ path: target.path,
1189
+ format: target.format
1190
+ });
1191
+ }
1192
+ if (!status.providers.claude.runtimeExists) {
1193
+ const target = await writeRuntimeConfig(setupForProvider(setup, "claude"), status.setupPath, claudeRuntimeOverride);
1194
+ repair.writtenRuntimeConfigs.push({
1195
+ provider: target.provider,
1196
+ path: target.path,
1197
+ format: target.format
1198
+ });
1199
+ }
1200
+ return repair;
1201
+ }
1202
+ async function runDoctor(args) {
1203
+ const status = await collectDoctorStatus(args);
1204
+ if (args.fix === true) {
1205
+ const repair = await repairDoctorStatus(args, status);
1206
+ const updatedStatus = await collectDoctorStatus(args);
1207
+ if (args.json === true) {
1208
+ console.log(JSON.stringify({ repair, status: updatedStatus }, null, 2));
1209
+ return;
1210
+ }
1211
+ console.log(renderRepairSummary(repair));
1212
+ console.log("");
1213
+ console.log(renderDoctorStatus(updatedStatus));
1214
+ return;
1215
+ }
1216
+ if (args.json === true) {
1217
+ console.log(JSON.stringify(status, null, 2));
1218
+ return;
1219
+ }
1220
+ console.log(renderDoctorStatus(status));
1221
+ }
734
1222
  async function runCodexPersistInit(args) {
735
1223
  const { flow, provider, answers } = await readPersistAnswers(args);
736
1224
  const outputValue = createPersistedSetupOutput(answers, provider, flow);
@@ -742,11 +1230,16 @@ async function runCodexPersistInit(args) {
742
1230
  if (provider === "codex" && args["install-prompts"] === true) {
743
1231
  installedPrompts = await installCodexPromptAliases(typeof args.dir === "string" ? args.dir : undefined);
744
1232
  }
1233
+ let installedSkills = [];
1234
+ if (provider === "codex" && args["install-skills"] === true) {
1235
+ installedSkills = await installCodexSkills(listRoleDefinitions(), typeof args.dir === "string" ? args.dir : undefined);
1236
+ }
745
1237
  if (args.json === true) {
746
1238
  console.log(JSON.stringify({
747
1239
  setup: outputValue,
748
1240
  install: result,
749
- installedPrompts: installedPrompts.map((prompt) => prompt.name)
1241
+ installedPrompts: installedPrompts.map((prompt) => prompt.name),
1242
+ installedSkills: installedSkills.map((skill) => skill.name)
750
1243
  }, null, 2));
751
1244
  return;
752
1245
  }
@@ -757,16 +1250,24 @@ async function runCodexPersistInit(args) {
757
1250
  console.log("");
758
1251
  console.log("Installed Codex prompt files:");
759
1252
  for (const prompt of installedPrompts) {
760
- console.log(`- /prompts:${prompt.name}`);
1253
+ console.log(`- ${prompt.name}`);
1254
+ }
1255
+ console.log(" Note: prompt files are legacy and may not be exposed by your Codex build.");
1256
+ }
1257
+ if (installedSkills.length > 0) {
1258
+ console.log("");
1259
+ console.log("Installed Codex skill files:");
1260
+ for (const skill of installedSkills) {
1261
+ console.log(`- ${skill.name}`);
761
1262
  }
762
- console.log(" Note: whether Codex exposes these as slash commands depends on your Codex build.");
1263
+ console.log(" Use these inside Codex by naming LongTable naturally, e.g. `lt panel: ...`.");
763
1264
  }
764
1265
  if (provider === "codex") {
765
1266
  console.log("");
766
1267
  console.log("Next step:");
767
1268
  console.log("- Start here: `longtable start`.");
768
1269
  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.");
1270
+ console.log("- Codex skills are the preferred native surface. Prompt files are legacy and may not expose slash commands.");
770
1271
  console.log("- Suggested next action: create a project workspace and let LongTable interview the current session.");
771
1272
  }
772
1273
  }
@@ -828,6 +1329,20 @@ function inferModeFromPrompt(prompt) {
828
1329
  }
829
1330
  return "explore";
830
1331
  }
1332
+ function parsePanelVisibility(value) {
1333
+ if (value === "synthesis_only" ||
1334
+ value === "show_on_conflict" ||
1335
+ value === "always_visible") {
1336
+ return value;
1337
+ }
1338
+ return undefined;
1339
+ }
1340
+ function parsePanelMode(value) {
1341
+ if (value && VALID_MODES.has(value) && value !== "explore" && value !== "submit") {
1342
+ return value;
1343
+ }
1344
+ return "review";
1345
+ }
831
1346
  async function loadOptionalSetup(path) {
832
1347
  try {
833
1348
  return await loadSetupOutput(path);
@@ -864,6 +1379,10 @@ async function runModeCommand(mode, args) {
864
1379
  throw new Error(`Invalid stage: ${stage}`);
865
1380
  }
866
1381
  const setup = await loadOptionalSetup(typeof args.setup === "string" ? args.setup : undefined);
1382
+ const projectContext = await loadProjectContextFromDirectory(workingDirectory);
1383
+ if (projectContext) {
1384
+ await assertWorkspaceNotBlocked(projectContext);
1385
+ }
867
1386
  const projectAware = await buildProjectAwarePrompt(prompt, workingDirectory);
868
1387
  const panelPreference = setup?.profileSeed.panelPreference;
869
1388
  const panelRequested = args.panel === true ||
@@ -898,6 +1417,125 @@ async function runModeCommand(mode, args) {
898
1417
  });
899
1418
  exit(exitCode);
900
1419
  }
1420
+ async function runPanelCommand(args) {
1421
+ const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
1422
+ const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
1423
+ if (!prompt) {
1424
+ throw new Error("A prompt is required.");
1425
+ }
1426
+ const setup = await loadOptionalSetup(typeof args.setup === "string" ? args.setup : undefined);
1427
+ const existingContext = await loadProjectContextFromDirectory(workingDirectory);
1428
+ if (existingContext) {
1429
+ await assertWorkspaceNotBlocked(existingContext);
1430
+ }
1431
+ const projectAware = await buildProjectAwarePrompt(prompt, workingDirectory);
1432
+ const provider = setup?.providerSelection.provider;
1433
+ const visibility = parsePanelVisibility(typeof args.visibility === "string" ? args.visibility : undefined) ??
1434
+ parsePanelVisibility(setup?.profileSeed.panelPreference) ??
1435
+ "always_visible";
1436
+ const mode = parsePanelMode(typeof args.mode === "string" ? args.mode : undefined);
1437
+ const fallback = buildPanelFallback({
1438
+ prompt: projectAware.prompt,
1439
+ mode,
1440
+ roleFlag: typeof args.role === "string" ? args.role : undefined,
1441
+ provider,
1442
+ visibility
1443
+ });
1444
+ if (projectAware.projectContextFound) {
1445
+ const context = await loadProjectContextFromDirectory(workingDirectory);
1446
+ if (context) {
1447
+ await appendInvocationRecordToWorkspace(context, fallback.invocationRecord, [fallback.questionRecord]);
1448
+ }
1449
+ }
1450
+ if (args.json === true) {
1451
+ console.log(JSON.stringify({
1452
+ intent: fallback.intent,
1453
+ plan: fallback.plan,
1454
+ result: fallback.result,
1455
+ invocationRecord: fallback.invocationRecord,
1456
+ questionRecord: fallback.questionRecord,
1457
+ execution: {
1458
+ status: "planned",
1459
+ stableSurface: "sequential_fallback",
1460
+ nativeParallel: "not_required_for_option_a",
1461
+ projectContextFound: projectAware.projectContextFound,
1462
+ invocationLogged: projectAware.projectContextFound
1463
+ },
1464
+ fallbackPrompt: fallback.prompt
1465
+ }, null, 2));
1466
+ return;
1467
+ }
1468
+ if (args.print === true) {
1469
+ console.log(fallback.prompt);
1470
+ return;
1471
+ }
1472
+ console.log(renderPanelSummary(fallback.plan));
1473
+ console.log("");
1474
+ const exitCode = await runCodexThinWrapper({
1475
+ prompt: fallback.prompt,
1476
+ mode,
1477
+ setupPath: typeof args.setup === "string" ? args.setup : undefined,
1478
+ workingDirectory,
1479
+ json: false
1480
+ });
1481
+ exit(exitCode);
1482
+ }
1483
+ async function runQuestion(args) {
1484
+ const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
1485
+ const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
1486
+ if (!prompt) {
1487
+ throw new Error("A decision context is required. Pass --prompt <text>.");
1488
+ }
1489
+ const context = await loadProjectContextFromDirectory(workingDirectory);
1490
+ if (!context) {
1491
+ throw new Error("No LongTable project workspace was found here. Run this inside a project or pass --cwd.");
1492
+ }
1493
+ const provider = args.provider === "claude" ? "claude" : args.provider === "codex" ? "codex" : undefined;
1494
+ const required = args.required === true ? true : args.advisory === true ? false : undefined;
1495
+ const result = await createWorkspaceQuestion({
1496
+ context,
1497
+ prompt,
1498
+ title: typeof args.title === "string" ? args.title : undefined,
1499
+ question: typeof args.text === "string" ? args.text : undefined,
1500
+ provider,
1501
+ required
1502
+ });
1503
+ const transport = provider === "claude"
1504
+ ? renderQuestionRecordInput(result.question)
1505
+ : renderQuestionRecordPrompt(result.question);
1506
+ if (args.json === true) {
1507
+ console.log(JSON.stringify({
1508
+ question: result.question,
1509
+ transport,
1510
+ files: {
1511
+ state: context.stateFilePath,
1512
+ current: context.currentFilePath
1513
+ },
1514
+ nextAction: `longtable decide --question ${result.question.id} --answer <value>`
1515
+ }, null, 2));
1516
+ return;
1517
+ }
1518
+ if (args.print === true) {
1519
+ if (provider === "claude") {
1520
+ console.log(JSON.stringify(transport, null, 2));
1521
+ }
1522
+ else {
1523
+ console.log("prompt" in transport ? transport.prompt : JSON.stringify(transport, null, 2));
1524
+ }
1525
+ return;
1526
+ }
1527
+ const optionValues = [
1528
+ ...result.question.prompt.options.map((option) => option.value),
1529
+ ...(result.question.prompt.allowOther ? ["other"] : [])
1530
+ ];
1531
+ console.log(result.question.prompt.required ? "LongTable required Researcher Checkpoint recorded" : "LongTable advisory Researcher Checkpoint recorded");
1532
+ console.log(`- question: ${result.question.id}`);
1533
+ console.log(`- checkpoint: ${result.question.prompt.checkpointKey ?? "manual"}`);
1534
+ console.log(`- prompt: ${result.question.prompt.question}`);
1535
+ console.log(`- options: ${optionValues.join("/")}`);
1536
+ console.log(`- answer: longtable decide --question ${result.question.id} --answer <value>`);
1537
+ console.log(`- current: ${context.currentFilePath}`);
1538
+ }
901
1539
  async function runAsk(args) {
902
1540
  const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
903
1541
  if (!prompt) {
@@ -907,7 +1545,7 @@ async function runAsk(args) {
907
1545
  const effectivePrompt = directive.cleanedPrompt;
908
1546
  const inferred = directive.mode ?? inferModeFromPrompt(effectivePrompt);
909
1547
  if (inferred === "status") {
910
- await runCodexSubcommand("status", args);
1548
+ await runDoctor(args);
911
1549
  return;
912
1550
  }
913
1551
  const mode = inferred === "panel" ? "review" : inferred;
@@ -918,18 +1556,60 @@ async function runAsk(args) {
918
1556
  if (directive.roles.length > 0 && typeof delegatedArgs.role !== "string") {
919
1557
  delegatedArgs.role = directive.roles.join(",");
920
1558
  }
921
- if ((inferred === "panel" || directive.panel) && delegatedArgs.panel !== true) {
922
- delegatedArgs.panel = true;
923
- delegatedArgs["show-conflicts"] = true;
1559
+ if (inferred === "panel" || directive.panel || delegatedArgs.panel === true) {
1560
+ await runPanelCommand({
1561
+ ...delegatedArgs,
1562
+ visibility: "always_visible"
1563
+ });
1564
+ return;
924
1565
  }
925
1566
  await runModeCommand(mode, delegatedArgs);
926
1567
  }
1568
+ async function runDecide(args) {
1569
+ const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
1570
+ const answer = typeof args.answer === "string" ? args.answer.trim() : "";
1571
+ if (!answer) {
1572
+ throw new Error("A decision answer is required. Pass --answer <value-or-text>.");
1573
+ }
1574
+ const context = await loadProjectContextFromDirectory(workingDirectory);
1575
+ if (!context) {
1576
+ throw new Error("No LongTable project workspace was found here. Run this inside a project or pass --cwd.");
1577
+ }
1578
+ const provider = args.provider === "claude" ? "claude" : args.provider === "codex" ? "codex" : undefined;
1579
+ const result = await answerWorkspaceQuestion({
1580
+ context,
1581
+ questionId: typeof args.question === "string" ? args.question : undefined,
1582
+ answer,
1583
+ rationale: typeof args.rationale === "string" ? args.rationale : undefined,
1584
+ provider
1585
+ });
1586
+ if (args.json === true) {
1587
+ console.log(JSON.stringify({
1588
+ question: result.question,
1589
+ decision: result.decision,
1590
+ files: {
1591
+ state: context.stateFilePath,
1592
+ current: context.currentFilePath
1593
+ }
1594
+ }, null, 2));
1595
+ return;
1596
+ }
1597
+ console.log("LongTable decision recorded");
1598
+ console.log(`- question: ${result.question.id}`);
1599
+ console.log(`- decision: ${result.decision.id}`);
1600
+ console.log(`- answer: ${result.decision.selectedOption ?? answer}`);
1601
+ console.log(`- state: ${context.stateFilePath}`);
1602
+ console.log(`- current: ${context.currentFilePath}`);
1603
+ }
927
1604
  async function runRoles(args) {
928
1605
  const payload = PERSONA_DEFINITIONS.map((persona) => ({
929
1606
  key: persona.key,
930
1607
  label: persona.label,
931
1608
  description: persona.shortDescription,
932
1609
  triggerMode: persona.triggerMode,
1610
+ defaultPanelMember: persona.defaultPanelMember,
1611
+ checkpointSensitivity: persona.checkpointSensitivity,
1612
+ supportedModes: persona.supportedModes,
933
1613
  exampleTriggers: persona.synonyms.slice(0, 4)
934
1614
  }));
935
1615
  if (args.json === true) {
@@ -944,6 +1624,8 @@ async function runRoles(args) {
944
1624
  console.log(`- ${persona.label} (${persona.key})`);
945
1625
  console.log(` ${persona.description}`);
946
1626
  console.log(` Trigger: ${persona.triggerMode === "auto-callable" ? "auto-callable when your language strongly implies it" : "explicit request only"}`);
1627
+ console.log(` Panel: ${persona.defaultPanelMember ? "default member" : "contextual member"}`);
1628
+ console.log(` Checkpoint sensitivity: ${persona.checkpointSensitivity}`);
947
1629
  console.log(` Examples: ${persona.exampleTriggers.join(", ")}`);
948
1630
  }
949
1631
  }
@@ -1027,12 +1709,28 @@ async function runResume(args) {
1027
1709
  }
1028
1710
  async function runCodexSubcommand(subcommand, args) {
1029
1711
  const customDir = typeof args.dir === "string" ? args.dir : undefined;
1712
+ const roles = listRoleDefinitions();
1713
+ if (subcommand === "install-skills") {
1714
+ const installed = await installCodexSkills(roles, customDir);
1715
+ console.log(`Installed ${installed.length} LongTable Codex skills in ${resolveCodexSkillsDir(customDir)}`);
1716
+ console.log("Use them inside Codex with natural-language triggers such as `lt explore: ...` or `lt panel: ...`.");
1717
+ console.log("If you want an explicit trigger, use `$longtable` when your Codex build exposes skills that way.");
1718
+ for (const skill of installed) {
1719
+ console.log(`- ${skill.name}`);
1720
+ }
1721
+ return;
1722
+ }
1723
+ if (subcommand === "remove-skills") {
1724
+ const removed = await removeCodexSkills(roles, customDir);
1725
+ console.log(`Removed ${removed.length} LongTable Codex skills from ${resolveCodexSkillsDir(customDir)}`);
1726
+ return;
1727
+ }
1030
1728
  if (subcommand === "install-prompts") {
1031
1729
  const installed = await installCodexPromptAliases(customDir);
1032
- console.log(`Installed ${installed.length} LongTable prompt aliases in ${resolveCodexPromptsDir(customDir)}`);
1033
- console.log("Note: prompt-file discovery depends on the Codex build. Treat this as an experimental integration.");
1730
+ console.log(`Installed ${installed.length} legacy LongTable prompt files in ${resolveCodexPromptsDir(customDir)}`);
1731
+ console.log("Note: current Codex builds may not expose these files as slash commands. Prefer `longtable codex install-skills`.");
1034
1732
  for (const prompt of installed) {
1035
- console.log(`- /prompts:${prompt.name}`);
1733
+ console.log(`- ${prompt.name}`);
1036
1734
  }
1037
1735
  return;
1038
1736
  }
@@ -1042,11 +1740,12 @@ async function runCodexSubcommand(subcommand, args) {
1042
1740
  }
1043
1741
  if (subcommand === "remove-prompts") {
1044
1742
  const removed = await removeCodexPromptAliases(customDir);
1045
- console.log(`Removed ${removed.length} LongTable prompt aliases from ${resolveCodexPromptsDir(customDir)}`);
1743
+ console.log(`Removed ${removed.length} legacy LongTable prompt files from ${resolveCodexPromptsDir(customDir)}`);
1046
1744
  return;
1047
1745
  }
1048
1746
  if (subcommand === "status") {
1049
1747
  const aliases = await listInstalledCodexPromptAliases(customDir);
1748
+ const skills = await listInstalledCodexSkills(roles, customDir);
1050
1749
  const setupPath = resolveDefaultSetupPath(typeof args.path === "string" ? args.path : undefined).path;
1051
1750
  const runtimePath = resolveDefaultRuntimeConfigPath("codex", typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined).path;
1052
1751
  const status = {
@@ -1054,8 +1753,10 @@ async function runCodexSubcommand(subcommand, args) {
1054
1753
  setupExists: existsSync(setupPath),
1055
1754
  runtimePath,
1056
1755
  runtimeExists: existsSync(runtimePath),
1756
+ skillsDir: resolveCodexSkillsDir(customDir),
1757
+ skillsInstalled: skills.map((skill) => skill.name),
1057
1758
  promptsDir: resolveCodexPromptsDir(customDir),
1058
- promptAliasesInstalled: aliases.map((alias) => alias.name)
1759
+ legacyPromptFilesInstalled: aliases.map((alias) => alias.name)
1059
1760
  };
1060
1761
  if (args.json === true) {
1061
1762
  console.log(JSON.stringify(status, null, 2));
@@ -1064,21 +1765,81 @@ async function runCodexSubcommand(subcommand, args) {
1064
1765
  console.log("LongTable Codex status");
1065
1766
  console.log(`- setup: ${status.setupExists ? "present" : "missing"} (${setupPath})`);
1066
1767
  console.log(`- codex runtime artifact: ${status.runtimeExists ? "present" : "missing"} (${runtimePath})`);
1768
+ console.log(`- skills dir: ${status.skillsDir}`);
1769
+ if (skills.length === 0) {
1770
+ console.log("- skills: none");
1771
+ }
1772
+ else {
1773
+ console.log("- skills:");
1774
+ for (const skill of skills) {
1775
+ console.log(` - ${skill.name}`);
1776
+ }
1777
+ }
1067
1778
  console.log(`- prompt aliases dir: ${status.promptsDir}`);
1068
- console.log("- prompt-file integration: experimental (your Codex build may not expose these as slash commands)");
1779
+ console.log("- prompt files: legacy; current Codex builds may not expose these as slash commands");
1069
1780
  if (aliases.length === 0) {
1070
- console.log("- prompt aliases: none");
1781
+ console.log("- legacy prompt files: none");
1071
1782
  }
1072
1783
  else {
1073
- console.log("- prompt aliases:");
1784
+ console.log("- legacy prompt files:");
1074
1785
  for (const alias of aliases) {
1075
- console.log(` - /prompts:${alias.name}`);
1786
+ console.log(` - ${alias.name}`);
1076
1787
  }
1077
1788
  }
1078
1789
  return;
1079
1790
  }
1080
1791
  throw new Error("Unknown codex subcommand.");
1081
1792
  }
1793
+ async function runClaudeSubcommand(subcommand, args) {
1794
+ const customDir = typeof args.dir === "string" ? args.dir : undefined;
1795
+ const roles = listRoleDefinitions();
1796
+ if (subcommand === "install-skills") {
1797
+ const installed = await installClaudeSkills(roles, customDir);
1798
+ console.log(`Installed ${installed.length} LongTable Claude skills in ${resolveClaudeSkillsDir(customDir)}`);
1799
+ console.log("Use them inside Claude Code with natural-language triggers such as `lt explore: ...` or `lt panel: ...`.");
1800
+ for (const skill of installed) {
1801
+ console.log(`- ${skill.name}`);
1802
+ }
1803
+ return;
1804
+ }
1805
+ if (subcommand === "remove-skills") {
1806
+ const removed = await removeClaudeSkills(roles, customDir);
1807
+ console.log(`Removed ${removed.length} LongTable Claude skills from ${resolveClaudeSkillsDir(customDir)}`);
1808
+ return;
1809
+ }
1810
+ if (subcommand === "status") {
1811
+ const skills = await listInstalledClaudeSkills(roles, customDir);
1812
+ const setupPath = resolveDefaultSetupPath(typeof args.path === "string" ? args.path : undefined).path;
1813
+ const runtimePath = resolveDefaultRuntimeConfigPath("claude", typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined).path;
1814
+ const status = {
1815
+ setupPath,
1816
+ setupExists: existsSync(setupPath),
1817
+ runtimePath,
1818
+ runtimeExists: existsSync(runtimePath),
1819
+ skillsDir: resolveClaudeSkillsDir(customDir),
1820
+ skillsInstalled: skills.map((skill) => skill.name)
1821
+ };
1822
+ if (args.json === true) {
1823
+ console.log(JSON.stringify(status, null, 2));
1824
+ return;
1825
+ }
1826
+ console.log("LongTable Claude status");
1827
+ console.log(`- setup: ${status.setupExists ? "present" : "missing"} (${setupPath})`);
1828
+ console.log(`- claude runtime artifact: ${status.runtimeExists ? "present" : "missing"} (${runtimePath})`);
1829
+ console.log(`- skills dir: ${status.skillsDir}`);
1830
+ if (skills.length === 0) {
1831
+ console.log("- skills: none");
1832
+ }
1833
+ else {
1834
+ console.log("- skills:");
1835
+ for (const skill of skills) {
1836
+ console.log(` - ${skill.name}`);
1837
+ }
1838
+ }
1839
+ return;
1840
+ }
1841
+ throw new Error("Unknown claude subcommand.");
1842
+ }
1082
1843
  async function main() {
1083
1844
  const parsed = parseArgs(process.argv.slice(2));
1084
1845
  const { command, subcommand, values } = parsed;
@@ -1098,6 +1859,10 @@ async function main() {
1098
1859
  await runResume(values);
1099
1860
  return;
1100
1861
  }
1862
+ if (command === "doctor" || command === "status") {
1863
+ await runDoctor(values);
1864
+ return;
1865
+ }
1101
1866
  if (command === "roles") {
1102
1867
  await runRoles(values);
1103
1868
  return;
@@ -1110,15 +1875,42 @@ async function main() {
1110
1875
  await runInstall(values);
1111
1876
  return;
1112
1877
  }
1878
+ if (command === "mcp") {
1879
+ await runMcpSubcommand(subcommand, values);
1880
+ return;
1881
+ }
1113
1882
  if (command === "ask") {
1114
1883
  await runAsk(values);
1115
1884
  return;
1116
1885
  }
1886
+ if (command === "question") {
1887
+ await runQuestion(values);
1888
+ return;
1889
+ }
1890
+ if (command === "panel") {
1891
+ await runPanelCommand(values);
1892
+ return;
1893
+ }
1894
+ if (command === "decide") {
1895
+ await runDecide(values);
1896
+ return;
1897
+ }
1117
1898
  if (command === "codex") {
1118
1899
  await runCodexSubcommand(subcommand, values);
1119
1900
  return;
1120
1901
  }
1902
+ if (command === "claude") {
1903
+ await runClaudeSubcommand(subcommand, values);
1904
+ return;
1905
+ }
1121
1906
  if (VALID_MODES.has(command)) {
1907
+ if (values.panel === true) {
1908
+ await runPanelCommand({
1909
+ ...values,
1910
+ mode: command
1911
+ });
1912
+ return;
1913
+ }
1122
1914
  await runModeCommand(command, values);
1123
1915
  return;
1124
1916
  }