@salesforce/graphiti 10.10.2

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 (282) hide show
  1. package/AGENT_GUIDE.md +424 -0
  2. package/CHANGELOG.md +448 -0
  3. package/LICENSE.txt +82 -0
  4. package/README.md +204 -0
  5. package/TASK.md +249 -0
  6. package/dist/cli.d.ts +7 -0
  7. package/dist/cli.js +683 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/commands/args.d.ts +13 -0
  10. package/dist/commands/args.js +207 -0
  11. package/dist/commands/args.js.map +1 -0
  12. package/dist/commands/build.d.ts +11 -0
  13. package/dist/commands/build.js +209 -0
  14. package/dist/commands/build.js.map +1 -0
  15. package/dist/commands/connect.d.ts +8 -0
  16. package/dist/commands/connect.js +55 -0
  17. package/dist/commands/connect.js.map +1 -0
  18. package/dist/commands/describe.d.ts +6 -0
  19. package/dist/commands/describe.js +229 -0
  20. package/dist/commands/describe.js.map +1 -0
  21. package/dist/commands/meta.d.ts +9 -0
  22. package/dist/commands/meta.js +140 -0
  23. package/dist/commands/meta.js.map +1 -0
  24. package/dist/commands/navigate.d.ts +14 -0
  25. package/dist/commands/navigate.js +105 -0
  26. package/dist/commands/navigate.js.map +1 -0
  27. package/dist/commands/query-helpers.d.ts +80 -0
  28. package/dist/commands/query-helpers.js +865 -0
  29. package/dist/commands/query-helpers.js.map +1 -0
  30. package/dist/commands/query.d.ts +26 -0
  31. package/dist/commands/query.js +901 -0
  32. package/dist/commands/query.js.map +1 -0
  33. package/dist/commands/review.d.ts +18 -0
  34. package/dist/commands/review.js +533 -0
  35. package/dist/commands/review.js.map +1 -0
  36. package/dist/commands/session-mgmt.d.ts +25 -0
  37. package/dist/commands/session-mgmt.js +206 -0
  38. package/dist/commands/session-mgmt.js.map +1 -0
  39. package/dist/commands/type.d.ts +6 -0
  40. package/dist/commands/type.js +342 -0
  41. package/dist/commands/type.js.map +1 -0
  42. package/dist/commands/validate-input.d.ts +6 -0
  43. package/dist/commands/validate-input.js +32 -0
  44. package/dist/commands/validate-input.js.map +1 -0
  45. package/dist/intent/build-aggregate.d.ts +33 -0
  46. package/dist/intent/build-aggregate.js +134 -0
  47. package/dist/intent/build-aggregate.js.map +1 -0
  48. package/dist/intent/build-create.d.ts +14 -0
  49. package/dist/intent/build-create.js +16 -0
  50. package/dist/intent/build-create.js.map +1 -0
  51. package/dist/intent/build-delete.d.ts +30 -0
  52. package/dist/intent/build-delete.js +53 -0
  53. package/dist/intent/build-delete.js.map +1 -0
  54. package/dist/intent/build-detail.d.ts +32 -0
  55. package/dist/intent/build-detail.js +80 -0
  56. package/dist/intent/build-detail.js.map +1 -0
  57. package/dist/intent/build-discover.d.ts +30 -0
  58. package/dist/intent/build-discover.js +149 -0
  59. package/dist/intent/build-discover.js.map +1 -0
  60. package/dist/intent/build-list.d.ts +28 -0
  61. package/dist/intent/build-list.js +75 -0
  62. package/dist/intent/build-list.js.map +1 -0
  63. package/dist/intent/build-mutation.d.ts +23 -0
  64. package/dist/intent/build-mutation.js +54 -0
  65. package/dist/intent/build-mutation.js.map +1 -0
  66. package/dist/intent/build-output.d.ts +27 -0
  67. package/dist/intent/build-output.js +60 -0
  68. package/dist/intent/build-output.js.map +1 -0
  69. package/dist/intent/build-raw.d.ts +23 -0
  70. package/dist/intent/build-raw.js +54 -0
  71. package/dist/intent/build-raw.js.map +1 -0
  72. package/dist/intent/build-update.d.ts +14 -0
  73. package/dist/intent/build-update.js +16 -0
  74. package/dist/intent/build-update.js.map +1 -0
  75. package/dist/intent/get-schema-with-priming.d.ts +26 -0
  76. package/dist/intent/get-schema-with-priming.js +32 -0
  77. package/dist/intent/get-schema-with-priming.js.map +1 -0
  78. package/dist/intent/select-child-relationship.d.ts +19 -0
  79. package/dist/intent/select-child-relationship.js +38 -0
  80. package/dist/intent/select-child-relationship.js.map +1 -0
  81. package/dist/intent/types.d.ts +159 -0
  82. package/dist/intent/types.js +21 -0
  83. package/dist/intent/types.js.map +1 -0
  84. package/dist/lib/apply-command.d.ts +15 -0
  85. package/dist/lib/apply-command.js +238 -0
  86. package/dist/lib/apply-command.js.map +1 -0
  87. package/dist/lib/auth.d.ts +38 -0
  88. package/dist/lib/auth.js +113 -0
  89. package/dist/lib/auth.js.map +1 -0
  90. package/dist/lib/codegen.d.ts +32 -0
  91. package/dist/lib/codegen.js +700 -0
  92. package/dist/lib/codegen.js.map +1 -0
  93. package/dist/lib/command-registry.d.ts +59 -0
  94. package/dist/lib/command-registry.js +366 -0
  95. package/dist/lib/command-registry.js.map +1 -0
  96. package/dist/lib/formatter.d.ts +76 -0
  97. package/dist/lib/formatter.js +419 -0
  98. package/dist/lib/formatter.js.map +1 -0
  99. package/dist/lib/fs-utils.d.ts +23 -0
  100. package/dist/lib/fs-utils.js +46 -0
  101. package/dist/lib/fs-utils.js.map +1 -0
  102. package/dist/lib/graphql-name.d.ts +27 -0
  103. package/dist/lib/graphql-name.js +32 -0
  104. package/dist/lib/graphql-name.js.map +1 -0
  105. package/dist/lib/interactive.d.ts +6 -0
  106. package/dist/lib/interactive.js +562 -0
  107. package/dist/lib/interactive.js.map +1 -0
  108. package/dist/lib/introspect.d.ts +40 -0
  109. package/dist/lib/introspect.js +280 -0
  110. package/dist/lib/introspect.js.map +1 -0
  111. package/dist/lib/object-info.d.ts +79 -0
  112. package/dist/lib/object-info.js +313 -0
  113. package/dist/lib/object-info.js.map +1 -0
  114. package/dist/lib/path-selection.d.ts +50 -0
  115. package/dist/lib/path-selection.js +146 -0
  116. package/dist/lib/path-selection.js.map +1 -0
  117. package/dist/lib/prime-schema.d.ts +59 -0
  118. package/dist/lib/prime-schema.js +158 -0
  119. package/dist/lib/prime-schema.js.map +1 -0
  120. package/dist/lib/query-builder.d.ts +10 -0
  121. package/dist/lib/query-builder.js +168 -0
  122. package/dist/lib/query-builder.js.map +1 -0
  123. package/dist/lib/query-commands.d.ts +19 -0
  124. package/dist/lib/query-commands.js +262 -0
  125. package/dist/lib/query-commands.js.map +1 -0
  126. package/dist/lib/session.d.ts +205 -0
  127. package/dist/lib/session.js +1075 -0
  128. package/dist/lib/session.js.map +1 -0
  129. package/dist/lib/tokenize.d.ts +12 -0
  130. package/dist/lib/tokenize.js +48 -0
  131. package/dist/lib/tokenize.js.map +1 -0
  132. package/dist/lib/uiapi.d.ts +109 -0
  133. package/dist/lib/uiapi.js +159 -0
  134. package/dist/lib/uiapi.js.map +1 -0
  135. package/dist/lib/validator.d.ts +27 -0
  136. package/dist/lib/validator.js +100 -0
  137. package/dist/lib/validator.js.map +1 -0
  138. package/dist/lib/variable-promotion.d.ts +69 -0
  139. package/dist/lib/variable-promotion.js +95 -0
  140. package/dist/lib/variable-promotion.js.map +1 -0
  141. package/dist/lib/walker.d.ts +147 -0
  142. package/dist/lib/walker.js +723 -0
  143. package/dist/lib/walker.js.map +1 -0
  144. package/dist/mcp/server.d.ts +7 -0
  145. package/dist/mcp/server.js +34 -0
  146. package/dist/mcp/server.js.map +1 -0
  147. package/dist/mcp/stdio.d.ts +7 -0
  148. package/dist/mcp/stdio.js +25 -0
  149. package/dist/mcp/stdio.js.map +1 -0
  150. package/dist/mcp/tools/echo.d.ts +7 -0
  151. package/dist/mcp/tools/echo.js +17 -0
  152. package/dist/mcp/tools/echo.js.map +1 -0
  153. package/dist/mcp/tools/sf-gql-aggregate.d.ts +11 -0
  154. package/dist/mcp/tools/sf-gql-aggregate.js +75 -0
  155. package/dist/mcp/tools/sf-gql-aggregate.js.map +1 -0
  156. package/dist/mcp/tools/sf-gql-create.d.ts +11 -0
  157. package/dist/mcp/tools/sf-gql-create.js +35 -0
  158. package/dist/mcp/tools/sf-gql-create.js.map +1 -0
  159. package/dist/mcp/tools/sf-gql-delete.d.ts +11 -0
  160. package/dist/mcp/tools/sf-gql-delete.js +31 -0
  161. package/dist/mcp/tools/sf-gql-delete.js.map +1 -0
  162. package/dist/mcp/tools/sf-gql-detail.d.ts +11 -0
  163. package/dist/mcp/tools/sf-gql-detail.js +58 -0
  164. package/dist/mcp/tools/sf-gql-detail.js.map +1 -0
  165. package/dist/mcp/tools/sf-gql-discover.d.ts +9 -0
  166. package/dist/mcp/tools/sf-gql-discover.js +51 -0
  167. package/dist/mcp/tools/sf-gql-discover.js.map +1 -0
  168. package/dist/mcp/tools/sf-gql-list.d.ts +11 -0
  169. package/dist/mcp/tools/sf-gql-list.js +53 -0
  170. package/dist/mcp/tools/sf-gql-list.js.map +1 -0
  171. package/dist/mcp/tools/sf-gql-raw.d.ts +11 -0
  172. package/dist/mcp/tools/sf-gql-raw.js +39 -0
  173. package/dist/mcp/tools/sf-gql-raw.js.map +1 -0
  174. package/dist/mcp/tools/sf-gql-update.d.ts +11 -0
  175. package/dist/mcp/tools/sf-gql-update.js +35 -0
  176. package/dist/mcp/tools/sf-gql-update.js.map +1 -0
  177. package/dist/mcp/tools/shared/zod-schemas.d.ts +49 -0
  178. package/dist/mcp/tools/shared/zod-schemas.js +46 -0
  179. package/dist/mcp/tools/shared/zod-schemas.js.map +1 -0
  180. package/package.json +36 -0
  181. package/ralph-loop.sh +120 -0
  182. package/scripts/smoke-orderby.sh +190 -0
  183. package/src/__tests__/helpers/prime-deps.ts +46 -0
  184. package/src/__tests__/helpers/schema.ts +73 -0
  185. package/src/__tests__/helpers/stdout.ts +33 -0
  186. package/src/__tests__/setup.ts +19 -0
  187. package/src/cli.ts +764 -0
  188. package/src/commands/__tests__/query.spec.ts +137 -0
  189. package/src/commands/args.ts +306 -0
  190. package/src/commands/build.ts +288 -0
  191. package/src/commands/connect.ts +60 -0
  192. package/src/commands/describe.ts +246 -0
  193. package/src/commands/meta.ts +171 -0
  194. package/src/commands/navigate.ts +134 -0
  195. package/src/commands/query-helpers.ts +1202 -0
  196. package/src/commands/query.ts +1085 -0
  197. package/src/commands/review.ts +670 -0
  198. package/src/commands/session-mgmt.ts +256 -0
  199. package/src/commands/type.ts +437 -0
  200. package/src/commands/validate-input.ts +38 -0
  201. package/src/intent/__tests__/build-aggregate.spec.ts +931 -0
  202. package/src/intent/__tests__/build-create-validation.spec.ts +135 -0
  203. package/src/intent/__tests__/build-delete.spec.ts +121 -0
  204. package/src/intent/__tests__/build-detail.spec.ts +333 -0
  205. package/src/intent/__tests__/build-discover.spec.ts +432 -0
  206. package/src/intent/__tests__/build-list.spec.ts +284 -0
  207. package/src/intent/__tests__/build-mutation.spec.ts +108 -0
  208. package/src/intent/__tests__/build-output.spec.ts +55 -0
  209. package/src/intent/__tests__/build-raw.spec.ts +153 -0
  210. package/src/intent/__tests__/build-update-validation.spec.ts +134 -0
  211. package/src/intent/build-aggregate.ts +182 -0
  212. package/src/intent/build-create.ts +19 -0
  213. package/src/intent/build-delete.ts +62 -0
  214. package/src/intent/build-detail.ts +95 -0
  215. package/src/intent/build-discover.ts +199 -0
  216. package/src/intent/build-list.ts +91 -0
  217. package/src/intent/build-mutation.ts +75 -0
  218. package/src/intent/build-output.ts +72 -0
  219. package/src/intent/build-raw.ts +61 -0
  220. package/src/intent/build-update.ts +19 -0
  221. package/src/intent/get-schema-with-priming.ts +43 -0
  222. package/src/intent/select-child-relationship.ts +48 -0
  223. package/src/intent/types.ts +181 -0
  224. package/src/lib/__tests__/apply-command.parity.spec.ts +97 -0
  225. package/src/lib/__tests__/apply-command.spec.ts +171 -0
  226. package/src/lib/__tests__/auth.spec.ts +292 -0
  227. package/src/lib/__tests__/formatter.spec.ts +86 -0
  228. package/src/lib/__tests__/graphql-name.spec.ts +64 -0
  229. package/src/lib/__tests__/introspect.spec.ts +399 -0
  230. package/src/lib/__tests__/object-info.spec.ts +124 -0
  231. package/src/lib/__tests__/path-selection.spec.ts +219 -0
  232. package/src/lib/__tests__/prime-schema.spec.ts +269 -0
  233. package/src/lib/__tests__/query-builder.spec.ts +95 -0
  234. package/src/lib/__tests__/query-commands.spec.ts +74 -0
  235. package/src/lib/__tests__/session.spec.ts +292 -0
  236. package/src/lib/__tests__/tokenize.spec.ts +33 -0
  237. package/src/lib/__tests__/uiapi.spec.ts +192 -0
  238. package/src/lib/__tests__/variable-promotion.spec.ts +211 -0
  239. package/src/lib/__tests__/walker.spec.ts +250 -0
  240. package/src/lib/apply-command.ts +286 -0
  241. package/src/lib/auth.ts +157 -0
  242. package/src/lib/codegen.ts +899 -0
  243. package/src/lib/command-registry.ts +434 -0
  244. package/src/lib/formatter.ts +587 -0
  245. package/src/lib/fs-utils.ts +46 -0
  246. package/src/lib/graphql-name.ts +35 -0
  247. package/src/lib/interactive.ts +634 -0
  248. package/src/lib/introspect.ts +320 -0
  249. package/src/lib/object-info.ts +409 -0
  250. package/src/lib/path-selection.ts +162 -0
  251. package/src/lib/prime-schema.ts +195 -0
  252. package/src/lib/query-builder.ts +193 -0
  253. package/src/lib/query-commands.ts +290 -0
  254. package/src/lib/session.ts +1304 -0
  255. package/src/lib/tokenize.ts +43 -0
  256. package/src/lib/uiapi.ts +176 -0
  257. package/src/lib/validator.ts +143 -0
  258. package/src/lib/variable-promotion.ts +145 -0
  259. package/src/lib/walker.ts +975 -0
  260. package/src/mcp/__tests__/server.spec.ts +155 -0
  261. package/src/mcp/server.ts +38 -0
  262. package/src/mcp/stdio.ts +29 -0
  263. package/src/mcp/tools/__tests__/sf-gql-aggregate.spec.ts +173 -0
  264. package/src/mcp/tools/__tests__/sf-gql-create.spec.ts +235 -0
  265. package/src/mcp/tools/__tests__/sf-gql-delete.spec.ts +194 -0
  266. package/src/mcp/tools/__tests__/sf-gql-detail.spec.ts +246 -0
  267. package/src/mcp/tools/__tests__/sf-gql-discover.spec.ts +320 -0
  268. package/src/mcp/tools/__tests__/sf-gql-list.spec.ts +128 -0
  269. package/src/mcp/tools/__tests__/sf-gql-raw.spec.ts +141 -0
  270. package/src/mcp/tools/__tests__/sf-gql-update.spec.ts +207 -0
  271. package/src/mcp/tools/echo.ts +24 -0
  272. package/src/mcp/tools/sf-gql-aggregate.ts +102 -0
  273. package/src/mcp/tools/sf-gql-create.ts +55 -0
  274. package/src/mcp/tools/sf-gql-delete.ts +49 -0
  275. package/src/mcp/tools/sf-gql-detail.ts +85 -0
  276. package/src/mcp/tools/sf-gql-discover.ts +67 -0
  277. package/src/mcp/tools/sf-gql-list.ts +73 -0
  278. package/src/mcp/tools/sf-gql-raw.ts +56 -0
  279. package/src/mcp/tools/sf-gql-update.ts +55 -0
  280. package/src/mcp/tools/shared/zod-schemas.ts +55 -0
  281. package/tsconfig.json +18 -0
  282. package/vitest.config.ts +14 -0
