Skip to content

feat: Built-in provider wizard + /provider command + .env/auth fixes - #29

Merged
Doorman11991 merged 18 commits into
Doorman11991:masterfrom
TheArchitectit:feat/provider-wizard
May 27, 2026
Merged

feat: Built-in provider wizard + /provider command + .env/auth fixes#29
Doorman11991 merged 18 commits into
Doorman11991:masterfrom
TheArchitectit:feat/provider-wizard

Conversation

@TheArchitectit

Copy link
Copy Markdown
Contributor

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 /provider command, plus 7 bug fixes for .env loading, authentication headers, and provider persistence across projects.

Changes

Provider wizard (bin/provider-wizard/wizard.js)

  • runWizard(options) — interactive wizard with 4 steps:
    1. Provider selection (LM Studio, Ollama, OpenRouter, OpenAI, Anthropic, DeepSeek, custom)
    2. Base URL configuration (with provider-specific defaults)
    3. API key entry (skipped for local providers, required for custom endpoints)
    4. Model selection (auto-lists from provider's /v1/models endpoint)
  • Supports both interactive (readline) and non-interactive (tool args) modes
  • formatProvidersList() — renders the provider menu with numbered options

Provider status detection (bin/provider-wizard/status.js)

  • getStatus() — reads .env files, env vars, and smallcode.toml to detect current provider config
  • formatStatus() — renders a human-readable status report
  • PROVIDERS — registry of all supported providers with defaults and key env names
  • parseEnvFile() — reads .env files into key-value pairs

Tool handlers

  • bin/provider-wizard/tool-status.jsprovider_status tool handler (reads status, returns formatted string)
  • bin/provider-wizard/tool-configure.jsconfigure_provider tool handler (runs wizard with optional args)

/provider command (bin/commands.js + bin/smallcode.js)

  • New /provider slash command that dispatches to the wizard
  • Works both when a model IS configured (reconfigure) and when none is set (initial setup)
  • Registered in the command table alongside /clear, /undo, /commit, etc.

configure_provider tool (bin/executor.js)

  • New tool available to the LLM for programmatic provider configuration
  • Accepts provider, baseUrl, model, apiKey, escalationProvider, escalationModel
  • Falls back to interactive wizard when no args provided

Bug fixes

1. Export PROVIDER_TOOLS (bin/tools.js)

  • PROVIDER_TOOLS was defined but not exported — tools.js couldn't reference the built-in provider tools

2. /provider when no model configured (bin/smallcode.js)

  • /provider was only dispatched in the non-interactive branch (when no model was set)
  • Added dispatch in the main main() function so /provider works in all contexts

3. /provider when model IS configured (bin/smallcode.js)

  • /provider dispatch was placed before the positional prompt branch
  • Now correctly intercepts the command even when SMALLCODE_MODEL is set

4. createCommandHandler require (bin/smallcode.js)

  • Provider dispatch required createCommandHandler but it wasn't imported at the dispatch point
  • Added proper require('./commands') import

5. Custom provider endpoint API key (bin/provider-wizard/wizard.js)

  • Custom endpoints skipped the API key step — the wizard now always prompts for an API key with custom providers
  • Prevents silent auth failures downstream

6. Provider config persistence (bin/provider-wizard/wizard.js)

  • Provider config was written to local .env — didn't persist across projects
  • Now writes to global ~/.smallcode/.env for cross-project persistence

7. .env loading (bin/smallcode.js)

  • loadDotenv() stopped at the first .env file found (local or global)
  • Now loads all .env files (local first, then global) and merges them

8. Missing os import (bin/provider-wizard/wizard.js)

  • os.homedir() was called without importing os — crashed on /provider command
  • Added const os = require('os') to the import block

Files changed

  • bin/smallcode.js — /provider dispatch, .env loading, createCommandHandler require
  • bin/commands.js — /provider command registration
  • bin/executor.js — configure_provider tool case
  • bin/tools.js — PROVIDER_TOOLS export
  • bin/config.js — provider configuration wiring
  • bin/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 init

Stacked with:

🤖 Generated with Claude Code

TheArchitectit and others added 5 commits May 21, 2026 14:47
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>
TheArchitectit and others added 11 commits May 21, 2026 14:54
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>

@Doorman11991 Doorman11991 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wizard UX is good. Issues to fix before merge:

  1. bin/commands.js /help block has chalk.cron('/sessions') — typo for chalk.cyan. Throws TypeError: chalk.cron is not a function whenever a user runs /help.
  2. /provider handler does const { configureProvider } = require('../src/compiled/providers/registry') but registry.js only exports register / get / has / list / getCapabilities. configureProvider is undefined, then wrapped in try { configureProvider(); } catch {} so the failure is silently swallowed and the wizard's persistence side-effect never runs through the registry.
  3. .smallcode/plugins/anthropic-provider/adapter.js reads tc.name / tc.arguments; SmallCode's internal tool_calls are OpenAI-format and nest under tc.function. Use tc.function.name / tc.function.arguments.
  4. resolveProvider() registers the OpenAI-compat fallback into the registry under any unknown name, so subsequent calls hit the plugin-provider branch in chatCompletion and bypass the real fetch. Make the fallback return without registering.
  5. positional.find(a => a.startsWith('/provider') || a === 'provider') will also match /provider-foo, /providerwhatever, etc. Use exact match or '/provider ' prefix.
  6. 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 calls runInit). 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.
  7. 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 --scope choice (project / user / global).
  8. Rebase on master — current head is v0.9.7 and package-lock.json is still at 0.9.6.

Your Name and others added 2 commits May 23, 2026 22:57
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
Doorman11991 merged commit b92b4ab into Doorman11991:master May 27, 2026
2 checks passed

@Doorman11991 Doorman11991 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@Doorman11991

Copy link
Copy Markdown
Owner

Landed in b92b4ab on master, shipped as 1.3.0. Verification details in the merge commit. Thanks @TheArchitectit.

Kothulhu94 pushed a commit to Kothulhu94/smallcode that referenced this pull request May 29, 2026
…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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants