@undefineds.co/linx 0.2.16 → 0.2.17

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 (119) hide show
  1. package/README.md +6 -3
  2. package/dist/generated/version.js +3 -0
  3. package/dist/generated/version.js.map +1 -0
  4. package/dist/index.js +139 -259
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/account-api.js +1 -1
  7. package/dist/lib/account-api.js.map +1 -1
  8. package/dist/lib/account-session.js +1 -1
  9. package/dist/lib/account-session.js.map +1 -1
  10. package/dist/lib/ai-command.js +105 -56
  11. package/dist/lib/ai-command.js.map +1 -1
  12. package/dist/lib/chat-api.js +19 -177
  13. package/dist/lib/chat-api.js.map +1 -1
  14. package/dist/lib/codex-plugin/bridge.js.map +1 -1
  15. package/dist/lib/codex-plugin/codex-native-proxy.js.map +1 -1
  16. package/dist/lib/codex-plugin/index.js.map +1 -1
  17. package/dist/lib/codex-plugin/runner.js.map +1 -1
  18. package/dist/lib/credentials-store.js +1 -7
  19. package/dist/lib/credentials-store.js.map +1 -1
  20. package/dist/lib/default-model.js +0 -1
  21. package/dist/lib/default-model.js.map +1 -1
  22. package/dist/lib/login-command.js +2 -17
  23. package/dist/lib/login-command.js.map +1 -1
  24. package/dist/lib/models.js +27 -2
  25. package/dist/lib/models.js.map +1 -1
  26. package/dist/lib/oidc-auth.js +13 -78
  27. package/dist/lib/oidc-auth.js.map +1 -1
  28. package/dist/lib/oidc-session-storage.js.map +1 -1
  29. package/dist/lib/pi-adapter/auth.js +5 -12
  30. package/dist/lib/pi-adapter/auth.js.map +1 -1
  31. package/dist/lib/pi-adapter/branding.js +76 -554
  32. package/dist/lib/pi-adapter/branding.js.map +1 -1
  33. package/dist/lib/pi-adapter/index.js.map +1 -1
  34. package/dist/lib/pi-adapter/interactive.js +4 -154
  35. package/dist/lib/pi-adapter/interactive.js.map +1 -1
  36. package/dist/lib/pi-adapter/runtime.js +25 -140
  37. package/dist/lib/pi-adapter/runtime.js.map +1 -1
  38. package/dist/lib/pi-adapter/stream.js +4 -154
  39. package/dist/lib/pi-adapter/stream.js.map +1 -1
  40. package/dist/lib/pi-adapter/theme.js.map +1 -1
  41. package/dist/lib/pod-chat-store.js +1 -1
  42. package/dist/lib/pod-chat-store.js.map +1 -1
  43. package/dist/lib/profile-identity.js +60 -16
  44. package/dist/lib/profile-identity.js.map +1 -1
  45. package/dist/lib/prompt.js.map +1 -1
  46. package/dist/lib/runtime-target.js +1 -1
  47. package/dist/lib/runtime-target.js.map +1 -1
  48. package/dist/lib/solid-auth.js.map +1 -1
  49. package/dist/lib/thread-utils.js.map +1 -1
  50. package/dist/lib/watch/archive.js +1 -1
  51. package/dist/lib/watch/archive.js.map +1 -1
  52. package/dist/lib/watch/auth.js +1 -1
  53. package/dist/lib/watch/auth.js.map +1 -1
  54. package/dist/lib/watch/codex-composer.js.map +1 -1
  55. package/dist/lib/watch/codex-footer.js.map +1 -1
  56. package/dist/lib/watch/codex-overlay.js.map +1 -1
  57. package/dist/lib/watch/codex-request-form.js +1 -1
  58. package/dist/lib/watch/codex-request-form.js.map +1 -1
  59. package/dist/lib/watch/codex-request-input.js.map +1 -1
  60. package/dist/lib/watch/display.js.map +1 -1
  61. package/dist/lib/watch/format.js.map +1 -1
  62. package/dist/lib/watch/hooks/claude.js +0 -4
  63. package/dist/lib/watch/hooks/claude.js.map +1 -1
  64. package/dist/lib/watch/hooks/codebuddy.js +0 -4
  65. package/dist/lib/watch/hooks/codebuddy.js.map +1 -1
  66. package/dist/lib/watch/hooks/codex.js +0 -4
  67. package/dist/lib/watch/hooks/codex.js.map +1 -1
  68. package/dist/lib/watch/hooks/index.js.map +1 -1
  69. package/dist/lib/watch/hooks/shared.js +1 -0
  70. package/dist/lib/watch/hooks/shared.js.map +1 -1
  71. package/dist/lib/watch/index.js.map +1 -1
  72. package/dist/lib/watch/pod-ai.js +37 -29
  73. package/dist/lib/watch/pod-ai.js.map +1 -1
  74. package/dist/lib/watch/pod-approval.js +216 -846
  75. package/dist/lib/watch/pod-approval.js.map +1 -1
  76. package/dist/lib/watch/pod-persistence.js +78 -184
  77. package/dist/lib/watch/pod-persistence.js.map +1 -1
  78. package/dist/lib/watch/runner.js +38 -243
  79. package/dist/lib/watch/runner.js.map +1 -1
  80. package/dist/lib/watch/types.js.map +1 -1
  81. package/dist/skills/drizzle-solid/SKILL.md +340 -0
  82. package/dist/skills/pod-storage/SKILL.md +60 -0
  83. package/dist/skills/solid-modeling/SKILL.md +274 -0
  84. package/dist/skills/xpod-componentsjs/SKILL.md +284 -0
  85. package/dist/watch-cli.js +34 -8
  86. package/dist/watch-cli.js.map +1 -1
  87. package/package.json +9 -3
  88. package/vendor/client/dist/client/index.d.ts +118 -0
  89. package/vendor/client/dist/client/index.js +260 -0
  90. package/vendor/client/dist/index.d.ts +1 -0
  91. package/vendor/client/dist/index.js +1 -0
  92. package/vendor/client/dist/watch/index.d.ts +226 -0
  93. package/vendor/client/dist/watch/index.js +1114 -0
  94. package/vendor/client/package.json +9 -0
  95. package/dist/lib/node-warning-filter.js +0 -34
  96. package/dist/lib/node-warning-filter.js.map +0 -1
  97. package/dist/lib/pi-adapter/pod-approval.js +0 -8
  98. package/dist/lib/pi-adapter/pod-approval.js.map +0 -1
  99. package/dist/lib/pi-adapter/pod-mirror-mapping.js +0 -189
  100. package/dist/lib/pi-adapter/pod-mirror-mapping.js.map +0 -1
  101. package/dist/lib/pi-adapter/pod-mirror.js +0 -334
  102. package/dist/lib/pi-adapter/pod-mirror.js.map +0 -1
  103. package/dist/lib/pi-adapter/pod-native.js +0 -478
  104. package/dist/lib/pi-adapter/pod-native.js.map +0 -1
  105. package/dist/lib/pi-adapter/session.js +0 -727
  106. package/dist/lib/pi-adapter/session.js.map +0 -1
  107. package/dist/lib/pod-data-session.js +0 -110
  108. package/dist/lib/pod-data-session.js.map +0 -1
  109. package/dist/lib/watch/secretary.js +0 -238
  110. package/dist/lib/watch/secretary.js.map +0 -1
  111. package/vendor/agent-runtime/dist/acp.d.ts +0 -27
  112. package/vendor/agent-runtime/dist/acp.js +0 -86
  113. package/vendor/agent-runtime/dist/companion-model.d.ts +0 -7
  114. package/vendor/agent-runtime/dist/companion-model.js +0 -12
  115. package/vendor/agent-runtime/dist/index.d.ts +0 -3
  116. package/vendor/agent-runtime/dist/index.js +0 -3
  117. package/vendor/agent-runtime/dist/turn-controller.d.ts +0 -69
  118. package/vendor/agent-runtime/dist/turn-controller.js +0 -129
  119. package/vendor/agent-runtime/package.json +0 -11
