@undefineds.co/linx 0.2.17 → 0.2.19
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 +3 -6
- package/dist/generated/version.js +1 -1
- package/dist/generated/version.js.map +1 -1
- package/dist/index.js +256 -143
- package/dist/index.js.map +1 -1
- package/dist/lib/account-api.js +1 -1
- package/dist/lib/account-api.js.map +1 -1
- package/dist/lib/account-session.js +1 -1
- package/dist/lib/account-session.js.map +1 -1
- package/dist/lib/ai-command.js +186 -367
- package/dist/lib/ai-command.js.map +1 -1
- package/dist/lib/chat-api.js +177 -19
- package/dist/lib/chat-api.js.map +1 -1
- package/dist/lib/codex-plugin/bridge.js.map +1 -1
- package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -1
- package/dist/lib/codex-plugin/index.js.map +1 -1
- package/dist/lib/codex-plugin/runner.js.map +1 -1
- package/dist/lib/credentials-store.js +7 -1
- package/dist/lib/credentials-store.js.map +1 -1
- package/dist/lib/default-model.js +1 -0
- package/dist/lib/default-model.js.map +1 -1
- package/dist/lib/login-command.js +33 -10
- package/dist/lib/login-command.js.map +1 -1
- package/dist/lib/models.js +2 -27
- package/dist/lib/models.js.map +1 -1
- package/dist/lib/node-warning-filter.js +34 -0
- package/dist/lib/node-warning-filter.js.map +1 -0
- package/dist/lib/oidc-auth.js +130 -18
- package/dist/lib/oidc-auth.js.map +1 -1
- package/dist/lib/oidc-session-storage.js.map +1 -1
- package/dist/lib/pi-adapter/auth.js +47 -11
- package/dist/lib/pi-adapter/auth.js.map +1 -1
- package/dist/lib/pi-adapter/branding.js +802 -78
- package/dist/lib/pi-adapter/branding.js.map +1 -1
- package/dist/lib/pi-adapter/index.js.map +1 -1
- package/dist/lib/pi-adapter/interactive.js +179 -5
- package/dist/lib/pi-adapter/interactive.js.map +1 -1
- package/dist/lib/pi-adapter/pod-approval.js +8 -0
- package/dist/lib/pi-adapter/pod-approval.js.map +1 -0
- package/dist/lib/pi-adapter/pod-mirror-mapping.js +189 -0
- package/dist/lib/pi-adapter/pod-mirror-mapping.js.map +1 -0
- package/dist/lib/pi-adapter/pod-mirror.js +416 -0
- package/dist/lib/pi-adapter/pod-mirror.js.map +1 -0
- package/dist/lib/pi-adapter/pod-tools.js +104 -0
- package/dist/lib/pi-adapter/pod-tools.js.map +1 -0
- package/dist/lib/pi-adapter/runtime.js +327 -28
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- package/dist/lib/pi-adapter/session.js +608 -0
- package/dist/lib/pi-adapter/session.js.map +1 -0
- package/dist/lib/pi-adapter/stream.js +154 -4
- package/dist/lib/pi-adapter/stream.js.map +1 -1
- package/dist/lib/pi-adapter/theme.js.map +1 -1
- package/dist/lib/pi-adapter/web-fetch.js +154 -0
- package/dist/lib/pi-adapter/web-fetch.js.map +1 -0
- package/dist/lib/pod-chat-store.js +40 -30
- package/dist/lib/pod-chat-store.js.map +1 -1
- package/dist/lib/pod-data-session.js +110 -0
- package/dist/lib/pod-data-session.js.map +1 -0
- package/dist/lib/profile-identity.js +16 -60
- package/dist/lib/profile-identity.js.map +1 -1
- package/dist/lib/prompt.js.map +1 -1
- package/dist/lib/runtime-target.js +1 -1
- package/dist/lib/runtime-target.js.map +1 -1
- package/dist/lib/solid-auth.js.map +1 -1
- package/dist/lib/thread-utils.js.map +1 -1
- package/dist/lib/watch/archive.js +1 -1
- package/dist/lib/watch/archive.js.map +1 -1
- package/dist/lib/watch/auth.js +1 -1
- package/dist/lib/watch/auth.js.map +1 -1
- package/dist/lib/watch/codex-composer.js.map +1 -1
- package/dist/lib/watch/codex-footer.js.map +1 -1
- package/dist/lib/watch/codex-overlay.js.map +1 -1
- package/dist/lib/watch/codex-request-form.js +1 -1
- package/dist/lib/watch/codex-request-form.js.map +1 -1
- package/dist/lib/watch/codex-request-input.js.map +1 -1
- package/dist/lib/watch/display.js.map +1 -1
- package/dist/lib/watch/format.js.map +1 -1
- package/dist/lib/watch/hooks/claude.js +4 -0
- package/dist/lib/watch/hooks/claude.js.map +1 -1
- package/dist/lib/watch/hooks/codebuddy.js +4 -0
- package/dist/lib/watch/hooks/codebuddy.js.map +1 -1
- package/dist/lib/watch/hooks/codex.js +4 -0
- package/dist/lib/watch/hooks/codex.js.map +1 -1
- package/dist/lib/watch/hooks/index.js.map +1 -1
- package/dist/lib/watch/hooks/shared.js +0 -1
- package/dist/lib/watch/hooks/shared.js.map +1 -1
- package/dist/lib/watch/index.js.map +1 -1
- package/dist/lib/watch/pod-ai.js +29 -37
- package/dist/lib/watch/pod-ai.js.map +1 -1
- package/dist/lib/watch/pod-approval.js +822 -220
- package/dist/lib/watch/pod-approval.js.map +1 -1
- package/dist/lib/watch/pod-persistence.js +214 -106
- package/dist/lib/watch/pod-persistence.js.map +1 -1
- package/dist/lib/watch/runner.js +243 -38
- package/dist/lib/watch/runner.js.map +1 -1
- package/dist/lib/watch/secretary.js +238 -0
- package/dist/lib/watch/secretary.js.map +1 -0
- package/dist/lib/watch/types.js.map +1 -1
- package/dist/watch-cli.js +8 -34
- package/dist/watch-cli.js.map +1 -1
- package/package.json +3 -9
- package/vendor/agent-runtime/dist/acp.d.ts +27 -0
- package/vendor/agent-runtime/dist/acp.js +86 -0
- package/vendor/agent-runtime/dist/companion-model.d.ts +7 -0
- package/vendor/agent-runtime/dist/companion-model.js +12 -0
- package/vendor/agent-runtime/dist/index.d.ts +3 -0
- package/vendor/agent-runtime/dist/index.js +3 -0
- package/vendor/agent-runtime/dist/turn-controller.d.ts +69 -0
- package/vendor/agent-runtime/dist/turn-controller.js +129 -0
- package/vendor/agent-runtime/package.json +11 -0
- package/vendor/client/dist/client/index.d.ts +0 -118
- package/vendor/client/dist/client/index.js +0 -260
- package/vendor/client/dist/index.d.ts +0 -1
- package/vendor/client/dist/index.js +0 -1
- package/vendor/client/dist/watch/index.d.ts +0 -226
- package/vendor/client/dist/watch/index.js +0 -1114
- package/vendor/client/package.json +0 -9
|
@@ -1,24 +1,46 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { basename, join } from 'node:path';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
4
5
|
import { LINX_HOME_DIRNAME } from '@undefineds.co/models/client';
|
|
5
|
-
import { keyHint, rawKeyHint } from '@mariozechner/pi-coding-agent';
|
|
6
|
+
import { initTheme, keyHint, LoginDialogComponent, rawKeyHint } from '@mariozechner/pi-coding-agent';
|
|
6
7
|
import { Text, truncateToWidth, visibleWidth, wrapTextWithAnsi } from '@mariozechner/pi-tui';
|
|
7
8
|
import { loadCredentials } from '../credentials-store.js';
|
|
8
9
|
import { extractUsernameFromWebId, resolveProfileDisplayName } from '../profile-identity.js';
|
|
9
|
-
import { LINX_CLI_VERSION } from '../../generated/version.js';
|
|
10
10
|
export const LINX_AGENT_DIR = join(homedir(), LINX_HOME_DIRNAME, 'agent');
|
|
11
11
|
export const LINX_UPDATE_PACKAGE_NAME = '@undefineds.co/linx';
|
|
12
12
|
export const LINX_CHANGELOG_URL = 'https://github.com/undefineds-co/linx-cli/releases';
|
|
13
|
+
export const LINX_CLI_VERSION = readLinxCliVersion();
|
|
14
|
+
const LINX_AUTH_LOGIN_IN_PROGRESS = Symbol.for('linx.tui.authLoginInProgress');
|
|
15
|
+
const LINX_AUTH_LOGIN_ON_INIT = Symbol.for('linx.tui.authLoginOnInit');
|
|
16
|
+
const LINX_AUTH_PENDING_RETRY = Symbol.for('linx.tui.authPendingRetry');
|
|
17
|
+
const LINX_AUTH_LOGIN_SCHEDULED = Symbol.for('linx.tui.authLoginScheduled');
|
|
18
|
+
const LINX_AUTH_REPORTING_ERROR = Symbol.for('linx.tui.authReportingError');
|
|
13
19
|
const LINX_UPDATE_IN_PROGRESS = Symbol.for('linx.tui.updateInProgress');
|
|
14
|
-
const
|
|
20
|
+
const LINX_PROVIDER_ID = 'undefineds';
|
|
21
|
+
const AUTH_OPTION_BROWSER = 'Authorize in browser';
|
|
22
|
+
const AUTH_OPTION_API_KEY = 'Enter API key';
|
|
23
|
+
const AUTH_OPTION_EXIT = 'Exit';
|
|
24
|
+
const UPDATE_OPTION_INSTALL = 'Install update and restart';
|
|
25
|
+
const UPDATE_OPTION_CHANGELOG = 'Open changelog';
|
|
26
|
+
const UPDATE_OPTION_LATER = 'Later';
|
|
15
27
|
export function applyLinxInteractiveBranding(interactive) {
|
|
16
28
|
patchTerminalTitle(interactive);
|
|
17
29
|
patchVersionCheck(interactive);
|
|
18
30
|
patchUpdateNotification(interactive);
|
|
19
|
-
|
|
31
|
+
patchNativeOAuthSelectors(interactive);
|
|
32
|
+
patchLoginCommand(interactive);
|
|
33
|
+
patchAuthExpiredSessionEvents(interactive);
|
|
34
|
+
patchAuthExpiredLoginPrompt(interactive);
|
|
20
35
|
patchHeader(interactive);
|
|
21
36
|
}
|
|
37
|
+
export function requestLinxCloudLogin(interactive, reason = 'manual') {
|
|
38
|
+
if (!interactive.isInitialized) {
|
|
39
|
+
interactive[LINX_AUTH_LOGIN_ON_INIT] = reason;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
void startLinxCloudLogin(interactive, { reason });
|
|
43
|
+
}
|
|
22
44
|
function patchTerminalTitle(interactive) {
|
|
23
45
|
const original = interactive.updateTerminalTitle?.bind(interactive);
|
|
24
46
|
interactive.updateTerminalTitle = function patchedUpdateTerminalTitle() {
|
|
@@ -43,7 +65,7 @@ function patchVersionCheck(interactive) {
|
|
|
43
65
|
}
|
|
44
66
|
const body = await response.json();
|
|
45
67
|
const latest = typeof body.version === 'string' ? body.version.trim() : '';
|
|
46
|
-
if (!latest || !
|
|
68
|
+
if (!latest || !isVersionNewer(latest, LINX_CLI_VERSION)) {
|
|
47
69
|
return undefined;
|
|
48
70
|
}
|
|
49
71
|
return latest;
|
|
@@ -55,45 +77,109 @@ function patchVersionCheck(interactive) {
|
|
|
55
77
|
}
|
|
56
78
|
function patchUpdateNotification(interactive) {
|
|
57
79
|
interactive.showNewVersionNotification = function patchedShowNewVersionNotification(newVersion) {
|
|
58
|
-
|
|
59
|
-
'\x1b[1m\x1b[33mLinX Update Available\x1b[39m\x1b[22m',
|
|
60
|
-
`\x1b[2mNew version ${newVersion} is available. \x1b[22m\x1b[36mType /update to install now.\x1b[39m`,
|
|
61
|
-
`\x1b[2mManual install: \x1b[22m\x1b[36mnpm install -g ${LINX_UPDATE_PACKAGE_NAME}@latest\x1b[39m`,
|
|
62
|
-
`\x1b[2mChangelog: \x1b[22m\x1b[36m${LINX_CHANGELOG_URL}\x1b[39m`,
|
|
63
|
-
];
|
|
64
|
-
this.chatContainer?.addChild?.(new Text(lines.join('\n'), 1, 0));
|
|
65
|
-
this.ui?.requestRender?.();
|
|
80
|
+
void showLinxUpdateSelector(this, newVersion);
|
|
66
81
|
};
|
|
67
82
|
}
|
|
68
|
-
function
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (!candidateParts || !currentParts) {
|
|
72
|
-
return candidate !== current;
|
|
83
|
+
async function showLinxUpdateSelector(interactive, newVersion) {
|
|
84
|
+
if (interactive[LINX_UPDATE_IN_PROGRESS]) {
|
|
85
|
+
return;
|
|
73
86
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
interactive[LINX_UPDATE_IN_PROGRESS] = true;
|
|
88
|
+
try {
|
|
89
|
+
const title = [
|
|
90
|
+
'LinX update available',
|
|
91
|
+
`Current ${LINX_CLI_VERSION} -> latest ${newVersion}`,
|
|
92
|
+
'Choose how to handle this update.',
|
|
93
|
+
].join('\n');
|
|
94
|
+
const options = [UPDATE_OPTION_INSTALL, UPDATE_OPTION_CHANGELOG, UPDATE_OPTION_LATER];
|
|
95
|
+
const selected = typeof interactive.showExtensionSelector === 'function'
|
|
96
|
+
? await interactive.showExtensionSelector(title, options)
|
|
97
|
+
: undefined;
|
|
98
|
+
if (selected === UPDATE_OPTION_INSTALL) {
|
|
99
|
+
await installLinxUpdateAndRestart(interactive, newVersion);
|
|
100
|
+
return;
|
|
77
101
|
}
|
|
78
|
-
if (
|
|
79
|
-
|
|
102
|
+
if (selected === UPDATE_OPTION_CHANGELOG) {
|
|
103
|
+
openExternalUrl(LINX_CHANGELOG_URL, interactive);
|
|
104
|
+
interactive.showStatus?.(`Opened LinX changelog for ${newVersion}.`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (!selected) {
|
|
108
|
+
showLinxUpdateFallback(interactive, newVersion);
|
|
109
|
+
return;
|
|
80
110
|
}
|
|
111
|
+
interactive.showStatus?.(`Skipped LinX ${newVersion} for now.`);
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
interactive[LINX_UPDATE_IN_PROGRESS] = false;
|
|
81
115
|
}
|
|
82
|
-
return false;
|
|
83
116
|
}
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
117
|
+
async function installLinxUpdateAndRestart(interactive, newVersion) {
|
|
118
|
+
interactive.showStatus?.(`Installing LinX ${newVersion}...`);
|
|
119
|
+
interactive.ui?.requestRender?.();
|
|
120
|
+
try {
|
|
121
|
+
await runNpmInstallLatest();
|
|
88
122
|
}
|
|
89
|
-
|
|
123
|
+
catch (error) {
|
|
124
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
125
|
+
interactive.showError?.(`LinX update failed: ${message}`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
interactive.showStatus?.(`LinX ${newVersion} installed. Restarting...`);
|
|
129
|
+
interactive.ui?.requestRender?.();
|
|
130
|
+
restartCurrentProcess(interactive);
|
|
90
131
|
}
|
|
91
|
-
function
|
|
132
|
+
function runNpmInstallLatest() {
|
|
133
|
+
const npmCommand = process.env.npm_execpath || 'npm';
|
|
134
|
+
const args = ['install', '-g', '--omit=peer', `${LINX_UPDATE_PACKAGE_NAME}@latest`];
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
const child = spawn(npmCommand, args, {
|
|
137
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
138
|
+
shell: false,
|
|
139
|
+
});
|
|
140
|
+
let stderr = '';
|
|
141
|
+
child.stderr?.on('data', (chunk) => {
|
|
142
|
+
stderr += chunk.toString();
|
|
143
|
+
});
|
|
144
|
+
child.on('error', reject);
|
|
145
|
+
child.on('close', (code) => {
|
|
146
|
+
if (code === 0) {
|
|
147
|
+
resolve();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
reject(new Error(stderr.trim() || `npm install exited with code ${code ?? 'unknown'}`));
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function restartCurrentProcess(interactive) {
|
|
155
|
+
const child = spawn(process.execPath, process.argv.slice(1), {
|
|
156
|
+
cwd: process.cwd(),
|
|
157
|
+
env: process.env,
|
|
158
|
+
stdio: 'inherit',
|
|
159
|
+
detached: false,
|
|
160
|
+
});
|
|
161
|
+
child.on('error', (error) => {
|
|
162
|
+
interactive.showError?.(`LinX restart failed: ${error.message}`);
|
|
163
|
+
});
|
|
164
|
+
interactive.stop?.();
|
|
165
|
+
setTimeout(() => process.exit(0), 50);
|
|
166
|
+
}
|
|
167
|
+
function showLinxUpdateFallback(interactive, newVersion) {
|
|
168
|
+
const lines = [
|
|
169
|
+
'\x1b[1m\x1b[33mLinX update available\x1b[39m\x1b[22m',
|
|
170
|
+
`\x1b[2mCurrent ${LINX_CLI_VERSION} -> latest ${newVersion}\x1b[22m`,
|
|
171
|
+
`\x1b[2mRun \x1b[22m\x1b[36mnpm install -g ${LINX_UPDATE_PACKAGE_NAME}@latest\x1b[39m\x1b[2m if this terminal cannot show the update selector.\x1b[22m`,
|
|
172
|
+
`\x1b[2mChangelog: \x1b[22m\x1b[36m${LINX_CHANGELOG_URL}\x1b[39m`,
|
|
173
|
+
];
|
|
174
|
+
interactive.chatContainer?.addChild?.(new Text(lines.join('\n'), 1, 0));
|
|
175
|
+
interactive.ui?.requestRender?.();
|
|
176
|
+
}
|
|
177
|
+
function patchLoginCommand(interactive) {
|
|
92
178
|
const originalSetup = interactive.setupEditorSubmitHandler?.bind(interactive);
|
|
93
179
|
if (typeof originalSetup !== 'function') {
|
|
94
180
|
return;
|
|
95
181
|
}
|
|
96
|
-
interactive.setupEditorSubmitHandler = function
|
|
182
|
+
interactive.setupEditorSubmitHandler = function patchedLinxLoginSetupEditorSubmitHandler() {
|
|
97
183
|
originalSetup();
|
|
98
184
|
const originalSubmit = this.defaultEditor?.onSubmit?.bind(this.defaultEditor);
|
|
99
185
|
if (typeof originalSubmit !== 'function') {
|
|
@@ -101,77 +187,643 @@ function patchUpdateCommand(interactive) {
|
|
|
101
187
|
}
|
|
102
188
|
this.defaultEditor.onSubmit = async (text) => {
|
|
103
189
|
const command = text.trim();
|
|
104
|
-
if (command === '/
|
|
190
|
+
if (command === '/login') {
|
|
105
191
|
this.editor?.setText?.('');
|
|
106
|
-
await
|
|
192
|
+
await startLinxCloudLogin(this);
|
|
107
193
|
return;
|
|
108
194
|
}
|
|
109
195
|
await originalSubmit(text);
|
|
110
196
|
};
|
|
111
197
|
};
|
|
112
198
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
199
|
+
function patchNativeOAuthSelectors(interactive) {
|
|
200
|
+
interactive.showOAuthSelector = async function patchedLinxOAuthSelector(mode = 'login') {
|
|
201
|
+
if (mode === 'logout') {
|
|
202
|
+
const authStorage = this.session?.modelRegistry?.authStorage;
|
|
203
|
+
authStorage?.logout?.(LINX_PROVIDER_ID);
|
|
204
|
+
authStorage?.setRuntimeApiKey?.(LINX_PROVIDER_ID, '');
|
|
205
|
+
await refreshLinxAuthState(this);
|
|
206
|
+
this.showStatus?.('Logged out of LinX Cloud.');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
await startLinxCloudLogin(this, { reason: 'manual' });
|
|
210
|
+
};
|
|
211
|
+
interactive.showLoginDialog = async function patchedLinxLoginDialog(providerId) {
|
|
212
|
+
if (!providerId || providerId === LINX_PROVIDER_ID) {
|
|
213
|
+
await startLinxCloudLogin(this, { reason: 'manual' });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
this.showStatus?.('LinX only supports LinX Cloud authentication in this TUI.');
|
|
217
|
+
await startLinxCloudLogin(this, { reason: 'manual' });
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function patchAuthExpiredSessionEvents(interactive) {
|
|
221
|
+
const originalHandleEvent = interactive.handleEvent?.bind(interactive);
|
|
222
|
+
if (typeof originalHandleEvent !== 'function') {
|
|
116
223
|
return;
|
|
117
224
|
}
|
|
118
|
-
interactive
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
225
|
+
interactive.handleEvent = async function patchedHandleEvent(event) {
|
|
226
|
+
if (eventHasLinxAuthExpiredError(event)) {
|
|
227
|
+
prepareLinxAuthExpiredRetry(this);
|
|
228
|
+
suppressLinxAuthExpiredAssistantError(this);
|
|
229
|
+
scheduleLinxCloudLogin(this, 'expired');
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
const result = await originalHandleEvent(event);
|
|
233
|
+
return result;
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function patchAuthExpiredLoginPrompt(interactive) {
|
|
237
|
+
const originalShowError = interactive.showError?.bind(interactive);
|
|
238
|
+
if (typeof originalShowError !== 'function') {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
interactive.showError = function patchedShowError(errorMessage) {
|
|
242
|
+
const text = typeof errorMessage === 'string' ? errorMessage : String(errorMessage);
|
|
243
|
+
if (!isLinxAuthExpiredError(text)) {
|
|
244
|
+
return originalShowError(errorMessage);
|
|
245
|
+
}
|
|
246
|
+
scheduleLinxCloudLogin(this, 'expired');
|
|
247
|
+
return undefined;
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function isLinxAuthExpiredError(text) {
|
|
251
|
+
const normalized = stripAnsi(text).toLowerCase();
|
|
252
|
+
return normalized.includes('linx cloud login expired')
|
|
253
|
+
|| normalized.includes('invalid solid token')
|
|
254
|
+
|| (normalized.includes('chat request failed (401)') && normalized.includes('unauthorized'));
|
|
255
|
+
}
|
|
256
|
+
function eventHasLinxAuthExpiredError(event) {
|
|
257
|
+
if (!isRecord(event)) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
const message = isRecord(event.message) ? event.message : undefined;
|
|
261
|
+
const errorMessage = typeof message?.errorMessage === 'string' ? message.errorMessage : '';
|
|
262
|
+
const error = isRecord(event.error) ? event.error : undefined;
|
|
263
|
+
const nestedErrorMessage = typeof error?.errorMessage === 'string' ? error.errorMessage : '';
|
|
264
|
+
return isLinxAuthExpiredError(`${errorMessage}\n${nestedErrorMessage}`);
|
|
265
|
+
}
|
|
266
|
+
async function startLinxCloudLogin(interactive, options = {}) {
|
|
267
|
+
if (interactive[LINX_AUTH_LOGIN_IN_PROGRESS]) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
interactive[LINX_AUTH_LOGIN_IN_PROGRESS] = true;
|
|
126
271
|
try {
|
|
127
|
-
interactive.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
272
|
+
const authStorage = interactive.session?.modelRegistry?.authStorage;
|
|
273
|
+
if (!authStorage) {
|
|
274
|
+
prefillLoginCommand(interactive);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const reason = options.reason ?? 'manual';
|
|
278
|
+
const selected = await selectLinxAuthMethod(interactive, reason);
|
|
279
|
+
if (!selected) {
|
|
280
|
+
interactive.showStatus?.('LinX Cloud authorization cancelled.');
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (selected === AUTH_OPTION_BROWSER) {
|
|
284
|
+
if (typeof authStorage.login !== 'function') {
|
|
285
|
+
prefillLoginCommand(interactive);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
await runLinxCloudBrowserLogin(interactive, authStorage, reason);
|
|
289
|
+
await refreshLinxAuthState(interactive);
|
|
290
|
+
await finishLinxAuthSuccess(interactive, reason, 'Browser authorization complete.');
|
|
135
291
|
return;
|
|
136
292
|
}
|
|
137
|
-
|
|
138
|
-
|
|
293
|
+
if (selected === AUTH_OPTION_API_KEY) {
|
|
294
|
+
await promptForLinxApiKey(interactive, reason);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (selected === AUTH_OPTION_EXIT) {
|
|
298
|
+
if (reason === 'startup') {
|
|
299
|
+
interactive.stop?.();
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
interactive.showStatus?.('LinX Cloud authorization cancelled.');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
139
305
|
}
|
|
140
306
|
catch (error) {
|
|
141
|
-
interactive.ui?.start?.();
|
|
142
307
|
const message = error instanceof Error ? error.message : String(error);
|
|
143
|
-
interactive
|
|
308
|
+
reportLinxLoginError(interactive, message);
|
|
144
309
|
}
|
|
145
310
|
finally {
|
|
146
|
-
interactive[
|
|
147
|
-
|
|
311
|
+
interactive[LINX_AUTH_LOGIN_IN_PROGRESS] = false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function scheduleLinxCloudLogin(interactive, reason) {
|
|
315
|
+
if (interactive[LINX_AUTH_LOGIN_IN_PROGRESS] || interactive[LINX_AUTH_LOGIN_SCHEDULED]) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
interactive[LINX_AUTH_LOGIN_SCHEDULED] = true;
|
|
319
|
+
setTimeout(() => {
|
|
320
|
+
interactive[LINX_AUTH_LOGIN_SCHEDULED] = false;
|
|
321
|
+
void startLinxCloudLogin(interactive, { reason });
|
|
322
|
+
}, 0);
|
|
323
|
+
}
|
|
324
|
+
function reportLinxLoginError(interactive, message) {
|
|
325
|
+
const rendered = normalizeLinxLoginError(message);
|
|
326
|
+
if (interactive[LINX_AUTH_REPORTING_ERROR]) {
|
|
327
|
+
interactive.showStatus?.(rendered);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
interactive[LINX_AUTH_REPORTING_ERROR] = true;
|
|
331
|
+
try {
|
|
332
|
+
if (typeof interactive.showError === 'function') {
|
|
333
|
+
interactive.showError(rendered);
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
interactive.showStatus?.(rendered);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
finally {
|
|
340
|
+
interactive[LINX_AUTH_REPORTING_ERROR] = false;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function normalizeLinxLoginError(message) {
|
|
344
|
+
const oidcCallbackError = /^OIDC callback returned\b/i.test(message);
|
|
345
|
+
if (oidcCallbackError) {
|
|
346
|
+
return `LinX Cloud login failed: ${message}`;
|
|
347
|
+
}
|
|
348
|
+
if (/server_error/i.test(message)) {
|
|
349
|
+
return `LinX Cloud login failed: the identity server rejected this browser login. ${message}`;
|
|
350
|
+
}
|
|
351
|
+
return `LinX Cloud login failed: ${message}`;
|
|
352
|
+
}
|
|
353
|
+
async function selectLinxAuthMethod(interactive, reason) {
|
|
354
|
+
const title = buildLinxAuthPromptTitle(reason, resolveRuntimeProviderLabel(interactive));
|
|
355
|
+
const options = [AUTH_OPTION_BROWSER, AUTH_OPTION_API_KEY, AUTH_OPTION_EXIT];
|
|
356
|
+
if (typeof interactive.showExtensionSelector === 'function') {
|
|
357
|
+
return await interactive.showExtensionSelector(title, options);
|
|
358
|
+
}
|
|
359
|
+
showLinxAuthFallback(interactive, title, options);
|
|
360
|
+
return undefined;
|
|
361
|
+
}
|
|
362
|
+
function buildLinxAuthPromptTitle(reason, providerLabel) {
|
|
363
|
+
if (reason === 'startup') {
|
|
364
|
+
return [
|
|
365
|
+
'LinX Cloud login required',
|
|
366
|
+
`Connect to ${providerLabel} before using LinX TUI.`,
|
|
367
|
+
'Choose a sign-in method.',
|
|
368
|
+
].join('\n');
|
|
369
|
+
}
|
|
370
|
+
if (reason === 'expired') {
|
|
371
|
+
return [
|
|
372
|
+
'LinX Cloud login expired',
|
|
373
|
+
'Your current Solid token was rejected by LinX Cloud.',
|
|
374
|
+
'Re-authorize or provide an API key, then retry your message.',
|
|
375
|
+
].join('\n');
|
|
376
|
+
}
|
|
377
|
+
return [
|
|
378
|
+
'LinX Cloud authorization',
|
|
379
|
+
`Choose how LinX should authenticate with ${providerLabel}.`,
|
|
380
|
+
].join('\n');
|
|
381
|
+
}
|
|
382
|
+
function showLinxAuthFallback(interactive, title, options) {
|
|
383
|
+
interactive.chatContainer?.addChild?.(new Text([
|
|
384
|
+
`\x1b[1m${title}\x1b[22m`,
|
|
385
|
+
'',
|
|
386
|
+
...options.map((option) => `- ${option}`),
|
|
387
|
+
'',
|
|
388
|
+
'This terminal build cannot render the LinX auth selector. Run `linx login` in another shell.',
|
|
389
|
+
].join('\n'), 1, 0));
|
|
390
|
+
interactive.ui?.requestRender?.();
|
|
391
|
+
}
|
|
392
|
+
async function promptForLinxApiKey(interactive, reason) {
|
|
393
|
+
if (typeof interactive.showExtensionInput !== 'function') {
|
|
394
|
+
interactive.showError?.('This terminal build cannot collect an API key inside the TUI.');
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const apiKey = await interactive.showExtensionInput([
|
|
398
|
+
reason === 'expired' ? 'Enter LinX Cloud API key' : 'Use LinX Cloud API key',
|
|
399
|
+
'Paste a key for this TUI session. Press Escape to cancel.',
|
|
400
|
+
].join('\n'), 'linx API key');
|
|
401
|
+
const trimmed = typeof apiKey === 'string' ? apiKey.trim() : '';
|
|
402
|
+
if (!trimmed) {
|
|
403
|
+
interactive.showStatus?.('LinX Cloud API key entry cancelled.');
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const authStorage = interactive.session?.modelRegistry?.authStorage;
|
|
407
|
+
authStorage?.setRuntimeApiKey?.(LINX_PROVIDER_ID, trimmed);
|
|
408
|
+
authStorage?.set?.(LINX_PROVIDER_ID, { type: 'api_key', key: trimmed });
|
|
409
|
+
await refreshLinxAuthState(interactive);
|
|
410
|
+
await finishLinxAuthSuccess(interactive, reason, 'API key saved for this TUI session.');
|
|
411
|
+
}
|
|
412
|
+
async function finishLinxAuthSuccess(interactive, reason, detail) {
|
|
413
|
+
const prefix = authStatusPrefix(reason);
|
|
414
|
+
const retryStarted = await retryPendingLinxAuthTurn(interactive, reason);
|
|
415
|
+
if (retryStarted) {
|
|
416
|
+
interactive.showStatus?.(`${prefix} ${detail} Retrying your message...`);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const suffix = reason === 'expired' ? ' Retry your message.' : '';
|
|
420
|
+
interactive.showStatus?.(`${prefix} ${detail}${suffix}`);
|
|
421
|
+
}
|
|
422
|
+
function prepareLinxAuthExpiredRetry(interactive) {
|
|
423
|
+
if (interactive[LINX_AUTH_PENDING_RETRY]) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const session = interactive.session;
|
|
427
|
+
const sessionManager = session?.sessionManager;
|
|
428
|
+
const leafId = typeof sessionManager?.getLeafId === 'function'
|
|
429
|
+
? sessionManager.getLeafId()
|
|
430
|
+
: undefined;
|
|
431
|
+
const leafEntry = leafId && typeof sessionManager?.getEntry === 'function'
|
|
432
|
+
? sessionManager.getEntry(leafId)
|
|
433
|
+
: undefined;
|
|
434
|
+
const leafMessage = leafEntry?.type === 'message' ? leafEntry.message : undefined;
|
|
435
|
+
const userEntry = findLastUserMessageEntry(sessionManager, leafId);
|
|
436
|
+
const promptText = extractUserMessageText(userEntry?.message)
|
|
437
|
+
?? extractUserMessageText(leafMessage)
|
|
438
|
+
?? findLastUserMessageText(session?.state?.messages);
|
|
439
|
+
const pending = {
|
|
440
|
+
continueFromId: userEntry?.id ?? (leafMessage?.role === 'user' ? leafId : undefined),
|
|
441
|
+
promptText,
|
|
442
|
+
promptParentId: userEntry?.parentId ?? (leafMessage?.role === 'user' ? leafEntry.parentId : undefined),
|
|
443
|
+
};
|
|
444
|
+
interactive[LINX_AUTH_PENDING_RETRY] = pending;
|
|
445
|
+
// AgentSession persists the assistant error after TUI subscribers run. Restore
|
|
446
|
+
// the active branch on the next tick so "continue" never resumes from the
|
|
447
|
+
// failed auth assistant message if the user cancels or login fails.
|
|
448
|
+
setTimeout(() => {
|
|
449
|
+
if (interactive[LINX_AUTH_PENDING_RETRY] === pending) {
|
|
450
|
+
restoreLinxRetryBranch(interactive.session, pending.continueFromId);
|
|
451
|
+
}
|
|
452
|
+
}, 0);
|
|
453
|
+
}
|
|
454
|
+
function suppressLinxAuthExpiredAssistantError(interactive) {
|
|
455
|
+
const streamingComponent = interactive.streamingComponent;
|
|
456
|
+
if (streamingComponent) {
|
|
457
|
+
interactive.chatContainer?.removeChild?.(streamingComponent);
|
|
458
|
+
}
|
|
459
|
+
interactive.streamingComponent = undefined;
|
|
460
|
+
interactive.streamingMessage = undefined;
|
|
461
|
+
interactive.footer?.invalidate?.();
|
|
462
|
+
interactive.ui?.requestRender?.();
|
|
463
|
+
}
|
|
464
|
+
async function retryPendingLinxAuthTurn(interactive, reason) {
|
|
465
|
+
if (reason !== 'expired') {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
const pending = interactive[LINX_AUTH_PENDING_RETRY];
|
|
469
|
+
if (!pending) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
interactive[LINX_AUTH_PENDING_RETRY] = undefined;
|
|
473
|
+
const session = interactive.session;
|
|
474
|
+
const sessionManager = session?.sessionManager;
|
|
475
|
+
if (!session || !sessionManager) {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
await session.agent?.waitForIdle?.();
|
|
479
|
+
try {
|
|
480
|
+
restoreLinxRetryBranch(session, pending.continueFromId);
|
|
481
|
+
if (typeof session.agent?.continue === 'function') {
|
|
482
|
+
startLinxContinuation(interactive, session, pending);
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
if (!pending.promptText) {
|
|
488
|
+
throw error;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (!pending.promptText || typeof session.prompt !== 'function') {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
restoreLinxRetryBranch(session, pending.promptParentId);
|
|
495
|
+
const promptResult = session.prompt(pending.promptText);
|
|
496
|
+
if (isPromiseLike(promptResult)) {
|
|
497
|
+
promptResult.catch((error) => {
|
|
498
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
499
|
+
interactive.showError?.(`LinX Cloud retry failed: ${message}`);
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
return true;
|
|
503
|
+
}
|
|
504
|
+
function startLinxContinuation(interactive, session, pending) {
|
|
505
|
+
try {
|
|
506
|
+
const result = session.agent.continue();
|
|
507
|
+
if (isPromiseLike(result)) {
|
|
508
|
+
result.catch((error) => {
|
|
509
|
+
void retryLinxPromptFallback(interactive, session, pending, error);
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
catch (error) {
|
|
514
|
+
void retryLinxPromptFallback(interactive, session, pending, error);
|
|
148
515
|
}
|
|
149
516
|
}
|
|
150
|
-
function
|
|
151
|
-
|
|
152
|
-
?
|
|
153
|
-
:
|
|
517
|
+
async function retryLinxPromptFallback(interactive, session, pending, cause) {
|
|
518
|
+
if (!pending.promptText || typeof session?.prompt !== 'function') {
|
|
519
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
520
|
+
interactive.showError?.(`LinX Cloud retry failed: ${message}`);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
try {
|
|
524
|
+
restoreLinxRetryBranch(session, pending.promptParentId);
|
|
525
|
+
await session.prompt(pending.promptText);
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
529
|
+
interactive.showError?.(`LinX Cloud retry failed: ${message}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function restoreLinxRetryBranch(session, leafId) {
|
|
533
|
+
const sessionManager = session?.sessionManager;
|
|
534
|
+
if (!sessionManager) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
if (typeof leafId === 'string' && leafId) {
|
|
538
|
+
sessionManager.branch?.(leafId);
|
|
539
|
+
}
|
|
540
|
+
else if (leafId === null) {
|
|
541
|
+
sessionManager.resetLeaf?.();
|
|
542
|
+
}
|
|
543
|
+
const context = sessionManager.buildSessionContext?.();
|
|
544
|
+
if (context?.messages && session.agent?.state) {
|
|
545
|
+
session.agent.state.messages = context.messages;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
function findLastUserMessageText(messages) {
|
|
549
|
+
if (!Array.isArray(messages)) {
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
553
|
+
const text = extractUserMessageText(messages[index]);
|
|
554
|
+
if (text) {
|
|
555
|
+
return text;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return undefined;
|
|
559
|
+
}
|
|
560
|
+
function findLastUserMessageEntry(sessionManager, leafId) {
|
|
561
|
+
const branch = typeof sessionManager?.getBranch === 'function' && typeof leafId === 'string'
|
|
562
|
+
? sessionManager.getBranch(leafId)
|
|
563
|
+
: undefined;
|
|
564
|
+
const entries = Array.isArray(branch) && branch.length > 0
|
|
565
|
+
? branch
|
|
566
|
+
: typeof sessionManager?.getEntries === 'function'
|
|
567
|
+
? sessionManager.getEntries()
|
|
568
|
+
: [];
|
|
569
|
+
if (!Array.isArray(entries)) {
|
|
570
|
+
return undefined;
|
|
571
|
+
}
|
|
572
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
573
|
+
const entry = entries[index];
|
|
574
|
+
if (isRecord(entry)
|
|
575
|
+
&& entry.type === 'message'
|
|
576
|
+
&& typeof entry.id === 'string'
|
|
577
|
+
&& isRecord(entry.message)
|
|
578
|
+
&& entry.message.role === 'user') {
|
|
579
|
+
return {
|
|
580
|
+
id: entry.id,
|
|
581
|
+
parentId: normalizeParentId(entry.parentId),
|
|
582
|
+
message: entry.message,
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return undefined;
|
|
587
|
+
}
|
|
588
|
+
function normalizeParentId(parentId) {
|
|
589
|
+
if (typeof parentId === 'string') {
|
|
590
|
+
return parentId;
|
|
591
|
+
}
|
|
592
|
+
if (parentId === null) {
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
return undefined;
|
|
596
|
+
}
|
|
597
|
+
function extractUserMessageText(message) {
|
|
598
|
+
if (!isRecord(message) || message.role !== 'user') {
|
|
599
|
+
return undefined;
|
|
600
|
+
}
|
|
601
|
+
const content = message.content;
|
|
602
|
+
if (typeof content === 'string') {
|
|
603
|
+
return content.trim() || undefined;
|
|
604
|
+
}
|
|
605
|
+
if (!Array.isArray(content)) {
|
|
606
|
+
return undefined;
|
|
607
|
+
}
|
|
608
|
+
const text = content
|
|
609
|
+
.filter((entry) => (isRecord(entry) && entry.type === 'text' && typeof entry.text === 'string'))
|
|
610
|
+
.map((entry) => entry.text)
|
|
611
|
+
.join('')
|
|
612
|
+
.trim();
|
|
613
|
+
return text || undefined;
|
|
614
|
+
}
|
|
615
|
+
async function refreshLinxAuthState(interactive) {
|
|
616
|
+
interactive.session?.modelRegistry?.refresh?.();
|
|
617
|
+
await interactive.updateAvailableProviderCount?.();
|
|
618
|
+
interactive.ui?.requestRender?.();
|
|
619
|
+
}
|
|
620
|
+
function authStatusPrefix(reason) {
|
|
621
|
+
if (reason === 'expired') {
|
|
622
|
+
return 'LinX Cloud login refreshed.';
|
|
623
|
+
}
|
|
624
|
+
if (reason === 'startup') {
|
|
625
|
+
return 'LinX Cloud connected.';
|
|
626
|
+
}
|
|
627
|
+
return 'LinX Cloud authorization updated.';
|
|
628
|
+
}
|
|
629
|
+
async function runLinxCloudLogin(interactive, authStorage, reason) {
|
|
630
|
+
await authStorage.login(LINX_PROVIDER_ID, {
|
|
631
|
+
forceFresh: true,
|
|
632
|
+
onAuth(info) {
|
|
633
|
+
showLinxLoginUrl(interactive, info);
|
|
634
|
+
openLoginUrl(info.url, interactive);
|
|
635
|
+
if (info.instructions) {
|
|
636
|
+
interactive.showStatus?.(info.instructions);
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
onProgress(message) {
|
|
640
|
+
interactive.showStatus?.(message);
|
|
641
|
+
interactive.ui?.requestRender?.();
|
|
642
|
+
},
|
|
643
|
+
onManualCodeInput(signal) {
|
|
644
|
+
return promptForLinxManualRedirectUrl(interactive, signal);
|
|
645
|
+
},
|
|
646
|
+
});
|
|
647
|
+
syncRuntimeCredential(interactive);
|
|
648
|
+
}
|
|
649
|
+
async function runLinxCloudBrowserLogin(interactive, authStorage, reason) {
|
|
650
|
+
if (canRenderLinxLoginDialog(interactive)) {
|
|
651
|
+
try {
|
|
652
|
+
await runLinxCloudLoginDialog(interactive, authStorage, reason);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
catch (error) {
|
|
656
|
+
if (!isThemeInitializationError(error)) {
|
|
657
|
+
throw error;
|
|
658
|
+
}
|
|
659
|
+
interactive.showStatus?.('LinX Cloud login prompt unavailable before TUI theme initialization; falling back to browser login.');
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
await runLinxCloudLogin(interactive, authStorage, reason);
|
|
663
|
+
}
|
|
664
|
+
function canRenderLinxLoginDialog(interactive) {
|
|
665
|
+
return Boolean(interactive.ui
|
|
666
|
+
&& typeof interactive.editorContainer?.clear === 'function'
|
|
667
|
+
&& typeof interactive.editorContainer?.addChild === 'function'
|
|
668
|
+
&& typeof interactive.ui?.setFocus === 'function'
|
|
669
|
+
&& typeof interactive.ui?.requestRender === 'function'
|
|
670
|
+
&& interactive.editor);
|
|
671
|
+
}
|
|
672
|
+
async function runLinxCloudLoginDialog(interactive, authStorage, reason) {
|
|
673
|
+
ensurePiThemeInitialized(interactive);
|
|
674
|
+
const dialog = new LoginDialogComponent(interactive.ui, LINX_PROVIDER_ID, () => undefined);
|
|
675
|
+
const restoreEditor = () => {
|
|
676
|
+
interactive.editorContainer.clear();
|
|
677
|
+
interactive.editorContainer.addChild(interactive.editor);
|
|
678
|
+
interactive.ui?.setFocus?.(interactive.editor);
|
|
679
|
+
interactive.ui?.requestRender?.();
|
|
680
|
+
};
|
|
681
|
+
interactive.editorContainer.clear();
|
|
682
|
+
interactive.editorContainer.addChild(dialog);
|
|
683
|
+
interactive.ui?.setFocus?.(dialog);
|
|
684
|
+
interactive.ui?.requestRender?.();
|
|
685
|
+
let manualRedirectResolve;
|
|
686
|
+
let manualRedirectReject;
|
|
687
|
+
const manualRedirectPromise = new Promise((resolve, reject) => {
|
|
688
|
+
manualRedirectResolve = resolve;
|
|
689
|
+
manualRedirectReject = reject;
|
|
690
|
+
});
|
|
691
|
+
try {
|
|
692
|
+
await authStorage.login(LINX_PROVIDER_ID, {
|
|
693
|
+
forceFresh: true,
|
|
694
|
+
onAuth(info) {
|
|
695
|
+
dialog.showAuth(info.url, info.instructions);
|
|
696
|
+
dialog.showManualInput('Paste redirect URL below, or complete login in browser:')
|
|
697
|
+
.then((value) => {
|
|
698
|
+
if (value && manualRedirectResolve) {
|
|
699
|
+
manualRedirectResolve(value);
|
|
700
|
+
manualRedirectResolve = undefined;
|
|
701
|
+
manualRedirectReject = undefined;
|
|
702
|
+
}
|
|
703
|
+
})
|
|
704
|
+
.catch((error) => {
|
|
705
|
+
if (manualRedirectReject) {
|
|
706
|
+
manualRedirectReject(error);
|
|
707
|
+
manualRedirectResolve = undefined;
|
|
708
|
+
manualRedirectReject = undefined;
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
},
|
|
712
|
+
onProgress(message) {
|
|
713
|
+
dialog.showProgress(message);
|
|
714
|
+
},
|
|
715
|
+
onManualCodeInput(signal) {
|
|
716
|
+
return waitForLinxDialogManualRedirect(manualRedirectPromise, signal);
|
|
717
|
+
},
|
|
718
|
+
signal: dialog.signal,
|
|
719
|
+
});
|
|
720
|
+
syncRuntimeCredential(interactive);
|
|
721
|
+
}
|
|
722
|
+
finally {
|
|
723
|
+
restoreEditor();
|
|
724
|
+
}
|
|
154
725
|
}
|
|
155
|
-
function
|
|
726
|
+
function waitForLinxDialogManualRedirect(manualRedirectPromise, signal) {
|
|
727
|
+
if (!signal) {
|
|
728
|
+
return manualRedirectPromise;
|
|
729
|
+
}
|
|
730
|
+
if (signal.aborted) {
|
|
731
|
+
return Promise.resolve('');
|
|
732
|
+
}
|
|
156
733
|
return new Promise((resolve, reject) => {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
734
|
+
const onAbort = () => resolve('');
|
|
735
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
736
|
+
manualRedirectPromise
|
|
737
|
+
.then((value) => {
|
|
738
|
+
signal.removeEventListener('abort', onAbort);
|
|
739
|
+
resolve(value);
|
|
740
|
+
})
|
|
741
|
+
.catch((error) => {
|
|
742
|
+
signal.removeEventListener('abort', onAbort);
|
|
743
|
+
reject(error);
|
|
160
744
|
});
|
|
161
|
-
child.once('error', reject);
|
|
162
|
-
child.once('close', (exitCode, signal) => resolve({ exitCode, signal }));
|
|
163
745
|
});
|
|
164
746
|
}
|
|
747
|
+
async function promptForLinxManualRedirectUrl(interactive, signal) {
|
|
748
|
+
if (typeof interactive.showExtensionInput !== 'function') {
|
|
749
|
+
throw new Error('Manual redirect paste is not available in this terminal. Run `linx login` in another shell if the browser callback is blocked.');
|
|
750
|
+
}
|
|
751
|
+
const redirect = await interactive.showExtensionInput([
|
|
752
|
+
'Paste final redirect URL',
|
|
753
|
+
'If the browser cannot return to this terminal, paste the full callback URL below.',
|
|
754
|
+
].join('\n'), 'http://127.0.0.1:PORT/auth/callback?code=...&state=...&iss=...', signal ? { signal } : undefined);
|
|
755
|
+
const trimmed = typeof redirect === 'string' ? redirect.trim() : '';
|
|
756
|
+
if (!trimmed) {
|
|
757
|
+
throw new Error('Login cancelled');
|
|
758
|
+
}
|
|
759
|
+
return trimmed;
|
|
760
|
+
}
|
|
761
|
+
function syncRuntimeCredential(interactive) {
|
|
762
|
+
const authStorage = interactive.session?.modelRegistry?.authStorage;
|
|
763
|
+
const credential = authStorage?.get?.(LINX_PROVIDER_ID);
|
|
764
|
+
if (credential?.type === 'oauth' && typeof credential.access === 'string' && credential.access) {
|
|
765
|
+
authStorage.setRuntimeApiKey?.(LINX_PROVIDER_ID, credential.access);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
if (credential?.type === 'api_key' && typeof credential.key === 'string' && credential.key) {
|
|
769
|
+
authStorage.setRuntimeApiKey?.(LINX_PROVIDER_ID, credential.key);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
function showLinxLoginUrl(interactive, info) {
|
|
773
|
+
const lines = [
|
|
774
|
+
'\x1b[1mLinX Cloud authorization\x1b[22m',
|
|
775
|
+
'Complete consent in the browser, then return here.',
|
|
776
|
+
'',
|
|
777
|
+
`\x1b[36m${info.url}\x1b[39m`,
|
|
778
|
+
];
|
|
779
|
+
if (info.instructions) {
|
|
780
|
+
lines.push('', `\x1b[2m${info.instructions}\x1b[22m`);
|
|
781
|
+
}
|
|
782
|
+
interactive.chatContainer?.addChild?.(new Text(lines.join('\n'), 1, 0));
|
|
783
|
+
interactive.ui?.requestRender?.();
|
|
784
|
+
}
|
|
785
|
+
function openLoginUrl(url, interactive) {
|
|
786
|
+
openExternalUrl(url, interactive);
|
|
787
|
+
}
|
|
788
|
+
function openExternalUrl(url, interactive) {
|
|
789
|
+
if (typeof interactive.openExternal === 'function') {
|
|
790
|
+
interactive.openExternal(url);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';
|
|
794
|
+
const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
795
|
+
const child = spawn(command, args, {
|
|
796
|
+
detached: true,
|
|
797
|
+
stdio: 'ignore',
|
|
798
|
+
shell: false,
|
|
799
|
+
});
|
|
800
|
+
child.unref();
|
|
801
|
+
}
|
|
802
|
+
function prefillLoginCommand(interactive) {
|
|
803
|
+
interactive.editor?.setText?.('/login');
|
|
804
|
+
interactive.ui?.setFocus?.(interactive.editor);
|
|
805
|
+
interactive.ui?.requestRender?.();
|
|
806
|
+
}
|
|
165
807
|
function patchHeader(interactive) {
|
|
166
|
-
const originalInit = interactive.init
|
|
808
|
+
const originalInit = interactive.init?.bind(interactive);
|
|
809
|
+
if (typeof originalInit !== 'function') {
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
167
812
|
interactive.init = async function patchedInit() {
|
|
168
813
|
await originalInit();
|
|
814
|
+
if (this[LINX_AUTH_LOGIN_ON_INIT]) {
|
|
815
|
+
const reason = typeof this[LINX_AUTH_LOGIN_ON_INIT] === 'string'
|
|
816
|
+
? this[LINX_AUTH_LOGIN_ON_INIT]
|
|
817
|
+
: 'startup';
|
|
818
|
+
this[LINX_AUTH_LOGIN_ON_INIT] = false;
|
|
819
|
+
queueMicrotask(() => startLinxCloudLogin(this, { reason }));
|
|
820
|
+
}
|
|
169
821
|
const quietStartup = this.options?.verbose ? false : this.settingsManager?.getQuietStartup?.();
|
|
170
822
|
if (quietStartup) {
|
|
171
823
|
return;
|
|
172
824
|
}
|
|
173
825
|
let profileDisplayName = null;
|
|
174
|
-
const replacement = new LinxWelcomeCard(() =>
|
|
826
|
+
const replacement = new LinxWelcomeCard(() => buildLinxWelcomeCardState(this, profileDisplayName));
|
|
175
827
|
const currentHeader = this.customHeader ?? this.builtInHeader;
|
|
176
828
|
const index = this.headerContainer?.children?.indexOf?.(currentHeader) ?? -1;
|
|
177
829
|
if (index >= 0) {
|
|
@@ -227,13 +879,13 @@ class LinxWelcomeCard {
|
|
|
227
879
|
];
|
|
228
880
|
}
|
|
229
881
|
}
|
|
230
|
-
function
|
|
882
|
+
export function buildLinxWelcomeCardState(interactive, profileDisplayName = null) {
|
|
231
883
|
const credentials = loadCredentials();
|
|
232
884
|
const webId = credentials?.webId ?? 'not logged in';
|
|
233
885
|
const workspace = interactive?.sessionManager?.getCwd?.() || process.cwd();
|
|
234
886
|
const sessionId = interactive?.sessionManager?.getSessionId?.();
|
|
235
887
|
const sessionName = interactive?.sessionManager?.getSessionName?.();
|
|
236
|
-
const session = sessionName && sessionId ? `${sessionName} (${
|
|
888
|
+
const session = sessionName && sessionId ? `${sessionName} (${formatSessionId(sessionId)})` : formatSessionId(sessionId);
|
|
237
889
|
const model = interactive?.session?.model?.id ?? 'unknown-model';
|
|
238
890
|
return {
|
|
239
891
|
webId,
|
|
@@ -243,13 +895,38 @@ function buildHeaderState(interactive, profileDisplayName = null) {
|
|
|
243
895
|
workspace,
|
|
244
896
|
session,
|
|
245
897
|
next: [
|
|
246
|
-
|
|
247
|
-
|
|
898
|
+
safeKeyHint('tui.input.submit', 'send', 'enter send'),
|
|
899
|
+
safeKeyHint('app.model.select', 'model', 'ctrl+l model'),
|
|
248
900
|
rawKeyHint('/login', 'auth'),
|
|
249
|
-
rawKeyHint('/
|
|
901
|
+
rawKeyHint('/hotkeys', 'keymap'),
|
|
250
902
|
].join(' \x1b[2m·\x1b[22m '),
|
|
251
903
|
};
|
|
252
904
|
}
|
|
905
|
+
function safeKeyHint(command, label, fallback) {
|
|
906
|
+
try {
|
|
907
|
+
return keyHint(command, label);
|
|
908
|
+
}
|
|
909
|
+
catch (error) {
|
|
910
|
+
if (isThemeInitializationError(error)) {
|
|
911
|
+
return fallback;
|
|
912
|
+
}
|
|
913
|
+
throw error;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
function isThemeInitializationError(error) {
|
|
917
|
+
return error instanceof Error && error.message.includes('Theme not initialized');
|
|
918
|
+
}
|
|
919
|
+
function ensurePiThemeInitialized(interactive) {
|
|
920
|
+
try {
|
|
921
|
+
keyHint('tui.select.cancel', 'cancel');
|
|
922
|
+
}
|
|
923
|
+
catch (error) {
|
|
924
|
+
if (!isThemeInitializationError(error)) {
|
|
925
|
+
throw error;
|
|
926
|
+
}
|
|
927
|
+
initTheme(interactive?.settingsManager?.getTheme?.());
|
|
928
|
+
}
|
|
929
|
+
}
|
|
253
930
|
function renderField(label, value, width) {
|
|
254
931
|
const prefix = `\x1b[2m${label}\x1b[22m`;
|
|
255
932
|
const paddedPrefix = prefix + ' '.repeat(Math.max(1, 10 - visibleWidth(prefix)));
|
|
@@ -264,11 +941,11 @@ function wrapAndPad(line, width) {
|
|
|
264
941
|
? wrapped.map((entry) => padLine(entry, width))
|
|
265
942
|
: [padLine('', width)];
|
|
266
943
|
}
|
|
267
|
-
function
|
|
944
|
+
function formatSessionId(sessionId) {
|
|
268
945
|
if (typeof sessionId !== 'string' || !sessionId.trim()) {
|
|
269
946
|
return 'new session';
|
|
270
947
|
}
|
|
271
|
-
return sessionId.
|
|
948
|
+
return sessionId.trim();
|
|
272
949
|
}
|
|
273
950
|
function padLine(line, width) {
|
|
274
951
|
const visible = visibleWidth(line);
|
|
@@ -277,12 +954,50 @@ function padLine(line, width) {
|
|
|
277
954
|
}
|
|
278
955
|
return `${line}${' '.repeat(width - visible)}`;
|
|
279
956
|
}
|
|
957
|
+
function readLinxCliVersion() {
|
|
958
|
+
try {
|
|
959
|
+
const raw = readFileSync(new URL('../../../package.json', import.meta.url), 'utf-8');
|
|
960
|
+
const pkg = JSON.parse(raw);
|
|
961
|
+
return typeof pkg.version === 'string' && pkg.version.trim() ? pkg.version.trim() : '0.1.0';
|
|
962
|
+
}
|
|
963
|
+
catch {
|
|
964
|
+
return '0.1.0';
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
export function isVersionNewer(candidate, current) {
|
|
968
|
+
const candidateVersion = parseSemverLike(candidate);
|
|
969
|
+
const currentVersion = parseSemverLike(current);
|
|
970
|
+
if (!candidateVersion || !currentVersion) {
|
|
971
|
+
return candidate !== current;
|
|
972
|
+
}
|
|
973
|
+
for (const key of ['major', 'minor', 'patch']) {
|
|
974
|
+
if (candidateVersion[key] > currentVersion[key]) {
|
|
975
|
+
return true;
|
|
976
|
+
}
|
|
977
|
+
if (candidateVersion[key] < currentVersion[key]) {
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
return !candidateVersion.prerelease && currentVersion.prerelease;
|
|
982
|
+
}
|
|
983
|
+
function parseSemverLike(version) {
|
|
984
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+.+)?$/.exec(version.trim());
|
|
985
|
+
if (!match) {
|
|
986
|
+
return null;
|
|
987
|
+
}
|
|
988
|
+
return {
|
|
989
|
+
major: Number(match[1]),
|
|
990
|
+
minor: Number(match[2]),
|
|
991
|
+
patch: Number(match[3]),
|
|
992
|
+
prerelease: Boolean(match[4]),
|
|
993
|
+
};
|
|
994
|
+
}
|
|
280
995
|
function resolveRuntimeProviderLabel(interactive) {
|
|
281
996
|
const bridge = interactive?.runtimeHost?.linxAuthBridge ?? interactive?.linxAuthBridge;
|
|
282
997
|
if (bridge?.providerLabel) {
|
|
283
998
|
return bridge.providerLabel;
|
|
284
999
|
}
|
|
285
|
-
return 'undefineds
|
|
1000
|
+
return 'undefineds';
|
|
286
1001
|
}
|
|
287
1002
|
async function suppressPodStatusOutput(operation) {
|
|
288
1003
|
if (process.env.LINX_TUI_SHOW_POD_STATUS === '1') {
|
|
@@ -327,4 +1042,13 @@ function stripPodStatusLines(input) {
|
|
|
327
1042
|
.replace(new RegExp(String.raw `Using WebID:\s*${urlPattern}[ \t]*(?:\r?\n)?`, 'g'), '')
|
|
328
1043
|
.replace(/Successfully connected to Solid Pod[ \t]*(?:\r?\n)?/g, '');
|
|
329
1044
|
}
|
|
1045
|
+
function isRecord(value) {
|
|
1046
|
+
return typeof value === 'object' && value !== null;
|
|
1047
|
+
}
|
|
1048
|
+
function stripAnsi(text) {
|
|
1049
|
+
return text.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '');
|
|
1050
|
+
}
|
|
1051
|
+
function isPromiseLike(value) {
|
|
1052
|
+
return isRecord(value) && typeof value.then === 'function';
|
|
1053
|
+
}
|
|
330
1054
|
//# sourceMappingURL=branding.js.map
|