@polderlabs/bizar 10.23.3 → 10.23.5
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 +1 -1
- package/cli/commands/install.mjs +9 -3
- package/cli/commands/setup-provider.mjs +12 -6
- package/cli/install/index.mjs +12 -2
- package/cli/install/interactive-setup.mjs +131 -0
- package/cli/provision.mjs +1 -1
- package/config/claude/commands/setup-provider.md +5 -2
- package/package.json +1 -1
- package/packages/sdk/dist/version.d.ts +1 -1
- package/packages/sdk/dist/version.js +1 -1
- package/packages/sdk/package.json +1 -1
package/cli/bin.mjs
CHANGED
|
@@ -113,7 +113,7 @@ function showHelp() {
|
|
|
113
113
|
backup Create / list / verify / delete backups of BizarHarness state
|
|
114
114
|
restore Restore BizarHarness from a backup
|
|
115
115
|
validate Validate the Bizar install
|
|
116
|
-
setup-provider Configure
|
|
116
|
+
setup-provider Configure the global provider used by Bizar and Claude Code
|
|
117
117
|
release-provenance Generate SBOM + provenance + minisig for a release (audit #83)
|
|
118
118
|
verify-release Verify a release artifact set against the pinned allowlist
|
|
119
119
|
spec-list List SDK schemas, policy docs, and mirror sync status (audit #84)
|
package/cli/commands/install.mjs
CHANGED
|
@@ -29,7 +29,8 @@ export function showInstallHelp() {
|
|
|
29
29
|
from the repo. Preserves ~/.config/bizar/
|
|
30
30
|
login state. Combine with --yes to skip prompts.
|
|
31
31
|
bizar install --deep Alias for --force (clean-install semantics)
|
|
32
|
-
bizar install --yes
|
|
32
|
+
bizar install --yes Non-interactive install (CI/script friendly)
|
|
33
|
+
bizar install --non-interactive Alias for --yes
|
|
33
34
|
bizar install --help Show this help
|
|
34
35
|
|
|
35
36
|
Description:
|
|
@@ -59,8 +60,13 @@ export function showInstallHelp() {
|
|
|
59
60
|
4. Registers the Bizar MCP server in ~/.claude/settings.json.
|
|
60
61
|
5. Wires Claude Code lifecycle hooks (SessionStart / PreToolUse /
|
|
61
62
|
PostToolUse / UserPromptSubmit) under ~/.claude/hooks/.
|
|
62
|
-
6.
|
|
63
|
-
|
|
63
|
+
6. In a terminal, confirms the install and securely asks for a provider
|
|
64
|
+
URL and key only when they are not already configured.
|
|
65
|
+
7. Runs 'bizar doctor' as a post-install health check.
|
|
66
|
+
|
|
67
|
+
Provider settings are global (~/.claude/settings.json), so they work from
|
|
68
|
+
every project. Key input is hidden. Use --yes or --non-interactive to skip
|
|
69
|
+
all prompts; missing provider values then produce actionable guidance.
|
|
64
70
|
`);
|
|
65
71
|
}
|
|
66
72
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Configure Claude Code's provider environment in settings.json.
|
|
3
3
|
*
|
|
4
4
|
* Bizar does not maintain a parallel provider registry. Claude Code reads
|
|
5
|
-
* ANTHROPIC_BASE_URL,
|
|
5
|
+
* ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN, and ANTHROPIC_MODEL directly.
|
|
6
6
|
*/
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
@@ -41,10 +41,16 @@ export function parseProviderArgs(args = []) {
|
|
|
41
41
|
|
|
42
42
|
export function updateProviderSettings(current, options) {
|
|
43
43
|
const next = { ...current, env: { ...(current.env || {}) } };
|
|
44
|
-
if (options.gateway !== undefined)
|
|
45
|
-
|
|
44
|
+
if (options.gateway !== undefined) {
|
|
45
|
+
next.env.ANTHROPIC_BASE_URL = options.gateway;
|
|
46
|
+
next.env.BIZAR_MODEL_ROUTER_URL = options.gateway;
|
|
47
|
+
}
|
|
48
|
+
if (options.key !== undefined) next.env.ANTHROPIC_AUTH_TOKEN = options.key;
|
|
46
49
|
if (options.model !== undefined) next.env.ANTHROPIC_MODEL = options.model;
|
|
47
|
-
if (options.removeKey)
|
|
50
|
+
if (options.removeKey) {
|
|
51
|
+
delete next.env.ANTHROPIC_AUTH_TOKEN;
|
|
52
|
+
delete next.env.ANTHROPIC_API_KEY;
|
|
53
|
+
}
|
|
48
54
|
return next;
|
|
49
55
|
}
|
|
50
56
|
|
|
@@ -81,9 +87,9 @@ export async function runSetupProvider(args = []) {
|
|
|
81
87
|
|
|
82
88
|
if (options.list) {
|
|
83
89
|
console.log(` Settings: ${path}`);
|
|
84
|
-
console.log(` Gateway: ${current.env?.ANTHROPIC_BASE_URL || '(Anthropic default)'}`);
|
|
90
|
+
console.log(` Gateway: ${current.env?.ANTHROPIC_BASE_URL || current.env?.BIZAR_MODEL_ROUTER_URL || '(Anthropic default)'}`);
|
|
85
91
|
console.log(` Model: ${current.env?.ANTHROPIC_MODEL || '(Claude Code default)'}`);
|
|
86
|
-
console.log(` API key: ${redact(current.env?.ANTHROPIC_API_KEY)}`);
|
|
92
|
+
console.log(` API key: ${redact(current.env?.ANTHROPIC_AUTH_TOKEN || current.env?.ANTHROPIC_API_KEY)}`);
|
|
87
93
|
return { ok: true, path, settings: current };
|
|
88
94
|
}
|
|
89
95
|
|
package/cli/install/index.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { runProvision, forceCleanInstall, clearSavedEnv } from '../provision.mjs
|
|
|
9
9
|
import { runDoctor } from '../doctor.mjs';
|
|
10
10
|
import { showBanner, sectionHeading } from './banner.mjs';
|
|
11
11
|
import { printInstallLocations } from './paths.mjs';
|
|
12
|
+
import { runInteractiveSetup } from './interactive-setup.mjs';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Thin orchestrator entry point.
|
|
@@ -43,6 +44,15 @@ export async function runInstaller(opts = {}) {
|
|
|
43
44
|
showBanner();
|
|
44
45
|
printInstallLocations({ dryRun, force });
|
|
45
46
|
|
|
47
|
+
// A normal TTY install is a guided setup. Automation remains prompt-free
|
|
48
|
+
// via --yes / --non-interactive, and update runs never request credentials.
|
|
49
|
+
let interactive = null;
|
|
50
|
+
if (mode !== 'update' && !dryRun) {
|
|
51
|
+
interactive = await runInteractiveSetup({ enabled: !yes });
|
|
52
|
+
if (!interactive.ok) return { ok: false, interactive };
|
|
53
|
+
if (interactive.cancelled) return { ok: true, cancelled: true, interactive };
|
|
54
|
+
}
|
|
55
|
+
|
|
46
56
|
// F-183 — pre-provision wipe for forced installs. Force-clean is what
|
|
47
57
|
// makes `--force` actually a clean install: dirs under ~/.claude/ are
|
|
48
58
|
// wiped (BIZAR_HOME and third-party state preserved), settings.json
|
|
@@ -94,5 +104,5 @@ export async function runInstaller(opts = {}) {
|
|
|
94
104
|
}
|
|
95
105
|
}
|
|
96
106
|
|
|
97
|
-
return { ...provisionResult, clean };
|
|
98
|
-
}
|
|
107
|
+
return { ...provisionResult, clean, interactive };
|
|
108
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { Writable } from 'node:stream';
|
|
5
|
+
import { createInterface } from 'node:readline/promises';
|
|
6
|
+
|
|
7
|
+
export function providerSettingsPath(env = process.env) {
|
|
8
|
+
const root = env.CLAUDE_CONFIG_DIR?.trim()
|
|
9
|
+
|| join(env.HOME?.trim() || homedir(), '.claude');
|
|
10
|
+
return join(root, 'settings.json');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function readProviderSettings(path = providerSettingsPath()) {
|
|
14
|
+
if (!existsSync(path)) return {};
|
|
15
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function detectProviderConfiguration({ env = process.env, settings = {} } = {}) {
|
|
19
|
+
const settingsEnv = settings?.env && typeof settings.env === 'object' ? settings.env : {};
|
|
20
|
+
const url = env.BIZAR_MODEL_ROUTER_URL?.trim()
|
|
21
|
+
|| env.ANTHROPIC_BASE_URL?.trim()
|
|
22
|
+
|| settingsEnv.BIZAR_MODEL_ROUTER_URL?.trim()
|
|
23
|
+
|| settingsEnv.ANTHROPIC_BASE_URL?.trim()
|
|
24
|
+
|| '';
|
|
25
|
+
const key = env.ANTHROPIC_AUTH_TOKEN?.trim()
|
|
26
|
+
|| env.ANTHROPIC_API_KEY?.trim()
|
|
27
|
+
|| settingsEnv.ANTHROPIC_AUTH_TOKEN?.trim()
|
|
28
|
+
|| settingsEnv.ANTHROPIC_API_KEY?.trim()
|
|
29
|
+
|| '';
|
|
30
|
+
return { url, key, missing: [...(!url ? ['url'] : []), ...(!key ? ['key'] : [])] };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isValidProviderUrl(value) {
|
|
34
|
+
try {
|
|
35
|
+
const parsed = new URL(value);
|
|
36
|
+
return parsed.protocol === 'https:' || parsed.protocol === 'http:';
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function askLine(prompt, { input = process.stdin, output = process.stdout } = {}) {
|
|
43
|
+
const rl = createInterface({ input, output });
|
|
44
|
+
try {
|
|
45
|
+
return await rl.question(prompt);
|
|
46
|
+
} finally {
|
|
47
|
+
rl.close();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Read a secret through readline without forwarding its terminal redraws. */
|
|
52
|
+
export async function askSecret(prompt, { input = process.stdin, output = process.stdout } = {}) {
|
|
53
|
+
const muted = new Writable({
|
|
54
|
+
write(_chunk, _encoding, callback) { callback(); },
|
|
55
|
+
});
|
|
56
|
+
const rl = createInterface({ input, output: muted, terminal: true });
|
|
57
|
+
output.write(prompt);
|
|
58
|
+
try {
|
|
59
|
+
return await rl.question('');
|
|
60
|
+
} finally {
|
|
61
|
+
rl.close();
|
|
62
|
+
output.write('\n');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function writeLine(output, value = '') {
|
|
67
|
+
output.write(`${value}\n`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Guided install preflight. Credentials are placed only in the current
|
|
72
|
+
* process; the provisioner's settings writer persists them globally with the
|
|
73
|
+
* rest of the install. Injected question functions keep the policy testable.
|
|
74
|
+
*/
|
|
75
|
+
export async function runInteractiveSetup({
|
|
76
|
+
env = process.env,
|
|
77
|
+
input = process.stdin,
|
|
78
|
+
output = process.stdout,
|
|
79
|
+
enabled = true,
|
|
80
|
+
readSettings = readProviderSettings,
|
|
81
|
+
askText = askLine,
|
|
82
|
+
askHidden = askSecret,
|
|
83
|
+
} = {}) {
|
|
84
|
+
let settings;
|
|
85
|
+
try {
|
|
86
|
+
settings = readSettings(providerSettingsPath(env));
|
|
87
|
+
} catch (error) {
|
|
88
|
+
return { ok: false, error: `Cannot read global Claude settings: ${error.message}` };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const detected = detectProviderConfiguration({ env, settings });
|
|
92
|
+
const interactive = enabled && input.isTTY === true && output.isTTY === true;
|
|
93
|
+
if (!interactive) {
|
|
94
|
+
if (detected.missing.length > 0) {
|
|
95
|
+
writeLine(output, ` ! Provider ${detected.missing.join(' and ')} not detected; set ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN or run \`bizar setup-provider\`.`);
|
|
96
|
+
}
|
|
97
|
+
return { ok: true, interactive: false, configured: detected.missing.length === 0, missing: detected.missing };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
writeLine(output, '');
|
|
101
|
+
writeLine(output, ' Interactive setup');
|
|
102
|
+
const confirmation = (await askText(' Continue with the installation? [Y/n] ', { input, output })).trim().toLowerCase();
|
|
103
|
+
if (confirmation === 'n' || confirmation === 'no') {
|
|
104
|
+
writeLine(output, ' Installation cancelled.');
|
|
105
|
+
return { ok: true, interactive: true, cancelled: true, configured: detected.missing.length === 0 };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let url = detected.url;
|
|
109
|
+
let key = detected.key;
|
|
110
|
+
if (url) writeLine(output, ` ✓ Provider URL detected: ${url}`);
|
|
111
|
+
while (!url) {
|
|
112
|
+
const answer = (await askText(' Provider URL (for example https://gateway.example/v1): ', { input, output })).trim();
|
|
113
|
+
if (!isValidProviderUrl(answer)) {
|
|
114
|
+
writeLine(output, ' ! Enter a valid http:// or https:// URL.');
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
url = answer.replace(/\/+$/, '');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (key) writeLine(output, ' ✓ Provider key detected (hidden)');
|
|
121
|
+
while (!key) {
|
|
122
|
+
key = (await askHidden(' Provider API key (input hidden): ', { input, output })).trim();
|
|
123
|
+
if (!key) writeLine(output, ' ! Provider key cannot be empty.');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
env.ANTHROPIC_BASE_URL = url;
|
|
127
|
+
env.BIZAR_MODEL_ROUTER_URL = url;
|
|
128
|
+
env.ANTHROPIC_AUTH_TOKEN = key;
|
|
129
|
+
writeLine(output, ' ✓ Provider configuration ready; the key will be stored in global Claude settings.');
|
|
130
|
+
return { ok: true, interactive: true, cancelled: false, configured: true, missing: detected.missing };
|
|
131
|
+
}
|
package/cli/provision.mjs
CHANGED
|
@@ -1000,7 +1000,7 @@ export function writeClaudeSettings({ dryRun = false, force = false } = {}) {
|
|
|
1000
1000
|
// malformed router has no enabled candidate; dispatch will fail closed.
|
|
1001
1001
|
if (installModel) {
|
|
1002
1002
|
merged.model = installModel;
|
|
1003
|
-
merged.modelOverrides =
|
|
1003
|
+
merged.modelOverrides = bizarSettings.modelOverrides;
|
|
1004
1004
|
}
|
|
1005
1005
|
|
|
1006
1006
|
// Auto-compaction is part of the Bizar reliability contract. Remove legacy
|
|
@@ -14,10 +14,13 @@ bizar setup-provider --remove-key
|
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
The command edits only `env.ANTHROPIC_BASE_URL`,
|
|
17
|
-
`env.
|
|
17
|
+
`env.BIZAR_MODEL_ROUTER_URL`, `env.ANTHROPIC_AUTH_TOKEN`, and
|
|
18
|
+
`env.ANTHROPIC_MODEL` in
|
|
18
19
|
`~/.claude/settings.json` (or `$CLAUDE_CONFIG_DIR/settings.json`).
|
|
19
20
|
All unrelated settings are preserved. It rejects invalid existing JSON
|
|
20
21
|
and never prints a full API key.
|
|
21
22
|
|
|
22
23
|
With no arguments it shows help; it does not guess credentials or query
|
|
23
|
-
an untrusted model catalog.
|
|
24
|
+
an untrusted model catalog. A normal `bizar install` provides the safer
|
|
25
|
+
interactive path with hidden key entry; this command remains useful for
|
|
26
|
+
automation and explicit changes.
|
package/package.json
CHANGED