@longtable/cli 0.1.15 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,34 +19,25 @@ The basic contract is:
19
19
  npm install -g @longtable/cli
20
20
  ```
21
21
 
22
- For global npm installs, LongTable also bootstraps provider-native skill files:
23
-
24
- - Codex skills are written to `~/.codex/skills/`.
25
- - Claude Code skills are written when the `claude` command is detected.
26
- - `LONGTABLE_POSTINSTALL_PROVIDERS=all npm install -g @longtable/cli` installs
27
- both provider skill sets.
28
- - `LONGTABLE_SKIP_POSTINSTALL=1 npm install -g @longtable/cli` skips the skill
29
- bootstrap.
30
-
31
- MCP config writes are intentionally opt-in:
32
-
33
- ```bash
34
- longtable mcp install --provider codex --write
35
- ```
36
-
37
- During `longtable init --flow interview`, LongTable asks whether to configure
38
- MCP and whether provider-native team/subagent surfaces should be used when they
39
- are available. The stable fallback remains LongTable's sequential panel.
22
+ The npm install only installs the CLI. It does not write Codex skills, MCP
23
+ config, hooks, tmux state, or provider runtime files without explicit setup
24
+ approval.
40
25
 
41
26
  ## Primary Flow
42
27
 
43
28
  ```bash
29
+ longtable setup --provider codex
44
30
  longtable init --flow interview
45
31
  longtable start
46
32
  cd "<project-path>"
47
33
  codex
48
34
  ```
49
35
 
36
+ `longtable setup --provider codex` is the permission-first setup route. It asks
37
+ which runtime surfaces LongTable may enable and explains why each choice matters:
38
+ CLI only, skills, skills + MCP, skills + MCP + sentinel, intervention posture,
39
+ tmux HUD/console, and team discussion mode.
40
+
50
41
  Return later:
51
42
 
52
43
  ```bash
@@ -100,6 +91,9 @@ longtable resume --cwd "<project-path>"
100
91
  longtable roles
101
92
  longtable ask --cwd "<project-path>" --prompt "..."
102
93
  longtable panel --prompt "..."
94
+ longtable sentinel --prompt "Should I define a new measurement construct?"
95
+ longtable hud --watch
96
+ longtable team --tmux --prompt "Review this measurement plan."
103
97
  longtable codex install-skills
104
98
  longtable claude install-skills
