voyageai-cli 1.30.1 → 1.30.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 (37) hide show
  1. package/README.md +4 -4
  2. package/package.json +1 -1
  3. package/src/cli.js +2 -0
  4. package/src/commands/about.js +3 -3
  5. package/src/commands/code-search.js +751 -0
  6. package/src/commands/doctor.js +1 -1
  7. package/src/commands/index-workspace.js +9 -5
  8. package/src/commands/playground.js +9 -1
  9. package/src/commands/quickstart.js +4 -4
  10. package/src/commands/workflow.js +132 -65
  11. package/src/lib/catalog.js +4 -2
  12. package/src/lib/code-search.js +315 -0
  13. package/src/lib/codegen.js +1 -1
  14. package/src/lib/explanations.js +3 -3
  15. package/src/lib/github.js +226 -0
  16. package/src/lib/template-engine.js +154 -20
  17. package/src/lib/workflow-builder.js +753 -0
  18. package/src/lib/workflow-formatters.js +454 -0
  19. package/src/lib/workflow-input-cache.js +111 -0
  20. package/src/lib/workflow-scaffold.js +1 -1
  21. package/src/lib/workflow.js +91 -1
  22. package/src/mcp/schemas/index.js +130 -0
  23. package/src/mcp/server.js +17 -4
  24. package/src/mcp/tools/authoring.js +662 -0
  25. package/src/mcp/tools/code-search.js +620 -0
  26. package/src/mcp/tools/ingest.js +2 -5
  27. package/src/mcp/tools/retrieval.js +2 -15
  28. package/src/mcp/tools/workspace.js +1 -12
  29. package/src/mcp/utils.js +20 -0
  30. package/src/playground/help/workflow-nodes.js +127 -2
  31. package/src/playground/index.html +1366 -24
  32. package/src/workflows/code-review.json +110 -0
  33. package/src/workflows/cost-analysis.json +5 -0
  34. package/src/workflows/tests/code-review.fresh-index.test.json +83 -0
  35. package/src/workflows/tests/code-review.happy-path.test.json +121 -0
  36. package/src/workflows/tests/code-review.no-question.test.json +70 -0
  37. package/src/workflows/tests/smart-ingest.duplicate-detected.test.json +2 -2
@@ -92,6 +92,132 @@ function resolvePath(segments, context) {
92
92
  return current;
93
93
  }
94
94
 
