Skip to content
This repository was archived by the owner on Mar 30, 2026. It is now read-only.

feat(cli): dynamic model/provider selection in init command#112

Merged
stepandel merged 1 commit intofeature/AGE-148-expand-model-providersfrom
feature/AGE-149-dynamic-provider-selection
Feb 26, 2026
Merged

feat(cli): dynamic model/provider selection in init command#112
stepandel merged 1 commit intofeature/AGE-148-expand-model-providersfrom
feature/AGE-149-dynamic-provider-selection

Conversation

@stepandel
Copy link
Copy Markdown
Owner

Summary

Replaces the hardcoded Anthropic API key collection in clawup init with a dynamic provider selection flow powered by MODEL_PROVIDERS.

Changes

  • packages/cli/commands/init.ts — Provider picker, dynamic model list, provider-aware API key validation and storage

How it works

  1. User picks a model provider (Anthropic, OpenAI, Google, OpenRouter)
  2. Available models shown from MODEL_PROVIDERS[provider].models
  3. For providers with empty model lists (OpenRouter), free-form model input
  4. API key collected with provider-specific prefix validation and setup instructions
  5. Config stores modelProvider, defaultModel, and the provider's env var as a secret

Backward compatibility

Selecting Anthropic produces equivalent behavior to the previous hardcoded flow.

Depends on: #111 (AGE-148)
Closes AGE-149

- Replace hardcoded Anthropic API key collection with provider picker
- Show models from MODEL_PROVIDERS for selected provider
- Free-form model input for providers with empty model lists (OpenRouter)
- Provider-specific API key validation and instructions
- Store modelProvider, defaultModel, and provider env var in Pulumi config
- Backward compatible: selecting Anthropic produces equivalent behavior

Closes AGE-149
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 23, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/AGE-149-dynamic-provider-selection

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@stepandel
Copy link
Copy Markdown
Owner Author

🔍 QA Review — APPROVED

All acceptance criteria for AGE-149 verified against the diff:

  • ✅ Init wizard shows provider selection (Anthropic, OpenAI, Google, OpenRouter) via MODEL_PROVIDERS
  • ✅ Model list dynamically populated from MODEL_PROVIDERS[provider].models
  • ✅ Free-form model input for providers with empty models array (OpenRouter)
  • ✅ API key validation uses correct prefix per provider (keyPrefix)
  • ✅ API key instructions come from KEY_INSTRUCTIONS via providerKeyMap
  • ✅ Selected provider and model stored in Pulumi config (modelProvider, defaultModel)
  • ✅ API key stored with provider-specific env var name
  • ✅ Selecting Anthropic produces identical output to previous behavior
  • ✅ Build passes clean (tsc --noEmit on both core and cli)
  • ✅ All 26 tests pass

Ship it 🚢

@stepandel
Copy link
Copy Markdown
Owner Author

🔍 QA — APPROVED by Scout

Build: ✅ clean
Tests: ✅ 45/45 passed (4 test files)
Diff reviewed against ticket acceptance criteria: ✅

Ship it 🚢

Copy link
Copy Markdown
Owner Author

@stepandel stepandel left a comment

Choose a reason for hiding this comment

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

🔍 QA Review — APPROVED

All acceptance criteria verified against the diff. Build passes clean. All 45 tests pass (including new constants.test.ts).

Ship it 🚢

@stepandel
Copy link
Copy Markdown
Owner Author

🔍 QA Review — Approved

AGE-149: Dynamic provider/model selection. Provider picker, key validation, Anthropic compat. Build+tests pass.

Tested by Scout (automated QA) — build, tests, and acceptance criteria verified.

