@cabaltrading/cli 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3872,7 +3872,7 @@ async function createServer() {
3872
3872
  const res = await fetch(`${baseUrl}/skill.json`);
3873
3873
  if (!res.ok)
3874
3874
  throw new Error(`Failed to fetch skill.json: ${res.status}`);
3875
- const data = await res.json();
3875
+ const data = z22.record(z22.string(), z22.unknown()).parse(await res.json());
3876
3876
  return textResult(data);
3877
3877
  } catch (error) {
3878
3878
  return textResult(toStructuredError(error));
@@ -3971,15 +3971,14 @@ async function loginCommand(options = {}) {
3971
3971
  body: JSON.stringify(options.ref ? { referralCode: options.ref } : {})
3972
3972
  });
3973
3973
  if (!res.ok) {
3974
- const data2 = await res.json().catch(() => ({}));
3975
3974
  spinner.fail("Failed to get login code");
3976
- console.log(chalk2.red(` ${data2?.error?.message || "Server error"}`));
3975
+ console.log(chalk2.red(" Server error"));
3977
3976
  return false;
3978
3977
  }
3979
- const { data } = await res.json();
3980
- deviceCode = data.deviceCode;
3981
- userCode = data.userCode;
3982
- verificationUrl = data.verificationUrl;
3978
+ const parsed = deviceCodeCreateResponseSchema.parse(await res.json());
3979
+ deviceCode = parsed.data.deviceCode;
3980
+ userCode = parsed.data.userCode;
3981
+ verificationUrl = parsed.data.verificationUrl;
3983
3982
  spinner.stop();
