deepline 0.1.12 → 0.1.20

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 (82) hide show
  1. package/README.md +14 -6
  2. package/dist/cli/index.js +1346 -717
  3. package/dist/cli/index.mjs +1342 -713
  4. package/dist/index.d.mts +199 -23
  5. package/dist/index.d.ts +199 -23
  6. package/dist/index.js +221 -14
  7. package/dist/index.mjs +221 -14
  8. package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +214 -77
  9. package/dist/repo/apps/play-runner-workers/src/dedup-do.ts +85 -60
  10. package/dist/repo/apps/play-runner-workers/src/entry.ts +385 -66
  11. package/dist/repo/sdk/src/client.ts +237 -0
  12. package/dist/repo/sdk/src/config.ts +125 -8
  13. package/dist/repo/sdk/src/http.ts +29 -5
  14. package/dist/repo/sdk/src/play.ts +19 -36
  15. package/dist/repo/sdk/src/plays/bundle-play-file.ts +22 -8
  16. package/dist/repo/sdk/src/plays/local-file-discovery.ts +207 -160
  17. package/dist/repo/sdk/src/types.ts +25 -0
  18. package/dist/repo/sdk/src/version.ts +2 -2
  19. package/dist/repo/shared_libs/play-runtime/tool-result.ts +237 -145
  20. package/dist/repo/shared_libs/plays/bundling/index.ts +206 -229
  21. package/dist/repo/shared_libs/plays/dataset.ts +28 -0
  22. package/dist/repo/shared_libs/plays/row-identity.ts +59 -4
  23. package/package.json +5 -4
  24. package/dist/cli/index.js.map +0 -1
  25. package/dist/cli/index.mjs.map +0 -1
  26. package/dist/index.js.map +0 -1
  27. package/dist/index.mjs.map +0 -1
  28. package/dist/repo/apps/play-runner-workers/src/runtime/README.md +0 -21
  29. package/dist/repo/apps/play-runner-workers/src/runtime/batching.ts +0 -177
  30. package/dist/repo/apps/play-runner-workers/src/runtime/execution-plan.ts +0 -52
  31. package/dist/repo/apps/play-runner-workers/src/runtime/tool-batch.ts +0 -100
  32. package/dist/repo/sdk/src/cli/commands/auth.ts +0 -500
  33. package/dist/repo/sdk/src/cli/commands/billing.ts +0 -188
  34. package/dist/repo/sdk/src/cli/commands/csv.ts +0 -123
  35. package/dist/repo/sdk/src/cli/commands/db.ts +0 -119
  36. package/dist/repo/sdk/src/cli/commands/feedback.ts +0 -40
  37. package/dist/repo/sdk/src/cli/commands/org.ts +0 -117
  38. package/dist/repo/sdk/src/cli/commands/play.ts +0 -3441
  39. package/dist/repo/sdk/src/cli/commands/tools.ts +0 -687
  40. package/dist/repo/sdk/src/cli/dataset-stats.ts +0 -415
  41. package/dist/repo/sdk/src/cli/index.ts +0 -148
  42. package/dist/repo/sdk/src/cli/progress.ts +0 -149
  43. package/dist/repo/sdk/src/cli/skills-sync.ts +0 -141
  44. package/dist/repo/sdk/src/cli/trace.ts +0 -61
  45. package/dist/repo/sdk/src/cli/utils.ts +0 -145
  46. package/dist/repo/sdk/src/compat.ts +0 -77
  47. package/dist/repo/shared_libs/observability/node-tracing.ts +0 -129
  48. package/dist/repo/shared_libs/observability/tracing.ts +0 -98
  49. package/dist/repo/shared_libs/play-runtime/context.ts +0 -4242
  50. package/dist/repo/shared_libs/play-runtime/ctx-contract.ts +0 -250
  51. package/dist/repo/shared_libs/play-runtime/ctx-types.ts +0 -725
  52. package/dist/repo/shared_libs/play-runtime/dataset-id.ts +0 -10
  53. package/dist/repo/shared_libs/play-runtime/db-session-crypto.ts +0 -304
  54. package/dist/repo/shared_libs/play-runtime/db-session.ts +0 -462
  55. package/dist/repo/shared_libs/play-runtime/live-events.ts +0 -214
  56. package/dist/repo/shared_libs/play-runtime/live-state-contract.ts +0 -50
  57. package/dist/repo/shared_libs/play-runtime/map-execution-frame.ts +0 -114
  58. package/dist/repo/shared_libs/play-runtime/map-row-identity.ts +0 -158
  59. package/dist/repo/shared_libs/play-runtime/progress-emitter.ts +0 -172
  60. package/dist/repo/shared_libs/play-runtime/protocol.ts +0 -121
  61. package/dist/repo/shared_libs/play-runtime/public-play-contract.ts +0 -42
  62. package/dist/repo/shared_libs/play-runtime/result-normalization.ts +0 -33
  63. package/dist/repo/shared_libs/play-runtime/runtime-api.ts +0 -1873
  64. package/dist/repo/shared_libs/play-runtime/runtime-constraints.ts +0 -2
  65. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-neon-serverless.ts +0 -201
  66. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-pg.ts +0 -48
  67. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver.ts +0 -84
  68. package/dist/repo/shared_libs/play-runtime/static-pipeline-types.ts +0 -147
  69. package/dist/repo/shared_libs/play-runtime/suspension.ts +0 -68
  70. package/dist/repo/shared_libs/play-runtime/tracing.ts +0 -31
  71. package/dist/repo/shared_libs/play-runtime/waterfall-replay.ts +0 -75
  72. package/dist/repo/shared_libs/play-runtime/worker-api-types.ts +0 -140
  73. package/dist/repo/shared_libs/plays/artifact-transport.ts +0 -14
  74. package/dist/repo/shared_libs/plays/artifact-types.ts +0 -49
  75. package/dist/repo/shared_libs/plays/compiler-manifest.ts +0 -186
  76. package/dist/repo/shared_libs/plays/definition.ts +0 -264
  77. package/dist/repo/shared_libs/plays/file-refs.ts +0 -11
  78. package/dist/repo/shared_libs/plays/rate-limit-scheduler.ts +0 -206
  79. package/dist/repo/shared_libs/plays/resolve-static-pipeline.ts +0 -164
  80. package/dist/repo/shared_libs/plays/runtime-validation.ts +0 -395
  81. package/dist/repo/shared_libs/temporal/constants.ts +0 -39
  82. package/dist/repo/shared_libs/temporal/preview-config.ts +0 -153
