@adecore/agents-react 0.30.0 → 0.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/agents/AgentIcon.js +4 -3
  2. package/dist/agents/accounts.d.ts +2 -0
  3. package/dist/agents/accounts.js +6 -0
  4. package/dist/agents/agent-marks.d.ts +8 -0
  5. package/dist/agents/agent-marks.js +12 -0
  6. package/dist/agents/provider-paths.js +3 -2
  7. package/dist/host.d.ts +4 -1
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.js +1 -0
  10. package/dist/locales/en/agent-providers.d.ts +40 -0
  11. package/dist/locales/en/agent-providers.js +42 -2
  12. package/dist/locales/nl/agent-providers.d.ts +40 -0
  13. package/dist/locales/nl/agent-providers.js +42 -2
  14. package/dist/providers/AccountDetail.d.ts +4 -3
  15. package/dist/providers/AccountDetail.js +6 -5
  16. package/dist/providers/AddAccountForm.d.ts +3 -2
  17. package/dist/providers/AddAccountForm.js +2 -2
  18. package/dist/providers/CliDetail.d.ts +3 -3
  19. package/dist/providers/CliDetail.js +3 -3
  20. package/dist/providers/InstallPage.d.ts +9 -0
  21. package/dist/providers/InstallPage.js +74 -0
  22. package/dist/providers/ProvidersPane.d.ts +3 -1
  23. package/dist/providers/ProvidersPane.js +25 -14
  24. package/dist/providers/index.d.ts +1 -0
  25. package/dist/providers/install-steps.d.ts +14 -0
  26. package/dist/providers/install-steps.js +19 -0
  27. package/dist/providers/parts.js +2 -1
  28. package/dist/theme.css +3 -0
  29. package/dist/usage/format.js +6 -2
  30. package/dist/usage/limit-groups.js +2 -1
  31. package/package.json +8 -8
  32. package/src/agents/AgentIcon.tsx +12 -3
  33. package/src/agents/accounts.ts +13 -0
  34. package/src/agents/agent-marks.ts +26 -0
  35. package/src/agents/provider-paths.ts +3 -2
  36. package/src/host.ts +11 -1
  37. package/src/index.ts +1 -0
  38. package/src/locales/en/agent-providers.ts +42 -2
  39. package/src/locales/nl/agent-providers.ts +42 -2
  40. package/src/providers/AccountDetail.tsx +34 -26
  41. package/src/providers/AddAccountForm.tsx +8 -3
  42. package/src/providers/CliDetail.tsx +5 -5
  43. package/src/providers/InstallPage.tsx +221 -0
  44. package/src/providers/ProvidersPane.tsx +36 -19
  45. package/src/providers/index.ts +1 -0
  46. package/src/providers/install-steps.ts +35 -0
  47. package/src/providers/parts.tsx +2 -1
  48. package/src/theme.css +3 -0
  49. package/src/usage/format.ts +6 -2
  50. package/src/usage/limit-groups.ts +2 -1
@@ -3,18 +3,19 @@ import clsx from 'clsx';
3
3
  import { Bot } from 'lucide-react';
4
4
  import { siClaude, siGithubcopilot, siGooglegemini } from 'simple-icons';
5
5
  import { Icon } from '@adecore/ui';
6
+ import { hostAgentMark } from './agent-marks.js';
6
7
  import { PROVIDER_PATHS } from './provider-paths.js';
7
8
  // simple-icons removed OpenAI's mark at its request, so Codex reuses the usage page's path.
