llmist 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -1447,6 +1447,89 @@ var init_hook_validators = __esm({
1447
1447
  }
1448
1448
  });
1449
1449
 
1450
+ // src/gadgets/error-formatter.ts
1451
+ var GadgetErrorFormatter;
1452
+ var init_error_formatter = __esm({
1453
+ "src/gadgets/error-formatter.ts"() {
1454
+ "use strict";
1455
+ init_constants();
1456
+ GadgetErrorFormatter = class {
1457
+ argPrefix;
1458
+ startPrefix;
1459
+ endPrefix;
1460
+ constructor(options = {}) {
1461
+ this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
1462
+ this.startPrefix = options.startPrefix ?? GADGET_START_PREFIX;
1463
+ this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
1464
+ }
1465
+ /**
1466
+ * Format a Zod validation error with full gadget instructions.
1467
+ *
1468
+ * @param gadgetName - Name of the gadget that was called
1469
+ * @param zodError - The Zod validation error
1470
+ * @param gadget - The gadget instance (for generating instructions)
1471
+ * @returns Formatted error message with usage instructions
1472
+ */
1473
+ formatValidationError(gadgetName, zodError, gadget) {
1474
+ const parts = [];
1475
+ parts.push(`Error: Invalid parameters for '${gadgetName}':`);
1476
+ for (const issue of zodError.issues) {
1477
+ const path2 = issue.path.join(".") || "root";
1478
+ parts.push(` - ${path2}: ${issue.message}`);
1479
+ }
1480
+ parts.push("");
1481
+ parts.push("Gadget Usage:");
1482
+ parts.push(gadget.getInstruction(this.argPrefix));
1483
+ return parts.join("\n");
1484
+ }
1485
+ /**
1486
+ * Format a parse error with block format reference.
1487
+ *
1488
+ * @param gadgetName - Name of the gadget that was called
1489
+ * @param parseError - The parse error message
1490
+ * @param gadget - The gadget instance if found (for generating instructions)
1491
+ * @returns Formatted error message with format reference
1492
+ */
1493
+ formatParseError(gadgetName, parseError, gadget) {
1494
+ const parts = [];
1495
+ parts.push(`Error: Failed to parse parameters for '${gadgetName}':`);
1496
+ parts.push(` ${parseError}`);
1497
+ if (gadget) {
1498
+ parts.push("");
1499
+ parts.push("Gadget Usage:");
1500
+ parts.push(gadget.getInstruction(this.argPrefix));
1501
+ }
1502
+ parts.push("");
1503
+ parts.push("Block Format Reference:");
1504
+ parts.push(` ${this.startPrefix}${gadgetName}`);
1505
+ parts.push(` ${this.argPrefix}parameterName`);
1506
+ parts.push(" parameter value here");
1507
+ parts.push(` ${this.endPrefix}`);
1508
+ return parts.join("\n");
1509
+ }
1510
+ /**
1511
+ * Format a registry error (gadget not found) with available gadgets list.
1512
+ *
1513
+ * @param gadgetName - Name of the gadget that was not found
1514
+ * @param availableGadgets - List of available gadget names
1515
+ * @returns Formatted error message with available gadgets
1516
+ */
1517
+ formatRegistryError(gadgetName, availableGadgets) {
1518
+ const parts = [];
1519
+ parts.push(`Error: Gadget '${gadgetName}' not found.`);
1520
+ if (availableGadgets.length > 0) {
1521
+ parts.push("");
1522
+ parts.push(`Available gadgets: ${availableGadgets.join(", ")}`);
1523
+ } else {
1524
+ parts.push("");
1525
+ parts.push("No gadgets are currently registered.");
1526
+ }
1527
+ return parts.join("\n");
1528
+ }
1529
+ };
1530
+ }
1531
+ });
1532
+
1450
1533
  // src/gadgets/exceptions.ts
1451
1534
  var BreakLoopException, HumanInputException, TimeoutException;