95
+ /**
96
+ * Resolve a single expression fragment (a path, string literal, or number).
97
+ * Returns the resolved value or undefined.
98
+ *
99
+ * @param {string} fragment - e.g. "inputs.code", "'hello'", "42"
100
+ * @param {object} context
101
+ * @returns {*}
102
+ */
103
+ function resolveFragment(fragment, context) {
104
+ const trimmed = fragment.trim();
105
+ if (!trimmed) return undefined;
106
+
107
+ // String literal (single or double quotes)
108
+ if ((trimmed.startsWith("'") && trimmed.endsWith("'")) ||
109
+ (trimmed.startsWith('"') && trimmed.endsWith('"'))) {
110
+ return trimmed.slice(1, -1);
111
+ }
112
+
113
+ // Number literal
114
+ if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
115
+ return parseFloat(trimmed);
116
+ }
117
+
118
+ // Boolean / null / undefined
119
+ if (trimmed === 'true') return true;
120
+ if (trimmed === 'false') return false;
121
+ if (trimmed === 'null') return null;
122
+ if (trimmed === 'undefined') return undefined;
123
+
124
+ // Path lookup
125
+ const segments = parseExpression(trimmed);
126
+ return resolvePath(segments, context);
127
+ }
128
+
129
+ /**
130
+ * Resolve an expression that may contain || (fallback) and + (concatenation).
131
+ *
132
+ * Supports:
133
+ * path.to.value simple path
134
+ * a.value || b.value fallback (first truthy)
135
+ * 'prefix: ' + path.to.value string concatenation
136
+ * a.value || 'default text' fallback with literal
137
+ * 'prefix' + a.value + ' suffix' multi-part concatenation
138
+ *
139
+ * Operator precedence: || is evaluated first (lowest), then +.
140
+ *
141
+ * @param {string} expr
142
+ * @param {object} context
143
+ * @returns {*}
144
+ */
145
+ function resolveExpr(expr, context) {
146
+ const trimmed = expr.trim();
147
+
148
+ // Split on || (fallback operator) first
149
+ if (trimmed.includes('||')) {
150
+ const parts = splitOnOperator(trimmed, '||');
151
+ if (parts.length > 1) {
152
+ for (const part of parts) {
153
+ const val = resolveExpr(part.trim(), context);
154
+ if (val !== undefined && val !== null && val !== '' && val !== false) {
155
+ return val;
156
+ }
157
+ }
158
+ // All parts falsy: return the last one resolved
159
+ return resolveExpr(parts[parts.length - 1].trim(), context);
160
+ }
161
+ }
162
+
163
+ // Split on + (concatenation)
164
+ if (trimmed.includes('+')) {
165
+ const parts = splitOnOperator(trimmed, '+');
166
+ if (parts.length > 1) {
167
+ let result = '';
168
+ for (const part of parts) {
169
+ const val = resolveFragment(part.trim(), context);
170
+ if (val === undefined || val === null) {
171
+ result += '';
172
+ } else if (typeof val === 'object') {
173
+ result += JSON.stringify(val);
174
+ } else {
175
+ result += String(val);
176
+ }
177
+ }
178
+ return result;
179
+ }
180
+ }
181
+
182
+ // Simple fragment (path, literal, etc.)
183
+ return resolveFragment(trimmed, context);
184
+ }
185
+
186
+ /**
187
+ * Split a string on an operator, respecting string literals.
188
+ * Doesn't split inside quoted strings.
189
+ *
190
+ * @param {string} str
191
+ * @param {string} op - e.g. "||" or "+"
192
+ * @returns {string[]}
193
+ */
194
+ function splitOnOperator(str, op) {
195
+ const parts = [];
196
+ let current = '';
197
+ let inSingle = false;
198
+ let inDouble = false;
199
+
200
+ for (let i = 0; i < str.length; i++) {
201
+ const ch = str[i];
202
+
203
+ if (ch === "'" && !inDouble) {
204
+ inSingle = !inSingle;
205
+ current += ch;
206
+ } else if (ch === '"' && !inSingle) {
207
+ inDouble = !inDouble;
208
+ current += ch;
209
+ } else if (!inSingle && !inDouble && str.slice(i, i + op.length) === op) {
210
+ parts.push(current);
211
+ current = '';
212
+ i += op.length - 1;
213
+ } else {
214
+ current += ch;
215
+ }
216
+ }
217
+ parts.push(current);
218
+ return parts;
219
+ }
220
+
95
221
  /**
96
222
  * Resolve template expressions within a single string.
97
223
  *
@@ -112,8 +238,7 @@ function resolveString(str, context) {
112
238
  const soleMatch = str.match(SOLE_TEMPLATE_RE);
113
239
  if (soleMatch) {
114
240
  try {
115
- const segments = parseExpression(soleMatch[1]);
116
- return resolvePath(segments, context);
241
+ return resolveExpr(soleMatch[1], context);
117
242
  } catch {
118
243
  return undefined;
119
244
  }
@@ -122,8 +247,7 @@ function resolveString(str, context) {
122
247
  // Mixed text + templates: substitute all, coerce to string
123
248
  return str.replace(TEMPLATE_RE, (_match, expr) => {
124
249
  try {
125
- const segments = parseExpression(expr);
126
- const value = resolvePath(segments, context);
250
+ const value = resolveExpr(expr, context);
127
251
  if (value === undefined) return '';
128
252
  if (value === null) return 'null';
129
253
  if (typeof value === 'object') return JSON.stringify(value);
@@ -192,23 +316,33 @@ function extractDependencies(obj) {
192
316
  let match;
193
317
  while ((match = re.exec(value)) !== null) {
194
318
  const expr = match[1].trim();
195
- // Extract the first segment (root identifier)
196
- const dotIdx = expr.indexOf('.');
197
- const bracketIdx = expr.indexOf('[');
198
- let root;
199
- if (dotIdx === -1 && bracketIdx === -1) {
200
- root = expr;
201
- } else if (dotIdx === -1) {
202
- root = expr.slice(0, bracketIdx);
203
- } else if (bracketIdx === -1) {
204
- root = expr.slice(0, dotIdx);
205
- } else {
206
- root = expr.slice(0, Math.min(dotIdx, bracketIdx));
319
+ // Extract ALL root identifiers from the expression.
320
+ // Expressions can contain operators (&&, ||, !, ===, etc.)
321
+ // so we scan for all dotted-path identifiers.
322
+ const idRe = /(?:^|[^a-zA-Z0-9_.])([a-zA-Z_][a-zA-Z0-9_]*)(?:\.|$|\[| *[><=!&|)])/g;
323
+ let idMatch;
324
+ while ((idMatch = idRe.exec(expr)) !== null) {
325
+ const root = idMatch[1];
326
+ // Skip workflow-level references, literals, and keywords
327
+ if (root !== 'inputs' && root !== 'defaults' &&
328
+ root !== 'true' && root !== 'false' &&
329
+ root !== 'null' && root !== 'undefined' &&
330
+ root !== 'item' && root !== 'index') {
331
+ deps.add(root);
332
+ }
207
333
  }
208
-
209
- // Skip workflow-level references
210
- if (root !== 'inputs' && root !== 'defaults') {
211
- deps.add(root);
334
+ // Also handle simple single-identifier case (e.g. "{{ stepId }}")
335
+ if (!expr.includes('.') && !expr.includes('[') &&
336
+ !expr.includes('&') && !expr.includes('|') &&
337
+ !expr.includes('!') && !expr.includes('=') &&
338
+ !expr.includes('>') && !expr.includes('<')) {
339
+ const root = expr;
340
+ if (root !== 'inputs' && root !== 'defaults' &&
341
+ root !== 'true' && root !== 'false' &&
342
+ root !== 'null' && root !== 'undefined' &&
343
+ root !== 'item' && root !== 'index') {
344
+ deps.add(root);
345
+ }
212
346
  }
213
347
  }
214
348
  return;