link-agents 0.9.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.
Files changed (184) hide show
  1. package/AGENTS.md +127 -0
  2. package/README.md +93 -0
  3. package/cursor-rules-notes.md +23 -0
  4. package/dist/cli/interactive.d.ts +9 -0
  5. package/dist/cli/interactive.d.ts.map +1 -0
  6. package/dist/cli/interactive.js +1176 -0
  7. package/dist/cli/interactive.js.map +1 -0
  8. package/dist/cli/options.d.ts +3 -0
  9. package/dist/cli/options.d.ts.map +1 -0
  10. package/dist/cli/options.js +107 -0
  11. package/dist/cli/options.js.map +1 -0
  12. package/dist/cli/options.spec.d.ts +2 -0
  13. package/dist/cli/options.spec.d.ts.map +1 -0
  14. package/dist/cli/options.spec.js +74 -0
  15. package/dist/cli/options.spec.js.map +1 -0
  16. package/dist/clients/definitions.d.ts +5 -0
  17. package/dist/clients/definitions.d.ts.map +1 -0
  18. package/dist/clients/definitions.js +82 -0
  19. package/dist/clients/definitions.js.map +1 -0
  20. package/dist/clients/definitions.spec.d.ts +2 -0
  21. package/dist/clients/definitions.spec.d.ts.map +1 -0
  22. package/dist/clients/definitions.spec.js +135 -0
  23. package/dist/clients/definitions.spec.js.map +1 -0
  24. package/dist/commands/doctor.d.ts +3 -0
  25. package/dist/commands/doctor.d.ts.map +1 -0
  26. package/dist/commands/doctor.js +81 -0
  27. package/dist/commands/doctor.js.map +1 -0
  28. package/dist/commands/restore.d.ts +3 -0
  29. package/dist/commands/restore.d.ts.map +1 -0
  30. package/dist/commands/restore.js +36 -0
  31. package/dist/commands/restore.js.map +1 -0
  32. package/dist/commands/sync.d.ts +3 -0
  33. package/dist/commands/sync.d.ts.map +1 -0
  34. package/dist/commands/sync.js +193 -0
  35. package/dist/commands/sync.js.map +1 -0
  36. package/dist/index.d.ts +3 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +30 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/types/index.d.ts +98 -0
  41. package/dist/types/index.d.ts.map +1 -0
  42. package/dist/types/index.js +2 -0
  43. package/dist/types/index.js.map +1 -0
  44. package/dist/utils/apply.d.ts +22 -0
  45. package/dist/utils/apply.d.ts.map +1 -0
  46. package/dist/utils/apply.js +215 -0
  47. package/dist/utils/apply.js.map +1 -0
  48. package/dist/utils/bootstrap.d.ts +18 -0
  49. package/dist/utils/bootstrap.d.ts.map +1 -0
  50. package/dist/utils/bootstrap.js +31 -0
  51. package/dist/utils/bootstrap.js.map +1 -0
  52. package/dist/utils/bootstrap.spec.d.ts +2 -0
  53. package/dist/utils/bootstrap.spec.d.ts.map +1 -0
  54. package/dist/utils/bootstrap.spec.js +92 -0
  55. package/dist/utils/bootstrap.spec.js.map +1 -0
  56. package/dist/utils/canonical.d.ts +17 -0
  57. package/dist/utils/canonical.d.ts.map +1 -0
  58. package/dist/utils/canonical.js +136 -0
  59. package/dist/utils/canonical.js.map +1 -0
  60. package/dist/utils/canonicalState.d.ts +19 -0
  61. package/dist/utils/canonicalState.d.ts.map +1 -0
  62. package/dist/utils/canonicalState.js +21 -0
  63. package/dist/utils/canonicalState.js.map +1 -0
  64. package/dist/utils/cursorHistory.d.ts +7 -0
  65. package/dist/utils/cursorHistory.d.ts.map +1 -0
  66. package/dist/utils/cursorHistory.js +54 -0
  67. package/dist/utils/cursorHistory.js.map +1 -0
  68. package/dist/utils/cursorPaths.d.ts +3 -0
  69. package/dist/utils/cursorPaths.d.ts.map +1 -0
  70. package/dist/utils/cursorPaths.js +17 -0
  71. package/dist/utils/cursorPaths.js.map +1 -0
  72. package/dist/utils/discovery.d.ts +8 -0
  73. package/dist/utils/discovery.d.ts.map +1 -0
  74. package/dist/utils/discovery.js +93 -0
  75. package/dist/utils/discovery.js.map +1 -0
  76. package/dist/utils/frontmatter.d.ts +32 -0
  77. package/dist/utils/frontmatter.d.ts.map +1 -0
  78. package/dist/utils/frontmatter.js +263 -0
  79. package/dist/utils/frontmatter.js.map +1 -0
  80. package/dist/utils/frontmatter.spec.d.ts +2 -0
  81. package/dist/utils/frontmatter.spec.d.ts.map +1 -0
  82. package/dist/utils/frontmatter.spec.js +264 -0
  83. package/dist/utils/frontmatter.spec.js.map +1 -0
  84. package/dist/utils/fs.d.ts +27 -0
  85. package/dist/utils/fs.d.ts.map +1 -0
  86. package/dist/utils/fs.js +137 -0
  87. package/dist/utils/fs.js.map +1 -0
  88. package/dist/utils/fs.spec.d.ts +2 -0
  89. package/dist/utils/fs.spec.d.ts.map +1 -0
  90. package/dist/utils/fs.spec.js +73 -0
  91. package/dist/utils/fs.spec.js.map +1 -0
  92. package/dist/utils/gitignore.d.ts +10 -0
  93. package/dist/utils/gitignore.d.ts.map +1 -0
  94. package/dist/utils/gitignore.js +63 -0
  95. package/dist/utils/gitignore.js.map +1 -0
  96. package/dist/utils/manifest.d.ts +28 -0
  97. package/dist/utils/manifest.d.ts.map +1 -0
  98. package/dist/utils/manifest.js +89 -0
  99. package/dist/utils/manifest.js.map +1 -0
  100. package/dist/utils/mcp.d.ts +73 -0
  101. package/dist/utils/mcp.d.ts.map +1 -0
  102. package/dist/utils/mcp.js +529 -0
  103. package/dist/utils/mcp.js.map +1 -0
  104. package/dist/utils/mcp.spec.d.ts +2 -0
  105. package/dist/utils/mcp.spec.d.ts.map +1 -0
  106. package/dist/utils/mcp.spec.js +488 -0
  107. package/dist/utils/mcp.spec.js.map +1 -0
  108. package/dist/utils/merge.d.ts +17 -0
  109. package/dist/utils/merge.d.ts.map +1 -0
  110. package/dist/utils/merge.js +45 -0
  111. package/dist/utils/merge.js.map +1 -0
  112. package/dist/utils/merge.spec.d.ts +2 -0
  113. package/dist/utils/merge.spec.d.ts.map +1 -0
  114. package/dist/utils/merge.spec.js +134 -0
  115. package/dist/utils/merge.spec.js.map +1 -0
  116. package/dist/utils/paths.d.ts +11 -0
  117. package/dist/utils/paths.d.ts.map +1 -0
  118. package/dist/utils/paths.js +164 -0
  119. package/dist/utils/paths.js.map +1 -0
  120. package/dist/utils/paths.spec.d.ts +2 -0
  121. package/dist/utils/paths.spec.d.ts.map +1 -0
  122. package/dist/utils/paths.spec.js +282 -0
  123. package/dist/utils/paths.spec.js.map +1 -0
  124. package/dist/utils/plan.d.ts +7 -0
  125. package/dist/utils/plan.d.ts.map +1 -0
  126. package/dist/utils/plan.js +118 -0
  127. package/dist/utils/plan.js.map +1 -0
  128. package/dist/utils/plan.spec.d.ts +2 -0
  129. package/dist/utils/plan.spec.d.ts.map +1 -0
  130. package/dist/utils/plan.spec.js +420 -0
  131. package/dist/utils/plan.spec.js.map +1 -0
  132. package/dist/utils/reporting.d.ts +21 -0
  133. package/dist/utils/reporting.d.ts.map +1 -0
  134. package/dist/utils/reporting.js +82 -0
  135. package/dist/utils/reporting.js.map +1 -0
  136. package/dist/utils/reporting.spec.d.ts +2 -0
  137. package/dist/utils/reporting.spec.d.ts.map +1 -0
  138. package/dist/utils/reporting.spec.js +78 -0
  139. package/dist/utils/reporting.spec.js.map +1 -0
  140. package/dist/utils/reset.d.ts +14 -0
  141. package/dist/utils/reset.d.ts.map +1 -0
  142. package/dist/utils/reset.js +81 -0
  143. package/dist/utils/reset.js.map +1 -0
  144. package/dist/utils/revert.d.ts +30 -0
  145. package/dist/utils/revert.d.ts.map +1 -0
  146. package/dist/utils/revert.js +89 -0
  147. package/dist/utils/revert.js.map +1 -0
  148. package/dist/utils/revert.spec.d.ts +2 -0
  149. package/dist/utils/revert.spec.d.ts.map +1 -0
  150. package/dist/utils/revert.spec.js +102 -0
  151. package/dist/utils/revert.spec.js.map +1 -0
  152. package/dist/utils/similarity.d.ts +14 -0
  153. package/dist/utils/similarity.d.ts.map +1 -0
  154. package/dist/utils/similarity.js +70 -0
  155. package/dist/utils/similarity.js.map +1 -0
  156. package/dist/utils/similarity.spec.d.ts +2 -0
  157. package/dist/utils/similarity.spec.d.ts.map +1 -0
  158. package/dist/utils/similarity.spec.js +62 -0
  159. package/dist/utils/similarity.spec.js.map +1 -0
  160. package/dist/utils/snapshots.d.ts +21 -0
  161. package/dist/utils/snapshots.d.ts.map +1 -0
  162. package/dist/utils/snapshots.js +81 -0
  163. package/dist/utils/snapshots.js.map +1 -0
  164. package/dist/utils/snapshots.spec.d.ts +2 -0
  165. package/dist/utils/snapshots.spec.d.ts.map +1 -0
  166. package/dist/utils/snapshots.spec.js +56 -0
  167. package/dist/utils/snapshots.spec.js.map +1 -0
  168. package/dist/utils/syncFilters.d.ts +3 -0
  169. package/dist/utils/syncFilters.d.ts.map +1 -0
  170. package/dist/utils/syncFilters.js +8 -0
  171. package/dist/utils/syncFilters.js.map +1 -0
  172. package/dist/utils/syncRuntime.d.ts +3 -0
  173. package/dist/utils/syncRuntime.d.ts.map +1 -0
  174. package/dist/utils/syncRuntime.js +31 -0
  175. package/dist/utils/syncRuntime.js.map +1 -0
  176. package/dist/utils/validation.d.ts +3 -0
  177. package/dist/utils/validation.d.ts.map +1 -0
  178. package/dist/utils/validation.js +19 -0
  179. package/dist/utils/validation.js.map +1 -0
  180. package/dist/utils/validation.spec.d.ts +2 -0
  181. package/dist/utils/validation.spec.d.ts.map +1 -0
  182. package/dist/utils/validation.spec.js +36 -0
  183. package/dist/utils/validation.spec.js.map +1 -0
  184. package/package.json +63 -0
