@longtable/cli 0.1.14 → 0.1.15

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,6 +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.
40
+
22
41
  ## Primary Flow
23
42
 
24
43
  ```bash
package/dist/cli.js CHANGED
@@ -40,7 +40,7 @@ const ANSI = {
40
40
  green: "\u001B[32m"
41
41
  };
42
42
  const LONGTABLE_MCP_SERVER_NAME = "longtable-state";
43
- const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.14";
43
+ const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.15";
44
44
  const LONGTABLE_MCP_MARKER_START = "# LongTable state MCP START";
45
45
  const LONGTABLE_MCP_MARKER_END = "# LongTable state MCP END";
46
46
  function style(text, prefix) {
@@ -76,7 +76,7 @@ function usage() {
76
76
  " Run `longtable ...` in your terminal, not inside the Codex chat box.",
77
77
  " After `longtable start`, move into the created project directory and open `codex` there.",
78
78
  "",
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]",
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
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]",
81
81
  " longtable resume [--cwd <path>] [--json]",
82
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>]",
@@ -197,9 +197,12 @@ function questionSection(questionId) {
197
197
  if (questionId === "preferredCheckpointIntensity" || questionId === "preferredEntryMode") {
198
198
  return "Interaction style";
199
199
  }
200
- if (questionId === "weakestDomain" || questionId === "panelPreference") {
200
+ if (questionId === "weakestDomain" || questionId === "panelPreference" || questionId === "agentTeamPreference") {
201
201
  return "How LongTable should challenge you";
202
202
  }
203
+ if (questionId === "mcpPreference") {
204
+ return "Provider integration";
205
+ }
203
206
  return "Authorship and voice";
204
207
  }
205
208
  function formatModeLabel(mode) {
@@ -496,6 +499,12 @@ function toSetupAnswers(args) {
496
499
  : undefined,
497
500
  panelPreference: typeof args["panel-preference"] === "string"
498
501
  ? 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)
499
508
  : undefined
500
509
  };
501
510
  }
@@ -543,6 +552,11 @@ async function collectInteractiveAnswers(initialFlow) {
543
552
  answers.weakestDomain = value;
544
553
  if (question.id === "panelPreference")
545
554
  answers.panelPreference = value;
555
+ if (question.id === "agentTeamPreference") {
556
+ answers.agentTeamPreference = value;
557
+ }
558
+ if (question.id === "mcpPreference")
559
+ answers.mcpPreference = value;
546
560
  }
547
561
  return {
548
562
  flow,
@@ -657,6 +671,12 @@ function normalizePersistAnswers(raw) {
657
671
  : {}),
658
672
  ...(raw.panelPreference
659
673
  ? { panelPreference: raw.panelPreference }
674
+ : {}),
675
+ ...(raw.agentTeamPreference
676
+ ? { agentTeamPreference: raw.agentTeamPreference }
677
+ : {}),
678
+ ...(raw.mcpPreference
679
+ ? { mcpPreference: raw.mcpPreference }
660
680
  : {})
661
681
  }
662
682
  };
@@ -685,6 +705,15 @@ async function readPersistAnswers(args) {
685
705
  }
686
706
  throw new Error("persist-init requires either --answers-json, --stdin, or the full set of setup flags.");
687
707
  }
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
+ }
688
717
  async function runInit(args) {
689
718
  const json = args.json === true;
690
719
  const installRuntime = args["no-install"] !== true;
@@ -717,15 +746,17 @@ async function runInit(args) {
717
746
  if (provider === "claude" && installSkills) {
718
747
  installedSkills = await installClaudeSkills(listRoleDefinitions(), skillsDir);
719
748
  }
749
+ const mcpInstall = await applySetupMcpPreference(provider, answers.mcpPreference);
720
750
  if (json) {
721
- if (installedPrompts.length === 0 && installedSkills.length === 0) {
751
+ if (installedPrompts.length === 0 && installedSkills.length === 0 && !mcpInstall) {
722
752
  console.log(serializeSetupOutput(outputValue));
723
753
  return;
724
754
  }
725
755
  console.log(JSON.stringify({
726
756
  setup: outputValue,
727
757
  installedPrompts: installedPrompts.map((prompt) => prompt.name),
728
- installedSkills: installedSkills.map((skill) => skill.name)
758
+ installedSkills: installedSkills.map((skill) => skill.name),
759
+ mcpInstall
729
760
  }, null, 2));
730
761
  return;
731
762
  }
@@ -750,6 +781,10 @@ async function runInit(args) {
750
781
  }
751
782
  console.log(" Use these by naming LongTable naturally, e.g. `lt panel: ...`.");
752
783
  }
784
+ if (mcpInstall) {
785
+ console.log("");
786
+ console.log(renderMcpInstallSummary(mcpInstall));
787
+ }
753
788
  if (provider === "codex") {
754
789
  console.log("");
755
790
  console.log("Next step:");
@@ -883,40 +918,43 @@ function renderMcpInstallSummary(result) {
883
918
  }
884
919
  return lines.join("\n").trimEnd();
885
920
  }
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
+ }
886
955
  async function runMcpSubcommand(subcommand, args) {
887
956
  if (!subcommand || subcommand === "install" || subcommand === "print-config") {
888
- const serverName = typeof args.name === "string" && args.name.trim()
889
- ? args.name.trim()
890
- : LONGTABLE_MCP_SERVER_NAME;
891
- const packageSpec = resolveMcpPackageSpec(args);
892
- const command = typeof args.command === "string" && args.command.trim() ? args.command.trim() : "npx";
893
- const mcpArgs = command === "npx" ? ["-y", packageSpec] : [packageSpec];
894
- const providers = resolveMcpProviders(args.provider);
895
- const write = args.write === true;
896
- const targets = [];
897
- for (const provider of providers) {
898
- if (provider === "codex") {
899
- const path = resolveCodexMcpConfigPath(args);
900
- const block = renderCodexMcpBlock(serverName, command, mcpArgs);
901
- const content = write ? await writeCodexMcpConfig(path, block, serverName) : block;
902
- targets.push({ provider, path, format: "toml", content });
903
- }
904
- if (provider === "claude") {
905
- const path = resolveClaudeMcpSettingsPath(args);
906
- const content = write
907
- ? await writeClaudeMcpSettings(path, serverName, command, mcpArgs)
908
- : renderClaudeMcpJson(serverName, command, mcpArgs);
909
- targets.push({ provider, path, format: "json", content });
910
- }
911
- }
912
- const result = {
913
- serverName,
914
- packageSpec,
915
- command,
916
- args: mcpArgs,
917
- write,
918
- targets
919
- };
957
+ const result = await installMcpConfig(args);
920
958
  if (args.json === true) {
921
959
  console.log(JSON.stringify(result, null, 2));
922
960
  return;
@@ -1449,7 +1487,8 @@ async function runPanelCommand(args) {
1449
1487
  mode,
1450
1488
  roleFlag: typeof args.role === "string" ? args.role : undefined,
1451
1489
  provider,
1452
- visibility
1490
+ visibility,
1491
+ agentTeamPreference: setup?.profileSeed.agentTeamPreference
1453
1492
  });
1454
1493
  if (projectAware.projectContextFound) {
1455
1494
  const context = await loadProjectContextFromDirectory(workingDirectory);
package/dist/panel.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CheckpointSensitivity, InteractionMode, InvocationIntent, InvocationRecord, PanelPlan, PanelResult, PanelVisibility, QuestionRecord, ProviderKind, RoleKey } from "@longtable/core";
1
+ import type { CheckpointSensitivity, InteractionMode, InvocationIntent, InvocationSurface, 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,6 +7,7 @@ export interface BuildPanelPlanOptions {
7
7
  roles?: CanonicalPersona[];
8
8
  provider?: ProviderKind;
9
9
  visibility?: PanelVisibility;
10
+ agentTeamPreference?: "native_when_available" | "sequential_panel_only";
10
11
  }
11
12
  export interface PanelFallback {
12
13
  intent: InvocationIntent;
@@ -22,6 +23,7 @@ export declare function buildInvocationIntent(options: {
22
23
  mode?: InteractionMode;
23
24
  roles: RoleKey[];
24
25
  provider?: ProviderKind;
26
+ requestedSurface?: InvocationSurface;
25
27
  visibility?: PanelVisibility;
26
28
  checkpointSensitivity?: CheckpointSensitivity;
27
29
  rationale?: string[];
package/dist/panel.js CHANGED
@@ -67,6 +67,9 @@ 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";
70
73
  return {
71
74
  id: createId("panel_plan"),
72
75
  createdAt,
@@ -74,12 +77,14 @@ export function buildPanelPlan(options) {
74
77
  prompt: options.prompt,
75
78
  members: roles.map((role) => memberForRole(role, explicitRoles, routedRoles)),
76
79
  visibility: options.visibility ?? "always_visible",
77
- preferredSurface: "sequential_fallback",
80
+ preferredSurface,
78
81
  fallbackSurface: "sequential_fallback",
79
82
  checkpointSensitivity: highestSensitivity(roles),
80
83
  rationale: [
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.",
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.",
83
88
  roles.length === explicitRoles.length && explicitRoles.length > 0
84
89
  ? "The panel is constrained by explicitly requested roles."
85
90
  : "The panel combines default research-review roles with prompt-triggered roles."
@@ -94,7 +99,7 @@ export function buildInvocationIntent(options) {
94
99
  prompt: options.prompt,
95
100
  roles: options.roles,
96
101
  provider: options.provider,
97
- requestedSurface: "sequential_fallback",
102
+ requestedSurface: options.requestedSurface ?? "sequential_fallback",
98
103
  visibility: options.visibility ?? "always_visible",
99
104
  checkpointSensitivity: options.checkpointSensitivity ?? "medium",
100
105
  rationale: options.rationale ?? ["Panel invocation requested."]
@@ -230,6 +235,7 @@ export function buildPanelFallback(options) {
230
235
  mode: plan.mode,
231
236
  roles: plan.members.map((member) => member.role),
232
237
  provider: options.provider,
238
+ requestedSurface: plan.preferredSurface,
233
239
  visibility: plan.visibility,
234
240
  checkpointSensitivity: plan.checkpointSensitivity,
235
241
  rationale: plan.rationale
@@ -17,6 +17,8 @@ export interface LongTableProjectRecord {
17
17
  humanAuthorshipSignal?: string;
18
18
  weakestDomain?: string;
19
19
  defaultPanelPreference?: ProjectDisagreementPreference;
20
+ agentTeamPreference?: string;
21
+ mcpPreference?: string;
20
22
  };
21
23
  }
22
24
  export interface LongTableSessionRecord {
@@ -917,6 +917,12 @@ 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 }
920
926
  : {})
921
927
  }
922
928
  };
@@ -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.",
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.",
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.14",
3
+ "version": "0.1.15",
4
4
  "private": false,
5
5
  "description": "Researcher-facing LongTable CLI",
6
6
  "type": "module",
@@ -21,19 +21,21 @@
21
21
  "files": [
22
22
  "dist",
23
23
  "bin",
24
+ "scripts/postinstall.mjs",
24
25
  "README.md"
25
26
  ],
26
27
  "scripts": {
28
+ "postinstall": "node ./scripts/postinstall.mjs",
27
29
  "build": "tsc -p tsconfig.json",
28
30
  "typecheck": "tsc -p tsconfig.json --noEmit"
29
31
  },
30
32
  "dependencies": {
31
- "@longtable/checkpoints": "0.1.14",
32
- "@longtable/core": "0.1.14",
33
- "@longtable/memory": "0.1.14",
34
- "@longtable/provider-claude": "0.1.14",
35
- "@longtable/provider-codex": "0.1.14",
36
- "@longtable/setup": "0.1.14"
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"
37
39
  },
38
40
  "devDependencies": {
39
41
  "@types/node": "^22.10.1",
@@ -0,0 +1,85 @@
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
+ });