@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 +28 -18
- package/dist/cli.js +425 -81
- package/dist/panel.d.ts +1 -3
- package/dist/panel.js +4 -10
- package/dist/project-session.d.ts +0 -2
- package/dist/project-session.js +0 -6
- package/dist/prompt-aliases.js +1 -1
- package/package.json +8 -10
- package/scripts/postinstall.mjs +0 -85
package/README.md
CHANGED
|
@@ -19,34 +19,25 @@ The basic contract is:
|
|
|
19
19
|
npm install -g @longtable/cli
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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.
|
|
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] [--
|
|
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"
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
85
|
-
|
|
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:
|
|
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 {
|
package/dist/project-session.js
CHANGED
|
@@ -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
|
};
|
package/dist/prompt-aliases.js
CHANGED
|
@@ -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.",
|
|
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.
|
|
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": "
|
|
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.
|
|
34
|
-
"@longtable/core": "0.1.
|
|
35
|
-
"@longtable/memory": "0.1.
|
|
36
|
-
"@longtable/provider-claude": "0.1.
|
|
37
|
-
"@longtable/provider-codex": "0.1.
|
|
38
|
-
"@longtable/setup": "0.1.
|
|
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",
|
package/scripts/postinstall.mjs
DELETED
|
@@ -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
|
-
});
|