@polderlabs/bizar 10.19.5 → 10.19.7

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/bin.mjs CHANGED
@@ -129,7 +129,8 @@ function showHelp() {
129
129
  bizar install
130
130
  bizar audit
131
131
  bizar doctor
132
- bizar update --all --dry-run
132
+ bizar update --dry-run # Preview what would change
133
+ bizar update --force --yes # Non-interactive full re-emit
133
134
 
134
135
  Run \`bizar <command> --help\` for per-command help.
135
136
 
@@ -3,10 +3,15 @@
3
3
  *
4
4
  * install + update command families.
5
5
  * v4.4.11+ — 'bizar install' and 'bizar update' share the same code path.
6
+ *
7
+ * v10.19.6 — `bizar update` now honors `--dry-run` / `--force` / `--yes`
8
+ * the same way `bizar install` does. The previous implementation called
9
+ * a legacy `runUpdate(args)` that swallowed every flag; this version
10
+ * routes update through `parseFlags` + `runInstaller` + `runRepair` so
11
+ * the documented flags actually do what they claim.
6
12
  */
7
13
  import chalk from 'chalk';
8
14
  import { runInstaller } from '../install.mjs';
9
- import { runUpdate } from '../update.mjs';
10
15
  import { runRepair } from '../repair.mjs';
11
16
  import { parseFlags } from '../provision.mjs';
12
17
 
@@ -61,35 +66,53 @@ export function showInstallHelp() {
61
66
 
62
67
  export function showUpdateHelp() {
63
68
  console.log(`
64
- bizar update — Update @anthropic-ai/claude-code + @polderlabs/bizar
65
- (which bundles the CLI, SDK, agents, skills, hooks, and commands). Detects what's
66
- installed and only touches what's missing or out of date.
69
+ bizar update — Update @polderlabs/bizar (CLI + SDK + agents +
70
+ skills + hooks + commands). Refreshing Claude Code itself is also
71
+ handled when the bundled install.sh runs under --force.
72
+
67
73
  Usage:
68
- bizar update Update all installed Bizar components
69
- bizar update --check Only print current vs. latest; do not update
70
- bizar update --channel=stable|beta Pick the npm dist-tag (default: stable)
74
+ bizar update Refresh every Bizar-managed surface
71
75
  bizar update --dry-run Print what would happen, change nothing
72
- bizar update --force Override .bizar/PRE_PUSH_NOTES.md blockers
73
- bizar update --yes Same as --force, but named for one-line scripts
76
+ bizar update --force | --deep Full clean re-emit: wipe Bizar-managed
77
+ dirs, back up settings env vars,
78
+ re-sync everything from the repo
79
+ bizar update --yes | -y Assume yes for any non-destructive prompt
80
+ bizar update --non-interactive Alias for --yes
74
81
  bizar update --help Show this help
75
- Components updated:
76
- @anthropic-ai/claude-code the Claude Code CLI itself
77
- @polderlabs/bizar CLI + SDK + agents + skills + hooks
78
- Behavior (v4.4.7+):
79
- • Single unified provisioner. 'bizar install' and 'bizar update' are
80
- the same code path with different mode flags. Every step is
81
- idempotent re-running is safe.
82
- Re-runs the provisioner so installed Claude Code surfaces match
83
- the just-upgraded npm version.
84
- Runs 'bizar doctor' after a successful update to catch config
85
- regressions before claude tries to start.
86
- With --check: prints the version matrix and release-notes excerpt
87
- between current and latest, exits non-zero if an update is available.
82
+
83
+ Behavior (v10.19.6+):
84
+ Single unified provisioner the same code path as 'bizar install'
85
+ with mode=update. Every step is idempotent; re-running is safe.
86
+
87
+ 1. Re-emits skills, commands, rules, hooks, agents, and workflows
88
+ from the repo source into ~/.claude/ (or $CLAUDE_CONFIG_DIR),
89
+ overwriting only the Bizar-managed surface and pruning stale
90
+ entries (F-141).
91
+ 2. Writes the install marker so subsequent runs short-circuit when
92
+ nothing has changed.
93
+ 3. With --force, re-runs the F-183 clean-install flow: wipes
94
+ ~/.claude/{agents,skills,commands,hooks,rules,workflows,plugins}/
95
+ and ~/.agents/, stashes the prior settings.json env block into
96
+ BIZAR_SAVED_ENV, then re-emits settings.json with the operator's
97
+ ANTHROPIC_* and BIZAR_* keys union-merged back in (so gateway
98
+ URL, auth token, and BIZAR_HOME are preserved across the wipe).
99
+ 4. Runs 'bizar doctor' after a successful update so config
100
+ regressions surface before the next Claude Code session.
101
+ 5. Repairs stale bin symlinks so the operator picks up the new code
102
+ on the next shell prompt.
103
+
104
+ Idempotency note:
105
+ When the SDK on disk matches the published npm version, every step
106
+ above is a no-op — settings.json is rewritten only if its hash
107
+ drifted, sync targets are skipped when the file set is unchanged,
108
+ and the bin symlink repair short-circuits. A clean re-run is fast.
109
+
88
110
  Examples:
89
- bizar update Full auto-update (recommended)
90
- bizar update --check Show version matrix + notes, do nothing
91
- bizar update --channel=beta Upgrade to latest beta build
92
- bizar update --dry-run Preview what would change
111
+ bizar update Refresh the managed surface
112
+ bizar update --dry-run Preview every step
113
+ bizar update --force Full clean re-emit, preserved env
114
+ bizar update --force --yes Same, no prompts (script-friendly)
115
+
93
116
  Errors:
94
117
  Network failures (registry offline / DNS) and npm permission issues
95
118
  are surfaced with the raw npm output. The provisioner never silently
@@ -97,7 +120,34 @@ export function showUpdateHelp() {
97
120
  `);
98
121
  }
99
122
 
123
+ // ── Shared post-install teardown ────────────────────────────────────────────────
124
+
125
+ /**
126
+ * Run `runRepair({})` after install/update and surface any repointed
127
+ * bin symlinks to the operator. Both `install()` and `update()` share
128
+ * this block; the previous `bizar update` skipped it entirely (audit
129
+ * A6), so a freshly-upgraded Bizar left stale bin symlinks pointing at
130
+ * the prior install until the operator manually re-sourced their shell.
131
+ *
132
+ * @param {object} [opts]
133
+ * @param {(opts: object) => Promise<{ ok: boolean, fixed: string[], notes?: string[] }>} [opts.runRepair]
134
+ * Dependency-injected for tests; defaults to the real `runRepair`.
135
+ */
136
+ async function runPostInstallerRepair({ runRepair: runRepairDep = runRepair } = {}) {
137
+ try {
138
+ const r = await runRepairDep({});
139
+ if (r.fixed.length > 0) {
140
+ console.log(chalk.cyan('\n Repair: repointed stale bin symlinks:'));
141
+ for (const f of r.fixed) console.log(` ${f}`);
142
+ console.log(chalk.dim(' Re-run your shell or `hash -r` to pick up the new path.'));
143
+ }
144
+ } catch (err) {
145
+ console.log(chalk.dim(` Repair skipped: ${err.message}`));
146
+ }
147
+ }
148
+
100
149
  // ── Command runners ────────────────────────────────────────────────────────────
150
+
101
151
  export async function install(args, isHelpRequest) {
102
152
  if (isHelpRequest) {
103
153
  showInstallHelp();
@@ -122,16 +172,10 @@ export async function install(args, isHelpRequest) {
122
172
  }
123
173
  // v4.4.3 — After install, repair any stale bin symlinks so the
124
174
  // user picks up the new code.
125
- try {
126
- const r = await runRepair({});
127
- if (r.fixed.length > 0) {
128
- console.log(chalk.cyan('\n Repair: repointed stale bin symlinks:'));
129
- for (const f of r.fixed) console.log(` ${f}`);
130
- console.log(chalk.dim(' Re-run your shell or `hash -r` to pick up the new path.'));
131
- }
132
- } catch (err) {
133
- console.log(chalk.dim(` Repair skipped: ${err.message}`));
134
- }
175
+ await runPostInstallerRepair();
176
+ // v10.19.6 propagate install failure to the parent shell so
177
+ // `bizar install && bizar doctor` short-circuits on install errors.
178
+ if (!result?.ok) process.exit(1);
135
179
  }
136
180
 
137
181
  export async function update(args, isHelpRequest) {
@@ -139,7 +183,46 @@ export async function update(args, isHelpRequest) {
139
183
  showUpdateHelp();
140
184
  return;
141
185
  }
142
- await runUpdate(args);
186
+ // v10.19.6 — route update through the same flag-parsing + installer
187
+ // pipeline as install so `--dry-run`, `--force`, `--yes`, etc. do
188
+ // what they claim. See `runUpdateWithFlags` for the testable
189
+ // dependency-injected core.
190
+ await runUpdateWithFlags({ args });
191
+ }
192
+
193
+ /**
194
+ * Testable core of `update()`. Default arguments bind to the real
195
+ * `runInstaller` / `parseFlags` / `runRepair` from this module; tests
196
+ * inject stubs to assert the wiring without touching disk.
197
+ *
198
+ * Sequence (mirrors install() so the audit's documented "Runs doctor +
199
+ * runs repair" promises become true):
200
+ * 1. `parseFlags(args)` → `{ mode, dryRun, force, yes }`
201
+ * 2. `runInstaller({ mode, dryRun, force, yes })` — runs the
202
+ * provisioner, then post-install `runDoctor({ silent: true })`.
203
+ * 3. `runRepair({})` — repoint stale bin symlinks.
204
+ * 4. If `runInstaller` returned `{ ok: false }`, exit(1) so scripts
205
+ * that gate on the exit code (`bizar update && bizar doctor`) see
206
+ * the failure.
207
+ *
208
+ * @param {object} [opts]
209
+ * @param {string[]} [opts.args]
210
+ * @param {(opts: { mode: string, dryRun: boolean, force: boolean, yes: boolean }) => Promise<{ ok?: boolean }>} [opts.runInstaller]
211
+ * @param {(argv: string[]) => { mode: string, dryRun: boolean, force: boolean, yes: boolean }} [opts.parseFlags]
212
+ * @param {(opts: { dryRun?: boolean }) => Promise<{ ok: boolean, fixed: string[] }>} [opts.runRepair]
213
+ * @returns {Promise<{ ok?: boolean }>}
214
+ */
215
+ export async function runUpdateWithFlags({
216
+ args = [],
217
+ runInstaller: runInstallerDep = runInstaller,
218
+ parseFlags: parseFlagsDep = parseFlags,
219
+ runRepair: runRepairDep = runRepair,
220
+ } = {}) {
221
+ const { mode, dryRun, force, yes } = parseFlagsDep(args);
222
+ const result = await runInstallerDep({ mode, dryRun, force, yes });
223
+ await runPostInstallerRepair({ runRepair: runRepairDep });
224
+ if (!result?.ok) process.exit(1);
225
+ return result;
143
226
  }
144
227
 
145
228
  // ── run() entry point (used by bin.mjs dispatcher) ──────────────────────────────
@@ -227,7 +227,15 @@ function normalizedModelIdentity(id) {
227
227
  };
228
228
  }
229
229
 
230
- function toCapabilityProfile(gatewayId, match, matchType, confidence) {
230
+ /**
231
+ * Build a per-model profile object from a Models.dev catalog row.
232
+ *
233
+ * Phase 1 (v10.19.7): in addition to the long-standing `name` / `family` /
234
+ * `capabilities` / `limits` / `metadata` fields, propagate Models.dev's
235
+ * `description` and `summary` onto the profile so Phase 3's status screen
236
+ * (10.19.9) can render the description without re-querying the catalog.
237
+ */
238
+ export function toCapabilityProfile(gatewayId, match, matchType, confidence) {
231
239
  const limit = match.limit && typeof match.limit === 'object' ? match.limit : {};
232
240
  const modalities = match.modalities && typeof match.modalities === 'object' ? match.modalities : {};
233
241
  return {
@@ -235,6 +243,8 @@ function toCapabilityProfile(gatewayId, match, matchType, confidence) {
235
243
  baseModel: match.id,
236
244
  name: typeof match.name === 'string' ? match.name : match.id,
237
245
  family: typeof match.family === 'string' ? match.family : null,
246
+ description: typeof match.description === 'string' ? match.description : null,
247
+ summary: typeof match.summary === 'string' ? match.summary : null,
238
248
  capabilities: {
239
249
  attachment: match.attachment === true,
240
250
  reasoning: match.reasoning === true,
@@ -265,6 +275,15 @@ function toCapabilityProfile(gatewayId, match, matchType, confidence) {
265
275
  * Enrich gateway-discovered models with Models.dev profiles. Exact IDs win.
266
276
  * A normalized/suffix match is accepted only when unique; ambiguous models
267
277
  * remain unmatched rather than receiving guessed capabilities.
278
+ *
279
+ * Phase 1 (v10.19.7): when Models.dev has no row for the candidate AND the
280
+ * candidate carries gateway-supplied `_gateway.name` / `_gateway.description`,
281
+ * build a minimal `profile` so the picker row renderer can read
282
+ * `profile.name` / `profile.description` directly without dereferencing
283
+ * `_gateway`. Candidates whose `normalizeModels` output had no `_gateway`
284
+ * fields keep the legacy `profile === null` contract so `capabilityLabel`
285
+ * still returns `'metadata unavailable'` (Phase 2 owns the rewrite that
286
+ * lets a non-null profile render the `'metadata unavailable'` label).
268
287
  */
269
288
  export function enrichModelsWithCapabilities(candidates, catalog) {
270
289
  const entries = flattenModelsDevCatalog(catalog);
@@ -286,7 +305,47 @@ export function enrichModelsWithCapabilities(candidates, catalog) {
286
305
  const profile = toCapabilityProfile(gatewayId, matches[0], 'unique-normalized-id', 0.7);
287
306
  return { ...candidate, profile, contextWindow: profile.limits.contextTokens };
288
307
  }
289
- return { ...candidate, profile: null, contextWindow: null };
308
+ // Models.dev miss: if the candidate carries gateway-supplied label /
309
+ // description, build a minimal `profile` so the picker row renderer
310
+ // can read `profile.name` / `profile.description` directly. Candidates
311
+ // that arrived from `normalizeModels` WITHOUT any `_gateway` data keep
312
+ // the legacy `profile === null` contract so `capabilityLabel(null)`
313
+ // still returns `'metadata unavailable'` (Phase 2 owns that rewrite).
314
+ const gw = (candidate && typeof candidate._gateway === 'object' && candidate._gateway) || {};
315
+ const gatewayName = typeof gw.name === 'string' ? gw.name
316
+ : (typeof gw.display_name === 'string' ? gw.display_name : null);
317
+ const gatewayDescription = typeof gw.description === 'string' ? gw.description : null;
318
+ if (gatewayName === null && gatewayDescription === null) {
319
+ return { ...candidate, profile: null, contextWindow: null };
320
+ }
321
+ const fallbackProfile = {
322
+ gatewayId,
323
+ baseModel: gatewayId,
324
+ name: gatewayName,
325
+ family: null,
326
+ description: gatewayDescription,
327
+ summary: null,
328
+ capabilities: {
329
+ attachment: false,
330
+ reasoning: false,
331
+ toolCall: false,
332
+ structuredOutput: false,
333
+ temperature: true,
334
+ inputModalities: ['text'],
335
+ outputModalities: ['text'],
336
+ },
337
+ limits: { contextTokens: null, inputTokens: null, outputTokens: null },
338
+ releaseDate: null,
339
+ lastUpdated: null,
340
+ metadata: {
341
+ source: 'gateway-fallback',
342
+ sourceUrl: null,
343
+ retrievedAt: new Date().toISOString(),
344
+ matchType: 'gateway-fallback',
345
+ confidence: 0,
346
+ },
347
+ };
348
+ return { ...candidate, profile: fallbackProfile, contextWindow: null };
290
349
  });
291
350
  }
292
351
 
@@ -379,7 +438,24 @@ async function fetchOnce({ doFetch, url, authToken, timeoutMs }) {
379
438
  }
380
439
  }
381
440
 
382
- function normalizeModels(body) {
441
+ /**
442
+ * Normalize the gateway `/models` response into the candidate-pool shape
443
+ * consumed by the picker.
444
+ *
445
+ * Phase 1 (v10.19.7): in addition to the existing `id` / `owned_by` / `kind`
446
+ * fields, preserve the gateway's optional `name` / `display_name` /
447
+ * `description` payload under a new `_gateway` sub-object. The picker row
448
+ * renderer reads `profile.name` / `profile.description`, so when the
449
+ * Models.dev enrichment misses (Phase 2) the renderer can still surface a
450
+ * gateway-supplied label or description rather than an id-derived fallback.
451
+ *
452
+ * NOTE: `_gateway` is in-memory only. `applyModels` never writes it to
453
+ * `model-router.json`; the persisted shape stays as it was before this
454
+ * change. See `models-namespace-sync.test.mjs#normalizeModels does not
455
+ * persist _gateway into userSelected on round-trip` for the regression
456
+ * pin.
457
+ */
458
+ export function normalizeModels(body) {
383
459
  if (!body || typeof body !== 'object') return [];
384
460
  const list = Array.isArray(body.data) ? body.data : Array.isArray(body) ? body : [];
385
461
  const out = [];
@@ -388,7 +464,11 @@ function normalizeModels(body) {
388
464
  const id = typeof m.id === 'string' ? m.id.trim() : '';
389
465
  if (!id) continue;
390
466
  const owned = typeof m.owned_by === 'string' ? m.owned_by : '';
391
- out.push({ id, owned_by: owned, kind: classifyKind(id) });
467
+ const gw = {};
468
+ if (typeof m.name === 'string' && m.name) gw.name = m.name;
469
+ if (typeof m.display_name === 'string' && m.display_name) gw.display_name = m.display_name;
470
+ if (typeof m.description === 'string' && m.description) gw.description = m.description;
471
+ out.push({ id, owned_by: owned, kind: classifyKind(id), _gateway: gw });
392
472
  }
393
473
  // Stable order — by id — so the picker does not shuffle between runs.
394
474
  out.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
@@ -182,17 +182,14 @@ export async function runTestGate() {
182
182
 
183
183
  export async function run(name, args, isHelpRequest) {
184
184
  switch (name) {
185
- case 'update':
186
- // `bizar update` lives in commands/install.mjs (the install/update
187
- // pair share a code path). Proxy to it.
188
- if (isHelpRequest) {
189
- const { showUpdateHelp } = await import('./install.mjs');
190
- showUpdateHelp();
191
- } else {
192
- const { runUpdate } = await import('./install.mjs');
193
- await runUpdate(args, {});
194
- }
195
- break;
185
+ // NOTE: 'update' is intentionally NOT routed through this dispatcher.
186
+ // `cli/bin.mjs` dispatches 'install' / 'update' directly to
187
+ // `cli/commands/install.mjs`, which owns both commands (they share
188
+ // the same code path). The legacy branch below used to import
189
+ // `runUpdate` from `./install.mjs`, but that module never exported
190
+ // `runUpdate` — the import would throw at runtime. The branch was
191
+ // dead code, deleted in v10.19.6 as part of the `bizar update` audit
192
+ // fix.
196
193
 
197
194
  case 'audit':
198
195
  if (isHelpRequest) showAuditHelp();
package/cli/provision.mjs CHANGED
@@ -1431,10 +1431,11 @@ if (import.meta.url === `file://${process.argv[1]}`) {
1431
1431
  });
