tau-coding-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/LICENSE +21 -0
- package/README.md +42 -0
- package/extensions/answer.ts +601 -0
- package/extensions/branch-term.ts +405 -0
- package/extensions/btw.ts +444 -0
- package/extensions/ghostty.ts +301 -0
- package/extensions/git-diff-stats.ts +277 -0
- package/extensions/git-pr-status.ts +286 -0
- package/extensions/insights.ts +2367 -0
- package/extensions/interlude.ts +144 -0
- package/extensions/loop.ts +529 -0
- package/extensions/memory.ts +1889 -0
- package/extensions/notify.ts +161 -0
- package/extensions/openai-fast.ts +227 -0
- package/extensions/openai-verbosity.ts +223 -0
- package/extensions/review.ts +4347 -0
- package/extensions/sandbox/index.ts +2578 -0
- package/extensions/usage/anthropic.ts +198 -0
- package/extensions/usage/github-copilot.ts +204 -0
- package/extensions/usage/google-gemini-cli.ts +232 -0
- package/extensions/usage/index.ts +2310 -0
- package/extensions/usage/minimax.ts +208 -0
- package/extensions/usage/openai-codex.ts +180 -0
- package/extensions/usage/openrouter.ts +168 -0
- package/extensions/usage/providers.ts +26 -0
- package/extensions/usage/shared.ts +156 -0
- package/extensions/usage/types.ts +53 -0
- package/extensions/usage/zai.ts +186 -0
- package/extensions/websearch/README.md +68 -0
- package/extensions/websearch/browser/chromium.ts +330 -0
- package/extensions/websearch/browser/discovery.ts +75 -0
- package/extensions/websearch/browser/firefox.ts +150 -0
- package/extensions/websearch/browser/sqlite.ts +38 -0
- package/extensions/websearch/config.ts +76 -0
- package/extensions/websearch/index.ts +312 -0
- package/extensions/websearch/normalize.ts +155 -0
- package/extensions/websearch/providers/anthropic.pi.ts +139 -0
- package/extensions/websearch/providers/gemini.browser.ts +185 -0
- package/extensions/websearch/providers/gemini.pi.ts +108 -0
- package/extensions/websearch/providers/openai-codex.browser.ts +77 -0
- package/extensions/websearch/providers/openai-codex.pi.ts +49 -0
- package/extensions/websearch/providers/openai-codex.shared.ts +123 -0
- package/extensions/websearch/providers/pi-model.shared.ts +86 -0
- package/extensions/websearch/providers/search-prompt.shared.ts +17 -0
- package/extensions/websearch/providers/shared.ts +76 -0
- package/extensions/websearch/types.ts +52 -0
- package/extensions/worktree.ts +2223 -0
- package/package.json +72 -0
- package/skills/browser-tools/SKILL.md +253 -0
- package/skills/browser-tools/scripts/browser-content.js +100 -0
- package/skills/browser-tools/scripts/browser-cookies.js +33 -0
- package/skills/browser-tools/scripts/browser-dismiss-cookies.js +455 -0
- package/skills/browser-tools/scripts/browser-eval.js +75 -0
- package/skills/browser-tools/scripts/browser-logs-tail.js +89 -0
- package/skills/browser-tools/scripts/browser-nav.js +28 -0
- package/skills/browser-tools/scripts/browser-net-summary.js +115 -0
- package/skills/browser-tools/scripts/browser-pick.js +133 -0
- package/skills/browser-tools/scripts/browser-screenshot.js +18 -0
- package/skills/browser-tools/scripts/browser-start.js +281 -0
- package/skills/browser-tools/scripts/browser-watch.js +321 -0
- package/skills/browser-tools/scripts/utils.js +60 -0
- package/skills/git-clean-history/SKILL.md +45 -0
- package/skills/git-commit/SKILL.md +55 -0
- package/skills/oracle/SKILL.md +64 -0
- package/skills/oracle/scripts/oracle +249 -0
- package/skills/oracle/scripts/oracle-bundle +227 -0
- package/skills/sentry/SKILL.md +198 -0
- package/skills/sentry/lib/auth.js +99 -0
- package/skills/sentry/scripts/fetch-event.js +325 -0
- package/skills/sentry/scripts/fetch-issue.js +368 -0
- package/skills/sentry/scripts/list-issues.js +245 -0
- package/skills/sentry/scripts/search-events.js +291 -0
- package/skills/sentry/scripts/search-logs.js +234 -0
- package/skills/update-changelog/SKILL.md +135 -0
- package/skills/web-design/SKILL.md +117 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
6
|
+
import {
|
|
7
|
+
CURRENT_SESSION_VERSION,
|
|
8
|
+
SessionManager,
|
|
9
|
+
highlightCode,
|
|
10
|
+
type SessionHeader,
|
|
11
|
+
} from "@mariozechner/pi-coding-agent";
|
|
12
|
+
import { Box, Text } from "@mariozechner/pi-tui";
|
|
13
|
+
|
|
14
|
+
const TERMINAL_FLAG = "branch-term";
|
|
15
|
+
const TMUX_LAYOUT_FLAG = "branch-tmux-layout";
|
|
16
|
+
const BRANCH_MESSAGE_TYPE = "branch-term-message";
|
|
17
|
+
const MANUAL_RESUME_INTRO =
|
|
18
|
+
"Branch session ready. Run this command in a separate terminal or tmux pane";
|
|
19
|
+
|
|
20
|
+
type TmuxLayout = "window" | "split-right" | "split-down";
|
|
21
|
+
type CommandMessageDetails = {
|
|
22
|
+
intro: string;
|
|
23
|
+
command: string;
|
|
24
|
+
copiedToClipboard: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const TMUX_LAYOUT_CONFIG: Record<
|
|
28
|
+
TmuxLayout,
|
|
29
|
+
{
|
|
30
|
+
label: string;
|
|
31
|
+
commandArgs: (cwd: string, command: string) => string[];
|
|
32
|
+
}
|
|
33
|
+
> = {
|
|
34
|
+
window: {
|
|
35
|
+
label: "window",
|
|
36
|
+
commandArgs: (cwd, command) => ["new-window", "-c", cwd, "-n", "branch", command],
|
|
37
|
+
},
|
|
38
|
+
"split-right": {
|
|
39
|
+
label: "split (right)",
|
|
40
|
+
commandArgs: (cwd, command) => ["split-window", "-h", "-c", cwd, command],
|
|
41
|
+
},
|
|
42
|
+
"split-down": {
|
|
43
|
+
label: "split (down)",
|
|
44
|
+
commandArgs: (cwd, command) => ["split-window", "-v", "-c", cwd, command],
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function getStringFlag(pi: ExtensionAPI, flagName: string): string | undefined {
|
|
49
|
+
const value = pi.getFlag(`--${flagName}`);
|
|
50
|
+
if (typeof value !== "string") return undefined;
|
|
51
|
+
const trimmed = value.trim();
|
|
52
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseTmuxLayout(value: string | undefined): TmuxLayout | undefined {
|
|
56
|
+
if (!value) return undefined;
|
|
57
|
+
const normalized = value.trim().toLowerCase();
|
|
58
|
+
if (normalized in TMUX_LAYOUT_CONFIG) {
|
|
59
|
+
return normalized as TmuxLayout;
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseBranchArgs(args: string): { tmuxLayout?: TmuxLayout; error?: string } {
|
|
65
|
+
const tokens = args
|
|
66
|
+
.trim()
|
|
67
|
+
.split(/\s+/)
|
|
68
|
+
.map((token) => token.trim())
|
|
69
|
+
.filter(Boolean);
|
|
70
|
+
|
|
71
|
+
if (tokens.length === 0) return {};
|
|
72
|
+
|
|
73
|
+
if (tokens.length === 1) {
|
|
74
|
+
const shorthandLayout = parseTmuxLayout(tokens[0]);
|
|
75
|
+
if (shorthandLayout) return { tmuxLayout: shorthandLayout };
|
|
76
|
+
|
|
77
|
+
if (tokens[0].startsWith("layout=")) {
|
|
78
|
+
const explicitLayout = parseTmuxLayout(tokens[0].slice("layout=".length));
|
|
79
|
+
if (explicitLayout) return { tmuxLayout: explicitLayout };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
error:
|
|
85
|
+
"Usage: /branch [window|split-right|split-down] or /branch layout=<window|split-right|split-down>",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getBranchArgumentCompletions(
|
|
90
|
+
prefix: string,
|
|
91
|
+
): Array<{ value: string; label: string }> | null {
|
|
92
|
+
const trimmed = prefix.trim().toLowerCase();
|
|
93
|
+
if (trimmed.includes(" ")) return null;
|
|
94
|
+
|
|
95
|
+
const layouts = Object.keys(TMUX_LAYOUT_CONFIG) as TmuxLayout[];
|
|
96
|
+
const options = [
|
|
97
|
+
...layouts.map((layout) => ({ value: layout, label: layout })),
|
|
98
|
+
...layouts.map((layout) => ({ value: `layout=${layout}`, label: `layout=${layout}` })),
|
|
99
|
+
];
|
|
100
|
+
const matches = options.filter((option) => option.label.startsWith(trimmed));
|
|
101
|
+
if (!matches.length) return null;
|
|
102
|
+
|
|
103
|
+
return matches;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function renderTerminalCommand(template: string, cwd: string, sessionFile: string): string {
|
|
107
|
+
let command = template;
|
|
108
|
+
command = command.split("{cwd}").join(cwd);
|
|
109
|
+
|
|
110
|
+
if (command.includes("{command}")) {
|
|
111
|
+
const piCommand = `pi --session ${shellQuote(sessionFile)}`;
|
|
112
|
+
command = command.split("{command}").join(piCommand);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (command.includes("{session}")) {
|
|
116
|
+
command = command.split("{session}").join(sessionFile);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (template.includes("{command}") || template.includes("{session}")) {
|
|
120
|
+
return command;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return `${command} ${sessionFile}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function spawnDetached(command: string, args: string[], onError?: (error: Error) => void): void {
|
|
127
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
128
|
+
child.unref();
|
|
129
|
+
if (onError) child.on("error", onError);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function shellQuote(value: string): string {
|
|
133
|
+
if (value.length === 0) return "''";
|
|
134
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function escapeForDoubleQuotes(value: string): string {
|
|
138
|
+
return value.replace(/(["\\$`!])/g, "\\$1");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function shellQuoteCompact(value: string): string {
|
|
142
|
+
const home = process.env.HOME;
|
|
143
|
+
if (!home) return shellQuote(value);
|
|
144
|
+
|
|
145
|
+
if (value === home) return "$HOME";
|
|
146
|
+
|
|
147
|
+
if (value.startsWith(`${home}/`)) {
|
|
148
|
+
const suffix = value.slice(home.length + 1);
|
|
149
|
+
return `"$HOME/${escapeForDoubleQuotes(suffix)}"`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return shellQuote(value);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function parseSessionIdFromFile(sessionFile: string): string | undefined {
|
|
156
|
+
const fileName = path.basename(sessionFile, ".jsonl");
|
|
157
|
+
const separatorIndex = fileName.lastIndexOf("_");
|
|
158
|
+
if (separatorIndex <= 0) return undefined;
|
|
159
|
+
|
|
160
|
+
const candidate = fileName.slice(separatorIndex + 1);
|
|
161
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(candidate)
|
|
162
|
+
? candidate
|
|
163
|
+
: undefined;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function formatResumeSessionArgument(sessionFile: string): string {
|
|
167
|
+
const sessionId = parseSessionIdFromFile(sessionFile);
|
|
168
|
+
if (sessionId) return sessionId;
|
|
169
|
+
|
|
170
|
+
return shellQuoteCompact(sessionFile);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function runClipboardCommand(command: string, args: string[], text: string): boolean {
|
|
174
|
+
try {
|
|
175
|
+
const result = spawnSync(command, args, {
|
|
176
|
+
input: text,
|
|
177
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
178
|
+
timeout: 3000,
|
|
179
|
+
});
|
|
180
|
+
return result.status === 0;
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function copyToClipboard(text: string): boolean {
|
|
187
|
+
if (process.platform === "darwin") {
|
|
188
|
+
return runClipboardCommand("pbcopy", [], text);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (process.platform === "win32") {
|
|
192
|
+
return runClipboardCommand("clip", [], text);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (process.platform === "linux") {
|
|
196
|
+
return (
|
|
197
|
+
runClipboardCommand("wl-copy", [], text) ||
|
|
198
|
+
runClipboardCommand("xclip", ["-selection", "clipboard"], text) ||
|
|
199
|
+
runClipboardCommand("xsel", ["--clipboard", "--input"], text)
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function formatCommandMessageIntro(intro: string, copiedToClipboard: boolean): string {
|
|
207
|
+
return `${intro}${copiedToClipboard ? " (copied to clipboard)" : ""}:`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function showCommandMessage(
|
|
211
|
+
pi: ExtensionAPI,
|
|
212
|
+
ctx: ExtensionCommandContext,
|
|
213
|
+
intro: string,
|
|
214
|
+
command: string,
|
|
215
|
+
): void {
|
|
216
|
+
const copiedToClipboard = copyToClipboard(command);
|
|
217
|
+
if (!ctx.hasUI) {
|
|
218
|
+
console.log(formatCommandMessageIntro(intro, copiedToClipboard));
|
|
219
|
+
console.log(command);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
pi.sendMessage({
|
|
224
|
+
customType: BRANCH_MESSAGE_TYPE,
|
|
225
|
+
content: intro,
|
|
226
|
+
display: true,
|
|
227
|
+
details: {
|
|
228
|
+
intro,
|
|
229
|
+
command,
|
|
230
|
+
copiedToClipboard,
|
|
231
|
+
} satisfies CommandMessageDetails,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function hasValidSessionFile(sessionFile: string): boolean {
|
|
236
|
+
if (!fs.existsSync(sessionFile)) return false;
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
const firstLine = fs.readFileSync(sessionFile, "utf8").split("\n", 1)[0]?.trim();
|
|
240
|
+
if (!firstLine) return false;
|
|
241
|
+
|
|
242
|
+
const header = JSON.parse(firstLine) as Partial<SessionHeader>;
|
|
243
|
+
return header.type === "session" && typeof header.id === "string";
|
|
244
|
+
} catch {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function createFreshSessionFile(cwd: string, sessionDir: string): string {
|
|
250
|
+
fs.mkdirSync(sessionDir, { recursive: true });
|
|
251
|
+
|
|
252
|
+
const sessionId = randomUUID();
|
|
253
|
+
const timestamp = new Date().toISOString();
|
|
254
|
+
const fileTimestamp = timestamp.replace(/[:.]/g, "-");
|
|
255
|
+
const sessionFile = path.join(sessionDir, `${fileTimestamp}_${sessionId}.jsonl`);
|
|
256
|
+
const header: SessionHeader = {
|
|
257
|
+
type: "session",
|
|
258
|
+
version: CURRENT_SESSION_VERSION,
|
|
259
|
+
id: sessionId,
|
|
260
|
+
timestamp,
|
|
261
|
+
cwd,
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
fs.writeFileSync(sessionFile, `${JSON.stringify(header)}\n`);
|
|
265
|
+
return sessionFile;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export default function (pi: ExtensionAPI) {
|
|
269
|
+
pi.registerMessageRenderer(BRANCH_MESSAGE_TYPE, (message, _options, theme) => {
|
|
270
|
+
const details = message.details as CommandMessageDetails | undefined;
|
|
271
|
+
if (!details || typeof details.intro !== "string" || typeof details.command !== "string") {
|
|
272
|
+
return new Text(typeof message.content === "string" ? message.content : "", 0, 0);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
|
276
|
+
box.addChild(
|
|
277
|
+
new Text(
|
|
278
|
+
[
|
|
279
|
+
theme.fg(
|
|
280
|
+
"customMessageText",
|
|
281
|
+
formatCommandMessageIntro(details.intro, details.copiedToClipboard),
|
|
282
|
+
),
|
|
283
|
+
"",
|
|
284
|
+
highlightCode(details.command, "bash").join("\n"),
|
|
285
|
+
].join("\n"),
|
|
286
|
+
0,
|
|
287
|
+
0,
|
|
288
|
+
),
|
|
289
|
+
);
|
|
290
|
+
return box;
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
pi.registerFlag(TERMINAL_FLAG, {
|
|
294
|
+
description:
|
|
295
|
+
"Command to open a new terminal. Use {cwd} for working directory and optional {command} for the pi command.",
|
|
296
|
+
type: "string",
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
pi.registerFlag(TMUX_LAYOUT_FLAG, {
|
|
300
|
+
description:
|
|
301
|
+
"When inside tmux, choose where branch sessions open: window (default), split-right, or split-down.",
|
|
302
|
+
type: "string",
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
pi.registerCommand("branch", {
|
|
306
|
+
description: "Fork current session into tmux (window/split) or show a resume command",
|
|
307
|
+
getArgumentCompletions: getBranchArgumentCompletions,
|
|
308
|
+
handler: async (args, ctx) => {
|
|
309
|
+
const parsedArgs = parseBranchArgs(args);
|
|
310
|
+
if (parsedArgs.error) {
|
|
311
|
+
if (ctx.hasUI) ctx.ui.notify(parsedArgs.error, "warning");
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!ctx.isIdle()) {
|
|
316
|
+
if (ctx.hasUI) ctx.ui.notify("Queued /branch", "info");
|
|
317
|
+
await ctx.waitForIdle();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
321
|
+
if (!sessionFile) {
|
|
322
|
+
if (ctx.hasUI)
|
|
323
|
+
ctx.ui.notify("Session is not persisted. Restart without --no-session.", "error");
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const leafId = ctx.sessionManager.getLeafId();
|
|
328
|
+
const hasAssistantReply = ctx.sessionManager
|
|
329
|
+
.getEntries()
|
|
330
|
+
.some((entry) => entry.type === "message" && entry.message.role === "assistant");
|
|
331
|
+
|
|
332
|
+
let forkFile: string;
|
|
333
|
+
if (leafId && hasValidSessionFile(sessionFile)) {
|
|
334
|
+
const forkManager = SessionManager.open(sessionFile);
|
|
335
|
+
const branchedSessionFile = forkManager.createBranchedSession(leafId);
|
|
336
|
+
if (!branchedSessionFile) {
|
|
337
|
+
throw new Error("Failed to create branched session");
|
|
338
|
+
}
|
|
339
|
+
forkFile = branchedSessionFile;
|
|
340
|
+
} else {
|
|
341
|
+
if (hasAssistantReply) {
|
|
342
|
+
throw new Error(`Current session file is missing or invalid: ${sessionFile}`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const message = "Current session has no persisted history yet. Opening a fresh session.";
|
|
346
|
+
if (ctx.hasUI) ctx.ui.notify(message, "warning");
|
|
347
|
+
else console.log(message);
|
|
348
|
+
|
|
349
|
+
forkFile = createFreshSessionFile(ctx.cwd, ctx.sessionManager.getSessionDir());
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const resumeCommand = `cd ${shellQuoteCompact(ctx.cwd)} && pi --session ${formatResumeSessionArgument(forkFile)}`;
|
|
353
|
+
|
|
354
|
+
const terminalFlag = getStringFlag(pi, TERMINAL_FLAG);
|
|
355
|
+
if (terminalFlag) {
|
|
356
|
+
const command = renderTerminalCommand(terminalFlag, ctx.cwd, forkFile);
|
|
357
|
+
spawnDetached("bash", ["-lc", command], (error) => {
|
|
358
|
+
if (ctx.hasUI) {
|
|
359
|
+
ctx.ui.notify(`Terminal command failed: ${error.message}`, "error");
|
|
360
|
+
} else {
|
|
361
|
+
console.error(`Terminal command failed: ${error.message}`);
|
|
362
|
+
}
|
|
363
|
+
showCommandMessage(pi, ctx, MANUAL_RESUME_INTRO, resumeCommand);
|
|
364
|
+
});
|
|
365
|
+
if (ctx.hasUI) ctx.ui.notify("Opened fork in new terminal", "info");
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (process.env.TMUX) {
|
|
370
|
+
const rawTmuxLayout = getStringFlag(pi, TMUX_LAYOUT_FLAG);
|
|
371
|
+
const tmuxLayout =
|
|
372
|
+
parsedArgs.tmuxLayout ?? (rawTmuxLayout ? parseTmuxLayout(rawTmuxLayout) : "window");
|
|
373
|
+
|
|
374
|
+
if (!tmuxLayout) {
|
|
375
|
+
if (ctx.hasUI) {
|
|
376
|
+
ctx.ui.notify(
|
|
377
|
+
`Invalid --${TMUX_LAYOUT_FLAG}: ${rawTmuxLayout}. Using window. Valid values: window, split-right, split-down`,
|
|
378
|
+
"warning",
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const resolvedLayout = tmuxLayout ?? "window";
|
|
384
|
+
const layoutConfig = TMUX_LAYOUT_CONFIG[resolvedLayout];
|
|
385
|
+
const tmuxCommand = `pi --session ${shellQuote(forkFile)}`;
|
|
386
|
+
const result = await pi.exec("tmux", layoutConfig.commandArgs(ctx.cwd, tmuxCommand));
|
|
387
|
+
if (result.code !== 0) {
|
|
388
|
+
const details = result.stderr || result.stdout || "tmux command failed";
|
|
389
|
+
if (ctx.hasUI) {
|
|
390
|
+
ctx.ui.notify(`tmux failed: ${details}`, "warning");
|
|
391
|
+
} else {
|
|
392
|
+
console.error(`tmux failed: ${details}`);
|
|
393
|
+
}
|
|
394
|
+
showCommandMessage(pi, ctx, MANUAL_RESUME_INTRO, resumeCommand);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (ctx.hasUI) ctx.ui.notify(`Opened fork in new tmux ${layoutConfig.label}`, "info");
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
showCommandMessage(pi, ctx, MANUAL_RESUME_INTRO, resumeCommand);
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
}
|