kiwivm-cli 0.1.0 → 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 (52) hide show
  1. package/README.md +62 -36
  2. package/dist/index.d.mts.map +1 -1
  3. package/dist/index.mjs +370 -50
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/src/commands/admin.test.ts +135 -22
  7. package/src/commands/admin.ts +57 -19
  8. package/src/commands/backup.test.ts +26 -23
  9. package/src/commands/backup.ts +13 -15
  10. package/src/commands/help.test.ts +27 -7
  11. package/src/commands/help.ts +61 -26
  12. package/src/commands/info.test.ts +47 -43
  13. package/src/commands/info.ts +11 -13
  14. package/src/commands/iso.test.ts +58 -0
  15. package/src/commands/iso.ts +21 -0
  16. package/src/commands/migrate.test.ts +105 -0
  17. package/src/commands/migrate.ts +38 -0
  18. package/src/commands/network.test.ts +107 -30
  19. package/src/commands/network.ts +56 -18
  20. package/src/commands/power.test.ts +58 -40
  21. package/src/commands/power.ts +27 -16
  22. package/src/commands/shell.test.ts +66 -0
  23. package/src/commands/shell.ts +25 -0
  24. package/src/commands/snapshot.test.ts +141 -71
  25. package/src/commands/snapshot.ts +85 -33
  26. package/src/commands/stats.test.ts +81 -0
  27. package/src/commands/stats.ts +25 -0
  28. package/src/commands/system.test.ts +109 -40
  29. package/src/commands/system.ts +55 -23
  30. package/src/index.test.ts +435 -148
  31. package/src/index.ts +129 -57
  32. package/src/types.ts +57 -1
  33. package/dist/admin-fOud1ZmX.mjs +0 -15
  34. package/dist/admin-fOud1ZmX.mjs.map +0 -1
  35. package/dist/backup-D1UJ4aap.mjs +0 -12
  36. package/dist/backup-D1UJ4aap.mjs.map +0 -1
  37. package/dist/help-Dk-WApoi.mjs +0 -40
  38. package/dist/help-Dk-WApoi.mjs.map +0 -1
  39. package/dist/info-DKExtFYH.mjs +0 -13
  40. package/dist/info-DKExtFYH.mjs.map +0 -1
  41. package/dist/monitoring-BSuv8fj9.mjs +0 -13
  42. package/dist/monitoring-BSuv8fj9.mjs.map +0 -1
  43. package/dist/network-1ycEIJqT.mjs +0 -15
  44. package/dist/network-1ycEIJqT.mjs.map +0 -1
  45. package/dist/power-CDg0Mx1A.mjs +0 -14
  46. package/dist/power-CDg0Mx1A.mjs.map +0 -1
  47. package/dist/snapshot-LO_ufoj5.mjs +0 -23
  48. package/dist/snapshot-LO_ufoj5.mjs.map +0 -1
  49. package/dist/system-Bl-dsqX9.mjs +0 -21
  50. package/dist/system-Bl-dsqX9.mjs.map +0 -1
  51. package/src/commands/monitoring.test.ts +0 -82
  52. package/src/commands/monitoring.ts +0 -20
package/README.md CHANGED
@@ -36,7 +36,7 @@ npx kiwivm-cli help
36
36
  ## Usage
37
37
 
38
38
  ```bash
39
- kiwivm-cli <category> <action> [--flags...]
39
+ kiwivm-cli <command> [<subcommand>] [args...] [--flags...]
40
40
  ```
41
41
 
42
42
  ### Examples
@@ -45,53 +45,79 @@ kiwivm-cli <category> <action> [--flags...]
45
45
  # Get service info
46
46
  kiwivm-cli info
47
47
 
48
- # Get live service info (CPU, RAM, disk, uptime)
49
- kiwivm-cli info live
48
+ # Get live status (CPU, RAM, disk, uptime)
49
+ kiwivm-cli status
50
50
 
51
51
  # Power control
52
- kiwivm-cli power start
53
- kiwivm-cli power stop
54
- kiwivm-cli power restart
55
- kiwivm-cli power kill
52
+ kiwivm-cli start
53
+ kiwivm-cli stop
54
+ kiwivm-cli restart
55
+ kiwivm-cli kill
56
56
 
57
57
  # Snapshots
58
58
  kiwivm-cli snapshot list
