callman-mcp 0.1.0 → 0.2.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 (204) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +45 -16
  3. package/dist/client/api.d.ts +64 -3
  4. package/dist/client/api.d.ts.map +1 -1
  5. package/dist/client/api.js +67 -2
  6. package/dist/client/api.js.map +1 -1
  7. package/dist/client/errors.d.ts.map +1 -1
  8. package/dist/client/errors.js +33 -7
  9. package/dist/client/errors.js.map +1 -1
  10. package/dist/client/types.d.ts +91 -2
  11. package/dist/client/types.d.ts.map +1 -1
  12. package/dist/context.d.ts +2 -0
  13. package/dist/context.d.ts.map +1 -1
  14. package/dist/context.js +7 -2
  15. package/dist/context.js.map +1 -1
  16. package/dist/domain/contracts.d.ts +893 -0
  17. package/dist/domain/contracts.d.ts.map +1 -0
  18. package/dist/domain/contracts.js +90 -0
  19. package/dist/domain/contracts.js.map +1 -0
  20. package/dist/domain/diagram/build.d.ts +520 -18
  21. package/dist/domain/diagram/build.d.ts.map +1 -1
  22. package/dist/domain/diagram/build.js +65 -19
  23. package/dist/domain/diagram/build.js.map +1 -1
  24. package/dist/domain/diagram/patch.d.ts +624 -51
  25. package/dist/domain/diagram/patch.d.ts.map +1 -1
  26. package/dist/domain/diagram/patch.js +180 -40
  27. package/dist/domain/diagram/patch.js.map +1 -1
  28. package/dist/domain/diagram/style.d.ts +315 -0
  29. package/dist/domain/diagram/style.d.ts.map +1 -0
  30. package/dist/domain/diagram/style.js +124 -0
  31. package/dist/domain/diagram/style.js.map +1 -0
  32. package/dist/domain/diagram/summarize.d.ts +8 -0
  33. package/dist/domain/diagram/summarize.d.ts.map +1 -1
  34. package/dist/domain/diagram/summarize.js +28 -13
  35. package/dist/domain/diagram/summarize.js.map +1 -1
  36. package/dist/domain/requestIntegrations.d.ts +719 -0
  37. package/dist/domain/requestIntegrations.d.ts.map +1 -0
  38. package/dist/domain/requestIntegrations.js +222 -0
  39. package/dist/domain/requestIntegrations.js.map +1 -0
  40. package/dist/domain/requestShape.d.ts +1350 -152
  41. package/dist/domain/requestShape.d.ts.map +1 -1
  42. package/dist/domain/requestShape.js +336 -121
  43. package/dist/domain/requestShape.js.map +1 -1
  44. package/dist/domain/resolve.d.ts +2 -1
  45. package/dist/domain/resolve.d.ts.map +1 -1
  46. package/dist/domain/resolve.js +5 -1
  47. package/dist/domain/resolve.js.map +1 -1
  48. package/dist/domain/scenario/build.d.ts +82 -26
  49. package/dist/domain/scenario/build.d.ts.map +1 -1
  50. package/dist/domain/scenario/build.js +196 -65
  51. package/dist/domain/scenario/build.js.map +1 -1
  52. package/dist/domain/scenario/nodeConfig.d.ts +11 -0
  53. package/dist/domain/scenario/nodeConfig.d.ts.map +1 -0
  54. package/dist/domain/scenario/nodeConfig.js +251 -0
  55. package/dist/domain/scenario/nodeConfig.js.map +1 -0
  56. package/dist/domain/scenario/summarize.d.ts +145 -25
  57. package/dist/domain/scenario/summarize.d.ts.map +1 -1
  58. package/dist/domain/scenario/summarize.js +84 -29
  59. package/dist/domain/scenario/summarize.js.map +1 -1
  60. package/dist/lib/mask.d.ts +0 -18
  61. package/dist/lib/mask.d.ts.map +1 -1
  62. package/dist/lib/mask.js +2 -44
  63. package/dist/lib/mask.js.map +1 -1
  64. package/dist/lib/pagination.d.ts +3 -2
  65. package/dist/lib/pagination.d.ts.map +1 -1
  66. package/dist/lib/pagination.js +13 -8
  67. package/dist/lib/pagination.js.map +1 -1
  68. package/dist/lib/redact.d.ts +12 -0
  69. package/dist/lib/redact.d.ts.map +1 -0
  70. package/dist/lib/redact.js +50 -0
  71. package/dist/lib/redact.js.map +1 -0
  72. package/dist/lib/strict.d.ts +4 -0
  73. package/dist/lib/strict.d.ts.map +1 -0
  74. package/dist/lib/strict.js +14 -0
  75. package/dist/lib/strict.js.map +1 -0
  76. package/dist/prompts/index.js +7 -7
  77. package/dist/prompts/index.js.map +1 -1
  78. package/dist/resources/docs/contracts.d.ts +2 -0
  79. package/dist/resources/docs/contracts.d.ts.map +1 -0
  80. package/dist/resources/docs/contracts.js +74 -0
  81. package/dist/resources/docs/contracts.js.map +1 -0
  82. package/dist/resources/docs/diagramSchema.d.ts +1 -1
  83. package/dist/resources/docs/diagramSchema.d.ts.map +1 -1
  84. package/dist/resources/docs/diagramSchema.js +95 -28
  85. package/dist/resources/docs/diagramSchema.js.map +1 -1
  86. package/dist/resources/docs/overview.d.ts +1 -1
  87. package/dist/resources/docs/overview.d.ts.map +1 -1
  88. package/dist/resources/docs/overview.js +29 -7
  89. package/dist/resources/docs/overview.js.map +1 -1
  90. package/dist/resources/docs/requestSchema.d.ts +1 -1
  91. package/dist/resources/docs/requestSchema.d.ts.map +1 -1
  92. package/dist/resources/docs/requestSchema.js +85 -16
  93. package/dist/resources/docs/requestSchema.js.map +1 -1
  94. package/dist/resources/generated/aiPacks.d.ts +1 -1
  95. package/dist/resources/generated/aiPacks.d.ts.map +1 -1
  96. package/dist/resources/generated/aiPacks.js +3 -2
  97. package/dist/resources/generated/aiPacks.js.map +1 -1
  98. package/dist/resources/index.d.ts.map +1 -1
  99. package/dist/resources/index.js +19 -19
  100. package/dist/resources/index.js.map +1 -1
  101. package/dist/run/executeCollection.d.ts +3 -1
  102. package/dist/run/executeCollection.d.ts.map +1 -1
  103. package/dist/run/executeCollection.js +16 -4
  104. package/dist/run/executeCollection.js.map +1 -1
  105. package/dist/run/summarize.d.ts +17 -6
  106. package/dist/run/summarize.d.ts.map +1 -1
  107. package/dist/run/summarize.js +48 -41
  108. package/dist/run/summarize.js.map +1 -1
  109. package/dist/run/types.d.ts +1 -0
  110. package/dist/run/types.d.ts.map +1 -1
  111. package/dist/server.d.ts +1 -1
  112. package/dist/server.d.ts.map +1 -1
  113. package/dist/server.js +1 -1
  114. package/dist/server.js.map +1 -1
  115. package/dist/tools/catalog.d.ts +1 -1
  116. package/dist/tools/catalog.d.ts.map +1 -1
  117. package/dist/tools/catalog.js +18 -0
  118. package/dist/tools/catalog.js.map +1 -1
  119. package/dist/tools/collections.d.ts.map +1 -1
  120. package/dist/tools/collections.js +53 -41
  121. package/dist/tools/collections.js.map +1 -1
  122. package/dist/tools/contracts.d.ts +4 -0
  123. package/dist/tools/contracts.d.ts.map +1 -0
  124. package/dist/tools/contracts.js +201 -0
  125. package/dist/tools/contracts.js.map +1 -0
  126. package/dist/tools/diagrams.d.ts.map +1 -1
  127. package/dist/tools/diagrams.js +19 -10
  128. package/dist/tools/diagrams.js.map +1 -1
  129. package/dist/tools/environments.d.ts.map +1 -1
  130. package/dist/tools/environments.js +184 -64
  131. package/dist/tools/environments.js.map +1 -1
  132. package/dist/tools/import.d.ts.map +1 -1
  133. package/dist/tools/import.js +111 -20
  134. package/dist/tools/import.js.map +1 -1
  135. package/dist/tools/index.d.ts +2 -1
  136. package/dist/tools/index.d.ts.map +1 -1
  137. package/dist/tools/index.js +19 -1
  138. package/dist/tools/index.js.map +1 -1
  139. package/dist/tools/lookups.d.ts +38 -7
  140. package/dist/tools/lookups.d.ts.map +1 -1
  141. package/dist/tools/lookups.js +113 -10
  142. package/dist/tools/lookups.js.map +1 -1
  143. package/dist/tools/meta.d.ts.map +1 -1
  144. package/dist/tools/meta.js +9 -4
  145. package/dist/tools/meta.js.map +1 -1
  146. package/dist/tools/requests.d.ts.map +1 -1
  147. package/dist/tools/requests.js +138 -50
  148. package/dist/tools/requests.js.map +1 -1
  149. package/dist/tools/run.d.ts.map +1 -1
  150. package/dist/tools/run.js +84 -28
  151. package/dist/tools/run.js.map +1 -1
  152. package/dist/tools/scenarios.d.ts.map +1 -1
  153. package/dist/tools/scenarios.js +238 -75
  154. package/dist/tools/scenarios.js.map +1 -1
  155. package/dist/tools/shared.d.ts +1 -0
  156. package/dist/tools/shared.d.ts.map +1 -1
  157. package/dist/tools/shared.js +9 -1
  158. package/dist/tools/shared.js.map +1 -1
  159. package/dist/tools/workspaces.d.ts.map +1 -1
  160. package/dist/tools/workspaces.js +44 -7
  161. package/dist/tools/workspaces.js.map +1 -1
  162. package/dist-cjs/client/api.js +67 -2
  163. package/dist-cjs/client/errors.js +33 -7
  164. package/dist-cjs/context.js +7 -2
  165. package/dist-cjs/domain/contracts.js +93 -0
  166. package/dist-cjs/domain/diagram/build.js +71 -20
  167. package/dist-cjs/domain/diagram/patch.js +179 -39
  168. package/dist-cjs/domain/diagram/style.js +130 -0
  169. package/dist-cjs/domain/diagram/summarize.js +28 -13
  170. package/dist-cjs/domain/requestIntegrations.js +228 -0
  171. package/dist-cjs/domain/requestShape.js +342 -121
  172. package/dist-cjs/domain/resolve.js +5 -1
  173. package/dist-cjs/domain/scenario/build.js +197 -65
  174. package/dist-cjs/domain/scenario/nodeConfig.js +260 -0
  175. package/dist-cjs/domain/scenario/summarize.js +86 -29
  176. package/dist-cjs/lib/mask.js +3 -48
  177. package/dist-cjs/lib/pagination.js +13 -8
  178. package/dist-cjs/lib/redact.js +53 -0
  179. package/dist-cjs/lib/strict.js +18 -0
  180. package/dist-cjs/prompts/index.js +7 -7
  181. package/dist-cjs/resources/docs/contracts.js +76 -0
  182. package/dist-cjs/resources/docs/diagramSchema.js +95 -28
  183. package/dist-cjs/resources/docs/overview.js +29 -7
  184. package/dist-cjs/resources/docs/requestSchema.js +85 -16
  185. package/dist-cjs/resources/generated/aiPacks.js +3 -2
  186. package/dist-cjs/resources/index.js +17 -17
  187. package/dist-cjs/run/executeCollection.js +16 -4
  188. package/dist-cjs/run/summarize.js +48 -41
  189. package/dist-cjs/server.js +1 -1
  190. package/dist-cjs/tools/catalog.js +18 -0
  191. package/dist-cjs/tools/collections.js +52 -40
  192. package/dist-cjs/tools/contracts.js +204 -0
  193. package/dist-cjs/tools/diagrams.js +19 -10
  194. package/dist-cjs/tools/environments.js +182 -62
  195. package/dist-cjs/tools/import.js +107 -16
  196. package/dist-cjs/tools/index.js +21 -2
  197. package/dist-cjs/tools/lookups.js +123 -11
  198. package/dist-cjs/tools/meta.js +9 -4
  199. package/dist-cjs/tools/requests.js +134 -46
  200. package/dist-cjs/tools/run.js +82 -26
  201. package/dist-cjs/tools/scenarios.js +235 -72
  202. package/dist-cjs/tools/shared.js +11 -2
  203. package/dist-cjs/tools/workspaces.js +43 -6
  204. package/package.json +2 -2
