@mistweaverco/kulala-cli 0.6.1 → 0.7.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/README.md CHANGED
@@ -37,10 +37,10 @@ You can also run it directly without installation using
37
37
  `npx`, `bunx`, `yarn dlx` or `pnpx`:
38
38
 
39
39
  ```sh
40
- npx @mistweaverco/kulala-cli run --report file.http
41
- bunx @mistweaverco/kulala-cli run --report file.http
42
- yarn dlx @mistweaverco/kulala-cli run --report file.http
43
- pnpx @mistweaverco/kulala-cli run --report file.http
40
+ npx @mistweaverco/kulala-cli run --tests file.http
41
+ bunx @mistweaverco/kulala-cli run --tests file.http
42
+ yarn dlx @mistweaverco/kulala-cli run --tests file.http
43
+ pnpx @mistweaverco/kulala-cli run --tests file.http
44
44
  ```
45
45
 
46
46
  On install, kulala-cli downloads a matching
@@ -71,22 +71,29 @@ Run a `.http` file and print the raw kulala-core JSON output:
71
71
  kulala run --json file.http
72
72
  ```
73
73
 
74
- Run a `.http` file and print a report:
74
+ Run a `.http` file and only print test output (full output on failures):
75
75
 
76
76
  ```sh
77
- kulala run --report file.http
77
+ kulala run --tests file.http
78
78
  ```
79
79
 
80
- Run all files in a directory and print a report:
80
+ Run curl with human-readable output (supports normal curl flags):
81
81
 
82
82
  ```sh
83
- kulala run --report ./requests
83
+ kulala curl -I https://echo.kulala.app/get
84
+ kulala curl -H "Accept: application/json" https://echo.kulala.app/get
84
85
  ```
85
86
 
86
- Run all files in a directory (in random order) and print a report:
87
+ Run all files in a directory and only print test output:
87
88
 
88
89
  ```sh
89
- kulala run --report --shuffle ./requests
90
+ kulala run --tests ./requests
91
+ ```
92
+
93
+ Run all files in a directory (in random order) and only print test output:
94
+
95
+ ```sh
96
+ kulala run --tests --shuffle ./requests
90
97
  ```
91
98
 
92
99
  Only print output when a request fails:
@@ -104,7 +111,7 @@ kulala run --halt ./requests
104
111
  Select an environment for variable resolution:
105
112
 
106
113
  ```sh
107
- kulala run --report --env=production file.http
114
+ kulala run --tests --env=production file.http
108
115
  ```
109
116
 
110
117
  Run a single request by block name (`###` name):
package/dist/cli.cjs CHANGED
@@ -46,7 +46,7 @@ let node_readline = require("node:readline");
46
46
  node_readline = __toESM(node_readline, 1);
47
47
  var package_default = {
48
48
  name: "@mistweaverco/kulala-cli",
49
- version: "0.6.1",
49
+ version: "0.7.0",
50
50
  repository: {
51
51
  "type": "git",
52
52
  "url": "https://github.com/mistweaverco/kulala-cli"
@@ -3471,7 +3471,7 @@ function fileWalker(inputPath, extensions) {
3471
3471
  }
3472
3472
  //#endregion
3473
3473
  //#region src/versions/backend.ts
3474
- var KULALA_CORE_VERSION = "0.22.1";
3474
+ var KULALA_CORE_VERSION = "0.24.0";
3475
3475
  //#endregion
3476
3476
  //#region src/lib/downloader/index.ts
3477
3477
  var BINARY_NAME = "kulala-core";
@@ -3671,9 +3671,17 @@ async function environments(options = {}, invokeOptions = {}) {
3671
3671
  filepath: options.filepath
3672
3672
  }, invokeOptions);
3673
3673
  }
3674
+ async function curl$1(options, invokeOptions = {}) {
3675
+ await executablePath();
3676
+ return invoke({
3677
+ action: "curl",
3678
+ argv: options.argv
3679
+ }, invokeOptions);
3680
+ }
3674
3681
  var kulalaCore = {
3675
3682
  runHttp,
3676
- environments
3683
+ environments,
3684
+ curl: curl$1
3677
3685
  };
