@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
package/README.md
CHANGED
|
@@ -44,9 +44,7 @@ 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
|
|
48
|
-
yarn workspace @undefineds.co/linx dev watch approve <approvalId> --session
|
|
49
|
-
yarn workspace @undefineds.co/linx dev watch reject <approvalId> --reason "unsafe command"
|
|
47
|
+
yarn workspace @undefineds.co/linx dev watch show <sessionId>
|
|
50
48
|
```
|
|
51
49
|
|
|
52
50
|
## Slash Commands
|
|
@@ -72,9 +70,8 @@ yarn workspace @undefineds.co/linx dev watch reject <approvalId> --reason "unsaf
|
|
|
72
70
|
- `--credential-source local|cloud|auto` 只决定凭据来源;`watch` 当前运行时始终是本地,不会因为选 cloud credential source 就切成 cloud runtime
|
|
73
71
|
- `--credential-source cloud` 当前可显式用于 `codex` / `claude` / `codebuddy`,前提是对应 API key 已写进 Pod
|
|
74
72
|
- 单本地会话时,approval 主路径是在当前 watch TUI 内直接处理;不会依赖额外的 approval inbox
|
|
75
|
-
- 默认人工审批同时支持当前本地 watch 和 Pod
|
|
76
|
-
- 如果本地已 `linx login`,LinX 会把 pending approval 写进 Pod 的 `approval / audit / inbox_notification
|
|
77
|
-
- `linx watch approvals` / `approve` / `reject` 主要用于远端、后台或多会话场景的 approval inbox;不是本地单会话 watch 的主交互路径
|
|
73
|
+
- 默认人工审批同时支持当前本地 watch 和 Pod 远端控制面,谁先决策谁生效;低上下文的 CLI approval inbox 命令已移除,远端审批队列由 App/Inbox 承载
|
|
74
|
+
- 如果本地已 `linx login`,LinX 会把 pending approval 写进 Pod 的 `approval / audit / inbox_notification`,供 App/Inbox 读取和处理
|
|
78
75
|
- 当前是最小多轮版:本地 REPL、统一 ACP 会话、归档结构化事件
|
|
79
76
|
- 在交互式 TTY 里,`watch run` 会默认进入全屏 TUI;非 TTY / 管道输出会自动降级到 plain mode
|
|
80
77
|
- `linx watch show <sessionId>` 现在会回放归档 timeline,而不是直接输出 `session.json`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["
|
|
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,21 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import './lib/node-warning-filter.js';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
2
4
|
import yargs from 'yargs';
|
|
3
5
|
import { hideBin } from 'yargs/helpers';
|
|
4
6
|
import { aiCommand } from './lib/ai-command.js';
|
|
5
7
|
import { resolveAccountBaseUrl } from './lib/account-api.js';
|
|
6
|
-
import {
|
|
7
|
-
import { loadAccountSession } from './lib/account-session.js';
|
|
8
|
+
import { loadCredentials } from './lib/credentials-store.js';
|
|
8
9
|
import { loginCommand, logoutCommand, whoamiCommand } from './lib/login-command.js';
|
|
9
|
-
import { runPrintMode } from '@mariozechner/pi-coding-agent';
|
|
10
|
+
import { DefaultPackageManager, SettingsManager, runPrintMode } from '@mariozechner/pi-coding-agent';
|
|
10
11
|
import { promptText } from './lib/prompt.js';
|
|
11
12
|
import { resolveRuntimeTarget } from './lib/runtime-target.js';
|
|
12
13
|
import { createCodexNativeProxy } from './lib/codex-plugin/index.js';
|
|
13
|
-
import { bootstrapPiInteractiveMode, createPiRuntimeAdapter } from './lib/pi-adapter/index.js';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
14
|
+
import { bootstrapPiInteractiveMode, createPiRuntimeAdapter, resolveLinxInteractiveLoginReason, resolveLinxStartupLoginPromptDecision } 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';
|
|
16
20
|
import { LINX_AGENT_DIR } from './lib/pi-adapter/branding.js';
|
|
17
|
-
import {
|
|
18
|
-
|
|
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
|
+
}
|
|
19
44
|
let chatRuntimePromise = null;
|
|
20
45
|
async function loadChatRuntime() {
|
|
21
46
|
if (!chatRuntimePromise) {
|
|
@@ -45,71 +70,53 @@ async function loadChatRuntime() {
|
|
|
45
70
|
}
|
|
46
71
|
async function resolveContext(urlOverride) {
|
|
47
72
|
const runtime = await loadChatRuntime();
|
|
48
|
-
const
|
|
49
|
-
if (!creds) {
|
|
50
|
-
throw new Error('No credentials found. Run `linx login` first.');
|
|
51
|
-
}
|
|
73
|
+
const podSession = await createLinxPodDataSession();
|
|
52
74
|
const target = resolveRuntimeTarget({
|
|
53
|
-
issuerUrl:
|
|
75
|
+
issuerUrl: podSession.credentials.url,
|
|
54
76
|
runtimeUrlOverride: urlOverride,
|
|
55
77
|
});
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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.');
|
|
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 };
|
|
86
83
|
}
|
|
87
84
|
async function resolveRuntimeAuthContext(urlOverride) {
|
|
88
85
|
const runtime = await loadChatRuntime();
|
|
89
|
-
const
|
|
90
|
-
if (!creds) {
|
|
91
|
-
throw new Error('No credentials found. Run `linx login` first.');
|
|
92
|
-
}
|
|
86
|
+
const podSession = await createLinxPodDataSession();
|
|
93
87
|
const target = resolveRuntimeTarget({
|
|
94
|
-
issuerUrl:
|
|
88
|
+
issuerUrl: podSession.credentials.url,
|
|
95
89
|
runtimeUrlOverride: urlOverride,
|
|
96
90
|
});
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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.');
|
|
101
107
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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.');
|
|
106
117
|
}
|
|
107
|
-
|
|
108
|
-
async logout() { },
|
|
109
|
-
};
|
|
110
|
-
return { runtimeUrl: target.runtimeUrl, apiKey: accessToken, session: pseudoSession, runtime };
|
|
118
|
+
throw error;
|
|
111
119
|
}
|
|
112
|
-
throw new Error('Unsupported LinX auth type. Run `linx login` again.');
|
|
113
120
|
}
|
|
114
121
|
async function runSingleTurn(options) {
|
|
115
122
|
const { ctx, threadId, model, prompt } = options;
|
|
@@ -142,7 +149,7 @@ async function runInteractive(options) {
|
|
|
142
149
|
const { ctx, initialThreadId, initialModel, initialPrompt } = options;
|
|
143
150
|
let threadId = initialThreadId;
|
|
144
151
|
let model = initialModel;
|
|
145
|
-
process.stdout.write(`LinX CLI ready\nthread: ${threadId}\nmodel: ${model || DEFAULT_LINX_CLOUD_MODEL_ID}\n输入 /
|
|
152
|
+
process.stdout.write(`LinX CLI ready\nthread: ${threadId}\nmodel: ${model || DEFAULT_LINX_CLOUD_MODEL_ID}\n输入 /hotkeys 查看快捷键。\n\n`);
|
|
146
153
|
if (initialPrompt) {
|
|
147
154
|
await runSingleTurn({ ctx, threadId, model, prompt: initialPrompt });
|
|
148
155
|
}
|
|
@@ -154,7 +161,7 @@ async function runInteractive(options) {
|
|
|
154
161
|
break;
|
|
155
162
|
}
|
|
156
163
|
if (input === '/help') {
|
|
157
|
-
process.stdout.write('/
|
|
164
|
+
process.stdout.write('/hotkeys 查看快捷键\n/threads 列出 threads\n/new 新建 thread\n/use <threadId> 切换 thread\n/model <modelId> 切换模型\n/exit 退出\n\n');
|
|
158
165
|
continue;
|
|
159
166
|
}
|
|
160
167
|
if (input === '/threads') {
|
|
@@ -190,34 +197,73 @@ async function runInteractive(options) {
|
|
|
190
197
|
await runSingleTurn({ ctx, threadId, model, prompt: input });
|
|
191
198
|
}
|
|
192
199
|
}
|
|
193
|
-
async function
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
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}`);
|
|
218
225
|
}
|
|
226
|
+
process.stdout.write(`Removed ${options.source}\n`);
|
|
227
|
+
return;
|
|
219
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;
|
|
220
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
|
+
async function runPiCommand(argv) {
|
|
261
|
+
const backend = argv.backend ?? 'cloud';
|
|
262
|
+
const startupLoginPrompt = await resolveLinxStartupLoginPromptDecision({
|
|
263
|
+
backend,
|
|
264
|
+
print: argv.print,
|
|
265
|
+
issuerUrl: resolveAccountBaseUrl(),
|
|
266
|
+
});
|
|
221
267
|
const adapter = createPiRuntimeAdapter({
|
|
222
268
|
createNativeProxy(options) {
|
|
223
269
|
return createCodexNativeProxy({
|
|
@@ -232,11 +278,11 @@ async function runPiCommand(argv) {
|
|
|
232
278
|
},
|
|
233
279
|
async listRemoteModels(session, runtimeUrl, apiKey) {
|
|
234
280
|
const chatApi = await import('./lib/chat-api.js');
|
|
235
|
-
return chatApi.listRemoteModels(session, runtimeUrl, apiKey);
|
|
281
|
+
return chatApi.listRemoteModels(session, runtimeUrl, apiKey, { fallback: false, timeoutMs: 5000 });
|
|
236
282
|
},
|
|
237
283
|
}, {
|
|
238
284
|
cwd: argv.cwd || process.cwd(),
|
|
239
|
-
model: argv.model
|
|
285
|
+
model: argv.model,
|
|
240
286
|
backend,
|
|
241
287
|
port: argv.port,
|
|
242
288
|
providerConfig: {
|
|
@@ -245,13 +291,39 @@ async function runPiCommand(argv) {
|
|
|
245
291
|
},
|
|
246
292
|
});
|
|
247
293
|
await adapter.start();
|
|
248
|
-
const
|
|
294
|
+
const sessionManager = await createLinxPiSessionManager({
|
|
295
|
+
cwd: adapter.cwd,
|
|
296
|
+
agentDir: LINX_AGENT_DIR,
|
|
297
|
+
session: argv.session,
|
|
298
|
+
last: argv.last,
|
|
299
|
+
});
|
|
249
300
|
const runtime = await adapter.createRuntime({
|
|
250
301
|
cwd: adapter.cwd,
|
|
251
302
|
agentDir: LINX_AGENT_DIR,
|
|
252
|
-
sessionManager
|
|
303
|
+
sessionManager,
|
|
304
|
+
});
|
|
305
|
+
const podMirror = new LinxPiPodMirror({
|
|
306
|
+
cwd: adapter.cwd,
|
|
307
|
+
sessionManager,
|
|
308
|
+
onError(error) {
|
|
309
|
+
if (process.env.LINX_DEBUG === '1') {
|
|
310
|
+
const message = error instanceof Error ? error.stack || error.message : String(error);
|
|
311
|
+
process.stderr.write(`[linx pod mirror] ${message}\n`);
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
const unsubscribePodMirror = runtime.session.subscribe((event) => {
|
|
316
|
+
podMirror.handleEvent(event);
|
|
253
317
|
});
|
|
254
318
|
const interactive = bootstrapPiInteractiveMode(runtime);
|
|
319
|
+
const bridge = runtime;
|
|
320
|
+
const loginPromptReason = resolveLinxInteractiveLoginReason({
|
|
321
|
+
startupDecision: startupLoginPrompt,
|
|
322
|
+
runtimePromptOnStart: bridge.linxAuthBridge?.shouldPromptLoginOnStart,
|
|
323
|
+
});
|
|
324
|
+
if (loginPromptReason) {
|
|
325
|
+
interactive.requestLogin?.(loginPromptReason);
|
|
326
|
+
}
|
|
255
327
|
try {
|
|
256
328
|
if (argv.print) {
|
|
257
329
|
const prompt = (argv.prompt ?? []).join(' ').trim();
|
|
@@ -267,6 +339,8 @@ async function runPiCommand(argv) {
|
|
|
267
339
|
await interactive.run();
|
|
268
340
|
}
|
|
269
341
|
finally {
|
|
342
|
+
unsubscribePodMirror();
|
|
343
|
+
await podMirror.close().catch(() => undefined);
|
|
270
344
|
interactive.stop();
|
|
271
345
|
await adapter.close();
|
|
272
346
|
}
|
|
@@ -279,7 +353,7 @@ function buildPiCommand(command) {
|
|
|
279
353
|
})
|
|
280
354
|
.option('model', {
|
|
281
355
|
type: 'string',
|
|
282
|
-
describe: 'Model id to expose through the Pi runtime adapter',
|
|
356
|
+
describe: 'Model id to expose through the Pi runtime adapter; defaults to the last LinX selection',
|
|
283
357
|
})
|
|
284
358
|
.option('backend', {
|
|
285
359
|
type: 'string',
|
|
@@ -301,6 +375,15 @@ function buildPiCommand(command) {
|
|
|
301
375
|
type: 'boolean',
|
|
302
376
|
default: false,
|
|
303
377
|
describe: 'Run a single prompt without entering interactive mode',
|
|
378
|
+
})
|
|
379
|
+
.option('session', {
|
|
380
|
+
type: 'string',
|
|
381
|
+
describe: 'Resume a specific LinX/Pi session id or JSONL file',
|
|
382
|
+
})
|
|
383
|
+
.option('last', {
|
|
384
|
+
type: 'boolean',
|
|
385
|
+
default: false,
|
|
386
|
+
describe: 'Continue the most recent local LinX/Pi session for this workspace',
|
|
304
387
|
})
|
|
305
388
|
.positional('prompt', {
|
|
306
389
|
array: true,
|
|
@@ -338,7 +421,7 @@ const execCommand = {
|
|
|
338
421
|
};
|
|
339
422
|
const cli = yargs(hideBin(process.argv))
|
|
340
423
|
.scriptName('linx')
|
|
341
|
-
.version(
|
|
424
|
+
.version(readPackageVersion())
|
|
342
425
|
.parserConfiguration({
|
|
343
426
|
'populate--': true,
|
|
344
427
|
})
|
|
@@ -346,6 +429,30 @@ const cli = yargs(hideBin(process.argv))
|
|
|
346
429
|
.command(logoutCommand)
|
|
347
430
|
.command(whoamiCommand)
|
|
348
431
|
.command(aiCommand)
|
|
432
|
+
.command('install [source]', 'Install a LinX package or extension', (command) => command
|
|
433
|
+
.positional('source', { type: 'string', describe: 'Package source to install' })
|
|
434
|
+
.option('local', { alias: 'l', type: 'boolean', default: false, describe: 'Install project-locally (.pi/settings.json)' }), async (argv) => {
|
|
435
|
+
await runLinxPackageCommand('install', {
|
|
436
|
+
source: typeof argv.source === 'string' ? argv.source : undefined,
|
|
437
|
+
local: Boolean(argv.local),
|
|
438
|
+
});
|
|
439
|
+
})
|
|
440
|
+
.command('remove [source]', 'Remove a LinX package or extension', (command) => command
|
|
441
|
+
.positional('source', { type: 'string', describe: 'Package source to remove' })
|
|
442
|
+
.option('local', { alias: 'l', type: 'boolean', default: false, describe: 'Remove from project settings (.pi/settings.json)' }), async (argv) => {
|
|
443
|
+
await runLinxPackageCommand('remove', {
|
|
444
|
+
source: typeof argv.source === 'string' ? argv.source : undefined,
|
|
445
|
+
local: Boolean(argv.local),
|
|
446
|
+
});
|
|
447
|
+
})
|
|
448
|
+
.command('update [source]', 'Update installed LinX packages', (command) => command.positional('source', { type: 'string', describe: 'Package source to update' }), async (argv) => {
|
|
449
|
+
await runLinxPackageCommand('update', {
|
|
450
|
+
source: typeof argv.source === 'string' ? argv.source : undefined,
|
|
451
|
+
});
|
|
452
|
+
})
|
|
453
|
+
.command('list', 'List installed LinX packages', () => undefined, async () => {
|
|
454
|
+
await runLinxPackageCommand('list');
|
|
455
|
+
})
|
|
349
456
|
.command(execCommand)
|
|
350
457
|
.command(defaultPiCommand)
|
|
351
458
|
.command('chat [prompt..]', false, (command) => command
|
|
@@ -364,11 +471,11 @@ const cli = yargs(hideBin(process.argv))
|
|
|
364
471
|
const prompt = argv.prompt?.join(' ').trim() || undefined;
|
|
365
472
|
if (prompt) {
|
|
366
473
|
await runSingleTurn({ ctx, threadId, model: argv.model, prompt });
|
|
367
|
-
await ctx.
|
|
474
|
+
await ctx.podSession.close();
|
|
368
475
|
return;
|
|
369
476
|
}
|
|
370
477
|
await runInteractive({ ctx, initialThreadId: threadId, initialModel: argv.model });
|
|
371
|
-
await ctx.
|
|
478
|
+
await ctx.podSession.close();
|
|
372
479
|
})
|
|
373
480
|
.command('models', 'List available remote models', (command) => command.option('url', { type: 'string', describe: 'Runtime API base URL override' }), async (argv) => {
|
|
374
481
|
const ctx = await resolveRuntimeAuthContext(argv.url);
|
|
@@ -385,24 +492,44 @@ const cli = yargs(hideBin(process.argv))
|
|
|
385
492
|
}
|
|
386
493
|
else {
|
|
387
494
|
for (const model of models) {
|
|
388
|
-
const meta =
|
|
389
|
-
.filter(Boolean)
|
|
390
|
-
.join(' · ');
|
|
495
|
+
const meta = formatRemoteModelMetadata(model);
|
|
391
496
|
process.stdout.write(`- ${model.id}${meta ? ` (${meta})` : ''}\n`);
|
|
392
497
|
}
|
|
393
498
|
}
|
|
394
|
-
await ctx.
|
|
499
|
+
await ctx.podSession.close();
|
|
395
500
|
})
|
|
396
|
-
.command('resume
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
501
|
+
.command('resume [session]', 'Resume a previous interactive LinX session', (command) => command
|
|
502
|
+
.positional('session', { type: 'string', describe: 'Session id/prefix or JSONL file to resume' })
|
|
503
|
+
.option('last', { type: 'boolean', default: false, describe: 'Resume the most recent local session for this workspace' })
|
|
504
|
+
.option('cwd', { type: 'string', describe: 'Workspace path for the resumed session' })
|
|
505
|
+
.option('model', { type: 'string', describe: 'Model id to expose through the Pi runtime adapter' })
|
|
506
|
+
.option('backend', {
|
|
507
|
+
type: 'string',
|
|
508
|
+
default: 'cloud',
|
|
509
|
+
choices: ['cloud', 'native'],
|
|
510
|
+
describe: 'Backend mode. Default is cloud; native keeps the local Codex proxy for debugging only.',
|
|
511
|
+
})
|
|
512
|
+
.option('port', { type: 'number', default: 8787, describe: 'Local websocket port used only when --backend native' })
|
|
513
|
+
.option('runtime-url', { type: 'string', default: 'https://api.undefineds.co/v1', describe: 'Cloud runtime API base URL' }), async (argv) => {
|
|
514
|
+
const session = typeof argv.session === 'string' ? argv.session : undefined;
|
|
515
|
+
if (!session && !argv.last) {
|
|
516
|
+
const sessions = await listLinxPiSessions(argv.cwd || process.cwd(), LINX_AGENT_DIR);
|
|
517
|
+
if (sessions.length === 0) {
|
|
518
|
+
process.stdout.write('No LinX sessions found.\n');
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
process.stdout.write(`${sessions.map(formatLinxPiSessionSummary).join('\n')}\n`);
|
|
522
|
+
return;
|
|
404
523
|
}
|
|
405
|
-
await
|
|
524
|
+
await runPiCommand({
|
|
525
|
+
cwd: argv.cwd,
|
|
526
|
+
model: argv.model,
|
|
527
|
+
backend: argv.backend,
|
|
528
|
+
port: argv.port,
|
|
529
|
+
'runtime-url': argv['runtime-url'],
|
|
530
|
+
session,
|
|
531
|
+
last: argv.last || !session,
|
|
532
|
+
});
|
|
406
533
|
})
|
|
407
534
|
.command('fork [thread]', 'Fork a previous interactive session', (command) => command
|
|
408
535
|
.positional('thread', { type: 'string', describe: 'Thread ID to fork' })
|
|
@@ -448,11 +575,11 @@ const cli = yargs(hideBin(process.argv))
|
|
|
448
575
|
.command('watch <action> [backend] [prompt..]', 'Run or inspect local watch backends', (command) => command
|
|
449
576
|
.positional('action', {
|
|
450
577
|
type: 'string',
|
|
451
|
-
choices: ['run', 'backends', 'sessions', 'show', '
|
|
578
|
+
choices: ['run', 'backends', 'sessions', 'show', 'codex', 'claude', 'codebuddy'],
|
|
452
579
|
})
|
|
453
580
|
.positional('backend', {
|
|
454
581
|
type: 'string',
|
|
455
|
-
describe: 'Watch backend for `run
|
|
582
|
+
describe: 'Watch backend for `run` or session id for `show`',
|
|
456
583
|
})
|
|
457
584
|
.option('mode', {
|
|
458
585
|
type: 'string',
|
|
@@ -479,14 +606,11 @@ const cli = yargs(hideBin(process.argv))
|
|
|
479
606
|
choices: ['auto', 'local', 'cloud'],
|
|
480
607
|
describe: 'Resolve credentials only: local CLI login, LinX cloud config, or auto fallback. Runtime still runs locally.',
|
|
481
608
|
})
|
|
482
|
-
.option('
|
|
483
|
-
type: 'boolean',
|
|
484
|
-
default: false,
|
|
485
|
-
describe: 'Approve for the current watch session instead of only once.',
|
|
486
|
-
})
|
|
487
|
-
.option('reason', {
|
|
609
|
+
.option('approval-source', {
|
|
488
610
|
type: 'string',
|
|
489
|
-
|
|
611
|
+
default: 'hybrid',
|
|
612
|
+
choices: ['local', 'remote', 'hybrid'],
|
|
613
|
+
describe: 'Resolve backend approval requests locally, through Pod remote approvals, or whichever answers first.',
|
|
490
614
|
}), async (argv) => {
|
|
491
615
|
const rawAction = String(argv.action);
|
|
492
616
|
const directBackend = ['codex', 'claude', 'codebuddy'].includes(rawAction);
|
|
@@ -523,30 +647,6 @@ const cli = yargs(hideBin(process.argv))
|
|
|
523
647
|
process.stdout.write(formatArchivedWatchSession(session, loadArchivedWatchEvents(sessionId)));
|
|
524
648
|
return;
|
|
525
649
|
}
|
|
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
|
-
}
|
|
550
650
|
const backend = (directBackend ? rawAction : argv.backend);
|
|
551
651
|
if (!backend || !['codex', 'claude', 'codebuddy'].includes(backend)) {
|
|
552
652
|
throw new Error('Usage: linx watch run <codex|claude|codebuddy> <prompt> [-- backend args]\n or: linx watch <codex|claude|codebuddy> <prompt>');
|
|
@@ -565,13 +665,26 @@ const cli = yargs(hideBin(process.argv))
|
|
|
565
665
|
prompt,
|
|
566
666
|
passthroughArgs: (argv['--'] ?? []).map(String),
|
|
567
667
|
credentialSource: argv['credential-source'],
|
|
668
|
+
approvalSource: argv['approval-source'],
|
|
568
669
|
});
|
|
569
670
|
if (exitCode !== 0) {
|
|
570
671
|
process.exitCode = exitCode;
|
|
571
672
|
}
|
|
572
673
|
})
|
|
573
674
|
.strict()
|
|
574
|
-
.help()
|
|
675
|
+
.help()
|
|
676
|
+
.fail((message, error, yargsInstance) => {
|
|
677
|
+
if (error) {
|
|
678
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
679
|
+
process.exit(1);
|
|
680
|
+
}
|
|
681
|
+
if (message) {
|
|
682
|
+
console.error(message);
|
|
683
|
+
process.exit(1);
|
|
684
|
+
}
|
|
685
|
+
yargsInstance.showHelp();
|
|
686
|
+
process.exit(1);
|
|
687
|
+
});
|
|
575
688
|
process.on('unhandledRejection', (error) => {
|
|
576
689
|
console.error(error instanceof Error ? error.message : String(error));
|
|
577
690
|
process.exit(1);
|