@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.
- package/README.md +6 -3
- package/dist/generated/version.js +3 -0
- package/dist/generated/version.js.map +1 -0
- package/dist/index.js +139 -259
- 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 +105 -56
- package/dist/lib/ai-command.js.map +1 -1
- package/dist/lib/chat-api.js +19 -177
- 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 +1 -7
- package/dist/lib/credentials-store.js.map +1 -1
- package/dist/lib/default-model.js +0 -1
- package/dist/lib/default-model.js.map +1 -1
- package/dist/lib/login-command.js +2 -17
- package/dist/lib/login-command.js.map +1 -1
- package/dist/lib/models.js +27 -2
- package/dist/lib/models.js.map +1 -1
- package/dist/lib/oidc-auth.js +13 -78
- 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 +5 -12
- package/dist/lib/pi-adapter/auth.js.map +1 -1
- package/dist/lib/pi-adapter/branding.js +76 -554
- 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 +4 -154
- package/dist/lib/pi-adapter/interactive.js.map +1 -1
- package/dist/lib/pi-adapter/runtime.js +25 -140
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- package/dist/lib/pi-adapter/stream.js +4 -154
- package/dist/lib/pi-adapter/stream.js.map +1 -1
- package/dist/lib/pi-adapter/theme.js.map +1 -1
- package/dist/lib/pod-chat-store.js +1 -1
- package/dist/lib/pod-chat-store.js.map +1 -1
- package/dist/lib/profile-identity.js +60 -16
- 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 +0 -4
- package/dist/lib/watch/hooks/claude.js.map +1 -1
- package/dist/lib/watch/hooks/codebuddy.js +0 -4
- package/dist/lib/watch/hooks/codebuddy.js.map +1 -1
- package/dist/lib/watch/hooks/codex.js +0 -4
- 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 +1 -0
- 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 +37 -29
- package/dist/lib/watch/pod-ai.js.map +1 -1
- package/dist/lib/watch/pod-approval.js +216 -846
- package/dist/lib/watch/pod-approval.js.map +1 -1
- package/dist/lib/watch/pod-persistence.js +78 -184
- package/dist/lib/watch/pod-persistence.js.map +1 -1
- package/dist/lib/watch/runner.js +38 -243
- package/dist/lib/watch/runner.js.map +1 -1
- package/dist/lib/watch/types.js.map +1 -1
- package/dist/skills/drizzle-solid/SKILL.md +340 -0
- package/dist/skills/pod-storage/SKILL.md +60 -0
- package/dist/skills/solid-modeling/SKILL.md +274 -0
- package/dist/skills/xpod-componentsjs/SKILL.md +284 -0
- package/dist/watch-cli.js +34 -8
- package/dist/watch-cli.js.map +1 -1
- package/package.json +9 -3
- package/vendor/client/dist/client/index.d.ts +118 -0
- package/vendor/client/dist/client/index.js +260 -0
- package/vendor/client/dist/index.d.ts +1 -0
- package/vendor/client/dist/index.js +1 -0
- package/vendor/client/dist/watch/index.d.ts +226 -0
- package/vendor/client/dist/watch/index.js +1114 -0
- package/vendor/client/package.json +9 -0
- package/dist/lib/node-warning-filter.js +0 -34
- package/dist/lib/node-warning-filter.js.map +0 -1
- package/dist/lib/pi-adapter/pod-approval.js +0 -8
- package/dist/lib/pi-adapter/pod-approval.js.map +0 -1
- package/dist/lib/pi-adapter/pod-mirror-mapping.js +0 -189
- package/dist/lib/pi-adapter/pod-mirror-mapping.js.map +0 -1
- package/dist/lib/pi-adapter/pod-mirror.js +0 -334
- package/dist/lib/pi-adapter/pod-mirror.js.map +0 -1
- package/dist/lib/pi-adapter/pod-native.js +0 -478
- package/dist/lib/pi-adapter/pod-native.js.map +0 -1
- package/dist/lib/pi-adapter/session.js +0 -727
- package/dist/lib/pi-adapter/session.js.map +0 -1
- package/dist/lib/pod-data-session.js +0 -110
- package/dist/lib/pod-data-session.js.map +0 -1
- package/dist/lib/watch/secretary.js +0 -238
- package/dist/lib/watch/secretary.js.map +0 -1
- package/vendor/agent-runtime/dist/acp.d.ts +0 -27
- package/vendor/agent-runtime/dist/acp.js +0 -86
- package/vendor/agent-runtime/dist/companion-model.d.ts +0 -7
- package/vendor/agent-runtime/dist/companion-model.js +0 -12
- package/vendor/agent-runtime/dist/index.d.ts +0 -3
- package/vendor/agent-runtime/dist/index.js +0 -3
- package/vendor/agent-runtime/dist/turn-controller.d.ts +0 -69
- package/vendor/agent-runtime/dist/turn-controller.js +0 -129
- 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
|
|
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
|
|
74
|
-
- 如果本地已 `linx login`,LinX 会把 pending approval 写进 Pod 的 `approval / audit / inbox_notification
|
|
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 @@
|
|
|
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 {
|
|
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 {
|
|
16
|
-
import {
|
|
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 {
|
|
22
|
-
|
|
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
|
|
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:
|
|
53
|
+
issuerUrl: creds.url,
|
|
76
54
|
runtimeUrlOverride: urlOverride,
|
|
77
55
|
});
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
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:
|
|
94
|
+
issuerUrl: creds.url,
|
|
89
95
|
runtimeUrlOverride: urlOverride,
|
|
90
96
|
});
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
apiKey,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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输入 /
|
|
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('/
|
|
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)
|
|
266
|
-
shouldPromptLinxCloudLoginOnStart = true;
|
|
267
|
-
return null;
|
|
268
|
-
});
|
|
197
|
+
const existingCredential = await resolveLinxPiCloudOAuthCredential(undefined);
|
|
269
198
|
if (!existingCredential) {
|
|
270
|
-
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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.
|
|
367
|
+
await ctx.session.logout();
|
|
482
368
|
return;
|
|
483
369
|
}
|
|
484
370
|
await runInteractive({ ctx, initialThreadId: threadId, initialModel: argv.model });
|
|
485
|
-
await ctx.
|
|
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 =
|
|
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.
|
|
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('
|
|
520
|
-
|
|
521
|
-
const
|
|
522
|
-
if (
|
|
523
|
-
|
|
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
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
|
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('
|
|
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
|
-
|
|
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);
|