suzi-cli 0.1.28 → 0.1.30

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 (101) hide show
  1. package/CLAUDE.md +0 -2
  2. package/README.md +9 -16
  3. package/dist/commands/accounts.d.ts.map +1 -1
  4. package/dist/commands/accounts.js +23 -4
  5. package/dist/commands/accounts.js.map +1 -1
  6. package/dist/commands/agent.d.ts +7 -0
  7. package/dist/commands/agent.d.ts.map +1 -1
  8. package/dist/commands/agent.js +94 -54
  9. package/dist/commands/agent.js.map +1 -1
  10. package/dist/commands/env.d.ts.map +1 -1
  11. package/dist/commands/env.js +41 -21
  12. package/dist/commands/env.js.map +1 -1
  13. package/dist/commands/feedback.d.ts.map +1 -1
  14. package/dist/commands/feedback.js +14 -1
  15. package/dist/commands/feedback.js.map +1 -1
  16. package/dist/commands/import.d.ts.map +1 -1
  17. package/dist/commands/import.js.map +1 -1
  18. package/dist/commands/init.d.ts.map +1 -1
  19. package/dist/commands/init.js.map +1 -1
  20. package/dist/commands/install-hooks.js.map +1 -1
  21. package/dist/commands/list-actions.d.ts.map +1 -1
  22. package/dist/commands/list-actions.js +65 -32
  23. package/dist/commands/list-actions.js.map +1 -1
  24. package/dist/commands/list-triggers.d.ts.map +1 -1
  25. package/dist/commands/list-triggers.js +69 -43
  26. package/dist/commands/list-triggers.js.map +1 -1
  27. package/dist/commands/login.d.ts.map +1 -1
  28. package/dist/commands/login.js +74 -68
  29. package/dist/commands/login.js.map +1 -1
  30. package/dist/commands/portfolio.d.ts.map +1 -1
  31. package/dist/commands/portfolio.js +72 -17
  32. package/dist/commands/portfolio.js.map +1 -1
  33. package/dist/commands/preferences.d.ts.map +1 -1
  34. package/dist/commands/preferences.js +8 -0
  35. package/dist/commands/preferences.js.map +1 -1
  36. package/dist/commands/run.d.ts +33 -0
  37. package/dist/commands/run.d.ts.map +1 -1
  38. package/dist/commands/run.js +805 -174
  39. package/dist/commands/run.js.map +1 -1
  40. package/dist/commands/run.test.d.ts +2 -0
  41. package/dist/commands/run.test.d.ts.map +1 -0
  42. package/dist/commands/run.test.js +84 -0
  43. package/dist/commands/run.test.js.map +1 -0
  44. package/dist/commands/share.js.map +1 -1
  45. package/dist/commands/skills.d.ts.map +1 -1
  46. package/dist/commands/skills.js +78 -41
  47. package/dist/commands/skills.js.map +1 -1
  48. package/dist/commands/transactions.d.ts.map +1 -1
  49. package/dist/commands/transactions.js +1 -58
  50. package/dist/commands/transactions.js.map +1 -1
  51. package/dist/index.js +186 -55
  52. package/dist/index.js.map +1 -1
  53. package/dist/lib/actions-cache.d.ts +2 -0
  54. package/dist/lib/actions-cache.d.ts.map +1 -1
  55. package/dist/lib/actions-cache.js +9 -1
  56. package/dist/lib/actions-cache.js.map +1 -1
  57. package/dist/lib/api.d.ts +15 -5
  58. package/dist/lib/api.d.ts.map +1 -1
  59. package/dist/lib/api.js +17 -16
  60. package/dist/lib/api.js.map +1 -1
  61. package/dist/lib/session-registry.d.ts +1 -1
  62. package/dist/lib/session-registry.d.ts.map +1 -1
  63. package/dist/lib/session-registry.js +1 -1
  64. package/dist/lib/session-registry.js.map +1 -1
  65. package/dist/lib/skills.d.ts +20 -11
  66. package/dist/lib/skills.d.ts.map +1 -1
  67. package/dist/lib/skills.js +77 -53
  68. package/dist/lib/skills.js.map +1 -1
  69. package/dist/lib/skills.test.d.ts +2 -0
  70. package/dist/lib/skills.test.d.ts.map +1 -0
  71. package/dist/lib/skills.test.js +102 -0
  72. package/dist/lib/skills.test.js.map +1 -0
  73. package/dist/lib/tooling-preflight.d.ts.map +1 -1
  74. package/dist/lib/tooling-preflight.js +0 -9
  75. package/dist/lib/tooling-preflight.js.map +1 -1
  76. package/dist/utils/agent-picker.d.ts.map +1 -1
  77. package/dist/utils/agent-picker.js +7 -41
  78. package/dist/utils/agent-picker.js.map +1 -1
  79. package/dist/utils/resolver.d.ts.map +1 -1
  80. package/dist/utils/resolver.js +11 -7
  81. package/dist/utils/resolver.js.map +1 -1
  82. package/dist/utils/tty.d.ts.map +1 -1
  83. package/dist/utils/tty.js.map +1 -1
  84. package/dist/utils/ui.d.ts +2 -0
  85. package/dist/utils/ui.d.ts.map +1 -1
  86. package/dist/utils/ui.js +28 -7
  87. package/dist/utils/ui.js.map +1 -1
  88. package/package.json +1 -1
  89. package/TEST_CHECKLIST.md +0 -243
  90. package/dist/commands/memory.d.ts +0 -3
  91. package/dist/commands/memory.d.ts.map +0 -1
  92. package/dist/commands/memory.js +0 -196
  93. package/dist/commands/memory.js.map +0 -1
  94. package/dist/commands/prompt-suggestions.d.ts +0 -3
  95. package/dist/commands/prompt-suggestions.d.ts.map +0 -1
  96. package/dist/commands/prompt-suggestions.js +0 -161
  97. package/dist/commands/prompt-suggestions.js.map +0 -1
  98. package/dist/commands/subagents.d.ts +0 -3
  99. package/dist/commands/subagents.d.ts.map +0 -1
  100. package/dist/commands/subagents.js +0 -206
  101. package/dist/commands/subagents.js.map +0 -1
@@ -3,59 +3,76 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseDynamicArgs = parseDynamicArgs;
7
+ exports.validateParamsAgainstSchema = validateParamsAgainstSchema;
8
+ exports.maybeHandleRunActionHelp = maybeHandleRunActionHelp;
9
+ exports.executeRunCommand = executeRunCommand;
6
10
  exports.registerRunCommand = registerRunCommand;
11
+ const commander_1 = require("commander");
7
12
  const chalk_1 = __importDefault(require("chalk"));
8
13
  const api_1 = require("../lib/api");
14
+ const api_errors_1 = require("../lib/api-errors");
15
+ const actions_cache_1 = require("../lib/actions-cache");
9
16
  const ui_1 = require("../utils/ui");
