deepline 0.0.1 → 0.1.1

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 (100) hide show
  1. package/README.md +324 -0
  2. package/dist/cli/index.js +6750 -503
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/cli/index.mjs +6735 -512
  5. package/dist/cli/index.mjs.map +1 -1
  6. package/dist/index.d.mts +2349 -32
  7. package/dist/index.d.ts +2349 -32
  8. package/dist/index.js +1631 -82
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +1617 -83
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +3256 -0
  13. package/dist/repo/apps/play-runner-workers/src/dedup-do.ts +710 -0
  14. package/dist/repo/apps/play-runner-workers/src/entry.ts +5070 -0
  15. package/dist/repo/apps/play-runner-workers/src/runtime/README.md +21 -0
  16. package/dist/repo/apps/play-runner-workers/src/runtime/batching.ts +177 -0
  17. package/dist/repo/apps/play-runner-workers/src/runtime/execution-plan.ts +52 -0
  18. package/dist/repo/apps/play-runner-workers/src/runtime/tool-batch.ts +100 -0
  19. package/dist/repo/apps/play-runner-workers/src/runtime/tool-result.ts +184 -0
  20. package/dist/repo/sdk/src/cli/commands/auth.ts +482 -0
  21. package/dist/repo/sdk/src/cli/commands/billing.ts +188 -0
  22. package/dist/repo/sdk/src/cli/commands/csv.ts +123 -0
  23. package/dist/repo/sdk/src/cli/commands/db.ts +119 -0
  24. package/dist/repo/sdk/src/cli/commands/feedback.ts +40 -0
  25. package/dist/repo/sdk/src/cli/commands/org.ts +117 -0
  26. package/dist/repo/sdk/src/cli/commands/play.ts +3200 -0
  27. package/dist/repo/sdk/src/cli/commands/tools.ts +687 -0
  28. package/dist/repo/sdk/src/cli/dataset-stats.ts +341 -0
  29. package/dist/repo/sdk/src/cli/index.ts +138 -0
  30. package/dist/repo/sdk/src/cli/progress.ts +135 -0
  31. package/dist/repo/sdk/src/cli/trace.ts +61 -0
  32. package/dist/repo/sdk/src/cli/utils.ts +145 -0
  33. package/dist/repo/sdk/src/client.ts +1188 -0
  34. package/dist/repo/sdk/src/compat.ts +77 -0
  35. package/dist/repo/sdk/src/config.ts +285 -0
  36. package/dist/repo/sdk/src/errors.ts +125 -0
  37. package/dist/repo/sdk/src/http.ts +391 -0
  38. package/dist/repo/sdk/src/index.ts +139 -0
  39. package/dist/repo/sdk/src/play.ts +1330 -0
  40. package/dist/repo/sdk/src/plays/bundle-play-file.ts +133 -0
  41. package/dist/repo/sdk/src/plays/harness-stub.ts +210 -0
  42. package/dist/repo/sdk/src/plays/local-file-discovery.ts +326 -0
  43. package/dist/repo/sdk/src/tool-output.ts +489 -0
  44. package/dist/repo/sdk/src/types.ts +669 -0
  45. package/dist/repo/sdk/src/version.ts +2 -0
  46. package/dist/repo/sdk/src/worker-play-entry.ts +286 -0
  47. package/dist/repo/shared_libs/observability/node-tracing.ts +129 -0
  48. package/dist/repo/shared_libs/observability/tracing.ts +98 -0
  49. package/dist/repo/shared_libs/play-runtime/backend.ts +139 -0
  50. package/dist/repo/shared_libs/play-runtime/batch-runtime.ts +182 -0
  51. package/dist/repo/shared_libs/play-runtime/batching-types.ts +91 -0
  52. package/dist/repo/shared_libs/play-runtime/context.ts +3999 -0
  53. package/dist/repo/shared_libs/play-runtime/coordinator-headers.ts +78 -0
  54. package/dist/repo/shared_libs/play-runtime/ctx-contract.ts +250 -0
  55. package/dist/repo/shared_libs/play-runtime/ctx-types.ts +713 -0
  56. package/dist/repo/shared_libs/play-runtime/dataset-id.ts +10 -0
  57. package/dist/repo/shared_libs/play-runtime/db-session-crypto.ts +304 -0
  58. package/dist/repo/shared_libs/play-runtime/db-session.ts +462 -0
  59. package/dist/repo/shared_libs/play-runtime/dedup-backend.ts +0 -0
  60. package/dist/repo/shared_libs/play-runtime/default-batch-strategies.ts +124 -0
  61. package/dist/repo/shared_libs/play-runtime/execution-plan.ts +262 -0
  62. package/dist/repo/shared_libs/play-runtime/live-events.ts +214 -0
  63. package/dist/repo/shared_libs/play-runtime/live-state-contract.ts +50 -0
  64. package/dist/repo/shared_libs/play-runtime/map-execution-frame.ts +114 -0
  65. package/dist/repo/shared_libs/play-runtime/map-row-identity.ts +158 -0
  66. package/dist/repo/shared_libs/play-runtime/profiles.ts +90 -0
  67. package/dist/repo/shared_libs/play-runtime/progress-emitter.ts +172 -0
  68. package/dist/repo/shared_libs/play-runtime/protocol.ts +121 -0
  69. package/dist/repo/shared_libs/play-runtime/public-play-contract.ts +42 -0
  70. package/dist/repo/shared_libs/play-runtime/result-normalization.ts +33 -0
  71. package/dist/repo/shared_libs/play-runtime/runtime-actions.ts +208 -0
  72. package/dist/repo/shared_libs/play-runtime/runtime-api.ts +1873 -0
  73. package/dist/repo/shared_libs/play-runtime/runtime-constraints.ts +2 -0
  74. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-neon-serverless.ts +201 -0
  75. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-pg.ts +48 -0
  76. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver.ts +84 -0
  77. package/dist/repo/shared_libs/play-runtime/scheduler-backend.ts +174 -0
  78. package/dist/repo/shared_libs/play-runtime/static-pipeline-types.ts +147 -0
  79. package/dist/repo/shared_libs/play-runtime/suspension.ts +68 -0
  80. package/dist/repo/shared_libs/play-runtime/tool-batch-executor.ts +146 -0
  81. package/dist/repo/shared_libs/play-runtime/tool-result.ts +387 -0
  82. package/dist/repo/shared_libs/play-runtime/tracing.ts +31 -0
  83. package/dist/repo/shared_libs/play-runtime/waterfall-replay.ts +75 -0
  84. package/dist/repo/shared_libs/play-runtime/worker-api-types.ts +140 -0
  85. package/dist/repo/shared_libs/plays/artifact-transport.ts +14 -0
  86. package/dist/repo/shared_libs/plays/artifact-types.ts +49 -0
  87. package/dist/repo/shared_libs/plays/bundling/index.ts +1346 -0
  88. package/dist/repo/shared_libs/plays/compiler-manifest.ts +186 -0
  89. package/dist/repo/shared_libs/plays/contracts.ts +51 -0
  90. package/dist/repo/shared_libs/plays/dataset.ts +308 -0
  91. package/dist/repo/shared_libs/plays/definition.ts +264 -0
  92. package/dist/repo/shared_libs/plays/file-refs.ts +11 -0
  93. package/dist/repo/shared_libs/plays/rate-limit-scheduler.ts +206 -0
  94. package/dist/repo/shared_libs/plays/resolve-static-pipeline.ts +164 -0
  95. package/dist/repo/shared_libs/plays/row-identity.ts +302 -0
  96. package/dist/repo/shared_libs/plays/runtime-validation.ts +415 -0
  97. package/dist/repo/shared_libs/plays/static-pipeline.ts +560 -0
  98. package/dist/repo/shared_libs/temporal/constants.ts +39 -0
  99. package/dist/repo/shared_libs/temporal/preview-config.ts +153 -0
  100. package/package.json +14 -12