59
- kiwivm-cli snapshot create --description "before update"
60
- kiwivm-cli snapshot delete --snapshot vsb1234567890
61
- kiwivm-cli snapshot restore --snapshot vsb1234567890
62
- kiwivm-cli snapshot sticky --snapshot vsb1234567890 --sticky 1
63
- kiwivm-cli snapshot import --source-veid 87654321 --source-token token123
59
+ kiwivm-cli snapshot create --desc "before update"
60
+ kiwivm-cli snapshot delete vsb1234567890
61
+ kiwivm-cli snapshot restore vsb1234567890
62
+ kiwivm-cli snapshot sticky vsb1234567890 --on
63
+ kiwivm-cli snapshot export vsb1234567890
64
+ kiwivm-cli snapshot import 87654321 token123
64
65
 
65
66
  # Backups
66
67
  kiwivm-cli backup list
67
- kiwivm-cli backup copy --backup-token abc123
68
+ kiwivm-cli backup copy abc123
68
69
 
69
70
  # System
70
- kiwivm-cli system hostname --new-hostname my-vps
71
- kiwivm-cli system rdns --ip 1.2.3.4 --ptr my.domain.com
72
- kiwivm-cli system password
73
- kiwivm-cli system sshkey
74
- kiwivm-cli system sshkey --ssh-keys "ssh-ed25519 AAAAC3..."
75
- kiwivm-cli system os
76
- kiwivm-cli system reinstall --os "ubuntu-22.04"
77
-
78
- # Network
79
- kiwivm-cli network ipv6-add
80
- kiwivm-cli network ipv6-delete --ip 2001:db8::1
81
- kiwivm-cli network private-list
82
- kiwivm-cli network private-assign --ip 10.0.0.5
83
- kiwivm-cli network private-delete --ip 10.0.0.5
84
-
85
- # Monitoring
86
- kiwivm-cli monitoring usage
87
- kiwivm-cli monitoring audit
88
- kiwivm-cli monitoring rate-limit
71
+ kiwivm-cli hostname my-vps
72
+ kiwivm-cli password
73
+
74
+ # SSH keys
75
+ kiwivm-cli ssh-key
76
+ kiwivm-cli ssh-key set "ssh-ed25519 AAAAC3..."
77
+
78
+ # OS
79
+ kiwivm-cli os list
80
+ kiwivm-cli os reinstall ubuntu-22.04
81
+
82
+ # rDNS
83
+ kiwivm-cli rdns set 1.2.3.4 my.domain.com
84
+
85
+ # IPv6
86
+ kiwivm-cli ipv6 add
87
+ kiwivm-cli ipv6 delete 2001:db8::1
88
+
89
+ # Private IP
90
+ kiwivm-cli private-ip list
91
+ kiwivm-cli private-ip assign 10.0.0.5
92
+ kiwivm-cli private-ip delete 10.0.0.5
93
+
94
+ # ISO
95
+ kiwivm-cli iso mount ubuntu-22.04-live
96
+ kiwivm-cli iso unmount
97
+
98
+ # Shell
99
+ kiwivm-cli shell exec "uptime"
100
+ kiwivm-cli shell script "apt update && apt upgrade -y"
101
+
102
+ # Migrate
103
+ kiwivm-cli migrate locations
104
+ kiwivm-cli migrate start "Las Vegas"
105
+ kiwivm-cli clone 1.2.3.4 "root-pass" --port 22
106
+
107
+ # Stats
108
+ kiwivm-cli stats usage
109
+ kiwivm-cli stats audit
110
+ kiwivm-cli stats rate-limit
89
111
 
90
112
  # Admin