10
17
  const tty_1 = require("../utils/tty");
11
- const actions_cache_1 = require("../lib/actions-cache");
12
- // Options that belong to the `run` command itself, not to the action params
13
- const KNOWN_OPTIONS = new Set(['json', 'output', 'dry-run']);
14
- const KNOWN_WITH_VALUE = new Set(['json', 'output']);
18
+ const KNOWN_OPTIONS = new Set(['params', 'json', 'output', 'dry-run', 'no-input', 'help']);
19
+ const KNOWN_WITH_VALUE = new Set(['params', 'output']);
20
+ const TRUTHY = new Set(['true', 'yes', 'on']);
21
+ const FALSY = new Set(['false', 'no', 'off']);
15
22
  /**
16
- * Convert kebab-case to camelCase. Passthrough if no hyphens.
17
- * e.g. "token-id" "tokenId", "tokenID" "tokenID"
23
+ * Convert kebab-case to camelCase. Passthrough if no hyphens.
24
+ * e.g. "token-id" -> "tokenId", "tokenID" -> "tokenID"
18
25
  */
19
26
  function kebabToCamel(str) {
20
27
  return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
21
28
  }
29
+ function camelToKebab(str) {
30
+ return str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
31
+ }
22
32
  /**
23
33
  * Resolve a param key against the action's JSON Schema property names.
24
34
  * Uses case-insensitive matching so "tokenid" resolves to "tokenID".
25
35
  */
26
36
  function resolveParamKey(inputKey, schemaProps) {
27
- // Exact match
28
37
  if (schemaProps.includes(inputKey))
29
38
  return inputKey;
30
- // Case-insensitive match
31
39
  const lower = inputKey.toLowerCase();
32
- const match = schemaProps.find((p) => p.toLowerCase() === lower);
40
+ const match = schemaProps.find((prop) => prop.toLowerCase() === lower);
33
41
  if (match)
34
42
  return match;
35
43
  return inputKey;
36
44
  }
37
- const TRUTHY = new Set(['true', 'yes', 'on']);
38
- const FALSY = new Set(['false', 'no', 'off']);
45
+ function getSchemaVariants(schema) {
46
+ if (Array.isArray(schema.anyOf)) {
47
+ return schema.anyOf.filter((variant) => Boolean(variant) && typeof variant === 'object' && !Array.isArray(variant));
48
+ }
49
+ if (Array.isArray(schema.oneOf)) {
50
+ return schema.oneOf.filter((variant) => Boolean(variant) && typeof variant === 'object' && !Array.isArray(variant));
51
+ }
52
+ if (Array.isArray(schema.type)) {
53
+ return schema.type
54
+ .filter((type) => typeof type === 'string')
55
+ .map((type) => ({ type }));
56
+ }
57
+ return [schema];
58
+ }
39
59
  /**
40
60
  * Heuristic type coercion for CLI string values.
41
61
  */