@@ -0,0 +1,687 @@
1
+ import { Command } from 'commander';
2
+ import { DeeplineClient } from '../../client.js';
3
+ import { DeeplineError } from '../../errors.js';
4
+ import type { PlayDescription, PlayListItem, ToolDefinition } from '../../types.js';
5
+ import {
6
+ extractSummaryFields,
7
+ tryConvertToList,
8
+ writeCsvOutputFile,
9
+ writeJsonOutputFile,
10
+ } from '../../tool-output.js';
11
+ import { argsWantJson, printJsonError, shouldEmitJson } from '../utils.js';
12
+
13
+ type OutputFormat = 'auto' | 'csv' | 'csv_file' | 'json' | 'json_file';
14
+ type ToolInputField = {
15
+ name?: unknown;
16
+ type?: unknown;
17
+ required?: unknown;
18
+ description?: unknown;
19
+ default?: unknown;
20
+ };
21
+ type CliToolMetadata = ToolDefinition & Record<string, unknown>;
22
+
23
+ type ListedTool = ToolDefinition & {
24
+ id: string;
25
+ type: 'tool';
26
+ callCommand: string;
27
+ };
28
+
29
+ function toListedTool(tool: ToolDefinition): ListedTool {
30
+ return {
31
+ ...tool,
32
+ id: tool.toolId,
33
+ type: 'tool',
34
+ callCommand: `deepline tools call ${tool.toolId}`,
35
+ };
36
+ }
37
+
38
+ async function listTools(args: string[]): Promise<number> {
39
+ const client = new DeeplineClient();
40
+ const items = (await client.listTools()).map(toListedTool);
41
+
42
+ if (argsWantJson(args)) {
43
+ process.stdout.write(`${JSON.stringify(items)}\n`);
44
+ return 0;
45
+ }
46
+
47
+ console.log(`${items.length} tools available:\n`);
48
+ for (const item of items) {
49
+ const cats = item.categories.length ? ` [${item.categories.join(', ')}]` : '';
50
+ const listHint = item.listExtractorPaths?.length
51
+ ? ` listExtractorPaths=${item.listExtractorPaths.join(',')}`
52
+ : '';
53
+ console.log(` ${item.toolId}${cats}`);
54
+ console.log(` ${item.description}${listHint}`);
55
+ }
56
+ return 0;
57
+ }
58
+
59
+ function toolMatchesQuery(tool: ToolDefinition, terms: string[]): boolean {
60
+ const haystack = [
61
+ tool.toolId,
62
+ tool.provider,
63
+ tool.displayName,
64
+ tool.description,
65
+ tool.operation,
66
+ tool.operationId,
67
+ ...(tool.operationAliases ?? []),
68
+ ...(tool.categories ?? []),
69
+ tool.inputSchema ? JSON.stringify(tool.inputSchema) : '',
70
+ ]
71
+ .filter(Boolean)
72
+ .join(' ')
73
+ .toLowerCase();
74
+ return terms.every((term) => haystack.includes(term));
75
+ }
76
+
77
+ async function searchTools(args: string[]): Promise<number> {
78
+ const query = args[0]?.trim();
79
+ if (!query) {
80
+ console.error('Usage: deepline tools search <query> [--json]');
81
+ return 1;
82
+ }
83
+
84
+ const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
85
+ const client = new DeeplineClient();
86
+ const items = (await client.listTools())
87
+ .filter((tool) => toolMatchesQuery(tool, terms))
88
+ .map(toListedTool);
89
+
90
+ if (argsWantJson(args)) {
91
+ process.stdout.write(`${JSON.stringify({ tools: items })}\n`);
92
+ return 0;
93
+ }
94
+
95
+ console.log(`${items.length} tools found:\n`);
96
+ for (const item of items) {
97
+ const cats = item.categories.length ? ` [${item.categories.join(', ')}]` : '';
98
+ console.log(` ${item.toolId}${cats}`);
99
+ console.log(` ${item.description}`);
100
+ if (item.inputSchema) {
101
+ console.log(' inputSchema: yes');
102
+ }
103
+ }
104
+ return 0;
105
+ }
106
+
107
+ function playIdentifiers(play: PlayListItem | PlayDescription): string[] {
108
+ return [play.name, play.reference, ...(play.aliases ?? [])]
109
+ .filter((value): value is string => Boolean(value?.trim()))
110
+ .map((value) => value.trim());
111
+ }
112
+
113
+ async function findPlayForToolId(
114
+ client: DeeplineClient,
115
+ toolId: string,
116
+ ): Promise<PlayDescription | null> {
117
+ const requested = toolId.trim();
118
+ if (!requested) {
119
+ return null;
120
+ }
121
+
122
+ const plays = await client.searchPlays({ query: requested, compact: true });
123
+ return (
124
+ plays.find((play) => playIdentifiers(play).includes(requested)) ?? null
125
+ );
126
+ }
127
+
128
+ function printPlayAliasToolError(toolId: string, play: PlayDescription): void {
129
+ const playName = play.reference ?? play.name;
130
+ console.error(
131
+ `${toolId} is a play, not a tool.\n` +
132
+ `Use: deepline plays run ${playName} --input '{...}' --watch\n` +
133
+ `Inspect its schema with: deepline plays describe ${playName} --json`,
134
+ );
135
+ }
136
+
137
+ export function registerToolsCommands(program: Command): void {
138
+ const tools = program
139
+ .command('tools')
140
+ .description('Search, describe, and call atomic provider tools.')
141
+ .addHelpText(
142
+ 'after',
143
+ `
144
+ Concepts:
145
+ Tools are atomic provider/API operations. Plays are workflows and must be run
146
+ through the plays namespace.
147
+
148
+ Common commands:
149
+ deepline tools search email --json
150
+ deepline tools get hunter_email_verifier --json
151
+ deepline tools call hunter_email_verifier --input '{"email":"a@b.com"}'
152
+ `,
153
+ );
154
+
155
+ tools
156
+ .command('list')
157
+ .description('List available tools.')
158
+ .option('--json', 'Emit JSON output')
159
+ .action(async (options) => {
160
+ process.exitCode = await listTools([
161
+ ...(options.json ? ['--json'] : []),
162
+ ]);
163
+ });
164
+
165
+ tools
166
+ .command('search <query>')
167
+ .description('Search available tools.')
168
+ .option('--json', 'Emit JSON output. Also automatic when stdout is piped')
169
+ .action(async (query, options) => {
170
+ process.exitCode = await searchTools([
171
+ query,
172
+ ...(options.json ? ['--json'] : []),
173
+ ]);
174
+ });
175
+
176
+ tools
177
+ .command('get <toolId>')
178
+ .alias('describe')
179
+ .description('Show metadata for a tool.')
180
+ .addHelpText(
181
+ 'after',
182
+ `
183
+ Notes:
184
+ Shows the tool contract, input schema, output schema, costs, aliases, and metadata.
185
+
186
+ Examples:
187
+ deepline tools get hunter_email_verifier
188
+ deepline tools get hunter_email_verifier --json | jq '.inputSchema'
189
+ `,
190
+ )
191
+ .option('--json', 'Emit JSON output. Also automatic when stdout is piped')
192
+ .action(async (toolId, options) => {
193
+ process.exitCode = await getTool([
194
+ toolId,
195
+ ...(options.json ? ['--json'] : []),
196
+ ]);
197
+ });
198
+
199
+ tools
200
+ .command('call <toolId>')
201
+ .alias('execute')
202
+ .alias('run')
203
+ .description('Execute a tool by id.')
204
+ .addHelpText(
205
+ 'after',
206
+ `
207
+ Notes:
208
+ Use tools for one atomic provider/API operation. Use plays for composed workflows,
209
+ waterfalls, row maps, checkpoints, and retries.
210
+
211
+ Examples:
212
+ deepline tools call hunter_email_verifier --input '{"email":"a@b.com"}'
213
+ deepline tools call hunter_email_verifier -p email=a@b.com
214
+ deepline tools call test_rate_limit --input '{"key":"smoke"}' --json | jq '.status'
215
+ `,
216
+ )
217
+ .option('-p, --param <key=value>', 'Pass one parameter. Repeat for multiple values.', (value, acc: string[]) => {
218
+ acc.push(value);
219
+ return acc;
220
+ }, [])
221
+ .option('--json [payload]', 'Emit JSON output. Use `--input` or `--payload` for passing JSON params.')
222
+ .option('--input <payload>', 'Merge a JSON object into the tool params')
223
+ .option('--payload <payload>', 'Merge a JSON object into the tool params')
224
+ .option(
225
+ '--output-format <format>',
226
+ 'Output format: auto, csv, csv_file, json, or json_file',
227
+ )
228
+ .option('--full-output', 'Emit the full JSON payload')
229
+ .option('--no-preview', 'Only print the extracted output path when applicable')
230
+ .action(async (toolId, options) => {
231
+ const args = [
232
+ toolId,
233
+ ...((options.param as string[]).flatMap((value) => ['--param', value])),
234
+ ...(options.json === true ? ['--json'] : []),
235
+ ...(typeof options.json === 'string' ? ['--json', options.json] : []),
236
+ ...(options.input ? ['--input', options.input] : []),
237
+ ...(options.payload ? ['--payload', options.payload] : []),
238
+ ...(options.outputFormat ? ['--output-format', options.outputFormat] : []),
239
+ ...(options.fullOutput ? ['--full-output'] : []),
240
+ ...(options.preview === false ? ['--no-preview'] : []),
241
+ ];
242
+ process.exitCode = await executeTool(args);
243
+ });
244
+ }
245
+
246
+ async function getTool(args: string[]): Promise<number> {
247
+ const toolId = args[0];
248
+ if (!toolId) {
249
+ console.error('Usage: deepline tools get <toolId> [--json]');
250
+ return 1;
251
+ }
252
+
253
+ const client = new DeeplineClient();
254
+ let tool: CliToolMetadata;
255
+ try {
256
+ tool = (await client.getTool(toolId)) as unknown as CliToolMetadata;
257
+ } catch (error) {
258
+ const play = await findPlayForToolId(client, toolId);
259
+ if (play) {
260
+ printPlayAliasToolError(toolId, play);
261
+ return 2;
262
+ }
263
+ throw error;
264
+ }
265
+
266
+ if (argsWantJson(args)) {
267
+ process.stdout.write(`${JSON.stringify(tool)}\n`);
268
+ return 0;
269
+ }
270
+
271
+ printToolDetails(tool as CliToolMetadata, toolId);
272
+ return 0;
273
+ }
274
+
275
+ function printToolDetails(tool: CliToolMetadata, requestedToolId: string): void {
276
+ const toolId = String(tool.toolId || requestedToolId);
277
+ const operation = typeof tool.operation === 'string' ? tool.operation : '';
278
+ const displayBase =
279
+ operation && operation.startsWith(`${tool.provider}_`)
280
+ ? operation.slice(String(tool.provider).length + 1)
281
+ : operation
282
+ ? `${tool.provider} ${operation}`.trim()
283
+ : toolId;
284
+ const displayName = titleCase(displayBase || String(tool.displayName || toolId));
285
+ const cost = isRecord(tool.cost) ? tool.cost : null;
286
+ const deeplineCredits = numberField(tool, 'deeplineCreditsPerPricingUnit', 'deepline_credits_per_pricing_unit');
287
+ const deeplineUsdPerPricingUnit = numberField(tool, 'deeplineUsdPerPricingUnit', 'deepline_usd_per_pricing_unit');
288
+ const deeplineUsdPerCredit = numberField(tool, 'deeplineUsdPerCredit', 'deepline_usd_per_credit');
289
+ const billingSource = stringField(tool, 'billingSource', 'billing_source');
290
+ const billingSourceLabel = stringField(tool, 'billingSourceLabel', 'billing_source_label');
291
+ const estimatedCreditsRange = stringField(tool, 'estimatedCreditsRange', 'estimated_credits_range');
292
+ const estimatedUsdRange = stringField(tool, 'estimatedUsdRange', 'estimated_usd_range');
293
+ const estimateModelVersion = stringField(tool, 'estimateModelVersion', 'estimate_model_version');
294
+ const estimateBasedOnTools = arrayField(tool, 'estimateBasedOnTools', 'estimate_based_on_tools')
295
+ .map((item) => String(item).trim())
296
+ .filter(Boolean);
297
+ const stepContributions = arrayField(tool, 'stepContributions', 'step_contributions');
298
+ const playExpansion = recordField(tool, 'playExpansion', 'play_expansion');
299
+ const samples = recordField(tool, 'samples');
300
+
301
+ console.log(`Tool: ${toolId}`);
302
+ if (displayName) {
303
+ console.log(' Display name:');
304
+ console.log(` ${displayName}`);
305
+ }
306
+ if (tool.categories.length > 0) {
307
+ console.log(' Categories:');
308
+ console.log(` ${tool.categories.join(', ')}`);
309
+ }
310
+
311
+ const printedCost = printToolCost({
312
+ cost,
313
+ billingSource,
314
+ deeplineCredits,
315
+ deeplineUsdPerPricingUnit,
316
+ });
317
+ if (!printedCost && ['run_javascript', 'call_local_codex', 'call_local_claude_code'].includes(toolId)) {
318
+ console.log(' Cost: free');
319
+ }
320
+ if (billingSourceLabel) {
321
+ console.log(` Billing source: ${billingSourceLabel}`);
322
+ }
323
+ if (typeof deeplineUsdPerCredit === 'number') {
324
+ console.log(` Deepline rate: $${deeplineUsdPerCredit.toFixed(2)} per credit`);
325
+ }
326
+
327
+ if (estimatedCreditsRange) {
328
+ const usdSuffix = estimatedUsdRange ? ` (~${estimatedUsdRange})` : '';
329
+ console.log(` Estimated play spend: ${estimatedCreditsRange}${usdSuffix}`);
330
+ if (estimateModelVersion) console.log(` model: ${estimateModelVersion}`);
331
+ if (estimateBasedOnTools.length) console.log(` based on: ${estimateBasedOnTools.join(', ')}`);
332
+ if (stepContributions.length) {
333
+ console.log(' step contributions:');
334
+ for (const item of stepContributions) {
335
+ if (!isRecord(item)) continue;
336
+ const stepTool = typeof item.tool === 'string' ? item.tool.trim() : '';
337
+ const low = typeof item.lowCredits === 'number' ? item.lowCredits : null;
338
+ const high = typeof item.highCredits === 'number' ? item.highCredits : null;
339
+ const lowUsd = typeof item.lowUsd === 'number' ? item.lowUsd : null;
340
+ const highUsd = typeof item.highUsd === 'number' ? item.highUsd : null;
341
+ if (!stepTool || low === null || high === null) continue;
342
+ const stepUsdSuffix = lowUsd !== null && highUsd !== null ? ` (~${formatUsd(lowUsd)}-${formatUsd(highUsd)})` : '';
343
+ console.log(` - ${stepTool}: ${formatDecimal(low)}-${formatDecimal(high)} credits${stepUsdSuffix}`);
344
+ }
345
+ }
346
+ }
347
+
348
+ if (playExpansion && Object.keys(playExpansion).length > 0) {
349
+ const group = typeof playExpansion.group === 'string' ? playExpansion.group.trim() : '';
350
+ console.log(' Play expansion:');
351
+ if (group) console.log(` group: ${group}`);
352
+ }
353
+
354
+ const fields = toolInputFieldsForDisplay(recordField(tool, 'inputSchema', 'input_schema'));
355
+ if (fields.length) {
356
+ console.log(' Inputs (operation-specific):');
357
+ for (const field of fields) {
358
+ const name = String(field.name || '');
359
+ const typeName = String(field.type || 'unknown');
360
+ const requiredLabel = field.required ? 'required' : 'optional';
361
+ const defaultSuffix = Object.prototype.hasOwnProperty.call(field, 'default')
362
+ ? `, default: ${JSON.stringify(field.default)}`
363
+ : '';
364
+ const desc = typeof field.description === 'string' && field.description.trim() ? ` - ${field.description.trim()}` : '';
365
+ console.log(` - ${name} (${typeName}, ${requiredLabel}${defaultSuffix})${desc}`);
366
+ }
367
+ console.log(' Tip: pass --payload with a JSON object.');
368
+ }
369
+
370
+ printSamples(samples);
371
+
372
+ if (isPlayTool(tool)) {
373
+ console.log(' Play contract:');
374
+ console.log(' - This is a deepline-native waterfall; the returned rows are extracted by target getters, not by hand-authored payload shape.');
375
+ if (playExpansion && typeof playExpansion.group === 'string' && playExpansion.group.trim()) {
376
+ console.log(` - Output alias/runtime group is: ${playExpansion.group.trim()}`);
377
+ }
378
+ const getters = recordField(tool, 'resultIdentityGetters', 'result_identity_getters');
379
+ const targets = Object.keys(getters).filter(Boolean).sort();
380
+ if (targets.length) {
381
+ console.log(` - Built-in extract targets: ${targets.join(', ')}`);
382
+ }
383
+ }
384
+
385
+ console.log('');
386
+ console.log('Usage:');
387
+ const requestPayload = samplePayload(samples, 'request');
388
+ if (isPlayTool(tool)) {
389
+ if (requestPayload !== undefined) {
390
+ console.log(` deepline enrich --with '${JSON.stringify({ alias: 'result', tool: toolId, payload: requestPayload })}'`);
391
+ } else {
392
+ console.log(` deepline enrich --with '{"alias":"result","tool":"${toolId}","payload":{...}}'`);
393
+ }
394
+ } else if (requestPayload !== undefined) {
395
+ console.log(` deepline tools execute ${toolId} --payload '${JSON.stringify(requestPayload)}'`);
396
+ } else {
397
+ console.log(` deepline tools execute ${toolId} --payload '{...}'`);
398
+ }
399
+ console.log(' deepline tools get <tool_id> --json # full machine-readable output');
400
+ }
401
+
402
+ function printToolCost(input: {
403
+ cost: Record<string, unknown> | null;
404
+ billingSource: string;
405
+ deeplineCredits: number | null;
406
+ deeplineUsdPerPricingUnit: number | null;
407
+ }): boolean {
408
+ const { cost, billingSource, deeplineCredits, deeplineUsdPerPricingUnit } = input;
409
+ if (!cost) return false;
410
+ const pricingModel = typeof cost.pricingModel === 'string' ? cost.pricingModel : typeof cost.pricing_model === 'string' ? cost.pricing_model : '';
411
+ const billingMode = typeof cost.billingMode === 'string' ? cost.billingMode : typeof cost.billing_mode === 'string' ? cost.billing_mode : '';
412
+ const providerPrice = typeof cost.providerPricePerPricingModel === 'number' ? cost.providerPricePerPricingModel : null;
413
+ if (pricingModel === 'fixed' && providerPrice === 0) {
414
+ console.log(' Cost: free');
415
+ return true;
416
+ }
417
+ if (billingSource === 'own_provider_credentials') {
418
+ console.log(' Cost: free through Deepline');
419
+ return true;
420
+ }
421
+ if (pricingModel && billingMode && deeplineCredits !== null) {
422
+ const unit = pricingModel === 'per_page' ? 'page' : pricingModel === 'per_result' ? 'result' : 'call';
423
+ const usdText = deeplineUsdPerPricingUnit !== null ? ` / ${formatUsd(deeplineUsdPerPricingUnit)}` : '';
424
+ console.log(` Cost: ${formatDecimal(deeplineCredits)} Deepline credits${usdText} per ${unit} (${billingMode})`);
425
+ return true;
426
+ }
427
+ if (pricingModel && billingMode && providerPrice !== null) {
428
+ const unit = pricingModel === 'per_page' ? 'page' : pricingModel === 'per_result' ? 'result' : 'call';
429
+ console.log(` Cost: ${formatDecimal(providerPrice)} provider credits per ${unit} (${billingMode})`);
430
+ return true;
431
+ }
432
+ return false;
433
+ }
434
+
435
+ function toolInputFieldsForDisplay(inputSchema: Record<string, unknown>): ToolInputField[] {
436
+ if (Array.isArray(inputSchema.fields)) return inputSchema.fields.filter(isRecord) as ToolInputField[];
437
+ const jsonSchema = isRecord(inputSchema.jsonSchema) ? inputSchema.jsonSchema : inputSchema;
438
+ const properties = isRecord(jsonSchema.properties) ? jsonSchema.properties : {};
439
+ const required = Array.isArray(jsonSchema.required) ? new Set(jsonSchema.required.map(String)) : new Set<string>();
440
+ return Object.entries(properties).map(([name, value]) => {
441
+ const property = isRecord(value) ? value : {};
442
+ return {
443
+ name,
444
+ type: typeof property.type === 'string' ? property.type : 'unknown',
445
+ required: required.has(name),
446
+ description: property.description,
447
+ ...(Object.prototype.hasOwnProperty.call(property, 'default') ? { default: property.default } : {}),
448
+ };
449
+ });
450
+ }
451
+
452
+ function printSamples(samples: Record<string, unknown>): void {
453
+ const requestPayload = samplePayload(samples, 'request');
454
+ const responsePayload = samplePayload(samples, 'response');
455
+ if (requestPayload === undefined && responsePayload === undefined) return;
456
+ console.log(' Samples:');
457
+ if (requestPayload !== undefined) printJsonPreview('Example payload', requestPayload);
458
+ if (responsePayload !== undefined) printJsonPreview('Example response', responsePayload);
459
+ }
460
+
461
+ function printJsonPreview(label: string, payload: unknown): void {
462
+ console.log(` ${label}:`);
463
+ const rendered = JSON.stringify(payload, null, 2);
464
+ for (const line of rendered.split('\n')) {
465
+ console.log(` ${line}`);
466
+ }
467
+ }
468
+
469
+ function samplePayload(samples: Record<string, unknown>, key: 'request' | 'response'): unknown {
470
+ const entry = samples[key];
471
+ if (!isRecord(entry)) return undefined;
472
+ return Object.prototype.hasOwnProperty.call(entry, 'payload') ? entry.payload : entry;
473
+ }
474
+
475
+ function isPlayTool(tool: Record<string, unknown>): boolean {
476
+ const provider = typeof tool.provider === 'string' ? tool.provider : '';
477
+ return provider === 'deepline_native' && Boolean(recordField(tool, 'playExpansion', 'play_expansion'));
478
+ }
479
+
480
+ function titleCase(value: string): string {
481
+ return value
482
+ .replace(/[_-]+/g, ' ')
483
+ .split(' ')
484
+ .filter(Boolean)
485
+ .map((part) => {
486
+ const lower = part.toLowerCase();
487
+ const special: Record<string, string> = { linkedin: 'LinkedIn', crm: 'CRM', api: 'API' };
488
+ return special[lower] ?? `${part[0]?.toUpperCase() ?? ''}${part.slice(1)}`;
489
+ })
490
+ .join(' ');
491
+ }
492
+
493
+ function formatDecimal(value: number): string {
494
+ const text = value.toFixed(12).replace(/0+$/, '').replace(/\.$/, '');
495
+ return text || '0';
496
+ }
497
+
498
+ function formatUsd(value: number): string {
499
+ return `$${formatDecimal(value)}`;
500
+ }
501
+
502
+ function isRecord(value: unknown): value is Record<string, unknown> {
503
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
504
+ }
505
+
506
+ function stringField(source: Record<string, unknown>, ...keys: string[]): string {
507
+ for (const key of keys) {
508
+ const value = source[key];
509
+ if (typeof value === 'string' && value.trim()) return value.trim();
510
+ }
511
+ return '';
512
+ }
513
+
514
+ function numberField(source: Record<string, unknown>, ...keys: string[]): number | null {
515
+ for (const key of keys) {
516
+ const value = source[key];
517
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
518
+ }
519
+ return null;
520
+ }
521
+
522
+ function arrayField(source: Record<string, unknown>, ...keys: string[]): unknown[] {
523
+ for (const key of keys) {
524
+ const value = source[key];
525
+ if (Array.isArray(value)) return value;
526
+ }
527
+ return [];
528
+ }
529
+
530
+ function recordField(source: Record<string, unknown>, ...keys: string[]): Record<string, unknown> {
531
+ for (const key of keys) {
532
+ const value = source[key];
533
+ if (isRecord(value)) return value;
534
+ }
535
+ return {};
536
+ }
537
+
538
+ function normalizeOutputFormat(raw: string | null): OutputFormat {
539
+ switch ((raw || 'auto').trim().toLowerCase()) {
540
+ case 'auto':
541
+ return 'auto';
542
+ case 'csv':
543
+ return 'csv';
544
+ case 'csv_file':
545
+ return 'csv_file';
546
+ case 'json':
547
+ return 'json';
548
+ case 'json_file':
549
+ return 'json_file';
550
+ default:
551
+ throw new Error(`Invalid value for --output-format: ${raw}`);
552
+ }
553
+ }
554
+
555
+ function parseExecuteOptions(args: string[]) {
556
+ const toolId = args[0];
557
+ if (!toolId) {
558
+ throw new Error('Usage: deepline tools call <toolId> [--param key=value ...] [--input \'{"k":"v"}\'] [--output-format auto|csv|csv_file|json|json_file] [--full-output] [--no-preview]');
559
+ }
560
+
561
+ const params: Record<string, unknown> = {};
562
+ let outputFormat: OutputFormat = 'auto';
563
+ let noPreview = false;
564
+
565
+ for (let index = 1; index < args.length; index += 1) {
566
+ const arg = args[index]!;
567
+ if ((arg === '--param' || arg === '-p') && args[index + 1]) {
568
+ const kv = args[++index]!;
569
+ const eqIdx = kv.indexOf('=');
570
+ if (eqIdx > 0) params[kv.slice(0, eqIdx)] = kv.slice(eqIdx + 1);
571
+ continue;
572
+ }
573
+ if (arg === '--json') {
574
+ const next = args[index + 1];
575
+ outputFormat = 'json';
576
+ if (next && !next.startsWith('--')) {
577
+ Object.assign(params, JSON.parse(args[++index]!));
578
+ }
579
+ continue;
580
+ }
581
+ if ((arg === '--input' || arg === '--payload') && args[index + 1]) {
582
+ Object.assign(params, JSON.parse(args[++index]!));
583
+ continue;
584
+ }
585
+ if (arg === '--output-format' && args[index + 1]) {
586
+ outputFormat = normalizeOutputFormat(args[++index]!);
587
+ continue;
588
+ }
589
+ if (arg === '--full-output') {
590
+ outputFormat = 'json';
591
+ continue;
592
+ }
593
+ if (arg === '--no-preview') {
594
+ noPreview = true;
595
+ continue;
596
+ }
597
+ throw new Error(`Unknown option: ${arg}`);
598
+ }
599
+
600
+ return { toolId, params, outputFormat, noPreview };
601
+ }
602
+
603
+ async function executeTool(args: string[]): Promise<number> {
604
+ let parsed: ReturnType<typeof parseExecuteOptions>;
605
+ try {
606
+ parsed = parseExecuteOptions(args);
607
+ } catch (error) {
608
+ if (argsWantJson(args)) {
609
+ printJsonError(error);
610
+ } else {
611
+ console.error(error instanceof Error ? error.message : String(error));
612
+ }
613
+ return 1;
614
+ }
615
+
616
+ const client = new DeeplineClient();
617
+ let metadata: Awaited<ReturnType<DeeplineClient['getTool']>>;
618
+ try {
619
+ metadata = await client.getTool(parsed.toolId);
620
+ } catch (error) {
621
+ const play = await findPlayForToolId(client, parsed.toolId);
622
+ if (play) {
623
+ printPlayAliasToolError(parsed.toolId, play);
624
+ return 2;
625
+ }
626
+ if (error instanceof DeeplineError) {
627
+ throw error;
628
+ }
629
+ throw error;
630
+ }
631
+ const rawResponse = await client.executeToolRaw(parsed.toolId, parsed.params);
632
+
633
+ const listConversion = tryConvertToList(rawResponse, {
634
+ listExtractorPaths: metadata.listExtractorPaths ?? [],
635
+ });
636
+ const summary = extractSummaryFields(rawResponse);
637
+
638
+ if (parsed.outputFormat === 'json' || (parsed.outputFormat === 'auto' && shouldEmitJson())) {
639
+ process.stdout.write(`${JSON.stringify(rawResponse, null, 2)}\n`);
640
+ return 0;
641
+ }
642
+
643
+ if (parsed.outputFormat === 'json_file') {
644
+ const jsonPath = writeJsonOutputFile(rawResponse, `payload_${parsed.toolId}`);
645
+ console.log(jsonPath);
646
+ return 0;
647
+ }
648
+
649
+ if (!listConversion) {
650
+ if (parsed.outputFormat === 'csv' || parsed.outputFormat === 'csv_file') {
651
+ const jsonPath = writeJsonOutputFile(rawResponse, `payload_${parsed.toolId}`);
652
+ console.log(jsonPath);
653
+ return 0;
654
+ }
655
+ process.stdout.write(`${JSON.stringify(rawResponse, null, 2)}\n`);
656
+ return 0;
657
+ }
658
+
659
+ const csv = writeCsvOutputFile(listConversion.rows, `${parsed.toolId}_output`);
660
+ if (parsed.outputFormat === 'csv_file') {
661
+ process.stdout.write(`${JSON.stringify({
662
+ extracted_csv: csv.path,
663
+ extracted_csv_rows: csv.rowCount,
664
+ extracted_csv_columns: csv.columns,
665
+ preview: csv.preview,
666
+ list_strategy: listConversion.strategy,
667
+ list_source_path: listConversion.sourcePath,
668
+ summary,
669
+ })}\n`);
670
+ return 0;
671
+ }
672
+
673
+ if (parsed.noPreview) {
674
+ console.log(csv.path);
675
+ return 0;
676
+ }
677
+
678
+ console.log(`${csv.path} (${csv.rowCount} rows)`);
679
+ if (csv.columns.length > 0) {
680
+ console.log(`columns: ${JSON.stringify(csv.columns)}`);
681
+ }
682
+ if (Object.keys(summary).length > 0) {
683
+ console.log(`summary: ${Object.entries(summary).map(([key, value]) => `${key}=${String(value)}`).join(', ')}`);
684
+ }
685
+ console.log(`preview: ${JSON.stringify(csv.preview)}`);
686
+ return 0;
687
+ }