deepline 0.1.11 → 0.1.19

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 (85) hide show
  1. package/README.md +18 -10
  2. package/dist/cli/index.js +1795 -1052
  3. package/dist/cli/index.mjs +1795 -1053
  4. package/dist/index.d.mts +427 -308
  5. package/dist/index.d.ts +427 -308
  6. package/dist/index.js +391 -326
  7. package/dist/index.mjs +391 -325
  8. package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +88 -22
  9. package/dist/repo/apps/play-runner-workers/src/entry.ts +804 -1253
  10. package/dist/repo/sdk/src/client.ts +287 -47
  11. package/dist/repo/sdk/src/config.ts +125 -8
  12. package/dist/repo/sdk/src/http.ts +10 -2
  13. package/dist/repo/sdk/src/index.ts +7 -16
  14. package/dist/repo/sdk/src/play.ts +105 -140
  15. package/dist/repo/sdk/src/plays/bundle-play-file.ts +23 -6
  16. package/dist/repo/sdk/src/plays/local-file-discovery.ts +207 -160
  17. package/dist/repo/sdk/src/tool-output.ts +0 -146
  18. package/dist/repo/sdk/src/types.ts +27 -0
  19. package/dist/repo/sdk/src/version.ts +2 -2
  20. package/dist/repo/sdk/src/worker-play-entry.ts +3 -0
  21. package/dist/repo/shared_libs/play-runtime/csv-rename.ts +180 -0
  22. package/dist/repo/shared_libs/play-runtime/tool-result.ts +250 -133
  23. package/dist/repo/shared_libs/plays/bundling/index.ts +274 -234
  24. package/dist/repo/shared_libs/plays/dataset.ts +29 -1
  25. package/package.json +5 -4
  26. package/dist/cli/index.js.map +0 -1
  27. package/dist/cli/index.mjs.map +0 -1
  28. package/dist/index.js.map +0 -1
  29. package/dist/index.mjs.map +0 -1
  30. package/dist/repo/apps/play-runner-workers/src/runtime/README.md +0 -21
  31. package/dist/repo/apps/play-runner-workers/src/runtime/batching.ts +0 -177
  32. package/dist/repo/apps/play-runner-workers/src/runtime/execution-plan.ts +0 -52
  33. package/dist/repo/apps/play-runner-workers/src/runtime/tool-batch.ts +0 -100
  34. package/dist/repo/apps/play-runner-workers/src/runtime/tool-result.ts +0 -184
  35. package/dist/repo/sdk/src/cli/commands/auth.ts +0 -500
  36. package/dist/repo/sdk/src/cli/commands/billing.ts +0 -188
  37. package/dist/repo/sdk/src/cli/commands/csv.ts +0 -123
  38. package/dist/repo/sdk/src/cli/commands/db.ts +0 -119
  39. package/dist/repo/sdk/src/cli/commands/feedback.ts +0 -40
  40. package/dist/repo/sdk/src/cli/commands/org.ts +0 -117
  41. package/dist/repo/sdk/src/cli/commands/play.ts +0 -3307
  42. package/dist/repo/sdk/src/cli/commands/tools.ts +0 -687
  43. package/dist/repo/sdk/src/cli/dataset-stats.ts +0 -341
  44. package/dist/repo/sdk/src/cli/index.ts +0 -148
  45. package/dist/repo/sdk/src/cli/progress.ts +0 -149
  46. package/dist/repo/sdk/src/cli/skills-sync.ts +0 -141
  47. package/dist/repo/sdk/src/cli/trace.ts +0 -61
  48. package/dist/repo/sdk/src/cli/utils.ts +0 -145
  49. package/dist/repo/sdk/src/compat.ts +0 -77
  50. package/dist/repo/shared_libs/observability/node-tracing.ts +0 -129
  51. package/dist/repo/shared_libs/observability/tracing.ts +0 -98
  52. package/dist/repo/shared_libs/play-runtime/context.ts +0 -3999
  53. package/dist/repo/shared_libs/play-runtime/ctx-contract.ts +0 -250
  54. package/dist/repo/shared_libs/play-runtime/ctx-types.ts +0 -713
  55. package/dist/repo/shared_libs/play-runtime/dataset-id.ts +0 -10
  56. package/dist/repo/shared_libs/play-runtime/db-session-crypto.ts +0 -304
  57. package/dist/repo/shared_libs/play-runtime/db-session.ts +0 -462
  58. package/dist/repo/shared_libs/play-runtime/live-events.ts +0 -214
  59. package/dist/repo/shared_libs/play-runtime/live-state-contract.ts +0 -50
  60. package/dist/repo/shared_libs/play-runtime/map-execution-frame.ts +0 -114
  61. package/dist/repo/shared_libs/play-runtime/map-row-identity.ts +0 -158
  62. package/dist/repo/shared_libs/play-runtime/progress-emitter.ts +0 -172
  63. package/dist/repo/shared_libs/play-runtime/protocol.ts +0 -121
  64. package/dist/repo/shared_libs/play-runtime/public-play-contract.ts +0 -42
  65. package/dist/repo/shared_libs/play-runtime/result-normalization.ts +0 -33
  66. package/dist/repo/shared_libs/play-runtime/runtime-api.ts +0 -1873
  67. package/dist/repo/shared_libs/play-runtime/runtime-constraints.ts +0 -2
  68. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-neon-serverless.ts +0 -201
  69. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-pg.ts +0 -48
  70. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver.ts +0 -84
  71. package/dist/repo/shared_libs/play-runtime/static-pipeline-types.ts +0 -147
  72. package/dist/repo/shared_libs/play-runtime/suspension.ts +0 -68
  73. package/dist/repo/shared_libs/play-runtime/tracing.ts +0 -31
  74. package/dist/repo/shared_libs/play-runtime/waterfall-replay.ts +0 -75
  75. package/dist/repo/shared_libs/play-runtime/worker-api-types.ts +0 -140
  76. package/dist/repo/shared_libs/plays/artifact-transport.ts +0 -14
  77. package/dist/repo/shared_libs/plays/artifact-types.ts +0 -49
  78. package/dist/repo/shared_libs/plays/compiler-manifest.ts +0 -186
  79. package/dist/repo/shared_libs/plays/definition.ts +0 -264
  80. package/dist/repo/shared_libs/plays/file-refs.ts +0 -11
  81. package/dist/repo/shared_libs/plays/rate-limit-scheduler.ts +0 -206
  82. package/dist/repo/shared_libs/plays/resolve-static-pipeline.ts +0 -164
  83. package/dist/repo/shared_libs/plays/runtime-validation.ts +0 -415
  84. package/dist/repo/shared_libs/temporal/constants.ts +0 -39
  85. package/dist/repo/shared_libs/temporal/preview-config.ts +0 -153
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/index.ts
4
- import { Command as Command2 } from "commander";
4
+ import { Command } from "commander";
5
5
 
6
6
  // src/config.ts
7
7
  import { readFileSync, existsSync, mkdirSync, writeFileSync } from "fs";
