Skip to content

Commit 1d73e9d

Browse files
feat(setup): add incremental Chat setup
1 parent 445ef62 commit 1d73e9d

10 files changed

Lines changed: 116 additions & 6 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ npx sim-setup add sandbox
8888
npx sim-setup add jobs
8989
npx sim-setup add cache
9090
npx sim-setup add knowledge
91+
npx sim-setup add chat
9192
npx sim-setup add llm
9293
npx sim-setup add integration slack
9394
```

bun.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/sim-setup/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,9 @@ npx sim-setup
99
Outside a Sim source checkout, the command creates a Docker Compose installation using published
1010
images. Inside a Sim source checkout, use `bun run sim-setup` to expose the complete development
1111
and deployment wizard.
12+
13+
To connect or replace the Chat API key without rerunning the full wizard:
14+
15+
```bash
16+
npx sim-setup add chat
17+
```

packages/sim-setup/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sim-setup",
3-
"version": "1.0.1",
3+
"version": "1.0.2",
44
"description": "Set up and manage a self-hosted Sim installation",
55
"type": "module",
66
"bin": {

packages/sim-setup/src/capability-config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -847,13 +847,14 @@ if (missingCapabilitySetups.length > 0) {
847847
}
848848

849849
export type CapabilitySetupId = (typeof CAPABILITY_SETUPS)[number]['definition']['id']
850-
export type SetupFeatureId = CapabilitySetupId | 'llm' | 'integration'
850+
export type SetupFeatureId = CapabilitySetupId | 'chat' | 'llm' | 'integration'
851851

852852
export const SETUP_FEATURES: readonly { id: SetupFeatureId; label: string }[] = [
853853
...CAPABILITY_SETUPS.map((setup) => ({
854854
id: setup.definition.id,
855855
label: setup.label,
856856
})),
857+
{ id: 'chat', label: 'Chat' },
857858
{ id: 'llm', label: 'LLM API keys' },
858859
{ id: 'integration', label: 'OAuth integration' },
859860
]

packages/sim-setup/src/capability-status.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
} from '@sim/deployment-config/env-capabilities'
2828
import { SETUP_FEATURES, type SetupFeatureId } from './capability-config'
2929

30-
export type SetupStatusFeatureId = Exclude<SetupFeatureId, 'integration'>
30+
export type SetupStatusFeatureId = Exclude<SetupFeatureId, 'chat' | 'integration'>
3131
export type CapabilityStatusState = 'default' | 'configured' | 'missing' | 'partial' | 'invalid'
3232

3333
export interface CapabilityStatusIssue {
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest'
2+
3+
const {
4+
mockDiscoverConfigurationSources,
5+
mockReconcileEnvValues,
6+
mockPromptCopilotKey,
7+
mockMothershipOverride,
8+
mockChatFlagValues,
9+
mockOutro,
10+
} = vi.hoisted(() => ({
11+
mockDiscoverConfigurationSources: vi.fn(),
12+
mockReconcileEnvValues: vi.fn(),
13+
mockPromptCopilotKey: vi.fn(),
14+
mockMothershipOverride: vi.fn(),
15+
mockChatFlagValues: vi.fn(),
16+
mockOutro: vi.fn(),
17+
}))
18+
19+
vi.mock('./configuration-sources', () => ({
20+
discoverConfigurationSources: mockDiscoverConfigurationSources,
21+
}))
22+
23+
vi.mock('./env-files', () => ({
24+
reconcileEnvValues: mockReconcileEnvValues,
25+
}))
26+
27+
vi.mock('./prompter', () => ({
28+
outro: mockOutro,
29+
}))
30+
31+
vi.mock('./steps', () => ({
32+
chatFlagValues: mockChatFlagValues,
33+
mothershipOverride: mockMothershipOverride,
34+
promptCopilotKey: mockPromptCopilotKey,
35+
}))
36+
37+
vi.mock('./theme', () => ({
38+
theme: { accent: (value: string) => value },
39+
}))
40+
41+
import { runFeatureSetup } from './feature-setup'
42+
43+
describe('Chat feature setup', () => {
44+
beforeEach(() => {
45+
vi.clearAllMocks()
46+
mockDiscoverConfigurationSources.mockReturnValue([
47+
{
48+
kind: 'compose',
49+
label: 'Docker Compose',
50+
location: '.env',
51+
values: new Map([['COPILOT_API_KEY', 'existing-key']]),
52+
managedByCurrentCheckout: true,
53+
},
54+
])
55+
mockMothershipOverride.mockReturnValue({
56+
SIM_AGENT_API_URL: 'https://copilot.example.com',
57+
})
58+
mockChatFlagValues.mockReturnValue({ NEXT_PUBLIC_CHAT_DISABLED: 'false' })
59+
})
60+
61+
it('writes only the Chat configuration to the detected install', async () => {
62+
mockPromptCopilotKey.mockResolvedValue('new-key')
63+
64+
await runFeatureSetup('chat', [])
65+
66+
expect(mockPromptCopilotKey).toHaveBeenCalledWith('existing-key')
67+
expect(mockReconcileEnvValues).toHaveBeenCalledWith('root', [], {
68+
COPILOT_API_KEY: 'new-key',
69+
NEXT_PUBLIC_CHAT_DISABLED: 'false',
70+
SIM_AGENT_API_URL: 'https://copilot.example.com',
71+
})
72+
expect(mockOutro).toHaveBeenCalledWith(
73+
'Chat written to .env. Recreate the app container for it to take effect.'
74+
)
75+
})
76+
77+
it('fails without changing configuration when no key is received', async () => {
78+
mockPromptCopilotKey.mockResolvedValue(null)
79+
80+
await expect(runFeatureSetup('chat', [])).rejects.toThrow(
81+
'Chat setup did not receive an API key. No configuration was changed.'
82+
)
83+
expect(mockReconcileEnvValues).not.toHaveBeenCalled()
84+
})
85+
})

packages/sim-setup/src/feature-setup.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { promptCapabilitySetup } from './capability-setup'
1313
import { type ConfigurationSource, discoverConfigurationSources } from './configuration-sources'
1414
import { type EnvTarget, reconcileEnvValues } from './env-files'
1515
import * as p from './prompter'
16+
import { chatFlagValues, mothershipOverride, promptCopilotKey } from './steps'
1617
import { theme } from './theme'
1718

1819
function isSetupFeatureId(value: string): value is SetupFeatureId {
@@ -113,6 +114,19 @@ async function setupLlm(vars: Map<string, string>): Promise<LlmSetupResult> {
113114
return reconcileLlmSetup(provider, values)
114115
}
115116

117+
async function setupChat(vars: Map<string, string>): Promise<Record<string, string>> {
118+
const overrides = mothershipOverride()
119+
const copilotKey = await promptCopilotKey(vars.get('COPILOT_API_KEY'))
120+
if (!copilotKey) {
121+
throw new Error('Chat setup did not receive an API key. No configuration was changed.')
122+
}
123+
return {
124+
...overrides,
125+
COPILOT_API_KEY: copilotKey,
126+
...chatFlagValues(copilotKey),
127+
}
128+
}
129+
116130
export function setupFeatureUsage(): string {
117131
return SETUP_FEATURES.map((feature) =>
118132
feature.id === 'integration' ? 'integration <slug>' : feature.id
@@ -182,6 +196,9 @@ export async function runFeatureSetup(feature: string, args: readonly string[]):
182196
})
183197
values = result.values
184198
remove = result.remove
199+
} else if (feature === 'chat') {
200+
values = await setupChat(vars)
201+
remove = []
185202
} else if (feature === 'integration') {
186203
values = await setupIntegration(args[0], vars)
187204
remove = []

packages/sim-setup/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { theme } from './theme'
77
import { SETUP_VERSION } from './version'
88

99
const SETUP_FEATURES =
10-
'email | storage | sandbox | jobs | cache | knowledge | knowledge-embeddings | llm | integration <slug>'
10+
'email | storage | sandbox | jobs | cache | knowledge | knowledge-embeddings | chat | llm | integration <slug>'
1111

1212
const USAGE = `Usage:
1313
sim-setup [--quick] [--dir <path>] [--mode compose|dev|k8s]

packages/sim-setup/src/setup-status.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export interface SetupStatusReport {
3838
const SECRET_KEYS = new Set(['BETTER_AUTH_SECRET', 'ENCRYPTION_KEY', 'INTERNAL_API_SECRET'])
3939
const URL_KEYS = new Set(['DATABASE_URL', 'BETTER_AUTH_URL', 'NEXT_PUBLIC_APP_URL'])
4040
const FEATURE_ORDER: readonly SetupStatusFeatureId[] = SETUP_FEATURES.flatMap((feature) =>
41-
feature.id === 'integration' ? [] : [feature.id]
41+
feature.id === 'chat' || feature.id === 'integration' ? [] : [feature.id]
4242
)
4343

4444
function readString(values: EnvCapabilityValues, key: string): string | undefined {

0 commit comments

Comments
 (0)