desic-okx-agent 0.1.9 → 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.
Files changed (43) hide show
  1. package/README.md +46 -4
  2. package/dist/account/private-websocket.d.ts +1 -0
  3. package/dist/account/private-websocket.js +27 -2
  4. package/dist/account/private-websocket.js.map +1 -1
  5. package/dist/cli/doctor.d.ts +14 -0
  6. package/dist/cli/doctor.js +133 -0
  7. package/dist/cli/doctor.js.map +1 -0
  8. package/dist/cli/index.js +185 -9
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/cli/prompts.d.ts +1 -0
  11. package/dist/cli/prompts.js +6 -0
  12. package/dist/cli/prompts.js.map +1 -1
  13. package/dist/cli/render.d.ts +8 -0
  14. package/dist/cli/render.js +73 -0
  15. package/dist/cli/render.js.map +1 -0
  16. package/dist/market/store.d.ts +1 -0
  17. package/dist/market/store.js +24 -0
  18. package/dist/market/store.js.map +1 -1
  19. package/dist/market/websocket.d.ts +1 -0
  20. package/dist/market/websocket.js +13 -0
  21. package/dist/market/websocket.js.map +1 -1
  22. package/dist/runtime/client.d.ts +1 -0
  23. package/dist/runtime/client.js +46 -2
  24. package/dist/runtime/client.js.map +1 -1
  25. package/dist/runtime/server.js +21 -2
  26. package/dist/runtime/server.js.map +1 -1
  27. package/dist/setup/installer.d.ts +21 -0
  28. package/dist/setup/installer.js +113 -27
  29. package/dist/setup/installer.js.map +1 -1
  30. package/dist/storage/database.d.ts +2 -0
  31. package/dist/storage/database.js +23 -0
  32. package/dist/storage/database.js.map +1 -1
  33. package/dist/tools/catalog.js +1 -1
  34. package/dist/tools/catalog.js.map +1 -1
  35. package/dist/tools/help.d.ts +10 -0
  36. package/dist/tools/help.js +79 -0
  37. package/dist/tools/help.js.map +1 -0
  38. package/dist/update/service.d.ts +10 -0
  39. package/dist/update/service.js +104 -0
  40. package/dist/update/service.js.map +1 -0
  41. package/package.json +1 -1
  42. package/skills/okx-news-intelligence/SKILL.md +2 -0
  43. package/skills/okx-smart-money-analysis/SKILL.md +2 -0
package/README.md CHANGED
@@ -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 status` and `desic-okx tools`. Do not ask for or configure any OKX API credentials. Confirm that public market tools are ready to use.
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:
@@ -101,10 +101,15 @@ Useful Runtime commands:
101
101
 
102
102
  ```bash
103
103
  desic-okx status
104
+ desic-okx status --json
105
+ desic-okx doctor
104
106
  desic-okx tools
107
+ desic-okx tool market_get_decision_snapshot
105
108
  desic-okx stop
106
109
  ```
107
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
+
108
113
  ## Connect Codex
109
114
 
110
115
  The recommended option is `desic-okx setup`, which also installs all Skills. To register only the local stdio MCP server manually:
@@ -169,6 +174,14 @@ Included skills:
169
174
 
170
175
  Restart the client if a newly installed skill does not appear.
171
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
+
172
185
  ## Configure an OKX account
173
186
 
174
187
  Public tools work without credentials. Account and trading tools use a named account alias and never accept credentials as tool arguments.
@@ -178,9 +191,15 @@ Add and verify an account interactively:
178
191
  ```bash
179
192
  desic-okx account add --name demo --environment demo
180
193
  desic-okx account verify --name demo
194
+ desic-okx account verify --all
181
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
182
199
  ```
183
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
+
184
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:
185
204
 
186
205
  ```bash
@@ -199,6 +218,29 @@ OKX_ENVIRONMENT=demo|live
199
218
 
200
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.
201
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
+
202
244
  ## Proxy
203
245
 
204
246
  REST and every public, business, and private WebSocket connection use the same proxy resolution order:
@@ -256,7 +298,7 @@ Use an OKX Demo account first. Trading software can lose money, and the project
256
298
 
257
299
  ## Experimental intelligence
258
300
 
259
- 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.
260
302
 
261
303
  ## Development
262
304
 
@@ -284,7 +326,7 @@ Normal tests never submit orders.
284
326
 
285
327
  ## Status
286
328
 
287
- This repository is currently pre-release software at version `0.1.9`. 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.
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.
288
330
 
289
331
  ## License
290
332
 
