Skip to content
Merged
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
32 changes: 31 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,37 @@
"!**/node_modules/**"
],
"preLaunchTask": "cli:build-and-watch-logs",
"cwd": "${workspaceFolder}/extensions/cli"
"cwd": "${workspaceFolder}/manual-testing-sandbox"
},
{
"name": "Debug CLI - AWS Bedrock",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/extensions/cli/dist/cn.js",
"args": [],
"runtimeArgs": ["--enable-source-maps"],
"env": {
"NODE_ENV": "development",
"CONTINUE_USE_BEDROCK": "1"
// "CONTINUE_API_BASE": "http://localhost:3001/",
// "WORKOS_CLIENT_ID": "client_01J0FW6XCPMJMQ3CG51RB4HBZQ",
// "HUB_URL": "http://localhost:3000"
},
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"skipFiles": ["<node_internals>/**"],
"sourceMaps": true,
"outFiles": ["${workspaceFolder}/extensions/cli/dist/**/*.js"],
"sourceMapPathOverrides": {
"../src/*": "${workspaceFolder}/extensions/cli/src/*",
"../../core/*": "${workspaceFolder}/core/*"
},
"resolveSourceMapLocations": [
"${workspaceFolder}/**",
"!**/node_modules/**"
],
"preLaunchTask": "cli:build-and-watch-logs",
"cwd": "${workspaceFolder}/manual-testing-sandbox"
},
{
"name": "Debug CLI Tests",
Expand Down
24 changes: 22 additions & 2 deletions extensions/cli/spec/onboarding.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ When a user first runs `cn` in interactive mode, they will be taken through "onb
**The onboarding flow runs when the user hasn't completed onboarding before, regardless of whether they have a valid config.yaml file.**

1. If the --config flag is provided, load this config
2. Otherwise, we will present the user with two options:
2. If the CONTINUE_USE_BEDROCK environment variable is set to "1", automatically use AWS Bedrock configuration and skip interactive prompts
3. Present the user with available options:

- Log in with Continue: log them in, which will automatically create their assistant and then we can load the first assistant from the first org
- Enter your Anthropic API key: let them enter the key, and then either create a ~/.continue/config.yaml with the following contents OR update the existing config.yaml to add the model
Expand All @@ -23,7 +24,26 @@ When a user first runs `cn` in interactive mode, they will be taken through "onb
ANTHROPIC_API_KEY: <THEIR_ANTHROPIC_API_KEY>
```

When something in the onboarding flow is done automatically, we should tell the user what happened.
When CONTINUE_USE_BEDROCK=1 is detected, it will use AWS Bedrock configuration. The user must have AWS credentials configured through the standard AWS credential chain (AWS CLI, environment variables, IAM roles, etc.).

When something in the onboarding flow is done automatically, we should tell the user what happened. For example, when CONTINUE_USE_BEDROCK=1 is detected, the CLI displays: "✓ Using AWS Bedrock (CONTINUE_USE_BEDROCK detected)"

### AWS Bedrock Environment Variable

Users can bypass the interactive onboarding menu by setting the `CONTINUE_USE_BEDROCK` environment variable to "1":

```bash
export CONTINUE_USE_BEDROCK=1
cn <command>
```

This will:

- Skip the interactive onboarding prompts
- Automatically configure the CLI to use AWS Bedrock
- Require that AWS credentials are already configured through the standard AWS credential chain
- Display a confirmation message to the user
- Mark onboarding as completed

## Normal flow

Expand Down
92 changes: 91 additions & 1 deletion extensions/cli/src/onboarding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as fs from "fs";
import * as os from "os";
import * as path from "path";

import { describe, expect, test, beforeEach, afterEach } from "vitest";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";

import type { AuthConfig } from "./auth/workos.js";
import { runNormalFlow } from "./onboarding.js";
Expand Down Expand Up @@ -160,3 +160,93 @@ name: "Incomplete Config"
}
});
});

// Separate describe block with its own mocking for BEDROCK tests
describe("CONTINUE_USE_BEDROCK environment variable", () => {
const mockConsoleLog = vi.fn();
let mockAuthConfig: AuthConfig;
const originalEnv = process.env.CONTINUE_USE_BEDROCK;

// Mock initialize for these tests only
const mockInitialize = vi.fn().mockResolvedValue({
config: { name: "test-config", models: [], rules: [] },
llmApi: {},
model: { name: "test-model" },
mcpService: {},
apiClient: {},
});

beforeEach(() => {
mockConsoleLog.mockClear();
mockInitialize.mockClear();

// Spy on console.log for these tests
vi.spyOn(console, "log").mockImplementation(mockConsoleLog);

// Mock the config module
vi.doMock("./config.js", () => ({ initialize: mockInitialize }));

mockAuthConfig = {
userId: "test-user",
userEmail: "[email protected]",
accessToken: "test-token",
refreshToken: "test-refresh",
expiresAt: Date.now() + 3600000,
organizationId: "test-org",
};
});

afterEach(() => {
if (originalEnv) {
process.env.CONTINUE_USE_BEDROCK = originalEnv;
} else {
delete process.env.CONTINUE_USE_BEDROCK;
}
vi.restoreAllMocks();
vi.doUnmock("./config.js");
});

test("should bypass interactive options when CONTINUE_USE_BEDROCK=1", async () => {
process.env.CONTINUE_USE_BEDROCK = "1";

// Re-import to get the mocked version
vi.resetModules();
const { runOnboardingFlow } = await import("./onboarding.js");

const result = await runOnboardingFlow(undefined, mockAuthConfig);

expect(result.wasOnboarded).toBe(true);
expect(mockConsoleLog).toHaveBeenCalledWith(
expect.stringContaining(
"✓ Using AWS Bedrock (CONTINUE_USE_BEDROCK detected)",
),
);
});

test("should not bypass when CONTINUE_USE_BEDROCK is not '1'", async () => {
process.env.CONTINUE_USE_BEDROCK = "0";

// Re-import to get the mocked version
vi.resetModules();
const { runOnboardingFlow } = await import("./onboarding.js");

// Mock non-interactive environment to avoid hanging
const originalIsTTY = process.stdin.isTTY;
process.stdin.isTTY = false;

try {
await runOnboardingFlow(undefined, mockAuthConfig);

// Verify the Bedrock message was NOT called by checking all calls
const allCalls = mockConsoleLog.mock.calls.flat();
const hasBedrockMessage = allCalls.some((call) =>
String(call).includes(
"✓ Using AWS Bedrock (CONTINUE_USE_BEDROCK detected)",
),
);
expect(hasBedrockMessage).toBe(false);
} finally {
process.stdin.isTTY = originalIsTTY;
}
});
});
17 changes: 13 additions & 4 deletions extensions/cli/src/onboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export async function createOrUpdateConfig(apiKey: string): Promise<void> {
fs.writeFileSync(CONFIG_PATH, updatedContent);
}

async function runOnboardingFlow(
export async function runOnboardingFlow(
configPath: string | undefined,
authConfig: AuthConfig,
): Promise<OnboardingResult> {
Expand All @@ -69,7 +69,16 @@ async function runOnboardingFlow(
return { ...result, wasOnboarded: false };
}

// Step 2: Check if we're in a test/CI environment - if so, skip interactive prompts
// Step 2: Check for CONTINUE_USE_BEDROCK environment variable first (before test env check)
if (process.env.CONTINUE_USE_BEDROCK === "1") {
console.log(
chalk.blue("✓ Using AWS Bedrock (CONTINUE_USE_BEDROCK detected)"),
);
const result = await initialize(authConfig, CONFIG_PATH);
return { ...result, wasOnboarded: true };
}

// Step 3: Check if we're in a test/CI environment - if so, skip interactive prompts
const isTestEnv =
process.env.NODE_ENV === "test" ||
process.env.CI === "true" ||
Expand All @@ -92,7 +101,7 @@ async function runOnboardingFlow(
return { ...result, wasOnboarded: false };
}

// Step 3: Present user with two options
// Step 4: Present user with two options
console.log(chalk.yellow("How do you want to get started?"));
console.log(chalk.white("1. ⏩ Log in with Continue"));
console.log(chalk.white("2. 🔑 Enter your Anthropic API key"));
Expand Down Expand Up @@ -129,7 +138,7 @@ async function runOnboardingFlow(
const result = await initialize(authConfig, CONFIG_PATH);
return { ...result, wasOnboarded: true };
} else {
throw new Error("Invalid choice. Please select 1 or 2.");
throw new Error(`Invalid choice. Please select "1" or "2"`);
}
}

Expand Down
Loading