bosun 0.36.2 → 0.36.4
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/agent-prompts.mjs +95 -0
- package/analyze-agent-work-helpers.mjs +308 -0
- package/analyze-agent-work.mjs +926 -0
- package/autofix.mjs +2 -0
- package/bosun.schema.json +101 -3
- package/codex-shell.mjs +85 -10
- package/desktop/main.mjs +871 -48
- package/desktop/preload.mjs +54 -1
- package/desktop-shortcut.mjs +90 -11
- package/git-editor-fix.mjs +273 -0
- package/mcp-registry.mjs +579 -0
- package/meeting-workflow-service.mjs +631 -0
- package/monitor.mjs +18 -103
- package/package.json +21 -2
- package/primary-agent.mjs +32 -12
- package/session-tracker.mjs +68 -0
- package/setup-web-server.mjs +20 -10
- package/setup.mjs +376 -83
- package/startup-service.mjs +51 -6
- package/stream-resilience.mjs +17 -7
- package/ui/app.js +164 -4
- package/ui/components/agent-selector.js +145 -1
- package/ui/components/chat-view.js +161 -15
- package/ui/components/session-list.js +2 -2
- package/ui/components/shared.js +188 -15
- package/ui/modules/icons.js +13 -0
- package/ui/modules/utils.js +44 -0
- package/ui/modules/voice-client-sdk.js +733 -0
- package/ui/modules/voice-overlay.js +128 -15
- package/ui/modules/voice.js +15 -6
- package/ui/setup.html +281 -81
- package/ui/styles/components.css +99 -3
- package/ui/styles/sessions.css +122 -14
- package/ui/styles.css +14 -0
- package/ui/tabs/agents.js +1 -1
- package/ui/tabs/chat.js +123 -14
- package/ui/tabs/control.js +16 -22
- package/ui/tabs/dashboard.js +85 -8
- package/ui/tabs/library.js +113 -17
- package/ui/tabs/settings.js +116 -2
- package/ui/tabs/tasks.js +388 -39
- package/ui/tabs/telemetry.js +0 -1
- package/ui/tabs/workflows.js +4 -0
- package/ui-server.mjs +400 -22
- package/update-check.mjs +41 -13
- package/voice-action-dispatcher.mjs +844 -0
- package/voice-agents-sdk.mjs +664 -0
- package/voice-auth-manager.mjs +164 -0
- package/voice-relay.mjs +1194 -0
- package/voice-tools.mjs +914 -0
- package/workflow-templates/agents.mjs +6 -2
- package/workflow-templates/github.mjs +154 -12
- package/workflow-templates.mjs +3 -0
- package/github-reconciler.mjs +0 -506
- package/merge-strategy.mjs +0 -1210
- package/pr-cleanup-daemon.mjs +0 -992
- package/workspace-reaper.mjs +0 -405
package/desktop/preload.mjs
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
|
-
import { contextBridge } from "electron";
|
|
1
|
+
import { contextBridge, ipcRenderer } from "electron";
|
|
2
2
|
|
|
3
3
|
contextBridge.exposeInMainWorld("veDesktop", {
|
|
4
4
|
platform: process.platform,
|
|
5
|
+
|
|
6
|
+
follow: {
|
|
7
|
+
open: async (detail = {}) => {
|
|
8
|
+
return ipcRenderer.invoke("bosun:desktop:follow:open", detail || {});
|
|
9
|
+
},
|
|
10
|
+
hide: async () => {
|
|
11
|
+
return ipcRenderer.invoke("bosun:desktop:follow:hide");
|
|
12
|
+
},
|
|
13
|
+
restore: async () => {
|
|
14
|
+
return ipcRenderer.invoke("bosun:desktop:follow:restore");
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Keyboard shortcuts API.
|
|
20
|
+
* Available in the renderer via `window.veDesktop.shortcuts.*`
|
|
21
|
+
*/
|
|
22
|
+
shortcuts: {
|
|
23
|
+
/**
|
|
24
|
+
* Returns the full shortcuts catalog with current effective accelerators.
|
|
25
|
+
* @returns {Promise<ShortcutEntry[]>}
|
|
26
|
+
*/
|
|
27
|
+
list: () => ipcRenderer.invoke("bosun:shortcuts:list"),
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Set a custom accelerator for a shortcut.
|
|
31
|
+
* Pass `null` as accelerator to disable the shortcut.
|
|
32
|
+
* @param {string} id
|
|
33
|
+
* @param {string|null} accelerator Electron accelerator string or null.
|
|
34
|
+
* @returns {Promise<{ ok: boolean, error?: string }>}
|
|
35
|
+
*/
|
|
36
|
+
set: (id, accelerator) =>
|
|
37
|
+
ipcRenderer.invoke("bosun:shortcuts:set", { id, accelerator }),
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Reset a single shortcut to its default accelerator.
|
|
41
|
+
* @param {string} id
|
|
42
|
+
* @returns {Promise<{ ok: boolean, error?: string }>}
|
|
43
|
+
*/
|
|
44
|
+
reset: (id) => ipcRenderer.invoke("bosun:shortcuts:reset", { id }),
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Reset all shortcuts to their defaults.
|
|
48
|
+
* @returns {Promise<{ ok: boolean }>}
|
|
49
|
+
*/
|
|
50
|
+
resetAll: () => ipcRenderer.invoke("bosun:shortcuts:resetAll"),
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Show the native keyboard shortcuts reference dialog.
|
|
54
|
+
* @returns {Promise<{ ok: boolean }>}
|
|
55
|
+
*/
|
|
56
|
+
showDialog: () => ipcRenderer.invoke("bosun:shortcuts:showDialog"),
|
|
57
|
+
},
|
|
5
58
|
});
|
package/desktop-shortcut.mjs
CHANGED
|
@@ -47,10 +47,91 @@ function getCliPath() {
|
|
|
47
47
|
return resolve(__dirname, "cli.mjs");
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
function getDesktopMainPath() {
|
|
51
|
+
return resolve(__dirname, "desktop", "main.mjs");
|
|
52
|
+
}
|
|
53
|
+
|
|
50
54
|
function getWorkingDirectory() {
|
|
51
55
|
return __dirname;
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Attempt to locate the Electron binary that should be used to launch the
|
|
60
|
+
* Bosun desktop app.
|
|
61
|
+
*
|
|
62
|
+
* Search order:
|
|
63
|
+
* 1. BOSUN_ELECTRON_PATH env var — explicit override
|
|
64
|
+
* 2. <bosun-dir>/node_modules/.bin/electron(.cmd)
|
|
65
|
+
* 3. <bosun-dir>/../node_modules/.bin/electron(.cmd) (repo root)
|
|
66
|
+
* 4. electron(.cmd) anywhere on $PATH
|
|
67
|
+
*
|
|
68
|
+
* Returns the resolved absolute path to the electron binary, or null when
|
|
69
|
+
* none could be located (falls back to node + cli.mjs --desktop).
|
|
70
|
+
*/
|
|
71
|
+
function findElectronBinary() {
|
|
72
|
+
// 1. Explicit env override
|
|
73
|
+
const envPath = process.env.BOSUN_ELECTRON_PATH;
|
|
74
|
+
if (envPath) {
|
|
75
|
+
const resolved = resolve(envPath);
|
|
76
|
+
if (existsSync(resolved)) return resolved;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const isWin = process.platform === "win32";
|
|
80
|
+
const candidates = isWin
|
|
81
|
+
? [
|
|
82
|
+
resolve(__dirname, "node_modules", ".bin", "electron.cmd"),
|
|
83
|
+
resolve(__dirname, "..", "node_modules", ".bin", "electron.cmd"),
|
|
84
|
+
resolve(__dirname, "node_modules", ".bin", "electron.exe"),
|
|
85
|
+
resolve(__dirname, "..", "node_modules", ".bin", "electron.exe"),
|
|
86
|
+
]
|
|
87
|
+
: [
|
|
88
|
+
resolve(__dirname, "node_modules", ".bin", "electron"),
|
|
89
|
+
resolve(__dirname, "..", "node_modules", ".bin", "electron"),
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
for (const c of candidates) {
|
|
93
|
+
if (existsSync(c)) return c;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 4. Search $PATH
|
|
97
|
+
try {
|
|
98
|
+
const cmd = isWin ? "where electron.cmd 2>nul" : "which electron 2>/dev/null";
|
|
99
|
+
const found = execSync(cmd, { encoding: "utf8", stdio: "pipe", timeout: 2000 })
|
|
100
|
+
.trim()
|
|
101
|
+
.split("\n")[0]
|
|
102
|
+
.trim();
|
|
103
|
+
if (found && existsSync(found)) return found;
|
|
104
|
+
} catch {
|
|
105
|
+
/* not found on PATH */
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Return `{ executable, args }` for launching the Bosun desktop app.
|
|
113
|
+
*
|
|
114
|
+
* - If an Electron binary is found: launches `electron desktop/main.mjs`
|
|
115
|
+
* - Otherwise: falls back to `node cli.mjs --desktop`
|
|
116
|
+
*/
|
|
117
|
+
export function resolveElectronLauncher() {
|
|
118
|
+
const electronPath = findElectronBinary();
|
|
119
|
+
if (electronPath) {
|
|
120
|
+
return { executable: electronPath, args: [getDesktopMainPath()] };
|
|
121
|
+
}
|
|
122
|
+
return { executable: getNodePath(), args: [getCliPath(), "--desktop"] };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Build a quoted shell command string for the launcher.
|
|
127
|
+
* Used by macOS .command fallback and Linux .desktop Exec= field.
|
|
128
|
+
*/
|
|
129
|
+
function buildShellCommand() {
|
|
130
|
+
const { executable, args } = resolveElectronLauncher();
|
|
131
|
+
const quotedArgs = args.map((a) => `"${a}"`).join(" ");
|
|
132
|
+
return `"${executable}" ${quotedArgs}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
54
135
|
function parseLinuxDesktopDir() {
|
|
55
136
|
try {
|
|
56
137
|
const configPath = resolve(homedir(), ".config", "user-dirs.dirs");
|
|
@@ -93,12 +174,6 @@ function ensureDesktopDir(dir) {
|
|
|
93
174
|
}
|
|
94
175
|
}
|
|
95
176
|
|
|
96
|
-
function buildShellCommand() {
|
|
97
|
-
const nodePath = getNodePath();
|
|
98
|
-
const cliPath = getCliPath();
|
|
99
|
-
return `"${nodePath}" "${cliPath}" --desktop`;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
177
|
function escapePowerShell(value) {
|
|
103
178
|
return String(value).replace(/'/g, "''");
|
|
104
179
|
}
|
|
@@ -109,17 +184,21 @@ function escapeAppleScript(value) {
|
|
|
109
184
|
|
|
110
185
|
function installWindowsShortcut(desktopDir) {
|
|
111
186
|
const shortcutPath = resolve(desktopDir, `${APP_NAME}.lnk`);
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
const args = `"${cliPath}" --desktop`;
|
|
187
|
+
const { executable, args } = resolveElectronLauncher();
|
|
188
|
+
const quotedArgs = args.map((a) => `"${a}"`).join(" ");
|
|
115
189
|
const workingDir = getWorkingDirectory();
|
|
190
|
+
const iconPath = resolve(__dirname, "logo.png");
|
|
191
|
+
const iconLine = existsSync(iconPath)
|
|
192
|
+
? `$Shortcut.IconLocation = '${escapePowerShell(iconPath)}'`
|
|
193
|
+
: "";
|
|
116
194
|
const psScript = `
|
|
117
195
|
$WshShell = New-Object -ComObject WScript.Shell
|
|
118
196
|
$Shortcut = $WshShell.CreateShortcut('${escapePowerShell(shortcutPath)}')
|
|
119
|
-
$Shortcut.TargetPath = '${escapePowerShell(
|
|
120
|
-
$Shortcut.Arguments = '${escapePowerShell(
|
|
197
|
+
$Shortcut.TargetPath = '${escapePowerShell(executable)}'
|
|
198
|
+
$Shortcut.Arguments = '${escapePowerShell(quotedArgs)}'
|
|
121
199
|
$Shortcut.WorkingDirectory = '${escapePowerShell(workingDir)}'
|
|
122
200
|
$Shortcut.Description = 'Bosun Desktop Portal'
|
|
201
|
+
${iconLine}
|
|
123
202
|
$Shortcut.Save()
|
|
124
203
|
`.trim();
|
|
125
204
|
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* git-editor-fix.mjs — Prevent agents from opening interactive editors
|
|
4
|
+
*
|
|
5
|
+
* Problem: Agents inherit user's git config which uses VSCode (`code --wait`)
|
|
6
|
+
* Result: Git operations block waiting for editor, freezing agents
|
|
7
|
+
*
|
|
8
|
+
* Solution: Set GIT_EDITOR=true (or GIT_EDITOR=:) for non-interactive mode
|
|
9
|
+
*
|
|
10
|
+
* This script ensures all agent workspaces have non-blocking git config.
|
|
11
|
+
* Covers: main repo, tmpclaude-* workspaces, git worktrees (ve/*),
|
|
12
|
+
* and VK task worktrees under $TEMP/vibe-kanban/worktrees/.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { execSync } from "child_process";
|
|
16
|
+
import { existsSync, readdirSync } from "fs";
|
|
17
|
+
import { resolve, basename } from "path";
|
|
18
|
+
import { tmpdir } from "os";
|
|
19
|
+
import { fileURLToPath } from "url";
|
|
20
|
+
import { resolveRepoRoot } from "./repo-root.mjs";
|
|
21
|
+
|
|
22
|
+
const __dirname = resolve(fileURLToPath(new URL(".", import.meta.url)));
|
|
23
|
+
const REPO_ROOT = resolveRepoRoot();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Configure git to never open interactive editors
|
|
27
|
+
* @param {string} workspacePath - Path to workspace directory
|
|
28
|
+
* @returns {boolean} true if configured successfully
|
|
29
|
+
*/
|
|
30
|
+
function configureNonInteractiveGit(workspacePath) {
|
|
31
|
+
const gitDir = resolve(workspacePath, ".git");
|
|
32
|
+
|
|
33
|
+
// .git can be a file (worktree link) or a directory — both are valid
|
|
34
|
+
if (!existsSync(gitDir)) {
|
|
35
|
+
console.warn(`[git-editor-fix] No .git entry at ${workspacePath}`);
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// Set local git config for this workspace
|
|
41
|
+
// Use ':' (colon) as no-op editor — POSIX standard that always succeeds
|
|
42
|
+
execSync("git config --local core.editor :", {
|
|
43
|
+
cwd: workspacePath,
|
|
44
|
+
stdio: "pipe",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Also disable merge commit editor prompts
|
|
48
|
+
execSync("git config --local merge.commit.autoEdit no", {
|
|
49
|
+
cwd: workspacePath,
|
|
50
|
+
stdio: "pipe",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
console.log(
|
|
54
|
+
`[git-editor-fix] ✓ Configured ${workspacePath} for non-interactive git`,
|
|
55
|
+
);
|
|
56
|
+
return true;
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error(
|
|
59
|
+
`[git-editor-fix] Failed to configure ${workspacePath}:`,
|
|
60
|
+
err.message,
|
|
61
|
+
);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Workspace discovery helpers ──────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Collect tmpclaude-* directories under REPO_ROOT
|
|
70
|
+
* @returns {string[]}
|
|
71
|
+
*/
|
|
72
|
+
function findTmpclaudeWorkspaces() {
|
|
73
|
+
/** @type {string[]} */
|
|
74
|
+
const results = [];
|
|
75
|
+
try {
|
|
76
|
+
const entries = readdirSync(REPO_ROOT, { withFileTypes: true });
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
if (entry.isDirectory() && entry.name.startsWith("tmpclaude-")) {
|
|
79
|
+
results.push(resolve(REPO_ROOT, entry.name));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} catch (err) {
|
|
83
|
+
console.error(
|
|
84
|
+
"[git-editor-fix] Failed to scan tmpclaude workspaces:",
|
|
85
|
+
err.message,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return results;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Parse `git worktree list --porcelain` and return paths of ve/* worktrees
|
|
93
|
+
* @returns {string[]}
|
|
94
|
+
*/
|
|
95
|
+
function findGitWorktrees() {
|
|
96
|
+
/** @type {string[]} */
|
|
97
|
+
const results = [];
|
|
98
|
+
try {
|
|
99
|
+
const raw = execSync("git worktree list --porcelain", {
|
|
100
|
+
cwd: REPO_ROOT,
|
|
101
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
102
|
+
encoding: "utf-8",
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Porcelain output has blocks separated by blank lines.
|
|
106
|
+
// Each block starts with "worktree <path>".
|
|
107
|
+
// We also look for "branch refs/heads/ve/..." to identify VK worktrees,
|
|
108
|
+
// but we include ALL worktrees — they all need the fix.
|
|
109
|
+
for (const line of raw.split("\n")) {
|
|
110
|
+
const match = line.match(/^worktree\s+(.+)$/);
|
|
111
|
+
if (match) {
|
|
112
|
+
const wtPath = match[1].trim();
|
|
113
|
+
// Skip the bare repo entry if present
|
|
114
|
+
if (existsSync(resolve(wtPath, ".git"))) {
|
|
115
|
+
results.push(wtPath);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} catch (err) {
|
|
120
|
+
// git worktree list fails if the repo has no worktrees — that's fine
|
|
121
|
+
if (!String(err.message).includes("is not a git repository")) {
|
|
122
|
+
console.warn(
|
|
123
|
+
"[git-editor-fix] Could not enumerate git worktrees:",
|
|
124
|
+
err.message,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return results;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Scan $TEMP/vibe-kanban/worktrees/ for VK task worktree directories
|
|
133
|
+
* @returns {string[]}
|
|
134
|
+
*/
|
|
135
|
+
function findVKWorktrees() {
|
|
136
|
+
/** @type {string[]} */
|
|
137
|
+
const results = [];
|
|
138
|
+
const vkBase = resolve(tmpdir(), "vibe-kanban", "worktrees");
|
|
139
|
+
|
|
140
|
+
if (!existsSync(vkBase)) {
|
|
141
|
+
return results;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const entries = readdirSync(vkBase, { withFileTypes: true });
|
|
146
|
+
for (const entry of entries) {
|
|
147
|
+
if (entry.isDirectory()) {
|
|
148
|
+
const candidate = resolve(vkBase, entry.name);
|
|
149
|
+
if (existsSync(resolve(candidate, ".git"))) {
|
|
150
|
+
results.push(candidate);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} catch (err) {
|
|
155
|
+
console.error("[git-editor-fix] Failed to scan VK worktrees:", err.message);
|
|
156
|
+
}
|
|
157
|
+
return results;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── Main functions ───────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Scan for all agent workspaces and fix git config.
|
|
164
|
+
* Sources:
|
|
165
|
+
* 1. Main repo root (REPO_ROOT)
|
|
166
|
+
* 2. tmpclaude-* directories
|
|
167
|
+
* 3. git worktrees (parsed from `git worktree list --porcelain`)
|
|
168
|
+
* 4. VK task worktrees under $TEMP/vibe-kanban/worktrees/
|
|
169
|
+
*
|
|
170
|
+
* Paths are deduplicated before configuration.
|
|
171
|
+
*/
|
|
172
|
+
function fixAllWorkspaces() {
|
|
173
|
+
console.log("[git-editor-fix] Scanning for agent workspaces...");
|
|
174
|
+
|
|
175
|
+
/** @type {Set<string>} */
|
|
176
|
+
const seen = new Set();
|
|
177
|
+
|
|
178
|
+
/** @param {string} p */
|
|
179
|
+
const add = (p) => {
|
|
180
|
+
const normalized = resolve(p);
|
|
181
|
+
seen.add(normalized);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// 1. Main repo root
|
|
185
|
+
add(REPO_ROOT);
|
|
186
|
+
|
|
187
|
+
// 2. tmpclaude-* directories
|
|
188
|
+
for (const ws of findTmpclaudeWorkspaces()) {
|
|
189
|
+
add(ws);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 3. Git worktrees (includes ve/* branches)
|
|
193
|
+
for (const ws of findGitWorktrees()) {
|
|
194
|
+
add(ws);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 4. VK worktrees under $TEMP
|
|
198
|
+
for (const ws of findVKWorktrees()) {
|
|
199
|
+
add(ws);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const workspaces = [...seen];
|
|
203
|
+
console.log(
|
|
204
|
+
`[git-editor-fix] Found ${workspaces.length} workspace(s) to configure`,
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
let fixed = 0;
|
|
208
|
+
for (const ws of workspaces) {
|
|
209
|
+
if (configureNonInteractiveGit(ws)) {
|
|
210
|
+
fixed++;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
console.log(
|
|
215
|
+
`[git-editor-fix] ✓ Fixed ${fixed}/${workspaces.length} workspaces`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Convenience wrapper: configure the main repo and all discoverable worktrees
|
|
221
|
+
* in a single call. Suitable for use from other modules.
|
|
222
|
+
* @returns {{ fixed: number, total: number }}
|
|
223
|
+
*/
|
|
224
|
+
function configureRepoAndWorktrees() {
|
|
225
|
+
console.log("[git-editor-fix] Configuring repo and all worktrees...");
|
|
226
|
+
|
|
227
|
+
/** @type {Set<string>} */
|
|
228
|
+
const seen = new Set();
|
|
229
|
+
|
|
230
|
+
const add = (/** @type {string} */ p) => seen.add(resolve(p));
|
|
231
|
+
|
|
232
|
+
add(REPO_ROOT);
|
|
233
|
+
findTmpclaudeWorkspaces().forEach(add);
|
|
234
|
+
findGitWorktrees().forEach(add);
|
|
235
|
+
findVKWorktrees().forEach(add);
|
|
236
|
+
|
|
237
|
+
const workspaces = [...seen];
|
|
238
|
+
let fixed = 0;
|
|
239
|
+
for (const ws of workspaces) {
|
|
240
|
+
if (configureNonInteractiveGit(ws)) {
|
|
241
|
+
fixed++;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
console.log(
|
|
246
|
+
`[git-editor-fix] ✓ Configured ${fixed}/${workspaces.length} workspace(s)`,
|
|
247
|
+
);
|
|
248
|
+
return { fixed, total: workspaces.length };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ── CLI Entry Point ──────────────────────────────────────────────────────────
|
|
252
|
+
|
|
253
|
+
const isMainModule = () => {
|
|
254
|
+
try {
|
|
255
|
+
const modulePath = fileURLToPath(import.meta.url);
|
|
256
|
+
return process.argv[1] === modulePath;
|
|
257
|
+
} catch {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
if (isMainModule()) {
|
|
263
|
+
fixAllWorkspaces();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export {
|
|
267
|
+
configureNonInteractiveGit,
|
|
268
|
+
fixAllWorkspaces,
|
|
269
|
+
configureRepoAndWorktrees,
|
|
270
|
+
findGitWorktrees,
|
|
271
|
+
findVKWorktrees,
|
|
272
|
+
findTmpclaudeWorkspaces,
|
|
273
|
+
};
|