Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions extensions/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,62 @@ cn ls --json
- `cn logout`: Sign out of current session
- `cn remote`: Launch a remote instance
- `cn serve`: Start HTTP server mode
- `cn config`: Manage configuration file (see below)

### Configuration Management (`cn config`)

Manage your `config.yaml` file programmatically. Works with any OpenAI-compatible API.

```bash
# Verify configured models exist on provider
cn config verify --provider openai

# Sync config - remove models that don't exist
cn config sync --provider openai --dry-run
cn config sync --provider openai

# List available models
cn config list --provider openai --chat-only

# Add/remove models
cn config add gpt-4o --provider openai --role chat
cn config remove gpt-3.5-turbo

# Generate fresh config from available models
cn config generate --provider openai --chat-only

# Compare config to available models
cn config diff --provider openai

# Backup management
cn config backups
cn config restore config.backup-2026-01-27.yaml

# JSON output for scripting
cn config list --provider openai --json
```

**Subcommands:**

| Command | Description |
| ------------------ | ------------------------------------------------- |
| `verify` | Check if configured models exist on provider |
| `sync` | Remove unavailable models (`--dry-run` supported) |
| `validate` | Validate config file structure |
| `sections` | Show config sections |
| `list` | List available models from provider |
| `test` | Test each chat model with sample prompt |
| `add <model>` | Add model with `--name`, `--role` options |
| `remove <model>` | Remove model from config |
| `generate` | Bootstrap config from available models |
| `show` | Display current config |
| `diff` | Compare config vs available models |
| `backups` | List backup files |
| `restore <backup>` | Restore from backup |

**Provider Presets:** `--provider` accepts: `openai`, `anthropic`, `azure`, `ollama`, `together`, `groq`, `mistral`

**Filter Options:** `--chat-only`, `--embed-only`, `--rerank-only`, `--filter <pattern>`

### Session Listing (`cn ls`)

Expand Down
64 changes: 64 additions & 0 deletions extensions/cli/src/CLIPlatformClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,70 @@ describe("CLIPlatformClient", () => {
});
});

it("prioritizes .env file over process.env when both have the secret", async () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can remove this test, see other comment

// This tests the correct priority order: .env files should be checked BEFORE process.env
// This matches the IDE's LocalPlatformClient behavior
const fqsn: FQSN = {
packageSlugs: [{ ownerSlug: "", packageSlug: "" }],
secretName: "API_KEY",
};

// Secret is in BOTH process.env AND .env file with DIFFERENT values
vi.stubEnv("API_KEY", "process-env-value");

// Mock .env file to return a different value
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue("API_KEY=dotenv-file-value\n");

const client = new CLIPlatformClient(null, mockApiClient as any);
const results = await client.resolveFQSNs([fqsn]);

// Should not call API since local env has the secret
expect(mockApiClient.syncSecrets).not.toHaveBeenCalled();

expect(results).toHaveLength(1);
expect(results[0]).toMatchObject({
found: true,
// The .env file value should be used, NOT the process.env value
value: "dotenv-file-value",
secretLocation: {
secretType: SecretType.LocalEnv,
secretName: "API_KEY",
},
});
});

it("falls back to process.env when .env file does not have the secret", async () => {
// .env file exists but doesn't have this specific secret
const fqsn: FQSN = {
packageSlugs: [{ ownerSlug: "", packageSlug: "" }],
secretName: "ONLY_IN_PROCESS_ENV",
};

// Secret is only in process.env
vi.stubEnv("ONLY_IN_PROCESS_ENV", "from-process-env");

// Mock .env file to exist but not contain this secret
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue("OTHER_KEY=other-value\n");

const client = new CLIPlatformClient(null, mockApiClient as any);
const results = await client.resolveFQSNs([fqsn]);

// Should not call API since process.env has the secret
expect(mockApiClient.syncSecrets).not.toHaveBeenCalled();

expect(results).toHaveLength(1);
expect(results[0]).toMatchObject({
found: true,
value: "from-process-env",
secretLocation: {
secretType: SecretType.LocalEnv,
secretName: "ONLY_IN_PROCESS_ENV",
},
});
});

it("falls back to API for file-based FQSN when not in local env", async () => {
// File-based FQSN with empty package slugs
const fqsn: FQSN = {
Expand Down
15 changes: 8 additions & 7 deletions extensions/cli/src/CLIPlatformClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,8 @@ export class CLIPlatformClient implements PlatformClient {
}

private findSecretInLocalEnvFiles(fqsn: FQSN): SecretResult | undefined {
// First check process.env (highest priority)
const processEnvSecret = this.findSecretInProcessEnv(fqsn);
if (processEnvSecret) {
return processEnvSecret;
}

// Then check in priority order: ~/.continue/.env, <workspace>/.continue/.env, <workspace>/.env
// Check .env files FIRST (highest priority), matching IDE behavior
Copy link
Copy Markdown
Contributor

@RomneyDa RomneyDa Feb 3, 2026

Choose a reason for hiding this comment

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

process.env should stay top priority here, IDEs don't have access to process.env the same way CLI does; it's a CLI-only thing unless people are building their own vscode etc

// Priority order: ~/.continue/.env, <workspace>/.continue/.env, <workspace>/.env
const workspaceDir = process.cwd();
const envPaths = [
path.join(env.continueHome, ".env"),
Expand All @@ -84,6 +79,12 @@ export class CLIPlatformClient implements PlatformClient {
}
}

// Fall back to process.env if not found in .env files
const processEnvSecret = this.findSecretInProcessEnv(fqsn);
if (processEnvSecret) {
return processEnvSecret;
}

return undefined;
}

Expand Down
Loading
Loading