3984
3983
  } catch (error) {
3985
3984
  spinner.fail("Failed to connect to server");
@@ -4013,7 +4012,8 @@ async function loginCommand(options = {}) {
4013
4012
  const res = await fetch(`${apiBase}/auth/device-code/poll?code=${deviceCode}`);
4014
4013
  if (!res.ok)
4015
4014
  continue;
4016
- const { data } = await res.json();
4015
+ const parsed = deviceCodePollResponseSchema.parse(await res.json());
4016
+ const data = parsed.data;
4017
4017
  if (data.status === "completed") {
4018
4018
  pollSpinner.succeed(chalk2.green("Logged in!"));
4019
4019
  console.log("");
@@ -4066,12 +4066,28 @@ function sleep(ms) {
4066
4066
  }
4067
4067
  var POLL_INTERVAL_MS = 5000, TIMEOUT_MS;
4068
4068
  var init_login = __esm(() => {
4069
+ init_src();
4069
4070
  init_browser();
4070
4071
  init_env();
4071
4072
  init_errors2();
4072
4073
  TIMEOUT_MS = 10 * 60 * 1000;
4073
4074
  });
4074
4075
 
4076
+ // src/lib/tty.ts
4077
+ function isTTY() {
4078
+ return !!process.stdin.isTTY;
4079
+ }
4080
+ function exitMissingFlags(command, missing) {
4081
+ console.error(`Error: this command requires an interactive terminal, or provide these flags:
4082
+ `);
4083
+ for (const { flag, description } of missing) {
4084
+ console.error(` ${flag.padEnd(22)} ${description}`);
4085
+ }
4086
+ console.error(`
4087
+ Example: cabal-cli ${command} ${missing.map((f) => f.flag + " <value>").join(" ")}`);
4088
+ process.exit(1);
4089
+ }
4090
+
4075
4091
  // src/commands/onboard.ts
4076
4092
  var exports_onboard = {};
4077
4093
  __export(exports_onboard, {
@@ -4090,21 +4106,21 @@ async function onboardCommand(options) {
4090
4106
  process.exit(1);
4091
4107
  }
4092
4108
  if (step === "connect") {
4093
- await stepConnect();
4109
+ await stepConnect(options);
4094
4110
  return;
4095
4111
  }
4096
4112
  const client = requireClient();
4097
4113
  if (step === "profile")
4098
- await stepProfile(client);
4114
+ await stepProfile(client, options);
4099
4115
  else if (step === "avatar")
4100
- await stepAvatar(client);
4116
+ await stepAvatar(client, options);
4101
4117
  else if (step === "verify")
4102
- await stepVerify(client);
4118
+ await stepVerify(client, options);
4103
4119
  return;
4104
4120
  }
4105
- await runWizard();
4121
+ await runWizard(options);
4106
4122
  }
4107
- async function runWizard() {
4123
+ async function runWizard(options) {
4108
4124
  if (isConfigured()) {
4109
4125
  const client2 = requireClient();
4110
4126
  let steps;
@@ -4113,14 +4129,27 @@ async function runWizard() {
4113
4129
  } catch {
4114
4130
  console.log(chalk3.yellow(`Saved API key is invalid. Starting fresh.
4115
4131
  `));
4116
- const newClient = await stepConnect();
4132
+ const newClient = await stepConnect(options);
4117
4133
  if (!newClient)
4118
4134
  return;
4119
- await continueWizard(newClient);
4135
+ await continueWizard(newClient, 1, options);
4120
4136
  return;
4121
4137
  }
4122
4138
  const allDone = steps.every((s) => s.done);
4123
4139
  const firstIncomplete = steps.findIndex((s) => !s.done);
4140
+ if (options.confirm) {
4141
+ if (allDone) {
4142
+ console.log(chalk3.green.bold("All onboarding steps already complete."));
4143
+ return;
4144
+ }
4145
+ await continueWizard(client2, firstIncomplete, options);
4146
+ return;
4147
+ }
4148
+ if (!isTTY()) {
4149
+ exitMissingFlags("onboard", [
4150
+ { flag: "-y, --confirm", description: "Auto-continue through wizard (required in non-TTY)" }
4151
+ ]);
4152
+ }
4124
4153
  printProgress(steps);
4125
4154
  if (allDone) {
4126
4155
  const { action: action2 } = await inquirer.prompt([
@@ -4136,10 +4165,10 @@ async function runWizard() {
4136
4165
  ]);
4137
4166
  if (action2 === "quit")
4138
4167
  return;
4139
- const newClient = await stepConnect();
4168
+ const newClient = await stepConnect(options);
4140
4169
  if (!newClient)
4141
4170
  return;
4142
- await continueWizard(newClient);
4171
+ await continueWizard(newClient, 1, options);
4143
4172
  return;
4144
4173
  }
4145
4174
  const { action } = await inquirer.prompt([
@@ -4157,25 +4186,30 @@ async function runWizard() {
4157
4186
  if (action === "quit")
4158
4187
  return;
4159
4188
  if (action === "reconfigure") {
4160
- const newClient = await stepConnect();
4189
+ const newClient = await stepConnect(options);
4161
4190
  if (!newClient)
4162
4191
  return;
4163
- await continueWizard(newClient);
4192
+ await continueWizard(newClient, 1, options);
4164
4193
  return;
4165
4194
  }
4166
- await continueWizard(client2, firstIncomplete);
4195
+ await continueWizard(client2, firstIncomplete, options);
4167
4196
  return;
4168
4197
  }
4169
- const client = await stepConnect();
4198
+ const client = await stepConnect(options);
4170
4199
  if (!client)
4171
4200
  return;
4172
- await continueWizard(client);
4201
+ await continueWizard(client, 1, options);
4173
4202
  }
4174
- async function continueWizard(client, startIdx = 1) {
4203
+ async function continueWizard(client, startIdx = 1, options = {}) {
4175
4204
  for (let i = startIdx;i < STEP_ORDER.length; i++) {
4176
4205
  const step = STEP_ORDER[i];
4177
4206
  console.log("");
4178
- if (i > startIdx) {
4207
+ if (i > startIdx && !options.confirm) {
4208
+ if (!isTTY()) {
4209
+ exitMissingFlags("onboard", [
4210
+ { flag: "-y, --confirm", description: "Auto-continue through wizard (required in non-TTY)" }
4211
+ ]);
4212
+ }
4179
4213
  const { action } = await inquirer.prompt([
4180
4214
  {
4181
4215
  type: "list",
@@ -4194,22 +4228,38 @@ async function continueWizard(client, startIdx = 1) {
4194
4228
  continue;
4195
4229
  }
4196
4230
  if (step === "profile")
4197
- await stepProfile(client);
4231
+ await stepProfile(client, options);
4198
4232
  else if (step === "avatar")
4199
- await stepAvatar(client);
4233
+ await stepAvatar(client, options);
4200
4234
  else if (step === "verify")
4201
- await stepVerify(client);
4235
+ await stepVerify(client, options);
4202
4236
  }
4203
4237
  console.log("");
4204
4238
  console.log(chalk3.green.bold("Onboarding complete!"));
4205
4239
  console.log(chalk3.dim("Run `cabal-cli status` to check your agent.\n"));
4206
4240
  }
4207
- async function stepConnect(apiKeyArg) {
4241
+ async function stepConnect(options, apiKeyArg) {
4208
4242
  console.log(chalk3.bold(`
4209
4243
  Step 1: Connect
4210
4244
  `));
4211
4245
  let apiKey = apiKeyArg;
4212
4246
  if (!apiKey) {
4247
+ if (isConfigured()) {
4248
+ const creds = getCredentials();
4249
+ if (creds.CABAL_API_KEY) {
4250
+ try {
4251
+ const client = new AgentClient(creds.CABAL_API_KEY, creds.NEXT_PUBLIC_SITE_URL);
4252
+ await client.getStatus();
4253
+ console.log(chalk3.dim(` Already connected. Using existing API key.
4254
+ `));
4255
+ return client;
4256
+ } catch {}
4257
+ }
4258
+ }
4259
+ if (!isTTY()) {
4260
+ console.error("Error: No valid API key found. Run `cabal-cli login` in a terminal first.");
4261
+ process.exit(1);
4262
+ }
4213
4263
  console.log(" To connect your agent, you need an API key.");
4214
4264
  console.log(" If you already have one, paste it below.");
4215
4265
  console.log("");
@@ -4300,7 +4350,7 @@ async function stepConnect(apiKeyArg) {
4300
4350
  return null;
4301
4351
  }
4302
4352
  }
4303
- async function stepProfile(client) {
4353
+ async function stepProfile(client, options = {}) {
4304
4354
  console.log(chalk3.bold(`
4305
4355
  Step 2: Profile
4306
4356
  `));
@@ -4315,61 +4365,86 @@ async function stepProfile(client) {
4315
4365
  printCliError(error);
4316
4366
  return;
4317
4367
  }
4318
- console.log(chalk3.dim(` Press Enter to keep current values.
4368
+ if (!isTTY()) {
4369
+ const missing = [];
4370
+ if (!options.name)
4371
+ missing.push({ flag: "--name", description: "Display name" });
4372
+ if (!options.handle)
4373
+ missing.push({ flag: "--handle", description: "Unique handle" });
4374
+ if (!options.bio)
4375
+ missing.push({ flag: "--bio", description: "Agent bio" });
4376
+ if (!options.strategy)
4377
+ missing.push({ flag: "--strategy", description: "Trading strategy" });
4378
+ if (missing.length > 0)
4379
+ exitMissingFlags("onboard --step profile", missing);
4380
+ }
4381
+ let name, handle, bio, strategy;
4382
+ if (options.name && options.handle && options.bio && options.strategy) {
4383
+ name = options.name;
4384
+ handle = options.handle;
4385
+ bio = options.bio;
4386
+ strategy = options.strategy;
4387
+ } else {
4388
+ console.log(chalk3.dim(` Press Enter to keep current values.
4319
4389
  `));
4320
- const answers = await inquirer.prompt([
4321
- {
4322
- type: "input",
4323
- name: "name",
4324
- message: "Display name:",
4325
- default: agent2.name
4326
- },
4327
- {
4328
- type: "input",
4329
- name: "handle",
4330
- message: "Handle (unique):",
4331
- default: agent2.handle ?? undefined,
4332
- validate: async (input) => {
4333
- if (!input)
4334
- return true;
4335
- if (!/^[a-zA-Z0-9_-]+$/.test(input))
4336
- return "Handle must be alphanumeric with _ or -";
4337
- if (input.length > 120)
4338
- return "Handle must be 120 characters or fewer";
4339
- if (input === agent2.handle)
4340
- return true;
4341
- try {
4342
- const result2 = await client.checkHandle(input);
4343
- if (!result2.available)
4344
- return result2.reason ?? "Handle is not available";
4345
- return true;
4346
- } catch {
4347
- return "Could not validate handle — try again";
4390
+ const answers = await inquirer.prompt([
4391
+ {
4392
+ type: "input",
4393
+ name: "name",
4394
+ message: "Display name:",
4395
+ default: options.name ?? agent2.name
4396
+ },
4397
+ {
4398
+ type: "input",
4399
+ name: "handle",
4400
+ message: "Handle (unique):",
4401
+ default: options.handle ?? agent2.handle ?? undefined,
4402
+ validate: async (input) => {
4403
+ if (!input)
4404
+ return true;
4405
+ if (!/^[a-zA-Z0-9_-]+$/.test(input))
4406
+ return "Handle must be alphanumeric with _ or -";
4407
+ if (input.length > 120)
4408
+ return "Handle must be 120 characters or fewer";
4409
+ if (input === agent2.handle)
4410
+ return true;
4411
+ try {
4412
+ const result2 = await client.checkHandle(input);
4413
+ if (!result2.available)
4414
+ return result2.reason ?? "Handle is not available";
4415
+ return true;
4416
+ } catch {
4417
+ return "Could not validate handle — try again";
4418
+ }
4348
4419
  }
4420
+ },
4421
+ {
4422
+ type: "input",
4423
+ name: "bio",
4424
+ message: "Bio:",
4425
+ default: options.bio ?? agent2.bio ?? undefined
4426
+ },
4427
+ {
4428
+ type: "input",
4429
+ name: "strategy",
4430
+ message: "Strategy:",
4431
+ default: options.strategy ?? agent2.strategy ?? undefined
4349
4432
  }
4350
- },
4351
- {
4352
- type: "input",
4353
- name: "bio",
4354
- message: "Bio:",
4355
- default: agent2.bio ?? undefined
4356
- },
4357
- {
4358
- type: "input",
4359
- name: "strategy",
4360
- message: "Strategy:",
4361
- default: agent2.strategy ?? undefined
4362
- }
4363
- ]);
4433
+ ]);
4434
+ name = answers.name;
4435
+ handle = answers.handle;
4436
+ bio = answers.bio;
4437
+ strategy = answers.strategy;
4438
+ }
4364
4439
  const updates = {};
4365
- if (answers.name && answers.name !== agent2.name)
4366
- updates.name = answers.name;
4367
- if (answers.handle && answers.handle !== agent2.handle)
4368
- updates.handle = answers.handle;
4369
- if (answers.bio && answers.bio !== agent2.bio)
4370
- updates.bio = answers.bio;
4371
- if (answers.strategy && answers.strategy !== agent2.strategy)
4372
- updates.strategy = answers.strategy;
4440
+ if (name && name !== agent2.name)
4441
+ updates.name = name;
4442
+ if (handle && handle !== agent2.handle)
4443
+ updates.handle = handle;
4444
+ if (bio && bio !== agent2.bio)
4445
+ updates.bio = bio;
4446
+ if (strategy && strategy !== agent2.strategy)
4447
+ updates.strategy = strategy;
4373
4448
  if (Object.keys(updates).length === 0) {
4374
4449
  console.log(chalk3.dim(`
4375
4450
  No changes made.
@@ -4386,10 +4461,62 @@ async function stepProfile(client) {
4386
4461
  printCliError(error);
4387
4462
  }
4388
4463
  }
4389
- async function stepAvatar(client) {
4464
+ async function stepAvatar(client, options = {}) {
4390
4465
  console.log(chalk3.bold(`
4391
4466
  Step 3: Avatar
4392
4467
  `));
4468
+ if (options.skipAvatar) {
4469
+ console.log(chalk3.dim(` Skipped avatar step.
4470
+ `));
4471
+ return;
4472
+ }
4473
+ if (options.avatarId) {
4474
+ const selectSpinner = ora2("Selecting avatar...").start();
4475
+ try {
4476
+ await client.selectAvatar(options.avatarId);
4477
+ selectSpinner.succeed("Avatar updated!");
4478
+ } catch (error) {
4479
+ selectSpinner.fail("Failed to select avatar");
4480
+ printCliError(error);
4481
+ }
4482
+ console.log("");
4483
+ return;
4484
+ }
4485
+ if (options.avatar) {
4486
+ const genSpinner = ora2("Generating avatar...").start();
4487
+ try {
4488
+ const result2 = await client.generateAvatar(options.avatar);
4489
+ genSpinner.succeed("Avatar generated!");
4490
+ console.log("");
4491
+ if (result2.generationId) {
4492
+ console.log(` ${chalk3.dim("Generation ID:")} ${result2.generationId}`);
4493
+ }
4494
+ console.log(` ${chalk3.dim("Image URL:")} ${chalk3.cyan(result2.imageUrl)}`);
4495
+ console.log(` ${chalk3.dim("Credits left:")} ${result2.creditsRemaining}`);
4496
+ if (result2.generationId) {
4497
+ const selectSpinner = ora2("Setting avatar...").start();
4498
+ try {
4499
+ await client.selectAvatar(result2.generationId);
4500
+ selectSpinner.succeed("Avatar set!");
4501
+ } catch (err2) {
4502
+ selectSpinner.fail("Failed to set avatar");
4503
+ printCliError(err2);
4504
+ }
4505
+ }
4506
+ } catch (error) {
4507
+ genSpinner.fail("Failed to generate avatar");
4508
+ printCliError(error);
4509
+ }
4510
+ console.log("");
4511
+ return;
4512
+ }
4513
+ if (!isTTY()) {
4514
+ exitMissingFlags("onboard --step avatar", [
4515
+ { flag: "--avatar", description: "Generate avatar from this description" },
4516
+ { flag: "--avatar-id", description: "Select an existing avatar generation by ID" },
4517
+ { flag: "--skip-avatar", description: "Skip avatar step entirely" }
4518
+ ]);
4519
+ }
4393
4520
  const spinner = ora2("Fetching avatar credits...").start();
4394
4521
  try {
4395
4522
  const credits = await client.getAvatarCredits();
@@ -4504,20 +4631,36 @@ async function stepAvatar(client) {
4504
4631
  printCliError(error);
4505
4632
  }
4506
4633
  }
4507
- async function stepVerify(client) {
4634
+ async function stepVerify(client, options = {}) {
4508
4635
  console.log(chalk3.bold(`
4509
4636
  Step 4: Verify (optional)
4510
4637
  `));
4511
- console.log(" Claim your agent by tweeting a verification post.");
4512
- console.log(` ${chalk3.dim("Dashboard:")} ${chalk3.cyan(DASHBOARD_URL)}`);
4513
- console.log("");
4514
- const { tweetUrl } = await inquirer.prompt([
4515
- {
4516
- type: "input",
4517
- name: "tweetUrl",
4518
- message: "Tweet URL (or press Enter to skip):"
4519
- }
4520
- ]);
4638
+ if (options.skipVerify) {
4639
+ console.log(chalk3.dim(` Skipped verification.
4640
+ `));
4641
+ return;
4642
+ }
4643
+ let tweetUrl;
4644
+ if (options.tweetUrl) {
4645
+ tweetUrl = options.tweetUrl;
4646
+ } else if (!isTTY()) {
4647
+ exitMissingFlags("onboard --step verify", [
4648
+ { flag: "--tweet-url", description: "Tweet URL for verification" },
4649
+ { flag: "--skip-verify", description: "Skip verification step" }
4650
+ ]);
4651
+ } else {
4652
+ console.log(" Claim your agent by tweeting a verification post.");
4653
+ console.log(` ${chalk3.dim("Dashboard:")} ${chalk3.cyan(DASHBOARD_URL)}`);
4654
+ console.log("");
4655
+ const answer = await inquirer.prompt([
4656
+ {
4657
+ type: "input",
4658
+ name: "tweetUrl",
4659
+ message: "Tweet URL (or press Enter to skip):"
4660
+ }
4661
+ ]);
4662
+ tweetUrl = answer.tweetUrl;
4663
+ }
4521
4664
  if (!tweetUrl.trim()) {
4522
4665
  console.log(chalk3.dim("\n Skipped verification. You can verify later with `cabal-cli onboard --step verify`.\n"));
4523
4666
  return;
@@ -4747,6 +4890,13 @@ import chalk5 from "chalk";
4747
4890
  import ora4 from "ora";
4748
4891
  import inquirer2 from "inquirer";
4749
4892
  import { z as z23 } from "zod";
4893
+ async function promptOrFlag(flagValue, promptFn) {
4894
+ if (flagValue !== undefined)
4895
+ return flagValue;
4896
+ if (!isTTY())
4897
+ throw new Error("__missing_flag__");
4898
+ return promptFn();
4899
+ }
4750
4900
  async function tradeCommand(options) {
4751
4901
  if (!isConfigured()) {
4752
4902
  console.log(chalk5.red("Error: No API key found. Run `cabal-cli init` first."));
@@ -4758,31 +4908,52 @@ async function tradeCommand(options) {
4758
4908
  process.exit(1);
4759
4909
  }
4760
4910
  let request;
4761
- const chain = options.chain || (await inquirer2.prompt([{
4762
- type: "list",
4763
- name: "chain",
4764
- message: "Chain:",
4765
- choices: ["solana", "hyperliquid"]
4766
- }])).chain;
4911
+ let chain;
4912
+ try {
4913
+ chain = await promptOrFlag(options.chain, async () => (await inquirer2.prompt([{
4914
+ type: "list",
4915
+ name: "chain",
4916
+ message: "Chain:",
4917
+ choices: ["solana", "hyperliquid"]
4918
+ }])).chain);
4919
+ } catch {
4920
+ exitMissingFlags("trade", [
4921
+ { flag: "-c, --chain", description: "Chain: solana or hyperliquid" }
4922
+ ]);
4923
+ }
4767
4924
  if (chain === "solana") {
4768
- const inputToken = options.input || (await inquirer2.prompt([{
4925
+ if (!isTTY()) {
4926
+ const missing = [];
4927
+ if (!options.input)
4928
+ missing.push({ flag: "-i, --input", description: "Input token (e.g. SOL, USDC)" });
4929
+ if (!options.output)
4930
+ missing.push({ flag: "-o, --output", description: "Output token (e.g. PEPE, BONK)" });
4931
+ if (!options.amount)
4932
+ missing.push({ flag: "-a, --amount", description: "Amount of input token" });
4933
+ if (!options.confirm)
4934
+ missing.push({ flag: "-y, --confirm", description: "Skip confirmation prompt" });
4935
+ if (missing.length > 0)
4936
+ exitMissingFlags("trade --chain solana", missing);
4937
+ }
4938
+ const inputToken = await promptOrFlag(options.input, async () => (await inquirer2.prompt([{
4769
4939
  type: "input",
4770
4940
  name: "value",
4771
4941
  message: "Input token (e.g. SOL, USDC):",
4772
4942
  validate: (v) => v.trim() ? true : "Required"
4773
- }])).value;
4774
- const outputToken = options.output || (await inquirer2.prompt([{
4943
+ }])).value);
4944
+ const outputToken = await promptOrFlag(options.output, async () => (await inquirer2.prompt([{
4775
4945
  type: "input",
4776
4946
  name: "value",
4777
4947
  message: "Output token (e.g. PEPE, BONK):",
4778
4948
  validate: (v) => v.trim() ? true : "Required"
4779
- }])).value;
4780
- const amount = options.amount ? parseFloat(options.amount) : parseFloat((await inquirer2.prompt([{
4949
+ }])).value);
4950
+ const amountStr = options.amount ?? await promptOrFlag(undefined, async () => (await inquirer2.prompt([{
4781
4951
  type: "input",
4782
4952
  name: "value",
4783
4953
  message: `Amount of ${inputToken} to swap:`,
4784
4954
  validate: (v) => parseFloat(v) > 0 ? true : "Must be a positive number"
4785
4955
  }])).value);
4956
+ const amount = parseFloat(amountStr);
4786
4957
  request = {
4787
4958
  chain: "solana",
4788
4959
  inputToken: inputToken.trim().toUpperCase(),
@@ -4791,34 +4962,49 @@ async function tradeCommand(options) {
4791
4962
  ...options.model && { model: modelSchema.parse(options.model) }
4792
4963
  };
4793
4964
  } else if (chain === "hyperliquid") {
4794
- const coin = options.coin || (await inquirer2.prompt([{
4965
+ if (!isTTY()) {
4966
+ const missing = [];
4967
+ if (!options.coin)
4968
+ missing.push({ flag: "--coin", description: "Coin symbol (e.g. BTC, ETH)" });
4969
+ if (!options.side)
4970
+ missing.push({ flag: "--side", description: "Side: buy or sell" });
4971
+ if (!options.size)
4972
+ missing.push({ flag: "--size", description: "Position size" });
4973
+ if (!options.confirm)
4974
+ missing.push({ flag: "-y, --confirm", description: "Skip confirmation prompt" });
4975
+ if (missing.length > 0)
4976
+ exitMissingFlags("trade --chain hyperliquid", missing);
4977
+ }
4978
+ const coin = await promptOrFlag(options.coin, async () => (await inquirer2.prompt([{
4795
4979
  type: "input",
4796
4980
  name: "value",
4797
4981
  message: "Coin (e.g. BTC, ETH):",
4798
4982
  validate: (v) => v.trim() ? true : "Required"
4799
- }])).value;
4800
- const rawSide = options.side || (await inquirer2.prompt([{
4983
+ }])).value);
4984
+ const rawSide = await promptOrFlag(options.side, async () => (await inquirer2.prompt([{
4801
4985
  type: "list",
4802
4986
  name: "value",
4803
4987
  message: "Side:",
4804
4988
  choices: ["buy", "sell"]
4805
- }])).value;
4989
+ }])).value);
4806
4990
  const side = z23.enum(["buy", "sell"]).parse(rawSide);
4807
- const size = options.size ? parseFloat(options.size) : parseFloat((await inquirer2.prompt([{
4991
+ const sizeStr = options.size ?? await promptOrFlag(undefined, async () => (await inquirer2.prompt([{
4808
4992
  type: "input",
4809
4993
  name: "value",
4810
4994
  message: "Size:",
4811
4995
  validate: (v) => parseFloat(v) > 0 ? true : "Must be a positive number"
4812
4996
  }])).value);
4997
+ const size = parseFloat(sizeStr);
4813
4998
  const orderType = z23.enum(["limit", "market"]).parse(options.orderType || "market");
4814
4999
  let price;
4815
5000
  if (orderType === "limit") {
4816
- price = options.price ? parseFloat(options.price) : parseFloat((await inquirer2.prompt([{
5001
+ const priceStr = options.price ?? await promptOrFlag(undefined, async () => (await inquirer2.prompt([{
4817
5002
  type: "input",
4818
5003
  name: "value",
4819
5004
  message: "Limit price:",
4820
5005
  validate: (v) => parseFloat(v) > 0 ? true : "Must be a positive number"
4821
5006
  }])).value);
5007
+ price = parseFloat(priceStr);
4822
5008
  }
4823
5009
  request = {
4824
5010
  chain: "hyperliquid",
@@ -4846,15 +5032,22 @@ async function tradeCommand(options) {
4846
5032
  console.log(` ${chalk5.dim("Price:")} $${request.price}`);
4847
5033
  }
4848
5034
  console.log("");
4849
- const { confirm } = await inquirer2.prompt([{
4850
- type: "confirm",
4851
- name: "confirm",
4852
- message: "Execute this trade?",
4853
- default: false
4854
- }]);
4855
- if (!confirm) {
4856
- console.log(chalk5.dim("Trade cancelled."));
4857
- return;
5035
+ if (!options.confirm) {
5036
+ if (!isTTY()) {
5037
+ exitMissingFlags("trade", [
5038
+ { flag: "-y, --confirm", description: "Skip confirmation prompt" }
5039
+ ]);
5040
+ }
5041
+ const { confirm } = await inquirer2.prompt([{
5042
+ type: "confirm",
5043
+ name: "confirm",
5044
+ message: "Execute this trade?",
5045
+ default: false
5046
+ }]);
5047
+ if (!confirm) {
5048
+ console.log(chalk5.dim("Trade cancelled."));
5049
+ return;
5050
+ }
4858
5051
  }
4859
5052
  const spinner = ora4("Executing trade...").start();
4860
5053
  try {
@@ -5449,6 +5642,7 @@ import chalk14 from "chalk";
5449
5642
  import ora13 from "ora";
5450
5643
  import fs2 from "fs";
5451
5644
  import path2 from "path";
5645
+ import { z as z26 } from "zod";
5452
5646
  function getBaseUrl() {
5453
5647
  const creds = getCredentials();
5454
5648
  return creds.NEXT_PUBLIC_SITE_URL || "https://cabal.trading";
@@ -5459,8 +5653,7 @@ async function fetchSkillJson() {
5459
5653
  if (!res.ok) {
5460
5654
  throw new Error(`Failed to fetch skill manifest: ${res.status} ${res.statusText}`);
5461
5655
  }
5462
- const data = await res.json();
5463
- return { files: data.files || {}, descriptions: data.descriptions || {} };
5656
+ return skillManifestSchema.parse(await res.json());
5464
5657
  }
5465
5658
  function readInstalledManifest(dir) {
5466
5659
  const manifestPath = path2.join(dir, MANIFEST_FILE);
@@ -5608,10 +5801,14 @@ function formatBytes(bytes) {
5608
5801
  const kb = bytes / 1024;
5609
5802
  return `${kb.toFixed(1)} KB`;
5610
5803
  }
5611
- var MANIFEST_FILE = ".cabal-skills.json";
5804
+ var MANIFEST_FILE = ".cabal-skills.json", skillManifestSchema;
5612
5805
  var init_skill = __esm(() => {
5613
5806
  init_env();
5614
5807
  init_errors2();
5808
+ skillManifestSchema = z26.object({
5809
+ files: z26.record(z26.string(), z26.string()).optional().default({}),
5810
+ descriptions: z26.record(z26.string(), z26.string()).optional().default({})
5811
+ });
5615
5812
  });
5616
5813
 
5617
5814
  // src/index.ts
@@ -5653,7 +5850,7 @@ if (process.argv.includes("--mcp")) {
5653
5850
  printBanner(chalk15);
5654
5851
  await loginCommand2(options);
5655
5852
  });
5656
- program.command("onboard").description("Guided setup wizard — connect, profile, avatar, verify").option("--step <step>", "Jump to a specific step: connect, profile, avatar, verify").action(async (options) => {
5853
+ program.command("onboard").description("Guided setup wizard — connect, profile, avatar, verify").option("--step <step>", "Jump to a specific step: connect, profile, avatar, verify").option("--name <name>", "Display name").option("--handle <handle>", "Unique handle").option("--bio <bio>", "Agent bio").option("--strategy <strategy>", "Trading strategy").option("--avatar <description>", "Generate avatar from this description").option("--avatar-id <id>", "Select an existing avatar generation by ID").option("--skip-avatar", "Skip avatar step").option("--tweet-url <url>", "Tweet URL for verification").option("--skip-verify", "Skip verification step").option("-y, --confirm", "Auto-continue through wizard (required in non-TTY)").action(async (options) => {
5657
5854
  printBanner(chalk15);
5658
5855
  await onboardCommand2(options);
5659
5856
  });
@@ -5662,7 +5859,7 @@ if (process.argv.includes("--mcp")) {
5662
5859
  `));
5663
5860
  await statusCommand2();
5664
5861
  });
5665
- program.command("trade").description("Execute a trade on Solana or Hyperliquid").option("-c, --chain <chain>", "Chain: solana or hyperliquid").option("-i, --input <token>", "Solana: input token symbol").option("-o, --output <token>", "Solana: output token symbol").option("-a, --amount <amount>", "Solana: amount of input token").option("--coin <coin>", "Hyperliquid: coin symbol").option("--side <side>", "Hyperliquid: buy or sell").option("--size <size>", "Hyperliquid: position size").option("--order-type <type>", "Hyperliquid: market or limit").option("--price <price>", "Hyperliquid: limit price").option("--model <model>", "AI model name for attribution").action(async (options) => {
5862
+ program.command("trade").description("Execute a trade on Solana or Hyperliquid").option("-c, --chain <chain>", "Chain: solana or hyperliquid").option("-i, --input <token>", "Solana: input token symbol").option("-o, --output <token>", "Solana: output token symbol").option("-a, --amount <amount>", "Solana: amount of input token").option("--coin <coin>", "Hyperliquid: coin symbol").option("--side <side>", "Hyperliquid: buy or sell").option("--size <size>", "Hyperliquid: position size").option("--order-type <type>", "Hyperliquid: market or limit").option("--price <price>", "Hyperliquid: limit price").option("--model <model>", "AI model name for attribution").option("-y, --confirm", "Skip confirmation prompt").action(async (options) => {
5666
5863
  console.log(chalk15.green.bold("Cabal") + chalk15.dim(` • Trade
5667
5864
  `));
5668
5865
  await tradeCommand2(options);
@@ -3871,7 +3871,7 @@ async function createServer() {
3871
3871
  const res = await fetch(`${baseUrl}/skill.json`);
3872
3872
  if (!res.ok)
3873
3873
  throw new Error(`Failed to fetch skill.json: ${res.status}`);
3874
- const data = await res.json();
3874
+ const data = z22.record(z22.string(), z22.unknown()).parse(await res.json());
3875
3875
  return textResult(data);
3876
3876
  } catch (error) {
3877
3877
  return textResult(toStructuredError(error));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabaltrading/cli",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "CLI for Cabal - connect your AI agent and start trading.",
5
5
  "keywords": [
6
6
  "cabal",
@@ -54,4 +54,4 @@
54
54
  "engines": {
55
55
  "node": ">=18.0.0"
56
56
  }
57
- }
57
+ }