105
99
  ```
@@ -170,6 +164,22 @@ Default panel roles include:
170
164
 
171
165
  Use `--role` to constrain the panel when the research problem is already clear.
172
166
 
167
+ ## Sentinel, HUD, And Tmux Team
168
+
169
+ `longtable sentinel` is an explicit gap/tacit check for prompts that may contain
170
+ measurement, theory, method, evidence, authorship, or tacit-assumption risks.
171
+ Use `--record` inside a LongTable workspace to store the finding as an
172
+ unconfirmed inferred hypothesis.
173
+
174
+ `longtable hud --watch` renders a compact view of the current project goal,
175
+ blocker, pending checkpoints, recent decisions, and invocation counts.
176
+ `longtable hud --tmux` opens that view in a tmux pane.
177
+
178
+ `longtable team --tmux` opens role-specific panes for research discussion and
179
+ writes logs under `.longtable/team/<id>/`. This is panel discussion, not merely
180
+ parallel execution: role panes are prompted to state claims, objections, open
181
+ questions, and likely disagreement.
182
+
173
183
  ## Evidence And Search Direction
174
184
 
175
185
  LongTable should not behave like a generic web scraper. Research search should
package/dist/cli.js CHANGED
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { existsSync, readFileSync, statSync } from "node:fs";
3
3
  import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
4
- import { execSync } from "node:child_process";
4
+ import { execFileSync, execSync } from "node:child_process";
5
5
  import { emitKeypressEvents } from "node:readline";
6
6
  import { createInterface } from "node:readline/promises";
7
7
  import { stdin as input, stdout as output, cwd, exit } from "node:process";
8
- import { dirname, resolve } from "node:path";
8
+ import { dirname, join, resolve } from "node:path";
9
9
  import { homedir } from "node:os";
10
+ import { classifyCheckpointTrigger } from "@longtable/checkpoints";
10
11
  import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput, installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultRuntimeConfigPath, resolveDefaultSetupPath, saveSetupAndRuntimeConfig, serializeSetupOutput, writeRuntimeConfig } from "@longtable/setup";
11
12
  import { buildCodexSkillSpecs, buildCodexThinWrappedPrompt, installCodexSkills, listInstalledCodexSkills, renderQuestionRecordPrompt, removeCodexSkills, resolveCodexSkillsDir, runCodexThinWrapper } from "@longtable/provider-codex";
12
13
  import { buildClaudeSkillSpecs, installClaudeSkills, listInstalledClaudeSkills, renderQuestionRecordInput, removeClaudeSkills, resolveClaudeSkillsDir } from "@longtable/provider-claude";
@@ -14,7 +15,7 @@ import { installCodexPromptAliases, listInstalledCodexPromptAliases, removeCodex
14
15
  import { buildPersonaGuidance, parseInvocationDirective } from "./persona-router.js";
15
16
  import { PERSONA_DEFINITIONS, listRoleDefinitions } from "./personas.js";
16
17
  import { buildPanelFallback, renderPanelSummary } from "./panel.js";
17
- import { appendInvocationRecordToWorkspace, assertWorkspaceNotBlocked, answerWorkspaceQuestion, createWorkspaceClarificationCard, createWorkspaceQuestion, createOrUpdateProjectWorkspace, inspectProjectWorkspace, loadProjectContextFromDirectory, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
18
+ import { appendInvocationRecordToWorkspace, assertWorkspaceNotBlocked, answerWorkspaceQuestion, createWorkspaceClarificationCard, createWorkspaceQuestion, createOrUpdateProjectWorkspace, inspectProjectWorkspace, loadWorkspaceState, loadProjectContextFromDirectory, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
18
19
  const VALID_MODES = new Set([
19
20
  "explore",
20
21
  "review",
@@ -40,7 +41,7 @@ const ANSI = {
40
41
  green: "\u001B[32m"
41
42
  };
42
43
  const LONGTABLE_MCP_SERVER_NAME = "longtable-state";
43
- const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.15";
44
+ const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.14";
44
45
  const LONGTABLE_MCP_MARKER_START = "# LongTable state MCP START";
45
46
  const LONGTABLE_MCP_MARKER_END = "# LongTable state MCP END";
46
47
  function style(text, prefix) {
@@ -76,7 +77,8 @@ function usage() {
76
77
  " Run `longtable ...` in your terminal, not inside the Codex chat box.",
77
78
  " After `longtable start`, move into the created project directory and open `codex` there.",
78
79
  "",
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] [--agent-team native_when_available|sequential_panel_only] [--mcp skip|print_config|write_provider|write_all] [--json] [--no-install] [--install-skills] [--install-prompts]",
80
+ " 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]",
81
+ " longtable setup [--provider codex|claude] [--json] [--dir <path>] [--skills-dir <path>] [--runtime-path <file>] [--setup-path <file>]",
80
82
  " longtable start [--path <dir>] [--name <project>] [--goal <text>] [--blocker <text>] [--perspectives <role[,role]>] [--disagreement synthesis_only|show_on_conflict|always_visible] [--setup <path>] [--json]",
81
83
  " longtable resume [--cwd <path>] [--json]",
82
84
  " longtable doctor [--cwd <path>] [--fix] [--json] [--codex-dir <path>] [--claude-dir <path>] [--codex-prompts-dir <path>] [--codex-runtime-path <file>] [--claude-runtime-path <file>]",
@@ -85,6 +87,9 @@ function usage() {
85
87
  " longtable show [--json] [--path <file>]",
86
88
  " longtable install [--json] [--path <file>] [--runtime-path <file>]",
87
89
  " longtable mcp install [--provider codex|claude|all] [--write] [--json] [--codex-config <path>] [--claude-settings <path>] [--package <spec>]",
90
+ " longtable hud [--watch] [--tmux] [--preset minimal|full] [--cwd <path>] [--json]",
91
+ " longtable sentinel --prompt <text> [--cwd <path>] [--json] [--record]",
92
+ " longtable team --prompt <text> [--role <role[,role]>] [--tmux] [--cwd <path>] [--json]",
88
93
  " longtable ask [--prompt <text>] [--print] [--json] [--setup <path>] [--cwd <path>]",
89
94
  " longtable clarify --prompt <task-context> [--provider codex|claude] [--required|--advisory] [--print] [--cwd <path>] [--json] [--force]",
90
95
  " longtable question --prompt <decision-context> [--title <text>] [--text <question>] [--provider codex|claude] [--required|--advisory] [--print] [--cwd <path>] [--json]",
@@ -120,7 +125,7 @@ function parseArgs(argv) {
120
125
  const values = {};
121
126
  let subcommand = maybeSubcommand;
122
127
  const modeCommand = command && VALID_MODES.has(command);
123
- const directCommand = command && ["init", "start", "resume", "doctor", "status", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "panel", "decide"].includes(command);
128
+ const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "panel", "decide", "hud", "sentinel", "team"].includes(command);
124
129
  let startIndex = 1;
125
130
  if (modeCommand) {
126
131
  subcommand = undefined;
@@ -197,12 +202,9 @@ function questionSection(questionId) {
197
202
  if (questionId === "preferredCheckpointIntensity" || questionId === "preferredEntryMode") {
198
203
  return "Interaction style";
199
204
  }
200
- if (questionId === "weakestDomain" || questionId === "panelPreference" || questionId === "agentTeamPreference") {
205
+ if (questionId === "weakestDomain" || questionId === "panelPreference") {
201
206
  return "How LongTable should challenge you";
202
207
  }
203
- if (questionId === "mcpPreference") {
204
- return "Provider integration";
205
- }
206
208
  return "Authorship and voice";
207
209
  }
208
210
  function formatModeLabel(mode) {
@@ -499,12 +501,6 @@ function toSetupAnswers(args) {
499
501
  : undefined,
500
502
  panelPreference: typeof args["panel-preference"] === "string"
501
503
  ? String(args["panel-preference"])
502
- : undefined,
503
- agentTeamPreference: typeof args["agent-team"] === "string"
504
- ? String(args["agent-team"])
505
- : undefined,
506
- mcpPreference: typeof args.mcp === "string"
507
- ? String(args.mcp)
508
504
  : undefined
509
505
  };
510
506
  }
@@ -552,11 +548,6 @@ async function collectInteractiveAnswers(initialFlow) {
552
548
  answers.weakestDomain = value;
553
549
  if (question.id === "panelPreference")
554
550
  answers.panelPreference = value;
555
- if (question.id === "agentTeamPreference") {
556
- answers.agentTeamPreference = value;
557
- }
558
- if (question.id === "mcpPreference")
559
- answers.mcpPreference = value;
560
551
  }
561
552
  return {
562
553
  flow,
@@ -568,6 +559,172 @@ async function collectInteractiveAnswers(initialFlow) {
568
559
  rl.close();
569
560
  }
570
561
  }
562
+ function buildPermissionSetupChoices() {
563
+ return {
564
+ surfaces: [
565
+ {
566
+ id: "cli_only",
567
+ label: "CLI only",
568
+ description: "Why: least invasive. Tradeoff: no natural in-provider LongTable entrypoints."
569
+ },
570
+ {
571
+ id: "skills",
572
+ label: "Skills",
573
+ description: "Why: enables natural LongTable skill routing. Tradeoff: writes provider skill files."
574
+ },
575
+ {
576
+ id: "skills_mcp",
577
+ label: "Skills + MCP",
578
+ description: "Why: adds structured state access. Tradeoff: writes provider config for MCP transport."
579
+ },
580
+ {
581
+ id: "skills_mcp_sentinel",
582
+ label: "Skills + MCP + Sentinel",
583
+ description: "Why: prepares advisory gap/tacit monitoring. Tradeoff: LongTable may nudge research turns."
584
+ }
585
+ ],
586
+ intervention: [
587
+ {
588
+ id: "advisory",
589
+ label: "Advisory",
590
+ description: "Why: notices gaps without blocking. Tradeoff: you may still miss hard commitments."
591
+ },
592
+ {
593
+ id: "balanced",
594
+ label: "Balanced",
595
+ description: "Why: blocks clear theory, measurement, method, or evidence commitments. Tradeoff: occasional stops."
596
+ },
597
+ {
598
+ id: "strong",
599
+ label: "Strong",
600
+ description: "Why: maximizes judgment protection. Tradeoff: more interruption before closure."
601
+ }
602
+ ],
603
+ tmux: [
604
+ {
605
+ id: "standard",
606
+ label: "Standard chat",
607
+ description: "Why: portable default. Tradeoff: checkpoints and gaps are less persistently visible."
608
+ },
609
+ {
610
+ id: "hud",
611
+ label: "Research HUD",
612
+ description: "Why: keeps goals, blockers, and pending checkpoints visible. Requires tmux."
613
+ },
614
+ {
615
+ id: "console",
616
+ label: "Research console",
617
+ description: "Why: enables a richer tmux layout for HUD and team discussion. Requires tmux."
618
+ }
619
+ ],
620
+ team: [
621
+ {
622
+ id: "off",
623
+ label: "Off",
624
+ description: "Why: simplest. Tradeoff: panel disagreement stays inside one LongTable response."
625
+ },
626
+ {
627
+ id: "panel",
628
+ label: "Structured panel",
629
+ description: "Why: role disagreement is visible without tmux. Tradeoff: not parallel."
630
+ },
631
+ {
632
+ id: "tmux_team",
633
+ label: "Tmux team discussion",
634
+ description: "Why: opens role panes for parallel debate. Tradeoff: terminal complexity and cleanup."
635
+ }
636
+ ]
637
+ };
638
+ }
639
+ function checkpointIntensityFromIntervention(choice) {
640
+ if (choice === "strong")
641
+ return "high";
642
+ if (choice === "advisory")
643
+ return "low";
644
+ return "balanced";
645
+ }
646
+ async function runSetup(args) {
647
+ const json = args.json === true;
648
+ const rl = createInterface({ input, output });
649
+ try {
650
+ const provider = (typeof args.provider === "string"
651
+ ? (args.provider === "claude" ? "claude" : "codex")
652
+ : await promptChoice(rl, "Which provider should LongTable configure?", buildProviderChoices()));
653
+ const choices = buildPermissionSetupChoices();
654
+ const surfaces = await promptChoice(rl, [
655
+ "Which LongTable runtime surfaces should be enabled?",
656
+ "This is a permission choice because skills, MCP, and sentinel support write provider-facing runtime files."
657
+ ].join("\n"), choices.surfaces);
658
+ const intervention = await promptChoice(rl, "How strongly may LongTable interrupt research decisions?", choices.intervention);
659
+ const tmuxMode = await promptChoice(rl, "Should LongTable recommend a tmux-based research interface?", choices.tmux);
660
+ const teamMode = await promptChoice(rl, "Should LongTable enable agent/team discussion mode?", choices.team);
661
+ const outputValue = createPersistedSetupOutput({
662
+ field: "unspecified",
663
+ careerStage: "unspecified",
664
+ experienceLevel: "advanced",
665
+ preferredCheckpointIntensity: checkpointIntensityFromIntervention(intervention),
666
+ preferredEntryMode: "explore",
667
+ panelPreference: teamMode === "off" ? "show_on_conflict" : "always_visible"
668
+ }, provider, "quickstart");
669
+ outputValue.initialState.explicitState = {
670
+ ...outputValue.initialState.explicitState,
671
+ runtimeSurfaces: surfaces,
672
+ interventionPosture: intervention,
673
+ tmuxMode,
674
+ teamMode
675
+ };
676
+ if (surfaces === "skills_mcp_sentinel") {
677
+ outputValue.initialState.inferredHypotheses.push({
678
+ hypothesis: "Researcher approved advisory Gap/Tacit Sentinel setup.",
679
+ confidence: 0.95,
680
+ evidence: ["Selected Skills + MCP + Sentinel during permission-first setup."],
681
+ status: "confirmed"
682
+ });
683
+ }
684
+ const result = await saveSetupAndRuntimeConfig(outputValue, {
685
+ setupPath: typeof args["setup-path"] === "string" ? args["setup-path"] : undefined,
686
+ runtimePath: typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined
687
+ });
688
+ const installedSkills = surfaces === "cli_only"
689
+ ? []
690
+ : provider === "codex"
691
+ ? await installCodexSkills(listRoleDefinitions(), typeof args["skills-dir"] === "string" ? args["skills-dir"] : typeof args.dir === "string" ? args.dir : undefined)
692
+ : await installClaudeSkills(listRoleDefinitions(), typeof args["skills-dir"] === "string" ? args["skills-dir"] : typeof args.dir === "string" ? args.dir : undefined);
693
+ const mcpRequested = surfaces === "skills_mcp" || surfaces === "skills_mcp_sentinel";
694
+ if (mcpRequested && !json) {
695
+ console.log("");
696
+ console.log("MCP setup is approved. To write provider config now, run:");
697
+ console.log(`- longtable mcp install --provider ${provider} --write`);
698
+ }
699
+ if (json) {
700
+ console.log(JSON.stringify({
701
+ setup: outputValue,
702
+ runtime: result,
703
+ installedSkills: installedSkills.map((skill) => skill.name),
704
+ mcpRequested,
705
+ tmuxMode,
706
+ teamMode
707
+ }, null, 2));
708
+ return;
709
+ }
710
+ console.log("");
711
+ console.log(renderSetupSummary(outputValue));
712
+ console.log("");
713
+ console.log(renderInstallSummary(result));
714
+ console.log(`Installed skills: ${installedSkills.length}`);
715
+ if (tmuxMode !== "standard") {
716
+ console.log("");
717
+ console.log("Tmux recommendation:");
718
+ console.log("- macOS: brew install tmux");
719
+ console.log("- Ubuntu/Debian: sudo apt install tmux");
720
+ console.log("- Start HUD in an existing tmux session: longtable hud --tmux");
721
+ console.log("- Start a discussion team: longtable team --tmux --prompt \"...\"");
722
+ }
723
+ }
724
+ finally {
725
+ rl.close();
726
+ }
727
+ }
571
728
  function perspectiveChoices() {
572
729
  return PERSONA_DEFINITIONS.map((persona) => ({
573
730
  id: persona.key,
@@ -671,12 +828,6 @@ function normalizePersistAnswers(raw) {
671
828
  : {}),
672
829
  ...(raw.panelPreference
673
830
  ? { panelPreference: raw.panelPreference }
674
- : {}),
675
- ...(raw.agentTeamPreference
676
- ? { agentTeamPreference: raw.agentTeamPreference }
677
- : {}),
678
- ...(raw.mcpPreference
679
- ? { mcpPreference: raw.mcpPreference }
680
831
  : {})
681
832
  }
682
833
  };
@@ -705,15 +856,6 @@ async function readPersistAnswers(args) {
705
856
  }
706
857
  throw new Error("persist-init requires either --answers-json, --stdin, or the full set of setup flags.");
707
858
  }
708
- async function applySetupMcpPreference(provider, preference) {
709
- if (!preference || preference === "skip") {
710
- return undefined;
711
- }
712
- return installMcpConfig({
713
- provider: preference === "write_all" ? "all" : provider,
714
- write: preference === "write_provider" || preference === "write_all"
715
- });
716
- }
717
859
  async function runInit(args) {
718
860
  const json = args.json === true;
719
861
  const installRuntime = args["no-install"] !== true;
@@ -746,17 +888,15 @@ async function runInit(args) {
746
888
  if (provider === "claude" && installSkills) {
747
889
  installedSkills = await installClaudeSkills(listRoleDefinitions(), skillsDir);
748
890
  }
749
- const mcpInstall = await applySetupMcpPreference(provider, answers.mcpPreference);
750
891
  if (json) {
751
- if (installedPrompts.length === 0 && installedSkills.length === 0 && !mcpInstall) {
892
+ if (installedPrompts.length === 0 && installedSkills.length === 0) {
752
893
  console.log(serializeSetupOutput(outputValue));
753
894
  return;
754
895
  }
755
896
  console.log(JSON.stringify({
756
897
  setup: outputValue,
757
898
  installedPrompts: installedPrompts.map((prompt) => prompt.name),
758
- installedSkills: installedSkills.map((skill) => skill.name),
759
- mcpInstall
899
+ installedSkills: installedSkills.map((skill) => skill.name)
760
900
  }, null, 2));
761
901
  return;
762
902
  }
@@ -781,10 +921,6 @@ async function runInit(args) {
781
921
  }
782
922
  console.log(" Use these by naming LongTable naturally, e.g. `lt panel: ...`.");
783
923
  }
784
- if (mcpInstall) {
785
- console.log("");
786
- console.log(renderMcpInstallSummary(mcpInstall));
787
- }
788
924
  if (provider === "codex") {
789
925
  console.log("");
790
926
  console.log("Next step:");
@@ -918,43 +1054,40 @@ function renderMcpInstallSummary(result) {
918
1054
  }
919
1055
  return lines.join("\n").trimEnd();
920
1056
  }
921
- async function installMcpConfig(args) {
922
- const serverName = typeof args.name === "string" && args.name.trim()
923
- ? args.name.trim()
924
- : LONGTABLE_MCP_SERVER_NAME;
925
- const packageSpec = resolveMcpPackageSpec(args);
926
- const command = typeof args.command === "string" && args.command.trim() ? args.command.trim() : "npx";
927
- const mcpArgs = command === "npx" ? ["-y", packageSpec] : [packageSpec];
928
- const providers = resolveMcpProviders(args.provider);
929
- const write = args.write === true;
930
- const targets = [];
931
- for (const provider of providers) {
932
- if (provider === "codex") {
933
- const path = resolveCodexMcpConfigPath(args);
934
- const block = renderCodexMcpBlock(serverName, command, mcpArgs);
935
- const content = write ? await writeCodexMcpConfig(path, block, serverName) : block;
936
- targets.push({ provider, path, format: "toml", content });
937
- }
938
- if (provider === "claude") {
939
- const path = resolveClaudeMcpSettingsPath(args);
940
- const content = write
941
- ? await writeClaudeMcpSettings(path, serverName, command, mcpArgs)
942
- : renderClaudeMcpJson(serverName, command, mcpArgs);
943
- targets.push({ provider, path, format: "json", content });
944
- }
945
- }
946
- return {
947
- serverName,
948
- packageSpec,
949
- command,
950
- args: mcpArgs,
951
- write,
952
- targets
953
- };
954
- }
955
1057
  async function runMcpSubcommand(subcommand, args) {
956
1058
  if (!subcommand || subcommand === "install" || subcommand === "print-config") {
957
- const result = await installMcpConfig(args);
1059
+ const serverName = typeof args.name === "string" && args.name.trim()
1060
+ ? args.name.trim()
1061
+ : LONGTABLE_MCP_SERVER_NAME;
1062
+ const packageSpec = resolveMcpPackageSpec(args);
1063
+ const command = typeof args.command === "string" && args.command.trim() ? args.command.trim() : "npx";
1064
+ const mcpArgs = command === "npx" ? ["-y", packageSpec] : [packageSpec];
1065
+ const providers = resolveMcpProviders(args.provider);
1066
+ const write = args.write === true;
1067
+ const targets = [];
1068
+ for (const provider of providers) {
1069
+ if (provider === "codex") {
1070
+ const path = resolveCodexMcpConfigPath(args);
1071
+ const block = renderCodexMcpBlock(serverName, command, mcpArgs);
1072
+ const content = write ? await writeCodexMcpConfig(path, block, serverName) : block;
1073
+ targets.push({ provider, path, format: "toml", content });
1074
+ }
1075
+ if (provider === "claude") {
1076
+ const path = resolveClaudeMcpSettingsPath(args);
1077
+ const content = write
1078
+ ? await writeClaudeMcpSettings(path, serverName, command, mcpArgs)
1079
+ : renderClaudeMcpJson(serverName, command, mcpArgs);
1080
+ targets.push({ provider, path, format: "json", content });
1081
+ }
1082
+ }
1083
+ const result = {
1084
+ serverName,
1085
+ packageSpec,
1086
+ command,
1087
+ args: mcpArgs,
1088
+ write,
1089
+ targets
1090
+ };
958
1091
  if (args.json === true) {
959
1092
  console.log(JSON.stringify(result, null, 2));
960
1093
  return;
@@ -1487,8 +1620,7 @@ async function runPanelCommand(args) {
1487
1620
  mode,
1488
1621
  roleFlag: typeof args.role === "string" ? args.role : undefined,
1489
1622
  provider,
1490
- visibility,
1491
- agentTeamPreference: setup?.profileSeed.agentTeamPreference
1623
+ visibility
1492
1624
  });
1493
1625
  if (projectAware.projectContextFound) {
1494
1626
  const context = await loadProjectContextFromDirectory(workingDirectory);
@@ -1809,6 +1941,202 @@ async function runAsk(args) {
1809
1941
  }
1810
1942
  await runModeCommand(mode, delegatedArgs);
1811
1943
  }
1944
+ function localId(prefix) {
1945
+ return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
1946
+ }
1947
+ function shellEscape(value) {
1948
+ return `'${value.replaceAll("'", "'\\''")}'`;
1949
+ }
1950
+ function sentinelSummary(prompt, workingDirectory) {
1951
+ const trigger = classifyCheckpointTrigger(prompt, {
1952
+ fallbackMode: "explore",
1953
+ unresolvedTensions: []
1954
+ });
1955
+ const normalized = prompt.toLowerCase();
1956
+ const signals = [];
1957
+ if (/measure|measurement|scale|validity|reliability|측정|척도|타당도|신뢰도/.test(normalized)) {
1958
+ signals.push("measurement gap or commitment");
1959
+ }
1960
+ if (/theory|theoretical|framework|construct|이론|프레임워크|개념/.test(normalized)) {
1961
+ signals.push("theory or construct commitment");
1962
+ }
1963
+ if (/method|design|sample|participant|방법|설계|표본|참여자/.test(normalized)) {
1964
+ signals.push("method/design gap");
1965
+ }
1966
+ if (/citation|reference|source|evidence|doi|문헌|인용|근거|출처/.test(normalized)) {
1967
+ signals.push("evidence gap");
1968
+ }
1969
+ if (/voice|authorship|narrative|저자성|서사|문체|목소리/.test(normalized)) {
1970
+ signals.push("authorship or narrative-trace risk");
1971
+ }
1972
+ if (/assumption|implicit|tacit|암묵|전제|가정/.test(normalized)) {
1973
+ signals.push("tacit assumption risk");
1974
+ }
1975
+ return {
1976
+ cwd: workingDirectory,
1977
+ checkpoint: trigger.signal.checkpointKey,
1978
+ family: trigger.family,
1979
+ confidence: trigger.confidence,
1980
+ requiresQuestionBeforeClosure: trigger.requiresQuestionBeforeClosure,
1981
+ signals: signals.length > 0 ? signals : ["no specific gap/tacit signal beyond checkpoint classifier"],
1982
+ rationale: trigger.rationale
1983
+ };
1984
+ }
1985
+ async function runSentinel(args) {
1986
+ const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
1987
+ const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
1988
+ if (!prompt) {
1989
+ throw new Error("A prompt is required.");
1990
+ }
1991
+ const summary = sentinelSummary(prompt, workingDirectory);
1992
+ const context = await loadProjectContextFromDirectory(workingDirectory);
1993
+ if (args.record === true && context) {
1994
+ const state = await loadWorkspaceState(context);
1995
+ state.inferredHypotheses.push({
1996
+ hypothesis: `Sentinel detected: ${summary.signals.join(", ")}.`,
1997
+ confidence: summary.confidence === "high" ? 0.85 : summary.confidence === "medium" ? 0.65 : 0.4,
1998
+ evidence: [`Prompt: ${prompt}`],
1999
+ status: "unconfirmed"
2000
+ });
2001
+ if (summary.requiresQuestionBeforeClosure) {
2002
+ state.openTensions.push(`Pending sentinel risk: ${summary.checkpoint}`);
2003
+ }
2004
+ await writeFile(context.stateFilePath, JSON.stringify(state, null, 2), "utf8");
2005
+ await syncCurrentWorkspaceView(context);
2006
+ }
2007
+ if (args.json === true) {
2008
+ console.log(JSON.stringify(summary, null, 2));
2009
+ return;
2010
+ }
2011
+ console.log("LongTable Sentinel");
2012
+ console.log(`- checkpoint: ${summary.checkpoint}`);
2013
+ console.log(`- family: ${summary.family}`);
2014
+ console.log(`- confidence: ${summary.confidence}`);
2015
+ console.log(`- question before closure: ${summary.requiresQuestionBeforeClosure ? "yes" : "no"}`);
2016
+ console.log("- detected signals:");
2017
+ for (const signal of summary.signals) {
2018
+ console.log(` - ${signal}`);
2019
+ }
2020
+ if (args.record === true) {
2021
+ console.log(context ? `- recorded in: ${context.stateFilePath}` : "- record skipped: no LongTable workspace found");
2022
+ }
2023
+ }
2024
+ function renderHudText(inspection, preset) {
2025
+ if (!inspection.found) {
2026
+ return [
2027
+ "LongTable HUD",
2028
+ "- workspace: not found",
2029
+ "- run `longtable start` for durable research state"
2030
+ ].join("\n");
2031
+ }
2032
+ const lines = [
2033
+ "LongTable HUD",
2034
+ `- project: ${inspection.project?.name}`,
2035
+ `- goal: ${inspection.session?.currentGoal}`,
2036
+ ...(inspection.session?.currentBlocker ? [`- blocker: ${inspection.session.currentBlocker}`] : []),
2037
+ `- questions: ${inspection.counts?.pendingQuestions ?? 0} pending / ${inspection.counts?.questions ?? 0} total`,
2038
+ `- decisions: ${inspection.counts?.decisions ?? 0}`,
2039
+ `- invocations: ${inspection.counts?.invocations ?? 0}`
2040
+ ];
2041
+ if (preset !== "minimal") {
2042
+ lines.push("- pending checkpoints:");
2043
+ for (const question of inspection.pendingQuestions ?? []) {
2044
+ lines.push(` - ${question.required ? "required" : "advisory"}: ${question.question}`);
2045
+ }
2046
+ lines.push("- recent decisions:");
2047
+ for (const decision of inspection.recentDecisions ?? []) {
2048
+ lines.push(` - ${decision.summary}`);
2049
+ }
2050
+ }
2051
+ return lines.join("\n");
2052
+ }
2053
+ async function runHud(args) {
2054
+ const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
2055
+ const preset = typeof args.preset === "string" ? args.preset : "full";
2056
+ if (args.tmux === true) {
2057
+ if (!process.env.TMUX) {
2058
+ throw new Error("`longtable hud --tmux` must be run inside an existing tmux session.");
2059
+ }
2060
+ const launcher = process.argv[1] ?? "longtable";
2061
+ const command = `node ${shellEscape(launcher)} hud --watch --preset ${shellEscape(preset)} --cwd ${shellEscape(workingDirectory)}`;
2062
+ execFileSync("tmux", ["split-window", "-v", "-l", "10", command], { stdio: "inherit" });
2063
+ return;
2064
+ }
2065
+ while (true) {
2066
+ const inspection = await inspectProjectWorkspace(workingDirectory);
2067
+ if (args.json === true) {
2068
+ console.log(JSON.stringify(inspection, null, 2));
2069
+ return;
2070
+ }
2071
+ if (args.watch === true) {
2072
+ process.stdout.write("\u001Bc");
2073
+ }
2074
+ console.log(renderHudText(inspection, preset));
2075
+ if (args.watch !== true) {
2076
+ return;
2077
+ }
2078
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, 1500));
2079
+ }
2080
+ }
2081
+ async function runTeam(args) {
2082
+ const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
2083
+ const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
2084
+ if (!prompt) {
2085
+ throw new Error("A prompt is required.");
2086
+ }
2087
+ const fallback = buildPanelFallback({
2088
+ prompt,
2089
+ mode: "review",
2090
+ roleFlag: typeof args.role === "string" ? args.role : undefined,
2091
+ provider: "codex",
2092
+ visibility: "always_visible"
2093
+ });
2094
+ const teamId = localId("team");
2095
+ const teamDir = join(workingDirectory, ".longtable", "team", teamId);
2096
+ await mkdir(teamDir, { recursive: true });
2097
+ await writeFile(join(teamDir, "prompt.txt"), prompt, "utf8");
2098
+ await writeFile(join(teamDir, "plan.json"), JSON.stringify(fallback.plan, null, 2), "utf8");
2099
+ if (args.json === true) {
2100
+ console.log(JSON.stringify({ teamId, teamDir, plan: fallback.plan }, null, 2));
2101
+ return;
2102
+ }
2103
+ if (args.tmux !== true) {
2104
+ console.log(renderPanelSummary(fallback.plan));
2105
+ console.log("");
2106
+ console.log("Run with `--tmux` to launch role panes for parallel discussion.");
2107
+ return;
2108
+ }
2109
+ const sessionName = `longtable-${teamId.replaceAll("_", "-")}`;
2110
+ const shell = process.env.SHELL || "/bin/sh";
2111
+ const launcher = process.argv[1] ?? "longtable";
2112
+ const leaderCommand = [
2113
+ `echo ${shellEscape(`LongTable team ${teamId}`)}`,
2114
+ `echo ${shellEscape(`Logs: ${teamDir}`)}`,
2115
+ "echo 'Role panes are running. Review logs, then run:'",
2116
+ `echo ${shellEscape(`longtable panel --role ${fallback.plan.members.map((member) => member.role).join(",")} --prompt ${JSON.stringify(prompt)}`)}`,
2117
+ `exec ${shellEscape(shell)}`
2118
+ ].join("; ");
2119
+ execFileSync("tmux", ["new-session", "-d", "-s", sessionName, "-c", workingDirectory, leaderCommand], { stdio: "inherit" });
2120
+ for (const member of fallback.plan.members) {
2121
+ const rolePrompt = [
2122
+ `LongTable team discussion role: ${member.label} (${member.role}).`,
2123
+ "Give claims, objections, open questions, and evidence needs. Address likely disagreement with other roles.",
2124
+ "",
2125
+ prompt
2126
+ ].join("\n");
2127
+ const logPath = join(teamDir, `${member.role}.log`);
2128
+ const command = [
2129
+ `node ${shellEscape(launcher)} review --role ${shellEscape(member.role)} --prompt ${shellEscape(rolePrompt)} --cwd ${shellEscape(workingDirectory)} 2>&1 | tee ${shellEscape(logPath)}`,
2130
+ `echo ${shellEscape(`Role log written to ${logPath}`)}`,
2131
+ `exec ${shellEscape(shell)}`
2132
+ ].join("; ");
2133
+ execFileSync("tmux", ["split-window", "-t", sessionName, "-c", workingDirectory, command], { stdio: "inherit" });
2134
+ execFileSync("tmux", ["select-layout", "-t", sessionName, "tiled"], { stdio: "ignore" });
2135
+ }
2136
+ console.log(`LongTable tmux team launched: ${sessionName}`);
2137
+ console.log(`Attach with: tmux attach -t ${sessionName}`);
2138
+ console.log(`Logs: ${teamDir}`);
2139
+ }
1812
2140
  async function runDecide(args) {
1813
2141
  const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
1814
2142
  const answer = typeof args.answer === "string" ? args.answer.trim() : "";
@@ -2095,6 +2423,10 @@ async function main() {
2095
2423
  await runInit(values);
2096
2424
  return;
2097
2425
  }
2426
+ if (command === "setup") {
2427
+ await runSetup(values);
2428
+ return;
2429
+ }
2098
2430
  if (command === "start") {
2099
2431
  await runStart(values);
2100
2432
  return;
@@ -2139,6 +2471,18 @@ async function main() {
2139
2471
  await runPanelCommand(values);
2140
2472
  return;
2141
2473
  }
2474
+ if (command === "hud") {
2475
+ await runHud(values);
2476
+ return;
2477
+ }
2478
+ if (command === "sentinel") {
2479
+ await runSentinel(values);
2480
+ return;
2481
+ }
2482
+ if (command === "team") {
2483
+ await runTeam(values);
2484
+ return;
2485
+ }
2142
2486
  if (command === "decide") {
2143
2487
  await runDecide(values);
2144
2488
  return;
package/dist/panel.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CheckpointSensitivity, InteractionMode, InvocationIntent, InvocationSurface, InvocationRecord, PanelPlan, PanelResult, PanelVisibility, QuestionRecord, ProviderKind, RoleKey } from "@longtable/core";
1
+ import type { CheckpointSensitivity, InteractionMode, InvocationIntent, InvocationRecord, PanelPlan, PanelResult, PanelVisibility, QuestionRecord, ProviderKind, RoleKey } from "@longtable/core";
2
2
  import { type CanonicalPersona } from "./personas.js";
3
3
  export interface BuildPanelPlanOptions {
4
4
  prompt: string;
@@ -7,7 +7,6 @@ export interface BuildPanelPlanOptions {
7
7
  roles?: CanonicalPersona[];
8
8
  provider?: ProviderKind;
9
9
  visibility?: PanelVisibility;
10
- agentTeamPreference?: "native_when_available" | "sequential_panel_only";
11
10
  }
12
11
  export interface PanelFallback {
13
12
  intent: InvocationIntent;
@@ -23,7 +22,6 @@ export declare function buildInvocationIntent(options: {
23
22
  mode?: InteractionMode;
24
23
  roles: RoleKey[];
25
24
  provider?: ProviderKind;
26
- requestedSurface?: InvocationSurface;
27
25
  visibility?: PanelVisibility;
28
26
  checkpointSensitivity?: CheckpointSensitivity;
29
27
  rationale?: string[];
package/dist/panel.js CHANGED
@@ -67,9 +67,6 @@ export function buildPanelPlan(options) {
67
67
  const routedRoles = routePersonas(options.prompt).consultedRoles;
68
68
  const roles = resolvePanelRoles(options);
69
69
  const createdAt = nowIso();
70
- const preferredSurface = options.agentTeamPreference === "native_when_available"
71
- ? "native_parallel"
72
- : "sequential_fallback";
73
70
  return {
74
71
  id: createId("panel_plan"),
75
72
  createdAt,
@@ -77,14 +74,12 @@ export function buildPanelPlan(options) {
77
74
  prompt: options.prompt,
78
75
  members: roles.map((role) => memberForRole(role, explicitRoles, routedRoles)),
79
76
  visibility: options.visibility ?? "always_visible",
80
- preferredSurface,
77
+ preferredSurface: "sequential_fallback",
81
78
  fallbackSurface: "sequential_fallback",
82
79
  checkpointSensitivity: highestSensitivity(roles),
83
80
  rationale: [
84
- preferredSurface === "native_parallel"
85
- ? "Setup prefers native provider team/subagent surfaces when available."
86
- : "Setup prefers LongTable's provider-neutral sequential panel surface.",
87
- "Sequential fallback remains the stable execution path for both Claude Code and Codex.",
81
+ "Option A uses provider-neutral panel semantics before native provider orchestration.",
82
+ "Sequential fallback is the stable execution path for both Claude Code and Codex.",
88
83
  roles.length === explicitRoles.length && explicitRoles.length > 0
89
84
  ? "The panel is constrained by explicitly requested roles."
90
85
  : "The panel combines default research-review roles with prompt-triggered roles."
@@ -99,7 +94,7 @@ export function buildInvocationIntent(options) {
99
94
  prompt: options.prompt,
100
95
  roles: options.roles,
101
96
  provider: options.provider,
102
- requestedSurface: options.requestedSurface ?? "sequential_fallback",
97
+ requestedSurface: "sequential_fallback",
103
98
  visibility: options.visibility ?? "always_visible",
104
99
  checkpointSensitivity: options.checkpointSensitivity ?? "medium",
105
100
  rationale: options.rationale ?? ["Panel invocation requested."]
@@ -235,7 +230,6 @@ export function buildPanelFallback(options) {
235
230
  mode: plan.mode,
236
231
  roles: plan.members.map((member) => member.role),
237
232
  provider: options.provider,
238
- requestedSurface: plan.preferredSurface,
239
233
  visibility: plan.visibility,
240
234
  checkpointSensitivity: plan.checkpointSensitivity,
241
235
  rationale: plan.rationale
@@ -17,8 +17,6 @@ export interface LongTableProjectRecord {
17
17
  humanAuthorshipSignal?: string;
18
18
  weakestDomain?: string;
19
19
  defaultPanelPreference?: ProjectDisagreementPreference;
20
- agentTeamPreference?: string;
21
- mcpPreference?: string;
22
20
  };
23
21
  }
24
22
  export interface LongTableSessionRecord {
@@ -917,12 +917,6 @@ export async function createOrUpdateProjectWorkspace(options) {
917
917
  : {}),
918
918
  ...(options.setup.profileSeed.panelPreference
919
919
  ? { defaultPanelPreference: options.setup.profileSeed.panelPreference }
920
- : {}),
921
- ...(options.setup.profileSeed.agentTeamPreference
922
- ? { agentTeamPreference: options.setup.profileSeed.agentTeamPreference }
923
- : {}),
924
- ...(options.setup.profileSeed.mcpPreference
925
- ? { mcpPreference: options.setup.profileSeed.mcpPreference }
926
920
  : {})
927
921
  }
928
922
  };
@@ -35,7 +35,7 @@ function promptSpec() {
35
35
  "Do not move to the next question until the researcher answers the current one.",
36
36
  "Quickstart covers: provider, field, career stage, experience level, checkpoint intensity, and human authorship signal.",
37
37
  "Interview also covers: preferred entry mode, weakest domain, and panel visibility preference.",
38
- "After collecting all answers, summarize the proposed setup and then output both: 1) the exact `longtable codex persist-init ... --install-skills` command and 2) a strict JSON object with keys provider, flow, field, careerStage, experienceLevel, preferredCheckpointIntensity, and optional humanAuthorshipSignal, preferredEntryMode, weakestDomain, panelPreference, agentTeamPreference, mcpPreference.",
38
+ "After collecting all answers, summarize the proposed setup and then output both: 1) the exact `longtable codex persist-init ... --install-skills` command and 2) a strict JSON object with keys provider, flow, field, careerStage, experienceLevel, preferredCheckpointIntensity, and optional humanAuthorshipSignal, preferredEntryMode, weakestDomain, panelPreference.",
39
39
  "If the user prefers paste-based setup, tell them they can pipe the JSON into `longtable codex persist-init --stdin --install-skills`.",
40
40
  "If the researcher asks you to stay inside Codex, keep the conversation in numbered form and do not prematurely close.",
41
41
  "Frame the setup like a short researcher interview, not a bare config form.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longtable/cli",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "private": false,
5
5
  "description": "Researcher-facing LongTable CLI",
6
6
  "type": "module",
@@ -13,7 +13,7 @@
13
13
  }
14
14
  },
15
15
  "bin": {
16
- "longtable": "./bin/longtable"
16
+ "longtable": "bin/longtable"
17
17
  },
18
18
  "directories": {
19
19
  "bin": "./bin"
@@ -21,21 +21,19 @@
21
21
  "files": [
22
22
  "dist",
23
23
  "bin",
24
- "scripts/postinstall.mjs",
25
24
  "README.md"
26
25
  ],
27
26
  "scripts": {
28
- "postinstall": "node ./scripts/postinstall.mjs",
29
27
  "build": "tsc -p tsconfig.json",
30
28
  "typecheck": "tsc -p tsconfig.json --noEmit"
31
29
  },
32
30
  "dependencies": {
33
- "@longtable/checkpoints": "0.1.15",
34
- "@longtable/core": "0.1.15",
35
- "@longtable/memory": "0.1.15",
36
- "@longtable/provider-claude": "0.1.15",
37
- "@longtable/provider-codex": "0.1.15",
38
- "@longtable/setup": "0.1.15"
31
+ "@longtable/checkpoints": "0.1.16",
32
+ "@longtable/core": "0.1.16",
33
+ "@longtable/memory": "0.1.16",
34
+ "@longtable/provider-claude": "0.1.16",
35
+ "@longtable/provider-codex": "0.1.16",
36
+ "@longtable/setup": "0.1.16"
39
37
  },
40
38
  "devDependencies": {
41
39
  "@types/node": "^22.10.1",
@@ -1,85 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { execSync } from "node:child_process";
4
- import { existsSync } from "node:fs";
5
- import { fileURLToPath } from "node:url";
6
-
7
- function envFlag(name) {
8
- const value = process.env[name];
9
- if (!value) return false;
10
- return !["0", "false", "no", "off"].includes(value.toLowerCase());
11
- }
12
-
13
- function isGlobalInstall() {
14
- return process.env.npm_config_global === "true" || process.env.npm_config_location === "global";
15
- }
16
-
17
- function commandOnPath(command) {
18
- try {
19
- execSync(`command -v ${command}`, { stdio: "ignore" });
20
- return true;
21
- } catch {
22
- return false;
23
- }
24
- }
25
-
26
- function requestedProviders() {
27
- const raw = process.env.LONGTABLE_POSTINSTALL_PROVIDERS?.trim().toLowerCase();
28
- if (!raw || raw === "auto") {
29
- const providers = ["codex"];
30
- if (commandOnPath("claude")) providers.push("claude");
31
- return providers;
32
- }
33
- if (raw === "all") return ["codex", "claude"];
34
- return raw
35
- .split(",")
36
- .map((entry) => entry.trim())
37
- .filter((entry) => entry === "codex" || entry === "claude");
38
- }
39
-
40
- async function main() {
41
- if (!isGlobalInstall() || envFlag("LONGTABLE_SKIP_POSTINSTALL")) {
42
- return;
43
- }
44
-
45
- const distEntrypoint = fileURLToPath(new URL("../dist/personas.js", import.meta.url));
46
- if (!existsSync(distEntrypoint)) {
47
- console.warn("[longtable] Skipped provider skill bootstrap because package build output was not found.");
48
- return;
49
- }
50
-
51
- const providers = requestedProviders();
52
- if (providers.length === 0) {
53
- return;
54
- }
55
-
56
- const { listRoleDefinitions } = await import("../dist/personas.js");
57
- const roles = listRoleDefinitions();
58
- const installed = [];
59
-
60
- if (providers.includes("codex")) {
61
- const { installCodexSkills, resolveCodexSkillsDir } = await import("@longtable/provider-codex");
62
- const skills = await installCodexSkills(roles);
63
- installed.push(`Codex skills: ${skills.length} (${resolveCodexSkillsDir()})`);
64
- }
65
-
66
- if (providers.includes("claude")) {
67
- const { installClaudeSkills, resolveClaudeSkillsDir } = await import("@longtable/provider-claude");
68
- const skills = await installClaudeSkills(roles);
69
- installed.push(`Claude skills: ${skills.length} (${resolveClaudeSkillsDir()})`);
70
- }
71
-
72
- if (installed.length > 0) {
73
- console.log("[longtable] Provider skills installed during global npm install:");
74
- for (const line of installed) {
75
- console.log(`[longtable] - ${line}`);
76
- }
77
- console.log("[longtable] Run `longtable doctor` to verify setup.");
78
- console.log("[longtable] MCP config is opt-in: run `longtable mcp install --provider codex --write` when you want provider config files updated.");
79
- }
80
- }
81
-
82
- main().catch((error) => {
83
- console.warn(`[longtable] Postinstall provider skill bootstrap failed: ${error instanceof Error ? error.message : String(error)}`);
84
- console.warn("[longtable] You can repair this later with `longtable doctor --fix`.");
85
- });