@mistweaverco/kulala-cli 0.6.1 → 0.7.1

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.1",
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.1";
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,10 +63171,14 @@ 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 formatStatusLine(status, httpVersion, failed = false) {
63175
+ const label = httpVersion ? `${httpVersion} ${status}` : `HTTP ${status}`;
63176
+ return failed ? import_picocolors.default.red(label) : statusColor(status)(label);
63177
+ }
63178
+ function formatRequestHeader(method, url, status, durationMs, failed = false, httpVersion) {
63179
+ const lines = [`${import_picocolors.default.bold(method)} ${url}`];
63158
63180
  if (status !== void 0) {
63159
- const statusText = failed ? import_picocolors.default.red(`HTTP ${status}`) : statusColor(status)(`HTTP ${status}`);
63181
+ const statusText = formatStatusLine(status, httpVersion, failed);
63160
63182
  const duration = durationMs !== void 0 ? import_picocolors.default.dim(` · ${formatMs(durationMs)}`) : "";
63161
63183
  lines.push(`${statusText}${duration}`);
63162
63184
  }
@@ -63175,23 +63197,24 @@ function appendScriptSections(parts, scriptConsole, requestFile) {
63175
63197
  }
63176
63198
  }
63177
63199
  function formatItem(item, requestFile) {
63200
+ const header = requestFile ? `${formatRunHeader(requestFile, itemDisplayName(item))}\n` : "";
63178
63201
  if (isPromptResponse(item)) {
63179
- const parts = [import_picocolors.default.yellow(`Prompt (${item.promptType}): ${item.message}`)];
63202
+ const parts = [header + import_picocolors.default.yellow(`Prompt (${item.promptType}): ${item.message}`)];
63180
63203
  for (const input of item.inputs) parts.push(` - ${input.label} (${input.type}${input.required ? ", required" : ""})`);
63181
63204
  return parts.join("\n");
63182
63205
  }
63183
63206
  if (isSkippedResponse(item)) {
63184
- const parts = [import_picocolors.default.dim(`Skipped${item.blockName ? ` · ${item.blockName}` : ""}`)];
63207
+ const parts = [header + import_picocolors.default.dim(`Skipped${item.blockName ? ` · ${item.blockName}` : ""}`)];
63185
63208
  appendScriptSections(parts, item.scriptConsole, requestFile);
63186
63209
  return parts.join("\n");
63187
63210
  }
63188
63211
  if (isWebSocketResponse(item)) {
63189
- const parts = [import_picocolors.default.cyan(`WebSocket: ${item.url}`)];
63212
+ const parts = [header + import_picocolors.default.cyan(`WebSocket: ${item.url}`)];
63190
63213
  if (item.initialMessage) parts.push(import_picocolors.default.dim(`Initial message: ${item.initialMessage}`));
63191
63214
  return parts.join("\n");
63192
63215
  }
63193
63216
  if (isErrorResponse(item)) {
63194
- const parts = [formatRequestHeader(item.request?.method ?? "GET", item.url ?? item.blockName ?? "unknown", item.blockName, item.status, void 0, true)];
63217
+ const parts = [header + formatRequestHeader(item.request?.method ?? "GET", item.url ?? item.blockName ?? "unknown", item.status, void 0, true, item.httpVersion)];
63195
63218
  if (item.error) parts.push(import_picocolors.default.red(`Error: ${item.error}`));
63196
63219
  const bodySection = formatBody(item.body);
63197
63220
  if (bodySection) {
@@ -63202,7 +63225,7 @@ function formatItem(item, requestFile) {
63202
63225
  return parts.join("\n");
63203
63226
  }
63204
63227
  if (isSuccessResponse(item)) {
63205
- const parts = [formatRequestHeader(item.request?.method ?? "GET", item.url, item.blockName, item.status, item.timings?.total)];
63228
+ const parts = [header + formatRequestHeader(item.request?.method ?? "GET", item.url, item.status, item.timings?.total, false, item.httpVersion)];
63206
63229
  if (Object.keys(item.headers).length > 0) {
63207
63230
  parts.push("");
63208
63231
  parts.push(formatSection("Headers", formatHeaders(item.headers)));
@@ -63215,15 +63238,13 @@ function formatItem(item, requestFile) {
63215
63238
  appendScriptSections(parts, item.scriptConsole, requestFile);
63216
63239
  return parts.join("\n");
63217
63240
  }
63218
- return import_picocolors.default.dim("Unknown response type");
63241
+ return header + import_picocolors.default.dim("Unknown response type");
63219
63242
  }
63220
63243
  function formatWrapper(wrapper, requestFile) {
63221
63244
  return (wrapper.type === "error" ? wrapper.data : wrapper.data).map((entry) => formatItem(entry, requestFile)).join("\n\n");
63222
63245
  }
63223
63246
  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
- });
63247
+ const blocks = results.map((result) => formatWrapper(result.response, result.filepath));
63227
63248
  console.log(blocks.join("\n\n"));
63228
63249
  }
63229
63250
  function printJson(results) {
@@ -63386,6 +63407,63 @@ function printReport(results) {
63386
63407
  console.log(parts.join("\n"));
63387
63408
  }
63388
63409
  //#endregion
63410
+ //#region src/lib/output/tests.ts
63411
+ function wrapperItems(wrapper) {
63412
+ return wrapper.type === "error" ? wrapper.data : wrapper.data;
63413
+ }
63414
+ function hasAnyTests(item) {
63415
+ if (!("scriptConsole" in item) || !item.scriptConsole?.length) return false;
63416
+ const tree = parseAssertionTree(item.scriptConsole);
63417
+ return tree.tests.length > 0 || tree.standaloneAsserts.length > 0;
63418
+ }
63419
+ function hasTestFailures(item) {
63420
+ if (!("scriptConsole" in item) || !item.scriptConsole?.length) return false;
63421
+ const tree = parseAssertionTree(item.scriptConsole);
63422
+ if (tree.tests.some((t) => !t.pass)) return true;
63423
+ return tree.standaloneAsserts.some((a) => !a.pass);
63424
+ }
63425
+ function formatTestsOnly(item) {
63426
+ if (!("scriptConsole" in item) || !item.scriptConsole?.length) return "";
63427
+ const tree = parseAssertionTree(item.scriptConsole);
63428
+ const lines = [];
63429
+ const GROUP_SYMBOL = "";
63430
+ const PASS_SYMBOL = "✓";
63431
+ const FAIL_SYMBOL = "✗";
63432
+ for (const test of tree.tests) {
63433
+ lines.push(test.pass ? import_picocolors.default.green(`${PASS_SYMBOL} ${GROUP_SYMBOL} ${test.name}`) : import_picocolors.default.red(`${FAIL_SYMBOL} ${GROUP_SYMBOL} ${test.name}`));
63434
+ 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}`));
63435
+ }
63436
+ 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}`));
63437
+ return lines.join("\n").trim();
63438
+ }
63439
+ function printTests(results, options) {
63440
+ const blocks = [];
63441
+ for (const result of results) {
63442
+ const items = wrapperItems(result.response);
63443
+ for (const item of items) {
63444
+ const requestFailed = !isResponseSuccessful(item);
63445
+ const testsFailed = hasTestFailures(item);
63446
+ if (!(options.quiet ? requestFailed || testsFailed : true)) continue;
63447
+ if (requestFailed || testsFailed) {
63448
+ printHumanReadable([{
63449
+ filepath: result.filepath,
63450
+ response: {
63451
+ type: "responses",
63452
+ data: [item]
63453
+ }
63454
+ }]);
63455
+ continue;
63456
+ }
63457
+ if (!hasAnyTests(item)) continue;
63458
+ const testsText = formatTestsOnly(item);
63459
+ if (!testsText) continue;
63460
+ const header = formatRunHeader(result.filepath, itemDisplayName(item));
63461
+ blocks.push(`${header}\n${testsText}`);
63462
+ }
63463
+ }
63464
+ if (blocks.length > 0 && !options.quiet) console.log(blocks.join("\n\n"));
63465
+ }
63466
+ //#endregion
63389
63467
  //#region src/lib/runner/limit.ts
