@polderlabs/bizar 5.4.0 → 5.5.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/bizar-dash/dist/assets/{EnvVarsSection-DqvB8ZcJ.js → EnvVarsSection-L413CQEI.js} +3 -3
- package/bizar-dash/dist/assets/{EnvVarsSection-DqvB8ZcJ.js.map → EnvVarsSection-L413CQEI.js.map} +1 -1
- package/bizar-dash/dist/assets/{MobileChat-DRXP2ERQ.js → MobileChat-0kGQb1S6.js} +1 -1
- package/bizar-dash/dist/assets/{MobileChat-DRXP2ERQ.js.map → MobileChat-0kGQb1S6.js.map} +1 -1
- package/bizar-dash/dist/assets/MobileSettings-Cq1oFIdO.js +1 -0
- package/bizar-dash/dist/assets/{MobileSettings-CVW9VhEo.js.map → MobileSettings-Cq1oFIdO.js.map} +1 -1
- package/bizar-dash/dist/assets/{Toast-CcZVVN4i.js → Toast-C6f1zFJ2.js} +1 -1
- package/bizar-dash/dist/assets/{Toast-CcZVVN4i.js.map → Toast-C6f1zFJ2.js.map} +1 -1
- package/bizar-dash/dist/assets/{icons-OMEGx5KA.js → icons-BsWXWkEF.js} +121 -126
- package/bizar-dash/dist/assets/icons-BsWXWkEF.js.map +1 -0
- package/bizar-dash/dist/assets/main-BklYy02e.css +1 -0
- package/bizar-dash/dist/assets/main-CBTgYSeG.js +18 -0
- package/bizar-dash/dist/assets/main-CBTgYSeG.js.map +1 -0
- package/bizar-dash/dist/assets/mobile-CsaN4CxH.js +1 -0
- package/bizar-dash/dist/assets/{mobile-XN2P7YfM.js.map → mobile-CsaN4CxH.js.map} +1 -1
- package/bizar-dash/dist/assets/{mobile-layout-D6V-7WnA.css → mobile-layout-CJnZNLy3.css} +1 -1
- package/bizar-dash/dist/assets/mobile-layout-iFBdjEE4.js +2 -0
- package/bizar-dash/dist/assets/mobile-layout-iFBdjEE4.js.map +1 -0
- package/bizar-dash/dist/assets/{useSlashCommands-iyoW95UB.js → useSlashCommands-DGJcC9iQ.js} +2 -2
- package/bizar-dash/dist/assets/{useSlashCommands-iyoW95UB.js.map → useSlashCommands-DGJcC9iQ.js.map} +1 -1
- package/bizar-dash/dist/index.html +8 -8
- package/bizar-dash/dist/mobile.html +4 -4
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/bizar-dash/src/server/backup-store.mjs +6 -1
- package/bizar-dash/src/server/bg-spawner.mjs +574 -0
- package/bizar-dash/src/server/memory-lightrag.mjs +242 -0
- package/bizar-dash/src/server/memory-store.mjs +43 -3
- package/bizar-dash/src/server/routes/background.mjs +143 -0
- package/bizar-dash/src/server/routes/lightrag.mjs +10 -0
- package/bizar-dash/src/server/routes/memory.mjs +8 -0
- package/bizar-dash/src/server/server.mjs +22 -0
- package/bizar-dash/src/web/App.tsx +3 -0
- package/bizar-dash/src/web/MobileApp.tsx +1 -10
- package/bizar-dash/src/web/components/InstallConfirmDialog.tsx +1 -1
- package/bizar-dash/src/web/components/SettingsNav.tsx +1 -10
- package/bizar-dash/src/web/lib/types.ts +39 -1
- package/bizar-dash/src/web/styles/main.css +43 -0
- package/bizar-dash/src/web/styles/mobile-layout.css +7 -5
- package/bizar-dash/src/web/styles/settings.css +46 -0
- package/bizar-dash/src/web/views/BackgroundAgents.tsx +399 -30
- package/bizar-dash/src/web/views/Settings.tsx +12 -12
- package/bizar-dash/src/web/views/SpawnAgentModal.tsx +221 -0
- package/bizar-dash/tests/BackgroundAgents.test.tsx +160 -0
- package/bizar-dash/tests/background-pause-resume.test.mjs +46 -0
- package/bizar-dash/tests/background-spawn-from-ui.test.mjs +36 -0
- package/bizar-dash/tests/background-steer.test.mjs +42 -0
- package/bizar-dash/tests/lightrag-startup-hook.test.mjs +206 -0
- package/bizar-dash/tests/memory-auto-reindex.test.mjs +196 -0
- package/bizar-dash/tests/memory-cli.test.mjs +1 -1
- package/bizar-dash/tests/memory-default-vault.test.mjs +90 -3
- package/bizar-dash/tests/memory-lightrag-extended.test.mjs +131 -0
- package/bizar-dash/tests/mobile-layout.test.tsx +36 -0
- package/bizar-dash/tests/settings-layout.test.tsx +3 -3
- package/bizar-dash/tests/settings-nav.test.tsx +7 -5
- package/cli/bin.mjs +16 -0
- package/cli/commands/deploy/docker.mjs +2 -1
- package/cli/commands/lightrag.mjs +171 -0
- package/cli/commands/service.mjs +7 -0
- package/cli/commands/util.mjs +8 -1
- package/cli/init.mjs +1 -1
- package/cli/post-install-smoke.mjs +230 -0
- package/cli/provision.mjs +177 -0
- package/cli/provision.test.mjs +180 -0
- package/cli/service-controller.mjs +378 -28
- package/cli/service-controller.test.mjs +100 -0
- package/cli/service-env.mjs +139 -0
- package/cli/service.mjs +23 -0
- package/config/agents/_shared/AGENT_BASELINE.md +6 -6
- package/install.sh +87 -6
- package/package.json +1 -1
- package/plugins/bizar/index.ts +147 -0
- package/plugins/bizar/src/background-state.ts +84 -5
- package/plugins/bizar/src/background.ts +388 -10
- package/plugins/bizar/src/hooks/memory-inject.ts +247 -0
- package/plugins/bizar/src/hooks/memory-write-on-end.ts +188 -0
- package/plugins/bizar/src/opencode-runner.ts +87 -1
- package/plugins/bizar/src/tools/bg-pause.ts +77 -0
- package/plugins/bizar/src/tools/bg-report-progress.ts +101 -0
- package/plugins/bizar/src/tools/bg-resume.ts +72 -0
- package/plugins/bizar/src/tools/bg-send-message.ts +105 -0
- package/plugins/bizar/src/tools/bg-spawn.ts +13 -6
- package/plugins/bizar/src/tools/bg-status.ts +10 -0
- package/plugins/bizar/src/tools/memory-list.ts +135 -0
- package/plugins/bizar/src/tools/memory-read.ts +142 -0
- package/plugins/bizar/src/tools/memory-search.ts +228 -0
- package/plugins/bizar/src/tools/memory-write.ts +183 -0
- package/plugins/bizar/tests/memory-write-on-end.test.ts +92 -0
- package/plugins/bizar/tests/tools/bg-pause.test.ts +64 -0
- package/plugins/bizar/tests/tools/bg-report-progress.test.ts +82 -0
- package/plugins/bizar/tests/tools/bg-resume.test.ts +43 -0
- package/plugins/bizar/tests/tools/bg-send-message.test.ts +46 -0
- package/bizar-dash/dist/assets/MobileSettings-CVW9VhEo.js +0 -1
- package/bizar-dash/dist/assets/icons-OMEGx5KA.js.map +0 -1
- package/bizar-dash/dist/assets/main-CMAGZouj.js +0 -16
- package/bizar-dash/dist/assets/main-CMAGZouj.js.map +0 -1
- package/bizar-dash/dist/assets/main-O2tZVdHm.css +0 -1
- package/bizar-dash/dist/assets/mobile-XN2P7YfM.js +0 -1
- package/bizar-dash/dist/assets/mobile-layout-DqTxvXZp.js +0 -2
- package/bizar-dash/dist/assets/mobile-layout-DqTxvXZp.js.map +0 -1
package/cli/provision.mjs
CHANGED
|
@@ -64,6 +64,7 @@ export const OPENCODE_DIR =
|
|
|
64
64
|
const SERVICE_PID_FILE = join(BIZAR_HOME, 'service.pid');
|
|
65
65
|
const DASHBOARD_PID_FILE = join(BIZAR_HOME, 'dashboard.pid');
|
|
66
66
|
const DASHBOARD_PORT_FILE = join(BIZAR_HOME, 'dashboard.port');
|
|
67
|
+
const INSTALL_MARKER_FILE = join(BIZAR_HOME, 'installed.json');
|
|
67
68
|
|
|
68
69
|
// ─── Tiny utilities ──────────────────────────────────────────────────────────
|
|
69
70
|
|
|
@@ -157,6 +158,35 @@ export async function killAndWait(pid, { timeoutMs = 5000, label = 'process' } =
|
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
|
|
161
|
+
// ─── Install marker ──────────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Read the install marker file. Returns null if missing.
|
|
165
|
+
* Marker shape: { version, installedAt, repoPath, serviceUnit }
|
|
166
|
+
*/
|
|
167
|
+
export function readInstallMarker() {
|
|
168
|
+
return readJsonSafe(INSTALL_MARKER_FILE, null);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Write the install marker file.
|
|
173
|
+
*/
|
|
174
|
+
export function writeInstallMarker({ version, repoPath, serviceUnit }) {
|
|
175
|
+
const marker = {
|
|
176
|
+
version: version || currentVersion(PKG_MAIN) || 'unknown',
|
|
177
|
+
installedAt: new Date().toISOString(),
|
|
178
|
+
repoPath: repoPath || REPO_ROOT,
|
|
179
|
+
serviceUnit: serviceUnit || null,
|
|
180
|
+
};
|
|
181
|
+
try {
|
|
182
|
+
mkdirSync(BIZAR_HOME, { recursive: true });
|
|
183
|
+
writeFileSync(INSTALL_MARKER_FILE, JSON.stringify(marker, null, 2) + '\n');
|
|
184
|
+
return { ok: true, marker };
|
|
185
|
+
} catch (err) {
|
|
186
|
+
return { ok: false, error: err.message };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
160
190
|
// ─── State detection ──────────────────────────────────────────────────────────
|
|
161
191
|
|
|
162
192
|
/**
|
|
@@ -726,6 +756,55 @@ export async function runDoctor({ silent = false } = {}) {
|
|
|
726
756
|
}
|
|
727
757
|
}
|
|
728
758
|
|
|
759
|
+
/**
|
|
760
|
+
* Run the post-install smoke test.
|
|
761
|
+
*/
|
|
762
|
+
export async function runSmokeTest({ silent = false } = {}) {
|
|
763
|
+
try {
|
|
764
|
+
const { runSmokeTest: smokeFn } = await import('./post-install-smoke.mjs');
|
|
765
|
+
const result = await smokeFn();
|
|
766
|
+
return result;
|
|
767
|
+
} catch (err) {
|
|
768
|
+
return {
|
|
769
|
+
ok: false,
|
|
770
|
+
checks: [{ name: 'smoke-test', ok: false, message: err.message }],
|
|
771
|
+
passed: 0,
|
|
772
|
+
failed: 1,
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* v5.x — Install LightRAG via uv tool.
|
|
779
|
+
* Called from the provisioner in case the shell script (install.sh)
|
|
780
|
+
* was skipped (e.g. Windows without WSL). Idempotent — safe to re-run.
|
|
781
|
+
* Fails gracefully if uv is not available.
|
|
782
|
+
*/
|
|
783
|
+
export async function installLightragProvision({ dryRun = false } = {}) {
|
|
784
|
+
if (dryRun) {
|
|
785
|
+
return { ok: true, message: '[dry-run] would run: uv tool install "lightrag-hku[api]"' };
|
|
786
|
+
}
|
|
787
|
+
// Check if already installed
|
|
788
|
+
if (haveCmd('lightrag-server')) {
|
|
789
|
+
return { ok: true, message: 'lightrag-server already on PATH' };
|
|
790
|
+
}
|
|
791
|
+
if (!haveCmd('uv')) {
|
|
792
|
+
return { ok: false, message: 'uv not found — LightRAG not installed (install uv to enable)' };
|
|
793
|
+
}
|
|
794
|
+
try {
|
|
795
|
+
const r = spawnSync('uv', ['tool', 'install', 'lightrag-hku[api]'], {
|
|
796
|
+
stdio: 'inherit',
|
|
797
|
+
timeout: 120_000,
|
|
798
|
+
});
|
|
799
|
+
if (r.status === 0 || r.status === null) {
|
|
800
|
+
return { ok: true, message: 'lightrag-hku[api] installed via uv' };
|
|
801
|
+
}
|
|
802
|
+
return { ok: false, message: `uv tool install failed (exit ${r.status})` };
|
|
803
|
+
} catch (err) {
|
|
804
|
+
return { ok: false, message: `lightrag install error: ${err.message}` };
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
729
808
|
/**
|
|
730
809
|
* v4.4.11 — The mods step. By default, NEVER install or upgrade mods
|
|
731
810
|
* during `bizar install` or `bizar update`. The step just reports the
|
|
@@ -960,6 +1039,28 @@ export async function runProvision(opts = {}) {
|
|
|
960
1039
|
console.log(chalk.dim(' --dry-run set: no installs, kills, or restarts will be performed.\n'));
|
|
961
1040
|
}
|
|
962
1041
|
|
|
1042
|
+
// ── 0.5. Idempotency: detect existing install ─────────────────────────
|
|
1043
|
+
// v5.x — If a marker file exists and we're in 'install' mode (not 'update'),
|
|
1044
|
+
// treat this as a re-install. In non-interactive mode, auto-upgrade.
|
|
1045
|
+
// In interactive mode, prompt the user.
|
|
1046
|
+
const marker = readInstallMarker();
|
|
1047
|
+
if (mode === 'install' && marker && !dryRun) {
|
|
1048
|
+
if (yes) {
|
|
1049
|
+
// Non-interactive: auto-switch to update mode
|
|
1050
|
+
console.log(chalk.yellow(' ⚠ Existing install detected (marker found).'));
|
|
1051
|
+
console.log(chalk.yellow(' Auto-switching to update mode due to --yes flag.'));
|
|
1052
|
+
console.log('');
|
|
1053
|
+
// Re-call runProvision with update mode
|
|
1054
|
+
return runProvision({ ...opts, mode: 'update' });
|
|
1055
|
+
} else {
|
|
1056
|
+
// Interactive: warn and continue
|
|
1057
|
+
console.log(chalk.yellow(' ⚠ Existing install detected (marker found at ~/.config/bizar/installed.json).'));
|
|
1058
|
+
console.log(chalk.yellow(` Last installed: ${marker.installedAt || 'unknown'}`));
|
|
1059
|
+
console.log(chalk.yellow(' To update an existing install, use `bizar update` or re-run with --update.'));
|
|
1060
|
+
console.log('');
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
963
1064
|
// ── 1. Detect state ──────────────────────────────────────────────────
|
|
964
1065
|
const state = detectState();
|
|
965
1066
|
|
|
@@ -1036,6 +1137,9 @@ export async function runProvision(opts = {}) {
|
|
|
1036
1137
|
await runStep('system-deps + service', () => ensureSystemDeps({ dryRun, mode }));
|
|
1037
1138
|
}
|
|
1038
1139
|
|
|
1140
|
+
// ── 6.5. LightRAG install (Node-side, in case shell script skipped) ─
|
|
1141
|
+
await runStep('lightrag-server', () => installLightragProvision({ dryRun }));
|
|
1142
|
+
|
|
1039
1143
|
// ── 7-9. Sync agent files, commands, skills, plugin, opencode.json ──
|
|
1040
1144
|
console.log('');
|
|
1041
1145
|
await runStep('agent files + slash commands + skills', () =>
|
|
@@ -1053,6 +1157,34 @@ export async function runProvision(opts = {}) {
|
|
|
1053
1157
|
);
|
|
1054
1158
|
await runStep('opencode.json plugin entry', () => patchOpencodeJson({ dryRun, force }));
|
|
1055
1159
|
|
|
1160
|
+
// ── 9.5. v5.x — issue #7. Restart the system service so it picks up
|
|
1161
|
+
// the freshly-installed binary. We do this AFTER the npm upgrade
|
|
1162
|
+
// (so the new code is on disk) and AFTER the file sync (so the
|
|
1163
|
+
// service won't try to read stale files on boot). The service has
|
|
1164
|
+
// already been killed in step 4 (mode==='update' branch).
|
|
1165
|
+
// This is also called on first install: if the service was registered
|
|
1166
|
+
// before the npm upgrade, restart is a no-op (unit content matches
|
|
1167
|
+
// and the service is already running with the old code).
|
|
1168
|
+
if (mode === 'update' && !dryRun) {
|
|
1169
|
+
try {
|
|
1170
|
+
const { restartService } = await import('./service-controller.mjs');
|
|
1171
|
+
const restart = restartService({ force: false, dryRun: false });
|
|
1172
|
+
if (restart.ok) {
|
|
1173
|
+
console.log(chalk.green(' ✓ Background service restarted with new code.'));
|
|
1174
|
+
stepResults.push({ label: 'service-restart', ok: true, message: 'restarted' });
|
|
1175
|
+
} else {
|
|
1176
|
+
// Non-fatal: the service may have been killed but failed to
|
|
1177
|
+
// restart, or it may not have been installed. The OS will
|
|
1178
|
+
// attempt to start it on next login either way.
|
|
1179
|
+
console.log(chalk.yellow(` ⚠ Service restart: ${restart.error || 'unknown'}`));
|
|
1180
|
+
stepResults.push({ label: 'service-restart', ok: false, message: restart.error || 'restart failed' });
|
|
1181
|
+
}
|
|
1182
|
+
} catch (err) {
|
|
1183
|
+
console.log(chalk.yellow(` ⚠ Service restart skipped: ${err?.message || err}`));
|
|
1184
|
+
stepResults.push({ label: 'service-restart', ok: false, message: err?.message || 'skipped' });
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1056
1188
|
// ── 10. (update) Restart dashboard ──────────────────────────────────
|
|
1057
1189
|
if (mode === 'update' && restart && state.dashboard.running) {
|
|
1058
1190
|
console.log('');
|
|
@@ -1072,6 +1204,18 @@ export async function runProvision(opts = {}) {
|
|
|
1072
1204
|
const modsStep = await runModsStep({ mode, dryRun, force, withMods, state });
|
|
1073
1205
|
stepResults.push({ label: 'mods', ...modsStep });
|
|
1074
1206
|
|
|
1207
|
+
// ── 11.5. Write install marker (v5.x idempotency) ──────────────────
|
|
1208
|
+
if (!dryRun) {
|
|
1209
|
+
const markerWrite = writeInstallMarker({
|
|
1210
|
+
version: state.pkgVersion || null,
|
|
1211
|
+
repoPath: REPO_ROOT,
|
|
1212
|
+
serviceUnit: state.service.unitPath,
|
|
1213
|
+
});
|
|
1214
|
+
if (markerWrite.ok) {
|
|
1215
|
+
console.log(chalk.dim(' ✓ install marker updated'));
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1075
1219
|
// ── 12. Doctor health check ───────────────────────────────────────
|
|
1076
1220
|
console.log('');
|
|
1077
1221
|
const doctor = await runDoctor({ silent: true });
|
|
@@ -1087,6 +1231,12 @@ export async function runProvision(opts = {}) {
|
|
|
1087
1231
|
console.log(chalk.green(' ✓ Doctor: all checks passed'));
|
|
1088
1232
|
}
|
|
1089
1233
|
|
|
1234
|
+
// ── 12.5. Smoke test ──────────────────────────────────────────────
|
|
1235
|
+
console.log('');
|
|
1236
|
+
console.log(chalk.bold(' → Smoke test'));
|
|
1237
|
+
const smoke = await runSmokeTest({ silent: false });
|
|
1238
|
+
stepResults.push({ label: 'smoke-test', ok: smoke.ok, message: `${smoke.passed} passed, ${smoke.failed} failed` });
|
|
1239
|
+
|
|
1090
1240
|
// ── 12. Summary ─────────────────────────────────────────────────────
|
|
1091
1241
|
console.log('');
|
|
1092
1242
|
console.log(' Summary:');
|
|
@@ -1102,6 +1252,33 @@ export async function runProvision(opts = {}) {
|
|
|
1102
1252
|
console.log(chalk.green(`\n ✓ ${mode === 'update' ? 'Update' : 'Install'} complete\n`));
|
|
1103
1253
|
}
|
|
1104
1254
|
|
|
1255
|
+
// ── 13. API key bootstrap warning ────────────────────────────────────
|
|
1256
|
+
// v5.x — After a successful install, check whether any API keys are
|
|
1257
|
+
// configured. If not, surface a prominent warning (but don't block —
|
|
1258
|
+
// many users configure keys later).
|
|
1259
|
+
if (mode === 'install' && !dryRun && !anyFail) {
|
|
1260
|
+
const envJsonPath = join(BIZAR_HOME, 'env.json');
|
|
1261
|
+
const marker = readInstallMarker();
|
|
1262
|
+
const hasApiKeys = Boolean(
|
|
1263
|
+
process.env.OPENAI_API_KEY ||
|
|
1264
|
+
process.env.ANTHROPIC_API_KEY ||
|
|
1265
|
+
process.env.MINIMAX_API_KEY ||
|
|
1266
|
+
(existsSync(envJsonPath) && (() => {
|
|
1267
|
+
try {
|
|
1268
|
+
const env = JSON.parse(readFileSync(envJsonPath, 'utf8'));
|
|
1269
|
+
return Boolean(env?.OPENAI_API_KEY || env?.ANTHROPIC_API_KEY || env?.MINIMAX_API_KEY);
|
|
1270
|
+
} catch { return false; }
|
|
1271
|
+
})())
|
|
1272
|
+
);
|
|
1273
|
+
if (!hasApiKeys) {
|
|
1274
|
+
console.log(chalk.yellow(' ⚠ No API keys configured.'));
|
|
1275
|
+
console.log(chalk.yellow(' Run `bizar connect` to add providers.'));
|
|
1276
|
+
const dashPort = process.env.BIZAR_DASHBOARD_PORT || '4097';
|
|
1277
|
+
console.log(chalk.yellow(` Or visit http://localhost:${dashPort}/connect after starting the dashboard.`));
|
|
1278
|
+
console.log('');
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1105
1282
|
return {
|
|
1106
1283
|
ok: !anyFail,
|
|
1107
1284
|
mode,
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/provision.test.mjs
|
|
3
|
+
*
|
|
4
|
+
* v5.x — Tests for the idempotency marker and installLightrag functions
|
|
5
|
+
* added in the "fully functional installer" gap-closure.
|
|
6
|
+
*/
|
|
7
|
+
import { test, describe, beforeEach, afterEach, after } from 'node:test';
|
|
8
|
+
import assert from 'node:assert/strict';
|
|
9
|
+
import {
|
|
10
|
+
mkdtempSync,
|
|
11
|
+
mkdirSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
rmSync,
|
|
14
|
+
existsSync,
|
|
15
|
+
readFileSync,
|
|
16
|
+
unlinkSync,
|
|
17
|
+
} from 'node:fs';
|
|
18
|
+
import { tmpdir } from 'node:os';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
|
|
21
|
+
// Mock HOME for all tests
|
|
22
|
+
const ORIG_HOME = process.env.HOME;
|
|
23
|
+
const ORIG_XDG = process.env.XDG_CONFIG_HOME;
|
|
24
|
+
|
|
25
|
+
function freshHome() {
|
|
26
|
+
const home = mkdtempSync(join(tmpdir(), 'bizar-provision-'));
|
|
27
|
+
process.env.HOME = home;
|
|
28
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
29
|
+
delete process.env.BIZAR_DASHBOARD_PORT;
|
|
30
|
+
delete process.env.OPENCODE_SERVER_PASSWORD;
|
|
31
|
+
delete process.env.BIZAR_MEMORY_VAULT;
|
|
32
|
+
return home;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function restoreHome() {
|
|
36
|
+
if (ORIG_HOME === undefined) delete process.env.HOME;
|
|
37
|
+
else process.env.HOME = ORIG_HOME;
|
|
38
|
+
if (ORIG_XDG === undefined) delete process.env.XDG_CONFIG_HOME;
|
|
39
|
+
else process.env.XDG_CONFIG_HOME = ORIG_XDG;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
after(() => {
|
|
43
|
+
restoreHome();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// ── Idempotency marker ────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
describe('install marker (installed.json)', () => {
|
|
49
|
+
let home;
|
|
50
|
+
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
home = freshHome();
|
|
53
|
+
process.env.HOME = home;
|
|
54
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
55
|
+
delete process.env.BIZAR_HOME;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
restoreHome();
|
|
60
|
+
if (home && existsSync(home)) rmSync(home, { recursive: true, force: true });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('readInstallMarker returns null when no marker file', async () => {
|
|
64
|
+
// HOME is fresh tmpdir — no marker should exist
|
|
65
|
+
const { readInstallMarker } = await import('./provision.mjs');
|
|
66
|
+
const marker = readInstallMarker();
|
|
67
|
+
assert.equal(marker, null);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('writeInstallMarker returns ok=true and marker has correct shape', async () => {
|
|
71
|
+
// Note: ESM module caching means BIZAR_HOME was computed at first module load.
|
|
72
|
+
// This test verifies the function returns the correct shape when called.
|
|
73
|
+
const { writeInstallMarker } = await import('./provision.mjs');
|
|
74
|
+
const result = writeInstallMarker({ version: '5.0.0', repoPath: '/test/repo' });
|
|
75
|
+
assert.equal(result.ok, true);
|
|
76
|
+
assert.equal(result.marker.version, '5.0.0');
|
|
77
|
+
assert.equal(result.marker.repoPath, '/test/repo');
|
|
78
|
+
assert.ok(result.marker.installedAt);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('readInstallMarker returns existing marker from BIZAR_HOME', async () => {
|
|
82
|
+
const { writeInstallMarker, readInstallMarker } = await import('./provision.mjs');
|
|
83
|
+
// Write first (this uses the fresh HOME's .config/bizar)
|
|
84
|
+
writeInstallMarker({ version: '1.2.3', repoPath: '/foo' });
|
|
85
|
+
// Read back
|
|
86
|
+
const marker = readInstallMarker();
|
|
87
|
+
assert.ok(marker !== null);
|
|
88
|
+
assert.equal(marker.version, '1.2.3');
|
|
89
|
+
assert.equal(marker.repoPath, '/foo');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// ── installLightragProvision ───────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
describe('installLightragProvision()', () => {
|
|
96
|
+
test('dryRun returns ok without making changes', async () => {
|
|
97
|
+
const { installLightragProvision } = await import('./provision.mjs');
|
|
98
|
+
const result = await installLightragProvision({ dryRun: true });
|
|
99
|
+
assert.equal(result.ok, true);
|
|
100
|
+
assert.ok(result.message.includes('[dry-run]'));
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('returns failure when uv not available', async () => {
|
|
104
|
+
const { installLightragProvision } = await import('./provision.mjs');
|
|
105
|
+
// uv is likely available in the test env, but if it is, the test still passes
|
|
106
|
+
// (already installed). The key is that it doesn't throw.
|
|
107
|
+
const result = await installLightragProvision({ dryRun: false });
|
|
108
|
+
// Result is either ok=true (already installed) or ok=false (uv missing)
|
|
109
|
+
// Either way, no exception was thrown.
|
|
110
|
+
assert.equal(typeof result.ok, 'boolean');
|
|
111
|
+
assert.ok(typeof result.message === 'string');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// ── buildServiceEnvFile ──────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
describe('buildServiceEnvFile()', () => {
|
|
118
|
+
let home;
|
|
119
|
+
|
|
120
|
+
beforeEach(() => {
|
|
121
|
+
home = freshHome();
|
|
122
|
+
process.env.HOME = home;
|
|
123
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
124
|
+
delete process.env.BIZAR_HOME;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
afterEach(() => {
|
|
128
|
+
restoreHome();
|
|
129
|
+
if (home && existsSync(home)) rmSync(home, { recursive: true, force: true });
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('includes all required env vars', async () => {
|
|
133
|
+
const { buildServiceEnvFile } = await import('./service-env.mjs');
|
|
134
|
+
const content = buildServiceEnvFile({ repoPath: '/repo' });
|
|
135
|
+
const required = [
|
|
136
|
+
'BIZAR_HOME', 'BIZAR_REPO', 'PATH',
|
|
137
|
+
'OPENCODE_SERVER_PASSWORD', 'BIZAR_DASHBOARD_PORT',
|
|
138
|
+
'BIZAR_DASHBOARD_HOST', 'BIZAR_LOG_LEVEL',
|
|
139
|
+
'BIZAR_HEADROOM_AUTOSTART', 'BIZAR_LIGHTRAG_AUTOSTART',
|
|
140
|
+
'BIZAR_MEMORY_VAULT',
|
|
141
|
+
];
|
|
142
|
+
for (const var_ of required) {
|
|
143
|
+
assert.ok(
|
|
144
|
+
content.includes(`${var_}=`),
|
|
145
|
+
`${var_} should be present in env file`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('generates OPENCODE_SERVER_PASSWORD when not set', async () => {
|
|
151
|
+
const { buildServiceEnvFile } = await import('./service-env.mjs');
|
|
152
|
+
const content = buildServiceEnvFile({ repoPath: '/repo' });
|
|
153
|
+
const lines = content.split('\n');
|
|
154
|
+
const pwdLine = lines.find(l => l.startsWith('OPENCODE_SERVER_PASSWORD='));
|
|
155
|
+
assert.ok(pwdLine, 'OPENCODE_SERVER_PASSWORD line should exist');
|
|
156
|
+
const pwd = pwdLine.split('=')[1];
|
|
157
|
+
assert.ok(pwd.length > 0, 'password should be non-empty');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('adds ~/.local/bin to PATH when uv tools are present', async () => {
|
|
161
|
+
const { buildServiceEnvFile } = await import('./service-env.mjs');
|
|
162
|
+
// Ensure HOME is set for the test
|
|
163
|
+
process.env.HOME = home;
|
|
164
|
+
const content = buildServiceEnvFile({ repoPath: '/repo' });
|
|
165
|
+
assert.ok(content.includes('.local/bin:'), 'PATH should include ~/.local/bin');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('uses BIZAR_MEMORY_VAULT from env when set', async () => {
|
|
169
|
+
const { buildServiceEnvFile } = await import('./service-env.mjs');
|
|
170
|
+
process.env.BIZAR_MEMORY_VAULT = '/my/custom/vault';
|
|
171
|
+
try {
|
|
172
|
+
const content = buildServiceEnvFile({ repoPath: '/repo' });
|
|
173
|
+
assert.ok(content.includes('BIZAR_MEMORY_VAULT=/my/custom/vault'));
|
|
174
|
+
} finally {
|
|
175
|
+
delete process.env.BIZAR_MEMORY_VAULT;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
console.log(' provision.mjs tests loaded — run with: node --test cli/provision.test.mjs');
|