8
- const MARKS = {
9
+ const BUILT_IN_MARKS = {
9
10
  claude: siClaude,
10
11
  codex: { title: 'Codex', path: PROVIDER_PATHS.codex },
11
12
  gemini: siGooglegemini,
12
13
  copilot: siGithubcopilot
13
14
  };
14
15
  export function AgentIcon({ kind, size = 14, className }) {
15
- const mark = MARKS[kind];
16
+ const mark = hostAgentMark(kind) ?? BUILT_IN_MARKS[kind];
16
17
  if (!mark) {
17
18
  return _jsx(Icon, { icon: Bot, size: size, className: className });
18
19
  }
19
- return (_jsx("svg", { role: "img", "aria-label": mark.title, viewBox: "0 0 24 24", width: size, height: size, fill: "currentColor", className: clsx('align-middle', className), children: _jsx("path", { d: mark.path }) }));
20
+ return (_jsx("svg", { role: "img", "aria-label": mark.title, viewBox: mark.viewBox ?? '0 0 24 24', width: size, height: size, fill: "currentColor", className: clsx('align-middle', className), children: _jsx("path", { d: mark.path }) }));
20
21
  }
@@ -10,6 +10,8 @@ export interface AccountEntry {
10
10
  }
11
11
  export declare function accountsOfKind(accounts: ProviderAccounts | null, kind: AgentKind): AccountEntry[];
12
12
  export declare function accountName(entry: Pick<AccountEntry, 'id' | 'account' | 'isDefault'>, providerName: string): string;
13
+ export type AccountLogin = 'terminal' | 'chat' | null;
14
+ export declare function accountLoginOf(accounts: ProviderAccounts | null | undefined, kind: AgentKind): AccountLogin;
13
15
  export type AccountTone = 'muted' | 'needs-you' | 'error';
14
16
  export declare const ACCOUNT_TONE_CLASSES: Record<AccountTone, string>;
15
17
  export declare function accountStatusLine(status: ProviderAccountStatus | null, canLogIn: boolean): {
@@ -26,6 +26,12 @@ export function accountsOfKind(accounts, kind) {
26
26
  export function accountName(entry, providerName) {
27
27
  return entry.account.label ?? (entry.isDefault ? providerName : entry.id);
28
28
  }
29
+ export function accountLoginOf(accounts, kind) {
30
+ if (accounts?.loginCommands?.[kind] !== undefined) {
31
+ return 'terminal';
32
+ }
33
+ return accounts?.chatLogins?.includes(kind) === true ? 'chat' : null;
34
+ }
29
35
  export const ACCOUNT_TONE_CLASSES = {
30
36
  muted: 'text-text-muted',
31
37
  'needs-you': 'text-status-needs-you',
@@ -0,0 +1,8 @@
1
+ import type { AgentKind } from '@adecore/agent-contracts';
2
+ export interface AgentMark {
3
+ title: string;
4
+ path: string;
5
+ viewBox?: string;
6
+ }
7
+ export declare function configureAgentMarks(marks: Readonly<Partial<Record<AgentKind, AgentMark>>>): void;
8
+ export declare function hostAgentMark(kind: AgentKind): AgentMark | undefined;
@@ -0,0 +1,12 @@
1
+ let hostMarks = {};
2
+ /*
3
+ * The marks of the kinds a host adds, and any it draws differently, set once before the first render. Every
4
+ * place the shared UI draws an agent's mark reads them; a kind with none keeps the built-in mark or the robot.
5
+ * A later call replaces the whole set.
6
+ */
7
+ export function configureAgentMarks(marks) {
8
+ hostMarks = { ...marks };
9
+ }
10
+ export function hostAgentMark(kind) {
11
+ return hostMarks[kind];
12
+ }
@@ -1,8 +1,9 @@
1
1
  /*
2
- * The marks of Anthropic and OpenAI. Lucide has no brand marks, so these are the icon rule's
2
+ * The marks of Anthropic and OpenAI, and a monogram for a provider with no mark of its own. Lucide has no brand marks, so these are the icon rule's
3
3
  * exception for provider marks. One path each, in `currentColor`, so a row decides how loud the logo is.
4
4
  */
5
5
  export const PROVIDER_PATHS = {
6
6
  claude: 'M4.709 15.955l4.72-2.647.079-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.377-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.048.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.607.212-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.914-1.316-.012.01z',
7
- codex: 'M21.55 10.004a5.416 5.416 0 00-.478-4.501c-1.217-2.09-3.662-3.166-6.05-2.66A5.59 5.59 0 0010.831 1C8.39.995 6.224 2.546 5.473 4.838A5.553 5.553 0 001.76 7.496a5.487 5.487 0 00.691 6.5 5.416 5.416 0 00.477 4.502c1.217 2.09 3.662 3.165 6.05 2.66A5.586 5.586 0 0013.168 23c2.443.006 4.61-1.546 5.361-3.84a5.554 5.554 0 003.715-2.66 5.488 5.488 0 00-.693-6.497v.001zm-8.381 11.558a4.199 4.199 0 01-2.675-.954c.034-.018.093-.05.132-.074l4.44-2.53a.71.71 0 00.364-.623v-6.176l1.877 1.069c.02.01.033.029.036.05v5.115c-.003 2.274-1.87 4.118-4.174 4.123zM4.192 17.78a4.059 4.059 0 01-.498-2.763c.032.019.09.055.131.078l4.44 2.53c.225.13.504.13.73 0l5.42-3.088v2.138a.068.068 0 01-.027.057L9.9 19.288c-1.999 1.136-4.552.46-5.707-1.51h-.001zM3.023 8.216A4.15 4.15 0 015.198 6.41l-.002.151v5.06a.711.711 0 00.364.624l5.42 3.087-1.876 1.07a.067.067 0 01-.063.005l-4.489-2.559c-1.995-1.14-2.679-3.658-1.53-5.63h.001zm15.417 3.54l-5.42-3.088L14.896 7.6a.067.067 0 01.063-.006l4.489 2.557c1.998 1.14 2.683 3.662 1.529 5.633a4.163 4.163 0 01-2.174 1.807V12.38a.71.71 0 00-.363-.623zm1.867-2.773a6.04 6.04 0 00-.132-.078l-4.44-2.53a.731.731 0 00-.729 0l-5.42 3.088V7.325a.068.068 0 01.027-.057L14.1 4.713c2-1.137 4.555-.46 5.707 1.513.487.833.664 1.809.499 2.757h.001zm-11.741 3.81l-1.877-1.068a.065.065 0 01-.036-.051V6.559c.001-2.277 1.873-4.122 4.181-4.12.976 0 1.92.338 2.671.954-.034.018-.092.05-.131.073l-4.44 2.53a.71.71 0 00-.365.623l-.003 6.173v.001zm1.02-2.168L12 9.25l2.414 1.375v2.75L12 14.75l-2.415-1.375v-2.75z'
7
+ codex: 'M21.55 10.004a5.416 5.416 0 00-.478-4.501c-1.217-2.09-3.662-3.166-6.05-2.66A5.59 5.59 0 0010.831 1C8.39.995 6.224 2.546 5.473 4.838A5.553 5.553 0 001.76 7.496a5.487 5.487 0 00.691 6.5 5.416 5.416 0 00.477 4.502c1.217 2.09 3.662 3.165 6.05 2.66A5.586 5.586 0 0013.168 23c2.443.006 4.61-1.546 5.361-3.84a5.554 5.554 0 003.715-2.66 5.488 5.488 0 00-.693-6.497v.001zm-8.381 11.558a4.199 4.199 0 01-2.675-.954c.034-.018.093-.05.132-.074l4.44-2.53a.71.71 0 00.364-.623v-6.176l1.877 1.069c.02.01.033.029.036.05v5.115c-.003 2.274-1.87 4.118-4.174 4.123zM4.192 17.78a4.059 4.059 0 01-.498-2.763c.032.019.09.055.131.078l4.44 2.53c.225.13.504.13.73 0l5.42-3.088v2.138a.068.068 0 01-.027.057L9.9 19.288c-1.999 1.136-4.552.46-5.707-1.51h-.001zM3.023 8.216A4.15 4.15 0 015.198 6.41l-.002.151v5.06a.711.711 0 00.364.624l5.42 3.087-1.876 1.07a.067.067 0 01-.063.005l-4.489-2.559c-1.995-1.14-2.679-3.658-1.53-5.63h.001zm15.417 3.54l-5.42-3.088L14.896 7.6a.067.067 0 01.063-.006l4.489 2.557c1.998 1.14 2.683 3.662 1.529 5.633a4.163 4.163 0 01-2.174 1.807V12.38a.71.71 0 00-.363-.623zm1.867-2.773a6.04 6.04 0 00-.132-.078l-4.44-2.53a.731.731 0 00-.729 0l-5.42 3.088V7.325a.068.068 0 01.027-.057L14.1 4.713c2-1.137 4.555-.46 5.707 1.513.487.833.664 1.809.499 2.757h.001zm-11.741 3.81l-1.877-1.068a.065.065 0 01-.036-.051V6.559c.001-2.277 1.873-4.122 4.181-4.12.976 0 1.92.338 2.671.954-.034.018-.092.05-.131.073l-4.44 2.53a.71.71 0 00-.365.623l-.003 6.173v.001zm1.02-2.168L12 9.25l2.414 1.375v2.75L12 14.75l-2.415-1.375v-2.75z',
8
+ nanogpt: 'M5 20V4h2.6l8.8 11.2V4H19v16h-2.6L7.6 8.8V20z'
8
9
  };
package/dist/host.d.ts CHANGED
@@ -3,7 +3,7 @@ import type { Extension } from '@codemirror/state';
3
3
  import type { EditorView } from '@codemirror/view';
4
4
  import type { Highlighter, ThemeRegistration } from 'shiki';
5
5
  import type { EditorEngine } from '@adecore/editor';
6
- import type { AgentKind, ChatCheckpointDiff, ChatConfigurePayload, ChatGoalAction, ChatItem, ChatSubagentItem, ChatUiChoicePayload, ChatUiQueryPayload, ChatUiQueryReading, ChatUiLinkPayload, ChatUiLinkReading } from '@adecore/agent-contracts';
6
+ import type { AgentKind, ChatCheckpointDiff, ChatConfigurePayload, ChatGoalAction, ChatItem, ChatSubagentItem, ChatUiChoicePayload, ChatUiQueryPayload, ChatUiQueryReading, ChatUiLinkPayload, ChatUiLinkReading, ProviderPlatform } from '@adecore/agent-contracts';
7
7
  import type { TimelineRow } from './chat/logic/timeline.js';
8
8
  import type { FindReveal } from './chat/ui/find-reveal.js';
9
9
  import type { PromptAction } from './prompts/logic/prompts.js';
@@ -249,6 +249,9 @@ export interface ChatHost {
249
249
  }>;
250
250
  openLogin: ((scopeId: string, kind: AgentKind, accountId: string, name: string) => Promise<void>) | null;
251
251
  useLoginBlocked(scopeId: string): string | null;
252
+ runProviderCommand?(scopeId: string, kind: AgentKind, command: string): void;
253
+ useMachinePlatform?(scopeId: string): ProviderPlatform | null;
254
+ providerKeyNote?(variable: string): string | null;
252
255
  useProjectLook(scopeId: string, projectId: string | null): {
253
256
  name: string;
254
257
  mark: ReactNode;
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { type ChatActions, type FileRef, type ShellCodeBlockContext, type ChatPl
2
2
  export { type ChatScope, ChatScopeContext, useChatScope } from './scope.js';
3
3
  export { type ChatRequestMap, type ChatEventMap, type ChatTransportStatus, type ChatTransport, ChatTransportError, errorCode, isConnectionError } from './transport.js';
4
4
  export { portTransport, type PortTransport } from './port-transport.js';
5
+ export { configureAgentMarks, type AgentMark } from './agents/agent-marks.js';
5
6
  export { configureChatStorage, type ChatStorageRecord, type ChatStorageAdapter, type ChatStorageOptions } from './storage.js';
6
7
  export { setLazyPrefetch, onLazyOpenError } from './lazy.js';
7
8
  export { type MountedEntry, MountedRegistry } from './mounted-registry.js';
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ export { ChatHostProvider, useChatHost } from './host.js';
3
3
  export { ChatScopeContext, useChatScope } from './scope.js';
4
4
  export { ChatTransportError, errorCode, isConnectionError } from './transport.js';
5
5
  export { portTransport } from './port-transport.js';
6
+ export { configureAgentMarks } from './agents/agent-marks.js';
6
7
  export { configureChatStorage } from './storage.js';
7
8
  export { setLazyPrefetch, onLazyOpenError } from './lazy.js';
8
9
  export { MountedRegistry } from './mounted-registry.js';
@@ -22,6 +22,43 @@ declare const _default: {
22
22
  none: string;
23
23
  noAccounts: string;
24
24
  };
25
+ install: {
26
+ notInstalled: string;
27
+ steps: string;
28
+ platform: string;
29
+ platforms: {
30
+ darwin: string;
31
+ linux: string;
32
+ win32: string;
33
+ };
34
+ copy: string;
35
+ copied: string;
36
+ run: string;
37
+ guide: string;
38
+ install: {
39
+ title: string;
40
+ none: string;
41
+ done: string;
42
+ doneNoVersion: string;
43
+ };
44
+ signIn: {
45
+ title: string;
46
+ command: string;
47
+ env: string;
48
+ chat: string;
49
+ or: string;
50
+ unknown: string;
51
+ };
52
+ start: {
53
+ title: string;
54
+ description: string;
55
+ };
56
+ footer: {
57
+ missing: string;
58
+ found: string;
59
+ };
60
+ fallback: string;
61
+ };
25
62
  abilities: {
26
63
  version: string;
27
64
  chatAndTerminal: string;
@@ -66,6 +103,8 @@ declare const _default: {
66
103
  signedOutDetail: string;
67
104
  logIn: string;
68
105
  logInAgain: string;
106
+ inChat: string;
107
+ inChatDetail: string;
69
108
  };
70
109
  folder: {
71
110
  label: string;
@@ -106,6 +145,7 @@ declare const _default: {
106
145
  add: {
107
146
  title: string;
108
147
  description: string;
148
+ chatDescription: string;
109
149
  name: string;
110
150
  namePlaceholder: string;
111
151
  color: string;
@@ -18,10 +18,47 @@ export default {
18
18
  addAccount: 'Add a {{provider}} account',
19
19
  notInstalled: 'Not installed',
20
20
  missing: 'Missing',
21
- notInstalledNote: 'To add one, install it and restart Ruimte on that machine.',
21
+ notInstalledNote: 'Install one on this machine to use it here.',
22
22
  none: 'No agent CLIs found on this machine.',
23
23
  noAccounts: 'This machine keeps no accounts yet. Update Ruimte there to add one.'
24
24
  },
25
+ install: {
26
+ notInstalled: 'Not installed',
27
+ steps: 'Steps to set up {{provider}}',
28
+ platform: 'System',
29
+ platforms: {
30
+ darwin: 'macOS',
31
+ linux: 'Linux',
32
+ win32: 'Windows'
33
+ },
34
+ copy: 'Copy',
35
+ copied: 'Copied',
36
+ run: 'Run in a terminal',
37
+ guide: 'Install guide',
38
+ install: {
39
+ title: 'Install the CLI',
40
+ none: 'There is no install command for this system.',
41
+ done: 'Installed, version {{version}}.',
42
+ doneNoVersion: 'Installed.'
43
+ },
44
+ signIn: {
45
+ title: 'Sign in',
46
+ command: 'Run this in a terminal and follow its steps:',
47
+ env: 'Set <code>{{variable}}</code> in the environment the CLI starts in.',
48
+ chat: 'The first chat asks you to sign in.',
49
+ or: 'Or',
50
+ unknown: 'The CLI asks you to sign in the first time it runs.'
51
+ },
52
+ start: {
53
+ title: 'Start working',
54
+ description: 'Pick {{provider}} for your next chat or terminal.'
55
+ },
56
+ footer: {
57
+ missing: 'Not found on this machine yet. It is checked again when you come back to this window.',
58
+ found: 'Found on this machine.'
59
+ },
60
+ fallback: 'Install {{provider}} on this machine to use it here.'
61
+ },
25
62
  abilities: {
26
63
  version: 'Version {{version}}.',
27
64
  chatAndTerminal: 'Chat and terminal.',
@@ -65,7 +102,9 @@ export default {
65
102
  signedOut: 'Not logged in',
66
103
  signedOutDetail: "Opens a terminal that runs the CLI's login with this folder.",
67
104
  logIn: 'Log in',
68
- logInAgain: 'Log in again'
105
+ logInAgain: 'Log in again',
106
+ inChat: 'Logs in from a chat',
107
+ inChatDetail: 'The first chat on this account asks you to log in, and the CLI keeps that login in this folder.'
69
108
  },
70
109
  folder: {
71
110
  label: 'Folder',
@@ -106,6 +145,7 @@ export default {
106
145
  add: {
107
146
  title: 'Add a {{provider}} account',
108
147
  description: 'Ruimte makes a folder for it on the machine, then opens a terminal where you log in.',
148
+ chatDescription: 'A folder is made for it on the machine. The first chat on it asks you to log in.',
109
149
  name: 'Name',
110
150
  namePlaceholder: 'Work',
111
151
  color: 'Color',
@@ -22,6 +22,43 @@ declare const _default: {
22
22
  none: string;
23
23
  noAccounts: string;
24
24
  };
25
+ install: {
26
+ notInstalled: string;
27
+ steps: string;
28
+ platform: string;
29
+ platforms: {
30
+ darwin: string;
31
+ linux: string;
32
+ win32: string;
33
+ };
34
+ copy: string;
35
+ copied: string;
36
+ run: string;
37
+ guide: string;
38
+ install: {
39
+ title: string;
40
+ none: string;
41
+ done: string;
42
+ doneNoVersion: string;
43
+ };
44
+ signIn: {
45
+ title: string;
46
+ command: string;
47
+ env: string;
48
+ chat: string;
49
+ or: string;
50
+ unknown: string;
51
+ };
52
+ start: {
53
+ title: string;
54
+ description: string;
55
+ };
56
+ footer: {
57
+ missing: string;
58
+ found: string;
59
+ };
60
+ fallback: string;
61
+ };
25
62
  abilities: {
26
63
  version: string;
27
64
  chatAndTerminal: string;
@@ -66,6 +103,8 @@ declare const _default: {
66
103
  signedOutDetail: string;
67
104
  logIn: string;
68
105
  logInAgain: string;
106
+ inChat: string;
107
+ inChatDetail: string;
69
108
  };
70
109
  folder: {
71
110
  label: string;
@@ -106,6 +145,7 @@ declare const _default: {
106
145
  add: {
107
146
  title: string;
108
147
  description: string;
148
+ chatDescription: string;
109
149
  name: string;
110
150
  namePlaceholder: string;
111
151
  color: string;
@@ -18,10 +18,47 @@ export default {
18
18
  addAccount: 'Een {{provider}}-account toevoegen',
19
19
  notInstalled: 'Niet geïnstalleerd',
20
20
  missing: 'Ontbreekt',
21
- notInstalledNote: 'Installeer er een en start Ruimte op die machine opnieuw om hem toe te voegen.',
21
+ notInstalledNote: 'Installeer er een op deze machine om hem hier te gebruiken.',
22
22
  none: "Geen agent-CLI's gevonden op deze machine.",
23
23
  noAccounts: 'Deze machine houdt nog geen accounts bij. Werk Ruimte daar bij om er een toe te voegen.'
24
24
  },
25
+ install: {
26
+ notInstalled: 'Niet geïnstalleerd',
27
+ steps: '{{provider}} instellen in stappen',
28
+ platform: 'Systeem',
29
+ platforms: {
30
+ darwin: 'macOS',
31
+ linux: 'Linux',
32
+ win32: 'Windows'
33
+ },
34
+ copy: 'Kopiëren',
35
+ copied: 'Gekopieerd',
36
+ run: 'In een terminal uitvoeren',
37
+ guide: 'Installatiegids',
38
+ install: {
39
+ title: 'Installeer de CLI',
40
+ none: 'Er is geen installatiecommando voor dit systeem.',
41
+ done: 'Geïnstalleerd, versie {{version}}.',
42
+ doneNoVersion: 'Geïnstalleerd.'
43
+ },
44
+ signIn: {
45
+ title: 'Log in',
46
+ command: 'Voer dit uit in een terminal en volg de stappen:',
47
+ env: 'Zet <code>{{variable}}</code> in de omgeving waarin de CLI start.',
48
+ chat: 'De eerste chat vraagt je om in te loggen.',
49
+ or: 'Of',
50
+ unknown: 'De CLI vraagt je om in te loggen als hij voor het eerst draait.'
51
+ },
52
+ start: {
53
+ title: 'Aan de slag',
54
+ description: 'Kies {{provider}} voor je volgende chat of terminal.'
55
+ },
56
+ footer: {
57
+ missing: 'Nog niet gevonden op deze machine. Er wordt opnieuw gekeken als je terugkomt in dit venster.',
58
+ found: 'Gevonden op deze machine.'
59
+ },
60
+ fallback: 'Installeer {{provider}} op deze machine om hem hier te gebruiken.'
61
+ },
25
62
  abilities: {
26
63
  version: 'Versie {{version}}.',
27
64
  chatAndTerminal: 'Chat en terminal.',
@@ -65,7 +102,9 @@ export default {
65
102
  signedOut: 'Niet ingelogd',
66
103
  signedOutDetail: 'Opent een terminal die de login van de CLI met deze map draait.',
67
104
  logIn: 'Inloggen',
68
- logInAgain: 'Opnieuw inloggen'
105
+ logInAgain: 'Opnieuw inloggen',
106
+ inChat: 'Logt in vanuit een chat',
107
+ inChatDetail: 'De eerste chat op dit account vraagt je in te loggen, en de CLI bewaart die login in deze map.'
69
108
  },
70
109
  folder: {
71
110
  label: 'Map',
@@ -106,6 +145,7 @@ export default {
106
145
  add: {
107
146
  title: 'Een {{provider}}-account toevoegen',
108
147
  description: 'Ruimte maakt er een map voor op de machine en opent dan een terminal waarin je inlogt.',
148
+ chatDescription: 'Er komt een map voor op de machine. De eerste chat erop vraagt je in te loggen.',
109
149
  name: 'Naam',
110
150
  namePlaceholder: 'Werk',
111
151
  color: 'Kleur',
@@ -1,12 +1,13 @@
1
1
  import type { ProviderInfo } from '@adecore/agent-contracts';
2
- import { type AccountEntry } from '../agents/accounts.js';
2
+ import { type AccountEntry, type AccountLogin } from '../agents/accounts.js';
3
3
  interface AccountDetailProps {
4
4
  provider: ProviderInfo;
5
5
  entry: AccountEntry;
6
- canLogIn: boolean;
6
+ login: AccountLogin;
7
+ canOpenLogin: boolean;
7
8
  loginBlocked: string | null;
8
9
  secretsAvailable: boolean;
9
10
  onRemoved(): void;
10
11
  }
11
- export declare function AccountDetail({ provider, entry, canLogIn, loginBlocked, secretsAvailable, onRemoved }: AccountDetailProps): import("react").JSX.Element;
12
+ export declare function AccountDetail({ provider, entry, login, canOpenLogin, loginBlocked, secretsAvailable, onRemoved }: AccountDetailProps): import("react").JSX.Element;
12
13
  export {};
@@ -32,7 +32,7 @@ function NameField({ value, label, onSave }) {
32
32
  } }));
33
33
  }
34
34
  /* One account: how it looks in a picker, who is signed in, where its folder is, its variables. */
35
- export function AccountDetail({ provider, entry, canLogIn, loginBlocked, secretsAvailable, onRemoved }) {
35
+ export function AccountDetail({ provider, entry, login, canOpenLogin, loginBlocked, secretsAvailable, onRemoved }) {
36
36
  const { t } = useTranslation('agent-providers');
37
37
  const scope = useChatScope();
38
38
  const host = useChatHost();
@@ -46,9 +46,10 @@ export function AccountDetail({ provider, entry, canLogIn, loginBlocked, secrets
46
46
  copyText(folder);
47
47
  host.notify({ kind: 'success', title: t('account.folderCopied'), description: folder });
48
48
  };
49
+ const trouble = status !== null && TROUBLE.has(status.state) && status.message ? status.message : null;
49
50
  const loginDetail = () => {
50
- if (status !== null && TROUBLE.has(status.state) && status.message) {
51
- return status.message;
51
+ if (trouble !== null) {
52
+ return trouble;
52
53
  }
53
54
  if (!signedIn) {
54
55
  return t('account.login.signedOutDetail');
@@ -59,11 +60,11 @@ export function AccountDetail({ provider, entry, canLogIn, loginBlocked, secrets
59
60
  return (_jsxs(_Fragment, { children: [_jsx(DetailHeader, { mark: _jsx("span", { className: "grid h-6 shrink-0 place-items-center", children: _jsx(AccountDot, { color: account.color, className: "size-3" }) }), title: name, subtitle: _jsxs(_Fragment, { children: [_jsx(CliMark, { kind: provider.kind, size: 12, className: "text-text-muted" }), t('account.kind', { provider: provider.name })] }), actions: folder !== '' && (_jsx(Button, { variant: "secondary", onClick: showFolder, children: t('account.showFolder') })) }), _jsxs(SettingsSection, { children: [_jsx(SettingsRow, { label: t('account.enabled.label'), description: t('account.enabled.description'), control: _jsx(Switch, { checked: account.enabled !== false, label: t('account.enabled.label'), onCheckedChange: (checked) => {
60
61
  const { enabled: _enabled, ...rest } = account;
61
62
  void saveAccount(scope, host, id, checked ? rest : { ...rest, enabled: false });
62
- } }) }), _jsx(SettingsRow, { label: t('account.name'), control: _jsx(NameField, { value: name, label: t('account.name'), onSave: (label) => void saveAccount(scope, host, id, { ...account, label }) }, name) }), _jsx(SettingsRow, { label: t('account.color'), control: _jsx(AccountColors, { value: account.color, label: t('account.color'), onChange: (color) => void saveAccount(scope, host, id, { ...account, color }) }) })] }), canLogIn && (_jsxs(SettingsSection, { title: t('account.login.title'), children: [_jsx(SettingsRow, { label: signedIn
63
+ } }) }), _jsx(SettingsRow, { label: t('account.name'), control: _jsx(NameField, { value: name, label: t('account.name'), onSave: (label) => void saveAccount(scope, host, id, { ...account, label }) }, name) }), _jsx(SettingsRow, { label: t('account.color'), control: _jsx(AccountColors, { value: account.color, label: t('account.color'), onChange: (color) => void saveAccount(scope, host, id, { ...account, color }) }) })] }), (login === 'chat' || (login === 'terminal' && canOpenLogin)) && (_jsxs(SettingsSection, { title: t('account.login.title'), children: [login === 'chat' ? (_jsx(SettingsRow, { label: t('account.login.inChat'), description: trouble ?? t('account.login.inChatDetail') })) : (_jsx(SettingsRow, { label: signedIn
63
64
  ? status?.email
64
65
  ? t('account.login.signedInAs', { email: status.email })
65
66
  : t('account.login.signedIn')
66
- : t('account.login.signedOut'), description: loginBlocked === null ? loginDetail() : `${loginDetail()} ${loginBlocked}`, control: loginBlocked === null ? (loginButton) : (_jsx(Tooltip, { label: loginBlocked, children: _jsx("span", { className: "inline-flex", children: loginButton }) })) }), _jsxs(SettingsRow, { label: t('account.folder.label'), children: [_jsx(Input, { mono: true, className: "h-8.5", value: folder, readOnly: true, "aria-label": t('account.folder.label') }), _jsx("p", { className: "-mt-1 text-xs text-text-muted", children: variable ? (_jsx(Trans, { t: t, i18nKey: "account.folder.detail", values: { variable }, components: { code: _jsx("code", { className: "font-mono text-code" }) } })) : (t('account.folder.detailPlain')) })] })] })), _jsx(AccountVariables, { id: id, account: account, secretsAvailable: secretsAvailable }, `${id}:${JSON.stringify(account.env ?? [])}`), _jsx(SettingsSection, { children: _jsx(SettingsRow, { label: t('account.remove.label'), description: isDefault ? t('account.remove.defaultNote') : t('account.remove.description'), control: _jsx(Button, { variant: "danger-outline", disabled: isDefault, onClick: () => setConfirming(true), children: t('account.remove.button') }) }) }), _jsx(ConfirmDialog, { open: confirming, onOpenChange: setConfirming, title: t('account.remove.confirmTitle', { account: name }), description: t('account.remove.confirmDescription', { folder }), confirmLabel: t('account.remove.confirm'), onConfirm: async () => {
67
+ : t('account.login.signedOut'), description: loginBlocked === null ? loginDetail() : `${loginDetail()} ${loginBlocked}`, control: loginBlocked === null ? (loginButton) : (_jsx(Tooltip, { label: loginBlocked, children: _jsx("span", { className: "inline-flex", children: loginButton }) })) })), _jsxs(SettingsRow, { label: t('account.folder.label'), children: [_jsx(Input, { mono: true, className: "h-8.5", value: folder, readOnly: true, "aria-label": t('account.folder.label') }), _jsx("p", { className: "-mt-1 text-xs text-text-muted", children: variable ? (_jsx(Trans, { t: t, i18nKey: "account.folder.detail", values: { variable }, components: { code: _jsx("code", { className: "font-mono text-code" }) } })) : (t('account.folder.detailPlain')) })] })] })), _jsx(AccountVariables, { id: id, account: account, secretsAvailable: secretsAvailable }, `${id}:${JSON.stringify(account.env ?? [])}`), _jsx(SettingsSection, { children: _jsx(SettingsRow, { label: t('account.remove.label'), description: isDefault ? t('account.remove.defaultNote') : t('account.remove.description'), control: _jsx(Button, { variant: "danger-outline", disabled: isDefault, onClick: () => setConfirming(true), children: t('account.remove.button') }) }) }), _jsx(ConfirmDialog, { open: confirming, onOpenChange: setConfirming, title: t('account.remove.confirmTitle', { account: name }), description: t('account.remove.confirmDescription', { folder }), confirmLabel: t('account.remove.confirm'), onConfirm: async () => {
67
68
  await removeAccount(scope, host, id, provider.kind);
68
69
  onRemoved();
69
70
  } })] }));
@@ -1,10 +1,11 @@
1
1
  import type { ProviderInfo } from '@adecore/agent-contracts';
2
- import { type AccountEntry } from '../agents/accounts.js';
2
+ import { type AccountEntry, type AccountLogin } from '../agents/accounts.js';
3
3
  interface AddAccountFormProps {
4
4
  provider: ProviderInfo;
5
5
  entries: AccountEntry[];
6
+ login: AccountLogin;
6
7
  onCancel(): void;
7
8
  onAdded(id: string, name: string, made: boolean): void;
8
9
  }
9
- export declare function AddAccountForm({ provider, entries, onCancel, onAdded }: AddAccountFormProps): import("react").JSX.Element;
10
+ export declare function AddAccountForm({ provider, entries, login, onCancel, onAdded }: AddAccountFormProps): import("react").JSX.Element;
10
11
  export {};
@@ -16,7 +16,7 @@ import { providerAccountsOf } from '../state/provider-accounts.js';
16
16
  * A name and a color, asked before anything exists, so the account is made once with both. By default
17
17
  * the machine makes the folder; under Advanced a person points at a folder of their own instead.
18
18
  */
19
- export function AddAccountForm({ provider, entries, onCancel, onAdded }) {
19
+ export function AddAccountForm({ provider, entries, login, onCancel, onAdded }) {
20
20
  const { t } = useTranslation('agent-providers');
21
21
  const scope = useChatScope();
22
22
  const { accents } = useChatHost();
@@ -51,5 +51,5 @@ export function AddAccountForm({ provider, entries, onCancel, onAdded }) {
51
51
  setBusy(false);
52
52
  }
53
53
  };
54
- return (_jsxs("form", { className: "contents", onSubmit: (event) => void submit(event), children: [_jsx(DetailHeader, { mark: _jsx(CliTile, { kind: provider.kind }), title: t('add.title', { provider: provider.name }), subtitle: t('add.description') }), _jsxs(SettingsSection, { children: [_jsx(SettingsRow, { label: t('add.name'), control: _jsx(Input, { className: "w-55 max-w-full", value: name, maxLength: 80, autoFocus: true, placeholder: t('add.namePlaceholder'), "aria-label": t('add.name'), onChange: (event) => setName(event.target.value) }) }), _jsx(SettingsRow, { label: t('add.color'), control: _jsx(AccountColors, { value: color, label: t('add.color'), onChange: setColor }) })] }), _jsxs("div", { className: "flex min-w-0 flex-col gap-2.5", children: [_jsxs("button", { type: "button", "aria-expanded": advanced, className: "flex items-center gap-1.5 self-start text-sm font-medium text-text-muted hover:text-text", onClick: () => setAdvanced((open) => !open), children: [_jsx(Icon, { icon: ChevronRight, size: 14, className: clsx('transition-transform', advanced && 'rotate-90') }), t('add.advanced')] }), advanced && (_jsx(SettingsSection, { children: _jsx(SettingsRow, { label: t('add.link.label'), description: t('add.link.description'), children: _jsx(Input, { mono: true, value: folder, spellCheck: false, autoComplete: "off", placeholder: t('add.link.placeholder'), "aria-label": t('add.link.label'), onChange: (event) => setFolder(event.target.value) }) }) }))] }), _jsxs("div", { className: "flex min-w-0 flex-wrap items-center justify-end gap-2", children: [error !== null && (_jsx(FormError, { render: _jsx("span", {}), className: "mr-auto break-words", children: error || t('add.failed') })), _jsx(Button, { onClick: onCancel, children: t('common.cancel') }), _jsx(Button, { type: "submit", variant: "primary", disabled: label === '' || busy, children: linking ? t('add.linkButton') : t('add.create') })] })] }));
54
+ return (_jsxs("form", { className: "contents", onSubmit: (event) => void submit(event), children: [_jsx(DetailHeader, { mark: _jsx(CliTile, { kind: provider.kind }), title: t('add.title', { provider: provider.name }), subtitle: login === 'chat' ? t('add.chatDescription') : t('add.description') }), _jsxs(SettingsSection, { children: [_jsx(SettingsRow, { label: t('add.name'), control: _jsx(Input, { className: "w-55 max-w-full", value: name, maxLength: 80, autoFocus: true, placeholder: t('add.namePlaceholder'), "aria-label": t('add.name'), onChange: (event) => setName(event.target.value) }) }), _jsx(SettingsRow, { label: t('add.color'), control: _jsx(AccountColors, { value: color, label: t('add.color'), onChange: setColor }) })] }), _jsxs("div", { className: "flex min-w-0 flex-col gap-2.5", children: [_jsxs("button", { type: "button", "aria-expanded": advanced, className: "flex items-center gap-1.5 self-start text-sm font-medium text-text-muted hover:text-text", onClick: () => setAdvanced((open) => !open), children: [_jsx(Icon, { icon: ChevronRight, size: 14, className: clsx('transition-transform', advanced && 'rotate-90') }), t('add.advanced')] }), advanced && (_jsx(SettingsSection, { children: _jsx(SettingsRow, { label: t('add.link.label'), description: t('add.link.description'), children: _jsx(Input, { mono: true, value: folder, spellCheck: false, autoComplete: "off", placeholder: t('add.link.placeholder'), "aria-label": t('add.link.label'), onChange: (event) => setFolder(event.target.value) }) }) }))] }), _jsxs("div", { className: "flex min-w-0 flex-wrap items-center justify-end gap-2", children: [error !== null && (_jsx(FormError, { render: _jsx("span", {}), className: "mr-auto break-words", children: error || t('add.failed') })), _jsx(Button, { onClick: onCancel, children: t('common.cancel') }), _jsx(Button, { type: "submit", variant: "primary", disabled: label === '' || busy, children: linking ? t('add.linkButton') : t('add.create') })] })] }));
55
55
  }
@@ -1,12 +1,12 @@
1
1
  import type { ProviderInfo } from '@adecore/agent-contracts';
2
- import { type AccountEntry } from '../agents/accounts.js';
2
+ import { type AccountEntry, type AccountLogin } from '../agents/accounts.js';
3
3
  interface CliDetailProps {
4
4
  provider: ProviderInfo;
5
5
  entries: AccountEntry[] | null;
6
6
  defaults: boolean;
7
- canAddAccount: boolean;
7
+ login: AccountLogin;
8
8
  onAddAccount(): void;
9
9
  onPickAccount(id: string): void;
10
10
  }
11
- export declare function CliDetail({ provider, entries, defaults, canAddAccount, onAddAccount, onPickAccount }: CliDetailProps): import("react").JSX.Element;
11
+ export declare function CliDetail({ provider, entries, defaults, login, onAddAccount, onPickAccount }: CliDetailProps): import("react").JSX.Element;
12
12
  export {};
@@ -21,7 +21,7 @@ function ModelOptionRows({ provider, model, options }) {
21
21
  return (_jsx(_Fragment, { children: model.options.map((option) => (_jsx(SettingsRow, { indent: true, muted: true, label: option.label, control: _jsx(ModelOptionControl, { option: option, options: options, layout: "row", onChange: (value) => setOption(option.id, value) }) }, option.id))) }));
22
22
  }
23
23
  /* One CLI: what it is, what a new chat of it starts with unless `defaults` is off, and its accounts. */
24
- export function CliDetail({ provider, entries, defaults, canAddAccount, onAddAccount, onPickAccount }) {
24
+ export function CliDetail({ provider, entries, defaults, login, onAddAccount, onPickAccount }) {
25
25
  const { t } = useTranslation('agent-providers');
26
26
  const { id: scopeId } = useChatScope();
27
27
  const preferences = useChatPreferences();
@@ -31,7 +31,7 @@ export function CliDetail({ provider, entries, defaults, canAddAccount, onAddAcc
31
31
  const picked = accountFor(preferences, scopeId, provider.kind, machineAccounts) ?? provider.kind;
32
32
  // An account that is off is out of every picker, unless it is the one picked already.
33
33
  const choosable = (entries ?? []).filter((entry) => entry.account.enabled !== false || entry.id === picked);
34
- const canLogIn = canAddAccount;
34
+ const canAddAccount = login !== null;
35
35
  return (_jsxs(_Fragment, { children: [_jsx(DetailHeader, { mark: _jsx(CliTile, { kind: provider.kind }), title: provider.name, subtitle: `${provider.version ? `${t('abilities.version', { version: provider.version })} ` : ''}${providerAbilities(provider)}`, actions: canAddAccount && (_jsxs(Button, { variant: "secondary", className: "whitespace-nowrap", onClick: onAddAccount, children: [_jsx(Icon, { icon: Plus, size: 14 }), t('cli.addAccount')] })) }), defaults && (_jsxs(SettingsSection, { title: t('cli.defaults.title'), description: t('cli.defaults.description'), children: [choosable.length > 0 && (_jsx(SettingsRow, { searchId: "providers.defaults.account", label: t('cli.defaults.account'), control: _jsx(Select, { value: picked, label: t('cli.defaults.accountFor', { provider: provider.name }), align: "end", items: choosable.map((entry) => ({
36
36
  value: entry.id,
37
37
  label: accountName(entry, provider.name),
@@ -48,7 +48,7 @@ export function CliDetail({ provider, entries, defaults, canAddAccount, onAddAcc
48
48
  model: value,
49
49
  options: carryOptions(selection?.options ?? {}, provider.models.find((entry) => entry.slug === value))
50
50
  }) }) })), model && _jsx(ModelOptionRows, { provider: provider.kind, model: model, options: selection?.options ?? {} }), choosable.length === 0 && provider.models.length === 0 && _jsx(SettingsRow, { muted: true, label: t('cli.defaults.usesCliDefault') })] })), entries === null ? (_jsx("p", { className: "text-xs text-text-faint", children: t('list.noAccounts') })) : (_jsx(SettingsSection, { title: t('cli.accounts'), footer: canAddAccount ? undefined : t('cli.onlyDefault', { provider: provider.name }), children: entries.map((entry) => {
51
- const status = accountStatusLine(entry.status, canLogIn);
51
+ const status = accountStatusLine(entry.status, login === 'terminal');
52
52
  return (_jsxs("button", { type: "button", className: "flex w-full min-w-0 items-center gap-3 px-4.5 py-3 text-left first:rounded-t-xl last:rounded-b-xl hover:bg-surface-hover", onClick: () => onPickAccount(entry.id), children: [_jsx(AccountDot, { color: entry.account.color }), _jsx("span", { className: "min-w-0 grow truncate text-sm text-text", children: accountName(entry, provider.name) }), _jsx("span", { className: `shrink-0 text-xs ${ACCOUNT_TONE_CLASSES[status.tone]}`, children: status.text }), _jsx(Icon, { icon: ChevronRight, size: 14, className: "shrink-0 text-text-faint" })] }, entry.id));
53
53
  }) }))] }));
54
54
  }
@@ -0,0 +1,9 @@
1
+ import type { ProviderInfo } from '@adecore/agent-contracts';
2
+ import { type ProviderInstallStep } from './install-steps.js';
3
+ interface InstallPageProps {
4
+ provider: ProviderInfo;
5
+ signedIn: boolean;
6
+ extra: ProviderInstallStep | null;
7
+ }
8
+ export declare function InstallPage({ provider, signedIn, extra }: InstallPageProps): import("react").JSX.Element;
9
+ export {};
@@ -0,0 +1,74 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import clsx from 'clsx';
4
+ import { Trans, useTranslation } from 'react-i18next';
5
+ import { Check, Copy, ExternalLink, SquareTerminal } from 'lucide-react';
6
+ import { Button, copyText, Icon, Pill, Segmented } from '@adecore/ui';
7
+ import { useChatHost } from '../host.js';
8
+ import { useChatScope } from '../scope.js';
9
+ import { installPlatforms, installStepStates, pagePlatform } from './install-steps.js';
10
+ import { CliMark } from './parts.js';
11
+ const noPlatform = () => null;
12
+ function CommandBlock({ command }) {
13
+ const { t } = useTranslation('agent-providers');
14
+ const [copied, setCopied] = useState(null);
15
+ return (_jsxs("div", { className: "flex min-w-0 items-center gap-2 rounded-lg border border-border bg-surface-sunken py-1 pr-1 pl-3", children: [_jsx("code", { className: "min-w-0 grow font-mono text-code break-all text-text select-text", children: command }), _jsxs(Button, { size: "xs", onClick: () => {
16
+ copyText(command);
17
+ setCopied(command);
18
+ }, children: [_jsx(Icon, { icon: copied === command ? Check : Copy, size: 12 }), copied === command ? t('install.copied') : t('install.copy')] })] }));
19
+ }
20
+ function Step({ index, state, title, children }) {
21
+ return (_jsxs("li", { "aria-current": state === 'current' ? 'step' : undefined, "data-state": state, className: clsx('flex min-w-0 gap-3 rounded-xl border p-4', state === 'current' ? 'border-accent' : 'border-border'), children: [_jsx("span", { className: clsx('grid size-6 shrink-0 place-items-center rounded-full text-xs font-medium', state === 'done' && 'bg-positive text-positive-text', state === 'current' && 'border border-accent text-accent', state === 'later' && 'bg-text/7 text-text-faint'), children: state === 'done' ? _jsx(Icon, { icon: Check, size: 14 }) : index + 1 }), _jsxs("div", { className: "flex min-w-0 grow flex-col gap-3", children: [_jsx("h4", { className: clsx('text-sm leading-6 font-medium', state === 'later' ? 'text-text-muted' : 'text-text'), children: title }), children] })] }));
22
+ }
23
+ function SignInWay({ way, canRun, onRun }) {
24
+ const { t } = useTranslation('agent-providers');
25
+ const { providerKeyNote } = useChatHost();
26
+ switch (way.kind) {
27
+ case 'command':
28
+ return (_jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [_jsx("p", { className: "text-xs text-text-muted", children: t('install.signIn.command') }), _jsx(CommandBlock, { command: way.command }), canRun && (_jsxs(Button, { variant: "secondary", size: "sm", className: "self-start", onClick: () => onRun(way.command), children: [_jsx(Icon, { icon: SquareTerminal, size: 14 }), t('install.run')] }))] }));
29
+ case 'env': {
30
+ const note = providerKeyNote?.(way.variable) ?? null;
31
+ return (_jsxs("p", { className: "text-xs text-text-muted", children: [_jsx(Trans, { t: t, i18nKey: "install.signIn.env", values: { variable: way.variable }, components: { code: _jsx("code", { className: "font-mono text-code text-text" }) } }), note !== null && ` ${note}`] }));
32
+ }
33
+ case 'chat':
34
+ return _jsx("p", { className: "text-xs text-text-muted", children: t('install.signIn.chat') });
35
+ }
36
+ }
37
+ /* How to get a CLI that is not on the machine yet, step by step, until it is there and signed in. */
38
+ export function InstallPage({ provider, signedIn, extra }) {
39
+ const { t } = useTranslation('agent-providers');
40
+ const { id: scopeId } = useChatScope();
41
+ const host = useChatHost();
42
+ const useMachinePlatform = host.useMachinePlatform ?? noPlatform;
43
+ const machine = useMachinePlatform(scopeId) ?? pagePlatform();
44
+ const { offered, initial } = installPlatforms(provider, machine);
45
+ // Only a person's pick is kept, so a machine whose system arrives late still opens on its own.
46
+ const [chosen, setPlatform] = useState(null);
47
+ const platform = chosen ?? initial;
48
+ const install = provider.install;
49
+ const run = host.runProviderCommand;
50
+ const onRun = (command) => run?.(scopeId, provider.kind, command);
51
+ const command = platform === null ? undefined : install?.commands[platform];
52
+ const steps = [];
53
+ steps.push({
54
+ title: t('install.install.title'),
55
+ done: provider.installed,
56
+ body: provider.installed ? (_jsx("p", { className: "text-xs text-text-muted", children: provider.version ? t('install.install.done', { version: provider.version }) : t('install.install.doneNoVersion') })) : (_jsxs(_Fragment, { children: [offered.length > 1 && platform !== null && (_jsx(Segmented, { value: platform, label: t('install.platform'), options: offered.map((id) => ({ id, label: t(`install.platforms.${id}`) })), onValueChange: setPlatform })), command ? _jsx(CommandBlock, { command: command }) : _jsx("p", { className: "text-xs text-text-muted", children: t('install.install.none') }), ((command && run) || install?.guideUrl) && (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [command && run && (_jsxs(Button, { variant: "primary", onClick: () => onRun(command), children: [_jsx(Icon, { icon: SquareTerminal, size: 14 }), t('install.run')] })), install?.guideUrl && (_jsxs(Button, { href: install.guideUrl, children: [t('install.guide'), _jsx(Icon, { icon: ExternalLink, size: 14 })] }))] }))] }))
57
+ });
58
+ if (extra !== null) {
59
+ steps.push(extra);
60
+ }
61
+ const ways = install?.signIn ?? [];
62
+ steps.push({
63
+ title: t('install.signIn.title'),
64
+ done: provider.installed && signedIn,
65
+ body: ways.length === 0 ? (_jsx("p", { className: "text-xs text-text-muted", children: t('install.signIn.unknown') })) : (ways.map((way, index) => (_jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [index > 0 && _jsx("span", { className: "text-xs font-medium text-text-faint", children: t('install.signIn.or') }), _jsx(SignInWay, { way: way, canRun: run !== undefined && provider.installed, onRun: onRun })] }, `${way.kind}-${index}`))))
66
+ });
67
+ steps.push({
68
+ title: t('install.start.title'),
69
+ done: false,
70
+ body: _jsx("p", { className: "text-xs text-text-muted", children: t('install.start.description', { provider: provider.name }) })
71
+ });
72
+ const states = installStepStates(steps.map((step) => step.done));
73
+ return (_jsxs(_Fragment, { children: [_jsxs("header", { className: "flex min-w-0 items-start gap-3", children: [_jsx("span", { className: "grid size-10 shrink-0 place-items-center rounded-xl bg-surface-sunken", children: _jsx(CliMark, { kind: provider.kind, size: 20 }) }), _jsxs("div", { className: "flex min-w-0 grow flex-col", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx("h3", { className: "truncate text-lg font-semibold text-text", children: provider.name }), provider.installed ? (provider.version && (_jsx(Pill, { tone: "idle", mono: true, children: provider.version }))) : (_jsx(Pill, { children: t('install.notInstalled') }))] }), install && _jsx("p", { className: "text-xs text-text-muted", children: install.description })] })] }), install ? (_jsx("ol", { "aria-label": t('install.steps', { provider: provider.name }), className: "flex min-w-0 flex-col gap-3", children: steps.map((step, index) => (_jsx(Step, { index: index, state: states[index], title: step.title, children: step.body }, index))) })) : (_jsx("p", { className: "text-xs text-text-muted", children: t('install.fallback', { provider: provider.name }) })), _jsxs("p", { className: "flex min-w-0 items-center gap-2 text-xs text-text-muted", children: [_jsx("span", { "aria-hidden": true, className: clsx('size-2 shrink-0 rounded-full', provider.installed ? 'bg-status-idle' : 'bg-text-faint') }), provider.installed ? t('install.footer.found') : t('install.footer.missing')] })] }));
74
+ }