1452
1535
  var init_exceptions = __esm({
@@ -1485,15 +1568,18 @@ var init_executor = __esm({
1485
1568
  "src/gadgets/executor.ts"() {
1486
1569
  "use strict";
1487
1570
  init_logger();
1571
+ init_error_formatter();
1488
1572
  init_exceptions();
1489
1573
  GadgetExecutor = class {
1490
- constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs) {
1574
+ constructor(registry, onHumanInputRequired, logger, defaultGadgetTimeoutMs, errorFormatterOptions) {
1491
1575
  this.registry = registry;
1492
1576
  this.onHumanInputRequired = onHumanInputRequired;
1493
1577
  this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
1494
1578
  this.logger = logger ?? createLogger({ name: "llmist:executor" });
1579
+ this.errorFormatter = new GadgetErrorFormatter(errorFormatterOptions);
1495
1580
  }
1496
1581
  logger;
1582
+ errorFormatter;
1497
1583
  /**
1498
1584
  * Creates a promise that rejects with a TimeoutException after the specified timeout.
1499
1585
  */
@@ -1518,11 +1604,12 @@ var init_executor = __esm({
1518
1604
  const gadget = this.registry.get(call.gadgetName);
1519
1605
  if (!gadget) {
1520
1606
  this.logger.error("Gadget not found", { gadgetName: call.gadgetName });
1607
+ const availableGadgets = this.registry.getNames();
1521
1608
  return {
1522
1609
  gadgetName: call.gadgetName,
1523
1610
  invocationId: call.invocationId,
1524
1611
  parameters: call.parameters ?? {},
1525
- error: `Gadget '${call.gadgetName}' not found in registry`,
1612
+ error: this.errorFormatter.formatRegistryError(call.gadgetName, availableGadgets),
1526
1613
  executionTimeMs: Date.now() - startTime
1527
1614
  };
1528
1615
  }
@@ -1532,25 +1619,26 @@ var init_executor = __esm({
1532
1619
  parseError: call.parseError,
1533
1620
  rawParameters: call.parametersRaw
1534
1621
  });
1622
+ const parseErrorMessage = call.parseError ?? "Failed to parse parameters";
1535
1623
  return {
1536
1624
  gadgetName: call.gadgetName,
1537
1625
  invocationId: call.invocationId,
1538
1626
  parameters: {},
1539
- error: call.parseError ?? "Failed to parse parameters",
1627
+ error: this.errorFormatter.formatParseError(call.gadgetName, parseErrorMessage, gadget),
1540
1628
  executionTimeMs: Date.now() - startTime
1541
1629
  };
1542
1630
  }
1543
1631
  if (gadget.parameterSchema) {
1544
1632
  const validationResult = gadget.parameterSchema.safeParse(rawParameters);
1545
1633
  if (!validationResult.success) {
1546
- const formattedIssues = validationResult.error.issues.map((issue) => {
1547
- const path2 = issue.path.join(".") || "root";
1548
- return `${path2}: ${issue.message}`;
1549
- }).join("; ");
1550
- const validationError = `Invalid parameters: ${formattedIssues}`;
1634
+ const validationError = this.errorFormatter.formatValidationError(
1635
+ call.gadgetName,
1636
+ validationResult.error,
1637
+ gadget
1638
+ );
1551
1639
  this.logger.error("Gadget parameter validation failed", {
1552
1640
  gadgetName: call.gadgetName,
1553
- error: validationError
1641
+ issueCount: validationResult.error.issues.length
1554
1642
  });
1555
1643
  return {
1556
1644
  gadgetName: call.gadgetName,
@@ -1839,17 +1927,12 @@ var init_parser = __esm({
1839
1927
  return { actualName: gadgetName, invocationId: `gadget_${++globalInvocationCounter}` };
1840
1928
  }
1841
1929
  /**
1842
- * Truncate verbose parse errors to avoid context overflow.
1843
- * Keeps first meaningful line and limits total length.
1930
+ * Extract the error message from a parse error.
1931
+ * Preserves full message since the error formatter adds contextual help
1932
+ * that benefits from precise, detailed error information.
1844
1933
  */
1845
- truncateParseError(error, format) {
1846
- const message = error instanceof Error ? error.message : String(error);
1847
- const firstLine = message.split("\n")[0];
1848
- const maxLen = 200;
1849
- if (firstLine.length <= maxLen) {
1850
- return firstLine;
1851
- }
1852
- return `${firstLine.slice(0, maxLen)}... (${message.length} chars total)`;
1934
+ extractParseError(error) {
1935
+ return error instanceof Error ? error.message : String(error);
1853
1936
  }
1854
1937
  /**
1855
1938
  * Parse parameter string using block format
@@ -1859,7 +1942,7 @@ var init_parser = __esm({
1859
1942
  try {
1860
1943
  return { parameters: parseBlockParams(cleaned, { argPrefix: this.argPrefix }) };
1861
1944
  } catch (error) {
1862
- return { parseError: this.truncateParseError(error, "block") };
1945
+ return { parseError: this.extractParseError(error) };
1863
1946
  }
1864
1947
  }
1865
1948
  // Feed a chunk of text and get parsed events
@@ -5460,7 +5543,8 @@ var CLI_DESCRIPTION = "Command line utilities for llmist agents and direct LLM a
5460
5543
  var COMMANDS = {
5461
5544
  complete: "complete",
5462
5545
  agent: "agent",
5463
- models: "models"
5546
+ models: "models",
5547
+ gadget: "gadget"
5464
5548
  };
5465
5549
  var LOG_LEVELS = ["silly", "trace", "debug", "info", "warn", "error", "fatal"];
5466
5550
  var DEFAULT_MODEL = "openai:gpt-5-nano";
@@ -5504,7 +5588,7 @@ var import_commander2 = require("commander");
5504
5588
  // package.json
5505
5589
  var package_default = {
5506
5590
  name: "llmist",
5507
- version: "0.8.0",
5591
+ version: "1.0.0",
5508
5592
  description: "Universal TypeScript LLM client with streaming-first agent framework. Works with any model - no structured outputs or native tool calling required. Implements its own flexible grammar for function calling.",
5509
5593
  type: "module",
5510
5594
  main: "dist/index.cjs",
@@ -7491,8 +7575,439 @@ function resolveInheritance(config, configPath) {
7491
7575
  return resolved;
7492
7576
  }
7493
7577
 
7494
- // src/cli/models-command.ts
7578
+ // src/cli/gadget-command.ts
7579
+ var import_chalk5 = __toESM(require("chalk"), 1);
7580
+ init_schema_to_json();
7581
+ init_schema_validator();
7582
+
7583
+ // src/cli/gadget-prompts.ts
7584
+ var import_promises3 = require("readline/promises");
7495
7585
  var import_chalk4 = __toESM(require("chalk"), 1);
7586
+ init_schema_to_json();
7587
+ async function promptForParameters(schema, ctx) {
7588
+ if (!schema) {
7589
+ return {};
7590
+ }
7591
+ const jsonSchema = schemaToJSONSchema(schema, { target: "draft-7" });
7592
+ if (!jsonSchema.properties || Object.keys(jsonSchema.properties).length === 0) {
7593
+ return {};
7594
+ }
7595
+ const rl = (0, import_promises3.createInterface)({ input: ctx.stdin, output: ctx.stdout });
7596
+ const params = {};
7597
+ try {
7598
+ for (const [key, prop] of Object.entries(jsonSchema.properties)) {
7599
+ const value = await promptForField(rl, key, prop, jsonSchema.required ?? []);
7600
+ if (value !== void 0) {
7601
+ params[key] = value;
7602
+ }
7603
+ }
7604
+ } finally {
7605
+ rl.close();
7606
+ }
7607
+ const result = schema.safeParse(params);
7608
+ if (!result.success) {
7609
+ const issues = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
7610
+ throw new Error(`Invalid parameters:
7611
+ ${issues}`);
7612
+ }
7613
+ return result.data;
7614
+ }
7615
+ async function promptForField(rl, key, prop, required) {
7616
+ const isRequired = required.includes(key);
7617
+ const typeHint = formatTypeHint(prop);
7618
+ const defaultHint = prop.default !== void 0 ? import_chalk4.default.dim(` [default: ${JSON.stringify(prop.default)}]`) : "";
7619
+ const requiredMarker = isRequired ? import_chalk4.default.red("*") : "";
7620
+ let prompt = `
7621
+ ${import_chalk4.default.cyan.bold(key)}${requiredMarker}`;
7622
+ if (prop.description) {
7623
+ prompt += import_chalk4.default.dim(` - ${prop.description}`);
7624
+ }
7625
+ prompt += `
7626
+ ${typeHint}${defaultHint}
7627
+ ${import_chalk4.default.green(">")} `;
7628
+ const answer = await rl.question(prompt);
7629
+ const trimmed = answer.trim();
7630
+ if (!trimmed) {
7631
+ if (prop.default !== void 0) {
7632
+ return void 0;
7633
+ }
7634
+ if (!isRequired) {
7635
+ return void 0;
7636
+ }
7637
+ throw new Error(`Parameter '${key}' is required.`);
7638
+ }
7639
+ return parseValue(trimmed, prop, key);
7640
+ }
7641
+ function formatTypeHint(prop) {
7642
+ if (prop.enum) {
7643
+ return import_chalk4.default.yellow(`(${prop.enum.join(" | ")})`);
7644
+ }
7645
+ if (prop.type === "array") {
7646
+ const items = prop.items;
7647
+ if (items?.enum) {
7648
+ return import_chalk4.default.yellow(`(${items.enum.join(" | ")})[] comma-separated`);
7649
+ }
7650
+ const itemType = items?.type ?? "any";
7651
+ return import_chalk4.default.yellow(`(${itemType}[]) comma-separated`);
7652
+ }
7653
+ if (prop.type === "object" && prop.properties) {
7654
+ return import_chalk4.default.yellow("(object) enter as JSON");
7655
+ }
7656
+ return import_chalk4.default.yellow(`(${prop.type ?? "any"})`);
7657
+ }
7658
+ function parseValue(input, prop, key) {
7659
+ const type = prop.type;
7660
+ if (type === "number" || type === "integer") {
7661
+ const num = Number(input);
7662
+ if (Number.isNaN(num)) {
7663
+ throw new Error(`Invalid number for '${key}': ${input}`);
7664
+ }
7665
+ if (type === "integer" && !Number.isInteger(num)) {
7666
+ throw new Error(`Expected integer for '${key}', got: ${input}`);
7667
+ }
7668
+ return num;
7669
+ }
7670
+ if (type === "boolean") {
7671
+ const lower = input.toLowerCase();
7672
+ if (["true", "yes", "1", "y"].includes(lower)) return true;
7673
+ if (["false", "no", "0", "n"].includes(lower)) return false;
7674
+ throw new Error(`Invalid boolean for '${key}': ${input} (use true/false, yes/no, 1/0)`);
7675
+ }
7676
+ if (type === "array") {
7677
+ const items = input.split(",").map((s) => s.trim()).filter(Boolean);
7678
+ const itemType = prop.items?.type;
7679
+ if (itemType === "number" || itemType === "integer") {
7680
+ return items.map((item) => {
7681
+ const num = Number(item);
7682
+ if (Number.isNaN(num)) throw new Error(`Invalid number in '${key}' array: ${item}`);
7683
+ return num;
7684
+ });
7685
+ }
7686
+ if (itemType === "boolean") {
7687
+ return items.map((item) => {
7688
+ const lower = item.toLowerCase();
7689
+ if (["true", "yes", "1", "y"].includes(lower)) return true;
7690
+ if (["false", "no", "0", "n"].includes(lower)) return false;
7691
+ throw new Error(`Invalid boolean in '${key}' array: ${item}`);
7692
+ });
7693
+ }
7694
+ return items;
7695
+ }
7696
+ if (type === "object") {
7697
+ try {
7698
+ return JSON.parse(input);
7699
+ } catch {
7700
+ throw new Error(`Invalid JSON for '${key}': ${input}`);
7701
+ }
7702
+ }
7703
+ return input;
7704
+ }
7705
+ async function readStdinJson(stdin) {
7706
+ const chunks = [];
7707
+ for await (const chunk of stdin) {
7708
+ if (typeof chunk === "string") {
7709
+ chunks.push(chunk);
7710
+ } else {
7711
+ chunks.push(chunk.toString("utf8"));
7712
+ }
7713
+ }
7714
+ const content = chunks.join("").trim();
7715
+ if (!content) {
7716
+ return {};
7717
+ }
7718
+ try {
7719
+ const parsed = JSON.parse(content);
7720
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
7721
+ throw new Error("Stdin must contain a JSON object, not an array or primitive.");
7722
+ }
7723
+ return parsed;
7724
+ } catch (error) {
7725
+ if (error instanceof SyntaxError) {
7726
+ throw new Error(`Invalid JSON from stdin: ${error.message}`);
7727
+ }
7728
+ throw error;
7729
+ }
7730
+ }
7731
+
7732
+ // src/cli/gadget-command.ts
7733
+ async function selectGadget(file, nameOption, cwd) {
7734
+ const gadgets = await loadGadgets([file], cwd);
7735
+ if (gadgets.length === 0) {
7736
+ throw new Error(
7737
+ `No gadgets found in '${file}'.
7738
+ Ensure the file exports a Gadget class or instance.`
7739
+ );
7740
+ }
7741
+ if (gadgets.length === 1) {
7742
+ const gadget = gadgets[0];
7743
+ const name = gadget.name ?? gadget.constructor.name;
7744
+ return { gadget, name };
7745
+ }
7746
+ const names = gadgets.map((g) => g.name ?? g.constructor.name);
7747
+ if (!nameOption) {
7748
+ throw new Error(
7749
+ `File '${file}' exports ${gadgets.length} gadgets.
7750
+ Use --name to select one:
7751
+ ` + names.map((n) => ` - ${n}`).join("\n")
7752
+ );
7753
+ }
7754
+ const found = gadgets.find((g) => (g.name ?? g.constructor.name) === nameOption);
7755
+ if (!found) {
7756
+ throw new Error(
7757
+ `Gadget '${nameOption}' not found in '${file}'.
7758
+ Available gadgets:
7759
+ ` + names.map((n) => ` - ${n}`).join("\n")
7760
+ );
7761
+ }
7762
+ return { gadget: found, name: nameOption };
7763
+ }
7764
+ async function executeGadgetRun(file, options, env) {
7765
+ const cwd = process.cwd();
7766
+ const { gadget, name } = await selectGadget(file, options.name, cwd);
7767
+ env.stderr.write(import_chalk5.default.cyan.bold(`
7768
+ \u{1F527} Running gadget: ${name}
7769
+ `));
7770
+ let params;
7771
+ if (env.isTTY) {
7772
+ params = await promptForParameters(gadget.parameterSchema, {
7773
+ stdin: env.stdin,
7774
+ stdout: env.stderr
7775
+ // Prompts go to stderr to keep stdout clean
7776
+ });
7777
+ } else {
7778
+ env.stderr.write(import_chalk5.default.dim("Reading parameters from stdin...\n"));
7779
+ const stdinParams = await readStdinJson(env.stdin);
7780
+ if (gadget.parameterSchema) {
7781
+ const result2 = gadget.parameterSchema.safeParse(stdinParams);
7782
+ if (!result2.success) {
7783
+ const issues = result2.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
7784
+ throw new Error(`Invalid parameters:
7785
+ ${issues}`);
7786
+ }
7787
+ params = result2.data;
7788
+ } else {
7789
+ params = stdinParams;
7790
+ }
7791
+ }
7792
+ env.stderr.write(import_chalk5.default.dim("\nExecuting...\n"));
7793
+ const startTime = Date.now();
7794
+ let result;
7795
+ try {
7796
+ if (gadget.timeoutMs && gadget.timeoutMs > 0) {
7797
+ result = await Promise.race([
7798
+ Promise.resolve(gadget.execute(params)),
7799
+ new Promise(
7800
+ (_, reject) => setTimeout(
7801
+ () => reject(new Error(`Gadget timed out after ${gadget.timeoutMs}ms`)),
7802
+ gadget.timeoutMs
7803
+ )
7804
+ )
7805
+ ]);
7806
+ } else {
7807
+ result = await Promise.resolve(gadget.execute(params));
7808
+ }
7809
+ } catch (error) {
7810
+ const message = error instanceof Error ? error.message : String(error);
7811
+ throw new Error(`Execution failed: ${message}`);
7812
+ }
7813
+ const elapsed = Date.now() - startTime;
7814
+ env.stderr.write(import_chalk5.default.green(`
7815
+ \u2713 Completed in ${elapsed}ms
7816
+
7817
+ `));
7818
+ formatOutput(result, options, env.stdout);
7819
+ }
7820
+ function formatOutput(result, options, stdout) {
7821
+ if (options.raw) {
7822
+ stdout.write(result);
7823
+ if (!result.endsWith("\n")) stdout.write("\n");
7824
+ return;
7825
+ }
7826
+ if (options.json || looksLikeJson(result)) {
7827
+ try {
7828
+ const parsed = JSON.parse(result);
7829
+ stdout.write(JSON.stringify(parsed, null, 2) + "\n");
7830
+ return;
7831
+ } catch {
7832
+ }
7833
+ }
7834
+ stdout.write(result);
7835
+ if (!result.endsWith("\n")) stdout.write("\n");
7836
+ }
7837
+ function looksLikeJson(str) {
7838
+ const trimmed = str.trim();
7839
+ return trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]");
7840
+ }
7841
+ async function executeGadgetInfo(file, options, env) {
7842
+ const cwd = process.cwd();
7843
+ const { gadget, name } = await selectGadget(file, options.name, cwd);
7844
+ if (options.json) {
7845
+ const info = buildGadgetInfo(gadget, name);
7846
+ env.stdout.write(JSON.stringify(info, null, 2) + "\n");
7847
+ return;
7848
+ }
7849
+ env.stdout.write("\n");
7850
+ env.stdout.write(import_chalk5.default.cyan.bold(`${name}
7851
+ `));
7852
+ env.stdout.write(import_chalk5.default.cyan("\u2550".repeat(name.length)) + "\n\n");
7853
+ env.stdout.write(import_chalk5.default.bold("Description:\n"));
7854
+ env.stdout.write(` ${gadget.description}
7855
+
7856
+ `);
7857
+ if (gadget.parameterSchema) {
7858
+ env.stdout.write(import_chalk5.default.bold("Parameters:\n"));
7859
+ const jsonSchema = schemaToJSONSchema(gadget.parameterSchema, { target: "draft-7" });
7860
+ env.stdout.write(formatSchemaAsText(jsonSchema, " ") + "\n\n");
7861
+ } else {
7862
+ env.stdout.write(import_chalk5.default.dim("No parameters required.\n\n"));
7863
+ }
7864
+ if (gadget.timeoutMs) {
7865
+ env.stdout.write(import_chalk5.default.bold("Timeout:\n"));
7866
+ env.stdout.write(` ${gadget.timeoutMs}ms
7867
+
7868
+ `);
7869
+ }
7870
+ if (gadget.examples && gadget.examples.length > 0) {
7871
+ env.stdout.write(import_chalk5.default.bold("Examples:\n"));
7872
+ for (const example of gadget.examples) {
7873
+ if (example.comment) {
7874
+ env.stdout.write(import_chalk5.default.dim(` # ${example.comment}
7875
+ `));
7876
+ }
7877
+ env.stdout.write(` Input: ${import_chalk5.default.cyan(JSON.stringify(example.params))}
7878
+ `);
7879
+ if (example.output !== void 0) {
7880
+ env.stdout.write(` Output: ${import_chalk5.default.green(example.output)}
7881
+ `);
7882
+ }
7883
+ env.stdout.write("\n");
7884
+ }
7885
+ }
7886
+ }
7887
+ function buildGadgetInfo(gadget, name) {
7888
+ const info = {
7889
+ name,
7890
+ description: gadget.description
7891
+ };
7892
+ if (gadget.parameterSchema) {
7893
+ info.schema = schemaToJSONSchema(gadget.parameterSchema, { target: "draft-7" });
7894
+ }
7895
+ if (gadget.timeoutMs) {
7896
+ info.timeoutMs = gadget.timeoutMs;
7897
+ }
7898
+ if (gadget.examples && gadget.examples.length > 0) {
7899
+ info.examples = gadget.examples;
7900
+ }
7901
+ return info;
7902
+ }
7903
+ function formatSchemaAsText(schema, indent = "") {
7904
+ const lines = [];
7905
+ const properties = schema.properties || {};
7906
+ const required = schema.required || [];
7907
+ for (const [key, prop] of Object.entries(properties)) {
7908
+ const type = prop.type;
7909
+ const description = prop.description;
7910
+ const isRequired = required.includes(key);
7911
+ const enumValues = prop.enum;
7912
+ const defaultValue = prop.default;
7913
+ let line = `${indent}${import_chalk5.default.cyan(key)}`;
7914
+ if (isRequired) {
7915
+ line += import_chalk5.default.red("*");
7916
+ }
7917
+ if (type === "array") {
7918
+ const items = prop.items;
7919
+ const itemType = items?.type || "any";
7920
+ line += import_chalk5.default.dim(` (${itemType}[])`);
7921
+ } else if (type === "object" && prop.properties) {
7922
+ line += import_chalk5.default.dim(" (object)");
7923
+ } else {
7924
+ line += import_chalk5.default.dim(` (${type})`);
7925
+ }
7926
+ if (defaultValue !== void 0) {
7927
+ line += import_chalk5.default.dim(` [default: ${JSON.stringify(defaultValue)}]`);
7928
+ }
7929
+ if (description) {
7930
+ line += `: ${description}`;
7931
+ }
7932
+ if (enumValues) {
7933
+ line += import_chalk5.default.yellow(` - one of: ${enumValues.join(", ")}`);
7934
+ }
7935
+ lines.push(line);
7936
+ if (type === "object" && prop.properties) {
7937
+ lines.push(formatSchemaAsText(prop, indent + " "));
7938
+ }
7939
+ }
7940
+ return lines.join("\n");
7941
+ }
7942
+ async function executeGadgetValidate(file, env) {
7943
+ const cwd = process.cwd();
7944
+ try {
7945
+ const gadgets = await loadGadgets([file], cwd);
7946
+ if (gadgets.length === 0) {
7947
+ throw new Error(
7948
+ "No gadgets exported from file.\nA valid gadget must have:\n - execute() method\n - description property\n - parameterSchema (optional)"
7949
+ );
7950
+ }
7951
+ const issues = [];
7952
+ for (const gadget of gadgets) {
7953
+ const name = gadget.name ?? gadget.constructor.name;
7954
+ if (!gadget.description) {
7955
+ issues.push(`${name}: Missing 'description' property.`);
7956
+ }
7957
+ if (gadget.parameterSchema) {
7958
+ try {
7959
+ validateGadgetSchema(gadget.parameterSchema, name);
7960
+ } catch (schemaError) {
7961
+ const message = schemaError instanceof Error ? schemaError.message : String(schemaError);
7962
+ issues.push(`${name}: ${message}`);
7963
+ }
7964
+ }
7965
+ if (typeof gadget.execute !== "function") {
7966
+ issues.push(`${name}: Missing 'execute()' method.`);
7967
+ }
7968
+ }
7969
+ if (issues.length > 0) {
7970
+ throw new Error(`Validation issues:
7971
+ ${issues.map((i) => ` - ${i}`).join("\n")}`);
7972
+ }
7973
+ env.stdout.write(import_chalk5.default.green.bold("\n\u2713 Valid\n\n"));
7974
+ env.stdout.write(import_chalk5.default.bold("Gadgets found:\n"));
7975
+ for (const gadget of gadgets) {
7976
+ const name = gadget.name ?? gadget.constructor.name;
7977
+ const schemaInfo = gadget.parameterSchema ? import_chalk5.default.cyan("(with schema)") : import_chalk5.default.dim("(no schema)");
7978
+ env.stdout.write(` ${import_chalk5.default.bold(name)} ${schemaInfo}
7979
+ `);
7980
+ env.stdout.write(import_chalk5.default.dim(` ${gadget.description}
7981
+ `));
7982
+ }
7983
+ env.stdout.write("\n");
7984
+ } catch (error) {
7985
+ const message = error instanceof Error ? error.message : String(error);
7986
+ env.stdout.write(import_chalk5.default.red.bold(`
7987
+ \u2717 Invalid
7988
+
7989
+ `));
7990
+ env.stdout.write(`${message}
7991
+
7992
+ `);
7993
+ env.setExitCode(1);
7994
+ }
7995
+ }
7996
+ function registerGadgetCommand(program, env) {
7997
+ const gadgetCmd = program.command("gadget").description("Test and inspect gadgets outside the agent loop.");
7998
+ gadgetCmd.command("run <file>").description("Execute a gadget with interactive prompts or stdin JSON.").option("--name <gadget>", "Select gadget by name (required if file exports multiple)").option("--json", "Format output as pretty-printed JSON").option("--raw", "Output result as raw string without formatting").action(
7999
+ (file, options) => executeAction(() => executeGadgetRun(file, options, env), env)
8000
+ );
8001
+ gadgetCmd.command("info <file>").description("Display gadget description, schema, and examples.").option("--name <gadget>", "Select gadget by name (required if file exports multiple)").option("--json", "Output as JSON instead of formatted text").action(
8002
+ (file, options) => executeAction(() => executeGadgetInfo(file, options, env), env)
8003
+ );
8004
+ gadgetCmd.command("validate <file>").description("Check if file exports valid gadget(s).").action(
8005
+ (file) => executeAction(() => executeGadgetValidate(file, env), env)
8006
+ );
8007
+ }
8008
+
8009
+ // src/cli/models-command.ts
8010
+ var import_chalk6 = __toESM(require("chalk"), 1);
7496
8011
  init_model_shortcuts();
7497
8012
  async function handleModelsCommand(options, env) {
7498
8013
  const client = env.createClient();
@@ -7512,13 +8027,13 @@ function renderTable(models, verbose, stream2) {
7512
8027
  }
7513
8028
  grouped.get(provider).push(model);
7514
8029
  }
7515
- stream2.write(import_chalk4.default.bold.cyan("\nAvailable Models\n"));
7516
- stream2.write(import_chalk4.default.cyan("=".repeat(80)) + "\n\n");
8030
+ stream2.write(import_chalk6.default.bold.cyan("\nAvailable Models\n"));
8031
+ stream2.write(import_chalk6.default.cyan("=".repeat(80)) + "\n\n");
7517
8032
  const providers = Array.from(grouped.keys()).sort();
7518
8033
  for (const provider of providers) {
7519
8034
  const providerModels = grouped.get(provider);
7520
8035
  const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
7521
- stream2.write(import_chalk4.default.bold.yellow(`${providerName} Models
8036
+ stream2.write(import_chalk6.default.bold.yellow(`${providerName} Models
7522
8037
  `));
7523
8038
  if (verbose) {
7524
8039
  renderVerboseTable(providerModels, stream2);
@@ -7527,11 +8042,11 @@ function renderTable(models, verbose, stream2) {
7527
8042
  }
7528
8043
  stream2.write("\n");
7529
8044
  }
7530
- stream2.write(import_chalk4.default.bold.magenta("Model Shortcuts\n"));
7531
- stream2.write(import_chalk4.default.dim("\u2500".repeat(80)) + "\n");
8045
+ stream2.write(import_chalk6.default.bold.magenta("Model Shortcuts\n"));
8046
+ stream2.write(import_chalk6.default.dim("\u2500".repeat(80)) + "\n");
7532
8047
  const shortcuts = Object.entries(MODEL_ALIASES).sort((a, b) => a[0].localeCompare(b[0]));
7533
8048
  for (const [shortcut, fullName] of shortcuts) {
7534
- stream2.write(import_chalk4.default.cyan(` ${shortcut.padEnd(15)}`) + import_chalk4.default.dim(" \u2192 ") + import_chalk4.default.white(fullName) + "\n");
8049
+ stream2.write(import_chalk6.default.cyan(` ${shortcut.padEnd(15)}`) + import_chalk6.default.dim(" \u2192 ") + import_chalk6.default.white(fullName) + "\n");
7535
8050
  }
7536
8051
  stream2.write("\n");
7537
8052
  }
@@ -7541,45 +8056,45 @@ function renderCompactTable(models, stream2) {
7541
8056
  const contextWidth = 13;
7542
8057
  const inputWidth = 10;
7543
8058
  const outputWidth = 10;
7544
- stream2.write(import_chalk4.default.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
8059
+ stream2.write(import_chalk6.default.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
7545
8060
  stream2.write(
7546
- import_chalk4.default.bold(
8061
+ import_chalk6.default.bold(
7547
8062
  "Model ID".padEnd(idWidth) + " " + "Display Name".padEnd(nameWidth) + " " + "Context".padEnd(contextWidth) + " " + "Input".padEnd(inputWidth) + " " + "Output".padEnd(outputWidth)
7548
8063
  ) + "\n"
7549
8064
  );
7550
- stream2.write(import_chalk4.default.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
8065
+ stream2.write(import_chalk6.default.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
7551
8066
  for (const model of models) {
7552
8067
  const contextFormatted = formatTokens2(model.contextWindow);
7553
8068
  const inputPrice = `$${model.pricing.input.toFixed(2)}`;
7554
8069
  const outputPrice = `$${model.pricing.output.toFixed(2)}`;
7555
8070
  stream2.write(
7556
- import_chalk4.default.green(model.modelId.padEnd(idWidth)) + " " + import_chalk4.default.white(model.displayName.padEnd(nameWidth)) + " " + import_chalk4.default.yellow(contextFormatted.padEnd(contextWidth)) + " " + import_chalk4.default.cyan(inputPrice.padEnd(inputWidth)) + " " + import_chalk4.default.cyan(outputPrice.padEnd(outputWidth)) + "\n"
8071
+ import_chalk6.default.green(model.modelId.padEnd(idWidth)) + " " + import_chalk6.default.white(model.displayName.padEnd(nameWidth)) + " " + import_chalk6.default.yellow(contextFormatted.padEnd(contextWidth)) + " " + import_chalk6.default.cyan(inputPrice.padEnd(inputWidth)) + " " + import_chalk6.default.cyan(outputPrice.padEnd(outputWidth)) + "\n"
7557
8072
  );
7558
8073
  }
7559
- stream2.write(import_chalk4.default.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
7560
- stream2.write(import_chalk4.default.dim(` * Prices are per 1M tokens
8074
+ stream2.write(import_chalk6.default.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
8075
+ stream2.write(import_chalk6.default.dim(` * Prices are per 1M tokens
7561
8076
  `));
7562
8077
  }
7563
8078
  function renderVerboseTable(models, stream2) {
7564
8079
  for (const model of models) {
7565
- stream2.write(import_chalk4.default.bold.green(`
8080
+ stream2.write(import_chalk6.default.bold.green(`
7566
8081
  ${model.modelId}
7567
8082
  `));
7568
- stream2.write(import_chalk4.default.dim(" " + "\u2500".repeat(60)) + "\n");
7569
- stream2.write(` ${import_chalk4.default.dim("Name:")} ${import_chalk4.default.white(model.displayName)}
8083
+ stream2.write(import_chalk6.default.dim(" " + "\u2500".repeat(60)) + "\n");
8084
+ stream2.write(` ${import_chalk6.default.dim("Name:")} ${import_chalk6.default.white(model.displayName)}
7570
8085
  `);
7571
- stream2.write(` ${import_chalk4.default.dim("Context:")} ${import_chalk4.default.yellow(formatTokens2(model.contextWindow))}
8086
+ stream2.write(` ${import_chalk6.default.dim("Context:")} ${import_chalk6.default.yellow(formatTokens2(model.contextWindow))}
7572
8087
  `);
7573
- stream2.write(` ${import_chalk4.default.dim("Max Output:")} ${import_chalk4.default.yellow(formatTokens2(model.maxOutputTokens))}
8088
+ stream2.write(` ${import_chalk6.default.dim("Max Output:")} ${import_chalk6.default.yellow(formatTokens2(model.maxOutputTokens))}
7574
8089
  `);
7575
- stream2.write(` ${import_chalk4.default.dim("Pricing:")} ${import_chalk4.default.cyan(`$${model.pricing.input.toFixed(2)} input`)} ${import_chalk4.default.dim("/")} ${import_chalk4.default.cyan(`$${model.pricing.output.toFixed(2)} output`)} ${import_chalk4.default.dim("(per 1M tokens)")}
8090
+ stream2.write(` ${import_chalk6.default.dim("Pricing:")} ${import_chalk6.default.cyan(`$${model.pricing.input.toFixed(2)} input`)} ${import_chalk6.default.dim("/")} ${import_chalk6.default.cyan(`$${model.pricing.output.toFixed(2)} output`)} ${import_chalk6.default.dim("(per 1M tokens)")}
7576
8091
  `);
7577
8092
  if (model.pricing.cachedInput !== void 0) {
7578
- stream2.write(` ${import_chalk4.default.dim("Cached Input:")} ${import_chalk4.default.cyan(`$${model.pricing.cachedInput.toFixed(2)} per 1M tokens`)}
8093
+ stream2.write(` ${import_chalk6.default.dim("Cached Input:")} ${import_chalk6.default.cyan(`$${model.pricing.cachedInput.toFixed(2)} per 1M tokens`)}
7579
8094
  `);
7580
8095
  }
7581
8096
  if (model.knowledgeCutoff) {
7582
- stream2.write(` ${import_chalk4.default.dim("Knowledge:")} ${model.knowledgeCutoff}
8097
+ stream2.write(` ${import_chalk6.default.dim("Knowledge:")} ${model.knowledgeCutoff}
7583
8098
  `);
7584
8099
  }
7585
8100
  const features = [];
@@ -7590,20 +8105,20 @@ function renderVerboseTable(models, stream2) {
7590
8105
  if (model.features.structuredOutputs) features.push("structured-outputs");
7591
8106
  if (model.features.fineTuning) features.push("fine-tuning");
7592
8107
  if (features.length > 0) {
7593
- stream2.write(` ${import_chalk4.default.dim("Features:")} ${import_chalk4.default.blue(features.join(", "))}
8108
+ stream2.write(` ${import_chalk6.default.dim("Features:")} ${import_chalk6.default.blue(features.join(", "))}
7594
8109
  `);
7595
8110
  }
7596
8111
  if (model.metadata) {
7597
8112
  if (model.metadata.family) {
7598
- stream2.write(` ${import_chalk4.default.dim("Family:")} ${model.metadata.family}
8113
+ stream2.write(` ${import_chalk6.default.dim("Family:")} ${model.metadata.family}
7599
8114
  `);
7600
8115
  }
7601
8116
  if (model.metadata.releaseDate) {
7602
- stream2.write(` ${import_chalk4.default.dim("Released:")} ${model.metadata.releaseDate}
8117
+ stream2.write(` ${import_chalk6.default.dim("Released:")} ${model.metadata.releaseDate}
7603
8118
  `);
7604
8119
  }
7605
8120
  if (model.metadata.notes) {
7606
- stream2.write(` ${import_chalk4.default.dim("Notes:")} ${import_chalk4.default.italic(model.metadata.notes)}
8121
+ stream2.write(` ${import_chalk6.default.dim("Notes:")} ${import_chalk6.default.italic(model.metadata.notes)}
7607
8122
  `);
7608
8123
  }
7609
8124
  }
@@ -7653,7 +8168,7 @@ function registerModelsCommand(program, env) {
7653
8168
 
7654
8169
  // src/cli/environment.ts
7655
8170
  var import_node_readline = __toESM(require("readline"), 1);
7656
- var import_chalk5 = __toESM(require("chalk"), 1);
8171
+ var import_chalk7 = __toESM(require("chalk"), 1);
7657
8172
  init_client();
7658
8173
  init_logger();
7659
8174
  var LOG_LEVEL_MAP = {
@@ -7702,14 +8217,14 @@ function createPromptFunction(stdin, stdout) {
7702
8217
  output: stdout
7703
8218
  });
7704
8219
  stdout.write("\n");
7705
- stdout.write(`${import_chalk5.default.cyan("\u2500".repeat(60))}
8220
+ stdout.write(`${import_chalk7.default.cyan("\u2500".repeat(60))}
7706
8221
  `);
7707
- stdout.write(import_chalk5.default.cyan.bold("\u{1F916} Agent asks:\n"));
8222
+ stdout.write(import_chalk7.default.cyan.bold("\u{1F916} Agent asks:\n"));
7708
8223
  stdout.write(`${question}
7709
8224
  `);
7710
- stdout.write(`${import_chalk5.default.cyan("\u2500".repeat(60))}
8225
+ stdout.write(`${import_chalk7.default.cyan("\u2500".repeat(60))}
7711
8226
  `);
7712
- rl.question(import_chalk5.default.green.bold("You: "), (answer) => {
8227
+ rl.question(import_chalk7.default.green.bold("You: "), (answer) => {
7713
8228
  rl.close();
7714
8229
  resolve(answer);
7715
8230
  });
@@ -7799,6 +8314,7 @@ function createProgram(env, config) {
7799
8314
  registerCompleteCommand(program, env, config?.complete);
7800
8315
  registerAgentCommand(program, env, config?.agent);
7801
8316
  registerModelsCommand(program, env);
8317
+ registerGadgetCommand(program, env);
7802
8318
  if (config) {
7803
8319
  const customNames = getCustomCommandNames(config);
7804
8320
  for (const name of customNames) {