3678
3686
  //#endregion
3679
3687
  //#region node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js
@@ -63074,6 +63082,16 @@ function splitScriptConsole(lines) {
63074
63082
  post
63075
63083
  };
63076
63084
  }
63085
+ function formatLabeledField(label, value) {
63086
+ return import_picocolors.default.bold(`${import_picocolors.default.cyan(`${label}:`)} ${value}`);
63087
+ }
63088
+ function formatRunHeader(filepath, blockName) {
63089
+ return [formatLabeledField("Filepath", filepath), formatLabeledField("Block name", blockName)].join("\n");
63090
+ }
63091
+ function itemDisplayName(item) {
63092
+ if ("blockName" in item && item.blockName) return item.blockName;
63093
+ return itemTitle(item);
63094
+ }
63077
63095
  function itemTitle(item) {
63078
63096
  if (isPromptResponse(item)) return `Prompt: ${item.promptType}`;
63079
63097
  if (isSkippedResponse(item)) return item.blockName ? `Skipped — ${item.blockName}` : "Skipped";
@@ -63153,8 +63171,8 @@ function formatScriptOutput(lines, requestFile) {
63153
63171
  }
63154
63172
  return parts.join("\n");
63155
63173
  }
63156
- function formatRequestHeader(method, url, blockName, status, durationMs, failed = false) {
63157
- const lines = [`${blockName ? import_picocolors.default.cyan(`${blockName}\n`) : ""}${import_picocolors.default.bold(method)} ${url}`];
63174
+ function formatRequestHeader(method, url, status, durationMs, failed = false) {
63175
+ const lines = [`${import_picocolors.default.bold(method)} ${url}`];
63158
63176
  if (status !== void 0) {
63159
63177
  const statusText = failed ? import_picocolors.default.red(`HTTP ${status}`) : statusColor(status)(`HTTP ${status}`);
63160
63178
  const duration = durationMs !== void 0 ? import_picocolors.default.dim(` · ${formatMs(durationMs)}`) : "";
@@ -63175,23 +63193,24 @@ function appendScriptSections(parts, scriptConsole, requestFile) {
63175
63193
  }
63176
63194
  }
63177
63195
  function formatItem(item, requestFile) {
63196
+ const header = requestFile ? `${formatRunHeader(requestFile, itemDisplayName(item))}\n` : "";
63178
63197
  if (isPromptResponse(item)) {
63179
- const parts = [import_picocolors.default.yellow(`Prompt (${item.promptType}): ${item.message}`)];
63198
+ const parts = [header + import_picocolors.default.yellow(`Prompt (${item.promptType}): ${item.message}`)];
63180
63199
  for (const input of item.inputs) parts.push(` - ${input.label} (${input.type}${input.required ? ", required" : ""})`);
63181
63200
  return parts.join("\n");
63182
63201
  }
63183
63202
  if (isSkippedResponse(item)) {
63184
- const parts = [import_picocolors.default.dim(`Skipped${item.blockName ? ` · ${item.blockName}` : ""}`)];
63203
+ const parts = [header + import_picocolors.default.dim(`Skipped${item.blockName ? ` · ${item.blockName}` : ""}`)];
63185
63204
  appendScriptSections(parts, item.scriptConsole, requestFile);
63186
63205
  return parts.join("\n");
63187
63206
  }
63188
63207
  if (isWebSocketResponse(item)) {
63189
- const parts = [import_picocolors.default.cyan(`WebSocket: ${item.url}`)];
63208
+ const parts = [header + import_picocolors.default.cyan(`WebSocket: ${item.url}`)];
63190
63209
  if (item.initialMessage) parts.push(import_picocolors.default.dim(`Initial message: ${item.initialMessage}`));
63191
63210
  return parts.join("\n");
63192
63211
  }
63193
63212
  if (isErrorResponse(item)) {
63194
- const parts = [formatRequestHeader(item.request?.method ?? "GET", item.url ?? item.blockName ?? "unknown", item.blockName, item.status, void 0, true)];
63213
+ const parts = [header + formatRequestHeader(item.request?.method ?? "GET", item.url ?? item.blockName ?? "unknown", item.status, void 0, true)];
63195
63214
  if (item.error) parts.push(import_picocolors.default.red(`Error: ${item.error}`));
63196
63215
  const bodySection = formatBody(item.body);
63197
63216
  if (bodySection) {
@@ -63202,7 +63221,7 @@ function formatItem(item, requestFile) {
63202
63221
  return parts.join("\n");
63203
63222
  }
63204
63223
  if (isSuccessResponse(item)) {
63205
- const parts = [formatRequestHeader(item.request?.method ?? "GET", item.url, item.blockName, item.status, item.timings?.total)];
63224
+ const parts = [header + formatRequestHeader(item.request?.method ?? "GET", item.url, item.status, item.timings?.total)];
63206
63225
  if (Object.keys(item.headers).length > 0) {
63207
63226
  parts.push("");
63208
63227
  parts.push(formatSection("Headers", formatHeaders(item.headers)));
@@ -63215,15 +63234,13 @@ function formatItem(item, requestFile) {
63215
63234
  appendScriptSections(parts, item.scriptConsole, requestFile);
63216
63235
  return parts.join("\n");
63217
63236
  }
63218
- return import_picocolors.default.dim("Unknown response type");
63237
+ return header + import_picocolors.default.dim("Unknown response type");
63219
63238
  }
63220
63239
  function formatWrapper(wrapper, requestFile) {
63221
63240
  return (wrapper.type === "error" ? wrapper.data : wrapper.data).map((entry) => formatItem(entry, requestFile)).join("\n\n");
63222
63241
  }
63223
63242
  function printHumanReadable(results) {
63224
- const blocks = results.map((result) => {
63225
- return `${results.length > 1 ? `${import_picocolors.default.bold(`# ${result.filepath}`)}\n` : ""}${formatWrapper(result.response, result.filepath)}`;
63226
- });
63243
+ const blocks = results.map((result) => formatWrapper(result.response, result.filepath));
63227
63244
  console.log(blocks.join("\n\n"));
63228
63245
  }
63229
63246
  function printJson(results) {
@@ -63386,6 +63403,63 @@ function printReport(results) {
63386
63403
  console.log(parts.join("\n"));
63387
63404
  }
63388
63405
  //#endregion
63406
+ //#region src/lib/output/tests.ts
63407
+ function wrapperItems(wrapper) {
63408
+ return wrapper.type === "error" ? wrapper.data : wrapper.data;
63409
+ }
63410
+ function hasAnyTests(item) {
63411
+ if (!("scriptConsole" in item) || !item.scriptConsole?.length) return false;
63412
+ const tree = parseAssertionTree(item.scriptConsole);
63413
+ return tree.tests.length > 0 || tree.standaloneAsserts.length > 0;
63414
+ }
63415
+ function hasTestFailures(item) {
63416
+ if (!("scriptConsole" in item) || !item.scriptConsole?.length) return false;
63417
+ const tree = parseAssertionTree(item.scriptConsole);
63418
+ if (tree.tests.some((t) => !t.pass)) return true;
63419
+ return tree.standaloneAsserts.some((a) => !a.pass);
63420
+ }
63421
+ function formatTestsOnly(item) {
63422
+ if (!("scriptConsole" in item) || !item.scriptConsole?.length) return "";
63423
+ const tree = parseAssertionTree(item.scriptConsole);
63424
+ const lines = [];
63425
+ const GROUP_SYMBOL = "";
63426
+ const PASS_SYMBOL = "✓";
63427
+ const FAIL_SYMBOL = "✗";
63428
+ for (const test of tree.tests) {
63429
+ lines.push(test.pass ? import_picocolors.default.green(`${PASS_SYMBOL} ${GROUP_SYMBOL} ${test.name}`) : import_picocolors.default.red(`${FAIL_SYMBOL} ${GROUP_SYMBOL} ${test.name}`));
63430
+ for (const assert of test.asserts) lines.push(assert.pass ? import_picocolors.default.green(` ${PASS_SYMBOL} ${assert.message}`) : import_picocolors.default.red(` ${FAIL_SYMBOL} ${assert.message}`));
63431
+ }
63432
+ for (const assert of tree.standaloneAsserts) lines.push(assert.pass ? import_picocolors.default.green(`${PASS_SYMBOL} ${assert.message}`) : import_picocolors.default.red(`${FAIL_SYMBOL} ${assert.message}`));
63433
+ return lines.join("\n").trim();
63434
+ }
63435
+ function printTests(results, options) {
63436
+ const blocks = [];
63437
+ for (const result of results) {
63438
+ const items = wrapperItems(result.response);
63439
+ for (const item of items) {
63440
+ const requestFailed = !isResponseSuccessful(item);
63441
+ const testsFailed = hasTestFailures(item);
63442
+ if (!(options.quiet ? requestFailed || testsFailed : true)) continue;
63443
+ if (requestFailed || testsFailed) {
63444
+ printHumanReadable([{
63445
+ filepath: result.filepath,
63446
+ response: {
63447
+ type: "responses",
63448
+ data: [item]
63449
+ }
63450
+ }]);
63451
+ continue;
63452
+ }
63453
+ if (!hasAnyTests(item)) continue;
63454
+ const testsText = formatTestsOnly(item);
63455
+ if (!testsText) continue;
63456
+ const header = formatRunHeader(result.filepath, itemDisplayName(item));
63457
+ blocks.push(`${header}\n${testsText}`);
63458
+ }
63459
+ }
63460
+ if (blocks.length > 0 && !options.quiet) console.log(blocks.join("\n\n"));
63461
+ }
63462
+ //#endregion
63389
63463
  //#region src/lib/runner/limit.ts
63390
63464
  function buildRunLimit(options) {
63391
63465
  const hasName = options.name !== void 0 && options.name.length > 0;
@@ -63486,6 +63560,7 @@ function hasFailures(results) {
63486
63560
  }
63487
63561
  async function run(inputPath, options) {
63488
63562
  if (options.color === false) setColorEnabled(false);
63563
+ if (options.report && !options.tests) options.tests = true;
63489
63564
  const limit = buildRunLimit(options);
63490
63565
  const files = limit ? [resolveSingleHttpFile(inputPath)] : resolveFiles(inputPath, options.shuffle ?? false);
63491
63566
  const results = [];
@@ -63495,13 +63570,65 @@ async function run(inputPath, options) {
63495
63570
  results.push(result);
63496
63571
  if (halt && responseHasFailure(result)) break;
63497
63572
  }
63498
- const outputResults = options.quiet ? filterFailedResults(results) : results;
63499
- if (!options.quiet || outputResults.length > 0) if (options.json) printJson(outputResults);
63573
+ const outputResults = options.quiet && !options.tests ? filterFailedResults(results) : results;
63574
+ if (options.tests) printTests(results, { quiet: options.quiet ?? false });
63575
+ else if (!options.quiet || outputResults.length > 0) if (options.json) printJson(outputResults);
63500
63576
  else if (options.report) printReport(outputResults);
63501
63577
  else printHumanReadable(outputResults);
63502
63578
  if (hasFailures(results)) process.exit(1);
63503
63579
  }
63504
63580
  //#endregion
63581
+ //#region src/lib/runner/curl.ts
63582
+ var KULALA_CURL_FLAGS = new Set(["--json", "--no-color"]);
63583
+ function parseCurlArgv(argv) {
63584
+ const curlArgv = [];
63585
+ const options = {};
63586
+ for (const arg of argv) {
63587
+ if (arg === "--json") {
63588
+ options.json = true;
63589
+ continue;
63590
+ }
63591
+ if (arg === "--no-color") {
63592
+ options.color = false;
63593
+ continue;
63594
+ }
63595
+ if (KULALA_CURL_FLAGS.has(arg)) continue;
63596
+ curlArgv.push(arg);
63597
+ }
63598
+ return {
63599
+ curlArgv,
63600
+ options
63601
+ };
63602
+ }
63603
+ function formatCurlLabel(curlArgv) {
63604
+ if (curlArgv.length === 0) return "curl";
63605
+ return `curl ${curlArgv.join(" ")}`;
63606
+ }
63607
+ async function curl(argv, options = {}) {
63608
+ if (options.color === false) setColorEnabled(false);
63609
+ const { curlArgv, options: parsedOptions } = parseCurlArgv(argv);
63610
+ const mergedOptions = {
63611
+ ...parsedOptions,
63612
+ ...options
63613
+ };
63614
+ if (curlArgv.length === 0) {
63615
+ console.error("Usage: kulala curl [options] <curl arguments...>");
63616
+ console.error("");
63617
+ console.error("Examples:");
63618
+ console.error(" kulala curl -I https://example.com");
63619
+ console.error(" kulala curl -H \"Accept: application/json\" https://echo.kulala.app/get");
63620
+ process.exit(1);
63621
+ }
63622
+ const response = await kulalaCore.curl({ argv: curlArgv });
63623
+ const result = {
63624
+ filepath: formatCurlLabel(curlArgv),
63625
+ response
63626
+ };
63627
+ if (mergedOptions.json) printJson([result]);
63628
+ else printHumanReadable([result]);
63629
+ if (countResults(response).failed > 0) process.exit(1);
63630
+ }
63631
+ //#endregion
63505
63632
  //#region node_modules/.pnpm/@inquirer+core@11.2.1_@types+node@25.9.1/node_modules/@inquirer/core/dist/lib/key.js
63506
63633
  var keybindingLookup = new Set(["emacs", "vim"]);
63507
63634
  function isKeybinding(value) {
@@ -65222,39 +65349,49 @@ function extractRequestNamesFromHttp(content) {
65222
65349
  //#region src/cli.ts
65223
65350
  var program = new Command();
65224
65351
  program.name("kulala").description("A fully-featured HTTP/GraphQL/gRPC/WebSocket client for your command-line.").version(package_default.version);
65225
- program.command("run").description("Run .http or .rest files").argument("<path>", "file or directory to run").option("--json", "print raw kulala-core JSON output").option("--report", "print a summary report").option("--no-color", "disable syntax highlighting and colors").option("-q, --quiet", "only print output when errors occur").option("--halt", "stop after the first failing request or file").option("--shuffle", "shuffle files when running a directory").option("--env [name]", "environment name for variable resolution").option("--name [name]", "run a single request by block name").option("--line <line>", "run request at 1-based line number", (value) => Number.parseInt(value, 10)).option("--column <column>", "1-based column for --line (default: 1)", (value) => Number.parseInt(value, 10)).action(async (inputPath, options) => {
65226
- if (options.name === true) {
65227
- const relativeFile = resolveSingleHttpFile(inputPath);
65228
- const absolutePath = node_path.default.resolve(process.cwd(), relativeFile);
65229
- const requestNames = extractRequestNamesFromHttp(node_fs.default.readFileSync(absolutePath, "utf-8"));
65230
- if (requestNames.length === 0) {
65231
- console.error(chalk.red("No request blocks found (expected lines like: ### MyRequest)"));
65232
- process.exit(1);
65233
- }
65234
- options.name = await pickOne("Select request to run", requestNames.map((n) => ({
65235
- name: n,
65236
- value: n
65237
- })));
65238
- }
65239
- if (options.env === true) {
65240
- const startDir = node_fs.default.existsSync(inputPath) ? node_fs.default.statSync(inputPath).isDirectory() ? node_path.default.resolve(process.cwd(), inputPath) : node_path.default.dirname(node_path.default.resolve(process.cwd(), inputPath)) : process.cwd();
65241
- const absoluteInputPath = node_path.default.resolve(process.cwd(), inputPath);
65242
- const filepath = node_fs.default.existsSync(absoluteInputPath) && node_fs.default.statSync(absoluteInputPath).isFile() ? absoluteInputPath : void 0;
65243
- const catalog = await kulalaCore.environments({
65244
- cwd: startDir,
65245
- filepath
65246
- });
65247
- const envNames = Object.keys(catalog.environments ?? {}).sort((a, b) => a.localeCompare(b));
65248
- if (envNames.length === 0) {
65249
- console.error(chalk.red("No environments found."));
65250
- process.exit(1);
65352
+ async function main() {
65353
+ if (process.argv[2] === "curl") {
65354
+ await curl(process.argv.slice(3));
65355
+ return;
65356
+ }
65357
+ program.command("run").description("Run .http or .rest files").argument("<path>", "file or directory to run").option("--json", "print raw kulala-core JSON output").option("--tests", "only print test output (full output on failures)").option("--report", "(deprecated) use --tests").option("--no-color", "disable syntax highlighting and colors").option("-q, --quiet", "only print output when errors occur").option("--halt", "stop after the first failing request or file").option("--shuffle", "shuffle files when running a directory").option("--env [name]", "environment name for variable resolution").option("--name [name]", "run a single request by block name").option("--line <line>", "run request at 1-based line number", (value) => Number.parseInt(value, 10)).option("--column <column>", "1-based column for --line (default: 1)", (value) => Number.parseInt(value, 10)).action(async (inputPath, options) => {
65358
+ if (options.name === true) {
65359
+ const relativeFile = resolveSingleHttpFile(inputPath);
65360
+ const absolutePath = node_path.default.resolve(process.cwd(), relativeFile);
65361
+ const requestNames = extractRequestNamesFromHttp(node_fs.default.readFileSync(absolutePath, "utf-8"));
65362
+ if (requestNames.length === 0) {
65363
+ console.error(chalk.red("No request blocks found (expected lines like: ### MyRequest)"));
65364
+ process.exit(1);
65365
+ }
65366
+ options.name = await pickOne("Select request to run", requestNames.map((n) => ({
65367
+ name: n,
65368
+ value: n
65369
+ })));
65370
+ }
65371
+ if (options.env === true) {
65372
+ const startDir = node_fs.default.existsSync(inputPath) ? node_fs.default.statSync(inputPath).isDirectory() ? node_path.default.resolve(process.cwd(), inputPath) : node_path.default.dirname(node_path.default.resolve(process.cwd(), inputPath)) : process.cwd();
65373
+ const absoluteInputPath = node_path.default.resolve(process.cwd(), inputPath);
65374
+ const filepath = node_fs.default.existsSync(absoluteInputPath) && node_fs.default.statSync(absoluteInputPath).isFile() ? absoluteInputPath : void 0;
65375
+ const catalog = await kulalaCore.environments({
65376
+ cwd: startDir,
65377
+ filepath
65378
+ });
65379
+ const envNames = Object.keys(catalog.environments ?? {}).sort((a, b) => a.localeCompare(b));
65380
+ if (envNames.length === 0) {
65381
+ console.error(chalk.red("No environments found."));
65382
+ process.exit(1);
65383
+ }
65384
+ options.env = await pickOne("Select environment", envNames.map((n) => ({
65385
+ name: n,
65386
+ value: n
65387
+ })));
65251
65388
  }
65252
- options.env = await pickOne("Select environment", envNames.map((n) => ({
65253
- name: n,
65254
- value: n
65255
- })));
65256
- }
65257
- await run(inputPath, options);
65389
+ await run(inputPath, options);
65390
+ });
65391
+ program.parse();
65392
+ }
65393
+ main().catch((error) => {
65394
+ console.error(error instanceof Error ? error.message : String(error));
65395
+ process.exit(1);
65258
65396
  });
65259
- program.parse();
65260
65397
  //#endregion
@@ -29,7 +29,7 @@ let path = require("path");
29
29
  path = __toESM(path, 1);
30
30
  let stream_promises = require("stream/promises");
31
31
  //#region src/versions/backend.ts
32
- var KULALA_CORE_VERSION = "0.22.1";
32
+ var KULALA_CORE_VERSION = "0.24.0";
33
33
  //#endregion
34
34
  //#region src/lib/downloader/index.ts
35
35
  var BINARY_NAME = "kulala-core";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mistweaverco/kulala-cli",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/mistweaverco/kulala-cli"