@@ -47,6 +47,16 @@ var ConfigError = class extends DeeplineError {
47
47
  var PROD_URL = "https://code.deepline.com";
48
48
  var DEFAULT_TIMEOUT = 6e4;
49
49
  var DEFAULT_MAX_RETRIES = 3;
50
+ var ACTIVE_DEEPLINE_ENV_FILE = ".env.deepline";
51
+ function isProdBaseUrl(baseUrl) {
52
+ return baseUrl.trim().replace(/\/$/, "") === PROD_URL;
53
+ }
54
+ function profileNameForBaseUrl(baseUrl) {
55
+ return isProdBaseUrl(baseUrl) ? "prod" : "dev";
56
+ }
57
+ function projectEnvStartDir() {
58
+ return process.env.DEEPLINE_PROJECT_ENV_DIR?.trim() || process.cwd();
59
+ }
50
60
  function baseUrlSlug(baseUrl) {
51
61
  let url;
52
62
  try {
@@ -82,16 +92,52 @@ function parseEnvFile(filePath) {
82
92
  }
83
93
  return env;
84
94
  }
85
- function findNearestWorktreeEnv(startDir = process.cwd()) {
95
+ function findNearestEnvFile(names, startDir = process.cwd()) {
86
96
  let current = resolve(startDir);
87
97
  while (true) {
88
- const values = parseEnvFile(join(current, ".env.worktree"));
89
- if (Object.keys(values).length > 0) return values;
98
+ for (const name of names) {
99
+ const filePath = join(current, name);
100
+ if (existsSync(filePath)) return filePath;
101
+ }
90
102
  const parent = dirname(current);
91
- if (parent === current) return {};
103
+ if (parent === current) return null;
92
104
  current = parent;
93
105
  }
94
106
  }
107
+ function findNearestEnv(names, startDir = process.cwd()) {
108
+ const filePath = findNearestEnvFile(names, startDir);
109
+ return filePath ? parseEnvFile(filePath) : {};
110
+ }
111
+ function findNearestWorktreeEnv(startDir = process.cwd()) {
112
+ return findNearestEnv([".env.worktree"], startDir);
113
+ }
114
+ function resolveProfileEnvFileNames() {
115
+ const explicitProfile = process.env.DEEPLINE_ENV_PROFILE?.trim() || process.env.DEEPLINE_PROFILE?.trim() || "";
116
+ const names = [];
117
+ if (explicitProfile) names.push(`.env.deepline.${explicitProfile}`);
118
+ const nodeEnv = process.env.NODE_ENV?.trim();
119
+ if (nodeEnv === "production") names.push(".env.deepline.prod");
120
+ else if (nodeEnv === "staging") names.push(".env.deepline.staging");
121
+ names.push(ACTIVE_DEEPLINE_ENV_FILE);
122
+ return names;
123
+ }
124
+ function resolveProjectAppEnvFileNames() {
125
+ const nodeEnv = process.env.NODE_ENV?.trim();
126
+ const names = [];
127
+ if (nodeEnv === "production") names.push(".env.prod");
128
+ if (nodeEnv === "staging") names.push(".env.staging");
129
+ names.push(".env.local", ".env");
130
+ return names;
131
+ }
132
+ function resolveBaseUrlFromEnvValues(env) {
133
+ return env.DEEPLINE_ORIGIN_URL?.trim() || env.DEEPLINE_API_BASE_URL?.trim() || "";
134
+ }
135
+ function loadProjectDeeplineEnv() {
136
+ return findNearestEnv(resolveProfileEnvFileNames(), projectEnvStartDir());
137
+ }
138
+ function loadProjectAppEnv() {
139
+ return findNearestEnv(resolveProjectAppEnvFileNames(), projectEnvStartDir());
140
+ }
95
141
  function normalizeWorktreeBaseUrl(baseUrl, worktreeEnv = findNearestWorktreeEnv()) {
96
142
  const trimmed = baseUrl.trim().replace(/\/$/, "");
97
143
  if (!trimmed) return trimmed;
@@ -143,6 +189,10 @@ function autoDetectBaseUrl() {
143
189
  if (envOrigin) return normalizeWorktreeBaseUrl(envOrigin);
144
190
  const envBase = process.env.DEEPLINE_API_BASE_URL?.trim();
145
191
  if (envBase) return normalizeWorktreeBaseUrl(envBase);
192
+ const projectDeeplineBaseUrl = resolveBaseUrlFromEnvValues(loadProjectDeeplineEnv());
193
+ if (projectDeeplineBaseUrl) return normalizeWorktreeBaseUrl(projectDeeplineBaseUrl);
194
+ const projectAppBaseUrl = resolveBaseUrlFromEnvValues(loadProjectAppEnv());
195
+ if (projectAppBaseUrl) return normalizeWorktreeBaseUrl(projectAppBaseUrl);
146
196
  const worktreeBaseUrl = resolveWorktreeBaseUrl();
147
197
  if (worktreeBaseUrl) return worktreeBaseUrl;
148
198
  const globalEnv = loadGlobalCliEnv();
@@ -154,7 +204,9 @@ function resolveConfig(options) {
154
204
  const requestedBaseUrl = options?.baseUrl?.trim() || autoDetectBaseUrl();
155
205
  const baseUrl = normalizeWorktreeBaseUrl(requestedBaseUrl);
156
206
  const cliEnv = loadCliEnv(baseUrl);
157
- const apiKey = options?.apiKey?.trim() || process.env.DEEPLINE_API_KEY?.trim() || cliEnv.DEEPLINE_API_KEY || "";
207
+ const projectDeeplineEnv = loadProjectDeeplineEnv();
208
+ const projectAppEnv = loadProjectAppEnv();
209
+ const apiKey = options?.apiKey?.trim() || process.env.DEEPLINE_API_KEY?.trim() || projectDeeplineEnv.DEEPLINE_API_KEY || projectAppEnv.DEEPLINE_API_KEY || cliEnv.DEEPLINE_API_KEY || "";
158
210
  if (!apiKey) {
159
211
  throw new ConfigError(
160
212
  `No API key found. Set DEEPLINE_API_KEY env var, pass apiKey option, or run: deepline auth register`
@@ -167,10 +219,32 @@ function resolveConfig(options) {
167
219
  maxRetries: options?.maxRetries ?? DEFAULT_MAX_RETRIES
168
220
  };
169
221
  }
222
+ function mergeEnvFile(filePath, values) {
223
+ const existing = existsSync(filePath) ? parseEnvFile(filePath) : {};
224
+ const merged = { ...existing, ...values };
225
+ const dir = dirname(filePath);
226
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
227
+ const lines = Object.entries(merged).filter(([, value]) => value !== "").map(([key, value]) => `${key}=${value}`);
228
+ writeFileSync(filePath, `${lines.join("\n")}
229
+ `, "utf-8");
230
+ }
231
+ function saveProjectDeeplineEnvValues(baseUrl, values, startDir = projectEnvStartDir()) {
232
+ const root = resolve(startDir);
233
+ const profile = profileNameForBaseUrl(baseUrl);
234
+ const files = [
235
+ join(root, ACTIVE_DEEPLINE_ENV_FILE),
236
+ join(root, `.env.deepline.${profile}`)
237
+ ];
238
+ if (profile === "dev") files.push(join(root, ".env"));
239
+ for (const filePath of files) {
240
+ mergeEnvFile(filePath, values);
241
+ }
242
+ return files;
243
+ }
170
244
 
171
245
  // src/version.ts
172
- var SDK_VERSION = "0.1.11";
173
- var SDK_API_CONTRACT = "2026-04-plays-v1";
246
+ var SDK_VERSION = "0.1.19";
247
+ var SDK_API_CONTRACT = "2026-05-runs-v2";
174
248
 
175
249
  // ../shared_libs/play-runtime/coordinator-headers.ts
176
250
  var COORDINATOR_INTERNAL_TOKEN_HEADER = "x-deepline-internal-token";
@@ -363,8 +437,12 @@ var HttpClient = class {
363
437
  * @param path - API path
364
438
  * @param body - Request body (will be JSON-serialized)
365
439
  */
366
- async post(path, body) {
367
- return this.request(path, { method: "POST", body });
440
+ async post(path, body, headers) {
441
+ return this.request(path, {
442
+ method: "POST",
443
+ body,
444
+ headers
445
+ });
368
446
  }
369
447
  /**
370
448
  * Send a DELETE request.
@@ -444,6 +522,10 @@ function sleep(ms) {
444
522
 
445
523
  // src/client.ts
446
524
  var TERMINAL_PLAY_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
525
+ var INCLUDE_TOOL_METADATA_HEADER = "x-deepline-include-tool-metadata";
526
+ function isRecord(value) {
527
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
528
+ }
447
529
  function normalizePlayStatus(raw) {
448
530
  const status = typeof raw.status === "string" ? raw.status : typeof raw.temporalStatus === "string" ? mapLegacyTemporalStatus(raw.temporalStatus) : "running";
449
531
  const runId = typeof raw.runId === "string" ? raw.runId : typeof raw.workflowId === "string" ? raw.workflowId : "";
@@ -473,6 +555,7 @@ function mapLegacyTemporalStatus(status) {
473
555
  var DeeplineClient = class {
474
556
  http;
475
557
  config;
558
+ runs;
476
559
  /**
477
560
  * @param options - Optional overrides for API key, base URL, timeout, and retries.
478
561
  * @throws {@link ConfigError} if no API key can be resolved from any source.
@@ -480,6 +563,13 @@ var DeeplineClient = class {
480
563
  constructor(options) {
481
564
  this.config = resolveConfig(options);
482
565
  this.http = new HttpClient(this.config);
566
+ this.runs = {
567
+ get: (runId) => this.getRunStatus(runId),
568
+ list: (options2) => this.listRuns(options2),
569
+ tail: (runId, options2) => this.tailRun(runId, options2),
570
+ logs: (runId, options2) => this.getRunLogs(runId, options2),
571
+ stop: (runId, options2) => this.stopRun(runId, options2)
572
+ };
483
573
  }
484
574
  /** The resolved base URL this client is targeting (e.g. `"http://localhost:3000"`). */
485
575
  get baseUrl() {
@@ -496,12 +586,27 @@ var DeeplineClient = class {
496
586
  ).filter((field) => Boolean(field?.name)) : [];
497
587
  return fields.length > 0 ? { fields } : schema;
498
588
  }
499
- playRunCommand(name) {
500
- return `deepline plays run ${name} --input '{...}' --watch`;
589
+ schemaMetadata(schema, key) {
590
+ if (!isRecord(schema)) return null;
591
+ const value = schema[key];
592
+ return isRecord(value) ? value : null;
593
+ }
594
+ playRunCommand(play, options) {
595
+ const target = play.reference || play.name;
596
+ if (options?.csvInput) {
597
+ const inputField = typeof options.csvInput.inputField === "string" && options.csvInput.inputField.trim() ? options.csvInput.inputField.trim() : "csv";
598
+ return `deepline plays run ${target} --${inputField} leads.csv --watch`;
599
+ }
600
+ return `deepline plays run ${target} --input '{...}' --watch`;
501
601
  }
502
602
  summarizePlayListItem(play, options) {
503
603
  const aliases = play.aliases?.length ? play.aliases : [play.name];
504
- const runCommand = this.playRunCommand(play.name);
604
+ const csvInput = this.schemaMetadata(play.inputSchema, "csvInput");
605
+ const rowOutputSchema = this.schemaMetadata(
606
+ play.outputSchema,
607
+ "rowOutputSchema"
608
+ );
609
+ const runCommand = this.playRunCommand(play, { csvInput });
505
610
  return {
506
611
  name: play.name,
507
612
  ...play.reference ? { reference: play.reference } : {},
@@ -513,6 +618,8 @@ var DeeplineClient = class {
513
618
  aliases,
514
619
  inputSchema: options?.compact ? this.compactSchema(play.inputSchema) : play.inputSchema ?? null,
515
620
  outputSchema: options?.compact ? this.compactSchema(play.outputSchema) : play.outputSchema ?? null,
621
+ ...csvInput ? { csvInput } : {},
622
+ ...rowOutputSchema ? { rowOutputSchema } : {},
516
623
  runCommand,
517
624
  examples: [runCommand],
518
625
  currentPublishedVersion: play.currentPublishedVersion ?? null,
@@ -551,6 +658,31 @@ var DeeplineClient = class {
551
658
  );
552
659
  return res.tools;
553
660
  }
661
+ /**
662
+ * Search available tools using Deepline's ranked backend search.
663
+ *
664
+ * This is the same discovery surface used by the legacy CLI: it ranks across
665
+ * tool metadata, categories, agent guidance, and input schema fields.
666
+ */
667
+ async searchTools(options = {}) {
668
+ const params = new URLSearchParams();
669
+ const query = options.query?.trim() ?? "";
670
+ params.set("q", query);
671
+ params.set(
672
+ "include_search_debug",
673
+ options.includeSearchDebug ? "true" : "false"
674
+ );
675
+ params.set("search_mode", options.searchMode ?? "v2");
676
+ if (options.categories?.trim()) {
677
+ params.set("categories", options.categories.trim());
678
+ }
679
+ if (options.searchTerms?.trim()) {
680
+ params.set("search_terms", options.searchTerms.trim());
681
+ }
682
+ return this.http.get(
683
+ `/api/v2/integrations/list?${params.toString()}`
684
+ );
685
+ }
554
686
  /**
555
687
  * Get detailed metadata for a single tool.
556
688
  *
@@ -579,55 +711,24 @@ var DeeplineClient = class {
579
711
  );
580
712
  }
581
713
  /**
582
- * Execute a tool and return the extracted result.
583
- *
584
- * Sends the input payload to the tool and returns the `.result` field from the
585
- * response. For the full response envelope (including job_id, credits, etc.),
586
- * use {@link executeToolRaw}.
587
- *
588
- * @param toolId - Tool identifier (e.g. `"test_company_search"`)
589
- * @param input - Tool-specific input parameters
590
- * @returns The tool's output (shape varies by tool)
591
- * @throws {@link DeeplineError} if the tool execution fails
592
- *
593
- * @example
594
- * ```typescript
595
- * const company = await client.executeTool('test_company_search', {
596
- * domain: 'stripe.com',
597
- * });
598
- * console.log(company); // { name: "Stripe", industry: "Financial Services", ... }
599
- * ```
600
- */
601
- async executeTool(toolId, input) {
602
- const res = await this.http.post(
603
- `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
604
- { payload: input }
605
- );
606
- return res.result ?? res;
607
- }
608
- /**
609
- * Execute a tool and return the full response envelope.
714
+ * Execute a tool and return the standard execution envelope.
610
715
  *
611
- * Unlike {@link executeTool}, this returns the complete API response including
612
- * `job_id`, `status`, `credits`, and the raw `result` object.
613
- *
614
- * @param toolId - Tool identifier
615
- * @param input - Tool-specific input parameters
616
- * @returns Full response with job metadata and result
617
- *
618
- * @example
619
- * ```typescript
620
- * const raw = await client.executeToolRaw('test_company_search', { domain: 'stripe.com' });
621
- * console.log(`Job: ${raw.job_id}, Credits: ${raw.credits}`);
622
- * console.log(`Result:`, raw.result);
623
- * ```
716
+ * The `result.data` field contains the provider payload. `result.meta`
717
+ * contains provider/upstream metadata such as HTTP status or paging details.
718
+ * Top-level fields such as `status`, `job_id`, and `billing` describe the
719
+ * Deepline execution.
624
720
  */
625
- async executeToolRaw(toolId, input) {
721
+ async executeTool(toolId, input, options) {
722
+ const headers = options?.includeToolMetadata ? { [INCLUDE_TOOL_METADATA_HEADER]: "true" } : void 0;
626
723
  return this.http.post(
627
724
  `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
628
- { payload: input }
725
+ { payload: input },
726
+ headers
629
727
  );
630
728
  }
729
+ async executeToolRaw(toolId, input, options) {
730
+ return this.executeTool(toolId, input, options);
731
+ }
631
732
  async queryCustomerDb(input) {
632
733
  return this.http.post("/api/v2/db/query", {
633
734
  sql: input.sql,
@@ -676,6 +777,7 @@ var DeeplineClient = class {
676
777
  ...request.revisionId ? { revisionId: request.revisionId } : {},
677
778
  ...request.artifactStorageKey ? { artifactStorageKey: request.artifactStorageKey } : {},
678
779
  ...request.sourceCode ? { sourceCode: request.sourceCode } : {},
780
+ ...request.sourceFiles ? { sourceFiles: request.sourceFiles } : {},
679
781
  ..."staticPipeline" in request ? { staticPipeline: request.staticPipeline } : {},
680
782
  ...request.artifactHash ? { artifactHash: request.artifactHash } : {},
681
783
  ...request.graphHash ? { graphHash: request.graphHash } : {},
@@ -700,6 +802,7 @@ var DeeplineClient = class {
700
802
  ...request.revisionId ? { revisionId: request.revisionId } : {},
701
803
  ...request.artifactStorageKey ? { artifactStorageKey: request.artifactStorageKey } : {},
702
804
  ...request.sourceCode ? { sourceCode: request.sourceCode } : {},
805
+ ...request.sourceFiles ? { sourceFiles: request.sourceFiles } : {},
703
806
  ..."staticPipeline" in request ? { staticPipeline: request.staticPipeline } : {},
704
807
  ...request.artifactHash ? { artifactHash: request.artifactHash } : {},
705
808
  ...request.graphHash ? { graphHash: request.graphHash } : {},
@@ -736,6 +839,7 @@ var DeeplineClient = class {
736
839
  const compilerManifest = input.compilerManifest ?? await this.compilePlayManifest({
737
840
  name: input.name,
738
841
  sourceCode: input.sourceCode,
842
+ sourceFiles: input.sourceFiles,
739
843
  artifact: input.artifact
740
844
  });
741
845
  return this.http.post("/api/v2/plays/artifacts", {
@@ -750,6 +854,7 @@ var DeeplineClient = class {
750
854
  compilerManifest: artifact.compilerManifest ?? await this.compilePlayManifest({
751
855
  name: artifact.name,
752
856
  sourceCode: artifact.sourceCode,
857
+ sourceFiles: artifact.sourceFiles,
753
858
  artifact: artifact.artifact
754
859
  })
755
860
  }))
@@ -776,11 +881,13 @@ var DeeplineClient = class {
776
881
  const compilerManifest = input.compilerManifest ?? await this.compilePlayManifest({
777
882
  name: input.name,
778
883
  sourceCode: input.sourceCode,
884
+ sourceFiles: input.sourceFiles,
779
885
  artifact: input.artifact
780
886
  });
781
887
  const registeredArtifact = await this.registerPlayArtifact({
782
888
  name: input.name,
783
889
  sourceCode: input.sourceCode,
890
+ sourceFiles: input.sourceFiles,
784
891
  artifact: input.artifact,
785
892
  compilerManifest,
786
893
  publish: false
@@ -840,11 +947,13 @@ var DeeplineClient = class {
840
947
  const compilerManifest = options?.compilerManifest ?? await this.compilePlayManifest({
841
948
  name,
842
949
  sourceCode,
950
+ sourceFiles: options?.sourceFiles,
843
951
  artifact
844
952
  });
845
953
  const registeredArtifact = await this.registerPlayArtifact({
846
954
  name,
847
955
  sourceCode,
956
+ sourceFiles: options?.sourceFiles,
848
957
  artifact,
849
958
  compilerManifest,
850
959
  publish: false
@@ -1028,6 +1137,112 @@ var DeeplineClient = class {
1028
1137
  );
1029
1138
  return response.runs ?? [];
1030
1139
  }
1140
+ /**
1141
+ * Get a run by id using the public runs resource model.
1142
+ *
1143
+ * This is the SDK equivalent of:
1144
+ *
1145
+ * ```bash
1146
+ * deepline runs get <run-id> --json
1147
+ * ```
1148
+ */
1149
+ async getRunStatus(runId) {
1150
+ const response = await this.http.get(
1151
+ `/api/v2/runs/${encodeURIComponent(runId)}`
1152
+ );
1153
+ return normalizePlayStatus(response);
1154
+ }
1155
+ /**
1156
+ * List play runs using the public runs resource model.
1157
+ *
1158
+ * This is the SDK equivalent of:
1159
+ *
1160
+ * ```bash
1161
+ * deepline runs list --play <play-name> --status failed --json
1162
+ * ```
1163
+ */
1164
+ async listRuns(options) {
1165
+ const playName = options.play.trim();
1166
+ if (!playName) {
1167
+ throw new Error("runs.list requires options.play.");
1168
+ }
1169
+ const params = new URLSearchParams({ play: playName });
1170
+ const status = options.status?.trim();
1171
+ if (status) {
1172
+ params.set("status", status);
1173
+ }
1174
+ const response = await this.http.get(
1175
+ `/api/v2/runs?${params.toString()}`
1176
+ );
1177
+ return response.runs ?? [];
1178
+ }
1179
+ /**
1180
+ * Fetch the lightweight tail status for a run using the public runs resource model.
1181
+ *
1182
+ * This is the SDK equivalent of:
1183
+ *
1184
+ * ```bash
1185
+ * deepline runs tail <run-id> --json
1186
+ * ```
1187
+ */
1188
+ async tailRun(runId, options) {
1189
+ const afterLogIndex = typeof options?.afterLogIndex === "number" ? options.afterLogIndex : typeof options?.cursor === "number" ? options.cursor : typeof options?.cursor === "string" && options.cursor.trim() ? Number(options.cursor) : void 0;
1190
+ const params = new URLSearchParams();
1191
+ if (Number.isFinite(afterLogIndex)) {
1192
+ params.set("afterLogIndex", String(Number(afterLogIndex)));
1193
+ }
1194
+ if (typeof options?.waitMs === "number") {
1195
+ params.set("waitMs", String(options.waitMs));
1196
+ }
1197
+ if (options?.terminalOnly) {
1198
+ params.set("terminalOnly", "true");
1199
+ }
1200
+ const suffix = params.toString() ? `?${params.toString()}` : "";
1201
+ const response = await this.http.get(
1202
+ `/api/v2/runs/${encodeURIComponent(runId)}/tail${suffix}`
1203
+ );
1204
+ return normalizePlayStatus(response);
1205
+ }
1206
+ /**
1207
+ * Fetch persisted logs for a run using the public runs resource model.
1208
+ *
1209
+ * This is the SDK equivalent of:
1210
+ *
1211
+ * ```bash
1212
+ * deepline runs logs <run-id> --limit 200 --json
1213
+ * ```
1214
+ */
1215
+ async getRunLogs(runId, options) {
1216
+ const status = await this.getRunStatus(runId);
1217
+ const logs = status.progress?.logs ?? [];
1218
+ const limit = typeof options?.limit === "number" && Number.isFinite(options.limit) ? Math.max(0, Math.trunc(options.limit)) : 200;
1219
+ const entries = logs.slice(Math.max(0, logs.length - limit));
1220
+ return {
1221
+ runId: status.runId,
1222
+ totalCount: logs.length,
1223
+ returnedCount: entries.length,
1224
+ firstSequence: logs.length === 0 ? null : logs.length - entries.length + 1,
1225
+ lastSequence: logs.length === 0 ? null : logs.length,
1226
+ truncated: logs.length > entries.length,
1227
+ hasMore: logs.length > entries.length,
1228
+ entries
1229
+ };
1230
+ }
1231
+ /**
1232
+ * Stop a run by id using the public runs resource model.
1233
+ *
1234
+ * This is the SDK equivalent of:
1235
+ *
1236
+ * ```bash
1237
+ * deepline runs stop <run-id> --reason "stale lock" --json
1238
+ * ```
1239
+ */
1240
+ async stopRun(runId, options) {
1241
+ return this.http.post(
1242
+ `/api/v2/runs/${encodeURIComponent(runId)}/stop`,
1243
+ options?.reason ? { reason: options.reason } : {}
1244
+ );
1245
+ }
1031
1246
  async listPlays() {
1032
1247
  const response = await this.http.get(
1033
1248
  "/api/v2/plays"
@@ -1414,6 +1629,7 @@ function saveEnvValues(values, baseUrl) {
1414
1629
  const merged = { ...existing, ...values };
1415
1630
  const lines = Object.entries(merged).filter(([, v]) => v !== "").map(([k, v]) => `${k}=${v}`);
1416
1631
  writeFileSync2(filePath, lines.join("\n") + "\n", "utf-8");
1632
+ saveProjectDeeplineEnvValues(baseUrl, values);
1417
1633
  }
1418
1634
  async function httpJson(method, url, apiKey, body) {
1419
1635
  const headers = { "Content-Type": "application/json" };
@@ -1953,24 +2169,70 @@ function registerBillingCommands(program) {
1953
2169
  // src/cli/dataset-stats.ts
1954
2170
  import { writeFileSync as writeFileSync3 } from "fs";
1955
2171
  import { resolve as resolve3 } from "path";
1956
- function isRecord(value) {
2172
+ var CSV_PROJECTED_FIELDS_KEY = "__deeplineCsvProjectedFields";
2173
+ function csvProjectedFields(row) {
2174
+ const serialized = row[CSV_PROJECTED_FIELDS_KEY];
2175
+ if (!Array.isArray(serialized)) {
2176
+ return /* @__PURE__ */ new Set();
2177
+ }
2178
+ return new Set(
2179
+ serialized.filter((field) => typeof field === "string")
2180
+ );
2181
+ }
2182
+ function stripCsvProjectionFields(row) {
2183
+ const projectedFields = csvProjectedFields(row);
2184
+ if (projectedFields.size === 0 && !(CSV_PROJECTED_FIELDS_KEY in row)) {
2185
+ return row;
2186
+ }
2187
+ const stripped = { ...row };
2188
+ for (const field of projectedFields) {
2189
+ delete stripped[field];
2190
+ }
2191
+ delete stripped[CSV_PROJECTED_FIELDS_KEY];
2192
+ return stripped;
2193
+ }
2194
+ function stripCsvProjectionColumns(columns, rows) {
2195
+ const projectedFields = /* @__PURE__ */ new Set();
2196
+ let hasProjectionMetadata = false;
2197
+ for (const row of rows) {
2198
+ for (const field of csvProjectedFields(row)) {
2199
+ projectedFields.add(field);
2200
+ }
2201
+ hasProjectionMetadata ||= CSV_PROJECTED_FIELDS_KEY in row;
2202
+ }
2203
+ if (!hasProjectionMetadata && projectedFields.size === 0) {
2204
+ return columns;
2205
+ }
2206
+ return columns.filter(
2207
+ (column) => column !== CSV_PROJECTED_FIELDS_KEY && !projectedFields.has(column)
2208
+ );
2209
+ }
2210
+ function sanitizeCsvProjectionInfo(input) {
2211
+ const columns = stripCsvProjectionColumns(input.columns, input.rows);
2212
+ const rows = input.rows.map(stripCsvProjectionFields);
2213
+ return { rows, columns };
2214
+ }
2215
+ function isRecord2(value) {
1957
2216
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
1958
2217
  }
1959
2218
  function isSerializedDataset(value) {
1960
- return isRecord(value) && value.kind === "dataset" && typeof value.count === "number" && Array.isArray(value.preview);
2219
+ return isRecord2(value) && value.kind === "dataset" && typeof value.count === "number" && Array.isArray(value.preview);
1961
2220
  }
1962
2221
  function rowArray(value) {
1963
2222
  if (!Array.isArray(value)) {
1964
2223
  return null;
1965
2224
  }
1966
2225
  const rows = value.filter(
1967
- (row) => isRecord(row)
2226
+ (row) => isRecord2(row)
1968
2227
  );
1969
2228
  return rows.length === value.length ? rows : null;
1970
2229
  }
1971
2230
  function readNumber(value) {
1972
2231
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : null;
1973
2232
  }
2233
+ function numericStat(value) {
2234
+ return readNumber(value) ?? 0;
2235
+ }
1974
2236
  function inferColumns(rows) {
1975
2237
  const columns = [];
1976
2238
  const seen = /* @__PURE__ */ new Set();
@@ -1988,12 +2250,12 @@ function inferColumns(rows) {
1988
2250
  return columns;
1989
2251
  }
1990
2252
  function extractCanonicalRowsInfo(statusOrResult) {
1991
- const root = isRecord(statusOrResult) ? statusOrResult : null;
1992
- const result = isRecord(root?.result) ? root.result : root;
2253
+ const root = isRecord2(statusOrResult) ? statusOrResult : null;
2254
+ const result = isRecord2(root?.result) ? root.result : root;
1993
2255
  if (!result) {
1994
2256
  return null;
1995
2257
  }
1996
- const metadata = isRecord(result._metadata) ? result._metadata : null;
2258
+ const metadata = isRecord2(result._metadata) ? result._metadata : null;
1997
2259
  const totalFromMetadata = metadata?.totalRows ?? metadata?.rowCount ?? metadata?.count;
1998
2260
  const candidates = [
1999
2261
  { source: "result.contacts", value: result.contacts, total: totalFromMetadata ?? result.totalRows ?? result.rowCount ?? result.count },
@@ -2001,8 +2263,8 @@ function extractCanonicalRowsInfo(statusOrResult) {
2001
2263
  { source: "result.rows", value: result.rows, total: totalFromMetadata ?? result.totalRows ?? result.rowCount ?? result.count },
2002
2264
  { source: "result.results", value: result.results, total: totalFromMetadata ?? result.totalRows ?? result.rowCount ?? result.count }
2003
2265
  ];
2004
- if (isRecord(result.output)) {
2005
- const outputMetadata = isRecord(result.output._metadata) ? result.output._metadata : null;
2266
+ if (isRecord2(result.output)) {
2267
+ const outputMetadata = isRecord2(result.output._metadata) ? result.output._metadata : null;
2006
2268
  const outputTotalFromMetadata = outputMetadata?.totalRows ?? outputMetadata?.rowCount ?? outputMetadata?.count;
2007
2269
  candidates.push(
2008
2270
  { source: "result.output.contacts", value: result.output.contacts, total: outputTotalFromMetadata ?? result.output.totalRows ?? result.output.rowCount ?? result.output.count },
@@ -2013,12 +2275,17 @@ function extractCanonicalRowsInfo(statusOrResult) {
2013
2275
  }
2014
2276
  for (const candidate of candidates) {
2015
2277
  if (isSerializedDataset(candidate.value)) {
2016
- const rows2 = rowArray(candidate.value.preview) ?? [];
2017
- const totalRows2 = readNumber(candidate.value.count) ?? rows2.length;
2278
+ const rawRows = rowArray(candidate.value.preview) ?? [];
2279
+ const totalRows2 = readNumber(candidate.value.count) ?? rawRows.length;
2280
+ const rawColumns = Array.isArray(candidate.value.columns) && candidate.value.columns.every((column) => typeof column === "string") ? candidate.value.columns : inferColumns(rawRows);
2281
+ const { rows: rows2, columns } = sanitizeCsvProjectionInfo({
2282
+ rows: rawRows,
2283
+ columns: rawColumns
2284
+ });
2018
2285
  return {
2019
2286
  rows: rows2,
2020
2287
  totalRows: totalRows2,
2021
- columns: Array.isArray(candidate.value.columns) && candidate.value.columns.every((column) => typeof column === "string") ? candidate.value.columns : inferColumns(rows2),
2288
+ columns,
2022
2289
  complete: rows2.length === totalRows2,
2023
2290
  source: candidate.source
2024
2291
  };
@@ -2028,10 +2295,14 @@ function extractCanonicalRowsInfo(statusOrResult) {
2028
2295
  continue;
2029
2296
  }
2030
2297
  const totalRows = readNumber(candidate.total) ?? rows.length;
2031
- return {
2298
+ const sanitized = sanitizeCsvProjectionInfo({
2032
2299
  rows,
2300
+ columns: inferColumns(rows)
2301
+ });
2302
+ return {
2303
+ rows: sanitized.rows,
2033
2304
  totalRows,
2034
- columns: inferColumns(rows),
2305
+ columns: sanitized.columns,
2035
2306
  complete: rows.length === totalRows,
2036
2307
  source: candidate.source
2037
2308
  };
@@ -2041,6 +2312,31 @@ function extractCanonicalRowsInfo(statusOrResult) {
2041
2312
  function percentText(numerator, denominator) {
2042
2313
  return denominator > 0 ? `${numerator}/${denominator} (${Math.round(100 * numerator / denominator)}%)` : "0/0 (0%)";
2043
2314
  }
2315
+ function isDatasetExecutionStatsInput(value) {
2316
+ return isRecord2(value) && isRecord2(value.columnStats) && Object.values(value.columnStats).every(isRecord2);
2317
+ }
2318
+ function extractDatasetExecutionStats(statusOrResult) {
2319
+ if (!isRecord2(statusOrResult)) {
2320
+ return null;
2321
+ }
2322
+ const direct = statusOrResult.dataset_execution_stats;
2323
+ if (isDatasetExecutionStatsInput(direct)) {
2324
+ return direct;
2325
+ }
2326
+ const nested = isRecord2(statusOrResult.result) ? statusOrResult.result.dataset_execution_stats : null;
2327
+ return isDatasetExecutionStatsInput(nested) ? nested : null;
2328
+ }
2329
+ function formatExecutionStats(raw, denominator) {
2330
+ return {
2331
+ queued: percentText(numericStat(raw.queued), denominator),
2332
+ running: percentText(numericStat(raw.running), denominator),
2333
+ "completed:executed": percentText(numericStat(raw.completed), denominator),
2334
+ "completed:reused": percentText(numericStat(raw.cached), denominator),
2335
+ "skipped:condition": percentText(numericStat(raw.skipped), denominator),
2336
+ "skipped:missed": percentText(numericStat(raw.missed), denominator),
2337
+ failed: percentText(numericStat(raw.failed), denominator)
2338
+ };
2339
+ }
2044
2340
  function countPercentText(count, denominator) {
2045
2341
  return denominator > 0 ? `${count} (${Math.round(100 * count / denominator)}%)` : "0 (0%)";
2046
2342
  }
@@ -2079,13 +2375,13 @@ function summarizeSampleValue(value, depth = 0) {
2079
2375
  if (typeof parsed === "number" || typeof parsed === "boolean") return parsed;
2080
2376
  if (depth >= 3) {
2081
2377
  if (Array.isArray(parsed)) return [];
2082
- if (isRecord(parsed)) return {};
2378
+ if (isRecord2(parsed)) return {};
2083
2379
  return compactScalar(parsed);
2084
2380
  }
2085
2381
  if (Array.isArray(parsed)) {
2086
2382
  return parsed.slice(0, 3).map((item) => summarizeSampleValue(item, depth + 1));
2087
2383
  }
2088
- if (isRecord(parsed)) {
2384
+ if (isRecord2(parsed)) {
2089
2385
  const out = {};
2090
2386
  for (const [key, nested] of Object.entries(parsed)) {
2091
2387
  if (["__dl", "meta", "metadata"].includes(key)) {
@@ -2115,7 +2411,7 @@ function compactCell(value) {
2115
2411
  }
2116
2412
  return `[${parsed.length} items]`;
2117
2413
  }
2118
- if (isRecord(parsed)) {
2414
+ if (isRecord2(parsed)) {
2119
2415
  for (const key of ["matched_result", "output"]) {
2120
2416
  if (parsed[key] !== null && parsed[key] !== void 0 && parsed[key] !== "") {
2121
2417
  return compactCell(parsed[key]);
@@ -2133,15 +2429,16 @@ function compactCell(value) {
2133
2429
  }
2134
2430
  return compactScalar(parsed, 120);
2135
2431
  }
2136
- function buildDatasetStats(rows, totalRows = rows.length, columns = inferColumns(rows)) {
2432
+ function buildDatasetStats(rows, totalRows = rows.length, columns = inferColumns(rows), executionStats) {
2433
+ const sanitized = sanitizeCsvProjectionInfo({ rows, columns });
2137
2434
  const columnStats = {};
2138
- for (const column of columns) {
2435
+ for (const column of sanitized.columns) {
2139
2436
  let nonEmpty = 0;
2140
2437
  let empty = 0;
2141
2438
  let sampleValue;
2142
2439
  let sampleValueType = null;
2143
2440
  const valueCounts = /* @__PURE__ */ new Map();
2144
- for (const row of rows) {
2441
+ for (const row of sanitized.rows) {
2145
2442
  const raw = row[column];
2146
2443
  const value = compactCell(raw);
2147
2444
  if (value) {
@@ -2160,6 +2457,10 @@ function buildDatasetStats(rows, totalRows = rows.length, columns = inferColumns
2160
2457
  non_empty: percentText(nonEmpty, denominator),
2161
2458
  unique: valueCounts.size
2162
2459
  };
2460
+ const rawExecutionStats = executionStats?.columnStats[column];
2461
+ if (rawExecutionStats) {
2462
+ stat3.execution = formatExecutionStats(rawExecutionStats, totalRows);
2463
+ }
2163
2464
  if (sampleValue !== void 0 && sampleValueType) {
2164
2465
  stat3.sample_value = sampleValue;
2165
2466
  stat3.sample_type = sampleValueType;
@@ -2193,10 +2494,14 @@ function writeCanonicalRowsCsv(rowsInfo, outPath) {
2193
2494
  `Run output only includes ${rowsInfo.rows.length} preview row(s) of ${rowsInfo.totalRows}; cannot export a complete CSV from this status payload yet.`
2194
2495
  );
2195
2496
  }
2497
+ const sanitized = sanitizeCsvProjectionInfo({
2498
+ rows: rowsInfo.rows,
2499
+ columns: rowsInfo.columns
2500
+ });
2196
2501
  const resolved = resolve3(outPath);
2197
2502
  writeFileSync3(
2198
2503
  resolved,
2199
- csvStringFromRows(rowsInfo.rows, rowsInfo.columns),
2504
+ csvStringFromRows(sanitized.rows, sanitized.columns),
2200
2505
  "utf-8"
2201
2506
  );
2202
2507
  return resolved;
@@ -2488,14 +2793,12 @@ function registerOrgCommands(program) {
2488
2793
  import { createHash as createHash3 } from "crypto";
2489
2794
  import {
2490
2795
  existsSync as existsSync4,
2491
- mkdirSync as mkdirSync3,
2492
2796
  readFileSync as readFileSync3,
2493
2797
  readdirSync,
2494
2798
  realpathSync,
2495
2799
  writeFileSync as writeFileSync4
2496
2800
  } from "fs";
2497
2801
  import { basename as basename3, dirname as dirname6, join as join6, resolve as resolve7 } from "path";
2498
- import { Option } from "commander";
2499
2802
 
2500
2803
  // src/plays/bundle-play-file.ts
2501
2804
  import { tmpdir as tmpdir2 } from "os";
@@ -2510,7 +2813,6 @@ import { tmpdir } from "os";
2510
2813
  import { basename, dirname as dirname3, extname, isAbsolute, join as join3, resolve as resolve4 } from "path";
2511
2814
  import { builtinModules, createRequire } from "module";
2512
2815
  import { build } from "esbuild";
2513
- import ts from "typescript";
2514
2816
 
2515
2817
  // ../shared_libs/play-runtime/backend.ts
2516
2818
  var PLAY_RUNTIME_BACKENDS = {
@@ -2579,6 +2881,14 @@ var PLAY_SOURCE_FILE_PATTERN = /\.play\.(?:[cm]?[jt]sx?)$/i;
2579
2881
  var NODE_BUILTIN_SET = new Set(
2580
2882
  builtinModules.flatMap((name) => name.startsWith("node:") ? [name, name.slice(5)] : [name, `node:${name}`])
2581
2883
  );
2884
+ function assertValidExportName(exportName) {
2885
+ if (exportName === "default") return;
2886
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(exportName)) {
2887
+ throw new Error(
2888
+ `Invalid play export name "${exportName}". Named prebuilt exports must be valid JavaScript identifiers.`
2889
+ );
2890
+ }
2891
+ }
2582
2892
  function sha256(value) {
2583
2893
  return createHash("sha256").update(value).digest("hex");
2584
2894
  }
@@ -2586,56 +2896,6 @@ function formatEsbuildMessage(message) {
2586
2896
  const location = message.location ? `${message.location.file}:${message.location.line}:${message.location.column}` : null;
2587
2897
  return location ? `${location} ${message.text}` : message.text;
2588
2898
  }
2589
- function formatTypeScriptDiagnostic(diagnostic) {
2590
- if (!diagnostic.file) {
2591
- const message2 = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n").trim();
2592
- return message2 || null;
2593
- }
2594
- const start = diagnostic.start ?? 0;
2595
- const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(start);
2596
- const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n").trim();
2597
- if (!message) {
2598
- return null;
2599
- }
2600
- return `${diagnostic.file.fileName}:${line + 1}:${character + 1} ${message}`;
2601
- }
2602
- function resolveBundledTypeRoots() {
2603
- try {
2604
- return [dirname3(dirname3(playArtifactRequire.resolve("@types/node/package.json")))];
2605
- } catch {
2606
- return [];
2607
- }
2608
- }
2609
- function typecheckPlaySource(input, adapter) {
2610
- const rootNames = Array.from(
2611
- /* @__PURE__ */ new Set([
2612
- ...input.importPolicy.localFiles,
2613
- ...input.importedPlayDependencies.map((dependency) => dependency.filePath)
2614
- ])
2615
- );
2616
- const sdkTypesPath = adapter.sdkTypesEntryFile ?? adapter.sdkEntryFile;
2617
- const program = ts.createProgram(rootNames, {
2618
- target: ts.ScriptTarget.ES2023,
2619
- // SDK source uses fetch/RequestInit/URL and node-aware config helpers.
2620
- // The play runtime import policy below still bans Node modules from play
2621
- // source for workers_edge bundles.
2622
- lib: ["lib.es2023.d.ts", "lib.dom.d.ts"],
2623
- module: ts.ModuleKind.ESNext,
2624
- moduleResolution: ts.ModuleResolutionKind.Bundler,
2625
- paths: { deepline: [sdkTypesPath] },
2626
- strict: true,
2627
- skipLibCheck: true,
2628
- noEmit: true,
2629
- esModuleInterop: true,
2630
- allowSyntheticDefaultImports: true,
2631
- allowImportingTsExtensions: true,
2632
- allowJs: true,
2633
- resolveJsonModule: true,
2634
- types: ["node"],
2635
- typeRoots: resolveBundledTypeRoots()
2636
- });
2637
- return ts.getPreEmitDiagnostics(program).map(formatTypeScriptDiagnostic).filter((message) => Boolean(message));
2638
- }
2639
2899
  function isLocalSpecifier(specifier) {
2640
2900
  return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/") || specifier.startsWith("file:");
2641
2901
  }
@@ -2659,11 +2919,8 @@ function assertWithinPlayWorkspace(input) {
2659
2919
  if (isPathInsideDirectory(input.resolvedPath, input.workspace.rootDir)) {
2660
2920
  return;
2661
2921
  }
2662
- const position = input.sourceFile.getLineAndCharacterOfPosition(
2663
- input.node.getStart(input.sourceFile)
2664
- );
2665
2922
  throw new Error(
2666
- `${input.importer}:${position.line + 1}:${position.character + 1} Local play imports must stay inside the play workspace (${input.workspace.rootDir}). Import "${input.specifier}" resolved to ${input.resolvedPath}, which crosses into app/backend code. Use the public SDK/API surface or move shared helpers into the play workspace.`
2923
+ `${input.importer}:${input.line}:${input.column} Local play imports must stay inside the play workspace (${input.workspace.rootDir}). Import "${input.specifier}" resolved to ${input.resolvedPath}, which crosses into app/backend code. Use the public SDK/API surface or move shared helpers into the play workspace.`
2667
2924
  );
2668
2925
  }
2669
2926
  function getPackageName(specifier) {
@@ -2673,72 +2930,135 @@ function getPackageName(specifier) {
2673
2930
  }
2674
2931
  return specifier.split("/")[0] ?? specifier;
2675
2932
  }
2676
- function scriptKindForFile(filePath) {
2677
- const extension = extname(filePath).toLowerCase();
2678
- switch (extension) {
2679
- case ".tsx":
2680
- return ts.ScriptKind.TSX;
2681
- case ".jsx":
2682
- return ts.ScriptKind.JSX;
2683
- case ".js":
2684
- case ".mjs":
2685
- case ".cjs":
2686
- return ts.ScriptKind.JS;
2687
- case ".json":
2688
- return ts.ScriptKind.JSON;
2689
- default:
2690
- return ts.ScriptKind.TS;
2691
- }
2692
- }
2693
2933
  function isPlaySourceFile(filePath) {
2694
2934
  return PLAY_SOURCE_FILE_PATTERN.test(filePath);
2695
2935
  }
2696
- function extractStringLiteralProperty(objectLiteral, propertyName) {
2697
- for (const property of objectLiteral.properties) {
2698
- if (!ts.isPropertyAssignment(property)) {
2936
+ function stripCommentsToSpaces(source) {
2937
+ return source.replace(/\/\*[\s\S]*?\*\//g, (match) => match.replace(/[^\n]/g, " ")).replace(
2938
+ /(^|[^:])\/\/.*$/gm,
2939
+ (match, prefix) => prefix + " ".repeat(Math.max(0, match.length - prefix.length))
2940
+ );
2941
+ }
2942
+ function lineAndColumnAt(source, index) {
2943
+ const prefix = source.slice(0, index);
2944
+ const lines = prefix.split("\n");
2945
+ return { line: lines.length, column: lines[lines.length - 1].length + 1 };
2946
+ }
2947
+ function findSourceImportReferences(sourceCode) {
2948
+ const source = stripCommentsToSpaces(sourceCode);
2949
+ const references = [];
2950
+ const addReference = (specifier, specifierIndex, kind) => {
2951
+ if (!specifier) return;
2952
+ const position = lineAndColumnAt(sourceCode, specifierIndex);
2953
+ references.push({
2954
+ specifier,
2955
+ line: position.line,
2956
+ column: position.column,
2957
+ kind
2958
+ });
2959
+ };
2960
+ const staticImportPattern = /\b(?:import|export)\s+(?!type\b)(?:[\s\S]*?\s+from\s*)?(['"])([^'"\n]+)\1/g;
2961
+ for (const match of source.matchAll(staticImportPattern)) {
2962
+ addReference(match[2], match.index + match[0].lastIndexOf(match[1]), "static");
2963
+ }
2964
+ const dynamicImportPattern = /\bimport\s*\(\s*(['"])([^'"\n]+)\1/g;
2965
+ for (const match of source.matchAll(dynamicImportPattern)) {
2966
+ addReference(match[2], match.index + match[0].lastIndexOf(match[1]), "dynamic-import");
2967
+ }
2968
+ const requirePattern = /\brequire\s*\(\s*(['"])([^'"\n]+)\1/g;
2969
+ for (const match of source.matchAll(requirePattern)) {
2970
+ addReference(match[2], match.index + match[0].lastIndexOf(match[1]), "require");
2971
+ }
2972
+ const literalDynamicImportIndexes = new Set(
2973
+ [...source.matchAll(dynamicImportPattern)].map((match) => match.index)
2974
+ );
2975
+ for (const match of source.matchAll(/\bimport\s*\(/g)) {
2976
+ if (literalDynamicImportIndexes.has(match.index)) continue;
2977
+ const position = lineAndColumnAt(sourceCode, match.index);
2978
+ throw new Error(
2979
+ `:${position.line}:${position.column} Dynamic import() is not allowed in plays. Use static imports instead.`
2980
+ );
2981
+ }
2982
+ const literalRequireIndexes = new Set(
2983
+ [...source.matchAll(requirePattern)].map((match) => match.index)
2984
+ );
2985
+ for (const match of source.matchAll(/\brequire\s*\(/g)) {
2986
+ if (literalRequireIndexes.has(match.index)) continue;
2987
+ const position = lineAndColumnAt(sourceCode, match.index);
2988
+ throw new Error(
2989
+ `:${position.line}:${position.column} Dynamic require() is not allowed in plays. Use static imports or require("literal") only.`
2990
+ );
2991
+ }
2992
+ return references.sort(
2993
+ (left, right) => left.line === right.line ? left.column - right.column : left.line - right.line
2994
+ );
2995
+ }
2996
+ function unquoteStringLiteral(literal) {
2997
+ const trimmed = literal.trim();
2998
+ const quote = trimmed[0];
2999
+ if (quote !== '"' && quote !== "'" || trimmed[trimmed.length - 1] !== quote) {
3000
+ return null;
3001
+ }
3002
+ try {
3003
+ return JSON.parse(quote === '"' ? trimmed : `"${trimmed.slice(1, -1).replace(/"/g, '\\"')}"`);
3004
+ } catch {
3005
+ return trimmed.slice(1, -1);
3006
+ }
3007
+ }
3008
+ function findMatchingBrace(source, openIndex) {
3009
+ let depth = 0;
3010
+ let quote = null;
3011
+ let escaped = false;
3012
+ for (let index = openIndex; index < source.length; index += 1) {
3013
+ const char = source[index];
3014
+ if (quote) {
3015
+ if (escaped) {
3016
+ escaped = false;
3017
+ } else if (char === "\\") {
3018
+ escaped = true;
3019
+ } else if (char === quote) {
3020
+ quote = null;
3021
+ }
2699
3022
  continue;
2700
3023
  }
2701
- const name = property.name;
2702
- const matches = ts.isIdentifier(name) && name.text === propertyName || ts.isStringLiteralLike(name) && name.text === propertyName;
2703
- if (!matches) {
3024
+ if (char === '"' || char === "'" || char === "`") {
3025
+ quote = char;
2704
3026
  continue;
2705
3027
  }
2706
- return ts.isStringLiteralLike(property.initializer) ? property.initializer.text.trim() : null;
2707
- }
2708
- return null;
2709
- }
2710
- function extractDefinedPlayName(sourceCode, filePath) {
2711
- const sourceFile = ts.createSourceFile(
2712
- filePath,
2713
- sourceCode,
2714
- ts.ScriptTarget.Latest,
2715
- true,
2716
- scriptKindForFile(filePath)
2717
- );
2718
- let detectedPlayName = null;
2719
- const visit = (node) => {
2720
- if (detectedPlayName) {
2721
- return;
2722
- }
2723
- if (ts.isCallExpression(node)) {
2724
- const expression = node.expression;
2725
- const isDefinePlayCall = ts.isIdentifier(expression) && (expression.text === "definePlay" || expression.text === "defineWorkflow") || ts.isPropertyAccessExpression(expression) && (expression.name.text === "definePlay" || expression.name.text === "defineWorkflow");
2726
- if (isDefinePlayCall) {
2727
- const firstArgument = node.arguments[0];
2728
- if (firstArgument && ts.isStringLiteralLike(firstArgument)) {
2729
- detectedPlayName = firstArgument.text.trim() || null;
2730
- return;
2731
- }
2732
- if (firstArgument && ts.isObjectLiteralExpression(firstArgument)) {
2733
- detectedPlayName = extractStringLiteralProperty(firstArgument, "id");
2734
- return;
2735
- }
3028
+ if (char === "{") depth += 1;
3029
+ if (char === "}") {
3030
+ depth -= 1;
3031
+ if (depth === 0) return index;
3032
+ }
3033
+ }
3034
+ return -1;
3035
+ }
3036
+ function extractDefinedPlayName(sourceCode, _filePath) {
3037
+ const source = stripCommentsToSpaces(sourceCode);
3038
+ const callPattern = /(?:\b[A-Za-z_$][\w$]*\s*\.\s*)?\b(?:definePlay|defineWorkflow)\s*\(/g;
3039
+ for (const match of source.matchAll(callPattern)) {
3040
+ const openParen = match.index + match[0].length - 1;
3041
+ const firstArgStart = openParen + 1;
3042
+ const firstNonSpace = source.slice(firstArgStart).search(/\S/);
3043
+ if (firstNonSpace < 0) continue;
3044
+ const argIndex = firstArgStart + firstNonSpace;
3045
+ const quote = source[argIndex];
3046
+ if (quote === '"' || quote === "'") {
3047
+ const literalMatch = source.slice(argIndex).match(/^(['"])(?:\\.|(?!\1)[\s\S])*\1/);
3048
+ const value = literalMatch ? unquoteStringLiteral(literalMatch[0]) : null;
3049
+ if (value?.trim()) return value.trim();
3050
+ }
3051
+ if (quote === "{") {
3052
+ const closeBrace = findMatchingBrace(source, argIndex);
3053
+ if (closeBrace < 0) continue;
3054
+ const objectSource = source.slice(argIndex + 1, closeBrace);
3055
+ const idMatch = objectSource.match(/(?:^|[,{\s])(?:id|['"]id['"])\s*:\s*(['"])([\s\S]*?)\1/);
3056
+ if (idMatch?.[2]?.trim()) {
3057
+ return idMatch[2].trim();
2736
3058
  }
2737
3059
  }
2738
- ts.forEachChild(node, visit);
2739
- };
2740
- visit(sourceFile);
2741
- return detectedPlayName;
3060
+ }
3061
+ return null;
2742
3062
  }
2743
3063
  function getPackageRequireCandidates(fromFile) {
2744
3064
  const candidates = [
@@ -2773,6 +3093,29 @@ function workersPlayEntryAliasPlugin(playFilePath) {
2773
3093
  }
2774
3094
  };
2775
3095
  }
3096
+ function workersNamedPlayEntryAliasPlugin(playFilePath, exportName) {
3097
+ return {
3098
+ name: "deepline-workers-named-play-entry-alias",
3099
+ setup(buildContext) {
3100
+ buildContext.onResolve(
3101
+ { filter: new RegExp(`^${WORKERS_PLAY_ENTRY_VIRTUAL}$`) },
3102
+ () => ({
3103
+ path: `${playFilePath}.${exportName}.entry.ts`,
3104
+ namespace: "deepline-named-play-entry"
3105
+ })
3106
+ );
3107
+ buildContext.onLoad(
3108
+ { filter: /.*/, namespace: "deepline-named-play-entry" },
3109
+ () => ({
3110
+ contents: `export { ${exportName} as default } from ${JSON.stringify(playFilePath)};
3111
+ `,
3112
+ loader: "ts",
3113
+ resolveDir: dirname3(playFilePath)
3114
+ })
3115
+ );
3116
+ }
3117
+ };
3118
+ }
2776
3119
  function workersNodeBuiltinStubPlugin() {
2777
3120
  const UNSUPPORTED = /* @__PURE__ */ new Set(["node:fs", "node:fs/promises", "node:os", "node:child_process"]);
2778
3121
  return {
@@ -3025,18 +3368,10 @@ async function analyzeSourceGraph(entryFile, adapter) {
3025
3368
  if (extname(absolutePath).toLowerCase() === ".json") {
3026
3369
  return;
3027
3370
  }
3028
- const sourceFile = ts.createSourceFile(
3029
- absolutePath,
3030
- sourceCode2,
3031
- ts.ScriptTarget.Latest,
3032
- true,
3033
- scriptKindForFile(absolutePath)
3034
- );
3035
- const handleSpecifier = async (specifier, node, kind) => {
3371
+ const handleSpecifier = async (specifier, line, column, kind) => {
3036
3372
  if (kind === "dynamic-import") {
3037
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3038
3373
  throw new Error(
3039
- `${absolutePath}:${position.line + 1}:${position.character + 1} Dynamic import() is not allowed in plays. Use static imports instead.`
3374
+ `${absolutePath}:${line}:${column} Dynamic import() is not allowed in plays. Use static imports instead.`
3040
3375
  );
3041
3376
  }
3042
3377
  if (NODE_BUILTIN_SET.has(specifier)) {
@@ -3050,16 +3385,15 @@ async function analyzeSourceGraph(entryFile, adapter) {
3050
3385
  specifier,
3051
3386
  resolvedPath: resolved,
3052
3387
  workspace,
3053
- sourceFile,
3054
- node
3388
+ line,
3389
+ column
3055
3390
  });
3056
3391
  if (resolved !== absoluteEntryFile && isPlaySourceFile(resolved)) {
3057
3392
  const importedSource = await readFile(resolved, "utf-8");
3058
3393
  const importedPlayName = extractDefinedPlayName(importedSource, resolved);
3059
3394
  if (!importedPlayName) {
3060
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3061
3395
  throw new Error(
3062
- `${absolutePath}:${position.line + 1}:${position.character + 1} Imported play file "${specifier}" must export definePlay(...) so it can be runtime-composed.`
3396
+ `${absolutePath}:${line}:${column} Imported play file "${specifier}" must export definePlay(...) so it can be runtime-composed.`
3063
3397
  );
3064
3398
  }
3065
3399
  importedPlayDependencies.set(resolved, {
@@ -3072,44 +3406,28 @@ async function analyzeSourceGraph(entryFile, adapter) {
3072
3406
  return;
3073
3407
  }
3074
3408
  if (specifier.includes(":")) {
3075
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3076
3409
  throw new Error(
3077
- `${absolutePath}:${position.line + 1}:${position.character + 1} Unsupported import specifier "${specifier}". Allowed imports are relative files, Node builtins, and installed packages.`
3410
+ `${absolutePath}:${line}:${column} Unsupported import specifier "${specifier}". Allowed imports are relative files, Node builtins, and installed packages.`
3078
3411
  );
3079
3412
  }
3080
3413
  const packageImport = resolvePackageImport(specifier, absolutePath, adapter);
3081
3414
  packages.set(packageImport.name, packageImport.version);
3082
3415
  };
3083
- const walk = async (node) => {
3084
- if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier && !(ts.isImportDeclaration(node) && node.importClause?.isTypeOnly || ts.isExportDeclaration(node) && node.isTypeOnly) && ts.isStringLiteralLike(node.moduleSpecifier)) {
3085
- await handleSpecifier(node.moduleSpecifier.text, node, "static");
3086
- }
3087
- if (ts.isCallExpression(node)) {
3088
- if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
3089
- if (node.arguments.length !== 1 || !ts.isStringLiteralLike(node.arguments[0])) {
3090
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3091
- throw new Error(
3092
- `${absolutePath}:${position.line + 1}:${position.character + 1} Dynamic import() is not allowed in plays. Use static imports instead.`
3093
- );
3094
- }
3095
- await handleSpecifier(node.arguments[0].text, node, "dynamic-import");
3096
- }
3097
- if (ts.isIdentifier(node.expression) && node.expression.text === "require") {
3098
- const firstArgument = node.arguments[0];
3099
- if (node.arguments.length !== 1 || !firstArgument || !ts.isStringLiteralLike(firstArgument)) {
3100
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3101
- throw new Error(
3102
- `${absolutePath}:${position.line + 1}:${position.character + 1} Dynamic require() is not allowed in plays. Use static imports or require("literal") only.`
3103
- );
3104
- }
3105
- await handleSpecifier(firstArgument.text, node, "require");
3106
- }
3416
+ try {
3417
+ for (const reference of findSourceImportReferences(sourceCode2)) {
3418
+ await handleSpecifier(
3419
+ reference.specifier,
3420
+ reference.line,
3421
+ reference.column,
3422
+ reference.kind
3423
+ );
3107
3424
  }
3108
- for (const child of node.getChildren(sourceFile)) {
3109
- await walk(child);
3425
+ } catch (error) {
3426
+ if (error instanceof Error && error.message.startsWith(":")) {
3427
+ throw new Error(`${absolutePath}${error.message}`);
3110
3428
  }
3111
- };
3112
- await walk(sourceFile);
3429
+ throw error;
3430
+ }
3113
3431
  };
3114
3432
  await visitFile(absoluteEntryFile);
3115
3433
  const sourceCode = localFiles.get(absoluteEntryFile) ?? "";
@@ -3129,6 +3447,11 @@ async function analyzeSourceGraph(entryFile, adapter) {
3129
3447
  const playName = extractDefinedPlayName(sourceCode, absoluteEntryFile);
3130
3448
  return {
3131
3449
  sourceCode,
3450
+ sourceFiles: Object.fromEntries(
3451
+ [...localFiles.entries()].sort(
3452
+ (left, right) => left[0].localeCompare(right[0])
3453
+ )
3454
+ ),
3132
3455
  sourceHash,
3133
3456
  graphHash,
3134
3457
  importPolicy: {
@@ -3192,8 +3515,7 @@ function normalizeSourceMapForRuntime(sourceMapText) {
3192
3515
  parsed.sourceRoot = void 0;
3193
3516
  return JSON.stringify(parsed);
3194
3517
  }
3195
- function getBundleSizeError(filePath, bundledCode, artifactKind) {
3196
- const bundleBytes = Buffer.byteLength(bundledCode, "utf8");
3518
+ function getBundleSizeErrorForBytes(filePath, bundleBytes, artifactKind) {
3197
3519
  if (bundleBytes > MAX_PLAY_BUNDLE_BYTES) {
3198
3520
  return `${filePath} Play bundle exceeds the 30 MiB limit (${bundleBytes} bytes > ${MAX_PLAY_BUNDLE_BYTES} bytes).`;
3199
3521
  }
@@ -3204,11 +3526,27 @@ function getBundleSizeError(filePath, bundledCode, artifactKind) {
3204
3526
  }
3205
3527
  return null;
3206
3528
  }
3207
- async function runEsbuildForCjsNode(entryFile, importedPlayDependencies, adapter) {
3529
+ function getBundleSizeError(filePath, bundledCode, artifactKind) {
3530
+ return getBundleSizeErrorForBytes(
3531
+ filePath,
3532
+ Buffer.byteLength(bundledCode, "utf8"),
3533
+ artifactKind
3534
+ );
3535
+ }
3536
+ async function runEsbuildForCjsNode(entryFile, importedPlayDependencies, adapter, exportName) {
3208
3537
  const sdkAliasPlugin = localSdkAliasPlugin(adapter);
3209
3538
  const playProxyPlugin = importedPlayProxyPlugin(importedPlayDependencies);
3539
+ const namedExportShim = exportName === "default" ? null : `export { ${exportName} as default } from ${JSON.stringify(entryFile)};
3540
+ `;
3210
3541
  const result = await build({
3211
- entryPoints: [entryFile],
3542
+ ...namedExportShim ? {
3543
+ stdin: {
3544
+ contents: namedExportShim,
3545
+ resolveDir: dirname3(entryFile),
3546
+ sourcefile: `${basename(entryFile)}.${exportName}.entry.ts`,
3547
+ loader: "ts"
3548
+ }
3549
+ } : { entryPoints: [entryFile] },
3212
3550
  absWorkingDir: adapter.projectRoot,
3213
3551
  bundle: true,
3214
3552
  format: "cjs",
@@ -3236,10 +3574,10 @@ async function runEsbuildForCjsNode(entryFile, importedPlayDependencies, adapter
3236
3574
  outputExtension: "cjs"
3237
3575
  };
3238
3576
  }
3239
- async function runEsbuildForEsmWorkers(playEntryFile, importedPlayDependencies, adapter) {
3577
+ async function runEsbuildForEsmWorkers(playEntryFile, importedPlayDependencies, adapter, exportName) {
3240
3578
  const sdkAliasPlugin = localSdkAliasPlugin(adapter, { workersRuntime: true });
3241
3579
  const playProxyPlugin = importedPlayProxyPlugin(importedPlayDependencies);
3242
- const playEntryAlias = workersPlayEntryAliasPlugin(playEntryFile);
3580
+ const playEntryAlias = exportName === "default" ? workersPlayEntryAliasPlugin(playEntryFile) : workersNamedPlayEntryAliasPlugin(playEntryFile, exportName);
3243
3581
  const result = await build({
3244
3582
  // Entry is the Workers harness; it imports the play via the virtual
3245
3583
  // `deepline-play-entry` alias resolved by workersPlayEntryAliasPlugin.
@@ -3310,10 +3648,16 @@ async function runEsbuildForEsmWorkers(playEntryFile, importedPlayDependencies,
3310
3648
  async function bundlePlayFile(filePath, options) {
3311
3649
  const adapter = options.adapter;
3312
3650
  const target = options.target ?? PLAY_ARTIFACT_KINDS.cjsNode20;
3651
+ const exportName = options.exportName?.trim() || "default";
3652
+ assertValidExportName(exportName);
3313
3653
  const absolutePath = await normalizeLocalPath(filePath);
3314
3654
  adapter.warnAboutNonDevelopmentBundling?.(absolutePath);
3315
3655
  try {
3316
3656
  const analysis = await analyzeSourceGraph(absolutePath, adapter);
3657
+ analysis.graphHash = sha256(
3658
+ `${analysis.graphHash}
3659
+ entry-export:${exportName}`
3660
+ );
3317
3661
  if (target === PLAY_ARTIFACT_KINDS.esmWorkers) {
3318
3662
  const harnessFingerprint = await computeWorkersHarnessFingerprintWithAdapter(adapter);
3319
3663
  analysis.graphHash = sha256(
@@ -3321,6 +3665,23 @@ async function bundlePlayFile(filePath, options) {
3321
3665
  workers-harness:${harnessFingerprint}`
3322
3666
  );
3323
3667
  }
3668
+ const typecheckErrors = [
3669
+ ...await adapter.typecheckPlaySource?.({
3670
+ sourceCode: analysis.sourceCode,
3671
+ sourcePath: absolutePath,
3672
+ importedFilePaths: [
3673
+ ...analysis.importPolicy.localFiles,
3674
+ ...analysis.importedPlayDependencies.map((dependency) => dependency.filePath)
3675
+ ]
3676
+ }) ?? []
3677
+ ];
3678
+ if (typecheckErrors.length > 0) {
3679
+ return {
3680
+ success: false,
3681
+ filePath: absolutePath,
3682
+ errors: typecheckErrors
3683
+ };
3684
+ }
3324
3685
  const cachedArtifact = await readArtifactCache(analysis.graphHash, target, adapter);
3325
3686
  const discoveredFiles = await adapter.discoverPackagedLocalFiles(absolutePath);
3326
3687
  if (cachedArtifact) {
@@ -3340,6 +3701,7 @@ workers-harness:${harnessFingerprint}`
3340
3701
  success: true,
3341
3702
  artifact: { ...cachedArtifact, cacheHit: true },
3342
3703
  sourceCode: analysis.sourceCode,
3704
+ sourceFiles: analysis.sourceFiles,
3343
3705
  filePath: absolutePath,
3344
3706
  playName: analysis.playName,
3345
3707
  packagedFiles: discoveredFiles.files,
@@ -3347,25 +3709,7 @@ workers-harness:${harnessFingerprint}`
3347
3709
  importedPlayDependencies: analysis.importedPlayDependencies
3348
3710
  };
3349
3711
  }
3350
- const typecheckErrors = [
3351
- ...adapter.typecheckSdkTypes === false ? [] : typecheckPlaySource(analysis, adapter),
3352
- ...await adapter.typecheckPlaySource?.({
3353
- sourceCode: analysis.sourceCode,
3354
- sourcePath: absolutePath,
3355
- importedFilePaths: [
3356
- ...analysis.importPolicy.localFiles,
3357
- ...analysis.importedPlayDependencies.map((dependency) => dependency.filePath)
3358
- ]
3359
- }) ?? []
3360
- ];
3361
- if (typecheckErrors.length > 0) {
3362
- return {
3363
- success: false,
3364
- filePath: absolutePath,
3365
- errors: typecheckErrors
3366
- };
3367
- }
3368
- const buildOutcome = target === PLAY_ARTIFACT_KINDS.esmWorkers ? await runEsbuildForEsmWorkers(absolutePath, analysis.importedPlayDependencies, adapter) : await runEsbuildForCjsNode(absolutePath, analysis.importedPlayDependencies, adapter);
3712
+ const buildOutcome = target === PLAY_ARTIFACT_KINDS.esmWorkers ? await runEsbuildForEsmWorkers(absolutePath, analysis.importedPlayDependencies, adapter, exportName) : await runEsbuildForCjsNode(absolutePath, analysis.importedPlayDependencies, adapter, exportName);
3369
3713
  if (Array.isArray(buildOutcome)) {
3370
3714
  return {
3371
3715
  success: false,
@@ -3375,7 +3719,8 @@ workers-harness:${harnessFingerprint}`
3375
3719
  }
3376
3720
  const { bundledCode, sourceMapText, outputExtension } = buildOutcome;
3377
3721
  const normalizedSourceMap = normalizeSourceMapForRuntime(sourceMapText);
3378
- const virtualFilename = `/virtual/deepline-plays/${analysis.graphHash}/${basename(absolutePath).replace(/\.[^.]+$/, "")}.${outputExtension}`;
3722
+ const virtualBaseName = exportName === "default" ? basename(absolutePath).replace(/\.[^.]+$/, "") : `${basename(absolutePath).replace(/\.[^.]+$/, "")}.${exportName}`;
3723
+ const virtualFilename = `/virtual/deepline-plays/${analysis.graphHash}/${virtualBaseName}.${outputExtension}`;
3379
3724
  const executableCode = `${bundledCode}
3380
3725
  //# sourceMappingURL=${basename(virtualFilename)}.map
3381
3726
  `;
@@ -3413,6 +3758,7 @@ workers-harness:${harnessFingerprint}`
3413
3758
  success: true,
3414
3759
  artifact,
3415
3760
  sourceCode: analysis.sourceCode,
3761
+ sourceFiles: analysis.sourceFiles,
3416
3762
  filePath: absolutePath,
3417
3763
  playName: analysis.playName,
3418
3764
  packagedFiles: discoveredFiles.files,
@@ -3494,7 +3840,6 @@ function resolveExecutionProfile(override) {
3494
3840
  import { createHash as createHash2 } from "crypto";
3495
3841
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
3496
3842
  import { basename as basename2, dirname as dirname4, extname as extname2, isAbsolute as isAbsolute2, join as join4, relative, resolve as resolve5 } from "path";
3497
- import ts2 from "typescript";
3498
3843
  var SOURCE_EXTENSIONS2 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".json"];
3499
3844
  function sha2562(buffer) {
3500
3845
  return createHash2("sha256").update(buffer).digest("hex");
@@ -3506,94 +3851,181 @@ function contentTypeForFile(filePath) {
3506
3851
  if (extension === ".txt") return "text/plain";
3507
3852
  return "application/octet-stream";
3508
3853
  }
3509
- function isCtxCsvCall(node) {
3510
- if (!ts2.isPropertyAccessExpression(node.expression)) {
3511
- return false;
3512
- }
3513
- const target = node.expression.expression;
3514
- return ts2.isIdentifier(target) && (target.text === "ctx" || target.text.endsWith("Ctx")) && node.expression.name.text === "csv";
3515
- }
3516
- function extractSourceFragment(source, node) {
3517
- return source.slice(node.getStart(), node.getEnd()).trim();
3518
- }
3519
- function referencesInputIdentifier(node) {
3520
- if (ts2.isIdentifier(node) && node.text === "input") {
3521
- return true;
3522
- }
3523
- return node.getChildren().some((child) => referencesInputIdentifier(child));
3854
+ function stripCommentsToSpaces2(source) {
3855
+ return source.replace(/\/\*[\s\S]*?\*\//g, (match) => match.replace(/[^\n]/g, " ")).replace(
3856
+ /(^|[^:])\/\/.*$/gm,
3857
+ (match, prefix) => prefix + " ".repeat(Math.max(0, match.length - prefix.length))
3858
+ );
3524
3859
  }
3525
- function isRuntimeInputExpression(node) {
3526
- if (ts2.isPropertyAccessExpression(node)) {
3527
- return ts2.isIdentifier(node.expression) && node.expression.text === "input";
3528
- }
3529
- if (ts2.isElementAccessExpression(node)) {
3530
- return ts2.isIdentifier(node.expression) && node.expression.text === "input";
3531
- }
3532
- if (ts2.isIdentifier(node)) {
3533
- return node.text === "input";
3534
- }
3535
- if (ts2.isParenthesizedExpression(node)) {
3536
- return isRuntimeInputExpression(node.expression);
3860
+ function unquoteStringLiteral2(literal) {
3861
+ const trimmed = literal.trim();
3862
+ const quote = trimmed[0];
3863
+ if (quote !== '"' && quote !== "'" || trimmed[trimmed.length - 1] !== quote) {
3864
+ return null;
3537
3865
  }
3538
- if (ts2.isBinaryExpression(node) && (node.operatorToken.kind === ts2.SyntaxKind.QuestionQuestionToken || node.operatorToken.kind === ts2.SyntaxKind.BarBarToken)) {
3539
- return isRuntimeInputExpression(node.left) || isRuntimeInputExpression(node.right);
3866
+ try {
3867
+ return JSON.parse(quote === '"' ? trimmed : `"${trimmed.slice(1, -1).replace(/"/g, '\\"')}"`);
3868
+ } catch {
3869
+ return trimmed.slice(1, -1);
3540
3870
  }
3541
- if (ts2.isConditionalExpression(node)) {
3542
- return isRuntimeInputExpression(node.condition) || isRuntimeInputExpression(node.whenTrue) || isRuntimeInputExpression(node.whenFalse);
3871
+ }
3872
+ function splitTopLevelPlus(expression) {
3873
+ const parts = [];
3874
+ let start = 0;
3875
+ let depth = 0;
3876
+ let quote = null;
3877
+ let escaped = false;
3878
+ for (let index = 0; index < expression.length; index += 1) {
3879
+ const char = expression[index];
3880
+ if (quote) {
3881
+ if (escaped) {
3882
+ escaped = false;
3883
+ } else if (char === "\\") {
3884
+ escaped = true;
3885
+ } else if (char === quote) {
3886
+ quote = null;
3887
+ }
3888
+ continue;
3889
+ }
3890
+ if (char === '"' || char === "'" || char === "`") {
3891
+ quote = char;
3892
+ continue;
3893
+ }
3894
+ if (char === "(" || char === "[" || char === "{") depth += 1;
3895
+ if (char === ")" || char === "]" || char === "}") depth -= 1;
3896
+ if (char === "+" && depth === 0) {
3897
+ parts.push(expression.slice(start, index));
3898
+ start = index + 1;
3899
+ }
3543
3900
  }
3544
- return referencesInputIdentifier(node);
3901
+ if (parts.length === 0) return null;
3902
+ parts.push(expression.slice(start));
3903
+ return parts;
3545
3904
  }
3546
- function resolveStringExpression(node, constants) {
3547
- if (ts2.isStringLiteralLike(node) || ts2.isNoSubstitutionTemplateLiteral(node)) {
3548
- return node.text;
3905
+ function stripOuterParens(expression) {
3906
+ let value = expression.trim();
3907
+ while (value.startsWith("(") && value.endsWith(")")) {
3908
+ value = value.slice(1, -1).trim();
3549
3909
  }
3550
- if (ts2.isParenthesizedExpression(node)) {
3551
- return resolveStringExpression(node.expression, constants);
3910
+ return value;
3911
+ }
3912
+ function isRuntimeInputExpression(expression) {
3913
+ return /(^|[^\w$])input([^\w$]|$)/.test(expression);
3914
+ }
3915
+ function resolveStringExpression(expression, constants) {
3916
+ const value = stripOuterParens(expression);
3917
+ if (/^(['"])(?:\\.|(?!\1)[\s\S])*\1$/.test(value)) {
3918
+ return unquoteStringLiteral2(value);
3552
3919
  }
3553
- if (ts2.isIdentifier(node)) {
3554
- return constants.get(node.text) ?? null;
3920
+ if (/^`(?:\\.|[^`$]|\$(?!\{))*`$/.test(value)) {
3921
+ return value.slice(1, -1);
3555
3922
  }
3556
- if (ts2.isTemplateExpression(node)) {
3557
- let value = node.head.text;
3558
- for (const span of node.templateSpans) {
3559
- const resolved = resolveStringExpression(span.expression, constants);
3560
- if (resolved == null) {
3561
- return null;
3562
- }
3563
- value += resolved + span.literal.text;
3564
- }
3565
- return value;
3923
+ if (/^[A-Za-z_$][\w$]*$/.test(value)) {
3924
+ return constants.get(value) ?? null;
3566
3925
  }
3567
- if (ts2.isBinaryExpression(node) && node.operatorToken.kind === ts2.SyntaxKind.PlusToken) {
3568
- const left = resolveStringExpression(node.left, constants);
3569
- const right = resolveStringExpression(node.right, constants);
3570
- if (left == null || right == null) {
3571
- return null;
3572
- }
3573
- return left + right;
3926
+ const parts = splitTopLevelPlus(value);
3927
+ if (parts) {
3928
+ const resolved = parts.map((part) => resolveStringExpression(part, constants));
3929
+ return resolved.every((part) => part != null) ? resolved.join("") : null;
3574
3930
  }
3575
3931
  return null;
3576
3932
  }
3577
- function collectTopLevelStringConstants(sourceFile) {
3933
+ function collectTopLevelStringConstants(sourceCode) {
3578
3934
  const constants = /* @__PURE__ */ new Map();
3579
- for (const statement of sourceFile.statements) {
3580
- if (!ts2.isVariableStatement(statement)) {
3935
+ const source = stripCommentsToSpaces2(sourceCode);
3936
+ for (const match of source.matchAll(/(?:^|\n)\s*const\s+([A-Za-z_$][\w$]*)\s*=\s*([^;\n]+)/g)) {
3937
+ const resolved = resolveStringExpression(match[2], constants);
3938
+ if (resolved != null) {
3939
+ constants.set(match[1], resolved);
3940
+ }
3941
+ }
3942
+ return constants;
3943
+ }
3944
+ function findMatchingGenericEnd(source, openIndex) {
3945
+ let depth = 0;
3946
+ let quote = null;
3947
+ let escaped = false;
3948
+ for (let index = openIndex; index < source.length; index += 1) {
3949
+ const char = source[index];
3950
+ if (quote) {
3951
+ if (escaped) {
3952
+ escaped = false;
3953
+ } else if (char === "\\") {
3954
+ escaped = true;
3955
+ } else if (char === quote) {
3956
+ quote = null;
3957
+ }
3581
3958
  continue;
3582
3959
  }
3583
- if (!(statement.declarationList.flags & ts2.NodeFlags.Const)) {
3960
+ if (char === '"' || char === "'" || char === "`") {
3961
+ quote = char;
3584
3962
  continue;
3585
3963
  }
3586
- for (const declaration of statement.declarationList.declarations) {
3587
- if (!ts2.isIdentifier(declaration.name) || !declaration.initializer) {
3588
- continue;
3589
- }
3590
- const resolved = resolveStringExpression(declaration.initializer, constants);
3591
- if (resolved != null) {
3592
- constants.set(declaration.name.text, resolved);
3964
+ if (char === "<") depth += 1;
3965
+ if (char === ">") {
3966
+ depth -= 1;
3967
+ if (depth === 0) return index;
3968
+ }
3969
+ }
3970
+ return -1;
3971
+ }
3972
+ function findCallOpenParen(source, afterCsvIndex) {
3973
+ let index = afterCsvIndex;
3974
+ while (/\s/.test(source[index] ?? "")) index += 1;
3975
+ if (source[index] === "<") {
3976
+ const genericEnd = findMatchingGenericEnd(source, index);
3977
+ if (genericEnd < 0) return -1;
3978
+ index = genericEnd + 1;
3979
+ while (/\s/.test(source[index] ?? "")) index += 1;
3980
+ }
3981
+ return source[index] === "(" ? index : -1;
3982
+ }
3983
+ function firstCallArgument(source, openParen) {
3984
+ let depth = 0;
3985
+ let quote = null;
3986
+ let escaped = false;
3987
+ const start = openParen + 1;
3988
+ for (let index = start; index < source.length; index += 1) {
3989
+ const char = source[index];
3990
+ if (quote) {
3991
+ if (escaped) {
3992
+ escaped = false;
3993
+ } else if (char === "\\") {
3994
+ escaped = true;
3995
+ } else if (char === quote) {
3996
+ quote = null;
3593
3997
  }
3998
+ continue;
3594
3999
  }
4000
+ if (char === '"' || char === "'" || char === "`") {
4001
+ quote = char;
4002
+ continue;
4003
+ }
4004
+ if (char === "(" || char === "[" || char === "{") depth += 1;
4005
+ if (char === ")" && depth === 0) {
4006
+ const text = source.slice(start, index).trim();
4007
+ return text ? { text, start, end: index } : null;
4008
+ }
4009
+ if (char === "," && depth === 0) {
4010
+ const text = source.slice(start, index).trim();
4011
+ return text ? { text, start, end: index } : null;
4012
+ }
4013
+ if (char === ")" || char === "]" || char === "}") depth -= 1;
3595
4014
  }
3596
- return constants;
4015
+ return null;
4016
+ }
4017
+ function localImportSpecifiers(sourceCode) {
4018
+ const source = stripCommentsToSpaces2(sourceCode);
4019
+ const specifiers = [];
4020
+ for (const match of source.matchAll(
4021
+ /\b(?:import|export)\s+(?!type\b)(?:[\s\S]*?\s+from\s*)?['"]([^'"]+)['"]/g
4022
+ )) {
4023
+ if (match[1]?.startsWith(".")) specifiers.push(match[1]);
4024
+ }
4025
+ for (const match of source.matchAll(/\brequire\s*\(\s*(['"])(\.[^'"]*)\1\s*\)/g)) {
4026
+ specifiers.push(match[2]);
4027
+ }
4028
+ return specifiers;
3597
4029
  }
3598
4030
  async function fileExists2(filePath) {
3599
4031
  try {
@@ -3638,69 +4070,60 @@ async function discoverPackagedLocalFiles(entryFile) {
3638
4070
  }
3639
4071
  visitedFiles.add(absolutePath);
3640
4072
  const sourceCode = await readFile2(absolutePath, "utf-8");
3641
- const sourceFile = ts2.createSourceFile(
3642
- absolutePath,
3643
- sourceCode,
3644
- ts2.ScriptTarget.Latest,
3645
- true,
3646
- ts2.ScriptKind.TS
3647
- );
3648
- const constants = collectTopLevelStringConstants(sourceFile);
4073
+ const scanSource = stripCommentsToSpaces2(sourceCode);
4074
+ const constants = collectTopLevelStringConstants(sourceCode);
3649
4075
  const childVisits = [];
3650
- const visitNode = async (node) => {
3651
- if (ts2.isCallExpression(node) && isCtxCsvCall(node)) {
3652
- const argument = node.arguments[0];
3653
- if (!argument) {
4076
+ for (const match of scanSource.matchAll(/\b([A-Za-z_$][\w$]*)\s*\.\s*csv\b/g)) {
4077
+ const target = match[1];
4078
+ if (target !== "ctx" && !target.endsWith("Ctx")) {
4079
+ continue;
4080
+ }
4081
+ const openParen = findCallOpenParen(scanSource, match.index + match[0].length);
4082
+ if (openParen < 0) {
4083
+ continue;
4084
+ }
4085
+ const argument = firstCallArgument(scanSource, openParen);
4086
+ if (!argument) {
4087
+ unresolved.push({
4088
+ sourceFragment: "ctx.csv()",
4089
+ message: "ctx.csv() requires a file path string or input reference."
4090
+ });
4091
+ } else if (!isRuntimeInputExpression(argument.text)) {
4092
+ const resolvedPath = resolveStringExpression(argument.text, constants);
4093
+ if (resolvedPath == null) {
3654
4094
  unresolved.push({
3655
- sourceFragment: "ctx.csv()",
3656
- message: "ctx.csv() requires a file path string or input reference."
4095
+ sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
4096
+ message: "Could not resolve this ctx.csv(...) path at submit time. Use a string literal, a top-level const string, or pass a runtime input like input.file."
3657
4097
  });
3658
- } else if (!isRuntimeInputExpression(argument)) {
3659
- const resolvedPath = resolveStringExpression(argument, constants);
3660
- if (resolvedPath == null) {
4098
+ } else {
4099
+ const absoluteCsvPath = resolve5(dirname4(absolutePath), resolvedPath);
4100
+ if (isAbsolute2(resolvedPath) || !isPathInsideDirectory2(absoluteCsvPath, packagingRoot)) {
3661
4101
  unresolved.push({
3662
- sourceFragment: extractSourceFragment(sourceCode, argument),
3663
- message: "Could not resolve this ctx.csv(...) path at submit time. Use a string literal, a top-level const string, or pass a runtime input like input.file."
3664
- });
3665
- } else {
3666
- const absoluteCsvPath = resolve5(dirname4(absolutePath), resolvedPath);
3667
- if (isAbsolute2(resolvedPath) || !isPathInsideDirectory2(absoluteCsvPath, packagingRoot)) {
3668
- unresolved.push({
3669
- sourceFragment: extractSourceFragment(sourceCode, argument),
3670
- message: "ctx.csv(...) packaged file paths must be relative paths inside the play directory. Pass external files at runtime with input.file instead."
3671
- });
3672
- return;
3673
- }
3674
- const buffer = await readFile2(absoluteCsvPath);
3675
- const stats = await stat2(absoluteCsvPath);
3676
- files.set(absoluteCsvPath, {
3677
- sourceFragment: extractSourceFragment(sourceCode, argument),
3678
- logicalPath: resolvedPath,
3679
- absolutePath: absoluteCsvPath,
3680
- bytes: stats.size,
3681
- contentHash: sha2562(buffer),
3682
- contentType: contentTypeForFile(absoluteCsvPath)
4102
+ sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
4103
+ message: "ctx.csv(...) packaged file paths must be relative paths inside the play directory. Pass external files at runtime with input.file instead."
3683
4104
  });
4105
+ continue;
3684
4106
  }
4107
+ const buffer = await readFile2(absoluteCsvPath);
4108
+ const stats = await stat2(absoluteCsvPath);
4109
+ files.set(absoluteCsvPath, {
4110
+ sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
4111
+ logicalPath: resolvedPath,
4112
+ absolutePath: absoluteCsvPath,
4113
+ bytes: stats.size,
4114
+ contentHash: sha2562(buffer),
4115
+ contentType: contentTypeForFile(absoluteCsvPath)
4116
+ });
3685
4117
  }
3686
4118
  }
3687
- if (ts2.isImportDeclaration(node) && !node.importClause?.isTypeOnly && ts2.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text.startsWith(".")) {
3688
- childVisits.push(
3689
- resolveLocalImport2(absolutePath, node.moduleSpecifier.text).then(
3690
- (resolvedImport) => visitSourceFile(resolvedImport)
3691
- )
3692
- );
3693
- }
3694
- if (ts2.isCallExpression(node) && ts2.isIdentifier(node.expression) && node.expression.text === "require" && node.arguments.length === 1 && ts2.isStringLiteral(node.arguments[0]) && node.arguments[0].text.startsWith(".")) {
3695
- childVisits.push(
3696
- resolveLocalImport2(absolutePath, node.arguments[0].text).then(
3697
- (resolvedImport) => visitSourceFile(resolvedImport)
3698
- )
3699
- );
3700
- }
3701
- await Promise.all(node.getChildren(sourceFile).map((child) => visitNode(child)));
3702
- };
3703
- await visitNode(sourceFile);
4119
+ }
4120
+ for (const specifier of localImportSpecifiers(sourceCode)) {
4121
+ childVisits.push(
4122
+ resolveLocalImport2(absolutePath, specifier).then(
4123
+ (resolvedImport) => visitSourceFile(resolvedImport)
4124
+ )
4125
+ );
4126
+ }
3704
4127
  await Promise.all(childVisits);
3705
4128
  };
3706
4129
  await visitSourceFile(absoluteEntryFile);
@@ -3711,7 +4134,7 @@ async function discoverPackagedLocalFiles(entryFile) {
3711
4134
  }
3712
4135
 
3713
4136
  // src/plays/bundle-play-file.ts
3714
- var PLAY_BUNDLE_CACHE_VERSION2 = 24;
4137
+ var PLAY_BUNDLE_CACHE_VERSION2 = 26;
3715
4138
  var MODULE_DIR = dirname5(fileURLToPath(import.meta.url));
3716
4139
  var SDK_PACKAGE_ROOT = resolve6(MODULE_DIR, "..", "..");
3717
4140
  var SOURCE_REPO_ROOT = resolve6(SDK_PACKAGE_ROOT, "..");
@@ -3726,7 +4149,7 @@ var PROJECT_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? SOURCE_REPO_ROOT : HAS_PACKAGED
3726
4149
  var SDK_SOURCE_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? resolve6(SOURCE_REPO_ROOT, "sdk", "src") : HAS_PACKAGED_BUNDLING_SOURCES ? resolve6(PACKAGED_REPO_ROOT, "sdk", "src") : resolve6(SDK_PACKAGE_ROOT, "src");
3727
4150
  var SDK_PACKAGE_JSON = resolve6(SDK_PACKAGE_ROOT, "package.json");
3728
4151
  var SDK_ENTRY_FILE = resolve6(SDK_SOURCE_ROOT, "index.ts");
3729
- var SDK_TYPES_ENTRY_FILE = resolve6(SDK_PACKAGE_ROOT, "dist", "index.d.ts");
4152
+ var SDK_TYPES_ENTRY_FILE = HAS_SOURCE_BUNDLING_SOURCES ? SDK_ENTRY_FILE : resolve6(SDK_PACKAGE_ROOT, "dist", "index.d.ts");
3730
4153
  var SDK_WORKERS_ENTRY_FILE = resolve6(SDK_SOURCE_ROOT, "worker-play-entry.ts");
3731
4154
  var WORKERS_HARNESS_ENTRY_FILE = resolve6(PROJECT_ROOT, "apps", "play-runner-workers", "src", "entry.ts");
3732
4155
  var WORKERS_HARNESS_FILES_DIR = resolve6(PROJECT_ROOT, "apps", "play-runner-workers", "src");
@@ -3758,7 +4181,7 @@ function createSdkPlayBundlingAdapter() {
3758
4181
  sdkSourceRoot: SDK_SOURCE_ROOT,
3759
4182
  sdkPackageJson: SDK_PACKAGE_JSON,
3760
4183
  sdkEntryFile: SDK_ENTRY_FILE,
3761
- sdkTypesEntryFile: existsSync3(SDK_TYPES_ENTRY_FILE) ? SDK_TYPES_ENTRY_FILE : SDK_ENTRY_FILE,
4184
+ sdkTypesEntryFile: HAS_SOURCE_BUNDLING_SOURCES || !existsSync3(SDK_TYPES_ENTRY_FILE) ? SDK_ENTRY_FILE : SDK_TYPES_ENTRY_FILE,
3762
4185
  sdkWorkersEntryFile: SDK_WORKERS_ENTRY_FILE,
3763
4186
  workersHarnessEntryFile: WORKERS_HARNESS_ENTRY_FILE,
3764
4187
  workersHarnessFilesDir: WORKERS_HARNESS_FILES_DIR,
@@ -3769,6 +4192,7 @@ function createSdkPlayBundlingAdapter() {
3769
4192
  async function bundlePlayFile2(filePath, options = {}) {
3770
4193
  return bundlePlayFile(filePath, {
3771
4194
  target: options.target ?? defaultPlayBundleTarget(),
4195
+ exportName: options.exportName,
3772
4196
  adapter: createSdkPlayBundlingAdapter()
3773
4197
  });
3774
4198
  }
@@ -3918,54 +4342,6 @@ function createCliProgress(enabled) {
3918
4342
  return progress;
3919
4343
  }
3920
4344
 
3921
- // src/cli/trace.ts
3922
- var cliTraceStartedAt = Date.now();
3923
- function isTruthyEnv(value) {
3924
- return value === "1" || value === "true" || value === "yes";
3925
- }
3926
- function isCliTraceEnabled() {
3927
- return isTruthyEnv(process.env.DEEPLINE_CLI_TRACE);
3928
- }
3929
- function recordCliTrace(event) {
3930
- if (!isCliTraceEnabled()) {
3931
- return;
3932
- }
3933
- const now = Date.now();
3934
- const payload = {
3935
- ts: now,
3936
- source: "cli",
3937
- sinceStartMs: now - cliTraceStartedAt,
3938
- ...event
3939
- };
3940
- process.stderr.write(`[cli-trace] ${JSON.stringify(payload)}
3941
- `);
3942
- }
3943
- async function traceCliSpan(phase, fields, run) {
3944
- if (!isCliTraceEnabled()) {
3945
- return run();
3946
- }
3947
- const startedAt = Date.now();
3948
- try {
3949
- const result = await run();
3950
- recordCliTrace({
3951
- phase,
3952
- ms: Date.now() - startedAt,
3953
- ok: true,
3954
- ...fields
3955
- });
3956
- return result;
3957
- } catch (error) {
3958
- recordCliTrace({
3959
- phase,
3960
- ms: Date.now() - startedAt,
3961
- ok: false,
3962
- error: error instanceof Error ? error.message : String(error),
3963
- ...fields
3964
- });
3965
- throw error;
3966
- }
3967
- }
3968
-
3969
4345
  // src/cli/commands/play.ts
3970
4346
  function parseReferencedPlayTarget(target) {
3971
4347
  const trimmed = target.trim();
@@ -4013,67 +4389,6 @@ function defaultMaterializedPlayPath(reference) {
4013
4389
  const safeName = playName.trim().toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
4014
4390
  return resolve7(`${safeName || "play"}.play.ts`);
4015
4391
  }
4016
- function sanitizeGeneratedPlayName(value) {
4017
- return value.trim().toLowerCase().replace(/^prebuilt\//, "").replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "play";
4018
- }
4019
- function buildGeneratedCsvWrapperSource(input) {
4020
- return `import { definePlay } from 'deepline';
4021
-
4022
- export default definePlay(
4023
- ${JSON.stringify(input.wrapperName)},
4024
- async (ctx, input: Record<string, unknown> & { file: string }) => {
4025
- const rows = await ctx.csv<Record<string, unknown>>(input.file);
4026
- const constants = Object.fromEntries(
4027
- Object.entries(input).filter(([key]) => key !== 'file'),
4028
- );
4029
-
4030
- const mappedRows = await ctx
4031
- .map('csv_rows', rows, {
4032
- key: (row, index) =>
4033
- String(
4034
- row.id ??
4035
- row.lead_id ??
4036
- row.email ??
4037
- row.linkedin_url ??
4038
- row.domain ??
4039
- index,
4040
- ),
4041
- })
4042
- .step('result', (row, rowCtx) =>
4043
- rowCtx.runPlay(
4044
- 'row_play',
4045
- ${JSON.stringify(input.playRef)},
4046
- {
4047
- ...constants,
4048
- ...row,
4049
- },
4050
- {
4051
- description: 'Run the source play for this CSV row.',
4052
- },
4053
- ),
4054
- )
4055
- .run({ description: 'Run the source play once per CSV row.' });
4056
-
4057
- return { rows: mappedRows };
4058
- },
4059
- );
4060
- `;
4061
- }
4062
- function writeGeneratedCsvWrapperPlay(playRef) {
4063
- const baseName = sanitizeGeneratedPlayName(
4064
- parseReferencedPlayTarget(playRef).unqualifiedPlayName
4065
- );
4066
- const wrapperName = `${baseName}-csv`;
4067
- const outputDir = resolve7(".deepline", "generated");
4068
- const outputPath = join6(outputDir, `${wrapperName}.play.ts`);
4069
- mkdirSync3(outputDir, { recursive: true });
4070
- writeFileSync4(
4071
- outputPath,
4072
- buildGeneratedCsvWrapperSource({ wrapperName, playRef }),
4073
- "utf-8"
4074
- );
4075
- return outputPath;
4076
- }
4077
4392
  function materializeRemotePlaySource(input) {
4078
4393
  if (isFileTarget(input.target)) {
4079
4394
  return null;
@@ -4103,13 +4418,15 @@ function formatLoadedPlayMessage(materializedFile) {
4103
4418
  return `Loaded play here: ${materializedFile.path}`;
4104
4419
  }
4105
4420
  function buildReadonlyPrebuiltPlayError(reference) {
4421
+ const localName = reference.split("/").slice(1).join("/") || "custom-play";
4106
4422
  return new Error(
4107
4423
  `Cannot edit or push ${reference} because Deepline prebuilt plays are read-only.
4108
4424
  To make your own version:
4109
- 1. Copy the source into a new local file.
4110
- 2. Change definePlay('${reference.split("/").slice(1).join("/")}', ...) to a new play name you own.
4111
- 3. Run: deepline plays publish <your-file.play.ts>
4112
- 4. Your play will then live under your workspace namespace.`
4425
+ 1. Run: deepline plays get ${reference} --source --out ./${localName}.play.ts
4426
+ 2. Change definePlay('${localName}', ...) to a new play name you own.
4427
+ 3. Run: deepline plays check ./${localName}.play.ts
4428
+ 4. Run: deepline plays publish ./${localName}.play.ts
4429
+ 5. Your play will then live under your workspace namespace.`
4113
4430
  );
4114
4431
  }
4115
4432
  async function ensureEditableRemotePlay(client, target) {
@@ -4139,7 +4456,10 @@ function looksLikeFilePath(target) {
4139
4456
  if (target.trim().toLowerCase().startsWith("prebuilt/")) {
4140
4457
  return false;
4141
4458
  }
4142
- return target.includes("/") || target.includes("\\") || /\.(ts|js|mjs|play\.ts)$/.test(target);
4459
+ if (target.startsWith("./") || target.startsWith("../") || target.startsWith("/") || target.startsWith("~/")) {
4460
+ return true;
4461
+ }
4462
+ return target.includes("\\") || /\.(ts|js|mjs|play\.ts)$/.test(target);
4143
4463
  }
4144
4464
  function parsePositiveInteger2(value, flagName) {
4145
4465
  const parsed = Number.parseInt(value, 10);
@@ -4156,6 +4476,142 @@ function parseJsonInput(raw) {
4156
4476
  }
4157
4477
  return parsed;
4158
4478
  }
4479
+ function parseInputFieldFlag(rawFlag, nextArg) {
4480
+ const flag = rawFlag.slice(2);
4481
+ const equalsIndex = flag.indexOf("=");
4482
+ if (equalsIndex > 0) {
4483
+ const path = flag.slice(0, equalsIndex).trim();
4484
+ const value = flag.slice(equalsIndex + 1);
4485
+ if (!path) {
4486
+ throw new Error(`Invalid play input flag: ${rawFlag}`);
4487
+ }
4488
+ return { path, value };
4489
+ }
4490
+ if (!nextArg || nextArg.startsWith("--")) {
4491
+ throw new Error(`Play input flag ${rawFlag} requires a value.`);
4492
+ }
4493
+ return { path: flag, value: nextArg };
4494
+ }
4495
+ function parseInputFlagValue(raw) {
4496
+ const trimmed = raw.trim();
4497
+ if (!trimmed) return "";
4498
+ if (trimmed === "true" || trimmed === "false" || trimmed === "null" || trimmed.startsWith("{") || trimmed.startsWith("[") || /^-?\d+(\.\d+)?$/.test(trimmed)) {
4499
+ try {
4500
+ return JSON.parse(trimmed);
4501
+ } catch {
4502
+ return raw;
4503
+ }
4504
+ }
4505
+ return raw;
4506
+ }
4507
+ function getDottedInputValue(input, path) {
4508
+ const parts = path.split(".").map((part) => part.trim()).filter(Boolean);
4509
+ let cursor = input;
4510
+ for (const part of parts) {
4511
+ if (!cursor || typeof cursor !== "object" || Array.isArray(cursor)) {
4512
+ return void 0;
4513
+ }
4514
+ cursor = cursor[part];
4515
+ }
4516
+ return cursor;
4517
+ }
4518
+ function setDottedInputValue(input, path, value) {
4519
+ const parts = path.split(".").map((part) => part.trim()).filter(Boolean);
4520
+ if (parts.length === 0) {
4521
+ throw new Error(`Invalid play input flag path: ${path}`);
4522
+ }
4523
+ let cursor = input;
4524
+ for (const part of parts.slice(0, -1)) {
4525
+ const existing = cursor[part];
4526
+ if (existing !== void 0 && (!existing || typeof existing !== "object" || Array.isArray(existing))) {
4527
+ throw new Error(
4528
+ `Cannot set --${path}; input.${part} is already a non-object value.`
4529
+ );
4530
+ }
4531
+ if (!existing) {
4532
+ cursor[part] = {};
4533
+ }
4534
+ cursor = cursor[part];
4535
+ }
4536
+ cursor[parts[parts.length - 1]] = value;
4537
+ }
4538
+ function schemaMetadata(schema, key) {
4539
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) return null;
4540
+ const value = schema[key];
4541
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
4542
+ }
4543
+ function stringMetadata(metadata, key) {
4544
+ const value = metadata?.[key];
4545
+ return typeof value === "string" && value.trim() ? value.trim() : null;
4546
+ }
4547
+ function inputFieldFromCsvArg(csvArg) {
4548
+ if (typeof csvArg !== "string") return null;
4549
+ const match = /^input\.([A-Za-z_$][\w$]*)$/.exec(csvArg.trim());
4550
+ return match?.[1] ?? null;
4551
+ }
4552
+ function fileInputBindingsFromPlaySchema(inputSchema) {
4553
+ const csvInput = schemaMetadata(inputSchema, "csvInput");
4554
+ if (!csvInput) return [];
4555
+ return [
4556
+ {
4557
+ inputPath: stringMetadata(csvInput, "inputField") ?? "csv"
4558
+ }
4559
+ ];
4560
+ }
4561
+ function fileInputBindingsFromStaticPipeline(staticPipeline) {
4562
+ if (!staticPipeline || typeof staticPipeline !== "object" || Array.isArray(staticPipeline)) {
4563
+ return [];
4564
+ }
4565
+ const inputField = inputFieldFromCsvArg(
4566
+ staticPipeline.csvArg
4567
+ );
4568
+ return inputField ? [{ inputPath: inputField }] : [];
4569
+ }
4570
+ function applyCsvShortcutInput(input) {
4571
+ const csvValue = getDottedInputValue(input.runtimeInput, "csv");
4572
+ if (csvValue == null || csvValue === "") return;
4573
+ const candidate = input.bindings.find((binding) => binding.inputPath !== "csv")?.inputPath ?? input.fallbackInputPath ?? null;
4574
+ if (!candidate || candidate === "csv") return;
4575
+ const existing = getDottedInputValue(input.runtimeInput, candidate);
4576
+ if (existing != null && existing !== "") return;
4577
+ setDottedInputValue(input.runtimeInput, candidate, csvValue);
4578
+ }
4579
+ function isLocalFilePathValue(value) {
4580
+ if (typeof value !== "string" || !value.trim()) return false;
4581
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim())) return false;
4582
+ return existsSync4(resolve7(value));
4583
+ }
4584
+ async function stageFileInputArgs(input) {
4585
+ const uniqueBindings = [
4586
+ ...new Map(input.bindings.map((binding) => [binding.inputPath, binding])).values()
4587
+ ];
4588
+ const localFiles = uniqueBindings.flatMap((binding) => {
4589
+ const value = getDottedInputValue(input.runtimeInput, binding.inputPath);
4590
+ if (!isLocalFilePathValue(value)) return [];
4591
+ const absolutePath = resolve7(value);
4592
+ return [{ binding, absolutePath, logicalPath: basename3(absolutePath) }];
4593
+ });
4594
+ if (localFiles.length === 0) {
4595
+ return { inputFile: null, packagedFiles: [] };
4596
+ }
4597
+ input.progress.phase(
4598
+ localFiles.length === 1 ? "staging input file" : "staging input files"
4599
+ );
4600
+ const staged = await input.client.stagePlayFiles(
4601
+ localFiles.map((file) => stageFile(file.logicalPath, file.absolutePath))
4602
+ );
4603
+ for (const [index, file] of localFiles.entries()) {
4604
+ setDottedInputValue(input.runtimeInput, file.binding.inputPath, file.logicalPath);
4605
+ const stagedFile = staged[index];
4606
+ if (stagedFile && stagedFile.logicalPath !== file.logicalPath) {
4607
+ setDottedInputValue(input.runtimeInput, file.binding.inputPath, stagedFile.logicalPath);
4608
+ }
4609
+ }
4610
+ return {
4611
+ inputFile: staged[0] ?? null,
4612
+ packagedFiles: staged.slice(1)
4613
+ };
4614
+ }
4159
4615
  function stageFile(logicalPath, absolutePath) {
4160
4616
  const buffer = readFileSync3(absolutePath);
4161
4617
  return {
@@ -4243,6 +4699,7 @@ async function compileBundledPlayGraphManifests(client, graph) {
4243
4699
  node.compilerManifest = await client.compilePlayManifest({
4244
4700
  name,
4245
4701
  sourceCode: node.sourceCode,
4702
+ sourceFiles: node.sourceFiles,
4246
4703
  artifact: node.artifact,
4247
4704
  importedPlayDependencies: node.importedPlayDependencies.map(
4248
4705
  (dependency) => {
@@ -4292,6 +4749,7 @@ async function publishImportedPlayDependencies(client, graph) {
4292
4749
  await client.registerPlayArtifact({
4293
4750
  name: node.playName,
4294
4751
  sourceCode: node.sourceCode,
4752
+ sourceFiles: node.sourceFiles,
4295
4753
  artifact: node.artifact,
4296
4754
  compilerManifest: requireCompilerManifest(node),
4297
4755
  publish: true
@@ -4309,67 +4767,6 @@ function formatTimestamp(value) {
4309
4767
  function formatRunLine(run) {
4310
4768
  return `${run.workflowId} ${run.status} ${formatTimestamp(run.startTime)}`;
4311
4769
  }
4312
- function parsePlayRunTarget(input) {
4313
- const { args, usage } = input;
4314
- let runId = null;
4315
- let playName = null;
4316
- for (let index = 0; index < args.length; index += 1) {
4317
- const arg = args[index];
4318
- if (arg === "--json") {
4319
- continue;
4320
- }
4321
- if (arg === "--reason" || arg === "--interval-ms" || arg === "--poll-interval-ms") {
4322
- index += 1;
4323
- continue;
4324
- }
4325
- if (arg === "--run-id" && args[index + 1]) {
4326
- runId = args[++index].trim();
4327
- continue;
4328
- }
4329
- if (arg === "--name" && args[index + 1] && input.allowName) {
4330
- playName = parseReferencedPlayTarget(args[++index]).playName;
4331
- continue;
4332
- }
4333
- if (arg.startsWith("--")) {
4334
- continue;
4335
- }
4336
- throw new DeeplineError(
4337
- `Unexpected positional target "${arg}". Use --run-id for run ids.
4338
- ${usage}`
4339
- );
4340
- }
4341
- const explicitTargets = [runId, playName].filter(Boolean).length;
4342
- if (explicitTargets > 1) {
4343
- throw new DeeplineError(`Choose exactly one play run target.
4344
- ${usage}`);
4345
- }
4346
- if (runId) {
4347
- return { kind: "run", runId };
4348
- }
4349
- if (playName) {
4350
- return { kind: "name", name: playName };
4351
- }
4352
- throw new DeeplineError(usage);
4353
- }
4354
- async function resolvePlayRunId(client, target) {
4355
- if (target.kind === "run") {
4356
- try {
4357
- const status = await client.getPlayStatus(target.runId);
4358
- return status.runId;
4359
- } catch (error) {
4360
- if (!(error instanceof DeeplineError) || error.statusCode !== 404) {
4361
- throw error;
4362
- }
4363
- throw new DeeplineError(`No play run found for run id: ${target.runId}`);
4364
- }
4365
- }
4366
- const runs = await client.listPlayRuns(target.name);
4367
- const workflowId = runs[0]?.workflowId ?? "";
4368
- if (!workflowId) {
4369
- throw new DeeplineError(`No runs found for play: ${target.name}`);
4370
- }
4371
- return workflowId;
4372
- }
4373
4770
  function isTransientPlayStatusPollError(error) {
4374
4771
  if (error instanceof DeeplineError && typeof error.statusCode === "number") {
4375
4772
  return error.statusCode >= 500 && error.statusCode < 600;
@@ -4473,7 +4870,7 @@ function assertPlayWaitNotTimedOut(input) {
4473
4870
  if (input.waitTimeoutMs !== null && Date.now() - input.startedAt >= input.waitTimeoutMs) {
4474
4871
  const hasRealRunId = input.workflowId.length > 0 && input.workflowId !== "pending";
4475
4872
  const phaseSuffix = input.lastPhase && input.lastPhase.trim() ? ` (last observed phase: ${input.lastPhase.trim()})` : "";
4476
- const tailHint = hasRealRunId ? ` Run 'deepline play tail --run-id ${input.workflowId} --json' to inspect it, or rerun with a larger --tail-timeout-ms.` : ` The run never reported a workflow id \u2014 the start request likely failed before reaching the scheduler. Check server logs and rerun with a larger --tail-timeout-ms.`;
4873
+ const tailHint = hasRealRunId ? ` Run 'deepline runs tail ${input.workflowId} --json' to inspect it, or rerun with a larger --tail-timeout-ms.` : ` The run never reported a workflow id \u2014 the start request likely failed before reaching the scheduler. Check server logs and rerun with a larger --tail-timeout-ms.`;
4477
4874
  throw new DeeplineError(
4478
4875
  `Timed out waiting for play ${hasRealRunId ? input.workflowId : "<no run id>"} after ${Math.ceil(input.waitTimeoutMs / 1e3)}s${phaseSuffix}.${tailHint}`,
4479
4876
  void 0,
@@ -4483,68 +4880,8 @@ function assertPlayWaitNotTimedOut(input) {
4483
4880
  ...input.lastPhase ? { phase: input.lastPhase } : {},
4484
4881
  timeoutMs: input.waitTimeoutMs
4485
4882
  }
4486
- );
4487
- }
4488
- }
4489
- async function waitForPlayCompletionByStream(input) {
4490
- const controller = new AbortController();
4491
- let timedOut = false;
4492
- let lastPhase = null;
4493
- const timeout = input.waitTimeoutMs === null ? null : setTimeout(
4494
- () => {
4495
- timedOut = true;
4496
- controller.abort();
4497
- },
4498
- Math.max(1, input.waitTimeoutMs - (Date.now() - input.startedAt))
4499
- );
4500
- try {
4501
- for await (const event of input.client.streamPlayRunEvents(
4502
- input.workflowId,
4503
- { signal: controller.signal }
4504
- )) {
4505
- assertPlayWaitNotTimedOut({ ...input, lastPhase });
4506
- const phase = describeLiveEventPhase(event);
4507
- if (phase) {
4508
- lastPhase = phase;
4509
- input.progress.phase(phase);
4510
- }
4511
- printPlayLogLines({
4512
- lines: getLogLinesFromLiveEvent(event),
4513
- status: null,
4514
- jsonOutput: input.jsonOutput,
4515
- emitLogs: input.emitLogs,
4516
- state: input.state,
4517
- progress: input.progress
4518
- });
4519
- const status = getStatusFromLiveEvent(event);
4520
- if (status && TERMINAL_PLAY_STATUSES2.has(status)) {
4521
- const finalStatus = await input.client.getPlayStatus(input.workflowId);
4522
- if (TERMINAL_PLAY_STATUSES2.has(finalStatus.status)) {
4523
- return finalStatus;
4524
- }
4525
- }
4526
- }
4527
- } catch (error) {
4528
- if (timedOut) {
4529
- assertPlayWaitNotTimedOut({ ...input, lastPhase });
4530
- }
4531
- throw error;
4532
- } finally {
4533
- if (timeout) {
4534
- clearTimeout(timeout);
4535
- }
4536
- }
4537
- const phaseSuffix = lastPhase && lastPhase.trim() ? ` (last observed phase: ${lastPhase.trim()})` : "";
4538
- throw new DeeplineError(
4539
- `Play live stream ended before the run reached a terminal state runId=${input.workflowId}${phaseSuffix}.`,
4540
- void 0,
4541
- "PLAY_LIVE_STREAM_ENDED",
4542
- {
4543
- runId: input.workflowId,
4544
- workflowId: input.workflowId,
4545
- ...lastPhase ? { phase: lastPhase } : {}
4546
- }
4547
- );
4883
+ );
4884
+ }
4548
4885
  }
4549
4886
  async function startAndWaitForPlayCompletionByStream(input) {
4550
4887
  const startedAt = Date.now();
@@ -4564,24 +4901,10 @@ async function startAndWaitForPlayCompletionByStream(input) {
4564
4901
  },
4565
4902
  Math.max(1, input.waitTimeoutMs)
4566
4903
  );
4567
- recordCliTrace({
4568
- phase: "cli.start_stream_request",
4569
- playName: input.playName
4570
- });
4571
4904
  try {
4572
- let eventCount = 0;
4573
4905
  for await (const event of input.client.startPlayRunStream(input.request, {
4574
4906
  signal: controller.signal
4575
4907
  })) {
4576
- eventCount += 1;
4577
- if (eventCount === 1) {
4578
- recordCliTrace({
4579
- phase: "cli.start_stream_first_event",
4580
- ms: Date.now() - startedAt,
4581
- playName: input.playName,
4582
- eventType: event.type
4583
- });
4584
- }
4585
4908
  const eventRunId = getEventPayload(event).runId;
4586
4909
  if (typeof eventRunId === "string" && eventRunId && eventRunId !== "pending") {
4587
4910
  lastKnownWorkflowId = eventRunId;
@@ -4622,14 +4945,6 @@ async function startAndWaitForPlayCompletionByStream(input) {
4622
4945
  });
4623
4946
  const finalStatus = getFinalStatusFromLiveEvent(event);
4624
4947
  if (finalStatus) {
4625
- recordCliTrace({
4626
- phase: "cli.start_stream_final_event",
4627
- ms: Date.now() - startedAt,
4628
- playName: input.playName,
4629
- runId: finalStatus.runId,
4630
- status: finalStatus.status,
4631
- eventCount
4632
- });
4633
4948
  return finalStatus;
4634
4949
  }
4635
4950
  }
@@ -4647,10 +4962,12 @@ async function startAndWaitForPlayCompletionByStream(input) {
4647
4962
  clearTimeout(timeout);
4648
4963
  }
4649
4964
  const reason = error instanceof Error ? error.message : String(error);
4650
- process.stderr.write(
4651
- `[play watch] start stream failed after run ${lastKnownWorkflowId}; falling back to polling (${reason})
4965
+ if (!input.jsonOutput) {
4966
+ process.stderr.write(
4967
+ `[play watch] start stream failed after run ${lastKnownWorkflowId}; falling back to polling (${reason})
4652
4968
  `
4653
- );
4969
+ );
4970
+ }
4654
4971
  return waitForPlayCompletionByPolling({
4655
4972
  client: input.client,
4656
4973
  workflowId: lastKnownWorkflowId,
@@ -4669,6 +4986,24 @@ async function startAndWaitForPlayCompletionByStream(input) {
4669
4986
  clearTimeout(timeout);
4670
4987
  }
4671
4988
  }
4989
+ if (lastKnownWorkflowId) {
4990
+ if (!input.jsonOutput) {
4991
+ input.progress.writeLine(
4992
+ `[play watch] start stream ended after run ${lastKnownWorkflowId}; falling back to polling`
4993
+ );
4994
+ }
4995
+ return waitForPlayCompletionByPolling({
4996
+ client: input.client,
4997
+ workflowId: lastKnownWorkflowId,
4998
+ pollIntervalMs: 500,
4999
+ jsonOutput: input.jsonOutput,
5000
+ emitLogs: input.emitLogs,
5001
+ waitTimeoutMs: input.waitTimeoutMs,
5002
+ startedAt,
5003
+ state,
5004
+ progress: input.progress
5005
+ });
5006
+ }
4672
5007
  const phaseSuffix = lastPhase && lastPhase.trim() ? ` (last observed phase: ${lastPhase.trim()})` : "";
4673
5008
  const idSuffix = lastKnownWorkflowId ? ` runId=${lastKnownWorkflowId}` : "";
4674
5009
  throw new DeeplineError(
@@ -4746,38 +5081,6 @@ async function waitForPlayCompletionByPolling(input) {
4746
5081
  }
4747
5082
  }
4748
5083
  }
4749
- async function waitForPlayCompletion(input) {
4750
- const startedAt = Date.now();
4751
- const state = {
4752
- lastLogIndex: 0,
4753
- emittedRunnerStarted: false
4754
- };
4755
- try {
4756
- return await waitForPlayCompletionByStream({
4757
- ...input,
4758
- startedAt,
4759
- state,
4760
- progress: input.progress
4761
- });
4762
- } catch (error) {
4763
- assertPlayWaitNotTimedOut({
4764
- workflowId: input.workflowId,
4765
- startedAt,
4766
- waitTimeoutMs: input.waitTimeoutMs
4767
- });
4768
- const reason = error instanceof Error ? error.message : String(error);
4769
- process.stderr.write(
4770
- `[play watch] SSE stream failed; falling back to polling (${reason})
4771
- `
4772
- );
4773
- return waitForPlayCompletionByPolling({
4774
- ...input,
4775
- startedAt,
4776
- state,
4777
- progress: input.progress
4778
- });
4779
- }
4780
- }
4781
5084
  function formatInteger(value) {
4782
5085
  return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : String(value ?? "-");
4783
5086
  }
@@ -4906,18 +5209,153 @@ function buildRunWarnings(status, rowsInfo) {
4906
5209
  }
4907
5210
  function buildRunNextCommands(runId) {
4908
5211
  return {
4909
- exportCsv: `deepline runs export ${runId} --out output.csv`,
4910
- status: `deepline runs status ${runId} --json`,
4911
- fullStatus: `deepline runs status ${runId} --json --full`,
4912
- logs: `deepline runs logs ${runId}`
5212
+ get: `deepline runs get ${runId} --json`,
5213
+ tail: `deepline runs tail ${runId} --json`,
5214
+ stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
5215
+ logs: `deepline runs logs ${runId} --out run.log --json`,
5216
+ exportCsv: `deepline runs export ${runId} --out output.csv`
5217
+ };
5218
+ }
5219
+ var RUN_LOG_PREVIEW_LIMIT = 20;
5220
+ function getRecordField(value, key) {
5221
+ return value && typeof value === "object" && !Array.isArray(value) ? value[key] : void 0;
5222
+ }
5223
+ function getNumericField(value, key) {
5224
+ const field = getRecordField(value, key);
5225
+ return typeof field === "number" && Number.isFinite(field) ? field : null;
5226
+ }
5227
+ function getStringField(value, key) {
5228
+ const field = getRecordField(value, key);
5229
+ return typeof field === "string" && field.trim() ? field : null;
5230
+ }
5231
+ function normalizeRunStatusForEnvelope(status) {
5232
+ const run = status.run ?? null;
5233
+ return {
5234
+ id: status.runId,
5235
+ playName: status.playName ?? status.name ?? getStringField(run, "playName") ?? null,
5236
+ status: status.status,
5237
+ runtime: getStringField(status, "runtime") ?? getStringField(status, "runtimeBackend") ?? getStringField(run, "runtime") ?? null,
5238
+ startedAt: getStringField(run, "startTime") ?? getStringField(run, "startedAt") ?? null,
5239
+ updatedAt: getStringField(status, "updatedAt") ?? getStringField(run, "updatedAt") ?? null,
5240
+ finishedAt: getStringField(run, "closeTime") ?? getStringField(run, "finishedAt") ?? null,
5241
+ source: getRecordField(status, "source") ?? getRecordField(status, "artifact") ?? null
5242
+ };
5243
+ }
5244
+ function normalizeProgressForEnvelope(status, rowsInfo) {
5245
+ const progress = status.progress;
5246
+ const total = getNumericField(progress, "totalRows") ?? getNumericField(progress, "total") ?? rowsInfo?.totalRows ?? null;
5247
+ const failed = getNumericField(progress, "failed") ?? getNumericField(progress, "failedRows") ?? null;
5248
+ const completed = getNumericField(progress, "completed") ?? getNumericField(progress, "completedRows") ?? (status.status === "completed" ? total : null);
5249
+ const pending = getNumericField(progress, "pending") ?? (typeof total === "number" && typeof completed === "number" && typeof failed === "number" ? Math.max(0, total - completed - failed) : null);
5250
+ return {
5251
+ total,
5252
+ completed,
5253
+ pending,
5254
+ failed,
5255
+ executed: getNumericField(progress, "executed"),
5256
+ reused: getNumericField(progress, "reused"),
5257
+ skipped: getNumericField(progress, "skipped"),
5258
+ retried: getNumericField(progress, "retried"),
5259
+ degraded: typeof getRecordField(progress, "degraded") === "boolean" ? getRecordField(progress, "degraded") : null,
5260
+ duplicates: getRecordField(progress, "duplicates") ?? null,
5261
+ active: getStringField(progress, "status") ?? getStringField(status, "activeStep") ?? getStringField(status, "activeNodeId") ?? null,
5262
+ wait: status.wait ?? null
5263
+ };
5264
+ }
5265
+ function normalizeOutputsForEnvelope(rowsInfo, exportedPath) {
5266
+ if (!rowsInfo) {
5267
+ return exportedPath ? [{ name: "output", kind: "file", path: exportedPath }] : [];
5268
+ }
5269
+ return [
5270
+ {
5271
+ name: "rows",
5272
+ kind: "dataset",
5273
+ rowCount: rowsInfo.totalRows,
5274
+ columns: rowsInfo.columns,
5275
+ preview: rowsInfo.rows.slice(0, 5),
5276
+ previewRowCount: Math.min(rowsInfo.rows.length, 5),
5277
+ previewLimit: 5,
5278
+ complete: rowsInfo.complete,
5279
+ source: rowsInfo.source,
5280
+ ...exportedPath ? { csv_path: exportedPath } : {}
5281
+ }
5282
+ ];
5283
+ }
5284
+ function normalizeStepsForEnvelope(status) {
5285
+ const directSteps = getRecordField(status, "steps");
5286
+ if (Array.isArray(directSteps)) {
5287
+ return directSteps;
5288
+ }
5289
+ const timeline = getRecordField(status, "timeline");
5290
+ if (Array.isArray(timeline)) {
5291
+ return timeline;
5292
+ }
5293
+ return [];
5294
+ }
5295
+ function normalizeErrorsForEnvelope(status, error) {
5296
+ const directErrors = getRecordField(status, "errors");
5297
+ if (Array.isArray(directErrors)) {
5298
+ return directErrors.filter(
5299
+ (entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)
5300
+ );
5301
+ }
5302
+ if (!error) {
5303
+ return [];
5304
+ }
5305
+ return [
5306
+ {
5307
+ code: getStringField(status, "errorCode") ?? "RUN_FAILED",
5308
+ phase: getStringField(status, "errorPhase") ?? "runtime",
5309
+ message: error,
5310
+ retryable: typeof getRecordField(status, "retryable") === "boolean" ? getRecordField(status, "retryable") : null,
5311
+ nextAction: `deepline runs get ${status.runId} --json`
5312
+ }
5313
+ ];
5314
+ }
5315
+ function normalizeLogsForEnvelope(status) {
5316
+ const logs = Array.isArray(status.progress?.logs) ? status.progress.logs : [];
5317
+ const offset = typeof status.progress?.logOffset === "number" && Number.isFinite(status.progress.logOffset) ? Math.max(0, Math.trunc(status.progress.logOffset)) : 0;
5318
+ const totalCount = offset + logs.length;
5319
+ const entries = logs.slice(Math.max(0, logs.length - RUN_LOG_PREVIEW_LIMIT));
5320
+ const firstSequence = entries.length === 0 ? null : offset + logs.length - entries.length + 1;
5321
+ const lastSequence = totalCount === 0 ? null : totalCount;
5322
+ return {
5323
+ totalCount,
5324
+ returnedCount: entries.length,
5325
+ firstSequence,
5326
+ lastSequence,
5327
+ truncated: totalCount > entries.length,
5328
+ hasMore: totalCount > entries.length,
5329
+ entries,
5330
+ nextCursor: lastSequence
4913
5331
  };
4914
5332
  }
5333
+ function stripProviderSpendFromBilling(value) {
5334
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
5335
+ return value;
5336
+ }
5337
+ const next = {};
5338
+ for (const [key, item] of Object.entries(value)) {
5339
+ if (key === "providerCostUsd" || key === "totalProviderCostUsd") {
5340
+ continue;
5341
+ }
5342
+ next[key] = item;
5343
+ }
5344
+ return next;
5345
+ }
4915
5346
  function compactPlayStatus(status, options) {
4916
5347
  const rowsInfo = extractCanonicalRowsInfo(status);
4917
5348
  const result = status && typeof status === "object" ? status.result : null;
4918
5349
  const warnings = buildRunWarnings(status, rowsInfo);
4919
- const datasetStats = rowsInfo && rowsInfo.complete ? buildDatasetStats(rowsInfo.rows, rowsInfo.totalRows, rowsInfo.columns) : null;
4920
- const billing = status && typeof status === "object" ? status.billing : null;
5350
+ const datasetStats = rowsInfo && rowsInfo.complete ? buildDatasetStats(
5351
+ rowsInfo.rows,
5352
+ rowsInfo.totalRows,
5353
+ rowsInfo.columns,
5354
+ extractDatasetExecutionStats(status)
5355
+ ) : null;
5356
+ const billing = status && typeof status === "object" ? stripProviderSpendFromBilling(
5357
+ status.billing
5358
+ ) : null;
4921
5359
  const progressError = status.progress?.error;
4922
5360
  const error = typeof progressError === "string" ? progressError : typeof status.error === "string" ? String(status.error) : null;
4923
5361
  return {
@@ -4926,18 +5364,38 @@ function compactPlayStatus(status, options) {
4926
5364
  ...typeof status.name === "string" ? { name: status.name } : {},
4927
5365
  ...typeof status.playName === "string" ? { playName: status.playName } : {},
4928
5366
  status: status.status,
5367
+ run: normalizeRunStatusForEnvelope(status),
5368
+ progress: normalizeProgressForEnvelope(status, rowsInfo),
5369
+ outputs: normalizeOutputsForEnvelope(rowsInfo, options?.exportedPath),
5370
+ steps: normalizeStepsForEnvelope(status),
5371
+ errors: normalizeErrorsForEnvelope(status, error),
5372
+ logs: normalizeLogsForEnvelope(status),
4929
5373
  ...error ? { error } : {},
4930
5374
  ...warnings.length > 0 ? { warnings } : {},
4931
5375
  output: buildOutputSummary(rowsInfo, options?.exportedPath) ?? result ?? null,
4932
5376
  ...result !== void 0 ? { result } : {},
4933
5377
  ...status.resultView ? { resultView: status.resultView } : {},
4934
5378
  ...datasetStats ? { dataset_stats: datasetStats } : {},
4935
- ...rowsInfo ? { previewRows: rowsInfo.rows.slice(0, 10) } : {},
5379
+ ...rowsInfo ? { previewRows: rowsInfo.rows.slice(0, 5) } : {},
4936
5380
  ...billing ? { billing } : {},
4937
- ...status.run ? { run: status.run } : {},
4938
5381
  next: buildRunNextCommands(status.runId)
4939
5382
  };
4940
5383
  }
5384
+ function enrichPlayStatusWithDatasetStats(status) {
5385
+ const rowsInfo = extractCanonicalRowsInfo(status);
5386
+ if (!rowsInfo?.complete) {
5387
+ return status;
5388
+ }
5389
+ return {
5390
+ ...status,
5391
+ dataset_stats: buildDatasetStats(
5392
+ rowsInfo.rows,
5393
+ rowsInfo.totalRows,
5394
+ rowsInfo.columns,
5395
+ extractDatasetExecutionStats(status)
5396
+ )
5397
+ };
5398
+ }
4941
5399
  function formatDatasetStatsLines(datasetStats) {
4942
5400
  if (!datasetStats) {
4943
5401
  return [];
@@ -4947,10 +5405,11 @@ function formatDatasetStatsLines(datasetStats) {
4947
5405
  0,
4948
5406
  12
4949
5407
  )) {
4950
- const topValues = stat3.top_values ? `, ${Object.entries(stat3.top_values).slice(0, 3).map(([value, count]) => `${value}=${count}`).join(", ")}` : "";
4951
- const sample = stat3.sample_value !== void 0 ? `, sample=${JSON.stringify(stat3.sample_value)}` : "";
5408
+ const topValues = stat3.top_values ? `, top_values=${Object.entries(stat3.top_values).slice(0, 3).map(([value, count]) => `${value}=${count}`).join(", ")}` : "";
5409
+ const sample = stat3.sample_value !== void 0 ? `, sample_value=${JSON.stringify(stat3.sample_value)}` : "";
5410
+ const execution = stat3.execution ? `, execution=${Object.entries(stat3.execution).map(([bucket, count]) => `${bucket}=${count}`).join(", ")}` : "";
4952
5411
  lines.push(
4953
- ` ${column}: ${stat3.non_empty}, unique=${stat3.unique}${topValues}${sample}`
5412
+ ` ${column}: non_empty=${stat3.non_empty}, unique=${stat3.unique}${topValues}${sample}${execution}`
4954
5413
  );
4955
5414
  }
4956
5415
  return lines;
@@ -4959,7 +5418,7 @@ function writePlayResult(status, jsonOutput, options) {
4959
5418
  if (jsonOutput) {
4960
5419
  process.stdout.write(
4961
5420
  `${JSON.stringify(
4962
- options?.fullJson ? status : compactPlayStatus(status, options)
5421
+ options?.fullJson ? enrichPlayStatusWithDatasetStats(status) : compactPlayStatus(status, options)
4963
5422
  )}
4964
5423
  `
4965
5424
  );
@@ -4973,7 +5432,12 @@ function writePlayResult(status, jsonOutput, options) {
4973
5432
  lines.push(`${success ? "\u2713" : "\u2717"} ${publicStatus} ${runId}`);
4974
5433
  const rowsInfo = extractCanonicalRowsInfo(status);
4975
5434
  const warnings = buildRunWarnings(status, rowsInfo);
4976
- const datasetStats = rowsInfo && rowsInfo.complete ? buildDatasetStats(rowsInfo.rows, rowsInfo.totalRows, rowsInfo.columns) : null;
5435
+ const datasetStats = rowsInfo && rowsInfo.complete ? buildDatasetStats(
5436
+ rowsInfo.rows,
5437
+ rowsInfo.totalRows,
5438
+ rowsInfo.columns,
5439
+ extractDatasetExecutionStats(status)
5440
+ ) : null;
4977
5441
  const outputSummary = buildOutputSummary(rowsInfo, options?.exportedPath);
4978
5442
  if (outputSummary) {
4979
5443
  const columns = Array.isArray(outputSummary.columns) ? outputSummary.columns.length : 0;
@@ -5092,10 +5556,10 @@ function writeStartedPlayRun(input) {
5092
5556
  const lines = [
5093
5557
  `Started ${input.playName}`,
5094
5558
  ` run id: ${input.runId}`,
5095
- ` check status: deepline play status --run-id ${input.runId}`,
5096
- ` tail logs: deepline play tail --run-id ${input.runId}`,
5097
- ` stop run: deepline play stop --run-id ${input.runId}`,
5098
- ` result JSON: deepline play status --run-id ${input.runId} --json`
5559
+ ` get status: deepline runs get ${input.runId} --json`,
5560
+ ` tail logs: deepline runs tail ${input.runId} --json`,
5561
+ ` stop run: deepline runs stop ${input.runId} --reason "stale lock" --json`,
5562
+ ` result JSON: deepline runs get ${input.runId} --json`
5099
5563
  ];
5100
5564
  if (input.dashboardUrl) {
5101
5565
  lines.push(` play page: ${input.dashboardUrl}`);
@@ -5108,10 +5572,9 @@ function writeStartedPlayRun(input) {
5108
5572
  console.log(output);
5109
5573
  }
5110
5574
  function parsePlayRunOptions(args) {
5111
- const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--csv file.csv] [--live|--latest|--revision-id <id>] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force]\n deepline plays run <play-file.ts> [--input '{...}'] [--csv file.csv] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--csv file.csv] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force]\n deepline plays run --name <name> [--input '{...}'] [--csv file.csv] [--live|--latest|--revision-id <id>] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--json]";
5575
+ const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--live|--latest|--revision-id <id>] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--json] [--<input> value]";
5112
5576
  let filePath = null;
5113
5577
  let playName = null;
5114
- let csvPath = null;
5115
5578
  let input = null;
5116
5579
  let revisionId = null;
5117
5580
  let revisionSelector = null;
@@ -5132,10 +5595,6 @@ function parsePlayRunOptions(args) {
5132
5595
  playName = parseReferencedPlayTarget(args[++index]).playName;
5133
5596
  continue;
5134
5597
  }
5135
- if (arg === "--csv" && args[index + 1]) {
5136
- csvPath = resolve7(args[++index]);
5137
- continue;
5138
- }
5139
5598
  if ((arg === "--input" || arg === "-i") && args[index + 1]) {
5140
5599
  input = parseJsonInput(args[++index]);
5141
5600
  continue;
@@ -5185,8 +5644,13 @@ function parsePlayRunOptions(args) {
5185
5644
  continue;
5186
5645
  }
5187
5646
  if (arg.startsWith("--")) {
5188
- throw new Error(`Unexpected flag: ${arg}
5189
- ${usage}`);
5647
+ const { path, value } = parseInputFieldFlag(arg, args[index + 1]);
5648
+ input ??= {};
5649
+ setDottedInputValue(input, path, parseInputFlagValue(value));
5650
+ if (!arg.includes("=")) {
5651
+ index += 1;
5652
+ }
5653
+ continue;
5190
5654
  }
5191
5655
  if (!arg.startsWith("--") && !filePath && !playName) {
5192
5656
  if (isFileTarget(arg) || looksLikeFilePath(arg)) {
@@ -5222,7 +5686,6 @@ ${usage}`);
5222
5686
  }
5223
5687
  return {
5224
5688
  target: filePath ? { kind: "file", path: filePath } : { kind: "name", name: playName },
5225
- csvPath,
5226
5689
  input,
5227
5690
  revisionId,
5228
5691
  revisionSelector,
@@ -5243,6 +5706,10 @@ function parsePlayCheckOptions(args) {
5243
5706
  const jsonOutput = argsWantJson(args);
5244
5707
  return { target, jsonOutput };
5245
5708
  }
5709
+ function shouldUseLocalOnlyPlayCheck() {
5710
+ const value = process.env.DEEPLINE_PLAY_CHECK_LOCAL_ONLY?.trim().toLowerCase();
5711
+ return value === "1" || value === "true" || value === "yes" || value === "on";
5712
+ }
5246
5713
  async function handlePlayCheck(args) {
5247
5714
  const options = parsePlayCheckOptions(args);
5248
5715
  if (!isFileTarget(options.target)) {
@@ -5268,10 +5735,28 @@ async function handlePlayCheck(args) {
5268
5735
  return 1;
5269
5736
  }
5270
5737
  const playName = graph.root.playName ?? extractPlayName(sourceCode, absolutePlayPath);
5738
+ if (shouldUseLocalOnlyPlayCheck()) {
5739
+ const result2 = {
5740
+ valid: true,
5741
+ errors: [],
5742
+ staticPipeline: graph.root.compilerManifest?.staticPipeline ?? null,
5743
+ artifactHash: graph.root.artifact.artifactHash,
5744
+ graphHash: graph.root.artifact.graphHash
5745
+ };
5746
+ if (options.jsonOutput) {
5747
+ process.stdout.write(`${JSON.stringify({ name: playName, ...result2 })}
5748
+ `);
5749
+ } else {
5750
+ console.log(`\u2713 ${playName} passed local play check`);
5751
+ console.log(` artifact: ${result2.artifactHash.slice(0, 12)}`);
5752
+ }
5753
+ return 0;
5754
+ }
5271
5755
  const client = new DeeplineClient();
5272
5756
  const result = await client.checkPlayArtifact({
5273
5757
  name: playName,
5274
5758
  sourceCode: graph.root.sourceCode,
5759
+ sourceFiles: graph.root.sourceFiles,
5275
5760
  artifact: graph.root.artifact
5276
5761
  });
5277
5762
  if (options.jsonOutput) {
@@ -5297,34 +5782,12 @@ async function handleFileBackedRun(options) {
5297
5782
  const client = new DeeplineClient();
5298
5783
  const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
5299
5784
  const absolutePlayPath = resolve7(options.target.path);
5300
- recordCliTrace({
5301
- phase: "cli.play_run_file_start",
5302
- playPath: absolutePlayPath,
5303
- watch: options.watch,
5304
- hasCsv: Boolean(options.csvPath),
5305
- force: options.force
5306
- });
5307
5785
  progress.phase("compiling play");
5308
- const readSourceStartedAt = Date.now();
5309
5786
  const sourceCode = readFileSync3(absolutePlayPath, "utf-8");
5310
- recordCliTrace({
5311
- phase: "cli.read_play_source",
5312
- ms: Date.now() - readSourceStartedAt,
5313
- bytes: sourceCode.length,
5314
- playPath: absolutePlayPath
5315
- });
5316
5787
  let graph;
5317
5788
  try {
5318
- graph = await traceCliSpan(
5319
- "cli.bundle_play_graph",
5320
- { playPath: absolutePlayPath },
5321
- () => collectBundledPlayGraph(absolutePlayPath)
5322
- );
5323
- await traceCliSpan(
5324
- "cli.compile_play_manifest",
5325
- { playPath: absolutePlayPath, nodeCount: graph.nodes.size },
5326
- () => compileBundledPlayGraphManifests(client, graph)
5327
- );
5789
+ graph = await collectBundledPlayGraph(absolutePlayPath);
5790
+ await compileBundledPlayGraphManifests(client, graph);
5328
5791
  progress.phase("compiled play");
5329
5792
  } catch (error) {
5330
5793
  progress.fail();
@@ -5335,87 +5798,65 @@ async function handleFileBackedRun(options) {
5335
5798
  const playName = bundleResult.playName ?? extractPlayName(sourceCode, absolutePlayPath);
5336
5799
  try {
5337
5800
  progress.phase("publishing imported plays");
5338
- await traceCliSpan(
5339
- "cli.publish_imported_plays",
5340
- { playName, nodeCount: graph.nodes.size },
5341
- () => publishImportedPlayDependencies(client, graph)
5342
- );
5801
+ await publishImportedPlayDependencies(client, graph);
5343
5802
  } catch (error) {
5344
5803
  progress.fail();
5345
5804
  console.error(error instanceof Error ? error.message : String(error));
5346
5805
  return 1;
5347
5806
  }
5348
5807
  const runtimeInput = options.input ? { ...options.input } : {};
5349
- const prepareFilesStartedAt = Date.now();
5350
5808
  const packagedFileUploads = bundleResult.packagedFiles.map(
5351
5809
  (file) => stageFile(file.logicalPath, file.absolutePath)
5352
5810
  );
5353
- const inputFileUpload = options.csvPath ? stageFile(basename3(options.csvPath), options.csvPath) : packagedFileUploads[0] ?? null;
5354
- if (options.csvPath && typeof runtimeInput.file !== "string" && typeof runtimeInput.csv !== "string") {
5355
- runtimeInput.file = basename3(options.csvPath);
5356
- }
5357
- recordCliTrace({
5358
- phase: "cli.prepare_input_files",
5359
- ms: Date.now() - prepareFilesStartedAt,
5360
- playName,
5361
- packagedFileCount: packagedFileUploads.length,
5362
- hasInputFile: Boolean(inputFileUpload)
5811
+ const fileInputBindings = fileInputBindingsFromStaticPipeline(
5812
+ requireCompilerManifest(bundleResult).staticPipeline
5813
+ );
5814
+ applyCsvShortcutInput({
5815
+ runtimeInput,
5816
+ bindings: fileInputBindings,
5817
+ fallbackInputPath: "file"
5818
+ });
5819
+ const stagedFileInputs = await stageFileInputArgs({
5820
+ client,
5821
+ runtimeInput,
5822
+ bindings: fileInputBindings,
5823
+ progress
5363
5824
  });
5364
5825
  const startRequest = {
5365
5826
  name: playName,
5366
5827
  sourceCode: bundleResult.sourceCode,
5828
+ sourceFiles: bundleResult.sourceFiles,
5367
5829
  runtimeArtifact: bundleResult.artifact,
5368
5830
  compilerManifest: requireCompilerManifest(bundleResult),
5369
- inputFileUpload,
5370
5831
  packagedFileUploads,
5371
5832
  ...Object.keys(runtimeInput).length > 0 ? { input: runtimeInput } : {},
5833
+ ...stagedFileInputs.inputFile ? { inputFile: stagedFileInputs.inputFile } : {},
5834
+ ...stagedFileInputs.packagedFiles.length ? { packagedFiles: stagedFileInputs.packagedFiles } : {},
5372
5835
  ...options.force ? { force: true } : {}
5373
5836
  };
5374
5837
  if (options.watch) {
5375
5838
  progress.phase("starting run");
5376
- const finalStatus = await traceCliSpan(
5377
- "cli.start_and_watch",
5378
- { playName },
5379
- () => startAndWaitForPlayCompletionByStream({
5380
- client,
5381
- request: startRequest,
5382
- playName,
5383
- jsonOutput: options.jsonOutput,
5384
- emitLogs: options.emitLogs,
5385
- waitTimeoutMs: options.waitTimeoutMs,
5386
- progress
5387
- })
5388
- );
5389
- const exportStartedAt = Date.now();
5390
- const exportedPath = exportPlayStatusRows(finalStatus, options.outPath);
5391
- recordCliTrace({
5392
- phase: "cli.export_rows",
5393
- ms: Date.now() - exportStartedAt,
5839
+ const finalStatus = await startAndWaitForPlayCompletionByStream({
5840
+ client,
5841
+ request: startRequest,
5394
5842
  playName,
5395
- exported: Boolean(exportedPath)
5843
+ jsonOutput: options.jsonOutput,
5844
+ emitLogs: options.emitLogs,
5845
+ waitTimeoutMs: options.waitTimeoutMs,
5846
+ progress
5396
5847
  });
5848
+ const exportedPath = exportPlayStatusRows(finalStatus, options.outPath);
5397
5849
  if (finalStatus.status === "completed") {
5398
5850
  progress.complete();
5399
5851
  } else {
5400
5852
  progress.fail();
5401
5853
  }
5402
- recordCliTrace({
5403
- phase: "cli.write_play_result",
5404
- playName,
5405
- status: finalStatus.status,
5406
- runId: finalStatus.runId
5407
- });
5408
5854
  writePlayResult(finalStatus, options.jsonOutput, { exportedPath });
5409
5855
  return finalStatus.status === "completed" ? 0 : 1;
5410
5856
  }
5411
5857
  progress.phase("starting run");
5412
- const started = await traceCliSpan(
5413
- "cli.start_run",
5414
- { playName },
5415
- () => client.startPlayRun(startRequest)
5416
- );
5417
- const fallbackDashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
5418
- const dashboardUrl = started.dashboardUrl ?? fallbackDashboardUrl;
5858
+ const started = await client.startPlayRun(startRequest);
5859
+ const dashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
5419
5860
  progress.phase(`loading play on ${dashboardUrl}`);
5420
5861
  progress.complete();
5421
5862
  writeStartedPlayRun({
@@ -5423,7 +5864,7 @@ async function handleFileBackedRun(options) {
5423
5864
  playName,
5424
5865
  status: started.status,
5425
5866
  statusUrl: started.statusUrl,
5426
- dashboardUrl,
5867
+ dashboardUrl: started.dashboardUrl ?? dashboardUrl,
5427
5868
  jsonOutput: options.jsonOutput,
5428
5869
  progress
5429
5870
  });
@@ -5449,9 +5890,8 @@ async function handleNamedRun(options) {
5449
5890
  }
5450
5891
  const client = new DeeplineClient();
5451
5892
  const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
5452
- let stagedInputFile = null;
5453
5893
  progress.phase("loading play definition");
5454
- await assertCanonicalNamedPlayReference(client, options.target.name);
5894
+ const playDetail = await assertCanonicalNamedPlayReference(client, options.target.name);
5455
5895
  progress.phase("selecting revision");
5456
5896
  const selectedRevisionId = await resolveNamedRunRevisionId({
5457
5897
  client,
@@ -5459,19 +5899,27 @@ async function handleNamedRun(options) {
5459
5899
  revisionId: options.revisionId,
5460
5900
  selector: options.revisionSelector
5461
5901
  });
5462
- if (options.csvPath) {
5463
- progress.phase("staging input file");
5464
- const [staged] = await client.stagePlayFiles([
5465
- stageFile(basename3(options.csvPath), options.csvPath)
5466
- ]);
5467
- stagedInputFile = staged ?? null;
5468
- }
5469
5902
  const runtimeInput = options.input ? { ...options.input } : {};
5903
+ const fileInputBindings = [
5904
+ ...fileInputBindingsFromPlaySchema(playDetail.play.inputSchema),
5905
+ ...fileInputBindingsFromStaticPipeline(playDetail.play.staticPipeline)
5906
+ ];
5907
+ applyCsvShortcutInput({
5908
+ runtimeInput,
5909
+ bindings: fileInputBindings
5910
+ });
5911
+ const stagedFileInputs = await stageFileInputArgs({
5912
+ client,
5913
+ runtimeInput,
5914
+ bindings: fileInputBindings,
5915
+ progress
5916
+ });
5470
5917
  const startRequest = {
5471
5918
  name: options.target.name,
5472
5919
  ...selectedRevisionId ? { revisionId: selectedRevisionId } : {},
5473
5920
  ...Object.keys(runtimeInput).length > 0 ? { input: runtimeInput } : {},
5474
- ...stagedInputFile ? { inputFile: stagedInputFile } : {},
5921
+ ...stagedFileInputs.inputFile ? { inputFile: stagedFileInputs.inputFile } : {},
5922
+ ...stagedFileInputs.packagedFiles.length ? { packagedFiles: stagedFileInputs.packagedFiles } : {},
5475
5923
  ...options.force ? { force: true } : {}
5476
5924
  };
5477
5925
  if (options.watch) {
@@ -5485,22 +5933,6 @@ async function handleNamedRun(options) {
5485
5933
  waitTimeoutMs: options.waitTimeoutMs,
5486
5934
  progress
5487
5935
  });
5488
- if (finalStatus.status !== "completed" && options.csvPath) {
5489
- progress.phase("generating csv wrapper play");
5490
- const generatedPlayPath = writeGeneratedCsvWrapperPlay(
5491
- options.target.name
5492
- );
5493
- progress.writeLogLine(
5494
- `Generated CSV wrapper play: ${generatedPlayPath}`
5495
- );
5496
- progress.phase("running generated csv wrapper play");
5497
- return handleFileBackedRun({
5498
- ...options,
5499
- target: { kind: "file", path: generatedPlayPath },
5500
- revisionId: null,
5501
- revisionSelector: null
5502
- });
5503
- }
5504
5936
  const exportedPath = exportPlayStatusRows(finalStatus, options.outPath);
5505
5937
  if (finalStatus.status === "completed") {
5506
5938
  progress.complete();
@@ -5512,11 +5944,10 @@ async function handleNamedRun(options) {
5512
5944
  }
5513
5945
  progress.phase("starting run");
5514
5946
  const started = await client.startPlayRun(startRequest);
5515
- const fallbackDashboardUrl = buildPlayDashboardUrl(
5947
+ const dashboardUrl = buildPlayDashboardUrl(
5516
5948
  client.baseUrl,
5517
5949
  options.target.name
5518
5950
  );
5519
- const dashboardUrl = started.dashboardUrl ?? fallbackDashboardUrl;
5520
5951
  progress.phase(`loading play on ${dashboardUrl}`);
5521
5952
  progress.complete();
5522
5953
  writeStartedPlayRun({
@@ -5524,7 +5955,7 @@ async function handleNamedRun(options) {
5524
5955
  playName: started.name ?? options.target.name,
5525
5956
  status: started.status,
5526
5957
  statusUrl: started.statusUrl,
5527
- dashboardUrl,
5958
+ dashboardUrl: started.dashboardUrl ?? dashboardUrl,
5528
5959
  jsonOutput: options.jsonOutput,
5529
5960
  progress
5530
5961
  });
@@ -5558,80 +5989,101 @@ async function handlePlayRun(args) {
5558
5989
  }
5559
5990
  return handleNamedRun(options);
5560
5991
  }
5561
- async function handlePlayTail(args) {
5562
- const usage = "Usage: deepline play tail --run-id <run-id> [--interval-ms 1000] [--json]\n deepline play tail --name <name> [--interval-ms 1000] [--json]";
5563
- let target;
5564
- try {
5565
- target = parsePlayRunTarget({ args, usage, allowName: true });
5566
- } catch (error) {
5567
- console.error(error instanceof Error ? error.message : usage);
5568
- return 1;
5569
- }
5570
- const client = new DeeplineClient();
5571
- const jsonOutput = argsWantJson(args);
5572
- const emitLogs = !jsonOutput || args.includes("--logs");
5573
- let intervalMs = 500;
5992
+ function parseRunIdPositional(args, usage) {
5574
5993
  for (let index = 0; index < args.length; index += 1) {
5575
5994
  const arg = args[index];
5576
- if ((arg === "--interval-ms" || arg === "--poll-interval-ms") && args[index + 1]) {
5577
- intervalMs = parsePositiveInteger2(args[++index], arg);
5995
+ if (arg === "--json" || arg === "--full" || arg === "--logs" || arg === "--compact" || arg === "--limit") {
5996
+ if (arg === "--limit" && args[index + 1]) {
5997
+ index += 1;
5998
+ }
5999
+ continue;
6000
+ }
6001
+ if ((arg === "--out" || arg === "--cursor" || arg === "--reason" || arg === "--interval-ms" || arg === "--poll-interval-ms") && args[index + 1]) {
6002
+ index += 1;
6003
+ continue;
6004
+ }
6005
+ if (!arg.startsWith("--")) {
6006
+ return arg;
5578
6007
  }
5579
6008
  }
5580
- const workflowId = await resolvePlayRunId(client, target);
5581
- const progress = getActiveCliProgress() ?? createCliProgress(!jsonOutput);
5582
- progress.phase(`tailing ${workflowId}`);
5583
- const finalStatus = await waitForPlayCompletion({
5584
- client,
5585
- workflowId,
5586
- pollIntervalMs: intervalMs,
5587
- jsonOutput,
5588
- emitLogs,
5589
- waitTimeoutMs: null,
5590
- progress
5591
- });
5592
- if (finalStatus.status === "completed") {
5593
- progress.complete();
5594
- } else {
5595
- progress.fail();
5596
- }
5597
- writePlayResult(finalStatus, jsonOutput);
5598
- return finalStatus.status === "completed" ? 0 : 1;
6009
+ throw new DeeplineError(usage);
5599
6010
  }
5600
- async function handlePlayStatus(args) {
5601
- const usage = "Usage: deepline play status --run-id <run-id> [--json] [--full]\n deepline play status --name <name> [--json] [--full]";
5602
- let target;
6011
+ async function handleRunGet(args) {
6012
+ const usage = "Usage: deepline runs get <run-id> [--json] [--full]";
6013
+ let runId;
5603
6014
  try {
5604
- target = parsePlayRunTarget({ args, usage, allowName: true });
6015
+ runId = parseRunIdPositional(args, usage);
5605
6016
  } catch (error) {
5606
6017
  console.error(error instanceof Error ? error.message : usage);
5607
6018
  return 1;
5608
6019
  }
5609
6020
  const client = new DeeplineClient();
5610
- const workflowId = await resolvePlayRunId(client, target);
5611
- const status = await client.getPlayStatus(workflowId);
6021
+ const status = await client.runs.get(runId);
5612
6022
  writePlayResult(status, argsWantJson(args), {
5613
6023
  fullJson: args.includes("--full")
5614
6024
  });
5615
6025
  return 0;
5616
6026
  }
5617
- function parseRunIdPositional(args, usage) {
6027
+ async function handleRunsList(args) {
6028
+ const usage = "Usage: deepline runs list --play <play-name> [--status <status>] [--json]";
6029
+ let playName = null;
6030
+ let statusFilter = null;
5618
6031
  for (let index = 0; index < args.length; index += 1) {
5619
6032
  const arg = args[index];
5620
- if (arg === "--json" || arg === "--full" || arg === "--logs") {
6033
+ if ((arg === "--play" || arg === "--name") && args[index + 1]) {
6034
+ playName = parseReferencedPlayTarget(args[++index]).playName;
5621
6035
  continue;
5622
6036
  }
5623
- if (arg === "--out" && args[index + 1]) {
5624
- index += 1;
6037
+ if (arg === "--status" && args[index + 1]) {
6038
+ statusFilter = args[++index].trim().toLowerCase();
5625
6039
  continue;
5626
6040
  }
5627
- if (!arg.startsWith("--")) {
5628
- return arg;
6041
+ if (arg === "--json" || arg === "--compact") {
6042
+ continue;
5629
6043
  }
5630
6044
  }
5631
- throw new DeeplineError(usage);
6045
+ if (!playName) {
6046
+ console.error(usage);
6047
+ return 1;
6048
+ }
6049
+ const client = new DeeplineClient();
6050
+ const runs = (await client.runs.list({
6051
+ play: playName,
6052
+ ...statusFilter ? { status: statusFilter } : {}
6053
+ })).map((run) => ({
6054
+ runId: run.workflowId,
6055
+ workflowId: run.workflowId,
6056
+ temporalRunId: run.runId,
6057
+ status: String(run.status ?? "").toLowerCase(),
6058
+ startedAt: run.startTime,
6059
+ finishedAt: run.closeTime,
6060
+ executionTime: run.executionTime,
6061
+ playName: run.memo?.playName ?? playName
6062
+ }));
6063
+ if (argsWantJson(args)) {
6064
+ process.stdout.write(
6065
+ `${JSON.stringify({
6066
+ runs,
6067
+ count: runs.length,
6068
+ next: {
6069
+ get: runs[0]?.runId ? `deepline runs get ${runs[0].runId} --json` : null
6070
+ }
6071
+ })}
6072
+ `
6073
+ );
6074
+ } else {
6075
+ if (runs.length === 0) {
6076
+ console.log(`No runs found for ${playName}.`);
6077
+ } else {
6078
+ for (const run of runs) {
6079
+ console.log(`${run.runId} ${run.status} ${formatTimestamp(run.startedAt)}`);
6080
+ }
6081
+ }
6082
+ }
6083
+ return 0;
5632
6084
  }
5633
- async function handleRunStatus(args) {
5634
- const usage = "Usage: deepline runs status <run-id> [--json] [--full]";
6085
+ async function handleRunTail(args) {
6086
+ const usage = "Usage: deepline runs tail <run-id> [--json] [--compact] [--cursor <cursor>]";
5635
6087
  let runId;
5636
6088
  try {
5637
6089
  runId = parseRunIdPositional(args, usage);
@@ -5640,14 +6092,24 @@ async function handleRunStatus(args) {
5640
6092
  return 1;
5641
6093
  }
5642
6094
  const client = new DeeplineClient();
5643
- const status = await client.getPlayStatus(runId);
5644
- writePlayResult(status, argsWantJson(args), {
5645
- fullJson: args.includes("--full")
6095
+ let afterLogIndex;
6096
+ for (let index = 0; index < args.length; index += 1) {
6097
+ const arg = args[index];
6098
+ if (arg === "--cursor" && args[index + 1]) {
6099
+ const parsed = Number(args[++index]);
6100
+ if (Number.isInteger(parsed) && parsed >= 0) {
6101
+ afterLogIndex = parsed;
6102
+ }
6103
+ }
6104
+ }
6105
+ const status = await client.runs.tail(runId, {
6106
+ ...afterLogIndex !== void 0 ? { afterLogIndex } : {}
5646
6107
  });
5647
- return 0;
6108
+ writePlayResult(status, argsWantJson(args));
6109
+ return status.status === "failed" ? 1 : 0;
5648
6110
  }
5649
6111
  async function handleRunLogs(args) {
5650
- const usage = "Usage: deepline runs logs <run-id> [--json]";
6112
+ const usage = "Usage: deepline runs logs <run-id> [--limit 200] [--out run.log] [--json]";
5651
6113
  let runId;
5652
6114
  try {
5653
6115
  runId = parseRunIdPositional(args, usage);
@@ -5655,14 +6117,86 @@ async function handleRunLogs(args) {
5655
6117
  console.error(error instanceof Error ? error.message : usage);
5656
6118
  return 1;
5657
6119
  }
6120
+ let limit = 200;
6121
+ let outPath = null;
6122
+ for (let index = 0; index < args.length; index += 1) {
6123
+ const arg = args[index];
6124
+ if (arg === "--limit" && args[index + 1]) {
6125
+ limit = parsePositiveInteger2(args[++index], "--limit");
6126
+ continue;
6127
+ }
6128
+ if (arg === "--out" && args[index + 1]) {
6129
+ outPath = resolve7(args[++index]);
6130
+ }
6131
+ }
5658
6132
  const client = new DeeplineClient();
5659
- const status = await client.getPlayStatus(runId);
6133
+ const status = await client.runs.get(runId);
5660
6134
  const logs = status.progress?.logs ?? [];
6135
+ if (outPath) {
6136
+ writeFileSync4(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
6137
+ if (argsWantJson(args)) {
6138
+ process.stdout.write(
6139
+ `${JSON.stringify({
6140
+ runId: status.runId,
6141
+ log_path: outPath,
6142
+ lineCount: logs.length
6143
+ })}
6144
+ `
6145
+ );
6146
+ } else {
6147
+ console.log(`Wrote ${logs.length} log lines to ${outPath}`);
6148
+ }
6149
+ return 0;
6150
+ }
6151
+ const entries = logs.slice(Math.max(0, logs.length - limit));
6152
+ if (argsWantJson(args)) {
6153
+ process.stdout.write(
6154
+ `${JSON.stringify({
6155
+ runId: status.runId,
6156
+ totalCount: logs.length,
6157
+ returnedCount: entries.length,
6158
+ firstSequence: logs.length === 0 ? null : logs.length - entries.length + 1,
6159
+ lastSequence: logs.length === 0 ? null : logs.length,
6160
+ truncated: logs.length > entries.length,
6161
+ hasMore: logs.length > entries.length,
6162
+ entries,
6163
+ next: {
6164
+ export: `deepline runs logs ${status.runId} --out run.log --json`
6165
+ }
6166
+ })}
6167
+ `
6168
+ );
6169
+ } else {
6170
+ process.stdout.write(`${entries.join("\n")}${entries.length > 0 ? "\n" : ""}`);
6171
+ }
6172
+ return 0;
6173
+ }
6174
+ async function handleRunStop(args) {
6175
+ const usage = 'Usage: deepline runs stop <run-id> [--reason "text"] [--json]';
6176
+ let runId;
6177
+ try {
6178
+ runId = parseRunIdPositional(args, usage);
6179
+ } catch (error) {
6180
+ console.error(error instanceof Error ? error.message : usage);
6181
+ return 1;
6182
+ }
6183
+ let reason;
6184
+ for (let index = 0; index < args.length; index += 1) {
6185
+ const arg = args[index];
6186
+ if (arg === "--reason" && args[index + 1]) {
6187
+ reason = args[++index];
6188
+ }
6189
+ }
6190
+ const client = new DeeplineClient();
6191
+ const result = await client.runs.stop(runId, { reason });
5661
6192
  if (argsWantJson(args)) {
5662
- process.stdout.write(`${JSON.stringify({ runId: status.runId, logs })}
6193
+ process.stdout.write(`${JSON.stringify(result)}
5663
6194
  `);
5664
6195
  } else {
5665
- process.stdout.write(`${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
6196
+ console.log(`Stopped ${result.runId}`);
6197
+ if (result.hitlCancelledCount > 0) {
6198
+ console.log(` cancelled HITL waits: ${result.hitlCancelledCount}`);
6199
+ }
5666
6200
  }
5667
6201
  return 0;
5668
6202
  }
@@ -5705,37 +6239,6 @@ async function handleRunExport(args) {
5705
6239
  }
5706
6240
  return 0;
5707
6241
  }
5708
- async function handlePlayStop(args) {
5709
- const usage = 'Usage: deepline play stop --run-id <run-id> [--reason "text"] [--json]';
5710
- let target;
5711
- try {
5712
- target = parsePlayRunTarget({ args, usage, allowName: false });
5713
- } catch (error) {
5714
- console.error(error instanceof Error ? error.message : usage);
5715
- return 1;
5716
- }
5717
- const client = new DeeplineClient();
5718
- const jsonOutput = argsWantJson(args);
5719
- let reason;
5720
- for (let index = 0; index < args.length; index += 1) {
5721
- const arg = args[index];
5722
- if (arg === "--reason" && args[index + 1]) {
5723
- reason = args[++index];
5724
- }
5725
- }
5726
- const workflowId = await resolvePlayRunId(client, target);
5727
- const result = await client.stopPlay(workflowId, { reason });
5728
- if (jsonOutput) {
5729
- process.stdout.write(`${JSON.stringify(result)}
5730
- `);
5731
- } else {
5732
- console.log(`Stopped ${result.runId}`);
5733
- if (result.hitlCancelledCount > 0) {
5734
- console.log(` cancelled HITL waits: ${result.hitlCancelledCount}`);
5735
- }
5736
- }
5737
- return 0;
5738
- }
5739
6242
  async function handlePlayGet(args) {
5740
6243
  const target = args[0];
5741
6244
  if (!target) {
@@ -5743,8 +6246,9 @@ async function handlePlayGet(args) {
5743
6246
  return 1;
5744
6247
  }
5745
6248
  const client = new DeeplineClient();
5746
- const jsonOutput = argsWantJson(args);
6249
+ const explicitJson = args.includes("--json");
5747
6250
  const sourceOutput = args.includes("--source");
6251
+ const jsonOutput = sourceOutput ? explicitJson : argsWantJson(args);
5748
6252
  let outPath = null;
5749
6253
  for (let index = 1; index < args.length; index += 1) {
5750
6254
  const arg = args[index];
@@ -5755,7 +6259,7 @@ async function handlePlayGet(args) {
5755
6259
  const playName = isFileTarget(target) ? extractPlayName(readFileSync3(resolve7(target), "utf-8"), resolve7(target)) : parseReferencedPlayTarget(target).playName;
5756
6260
  const detail = isFileTarget(target) ? await client.getPlay(playName) : await assertCanonicalNamedPlayReference(client, target);
5757
6261
  const resolvedSource = detail.play.workingRevision?.sourceCode ?? detail.play.liveRevision?.sourceCode ?? detail.play.currentRevision?.sourceCode ?? detail.play.sourceCode ?? "";
5758
- const materializedFile = sourceOutput || outPath ? materializeRemotePlaySource({
6262
+ const materializedFile = outPath ? materializeRemotePlaySource({
5759
6263
  target,
5760
6264
  playName,
5761
6265
  sourceCode: resolvedSource,
@@ -5795,6 +6299,10 @@ async function handlePlayGet(args) {
5795
6299
  }
5796
6300
  return 0;
5797
6301
  }
6302
+ if (outPath && loadedMessage) {
6303
+ console.log(loadedMessage);
6304
+ return 0;
6305
+ }
5798
6306
  console.log(`Play: ${formatPlayReference(detail.play)}`);
5799
6307
  console.log(
5800
6308
  `Working version: ${detail.play.workingRevision?.version ?? "\u2014"}`
@@ -5818,33 +6326,6 @@ async function handlePlayGet(args) {
5818
6326
  }
5819
6327
  return 0;
5820
6328
  }
5821
- async function handlePlayRuns(args) {
5822
- const nameIndex = args.indexOf("--name");
5823
- const name = nameIndex >= 0 ? args[nameIndex + 1] : void 0;
5824
- if (!name) {
5825
- console.error("Usage: deepline play runs --name <name> [--json]");
5826
- return 1;
5827
- }
5828
- const client = new DeeplineClient();
5829
- const jsonOutput = argsWantJson(args);
5830
- await assertCanonicalNamedPlayReference(client, name);
5831
- const runs = await client.listPlayRuns(
5832
- parseReferencedPlayTarget(name).playName
5833
- );
5834
- if (jsonOutput) {
5835
- process.stdout.write(`${JSON.stringify({ runs })}
5836
- `);
5837
- return 0;
5838
- }
5839
- if (runs.length === 0) {
5840
- console.log(`No runs found for ${name}.`);
5841
- return 0;
5842
- }
5843
- for (const run of runs) {
5844
- console.log(formatRunLine(run));
5845
- }
5846
- return 0;
5847
- }
5848
6329
  function formatVersionLine(version) {
5849
6330
  const revisionLabel = version.artifactHash?.slice(0, 12) ?? "unknown-revision";
5850
6331
  return `v${version.version} ${revisionLabel} ${formatTimestamp(version.createdAt)}`;
@@ -5961,7 +6442,26 @@ function printPlayDescription(play) {
5961
6442
  console.log(` ${line}`);
5962
6443
  }
5963
6444
  }
6445
+ if (play.csvInput) {
6446
+ console.log(" CSV input:");
6447
+ const rendered = JSON.stringify(play.csvInput, null, 2);
6448
+ for (const line of rendered.split("\n")) {
6449
+ console.log(` ${line}`);
6450
+ }
6451
+ }
6452
+ if (play.rowOutputSchema) {
6453
+ console.log(" Row output schema:");
6454
+ const rendered = JSON.stringify(play.rowOutputSchema, null, 2);
6455
+ for (const line of rendered.split("\n")) {
6456
+ console.log(` ${line}`);
6457
+ }
6458
+ }
5964
6459
  console.log(` Run: ${play.runCommand}`);
6460
+ if (play.origin === "prebuilt" && !play.canEdit) {
6461
+ console.log(
6462
+ ` Customize: deepline plays get ${reference} --source --out ./my-play.play.ts`
6463
+ );
6464
+ }
5965
6465
  }
5966
6466
  async function handlePlaySearch(args) {
5967
6467
  let options;
@@ -6059,6 +6559,7 @@ async function handlePlayPublish(args) {
6059
6559
  const published = await client.registerPlayArtifact({
6060
6560
  name: rootPlayName,
6061
6561
  sourceCode: graph.root.sourceCode,
6562
+ sourceFiles: graph.root.sourceFiles,
6062
6563
  artifact: graph.root.artifact,
6063
6564
  compilerManifest: requireCompilerManifest(graph.root),
6064
6565
  publish: true
@@ -6100,6 +6601,45 @@ async function handlePlayPublish(args) {
6100
6601
  `);
6101
6602
  return result.success ? 0 : 1;
6102
6603
  }
6604
+ async function handlePlayDelete(args) {
6605
+ const playName = args[0];
6606
+ if (!playName) {
6607
+ console.error("Usage: deepline plays delete <play-name> --yes [--json]");
6608
+ return 1;
6609
+ }
6610
+ const confirmed = args.includes("--yes") || args.includes("-y") || args.includes("--force");
6611
+ if (!confirmed) {
6612
+ console.error(
6613
+ "Refusing to delete without --yes. This deletes the org-owned play, its revisions, trigger bindings, and local run records."
6614
+ );
6615
+ return 1;
6616
+ }
6617
+ const client = new DeeplineClient();
6618
+ let detail;
6619
+ try {
6620
+ detail = await client.getPlay(parseReferencedPlayTarget(playName).playName);
6621
+ } catch (error) {
6622
+ console.error(error instanceof Error ? error.message : String(error));
6623
+ return 1;
6624
+ }
6625
+ if (detail.play.ownerType === "deepline" || detail.play.origin === "prebuilt") {
6626
+ console.error(`Cannot delete prebuilt play: ${formatPlayReference(detail.play)}`);
6627
+ return 1;
6628
+ }
6629
+ const result = await client.deletePlay(
6630
+ parseReferencedPlayTarget(formatPlayReference(detail.play)).playName
6631
+ );
6632
+ if (argsWantJson(args)) {
6633
+ process.stdout.write(`${JSON.stringify(result)}
6634
+ `);
6635
+ return result.deleted ? 0 : 1;
6636
+ }
6637
+ process.stdout.write(
6638
+ `Deleted ${result.name}: revisions=${result.deletedRevisionCount}, bindings=${result.deletedBindingCount}, runs=${result.deletedRunCount}
6639
+ `
6640
+ );
6641
+ return result.deleted ? 0 : 1;
6642
+ }
6103
6643
  function registerPlayCommands(program) {
6104
6644
  const play = program.command("plays").alias("play").description("Search, validate, run, and manage cloud plays.").addHelpText(
6105
6645
  "after",
@@ -6133,21 +6673,23 @@ Examples:
6133
6673
  ...options.json ? ["--json"] : []
6134
6674
  ]);
6135
6675
  });
6136
- play.command("run [target]").description("Run a play file or named play.").addHelpText(
6676
+ play.command("run [target]").description("Run a play file or named play.").allowUnknownOption(true).allowExcessArguments(true).addHelpText(
6137
6677
  "after",
6138
6678
  `
6139
6679
  Notes:
6140
- Local play files are bundled locally, then validated and executed in Deepline cloud.
6141
- Named plays run the stored live cloud revision.
6142
- Run performs server preflight automatically. Use \`deepline plays check <file>\`
6143
- to validate without starting a run.
6680
+ Local play files are bundled locally, then validated and executed in Deepline cloud.
6681
+ Named plays run the stored live cloud revision.
6682
+ Unknown --foo and --foo.bar flags are treated as play input args.
6683
+ File-like input args accept local paths; the CLI stages those files before submit.
6684
+ Run performs server preflight automatically. Use \`deepline plays check <file>\`
6685
+ to validate without starting a run.
6144
6686
 
6145
6687
  Examples:
6146
6688
  deepline plays run my.play.ts --input '{"domain":"stripe.com"}' --watch
6147
6689
  deepline plays run person-linkedin-to-email --input '{"linkedin_url":"..."}' --watch
6148
6690
  deepline plays run enrich.play.ts --csv leads.csv --watch --out leads-enriched.csv
6149
6691
  `
6150
- ).option("--file <path>", "Local play file to run").option("--name <name>", "Saved play name to run").option("--csv <path>", "Attach a CSV file").option("-i, --input <json>", "Input JSON object or @file path").option("--live", "Run the current live revision explicitly").option("--latest", "Run the newest saved revision, even if it is not live").option(
6692
+ ).option("--file <path>", "Local play file to run").option("--name <name>", "Saved play name to run").option("-i, --input <json>", "Input JSON object or @file path").option("--live", "Run the current live revision explicitly").option("--latest", "Run the newest saved revision, even if it is not live").option(
6151
6693
  "--revision-id <id>",
6152
6694
  "Run a specific saved revision instead of the live revision"
6153
6695
  ).option(
@@ -6156,12 +6698,21 @@ Examples:
6156
6698
  ).option("--watch", "Stream logs until completion").option(
6157
6699
  "--logs",
6158
6700
  "When output is non-interactive, stream play logs to stderr while waiting"
6159
- ).option("--poll-interval-ms <ms>", "Polling interval while tailing").option("--tail-timeout-ms <ms>", "Timeout while tailing").option("--force", "Supersede any active runs for this play").option("--json", "Emit JSON output").action(async (target, options) => {
6701
+ ).option("--poll-interval-ms <ms>", "Polling interval while tailing").option("--tail-timeout-ms <ms>", "Timeout while tailing").option("--force", "Supersede any active runs for this play").option("--json", "Emit JSON output").action(async (target, options, command) => {
6702
+ const passthroughArgs = [...command.args];
6703
+ const explicitTarget = options.file || options.name;
6704
+ const targetIsInputFlag = typeof target === "string" && target.startsWith("--");
6705
+ const effectiveTarget = explicitTarget || targetIsInputFlag ? null : target;
6706
+ if (explicitTarget && typeof target === "string" && !targetIsInputFlag && !passthroughArgs.includes(target)) {
6707
+ passthroughArgs.push(target);
6708
+ }
6709
+ if (effectiveTarget && passthroughArgs[0] === effectiveTarget) {
6710
+ passthroughArgs.shift();
6711
+ }
6160
6712
  process.exitCode = await handlePlayRun([
6161
- ...target ? [target] : [],
6713
+ ...effectiveTarget ? [effectiveTarget] : [],
6162
6714
  ...options.file ? ["--file", options.file] : [],
6163
6715
  ...options.name ? ["--name", options.name] : [],
6164
- ...options.csv ? ["--csv", options.csv] : [],
6165
6716
  ...options.input ? ["--input", options.input] : [],
6166
6717
  ...options.live ? ["--live"] : [],
6167
6718
  ...options.latest ? ["--latest"] : [],
@@ -6172,7 +6723,8 @@ Examples:
6172
6723
  ...options.pollIntervalMs ? ["--poll-interval-ms", options.pollIntervalMs] : [],
6173
6724
  ...options.tailTimeoutMs ? ["--tail-timeout-ms", options.tailTimeoutMs] : [],
6174
6725
  ...options.force ? ["--force"] : [],
6175
- ...options.json ? ["--json"] : []
6726
+ ...options.json ? ["--json"] : [],
6727
+ ...passthroughArgs
6176
6728
  ]);
6177
6729
  });
6178
6730
  play.command("get <target>").description("Fetch full play details.").addHelpText(
@@ -6186,12 +6738,10 @@ Notes:
6186
6738
  Examples:
6187
6739
  deepline plays get person-linkedin-to-email
6188
6740
  deepline plays get person-linkedin-to-email --json | jq '.play.liveRevision'
6741
+ deepline plays get prebuilt/name-and-domain-to-email-waterfall-batch --source > email-waterfall.play.ts
6742
+ deepline plays get prebuilt/name-and-domain-to-email-waterfall-batch --source --out ./email-waterfall.play.ts
6189
6743
  `
6190
- ).option("--json", "Emit JSON output. Also automatic when stdout is piped").addOption(
6191
- new Option("--source", "Materialize or print the source code").hideHelp()
6192
- ).addOption(
6193
- new Option("--out <path>", "Write source to a specific path").hideHelp()
6194
- ).action(async (target, options) => {
6744
+ ).option("--json", "Emit JSON output. Also automatic when stdout is piped").option("--source", "Print raw source code; combine with --out to write a file").option("--out <path>", "Write source to a specific path").action(async (target, options) => {
6195
6745
  process.exitCode = await handlePlayGet([
6196
6746
  target,
6197
6747
  ...options.json ? ["--json"] : [],
@@ -6230,50 +6780,12 @@ Examples:
6230
6780
  ...options.json ? ["--json"] : []
6231
6781
  ]);
6232
6782
  });
6233
- play.command("runs").description("List runs for a named play.").option("--name <name>", "Saved play name").option("--json", "Emit JSON output").action(async (options) => {
6234
- process.exitCode = await handlePlayRuns([
6235
- ...options.name ? ["--name", options.name] : [],
6236
- ...options.json ? ["--json"] : []
6237
- ]);
6238
- });
6239
6783
  play.command("versions").description("List revisions for a named play.").option("--name <name>", "Saved play name").option("--json", "Emit JSON output").action(async (options) => {
6240
6784
  process.exitCode = await handlePlayVersions([
6241
6785
  ...options.name ? ["--name", options.name] : [],
6242
6786
  ...options.json ? ["--json"] : []
6243
6787
  ]);
6244
6788
  });
6245
- play.command("tail").description("Tail events for a play run.").option("--run-id <runId>", "Run id to tail").option("--name <name>", "Tail the latest run for a named play").option("--interval-ms <ms>", "Polling interval while tailing").option("--logs", "With --json, stream play logs to stderr while waiting").option("--json", "Emit JSON output").action(async (options) => {
6246
- process.exitCode = await handlePlayTail([
6247
- ...options.runId ? ["--run-id", options.runId] : [],
6248
- ...options.name ? ["--name", options.name] : [],
6249
- ...options.intervalMs ? ["--interval-ms", options.intervalMs] : [],
6250
- ...options.logs ? ["--logs"] : [],
6251
- ...options.json ? ["--json"] : []
6252
- ]);
6253
- });
6254
- play.command("status").description("Show status for a play run.").option("--run-id <runId>", "Run id to inspect").option("--name <name>", "Inspect the latest run for a named play").option("--json", "Emit JSON output").option("--full", "With --json, emit the full raw status payload").action(async (options) => {
6255
- process.exitCode = await handlePlayStatus([
6256
- ...options.runId ? ["--run-id", options.runId] : [],
6257
- ...options.name ? ["--name", options.name] : [],
6258
- ...options.json ? ["--json"] : [],
6259
- ...options.full ? ["--full"] : []
6260
- ]);
6261
- });
6262
- play.command("export").description("Export a completed play run to CSV.").requiredOption("--run-id <runId>", "Run id to export").requiredOption("--out <path>", "Output CSV path").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
6263
- process.exitCode = await handleRunExport([
6264
- options.runId,
6265
- "--out",
6266
- options.out,
6267
- ...options.json ? ["--json"] : []
6268
- ]);
6269
- });
6270
- play.command("stop").description("Stop a play run.").option("--run-id <runId>", "Run id to stop").option("--reason <text>", "Reason to include with the stop request").option("--json", "Emit JSON output").action(async (options) => {
6271
- process.exitCode = await handlePlayStop([
6272
- ...options.runId ? ["--run-id", options.runId] : [],
6273
- ...options.reason ? ["--reason", options.reason] : [],
6274
- ...options.json ? ["--json"] : []
6275
- ]);
6276
- });
6277
6789
  play.command("publish <target>").description("Bundle, validate, save, and publish a play.").option("--latest", "Promote the newest saved revision").option("--revision-id <id>", "Revision to promote").option("--json", "Emit JSON output").action(async (target, options) => {
6278
6790
  process.exitCode = await handlePlayPublish([
6279
6791
  target,
@@ -6282,40 +6794,81 @@ Examples:
6282
6794
  ...options.json ? ["--json"] : []
6283
6795
  ]);
6284
6796
  });
6285
- const runs = program.command("runs").description("Inspect and export play runs.").addHelpText(
6797
+ play.command("delete <target>").description("Delete an org-owned play and its saved revisions/runs.").option("-y, --yes", "Confirm deletion").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (target, options) => {
6798
+ process.exitCode = await handlePlayDelete([
6799
+ target,
6800
+ ...options.yes ? ["--yes"] : [],
6801
+ ...options.json ? ["--json"] : []
6802
+ ]);
6803
+ });
6804
+ const runs = program.command("runs").description("Inspect, tail, stop, and export play runs.").addHelpText(
6286
6805
  "after",
6287
6806
  `
6288
6807
  Examples:
6289
- deepline runs status play/my-play/run/20260501t000000-000
6808
+ deepline runs get play/my-play/run/20260501t000000-000 --json
6809
+ deepline runs tail play/my-play/run/20260501t000000-000
6810
+ deepline runs logs play/my-play/run/20260501t000000-000 --out run.log --json
6811
+ deepline runs list --play my-play --status failed --json
6812
+ deepline runs stop play/my-play/run/20260501t000000-000 --reason "stale lock" --json
6290
6813
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv
6291
- deepline runs logs play/my-play/run/20260501t000000-000
6292
6814
  `
6293
6815
  );
6294
- runs.command("status <runId>").description("Show compact status for a play run.").option("--json", "Emit JSON output. Also automatic when stdout is piped").option("--full", "With --json, emit the full raw status payload").action(async (runId, options) => {
6295
- process.exitCode = await handleRunStatus([
6816
+ runs.command("get <runId>").description("Get status, progress, outputs, errors, and recovery metadata for a play run.").option("--json", "Emit JSON output. Also automatic when stdout is piped").option("--full", "Debug only: with --json, emit the raw status payload").action(async (runId, options) => {
6817
+ process.exitCode = await handleRunGet([
6296
6818
  runId,
6297
6819
  ...options.json ? ["--json"] : [],
6298
6820
  ...options.full ? ["--full"] : []
6299
6821
  ]);
6300
6822
  });
6301
- runs.command("export <runId>").description("Export the completed row output for a play run to CSV.").requiredOption("--out <path>", "Output CSV path").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (runId, options) => {
6302
- process.exitCode = await handleRunExport([
6303
- runId,
6304
- "--out",
6305
- options.out,
6823
+ runs.command("list").description("List play runs.").requiredOption("--play <name>", "Play name to filter runs").option("--status <status>", "Filter by run status").option("--compact", "Drop verbose fields from JSON output").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
6824
+ process.exitCode = await handleRunsList([
6825
+ "--play",
6826
+ options.play,
6827
+ ...options.status ? ["--status", options.status] : [],
6828
+ ...options.compact ? ["--compact"] : [],
6306
6829
  ...options.json ? ["--json"] : []
6307
6830
  ]);
6308
6831
  });
6309
- runs.command("logs <runId>").description("Print logs for a play run.").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (runId, options) => {
6832
+ runs.command("tail <runId>").description("Tail or fetch recent live events for a play run.").option("--json", "Emit JSON output. Also automatic when stdout is piped").option("--compact", "Drop verbose fields from JSON output").option("--cursor <cursor>", "Resume after a previously returned event cursor").action(async (runId, options) => {
6833
+ process.exitCode = await handleRunTail([
6834
+ runId,
6835
+ ...options.json ? ["--json"] : [],
6836
+ ...options.compact ? ["--compact"] : [],
6837
+ ...options.cursor ? ["--cursor", options.cursor] : []
6838
+ ]);
6839
+ });
6840
+ runs.command("logs <runId>").description("Fetch persisted logs for a play run.").option("--limit <count>", "Maximum recent log lines to print without --out", "200").option("--out <path>", "Write the full persisted log stream to a file").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (runId, options) => {
6310
6841
  process.exitCode = await handleRunLogs([
6311
6842
  runId,
6843
+ ...options.limit ? ["--limit", options.limit] : [],
6844
+ ...options.out ? ["--out", options.out] : [],
6845
+ ...options.json ? ["--json"] : []
6846
+ ]);
6847
+ });
6848
+ runs.command("stop <runId>").description("Stop a play run.").option("--reason <text>", "Reason to include with the stop request").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (runId, options) => {
6849
+ process.exitCode = await handleRunStop([
6850
+ runId,
6851
+ ...options.reason ? ["--reason", options.reason] : [],
6852
+ ...options.json ? ["--json"] : []
6853
+ ]);
6854
+ });
6855
+ runs.command("export <runId>").description("Export the completed row output for a play run to CSV.").requiredOption("--out <path>", "Output CSV path").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (runId, options) => {
6856
+ process.exitCode = await handleRunExport([
6857
+ runId,
6858
+ "--out",
6859
+ options.out,
6312
6860
  ...options.json ? ["--json"] : []
6313
6861
  ]);
6314
6862
  });
6315
6863
  }
6316
6864
 
6865
+ // src/cli/commands/tools.ts
6866
+ import { chmodSync, mkdtempSync, writeFileSync as writeFileSync6 } from "fs";
6867
+ import { tmpdir as tmpdir3 } from "os";
6868
+ import { join as join8 } from "path";
6869
+
6317
6870
  // src/tool-output.ts
6318
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
6871
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
6319
6872
  import { homedir as homedir3 } from "os";
6320
6873
  import { join as join7 } from "path";
6321
6874
  function isPlainObject(value) {
@@ -6393,7 +6946,7 @@ function tryConvertToList(payload, options) {
6393
6946
  }
6394
6947
  function ensureOutputDir() {
6395
6948
  const outputDir = join7(homedir3(), ".local", "share", "deepline", "data");
6396
- mkdirSync4(outputDir, { recursive: true });
6949
+ mkdirSync3(outputDir, { recursive: true });
6397
6950
  return outputDir;
6398
6951
  }
6399
6952
  function writeJsonOutputFile(payload, stem) {
@@ -6482,31 +7035,23 @@ async function listTools(args) {
6482
7035
  }
6483
7036
  return 0;
6484
7037
  }
6485
- function toolMatchesQuery(tool, terms) {
6486
- const haystack = [
6487
- tool.toolId,
6488
- tool.provider,
6489
- tool.displayName,
6490
- tool.description,
6491
- tool.operation,
6492
- tool.operationId,
6493
- ...tool.operationAliases ?? [],
6494
- ...tool.categories ?? [],
6495
- tool.inputSchema ? JSON.stringify(tool.inputSchema) : ""
6496
- ].filter(Boolean).join(" ").toLowerCase();
6497
- return terms.every((term) => haystack.includes(term));
6498
- }
6499
- async function searchTools(args) {
6500
- const query = args[0]?.trim();
7038
+ async function searchTools(queryInput, options = {}) {
7039
+ const query = queryInput.trim();
6501
7040
  if (!query) {
6502
7041
  console.error("Usage: deepline tools search <query> [--json]");
6503
7042
  return 1;
6504
7043
  }
6505
- const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
6506
7044
  const client = new DeeplineClient();
6507
- const items = (await client.listTools()).filter((tool) => toolMatchesQuery(tool, terms)).map(toListedTool);
6508
- if (argsWantJson(args)) {
6509
- process.stdout.write(`${JSON.stringify({ tools: items })}
7045
+ const result = await client.searchTools({
7046
+ query,
7047
+ categories: options.categories,
7048
+ searchTerms: options.searchTerms,
7049
+ searchMode: options.searchMode,
7050
+ includeSearchDebug: options.includeSearchDebug
7051
+ });
7052
+ const items = result.tools.map(toListedTool);
7053
+ if (options.json || shouldEmitJson()) {
7054
+ process.stdout.write(`${JSON.stringify({ ...result, tools: items })}
6510
7055
  `);
6511
7056
  return 0;
6512
7057
  }
@@ -6560,11 +7105,14 @@ Common commands:
6560
7105
  ...options.json ? ["--json"] : []
6561
7106
  ]);
6562
7107
  });
6563
- tools.command("search <query>").description("Search available tools.").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (query, options) => {
6564
- process.exitCode = await searchTools([
6565
- query,
6566
- ...options.json ? ["--json"] : []
6567
- ]);
7108
+ tools.command("search <query>").description("Search available tools.").option("--categories <categories>", "Comma-separated categories to filter ranked search").option("--search_terms <terms>", "Structured search terms for ranked search").option("--search-terms <terms>", "Structured search terms for ranked search").option("--search-mode <mode>", "Ranked search mode: v1 or v2").option("--include-search-debug", "Include ranked search debug metadata").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (query, options) => {
7109
+ process.exitCode = await searchTools(query, {
7110
+ json: options.json,
7111
+ categories: options.categories,
7112
+ searchTerms: options.searchTerms ?? options.search_terms,
7113
+ searchMode: options.searchMode === "v1" || options.searchMode === "v2" ? options.searchMode : void 0,
7114
+ includeSearchDebug: Boolean(options.includeSearchDebug)
7115
+ });
6568
7116
  });
6569
7117
  tools.command("get <toolId>").alias("describe").description("Show metadata for a tool.").addHelpText(
6570
7118
  "after",
@@ -6646,7 +7194,7 @@ function printToolDetails(tool, requestedToolId) {
6646
7194
  const operation = typeof tool.operation === "string" ? tool.operation : "";
6647
7195
  const displayBase = operation && operation.startsWith(`${tool.provider}_`) ? operation.slice(String(tool.provider).length + 1) : operation ? `${tool.provider} ${operation}`.trim() : toolId;
6648
7196
  const displayName = titleCase(displayBase || String(tool.displayName || toolId));
6649
- const cost = isRecord2(tool.cost) ? tool.cost : null;
7197
+ const cost = isRecord3(tool.cost) ? tool.cost : null;
6650
7198
  const deeplineCredits = numberField(tool, "deeplineCreditsPerPricingUnit", "deepline_credits_per_pricing_unit");
6651
7199
  const deeplineUsdPerPricingUnit = numberField(tool, "deeplineUsdPerPricingUnit", "deepline_usd_per_pricing_unit");
6652
7200
  const deeplineUsdPerCredit = numberField(tool, "deeplineUsdPerCredit", "deepline_usd_per_credit");
@@ -6691,7 +7239,7 @@ function printToolDetails(tool, requestedToolId) {
6691
7239
  if (stepContributions.length) {
6692
7240
  console.log(" step contributions:");
6693
7241
  for (const item of stepContributions) {
6694
- if (!isRecord2(item)) continue;
7242
+ if (!isRecord3(item)) continue;
6695
7243
  const stepTool = typeof item.tool === "string" ? item.tool.trim() : "";
6696
7244
  const low = typeof item.lowCredits === "number" ? item.lowCredits : null;
6697
7245
  const high = typeof item.highCredits === "number" ? item.highCredits : null;
@@ -6778,12 +7326,12 @@ function printToolCost(input) {
6778
7326
  return false;
6779
7327
  }
6780
7328
  function toolInputFieldsForDisplay(inputSchema) {
6781
- if (Array.isArray(inputSchema.fields)) return inputSchema.fields.filter(isRecord2);
6782
- const jsonSchema = isRecord2(inputSchema.jsonSchema) ? inputSchema.jsonSchema : inputSchema;
6783
- const properties = isRecord2(jsonSchema.properties) ? jsonSchema.properties : {};
7329
+ if (Array.isArray(inputSchema.fields)) return inputSchema.fields.filter(isRecord3);
7330
+ const jsonSchema = isRecord3(inputSchema.jsonSchema) ? inputSchema.jsonSchema : inputSchema;
7331
+ const properties = isRecord3(jsonSchema.properties) ? jsonSchema.properties : {};
6784
7332
  const required = Array.isArray(jsonSchema.required) ? new Set(jsonSchema.required.map(String)) : /* @__PURE__ */ new Set();
6785
7333
  return Object.entries(properties).map(([name, value]) => {
6786
- const property = isRecord2(value) ? value : {};
7334
+ const property = isRecord3(value) ? value : {};
6787
7335
  return {
6788
7336
  name,
6789
7337
  type: typeof property.type === "string" ? property.type : "unknown",
@@ -6810,7 +7358,7 @@ function printJsonPreview(label, payload) {
6810
7358
  }
6811
7359
  function samplePayload(samples, key) {
6812
7360
  const entry = samples[key];
6813
- if (!isRecord2(entry)) return void 0;
7361
+ if (!isRecord3(entry)) return void 0;
6814
7362
  return Object.prototype.hasOwnProperty.call(entry, "payload") ? entry.payload : entry;
6815
7363
  }
6816
7364
  function isPlayTool(tool) {
@@ -6831,7 +7379,7 @@ function formatDecimal(value) {
6831
7379
  function formatUsd(value) {
6832
7380
  return `$${formatDecimal(value)}`;
6833
7381
  }
6834
- function isRecord2(value) {
7382
+ function isRecord3(value) {
6835
7383
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
6836
7384
  }
6837
7385
  function stringField(source, ...keys) {
@@ -6858,7 +7406,7 @@ function arrayField(source, ...keys) {
6858
7406
  function recordField(source, ...keys) {
6859
7407
  for (const key of keys) {
6860
7408
  const value = source[key];
6861
- if (isRecord2(value)) return value;
7409
+ if (isRecord3(value)) return value;
6862
7410
  }
6863
7411
  return {};
6864
7412
  }
@@ -6922,6 +7470,61 @@ function parseExecuteOptions(args) {
6922
7470
  }
6923
7471
  return { toolId, params, outputFormat, noPreview };
6924
7472
  }
7473
+ function safeFileStem(value) {
7474
+ return value.trim().replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "tool";
7475
+ }
7476
+ function shellQuote(value) {
7477
+ return `'${value.replace(/'/g, `'\\''`)}'`;
7478
+ }
7479
+ function powerShellQuote(value) {
7480
+ return `'${value.replace(/'/g, "''")}'`;
7481
+ }
7482
+ function seedToolListScript(input) {
7483
+ const stem = safeFileStem(input.toolId);
7484
+ const fileName = `${stem}-workflow-seed-${Date.now()}.play.ts`;
7485
+ const scriptDir = mkdtempSync(join8(tmpdir3(), "deepline-workflow-seed-"));
7486
+ chmodSync(scriptDir, 448);
7487
+ const scriptPath = join8(scriptDir, fileName);
7488
+ const projectDir = `deepline/projects/${stem}-workflow`;
7489
+ const playName = `${stem}-workflow`;
7490
+ const sampleRows = input.rows.length > 0 ? `${JSON.stringify(input.rows.slice(0, 2)).replace(/\]$/, "")}, ...]` : "[]";
7491
+ const columns = Object.keys(input.rows[0] ?? {}).join(", ");
7492
+ const rowKey = Object.prototype.hasOwnProperty.call(input.rows[0] ?? {}, "id") ? '"id"' : "(row) => JSON.stringify(row)";
7493
+ const script = `import { definePlay } from 'deepline';
7494
+
7495
+ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
7496
+ const result = await ctx.tools.execute({
7497
+ id: ${JSON.stringify(input.toolId)},
7498
+ tool: ${JSON.stringify(input.toolId)},
7499
+ input: ${JSON.stringify(input.payload)},
7500
+ description: ${JSON.stringify(`Seed ${input.toolId} rows for workflow expansion.`)},
7501
+ });
7502
+
7503
+ const list = Object.values(result.lists)[0];
7504
+ const rows = (list?.get() ?? []).slice(0, 100);
7505
+ // ${sampleRows}
7506
+ // columns: ${columns}
7507
+ // .step('email_waterfall', (row, rowCtx) => rowCtx.runPlay('name_domain_email', 'name-and-domain-to-email', { first_name: String(row.first_name ?? ''), last_name: String(row.last_name ?? ''), domain: String(row.domain ?? '') }, { description: 'Resolve email.' }))
7508
+ // .step('phone_waterfall', (row, rowCtx) => rowCtx.runPlay('contact_phone', 'contact-to-phone', { first_name: String(row.first_name ?? ''), last_name: String(row.last_name ?? ''), email: String(row.email ?? '') }, { description: 'Resolve phone.' }))
7509
+ // ctx.map is idempotent by map key + row key; reruns reuse completed rows.
7510
+ const enrichedData = await ctx
7511
+ .map('enriched_data', rows, { key: ${rowKey} })
7512
+ .run({ description: 'Enrich seeded rows.' });
7513
+
7514
+ return {
7515
+ rows: enrichedData,
7516
+ count: await enrichedData.count(),
7517
+ };
7518
+ });
7519
+ `;
7520
+ writeFileSync6(scriptPath, script, { encoding: "utf-8", mode: 384 });
7521
+ return {
7522
+ path: scriptPath,
7523
+ projectDir,
7524
+ macCopyCommand: `mkdir -p ${shellQuote(projectDir)} && cp ${shellQuote(scriptPath)} ${shellQuote(`${projectDir}/${fileName}`)}`,
7525
+ windowsCopyCommand: `New-Item -ItemType Directory -Force -Path ${powerShellQuote(projectDir.replace(/\//g, "\\"))} | Out-Null; Copy-Item -LiteralPath ${powerShellQuote(scriptPath)} -Destination ${powerShellQuote(`${projectDir.replace(/\//g, "\\")}\\${fileName}`)}`
7526
+ };
7527
+ }
6925
7528
  async function executeTool(args) {
6926
7529
  let parsed;
6927
7530
  try {
@@ -6949,7 +7552,7 @@ async function executeTool(args) {
6949
7552
  }
6950
7553
  throw error;
6951
7554
  }
6952
- const rawResponse = await client.executeToolRaw(parsed.toolId, parsed.params);
7555
+ const rawResponse = await client.executeTool(parsed.toolId, parsed.params);
6953
7556
  const listConversion = tryConvertToList(rawResponse, {
6954
7557
  listExtractorPaths: metadata.listExtractorPaths ?? []
6955
7558
  });
@@ -6975,6 +7578,11 @@ async function executeTool(args) {
6975
7578
  return 0;
6976
7579
  }
6977
7580
  const csv = writeCsvOutputFile(listConversion.rows, `${parsed.toolId}_output`);
7581
+ const seededScript = seedToolListScript({
7582
+ toolId: parsed.toolId,
7583
+ payload: parsed.params,
7584
+ rows: listConversion.rows
7585
+ });
6978
7586
  if (parsed.outputFormat === "csv_file") {
6979
7587
  process.stdout.write(`${JSON.stringify({
6980
7588
  extracted_csv: csv.path,
@@ -6983,6 +7591,12 @@ async function executeTool(args) {
6983
7591
  preview: csv.preview,
6984
7592
  list_strategy: listConversion.strategy,
6985
7593
  list_source_path: listConversion.sourcePath,
7594
+ starter_script: seededScript.path,
7595
+ project_dir: seededScript.projectDir,
7596
+ copy_to_project: {
7597
+ macos_linux: seededScript.macCopyCommand,
7598
+ windows_powershell: seededScript.windowsCopyCommand
7599
+ },
6986
7600
  summary
6987
7601
  })}
6988
7602
  `);
@@ -7000,14 +7614,20 @@ async function executeTool(args) {
7000
7614
  console.log(`summary: ${Object.entries(summary).map(([key, value]) => `${key}=${String(value)}`).join(", ")}`);
7001
7615
  }
7002
7616
  console.log(`preview: ${JSON.stringify(csv.preview)}`);
7617
+ console.log(`starter script: ${seededScript.path}`);
7618
+ console.log(
7619
+ "next: Move the script into a project folder and expand it into a Deepline play. Use stable map and step keys so reruns are idempotent: completed rows are reused, and only missing or stale work runs again."
7620
+ );
7621
+ console.log(`macOS/Linux: ${seededScript.macCopyCommand}`);
7622
+ console.log(`Windows PowerShell: ${seededScript.windowsCopyCommand}`);
7003
7623
  return 0;
7004
7624
  }
7005
7625
 
7006
7626
  // src/cli/skills-sync.ts
7007
- import { spawn } from "child_process";
7008
- import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "fs";
7627
+ import { spawn, spawnSync } from "child_process";
7628
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync7 } from "fs";
7009
7629
  import { homedir as homedir4 } from "os";
7010
- import { dirname as dirname7, join as join8 } from "path";
7630
+ import { dirname as dirname7, join as join9 } from "path";
7011
7631
  var CHECK_TIMEOUT_MS2 = 3e3;
7012
7632
  var SDK_SKILL_NAME = "deepline-sdk";
7013
7633
  var SKILL_AGENTS = ["codex", "claude-code", "cursor"];
@@ -7018,7 +7638,7 @@ function shouldSkipSkillsSync() {
7018
7638
  }
7019
7639
  function sdkSkillsVersionPath(baseUrl) {
7020
7640
  const home = process.env.HOME?.trim() || homedir4();
7021
- return join8(home, ".local", "deepline", baseUrlSlug(baseUrl), "sdk-skills", ".version");
7641
+ return join9(home, ".local", "deepline", baseUrlSlug(baseUrl), "sdk-skills", ".version");
7022
7642
  }
7023
7643
  function readLocalSkillsVersion(baseUrl) {
7024
7644
  const path = sdkSkillsVersionPath(baseUrl);
@@ -7031,8 +7651,8 @@ function readLocalSkillsVersion(baseUrl) {
7031
7651
  }
7032
7652
  function writeLocalSkillsVersion(baseUrl, version) {
7033
7653
  const path = sdkSkillsVersionPath(baseUrl);
7034
- mkdirSync5(dirname7(path), { recursive: true });
7035
- writeFileSync6(path, `${version}
7654
+ mkdirSync4(dirname7(path), { recursive: true });
7655
+ writeFileSync7(path, `${version}
7036
7656
  `, "utf-8");
7037
7657
  }
7038
7658
  async function fetchSkillsUpdate(baseUrl, localVersion) {
@@ -7063,9 +7683,26 @@ async function fetchSkillsUpdate(baseUrl, localVersion) {
7063
7683
  clearTimeout(timeout);
7064
7684
  }
7065
7685
  }
7066
- function runSkillsInstall(baseUrl) {
7686
+ function buildSkillsInstallArgs(baseUrl) {
7687
+ const packageUrl = new URL("/.well-known/skills/index.json", baseUrl).toString();
7688
+ return [
7689
+ "--yes",
7690
+ "skills",
7691
+ "add",
7692
+ packageUrl,
7693
+ "--agents",
7694
+ ...SKILL_AGENTS,
7695
+ "--global",
7696
+ "--yes",
7697
+ "--skill",
7698
+ SDK_SKILL_NAME,
7699
+ "--full-depth"
7700
+ ];
7701
+ }
7702
+ function buildBunxSkillsInstallArgs(baseUrl) {
7067
7703
  const packageUrl = new URL("/.well-known/skills/index.json", baseUrl).toString();
7068
- const args = [
7704
+ return [
7705
+ "--bun",
7069
7706
  "skills",
7070
7707
  "add",
7071
7708
  packageUrl,
@@ -7077,8 +7714,40 @@ function runSkillsInstall(baseUrl) {
7077
7714
  SDK_SKILL_NAME,
7078
7715
  "--full-depth"
7079
7716
  ];
7717
+ }
7718
+ function hasCommand(command) {
7719
+ const result = spawnSync(command, ["--version"], {
7720
+ stdio: "ignore",
7721
+ shell: process.platform === "win32"
7722
+ });
7723
+ return result.status === 0;
7724
+ }
7725
+ function shellQuote2(arg) {
7726
+ return `'${arg.replace(/'/g, `'\\''`)}'`;
7727
+ }
7728
+ function resolveSkillsInstallCommands(baseUrl) {
7729
+ const npxArgs = buildSkillsInstallArgs(baseUrl);
7730
+ const npxInstall = {
7731
+ command: "npx",
7732
+ args: npxArgs,
7733
+ manualCommand: `npx ${npxArgs.map(shellQuote2).join(" ")}`
7734
+ };
7735
+ if (hasCommand("bunx")) {
7736
+ const bunxArgs = buildBunxSkillsInstallArgs(baseUrl);
7737
+ return [
7738
+ {
7739
+ command: "bunx",
7740
+ args: bunxArgs,
7741
+ manualCommand: `bunx ${bunxArgs.map(shellQuote2).join(" ")}`
7742
+ },
7743
+ npxInstall
7744
+ ];
7745
+ }
7746
+ return [npxInstall];
7747
+ }
7748
+ function runOneSkillsInstall(install) {
7080
7749
  return new Promise((resolve8) => {
7081
- const child = spawn("npx", args, {
7750
+ const child = spawn(install.command, install.args, {
7082
7751
  stdio: ["ignore", "ignore", "pipe"],
7083
7752
  env: process.env
7084
7753
  });
@@ -7087,37 +7756,111 @@ function runSkillsInstall(baseUrl) {
7087
7756
  stderr += chunk.toString("utf-8");
7088
7757
  });
7089
7758
  child.on("error", (error) => {
7090
- process.stderr.write(`SDK skills sync failed to start: ${error.message}
7091
- `);
7092
- resolve8(false);
7759
+ resolve8({
7760
+ ok: false,
7761
+ detail: `failed to start ${install.command}: ${error.message}`,
7762
+ manualCommand: install.manualCommand
7763
+ });
7093
7764
  });
7094
7765
  child.on("close", (code) => {
7095
7766
  if (code === 0) {
7096
- resolve8(true);
7767
+ resolve8({ ok: true, detail: "", manualCommand: install.manualCommand });
7097
7768
  return;
7098
7769
  }
7099
7770
  const detail = stderr.trim();
7100
- process.stderr.write(
7101
- `SDK skills sync failed${detail ? `: ${detail}` : ""}
7102
- Run manually: npx ${args.map((arg) => arg.includes(" ") ? JSON.stringify(arg) : arg).join(" ")}
7103
- `
7104
- );
7105
- resolve8(false);
7771
+ resolve8({
7772
+ ok: false,
7773
+ detail: detail ? `${install.command}: ${detail}` : `${install.command} exited ${code}`,
7774
+ manualCommand: install.manualCommand
7775
+ });
7106
7776
  });
7107
7777
  });
7108
7778
  }
7779
+ async function runSkillsInstall(baseUrl) {
7780
+ const failures = [];
7781
+ for (const install of resolveSkillsInstallCommands(baseUrl)) {
7782
+ const result = await runOneSkillsInstall(install);
7783
+ if (result.ok) return true;
7784
+ failures.push(result);
7785
+ }
7786
+ const details = failures.map((failure) => failure.detail).filter(Boolean).join("\n");
7787
+ const manualCommand = failures.at(-1)?.manualCommand;
7788
+ process.stderr.write(
7789
+ `SDK skills sync failed${details ? `:
7790
+ ${details}` : ""}
7791
+ ` + (manualCommand ? `Run manually: ${manualCommand}
7792
+ ` : "")
7793
+ );
7794
+ return false;
7795
+ }
7796
+ function writeSdkSkillsStatusLine(line) {
7797
+ const progress = getActiveCliProgress();
7798
+ if (progress) {
7799
+ progress.writeLine(line);
7800
+ return;
7801
+ }
7802
+ process.stderr.write(`${line}
7803
+ `);
7804
+ }
7109
7805
  async function syncSdkSkillsIfNeeded(baseUrl) {
7110
7806
  if (attemptedSync || shouldSkipSkillsSync()) return;
7111
7807
  attemptedSync = true;
7112
7808
  const localVersion = readLocalSkillsVersion(baseUrl);
7113
7809
  const update = await fetchSkillsUpdate(baseUrl, localVersion);
7114
7810
  if (!update?.needsUpdate || !update.remoteVersion) return;
7115
- const progress = getActiveCliProgress();
7116
- progress?.writeLine("SDK skills changed; syncing deepline-sdk skill...") ?? process.stderr.write("SDK skills changed; syncing deepline-sdk skill...\n");
7811
+ writeSdkSkillsStatusLine("SDK skills changed; syncing deepline-sdk skill...");
7117
7812
  const installed = await runSkillsInstall(baseUrl);
7118
7813
  if (!installed) return;
7119
7814
  writeLocalSkillsVersion(baseUrl, update.remoteVersion);
7120
- progress?.writeLine("SDK skills are up to date.") ?? process.stderr.write("SDK skills are up to date.\n");
7815
+ writeSdkSkillsStatusLine("SDK skills are up to date.");
7816
+ }
7817
+
7818
+ // src/cli/trace.ts
7819
+ var cliTraceStartedAt = Date.now();
7820
+ function isTruthyEnv(value) {
7821
+ return value === "1" || value === "true" || value === "yes";
7822
+ }
7823
+ function isCliTraceEnabled() {
7824
+ return isTruthyEnv(process.env.DEEPLINE_CLI_TRACE);
7825
+ }
7826
+ function recordCliTrace(event) {
7827
+ if (!isCliTraceEnabled()) {
7828
+ return;
7829
+ }
7830
+ const now = Date.now();
7831
+ const payload = {
7832
+ ts: now,
7833
+ source: "cli",
7834
+ sinceStartMs: now - cliTraceStartedAt,
7835
+ ...event
7836
+ };
7837
+ process.stderr.write(`[cli-trace] ${JSON.stringify(payload)}
7838
+ `);
7839
+ }
7840
+ async function traceCliSpan(phase, fields, run) {
7841
+ if (!isCliTraceEnabled()) {
7842
+ return run();
7843
+ }
7844
+ const startedAt = Date.now();
7845
+ try {
7846
+ const result = await run();
7847
+ recordCliTrace({
7848
+ phase,
7849
+ ms: Date.now() - startedAt,
7850
+ ok: true,
7851
+ ...fields
7852
+ });
7853
+ return result;
7854
+ } catch (error) {
7855
+ recordCliTrace({
7856
+ phase,
7857
+ ms: Date.now() - startedAt,
7858
+ ok: false,
7859
+ error: error instanceof Error ? error.message : String(error),
7860
+ ...fields
7861
+ });
7862
+ throw error;
7863
+ }
7121
7864
  }
7122
7865
 
7123
7866
  // src/cli/index.ts
@@ -7141,7 +7884,7 @@ async function main() {
7141
7884
  if (printStartupPhase) {
7142
7885
  progress?.phase("loading deepline cli");
7143
7886
  }
7144
- const program = new Command2();
7887
+ const program = new Command();
7145
7888
  program.name("deepline").description("Deepline CLI (TypeScript SDK)").version(SDK_VERSION, "-v, --version", "Show version").showHelpAfterError().showSuggestionAfterError(true).addHelpText(
7146
7889
  "after",
7147
7890
  `
@@ -7231,4 +7974,3 @@ Output:
7231
7974
  process.exit(process.exitCode ?? 0);
7232
7975
  }
7233
7976
  main();
7234
- //# sourceMappingURL=index.mjs.map