@@ -0,0 +1,587 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ import { formatPath, type ProjectionNode, type VariableDefinition } from "./session.js";
8
+ import type {
9
+ FieldInfo,
10
+ ArgInfo,
11
+ TypeInfo,
12
+ WalkerResult,
13
+ InputWalkerResult,
14
+ SearchMatcher,
15
+ } from "./walker.js";
16
+ import { filterDataCloudFields, parseSearchTerms, parseSearchRegex } from "./walker.js";
17
+
18
+ export { parseSearchTerms, parseSearchRegex, type SearchMatcher };
19
+
20
+ export interface TypeAnnotations {
21
+ /** Per-field comment lines keyed by field name. */
22
+ fieldComments?: Map<string, string[]>;
23
+ /** For filter types: example JSON expressions. */
24
+ filterExamples?: string[];
25
+ /** For mutation inputs: nested input types to inline after the parent. */
26
+ inlinedInputs?: { info: TypeInfo; annotations: TypeAnnotations }[];
27
+ /** For mutation inputs: sample JSON variable. */
28
+ sampleInput?: string;
29
+ }
30
+
31
+ export interface AliasEntry {
32
+ alias: string;
33
+ fieldName: string;
34
+ argCount: number;
35
+ isActive: boolean;
36
+ }
37
+
38
+ function resolveSearchMatcher(
39
+ searchPattern?: string,
40
+ regexPattern?: string,
41
+ ): SearchMatcher | undefined {
42
+ if (regexPattern) return parseSearchRegex(regexPattern);
43
+ if (searchPattern) return parseSearchTerms(searchPattern);
44
+ return undefined;
45
+ }
46
+
47
+ export function formatDirectoryListing(
48
+ result: WalkerResult,
49
+ opts: {
50
+ searchPattern?: string;
51
+ regexPattern?: string;
52
+ maxFields?: number;
53
+ long?: boolean;
54
+ all?: boolean;
55
+ hasArgs?: boolean;
56
+ aliases?: AliasEntry[];
57
+ selectedFields?: Set<string>;
58
+ optionalFields?: Set<string>;
59
+ fieldLongInfo?: Map<string, FieldLongInfo>;
60
+ dataCloud?: boolean;
61
+ } = {},
62
+ ): string {
63
+ const {
64
+ searchPattern,
65
+ regexPattern,
66
+ maxFields = 20,
67
+ long = false,
68
+ all = false,
69
+ hasArgs = false,
70
+ aliases = [],
71
+ selectedFields,
72
+ optionalFields,
73
+ fieldLongInfo,
74
+ dataCloud = false,
75
+ } = opts;
76
+ const matcher = resolveSearchMatcher(searchPattern, regexPattern);
77
+ const isFiltering = !!matcher;
78
+ const lines: string[] = [];
79
+
80
+ if (result.isLeaf) {
81
+ lines.push("Leaf node. Use `select <field>` from the parent directory to project it.");
82
+ return lines.join("\n");
83
+ }
84
+
85
+ if (hasArgs) {
86
+ if (long) {
87
+ lines.push(
88
+ ` ${"@args/".padEnd(28)} args (${result.args.length} argument${result.args.length !== 1 ? "s" : ""})`,
89
+ );
90
+ } else {
91
+ lines.push(" @args/");
92
+ }
93
+ }
94
+
95
+ for (const a of aliases) {
96
+ const entry = `${a.alias}/`;
97
+ if (long) {
98
+ const active = a.isActive ? "*" : " ";
99
+ const argPart = a.argCount > 0 ? ` [${a.argCount} arg${a.argCount !== 1 ? "s" : ""}]` : "";
100
+ lines.push(`${active} ${entry.padEnd(28)} alias ${a.fieldName}${argPart}`);
101
+ } else {
102
+ lines.push(` ${entry}`);
103
+ }
104
+ }
105
+
106
+ const dataCloudHidden = dataCloud
107
+ ? 0
108
+ : result.fields.length - filterDataCloudFields(result.fields, false).length;
109
+ let fields = filterDataCloudFields(result.fields, dataCloud);
110
+ if (matcher) {
111
+ fields = fields.filter((f) => matcher.test(f.name));
112
+ }
113
+
114
+ const displayFields = isFiltering || all ? fields : fields.slice(0, maxFields);
115
+ const fragments = matcher
116
+ ? result.possibleTypes.filter((typeName) => matcher.test(typeName))
117
+ : result.possibleTypes;
118
+
119
+ for (const field of displayFields) {
120
+ lines.push(
121
+ formatFieldLine(
122
+ field,
123
+ long,
124
+ selectedFields?.has(field.name),
125
+ fieldLongInfo?.get(field.name),
126
+ optionalFields?.has(field.name),
127
+ ),
128
+ );
129
+ }
130
+
131
+ if (!isFiltering && !all && fields.length > maxFields) {
132
+ lines.push(
133
+ ` ... ${fields.length - maxFields} more (use -a to show all, or --search to filter)`,
134
+ );
135
+ }
136
+
137
+ if (dataCloudHidden > 0) {
138
+ lines.push(` (${dataCloudHidden} Data Cloud fields hidden — use --data-cloud to show)`);
139
+ }
140
+
141
+ if (result.mutationHiddenFields.length > 0 && long) {
142
+ let hiddenFields = result.mutationHiddenFields;
143
+ if (matcher) {
144
+ hiddenFields = hiddenFields.filter((f) => matcher.test(f.name));
145
+ }
146
+ for (const field of hiddenFields) {
147
+ const name = `${field.name}/`;
148
+ lines.push(` ${name.padEnd(28)} dir ${field.typeName} [query-only]`);
149
+ }
150
+ }
151
+
152
+ for (const typeName of fragments) {
153
+ if (long) {
154
+ lines.push(` ${`on:${typeName}/`.padEnd(28)} frag ${typeName}`);
155
+ } else {
156
+ lines.push(` on:${typeName}/`);
157
+ }
158
+ }
159
+
160
+ return lines.join("\n");
161
+ }
162
+
163
+ export function formatArgs(args: ArgInfo[]): string {
164
+ if (args.length === 0) return "";
165
+
166
+ const lines = ["Args:"];
167
+ for (const arg of args) {
168
+ let line = ` ${arg.name}: ${arg.typeName}`;
169
+ if (arg.enumValues) {
170
+ line += ` -- ${arg.enumValues.join(" | ")}`;
171
+ }
172
+ if (arg.description && !arg.enumValues) {
173
+ const shortDesc =
174
+ arg.description.length > 50 ? arg.description.slice(0, 47) + "..." : arg.description;
175
+ line += ` -- ${shortDesc}`;
176
+ }
177
+ lines.push(line);
178
+ }
179
+ return lines.join("\n");
180
+ }
181
+
182
+ export function formatArgsDirectoryListing(
183
+ args: ArgInfo[],
184
+ long: boolean,
185
+ currentValues?: Record<string, string>,
186
+ searchPattern?: string,
187
+ regexPattern?: string,
188
+ ): string {
189
+ if (args.length === 0) return "No arguments available.";
190
+
191
+ let filtered = args;
192
+ const matcher = resolveSearchMatcher(searchPattern, regexPattern);
193
+ if (matcher) {
194
+ filtered = filtered.filter((a) => matcher.test(a.name));
195
+ }
196
+
197
+ const lines: string[] = [];
198
+ for (const arg of filtered) {
199
+ const isLeaf = arg.typeKind === "SCALAR" || arg.typeKind === "ENUM";
200
+ const suffix = isLeaf ? "" : "/";
201
+ const hasValue = currentValues?.[arg.name] !== undefined;
202
+ const mark = hasValue ? "*" : " ";
203
+ const name = arg.name + suffix;
204
+
205
+ if (!long) {
206
+ lines.push(`${mark} ${name}`);
207
+ continue;
208
+ }
209
+
210
+ const valStr = hasValue ? ` = ${truncate(currentValues[arg.name], 40)}` : "";
211
+ let enumHint = "";
212
+ if (arg.enumValues && arg.enumValues.length > 0) {
213
+ enumHint = ` [${arg.enumValues.join(", ")}]`;
214
+ }
215
+ const reqTag = arg.isNonNull ? " [required]" : "";
216
+ lines.push(`${mark} ${name.padEnd(24)} ${arg.typeName}${reqTag}${valStr}${enumHint}`);
217
+ }
218
+ return lines.join("\n");
219
+ }
220
+
221
+ export function formatInputDirectoryListing(
222
+ inputResult: InputWalkerResult,
223
+ long: boolean,
224
+ currentValues?: Record<string, unknown>,
225
+ listElementCount?: number,
226
+ searchPattern?: string,
227
+ regexPattern?: string,
228
+ ): string {
229
+ const lines: string[] = [];
230
+
231
+ if (inputResult.isList) {
232
+ if (listElementCount === undefined || listElementCount === 0) {
233
+ lines.push(" (empty list — use `mkdir` to add an element)");
234
+ } else {
235
+ for (let i = 0; i < listElementCount; i++) {
236
+ const val = currentValues?.[String(i)];
237
+ const hasVal = val !== undefined;
238
+ const mark = hasVal ? "*" : " ";
239
+ const name = String(i) + "/";
240
+ if (long) {
241
+ const valStr = hasVal ? ` = ${truncate(JSON.stringify(val), 40)}` : "";
242
+ lines.push(`${mark} ${name.padEnd(24)}${valStr}`);
243
+ } else {
244
+ lines.push(`${mark} ${name}`);
245
+ }
246
+ }
247
+ }
248
+ return lines.join("\n");
249
+ }
250
+
251
+ if (inputResult.inputFields.length === 0 && inputResult.enumValues.length > 0) {
252
+ lines.push(`Enum values: ${inputResult.enumValues.map((e) => e.name).join(", ")}`);
253
+ return lines.join("\n");
254
+ }
255
+
256
+ let fields = inputResult.inputFields;
257
+ const matcher = resolveSearchMatcher(searchPattern, regexPattern);
258
+ if (matcher) {
259
+ fields = fields.filter((f) => matcher.test(f.name));
260
+ }
261
+
262
+ for (const field of fields) {
263
+ const isLeaf = field.typeKind === "SCALAR" || field.typeKind === "ENUM";
264
+ const suffix = isLeaf ? "" : "/";
265
+ const val = currentValues?.[field.name];
266
+ const hasVal = val !== undefined;
267
+ const mark = hasVal ? "*" : " ";
268
+ const name = field.name + suffix;
269
+
270
+ if (!long) {
271
+ lines.push(`${mark} ${name}`);
272
+ continue;
273
+ }
274
+
275
+ const valStr = hasVal ? ` = ${truncate(JSON.stringify(val), 40)}` : "";
276
+ let enumHint = "";
277
+ if (field.enumValues && field.enumValues.length > 0) {
278
+ enumHint = ` [${field.enumValues.join(", ")}]`;
279
+ }
280
+ const reqTag = field.isNonNull ? " [required]" : "";
281
+ lines.push(`${mark} ${name.padEnd(24)} ${field.typeName}${reqTag}${valStr}${enumHint}`);
282
+ }
283
+ return lines.join("\n");
284
+ }
285
+
286
+ export function formatVariablesListing(variables: VariableDefinition[], long: boolean): string {
287
+ if (variables.length === 0)
288
+ return " (no variables defined — use `define $name Type` to create one)";
289
+
290
+ const lines: string[] = [];
291
+ for (const v of variables) {
292
+ const isScalar = !v.type.replace(/[![\]]/g, "").match(/Input|Where|OrderBy|Filter/i);
293
+ const suffix = isScalar ? "" : "/";
294
+ const hasValue = v.runtimeValue !== undefined || v.defaultValue !== undefined;
295
+ const mark = hasValue ? "*" : " ";
296
+ const name = `$${v.name}${suffix}`;
297
+
298
+ if (!long) {
299
+ lines.push(`${mark} ${name}`);
300
+ continue;
301
+ }
302
+
303
+ let detail = name.padEnd(24);
304
+ detail += ` ${v.type}`;
305
+ if (v.defaultValue !== undefined) detail += ` default=${truncate(v.defaultValue, 30)}`;
306
+ if (v.runtimeValue !== undefined) detail += ` value=${truncate(v.runtimeValue, 30)}`;
307
+ lines.push(`${mark} ${detail}`);
308
+ }
309
+ return lines.join("\n");
310
+ }
311
+
312
+ function truncate(s: string, max: number): string {
313
+ return s.length <= max ? s : s.slice(0, max - 3) + "...";
314
+ }
315
+
316
+ export function formatNavigationPath(pathSegments: string[]): string {
317
+ return `Path: ${formatPath(pathSegments)}`;
318
+ }
319
+
320
+ export function formatInstanceSummary(
321
+ currentPath: string[],
322
+ instances: ProjectionNode[],
323
+ activeInstanceId?: string,
324
+ ): string {
325
+ if (currentPath.length === 0 || instances.length === 0) {
326
+ return "Projection instances: none";
327
+ }
328
+
329
+ const items = instances.map((instance) => {
330
+ const marker = instance.id === activeInstanceId ? "*" : " ";
331
+ if (instance.kind === "fragment") {
332
+ return `${marker} [${instance.onType}] (${instance.id})`;
333
+ }
334
+ const aliasPart = instance.alias ? `${instance.alias}: ` : "";
335
+ const argCount = Object.keys(instance.args).length;
336
+ return `${marker} ${aliasPart}${instance.fieldName} (${instance.id}${argCount > 0 ? `, ${argCount} arg${argCount === 1 ? "" : "s"}` : ""})`;
337
+ });
338
+
339
+ return ["Projection instances:", ...items.map((item) => ` ${item}`)].join("\n");
340
+ }
341
+
342
+ export function formatTypeInfo(
343
+ info: TypeInfo,
344
+ annotations?: TypeAnnotations,
345
+ opts: { dataCloud?: boolean } = {},
346
+ ): string {
347
+ const lines: string[] = [];
348
+
349
+ switch (info.kind) {
350
+ case "OBJECT":
351
+ case "INTERFACE": {
352
+ const keyword = info.kind === "INTERFACE" ? "interface" : "type";
353
+ const impl = info.interfaces.length > 0 ? ` implements ${info.interfaces.join(" & ")}` : "";
354
+
355
+ if (info.description) {
356
+ lines.push(`"""${info.description}"""`);
357
+ }
358
+ lines.push(`${keyword} ${info.name}${impl} {`);
359
+
360
+ const displayFields = filterDataCloudFields(info.fields, !!opts.dataCloud);
361
+ const hiddenCount = info.fields.length - displayFields.length;
362
+ for (const field of displayFields) {
363
+ if (field.description) {
364
+ lines.push(` """${field.description}"""`);
365
+ }
366
+ let fieldLine = ` ${field.name}`;
367
+ if (field.args.length > 0) {
368
+ const argParts = field.args.map((a) => `${a.name}: ${a.typeName}`);
369
+ fieldLine += `(${argParts.join(", ")})`;
370
+ }
371
+ fieldLine += `: ${field.typeName}`;
372
+ const comments = annotations?.fieldComments?.get(field.name);
373
+ if (comments && comments.length > 0) {
374
+ fieldLine += ` # ${comments.join(" | ")}`;
375
+ }
376
+ lines.push(fieldLine);
377
+ }
378
+
379
+ lines.push("}");
380
+
381
+ if (hiddenCount > 0) {
382
+ lines.push(` (${hiddenCount} Data Cloud fields hidden — use --data-cloud to show)`);
383
+ }
384
+
385
+ if (info.possibleTypes.length > 0) {
386
+ lines.push("");
387
+ lines.push(`Possible types: ${info.possibleTypes.join(", ")}`);
388
+ }
389
+ break;
390
+ }
391
+
392
+ case "INPUT_OBJECT": {
393
+ formatInputObject(info, annotations, lines);
394
+ break;
395
+ }
396
+
397
+ case "ENUM": {
398
+ if (info.description) {
399
+ lines.push(`"""${info.description}"""`);
400
+ }
401
+ lines.push(`enum ${info.name} {`);
402
+ for (const val of info.enumValues) {
403
+ if (val.description) {
404
+ lines.push(` """${val.description}"""`);
405
+ }
406
+ lines.push(` ${val.name}`);
407
+ }
408
+ lines.push("}");
409
+ break;
410
+ }
411
+
412
+ case "UNION": {
413
+ if (info.description) {
414
+ lines.push(`"""${info.description}"""`);
415
+ }
416
+ lines.push(`union ${info.name} = ${info.possibleTypes.join(" | ")}`);
417
+ break;
418
+ }
419
+
420
+ case "SCALAR": {
421
+ if (info.description) {
422
+ lines.push(`"""${info.description}"""`);
423
+ }
424
+ lines.push(`scalar ${info.name}`);
425
+ break;
426
+ }
427
+ }
428
+
429
+ return lines.join("\n");
430
+ }
431
+
432
+ function formatInputObject(
433
+ info: TypeInfo,
434
+ annotations: TypeAnnotations | undefined,
435
+ lines: string[],
436
+ ): void {
437
+ if (info.description) {
438
+ lines.push(`"""${info.description}"""`);
439
+ }
440
+ lines.push(`input ${info.name} {`);
441
+
442
+ for (const field of info.inputFields) {
443
+ if (field.description) {
444
+ lines.push(` """${field.description}"""`);
445
+ }
446
+ let fieldLine = ` ${field.name}: ${field.typeName}`;
447
+ const comments = annotations?.fieldComments?.get(field.name);
448
+ if (comments && comments.length > 0) {
449
+ fieldLine += ` # ${comments.join(" | ")}`;
450
+ }
451
+ lines.push(fieldLine);
452
+ }
453
+ lines.push("}");
454
+
455
+ if (annotations?.filterExamples && annotations.filterExamples.length > 0) {
456
+ lines.push("");
457
+ for (const ex of annotations.filterExamples) {
458
+ lines.push(`# example: ${ex}`);
459
+ }
460
+ }
461
+
462
+ if (annotations?.inlinedInputs) {
463
+ for (const { info: nested, annotations: nestedAnn } of annotations.inlinedInputs) {
464
+ lines.push("");
465
+ formatInputObject(nested, nestedAnn, lines);
466
+
467
+ if (nestedAnn.sampleInput) {
468
+ lines.push("");
469
+ lines.push("# sample:");
470
+ for (const sampleLine of nestedAnn.sampleInput.split("\n")) {
471
+ lines.push(`# ${sampleLine}`);
472
+ }
473
+ }
474
+ }
475
+ }
476
+ }
477
+
478
+ export function formatValidationErrors(errors: { message: string }[]): string {
479
+ if (errors.length === 0) return "Query is valid.";
480
+
481
+ const lines = [`${errors.length} validation error${errors.length === 1 ? "" : "s"}:`];
482
+ errors.forEach((e, i) => {
483
+ lines.push(` ${i + 1}. ${e.message}`);
484
+ });
485
+ return lines.join("\n");
486
+ }
487
+
488
+ export interface FieldLongInfo {
489
+ required: boolean;
490
+ createable: boolean;
491
+ updateable: boolean;
492
+ defaultedOnCreate: boolean;
493
+ filterable: boolean;
494
+ sortable: boolean;
495
+ referenceTargets?: string[];
496
+ referenceNameFields?: string[];
497
+ childRelTarget?: string;
498
+ picklistValues?: string[];
499
+ label?: string;
500
+ dataType?: string;
501
+ nameField?: boolean;
502
+ compound?: boolean;
503
+ compoundFieldName?: string;
504
+ extraTypeInfo?: string;
505
+ calculated?: boolean;
506
+ custom?: boolean;
507
+ inlineHelpText?: string;
508
+ precision?: number;
509
+ scale?: number;
510
+ }
511
+
512
+ function formatFieldLine(
513
+ field: FieldInfo,
514
+ long: boolean,
515
+ selected?: boolean,
516
+ fieldLong?: FieldLongInfo | null,
517
+ hasOptional?: boolean,
518
+ ): string {
519
+ const isDirectory = field.typeKind !== "SCALAR" && field.typeKind !== "ENUM";
520
+ const suffix = isDirectory ? "/" : "";
521
+ const optionalMark = hasOptional ? "?" : "";
522
+ const selectedMark = selected ? "*" : " ";
523
+ const base = `${selectedMark}${optionalMark}${optionalMark ? "" : " "}${field.name}${suffix}`;
524
+
525
+ if (!long) {
526
+ return base;
527
+ }
528
+
529
+ const kind = isDirectory ? "dir " : "leaf";
530
+ const argHint =
531
+ field.args.length > 0 ? ` args=${field.args.map((arg) => arg.name).join(",")}` : "";
532
+
533
+ let annotations = "";
534
+ if (fieldLong) {
535
+ // Label (when different from API name)
536
+ const labelPart =
537
+ fieldLong.label && fieldLong.label !== field.name ? ` "${fieldLong.label}"` : "";
538
+
539
+ const tags: string[] = [];
540
+ if (fieldLong.required && !fieldLong.defaultedOnCreate) tags.push("required");
541
+ else if (fieldLong.required && fieldLong.defaultedOnCreate) tags.push("defaulted");
542
+ if (fieldLong.nameField) tags.push("name-field");
543
+ if (!fieldLong.createable && !fieldLong.updateable) tags.push("read-only");
544
+ else if (fieldLong.createable && !fieldLong.updateable) tags.push("create-only");
545
+ else if (!fieldLong.createable) tags.push("no-create");
546
+ if (fieldLong.defaultedOnCreate && fieldLong.createable) tags.push("default-on-create");
547
+ if (!fieldLong.filterable) tags.push("no-filter");
548
+ if (!fieldLong.sortable) tags.push("no-sort");
549
+ if (fieldLong.calculated) tags.push("formula");
550
+ if (fieldLong.custom) tags.push("custom");
551
+ if (fieldLong.compound) tags.push("compound");
552
+ if (fieldLong.compoundFieldName) tags.push(`child of ${fieldLong.compoundFieldName}`);
553
+ const tagStr = tags.length > 0 ? ` [${tags.join(", ")}]` : "";
554
+
555
+ let extra = "";
556
+ if (fieldLong.referenceTargets && fieldLong.referenceTargets.length > 0) {
557
+ const nameHint =
558
+ fieldLong.referenceNameFields && fieldLong.referenceNameFields.length > 0
559
+ ? ` (${fieldLong.referenceNameFields.join(", ")})`
560
+ : "";
561
+ extra += ` -> ${fieldLong.referenceTargets.join(", ")}${nameHint}`;
562
+ }
563
+ if (fieldLong.childRelTarget) {
564
+ extra += ` child -> ${fieldLong.childRelTarget}`;
565
+ }
566
+
567
+ if (fieldLong.picklistValues && fieldLong.picklistValues.length > 0) {
568
+ if (fieldLong.picklistValues.length <= 8) {
569
+ extra += ` [${fieldLong.picklistValues.join(", ")}]`;
570
+ } else {
571
+ extra += ` [${fieldLong.picklistValues.slice(0, 6).join(", ")}, ... +${fieldLong.picklistValues.length - 6}]`;
572
+ }
573
+ }
574
+
575
+ if (fieldLong.extraTypeInfo) {
576
+ extra += ` (${fieldLong.extraTypeInfo})`;
577
+ }
578
+
579
+ if (fieldLong.inlineHelpText) {
580
+ extra += ` -- ${fieldLong.inlineHelpText}`;
581
+ }
582
+
583
+ annotations = `${labelPart}${tagStr}${extra}`;
584
+ }
585
+
586
+ return `${base.padEnd(28)} ${kind} ${field.typeName}${argHint}${annotations}`;
587
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ import crypto from "node:crypto";
8
+ import fs from "node:fs";
9
+ import os from "node:os";
10
+ import path from "node:path";
11
+
12
+ /**
13
+ * Resolves the graphiti home directory at call time. Re-reads
14
+ * `GRAPHITI_HOME` on every call so tests that mutate the env var after
15
+ * module load (or fixtures that set it before each call) see the current
16
+ * value rather than a frozen snapshot from import time.
17
+ *
18
+ * Single source of truth for every graphiti file/directory path: schema
19
+ * cache, sessions, ObjectInfo cache, the active-session pointer.
20
+ */
21
+ export function graphitiHome(): string {
22
+ return process.env.GRAPHITI_HOME ?? path.join(os.homedir(), ".graphiti");
23
+ }
24
+
25
+ /**
26
+ * Write JSON to `finalPath` atomically. Writes to a sibling
27
+ * `<base>.tmp.<pid>.<timestamp>.<rand>` file, then renames over the final
28
+ * path. Concurrent readers see either the old contents or the fully-written
29
+ * new contents — never a partial. The temp file is unlinked if the rename
30
+ * fails, so failure paths do not leak partial files.
31
+ */
32
+ export function atomicWriteJson(finalPath: string, payload: unknown): void {
33
+ fs.mkdirSync(path.dirname(finalPath), { recursive: true });
34
+ const tmp = `${finalPath}.tmp.${process.pid}.${Date.now()}.${crypto.randomBytes(4).toString("hex")}`;
35
+ fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), "utf-8");
36
+ try {
37
+ fs.renameSync(tmp, finalPath);
38
+ } catch (e) {
39
+ try {
40
+ fs.unlinkSync(tmp);
41
+ } catch {
42
+ // best-effort cleanup
43
+ }
44
+ throw e;
45
+ }
46
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ /**
8
+ * GraphQL Name production per spec October 2021 §2.1.9 — `[_A-Za-z][_0-9A-Za-z]*`.
9
+ *
10
+ * Shared by the intent builders to guard any value that lands in an
11
+ * operation-name, variable-name, or type-name position in a rendered query.
12
+ * Without this guard, strings like `"Order Item"` or `"my-id"` produce
13
+ * unparseable GraphQL that surfaces only as a `Validation: Parse error`
14
+ * warning (`buildOutput` never throws) while the malformed query is still
15
+ * returned as `ToolOutput.query`.
16
+ *
17
+ * The MCP boundary has its own zod-based `graphqlName()` factory
18
+ * (`src/mcp/tools/shared/zod-schemas.ts`); this is the intent-layer twin used
19
+ * by the builders, which are also reachable directly (CLI, eval harness).
20
+ */
21
+ export const GRAPHQL_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
22
+
23
+ /**
24
+ * Throw if `value` is not a valid GraphQL Name. `builder` and `field` shape the
25
+ * error message (e.g. `buildList: operationName 'X Y' is not a valid GraphQL
26
+ * Name (must match /^[A-Za-z_][A-Za-z0-9_]*$/)`), matching the inline guards the
27
+ * builders already use for `object` / `inputVariable`.
28
+ */
29
+ export function assertGraphqlName(value: string, builder: string, field: string): void {
30
+ if (!GRAPHQL_NAME_RE.test(value)) {
31
+ throw new Error(
32
+ `${builder}: ${field} '${value}' is not a valid GraphQL Name (must match ${GRAPHQL_NAME_RE})`,
33
+ );
34
+ }
35
+ }