feat: Built-in provider wizard + /provider command + .env/auth fixes - #29
Conversation
Implements the core plugin provider infrastructure: - Add ProviderRegistry singleton for named provider lookup - Wire plugin providers into chatCompletion and escalation flows - Support plugin manifest `providers` field in loader - Register openai_compat and ollama providers on startup Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implements a decorator-style provider that wraps any registered provider and injects custom content into system prompts. Supports prepend, append, and replace positions. Inner provider is lazily resolved from the ProviderRegistry at first chat() call. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds plugin init/shutdown lifecycle and hook events for LLM request interception: - `init` / `shutdown` manifest fields for startup/teardown handlers - `pre_request`, `post_request`, `on_error` hook events in chat path - `session_start`, `session_end` events (wired at session boundaries) - `runInit()`, `runShutdown()`, `runHooks()` methods on PluginLoader - Hook event validation with warning for unknown events Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds support for: - `permissions` manifest field (read/write/execute/network) - `mcpServers` manifest field for declaring bundled MCP servers - `capabilities` field on provider declarations (passed to registry) - getPermissions(), hasPermission(), getMCPServers() accessors - Default permissions: read-only, no write/execute/network Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PROVIDER_TOOLS was defined but not included in module.exports, causing TypeError: PROVIDER_TOOLS is not iterable in smallcode.js. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
End-to-end demonstration plugin implementing all 7 features: - adapter.js: IModelProvider for Anthropic Messages API (tools, vision) - init.js: API key validation on startup - cleanup.js: shutdown hook - pre-request.js / post-request.js / on-error.js: lifecycle hooks - plugin.json: full manifest with permissions, providers, capabilities Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add provider-wizard plugin: /provider command for interactive setup of LLM providers (LM Studio, Ollama, OpenRouter, OpenAI, Anthropic, DeepSeek, custom). Includes /provider status tool and configure tool with validation and discovery. - Add plugin install scopes (project/user/global) via --scope flag with per-scope add/remove commands - Fix plugin command dispatch: strip leading slash before lookup - Fix symlink handling in plugin loader: readdirSync with withFileTypes doesn't follow symlinks, so isDirectory() returns false for them Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves the provider wizard out of the plugin system (.smallcode/plugins/provider-wizard/) into bin/provider-wizard/ as a built-in feature. This makes the wizard always available without requiring plugin install, and gives the PR its own clear identity. Changes: - bin/provider-wizard/ - wizard, status, tool-configure, tool-status (moved from plugin) - bin/commands.js - /provider command wired directly (not via plugin dispatch) - bin/executor.js - configure_provider + provider_status tool cases added - bin/tools.js - PROVIDER_TOOLS schema array (configure_provider, provider_status) - bin/smallcode.js - ALL_TOOLS includes PROVIDER_TOOLS - .smallcode/plugins/provider-wizard/ - removed (no longer a plugin) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The model gate at startup was blocking all commands including /provider. Now /provider commands are dispatched before the model check so users can configure a provider even with no model set. Also changes the no-model path to always show the setup TUI instead of only when a plugin command was registered (provider is now built-in). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Only ollama and LM Studio are truly key-free. Custom endpoints often need an API key (e.g. vLLM with auth, proxied APIs). Changed custom provider keyEnv from null to 'SMALLCODE_API_KEY' so the wizard prompts for it. Also fixed validateApiKey to accept the actual base URL instead of always using the provider's defaultUrl (which is empty for custom). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The wizard previously only wrote to project .env, causing re-prompting when switching projects. Now writes to ~/.config/smallcode/.env which the startup loader already checks as a global fallback. Project .env still gets written too if it exists, so project-level overrides work. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The loader used `break` after finding the first .env, so if a project had its own .env, the global ~/.config/smallcode/.env was never loaded. This caused provider config to disappear when switching projects. Now loads all env files with project-level values taking priority. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The os module was never imported, causing 'os is not defined' fatal error when the wizard tried to write to ~/.config/smallcode/. This meant the global env file was never created and provider config couldn't persist across projects. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The /provider wizard stores the API key as SMALLCODE_API_KEY but the HTTP auth code only checked OPENAI_API_KEY, ANTHROPIC_API_KEY, and DEEPSEEK_API_KEY — so custom endpoint keys were never sent, causing 401 Unauthorized on every request. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The /provider command was only dispatched when no model was set. This adds a handler before the positional prompt branch so users can reconfigure their provider even after one is already set. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
85a5740 to
ce4a697
Compare
Doorman11991
left a comment
There was a problem hiding this comment.
Wizard UX is good. Issues to fix before merge:
bin/commands.js/helpblock haschalk.cron('/sessions')— typo forchalk.cyan. ThrowsTypeError: chalk.cron is not a functionwhenever a user runs/help./providerhandler doesconst { configureProvider } = require('../src/compiled/providers/registry')butregistry.jsonly exportsregister / get / has / list / getCapabilities.configureProviderisundefined, then wrapped intry { configureProvider(); } catch {}so the failure is silently swallowed and the wizard's persistence side-effect never runs through the registry..smallcode/plugins/anthropic-provider/adapter.jsreadstc.name/tc.arguments; SmallCode's internal tool_calls are OpenAI-format and nest undertc.function. Usetc.function.name/tc.function.arguments.resolveProvider()registers the OpenAI-compat fallback into the registry under any unknown name, so subsequent calls hit the plugin-provider branch inchatCompletionand bypass the real fetch. Make the fallback return without registering.positional.find(a => a.startsWith('/provider') || a === 'provider')will also match/provider-foo,/providerwhatever, etc. Use exact match or'/provider 'prefix.- In
main(), when a model is configured,new PluginLoader(...).loadAll()runs twice (once early for the no-model branch, then again at the original site that callsrunInit). This re-registers providers and re-pushes init handlers — providers get clobbered with new instances and any side-effects in plugin module top-level run twice. - The wizard always writes to
~/.config/smallcode/.env, including API keys, with no prompt. That globally persists secrets across every project the user later opens. Add a confirm step or a--scopechoice (project / user / global). - Rebase on master — current head is v0.9.7 and
package-lock.jsonis still at 0.9.6.
1. Fix chalk.cron typo → chalk.cyan in /help (was crashing /help command)
2. Remove undefined configureProvider import from registry (was silently
swallowed by try/catch, making post-wizard registry activation a no-op)
3. Fix adapter.js tool_calls format: use tc.function.name/arguments on
input and return OpenAI-format { id, type, function: {...} } on output
4. Remove providerRegistry.register() from resolveProvider() fallback —
resolveProvider is a lookup, not a mutation; registering the fallback
pollutes the registry and bypasses real fetch on subsequent calls
5. Use exact match for /provider command instead of startsWith('/provider')
which incorrectly matched /provider-foo, /providers, etc.
6. Remove duplicate PluginLoader.loadAll() call — was running loadAll()
twice (once before no-model check, once after), causing providers to
be re-registered with new instances and hooks/init to fire twice
7. Add scope prompt to wizard before writing config — previously wrote
API keys to ~/.config/smallcode/.env without confirmation; now asks
user to choose global/project/both, defaults to project-only for
non-interactive mode
8. Fix runHooks catch block: plugin → hook.plugin (bare `plugin` was
undefined in scope, masking real errors with ReferenceError)
Also removes the dead configureProvider import from executor.js.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…r match site
1. Remove fallback to tc.name/tc.arguments in adapter.js — SmallCode always
uses OpenAI-format tool_calls, so the fallback was dead code that could
mask future format-breaking changes.
2. Fix the second positional.find('/provider') call site in the
model-configured branch (line 2694) — was still using startsWith which
incorrectly matches /provider-foo, /providers, etc.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Doorman11991
left a comment
There was a problem hiding this comment.
Reviewed locally. The provider wizard, /provider command, configure_provider / provider_status tools, and the --scope flag for /plugin install / remove are all good additions. Merging by hand on top of master to resolve the conflicts: PR #28 already shipped a superset of the plugin-loader / registry / anthropic-adapter changes, and the auth-header sites needed master's 1.2.1 provider-aware buildAuthHeaders() preserved (otherwise OpenAI keys leak to DeepSeek). Tested 124/124 unit tests + offline E2E green. Thank you for both PRs.
|
Landed in b92b4ab on master, shipped as 1.3.0. Verification details in the merge commit. Thanks @TheArchitectit. |
…oorman11991#28) Resolves the conflicts that left PR Doorman11991#29 in CONFLICTING state. Strategy: - Conflicting files where PR Doorman11991#28 already shipped a superset (.smallcode/plugins/anthropic-provider/adapter.js, src/compiled/providers/index.js, src/compiled/providers/registry.js, src/plugins/loader.js) — kept master's version. - Auth-header sites (bin/config.js, bin/smallcode.js × 4) — kept the 1.2.1 provider-aware buildAuthHeaders() so OpenAI keys aren't sent to DeepSeek when both are configured. - Tool dispatch (bin/executor.js) — combined contract_* cases (PR Doorman11991#28) and configure_provider / provider_status cases (PR Doorman11991#29). - PROVIDER_TOOLS in bin/tools.js — kept PR Doorman11991#29's richer schema (configure_provider with full args + provider_status), dropped the duplicate single-arg version added by PR Doorman11991#28. - bin/smallcode.js pre_request hook — placed once before the plugin-provider check so it fires for all providers, removed the duplicate placement after the registry call. PR Doorman11991#29's unique additions (bin/provider-wizard/*, /provider command, --scope flag for /plugin install) ship as authored. Adds test/provider_wizard.test.js (9 tests) pinning parseEnvFile, mergeEnvFile, formatStatus, and the PROVIDERS registry. Verification - 124/124 unit tests pass (npm test) - Offline E2E (npm run test:e2e:offline) green - bin/smallcode.js parses cleanly (node --check) Bumps to 1.3.0 because the plugin manifest is a new public surface. Closes PR Doorman11991#29. Credits: @TheArchitectit (PR Doorman11991#28, PR Doorman11991#29).
Summary
This is PR 2 of 3 in a stacked PR chain implementing the plugin-provider architecture for SmallCode.
This PR adds a built-in provider configuration wizard that runs via the
/providercommand, plus 7 bug fixes for.envloading, authentication headers, and provider persistence across projects.Changes
Provider wizard (
bin/provider-wizard/wizard.js)runWizard(options)— interactive wizard with 4 steps:/v1/modelsendpoint)formatProvidersList()— renders the provider menu with numbered optionsProvider status detection (
bin/provider-wizard/status.js)getStatus()— reads.envfiles, env vars, andsmallcode.tomlto detect current provider configformatStatus()— renders a human-readable status reportPROVIDERS— registry of all supported providers with defaults and key env namesparseEnvFile()— reads.envfiles into key-value pairsTool handlers
bin/provider-wizard/tool-status.js—provider_statustool handler (reads status, returns formatted string)bin/provider-wizard/tool-configure.js—configure_providertool handler (runs wizard with optional args)/providercommand (bin/commands.js+bin/smallcode.js)/providerslash command that dispatches to the wizard/clear,/undo,/commit, etc.configure_providertool (bin/executor.js)provider,baseUrl,model,apiKey,escalationProvider,escalationModelBug fixes
1. Export PROVIDER_TOOLS (
bin/tools.js)PROVIDER_TOOLSwas defined but not exported — tools.js couldn't reference the built-in provider tools2. /provider when no model configured (
bin/smallcode.js)/providerwas only dispatched in the non-interactive branch (when no model was set)main()function so/providerworks in all contexts3. /provider when model IS configured (
bin/smallcode.js)/providerdispatch was placed before the positional prompt branchSMALLCODE_MODELis set4. createCommandHandler require (
bin/smallcode.js)createCommandHandlerbut it wasn't imported at the dispatch pointrequire('./commands')import5. Custom provider endpoint API key (
bin/provider-wizard/wizard.js)6. Provider config persistence (
bin/provider-wizard/wizard.js).env— didn't persist across projects~/.smallcode/.envfor cross-project persistence7. .env loading (
bin/smallcode.js)loadDotenv()stopped at the first.envfile found (local or global).envfiles (local first, then global) and merges them8. Missing
osimport (bin/provider-wizard/wizard.js)os.homedir()was called without importingos— crashed on/providercommandconst os = require('os')to the import blockFiles changed
bin/smallcode.js— /provider dispatch, .env loading, createCommandHandler requirebin/commands.js— /provider command registrationbin/executor.js— configure_provider tool casebin/tools.js— PROVIDER_TOOLS exportbin/config.js— provider configuration wiringbin/provider-wizard/wizard.js— interactive wizard (318 lines)bin/provider-wizard/status.js— status detection (112 lines)bin/provider-wizard/tool-configure.js— tool handler (29 lines)bin/provider-wizard/tool-status.js— tool handler (7 lines)bin/init.js— symlink fix for initStacked with:
🤖 Generated with Claude Code