shennian 0.2.88 → 0.2.90
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/dist/assets/wechat-channel/macos/manifest.json +22 -0
- package/dist/assets/wechat-channel/macos/shennian-wechat-channel-helper +0 -0
- package/dist/bin/shennian.js +1 -1
- package/dist/publish-build-manifest.json +548 -0
- package/dist/scripts/wechat-rpa-confirmation.mjs +5 -97
- package/dist/src/agent-env.js +4 -105
- package/dist/src/agents/adapter.d.ts +6 -0
- package/dist/src/agents/adapter.js +1 -19
- package/dist/src/agents/claude.js +8 -305
- package/dist/src/agents/codex-control.d.ts +35 -0
- package/dist/src/agents/codex-control.js +2 -0
- package/dist/src/agents/codex-utils.js +7 -200
- package/dist/src/agents/codex.d.ts +8 -0
- package/dist/src/agents/codex.js +15 -863
- package/dist/src/agents/command-spec.js +2 -413
- package/dist/src/agents/config-status.js +1 -226
- package/dist/src/agents/cursor.js +1 -249
- package/dist/src/agents/custom.js +4 -271
- package/dist/src/agents/detect.js +1 -56
- package/dist/src/agents/external-channel-instructions.js +10 -94
- package/dist/src/agents/gemini.js +1 -173
- package/dist/src/agents/manager.js +13 -157
- package/dist/src/agents/model-registry/cache.js +1 -37
- package/dist/src/agents/model-registry/discovery.js +2 -187
- package/dist/src/agents/model-registry/parsers.js +4 -447
- package/dist/src/agents/model-registry/runner.js +1 -30
- package/dist/src/agents/model-registry/service.js +1 -78
- package/dist/src/agents/model-registry/types.js +1 -8
- package/dist/src/agents/model-registry.js +1 -18
- package/dist/src/agents/openclaw.js +2 -275
- package/dist/src/agents/opencode.js +1 -231
- package/dist/src/agents/pi-context.js +12 -217
- package/dist/src/agents/pi.js +14 -723
- package/dist/src/agents/platform-instructions.js +9 -54
- package/dist/src/channels/base.d.ts +4 -1
- package/dist/src/channels/base.js +1 -3
- package/dist/src/channels/registry.js +1 -30
- package/dist/src/channels/reply-split.js +10 -89
- package/dist/src/channels/runtime.d.ts +1 -0
- package/dist/src/channels/runtime.js +5 -533
- package/dist/src/channels/secret-registry.d.ts +1 -0
- package/dist/src/channels/secret-registry.js +1 -46
- package/dist/src/channels/websocket.js +8 -378
- package/dist/src/channels/wechat-channel/anchor.d.ts +10 -0
- package/dist/src/channels/wechat-channel/anchor.js +1 -0
- package/dist/src/channels/wechat-channel/client.d.ts +74 -0
- package/dist/src/channels/wechat-channel/client.js +1 -0
- package/dist/src/channels/wechat-channel/cooldown.d.ts +15 -0
- package/dist/src/channels/wechat-channel/cooldown.js +1 -0
- package/dist/src/channels/wechat-channel/fingerprint.d.ts +28 -0
- package/dist/src/channels/wechat-channel/fingerprint.js +1 -0
- package/dist/src/channels/wechat-channel/helper-assets.d.ts +37 -0
- package/dist/src/channels/wechat-channel/helper-assets.js +1 -0
- package/dist/src/channels/wechat-channel/helper-client.d.ts +25 -0
- package/dist/src/channels/wechat-channel/helper-client.js +3 -0
- package/dist/src/channels/wechat-channel/helper-protocol.d.ts +84 -0
- package/dist/src/channels/wechat-channel/helper-protocol.js +1 -0
- package/dist/src/channels/wechat-channel/index.d.ts +17 -0
- package/dist/src/channels/wechat-channel/index.js +1 -0
- package/dist/src/channels/wechat-channel/ledger.d.ts +33 -0
- package/dist/src/channels/wechat-channel/ledger.js +1 -0
- package/dist/src/channels/wechat-channel/media-resolver.d.ts +32 -0
- package/dist/src/channels/wechat-channel/media-resolver.js +1 -0
- package/dist/src/channels/wechat-channel/message-key.d.ts +19 -0
- package/dist/src/channels/wechat-channel/message-key.js +1 -0
- package/dist/src/channels/wechat-channel/observer.d.ts +64 -0
- package/dist/src/channels/wechat-channel/observer.js +1 -0
- package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +69 -0
- package/dist/src/channels/wechat-channel/outbound-ledger.js +2 -0
- package/dist/src/channels/wechat-channel/outbound-sender.d.ts +26 -0
- package/dist/src/channels/wechat-channel/outbound-sender.js +1 -0
- package/dist/src/channels/wechat-channel/preflight.d.ts +37 -0
- package/dist/src/channels/wechat-channel/preflight.js +1 -0
- package/dist/src/channels/wechat-channel/runner.d.ts +34 -0
- package/dist/src/channels/wechat-channel/runner.js +1 -0
- package/dist/src/channels/wechat-channel/runtime.d.ts +45 -0
- package/dist/src/channels/wechat-channel/runtime.js +1 -0
- package/dist/src/channels/wechat-channel/scheduler.d.ts +35 -0
- package/dist/src/channels/wechat-channel/scheduler.js +1 -0
- package/dist/src/channels/wechat-rpa/macos-flow.js +1 -96
- package/dist/src/channels/wechat-rpa/macos.js +6 -48
- package/dist/src/channels/wechat-rpa/normalizer.js +7 -127
- package/dist/src/channels/wechat-rpa.d.ts +21 -0
- package/dist/src/channels/wechat-rpa.js +6 -1022
- package/dist/src/channels/wecom.js +4 -357
- package/dist/src/commands/agent.js +6 -131
- package/dist/src/commands/daemon-windows.js +8 -48
- package/dist/src/commands/daemon.js +19 -1013
- package/dist/src/commands/external-attachments.js +1 -51
- package/dist/src/commands/external.js +1 -137
- package/dist/src/commands/manager.js +2 -389
- package/dist/src/commands/pair-qr.js +1 -6
- package/dist/src/commands/pair.js +9 -287
- package/dist/src/commands/tools.js +1 -34
- package/dist/src/commands/upgrade.js +1 -198
- package/dist/src/config/index.js +1 -35
- package/dist/src/daemon-log.js +6 -58
- package/dist/src/env-path.js +1 -64
- package/dist/src/fs/boundary.js +1 -126
- package/dist/src/fs/handler.js +1 -130
- package/dist/src/fs/security.js +1 -32
- package/dist/src/fs/text-decoder.d.ts +10 -0
- package/dist/src/fs/text-decoder.js +1 -0
- package/dist/src/index.js +2 -404
- package/dist/src/log-reporter.js +1 -16
- package/dist/src/manager/prompt.js +29 -34
- package/dist/src/manager/registry.js +2 -269
- package/dist/src/manager/runtime.js +19 -1003
- package/dist/src/native-fusion/config.js +1 -5
- package/dist/src/native-fusion/opencode-parser.js +3 -123
- package/dist/src/native-fusion/parser-common.js +8 -264
- package/dist/src/native-fusion/parsers.js +8 -729
- package/dist/src/native-fusion/service.d.ts +10 -0
- package/dist/src/native-fusion/service.js +2 -198
- package/dist/src/native-fusion/state.js +1 -22
- package/dist/src/native-fusion/types.js +1 -1
- package/dist/src/region.js +1 -88
- package/dist/src/relay/client.js +1 -343
- package/dist/src/session/archive-zip.js +1 -220
- package/dist/src/session/handlers/agent-config.js +1 -150
- package/dist/src/session/handlers/agents.js +1 -55
- package/dist/src/session/handlers/chat.js +2 -733
- package/dist/src/session/handlers/control.js +1 -55
- package/dist/src/session/handlers/fs.js +1 -747
- package/dist/src/session/handlers/session-refresh.js +1 -35
- package/dist/src/session/handlers/skills.js +1 -121
- package/dist/src/session/handlers/title.js +1 -60
- package/dist/src/session/handlers/tool-detail.d.ts +3 -0
- package/dist/src/session/handlers/tool-detail.js +1 -0
- package/dist/src/session/manager.d.ts +3 -0
- package/dist/src/session/manager.js +1 -261
- package/dist/src/session/projection.js +1 -54
- package/dist/src/session/queue.js +4 -317
- package/dist/src/session/remote-attachments.js +1 -72
- package/dist/src/session/store.js +3 -109
- package/dist/src/session/types.d.ts +4 -0
- package/dist/src/session/types.js +1 -4
- package/dist/src/skills/registry.js +15 -148
- package/dist/src/skills/setup.js +1 -101
- package/dist/src/tools/markdown-to-pdf.js +10 -346
- package/dist/src/upgrade/engine.js +3 -347
- package/package.json +3 -2
- package/dist/scripts/wechat-rpa-download-candidates.mjs +0 -105
|
@@ -1,413 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { spawn, spawnSync } from 'node:child_process';
|
|
4
|
-
import fs from 'node:fs';
|
|
5
|
-
import os from 'node:os';
|
|
6
|
-
import path from 'node:path';
|
|
7
|
-
const BUILTIN_COMMANDS = {
|
|
8
|
-
claude: [
|
|
9
|
-
{ command: 'claude', args: [], display: 'claude' },
|
|
10
|
-
],
|
|
11
|
-
codex: [
|
|
12
|
-
{ command: 'codex', args: [], display: 'codex' },
|
|
13
|
-
],
|
|
14
|
-
gemini: [
|
|
15
|
-
{ command: 'gemini', args: [], display: 'gemini' },
|
|
16
|
-
],
|
|
17
|
-
cursor: [
|
|
18
|
-
{ command: 'agent', args: [], display: 'agent' },
|
|
19
|
-
{ command: 'cursor', args: ['agent'], display: 'cursor agent' },
|
|
20
|
-
],
|
|
21
|
-
// OpenClaw support is intentionally disabled.
|
|
22
|
-
openclaw: [],
|
|
23
|
-
opencode: [
|
|
24
|
-
{ command: 'opencode', args: [], display: 'opencode' },
|
|
25
|
-
],
|
|
26
|
-
pi: [
|
|
27
|
-
{ command: 'shennian', args: [], display: 'shennian' },
|
|
28
|
-
],
|
|
29
|
-
manager: [
|
|
30
|
-
{ command: 'shennian', args: ['manager'], display: 'shennian manager' },
|
|
31
|
-
],
|
|
32
|
-
};
|
|
33
|
-
const availabilityCache = new Map();
|
|
34
|
-
const resolvedCache = new Map();
|
|
35
|
-
const pathLookupCache = new Map();
|
|
36
|
-
const wslAvailabilityCache = new Map();
|
|
37
|
-
export function resetCommandSpecCache() {
|
|
38
|
-
availabilityCache.clear();
|
|
39
|
-
resolvedCache.clear();
|
|
40
|
-
pathLookupCache.clear();
|
|
41
|
-
wslAvailabilityCache.clear();
|
|
42
|
-
}
|
|
43
|
-
export function getProcessPlatform() {
|
|
44
|
-
return process.platform;
|
|
45
|
-
}
|
|
46
|
-
function splitLines(text) {
|
|
47
|
-
return text
|
|
48
|
-
.split(/\r?\n/)
|
|
49
|
-
.map((line) => line.trim())
|
|
50
|
-
.filter(Boolean);
|
|
51
|
-
}
|
|
52
|
-
function getFallbackCommandCandidates(command) {
|
|
53
|
-
if (path.basename(command) !== command)
|
|
54
|
-
return [command];
|
|
55
|
-
const home = os.homedir();
|
|
56
|
-
if (getProcessPlatform() === 'win32') {
|
|
57
|
-
const winPath = path.win32;
|
|
58
|
-
const names = path.extname(command)
|
|
59
|
-
? [command]
|
|
60
|
-
: [command, `${command}.cmd`, `${command}.exe`, `${command}.bat`];
|
|
61
|
-
const appData = process.env.APPDATA || winPath.join(home, 'AppData', 'Roaming');
|
|
62
|
-
const localAppData = process.env.LOCALAPPDATA || winPath.join(home, 'AppData', 'Local');
|
|
63
|
-
const dirs = [
|
|
64
|
-
winPath.join(home, '.shennian', 'node'),
|
|
65
|
-
winPath.join('C:\\', 'nvm4w', 'nodejs'),
|
|
66
|
-
winPath.join(localAppData, 'npm-global'),
|
|
67
|
-
winPath.join(appData, 'npm'),
|
|
68
|
-
winPath.join(localAppData, 'pnpm'),
|
|
69
|
-
winPath.join(home, 'scoop', 'shims'),
|
|
70
|
-
winPath.join('C:\\', 'Program Files', 'nodejs'),
|
|
71
|
-
];
|
|
72
|
-
return dirs.flatMap((dir) => names.map((name) => winPath.join(dir, name)));
|
|
73
|
-
}
|
|
74
|
-
const dirs = [
|
|
75
|
-
path.join(home, '.npm-global', 'bin'),
|
|
76
|
-
path.join(home, '.local', 'bin'),
|
|
77
|
-
path.join(home, '.bun', 'bin'),
|
|
78
|
-
'/opt/homebrew/bin',
|
|
79
|
-
'/usr/local/bin',
|
|
80
|
-
'/usr/bin',
|
|
81
|
-
'/bin',
|
|
82
|
-
];
|
|
83
|
-
return dirs.map((dir) => path.join(dir, command));
|
|
84
|
-
}
|
|
85
|
-
function buildFallbackPathEnv(currentPath, command) {
|
|
86
|
-
const parts = currentPath?.split(path.delimiter).filter(Boolean) ?? [];
|
|
87
|
-
if (getProcessPlatform() === 'win32') {
|
|
88
|
-
if (command && path.basename(command) !== command) {
|
|
89
|
-
const commandDir = path.win32.dirname(command);
|
|
90
|
-
if (!parts.includes(commandDir))
|
|
91
|
-
parts.unshift(commandDir);
|
|
92
|
-
}
|
|
93
|
-
for (const candidate of getFallbackCommandCandidates('shennian')) {
|
|
94
|
-
const dir = path.win32.dirname(candidate);
|
|
95
|
-
if (!parts.includes(dir))
|
|
96
|
-
parts.push(dir);
|
|
97
|
-
}
|
|
98
|
-
return parts.join(path.delimiter);
|
|
99
|
-
}
|
|
100
|
-
if (command && path.basename(command) !== command) {
|
|
101
|
-
const commandDir = path.dirname(command);
|
|
102
|
-
if (!parts.includes(commandDir))
|
|
103
|
-
parts.unshift(commandDir);
|
|
104
|
-
}
|
|
105
|
-
for (const candidate of getFallbackCommandCandidates('shennian')) {
|
|
106
|
-
const dir = path.dirname(candidate);
|
|
107
|
-
if (!parts.includes(dir))
|
|
108
|
-
parts.push(dir);
|
|
109
|
-
}
|
|
110
|
-
return parts.join(path.delimiter);
|
|
111
|
-
}
|
|
112
|
-
function lookupCommandPaths(command) {
|
|
113
|
-
if (pathLookupCache.has(command)) {
|
|
114
|
-
return pathLookupCache.get(command) ?? [];
|
|
115
|
-
}
|
|
116
|
-
const isWindows = getProcessPlatform() === 'win32';
|
|
117
|
-
const lookup = isWindows ? 'where' : 'which';
|
|
118
|
-
const result = spawnSync(lookup, [command], {
|
|
119
|
-
encoding: 'utf-8',
|
|
120
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
121
|
-
timeout: 3000,
|
|
122
|
-
windowsHide: true,
|
|
123
|
-
env: {
|
|
124
|
-
...process.env,
|
|
125
|
-
PATH: isWindows ? process.env.PATH : buildFallbackPathEnv(process.env.PATH, command),
|
|
126
|
-
},
|
|
127
|
-
});
|
|
128
|
-
const paths = result.status === 0 ? splitLines(result.stdout ?? '') : [];
|
|
129
|
-
const withFallbacks = [...paths];
|
|
130
|
-
for (const candidate of getFallbackCommandCandidates(command)) {
|
|
131
|
-
if (fs.existsSync(candidate) && !withFallbacks.includes(candidate))
|
|
132
|
-
withFallbacks.push(candidate);
|
|
133
|
-
}
|
|
134
|
-
pathLookupCache.set(command, withFallbacks);
|
|
135
|
-
return withFallbacks;
|
|
136
|
-
}
|
|
137
|
-
function getWindowsShellPath() {
|
|
138
|
-
return process.env.ComSpec || 'cmd.exe';
|
|
139
|
-
}
|
|
140
|
-
function isWindowsNativeBinary(filePath) {
|
|
141
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
142
|
-
return ext === '.exe' || ext === '.com';
|
|
143
|
-
}
|
|
144
|
-
function isWindowsCmdShim(filePath) {
|
|
145
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
146
|
-
return ext === '.cmd' || ext === '.bat';
|
|
147
|
-
}
|
|
148
|
-
function isWindowsNodeScript(filePath) {
|
|
149
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
150
|
-
return ext === '.js' || ext === '.mjs' || ext === '.cjs';
|
|
151
|
-
}
|
|
152
|
-
function resolveWindowsNodeCommand() {
|
|
153
|
-
return lookupCommandPaths('node.exe')[0] ?? lookupCommandPaths('node')[0] ?? 'node';
|
|
154
|
-
}
|
|
155
|
-
function quoteCmdArg(text) {
|
|
156
|
-
return `"${text.replace(/"/g, '""')}"`;
|
|
157
|
-
}
|
|
158
|
-
function quotePosixArg(text) {
|
|
159
|
-
return `'${text.replace(/'/g, `'\\''`)}'`;
|
|
160
|
-
}
|
|
161
|
-
export function windowsPathToWsl(input) {
|
|
162
|
-
if (!input)
|
|
163
|
-
return undefined;
|
|
164
|
-
const normalized = input.replace(/\//g, '\\');
|
|
165
|
-
const driveMatch = normalized.match(/^([A-Za-z]):\\(.*)$/);
|
|
166
|
-
if (driveMatch) {
|
|
167
|
-
const [, drive, rest] = driveMatch;
|
|
168
|
-
const suffix = rest.replace(/\\/g, '/');
|
|
169
|
-
return `/mnt/${drive.toLowerCase()}/${suffix}`;
|
|
170
|
-
}
|
|
171
|
-
if (normalized.startsWith('\\\\wsl$\\')) {
|
|
172
|
-
const remainder = normalized.replace(/^\\\\wsl\$\\[^\\]+\\?/, '');
|
|
173
|
-
return `/${remainder.replace(/\\/g, '/')}`;
|
|
174
|
-
}
|
|
175
|
-
if (input.startsWith('/'))
|
|
176
|
-
return input;
|
|
177
|
-
return undefined;
|
|
178
|
-
}
|
|
179
|
-
function resolveArbitraryWindowsCommand(command) {
|
|
180
|
-
const normalized = command.trim();
|
|
181
|
-
if (!normalized) {
|
|
182
|
-
throw new Error('Command string is empty');
|
|
183
|
-
}
|
|
184
|
-
const hasExplicitPath = /[\\/]/.test(normalized) || /^[A-Za-z]:/.test(normalized);
|
|
185
|
-
if (hasExplicitPath) {
|
|
186
|
-
if (isWindowsCmdShim(normalized))
|
|
187
|
-
return { kind: 'cmd-shim', path: normalized };
|
|
188
|
-
return { kind: 'direct', path: normalized };
|
|
189
|
-
}
|
|
190
|
-
const paths = lookupCommandPaths(normalized);
|
|
191
|
-
const native = paths.find(isWindowsNativeBinary);
|
|
192
|
-
if (native)
|
|
193
|
-
return { kind: 'direct', path: native };
|
|
194
|
-
const shim = paths.find(isWindowsCmdShim);
|
|
195
|
-
if (shim)
|
|
196
|
-
return { kind: 'cmd-shim', path: shim };
|
|
197
|
-
return { kind: 'direct', path: normalized };
|
|
198
|
-
}
|
|
199
|
-
function canUseWslBridge(command) {
|
|
200
|
-
if (wslAvailabilityCache.has(command)) {
|
|
201
|
-
return wslAvailabilityCache.get(command) ?? false;
|
|
202
|
-
}
|
|
203
|
-
const wslPath = lookupCommandPaths('wsl.exe')[0] ?? lookupCommandPaths('wsl')[0];
|
|
204
|
-
if (!wslPath) {
|
|
205
|
-
wslAvailabilityCache.set(command, false);
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
const result = spawnSync(wslPath, ['-e', 'sh', '-lc', `command -v ${quotePosixArg(command)} >/dev/null 2>&1`], {
|
|
209
|
-
stdio: 'ignore',
|
|
210
|
-
timeout: 5000,
|
|
211
|
-
windowsHide: true,
|
|
212
|
-
});
|
|
213
|
-
const ok = result.status === 0;
|
|
214
|
-
wslAvailabilityCache.set(command, ok);
|
|
215
|
-
return ok;
|
|
216
|
-
}
|
|
217
|
-
export function commandExists(command) {
|
|
218
|
-
if (availabilityCache.has(command)) {
|
|
219
|
-
return availabilityCache.get(command) ?? false;
|
|
220
|
-
}
|
|
221
|
-
const exists = lookupCommandPaths(command).length > 0 || (getProcessPlatform() === 'win32' && canUseWslBridge(command));
|
|
222
|
-
availabilityCache.set(command, exists);
|
|
223
|
-
return exists;
|
|
224
|
-
}
|
|
225
|
-
function resolveWindowsBuiltinCommand(candidate) {
|
|
226
|
-
const paths = lookupCommandPaths(candidate.command);
|
|
227
|
-
const native = paths.find(isWindowsNativeBinary);
|
|
228
|
-
if (native) {
|
|
229
|
-
return { ...candidate, kind: 'native', path: native };
|
|
230
|
-
}
|
|
231
|
-
const shim = paths.find(isWindowsCmdShim);
|
|
232
|
-
if (shim) {
|
|
233
|
-
return { ...candidate, kind: 'cmd-shim', path: shim };
|
|
234
|
-
}
|
|
235
|
-
if (canUseWslBridge(candidate.command)) {
|
|
236
|
-
const wslPath = lookupCommandPaths('wsl.exe')[0] ?? lookupCommandPaths('wsl')[0];
|
|
237
|
-
if (wslPath) {
|
|
238
|
-
return { ...candidate, kind: 'wsl-bridge', path: wslPath };
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
243
|
-
export function resolveBuiltinCommand(type) {
|
|
244
|
-
if (resolvedCache.has(type)) {
|
|
245
|
-
return resolvedCache.get(type) ?? null;
|
|
246
|
-
}
|
|
247
|
-
const resolved = BUILTIN_COMMANDS[type]
|
|
248
|
-
.map((candidate) => {
|
|
249
|
-
if (getProcessPlatform() === 'win32') {
|
|
250
|
-
return resolveWindowsBuiltinCommand(candidate);
|
|
251
|
-
}
|
|
252
|
-
const commandPath = lookupCommandPaths(candidate.command)[0];
|
|
253
|
-
return commandPath ? { ...candidate, kind: 'native', path: commandPath } : null;
|
|
254
|
-
})
|
|
255
|
-
.find(Boolean) ?? null;
|
|
256
|
-
resolvedCache.set(type, resolved);
|
|
257
|
-
return resolved;
|
|
258
|
-
}
|
|
259
|
-
export function buildLaunchSpec(spec, runtimeArgs, cwd) {
|
|
260
|
-
if (spec.kind === 'native') {
|
|
261
|
-
if (getProcessPlatform() === 'win32' && isWindowsNodeScript(spec.path)) {
|
|
262
|
-
return {
|
|
263
|
-
command: resolveWindowsNodeCommand(),
|
|
264
|
-
args: [spec.path, ...spec.args, ...runtimeArgs],
|
|
265
|
-
cwd,
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
return {
|
|
269
|
-
command: spec.path,
|
|
270
|
-
args: [...spec.args, ...runtimeArgs],
|
|
271
|
-
cwd,
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
if (spec.kind === 'cmd-shim') {
|
|
275
|
-
const commandLine = [spec.path, ...spec.args, ...runtimeArgs].map(quoteCmdArg).join(' ');
|
|
276
|
-
return {
|
|
277
|
-
command: getWindowsShellPath(),
|
|
278
|
-
args: ['/d', '/s', '/c', `"${commandLine}"`],
|
|
279
|
-
cwd,
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
const wslCwd = cwd ? windowsPathToWsl(cwd) : undefined;
|
|
283
|
-
if (cwd && !wslCwd) {
|
|
284
|
-
throw new Error(`Cannot map Windows path to WSL workDir: ${cwd}`);
|
|
285
|
-
}
|
|
286
|
-
const shellCommand = [
|
|
287
|
-
wslCwd ? `cd ${quotePosixArg(wslCwd)}` : '',
|
|
288
|
-
`exec ${[spec.command, ...spec.args, ...runtimeArgs].map(quotePosixArg).join(' ')}`,
|
|
289
|
-
].filter(Boolean).join(' && ');
|
|
290
|
-
return {
|
|
291
|
-
command: spec.path,
|
|
292
|
-
args: ['-e', 'sh', '-lc', shellCommand],
|
|
293
|
-
cwd: undefined,
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
export function spawnResolvedCommand(spec, runtimeArgs, options = {}) {
|
|
297
|
-
const launch = buildLaunchSpec(spec, runtimeArgs, options.cwd);
|
|
298
|
-
return spawn(launch.command, launch.args, {
|
|
299
|
-
...options,
|
|
300
|
-
cwd: launch.cwd,
|
|
301
|
-
windowsHide: options.windowsHide ?? true,
|
|
302
|
-
...(getProcessPlatform() === 'win32' && spec.kind === 'cmd-shim'
|
|
303
|
-
? { windowsVerbatimArguments: true }
|
|
304
|
-
: {}),
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
export function spawnAgentCommand(spec, runtimeArgs, options = {}) {
|
|
308
|
-
return spawnResolvedCommand(spec, runtimeArgs, options);
|
|
309
|
-
}
|
|
310
|
-
export function spawnResolvedCommandSync(spec, runtimeArgs, options = {}) {
|
|
311
|
-
const launch = buildLaunchSpec(spec, runtimeArgs, options.cwd);
|
|
312
|
-
return spawnSync(launch.command, launch.args, {
|
|
313
|
-
...options,
|
|
314
|
-
cwd: launch.cwd,
|
|
315
|
-
windowsHide: options.windowsHide ?? true,
|
|
316
|
-
...(getProcessPlatform() === 'win32' && spec.kind === 'cmd-shim'
|
|
317
|
-
? { windowsVerbatimArguments: true }
|
|
318
|
-
: {}),
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
export function parseCommandString(command) {
|
|
322
|
-
const args = [];
|
|
323
|
-
let current = '';
|
|
324
|
-
let inSingle = false;
|
|
325
|
-
let inDouble = false;
|
|
326
|
-
for (const ch of command) {
|
|
327
|
-
if (ch === "'" && !inDouble) {
|
|
328
|
-
inSingle = !inSingle;
|
|
329
|
-
continue;
|
|
330
|
-
}
|
|
331
|
-
if (ch === '"' && !inSingle) {
|
|
332
|
-
inDouble = !inDouble;
|
|
333
|
-
continue;
|
|
334
|
-
}
|
|
335
|
-
if (ch === ' ' && !inSingle && !inDouble) {
|
|
336
|
-
if (current) {
|
|
337
|
-
args.push(current);
|
|
338
|
-
current = '';
|
|
339
|
-
}
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
|
-
current += ch;
|
|
343
|
-
}
|
|
344
|
-
if (current)
|
|
345
|
-
args.push(current);
|
|
346
|
-
return args;
|
|
347
|
-
}
|
|
348
|
-
export function buildCommandStringLaunchSpec(commandString, runtimeArgs, cwd) {
|
|
349
|
-
const parts = parseCommandString(commandString);
|
|
350
|
-
const bin = parts[0];
|
|
351
|
-
if (!bin) {
|
|
352
|
-
throw new Error('Command string is empty');
|
|
353
|
-
}
|
|
354
|
-
const baseArgs = parts.slice(1);
|
|
355
|
-
if (getProcessPlatform() !== 'win32') {
|
|
356
|
-
return {
|
|
357
|
-
command: bin,
|
|
358
|
-
args: [...baseArgs, ...runtimeArgs],
|
|
359
|
-
cwd,
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
const invocation = resolveArbitraryWindowsCommand(bin);
|
|
363
|
-
if (invocation.kind === 'cmd-shim') {
|
|
364
|
-
return {
|
|
365
|
-
command: getWindowsShellPath(),
|
|
366
|
-
args: ['/d', '/s', '/c', `"${[invocation.path, ...baseArgs, ...runtimeArgs].map(quoteCmdArg).join(' ')}"`],
|
|
367
|
-
cwd,
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
if (isWindowsNodeScript(invocation.path)) {
|
|
371
|
-
return {
|
|
372
|
-
command: resolveWindowsNodeCommand(),
|
|
373
|
-
args: [invocation.path, ...baseArgs, ...runtimeArgs],
|
|
374
|
-
cwd,
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
return {
|
|
378
|
-
command: invocation.path,
|
|
379
|
-
args: [...baseArgs, ...runtimeArgs],
|
|
380
|
-
cwd,
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
export function spawnCommandString(commandString, runtimeArgs, options = {}) {
|
|
384
|
-
const launch = buildCommandStringLaunchSpec(commandString, runtimeArgs, options.cwd);
|
|
385
|
-
const parts = parseCommandString(commandString);
|
|
386
|
-
const invocation = getProcessPlatform() === 'win32' && parts[0]
|
|
387
|
-
? resolveArbitraryWindowsCommand(parts[0])
|
|
388
|
-
: null;
|
|
389
|
-
return spawn(launch.command, launch.args, {
|
|
390
|
-
...options,
|
|
391
|
-
cwd: launch.cwd,
|
|
392
|
-
windowsHide: options.windowsHide ?? true,
|
|
393
|
-
env: {
|
|
394
|
-
...options.env,
|
|
395
|
-
PATH: buildFallbackPathEnv(options.env?.PATH ?? process.env.PATH, parts[0]),
|
|
396
|
-
},
|
|
397
|
-
...(getProcessPlatform() === 'win32' && invocation?.kind === 'cmd-shim'
|
|
398
|
-
? { windowsVerbatimArguments: true }
|
|
399
|
-
: {}),
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
export function getCommandVersion(spec, flag = '--version') {
|
|
403
|
-
const result = spawnResolvedCommandSync(spec, [flag], {
|
|
404
|
-
encoding: 'utf-8',
|
|
405
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
406
|
-
timeout: 5000,
|
|
407
|
-
});
|
|
408
|
-
if (result.status !== 0)
|
|
409
|
-
return undefined;
|
|
410
|
-
const text = `${result.stdout ?? ''}\n${result.stderr ?? ''}`.trim();
|
|
411
|
-
const match = text.match(/\d+\.\d+[\w.-]*/);
|
|
412
|
-
return match?.[0];
|
|
413
|
-
}
|
|
1
|
+
import{spawn as x,spawnSync as p}from"node:child_process";import W from"node:fs";import D from"node:os";import s from"node:path";const T={claude:[{command:"claude",args:[],display:"claude"}],codex:[{command:"codex",args:[],display:"codex"}],gemini:[{command:"gemini",args:[],display:"gemini"}],cursor:[{command:"agent",args:[],display:"agent"},{command:"cursor",args:["agent"],display:"cursor agent"}],openclaw:[],opencode:[{command:"opencode",args:[],display:"opencode"}],pi:[{command:"shennian",args:[],display:"shennian"}],manager:[{command:"shennian",args:["manager"],display:"shennian manager"}]},l=new Map,m=new Map,f=new Map,u=new Map;function U(){l.clear(),m.clear(),f.clear(),u.clear()}function c(){return process.platform}function B(n){return n.split(/\r?\n/).map(e=>e.trim()).filter(Boolean)}function h(n){if(s.basename(n)!==n)return[n];const e=D.homedir();if(c()==="win32"){const t=s.win32,r=s.extname(n)?[n]:[n,`${n}.cmd`,`${n}.exe`,`${n}.bat`],o=process.env.APPDATA||t.join(e,"AppData","Roaming"),a=process.env.LOCALAPPDATA||t.join(e,"AppData","Local");return[t.join(e,".shennian","node"),t.join("C:\\","nvm4w","nodejs"),t.join(a,"npm-global"),t.join(o,"npm"),t.join(a,"pnpm"),t.join(e,"scoop","shims"),t.join("C:\\","Program Files","nodejs")].flatMap(H=>r.map(L=>t.join(H,L)))}return[s.join(e,".npm-global","bin"),s.join(e,".local","bin"),s.join(e,".bun","bin"),"/opt/homebrew/bin","/usr/local/bin","/usr/bin","/bin"].map(t=>s.join(t,n))}function v(n,e){const i=n?.split(s.delimiter).filter(Boolean)??[];if(c()==="win32"){if(e&&s.basename(e)!==e){const t=s.win32.dirname(e);i.includes(t)||i.unshift(t)}for(const t of h("shennian")){const r=s.win32.dirname(t);i.includes(r)||i.push(r)}return i.join(s.delimiter)}if(e&&s.basename(e)!==e){const t=s.dirname(e);i.includes(t)||i.unshift(t)}for(const t of h("shennian")){const r=s.dirname(t);i.includes(r)||i.push(r)}return i.join(s.delimiter)}function d(n){if(f.has(n))return f.get(n)??[];const e=c()==="win32",t=p(e?"where":"which",[n],{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:3e3,windowsHide:!0,env:{...process.env,PATH:e?process.env.PATH:v(process.env.PATH,n)}}),o=[...t.status===0?B(t.stdout??""):[]];for(const a of h(n))W.existsSync(a)&&!o.includes(a)&&o.push(a);return f.set(n,o),o}function C(){return process.env.ComSpec||"cmd.exe"}function b(n){const e=s.extname(n).toLowerCase();return e===".exe"||e===".com"}function w(n){const e=s.extname(n).toLowerCase();return e===".cmd"||e===".bat"}function j(n){const e=s.extname(n).toLowerCase();return e===".js"||e===".mjs"||e===".cjs"}function k(){return d("node.exe")[0]??d("node")[0]??"node"}function P(n){return`"${n.replace(/"/g,'""')}"`}function g(n){return`'${n.replace(/'/g,"'\\''")}'`}function M(n){if(!n)return;const e=n.replace(/\//g,"\\"),i=e.match(/^([A-Za-z]):\\(.*)$/);if(i){const[,t,r]=i,o=r.replace(/\\/g,"/");return`/mnt/${t.toLowerCase()}/${o}`}if(e.startsWith("\\\\wsl$\\"))return`/${e.replace(/^\\\\wsl\$\\[^\\]+\\?/,"").replace(/\\/g,"/")}`;if(n.startsWith("/"))return n}function A(n){const e=n.trim();if(!e)throw new Error("Command string is empty");if(/[\\/]/.test(e)||/^[A-Za-z]:/.test(e))return w(e)?{kind:"cmd-shim",path:e}:{kind:"direct",path:e};const t=d(e),r=t.find(b);if(r)return{kind:"direct",path:r};const o=t.find(w);return o?{kind:"cmd-shim",path:o}:{kind:"direct",path:e}}function $(n){if(u.has(n))return u.get(n)??!1;const e=d("wsl.exe")[0]??d("wsl")[0];if(!e)return u.set(n,!1),!1;const t=p(e,["-e","sh","-lc",`command -v ${g(n)} >/dev/null 2>&1`],{stdio:"ignore",timeout:5e3,windowsHide:!0}).status===0;return u.set(n,t),t}function Z(n){if(l.has(n))return l.get(n)??!1;const e=d(n).length>0||c()==="win32"&&$(n);return l.set(n,e),e}function E(n){const e=d(n.command),i=e.find(b);if(i)return{...n,kind:"native",path:i};const t=e.find(w);if(t)return{...n,kind:"cmd-shim",path:t};if($(n.command)){const r=d("wsl.exe")[0]??d("wsl")[0];if(r)return{...n,kind:"wsl-bridge",path:r}}return null}function _(n){if(m.has(n))return m.get(n)??null;const e=T[n].map(i=>{if(c()==="win32")return E(i);const t=d(i.command)[0];return t?{...i,kind:"native",path:t}:null}).find(Boolean)??null;return m.set(n,e),e}function S(n,e,i){if(n.kind==="native")return c()==="win32"&&j(n.path)?{command:k(),args:[n.path,...n.args,...e],cwd:i}:{command:n.path,args:[...n.args,...e],cwd:i};if(n.kind==="cmd-shim"){const o=[n.path,...n.args,...e].map(P).join(" ");return{command:C(),args:["/d","/s","/c",`"${o}"`],cwd:i}}const t=i?M(i):void 0;if(i&&!t)throw new Error(`Cannot map Windows path to WSL workDir: ${i}`);const r=[t?`cd ${g(t)}`:"",`exec ${[n.command,...n.args,...e].map(g).join(" ")}`].filter(Boolean).join(" && ");return{command:n.path,args:["-e","sh","-lc",r],cwd:void 0}}function N(n,e,i={}){const t=S(n,e,i.cwd);return x(t.command,t.args,{...i,cwd:t.cwd,windowsHide:i.windowsHide??!0,...c()==="win32"&&n.kind==="cmd-shim"?{windowsVerbatimArguments:!0}:{}})}function G(n,e,i={}){return N(n,e,i)}function z(n,e,i={}){const t=S(n,e,i.cwd);return p(t.command,t.args,{...i,cwd:t.cwd,windowsHide:i.windowsHide??!0,...c()==="win32"&&n.kind==="cmd-shim"?{windowsVerbatimArguments:!0}:{}})}function y(n){const e=[];let i="",t=!1,r=!1;for(const o of n){if(o==="'"&&!r){t=!t;continue}if(o==='"'&&!t){r=!r;continue}if(o===" "&&!t&&!r){i&&(e.push(i),i="");continue}i+=o}return i&&e.push(i),e}function F(n,e,i){const t=y(n),r=t[0];if(!r)throw new Error("Command string is empty");const o=t.slice(1);if(c()!=="win32")return{command:r,args:[...o,...e],cwd:i};const a=A(r);return a.kind==="cmd-shim"?{command:C(),args:["/d","/s","/c",`"${[a.path,...o,...e].map(P).join(" ")}"`],cwd:i}:j(a.path)?{command:k(),args:[a.path,...o,...e],cwd:i}:{command:a.path,args:[...o,...e],cwd:i}}function J(n,e,i={}){const t=F(n,e,i.cwd),r=y(n),o=c()==="win32"&&r[0]?A(r[0]):null;return x(t.command,t.args,{...i,cwd:t.cwd,windowsHide:i.windowsHide??!0,env:{...i.env,PATH:v(i.env?.PATH??process.env.PATH,r[0])},...c()==="win32"&&o?.kind==="cmd-shim"?{windowsVerbatimArguments:!0}:{}})}function K(n,e="--version"){const i=z(n,[e],{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:5e3});return i.status!==0?void 0:`${i.stdout??""}
|
|
2
|
+
${i.stderr??""}`.trim().match(/\d+\.\d+[\w.-]*/)?.[0]}export{F as buildCommandStringLaunchSpec,S as buildLaunchSpec,Z as commandExists,K as getCommandVersion,c as getProcessPlatform,y as parseCommandString,U as resetCommandSpecCache,_ as resolveBuiltinCommand,G as spawnAgentCommand,J as spawnCommandString,N as spawnResolvedCommand,z as spawnResolvedCommandSync,M as windowsPathToWsl};
|
|
@@ -1,226 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// @test src/__tests__/agent-config-status.test.ts
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import os from 'node:os';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import { readLatestUserEnv } from '../agent-env.js';
|
|
7
|
-
import { loadConfig, resolveShennianPath } from '../config/index.js';
|
|
8
|
-
const MANAGED_CONFIG_PATH = resolveShennianPath('agent-provider-config.json');
|
|
9
|
-
const TOKEN_SUFFIX_LENGTH = 4;
|
|
10
|
-
function isConfigurableAgent(agent) {
|
|
11
|
-
return agent === 'codex' || agent === 'claude' || agent === 'pi';
|
|
12
|
-
}
|
|
13
|
-
const AGENT_ENV_KEYS = {
|
|
14
|
-
codex: {
|
|
15
|
-
baseUrl: ['OPENAI_BASE_URL', 'OPENAI_API_BASE', 'OPENAI_API_URL'],
|
|
16
|
-
token: ['OPENAI_API_KEY', 'OPENAI_TOKEN'],
|
|
17
|
-
},
|
|
18
|
-
claude: {
|
|
19
|
-
baseUrl: ['ANTHROPIC_BASE_URL', 'ANTHROPIC_API_URL'],
|
|
20
|
-
token: ['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN'],
|
|
21
|
-
},
|
|
22
|
-
pi: {
|
|
23
|
-
baseUrl: ['DASHSCOPE_BASE_URL', 'DASHSCOPE_API_BASE', 'DASHSCOPE_API_URL'],
|
|
24
|
-
token: ['DASHSCOPE_API_KEY', 'DASHSCOPE_TOKEN'],
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
export function getManagedAgentProviderConfig(agent) {
|
|
28
|
-
return loadManagedConfig().configs[agent];
|
|
29
|
-
}
|
|
30
|
-
export function upsertManagedAgentProviderConfig(input) {
|
|
31
|
-
const file = loadManagedConfig();
|
|
32
|
-
const existing = file.configs[input.agent];
|
|
33
|
-
const record = {
|
|
34
|
-
agent: input.agent,
|
|
35
|
-
baseUrl: input.baseUrl?.trim() || existing?.baseUrl,
|
|
36
|
-
token: input.token?.trim() || existing?.token,
|
|
37
|
-
updatedAt: new Date().toISOString(),
|
|
38
|
-
};
|
|
39
|
-
file.configs[input.agent] = record;
|
|
40
|
-
saveManagedConfig(file);
|
|
41
|
-
return record;
|
|
42
|
-
}
|
|
43
|
-
export function deleteManagedAgentProviderConfig(agent) {
|
|
44
|
-
const file = loadManagedConfig();
|
|
45
|
-
delete file.configs[agent];
|
|
46
|
-
saveManagedConfig(file);
|
|
47
|
-
}
|
|
48
|
-
export function buildManagedAgentEnv(agent) {
|
|
49
|
-
if (!isConfigurableAgent(agent))
|
|
50
|
-
return {};
|
|
51
|
-
const config = getManagedAgentProviderConfig(agent);
|
|
52
|
-
if (!config)
|
|
53
|
-
return {};
|
|
54
|
-
if (agent === 'codex') {
|
|
55
|
-
return compactEnv({
|
|
56
|
-
OPENAI_BASE_URL: config.baseUrl,
|
|
57
|
-
OPENAI_API_KEY: config.token,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
if (agent === 'claude') {
|
|
61
|
-
return compactEnv({
|
|
62
|
-
ANTHROPIC_BASE_URL: config.baseUrl,
|
|
63
|
-
ANTHROPIC_API_KEY: config.token,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
return compactEnv({
|
|
67
|
-
DASHSCOPE_BASE_URL: config.baseUrl,
|
|
68
|
-
DASHSCOPE_API_KEY: config.token,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
export function getAgentConfigSummary(agent, env = readLatestUserEnv()) {
|
|
72
|
-
if (!isConfigurableAgent(agent))
|
|
73
|
-
return undefined;
|
|
74
|
-
const managed = getManagedAgentProviderConfig(agent);
|
|
75
|
-
if (managed?.token || managed?.baseUrl) {
|
|
76
|
-
return toSummary({
|
|
77
|
-
agent,
|
|
78
|
-
source: 'shennian',
|
|
79
|
-
status: managed.token ? 'managed' : 'missing',
|
|
80
|
-
baseUrl: managed.baseUrl,
|
|
81
|
-
token: managed.token,
|
|
82
|
-
tokenPresent: !!managed.token,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
const envConfig = detectFromEnv(agent, env);
|
|
86
|
-
if (envConfig.tokenPresent || envConfig.baseUrl) {
|
|
87
|
-
return toSummary({
|
|
88
|
-
agent,
|
|
89
|
-
source: 'env',
|
|
90
|
-
status: envConfig.tokenPresent ? 'detected' : 'missing',
|
|
91
|
-
baseUrl: envConfig.baseUrl,
|
|
92
|
-
token: envConfig.token,
|
|
93
|
-
tokenPresent: envConfig.tokenPresent,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
const fileConfig = detectFromKnownConfigFiles(agent);
|
|
97
|
-
if (fileConfig.tokenPresent || fileConfig.baseUrl) {
|
|
98
|
-
return toSummary({
|
|
99
|
-
agent,
|
|
100
|
-
source: 'config-file',
|
|
101
|
-
status: fileConfig.tokenPresent ? 'detected' : 'missing',
|
|
102
|
-
baseUrl: fileConfig.baseUrl,
|
|
103
|
-
token: fileConfig.token,
|
|
104
|
-
tokenPresent: fileConfig.tokenPresent,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
return toSummary({
|
|
108
|
-
agent,
|
|
109
|
-
source: 'unknown',
|
|
110
|
-
status: 'missing',
|
|
111
|
-
tokenPresent: false,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
function loadManagedConfig() {
|
|
115
|
-
try {
|
|
116
|
-
const parsed = JSON.parse(fs.readFileSync(MANAGED_CONFIG_PATH, 'utf-8'));
|
|
117
|
-
return { configs: parsed.configs ?? {} };
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
return { configs: {} };
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
function saveManagedConfig(file) {
|
|
124
|
-
fs.mkdirSync(path.dirname(MANAGED_CONFIG_PATH), { recursive: true });
|
|
125
|
-
fs.writeFileSync(MANAGED_CONFIG_PATH, JSON.stringify(file, null, 2), { mode: 0o600 });
|
|
126
|
-
try {
|
|
127
|
-
fs.chmodSync(MANAGED_CONFIG_PATH, 0o600);
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
// Best effort on filesystems without POSIX modes.
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
function compactEnv(env) {
|
|
134
|
-
const compacted = {};
|
|
135
|
-
for (const [key, value] of Object.entries(env)) {
|
|
136
|
-
if (value)
|
|
137
|
-
compacted[key] = value;
|
|
138
|
-
}
|
|
139
|
-
return compacted;
|
|
140
|
-
}
|
|
141
|
-
function detectFromEnv(agent, env) {
|
|
142
|
-
const keys = AGENT_ENV_KEYS[agent];
|
|
143
|
-
const baseUrl = firstEnv(env, keys.baseUrl);
|
|
144
|
-
const token = firstEnv(env, keys.token);
|
|
145
|
-
return { baseUrl, token, tokenPresent: !!token };
|
|
146
|
-
}
|
|
147
|
-
function firstEnv(env, keys) {
|
|
148
|
-
for (const key of keys) {
|
|
149
|
-
const value = env[key]?.trim();
|
|
150
|
-
if (value)
|
|
151
|
-
return value;
|
|
152
|
-
}
|
|
153
|
-
return undefined;
|
|
154
|
-
}
|
|
155
|
-
function detectFromKnownConfigFiles(agent) {
|
|
156
|
-
const home = os.homedir();
|
|
157
|
-
if (agent === 'pi') {
|
|
158
|
-
const config = loadConfig();
|
|
159
|
-
const token = config.apiKeys?.dashscope?.trim();
|
|
160
|
-
if (token)
|
|
161
|
-
return { token, tokenPresent: true };
|
|
162
|
-
}
|
|
163
|
-
const candidates = agent === 'codex'
|
|
164
|
-
? [path.join(home, '.codex', 'config.toml'), path.join(home, '.codex', 'auth.json')]
|
|
165
|
-
: agent === 'claude'
|
|
166
|
-
? [path.join(home, '.claude', 'settings.json'), path.join(home, '.claude.json')]
|
|
167
|
-
: [path.join(home, '.dashscope', 'config.json')];
|
|
168
|
-
for (const file of candidates) {
|
|
169
|
-
const text = readSmallTextFile(file);
|
|
170
|
-
if (!text)
|
|
171
|
-
continue;
|
|
172
|
-
const token = extractTokenLikeValue(text, agent);
|
|
173
|
-
const baseUrl = extractBaseUrl(text);
|
|
174
|
-
if (token || baseUrl)
|
|
175
|
-
return { token, baseUrl, tokenPresent: !!token };
|
|
176
|
-
}
|
|
177
|
-
return { tokenPresent: false };
|
|
178
|
-
}
|
|
179
|
-
function readSmallTextFile(file) {
|
|
180
|
-
try {
|
|
181
|
-
const stat = fs.statSync(file);
|
|
182
|
-
if (!stat.isFile() || stat.size > 1024 * 1024)
|
|
183
|
-
return null;
|
|
184
|
-
return fs.readFileSync(file, 'utf-8');
|
|
185
|
-
}
|
|
186
|
-
catch {
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
function extractBaseUrl(text) {
|
|
191
|
-
const match = text.match(/(?:base_url|baseURL|api_url|apiUrl|ANTHROPIC_BASE_URL|OPENAI_BASE_URL|DASHSCOPE_BASE_URL)["'\s:=]+([^"'\s,}]+)/i);
|
|
192
|
-
return match?.[1]?.trim();
|
|
193
|
-
}
|
|
194
|
-
function extractTokenLikeValue(text, agent) {
|
|
195
|
-
const prefix = agent === 'claude' ? 'sk-ant-' : agent === 'pi' ? 'sk-' : 'sk-';
|
|
196
|
-
const direct = text.match(new RegExp(`${prefix.replace(/-/g, '\\-')}[A-Za-z0-9_\\-]{8,}`));
|
|
197
|
-
if (direct?.[0])
|
|
198
|
-
return direct[0];
|
|
199
|
-
const keyMatch = text.match(/(?:api[_-]?key|auth[_-]?token|token|dashscope)["'\s:=]+([A-Za-z0-9_\-.]{12,})/i);
|
|
200
|
-
return keyMatch?.[1]?.trim();
|
|
201
|
-
}
|
|
202
|
-
function toSummary(input) {
|
|
203
|
-
return {
|
|
204
|
-
status: input.status,
|
|
205
|
-
source: input.source,
|
|
206
|
-
tokenPresent: input.tokenPresent,
|
|
207
|
-
...(input.baseUrl ? { baseUrlHost: formatBaseUrlHost(input.baseUrl) } : {}),
|
|
208
|
-
...(input.token ? { tokenHint: maskToken(input.token) } : {}),
|
|
209
|
-
updatedAt: new Date().toISOString(),
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
export function maskToken(token) {
|
|
213
|
-
const trimmed = token.trim();
|
|
214
|
-
if (!trimmed)
|
|
215
|
-
return '';
|
|
216
|
-
const suffix = trimmed.slice(-TOKEN_SUFFIX_LENGTH);
|
|
217
|
-
return `••••${suffix}`;
|
|
218
|
-
}
|
|
219
|
-
function formatBaseUrlHost(baseUrl) {
|
|
220
|
-
try {
|
|
221
|
-
return new URL(baseUrl).host || baseUrl;
|
|
222
|
-
}
|
|
223
|
-
catch {
|
|
224
|
-
return baseUrl.replace(/^https?:\/\//i, '').replace(/\/.*$/, '') || baseUrl;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
1
|
+
import s from"node:fs";import m from"node:os";import i from"node:path";import{readLatestUserEnv as E}from"../agent-env.js";import{loadConfig as S,resolveShennianPath as g}from"../config/index.js";const c=g("agent-provider-config.json"),U=4;function A(e){return e==="codex"||e==="claude"||e==="pi"}const O={codex:{baseUrl:["OPENAI_BASE_URL","OPENAI_API_BASE","OPENAI_API_URL"],token:["OPENAI_API_KEY","OPENAI_TOKEN"]},claude:{baseUrl:["ANTHROPIC_BASE_URL","ANTHROPIC_API_URL"],token:["ANTHROPIC_API_KEY","ANTHROPIC_AUTH_TOKEN"]},pi:{baseUrl:["DASHSCOPE_BASE_URL","DASHSCOPE_API_BASE","DASHSCOPE_API_URL"],token:["DASHSCOPE_API_KEY","DASHSCOPE_TOKEN"]}};function k(e){return u().configs[e]}function v(e){const n=u(),o=n.configs[e.agent],t={agent:e.agent,baseUrl:e.baseUrl?.trim()||o?.baseUrl,token:e.token?.trim()||o?.token,updatedAt:new Date().toISOString()};return n.configs[e.agent]=t,_(n),t}function B(e){const n=u();delete n.configs[e],_(n)}function K(e){if(!A(e))return{};const n=k(e);return n?l(e==="codex"?{OPENAI_BASE_URL:n.baseUrl,OPENAI_API_KEY:n.token}:e==="claude"?{ANTHROPIC_BASE_URL:n.baseUrl,ANTHROPIC_API_KEY:n.token}:{DASHSCOPE_BASE_URL:n.baseUrl,DASHSCOPE_API_KEY:n.token}):{}}function j(e,n=E()){if(!A(e))return;const o=k(e);if(o?.token||o?.baseUrl)return a({agent:e,source:"shennian",status:o.token?"managed":"missing",baseUrl:o.baseUrl,token:o.token,tokenPresent:!!o.token});const t=I(e,n);if(t.tokenPresent||t.baseUrl)return a({agent:e,source:"env",status:t.tokenPresent?"detected":"missing",baseUrl:t.baseUrl,token:t.token,tokenPresent:t.tokenPresent});const r=p(e);return r.tokenPresent||r.baseUrl?a({agent:e,source:"config-file",status:r.tokenPresent?"detected":"missing",baseUrl:r.baseUrl,token:r.token,tokenPresent:r.tokenPresent}):a({agent:e,source:"unknown",status:"missing",tokenPresent:!1})}function u(){try{return{configs:JSON.parse(s.readFileSync(c,"utf-8")).configs??{}}}catch{return{configs:{}}}}function _(e){s.mkdirSync(i.dirname(c),{recursive:!0}),s.writeFileSync(c,JSON.stringify(e,null,2),{mode:384});try{s.chmodSync(c,384)}catch{}}function l(e){const n={};for(const[o,t]of Object.entries(e))t&&(n[o]=t);return n}function I(e,n){const o=O[e],t=P(n,o.baseUrl),r=P(n,o.token);return{baseUrl:t,token:r,tokenPresent:!!r}}function P(e,n){for(const o of n){const t=e[o]?.trim();if(t)return t}}function p(e){const n=m.homedir();if(e==="pi"){const r=S().apiKeys?.dashscope?.trim();if(r)return{token:r,tokenPresent:!0}}const o=e==="codex"?[i.join(n,".codex","config.toml"),i.join(n,".codex","auth.json")]:e==="claude"?[i.join(n,".claude","settings.json"),i.join(n,".claude.json")]:[i.join(n,".dashscope","config.json")];for(const t of o){const r=C(t);if(!r)continue;const f=N(r,e),d=b(r);if(f||d)return{token:f,baseUrl:d,tokenPresent:!!f}}return{tokenPresent:!1}}function C(e){try{const n=s.statSync(e);return!n.isFile()||n.size>1024*1024?null:s.readFileSync(e,"utf-8")}catch{return null}}function b(e){return e.match(/(?:base_url|baseURL|api_url|apiUrl|ANTHROPIC_BASE_URL|OPENAI_BASE_URL|DASHSCOPE_BASE_URL)["'\s:=]+([^"'\s,}]+)/i)?.[1]?.trim()}function N(e,n){const o=n==="claude"?"sk-ant-":"sk-",t=e.match(new RegExp(`${o.replace(/-/g,"\\-")}[A-Za-z0-9_\\-]{8,}`));return t?.[0]?t[0]:e.match(/(?:api[_-]?key|auth[_-]?token|token|dashscope)["'\s:=]+([A-Za-z0-9_\-.]{12,})/i)?.[1]?.trim()}function a(e){return{status:e.status,source:e.source,tokenPresent:e.tokenPresent,...e.baseUrl?{baseUrlHost:R(e.baseUrl)}:{},...e.token?{tokenHint:h(e.token)}:{},updatedAt:new Date().toISOString()}}function h(e){const n=e.trim();return n?`\u2022\u2022\u2022\u2022${n.slice(-U)}`:""}function R(e){try{return new URL(e).host||e}catch{return e.replace(/^https?:\/\//i,"").replace(/\/.*$/,"")||e}}export{K as buildManagedAgentEnv,B as deleteManagedAgentProviderConfig,j as getAgentConfigSummary,k as getManagedAgentProviderConfig,h as maskToken,v as upsertManagedAgentProviderConfig};
|