package/README.md CHANGED
@@ -44,7 +44,9 @@ yarn workspace @undefineds.co/linx dev watch run claude "先总结这个目录
44
44
  yarn workspace @undefineds.co/linx dev watch run codebuddy -- --tools Read,Edit
45
45
  yarn workspace @undefineds.co/linx dev watch backends
46
46
  yarn workspace @undefineds.co/linx dev watch sessions
47
- yarn workspace @undefineds.co/linx dev watch show <sessionId>
47
+ yarn workspace @undefineds.co/linx dev watch approvals
48
+ yarn workspace @undefineds.co/linx dev watch approve <approvalId> --session
49
+ yarn workspace @undefineds.co/linx dev watch reject <approvalId> --reason "unsafe command"
48
50
  ```
49
51
 
50
52
  ## Slash Commands
@@ -70,8 +72,9 @@ yarn workspace @undefineds.co/linx dev watch show <sessionId>
70
72
  - `--credential-source local|cloud|auto` 只决定凭据来源;`watch` 当前运行时始终是本地,不会因为选 cloud credential source 就切成 cloud runtime
71
73
  - `--credential-source cloud` 当前可显式用于 `codex` / `claude` / `codebuddy`,前提是对应 API key 已写进 Pod
72
74
  - 单本地会话时,approval 主路径是在当前 watch TUI 内直接处理;不会依赖额外的 approval inbox
73
- - 默认人工审批同时支持当前本地 watch 和 Pod 远端控制面,谁先决策谁生效;低上下文的 CLI approval inbox 命令已移除,远端审批队列由 App/Inbox 承载
74
- - 如果本地已 `linx login`,LinX 会把 pending approval 写进 Pod 的 `approval / audit / inbox_notification`,供 App/Inbox 读取和处理
75
+ - 默认人工审批同时支持当前本地 watch 和 Pod 远端控制面,谁先决策谁生效
76
+ - 如果本地已 `linx login`,LinX 会把 pending approval 写进 Pod 的 `approval / audit / inbox_notification`
77
+ - `linx watch approvals` / `approve` / `reject` 主要用于远端、后台或多会话场景的 approval inbox;不是本地单会话 watch 的主交互路径
75
78
  - 当前是最小多轮版:本地 REPL、统一 ACP 会话、归档结构化事件
76
79
  - 在交互式 TTY 里,`watch run` 会默认进入全屏 TUI;非 TTY / 管道输出会自动降级到 plain mode
77
80
  - `linx watch show <sessionId>` 现在会回放归档 timeline,而不是直接输出 `session.json`
@@ -0,0 +1,3 @@
1
+ // Generated by apps/cli/scripts/build.mjs. Keep a committed fallback for direct test compiles.
2
+ export const LINX_CLI_VERSION = "0.2.17";
3
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../../../../src/generated/version.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,MAAM,CAAC,MAAM,gBAAgB,GAAG,QAAQ,CAAA"}
package/dist/index.js CHANGED
@@ -1,46 +1,21 @@
1
1
  #!/usr/bin/env node
2
- import './lib/node-warning-filter.js';
3
- import { readFileSync } from 'node:fs';
4
2
  import yargs from 'yargs';
5
3
  import { hideBin } from 'yargs/helpers';
6
4
  import { aiCommand } from './lib/ai-command.js';
7
5
  import { resolveAccountBaseUrl } from './lib/account-api.js';
8
- import { loadCredentials } from './lib/credentials-store.js';
6
+ import { getClientCredentials, loadCredentials } from './lib/credentials-store.js';
7
+ import { loadAccountSession } from './lib/account-session.js';
9
8
  import { loginCommand, logoutCommand, whoamiCommand } from './lib/login-command.js';
10
- import { DefaultPackageManager, SettingsManager, runPrintMode } from '@mariozechner/pi-coding-agent';
9
+ import { runPrintMode } from '@mariozechner/pi-coding-agent';
11
10
  import { promptText } from './lib/prompt.js';
12
11
  import { resolveRuntimeTarget } from './lib/runtime-target.js';
13
12
  import { createCodexNativeProxy } from './lib/codex-plugin/index.js';
14
13
  import { bootstrapPiInteractiveMode, createPiRuntimeAdapter } from './lib/pi-adapter/index.js';
15
- import { isOidcLoginExpiredError } from './lib/oidc-auth.js';
16
- import { createPodDataSession } from './lib/pod-data-session.js';
17
- import { DEFAULT_LINX_CLOUD_MODEL_ID, FALLBACK_LINX_CLOUD_MODEL_IDS } from './lib/default-model.js';
18
- import { createLinxPiSessionManager, formatLinxPiSessionSummary, listLinxPiSessions, } from './lib/pi-adapter/session.js';
19
- import { LinxPiPodMirror } from './lib/pi-adapter/pod-mirror.js';
14
+ import { getOidcAccessToken } from './lib/oidc-auth.js';
15
+ import { DEFAULT_LINX_CLOUD_MODEL_ID } from './lib/default-model.js';
20
16
  import { LINX_AGENT_DIR } from './lib/pi-adapter/branding.js';
21
- import { formatArchivedWatchSession, formatWatchSessionSummary, loadArchivedWatchEvents, listArchivedWatchSessions, listSupportedWatchBackends, loadArchivedWatchSession, runWatch, } from './lib/watch/index.js';
22
- function readPackageVersion() {
23
- try {
24
- const raw = readFileSync(new URL('../package.json', import.meta.url), 'utf-8');
25
- const pkg = JSON.parse(raw);
26
- return typeof pkg.version === 'string' && pkg.version.trim() ? pkg.version.trim() : 'unknown';
27
- }
28
- catch {
29
- return 'unknown';
30
- }
31
- }
32
- function formatRemoteModelMetadata(model) {
33
- const provider = resolveRemoteModelProviderLabel(model);
34
- return [provider, model.contextWindow ? `${model.contextWindow}` : '']
35
- .filter(Boolean)
36
- .join(' · ');
37
- }
38
- function resolveRemoteModelProviderLabel(model) {
39
- if (FALLBACK_LINX_CLOUD_MODEL_IDS.includes(model.id)) {
40
- return 'undefineds';
41
- }
42
- return model.provider || model.ownedBy;
43
- }
17
+ import { LINX_CLI_VERSION } from './generated/version.js';
18
+ import { formatRemoteWatchApprovalSummary, formatArchivedWatchSession, formatWatchSessionSummary, loadArchivedWatchEvents, listArchivedWatchSessions, listRemoteWatchApprovals, listSupportedWatchBackends, loadArchivedWatchSession, resolveRemoteWatchApproval, runWatch, } from './lib/watch/index.js';
44
19
  let chatRuntimePromise = null;
45
20
  async function loadChatRuntime() {
46
21
  if (!chatRuntimePromise) {
@@ -70,53 +45,71 @@ async function loadChatRuntime() {
70
45
  }
71
46
  async function resolveContext(urlOverride) {
72
47
  const runtime = await loadChatRuntime();
73
- const podSession = await createLinxPodDataSession();
48
+ const creds = loadCredentials();
49
+ if (!creds) {
50
+ throw new Error('No credentials found. Run `linx login` first.');
51
+ }
74
52
  const target = resolveRuntimeTarget({
75
- issuerUrl: podSession.credentials.url,
53
+ issuerUrl: creds.url,
76
54
  runtimeUrlOverride: urlOverride,
77
55
  });
78
- const apiKey = await resolvePodRuntimeAuthToken(podSession);
79
- const session = podSession.solidSession;
80
- await runtime.initPodData(session);
81
- const chatId = await runtime.getOrCreateDefaultChat(session);
82
- return { runtimeUrl: target.runtimeUrl, apiKey, session, podSession, chatId, runtime };
56
+ const clientCreds = getClientCredentials(creds);
57
+ if (clientCreds) {
58
+ const { session, apiKey } = await runtime.authenticate(clientCreds.clientId, clientCreds.clientSecret, target.oidcIssuer);
59
+ await runtime.initPodData(session);
60
+ const chatId = await runtime.getOrCreateDefaultChat(session);
61
+ return { runtimeUrl: target.runtimeUrl, apiKey, session, chatId, runtime };
62
+ }
63
+ if (creds.authType === 'oidc_oauth') {
64
+ const accessToken = await getOidcAccessToken(creds);
65
+ if (!accessToken) {
66
+ throw new Error('Failed to restore OIDC access token. Run `linx login` again.');
67
+ }
68
+ const pseudoSession = {
69
+ async logout() { },
70
+ };
71
+ const podUrl = loadAccountSession()?.podUrl || creds.webId.replace('/card#me', '').replace(/\/?$/, '/');
72
+ const session = {
73
+ ...pseudoSession,
74
+ info: {
75
+ isLoggedIn: true,
76
+ webId: creds.webId,
77
+ podUrl,
78
+ },
79
+ fetch: (url, init) => runtime.authenticatedFetch(url, accessToken, init),
80
+ };
81
+ await runtime.initPodData(session);
82
+ const chatId = await runtime.getOrCreateDefaultChat(session);
83
+ return { runtimeUrl: target.runtimeUrl, apiKey: accessToken, session, chatId, runtime };
84
+ }
85
+ throw new Error('Unsupported LinX auth type. Run `linx login` again.');
83
86
  }
84
87
  async function resolveRuntimeAuthContext(urlOverride) {
85
88
  const runtime = await loadChatRuntime();
86
- const podSession = await createLinxPodDataSession();
89
+ const creds = loadCredentials();
90
+ if (!creds) {
91
+ throw new Error('No credentials found. Run `linx login` first.');
92
+ }
87
93
  const target = resolveRuntimeTarget({
88
- issuerUrl: podSession.credentials.url,
94
+ issuerUrl: creds.url,
89
95
  runtimeUrlOverride: urlOverride,
90
96
  });
91
- const apiKey = await resolvePodRuntimeAuthToken(podSession);
92
- return {
93
- runtimeUrl: target.runtimeUrl,
94
- apiKey,
95
- session: podSession.solidSession,
96
- podSession,
97
- runtime,
98
- };
99
- }
100
- async function createLinxPodDataSession() {
101
- if (!loadCredentials()) {
102
- throw new Error('No credentials found. Run `linx login` first.');
103
- }
104
- const podSession = await createPodDataSession();
105
- if (!podSession) {
106
- throw new Error('Unsupported LinX auth type. Run `linx login` again.');
107
- }
108
- return podSession;
109
- }
110
- async function resolvePodRuntimeAuthToken(podSession) {
111
- try {
112
- return await podSession.getRuntimeAuthToken();
113
- }
114
- catch (error) {
115
- if (isOidcLoginExpiredError(error)) {
116
- throw new Error('LinX Cloud login expired. Run `linx login` to re-authorize.');
97
+ const clientCreds = getClientCredentials(creds);
98
+ if (clientCreds) {
99
+ const { session, apiKey } = await runtime.authenticate(clientCreds.clientId, clientCreds.clientSecret, target.oidcIssuer);
100
+ return { runtimeUrl: target.runtimeUrl, apiKey, session, runtime };
101
+ }
102
+ if (creds.authType === 'oidc_oauth') {
103
+ const accessToken = await getOidcAccessToken(creds);
104
+ if (!accessToken) {
105
+ throw new Error('Failed to restore OIDC access token. Run `linx login` again.');
117
106
  }
118
- throw error;
107
+ const pseudoSession = {
108
+ async logout() { },
109
+ };
110
+ return { runtimeUrl: target.runtimeUrl, apiKey: accessToken, session: pseudoSession, runtime };
119
111
  }
112
+ throw new Error('Unsupported LinX auth type. Run `linx login` again.');
120
113
  }
121
114
  async function runSingleTurn(options) {
122
115
  const { ctx, threadId, model, prompt } = options;
@@ -149,7 +142,7 @@ async function runInteractive(options) {
149
142
  const { ctx, initialThreadId, initialModel, initialPrompt } = options;
150
143
  let threadId = initialThreadId;
151
144
  let model = initialModel;
152
- process.stdout.write(`LinX CLI ready\nthread: ${threadId}\nmodel: ${model || DEFAULT_LINX_CLOUD_MODEL_ID}\n输入 /hotkeys 查看快捷键。\n\n`);
145
+ process.stdout.write(`LinX CLI ready\nthread: ${threadId}\nmodel: ${model || DEFAULT_LINX_CLOUD_MODEL_ID}\n输入 /help 查看命令。\n\n`);
153
146
  if (initialPrompt) {
154
147
  await runSingleTurn({ ctx, threadId, model, prompt: initialPrompt });
155
148
  }
@@ -161,7 +154,7 @@ async function runInteractive(options) {
161
154
  break;
162
155
  }
163
156
  if (input === '/help') {
164
- process.stdout.write('/hotkeys 查看快捷键\n/threads 列出 threads\n/new 新建 thread\n/use <threadId> 切换 thread\n/model <modelId> 切换模型\n/exit 退出\n\n');
157
+ process.stdout.write('/help 查看帮助\n/threads 列出 threads\n/new 新建 thread\n/use <threadId> 切换 thread\n/model <modelId> 切换模型\n/exit 退出\n\n');
165
158
  continue;
166
159
  }
167
160
  if (input === '/threads') {
@@ -197,77 +190,32 @@ async function runInteractive(options) {
197
190
  await runSingleTurn({ ctx, threadId, model, prompt: input });
198
191
  }
199
192
  }
200
- async function runLinxPackageCommand(command, options = {}) {
201
- if ((command === 'install' || command === 'remove') && !options.source) {
202
- throw new Error(`Missing ${command} source. Usage: linx ${command} <source> [-l]`);
203
- }
204
- const cwd = process.cwd();
205
- const settingsManager = SettingsManager.create(cwd, LINX_AGENT_DIR);
206
- const packageManager = new DefaultPackageManager({
207
- cwd,
208
- agentDir: LINX_AGENT_DIR,
209
- settingsManager,
210
- });
211
- packageManager.setProgressCallback((event) => {
212
- if (event.type === 'start' && event.message) {
213
- process.stdout.write(`${event.message}\n`);
214
- }
215
- });
216
- switch (command) {
217
- case 'install':
218
- await packageManager.installAndPersist(options.source, { local: Boolean(options.local) });
219
- process.stdout.write(`Installed ${options.source}\n`);
220
- return;
221
- case 'remove': {
222
- const removed = await packageManager.removeAndPersist(options.source, { local: Boolean(options.local) });
223
- if (!removed) {
224
- throw new Error(`No matching package found for ${options.source}`);
225
- }
226
- process.stdout.write(`Removed ${options.source}\n`);
227
- return;
228
- }
229
- case 'update':
230
- await packageManager.update(options.source);
231
- process.stdout.write(options.source ? `Updated ${options.source}\n` : 'Updated packages\n');
232
- return;
233
- case 'list':
234
- printConfiguredLinxPackages(packageManager);
235
- return;
236
- }
237
- }
238
- function printConfiguredLinxPackages(packageManager) {
239
- const configuredPackages = packageManager.listConfiguredPackages();
240
- if (configuredPackages.length === 0) {
241
- process.stdout.write('No packages installed.\n');
242
- return;
243
- }
244
- const printGroup = (title, packages) => {
245
- if (packages.length === 0) {
246
- return;
247
- }
248
- process.stdout.write(`${title}:\n`);
249
- for (const pkg of packages) {
250
- const display = pkg.filtered ? `${pkg.source} (filtered)` : pkg.source;
251
- process.stdout.write(` ${display}\n`);
252
- if (pkg.installedPath) {
253
- process.stdout.write(` ${pkg.installedPath}\n`);
254
- }
255
- }
256
- };
257
- printGroup('User packages', configuredPackages.filter((pkg) => pkg.scope === 'user'));
258
- printGroup('Project packages', configuredPackages.filter((pkg) => pkg.scope === 'project'));
259
- }
260
193
  async function runPiCommand(argv) {
261
194
  const backend = argv.backend ?? 'cloud';
262
- let shouldPromptLinxCloudLoginOnStart = false;
263
195
  if (!argv.print && backend === 'cloud') {
264
196
  const { resolveLinxPiCloudOAuthCredential } = await import('./lib/pi-adapter/auth.js');
265
- const existingCredential = await resolveLinxPiCloudOAuthCredential(undefined).catch((error) => {
266
- shouldPromptLinxCloudLoginOnStart = true;
267
- return null;
268
- });
197
+ const existingCredential = await resolveLinxPiCloudOAuthCredential(undefined);
269
198
  if (!existingCredential) {
270
- shouldPromptLinxCloudLoginOnStart = true;
199
+ const answer = (await promptText('LinX Cloud not connected. Open browser login now? [Y/n] ')).trim().toLowerCase();
200
+ const shouldLoginNow = answer === '' || answer === 'y' || answer === 'yes';
201
+ if (shouldLoginNow) {
202
+ const { ensureBrowserConsentLogin } = await import('./lib/oidc-auth.js');
203
+ process.stdout.write('Opening LinX Cloud login in your browser...\n');
204
+ try {
205
+ const result = await ensureBrowserConsentLogin({
206
+ issuerUrl: resolveAccountBaseUrl(),
207
+ });
208
+ if (result.reusedExistingSession) {
209
+ process.stdout.write('Reused existing LinX Cloud session.\n');
210
+ }
211
+ }
212
+ catch (error) {
213
+ process.stdout.write('LinX Cloud login was not completed. Continuing into TUI without auth.\n');
214
+ if (error instanceof Error && error.message.trim()) {
215
+ process.stdout.write(`${error.message}\n`);
216
+ }
217
+ }
218
+ }
271
219
  }
272
220
  }
273
221
  const adapter = createPiRuntimeAdapter({
@@ -284,11 +232,11 @@ async function runPiCommand(argv) {
284
232
  },
285
233
  async listRemoteModels(session, runtimeUrl, apiKey) {
286
234
  const chatApi = await import('./lib/chat-api.js');
287
- return chatApi.listRemoteModels(session, runtimeUrl, apiKey, { fallback: false, timeoutMs: 5000 });
235
+ return chatApi.listRemoteModels(session, runtimeUrl, apiKey);
288
236
  },
289
237
  }, {
290
238
  cwd: argv.cwd || process.cwd(),
291
- model: argv.model,
239
+ model: argv.model || DEFAULT_LINX_CLOUD_MODEL_ID,
292
240
  backend,
293
241
  port: argv.port,
294
242
  providerConfig: {
@@ -297,40 +245,13 @@ async function runPiCommand(argv) {
297
245
  },
298
246
  });
299
247
  await adapter.start();
300
- const sessionManager = await createLinxPiSessionManager({
301
- cwd: adapter.cwd,
302
- agentDir: LINX_AGENT_DIR,
303
- session: argv.session,
304
- last: argv.last,
305
- });
248
+ const { SessionManager } = await import('@mariozechner/pi-coding-agent');
306
249
  const runtime = await adapter.createRuntime({
307
250
  cwd: adapter.cwd,
308
251
  agentDir: LINX_AGENT_DIR,
309
- sessionManager,
310
- });
311
- const podMirror = new LinxPiPodMirror({
312
- cwd: adapter.cwd,
313
- sessionManager,
314
- onError(error) {
315
- if (process.env.LINX_DEBUG === '1') {
316
- const message = error instanceof Error ? error.stack || error.message : String(error);
317
- process.stderr.write(`[linx pod mirror] ${message}\n`);
318
- }
319
- },
320
- });
321
- const unsubscribePodMirror = runtime.session.subscribe((event) => {
322
- podMirror.handleEvent(event);
252
+ sessionManager: SessionManager.inMemory(adapter.cwd),
323
253
  });
324
254
  const interactive = bootstrapPiInteractiveMode(runtime);
325
- const bridge = runtime;
326
- const loginPromptReason = bridge.linxAuthBridge?.shouldPromptLoginOnStart
327
- ? 'expired'
328
- : shouldPromptLinxCloudLoginOnStart
329
- ? 'startup'
330
- : null;
331
- if (loginPromptReason) {
332
- interactive.requestLogin?.(loginPromptReason);
333
- }
334
255
  try {
335
256
  if (argv.print) {
336
257
  const prompt = (argv.prompt ?? []).join(' ').trim();
@@ -346,8 +267,6 @@ async function runPiCommand(argv) {
346
267
  await interactive.run();
347
268
  }
348
269
  finally {
349
- unsubscribePodMirror();
350
- await podMirror.close().catch(() => undefined);
351
270
  interactive.stop();
352
271
  await adapter.close();
353
272
  }
@@ -360,7 +279,7 @@ function buildPiCommand(command) {
360
279
  })
361
280
  .option('model', {
362
281
  type: 'string',
363
- describe: 'Model id to expose through the Pi runtime adapter; defaults to the last LinX selection',
282
+ describe: 'Model id to expose through the Pi runtime adapter',
364
283
  })
365
284
  .option('backend', {
366
285
  type: 'string',
@@ -382,15 +301,6 @@ function buildPiCommand(command) {
382
301
  type: 'boolean',
383
302
  default: false,
384
303
  describe: 'Run a single prompt without entering interactive mode',
385
- })
386
- .option('session', {
387
- type: 'string',
388
- describe: 'Resume a specific LinX/Pi session id or JSONL file',
389
- })
390
- .option('last', {
391
- type: 'boolean',
392
- default: false,
393
- describe: 'Continue the most recent local LinX/Pi session for this workspace',
394
304
  })
395
305
  .positional('prompt', {
396
306
  array: true,
@@ -428,7 +338,7 @@ const execCommand = {
428
338
  };
429
339
  const cli = yargs(hideBin(process.argv))
430
340
  .scriptName('linx')
431
- .version(readPackageVersion())
341
+ .version(LINX_CLI_VERSION)
432
342
  .parserConfiguration({
433
343
  'populate--': true,
434
344
  })
@@ -436,30 +346,6 @@ const cli = yargs(hideBin(process.argv))
436
346
  .command(logoutCommand)
437
347
  .command(whoamiCommand)
438
348
  .command(aiCommand)
439
- .command('install [source]', 'Install a LinX package or extension', (command) => command
440
- .positional('source', { type: 'string', describe: 'Package source to install' })
441
- .option('local', { alias: 'l', type: 'boolean', default: false, describe: 'Install project-locally (.pi/settings.json)' }), async (argv) => {
442
- await runLinxPackageCommand('install', {
443
- source: typeof argv.source === 'string' ? argv.source : undefined,
444
- local: Boolean(argv.local),
445
- });
446
- })
447
- .command('remove [source]', 'Remove a LinX package or extension', (command) => command
448
- .positional('source', { type: 'string', describe: 'Package source to remove' })
449
- .option('local', { alias: 'l', type: 'boolean', default: false, describe: 'Remove from project settings (.pi/settings.json)' }), async (argv) => {
450
- await runLinxPackageCommand('remove', {
451
- source: typeof argv.source === 'string' ? argv.source : undefined,
452
- local: Boolean(argv.local),
453
- });
454
- })
455
- .command('update [source]', 'Update installed LinX packages', (command) => command.positional('source', { type: 'string', describe: 'Package source to update' }), async (argv) => {
456
- await runLinxPackageCommand('update', {
457
- source: typeof argv.source === 'string' ? argv.source : undefined,
458
- });
459
- })
460
- .command('list', 'List installed LinX packages', () => undefined, async () => {
461
- await runLinxPackageCommand('list');
462
- })
463
349
  .command(execCommand)
464
350
  .command(defaultPiCommand)
465
351
  .command('chat [prompt..]', false, (command) => command
@@ -478,11 +364,11 @@ const cli = yargs(hideBin(process.argv))
478
364
  const prompt = argv.prompt?.join(' ').trim() || undefined;
479
365
  if (prompt) {
480
366
  await runSingleTurn({ ctx, threadId, model: argv.model, prompt });
481
- await ctx.podSession.close();
367
+ await ctx.session.logout();
482
368
  return;
483
369
  }
484
370
  await runInteractive({ ctx, initialThreadId: threadId, initialModel: argv.model });
485
- await ctx.podSession.close();
371
+ await ctx.session.logout();
486
372
  })
487
373
  .command('models', 'List available remote models', (command) => command.option('url', { type: 'string', describe: 'Runtime API base URL override' }), async (argv) => {
488
374
  const ctx = await resolveRuntimeAuthContext(argv.url);
@@ -499,44 +385,24 @@ const cli = yargs(hideBin(process.argv))
499
385
  }
500
386
  else {
501
387
  for (const model of models) {
502
- const meta = formatRemoteModelMetadata(model);
388
+ const meta = [model.provider || model.ownedBy, model.contextWindow ? `${model.contextWindow}` : '']
389
+ .filter(Boolean)
390
+ .join(' · ');
503
391
  process.stdout.write(`- ${model.id}${meta ? ` (${meta})` : ''}\n`);
504
392
  }
505
393
  }
506
- await ctx.podSession.close();
507
- })
508
- .command('resume [session]', 'Resume a previous interactive LinX session', (command) => command
509
- .positional('session', { type: 'string', describe: 'Session id/prefix or JSONL file to resume' })
510
- .option('last', { type: 'boolean', default: false, describe: 'Resume the most recent local session for this workspace' })
511
- .option('cwd', { type: 'string', describe: 'Workspace path for the resumed session' })
512
- .option('model', { type: 'string', describe: 'Model id to expose through the Pi runtime adapter' })
513
- .option('backend', {
514
- type: 'string',
515
- default: 'cloud',
516
- choices: ['cloud', 'native'],
517
- describe: 'Backend mode. Default is cloud; native keeps the local Codex proxy for debugging only.',
394
+ await ctx.session.logout();
518
395
  })
519
- .option('port', { type: 'number', default: 8787, describe: 'Local websocket port used only when --backend native' })
520
- .option('runtime-url', { type: 'string', default: 'https://api.undefineds.co/v1', describe: 'Cloud runtime API base URL' }), async (argv) => {
521
- const session = typeof argv.session === 'string' ? argv.session : undefined;
522
- if (!session && !argv.last) {
523
- const sessions = await listLinxPiSessions(argv.cwd || process.cwd(), LINX_AGENT_DIR);
524
- if (sessions.length === 0) {
525
- process.stdout.write('No LinX sessions found.\n');
526
- return;
527
- }
528
- process.stdout.write(`${sessions.map(formatLinxPiSessionSummary).join('\n')}\n`);
529
- return;
396
+ .command('resume', 'List resumable CLI threads', (command) => command.option('url', { type: 'string', describe: 'Runtime API base URL override' }), async (argv) => {
397
+ const ctx = await resolveContext(argv.url);
398
+ const threads = await ctx.runtime.listThreads(ctx.session, ctx.chatId);
399
+ if (threads.length === 0) {
400
+ process.stdout.write('No threads found.\n');
530
401
  }
531
- await runPiCommand({
532
- cwd: argv.cwd,
533
- model: argv.model,
534
- backend: argv.backend,
535
- port: argv.port,
536
- 'runtime-url': argv['runtime-url'],
537
- session,
538
- last: argv.last || !session,
539
- });
402
+ else {
403
+ process.stdout.write(`${threads.map((thread) => `- ${ctx.runtime.formatThreadLabel(thread)}`).join('\n')}\n`);
404
+ }
405
+ await ctx.session.logout();
540
406
  })
541
407
  .command('fork [thread]', 'Fork a previous interactive session', (command) => command
542
408
  .positional('thread', { type: 'string', describe: 'Thread ID to fork' })
@@ -582,11 +448,11 @@ const cli = yargs(hideBin(process.argv))
582
448
  .command('watch <action> [backend] [prompt..]', 'Run or inspect local watch backends', (command) => command
583
449
  .positional('action', {
584
450
  type: 'string',
585
- choices: ['run', 'backends', 'sessions', 'show', 'codex', 'claude', 'codebuddy'],
451
+ choices: ['run', 'backends', 'sessions', 'show', 'approvals', 'approve', 'reject', 'codex', 'claude', 'codebuddy'],
586
452
  })
587
453
  .positional('backend', {
588
454
  type: 'string',
589
- describe: 'Watch backend for `run` or session id for `show`',
455
+ describe: 'Watch backend for `run`, session id for `show`, or approval id for `approve|reject`',
590
456
  })
591
457
  .option('mode', {
592
458
  type: 'string',
@@ -613,11 +479,14 @@ const cli = yargs(hideBin(process.argv))
613
479
  choices: ['auto', 'local', 'cloud'],
614
480
  describe: 'Resolve credentials only: local CLI login, LinX cloud config, or auto fallback. Runtime still runs locally.',
615
481
  })
616
- .option('approval-source', {
482
+ .option('session', {
483
+ type: 'boolean',
484
+ default: false,
485
+ describe: 'Approve for the current watch session instead of only once.',
486
+ })
487
+ .option('reason', {
617
488
  type: 'string',
618
- default: 'hybrid',
619
- choices: ['local', 'remote', 'hybrid'],
620
- describe: 'Resolve backend approval requests locally, through Pod remote approvals, or whichever answers first.',
489
+ describe: 'Optional note recorded with an approval decision.',
621
490
  }), async (argv) => {
622
491
  const rawAction = String(argv.action);
623
492
  const directBackend = ['codex', 'claude', 'codebuddy'].includes(rawAction);
@@ -654,6 +523,30 @@ const cli = yargs(hideBin(process.argv))
654
523
  process.stdout.write(formatArchivedWatchSession(session, loadArchivedWatchEvents(sessionId)));
655
524
  return;
656
525
  }
526
+ if (action === 'approvals') {
527
+ const approvals = await listRemoteWatchApprovals();
528
+ if (approvals.length === 0) {
529
+ process.stdout.write('No pending remote approvals in the approval inbox.\n');
530
+ return;
531
+ }
532
+ process.stdout.write(`${approvals.map(formatRemoteWatchApprovalSummary).join('\n')}\n`);
533
+ return;
534
+ }
535
+ if (action === 'approve' || action === 'reject') {
536
+ const approvalId = argv.backend ? String(argv.backend) : '';
537
+ if (!approvalId) {
538
+ throw new Error(`Usage: linx watch ${action} <approvalId>`);
539
+ }
540
+ const summary = await resolveRemoteWatchApproval({
541
+ approvalId,
542
+ decision: action === 'approve'
543
+ ? (argv.session ? 'accept_for_session' : 'accept')
544
+ : 'decline',
545
+ note: argv.reason ? String(argv.reason) : undefined,
546
+ });
547
+ process.stdout.write(`${formatRemoteWatchApprovalSummary(summary)}\n`);
548
+ return;
549
+ }
657
550
  const backend = (directBackend ? rawAction : argv.backend);
658
551
  if (!backend || !['codex', 'claude', 'codebuddy'].includes(backend)) {
659
552
  throw new Error('Usage: linx watch run <codex|claude|codebuddy> <prompt> [-- backend args]\n or: linx watch <codex|claude|codebuddy> <prompt>');
@@ -672,26 +565,13 @@ const cli = yargs(hideBin(process.argv))
672
565
  prompt,
673
566
  passthroughArgs: (argv['--'] ?? []).map(String),
674
567
  credentialSource: argv['credential-source'],
675
- approvalSource: argv['approval-source'],
676
568
  });
677
569
  if (exitCode !== 0) {
678
570
  process.exitCode = exitCode;
679
571
  }
680
572
  })
681
573
  .strict()
682
- .help()
683
- .fail((message, error, yargsInstance) => {
684
- if (error) {
685
- console.error(error instanceof Error ? error.message : String(error));
686
- process.exit(1);
687
- }
688
- if (message) {
689
- console.error(message);
690
- process.exit(1);
691
- }
692
- yargsInstance.showHelp();
693
- process.exit(1);
694
- });
574
+ .help();
695
575
  process.on('unhandledRejection', (error) => {
696
576
  console.error(error instanceof Error ? error.message : String(error));
697
577
  process.exit(1);