@@ -1,10 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.requestSummaryView = exports.compactRequestView = exports.toUpdateRequestPayload = exports.updateRequestInputSchema = exports.toFlatImportRequest = exports.toCreateRequestPayload = exports.toBodyPayload = exports.toAuthPayload = exports.toRows = exports.requestInputSchema = exports.authInputSchema = exports.bodyInputSchema = exports.kvSchema = exports.HTTP_METHODS = void 0;
3
+ exports.requestSummaryView = exports.compactRequestView = exports.bodyInputView = exports.toUpdateRequestPayload = exports.updateRequestInputSchema = exports.toFlatImportRequest = exports.toCreateRequestPayload = exports.resolveMaskedValues = exports.authInputView = exports.toAuthPayload = exports.sanitizedKeyWarnings = exports.toBodyPayload = exports.syncUrlAndQuery = exports.buildUrlWithQueryRows = exports.toRows = exports.requestInputSchema = exports.authInputSchema = exports.bodyInputSchema = exports.pathVariableSchema = exports.kvSchema = exports.HTTP_METHODS = void 0;
4
4
  const callman_core_1 = require("callman-core");
5
5
  const zod_1 = require("zod");
6
+ const errors_js_1 = require("../client/errors.js");
6
7
  const mask_js_1 = require("../lib/mask.js");
