@longtable/cli 0.1.14 → 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 +29 -0
- package/dist/cli.js +387 -4
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -19,15 +19,25 @@ The basic contract is:
|
|
|
19
19
|
npm install -g @longtable/cli
|
|
20
20
|
```
|
|
21
21
|
|
|
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.
|
|
25
|
+
|
|
22
26
|
## Primary Flow
|
|
23
27
|
|
|
24
28
|
```bash
|
|
29
|
+
longtable setup --provider codex
|
|
25
30
|
longtable init --flow interview
|
|
26
31
|
longtable start
|
|
27
32
|
cd "<project-path>"
|
|
28
33
|
codex
|
|
29
34
|
```
|
|
30
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
|
+
|
|
31
41
|
Return later:
|
|
32
42
|
|
|
33
43
|
```bash
|
|
@@ -81,6 +91,9 @@ longtable resume --cwd "<project-path>"
|
|
|
81
91
|
longtable roles
|
|
82
92
|
longtable ask --cwd "<project-path>" --prompt "..."
|
|
83
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."
|
|
84
97
|
longtable codex install-skills
|
|
85
98
|
longtable claude install-skills
|
|
86
99
|
```
|
|
@@ -151,6 +164,22 @@ Default panel roles include:
|
|
|
151
164
|
|
|
152
165
|
Use `--role` to constrain the panel when the research problem is already clear.
|
|
153
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
|
+
|
|
154
183
|
## Evidence And Search Direction
|
|
155
184
|
|
|
156
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",
|
|
@@ -77,6 +78,7 @@ function usage() {
|
|
|
77
78
|
" After `longtable start`, move into the created project directory and open `codex` there.",
|
|
78
79
|
"",
|
|
79
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;
|
|
@@ -554,6 +559,172 @@ async function collectInteractiveAnswers(initialFlow) {
|
|
|
554
559
|
rl.close();
|
|
555
560
|
}
|
|
556
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
|
+
}
|
|
557
728
|
function perspectiveChoices() {
|
|
558
729
|
return PERSONA_DEFINITIONS.map((persona) => ({
|
|
559
730
|
id: persona.key,
|
|
@@ -1770,6 +1941,202 @@ async function runAsk(args) {
|
|
|
1770
1941
|
}
|
|
1771
1942
|
await runModeCommand(mode, delegatedArgs);
|
|
1772
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
|
+
}
|
|
1773
2140
|
async function runDecide(args) {
|
|
1774
2141
|
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
1775
2142
|
const answer = typeof args.answer === "string" ? args.answer.trim() : "";
|
|
@@ -2056,6 +2423,10 @@ async function main() {
|
|
|
2056
2423
|
await runInit(values);
|
|
2057
2424
|
return;
|
|
2058
2425
|
}
|
|
2426
|
+
if (command === "setup") {
|
|
2427
|
+
await runSetup(values);
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
2059
2430
|
if (command === "start") {
|
|
2060
2431
|
await runStart(values);
|
|
2061
2432
|
return;
|
|
@@ -2100,6 +2471,18 @@ async function main() {
|
|
|
2100
2471
|
await runPanelCommand(values);
|
|
2101
2472
|
return;
|
|
2102
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
|
+
}
|
|
2103
2486
|
if (command === "decide") {
|
|
2104
2487
|
await runDecide(values);
|
|
2105
2488
|
return;
|
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"
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@longtable/checkpoints": "0.1.
|
|
32
|
-
"@longtable/core": "0.1.
|
|
33
|
-
"@longtable/memory": "0.1.
|
|
34
|
-
"@longtable/provider-claude": "0.1.
|
|
35
|
-
"@longtable/provider-codex": "0.1.
|
|
36
|
-
"@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"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^22.10.1",
|