91
- kiwivm-cli admin suspensions
92
- kiwivm-cli admin unsuspend --record-id 123
93
- kiwivm-cli admin resolve
94
- kiwivm-cli admin resolve-violation --record-id 456
113
+ kiwivm-cli suspensions
114
+ kiwivm-cli unsuspend 123
115
+ kiwivm-cli violations
116
+ kiwivm-cli violations resolve 456
117
+
118
+ # Notifications
119
+ kiwivm-cli notifications
120
+ kiwivm-cli notifications set '{"1":1,"2":0}'
95
121
  ```
96
122
 
97
123
  Output is JSON to stdout. Errors go to stderr with exit code 1.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";iBAoEe,IAAA,CAAA,GAAI,OAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";iBAwFe,IAAA,CAAA,GAAI,OAAA"}
package/dist/index.mjs CHANGED
@@ -37,6 +37,288 @@ var KiwiVMClient = class {
37
37
  }
38
38
  };
39
39
  //#endregion
40
+ //#region src/commands/admin.ts
41
+ async function suspensions(_args, _flags, client) {
42
+ return client.call("getSuspensionDetails");
43
+ }
44
+ async function unsuspend(args, _flags, client) {
45
+ const recordId = args[0];
46
+ if (!recordId) throw new Error("unsuspend requires a <record-id> argument");
47
+ return client.call("unsuspend", { record_id: recordId });
48
+ }
49
+ async function violationsList(_args, _flags, client) {
50
+ return client.call("getPolicyViolations");
51
+ }
52
+ async function violationsResolve(args, _flags, client) {
53
+ const recordId = args[0];
54
+ if (!recordId) throw new Error("violations resolve requires a <record-id> argument");
55
+ return client.call("resolvePolicyViolation", { record_id: recordId });
56
+ }
57
+ async function notificationsGet(_args, _flags, client) {
58
+ return client.call("kiwivm/getNotificationPreferences");
59
+ }
60
+ async function notificationsSet(args, _flags, client) {
61
+ const prefs = args[0];
62
+ if (!prefs) throw new Error("notifications set requires a <json> argument");
63
+ return client.call("kiwivm/setNotificationPreferences", { json_notification_preferences: prefs });
64
+ }
65
+ //#endregion
66
+ //#region src/commands/backup.ts
67
+ async function list$1(_args, _flags, client) {
68
+ return client.call("backup/list");
69
+ }
70
+ async function copy(args, _flags, client) {
71
+ const token = args[0];
72
+ if (!token) throw new Error("backup copy requires a <token> argument");
73
+ return client.call("backup/copyToSnapshot", { backupToken: token });
74
+ }
75
+ //#endregion
76
+ //#region src/commands/help.ts
77
+ const HELP = `Usage: kiwivm-cli <command> [<subcommand>] [args...] [--flags...]
78
+
79
+ Auth: --veid <VEID> --api-key <KEY> (or KIWIVM_VEID / KIWIVM_API_KEY env vars)
80
+
81
+ Commands:
82
+
83
+ start Start the VPS
84
+ stop Stop the VPS
85
+ restart Reboot the VPS
86
+ kill Force-stop a stuck VPS
87
+
88
+ info Get service info (plan, IPs, bandwidth, etc.)
89
+ status Get live status (CPU, RAM, disk, uptime)
90
+
91
+ snapshot list List all snapshots
92
+ snapshot create [--desc] Create a new snapshot
93
+ snapshot delete <token> Delete a snapshot
94
+ snapshot restore <token> Restore from snapshot
95
+ snapshot sticky <token> Toggle sticky (--on | --off)
96
+ snapshot export <token> Generate transfer token
97
+ snapshot import <veid> <token> Import snapshot from another instance
98
+
99
+ backup list List automatic backups
100
+ backup copy <token> Convert backup to restorable snapshot
101
+
102
+ os list List available OS templates
103
+ os reinstall <template> Reinstall OS
104
+
105
+ hostname <name> Set new hostname
106
+ password Reset root password
107
+
108
+ ssh-key Show SSH keys
109
+ ssh-key set <keys> Set SSH keys
110
+
111
+ rdns set <ip> <ptr> Set reverse DNS record
112
+
113
+ ipv6 add Assign new IPv6 /64 subnet
114
+ ipv6 delete <subnet> Release IPv6 subnet
115
+
116
+ private-ip list List available private IPs
117
+ private-ip assign [<ip>] Assign private IP (random if omitted)
118
+ private-ip delete <ip> Release private IP
119
+
120
+ iso mount <name> Mount ISO for boot (VM must be off)
121
+ iso unmount Unmount ISO, boot from disk
122
+
123
+ shell exec <command> Execute command synchronously
124
+ shell script <script> Execute script asynchronously
125
+
126
+ migrate locations List available migration locations
127
+ migrate start <location> Start migration to new location
128
+ clone <ip> <password> [--port] Clone from external server (OVZ only)
129
+
130
+ stats usage Get detailed usage statistics
131
+ stats audit Get audit log
132
+ stats rate-limit Check API rate limit status
133
+
134
+ suspensions View suspension details
135
+ unsuspend <record-id> Clear abuse issue and unsuspend
136
+ violations View policy violations
137
+ violations resolve <record-id> Resolve policy violation
138
+
139
+ notifications Get notification preferences
140
+ notifications set <json> Update notification preferences
141
+
142
+ Output: JSON to stdout. Errors to stderr with exit code 1.
143
+ `;
144
+ async function run() {
145
+ return HELP;
146
+ }
147
+ //#endregion
148
+ //#region src/commands/info.ts
149
+ async function info(_args, _flags, client) {
150
+ return client.call("getServiceInfo");
151
+ }
152
+ async function status(_args, _flags, client) {
153
+ return client.call("getLiveServiceInfo");
154
+ }
155
+ //#endregion
156
+ //#region src/commands/iso.ts
157
+ async function mount(args, _flags, client) {
158
+ const iso = args[0];
159
+ if (!iso) throw new Error("iso mount requires a <name> argument");
160
+ return client.call("iso/mount", { iso });
161
+ }
162
+ async function unmount(_args, _flags, client) {
163
+ return client.call("iso/unmount");
164
+ }
165
+ //#endregion
166
+ //#region src/commands/migrate.ts
167
+ async function locations(_args, _flags, client) {
168
+ return client.call("migrate/getLocations");
169
+ }
170
+ async function migrateStart(args, _flags, client) {
171
+ const location = args[0];
172
+ if (!location) throw new Error("migrate start requires a <location> argument");
173
+ return client.call("migrate/start", { location });
174
+ }
175
+ async function clone(args, flags, client) {
176
+ const ip = args[0];
177
+ const password = args[1];
178
+ if (!ip || !password) throw new Error("clone requires both <ip> and <password> arguments");
179
+ return client.call("cloneFromExternalServer", {
180
+ externalServerIP: ip,
181
+ externalServerSSHport: flags["port"] || "22",
182
+ externalServerRootPassword: password
183
+ });
184
+ }
185
+ //#endregion
186
+ //#region src/commands/network.ts
187
+ async function rdnsSet(args, _flags, client) {
188
+ const ip = args[0];
189
+ const ptr = args[1];
190
+ if (!ip || !ptr) throw new Error("rdns set requires both <ip> and <ptr> arguments");
191
+ return client.call("setPTR", {
192
+ ip,
193
+ ptr
194
+ });
195
+ }
196
+ async function ipv6Add(_args, _flags, client) {
197
+ return client.call("ipv6/add");
198
+ }
199
+ async function ipv6Delete(args, _flags, client) {
200
+ const ip = args[0];
201
+ if (!ip) throw new Error("ipv6 delete requires a <subnet> argument");
202
+ return client.call("ipv6/delete", { ip });
203
+ }
204
+ async function privateIpList(_args, _flags, client) {
205
+ return client.call("privateIp/getAvailableIps");
206
+ }
207
+ async function privateIpAssign(args, _flags, client) {
208
+ return client.call("privateIp/assign", { ip: args[0] });
209
+ }
210
+ async function privateIpDelete(args, _flags, client) {
211
+ const ip = args[0];
212
+ if (!ip) throw new Error("private-ip delete requires an <ip> argument");
213
+ return client.call("privateIp/delete", { ip });
214
+ }
215
+ //#endregion
216
+ //#region src/commands/power.ts
217
+ async function start(_args, _flags, client) {
218
+ return client.call("start");
219
+ }
220
+ async function stop(_args, _flags, client) {
221
+ return client.call("stop");
222
+ }
223
+ async function restart(_args, _flags, client) {
224
+ return client.call("restart");
225
+ }
226
+ async function kill(_args, _flags, client) {
227
+ return client.call("kill");
228
+ }
229
+ //#endregion
230
+ //#region src/commands/shell.ts
231
+ async function exec(args, _flags, client) {
232
+ const command = args[0];
233
+ if (!command) throw new Error("shell exec requires a <command> argument");
234
+ return client.call("basicShell/exec", { command });
235
+ }
236
+ async function script(args, _flags, client) {
237
+ const scriptContent = args[0];
238
+ if (!scriptContent) throw new Error("shell script requires a <script> argument");
239
+ return client.call("shellScript/exec", { script: scriptContent });
240
+ }
241
+ //#endregion
242
+ //#region src/commands/snapshot.ts
243
+ async function list(_args, _flags, client) {
244
+ return client.call("snapshot/list");
245
+ }
246
+ async function create(_args, flags, client) {
247
+ return client.call("snapshot/create", { description: flags["desc"] || flags["description"] });
248
+ }
249
+ async function deleteSnapshot(args, _flags, client) {
250
+ const token = args[0];
251
+ if (!token) throw new Error("snapshot delete requires a <token> argument");
252
+ return client.call("snapshot/delete", { snapshot: token });
253
+ }
254
+ async function restore(args, _flags, client) {
255
+ const token = args[0];
256
+ if (!token) throw new Error("snapshot restore requires a <token> argument");
257
+ return client.call("snapshot/restore", { snapshot: token });
258
+ }
259
+ async function sticky(args, flags, client) {
260
+ const token = args[0];
261
+ if (!token) throw new Error("snapshot sticky requires a <token> argument");
262
+ if (flags["on"] !== void 0 && flags["off"] !== void 0) throw new Error("snapshot sticky requires exactly one of --on or --off");
263
+ if (flags["on"] === void 0 && flags["off"] === void 0) throw new Error("snapshot sticky requires exactly one of --on or --off");
264
+ const stickyVal = flags["on"] !== void 0 ? 1 : 0;
265
+ return client.call("snapshot/toggleSticky", {
266
+ snapshot: token,
267
+ sticky: stickyVal
268
+ });
269
+ }
270
+ async function exportSnapshot(args, _flags, client) {
271
+ const token = args[0];
272
+ if (!token) throw new Error("snapshot export requires a <token> argument");
273
+ return client.call("snapshot/export", { snapshot: token });
274
+ }
275
+ async function importSnapshot(args, _flags, client) {
276
+ const sourceVeid = args[0];
277
+ const sourceToken = args[1];
278
+ if (!sourceVeid || !sourceToken) throw new Error("snapshot import requires both <sourceVeid> and <sourceToken> arguments");
279
+ return client.call("snapshot/import", {
280
+ sourceVeid,
281
+ sourceToken
282
+ });
283
+ }
284
+ //#endregion
285
+ //#region src/commands/stats.ts
286
+ async function usage(_args, _flags, client) {
287
+ return client.call("getRawUsageStats");
288
+ }
289
+ async function audit(_args, _flags, client) {
290
+ return client.call("getAuditLog");
291
+ }
292
+ async function rateLimit(_args, _flags, client) {
293
+ return client.call("getRateLimitStatus");
294
+ }
295
+ //#endregion
296
+ //#region src/commands/system.ts
297
+ async function hostname(args, _flags, client) {
298
+ const name = args[0];
299
+ if (!name) throw new Error("hostname requires a <name> argument");
300
+ return client.call("setHostname", { newHostname: name });
301
+ }
302
+ async function password(_args, _flags, client) {
303
+ return client.call("resetRootPassword");
304
+ }
305
+ async function osList(_args, _flags, client) {
306
+ return client.call("getAvailableOS");
307
+ }
308
+ async function osReinstall(args, _flags, client) {
309
+ const os = args[0];
310
+ if (!os) throw new Error("os reinstall requires a <template> argument");
311
+ return client.call("reinstallOS", { os });
312
+ }
313
+ async function sshKeyShow(_args, _flags, client) {
314
+ return client.call("getSshKeys");
315
+ }
316
+ async function sshKeySet(args, _flags, client) {
317
+ const keys = args[0];
318
+ if (!keys) throw new Error("ssh-key set requires a <keys> argument");
319
+ return client.call("updateSshKeys", { ssh_keys: keys });
320
+ }
321
+ //#endregion
40
322
  //#region src/index.ts
41
323
  function parseFlags(args) {
42
324
  const flags = {};
@@ -56,7 +338,7 @@ function parseFlags(args) {
56
338
  i++;
57
339
  } else {
58
340
  const key = arg.slice(2);
59
- flags[toCamelCase(key)] = "";
341
+ flags[toCamelCase(key)] = "1";
60
342
  }
61
343
  }
62
344
  }
@@ -92,12 +374,9 @@ function getCommandFromArgs(args) {
92
374
  }
93
375
  async function main() {
94
376
  const { positional, flags } = getCommandFromArgs(process.argv.slice(2));
95
- const category = positional[0] ?? "";
96
- const action = positional[1] ?? "";
97
- if (category === "" || category === "help") {
98
- const { run } = await import("./help-Dk-WApoi.mjs");
99
- const text = await run();
100
- console.log(text);
377
+ const command = positional[0] ?? "";
378
+ if (command === "" || command === "help") {
379
+ console.log(await run());
101
380
  return;
102
381
  }
103
382
  const flagVeid = flags["veid"];
@@ -117,51 +396,92 @@ async function main() {
117
396
  });
118
397
  try {
119
398
  let result;
120
- switch (category) {
121
- case "power": {
122
- const { run } = await import("./power-CDg0Mx1A.mjs");
123
- result = await run(action, handlerFlags, client);
124
- break;
125
- }
126
- case "info": {
127
- const { run } = await import("./info-DKExtFYH.mjs");
128
- result = await run(action, handlerFlags, client);
129
- break;
130
- }
131
- case "snapshot": {
132
- const { run } = await import("./snapshot-LO_ufoj5.mjs");
133
- result = await run(action, handlerFlags, client);
134
- break;
135
- }
136
- case "backup": {
137
- const { run } = await import("./backup-D1UJ4aap.mjs");
138
- result = await run(action, handlerFlags, client);
139
- break;
140
- }
141
- case "system": {
142
- const { run } = await import("./system-Bl-dsqX9.mjs");
143
- result = await run(action, handlerFlags, client);
144
- break;
399
+ const route = {
400
+ start,
401
+ stop,
402
+ restart,
403
+ kill,
404
+ info,
405
+ status,
406
+ hostname,
407
+ password,
408
+ suspensions,
409
+ unsuspend,
410
+ clone,
411
+ snapshot: {
412
+ list,
413
+ create,
414
+ delete: deleteSnapshot,
415
+ restore,
416
+ sticky,
417
+ export: exportSnapshot,
418
+ import: importSnapshot
419
+ },
420
+ backup: {
421
+ list: list$1,
422
+ copy
423
+ },
424
+ os: {
425
+ list: osList,
426
+ reinstall: osReinstall
427
+ },
428
+ "ssh-key": {
429
+ default: sshKeyShow,
430
+ set: sshKeySet
431
+ },
432
+ rdns: { set: rdnsSet },
433
+ ipv6: {
434
+ add: ipv6Add,
435
+ delete: ipv6Delete
436
+ },
437
+ "private-ip": {
438
+ list: privateIpList,
439
+ assign: privateIpAssign,
440
+ delete: privateIpDelete
441
+ },
442
+ iso: {
443
+ mount,
444
+ unmount
445
+ },
446
+ shell: {
447
+ exec,
448
+ script
449
+ },
450
+ migrate: {
451
+ locations,
452
+ start: migrateStart
453
+ },
454
+ stats: {
455
+ usage,
456
+ audit,
457
+ "rate-limit": rateLimit
458
+ },
459
+ violations: {
460
+ default: violationsList,
461
+ resolve: violationsResolve
462
+ },
463
+ notifications: {
464
+ default: notificationsGet,
465
+ set: notificationsSet
145
466
  }
146
- case "network": {
147
- const { run } = await import("./network-1ycEIJqT.mjs");
148
- result = await run(action, handlerFlags, client);
149
- break;
150
- }
151
- case "monitoring": {
152
- const { run } = await import("./monitoring-BSuv8fj9.mjs");
153
- result = await run(action, handlerFlags, client);
154
- break;
155
- }
156
- case "admin": {
157
- const { run } = await import("./admin-fOud1ZmX.mjs");
158
- result = await run(action, handlerFlags, client);
159
- break;
160
- }
161
- default:
162
- console.error(`Unknown category: ${category}`);
163
- console.error("Run 'kiwivm-cli help' for usage.");
467
+ }[command];
468
+ if (!route) {
469
+ console.error(`Unknown command: ${command}`);
470
+ console.error("Run 'kiwivm-cli help' for usage.");
471
+ process.exit(1);
472
+ }
473
+ if (typeof route === "function") result = await route(positional.slice(1), handlerFlags, client);
474
+ else {
475
+ const subcommand = positional[1];
476
+ let handler;
477
+ if (subcommand && subcommand in route) handler = route[subcommand];
478
+ else if (!subcommand && route.default) handler = route.default;
479
+ if (!handler) {
480
+ const valid = Object.keys(route).filter((k) => k !== "default").join(", ");
481
+ console.error(`Unknown subcommand for ${command}: ${subcommand || "(none)"}. Valid: ${valid}`);
164
482
  process.exit(1);
483
+ }
484
+ result = await handler(positional.slice(2), handlerFlags, client);
165
485
  }
166
486
  console.log(JSON.stringify(result));
167
487
  } catch (error) {