8
+ const strict_js_1 = require("../lib/strict.js");
7
9
  const truncate_js_1 = require("../lib/truncate.js");
10
+ const requestIntegrations_js_1 = require("./requestIntegrations.js");
8
11
  // The COMPACT request form a model writes (name/method/url, key-value lists,
9
12
  // a body that may be a JSON value, a string or a mode object, a small auth
10
13
  // object) and its translation into the backend's `POST /requests` payload —
@@ -12,76 +15,205 @@ const truncate_js_1 = require("../lib/truncate.js");
12
15
  // serialisation (`toPersistedRequestBody`) so an imported request opens in
13
16
  // the editor exactly like a hand-made one.
14
17
  exports.HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "TRACE"];
15
- exports.kvSchema = zod_1.z.object({
16
- key: zod_1.z.string().max(100),
18
+ // Key/value rows — the backend's own limits (key 1–100 chars, value ≤ 5000),
19
+ // strict so a stray `description` or `type` is an error, not a silent drop.
20
+ exports.kvSchema = (0, strict_js_1.strictObject)({
21
+ key: zod_1.z.string().trim().min(1).max(100),
17
22
  value: zod_1.z.string().max(5000).default(""),
18
- enabled: zod_1.z.boolean().optional(),
19
- description: zod_1.z.string().max(500).optional(),
20
- });
21
- exports.bodyInputSchema = zod_1.z.union([
22
- zod_1.z.object({
23
- mode: zod_1.z.literal("raw"),
24
- raw: zod_1.z.string().max(2_000_000),
25
- language: zod_1.z.enum(["json", "xml", "text", "html"]).optional(),
26
- }),
27
- zod_1.z.object({
28
- mode: zod_1.z.enum(["formdata", "urlencoded"]),
29
- entries: zod_1.z.array(exports.kvSchema).max(200),
30
- }),
31
- zod_1.z.object({ mode: zod_1.z.literal("none") }),
32
- zod_1.z.unknown(),
33
- ]);
23
+ enabled: zod_1.z.boolean().optional().describe("false keeps the row but does not send it (default true)"),
24
+ }, "row field");
25
+ exports.pathVariableSchema = (0, strict_js_1.strictObject)({
26
+ key: zod_1.z.string().trim().min(1).max(100).describe("The name after ':' in the URL, without the colon"),
27
+ value: zod_1.z.string().max(5000).default(""),
28
+ description: zod_1.z.string().max(5000).optional(),
29
+ }, "path variable field");
30
+ const RAW_LANGUAGES = ["json", "xml", "text", "html"];
31
+ const rawBodySchema = (0, strict_js_1.strictObject)({
32
+ mode: zod_1.z.literal("raw"),
33
+ raw: zod_1.z.string().max(2_000_000).describe("The body text; {{variables}} are resolved when it runs"),
34
+ language: zod_1.z.enum(RAW_LANGUAGES).optional().describe("Editor language and default Content-Type; sniffed from the text when omitted"),
35
+ rawLanguage: zod_1.z.enum(RAW_LANGUAGES).optional().describe("Same as language (the stored name)"),
36
+ }, "raw body field");
37
+ const formBodySchema = (0, strict_js_1.strictObject)({
38
+ mode: zod_1.z.enum(["formdata", "urlencoded"]),
39
+ entries: zod_1.z.array(exports.kvSchema).max(200).optional().describe("Text fields; file parts cannot be set through the API"),
40
+ formEntries: zod_1.z.array(zod_1.z.object({ key: zod_1.z.string(), value: zod_1.z.string().default(""), enabled: zod_1.z.boolean().optional() }).passthrough()).max(200).optional().describe("Same as entries (the stored name)"),
41
+ }, "form body field");
42
+ const jsonBodySchema = (0, strict_js_1.strictObject)({ mode: zod_1.z.literal("json"), json: zod_1.z.unknown().describe("Any JSON value — use this form when the payload itself has a top-level `mode` key") }, "json body field");
43
+ const noneBodySchema = (0, strict_js_1.strictObject)({ mode: zod_1.z.literal("none") }, "body field");
44
+ const BODY_MODES = ["raw", "formdata", "urlencoded", "json", "none"];
45
+ exports.bodyInputSchema = zod_1.z
46
+ .union([
47
+ zod_1.z.string().max(2_000_000),
48
+ rawBodySchema,
49
+ formBodySchema,
50
+ jsonBodySchema,
51
+ noneBodySchema,
52
+ zod_1.z.array(zod_1.z.unknown()),
53
+ zod_1.z.number(),
54
+ zod_1.z.boolean(),
55
+ zod_1.z.record(zod_1.z.unknown()).refine((value) => !("mode" in value), { message: `A body object with a "mode" key must be one of the modes ${BODY_MODES.join(", ")}` }),
56
+ ], {
57
+ errorMap: (issue, ctx) => issue.code === zod_1.z.ZodIssueCode.invalid_union
58
+ ? {
59
+ message: `Invalid body. Send a JSON value (object/array), a string, or { mode: "raw", raw, language? } | { mode: "formdata"|"urlencoded", entries: [{ key, value }] } | { mode: "json", json } | { mode: "none" }.`,
60
+ }
61
+ : { message: ctx.defaultError },
62
+ })
63
+ .describe('A JSON object/array (sent as JSON); a string (JSON is parsed, <xml>/<html> sniffed, otherwise text); { mode: "raw", raw, language: json|xml|text|html }; { mode: "formdata"|"urlencoded", entries: [{ key, value }] }; { mode: "json", json } when the payload itself has a top-level "mode" key; { mode: "none" }.');
64
+ const secret = (what) => zod_1.z.string().max(10_000).describe(`${what}. {{variable}} references are fine`);
34
65
  exports.authInputSchema = zod_1.z.discriminatedUnion("type", [
35
- zod_1.z.object({ type: zod_1.z.literal("none") }),
36
- zod_1.z.object({ type: zod_1.z.literal("bearer"), token: zod_1.z.string().max(10_000) }),
37
- zod_1.z.object({ type: zod_1.z.literal("basic"), username: zod_1.z.string().max(5000), password: zod_1.z.string().max(5000).default("") }),
38
- zod_1.z.object({
66
+ (0, strict_js_1.strictObject)({ type: zod_1.z.literal("none") }, "auth field"),
67
+ (0, strict_js_1.strictObject)({ type: zod_1.z.literal("bearer"), token: secret("Bearer token") }, "bearer auth field"),
68
+ (0, strict_js_1.strictObject)({ type: zod_1.z.literal("basic"), username: zod_1.z.string().max(5000), password: zod_1.z.string().max(5000).default("") }, "basic auth field"),
69
+ (0, strict_js_1.strictObject)({
39
70
  type: zod_1.z.literal("apiKey"),
40
- key: zod_1.z.string().max(5000),
41
- value: zod_1.z.string().max(10_000),
71
+ key: zod_1.z.string().max(5000).describe("Header or query parameter name, e.g. X-API-Key"),
72
+ value: secret("API key"),
42
73
  location: zod_1.z.enum(["header", "query"]).default("header"),
43
- }),
44
- zod_1.z.object({
74
+ }, "apiKey auth field"),
75
+ (0, strict_js_1.strictObject)({
45
76
  type: zod_1.z.literal("oauth2"),
46
77
  grantType: zod_1.z.enum(["client_credentials", "password", "refresh_token"]).default("client_credentials"),
47
78
  tokenUrl: zod_1.z.string().max(4096),
48
79
  clientId: zod_1.z.string().max(5000).default(""),
49
80
  clientSecret: zod_1.z.string().max(10_000).default(""),
50
81
  scope: zod_1.z.string().max(5000).default(""),
51
- username: zod_1.z.string().max(5000).default(""),
52
- password: zod_1.z.string().max(5000).default(""),
53
- refreshToken: zod_1.z.string().max(10_000).default(""),
54
- clientAuth: zod_1.z.enum(["basic", "body"]).default("body"),
82
+ username: zod_1.z.string().max(5000).default("").describe("password grant"),
83
+ password: zod_1.z.string().max(5000).default("").describe("password grant"),
84
+ refreshToken: zod_1.z.string().max(10_000).default("").describe("refresh_token grant"),
85
+ clientAuth: zod_1.z.enum(["basic", "body"]).default("body").describe("Send client credentials as a Basic header or in the form body"),
55
86
  audience: zod_1.z.string().max(4096).default(""),
56
- }),
87
+ }, "oauth2 auth field"),
57
88
  ]);
