@yagr/agent 0.2.8 → 0.2.10
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.
- package/README.md +8 -8
- package/dist/cli.js +206 -9
- package/dist/cli.js.map +1 -1
- package/dist/config/yagr-config-service.d.ts +1 -1
- package/dist/config/yagr-config-service.d.ts.map +1 -1
- package/dist/config/yagr-home.d.ts +7 -0
- package/dist/config/yagr-home.d.ts.map +1 -1
- package/dist/config/yagr-home.js +19 -0
- package/dist/config/yagr-home.js.map +1 -1
- package/dist/gateway/format-message.d.ts +3 -0
- package/dist/gateway/format-message.d.ts.map +1 -1
- package/dist/gateway/format-message.js +63 -10
- package/dist/gateway/format-message.js.map +1 -1
- package/dist/gateway/interactive-ui.d.ts.map +1 -1
- package/dist/gateway/interactive-ui.js +49 -4
- package/dist/gateway/interactive-ui.js.map +1 -1
- package/dist/gateway/local-open-bridge.d.ts +3 -0
- package/dist/gateway/local-open-bridge.d.ts.map +1 -0
- package/dist/gateway/local-open-bridge.js +54 -0
- package/dist/gateway/local-open-bridge.js.map +1 -0
- package/dist/gateway/webui.d.ts.map +1 -1
- package/dist/gateway/webui.js +50 -53
- package/dist/gateway/webui.js.map +1 -1
- package/dist/gateway/workflow-links.d.ts +12 -0
- package/dist/gateway/workflow-links.d.ts.map +1 -0
- package/dist/gateway/workflow-links.js +53 -0
- package/dist/gateway/workflow-links.js.map +1 -0
- package/dist/llm/anthropic-account.d.ts +20 -0
- package/dist/llm/anthropic-account.d.ts.map +1 -0
- package/dist/llm/anthropic-account.js +111 -0
- package/dist/llm/anthropic-account.js.map +1 -0
- package/dist/llm/copilot-account.d.ts +36 -0
- package/dist/llm/copilot-account.d.ts.map +1 -0
- package/dist/llm/copilot-account.js +573 -0
- package/dist/llm/copilot-account.js.map +1 -0
- package/dist/llm/create-language-model.d.ts +2 -2
- package/dist/llm/create-language-model.d.ts.map +1 -1
- package/dist/llm/create-language-model.js +187 -38
- package/dist/llm/create-language-model.js.map +1 -1
- package/dist/llm/google-account.d.ts +31 -0
- package/dist/llm/google-account.d.ts.map +1 -0
- package/dist/llm/google-account.js +851 -0
- package/dist/llm/google-account.js.map +1 -0
- package/dist/llm/model-catalog-cache.d.ts +4 -0
- package/dist/llm/model-catalog-cache.d.ts.map +1 -0
- package/dist/llm/model-catalog-cache.js +46 -0
- package/dist/llm/model-catalog-cache.js.map +1 -0
- package/dist/llm/openai-account.d.ts +33 -0
- package/dist/llm/openai-account.d.ts.map +1 -0
- package/dist/llm/openai-account.js +430 -0
- package/dist/llm/openai-account.js.map +1 -0
- package/dist/llm/provider-discovery.d.ts +3 -0
- package/dist/llm/provider-discovery.d.ts.map +1 -0
- package/dist/llm/provider-discovery.js +40 -0
- package/dist/llm/provider-discovery.js.map +1 -0
- package/dist/llm/provider-registry.d.ts +37 -0
- package/dist/llm/provider-registry.d.ts.map +1 -0
- package/dist/llm/provider-registry.js +186 -0
- package/dist/llm/provider-registry.js.map +1 -0
- package/dist/llm/proxy-runtime.d.ts +37 -0
- package/dist/llm/proxy-runtime.d.ts.map +1 -0
- package/dist/llm/proxy-runtime.js +462 -0
- package/dist/llm/proxy-runtime.js.map +1 -0
- package/dist/llm/test-model-policy.d.ts +3 -0
- package/dist/llm/test-model-policy.d.ts.map +1 -0
- package/dist/llm/test-model-policy.js +16 -0
- package/dist/llm/test-model-policy.js.map +1 -0
- package/dist/n8n-local/bootstrap.d.ts +12 -0
- package/dist/n8n-local/bootstrap.d.ts.map +1 -0
- package/dist/n8n-local/bootstrap.js +281 -0
- package/dist/n8n-local/bootstrap.js.map +1 -0
- package/dist/n8n-local/browser-auth.d.ts +12 -0
- package/dist/n8n-local/browser-auth.d.ts.map +1 -0
- package/dist/n8n-local/browser-auth.js +159 -0
- package/dist/n8n-local/browser-auth.js.map +1 -0
- package/dist/n8n-local/detect.d.ts +50 -0
- package/dist/n8n-local/detect.d.ts.map +1 -0
- package/dist/n8n-local/detect.js +202 -0
- package/dist/n8n-local/detect.js.map +1 -0
- package/dist/n8n-local/direct-manager.d.ts +15 -0
- package/dist/n8n-local/direct-manager.d.ts.map +1 -0
- package/dist/n8n-local/direct-manager.js +231 -0
- package/dist/n8n-local/direct-manager.js.map +1 -0
- package/dist/n8n-local/docker-manager.d.ts +18 -0
- package/dist/n8n-local/docker-manager.d.ts.map +1 -0
- package/dist/n8n-local/docker-manager.js +216 -0
- package/dist/n8n-local/docker-manager.js.map +1 -0
- package/dist/n8n-local/owner-credentials.d.ts +15 -0
- package/dist/n8n-local/owner-credentials.d.ts.map +1 -0
- package/dist/n8n-local/owner-credentials.js +50 -0
- package/dist/n8n-local/owner-credentials.js.map +1 -0
- package/dist/n8n-local/plan.d.ts +16 -0
- package/dist/n8n-local/plan.d.ts.map +1 -0
- package/dist/n8n-local/plan.js +48 -0
- package/dist/n8n-local/plan.js.map +1 -0
- package/dist/n8n-local/state.d.ts +41 -0
- package/dist/n8n-local/state.d.ts.map +1 -0
- package/dist/n8n-local/state.js +80 -0
- package/dist/n8n-local/state.js.map +1 -0
- package/dist/n8n-local/workflow-open.d.ts +21 -0
- package/dist/n8n-local/workflow-open.d.ts.map +1 -0
- package/dist/n8n-local/workflow-open.js +50 -0
- package/dist/n8n-local/workflow-open.js.map +1 -0
- package/dist/runtime/context-compaction.d.ts.map +1 -1
- package/dist/runtime/context-compaction.js +7 -0
- package/dist/runtime/context-compaction.js.map +1 -1
- package/dist/runtime/run-engine.d.ts.map +1 -1
- package/dist/runtime/run-engine.js +52 -1
- package/dist/runtime/run-engine.js.map +1 -1
- package/dist/setup/setup-wizard.d.ts +35 -3
- package/dist/setup/setup-wizard.d.ts.map +1 -1
- package/dist/setup/setup-wizard.js +461 -54
- package/dist/setup/setup-wizard.js.map +1 -1
- package/dist/setup.d.ts +2 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +201 -87
- package/dist/setup.js.map +1 -1
- package/dist/system/open-external.d.ts +2 -0
- package/dist/system/open-external.d.ts.map +1 -0
- package/dist/system/open-external.js +25 -0
- package/dist/system/open-external.js.map +1 -0
- package/dist/system/package-manager.d.ts +6 -0
- package/dist/system/package-manager.d.ts.map +1 -0
- package/dist/system/package-manager.js +13 -0
- package/dist/system/package-manager.js.map +1 -0
- package/dist/tools/build-tools.d.ts +110 -65
- package/dist/tools/build-tools.d.ts.map +1 -1
- package/dist/tools/deploy.d.ts +14 -14
- package/dist/tools/generate-workflow.d.ts +5 -5
- package/dist/tools/n8nac.d.ts +96 -53
- package/dist/tools/n8nac.d.ts.map +1 -1
- package/dist/tools/n8nac.js +52 -20
- package/dist/tools/n8nac.js.map +1 -1
- package/dist/tools/observer.d.ts +1 -0
- package/dist/tools/observer.d.ts.map +1 -1
- package/dist/tools/observer.js.map +1 -1
- package/dist/tools/present-workflow-result.d.ts +2 -0
- package/dist/tools/present-workflow-result.d.ts.map +1 -1
- package/dist/tools/present-workflow-result.js +11 -2
- package/dist/tools/present-workflow-result.js.map +1 -1
- package/dist/tools/request-required-action.d.ts +7 -7
- package/dist/tools/request-required-action.js +4 -4
- package/dist/tools/request-required-action.js.map +1 -1
- package/dist/tools/validate.d.ts +14 -14
- package/dist/tools/write-workspace-file.d.ts +5 -5
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/webui/app.js +14 -5
- package/dist/webui/app.js.map +2 -2
- package/package.json +14 -2
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text, render, useApp, useInput, useStdout } from 'ink';
|
|
3
3
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { getProviderDisplayName, getProviderSetupHint, isExperimentalProvider, isOAuthAccountProvider, providerRequiresApiKey, YAGR_MODEL_PROVIDERS, } from '../llm/provider-registry.js';
|
|
4
5
|
// ─── Palette ──────────────────────────────────────────────────────────────────
|
|
5
6
|
const CURSOR = '▸';
|
|
6
7
|
const CHECK = '✓';
|
|
7
8
|
const DOT = '·';
|
|
8
9
|
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
9
|
-
const
|
|
10
|
-
'
|
|
10
|
+
const PROVIDER_WIZARD_ORDER = [
|
|
11
|
+
'openai',
|
|
12
|
+
'openai-proxy',
|
|
13
|
+
'anthropic',
|
|
14
|
+
'anthropic-proxy',
|
|
15
|
+
'google',
|
|
16
|
+
'google-proxy',
|
|
17
|
+
'copilot-proxy',
|
|
18
|
+
'groq',
|
|
19
|
+
'mistral',
|
|
20
|
+
'openrouter',
|
|
11
21
|
];
|
|
22
|
+
const VALID_PROVIDERS = PROVIDER_WIZARD_ORDER.filter((provider) => YAGR_MODEL_PROVIDERS.includes(provider));
|
|
12
23
|
const SURFACE_OPTIONS = [
|
|
13
24
|
{ value: 'telegram', label: 'Telegram', hint: 'Bot-based chat gateway' },
|
|
14
|
-
{ value: 'whatsapp', label: 'WhatsApp', hint: 'Configuration only - runtime coming soon' },
|
|
15
25
|
];
|
|
16
|
-
export function runSetupWizard(callbacks) {
|
|
26
|
+
export function runSetupWizard(callbacks, options = {}) {
|
|
17
27
|
return new Promise((resolve) => {
|
|
18
|
-
const { unmount } = render(_jsx(SetupWizard, { callbacks: callbacks, onDone: (result) => { unmount(); resolve(result); } }));
|
|
28
|
+
const { unmount } = render(_jsx(SetupWizard, { callbacks: callbacks, options: options, onDone: (result) => { unmount(); resolve(result); } }));
|
|
19
29
|
});
|
|
20
30
|
}
|
|
21
31
|
function sectionFor(phase) {
|
|
@@ -34,15 +44,65 @@ function sectionIndex(phase) {
|
|
|
34
44
|
return 2;
|
|
35
45
|
return 3;
|
|
36
46
|
}
|
|
47
|
+
function getProviderAuthCopy(provider) {
|
|
48
|
+
if (provider === 'openai-proxy') {
|
|
49
|
+
return {
|
|
50
|
+
title: 'Connect OpenAI account',
|
|
51
|
+
body: [
|
|
52
|
+
'Yagr will open your browser to sign you in with your ChatGPT account.',
|
|
53
|
+
'This uses your ChatGPT subscription — no API credits are consumed.',
|
|
54
|
+
],
|
|
55
|
+
continueLabel: 'Sign in with ChatGPT',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (provider === 'anthropic-proxy') {
|
|
59
|
+
return {
|
|
60
|
+
title: 'Connect Claude token',
|
|
61
|
+
body: [
|
|
62
|
+
'Generate a setup-token on a machine where Claude CLI is installed and logged in:',
|
|
63
|
+
'`claude setup-token`',
|
|
64
|
+
'Paste the generated setup-token below.',
|
|
65
|
+
],
|
|
66
|
+
continueLabel: 'Paste setup-token',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (provider === 'google-proxy') {
|
|
70
|
+
return {
|
|
71
|
+
title: 'Connect Gemini account',
|
|
72
|
+
body: [
|
|
73
|
+
'Yagr runs a native Google OAuth flow for your Gemini account.',
|
|
74
|
+
'It will show a browser URL and ask you to paste the redirect URL back here.',
|
|
75
|
+
],
|
|
76
|
+
continueLabel: 'Continue with Gemini sign-in',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (provider === 'copilot-proxy') {
|
|
80
|
+
return {
|
|
81
|
+
title: 'Connect GitHub Copilot account',
|
|
82
|
+
body: [
|
|
83
|
+
'Yagr runs a native GitHub device login and exchanges it for a Copilot runtime token.',
|
|
84
|
+
'It will show a verification URL and code in the terminal.',
|
|
85
|
+
],
|
|
86
|
+
continueLabel: 'Continue with GitHub sign-in',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
title: `Connect ${getProviderDisplayName(provider)}`,
|
|
91
|
+
body: ['Yagr will verify your account session before loading models.'],
|
|
92
|
+
continueLabel: 'Continue',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
37
95
|
// ─── Primitive UI components ──────────────────────────────────────────────────
|
|
38
96
|
function Rule() {
|
|
39
97
|
return _jsx(Text, { dimColor: true, children: '─'.repeat(56) });
|
|
40
98
|
}
|
|
41
|
-
function Header({ phase }) {
|
|
99
|
+
function Header({ phase, mode }) {
|
|
42
100
|
const section = sectionFor(phase);
|
|
43
101
|
const idx = sectionIndex(phase);
|
|
44
102
|
const isDone = phase.kind === 'done' || phase.kind === 'cancelled' || phase.kind === 'error';
|
|
45
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: "cyan", bold: true, children: "\u25C8 Yagr Setup" }), !isDone && (_jsx(Text, { dimColor: true, children:
|
|
103
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: "cyan", bold: true, children: "\u25C8 Yagr Setup" }), !isDone && (_jsx(Text, { dimColor: true, children: mode === 'llm-only'
|
|
104
|
+
? '● step 1 / 1'
|
|
105
|
+
: `${idx === 1 ? '●' : '○'}${idx === 2 ? '●' : '○'}${idx === 3 ? '●' : '○'} step ${idx} / 3` }))] }), section ? (_jsx(Text, { color: "cyan", dimColor: true, children: section })) : null, _jsx(Rule, {})] }));
|
|
46
106
|
}
|
|
47
107
|
function HintBar({ hints }) {
|
|
48
108
|
return (_jsxs(Box, { marginTop: 1, children: [_jsx(Rule, {}), _jsx(Box, { children: hints.map((hint, i) => (_jsxs(Text, { dimColor: true, children: [hint, i < hints.length - 1 ? ' ' : ''] }, i))) })] }));
|
|
@@ -166,11 +226,16 @@ function SelectList({ options, cursor, getLabel, getHint, maxVisibleRows, maxLin
|
|
|
166
226
|
const active = i === cursor;
|
|
167
227
|
const hint = getHint?.(opt);
|
|
168
228
|
const prefix = active ? ` ${CURSOR} ` : ' ';
|
|
169
|
-
const
|
|
170
|
-
const line = truncateTerminalLine(`${prefix}${getLabel(opt)}${suffix}`, availableWidth);
|
|
229
|
+
const line = truncateTerminalLine(formatOptionLineWithHint(prefix, getLabel(opt), hint, availableWidth), availableWidth);
|
|
171
230
|
return (_jsx(Box, { children: _jsx(Text, { color: active ? 'cyan' : undefined, bold: active, children: line }) }, i));
|
|
172
231
|
}), end < options.length ? _jsx(Text, { dimColor: true, children: truncateTerminalLine(` ↓ ${options.length - end} more`, availableWidth) }) : null] }));
|
|
173
232
|
}
|
|
233
|
+
function formatOptionLineWithHint(prefix, label, hint, width) {
|
|
234
|
+
if (!hint) {
|
|
235
|
+
return `${prefix}${label}`;
|
|
236
|
+
}
|
|
237
|
+
return `${prefix}${label} ${DOT} ${hint}`;
|
|
238
|
+
}
|
|
174
239
|
function MultiSelectList({ options, cursor, selected, maxVisibleRows, maxLineWidth, }) {
|
|
175
240
|
const visibleRows = Math.max(1, maxVisibleRows ?? options.length);
|
|
176
241
|
const { start, end } = getVisibleWindow(options.length, cursor, visibleRows);
|
|
@@ -187,21 +252,41 @@ function MultiSelectList({ options, cursor, selected, maxVisibleRows, maxLineWid
|
|
|
187
252
|
}), end < options.length ? _jsx(Text, { dimColor: true, children: truncateTerminalLine(` ↓ ${options.length - end} more`, availableWidth) }) : null] }));
|
|
188
253
|
}
|
|
189
254
|
// ─── Main wizard component ────────────────────────────────────────────────────
|
|
190
|
-
function SetupWizard({ callbacks, onDone }) {
|
|
255
|
+
function SetupWizard({ callbacks, options, onDone }) {
|
|
191
256
|
const app = useApp();
|
|
192
257
|
const { stdout } = useStdout();
|
|
193
258
|
const n8nDef = callbacks.getN8nDefaults();
|
|
194
259
|
const llmDef = callbacks.getLlmDefaults();
|
|
195
260
|
const surfDef = callbacks.getSurfaceDefaults();
|
|
196
|
-
const
|
|
197
|
-
const [
|
|
261
|
+
const mode = options?.mode ?? 'full';
|
|
262
|
+
const [phase, setPhase] = useState(() => {
|
|
263
|
+
if (mode === 'llm-only') {
|
|
264
|
+
const llmProvider = llmDef.provider;
|
|
265
|
+
if (llmProvider) {
|
|
266
|
+
const existingApiKey = llmDef.getApiKey(llmProvider);
|
|
267
|
+
const existingModel = llmDef.getDefaultModel(llmProvider);
|
|
268
|
+
if (existingModel && (existingApiKey || !providerRequiresApiKey(llmProvider))) {
|
|
269
|
+
return { kind: 'llm-reuse-config', provider: llmProvider, apiKey: existingApiKey ?? '', model: existingModel, cursor: 0 };
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
kind: 'llm-provider',
|
|
273
|
+
initial: llmProvider,
|
|
274
|
+
cursor: Math.max(0, VALID_PROVIDERS.indexOf(llmProvider)),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return { kind: 'llm-provider', cursor: 0 };
|
|
278
|
+
}
|
|
279
|
+
return { kind: 'n8n-mode', cursor: 0 };
|
|
280
|
+
});
|
|
281
|
+
const [textValue, setTextValue] = useState('');
|
|
198
282
|
const [spinnerFrame, setSpinnerFrame] = useState(0);
|
|
199
283
|
const asyncGuard = useRef(0);
|
|
200
284
|
const llmApiKeyDraftsRef = useRef({});
|
|
285
|
+
const llmBaseUrlDraftsRef = useRef({});
|
|
201
286
|
const terminalRows = stdout?.rows ?? process.stdout.rows ?? 24;
|
|
202
287
|
const terminalColumns = stdout?.columns ?? process.stdout.columns ?? 80;
|
|
203
288
|
const listLineWidth = Math.max(12, terminalColumns - 6);
|
|
204
|
-
const isLoading = phase.kind === 'n8n-connecting' || phase.kind === 'n8n-saving'
|
|
289
|
+
const isLoading = phase.kind === 'n8n-connecting' || phase.kind === 'n8n-saving' || phase.kind === 'n8n-local-installing'
|
|
205
290
|
|| phase.kind === 'llm-models-loading' || phase.kind === 'telegram-connecting';
|
|
206
291
|
useEffect(() => {
|
|
207
292
|
if (!isLoading)
|
|
@@ -218,6 +303,45 @@ function SetupWizard({ callbacks, onDone }) {
|
|
|
218
303
|
cancel('Setup cancelled.');
|
|
219
304
|
}
|
|
220
305
|
});
|
|
306
|
+
useEffect(() => {
|
|
307
|
+
if (phase.kind !== 'n8n-local-installing')
|
|
308
|
+
return;
|
|
309
|
+
const guard = ++asyncGuard.current;
|
|
310
|
+
void (async () => {
|
|
311
|
+
try {
|
|
312
|
+
const state = await callbacks.installManagedLocalN8n(phase.strategy);
|
|
313
|
+
if (guard !== asyncGuard.current)
|
|
314
|
+
return;
|
|
315
|
+
const bootstrap = await callbacks.bootstrapManagedLocalN8n(state.url);
|
|
316
|
+
if (guard !== asyncGuard.current)
|
|
317
|
+
return;
|
|
318
|
+
if (bootstrap.mode === 'silent' && bootstrap.apiKey) {
|
|
319
|
+
setPhase({ kind: 'n8n-connecting', url: state.url, apiKey: bootstrap.apiKey });
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const existing = callbacks.getN8nDefaults(state.url).apiKey;
|
|
323
|
+
if (existing) {
|
|
324
|
+
setPhase({ kind: 'n8n-connecting', url: state.url, apiKey: existing });
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
setPhase({
|
|
328
|
+
kind: 'n8n-local-ready',
|
|
329
|
+
url: state.url,
|
|
330
|
+
cursor: 0,
|
|
331
|
+
note: bootstrap.reason ? `Silent bootstrap fallback: ${bootstrap.reason}` : undefined,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
catch (err) {
|
|
335
|
+
if (guard !== asyncGuard.current)
|
|
336
|
+
return;
|
|
337
|
+
setPhase({
|
|
338
|
+
kind: 'n8n-mode',
|
|
339
|
+
cursor: phase.strategy === 'docker' ? 0 : 1,
|
|
340
|
+
err: err instanceof Error ? err.message : String(err),
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
})();
|
|
344
|
+
}, [phase.kind]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
221
345
|
useEffect(() => {
|
|
222
346
|
if (phase.kind !== 'n8n-connecting')
|
|
223
347
|
return;
|
|
@@ -261,8 +385,8 @@ function SetupWizard({ callbacks, onDone }) {
|
|
|
261
385
|
if (llmProvider) {
|
|
262
386
|
const existingApiKey = llmDef.getApiKey(llmProvider);
|
|
263
387
|
const existingModel = llmDef.getDefaultModel(llmProvider);
|
|
264
|
-
if (
|
|
265
|
-
setPhase({ kind: 'llm-reuse-config', provider: llmProvider, apiKey: existingApiKey, model: existingModel, cursor: 0 });
|
|
388
|
+
if (existingModel && (existingApiKey || !providerRequiresApiKey(llmProvider))) {
|
|
389
|
+
setPhase({ kind: 'llm-reuse-config', provider: llmProvider, apiKey: existingApiKey ?? '', model: existingModel, cursor: 0 });
|
|
266
390
|
return;
|
|
267
391
|
}
|
|
268
392
|
}
|
|
@@ -282,16 +406,56 @@ function SetupWizard({ callbacks, onDone }) {
|
|
|
282
406
|
const guard = ++asyncGuard.current;
|
|
283
407
|
void (async () => {
|
|
284
408
|
try {
|
|
285
|
-
|
|
409
|
+
let models = [];
|
|
410
|
+
let resolvedApiKey = phase.apiKey;
|
|
411
|
+
let note = phase.note;
|
|
412
|
+
const prepared = await callbacks.prepareProvider(phase.provider, phase.apiKey || undefined);
|
|
413
|
+
if (guard !== asyncGuard.current)
|
|
414
|
+
return;
|
|
415
|
+
if (prepared.ready) {
|
|
416
|
+
if (prepared.baseUrl) {
|
|
417
|
+
llmBaseUrlDraftsRef.current[phase.provider] = prepared.baseUrl;
|
|
418
|
+
}
|
|
419
|
+
if (prepared.apiKey !== undefined) {
|
|
420
|
+
resolvedApiKey = prepared.apiKey;
|
|
421
|
+
llmApiKeyDraftsRef.current[phase.provider] = prepared.apiKey;
|
|
422
|
+
}
|
|
423
|
+
if (prepared.notes && prepared.notes.length > 0) {
|
|
424
|
+
note = prepared.notes.join(' ');
|
|
425
|
+
}
|
|
426
|
+
models = prepared.models ?? [];
|
|
427
|
+
}
|
|
428
|
+
else if (isOAuthAccountProvider(phase.provider)) {
|
|
429
|
+
const preparedError = prepared.error?.toLowerCase() ?? '';
|
|
430
|
+
if (preparedError.includes('insufficient authentication scopes') || preparedError.includes('http 403')) {
|
|
431
|
+
setTextValue('');
|
|
432
|
+
setPhase({ kind: 'llm-account-auth', provider: phase.provider, cursor: 0 });
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
setPhase({
|
|
436
|
+
kind: 'llm-model',
|
|
437
|
+
provider: phase.provider,
|
|
438
|
+
apiKey: phase.apiKey,
|
|
439
|
+
models: [],
|
|
440
|
+
defModel: phase.defModel,
|
|
441
|
+
cursor: 0,
|
|
442
|
+
note: prepared.error || note,
|
|
443
|
+
});
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (models.length === 0) {
|
|
447
|
+
models = await callbacks.fetchModels(phase.provider, resolvedApiKey || undefined);
|
|
448
|
+
}
|
|
286
449
|
if (guard !== asyncGuard.current)
|
|
287
450
|
return;
|
|
288
451
|
const displayedOptions = getDisplayedModelOptions(models);
|
|
289
452
|
const idx = phase.defModel ? displayedOptions.indexOf(phase.defModel) : -1;
|
|
290
453
|
setPhase({
|
|
291
454
|
kind: 'llm-model',
|
|
292
|
-
provider: phase.provider, apiKey:
|
|
455
|
+
provider: phase.provider, apiKey: resolvedApiKey,
|
|
293
456
|
models, defModel: phase.defModel,
|
|
294
457
|
cursor: idx >= 0 ? idx : 0,
|
|
458
|
+
note,
|
|
295
459
|
});
|
|
296
460
|
}
|
|
297
461
|
catch {
|
|
@@ -300,7 +464,7 @@ function SetupWizard({ callbacks, onDone }) {
|
|
|
300
464
|
setPhase({
|
|
301
465
|
kind: 'llm-model',
|
|
302
466
|
provider: phase.provider, apiKey: phase.apiKey,
|
|
303
|
-
models: [], defModel: phase.defModel, cursor: 0,
|
|
467
|
+
models: [], defModel: phase.defModel, cursor: 0, note: phase.note,
|
|
304
468
|
});
|
|
305
469
|
}
|
|
306
470
|
})();
|
|
@@ -368,16 +532,6 @@ function SetupWizard({ callbacks, onDone }) {
|
|
|
368
532
|
}
|
|
369
533
|
setPhase({ kind: 'n8n-saving', url, apiKey, project, syncFolder: folder });
|
|
370
534
|
}, []);
|
|
371
|
-
const handleLlmApiKeySubmit = useCallback((provider) => (value) => {
|
|
372
|
-
const key = value.trim();
|
|
373
|
-
if (!key) {
|
|
374
|
-
setPhase((p) => ({ ...p, err: 'API key is required.' }));
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
llmApiKeyDraftsRef.current[provider] = key;
|
|
378
|
-
const defModel = llmDef.getDefaultModel(provider);
|
|
379
|
-
setPhase({ kind: 'llm-models-loading', provider, apiKey: key, defModel });
|
|
380
|
-
}, [llmDef]);
|
|
381
535
|
const handleBaseUrlSubmit = useCallback((provider, apiKey, model) => (value) => {
|
|
382
536
|
const url = value.trim();
|
|
383
537
|
if (url) {
|
|
@@ -401,8 +555,118 @@ function SetupWizard({ callbacks, onDone }) {
|
|
|
401
555
|
}
|
|
402
556
|
setPhase({ kind: 'telegram-connecting', surfaces, token });
|
|
403
557
|
}, []);
|
|
558
|
+
const transitionToLlmModelsLoading = useCallback((provider, apiKey, defModel, note) => {
|
|
559
|
+
setTextValue('');
|
|
560
|
+
setPhase({
|
|
561
|
+
kind: 'llm-models-loading',
|
|
562
|
+
provider,
|
|
563
|
+
apiKey,
|
|
564
|
+
defModel,
|
|
565
|
+
...(note ? { note } : {}),
|
|
566
|
+
});
|
|
567
|
+
}, []);
|
|
568
|
+
const handleLlmApiKeySubmit = useCallback((provider) => (value) => {
|
|
569
|
+
const key = value.trim();
|
|
570
|
+
if (!key && providerRequiresApiKey(provider)) {
|
|
571
|
+
setPhase((p) => ({ ...p, err: 'API key is required.' }));
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
llmApiKeyDraftsRef.current[provider] = key;
|
|
575
|
+
const defModel = llmDef.getDefaultModel(provider);
|
|
576
|
+
transitionToLlmModelsLoading(provider, key, defModel);
|
|
577
|
+
}, [llmDef, transitionToLlmModelsLoading]);
|
|
578
|
+
const handleAccountAuthSubmit = useCallback((provider, state) => (value) => {
|
|
579
|
+
void (async () => {
|
|
580
|
+
try {
|
|
581
|
+
const result = await callbacks.completeAccountAuth(provider, value, state);
|
|
582
|
+
if (!result.ok) {
|
|
583
|
+
setPhase((current) => current.kind === 'llm-account-input'
|
|
584
|
+
? { ...current, err: result.error || 'Authentication failed.' }
|
|
585
|
+
: current);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const defModel = llmDef.getDefaultModel(provider);
|
|
589
|
+
transitionToLlmModelsLoading(provider, result.apiKey ?? '', defModel);
|
|
590
|
+
}
|
|
591
|
+
catch (error) {
|
|
592
|
+
setPhase((current) => current.kind === 'llm-account-input'
|
|
593
|
+
? { ...current, err: error instanceof Error ? error.message : String(error) }
|
|
594
|
+
: current);
|
|
595
|
+
}
|
|
596
|
+
})();
|
|
597
|
+
}, [callbacks, llmDef, transitionToLlmModelsLoading]);
|
|
598
|
+
const saveLlmAndContinue = useCallback((provider, apiKey, model, note) => {
|
|
599
|
+
const draftedBaseUrl = llmBaseUrlDraftsRef.current[provider];
|
|
600
|
+
callbacks.saveLlmConfig({ provider, apiKey, model, baseUrl: draftedBaseUrl || undefined });
|
|
601
|
+
setTextValue('');
|
|
602
|
+
if (mode === 'llm-only') {
|
|
603
|
+
setPhase({ kind: 'done', n8nHost: '', n8nProject: '', provider, model, surfaces: surfDef.surfaces });
|
|
604
|
+
setTimeout(() => { onDone({ ok: true }); app.exit(); }, 250);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
setPhase({ kind: 'surfaces', cursor: 0, selected: surfDef.surfaces });
|
|
608
|
+
}, [app, callbacks, mode, onDone, surfDef]);
|
|
404
609
|
const handleSelectKey = useCallback((input, key) => {
|
|
405
|
-
if (phase.kind === 'n8n-
|
|
610
|
+
if (phase.kind === 'n8n-mode') {
|
|
611
|
+
if (key.upArrow)
|
|
612
|
+
setPhase({ ...phase, cursor: Math.max(0, phase.cursor - 1), err: undefined });
|
|
613
|
+
else if (key.downArrow)
|
|
614
|
+
setPhase({ ...phase, cursor: Math.min(2, phase.cursor + 1), err: undefined });
|
|
615
|
+
else if (key.return) {
|
|
616
|
+
if (phase.cursor === 0) {
|
|
617
|
+
setPhase({ kind: 'n8n-local-installing', startedAt: Date.now(), strategy: 'docker' });
|
|
618
|
+
}
|
|
619
|
+
else if (phase.cursor === 1) {
|
|
620
|
+
setPhase({ kind: 'n8n-local-installing', startedAt: Date.now(), strategy: 'direct' });
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
setPhase({ kind: 'n8n-url', def: n8nDef.url });
|
|
624
|
+
setTextValue(n8nDef.url);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
else if (key.escape)
|
|
628
|
+
cancel('Setup cancelled.');
|
|
629
|
+
}
|
|
630
|
+
else if (phase.kind === 'n8n-local-ready') {
|
|
631
|
+
if (key.upArrow)
|
|
632
|
+
setPhase({ ...phase, cursor: Math.max(0, phase.cursor - 1) });
|
|
633
|
+
else if (key.downArrow)
|
|
634
|
+
setPhase({ ...phase, cursor: Math.min(1, phase.cursor + 1) });
|
|
635
|
+
else if (key.return) {
|
|
636
|
+
if (phase.cursor === 0) {
|
|
637
|
+
void (async () => {
|
|
638
|
+
try {
|
|
639
|
+
await callbacks.openUrl(phase.url);
|
|
640
|
+
}
|
|
641
|
+
catch {
|
|
642
|
+
// Leave the flow available even if browser opening fails.
|
|
643
|
+
}
|
|
644
|
+
const existing = callbacks.getN8nDefaults(phase.url).apiKey;
|
|
645
|
+
if (existing) {
|
|
646
|
+
setPhase({ kind: 'n8n-connecting', url: phase.url, apiKey: existing });
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
setPhase({
|
|
650
|
+
kind: 'n8n-local-auth',
|
|
651
|
+
url: phase.url,
|
|
652
|
+
message: 'The local n8n editor is ready. Create the owner account, then open Settings > n8n API, generate a key for Yagr, and paste it here.',
|
|
653
|
+
});
|
|
654
|
+
setTextValue('');
|
|
655
|
+
})();
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
setPhase({
|
|
659
|
+
kind: 'n8n-local-auth',
|
|
660
|
+
url: phase.url,
|
|
661
|
+
message: 'Open the local n8n URL in your browser, create the owner account, then generate a key in Settings > n8n API and paste it here.',
|
|
662
|
+
});
|
|
663
|
+
setTextValue('');
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
else if (key.escape)
|
|
667
|
+
cancel('Setup cancelled.');
|
|
668
|
+
}
|
|
669
|
+
else if (phase.kind === 'n8n-reuse-apikey') {
|
|
406
670
|
if (key.upArrow)
|
|
407
671
|
setPhase({ ...phase, cursor: Math.max(0, phase.cursor - 1) });
|
|
408
672
|
else if (key.downArrow)
|
|
@@ -440,10 +704,13 @@ function SetupWizard({ callbacks, onDone }) {
|
|
|
440
704
|
else if (key.return) {
|
|
441
705
|
if (phase.cursor === 0) {
|
|
442
706
|
llmApiKeyDraftsRef.current[phase.provider] = phase.apiKey;
|
|
443
|
-
|
|
707
|
+
const draftedBaseUrl = llmBaseUrlDraftsRef.current[phase.provider] ?? llmDef.getBaseUrl(phase.provider);
|
|
708
|
+
callbacks.saveLlmConfig({ provider: phase.provider, apiKey: phase.apiKey, model: phase.model, baseUrl: draftedBaseUrl });
|
|
709
|
+
setTextValue('');
|
|
444
710
|
setPhase({ kind: 'surfaces', cursor: 0, selected: surfDef.surfaces });
|
|
445
711
|
}
|
|
446
712
|
else {
|
|
713
|
+
setTextValue('');
|
|
447
714
|
setPhase({ kind: 'llm-provider', initial: phase.provider, cursor: VALID_PROVIDERS.indexOf(phase.provider) });
|
|
448
715
|
}
|
|
449
716
|
}
|
|
@@ -458,7 +725,16 @@ function SetupWizard({ callbacks, onDone }) {
|
|
|
458
725
|
else if (key.return) {
|
|
459
726
|
const provider = VALID_PROVIDERS[phase.cursor];
|
|
460
727
|
const existing = llmApiKeyDraftsRef.current[provider] ?? llmDef.getApiKey(provider);
|
|
461
|
-
if (
|
|
728
|
+
if (isOAuthAccountProvider(provider)) {
|
|
729
|
+
setTextValue('');
|
|
730
|
+
setPhase({ kind: 'llm-account-auth', provider, cursor: 0 });
|
|
731
|
+
}
|
|
732
|
+
else if (!providerRequiresApiKey(provider)) {
|
|
733
|
+
const defModel = llmDef.getDefaultModel(provider);
|
|
734
|
+
transitionToLlmModelsLoading(provider, existing ?? '', defModel);
|
|
735
|
+
}
|
|
736
|
+
else if (existing) {
|
|
737
|
+
setTextValue('');
|
|
462
738
|
setPhase({ kind: 'llm-reuse-apikey', provider, existing, cursor: 0 });
|
|
463
739
|
}
|
|
464
740
|
else {
|
|
@@ -469,6 +745,81 @@ function SetupWizard({ callbacks, onDone }) {
|
|
|
469
745
|
else if (key.escape)
|
|
470
746
|
cancel('Setup cancelled.');
|
|
471
747
|
}
|
|
748
|
+
else if (phase.kind === 'llm-account-auth') {
|
|
749
|
+
const maxCursor = 1;
|
|
750
|
+
if (key.upArrow)
|
|
751
|
+
setPhase({ ...phase, cursor: Math.max(0, phase.cursor - 1) });
|
|
752
|
+
else if (key.downArrow)
|
|
753
|
+
setPhase({ ...phase, cursor: Math.min(maxCursor, phase.cursor + 1) });
|
|
754
|
+
else if (key.return) {
|
|
755
|
+
if (phase.provider === 'anthropic-proxy' && phase.cursor === 0) {
|
|
756
|
+
setPhase({
|
|
757
|
+
kind: 'llm-account-input',
|
|
758
|
+
provider: 'anthropic-proxy',
|
|
759
|
+
title: 'Claude setup-token',
|
|
760
|
+
instructions: [
|
|
761
|
+
'On a machine where Claude CLI is installed and logged in, run:',
|
|
762
|
+
'claude setup-token',
|
|
763
|
+
'Copy the generated setup-token and paste it below.',
|
|
764
|
+
],
|
|
765
|
+
placeholder: 'Paste setup-token',
|
|
766
|
+
submitLabel: 'Continue with setup-token',
|
|
767
|
+
state: 'anthropic:setup-token',
|
|
768
|
+
});
|
|
769
|
+
setTextValue('');
|
|
770
|
+
}
|
|
771
|
+
else if (phase.cursor === 0) {
|
|
772
|
+
void (async () => {
|
|
773
|
+
try {
|
|
774
|
+
const authStep = await callbacks.startAccountAuth(phase.provider);
|
|
775
|
+
const authUrl = (authStep.instructions ?? [])
|
|
776
|
+
.map((line) => line.match(/https?:\/\/\S+/)?.[0])
|
|
777
|
+
.find(Boolean);
|
|
778
|
+
if (authUrl) {
|
|
779
|
+
void callbacks.openUrl(authUrl).catch(() => {
|
|
780
|
+
// Browser auto-open is best effort only.
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
if (authStep.kind === 'input') {
|
|
784
|
+
setPhase({
|
|
785
|
+
kind: 'llm-account-input',
|
|
786
|
+
provider: phase.provider,
|
|
787
|
+
title: authStep.title ?? getProviderAuthCopy(phase.provider).title,
|
|
788
|
+
instructions: authStep.instructions ?? [],
|
|
789
|
+
placeholder: authStep.placeholder,
|
|
790
|
+
submitLabel: authStep.submitLabel ?? 'Continue',
|
|
791
|
+
state: authStep.state,
|
|
792
|
+
});
|
|
793
|
+
setTextValue('');
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
const defModel = llmDef.getDefaultModel(phase.provider);
|
|
797
|
+
const authCopy = getProviderAuthCopy(phase.provider);
|
|
798
|
+
transitionToLlmModelsLoading(phase.provider, '', defModel, authCopy.body.join(' '));
|
|
799
|
+
}
|
|
800
|
+
catch (error) {
|
|
801
|
+
setPhase({
|
|
802
|
+
kind: 'llm-account-input',
|
|
803
|
+
provider: phase.provider,
|
|
804
|
+
title: getProviderAuthCopy(phase.provider).title,
|
|
805
|
+
instructions: getProviderAuthCopy(phase.provider).body,
|
|
806
|
+
submitLabel: 'Continue',
|
|
807
|
+
err: error instanceof Error ? error.message : String(error),
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
})();
|
|
811
|
+
}
|
|
812
|
+
else {
|
|
813
|
+
setPhase({ kind: 'llm-provider', initial: phase.provider, cursor: VALID_PROVIDERS.indexOf(phase.provider) });
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
else if (key.escape)
|
|
817
|
+
cancel('Setup cancelled.');
|
|
818
|
+
}
|
|
819
|
+
else if (phase.kind === 'llm-account-input') {
|
|
820
|
+
if (key.escape)
|
|
821
|
+
cancel('Setup cancelled.');
|
|
822
|
+
}
|
|
472
823
|
else if (phase.kind === 'llm-reuse-apikey') {
|
|
473
824
|
if (key.upArrow)
|
|
474
825
|
setPhase({ ...phase, cursor: Math.max(0, phase.cursor - 1) });
|
|
@@ -478,7 +829,7 @@ function SetupWizard({ callbacks, onDone }) {
|
|
|
478
829
|
if (phase.cursor === 0) {
|
|
479
830
|
const defModel = llmDef.getDefaultModel(phase.provider);
|
|
480
831
|
llmApiKeyDraftsRef.current[phase.provider] = phase.existing;
|
|
481
|
-
|
|
832
|
+
transitionToLlmModelsLoading(phase.provider, phase.existing, defModel);
|
|
482
833
|
}
|
|
483
834
|
else {
|
|
484
835
|
setPhase({ kind: 'llm-apikey', provider: phase.provider });
|
|
@@ -502,14 +853,18 @@ function SetupWizard({ callbacks, onDone }) {
|
|
|
502
853
|
return;
|
|
503
854
|
}
|
|
504
855
|
const model = selected;
|
|
856
|
+
const draftedBaseUrl = llmBaseUrlDraftsRef.current[phase.provider];
|
|
857
|
+
const defaultBaseUrl = draftedBaseUrl ?? llmDef.getBaseUrl(phase.provider);
|
|
505
858
|
const needsUrl = llmDef.needsBaseUrl(phase.provider);
|
|
506
|
-
if (
|
|
507
|
-
|
|
508
|
-
|
|
859
|
+
if (draftedBaseUrl) {
|
|
860
|
+
saveLlmAndContinue(phase.provider, phase.apiKey, model, phase.note);
|
|
861
|
+
}
|
|
862
|
+
else if (needsUrl || defaultBaseUrl) {
|
|
863
|
+
setPhase({ kind: 'llm-baseurl', provider: phase.provider, apiKey: phase.apiKey, model, def: defaultBaseUrl ?? '' });
|
|
864
|
+
setTextValue(defaultBaseUrl ?? '');
|
|
509
865
|
}
|
|
510
866
|
else {
|
|
511
|
-
|
|
512
|
-
setPhase({ kind: 'surfaces', cursor: 0, selected: surfDef.surfaces });
|
|
867
|
+
saveLlmAndContinue(phase.provider, phase.apiKey, model, phase.note);
|
|
513
868
|
}
|
|
514
869
|
}
|
|
515
870
|
else if (key.escape)
|
|
@@ -569,7 +924,7 @@ function SetupWizard({ callbacks, onDone }) {
|
|
|
569
924
|
cancel('Setup cancelled.');
|
|
570
925
|
}
|
|
571
926
|
}, [phase, cancel, callbacks, llmDef, surfDef, n8nDef.syncFolder, app, onDone]);
|
|
572
|
-
const isSelectPhase = ['n8n-reuse-apikey', 'n8n-project', 'llm-provider', 'llm-reuse-config', 'llm-reuse-apikey', 'surfaces', 'telegram-reuse-token'].includes(phase.kind)
|
|
927
|
+
const isSelectPhase = ['n8n-mode', 'n8n-local-ready', 'n8n-reuse-apikey', 'n8n-project', 'llm-provider', 'llm-account-auth', 'llm-reuse-config', 'llm-reuse-apikey', 'surfaces', 'telegram-reuse-token'].includes(phase.kind)
|
|
573
928
|
|| (phase.kind === 'llm-model' && phase.models.length > 0);
|
|
574
929
|
useInput((input, key) => {
|
|
575
930
|
if (key.ctrl && input === 'c')
|
|
@@ -577,9 +932,23 @@ function SetupWizard({ callbacks, onDone }) {
|
|
|
577
932
|
if (isSelectPhase)
|
|
578
933
|
handleSelectKey(input, key);
|
|
579
934
|
}, { isActive: isSelectPhase });
|
|
580
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsx(Header, { phase: phase }), renderPhase()] }));
|
|
935
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsx(Header, { phase: phase, mode: mode }), renderPhase()] }));
|
|
581
936
|
function renderPhase() {
|
|
582
937
|
switch (phase.kind) {
|
|
938
|
+
case 'n8n-mode':
|
|
939
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: "n8n setup path" }), _jsx(Text, { dimColor: true, children: " Choose the lowest-friction way to connect Yagr to n8n." }), _jsx(SelectList, { options: [
|
|
940
|
+
'Install a Yagr-managed n8n with Docker',
|
|
941
|
+
'Install a Yagr-managed local n8n',
|
|
942
|
+
'Use an existing n8n instance and API key',
|
|
943
|
+
], cursor: phase.cursor, getLabel: (v) => v, getHint: (v) => {
|
|
944
|
+
if (v === 'Install a Yagr-managed n8n with Docker') {
|
|
945
|
+
return 'Recommended if Docker is installed and running';
|
|
946
|
+
}
|
|
947
|
+
if (v === 'Install a Yagr-managed local n8n') {
|
|
948
|
+
return 'Managed local runtime without Docker';
|
|
949
|
+
}
|
|
950
|
+
return 'Cloud or self-managed n8n';
|
|
951
|
+
}, maxVisibleRows: getListViewportHeight(terminalRows, 13), maxLineWidth: listLineWidth }), phase.err ? _jsx(ErrorLine, { message: phase.err }) : null, _jsx(HintBar, { hints: ['↑↓ move', 'Enter ↵ confirm', 'Ctrl+C cancel'] })] }));
|
|
583
952
|
case 'n8n-url':
|
|
584
953
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: "Instance URL" }), _jsx(Box, { marginLeft: 2, children: _jsx(WizardTextInput, { value: textValue, onChange: setTextValue, onSubmit: handleN8nUrlSubmit, placeholder: "https://your-n8n.example.com" }) }), phase.err ? _jsx(ErrorLine, { message: phase.err }) : null, _jsx(HintBar, { hints: ['Enter ↵ confirm', 'Ctrl+C cancel'] })] }));
|
|
585
954
|
case 'n8n-reuse-apikey':
|
|
@@ -588,6 +957,18 @@ function SetupWizard({ callbacks, onDone }) {
|
|
|
588
957
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: "n8n API key" }), _jsx(Box, { marginLeft: 2, children: _jsx(WizardTextInput, { value: textValue, onChange: setTextValue, onSubmit: handleN8nApiKeySubmit(phase.url), mask: "\u25CF", placeholder: "your-api-key" }) }), phase.err ? _jsx(ErrorLine, { message: phase.err }) : null, _jsx(HintBar, { hints: ['Enter ↵ confirm', 'Ctrl+C cancel'] })] }));
|
|
589
958
|
case 'n8n-connecting':
|
|
590
959
|
return (_jsx(Box, { flexDirection: "column", children: _jsx(SpinnerDisplay, { message: `Connecting to ${phase.url}…`, frame: spinnerFrame }) }));
|
|
960
|
+
case 'n8n-local-installing':
|
|
961
|
+
{
|
|
962
|
+
const elapsedSeconds = Math.max(0, Math.round((Date.now() - phase.startedAt) / 1000));
|
|
963
|
+
const elapsedLabel = elapsedSeconds < 60
|
|
964
|
+
? `${elapsedSeconds}s`
|
|
965
|
+
: `${Math.floor(elapsedSeconds / 60)}m ${String(elapsedSeconds % 60).padStart(2, '0')}s`;
|
|
966
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(SpinnerDisplay, { message: "Installing and starting a Yagr-managed local n8n instance\u2026", frame: spinnerFrame }), _jsx(Text, { dimColor: true, children: " Yagr is waiting for the n8n API and editor to become ready before continuing." }), _jsx(Text, { dimColor: true, children: " First run can take 1 to 3 minutes depending on Docker, npm downloads, and machine speed." }), _jsxs(Text, { dimColor: true, children: [" Elapsed: ", elapsedLabel] })] }));
|
|
967
|
+
}
|
|
968
|
+
case 'n8n-local-ready':
|
|
969
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: "Local n8n is ready" }), _jsxs(Text, { dimColor: true, children: [" Instance URL: ", phase.url] }), _jsx(Text, { dimColor: true, children: " Yagr waited for the n8n editor to finish starting before showing this step." }), _jsx(Text, { dimColor: true, children: " Yagr attempted a silent owner/API bootstrap first." }), phase.note ? _jsxs(Text, { dimColor: true, children: [" ", phase.note] }) : null, _jsx(Text, { dimColor: true, children: " You will first create the owner account, then create an API key for Yagr." }), _jsx(SelectList, { options: ['Open n8n in the browser', 'I will open it myself'], cursor: phase.cursor, getLabel: (v) => v, getHint: (v) => v.startsWith('Open') ? 'recommended' : phase.url, maxVisibleRows: getListViewportHeight(terminalRows, 12), maxLineWidth: listLineWidth }), _jsx(HintBar, { hints: ['↑↓ move', 'Enter ↵ confirm', 'Ctrl+C cancel'] })] }));
|
|
970
|
+
case 'n8n-local-auth':
|
|
971
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: "Local n8n API key" }), _jsxs(Text, { dimColor: true, children: [" Local instance URL: ", phase.url] }), _jsxs(Text, { dimColor: true, children: [" ", phase.message] }), _jsx(Text, { dimColor: true, children: " If the browser is not already open, run `yagr n8n local open` or open the URL manually." }), _jsx(Box, { marginLeft: 2, marginTop: 1, children: _jsx(WizardTextInput, { value: textValue, onChange: setTextValue, onSubmit: handleN8nApiKeySubmit(phase.url), mask: "\u25CF", placeholder: "Paste the new n8n API key" }) }), _jsx(HintBar, { hints: ['Enter ↵ confirm', 'Ctrl+C cancel'] })] }));
|
|
591
972
|
case 'n8n-project':
|
|
592
973
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: "n8n project" }), _jsxs(Text, { dimColor: true, children: [" ", phase.projects.length, " project(s) found"] }), _jsx(SelectList, { options: phase.projects, cursor: phase.cursor, getLabel: (p) => p.name ?? p.id, getHint: (p) => p.id, maxVisibleRows: getListViewportHeight(terminalRows, 11), maxLineWidth: listLineWidth }), _jsx(HintBar, { hints: ['↑↓ move', 'Enter ↵ select', 'Ctrl+C cancel'] })] }));
|
|
593
974
|
case 'n8n-syncfolder':
|
|
@@ -595,37 +976,63 @@ function SetupWizard({ callbacks, onDone }) {
|
|
|
595
976
|
case 'n8n-saving':
|
|
596
977
|
return (_jsx(Box, { flexDirection: "column", children: _jsx(SpinnerDisplay, { message: "Saving n8n configuration and refreshing workspace\u2026", frame: spinnerFrame }) }));
|
|
597
978
|
case 'llm-reuse-config':
|
|
598
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: "LLM configuration" }), _jsxs(Text, { dimColor: true, children: [" Currently configured: ", phase.provider, " / ", phase.model] }), _jsx(SelectList, { options: ['Keep current configuration', 'Change provider or model'], cursor: phase.cursor, getLabel: (v) => v, maxVisibleRows: getListViewportHeight(terminalRows, 11), maxLineWidth: listLineWidth }), _jsx(HintBar, { hints: ['↑↓ move', 'Enter ↵ confirm', 'Ctrl+C cancel'] })] }));
|
|
979
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: "LLM configuration" }), _jsxs(Text, { dimColor: true, children: [" Currently configured: ", getProviderDisplayName(phase.provider), " / ", phase.model] }), _jsx(SelectList, { options: ['Keep current configuration', 'Change provider or model'], cursor: phase.cursor, getLabel: (v) => v, maxVisibleRows: getListViewportHeight(terminalRows, 11), maxLineWidth: listLineWidth }), _jsx(HintBar, { hints: ['↑↓ move', 'Enter ↵ confirm', 'Ctrl+C cancel'] })] }));
|
|
599
980
|
case 'llm-provider':
|
|
600
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: "Default LLM provider" }), _jsx(SelectList, { options: VALID_PROVIDERS, cursor: phase.cursor, getLabel: (v) => v, getHint: (v) =>
|
|
981
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: "Default LLM provider" }), _jsx(SelectList, { options: VALID_PROVIDERS, cursor: phase.cursor, getLabel: (v) => getProviderDisplayName(v), getHint: (v) => {
|
|
982
|
+
const parts = [
|
|
983
|
+
getProviderSetupHint(v),
|
|
984
|
+
isExperimentalProvider(v) ? 'experimental' : undefined,
|
|
985
|
+
v === phase.initial ? 'currently configured' : undefined,
|
|
986
|
+
].filter(Boolean);
|
|
987
|
+
return parts.length > 0 ? parts.join(' · ') : undefined;
|
|
988
|
+
}, maxVisibleRows: getListViewportHeight(terminalRows, 10), maxLineWidth: listLineWidth }), _jsx(HintBar, { hints: ['↑↓ move', 'Enter ↵ select', 'Ctrl+C cancel'] })] }));
|
|
989
|
+
case 'llm-account-auth':
|
|
990
|
+
{
|
|
991
|
+
const authCopy = getProviderAuthCopy(phase.provider);
|
|
992
|
+
const authOptions = [authCopy.continueLabel, 'Back to providers'];
|
|
993
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: authCopy.title }), authCopy.body.map((line) => (_jsxs(Text, { dimColor: true, children: [" ", line] }, line))), _jsx(SelectList, { options: authOptions, cursor: phase.cursor, getLabel: (v) => v, getHint: (v) => {
|
|
994
|
+
return (v.startsWith('Continue') || v.startsWith('Paste')) ? 'recommended' : undefined;
|
|
995
|
+
}, maxVisibleRows: getListViewportHeight(terminalRows, 11), maxLineWidth: listLineWidth }), _jsx(HintBar, { hints: ['↑↓ move', 'Enter ↵ confirm', 'Ctrl+C cancel'] })] }));
|
|
996
|
+
}
|
|
997
|
+
case 'llm-account-input':
|
|
998
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: phase.title }), phase.instructions.map((line, index) => (_jsxs(Text, { dimColor: true, children: [" ", line] }, `${index}-${line}`))), _jsx(Box, { marginLeft: 2, marginTop: 1, children: _jsx(WizardTextInput, { value: textValue, onChange: setTextValue, onSubmit: handleAccountAuthSubmit(phase.provider, phase.state), placeholder: phase.placeholder }) }), phase.err ? _jsx(ErrorLine, { message: phase.err }) : null, _jsx(HintBar, { hints: [`${phase.submitLabel} (Enter ↵)`, 'Ctrl+C cancel'] })] }));
|
|
601
999
|
case 'llm-reuse-apikey':
|
|
602
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: `${phase.provider} API key` }), _jsxs(Text, { dimColor: true, children: [" A saved key exists for ", phase.provider] }), _jsx(SelectList, { options: ['Keep the saved key', 'Enter a new key'], cursor: phase.cursor, getLabel: (v) => v, maxVisibleRows: getListViewportHeight(terminalRows, 11), maxLineWidth: listLineWidth }), _jsx(HintBar, { hints: ['↑↓ move', 'Enter ↵ confirm', 'Ctrl+C cancel'] })] }));
|
|
1000
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: `${getProviderDisplayName(phase.provider)} API key` }), _jsxs(Text, { dimColor: true, children: [" A saved key exists for ", getProviderDisplayName(phase.provider)] }), _jsx(SelectList, { options: ['Keep the saved key', 'Enter a new key'], cursor: phase.cursor, getLabel: (v) => v, maxVisibleRows: getListViewportHeight(terminalRows, 11), maxLineWidth: listLineWidth }), _jsx(HintBar, { hints: ['↑↓ move', 'Enter ↵ confirm', 'Ctrl+C cancel'] })] }));
|
|
603
1001
|
case 'llm-apikey':
|
|
604
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: `${phase.provider} API key` }), _jsx(Box, { marginLeft: 2, children: _jsx(WizardTextInput, { value: textValue, onChange: (nextValue) => {
|
|
1002
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: `${getProviderDisplayName(phase.provider)} API key` }), !providerRequiresApiKey(phase.provider) ? (_jsx(Text, { dimColor: true, children: " Optional for local proxy providers. Leave empty if your proxy handles account auth." })) : null, _jsx(Box, { marginLeft: 2, children: _jsx(WizardTextInput, { value: textValue, onChange: (nextValue) => {
|
|
605
1003
|
setTextValue(nextValue);
|
|
606
1004
|
llmApiKeyDraftsRef.current[phase.provider] = nextValue;
|
|
607
|
-
}, onSubmit: handleLlmApiKeySubmit(phase.provider), mask: "\u25CF", placeholder:
|
|
1005
|
+
}, onSubmit: handleLlmApiKeySubmit(phase.provider), mask: "\u25CF", placeholder: providerRequiresApiKey(phase.provider) ? 'your-api-key' : 'optional' }) }), phase.err ? _jsx(ErrorLine, { message: phase.err }) : null, _jsx(HintBar, { hints: ['Enter ↵ confirm', 'Ctrl+C cancel'] })] }));
|
|
608
1006
|
case 'llm-models-loading':
|
|
609
|
-
|
|
1007
|
+
const loadingMessage = isOAuthAccountProvider(phase.provider)
|
|
1008
|
+
? `Finalizing ${getProviderDisplayName(phase.provider)} account and fetching models…`
|
|
1009
|
+
: `Fetching available models for ${getProviderDisplayName(phase.provider)}…`;
|
|
1010
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(SpinnerDisplay, { message: loadingMessage, frame: spinnerFrame }), phase.note ? _jsxs(Text, { dimColor: true, children: [" ", phase.note] }) : null] }));
|
|
610
1011
|
case 'llm-model': {
|
|
611
1012
|
const modelOptions = getDisplayedModelOptions(phase.models);
|
|
612
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: `Default model · ${phase.provider}` }), phase.models.length === 0 ? (_jsx(Box, { marginLeft: 2, children: _jsx(WizardTextInput, { value: textValue, onChange: setTextValue, onSubmit: (v) => {
|
|
1013
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: `Default model · ${getProviderDisplayName(phase.provider)}` }), phase.note ? _jsxs(Text, { dimColor: true, children: [" ", phase.note] }) : null, phase.models.length === 0 ? (_jsx(Box, { marginLeft: 2, children: _jsx(WizardTextInput, { value: textValue, onChange: setTextValue, onSubmit: (v) => {
|
|
613
1014
|
const m = v.trim() || phase.defModel || '';
|
|
614
1015
|
if (!m)
|
|
615
1016
|
return;
|
|
1017
|
+
const draftedBaseUrl = llmBaseUrlDraftsRef.current[phase.provider];
|
|
1018
|
+
const defaultBaseUrl = draftedBaseUrl ?? llmDef.getBaseUrl(phase.provider);
|
|
616
1019
|
const needsUrl = llmDef.needsBaseUrl(phase.provider);
|
|
617
|
-
if (
|
|
618
|
-
|
|
619
|
-
|
|
1020
|
+
if (draftedBaseUrl) {
|
|
1021
|
+
saveLlmAndContinue(phase.provider, phase.apiKey, m, phase.note);
|
|
1022
|
+
}
|
|
1023
|
+
else if (needsUrl || defaultBaseUrl) {
|
|
1024
|
+
setPhase({ kind: 'llm-baseurl', provider: phase.provider, apiKey: phase.apiKey, model: m, def: defaultBaseUrl ?? '' });
|
|
1025
|
+
setTextValue(defaultBaseUrl ?? '');
|
|
620
1026
|
}
|
|
621
1027
|
else {
|
|
622
|
-
|
|
623
|
-
setPhase({ kind: 'surfaces', cursor: 0, selected: surfDef.surfaces });
|
|
1028
|
+
saveLlmAndContinue(phase.provider, phase.apiKey, m, phase.note);
|
|
624
1029
|
}
|
|
625
|
-
}, placeholder: phase.defModel }) })) : (_jsx(SelectList, { options: modelOptions, cursor: phase.cursor, getLabel: (v) => v === '__custom__' ? '⌨ enter manually…' : v, getHint: (v) => (phase.defModel && v === phase.defModel) ? 'previously selected' : undefined, maxVisibleRows: getListViewportHeight(terminalRows, 10), maxLineWidth: listLineWidth })), _jsx(HintBar, { hints: phase.models.length > 0
|
|
1030
|
+
}, placeholder: phase.defModel }) })) : (_jsx(SelectList, { options: modelOptions, cursor: phase.cursor, getLabel: (v) => v === '__custom__' ? '⌨ enter manually…' : v, getHint: (v) => (phase.defModel && v === phase.defModel) ? 'previously selected' : undefined, maxVisibleRows: getListViewportHeight(terminalRows, 10), maxLineWidth: listLineWidth })), _jsx(HintBar, { hints: phase.models.length > 0
|
|
1031
|
+
? ['↑↓ move', 'Enter ↵ select', 'Ctrl+C cancel']
|
|
1032
|
+
: ['Enter ↵ confirm', 'Ctrl+C cancel'] })] }));
|
|
626
1033
|
}
|
|
627
1034
|
case 'llm-baseurl':
|
|
628
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: `${phase.provider} base URL` }), _jsx(Text, { dimColor: true, children: " Leave empty to use the default endpoint" }), _jsx(Box, { marginLeft: 2, children: _jsx(WizardTextInput, { value: textValue, onChange: setTextValue, onSubmit: handleBaseUrlSubmit(phase.provider, phase.apiKey, phase.model), placeholder: phase.def || 'https://api.example.com/v1' }) }), phase.err ? _jsx(ErrorLine, { message: phase.err }) : null, _jsx(HintBar, { hints: ['Enter ↵ confirm (empty = default)', 'Ctrl+C cancel'] })] }));
|
|
1035
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: `${getProviderDisplayName(phase.provider)} base URL` }), _jsx(Text, { dimColor: true, children: " Leave empty to use the default endpoint" }), _jsx(Box, { marginLeft: 2, children: _jsx(WizardTextInput, { value: textValue, onChange: setTextValue, onSubmit: handleBaseUrlSubmit(phase.provider, phase.apiKey, phase.model), placeholder: phase.def || 'https://api.example.com/v1' }) }), phase.err ? _jsx(ErrorLine, { message: phase.err }) : null, _jsx(HintBar, { hints: ['Enter ↵ confirm (empty = default)', 'Ctrl+C cancel'] })] }));
|
|
629
1036
|
case 'surfaces':
|
|
630
1037
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(FieldLabel, { label: "Optional messaging gateways" }), _jsx(Text, { dimColor: true, children: " These enable Yagr to receive work from external channels" }), _jsx(Box, { marginTop: 1, children: _jsx(MultiSelectList, { options: SURFACE_OPTIONS, cursor: phase.cursor, selected: phase.selected, maxVisibleRows: getListViewportHeight(terminalRows, 11), maxLineWidth: listLineWidth }) }), _jsx(HintBar, { hints: ['↑↓ move', 'Space toggle', 'Enter ↵ confirm', 'Ctrl+C cancel'] })] }));
|
|
631
1038
|
case 'telegram-reuse-token':
|
|
@@ -635,7 +1042,7 @@ function SetupWizard({ callbacks, onDone }) {
|
|
|
635
1042
|
case 'telegram-connecting':
|
|
636
1043
|
return (_jsx(Box, { flexDirection: "column", children: _jsx(SpinnerDisplay, { message: "Verifying Telegram bot token\u2026", frame: spinnerFrame }) }));
|
|
637
1044
|
case 'done':
|
|
638
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "green", bold: true, children: [CHECK, " Setup complete"] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [phase.n8nHost ? _jsxs(Text, { dimColor: true, children: [" n8n ", DOT, " ", phase.n8nHost] }) : null, phase.provider ? _jsxs(Text, { dimColor: true, children: [" LLM ", DOT, " ", phase.provider, phase.model ? ` / ${phase.model}` : ''] }) : null, phase.surfaces.length > 0
|
|
1045
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "green", bold: true, children: [CHECK, " Setup complete"] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [phase.n8nHost ? _jsxs(Text, { dimColor: true, children: [" n8n ", DOT, " ", phase.n8nHost] }) : null, phase.provider ? _jsxs(Text, { dimColor: true, children: [" LLM ", DOT, " ", getProviderDisplayName(phase.provider), phase.model ? ` / ${phase.model}` : ''] }) : null, phase.surfaces.length > 0
|
|
639
1046
|
? _jsxs(Text, { dimColor: true, children: [" Gates ", DOT, " ", phase.surfaces.join(', ')] })
|
|
640
1047
|
: _jsxs(Text, { dimColor: true, children: [" Gates ", DOT, " none"] })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Next: " }), _jsx(Text, { color: "cyan", children: "yagr start" })] })] }));
|
|
641
1048
|
case 'cancelled':
|