@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/README.md +82 -1
- package/dist/cli.js +825 -33
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/panel.d.ts +40 -0
- package/dist/panel.js +268 -0
- package/dist/personas.d.ts +5 -0
- package/dist/personas.js +44 -8
- package/dist/project-session.d.ts +82 -0
- package/dist/project-session.js +367 -3
- package/dist/prompt-aliases.js +3 -3
- package/package.json +7 -4
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 {
|
|
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-
|
|
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-
|
|
93
|
-
" longtable codex install-
|
|
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
|
-
|
|
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(`-
|
|
740
|
+
console.log(`- ${prompt.name}`);
|
|
703
741
|
}
|
|
704
|
-
console.log(" Note:
|
|
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
|
|
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(`-
|
|
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("
|
|
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
|
|
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
|
|
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 (
|
|
922
|
-
|
|
923
|
-
|
|
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
|
|
1033
|
-
console.log("Note:
|
|
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(`-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
1781
|
+
console.log("- legacy prompt files: none");
|
|
1071
1782
|
}
|
|
1072
1783
|
else {
|
|
1073
|
-
console.log("- prompt
|
|
1784
|
+
console.log("- legacy prompt files:");
|
|
1074
1785
|
for (const alias of aliases) {
|
|
1075
|
-
console.log(` -
|
|
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
|
}
|