feat(setup): advanced settings registry with remote OneCLI support
Adds a single config registry that drives both CLI flags and an opt-in advanced-settings screen, so power users can override defaults like remote OneCLI host/token or alt Anthropic endpoints without burdening the standard linear flow with extra prompts. Why: advanced configurations didn't fit cleanly into the existing sequenced setup. PR #2030 took the "add another prompt step" route for remote OneCLI; this approach instead routes those overrides through a single source of truth so adding the next knob (alt endpoint, custom host pattern, …) doesn't mean another prompt-or-skip decision. setup/lib/setup-config.ts — schema (typed entry list with surface 'flag' | 'flag+ui'), name derivation (camelCase → NANOCLAW_UPPER_SNAKE + --kebab-case), seeded with --onecli-api-host, --onecli-api-token, --anthropic-base-url, plus existing NANOCLAW_SKIP / NANOCLAW_DISPLAY_NAME as flag-only entries. setup/lib/setup-config-parse.ts — argv parser (--key value, --key=value, --no-bool, -- terminator), env reader, applyToEnv() bridge that writes resolved values back to process.env so existing step code keeps reading env vars unchanged. Also --help printer. setup/lib/setup-config-screen.ts — interactive menu loop. Entries render with current value as hint; selecting one opens the right prompt type (text / password for secrets / confirm / brightSelect for enums); "Done" returns to the main flow. setup/auto.ts — parses argv first (--help short-circuits before any render), folds env+flags into process.env, then offers a welcome menu: "Standard setup" (default) vs "Advanced". The onecli step branches on NANOCLAW_ONECLI_API_HOST: if set, skips the local-vs-fresh prompt entirely, runs pollHealth pre-flight, then calls runQuietStep with --remote-url. Token, when provided, writes through to ONECLI_API_KEY in .env. Welcome copy tightened (drops the duplicate wordmark/tagline) so the bash → clack handoff reads as one flow. setup/onecli.ts — cherries the --remote-url implementation from PR run()) and generalizes writeEnvOnecliUrl into a writeEnvVar helper so ONECLI_API_KEY follows the same upsert path. nanoclaw.sh — forwards "$@" to setup:auto so flags reach the parser; trims the redundant "Setting up your personal AI assistant" subtitle and the bootstrap teach line so the pre-clack section isn't competing with the clack intro for the same role. Token plumbing only fires in --remote-url mode; local installs are unauthenticated against localhost and don't need it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
130
setup/lib/setup-config.ts
Normal file
130
setup/lib/setup-config.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Setup-time advanced-config registry.
|
||||
*
|
||||
* One source of truth for: CLI flags, env-var names, the advanced-settings
|
||||
* screen, and `--help` output. The flag parser, env reader, and UI screen
|
||||
* all consume this list and write resolved values back to `process.env` so
|
||||
* existing step code keeps reading env vars unchanged.
|
||||
*
|
||||
* Default name conventions (overridable per entry):
|
||||
* key 'fooBar' → envVar 'NANOCLAW_FOO_BAR' → flag '--foo-bar'
|
||||
*
|
||||
* Surface levels:
|
||||
* 'flag' — CLI flag + env var only (debug/internal knobs)
|
||||
* 'flag+ui' — also shown in the advanced-settings screen
|
||||
*/
|
||||
|
||||
export type EntrySurface = 'flag' | 'flag+ui';
|
||||
|
||||
interface BaseEntry {
|
||||
/** Canonical camelCase key. */
|
||||
key: string;
|
||||
/** Override of the auto-derived NANOCLAW_<UPPER_SNAKE> env var. */
|
||||
envVar?: string;
|
||||
/** Override of the auto-derived --kebab-case flag. */
|
||||
flag?: string;
|
||||
label: string;
|
||||
help: string;
|
||||
surface: EntrySurface;
|
||||
/** UI section header. Entries without a group land in 'Other'. */
|
||||
group?: string;
|
||||
/** Mask in UI, redact in logs. */
|
||||
secret?: boolean;
|
||||
}
|
||||
|
||||
interface StringEntry extends BaseEntry {
|
||||
type: 'string' | 'url';
|
||||
default?: string;
|
||||
placeholder?: string;
|
||||
validate?: (v: string) => string | undefined;
|
||||
}
|
||||
|
||||
interface EnumEntry extends BaseEntry {
|
||||
type: 'enum';
|
||||
options: { value: string; label: string; hint?: string }[];
|
||||
default?: string;
|
||||
}
|
||||
|
||||
interface BoolEntry extends BaseEntry {
|
||||
type: 'boolean';
|
||||
default?: boolean;
|
||||
}
|
||||
|
||||
interface IntEntry extends BaseEntry {
|
||||
type: 'integer';
|
||||
default?: number;
|
||||
min?: number;
|
||||
max?: number;
|
||||
}
|
||||
|
||||
export type Entry = StringEntry | EnumEntry | BoolEntry | IntEntry;
|
||||
|
||||
const httpUrl = (v: string): string | undefined =>
|
||||
/^https?:\/\/\S+/.test(v) ? undefined : 'Must be http(s)://…';
|
||||
|
||||
export const CONFIG: Entry[] = [
|
||||
{
|
||||
key: 'onecliApiHost',
|
||||
label: 'OneCLI vault URL',
|
||||
help: 'Use a remote OneCLI vault instead of installing one locally.',
|
||||
surface: 'flag+ui',
|
||||
group: 'OneCLI',
|
||||
type: 'url',
|
||||
placeholder: 'https://vault.example.internal',
|
||||
validate: httpUrl,
|
||||
},
|
||||
{
|
||||
key: 'onecliApiToken',
|
||||
label: 'OneCLI access token',
|
||||
help: 'Bearer token for the remote vault. Required if --onecli-api-host is set.',
|
||||
surface: 'flag+ui',
|
||||
group: 'OneCLI',
|
||||
type: 'string',
|
||||
secret: true,
|
||||
placeholder: 'oat_…',
|
||||
},
|
||||
{
|
||||
key: 'anthropicBaseUrl',
|
||||
label: 'Anthropic API base URL',
|
||||
help: 'Use a proxy or alternative endpoint instead of api.anthropic.com.',
|
||||
surface: 'flag+ui',
|
||||
group: 'Anthropic',
|
||||
type: 'url',
|
||||
placeholder: 'https://api.anthropic.com',
|
||||
validate: httpUrl,
|
||||
},
|
||||
|
||||
// Existing env-var knobs — flag-only so they don't clutter the UI screen.
|
||||
{
|
||||
key: 'skip',
|
||||
envVar: 'NANOCLAW_SKIP',
|
||||
label: 'Skip steps',
|
||||
help: 'Comma-separated step names to skip (debugging only).',
|
||||
surface: 'flag',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
key: 'displayName',
|
||||
envVar: 'NANOCLAW_DISPLAY_NAME',
|
||||
label: 'Display name',
|
||||
help: 'Skip the "what should your assistant call you?" prompt.',
|
||||
surface: 'flag',
|
||||
type: 'string',
|
||||
},
|
||||
];
|
||||
|
||||
// ─── name derivation ───────────────────────────────────────────────────
|
||||
|
||||
export function envVarFor(e: Entry): string {
|
||||
if (e.envVar) return e.envVar;
|
||||
return `NANOCLAW_${e.key.replace(/[A-Z]/g, (c) => `_${c}`).toUpperCase()}`;
|
||||
}
|
||||
|
||||
export function flagFor(e: Entry): string {
|
||||
if (e.flag) return e.flag;
|
||||
return `--${e.key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}`;
|
||||
}
|
||||
|
||||
export function findByFlag(flag: string): Entry | null {
|
||||
return CONFIG.find((e) => flagFor(e) === flag) ?? null;
|
||||
}
|
||||
Reference in New Issue
Block a user