58
- exports.requestInputSchema = zod_1.z.object({
59
- name: zod_1.z.string().min(1).max(truncate_js_1.LIMITS.NAME).describe("Display name, e.g. \"GET /users\" or \"Login\""),
89
+ exports.requestInputSchema = (0, strict_js_1.strictObject)({
90
+ name: zod_1.z.string().trim().min(1).max(truncate_js_1.LIMITS.NAME).describe("Display name, e.g. \"GET /users\" or \"Login\""),
60
91
  method: zod_1.z.enum(exports.HTTP_METHODS),
61
92
  url: zod_1.z
62
93
  .string()
94
+ .trim()
63
95
  .min(1)
64
96
  .max(4096)
65
- .describe("Full URL; use {{variable}} for hosts/tokens (e.g. {{baseUrl}}/users/:id) and :name for path variables"),
97
+ .describe("Full URL; use {{variable}} for hosts/tokens (e.g. {{baseUrl}}/users/:id) and :name for path variables. A ?query in the URL becomes `query` rows (they are the same thing — runs send the rows)"),
66
98
  headers: zod_1.z.array(exports.kvSchema).max(200).optional(),
67
- query: zod_1.z.array(exports.kvSchema).max(200).optional(),
68
- pathVariables: zod_1.z.array(exports.kvSchema).max(200).optional().describe("Values for :name segments in the URL"),
69
- body: exports.bodyInputSchema
70
- .optional()
71
- .describe("A JSON value (stored as JSON), a string (JSON is parsed, otherwise raw text/xml/html by sniffing), or { mode: \"raw\" | \"formdata\" | \"urlencoded\" | \"none\", … }"),
99
+ query: zod_1.z.array(exports.kvSchema).max(200).optional().describe("Query parameters; when omitted they are taken from the URL's ?query"),
100
+ pathVariables: zod_1.z.array(exports.pathVariableSchema).max(200).optional().describe("Values for :name segments in the URL"),
101
+ body: exports.bodyInputSchema.optional(),
72
102
  auth: exports.authInputSchema.optional(),
73
- preRequestScript: zod_1.z.string().max(100_000).optional(),
74
- postResponseScript: zod_1.z.string().max(100_000).optional().describe("pm.* script run after the response (tests, pm.environment.set …)"),
75
- testsScript: zod_1.z.string().max(100_000).optional(),
76
- folder: zod_1.z.string().max(200).optional().describe("Folder id, name or path (\"Auth/Login\") inside the collection"),
77
- folderKey: zod_1.z.string().max(120).optional().describe("Bulk create only: key of a folder declared in `folders`"),
78
- isEnabledInRunner: zod_1.z.boolean().optional(),
79
- });
103
+ preRequestScript: zod_1.z.string().max(100_000).optional().describe("pm.* script run before the request (callman://docs/scripting/pm-api)"),
104
+ postResponseScript: zod_1.z.string().max(100_000).optional().describe("pm.* script run after the response (pm.test, pm.environment.set …)"),
105
+ testsScript: zod_1.z.string().max(100_000).optional().describe("pm.test(...) assertions run after the response"),
106
+ folder: zod_1.z.string().max(300).optional().describe("Folder id, name or path (\"Auth/Login\") inside the collection"),
107
+ folderKey: zod_1.z.string().max(120).optional().describe("create_requests_bulk with collectionName only: key of a folder declared in `folders`"),
108
+ isEnabledInRunner: zod_1.z.boolean().optional().describe("false = skipped by collection runs (run_collection, the desktop runner, the CLI); default true"),
109
+ ...requestIntegrations_js_1.requestIntegrationFields,
110
+ }, "request field");
80
111
  const toRows = (entries) => (entries ?? [])