1432
1432
  }
1433
1433
 
1434
- // Back-compat alias — `bizar update` historically
1435
- // called `runUpdate(args)`; we collapsed install + update onto
1436
- // `runProvision({ mode: 'update', ... })`. Keep `runUpdate` importable so
1437
- // `cli/commands/install.mjs` (which still uses the historical signature)
1438
- // works without modification.
1434
+ // Back-compat alias — `bizar update` historically called `runUpdate(args)`;
1435
+ // install + update now share `runProvision({ mode: 'update', ... })`.
1436
+ // `runUpdate` is retained as an importable export for external SDK
1437
+ // consumers (re-exported from `cli/update.mjs`); `cli/commands/install.mjs`
1438
+ // no longer imports it — its `update()` command routes through `parseFlags`
1439
+ // → `runInstaller({ mode: 'update', ... })` instead.
1439
1440
  export const runUpdate = (subargs, opts = {}) =>
1440
1441
  runProvision({ ...opts, mode: 'update', subargs });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "10.19.5",
3
+ "version": "10.19.7",
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.19.5";
4
+ export declare const SDK_VERSION: "10.19.7";
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.19.5";
4
+ export const SDK_VERSION = "10.19.7";
5
5
  //# sourceMappingURL=version.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar-sdk",
3
- "version": "10.19.5",
3
+ "version": "10.19.7",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",