@mutagent/cli 0.1.396 → 0.1.398

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 (4) hide show
  1. package/README.md +37 -17
  2. package/dist/bin/cli.js +31120 -25907
  3. package/dist/index.js +559 -322
  4. package/package.json +4 -2
package/dist/index.js CHANGED
@@ -425,116 +425,357 @@ var init_config = __esm(() => {
425
425
  CREDENTIALS_FILE = join(CREDENTIALS_DIR, "credentials.json");
426
426
  });
427
427
 
428
- // src/lib/errors.ts
429
- function createErrorFromUnknown(error) {
430
- if (error instanceof MutagentError) {
431
- return error;
428
+ // src/lib/command-path.ts
429
+ function commandPathOf(command) {
430
+ const names = [];
431
+ for (let current = command;current.parent; current = current.parent) {
432
+ names.unshift(current.name());
432
433
  }
433
- if (error instanceof Error) {
434
- return new MutagentError("UNKNOWN_ERROR", error.message, undefined, 1);
435
- }
436
- return new MutagentError("UNKNOWN_ERROR", typeof error === "string" ? error : "An unknown error occurred", undefined, 1);
434
+ return names.join(" ");
437
435
  }
438
- function handleError(error, isJson) {
439
- if (error instanceof MutagentError) {
440
- if (isJson) {
441
- const jsonOutput = error.toJSON();
442
- if (error instanceof AuthenticationError) {
443
- jsonOutput.remediation = {
444
- interactive: "mutagent auth login --browser",
445
- nonInteractive: "export MUTAGENT_API_KEY=<your-key>",
446
- ciCd: "mutagent auth login --api-key <key>"
447
- };
448
- }
449
- if (error instanceof WorkspaceContextError) {
450
- jsonOutput.remediation = {
451
- setWorkspace: "mutagent workspaces use <name>",
452
- listWorkspaces: "mutagent workspaces list"
453
- };
454
- }
455
- if (error instanceof ApiError && error.statusCode === 403) {
456
- jsonOutput.remediation = {
457
- interactive: "mutagent auth login --browser",
458
- nonInteractive: "export MUTAGENT_API_KEY=<your-key>",
459
- checkPermissions: "Verify your API key has the required permissions"
460
- };
436
+ function setRunningCommandPath(path) {
437
+ runningPath = path;
438
+ }
439
+ function runningHelpCommand() {
440
+ return runningPath ? `mutagent ${runningPath} --help` : "mutagent --help";
441
+ }
442
+ var runningPath;
443
+
444
+ // src/lib/output.ts
445
+ import chalk from "chalk";
446
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
447
+ import { join as join2 } from "path";
448
+ function setCliVersion(version) {
449
+ _globalCliVersion = version;
450
+ _compatCache = null;
451
+ }
452
+ function getCliVersion() {
453
+ return _globalCliVersion;
454
+ }
455
+ function getCompatMetadata(cliVersion = _globalCliVersion) {
456
+ if (_compatCache !== null)
457
+ return _compatCache;
458
+ let skillVersion = "unknown";
459
+ let skillMinCliVersion = "unknown";
460
+ try {
461
+ const candidatePaths = [
462
+ join2(process.cwd(), ".claude", "skills", "mutagent-cli", "SKILL.md"),
463
+ join2(process.cwd(), "mutagent-cli", ".claude", "skills", "mutagent-cli", "SKILL.md")
464
+ ];
465
+ for (const p of candidatePaths) {
466
+ if (existsSync2(p)) {
467
+ const content = readFileSync2(p, "utf-8");
468
+ const verMatch = /^\s+skill_version:\s+"?([^"\n]+)"?\s*$/m.exec(content);
469
+ const minMatch = /^\s+skill_min_cli_version:\s+"?([^"\n]+)"?\s*$/m.exec(content);
470
+ if (/^metadata:/m.test(content) && !verMatch && !minMatch) {
471
+ throw new Error("Bundled SKILL.md is missing metadata.skill_version / skill_min_cli_version" + " — re-run `bun run sync-skill` to regenerate.");
472
+ }
473
+ if (verMatch?.[1])
474
+ skillVersion = verMatch[1].trim();
475
+ if (minMatch?.[1])
476
+ skillMinCliVersion = minMatch[1].trim();
477
+ break;
461
478
  }
462
- if (error.suggestion) {
463
- const helpMatch = /^Run:\s+(mutagent\s+[^\n]+--help)/.exec(error.suggestion);
464
- const helpCommand = helpMatch ? helpMatch[1] : "mutagent --help";
465
- jsonOutput._agentGuidance = {
466
- helpCommand,
467
- suggestion: error.suggestion
479
+ }
480
+ } catch (err) {
481
+ if (err instanceof Error && err.message.startsWith("Bundled SKILL.md is missing")) {
482
+ throw err;
483
+ }
484
+ }
485
+ _compatCache = { cliVersion, skillVersion, skillMinCliVersion };
486
+ return _compatCache;
487
+ }
488
+ function getJsonFlag(command) {
489
+ if (!command)
490
+ return false;
491
+ const parentOpts = command.parent ? command.parent.opts() : null;
492
+ const ownOpts = command.opts();
493
+ return Boolean(parentOpts?.json ?? ownOpts.json);
494
+ }
495
+ function startJsonLines() {
496
+ jsonLines = true;
497
+ }
498
+ function jsonText(value) {
499
+ return jsonLines ? JSON.stringify(value) : JSON.stringify(value, null, 2);
500
+ }
501
+ function withSuccess(value) {
502
+ if (value === null || typeof value !== "object" || Array.isArray(value))
503
+ return value;
504
+ if ("success" in value)
505
+ return value;
506
+ return { success: true, ...value };
507
+ }
508
+
509
+ class OutputFormatter {
510
+ format;
511
+ constructor(format) {
512
+ this.format = format;
513
+ }
514
+ output(data) {
515
+ const clean = scrubKnownSuccessDeep(data);
516
+ if (this.format === "json") {
517
+ let enriched = withSuccess(clean);
518
+ if (_globalCliVersion !== "0.0.0" && clean !== null && typeof clean === "object" && !Array.isArray(clean) && "_directive" in clean) {
519
+ enriched = {
520
+ ...enriched,
521
+ _compat: getCompatMetadata(_globalCliVersion)
468
522
  };
469
523
  }
470
- console.log(JSON.stringify(scrubKnownDeep(jsonOutput), null, 2));
524
+ console.log(jsonText(enriched));
471
525
  } else {
472
- console.error(`Error: ${scrubKnown(error.message)}`);
473
- if (error.name === "TenancyError") {} else if (error instanceof AuthenticationError) {
474
- console.error("");
475
- console.error(AUTH_REMEDIATION_MESSAGE);
476
- } else if (error instanceof WorkspaceContextError) {
477
- console.error("");
478
- console.error(WORKSPACE_REMEDIATION_MESSAGE);
479
- } else if (error instanceof ApiError && error.statusCode === 403) {
480
- console.error("");
481
- console.error("Access denied. To fix:");
482
- console.error(" mutagent auth login --browser # Re-authenticate");
483
- console.error(" export MUTAGENT_API_KEY=mt_xxx # Or use a different API key");
484
- } else if (error instanceof ValidationError) {
485
- if (error.suggestion) {
486
- console.error(`Suggestion: ${scrubKnown(error.suggestion)}`);
487
- }
488
- console.error("");
489
- console.error("Run `mutagent <command> --help` for usage information.");
490
- } else if (error.suggestion) {
491
- console.error(`Suggestion: ${scrubKnown(error.suggestion)}`);
492
- }
493
- if (error.code === "MISSING_ARGUMENTS" || error.code === "INVALID_ARGUMENTS" || error.code === "INVALID_INPUT") {
494
- console.error("");
495
- console.error("Run `mutagent <command> --help` for usage information.");
496
- }
526
+ this.formatTable(clean);
527
+ }
528
+ }
529
+ formatTable(data) {
530
+ if (Array.isArray(data)) {
531
+ this.formatArray(data);
532
+ } else if (data && typeof data === "object") {
533
+ this.formatObject(data);
534
+ } else {
535
+ console.log(data);
536
+ }
537
+ }
538
+ formatArray(data) {
539
+ if (data.length === 0) {
540
+ console.log(chalk.gray("No results"));
541
+ return;
542
+ }
543
+ const firstItem = data[0];
544
+ if (!firstItem || typeof firstItem !== "object") {
545
+ data.forEach((item) => {
546
+ console.log(` ${String(item)}`);
547
+ });
548
+ return;
497
549
  }
498
- process.exit(error.exitCode);
499
- }
500
- if (error && typeof error === "object") {
501
- const sdkErr = error;
502
- if (sdkErr.name === "SDKValidationError" || sdkErr.name === "SDKError" || sdkErr.rawValue !== undefined || sdkErr.rawMessage !== undefined) {
503
- const friendlyMessage = scrubKnown(sdkErr.rawMessage ?? sdkErr.message ?? "Request failed");
504
- const wrapped = new MutagentError("REQUEST_FAILED", friendlyMessage, "Check your input and try again. Run the command with --help for usage details.");
505
- if (isJson) {
506
- console.log(JSON.stringify(wrapped.toJSON(), null, 2));
550
+ const keys = Object.keys(firstItem);
551
+ const toDisplayString = (val) => {
552
+ if (val === null || val === undefined)
553
+ return "";
554
+ if (typeof val === "object")
555
+ return JSON.stringify(val);
556
+ if (typeof val === "string")
557
+ return val;
558
+ if (typeof val === "number" || typeof val === "boolean" || typeof val === "bigint") {
559
+ return val.toString();
560
+ }
561
+ return JSON.stringify(val);
562
+ };
563
+ const widths = {};
564
+ keys.forEach((key) => {
565
+ widths[key] = Math.max(key.length, ...data.map((item) => toDisplayString(item[key]).length));
566
+ });
567
+ const header = keys.map((key) => chalk.bold(key.toUpperCase().padEnd(widths[key] ?? 10))).join(" ");
568
+ console.log(header);
569
+ console.log(keys.map((key) => "-".repeat(widths[key] ?? 10)).join(" "));
570
+ data.forEach((item) => {
571
+ const row = keys.map((key) => {
572
+ const value = toDisplayString(item[key]);
573
+ const width = widths[key] ?? 10;
574
+ return value.padEnd(width).slice(0, width);
575
+ }).join(" ");
576
+ console.log(row);
577
+ });
578
+ console.log(chalk.gray(`
579
+ ${String(data.length)} result(s)`));
580
+ }
581
+ formatObject(data) {
582
+ const maxKeyLength = Math.max(...Object.keys(data).map((k) => k.length));
583
+ Object.entries(data).forEach(([key, value]) => {
584
+ const formattedKey = chalk.bold(key.padEnd(maxKeyLength));
585
+ let formattedValue;
586
+ if (value === null || value === undefined) {
587
+ formattedValue = "";
588
+ } else if (typeof value === "object") {
589
+ formattedValue = JSON.stringify(value, null, 2);
590
+ } else if (typeof value === "string") {
591
+ formattedValue = value;
592
+ } else if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
593
+ formattedValue = value.toString();
507
594
  } else {
508
- console.error(`Error: ${wrapped.message}`);
509
- if (wrapped.suggestion) {
510
- console.error(`Suggestion: ${wrapped.suggestion}`);
511
- }
595
+ formattedValue = JSON.stringify(value);
512
596
  }
513
- process.exit(1);
597
+ console.log(`${formattedKey} ${formattedValue}`);
598
+ });
599
+ }
600
+ success(message) {
601
+ const text = scrubKnownSuccess(message);
602
+ if (this.format === "json") {
603
+ console.log(JSON.stringify({ success: true, message: text }, null, 2));
604
+ } else {
605
+ console.log(chalk.green(`✓ ${text}`));
606
+ }
607
+ }
608
+ error(message, code, suggestedAction) {
609
+ const text = scrubKnownSuccess(message);
610
+ const action = suggestedAction === undefined ? undefined : scrubKnownSuccess(suggestedAction);
611
+ if (this.format === "json") {
612
+ const result = { success: false, error: text };
613
+ if (code)
614
+ result.code = code;
615
+ if (action)
616
+ result.suggestedAction = action;
617
+ console.log(JSON.stringify(result, null, 2));
618
+ } else {
619
+ console.log(chalk.red(`✗ ${text}`));
620
+ }
621
+ }
622
+ info(message) {
623
+ if (this.format !== "json") {
624
+ console.log(chalk.blue(`ℹ ${scrubKnownSuccess(message)}`));
514
625
  }
515
626
  }
516
- const message = error instanceof Error ? scrubKnown(error.message) : "Unknown error";
627
+ warn(message) {
628
+ const text = scrubKnownSuccess(message);
629
+ if (this.format === "json") {
630
+ console.log(JSON.stringify({ warning: text }, null, 2));
631
+ } else {
632
+ console.log(chalk.yellow(`⚠ ${text}`));
633
+ }
634
+ }
635
+ }
636
+ function createSpinner(text, isJson) {
637
+ if (isJson || !process.stderr.isTTY || process.env.CI === "true") {
638
+ const noop = () => {
639
+ return;
640
+ };
641
+ return {
642
+ start: noop,
643
+ stop: noop,
644
+ succeed: noop,
645
+ fail: noop
646
+ };
647
+ }
648
+ const start = async () => {
649
+ const { default: ora } = await import("ora");
650
+ return ora(text).start();
651
+ };
652
+ return { start };
653
+ }
654
+ var _compatCache = null, _globalCliVersion = "0.0.0", jsonLines = false;
655
+ var init_output = __esm(() => {
656
+ init_known_secrets();
657
+ });
658
+
659
+ // src/lib/errors.ts
660
+ function exitCodeFor(code) {
661
+ if (KEY_REJECTED_CODES.has(code))
662
+ return EXIT_CODES.keyExpiredOrInvalid;
663
+ if (NOT_SIGNED_IN_CODES.has(code))
664
+ return EXIT_CODES.notSignedInOrNoWorkspace;
665
+ return EXIT_CODES.failure;
666
+ }
667
+ function createErrorFromUnknown(error) {
668
+ if (error instanceof MutagentError) {
669
+ return error;
670
+ }
671
+ const message = error instanceof Error ? error.message : typeof error === "string" ? error : "An unknown error occurred";
672
+ return new MutagentError("UNKNOWN_ERROR", message);
673
+ }
674
+ function runnableCommand(suggestion) {
675
+ const line = suggestion?.replace(/^(Run|See|To switch):\s+/, "").trim();
676
+ return line !== undefined && /^mutagent [^\s]/.test(line) && !/[\n:]|\s\(/.test(line) ? line : undefined;
677
+ }
678
+ function agentGuidance(error, own) {
679
+ const command = runnableCommand(error.suggestion);
680
+ const helpMatch = error.suggestion === undefined ? null : /^Run:\s+(mutagent\s+[^\n]+--help)$/.exec(error.suggestion);
681
+ const signIn = error.exitCode !== EXIT_CODES.failure && error.code !== "WORKSPACE_REQUIRED";
682
+ return {
683
+ helpCommand: helpMatch?.[1] ?? runningHelpCommand(),
684
+ suggestion: error.suggestion,
685
+ fix: command === undefined ? [] : [command],
686
+ notes: command === undefined && error.suggestion !== undefined ? [error.suggestion] : [],
687
+ ...signIn ? { escalate: SIGN_IN_ESCALATION } : {},
688
+ ...own !== null && typeof own === "object" ? own : {}
689
+ };
690
+ }
691
+ function errorBody(error) {
692
+ const body = { ...error.toJSON() };
693
+ if (error instanceof AuthenticationError)
694
+ body.remediation = { ...AUTH_REMEDIATION };
695
+ if (error instanceof WorkspaceContextError) {
696
+ body.remediation = { setWorkspace: "mutagent workspaces use <name>", listWorkspaces: "mutagent workspaces list" };
697
+ }
698
+ if (error instanceof ApiError && error.statusCode === 403) {
699
+ body.remediation = {
700
+ interactive: SIGN_IN_COMMAND,
701
+ nonInteractive: "export MUTAGENT_API_KEY=<your-key>",
702
+ checkPermissions: "Verify your API key has the required permissions"
703
+ };
704
+ }
705
+ const guidance = agentGuidance(error, body._agentGuidance);
706
+ body._agentGuidance = guidance;
707
+ if (typeof body.suggestedAction !== "string" || body.suggestedAction === "") {
708
+ body.suggestedAction = `Run: ${String(guidance.helpCommand)}`;
709
+ }
710
+ return body;
711
+ }
712
+ function printHuman(error) {
713
+ console.error(`Error: ${scrubKnown(error.message)}`);
714
+ if (error.name === "TenancyError") {} else if (error instanceof AuthenticationError && error.code === "AUTH_REQUIRED") {
715
+ console.error("");
716
+ console.error(AUTH_REMEDIATION_MESSAGE);
717
+ } else if (error instanceof WorkspaceContextError) {
718
+ console.error("");
719
+ console.error(WORKSPACE_REMEDIATION_MESSAGE);
720
+ } else if (error instanceof ApiError && error.statusCode === 403) {
721
+ console.error("");
722
+ console.error("Access denied. To fix:");
723
+ console.error(` ${SIGN_IN_COMMAND} # Re-authenticate`);
724
+ console.error(" export MUTAGENT_API_KEY=mg_live_... # Or use a different API key");
725
+ } else if (error.suggestion) {
726
+ console.error(`Suggestion: ${scrubKnown(error.suggestion)}`);
727
+ }
728
+ const usage = ["MISSING_ARGUMENTS", "INVALID_ARGUMENTS", "INVALID_INPUT", "VALIDATION_ERROR", "UNKNOWN_OPTION", "UNKNOWN_COMMAND"];
729
+ if (usage.includes(error.code) && !(error.suggestion ?? "").includes(runningHelpCommand())) {
730
+ console.error("");
731
+ console.error(`Run \`${runningHelpCommand()}\` for usage information.`);
732
+ }
733
+ }
734
+ function asSdkError(error) {
735
+ if (!error || typeof error !== "object")
736
+ return;
737
+ const sdkErr = error;
738
+ if (sdkErr.name !== "SDKValidationError" && sdkErr.name !== "SDKError" && sdkErr.rawValue === undefined && sdkErr.rawMessage === undefined) {
739
+ return;
740
+ }
741
+ return new MutagentError("REQUEST_FAILED", scrubKnown(sdkErr.rawMessage ?? sdkErr.message ?? "Request failed"), "Check your input and try again. Run the command with --help for usage details.");
742
+ }
743
+ function toFailure(error) {
744
+ return error instanceof MutagentError ? error : asSdkError(error) ?? new MutagentError("UNKNOWN_ERROR", error instanceof Error ? scrubKnown(error.message) : "Unknown error", 'If it keeps happening, report it: mutagent feedback send "<what happened>" --category cli');
745
+ }
746
+ function handleError(error, isJson) {
747
+ const failure = toFailure(error);
517
748
  if (isJson) {
518
- console.log(JSON.stringify({ success: false, error: message, code: "UNKNOWN_ERROR" }, null, 2));
749
+ console.log(jsonText(scrubKnownDeep(errorBody(failure))));
519
750
  } else {
520
- console.error(`Error: ${message}`);
751
+ printHuman(failure);
521
752
  }
522
- process.exit(1);
753
+ process.exit(failure.exitCode);
523
754
  }
524
- var MutagentError, AUTH_REMEDIATION_MESSAGE, AuthenticationError, ApiError, WORKSPACE_REMEDIATION_MESSAGE, WorkspaceContextError, ValidationError, NetworkError, RateLimitError, NotFoundError;
755
+ var EXIT_CODES, KEY_REJECTED_CODES, NOT_SIGNED_IN_CODES, EXIT_CODES_LINE = "Exit codes: 0 success · 1 failure (usage errors included) · 2 key expired or invalid · 3 not signed in or no workspace", MutagentError, SIGN_IN_COMMAND = "mutagent login --browser", SIGN_IN_WITH_KEY = "export MUTAGENT_API_KEY=<key>, then: mutagent login --json", AUTH_REMEDIATION_MESSAGE, AUTH_REMEDIATION, AuthenticationError, ApiError, WORKSPACE_REMEDIATION_MESSAGE, WorkspaceContextError, ValidationError, NetworkError, RateLimitError, NotFoundError, SIGN_IN_ESCALATION = "Signing in needs a person at a browser, or a MUTAGENT_API_KEY from them.";
525
756
  var init_errors = __esm(() => {
526
757
  init_known_secrets();
758
+ init_output();
759
+ EXIT_CODES = {
760
+ success: 0,
761
+ failure: 1,
762
+ keyExpiredOrInvalid: 2,
763
+ notSignedInOrNoWorkspace: 3
764
+ };
765
+ KEY_REJECTED_CODES = new Set(["AUTH_EXPIRED", "INVALID_API_KEY", "FOREIGN_API_KEY", "API_401", "SANDBOX_TOKEN_REFUSED"]);
766
+ NOT_SIGNED_IN_CODES = new Set(["AUTH_REQUIRED", "WORKSPACE_REQUIRED"]);
527
767
  MutagentError = class MutagentError extends Error {
528
768
  code;
529
769
  suggestion;
530
- exitCode;
531
- constructor(code, message, suggestion, exitCode = 1) {
770
+ constructor(code, message, suggestion) {
532
771
  super(message);
533
772
  this.code = code;
534
773
  this.suggestion = suggestion;
535
- this.exitCode = exitCode;
536
774
  this.name = "MutagentError";
537
775
  }
776
+ get exitCode() {
777
+ return exitCodeFor(this.code);
778
+ }
538
779
  toJSON() {
539
780
  return {
540
781
  success: false,
@@ -546,29 +787,34 @@ var init_errors = __esm(() => {
546
787
  };
547
788
  AUTH_REMEDIATION_MESSAGE = [
548
789
  "Authentication required. Options:",
549
- " Interactive: mutagent auth login --browser",
790
+ ` Interactive: ${SIGN_IN_COMMAND}`,
550
791
  " Non-interactive: export MUTAGENT_API_KEY=<your-key>",
551
- " CI/CD: mutagent auth login --api-key <key>"
792
+ ` CI/CD: ${SIGN_IN_WITH_KEY}`
552
793
  ].join(`
553
794
  `);
795
+ AUTH_REMEDIATION = {
796
+ interactive: SIGN_IN_COMMAND,
797
+ nonInteractive: "export MUTAGENT_API_KEY=<your-key>",
798
+ ciCd: SIGN_IN_WITH_KEY
799
+ };
554
800
  AuthenticationError = class AuthenticationError extends MutagentError {
555
801
  suggestions;
556
802
  cause;
557
803
  constructor(message, options = {}) {
558
- super("AUTH_REQUIRED", message ?? 'Authentication required. Please run "mutagent auth login"', options.suggestions ? options.suggestions[0] : "Run: mutagent auth login --browser", 2);
804
+ const suggestion = options.suggestions ? options.suggestions[0] : `Run: ${SIGN_IN_COMMAND}`;
805
+ super("AUTH_REQUIRED", message ?? "Not signed in. Run: mutagent login", suggestion);
559
806
  this.suggestions = options.suggestions ?? [
560
- "mutagent auth login --browser",
807
+ SIGN_IN_COMMAND,
561
808
  "export MUTAGENT_API_KEY=<your-key>",
562
- "mutagent auth login --api-key <key>"
809
+ SIGN_IN_WITH_KEY
563
810
  ];
564
- this.suggestion = options.suggestions ? options.suggestions[0] : "Run: mutagent auth login --browser";
565
811
  this.cause = options.cause;
566
812
  }
567
813
  };
568
814
  ApiError = class ApiError extends MutagentError {
569
815
  statusCode;
570
816
  constructor(status, message) {
571
- super(`API_${String(status)}`, scrubKnown(message), status === 401 ? "Check your API key with: mutagent auth status" : undefined, 1);
817
+ super(`API_${String(status)}`, scrubKnown(message), status === 401 ? "Check your API key with: mutagent auth status" : undefined);
572
818
  this.statusCode = status;
573
819
  }
574
820
  };
@@ -580,19 +826,19 @@ var init_errors = __esm(() => {
580
826
  `);
581
827
  WorkspaceContextError = class WorkspaceContextError extends MutagentError {
582
828
  constructor(message) {
583
- super("WORKSPACE_REQUIRED", message ?? "Workspace context is required but not configured", "Run: mutagent workspaces use <name>", 3);
829
+ super("WORKSPACE_REQUIRED", message ?? "Workspace context is required but not configured", "Run: mutagent workspaces use <name>");
584
830
  }
585
831
  };
586
832
  ValidationError = class ValidationError extends MutagentError {
587
833
  constructor(message) {
588
- super("VALIDATION_ERROR", message, "Check the command syntax with: mutagent <command> --help", 1);
834
+ super("VALIDATION_ERROR", message, "Check the command syntax with: mutagent <command> --help");
589
835
  }
590
836
  };
591
837
  NetworkError = class NetworkError extends MutagentError {
592
838
  url;
593
839
  statusCode;
594
840
  constructor(message = "Network error", url, statusCode) {
595
- super("NETWORK_ERROR", message, "Check your internet connection and verify the server URL", 5);
841
+ super("NETWORK_ERROR", message, "Check your internet connection and verify the server URL");
596
842
  this.url = url;
597
843
  this.statusCode = statusCode;
598
844
  }
@@ -604,12 +850,208 @@ var init_errors = __esm(() => {
604
850
  this.code = "RATE_LIMIT_ERROR";
605
851
  this.suggestion = retryAfter ? `Wait ${String(retryAfter)} seconds before retrying` : "Wait a few seconds before retrying";
606
852
  this.retryAfter = retryAfter;
607
- this.exitCode = 6;
608
853
  }
609
854
  };
610
855
  NotFoundError = class NotFoundError extends MutagentError {
611
- constructor(resource, id) {
612
- super("NOT_FOUND", `${resource} not found: ${id}`, `Check the ID and try again. List available ${resource.toLowerCase()}s with the appropriate list command.`, 1);
856
+ constructor(resource, id, listCommand) {
857
+ super("NOT_FOUND", `${resource} not found: ${id}`, listCommand === undefined ? `Check the ID and try again. List available ${resource.toLowerCase()}s with the appropriate list command.` : `Run: ${listCommand}`);
858
+ }
859
+ };
860
+ });
861
+
862
+ // src/lib/secret-scan.ts
863
+ function foreignCredential(key) {
864
+ const hit = FOREIGN_KEY_PREFIXES.find((family) => family.prefix.test(key));
865
+ if (hit === undefined)
866
+ return;
867
+ const label = PATTERNS.find((pattern) => pattern.kind === hit.kind)?.label ?? "credential from another service";
868
+ return { kind: hit.kind, label };
869
+ }
870
+ function hasCredentialEntropy(candidate) {
871
+ if (candidate.length < 16)
872
+ return false;
873
+ const distinct = new Set(candidate).size;
874
+ if (distinct < 8)
875
+ return false;
876
+ return !/(.)\1{7,}/.test(candidate);
877
+ }
878
+ function maskSecret(secret) {
879
+ const head = secret.slice(0, 6);
880
+ return `${head}${"*".repeat(Math.min(12, Math.max(4, secret.length - head.length)))}`;
881
+ }
882
+ function collectHits(content) {
883
+ const accepted = [];
884
+ const overlaps = (start, end) => accepted.some((hit) => start < hit.end && end > hit.start);
885
+ for (const pattern of PATTERNS) {
886
+ const regex = new RegExp(pattern.regex.source, pattern.regex.flags);
887
+ let match;
888
+ while ((match = regex.exec(content)) !== null) {
889
+ if (match[0].length === 0) {
890
+ regex.lastIndex += 1;
891
+ continue;
892
+ }
893
+ const captured = match[1];
894
+ const secret = captured ?? match[0];
895
+ const start = captured === undefined ? match.index : match.index + match[0].indexOf(captured);
896
+ const end = start + secret.length;
897
+ const exemptFromEntropy = pattern.kind === "private-key";
898
+ if (!exemptFromEntropy && !hasCredentialEntropy(secret))
899
+ continue;
900
+ if (overlaps(start, end))
901
+ continue;
902
+ accepted.push({ kind: pattern.kind, label: pattern.label, start, end, secret });
903
+ }
904
+ }
905
+ return accepted.sort((a, b) => a.start - b.start);
906
+ }
907
+ function lineOf(content, offset) {
908
+ let line = 1;
909
+ for (let i = 0;i < offset && i < content.length; i++) {
910
+ if (content[i] === `
911
+ `)
912
+ line++;
913
+ }
914
+ return line;
915
+ }
916
+ function redactSecrets(content) {
917
+ if (!content)
918
+ return { content, matches: [] };
919
+ const hits = collectHits(content);
920
+ if (hits.length === 0)
921
+ return { content, matches: [] };
922
+ let out = "";
923
+ let cursor = 0;
924
+ for (const hit of hits) {
925
+ out += content.slice(cursor, hit.start);
926
+ out += `[REDACTED:${hit.kind}]`;
927
+ cursor = hit.end;
928
+ }
929
+ out += content.slice(cursor);
930
+ return {
931
+ content: out,
932
+ matches: hits.map((hit) => ({
933
+ kind: hit.kind,
934
+ label: hit.label,
935
+ line: lineOf(content, hit.start),
936
+ preview: maskSecret(hit.secret)
937
+ }))
938
+ };
939
+ }
940
+ var PATTERNS, FOREIGN_KEY_PREFIXES;
941
+ var init_secret_scan = __esm(() => {
942
+ PATTERNS = [
943
+ {
944
+ kind: "private-key",
945
+ label: "Private key block",
946
+ regex: /-----BEGIN(?: [A-Z]+)* PRIVATE KEY-----[\s\S]*?-----END(?: [A-Z]+)* PRIVATE KEY-----/g
947
+ },
948
+ {
949
+ kind: "anthropic-api-key",
950
+ label: "Anthropic API key",
951
+ regex: /sk-ant-[A-Za-z0-9_-]{16,}/g
952
+ },
953
+ {
954
+ kind: "openai-api-key",
955
+ label: "OpenAI API key",
956
+ regex: /sk-(?!ant-)(?:proj-)?[A-Za-z0-9_-]{20,}/g
957
+ },
958
+ {
959
+ kind: "aws-access-key-id",
960
+ label: "AWS access key ID",
961
+ regex: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g
962
+ },
963
+ {
964
+ kind: "github-token",
965
+ label: "GitHub token",
966
+ regex: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}\b|\bgithub_pat_[A-Za-z0-9_]{22,}\b/g
967
+ },
968
+ {
969
+ kind: "google-api-key",
970
+ label: "Google API key",
971
+ regex: /\bAIza[0-9A-Za-z_-]{35}\b/g
972
+ },
973
+ {
974
+ kind: "slack-token",
975
+ label: "Slack token",
976
+ regex: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g
977
+ },
978
+ {
979
+ kind: "mutagent-api-key",
980
+ label: "Mutagent API key",
981
+ regex: /\bmt_[A-Za-z0-9]{16,}\b/g
982
+ },
983
+ {
984
+ kind: "bearer-token",
985
+ label: "Bearer token",
986
+ regex: /\bBearer\s+([A-Za-z0-9._-]{20,})/g
987
+ },
988
+ {
989
+ kind: "generic-assignment",
990
+ label: "Credential assignment",
991
+ regex: /(?:api[_-]?key|apikey|secret|password|passwd|token|access[_-]?key)["']?\s*[:=]\s*["']?([A-Za-z0-9._/+-]{16,})/gi
992
+ }
993
+ ];
994
+ FOREIGN_KEY_PREFIXES = [
995
+ { kind: "anthropic-api-key", prefix: /^sk-ant-/ },
996
+ { kind: "openai-api-key", prefix: /^sk-/ },
997
+ { kind: "aws-access-key-id", prefix: /^(?:AKIA|ASIA)[0-9A-Z]/ },
998
+ { kind: "github-token", prefix: /^(?:gh[pousr]_|github_pat_)/ },
999
+ { kind: "google-api-key", prefix: /^AIza/ },
1000
+ { kind: "slack-token", prefix: /^xox[baprs]-/ }
1001
+ ];
1002
+ });
1003
+
1004
+ // src/lib/key-check.ts
1005
+ async function checkApiKey(apiKey, endpoint) {
1006
+ let response;
1007
+ try {
1008
+ response = await fetch(`${endpoint}/api/providers`, { headers: { "x-api-key": apiKey } });
1009
+ } catch (error) {
1010
+ return { ok: false, reason: "unreachable", cause: error instanceof Error ? error.message : String(error) };
1011
+ }
1012
+ if (response.ok)
1013
+ return { ok: true };
1014
+ if (response.status === 401 || response.status === 403)
1015
+ return { ok: false, reason: "rejected", status: response.status };
1016
+ return { ok: false, reason: "failed", status: response.status };
1017
+ }
1018
+ function endpointUnreachableError(endpoint, cause) {
1019
+ const why = cause ? ` (${cause})` : "";
1020
+ return new MutagentError("ENDPOINT_UNREACHABLE", `Could not reach the Mutagent server at ${endpoint}${why}. The API key was not checked.`, "Check the endpoint (MUTAGENT_ENDPOINT or --endpoint) and the network, then run the same command again.");
1021
+ }
1022
+ function keyCheckError(check, endpoint) {
1023
+ if (check.reason === "unreachable")
1024
+ return endpointUnreachableError(endpoint, check.cause);
1025
+ if (check.reason === "rejected") {
1026
+ return new MutagentError("INVALID_API_KEY", `The API key was rejected by ${endpoint} (HTTP ${String(check.status)}).`, "Run: mutagent login --browser");
1027
+ }
1028
+ return new MutagentError("KEY_CHECK_FAILED", `The Mutagent server at ${endpoint} answered HTTP ${String(check.status)} while checking the API key.`, "Run the same command again; if it keeps failing, check the endpoint with: mutagent config get endpoint");
1029
+ }
1030
+ function assertMutagentKey(apiKey, source) {
1031
+ const foreign = foreignCredential(apiKey.trim());
1032
+ if (foreign !== undefined)
1033
+ throw new ForeignApiKeyError(source, foreign.label);
1034
+ }
1035
+ var ForeignApiKeyError;
1036
+ var init_key_check = __esm(() => {
1037
+ init_errors();
1038
+ init_secret_scan();
1039
+ ForeignApiKeyError = class ForeignApiKeyError extends MutagentError {
1040
+ constructor(source, label) {
1041
+ super("FOREIGN_API_KEY", `${source} looks like ${/^[AEIOU]/.test(label) ? "an" : "a"} ${label}, not a Mutagent API key. Nothing was sent.`, "Run: mutagent login --browser");
1042
+ this.name = "ForeignApiKeyError";
1043
+ }
1044
+ toJSON() {
1045
+ return {
1046
+ ...super.toJSON(),
1047
+ _agentGuidance: {
1048
+ fix: ["mutagent login --browser"],
1049
+ notes: [
1050
+ "Mutagent API keys come from signing in (mutagent login), or from the dashboard; they start with mg_live_.",
1051
+ "An LLM provider key belongs in the workspace: mutagent providers add --provider <type> --name <name> --api-key <key>"
1052
+ ]
1053
+ }
1054
+ };
613
1055
  }
614
1056
  };
615
1057
  });
@@ -617,6 +1059,7 @@ var init_errors = __esm(() => {
617
1059
  // src/lib/organization-scope.ts
618
1060
  var exports_organization_scope = {};
619
1061
  __export(exports_organization_scope, {
1062
+ workspaceDenied: () => workspaceDenied,
620
1063
  unreachableServerError: () => unreachableServerError,
621
1064
  tenancyHeaders: () => tenancyHeaders,
622
1065
  tenancyFailure: () => tenancyFailure,
@@ -674,20 +1117,22 @@ function parseBody(body) {
674
1117
  return {};
675
1118
  }
676
1119
  function deadKeyError() {
677
- return new TenancyError("AUTH_EXPIRED", "Your CLI key expired or was revoked.", "Run: mutagent login", 2);
1120
+ return new TenancyError("AUTH_EXPIRED", "Your CLI key expired or was revoked.", "Run: mutagent login");
1121
+ }
1122
+ function workspaceDenied(ctx) {
1123
+ const shown = ctx.workspaceName ?? ctx.workspaceId ?? "workspace";
1124
+ if (ctx.workspaceOrigin === "selection") {
1125
+ return new TenancyError(TENANCY_DENIED, `Your saved workspace ${shown} is no longer available to you.`, "Run: mutagent workspaces use <name>");
1126
+ }
1127
+ const where = ctx.organizationName ? ` in ${ctx.organizationName}` : "";
1128
+ return new TenancyError(TENANCY_DENIED, `No workspace "${shown}" among your workspaces${where}.`, "See: mutagent workspaces list");
678
1129
  }
679
1130
  function tenancyFailure(status, body, ctx) {
680
1131
  const parsed = parseBody(body);
681
1132
  if (status === 401)
682
1133
  return deadKeyError();
683
- if (status === 403 && parsed.code === TENANCY_DENIED) {
684
- const shown = ctx.workspaceName ?? ctx.workspaceId ?? "workspace";
685
- if (ctx.workspaceOrigin === "selection") {
686
- return new TenancyError(TENANCY_DENIED, `Your saved workspace ${shown} is no longer available to you.`, "Run: mutagent workspaces use <name>");
687
- }
688
- const where = ctx.organizationName ? ` in ${ctx.organizationName}` : "";
689
- return new TenancyError(TENANCY_DENIED, `No workspace "${shown}" among your workspaces${where}.`, "See: mutagent workspaces list");
690
- }
1134
+ if (status === 403 && parsed.code === TENANCY_DENIED)
1135
+ return workspaceDenied(ctx);
691
1136
  if (status === 403 && ctx.keyScope === "workspace" && ctx.workspaceOrigin === "override") {
692
1137
  return new TenancyError("WORKSPACE_KEY_MISMATCH", "This API key only works in its own workspace.", "Run: mutagent login (for a key that can switch workspaces)");
693
1138
  }
@@ -698,7 +1143,7 @@ function tenancyFailure(status, body, ctx) {
698
1143
  }
699
1144
  function noWorkspaceSelected() {
700
1145
  const fix = envKeyReplacesFile() ? ENV_KEY_WORKSPACE_FIX : "Run: mutagent workspaces use <name>";
701
- return new TenancyError("WORKSPACE_REQUIRED", "No workspace selected.", fix, 3);
1146
+ return new TenancyError("WORKSPACE_REQUIRED", "No workspace selected.", fix);
702
1147
  }
703
1148
  function envKeyHasNoSelection() {
704
1149
  return new TenancyError("WORKSPACE_FROM_ENV", "With MUTAGENT_API_KEY set, the workspace comes from --workspace or MUTAGENT_WORKSPACE_ID.", ENV_KEY_WORKSPACE_FIX);
@@ -889,8 +1334,8 @@ var init_organization_scope = __esm(() => {
889
1334
  init_config();
890
1335
  UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
891
1336
  TenancyError = class TenancyError extends MutagentError {
892
- constructor(code, what, fix, exitCode = 1) {
893
- super(code, `${what} ${fix}`, fix.replace(/^(Run|See|To switch): /, ""), exitCode);
1337
+ constructor(code, what, fix) {
1338
+ super(code, `${what} ${fix}`, fix.replace(/^(Run|See|To switch): /, ""));
894
1339
  this.name = "TenancyError";
895
1340
  }
896
1341
  };
@@ -1485,19 +1930,13 @@ function resetSDKClient() {
1485
1930
  sdkClient = null;
1486
1931
  }
1487
1932
  async function validateApiKey(apiKey, endpoint) {
1488
- try {
1489
- const response = await fetch(`${endpoint}/api/providers`, {
1490
- headers: { "x-api-key": apiKey }
1491
- });
1492
- return response.ok;
1493
- } catch {
1494
- return false;
1495
- }
1933
+ return (await checkApiKey(apiKey, endpoint)).ok;
1496
1934
  }
1497
1935
  var SDK_TRANSPORT_ERRORS, sdkClient = null;
1498
1936
  var init_sdk_client = __esm(() => {
1499
1937
  init_errors();
1500
1938
  init_config();
1939
+ init_key_check();
1501
1940
  init_organization_scope();
1502
1941
  init_sdk_debug();
1503
1942
  init_known_secrets();
@@ -1509,209 +1948,7 @@ var init_sdk_client = __esm(() => {
1509
1948
  // src/index.ts
1510
1949
  init_config();
1511
1950
  init_sdk_client();
1512
-
1513
- // src/lib/output.ts
1514
- init_known_secrets();
1515
- import chalk from "chalk";
1516
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
1517
- import { join as join2 } from "path";
1518
- var _compatCache = null;
1519
- var _globalCliVersion = "0.0.0";
1520
- function setCliVersion(version) {
1521
- _globalCliVersion = version;
1522
- _compatCache = null;
1523
- }
1524
- function getCliVersion() {
1525
- return _globalCliVersion;
1526
- }
1527
- function getCompatMetadata(cliVersion = _globalCliVersion) {
1528
- if (_compatCache !== null)
1529
- return _compatCache;
1530
- let skillVersion = "unknown";
1531
- let skillMinCliVersion = "unknown";
1532
- try {
1533
- const candidatePaths = [
1534
- join2(process.cwd(), ".claude", "skills", "mutagent-cli", "SKILL.md"),
1535
- join2(process.cwd(), "mutagent-cli", ".claude", "skills", "mutagent-cli", "SKILL.md")
1536
- ];
1537
- for (const p of candidatePaths) {
1538
- if (existsSync2(p)) {
1539
- const content = readFileSync2(p, "utf-8");
1540
- const verMatch = /^\s+skill_version:\s+"?([^"\n]+)"?\s*$/m.exec(content);
1541
- const minMatch = /^\s+skill_min_cli_version:\s+"?([^"\n]+)"?\s*$/m.exec(content);
1542
- if (/^metadata:/m.test(content) && !verMatch && !minMatch) {
1543
- throw new Error("Bundled SKILL.md is missing metadata.skill_version / skill_min_cli_version" + " — re-run `bun run sync-skill` to regenerate.");
1544
- }
1545
- if (verMatch?.[1])
1546
- skillVersion = verMatch[1].trim();
1547
- if (minMatch?.[1])
1548
- skillMinCliVersion = minMatch[1].trim();
1549
- break;
1550
- }
1551
- }
1552
- } catch (err) {
1553
- if (err instanceof Error && err.message.startsWith("Bundled SKILL.md is missing")) {
1554
- throw err;
1555
- }
1556
- }
1557
- _compatCache = { cliVersion, skillVersion, skillMinCliVersion };
1558
- return _compatCache;
1559
- }
1560
- function getJsonFlag(command) {
1561
- if (!command)
1562
- return false;
1563
- const parentOpts = command.parent ? command.parent.opts() : null;
1564
- const ownOpts = command.opts();
1565
- return Boolean(parentOpts?.json ?? ownOpts.json);
1566
- }
1567
-
1568
- class OutputFormatter {
1569
- format;
1570
- constructor(format2) {
1571
- this.format = format2;
1572
- }
1573
- output(data) {
1574
- const clean = scrubKnownSuccessDeep(data);
1575
- if (this.format === "json") {
1576
- let enriched = clean;
1577
- if (_globalCliVersion !== "0.0.0" && clean !== null && typeof clean === "object" && !Array.isArray(clean) && "_directive" in clean) {
1578
- enriched = {
1579
- ...clean,
1580
- _compat: getCompatMetadata(_globalCliVersion)
1581
- };
1582
- }
1583
- console.log(JSON.stringify(enriched, null, 2));
1584
- } else {
1585
- this.formatTable(clean);
1586
- }
1587
- }
1588
- formatTable(data) {
1589
- if (Array.isArray(data)) {
1590
- this.formatArray(data);
1591
- } else if (data && typeof data === "object") {
1592
- this.formatObject(data);
1593
- } else {
1594
- console.log(data);
1595
- }
1596
- }
1597
- formatArray(data) {
1598
- if (data.length === 0) {
1599
- console.log(chalk.gray("No results"));
1600
- return;
1601
- }
1602
- const firstItem = data[0];
1603
- if (!firstItem || typeof firstItem !== "object") {
1604
- data.forEach((item) => {
1605
- console.log(` ${String(item)}`);
1606
- });
1607
- return;
1608
- }
1609
- const keys = Object.keys(firstItem);
1610
- const toDisplayString = (val) => {
1611
- if (val === null || val === undefined)
1612
- return "";
1613
- if (typeof val === "object")
1614
- return JSON.stringify(val);
1615
- if (typeof val === "string")
1616
- return val;
1617
- if (typeof val === "number" || typeof val === "boolean" || typeof val === "bigint") {
1618
- return val.toString();
1619
- }
1620
- return JSON.stringify(val);
1621
- };
1622
- const widths = {};
1623
- keys.forEach((key) => {
1624
- widths[key] = Math.max(key.length, ...data.map((item) => toDisplayString(item[key]).length));
1625
- });
1626
- const header = keys.map((key) => chalk.bold(key.toUpperCase().padEnd(widths[key] ?? 10))).join(" ");
1627
- console.log(header);
1628
- console.log(keys.map((key) => "-".repeat(widths[key] ?? 10)).join(" "));
1629
- data.forEach((item) => {
1630
- const row = keys.map((key) => {
1631
- const value = toDisplayString(item[key]);
1632
- const width = widths[key] ?? 10;
1633
- return value.padEnd(width).slice(0, width);
1634
- }).join(" ");
1635
- console.log(row);
1636
- });
1637
- console.log(chalk.gray(`
1638
- ${String(data.length)} result(s)`));
1639
- }
1640
- formatObject(data) {
1641
- const maxKeyLength = Math.max(...Object.keys(data).map((k) => k.length));
1642
- Object.entries(data).forEach(([key, value]) => {
1643
- const formattedKey = chalk.bold(key.padEnd(maxKeyLength));
1644
- let formattedValue;
1645
- if (value === null || value === undefined) {
1646
- formattedValue = "";
1647
- } else if (typeof value === "object") {
1648
- formattedValue = JSON.stringify(value, null, 2);
1649
- } else if (typeof value === "string") {
1650
- formattedValue = value;
1651
- } else if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
1652
- formattedValue = value.toString();
1653
- } else {
1654
- formattedValue = JSON.stringify(value);
1655
- }
1656
- console.log(`${formattedKey} ${formattedValue}`);
1657
- });
1658
- }
1659
- success(message) {
1660
- const text = scrubKnownSuccess(message);
1661
- if (this.format === "json") {
1662
- console.log(JSON.stringify({ success: true, message: text }, null, 2));
1663
- } else {
1664
- console.log(chalk.green(`✓ ${text}`));
1665
- }
1666
- }
1667
- error(message, code, suggestedAction) {
1668
- const text = scrubKnownSuccess(message);
1669
- const action = suggestedAction === undefined ? undefined : scrubKnownSuccess(suggestedAction);
1670
- if (this.format === "json") {
1671
- const result = { success: false, error: text };
1672
- if (code)
1673
- result.code = code;
1674
- if (action)
1675
- result.suggestedAction = action;
1676
- console.log(JSON.stringify(result, null, 2));
1677
- } else {
1678
- console.log(chalk.red(`✗ ${text}`));
1679
- }
1680
- }
1681
- info(message) {
1682
- if (this.format !== "json") {
1683
- console.log(chalk.blue(`ℹ ${scrubKnownSuccess(message)}`));
1684
- }
1685
- }
1686
- warn(message) {
1687
- const text = scrubKnownSuccess(message);
1688
- if (this.format === "json") {
1689
- console.log(JSON.stringify({ warning: text }, null, 2));
1690
- } else {
1691
- console.log(chalk.yellow(`⚠ ${text}`));
1692
- }
1693
- }
1694
- }
1695
- function createSpinner(text, isJson) {
1696
- if (isJson) {
1697
- const noop = () => {
1698
- return;
1699
- };
1700
- return {
1701
- start: noop,
1702
- stop: noop,
1703
- succeed: noop,
1704
- fail: noop
1705
- };
1706
- }
1707
- const start = async () => {
1708
- const { default: ora } = await import("ora");
1709
- return ora(text).start();
1710
- };
1711
- return { start };
1712
- }
1713
-
1714
- // src/index.ts
1951
+ init_output();
1715
1952
  init_errors();
1716
1953
  var version = "0.1.0";
1717
1954
  export {
@@ -1727,4 +1964,4 @@ export {
1727
1964
  ApiError
1728
1965
  };
1729
1966
 
1730
- //# debugId=775F42A23F18BBFB64756E2164756E21
1967
+ //# debugId=0397F8E1C872BF7364756E2164756E21