package/dist/cli/index.js CHANGED
@@ -70,6 +70,16 @@ var ConfigError = class extends DeeplineError {
70
70
  var PROD_URL = "https://code.deepline.com";
71
71
  var DEFAULT_TIMEOUT = 6e4;
72
72
  var DEFAULT_MAX_RETRIES = 3;
73
+ var ACTIVE_DEEPLINE_ENV_FILE = ".env.deepline";
74
+ function isProdBaseUrl(baseUrl) {
75
+ return baseUrl.trim().replace(/\/$/, "") === PROD_URL;
76
+ }
77
+ function profileNameForBaseUrl(baseUrl) {
78
+ return isProdBaseUrl(baseUrl) ? "prod" : "dev";
79
+ }
80
+ function projectEnvStartDir() {
81
+ return process.env.DEEPLINE_PROJECT_ENV_DIR?.trim() || process.cwd();
82
+ }
73
83
  function baseUrlSlug(baseUrl) {
74
84
  let url;
75
85
  try {
@@ -105,16 +115,52 @@ function parseEnvFile(filePath) {
105
115
  }
106
116
  return env;
107
117
  }
108
- function findNearestWorktreeEnv(startDir = process.cwd()) {
118
+ function findNearestEnvFile(names, startDir = process.cwd()) {
109
119
  let current = (0, import_node_path.resolve)(startDir);
110
120
  while (true) {
111
- const values = parseEnvFile((0, import_node_path.join)(current, ".env.worktree"));
112
- if (Object.keys(values).length > 0) return values;
121
+ for (const name of names) {
122
+ const filePath = (0, import_node_path.join)(current, name);
123
+ if ((0, import_node_fs.existsSync)(filePath)) return filePath;
124
+ }
113
125
  const parent = (0, import_node_path.dirname)(current);
114
- if (parent === current) return {};
126
+ if (parent === current) return null;
115
127
  current = parent;
116
128
  }
117
129
  }
130
+ function findNearestEnv(names, startDir = process.cwd()) {
131
+ const filePath = findNearestEnvFile(names, startDir);
132
+ return filePath ? parseEnvFile(filePath) : {};
133
+ }
134
+ function findNearestWorktreeEnv(startDir = process.cwd()) {
135
+ return findNearestEnv([".env.worktree"], startDir);
136
+ }
137
+ function resolveProfileEnvFileNames() {
138
+ const explicitProfile = process.env.DEEPLINE_ENV_PROFILE?.trim() || process.env.DEEPLINE_PROFILE?.trim() || "";
139
+ const names = [];
140
+ if (explicitProfile) names.push(`.env.deepline.${explicitProfile}`);
141
+ const nodeEnv = process.env.NODE_ENV?.trim();
142
+ if (nodeEnv === "production") names.push(".env.deepline.prod");
143
+ else if (nodeEnv === "staging") names.push(".env.deepline.staging");
144
+ names.push(ACTIVE_DEEPLINE_ENV_FILE);
145
+ return names;
146
+ }
147
+ function resolveProjectAppEnvFileNames() {
148
+ const nodeEnv = process.env.NODE_ENV?.trim();
149
+ const names = [];
150
+ if (nodeEnv === "production") names.push(".env.prod");
151
+ if (nodeEnv === "staging") names.push(".env.staging");
152
+ names.push(".env.local", ".env");
153
+ return names;
154
+ }
155
+ function resolveBaseUrlFromEnvValues(env) {
156
+ return env.DEEPLINE_ORIGIN_URL?.trim() || env.DEEPLINE_API_BASE_URL?.trim() || "";
157
+ }
158
+ function loadProjectDeeplineEnv() {
159
+ return findNearestEnv(resolveProfileEnvFileNames(), projectEnvStartDir());
160
+ }
161
+ function loadProjectAppEnv() {
162
+ return findNearestEnv(resolveProjectAppEnvFileNames(), projectEnvStartDir());
163
+ }
118
164
  function normalizeWorktreeBaseUrl(baseUrl, worktreeEnv = findNearestWorktreeEnv()) {
119
165
  const trimmed = baseUrl.trim().replace(/\/$/, "");
120
166
  if (!trimmed) return trimmed;
@@ -166,6 +212,10 @@ function autoDetectBaseUrl() {
166
212
  if (envOrigin) return normalizeWorktreeBaseUrl(envOrigin);
167
213
  const envBase = process.env.DEEPLINE_API_BASE_URL?.trim();
168
214
  if (envBase) return normalizeWorktreeBaseUrl(envBase);
215
+ const projectDeeplineBaseUrl = resolveBaseUrlFromEnvValues(loadProjectDeeplineEnv());
216
+ if (projectDeeplineBaseUrl) return normalizeWorktreeBaseUrl(projectDeeplineBaseUrl);
217
+ const projectAppBaseUrl = resolveBaseUrlFromEnvValues(loadProjectAppEnv());
218
+ if (projectAppBaseUrl) return normalizeWorktreeBaseUrl(projectAppBaseUrl);
169
219
  const worktreeBaseUrl = resolveWorktreeBaseUrl();
170
220
  if (worktreeBaseUrl) return worktreeBaseUrl;
171
221
  const globalEnv = loadGlobalCliEnv();
@@ -177,7 +227,9 @@ function resolveConfig(options) {
177
227
  const requestedBaseUrl = options?.baseUrl?.trim() || autoDetectBaseUrl();
178
228
  const baseUrl = normalizeWorktreeBaseUrl(requestedBaseUrl);
179
229
  const cliEnv = loadCliEnv(baseUrl);
180
- const apiKey = options?.apiKey?.trim() || process.env.DEEPLINE_API_KEY?.trim() || cliEnv.DEEPLINE_API_KEY || "";
230
+ const projectDeeplineEnv = loadProjectDeeplineEnv();
231
+ const projectAppEnv = loadProjectAppEnv();
232
+ const apiKey = options?.apiKey?.trim() || process.env.DEEPLINE_API_KEY?.trim() || projectDeeplineEnv.DEEPLINE_API_KEY || projectAppEnv.DEEPLINE_API_KEY || cliEnv.DEEPLINE_API_KEY || "";
181
233
  if (!apiKey) {
182
234
  throw new ConfigError(
183
235
  `No API key found. Set DEEPLINE_API_KEY env var, pass apiKey option, or run: deepline auth register`
@@ -190,10 +242,32 @@ function resolveConfig(options) {
190
242
  maxRetries: options?.maxRetries ?? DEFAULT_MAX_RETRIES
191
243
  };
192
244
  }
245
+ function mergeEnvFile(filePath, values) {
246
+ const existing = (0, import_node_fs.existsSync)(filePath) ? parseEnvFile(filePath) : {};
247
+ const merged = { ...existing, ...values };
248
+ const dir = (0, import_node_path.dirname)(filePath);
249
+ if (!(0, import_node_fs.existsSync)(dir)) (0, import_node_fs.mkdirSync)(dir, { recursive: true });
250
+ const lines = Object.entries(merged).filter(([, value]) => value !== "").map(([key, value]) => `${key}=${value}`);
251
+ (0, import_node_fs.writeFileSync)(filePath, `${lines.join("\n")}
252
+ `, "utf-8");
253
+ }
254
+ function saveProjectDeeplineEnvValues(baseUrl, values, startDir = projectEnvStartDir()) {
255
+ const root = (0, import_node_path.resolve)(startDir);
256
+ const profile = profileNameForBaseUrl(baseUrl);
257
+ const files = [
258
+ (0, import_node_path.join)(root, ACTIVE_DEEPLINE_ENV_FILE),
259
+ (0, import_node_path.join)(root, `.env.deepline.${profile}`)
260
+ ];
261
+ if (profile === "dev") files.push((0, import_node_path.join)(root, ".env"));
262
+ for (const filePath of files) {
263
+ mergeEnvFile(filePath, values);
264
+ }
265
+ return files;
266
+ }
193
267
 
194
268
  // src/version.ts
195
- var SDK_VERSION = "0.1.12";
196
- var SDK_API_CONTRACT = "2026-04-plays-v1";
269
+ var SDK_VERSION = "0.1.20";
270
+ var SDK_API_CONTRACT = "2026-05-runs-v2";
197
271
 
198
272
  // ../shared_libs/play-runtime/coordinator-headers.ts
199
273
  var COORDINATOR_INTERNAL_TOKEN_HEADER = "x-deepline-internal-token";
@@ -299,7 +373,8 @@ var HttpClient = class {
299
373
  parsed = body;
300
374
  }
301
375
  if (!response.ok) {
302
- const msg = typeof parsed === "object" && parsed && "error" in parsed ? String(parsed.error) : `HTTP ${response.status}`;
376
+ const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
377
+ const msg = typeof errorValue === "string" ? errorValue : errorValue && typeof errorValue === "object" && "message" in errorValue && typeof errorValue.message === "string" ? errorValue.message : typeof parsed === "object" && parsed && "message" in parsed && typeof parsed.message === "string" ? parsed.message : `HTTP ${response.status}`;
303
378
  throw new DeeplineError(msg, response.status, "API_ERROR", {
304
379
  response: parsed
305
380
  });
@@ -386,8 +461,12 @@ var HttpClient = class {
386
461
  * @param path - API path
387
462
  * @param body - Request body (will be JSON-serialized)
388
463
  */
389
- async post(path, body) {
390
- return this.request(path, { method: "POST", body });
464
+ async post(path, body, headers) {
465
+ return this.request(path, {
466
+ method: "POST",
467
+ body,
468
+ headers
469
+ });
391
470
  }
392
471
  /**
393
472
  * Send a DELETE request.
@@ -467,6 +546,7 @@ function sleep(ms) {
467
546
 
468
547
  // src/client.ts
469
548
  var TERMINAL_PLAY_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
549
+ var INCLUDE_TOOL_METADATA_HEADER = "x-deepline-include-tool-metadata";
470
550
  function isRecord(value) {
471
551
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
472
552
  }
@@ -499,6 +579,7 @@ function mapLegacyTemporalStatus(status) {
499
579
  var DeeplineClient = class {
500
580
  http;
501
581
  config;
582
+ runs;
502
583
  /**
503
584
  * @param options - Optional overrides for API key, base URL, timeout, and retries.
504
585
  * @throws {@link ConfigError} if no API key can be resolved from any source.
@@ -506,6 +587,13 @@ var DeeplineClient = class {
506
587
  constructor(options) {
507
588
  this.config = resolveConfig(options);
508
589
  this.http = new HttpClient(this.config);
590
+ this.runs = {
591
+ get: (runId) => this.getRunStatus(runId),
592
+ list: (options2) => this.listRuns(options2),
593
+ tail: (runId, options2) => this.tailRun(runId, options2),
594
+ logs: (runId, options2) => this.getRunLogs(runId, options2),
595
+ stop: (runId, options2) => this.stopRun(runId, options2)
596
+ };
509
597
  }
510
598
  /** The resolved base URL this client is targeting (e.g. `"http://localhost:3000"`). */
511
599
  get baseUrl() {
@@ -594,6 +682,31 @@ var DeeplineClient = class {
594
682
  );
595
683
  return res.tools;
596
684
  }
685
+ /**
686
+ * Search available tools using Deepline's ranked backend search.
687
+ *
688
+ * This is the same discovery surface used by the legacy CLI: it ranks across
689
+ * tool metadata, categories, agent guidance, and input schema fields.
690
+ */
691
+ async searchTools(options = {}) {
692
+ const params = new URLSearchParams();
693
+ const query = options.query?.trim() ?? "";
694
+ params.set("q", query);
695
+ params.set(
696
+ "include_search_debug",
697
+ options.includeSearchDebug ? "true" : "false"
698
+ );
699
+ params.set("search_mode", options.searchMode ?? "v2");
700
+ if (options.categories?.trim()) {
701
+ params.set("categories", options.categories.trim());
702
+ }
703
+ if (options.searchTerms?.trim()) {
704
+ params.set("search_terms", options.searchTerms.trim());
705
+ }
706
+ return this.http.get(
707
+ `/api/v2/integrations/list?${params.toString()}`
708
+ );
709
+ }
597
710
  /**
598
711
  * Get detailed metadata for a single tool.
599
712
  *
@@ -629,12 +742,17 @@ var DeeplineClient = class {
629
742
  * Top-level fields such as `status`, `job_id`, and `billing` describe the
630
743
  * Deepline execution.
631
744
  */
632
- async executeTool(toolId, input) {
745
+ async executeTool(toolId, input, options) {
746
+ const headers = options?.includeToolMetadata ? { [INCLUDE_TOOL_METADATA_HEADER]: "true" } : void 0;
633
747
  return this.http.post(
634
748
  `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
635
- { payload: input }
749
+ { payload: input },
750
+ headers
636
751
  );
637
752
  }
753
+ async executeToolRaw(toolId, input, options) {
754
+ return this.executeTool(toolId, input, options);
755
+ }
638
756
  async queryCustomerDb(input) {
639
757
  return this.http.post("/api/v2/db/query", {
640
758
  sql: input.sql,
@@ -683,6 +801,7 @@ var DeeplineClient = class {
683
801
  ...request.revisionId ? { revisionId: request.revisionId } : {},
684
802
  ...request.artifactStorageKey ? { artifactStorageKey: request.artifactStorageKey } : {},
685
803
  ...request.sourceCode ? { sourceCode: request.sourceCode } : {},
804
+ ...request.sourceFiles ? { sourceFiles: request.sourceFiles } : {},
686
805
  ..."staticPipeline" in request ? { staticPipeline: request.staticPipeline } : {},
687
806
  ...request.artifactHash ? { artifactHash: request.artifactHash } : {},
688
807
  ...request.graphHash ? { graphHash: request.graphHash } : {},
@@ -707,6 +826,7 @@ var DeeplineClient = class {
707
826
  ...request.revisionId ? { revisionId: request.revisionId } : {},
708
827
  ...request.artifactStorageKey ? { artifactStorageKey: request.artifactStorageKey } : {},
709
828
  ...request.sourceCode ? { sourceCode: request.sourceCode } : {},
829
+ ...request.sourceFiles ? { sourceFiles: request.sourceFiles } : {},
710
830
  ..."staticPipeline" in request ? { staticPipeline: request.staticPipeline } : {},
711
831
  ...request.artifactHash ? { artifactHash: request.artifactHash } : {},
712
832
  ...request.graphHash ? { graphHash: request.graphHash } : {},
@@ -743,6 +863,7 @@ var DeeplineClient = class {
743
863
  const compilerManifest = input.compilerManifest ?? await this.compilePlayManifest({
744
864
  name: input.name,
745
865
  sourceCode: input.sourceCode,
866
+ sourceFiles: input.sourceFiles,
746
867
  artifact: input.artifact
747
868
  });
748
869
  return this.http.post("/api/v2/plays/artifacts", {
@@ -757,6 +878,7 @@ var DeeplineClient = class {
757
878
  compilerManifest: artifact.compilerManifest ?? await this.compilePlayManifest({
758
879
  name: artifact.name,
759
880
  sourceCode: artifact.sourceCode,
881
+ sourceFiles: artifact.sourceFiles,
760
882
  artifact: artifact.artifact
761
883
  })
762
884
  }))
@@ -783,11 +905,13 @@ var DeeplineClient = class {
783
905
  const compilerManifest = input.compilerManifest ?? await this.compilePlayManifest({
784
906
  name: input.name,
785
907
  sourceCode: input.sourceCode,
908
+ sourceFiles: input.sourceFiles,
786
909
  artifact: input.artifact
787
910
  });
788
911
  const registeredArtifact = await this.registerPlayArtifact({
789
912
  name: input.name,
790
913
  sourceCode: input.sourceCode,
914
+ sourceFiles: input.sourceFiles,
791
915
  artifact: input.artifact,
792
916
  compilerManifest,
793
917
  publish: false
@@ -847,11 +971,13 @@ var DeeplineClient = class {
847
971
  const compilerManifest = options?.compilerManifest ?? await this.compilePlayManifest({
848
972
  name,
849
973
  sourceCode,
974
+ sourceFiles: options?.sourceFiles,
850
975
  artifact
851
976
  });
852
977
  const registeredArtifact = await this.registerPlayArtifact({
853
978
  name,
854
979
  sourceCode,
980
+ sourceFiles: options?.sourceFiles,
855
981
  artifact,
856
982
  compilerManifest,
857
983
  publish: false
@@ -1035,6 +1161,112 @@ var DeeplineClient = class {
1035
1161
  );
1036
1162
  return response.runs ?? [];
1037
1163
  }
1164
+ /**
1165
+ * Get a run by id using the public runs resource model.
1166
+ *
1167
+ * This is the SDK equivalent of:
1168
+ *
1169
+ * ```bash
1170
+ * deepline runs get <run-id> --json
1171
+ * ```
1172
+ */
1173
+ async getRunStatus(runId) {
1174
+ const response = await this.http.get(
1175
+ `/api/v2/runs/${encodeURIComponent(runId)}`
1176
+ );
1177
+ return normalizePlayStatus(response);
1178
+ }
1179
+ /**
1180
+ * List play runs using the public runs resource model.
1181
+ *
1182
+ * This is the SDK equivalent of:
1183
+ *
1184
+ * ```bash
1185
+ * deepline runs list --play <play-name> --status failed --json
1186
+ * ```
1187
+ */
1188
+ async listRuns(options) {
1189
+ const playName = options.play.trim();
1190
+ if (!playName) {
1191
+ throw new Error("runs.list requires options.play.");
1192
+ }
1193
+ const params = new URLSearchParams({ play: playName });
1194
+ const status = options.status?.trim();
1195
+ if (status) {
1196
+ params.set("status", status);
1197
+ }
1198
+ const response = await this.http.get(
1199
+ `/api/v2/runs?${params.toString()}`
1200
+ );
1201
+ return response.runs ?? [];
1202
+ }
1203
+ /**
1204
+ * Fetch the lightweight tail status for a run using the public runs resource model.
1205
+ *
1206
+ * This is the SDK equivalent of:
1207
+ *
1208
+ * ```bash
1209
+ * deepline runs tail <run-id> --json
1210
+ * ```
1211
+ */
1212
+ async tailRun(runId, options) {
1213
+ 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;
1214
+ const params = new URLSearchParams();
1215
+ if (Number.isFinite(afterLogIndex)) {
1216
+ params.set("afterLogIndex", String(Number(afterLogIndex)));
1217
+ }
1218
+ if (typeof options?.waitMs === "number") {
1219
+ params.set("waitMs", String(options.waitMs));
1220
+ }
1221
+ if (options?.terminalOnly) {
1222
+ params.set("terminalOnly", "true");
1223
+ }
1224
+ const suffix = params.toString() ? `?${params.toString()}` : "";
1225
+ const response = await this.http.get(
1226
+ `/api/v2/runs/${encodeURIComponent(runId)}/tail${suffix}`
1227
+ );
1228
+ return normalizePlayStatus(response);
1229
+ }
1230
+ /**
1231
+ * Fetch persisted logs for a run using the public runs resource model.
1232
+ *
1233
+ * This is the SDK equivalent of:
1234
+ *
1235
+ * ```bash
1236
+ * deepline runs logs <run-id> --limit 200 --json
1237
+ * ```
1238
+ */
1239
+ async getRunLogs(runId, options) {
1240
+ const status = await this.getRunStatus(runId);
1241
+ const logs = status.progress?.logs ?? [];
1242
+ const limit = typeof options?.limit === "number" && Number.isFinite(options.limit) ? Math.max(0, Math.trunc(options.limit)) : 200;
1243
+ const entries = logs.slice(Math.max(0, logs.length - limit));
1244
+ return {
1245
+ runId: status.runId,
1246
+ totalCount: logs.length,
1247
+ returnedCount: entries.length,
1248
+ firstSequence: logs.length === 0 ? null : logs.length - entries.length + 1,
1249
+ lastSequence: logs.length === 0 ? null : logs.length,
1250
+ truncated: logs.length > entries.length,
1251
+ hasMore: logs.length > entries.length,
1252
+ entries
1253
+ };
1254
+ }
1255
+ /**
1256
+ * Stop a run by id using the public runs resource model.
1257
+ *
1258
+ * This is the SDK equivalent of:
1259
+ *
1260
+ * ```bash
1261
+ * deepline runs stop <run-id> --reason "stale lock" --json
1262
+ * ```
1263
+ */
1264
+ async stopRun(runId, options) {
1265
+ return this.http.post(
1266
+ `/api/v2/runs/${encodeURIComponent(runId)}/stop`,
1267
+ options?.reason ? { reason: options.reason } : {}
1268
+ );
1269
+ }
1038
1270
  async listPlays() {
1039
1271
  const response = await this.http.get(
1040
1272
  "/api/v2/plays"
@@ -1421,6 +1653,7 @@ function saveEnvValues(values, baseUrl) {
1421
1653
  const merged = { ...existing, ...values };
1422
1654
  const lines = Object.entries(merged).filter(([, v]) => v !== "").map(([k, v]) => `${k}=${v}`);
1423
1655
  (0, import_node_fs3.writeFileSync)(filePath, lines.join("\n") + "\n", "utf-8");
1656
+ saveProjectDeeplineEnvValues(baseUrl, values);
1424
1657
  }
1425
1658
  async function httpJson(method, url, apiKey, body) {
1426
1659
  const headers = { "Content-Type": "application/json" };
@@ -2021,6 +2254,9 @@ function rowArray(value) {
2021
2254
  function readNumber(value) {
2022
2255
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : null;
2023
2256
  }
2257
+ function numericStat(value) {
2258
+ return readNumber(value) ?? 0;
2259
+ }
2024
2260
  function inferColumns(rows) {
2025
2261
  const columns = [];
2026
2262
  const seen = /* @__PURE__ */ new Set();
@@ -2100,6 +2336,31 @@ function extractCanonicalRowsInfo(statusOrResult) {
2100
2336
  function percentText(numerator, denominator) {
2101
2337
  return denominator > 0 ? `${numerator}/${denominator} (${Math.round(100 * numerator / denominator)}%)` : "0/0 (0%)";
2102
2338
  }
2339
+ function isDatasetExecutionStatsInput(value) {
2340
+ return isRecord2(value) && isRecord2(value.columnStats) && Object.values(value.columnStats).every(isRecord2);
2341
+ }
2342
+ function extractDatasetExecutionStats(statusOrResult) {
2343
+ if (!isRecord2(statusOrResult)) {
2344
+ return null;
2345
+ }
2346
+ const direct = statusOrResult.dataset_execution_stats;
2347
+ if (isDatasetExecutionStatsInput(direct)) {
2348
+ return direct;
2349
+ }
2350
+ const nested = isRecord2(statusOrResult.result) ? statusOrResult.result.dataset_execution_stats : null;
2351
+ return isDatasetExecutionStatsInput(nested) ? nested : null;
2352
+ }
2353
+ function formatExecutionStats(raw, denominator) {
2354
+ return {
2355
+ queued: percentText(numericStat(raw.queued), denominator),
2356
+ running: percentText(numericStat(raw.running), denominator),
2357
+ "completed:executed": percentText(numericStat(raw.completed), denominator),
2358
+ "completed:reused": percentText(numericStat(raw.cached), denominator),
2359
+ "skipped:condition": percentText(numericStat(raw.skipped), denominator),
2360
+ "skipped:missed": percentText(numericStat(raw.missed), denominator),
2361
+ failed: percentText(numericStat(raw.failed), denominator)
2362
+ };
2363
+ }
2103
2364
  function countPercentText(count, denominator) {
2104
2365
  return denominator > 0 ? `${count} (${Math.round(100 * count / denominator)}%)` : "0 (0%)";
2105
2366
  }
@@ -2192,7 +2453,7 @@ function compactCell(value) {
2192
2453
  }
2193
2454
  return compactScalar(parsed, 120);
2194
2455
  }
2195
- function buildDatasetStats(rows, totalRows = rows.length, columns = inferColumns(rows)) {
2456
+ function buildDatasetStats(rows, totalRows = rows.length, columns = inferColumns(rows), executionStats) {
2196
2457
  const sanitized = sanitizeCsvProjectionInfo({ rows, columns });
2197
2458
  const columnStats = {};
2198
2459
  for (const column of sanitized.columns) {
@@ -2220,6 +2481,10 @@ function buildDatasetStats(rows, totalRows = rows.length, columns = inferColumns
2220
2481
  non_empty: percentText(nonEmpty, denominator),
2221
2482
  unique: valueCounts.size
2222
2483
  };
2484
+ const rawExecutionStats = executionStats?.columnStats[column];
2485
+ if (rawExecutionStats) {
2486
+ stat3.execution = formatExecutionStats(rawExecutionStats, totalRows);
2487
+ }
2223
2488
  if (sampleValue !== void 0 && sampleValueType) {
2224
2489
  stat3.sample_value = sampleValue;
2225
2490
  stat3.sample_type = sampleValueType;
@@ -2566,7 +2831,6 @@ var import_node_os4 = require("os");
2566
2831
  var import_node_path5 = require("path");
2567
2832
  var import_node_module = require("module");
2568
2833
  var import_esbuild = require("esbuild");
2569
- var import_typescript = __toESM(require("typescript"));
2570
2834
 
2571
2835
  // ../shared_libs/play-runtime/backend.ts
2572
2836
  var PLAY_RUNTIME_BACKENDS = {
@@ -2651,56 +2915,6 @@ function formatEsbuildMessage(message) {
2651
2915
  const location = message.location ? `${message.location.file}:${message.location.line}:${message.location.column}` : null;
2652
2916
  return location ? `${location} ${message.text}` : message.text;
2653
2917
  }
2654
- function formatTypeScriptDiagnostic(diagnostic) {
2655
- if (!diagnostic.file) {
2656
- const message2 = import_typescript.default.flattenDiagnosticMessageText(diagnostic.messageText, "\n").trim();
2657
- return message2 || null;
2658
- }
2659
- const start = diagnostic.start ?? 0;
2660
- const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(start);
2661
- const message = import_typescript.default.flattenDiagnosticMessageText(diagnostic.messageText, "\n").trim();
2662
- if (!message) {
2663
- return null;
2664
- }
2665
- return `${diagnostic.file.fileName}:${line + 1}:${character + 1} ${message}`;
2666
- }
2667
- function resolveBundledTypeRoots() {
2668
- try {
2669
- return [(0, import_node_path5.dirname)((0, import_node_path5.dirname)(playArtifactRequire.resolve("@types/node/package.json")))];
2670
- } catch {
2671
- return [];
2672
- }
2673
- }
2674
- function typecheckPlaySource(input, adapter) {
2675
- const rootNames = Array.from(
2676
- /* @__PURE__ */ new Set([
2677
- ...input.importPolicy.localFiles,
2678
- ...input.importedPlayDependencies.map((dependency) => dependency.filePath)
2679
- ])
2680
- );
2681
- const sdkTypesPath = adapter.sdkTypesEntryFile ?? adapter.sdkEntryFile;
2682
- const program = import_typescript.default.createProgram(rootNames, {
2683
- target: import_typescript.default.ScriptTarget.ES2023,
2684
- // SDK source uses fetch/RequestInit/URL and node-aware config helpers.
2685
- // The play runtime import policy below still bans Node modules from play
2686
- // source for workers_edge bundles.
2687
- lib: ["lib.es2023.d.ts", "lib.dom.d.ts"],
2688
- module: import_typescript.default.ModuleKind.ESNext,
2689
- moduleResolution: import_typescript.default.ModuleResolutionKind.Bundler,
2690
- paths: { deepline: [sdkTypesPath] },
2691
- strict: true,
2692
- skipLibCheck: true,
2693
- noEmit: true,
2694
- esModuleInterop: true,
2695
- allowSyntheticDefaultImports: true,
2696
- allowImportingTsExtensions: true,
2697
- allowJs: true,
2698
- resolveJsonModule: true,
2699
- types: ["node"],
2700
- typeRoots: resolveBundledTypeRoots()
2701
- });
2702
- return import_typescript.default.getPreEmitDiagnostics(program).map(formatTypeScriptDiagnostic).filter((message) => Boolean(message));
2703
- }
2704
2918
  function isLocalSpecifier(specifier) {
2705
2919
  return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/") || specifier.startsWith("file:");
2706
2920
  }
@@ -2724,11 +2938,8 @@ function assertWithinPlayWorkspace(input) {
2724
2938
  if (isPathInsideDirectory(input.resolvedPath, input.workspace.rootDir)) {
2725
2939
  return;
2726
2940
  }
2727
- const position = input.sourceFile.getLineAndCharacterOfPosition(
2728
- input.node.getStart(input.sourceFile)
2729
- );
2730
2941
  throw new Error(
2731
- `${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.`
2942
+ `${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.`
2732
2943
  );
2733
2944
  }
2734
2945
  function getPackageName(specifier) {
@@ -2738,72 +2949,135 @@ function getPackageName(specifier) {
2738
2949
  }
2739
2950
  return specifier.split("/")[0] ?? specifier;
2740
2951
  }
2741
- function scriptKindForFile(filePath) {
2742
- const extension = (0, import_node_path5.extname)(filePath).toLowerCase();
2743
- switch (extension) {
2744
- case ".tsx":
2745
- return import_typescript.default.ScriptKind.TSX;
2746
- case ".jsx":
2747
- return import_typescript.default.ScriptKind.JSX;
2748
- case ".js":
2749
- case ".mjs":
2750
- case ".cjs":
2751
- return import_typescript.default.ScriptKind.JS;
2752
- case ".json":
2753
- return import_typescript.default.ScriptKind.JSON;
2754
- default:
2755
- return import_typescript.default.ScriptKind.TS;
2756
- }
2757
- }
2758
2952
  function isPlaySourceFile(filePath) {
2759
2953
  return PLAY_SOURCE_FILE_PATTERN.test(filePath);
2760
2954
  }
2761
- function extractStringLiteralProperty(objectLiteral, propertyName) {
2762
- for (const property of objectLiteral.properties) {
2763
- if (!import_typescript.default.isPropertyAssignment(property)) {
2955
+ function stripCommentsToSpaces(source) {
2956
+ return source.replace(/\/\*[\s\S]*?\*\//g, (match) => match.replace(/[^\n]/g, " ")).replace(
2957
+ /(^|[^:])\/\/.*$/gm,
2958
+ (match, prefix) => prefix + " ".repeat(Math.max(0, match.length - prefix.length))
2959
+ );
2960
+ }
2961
+ function lineAndColumnAt(source, index) {
2962
+ const prefix = source.slice(0, index);
2963
+ const lines = prefix.split("\n");
2964
+ return { line: lines.length, column: lines[lines.length - 1].length + 1 };
2965
+ }
2966
+ function findSourceImportReferences(sourceCode) {
2967
+ const source = stripCommentsToSpaces(sourceCode);
2968
+ const references = [];
2969
+ const addReference = (specifier, specifierIndex, kind) => {
2970
+ if (!specifier) return;
2971
+ const position = lineAndColumnAt(sourceCode, specifierIndex);
2972
+ references.push({
2973
+ specifier,
2974
+ line: position.line,
2975
+ column: position.column,
2976
+ kind
2977
+ });
2978
+ };
2979
+ const staticImportPattern = /\b(?:import|export)\s+(?!type\b)(?:[\s\S]*?\s+from\s*)?(['"])([^'"\n]+)\1/g;
2980
+ for (const match of source.matchAll(staticImportPattern)) {
2981
+ addReference(match[2], match.index + match[0].lastIndexOf(match[1]), "static");
2982
+ }
2983
+ const dynamicImportPattern = /\bimport\s*\(\s*(['"])([^'"\n]+)\1/g;
2984
+ for (const match of source.matchAll(dynamicImportPattern)) {
2985
+ addReference(match[2], match.index + match[0].lastIndexOf(match[1]), "dynamic-import");
2986
+ }
2987
+ const requirePattern = /\brequire\s*\(\s*(['"])([^'"\n]+)\1/g;
2988
+ for (const match of source.matchAll(requirePattern)) {
2989
+ addReference(match[2], match.index + match[0].lastIndexOf(match[1]), "require");
2990
+ }
2991
+ const literalDynamicImportIndexes = new Set(
2992
+ [...source.matchAll(dynamicImportPattern)].map((match) => match.index)
2993
+ );
2994
+ for (const match of source.matchAll(/\bimport\s*\(/g)) {
2995
+ if (literalDynamicImportIndexes.has(match.index)) continue;
2996
+ const position = lineAndColumnAt(sourceCode, match.index);
2997
+ throw new Error(
2998
+ `:${position.line}:${position.column} Dynamic import() is not allowed in plays. Use static imports instead.`
2999
+ );
3000
+ }
3001
+ const literalRequireIndexes = new Set(
3002
+ [...source.matchAll(requirePattern)].map((match) => match.index)
3003
+ );
3004
+ for (const match of source.matchAll(/\brequire\s*\(/g)) {
3005
+ if (literalRequireIndexes.has(match.index)) continue;
3006
+ const position = lineAndColumnAt(sourceCode, match.index);
3007
+ throw new Error(
3008
+ `:${position.line}:${position.column} Dynamic require() is not allowed in plays. Use static imports or require("literal") only.`
3009
+ );
3010
+ }
3011
+ return references.sort(
3012
+ (left, right) => left.line === right.line ? left.column - right.column : left.line - right.line
3013
+ );
3014
+ }
3015
+ function unquoteStringLiteral(literal) {
3016
+ const trimmed = literal.trim();
3017
+ const quote = trimmed[0];
3018
+ if (quote !== '"' && quote !== "'" || trimmed[trimmed.length - 1] !== quote) {
3019
+ return null;
3020
+ }
3021
+ try {
3022
+ return JSON.parse(quote === '"' ? trimmed : `"${trimmed.slice(1, -1).replace(/"/g, '\\"')}"`);
3023
+ } catch {
3024
+ return trimmed.slice(1, -1);
3025
+ }
3026
+ }
3027
+ function findMatchingBrace(source, openIndex) {
3028
+ let depth = 0;
3029
+ let quote = null;
3030
+ let escaped = false;
3031
+ for (let index = openIndex; index < source.length; index += 1) {
3032
+ const char = source[index];
3033
+ if (quote) {
3034
+ if (escaped) {
3035
+ escaped = false;
3036
+ } else if (char === "\\") {
3037
+ escaped = true;
3038
+ } else if (char === quote) {
3039
+ quote = null;
3040
+ }
2764
3041
  continue;
2765
3042
  }
2766
- const name = property.name;
2767
- const matches = import_typescript.default.isIdentifier(name) && name.text === propertyName || import_typescript.default.isStringLiteralLike(name) && name.text === propertyName;
2768
- if (!matches) {
3043
+ if (char === '"' || char === "'" || char === "`") {
3044
+ quote = char;
2769
3045
  continue;
2770
3046
  }
2771
- return import_typescript.default.isStringLiteralLike(property.initializer) ? property.initializer.text.trim() : null;
2772
- }
2773
- return null;
2774
- }
2775
- function extractDefinedPlayName(sourceCode, filePath) {
2776
- const sourceFile = import_typescript.default.createSourceFile(
2777
- filePath,
2778
- sourceCode,
2779
- import_typescript.default.ScriptTarget.Latest,
2780
- true,
2781
- scriptKindForFile(filePath)
2782
- );
2783
- let detectedPlayName = null;
2784
- const visit = (node) => {
2785
- if (detectedPlayName) {
2786
- return;
2787
- }
2788
- if (import_typescript.default.isCallExpression(node)) {
2789
- const expression = node.expression;
2790
- const isDefinePlayCall = import_typescript.default.isIdentifier(expression) && (expression.text === "definePlay" || expression.text === "defineWorkflow") || import_typescript.default.isPropertyAccessExpression(expression) && (expression.name.text === "definePlay" || expression.name.text === "defineWorkflow");
2791
- if (isDefinePlayCall) {
2792
- const firstArgument = node.arguments[0];
2793
- if (firstArgument && import_typescript.default.isStringLiteralLike(firstArgument)) {
2794
- detectedPlayName = firstArgument.text.trim() || null;
2795
- return;
2796
- }
2797
- if (firstArgument && import_typescript.default.isObjectLiteralExpression(firstArgument)) {
2798
- detectedPlayName = extractStringLiteralProperty(firstArgument, "id");
2799
- return;
2800
- }
3047
+ if (char === "{") depth += 1;
3048
+ if (char === "}") {
3049
+ depth -= 1;
3050
+ if (depth === 0) return index;
3051
+ }
3052
+ }
3053
+ return -1;
3054
+ }
3055
+ function extractDefinedPlayName(sourceCode, _filePath) {
3056
+ const source = stripCommentsToSpaces(sourceCode);
3057
+ const callPattern = /(?:\b[A-Za-z_$][\w$]*\s*\.\s*)?\b(?:definePlay|defineWorkflow)\s*\(/g;
3058
+ for (const match of source.matchAll(callPattern)) {
3059
+ const openParen = match.index + match[0].length - 1;
3060
+ const firstArgStart = openParen + 1;
3061
+ const firstNonSpace = source.slice(firstArgStart).search(/\S/);
3062
+ if (firstNonSpace < 0) continue;
3063
+ const argIndex = firstArgStart + firstNonSpace;
3064
+ const quote = source[argIndex];
3065
+ if (quote === '"' || quote === "'") {
3066
+ const literalMatch = source.slice(argIndex).match(/^(['"])(?:\\.|(?!\1)[\s\S])*\1/);
3067
+ const value = literalMatch ? unquoteStringLiteral(literalMatch[0]) : null;
3068
+ if (value?.trim()) return value.trim();
3069
+ }
3070
+ if (quote === "{") {
3071
+ const closeBrace = findMatchingBrace(source, argIndex);
3072
+ if (closeBrace < 0) continue;
3073
+ const objectSource = source.slice(argIndex + 1, closeBrace);
3074
+ const idMatch = objectSource.match(/(?:^|[,{\s])(?:id|['"]id['"])\s*:\s*(['"])([\s\S]*?)\1/);
3075
+ if (idMatch?.[2]?.trim()) {
3076
+ return idMatch[2].trim();
2801
3077
  }
2802
3078
  }
2803
- import_typescript.default.forEachChild(node, visit);
2804
- };
2805
- visit(sourceFile);
2806
- return detectedPlayName;
3079
+ }
3080
+ return null;
2807
3081
  }
2808
3082
  function getPackageRequireCandidates(fromFile) {
2809
3083
  const candidates = [
@@ -3113,18 +3387,10 @@ async function analyzeSourceGraph(entryFile, adapter) {
3113
3387
  if ((0, import_node_path5.extname)(absolutePath).toLowerCase() === ".json") {
3114
3388
  return;
3115
3389
  }
3116
- const sourceFile = import_typescript.default.createSourceFile(
3117
- absolutePath,
3118
- sourceCode2,
3119
- import_typescript.default.ScriptTarget.Latest,
3120
- true,
3121
- scriptKindForFile(absolutePath)
3122
- );
3123
- const handleSpecifier = async (specifier, node, kind) => {
3390
+ const handleSpecifier = async (specifier, line, column, kind) => {
3124
3391
  if (kind === "dynamic-import") {
3125
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3126
3392
  throw new Error(
3127
- `${absolutePath}:${position.line + 1}:${position.character + 1} Dynamic import() is not allowed in plays. Use static imports instead.`
3393
+ `${absolutePath}:${line}:${column} Dynamic import() is not allowed in plays. Use static imports instead.`
3128
3394
  );
3129
3395
  }
3130
3396
  if (NODE_BUILTIN_SET.has(specifier)) {
@@ -3138,16 +3404,15 @@ async function analyzeSourceGraph(entryFile, adapter) {
3138
3404
  specifier,
3139
3405
  resolvedPath: resolved,
3140
3406
  workspace,
3141
- sourceFile,
3142
- node
3407
+ line,
3408
+ column
3143
3409
  });
3144
3410
  if (resolved !== absoluteEntryFile && isPlaySourceFile(resolved)) {
3145
3411
  const importedSource = await (0, import_promises2.readFile)(resolved, "utf-8");
3146
3412
  const importedPlayName = extractDefinedPlayName(importedSource, resolved);
3147
3413
  if (!importedPlayName) {
3148
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3149
3414
  throw new Error(
3150
- `${absolutePath}:${position.line + 1}:${position.character + 1} Imported play file "${specifier}" must export definePlay(...) so it can be runtime-composed.`
3415
+ `${absolutePath}:${line}:${column} Imported play file "${specifier}" must export definePlay(...) so it can be runtime-composed.`
3151
3416
  );
3152
3417
  }
3153
3418
  importedPlayDependencies.set(resolved, {
@@ -3160,44 +3425,28 @@ async function analyzeSourceGraph(entryFile, adapter) {
3160
3425
  return;
3161
3426
  }
3162
3427
  if (specifier.includes(":")) {
3163
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3164
3428
  throw new Error(
3165
- `${absolutePath}:${position.line + 1}:${position.character + 1} Unsupported import specifier "${specifier}". Allowed imports are relative files, Node builtins, and installed packages.`
3429
+ `${absolutePath}:${line}:${column} Unsupported import specifier "${specifier}". Allowed imports are relative files, Node builtins, and installed packages.`
3166
3430
  );
3167
3431
  }
3168
3432
  const packageImport = resolvePackageImport(specifier, absolutePath, adapter);
3169
3433
  packages.set(packageImport.name, packageImport.version);
3170
3434
  };
3171
- const walk = async (node) => {
3172
- if ((import_typescript.default.isImportDeclaration(node) || import_typescript.default.isExportDeclaration(node)) && node.moduleSpecifier && !(import_typescript.default.isImportDeclaration(node) && node.importClause?.isTypeOnly || import_typescript.default.isExportDeclaration(node) && node.isTypeOnly) && import_typescript.default.isStringLiteralLike(node.moduleSpecifier)) {
3173
- await handleSpecifier(node.moduleSpecifier.text, node, "static");
3174
- }
3175
- if (import_typescript.default.isCallExpression(node)) {
3176
- if (node.expression.kind === import_typescript.default.SyntaxKind.ImportKeyword) {
3177
- if (node.arguments.length !== 1 || !import_typescript.default.isStringLiteralLike(node.arguments[0])) {
3178
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3179
- throw new Error(
3180
- `${absolutePath}:${position.line + 1}:${position.character + 1} Dynamic import() is not allowed in plays. Use static imports instead.`
3181
- );
3182
- }
3183
- await handleSpecifier(node.arguments[0].text, node, "dynamic-import");
3184
- }
3185
- if (import_typescript.default.isIdentifier(node.expression) && node.expression.text === "require") {
3186
- const firstArgument = node.arguments[0];
3187
- if (node.arguments.length !== 1 || !firstArgument || !import_typescript.default.isStringLiteralLike(firstArgument)) {
3188
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3189
- throw new Error(
3190
- `${absolutePath}:${position.line + 1}:${position.character + 1} Dynamic require() is not allowed in plays. Use static imports or require("literal") only.`
3191
- );
3192
- }
3193
- await handleSpecifier(firstArgument.text, node, "require");
3194
- }
3435
+ try {
3436
+ for (const reference of findSourceImportReferences(sourceCode2)) {
3437
+ await handleSpecifier(
3438
+ reference.specifier,
3439
+ reference.line,
3440
+ reference.column,
3441
+ reference.kind
3442
+ );
3195
3443
  }
3196
- for (const child of node.getChildren(sourceFile)) {
3197
- await walk(child);
3444
+ } catch (error) {
3445
+ if (error instanceof Error && error.message.startsWith(":")) {
3446
+ throw new Error(`${absolutePath}${error.message}`);
3198
3447
  }
3199
- };
3200
- await walk(sourceFile);
3448
+ throw error;
3449
+ }
3201
3450
  };
3202
3451
  await visitFile(absoluteEntryFile);
3203
3452
  const sourceCode = localFiles.get(absoluteEntryFile) ?? "";
@@ -3217,6 +3466,11 @@ async function analyzeSourceGraph(entryFile, adapter) {
3217
3466
  const playName = extractDefinedPlayName(sourceCode, absoluteEntryFile);
3218
3467
  return {
3219
3468
  sourceCode,
3469
+ sourceFiles: Object.fromEntries(
3470
+ [...localFiles.entries()].sort(
3471
+ (left, right) => left[0].localeCompare(right[0])
3472
+ )
3473
+ ),
3220
3474
  sourceHash,
3221
3475
  graphHash,
3222
3476
  importPolicy: {
@@ -3280,8 +3534,7 @@ function normalizeSourceMapForRuntime(sourceMapText) {
3280
3534
  parsed.sourceRoot = void 0;
3281
3535
  return JSON.stringify(parsed);
3282
3536
  }
3283
- function getBundleSizeError(filePath, bundledCode, artifactKind) {
3284
- const bundleBytes = Buffer.byteLength(bundledCode, "utf8");
3537
+ function getBundleSizeErrorForBytes(filePath, bundleBytes, artifactKind) {
3285
3538
  if (bundleBytes > MAX_PLAY_BUNDLE_BYTES) {
3286
3539
  return `${filePath} Play bundle exceeds the 30 MiB limit (${bundleBytes} bytes > ${MAX_PLAY_BUNDLE_BYTES} bytes).`;
3287
3540
  }
@@ -3292,6 +3545,13 @@ function getBundleSizeError(filePath, bundledCode, artifactKind) {
3292
3545
  }
3293
3546
  return null;
3294
3547
  }
3548
+ function getBundleSizeError(filePath, bundledCode, artifactKind) {
3549
+ return getBundleSizeErrorForBytes(
3550
+ filePath,
3551
+ Buffer.byteLength(bundledCode, "utf8"),
3552
+ artifactKind
3553
+ );
3554
+ }
3295
3555
  async function runEsbuildForCjsNode(entryFile, importedPlayDependencies, adapter, exportName) {
3296
3556
  const sdkAliasPlugin = localSdkAliasPlugin(adapter);
3297
3557
  const playProxyPlugin = importedPlayProxyPlugin(importedPlayDependencies);
@@ -3424,6 +3684,23 @@ entry-export:${exportName}`
3424
3684
  workers-harness:${harnessFingerprint}`
3425
3685
  );
3426
3686
  }
3687
+ const typecheckErrors = [
3688
+ ...await adapter.typecheckPlaySource?.({
3689
+ sourceCode: analysis.sourceCode,
3690
+ sourcePath: absolutePath,
3691
+ importedFilePaths: [
3692
+ ...analysis.importPolicy.localFiles,
3693
+ ...analysis.importedPlayDependencies.map((dependency) => dependency.filePath)
3694
+ ]
3695
+ }) ?? []
3696
+ ];
3697
+ if (typecheckErrors.length > 0) {
3698
+ return {
3699
+ success: false,
3700
+ filePath: absolutePath,
3701
+ errors: typecheckErrors
3702
+ };
3703
+ }
3427
3704
  const cachedArtifact = await readArtifactCache(analysis.graphHash, target, adapter);
3428
3705
  const discoveredFiles = await adapter.discoverPackagedLocalFiles(absolutePath);
3429
3706
  if (cachedArtifact) {
@@ -3443,6 +3720,7 @@ workers-harness:${harnessFingerprint}`
3443
3720
  success: true,
3444
3721
  artifact: { ...cachedArtifact, cacheHit: true },
3445
3722
  sourceCode: analysis.sourceCode,
3723
+ sourceFiles: analysis.sourceFiles,
3446
3724
  filePath: absolutePath,
3447
3725
  playName: analysis.playName,
3448
3726
  packagedFiles: discoveredFiles.files,
@@ -3450,24 +3728,6 @@ workers-harness:${harnessFingerprint}`
3450
3728
  importedPlayDependencies: analysis.importedPlayDependencies
3451
3729
  };
3452
3730
  }
3453
- const typecheckErrors = [
3454
- ...adapter.typecheckSdkTypes === false ? [] : typecheckPlaySource(analysis, adapter),
3455
- ...await adapter.typecheckPlaySource?.({
3456
- sourceCode: analysis.sourceCode,
3457
- sourcePath: absolutePath,
3458
- importedFilePaths: [
3459
- ...analysis.importPolicy.localFiles,
3460
- ...analysis.importedPlayDependencies.map((dependency) => dependency.filePath)
3461
- ]
3462
- }) ?? []
3463
- ];
3464
- if (typecheckErrors.length > 0) {
3465
- return {
3466
- success: false,
3467
- filePath: absolutePath,
3468
- errors: typecheckErrors
3469
- };
3470
- }
3471
3731
  const buildOutcome = target === PLAY_ARTIFACT_KINDS.esmWorkers ? await runEsbuildForEsmWorkers(absolutePath, analysis.importedPlayDependencies, adapter, exportName) : await runEsbuildForCjsNode(absolutePath, analysis.importedPlayDependencies, adapter, exportName);
3472
3732
  if (Array.isArray(buildOutcome)) {
3473
3733
  return {
@@ -3517,6 +3777,7 @@ workers-harness:${harnessFingerprint}`
3517
3777
  success: true,
3518
3778
  artifact,
3519
3779
  sourceCode: analysis.sourceCode,
3780
+ sourceFiles: analysis.sourceFiles,
3520
3781
  filePath: absolutePath,
3521
3782
  playName: analysis.playName,
3522
3783
  packagedFiles: discoveredFiles.files,
@@ -3598,7 +3859,6 @@ function resolveExecutionProfile(override) {
3598
3859
  var import_node_crypto2 = require("crypto");
3599
3860
  var import_promises3 = require("fs/promises");
3600
3861
  var import_node_path6 = require("path");
3601
- var import_typescript2 = __toESM(require("typescript"));
3602
3862
  var SOURCE_EXTENSIONS2 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".json"];
3603
3863
  function sha2562(buffer) {
3604
3864
  return (0, import_node_crypto2.createHash)("sha256").update(buffer).digest("hex");
@@ -3610,94 +3870,181 @@ function contentTypeForFile(filePath) {
3610
3870
  if (extension === ".txt") return "text/plain";
3611
3871
  return "application/octet-stream";
3612
3872
  }
3613
- function isCtxCsvCall(node) {
3614
- if (!import_typescript2.default.isPropertyAccessExpression(node.expression)) {
3615
- return false;
3616
- }
3617
- const target = node.expression.expression;
3618
- return import_typescript2.default.isIdentifier(target) && (target.text === "ctx" || target.text.endsWith("Ctx")) && node.expression.name.text === "csv";
3619
- }
3620
- function extractSourceFragment(source, node) {
3621
- return source.slice(node.getStart(), node.getEnd()).trim();
3622
- }
3623
- function referencesInputIdentifier(node) {
3624
- if (import_typescript2.default.isIdentifier(node) && node.text === "input") {
3625
- return true;
3626
- }
3627
- return node.getChildren().some((child) => referencesInputIdentifier(child));
3873
+ function stripCommentsToSpaces2(source) {
3874
+ return source.replace(/\/\*[\s\S]*?\*\//g, (match) => match.replace(/[^\n]/g, " ")).replace(
3875
+ /(^|[^:])\/\/.*$/gm,
3876
+ (match, prefix) => prefix + " ".repeat(Math.max(0, match.length - prefix.length))
3877
+ );
3628
3878
  }
3629
- function isRuntimeInputExpression(node) {
3630
- if (import_typescript2.default.isPropertyAccessExpression(node)) {
3631
- return import_typescript2.default.isIdentifier(node.expression) && node.expression.text === "input";
3632
- }
3633
- if (import_typescript2.default.isElementAccessExpression(node)) {
3634
- return import_typescript2.default.isIdentifier(node.expression) && node.expression.text === "input";
3635
- }
3636
- if (import_typescript2.default.isIdentifier(node)) {
3637
- return node.text === "input";
3638
- }
3639
- if (import_typescript2.default.isParenthesizedExpression(node)) {
3640
- return isRuntimeInputExpression(node.expression);
3879
+ function unquoteStringLiteral2(literal) {
3880
+ const trimmed = literal.trim();
3881
+ const quote = trimmed[0];
3882
+ if (quote !== '"' && quote !== "'" || trimmed[trimmed.length - 1] !== quote) {
3883
+ return null;
3641
3884
  }
3642
- if (import_typescript2.default.isBinaryExpression(node) && (node.operatorToken.kind === import_typescript2.default.SyntaxKind.QuestionQuestionToken || node.operatorToken.kind === import_typescript2.default.SyntaxKind.BarBarToken)) {
3643
- return isRuntimeInputExpression(node.left) || isRuntimeInputExpression(node.right);
3885
+ try {
3886
+ return JSON.parse(quote === '"' ? trimmed : `"${trimmed.slice(1, -1).replace(/"/g, '\\"')}"`);
3887
+ } catch {
3888
+ return trimmed.slice(1, -1);
3644
3889
  }
3645
- if (import_typescript2.default.isConditionalExpression(node)) {
3646
- return isRuntimeInputExpression(node.condition) || isRuntimeInputExpression(node.whenTrue) || isRuntimeInputExpression(node.whenFalse);
3890
+ }
3891
+ function splitTopLevelPlus(expression) {
3892
+ const parts = [];
3893
+ let start = 0;
3894
+ let depth = 0;
3895
+ let quote = null;
3896
+ let escaped = false;
3897
+ for (let index = 0; index < expression.length; index += 1) {
3898
+ const char = expression[index];
3899
+ if (quote) {
3900
+ if (escaped) {
3901
+ escaped = false;
3902
+ } else if (char === "\\") {
3903
+ escaped = true;
3904
+ } else if (char === quote) {
3905
+ quote = null;
3906
+ }
3907
+ continue;
3908
+ }
3909
+ if (char === '"' || char === "'" || char === "`") {
3910
+ quote = char;
3911
+ continue;
3912
+ }
3913
+ if (char === "(" || char === "[" || char === "{") depth += 1;
3914
+ if (char === ")" || char === "]" || char === "}") depth -= 1;
3915
+ if (char === "+" && depth === 0) {
3916
+ parts.push(expression.slice(start, index));
3917
+ start = index + 1;
3918
+ }
3647
3919
  }
3648
- return referencesInputIdentifier(node);
3920
+ if (parts.length === 0) return null;
3921
+ parts.push(expression.slice(start));
3922
+ return parts;
3649
3923
  }
3650
- function resolveStringExpression(node, constants) {
3651
- if (import_typescript2.default.isStringLiteralLike(node) || import_typescript2.default.isNoSubstitutionTemplateLiteral(node)) {
3652
- return node.text;
3924
+ function stripOuterParens(expression) {
3925
+ let value = expression.trim();
3926
+ while (value.startsWith("(") && value.endsWith(")")) {
3927
+ value = value.slice(1, -1).trim();
3653
3928
  }
3654
- if (import_typescript2.default.isParenthesizedExpression(node)) {
3655
- return resolveStringExpression(node.expression, constants);
3929
+ return value;
3930
+ }
3931
+ function isRuntimeInputExpression(expression) {
3932
+ return /(^|[^\w$])input([^\w$]|$)/.test(expression);
3933
+ }
3934
+ function resolveStringExpression(expression, constants) {
3935
+ const value = stripOuterParens(expression);
3936
+ if (/^(['"])(?:\\.|(?!\1)[\s\S])*\1$/.test(value)) {
3937
+ return unquoteStringLiteral2(value);
3656
3938
  }
3657
- if (import_typescript2.default.isIdentifier(node)) {
3658
- return constants.get(node.text) ?? null;
3939
+ if (/^`(?:\\.|[^`$]|\$(?!\{))*`$/.test(value)) {
3940
+ return value.slice(1, -1);
3659
3941
  }
3660
- if (import_typescript2.default.isTemplateExpression(node)) {
3661
- let value = node.head.text;
3662
- for (const span of node.templateSpans) {
3663
- const resolved = resolveStringExpression(span.expression, constants);
3664
- if (resolved == null) {
3665
- return null;
3666
- }
3667
- value += resolved + span.literal.text;
3668
- }
3669
- return value;
3942
+ if (/^[A-Za-z_$][\w$]*$/.test(value)) {
3943
+ return constants.get(value) ?? null;
3670
3944
  }
3671
- if (import_typescript2.default.isBinaryExpression(node) && node.operatorToken.kind === import_typescript2.default.SyntaxKind.PlusToken) {
3672
- const left = resolveStringExpression(node.left, constants);
3673
- const right = resolveStringExpression(node.right, constants);
3674
- if (left == null || right == null) {
3675
- return null;
3676
- }
3677
- return left + right;
3945
+ const parts = splitTopLevelPlus(value);
3946
+ if (parts) {
3947
+ const resolved = parts.map((part) => resolveStringExpression(part, constants));
3948
+ return resolved.every((part) => part != null) ? resolved.join("") : null;
3678
3949
  }
3679
3950
  return null;
3680
3951
  }
3681
- function collectTopLevelStringConstants(sourceFile) {
3952
+ function collectTopLevelStringConstants(sourceCode) {
3682
3953
  const constants = /* @__PURE__ */ new Map();
3683
- for (const statement of sourceFile.statements) {
3684
- if (!import_typescript2.default.isVariableStatement(statement)) {
3954
+ const source = stripCommentsToSpaces2(sourceCode);
3955
+ for (const match of source.matchAll(/(?:^|\n)\s*const\s+([A-Za-z_$][\w$]*)\s*=\s*([^;\n]+)/g)) {
3956
+ const resolved = resolveStringExpression(match[2], constants);
3957
+ if (resolved != null) {
3958
+ constants.set(match[1], resolved);
3959
+ }
3960
+ }
3961
+ return constants;
3962
+ }
3963
+ function findMatchingGenericEnd(source, openIndex) {
3964
+ let depth = 0;
3965
+ let quote = null;
3966
+ let escaped = false;
3967
+ for (let index = openIndex; index < source.length; index += 1) {
3968
+ const char = source[index];
3969
+ if (quote) {
3970
+ if (escaped) {
3971
+ escaped = false;
3972
+ } else if (char === "\\") {
3973
+ escaped = true;
3974
+ } else if (char === quote) {
3975
+ quote = null;
3976
+ }
3685
3977
  continue;
3686
3978
  }
3687
- if (!(statement.declarationList.flags & import_typescript2.default.NodeFlags.Const)) {
3979
+ if (char === '"' || char === "'" || char === "`") {
3980
+ quote = char;
3688
3981
  continue;
3689
3982
  }
3690
- for (const declaration of statement.declarationList.declarations) {
3691
- if (!import_typescript2.default.isIdentifier(declaration.name) || !declaration.initializer) {
3692
- continue;
3693
- }
3694
- const resolved = resolveStringExpression(declaration.initializer, constants);
3695
- if (resolved != null) {
3696
- constants.set(declaration.name.text, resolved);
3983
+ if (char === "<") depth += 1;
3984
+ if (char === ">") {
3985
+ depth -= 1;
3986
+ if (depth === 0) return index;
3987
+ }
3988
+ }
3989
+ return -1;
3990
+ }
3991
+ function findCallOpenParen(source, afterCsvIndex) {
3992
+ let index = afterCsvIndex;
3993
+ while (/\s/.test(source[index] ?? "")) index += 1;
3994
+ if (source[index] === "<") {
3995
+ const genericEnd = findMatchingGenericEnd(source, index);
3996
+ if (genericEnd < 0) return -1;
3997
+ index = genericEnd + 1;
3998
+ while (/\s/.test(source[index] ?? "")) index += 1;
3999
+ }
4000
+ return source[index] === "(" ? index : -1;
4001
+ }
4002
+ function firstCallArgument(source, openParen) {
4003
+ let depth = 0;
4004
+ let quote = null;
4005
+ let escaped = false;
4006
+ const start = openParen + 1;
4007
+ for (let index = start; index < source.length; index += 1) {
4008
+ const char = source[index];
4009
+ if (quote) {
4010
+ if (escaped) {
4011
+ escaped = false;
4012
+ } else if (char === "\\") {
4013
+ escaped = true;
4014
+ } else if (char === quote) {
4015
+ quote = null;
3697
4016
  }
4017
+ continue;
4018
+ }
4019
+ if (char === '"' || char === "'" || char === "`") {
4020
+ quote = char;
4021
+ continue;
4022
+ }
4023
+ if (char === "(" || char === "[" || char === "{") depth += 1;
4024
+ if (char === ")" && depth === 0) {
4025
+ const text = source.slice(start, index).trim();
4026
+ return text ? { text, start, end: index } : null;
3698
4027
  }
4028
+ if (char === "," && depth === 0) {
4029
+ const text = source.slice(start, index).trim();
4030
+ return text ? { text, start, end: index } : null;
4031
+ }
4032
+ if (char === ")" || char === "]" || char === "}") depth -= 1;
3699
4033
  }
3700
- return constants;
4034
+ return null;
4035
+ }
4036
+ function localImportSpecifiers(sourceCode) {
4037
+ const source = stripCommentsToSpaces2(sourceCode);
4038
+ const specifiers = [];
4039
+ for (const match of source.matchAll(
4040
+ /\b(?:import|export)\s+(?!type\b)(?:[\s\S]*?\s+from\s*)?['"]([^'"]+)['"]/g
4041
+ )) {
4042
+ if (match[1]?.startsWith(".")) specifiers.push(match[1]);
4043
+ }
4044
+ for (const match of source.matchAll(/\brequire\s*\(\s*(['"])(\.[^'"]*)\1\s*\)/g)) {
4045
+ specifiers.push(match[2]);
4046
+ }
4047
+ return specifiers;
3701
4048
  }
3702
4049
  async function fileExists2(filePath) {
3703
4050
  try {
@@ -3742,69 +4089,60 @@ async function discoverPackagedLocalFiles(entryFile) {
3742
4089
  }
3743
4090
  visitedFiles.add(absolutePath);
3744
4091
  const sourceCode = await (0, import_promises3.readFile)(absolutePath, "utf-8");
3745
- const sourceFile = import_typescript2.default.createSourceFile(
3746
- absolutePath,
3747
- sourceCode,
3748
- import_typescript2.default.ScriptTarget.Latest,
3749
- true,
3750
- import_typescript2.default.ScriptKind.TS
3751
- );
3752
- const constants = collectTopLevelStringConstants(sourceFile);
4092
+ const scanSource = stripCommentsToSpaces2(sourceCode);
4093
+ const constants = collectTopLevelStringConstants(sourceCode);
3753
4094
  const childVisits = [];
3754
- const visitNode = async (node) => {
3755
- if (import_typescript2.default.isCallExpression(node) && isCtxCsvCall(node)) {
3756
- const argument = node.arguments[0];
3757
- if (!argument) {
4095
+ for (const match of scanSource.matchAll(/\b([A-Za-z_$][\w$]*)\s*\.\s*csv\b/g)) {
4096
+ const target = match[1];
4097
+ if (target !== "ctx" && !target.endsWith("Ctx")) {
4098
+ continue;
4099
+ }
4100
+ const openParen = findCallOpenParen(scanSource, match.index + match[0].length);
4101
+ if (openParen < 0) {
4102
+ continue;
4103
+ }
4104
+ const argument = firstCallArgument(scanSource, openParen);
4105
+ if (!argument) {
4106
+ unresolved.push({
4107
+ sourceFragment: "ctx.csv()",
4108
+ message: "ctx.csv() requires a file path string or input reference."
4109
+ });
4110
+ } else if (!isRuntimeInputExpression(argument.text)) {
4111
+ const resolvedPath = resolveStringExpression(argument.text, constants);
4112
+ if (resolvedPath == null) {
3758
4113
  unresolved.push({
3759
- sourceFragment: "ctx.csv()",
3760
- message: "ctx.csv() requires a file path string or input reference."
4114
+ sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
4115
+ 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."
3761
4116
  });
3762
- } else if (!isRuntimeInputExpression(argument)) {
3763
- const resolvedPath = resolveStringExpression(argument, constants);
3764
- if (resolvedPath == null) {
4117
+ } else {
4118
+ const absoluteCsvPath = (0, import_node_path6.resolve)((0, import_node_path6.dirname)(absolutePath), resolvedPath);
4119
+ if ((0, import_node_path6.isAbsolute)(resolvedPath) || !isPathInsideDirectory2(absoluteCsvPath, packagingRoot)) {
3765
4120
  unresolved.push({
3766
- sourceFragment: extractSourceFragment(sourceCode, argument),
3767
- 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."
3768
- });
3769
- } else {
3770
- const absoluteCsvPath = (0, import_node_path6.resolve)((0, import_node_path6.dirname)(absolutePath), resolvedPath);
3771
- if ((0, import_node_path6.isAbsolute)(resolvedPath) || !isPathInsideDirectory2(absoluteCsvPath, packagingRoot)) {
3772
- unresolved.push({
3773
- sourceFragment: extractSourceFragment(sourceCode, argument),
3774
- message: "ctx.csv(...) packaged file paths must be relative paths inside the play directory. Pass external files at runtime with input.file instead."
3775
- });
3776
- return;
3777
- }
3778
- const buffer = await (0, import_promises3.readFile)(absoluteCsvPath);
3779
- const stats = await (0, import_promises3.stat)(absoluteCsvPath);
3780
- files.set(absoluteCsvPath, {
3781
- sourceFragment: extractSourceFragment(sourceCode, argument),
3782
- logicalPath: resolvedPath,
3783
- absolutePath: absoluteCsvPath,
3784
- bytes: stats.size,
3785
- contentHash: sha2562(buffer),
3786
- contentType: contentTypeForFile(absoluteCsvPath)
4121
+ sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
4122
+ message: "ctx.csv(...) packaged file paths must be relative paths inside the play directory. Pass external files at runtime with input.file instead."
3787
4123
  });
4124
+ continue;
3788
4125
  }
4126
+ const buffer = await (0, import_promises3.readFile)(absoluteCsvPath);
4127
+ const stats = await (0, import_promises3.stat)(absoluteCsvPath);
4128
+ files.set(absoluteCsvPath, {
4129
+ sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
4130
+ logicalPath: resolvedPath,
4131
+ absolutePath: absoluteCsvPath,
4132
+ bytes: stats.size,
4133
+ contentHash: sha2562(buffer),
4134
+ contentType: contentTypeForFile(absoluteCsvPath)
4135
+ });
3789
4136
  }
3790
4137
  }
3791
- if (import_typescript2.default.isImportDeclaration(node) && !node.importClause?.isTypeOnly && import_typescript2.default.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text.startsWith(".")) {
3792
- childVisits.push(
3793
- resolveLocalImport2(absolutePath, node.moduleSpecifier.text).then(
3794
- (resolvedImport) => visitSourceFile(resolvedImport)
3795
- )
3796
- );
3797
- }
3798
- if (import_typescript2.default.isCallExpression(node) && import_typescript2.default.isIdentifier(node.expression) && node.expression.text === "require" && node.arguments.length === 1 && import_typescript2.default.isStringLiteral(node.arguments[0]) && node.arguments[0].text.startsWith(".")) {
3799
- childVisits.push(
3800
- resolveLocalImport2(absolutePath, node.arguments[0].text).then(
3801
- (resolvedImport) => visitSourceFile(resolvedImport)
3802
- )
3803
- );
3804
- }
3805
- await Promise.all(node.getChildren(sourceFile).map((child) => visitNode(child)));
3806
- };
3807
- await visitNode(sourceFile);
4138
+ }
4139
+ for (const specifier of localImportSpecifiers(sourceCode)) {
4140
+ childVisits.push(
4141
+ resolveLocalImport2(absolutePath, specifier).then(
4142
+ (resolvedImport) => visitSourceFile(resolvedImport)
4143
+ )
4144
+ );
4145
+ }
3808
4146
  await Promise.all(childVisits);
3809
4147
  };
3810
4148
  await visitSourceFile(absoluteEntryFile);
@@ -3816,7 +4154,7 @@ async function discoverPackagedLocalFiles(entryFile) {
3816
4154
 
3817
4155
  // src/plays/bundle-play-file.ts
3818
4156
  var import_meta2 = {};
3819
- var PLAY_BUNDLE_CACHE_VERSION2 = 24;
4157
+ var PLAY_BUNDLE_CACHE_VERSION2 = 26;
3820
4158
  var MODULE_DIR = (0, import_node_path7.dirname)((0, import_node_url.fileURLToPath)(import_meta2.url));
3821
4159
  var SDK_PACKAGE_ROOT = (0, import_node_path7.resolve)(MODULE_DIR, "..", "..");
3822
4160
  var SOURCE_REPO_ROOT = (0, import_node_path7.resolve)(SDK_PACKAGE_ROOT, "..");
@@ -3831,7 +4169,7 @@ var PROJECT_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? SOURCE_REPO_ROOT : HAS_PACKAGED
3831
4169
  var SDK_SOURCE_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? (0, import_node_path7.resolve)(SOURCE_REPO_ROOT, "sdk", "src") : HAS_PACKAGED_BUNDLING_SOURCES ? (0, import_node_path7.resolve)(PACKAGED_REPO_ROOT, "sdk", "src") : (0, import_node_path7.resolve)(SDK_PACKAGE_ROOT, "src");
3832
4170
  var SDK_PACKAGE_JSON = (0, import_node_path7.resolve)(SDK_PACKAGE_ROOT, "package.json");
3833
4171
  var SDK_ENTRY_FILE = (0, import_node_path7.resolve)(SDK_SOURCE_ROOT, "index.ts");
3834
- var SDK_TYPES_ENTRY_FILE = (0, import_node_path7.resolve)(SDK_PACKAGE_ROOT, "dist", "index.d.ts");
4172
+ var SDK_TYPES_ENTRY_FILE = HAS_SOURCE_BUNDLING_SOURCES ? SDK_ENTRY_FILE : (0, import_node_path7.resolve)(SDK_PACKAGE_ROOT, "dist", "index.d.ts");
3835
4173
  var SDK_WORKERS_ENTRY_FILE = (0, import_node_path7.resolve)(SDK_SOURCE_ROOT, "worker-play-entry.ts");
3836
4174
  var WORKERS_HARNESS_ENTRY_FILE = (0, import_node_path7.resolve)(PROJECT_ROOT, "apps", "play-runner-workers", "src", "entry.ts");
3837
4175
  var WORKERS_HARNESS_FILES_DIR = (0, import_node_path7.resolve)(PROJECT_ROOT, "apps", "play-runner-workers", "src");
@@ -3863,7 +4201,7 @@ function createSdkPlayBundlingAdapter() {
3863
4201
  sdkSourceRoot: SDK_SOURCE_ROOT,
3864
4202
  sdkPackageJson: SDK_PACKAGE_JSON,
3865
4203
  sdkEntryFile: SDK_ENTRY_FILE,
3866
- sdkTypesEntryFile: HAS_SOURCE_BUNDLING_SOURCES ? SDK_ENTRY_FILE : (0, import_node_fs5.existsSync)(SDK_TYPES_ENTRY_FILE) ? SDK_TYPES_ENTRY_FILE : SDK_ENTRY_FILE,
4204
+ sdkTypesEntryFile: HAS_SOURCE_BUNDLING_SOURCES || !(0, import_node_fs5.existsSync)(SDK_TYPES_ENTRY_FILE) ? SDK_ENTRY_FILE : SDK_TYPES_ENTRY_FILE,
3867
4205
  sdkWorkersEntryFile: SDK_WORKERS_ENTRY_FILE,
3868
4206
  workersHarnessEntryFile: WORKERS_HARNESS_ENTRY_FILE,
3869
4207
  workersHarnessFilesDir: WORKERS_HARNESS_FILES_DIR,
@@ -4100,13 +4438,15 @@ function formatLoadedPlayMessage(materializedFile) {
4100
4438
  return `Loaded play here: ${materializedFile.path}`;
4101
4439
  }
4102
4440
  function buildReadonlyPrebuiltPlayError(reference) {
4441
+ const localName = reference.split("/").slice(1).join("/") || "custom-play";
4103
4442
  return new Error(
4104
4443
  `Cannot edit or push ${reference} because Deepline prebuilt plays are read-only.
4105
4444
  To make your own version:
4106
- 1. Copy the source into a new local file.
4107
- 2. Change definePlay('${reference.split("/").slice(1).join("/")}', ...) to a new play name you own.
4108
- 3. Run: deepline plays publish <your-file.play.ts>
4109
- 4. Your play will then live under your workspace namespace.`
4445
+ 1. Run: deepline plays get ${reference} --source --out ./${localName}.play.ts
4446
+ 2. Change definePlay('${localName}', ...) to a new play name you own.
4447
+ 3. Run: deepline plays check ./${localName}.play.ts
4448
+ 4. Run: deepline plays publish ./${localName}.play.ts
4449
+ 5. Your play will then live under your workspace namespace.`
4110
4450
  );
4111
4451
  }
4112
4452
  async function ensureEditableRemotePlay(client, target) {
@@ -4247,6 +4587,15 @@ function fileInputBindingsFromStaticPipeline(staticPipeline) {
4247
4587
  );
4248
4588
  return inputField ? [{ inputPath: inputField }] : [];
4249
4589
  }
4590
+ function applyCsvShortcutInput(input) {
4591
+ const csvValue = getDottedInputValue(input.runtimeInput, "csv");
4592
+ if (csvValue == null || csvValue === "") return;
4593
+ const candidate = input.bindings.find((binding) => binding.inputPath !== "csv")?.inputPath ?? input.fallbackInputPath ?? null;
4594
+ if (!candidate || candidate === "csv") return;
4595
+ const existing = getDottedInputValue(input.runtimeInput, candidate);
4596
+ if (existing != null && existing !== "") return;
4597
+ setDottedInputValue(input.runtimeInput, candidate, csvValue);
4598
+ }
4250
4599
  function isLocalFilePathValue(value) {
4251
4600
  if (typeof value !== "string" || !value.trim()) return false;
4252
4601
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim())) return false;
@@ -4370,6 +4719,7 @@ async function compileBundledPlayGraphManifests(client, graph) {
4370
4719
  node.compilerManifest = await client.compilePlayManifest({
4371
4720
  name,
4372
4721
  sourceCode: node.sourceCode,
4722
+ sourceFiles: node.sourceFiles,
4373
4723
  artifact: node.artifact,
4374
4724
  importedPlayDependencies: node.importedPlayDependencies.map(
4375
4725
  (dependency) => {
@@ -4419,6 +4769,7 @@ async function publishImportedPlayDependencies(client, graph) {
4419
4769
  await client.registerPlayArtifact({
4420
4770
  name: node.playName,
4421
4771
  sourceCode: node.sourceCode,
4772
+ sourceFiles: node.sourceFiles,
4422
4773
  artifact: node.artifact,
4423
4774
  compilerManifest: requireCompilerManifest(node),
4424
4775
  publish: true
@@ -4436,67 +4787,6 @@ function formatTimestamp(value) {
4436
4787
  function formatRunLine(run) {
4437
4788
  return `${run.workflowId} ${run.status} ${formatTimestamp(run.startTime)}`;
4438
4789
  }
4439
- function parsePlayRunTarget(input) {
4440
- const { args, usage } = input;
4441
- let runId = null;
4442
- let playName = null;
4443
- for (let index = 0; index < args.length; index += 1) {
4444
- const arg = args[index];
4445
- if (arg === "--json") {
4446
- continue;
4447
- }
4448
- if (arg === "--reason" || arg === "--interval-ms" || arg === "--poll-interval-ms") {
4449
- index += 1;
4450
- continue;
4451
- }
4452
- if (arg === "--run-id" && args[index + 1]) {
4453
- runId = args[++index].trim();
4454
- continue;
4455
- }
4456
- if (arg === "--name" && args[index + 1] && input.allowName) {
4457
- playName = parseReferencedPlayTarget(args[++index]).playName;
4458
- continue;
4459
- }
4460
- if (arg.startsWith("--")) {
4461
- continue;
4462
- }
4463
- throw new DeeplineError(
4464
- `Unexpected positional target "${arg}". Use --run-id for run ids.
4465
- ${usage}`
4466
- );
4467
- }
4468
- const explicitTargets = [runId, playName].filter(Boolean).length;
4469
- if (explicitTargets > 1) {
4470
- throw new DeeplineError(`Choose exactly one play run target.
4471
- ${usage}`);
4472
- }
4473
- if (runId) {
4474
- return { kind: "run", runId };
4475
- }
4476
- if (playName) {
4477
- return { kind: "name", name: playName };
4478
- }
4479
- throw new DeeplineError(usage);
4480
- }
4481
- async function resolvePlayRunId(client, target) {
4482
- if (target.kind === "run") {
4483
- try {
4484
- const status = await client.getPlayStatus(target.runId);
4485
- return status.runId;
4486
- } catch (error) {
4487
- if (!(error instanceof DeeplineError) || error.statusCode !== 404) {
4488
- throw error;
4489
- }
4490
- throw new DeeplineError(`No play run found for run id: ${target.runId}`);
4491
- }
4492
- }
4493
- const runs = await client.listPlayRuns(target.name);
4494
- const workflowId = runs[0]?.workflowId ?? "";
4495
- if (!workflowId) {
4496
- throw new DeeplineError(`No runs found for play: ${target.name}`);
4497
- }
4498
- return workflowId;
4499
- }
4500
4790
  function isTransientPlayStatusPollError(error) {
4501
4791
  if (error instanceof DeeplineError && typeof error.statusCode === "number") {
4502
4792
  return error.statusCode >= 500 && error.statusCode < 600;
@@ -4600,7 +4890,7 @@ function assertPlayWaitNotTimedOut(input) {
4600
4890
  if (input.waitTimeoutMs !== null && Date.now() - input.startedAt >= input.waitTimeoutMs) {
4601
4891
  const hasRealRunId = input.workflowId.length > 0 && input.workflowId !== "pending";
4602
4892
  const phaseSuffix = input.lastPhase && input.lastPhase.trim() ? ` (last observed phase: ${input.lastPhase.trim()})` : "";
4603
- 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.`;
4893
+ 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.`;
4604
4894
  throw new DeeplineError(
4605
4895
  `Timed out waiting for play ${hasRealRunId ? input.workflowId : "<no run id>"} after ${Math.ceil(input.waitTimeoutMs / 1e3)}s${phaseSuffix}.${tailHint}`,
4606
4896
  void 0,
@@ -4613,66 +4903,6 @@ function assertPlayWaitNotTimedOut(input) {
4613
4903
  );
4614
4904
  }
4615
4905
  }
4616
- async function waitForPlayCompletionByStream(input) {
4617
- const controller = new AbortController();
4618
- let timedOut = false;
4619
- let lastPhase = null;
4620
- const timeout = input.waitTimeoutMs === null ? null : setTimeout(
4621
- () => {
4622
- timedOut = true;
4623
- controller.abort();
4624
- },
4625
- Math.max(1, input.waitTimeoutMs - (Date.now() - input.startedAt))
4626
- );
4627
- try {
4628
- for await (const event of input.client.streamPlayRunEvents(
4629
- input.workflowId,
4630
- { signal: controller.signal }
4631
- )) {
4632
- assertPlayWaitNotTimedOut({ ...input, lastPhase });
4633
- const phase = describeLiveEventPhase(event);
4634
- if (phase) {
4635
- lastPhase = phase;
4636
- input.progress.phase(phase);
4637
- }
4638
- printPlayLogLines({
4639
- lines: getLogLinesFromLiveEvent(event),
4640
- status: null,
4641
- jsonOutput: input.jsonOutput,
4642
- emitLogs: input.emitLogs,
4643
- state: input.state,
4644
- progress: input.progress
4645
- });
4646
- const status = getStatusFromLiveEvent(event);
4647
- if (status && TERMINAL_PLAY_STATUSES2.has(status)) {
4648
- const finalStatus = await input.client.getPlayStatus(input.workflowId);
4649
- if (TERMINAL_PLAY_STATUSES2.has(finalStatus.status)) {
4650
- return finalStatus;
4651
- }
4652
- }
4653
- }
4654
- } catch (error) {
4655
- if (timedOut) {
4656
- assertPlayWaitNotTimedOut({ ...input, lastPhase });
4657
- }
4658
- throw error;
4659
- } finally {
4660
- if (timeout) {
4661
- clearTimeout(timeout);
4662
- }
4663
- }
4664
- const phaseSuffix = lastPhase && lastPhase.trim() ? ` (last observed phase: ${lastPhase.trim()})` : "";
4665
- throw new DeeplineError(
4666
- `Play live stream ended before the run reached a terminal state runId=${input.workflowId}${phaseSuffix}.`,
4667
- void 0,
4668
- "PLAY_LIVE_STREAM_ENDED",
4669
- {
4670
- runId: input.workflowId,
4671
- workflowId: input.workflowId,
4672
- ...lastPhase ? { phase: lastPhase } : {}
4673
- }
4674
- );
4675
- }
4676
4906
  async function startAndWaitForPlayCompletionByStream(input) {
4677
4907
  const startedAt = Date.now();
4678
4908
  const state = {
@@ -4752,10 +4982,12 @@ async function startAndWaitForPlayCompletionByStream(input) {
4752
4982
  clearTimeout(timeout);
4753
4983
  }
4754
4984
  const reason = error instanceof Error ? error.message : String(error);
4755
- process.stderr.write(
4756
- `[play watch] start stream failed after run ${lastKnownWorkflowId}; falling back to polling (${reason})
4985
+ if (!input.jsonOutput) {
4986
+ process.stderr.write(
4987
+ `[play watch] start stream failed after run ${lastKnownWorkflowId}; falling back to polling (${reason})
4757
4988
  `
4758
- );
4989
+ );
4990
+ }
4759
4991
  return waitForPlayCompletionByPolling({
4760
4992
  client: input.client,
4761
4993
  workflowId: lastKnownWorkflowId,
@@ -4774,6 +5006,24 @@ async function startAndWaitForPlayCompletionByStream(input) {
4774
5006
  clearTimeout(timeout);
4775
5007
  }
4776
5008
  }
5009
+ if (lastKnownWorkflowId) {
5010
+ if (!input.jsonOutput) {
5011
+ input.progress.writeLine(
5012
+ `[play watch] start stream ended after run ${lastKnownWorkflowId}; falling back to polling`
5013
+ );
5014
+ }
5015
+ return waitForPlayCompletionByPolling({
5016
+ client: input.client,
5017
+ workflowId: lastKnownWorkflowId,
5018
+ pollIntervalMs: 500,
5019
+ jsonOutput: input.jsonOutput,
5020
+ emitLogs: input.emitLogs,
5021
+ waitTimeoutMs: input.waitTimeoutMs,
5022
+ startedAt,
5023
+ state,
5024
+ progress: input.progress
5025
+ });
5026
+ }
4777
5027
  const phaseSuffix = lastPhase && lastPhase.trim() ? ` (last observed phase: ${lastPhase.trim()})` : "";
4778
5028
  const idSuffix = lastKnownWorkflowId ? ` runId=${lastKnownWorkflowId}` : "";
4779
5029
  throw new DeeplineError(
@@ -4851,38 +5101,6 @@ async function waitForPlayCompletionByPolling(input) {
4851
5101
  }
4852
5102
  }
4853
5103
  }
4854
- async function waitForPlayCompletion(input) {
4855
- const startedAt = Date.now();
4856
- const state = {
4857
- lastLogIndex: 0,
4858
- emittedRunnerStarted: false
4859
- };
4860
- try {
4861
- return await waitForPlayCompletionByStream({
4862
- ...input,
4863
- startedAt,
4864
- state,
4865
- progress: input.progress
4866
- });
4867
- } catch (error) {
4868
- assertPlayWaitNotTimedOut({
4869
- workflowId: input.workflowId,
4870
- startedAt,
4871
- waitTimeoutMs: input.waitTimeoutMs
4872
- });
4873
- const reason = error instanceof Error ? error.message : String(error);
4874
- process.stderr.write(
4875
- `[play watch] SSE stream failed; falling back to polling (${reason})
4876
- `
4877
- );
4878
- return waitForPlayCompletionByPolling({
4879
- ...input,
4880
- startedAt,
4881
- state,
4882
- progress: input.progress
4883
- });
4884
- }
4885
- }
4886
5104
  function formatInteger(value) {
4887
5105
  return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : String(value ?? "-");
4888
5106
  }
@@ -4989,14 +5207,20 @@ function formatReturnValue(result) {
4989
5207
  }
4990
5208
  return lines;
4991
5209
  }
4992
- function buildOutputSummary(rowsInfo, exportedPath) {
5210
+ function buildOutputSummary(rowsInfo, runId, exportedPath) {
4993
5211
  if (!rowsInfo) {
4994
5212
  return exportedPath ? { csv_path: exportedPath } : null;
4995
5213
  }
5214
+ const isPartial = !rowsInfo.complete;
4996
5215
  return {
4997
5216
  kind: "rows",
4998
5217
  rowCount: rowsInfo.totalRows,
4999
5218
  previewRowCount: rowsInfo.rows.length,
5219
+ ...isPartial ? {
5220
+ isPartial: true,
5221
+ previewCount: rowsInfo.rows.length,
5222
+ totalCount: rowsInfo.totalRows
5223
+ } : { isPartial: false },
5000
5224
  complete: rowsInfo.complete,
5001
5225
  columns: rowsInfo.columns,
5002
5226
  source: rowsInfo.source,
@@ -5007,21 +5231,174 @@ function buildRunWarnings(status, rowsInfo) {
5007
5231
  if (status.status === "completed" && rowsInfo?.totalRows === 0) {
5008
5232
  return ["Run completed with 0 output rows."];
5009
5233
  }
5234
+ if (rowsInfo && !rowsInfo.complete) {
5235
+ return [
5236
+ `Run output is partial: showing ${rowsInfo.rows.length} preview row(s) of ${rowsInfo.totalRows}.`
5237
+ ];
5238
+ }
5010
5239
  return [];
5011
5240
  }
5012
- function buildRunNextCommands(runId) {
5241
+ function buildRunNextCommands(runId, rowsInfo) {
5242
+ const commands = {
5243
+ get: `deepline runs get ${runId} --json`,
5244
+ tail: `deepline runs tail ${runId} --json`,
5245
+ stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
5246
+ logs: `deepline runs logs ${runId} --out run.log --json`
5247
+ };
5248
+ if (!rowsInfo || rowsInfo.complete) {
5249
+ commands.exportCsv = buildRunExportCommand(runId);
5250
+ }
5251
+ return commands;
5252
+ }
5253
+ function buildRunExportCommand(runId) {
5254
+ return `deepline runs export ${runId} --out output.csv`;
5255
+ }
5256
+ var RUN_LOG_PREVIEW_LIMIT = 20;
5257
+ function getRecordField(value, key) {
5258
+ return value && typeof value === "object" && !Array.isArray(value) ? value[key] : void 0;
5259
+ }
5260
+ function getNumericField(value, key) {
5261
+ const field = getRecordField(value, key);
5262
+ return typeof field === "number" && Number.isFinite(field) ? field : null;
5263
+ }
5264
+ function getStringField(value, key) {
5265
+ const field = getRecordField(value, key);
5266
+ return typeof field === "string" && field.trim() ? field : null;
5267
+ }
5268
+ function normalizeRunStatusForEnvelope(status) {
5269
+ const run = status.run ?? null;
5013
5270
  return {
5014
- exportCsv: `deepline runs export ${runId} --out output.csv`,
5015
- status: `deepline runs status ${runId} --json`,
5016
- logs: `deepline runs logs ${runId}`
5271
+ id: status.runId,
5272
+ playName: status.playName ?? status.name ?? getStringField(run, "playName") ?? null,
5273
+ status: status.status,
5274
+ runtime: getStringField(status, "runtime") ?? getStringField(status, "runtimeBackend") ?? getStringField(run, "runtime") ?? null,
5275
+ startedAt: getStringField(run, "startTime") ?? getStringField(run, "startedAt") ?? null,
5276
+ updatedAt: getStringField(status, "updatedAt") ?? getStringField(run, "updatedAt") ?? null,
5277
+ finishedAt: getStringField(run, "closeTime") ?? getStringField(run, "finishedAt") ?? null,
5278
+ source: getRecordField(status, "source") ?? getRecordField(status, "artifact") ?? null
5017
5279
  };
5018
5280
  }
5281
+ function normalizeProgressForEnvelope(status, rowsInfo) {
5282
+ const progress = status.progress;
5283
+ const total = getNumericField(progress, "totalRows") ?? getNumericField(progress, "total") ?? rowsInfo?.totalRows ?? null;
5284
+ const failed = getNumericField(progress, "failed") ?? getNumericField(progress, "failedRows") ?? null;
5285
+ const completed = getNumericField(progress, "completed") ?? getNumericField(progress, "completedRows") ?? (status.status === "completed" ? total : null);
5286
+ const pending = getNumericField(progress, "pending") ?? (typeof total === "number" && typeof completed === "number" && typeof failed === "number" ? Math.max(0, total - completed - failed) : null);
5287
+ return {
5288
+ total,
5289
+ completed,
5290
+ pending,
5291
+ failed,
5292
+ executed: getNumericField(progress, "executed"),
5293
+ reused: getNumericField(progress, "reused"),
5294
+ skipped: getNumericField(progress, "skipped"),
5295
+ retried: getNumericField(progress, "retried"),
5296
+ degraded: typeof getRecordField(progress, "degraded") === "boolean" ? getRecordField(progress, "degraded") : null,
5297
+ duplicates: getRecordField(progress, "duplicates") ?? null,
5298
+ active: getStringField(progress, "status") ?? getStringField(status, "activeStep") ?? getStringField(status, "activeNodeId") ?? null,
5299
+ wait: status.wait ?? null
5300
+ };
5301
+ }
5302
+ function normalizeOutputsForEnvelope(rowsInfo, runId, exportedPath) {
5303
+ if (!rowsInfo) {
5304
+ return exportedPath ? [{ name: "output", kind: "file", path: exportedPath }] : [];
5305
+ }
5306
+ const isPartial = !rowsInfo.complete;
5307
+ return [
5308
+ {
5309
+ name: "rows",
5310
+ kind: "dataset",
5311
+ rowCount: rowsInfo.totalRows,
5312
+ columns: rowsInfo.columns,
5313
+ preview: rowsInfo.rows.slice(0, 5),
5314
+ previewRowCount: Math.min(rowsInfo.rows.length, 5),
5315
+ previewLimit: 5,
5316
+ ...isPartial ? {
5317
+ isPartial: true,
5318
+ previewCount: rowsInfo.rows.length,
5319
+ totalCount: rowsInfo.totalRows
5320
+ } : { isPartial: false },
5321
+ complete: rowsInfo.complete,
5322
+ source: rowsInfo.source,
5323
+ ...exportedPath ? { csv_path: exportedPath } : {}
5324
+ }
5325
+ ];
5326
+ }
5327
+ function normalizeStepsForEnvelope(status) {
5328
+ const directSteps = getRecordField(status, "steps");
5329
+ if (Array.isArray(directSteps)) {
5330
+ return directSteps;
5331
+ }
5332
+ const timeline = getRecordField(status, "timeline");
5333
+ if (Array.isArray(timeline)) {
5334
+ return timeline;
5335
+ }
5336
+ return [];
5337
+ }
5338
+ function normalizeErrorsForEnvelope(status, error) {
5339
+ const directErrors = getRecordField(status, "errors");
5340
+ if (Array.isArray(directErrors)) {
5341
+ return directErrors.filter(
5342
+ (entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)
5343
+ );
5344
+ }
5345
+ if (!error) {
5346
+ return [];
5347
+ }
5348
+ return [
5349
+ {
5350
+ code: getStringField(status, "errorCode") ?? "RUN_FAILED",
5351
+ phase: getStringField(status, "errorPhase") ?? "runtime",
5352
+ message: error,
5353
+ retryable: typeof getRecordField(status, "retryable") === "boolean" ? getRecordField(status, "retryable") : null,
5354
+ nextAction: `deepline runs get ${status.runId} --json`
5355
+ }
5356
+ ];
5357
+ }
5358
+ function normalizeLogsForEnvelope(status) {
5359
+ const logs = Array.isArray(status.progress?.logs) ? status.progress.logs : [];
5360
+ const offset = typeof status.progress?.logOffset === "number" && Number.isFinite(status.progress.logOffset) ? Math.max(0, Math.trunc(status.progress.logOffset)) : 0;
5361
+ const totalCount = offset + logs.length;
5362
+ const entries = logs.slice(Math.max(0, logs.length - RUN_LOG_PREVIEW_LIMIT));
5363
+ const firstSequence = entries.length === 0 ? null : offset + logs.length - entries.length + 1;
5364
+ const lastSequence = totalCount === 0 ? null : totalCount;
5365
+ return {
5366
+ totalCount,
5367
+ returnedCount: entries.length,
5368
+ firstSequence,
5369
+ lastSequence,
5370
+ truncated: totalCount > entries.length,
5371
+ hasMore: totalCount > entries.length,
5372
+ entries,
5373
+ nextCursor: lastSequence
5374
+ };
5375
+ }
5376
+ function stripProviderSpendFromBilling(value) {
5377
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
5378
+ return value;
5379
+ }
5380
+ const next = {};
5381
+ for (const [key, item] of Object.entries(value)) {
5382
+ if (key === "providerCostUsd" || key === "totalProviderCostUsd") {
5383
+ continue;
5384
+ }
5385
+ next[key] = item;
5386
+ }
5387
+ return next;
5388
+ }
5019
5389
  function compactPlayStatus(status, options) {
5020
5390
  const rowsInfo = extractCanonicalRowsInfo(status);
5021
5391
  const result = status && typeof status === "object" ? status.result : null;
5022
5392
  const warnings = buildRunWarnings(status, rowsInfo);
5023
- const datasetStats = rowsInfo && rowsInfo.complete ? buildDatasetStats(rowsInfo.rows, rowsInfo.totalRows, rowsInfo.columns) : null;
5024
- const billing = status && typeof status === "object" ? status.billing : null;
5393
+ const datasetStats = rowsInfo && rowsInfo.complete ? buildDatasetStats(
5394
+ rowsInfo.rows,
5395
+ rowsInfo.totalRows,
5396
+ rowsInfo.columns,
5397
+ extractDatasetExecutionStats(status)
5398
+ ) : null;
5399
+ const billing = status && typeof status === "object" ? stripProviderSpendFromBilling(
5400
+ status.billing
5401
+ ) : null;
5025
5402
  const progressError = status.progress?.error;
5026
5403
  const error = typeof progressError === "string" ? progressError : typeof status.error === "string" ? String(status.error) : null;
5027
5404
  return {
@@ -5030,16 +5407,25 @@ function compactPlayStatus(status, options) {
5030
5407
  ...typeof status.name === "string" ? { name: status.name } : {},
5031
5408
  ...typeof status.playName === "string" ? { playName: status.playName } : {},
5032
5409
  status: status.status,
5410
+ run: normalizeRunStatusForEnvelope(status),
5411
+ progress: normalizeProgressForEnvelope(status, rowsInfo),
5412
+ outputs: normalizeOutputsForEnvelope(
5413
+ rowsInfo,
5414
+ status.runId,
5415
+ options?.exportedPath
5416
+ ),
5417
+ steps: normalizeStepsForEnvelope(status),
5418
+ errors: normalizeErrorsForEnvelope(status, error),
5419
+ logs: normalizeLogsForEnvelope(status),
5033
5420
  ...error ? { error } : {},
5034
5421
  ...warnings.length > 0 ? { warnings } : {},
5035
- output: buildOutputSummary(rowsInfo, options?.exportedPath) ?? result ?? null,
5422
+ output: buildOutputSummary(rowsInfo, status.runId, options?.exportedPath) ?? result ?? null,
5036
5423
  ...result !== void 0 ? { result } : {},
5037
5424
  ...status.resultView ? { resultView: status.resultView } : {},
5038
5425
  ...datasetStats ? { dataset_stats: datasetStats } : {},
5039
5426
  ...rowsInfo ? { previewRows: rowsInfo.rows.slice(0, 5) } : {},
5040
5427
  ...billing ? { billing } : {},
5041
- ...status.run ? { run: status.run } : {},
5042
- next: buildRunNextCommands(status.runId)
5428
+ next: buildRunNextCommands(status.runId, rowsInfo)
5043
5429
  };
5044
5430
  }
5045
5431
  function enrichPlayStatusWithDatasetStats(status) {
@@ -5052,7 +5438,8 @@ function enrichPlayStatusWithDatasetStats(status) {
5052
5438
  dataset_stats: buildDatasetStats(
5053
5439
  rowsInfo.rows,
5054
5440
  rowsInfo.totalRows,
5055
- rowsInfo.columns
5441
+ rowsInfo.columns,
5442
+ extractDatasetExecutionStats(status)
5056
5443
  )
5057
5444
  };
5058
5445
  }
@@ -5067,8 +5454,9 @@ function formatDatasetStatsLines(datasetStats) {
5067
5454
  )) {
5068
5455
  const topValues = stat3.top_values ? `, top_values=${Object.entries(stat3.top_values).slice(0, 3).map(([value, count]) => `${value}=${count}`).join(", ")}` : "";
5069
5456
  const sample = stat3.sample_value !== void 0 ? `, sample_value=${JSON.stringify(stat3.sample_value)}` : "";
5457
+ const execution = stat3.execution ? `, execution=${Object.entries(stat3.execution).map(([bucket, count]) => `${bucket}=${count}`).join(", ")}` : "";
5070
5458
  lines.push(
5071
- ` ${column}: non_empty=${stat3.non_empty}, unique=${stat3.unique}${topValues}${sample}`
5459
+ ` ${column}: non_empty=${stat3.non_empty}, unique=${stat3.unique}${topValues}${sample}${execution}`
5072
5460
  );
5073
5461
  }
5074
5462
  return lines;
@@ -5091,14 +5479,28 @@ function writePlayResult(status, jsonOutput, options) {
5091
5479
  lines.push(`${success ? "\u2713" : "\u2717"} ${publicStatus} ${runId}`);
5092
5480
  const rowsInfo = extractCanonicalRowsInfo(status);
5093
5481
  const warnings = buildRunWarnings(status, rowsInfo);
5094
- const datasetStats = rowsInfo && rowsInfo.complete ? buildDatasetStats(rowsInfo.rows, rowsInfo.totalRows, rowsInfo.columns) : null;
5095
- const outputSummary = buildOutputSummary(rowsInfo, options?.exportedPath);
5482
+ const datasetStats = rowsInfo && rowsInfo.complete ? buildDatasetStats(
5483
+ rowsInfo.rows,
5484
+ rowsInfo.totalRows,
5485
+ rowsInfo.columns,
5486
+ extractDatasetExecutionStats(status)
5487
+ ) : null;
5488
+ const outputSummary = buildOutputSummary(
5489
+ rowsInfo,
5490
+ runId,
5491
+ options?.exportedPath
5492
+ );
5096
5493
  if (outputSummary) {
5097
5494
  const columns = Array.isArray(outputSummary.columns) ? outputSummary.columns.length : 0;
5098
5495
  const path = typeof outputSummary.csv_path === "string" ? ` file=${outputSummary.csv_path}` : "";
5099
5496
  lines.push(
5100
5497
  ` output: rows=${formatInteger(outputSummary.rowCount)} columns=${formatInteger(columns)}${path}`
5101
5498
  );
5499
+ if (outputSummary.isPartial === true) {
5500
+ lines.push(
5501
+ ` partial output: showing ${formatInteger(outputSummary.previewCount)} preview row(s) of ${formatInteger(outputSummary.totalCount)}`
5502
+ );
5503
+ }
5102
5504
  }
5103
5505
  for (const warning of warnings) {
5104
5506
  lines.push(` warning: ${warning}`);
@@ -5128,6 +5530,11 @@ function exportPlayStatusRows(status, outPath) {
5128
5530
  `Run ${status.runId} did not expose a row-shaped final output to export.`
5129
5531
  );
5130
5532
  }
5533
+ if (!rowsInfo.complete) {
5534
+ throw new DeeplineError(
5535
+ `Run output only includes ${rowsInfo.rows.length} preview row(s) of ${rowsInfo.totalRows}; full dataset export is not available from this status response yet.`
5536
+ );
5537
+ }
5131
5538
  return writeCanonicalRowsCsv(rowsInfo, outPath);
5132
5539
  }
5133
5540
  function renderServerResultView(value) {
@@ -5210,10 +5617,10 @@ function writeStartedPlayRun(input) {
5210
5617
  const lines = [
5211
5618
  `Started ${input.playName}`,
5212
5619
  ` run id: ${input.runId}`,
5213
- ` check status: deepline play status --run-id ${input.runId}`,
5214
- ` tail logs: deepline play tail --run-id ${input.runId}`,
5215
- ` stop run: deepline play stop --run-id ${input.runId}`,
5216
- ` result JSON: deepline play status --run-id ${input.runId} --json`
5620
+ ` get status: deepline runs get ${input.runId} --json`,
5621
+ ` tail logs: deepline runs tail ${input.runId} --json`,
5622
+ ` stop run: deepline runs stop ${input.runId} --reason "stale lock" --json`,
5623
+ ` result JSON: deepline runs get ${input.runId} --json`
5217
5624
  ];
5218
5625
  if (input.dashboardUrl) {
5219
5626
  lines.push(` play page: ${input.dashboardUrl}`);
@@ -5360,6 +5767,10 @@ function parsePlayCheckOptions(args) {
5360
5767
  const jsonOutput = argsWantJson(args);
5361
5768
  return { target, jsonOutput };
5362
5769
  }
5770
+ function shouldUseLocalOnlyPlayCheck() {
5771
+ const value = process.env.DEEPLINE_PLAY_CHECK_LOCAL_ONLY?.trim().toLowerCase();
5772
+ return value === "1" || value === "true" || value === "yes" || value === "on";
5773
+ }
5363
5774
  async function handlePlayCheck(args) {
5364
5775
  const options = parsePlayCheckOptions(args);
5365
5776
  if (!isFileTarget(options.target)) {
@@ -5385,10 +5796,28 @@ async function handlePlayCheck(args) {
5385
5796
  return 1;
5386
5797
  }
5387
5798
  const playName = graph.root.playName ?? extractPlayName(sourceCode, absolutePlayPath);
5799
+ if (shouldUseLocalOnlyPlayCheck()) {
5800
+ const result2 = {
5801
+ valid: true,
5802
+ errors: [],
5803
+ staticPipeline: graph.root.compilerManifest?.staticPipeline ?? null,
5804
+ artifactHash: graph.root.artifact.artifactHash,
5805
+ graphHash: graph.root.artifact.graphHash
5806
+ };
5807
+ if (options.jsonOutput) {
5808
+ process.stdout.write(`${JSON.stringify({ name: playName, ...result2 })}
5809
+ `);
5810
+ } else {
5811
+ console.log(`\u2713 ${playName} passed local play check`);
5812
+ console.log(` artifact: ${result2.artifactHash.slice(0, 12)}`);
5813
+ }
5814
+ return 0;
5815
+ }
5388
5816
  const client = new DeeplineClient();
5389
5817
  const result = await client.checkPlayArtifact({
5390
5818
  name: playName,
5391
5819
  sourceCode: graph.root.sourceCode,
5820
+ sourceFiles: graph.root.sourceFiles,
5392
5821
  artifact: graph.root.artifact
5393
5822
  });
5394
5823
  if (options.jsonOutput) {
@@ -5440,17 +5869,24 @@ async function handleFileBackedRun(options) {
5440
5869
  const packagedFileUploads = bundleResult.packagedFiles.map(
5441
5870
  (file) => stageFile(file.logicalPath, file.absolutePath)
5442
5871
  );
5872
+ const fileInputBindings = fileInputBindingsFromStaticPipeline(
5873
+ requireCompilerManifest(bundleResult).staticPipeline
5874
+ );
5875
+ applyCsvShortcutInput({
5876
+ runtimeInput,
5877
+ bindings: fileInputBindings,
5878
+ fallbackInputPath: "file"
5879
+ });
5443
5880
  const stagedFileInputs = await stageFileInputArgs({
5444
5881
  client,
5445
5882
  runtimeInput,
5446
- bindings: fileInputBindingsFromStaticPipeline(
5447
- requireCompilerManifest(bundleResult).staticPipeline
5448
- ),
5883
+ bindings: fileInputBindings,
5449
5884
  progress
5450
5885
  });
5451
5886
  const startRequest = {
5452
5887
  name: playName,
5453
5888
  sourceCode: bundleResult.sourceCode,
5889
+ sourceFiles: bundleResult.sourceFiles,
5454
5890
  runtimeArtifact: bundleResult.artifact,
5455
5891
  compilerManifest: requireCompilerManifest(bundleResult),
5456
5892
  packagedFileUploads,
@@ -5525,13 +5961,18 @@ async function handleNamedRun(options) {
5525
5961
  selector: options.revisionSelector
5526
5962
  });
5527
5963
  const runtimeInput = options.input ? { ...options.input } : {};
5964
+ const fileInputBindings = [
5965
+ ...fileInputBindingsFromPlaySchema(playDetail.play.inputSchema),
5966
+ ...fileInputBindingsFromStaticPipeline(playDetail.play.staticPipeline)
5967
+ ];
5968
+ applyCsvShortcutInput({
5969
+ runtimeInput,
5970
+ bindings: fileInputBindings
5971
+ });
5528
5972
  const stagedFileInputs = await stageFileInputArgs({
5529
5973
  client,
5530
5974
  runtimeInput,
5531
- bindings: [
5532
- ...fileInputBindingsFromPlaySchema(playDetail.play.inputSchema),
5533
- ...fileInputBindingsFromStaticPipeline(playDetail.play.staticPipeline)
5534
- ],
5975
+ bindings: fileInputBindings,
5535
5976
  progress
5536
5977
  });
5537
5978
  const startRequest = {
@@ -5609,80 +6050,101 @@ async function handlePlayRun(args) {
5609
6050
  }
5610
6051
  return handleNamedRun(options);
5611
6052
  }
5612
- async function handlePlayTail(args) {
5613
- const usage = "Usage: deepline play tail --run-id <run-id> [--interval-ms 1000] [--json]\n deepline play tail --name <name> [--interval-ms 1000] [--json]";
5614
- let target;
5615
- try {
5616
- target = parsePlayRunTarget({ args, usage, allowName: true });
5617
- } catch (error) {
5618
- console.error(error instanceof Error ? error.message : usage);
5619
- return 1;
5620
- }
5621
- const client = new DeeplineClient();
5622
- const jsonOutput = argsWantJson(args);
5623
- const emitLogs = !jsonOutput || args.includes("--logs");
5624
- let intervalMs = 500;
6053
+ function parseRunIdPositional(args, usage) {
5625
6054
  for (let index = 0; index < args.length; index += 1) {
5626
6055
  const arg = args[index];
5627
- if ((arg === "--interval-ms" || arg === "--poll-interval-ms") && args[index + 1]) {
5628
- intervalMs = parsePositiveInteger2(args[++index], arg);
6056
+ if (arg === "--json" || arg === "--full" || arg === "--logs" || arg === "--compact" || arg === "--limit") {
6057
+ if (arg === "--limit" && args[index + 1]) {
6058
+ index += 1;
6059
+ }
6060
+ continue;
6061
+ }
6062
+ if ((arg === "--out" || arg === "--cursor" || arg === "--reason" || arg === "--interval-ms" || arg === "--poll-interval-ms") && args[index + 1]) {
6063
+ index += 1;
6064
+ continue;
6065
+ }
6066
+ if (!arg.startsWith("--")) {
6067
+ return arg;
5629
6068
  }
5630
6069
  }
5631
- const workflowId = await resolvePlayRunId(client, target);
5632
- const progress = getActiveCliProgress() ?? createCliProgress(!jsonOutput);
5633
- progress.phase(`tailing ${workflowId}`);
5634
- const finalStatus = await waitForPlayCompletion({
5635
- client,
5636
- workflowId,
5637
- pollIntervalMs: intervalMs,
5638
- jsonOutput,
5639
- emitLogs,
5640
- waitTimeoutMs: null,
5641
- progress
5642
- });
5643
- if (finalStatus.status === "completed") {
5644
- progress.complete();
5645
- } else {
5646
- progress.fail();
5647
- }
5648
- writePlayResult(finalStatus, jsonOutput);
5649
- return finalStatus.status === "completed" ? 0 : 1;
6070
+ throw new DeeplineError(usage);
5650
6071
  }
5651
- async function handlePlayStatus(args) {
5652
- const usage = "Usage: deepline play status --run-id <run-id> [--json] [--full]\n deepline play status --name <name> [--json] [--full]";
5653
- let target;
6072
+ async function handleRunGet(args) {
6073
+ const usage = "Usage: deepline runs get <run-id> [--json] [--full]";
6074
+ let runId;
5654
6075
  try {
5655
- target = parsePlayRunTarget({ args, usage, allowName: true });
6076
+ runId = parseRunIdPositional(args, usage);
5656
6077
  } catch (error) {
5657
6078
  console.error(error instanceof Error ? error.message : usage);
5658
6079
  return 1;
5659
6080
  }
5660
6081
  const client = new DeeplineClient();
5661
- const workflowId = await resolvePlayRunId(client, target);
5662
- const status = await client.getPlayStatus(workflowId);
6082
+ const status = await client.runs.get(runId);
5663
6083
  writePlayResult(status, argsWantJson(args), {
5664
6084
  fullJson: args.includes("--full")
5665
6085
  });
5666
6086
  return 0;
5667
6087
  }
5668
- function parseRunIdPositional(args, usage) {
6088
+ async function handleRunsList(args) {
6089
+ const usage = "Usage: deepline runs list --play <play-name> [--status <status>] [--json]";
6090
+ let playName = null;
6091
+ let statusFilter = null;
5669
6092
  for (let index = 0; index < args.length; index += 1) {
5670
6093
  const arg = args[index];
5671
- if (arg === "--json" || arg === "--full" || arg === "--logs") {
6094
+ if ((arg === "--play" || arg === "--name") && args[index + 1]) {
6095
+ playName = parseReferencedPlayTarget(args[++index]).playName;
5672
6096
  continue;
5673
6097
  }
5674
- if (arg === "--out" && args[index + 1]) {
5675
- index += 1;
6098
+ if (arg === "--status" && args[index + 1]) {
6099
+ statusFilter = args[++index].trim().toLowerCase();
5676
6100
  continue;
5677
6101
  }
5678
- if (!arg.startsWith("--")) {
5679
- return arg;
6102
+ if (arg === "--json" || arg === "--compact") {
6103
+ continue;
5680
6104
  }
5681
6105
  }
5682
- throw new DeeplineError(usage);
6106
+ if (!playName) {
6107
+ console.error(usage);
6108
+ return 1;
6109
+ }
6110
+ const client = new DeeplineClient();
6111
+ const runs = (await client.runs.list({
6112
+ play: playName,
6113
+ ...statusFilter ? { status: statusFilter } : {}
6114
+ })).map((run) => ({
6115
+ runId: run.workflowId,
6116
+ workflowId: run.workflowId,
6117
+ temporalRunId: run.runId,
6118
+ status: String(run.status ?? "").toLowerCase(),
6119
+ startedAt: run.startTime,
6120
+ finishedAt: run.closeTime,
6121
+ executionTime: run.executionTime,
6122
+ playName: run.memo?.playName ?? playName
6123
+ }));
6124
+ if (argsWantJson(args)) {
6125
+ process.stdout.write(
6126
+ `${JSON.stringify({
6127
+ runs,
6128
+ count: runs.length,
6129
+ next: {
6130
+ get: runs[0]?.runId ? `deepline runs get ${runs[0].runId} --json` : null
6131
+ }
6132
+ })}
6133
+ `
6134
+ );
6135
+ } else {
6136
+ if (runs.length === 0) {
6137
+ console.log(`No runs found for ${playName}.`);
6138
+ } else {
6139
+ for (const run of runs) {
6140
+ console.log(`${run.runId} ${run.status} ${formatTimestamp(run.startedAt)}`);
6141
+ }
6142
+ }
6143
+ }
6144
+ return 0;
5683
6145
  }
5684
- async function handleRunStatus(args) {
5685
- const usage = "Usage: deepline runs status <run-id> [--json] [--full]";
6146
+ async function handleRunTail(args) {
6147
+ const usage = "Usage: deepline runs tail <run-id> [--json] [--compact] [--cursor <cursor>]";
5686
6148
  let runId;
5687
6149
  try {
5688
6150
  runId = parseRunIdPositional(args, usage);
@@ -5691,14 +6153,24 @@ async function handleRunStatus(args) {
5691
6153
  return 1;
5692
6154
  }
5693
6155
  const client = new DeeplineClient();
5694
- const status = await client.getPlayStatus(runId);
5695
- writePlayResult(status, argsWantJson(args), {
5696
- fullJson: args.includes("--full")
6156
+ let afterLogIndex;
6157
+ for (let index = 0; index < args.length; index += 1) {
6158
+ const arg = args[index];
6159
+ if (arg === "--cursor" && args[index + 1]) {
6160
+ const parsed = Number(args[++index]);
6161
+ if (Number.isInteger(parsed) && parsed >= 0) {
6162
+ afterLogIndex = parsed;
6163
+ }
6164
+ }
6165
+ }
6166
+ const status = await client.runs.tail(runId, {
6167
+ ...afterLogIndex !== void 0 ? { afterLogIndex } : {}
5697
6168
  });
5698
- return 0;
6169
+ writePlayResult(status, argsWantJson(args));
6170
+ return status.status === "failed" ? 1 : 0;
5699
6171
  }
5700
6172
  async function handleRunLogs(args) {
5701
- const usage = "Usage: deepline runs logs <run-id> [--json]";
6173
+ const usage = "Usage: deepline runs logs <run-id> [--limit 200] [--out run.log] [--json]";
5702
6174
  let runId;
5703
6175
  try {
5704
6176
  runId = parseRunIdPositional(args, usage);
@@ -5706,14 +6178,86 @@ async function handleRunLogs(args) {
5706
6178
  console.error(error instanceof Error ? error.message : usage);
5707
6179
  return 1;
5708
6180
  }
6181
+ let limit = 200;
6182
+ let outPath = null;
6183
+ for (let index = 0; index < args.length; index += 1) {
6184
+ const arg = args[index];
6185
+ if (arg === "--limit" && args[index + 1]) {
6186
+ limit = parsePositiveInteger2(args[++index], "--limit");
6187
+ continue;
6188
+ }
6189
+ if (arg === "--out" && args[index + 1]) {
6190
+ outPath = (0, import_node_path8.resolve)(args[++index]);
6191
+ }
6192
+ }
5709
6193
  const client = new DeeplineClient();
5710
- const status = await client.getPlayStatus(runId);
6194
+ const status = await client.runs.get(runId);
5711
6195
  const logs = status.progress?.logs ?? [];
6196
+ if (outPath) {
6197
+ (0, import_node_fs6.writeFileSync)(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
6198
+ if (argsWantJson(args)) {
6199
+ process.stdout.write(
6200
+ `${JSON.stringify({
6201
+ runId: status.runId,
6202
+ log_path: outPath,
6203
+ lineCount: logs.length
6204
+ })}
6205
+ `
6206
+ );
6207
+ } else {
6208
+ console.log(`Wrote ${logs.length} log lines to ${outPath}`);
6209
+ }
6210
+ return 0;
6211
+ }
6212
+ const entries = logs.slice(Math.max(0, logs.length - limit));
5712
6213
  if (argsWantJson(args)) {
5713
- process.stdout.write(`${JSON.stringify({ runId: status.runId, logs })}
6214
+ process.stdout.write(
6215
+ `${JSON.stringify({
6216
+ runId: status.runId,
6217
+ totalCount: logs.length,
6218
+ returnedCount: entries.length,
6219
+ firstSequence: logs.length === 0 ? null : logs.length - entries.length + 1,
6220
+ lastSequence: logs.length === 0 ? null : logs.length,
6221
+ truncated: logs.length > entries.length,
6222
+ hasMore: logs.length > entries.length,
6223
+ entries,
6224
+ next: {
6225
+ export: `deepline runs logs ${status.runId} --out run.log --json`
6226
+ }
6227
+ })}
6228
+ `
6229
+ );
6230
+ } else {
6231
+ process.stdout.write(`${entries.join("\n")}${entries.length > 0 ? "\n" : ""}`);
6232
+ }
6233
+ return 0;
6234
+ }
6235
+ async function handleRunStop(args) {
6236
+ const usage = 'Usage: deepline runs stop <run-id> [--reason "text"] [--json]';
6237
+ let runId;
6238
+ try {
6239
+ runId = parseRunIdPositional(args, usage);
6240
+ } catch (error) {
6241
+ console.error(error instanceof Error ? error.message : usage);
6242
+ return 1;
6243
+ }
6244
+ let reason;
6245
+ for (let index = 0; index < args.length; index += 1) {
6246
+ const arg = args[index];
6247
+ if (arg === "--reason" && args[index + 1]) {
6248
+ reason = args[++index];
6249
+ }
6250
+ }
6251
+ const client = new DeeplineClient();
6252
+ const result = await client.runs.stop(runId, { reason });
6253
+ if (argsWantJson(args)) {
6254
+ process.stdout.write(`${JSON.stringify(result)}
5714
6255
  `);
5715
6256
  } else {
5716
- process.stdout.write(`${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
6257
+ console.log(`Stopped ${result.runId}`);
6258
+ if (result.hitlCancelledCount > 0) {
6259
+ console.log(` cancelled HITL waits: ${result.hitlCancelledCount}`);
6260
+ }
5717
6261
  }
5718
6262
  return 0;
5719
6263
  }
@@ -5756,37 +6300,6 @@ async function handleRunExport(args) {
5756
6300
  }
5757
6301
  return 0;
5758
6302
  }
5759
- async function handlePlayStop(args) {
5760
- const usage = 'Usage: deepline play stop --run-id <run-id> [--reason "text"] [--json]';
5761
- let target;
5762
- try {
5763
- target = parsePlayRunTarget({ args, usage, allowName: false });
5764
- } catch (error) {
5765
- console.error(error instanceof Error ? error.message : usage);
5766
- return 1;
5767
- }
5768
- const client = new DeeplineClient();
5769
- const jsonOutput = argsWantJson(args);
5770
- let reason;
5771
- for (let index = 0; index < args.length; index += 1) {
5772
- const arg = args[index];
5773
- if (arg === "--reason" && args[index + 1]) {
5774
- reason = args[++index];
5775
- }
5776
- }
5777
- const workflowId = await resolvePlayRunId(client, target);
5778
- const result = await client.stopPlay(workflowId, { reason });
5779
- if (jsonOutput) {
5780
- process.stdout.write(`${JSON.stringify(result)}
5781
- `);
5782
- } else {
5783
- console.log(`Stopped ${result.runId}`);
5784
- if (result.hitlCancelledCount > 0) {
5785
- console.log(` cancelled HITL waits: ${result.hitlCancelledCount}`);
5786
- }
5787
- }
5788
- return 0;
5789
- }
5790
6303
  async function handlePlayGet(args) {
5791
6304
  const target = args[0];
5792
6305
  if (!target) {
@@ -5874,33 +6387,6 @@ async function handlePlayGet(args) {
5874
6387
  }
5875
6388
  return 0;
5876
6389
  }
5877
- async function handlePlayRuns(args) {
5878
- const nameIndex = args.indexOf("--name");
5879
- const name = nameIndex >= 0 ? args[nameIndex + 1] : void 0;
5880
- if (!name) {
5881
- console.error("Usage: deepline play runs --name <name> [--json]");
5882
- return 1;
5883
- }
5884
- const client = new DeeplineClient();
5885
- const jsonOutput = argsWantJson(args);
5886
- await assertCanonicalNamedPlayReference(client, name);
5887
- const runs = await client.listPlayRuns(
5888
- parseReferencedPlayTarget(name).playName
5889
- );
5890
- if (jsonOutput) {
5891
- process.stdout.write(`${JSON.stringify({ runs })}
5892
- `);
5893
- return 0;
5894
- }
5895
- if (runs.length === 0) {
5896
- console.log(`No runs found for ${name}.`);
5897
- return 0;
5898
- }
5899
- for (const run of runs) {
5900
- console.log(formatRunLine(run));
5901
- }
5902
- return 0;
5903
- }
5904
6390
  function formatVersionLine(version) {
5905
6391
  const revisionLabel = version.artifactHash?.slice(0, 12) ?? "unknown-revision";
5906
6392
  return `v${version.version} ${revisionLabel} ${formatTimestamp(version.createdAt)}`;
@@ -6032,6 +6518,11 @@ function printPlayDescription(play) {
6032
6518
  }
6033
6519
  }
6034
6520
  console.log(` Run: ${play.runCommand}`);
6521
+ if (play.origin === "prebuilt" && !play.canEdit) {
6522
+ console.log(
6523
+ ` Customize: deepline plays get ${reference} --source --out ./my-play.play.ts`
6524
+ );
6525
+ }
6035
6526
  }
6036
6527
  async function handlePlaySearch(args) {
6037
6528
  let options;
@@ -6129,6 +6620,7 @@ async function handlePlayPublish(args) {
6129
6620
  const published = await client.registerPlayArtifact({
6130
6621
  name: rootPlayName,
6131
6622
  sourceCode: graph.root.sourceCode,
6623
+ sourceFiles: graph.root.sourceFiles,
6132
6624
  artifact: graph.root.artifact,
6133
6625
  compilerManifest: requireCompilerManifest(graph.root),
6134
6626
  publish: true
@@ -6349,50 +6841,12 @@ Examples:
6349
6841
  ...options.json ? ["--json"] : []
6350
6842
  ]);
6351
6843
  });
6352
- play.command("runs").description("List runs for a named play.").option("--name <name>", "Saved play name").option("--json", "Emit JSON output").action(async (options) => {
6353
- process.exitCode = await handlePlayRuns([
6354
- ...options.name ? ["--name", options.name] : [],
6355
- ...options.json ? ["--json"] : []
6356
- ]);
6357
- });
6358
6844
  play.command("versions").description("List revisions for a named play.").option("--name <name>", "Saved play name").option("--json", "Emit JSON output").action(async (options) => {
6359
6845
  process.exitCode = await handlePlayVersions([
6360
6846
  ...options.name ? ["--name", options.name] : [],
6361
6847
  ...options.json ? ["--json"] : []
6362
6848
  ]);
6363
6849
  });
6364
- 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) => {
6365
- process.exitCode = await handlePlayTail([
6366
- ...options.runId ? ["--run-id", options.runId] : [],
6367
- ...options.name ? ["--name", options.name] : [],
6368
- ...options.intervalMs ? ["--interval-ms", options.intervalMs] : [],
6369
- ...options.logs ? ["--logs"] : [],
6370
- ...options.json ? ["--json"] : []
6371
- ]);
6372
- });
6373
- 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", "Debug only: with --json, emit the raw status payload").action(async (options) => {
6374
- process.exitCode = await handlePlayStatus([
6375
- ...options.runId ? ["--run-id", options.runId] : [],
6376
- ...options.name ? ["--name", options.name] : [],
6377
- ...options.json ? ["--json"] : [],
6378
- ...options.full ? ["--full"] : []
6379
- ]);
6380
- });
6381
- 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) => {
6382
- process.exitCode = await handleRunExport([
6383
- options.runId,
6384
- "--out",
6385
- options.out,
6386
- ...options.json ? ["--json"] : []
6387
- ]);
6388
- });
6389
- 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) => {
6390
- process.exitCode = await handlePlayStop([
6391
- ...options.runId ? ["--run-id", options.runId] : [],
6392
- ...options.reason ? ["--reason", options.reason] : [],
6393
- ...options.json ? ["--json"] : []
6394
- ]);
6395
- });
6396
6850
  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) => {
6397
6851
  process.exitCode = await handlePlayPublish([
6398
6852
  target,
@@ -6408,38 +6862,72 @@ Examples:
6408
6862
  ...options.json ? ["--json"] : []
6409
6863
  ]);
6410
6864
  });
6411
- const runs = program.command("runs").description("Inspect and export play runs.").addHelpText(
6865
+ const runs = program.command("runs").description("Inspect, tail, stop, and export play runs.").addHelpText(
6412
6866
  "after",
6413
6867
  `
6414
6868
  Examples:
6415
- deepline runs status play/my-play/run/20260501t000000-000
6869
+ deepline runs get play/my-play/run/20260501t000000-000 --json
6870
+ deepline runs tail play/my-play/run/20260501t000000-000
6871
+ deepline runs logs play/my-play/run/20260501t000000-000 --out run.log --json
6872
+ deepline runs list --play my-play --status failed --json
6873
+ deepline runs stop play/my-play/run/20260501t000000-000 --reason "stale lock" --json
6416
6874
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv
6417
- deepline runs logs play/my-play/run/20260501t000000-000
6418
6875
  `
6419
6876
  );
6420
- 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", "Debug only: with --json, emit the raw status payload").action(async (runId, options) => {
6421
- process.exitCode = await handleRunStatus([
6877
+ 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) => {
6878
+ process.exitCode = await handleRunGet([
6422
6879
  runId,
6423
6880
  ...options.json ? ["--json"] : [],
6424
6881
  ...options.full ? ["--full"] : []
6425
6882
  ]);
6426
6883
  });
6427
- 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) => {
6428
- process.exitCode = await handleRunExport([
6429
- runId,
6430
- "--out",
6431
- options.out,
6884
+ 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) => {
6885
+ process.exitCode = await handleRunsList([
6886
+ "--play",
6887
+ options.play,
6888
+ ...options.status ? ["--status", options.status] : [],
6889
+ ...options.compact ? ["--compact"] : [],
6432
6890
  ...options.json ? ["--json"] : []
6433
6891
  ]);
6434
6892
  });
6435
- 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) => {
6893
+ 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) => {
6894
+ process.exitCode = await handleRunTail([
6895
+ runId,
6896
+ ...options.json ? ["--json"] : [],
6897
+ ...options.compact ? ["--compact"] : [],
6898
+ ...options.cursor ? ["--cursor", options.cursor] : []
6899
+ ]);
6900
+ });
6901
+ 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) => {
6436
6902
  process.exitCode = await handleRunLogs([
6437
6903
  runId,
6904
+ ...options.limit ? ["--limit", options.limit] : [],
6905
+ ...options.out ? ["--out", options.out] : [],
6906
+ ...options.json ? ["--json"] : []
6907
+ ]);
6908
+ });
6909
+ 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) => {
6910
+ process.exitCode = await handleRunStop([
6911
+ runId,
6912
+ ...options.reason ? ["--reason", options.reason] : [],
6913
+ ...options.json ? ["--json"] : []
6914
+ ]);
6915
+ });
6916
+ 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) => {
6917
+ process.exitCode = await handleRunExport([
6918
+ runId,
6919
+ "--out",
6920
+ options.out,
6438
6921
  ...options.json ? ["--json"] : []
6439
6922
  ]);
6440
6923
  });
6441
6924
  }
6442
6925
 
6926
+ // src/cli/commands/tools.ts
6927
+ var import_node_fs8 = require("fs");
6928
+ var import_node_os7 = require("os");
6929
+ var import_node_path10 = require("path");
6930
+
6443
6931
  // src/tool-output.ts
6444
6932
  var import_node_fs7 = require("fs");
6445
6933
  var import_node_os6 = require("os");
@@ -6608,31 +7096,23 @@ async function listTools(args) {
6608
7096
  }
6609
7097
  return 0;
6610
7098
  }
6611
- function toolMatchesQuery(tool, terms) {
6612
- const haystack = [
6613
- tool.toolId,
6614
- tool.provider,
6615
- tool.displayName,
6616
- tool.description,
6617
- tool.operation,
6618
- tool.operationId,
6619
- ...tool.operationAliases ?? [],
6620
- ...tool.categories ?? [],
6621
- tool.inputSchema ? JSON.stringify(tool.inputSchema) : ""
6622
- ].filter(Boolean).join(" ").toLowerCase();
6623
- return terms.every((term) => haystack.includes(term));
6624
- }
6625
- async function searchTools(args) {
6626
- const query = args[0]?.trim();
7099
+ async function searchTools(queryInput, options = {}) {
7100
+ const query = queryInput.trim();
6627
7101
  if (!query) {
6628
7102
  console.error("Usage: deepline tools search <query> [--json]");
6629
7103
  return 1;
6630
7104
  }
6631
- const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
6632
7105
  const client = new DeeplineClient();
6633
- const items = (await client.listTools()).filter((tool) => toolMatchesQuery(tool, terms)).map(toListedTool);
6634
- if (argsWantJson(args)) {
6635
- process.stdout.write(`${JSON.stringify({ tools: items })}
7106
+ const result = await client.searchTools({
7107
+ query,
7108
+ categories: options.categories,
7109
+ searchTerms: options.searchTerms,
7110
+ searchMode: options.searchMode,
7111
+ includeSearchDebug: options.includeSearchDebug
7112
+ });
7113
+ const items = result.tools.map(toListedTool);
7114
+ if (options.json || shouldEmitJson()) {
7115
+ process.stdout.write(`${JSON.stringify({ ...result, tools: items })}
6636
7116
  `);
6637
7117
  return 0;
6638
7118
  }
@@ -6686,11 +7166,14 @@ Common commands:
6686
7166
  ...options.json ? ["--json"] : []
6687
7167
  ]);
6688
7168
  });
6689
- tools.command("search <query>").description("Search available tools.").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (query, options) => {
6690
- process.exitCode = await searchTools([
6691
- query,
6692
- ...options.json ? ["--json"] : []
6693
- ]);
7169
+ 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) => {
7170
+ process.exitCode = await searchTools(query, {
7171
+ json: options.json,
7172
+ categories: options.categories,
7173
+ searchTerms: options.searchTerms ?? options.search_terms,
7174
+ searchMode: options.searchMode === "v1" || options.searchMode === "v2" ? options.searchMode : void 0,
7175
+ includeSearchDebug: Boolean(options.includeSearchDebug)
7176
+ });
6694
7177
  });
6695
7178
  tools.command("get <toolId>").alias("describe").description("Show metadata for a tool.").addHelpText(
6696
7179
  "after",
@@ -7048,6 +7531,61 @@ function parseExecuteOptions(args) {
7048
7531
  }
7049
7532
  return { toolId, params, outputFormat, noPreview };
7050
7533
  }
7534
+ function safeFileStem(value) {
7535
+ return value.trim().replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "tool";
7536
+ }
7537
+ function shellQuote(value) {
7538
+ return `'${value.replace(/'/g, `'\\''`)}'`;
7539
+ }
7540
+ function powerShellQuote(value) {
7541
+ return `'${value.replace(/'/g, "''")}'`;
7542
+ }
7543
+ function seedToolListScript(input) {
7544
+ const stem = safeFileStem(input.toolId);
7545
+ const fileName = `${stem}-workflow-seed-${Date.now()}.play.ts`;
7546
+ const scriptDir = (0, import_node_fs8.mkdtempSync)((0, import_node_path10.join)((0, import_node_os7.tmpdir)(), "deepline-workflow-seed-"));
7547
+ (0, import_node_fs8.chmodSync)(scriptDir, 448);
7548
+ const scriptPath = (0, import_node_path10.join)(scriptDir, fileName);
7549
+ const projectDir = `deepline/projects/${stem}-workflow`;
7550
+ const playName = `${stem}-workflow`;
7551
+ const sampleRows = input.rows.length > 0 ? `${JSON.stringify(input.rows.slice(0, 2)).replace(/\]$/, "")}, ...]` : "[]";
7552
+ const columns = Object.keys(input.rows[0] ?? {}).join(", ");
7553
+ const rowKey = Object.prototype.hasOwnProperty.call(input.rows[0] ?? {}, "id") ? '"id"' : "(row) => JSON.stringify(row)";
7554
+ const script = `import { definePlay } from 'deepline';
7555
+
7556
+ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
7557
+ const result = await ctx.tools.execute({
7558
+ id: ${JSON.stringify(input.toolId)},
7559
+ tool: ${JSON.stringify(input.toolId)},
7560
+ input: ${JSON.stringify(input.payload)},
7561
+ description: ${JSON.stringify(`Seed ${input.toolId} rows for workflow expansion.`)},
7562
+ });
7563
+
7564
+ const list = Object.values(result.lists)[0];
7565
+ const rows = (list?.get() ?? []).slice(0, 100);
7566
+ // ${sampleRows}
7567
+ // columns: ${columns}
7568
+ // .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.' }))
7569
+ // .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.' }))
7570
+ // ctx.map is idempotent by map key + row key; reruns reuse completed rows.
7571
+ const enrichedData = await ctx
7572
+ .map('enriched_data', rows, { key: ${rowKey} })
7573
+ .run({ description: 'Enrich seeded rows.' });
7574
+
7575
+ return {
7576
+ rows: enrichedData,
7577
+ count: await enrichedData.count(),
7578
+ };
7579
+ });
7580
+ `;
7581
+ (0, import_node_fs8.writeFileSync)(scriptPath, script, { encoding: "utf-8", mode: 384 });
7582
+ return {
7583
+ path: scriptPath,
7584
+ projectDir,
7585
+ macCopyCommand: `mkdir -p ${shellQuote(projectDir)} && cp ${shellQuote(scriptPath)} ${shellQuote(`${projectDir}/${fileName}`)}`,
7586
+ windowsCopyCommand: `New-Item -ItemType Directory -Force -Path ${powerShellQuote(projectDir.replace(/\//g, "\\"))} | Out-Null; Copy-Item -LiteralPath ${powerShellQuote(scriptPath)} -Destination ${powerShellQuote(`${projectDir.replace(/\//g, "\\")}\\${fileName}`)}`
7587
+ };
7588
+ }
7051
7589
  async function executeTool(args) {
7052
7590
  let parsed;
7053
7591
  try {
@@ -7101,6 +7639,11 @@ async function executeTool(args) {
7101
7639
  return 0;
7102
7640
  }
7103
7641
  const csv = writeCsvOutputFile(listConversion.rows, `${parsed.toolId}_output`);
7642
+ const seededScript = seedToolListScript({
7643
+ toolId: parsed.toolId,
7644
+ payload: parsed.params,
7645
+ rows: listConversion.rows
7646
+ });
7104
7647
  if (parsed.outputFormat === "csv_file") {
7105
7648
  process.stdout.write(`${JSON.stringify({
7106
7649
  extracted_csv: csv.path,
@@ -7109,6 +7652,12 @@ async function executeTool(args) {
7109
7652
  preview: csv.preview,
7110
7653
  list_strategy: listConversion.strategy,
7111
7654
  list_source_path: listConversion.sourcePath,
7655
+ starter_script: seededScript.path,
7656
+ project_dir: seededScript.projectDir,
7657
+ copy_to_project: {
7658
+ macos_linux: seededScript.macCopyCommand,
7659
+ windows_powershell: seededScript.windowsCopyCommand
7660
+ },
7112
7661
  summary
7113
7662
  })}
7114
7663
  `);
@@ -7126,14 +7675,20 @@ async function executeTool(args) {
7126
7675
  console.log(`summary: ${Object.entries(summary).map(([key, value]) => `${key}=${String(value)}`).join(", ")}`);
7127
7676
  }
7128
7677
  console.log(`preview: ${JSON.stringify(csv.preview)}`);
7678
+ console.log(`starter script: ${seededScript.path}`);
7679
+ console.log(
7680
+ "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."
7681
+ );
7682
+ console.log(`macOS/Linux: ${seededScript.macCopyCommand}`);
7683
+ console.log(`Windows PowerShell: ${seededScript.windowsCopyCommand}`);
7129
7684
  return 0;
7130
7685
  }
7131
7686
 
7132
7687
  // src/cli/skills-sync.ts
7133
7688
  var import_node_child_process2 = require("child_process");
7134
- var import_node_fs8 = require("fs");
7135
- var import_node_os7 = require("os");
7136
- var import_node_path10 = require("path");
7689
+ var import_node_fs9 = require("fs");
7690
+ var import_node_os8 = require("os");
7691
+ var import_node_path11 = require("path");
7137
7692
  var CHECK_TIMEOUT_MS2 = 3e3;
7138
7693
  var SDK_SKILL_NAME = "deepline-sdk";
7139
7694
  var SKILL_AGENTS = ["codex", "claude-code", "cursor"];
@@ -7143,22 +7698,22 @@ function shouldSkipSkillsSync() {
7143
7698
  return value === "1" || value === "true" || value === "yes" || value === "on";
7144
7699
  }
7145
7700
  function sdkSkillsVersionPath(baseUrl) {
7146
- const home = process.env.HOME?.trim() || (0, import_node_os7.homedir)();
7147
- return (0, import_node_path10.join)(home, ".local", "deepline", baseUrlSlug(baseUrl), "sdk-skills", ".version");
7701
+ const home = process.env.HOME?.trim() || (0, import_node_os8.homedir)();
7702
+ return (0, import_node_path11.join)(home, ".local", "deepline", baseUrlSlug(baseUrl), "sdk-skills", ".version");
7148
7703
  }
7149
7704
  function readLocalSkillsVersion(baseUrl) {
7150
7705
  const path = sdkSkillsVersionPath(baseUrl);
7151
- if (!(0, import_node_fs8.existsSync)(path)) return "";
7706
+ if (!(0, import_node_fs9.existsSync)(path)) return "";
7152
7707
  try {
7153
- return (0, import_node_fs8.readFileSync)(path, "utf-8").trim();
7708
+ return (0, import_node_fs9.readFileSync)(path, "utf-8").trim();
7154
7709
  } catch {
7155
7710
  return "";
7156
7711
  }
7157
7712
  }
7158
7713
  function writeLocalSkillsVersion(baseUrl, version) {
7159
7714
  const path = sdkSkillsVersionPath(baseUrl);
7160
- (0, import_node_fs8.mkdirSync)((0, import_node_path10.dirname)(path), { recursive: true });
7161
- (0, import_node_fs8.writeFileSync)(path, `${version}
7715
+ (0, import_node_fs9.mkdirSync)((0, import_node_path11.dirname)(path), { recursive: true });
7716
+ (0, import_node_fs9.writeFileSync)(path, `${version}
7162
7717
  `, "utf-8");
7163
7718
  }
7164
7719
  async function fetchSkillsUpdate(baseUrl, localVersion) {
@@ -7189,9 +7744,26 @@ async function fetchSkillsUpdate(baseUrl, localVersion) {
7189
7744
  clearTimeout(timeout);
7190
7745
  }
7191
7746
  }
7192
- function runSkillsInstall(baseUrl) {
7747
+ function buildSkillsInstallArgs(baseUrl) {
7748
+ const packageUrl = new URL("/.well-known/skills/index.json", baseUrl).toString();
7749
+ return [
7750
+ "--yes",
7751
+ "skills",
7752
+ "add",
7753
+ packageUrl,
7754
+ "--agents",
7755
+ ...SKILL_AGENTS,
7756
+ "--global",
7757
+ "--yes",
7758
+ "--skill",
7759
+ SDK_SKILL_NAME,
7760
+ "--full-depth"
7761
+ ];
7762
+ }
7763
+ function buildBunxSkillsInstallArgs(baseUrl) {
7193
7764
  const packageUrl = new URL("/.well-known/skills/index.json", baseUrl).toString();
7194
- const args = [
7765
+ return [
7766
+ "--bun",
7195
7767
  "skills",
7196
7768
  "add",
7197
7769
  packageUrl,
@@ -7203,8 +7775,40 @@ function runSkillsInstall(baseUrl) {
7203
7775
  SDK_SKILL_NAME,
7204
7776
  "--full-depth"
7205
7777
  ];
7778
+ }
7779
+ function hasCommand(command) {
7780
+ const result = (0, import_node_child_process2.spawnSync)(command, ["--version"], {
7781
+ stdio: "ignore",
7782
+ shell: process.platform === "win32"
7783
+ });
7784
+ return result.status === 0;
7785
+ }
7786
+ function shellQuote2(arg) {
7787
+ return `'${arg.replace(/'/g, `'\\''`)}'`;
7788
+ }
7789
+ function resolveSkillsInstallCommands(baseUrl) {
7790
+ const npxArgs = buildSkillsInstallArgs(baseUrl);
7791
+ const npxInstall = {
7792
+ command: "npx",
7793
+ args: npxArgs,
7794
+ manualCommand: `npx ${npxArgs.map(shellQuote2).join(" ")}`
7795
+ };
7796
+ if (hasCommand("bunx")) {
7797
+ const bunxArgs = buildBunxSkillsInstallArgs(baseUrl);
7798
+ return [
7799
+ {
7800
+ command: "bunx",
7801
+ args: bunxArgs,
7802
+ manualCommand: `bunx ${bunxArgs.map(shellQuote2).join(" ")}`
7803
+ },
7804
+ npxInstall
7805
+ ];
7806
+ }
7807
+ return [npxInstall];
7808
+ }
7809
+ function runOneSkillsInstall(install) {
7206
7810
  return new Promise((resolve8) => {
7207
- const child = (0, import_node_child_process2.spawn)("npx", args, {
7811
+ const child = (0, import_node_child_process2.spawn)(install.command, install.args, {
7208
7812
  stdio: ["ignore", "ignore", "pipe"],
7209
7813
  env: process.env
7210
7814
  });
@@ -7213,37 +7817,63 @@ function runSkillsInstall(baseUrl) {
7213
7817
  stderr += chunk.toString("utf-8");
7214
7818
  });
7215
7819
  child.on("error", (error) => {
7216
- process.stderr.write(`SDK skills sync failed to start: ${error.message}
7217
- `);
7218
- resolve8(false);
7820
+ resolve8({
7821
+ ok: false,
7822
+ detail: `failed to start ${install.command}: ${error.message}`,
7823
+ manualCommand: install.manualCommand
7824
+ });
7219
7825
  });
7220
7826
  child.on("close", (code) => {
7221
7827
  if (code === 0) {
7222
- resolve8(true);
7828
+ resolve8({ ok: true, detail: "", manualCommand: install.manualCommand });
7223
7829
  return;
7224
7830
  }
7225
7831
  const detail = stderr.trim();
7226
- process.stderr.write(
7227
- `SDK skills sync failed${detail ? `: ${detail}` : ""}
7228
- Run manually: npx ${args.map((arg) => arg.includes(" ") ? JSON.stringify(arg) : arg).join(" ")}
7229
- `
7230
- );
7231
- resolve8(false);
7832
+ resolve8({
7833
+ ok: false,
7834
+ detail: detail ? `${install.command}: ${detail}` : `${install.command} exited ${code}`,
7835
+ manualCommand: install.manualCommand
7836
+ });
7232
7837
  });
7233
7838
  });
7234
7839
  }
7840
+ async function runSkillsInstall(baseUrl) {
7841
+ const failures = [];
7842
+ for (const install of resolveSkillsInstallCommands(baseUrl)) {
7843
+ const result = await runOneSkillsInstall(install);
7844
+ if (result.ok) return true;
7845
+ failures.push(result);
7846
+ }
7847
+ const details = failures.map((failure) => failure.detail).filter(Boolean).join("\n");
7848
+ const manualCommand = failures.at(-1)?.manualCommand;
7849
+ process.stderr.write(
7850
+ `SDK skills sync failed${details ? `:
7851
+ ${details}` : ""}
7852
+ ` + (manualCommand ? `Run manually: ${manualCommand}
7853
+ ` : "")
7854
+ );
7855
+ return false;
7856
+ }
7857
+ function writeSdkSkillsStatusLine(line) {
7858
+ const progress = getActiveCliProgress();
7859
+ if (progress) {
7860
+ progress.writeLine(line);
7861
+ return;
7862
+ }
7863
+ process.stderr.write(`${line}
7864
+ `);
7865
+ }
7235
7866
  async function syncSdkSkillsIfNeeded(baseUrl) {
7236
7867
  if (attemptedSync || shouldSkipSkillsSync()) return;
7237
7868
  attemptedSync = true;
7238
7869
  const localVersion = readLocalSkillsVersion(baseUrl);
7239
7870
  const update = await fetchSkillsUpdate(baseUrl, localVersion);
7240
7871
  if (!update?.needsUpdate || !update.remoteVersion) return;
7241
- const progress = getActiveCliProgress();
7242
- progress?.writeLine("SDK skills changed; syncing deepline-sdk skill...") ?? process.stderr.write("SDK skills changed; syncing deepline-sdk skill...\n");
7872
+ writeSdkSkillsStatusLine("SDK skills changed; syncing deepline-sdk skill...");
7243
7873
  const installed = await runSkillsInstall(baseUrl);
7244
7874
  if (!installed) return;
7245
7875
  writeLocalSkillsVersion(baseUrl, update.remoteVersion);
7246
- progress?.writeLine("SDK skills are up to date.") ?? process.stderr.write("SDK skills are up to date.\n");
7876
+ writeSdkSkillsStatusLine("SDK skills are up to date.");
7247
7877
  }
7248
7878
 
7249
7879
  // src/cli/trace.ts
@@ -7405,4 +8035,3 @@ Output:
7405
8035
  process.exit(process.exitCode ?? 0);
7406
8036
  }
7407
8037
  main();
7408
- //# sourceMappingURL=index.js.map