@undefineds.co/linx 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/README.md +58 -23
  2. package/dist/generated/version.js +1 -1
  3. package/dist/generated/version.js.map +1 -1
  4. package/dist/index.js +334 -162
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/account-session.js +4 -8
  7. package/dist/lib/account-session.js.map +1 -1
  8. package/dist/lib/ai-command.js +228 -178
  9. package/dist/lib/ai-command.js.map +1 -1
  10. package/dist/lib/auto-mode/archive.js +38 -7
  11. package/dist/lib/auto-mode/archive.js.map +1 -1
  12. package/dist/lib/auto-mode/auth.js.map +1 -1
  13. package/dist/lib/auto-mode/display.js +71 -45
  14. package/dist/lib/auto-mode/display.js.map +1 -1
  15. package/dist/lib/auto-mode/format.js +9 -7
  16. package/dist/lib/auto-mode/format.js.map +1 -1
  17. package/dist/lib/auto-mode/hooks/claude.js +12 -2
  18. package/dist/lib/auto-mode/hooks/claude.js.map +1 -1
  19. package/dist/lib/auto-mode/hooks/codex.js +17 -7
  20. package/dist/lib/auto-mode/hooks/codex.js.map +1 -1
  21. package/dist/lib/auto-mode/hooks/index.js +28 -8
  22. package/dist/lib/auto-mode/hooks/index.js.map +1 -1
  23. package/dist/lib/auto-mode/pod-ai.js +20 -37
  24. package/dist/lib/auto-mode/pod-ai.js.map +1 -1
  25. package/dist/lib/auto-mode/pod-approval.js +124 -195
  26. package/dist/lib/auto-mode/pod-approval.js.map +1 -1
  27. package/dist/lib/auto-mode/pod-persistence.js +169 -90
  28. package/dist/lib/auto-mode/pod-persistence.js.map +1 -1
  29. package/dist/lib/auto-mode/runner.js +683 -81
  30. package/dist/lib/auto-mode/runner.js.map +1 -1
  31. package/dist/lib/auto-mode/secretary.js +186 -41
  32. package/dist/lib/auto-mode/secretary.js.map +1 -1
  33. package/dist/lib/auto-mode-command.js +32 -32
  34. package/dist/lib/auto-mode-command.js.map +1 -1
  35. package/dist/lib/chat-api.js +242 -50
  36. package/dist/lib/chat-api.js.map +1 -1
  37. package/dist/lib/codex-plugin/bridge.js +164 -17
  38. package/dist/lib/codex-plugin/bridge.js.map +1 -1
  39. package/dist/lib/codex-plugin/codex-native-proxy.js +370 -34
  40. package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -1
  41. package/dist/lib/credentials-store.js +33 -42
  42. package/dist/lib/credentials-store.js.map +1 -1
  43. package/dist/lib/linx-cloud-errors.js +61 -0
  44. package/dist/lib/linx-cloud-errors.js.map +1 -0
  45. package/dist/lib/linx-tui-contract.js +8 -5
  46. package/dist/lib/linx-tui-contract.js.map +1 -1
  47. package/dist/lib/login-command.js +9 -2
  48. package/dist/lib/login-command.js.map +1 -1
  49. package/dist/lib/models.js +3 -20
  50. package/dist/lib/models.js.map +1 -1
  51. package/dist/lib/oidc-auth.js +143 -17
  52. package/dist/lib/oidc-auth.js.map +1 -1
  53. package/dist/lib/oidc-session-storage.js +2 -6
  54. package/dist/lib/oidc-session-storage.js.map +1 -1
  55. package/dist/lib/pi-adapter/auto-input-controller.js +988 -0
  56. package/dist/lib/pi-adapter/auto-input-controller.js.map +1 -0
  57. package/dist/lib/pi-adapter/backend-command.js +2 -0
  58. package/dist/lib/pi-adapter/backend-command.js.map +1 -0
  59. package/dist/lib/pi-adapter/backend-credentials.js +80 -0
  60. package/dist/lib/pi-adapter/backend-credentials.js.map +1 -0
  61. package/dist/lib/pi-adapter/branding.js +246 -108
  62. package/dist/lib/pi-adapter/branding.js.map +1 -1
  63. package/dist/lib/pi-adapter/control-state.js +72 -0
  64. package/dist/lib/pi-adapter/control-state.js.map +1 -0
  65. package/dist/lib/pi-adapter/interactive.js +2634 -30
  66. package/dist/lib/pi-adapter/interactive.js.map +1 -1
  67. package/dist/lib/pi-adapter/pod-approval.js +382 -210
  68. package/dist/lib/pi-adapter/pod-approval.js.map +1 -1
  69. package/dist/lib/pi-adapter/pod-mirror-mapping.js +71 -17
  70. package/dist/lib/pi-adapter/pod-mirror-mapping.js.map +1 -1
  71. package/dist/lib/pi-adapter/pod-mirror.js +531 -64
  72. package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
  73. package/dist/lib/pi-adapter/pod-native.js +81 -85
  74. package/dist/lib/pi-adapter/pod-native.js.map +1 -1
  75. package/dist/lib/pi-adapter/pod-status-output.js +54 -0
  76. package/dist/lib/pi-adapter/pod-status-output.js.map +1 -0
  77. package/dist/lib/pi-adapter/runtime.js +458 -228
  78. package/dist/lib/pi-adapter/runtime.js.map +1 -1
  79. package/dist/lib/pi-adapter/session-control.js +509 -0
  80. package/dist/lib/pi-adapter/session-control.js.map +1 -0
  81. package/dist/lib/pi-adapter/session.js +35 -22
  82. package/dist/lib/pi-adapter/session.js.map +1 -1
  83. package/dist/lib/pi-adapter/stream.js +89 -32
  84. package/dist/lib/pi-adapter/stream.js.map +1 -1
  85. package/dist/lib/pi-adapter/sync-recovery.js +89 -0
  86. package/dist/lib/pi-adapter/sync-recovery.js.map +1 -0
  87. package/dist/lib/pi-adapter/web-fetch.js +13 -14
  88. package/dist/lib/pi-adapter/web-fetch.js.map +1 -1
  89. package/dist/lib/pod-chat-store.js +254 -78
  90. package/dist/lib/pod-chat-store.js.map +1 -1
  91. package/dist/lib/pod-data-session.js +156 -35
  92. package/dist/lib/pod-data-session.js.map +1 -1
  93. package/dist/lib/solid-auth-store.js +27 -0
  94. package/dist/lib/solid-auth-store.js.map +1 -0
  95. package/dist/lib/solid-auth.js +2 -4
  96. package/dist/lib/solid-auth.js.map +1 -1
  97. package/dist/lib/solid-client-credentials-login.js +100 -0
  98. package/dist/lib/solid-client-credentials-login.js.map +1 -0
  99. package/dist/lib/solid-local-store.js +31 -0
  100. package/dist/lib/solid-local-store.js.map +1 -0
  101. package/dist/lib/symphony/archive.js +328 -18
  102. package/dist/lib/symphony/archive.js.map +1 -1
  103. package/dist/lib/symphony/pod-projection.js +2222 -0
  104. package/dist/lib/symphony/pod-projection.js.map +1 -0
  105. package/dist/lib/symphony-command.js +602 -178
  106. package/dist/lib/symphony-command.js.map +1 -1
  107. package/dist/lib/sync-checkpoint-store.js +74 -0
  108. package/dist/lib/sync-checkpoint-store.js.map +1 -0
  109. package/dist/skills/symphony/SKILL.md +665 -0
  110. package/package.json +15 -9
  111. package/vendor/agent-runtime/dist/agent-runtime.d.ts +137 -0
  112. package/vendor/agent-runtime/dist/agent-runtime.js +211 -0
  113. package/vendor/agent-runtime/dist/auto-mode.d.ts +78 -13
  114. package/vendor/agent-runtime/dist/auto-mode.js +288 -31
  115. package/vendor/agent-runtime/dist/control-plane.d.ts +28 -0
  116. package/vendor/agent-runtime/dist/control-plane.js +79 -0
  117. package/vendor/agent-runtime/dist/file-sync.d.ts +157 -0
  118. package/vendor/agent-runtime/dist/file-sync.js +314 -0
  119. package/vendor/agent-runtime/dist/index.d.ts +7 -0
  120. package/vendor/agent-runtime/dist/index.js +7 -0
  121. package/vendor/agent-runtime/dist/reconciler.d.ts +117 -0
  122. package/vendor/agent-runtime/dist/reconciler.js +361 -0
  123. package/vendor/agent-runtime/dist/symphony.d.ts +128 -8
  124. package/vendor/agent-runtime/dist/symphony.js +362 -57
  125. package/vendor/agent-runtime/dist/sync.d.ts +271 -0
  126. package/vendor/agent-runtime/dist/sync.js +550 -0
  127. package/vendor/agent-runtime/dist/thread-reconciler-controller.d.ts +58 -0
  128. package/vendor/agent-runtime/dist/thread-reconciler-controller.js +137 -0
  129. package/vendor/agent-runtime/dist/turn-controller.js +2 -2
  130. package/vendor/agent-runtime/dist/wake-scheduler.d.ts +67 -0
  131. package/vendor/agent-runtime/dist/wake-scheduler.js +194 -0
  132. package/vendor/agent-runtime/package.json +8 -1
  133. package/vendor/pi-web-access/CHANGELOG.md +387 -0
  134. package/vendor/pi-web-access/LICENSE +21 -0
  135. package/vendor/pi-web-access/README.md +352 -0
  136. package/vendor/pi-web-access/activity.ts +101 -0
  137. package/vendor/pi-web-access/banner.png +0 -0
  138. package/vendor/pi-web-access/chrome-cookies.ts +322 -0
  139. package/vendor/pi-web-access/code-search.ts +107 -0
  140. package/vendor/pi-web-access/curator-page.ts +3359 -0
  141. package/vendor/pi-web-access/curator-server.ts +605 -0
  142. package/vendor/pi-web-access/exa.ts +520 -0
  143. package/vendor/pi-web-access/extract.ts +641 -0
  144. package/vendor/pi-web-access/gemini-api.ts +112 -0
  145. package/vendor/pi-web-access/gemini-search.ts +361 -0
  146. package/vendor/pi-web-access/gemini-url-context.ts +126 -0
  147. package/vendor/pi-web-access/gemini-web-config.ts +52 -0
  148. package/vendor/pi-web-access/gemini-web.ts +396 -0
  149. package/vendor/pi-web-access/github-api.ts +196 -0
  150. package/vendor/pi-web-access/github-extract.ts +634 -0
  151. package/vendor/pi-web-access/index.ts +2346 -0
  152. package/vendor/pi-web-access/package.json +45 -0
  153. package/vendor/pi-web-access/pdf-extract.ts +192 -0
  154. package/vendor/pi-web-access/perplexity.ts +195 -0
  155. package/vendor/pi-web-access/pi-web-fetch-demo.mp4 +0 -0
  156. package/vendor/pi-web-access/rsc-extract.ts +338 -0
  157. package/vendor/pi-web-access/skills/librarian/SKILL.md +195 -0
  158. package/vendor/pi-web-access/storage.ts +72 -0
  159. package/vendor/pi-web-access/summary-review.ts +276 -0
  160. package/vendor/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +41 -0
  161. package/vendor/pi-web-access/test/pdf-extract.test.mjs +95 -0
  162. package/vendor/pi-web-access/utils.ts +44 -0
  163. package/vendor/pi-web-access/video-extract.ts +378 -0
  164. package/vendor/pi-web-access/youtube-extract.ts +310 -0
  165. package/dist/lib/pi-adapter/auth.js +0 -68
  166. package/dist/lib/pi-adapter/auth.js.map +0 -1
  167. package/dist/lib/pi-adapter/pod-tools.js +0 -140
  168. package/dist/lib/pi-adapter/pod-tools.js.map +0 -1
  169. package/dist/skills/drizzle-solid/SKILL.md +0 -340
  170. package/dist/skills/pod-storage/SKILL.md +0 -100
  171. package/dist/skills/solid-modeling/SKILL.md +0 -274
  172. package/dist/skills/xpod-componentsjs/SKILL.md +0 -284
