tmux-agent 0.1.0
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/.codex/skills/speckit/SKILL.md +173 -0
- package/.codex/skills/speckit/assets/templates/checklist-template.md +49 -0
- package/.codex/skills/speckit/assets/templates/notes-entrypoints-template.md +11 -0
- package/.codex/skills/speckit/assets/templates/notes-questions-template.md +7 -0
- package/.codex/skills/speckit/assets/templates/notes-readme-template.md +36 -0
- package/.codex/skills/speckit/assets/templates/notes-session-template.md +21 -0
- package/.codex/skills/speckit/assets/templates/plan-template.md +126 -0
- package/.codex/skills/speckit/assets/templates/spec-template.md +135 -0
- package/.codex/skills/speckit/assets/templates/tasks-template.md +269 -0
- package/.codex/skills/speckit/references/acceptance.md +183 -0
- package/.codex/skills/speckit/references/analyze.md +186 -0
- package/.codex/skills/speckit/references/checklist.md +302 -0
- package/.codex/skills/speckit/references/clarify-auto.md +69 -0
- package/.codex/skills/speckit/references/clarify-detailed.md +78 -0
- package/.codex/skills/speckit/references/clarify.md +189 -0
- package/.codex/skills/speckit/references/constitution.md +90 -0
- package/.codex/skills/speckit/references/group.md +89 -0
- package/.codex/skills/speckit/references/implement-task.md +115 -0
- package/.codex/skills/speckit/references/implement.md +129 -0
- package/.codex/skills/speckit/references/notes.md +82 -0
- package/.codex/skills/speckit/references/plan-deep.md +87 -0
- package/.codex/skills/speckit/references/plan-from-questions.md +115 -0
- package/.codex/skills/speckit/references/plan-from-review.md +89 -0
- package/.codex/skills/speckit/references/plan.md +97 -0
- package/.codex/skills/speckit/references/review-plan.md +156 -0
- package/.codex/skills/speckit/references/specify.md +246 -0
- package/.codex/skills/speckit/references/tasks.md +155 -0
- package/.codex/skills/speckit/references/taskstoissues.md +33 -0
- package/.codex/skills/speckit/scripts/bash/check-prerequisites.sh +206 -0
- package/.codex/skills/speckit/scripts/bash/common.sh +191 -0
- package/.codex/skills/speckit/scripts/bash/create-new-feature.sh +259 -0
- package/.codex/skills/speckit/scripts/bash/extract-coded-points.sh +322 -0
- package/.codex/skills/speckit/scripts/bash/extract-spec-ids.sh +238 -0
- package/.codex/skills/speckit/scripts/bash/extract-tasks.sh +295 -0
- package/.codex/skills/speckit/scripts/bash/extract-user-stories.sh +312 -0
- package/.codex/skills/speckit/scripts/bash/setup-notes.sh +182 -0
- package/.codex/skills/speckit/scripts/bash/setup-plan.sh +110 -0
- package/.codex/skills/speckit/scripts/bash/show-todo-tasks.sh +257 -0
- package/.codex/skills/speckit/scripts/bash/spec-group-checklist.sh +402 -0
- package/.codex/skills/speckit/scripts/bash/spec-group-members.sh +215 -0
- package/.codex/skills/speckit/scripts/bash/spec-registry-graph.sh +399 -0
- package/.specify/memory/constitution.md +67 -0
- package/.specify/templates/agent-file-template.md +28 -0
- package/.specify/templates/checklist-template.md +49 -0
- package/.specify/templates/plan-template.md +126 -0
- package/.specify/templates/spec-template.md +135 -0
- package/.specify/templates/tasks-template.md +269 -0
- package/README.md +128 -0
- package/README.zh-CN.md +127 -0
- package/bun.lock +269 -0
- package/dist/cli/commands/codex/forkHome.js +88 -0
- package/dist/cli/commands/codex/send.js +55 -0
- package/dist/cli/commands/codex/sessionInfo.js +42 -0
- package/dist/cli/commands/codex/spawn.js +68 -0
- package/dist/cli/commands/find.js +26 -0
- package/dist/cli/commands/paneKill.js +33 -0
- package/dist/cli/commands/paneSpawn.js +40 -0
- package/dist/cli/commands/paneTitle.js +33 -0
- package/dist/cli/commands/read.js +34 -0
- package/dist/cli/commands/send.js +51 -0
- package/dist/cli/commands/snapshot.js +19 -0
- package/dist/cli/commands/ui/select.js +41 -0
- package/dist/cli/commands/windowKill.js +25 -0
- package/dist/cli/commands/windowLs.js +15 -0
- package/dist/cli/commands/windowNew.js +28 -0
- package/dist/cli/commands/windowRename.js +25 -0
- package/dist/cli/index.js +365 -0
- package/dist/cli/parse.js +39 -0
- package/dist/lib/codex/forkHome.js +101 -0
- package/dist/lib/codex/isCodexPane.js +55 -0
- package/dist/lib/codex/send.js +58 -0
- package/dist/lib/codex/sessionInfo.js +449 -0
- package/dist/lib/codex/spawn.js +246 -0
- package/dist/lib/contracts/types.js +2 -0
- package/dist/lib/fs/safeRm.js +32 -0
- package/dist/lib/io/readStdin.js +14 -0
- package/dist/lib/os/process.js +55 -0
- package/dist/lib/output/format.js +95 -0
- package/dist/lib/proc/lsof.js +42 -0
- package/dist/lib/proc/ps.js +60 -0
- package/dist/lib/targeting/errors.js +13 -0
- package/dist/lib/targeting/resolvePaneTarget.js +91 -0
- package/dist/lib/targeting/resolveWindowTarget.js +40 -0
- package/dist/lib/targeting/scope.js +58 -0
- package/dist/lib/tmux/capturePane.js +20 -0
- package/dist/lib/tmux/exec.js +66 -0
- package/dist/lib/tmux/paneOps.js +29 -0
- package/dist/lib/tmux/paste.js +23 -0
- package/dist/lib/tmux/sendKeys.js +47 -0
- package/dist/lib/tmux/session.js +29 -0
- package/dist/lib/tmux/snapshotPanes.js +46 -0
- package/dist/lib/tmux/snapshotWindows.js +24 -0
- package/dist/lib/tmux/windowOps.js +32 -0
- package/dist/lib/ui/popupSelect.js +432 -0
- package/dist/lib/ui/popupSupport.js +76 -0
- package/package.json +23 -0
- package/src/cli/commands/codex/forkHome.ts +141 -0
- package/src/cli/commands/codex/send.ts +83 -0
- package/src/cli/commands/codex/sessionInfo.ts +59 -0
- package/src/cli/commands/codex/spawn.ts +90 -0
- package/src/cli/commands/find.ts +40 -0
- package/src/cli/commands/paneKill.ts +49 -0
- package/src/cli/commands/paneSpawn.ts +53 -0
- package/src/cli/commands/paneTitle.ts +50 -0
- package/src/cli/commands/read.ts +48 -0
- package/src/cli/commands/send.ts +71 -0
- package/src/cli/commands/snapshot.ts +28 -0
- package/src/cli/commands/ui/select.ts +49 -0
- package/src/cli/commands/windowKill.ts +35 -0
- package/src/cli/commands/windowLs.ts +20 -0
- package/src/cli/commands/windowNew.ts +40 -0
- package/src/cli/commands/windowRename.ts +36 -0
- package/src/cli/index.ts +430 -0
- package/src/lib/codex/forkHome.ts +148 -0
- package/src/lib/codex/isCodexPane.ts +56 -0
- package/src/lib/codex/send.ts +84 -0
- package/src/lib/codex/sessionInfo.ts +521 -0
- package/src/lib/codex/spawn.ts +305 -0
- package/src/lib/contracts/types.ts +30 -0
- package/src/lib/fs/safeRm.ts +32 -0
- package/src/lib/io/readStdin.ts +11 -0
- package/src/lib/output/format.ts +105 -0
- package/src/lib/proc/lsof.ts +44 -0
- package/src/lib/proc/ps.ts +70 -0
- package/src/lib/targeting/errors.ts +25 -0
- package/src/lib/targeting/resolvePaneTarget.ts +106 -0
- package/src/lib/targeting/resolveWindowTarget.ts +45 -0
- package/src/lib/targeting/scope.ts +76 -0
- package/src/lib/tmux/capturePane.ts +21 -0
- package/src/lib/tmux/exec.ts +90 -0
- package/src/lib/tmux/paneOps.ts +35 -0
- package/src/lib/tmux/paste.ts +20 -0
- package/src/lib/tmux/sendKeys.ts +72 -0
- package/src/lib/tmux/session.ts +27 -0
- package/src/lib/tmux/snapshotPanes.ts +52 -0
- package/src/lib/tmux/snapshotWindows.ts +23 -0
- package/src/lib/tmux/windowOps.ts +43 -0
- package/src/lib/ui/popupSelect.ts +561 -0
- package/src/lib/ui/popupSupport.ts +84 -0
- package/tests/e2e/codexForkHome.test.ts +146 -0
- package/tests/e2e/codexSessionInfo.test.ts +112 -0
- package/tests/e2e/codexTuiSend.test.ts +68 -0
- package/tests/integration/codexSpawn.test.ts +113 -0
- package/tests/integration/paneOps.test.ts +60 -0
- package/tests/integration/sendRead.test.ts +52 -0
- package/tests/integration/snapshot.test.ts +39 -0
- package/tests/integration/tmuxHarness.ts +39 -0
- package/tests/integration/windowOps.test.ts +60 -0
- package/tests/unit/codexSend.test.ts +105 -0
- package/tests/unit/codexSessionInfo.test.ts +88 -0
- package/tests/unit/codexSpawn.test.ts +34 -0
- package/tests/unit/keys.test.ts +30 -0
- package/tests/unit/outputFormat.test.ts +52 -0
- package/tests/unit/popupSelect.test.ts +77 -0
- package/tests/unit/popupSupport.test.ts +109 -0
- package/tests/unit/resolvePaneTarget.test.ts +43 -0
- package/tests/unit/resolveWindowTarget.test.ts +36 -0
- package/tests/unit/safeRm.test.ts +41 -0
- package/tests/unit/scope.test.ts +57 -0
- package/tsconfig.json +14 -0
- package/vitest.config.ts +16 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const format_1 = require("../lib/output/format");
|
|
6
|
+
const errors_1 = require("../lib/targeting/errors");
|
|
7
|
+
const windowLs_1 = require("./commands/windowLs");
|
|
8
|
+
const snapshot_1 = require("./commands/snapshot");
|
|
9
|
+
const find_1 = require("./commands/find");
|
|
10
|
+
const send_1 = require("./commands/send");
|
|
11
|
+
const read_1 = require("./commands/read");
|
|
12
|
+
const windowNew_1 = require("./commands/windowNew");
|
|
13
|
+
const windowRename_1 = require("./commands/windowRename");
|
|
14
|
+
const windowKill_1 = require("./commands/windowKill");
|
|
15
|
+
const paneSpawn_1 = require("./commands/paneSpawn");
|
|
16
|
+
const paneTitle_1 = require("./commands/paneTitle");
|
|
17
|
+
const paneKill_1 = require("./commands/paneKill");
|
|
18
|
+
const send_2 = require("./commands/codex/send");
|
|
19
|
+
const sessionInfo_1 = require("./commands/codex/sessionInfo");
|
|
20
|
+
const forkHome_1 = require("./commands/codex/forkHome");
|
|
21
|
+
const spawn_1 = require("./commands/codex/spawn");
|
|
22
|
+
const select_1 = require("./commands/ui/select");
|
|
23
|
+
function writeLine(stream, text) {
|
|
24
|
+
if (!text.endsWith("\n")) {
|
|
25
|
+
stream.write(`${text}\n`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
stream.write(text);
|
|
29
|
+
}
|
|
30
|
+
function formatError(error) {
|
|
31
|
+
if (error instanceof errors_1.TargetResolutionError) {
|
|
32
|
+
const base = (0, format_1.ensureErrorPrefix)(error.message);
|
|
33
|
+
if (error.kind === "ambiguous" && error.candidates?.length) {
|
|
34
|
+
if (error.targetKind === "pane") {
|
|
35
|
+
return `${base}\n${(0, format_1.formatPaneTable)(error.candidates)}`;
|
|
36
|
+
}
|
|
37
|
+
return `${base}\n${(0, format_1.formatWindowTable)(error.candidates)}`;
|
|
38
|
+
}
|
|
39
|
+
return base;
|
|
40
|
+
}
|
|
41
|
+
if (error instanceof Error) {
|
|
42
|
+
return (0, format_1.ensureErrorPrefix)(error.message);
|
|
43
|
+
}
|
|
44
|
+
return (0, format_1.ensureErrorPrefix)(String(error));
|
|
45
|
+
}
|
|
46
|
+
function parseOptionalBoolean(value) {
|
|
47
|
+
if (value === undefined) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (typeof value === "boolean") {
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
const normalized = value.trim().toLowerCase();
|
|
54
|
+
return normalized !== "false";
|
|
55
|
+
}
|
|
56
|
+
function parseNonNegativeNumber(optionName) {
|
|
57
|
+
return (value) => {
|
|
58
|
+
const num = Number(value);
|
|
59
|
+
if (!Number.isFinite(num) || num < 0) {
|
|
60
|
+
throw new Error(`invalid --${optionName}`);
|
|
61
|
+
}
|
|
62
|
+
return num;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function buildProgram() {
|
|
66
|
+
const program = new commander_1.Command();
|
|
67
|
+
program
|
|
68
|
+
.name("agent-tmux")
|
|
69
|
+
.description("LLM-friendly tmux control plane CLI for windows/panes.")
|
|
70
|
+
.exitOverride()
|
|
71
|
+
.configureOutput({
|
|
72
|
+
writeErr: () => undefined
|
|
73
|
+
});
|
|
74
|
+
const window = program.command("window");
|
|
75
|
+
window.command("ls")
|
|
76
|
+
.option("--json [boolean]", "output json", parseOptionalBoolean, false)
|
|
77
|
+
.option("--session <session>", "tmux session name")
|
|
78
|
+
.action(async (options) => {
|
|
79
|
+
const output = await (0, windowLs_1.windowLs)({ json: options.json, session: options.session });
|
|
80
|
+
if (output) {
|
|
81
|
+
writeLine(process.stdout, output);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
window.command("new")
|
|
85
|
+
.argument("<name>")
|
|
86
|
+
.option("--json [boolean]", "output json", parseOptionalBoolean, false)
|
|
87
|
+
.option("--session <session>", "tmux session name")
|
|
88
|
+
.option("--command <command>", "initial command to run (or '-' to read from stdin)")
|
|
89
|
+
.action(async (name, options) => {
|
|
90
|
+
const output = await (0, windowNew_1.windowNewCommand)(name, {
|
|
91
|
+
json: options.json,
|
|
92
|
+
session: options.session,
|
|
93
|
+
command: options.command
|
|
94
|
+
});
|
|
95
|
+
if (output) {
|
|
96
|
+
writeLine(process.stdout, output);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
window.command("rename")
|
|
100
|
+
.argument("<windowTarget>")
|
|
101
|
+
.argument("<name>")
|
|
102
|
+
.option("--json [boolean]", "output json", parseOptionalBoolean, false)
|
|
103
|
+
.option("--session <session>", "tmux session name")
|
|
104
|
+
.action(async (target, name, options) => {
|
|
105
|
+
const output = await (0, windowRename_1.windowRenameCommand)(target, name, {
|
|
106
|
+
json: options.json,
|
|
107
|
+
session: options.session
|
|
108
|
+
});
|
|
109
|
+
if (output) {
|
|
110
|
+
writeLine(process.stdout, output);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
window.command("kill")
|
|
114
|
+
.argument("<windowTarget>")
|
|
115
|
+
.option("--json [boolean]", "output json", parseOptionalBoolean, false)
|
|
116
|
+
.option("--session <session>", "tmux session name")
|
|
117
|
+
.action(async (target, options) => {
|
|
118
|
+
const output = await (0, windowKill_1.windowKillCommand)(target, {
|
|
119
|
+
json: options.json,
|
|
120
|
+
session: options.session
|
|
121
|
+
});
|
|
122
|
+
if (output) {
|
|
123
|
+
writeLine(process.stdout, output);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
const pane = program.command("pane");
|
|
127
|
+
pane.command("spawn")
|
|
128
|
+
.argument("<command>")
|
|
129
|
+
.option("--json [boolean]", "output json", parseOptionalBoolean, false)
|
|
130
|
+
.option("--session <session>", "tmux session name")
|
|
131
|
+
.option("--window <window>", "tmux window target")
|
|
132
|
+
.option("--title <title>", "pane title")
|
|
133
|
+
.action(async (command, options) => {
|
|
134
|
+
const output = await (0, paneSpawn_1.paneSpawnCommand)(command, {
|
|
135
|
+
json: options.json,
|
|
136
|
+
session: options.session,
|
|
137
|
+
window: options.window,
|
|
138
|
+
title: options.title
|
|
139
|
+
});
|
|
140
|
+
if (output) {
|
|
141
|
+
writeLine(process.stdout, output);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
pane.command("title")
|
|
145
|
+
.argument("<paneTarget>")
|
|
146
|
+
.argument("<title>")
|
|
147
|
+
.option("--json [boolean]", "output json", parseOptionalBoolean, false)
|
|
148
|
+
.option("--session <session>", "tmux session name")
|
|
149
|
+
.option("--window <window>", "tmux window target")
|
|
150
|
+
.action(async (target, title, options) => {
|
|
151
|
+
const output = await (0, paneTitle_1.paneTitleCommand)(target, title, {
|
|
152
|
+
json: options.json,
|
|
153
|
+
session: options.session,
|
|
154
|
+
window: options.window
|
|
155
|
+
});
|
|
156
|
+
if (output) {
|
|
157
|
+
writeLine(process.stdout, output);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
pane.command("kill")
|
|
161
|
+
.argument("<paneTarget>")
|
|
162
|
+
.option("--json [boolean]", "output json", parseOptionalBoolean, false)
|
|
163
|
+
.option("--session <session>", "tmux session name")
|
|
164
|
+
.option("--window <window>", "tmux window target")
|
|
165
|
+
.action(async (target, options) => {
|
|
166
|
+
const output = await (0, paneKill_1.paneKillCommand)(target, {
|
|
167
|
+
json: options.json,
|
|
168
|
+
session: options.session,
|
|
169
|
+
window: options.window
|
|
170
|
+
});
|
|
171
|
+
if (output) {
|
|
172
|
+
writeLine(process.stdout, output);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
program.command("snapshot")
|
|
176
|
+
.option("--json [boolean]", "output json", parseOptionalBoolean, false)
|
|
177
|
+
.option("--session <session>", "tmux session name")
|
|
178
|
+
.option("--window <window>", "tmux window target")
|
|
179
|
+
.action(async (options) => {
|
|
180
|
+
const output = await (0, snapshot_1.snapshot)({
|
|
181
|
+
json: options.json,
|
|
182
|
+
session: options.session,
|
|
183
|
+
window: options.window
|
|
184
|
+
});
|
|
185
|
+
if (output) {
|
|
186
|
+
writeLine(process.stdout, output);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
program.command("find")
|
|
190
|
+
.argument("<query>")
|
|
191
|
+
.option("--json [boolean]", "output json", parseOptionalBoolean, false)
|
|
192
|
+
.option("--session <session>", "tmux session name")
|
|
193
|
+
.option("--window <window>", "tmux window target")
|
|
194
|
+
.action(async (query, options) => {
|
|
195
|
+
const output = await (0, find_1.find)(query, {
|
|
196
|
+
json: options.json,
|
|
197
|
+
session: options.session,
|
|
198
|
+
window: options.window
|
|
199
|
+
});
|
|
200
|
+
if (output) {
|
|
201
|
+
writeLine(process.stdout, output);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
program.command("send")
|
|
205
|
+
.argument("<paneTarget>")
|
|
206
|
+
.argument("<text>")
|
|
207
|
+
.option("--json [boolean]", "output json", parseOptionalBoolean, false)
|
|
208
|
+
.option("--session <session>", "tmux session name")
|
|
209
|
+
.option("--window <window>", "tmux window target")
|
|
210
|
+
.option("--no-enter [boolean]", "do not submit Enter", parseOptionalBoolean, false)
|
|
211
|
+
.option("--enter-delay-ms <ms>", "delay before submitting Enter", parseNonNegativeNumber("enter-delay-ms"))
|
|
212
|
+
.action(async (target, text, options) => {
|
|
213
|
+
const output = await (0, send_1.send)(target, text, {
|
|
214
|
+
json: options.json,
|
|
215
|
+
session: options.session,
|
|
216
|
+
window: options.window,
|
|
217
|
+
noEnter: options.noEnter,
|
|
218
|
+
enterDelayMs: options.enterDelayMs
|
|
219
|
+
});
|
|
220
|
+
if (output) {
|
|
221
|
+
writeLine(process.stdout, output);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
program.command("read")
|
|
225
|
+
.argument("<paneTarget>")
|
|
226
|
+
.option("--json [boolean]", "output json", parseOptionalBoolean, false)
|
|
227
|
+
.option("--session <session>", "tmux session name")
|
|
228
|
+
.option("--window <window>", "tmux window target")
|
|
229
|
+
.option("--lines <n>", "lines to capture", parseNonNegativeNumber("lines"))
|
|
230
|
+
.action(async (target, options) => {
|
|
231
|
+
const output = await (0, read_1.read)(target, {
|
|
232
|
+
json: options.json,
|
|
233
|
+
session: options.session,
|
|
234
|
+
window: options.window,
|
|
235
|
+
lines: options.lines
|
|
236
|
+
});
|
|
237
|
+
if (output) {
|
|
238
|
+
writeLine(process.stdout, output);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
const codex = program.command("codex");
|
|
242
|
+
codex.command("send")
|
|
243
|
+
.argument("<paneTarget>")
|
|
244
|
+
.argument("<text>")
|
|
245
|
+
.option("--json [boolean]", "output json", parseOptionalBoolean, false)
|
|
246
|
+
.option("--session <session>", "tmux session name")
|
|
247
|
+
.option("--window <window>", "tmux window target")
|
|
248
|
+
.option("--submit <key>", "submit key (default Enter; use 'none' to disable)")
|
|
249
|
+
.option("--submit-delay-ms <ms>", "delay before submitting", parseNonNegativeNumber("submit-delay-ms"))
|
|
250
|
+
.option("--post-delay-ms <ms>", "delay after submit", parseNonNegativeNumber("post-delay-ms"))
|
|
251
|
+
.option("--capture-tail <n>", "capture tail lines (prints captured tail when not --json)", parseNonNegativeNumber("capture-tail"))
|
|
252
|
+
.action(async (target, text, options) => {
|
|
253
|
+
const output = await (0, send_2.codexSendCommand)(target, text, {
|
|
254
|
+
json: options.json,
|
|
255
|
+
session: options.session,
|
|
256
|
+
window: options.window,
|
|
257
|
+
submit: options.submit,
|
|
258
|
+
submitDelayMs: options.submitDelayMs,
|
|
259
|
+
postDelayMs: options.postDelayMs,
|
|
260
|
+
captureTail: options.captureTail
|
|
261
|
+
});
|
|
262
|
+
if (output) {
|
|
263
|
+
writeLine(process.stdout, output);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
codex.command("session-info")
|
|
267
|
+
.argument("<paneTarget>")
|
|
268
|
+
.option("--json [boolean]", "output json", parseOptionalBoolean, false)
|
|
269
|
+
.option("--session <session>", "tmux session name")
|
|
270
|
+
.option("--window <window>", "tmux window target")
|
|
271
|
+
.action(async (target, options) => {
|
|
272
|
+
const output = await (0, sessionInfo_1.codexSessionInfoCommand)(target, {
|
|
273
|
+
json: options.json,
|
|
274
|
+
session: options.session,
|
|
275
|
+
window: options.window
|
|
276
|
+
});
|
|
277
|
+
if (output) {
|
|
278
|
+
writeLine(process.stdout, output);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
const forkHome = codex.command("fork-home");
|
|
282
|
+
forkHome.command("prepare")
|
|
283
|
+
.option("--json [boolean]", "output json", parseOptionalBoolean, false)
|
|
284
|
+
.option("--parent-rollout <path>", "parent rollout path")
|
|
285
|
+
.option("--fork-home <path>", "fork CODEX_HOME path")
|
|
286
|
+
.option("--parent-codex-home <path>", "parent CODEX_HOME override")
|
|
287
|
+
.option("--run-id <id>", "run id for naming")
|
|
288
|
+
.option("--copy-config [boolean]", "copy config.toml", parseOptionalBoolean)
|
|
289
|
+
.option("--spec <spec>", "read json spec from stdin (use '-')")
|
|
290
|
+
.action(async (options) => {
|
|
291
|
+
const output = await (0, forkHome_1.codexForkHomePrepareCommand)({
|
|
292
|
+
json: options.json,
|
|
293
|
+
parentRollout: options.parentRollout,
|
|
294
|
+
forkHome: options.forkHome,
|
|
295
|
+
parentCodexHome: options.parentCodexHome,
|
|
296
|
+
runId: options.runId,
|
|
297
|
+
copyConfig: options.copyConfig,
|
|
298
|
+
spec: options.spec
|
|
299
|
+
});
|
|
300
|
+
if (output) {
|
|
301
|
+
writeLine(process.stdout, output);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
forkHome.command("cleanup")
|
|
305
|
+
.option("--json [boolean]", "output json", parseOptionalBoolean, false)
|
|
306
|
+
.option("--path <path>", "path to remove")
|
|
307
|
+
.option("--allowed-root <root>", "allowed root guard")
|
|
308
|
+
.option("--spec <spec>", "read json spec from stdin (use '-')")
|
|
309
|
+
.action(async (options) => {
|
|
310
|
+
const output = await (0, forkHome_1.codexForkHomeCleanupCommand)({
|
|
311
|
+
json: options.json,
|
|
312
|
+
path: options.path,
|
|
313
|
+
allowedRoot: options.allowedRoot,
|
|
314
|
+
spec: options.spec
|
|
315
|
+
});
|
|
316
|
+
if (output) {
|
|
317
|
+
writeLine(process.stdout, output);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
codex.command("spawn")
|
|
321
|
+
.option("--json [boolean]", "output json", parseOptionalBoolean, false)
|
|
322
|
+
.option("--origin <paneTarget>", "origin pane target")
|
|
323
|
+
.option("--session <session>", "tmux session name")
|
|
324
|
+
.option("--window <window>", "tmux window target")
|
|
325
|
+
.option("--force-simple-split [boolean]", "force fallback split", parseOptionalBoolean, false)
|
|
326
|
+
.action(async (options) => {
|
|
327
|
+
const output = await (0, spawn_1.codexSpawnCommand)({
|
|
328
|
+
json: options.json,
|
|
329
|
+
origin: options.origin,
|
|
330
|
+
session: options.session,
|
|
331
|
+
window: options.window,
|
|
332
|
+
forceSimpleSplit: options.forceSimpleSplit
|
|
333
|
+
});
|
|
334
|
+
if (output) {
|
|
335
|
+
writeLine(process.stdout, output);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
const ui = program.command("ui");
|
|
339
|
+
ui.command("select")
|
|
340
|
+
.requiredOption("--spec <spec>", "read json spec from stdin (use '-')")
|
|
341
|
+
.option("--json [boolean]", "output json", parseOptionalBoolean, false)
|
|
342
|
+
.action(async (options) => {
|
|
343
|
+
const output = await (0, select_1.uiSelectCommand)({ json: options.json, spec: options.spec });
|
|
344
|
+
if (output) {
|
|
345
|
+
writeLine(process.stdout, output);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
return program;
|
|
349
|
+
}
|
|
350
|
+
async function run() {
|
|
351
|
+
const argv = process.argv.slice(2);
|
|
352
|
+
if (argv.length === 0) {
|
|
353
|
+
throw new Error("missing command");
|
|
354
|
+
}
|
|
355
|
+
const program = buildProgram();
|
|
356
|
+
await program.parseAsync(process.argv);
|
|
357
|
+
}
|
|
358
|
+
run().catch((error) => {
|
|
359
|
+
if (error instanceof commander_1.CommanderError && error.exitCode === 0) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const message = formatError(error);
|
|
363
|
+
writeLine(process.stderr, message);
|
|
364
|
+
process.exitCode = 1;
|
|
365
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseOptions = parseOptions;
|
|
4
|
+
function parseOptions(argv, spec) {
|
|
5
|
+
const options = {};
|
|
6
|
+
const positionals = [];
|
|
7
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
8
|
+
const arg = argv[i];
|
|
9
|
+
if (arg === "--") {
|
|
10
|
+
positionals.push(...argv.slice(i + 1));
|
|
11
|
+
break;
|
|
12
|
+
}
|
|
13
|
+
if (arg.startsWith("--")) {
|
|
14
|
+
const [rawKey, inlineValue] = arg.split("=");
|
|
15
|
+
const key = rawKey.slice(2);
|
|
16
|
+
const specType = spec[key];
|
|
17
|
+
if (!specType) {
|
|
18
|
+
throw new Error(`unknown option --${key}`);
|
|
19
|
+
}
|
|
20
|
+
if (specType === "boolean") {
|
|
21
|
+
options[key] = inlineValue ? inlineValue !== "false" : true;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (inlineValue !== undefined) {
|
|
25
|
+
options[key] = inlineValue;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const next = argv[i + 1];
|
|
29
|
+
if (!next || next.startsWith("--")) {
|
|
30
|
+
throw new Error(`missing value for --${key}`);
|
|
31
|
+
}
|
|
32
|
+
options[key] = next;
|
|
33
|
+
i += 1;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
positionals.push(arg);
|
|
37
|
+
}
|
|
38
|
+
return { options, positionals };
|
|
39
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.prepareForkHome = prepareForkHome;
|
|
7
|
+
exports.cleanupForkHome = cleanupForkHome;
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const safeRm_1 = require("../fs/safeRm");
|
|
11
|
+
function resolvePath(value) {
|
|
12
|
+
return node_path_1.default.resolve(value);
|
|
13
|
+
}
|
|
14
|
+
function defaultCodexHome() {
|
|
15
|
+
const env = process.env.CODEX_HOME?.trim();
|
|
16
|
+
if (env) {
|
|
17
|
+
return resolvePath(env);
|
|
18
|
+
}
|
|
19
|
+
return resolvePath(node_path_1.default.join(process.env.HOME || "", ".codex"));
|
|
20
|
+
}
|
|
21
|
+
async function copyOrSymlink(src, dst) {
|
|
22
|
+
try {
|
|
23
|
+
await node_fs_1.promises.stat(src);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return "missing";
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
await node_fs_1.promises.mkdir(node_path_1.default.dirname(dst), { recursive: true });
|
|
30
|
+
try {
|
|
31
|
+
await node_fs_1.promises.unlink(dst);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// ignore
|
|
35
|
+
}
|
|
36
|
+
await node_fs_1.promises.symlink(src, dst);
|
|
37
|
+
return "symlink";
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
try {
|
|
41
|
+
await node_fs_1.promises.copyFile(src, dst);
|
|
42
|
+
return "copy";
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return "failed";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function resolveForkRolloutPath(parentCodexHome, parentRolloutPath, forkHome, runId) {
|
|
50
|
+
try {
|
|
51
|
+
const relative = node_path_1.default.relative(parentCodexHome, parentRolloutPath);
|
|
52
|
+
if (!relative.startsWith("..") && !node_path_1.default.isAbsolute(relative)) {
|
|
53
|
+
return node_path_1.default.join(forkHome, relative);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// ignore
|
|
58
|
+
}
|
|
59
|
+
return node_path_1.default.join(forkHome, "sessions", `rollout-${runId}.jsonl`);
|
|
60
|
+
}
|
|
61
|
+
async function prepareForkHome(options) {
|
|
62
|
+
const parentCodexHome = resolvePath(options.parentCodexHome || defaultCodexHome());
|
|
63
|
+
const parentRolloutPath = resolvePath(options.parentRolloutPath);
|
|
64
|
+
const forkHome = resolvePath(options.forkHome);
|
|
65
|
+
const runId = options.runId?.trim() || String(process.pid);
|
|
66
|
+
const copyConfig = options.copyConfig ?? true;
|
|
67
|
+
const stat = await node_fs_1.promises.stat(parentRolloutPath);
|
|
68
|
+
if (!stat.isFile()) {
|
|
69
|
+
throw new Error(`parent_rollout_path is not a file: ${parentRolloutPath}`);
|
|
70
|
+
}
|
|
71
|
+
await node_fs_1.promises.mkdir(forkHome, { recursive: true });
|
|
72
|
+
let copiedConfig = false;
|
|
73
|
+
if (copyConfig) {
|
|
74
|
+
const configPath = node_path_1.default.join(parentCodexHome, "config.toml");
|
|
75
|
+
try {
|
|
76
|
+
await node_fs_1.promises.copyFile(configPath, node_path_1.default.join(forkHome, "config.toml"));
|
|
77
|
+
copiedConfig = true;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
copiedConfig = false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const authStrategy = await copyOrSymlink(node_path_1.default.join(parentCodexHome, "auth.json"), node_path_1.default.join(forkHome, "auth.json"));
|
|
84
|
+
const credentialsStrategy = await copyOrSymlink(node_path_1.default.join(parentCodexHome, ".credentials.json"), node_path_1.default.join(forkHome, ".credentials.json"));
|
|
85
|
+
const forkRolloutPath = resolveForkRolloutPath(parentCodexHome, parentRolloutPath, forkHome, runId);
|
|
86
|
+
await node_fs_1.promises.mkdir(node_path_1.default.dirname(forkRolloutPath), { recursive: true });
|
|
87
|
+
await node_fs_1.promises.copyFile(parentRolloutPath, forkRolloutPath);
|
|
88
|
+
return {
|
|
89
|
+
action: "prepare",
|
|
90
|
+
parent_rollout_path: parentRolloutPath,
|
|
91
|
+
fork_home: forkHome,
|
|
92
|
+
fork_rollout_path: forkRolloutPath,
|
|
93
|
+
copied_config: copiedConfig,
|
|
94
|
+
auth_strategy: authStrategy,
|
|
95
|
+
credentials_strategy: credentialsStrategy
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
async function cleanupForkHome(pathToRemove, allowedRoot) {
|
|
99
|
+
const removed = await (0, safeRm_1.safeRemove)(pathToRemove, allowedRoot);
|
|
100
|
+
return { action: "cleanup", removed };
|
|
101
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isCodexPane = isCodexPane;
|
|
4
|
+
const ps_1 = require("../proc/ps");
|
|
5
|
+
const CODEX_PATTERN = /codex/i;
|
|
6
|
+
function matchesCodex(value) {
|
|
7
|
+
return CODEX_PATTERN.test(value);
|
|
8
|
+
}
|
|
9
|
+
function hasCodexDescendant(processes, rootPid) {
|
|
10
|
+
const byParent = new Map();
|
|
11
|
+
for (const process of processes) {
|
|
12
|
+
const list = byParent.get(process.ppid);
|
|
13
|
+
if (list) {
|
|
14
|
+
list.push(process);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
byParent.set(process.ppid, [process]);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const queue = [rootPid];
|
|
21
|
+
const visited = new Set();
|
|
22
|
+
while (queue.length) {
|
|
23
|
+
const current = queue.shift();
|
|
24
|
+
if (current === undefined || visited.has(current)) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
visited.add(current);
|
|
28
|
+
const children = byParent.get(current);
|
|
29
|
+
if (!children) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
for (const child of children) {
|
|
33
|
+
if (matchesCodex(child.command)) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
queue.push(child.pid);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
async function isCodexPane(pane) {
|
|
42
|
+
if (matchesCodex(pane.command) || matchesCodex(pane.title)) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
if (!pane.pid || !Number.isFinite(pane.pid)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const processes = await (0, ps_1.listProcesses)();
|
|
50
|
+
return hasCodexDescendant(processes, pane.pid);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.codexSend = codexSend;
|
|
4
|
+
const capturePane_1 = require("../tmux/capturePane");
|
|
5
|
+
const exec_1 = require("../tmux/exec");
|
|
6
|
+
const paste_1 = require("../tmux/paste");
|
|
7
|
+
const sendKeys_1 = require("../tmux/sendKeys");
|
|
8
|
+
function sleep(ms) {
|
|
9
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10
|
+
}
|
|
11
|
+
function normalizeTmuxKey(key) {
|
|
12
|
+
const parsed = (0, sendKeys_1.parseKeyExpression)(key);
|
|
13
|
+
return parsed ?? key;
|
|
14
|
+
}
|
|
15
|
+
async function codexSend(paneId, input, options = {}) {
|
|
16
|
+
const submitKey = options.submitKey?.trim() ?? "";
|
|
17
|
+
const submitDelayMs = Math.max(0, options.submitDelayMs ?? 0);
|
|
18
|
+
const postDelayMs = Math.max(0, options.postDelayMs ?? 0);
|
|
19
|
+
const captureTailLines = options.captureTailLines;
|
|
20
|
+
const tmuxKey = (0, sendKeys_1.parseKeyExpression)(input);
|
|
21
|
+
if (tmuxKey) {
|
|
22
|
+
await (0, exec_1.tmuxExec)(["send-keys", "-t", paneId, tmuxKey]);
|
|
23
|
+
if (postDelayMs > 0) {
|
|
24
|
+
await sleep(postDelayMs);
|
|
25
|
+
}
|
|
26
|
+
const capture_tail = captureTailLines && captureTailLines > 0
|
|
27
|
+
? await (0, capturePane_1.capturePane)(paneId, captureTailLines)
|
|
28
|
+
: undefined;
|
|
29
|
+
return {
|
|
30
|
+
mode: "keys",
|
|
31
|
+
...(capture_tail ? { capture_tail } : {})
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
await (0, paste_1.pasteText)(paneId, input);
|
|
35
|
+
if (submitKey) {
|
|
36
|
+
if (submitDelayMs > 0) {
|
|
37
|
+
await sleep(submitDelayMs);
|
|
38
|
+
}
|
|
39
|
+
await (0, exec_1.tmuxExec)(["send-keys", "-t", paneId, normalizeTmuxKey(submitKey)]);
|
|
40
|
+
}
|
|
41
|
+
if (postDelayMs > 0) {
|
|
42
|
+
await sleep(postDelayMs);
|
|
43
|
+
}
|
|
44
|
+
const capture_tail = captureTailLines && captureTailLines > 0
|
|
45
|
+
? await (0, capturePane_1.capturePane)(paneId, captureTailLines)
|
|
46
|
+
: undefined;
|
|
47
|
+
return {
|
|
48
|
+
mode: "text",
|
|
49
|
+
...(submitKey
|
|
50
|
+
? {
|
|
51
|
+
submit: submitKey,
|
|
52
|
+
submit_delay_ms: submitDelayMs
|
|
53
|
+
}
|
|
54
|
+
: {}),
|
|
55
|
+
...(postDelayMs > 0 ? { post_delay_ms: postDelayMs } : {}),
|
|
56
|
+
...(capture_tail ? { capture_tail } : {})
|
|
57
|
+
};
|
|
58
|
+
}
|