81
112
  .filter((entry) => entry.key.trim().length > 0)
82
- .map((entry) => ({ key: entry.key.trim(), value: entry.value ?? "", enabled: entry.enabled ?? true }))
83
- .slice(0, 200);
113
+ .map((entry) => ({ key: entry.key.trim(), value: entry.value ?? "", enabled: entry.enabled ?? true }));
84
114
  exports.toRows = toRows;
115
+ const toPathVariables = (entries) => entries.map((entry) => ({ key: entry.key.trim().replace(/^:/, ""), value: entry.value ?? "", description: entry.description ?? "" }));
116
+ // ── URL ⇄ query rows ───────────────────────────────────────────────────
117
+ // The desktop keeps the URL's ?query and the query rows in sync
118
+ // (request-editor/lib/urlSync.ts), and every runner sends the ROWS — the run
119
+ // bundle strips the URL query. So a query typed into `url` becomes rows, and
120
+ // the stored URL mirrors the enabled rows exactly like the editor does.
121
+ const splitHash = (url) => {
122
+ const index = url.indexOf("#");
123
+ return index === -1 ? { withoutHash: url, hash: "" } : { withoutHash: url.slice(0, index), hash: url.slice(index) };
124
+ };
125
+ const buildUrlWithQueryRows = (url, rows) => {
126
+ const { withoutHash, hash } = splitHash(url);
127
+ const base = (0, callman_core_1.stripUrlQuery)(withoutHash);
128
+ const enabled = rows.filter((row) => row.enabled !== false && row.key.trim());
129
+ return enabled.length === 0 ? `${base}${hash}` : `${base}?${enabled.map((row) => `${row.key}=${row.value}`).join("&")}${hash}`;
130
+ };
131
+ exports.buildUrlWithQueryRows = buildUrlWithQueryRows;
132
+ const syncUrlAndQuery = (url, query) => {
133
+ const fromUrl = (0, callman_core_1.extractUrlQueryPairs)(url).map((pair) => ({ key: pair.key, value: pair.value, enabled: true }));
134
+ if (query === undefined)
135
+ return { url: (0, exports.buildUrlWithQueryRows)(url, fromUrl), query: fromUrl, warnings: [] };
136
+ const rows = (0, exports.toRows)(query);
137
+ const dropped = fromUrl.filter((pair) => !rows.some((row) => row.key === pair.key));
138
+ return {
139
+ url: (0, exports.buildUrlWithQueryRows)(url, rows),
140
+ query: rows,
141
+ warnings: dropped.length > 0 ? [`The URL's query parameter(s) ${dropped.map((pair) => pair.key).join(", ")} were replaced by the \`query\` rows you passed — add them to \`query\` to keep them.`] : [],
142
+ };
143
+ };
144
+ exports.syncUrlAndQuery = syncUrlAndQuery;
145
+ // ── body ───────────────────────────────────────────────────────────────
146
+ const isModeObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.mode === "string";
147
+ // Persisted body — the exact rules of the desktop editor's serializeBodyState
148
+ // (guarded in the desktop by persistedBody.drift.test.ts): JSON stays a bare
149
+ // JSON value; raw xml/text/html persists as { mode, raw, rawLanguage };
150
+ // form bodies keep their entries; empty → null.
151
+ const toBodyPayload = (body, headers) => {
152
+ if (body === undefined || body === null)
153
+ return null;
154
+ if (typeof body === "string") {
155
+ if (isClipped(body))
156
+ throw clippedError("body");
157
+ return (0, callman_core_1.toPersistedRequestBody)((0, callman_core_1.inferBodyStateFromRaw)(body, headers));
158
+ }
159
+ if (isModeObject(body)) {
160
+ const record = body;
161
+ if (record.mode === "none")
162
+ return null;
163
+ if (record.mode === "json")
164
+ return (record.json ?? null);
165
+ if (record.mode === "raw") {
166
+ const raw = typeof record.raw === "string" ? record.raw : "";
167
+ if (isClipped(raw))
168
+ throw clippedError("body.raw");
169
+ const language = (record.language ?? record.rawLanguage);
170
+ if (language)
171
+ return (0, callman_core_1.toPersistedRequestBody)({ mode: "raw", raw, rawLanguage: language, formEntries: [] });
172
+ return (0, callman_core_1.toPersistedRequestBody)((0, callman_core_1.inferBodyStateFromRaw)(raw, headers));
173
+ }
174
+ if (record.mode === "formdata" || record.mode === "urlencoded") {
175
+ const persisted = Array.isArray(record.formEntries) ? record.formEntries : null;
176
+ if (persisted?.some((entry) => entry.type === "file")) {
177
+ throw new errors_js_1.ToolInputError("FORM_FILE_UNSUPPORTED", "File parts of a form body cannot be set through the API — keep them in the desktop app, or send text fields only.");
178
+ }
179
+ const entries = persisted
180
+ ? persisted.map((entry) => ({ key: String(entry.key ?? ""), value: String(entry.value ?? ""), enabled: entry.enabled !== false }))
181
+ : (record.entries ?? []);
182
+ return (0, callman_core_1.toPersistedRequestBody)({ mode: record.mode, raw: "", formEntries: (0, exports.toRows)(entries) });
183
+ }
184
+ }
185
+ // Any other JSON value is stored as-is (the editor shows it on the JSON tab).
186
+ return body;
187
+ };
188
+ exports.toBodyPayload = toBodyPayload;
189
+ // Previews end in "…" (lib/truncate) — writing one back would store the cut.
190
+ const isClipped = (text) => text.endsWith("…") && text.length >= truncate_js_1.LIMITS.BODY_PREVIEW;
191
+ const clippedError = (field) => new errors_js_1.ToolInputError("CLIPPED_VALUE", `${field} looks like a clipped preview (ends with "…"). Read it with get_request full: true, or pass the complete value.`);
192
+ // The backend (express-mongo-sanitize) rewrites JSON keys that start with "$"
193
+ // or contain "." on ordinary writes — { "$schema": … } is stored as "_schema".
194
+ const sanitizedKeyWarnings = (body) => {
195
+ const found = new Set();
196
+ const walk = (value, depth) => {
197
+ if (depth > 20 || found.size >= 10 || value === null || typeof value !== "object")
198
+ return;
199
+ if (Array.isArray(value)) {
200
+ for (const item of value)
201
+ walk(item, depth + 1);
202
+ return;
203
+ }
204
+ for (const [key, child] of Object.entries(value)) {
205
+ if (key.startsWith("$") || key.includes("."))
206
+ found.add(key);
207
+ walk(child, depth + 1);
208
+ }
209
+ };
210
+ walk(body, 0);
211
+ return found.size > 0
212
+ ? [`The server rewrites JSON keys that start with "$" or contain "." (${[...found].join(", ")} → "_"). Keep such payloads as a raw string body ({ mode: "raw", raw: "…", language: "json" }).`]
213
+ : [];
214
+ };
215
+ exports.sanitizedKeyWarnings = sanitizedKeyWarnings;
216
+ // ── auth ───────────────────────────────────────────────────────────────
85
217
  const EMPTY_AUTH = {
86
218
  type: "none",
87
219
  bearerToken: "",
@@ -110,89 +242,145 @@ const toAuthPayload = (auth) => {
110
242
  }
111
243
  };
