tau-coding-agent 0.1.6 → 0.2.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/README.md +15 -12
- package/extensions/answer.ts +129 -79
- package/extensions/branch-term/README.md +7 -0
- package/extensions/{branch-term.ts → branch-term/index.ts} +113 -104
- package/extensions/btw.ts +17 -2
- package/extensions/caffeinate/README.md +5 -0
- package/extensions/caffeinate/index.ts +144 -0
- package/extensions/fast.ts +292 -0
- package/extensions/ghostty.ts +214 -211
- package/extensions/git-diff-stats.ts +124 -84
- package/extensions/git-pr-status.ts +274 -208
- package/extensions/insights.ts +109 -191
- package/extensions/loop.ts +149 -145
- package/extensions/memory.ts +138 -86
- package/extensions/notify.ts +14 -26
- package/extensions/openai-verbosity.ts +108 -42
- package/extensions/review/fix.ts +15 -6
- package/extensions/review/git.ts +93 -103
- package/extensions/review/index.ts +60 -62
- package/extensions/review/interrupt.ts +117 -0
- package/extensions/review/message-queue.ts +45 -12
- package/extensions/review/models.ts +79 -3
- package/extensions/review/prompts.ts +41 -38
- package/extensions/review/review.ts +161 -166
- package/extensions/review/runner.ts +139 -147
- package/extensions/review/runtime.ts +173 -120
- package/extensions/review/triage.ts +41 -44
- package/extensions/sandbox/bash.ts +775 -0
- package/extensions/sandbox/command.ts +605 -0
- package/extensions/sandbox/config.ts +764 -0
- package/extensions/sandbox/index.ts +132 -3338
- package/extensions/sandbox/permissions/dialog.ts +118 -0
- package/extensions/sandbox/permissions/filesystem.ts +559 -0
- package/extensions/sandbox/permissions/mach-lookup.ts +186 -0
- package/extensions/sandbox/permissions/network.ts +164 -0
- package/extensions/sandbox/permissions/unsandboxed.ts +270 -0
- package/extensions/sandbox/runtime.ts +616 -0
- package/extensions/stash.ts +28 -15
- package/extensions/subagent/README.md +73 -0
- package/extensions/subagent/index.ts +822 -0
- package/extensions/subagent/interrupt.ts +117 -0
- package/extensions/subagent/permissions.ts +101 -0
- package/extensions/subagent/rpc.ts +177 -0
- package/extensions/tool-display-mode.ts +267 -64
- package/extensions/usage/index.ts +249 -356
- package/extensions/usage/openrouter.ts +10 -2
- package/extensions/websearch/README.md +12 -47
- package/extensions/websearch/config.ts +5 -2
- package/extensions/websearch/index.ts +94 -109
- package/extensions/websearch/output.ts +51 -0
- package/extensions/websearch/providers/anthropic.pi.ts +27 -44
- package/extensions/websearch/providers/gemini.browser.ts +68 -77
- package/extensions/websearch/providers/gemini.pi.ts +17 -17
- package/extensions/websearch/providers/openai-codex.pi.ts +145 -7
- package/extensions/websearch/providers/pi-model.shared.ts +33 -39
- package/extensions/websearch/providers/shared.ts +87 -2
- package/extensions/websearch/types.ts +0 -3
- package/extensions/worktree.ts +132 -172
- package/package.json +10 -5
- package/skills/browser-tools/SKILL.md +29 -234
- package/skills/browser-tools/references/cookies.md +36 -0
- package/skills/browser-tools/references/interaction.md +90 -0
- package/skills/browser-tools/references/logging.md +34 -0
- package/skills/git-clean-history/SKILL.md +6 -6
- package/skills/git-commit/SKILL.md +5 -3
- package/skills/github-pull-request/SKILL.md +60 -0
- package/skills/github-pull-request/references/create.md +40 -0
- package/skills/github-pull-request/references/stewardship.md +60 -0
- package/skills/oracle/SKILL.md +3 -3
- package/skills/oracle/scripts/oracle +10 -1
- package/skills/sentry/SKILL.md +15 -185
- package/skills/sentry/references/events.md +79 -0
- package/skills/sentry/references/issues.md +62 -0
- package/skills/sentry/references/logs.md +46 -0
- package/skills/update-changelog/SKILL.md +27 -121
- package/skills/web-design/SKILL.md +16 -105
- package/themes/tau-dark.json +4 -0
- package/extensions/openai-fast.ts +0 -229
- package/extensions/websearch/providers/openai-codex.browser.ts +0 -77
- package/extensions/websearch/providers/openai-codex.shared.ts +0 -123
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ pi install -l npm:tau-coding-agent
|
|
|
23
23
|
| `answer` | `/answer` | Extract and interactively answer agent questions. |
|
|
24
24
|
| `branch-term` | `/branch` | Open a new terminal on the current session's git branch. |
|
|
25
25
|
| `btw` | `/btw` | Run a one-off side request with read-only tools and no context persistence. |
|
|
26
|
+
| `caffeinate` | — | Prevent system sleep while the agent runs. |
|
|
26
27
|
| `ghostty` | — | Ghostty tab title enhancements while the agent is working, waiting, or idle. |
|
|
27
28
|
| `git-diff-stats` | — | Status bar diff stats for local changes in the current repo. |
|
|
28
29
|
| `git-pr-status` | — | Status bar PR number and link for the current branch. |
|
|
@@ -31,26 +32,28 @@ pi install -l npm:tau-coding-agent
|
|
|
31
32
|
| `loop` | `/loop` | Repeat a prompt until the agent signals success. |
|
|
32
33
|
| `memory` | `/memory` | Opt-in project-local memory for learning and continuity across sessions. |
|
|
33
34
|
| `notify` | — | Terminal notification when the agent is waiting for input. |
|
|
34
|
-
| `
|
|
35
|
-
| `openai-verbosity` | `/verbosity` | Set verbosity for supported
|
|
35
|
+
| `fast` | `/fast` | Toggle fast processing for compatible models. |
|
|
36
|
+
| `openai-verbosity` | `/verbosity` | Set verbosity for supported GPT models. |
|
|
36
37
|
| `review` | `/review`, `/triage` | Multi-focus review and PR feedback triage for PRs, branches, commits, and local changes, with integrated follow-up fixes. |
|
|
37
38
|
| `sandbox` | `/sandbox` | OS-level sandboxing for bash commands with runtime overrides. |
|
|
38
|
-
| `
|
|
39
|
+
| `subagent` | — | Delegate work to background Pi agents and follow up with them. |
|
|
40
|
+
| `tool-display-mode` | `ctrl+o` | Cycle tool output modes and combine background activity in the composer. |
|
|
39
41
|
| `usage` | `/usage` | Historical provider usage breakdown with all-provider history and live quota snapshots. |
|
|
40
42
|
| `websearch` | — | Web search via Gemini, OpenAI, or Claude, leveraging Pi or browser session credentials. |
|
|
41
43
|
| `worktree` | `/worktree` | Create, list, and archive git worktrees, optionally opening them in a new terminal or tmux pane. |
|
|
42
44
|
|
|
43
45
|
## Skills
|
|
44
46
|
|
|
45
|
-
| Skill
|
|
46
|
-
|
|
|
47
|
-
| `browser-tools`
|
|
48
|
-
| `git-clean-history`
|
|
49
|
-
| `git-commit`
|
|
50
|
-
| `
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
47
|
+
| Skill | Description |
|
|
48
|
+
| --------------------- | ---------------------------------------------------------------------------------- |
|
|
49
|
+
| `browser-tools` | Interactive browser automation via Chrome DevTools Protocol. |
|
|
50
|
+
| `git-clean-history` | Reimplement a branch on a fresh branch off `main` with a clean commit history. |
|
|
51
|
+
| `git-commit` | Tidy, focused commits with clear rationale in messages. |
|
|
52
|
+
| `github-pull-request` | Create and steward pull requests through CI, review, and merge. |
|
|
53
|
+
| `oracle` | Second opinion from another LLM for debugging, refactors, design, or code reviews. |
|
|
54
|
+
| `sentry` | Fetch and analyze Sentry issues, events, transactions, and logs. |
|
|
55
|
+
| `update-changelog` | Update CHANGELOG.md following Keep a Changelog. |
|
|
56
|
+
| `web-design` | Distinctive, production-ready web interfaces. |
|
|
54
57
|
|
|
55
58
|
## Themes
|
|
56
59
|
|
package/extensions/answer.ts
CHANGED
|
@@ -10,9 +10,13 @@
|
|
|
10
10
|
* 4. Submits the compiled answers when done
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { complete } from "@earendil-works/pi-ai/compat";
|
|
14
13
|
import type { Model, Api, UserMessage } from "@earendil-works/pi-ai";
|
|
15
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
BorderedLoader,
|
|
16
|
+
type ExtensionAPI,
|
|
17
|
+
type ModelRegistry,
|
|
18
|
+
type Theme,
|
|
19
|
+
} from "@earendil-works/pi-coding-agent";
|
|
16
20
|
import {
|
|
17
21
|
type Component,
|
|
18
22
|
Editor,
|
|
@@ -26,6 +30,8 @@ import {
|
|
|
26
30
|
wrapTextWithAnsi,
|
|
27
31
|
} from "@earendil-works/pi-tui";
|
|
28
32
|
|
|
33
|
+
const MIN_QUESTIONNAIRE_WIDTH = 40;
|
|
34
|
+
|
|
29
35
|
const SYSTEM_PROMPT = `You extract items that need user input from assistant text:
|
|
30
36
|
|
|
31
37
|
- Direct questions
|
|
@@ -78,7 +84,7 @@ Example output:
|
|
|
78
84
|
"questions": []
|
|
79
85
|
}`;
|
|
80
86
|
|
|
81
|
-
const OPENAI_FAST_MODEL_ID = "gpt-5.
|
|
87
|
+
const OPENAI_FAST_MODEL_ID = "gpt-5.6-luna";
|
|
82
88
|
const ANTHROPIC_FAST_MODEL_ID = "claude-haiku-4-5";
|
|
83
89
|
|
|
84
90
|
// Structured output format for question extraction
|
|
@@ -91,25 +97,16 @@ interface ExtractionResult {
|
|
|
91
97
|
questions: ExtractedQuestion[];
|
|
92
98
|
}
|
|
93
99
|
|
|
94
|
-
|
|
100
|
+
type ExtractionOutcome =
|
|
101
|
+
| { status: "success"; result: ExtractionResult }
|
|
102
|
+
| { status: "cancelled" }
|
|
103
|
+
| { status: "error"; message: string };
|
|
104
|
+
|
|
105
|
+
interface ModelSelection {
|
|
95
106
|
model: Model<Api>;
|
|
96
|
-
apiKey?: string;
|
|
97
|
-
headers?: Record<string, string>;
|
|
98
|
-
env?: Record<string, string>;
|
|
99
107
|
}
|
|
100
108
|
|
|
101
|
-
type
|
|
102
|
-
find: (provider: string, modelId: string) => Model<Api> | undefined;
|
|
103
|
-
getApiKeyAndHeaders: (model: Model<Api>) => Promise<
|
|
104
|
-
| {
|
|
105
|
-
ok: true;
|
|
106
|
-
apiKey?: string;
|
|
107
|
-
headers?: Record<string, string>;
|
|
108
|
-
env?: Record<string, string>;
|
|
109
|
-
}
|
|
110
|
-
| { ok: false; error: string }
|
|
111
|
-
>;
|
|
112
|
-
};
|
|
109
|
+
type ModelLookup = Pick<ModelRegistry, "find" | "getApiKeyAndHeaders">;
|
|
113
110
|
|
|
114
111
|
type ModelFamily = "openai" | "anthropic";
|
|
115
112
|
|
|
@@ -125,10 +122,10 @@ function detectModelFamily(provider: string): ModelFamily | null {
|
|
|
125
122
|
*/
|
|
126
123
|
async function selectExtractionModel(
|
|
127
124
|
currentModel: Model<Api>,
|
|
128
|
-
modelRegistry:
|
|
129
|
-
): Promise<
|
|
125
|
+
modelRegistry: ModelLookup,
|
|
126
|
+
): Promise<ModelSelection | null> {
|
|
130
127
|
const family = detectModelFamily(currentModel.provider);
|
|
131
|
-
if (!family) return
|
|
128
|
+
if (!family) return resolveModelSelection(modelRegistry, currentModel);
|
|
132
129
|
|
|
133
130
|
const modelId = family === "openai" ? OPENAI_FAST_MODEL_ID : ANTHROPIC_FAST_MODEL_ID;
|
|
134
131
|
const providerCandidates =
|
|
@@ -140,21 +137,21 @@ async function selectExtractionModel(
|
|
|
140
137
|
const model = modelRegistry.find(provider, modelId);
|
|
141
138
|
if (!model) continue;
|
|
142
139
|
|
|
143
|
-
const
|
|
144
|
-
if (
|
|
145
|
-
return
|
|
140
|
+
const selection = await resolveModelSelection(modelRegistry, model);
|
|
141
|
+
if (selection) {
|
|
142
|
+
return selection;
|
|
146
143
|
}
|
|
147
144
|
}
|
|
148
145
|
|
|
149
|
-
return
|
|
146
|
+
return resolveModelSelection(modelRegistry, currentModel);
|
|
150
147
|
}
|
|
151
148
|
|
|
152
|
-
async function
|
|
153
|
-
modelRegistry:
|
|
149
|
+
async function resolveModelSelection(
|
|
150
|
+
modelRegistry: ModelLookup,
|
|
154
151
|
model: Model<Api>,
|
|
155
|
-
): Promise<
|
|
152
|
+
): Promise<ModelSelection | null> {
|
|
156
153
|
const auth = await modelRegistry.getApiKeyAndHeaders(model);
|
|
157
|
-
return auth.ok ? { model
|
|
154
|
+
return auth.ok ? { model } : null;
|
|
158
155
|
}
|
|
159
156
|
|
|
160
157
|
/**
|
|
@@ -171,11 +168,25 @@ function parseExtractionResult(text: string): ExtractionResult | null {
|
|
|
171
168
|
jsonStr = jsonMatch[1].trim();
|
|
172
169
|
}
|
|
173
170
|
|
|
174
|
-
const parsed = JSON.parse(jsonStr);
|
|
175
|
-
if (
|
|
176
|
-
|
|
171
|
+
const parsed: unknown = JSON.parse(jsonStr);
|
|
172
|
+
if (
|
|
173
|
+
!parsed ||
|
|
174
|
+
typeof parsed !== "object" ||
|
|
175
|
+
!("questions" in parsed) ||
|
|
176
|
+
!Array.isArray(parsed.questions) ||
|
|
177
|
+
!parsed.questions.every(
|
|
178
|
+
(question: unknown): question is ExtractedQuestion =>
|
|
179
|
+
question !== null &&
|
|
180
|
+
typeof question === "object" &&
|
|
181
|
+
"question" in question &&
|
|
182
|
+
typeof question.question === "string" &&
|
|
183
|
+
question.question.trim().length > 0 &&
|
|
184
|
+
(!("context" in question) || typeof question.context === "string"),
|
|
185
|
+
)
|
|
186
|
+
) {
|
|
187
|
+
return null;
|
|
177
188
|
}
|
|
178
|
-
return
|
|
189
|
+
return { questions: parsed.questions };
|
|
179
190
|
} catch {
|
|
180
191
|
return null;
|
|
181
192
|
}
|
|
@@ -195,6 +206,7 @@ class QnAComponent implements Component, Focusable {
|
|
|
195
206
|
private showingConfirmation: boolean = false;
|
|
196
207
|
|
|
197
208
|
// Cache
|
|
209
|
+
private renderWidth?: number;
|
|
198
210
|
private cachedWidth?: number;
|
|
199
211
|
private cachedLines?: string[];
|
|
200
212
|
|
|
@@ -244,7 +256,7 @@ class QnAComponent implements Component, Focusable {
|
|
|
244
256
|
}
|
|
245
257
|
|
|
246
258
|
private saveCurrentAnswer(): void {
|
|
247
|
-
this.answers[this.currentIndex] = this.editor.
|
|
259
|
+
this.answers[this.currentIndex] = this.editor.getExpandedText();
|
|
248
260
|
}
|
|
249
261
|
|
|
250
262
|
private navigateTo(index: number): void {
|
|
@@ -287,6 +299,11 @@ class QnAComponent implements Component, Focusable {
|
|
|
287
299
|
}
|
|
288
300
|
|
|
289
301
|
handleInput(data: string): void {
|
|
302
|
+
if ((this.renderWidth ?? this.tui.terminal.columns) < MIN_QUESTIONNAIRE_WIDTH) {
|
|
303
|
+
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) this.cancel();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
290
307
|
// Handle confirmation dialog
|
|
291
308
|
if (this.showingConfirmation) {
|
|
292
309
|
if (matchesKey(data, Key.enter) || data.toLowerCase() === "y") {
|
|
@@ -368,6 +385,19 @@ class QnAComponent implements Component, Focusable {
|
|
|
368
385
|
}
|
|
369
386
|
|
|
370
387
|
render(width: number): string[] {
|
|
388
|
+
this.renderWidth = width;
|
|
389
|
+
if (width < MIN_QUESTIONNAIRE_WIDTH) {
|
|
390
|
+
return [
|
|
391
|
+
truncateToWidth(
|
|
392
|
+
this.theme.fg(
|
|
393
|
+
"muted",
|
|
394
|
+
`Resize to ${MIN_QUESTIONNAIRE_WIDTH}+ columns to answer. Esc cancels.`,
|
|
395
|
+
),
|
|
396
|
+
width,
|
|
397
|
+
),
|
|
398
|
+
];
|
|
399
|
+
}
|
|
400
|
+
|
|
371
401
|
if (this.cachedLines && this.cachedWidth === width) {
|
|
372
402
|
return this.cachedLines;
|
|
373
403
|
}
|
|
@@ -416,7 +446,9 @@ class QnAComponent implements Component, Focusable {
|
|
|
416
446
|
progressParts.push(theme.fg("dim", "○"));
|
|
417
447
|
}
|
|
418
448
|
}
|
|
419
|
-
|
|
449
|
+
for (const line of wrapTextWithAnsi(progressParts.join(" "), contentWidth)) {
|
|
450
|
+
lines.push(padToWidth(boxLine(line)));
|
|
451
|
+
}
|
|
420
452
|
lines.push(padToWidth(emptyBoxLine()));
|
|
421
453
|
|
|
422
454
|
// Current question
|
|
@@ -525,66 +557,84 @@ export default function (pi: ExtensionAPI) {
|
|
|
525
557
|
}
|
|
526
558
|
|
|
527
559
|
// Run extraction with loader UI
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
560
|
+
const extraction = await ctx.ui.custom<ExtractionOutcome>((tui, theme, _kb, done) => {
|
|
561
|
+
const loader = new BorderedLoader(
|
|
562
|
+
tui,
|
|
563
|
+
theme,
|
|
564
|
+
`Extracting questions using ${extractionSelection.model.id}...`,
|
|
565
|
+
);
|
|
566
|
+
loader.onAbort = () => done({ status: "cancelled" });
|
|
567
|
+
|
|
568
|
+
const doExtract = async (): Promise<ExtractionOutcome> => {
|
|
569
|
+
const userMessage: UserMessage = {
|
|
570
|
+
role: "user",
|
|
571
|
+
content: [{ type: "text", text: lastAssistantText! }],
|
|
572
|
+
timestamp: Date.now(),
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const response = await ctx.modelRegistry.complete(
|
|
576
|
+
extractionSelection.model,
|
|
577
|
+
{ systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
|
|
578
|
+
{ signal: loader.signal },
|
|
534
579
|
);
|
|
535
|
-
loader.onAbort = () => done(null);
|
|
536
580
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
581
|
+
if (loader.signal.aborted || response.stopReason === "aborted") {
|
|
582
|
+
return { status: "cancelled" };
|
|
583
|
+
}
|
|
584
|
+
if (response.stopReason !== "stop") {
|
|
585
|
+
return {
|
|
586
|
+
status: "error",
|
|
587
|
+
message: response.errorMessage
|
|
588
|
+
? `Question extraction failed: ${response.errorMessage}`
|
|
589
|
+
: `Question extraction did not complete (${response.stopReason})`,
|
|
542
590
|
};
|
|
591
|
+
}
|
|
543
592
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
593
|
+
const responseText = response.content
|
|
594
|
+
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
595
|
+
.map((c) => c.text)
|
|
596
|
+
.join("\n");
|
|
597
|
+
|
|
598
|
+
const result = parseExtractionResult(responseText);
|
|
599
|
+
return result
|
|
600
|
+
? { status: "success", result }
|
|
601
|
+
: { status: "error", message: "Question extraction returned an invalid response" };
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
doExtract()
|
|
605
|
+
.then(done)
|
|
606
|
+
.catch((error: unknown) => {
|
|
607
|
+
done(
|
|
608
|
+
loader.signal.aborted
|
|
609
|
+
? { status: "cancelled" }
|
|
610
|
+
: {
|
|
611
|
+
status: "error",
|
|
612
|
+
message: `Question extraction failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
613
|
+
},
|
|
553
614
|
);
|
|
615
|
+
});
|
|
554
616
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
const responseText = response.content
|
|
560
|
-
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
561
|
-
.map((c) => c.text)
|
|
562
|
-
.join("\n");
|
|
563
|
-
|
|
564
|
-
return parseExtractionResult(responseText);
|
|
565
|
-
};
|
|
566
|
-
|
|
567
|
-
doExtract()
|
|
568
|
-
.then(done)
|
|
569
|
-
.catch(() => done(null));
|
|
570
|
-
|
|
571
|
-
return loader;
|
|
572
|
-
},
|
|
573
|
-
);
|
|
617
|
+
return loader;
|
|
618
|
+
});
|
|
574
619
|
|
|
575
|
-
if (
|
|
620
|
+
if (extraction.status === "cancelled") {
|
|
576
621
|
ctx.ui.notify("Cancelled", "info");
|
|
577
622
|
return;
|
|
578
623
|
}
|
|
624
|
+
if (extraction.status === "error") {
|
|
625
|
+
ctx.ui.notify(extraction.message, "error");
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
579
628
|
|
|
580
|
-
|
|
629
|
+
const { questions } = extraction.result;
|
|
630
|
+
if (questions.length === 0) {
|
|
581
631
|
ctx.ui.notify("No questions found in the last message", "info");
|
|
582
632
|
return;
|
|
583
633
|
}
|
|
584
634
|
|
|
585
635
|
// Show the Q&A component
|
|
586
636
|
const answersResult = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
587
|
-
return new QnAComponent(
|
|
637
|
+
return new QnAComponent(questions, tui, theme, done);
|
|
588
638
|
});
|
|
589
639
|
|
|
590
640
|
if (answersResult === null) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Branch terminals
|
|
2
|
+
|
|
3
|
+
`/branch` forks the selected conversation without switching the current session. Inside tmux, `--branch-tmux-layout` chooses `window`, `split-right`, or `split-down`; a `/branch` argument overrides that setting.
|
|
4
|
+
|
|
5
|
+
`--branch-term` takes precedence over tmux and accepts a Bash launcher template. Use unquoted `{cwd}` and `{session}` placeholders for path arguments. `{command}` supplies a single command-string argument, suitable for a launcher's command option or `bash -c`. Do not add shell quotes around placeholders; they are already quoted. If neither `{session}` nor `{command}` appears, the fork path is appended. The launcher runs in the session's working directory with the current environment.
|
|
6
|
+
|
|
7
|
+
A failed launcher produces a recovery command. Reloading or closing the original Pi session stops observing launcher failures but does not close user-owned terminals.
|