xgen-dex-cli 1.2.0
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 +175 -0
- package/dist/chunks/chunk-QXSCKZEG.js +4560 -0
- package/dist/chunks/chunk-QXSCKZEG.js.map +7 -0
- package/dist/chunks/tui-DMHUMWPG.js +1048 -0
- package/dist/chunks/tui-DMHUMWPG.js.map +7 -0
- package/dist/cli.js +1094 -0
- package/dist/cli.js.map +7 -0
- package/docs/CONNECTOR_FEATURE_MAP.md +300 -0
- package/docs/PROTOCOL.md +187 -0
- package/docs/TUI.md +46 -0
- package/docs/VSCODE.md +41 -0
- package/package.json +65 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1094 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DANGEROUS_COMMAND_PROMPT,
|
|
4
|
+
DexEngine,
|
|
5
|
+
DexError,
|
|
6
|
+
FileConfigStore,
|
|
7
|
+
KeytarCredentialStore,
|
|
8
|
+
bindHost,
|
|
9
|
+
dataDirectory,
|
|
10
|
+
openerInvocation,
|
|
11
|
+
publicError
|
|
12
|
+
} from "./chunks/chunk-QXSCKZEG.js";
|
|
13
|
+
|
|
14
|
+
// src/cli.ts
|
|
15
|
+
import { stdin as stdin3, stdout as stdout2, stderr as stderr2 } from "node:process";
|
|
16
|
+
|
|
17
|
+
// src/args.ts
|
|
18
|
+
var BOOLEAN_OPTIONS = /* @__PURE__ */ new Set([
|
|
19
|
+
"allow-dangerous",
|
|
20
|
+
"help",
|
|
21
|
+
"include-harness",
|
|
22
|
+
"json",
|
|
23
|
+
"jsonl",
|
|
24
|
+
"no-allow-dangerous",
|
|
25
|
+
"password-stdin",
|
|
26
|
+
"stdin",
|
|
27
|
+
"stdio",
|
|
28
|
+
"version"
|
|
29
|
+
]);
|
|
30
|
+
function parseArgs(argv) {
|
|
31
|
+
const positionals = [];
|
|
32
|
+
const options = /* @__PURE__ */ new Map();
|
|
33
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
34
|
+
const argument = argv[index];
|
|
35
|
+
if (argument === "-h") {
|
|
36
|
+
options.set("help", true);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (argument === "-v") {
|
|
40
|
+
options.set("version", true);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (!argument.startsWith("--")) {
|
|
44
|
+
positionals.push(argument);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const equal = argument.indexOf("=");
|
|
48
|
+
const name = argument.slice(2, equal >= 0 ? equal : void 0);
|
|
49
|
+
if (!name) throw new DexError("usage_error", `\uC798\uBABB\uB41C option\uC785\uB2C8\uB2E4: ${argument}`);
|
|
50
|
+
if (equal >= 0) {
|
|
51
|
+
options.set(name, argument.slice(equal + 1));
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (BOOLEAN_OPTIONS.has(name)) {
|
|
55
|
+
options.set(name, true);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const value = argv[index + 1];
|
|
59
|
+
if (value === void 0 || value.startsWith("--")) {
|
|
60
|
+
throw new DexError("usage_error", `--${name} \uAC12\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.`);
|
|
61
|
+
}
|
|
62
|
+
options.set(name, value);
|
|
63
|
+
index += 1;
|
|
64
|
+
}
|
|
65
|
+
return { positionals, options };
|
|
66
|
+
}
|
|
67
|
+
function option(args, name) {
|
|
68
|
+
const value = args.options.get(name);
|
|
69
|
+
return typeof value === "string" ? value : void 0;
|
|
70
|
+
}
|
|
71
|
+
function requiredOption(args, name) {
|
|
72
|
+
const value = option(args, name);
|
|
73
|
+
if (!value) throw new DexError("usage_error", `--${name} \uAC12\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.`);
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
function flag(args, name) {
|
|
77
|
+
return args.options.get(name) === true;
|
|
78
|
+
}
|
|
79
|
+
function positiveIntegerOption(args, name) {
|
|
80
|
+
const raw = option(args, name);
|
|
81
|
+
if (raw === void 0) return void 0;
|
|
82
|
+
const value = Number(raw);
|
|
83
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
84
|
+
throw new DexError("usage_error", `--${name}\uC740 \uC591\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
|
|
85
|
+
}
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/io.ts
|
|
90
|
+
import { createInterface } from "node:readline/promises";
|
|
91
|
+
import { stdin, stderr } from "node:process";
|
|
92
|
+
async function readStdin() {
|
|
93
|
+
const chunks = [];
|
|
94
|
+
for await (const chunk of stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
95
|
+
return Buffer.concat(chunks).toString("utf8").replace(/[\r\n]+$/, "");
|
|
96
|
+
}
|
|
97
|
+
async function promptLine(label) {
|
|
98
|
+
const readline = createInterface({ input: stdin, output: stderr });
|
|
99
|
+
try {
|
|
100
|
+
return await readline.question(label);
|
|
101
|
+
} finally {
|
|
102
|
+
readline.close();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function promptSecret(label) {
|
|
106
|
+
if (!stdin.isTTY || typeof stdin.setRawMode !== "function") return readStdin();
|
|
107
|
+
stderr.write(label);
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
let value = "";
|
|
110
|
+
const cleanup = () => {
|
|
111
|
+
stdin.off("data", onData);
|
|
112
|
+
stdin.setRawMode(false);
|
|
113
|
+
stdin.pause();
|
|
114
|
+
stderr.write("\n");
|
|
115
|
+
};
|
|
116
|
+
const onData = (chunk) => {
|
|
117
|
+
const text = chunk.toString();
|
|
118
|
+
for (const character of text) {
|
|
119
|
+
if (character === "") {
|
|
120
|
+
cleanup();
|
|
121
|
+
reject(Object.assign(new Error("\uC785\uB825\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."), { name: "AbortError" }));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (character === "\r" || character === "\n") {
|
|
125
|
+
cleanup();
|
|
126
|
+
resolve(value);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (character === "\x7F" || character === "\b") value = value.slice(0, -1);
|
|
130
|
+
else if (character >= " ") value += character;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
stdin.setRawMode(true);
|
|
134
|
+
stdin.resume();
|
|
135
|
+
stdin.on("data", onData);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/mode.ts
|
|
140
|
+
function isInteractiveTerminal(input) {
|
|
141
|
+
const ci = input.ci?.trim().toLowerCase();
|
|
142
|
+
const isCi = !!ci && ci !== "false" && ci !== "0";
|
|
143
|
+
return input.stdinIsTty && input.stdoutIsTty && input.term !== "dumb" && !isCi;
|
|
144
|
+
}
|
|
145
|
+
function shouldLaunchTui(positionals, terminal) {
|
|
146
|
+
if (!isInteractiveTerminal(terminal)) return false;
|
|
147
|
+
return positionals.length === 0 || positionals.length === 1 && positionals[0] === "ui";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ../../packages/rpc/src/server.ts
|
|
151
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
152
|
+
import { randomUUID } from "node:crypto";
|
|
153
|
+
|
|
154
|
+
// ../../packages/rpc/src/wire.ts
|
|
155
|
+
var DEX_PROTOCOL_VERSION = 1;
|
|
156
|
+
|
|
157
|
+
// ../../packages/rpc/src/server.ts
|
|
158
|
+
var RpcFailure = class extends Error {
|
|
159
|
+
constructor(rpcCode, message, data) {
|
|
160
|
+
super(message);
|
|
161
|
+
this.rpcCode = rpcCode;
|
|
162
|
+
this.data = data;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
function objectParams(params) {
|
|
166
|
+
if (params === void 0) return {};
|
|
167
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
168
|
+
throw new RpcFailure(-32602, "params must be an object");
|
|
169
|
+
}
|
|
170
|
+
return params;
|
|
171
|
+
}
|
|
172
|
+
function requiredString(params, key) {
|
|
173
|
+
const value = params[key];
|
|
174
|
+
if (typeof value !== "string" || !value.trim()) throw new RpcFailure(-32602, `${key} is required`);
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
177
|
+
function optionalString(params, key) {
|
|
178
|
+
const value = params[key];
|
|
179
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
180
|
+
if (typeof value !== "string") throw new RpcFailure(-32602, `${key} must be a string`);
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
function optionalInteger(params, key) {
|
|
184
|
+
const value = params[key];
|
|
185
|
+
if (value === void 0 || value === null) return void 0;
|
|
186
|
+
if (!Number.isInteger(value) || Number(value) < 1) {
|
|
187
|
+
throw new RpcFailure(-32602, `${key} must be a positive integer`);
|
|
188
|
+
}
|
|
189
|
+
return Number(value);
|
|
190
|
+
}
|
|
191
|
+
function optionalBoolean(params, key) {
|
|
192
|
+
const value = params[key];
|
|
193
|
+
if (value === void 0 || value === null) return void 0;
|
|
194
|
+
if (typeof value !== "boolean") throw new RpcFailure(-32602, `${key} must be a boolean`);
|
|
195
|
+
return value;
|
|
196
|
+
}
|
|
197
|
+
function optionalStringArray(params, key) {
|
|
198
|
+
const value = params[key];
|
|
199
|
+
if (value === void 0 || value === null) return void 0;
|
|
200
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
201
|
+
throw new RpcFailure(-32602, `${key} must be an array of strings`);
|
|
202
|
+
}
|
|
203
|
+
return value.map((item) => item.trim()).filter(Boolean);
|
|
204
|
+
}
|
|
205
|
+
function isRequest(value) {
|
|
206
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
207
|
+
const request = value;
|
|
208
|
+
return request.jsonrpc === "2.0" && typeof request.method === "string";
|
|
209
|
+
}
|
|
210
|
+
var DexRpcServer = class {
|
|
211
|
+
constructor(engine, options = {}) {
|
|
212
|
+
this.engine = engine;
|
|
213
|
+
this.input = options.input ?? process.stdin;
|
|
214
|
+
this.output = options.output ?? process.stdout;
|
|
215
|
+
this.log = options.log ?? ((message) => process.stderr.write(`${message}
|
|
216
|
+
`));
|
|
217
|
+
this.version = options.version ?? "0.1.0";
|
|
218
|
+
this.removeLocalToolsListener = engine.onLocalToolsStatus((status) => {
|
|
219
|
+
if (this.initialized && !this.closed) this.notify("localTools/status", status);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
input;
|
|
223
|
+
output;
|
|
224
|
+
log;
|
|
225
|
+
version;
|
|
226
|
+
activeChats = /* @__PURE__ */ new Map();
|
|
227
|
+
readline = null;
|
|
228
|
+
initialized = false;
|
|
229
|
+
closed = false;
|
|
230
|
+
removeLocalToolsListener;
|
|
231
|
+
start() {
|
|
232
|
+
if (this.readline) return;
|
|
233
|
+
this.readline = createInterface2({ input: this.input, crlfDelay: Infinity });
|
|
234
|
+
this.readline.on("line", (line) => void this.onLine(line));
|
|
235
|
+
this.readline.on("close", () => this.close());
|
|
236
|
+
}
|
|
237
|
+
close() {
|
|
238
|
+
if (this.closed) return;
|
|
239
|
+
this.closed = true;
|
|
240
|
+
for (const controller of this.activeChats.values()) controller.abort();
|
|
241
|
+
this.activeChats.clear();
|
|
242
|
+
this.engine.stopLocalTools();
|
|
243
|
+
this.removeLocalToolsListener();
|
|
244
|
+
if (this.readline) {
|
|
245
|
+
const readline = this.readline;
|
|
246
|
+
this.readline = null;
|
|
247
|
+
readline.close();
|
|
248
|
+
}
|
|
249
|
+
this.input.pause();
|
|
250
|
+
}
|
|
251
|
+
async onLine(line) {
|
|
252
|
+
if (!line.trim() || this.closed) return;
|
|
253
|
+
let value;
|
|
254
|
+
try {
|
|
255
|
+
value = JSON.parse(line);
|
|
256
|
+
} catch {
|
|
257
|
+
this.writeError(null, -32700, "Parse error");
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (!isRequest(value)) {
|
|
261
|
+
this.writeError(null, -32600, "Invalid Request");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const hasId = Object.prototype.hasOwnProperty.call(value, "id");
|
|
265
|
+
try {
|
|
266
|
+
if (!this.initialized && value.method !== "initialize" && value.method !== "exit") {
|
|
267
|
+
throw new RpcFailure(-32002, "initialize must be called first");
|
|
268
|
+
}
|
|
269
|
+
const result = await this.dispatch(value.method, value.params);
|
|
270
|
+
if (hasId) this.write({ jsonrpc: "2.0", id: value.id ?? null, result });
|
|
271
|
+
if (value.method === "shutdown" || value.method === "exit") setImmediate(() => this.close());
|
|
272
|
+
} catch (error) {
|
|
273
|
+
if (!hasId) {
|
|
274
|
+
this.log(`notification ${value.method} failed: ${publicError(error).message}`);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (error instanceof RpcFailure) {
|
|
278
|
+
this.writeError(value.id ?? null, error.rpcCode, error.message, error.data);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const exposed = publicError(error);
|
|
282
|
+
this.writeError(value.id ?? null, -32e3, exposed.message, exposed);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
async dispatch(method, rawParams) {
|
|
286
|
+
const params = objectParams(rawParams);
|
|
287
|
+
switch (method) {
|
|
288
|
+
case "initialize": {
|
|
289
|
+
const requested = params.protocolVersion;
|
|
290
|
+
if (requested !== DEX_PROTOCOL_VERSION) {
|
|
291
|
+
throw new DexError(
|
|
292
|
+
"protocol_mismatch",
|
|
293
|
+
`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 protocolVersion\uC785\uB2C8\uB2E4: ${String(requested)}`,
|
|
294
|
+
{ supported: DEX_PROTOCOL_VERSION }
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
this.initialized = true;
|
|
298
|
+
setImmediate(() => {
|
|
299
|
+
void this.engine.startLocalTools().catch(
|
|
300
|
+
(error) => this.log(`local tools: ${publicError(error).message}`)
|
|
301
|
+
);
|
|
302
|
+
});
|
|
303
|
+
return {
|
|
304
|
+
protocolVersion: DEX_PROTOCOL_VERSION,
|
|
305
|
+
server: { name: "dex-cli", version: this.version },
|
|
306
|
+
capabilities: {
|
|
307
|
+
profiles: true,
|
|
308
|
+
authentication: ["password"],
|
|
309
|
+
agents: true,
|
|
310
|
+
chatStreaming: true,
|
|
311
|
+
chatCancellation: true,
|
|
312
|
+
history: true,
|
|
313
|
+
localTools: true,
|
|
314
|
+
ssh: true
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
case "shutdown":
|
|
319
|
+
case "exit":
|
|
320
|
+
this.engine.stopLocalTools();
|
|
321
|
+
return null;
|
|
322
|
+
case "health":
|
|
323
|
+
return { ok: true, activeChats: this.activeChats.size };
|
|
324
|
+
case "profile/list":
|
|
325
|
+
return this.engine.listProfiles();
|
|
326
|
+
case "profile/set":
|
|
327
|
+
return this.engine.setProfile(requiredString(params, "name"), requiredString(params, "serverUrl"));
|
|
328
|
+
case "profile/use": {
|
|
329
|
+
const profile = await this.engine.useProfile(requiredString(params, "name"));
|
|
330
|
+
void this.engine.startLocalTools(profile.name).catch((error) => this.log(`local tools: ${publicError(error).message}`));
|
|
331
|
+
return profile;
|
|
332
|
+
}
|
|
333
|
+
case "auth/login": {
|
|
334
|
+
const auth = await this.engine.login(
|
|
335
|
+
requiredString(params, "email"),
|
|
336
|
+
requiredString(params, "password"),
|
|
337
|
+
optionalString(params, "profile")
|
|
338
|
+
);
|
|
339
|
+
void this.engine.startLocalTools(auth.profile).catch((error) => this.log(`local tools: ${publicError(error).message}`));
|
|
340
|
+
return auth;
|
|
341
|
+
}
|
|
342
|
+
case "auth/status":
|
|
343
|
+
return this.engine.authStatus(optionalString(params, "profile"));
|
|
344
|
+
case "auth/logout":
|
|
345
|
+
await this.engine.logout(optionalString(params, "profile"));
|
|
346
|
+
return { ok: true };
|
|
347
|
+
// ── SSH ──
|
|
348
|
+
// 프로토콜에는 Teams · 음성 · 알림도 있지만 RPC 로 열지 않는다. CLI 와
|
|
349
|
+
// 편집기에서 쓸 일이 아직 없고, 열어 두면 "되는 줄 알고" 부르는 경로가
|
|
350
|
+
// 생긴다. 타입은 @dex/protocol 에 그대로 있으므로 여는 것은 한 줄이다.
|
|
351
|
+
case "ssh/config":
|
|
352
|
+
return this.engine.sshConfig(optionalString(params, "profile"));
|
|
353
|
+
case "ssh/setEnabled":
|
|
354
|
+
return this.engine.setSshEnabled(
|
|
355
|
+
params.enabled === true,
|
|
356
|
+
optionalString(params, "profile")
|
|
357
|
+
);
|
|
358
|
+
case "ssh/createServer":
|
|
359
|
+
return this.engine.createSshServer(
|
|
360
|
+
objectParams(params.server),
|
|
361
|
+
optionalString(params, "profile")
|
|
362
|
+
);
|
|
363
|
+
case "ssh/updateServer":
|
|
364
|
+
return this.engine.updateSshServer(
|
|
365
|
+
requiredString(params, "name"),
|
|
366
|
+
objectParams(params.server),
|
|
367
|
+
optionalString(params, "profile")
|
|
368
|
+
);
|
|
369
|
+
case "ssh/deleteServer":
|
|
370
|
+
return this.engine.deleteSshServer(
|
|
371
|
+
requiredString(params, "name"),
|
|
372
|
+
optionalString(params, "profile")
|
|
373
|
+
);
|
|
374
|
+
case "ssh/testServer":
|
|
375
|
+
return this.engine.testSshServer(
|
|
376
|
+
requiredString(params, "name"),
|
|
377
|
+
optionalString(params, "profile")
|
|
378
|
+
);
|
|
379
|
+
case "localTools/status":
|
|
380
|
+
return this.engine.localToolsStatus();
|
|
381
|
+
case "localTools/list":
|
|
382
|
+
return (await this.engine.localToolsStatus()).tools;
|
|
383
|
+
case "localTools/configure": {
|
|
384
|
+
const patch = {};
|
|
385
|
+
const enabled = optionalBoolean(params, "enabled");
|
|
386
|
+
const cwd = optionalString(params, "cwd");
|
|
387
|
+
const timeoutMs = optionalInteger(params, "timeoutMs");
|
|
388
|
+
const allowedRoots = optionalStringArray(params, "allowedRoots");
|
|
389
|
+
const blockedCommands = optionalStringArray(params, "blockedCommands");
|
|
390
|
+
const allowDangerous = optionalBoolean(params, "allowDangerous");
|
|
391
|
+
if (enabled !== void 0) patch.enabled = enabled;
|
|
392
|
+
if (cwd !== void 0) patch.cwd = cwd;
|
|
393
|
+
if (timeoutMs !== void 0) patch.timeoutMs = timeoutMs;
|
|
394
|
+
if (allowedRoots !== void 0) patch.allowedRoots = allowedRoots;
|
|
395
|
+
if (blockedCommands !== void 0) patch.blockedCommands = blockedCommands;
|
|
396
|
+
if (allowDangerous !== void 0) patch.allowDangerous = allowDangerous;
|
|
397
|
+
const status = await this.engine.configureLocalTools(patch);
|
|
398
|
+
if (status.config.enabled) {
|
|
399
|
+
void this.engine.startLocalTools(optionalString(params, "profile")).catch(
|
|
400
|
+
(error) => this.log(`local tools: ${publicError(error).message}`)
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
return status;
|
|
404
|
+
}
|
|
405
|
+
case "localTools/run":
|
|
406
|
+
return this.engine.runLocalTool(requiredString(params, "tool"), params.args ?? {});
|
|
407
|
+
case "localTools/start":
|
|
408
|
+
return this.engine.startLocalTools(optionalString(params, "profile"), optionalInteger(params, "waitMs") ?? 0);
|
|
409
|
+
case "localTools/stop":
|
|
410
|
+
this.engine.stopLocalTools();
|
|
411
|
+
return this.engine.localToolsStatus();
|
|
412
|
+
case "agents/list": {
|
|
413
|
+
const owner = optionalString(params, "owner");
|
|
414
|
+
if (owner && owner !== "personal" && owner !== "shared") {
|
|
415
|
+
throw new RpcFailure(-32602, "owner must be personal or shared");
|
|
416
|
+
}
|
|
417
|
+
const query = {
|
|
418
|
+
page: optionalInteger(params, "page"),
|
|
419
|
+
pageSize: optionalInteger(params, "pageSize"),
|
|
420
|
+
search: optionalString(params, "search"),
|
|
421
|
+
status: optionalString(params, "status"),
|
|
422
|
+
owner,
|
|
423
|
+
includeHarness: params.includeHarness === true
|
|
424
|
+
};
|
|
425
|
+
return this.engine.listAgents(query, optionalString(params, "profile"));
|
|
426
|
+
}
|
|
427
|
+
case "history/conversations":
|
|
428
|
+
return this.engine.listConversations(optionalString(params, "profile"));
|
|
429
|
+
case "history/turns":
|
|
430
|
+
return this.engine.historyTurns(
|
|
431
|
+
requiredString(params, "workflowId"),
|
|
432
|
+
requiredString(params, "interactionId"),
|
|
433
|
+
optionalString(params, "workflowName"),
|
|
434
|
+
optionalString(params, "profile")
|
|
435
|
+
);
|
|
436
|
+
case "chat/start":
|
|
437
|
+
return this.startChat(params);
|
|
438
|
+
case "chat/cancel": {
|
|
439
|
+
const streamId = requiredString(params, "streamId");
|
|
440
|
+
const controller = this.activeChats.get(streamId);
|
|
441
|
+
if (!controller) return { cancelled: false };
|
|
442
|
+
controller.abort();
|
|
443
|
+
return { cancelled: true };
|
|
444
|
+
}
|
|
445
|
+
default:
|
|
446
|
+
throw new RpcFailure(-32601, `Method not found: ${method}`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
async startChat(params) {
|
|
450
|
+
const rawInput = params.input;
|
|
451
|
+
if (typeof rawInput !== "string" && !Array.isArray(rawInput) && (!rawInput || typeof rawInput !== "object")) {
|
|
452
|
+
throw new RpcFailure(-32602, "input must be a string, object, or array");
|
|
453
|
+
}
|
|
454
|
+
const input = {
|
|
455
|
+
profile: optionalString(params, "profile"),
|
|
456
|
+
workflowId: requiredString(params, "workflowId"),
|
|
457
|
+
workflowName: optionalString(params, "workflowName"),
|
|
458
|
+
interactionId: optionalString(params, "interactionId"),
|
|
459
|
+
input: rawInput
|
|
460
|
+
};
|
|
461
|
+
const resolved = await this.engine.resolveChatInput(input);
|
|
462
|
+
const streamId = optionalString(params, "streamId") ?? randomUUID();
|
|
463
|
+
if (this.activeChats.has(streamId)) throw new RpcFailure(-32602, `streamId already exists: ${streamId}`);
|
|
464
|
+
const controller = new AbortController();
|
|
465
|
+
this.activeChats.set(streamId, controller);
|
|
466
|
+
setImmediate(() => void this.runChat(streamId, resolved, controller));
|
|
467
|
+
return {
|
|
468
|
+
streamId,
|
|
469
|
+
interactionId: resolved.interactionId,
|
|
470
|
+
workflowId: resolved.workflowId,
|
|
471
|
+
workflowName: resolved.workflowName
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
async runChat(streamId, input, controller) {
|
|
475
|
+
try {
|
|
476
|
+
for await (const event of this.engine.chat(input, controller.signal)) {
|
|
477
|
+
this.notify("chat/event", { streamId, event });
|
|
478
|
+
}
|
|
479
|
+
this.notify("chat/complete", { streamId, interactionId: input.interactionId });
|
|
480
|
+
} catch (error) {
|
|
481
|
+
const exposed = publicError(error);
|
|
482
|
+
this.notify("chat/error", { streamId, error: exposed });
|
|
483
|
+
} finally {
|
|
484
|
+
this.activeChats.delete(streamId);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
notify(method, params) {
|
|
488
|
+
this.write({ jsonrpc: "2.0", method, params });
|
|
489
|
+
}
|
|
490
|
+
writeError(id, code, message, data) {
|
|
491
|
+
this.write({
|
|
492
|
+
jsonrpc: "2.0",
|
|
493
|
+
id,
|
|
494
|
+
error: { code, message, ...data === void 0 ? {} : { data } }
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
write(value) {
|
|
498
|
+
if (this.closed) return;
|
|
499
|
+
this.output.write(`${JSON.stringify(value)}
|
|
500
|
+
`);
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
// src/dex-host.ts
|
|
505
|
+
import { spawn } from "node:child_process";
|
|
506
|
+
import { createInterface as createInterface3 } from "node:readline";
|
|
507
|
+
import { platform } from "node:os";
|
|
508
|
+
import { stdin as stdin2, stdout } from "node:process";
|
|
509
|
+
function run(file, args, input) {
|
|
510
|
+
return new Promise((resolve) => {
|
|
511
|
+
let child;
|
|
512
|
+
try {
|
|
513
|
+
child = spawn(file, args, { stdio: ["pipe", "pipe", "ignore"] });
|
|
514
|
+
} catch {
|
|
515
|
+
resolve({ ok: false, out: "" });
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
let out = "";
|
|
519
|
+
child.stdout?.on("data", (c) => out += c.toString("utf8"));
|
|
520
|
+
child.on("error", () => resolve({ ok: false, out: "" }));
|
|
521
|
+
child.on("close", (code) => resolve({ ok: code === 0, out }));
|
|
522
|
+
if (input !== void 0) child.stdin?.end(input);
|
|
523
|
+
else child.stdin?.end();
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
function clipboardCommands() {
|
|
527
|
+
const os = platform();
|
|
528
|
+
if (os === "darwin") return { read: ["pbpaste", []], write: ["pbcopy", []] };
|
|
529
|
+
if (os === "win32") {
|
|
530
|
+
return {
|
|
531
|
+
read: ["powershell", ["-NoProfile", "-Command", "Get-Clipboard"]],
|
|
532
|
+
write: ["clip", []]
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
return { read: ["xclip", ["-selection", "clipboard", "-o"]], write: ["xclip", ["-selection", "clipboard"]] };
|
|
536
|
+
}
|
|
537
|
+
async function confirmDangerous(command) {
|
|
538
|
+
if (!stdin2.isTTY || !stdout.isTTY) return "deny";
|
|
539
|
+
const rl = createInterface3({ input: stdin2, output: stdout });
|
|
540
|
+
try {
|
|
541
|
+
stdout.write(`
|
|
542
|
+
${DANGEROUS_COMMAND_PROMPT.title}
|
|
543
|
+
${DANGEROUS_COMMAND_PROMPT.message}
|
|
544
|
+
`);
|
|
545
|
+
stdout.write(` ${DANGEROUS_COMMAND_PROMPT.detail(command)}
|
|
546
|
+
`);
|
|
547
|
+
const answer = await new Promise(
|
|
548
|
+
(resolve) => rl.question("\uD5C8\uC6A9\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? [n=\uAC70\uBD80 / y=\uC774\uBC88\uB9CC / a=\uC774 \uC138\uC158 \uB3D9\uC548] ", resolve)
|
|
549
|
+
);
|
|
550
|
+
const a = answer.trim().toLowerCase();
|
|
551
|
+
return a === "a" ? "session" : a === "y" ? "once" : "deny";
|
|
552
|
+
} finally {
|
|
553
|
+
rl.close();
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
var clip = clipboardCommands();
|
|
557
|
+
var terminalInteraction = {
|
|
558
|
+
confirmDangerous,
|
|
559
|
+
clipboard: clip ? {
|
|
560
|
+
async read() {
|
|
561
|
+
const r = await run(clip.read[0], clip.read[1]);
|
|
562
|
+
if (!r.ok) throw new Error(`\uD074\uB9BD\uBCF4\uB4DC\uB97C \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 (${clip.read[0]} \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4).`);
|
|
563
|
+
return r.out;
|
|
564
|
+
},
|
|
565
|
+
async write(text) {
|
|
566
|
+
const r = await run(clip.write[0], clip.write[1], text);
|
|
567
|
+
if (!r.ok) throw new Error(`\uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uC4F0\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 (${clip.write[0]} \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4).`);
|
|
568
|
+
}
|
|
569
|
+
} : void 0,
|
|
570
|
+
/**
|
|
571
|
+
* 터미널에는 알림 센터가 없다. OS 도우미가 있으면 그것으로, 없으면 **표준
|
|
572
|
+
* 오류에 한 줄** 쓴다 — 사용자가 보고 있는 곳이 거기다. 조용히 성공한 척하지
|
|
573
|
+
* 않는 것이 규칙이지만, 여기서는 실제로 사람에게 닿는다.
|
|
574
|
+
*/
|
|
575
|
+
async notify(title, body) {
|
|
576
|
+
const os = platform();
|
|
577
|
+
if (os === "darwin") {
|
|
578
|
+
const script = `display notification ${JSON.stringify(body)} with title ${JSON.stringify(title)}`;
|
|
579
|
+
const r = await run("osascript", ["-e", script]);
|
|
580
|
+
if (r.ok) return true;
|
|
581
|
+
} else if (os === "linux") {
|
|
582
|
+
const r = await run("notify-send", [title, body]);
|
|
583
|
+
if (r.ok) return true;
|
|
584
|
+
}
|
|
585
|
+
process.stderr.write(`
|
|
586
|
+
[\uC54C\uB9BC] ${title}${body ? `: ${body}` : ""}
|
|
587
|
+
`);
|
|
588
|
+
return true;
|
|
589
|
+
},
|
|
590
|
+
async openExternal(url) {
|
|
591
|
+
const { file, args } = openerInvocation(url);
|
|
592
|
+
const r = await run(file, args);
|
|
593
|
+
if (!r.ok) throw new Error(`\uC5F4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 (${file} \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4).`);
|
|
594
|
+
},
|
|
595
|
+
async openPath(absolutePath) {
|
|
596
|
+
const { file, args } = openerInvocation(absolutePath);
|
|
597
|
+
const r = await run(file, args);
|
|
598
|
+
return r.ok ? "" : `\uC5F4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 (${file} \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4).`;
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
function bindCliHost(configStore) {
|
|
602
|
+
let cached = null;
|
|
603
|
+
void configStore.read().then((c) => cached = c).catch(() => void 0);
|
|
604
|
+
const ports = {
|
|
605
|
+
secrets: {
|
|
606
|
+
get: (name) => credentials.getRaw(name),
|
|
607
|
+
set: (name, value) => credentials.setRaw(name, value)
|
|
608
|
+
},
|
|
609
|
+
config: {
|
|
610
|
+
load: () => cached ?? {},
|
|
611
|
+
save: (patch) => {
|
|
612
|
+
const next = { ...cached ?? {}, ...patch };
|
|
613
|
+
cached = next;
|
|
614
|
+
void configStore.write(next).catch(() => void 0);
|
|
615
|
+
return next;
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
paths: { dataRoot: () => dataDirectory() },
|
|
619
|
+
interaction: terminalInteraction
|
|
620
|
+
};
|
|
621
|
+
bindHost(ports);
|
|
622
|
+
}
|
|
623
|
+
var credentials = new KeytarCredentialStore();
|
|
624
|
+
|
|
625
|
+
// src/cli.ts
|
|
626
|
+
var VERSION = "0.1.0";
|
|
627
|
+
var HELP = `XGEN Dex CLI ${VERSION}
|
|
628
|
+
|
|
629
|
+
Usage:
|
|
630
|
+
dex \uB300\uD654\uD615 \uD130\uBBF8\uB110 UI
|
|
631
|
+
dex ui \uB300\uD654\uD615 \uD130\uBBF8\uB110 UI
|
|
632
|
+
dex profile set [name] --server <url>
|
|
633
|
+
dex profile use <name>
|
|
634
|
+
dex profile list [--json]
|
|
635
|
+
dex login --email <email> [--profile <name>] [--password-stdin]
|
|
636
|
+
dex status [--profile <name>] [--json]
|
|
637
|
+
dex logout [--profile <name>]
|
|
638
|
+
dex agents list [--search <text>] [--owner personal|shared] [--json]
|
|
639
|
+
dex chat --agent <workflow-id> [--name <workflow-name>] [--interaction <id>] [--jsonl]
|
|
640
|
+
dex history list [--json]
|
|
641
|
+
dex history turns --workflow <id> --interaction <id> [--json]
|
|
642
|
+
dex tools list [--json]
|
|
643
|
+
dex tools status [--profile <name>] [--json]
|
|
644
|
+
dex tools enable [--cwd <path>] [--allow <path,...>] [--block <command,...>] [--allow-dangerous]
|
|
645
|
+
dex tools configure [--cwd <path>] [--allow <path,...>] [--block <command,...>] [--timeout <ms>]
|
|
646
|
+
[--allow-dangerous|--no-allow-dangerous]
|
|
647
|
+
dex tools disable
|
|
648
|
+
dex tools run <Shell|ShellJob|ReadFile|WriteFile|ListDir|Search|Open|Clipboard|Notify> [--args <json>] [--json]
|
|
649
|
+
dex ssh list [--json]
|
|
650
|
+
dex ssh enable | dex ssh disable
|
|
651
|
+
dex ssh test <name> [--json]
|
|
652
|
+
dex tools serve [--profile <name>] \uB85C\uCEEC \uB3C4\uAD6C \uBE0C\uB9AC\uC9C0\uB9CC \uACC4\uC18D \uC2E4\uD589
|
|
653
|
+
dex tool ... dex tools ...\uC758 \uB2E8\uC218\uD615 \uBCC4\uCE6D
|
|
654
|
+
dex serve --stdio
|
|
655
|
+
|
|
656
|
+
Global options:
|
|
657
|
+
--profile <name> \uC0AC\uC6A9\uD560 \uC11C\uBC84 \uD504\uB85C\uD544
|
|
658
|
+
--json \uB2E8\uC77C JSON \uACB0\uACFC
|
|
659
|
+
--jsonl \uCC44\uD305 \uC774\uBCA4\uD2B8\uB97C NDJSON\uC73C\uB85C \uCD9C\uB825
|
|
660
|
+
-h, --help \uB3C4\uC6C0\uB9D0
|
|
661
|
+
-v, --version \uBC84\uC804
|
|
662
|
+
|
|
663
|
+
Examples:
|
|
664
|
+
dex profile set corp --server https://xgen.example.com
|
|
665
|
+
dex login --email me@corp.com
|
|
666
|
+
dex agents list
|
|
667
|
+
dex tools enable --cwd . --allow . --block sudo
|
|
668
|
+
echo '\uC774 \uC800\uC7A5\uC18C\uB97C \uC124\uBA85\uD574\uC918' | dex chat --agent wf_abc
|
|
669
|
+
`;
|
|
670
|
+
function writeJson(value) {
|
|
671
|
+
stdout2.write(`${JSON.stringify(value, null, 2)}
|
|
672
|
+
`);
|
|
673
|
+
}
|
|
674
|
+
function cell(value, width) {
|
|
675
|
+
const text = String(value ?? "");
|
|
676
|
+
return text.length > width ? `${text.slice(0, Math.max(0, width - 1))}\u2026` : text.padEnd(width);
|
|
677
|
+
}
|
|
678
|
+
function printAgents(agents) {
|
|
679
|
+
if (agents.length === 0) {
|
|
680
|
+
stdout2.write("Agent\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.\n");
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
stdout2.write(`${cell("WORKFLOW ID", 28)} ${cell("NAME", 30)} OWNER
|
|
684
|
+
`);
|
|
685
|
+
for (const agent of agents) {
|
|
686
|
+
stdout2.write(
|
|
687
|
+
`${cell(agent.workflowId, 28)} ${cell(agent.workflowName, 30)} ${agent.isShared ? "shared" : "personal"}
|
|
688
|
+
`
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
function printConversations(items) {
|
|
693
|
+
if (items.length === 0) {
|
|
694
|
+
stdout2.write("\uB300\uD654 \uAE30\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n");
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
stdout2.write(`${cell("INTERACTION ID", 38)} ${cell("AGENT", 28)} UPDATED
|
|
698
|
+
`);
|
|
699
|
+
for (const item of items) {
|
|
700
|
+
stdout2.write(`${cell(item.interactionId, 38)} ${cell(item.workflowName, 28)} ${item.updatedAt}
|
|
701
|
+
`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
function printTurns(items) {
|
|
705
|
+
for (const item of items) {
|
|
706
|
+
stdout2.write(`
|
|
707
|
+
[You]
|
|
708
|
+
${item.input}
|
|
709
|
+
|
|
710
|
+
[${item.workflowName}]
|
|
711
|
+
${item.output}
|
|
712
|
+
`);
|
|
713
|
+
}
|
|
714
|
+
if (items.length === 0) stdout2.write("\uB300\uD654 turn\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n");
|
|
715
|
+
}
|
|
716
|
+
function describeEvent(event) {
|
|
717
|
+
if (event.kind === "status") return `${event.surface}: ${event.detail ?? event.reason ?? "\uC0C1\uD0DC \uBCC0\uACBD"}`;
|
|
718
|
+
if (event.kind === "tool") return `tool: ${event.event.toolName ?? event.event.eventType}`;
|
|
719
|
+
if (event.kind === "node_status") return `node: ${event.event.nodeId} ${event.event.status}`;
|
|
720
|
+
if (event.kind === "quota") return `quota: ${event.level}`;
|
|
721
|
+
if (event.kind === "error") return `error: ${event.detail}`;
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
function printLocalToolsStatus(status) {
|
|
725
|
+
stdout2.write(`\uB85C\uCEEC \uB3C4\uAD6C: ${status.config.enabled ? "\uCF1C\uC9D0" : "\uAEBC\uC9D0"}
|
|
726
|
+
`);
|
|
727
|
+
stdout2.write(`\uC791\uC5C5 \uD3F4\uB354: ${status.config.cwd || "(\uBBF8\uC124\uC815)"}
|
|
728
|
+
`);
|
|
729
|
+
stdout2.write(`\uD5C8\uC6A9 \uACBD\uB85C: ${status.config.allowedRoots.join(", ") || "(\uC791\uC5C5 \uD3F4\uB354)"}
|
|
730
|
+
`);
|
|
731
|
+
stdout2.write(`\uC704\uD5D8 \uBA85\uB839: ${status.config.allowDangerous ? "\uD5C8\uC6A9" : "\uCC28\uB2E8"}
|
|
732
|
+
`);
|
|
733
|
+
stdout2.write(
|
|
734
|
+
`\uBE0C\uB9AC\uC9C0: ${status.bridge.catalogSynced ? `\uC5F0\uACB0\uB428 (\uB3C4\uAD6C ${status.bridge.serverToolCount}\uAC1C)` : status.bridge.connected ? "\uCE74\uD0C8\uB85C\uADF8 \uB3D9\uAE30\uD654 \uC911" : status.bridge.enabled ? "\uC5F0\uACB0 \uB300\uAE30 \uC911" : "\uC911\uC9C0\uB428"}
|
|
735
|
+
`
|
|
736
|
+
);
|
|
737
|
+
if (status.bridge.error) stdout2.write(`\uC624\uB958: ${status.bridge.error}
|
|
738
|
+
`);
|
|
739
|
+
}
|
|
740
|
+
function csvOption(args, name) {
|
|
741
|
+
const value = option(args, name);
|
|
742
|
+
if (value === void 0) return void 0;
|
|
743
|
+
return [...new Set(value.split(",").map((item) => item.trim()).filter(Boolean))];
|
|
744
|
+
}
|
|
745
|
+
function jsonObjectOption(args, name) {
|
|
746
|
+
const value = option(args, name);
|
|
747
|
+
if (!value) return {};
|
|
748
|
+
let parsed;
|
|
749
|
+
try {
|
|
750
|
+
parsed = JSON.parse(value);
|
|
751
|
+
} catch {
|
|
752
|
+
throw new DexError("usage_error", `--${name}\uC740 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
|
|
753
|
+
}
|
|
754
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
755
|
+
throw new DexError("usage_error", `--${name}\uC740 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
|
|
756
|
+
}
|
|
757
|
+
return parsed;
|
|
758
|
+
}
|
|
759
|
+
async function waitForStopSignal() {
|
|
760
|
+
await new Promise((resolve) => {
|
|
761
|
+
const stop = () => {
|
|
762
|
+
process.off("SIGINT", stop);
|
|
763
|
+
process.off("SIGTERM", stop);
|
|
764
|
+
resolve();
|
|
765
|
+
};
|
|
766
|
+
process.once("SIGINT", stop);
|
|
767
|
+
process.once("SIGTERM", stop);
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
function exitCode(error) {
|
|
771
|
+
if (!(error instanceof DexError)) return 1;
|
|
772
|
+
if (error.code === "usage_error" || error.code === "config_invalid") return 2;
|
|
773
|
+
if (error.code === "auth_required" || error.code === "auth_invalid") return 3;
|
|
774
|
+
if (error.code === "network_error") return 4;
|
|
775
|
+
return 1;
|
|
776
|
+
}
|
|
777
|
+
async function runChat(engine, args) {
|
|
778
|
+
const workflowId = requiredOption(args, "agent");
|
|
779
|
+
const input = flag(args, "stdin") || !stdin3.isTTY ? await readStdin() : await promptLine("Message: ");
|
|
780
|
+
if (!input.trim()) throw new DexError("usage_error", "\uBCF4\uB0BC \uBA54\uC2DC\uC9C0\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");
|
|
781
|
+
const resolved = await engine.resolveChatInput({
|
|
782
|
+
profile: option(args, "profile"),
|
|
783
|
+
workflowId,
|
|
784
|
+
workflowName: option(args, "name"),
|
|
785
|
+
interactionId: option(args, "interaction"),
|
|
786
|
+
input
|
|
787
|
+
});
|
|
788
|
+
const jsonl = flag(args, "jsonl");
|
|
789
|
+
if (jsonl) writeJson({ kind: "start", ...resolved, input: void 0 });
|
|
790
|
+
else stderr2.write(`interaction: ${resolved.interactionId}
|
|
791
|
+
`);
|
|
792
|
+
const controller = new AbortController();
|
|
793
|
+
const onInterrupt = () => controller.abort();
|
|
794
|
+
process.once("SIGINT", onInterrupt);
|
|
795
|
+
try {
|
|
796
|
+
for await (const event of engine.chat(resolved, controller.signal)) {
|
|
797
|
+
if (jsonl) {
|
|
798
|
+
stdout2.write(`${JSON.stringify(event)}
|
|
799
|
+
`);
|
|
800
|
+
} else if (event.kind === "text") {
|
|
801
|
+
stdout2.write(event.content);
|
|
802
|
+
} else if (event.kind === "summary") {
|
|
803
|
+
stdout2.write(event.text);
|
|
804
|
+
} else {
|
|
805
|
+
const description = describeEvent(event);
|
|
806
|
+
if (description) stderr2.write(`[${description}]
|
|
807
|
+
`);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (!jsonl) stdout2.write("\n");
|
|
811
|
+
} finally {
|
|
812
|
+
process.off("SIGINT", onInterrupt);
|
|
813
|
+
engine.stopLocalTools();
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
async function run2() {
|
|
817
|
+
const args = parseArgs(process.argv.slice(2));
|
|
818
|
+
if (flag(args, "version")) {
|
|
819
|
+
stdout2.write(`${VERSION}
|
|
820
|
+
`);
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
if (flag(args, "help")) {
|
|
824
|
+
stdout2.write(HELP);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
const configStore = new FileConfigStore();
|
|
828
|
+
bindCliHost(configStore);
|
|
829
|
+
const engine = new DexEngine(configStore, new KeytarCredentialStore());
|
|
830
|
+
const terminal = {
|
|
831
|
+
stdinIsTty: !!stdin3.isTTY,
|
|
832
|
+
stdoutIsTty: !!stdout2.isTTY,
|
|
833
|
+
term: process.env.TERM,
|
|
834
|
+
ci: process.env.CI
|
|
835
|
+
};
|
|
836
|
+
if (shouldLaunchTui(args.positionals, terminal)) {
|
|
837
|
+
const { runTui } = await import("./chunks/tui-DMHUMWPG.js");
|
|
838
|
+
try {
|
|
839
|
+
await runTui(engine);
|
|
840
|
+
} finally {
|
|
841
|
+
engine.stopLocalTools();
|
|
842
|
+
}
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
if (args.positionals.length === 0) {
|
|
846
|
+
stdout2.write(HELP);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
if (args.positionals[0] === "ui" && !isInteractiveTerminal(terminal)) {
|
|
850
|
+
throw new DexError("usage_error", "\uD130\uBBF8\uB110 UI\uB294 \uB300\uD654\uD615 TTY\uC5D0\uC11C\uB9CC \uC2E4\uD589\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
|
|
851
|
+
}
|
|
852
|
+
const [rawCommand, action] = args.positionals;
|
|
853
|
+
const command = rawCommand === "tool" ? "tools" : rawCommand;
|
|
854
|
+
const asJson = flag(args, "json");
|
|
855
|
+
if (command === "profile" && action === "set") {
|
|
856
|
+
const profile = await engine.setProfile(args.positionals[2] ?? "default", requiredOption(args, "server"));
|
|
857
|
+
if (asJson) writeJson(profile);
|
|
858
|
+
else stdout2.write(`\uD504\uB85C\uD544 \uC800\uC7A5: ${profile.name} \u2192 ${profile.serverUrl}
|
|
859
|
+
`);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
if (command === "profile" && action === "use") {
|
|
863
|
+
const name = args.positionals[2];
|
|
864
|
+
if (!name) throw new DexError("usage_error", "\uC0AC\uC6A9\uD560 \uD504\uB85C\uD544 \uC774\uB984\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
865
|
+
const profile = await engine.useProfile(name);
|
|
866
|
+
if (asJson) writeJson(profile);
|
|
867
|
+
else stdout2.write(`\uD604\uC7AC \uD504\uB85C\uD544: ${profile.name}
|
|
868
|
+
`);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
if (command === "profile" && action === "list") {
|
|
872
|
+
const profiles = await engine.listProfiles();
|
|
873
|
+
if (asJson) writeJson(profiles);
|
|
874
|
+
else if (profiles.length === 0) stdout2.write("\uD504\uB85C\uD544\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n");
|
|
875
|
+
else {
|
|
876
|
+
for (const profile of profiles) {
|
|
877
|
+
stdout2.write(`${profile.current ? "*" : " "} ${profile.name.padEnd(16)} ${profile.serverUrl}
|
|
878
|
+
`);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
if (command === "login") {
|
|
884
|
+
const password = flag(args, "password-stdin") || !stdin3.isTTY ? await readStdin() : await promptSecret("Password: ");
|
|
885
|
+
const status = await engine.login(requiredOption(args, "email"), password, option(args, "profile"));
|
|
886
|
+
if (asJson) writeJson(status);
|
|
887
|
+
else stdout2.write(`\uB85C\uADF8\uC778\uB428: ${status.user?.username ?? "unknown"} (${status.profile})
|
|
888
|
+
`);
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
if (command === "status") {
|
|
892
|
+
const status = await engine.authStatus(option(args, "profile"));
|
|
893
|
+
if (asJson) writeJson(status);
|
|
894
|
+
else if (status.authenticated) {
|
|
895
|
+
stdout2.write(`\uB85C\uADF8\uC778\uB428: ${status.user?.username ?? "unknown"} @ ${status.serverUrl}
|
|
896
|
+
`);
|
|
897
|
+
} else {
|
|
898
|
+
stdout2.write(`\uB85C\uADF8\uC544\uC6C3\uB428: ${status.profile} (${status.reason ?? "unknown"})
|
|
899
|
+
`);
|
|
900
|
+
}
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
if (command === "logout") {
|
|
904
|
+
await engine.logout(option(args, "profile"));
|
|
905
|
+
if (asJson) writeJson({ ok: true });
|
|
906
|
+
else stdout2.write("\uB85C\uADF8\uC544\uC6C3\uD588\uC2B5\uB2C8\uB2E4.\n");
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
if (command === "tools" && action === "list") {
|
|
910
|
+
const status = await engine.localToolsStatus();
|
|
911
|
+
const exposed = new Set(status.tools.map((t) => t.name));
|
|
912
|
+
if (asJson) writeJson({ enabled: status.config.enabled, catalog: status.catalog, exposed: [...exposed] });
|
|
913
|
+
else {
|
|
914
|
+
if (!status.config.enabled) {
|
|
915
|
+
stdout2.write("\uB85C\uCEEC \uB3C4\uAD6C\uAC00 \uAEBC\uC838 \uC788\uC2B5\uB2C8\uB2E4 \u2014 \uC544\uB798\uB294 \uCF30\uC744 \uB54C \uC4F8 \uC218 \uC788\uB294 \uBAA9\uB85D\uC785\uB2C8\uB2E4.\n");
|
|
916
|
+
stdout2.write("\uCF1C\uAE30: dex tools enable\n\n");
|
|
917
|
+
}
|
|
918
|
+
stdout2.write(`${cell("TOOL", 14)} ${cell("\uB178\uCD9C", 5)} DESCRIPTION
|
|
919
|
+
`);
|
|
920
|
+
for (const tool of status.catalog) {
|
|
921
|
+
const summary = String(tool.description ?? "").split("\n")[0] ?? "";
|
|
922
|
+
stdout2.write(
|
|
923
|
+
`${cell(tool.name, 14)} ${cell(exposed.has(tool.name) ? "\u25CF" : "\xB7", 5)} ${summary.slice(0, 96)}
|
|
924
|
+
`
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
if (command === "tools" && (action === "enable" || action === "configure")) {
|
|
931
|
+
const current = (await engine.localToolsStatus()).config;
|
|
932
|
+
const cwd = option(args, "cwd") || current.cwd || process.cwd();
|
|
933
|
+
const timeoutRaw = option(args, "timeout");
|
|
934
|
+
const timeoutMs = timeoutRaw === void 0 ? current.timeoutMs : Number(timeoutRaw);
|
|
935
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 1e3 || timeoutMs > 36e5) {
|
|
936
|
+
throw new DexError("usage_error", "--timeout\uC740 1000~3600000 \uC0AC\uC774\uC758 \uBC00\uB9AC\uCD08\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
|
|
937
|
+
}
|
|
938
|
+
const allowedRoots = csvOption(args, "allow") ?? (current.allowedRoots.length ? current.allowedRoots : [cwd]);
|
|
939
|
+
const blockedCommands = csvOption(args, "block") ?? current.blockedCommands;
|
|
940
|
+
const allowDangerous = flag(args, "allow-dangerous") ? true : flag(args, "no-allow-dangerous") ? false : current.allowDangerous;
|
|
941
|
+
const status = await engine.configureLocalTools({
|
|
942
|
+
enabled: action === "enable" ? true : current.enabled,
|
|
943
|
+
cwd,
|
|
944
|
+
timeoutMs,
|
|
945
|
+
allowedRoots,
|
|
946
|
+
blockedCommands,
|
|
947
|
+
allowDangerous
|
|
948
|
+
});
|
|
949
|
+
if (asJson) writeJson(status);
|
|
950
|
+
else printLocalToolsStatus(status);
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
if (command === "tools" && action === "disable") {
|
|
954
|
+
const status = await engine.configureLocalTools({ enabled: false });
|
|
955
|
+
if (asJson) writeJson(status);
|
|
956
|
+
else printLocalToolsStatus(status);
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
if (command === "tools" && action === "run") {
|
|
960
|
+
const tool = args.positionals[2];
|
|
961
|
+
if (!tool) throw new DexError("usage_error", "\uC2E4\uD589\uD560 \uB85C\uCEEC \uB3C4\uAD6C \uC774\uB984\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
962
|
+
const result = await engine.runLocalTool(tool, jsonObjectOption(args, "args"));
|
|
963
|
+
if (asJson) writeJson(result);
|
|
964
|
+
else for (const content of result.content) stdout2.write(`${content.text}
|
|
965
|
+
`);
|
|
966
|
+
if (result.isError) process.exitCode = 1;
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
if (command === "tools" && (action === "status" || action === "serve")) {
|
|
970
|
+
let status = await engine.localToolsStatus();
|
|
971
|
+
if (status.config.enabled) {
|
|
972
|
+
try {
|
|
973
|
+
status = await engine.startLocalTools(option(args, "profile"), action === "serve" ? 5e3 : 2e3);
|
|
974
|
+
} catch (error) {
|
|
975
|
+
if (action === "serve") throw error;
|
|
976
|
+
status = {
|
|
977
|
+
...status,
|
|
978
|
+
bridge: { ...status.bridge, error: error instanceof Error ? error.message : String(error) }
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (asJson) writeJson(status);
|
|
983
|
+
else printLocalToolsStatus(status);
|
|
984
|
+
if (action === "serve") {
|
|
985
|
+
if (!status.config.enabled) throw new DexError("local_tools_disabled", "\uBA3C\uC800 dex tools enable\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
|
|
986
|
+
if (!asJson) stderr2.write("\uB85C\uCEEC \uB3C4\uAD6C \uBE0C\uB9AC\uC9C0\uAC00 \uC2E4\uD589 \uC911\uC785\uB2C8\uB2E4. \uC885\uB8CC\uD558\uB824\uBA74 Ctrl+C\uB97C \uB204\uB974\uC138\uC694.\n");
|
|
987
|
+
try {
|
|
988
|
+
await waitForStopSignal();
|
|
989
|
+
} finally {
|
|
990
|
+
engine.stopLocalTools();
|
|
991
|
+
}
|
|
992
|
+
} else {
|
|
993
|
+
engine.stopLocalTools();
|
|
994
|
+
}
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
if (command === "agents" && action === "list") {
|
|
998
|
+
const owner = option(args, "owner");
|
|
999
|
+
if (owner && owner !== "personal" && owner !== "shared") {
|
|
1000
|
+
throw new DexError("usage_error", "--owner\uB294 personal \uB610\uB294 shared\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
|
|
1001
|
+
}
|
|
1002
|
+
const query = {
|
|
1003
|
+
page: positiveIntegerOption(args, "page"),
|
|
1004
|
+
pageSize: positiveIntegerOption(args, "page-size"),
|
|
1005
|
+
search: option(args, "search"),
|
|
1006
|
+
owner,
|
|
1007
|
+
status: option(args, "status"),
|
|
1008
|
+
includeHarness: flag(args, "include-harness")
|
|
1009
|
+
};
|
|
1010
|
+
const result = await engine.listAgents(query, option(args, "profile"));
|
|
1011
|
+
if (asJson) writeJson(result);
|
|
1012
|
+
else printAgents(result.items);
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
if (command === "chat") {
|
|
1016
|
+
await runChat(engine, args);
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
if (command === "history" && action === "list") {
|
|
1020
|
+
const conversations = await engine.listConversations(option(args, "profile"));
|
|
1021
|
+
if (asJson) writeJson(conversations);
|
|
1022
|
+
else printConversations(conversations);
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
if (command === "history" && action === "turns") {
|
|
1026
|
+
const turns = await engine.historyTurns(
|
|
1027
|
+
requiredOption(args, "workflow"),
|
|
1028
|
+
requiredOption(args, "interaction"),
|
|
1029
|
+
option(args, "name"),
|
|
1030
|
+
option(args, "profile")
|
|
1031
|
+
);
|
|
1032
|
+
if (asJson) writeJson(turns);
|
|
1033
|
+
else printTurns(turns);
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
if (command === "serve") {
|
|
1037
|
+
if (!flag(args, "stdio")) throw new DexError("usage_error", "\uD604\uC7AC\uB294 serve --stdio\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4.");
|
|
1038
|
+
const server = new DexRpcServer(engine, { version: VERSION });
|
|
1039
|
+
server.start();
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
if (command === "ssh" && (action === "list" || action === void 0)) {
|
|
1043
|
+
const config = await engine.sshConfig(option(args, "profile"));
|
|
1044
|
+
if (asJson) writeJson(config);
|
|
1045
|
+
else {
|
|
1046
|
+
stdout2.write(`SSH \uC5F0\uB3D9: ${config.enabled ? "\uCF1C\uC9D0" : "\uAEBC\uC9D0"}
|
|
1047
|
+
`);
|
|
1048
|
+
if (config.servers.length === 0) stdout2.write("\uB4F1\uB85D\uB41C \uC11C\uBC84\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.\n");
|
|
1049
|
+
for (const srv of config.servers) {
|
|
1050
|
+
const via = srv.jump_via.length ? ` (\uACBD\uC720 ${srv.jump_via.join(" \u2192 ")})` : "";
|
|
1051
|
+
const off = srv.enabled ? "" : " [\uC0AC\uC6A9 \uC548 \uD568]";
|
|
1052
|
+
stdout2.write(`${cell(srv.name, 18)} ${srv.username}@${srv.host}:${srv.port} ${srv.auth}${via}${off}
|
|
1053
|
+
`);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
if (command === "ssh" && (action === "enable" || action === "disable")) {
|
|
1059
|
+
const config = await engine.setSshEnabled(action === "enable", option(args, "profile"));
|
|
1060
|
+
if (asJson) writeJson(config);
|
|
1061
|
+
else stdout2.write(`SSH \uC5F0\uB3D9\uC744 ${config.enabled ? "\uCF30\uC2B5\uB2C8\uB2E4" : "\uAED0\uC2B5\uB2C8\uB2E4"}.
|
|
1062
|
+
`);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
if (command === "ssh" && action === "test") {
|
|
1066
|
+
const name = args.positionals[2];
|
|
1067
|
+
if (!name) throw new DexError("usage_error", "\uC11C\uBC84 \uC774\uB984\uC774 \uD544\uC694\uD569\uB2C8\uB2E4: dex ssh test <name>");
|
|
1068
|
+
const result = await engine.testSshServer(name, option(args, "profile"));
|
|
1069
|
+
if (asJson) writeJson(result);
|
|
1070
|
+
else {
|
|
1071
|
+
stdout2.write(
|
|
1072
|
+
result.success ? `\uC811\uC18D \uC131\uACF5 (${Math.round(result.latency_ms ?? 0)}ms)
|
|
1073
|
+
` : `\uC811\uC18D \uC2E4\uD328 \u2014 ${result.error ?? ""}
|
|
1074
|
+
`
|
|
1075
|
+
);
|
|
1076
|
+
if (result.hops && result.hops.length > 1) {
|
|
1077
|
+
stdout2.write(`\uACBD\uB85C: ${result.hops.join(" \u2192 ")}
|
|
1078
|
+
`);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
throw new DexError("usage_error", `\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839\uC785\uB2C8\uB2E4: ${args.positionals.join(" ")}`);
|
|
1084
|
+
}
|
|
1085
|
+
run2().catch((error) => {
|
|
1086
|
+
const exposed = publicError(error);
|
|
1087
|
+
const machine = process.argv.includes("--json") || process.argv.includes("--jsonl");
|
|
1088
|
+
if (machine) stderr2.write(`${JSON.stringify({ error: exposed })}
|
|
1089
|
+
`);
|
|
1090
|
+
else stderr2.write(`dex: ${exposed.message}
|
|
1091
|
+
`);
|
|
1092
|
+
process.exitCode = exitCode(error);
|
|
1093
|
+
});
|
|
1094
|
+
//# sourceMappingURL=cli.js.map
|