bosun 0.38.1 → 0.38.12
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/bosun-skills.mjs +6 -6
- package/bosun.schema.json +31 -1
- package/config.mjs +8 -0
- package/desktop/main.mjs +363 -17
- package/library-manager.mjs +9 -9
- package/manual-flows.mjs +1390 -0
- package/opencode-providers.mjs +483 -0
- package/opencode-shell.mjs +76 -8
- package/package.json +4 -1
- package/setup.mjs +321 -9
- package/task-complexity.mjs +11 -0
- package/task-executor.mjs +0 -217
- package/telegram-sentinel.mjs +153 -10
- package/tools/workflow-orphan-worktree-recovery.mjs +452 -0
- package/ui/app.js +1 -1
- package/ui/demo.html +4 -0
- package/ui/modules/voice-overlay.js +197 -16
- package/ui/styles/layout.css +2 -0
- package/ui/tabs/library.js +48 -7
- package/ui/tabs/manual-flows.js +142 -7
- package/ui/tabs/telemetry.js +12 -0
- package/ui/tabs/workflows.js +223 -33
- package/ui-server.mjs +393 -44
- package/workflow-engine.mjs +14 -0
- package/workflow-nodes.mjs +213 -10
- package/workflow-templates/coverage.mjs +226 -0
- package/workflow-templates/reliability.mjs +98 -0
- package/workflow-templates/task-lifecycle.mjs +6 -0
- package/workflow-templates.mjs +26 -2
package/bosun-skills.mjs
CHANGED
|
@@ -790,12 +790,12 @@ runtime tokens and dramatically reduce exploration time.
|
|
|
790
790
|
Use structured comment headers that agents are trained to recognize:
|
|
791
791
|
|
|
792
792
|
\\\`\\\`\\\`
|
|
793
|
-
//
|
|
793
|
+
// BOSUN:SUMMARY — <module-name>
|
|
794
794
|
// <1–3 sentence summary of purpose, key types, and public API>
|
|
795
795
|
\\\`\\\`\\\`
|
|
796
796
|
|
|
797
797
|
\\\`\\\`\\\`
|
|
798
|
-
//
|
|
798
|
+
// BOSUN:WARN — <module-name>
|
|
799
799
|
// <non-obvious pitfall, race condition, or constraint agents MUST know>
|
|
800
800
|
\\\`\\\`\\\`
|
|
801
801
|
|
|
@@ -821,7 +821,7 @@ Output: \\\`.bosun/audit/inventory.json\\\`
|
|
|
821
821
|
### Phase 2 — Summaries
|
|
822
822
|
For every file where \\\`has_summary === false\\\` and \\\`category !== "generated"\\\`:
|
|
823
823
|
1. Read the file.
|
|
824
|
-
2. Write a \\\`
|
|
824
|
+
2. Write a \\\`BOSUN:SUMMARY\\\` comment at the top.
|
|
825
825
|
3. Stage the file.
|
|
826
826
|
|
|
827
827
|
### Phase 3 — Warnings
|
|
@@ -831,7 +831,7 @@ For every file, check for non-obvious constraints:
|
|
|
831
831
|
- Order-dependent initialization
|
|
832
832
|
- Platform-specific behavior (Windows paths, etc.)
|
|
833
833
|
|
|
834
|
-
Add \\\`
|
|
834
|
+
Add \\\`BOSUN:WARN\\\` comments where found.
|
|
835
835
|
|
|
836
836
|
### Phase 4 — Manifest Audit
|
|
837
837
|
Ensure \\\`AGENTS.md\\\` (or equivalent) at repo root is accurate:
|
|
@@ -845,8 +845,8 @@ If the file is outdated or missing sections, append corrections.
|
|
|
845
845
|
|
|
846
846
|
### Phase 5 — Conformity Check
|
|
847
847
|
Re-scan all annotations and validate:
|
|
848
|
-
- \\\`
|
|
849
|
-
- \\\`
|
|
848
|
+
- \\\`BOSUN:SUMMARY\\\` is present in every non-trivial source file.
|
|
849
|
+
- \\\`BOSUN:WARN\\\` exists for files with known pitfalls.
|
|
850
850
|
- No stale annotations reference symbols/functions that no longer exist.
|
|
851
851
|
|
|
852
852
|
Output: \\\`.bosun/audit/conformity-report.json\\\`
|
package/bosun.schema.json
CHANGED
|
@@ -888,7 +888,37 @@
|
|
|
888
888
|
},
|
|
889
889
|
"weight": { "type": "number" },
|
|
890
890
|
"role": { "type": "string" },
|
|
891
|
-
"enabled": { "type": "boolean" }
|
|
891
|
+
"enabled": { "type": "boolean" },
|
|
892
|
+
"provider": {
|
|
893
|
+
"type": "string",
|
|
894
|
+
"description": "AI provider for this executor (e.g. anthropic, openai, ollama, nebius, scaleway, deepinfra, qiniu, cloudflare, xiaomi, synthetic, perplexity)"
|
|
895
|
+
},
|
|
896
|
+
"providerConfig": {
|
|
897
|
+
"type": "object",
|
|
898
|
+
"description": "Provider-specific configuration for this executor",
|
|
899
|
+
"properties": {
|
|
900
|
+
"apiKey": {
|
|
901
|
+
"type": "string",
|
|
902
|
+
"description": "API key for this provider (overrides env)"
|
|
903
|
+
},
|
|
904
|
+
"baseUrl": {
|
|
905
|
+
"type": "string",
|
|
906
|
+
"description": "API base URL for this provider"
|
|
907
|
+
},
|
|
908
|
+
"model": {
|
|
909
|
+
"type": "string",
|
|
910
|
+
"description": "Model ID to use with this provider (e.g. claude-sonnet-4-20250514)"
|
|
911
|
+
},
|
|
912
|
+
"port": {
|
|
913
|
+
"type": "number",
|
|
914
|
+
"description": "Port for local server (OpenCode)"
|
|
915
|
+
},
|
|
916
|
+
"timeoutMs": {
|
|
917
|
+
"type": "number",
|
|
918
|
+
"description": "Request timeout in milliseconds"
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
892
922
|
}
|
|
893
923
|
},
|
|
894
924
|
"failover": {
|
package/config.mjs
CHANGED
|
@@ -530,6 +530,12 @@ function normalizeExecutorEntry(entry, index = 0, total = 1) {
|
|
|
530
530
|
entry.codexProfile || entry.modelProfile || "",
|
|
531
531
|
).trim();
|
|
532
532
|
|
|
533
|
+
// Provider configuration for the executor (e.g. opencode with specific provider)
|
|
534
|
+
const provider = String(entry.provider || "").trim() || null;
|
|
535
|
+
const providerConfig = entry.providerConfig && typeof entry.providerConfig === "object"
|
|
536
|
+
? { ...entry.providerConfig }
|
|
537
|
+
: null;
|
|
538
|
+
|
|
533
539
|
return {
|
|
534
540
|
name,
|
|
535
541
|
executor: executorType,
|
|
@@ -539,6 +545,8 @@ function normalizeExecutorEntry(entry, index = 0, total = 1) {
|
|
|
539
545
|
enabled: entry.enabled !== false,
|
|
540
546
|
models,
|
|
541
547
|
codexProfile,
|
|
548
|
+
provider,
|
|
549
|
+
providerConfig,
|
|
542
550
|
};
|
|
543
551
|
}
|
|
544
552
|
|
package/desktop/main.mjs
CHANGED
|
@@ -13,11 +13,11 @@ import {
|
|
|
13
13
|
} from "electron";
|
|
14
14
|
import { dirname, join, resolve } from "node:path";
|
|
15
15
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
16
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
16
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
17
17
|
import { execFileSync, spawn } from "node:child_process";
|
|
18
18
|
import { request as httpRequest } from "node:http";
|
|
19
19
|
import { request as httpsRequest } from "node:https";
|
|
20
|
-
import { homedir } from "node:os";
|
|
20
|
+
import { homedir, tmpdir } from "node:os";
|
|
21
21
|
|
|
22
22
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
23
|
|
|
@@ -921,9 +921,8 @@ function buildAppMenu() {
|
|
|
921
921
|
},
|
|
922
922
|
{ type: /** @type {const} */ ("separator") },
|
|
923
923
|
{
|
|
924
|
-
label: "Check for Updates",
|
|
925
|
-
|
|
926
|
-
click: () => maybeAutoUpdate().catch(() => {}),
|
|
924
|
+
label: "Check for Updates\u2026",
|
|
925
|
+
click: () => checkForUpdateDesktop().catch(() => {}),
|
|
927
926
|
},
|
|
928
927
|
],
|
|
929
928
|
},
|
|
@@ -1200,12 +1199,36 @@ async function createMainWindow() {
|
|
|
1200
1199
|
});
|
|
1201
1200
|
|
|
1202
1201
|
mainWindow.on("close", (event) => {
|
|
1203
|
-
//
|
|
1202
|
+
// Programmatic quit — let it through.
|
|
1204
1203
|
if (shuttingDown) return;
|
|
1205
1204
|
event.preventDefault();
|
|
1206
1205
|
if (mainWindow?.isMinimized()) return;
|
|
1207
|
-
|
|
1208
|
-
|
|
1206
|
+
|
|
1207
|
+
// Show an exit dialog so the user can choose what to do.
|
|
1208
|
+
dialog
|
|
1209
|
+
.showMessageBox(mainWindow, {
|
|
1210
|
+
type: "question",
|
|
1211
|
+
title: "Close Bosun",
|
|
1212
|
+
message: "What would you like to do?",
|
|
1213
|
+
buttons: ["Hide to Taskbar", "Exit Bosun", "Cancel"],
|
|
1214
|
+
defaultId: 0,
|
|
1215
|
+
cancelId: 2,
|
|
1216
|
+
noLink: true,
|
|
1217
|
+
})
|
|
1218
|
+
.then(({ response }) => {
|
|
1219
|
+
if (response === 0) {
|
|
1220
|
+
// Hide to taskbar / system tray
|
|
1221
|
+
mainWindow?.hide();
|
|
1222
|
+
if (trayMode && process.platform === "darwin") {
|
|
1223
|
+
app.dock?.hide();
|
|
1224
|
+
}
|
|
1225
|
+
} else if (response === 1) {
|
|
1226
|
+
// Full exit
|
|
1227
|
+
void shutdown("user_exit");
|
|
1228
|
+
}
|
|
1229
|
+
// response === 2 → Cancel — do nothing
|
|
1230
|
+
})
|
|
1231
|
+
.catch(() => {});
|
|
1209
1232
|
});
|
|
1210
1233
|
|
|
1211
1234
|
mainWindow.on("closed", () => {
|
|
@@ -1473,6 +1496,10 @@ function refreshTrayMenu() {
|
|
|
1473
1496
|
},
|
|
1474
1497
|
{ type: /** @type {const} */ ("separator") },
|
|
1475
1498
|
|
|
1499
|
+
{
|
|
1500
|
+
label: "Check for Updates\u2026",
|
|
1501
|
+
click: () => checkForUpdateDesktop().catch(() => {}),
|
|
1502
|
+
},
|
|
1476
1503
|
{
|
|
1477
1504
|
label: "Restart to Apply Update",
|
|
1478
1505
|
enabled: app.isPackaged,
|
|
@@ -1897,18 +1924,337 @@ async function bootstrap() {
|
|
|
1897
1924
|
}
|
|
1898
1925
|
|
|
1899
1926
|
async function maybeAutoUpdate() {
|
|
1900
|
-
|
|
1901
|
-
if (process.env.BOSUN_DESKTOP_AUTO_UPDATE
|
|
1927
|
+
// Packaged Electron builds use electron-updater (Squirrel / NSIS).
|
|
1928
|
+
if (app.isPackaged && process.env.BOSUN_DESKTOP_AUTO_UPDATE === "1") {
|
|
1929
|
+
try {
|
|
1930
|
+
const { autoUpdater } = await import("electron-updater");
|
|
1931
|
+
const feedUrl = process.env.BOSUN_DESKTOP_UPDATE_URL;
|
|
1932
|
+
if (feedUrl) {
|
|
1933
|
+
autoUpdater.setFeedURL({ url: feedUrl });
|
|
1934
|
+
}
|
|
1935
|
+
autoUpdater.autoDownload = true;
|
|
1936
|
+
autoUpdater.checkForUpdatesAndNotify().catch(() => {});
|
|
1937
|
+
} catch (err) {
|
|
1938
|
+
console.warn("[desktop] auto-update unavailable", err?.message || err);
|
|
1939
|
+
}
|
|
1940
|
+
return;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
// For dev / npm-global installs: silent background check on launch.
|
|
1944
|
+
// Just logs — no dialogs. The user can trigger interactive flow from the menu.
|
|
1902
1945
|
try {
|
|
1903
|
-
const
|
|
1904
|
-
const
|
|
1905
|
-
if (
|
|
1906
|
-
|
|
1946
|
+
const currentVersion = readCurrentBosunVersion();
|
|
1947
|
+
const latest = await fetchLatestVersionFromRegistry();
|
|
1948
|
+
if (latest && isNewerVersion(latest, currentVersion)) {
|
|
1949
|
+
console.log(
|
|
1950
|
+
`[desktop] Update available: v${currentVersion} → v${latest}. ` +
|
|
1951
|
+
`Use Bosun → Check for Updates to install.`,
|
|
1952
|
+
);
|
|
1907
1953
|
}
|
|
1908
|
-
|
|
1909
|
-
|
|
1954
|
+
} catch {
|
|
1955
|
+
// silent — never block startup
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
// ── Desktop Update Helpers ────────────────────────────────────────────────────
|
|
1960
|
+
|
|
1961
|
+
/**
|
|
1962
|
+
* Read the current bosun version from the on-disk package.json.
|
|
1963
|
+
* Works in both packaged and dev setups.
|
|
1964
|
+
*/
|
|
1965
|
+
function readCurrentBosunVersion() {
|
|
1966
|
+
try {
|
|
1967
|
+
const pkg = JSON.parse(
|
|
1968
|
+
readFileSync(resolveBosunRuntimePath("package.json"), "utf8"),
|
|
1969
|
+
);
|
|
1970
|
+
return pkg.version || "0.0.0";
|
|
1971
|
+
} catch {
|
|
1972
|
+
return "0.0.0";
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
/** Simple semver comparison (major.minor.patch). */
|
|
1977
|
+
function isNewerVersion(remote, local) {
|
|
1978
|
+
const parse = (v) => {
|
|
1979
|
+
const parts = String(v).replace(/^v/, "").split(".").map(Number);
|
|
1980
|
+
return { major: parts[0] || 0, minor: parts[1] || 0, patch: parts[2] || 0 };
|
|
1981
|
+
};
|
|
1982
|
+
const r = parse(remote);
|
|
1983
|
+
const l = parse(local);
|
|
1984
|
+
if (r.major !== l.major) return r.major > l.major;
|
|
1985
|
+
if (r.minor !== l.minor) return r.minor > l.minor;
|
|
1986
|
+
return r.patch > l.patch;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
/**
|
|
1990
|
+
* Fetch the latest published version of `bosun` from the npm registry.
|
|
1991
|
+
* Uses raw https to avoid undici handle leaks on Windows.
|
|
1992
|
+
*/
|
|
1993
|
+
function fetchLatestVersionFromRegistry() {
|
|
1994
|
+
return new Promise((res) => {
|
|
1995
|
+
const req = httpsRequest(
|
|
1996
|
+
"https://registry.npmjs.org/bosun/latest",
|
|
1997
|
+
{ headers: { Accept: "application/json" }, timeout: 10_000 },
|
|
1998
|
+
(response) => {
|
|
1999
|
+
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
2000
|
+
response.resume();
|
|
2001
|
+
return res(null);
|
|
2002
|
+
}
|
|
2003
|
+
let body = "";
|
|
2004
|
+
response.setEncoding("utf8");
|
|
2005
|
+
response.on("data", (chunk) => { body += chunk; });
|
|
2006
|
+
response.on("end", () => {
|
|
2007
|
+
try { res(JSON.parse(body).version || null); } catch { res(null); }
|
|
2008
|
+
});
|
|
2009
|
+
},
|
|
2010
|
+
);
|
|
2011
|
+
req.on("error", () => res(null));
|
|
2012
|
+
req.on("timeout", () => { req.destroy(); res(null); });
|
|
2013
|
+
req.end();
|
|
2014
|
+
});
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
/**
|
|
2018
|
+
* Interactive "Check for Updates" flow for the desktop app.
|
|
2019
|
+
* Fetches the latest version from npm, shows a dialog, and — if the user
|
|
2020
|
+
* confirms — spawns a resilient updater script that survives EBUSY locks.
|
|
2021
|
+
*/
|
|
2022
|
+
async function checkForUpdateDesktop() {
|
|
2023
|
+
const parentWin = mainWindow && !mainWindow.isDestroyed() ? mainWindow : null;
|
|
2024
|
+
|
|
2025
|
+
const currentVersion = readCurrentBosunVersion();
|
|
2026
|
+
|
|
2027
|
+
let latestVersion;
|
|
2028
|
+
try {
|
|
2029
|
+
latestVersion = await fetchLatestVersionFromRegistry();
|
|
2030
|
+
} catch {
|
|
2031
|
+
latestVersion = null;
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
if (!latestVersion) {
|
|
2035
|
+
await dialog.showMessageBox(parentWin, {
|
|
2036
|
+
type: "warning",
|
|
2037
|
+
title: "Check for Updates",
|
|
2038
|
+
message: "Could not reach the npm registry.",
|
|
2039
|
+
detail:
|
|
2040
|
+
"Make sure you are connected to the internet and try again.\n\n" +
|
|
2041
|
+
"You can also update manually:\n npm install -g bosun@latest",
|
|
2042
|
+
buttons: ["OK"],
|
|
2043
|
+
});
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
if (!isNewerVersion(latestVersion, currentVersion)) {
|
|
2048
|
+
await dialog.showMessageBox(parentWin, {
|
|
2049
|
+
type: "info",
|
|
2050
|
+
title: "Check for Updates",
|
|
2051
|
+
message: "Bosun is up to date!",
|
|
2052
|
+
detail: `Current version: v${currentVersion}\nLatest version: v${latestVersion}`,
|
|
2053
|
+
buttons: ["OK"],
|
|
2054
|
+
});
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
// Update is available — ask the user whether to install.
|
|
2059
|
+
const { response } = await dialog.showMessageBox(parentWin, {
|
|
2060
|
+
type: "info",
|
|
2061
|
+
title: "Update Available",
|
|
2062
|
+
message: `A new version of Bosun is available!`,
|
|
2063
|
+
detail:
|
|
2064
|
+
`Current version: v${currentVersion}\n` +
|
|
2065
|
+
`New version: v${latestVersion}\n\n` +
|
|
2066
|
+
"Bosun will close, install the update, and relaunch automatically.\n" +
|
|
2067
|
+
"The update typically takes 10–30 seconds.",
|
|
2068
|
+
buttons: ["Install Update & Restart", "Later"],
|
|
2069
|
+
defaultId: 0,
|
|
2070
|
+
cancelId: 1,
|
|
2071
|
+
});
|
|
2072
|
+
|
|
2073
|
+
if (response !== 0) return;
|
|
2074
|
+
|
|
2075
|
+
// Spawn a detached updater script that waits for the app to exit,
|
|
2076
|
+
// retries npm install (surviving EBUSY), and relaunches.
|
|
2077
|
+
try {
|
|
2078
|
+
spawnResilientUpdater(latestVersion);
|
|
2079
|
+
// Brief pause so the updater process is established before we quit.
|
|
2080
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
2081
|
+
void shutdown("update_install");
|
|
1910
2082
|
} catch (err) {
|
|
1911
|
-
|
|
2083
|
+
await dialog.showMessageBox(parentWin, {
|
|
2084
|
+
type: "error",
|
|
2085
|
+
title: "Update Failed",
|
|
2086
|
+
message: "Could not start the update process.",
|
|
2087
|
+
detail:
|
|
2088
|
+
`${err?.message || err}\n\n` +
|
|
2089
|
+
"You can update manually by running:\n npm install -g bosun@latest",
|
|
2090
|
+
buttons: ["OK"],
|
|
2091
|
+
});
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
/**
|
|
2096
|
+
* Spawn a detached OS-native script that:
|
|
2097
|
+
* 1. Waits for the Bosun desktop process to fully exit.
|
|
2098
|
+
* 2. Retries `npm install -g bosun@<version>` with exponential back-off
|
|
2099
|
+
* to survive EBUSY / file-lock errors on Windows.
|
|
2100
|
+
* 3. Attempts to relaunch Bosun desktop after a successful install.
|
|
2101
|
+
* 4. Self-deletes the temp script.
|
|
2102
|
+
*/
|
|
2103
|
+
function spawnResilientUpdater(version) {
|
|
2104
|
+
const safeVersion = String(version).replaceAll(/[^0-9.a-zA-Z-]/g, "");
|
|
2105
|
+
const scriptId = `bosun-update-${Date.now()}`;
|
|
2106
|
+
|
|
2107
|
+
if (process.platform === "win32") {
|
|
2108
|
+
const scriptPath = resolve(tmpdir(), `${scriptId}.ps1`);
|
|
2109
|
+
// Resolve the bosun CLI so we can relaunch after install.
|
|
2110
|
+
const bosunCliPath = resolveBosunRuntimePath("cli.mjs").replaceAll("\\", "\\\\");
|
|
2111
|
+
const nodeExePath = process.execPath.replaceAll("\\", "\\\\");
|
|
2112
|
+
const script = [
|
|
2113
|
+
`# Bosun Desktop Auto-Updater — generated ${new Date().toISOString()}`,
|
|
2114
|
+
`$ErrorActionPreference = 'Stop'`,
|
|
2115
|
+
`$parentPid = ${process.pid}`,
|
|
2116
|
+
``,
|
|
2117
|
+
`# ── Wait for the desktop process to exit ──────────────────────`,
|
|
2118
|
+
`$maxWait = 30`,
|
|
2119
|
+
`$waited = 0`,
|
|
2120
|
+
`while ($waited -lt $maxWait) {`,
|
|
2121
|
+
` try {`,
|
|
2122
|
+
` Get-Process -Id $parentPid -ErrorAction Stop | Out-Null`,
|
|
2123
|
+
` Start-Sleep -Seconds 1`,
|
|
2124
|
+
` $waited++`,
|
|
2125
|
+
` } catch {`,
|
|
2126
|
+
` break`,
|
|
2127
|
+
` }`,
|
|
2128
|
+
`}`,
|
|
2129
|
+
``,
|
|
2130
|
+
`# ── Retry npm install with exponential back-off ───────────────`,
|
|
2131
|
+
`$maxRetries = 6`,
|
|
2132
|
+
`$delay = 2`,
|
|
2133
|
+
`$success = $false`,
|
|
2134
|
+
``,
|
|
2135
|
+
`for ($i = 1; $i -le $maxRetries; $i++) {`,
|
|
2136
|
+
` Write-Host "[bosun-updater] Attempt $i / $maxRetries ..."`,
|
|
2137
|
+
` try {`,
|
|
2138
|
+
` $output = & npm install -g bosun@${safeVersion} 2>&1 | Out-String`,
|
|
2139
|
+
` if ($LASTEXITCODE -eq 0) {`,
|
|
2140
|
+
` Write-Host "[bosun-updater] Success!"`,
|
|
2141
|
+
` $success = $true`,
|
|
2142
|
+
` break`,
|
|
2143
|
+
` }`,
|
|
2144
|
+
` Write-Host $output`,
|
|
2145
|
+
` } catch {`,
|
|
2146
|
+
` Write-Host "[bosun-updater] Error: $_"`,
|
|
2147
|
+
` }`,
|
|
2148
|
+
` if ($i -lt $maxRetries) {`,
|
|
2149
|
+
` Write-Host "[bosun-updater] Retrying in $delay seconds ..."`,
|
|
2150
|
+
` Start-Sleep -Seconds $delay`,
|
|
2151
|
+
` $delay = [math]::Min($delay * 2, 30)`,
|
|
2152
|
+
` }`,
|
|
2153
|
+
`}`,
|
|
2154
|
+
``,
|
|
2155
|
+
`# ── Last-resort: --force ──────────────────────────────────────`,
|
|
2156
|
+
`if (-not $success) {`,
|
|
2157
|
+
` Write-Host "[bosun-updater] Trying with --force ..."`,
|
|
2158
|
+
` try {`,
|
|
2159
|
+
` & npm install -g bosun@${safeVersion} --force 2>&1 | Out-String`,
|
|
2160
|
+
` if ($LASTEXITCODE -eq 0) { $success = $true }`,
|
|
2161
|
+
` } catch {`,
|
|
2162
|
+
` Write-Host "[bosun-updater] Force install also failed: $_"`,
|
|
2163
|
+
` }`,
|
|
2164
|
+
`}`,
|
|
2165
|
+
``,
|
|
2166
|
+
`# ── Relaunch Bosun desktop ────────────────────────────────────`,
|
|
2167
|
+
`if ($success) {`,
|
|
2168
|
+
` Write-Host "[bosun-updater] Relaunching Bosun desktop ..."`,
|
|
2169
|
+
` try {`,
|
|
2170
|
+
` Start-Process -FilePath "bosun" -ArgumentList "--desktop" -WindowStyle Hidden`,
|
|
2171
|
+
` } catch {`,
|
|
2172
|
+
` # Fallback: launch via node directly`,
|
|
2173
|
+
` try {`,
|
|
2174
|
+
` Start-Process -FilePath "${nodeExePath}" -ArgumentList "${bosunCliPath}", "--desktop" -WindowStyle Hidden`,
|
|
2175
|
+
` } catch {`,
|
|
2176
|
+
` Write-Host "[bosun-updater] Could not relaunch: $_"`,
|
|
2177
|
+
` }`,
|
|
2178
|
+
` }`,
|
|
2179
|
+
`} else {`,
|
|
2180
|
+
` Write-Host "[bosun-updater] Update failed. Please run manually: npm install -g bosun@latest"`,
|
|
2181
|
+
`}`,
|
|
2182
|
+
``,
|
|
2183
|
+
`# ── Clean up ─────────────────────────────────────────────────`,
|
|
2184
|
+
`Start-Sleep -Seconds 2`,
|
|
2185
|
+
`Remove-Item -Path $MyInvocation.MyCommand.Path -Force -ErrorAction SilentlyContinue`,
|
|
2186
|
+
].join("\n");
|
|
2187
|
+
|
|
2188
|
+
writeFileSync(scriptPath, script, "utf8");
|
|
2189
|
+
|
|
2190
|
+
const child = spawn(
|
|
2191
|
+
"powershell.exe",
|
|
2192
|
+
["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", scriptPath],
|
|
2193
|
+
{ detached: true, stdio: "ignore", windowsHide: false },
|
|
2194
|
+
);
|
|
2195
|
+
child.unref();
|
|
2196
|
+
console.log(`[desktop] spawned updater script: ${scriptPath} (pid ${child.pid})`);
|
|
2197
|
+
} else {
|
|
2198
|
+
// ── macOS / Linux ─────────────────────────────────────────────────
|
|
2199
|
+
const scriptPath = resolve(tmpdir(), `${scriptId}.sh`);
|
|
2200
|
+
const script = [
|
|
2201
|
+
`#!/usr/bin/env bash`,
|
|
2202
|
+
`# Bosun Desktop Auto-Updater — generated ${new Date().toISOString()}`,
|
|
2203
|
+
`set -e`,
|
|
2204
|
+
`PARENT_PID=${process.pid}`,
|
|
2205
|
+
`MAX_WAIT=30`,
|
|
2206
|
+
`WAITED=0`,
|
|
2207
|
+
``,
|
|
2208
|
+
`# Wait for parent to exit`,
|
|
2209
|
+
`while [ $WAITED -lt $MAX_WAIT ]; do`,
|
|
2210
|
+
` if kill -0 $PARENT_PID 2>/dev/null; then`,
|
|
2211
|
+
` sleep 1`,
|
|
2212
|
+
` WAITED=$((WAITED + 1))`,
|
|
2213
|
+
` else`,
|
|
2214
|
+
` break`,
|
|
2215
|
+
` fi`,
|
|
2216
|
+
`done`,
|
|
2217
|
+
``,
|
|
2218
|
+
`# Retry npm install with back-off`,
|
|
2219
|
+
`MAX_RETRIES=6`,
|
|
2220
|
+
`DELAY=2`,
|
|
2221
|
+
`SUCCESS=0`,
|
|
2222
|
+
`for i in $(seq 1 $MAX_RETRIES); do`,
|
|
2223
|
+
` echo "[bosun-updater] Attempt $i / $MAX_RETRIES ..."`,
|
|
2224
|
+
` if npm install -g bosun@${safeVersion} 2>&1; then`,
|
|
2225
|
+
` SUCCESS=1`,
|
|
2226
|
+
` break`,
|
|
2227
|
+
` fi`,
|
|
2228
|
+
` echo "[bosun-updater] Retrying in \${DELAY}s ..."`,
|
|
2229
|
+
` sleep $DELAY`,
|
|
2230
|
+
` DELAY=$((DELAY * 2))`,
|
|
2231
|
+
` [ $DELAY -gt 30 ] && DELAY=30`,
|
|
2232
|
+
`done`,
|
|
2233
|
+
``,
|
|
2234
|
+
`# Last resort: --force`,
|
|
2235
|
+
`if [ $SUCCESS -eq 0 ]; then`,
|
|
2236
|
+
` echo "[bosun-updater] Trying with --force ..."`,
|
|
2237
|
+
` npm install -g bosun@${safeVersion} --force 2>&1 && SUCCESS=1 || true`,
|
|
2238
|
+
`fi`,
|
|
2239
|
+
``,
|
|
2240
|
+
`# Relaunch`,
|
|
2241
|
+
`if [ $SUCCESS -eq 1 ]; then`,
|
|
2242
|
+
` echo "[bosun-updater] Relaunching Bosun desktop ..."`,
|
|
2243
|
+
` nohup bosun --desktop </dev/null >/dev/null 2>&1 &`,
|
|
2244
|
+
`fi`,
|
|
2245
|
+
``,
|
|
2246
|
+
`# Clean up`,
|
|
2247
|
+
`rm -f "$0"`,
|
|
2248
|
+
].join("\n");
|
|
2249
|
+
|
|
2250
|
+
writeFileSync(scriptPath, script, { encoding: "utf8", mode: 0o755 });
|
|
2251
|
+
|
|
2252
|
+
const child = spawn("bash", [scriptPath], {
|
|
2253
|
+
detached: true,
|
|
2254
|
+
stdio: "ignore",
|
|
2255
|
+
});
|
|
2256
|
+
child.unref();
|
|
2257
|
+
console.log(`[desktop] spawned updater script: ${scriptPath} (pid ${child.pid})`);
|
|
1912
2258
|
}
|
|
1913
2259
|
}
|
|
1914
2260
|
|
package/library-manager.mjs
CHANGED
|
@@ -34,12 +34,12 @@ export const RESOURCE_TYPES = Object.freeze(["prompt", "agent", "skill", "mcp",
|
|
|
34
34
|
|
|
35
35
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
36
36
|
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
);
|
|
37
|
+
export function getBosunHomeDir() {
|
|
38
|
+
const explicit = process.env.BOSUN_HOME || process.env.BOSUN_DIR;
|
|
39
|
+
if (explicit) return resolve(String(explicit));
|
|
40
|
+
const modernDefault = resolve(homedir(), "bosun");
|
|
41
|
+
if (existsSync(modernDefault)) return modernDefault;
|
|
42
|
+
return resolve(homedir(), ".bosun");
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
function ensureDir(dir) {
|
|
@@ -111,7 +111,7 @@ function nowISO() {
|
|
|
111
111
|
* Get the manifest path for a workspace (or global).
|
|
112
112
|
*/
|
|
113
113
|
export function getManifestPath(rootDir) {
|
|
114
|
-
return resolve(rootDir ||
|
|
114
|
+
return resolve(rootDir || getBosunHomeDir(), ".bosun", LIBRARY_MANIFEST);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
/**
|
|
@@ -139,7 +139,7 @@ export function saveManifest(rootDir, manifest) {
|
|
|
139
139
|
// ── CRUD operations ──────────────────────────────────────────────────────────
|
|
140
140
|
|
|
141
141
|
function dirForType(rootDir, type) {
|
|
142
|
-
const root = rootDir ||
|
|
142
|
+
const root = rootDir || getBosunHomeDir();
|
|
143
143
|
switch (type) {
|
|
144
144
|
case "prompt": return resolve(root, PROMPT_DIR);
|
|
145
145
|
case "skill": return resolve(root, SKILL_DIR);
|
|
@@ -613,7 +613,7 @@ export function resolveEntry(workspaceRoot, id) {
|
|
|
613
613
|
}
|
|
614
614
|
|
|
615
615
|
// 2. Check global
|
|
616
|
-
const globalRoot =
|
|
616
|
+
const globalRoot = getBosunHomeDir();
|
|
617
617
|
if (globalRoot !== workspaceRoot) {
|
|
618
618
|
const entry = getEntry(globalRoot, id);
|
|
619
619
|
if (entry) {
|