@@ -1,16 +1,19 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { homedir } from 'node:os';
3
- import { basename, join } from 'node:path';
2
+ import { basename } from 'node:path';
4
3
  import { readFileSync } from 'node:fs';
5
- import { LINX_HOME_DIRNAME } from '@undefineds.co/models/client';
6
- import { keyHint, LoginDialogComponent, rawKeyHint } from '@mariozechner/pi-coding-agent';
7
- import { Text, truncateToWidth, visibleWidth, wrapTextWithAnsi } from '@mariozechner/pi-tui';
4
+ import { keyHint, LoginDialogComponent, rawKeyHint } from '@earendil-works/pi-coding-agent';
5
+ import { Text, truncateToWidth, visibleWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
8
6
  import { clearAccountSession } from '../account-session.js';
9
7
  import { clearCredentials, loadCredentials } from '../credentials-store.js';
10
8
  import { clearOidcSessionStorage } from '../oidc-session-storage.js';
9
+ import { persistSolidClientCredentialsLogin } from '../solid-client-credentials-login.js';
11
10
  import { extractUsernameFromWebId, resolveProfileDisplayName } from '../profile-identity.js';
12
11
  import { LINX_TUI_KEYMAP_COMMAND, LINX_TUI_KEYMAP_LABEL, LINX_TUI_LOGIN_COMMAND } from '../linx-tui-contract.js';
13
- export const LINX_AGENT_DIR = join(homedir(), LINX_HOME_DIRNAME, 'agent');
12
+ import { suppressPodStatusOutput } from './pod-status-output.js';
13
+ import { LINX_RUNTIME_MANAGED_AUTH_KEY } from './runtime.js';
14
+ import { formatLinxCliErrorMessage } from '../linx-cloud-errors.js';
15
+ import { getSolidLinxAgentDir } from '../solid-local-store.js';
16
+ export const LINX_AGENT_DIR = getSolidLinxAgentDir();
14
17
  export const LINX_UPDATE_PACKAGE_NAME = '@undefineds.co/linx';
15
18
  export const LINX_CHANGELOG_URL = 'https://github.com/undefineds-co/linx-cli/releases';
16
19
  export const LINX_CLI_VERSION = readLinxCliVersion();
@@ -20,9 +23,11 @@ const LINX_AUTH_PENDING_RETRY = Symbol.for('linx.tui.authPendingRetry');
20
23
  const LINX_AUTH_LOGIN_SCHEDULED = Symbol.for('linx.tui.authLoginScheduled');
21
24
  const LINX_AUTH_REPORTING_ERROR = Symbol.for('linx.tui.authReportingError');
22
25
  const LINX_UPDATE_IN_PROGRESS = Symbol.for('linx.tui.updateInProgress');
26
+ const LINX_UPDATE_CHECK_SCHEDULED = Symbol.for('linx.tui.updateCheckScheduled');
27
+ const LINX_SUPPRESS_UPSTREAM_PI_UPDATE = Symbol.for('linx.tui.suppressUpstreamPiUpdate');
23
28
  const LINX_PROVIDER_ID = 'undefineds';
24
29
  const AUTH_OPTION_BROWSER = 'Authorize in browser';
25
- const AUTH_OPTION_API_KEY = 'Enter API key';
30
+ const AUTH_OPTION_CLIENT_CREDENTIALS = 'Enter Solid client credentials';
26
31
  const AUTH_OPTION_EXIT = 'Exit';
27
32
  const UPDATE_OPTION_INSTALL = 'Install update and restart';
28
33
  const UPDATE_OPTION_CHANGELOG = 'Open changelog';
@@ -55,34 +60,77 @@ function patchTerminalTitle(interactive) {
55
60
  };
56
61
  }
