@senad-d/branchme 0.1.1

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.
@@ -0,0 +1,82 @@
1
+ # BranchMe Structure Guide
2
+
3
+ BranchMe is a TypeScript Pi extension package for current-repository git branch, push, and GitHub pull request workflows.
4
+
5
+ ## Source layout
6
+
7
+ ```text
8
+ src/
9
+ ├── extension.ts # extension entry point; registers command and tools only
10
+ ├── constants.ts # command/tool names, timeouts, GitHub API constants
11
+ ├── types.ts # serializable tool details and helper result types
12
+ ├── commands/
13
+ │ └── branchme-command.ts # /branchme status/help command; informational only
14
+ ├── tools/
15
+ │ └── branchme-tools.ts # branch_status/change_branch/create_branch/push_branch/pull_request registration
16
+ ├── git.ts # argv-style git helpers and per-repo workflow queue
17
+ ├── github.ts # GitHub repo resolution, env/.env tokens, branch preflight, REST calls, redaction
18
+ └── ui/
19
+ └── branchme-panel.ts # compact /branchme status panel renderer
20
+ ```
21
+
22
+ ## Module boundaries
23
+
24
+ 1. `src/extension.ts` stays small and only calls `registerBranchMeCommand(pi)` and `registerBranchMeTools(pi)`.
25
+ 2. `src/commands/branchme-command.ts` parses `/branchme`, `/branchme help`, `--help`, and `-h`; it never performs git or GitHub mutations and avoids raw stdout in JSON mode.
26
+ 3. `src/tools/branchme-tools.ts` owns TypeBox schemas, prompt metadata, tool content, and safe structured details.
27
+ 4. `src/git.ts` owns current-repository git behavior: root detection, branch/upstream/status inspection, branch validation, branch creation, existing-local-branch switching, clean-worktree preflight, current-branch push/publish, and the per-repository workflow queue.
28
+ 5. `src/github.ts` owns GitHub `owner/repo` parsing, repository boundary checks, `GITHUB_TOKEN`/`GH_TOKEN` process-env and hardened git-root `.env` fallback resolution, PR branch-name syntax validation, GitHub branch visibility/commit preflight, PR REST calls, response validation, and redacted errors.
29
+ 6. `src/redaction.ts` owns shared credential redaction for Git and GitHub messages.
30
+ 7. `src/types.ts` keeps serializable details shared by helpers and tools.
31
+ 8. `src/ui/branchme-panel.ts` renders a compact status panel and clips lines to terminal width.
32
+
33
+ ## Pi extension conventions
34
+
35
+ - No long-lived processes, watchers, timers, sockets, or background jobs start in the extension factory.
36
+ - Slash commands are informational; tools perform branch, push, and PR actions.
37
+ - Every tool uses a strict TypeBox object schema with `additionalProperties: false`.
38
+ - Every tool defines a description, `promptSnippet`, and tool-specific `promptGuidelines` that explicitly name the tool.
39
+ - Git commands use `pi.exec("git", args, { cwd, signal, timeout })` with argv arrays; repository mutations run from the verified git root and same-repository mutation/PR windows are serialized per repository.
40
+ - Tool details avoid token values and unbounded raw command/API output.
41
+ - Pi core packages, including `@earendil-works/pi-tui` for key/width utilities, remain in `peerDependencies` with `"*"`.
42
+
43
+ ## Security-sensitive areas
44
+
45
+ - `change_branch` mutates local HEAD and working-tree files only through `git switch <branchName>` for existing local branches after a clean-worktree preflight.
46
+ - `create_branch` mutates local branch/HEAD only with `git switch -c`.
47
+ - `push_branch` mutates remote refs only for the current branch and uses an explicit upstream remote/refspec instead of bare `git push` when an upstream exists.
48
+ - `pull_request` requires `headBranch` and `baseBranch` to exist locally, requires `headBranch` to match the GitHub-visible branch commit, queues behind already-started same-repository git mutation windows, makes GitHub REST API calls for the resolved current repository only, and rejects missing, owner-prefixed, or unsafe branch refs before token lookup or the request.
49
+ - `pull_request` reads `GITHUB_TOKEN` or `GH_TOKEN` from process environment first; only when neither process token is set does it read those token keys from a small regular `.env` file in the verified git root as a fallback.
50
+ - BranchMe does not force checkout, stash, stage, commit, directly edit files, read non-token `.env` keys, follow unsafe `.env` file types, depend on GitHub CLI, or collect telemetry.
51
+
52
+ ## Documentation
53
+
54
+ - `docs/PROJECT_DEFINITION_BRIEF.md` preserves the approved project definition.
55
+ - `docs/STRUCTURE.md` describes the implemented source layout.
56
+ - `docs/SMOKE_TEST.md` records isolated validation/smoke-test findings.
57
+ - `docs/TUI_CAPTURE.md` stores deterministic text captures of BranchMe TUI/help surfaces for visual regression review.
58
+
59
+ ## Tests
60
+
61
+ ```text
62
+ test/
63
+ ├── command.test.mjs # /branchme parsing, help, fallback, panel width
64
+ ├── git.test.mjs # git helper command construction and failures
65
+ ├── git-integration.test.mjs # isolated real-git branch helper coverage
66
+ ├── github.test.mjs # GitHub parsing, token resolution, fetch wrapper, redaction
67
+ ├── preparation.test.mjs # package/docs/source metadata checks
68
+ ├── tools.test.mjs # extension registration, schemas, prompt metadata, tool behavior
69
+ └── tui-capture.test.mjs # generated text capture for TUI/help visual baselines
70
+ ```
71
+
72
+ Validation commands:
73
+
74
+ ```bash
75
+ npm run typecheck
76
+ npm run format:check
77
+ npm run test
78
+ npm run check:pack
79
+ npm run validate
80
+ npm run smoke:pi:packed # release gate for the installed npm artifact
81
+ pi --no-extensions -e .
82
+ ```
@@ -0,0 +1,209 @@
1
+ # BranchMe TUI Capture
2
+
3
+ This file is a deterministic text capture of BranchMe user-facing TUI/help surfaces.
4
+ Use it as a visual baseline when improving layout, wording, spacing, or responsive behavior.
5
+ Trailing spaces are shown as `·` so formatting checks can keep the repository whitespace-clean.
6
+
7
+ Generated and verified by `test/tui-capture.test.mjs`.
8
+ Update intentionally with:
9
+
10
+ ```text
11
+ UPDATE_TUI_CAPTURE=1 node --test test/tui-capture.test.mjs
12
+ ```
13
+
14
+ ## /branchme help
15
+
16
+ ```text
17
+ # BranchMe
18
+
19
+ Current-repository branch workflow tools for Pi.
20
+
21
+ Commands only show info; BranchMe tools perform actions.
22
+
23
+ ## Workflow
24
+
25
+ 1. `branch_status` — inspect repo and branch state.
26
+ 2. `change_branch` / `create_branch` — switch to an existing clean branch or create one from `HEAD`.
27
+ 3. Commit outside BranchMe.
28
+ 4. `push_branch` — push the current branch.
29
+ 5. `pull_request` — open a PR after `push_branch` completes and GitHub sees the branches.
30
+
31
+ ## Requirements
32
+
33
+ - Run inside a Git repo with `git` available.
34
+ - For PRs: GitHub `origin` and `GITHUB_TOKEN` or `GH_TOKEN` (environment or `.env`).
35
+ - BranchMe never stages or commits.
36
+ ```
37
+
38
+ ## Panel: Tiny mode: clean branch with token
39
+
40
+ Width: 18
41
+
42
+ ```text
43
+ BranchMe
44
+ branch: feature/c…
45
+ repo: senad-d/bra…
46
+ q quit
47
+ ```
48
+
49
+ ## Panel: Narrow mode: clean branch with token
50
+
51
+ Width: 40
52
+
53
+ ```text
54
+ ╭ BranchMe ──────────────────── Status ╮
55
+ │current repo only • informational │
56
+ │ ↑↓ section • q quit • /branchme help │
57
+ ├──────────────────────────────────────┤
58
+ │ STATUS │
59
+ │ Current branch: feature/current │
60
+ │ GitHub repository: senad-d/branchme │
61
+ │ GitHub token: present │
62
+ │ │
63
+ │ │
64
+ │ │
65
+ ├──────────────────────────────────────┤
66
+ │ 1/2 • status • current repository on…│
67
+ ╰──────────────────────────────────────╯
68
+ ```
69
+
70
+ ## Panel: Wide mode: Status selected
71
+
72
+ Width: 80
73
+
74
+ ```text
75
+ ╭ BranchMe ──────────────────────────────────────────────────────────── Status ╮
76
+ │ ↑↓ section • q quit • /branchme help │
77
+ ├─────────────────────┬────────────────────────────────────────────────────────┤
78
+ │▶ Status │ STATUS │
79
+ │ Workflow │ Current branch: feature/current │
80
+ │ │ GitHub repository: senad-d/branchme │
81
+ │ │ GitHub token: present │
82
+ │ │ │
83
+ │ │ │
84
+ │ │ │
85
+ ├─────────────────────┴────────────────────────────────────────────────────────┤
86
+ │ 1/2 • status • current repository only • tools perform actions │
87
+ ╰──────────────────────────────────────────────────────────────────────────────╯
88
+ ```
89
+
90
+ ## Panel: Wide mode: Workflow selected
91
+
92
+ Width: 80
93
+
94
+ ```text
95
+ ╭ BranchMe ────────────────────────────────────────────────────────── Workflow ╮
96
+ │ ↑↓ section • q quit • /branchme help │
97
+ ├─────────────────────┬────────────────────────────────────────────────────────┤
98
+ │ Status │ WORKFLOW │
99
+ │▶ Workflow │ branch_status -> inspect │
100
+ │ │ change_branch -> existing local │
101
+ │ │ create_branch -> from HEAD │
102
+ │ │ push_branch -> current branch │
103
+ │ │ pull_request -> after push │
104
+ │ │ │
105
+ ├─────────────────────┴────────────────────────────────────────────────────────┤
106
+ │ 2/2 • workflow • inspect → change/create → push → PR │
107
+ ╰──────────────────────────────────────────────────────────────────────────────╯
108
+ ```
109
+
110
+ ## Panel: Very wide terminal: panel width capped
111
+
112
+ Width: 112
113
+
114
+ ```text
115
+ ╭ BranchMe ──────────────────────────────────────────────────────────────────────────── Status ╮
116
+ │ ↑↓ section • q quit • /branchme help │
117
+ ├──────────────────────┬───────────────────────────────────────────────────────────────────────┤
118
+ │▶ Status │ STATUS │
119
+ │ Workflow │ Current branch: main │
120
+ │ │ GitHub repository: senad-d/BranchMe │
121
+ │ │ GitHub token: not set │
122
+ │ │ │
123
+ │ │ │
124
+ │ │ │
125
+ ├──────────────────────┴───────────────────────────────────────────────────────────────────────┤
126
+ │ 1/2 • status • current repository only • tools perform actions │
127
+ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯
128
+ ```
129
+
130
+ ## Panel: Fallback values: detached HEAD without repository or token
131
+
132
+ Width: 50
133
+
134
+ ```text
135
+ ╭ BranchMe ────────────────────────────── Status ╮
136
+ │current repo only • informational │
137
+ │ ↑↓ section • q quit • /branchme help │
138
+ ├────────────────────────────────────────────────┤
139
+ │ STATUS │
140
+ │ Current branch: detached HEAD │
141
+ │ GitHub repository: not resolved │
142
+ │ GitHub token: not set │
143
+ │ │
144
+ │ │
145
+ │ │
146
+ ├────────────────────────────────────────────────┤
147
+ │ 1/2 • warning • Unable to resolve a GitHub rep…│
148
+ ╰────────────────────────────────────────────────╯
149
+ ```
150
+
151
+ ## Panel: Repository warning: boundary mismatch
152
+
153
+ Width: 80
154
+
155
+ ```text
156
+ ╭ BranchMe ──────────────────────────────────────────────────────────── Status ╮
157
+ │ ↑↓ section • q quit • /branchme help │
158
+ ├─────────────────────┬────────────────────────────────────────────────────────┤
159
+ │▶ Status │ STATUS │
160
+ │ Workflow │ Current branch: main │
161
+ │ │ GitHub repository: warning: Repository boundary misma…│
162
+ │ │ GitHub token: present │
163
+ │ │ │
164
+ │ │ │
165
+ │ │ │
166
+ ├─────────────────────┴────────────────────────────────────────────────────────┤
167
+ │ 1/2 • warning • Repository boundary mismatch: local origin resolves to senad…│
168
+ ╰──────────────────────────────────────────────────────────────────────────────╯
169
+ ```
170
+
171
+ ## Panel: Token warning: fallback error
172
+
173
+ Width: 72
174
+
175
+ ```text
176
+ ╭ BranchMe ──────────────────────────────────────────────────── Status ╮
177
+ │ ↑↓ section • q quit • /branchme help │
178
+ ├───────────────────┬──────────────────────────────────────────────────┤
179
+ │▶ Status │ STATUS │
180
+ │ Workflow │ Current branch: main │
181
+ │ │ GitHub repository: senad-d/branchme │
182
+ │ │ GitHub token: warning: Unable to read .env…│
183
+ │ │ │
184
+ │ │ │
185
+ │ │ │
186
+ ├───────────────────┴──────────────────────────────────────────────────┤
187
+ │ 1/2 • warning • Unable to read .env file for GitHub token fallback: …│
188
+ ╰──────────────────────────────────────────────────────────────────────╯
189
+ ```
190
+
191
+ ## Panel: Long values: truncation
192
+
193
+ Width: 72
194
+
195
+ ```text
196
+ ╭ BranchMe ──────────────────────────────────────────────────── Status ╮
197
+ │ ↑↓ section • q quit • /branchme help │
198
+ ├───────────────────┬──────────────────────────────────────────────────┤
199
+ │▶ Status │ STATUS │
200
+ │ Workflow │ Current branch: feature/super-long-branch-na…│
201
+ │ │ GitHub repository: very-long-owner-name/very-lo…│
202
+ │ │ GitHub token: present │
203
+ │ │ │
204
+ │ │ │
205
+ │ │ │
206
+ ├───────────────────┴──────────────────────────────────────────────────┤
207
+ │ 1/2 • warning • This deliberately long status note is captured to de…│
208
+ ╰──────────────────────────────────────────────────────────────────────╯
209
+ ```
package/img/demo.gif ADDED
Binary file
package/img/icon.png ADDED
Binary file
package/img/icon.svg ADDED
@@ -0,0 +1,28 @@
1
+ <svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <linearGradient id="bg" x1="160" y1="120" x2="864" y2="904" gradientUnits="userSpaceOnUse">
4
+ <stop offset="0" stop-color="#111827"/>
5
+ <stop offset="1" stop-color="#0F766E"/>
6
+ </linearGradient>
7
+ </defs>
8
+
9
+ <!-- App tile -->
10
+ <rect x="96" y="96" width="832" height="832" rx="208" fill="url(#bg)"/>
11
+
12
+ <!-- Main branch shape -->
13
+ <path d="M344 704V320" stroke="#F8FAFC" stroke-width="72" stroke-linecap="round"/>
14
+ <path d="M344 512H512C604 512 680 436 680 344" stroke="#F8FAFC" stroke-width="72" stroke-linecap="round" stroke-linejoin="round"/>
15
+ <path d="M344 512H512C604 512 680 588 680 680" stroke="#F8FAFC" stroke-width="72" stroke-linecap="round" stroke-linejoin="round"/>
16
+
17
+ <!-- Nodes -->
18
+ <circle cx="344" cy="320" r="72" fill="#34D399"/>
19
+ <circle cx="344" cy="704" r="72" fill="#38BDF8"/>
20
+ <circle cx="680" cy="344" r="72" fill="#38BDF8"/>
21
+ <circle cx="680" cy="680" r="72" fill="#34D399"/>
22
+
23
+ <!-- Inner node cutouts for a clean Git-like look -->
24
+ <circle cx="344" cy="320" r="30" fill="#111827" opacity="0.9"/>
25
+ <circle cx="344" cy="704" r="30" fill="#111827" opacity="0.9"/>
26
+ <circle cx="680" cy="344" r="30" fill="#111827" opacity="0.9"/>
27
+ <circle cx="680" cy="680" r="30" fill="#111827" opacity="0.9"/>
28
+ </svg>
package/img/image.png ADDED
Binary file
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@senad-d/branchme",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "description": "Minimal Pi extension for current-repository git branch, push, and pull request workflows.",
6
+ "license": "MIT",
7
+ "author": "Senad Dizdarević <112484166+senad-d@users.noreply.github.com>",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/senad-d/branchme.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/senad-d/branchme/issues"
14
+ },
15
+ "homepage": "https://github.com/senad-d/branchme#readme",
16
+ "keywords": [
17
+ "pi",
18
+ "pi-package",
19
+ "pi-extension",
20
+ "pi-coding-agent",
21
+ "branchme",
22
+ "git",
23
+ "git-branch",
24
+ "git-workflow",
25
+ "github",
26
+ "github-pr",
27
+ "pull-request",
28
+ "branch-workflow",
29
+ "automation",
30
+ "typescript"
31
+ ],
32
+ "engines": {
33
+ "node": ">=22.19.0"
34
+ },
35
+ "files": [
36
+ ".env.example",
37
+ "README.md",
38
+ "LICENSE",
39
+ "SECURITY.md",
40
+ "CHANGELOG.md",
41
+ "docs/**/*.md",
42
+ "img/**/*",
43
+ "src/**/*.ts",
44
+ "tsconfig.json"
45
+ ],
46
+ "scripts": {
47
+ "dev": "pi --no-extensions -e .",
48
+ "typecheck": "tsc --noEmit",
49
+ "lint": "npm run typecheck && npm run format:check && npm run check",
50
+ "format:check": "node scripts/check-format.mjs",
51
+ "test": "node --test test/*.test.mjs",
52
+ "smoke:pi": "node scripts/smoke-pi-runtime.mjs",
53
+ "smoke:pi:packed": "node scripts/smoke-pi-installed-package.mjs",
54
+ "check": "node --check scripts/check-format.mjs && node --check scripts/check-package-contents.mjs && node --check scripts/publish-npm.mjs && node --check scripts/smoke-pi-installed-package.mjs && node --check scripts/smoke-pi-runtime.mjs",
55
+ "pack:dry-run": "npm pack --dry-run --json",
56
+ "check:pack": "node scripts/check-package-contents.mjs",
57
+ "validate": "npm run typecheck && npm run format:check && npm run test && npm run smoke:pi && npm run check && npm run check:pack",
58
+ "release:check": "npm run validate && npm run smoke:pi:packed"
59
+ },
60
+ "pi": {
61
+ "extensions": [
62
+ "./src/extension.ts"
63
+ ],
64
+ "image": "https://raw.githubusercontent.com/senad-d/BranchMe/main/img/demo.gif"
65
+ },
66
+ "peerDependencies": {
67
+ "@earendil-works/pi-coding-agent": "*",
68
+ "@earendil-works/pi-tui": "*",
69
+ "typebox": "*"
70
+ },
71
+ "devDependencies": {
72
+ "@earendil-works/pi-coding-agent": "^0.80.2",
73
+ "@earendil-works/pi-tui": "^0.80.2",
74
+ "@types/node": "^26.0.1",
75
+ "typebox": "^1.2.18",
76
+ "typescript": "^6.0.3"
77
+ }
78
+ }
@@ -0,0 +1,166 @@
1
+ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
+ import { BRANCHME_COMMAND_NAME, EXTENSION_DISPLAY_NAME } from "../constants.ts";
3
+ import { getBranchStatus } from "../git.ts";
4
+ import { redactSecrets, repositoryLabel, resolveGitHubRepository, resolveGitHubToken } from "../github.ts";
5
+ import { BranchMePanel, type BranchMePanelData } from "../ui/branchme-panel.ts";
6
+
7
+ export type BranchMeCommandMode = "panel" | "help" | "unsupported";
8
+
9
+ function firstBranchMeArgumentLine(args: string): string {
10
+ return args.split(/\r?\n/u, 1)[0] ?? "";
11
+ }
12
+
13
+ export function parseBranchMeArgs(args: string): BranchMeCommandMode {
14
+ const normalized = firstBranchMeArgumentLine(args).trim().toLowerCase();
15
+ if (!normalized) return "panel";
16
+ if (normalized === "help" || normalized === "--help" || normalized === "-h") return "help";
17
+ return "unsupported";
18
+ }
19
+
20
+ export function formatUnsupportedBranchMeArgument(args: string): string {
21
+ return `Unknown /branchme argument ${JSON.stringify(firstBranchMeArgumentLine(args).trim())}. Use /branchme help.`;
22
+ }
23
+
24
+ export function getBranchMeHelpText(): string {
25
+ return [
26
+ "# BranchMe",
27
+ "",
28
+ "Current-repository branch workflow tools for Pi.",
29
+ "",
30
+ "Commands only show info; BranchMe tools perform actions.",
31
+ "",
32
+ "## Workflow",
33
+ "",
34
+ "1. `branch_status` — inspect repo and branch state.",
35
+ "2. `change_branch` / `create_branch` — switch to an existing clean branch or create one from `HEAD`.",
36
+ "3. Commit outside BranchMe.",
37
+ "4. `push_branch` — push the current branch.",
38
+ "5. `pull_request` — open a PR after `push_branch` completes and GitHub sees the branches.",
39
+ "",
40
+ "## Requirements",
41
+ "",
42
+ "- Run inside a Git repo with `git` available.",
43
+ "- For PRs: GitHub `origin` and `GITHUB_TOKEN` or `GH_TOKEN` (environment or `.env`).",
44
+ "- BranchMe never stages or commits.",
45
+ ].join("\n");
46
+ }
47
+
48
+ function emitCommandMessage(ctx: ExtensionCommandContext, message: string, level: "info" | "warning" | "error" = "info"): void {
49
+ if (ctx.hasUI) {
50
+ ctx.ui.notify(message, level);
51
+ return;
52
+ }
53
+
54
+ if (ctx.mode === "print") {
55
+ console.log(message);
56
+ }
57
+ }
58
+
59
+ function safeWarningText(error: unknown): string {
60
+ return redactSecrets(error instanceof Error ? error.message : String(error));
61
+ }
62
+
63
+ function repositoryWarningFromError(error: unknown): string | null {
64
+ const message = safeWarningText(error);
65
+ if (/Unable to resolve a GitHub repository from origin or GITHUB_REPOSITORY/iu.test(message)) return null;
66
+ if (/Not a git repository/iu.test(message)) return null;
67
+ return message;
68
+ }
69
+
70
+ function tokenWarningFromError(error: unknown): string | null {
71
+ const message = safeWarningText(error);
72
+ if (/GitHub token is required\. Set GITHUB_TOKEN or GH_TOKEN/iu.test(message)) return null;
73
+ return message;
74
+ }
75
+
76
+ function hasPanelWarning(data: BranchMePanelData): boolean {
77
+ return Boolean(data.statusNote || data.repositoryWarning || data.tokenWarning);
78
+ }
79
+
80
+ function summaryRepository(data: BranchMePanelData): string {
81
+ return data.repositoryWarning ? `warning: ${data.repositoryWarning}` : data.githubRepository ?? "not resolved";
82
+ }
83
+
84
+ function summaryToken(data: BranchMePanelData): string {
85
+ return data.tokenWarning ? `warning: ${data.tokenWarning}` : data.tokenSource ? `present (${data.tokenSource})` : "not set";
86
+ }
87
+
88
+ function summaryStatusWarning(data: BranchMePanelData): string {
89
+ return data.statusNote ? `; warning: ${data.statusNote}` : "";
90
+ }
91
+
92
+ async function collectPanelData(
93
+ pi: Pick<ExtensionAPI, "exec">,
94
+ ctx: ExtensionCommandContext,
95
+ ): Promise<BranchMePanelData> {
96
+ const data: BranchMePanelData = {
97
+ currentBranch: null,
98
+ detached: false,
99
+ githubRepository: null,
100
+ tokenSource: null,
101
+ };
102
+
103
+ let tokenLookupCwd: string | undefined;
104
+ try {
105
+ const status = await getBranchStatus(pi, ctx, ctx.signal);
106
+ data.currentBranch = status.currentBranch;
107
+ data.detached = status.detached;
108
+ tokenLookupCwd = status.repoRoot;
109
+ } catch (error) {
110
+ data.statusNote = safeWarningText(error);
111
+ }
112
+
113
+ if (!data.statusNote) {
114
+ try {
115
+ const repository = await resolveGitHubRepository(pi, ctx, ctx.signal);
116
+ data.githubRepository = repositoryLabel(repository);
117
+ } catch (error) {
118
+ data.githubRepository = null;
119
+ data.repositoryWarning = repositoryWarningFromError(error) ?? undefined;
120
+ }
121
+ }
122
+
123
+ try {
124
+ data.tokenSource = (await resolveGitHubToken(process.env, { cwd: tokenLookupCwd, signal: ctx.signal })).source;
125
+ } catch (error) {
126
+ data.tokenSource = null;
127
+ data.tokenWarning = tokenWarningFromError(error) ?? undefined;
128
+ }
129
+
130
+ return data;
131
+ }
132
+
133
+ async function showPanel(pi: Pick<ExtensionAPI, "exec">, ctx: ExtensionCommandContext): Promise<void> {
134
+ const data = await collectPanelData(pi, ctx);
135
+
136
+ if (ctx.mode !== "tui") {
137
+ const branch = data.detached ? "detached HEAD" : data.currentBranch ?? "unknown";
138
+ emitCommandMessage(
139
+ ctx,
140
+ `${EXTENSION_DISPLAY_NAME}: branch ${branch}; GitHub repository ${summaryRepository(data)}; token ${summaryToken(data)}${summaryStatusWarning(data)}.`,
141
+ hasPanelWarning(data) ? "warning" : "info",
142
+ );
143
+ return;
144
+ }
145
+
146
+ await ctx.ui.custom<void>((tui, theme, _keybindings, done) => new BranchMePanel(data, theme, () => done(undefined), () => tui.requestRender()));
147
+ }
148
+
149
+ export function registerBranchMeCommand(pi: Pick<ExtensionAPI, "registerCommand" | "exec">): void {
150
+ pi.registerCommand(BRANCHME_COMMAND_NAME, {
151
+ description: "Show BranchMe status and help",
152
+ handler: async (args, ctx) => {
153
+ const mode = parseBranchMeArgs(args);
154
+ if (mode === "help") {
155
+ emitCommandMessage(ctx, getBranchMeHelpText(), "info");
156
+ return;
157
+ }
158
+ if (mode === "unsupported") {
159
+ emitCommandMessage(ctx, formatUnsupportedBranchMeArgument(args), "warning");
160
+ return;
161
+ }
162
+
163
+ await showPanel(pi, ctx);
164
+ },
165
+ });
166
+ }
@@ -0,0 +1,27 @@
1
+ export const EXTENSION_DISPLAY_NAME = "BranchMe";
2
+ export const BRANCHME_COMMAND_NAME = "branchme";
3
+
4
+ export const BRANCH_STATUS_TOOL_NAME = "branch_status";
5
+ export const CREATE_BRANCH_TOOL_NAME = "create_branch";
6
+ export const CHANGE_BRANCH_TOOL_NAME = "change_branch";
7
+ export const PUSH_BRANCH_TOOL_NAME = "push_branch";
8
+ export const PULL_REQUEST_TOOL_NAME = "pull_request";
9
+
10
+ export const BRANCHME_TOOL_NAMES = [
11
+ BRANCH_STATUS_TOOL_NAME,
12
+ CREATE_BRANCH_TOOL_NAME,
13
+ CHANGE_BRANCH_TOOL_NAME,
14
+ PUSH_BRANCH_TOOL_NAME,
15
+ PULL_REQUEST_TOOL_NAME,
16
+ ] as const;
17
+
18
+ export const GIT_STATUS_TIMEOUT_MS = 5_000;
19
+ export const GIT_MUTATION_TIMEOUT_MS = 30_000;
20
+ export const GIT_PUSH_TIMEOUT_MS = 120_000;
21
+
22
+ export const GITHUB_API_BASE_URL = "https://api.github.com";
23
+ export const GITHUB_API_VERSION = "2022-11-28";
24
+ export const GITHUB_USER_AGENT = "BranchMe Pi extension";
25
+
26
+ export const MAX_SUMMARY_OUTPUT_CHARS = 4_000;
27
+ export const GITHUB_RESPONSE_BODY_LIMIT_BYTES = 64 * 1024;
@@ -0,0 +1,10 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { registerBranchMeCommand } from "./commands/branchme-command.ts";
3
+ import { registerBranchMeTools } from "./tools/branchme-tools.ts";
4
+
5
+ export function branchMeExtension(pi: ExtensionAPI): void {
6
+ registerBranchMeCommand(pi);
7
+ registerBranchMeTools(pi);
8
+ }
9
+
10
+ export default branchMeExtension;