@stepandel stepandel merged commit f6fb5a3 into feature/AGE-148-expand-model-providers Feb 26, 2026
2 checks passed
@stepandel stepandel deleted the feature/AGE-149-dynamic-provider-selection branch February 26, 2026 21:17
stepandel added a commit that referenced this pull request Feb 27, 2026
…Codex agent (#111)

* feat(core): expand MODEL_PROVIDERS with OpenAI, Google, and OpenRouter

- Add openai, google, openrouter entries to MODEL_PROVIDERS
- Add KEY_INSTRUCTIONS for each provider's API key
- Add getProviderForModel() helper to extract provider from model strings
- Add comprehensive unit tests for new constants and helper

Closes AGE-148

* feat: centralize plugin metadata into enriched registry manifests

Replace 15+ hardcoded plugin-specific locations across 8+ files with a
centralized plugin manifest system. All plugin knowledge (secrets, config
paths, internal keys, transforms, webhook setup, validators, instructions)
now lives in enriched registry entries that consumers read from instead of
hardcoding plugin-specific logic.

Key changes:
- Add PluginManifest Zod schema with full metadata (secrets with scope/
  isSecret/autoResolvable/validator, configPath, internalKeys,
  configTransforms, webhookSetup)
- Create plugin-loader with resolvePlugin() resolution chain and generic
  fallback for unknown plugins
- Generalize config-generator to use configPath-driven code paths instead
  of name-based branching
- Make buildManifestSecrets, validators, KNOWN_SECRETS, webhook setup,
  auto-resolve, and post-deploy messages all data-driven from the registry

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add identity-bundled plugin manifests with 3-tier resolution

Plugin resolution now checks: built-in registry > identity-bundled
manifests (from plugins/ dir in identity repos) > generic fallback.
This lets third-party identity repos ship their own plugin metadata
without requiring changes to the built-in registry.

- Add standalone example manifests for Linear and Slack plugins
- Add pluginManifests field to IdentityResult
- Scan plugins/ directory in identity repos for YAML plugin manifests
- Thread IdentityResult through all resolvePlugin() call sites

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(pulumi): provider-aware config generation (#113)

- config-generator uses MODEL_PROVIDERS to set correct env var per provider
- Anthropic OAuth detection preserved only for Anthropic provider
- cloud-init exports correct env var name for non-Anthropic providers
- CloudInitConfig gains optional modelProvider field

Closes AGE-150

Co-authored-by: Titus <titus@openclaw.ai>

* feat(core): add Codex to coding agent registry (#114)

- Add 'codex' entry to CODING_AGENT_REGISTRY with install script, model config, and cliBackend
- Install via npm install -g @openai/codex
- Config stored in ~/.codex/config.toml
- Uses exec --full-auto for one-shot execution

Closes AGE-151

Co-authored-by: Titus <titus@openclaw.ai>

* feat(cli): dynamic model/provider selection in init command (#112)

- Replace hardcoded Anthropic API key collection with provider picker
- Show models from MODEL_PROVIDERS for selected provider
- Free-form model input for providers with empty model lists (OpenRouter)
- Provider-specific API key validation and instructions
- Store modelProvider, defaultModel, and provider env var in Pulumi config
- Backward compatible: selecting Anthropic produces equivalent behavior

Closes AGE-149

Co-authored-by: Titus <titus@openclaw.ai>

* fix: address CodeRabbit review comments and add plugin E2E tests

Phase 1 — Review fixes:
- Use plugin.enabled for channel-level enabled flag in config-generator
- Respect transform.removeSource before skipping source key
- Log warnings instead of swallowing identity/plugin resolution errors
- Namespace buildKnownSecrets/buildValidators keys by envVar-derived
  camelCase to prevent collisions when plugins share raw key names
- Fix configJsonPath from "plugins.entries.linear" to
  "plugins.entries.openclaw-linear" in both registry and YAML example
- Add superRefine validation that webhookSetup.secretKey exists in secrets
- Use unique per-plugin webhook output keys (${role}${PluginSlug}WebhookUrl)
- Build validators from full resolved manifests, not just static registry
- Fix jqPath construction (was a no-op replace, now strips leading dot)
- Use secret.envVar for Pulumi key derivation instead of plugin.displayName
- Add AbortController timeout, HTTP status check, and GraphQL error
  handling to Linear API call in setup
- Log warnings for invalid identity-bundled plugin manifests
- Resolve isSecret by agent's plugin context instead of scanning all
  manifests by raw key name
- Scope auto-resolvable checks in env.ts to agent's actual plugins

Phase 2 — E2E tests (57 new tests):
- plugin-loader.test.ts: resolution, secrets, known secrets, validators
- plugin-manifest.test.ts: schema validation, webhook secretKey validation
- plugin-e2e.test.ts: end-to-end integration for Linear, Slack, multi-plugin

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add plugin deployment E2E tests for Slack and Linear

Add E2E tests that verify Slack and Linear plugin configuration through
the full deploy → validate → destroy lifecycle using local Docker
containers. Includes a new plugin-identity fixture with both plugins
enabled, and extends createTestProject with options for custom identity
directories, agent names, roles, and extra env lines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve autoResolvable lookup mismatch in env.ts

The auto-resolvable check was using envVar-derived camelCase keys (e.g.,
linearApiKey) to index pm.secrets, but manifest secrets use raw key names
(e.g., apiKey). Fixed to match via envVar instead of raw key lookup.

* feat: add hooks field to PluginManifestSchema (AGE-204)

- Add PluginHooksSchema with resolve, postProvision, preStart hooks
- Add validation: resolve hook keys must match autoResolvable secrets
- Add resolve hook for linearUserUuid in openclaw-linear manifest
- Add comprehensive tests for hooks validation
- Export PluginHooksSchema from barrel

* feat: implement manifest hook execution engine (AGE-205)

- Add runResolveHook(): executes shell script, captures stdout as resolved value
- Add runLifecycleHook(): executes postProvision/preStart scripts with streaming output
- Add resolvePluginSecrets(): orchestrates all resolve hooks for a manifest
- Timeout enforcement via AbortSignal, error handling for non-zero exits
- 12 tests covering happy paths, timeouts, errors, env inheritance
- Export all functions and types from @clawup/core

* feat: integrate manifest hooks into provisioning pipeline (AGE-206)

- Wire postProvision/preStart hooks into cloud-init at correct lifecycle points
- Replace hardcoded Linear UUID auto-resolve with generic resolvePluginSecrets()
- Add --skip-hooks CLI flag to bypass hook execution during setup
- Pass hooks through Pulumi plugin config to cloud-init generation
- Move manifest-hooks to @clawup/core/manifest-hooks subpath (avoids child_process in browser)
- Full build passing (core, cli, pulumi, web), all 196 tests green

* feat: rework multi-provider model selection for current architecture (AGE-207)

- Merge feat/plugin-abstraction + feat/manifest-hooks-schema into PR #111 branch
- Resolve init.ts conflicts (keep manifest-only init, no model selection UI)
- Add modelProvider/defaultModel to ClawupManifestSchema
- Wire provider-aware API key handling in setup.ts (replaces hardcoded Anthropic)
- Wire modelProvider through Pulumi shared component to cloud-init
- Fix Codex coding agent missing secrets field
- Add OpenAI/Google/OpenRouter validation hints in setup.ts
- All 209 tests passing, full build green

* fix: resolve workspace mock, env, and Pulumi backend issues in E2E tests

- Add workspace module mocks to all E2E tests (isDevMode, getWorkspaceDir, ensureWorkspace)
- Add PLUGINTESTER_LINEAR_USER_UUID to bypass API auto-resolve
- Fix error-cases expectations (TestCancelError → ProcessExitError for deploy/destroy cancellation)
- Add PULUMI_BACKEND_URL=file://~ to ensure local backend is used

* fix: use project mode for E2E tests and handle manifest path in workspace

- Change workspace mocks to use project mode (tempDir/.clawup) instead of dev mode
- Update Pulumi program to check parent dir for clawup.yaml (project mode support)
- Add workspace setup in E2E test beforeAll to copy Pulumi files
- Rebuild Pulumi dist with updated manifest resolution

* fix: proper workspace setup with matching path structure for E2E tests

- Fix repoRoot path resolution (../../.. from __e2e__ dir)
- Create packages/pulumi/dist structure in workspace to match Pulumi.yaml main path
- Symlink node_modules to avoid ESM resolution issues
- Apply to lifecycle, plugins, and redeploy (both describe blocks)

* fix: search upward for clawup.yaml in Pulumi program

Pulumi sets CWD to the program directory (packages/pulumi/dist/),
not the Pulumi.yaml directory. Walk up to find clawup.yaml.
Also add package-directory dependency for Pulumi ESM support.

* fix: configurable local base port for E2E tests (avoid gateway port conflict)

Add CLAWUP_LOCAL_BASE_PORT env var to override the default 18789 base port
for local Docker deployments. E2E tests use 28789 to avoid conflicts with
the running OpenClaw gateway.

* fix: add retry for openclaw.json read in plugin E2E test

Container cloud-init takes a moment to write openclaw.json.
Retry up to 10 times with 1s delay.

* fix: verify plugin config via container env vars instead of openclaw.json

The container's cloud-init doesn't create openclaw.json in E2E test
environments. Verify plugin secrets are passed as env vars instead.

* fix: verify plugin secrets in cloud-init script instead of env vars

Secrets are embedded in base64-encoded CLOUDINIT_SCRIPT, not as
direct Docker env vars.

* ci: add GitHub Actions CI workflow for unit + E2E tests

- Unit tests run on push/PR to main
- E2E tests run after unit tests pass (needs Docker, Pulumi)
- Lint/typecheck job runs in parallel
- Concurrency: cancel in-progress runs on same ref

* fix: address CodeRabbit review comments (round 3)

- CI: use explicit backend path instead of file://~ (no tilde expansion in GHA)
- CI: seed AGENT_LINEAR_USER_UUID for deterministic E2E
- CI: pulumi login uses PULUMI_BACKEND_URL for consistency
- E2E: clean up all env vars in afterAll (prevent state leakage)
- E2E: fix misleading 'dev mode' comments (tests use project mode)

* fix: stabilize cancellation tests for CI compatibility

- Accept both TestCancelError and ProcessExitError in cancel tests
  (deploy tool may propagate either depending on environment)
- Create Pulumi backend directory before login in CI
- Fix: mkdir .pulumi-state before pulumi login

* fix: address CodeRabbit review comments (round 4)

- CI: add explicit permissions (contents: read) for least privilege
- CI: pin Pulumi CLI to v3.223.0 for deterministic builds
- E2E: isolate Pulumi backend per suite (file://<tempDir>/.pulumi-backend)
- E2E: save/restore env vars instead of delete (prevents state leakage)
- E2E: add tempDir guard to getWorkspaceDir mocks in all test files

* feat: add lifecycle hooks to plugin manifests (AGE-203) (#151)

* feat: add hooks field to PluginManifestSchema (AGE-204)

- Add PluginHooksSchema with resolve, postProvision, preStart hooks
- Add validation: resolve hook keys must match autoResolvable secrets
- Add resolve hook for linearUserUuid in openclaw-linear manifest
- Add comprehensive tests for hooks validation
- Export PluginHooksSchema from barrel

* feat: implement manifest hook execution engine (AGE-205)

- Add runResolveHook(): executes shell script, captures stdout as resolved value
- Add runLifecycleHook(): executes postProvision/preStart scripts with streaming output
- Add resolvePluginSecrets(): orchestrates all resolve hooks for a manifest
- Timeout enforcement via AbortSignal, error handling for non-zero exits
- 12 tests covering happy paths, timeouts, errors, env inheritance
- Export all functions and types from @clawup/core

* feat: integrate manifest hooks into provisioning pipeline (AGE-206)

- Wire postProvision/preStart hooks into cloud-init at correct lifecycle points
- Replace hardcoded Linear UUID auto-resolve with generic resolvePluginSecrets()
- Add --skip-hooks CLI flag to bypass hook execution during setup
- Pass hooks through Pulumi plugin config to cloud-init generation
- Move manifest-hooks to @clawup/core/manifest-hooks subpath (avoids child_process in browser)
- Full build passing (core, cli, pulumi, web), all 196 tests green

* merge: main into feat/manifest-hooks-schema

- Merge main (includes PR #148 plugin abstraction)
- Fix plugin E2E test: skip hooks during setup (fake API keys can't resolve)

* feat: test hooks E2E with echo-based stub manifests

- Created test-linear and test-slack plugin manifests in fixture identity
  with echo-based resolve hooks (no real API calls)
- test-linear hook: resolves linearUserUuid via echo instead of curl to Linear API
- Plugin E2E tests now exercise full hook resolution pipeline
- Removed skipHooks workaround — hooks run end-to-end
- Verify hook-resolved UUID appears in deployed container's cloud-init script
- All 24 E2E tests passing, 196 unit tests passing

---------

Co-authored-by: Titus <stepan.arsentjev+titus@gmail.com>
Co-authored-by: Scout <scout@openclaw.ai>

* fix: use printf instead of echo -n in resolve hook test

On macOS, /bin/sh in POSIX mode doesn't support echo -n and
literally outputs "-n", causing the empty-output test to pass
when it should fail.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: bump CLI to v2.2.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: fail fast on unknown model provider instead of silent fallback

- cloud-init.ts: throw on unknown modelProvider instead of degrading to MODEL_API_KEY
- config-generator.ts: validate provider key with explicit error, remove unsafe assertion

* test: add comprehensive test coverage for multi-provider changes

Unit tests (23 new, 232 total):
- cloud-init-providers.test.ts: 7 tests — verifies env var export for
  Anthropic (default + explicit), OpenAI, Google, OpenRouter, unknown
  provider error, and undefined modelProvider fallback
- config-generator-providers.test.ts: 8 tests — verifies Python config
  patching for all providers, unknown provider error, model string in
  config, backup model, and Codex coding agent CLI backend
- coding-agent-registry.test.ts: 8 tests — verifies Codex entry
  (secrets, install script, configureModelScript, cliBackend)

E2E enhancements:
- lifecycle deploy test now verifies CLOUDINIT_SCRIPT contains Anthropic
  auto-detect logic (default provider)
- test-project helper supports model/modelProvider overrides

---------

Co-authored-by: Titus <titus@openclaw.ai>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Scout <scout@openclaw.ai>
Co-authored-by: Titus <stepan.arsentjev+titus@gmail.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant