Swadhin Biswas feat: implement mervelas cli engine, built-in tools, and reactive UI · 4/3/2026
0358986
157 lines · 4.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import chalk from 'chalk'
import * as React from 'react'
import { useMemo } from 'react'
import type { LocalJSXCommandContext } from '../../commands.js'
import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js'
import { Select } from '../../components/CustomSelect/select.js'
import { Dialog } from '../../components/design-system/Dialog.js'
import { Box, Text } from '../../ink.js'
import type { LocalJSXCommandOnDone } from '../../types/command.js'
import { getConfiguredAuthProvider } from '../../utils/auth.js'
import {
  AUTH_PROVIDER_CHOICES,
  type AuthProviderChoice,
  getAuthProviderDescription,
  getProviderSetupHint,
  getProviderStatusText,
  hasConfiguredCredentialsForProvider,
  isAuthProviderChoice,
  renderAuthProviderName,
  setPreferredAuthProvider,
} from '../../utils/authProviders.js'
import { saveGlobalConfig } from '../../utils/config.js'

const HELP_ARGS = ['help', '-h', '--help']

function applyProviderPreference(
  provider: AuthProviderChoice,
  context: LocalJSXCommandContext,
  onDone: LocalJSXCommandOnDone,
): void {
  saveGlobalConfig(current => setPreferredAuthProvider(current, provider))
  context.onChangeAPIKey()
  context.setAppState(prev => ({
    ...prev,
    authVersion: prev.authVersion + 1,
  }))

  let message = `Set provider to ${chalk.bold(renderAuthProviderName(provider))}`
  if (!hasConfiguredCredentialsForProvider(provider)) {
    message += `\n${getProviderSetupHint(provider)}`
  }

  // clear the mainLoopModel from AppState so it falls back to the default of the newly selected provider
  context.setAppState(prev => ({
    ...prev,
    mainLoopModel: null,
    mainLoopModelForSession: null,
  }))

  onDone(message)
}

function ProviderPicker({
  context,
  onDone,
}: {
  context: LocalJSXCommandContext
  onDone: LocalJSXCommandOnDone
}): React.ReactNode {
  const configuredProvider = getConfiguredAuthProvider()
  const providerOptions = useMemo(
    () =>
      AUTH_PROVIDER_CHOICES.map(provider => ({
        label: (
          <Text>
            {renderAuthProviderName(provider)}{' '}
            <Text dimColor>{getAuthProviderDescription(provider)}</Text>
            {'\n'}
          </Text>
        ),
        value: provider,
      })),
    [],
  )

  return (
    <Dialog
      title="Provider"
      onCancel={() =>
        onDone(
          `Kept provider as ${chalk.bold(renderAuthProviderName(configuredProvider))}`,
          { display: 'system' },
        )
      }
      color="permission"
      inputGuide={exitState =>
        exitState.pending ? (
          <Text>Press {exitState.keyName} again to exit</Text>
        ) : (
          <ConfigurableShortcutHint
            action="confirm:no"
            context="Confirmation"
            fallback="Esc"
            description="cancel"
          />
        )
      }
    >
      <Box flexDirection="column" gap={1}>
        <Text bold>{getProviderStatusText()}</Text>
        <Text dimColor>
          Provider selection is separate from credential setup. Pick a provider
          first, then connect it with /login if needed.
        </Text>
        <Text>Select provider:</Text>
        <Box>
          <Select
            options={providerOptions}
            defaultFocusValue={configuredProvider}
            onChange={value =>
              applyProviderPreference(
                value as AuthProviderChoice,
                context,
                onDone,
              )
            }
          />
        </Box>
      </Box>
    </Dialog>
  )
}

export async function call(
  onDone: LocalJSXCommandOnDone,
  context: LocalJSXCommandContext,
  args?: string,
): Promise<React.ReactNode | undefined> {
  const normalized = args?.trim().toLowerCase() ?? ''

  if (HELP_ARGS.includes(normalized)) {
    onDone(
      `Usage: /provider [anthropic|openai|openrouter|opencode|nvidia]\n\nRun /provider with no argument to open an interactive picker.\nRun /config provider to jump straight into the same picker.`,
    )
    return
  }

  if (!normalized) {
    return <ProviderPicker context={context} onDone={onDone} />
  }

  if (normalized === 'current' || normalized === 'status') {
    onDone(getProviderStatusText())
    return
  }

  if (!isAuthProviderChoice(normalized)) {
    onDone(
      `Invalid provider: ${args}. Valid options are: ${AUTH_PROVIDER_CHOICES.join(', ')}`,
      { display: 'system' },
    )
    return
  }

  applyProviderPreference(normalized, context, onDone)
}