42
62
  function coerceValue(raw) {
43
- // JSON objects/arrays
44
63
  if ((raw.startsWith('{') && raw.endsWith('}')) || (raw.startsWith('[') && raw.endsWith(']'))) {
45
64
  try {
46
65
  return JSON.parse(raw);
47
66
  }
48
67
  catch {
49
- /* keep as string */
68
+ // Keep as string.
50
69
  }
51
70
  }
52
- // Numbers first — "1" and "0" should be numbers, not booleans
53
71
  if (/^-?\d+(\.\d+)?$/.test(raw) && raw.length <= 15) {
54
72
  const num = Number(raw);
55
- if (!isNaN(num) && isFinite(num))
73
+ if (!Number.isNaN(num) && Number.isFinite(num))
56
74
  return num;
57
75
  }
58
- // Booleans (only non-numeric truthy/falsy values)
59
76
  if (TRUTHY.has(raw.toLowerCase()))
60
77
  return true;
61
78
  if (FALSY.has(raw.toLowerCase()))
@@ -63,106 +80,371 @@ function coerceValue(raw) {
63
80
  return raw;
64
81
  }
65
82
  /**
66
- * Schema-aware type coercion: uses the JSON Schema type to decide.
83
+ * Schema-aware type coercion: uses the JSON Schema type (and anyOf/oneOf) to decide.
67
84
  */
68
85
  function coerceWithSchema(raw, propSchema) {
69
- const type = propSchema.type;
70
- if (type === 'number' || type === 'integer') {
71
- const num = Number(raw);
72
- return isNaN(num) ? raw : num;
73
- }
74
- if (type === 'boolean')
75
- return !FALSY.has(raw.toLowerCase());
76
- if (type === 'string')
77
- return raw;
78
- if (type === 'object' || type === 'array') {
79
- try {
80
- return JSON.parse(raw);
86
+ const variants = getSchemaVariants(propSchema);
87
+ for (const variant of variants) {
88
+ const type = variant.type;
89
+ if (type === 'number' || type === 'integer') {
90
+ const num = Number(raw);
91
+ if (!Number.isNaN(num)) {
92
+ return type === 'integer' ? Math.trunc(num) : num;
93
+ }
94
+ continue;
81
95
  }
82
- catch {
96
+ if (type === 'boolean') {
97
+ const lower = raw.toLowerCase();
98
+ if (TRUTHY.has(lower))
99
+ return true;
100
+ if (FALSY.has(lower))
101
+ return false;
102
+ continue;
103
+ }
104
+ if (type === 'object' || type === 'array') {
105
+ try {
106
+ return JSON.parse(raw);
107
+ }
108
+ catch {
109
+ continue;
110
+ }
111
+ }
112
+ if (type === 'string') {
83
113
  return raw;
84
114
  }
85
115
  }
86
116
  return coerceValue(raw);
87
117
  }
118
+ function isNumericToken(token) {
119
+ return /^-?\d+(\.\d+)?$/.test(token);
120
+ }
121
+ function isOptionToken(token) {
122
+ if (!token.startsWith('-'))
123
+ return false;
124
+ return !isNumericToken(token);
125
+ }
88
126
  /**
89
- * Parse dynamic --key value params from process.argv, skipping known run-command options.
127
+ * Parse dynamic params from process.argv, skipping run-command options.
128
+ * Supports both `--amount 1` and `amount 1`.
90
129
  */
91
130
  function parseDynamicArgs(argv) {
92
131
  const runIdx = argv.indexOf('run');
93
- if (runIdx === -1)
94
- return {};
95
- // Skip 'run' and the '<action>' argument
132
+ if (runIdx === -1) {
133
+ return { params: {}, positionalPairs: [], strayTokens: [] };
134
+ }
96
135
  const args = argv.slice(runIdx + 2);
97
136
  const params = {};
137
+ const positionalPairs = [];
138
+ const strayTokens = [];
98
139
  let i = 0;
99
140
  while (i < args.length) {
100
141
  const arg = args[i];
101
- if (!arg.startsWith('--')) {
142
+ if (arg === '--') {
102
143
  i++;
103
144
  continue;
104
145
  }
105
- const rawKey = arg.slice(2);
106
- // Skip known run-command options
107
- if (KNOWN_OPTIONS.has(rawKey)) {
108
- i++;
109
- if (KNOWN_WITH_VALUE.has(rawKey) && i < args.length && !args[i].startsWith('--')) {
146
+ if (arg.startsWith('--')) {
147
+ const rawKey = arg.slice(2);
148
+ if (KNOWN_OPTIONS.has(rawKey)) {
110
149
  i++;
150
+ if (KNOWN_WITH_VALUE.has(rawKey) &&
151
+ i < args.length &&
152
+ !isOptionToken(args[i])) {
153
+ i++;
154
+ }
155
+ continue;
156
+ }
157
+ const camelKey = kebabToCamel(rawKey);
158
+ const nextArg = args[i + 1];
159
+ if (nextArg === undefined || isOptionToken(nextArg)) {
160
+ params[camelKey] = true;
161
+ i++;
162
+ }
163
+ else {
164
+ params[camelKey] = coerceValue(nextArg);
165
+ i += 2;
111
166
  }
112
167
  continue;
113
168
  }
114
- const camelKey = kebabToCamel(rawKey);
115
- const nextArg = args[i + 1];
116
- if (nextArg === undefined || nextArg.startsWith('--')) {
117
- params[camelKey] = true;
169
+ if (arg.startsWith('-')) {
170
+ strayTokens.push(arg);
118
171
  i++;
172
+ continue;
119
173
  }
120
- else {
121
- params[camelKey] = coerceValue(nextArg);
122
- i += 2;
174
+ const nextArg = args[i + 1];
175
+ if (nextArg === undefined || isOptionToken(nextArg)) {
176
+ strayTokens.push(arg);
177
+ i++;
178
+ continue;
123
179
  }
180
+ const camelKey = kebabToCamel(arg);
181
+ params[camelKey] = coerceValue(nextArg);
182
+ positionalPairs.push({ key: camelKey, value: nextArg });
183
+ i += 2;
124
184
  }
125
- return params;
185
+ return { params, positionalPairs, strayTokens };
126
186
  }
127
187
  /**
128
188
  * Apply schema-aware key resolution and type coercion to params.
129
189
  */
130
190
  function refineParamsWithSchema(params, action) {
131
191
  const schemaProps = (0, actions_cache_1.getSchemaProperties)(action);
132
- if (schemaProps.length === 0)
133
- return params;
192
+ if (schemaProps.length === 0) {
193
+ return { params, unknownKeys: [] };
194
+ }
134
195
  const paramSchema = action.parameters;
135
196
  const refined = {};
136
- for (const [key, val] of Object.entries(params)) {
197
+ const unknownKeys = [];
198
+ const allowsAdditionalProperties = paramSchema.additionalProperties === true;
199
+ for (const [key, value] of Object.entries(params)) {
137
200
  const resolvedKey = resolveParamKey(key, schemaProps);
138
201
  const propDef = paramSchema.properties?.[resolvedKey];
139
- if (propDef && typeof val === 'string') {
140
- refined[resolvedKey] = coerceWithSchema(val, propDef);
202
+ if (!propDef) {
203
+ if (allowsAdditionalProperties) {
204
+ refined[key] = value;
205
+ }
206
+ else {
207
+ unknownKeys.push(key);
208
+ }
209
+ continue;
210
+ }
211
+ if (typeof value === 'string') {
212
+ refined[resolvedKey] = coerceWithSchema(value, propDef);
141
213
  }
142
214
  else {
143
- refined[resolvedKey] = val;
215
+ refined[resolvedKey] = value;
144
216
  }
145
217
  }
146
- return refined;
218
+ return { params: refined, unknownKeys };
147
219
  }
148
- /**
149
- * Suggest similar action names when the user's input doesn't match.
150
- */
151
- function suggestActions(input, actions) {
152
- const lower = input.toLowerCase();
153
- return actions
154
- .filter((a) => {
155
- if (a.name.toLowerCase().includes(lower))
156
- return true;
157
- const actionPart = a.name.split('.')[1]?.toLowerCase();
158
- return actionPart ? lower.includes(actionPart) : false;
220
+ function uniqueStrings(values) {
221
+ return Array.from(new Set(values));
222
+ }
223
+ function schemaTypeLabel(schema) {
224
+ const enumValues = Array.isArray(schema.enum) ? schema.enum : undefined;
225
+ if (enumValues && enumValues.length > 0) {
226
+ return uniqueStrings(enumValues.map((value) => JSON.stringify(value))).join(' | ');
227
+ }
228
+ if (Object.prototype.hasOwnProperty.call(schema, 'const')) {
229
+ return JSON.stringify(schema.const);
230
+ }
231
+ const labels = getSchemaVariants(schema)
232
+ .map((variant) => {
233
+ const type = variant.type;
234
+ if (type)
235
+ return type;
236
+ if (variant.properties && typeof variant.properties === 'object')
237
+ return 'object';
238
+ if (variant.items)
239
+ return 'array';
240
+ return 'unknown';
159
241
  })
160
- .slice(0, 5)
161
- .map((a) => a.name);
242
+ .filter(Boolean);
243
+ return uniqueStrings(labels).join(' | ') || 'unknown';
244
+ }
245
+ function schemaConstraintLabel(schema) {
246
+ const variants = getSchemaVariants(schema);
247
+ const parts = [];
248
+ const numericVariant = variants.find((variant) => {
249
+ const type = variant.type;
250
+ return type === 'number' || type === 'integer';
251
+ });
252
+ if (numericVariant) {
253
+ if (typeof numericVariant.exclusiveMinimum === 'number') {
254
+ parts.push(`> ${numericVariant.exclusiveMinimum}`);
255
+ }
256
+ else if (typeof numericVariant.minimum === 'number') {
257
+ parts.push(`>= ${numericVariant.minimum}`);
258
+ }
259
+ if (typeof numericVariant.exclusiveMaximum === 'number') {
260
+ parts.push(`< ${numericVariant.exclusiveMaximum}`);
261
+ }
262
+ else if (typeof numericVariant.maximum === 'number') {
263
+ parts.push(`<= ${numericVariant.maximum}`);
264
+ }
265
+ }
266
+ const stringVariant = variants.find((variant) => variant.type === 'string');
267
+ if (stringVariant) {
268
+ if (typeof stringVariant.minLength === 'number') {
269
+ parts.push(`min length ${stringVariant.minLength}`);
270
+ }
271
+ if (typeof stringVariant.maxLength === 'number') {
272
+ parts.push(`max length ${stringVariant.maxLength}`);
273
+ }
274
+ }
275
+ const arrayVariant = variants.find((variant) => variant.type === 'array');
276
+ if (arrayVariant) {
277
+ if (typeof arrayVariant.minItems === 'number') {
278
+ parts.push(`min items ${arrayVariant.minItems}`);
279
+ }
280
+ if (typeof arrayVariant.maxItems === 'number') {
281
+ parts.push(`max items ${arrayVariant.maxItems}`);
282
+ }
283
+ }
284
+ return parts.length > 0 ? parts.join(', ') : undefined;
285
+ }
286
+ function getActionParameterFields(action) {
287
+ const paramSchema = action.parameters;
288
+ const required = new Set(paramSchema.required ?? []);
289
+ return Object.entries(paramSchema.properties ?? {}).map(([name, schema]) => ({
290
+ name,
291
+ required: required.has(name),
292
+ typeLabel: schemaTypeLabel(schema),
293
+ constraints: schemaConstraintLabel(schema),
294
+ schema,
295
+ }));
296
+ }
297
+ function exampleValueForSchema(schema) {
298
+ const enumValues = Array.isArray(schema.enum) ? schema.enum : undefined;
299
+ if (enumValues && enumValues.length > 0) {
300
+ return enumValues[0];
301
+ }
302
+ if (Object.prototype.hasOwnProperty.call(schema, 'const')) {
303
+ return schema.const;
304
+ }
305
+ const variants = getSchemaVariants(schema).sort((left, right) => {
306
+ const order = ['integer', 'number', 'boolean', 'object', 'array', 'string'];
307
+ const leftIndex = order.indexOf(left.type ?? 'string');
308
+ const rightIndex = order.indexOf(right.type ?? 'string');
309
+ return (leftIndex === -1 ? order.length : leftIndex) - (rightIndex === -1 ? order.length : rightIndex);
310
+ });
311
+ for (const variant of variants) {
312
+ switch (variant.type) {
313
+ case 'number':
314
+ case 'integer':
315
+ return 1;
316
+ case 'boolean':
317
+ return true;
318
+ case 'object':
319
+ return {};
320
+ case 'array':
321
+ return [];
322
+ case 'string':
323
+ return 'value';
324
+ default:
325
+ break;
326
+ }
327
+ }
328
+ return 'value';
329
+ }
330
+ function formatExampleCliValue(value) {
331
+ if (typeof value === 'string')
332
+ return value;
333
+ return JSON.stringify(value);
334
+ }
335
+ function buildExampleObject(fields) {
336
+ return Object.fromEntries(fields.map((field) => [field.name, exampleValueForSchema(field.schema)]));
337
+ }
338
+ function buildActionExamples(action, command = 'suzi run') {
339
+ const fields = getActionParameterFields(action);
340
+ const requiredFields = fields.filter((field) => field.required);
341
+ const primaryFields = requiredFields.length > 0 ? requiredFields : fields.slice(0, 1);
342
+ const examples = [];
343
+ if (primaryFields.length === 0) {
344
+ examples.push(`${command} ${action.name}`);
345
+ return examples;
346
+ }
347
+ const flagArgs = primaryFields
348
+ .map((field) => `--${camelToKebab(field.name)} ${formatExampleCliValue(exampleValueForSchema(field.schema))}`)
349
+ .join(' ');
350
+ examples.push(`${command} ${action.name} ${flagArgs}`);
351
+ const positionalArgs = primaryFields
352
+ .map((field) => `${field.name} ${formatExampleCliValue(exampleValueForSchema(field.schema))}`)
353
+ .join(' ');
354
+ examples.push(`${command} ${action.name} ${positionalArgs}`);
355
+ const paramsJson = JSON.stringify(buildExampleObject(primaryFields));
356
+ examples.push(`${command} ${action.name} --params '${paramsJson}'`);
357
+ return uniqueStrings(examples);
358
+ }
359
+ function primitiveEquals(left, right) {
360
+ return JSON.stringify(left) === JSON.stringify(right);
361
+ }
362
+ function matchesSchema(value, schema) {
363
+ const variants = getSchemaVariants(schema);
364
+ if (variants.length > 1) {
365
+ return variants.some((variant) => matchesSchema(value, variant));
366
+ }
367
+ const variant = variants[0] ?? schema;
368
+ if (Array.isArray(variant.enum)) {
369
+ return variant.enum.some((candidate) => primitiveEquals(candidate, value));
370
+ }
371
+ if (Object.prototype.hasOwnProperty.call(variant, 'const')) {
372
+ return primitiveEquals(variant.const, value);
373
+ }
374
+ const type = variant.type;
375
+ if (!type)
376
+ return true;
377
+ switch (type) {
378
+ case 'number':
379
+ return (typeof value === 'number' &&
380
+ Number.isFinite(value) &&
381
+ (typeof variant.minimum !== 'number' || value >= variant.minimum) &&
382
+ (typeof variant.maximum !== 'number' || value <= variant.maximum) &&
383
+ (typeof variant.exclusiveMinimum !== 'number' || value > variant.exclusiveMinimum) &&
384
+ (typeof variant.exclusiveMaximum !== 'number' || value < variant.exclusiveMaximum));
385
+ case 'integer':
386
+ return (typeof value === 'number' &&
387
+ Number.isInteger(value) &&
388
+ (typeof variant.minimum !== 'number' || value >= variant.minimum) &&
389
+ (typeof variant.maximum !== 'number' || value <= variant.maximum) &&
390
+ (typeof variant.exclusiveMinimum !== 'number' || value > variant.exclusiveMinimum) &&
391
+ (typeof variant.exclusiveMaximum !== 'number' || value < variant.exclusiveMaximum));
392
+ case 'boolean':
393
+ return typeof value === 'boolean';
394
+ case 'string':
395
+ return (typeof value === 'string' &&
396
+ (typeof variant.minLength !== 'number' || value.length >= variant.minLength) &&
397
+ (typeof variant.maxLength !== 'number' || value.length <= variant.maxLength));
398
+ case 'object':
399
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
400
+ case 'array':
401
+ return Array.isArray(value);
402
+ case 'null':
403
+ return value === null;
404
+ default:
405
+ return true;
406
+ }
407
+ }
408
+ function describeExpectedValue(schema, key) {
409
+ const typeLabel = schemaTypeLabel(schema);
410
+ const constraints = schemaConstraintLabel(schema);
411
+ const base = constraints ? `${typeLabel} (${constraints})` : typeLabel;
412
+ if (typeLabel === 'boolean') {
413
+ return 'boolean (true or false)';
414
+ }
415
+ if (typeLabel.includes('object') || typeLabel.includes('array')) {
416
+ return `${base}; pass JSON via --${camelToKebab(key)} '${JSON.stringify(exampleValueForSchema(schema))}' or --params '{...}'`;
417
+ }
418
+ return base;
419
+ }
420
+ function validateParamsAgainstSchema(params, action) {
421
+ const paramSchema = action.parameters;
422
+ const properties = paramSchema.properties ?? {};
423
+ const required = new Set(paramSchema.required ?? []);
424
+ const missingRequired = Array.from(required).filter((key) => params[key] === undefined);
425
+ const invalidFields = [];
426
+ for (const [key, value] of Object.entries(params)) {
427
+ const schema = properties[key];
428
+ if (!schema)
429
+ continue;
430
+ if (!matchesSchema(value, schema)) {
431
+ invalidFields.push({
432
+ key,
433
+ message: `Expected ${describeExpectedValue(schema, key)}.`,
434
+ });
435
+ }
436
+ }
437
+ return { missingRequired, invalidFields };
438
+ }
439
+ function printCliApiIssues(issues) {
440
+ if (issues.length === 0)
441
+ return;
442
+ console.log();
443
+ console.log(ui_1.colors.error(' Issues:'));
444
+ for (const issue of issues) {
445
+ console.log(` ${ui_1.colors.error('✕')} ${(0, api_errors_1.formatCliApiErrorIssue)(issue)}`);
446
+ }
162
447
  }
163
- /**
164
- * Pretty-print the action result.
165
- */
166
448
  function formatPretty(actionName, result) {
167
449
  (0, ui_1.header)(`Result: ${actionName}`);
168
450
  console.log();
@@ -183,137 +465,486 @@ function formatPretty(actionName, result) {
183
465
  return;
184
466
  }
185
467
  const obj = result;
186
- for (const [key, val] of Object.entries(obj)) {
187
- if (val === null || val === undefined)
468
+ for (const [key, value] of Object.entries(obj)) {
469
+ if (value === null || value === undefined)
188
470
  continue;
189
- if (typeof val === 'object') {
190
- console.log(` ${ui_1.colors.muted(key + ':')} ${JSON.stringify(val, null, 2).split('\n').join('\n ')}`);
471
+ if (typeof value === 'object') {
472
+ console.log(` ${ui_1.colors.muted(key + ':')} ${JSON.stringify(value, null, 2).split('\n').join('\n ')}`);
191
473
  }
192
474
  else {
193
- (0, ui_1.label)(key, String(val));
475
+ (0, ui_1.label)(key, String(value));
194
476
  }
195
477
  }
196
478
  }
197
- function registerRunCommand(program) {
198
- program
199
- .command('run <action>')
200
- .description('Execute an action directly (e.g. suzi run polymarket.get_market_price --tokenID 0x123)')
201
- .allowUnknownOption()
202
- .allowExcessArguments(true)
203
- .option('--json <params>', 'Pass all parameters as a JSON string')
204
- .option('--output <format>', 'Output format: pretty (default) or json', 'pretty')
205
- .option('--dry-run', 'Preview the request payload without executing')
206
- .action(async (action, opts) => {
207
- const isJson = opts.output === 'json';
208
- if (isJson) {
209
- console.error('Warning: --output json is deprecated. Use suzi agents run instead.');
479
+ function getCanonicalRunCommand() {
480
+ return 'suzi run';
481
+ }
482
+ function getRunHelpPayload(action) {
483
+ const fields = getActionParameterFields(action);
484
+ return {
485
+ action: action.name,
486
+ description: action.description,
487
+ usage: `${getCanonicalRunCommand()} ${action.name} [--param value] [--params '{"key":"value"}'] [--json]`,
488
+ parameters: fields.map((field) => ({
489
+ name: field.name,
490
+ required: field.required,
491
+ type: field.typeLabel,
492
+ ...(field.constraints ? { constraints: field.constraints } : {}),
493
+ })),
494
+ examples: buildActionExamples(action),
495
+ };
496
+ }
497
+ function printActionParameters(action) {
498
+ const fields = getActionParameterFields(action);
499
+ if (fields.length === 0) {
500
+ (0, ui_1.info)(`Action ${chalk_1.default.cyan(action.name)} takes no parameters.`);
501
+ return;
502
+ }
503
+ console.log(chalk_1.default.bold('Parameters:'));
504
+ for (const field of fields) {
505
+ const requiredText = field.required ? chalk_1.default.yellow('required') : ui_1.colors.muted('optional');
506
+ const constraintText = field.constraints ? ` ${ui_1.colors.muted(`(${field.constraints})`)}` : '';
507
+ console.log(` ${chalk_1.default.cyan(field.name)} ${ui_1.colors.muted(field.typeLabel)} ${requiredText}${constraintText}`);
508
+ }
509
+ }
510
+ function printActionExamples(action) {
511
+ console.log();
512
+ console.log(chalk_1.default.bold('Examples:'));
513
+ for (const example of buildActionExamples(action)) {
514
+ console.log(` ${ui_1.colors.primary('$')} ${chalk_1.default.white(example)}`);
515
+ }
516
+ }
517
+ function printRunActionHelp(action, jsonMode, sourceCommand) {
518
+ if (jsonMode) {
519
+ (0, tty_1.outputJson)({ success: true, data: getRunHelpPayload(action) });
520
+ return;
521
+ }
522
+ if (sourceCommand === 'agents run') {
523
+ (0, ui_1.warn)('`suzi agents run` is deprecated. Use `suzi run` instead.');
524
+ console.log();
525
+ }
526
+ (0, ui_1.header)(`Run ${action.name}`);
527
+ console.log();
528
+ console.log(` ${action.description}`);
529
+ console.log();
530
+ console.log(chalk_1.default.bold('Usage:'));
531
+ console.log(` ${ui_1.colors.primary(getCanonicalRunCommand())} ${chalk_1.default.white(action.name)} ${ui_1.colors.muted('[options]')}`);
532
+ console.log();
533
+ printActionParameters(action);
534
+ printActionExamples(action);
535
+ console.log();
536
+ (0, ui_1.info)(`Use ${chalk_1.default.cyan(`suzi list-actions --protocol ${action.protocol} --schema ${action.name.split('.')[1]}`)} to inspect the raw schema.`);
537
+ console.log();
538
+ }
539
+ function printGenericRunExamples() {
540
+ console.log();
541
+ console.log(chalk_1.default.bold('Examples:'));
542
+ console.log(` ${ui_1.colors.primary('$')} ${chalk_1.default.white('suzi run polymarket.deposit --amount 1')}`);
543
+ console.log(` ${ui_1.colors.primary('$')} ${chalk_1.default.white('suzi run polymarket.deposit amount 1')}`);
544
+ console.log(` ${ui_1.colors.primary('$')} ${chalk_1.default.white(`suzi run polymarket.deposit --params '{"amount":1}'`)}`);
545
+ }
546
+ function emitRunFailure(message, options) {
547
+ const code = options.code ?? 'VALIDATION_FAILED';
548
+ if (options.jsonMode) {
549
+ (0, tty_1.outputJson)({
550
+ success: false,
551
+ error: { code, message },
552
+ ...(options.data ? { data: options.data } : {}),
553
+ });
554
+ return;
555
+ }
556
+ (0, ui_1.emitCliError)(message, code, false);
557
+ options.printTextDetails?.();
558
+ }
559
+ function emitSchemaAwareFailure(message, action, options) {
560
+ const helpPayload = getRunHelpPayload(action);
561
+ const notes = options.notes ?? [];
562
+ const extraIssues = options.extraIssues ?? [];
563
+ emitRunFailure(message, {
564
+ jsonMode: options.jsonMode,
565
+ code: options.code,
566
+ data: {
567
+ action: helpPayload.action,
568
+ parameters: helpPayload.parameters,
569
+ examples: helpPayload.examples,
570
+ ...(notes.length > 0 ? { notes } : {}),
571
+ ...(extraIssues.length > 0 ? { issues: extraIssues } : {}),
572
+ },
573
+ printTextDetails: () => {
574
+ console.log();
575
+ printActionParameters(action);
576
+ if (extraIssues.length > 0) {
577
+ console.log();
578
+ console.log(ui_1.colors.error('Issues:'));
579
+ for (const issue of extraIssues) {
580
+ console.log(` ${ui_1.colors.error('✕')} ${issue.key}: ${issue.message}`);
581
+ }
582
+ }
583
+ if (notes.length > 0) {
584
+ console.log();
585
+ for (const note of notes) {
586
+ (0, ui_1.info)(note);
587
+ }
588
+ }
589
+ printActionExamples(action);
590
+ },
591
+ });
592
+ }
593
+ function suggestActions(input, actions) {
594
+ const lower = input.toLowerCase();
595
+ return actions
596
+ .filter((action) => {
597
+ if (action.name.toLowerCase().includes(lower))
598
+ return true;
599
+ const actionPart = action.name.split('.')[1]?.toLowerCase();
600
+ return actionPart ? lower.includes(actionPart) : false;
601
+ })
602
+ .slice(0, 5)
603
+ .map((action) => action.name);
604
+ }
605
+ function looksLikeJsonLiteral(value) {
606
+ return ((value.startsWith('{') && value.endsWith('}')) ||
607
+ (value.startsWith('[') && value.endsWith(']')));
608
+ }
609
+ function buildStrayTokenNotes(strayTokens) {
610
+ if (strayTokens.length === 0)
611
+ return [];
612
+ if (strayTokens.length === 1 && looksLikeJsonLiteral(strayTokens[0])) {
613
+ return [
614
+ `Unexpected JSON argument ${strayTokens[0]}. --json controls output; use --params '${strayTokens[0]}' for input.`,
615
+ ];
616
+ }
617
+ if (strayTokens.length === 1) {
618
+ return [
619
+ `Could not interpret "${strayTokens[0]}". If it is a parameter name, pass a value after it or use --params '{...}'.`,
620
+ ];
621
+ }
622
+ return [
623
+ `Could not interpret these extra tokens: ${strayTokens.join(' ')}.`,
624
+ 'Use --param value pairs or --params \'{...}\' for action input.',
625
+ ];
626
+ }
627
+ function hasLegacyJsonOutputFlag(args) {
628
+ return args.some((arg, index) => arg === '--output' && args[index + 1] === 'json');
629
+ }
630
+ async function loadActionForHelp(actionName) {
631
+ const cached = (0, actions_cache_1.getCachedActionByName)(actionName);
632
+ if (cached)
633
+ return cached;
634
+ try {
635
+ return await (0, actions_cache_1.getActionByName)(actionName);
636
+ }
637
+ catch {
638
+ return undefined;
639
+ }
640
+ }
641
+ async function maybeHandleRunActionHelp(argv = process.argv) {
642
+ const args = argv.slice(2);
643
+ const helpRequested = args.includes('--help') || args.includes('-h');
644
+ if (!helpRequested)
645
+ return false;
646
+ let action;
647
+ let sourceCommand;
648
+ if (args[0] === 'run' && args[1] && !args[1].startsWith('-')) {
649
+ action = args[1];
650
+ sourceCommand = 'run';
651
+ }
652
+ else if (args[0] === 'agents' && args[1] === 'run' && args[2] && !args[2].startsWith('-')) {
653
+ action = args[2];
654
+ sourceCommand = 'agents run';
655
+ }
656
+ if (!action || !sourceCommand) {
657
+ return false;
658
+ }
659
+ const jsonMode = args.includes('--json') || hasLegacyJsonOutputFlag(args);
660
+ const actionMeta = await loadActionForHelp(action);
661
+ if (!actionMeta) {
662
+ emitRunFailure(`Action "${action}" was not found.`, {
663
+ jsonMode,
664
+ code: 'NOT_FOUND',
665
+ data: {
666
+ action,
667
+ },
668
+ printTextDetails: () => {
669
+ (0, ui_1.info)(`Run ${chalk_1.default.cyan('suzi list-actions')} to see all available actions.`);
670
+ },
671
+ });
672
+ return true;
673
+ }
674
+ printRunActionHelp(actionMeta, jsonMode, sourceCommand);
675
+ return true;
676
+ }
677
+ async function executeRunCommand(action, opts, argv = process.argv, sourceCommand = 'run') {
678
+ const jsonMode = Boolean(opts.json || opts.output === 'json');
679
+ if (sourceCommand === 'agents run' && !jsonMode) {
680
+ (0, ui_1.warn)('`suzi agents run` is deprecated. Use `suzi run` instead.');
681
+ }
682
+ if (!action) {
683
+ emitRunFailure(`Missing action. Expected ${getCanonicalRunCommand()} <protocol.action>.`, {
684
+ jsonMode,
685
+ code: 'INPUT_REQUIRED',
686
+ data: {
687
+ examples: [
688
+ 'suzi run polymarket.deposit --amount 1',
689
+ 'suzi run polymarket.deposit --params \'{"amount":1}\'',
690
+ ],
691
+ },
692
+ printTextDetails: () => {
693
+ printGenericRunExamples();
694
+ console.log();
695
+ (0, ui_1.info)(`Run ${chalk_1.default.cyan('suzi list-actions')} to see all available actions.`);
696
+ },
697
+ });
698
+ return;
699
+ }
700
+ const [protocol, actionName] = action.split('.', 2);
701
+ if (!protocol || !actionName) {
702
+ emitRunFailure('Action name must be in "protocol.action" format (e.g. polymarket.deposit).', {
703
+ jsonMode,
704
+ code: 'VALIDATION_FAILED',
705
+ data: {
706
+ action,
707
+ },
708
+ printTextDetails: () => {
709
+ printGenericRunExamples();
710
+ console.log();
711
+ (0, ui_1.info)(`Run ${chalk_1.default.cyan('suzi list-actions')} to see all available actions.`);
712
+ },
713
+ });
714
+ return;
715
+ }
716
+ if (!(0, ui_1.requireAuth)(jsonMode))
717
+ return;
718
+ const rawParams = parseDynamicArgs(argv);
719
+ let params;
720
+ if (opts.params) {
721
+ if (Object.keys(rawParams.params).length > 0 || rawParams.strayTokens.length > 0) {
722
+ const notes = [
723
+ 'Do not mix --params with individual action arguments.',
724
+ 'Choose either --params \'{...}\' or --param value pairs.',
725
+ ];
726
+ if (rawParams.strayTokens.length > 0) {
727
+ notes.push(...buildStrayTokenNotes(rawParams.strayTokens));
728
+ }
729
+ emitRunFailure('Conflicting action input: received --params together with additional action arguments.', {
730
+ jsonMode,
731
+ code: 'VALIDATION_FAILED',
732
+ data: {
733
+ action,
734
+ notes,
735
+ },
736
+ printTextDetails: () => {
737
+ for (const note of notes) {
738
+ (0, ui_1.info)(note);
739
+ }
740
+ },
741
+ });
742
+ return;
210
743
  }
211
- if (!(0, ui_1.requireAuth)(isJson))
744
+ try {
745
+ params = JSON.parse(opts.params);
746
+ }
747
+ catch (err) {
748
+ emitRunFailure(`Invalid JSON in --params: ${err instanceof Error ? err.message : String(err)}`, {
749
+ jsonMode,
750
+ code: 'VALIDATION_FAILED',
751
+ data: {
752
+ action,
753
+ example: `${getCanonicalRunCommand()} ${action} --params '{"amount":1}'`,
754
+ },
755
+ printTextDetails: () => {
756
+ (0, ui_1.info)('Pass a JSON object to --params, for example --params \'{"amount":1}\'.');
757
+ },
758
+ });
212
759
  return;
213
- // Validate action name format
214
- const [protocol, actionName] = action.split('.', 2);
215
- if (!protocol || !actionName) {
216
- (0, ui_1.error)('Action name must be in "protocol.action" format (e.g. polymarket.place_order).');
217
- (0, ui_1.info)(`Run ${chalk_1.default.cyan('suzi list-actions')} to see all available actions.`);
760
+ }
761
+ if (typeof params !== 'object' || params === null || Array.isArray(params)) {
762
+ emitRunFailure('--params must be a JSON object (e.g. \'{"key":"value"}\').', {
763
+ jsonMode,
764
+ code: 'VALIDATION_FAILED',
765
+ });
218
766
  return;
219
767
  }
220
- // Build params
221
- let params;
222
- if (opts.json) {
223
- try {
224
- params = JSON.parse(opts.json);
225
- }
226
- catch (err) {
227
- (0, ui_1.error)(`Invalid JSON in --json flag: ${err instanceof Error ? err.message : String(err)}`);
228
- return;
768
+ }
769
+ else {
770
+ params = rawParams.params;
771
+ }
772
+ let actionMeta;
773
+ try {
774
+ actionMeta = await (0, actions_cache_1.getActionByName)(action);
775
+ }
776
+ catch {
777
+ // Proceed without local schema metadata.
778
+ }
779
+ if (rawParams.strayTokens.length > 0) {
780
+ const notes = buildStrayTokenNotes(rawParams.strayTokens);
781
+ if (actionMeta) {
782
+ emitSchemaAwareFailure('Could not parse one or more action parameters.', actionMeta, {
783
+ jsonMode,
784
+ code: 'VALIDATION_FAILED',
785
+ notes,
786
+ });
787
+ }
788
+ else {
789
+ emitRunFailure('Could not parse one or more action parameters.', {
790
+ jsonMode,
791
+ code: 'VALIDATION_FAILED',
792
+ data: {
793
+ action,
794
+ notes,
795
+ },
796
+ printTextDetails: () => {
797
+ for (const note of notes) {
798
+ (0, ui_1.info)(note);
799
+ }
800
+ },
801
+ });
802
+ }
803
+ return;
804
+ }
805
+ if (actionMeta) {
806
+ const refined = refineParamsWithSchema(params, actionMeta);
807
+ params = refined.params;
808
+ if (refined.unknownKeys.length > 0) {
809
+ emitSchemaAwareFailure(`Unknown parameter(s): ${refined.unknownKeys.map((key) => `--${key}`).join(', ')}`, actionMeta, {
810
+ jsonMode,
811
+ code: 'VALIDATION_FAILED',
812
+ notes: [
813
+ `Use suzi list-actions --protocol ${protocol} --schema ${actionName} to inspect the raw schema.`,
814
+ ],
815
+ });
816
+ return;
817
+ }
818
+ const validation = validateParamsAgainstSchema(params, actionMeta);
819
+ if (validation.missingRequired.length > 0 || validation.invalidFields.length > 0) {
820
+ const messageParts = [];
821
+ if (validation.missingRequired.length > 0) {
822
+ messageParts.push(`Missing required parameter(s): ${validation.missingRequired.join(', ')}`);
229
823
  }
230
- if (typeof params !== 'object' || params === null || Array.isArray(params)) {
231
- (0, ui_1.error)('--json must be a JSON object (e.g. \'{"key":"value"}\').');
232
- return;
824
+ if (validation.invalidFields.length > 0) {
825
+ messageParts.push(...validation.invalidFields.map((issue) => `${issue.key}: ${issue.message}`));
233
826
  }
827
+ emitSchemaAwareFailure(messageParts.join('. '), actionMeta, {
828
+ jsonMode,
829
+ code: 'VALIDATION_FAILED',
830
+ notes: rawParams.positionalPairs.length > 0
831
+ ? ['Bare key/value pairs are supported, but the canonical form is --param value or --params \'{...}\'.']
832
+ : undefined,
833
+ extraIssues: validation.invalidFields,
834
+ });
835
+ return;
234
836
  }
235
- else {
236
- params = parseDynamicArgs(process.argv);
837
+ }
838
+ if (!actionMeta) {
839
+ if (!jsonMode) {
840
+ (0, ui_1.warn)(`Action "${action}" not found in local cache. Attempting execution anyway.`);
237
841
  }
238
- // Look up action metadata for key resolution and coercion
239
- let actionMeta;
240
842
  try {
241
- actionMeta = await (0, actions_cache_1.getActionByName)(action);
843
+ const allActions = await (0, actions_cache_1.getActions)();
844
+ const suggestions = suggestActions(action, allActions);
845
+ if (!jsonMode && suggestions.length > 0) {
846
+ (0, ui_1.info)(`Did you mean: ${suggestions.map((suggestion) => chalk_1.default.cyan(suggestion)).join(', ')}?`);
847
+ }
242
848
  }
243
849
  catch {
244
- // Cache fetch failed — proceed without schema refinement
850
+ // Ignore suggestion failures.
245
851
  }
246
- if (actionMeta && !opts.json) {
247
- params = refineParamsWithSchema(params, actionMeta);
852
+ }
853
+ if (opts.dryRun) {
854
+ if (jsonMode) {
855
+ (0, tty_1.outputJson)({
856
+ success: true,
857
+ data: {
858
+ action,
859
+ ...(actionMeta ? { description: actionMeta.description } : {}),
860
+ request: { name: action, params },
861
+ },
862
+ });
863
+ return;
248
864
  }
249
- if (!actionMeta) {
250
- // Action not in cache — warn but don't block
251
- (0, ui_1.warn)(`Action "${action}" not found in local cache. Attempting execution anyway.`);
252
- try {
253
- const allActions = await (0, actions_cache_1.getActions)();
254
- const suggestions = suggestActions(action, allActions);
255
- if (suggestions.length > 0) {
256
- (0, ui_1.info)(`Did you mean: ${suggestions.map((s) => chalk_1.default.cyan(s)).join(', ')}?`);
257
- }
258
- }
259
- catch {
260
- // Ignore suggestion failures
261
- }
865
+ (0, ui_1.header)('Dry Run');
866
+ console.log();
867
+ (0, ui_1.label)('action', action);
868
+ if (actionMeta) {
869
+ (0, ui_1.label)('description', actionMeta.description);
262
870
  }
263
- // Dry-run: show what would be sent
264
- if (opts.dryRun) {
265
- (0, ui_1.header)('Dry Run');
266
- console.log();
267
- (0, ui_1.label)('action', action);
268
- if (actionMeta) {
269
- (0, ui_1.label)('description', actionMeta.description);
871
+ console.log();
872
+ console.log(` ${ui_1.colors.muted('Request payload:')}`);
873
+ console.log(JSON.stringify({ name: action, params }, null, 2));
874
+ return;
875
+ }
876
+ const spinner = (0, tty_1.createSpinner)(`Executing ${chalk_1.default.bold(action)}...`, jsonMode);
877
+ try {
878
+ const resp = await (0, api_1.post)('/api/actions/execute', { name: action, params });
879
+ if (!resp.ok || !resp.data?.ok) {
880
+ spinner?.fail('Action failed');
881
+ const parsed = (0, api_errors_1.parseCliApiError)(resp.data);
882
+ const summary = parsed.summary || `API returned status ${resp.status}`;
883
+ if (jsonMode) {
884
+ (0, tty_1.outputJson)({
885
+ success: false,
886
+ error: {
887
+ code: resp.status === 400 ? 'VALIDATION_FAILED' : 'INTERNAL_ERROR',
888
+ message: summary,
889
+ },
890
+ data: {
891
+ error: parsed.raw,
892
+ ...(parsed.issues.length > 0 ? { issues: parsed.issues } : {}),
893
+ ...(parsed.detailsText ? { details: parsed.detailsText } : {}),
894
+ },
895
+ });
270
896
  }
271
- console.log();
272
- console.log(` ${ui_1.colors.muted('Request payload:')}`);
273
- console.log(JSON.stringify({ name: action, params }, null, 2));
274
- return;
275
- }
276
- // Execute
277
- const spinner = (0, tty_1.createSpinner)(`Executing ${chalk_1.default.bold(action)}...`, isJson);
278
- try {
279
- const resp = await (0, api_1.post)('/api/actions/execute', { name: action, params });
280
- if (!resp.ok || !resp.data?.ok) {
281
- spinner?.fail(`Action failed`);
282
- const msg = resp.data?.message || resp.data?.error || `API returned status ${resp.status}`;
283
- if (isJson) {
284
- (0, tty_1.outputJson)({ success: false, error: msg });
897
+ else {
898
+ (0, ui_1.emitCliError)(summary, resp.status === 400 ? 'VALIDATION_FAILED' : 'INTERNAL_ERROR', false);
899
+ if (parsed.issues.length > 0) {
900
+ printCliApiIssues(parsed.issues);
285
901
  }
286
- else {
287
- (0, ui_1.error)(msg);
288
- if (resp.status === 400) {
289
- (0, ui_1.info)(`Run ${chalk_1.default.cyan(`suzi list-actions --protocol ${protocol} --schema ${actionName}`)} to see the expected parameters.`);
290
- }
902
+ else if (parsed.detailsText) {
903
+ console.log(ui_1.colors.muted(` ${parsed.detailsText}`));
904
+ }
905
+ if (resp.status === 400) {
906
+ console.log();
907
+ (0, ui_1.info)(`Run ${chalk_1.default.cyan(`${getCanonicalRunCommand()} ${action} --help`)} for parameters and examples.`);
291
908
  }
292
- return;
293
- }
294
- if (isJson) {
295
- (0, tty_1.outputJson)({ success: true, data: resp.data.result });
296
- }
297
- else {
298
- spinner?.succeed(`${action} executed successfully`);
299
- console.log();
300
- formatPretty(action, resp.data.result);
301
909
  }
910
+ return;
302
911
  }
303
- catch (err) {
304
- const message = err instanceof Error ? err.message : String(err);
305
- if (isJson) {
306
- (0, tty_1.outputJson)({ success: false, error: { code: 'NETWORK_ERROR', message } });
307
- }
308
- else {
309
- spinner?.fail('Request failed');
310
- (0, ui_1.error)(message);
311
- }
912
+ if (jsonMode) {
913
+ (0, tty_1.outputJson)({ success: true, data: resp.data.result });
312
914
  }
313
- if (!isJson) {
915
+ else {
916
+ spinner?.succeed(`${action} executed successfully`);
314
917
  console.log();
315
- (0, ui_1.divider)();
918
+ formatPretty(action, resp.data.result);
919
+ }
920
+ }
921
+ catch (err) {
922
+ const message = err instanceof Error ? err.message : String(err);
923
+ if (jsonMode) {
924
+ (0, tty_1.outputJson)({ success: false, error: { code: 'NETWORK_ERROR', message } });
316
925
  }
926
+ else {
927
+ spinner?.fail('Request failed');
928
+ (0, ui_1.emitCliError)(message, 'NETWORK_ERROR', false);
929
+ }
930
+ }
931
+ if (!jsonMode) {
932
+ console.log();
933
+ (0, ui_1.divider)();
934
+ }
935
+ }
936
+ function registerRunCommand(program) {
937
+ program
938
+ .command('run [action]')
939
+ .description('Execute an action directly (e.g. suzi run polymarket.deposit --amount 1)')
940
+ .allowUnknownOption()
941
+ .allowExcessArguments(true)
942
+ .option('--params <json>', 'Pass action parameters as a JSON object')
943
+ .option('--json', 'Output as JSON')
944
+ .addOption(new commander_1.Option('--output <format>', 'Deprecated output format alias').choices(['pretty', 'json']).hideHelp())
945
+ .option('--dry-run', 'Preview the request payload without executing')
946
+ .action(async (action, opts) => {
947
+ await executeRunCommand(action, opts, process.argv, 'run');
317
948
  });
318
949
  }
319
950
  //# sourceMappingURL=run.js.map