112
244
  exports.toAuthPayload = toAuthPayload;
113
- const isModeObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.mode === "string";
114
- // Persisted body — the exact rules of the desktop editor's serializeBodyState
115
- // (guarded in the desktop by persistedBody.drift.test.ts): JSON stays a bare
116
- // JSON value; raw xml/text/html persists as { mode, raw, rawLanguage };
117
- // form bodies keep their entries; empty → null.
118
- const toBodyPayload = (body, headers) => {
119
- if (body === undefined || body === null)
120
- return null;
121
- if (typeof body === "string") {
122
- return (0, callman_core_1.toPersistedRequestBody)((0, callman_core_1.inferBodyStateFromRaw)(body, headers));
123
- }
124
- if (isModeObject(body)) {
125
- const record = body;
126
- if (record.mode === "none")
127
- return null;
128
- if (record.mode === "raw") {
129
- const raw = typeof record.raw === "string" ? record.raw : "";
130
- const language = record.language;
131
- if (language) {
132
- return (0, callman_core_1.toPersistedRequestBody)({ mode: "raw", raw, rawLanguage: language, formEntries: [] });
133
- }
134
- return (0, callman_core_1.toPersistedRequestBody)((0, callman_core_1.inferBodyStateFromRaw)(raw, headers));
245
+ // Stored auth → the SAME shape authInputSchema accepts, so a read can be
246
+ // written straight back. Secrets are masked unless `reveal`.
247
+ const authInputView = (auth, reveal = false) => {
248
+ const hide = (value) => (value ? (reveal ? value : (0, mask_js_1.maskSecret)(value)) : "");
249
+ switch (auth?.type) {
250
+ case "bearer":
251
+ return { type: "bearer", token: hide(auth.bearerToken) };
252
+ case "basic":
253
+ return { type: "basic", username: auth.basicUsername ?? "", password: hide(auth.basicPassword) };
254
+ case "apikey":
255
+ return { type: "apiKey", key: auth.apiKeyKey ?? "", value: hide(auth.apiKeyValue), location: auth.apiKeyLocation ?? "header" };
256
+ case "oauth2": {
257
+ const oauth2 = (auth.oauth2 ?? {});
258
+ return {
259
+ type: "oauth2",
260
+ grantType: oauth2.grantType ?? "client_credentials",
261
+ tokenUrl: oauth2.tokenUrl ?? "",
262
+ clientId: oauth2.clientId ?? "",
263
+ clientSecret: hide(oauth2.clientSecret),
264
+ scope: oauth2.scope ?? "",
265
+ username: oauth2.username ?? "",
266
+ password: hide(oauth2.password),
267
+ refreshToken: hide(oauth2.refreshToken),
268
+ clientAuth: oauth2.clientAuth ?? "body",
269
+ audience: oauth2.audience ?? "",
270
+ };
135
271
  }
136
- if (record.mode === "formdata" || record.mode === "urlencoded") {
137
- const entries = Array.isArray(record.entries) ? record.entries : [];
138
- return (0, callman_core_1.toPersistedRequestBody)({ mode: record.mode, raw: "", formEntries: (0, exports.toRows)(entries) });
272
+ default:
273
+ return { type: "none" };
274
+ }
275
+ };
276
+ exports.authInputView = authInputView;
277
+ // ── masked values ──────────────────────────────────────────────────────
278
+ // get_request shows secrets as ••••ab. Writing that back would store the
279
+ // dots as the credential. On update a masked value that matches the stored
280
+ // secret's mask means "keep the stored value"; anything else masked is refused.
281
+ const resolveMaskedValues = (item, current) => {
282
+ const problems = [];
283
+ const restoreRows = (field, rows) => rows?.map((row) => {
284
+ if (!(0, mask_js_1.isMasked)(row.value))
285
+ return row;
286
+ const stored = current?.[field]?.find((entry) => entry.key.toLowerCase() === row.key.toLowerCase());
287
+ if (stored && field === "headers" && (0, mask_js_1.maskHeaderValue)(stored.key, stored.value) === row.value)
288
+ return { ...row, value: stored.value };
289
+ problems.push(`${field}.${row.key}`);
290
+ return row;
291
+ });
292
+ const out = { ...item, headers: restoreRows("headers", item.headers), query: restoreRows("query", item.query) };
293
+ if (item.headers === undefined)
294
+ delete out.headers;
295
+ if (item.query === undefined)
296
+ delete out.query;
297
+ for (const entry of item.pathVariables ?? [])
298
+ if ((0, mask_js_1.isMasked)(entry.value))
299
+ problems.push(`pathVariables.${entry.key}`);
300
+ if (item.auth) {
301
+ const storedView = (0, exports.authInputView)(current?.auth, true);
302
+ const auth = { ...item.auth };
303
+ for (const [key, value] of Object.entries(auth)) {
304
+ if (!(0, mask_js_1.isMasked)(value))
305
+ continue;
306
+ const stored = storedView.type === auth.type ? storedView[key] : undefined;
307
+ if (typeof stored === "string" && stored && (0, mask_js_1.maskSecret)(stored) === value)
308
+ auth[key] = stored;
309
+ else
310
+ problems.push(`auth.${key}`);
139
311
  }
312
+ out.auth = auth;
140
313
  }
141
- // Any other JSON value is stored as-is (the editor shows it on the JSON tab).
142
- return body;
314
+ if (problems.length > 0) {
315
+ throw new errors_js_1.ToolInputError("MASKED_VALUE", `Masked values (••••) cannot be saved: ${problems.join(", ")}. Pass the real value or a {{variable}}, or leave the field out to keep what is stored (get_request reveal: true shows it).`, { fields: problems });
316
+ }
317
+ return out;
143
318
  };
144
- exports.toBodyPayload = toBodyPayload;
145
- const toCreateRequestPayload = (item, target) => {
319
+ exports.resolveMaskedValues = resolveMaskedValues;
320
+ const noConnections = (_ref, _type, where) => {
321
+ throw new errors_js_1.ToolInputError("CONNECTIONS_UNAVAILABLE", `${where}: connections were not loaded for this call.`);
322
+ };
323
+ const toCreateRequestPayload = (item, target, resolve = noConnections) => {
146
324
  const headers = (0, exports.toRows)(item.headers);
325
+ const { url, query, warnings } = (0, exports.syncUrlAndQuery)(item.url.trim(), item.query);
326
+ const body = (0, exports.toBodyPayload)(item.body, headers);
147
327
  return {
148
- name: item.name.trim().slice(0, truncate_js_1.LIMITS.NAME),
149
- method: item.method,
150
- url: item.url.trim(),
151
- headers,
152
- query: (0, exports.toRows)(item.query),
153
- pathVariables: (item.pathVariables ?? [])
154
- .filter((entry) => entry.key.trim())
155
- .map((entry) => ({ key: entry.key.trim(), value: entry.value ?? "", description: entry.description ?? "" })),
156
- body: (0, exports.toBodyPayload)(item.body, headers),
157
- auth: (0, exports.toAuthPayload)(item.auth),
158
- preRequestScript: item.preRequestScript ?? "",
159
- postResponseScript: item.postResponseScript ?? "",
160
- testsScript: item.testsScript ?? "",
161
- collectionId: target.collectionId,
162
- folderId: target.folderId,
328
+ payload: {
329
+ name: item.name.trim().slice(0, truncate_js_1.LIMITS.NAME),
330
+ method: item.method,
331
+ url,
332
+ headers,
333
+ query,
334
+ pathVariables: toPathVariables(item.pathVariables ?? []),
335
+ body,
336
+ auth: (0, exports.toAuthPayload)(item.auth),
337
+ preRequestScript: item.preRequestScript ?? "",
338
+ postResponseScript: item.postResponseScript ?? "",
339
+ testsScript: item.testsScript ?? "",
340
+ ...((0, requestIntegrations_js_1.hasIntegrations)(item) ? (0, requestIntegrations_js_1.toIntegrationPayload)(item, resolve) : {}),
341
+ collectionId: target.collectionId,
342
+ folderId: target.folderId,
343
+ },
344
+ warnings: [...warnings, ...(0, exports.sanitizedKeyWarnings)(body)],
163
345
  };
164
346
  };
165
347
  exports.toCreateRequestPayload = toCreateRequestPayload;
166
348
  // `format: "requests"` bulk import row — same document minus the ids the
167
- // server assigns, plus the payload-local folderKey.
168
- const toFlatImportRequest = (item) => {
169
- const { collectionId: _c, folderId: _f, ...payload } = (0, exports.toCreateRequestPayload)(item, { collectionId: "", folderId: null });
349
+ // server assigns, plus the payload-local folderKey. The import route is not
350
+ // key-sanitized, so no "$" warning here.
351
+ const toFlatImportRequest = (item, resolve = noConnections) => {
352
+ const { collectionId: _c, folderId: _f, ...payload } = (0, exports.toCreateRequestPayload)(item, { collectionId: "", folderId: null }, resolve).payload;
170
353
  return { ...payload, ...(item.folderKey ? { folderKey: item.folderKey } : {}) };
171
354
  };
172
355
  exports.toFlatImportRequest = toFlatImportRequest;
173
- exports.updateRequestInputSchema = exports.requestInputSchema
174
- .partial()
175
- .omit({ folderKey: true })
176
- .extend({ collection: zod_1.z.string().max(200).optional() });
177
- const toUpdateRequestPayload = (item, target) => {
356
+ exports.updateRequestInputSchema = (0, strict_js_1.strictObject)({
357
+ ...Object.fromEntries(Object.entries(exports.requestInputSchema.shape).filter(([key]) => key !== "folderKey").map(([key, schema]) => [key, schema.optional()])),
358
+ collection: zod_1.z.string().max(200).optional().describe("Narrows a request NAME lookup to one collection (it does not move the request — use move_requests)"),
359
+ }, "request field");
360
+ // Only the fields passed change. isEnabledInRunner is NOT part of the PATCH
361
+ // (the backend's update schema strips it) — the caller sends it to the
362
+ // run-toggle endpoint.
363
+ const toUpdateRequestPayload = (item, current, target, resolve = noConnections) => {
178
364
  const out = {};
365
+ const warnings = [];
179
366
  if (item.name !== undefined)
180
367
  out.name = item.name.trim().slice(0, truncate_js_1.LIMITS.NAME);
181
368
  if (item.method !== undefined)
182
369
  out.method = item.method;
183
- if (item.url !== undefined)
184
- out.url = item.url.trim();
370
+ if (item.url !== undefined || item.query !== undefined) {
371
+ const synced = (0, exports.syncUrlAndQuery)((item.url ?? current.url).trim(), item.query ?? (item.url !== undefined ? undefined : current.query ?? []));
372
+ out.url = synced.url;
373
+ out.query = synced.query;
374
+ warnings.push(...synced.warnings);
375
+ }
185
376
  if (item.headers !== undefined)
186
377
  out.headers = (0, exports.toRows)(item.headers);
187
- if (item.query !== undefined)
188
- out.query = (0, exports.toRows)(item.query);
189
- if (item.pathVariables !== undefined) {
190
- out.pathVariables = item.pathVariables
191
- .filter((entry) => entry.key.trim())
192
- .map((entry) => ({ key: entry.key.trim(), value: entry.value ?? "", description: entry.description ?? "" }));
378
+ if (item.pathVariables !== undefined)
379
+ out.pathVariables = toPathVariables(item.pathVariables);
380
+ if (item.body !== undefined) {
381
+ out.body = (0, exports.toBodyPayload)(item.body, item.headers !== undefined ? (0, exports.toRows)(item.headers) : (current.headers ?? []));
382
+ warnings.push(...(0, exports.sanitizedKeyWarnings)(out.body));
193
383
  }
194
- if (item.body !== undefined)
195
- out.body = (0, exports.toBodyPayload)(item.body, (0, exports.toRows)(item.headers));
196
384
  if (item.auth !== undefined)
197
385
  out.auth = (0, exports.toAuthPayload)(item.auth);
198
386
  if (item.preRequestScript !== undefined)
@@ -201,13 +389,11 @@ const toUpdateRequestPayload = (item, target) => {
201
389
  out.postResponseScript = item.postResponseScript;
202
390
  if (item.testsScript !== undefined)
203
391
  out.testsScript = item.testsScript;
204
- if (item.isEnabledInRunner !== undefined)
205
- out.isEnabledInRunner = item.isEnabledInRunner;
206
- if (target.collectionId !== undefined)
207
- out.collectionId = target.collectionId;
392
+ if ((0, requestIntegrations_js_1.hasIntegrations)(item))
393
+ Object.assign(out, (0, requestIntegrations_js_1.toIntegrationPayload)(item, resolve, current));
208
394
  if (target.folderId !== undefined)
209
395
  out.folderId = target.folderId;
210
- return out;
396
+ return { payload: out, warnings };
211
397
  };
212
398
  exports.toUpdateRequestPayload = toUpdateRequestPayload;
213
399
  // ── outbound views ─────────────────────────────────────────────────────
@@ -216,9 +402,36 @@ const kvView = (rows, maskHeaders) => (rows ?? []).map((row) => ({
216
402
  value: maskHeaders ? (0, mask_js_1.maskHeaderValue)(row.key, row.value) : row.value,
217
403
  ...(row.enabled === false ? { enabled: false } : {}),
218
404
  }));
405
+ // Stored body → the input shape (a bare JSON value stays bare unless it has
406
+ // a top-level "mode", which would read as a mode object).
407
+ const bodyInputView = (body) => {
408
+ if (body === null || body === undefined)
409
+ return null;
410
+ if (isModeObject(body)) {
411
+ const record = body;
412
+ if (record.mode === "raw" && typeof record.raw === "string")
413
+ return { mode: "raw", raw: record.raw, language: record.rawLanguage ?? "text" };
414
+ if ((record.mode === "formdata" || record.mode === "urlencoded") && Array.isArray(record.formEntries)) {
415
+ return {
416
+ mode: record.mode,
417
+ entries: record.formEntries.map((entry) => ({
418
+ key: String(entry.key ?? ""),
419
+ value: String(entry.value ?? ""),
420
+ ...(entry.enabled === false ? { enabled: false } : {}),
421
+ ...(entry.type === "file" ? { file: true } : {}),
422
+ })),
423
+ };
424
+ }
425
+ return { mode: "json", json: body };
426
+ }
427
+ return body;
428
+ };
429
+ exports.bodyInputView = bodyInputView;
219
430
  const compactRequestView = (request, options = {}) => {
220
- const bodyText = request.body == null ? null : (0, truncate_js_1.safeStringify)(request.body);
431
+ const body = (0, exports.bodyInputView)(request.body);
432
+ const bodyText = body === null ? null : (0, truncate_js_1.safeStringify)(body);
221
433
  const bodyTruncated = bodyText !== null && !options.full && bodyText.length > truncate_js_1.LIMITS.BODY_PREVIEW;
434
+ const integrations = (0, requestIntegrations_js_1.integrationsInputView)(request);
222
435
  return {
223
436
  id: request.id,
224
437
  name: request.name,
@@ -229,17 +442,25 @@ const compactRequestView = (request, options = {}) => {
229
442
  headers: kvView(request.headers, !options.reveal),
230
443
  query: kvView(request.query, false),
231
444
  ...(request.pathVariables && request.pathVariables.length > 0
232
- ? { pathVariables: request.pathVariables.map((entry) => ({ key: entry.key, value: entry.value ?? "" })) }
445
+ ? {
446
+ pathVariables: request.pathVariables.map((entry) => ({
447
+ key: entry.key,
448
+ value: entry.value ?? "",
449
+ ...(entry.description ? { description: entry.description } : {}),
450
+ })),
451
+ }
233
452
  : {}),
234
- body: options.full ? request.body : (0, truncate_js_1.jsonPreview)(request.body),
235
- ...(bodyTruncated ? { bodyTruncated: true } : {}),
236
- auth: options.reveal ? (request.auth ?? { type: "none" }) : (0, mask_js_1.maskAuth)(request.auth),
453
+ // A clipped body is never returned as `body` — it would validate as a
454
+ // (cut) body if written back.
455
+ ...(bodyTruncated ? { body: null, bodyPreview: (0, truncate_js_1.jsonPreview)(body), bodyTruncated: true } : { body }),
456
+ auth: (0, exports.authInputView)(request.auth, options.reveal),
237
457
  scripts: {
238
458
  preRequest: scriptView(request.preRequestScript, options.full),
239
459
  postResponse: scriptView(request.postResponseScript, options.full),
240
460
  tests: scriptView(request.testsScript, options.full),
241
461
  },
242
462
  isEnabledInRunner: request.isEnabledInRunner !== false,
463
+ ...(integrations ?? {}),
243
464
  updatedAt: request.updatedAt ?? null,
244
465
  };
245
466
  };
@@ -25,6 +25,9 @@ const resolveRef = (kind, ref, items, options) => {
25
25
  throw new errors_js_1.ToolInputError("REF_AMBIGUOUS", `${exact.length} ${kind}s are named "${reference}" — use the id.`, { kind, ref: reference, candidates: candidatesOf(exact, options) });
26
26
  }
27
27
  const partial = items.filter((item) => names(item).some((name) => name.includes(needle)));
28
+ if (options?.exact) {
29
+ throw new errors_js_1.ToolInputError("REF_NOT_FOUND", `No ${kind} is named exactly "${reference}". This tool needs the exact name or the id${partial.length > 0 ? " — see candidates" : ""}.`, { kind, ref: reference, candidates: candidatesOf(partial.length > 0 ? partial : items, options) });
30
+ }
28
31
  if (partial.length === 1) {
29
32
  return { item: partial[0], matched: "partial", annotation: `matched "${reference}" → ${partial[0].name}` };
30
33
  }
@@ -56,8 +59,9 @@ const flattenFolderTree = (tree) => {
56
59
  return out;
57
60
  };
58
61
  exports.flattenFolderTree = flattenFolderTree;
59
- const resolveFolderRef = (ref, folders) => (0, exports.resolveRef)("folder", ref, folders, {
62
+ const resolveFolderRef = (ref, folders, exact = false) => (0, exports.resolveRef)("folder", ref, folders, {
60
63
  describe: (folder) => ({ path: folder.path }),
61
64
  aliases: (folder) => [folder.path],
65
+ exact,
62
66
  });
63
67
  exports.resolveFolderRef = resolveFolderRef;