@@ -0,0 +1,89 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ import { fileExists, readFileSafe } from "./fs.js";
5
+ const MANIFEST_DIR = path.join(os.homedir(), ".link-agents");
6
+ const MANIFEST_PATH = path.join(MANIFEST_DIR, "manifest.json");
7
+ function createEmptyManifest() {
8
+ return {
9
+ version: 1,
10
+ lastSync: new Date().toISOString(),
11
+ generatedFiles: [],
12
+ };
13
+ }
14
+ export async function readManifest() {
15
+ const content = await readFileSafe(MANIFEST_PATH);
16
+ if (!content) {
17
+ return createEmptyManifest();
18
+ }
19
+ try {
20
+ return JSON.parse(content);
21
+ }
22
+ catch {
23
+ return createEmptyManifest();
24
+ }
25
+ }
26
+ export async function writeManifest(manifest) {
27
+ await fs.mkdir(MANIFEST_DIR, { recursive: true });
28
+ await fs.writeFile(MANIFEST_PATH, JSON.stringify(manifest, null, 2), "utf8");
29
+ }
30
+ export async function updateManifest(generatedFiles) {
31
+ const manifest = {
32
+ version: 1,
33
+ lastSync: new Date().toISOString(),
34
+ generatedFiles: [...new Set(generatedFiles)].sort(),
35
+ };
36
+ await writeManifest(manifest);
37
+ }
38
+ /**
39
+ * Find files that were in the previous manifest but not in the current plan.
40
+ * These are "stale" files that should be cleaned up.
41
+ */
42
+ export async function findStaleFiles(currentFiles) {
43
+ const manifest = await readManifest();
44
+ const currentSet = new Set(currentFiles);
45
+ const staleFiles = [];
46
+ for (const file of manifest.generatedFiles) {
47
+ if (!currentSet.has(file) && (await fileExists(file))) {
48
+ staleFiles.push(file);
49
+ }
50
+ }
51
+ return staleFiles;
52
+ }
53
+ /**
54
+ * Remove stale files that are no longer in the sync plan.
55
+ * Returns list of removed files.
56
+ */
57
+ export async function pruneStaleFiles(currentFiles) {
58
+ const staleFiles = await findStaleFiles(currentFiles);
59
+ const removed = [];
60
+ for (const file of staleFiles) {
61
+ try {
62
+ await fs.unlink(file);
63
+ removed.push(file);
64
+ }
65
+ catch {
66
+ // File may have been manually deleted
67
+ }
68
+ }
69
+ return removed;
70
+ }
71
+ /**
72
+ * Clear the manifest (used by reset command).
73
+ */
74
+ export async function clearManifest() {
75
+ try {
76
+ await fs.unlink(MANIFEST_PATH);
77
+ }
78
+ catch {
79
+ // File doesn't exist
80
+ }
81
+ }
82
+ /**
83
+ * Get all files from manifest (for reset command).
84
+ */
85
+ export async function getManifestFiles() {
86
+ const manifest = await readManifest();
87
+ return manifest.generatedFiles;
88
+ }
89
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/utils/manifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import type { AssetContent } from "../types/index.js";
2
+ /**
3
+ * Obfuscate an env value if it appears to be a secret
4
+ * Returns the original value if not a secret, obfuscated version otherwise
5
+ */
6
+ export declare function obfuscateEnvValue(key: string, value: string): string;
7
+ /**
8
+ * Format env vars for display, obfuscating secrets
9
+ */
10
+ export declare function formatEnvForDisplay(env: Record<string, string> | undefined): string;
11
+ /**
12
+ * Compare two server configs and return differences
13
+ */
14
+ export declare function compareServerConfigs(a: McpServerConfig, b: McpServerConfig): {
15
+ same: boolean;
16
+ differences: string[];
17
+ };
18
+ /**
19
+ * Normalized MCP config structure
20
+ */
21
+ export interface McpConfig {
22
+ mcpServers?: Record<string, McpServerConfig>;
23
+ [key: string]: unknown;
24
+ }
25
+ export interface McpServerConfig {
26
+ command?: string;
27
+ args?: string[];
28
+ env?: Record<string, string>;
29
+ [key: string]: unknown;
30
+ }
31
+ export type McpFormat = "json" | "jsonc" | "toml" | "yaml" | "unknown";
32
+ /**
33
+ * Detect config format from file extension
34
+ */
35
+ export declare function detectMcpFormat(filePath: string): McpFormat;
36
+ /**
37
+ * Parse MCP config from various formats
38
+ */
39
+ export declare function parseMcpConfig(content: string, format: McpFormat): McpConfig | null;
40
+ /**
41
+ * Serialize MCP config to target format
42
+ */
43
+ export declare function serializeMcpConfig(config: McpConfig, format: McpFormat, indent?: number): string;
44
+ /**
45
+ * Merge multiple MCP configs at entry level
46
+ * Later entries override earlier ones for the same server key
47
+ */
48
+ export declare function mergeMcpConfigs(configs: McpConfig[]): McpConfig;
49
+ /**
50
+ * Merge MCP assets and serialize to target format
51
+ */
52
+ export declare function mergeMcpAssets(assets: AssetContent[]): string | null;
53
+ /**
54
+ * Validation result for MCP config
55
+ */
56
+ export interface McpValidationResult {
57
+ valid: boolean;
58
+ errors: string[];
59
+ warnings: string[];
60
+ }
61
+ /**
62
+ * Validate MCP config content
63
+ */
64
+ export declare function validateMcpConfig(content: string, format: McpFormat): McpValidationResult;
65
+ /**
66
+ * Get list of commands used in MCP config
67
+ */
68
+ export declare function getMcpCommands(config: McpConfig): string[];
69
+ /**
70
+ * Find servers that exist in target but not in source (would be removed)
71
+ */
72
+ export declare function findRemovedServers(sourceConfig: McpConfig, targetConfig: McpConfig): string[];
73
+ //# sourceMappingURL=mcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/utils/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAyEtD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAKpE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GACtC,MAAM,CAYR;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,CAAC,EAAE,eAAe,EAClB,CAAC,EAAE,eAAe,GACjB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAA;CAAE,CA6C1C;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC7C,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAEvE;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAO3D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,SAAS,GAChB,SAAS,GAAG,IAAI,CAqBlB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,SAAS,EACjB,MAAM,SAAI,GACT,MAAM,CAYR;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,SAAS,CAqB/D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,GAAG,IAAI,CA4BpE;AAwRD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,SAAS,GAChB,mBAAmB,CAkCrB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,EAAE,CAc1D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,SAAS,EACvB,YAAY,EAAE,SAAS,GACtB,MAAM,EAAE,CAKV"}
@@ -0,0 +1,529 @@
1
+ /**
2
+ * Secret key name patterns (case-insensitive)
3
+ */
4
+ const SECRET_KEY_PATTERNS = [
5
+ /key/i,
6
+ /token/i,
7
+ /secret/i,
8
+ /password/i,
9
+ /credential/i,
10
+ /auth/i,
11
+ /private/i,
12
+ /access/i,
13
+ /api_/i,
14
+ ];
15
+ /**
16
+ * Secret value patterns
17
+ */
18
+ const SECRET_VALUE_PATTERNS = [
19
+ /^sk-/, // OpenAI
20
+ /^pk-/, // OpenAI public
21
+ /^ghp_/, // GitHub PAT
22
+ /^gho_/, // GitHub OAuth
23
+ /^ghs_/, // GitHub App
24
+ /^ghu_/, // GitHub user-to-server
25
+ /^github_pat_/, // GitHub fine-grained PAT
26
+ /^xox[baprs]-/, // Slack tokens
27
+ /^Bearer\s/i, // Bearer tokens
28
+ /^Basic\s/i, // Basic auth
29
+ /^AKIA/, // AWS access key
30
+ /^eyJ/, // JWT tokens (base64 JSON)
31
+ ];
32
+ /**
33
+ * Check if a key name suggests it contains a secret
34
+ */
35
+ function isSecretKey(key) {
36
+ return SECRET_KEY_PATTERNS.some((pattern) => pattern.test(key));
37
+ }
38
+ /**
39
+ * Check if a value looks like a secret
40
+ */
41
+ function isSecretValue(value) {
42
+ // Check known patterns
43
+ if (SECRET_VALUE_PATTERNS.some((pattern) => pattern.test(value))) {
44
+ return true;
45
+ }
46
+ // Long alphanumeric strings (32+ chars) are likely secrets
47
+ if (value.length >= 32 && /^[A-Za-z0-9_-]+$/.test(value)) {
48
+ return true;
49
+ }
50
+ return false;
51
+ }
52
+ /**
53
+ * Obfuscate a value, showing only prefix and suffix
54
+ */
55
+ function obfuscateValue(value) {
56
+ if (value.length <= 8) {
57
+ return "[hidden]";
58
+ }
59
+ const prefixLen = Math.min(4, Math.floor(value.length / 4));
60
+ const suffixLen = Math.min(3, Math.floor(value.length / 4));
61
+ return `${value.slice(0, prefixLen)}...${value.slice(-suffixLen)}`;
62
+ }
63
+ /**
64
+ * Obfuscate an env value if it appears to be a secret
65
+ * Returns the original value if not a secret, obfuscated version otherwise
66
+ */
67
+ export function obfuscateEnvValue(key, value) {
68
+ if (isSecretKey(key) || isSecretValue(value)) {
69
+ return obfuscateValue(value);
70
+ }
71
+ return value;
72
+ }
73
+ /**
74
+ * Format env vars for display, obfuscating secrets
75
+ */
76
+ export function formatEnvForDisplay(env) {
77
+ if (!env || Object.keys(env).length === 0) {
78
+ return "no env";
79
+ }
80
+ const parts = [];
81
+ for (const [key, value] of Object.entries(env)) {
82
+ const displayValue = obfuscateEnvValue(key, value);
83
+ parts.push(`${key}=${displayValue}`);
84
+ }
85
+ return parts.join(", ");
86
+ }
87
+ /**
88
+ * Compare two server configs and return differences
89
+ */
90
+ export function compareServerConfigs(a, b) {
91
+ const differences = [];
92
+ // Compare command
93
+ if (a.command !== b.command) {
94
+ differences.push(`command: "${a.command}" vs "${b.command}"`);
95
+ }
96
+ // Compare args
97
+ const argsA = JSON.stringify(a.args ?? []);
98
+ const argsB = JSON.stringify(b.args ?? []);
99
+ if (argsA !== argsB) {
100
+ differences.push(`args differ`);
101
+ }
102
+ // Compare env
103
+ const envA = a.env ?? {};
104
+ const envB = b.env ?? {};
105
+ const allEnvKeys = new Set([...Object.keys(envA), ...Object.keys(envB)]);
106
+ for (const key of allEnvKeys) {
107
+ const valA = envA[key];
108
+ const valB = envB[key];
109
+ if (valA !== valB) {
110
+ if (valA === undefined) {
111
+ differences.push(`${key}: [missing] vs ${obfuscateEnvValue(key, valB)}`);
112
+ }
113
+ else if (valB === undefined) {
114
+ differences.push(`${key}: ${obfuscateEnvValue(key, valA)} vs [missing]`);
115
+ }
116
+ else {
117
+ differences.push(`${key}: ${obfuscateEnvValue(key, valA)} vs ${obfuscateEnvValue(key, valB)}`);
118
+ }
119
+ }
120
+ }
121
+ return {
122
+ same: differences.length === 0,
123
+ differences,
124
+ };
125
+ }
126
+ /**
127
+ * Detect config format from file extension
128
+ */
129
+ export function detectMcpFormat(filePath) {
130
+ const lower = filePath.toLowerCase();
131
+ if (lower.endsWith(".json"))
132
+ return "json";
133
+ if (lower.endsWith(".jsonc"))
134
+ return "jsonc";
135
+ if (lower.endsWith(".toml"))
136
+ return "toml";
137
+ if (lower.endsWith(".yaml") || lower.endsWith(".yml"))
138
+ return "yaml";
139
+ return "unknown";
140
+ }
141
+ /**
142
+ * Parse MCP config from various formats
143
+ */
144
+ export function parseMcpConfig(content, format) {
145
+ // Handle empty content
146
+ if (!content.trim()) {
147
+ return { mcpServers: {} };
148
+ }
149
+ try {
150
+ switch (format) {
151
+ case "json":
152
+ case "jsonc":
153
+ return parseJsonWithComments(content);
154
+ case "toml":
155
+ return parseToml(content);
156
+ case "yaml":
157
+ return parseYaml(content);
158
+ default:
159
+ return null;
160
+ }
161
+ }
162
+ catch {
163
+ return null;
164
+ }
165
+ }
166
+ /**
167
+ * Serialize MCP config to target format
168
+ */
169
+ export function serializeMcpConfig(config, format, indent = 2) {
170
+ switch (format) {
171
+ case "json":
172
+ case "jsonc":
173
+ return JSON.stringify(config, null, indent);
174
+ case "toml":
175
+ return serializeToml(config, indent);
176
+ case "yaml":
177
+ return serializeYaml(config, indent);
178
+ default:
179
+ return JSON.stringify(config, null, indent);
180
+ }
181
+ }
182
+ /**
183
+ * Merge multiple MCP configs at entry level
184
+ * Later entries override earlier ones for the same server key
185
+ */
186
+ export function mergeMcpConfigs(configs) {
187
+ const merged = { mcpServers: {} };
188
+ for (const config of configs) {
189
+ // Merge mcpServers
190
+ if (config.mcpServers) {
191
+ merged.mcpServers = {
192
+ ...merged.mcpServers,
193
+ ...config.mcpServers,
194
+ };
195
+ }
196
+ // Merge other top-level keys
197
+ for (const [key, value] of Object.entries(config)) {
198
+ if (key !== "mcpServers") {
199
+ merged[key] = value;
200
+ }
201
+ }
202
+ }
203
+ return merged;
204
+ }
205
+ /**
206
+ * Merge MCP assets and serialize to target format
207
+ */
208
+ export function mergeMcpAssets(assets) {
209
+ if (assets.length === 0)
210
+ return null;
211
+ if (assets.length === 1)
212
+ return assets[0].content;
213
+ const configs = [];
214
+ let targetFormat = "json";
215
+ // Parse all configs
216
+ for (const asset of assets) {
217
+ const format = detectMcpFormat(asset.path);
218
+ const parsed = parseMcpConfig(asset.content, format);
219
+ if (parsed) {
220
+ configs.push(parsed);
221
+ // Use format of first asset as target
222
+ if (configs.length === 1) {
223
+ targetFormat = format;
224
+ }
225
+ }
226
+ }
227
+ if (configs.length === 0) {
228
+ // Fallback: concatenate if parsing fails
229
+ return assets.map((a) => a.content).join("\n\n---\n\n");
230
+ }
231
+ // Merge and serialize
232
+ const merged = mergeMcpConfigs(configs);
233
+ return serializeMcpConfig(merged, targetFormat);
234
+ }
235
+ /**
236
+ * Parse JSON with comments (JSONC format)
237
+ * Handles // and /* comments while preserving URLs inside strings
238
+ */
239
+ function parseJsonWithComments(content) {
240
+ // Remove comments while preserving string contents
241
+ // Strategy: tokenize strings first, then remove comments from non-string parts
242
+ const result = [];
243
+ let i = 0;
244
+ while (i < content.length) {
245
+ const char = content[i];
246
+ // Handle string literals - copy them verbatim
247
+ if (char === '"') {
248
+ const start = i;
249
+ i++; // skip opening quote
250
+ while (i < content.length) {
251
+ if (content[i] === "\\" && i + 1 < content.length) {
252
+ i += 2; // skip escaped char
253
+ }
254
+ else if (content[i] === '"') {
255
+ i++; // skip closing quote
256
+ break;
257
+ }
258
+ else {
259
+ i++;
260
+ }
261
+ }
262
+ result.push(content.slice(start, i));
263
+ continue;
264
+ }
265
+ // Handle single-line comments
266
+ if (char === "/" && content[i + 1] === "/") {
267
+ // Skip until end of line
268
+ while (i < content.length && content[i] !== "\n") {
269
+ i++;
270
+ }
271
+ continue;
272
+ }
273
+ // Handle multi-line comments
274
+ if (char === "/" && content[i + 1] === "*") {
275
+ i += 2; // skip /*
276
+ while (i < content.length &&
277
+ !(content[i] === "*" && content[i + 1] === "/")) {
278
+ i++;
279
+ }
280
+ i += 2; // skip */
281
+ continue;
282
+ }
283
+ // Regular character
284
+ result.push(char);
285
+ i++;
286
+ }
287
+ return JSON.parse(result.join(""));
288
+ }
289
+ /**
290
+ * Parse TOML (basic implementation)
291
+ * For production, consider using a library like @iarna/toml
292
+ */
293
+ function parseToml(content) {
294
+ const config = { mcpServers: {} };
295
+ const lines = content.split("\n");
296
+ let currentSection = null;
297
+ let currentServer = null;
298
+ for (let line of lines) {
299
+ line = line.trim();
300
+ if (!line || line.startsWith("#"))
301
+ continue;
302
+ // Section header [mcpServers.servername]
303
+ const sectionMatch = line.match(/^\[mcpServers\.(.+)\]$/);
304
+ if (sectionMatch) {
305
+ currentSection = sectionMatch[1];
306
+ currentServer = {};
307
+ config.mcpServers[currentSection] = currentServer;
308
+ continue;
309
+ }
310
+ // Key-value pair
311
+ const kvMatch = line.match(/^(\w+)\s*=\s*(.+)$/);
312
+ if (kvMatch && currentServer) {
313
+ const [, key, value] = kvMatch;
314
+ currentServer[key] = parseTomlValue(value);
315
+ }
316
+ }
317
+ return config;
318
+ }
319
+ /**
320
+ * Parse TOML value
321
+ */
322
+ function parseTomlValue(value) {
323
+ value = value.trim();
324
+ // String
325
+ if (value.startsWith('"') && value.endsWith('"')) {
326
+ return value.slice(1, -1);
327
+ }
328
+ // Array
329
+ if (value.startsWith("[") && value.endsWith("]")) {
330
+ const items = value
331
+ .slice(1, -1)
332
+ .split(",")
333
+ .map((v) => parseTomlValue(v));
334
+ return items;
335
+ }
336
+ // Boolean
337
+ if (value === "true")
338
+ return true;
339
+ if (value === "false")
340
+ return false;
341
+ // Number
342
+ if (/^-?\d+(\.\d+)?$/.test(value)) {
343
+ return parseFloat(value);
344
+ }
345
+ return value;
346
+ }
347
+ /**
348
+ * Serialize to TOML
349
+ */
350
+ function serializeToml(config, indent) {
351
+ let output = "";
352
+ if (config.mcpServers) {
353
+ for (const [serverName, serverConfig] of Object.entries(config.mcpServers)) {
354
+ output += `[mcpServers.${serverName}]\n`;
355
+ for (const [key, value] of Object.entries(serverConfig)) {
356
+ output += `${key} = ${serializeTomlValue(value)}\n`;
357
+ }
358
+ output += "\n";
359
+ }
360
+ }
361
+ return output.trim();
362
+ }
363
+ /**
364
+ * Serialize TOML value
365
+ */
366
+ function serializeTomlValue(value) {
367
+ if (typeof value === "string") {
368
+ return `"${value}"`;
369
+ }
370
+ if (Array.isArray(value)) {
371
+ return `[${value.map(serializeTomlValue).join(", ")}]`;
372
+ }
373
+ if (typeof value === "object" && value !== null) {
374
+ return JSON.stringify(value);
375
+ }
376
+ return String(value);
377
+ }
378
+ /**
379
+ * Parse YAML (basic implementation)
380
+ * For production, consider using a library like js-yaml
381
+ */
382
+ function parseYaml(content) {
383
+ const config = { mcpServers: {} };
384
+ const lines = content.split("\n");
385
+ let currentKey = null;
386
+ let currentServer = null;
387
+ let indent = 0;
388
+ for (let line of lines) {
389
+ if (!line.trim() || line.trim().startsWith("#"))
390
+ continue;
391
+ const leadingSpaces = line.length - line.trimStart().length;
392
+ line = line.trim();
393
+ // mcpServers section
394
+ if (line === "mcpServers:") {
395
+ indent = leadingSpaces;
396
+ continue;
397
+ }
398
+ // Server name
399
+ if (leadingSpaces === indent + 2 && line.endsWith(":")) {
400
+ currentKey = line.slice(0, -1);
401
+ currentServer = {};
402
+ config.mcpServers[currentKey] = currentServer;
403
+ continue;
404
+ }
405
+ // Server properties
406
+ const kvMatch = line.match(/^(\w+):\s*(.*)$/);
407
+ if (kvMatch && currentServer) {
408
+ const [, key, value] = kvMatch;
409
+ currentServer[key] = parseYamlValue(value);
410
+ }
411
+ }
412
+ return config;
413
+ }
414
+ /**
415
+ * Parse YAML value
416
+ */
417
+ function parseYamlValue(value) {
418
+ value = value.trim();
419
+ if (!value)
420
+ return "";
421
+ // Array
422
+ if (value.startsWith("[") && value.endsWith("]")) {
423
+ return value
424
+ .slice(1, -1)
425
+ .split(",")
426
+ .map((v) => v.trim().replace(/^["']|["']$/g, ""));
427
+ }
428
+ // String with quotes
429
+ if ((value.startsWith('"') && value.endsWith('"')) ||
430
+ (value.startsWith("'") && value.endsWith("'"))) {
431
+ return value.slice(1, -1);
432
+ }
433
+ // Boolean
434
+ if (value === "true")
435
+ return true;
436
+ if (value === "false")
437
+ return false;
438
+ // Number
439
+ if (/^-?\d+(\.\d+)?$/.test(value)) {
440
+ return parseFloat(value);
441
+ }
442
+ return value;
443
+ }
444
+ /**
445
+ * Serialize to YAML
446
+ */
447
+ function serializeYaml(config, indent) {
448
+ let output = "mcpServers:\n";
449
+ if (config.mcpServers) {
450
+ for (const [serverName, serverConfig] of Object.entries(config.mcpServers)) {
451
+ output += ` ${serverName}:\n`;
452
+ for (const [key, value] of Object.entries(serverConfig)) {
453
+ output += ` ${key}: ${serializeYamlValue(value)}\n`;
454
+ }
455
+ }
456
+ }
457
+ return output;
458
+ }
459
+ /**
460
+ * Serialize YAML value
461
+ */
462
+ function serializeYamlValue(value) {
463
+ if (typeof value === "string") {
464
+ return `"${value}"`;
465
+ }
466
+ if (Array.isArray(value)) {
467
+ return `[${value.map((v) => `"${v}"`).join(", ")}]`;
468
+ }
469
+ if (typeof value === "object" && value !== null) {
470
+ return JSON.stringify(value);
471
+ }
472
+ return String(value);
473
+ }
474
+ /**
475
+ * Validate MCP config content
476
+ */
477
+ export function validateMcpConfig(content, format) {
478
+ const errors = [];
479
+ const warnings = [];
480
+ // Try to parse
481
+ const config = parseMcpConfig(content, format);
482
+ if (!config) {
483
+ errors.push(`Failed to parse ${format} config`);
484
+ return { valid: false, errors, warnings };
485
+ }
486
+ // Check for mcpServers
487
+ if (!config.mcpServers) {
488
+ warnings.push("Config has no mcpServers key");
489
+ return { valid: true, errors, warnings };
490
+ }
491
+ // Validate each server
492
+ for (const [serverName, serverConfig] of Object.entries(config.mcpServers)) {
493
+ if (!serverConfig.command) {
494
+ errors.push(`Server "${serverName}" has no command`);
495
+ }
496
+ // Check for empty command
497
+ if (serverConfig.command &&
498
+ typeof serverConfig.command === "string" &&
499
+ serverConfig.command.trim() === "") {
500
+ errors.push(`Server "${serverName}" has empty command`);
501
+ }
502
+ }
503
+ return { valid: errors.length === 0, errors, warnings };
504
+ }
505
+ /**
506
+ * Get list of commands used in MCP config
507
+ */
508
+ export function getMcpCommands(config) {
509
+ const commands = new Set();
510
+ if (config.mcpServers) {
511
+ for (const serverConfig of Object.values(config.mcpServers)) {
512
+ if (serverConfig.command) {
513
+ // Extract base command (first word)
514
+ const baseCommand = serverConfig.command.split(/\s+/)[0];
515
+ commands.add(baseCommand);
516
+ }
517
+ }
518
+ }
519
+ return Array.from(commands);
520
+ }
521
+ /**
522
+ * Find servers that exist in target but not in source (would be removed)
523
+ */
524
+ export function findRemovedServers(sourceConfig, targetConfig) {
525
+ const sourceServers = new Set(Object.keys(sourceConfig.mcpServers ?? {}));
526
+ const targetServers = Object.keys(targetConfig.mcpServers ?? {});
527
+ return targetServers.filter((name) => !sourceServers.has(name));
528
+ }
529
+ //# sourceMappingURL=mcp.js.map