@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.
package/cli/commands/openkan.mjs
CHANGED
|
@@ -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
|
|
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 (`
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
|
29
|
-
export const
|
|
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(
|
|
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
|
|
75
|
-
|
|
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(
|
|
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
|
-
/**
|
|
169
|
-
export function
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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. `
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
* `
|
|
212
|
-
*
|
|
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
|
|
218
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
274
|
+
const pkgJsonPath = join(home, 'node_modules', '@polderlabs', 'openkan', 'package.json');
|
|
275
|
+
let installedVersion = 'unknown';
|
|
241
276
|
try {
|
|
242
|
-
|
|
277
|
+
installedVersion = JSON.parse(readFileSync(pkgJsonPath, 'utf8')).version;
|
|
243
278
|
} catch (error) {
|
|
244
|
-
throw new OpenKanError(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
message: `OpenKan installed
|
|
304
|
+
version: installedVersion,
|
|
305
|
+
previousVersion,
|
|
306
|
+
message: `OpenKan ${installedVersion} installed at ${launcher}`,
|
|
309
307
|
};
|
|
310
308
|
}
|
|
311
309
|
|
package/package.json
CHANGED