@polderlabs/bizar 10.25.0 → 10.25.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.
@@ -12,7 +12,7 @@ function help() {
12
12
  bizar openkan — default durable planning and goals surface
13
13
 
14
14
  Usage:
15
- bizar openkan install Install OpenKan natively (Node fetch + tar, no curl|bash)
15
+ bizar openkan install Install @polderlabs/openkan@latest from npm
16
16
  bizar openkan init Initialise .ok/ in this project
17
17
  bizar openkan task <ok task args...> Manage durable tasks
18
18
  bizar openkan plan <ok plan args...> Manage plans and phases
package/cli/openkan.mjs CHANGED
@@ -5,30 +5,35 @@
5
5
  * `.ok/`. Keeping this bridge subprocess-only means Bizar never imports or
6
6
  * forks OpenKan's storage implementation, and upgrades remain independent.
7
7
  *
8
- * The native installer (`installOpenKan`) avoids the previous `curl | bash`
9
- * pipeline. Bizar now downloads the OpenKan tarball with Node's built-in
10
- * `fetch`, decompresses with `node:zlib`, parses USTAR with a tiny
11
- * in-process parser, and only shells out to `npm install --omit=dev
12
- * --ignore-scripts` for runtime dependencies. No remote shell script ever
13
- * runs on the operator's machine the install is fully driven by the
14
- * Bizar provisioning code path.
8
+ * The native installer (`installOpenKanPromise`) installs the latest
9
+ * `@polderlabs/openkan` from the public npm registry via `npm install
10
+ * --prefix <home> @polderlabs/openkan@latest`. No remote shell script
11
+ * ever runs on the operator's machine; the version is resolved by npm
12
+ * itself. Each `bizar openkan install` invocation resolves the latest
13
+ * tag at call time, so Bizar tracks upstream automatically.
15
14
  */
16
15
  import {
17
16
  existsSync,
18
- mkdirSync,
17
+ readFileSync,
19
18
  realpathSync,
20
- rmSync,
21
19
  writeFileSync,
22
20
  } from 'node:fs';
23
21
  import { homedir } from 'node:os';
24
- import { dirname, isAbsolute, join, resolve, sep } from 'node:path';
22
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
25
23
  import { spawnSync } from 'node:child_process';
26
- import { gunzipSync } from 'node:zlib';
27
24
 
28
- export const OPENKAN_TARBALL_URL = 'https://codeload.github.com/PolderLabsVOF/openkan/tar.gz/refs/heads/main';
29
- export const OPENKAN_TARBALL_PREFIX = 'openkan-main/';
25
+ export const OPENKAN_NPM_PACKAGE = '@polderlabs/openkan';
26
+ export const OPENKAN_NPM_VERSION_SPEC = 'latest';
30
27
  export const OPENKAN_HOME_DEFAULT = join(homedir(), '.config', 'bizar', 'openkan');
31
- export const OPENKAN_DEFAULT_LAUNCHER = join(OPENKAN_HOME_DEFAULT, 'bin', 'ok.mjs');
28
+ export const OPENKAN_DEFAULT_LAUNCHER = join(
29
+ OPENKAN_HOME_DEFAULT,
30
+ 'node_modules',
31
+ '@polderlabs',
32
+ 'openkan',
33
+ 'bin',
34
+ 'ok.mjs',
35
+ );
36
+ export const OPENKAN_VERSION_MARKER = '.installed-version';
32
37
 
33
38
  export class OpenKanError extends Error {
34
39
  constructor(code, message, details = {}) {
@@ -71,10 +76,18 @@ export function resolveOpenKanOk(options = {}) {
71
76
 
72
77
  const home = options.home || process.env.BIZAR_OPENKAN_HOME;
73
78
  if (home) {
74
- const candidate = [join(home, 'bin', 'ok.mjs'), join(home, 'bin', 'ok.ts')]
75
- .find((path) => existsSync(path));
79
+ const candidates = [
80
+ join(home, 'bin', 'ok.mjs'),
81
+ join(home, 'bin', 'ok.ts'),
82
+ join(home, 'node_modules', '@polderlabs', 'openkan', 'bin', 'ok.mjs'),
83
+ join(home, 'node_modules', '@polderlabs', 'openkan', 'bin', 'ok.ts'),
84
+ ];
85
+ const candidate = candidates.find((path) => existsSync(path));
76
86
  if (candidate) return candidate;
77
- throw new OpenKanError('OPENKAN_NOT_FOUND', `OpenKan launcher missing under ${resolve(home)}/bin`);
87
+ throw new OpenKanError(
88
+ 'OPENKAN_NOT_FOUND',
89
+ `OpenKan launcher missing under ${resolve(home)} (expected node_modules/@polderlabs/openkan/bin/ok.mjs or bin/ok.mjs)`,
90
+ );
78
91
  }
79
92
 
80
93
  const configuredBin = options.openkanBin || process.env.BIZAR_OPENKAN_BIN || executableOnPath('openkan');
@@ -165,31 +178,15 @@ export function installOpenKan(options = {}) {
165
178
  );
166
179
  }
167
180
 
168
- /** Parse a USTAR tar buffer into { name, content } entries. */
169
- export function parseTarEntries(buffer) {
170
- if (!Buffer.isBuffer(buffer)) {
171
- throw new TypeError('parseTarEntries expects a Buffer');
172
- }
173
- const entries = [];
174
- const BLOCK = 512;
175
- let offset = 0;
176
- while (offset + BLOCK <= buffer.length) {
177
- const header = buffer.subarray(offset, offset + BLOCK);
178
- if (header.every((byte) => byte === 0)) break; // tar terminator
179
- const name = header.subarray(0, 100).toString('utf8').replace(/\0+$/, '');
180
- const prefix = header.subarray(345, 500).toString('utf8').replace(/\0+$/, '');
181
- const sizeOctal = header.subarray(124, 136).toString('utf8').trim();
182
- const size = Number.parseInt(sizeOctal, 8) || 0;
183
- const typeFlag = String.fromCharCode(header[156] || 0x30);
184
- const fullName = prefix ? `${prefix}/${name}` : name;
185
- const paddedSize = Math.ceil(size / BLOCK) * BLOCK;
186
- const content = size > 0
187
- ? Buffer.from(buffer.subarray(offset + BLOCK, offset + BLOCK + size))
188
- : Buffer.alloc(0);
189
- entries.push({ name: fullName, typeFlag, size, content });
190
- offset += BLOCK + paddedSize;
181
+ /** Read the installed OpenKan version marker from the home directory, if any. */
182
+ export function readInstalledOpenKanVersion(home = OPENKAN_HOME_DEFAULT) {
183
+ const marker = join(home, OPENKAN_VERSION_MARKER);
184
+ if (!existsSync(marker)) return null;
185
+ try {
186
+ return readFileSync(marker, 'utf8').trim() || null;
187
+ } catch {
188
+ return null;
191
189
  }
192
- return entries;
193
190
  }
194
191
 
195
192
  /** Resolve the default install root for the native installer. */
@@ -203,99 +200,100 @@ export function resolveOpenKanHome(options = {}) {
203
200
  * Native install — runs entirely inside the Bizar provisioning code path.
204
201
  *
205
202
  * Steps:
206
- * 1. `fetch(OPENKAN_TARBALL_URL)` (Node 18+) no `curl` binary.
207
- * 2. `zlib.gunzipSync` — no remote shell.
208
- * 3. `parseTarEntries` + `mkdirSync`/`writeFileSync` no `tar` binary.
209
- * 4. Optional `npm install --omit=dev --ignore-scripts --prefix <home>`
210
- * via `spawnSync('npm', ...)` to fetch runtime deps; skipped when
211
- * `BIZAR_SKIP_OPENKAN_NPM_INSTALL=1`.
212
- * 5. `verifyOpenKanRuntime` — launcher probe.
203
+ * 1. `npm install --prefix <home> @polderlabs/openkan@latest`
204
+ * via `spawnSync('npm', ...)` — npm resolves `@latest` against the
205
+ * public registry on every call, so Bizar always tracks the newest
206
+ * upstream OpenKan.
207
+ * 2. Record the resolved version under `<home>/.installed-version` so
208
+ * operators can inspect what shipped and `bizar update` can detect
209
+ * drift.
210
+ * 3. `verifyOpenKanRuntime` — launcher probe; surfaces a clear error
211
+ * if npm installed something but the `ok` binary does not start.
212
+ *
213
+ * The npm path replaces the previous `codeload` tarball + USTAR parser.
214
+ * Operators no longer need `curl`, `tar`, or `gunzip` on PATH; npm is
215
+ * the only subprocess. Set `BIZAR_SKIP_OPENKAN_NPM_INSTALL=1` to skip the
216
+ * network call (handy for air-gapped installs that pre-stage the home).
213
217
  */
214
218
  export async function installOpenKanPromise(options = {}) {
215
219
  const home = resolveOpenKanHome(options);
216
220
  const cwd = resolve(options.cwd || process.cwd());
217
- const fetchImpl = options.fetchImpl || (typeof globalThis.fetch === 'function' ? globalThis.fetch.bind(globalThis) : null);
218
- if (typeof fetchImpl !== 'function') {
219
- throw new OpenKanError('OPENKAN_FETCH_UNAVAILABLE', 'Node 18+ global fetch is required for the native installer');
220
- }
221
+ const pkgSpec = options.packageSpec || `${OPENKAN_NPM_PACKAGE}@${OPENKAN_NPM_VERSION_SPEC}`;
222
+ const previousVersion = options.refresh ? null : readInstalledOpenKanVersion(home);
221
223
 
222
- let response;
223
- try {
224
- response = await fetchImpl(OPENKAN_TARBALL_URL, { redirect: 'follow' });
225
- } catch (error) {
226
- throw new OpenKanError('OPENKAN_DOWNLOAD_FAILED', error?.message || 'fetch failed', { cause: error });
227
- }
228
- if (!response?.ok) {
229
- throw new OpenKanError('OPENKAN_DOWNLOAD_FAILED', `HTTP ${response?.status || '???'} fetching ${OPENKAN_TARBALL_URL}`);
224
+ if (process.env.BIZAR_SKIP_OPENKAN_NPM_INSTALL === '1' || options.skipNpmInstall) {
225
+ if (previousVersion) {
226
+ const launcher = resolveOpenKanOk({ cwd, home });
227
+ return {
228
+ ok: true,
229
+ installed: false,
230
+ skipped: true,
231
+ home,
232
+ version: previousVersion,
233
+ launcher,
234
+ message: `OpenKan install skipped; ${previousVersion} already present at ${home}`,
235
+ };
236
+ }
237
+ throw new OpenKanError(
238
+ 'OPENKAN_SKIP_BUT_MISSING',
239
+ 'BIZAR_SKIP_OPENKAN_NPM_INSTALL=1 but no .installed-version marker is present at ' + home,
240
+ );
230
241
  }
231
- const compressed = Buffer.from(await response.arrayBuffer());
232
242
 
233
- let tarBuffer;
234
- try {
235
- tarBuffer = gunzipSync(compressed);
236
- } catch (error) {
237
- throw new OpenKanError('OPENKAN_EXTRACT_FAILED', `Could not decompress tarball: ${error?.message || error}`, { cause: error });
243
+ const npm = spawnSync('npm', [
244
+ 'install',
245
+ '--prefix', home,
246
+ '--no-audit',
247
+ '--no-fund',
248
+ '--omit=dev',
249
+ '--ignore-scripts',
250
+ '--silent',
251
+ pkgSpec,
252
+ ], {
253
+ cwd,
254
+ encoding: 'utf8',
255
+ shell: false,
256
+ timeout: options.npmTimeoutMs || 240_000,
257
+ });
258
+ if (process.env.BIZAR_OPENKAN_TEST_FAIL_NPM === '1') {
259
+ // Test seam: force a deterministic failure path without hitting the network.
260
+ throw new OpenKanError(
261
+ 'OPENKAN_NPM_INSTALL_FAILED',
262
+ `npm install ${pkgSpec} failed (test seam)`,
263
+ { npmStatus: 1 },
264
+ );
265
+ }
266
+ if (npm.status !== 0) {
267
+ throw new OpenKanError(
268
+ 'OPENKAN_NPM_INSTALL_FAILED',
269
+ (npm.stderr || npm.stdout || '').trim() || `npm install ${pkgSpec} failed`,
270
+ { npmStatus: npm.status },
271
+ );
238
272
  }
239
273
 
240
- let entries;
274
+ const pkgJsonPath = join(home, 'node_modules', '@polderlabs', 'openkan', 'package.json');
275
+ let installedVersion = 'unknown';
241
276
  try {
242
- entries = parseTarEntries(tarBuffer);
277
+ installedVersion = JSON.parse(readFileSync(pkgJsonPath, 'utf8')).version;
243
278
  } catch (error) {
244
- throw new OpenKanError('OPENKAN_EXTRACT_FAILED', `Could not parse tar entries: ${error?.message || error}`, { cause: error });
245
- }
246
- const extracted = entries.filter((entry) => entry.name.startsWith(OPENKAN_TARBALL_PREFIX));
247
- if (extracted.length === 0) {
248
- throw new OpenKanError('OPENKAN_EXTRACT_FAILED', `Tarball contained no entries under ${OPENKAN_TARBALL_PREFIX}`);
249
- }
250
-
251
- if (options.refresh && existsSync(home)) {
252
- rmSync(home, { recursive: true, force: true });
253
- }
254
- mkdirSync(home, { recursive: true });
255
-
256
- const skipped = [];
257
- for (const entry of entries) {
258
- if (!entry.name.startsWith(OPENKAN_TARBALL_PREFIX)) continue;
259
- const relative = entry.name.slice(OPENKAN_TARBALL_PREFIX.length);
260
- if (!relative || relative.startsWith('..') || relative.includes(`..${sep}`)) {
261
- skipped.push(entry.name);
262
- continue;
263
- }
264
- const target = join(home, relative);
265
- if (entry.typeFlag === '5' || entry.name.endsWith('/')) {
266
- mkdirSync(target, { recursive: true });
267
- continue;
268
- }
269
- mkdirSync(dirname(target), { recursive: true });
270
- writeFileSync(target, entry.content);
279
+ throw new OpenKanError(
280
+ 'OPENKAN_INSTALL_INCOMPLETE',
281
+ `npm reported success but ${pkgJsonPath} is missing or unreadable`,
282
+ { cause: error },
283
+ );
271
284
  }
272
285
 
273
- let installResult = null;
274
- if (process.env.BIZAR_SKIP_OPENKAN_NPM_INSTALL !== '1' && !options.skipNpmInstall) {
275
- const packageJson = join(home, 'package.json');
276
- if (existsSync(packageJson)) {
277
- const npm = spawnSync('npm', ['install', '--omit=dev', '--ignore-scripts', '--no-audit', '--no-fund', '--silent'], {
278
- cwd: home,
279
- encoding: 'utf8',
280
- shell: false,
281
- timeout: options.npmTimeoutMs || 240_000,
282
- });
283
- if (npm.status !== 0) {
284
- throw new OpenKanError(
285
- 'OPENKAN_NPM_INSTALL_FAILED',
286
- (npm.stderr || npm.stdout || '').trim() || 'npm install failed',
287
- { npmStatus: npm.status },
288
- );
289
- }
290
- installResult = { stdout: npm.stdout || '', stderr: npm.stderr || '' };
291
- }
292
- }
286
+ writeFileSync(join(home, OPENKAN_VERSION_MARKER), `${installedVersion}\n`, { encoding: 'utf8' });
293
287
 
294
288
  let launcher;
295
289
  try {
296
290
  launcher = verifyOpenKanRuntime({ cwd, home }).launcher;
297
291
  } catch (error) {
298
- throw new OpenKanError('OPENKAN_INSTALL_INCOMPLETE', `OpenKan installed but did not start: ${error.message || String(error)}`, { cause: error });
292
+ throw new OpenKanError(
293
+ 'OPENKAN_INSTALL_INCOMPLETE',
294
+ `OpenKan ${installedVersion} installed but did not start: ${error.message || String(error)}`,
295
+ { cause: error },
296
+ );
299
297
  }
300
298
 
301
299
  return {
@@ -303,9 +301,9 @@ export async function installOpenKanPromise(options = {}) {
303
301
  installed: true,
304
302
  home,
305
303
  launcher,
306
- skipped,
307
- npm: installResult,
308
- message: `OpenKan installed (${launcher})`,
304
+ version: installedVersion,
305
+ previousVersion,
306
+ message: `OpenKan ${installedVersion} installed at ${launcher}`,
309
307
  };
310
308
  }
311
309
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "10.25.0",
3
+ "version": "10.25.1",
4
4
  "description": "Autonomous, human-in-the-loop multi-agent harness for Claude Code with guarded workflows, typed SDK primitives, and MCP tools.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * SDK version constant. Keep synchronized with the workspace package versions.
3
3
  */
4
- export declare const SDK_VERSION: "10.24.0";
4
+ export declare const SDK_VERSION: "10.25.1";
5
5
  //# sourceMappingURL=version.d.ts.map
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * SDK version constant. Keep synchronized with the workspace package versions.
3
3
  */
4
- export const SDK_VERSION = "10.24.0";
4
+ export const SDK_VERSION = "10.25.1";
5
5
  //# sourceMappingURL=version.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar-sdk",
3
- "version": "10.24.0",
3
+ "version": "10.25.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",