57
62
  function patchVersionCheck(interactive) {
63
+ const originalRun = interactive.run?.bind(interactive);
64
+ if (typeof originalRun === 'function') {
65
+ interactive.run = async function patchedLinxRun(...args) {
66
+ this[LINX_SUPPRESS_UPSTREAM_PI_UPDATE] = true;
67
+ return originalRun(...args);
68
+ };
69
+ }
70
+ const originalInit = interactive.init?.bind(interactive);
71
+ if (typeof originalInit === 'function') {
72
+ interactive.init = async function patchedLinxVersionInit(...args) {
73
+ await originalInit(...args);
74
+ scheduleLinxVersionCheck(this);
75
+ };
76
+ }
58
77
  interactive.checkForNewVersion = async function patchedCheckForNewVersion() {
59
- if (process.env.PI_OFFLINE) {
60
- return undefined;
61
- }
62
- try {
63
- const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(LINX_UPDATE_PACKAGE_NAME)}/latest`, {
64
- signal: AbortSignal.timeout(5000),
65
- });
66
- if (!response.ok) {
67
- return undefined;
68
- }
69
- const body = await response.json();
70
- const latest = typeof body.version === 'string' ? body.version.trim() : '';
71
- if (!latest || !isVersionNewer(latest, LINX_CLI_VERSION)) {
72
- return undefined;
73
- }
74
- return latest;
75
- }
76
- catch {
77
- return undefined;
78
- }
78
+ return checkForNewLinxVersion();
79
79
  };
80
80
  }
81
81
  function patchUpdateNotification(interactive) {
82
82
  interactive.showNewVersionNotification = function patchedShowNewVersionNotification(newVersion) {
83
- void showLinxUpdateSelector(this, newVersion);
83
+ if (this[LINX_SUPPRESS_UPSTREAM_PI_UPDATE]) {
84
+ return;
85
+ }
86
+ if (shouldDeferLinxUpdateNotification(this)) {
87
+ return;
88
+ }
89
+ const normalizedVersion = normalizeLinxUpdateVersion(newVersion);
90
+ if (!normalizedVersion) {
91
+ return;
92
+ }
93
+ void showLinxUpdateSelector(this, normalizedVersion);
84
94
  };
85
95
  }
96
+ function scheduleLinxVersionCheck(interactive) {
97
+ if (interactive[LINX_UPDATE_CHECK_SCHEDULED]) {
98
+ return;
99
+ }
100
+ interactive[LINX_UPDATE_CHECK_SCHEDULED] = true;
101
+ queueMicrotask(() => {
102
+ void checkForNewLinxVersion()
103
+ .then((latest) => {
104
+ if (!latest || shouldDeferLinxUpdateNotification(interactive)) {
105
+ return;
106
+ }
107
+ void showLinxUpdateSelector(interactive, latest);
108
+ })
109
+ .catch(() => undefined);
110
+ });
111
+ }
112
+ async function checkForNewLinxVersion() {
113
+ if (process.env.PI_OFFLINE) {
114
+ return undefined;
115
+ }
116
+ try {
117
+ const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(LINX_UPDATE_PACKAGE_NAME)}/latest`, {
118
+ signal: AbortSignal.timeout(5000),
119
+ });
120
+ if (!response.ok) {
121
+ return undefined;
122
+ }
123
+ const body = await response.json();
124
+ const latest = typeof body.version === 'string' ? body.version.trim() : '';
125
+ if (!latest || !isVersionNewer(latest, LINX_CLI_VERSION)) {
126
+ return undefined;
127
+ }
128
+ return latest;
129
+ }
130
+ catch {
131
+ return undefined;
132
+ }
133
+ }
86
134
  async function showLinxUpdateSelector(interactive, newVersion) {
87
135
  if (interactive[LINX_UPDATE_IN_PROGRESS]) {
88
136
  return;
@@ -94,10 +142,11 @@ async function showLinxUpdateSelector(interactive, newVersion) {
94
142
  `Current ${LINX_CLI_VERSION} -> latest ${newVersion}`,
95
143
  'Choose how to handle this update.',
96
144
  ].join('\n');
97
- const options = [UPDATE_OPTION_INSTALL, UPDATE_OPTION_CHANGELOG, UPDATE_OPTION_LATER];
98
- const selected = typeof interactive.showExtensionSelector === 'function'
145
+ const options = [UPDATE_OPTION_LATER, UPDATE_OPTION_INSTALL, UPDATE_OPTION_CHANGELOG];
146
+ const rawSelected = typeof interactive.showExtensionSelector === 'function'
99
147
  ? await interactive.showExtensionSelector(title, options)
100
148
  : undefined;
149
+ const selected = normalizeSelectorChoice(rawSelected, options);
101
150
  if (selected === UPDATE_OPTION_INSTALL) {
102
151
  await installLinxUpdateAndRestart(interactive, newVersion);
103
152
  return;
@@ -117,6 +166,28 @@ async function showLinxUpdateSelector(interactive, newVersion) {
117
166
  interactive[LINX_UPDATE_IN_PROGRESS] = false;
118
167
  }
119
168
  }
169
+ function shouldDeferLinxUpdateNotification(interactive) {
170
+ return Boolean(interactive[LINX_AUTH_LOGIN_IN_PROGRESS]
171
+ || interactive[LINX_AUTH_LOGIN_ON_INIT]
172
+ || interactive[LINX_AUTH_PENDING_RETRY]
173
+ || interactive[LINX_AUTH_LOGIN_SCHEDULED]);
174
+ }
175
+ function normalizeLinxUpdateVersion(value) {
176
+ const direct = normalizeNonEmptyString(value);
177
+ if (direct) {
178
+ return direct;
179
+ }
180
+ if (!isRecord(value)) {
181
+ return undefined;
182
+ }
183
+ for (const key of ['version', 'latest', 'latestVersion', 'packageVersion', 'newVersion']) {
184
+ const nested = normalizeNonEmptyString(value[key]);
185
+ if (nested) {
186
+ return nested;
187
+ }
188
+ }
189
+ return undefined;
190
+ }
120
191
  async function installLinxUpdateAndRestart(interactive, newVersion) {
121
192
  interactive.showStatus?.(`Installing LinX ${newVersion}...`);
122
193
  interactive.ui?.requestRender?.();
@@ -229,13 +300,15 @@ function patchAuthExpiredSessionEvents(interactive) {
229
300
  return;
230
301
  }
231
302
  interactive.handleEvent = async function patchedHandleEvent(event) {
232
- if (eventHasLinxAuthExpiredError(event)) {
303
+ const normalizedEvent = normalizeLinxCliErrorEvent(event);
304
+ if (eventHasLinxAuthExpiredError(normalizedEvent)) {
305
+ showLinxAuthExpiredRecoveryNotice(this);
233
306
  prepareLinxAuthExpiredRetry(this);
234
307
  suppressLinxAuthExpiredAssistantError(this);
235
308
  scheduleLinxCloudLogin(this, 'expired');
236
309
  return undefined;
237
310
  }
238
- const result = await originalHandleEvent(event);
311
+ const result = await originalHandleEvent(normalizedEvent);
239
312
  return result;
240
313
  };
241
314
  }
@@ -246,9 +319,11 @@ function patchAuthExpiredLoginPrompt(interactive) {
246
319
  }
247
320
  interactive.showError = function patchedShowError(errorMessage) {
248
321
  const text = typeof errorMessage === 'string' ? errorMessage : String(errorMessage);
249
- if (!isLinxAuthExpiredError(text)) {
250
- return originalShowError(errorMessage);
322
+ if (this[LINX_AUTH_REPORTING_ERROR] || !isLinxAuthExpiredError(text)) {
323
+ return originalShowError(formatLinxCliErrorMessage(errorMessage));
251
324
  }
325
+ showLinxAuthExpiredRecoveryNotice(this);
326
+ prepareLinxAuthExpiredRetry(this);
252
327
  scheduleLinxCloudLogin(this, 'expired');
253
328
  return undefined;
254
329
  };
@@ -264,10 +339,49 @@ function eventHasLinxAuthExpiredError(event) {
264
339
  return false;
265
340
  }
266
341
  const message = isRecord(event.message) ? event.message : undefined;
342
+ const topLevelErrorMessage = typeof event.errorMessage === 'string' ? event.errorMessage : '';
267
343
  const errorMessage = typeof message?.errorMessage === 'string' ? message.errorMessage : '';
268
344
  const error = isRecord(event.error) ? event.error : undefined;
269
345
  const nestedErrorMessage = typeof error?.errorMessage === 'string' ? error.errorMessage : '';
270
- return isLinxAuthExpiredError(`${errorMessage}\n${nestedErrorMessage}`);
346
+ return isLinxAuthExpiredError(`${topLevelErrorMessage}\n${errorMessage}\n${nestedErrorMessage}`);
347
+ }
348
+ function normalizeLinxCliErrorEvent(event) {
349
+ if (typeof event === 'string') {
350
+ const normalized = formatLinxCliErrorMessage(event);
351
+ return normalized === event ? event : normalized;
352
+ }
353
+ if (Array.isArray(event)) {
354
+ let changed = false;
355
+ const next = event.map((item) => {
356
+ const normalized = normalizeLinxCliErrorEvent(item);
357
+ if (normalized !== item) {
358
+ changed = true;
359
+ }
360
+ return normalized;
361
+ });
362
+ return changed ? next : event;
363
+ }
364
+ if (!isRecord(event)) {
365
+ return event;
366
+ }
367
+ let changed = false;
368
+ const next = {};
369
+ for (const [key, value] of Object.entries(event)) {
370
+ const normalized = normalizeLinxCliErrorEvent(value);
371
+ next[key] = normalized;
372
+ if (normalized !== value) {
373
+ changed = true;
374
+ }
375
+ }
376
+ return changed ? { ...event, ...next } : event;
377
+ }
378
+ function showLinxAuthExpiredRecoveryNotice(interactive) {
379
+ interactive.showStatus?.([
380
+ 'LinX Cloud login expired.',
381
+ 'Your message reached LinX, but the Cloud token was rejected.',
382
+ 'Choose a sign-in method below, or run /login if the selector is not visible.',
383
+ ].join('\n'));
384
+ interactive.ui?.requestRender?.();
271
385
  }
272
386
  async function startLinxCloudLogin(interactive, options = {}) {
273
387
  if (interactive[LINX_AUTH_LOGIN_IN_PROGRESS]) {
@@ -296,8 +410,8 @@ async function startLinxCloudLogin(interactive, options = {}) {
296
410
  await finishLinxAuthSuccess(interactive, reason, 'Browser authorization complete.');
297
411
  return;
298
412
  }
299
- if (selected === AUTH_OPTION_API_KEY) {
300
- await promptForLinxApiKey(interactive, reason);
413
+ if (selected === AUTH_OPTION_CLIENT_CREDENTIALS) {
414
+ await promptForLinxClientCredentials(interactive, reason);
301
415
  return;
302
416
  }
303
417
  if (selected === AUTH_OPTION_EXIT) {
@@ -358,13 +472,37 @@ function normalizeLinxLoginError(message) {
358
472
  }
359
473
  async function selectLinxAuthMethod(interactive, reason) {
360
474
  const title = buildLinxAuthPromptTitle(reason, resolveRuntimeProviderLabel(interactive));
361
- const options = [AUTH_OPTION_BROWSER, AUTH_OPTION_API_KEY, AUTH_OPTION_EXIT];
475
+ const options = [AUTH_OPTION_BROWSER, AUTH_OPTION_CLIENT_CREDENTIALS, AUTH_OPTION_EXIT];
362
476
  if (typeof interactive.showExtensionSelector === 'function') {
363
- return await interactive.showExtensionSelector(title, options);
477
+ return normalizeSelectorChoice(await interactive.showExtensionSelector(title, options), options);
364
478
  }
365
479
  showLinxAuthFallback(interactive, title, options);
366
480
  return undefined;
367
481
  }
482
+ function normalizeSelectorChoice(value, options) {
483
+ const direct = matchSelectorChoice(value, options);
484
+ if (direct) {
485
+ return direct;
486
+ }
487
+ if (!isRecord(value)) {
488
+ return undefined;
489
+ }
490
+ for (const key of ['value', 'label', 'title', 'name', 'display', 'text', 'option', 'id']) {
491
+ const match = matchSelectorChoice(value[key], options);
492
+ if (match) {
493
+ return match;
494
+ }
495
+ }
496
+ return undefined;
497
+ }
498
+ function matchSelectorChoice(value, options) {
499
+ const normalized = normalizeNonEmptyString(value);
500
+ if (!normalized) {
501
+ return undefined;
502
+ }
503
+ return options.find((option) => option === normalized)
504
+ ?? options.find((option) => stripAnsi(option).trim() === stripAnsi(normalized).trim());
505
+ }
368
506
  function buildLinxAuthPromptTitle(reason, providerLabel) {
369
507
  if (reason === 'startup') {
370
508
  return [
@@ -377,7 +515,7 @@ function buildLinxAuthPromptTitle(reason, providerLabel) {
377
515
  return [
378
516
  'LinX Cloud login expired',
379
517
  'Your current Solid token was rejected by LinX Cloud.',
380
- 'Re-authorize or provide an API key, then retry your message.',
518
+ 'Re-authorize or enter Solid client credentials, then retry your message.',
381
519
  ].join('\n');
382
520
  }
383
521
  return [
@@ -395,25 +533,37 @@ function showLinxAuthFallback(interactive, title, options) {
395
533
  ].join('\n'), 1, 0));
396
534
  interactive.ui?.requestRender?.();
397
535
  }
398
- async function promptForLinxApiKey(interactive, reason) {
536
+ async function promptForLinxClientCredentials(interactive, reason) {
399
537
  if (typeof interactive.showExtensionInput !== 'function') {
400
- interactive.showError?.('This terminal build cannot collect an API key inside the TUI.');
538
+ interactive.showError?.('This terminal build cannot collect Solid client credentials inside the TUI.');
401
539
  return;
402
540
  }
403
- const apiKey = await interactive.showExtensionInput([
404
- reason === 'expired' ? 'Enter LinX Cloud API key' : 'Use LinX Cloud API key',
405
- 'Paste a key for this TUI session. Press Escape to cancel.',
406
- ].join('\n'), 'linx API key');
407
- const trimmed = typeof apiKey === 'string' ? apiKey.trim() : '';
541
+ const credentials = await interactive.showExtensionInput([
542
+ reason === 'expired' ? 'Enter Solid client credentials' : 'Use Solid client credentials',
543
+ 'This is only for Solid/LinX identity. AI provider keys belong in `linx ai connect`.',
544
+ 'Format: client_id:client_secret',
545
+ 'Press Escape to cancel.',
546
+ ].join('\n'), 'client_id:client_secret');
547
+ const trimmed = typeof credentials === 'string' ? credentials.trim() : '';
408
548
  if (!trimmed) {
409
- interactive.showStatus?.('LinX Cloud API key entry cancelled.');
549
+ interactive.showStatus?.('Solid client credentials entry cancelled.');
410
550
  return;
411
551
  }
552
+ const result = await resolveSolidClientCredentialsLogin(interactive)(trimmed);
412
553
  const authStorage = interactive.session?.modelRegistry?.authStorage;
413
- authStorage?.setRuntimeApiKey?.(LINX_PROVIDER_ID, trimmed);
414
- authStorage?.set?.(LINX_PROVIDER_ID, { type: 'api_key', key: trimmed });
554
+ authStorage?.setRuntimeApiKey?.(LINX_PROVIDER_ID, LINX_RUNTIME_MANAGED_AUTH_KEY);
555
+ authStorage?.set?.(LINX_PROVIDER_ID, {
556
+ type: 'api_key',
557
+ key: LINX_RUNTIME_MANAGED_AUTH_KEY,
558
+ webId: result.webId,
559
+ podUrl: result.podUrl,
560
+ });
415
561
  await refreshLinxAuthState(interactive);
416
- await finishLinxAuthSuccess(interactive, reason, 'API key saved for this TUI session.');
562
+ await finishLinxAuthSuccess(interactive, reason, 'Solid client credentials saved to ~/.solid/auth.');
563
+ }
564
+ function resolveSolidClientCredentialsLogin(interactive) {
565
+ const override = interactive?.__linxPersistSolidClientCredentialsLogin ?? interactive?.__linxPersistSolidSecretLogin;
566
+ return typeof override === 'function' ? override : persistSolidClientCredentialsLogin;
417
567
  }
418
568
  async function finishLinxAuthSuccess(interactive, reason, detail) {
419
569
  const prefix = authStatusPrefix(reason);
@@ -619,10 +769,13 @@ function extractUserMessageText(message) {
619
769
  return text || undefined;
620
770
  }
621
771
  async function refreshLinxAuthState(interactive) {
772
+ clearLinxAuthPromptOnStart(interactive);
773
+ syncRuntimeCredential(interactive);
622
774
  interactive.session?.modelRegistry?.refresh?.();
623
775
  await interactive.updateAvailableProviderCount?.();
624
776
  interactive.ui?.requestRender?.();
625
777
  }
778
+ export const __testRefreshLinxAuthState = refreshLinxAuthState;
626
779
  function authStatusPrefix(reason) {
627
780
  if (reason === 'expired') {
628
781
  return 'LinX Cloud login refreshed.';
@@ -650,7 +803,6 @@ async function runLinxCloudLogin(interactive, authStorage, reason) {
650
803
  return promptForLinxManualRedirectUrl(interactive, signal);
651
804
  },
652
805
  });
653
- syncRuntimeCredential(interactive);
654
806
  }
655
807
  async function runLinxCloudBrowserLogin(interactive, authStorage, reason) {
656
808
  if (canRenderLinxLoginDialog(interactive)) {
@@ -712,7 +864,6 @@ async function runLinxCloudLoginDialog(interactive, authStorage, reason) {
712
864
  },
713
865
  signal: dialog.signal,
714
866
  });
715
- syncRuntimeCredential(interactive);
716
867
  }
717
868
  finally {
718
869
  restoreEditor();
@@ -755,13 +906,20 @@ async function promptForLinxManualRedirectUrl(interactive, signal) {
755
906
  }
756
907
  function syncRuntimeCredential(interactive) {
757
908
  const authStorage = interactive.session?.modelRegistry?.authStorage;
758
- const credential = authStorage?.get?.(LINX_PROVIDER_ID);
759
- if (credential?.type === 'oauth' && typeof credential.access === 'string' && credential.access) {
760
- authStorage.setRuntimeApiKey?.(LINX_PROVIDER_ID, credential.access);
761
- return;
762
- }
763
- if (credential?.type === 'api_key' && typeof credential.key === 'string' && credential.key) {
764
- authStorage.setRuntimeApiKey?.(LINX_PROVIDER_ID, credential.key);
909
+ authStorage?.setRuntimeApiKey?.(LINX_PROVIDER_ID, LINX_RUNTIME_MANAGED_AUTH_KEY);
910
+ }
911
+ function clearLinxAuthPromptOnStart(interactive) {
912
+ const candidates = [
913
+ interactive,
914
+ interactive?.runtimeHost,
915
+ interactive?.runtime,
916
+ interactive?.session,
917
+ ];
918
+ for (const candidate of candidates) {
919
+ const bridge = candidate?.linxAuthBridge;
920
+ if (bridge && typeof bridge === 'object') {
921
+ bridge.shouldPromptLoginOnStart = false;
922
+ }
765
923
  }
766
924
  }
767
925
  function showLinxLoginUrl(interactive, info) {
@@ -890,13 +1048,29 @@ export function buildLinxWelcomeCardState(interactive, profileDisplayName = null
890
1048
  workspace,
891
1049
  session,
892
1050
  next: [
893
- keyHint('tui.input.submit', 'send'),
894
- keyHint('app.model.select', 'model'),
895
- rawKeyHint(LINX_TUI_LOGIN_COMMAND, 'auth'),
896
- rawKeyHint(LINX_TUI_KEYMAP_COMMAND, LINX_TUI_KEYMAP_LABEL),
1051
+ safeKeyHint('tui.input.submit', 'send'),
1052
+ safeKeyHint('app.model.select', 'model'),
1053
+ safeRawKeyHint(LINX_TUI_LOGIN_COMMAND, 'auth'),
1054
+ safeRawKeyHint(LINX_TUI_KEYMAP_COMMAND, LINX_TUI_KEYMAP_LABEL),
897
1055
  ].join(' \x1b[2m·\x1b[22m '),
898
1056
  };
899
1057
  }
1058
+ function safeKeyHint(keybinding, description) {
1059
+ try {
1060
+ return keyHint(keybinding, description);
1061
+ }
1062
+ catch {
1063
+ return `\x1b[2m${keybinding}\x1b[22m \x1b[2m${description}\x1b[22m`;
1064
+ }
1065
+ }
1066
+ function safeRawKeyHint(key, description) {
1067
+ try {
1068
+ return rawKeyHint(key, description);
1069
+ }
1070
+ catch {
1071
+ return `\x1b[2m${key}\x1b[22m \x1b[2m${description}\x1b[22m`;
1072
+ }
1073
+ }
900
1074
  function renderField(label, value, width) {
901
1075
  const prefix = `\x1b[2m${label}\x1b[22m`;
902
1076
  const paddedPrefix = prefix + ' '.repeat(Math.max(1, 10 - visibleWidth(prefix)));
@@ -967,54 +1141,18 @@ function resolveRuntimeProviderLabel(interactive) {
967
1141
  if (bridge?.providerLabel) {
968
1142
  return bridge.providerLabel;
969
1143
  }
970
- return 'undefineds';
971
- }
972
- async function suppressPodStatusOutput(operation) {
973
- if (process.env.LINX_TUI_SHOW_POD_STATUS === '1') {
974
- return await operation();
975
- }
976
- const restoreStdout = patchPodStatusWriter(process.stdout);
977
- const restoreStderr = patchPodStatusWriter(process.stderr);
978
- try {
979
- return await operation();
980
- }
981
- finally {
982
- restoreStdout();
983
- restoreStderr();
984
- }
985
- }
986
- function patchPodStatusWriter(stream) {
987
- const originalWrite = stream.write.bind(stream);
988
- stream.write = function patchedWrite(chunk, encodingOrCallback, callback) {
989
- const encoding = typeof encodingOrCallback === 'string' ? encodingOrCallback : undefined;
990
- const onComplete = typeof encodingOrCallback === 'function' ? encodingOrCallback : callback;
991
- const text = typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString(encoding);
992
- const filtered = stripPodStatusLines(text);
993
- if (!filtered) {
994
- onComplete?.();
995
- return true;
996
- }
997
- if (typeof chunk === 'string') {
998
- return originalWrite(filtered, encodingOrCallback, callback);
999
- }
1000
- return originalWrite(Buffer.from(filtered, encoding), callback);
1001
- };
1002
- return () => {
1003
- ;
1004
- stream.write = originalWrite;
1005
- };
1006
- }
1007
- function stripPodStatusLines(input) {
1008
- const urlPattern = String.raw `https?:\/\/[A-Za-z0-9._~:/?#[\]@!$&'()*+,;=%-]+`;
1009
- return input
1010
- .replace(new RegExp(String.raw `\[Container\]\s*容器已存在:\s*${urlPattern}[ \t]*(?:\r?\n)?`, 'g'), '')
1011
- .replace(new RegExp(String.raw `Connecting to Solid Pod:\s*${urlPattern}[ \t]*(?:\r?\n)?`, 'g'), '')
1012
- .replace(new RegExp(String.raw `Using WebID:\s*${urlPattern}[ \t]*(?:\r?\n)?`, 'g'), '')
1013
- .replace(/Successfully connected to Solid Pod[ \t]*(?:\r?\n)?/g, '');
1144
+ return 'LinX Cloud';
1014
1145
  }
1015
1146
  function isRecord(value) {
1016
1147
  return typeof value === 'object' && value !== null;
1017
1148
  }
1149
+ function normalizeNonEmptyString(value) {
1150
+ if (typeof value !== 'string') {
1151
+ return undefined;
1152
+ }
1153
+ const normalized = value.trim();
1154
+ return normalized || undefined;
1155
+ }
1018
1156
  function stripAnsi(text) {
1019
1157
  return text.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '');
1020
1158
  }