@longtable/cli 0.1.31 → 0.1.33
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 +16 -13
- package/dist/cli.js +265 -36
- package/dist/codex-hooks.d.ts +22 -0
- package/dist/codex-hooks.js +240 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/longtable-codex-native-hook.d.ts +4 -0
- package/dist/longtable-codex-native-hook.js +314 -0
- package/dist/project-session.d.ts +108 -3
- package/dist/project-session.js +420 -23
- package/dist/prompt-aliases.js +5 -5
- package/dist/question-obligations.d.ts +22 -0
- package/dist/question-obligations.js +112 -0
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -8,9 +8,9 @@ Claude skills, and future MCP surfaces remain generated adapter artifacts.
|
|
|
8
8
|
|
|
9
9
|
The basic contract is:
|
|
10
10
|
|
|
11
|
-
1.
|
|
12
|
-
2.
|
|
13
|
-
3.
|
|
11
|
+
1. approve provider/runtime support once
|
|
12
|
+
2. start each project inside the provider with `$longtable-interview`
|
|
13
|
+
3. create or resume a workspace from that interview
|
|
14
14
|
4. preserve decisions, tensions, and evidence as durable project state
|
|
15
15
|
|
|
16
16
|
## Install
|
|
@@ -26,15 +26,17 @@ config, hooks, or provider runtime files without explicit setup approval.
|
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
28
|
longtable setup --provider codex
|
|
29
|
-
|
|
30
|
-
cd "<project-path>"
|
|
29
|
+
cd "<research-folder>"
|
|
31
30
|
codex
|
|
32
31
|
```
|
|
33
32
|
|
|
33
|
+
Then invoke `$longtable-interview` inside Codex.
|
|
34
|
+
|
|
34
35
|
`longtable setup --provider codex` is the permission-first setup route. It asks
|
|
35
36
|
where LongTable may install support, which runtime surfaces it may enable, how
|
|
36
|
-
strongly it may interrupt research decisions, and whether to
|
|
37
|
-
|
|
37
|
+
strongly it may interrupt research decisions, and whether to show the
|
|
38
|
+
provider-native interview launch steps. `longtable init` remains only as a
|
|
39
|
+
deprecated compatibility alias.
|
|
38
40
|
|
|
39
41
|
Return later:
|
|
40
42
|
|
|
@@ -44,7 +46,7 @@ longtable resume
|
|
|
44
46
|
codex
|
|
45
47
|
```
|
|
46
48
|
|
|
47
|
-
## What
|
|
49
|
+
## What `$longtable-interview` Creates
|
|
48
50
|
|
|
49
51
|
```text
|
|
50
52
|
<project>/
|
|
@@ -84,7 +86,6 @@ This is how LongTable avoids turning tacit knowledge into fake certainty.
|
|
|
84
86
|
|
|
85
87
|
```bash
|
|
86
88
|
longtable setup
|
|
87
|
-
longtable start
|
|
88
89
|
longtable resume --cwd "<project-path>"
|
|
89
90
|
longtable roles
|
|
90
91
|
longtable ask --cwd "<project-path>" --prompt "..."
|
|
@@ -136,12 +137,14 @@ longtable claude install-skills
|
|
|
136
137
|
|
|
137
138
|
Codex skills include `longtable`, `longtable-panel`, and generated role-specific
|
|
138
139
|
skills such as `longtable-methods-critic`. If your Codex build exposes explicit
|
|
139
|
-
skill shortcuts, `$longtable` is the
|
|
140
|
-
|
|
140
|
+
skill shortcuts, `$longtable-interview` is the research-start entry and
|
|
141
|
+
`$longtable` is the general router. Do not depend on `/prompts`; current Codex
|
|
142
|
+
builds may reject it.
|
|
141
143
|
|
|
142
144
|
Claude Code skills include `longtable`, `longtable-panel`, and generated
|
|
143
|
-
role-specific skills such as `longtable-methods-critic`. They
|
|
144
|
-
|
|
145
|
+
role-specific skills such as `longtable-methods-critic`. They also include
|
|
146
|
+
`longtable-interview` for the First Research Shape workflow. They are adapter
|
|
147
|
+
files generated from the LongTable role registry.
|
|
145
148
|
|
|
146
149
|
## Panel Orchestration
|
|
147
150
|
|
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import { createInterface } from "node:readline/promises";
|
|
|
6
6
|
import { stdin as input, stdout as output, cwd, env, exit } from "node:process";
|
|
7
7
|
import { dirname, join, resolve } from "node:path";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
9
10
|
import { classifyCheckpointTrigger } from "@longtable/checkpoints";
|
|
10
11
|
import { assessSearchSourceCapabilities, buildResearchSearchIntent, buildSearchCapabilitySnapshot, parsePublisherTarget, probePublisherAccess, publisherConfigs, runResearchSearch, searchCapabilitySnapshotPath, summarizeConfiguredPublisherAccess } from "./search/index.js";
|
|
11
12
|
import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput, installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultRuntimeConfigPath, resolveDefaultSetupPath, saveSetupOutput, saveSetupAndRuntimeConfig, serializeSetupOutput, writeRuntimeConfig } from "@longtable/setup";
|
|
@@ -15,7 +16,8 @@ import { installCodexPromptAliases, listInstalledCodexPromptAliases, removeCodex
|
|
|
15
16
|
import { buildPersonaGuidance, parseInvocationDirective } from "./persona-router.js";
|
|
16
17
|
import { PERSONA_DEFINITIONS, listRoleDefinitions } from "./personas.js";
|
|
17
18
|
import { buildPanelFallback, renderPanelSummary } from "./panel.js";
|
|
18
|
-
import {
|
|
19
|
+
import { LONGTABLE_MANAGED_HOOK_EVENTS, codexHooksEnabled, enableCodexHooksFeature, getMissingManagedCodexHookEvents, mergeManagedCodexHooksConfig, removeManagedCodexHooks } from "./codex-hooks.js";
|
|
20
|
+
import { appendInvocationRecordToWorkspace, assertWorkspaceNotBlocked, answerWorkspaceQuestion, clearWorkspaceQuestion, createWorkspaceFollowUpQuestions, createWorkspaceQuestion, createOrUpdateProjectWorkspace, inspectProjectWorkspace, loadWorkspaceState, loadProjectContextFromDirectory, repairWorkspaceStateConsistency, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
|
|
19
21
|
import { buildTeamDebate, buildTeamReview, renderTeamDebateSummary } from "./debate.js";
|
|
20
22
|
import { createPromptRenderer } from "./prompt-renderer.js";
|
|
21
23
|
const VALID_MODES = new Set([
|
|
@@ -43,7 +45,7 @@ const ANSI = {
|
|
|
43
45
|
green: "\u001B[32m"
|
|
44
46
|
};
|
|
45
47
|
const LONGTABLE_MCP_SERVER_NAME = "longtable-state";
|
|
46
|
-
const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.
|
|
48
|
+
const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.33";
|
|
47
49
|
const LONGTABLE_MCP_MARKER_START = "# LongTable state MCP START";
|
|
48
50
|
const LONGTABLE_MCP_MARKER_END = "# LongTable state MCP END";
|
|
49
51
|
function style(text, prefix) {
|
|
@@ -68,6 +70,20 @@ function renderBrandBanner(title, subtitle) {
|
|
|
68
70
|
}
|
|
69
71
|
return lines.join("\n");
|
|
70
72
|
}
|
|
73
|
+
function renderInterviewLaunchSteps(provider) {
|
|
74
|
+
const command = provider === "codex" ? "codex" : "claude";
|
|
75
|
+
return renderSectionCard("LongTable Interview", [
|
|
76
|
+
"Setup is permission and runtime calibration, not the research interview.",
|
|
77
|
+
"The first research conversation now happens inside the provider so the model can listen, reflect, and ask one natural-language follow-up at a time.",
|
|
78
|
+
"",
|
|
79
|
+
"Next:",
|
|
80
|
+
"1. cd \"<research-folder>\"",
|
|
81
|
+
`2. run \`${command}\``,
|
|
82
|
+
"3. invoke `$longtable-interview`",
|
|
83
|
+
"",
|
|
84
|
+
"The interview will create or resume `.longtable/`, build a First Research Shape, and use option UI only for the final confirmation."
|
|
85
|
+
]);
|
|
86
|
+
}
|
|
71
87
|
function renderProgressBar(current, total) {
|
|
72
88
|
const width = 10;
|
|
73
89
|
const filled = Math.max(1, Math.round((current / total) * width));
|
|
@@ -77,14 +93,14 @@ function usage() {
|
|
|
77
93
|
return [
|
|
78
94
|
"Usage:",
|
|
79
95
|
" Run `longtable ...` in your terminal, not inside the Codex chat box.",
|
|
80
|
-
"
|
|
96
|
+
" LongTable research starts inside Codex or Claude with `$longtable-interview` after setup.",
|
|
81
97
|
"",
|
|
82
98
|
" longtable setup [--provider codex|claude] [--install-scope user|project|none] [--surfaces cli_only|skills|skills_mcp|skills_mcp_sentinel] [--intervention advisory|balanced|strong] [--checkpoint-ui off|interactive|strong] [--workspace create|later] [--project-dir <path>] [--json] [--dir <path>] [--skills-dir <path>] [--runtime-path <file>] [--setup-path <file>]",
|
|
83
99
|
" longtable init [deprecated alias for setup; full legacy flags still supported for automation]",
|
|
84
|
-
" longtable start [--path <dir>] [--name <project>] [--goal <text>] [--blocker <text>] [--research-object research_question|theory_framework|measurement_instrument|study_design|analysis_plan|manuscript] [--gap-risk known_gap|suspected_tacit_assumptions|diagnose] [--protected-decision theory|measurement|method|evidence_citation|authorship_voice|submission_public_sharing] [--perspectives <role[,role]>] [--disagreement synthesis_only|show_on_conflict|always_visible] [--setup <path>] [--json]",
|
|
100
|
+
" longtable start [deprecated fallback] [--path <dir>] [--name <project>] [--goal <text>] [--blocker <text>] [--research-object research_question|theory_framework|measurement_instrument|study_design|analysis_plan|manuscript] [--gap-risk known_gap|suspected_tacit_assumptions|diagnose] [--protected-decision theory|measurement|method|evidence_citation|authorship_voice|submission_public_sharing] [--perspectives <role[,role]>] [--disagreement synthesis_only|show_on_conflict|always_visible] [--setup <path>] [--json] [--no-interview]",
|
|
85
101
|
" longtable resume [--cwd <path>] [--json]",
|
|
86
|
-
" longtable doctor [--cwd <path>] [--fix] [--json] [--codex-dir <path>] [--codex-config <path>] [--claude-dir <path>] [--codex-prompts-dir <path>] [--codex-runtime-path <file>] [--claude-runtime-path <file>]",
|
|
87
|
-
" longtable status [--cwd <path>] [--fix] [--json] [--codex-dir <path>] [--codex-config <path>] [--claude-dir <path>] [--codex-prompts-dir <path>] [--codex-runtime-path <file>] [--claude-runtime-path <file>]",
|
|
102
|
+
" longtable doctor [--cwd <path>] [--fix] [--json] [--codex-dir <path>] [--codex-config <path>] [--hooks-path <path>] [--claude-dir <path>] [--codex-prompts-dir <path>] [--codex-runtime-path <file>] [--claude-runtime-path <file>]",
|
|
103
|
+
" longtable status [--cwd <path>] [--fix] [--json] [--codex-dir <path>] [--codex-config <path>] [--hooks-path <path>] [--claude-dir <path>] [--codex-prompts-dir <path>] [--codex-runtime-path <file>] [--claude-runtime-path <file>]",
|
|
88
104
|
" longtable roles [--json]",
|
|
89
105
|
" longtable show [--json] [--path <file>]",
|
|
90
106
|
" longtable install [--json] [--path <file>] [--runtime-path <file>]",
|
|
@@ -98,6 +114,7 @@ function usage() {
|
|
|
98
114
|
" longtable ask [--prompt <text>] [--print] [--json] [--setup <path>] [--cwd <path>]",
|
|
99
115
|
" longtable clarify --prompt <task-context> [--provider codex|claude] [--required|--advisory] [--print] [--cwd <path>] [--json] [--force]",
|
|
100
116
|
" longtable question --prompt <decision-context> [--title <text>] [--text <question>] [--provider codex|claude] [--required|--advisory] [--print] [--cwd <path>] [--json]",
|
|
117
|
+
" longtable clear-question --question <id> --reason <text> [--cwd <path>] [--json]",
|
|
101
118
|
" longtable panel [--prompt <text>] [--role <role[,role]>] [--mode review|critique|draft|commit] [--visibility synthesis_only|show_on_conflict|always_visible] [--print] [--json] [--setup <path>] [--cwd <path>]",
|
|
102
119
|
" longtable decide [--question <id>] --answer <value-or-text> [--rationale <text>] [--provider codex|claude] [--cwd <path>] [--json]",
|
|
103
120
|
" longtable explore|review|critique|draft|commit|submit [--prompt <text>] [--role <role[,role]>] [--panel] [--show-conflicts] [--show-deliberation] [--print] [--json] [--stage <stage>] [--setup <path>] [--cwd <path>]",
|
|
@@ -106,7 +123,9 @@ function usage() {
|
|
|
106
123
|
" longtable codex remove-skills [--dir <path>]",
|
|
107
124
|
" longtable codex install-prompts [--dir <path>]",
|
|
108
125
|
" longtable codex remove-prompts [--dir <path>]",
|
|
109
|
-
" longtable codex
|
|
126
|
+
" longtable codex install-hooks [--codex-config <path>] [--hooks-path <path>] [--json]",
|
|
127
|
+
" longtable codex remove-hooks [--codex-config <path>] [--hooks-path <path>] [--json]",
|
|
128
|
+
" longtable codex status [--dir <path>] [--codex-config <path>] [--hooks-path <path>] [--json]",
|
|
110
129
|
" longtable claude install-skills [--dir <path>]",
|
|
111
130
|
" longtable claude remove-skills [--dir <path>]",
|
|
112
131
|
" longtable claude status [--dir <path>] [--json]",
|
|
@@ -114,9 +133,9 @@ function usage() {
|
|
|
114
133
|
"",
|
|
115
134
|
"Examples:",
|
|
116
135
|
" longtable setup --provider codex",
|
|
117
|
-
"
|
|
118
|
-
" longtable
|
|
119
|
-
"
|
|
136
|
+
" cd \"<research-folder>\" && codex",
|
|
137
|
+
" $longtable-interview",
|
|
138
|
+
" longtable start --no-interview --path ~/Research/My-Project --name \"AI Adoption Meta-Analysis\" --goal \"Narrow the review question\"",
|
|
120
139
|
" longtable doctor",
|
|
121
140
|
" longtable roles",
|
|
122
141
|
" longtable ask --prompt \"연구를 시작하고 싶어. 지금 어디서부터 좁혀야 할지 모르겠어.\"",
|
|
@@ -130,7 +149,7 @@ function parseArgs(argv) {
|
|
|
130
149
|
const values = {};
|
|
131
150
|
let subcommand = maybeSubcommand;
|
|
132
151
|
const modeCommand = command && VALID_MODES.has(command);
|
|
133
|
-
const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "panel", "decide", "sentinel", "team", "search"].includes(command);
|
|
152
|
+
const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "clear-question", "panel", "decide", "sentinel", "team", "search"].includes(command);
|
|
134
153
|
let startIndex = 1;
|
|
135
154
|
if (modeCommand) {
|
|
136
155
|
subcommand = undefined;
|
|
@@ -447,13 +466,13 @@ function buildPermissionSetupChoices() {
|
|
|
447
466
|
workspace: [
|
|
448
467
|
{
|
|
449
468
|
id: "create",
|
|
450
|
-
label: "
|
|
451
|
-
description: "Why:
|
|
469
|
+
label: "Show interview launch steps",
|
|
470
|
+
description: "Why: research should start inside the provider. What you get: setup finishes with Codex/Claude + $longtable-interview steps. Tradeoff: workspace creation waits for the in-provider interview."
|
|
452
471
|
},
|
|
453
472
|
{
|
|
454
473
|
id: "later",
|
|
455
474
|
label: "No, prepare runtime only",
|
|
456
|
-
description: "Why: keeps setup short. What you get: runtime support without project state. Tradeoff: no durable research memory until `
|
|
475
|
+
description: "Why: keeps setup short. What you get: runtime support without project state. Tradeoff: no durable research memory until `$longtable-interview` creates or resumes a workspace."
|
|
457
476
|
}
|
|
458
477
|
]
|
|
459
478
|
};
|
|
@@ -575,6 +594,8 @@ async function runSetup(args) {
|
|
|
575
594
|
interventionPosture: effectiveIntervention,
|
|
576
595
|
checkpointUiMode: checkpointUi,
|
|
577
596
|
workspaceCreationPreference: workspacePreference,
|
|
597
|
+
officialStartSurface: "$longtable-interview",
|
|
598
|
+
setupPosture: "permission_first",
|
|
578
599
|
teamMode: "panel"
|
|
579
600
|
};
|
|
580
601
|
if (surfaces === "skills_mcp_sentinel") {
|
|
@@ -617,7 +638,12 @@ async function runSetup(args) {
|
|
|
617
638
|
runtime: result,
|
|
618
639
|
installedSkills: installedSkills.map((skill) => skill.name),
|
|
619
640
|
mcpInstall,
|
|
620
|
-
workspacePreference
|
|
641
|
+
workspacePreference,
|
|
642
|
+
nextStep: {
|
|
643
|
+
surface: "$longtable-interview",
|
|
644
|
+
command: provider === "codex" ? "codex" : "claude",
|
|
645
|
+
description: "Open the provider in the research folder and invoke `$longtable-interview`."
|
|
646
|
+
}
|
|
621
647
|
}, null, 2));
|
|
622
648
|
return;
|
|
623
649
|
}
|
|
@@ -646,12 +672,11 @@ async function runSetup(args) {
|
|
|
646
672
|
console.log("Background sentinel approval recorded.");
|
|
647
673
|
console.log("Hook installation remains opt-in; LongTable will not install hooks without an explicit hook command.");
|
|
648
674
|
}
|
|
675
|
+
console.log("");
|
|
676
|
+
console.log(renderInterviewLaunchSteps(provider));
|
|
649
677
|
if (workspacePreference === "create") {
|
|
650
678
|
console.log("");
|
|
651
|
-
console.log("
|
|
652
|
-
await runStart({
|
|
653
|
-
setup: result.setupTarget.path
|
|
654
|
-
});
|
|
679
|
+
console.log("Workspace launch requested. Open the provider in your research folder and run `$longtable-interview`; the interview will create `.longtable/` there.");
|
|
655
680
|
}
|
|
656
681
|
}
|
|
657
682
|
function perspectiveChoices() {
|
|
@@ -849,6 +874,7 @@ function normalizePerspectiveList(value) {
|
|
|
849
874
|
.filter(Boolean);
|
|
850
875
|
}
|
|
851
876
|
async function collectProjectInterview(setup, args) {
|
|
877
|
+
const skipResearchInterview = args["no-interview"] === true;
|
|
852
878
|
const providedPerspectives = normalizePerspectiveList(typeof args.perspectives === "string" ? args.perspectives : undefined);
|
|
853
879
|
const providedGoal = typeof args.goal === "string" && args.goal.trim() ? args.goal.trim() : undefined;
|
|
854
880
|
const providedBlocker = typeof args.blocker === "string" && args.blocker.trim() ? args.blocker.trim() : undefined;
|
|
@@ -863,11 +889,11 @@ async function collectProjectInterview(setup, args) {
|
|
|
863
889
|
: undefined;
|
|
864
890
|
const needsInteractivePrompts = !(typeof args.name === "string" && args.name.trim()) ||
|
|
865
891
|
!(typeof args.path === "string" && args.path.trim()) ||
|
|
866
|
-
!providedGoal ||
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
892
|
+
(!skipResearchInterview && (!providedGoal ||
|
|
893
|
+
!providedBlocker ||
|
|
894
|
+
!providedResearchObject ||
|
|
895
|
+
!providedGapRisk ||
|
|
896
|
+
!providedProtectedDecision));
|
|
871
897
|
if (needsInteractivePrompts) {
|
|
872
898
|
console.log("");
|
|
873
899
|
console.log(renderBrandBanner("LongTable", "Project workspace interview"));
|
|
@@ -887,15 +913,17 @@ async function collectProjectInterview(setup, args) {
|
|
|
887
913
|
const projectPath = (typeof args.path === "string" && args.path.trim()
|
|
888
914
|
? normalizeUserPath(args.path.trim())
|
|
889
915
|
: resolveInteractiveProjectPath((await promptText(renderQuestionHeader(2, 2, "Workspace", `Which parent directory should contain this project?\nLongTable will create this folder:\n${suggestedPath}`), true)), projectName));
|
|
890
|
-
const adaptive =
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
!
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
916
|
+
const adaptive = skipResearchInterview
|
|
917
|
+
? {}
|
|
918
|
+
: await collectAdaptiveStartInterview({
|
|
919
|
+
currentGoal: providedGoal,
|
|
920
|
+
currentBlocker: providedBlocker,
|
|
921
|
+
needsResearchSeed: !providedGoal ||
|
|
922
|
+
!providedBlocker ||
|
|
923
|
+
!providedResearchObject ||
|
|
924
|
+
!providedGapRisk ||
|
|
925
|
+
!providedProtectedDecision
|
|
926
|
+
});
|
|
899
927
|
const currentGoal = providedGoal ?? adaptive.currentGoal;
|
|
900
928
|
if (!currentGoal?.trim()) {
|
|
901
929
|
throw new Error("LongTable start needs a current research goal or an opening interview answer.");
|
|
@@ -1090,6 +1118,16 @@ function resolveCodexMcpConfigPath(args) {
|
|
|
1090
1118
|
? args["codex-config"].trim()
|
|
1091
1119
|
: "~/.codex/config.toml"));
|
|
1092
1120
|
}
|
|
1121
|
+
function resolveCodexHooksPath(args) {
|
|
1122
|
+
if (typeof args["hooks-path"] === "string" && args["hooks-path"].trim()) {
|
|
1123
|
+
return resolve(normalizeUserPath(args["hooks-path"]));
|
|
1124
|
+
}
|
|
1125
|
+
const configPath = resolveCodexMcpConfigPath(args);
|
|
1126
|
+
return resolve(dirname(configPath), "hooks.json");
|
|
1127
|
+
}
|
|
1128
|
+
function resolveCliPackageRoot() {
|
|
1129
|
+
return resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
1130
|
+
}
|
|
1093
1131
|
function resolveClaudeMcpSettingsPath(args) {
|
|
1094
1132
|
return resolve(normalizeUserPath(typeof args["claude-settings"] === "string" && args["claude-settings"].trim()
|
|
1095
1133
|
? args["claude-settings"].trim()
|
|
@@ -1172,6 +1210,56 @@ async function writeClaudeMcpSettings(path, serverName, command, mcpArgs) {
|
|
|
1172
1210
|
await writeFile(path, `${updated}\n`, "utf8");
|
|
1173
1211
|
return `${updated}\n`;
|
|
1174
1212
|
}
|
|
1213
|
+
async function installCodexNativeHooks(args) {
|
|
1214
|
+
const configPath = resolveCodexMcpConfigPath(args);
|
|
1215
|
+
const hooksPath = resolveCodexHooksPath(args);
|
|
1216
|
+
const packageRoot = resolveCliPackageRoot();
|
|
1217
|
+
const existingConfig = existsSync(configPath) ? await readFile(configPath, "utf8") : "";
|
|
1218
|
+
const existingHooks = existsSync(hooksPath) ? await readFile(hooksPath, "utf8") : "";
|
|
1219
|
+
const nextConfig = enableCodexHooksFeature(existingConfig);
|
|
1220
|
+
const nextHooks = mergeManagedCodexHooksConfig(existingHooks, packageRoot);
|
|
1221
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
1222
|
+
await mkdir(dirname(hooksPath), { recursive: true });
|
|
1223
|
+
await writeFile(configPath, nextConfig, "utf8");
|
|
1224
|
+
await writeFile(hooksPath, nextHooks, "utf8");
|
|
1225
|
+
return {
|
|
1226
|
+
configPath,
|
|
1227
|
+
hooksPath,
|
|
1228
|
+
codexHooksEnabled: codexHooksEnabled(nextConfig),
|
|
1229
|
+
managedEvents: [...LONGTABLE_MANAGED_HOOK_EVENTS],
|
|
1230
|
+
write: true
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
async function removeCodexNativeHooks(args) {
|
|
1234
|
+
const configPath = resolveCodexMcpConfigPath(args);
|
|
1235
|
+
const hooksPath = resolveCodexHooksPath(args);
|
|
1236
|
+
const existingHooks = existsSync(hooksPath) ? await readFile(hooksPath, "utf8") : "";
|
|
1237
|
+
const removed = existingHooks ? removeManagedCodexHooks(existingHooks) : { nextContent: null, removedCount: 0 };
|
|
1238
|
+
if (removed.nextContent === null) {
|
|
1239
|
+
await rm(hooksPath, { force: true });
|
|
1240
|
+
}
|
|
1241
|
+
else {
|
|
1242
|
+
await mkdir(dirname(hooksPath), { recursive: true });
|
|
1243
|
+
await writeFile(hooksPath, removed.nextContent, "utf8");
|
|
1244
|
+
}
|
|
1245
|
+
const configContent = existsSync(configPath) ? await readFile(configPath, "utf8") : "";
|
|
1246
|
+
return {
|
|
1247
|
+
configPath,
|
|
1248
|
+
hooksPath,
|
|
1249
|
+
codexHooksEnabled: codexHooksEnabled(configContent),
|
|
1250
|
+
managedEvents: removed.removedCount > 0 ? [...LONGTABLE_MANAGED_HOOK_EVENTS] : [],
|
|
1251
|
+
write: true
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
function renderCodexHookInstallSummary(result) {
|
|
1255
|
+
return [
|
|
1256
|
+
"LongTable Codex hooks",
|
|
1257
|
+
`- config: ${result.configPath}`,
|
|
1258
|
+
`- hooks: ${result.hooksPath}`,
|
|
1259
|
+
`- codex_hooks feature: ${result.codexHooksEnabled ? "enabled" : "missing"}`,
|
|
1260
|
+
`- managed events: ${result.managedEvents.length > 0 ? result.managedEvents.join(", ") : "none"}`
|
|
1261
|
+
].join("\n");
|
|
1262
|
+
}
|
|
1175
1263
|
function renderMcpInstallSummary(result) {
|
|
1176
1264
|
const lines = [
|
|
1177
1265
|
"LongTable MCP transport",
|
|
@@ -1358,6 +1446,13 @@ async function collectDoctorStatus(args) {
|
|
|
1358
1446
|
const codexMcpConfig = existsSync(codexMcpConfigPath)
|
|
1359
1447
|
? await readFile(codexMcpConfigPath, "utf8")
|
|
1360
1448
|
: "";
|
|
1449
|
+
const codexHooksPath = resolveCodexHooksPath(args);
|
|
1450
|
+
const codexHooksContent = existsSync(codexHooksPath)
|
|
1451
|
+
? await readFile(codexHooksPath, "utf8")
|
|
1452
|
+
: "";
|
|
1453
|
+
const missingManagedHookEvents = codexHooksContent
|
|
1454
|
+
? (getMissingManagedCodexHookEvents(codexHooksContent) ?? [...LONGTABLE_MANAGED_HOOK_EVENTS])
|
|
1455
|
+
: [...LONGTABLE_MANAGED_HOOK_EVENTS];
|
|
1361
1456
|
const expectedCodexSkills = buildCodexSkillSpecs(roles).map((skill) => skill.name);
|
|
1362
1457
|
const expectedClaudeSkills = buildClaudeSkillSpecs(roles).map((skill) => skill.name);
|
|
1363
1458
|
const [codexSkills, claudeSkills, codexAliases, workspace] = await Promise.all([
|
|
@@ -1386,7 +1481,11 @@ async function collectDoctorStatus(args) {
|
|
|
1386
1481
|
mcpConfigPath: codexMcpConfigPath,
|
|
1387
1482
|
mcpConfigExists: existsSync(codexMcpConfigPath),
|
|
1388
1483
|
longtableMcpConfigured: codexLongTableMcpConfigured(codexMcpConfig),
|
|
1389
|
-
mcpElicitationsAllowed: codexMcpElicitationsAllowed(codexMcpConfig)
|
|
1484
|
+
mcpElicitationsAllowed: codexMcpElicitationsAllowed(codexMcpConfig),
|
|
1485
|
+
hooksPath: codexHooksPath,
|
|
1486
|
+
hooksExists: existsSync(codexHooksPath),
|
|
1487
|
+
codexHooksEnabled: codexHooksEnabled(codexMcpConfig),
|
|
1488
|
+
missingManagedHookEvents
|
|
1390
1489
|
},
|
|
1391
1490
|
claude: {
|
|
1392
1491
|
command: "claude",
|
|
@@ -1428,6 +1527,9 @@ function renderDoctorStatus(status) {
|
|
|
1428
1527
|
`- MCP config: ${status.providers.codex.mcpConfigExists ? "present" : "missing"} (${status.providers.codex.mcpConfigPath})`,
|
|
1429
1528
|
`- LongTable MCP: ${status.providers.codex.longtableMcpConfigured ? "configured" : "missing"}`,
|
|
1430
1529
|
`- MCP elicitation approval: ${status.providers.codex.mcpElicitationsAllowed ? "allowed" : "not allowed"}`,
|
|
1530
|
+
`- Codex hooks file: ${status.providers.codex.hooksExists ? "present" : "missing"} (${status.providers.codex.hooksPath})`,
|
|
1531
|
+
`- codex_hooks feature: ${status.providers.codex.codexHooksEnabled ? "enabled" : "missing"}`,
|
|
1532
|
+
`- managed hook coverage: ${status.providers.codex.missingManagedHookEvents.length === 0 ? "complete" : `missing ${status.providers.codex.missingManagedHookEvents.join(", ")}`}`,
|
|
1431
1533
|
"",
|
|
1432
1534
|
...renderProviderDoctorBlock("Claude", status.providers.claude),
|
|
1433
1535
|
"",
|
|
@@ -1438,7 +1540,7 @@ function renderDoctorStatus(status) {
|
|
|
1438
1540
|
}
|
|
1439
1541
|
else {
|
|
1440
1542
|
const workspace = status.workspace;
|
|
1441
|
-
lines.push(`- project: ${workspace.project?.name ?? "unknown"}`, `- root: ${workspace.rootPath ?? "unknown"}`, `- goal: ${workspace.session?.currentGoal ?? "unknown"}`, `- invocations: ${workspace.counts?.invocations ?? 0}`, `- questions: ${workspace.counts?.questions ?? 0} (${workspace.counts?.pendingQuestions ?? 0} pending, ${workspace.counts?.answeredQuestions ?? 0} answered)`, `- decisions: ${workspace.counts?.decisions ?? 0}`);
|
|
1543
|
+
lines.push(`- project: ${workspace.project?.name ?? "unknown"}`, `- root: ${workspace.rootPath ?? "unknown"}`, `- goal: ${workspace.session?.currentGoal ?? "unknown"}`, `- invocations: ${workspace.counts?.invocations ?? 0}`, `- questions: ${workspace.counts?.questions ?? 0} (${workspace.counts?.pendingQuestions ?? 0} pending, ${workspace.counts?.answeredQuestions ?? 0} answered)`, `- obligations: ${workspace.counts?.pendingObligations ?? 0} pending`, `- decisions: ${workspace.counts?.decisions ?? 0}`);
|
|
1442
1544
|
if ((workspace.recentInvocations ?? []).length > 0) {
|
|
1443
1545
|
lines.push("- recent invocations:");
|
|
1444
1546
|
for (const invocation of workspace.recentInvocations ?? []) {
|
|
@@ -1452,6 +1554,12 @@ function renderDoctorStatus(status) {
|
|
|
1452
1554
|
lines.push(` - ${question.id}: ${question.question} (${question.options.join("/")})`);
|
|
1453
1555
|
}
|
|
1454
1556
|
}
|
|
1557
|
+
if ((workspace.pendingObligations ?? []).length > 0) {
|
|
1558
|
+
lines.push("- pending obligations:");
|
|
1559
|
+
for (const obligation of workspace.pendingObligations ?? []) {
|
|
1560
|
+
lines.push(` - ${obligation.id}: ${obligation.prompt}`);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1455
1563
|
if ((workspace.answerWarnings ?? []).length > 0) {
|
|
1456
1564
|
lines.push("- answer warnings:");
|
|
1457
1565
|
for (const warning of workspace.answerWarnings ?? []) {
|
|
@@ -1466,11 +1574,16 @@ function renderDoctorStatus(status) {
|
|
|
1466
1574
|
const canFix = status.providers.codex.missingSkills.length > 0 ||
|
|
1467
1575
|
status.providers.claude.missingSkills.length > 0 ||
|
|
1468
1576
|
status.providers.codex.legacyPromptFilesInstalled.length > 0 ||
|
|
1577
|
+
!status.providers.codex.codexHooksEnabled ||
|
|
1578
|
+
status.providers.codex.missingManagedHookEvents.length > 0 ||
|
|
1469
1579
|
(status.setupExists &&
|
|
1470
1580
|
(!status.providers.codex.runtimeExists || !status.providers.claude.runtimeExists));
|
|
1471
1581
|
if (canFix) {
|
|
1472
1582
|
nextActions.push("longtable doctor --fix");
|
|
1473
1583
|
}
|
|
1584
|
+
if (!status.providers.codex.codexHooksEnabled || status.providers.codex.missingManagedHookEvents.length > 0) {
|
|
1585
|
+
nextActions.push("longtable codex install-hooks");
|
|
1586
|
+
}
|
|
1474
1587
|
if (!status.setupExists) {
|
|
1475
1588
|
nextActions.push("longtable setup --provider codex");
|
|
1476
1589
|
}
|
|
@@ -1500,6 +1613,15 @@ function renderRepairSummary(repair) {
|
|
|
1500
1613
|
if (repair.removedLegacyPromptFiles.length > 0) {
|
|
1501
1614
|
lines.push(`- removed legacy prompt files: ${repair.removedLegacyPromptFiles.length}`);
|
|
1502
1615
|
}
|
|
1616
|
+
if (repair.installedCodexHooks) {
|
|
1617
|
+
lines.push("- installed Codex native hooks");
|
|
1618
|
+
}
|
|
1619
|
+
if ((repair.repairedWorkspaceState ?? []).length > 0) {
|
|
1620
|
+
lines.push("- repaired workspace state:");
|
|
1621
|
+
for (const item of repair.repairedWorkspaceState ?? []) {
|
|
1622
|
+
lines.push(` - ${item}`);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1503
1625
|
if (repair.writtenRuntimeConfigs.length > 0) {
|
|
1504
1626
|
lines.push("- wrote runtime configs:");
|
|
1505
1627
|
for (const target of repair.writtenRuntimeConfigs) {
|
|
@@ -1551,6 +1673,8 @@ async function repairDoctorStatus(args, status) {
|
|
|
1551
1673
|
installedCodexSkills: [],
|
|
1552
1674
|
installedClaudeSkills: [],
|
|
1553
1675
|
removedLegacyPromptFiles: [],
|
|
1676
|
+
installedCodexHooks: false,
|
|
1677
|
+
repairedWorkspaceState: [],
|
|
1554
1678
|
writtenRuntimeConfigs: [],
|
|
1555
1679
|
skipped: []
|
|
1556
1680
|
};
|
|
@@ -1563,8 +1687,16 @@ async function repairDoctorStatus(args, status) {
|
|
|
1563
1687
|
if (status.providers.codex.legacyPromptFilesInstalled.length > 0) {
|
|
1564
1688
|
repair.removedLegacyPromptFiles = await removeCodexPromptAliases(codexPromptsDir);
|
|
1565
1689
|
}
|
|
1690
|
+
if (!status.providers.codex.codexHooksEnabled || status.providers.codex.missingManagedHookEvents.length > 0) {
|
|
1691
|
+
await installCodexNativeHooks(args);
|
|
1692
|
+
repair.installedCodexHooks = true;
|
|
1693
|
+
}
|
|
1566
1694
|
if (!status.setupExists) {
|
|
1567
1695
|
repair.skipped.push("runtime configs require setup approval; run `longtable setup --provider codex` first");
|
|
1696
|
+
const workspaceContext = await loadProjectContextFromDirectory(typeof args.cwd === "string" ? args.cwd : cwd());
|
|
1697
|
+
if (workspaceContext) {
|
|
1698
|
+
repair.repairedWorkspaceState = (await repairWorkspaceStateConsistency({ context: workspaceContext })).repaired;
|
|
1699
|
+
}
|
|
1568
1700
|
return repair;
|
|
1569
1701
|
}
|
|
1570
1702
|
const setup = await loadSetupOutput(setupOverride);
|
|
@@ -1584,6 +1716,10 @@ async function repairDoctorStatus(args, status) {
|
|
|
1584
1716
|
format: target.format
|
|
1585
1717
|
});
|
|
1586
1718
|
}
|
|
1719
|
+
const workspaceContext = await loadProjectContextFromDirectory(typeof args.cwd === "string" ? args.cwd : cwd());
|
|
1720
|
+
if (workspaceContext) {
|
|
1721
|
+
repair.repairedWorkspaceState = (await repairWorkspaceStateConsistency({ context: workspaceContext })).repaired;
|
|
1722
|
+
}
|
|
1587
1723
|
return repair;
|
|
1588
1724
|
}
|
|
1589
1725
|
async function runDoctor(args) {
|
|
@@ -2308,6 +2444,41 @@ async function runQuestion(args) {
|
|
|
2308
2444
|
console.log(`- answer: longtable decide --question ${result.question.id} --answer <value>`);
|
|
2309
2445
|
console.log(`- current: ${context.currentFilePath}`);
|
|
2310
2446
|
}
|
|
2447
|
+
async function runClearQuestion(args) {
|
|
2448
|
+
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
2449
|
+
const questionId = typeof args.question === "string" ? args.question.trim() : "";
|
|
2450
|
+
const reason = typeof args.reason === "string" ? args.reason.trim() : "";
|
|
2451
|
+
if (!questionId) {
|
|
2452
|
+
throw new Error("`clear-question` requires --question <id>.");
|
|
2453
|
+
}
|
|
2454
|
+
if (!reason) {
|
|
2455
|
+
throw new Error("`clear-question` requires --reason <text>.");
|
|
2456
|
+
}
|
|
2457
|
+
const context = await loadProjectContextFromDirectory(workingDirectory);
|
|
2458
|
+
if (!context) {
|
|
2459
|
+
throw new Error("No LongTable project workspace was found here. Run this inside a project or pass --cwd.");
|
|
2460
|
+
}
|
|
2461
|
+
const result = await clearWorkspaceQuestion({
|
|
2462
|
+
context,
|
|
2463
|
+
questionId,
|
|
2464
|
+
reason
|
|
2465
|
+
});
|
|
2466
|
+
if (args.json === true) {
|
|
2467
|
+
console.log(JSON.stringify({
|
|
2468
|
+
question: result.question,
|
|
2469
|
+
files: {
|
|
2470
|
+
state: context.stateFilePath,
|
|
2471
|
+
current: context.currentFilePath
|
|
2472
|
+
}
|
|
2473
|
+
}, null, 2));
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
console.log("LongTable question cleared");
|
|
2477
|
+
console.log(`- question: ${result.question.id}`);
|
|
2478
|
+
console.log(`- reason: ${result.question.clearedReason ?? reason}`);
|
|
2479
|
+
console.log(`- state: ${context.stateFilePath}`);
|
|
2480
|
+
console.log(`- current: ${context.currentFilePath}`);
|
|
2481
|
+
}
|
|
2311
2482
|
function isInteractiveTerminal() {
|
|
2312
2483
|
return Boolean(input.isTTY && output.isTTY);
|
|
2313
2484
|
}
|
|
@@ -2773,6 +2944,26 @@ async function runRoles(args) {
|
|
|
2773
2944
|
}
|
|
2774
2945
|
}
|
|
2775
2946
|
async function runStart(args) {
|
|
2947
|
+
const hasMinimalFallbackArgs = typeof args.name === "string" &&
|
|
2948
|
+
typeof args.path === "string" &&
|
|
2949
|
+
typeof args.goal === "string";
|
|
2950
|
+
const hasFallbackIntent = args["no-interview"] === true ||
|
|
2951
|
+
hasMinimalFallbackArgs;
|
|
2952
|
+
if (!hasFallbackIntent) {
|
|
2953
|
+
console.log(renderSectionCard("LongTable Start Has Moved", [
|
|
2954
|
+
"`longtable start` is now a fallback for automation and scripted workspace creation.",
|
|
2955
|
+
"The primary research-start experience is provider-native so LongTable can run a real interview instead of a terminal questionnaire.",
|
|
2956
|
+
"",
|
|
2957
|
+
"Use:",
|
|
2958
|
+
"1. longtable setup --provider codex",
|
|
2959
|
+
"2. cd \"<research-folder>\"",
|
|
2960
|
+
"3. codex",
|
|
2961
|
+
"4. $longtable-interview",
|
|
2962
|
+
"",
|
|
2963
|
+
"For automation, pass `--no-interview --json` with `--name`, `--path`, and `--goal`."
|
|
2964
|
+
]));
|
|
2965
|
+
return;
|
|
2966
|
+
}
|
|
2776
2967
|
const setupPath = typeof args.setup === "string" ? args.setup : undefined;
|
|
2777
2968
|
const existingSetup = await loadOptionalSetup(setupPath);
|
|
2778
2969
|
if (!existingSetup) {
|
|
@@ -2890,11 +3081,34 @@ async function runCodexSubcommand(subcommand, args) {
|
|
|
2890
3081
|
console.log(`Removed ${removed.length} legacy LongTable prompt files from ${resolveCodexPromptsDir(customDir)}`);
|
|
2891
3082
|
return;
|
|
2892
3083
|
}
|
|
3084
|
+
if (subcommand === "install-hooks") {
|
|
3085
|
+
const result = await installCodexNativeHooks(args);
|
|
3086
|
+
if (args.json === true) {
|
|
3087
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3088
|
+
return;
|
|
3089
|
+
}
|
|
3090
|
+
console.log(renderCodexHookInstallSummary(result));
|
|
3091
|
+
console.log("Restart Codex so the native hook config is reloaded.");
|
|
3092
|
+
return;
|
|
3093
|
+
}
|
|
3094
|
+
if (subcommand === "remove-hooks") {
|
|
3095
|
+
const result = await removeCodexNativeHooks(args);
|
|
3096
|
+
if (args.json === true) {
|
|
3097
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
3100
|
+
console.log(renderCodexHookInstallSummary(result));
|
|
3101
|
+
return;
|
|
3102
|
+
}
|
|
2893
3103
|
if (subcommand === "status") {
|
|
2894
3104
|
const aliases = await listInstalledCodexPromptAliases(customDir);
|
|
2895
3105
|
const skills = await listInstalledCodexSkills(roles, customDir);
|
|
2896
3106
|
const setupPath = resolveDefaultSetupPath(typeof args.path === "string" ? args.path : undefined).path;
|
|
2897
3107
|
const runtimePath = resolveDefaultRuntimeConfigPath("codex", typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined).path;
|
|
3108
|
+
const configPath = resolveCodexMcpConfigPath(args);
|
|
3109
|
+
const configContent = existsSync(configPath) ? await readFile(configPath, "utf8") : "";
|
|
3110
|
+
const hooksPath = resolveCodexHooksPath(args);
|
|
3111
|
+
const hooksContent = existsSync(hooksPath) ? await readFile(hooksPath, "utf8") : "";
|
|
2898
3112
|
const status = {
|
|
2899
3113
|
setupPath,
|
|
2900
3114
|
setupExists: existsSync(setupPath),
|
|
@@ -2903,7 +3117,14 @@ async function runCodexSubcommand(subcommand, args) {
|
|
|
2903
3117
|
skillsDir: resolveCodexSkillsDir(customDir),
|
|
2904
3118
|
skillsInstalled: skills.map((skill) => skill.name),
|
|
2905
3119
|
promptsDir: resolveCodexPromptsDir(customDir),
|
|
2906
|
-
legacyPromptFilesInstalled: aliases.map((alias) => alias.name)
|
|
3120
|
+
legacyPromptFilesInstalled: aliases.map((alias) => alias.name),
|
|
3121
|
+
codexConfigPath: configPath,
|
|
3122
|
+
codexHooksEnabled: codexHooksEnabled(configContent),
|
|
3123
|
+
hooksPath,
|
|
3124
|
+
hooksExists: existsSync(hooksPath),
|
|
3125
|
+
missingManagedHookEvents: hooksContent
|
|
3126
|
+
? (getMissingManagedCodexHookEvents(hooksContent) ?? [...LONGTABLE_MANAGED_HOOK_EVENTS])
|
|
3127
|
+
: [...LONGTABLE_MANAGED_HOOK_EVENTS]
|
|
2907
3128
|
};
|
|
2908
3129
|
if (args.json === true) {
|
|
2909
3130
|
console.log(JSON.stringify(status, null, 2));
|
|
@@ -2933,6 +3154,10 @@ async function runCodexSubcommand(subcommand, args) {
|
|
|
2933
3154
|
console.log(` - ${alias.name}`);
|
|
2934
3155
|
}
|
|
2935
3156
|
}
|
|
3157
|
+
console.log(`- codex config: ${status.codexConfigPath}`);
|
|
3158
|
+
console.log(`- codex_hooks feature: ${status.codexHooksEnabled ? "enabled" : "missing"}`);
|
|
3159
|
+
console.log(`- hooks file: ${status.hooksExists ? "present" : "missing"} (${status.hooksPath})`);
|
|
3160
|
+
console.log(`- managed hook coverage: ${status.missingManagedHookEvents.length === 0 ? "complete" : `missing ${status.missingManagedHookEvents.join(", ")}`}`);
|
|
2936
3161
|
return;
|
|
2937
3162
|
}
|
|
2938
3163
|
throw new Error("Unknown codex subcommand.");
|
|
@@ -3046,6 +3271,10 @@ async function main() {
|
|
|
3046
3271
|
await runQuestion(values);
|
|
3047
3272
|
return;
|
|
3048
3273
|
}
|
|
3274
|
+
if (command === "clear-question") {
|
|
3275
|
+
await runClearQuestion(values);
|
|
3276
|
+
return;
|
|
3277
|
+
}
|
|
3049
3278
|
if (command === "panel") {
|
|
3050
3279
|
await runPanelCommand(values);
|
|
3051
3280
|
return;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const LONGTABLE_MANAGED_HOOK_EVENTS: readonly ["SessionStart", "PreToolUse", "PostToolUse", "UserPromptSubmit", "Stop"];
|
|
2
|
+
type ManagedHookEventName = (typeof LONGTABLE_MANAGED_HOOK_EVENTS)[number];
|
|
3
|
+
type JsonObject = Record<string, unknown>;
|
|
4
|
+
export interface ManagedCodexHooksConfig {
|
|
5
|
+
hooks: Record<ManagedHookEventName, Array<Record<string, unknown>>>;
|
|
6
|
+
}
|
|
7
|
+
interface ParsedCodexHooksConfig {
|
|
8
|
+
root: JsonObject;
|
|
9
|
+
hooks: JsonObject;
|
|
10
|
+
}
|
|
11
|
+
export interface RemoveManagedCodexHooksResult {
|
|
12
|
+
nextContent: string | null;
|
|
13
|
+
removedCount: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function buildManagedCodexHooksConfig(packageRoot: string): ManagedCodexHooksConfig;
|
|
16
|
+
export declare function parseCodexHooksConfig(content: string): ParsedCodexHooksConfig | null;
|
|
17
|
+
export declare function getMissingManagedCodexHookEvents(content: string): ManagedHookEventName[] | null;
|
|
18
|
+
export declare function mergeManagedCodexHooksConfig(existingContent: string | null | undefined, packageRoot: string): string;
|
|
19
|
+
export declare function removeManagedCodexHooks(existingContent: string): RemoveManagedCodexHooksResult;
|
|
20
|
+
export declare function enableCodexHooksFeature(existing: string): string;
|
|
21
|
+
export declare function codexHooksEnabled(config: string): boolean;
|
|
22
|
+
export {};
|