desic-okx-agent 0.1.8 → 0.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 +48 -5
- package/dist/account/private-websocket.d.ts +1 -0
- package/dist/account/private-websocket.js +27 -2
- package/dist/account/private-websocket.js.map +1 -1
- package/dist/cli/doctor.d.ts +14 -0
- package/dist/cli/doctor.js +133 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/index.js +190 -11
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/prompts.d.ts +1 -0
- package/dist/cli/prompts.js +6 -0
- package/dist/cli/prompts.js.map +1 -1
- package/dist/cli/render.d.ts +8 -0
- package/dist/cli/render.js +73 -0
- package/dist/cli/render.js.map +1 -0
- package/dist/market/store.d.ts +1 -0
- package/dist/market/store.js +24 -0
- package/dist/market/store.js.map +1 -1
- package/dist/market/websocket.d.ts +1 -0
- package/dist/market/websocket.js +13 -0
- package/dist/market/websocket.js.map +1 -1
- package/dist/network/connectivity.d.ts +3 -0
- package/dist/network/connectivity.js +34 -14
- package/dist/network/connectivity.js.map +1 -1
- package/dist/network/proxy.js +3 -0
- package/dist/network/proxy.js.map +1 -1
- package/dist/runtime/client.d.ts +1 -0
- package/dist/runtime/client.js +46 -2
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/server.js +21 -2
- package/dist/runtime/server.js.map +1 -1
- package/dist/setup/installer.d.ts +21 -0
- package/dist/setup/installer.js +113 -27
- package/dist/setup/installer.js.map +1 -1
- package/dist/setup/wizard.js +10 -4
- package/dist/setup/wizard.js.map +1 -1
- package/dist/storage/database.d.ts +2 -0
- package/dist/storage/database.js +23 -0
- package/dist/storage/database.js.map +1 -1
- package/dist/tools/catalog.js +1 -1
- package/dist/tools/catalog.js.map +1 -1
- package/dist/tools/help.d.ts +10 -0
- package/dist/tools/help.js +79 -0
- package/dist/tools/help.js.map +1 -0
- package/dist/update/service.d.ts +10 -0
- package/dist/update/service.js +104 -0
- package/dist/update/service.js.map +1 -0
- package/package.json +1 -1
- package/skills/okx-news-intelligence/SKILL.md +2 -0
- package/skills/okx-smart-money-analysis/SKILL.md +2 -0
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ The terminal guide lets you select Codex, Claude Code, Cursor, VS Code / GitHub
|
|
|
47
47
|
|
|
48
48
|
For Codex and Claude Code, setup also installs all bundled Skills automatically. Cursor, VS Code / GitHub Copilot, and Cline receive MCP configuration only because they do not share a portable `SKILL.md` installation format.
|
|
49
49
|
|
|
50
|
-
Setup then checks both OKX REST and WebSocket connectivity. When direct and detected system-proxy connections fail, it offers to test and save an HTTP proxy URL, retry, or continue without network access.
|
|
50
|
+
Setup then checks both OKX REST and WebSocket connectivity. Transient TLS and network failures are retried before setup asks for action. When direct and detected system-proxy connections still fail, it offers to test and save an HTTP proxy URL, retry, or continue without network access.
|
|
51
51
|
|
|
52
52
|
For automation, choose explicit targets without opening the UI:
|
|
53
53
|
|
|
@@ -60,7 +60,7 @@ desic-okx setup --targets codex --yes --skip-network-check
|
|
|
60
60
|
|
|
61
61
|
Non-interactive setup exits with an error when the network check fails. Use `--skip-network-check` only when connectivity will be configured later.
|
|
62
62
|
|
|
63
|
-
Restart the selected client after setup. The MCP server starts the local Runtime automatically when it is first used.
|
|
63
|
+
Restart the selected client after setup. The MCP server starts the local Runtime automatically when it is first used. Run `desic-okx doctor` at any time to verify the package, client configuration, Skills, network, Runtime, SQLite, public market data, and configured accounts.
|
|
64
64
|
|
|
65
65
|
## Ask an AI to install it
|
|
66
66
|
|
|
@@ -71,7 +71,7 @@ Install Desic OKX Agent for me. Check that Node.js is at least 22.12, then run:
|
|
|
71
71
|
npm install --global desic-okx-agent
|
|
72
72
|
desic-okx setup --targets codex --yes
|
|
73
73
|
|
|
74
|
-
Verify the installation with `desic-okx
|
|
74
|
+
Verify the installation with `desic-okx doctor`. Do not ask for or configure any OKX API credentials. Confirm that public market tools are ready to use.
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
For development from source:
|
|
@@ -95,15 +95,21 @@ desic-okx call market_get_decision_snapshot --json '{"instId":"BTC-USDT-SWAP","b
|
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
The Runtime starts automatically when an MCP client or CLI call needs it. `desic-okx start` is optional.
|
|
98
|
+
The stdio MCP adapter does not load the native SQLite module itself, which avoids holding the package binary open during Windows upgrades. Stop a running Runtime with `desic-okx stop` before upgrading.
|
|
98
99
|
|
|
99
100
|
Useful Runtime commands:
|
|
100
101
|
|
|
101
102
|
```bash
|
|
102
103
|
desic-okx status
|
|
104
|
+
desic-okx status --json
|
|
105
|
+
desic-okx doctor
|
|
103
106
|
desic-okx tools
|
|
107
|
+
desic-okx tool market_get_decision_snapshot
|
|
104
108
|
desic-okx stop
|
|
105
109
|
```
|
|
106
110
|
|
|
111
|
+
`status` shows the Runtime version, uptime, redacted proxy route, public/business/private WebSocket state, subscriptions and data ages, accounts, and database size. `doctor` performs active network, SQLite, market-data, account, MCP-client, Skill, and package checks. Both support machine-readable JSON.
|
|
112
|
+
|
|
107
113
|
## Connect Codex
|
|
108
114
|
|
|
109
115
|
The recommended option is `desic-okx setup`, which also installs all Skills. To register only the local stdio MCP server manually:
|
|
@@ -168,6 +174,14 @@ Included skills:
|
|
|
168
174
|
|
|
169
175
|
Restart the client if a newly installed skill does not appear.
|
|
170
176
|
|
|
177
|
+
Setup synchronizes bundled Skills on every run. Identical Skills are left untouched. Changed copies are backed up before the bundled version is installed. They can also be inspected or synchronized explicitly:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
desic-okx skills status
|
|
181
|
+
desic-okx skills sync
|
|
182
|
+
desic-okx skills sync --targets codex --dry-run
|
|
183
|
+
```
|
|
184
|
+
|
|
171
185
|
## Configure an OKX account
|
|
172
186
|
|
|
173
187
|
Public tools work without credentials. Account and trading tools use a named account alias and never accept credentials as tool arguments.
|
|
@@ -177,9 +191,15 @@ Add and verify an account interactively:
|
|
|
177
191
|
```bash
|
|
178
192
|
desic-okx account add --name demo --environment demo
|
|
179
193
|
desic-okx account verify --name demo
|
|
194
|
+
desic-okx account verify --all
|
|
180
195
|
desic-okx account list
|
|
196
|
+
desic-okx account set-default demo
|
|
197
|
+
desic-okx account rename demo demo-primary
|
|
198
|
+
desic-okx account edit demo-primary
|
|
181
199
|
```
|
|
182
200
|
|
|
201
|
+
`account add` and `account edit` verify the credentials with OKX before writing the configuration file. Editing credentials remains interactive so secrets are not passed as command-line arguments.
|
|
202
|
+
|
|
183
203
|
The credential prompts hide input. Credentials are stored in the system configuration directory in `config.json`; Unix permissions are set to `0600`. The active path is shown by:
|
|
184
204
|
|
|
185
205
|
```bash
|
|
@@ -198,6 +218,29 @@ OKX_ENVIRONMENT=demo|live
|
|
|
198
218
|
|
|
199
219
|
The three credential values must be provided together. Whether an account can trade is determined by the permissions assigned to its API key in OKX.
|
|
200
220
|
|
|
221
|
+
## Tool help
|
|
222
|
+
|
|
223
|
+
The MCP schemas are available to AI clients. Humans can inspect the same schema, account requirement, and a runnable example from the CLI:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
desic-okx tool news_search
|
|
227
|
+
desic-okx tool trade_place_order --json
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Remote News and Smart Money tools require a configured **live OKX account**. Read-only API permission is sufficient. Local News event and history tools can use previously persisted data where available. This requirement is included in the MCP tool descriptions and relevant Skills.
|
|
231
|
+
|
|
232
|
+
## Update
|
|
233
|
+
|
|
234
|
+
Check or install the latest npm release:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
desic-okx update --check
|
|
238
|
+
desic-okx update
|
|
239
|
+
desic-okx update --yes
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The updater checks the official npm registry, stops the Runtime, waits for the native SQLite module to be released, and then performs the global npm update. It synchronizes Skills for detected installations and runs Doctor afterward. When a newer CLI or MCP adapter encounters an older running Runtime, it automatically replaces it before handling requests.
|
|
243
|
+
|
|
201
244
|
## Proxy
|
|
202
245
|
|
|
203
246
|
REST and every public, business, and private WebSocket connection use the same proxy resolution order:
|
|
@@ -255,7 +298,7 @@ Use an OKX Demo account first. Trading software can lose money, and the project
|
|
|
255
298
|
|
|
256
299
|
## Experimental intelligence
|
|
257
300
|
|
|
258
|
-
News and Smart Money use upstream interfaces that may change without notice. Compatibility failures return `CAPABILITY_UNAVAILABLE`, or locally persisted history when available. A failure in these modules does not disable market, account, or trading tools.
|
|
301
|
+
Remote News and Smart Money use upstream interfaces that may change without notice and require a configured live OKX account; read-only API permission is sufficient. Compatibility failures return `CAPABILITY_UNAVAILABLE`, or locally persisted history when available. A failure in these modules does not disable market, account, or trading tools.
|
|
259
302
|
|
|
260
303
|
## Development
|
|
261
304
|
|
|
@@ -283,7 +326,7 @@ Normal tests never submit orders.
|
|
|
283
326
|
|
|
284
327
|
## Status
|
|
285
328
|
|
|
286
|
-
This repository is currently pre-release software at version `0.
|
|
329
|
+
This repository is currently pre-release software at version `0.2.0`. Unit, packaging, Skill, and MCP transport checks are automated. Real OKX Demo trading should be validated in the target network environment before a `1.0.0` release.
|
|
287
330
|
|
|
288
331
|
## License
|
|
289
332
|
|
|
@@ -8,7 +8,9 @@ export class PrivateAccountWebSockets {
|
|
|
8
8
|
agent;
|
|
9
9
|
constructor(config, store) {
|
|
10
10
|
this.store = store;
|
|
11
|
-
this.connections = Object.entries(config.accounts).map(([name, account]) => ({
|
|
11
|
+
this.connections = Object.entries(config.accounts).map(([name, account]) => ({
|
|
12
|
+
account: { name, ...account }, stopping: false, attempt: 0, authenticated: false
|
|
13
|
+
}));
|
|
12
14
|
const proxyUrl = resolveProxy(config.proxy.url).url;
|
|
13
15
|
this.agent = proxyUrl ? new HttpsProxyAgent(proxyUrl) : undefined;
|
|
14
16
|
}
|
|
@@ -26,6 +28,18 @@ export class PrivateAccountWebSockets {
|
|
|
26
28
|
connection.socket?.close();
|
|
27
29
|
}
|
|
28
30
|
}
|
|
31
|
+
status() {
|
|
32
|
+
const states = ["connecting", "open", "closing", "closed"];
|
|
33
|
+
return this.connections.map((connection) => ({
|
|
34
|
+
account: connection.account.name,
|
|
35
|
+
environment: connection.account.environment,
|
|
36
|
+
state: connection.socket ? states[connection.socket.readyState] ?? "unknown" : "not-started",
|
|
37
|
+
authenticated: connection.authenticated,
|
|
38
|
+
reconnectAttempt: connection.attempt,
|
|
39
|
+
lastMessageAt: connection.lastMessageAt ?? null,
|
|
40
|
+
lastError: connection.lastError ?? null
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
29
43
|
connect(connection) {
|
|
30
44
|
if (connection.stopping)
|
|
31
45
|
return;
|
|
@@ -34,10 +48,14 @@ export class PrivateAccountWebSockets {
|
|
|
34
48
|
: "wss://ws.okx.com:8443/ws/v5/private";
|
|
35
49
|
const socket = new WebSocket(url, this.agent ? { agent: this.agent } : undefined);
|
|
36
50
|
connection.socket = socket;
|
|
51
|
+
connection.authenticated = false;
|
|
37
52
|
socket.on("open", () => socket.send(JSON.stringify({ op: "login", args: [loginArgs(connection.account)] })));
|
|
38
53
|
socket.on("message", (buffer) => this.message(connection, buffer.toString()));
|
|
39
54
|
socket.on("close", () => this.reconnect(connection));
|
|
40
|
-
socket.on("error", () =>
|
|
55
|
+
socket.on("error", (error) => {
|
|
56
|
+
connection.lastError = error.message;
|
|
57
|
+
socket.close();
|
|
58
|
+
});
|
|
41
59
|
}
|
|
42
60
|
reconnect(connection) {
|
|
43
61
|
if (connection.stopping || connection.reconnect)
|
|
@@ -55,6 +73,7 @@ export class PrivateAccountWebSockets {
|
|
|
55
73
|
message(connection, text) {
|
|
56
74
|
if (text === "pong")
|
|
57
75
|
return;
|
|
76
|
+
connection.lastMessageAt = Date.now();
|
|
58
77
|
let message;
|
|
59
78
|
try {
|
|
60
79
|
message = JSON.parse(text);
|
|
@@ -64,6 +83,8 @@ export class PrivateAccountWebSockets {
|
|
|
64
83
|
}
|
|
65
84
|
if (message.event === "login" && message.code === "0") {
|
|
66
85
|
connection.attempt = 0;
|
|
86
|
+
connection.authenticated = true;
|
|
87
|
+
connection.lastError = undefined;
|
|
67
88
|
connection.socket?.send(JSON.stringify({
|
|
68
89
|
op: "subscribe",
|
|
69
90
|
args: [
|
|
@@ -80,6 +101,10 @@ export class PrivateAccountWebSockets {
|
|
|
80
101
|
connection.heartbeat.unref();
|
|
81
102
|
return;
|
|
82
103
|
}
|
|
104
|
+
if (message.event === "login" && message.code !== "0") {
|
|
105
|
+
connection.authenticated = false;
|
|
106
|
+
connection.lastError = String(message.msg ?? `Login failed with code ${String(message.code ?? "unknown")}`);
|
|
107
|
+
}
|
|
83
108
|
const arg = message.arg;
|
|
84
109
|
const data = Array.isArray(message.data) ? message.data : [];
|
|
85
110
|
if (!arg?.channel || !data.length)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"private-websocket.js","sourceRoot":"","sources":["../../src/account/private-websocket.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAIpD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"private-websocket.js","sourceRoot":"","sources":["../../src/account/private-websocket.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type DoctorStatus = "pass" | "warn" | "fail";
|
|
2
|
+
export interface DoctorCheck {
|
|
3
|
+
name: string;
|
|
4
|
+
status: DoctorStatus;
|
|
5
|
+
details: string;
|
|
6
|
+
}
|
|
7
|
+
export interface DoctorReport {
|
|
8
|
+
ok: boolean;
|
|
9
|
+
version: string;
|
|
10
|
+
checkedAt: number;
|
|
11
|
+
checks: DoctorCheck[];
|
|
12
|
+
}
|
|
13
|
+
export declare function runDoctor(): Promise<DoctorReport>;
|
|
14
|
+
export declare function formatDoctor(reportValue: DoctorReport): string;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { AccountService } from "../account/service.js";
|
|
3
|
+
import { loadConfig } from "../config/loader.js";
|
|
4
|
+
import { CONFIG_PATH } from "../config/paths.js";
|
|
5
|
+
import { OkxClient } from "../core/okx-client.js";
|
|
6
|
+
import { PACKAGE_VERSION } from "../core/version.js";
|
|
7
|
+
import { checkOkxConnectivity, OKX_REST_BASE_URL } from "../network/connectivity.js";
|
|
8
|
+
import { displayProxy, resolveProxy } from "../network/proxy.js";
|
|
9
|
+
import { RuntimeClient } from "../runtime/client.js";
|
|
10
|
+
import { inspectSetupTargets, syncSkills } from "../setup/installer.js";
|
|
11
|
+
import { checkForUpdate } from "../update/service.js";
|
|
12
|
+
export async function runDoctor() {
|
|
13
|
+
const checks = [];
|
|
14
|
+
let config;
|
|
15
|
+
try {
|
|
16
|
+
config = loadConfig();
|
|
17
|
+
const permission = configPermission();
|
|
18
|
+
checks.push({ name: "Configuration", status: permission.secure ? "pass" : "warn", details: permission.details });
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
checks.push({ name: "Configuration", status: "fail", details: errorMessage(error) });
|
|
22
|
+
return report(checks);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const update = await checkForUpdate(undefined, config.proxy.url);
|
|
26
|
+
checks.push({
|
|
27
|
+
name: "Package",
|
|
28
|
+
status: update.updateAvailable ? "warn" : "pass",
|
|
29
|
+
details: update.updateAvailable ? `${update.current} installed; ${update.latest} available` : `${update.current} is current`
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
checks.push({ name: "Package", status: "warn", details: `Could not check npm: ${errorMessage(error)}` });
|
|
34
|
+
}
|
|
35
|
+
const clients = inspectSetupTargets();
|
|
36
|
+
const configured = clients.filter((item) => item.mcpConfigured === true);
|
|
37
|
+
checks.push({
|
|
38
|
+
name: "MCP clients",
|
|
39
|
+
status: configured.length ? "pass" : "warn",
|
|
40
|
+
details: configured.length ? configured.map((item) => item.target).join(", ") : "No supported client configuration was detected"
|
|
41
|
+
});
|
|
42
|
+
const installedSkillTargets = clients.filter((item) => item.skillsInstalled !== null && item.skillsInstalled > 0)
|
|
43
|
+
.map((item) => item.target).filter((target) => target === "codex" || target === "claude-code");
|
|
44
|
+
if (installedSkillTargets.length) {
|
|
45
|
+
const skills = syncSkills(installedSkillTargets, { dryRun: true });
|
|
46
|
+
const pending = skills.reduce((total, item) => total + item.installed + item.updated, 0);
|
|
47
|
+
checks.push({
|
|
48
|
+
name: "Skills",
|
|
49
|
+
status: pending ? "warn" : "pass",
|
|
50
|
+
details: pending ? `${pending} bundled Skills need synchronization` : `${skills.reduce((sum, item) => sum + item.unchanged, 0)} bundled Skills are current`
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
checks.push({ name: "Skills", status: "warn", details: "No Codex or Claude Code Skills were detected" });
|
|
55
|
+
}
|
|
56
|
+
const proxy = resolveProxy(config.proxy.url);
|
|
57
|
+
const network = await checkOkxConnectivity(proxy.url);
|
|
58
|
+
checks.push({
|
|
59
|
+
name: "OKX network",
|
|
60
|
+
status: network.ok ? "pass" : "fail",
|
|
61
|
+
details: network.ok
|
|
62
|
+
? `${displayProxy(proxy)}; REST ${network.rest.latencyMs}ms, WebSocket ${network.websocket.latencyMs}ms`
|
|
63
|
+
: `${displayProxy(proxy)}; REST ${network.rest.error ?? "failed"}; WebSocket ${network.websocket.error ?? "failed"}`
|
|
64
|
+
});
|
|
65
|
+
if (network.ok) {
|
|
66
|
+
try {
|
|
67
|
+
const runtime = await RuntimeClient.connect();
|
|
68
|
+
const health = await runtime.health();
|
|
69
|
+
const diagnostics = await runtime.diagnostics();
|
|
70
|
+
checks.push({ name: "Runtime", status: health.version === PACKAGE_VERSION ? "pass" : "warn", details: `version ${String(health.version)}, PID ${String(health.pid)}, uptime ${formatDuration(Number(health.uptimeMs))}` });
|
|
71
|
+
const integrity = String(diagnostics.database?.integrity ?? "unknown");
|
|
72
|
+
checks.push({ name: "SQLite", status: integrity === "ok" ? "pass" : "fail", details: `integrity ${integrity}` });
|
|
73
|
+
const ticker = await runtime.call("market_get_ticker", { instId: "BTC-USDT-SWAP" });
|
|
74
|
+
checks.push({ name: "Market data", status: "pass", details: `BTC-USDT-SWAP last ${String(ticker.data?.last ?? "received")}, age ${String(ticker.meta?.ageMs ?? "unknown")}ms` });
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
checks.push({ name: "Runtime", status: "fail", details: errorMessage(error) });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const accounts = Object.keys(config.accounts);
|
|
81
|
+
if (!accounts.length) {
|
|
82
|
+
checks.push({ name: "Accounts", status: "warn", details: "No account configured; public market tools remain available" });
|
|
83
|
+
}
|
|
84
|
+
else if (network.ok) {
|
|
85
|
+
const client = new OkxClient(OKX_REST_BASE_URL, proxy.url);
|
|
86
|
+
const service = new AccountService(config, client);
|
|
87
|
+
for (const name of accounts) {
|
|
88
|
+
try {
|
|
89
|
+
const verified = await service.verify(name);
|
|
90
|
+
checks.push({ name: `Account ${name}`, status: "pass", details: `${config.accounts[name]?.environment}; permissions ${String(verified.data.perm ?? "reported by OKX")}` });
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
checks.push({ name: `Account ${name}`, status: "fail", details: errorMessage(error) });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return report(checks);
|
|
98
|
+
}
|
|
99
|
+
export function formatDoctor(reportValue) {
|
|
100
|
+
const width = Math.max(...reportValue.checks.map((check) => check.name.length), 6);
|
|
101
|
+
const lines = ["Desic OKX Doctor", ""];
|
|
102
|
+
for (const check of reportValue.checks) {
|
|
103
|
+
lines.push(`${check.status.toUpperCase().padEnd(4)} ${check.name.padEnd(width)} ${check.details}`);
|
|
104
|
+
}
|
|
105
|
+
lines.push("", reportValue.ok ? "Ready" : "Action required");
|
|
106
|
+
return lines.join("\n");
|
|
107
|
+
}
|
|
108
|
+
function configPermission() {
|
|
109
|
+
if (!fs.existsSync(CONFIG_PATH))
|
|
110
|
+
return { secure: true, details: "defaults in use; no config file created" };
|
|
111
|
+
if (process.platform === "win32")
|
|
112
|
+
return { secure: true, details: CONFIG_PATH };
|
|
113
|
+
const mode = fs.statSync(CONFIG_PATH).mode & 0o777;
|
|
114
|
+
return mode & 0o077
|
|
115
|
+
? { secure: false, details: `${CONFIG_PATH}; permissions should be 0600` }
|
|
116
|
+
: { secure: true, details: `${CONFIG_PATH}; permissions 0600` };
|
|
117
|
+
}
|
|
118
|
+
function report(checks) {
|
|
119
|
+
return { ok: checks.every((check) => check.status !== "fail"), version: PACKAGE_VERSION, checkedAt: Date.now(), checks };
|
|
120
|
+
}
|
|
121
|
+
function formatDuration(ms) {
|
|
122
|
+
if (!Number.isFinite(ms))
|
|
123
|
+
return "unknown";
|
|
124
|
+
if (ms < 60_000)
|
|
125
|
+
return `${Math.round(ms / 1_000)}s`;
|
|
126
|
+
if (ms < 3_600_000)
|
|
127
|
+
return `${Math.round(ms / 60_000)}m`;
|
|
128
|
+
return `${(ms / 3_600_000).toFixed(1)}h`;
|
|
129
|
+
}
|
|
130
|
+
function errorMessage(error) {
|
|
131
|
+
return error instanceof Error ? error.message : String(error);
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAiBtD,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,MAAM,MAAM,GAAkB,EAAE,CAAC;IACjC,IAAI,MAAqC,CAAC;IAC1C,IAAI,CAAC;QACH,MAAM,GAAG,UAAU,EAAE,CAAC;QACtB,MAAM,UAAU,GAAG,gBAAgB,EAAE,CAAC;QACtC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;IACnH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACrF,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;YAChD,OAAO,EAAE,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,eAAe,MAAM,CAAC,MAAM,YAAY,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,aAAa;SAC7H,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,wBAAwB,YAAY,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;IAC3G,CAAC;IAED,MAAM,OAAO,GAAG,mBAAmB,EAAE,CAAC;IACtC,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,KAAK,IAAI,CAAC,CAAC;IACzE,MAAM,CAAC,IAAI,CAAC;QACV,IAAI,EAAE,aAAa;QACnB,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;QAC3C,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,gDAAgD;KACjI,CAAC,CAAC;IAEH,MAAM,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,eAAe,KAAK,IAAI,IAAI,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;SAC9G,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,EAAqC,EAAE,CAAC,MAAM,KAAK,OAAO,IAAI,MAAM,KAAK,aAAa,CAAC,CAAC;IACpI,IAAI,qBAAqB,CAAC,MAAM,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,UAAU,CAAC,qBAAqB,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACnE,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACzF,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;YACjC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,sCAAsC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,6BAA6B;SAC5J,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,8CAA8C,EAAE,CAAC,CAAC;IAC3G,CAAC;IAED,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,MAAM,oBAAoB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACtD,MAAM,CAAC,IAAI,CAAC;QACV,IAAI,EAAE,aAAa;QACnB,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;QACpC,OAAO,EAAE,OAAO,CAAC,EAAE;YACjB,CAAC,CAAC,GAAG,YAAY,CAAC,KAAK,CAAC,UAAU,OAAO,CAAC,IAAI,CAAC,SAAS,iBAAiB,OAAO,CAAC,SAAS,CAAC,SAAS,IAAI;YACxG,CAAC,CAAC,GAAG,YAAY,CAAC,KAAK,CAAC,UAAU,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,QAAQ,eAAe,OAAO,CAAC,SAAS,CAAC,KAAK,IAAI,QAAQ,EAAE;KACvH,CAAC,CAAC;IAEH,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;QACf,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,CAAC;YAC9C,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC;YACtC,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC;YAChD,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,KAAK,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,WAAW,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,YAAY,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC3N,MAAM,SAAS,GAAG,MAAM,CAAE,WAAW,CAAC,QAAgD,EAAE,SAAS,IAAI,SAAS,CAAC,CAAC;YAChH,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,SAAS,EAAE,EAAE,CAAC,CAAC;YACjH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,CAAuE,CAAC;YAC1J,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,IAAI,UAAU,CAAC,SAAS,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;QACnL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QACrB,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,6DAA6D,EAAE,CAAC,CAAC;IAC5H,CAAC;SAAM,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,iBAAiB,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;QAC3D,MAAM,OAAO,GAAG,IAAI,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACnD,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC5B,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBAC5C,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,iBAAiB,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,iBAAiB,CAAC,EAAE,EAAE,CAAC,CAAC;YAC7K,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACzF,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,WAAyB;IACpD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;IACnF,MAAM,KAAK,GAAG,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;IACvC,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC;QACvC,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IACvG,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC;IAC7D,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,gBAAgB;IACvB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,yCAAyC,EAAE,CAAC;IAC7G,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IAChF,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC;IACnD,OAAO,IAAI,GAAG,KAAK;QACjB,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,WAAW,8BAA8B,EAAE;QAC1E,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,WAAW,oBAAoB,EAAE,CAAC;AACpE,CAAC;AAED,SAAS,MAAM,CAAC,MAAqB;IACnC,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC;AAC3H,CAAC;AAED,SAAS,cAAc,CAAC,EAAU;IAChC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QAAE,OAAO,SAAS,CAAC;IAC3C,IAAI,EAAE,GAAG,MAAM;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC;IACrD,IAAI,EAAE,GAAG,SAAS;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC;IACzD,OAAO,GAAG,CAAC,EAAE,GAAG,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AAC3C,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC"}
|
package/dist/cli/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
+
import * as prompts from "@clack/prompts";
|
|
3
4
|
import { accountSummaries, loadConfig, loadStoredConfig, saveConfig } from "../config/loader.js";
|
|
4
5
|
import { CONFIG_PATH } from "../config/paths.js";
|
|
5
6
|
import { OkxClient } from "../core/okx-client.js";
|
|
@@ -10,10 +11,13 @@ import { displayProxy, resolveProxy } from "../network/proxy.js";
|
|
|
10
11
|
import { AccountService } from "../account/service.js";
|
|
11
12
|
import { runMcpServer } from "../mcp/server.js";
|
|
12
13
|
import { RuntimeClient } from "../runtime/client.js";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { SetupCancelledError, parseSetupTargets, SETUP_TARGETS, setupSucceeded, setupSummary, setupTargets } from "../setup/installer.js";
|
|
14
|
+
import { ask, askConfirm, askSecret } from "./prompts.js";
|
|
15
|
+
import { SetupCancelledError, inspectSetupTargets, parseSetupTargets, SETUP_TARGETS, setupSucceeded, setupSummary, setupTargets, syncSkills } from "../setup/installer.js";
|
|
16
16
|
import { runSetupWizard } from "../setup/wizard.js";
|
|
17
|
+
import { runDoctor, formatDoctor } from "./doctor.js";
|
|
18
|
+
import { formatRuntimeStatus, formatToolHelp } from "./render.js";
|
|
19
|
+
import { getToolHelp } from "../tools/help.js";
|
|
20
|
+
import { checkForUpdate, installLatestVersion, OFFICIAL_NPM_REGISTRY, runInstalledCommand } from "../update/service.js";
|
|
17
21
|
const program = new Command()
|
|
18
22
|
.name("desic-okx")
|
|
19
23
|
.description("Independent Desic runtime, MCP server, CLI, and skills for OKX")
|
|
@@ -25,7 +29,11 @@ program.command("start")
|
|
|
25
29
|
print(await client.health());
|
|
26
30
|
});
|
|
27
31
|
program.command("daemon", { hidden: true })
|
|
28
|
-
.action(async () =>
|
|
32
|
+
.action(async () => {
|
|
33
|
+
// Keep native SQLite out of long-lived MCP adapter processes on Windows.
|
|
34
|
+
const { runRuntimeServer } = await import("../runtime/server.js");
|
|
35
|
+
await runRuntimeServer();
|
|
36
|
+
});
|
|
29
37
|
program.command("stop")
|
|
30
38
|
.description("Stop the local runtime")
|
|
31
39
|
.action(async () => {
|
|
@@ -35,9 +43,26 @@ program.command("stop")
|
|
|
35
43
|
});
|
|
36
44
|
program.command("status")
|
|
37
45
|
.description("Show local runtime status")
|
|
38
|
-
.
|
|
46
|
+
.option("--json", "Print machine-readable JSON")
|
|
47
|
+
.action(async (options) => {
|
|
39
48
|
const client = await RuntimeClient.connect({ start: false });
|
|
40
|
-
|
|
49
|
+
const health = await client.health();
|
|
50
|
+
if (options.json || !process.stdout.isTTY)
|
|
51
|
+
print(health);
|
|
52
|
+
else
|
|
53
|
+
process.stdout.write(`${formatRuntimeStatus(health)}\n`);
|
|
54
|
+
});
|
|
55
|
+
program.command("doctor")
|
|
56
|
+
.description("Diagnose installation, network, Runtime, Skills, and accounts")
|
|
57
|
+
.option("--json", "Print machine-readable JSON")
|
|
58
|
+
.action(async (options) => {
|
|
59
|
+
const report = await runDoctor();
|
|
60
|
+
if (options.json || !process.stdout.isTTY)
|
|
61
|
+
print(report);
|
|
62
|
+
else
|
|
63
|
+
process.stdout.write(`${formatDoctor(report)}\n`);
|
|
64
|
+
if (!report.ok)
|
|
65
|
+
process.exitCode = 1;
|
|
41
66
|
});
|
|
42
67
|
program.command("mcp")
|
|
43
68
|
.description("Run the stdio MCP adapter")
|
|
@@ -88,6 +113,61 @@ program.command("tools")
|
|
|
88
113
|
const client = await RuntimeClient.connect();
|
|
89
114
|
print(await client.tools());
|
|
90
115
|
});
|
|
116
|
+
program.command("tool")
|
|
117
|
+
.description("Show one tool's requirements, schema, and example")
|
|
118
|
+
.argument("<name>", "tool name")
|
|
119
|
+
.option("--json", "Print machine-readable JSON")
|
|
120
|
+
.action((name, options) => {
|
|
121
|
+
const help = getToolHelp(name);
|
|
122
|
+
if (!help)
|
|
123
|
+
throw new Error(`Unknown tool '${name}'`);
|
|
124
|
+
if (options.json || !process.stdout.isTTY)
|
|
125
|
+
print(help);
|
|
126
|
+
else
|
|
127
|
+
process.stdout.write(`${formatToolHelp(help)}\n`);
|
|
128
|
+
});
|
|
129
|
+
const skills = program.command("skills").description("Inspect and synchronize bundled Skills");
|
|
130
|
+
skills.command("status")
|
|
131
|
+
.description("Show pending Skill installations and updates")
|
|
132
|
+
.option("--targets <targets>", "codex,claude-code; defaults to detected installations")
|
|
133
|
+
.action((options) => print(syncSkills(resolveSkillTargets(options.targets), { dryRun: true })));
|
|
134
|
+
skills.command("sync")
|
|
135
|
+
.description("Install or update bundled Skills, backing up changed copies")
|
|
136
|
+
.option("--targets <targets>", "codex,claude-code; defaults to detected installations")
|
|
137
|
+
.option("--dry-run", "Show changes without writing")
|
|
138
|
+
.action((options) => print(syncSkills(resolveSkillTargets(options.targets), { dryRun: options.dryRun })));
|
|
139
|
+
program.command("update")
|
|
140
|
+
.description("Check for and install the latest npm release safely")
|
|
141
|
+
.option("--check", "Only check for a newer version")
|
|
142
|
+
.option("--yes", "Install without confirmation")
|
|
143
|
+
.option("--registry <url>", "npm registry", OFFICIAL_NPM_REGISTRY)
|
|
144
|
+
.action(async (options) => {
|
|
145
|
+
const info = await checkForUpdate(options.registry, loadStoredConfig().proxy.url);
|
|
146
|
+
if (options.check || !info.updateAvailable) {
|
|
147
|
+
print(info);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
let approved = options.yes === true;
|
|
151
|
+
if (!approved && process.stdin.isTTY) {
|
|
152
|
+
const answer = await prompts.confirm({ message: `Update ${info.current} to ${info.latest}?` });
|
|
153
|
+
if (prompts.isCancel(answer) || answer === false)
|
|
154
|
+
throw new SetupCancelledError();
|
|
155
|
+
approved = true;
|
|
156
|
+
}
|
|
157
|
+
if (!approved)
|
|
158
|
+
throw new Error("Use --yes to update in a non-interactive terminal");
|
|
159
|
+
const exitCode = await installLatestVersion(info);
|
|
160
|
+
if (exitCode !== 0)
|
|
161
|
+
throw new Error(`npm exited with code ${exitCode}`);
|
|
162
|
+
process.stdout.write(`Updated to ${info.latest}. Synchronizing installed Skills...\n`);
|
|
163
|
+
const skillExit = await runInstalledCommand(["skills", "sync"]);
|
|
164
|
+
if (skillExit !== 0)
|
|
165
|
+
process.stderr.write("The package was updated, but Skill synchronization needs attention.\n");
|
|
166
|
+
process.stdout.write("Verifying the updated installation...\n");
|
|
167
|
+
const doctorExit = await runInstalledCommand(["doctor"]);
|
|
168
|
+
if (doctorExit !== 0)
|
|
169
|
+
process.stderr.write("The package was updated, but Doctor reported an issue.\n");
|
|
170
|
+
});
|
|
91
171
|
program.command("config-path")
|
|
92
172
|
.description("Print the active configuration path")
|
|
93
173
|
.action(() => {
|
|
@@ -109,11 +189,13 @@ account.command("add")
|
|
|
109
189
|
if (!apiKey || !secretKey || !passphrase)
|
|
110
190
|
throw new Error("All three credential fields are required");
|
|
111
191
|
const config = loadStoredConfig();
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
192
|
+
const candidate = structuredClone(config);
|
|
193
|
+
candidate.accounts[name] = { environment: environmentValue, apiKey, secretKey, passphrase };
|
|
194
|
+
candidate.defaultAccount ??= name;
|
|
195
|
+
const verified = await verifyAccount(candidate, name);
|
|
196
|
+
saveConfig(candidate);
|
|
115
197
|
await stopForReload();
|
|
116
|
-
print({ saved: true, name, environment: environmentValue, configPath: CONFIG_PATH });
|
|
198
|
+
print({ saved: true, name, environment: environmentValue, permissions: verified.data, configPath: CONFIG_PATH });
|
|
117
199
|
});
|
|
118
200
|
account.command("list")
|
|
119
201
|
.description("List configured account aliases")
|
|
@@ -121,11 +203,87 @@ account.command("list")
|
|
|
121
203
|
account.command("verify")
|
|
122
204
|
.description("Verify one account and show OKX-reported permissions")
|
|
123
205
|
.option("--name <name>", "account alias")
|
|
206
|
+
.option("--all", "Verify all configured accounts")
|
|
124
207
|
.action(async (options) => {
|
|
125
208
|
const config = loadConfig();
|
|
126
209
|
const client = new OkxClient(OKX_REST_BASE_URL, resolveProxy(config.proxy.url).url);
|
|
127
210
|
const service = new AccountService(config, client);
|
|
128
|
-
|
|
211
|
+
if (options.all) {
|
|
212
|
+
const results = await Promise.all(Object.keys(config.accounts).map(async (name) => {
|
|
213
|
+
try {
|
|
214
|
+
return { name, ok: true, result: await service.verify(name) };
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
return { name, ok: false, error: publicError(error) };
|
|
218
|
+
}
|
|
219
|
+
}));
|
|
220
|
+
print(results);
|
|
221
|
+
if (results.some((result) => !result.ok))
|
|
222
|
+
process.exitCode = 1;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
print(await service.verify(options.name));
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
account.command("set-default")
|
|
229
|
+
.description("Set the default account alias")
|
|
230
|
+
.argument("<name>", "account alias")
|
|
231
|
+
.action(async (name) => {
|
|
232
|
+
const config = loadStoredConfig();
|
|
233
|
+
if (!config.accounts[name])
|
|
234
|
+
throw new Error(`Account '${name}' was not found`);
|
|
235
|
+
config.defaultAccount = name;
|
|
236
|
+
saveConfig(config);
|
|
237
|
+
await stopForReload();
|
|
238
|
+
print({ saved: true, defaultAccount: name });
|
|
239
|
+
});
|
|
240
|
+
account.command("rename")
|
|
241
|
+
.description("Rename an account alias")
|
|
242
|
+
.argument("<oldName>", "current account alias")
|
|
243
|
+
.argument("<newName>", "new account alias")
|
|
244
|
+
.action(async (oldName, newName) => {
|
|
245
|
+
const config = loadStoredConfig();
|
|
246
|
+
if (!config.accounts[oldName])
|
|
247
|
+
throw new Error(`Account '${oldName}' was not found`);
|
|
248
|
+
if (config.accounts[newName])
|
|
249
|
+
throw new Error(`Account '${newName}' already exists`);
|
|
250
|
+
config.accounts[newName] = config.accounts[oldName];
|
|
251
|
+
delete config.accounts[oldName];
|
|
252
|
+
if (config.defaultAccount === oldName)
|
|
253
|
+
config.defaultAccount = newName;
|
|
254
|
+
saveConfig(config);
|
|
255
|
+
await stopForReload();
|
|
256
|
+
print({ renamed: true, from: oldName, to: newName, default: config.defaultAccount === newName });
|
|
257
|
+
});
|
|
258
|
+
account.command("edit")
|
|
259
|
+
.description("Edit an account environment or replace its credentials")
|
|
260
|
+
.argument("<name>", "account alias")
|
|
261
|
+
.option("--environment <environment>", "demo or live")
|
|
262
|
+
.option("--replace-credentials", "Prompt for replacement credentials")
|
|
263
|
+
.action(async (name, options) => {
|
|
264
|
+
const config = loadStoredConfig();
|
|
265
|
+
const current = config.accounts[name];
|
|
266
|
+
if (!current)
|
|
267
|
+
throw new Error(`Account '${name}' was not found`);
|
|
268
|
+
const environment = options.environment?.trim() || await ask("Environment (demo/live)", current.environment);
|
|
269
|
+
if (environment !== "demo" && environment !== "live")
|
|
270
|
+
throw new Error("Environment must be demo or live");
|
|
271
|
+
const normalizedEnvironment = environment;
|
|
272
|
+
const replace = options.replaceCredentials ?? await askConfirm("Replace API credentials", false);
|
|
273
|
+
const updated = { ...current, environment: normalizedEnvironment };
|
|
274
|
+
if (replace) {
|
|
275
|
+
updated.apiKey = await askSecret("API Key");
|
|
276
|
+
updated.secretKey = await askSecret("Secret Key");
|
|
277
|
+
updated.passphrase = await askSecret("Passphrase");
|
|
278
|
+
if (!updated.apiKey || !updated.secretKey || !updated.passphrase)
|
|
279
|
+
throw new Error("All three credential fields are required");
|
|
280
|
+
}
|
|
281
|
+
const candidate = structuredClone(config);
|
|
282
|
+
candidate.accounts[name] = updated;
|
|
283
|
+
const verified = await verifyAccount(candidate, name);
|
|
284
|
+
saveConfig(candidate);
|
|
285
|
+
await stopForReload();
|
|
286
|
+
print({ saved: true, name, environment, permissions: verified.data });
|
|
129
287
|
});
|
|
130
288
|
account.command("remove")
|
|
131
289
|
.description("Remove one account")
|
|
@@ -157,6 +315,27 @@ async function stopForReload() {
|
|
|
157
315
|
}
|
|
158
316
|
catch { }
|
|
159
317
|
}
|
|
318
|
+
async function verifyAccount(config, name) {
|
|
319
|
+
const client = new OkxClient(OKX_REST_BASE_URL, resolveProxy(config.proxy.url).url);
|
|
320
|
+
return new AccountService(config, client).verify(name);
|
|
321
|
+
}
|
|
322
|
+
function parseSkillTargets(value) {
|
|
323
|
+
const targets = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
324
|
+
const invalid = targets.filter((target) => target !== "codex" && target !== "claude-code");
|
|
325
|
+
if (invalid.length)
|
|
326
|
+
throw new Error(`Unsupported Skill target: ${invalid.join(", ")}`);
|
|
327
|
+
return [...new Set(targets)];
|
|
328
|
+
}
|
|
329
|
+
function resolveSkillTargets(value) {
|
|
330
|
+
if (value)
|
|
331
|
+
return parseSkillTargets(value);
|
|
332
|
+
const detected = inspectSetupTargets()
|
|
333
|
+
.filter((item) => (item.target === "codex" || item.target === "claude-code") && item.skillsInstalled !== null && item.skillsInstalled > 0)
|
|
334
|
+
.map((item) => item.target);
|
|
335
|
+
if (!detected.length)
|
|
336
|
+
throw new Error("No installed Codex or Claude Code Skills were detected; pass --targets to install them");
|
|
337
|
+
return detected;
|
|
338
|
+
}
|
|
160
339
|
function print(value) {
|
|
161
340
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
162
341
|
}
|