@ainecto/cli 0.8.1 → 0.10.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/bin/mcp.js CHANGED
@@ -420,8 +420,8 @@ function toStoredToken(endpoint, token, metadata, previous) {
420
420
  async function createLoopbackReceiver(expectedState, host = "127.0.0.1") {
421
421
  let resolveCode;
422
422
  let rejectCode;
423
- const codePromise = new Promise((resolve, reject) => {
424
- resolveCode = resolve;
423
+ const codePromise = new Promise((resolve2, reject) => {
424
+ resolveCode = resolve2;
425
425
  rejectCode = reject;
426
426
  });
427
427
  codePromise.catch(() => void 0);
@@ -452,12 +452,12 @@ async function createLoopbackReceiver(expectedState, host = "127.0.0.1") {
452
452
  res.end("AiNecto CLI authorization complete. You can close this tab.");
453
453
  resolveCode(code);
454
454
  });
455
- await new Promise((resolve, reject) => {
455
+ await new Promise((resolve2, reject) => {
456
456
  server.once("error", (error) => reject(new OAuthLoginError(
457
457
  "CALLBACK_UNAVAILABLE",
458
458
  `Could not start the local sign-in callback on ${host} (${error.message}). If this command runs in a sandbox, allow local network binding or run it outside the sandbox.`
459
459
  )));
460
- server.listen(0, host, () => resolve());
460
+ server.listen(0, host, () => resolve2());
461
461
  });
462
462
  const address = server.address();
463
463
  if (!address || typeof address === "string") {
@@ -466,9 +466,9 @@ async function createLoopbackReceiver(expectedState, host = "127.0.0.1") {
466
466
  return {
467
467
  redirectUri: `http://127.0.0.1:${address.port}/callback`,
468
468
  waitForCode: () => codePromise,
469
- close: () => new Promise((resolve, reject) => {
469
+ close: () => new Promise((resolve2, reject) => {
470
470
  server.closeAllConnections?.();
471
- server.close((error) => error ? reject(error) : resolve());
471
+ server.close((error) => error ? reject(error) : resolve2());
472
472
  })
473
473
  };
474
474
  }
@@ -497,7 +497,7 @@ function launchBrowser(url, deps = {}) {
497
497
  const platform = deps.platform ?? process.platform;
498
498
  const { command, args } = getBrowserOpenCommand(url, platform);
499
499
  const spawnImpl = deps.spawnImpl ?? spawn;
500
- return new Promise((resolve, reject) => {
500
+ return new Promise((resolve2, reject) => {
501
501
  const unavailable = (reason) => new OAuthLoginError(
502
502
  "BROWSER_UNAVAILABLE",
503
503
  `Could not open a browser on this machine (${command}: ${reason}). Sign-in needs a browser here.`
@@ -511,11 +511,11 @@ function launchBrowser(url, deps = {}) {
511
511
  }
512
512
  child.once("error", (error) => reject(unavailable(error.message)));
513
513
  if (platform === "win32") {
514
- child.once("spawn", () => resolve());
514
+ child.once("spawn", () => resolve2());
515
515
  } else {
516
516
  child.once("exit", (code, signal) => {
517
517
  if (code === 0) {
518
- resolve();
518
+ resolve2();
519
519
  } else {
520
520
  reject(unavailable(code === null ? `stopped by ${signal ?? "a signal"}` : `exited with code ${code}`));
521
521
  }
@@ -547,13 +547,14 @@ function legacyRoleInput(flag, envVars = process.env) {
547
547
  }
548
548
 
549
549
  // src/core/version.ts
550
- var CLI_VERSION = true ? "0.8.1" : UNBUNDLED_VERSION;
550
+ var CLI_VERSION = true ? "0.10.0" : UNBUNDLED_VERSION;
551
551
  var PACKAGE_NAME = "@ainecto/mcp";
552
552
  var CLI_USER_AGENT = `${PACKAGE_NAME} ${CLI_VERSION}`;
553
553
 
554
554
  // src/core/mcp/rpcClient.ts
555
555
  var CHANNEL_HEADER = "X-AiNecto-Channel";
556
556
  var AGENT_HEADER = "X-AiNecto-Agent";
557
+ var AGENT_NAME_HEADER = "X-AiNecto-Agent-Name";
557
558
  var MAX_AGENT_NAME_LENGTH = 100;
558
559
  var McpRpcError = class extends Error {
559
560
  constructor(message, code, details) {
@@ -626,10 +627,11 @@ var McpRpcClient = class {
626
627
  }
627
628
  const text = await response.text();
628
629
  if (!response.ok) {
630
+ const serverError = parseServerError(text);
629
631
  throw new McpRpcError(
630
- `MCP HTTP request failed with HTTP ${response.status}.`,
632
+ `MCP HTTP request failed with HTTP ${response.status}${serverError ? ` \u2014 ${describeServerError(serverError)}` : "."}`,
631
633
  "MCP_HTTP_ERROR",
632
- httpErrorDetails(response.status, text, this.options.envVars ?? process.env)
634
+ { ...httpErrorDetails(response.status, text, this.options.envVars ?? process.env), ...serverError ? { error: serverError.error } : {} }
633
635
  );
634
636
  }
635
637
  if (!text.trim()) {
@@ -657,6 +659,9 @@ var McpRpcClient = class {
657
659
  if (this.agent) {
658
660
  headers[AGENT_HEADER.toLowerCase()] = this.agent;
659
661
  }
662
+ if (this.options.agentName) {
663
+ headers[AGENT_NAME_HEADER.toLowerCase()] = this.options.agentName;
664
+ }
660
665
  return this.fetchImpl(this.options.endpoint, {
661
666
  method: "POST",
662
667
  headers,
@@ -674,6 +679,25 @@ function printableHeaderValue(value) {
674
679
  const printable = value.replace(/[^\x20-\x7E]/g, "").trim().slice(0, MAX_AGENT_NAME_LENGTH).trim();
675
680
  return printable || void 0;
676
681
  }
682
+ function parseServerError(body) {
683
+ let parsed;
684
+ try {
685
+ parsed = JSON.parse(body);
686
+ } catch {
687
+ return void 0;
688
+ }
689
+ if (!isRecord2(parsed) || typeof parsed.error !== "string" || !parsed.error.trim()) {
690
+ return void 0;
691
+ }
692
+ return {
693
+ error: parsed.error.trim(),
694
+ description: typeof parsed.error_description === "string" && parsed.error_description.trim() ? parsed.error_description : void 0
695
+ };
696
+ }
697
+ function describeServerError(serverError) {
698
+ const line = serverError.description ? `${serverError.error}: ${serverError.description}` : serverError.error;
699
+ return line.replace(/\s+/g, " ").trim();
700
+ }
677
701
  function isJsonRpcResponse(value) {
678
702
  return isRecord2(value) && value.jsonrpc === "2.0" && ("result" in value || "error" in value);
679
703
  }
@@ -699,27 +723,49 @@ async function runStdioBridge(options) {
699
723
  if (!line.trim()) {
700
724
  continue;
701
725
  }
726
+ let request;
727
+ try {
728
+ request = parseJsonRpcRequest(line);
729
+ } catch (error) {
730
+ writeLine(options.output, failure(null, error instanceof SyntaxError ? PARSE_ERROR : INVALID_REQUEST, error));
731
+ continue;
732
+ }
702
733
  try {
703
- const request = parseJsonRpcRequest(line);
704
734
  if (request.method === "initialize") {
705
735
  options.client.useAgent(clientInfoName(request.params));
706
736
  }
707
737
  const response = await options.client.sendRaw(request);
708
738
  if (response && request.id !== void 0) {
709
- options.output.write(`${JSON.stringify(response)}
710
- `);
739
+ writeLine(options.output, response);
711
740
  }
712
741
  } catch (error) {
713
- const message = error instanceof Error ? error.message : String(error);
714
- options.output.write(`${JSON.stringify({
715
- jsonrpc: "2.0",
716
- id: null,
717
- error: { code: -32603, message }
718
- })}
742
+ if (request.id === void 0) {
743
+ options.errorOutput.write(`${errorMessage(error)}
719
744
  `);
745
+ continue;
746
+ }
747
+ writeLine(options.output, failure(request.id, INTERNAL_ERROR, error));
720
748
  }
721
749
  }
722
750
  }
751
+ var PARSE_ERROR = -32700;
752
+ var INVALID_REQUEST = -32600;
753
+ var INTERNAL_ERROR = -32603;
754
+ function failure(id, code, error) {
755
+ const data = error instanceof McpRpcError ? { code: error.code, ...isRecord3(error.details) ? error.details : {} } : void 0;
756
+ return {
757
+ jsonrpc: "2.0",
758
+ id,
759
+ error: { code, message: errorMessage(error), ...data ? { data } : {} }
760
+ };
761
+ }
762
+ function errorMessage(error) {
763
+ return error instanceof Error ? error.message : String(error);
764
+ }
765
+ function writeLine(output, message) {
766
+ output.write(`${JSON.stringify(message)}
767
+ `);
768
+ }
723
769
  function parseJsonRpcRequest(line) {
724
770
  const parsed = JSON.parse(line);
725
771
  if (!isRecord3(parsed) || parsed.jsonrpc !== "2.0" || typeof parsed.method !== "string") {
@@ -739,6 +785,109 @@ function isRecord3(value) {
739
785
  return typeof value === "object" && value !== null && !Array.isArray(value);
740
786
  }
741
787
 
788
+ // src/core/harness/agentTargets.ts
789
+ import { basename } from "path";
790
+ var SERVER_NAME = "ainecto";
791
+ var REMOTE_SIGN_IN = {
792
+ "claude-code": `Claude Code \u2014 in a session, run /mcp, pick "${SERVER_NAME}" and sign in`,
793
+ cursor: `Cursor \u2014 sign in when "${SERVER_NAME}" asks for it in Cursor's MCP settings`,
794
+ codex: `Codex \u2014 signs in while setup connects it (its sign-in link is printed above); if that did not finish, run \`codex mcp login ${SERVER_NAME}\``
795
+ };
796
+
797
+ // src/core/harness/harnessDoc.ts
798
+ var MARKER_BEGIN = "<!-- ainecto:begin -->";
799
+ var MARKER_END = "<!-- ainecto:end -->";
800
+ var HARNESS_DIR = ".ainecto";
801
+ var HARNESS_DOC_PATH = `${HARNESS_DIR}/HARNESS.md`;
802
+ var HARNESS_CONFIG_PATH = `${HARNESS_DIR}/config.json`;
803
+ var HARNESS_RECORD_PATH = `${HARNESS_DIR}/init-record.json`;
804
+ var DOC_KEY = "HARNESS.DOC";
805
+ var AGENT_NOTE_KEY = "HARNESS.AGENT_NOTE";
806
+ var REQUIRED_VARIABLES = {
807
+ [DOC_KEY]: ["endpoint"],
808
+ [AGENT_NOTE_KEY]: ["harnessDocPath", "serverName"]
809
+ };
810
+ var DEFAULT_AGENT_NOTE = `${MARKER_BEGIN}
811
+ ## AiNecto Harness
812
+
813
+ This repository uses AiNecto as its design source of truth. Read \`{{harnessDocPath}}\` before doing
814
+ work that touches requirements, the ERD, or architecture.
815
+
816
+ AiNecto tools come from the \`{{serverName}}\` MCP server. Every session sees the same tools; the
817
+ signed-in user's AiNecto permissions decide what they may change, and the server refuses the rest.
818
+ ${MARKER_END}`;
819
+
820
+ // src/core/harness/repositoryFiles.ts
821
+ import { lstat, readFile as readFile2, realpath } from "fs/promises";
822
+ import { dirname as dirname2, join as join2, resolve, sep } from "path";
823
+ async function findGitRoot(cwd) {
824
+ for (let at = resolve(cwd); ; at = dirname2(at)) {
825
+ try {
826
+ await lstat(join2(at, ".git"));
827
+ return at;
828
+ } catch {
829
+ }
830
+ if (dirname2(at) === at) {
831
+ return void 0;
832
+ }
833
+ }
834
+ }
835
+ async function readManagedFile(root, path) {
836
+ await assertInsideRepository(root, path);
837
+ return readIfExists(join2(root, path));
838
+ }
839
+ async function readIfExists(absolutePath) {
840
+ try {
841
+ return await readFile2(absolutePath, "utf8");
842
+ } catch (error) {
843
+ if (isNodeError2(error, "ENOENT")) {
844
+ return void 0;
845
+ }
846
+ throw error;
847
+ }
848
+ }
849
+ async function assertInsideRepository(root, relativePath) {
850
+ const absolutePath = join2(root, relativePath);
851
+ const realRoot = await realpath(root);
852
+ let realParent;
853
+ try {
854
+ realParent = await realpath(dirname2(absolutePath));
855
+ } catch (error) {
856
+ if (isNodeError2(error, "ENOENT")) {
857
+ return;
858
+ }
859
+ throw error;
860
+ }
861
+ if (realParent !== realRoot && !realParent.startsWith(realRoot + sep)) {
862
+ throw new Error(
863
+ `${relativePath} resolves outside the repository (${realParent}); refusing to touch it.`
864
+ );
865
+ }
866
+ try {
867
+ if ((await lstat(absolutePath)).isSymbolicLink()) {
868
+ throw new Error(`${relativePath} is a symbolic link; refusing to write through it.`);
869
+ }
870
+ } catch (error) {
871
+ if (!isNodeError2(error, "ENOENT")) {
872
+ throw error;
873
+ }
874
+ }
875
+ }
876
+ function isNodeError2(error, code) {
877
+ return typeof error === "object" && error !== null && error.code === code;
878
+ }
879
+
880
+ // src/core/harness/agentName.ts
881
+ var AGENT_NAME_PATH = `${HARNESS_DIR}/agent.local`;
882
+ function agentNameOf(raw) {
883
+ const name = raw?.trim();
884
+ return name ? name : void 0;
885
+ }
886
+ async function readAgentName(cwd) {
887
+ const root = await findGitRoot(cwd) ?? cwd;
888
+ return agentNameOf(await readManagedFile(root, AGENT_NAME_PATH));
889
+ }
890
+
742
891
  // src/adapters/mcp/connector.ts
743
892
  async function runConnector(options) {
744
893
  const legacyRole = legacyRoleInput(options.legacyRoleFlag, options.envVars);
@@ -751,7 +900,8 @@ async function runConnector(options) {
751
900
  endpoint: options.endpoint,
752
901
  tokenProvider: auth,
753
902
  legacyRole,
754
- // 에이전트의 MCP 연결 — 에이전트 이름은 브리지가 호스트의 initialize 에서 읽어 싣는다.
903
+ agentName: options.agentFlag ?? await readAgentName(options.cwd ?? process.cwd()),
904
+ // 에이전트의 MCP 연결 — 에이전트 «종류»는 브리지가 호스트의 initialize 에서 읽어 싣는다(«이름»은 위 agentName).
755
905
  channel: "mcp"
756
906
  });
757
907
  await runStdioBridge({
@@ -829,6 +979,12 @@ function parseMcpArgs(argv) {
829
979
  result.env = assertEnv(requireFlagValue(arg, argv[++index]));
830
980
  } else if (arg === "--endpoint") {
831
981
  result.endpoint = requireFlagValue(arg, argv[++index]);
982
+ } else if (arg === "--agent") {
983
+ const name = agentNameOf(requireFlagValue(arg, argv[++index]));
984
+ if (name === void 0) {
985
+ throw new Error(`${arg} requires a value.`);
986
+ }
987
+ result.agent = name;
832
988
  } else if (arg === "--role") {
833
989
  const value = argv[index + 1];
834
990
  if (value !== void 0 && !value.startsWith("--")) {
@@ -854,13 +1010,14 @@ function requireFlagValue(flag, value) {
854
1010
  try {
855
1011
  const args = parseMcpArgs(process.argv.slice(2));
856
1012
  if (args.help) {
857
- process.stdout.write("Usage: mcp [--env prod|dev] [--endpoint URL]\n");
1013
+ process.stdout.write("Usage: mcp [--env prod|dev] [--endpoint URL] [--agent NAME]\n");
858
1014
  process.exit(0);
859
1015
  }
860
1016
  const resolved = resolveEndpoint({ env: args.env, endpoint: args.endpoint });
861
1017
  await runConnector({
862
1018
  endpoint: resolved.endpoint,
863
1019
  legacyRoleFlag: args.legacyRole,
1020
+ agentFlag: args.agent,
864
1021
  input: process.stdin,
865
1022
  output: process.stdout,
866
1023
  errorOutput: process.stderr