@@ -7,6 +7,7 @@ export declare class PrivateAccountWebSockets {
7
7
  constructor(config: RuntimeConfig, store: AccountStore);
8
8
  start(): void;
9
9
  stop(): void;
10
+ status(): Array<Record<string, unknown>>;
10
11
  private connect;
11
12
  private reconnect;
12
13
  private message;
@@ -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]) => ({ account: { name, ...account }, stopping: false, attempt: 0 }));
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", () => socket.close());
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;AAWnD,MAAM,OAAO,wBAAwB;IAIiB;IAHnC,WAAW,CAAe;IAC1B,KAAK,CAA2B;IAEjD,YAAY,MAAqB,EAAmB,KAAmB;QAAnB,UAAK,GAAL,KAAK,CAAc;QACrE,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC9I,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC;QACpD,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACpE,CAAC;IAED,KAAK;QACH,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,WAAW;YAAE,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACtE,CAAC;IAED,IAAI;QACF,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YAC1C,UAAU,CAAC,QAAQ,GAAG,IAAI,CAAC;YAC3B,IAAI,UAAU,CAAC,SAAS;gBAAE,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YAC7D,IAAI,UAAU,CAAC,SAAS;gBAAE,aAAa,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YAC9D,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC;QAC7B,CAAC;IACH,CAAC;IAEO,OAAO,CAAC,UAAsB;QACpC,IAAI,UAAU,CAAC,QAAQ;YAAE,OAAO;QAChC,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,WAAW,KAAK,MAAM;YACnD,CAAC,CAAC,wCAAwC;YAC1C,CAAC,CAAC,qCAAqC,CAAC;QAC1C,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAClF,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC;QAC3B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7G,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC9E,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;QACrD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;IAC3C,CAAC;IAEO,SAAS,CAAC,UAAsB;QACtC,IAAI,UAAU,CAAC,QAAQ,IAAI,UAAU,CAAC,SAAS;YAAE,OAAO;QACxD,IAAI,UAAU,CAAC,SAAS;YAAE,aAAa,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAC9D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC;QAChG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;QACxB,UAAU,CAAC,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YACrC,UAAU,CAAC,SAAS,GAAG,SAAS,CAAC;YACjC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC3B,CAAC,EAAE,KAAK,CAAC,CAAC;QACV,UAAU,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IAC/B,CAAC;IAEO,OAAO,CAAC,UAAsB,EAAE,IAAY;QAClD,IAAI,IAAI,KAAK,MAAM;YAAE,OAAO;QAC5B,IAAI,OAAgC,CAAC;QACrC,IAAI,CAAC;YAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA4B,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO;QAAC,CAAC;QAChF,IAAI,OAAO,CAAC,KAAK,KAAK,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,GAAG,EAAE,CAAC;YACtD,UAAU,CAAC,OAAO,GAAG,CAAC,CAAC;YACvB,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;gBACrC,EAAE,EAAE,WAAW;gBACf,IAAI,EAAE;oBACJ,EAAE,OAAO,EAAE,SAAS,EAAE;oBACtB,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE;oBACzC,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE;oBACtC,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE;iBAC5C;aACF,CAAC,CAAC,CAAC;YACJ,UAAU,CAAC,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE;gBACtC,IAAI,UAAU,CAAC,MAAM,EAAE,UAAU,KAAK,SAAS,CAAC,IAAI;oBAAE,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACvF,CAAC,EAAE,MAAM,CAAC,CAAC;YACX,UAAU,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;YAC7B,OAAO;QACT,CAAC;QACD,MAAM,GAAG,GAAG,OAAO,CAAC,GAA0C,CAAC;QAC/D,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAsC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/F,IAAI,CAAC,GAAG,EAAE,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QAC1C,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;aAChF,IAAI,GAAG,CAAC,OAAO,KAAK,WAAW;YAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;aAC1F,IAAI,GAAG,CAAC,OAAO,KAAK,QAAQ;YAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;aACpF,IAAI,GAAG,CAAC,OAAO,KAAK,aAAa;YAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACtG,CAAC;CACF;AAED,SAAS,SAAS,CAAC,OAA2B;IAC5C,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC;IACzD,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,SAAS,CAAC;SACxD,MAAM,CAAC,GAAG,SAAS,uBAAuB,CAAC;SAC3C,MAAM,CAAC,QAAQ,CAAC,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AACrF,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":"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"}
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,9 +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 { ask, askSecret } from "./prompts.js";
14
- 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";
15
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";
16
21
  const program = new Command()
17
22
  .name("desic-okx")
18
23
  .description("Independent Desic runtime, MCP server, CLI, and skills for OKX")
@@ -38,9 +43,26 @@ program.command("stop")
38
43
  });
39
44
  program.command("status")
40
45
  .description("Show local runtime status")
41
- .action(async () => {
46
+ .option("--json", "Print machine-readable JSON")
47
+ .action(async (options) => {
42
48
  const client = await RuntimeClient.connect({ start: false });
43
- print(await client.health());
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;
44
66
  });
45
67
  program.command("mcp")
46
68
  .description("Run the stdio MCP adapter")
@@ -91,6 +113,61 @@ program.command("tools")
91
113
  const client = await RuntimeClient.connect();
92
114
  print(await client.tools());
93
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
+ });
94
171
  program.command("config-path")
95
172
  .description("Print the active configuration path")
96
173
  .action(() => {
@@ -112,11 +189,13 @@ account.command("add")
112
189
  if (!apiKey || !secretKey || !passphrase)
113
190
  throw new Error("All three credential fields are required");
114
191
  const config = loadStoredConfig();
115
- config.accounts[name] = { environment: environmentValue, apiKey, secretKey, passphrase };
116
- config.defaultAccount ??= name;
117
- saveConfig(config);
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);
118
197
  await stopForReload();
119
- print({ saved: true, name, environment: environmentValue, configPath: CONFIG_PATH });
198
+ print({ saved: true, name, environment: environmentValue, permissions: verified.data, configPath: CONFIG_PATH });
120
199
  });
121
200
  account.command("list")
122
201
  .description("List configured account aliases")
@@ -124,11 +203,87 @@ account.command("list")
124
203
  account.command("verify")
125
204
  .description("Verify one account and show OKX-reported permissions")
126
205
  .option("--name <name>", "account alias")
206
+ .option("--all", "Verify all configured accounts")
127
207
  .action(async (options) => {
128
208
  const config = loadConfig();
129
209
  const client = new OkxClient(OKX_REST_BASE_URL, resolveProxy(config.proxy.url).url);
130
210
  const service = new AccountService(config, client);
131
- print(await service.verify(options.name));
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 });
132
287
  });
133
288
  account.command("remove")
134
289
  .description("Remove one account")
@@ -160,6 +315,27 @@ async function stopForReload() {
160
315
  }
161
316
  catch { }
162
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
+ }
163
339
  function print(value) {
164
340
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
165
341
  }