63390
63468
  function buildRunLimit(options) {
63391
63469
  const hasName = options.name !== void 0 && options.name.length > 0;
@@ -63486,6 +63564,7 @@ function hasFailures(results) {
63486
63564
  }
63487
63565
  async function run(inputPath, options) {
63488
63566
  if (options.color === false) setColorEnabled(false);
63567
+ if (options.report && !options.tests) options.tests = true;
63489
63568
  const limit = buildRunLimit(options);
63490
63569
  const files = limit ? [resolveSingleHttpFile(inputPath)] : resolveFiles(inputPath, options.shuffle ?? false);
63491
63570
  const results = [];
@@ -63495,13 +63574,65 @@ async function run(inputPath, options) {
63495
63574
  results.push(result);
63496
63575
  if (halt && responseHasFailure(result)) break;
63497
63576
  }
63498
- const outputResults = options.quiet ? filterFailedResults(results) : results;
63499
- if (!options.quiet || outputResults.length > 0) if (options.json) printJson(outputResults);
63577
+ const outputResults = options.quiet && !options.tests ? filterFailedResults(results) : results;
63578
+ if (options.tests) printTests(results, { quiet: options.quiet ?? false });
63579
+ else if (!options.quiet || outputResults.length > 0) if (options.json) printJson(outputResults);
63500
63580
  else if (options.report) printReport(outputResults);
63501
63581
  else printHumanReadable(outputResults);
63502
63582
  if (hasFailures(results)) process.exit(1);
63503
63583
  }
63504
63584
  //#endregion
63585
+ //#region src/lib/runner/curl.ts
63586
+ var KULALA_CURL_FLAGS = new Set(["--json", "--no-color"]);
63587
+ function parseCurlArgv(argv) {
63588
+ const curlArgv = [];
63589
+ const options = {};
63590
+ for (const arg of argv) {
63591
+ if (arg === "--json") {
63592
+ options.json = true;
63593
+ continue;
63594
+ }
63595
+ if (arg === "--no-color") {
63596
+ options.color = false;
63597
+ continue;
63598
+ }
63599
+ if (KULALA_CURL_FLAGS.has(arg)) continue;
63600
+ curlArgv.push(arg);
63601
+ }
63602
+ return {
63603
+ curlArgv,
63604
+ options
63605
+ };
63606
+ }
63607
+ function formatCurlLabel(curlArgv) {
63608
+ if (curlArgv.length === 0) return "curl";
63609
+ return `curl ${curlArgv.join(" ")}`;
63610
+ }
63611
+ async function curl(argv, options = {}) {
63612
+ if (options.color === false) setColorEnabled(false);
63613
+ const { curlArgv, options: parsedOptions } = parseCurlArgv(argv);
63614
+ const mergedOptions = {
63615
+ ...parsedOptions,
63616
+ ...options
63617
+ };
63618
+ if (curlArgv.length === 0) {
63619
+ console.error("Usage: kulala curl [options] <curl arguments...>");
63620
+ console.error("");
63621
+ console.error("Examples:");
63622
+ console.error(" kulala curl -I https://example.com");
63623
+ console.error(" kulala curl -H \"Accept: application/json\" https://echo.kulala.app/get");
63624
+ process.exit(1);
63625
+ }
63626
+ const response = await kulalaCore.curl({ argv: curlArgv });
63627
+ const result = {
63628
+ filepath: formatCurlLabel(curlArgv),
63629
+ response
63630
+ };
63631
+ if (mergedOptions.json) printJson([result]);
63632
+ else printHumanReadable([result]);
63633
+ if (countResults(response).failed > 0) process.exit(1);
63634
+ }
63635
+ //#endregion
63505
63636
  //#region node_modules/.pnpm/@inquirer+core@11.2.1_@types+node@25.9.1/node_modules/@inquirer/core/dist/lib/key.js
63506
63637
  var keybindingLookup = new Set(["emacs", "vim"]);
63507
63638
  function isKeybinding(value) {
@@ -65222,39 +65353,49 @@ function extractRequestNamesFromHttp(content) {
65222
65353
  //#region src/cli.ts
65223
65354
  var program = new Command();
65224
65355
  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);
65356
+ async function main() {
65357
+ if (process.argv[2] === "curl") {
65358
+ await curl(process.argv.slice(3));
65359
+ return;
65360
+ }
65361
+ 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) => {
65362
+ if (options.name === true) {
65363
+ const relativeFile = resolveSingleHttpFile(inputPath);
65364
+ const absolutePath = node_path.default.resolve(process.cwd(), relativeFile);
65365
+ const requestNames = extractRequestNamesFromHttp(node_fs.default.readFileSync(absolutePath, "utf-8"));
65366
+ if (requestNames.length === 0) {
65367
+ console.error(chalk.red("No request blocks found (expected lines like: ### MyRequest)"));
65368
+ process.exit(1);
65369
+ }
65370
+ options.name = await pickOne("Select request to run", requestNames.map((n) => ({
65371
+ name: n,
65372
+ value: n
65373
+ })));
65374
+ }
65375
+ if (options.env === true) {
65376
+ 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();
65377
+ const absoluteInputPath = node_path.default.resolve(process.cwd(), inputPath);
65378
+ const filepath = node_fs.default.existsSync(absoluteInputPath) && node_fs.default.statSync(absoluteInputPath).isFile() ? absoluteInputPath : void 0;
65379
+ const catalog = await kulalaCore.environments({
65380
+ cwd: startDir,
65381
+ filepath
65382
+ });
65383
+ const envNames = Object.keys(catalog.environments ?? {}).sort((a, b) => a.localeCompare(b));
65384
+ if (envNames.length === 0) {
65385
+ console.error(chalk.red("No environments found."));
65386
+ process.exit(1);
65387
+ }
65388
+ options.env = await pickOne("Select environment", envNames.map((n) => ({
65389
+ name: n,
65390
+ value: n
65391
+ })));
65251
65392
  }
65252
- options.env = await pickOne("Select environment", envNames.map((n) => ({
65253
- name: n,
65254
- value: n
65255
- })));
65256
- }
65257
- await run(inputPath, options);
65393
+ await run(inputPath, options);
65394
+ });
65395
+ program.parse();
65396
+ }
65397
+ main().catch((error) => {
65398
+ console.error(error instanceof Error ? error.message : String(error));
65399
+ process.exit(1);
65258
65400
  });
65259
- program.parse();
65260
65401
  //#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.1";
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.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/mistweaverco/kulala-cli"