@shanepadgett/tau-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/README.md +30 -0
- package/docs/extending-tau-agent.md +119 -0
- package/docs/subagents.md +57 -0
- package/docs/tui.md +107 -0
- package/extensions/appshot/README.md +11 -0
- package/extensions/appshot/capture.swift +191 -0
- package/extensions/appshot/index.ts +245 -0
- package/extensions/appshot/native-helper.ts +102 -0
- package/extensions/attention/README.md +23 -0
- package/extensions/attention/index.ts +71 -0
- package/extensions/auto-name/README.md +26 -0
- package/extensions/auto-name/index.ts +163 -0
- package/extensions/branch/README.md +22 -0
- package/extensions/branch/index.ts +145 -0
- package/extensions/branch/panel.ts +148 -0
- package/extensions/clear-screen/README.md +29 -0
- package/extensions/clear-screen/index.ts +75 -0
- package/extensions/commit/README.md +15 -0
- package/extensions/commit/commit-plan.ts +328 -0
- package/extensions/commit/git-change-set.ts +268 -0
- package/extensions/commit/index.ts +138 -0
- package/extensions/commit/review-ui.ts +745 -0
- package/extensions/explore/README.md +7 -0
- package/extensions/explore/autoread.ts +140 -0
- package/extensions/explore/find.ts +147 -0
- package/extensions/explore/grep.ts +749 -0
- package/extensions/explore/index.ts +17 -0
- package/extensions/explore/limits.ts +22 -0
- package/extensions/explore/ls.ts +113 -0
- package/extensions/explore/path-display.ts +41 -0
- package/extensions/explore/path-tree.ts +150 -0
- package/extensions/explore/read.ts +160 -0
- package/extensions/explore/result.ts +29 -0
- package/extensions/explore/traverse.ts +226 -0
- package/extensions/footer/README.md +23 -0
- package/extensions/footer/index.ts +534 -0
- package/extensions/footer/settings.ts +13 -0
- package/extensions/ideas/README.md +10 -0
- package/extensions/ideas/browser.ts +66 -0
- package/extensions/ideas/index.ts +31 -0
- package/extensions/ideas/store.ts +32 -0
- package/extensions/image-gen/README.md +11 -0
- package/extensions/image-gen/client.ts +186 -0
- package/extensions/image-gen/index.ts +152 -0
- package/extensions/manage-sessions/README.md +12 -0
- package/extensions/manage-sessions/index.ts +58 -0
- package/extensions/manage-sessions/manager-ui.ts +323 -0
- package/extensions/manage-sessions/sessions.ts +126 -0
- package/extensions/patch/README.md +48 -0
- package/extensions/patch/executor.ts +355 -0
- package/extensions/patch/index.ts +170 -0
- package/extensions/patch/matcher.ts +236 -0
- package/extensions/patch/parser.ts +347 -0
- package/extensions/patch/render.ts +247 -0
- package/extensions/patch/summary.ts +35 -0
- package/extensions/publish/README.md +11 -0
- package/extensions/publish/index.ts +225 -0
- package/extensions/qna/README.md +45 -0
- package/extensions/qna/additional-context-body.ts +51 -0
- package/extensions/qna/body-render.ts +6 -0
- package/extensions/qna/choice-question-body.ts +322 -0
- package/extensions/qna/index.ts +245 -0
- package/extensions/qna/inline-editor-row.ts +56 -0
- package/extensions/qna/input-question-body.ts +89 -0
- package/extensions/qna/model.ts +317 -0
- package/extensions/qna/panel.ts +240 -0
- package/extensions/qna/ui.ts +16 -0
- package/extensions/reference/README.md +47 -0
- package/extensions/reference/index.ts +76 -0
- package/extensions/reference/panel.ts +874 -0
- package/extensions/reference/settings.ts +29 -0
- package/extensions/run-summary/README.md +5 -0
- package/extensions/run-summary/index.ts +98 -0
- package/extensions/silent-command-runner/README.md +25 -0
- package/extensions/silent-command-runner/index.ts +468 -0
- package/extensions/silent-command-runner/settings.ts +65 -0
- package/extensions/soul/README.md +19 -0
- package/extensions/soul/index.ts +20 -0
- package/extensions/soul/prompt.ts +254 -0
- package/extensions/soul/settings.ts +13 -0
- package/extensions/stash/README.md +48 -0
- package/extensions/stash/browser.ts +51 -0
- package/extensions/stash/index.ts +55 -0
- package/extensions/stash/store.ts +36 -0
- package/extensions/subagent/README.md +34 -0
- package/extensions/subagent/agents/scout.md +85 -0
- package/extensions/subagent/agents/web-research.md +95 -0
- package/extensions/subagent/agents.ts +196 -0
- package/extensions/subagent/index.ts +214 -0
- package/extensions/subagent/render.ts +89 -0
- package/extensions/subagent/run.ts +325 -0
- package/extensions/tau/README.md +17 -0
- package/extensions/tau/index.ts +122 -0
- package/extensions/tau-help/README.md +3 -0
- package/extensions/tau-help/help.md +125 -0
- package/extensions/tau-help/index.ts +68 -0
- package/extensions/turn-budget/README.md +14 -0
- package/extensions/turn-budget/index.ts +150 -0
- package/extensions/turn-budget/settings.ts +35 -0
- package/extensions/web/README.md +13 -0
- package/extensions/web/codesearch.ts +79 -0
- package/extensions/web/exa.ts +101 -0
- package/extensions/web/html.ts +67 -0
- package/extensions/web/index.ts +13 -0
- package/extensions/web/limits.ts +11 -0
- package/extensions/web/tool-output.ts +54 -0
- package/extensions/web/webfetch.ts +177 -0
- package/extensions/web/websearch.ts +100 -0
- package/package.json +57 -0
- package/prompts/.gitkeep +1 -0
- package/prompts/cavemanify.md +25 -0
- package/prompts/implement.md +25 -0
- package/prompts/interview.md +48 -0
- package/prompts/plan-feature.md +30 -0
- package/prompts/plan-implementation.md +50 -0
- package/schemas/tau.schema.json +205 -0
- package/shared/agent-blocked.ts +12 -0
- package/shared/description.ts +28 -0
- package/shared/events.ts +135 -0
- package/shared/git.ts +41 -0
- package/shared/injected-context.ts +69 -0
- package/shared/jsonl-store.ts +108 -0
- package/shared/model-fallback/index.ts +278 -0
- package/shared/model-fallback/settings.ts +18 -0
- package/shared/model-fallback/types.ts +8 -0
- package/shared/ranges.ts +21 -0
- package/shared/settings/define.ts +15 -0
- package/shared/settings/files.ts +20 -0
- package/shared/settings/json.ts +41 -0
- package/shared/settings/load.ts +59 -0
- package/shared/settings/merge.ts +21 -0
- package/shared/settings/paths.ts +41 -0
- package/shared/settings/schema.ts +25 -0
- package/shared/settings/specs.ts +23 -0
- package/shared/text.ts +11 -0
- package/shared/tool-row-state.ts +43 -0
- package/skills/writing-preferences/SKILL.md +136 -0
- package/themes/.gitkeep +1 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { AutocompleteItem } from "@earendil-works/pi-tui";
|
|
3
|
+
import { createGitRunner, type GitRunner } from "../../shared/git.ts";
|
|
4
|
+
import { errorText } from "../../shared/text.ts";
|
|
5
|
+
import { type BranchChoice, showBranchPanel } from "./panel.ts";
|
|
6
|
+
|
|
7
|
+
const GIT_FETCH_TIMEOUT_MS = 120_000;
|
|
8
|
+
|
|
9
|
+
const LIST_BRANCH_REFS_ARGS = [
|
|
10
|
+
"for-each-ref",
|
|
11
|
+
"--sort=-committerdate",
|
|
12
|
+
"--format=%(refname)%00%(committerdate:unix)%00%(HEAD)%00%(symref)",
|
|
13
|
+
"refs/heads",
|
|
14
|
+
"refs/remotes",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export function normalizeBranchName(name: string): string {
|
|
18
|
+
return name
|
|
19
|
+
.toLowerCase()
|
|
20
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
21
|
+
.replace(/^-|-$/g, "");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function branchExtension(pi: ExtensionAPI): void {
|
|
25
|
+
pi.registerCommand("branch", {
|
|
26
|
+
description: "Switch branches, or create one with /branch new",
|
|
27
|
+
getArgumentCompletions(prefix: string): AutocompleteItem[] | null {
|
|
28
|
+
const value = prefix.trimStart();
|
|
29
|
+
if (/\s/.test(value)) return null;
|
|
30
|
+
|
|
31
|
+
const item = { value: "new", label: "new", description: "Create a new branch" };
|
|
32
|
+
return item.value.startsWith(value) ? [item] : null;
|
|
33
|
+
},
|
|
34
|
+
handler: async (args, ctx) => {
|
|
35
|
+
await ctx.waitForIdle();
|
|
36
|
+
|
|
37
|
+
const action = args.trim();
|
|
38
|
+
if (action && action !== "new") {
|
|
39
|
+
ctx.ui.notify("Usage: /branch or /branch new", "error");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!ctx.hasUI) {
|
|
44
|
+
const operation = action === "new" ? "creation" : "switching";
|
|
45
|
+
ctx.ui.notify(`Branch ${operation} requires interactive UI.`, "error");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (action !== "new" && ctx.mode !== "tui") {
|
|
49
|
+
ctx.ui.notify("Branch switching requires interactive TUI.", "error");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const git = createGitRunner(pi, ctx);
|
|
54
|
+
try {
|
|
55
|
+
if (action === "new") await createBranch(git, ctx);
|
|
56
|
+
else await chooseBranch(git, ctx);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
const operation = action === "new" ? "creation" : "switch";
|
|
59
|
+
ctx.ui.notify(`Branch ${operation} failed: ${errorText(error)}`, "error");
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function createBranch(git: GitRunner, ctx: ExtensionCommandContext): Promise<void> {
|
|
66
|
+
const type = await ctx.ui.select("Branch type", ["feature", "fix", "chore"]);
|
|
67
|
+
if (!type) return;
|
|
68
|
+
|
|
69
|
+
const input = await ctx.ui.input("Branch name", "add branch command");
|
|
70
|
+
if (input === undefined) return;
|
|
71
|
+
|
|
72
|
+
const name = normalizeBranchName(input);
|
|
73
|
+
if (!name) {
|
|
74
|
+
ctx.ui.notify("Branch name must contain letters or numbers.", "error");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const root = await git.run(["rev-parse", "--show-toplevel"], { optional: true });
|
|
79
|
+
if (!root) {
|
|
80
|
+
ctx.ui.notify("No Git repository found.", "error");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const branch = `${type}/${name}`;
|
|
85
|
+
await git.run(["switch", "-c", branch], { cwd: root });
|
|
86
|
+
ctx.ui.notify(`Created and switched to ${branch}.`, "info");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function chooseBranch(git: GitRunner, ctx: ExtensionCommandContext): Promise<void> {
|
|
90
|
+
const root = await git.run(["rev-parse", "--show-toplevel"], { optional: true });
|
|
91
|
+
if (!root) {
|
|
92
|
+
ctx.ui.notify("No Git repository found.", "error");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const choice = await showBranchPanel(ctx, await loadBranchChoices(git, root), async () => {
|
|
97
|
+
await git.run(["fetch", "--all"], { cwd: root, timeout: GIT_FETCH_TIMEOUT_MS });
|
|
98
|
+
return loadBranchChoices(git, root);
|
|
99
|
+
});
|
|
100
|
+
if (!choice) return;
|
|
101
|
+
|
|
102
|
+
if (choice.kind === "local") await git.run(["switch", choice.name], { cwd: root });
|
|
103
|
+
else await git.run(["switch", "--track", "-c", choice.name, choice.upstream], { cwd: root });
|
|
104
|
+
ctx.ui.notify(`Switched to ${choice.name}.`, "info");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function loadBranchChoices(git: GitRunner, root: string): Promise<BranchChoice[]> {
|
|
108
|
+
return parseBranchChoices(await git.run(LIST_BRANCH_REFS_ARGS, { cwd: root }));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parseBranchChoices(output: string): BranchChoice[] {
|
|
112
|
+
const refs = output
|
|
113
|
+
.split("\n")
|
|
114
|
+
.filter(Boolean)
|
|
115
|
+
.map((line) => {
|
|
116
|
+
const [ref = "", seconds = "0", head = "", symref = ""] = line.split("\0");
|
|
117
|
+
return { ref, updatedAt: Number(seconds) * 1000, current: head.trim() === "*", symbolic: Boolean(symref) };
|
|
118
|
+
});
|
|
119
|
+
const localPrefix = "refs/heads/";
|
|
120
|
+
const remotePrefix = "refs/remotes/";
|
|
121
|
+
const localNames = new Set(
|
|
122
|
+
refs.filter(({ ref }) => ref.startsWith(localPrefix)).map(({ ref }) => ref.slice(localPrefix.length)),
|
|
123
|
+
);
|
|
124
|
+
const choices: BranchChoice[] = [];
|
|
125
|
+
|
|
126
|
+
for (const ref of refs) {
|
|
127
|
+
if (ref.ref.startsWith(localPrefix)) {
|
|
128
|
+
const name = ref.ref.slice(localPrefix.length);
|
|
129
|
+
if (name && !ref.current)
|
|
130
|
+
choices.push({ id: `local:${name}`, kind: "local", label: name, name, updatedAt: ref.updatedAt });
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (!ref.ref.startsWith(remotePrefix) || ref.symbolic) continue;
|
|
134
|
+
|
|
135
|
+
const upstream = ref.ref.slice(remotePrefix.length);
|
|
136
|
+
const separator = upstream.indexOf("/");
|
|
137
|
+
if (separator < 1) continue;
|
|
138
|
+
const name = upstream.slice(separator + 1);
|
|
139
|
+
if (!name || name === "HEAD" || localNames.has(name)) continue;
|
|
140
|
+
const label = localNames.has(upstream) ? `${upstream} (remote)` : upstream;
|
|
141
|
+
choices.push({ id: `remote:${upstream}`, kind: "remote", label, name, upstream, updatedAt: ref.updatedAt });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return choices.sort((left, right) => right.updatedAt - left.updatedAt || left.label.localeCompare(right.label));
|
|
145
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { ExtensionCommandContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { type Component, Key, type TUI, truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import { errorText } from "../../shared/text.ts";
|
|
4
|
+
import { bindingHint, rawHint } from "@shanepadgett/tau-tui";
|
|
5
|
+
import { SelectableList, type SelectableListResult } from "@shanepadgett/tau-tui";
|
|
6
|
+
import { ToolPanel, type ToolPanelConfig, type ToolPanelFooter } from "@shanepadgett/tau-tui";
|
|
7
|
+
|
|
8
|
+
const MAX_VISIBLE_BRANCHES = 10;
|
|
9
|
+
|
|
10
|
+
export type BranchChoice =
|
|
11
|
+
| { id: string; kind: "local"; label: string; name: string; updatedAt: number }
|
|
12
|
+
| { id: string; kind: "remote"; label: string; name: string; upstream: string; updatedAt: number };
|
|
13
|
+
|
|
14
|
+
export function showBranchPanel(
|
|
15
|
+
ctx: ExtensionCommandContext,
|
|
16
|
+
initial: readonly BranchChoice[],
|
|
17
|
+
refresh: () => Promise<readonly BranchChoice[]>,
|
|
18
|
+
): Promise<BranchChoice | undefined> {
|
|
19
|
+
return ctx.ui.custom<BranchChoice | undefined>(
|
|
20
|
+
(tui, theme, _keybindings, done) =>
|
|
21
|
+
new BranchPanel(
|
|
22
|
+
tui,
|
|
23
|
+
theme,
|
|
24
|
+
initial,
|
|
25
|
+
refresh,
|
|
26
|
+
(error) => {
|
|
27
|
+
ctx.ui.notify(`Branch fetch failed: ${errorText(error)}`, "error");
|
|
28
|
+
},
|
|
29
|
+
done,
|
|
30
|
+
),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class BranchPanel implements Component {
|
|
35
|
+
private readonly tui: TUI;
|
|
36
|
+
private readonly refresh: () => Promise<readonly BranchChoice[]>;
|
|
37
|
+
private readonly onFetchError: (error: unknown) => void;
|
|
38
|
+
private readonly done: (choice: BranchChoice | undefined) => void;
|
|
39
|
+
private readonly list: SelectableList<BranchChoice>;
|
|
40
|
+
private readonly panelConfig: ToolPanelConfig;
|
|
41
|
+
private readonly panel: ToolPanel;
|
|
42
|
+
private itemCount: number;
|
|
43
|
+
private fetching = false;
|
|
44
|
+
private closed = false;
|
|
45
|
+
|
|
46
|
+
constructor(
|
|
47
|
+
tui: TUI,
|
|
48
|
+
theme: Theme,
|
|
49
|
+
initial: readonly BranchChoice[],
|
|
50
|
+
refresh: () => Promise<readonly BranchChoice[]>,
|
|
51
|
+
onFetchError: (error: unknown) => void,
|
|
52
|
+
done: (choice: BranchChoice | undefined) => void,
|
|
53
|
+
) {
|
|
54
|
+
this.tui = tui;
|
|
55
|
+
this.refresh = refresh;
|
|
56
|
+
this.onFetchError = onFetchError;
|
|
57
|
+
this.done = done;
|
|
58
|
+
this.itemCount = initial.length;
|
|
59
|
+
this.list = new SelectableList(theme, {
|
|
60
|
+
items: initial,
|
|
61
|
+
emptyMessage: "No cached branches. Press ctrl+f to fetch.",
|
|
62
|
+
selection: { kind: "single", primaryLabel: "switch" },
|
|
63
|
+
actions: [{ id: "fetch", key: Key.ctrl("f"), hint: rawHint("ctrl+f", "fetch") }],
|
|
64
|
+
cancelLabel: "cancel",
|
|
65
|
+
maxVisible: MAX_VISIBLE_BRANCHES,
|
|
66
|
+
renderItem: (item, state, width) => [
|
|
67
|
+
truncateToWidth(theme.fg(state.active ? "accent" : "text", item.label), width, ""),
|
|
68
|
+
],
|
|
69
|
+
onResult: (result) => this.handleResult(result),
|
|
70
|
+
});
|
|
71
|
+
this.panelConfig = {
|
|
72
|
+
title: "Branches",
|
|
73
|
+
secondary: this.secondaryText(),
|
|
74
|
+
body: this.list,
|
|
75
|
+
footer: this.footer(),
|
|
76
|
+
};
|
|
77
|
+
this.panel = new ToolPanel(theme, this.panelConfig);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
render(width: number): string[] {
|
|
81
|
+
return this.panel.render(width);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
invalidate(): void {
|
|
85
|
+
this.panel.invalidate();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
handleInput(data: string): void {
|
|
89
|
+
this.list.handleInput(data);
|
|
90
|
+
if (!this.closed) this.syncPanel();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private handleResult(result: SelectableListResult<BranchChoice>): void {
|
|
94
|
+
if (result.kind === "cancel") {
|
|
95
|
+
this.closed = true;
|
|
96
|
+
this.done(undefined);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (this.fetching) return;
|
|
100
|
+
if (result.kind === "primary") {
|
|
101
|
+
const choice = result.items[0];
|
|
102
|
+
if (!choice) return;
|
|
103
|
+
this.closed = true;
|
|
104
|
+
this.done(choice);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (result.actionId === "fetch") void this.fetchBranches();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private async fetchBranches(): Promise<void> {
|
|
111
|
+
const activeId = this.list.getCurrentItem()?.id;
|
|
112
|
+
this.fetching = true;
|
|
113
|
+
this.syncPanel();
|
|
114
|
+
try {
|
|
115
|
+
const choices = await this.refresh();
|
|
116
|
+
if (this.closed) return;
|
|
117
|
+
this.itemCount = choices.length;
|
|
118
|
+
this.list.setItems(choices, activeId);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (!this.closed) this.onFetchError(error);
|
|
121
|
+
} finally {
|
|
122
|
+
if (!this.closed) {
|
|
123
|
+
this.fetching = false;
|
|
124
|
+
this.syncPanel();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private syncPanel(): void {
|
|
130
|
+
this.panelConfig.secondary = this.secondaryText();
|
|
131
|
+
this.panelConfig.footer = this.footer();
|
|
132
|
+
this.tui.requestRender();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private secondaryText(): string {
|
|
136
|
+
return `${this.itemCount} branch${this.itemCount === 1 ? "" : "es"}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private footer(): ToolPanelFooter {
|
|
140
|
+
return this.fetching
|
|
141
|
+
? {
|
|
142
|
+
kind: "infoAck",
|
|
143
|
+
message: "Fetching branches…",
|
|
144
|
+
hints: [bindingHint("tui.select.cancel", "cancel")],
|
|
145
|
+
}
|
|
146
|
+
: { kind: "hints", hints: this.list.getKeyHints() };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Clear Screen
|
|
2
|
+
|
|
3
|
+
Clears the terminal viewport and scrollback when Pi starts, then clears again when Pi quits.
|
|
4
|
+
|
|
5
|
+
It does **not** clear on `/reload`, `/resume`, `/new`, or `/fork`, so Pi hot reloads and session switches keep the current TUI flow intact.
|
|
6
|
+
|
|
7
|
+
## Behavior
|
|
8
|
+
|
|
9
|
+
- Runs only when `process.stdout` is a TTY.
|
|
10
|
+
- Clears once per process on startup. Reloads do not re-clear.
|
|
11
|
+
- Clears on process exit only when the final `session_shutdown` reason is `"quit"`.
|
|
12
|
+
- Uses ANSI clear sequences (`\x1b[2J\x1b[3J`) on Unix and modern Windows consoles.
|
|
13
|
+
- Falls back to a newline-fill on older Windows consoles that do not support the scrollback escape.
|
|
14
|
+
|
|
15
|
+
## Limits
|
|
16
|
+
|
|
17
|
+
- TTY-only. Pipe, socket, and script runs are a no-op.
|
|
18
|
+
- Old Windows consoles get a best-effort visible clear, not a guaranteed scrollback clear.
|
|
19
|
+
- Process-level state is keyed on a `globalThis` symbol so reloads reuse a single startup clear and a single exit hook.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
Load extension, then reload Pi:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
/reload
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The package manifest glob `./extensions/*/index.ts` auto-loads this extension.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
type ClearScreenState = {
|
|
4
|
+
startupCleared: boolean;
|
|
5
|
+
exitHooked: boolean;
|
|
6
|
+
clearOnExit: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const STATE_KEY = Symbol.for("@tau-agent/clear-screen/state");
|
|
10
|
+
|
|
11
|
+
function getState(): ClearScreenState {
|
|
12
|
+
const globalState = globalThis as typeof globalThis & {
|
|
13
|
+
[key: symbol]: ClearScreenState | undefined;
|
|
14
|
+
};
|
|
15
|
+
const state =
|
|
16
|
+
globalState[STATE_KEY] ??
|
|
17
|
+
({
|
|
18
|
+
startupCleared: false,
|
|
19
|
+
exitHooked: false,
|
|
20
|
+
clearOnExit: false,
|
|
21
|
+
} satisfies ClearScreenState);
|
|
22
|
+
globalState[STATE_KEY] = state;
|
|
23
|
+
return state;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function supportsAnsiClear(): boolean {
|
|
27
|
+
if (process.platform !== "win32") return true;
|
|
28
|
+
|
|
29
|
+
return Boolean(
|
|
30
|
+
process.env.WT_SESSION ||
|
|
31
|
+
process.env.TERM_PROGRAM === "vscode" ||
|
|
32
|
+
process.env.TERM?.includes("xterm") ||
|
|
33
|
+
process.env.ANSICON ||
|
|
34
|
+
process.env.ConEmuANSI === "ON",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getClearSequence(): string {
|
|
39
|
+
if (!supportsAnsiClear()) {
|
|
40
|
+
return "\n".repeat(process.stdout.rows || 30);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return "\x1b[0m\x1b[?25h\x1b[H\x1b[2J\x1b[3J";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function clearTerminal(): void {
|
|
47
|
+
try {
|
|
48
|
+
process.stdout.write(getClearSequence());
|
|
49
|
+
} catch {
|
|
50
|
+
// Ignore terminal write failures during process shutdown.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default function clearScreenExtension(pi: ExtensionAPI): void {
|
|
55
|
+
if (!process.stdout.isTTY) return;
|
|
56
|
+
|
|
57
|
+
const state = getState();
|
|
58
|
+
state.clearOnExit = false;
|
|
59
|
+
|
|
60
|
+
pi.on("session_shutdown", (event) => {
|
|
61
|
+
state.clearOnExit = event.reason === "quit";
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (!state.exitHooked) {
|
|
65
|
+
state.exitHooked = true;
|
|
66
|
+
process.on("exit", () => {
|
|
67
|
+
if (state.clearOnExit) clearTerminal();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!state.startupCleared) {
|
|
72
|
+
state.startupCleared = true;
|
|
73
|
+
clearTerminal();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Commit
|
|
2
|
+
|
|
3
|
+
Generate a semantic commit plan from current repository changes, review it, then create one or more commits.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
/commit
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
`/commit` looks at staged, unstaged, and untracked files, gathers bounded change evidence, and asks a model for a small set of meaningful conventional commits. You can review the plan in the TUI, edit messages, move files between commits, regenerate messages, and then commit the approved groups.
|
|
14
|
+
|
|
15
|
+
Unassigned files are left uncommitted. After successful commits, Tau asks whether to run `git push`.
|