deepline 0.1.12 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +14 -6
  2. package/dist/cli/index.js +1298 -711
  3. package/dist/cli/index.mjs +1294 -707
  4. package/dist/index.d.mts +199 -23
  5. package/dist/index.d.ts +199 -23
  6. package/dist/index.js +219 -13
  7. package/dist/index.mjs +219 -13
  8. package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +68 -12
  9. package/dist/repo/apps/play-runner-workers/src/entry.ts +241 -51
  10. package/dist/repo/sdk/src/client.ts +237 -0
  11. package/dist/repo/sdk/src/config.ts +125 -8
  12. package/dist/repo/sdk/src/http.ts +10 -2
  13. package/dist/repo/sdk/src/play.ts +19 -36
  14. package/dist/repo/sdk/src/plays/bundle-play-file.ts +22 -8
  15. package/dist/repo/sdk/src/plays/local-file-discovery.ts +207 -160
  16. package/dist/repo/sdk/src/types.ts +25 -0
  17. package/dist/repo/sdk/src/version.ts +2 -2
  18. package/dist/repo/shared_libs/play-runtime/tool-result.ts +237 -145
  19. package/dist/repo/shared_libs/plays/bundling/index.ts +206 -229
  20. package/dist/repo/shared_libs/plays/dataset.ts +28 -0
  21. package/package.json +5 -4
  22. package/dist/cli/index.js.map +0 -1
  23. package/dist/cli/index.mjs.map +0 -1
  24. package/dist/index.js.map +0 -1
  25. package/dist/index.mjs.map +0 -1
  26. package/dist/repo/apps/play-runner-workers/src/runtime/README.md +0 -21
  27. package/dist/repo/apps/play-runner-workers/src/runtime/batching.ts +0 -177
  28. package/dist/repo/apps/play-runner-workers/src/runtime/execution-plan.ts +0 -52
  29. package/dist/repo/apps/play-runner-workers/src/runtime/tool-batch.ts +0 -100
  30. package/dist/repo/sdk/src/cli/commands/auth.ts +0 -500
  31. package/dist/repo/sdk/src/cli/commands/billing.ts +0 -188
  32. package/dist/repo/sdk/src/cli/commands/csv.ts +0 -123
  33. package/dist/repo/sdk/src/cli/commands/db.ts +0 -119
  34. package/dist/repo/sdk/src/cli/commands/feedback.ts +0 -40
  35. package/dist/repo/sdk/src/cli/commands/org.ts +0 -117
  36. package/dist/repo/sdk/src/cli/commands/play.ts +0 -3441
  37. package/dist/repo/sdk/src/cli/commands/tools.ts +0 -687
  38. package/dist/repo/sdk/src/cli/dataset-stats.ts +0 -415
  39. package/dist/repo/sdk/src/cli/index.ts +0 -148
  40. package/dist/repo/sdk/src/cli/progress.ts +0 -149
  41. package/dist/repo/sdk/src/cli/skills-sync.ts +0 -141
  42. package/dist/repo/sdk/src/cli/trace.ts +0 -61
  43. package/dist/repo/sdk/src/cli/utils.ts +0 -145
  44. package/dist/repo/sdk/src/compat.ts +0 -77
  45. package/dist/repo/shared_libs/observability/node-tracing.ts +0 -129
  46. package/dist/repo/shared_libs/observability/tracing.ts +0 -98
  47. package/dist/repo/shared_libs/play-runtime/context.ts +0 -4242
  48. package/dist/repo/shared_libs/play-runtime/ctx-contract.ts +0 -250
  49. package/dist/repo/shared_libs/play-runtime/ctx-types.ts +0 -725
  50. package/dist/repo/shared_libs/play-runtime/dataset-id.ts +0 -10
  51. package/dist/repo/shared_libs/play-runtime/db-session-crypto.ts +0 -304
  52. package/dist/repo/shared_libs/play-runtime/db-session.ts +0 -462
  53. package/dist/repo/shared_libs/play-runtime/live-events.ts +0 -214
  54. package/dist/repo/shared_libs/play-runtime/live-state-contract.ts +0 -50
  55. package/dist/repo/shared_libs/play-runtime/map-execution-frame.ts +0 -114
  56. package/dist/repo/shared_libs/play-runtime/map-row-identity.ts +0 -158
  57. package/dist/repo/shared_libs/play-runtime/progress-emitter.ts +0 -172
  58. package/dist/repo/shared_libs/play-runtime/protocol.ts +0 -121
  59. package/dist/repo/shared_libs/play-runtime/public-play-contract.ts +0 -42
  60. package/dist/repo/shared_libs/play-runtime/result-normalization.ts +0 -33
  61. package/dist/repo/shared_libs/play-runtime/runtime-api.ts +0 -1873
  62. package/dist/repo/shared_libs/play-runtime/runtime-constraints.ts +0 -2
  63. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-neon-serverless.ts +0 -201
  64. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-pg.ts +0 -48
  65. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver.ts +0 -84
  66. package/dist/repo/shared_libs/play-runtime/static-pipeline-types.ts +0 -147
  67. package/dist/repo/shared_libs/play-runtime/suspension.ts +0 -68
  68. package/dist/repo/shared_libs/play-runtime/tracing.ts +0 -31
  69. package/dist/repo/shared_libs/play-runtime/waterfall-replay.ts +0 -75
  70. package/dist/repo/shared_libs/play-runtime/worker-api-types.ts +0 -140
  71. package/dist/repo/shared_libs/plays/artifact-transport.ts +0 -14
  72. package/dist/repo/shared_libs/plays/artifact-types.ts +0 -49
  73. package/dist/repo/shared_libs/plays/compiler-manifest.ts +0 -186
  74. package/dist/repo/shared_libs/plays/definition.ts +0 -264
  75. package/dist/repo/shared_libs/plays/file-refs.ts +0 -11
  76. package/dist/repo/shared_libs/plays/rate-limit-scheduler.ts +0 -206
  77. package/dist/repo/shared_libs/plays/resolve-static-pipeline.ts +0 -164
  78. package/dist/repo/shared_libs/plays/runtime-validation.ts +0 -395
  79. package/dist/repo/shared_libs/temporal/constants.ts +0 -39
  80. 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.19";
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";
@@ -386,8 +460,12 @@ var HttpClient = class {
386
460
  * @param path - API path
387
461
  * @param body - Request body (will be JSON-serialized)
388
462
  */
389
- async post(path, body) {
390
- return this.request(path, { method: "POST", body });
463
+ async post(path, body, headers) {
464
+ return this.request(path, {
465
+ method: "POST",
466
+ body,
467
+ headers
468
+ });
391
469
  }
392
470
  /**
393
471
  * Send a DELETE request.
@@ -467,6 +545,7 @@ function sleep(ms) {
467
545
 
468
546
  // src/client.ts
469
547
  var TERMINAL_PLAY_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
548
+ var INCLUDE_TOOL_METADATA_HEADER = "x-deepline-include-tool-metadata";
470
549
  function isRecord(value) {
471
550
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
472
551
  }
@@ -499,6 +578,7 @@ function mapLegacyTemporalStatus(status) {
499
578
  var DeeplineClient = class {
500
579
  http;
501
580
  config;
581
+ runs;
502
582
  /**
503
583
  * @param options - Optional overrides for API key, base URL, timeout, and retries.
504
584
  * @throws {@link ConfigError} if no API key can be resolved from any source.
@@ -506,6 +586,13 @@ var DeeplineClient = class {
506
586
  constructor(options) {
507
587
  this.config = resolveConfig(options);
508
588
  this.http = new HttpClient(this.config);
589
+ this.runs = {
590
+ get: (runId) => this.getRunStatus(runId),
591
+ list: (options2) => this.listRuns(options2),
592
+ tail: (runId, options2) => this.tailRun(runId, options2),
593
+ logs: (runId, options2) => this.getRunLogs(runId, options2),
594
+ stop: (runId, options2) => this.stopRun(runId, options2)
595
+ };
509
596
  }
510
597
  /** The resolved base URL this client is targeting (e.g. `"http://localhost:3000"`). */
511
598
  get baseUrl() {
@@ -594,6 +681,31 @@ var DeeplineClient = class {
594
681
  );
595
682
  return res.tools;
596
683
  }
684
+ /**
685
+ * Search available tools using Deepline's ranked backend search.
686
+ *
687
+ * This is the same discovery surface used by the legacy CLI: it ranks across
688
+ * tool metadata, categories, agent guidance, and input schema fields.
689
+ */
690
+ async searchTools(options = {}) {
691
+ const params = new URLSearchParams();
692
+ const query = options.query?.trim() ?? "";
693
+ params.set("q", query);
694
+ params.set(
695
+ "include_search_debug",
696
+ options.includeSearchDebug ? "true" : "false"
697
+ );
698
+ params.set("search_mode", options.searchMode ?? "v2");
699
+ if (options.categories?.trim()) {
700
+ params.set("categories", options.categories.trim());
701
+ }
702
+ if (options.searchTerms?.trim()) {
703
+ params.set("search_terms", options.searchTerms.trim());
704
+ }
705
+ return this.http.get(
706
+ `/api/v2/integrations/list?${params.toString()}`
707
+ );
708
+ }
597
709
  /**
598
710
  * Get detailed metadata for a single tool.
599
711
  *
@@ -629,12 +741,17 @@ var DeeplineClient = class {
629
741
  * Top-level fields such as `status`, `job_id`, and `billing` describe the
630
742
  * Deepline execution.
631
743
  */
632
- async executeTool(toolId, input) {
744
+ async executeTool(toolId, input, options) {
745
+ const headers = options?.includeToolMetadata ? { [INCLUDE_TOOL_METADATA_HEADER]: "true" } : void 0;
633
746
  return this.http.post(
634
747
  `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
635
- { payload: input }
748
+ { payload: input },
749
+ headers
636
750
  );
637
751
  }
752
+ async executeToolRaw(toolId, input, options) {
753
+ return this.executeTool(toolId, input, options);
754
+ }
638
755
  async queryCustomerDb(input) {
639
756
  return this.http.post("/api/v2/db/query", {
640
757
  sql: input.sql,
@@ -683,6 +800,7 @@ var DeeplineClient = class {
683
800
  ...request.revisionId ? { revisionId: request.revisionId } : {},
684
801
  ...request.artifactStorageKey ? { artifactStorageKey: request.artifactStorageKey } : {},
685
802
  ...request.sourceCode ? { sourceCode: request.sourceCode } : {},
803
+ ...request.sourceFiles ? { sourceFiles: request.sourceFiles } : {},
686
804
  ..."staticPipeline" in request ? { staticPipeline: request.staticPipeline } : {},
687
805
  ...request.artifactHash ? { artifactHash: request.artifactHash } : {},
688
806
  ...request.graphHash ? { graphHash: request.graphHash } : {},
@@ -707,6 +825,7 @@ var DeeplineClient = class {
707
825
  ...request.revisionId ? { revisionId: request.revisionId } : {},
708
826
  ...request.artifactStorageKey ? { artifactStorageKey: request.artifactStorageKey } : {},
709
827
  ...request.sourceCode ? { sourceCode: request.sourceCode } : {},
828
+ ...request.sourceFiles ? { sourceFiles: request.sourceFiles } : {},
710
829
  ..."staticPipeline" in request ? { staticPipeline: request.staticPipeline } : {},
711
830
  ...request.artifactHash ? { artifactHash: request.artifactHash } : {},
712
831
  ...request.graphHash ? { graphHash: request.graphHash } : {},
@@ -743,6 +862,7 @@ var DeeplineClient = class {
743
862
  const compilerManifest = input.compilerManifest ?? await this.compilePlayManifest({
744
863
  name: input.name,
745
864
  sourceCode: input.sourceCode,
865
+ sourceFiles: input.sourceFiles,
746
866
  artifact: input.artifact
747
867
  });
748
868
  return this.http.post("/api/v2/plays/artifacts", {
@@ -757,6 +877,7 @@ var DeeplineClient = class {
757
877
  compilerManifest: artifact.compilerManifest ?? await this.compilePlayManifest({
758
878
  name: artifact.name,
759
879
  sourceCode: artifact.sourceCode,
880
+ sourceFiles: artifact.sourceFiles,
760
881
  artifact: artifact.artifact
761
882
  })
762
883
  }))
@@ -783,11 +904,13 @@ var DeeplineClient = class {
783
904
  const compilerManifest = input.compilerManifest ?? await this.compilePlayManifest({
784
905
  name: input.name,
785
906
  sourceCode: input.sourceCode,
907
+ sourceFiles: input.sourceFiles,
786
908
  artifact: input.artifact
787
909
  });
788
910
  const registeredArtifact = await this.registerPlayArtifact({
789
911
  name: input.name,
790
912
  sourceCode: input.sourceCode,
913
+ sourceFiles: input.sourceFiles,
791
914
  artifact: input.artifact,
792
915
  compilerManifest,
793
916
  publish: false
@@ -847,11 +970,13 @@ var DeeplineClient = class {
847
970
  const compilerManifest = options?.compilerManifest ?? await this.compilePlayManifest({
848
971
  name,
849
972
  sourceCode,
973
+ sourceFiles: options?.sourceFiles,
850
974
  artifact
851
975
  });
852
976
  const registeredArtifact = await this.registerPlayArtifact({
853
977
  name,
854
978
  sourceCode,
979
+ sourceFiles: options?.sourceFiles,
855
980
  artifact,
856
981
  compilerManifest,
857
982
  publish: false
@@ -1035,6 +1160,112 @@ var DeeplineClient = class {
1035
1160
  );
1036
1161
  return response.runs ?? [];
1037
1162
  }
1163
+ /**
1164
+ * Get a run by id using the public runs resource model.
1165
+ *
1166
+ * This is the SDK equivalent of:
1167
+ *
1168
+ * ```bash
1169
+ * deepline runs get <run-id> --json
1170
+ * ```
1171
+ */
1172
+ async getRunStatus(runId) {
1173
+ const response = await this.http.get(
1174
+ `/api/v2/runs/${encodeURIComponent(runId)}`
1175
+ );
1176
+ return normalizePlayStatus(response);
1177
+ }
1178
+ /**
1179
+ * List play runs using the public runs resource model.
1180
+ *
1181
+ * This is the SDK equivalent of:
1182
+ *
1183
+ * ```bash
1184
+ * deepline runs list --play <play-name> --status failed --json
1185
+ * ```
1186
+ */
1187
+ async listRuns(options) {
1188
+ const playName = options.play.trim();
1189
+ if (!playName) {
1190
+ throw new Error("runs.list requires options.play.");
1191
+ }
1192
+ const params = new URLSearchParams({ play: playName });
1193
+ const status = options.status?.trim();
1194
+ if (status) {
1195
+ params.set("status", status);
1196
+ }
1197
+ const response = await this.http.get(
1198
+ `/api/v2/runs?${params.toString()}`
1199
+ );
1200
+ return response.runs ?? [];
1201
+ }
1202
+ /**
1203
+ * Fetch the lightweight tail status for a run using the public runs resource model.
1204
+ *
1205
+ * This is the SDK equivalent of:
1206
+ *
1207
+ * ```bash
1208
+ * deepline runs tail <run-id> --json
1209
+ * ```
1210
+ */
1211
+ async tailRun(runId, options) {
1212
+ 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;
1213
+ const params = new URLSearchParams();
1214
+ if (Number.isFinite(afterLogIndex)) {
1215
+ params.set("afterLogIndex", String(Number(afterLogIndex)));
1216
+ }
1217
+ if (typeof options?.waitMs === "number") {
1218
+ params.set("waitMs", String(options.waitMs));
1219
+ }
1220
+ if (options?.terminalOnly) {
1221
+ params.set("terminalOnly", "true");
1222
+ }
1223
+ const suffix = params.toString() ? `?${params.toString()}` : "";
1224
+ const response = await this.http.get(
1225
+ `/api/v2/runs/${encodeURIComponent(runId)}/tail${suffix}`
1226
+ );
1227
+ return normalizePlayStatus(response);
1228
+ }
1229
+ /**
1230
+ * Fetch persisted logs for a run using the public runs resource model.
1231
+ *
1232
+ * This is the SDK equivalent of:
1233
+ *
1234
+ * ```bash
1235
+ * deepline runs logs <run-id> --limit 200 --json
1236
+ * ```
1237
+ */
1238
+ async getRunLogs(runId, options) {
1239
+ const status = await this.getRunStatus(runId);
1240
+ const logs = status.progress?.logs ?? [];
1241
+ const limit = typeof options?.limit === "number" && Number.isFinite(options.limit) ? Math.max(0, Math.trunc(options.limit)) : 200;
1242
+ const entries = logs.slice(Math.max(0, logs.length - limit));
1243
+ return {
1244
+ runId: status.runId,
1245
+ totalCount: logs.length,
1246
+ returnedCount: entries.length,
1247
+ firstSequence: logs.length === 0 ? null : logs.length - entries.length + 1,
1248
+ lastSequence: logs.length === 0 ? null : logs.length,
1249
+ truncated: logs.length > entries.length,
1250
+ hasMore: logs.length > entries.length,
1251
+ entries
1252
+ };
1253
+ }
1254
+ /**
1255
+ * Stop a run by id using the public runs resource model.
1256
+ *
1257
+ * This is the SDK equivalent of:
1258
+ *
1259
+ * ```bash
1260
+ * deepline runs stop <run-id> --reason "stale lock" --json
1261
+ * ```
1262
+ */
1263
+ async stopRun(runId, options) {
1264
+ return this.http.post(
1265
+ `/api/v2/runs/${encodeURIComponent(runId)}/stop`,
1266
+ options?.reason ? { reason: options.reason } : {}
1267
+ );
1268
+ }
1038
1269
  async listPlays() {
1039
1270
  const response = await this.http.get(
1040
1271
  "/api/v2/plays"
@@ -1421,6 +1652,7 @@ function saveEnvValues(values, baseUrl) {
1421
1652
  const merged = { ...existing, ...values };
1422
1653
  const lines = Object.entries(merged).filter(([, v]) => v !== "").map(([k, v]) => `${k}=${v}`);
1423
1654
  (0, import_node_fs3.writeFileSync)(filePath, lines.join("\n") + "\n", "utf-8");
1655
+ saveProjectDeeplineEnvValues(baseUrl, values);
1424
1656
  }
1425
1657
  async function httpJson(method, url, apiKey, body) {
1426
1658
  const headers = { "Content-Type": "application/json" };
@@ -2021,6 +2253,9 @@ function rowArray(value) {
2021
2253
  function readNumber(value) {
2022
2254
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : null;
2023
2255
  }
2256
+ function numericStat(value) {
2257
+ return readNumber(value) ?? 0;
2258
+ }
2024
2259
  function inferColumns(rows) {
2025
2260
  const columns = [];
2026
2261
  const seen = /* @__PURE__ */ new Set();
@@ -2100,6 +2335,31 @@ function extractCanonicalRowsInfo(statusOrResult) {
2100
2335
  function percentText(numerator, denominator) {
2101
2336
  return denominator > 0 ? `${numerator}/${denominator} (${Math.round(100 * numerator / denominator)}%)` : "0/0 (0%)";
2102
2337
  }
2338
+ function isDatasetExecutionStatsInput(value) {
2339
+ return isRecord2(value) && isRecord2(value.columnStats) && Object.values(value.columnStats).every(isRecord2);
2340
+ }
2341
+ function extractDatasetExecutionStats(statusOrResult) {
2342
+ if (!isRecord2(statusOrResult)) {
2343
+ return null;
2344
+ }
2345
+ const direct = statusOrResult.dataset_execution_stats;
2346
+ if (isDatasetExecutionStatsInput(direct)) {
2347
+ return direct;
2348
+ }
2349
+ const nested = isRecord2(statusOrResult.result) ? statusOrResult.result.dataset_execution_stats : null;
2350
+ return isDatasetExecutionStatsInput(nested) ? nested : null;
2351
+ }
2352
+ function formatExecutionStats(raw, denominator) {
2353
+ return {
2354
+ queued: percentText(numericStat(raw.queued), denominator),
2355
+ running: percentText(numericStat(raw.running), denominator),
2356
+ "completed:executed": percentText(numericStat(raw.completed), denominator),
2357
+ "completed:reused": percentText(numericStat(raw.cached), denominator),
2358
+ "skipped:condition": percentText(numericStat(raw.skipped), denominator),
2359
+ "skipped:missed": percentText(numericStat(raw.missed), denominator),
2360
+ failed: percentText(numericStat(raw.failed), denominator)
2361
+ };
2362
+ }
2103
2363
  function countPercentText(count, denominator) {
2104
2364
  return denominator > 0 ? `${count} (${Math.round(100 * count / denominator)}%)` : "0 (0%)";
2105
2365
  }
@@ -2192,7 +2452,7 @@ function compactCell(value) {
2192
2452
  }
2193
2453
  return compactScalar(parsed, 120);
2194
2454
  }
2195
- function buildDatasetStats(rows, totalRows = rows.length, columns = inferColumns(rows)) {
2455
+ function buildDatasetStats(rows, totalRows = rows.length, columns = inferColumns(rows), executionStats) {
2196
2456
  const sanitized = sanitizeCsvProjectionInfo({ rows, columns });
2197
2457
  const columnStats = {};
2198
2458
  for (const column of sanitized.columns) {
@@ -2220,6 +2480,10 @@ function buildDatasetStats(rows, totalRows = rows.length, columns = inferColumns
2220
2480
  non_empty: percentText(nonEmpty, denominator),
2221
2481
  unique: valueCounts.size
2222
2482
  };
2483
+ const rawExecutionStats = executionStats?.columnStats[column];
2484
+ if (rawExecutionStats) {
2485
+ stat3.execution = formatExecutionStats(rawExecutionStats, totalRows);
2486
+ }
2223
2487
  if (sampleValue !== void 0 && sampleValueType) {
2224
2488
  stat3.sample_value = sampleValue;
2225
2489
  stat3.sample_type = sampleValueType;
@@ -2566,7 +2830,6 @@ var import_node_os4 = require("os");
2566
2830
  var import_node_path5 = require("path");
2567
2831
  var import_node_module = require("module");
2568
2832
  var import_esbuild = require("esbuild");
2569
- var import_typescript = __toESM(require("typescript"));
2570
2833
 
2571
2834
  // ../shared_libs/play-runtime/backend.ts
2572
2835
  var PLAY_RUNTIME_BACKENDS = {
@@ -2651,56 +2914,6 @@ function formatEsbuildMessage(message) {
2651
2914
  const location = message.location ? `${message.location.file}:${message.location.line}:${message.location.column}` : null;
2652
2915
  return location ? `${location} ${message.text}` : message.text;
2653
2916
  }
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
2917
  function isLocalSpecifier(specifier) {
2705
2918
  return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/") || specifier.startsWith("file:");
2706
2919
  }
@@ -2724,11 +2937,8 @@ function assertWithinPlayWorkspace(input) {
2724
2937
  if (isPathInsideDirectory(input.resolvedPath, input.workspace.rootDir)) {
2725
2938
  return;
2726
2939
  }
2727
- const position = input.sourceFile.getLineAndCharacterOfPosition(
2728
- input.node.getStart(input.sourceFile)
2729
- );
2730
2940
  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.`
2941
+ `${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
2942
  );
2733
2943
  }
2734
2944
  function getPackageName(specifier) {
@@ -2738,72 +2948,135 @@ function getPackageName(specifier) {
2738
2948
  }
2739
2949
  return specifier.split("/")[0] ?? specifier;
2740
2950
  }
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
2951
  function isPlaySourceFile(filePath) {
2759
2952
  return PLAY_SOURCE_FILE_PATTERN.test(filePath);
2760
2953
  }
2761
- function extractStringLiteralProperty(objectLiteral, propertyName) {
2762
- for (const property of objectLiteral.properties) {
2763
- if (!import_typescript.default.isPropertyAssignment(property)) {
2954
+ function stripCommentsToSpaces(source) {
2955
+ return source.replace(/\/\*[\s\S]*?\*\//g, (match) => match.replace(/[^\n]/g, " ")).replace(
2956
+ /(^|[^:])\/\/.*$/gm,
2957
+ (match, prefix) => prefix + " ".repeat(Math.max(0, match.length - prefix.length))
2958
+ );
2959
+ }
2960
+ function lineAndColumnAt(source, index) {
2961
+ const prefix = source.slice(0, index);
2962
+ const lines = prefix.split("\n");
2963
+ return { line: lines.length, column: lines[lines.length - 1].length + 1 };
2964
+ }
2965
+ function findSourceImportReferences(sourceCode) {
2966
+ const source = stripCommentsToSpaces(sourceCode);
2967
+ const references = [];
2968
+ const addReference = (specifier, specifierIndex, kind) => {
2969
+ if (!specifier) return;
2970
+ const position = lineAndColumnAt(sourceCode, specifierIndex);
2971
+ references.push({
2972
+ specifier,
2973
+ line: position.line,
2974
+ column: position.column,
2975
+ kind
2976
+ });
2977
+ };
2978
+ const staticImportPattern = /\b(?:import|export)\s+(?!type\b)(?:[\s\S]*?\s+from\s*)?(['"])([^'"\n]+)\1/g;
2979
+ for (const match of source.matchAll(staticImportPattern)) {
2980
+ addReference(match[2], match.index + match[0].lastIndexOf(match[1]), "static");
2981
+ }
2982
+ const dynamicImportPattern = /\bimport\s*\(\s*(['"])([^'"\n]+)\1/g;
2983
+ for (const match of source.matchAll(dynamicImportPattern)) {
2984
+ addReference(match[2], match.index + match[0].lastIndexOf(match[1]), "dynamic-import");
2985
+ }
2986
+ const requirePattern = /\brequire\s*\(\s*(['"])([^'"\n]+)\1/g;
2987
+ for (const match of source.matchAll(requirePattern)) {
2988
+ addReference(match[2], match.index + match[0].lastIndexOf(match[1]), "require");
2989
+ }
2990
+ const literalDynamicImportIndexes = new Set(
2991
+ [...source.matchAll(dynamicImportPattern)].map((match) => match.index)
2992
+ );
2993
+ for (const match of source.matchAll(/\bimport\s*\(/g)) {
2994
+ if (literalDynamicImportIndexes.has(match.index)) continue;
2995
+ const position = lineAndColumnAt(sourceCode, match.index);
2996
+ throw new Error(
2997
+ `:${position.line}:${position.column} Dynamic import() is not allowed in plays. Use static imports instead.`
2998
+ );
2999
+ }
3000
+ const literalRequireIndexes = new Set(
3001
+ [...source.matchAll(requirePattern)].map((match) => match.index)
3002
+ );
3003
+ for (const match of source.matchAll(/\brequire\s*\(/g)) {
3004
+ if (literalRequireIndexes.has(match.index)) continue;
3005
+ const position = lineAndColumnAt(sourceCode, match.index);
3006
+ throw new Error(
3007
+ `:${position.line}:${position.column} Dynamic require() is not allowed in plays. Use static imports or require("literal") only.`
3008
+ );
3009
+ }
3010
+ return references.sort(
3011
+ (left, right) => left.line === right.line ? left.column - right.column : left.line - right.line
3012
+ );
3013
+ }
3014
+ function unquoteStringLiteral(literal) {
3015
+ const trimmed = literal.trim();
3016
+ const quote = trimmed[0];
3017
+ if (quote !== '"' && quote !== "'" || trimmed[trimmed.length - 1] !== quote) {
3018
+ return null;
3019
+ }
3020
+ try {
3021
+ return JSON.parse(quote === '"' ? trimmed : `"${trimmed.slice(1, -1).replace(/"/g, '\\"')}"`);
3022
+ } catch {
3023
+ return trimmed.slice(1, -1);
3024
+ }
3025
+ }
3026
+ function findMatchingBrace(source, openIndex) {
3027
+ let depth = 0;
3028
+ let quote = null;
3029
+ let escaped = false;
3030
+ for (let index = openIndex; index < source.length; index += 1) {
3031
+ const char = source[index];
3032
+ if (quote) {
3033
+ if (escaped) {
3034
+ escaped = false;
3035
+ } else if (char === "\\") {
3036
+ escaped = true;
3037
+ } else if (char === quote) {
3038
+ quote = null;
3039
+ }
2764
3040
  continue;
2765
3041
  }
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) {
3042
+ if (char === '"' || char === "'" || char === "`") {
3043
+ quote = char;
2769
3044
  continue;
2770
3045
  }
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
- }
3046
+ if (char === "{") depth += 1;
3047
+ if (char === "}") {
3048
+ depth -= 1;
3049
+ if (depth === 0) return index;
3050
+ }
3051
+ }
3052
+ return -1;
3053
+ }
3054
+ function extractDefinedPlayName(sourceCode, _filePath) {
3055
+ const source = stripCommentsToSpaces(sourceCode);
3056
+ const callPattern = /(?:\b[A-Za-z_$][\w$]*\s*\.\s*)?\b(?:definePlay|defineWorkflow)\s*\(/g;
3057
+ for (const match of source.matchAll(callPattern)) {
3058
+ const openParen = match.index + match[0].length - 1;
3059
+ const firstArgStart = openParen + 1;
3060
+ const firstNonSpace = source.slice(firstArgStart).search(/\S/);
3061
+ if (firstNonSpace < 0) continue;
3062
+ const argIndex = firstArgStart + firstNonSpace;
3063
+ const quote = source[argIndex];
3064
+ if (quote === '"' || quote === "'") {
3065
+ const literalMatch = source.slice(argIndex).match(/^(['"])(?:\\.|(?!\1)[\s\S])*\1/);
3066
+ const value = literalMatch ? unquoteStringLiteral(literalMatch[0]) : null;
3067
+ if (value?.trim()) return value.trim();
3068
+ }
3069
+ if (quote === "{") {
3070
+ const closeBrace = findMatchingBrace(source, argIndex);
3071
+ if (closeBrace < 0) continue;
3072
+ const objectSource = source.slice(argIndex + 1, closeBrace);
3073
+ const idMatch = objectSource.match(/(?:^|[,{\s])(?:id|['"]id['"])\s*:\s*(['"])([\s\S]*?)\1/);
3074
+ if (idMatch?.[2]?.trim()) {
3075
+ return idMatch[2].trim();
2801
3076
  }
2802
3077
  }
2803
- import_typescript.default.forEachChild(node, visit);
2804
- };
2805
- visit(sourceFile);
2806
- return detectedPlayName;
3078
+ }
3079
+ return null;
2807
3080
  }
2808
3081
  function getPackageRequireCandidates(fromFile) {
2809
3082
  const candidates = [
@@ -3113,18 +3386,10 @@ async function analyzeSourceGraph(entryFile, adapter) {
3113
3386
  if ((0, import_node_path5.extname)(absolutePath).toLowerCase() === ".json") {
3114
3387
  return;
3115
3388
  }
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) => {
3389
+ const handleSpecifier = async (specifier, line, column, kind) => {
3124
3390
  if (kind === "dynamic-import") {
3125
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3126
3391
  throw new Error(
3127
- `${absolutePath}:${position.line + 1}:${position.character + 1} Dynamic import() is not allowed in plays. Use static imports instead.`
3392
+ `${absolutePath}:${line}:${column} Dynamic import() is not allowed in plays. Use static imports instead.`
3128
3393
  );
3129
3394
  }
3130
3395
  if (NODE_BUILTIN_SET.has(specifier)) {
@@ -3138,16 +3403,15 @@ async function analyzeSourceGraph(entryFile, adapter) {
3138
3403
  specifier,
3139
3404
  resolvedPath: resolved,
3140
3405
  workspace,
3141
- sourceFile,
3142
- node
3406
+ line,
3407
+ column
3143
3408
  });
3144
3409
  if (resolved !== absoluteEntryFile && isPlaySourceFile(resolved)) {
3145
3410
  const importedSource = await (0, import_promises2.readFile)(resolved, "utf-8");
3146
3411
  const importedPlayName = extractDefinedPlayName(importedSource, resolved);
3147
3412
  if (!importedPlayName) {
3148
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3149
3413
  throw new Error(
3150
- `${absolutePath}:${position.line + 1}:${position.character + 1} Imported play file "${specifier}" must export definePlay(...) so it can be runtime-composed.`
3414
+ `${absolutePath}:${line}:${column} Imported play file "${specifier}" must export definePlay(...) so it can be runtime-composed.`
3151
3415
  );
3152
3416
  }
3153
3417
  importedPlayDependencies.set(resolved, {
@@ -3160,44 +3424,28 @@ async function analyzeSourceGraph(entryFile, adapter) {
3160
3424
  return;
3161
3425
  }
3162
3426
  if (specifier.includes(":")) {
3163
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3164
3427
  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.`
3428
+ `${absolutePath}:${line}:${column} Unsupported import specifier "${specifier}". Allowed imports are relative files, Node builtins, and installed packages.`
3166
3429
  );
3167
3430
  }
3168
3431
  const packageImport = resolvePackageImport(specifier, absolutePath, adapter);
3169
3432
  packages.set(packageImport.name, packageImport.version);
3170
3433
  };
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
- }
3434
+ try {
3435
+ for (const reference of findSourceImportReferences(sourceCode2)) {
3436
+ await handleSpecifier(
3437
+ reference.specifier,
3438
+ reference.line,
3439
+ reference.column,
3440
+ reference.kind
3441
+ );
3195
3442
  }
3196
- for (const child of node.getChildren(sourceFile)) {
3197
- await walk(child);
3443
+ } catch (error) {
3444
+ if (error instanceof Error && error.message.startsWith(":")) {
3445
+ throw new Error(`${absolutePath}${error.message}`);
3198
3446
  }
3199
- };
3200
- await walk(sourceFile);
3447
+ throw error;
3448
+ }
3201
3449
  };
3202
3450
  await visitFile(absoluteEntryFile);
3203
3451
  const sourceCode = localFiles.get(absoluteEntryFile) ?? "";
@@ -3217,6 +3465,11 @@ async function analyzeSourceGraph(entryFile, adapter) {
3217
3465
  const playName = extractDefinedPlayName(sourceCode, absoluteEntryFile);
3218
3466
  return {
3219
3467
  sourceCode,
3468
+ sourceFiles: Object.fromEntries(
3469
+ [...localFiles.entries()].sort(
3470
+ (left, right) => left[0].localeCompare(right[0])
3471
+ )
3472
+ ),
3220
3473
  sourceHash,
3221
3474
  graphHash,
3222
3475
  importPolicy: {
@@ -3280,8 +3533,7 @@ function normalizeSourceMapForRuntime(sourceMapText) {
3280
3533
  parsed.sourceRoot = void 0;
3281
3534
  return JSON.stringify(parsed);
3282
3535
  }
3283
- function getBundleSizeError(filePath, bundledCode, artifactKind) {
3284
- const bundleBytes = Buffer.byteLength(bundledCode, "utf8");
3536
+ function getBundleSizeErrorForBytes(filePath, bundleBytes, artifactKind) {
3285
3537
  if (bundleBytes > MAX_PLAY_BUNDLE_BYTES) {
3286
3538
  return `${filePath} Play bundle exceeds the 30 MiB limit (${bundleBytes} bytes > ${MAX_PLAY_BUNDLE_BYTES} bytes).`;
3287
3539
  }
@@ -3292,6 +3544,13 @@ function getBundleSizeError(filePath, bundledCode, artifactKind) {
3292
3544
  }
3293
3545
  return null;
3294
3546
  }
3547
+ function getBundleSizeError(filePath, bundledCode, artifactKind) {
3548
+ return getBundleSizeErrorForBytes(
3549
+ filePath,
3550
+ Buffer.byteLength(bundledCode, "utf8"),
3551
+ artifactKind
3552
+ );
3553
+ }
3295
3554
  async function runEsbuildForCjsNode(entryFile, importedPlayDependencies, adapter, exportName) {
3296
3555
  const sdkAliasPlugin = localSdkAliasPlugin(adapter);
3297
3556
  const playProxyPlugin = importedPlayProxyPlugin(importedPlayDependencies);
@@ -3424,6 +3683,23 @@ entry-export:${exportName}`
3424
3683
  workers-harness:${harnessFingerprint}`
3425
3684
  );
3426
3685
  }
3686
+ const typecheckErrors = [
3687
+ ...await adapter.typecheckPlaySource?.({
3688
+ sourceCode: analysis.sourceCode,
3689
+ sourcePath: absolutePath,
3690
+ importedFilePaths: [
3691
+ ...analysis.importPolicy.localFiles,
3692
+ ...analysis.importedPlayDependencies.map((dependency) => dependency.filePath)
3693
+ ]
3694
+ }) ?? []
3695
+ ];
3696
+ if (typecheckErrors.length > 0) {
3697
+ return {
3698
+ success: false,
3699
+ filePath: absolutePath,
3700
+ errors: typecheckErrors
3701
+ };
3702
+ }
3427
3703
  const cachedArtifact = await readArtifactCache(analysis.graphHash, target, adapter);
3428
3704
  const discoveredFiles = await adapter.discoverPackagedLocalFiles(absolutePath);
3429
3705
  if (cachedArtifact) {
@@ -3443,6 +3719,7 @@ workers-harness:${harnessFingerprint}`
3443
3719
  success: true,
3444
3720
  artifact: { ...cachedArtifact, cacheHit: true },
3445
3721
  sourceCode: analysis.sourceCode,
3722
+ sourceFiles: analysis.sourceFiles,
3446
3723
  filePath: absolutePath,
3447
3724
  playName: analysis.playName,
3448
3725
  packagedFiles: discoveredFiles.files,
@@ -3450,24 +3727,6 @@ workers-harness:${harnessFingerprint}`
3450
3727
  importedPlayDependencies: analysis.importedPlayDependencies
3451
3728
  };
3452
3729
  }
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
3730
  const buildOutcome = target === PLAY_ARTIFACT_KINDS.esmWorkers ? await runEsbuildForEsmWorkers(absolutePath, analysis.importedPlayDependencies, adapter, exportName) : await runEsbuildForCjsNode(absolutePath, analysis.importedPlayDependencies, adapter, exportName);
3472
3731
  if (Array.isArray(buildOutcome)) {
3473
3732
  return {
@@ -3517,6 +3776,7 @@ workers-harness:${harnessFingerprint}`
3517
3776
  success: true,
3518
3777
  artifact,
3519
3778
  sourceCode: analysis.sourceCode,
3779
+ sourceFiles: analysis.sourceFiles,
3520
3780
  filePath: absolutePath,
3521
3781
  playName: analysis.playName,
3522
3782
  packagedFiles: discoveredFiles.files,
@@ -3598,7 +3858,6 @@ function resolveExecutionProfile(override) {
3598
3858
  var import_node_crypto2 = require("crypto");
3599
3859
  var import_promises3 = require("fs/promises");
3600
3860
  var import_node_path6 = require("path");
3601
- var import_typescript2 = __toESM(require("typescript"));
3602
3861
  var SOURCE_EXTENSIONS2 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".json"];
3603
3862
  function sha2562(buffer) {
3604
3863
  return (0, import_node_crypto2.createHash)("sha256").update(buffer).digest("hex");
@@ -3610,94 +3869,181 @@ function contentTypeForFile(filePath) {
3610
3869
  if (extension === ".txt") return "text/plain";
3611
3870
  return "application/octet-stream";
3612
3871
  }
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));
3872
+ function stripCommentsToSpaces2(source) {
3873
+ return source.replace(/\/\*[\s\S]*?\*\//g, (match) => match.replace(/[^\n]/g, " ")).replace(
3874
+ /(^|[^:])\/\/.*$/gm,
3875
+ (match, prefix) => prefix + " ".repeat(Math.max(0, match.length - prefix.length))
3876
+ );
3628
3877
  }
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);
3878
+ function unquoteStringLiteral2(literal) {
3879
+ const trimmed = literal.trim();
3880
+ const quote = trimmed[0];
3881
+ if (quote !== '"' && quote !== "'" || trimmed[trimmed.length - 1] !== quote) {
3882
+ return null;
3641
3883
  }
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);
3884
+ try {
3885
+ return JSON.parse(quote === '"' ? trimmed : `"${trimmed.slice(1, -1).replace(/"/g, '\\"')}"`);
3886
+ } catch {
3887
+ return trimmed.slice(1, -1);
3644
3888
  }
3645
- if (import_typescript2.default.isConditionalExpression(node)) {
3646
- return isRuntimeInputExpression(node.condition) || isRuntimeInputExpression(node.whenTrue) || isRuntimeInputExpression(node.whenFalse);
3889
+ }
3890
+ function splitTopLevelPlus(expression) {
3891
+ const parts = [];
3892
+ let start = 0;
3893
+ let depth = 0;
3894
+ let quote = null;
3895
+ let escaped = false;
3896
+ for (let index = 0; index < expression.length; index += 1) {
3897
+ const char = expression[index];
3898
+ if (quote) {
3899
+ if (escaped) {
3900
+ escaped = false;
3901
+ } else if (char === "\\") {
3902
+ escaped = true;
3903
+ } else if (char === quote) {
3904
+ quote = null;
3905
+ }
3906
+ continue;
3907
+ }
3908
+ if (char === '"' || char === "'" || char === "`") {
3909
+ quote = char;
3910
+ continue;
3911
+ }
3912
+ if (char === "(" || char === "[" || char === "{") depth += 1;
3913
+ if (char === ")" || char === "]" || char === "}") depth -= 1;
3914
+ if (char === "+" && depth === 0) {
3915
+ parts.push(expression.slice(start, index));
3916
+ start = index + 1;
3917
+ }
3647
3918
  }
3648
- return referencesInputIdentifier(node);
3919
+ if (parts.length === 0) return null;
3920
+ parts.push(expression.slice(start));
3921
+ return parts;
3649
3922
  }
3650
- function resolveStringExpression(node, constants) {
3651
- if (import_typescript2.default.isStringLiteralLike(node) || import_typescript2.default.isNoSubstitutionTemplateLiteral(node)) {
3652
- return node.text;
3923
+ function stripOuterParens(expression) {
3924
+ let value = expression.trim();
3925
+ while (value.startsWith("(") && value.endsWith(")")) {
3926
+ value = value.slice(1, -1).trim();
3653
3927
  }
3654
- if (import_typescript2.default.isParenthesizedExpression(node)) {
3655
- return resolveStringExpression(node.expression, constants);
3928
+ return value;
3929
+ }
3930
+ function isRuntimeInputExpression(expression) {
3931
+ return /(^|[^\w$])input([^\w$]|$)/.test(expression);
3932
+ }
3933
+ function resolveStringExpression(expression, constants) {
3934
+ const value = stripOuterParens(expression);
3935
+ if (/^(['"])(?:\\.|(?!\1)[\s\S])*\1$/.test(value)) {
3936
+ return unquoteStringLiteral2(value);
3656
3937
  }
3657
- if (import_typescript2.default.isIdentifier(node)) {
3658
- return constants.get(node.text) ?? null;
3938
+ if (/^`(?:\\.|[^`$]|\$(?!\{))*`$/.test(value)) {
3939
+ return value.slice(1, -1);
3659
3940
  }
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;
3941
+ if (/^[A-Za-z_$][\w$]*$/.test(value)) {
3942
+ return constants.get(value) ?? null;
3670
3943
  }
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;
3944
+ const parts = splitTopLevelPlus(value);
3945
+ if (parts) {
3946
+ const resolved = parts.map((part) => resolveStringExpression(part, constants));
3947
+ return resolved.every((part) => part != null) ? resolved.join("") : null;
3678
3948
  }
3679
3949
  return null;
3680
3950
  }
3681
- function collectTopLevelStringConstants(sourceFile) {
3951
+ function collectTopLevelStringConstants(sourceCode) {
3682
3952
  const constants = /* @__PURE__ */ new Map();
3683
- for (const statement of sourceFile.statements) {
3684
- if (!import_typescript2.default.isVariableStatement(statement)) {
3953
+ const source = stripCommentsToSpaces2(sourceCode);
3954
+ for (const match of source.matchAll(/(?:^|\n)\s*const\s+([A-Za-z_$][\w$]*)\s*=\s*([^;\n]+)/g)) {
3955
+ const resolved = resolveStringExpression(match[2], constants);
3956
+ if (resolved != null) {
3957
+ constants.set(match[1], resolved);
3958
+ }
3959
+ }
3960
+ return constants;
3961
+ }
3962
+ function findMatchingGenericEnd(source, openIndex) {
3963
+ let depth = 0;
3964
+ let quote = null;
3965
+ let escaped = false;
3966
+ for (let index = openIndex; index < source.length; index += 1) {
3967
+ const char = source[index];
3968
+ if (quote) {
3969
+ if (escaped) {
3970
+ escaped = false;
3971
+ } else if (char === "\\") {
3972
+ escaped = true;
3973
+ } else if (char === quote) {
3974
+ quote = null;
3975
+ }
3685
3976
  continue;
3686
3977
  }
3687
- if (!(statement.declarationList.flags & import_typescript2.default.NodeFlags.Const)) {
3978
+ if (char === '"' || char === "'" || char === "`") {
3979
+ quote = char;
3688
3980
  continue;
3689
3981
  }
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);
3982
+ if (char === "<") depth += 1;
3983
+ if (char === ">") {
3984
+ depth -= 1;
3985
+ if (depth === 0) return index;
3986
+ }
3987
+ }
3988
+ return -1;
3989
+ }
3990
+ function findCallOpenParen(source, afterCsvIndex) {
3991
+ let index = afterCsvIndex;
3992
+ while (/\s/.test(source[index] ?? "")) index += 1;
3993
+ if (source[index] === "<") {
3994
+ const genericEnd = findMatchingGenericEnd(source, index);
3995
+ if (genericEnd < 0) return -1;
3996
+ index = genericEnd + 1;
3997
+ while (/\s/.test(source[index] ?? "")) index += 1;
3998
+ }
3999
+ return source[index] === "(" ? index : -1;
4000
+ }
4001
+ function firstCallArgument(source, openParen) {
4002
+ let depth = 0;
4003
+ let quote = null;
4004
+ let escaped = false;
4005
+ const start = openParen + 1;
4006
+ for (let index = start; index < source.length; index += 1) {
4007
+ const char = source[index];
4008
+ if (quote) {
4009
+ if (escaped) {
4010
+ escaped = false;
4011
+ } else if (char === "\\") {
4012
+ escaped = true;
4013
+ } else if (char === quote) {
4014
+ quote = null;
3697
4015
  }
4016
+ continue;
4017
+ }
4018
+ if (char === '"' || char === "'" || char === "`") {
4019
+ quote = char;
4020
+ continue;
4021
+ }
4022
+ if (char === "(" || char === "[" || char === "{") depth += 1;
4023
+ if (char === ")" && depth === 0) {
4024
+ const text = source.slice(start, index).trim();
4025
+ return text ? { text, start, end: index } : null;
3698
4026
  }
4027
+ if (char === "," && depth === 0) {
4028
+ const text = source.slice(start, index).trim();
4029
+ return text ? { text, start, end: index } : null;
4030
+ }
4031
+ if (char === ")" || char === "]" || char === "}") depth -= 1;
3699
4032
  }
3700
- return constants;
4033
+ return null;
4034
+ }
4035
+ function localImportSpecifiers(sourceCode) {
4036
+ const source = stripCommentsToSpaces2(sourceCode);
4037
+ const specifiers = [];
4038
+ for (const match of source.matchAll(
4039
+ /\b(?:import|export)\s+(?!type\b)(?:[\s\S]*?\s+from\s*)?['"]([^'"]+)['"]/g
4040
+ )) {
4041
+ if (match[1]?.startsWith(".")) specifiers.push(match[1]);
4042
+ }
4043
+ for (const match of source.matchAll(/\brequire\s*\(\s*(['"])(\.[^'"]*)\1\s*\)/g)) {
4044
+ specifiers.push(match[2]);
4045
+ }
4046
+ return specifiers;
3701
4047
  }
3702
4048
  async function fileExists2(filePath) {
3703
4049
  try {
@@ -3742,69 +4088,60 @@ async function discoverPackagedLocalFiles(entryFile) {
3742
4088
  }
3743
4089
  visitedFiles.add(absolutePath);
3744
4090
  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);
4091
+ const scanSource = stripCommentsToSpaces2(sourceCode);
4092
+ const constants = collectTopLevelStringConstants(sourceCode);
3753
4093
  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) {
4094
+ for (const match of scanSource.matchAll(/\b([A-Za-z_$][\w$]*)\s*\.\s*csv\b/g)) {
4095
+ const target = match[1];
4096
+ if (target !== "ctx" && !target.endsWith("Ctx")) {
4097
+ continue;
4098
+ }
4099
+ const openParen = findCallOpenParen(scanSource, match.index + match[0].length);
4100
+ if (openParen < 0) {
4101
+ continue;
4102
+ }
4103
+ const argument = firstCallArgument(scanSource, openParen);
4104
+ if (!argument) {
4105
+ unresolved.push({
4106
+ sourceFragment: "ctx.csv()",
4107
+ message: "ctx.csv() requires a file path string or input reference."
4108
+ });
4109
+ } else if (!isRuntimeInputExpression(argument.text)) {
4110
+ const resolvedPath = resolveStringExpression(argument.text, constants);
4111
+ if (resolvedPath == null) {
3758
4112
  unresolved.push({
3759
- sourceFragment: "ctx.csv()",
3760
- message: "ctx.csv() requires a file path string or input reference."
4113
+ sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
4114
+ 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
4115
  });
3762
- } else if (!isRuntimeInputExpression(argument)) {
3763
- const resolvedPath = resolveStringExpression(argument, constants);
3764
- if (resolvedPath == null) {
4116
+ } else {
4117
+ const absoluteCsvPath = (0, import_node_path6.resolve)((0, import_node_path6.dirname)(absolutePath), resolvedPath);
4118
+ if ((0, import_node_path6.isAbsolute)(resolvedPath) || !isPathInsideDirectory2(absoluteCsvPath, packagingRoot)) {
3765
4119
  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)
4120
+ sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
4121
+ message: "ctx.csv(...) packaged file paths must be relative paths inside the play directory. Pass external files at runtime with input.file instead."
3787
4122
  });
4123
+ continue;
3788
4124
  }
4125
+ const buffer = await (0, import_promises3.readFile)(absoluteCsvPath);
4126
+ const stats = await (0, import_promises3.stat)(absoluteCsvPath);
4127
+ files.set(absoluteCsvPath, {
4128
+ sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
4129
+ logicalPath: resolvedPath,
4130
+ absolutePath: absoluteCsvPath,
4131
+ bytes: stats.size,
4132
+ contentHash: sha2562(buffer),
4133
+ contentType: contentTypeForFile(absoluteCsvPath)
4134
+ });
3789
4135
  }
3790
4136
  }
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);
4137
+ }
4138
+ for (const specifier of localImportSpecifiers(sourceCode)) {
4139
+ childVisits.push(
4140
+ resolveLocalImport2(absolutePath, specifier).then(
4141
+ (resolvedImport) => visitSourceFile(resolvedImport)
4142
+ )
4143
+ );
4144
+ }
3808
4145
  await Promise.all(childVisits);
3809
4146
  };
3810
4147
  await visitSourceFile(absoluteEntryFile);
@@ -3816,7 +4153,7 @@ async function discoverPackagedLocalFiles(entryFile) {
3816
4153
 
3817
4154
  // src/plays/bundle-play-file.ts
3818
4155
  var import_meta2 = {};
3819
- var PLAY_BUNDLE_CACHE_VERSION2 = 24;
4156
+ var PLAY_BUNDLE_CACHE_VERSION2 = 26;
3820
4157
  var MODULE_DIR = (0, import_node_path7.dirname)((0, import_node_url.fileURLToPath)(import_meta2.url));
3821
4158
  var SDK_PACKAGE_ROOT = (0, import_node_path7.resolve)(MODULE_DIR, "..", "..");
3822
4159
  var SOURCE_REPO_ROOT = (0, import_node_path7.resolve)(SDK_PACKAGE_ROOT, "..");
@@ -3831,7 +4168,7 @@ var PROJECT_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? SOURCE_REPO_ROOT : HAS_PACKAGED
3831
4168
  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
4169
  var SDK_PACKAGE_JSON = (0, import_node_path7.resolve)(SDK_PACKAGE_ROOT, "package.json");
3833
4170
  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");
4171
+ 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
4172
  var SDK_WORKERS_ENTRY_FILE = (0, import_node_path7.resolve)(SDK_SOURCE_ROOT, "worker-play-entry.ts");
3836
4173
  var WORKERS_HARNESS_ENTRY_FILE = (0, import_node_path7.resolve)(PROJECT_ROOT, "apps", "play-runner-workers", "src", "entry.ts");
3837
4174
  var WORKERS_HARNESS_FILES_DIR = (0, import_node_path7.resolve)(PROJECT_ROOT, "apps", "play-runner-workers", "src");
@@ -3863,7 +4200,7 @@ function createSdkPlayBundlingAdapter() {
3863
4200
  sdkSourceRoot: SDK_SOURCE_ROOT,
3864
4201
  sdkPackageJson: SDK_PACKAGE_JSON,
3865
4202
  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,
4203
+ sdkTypesEntryFile: HAS_SOURCE_BUNDLING_SOURCES || !(0, import_node_fs5.existsSync)(SDK_TYPES_ENTRY_FILE) ? SDK_ENTRY_FILE : SDK_TYPES_ENTRY_FILE,
3867
4204
  sdkWorkersEntryFile: SDK_WORKERS_ENTRY_FILE,
3868
4205
  workersHarnessEntryFile: WORKERS_HARNESS_ENTRY_FILE,
3869
4206
  workersHarnessFilesDir: WORKERS_HARNESS_FILES_DIR,
@@ -4100,13 +4437,15 @@ function formatLoadedPlayMessage(materializedFile) {
4100
4437
  return `Loaded play here: ${materializedFile.path}`;
4101
4438
  }
4102
4439
  function buildReadonlyPrebuiltPlayError(reference) {
4440
+ const localName = reference.split("/").slice(1).join("/") || "custom-play";
4103
4441
  return new Error(
4104
4442
  `Cannot edit or push ${reference} because Deepline prebuilt plays are read-only.
4105
4443
  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.`
4444
+ 1. Run: deepline plays get ${reference} --source --out ./${localName}.play.ts
4445
+ 2. Change definePlay('${localName}', ...) to a new play name you own.
4446
+ 3. Run: deepline plays check ./${localName}.play.ts
4447
+ 4. Run: deepline plays publish ./${localName}.play.ts
4448
+ 5. Your play will then live under your workspace namespace.`
4110
4449
  );
4111
4450
  }
4112
4451
  async function ensureEditableRemotePlay(client, target) {
@@ -4247,6 +4586,15 @@ function fileInputBindingsFromStaticPipeline(staticPipeline) {
4247
4586
  );
4248
4587
  return inputField ? [{ inputPath: inputField }] : [];
4249
4588
  }
4589
+ function applyCsvShortcutInput(input) {
4590
+ const csvValue = getDottedInputValue(input.runtimeInput, "csv");
4591
+ if (csvValue == null || csvValue === "") return;
4592
+ const candidate = input.bindings.find((binding) => binding.inputPath !== "csv")?.inputPath ?? input.fallbackInputPath ?? null;
4593
+ if (!candidate || candidate === "csv") return;
4594
+ const existing = getDottedInputValue(input.runtimeInput, candidate);
4595
+ if (existing != null && existing !== "") return;
4596
+ setDottedInputValue(input.runtimeInput, candidate, csvValue);
4597
+ }
4250
4598
  function isLocalFilePathValue(value) {
4251
4599
  if (typeof value !== "string" || !value.trim()) return false;
4252
4600
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim())) return false;
@@ -4370,6 +4718,7 @@ async function compileBundledPlayGraphManifests(client, graph) {
4370
4718
  node.compilerManifest = await client.compilePlayManifest({
4371
4719
  name,
4372
4720
  sourceCode: node.sourceCode,
4721
+ sourceFiles: node.sourceFiles,
4373
4722
  artifact: node.artifact,
4374
4723
  importedPlayDependencies: node.importedPlayDependencies.map(
4375
4724
  (dependency) => {
@@ -4419,6 +4768,7 @@ async function publishImportedPlayDependencies(client, graph) {
4419
4768
  await client.registerPlayArtifact({
4420
4769
  name: node.playName,
4421
4770
  sourceCode: node.sourceCode,
4771
+ sourceFiles: node.sourceFiles,
4422
4772
  artifact: node.artifact,
4423
4773
  compilerManifest: requireCompilerManifest(node),
4424
4774
  publish: true
@@ -4436,67 +4786,6 @@ function formatTimestamp(value) {
4436
4786
  function formatRunLine(run) {
4437
4787
  return `${run.workflowId} ${run.status} ${formatTimestamp(run.startTime)}`;
4438
4788
  }
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
4789
  function isTransientPlayStatusPollError(error) {
4501
4790
  if (error instanceof DeeplineError && typeof error.statusCode === "number") {
4502
4791
  return error.statusCode >= 500 && error.statusCode < 600;
@@ -4600,7 +4889,7 @@ function assertPlayWaitNotTimedOut(input) {
4600
4889
  if (input.waitTimeoutMs !== null && Date.now() - input.startedAt >= input.waitTimeoutMs) {
4601
4890
  const hasRealRunId = input.workflowId.length > 0 && input.workflowId !== "pending";
4602
4891
  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.`;
4892
+ 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
4893
  throw new DeeplineError(
4605
4894
  `Timed out waiting for play ${hasRealRunId ? input.workflowId : "<no run id>"} after ${Math.ceil(input.waitTimeoutMs / 1e3)}s${phaseSuffix}.${tailHint}`,
4606
4895
  void 0,
@@ -4613,66 +4902,6 @@ function assertPlayWaitNotTimedOut(input) {
4613
4902
  );
4614
4903
  }
4615
4904
  }
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
4905
  async function startAndWaitForPlayCompletionByStream(input) {
4677
4906
  const startedAt = Date.now();
4678
4907
  const state = {
@@ -4752,10 +4981,12 @@ async function startAndWaitForPlayCompletionByStream(input) {
4752
4981
  clearTimeout(timeout);
4753
4982
  }
4754
4983
  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})
4984
+ if (!input.jsonOutput) {
4985
+ process.stderr.write(
4986
+ `[play watch] start stream failed after run ${lastKnownWorkflowId}; falling back to polling (${reason})
4757
4987
  `
4758
- );
4988
+ );
4989
+ }
4759
4990
  return waitForPlayCompletionByPolling({
4760
4991
  client: input.client,
4761
4992
  workflowId: lastKnownWorkflowId,
@@ -4774,6 +5005,24 @@ async function startAndWaitForPlayCompletionByStream(input) {
4774
5005
  clearTimeout(timeout);
4775
5006
  }
4776
5007
  }
5008
+ if (lastKnownWorkflowId) {
5009
+ if (!input.jsonOutput) {
5010
+ input.progress.writeLine(
5011
+ `[play watch] start stream ended after run ${lastKnownWorkflowId}; falling back to polling`
5012
+ );
5013
+ }
5014
+ return waitForPlayCompletionByPolling({
5015
+ client: input.client,
5016
+ workflowId: lastKnownWorkflowId,
5017
+ pollIntervalMs: 500,
5018
+ jsonOutput: input.jsonOutput,
5019
+ emitLogs: input.emitLogs,
5020
+ waitTimeoutMs: input.waitTimeoutMs,
5021
+ startedAt,
5022
+ state,
5023
+ progress: input.progress
5024
+ });
5025
+ }
4777
5026
  const phaseSuffix = lastPhase && lastPhase.trim() ? ` (last observed phase: ${lastPhase.trim()})` : "";
4778
5027
  const idSuffix = lastKnownWorkflowId ? ` runId=${lastKnownWorkflowId}` : "";
4779
5028
  throw new DeeplineError(
@@ -4851,38 +5100,6 @@ async function waitForPlayCompletionByPolling(input) {
4851
5100
  }
4852
5101
  }
4853
5102
  }
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
5103
  function formatInteger(value) {
4887
5104
  return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : String(value ?? "-");
4888
5105
  }
@@ -5011,17 +5228,153 @@ function buildRunWarnings(status, rowsInfo) {
5011
5228
  }
5012
5229
  function buildRunNextCommands(runId) {
5013
5230
  return {
5014
- exportCsv: `deepline runs export ${runId} --out output.csv`,
5015
- status: `deepline runs status ${runId} --json`,
5016
- logs: `deepline runs logs ${runId}`
5231
+ get: `deepline runs get ${runId} --json`,
5232
+ tail: `deepline runs tail ${runId} --json`,
5233
+ stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
5234
+ logs: `deepline runs logs ${runId} --out run.log --json`,
5235
+ exportCsv: `deepline runs export ${runId} --out output.csv`
5236
+ };
5237
+ }
5238
+ var RUN_LOG_PREVIEW_LIMIT = 20;
5239
+ function getRecordField(value, key) {
5240
+ return value && typeof value === "object" && !Array.isArray(value) ? value[key] : void 0;
5241
+ }
5242
+ function getNumericField(value, key) {
5243
+ const field = getRecordField(value, key);
5244
+ return typeof field === "number" && Number.isFinite(field) ? field : null;
5245
+ }
5246
+ function getStringField(value, key) {
5247
+ const field = getRecordField(value, key);
5248
+ return typeof field === "string" && field.trim() ? field : null;
5249
+ }
5250
+ function normalizeRunStatusForEnvelope(status) {
5251
+ const run = status.run ?? null;
5252
+ return {
5253
+ id: status.runId,
5254
+ playName: status.playName ?? status.name ?? getStringField(run, "playName") ?? null,
5255
+ status: status.status,
5256
+ runtime: getStringField(status, "runtime") ?? getStringField(status, "runtimeBackend") ?? getStringField(run, "runtime") ?? null,
5257
+ startedAt: getStringField(run, "startTime") ?? getStringField(run, "startedAt") ?? null,
5258
+ updatedAt: getStringField(status, "updatedAt") ?? getStringField(run, "updatedAt") ?? null,
5259
+ finishedAt: getStringField(run, "closeTime") ?? getStringField(run, "finishedAt") ?? null,
5260
+ source: getRecordField(status, "source") ?? getRecordField(status, "artifact") ?? null
5017
5261
  };
5018
5262
  }
5263
+ function normalizeProgressForEnvelope(status, rowsInfo) {
5264
+ const progress = status.progress;
5265
+ const total = getNumericField(progress, "totalRows") ?? getNumericField(progress, "total") ?? rowsInfo?.totalRows ?? null;
5266
+ const failed = getNumericField(progress, "failed") ?? getNumericField(progress, "failedRows") ?? null;
5267
+ const completed = getNumericField(progress, "completed") ?? getNumericField(progress, "completedRows") ?? (status.status === "completed" ? total : null);
5268
+ const pending = getNumericField(progress, "pending") ?? (typeof total === "number" && typeof completed === "number" && typeof failed === "number" ? Math.max(0, total - completed - failed) : null);
5269
+ return {
5270
+ total,
5271
+ completed,
5272
+ pending,
5273
+ failed,
5274
+ executed: getNumericField(progress, "executed"),
5275
+ reused: getNumericField(progress, "reused"),
5276
+ skipped: getNumericField(progress, "skipped"),
5277
+ retried: getNumericField(progress, "retried"),
5278
+ degraded: typeof getRecordField(progress, "degraded") === "boolean" ? getRecordField(progress, "degraded") : null,
5279
+ duplicates: getRecordField(progress, "duplicates") ?? null,
5280
+ active: getStringField(progress, "status") ?? getStringField(status, "activeStep") ?? getStringField(status, "activeNodeId") ?? null,
5281
+ wait: status.wait ?? null
5282
+ };
5283
+ }
5284
+ function normalizeOutputsForEnvelope(rowsInfo, exportedPath) {
5285
+ if (!rowsInfo) {
5286
+ return exportedPath ? [{ name: "output", kind: "file", path: exportedPath }] : [];
5287
+ }
5288
+ return [
5289
+ {
5290
+ name: "rows",
5291
+ kind: "dataset",
5292
+ rowCount: rowsInfo.totalRows,
5293
+ columns: rowsInfo.columns,
5294
+ preview: rowsInfo.rows.slice(0, 5),
5295
+ previewRowCount: Math.min(rowsInfo.rows.length, 5),
5296
+ previewLimit: 5,
5297
+ complete: rowsInfo.complete,
5298
+ source: rowsInfo.source,
5299
+ ...exportedPath ? { csv_path: exportedPath } : {}
5300
+ }
5301
+ ];
5302
+ }
5303
+ function normalizeStepsForEnvelope(status) {
5304
+ const directSteps = getRecordField(status, "steps");
5305
+ if (Array.isArray(directSteps)) {
5306
+ return directSteps;
5307
+ }
5308
+ const timeline = getRecordField(status, "timeline");
5309
+ if (Array.isArray(timeline)) {
5310
+ return timeline;
5311
+ }
5312
+ return [];
5313
+ }
5314
+ function normalizeErrorsForEnvelope(status, error) {
5315
+ const directErrors = getRecordField(status, "errors");
5316
+ if (Array.isArray(directErrors)) {
5317
+ return directErrors.filter(
5318
+ (entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)
5319
+ );
5320
+ }
5321
+ if (!error) {
5322
+ return [];
5323
+ }
5324
+ return [
5325
+ {
5326
+ code: getStringField(status, "errorCode") ?? "RUN_FAILED",
5327
+ phase: getStringField(status, "errorPhase") ?? "runtime",
5328
+ message: error,
5329
+ retryable: typeof getRecordField(status, "retryable") === "boolean" ? getRecordField(status, "retryable") : null,
5330
+ nextAction: `deepline runs get ${status.runId} --json`
5331
+ }
5332
+ ];
5333
+ }
5334
+ function normalizeLogsForEnvelope(status) {
5335
+ const logs = Array.isArray(status.progress?.logs) ? status.progress.logs : [];
5336
+ const offset = typeof status.progress?.logOffset === "number" && Number.isFinite(status.progress.logOffset) ? Math.max(0, Math.trunc(status.progress.logOffset)) : 0;
5337
+ const totalCount = offset + logs.length;
5338
+ const entries = logs.slice(Math.max(0, logs.length - RUN_LOG_PREVIEW_LIMIT));
5339
+ const firstSequence = entries.length === 0 ? null : offset + logs.length - entries.length + 1;
5340
+ const lastSequence = totalCount === 0 ? null : totalCount;
5341
+ return {
5342
+ totalCount,
5343
+ returnedCount: entries.length,
5344
+ firstSequence,
5345
+ lastSequence,
5346
+ truncated: totalCount > entries.length,
5347
+ hasMore: totalCount > entries.length,
5348
+ entries,
5349
+ nextCursor: lastSequence
5350
+ };
5351
+ }
5352
+ function stripProviderSpendFromBilling(value) {
5353
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
5354
+ return value;
5355
+ }
5356
+ const next = {};
5357
+ for (const [key, item] of Object.entries(value)) {
5358
+ if (key === "providerCostUsd" || key === "totalProviderCostUsd") {
5359
+ continue;
5360
+ }
5361
+ next[key] = item;
5362
+ }
5363
+ return next;
5364
+ }
5019
5365
  function compactPlayStatus(status, options) {
5020
5366
  const rowsInfo = extractCanonicalRowsInfo(status);
5021
5367
  const result = status && typeof status === "object" ? status.result : null;
5022
5368
  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;
5369
+ const datasetStats = rowsInfo && rowsInfo.complete ? buildDatasetStats(
5370
+ rowsInfo.rows,
5371
+ rowsInfo.totalRows,
5372
+ rowsInfo.columns,
5373
+ extractDatasetExecutionStats(status)
5374
+ ) : null;
5375
+ const billing = status && typeof status === "object" ? stripProviderSpendFromBilling(
5376
+ status.billing
5377
+ ) : null;
5025
5378
  const progressError = status.progress?.error;
5026
5379
  const error = typeof progressError === "string" ? progressError : typeof status.error === "string" ? String(status.error) : null;
5027
5380
  return {
@@ -5030,6 +5383,12 @@ function compactPlayStatus(status, options) {
5030
5383
  ...typeof status.name === "string" ? { name: status.name } : {},
5031
5384
  ...typeof status.playName === "string" ? { playName: status.playName } : {},
5032
5385
  status: status.status,
5386
+ run: normalizeRunStatusForEnvelope(status),
5387
+ progress: normalizeProgressForEnvelope(status, rowsInfo),
5388
+ outputs: normalizeOutputsForEnvelope(rowsInfo, options?.exportedPath),
5389
+ steps: normalizeStepsForEnvelope(status),
5390
+ errors: normalizeErrorsForEnvelope(status, error),
5391
+ logs: normalizeLogsForEnvelope(status),
5033
5392
  ...error ? { error } : {},
5034
5393
  ...warnings.length > 0 ? { warnings } : {},
5035
5394
  output: buildOutputSummary(rowsInfo, options?.exportedPath) ?? result ?? null,
@@ -5038,7 +5397,6 @@ function compactPlayStatus(status, options) {
5038
5397
  ...datasetStats ? { dataset_stats: datasetStats } : {},
5039
5398
  ...rowsInfo ? { previewRows: rowsInfo.rows.slice(0, 5) } : {},
5040
5399
  ...billing ? { billing } : {},
5041
- ...status.run ? { run: status.run } : {},
5042
5400
  next: buildRunNextCommands(status.runId)
5043
5401
  };
5044
5402
  }
@@ -5052,7 +5410,8 @@ function enrichPlayStatusWithDatasetStats(status) {
5052
5410
  dataset_stats: buildDatasetStats(
5053
5411
  rowsInfo.rows,
5054
5412
  rowsInfo.totalRows,
5055
- rowsInfo.columns
5413
+ rowsInfo.columns,
5414
+ extractDatasetExecutionStats(status)
5056
5415
  )
5057
5416
  };
5058
5417
  }
@@ -5067,8 +5426,9 @@ function formatDatasetStatsLines(datasetStats) {
5067
5426
  )) {
5068
5427
  const topValues = stat3.top_values ? `, top_values=${Object.entries(stat3.top_values).slice(0, 3).map(([value, count]) => `${value}=${count}`).join(", ")}` : "";
5069
5428
  const sample = stat3.sample_value !== void 0 ? `, sample_value=${JSON.stringify(stat3.sample_value)}` : "";
5429
+ const execution = stat3.execution ? `, execution=${Object.entries(stat3.execution).map(([bucket, count]) => `${bucket}=${count}`).join(", ")}` : "";
5070
5430
  lines.push(
5071
- ` ${column}: non_empty=${stat3.non_empty}, unique=${stat3.unique}${topValues}${sample}`
5431
+ ` ${column}: non_empty=${stat3.non_empty}, unique=${stat3.unique}${topValues}${sample}${execution}`
5072
5432
  );
5073
5433
  }
5074
5434
  return lines;
@@ -5091,7 +5451,12 @@ function writePlayResult(status, jsonOutput, options) {
5091
5451
  lines.push(`${success ? "\u2713" : "\u2717"} ${publicStatus} ${runId}`);
5092
5452
  const rowsInfo = extractCanonicalRowsInfo(status);
5093
5453
  const warnings = buildRunWarnings(status, rowsInfo);
5094
- const datasetStats = rowsInfo && rowsInfo.complete ? buildDatasetStats(rowsInfo.rows, rowsInfo.totalRows, rowsInfo.columns) : null;
5454
+ const datasetStats = rowsInfo && rowsInfo.complete ? buildDatasetStats(
5455
+ rowsInfo.rows,
5456
+ rowsInfo.totalRows,
5457
+ rowsInfo.columns,
5458
+ extractDatasetExecutionStats(status)
5459
+ ) : null;
5095
5460
  const outputSummary = buildOutputSummary(rowsInfo, options?.exportedPath);
5096
5461
  if (outputSummary) {
5097
5462
  const columns = Array.isArray(outputSummary.columns) ? outputSummary.columns.length : 0;
@@ -5210,10 +5575,10 @@ function writeStartedPlayRun(input) {
5210
5575
  const lines = [
5211
5576
  `Started ${input.playName}`,
5212
5577
  ` 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`
5578
+ ` get status: deepline runs get ${input.runId} --json`,
5579
+ ` tail logs: deepline runs tail ${input.runId} --json`,
5580
+ ` stop run: deepline runs stop ${input.runId} --reason "stale lock" --json`,
5581
+ ` result JSON: deepline runs get ${input.runId} --json`
5217
5582
  ];
5218
5583
  if (input.dashboardUrl) {
5219
5584
  lines.push(` play page: ${input.dashboardUrl}`);
@@ -5360,6 +5725,10 @@ function parsePlayCheckOptions(args) {
5360
5725
  const jsonOutput = argsWantJson(args);
5361
5726
  return { target, jsonOutput };
5362
5727
  }
5728
+ function shouldUseLocalOnlyPlayCheck() {
5729
+ const value = process.env.DEEPLINE_PLAY_CHECK_LOCAL_ONLY?.trim().toLowerCase();
5730
+ return value === "1" || value === "true" || value === "yes" || value === "on";
5731
+ }
5363
5732
  async function handlePlayCheck(args) {
5364
5733
  const options = parsePlayCheckOptions(args);
5365
5734
  if (!isFileTarget(options.target)) {
@@ -5385,10 +5754,28 @@ async function handlePlayCheck(args) {
5385
5754
  return 1;
5386
5755
  }
5387
5756
  const playName = graph.root.playName ?? extractPlayName(sourceCode, absolutePlayPath);
5757
+ if (shouldUseLocalOnlyPlayCheck()) {
5758
+ const result2 = {
5759
+ valid: true,
5760
+ errors: [],
5761
+ staticPipeline: graph.root.compilerManifest?.staticPipeline ?? null,
5762
+ artifactHash: graph.root.artifact.artifactHash,
5763
+ graphHash: graph.root.artifact.graphHash
5764
+ };
5765
+ if (options.jsonOutput) {
5766
+ process.stdout.write(`${JSON.stringify({ name: playName, ...result2 })}
5767
+ `);
5768
+ } else {
5769
+ console.log(`\u2713 ${playName} passed local play check`);
5770
+ console.log(` artifact: ${result2.artifactHash.slice(0, 12)}`);
5771
+ }
5772
+ return 0;
5773
+ }
5388
5774
  const client = new DeeplineClient();
5389
5775
  const result = await client.checkPlayArtifact({
5390
5776
  name: playName,
5391
5777
  sourceCode: graph.root.sourceCode,
5778
+ sourceFiles: graph.root.sourceFiles,
5392
5779
  artifact: graph.root.artifact
5393
5780
  });
5394
5781
  if (options.jsonOutput) {
@@ -5440,17 +5827,24 @@ async function handleFileBackedRun(options) {
5440
5827
  const packagedFileUploads = bundleResult.packagedFiles.map(
5441
5828
  (file) => stageFile(file.logicalPath, file.absolutePath)
5442
5829
  );
5830
+ const fileInputBindings = fileInputBindingsFromStaticPipeline(
5831
+ requireCompilerManifest(bundleResult).staticPipeline
5832
+ );
5833
+ applyCsvShortcutInput({
5834
+ runtimeInput,
5835
+ bindings: fileInputBindings,
5836
+ fallbackInputPath: "file"
5837
+ });
5443
5838
  const stagedFileInputs = await stageFileInputArgs({
5444
5839
  client,
5445
5840
  runtimeInput,
5446
- bindings: fileInputBindingsFromStaticPipeline(
5447
- requireCompilerManifest(bundleResult).staticPipeline
5448
- ),
5841
+ bindings: fileInputBindings,
5449
5842
  progress
5450
5843
  });
5451
5844
  const startRequest = {
5452
5845
  name: playName,
5453
5846
  sourceCode: bundleResult.sourceCode,
5847
+ sourceFiles: bundleResult.sourceFiles,
5454
5848
  runtimeArtifact: bundleResult.artifact,
5455
5849
  compilerManifest: requireCompilerManifest(bundleResult),
5456
5850
  packagedFileUploads,
@@ -5525,13 +5919,18 @@ async function handleNamedRun(options) {
5525
5919
  selector: options.revisionSelector
5526
5920
  });
5527
5921
  const runtimeInput = options.input ? { ...options.input } : {};
5922
+ const fileInputBindings = [
5923
+ ...fileInputBindingsFromPlaySchema(playDetail.play.inputSchema),
5924
+ ...fileInputBindingsFromStaticPipeline(playDetail.play.staticPipeline)
5925
+ ];
5926
+ applyCsvShortcutInput({
5927
+ runtimeInput,
5928
+ bindings: fileInputBindings
5929
+ });
5528
5930
  const stagedFileInputs = await stageFileInputArgs({
5529
5931
  client,
5530
5932
  runtimeInput,
5531
- bindings: [
5532
- ...fileInputBindingsFromPlaySchema(playDetail.play.inputSchema),
5533
- ...fileInputBindingsFromStaticPipeline(playDetail.play.staticPipeline)
5534
- ],
5933
+ bindings: fileInputBindings,
5535
5934
  progress
5536
5935
  });
5537
5936
  const startRequest = {
@@ -5609,80 +6008,101 @@ async function handlePlayRun(args) {
5609
6008
  }
5610
6009
  return handleNamedRun(options);
5611
6010
  }
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;
6011
+ function parseRunIdPositional(args, usage) {
5625
6012
  for (let index = 0; index < args.length; index += 1) {
5626
6013
  const arg = args[index];
5627
- if ((arg === "--interval-ms" || arg === "--poll-interval-ms") && args[index + 1]) {
5628
- intervalMs = parsePositiveInteger2(args[++index], arg);
6014
+ if (arg === "--json" || arg === "--full" || arg === "--logs" || arg === "--compact" || arg === "--limit") {
6015
+ if (arg === "--limit" && args[index + 1]) {
6016
+ index += 1;
6017
+ }
6018
+ continue;
6019
+ }
6020
+ if ((arg === "--out" || arg === "--cursor" || arg === "--reason" || arg === "--interval-ms" || arg === "--poll-interval-ms") && args[index + 1]) {
6021
+ index += 1;
6022
+ continue;
6023
+ }
6024
+ if (!arg.startsWith("--")) {
6025
+ return arg;
5629
6026
  }
5630
6027
  }
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;
6028
+ throw new DeeplineError(usage);
5650
6029
  }
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;
6030
+ async function handleRunGet(args) {
6031
+ const usage = "Usage: deepline runs get <run-id> [--json] [--full]";
6032
+ let runId;
5654
6033
  try {
5655
- target = parsePlayRunTarget({ args, usage, allowName: true });
6034
+ runId = parseRunIdPositional(args, usage);
5656
6035
  } catch (error) {
5657
6036
  console.error(error instanceof Error ? error.message : usage);
5658
6037
  return 1;
5659
6038
  }
5660
6039
  const client = new DeeplineClient();
5661
- const workflowId = await resolvePlayRunId(client, target);
5662
- const status = await client.getPlayStatus(workflowId);
6040
+ const status = await client.runs.get(runId);
5663
6041
  writePlayResult(status, argsWantJson(args), {
5664
6042
  fullJson: args.includes("--full")
5665
6043
  });
5666
6044
  return 0;
5667
6045
  }
5668
- function parseRunIdPositional(args, usage) {
6046
+ async function handleRunsList(args) {
6047
+ const usage = "Usage: deepline runs list --play <play-name> [--status <status>] [--json]";
6048
+ let playName = null;
6049
+ let statusFilter = null;
5669
6050
  for (let index = 0; index < args.length; index += 1) {
5670
6051
  const arg = args[index];
5671
- if (arg === "--json" || arg === "--full" || arg === "--logs") {
6052
+ if ((arg === "--play" || arg === "--name") && args[index + 1]) {
6053
+ playName = parseReferencedPlayTarget(args[++index]).playName;
5672
6054
  continue;
5673
6055
  }
5674
- if (arg === "--out" && args[index + 1]) {
5675
- index += 1;
6056
+ if (arg === "--status" && args[index + 1]) {
6057
+ statusFilter = args[++index].trim().toLowerCase();
5676
6058
  continue;
5677
6059
  }
5678
- if (!arg.startsWith("--")) {
5679
- return arg;
6060
+ if (arg === "--json" || arg === "--compact") {
6061
+ continue;
5680
6062
  }
5681
6063
  }
5682
- throw new DeeplineError(usage);
6064
+ if (!playName) {
6065
+ console.error(usage);
6066
+ return 1;
6067
+ }
6068
+ const client = new DeeplineClient();
6069
+ const runs = (await client.runs.list({
6070
+ play: playName,
6071
+ ...statusFilter ? { status: statusFilter } : {}
6072
+ })).map((run) => ({
6073
+ runId: run.workflowId,
6074
+ workflowId: run.workflowId,
6075
+ temporalRunId: run.runId,
6076
+ status: String(run.status ?? "").toLowerCase(),
6077
+ startedAt: run.startTime,
6078
+ finishedAt: run.closeTime,
6079
+ executionTime: run.executionTime,
6080
+ playName: run.memo?.playName ?? playName
6081
+ }));
6082
+ if (argsWantJson(args)) {
6083
+ process.stdout.write(
6084
+ `${JSON.stringify({
6085
+ runs,
6086
+ count: runs.length,
6087
+ next: {
6088
+ get: runs[0]?.runId ? `deepline runs get ${runs[0].runId} --json` : null
6089
+ }
6090
+ })}
6091
+ `
6092
+ );
6093
+ } else {
6094
+ if (runs.length === 0) {
6095
+ console.log(`No runs found for ${playName}.`);
6096
+ } else {
6097
+ for (const run of runs) {
6098
+ console.log(`${run.runId} ${run.status} ${formatTimestamp(run.startedAt)}`);
6099
+ }
6100
+ }
6101
+ }
6102
+ return 0;
5683
6103
  }
5684
- async function handleRunStatus(args) {
5685
- const usage = "Usage: deepline runs status <run-id> [--json] [--full]";
6104
+ async function handleRunTail(args) {
6105
+ const usage = "Usage: deepline runs tail <run-id> [--json] [--compact] [--cursor <cursor>]";
5686
6106
  let runId;
5687
6107
  try {
5688
6108
  runId = parseRunIdPositional(args, usage);
@@ -5691,14 +6111,24 @@ async function handleRunStatus(args) {
5691
6111
  return 1;
5692
6112
  }
5693
6113
  const client = new DeeplineClient();
5694
- const status = await client.getPlayStatus(runId);
5695
- writePlayResult(status, argsWantJson(args), {
5696
- fullJson: args.includes("--full")
6114
+ let afterLogIndex;
6115
+ for (let index = 0; index < args.length; index += 1) {
6116
+ const arg = args[index];
6117
+ if (arg === "--cursor" && args[index + 1]) {
6118
+ const parsed = Number(args[++index]);
6119
+ if (Number.isInteger(parsed) && parsed >= 0) {
6120
+ afterLogIndex = parsed;
6121
+ }
6122
+ }
6123
+ }
6124
+ const status = await client.runs.tail(runId, {
6125
+ ...afterLogIndex !== void 0 ? { afterLogIndex } : {}
5697
6126
  });
5698
- return 0;
6127
+ writePlayResult(status, argsWantJson(args));
6128
+ return status.status === "failed" ? 1 : 0;
5699
6129
  }
5700
6130
  async function handleRunLogs(args) {
5701
- const usage = "Usage: deepline runs logs <run-id> [--json]";
6131
+ const usage = "Usage: deepline runs logs <run-id> [--limit 200] [--out run.log] [--json]";
5702
6132
  let runId;
5703
6133
  try {
5704
6134
  runId = parseRunIdPositional(args, usage);
@@ -5706,14 +6136,86 @@ async function handleRunLogs(args) {
5706
6136
  console.error(error instanceof Error ? error.message : usage);
5707
6137
  return 1;
5708
6138
  }
6139
+ let limit = 200;
6140
+ let outPath = null;
6141
+ for (let index = 0; index < args.length; index += 1) {
6142
+ const arg = args[index];
6143
+ if (arg === "--limit" && args[index + 1]) {
6144
+ limit = parsePositiveInteger2(args[++index], "--limit");
6145
+ continue;
6146
+ }
6147
+ if (arg === "--out" && args[index + 1]) {
6148
+ outPath = (0, import_node_path8.resolve)(args[++index]);
6149
+ }
6150
+ }
5709
6151
  const client = new DeeplineClient();
5710
- const status = await client.getPlayStatus(runId);
6152
+ const status = await client.runs.get(runId);
5711
6153
  const logs = status.progress?.logs ?? [];
6154
+ if (outPath) {
6155
+ (0, import_node_fs6.writeFileSync)(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
6156
+ if (argsWantJson(args)) {
6157
+ process.stdout.write(
6158
+ `${JSON.stringify({
6159
+ runId: status.runId,
6160
+ log_path: outPath,
6161
+ lineCount: logs.length
6162
+ })}
6163
+ `
6164
+ );
6165
+ } else {
6166
+ console.log(`Wrote ${logs.length} log lines to ${outPath}`);
6167
+ }
6168
+ return 0;
6169
+ }
6170
+ const entries = logs.slice(Math.max(0, logs.length - limit));
6171
+ if (argsWantJson(args)) {
6172
+ process.stdout.write(
6173
+ `${JSON.stringify({
6174
+ runId: status.runId,
6175
+ totalCount: logs.length,
6176
+ returnedCount: entries.length,
6177
+ firstSequence: logs.length === 0 ? null : logs.length - entries.length + 1,
6178
+ lastSequence: logs.length === 0 ? null : logs.length,
6179
+ truncated: logs.length > entries.length,
6180
+ hasMore: logs.length > entries.length,
6181
+ entries,
6182
+ next: {
6183
+ export: `deepline runs logs ${status.runId} --out run.log --json`
6184
+ }
6185
+ })}
6186
+ `
6187
+ );
6188
+ } else {
6189
+ process.stdout.write(`${entries.join("\n")}${entries.length > 0 ? "\n" : ""}`);
6190
+ }
6191
+ return 0;
6192
+ }
6193
+ async function handleRunStop(args) {
6194
+ const usage = 'Usage: deepline runs stop <run-id> [--reason "text"] [--json]';
6195
+ let runId;
6196
+ try {
6197
+ runId = parseRunIdPositional(args, usage);
6198
+ } catch (error) {
6199
+ console.error(error instanceof Error ? error.message : usage);
6200
+ return 1;
6201
+ }
6202
+ let reason;
6203
+ for (let index = 0; index < args.length; index += 1) {
6204
+ const arg = args[index];
6205
+ if (arg === "--reason" && args[index + 1]) {
6206
+ reason = args[++index];
6207
+ }
6208
+ }
6209
+ const client = new DeeplineClient();
6210
+ const result = await client.runs.stop(runId, { reason });
5712
6211
  if (argsWantJson(args)) {
5713
- process.stdout.write(`${JSON.stringify({ runId: status.runId, logs })}
6212
+ process.stdout.write(`${JSON.stringify(result)}
5714
6213
  `);
5715
6214
  } else {
5716
- process.stdout.write(`${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
6215
+ console.log(`Stopped ${result.runId}`);
6216
+ if (result.hitlCancelledCount > 0) {
6217
+ console.log(` cancelled HITL waits: ${result.hitlCancelledCount}`);
6218
+ }
5717
6219
  }
5718
6220
  return 0;
5719
6221
  }
@@ -5756,37 +6258,6 @@ async function handleRunExport(args) {
5756
6258
  }
5757
6259
  return 0;
5758
6260
  }
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
6261
  async function handlePlayGet(args) {
5791
6262
  const target = args[0];
5792
6263
  if (!target) {
@@ -5874,33 +6345,6 @@ async function handlePlayGet(args) {
5874
6345
  }
5875
6346
  return 0;
5876
6347
  }
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
6348
  function formatVersionLine(version) {
5905
6349
  const revisionLabel = version.artifactHash?.slice(0, 12) ?? "unknown-revision";
5906
6350
  return `v${version.version} ${revisionLabel} ${formatTimestamp(version.createdAt)}`;
@@ -6032,6 +6476,11 @@ function printPlayDescription(play) {
6032
6476
  }
6033
6477
  }
6034
6478
  console.log(` Run: ${play.runCommand}`);
6479
+ if (play.origin === "prebuilt" && !play.canEdit) {
6480
+ console.log(
6481
+ ` Customize: deepline plays get ${reference} --source --out ./my-play.play.ts`
6482
+ );
6483
+ }
6035
6484
  }
6036
6485
  async function handlePlaySearch(args) {
6037
6486
  let options;
@@ -6129,6 +6578,7 @@ async function handlePlayPublish(args) {
6129
6578
  const published = await client.registerPlayArtifact({
6130
6579
  name: rootPlayName,
6131
6580
  sourceCode: graph.root.sourceCode,
6581
+ sourceFiles: graph.root.sourceFiles,
6132
6582
  artifact: graph.root.artifact,
6133
6583
  compilerManifest: requireCompilerManifest(graph.root),
6134
6584
  publish: true
@@ -6349,50 +6799,12 @@ Examples:
6349
6799
  ...options.json ? ["--json"] : []
6350
6800
  ]);
6351
6801
  });
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
6802
  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
6803
  process.exitCode = await handlePlayVersions([
6360
6804
  ...options.name ? ["--name", options.name] : [],
6361
6805
  ...options.json ? ["--json"] : []
6362
6806
  ]);
6363
6807
  });
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
6808
  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
6809
  process.exitCode = await handlePlayPublish([
6398
6810
  target,
@@ -6408,38 +6820,72 @@ Examples:
6408
6820
  ...options.json ? ["--json"] : []
6409
6821
  ]);
6410
6822
  });
6411
- const runs = program.command("runs").description("Inspect and export play runs.").addHelpText(
6823
+ const runs = program.command("runs").description("Inspect, tail, stop, and export play runs.").addHelpText(
6412
6824
  "after",
6413
6825
  `
6414
6826
  Examples:
6415
- deepline runs status play/my-play/run/20260501t000000-000
6827
+ deepline runs get play/my-play/run/20260501t000000-000 --json
6828
+ deepline runs tail play/my-play/run/20260501t000000-000
6829
+ deepline runs logs play/my-play/run/20260501t000000-000 --out run.log --json
6830
+ deepline runs list --play my-play --status failed --json
6831
+ deepline runs stop play/my-play/run/20260501t000000-000 --reason "stale lock" --json
6416
6832
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv
6417
- deepline runs logs play/my-play/run/20260501t000000-000
6418
6833
  `
6419
6834
  );
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([
6835
+ 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) => {
6836
+ process.exitCode = await handleRunGet([
6422
6837
  runId,
6423
6838
  ...options.json ? ["--json"] : [],
6424
6839
  ...options.full ? ["--full"] : []
6425
6840
  ]);
6426
6841
  });
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,
6842
+ 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) => {
6843
+ process.exitCode = await handleRunsList([
6844
+ "--play",
6845
+ options.play,
6846
+ ...options.status ? ["--status", options.status] : [],
6847
+ ...options.compact ? ["--compact"] : [],
6432
6848
  ...options.json ? ["--json"] : []
6433
6849
  ]);
6434
6850
  });
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) => {
6851
+ 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) => {
6852
+ process.exitCode = await handleRunTail([
6853
+ runId,
6854
+ ...options.json ? ["--json"] : [],
6855
+ ...options.compact ? ["--compact"] : [],
6856
+ ...options.cursor ? ["--cursor", options.cursor] : []
6857
+ ]);
6858
+ });
6859
+ 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
6860
  process.exitCode = await handleRunLogs([
6437
6861
  runId,
6862
+ ...options.limit ? ["--limit", options.limit] : [],
6863
+ ...options.out ? ["--out", options.out] : [],
6864
+ ...options.json ? ["--json"] : []
6865
+ ]);
6866
+ });
6867
+ 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) => {
6868
+ process.exitCode = await handleRunStop([
6869
+ runId,
6870
+ ...options.reason ? ["--reason", options.reason] : [],
6871
+ ...options.json ? ["--json"] : []
6872
+ ]);
6873
+ });
6874
+ 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) => {
6875
+ process.exitCode = await handleRunExport([
6876
+ runId,
6877
+ "--out",
6878
+ options.out,
6438
6879
  ...options.json ? ["--json"] : []
6439
6880
  ]);
6440
6881
  });
6441
6882
  }
6442
6883
 
6884
+ // src/cli/commands/tools.ts
6885
+ var import_node_fs8 = require("fs");
6886
+ var import_node_os7 = require("os");
6887
+ var import_node_path10 = require("path");
6888
+
6443
6889
  // src/tool-output.ts
6444
6890
  var import_node_fs7 = require("fs");
6445
6891
  var import_node_os6 = require("os");
@@ -6608,31 +7054,23 @@ async function listTools(args) {
6608
7054
  }
6609
7055
  return 0;
6610
7056
  }
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();
7057
+ async function searchTools(queryInput, options = {}) {
7058
+ const query = queryInput.trim();
6627
7059
  if (!query) {
6628
7060
  console.error("Usage: deepline tools search <query> [--json]");
6629
7061
  return 1;
6630
7062
  }
6631
- const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
6632
7063
  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 })}
7064
+ const result = await client.searchTools({
7065
+ query,
7066
+ categories: options.categories,
7067
+ searchTerms: options.searchTerms,
7068
+ searchMode: options.searchMode,
7069
+ includeSearchDebug: options.includeSearchDebug
7070
+ });
7071
+ const items = result.tools.map(toListedTool);
7072
+ if (options.json || shouldEmitJson()) {
7073
+ process.stdout.write(`${JSON.stringify({ ...result, tools: items })}
6636
7074
  `);
6637
7075
  return 0;
6638
7076
  }
@@ -6686,11 +7124,14 @@ Common commands:
6686
7124
  ...options.json ? ["--json"] : []
6687
7125
  ]);
6688
7126
  });
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
- ]);
7127
+ 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) => {
7128
+ process.exitCode = await searchTools(query, {
7129
+ json: options.json,
7130
+ categories: options.categories,
7131
+ searchTerms: options.searchTerms ?? options.search_terms,
7132
+ searchMode: options.searchMode === "v1" || options.searchMode === "v2" ? options.searchMode : void 0,
7133
+ includeSearchDebug: Boolean(options.includeSearchDebug)
7134
+ });
6694
7135
  });
6695
7136
  tools.command("get <toolId>").alias("describe").description("Show metadata for a tool.").addHelpText(
6696
7137
  "after",
@@ -7048,6 +7489,61 @@ function parseExecuteOptions(args) {
7048
7489
  }
7049
7490
  return { toolId, params, outputFormat, noPreview };
7050
7491
  }
7492
+ function safeFileStem(value) {
7493
+ return value.trim().replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "tool";
7494
+ }
7495
+ function shellQuote(value) {
7496
+ return `'${value.replace(/'/g, `'\\''`)}'`;
7497
+ }
7498
+ function powerShellQuote(value) {
7499
+ return `'${value.replace(/'/g, "''")}'`;
7500
+ }
7501
+ function seedToolListScript(input) {
7502
+ const stem = safeFileStem(input.toolId);
7503
+ const fileName = `${stem}-workflow-seed-${Date.now()}.play.ts`;
7504
+ const scriptDir = (0, import_node_fs8.mkdtempSync)((0, import_node_path10.join)((0, import_node_os7.tmpdir)(), "deepline-workflow-seed-"));
7505
+ (0, import_node_fs8.chmodSync)(scriptDir, 448);
7506
+ const scriptPath = (0, import_node_path10.join)(scriptDir, fileName);
7507
+ const projectDir = `deepline/projects/${stem}-workflow`;
7508
+ const playName = `${stem}-workflow`;
7509
+ const sampleRows = input.rows.length > 0 ? `${JSON.stringify(input.rows.slice(0, 2)).replace(/\]$/, "")}, ...]` : "[]";
7510
+ const columns = Object.keys(input.rows[0] ?? {}).join(", ");
7511
+ const rowKey = Object.prototype.hasOwnProperty.call(input.rows[0] ?? {}, "id") ? '"id"' : "(row) => JSON.stringify(row)";
7512
+ const script = `import { definePlay } from 'deepline';
7513
+
7514
+ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
7515
+ const result = await ctx.tools.execute({
7516
+ id: ${JSON.stringify(input.toolId)},
7517
+ tool: ${JSON.stringify(input.toolId)},
7518
+ input: ${JSON.stringify(input.payload)},
7519
+ description: ${JSON.stringify(`Seed ${input.toolId} rows for workflow expansion.`)},
7520
+ });
7521
+
7522
+ const list = Object.values(result.lists)[0];
7523
+ const rows = (list?.get() ?? []).slice(0, 100);
7524
+ // ${sampleRows}
7525
+ // columns: ${columns}
7526
+ // .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.' }))
7527
+ // .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.' }))
7528
+ // ctx.map is idempotent by map key + row key; reruns reuse completed rows.
7529
+ const enrichedData = await ctx
7530
+ .map('enriched_data', rows, { key: ${rowKey} })
7531
+ .run({ description: 'Enrich seeded rows.' });
7532
+
7533
+ return {
7534
+ rows: enrichedData,
7535
+ count: await enrichedData.count(),
7536
+ };
7537
+ });
7538
+ `;
7539
+ (0, import_node_fs8.writeFileSync)(scriptPath, script, { encoding: "utf-8", mode: 384 });
7540
+ return {
7541
+ path: scriptPath,
7542
+ projectDir,
7543
+ macCopyCommand: `mkdir -p ${shellQuote(projectDir)} && cp ${shellQuote(scriptPath)} ${shellQuote(`${projectDir}/${fileName}`)}`,
7544
+ windowsCopyCommand: `New-Item -ItemType Directory -Force -Path ${powerShellQuote(projectDir.replace(/\//g, "\\"))} | Out-Null; Copy-Item -LiteralPath ${powerShellQuote(scriptPath)} -Destination ${powerShellQuote(`${projectDir.replace(/\//g, "\\")}\\${fileName}`)}`
7545
+ };
7546
+ }
7051
7547
  async function executeTool(args) {
7052
7548
  let parsed;
7053
7549
  try {
@@ -7101,6 +7597,11 @@ async function executeTool(args) {
7101
7597
  return 0;
7102
7598
  }
7103
7599
  const csv = writeCsvOutputFile(listConversion.rows, `${parsed.toolId}_output`);
7600
+ const seededScript = seedToolListScript({
7601
+ toolId: parsed.toolId,
7602
+ payload: parsed.params,
7603
+ rows: listConversion.rows
7604
+ });
7104
7605
  if (parsed.outputFormat === "csv_file") {
7105
7606
  process.stdout.write(`${JSON.stringify({
7106
7607
  extracted_csv: csv.path,
@@ -7109,6 +7610,12 @@ async function executeTool(args) {
7109
7610
  preview: csv.preview,
7110
7611
  list_strategy: listConversion.strategy,
7111
7612
  list_source_path: listConversion.sourcePath,
7613
+ starter_script: seededScript.path,
7614
+ project_dir: seededScript.projectDir,
7615
+ copy_to_project: {
7616
+ macos_linux: seededScript.macCopyCommand,
7617
+ windows_powershell: seededScript.windowsCopyCommand
7618
+ },
7112
7619
  summary
7113
7620
  })}
7114
7621
  `);
@@ -7126,14 +7633,20 @@ async function executeTool(args) {
7126
7633
  console.log(`summary: ${Object.entries(summary).map(([key, value]) => `${key}=${String(value)}`).join(", ")}`);
7127
7634
  }
7128
7635
  console.log(`preview: ${JSON.stringify(csv.preview)}`);
7636
+ console.log(`starter script: ${seededScript.path}`);
7637
+ console.log(
7638
+ "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."
7639
+ );
7640
+ console.log(`macOS/Linux: ${seededScript.macCopyCommand}`);
7641
+ console.log(`Windows PowerShell: ${seededScript.windowsCopyCommand}`);
7129
7642
  return 0;
7130
7643
  }
7131
7644
 
7132
7645
  // src/cli/skills-sync.ts
7133
7646
  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");
7647
+ var import_node_fs9 = require("fs");
7648
+ var import_node_os8 = require("os");
7649
+ var import_node_path11 = require("path");
7137
7650
  var CHECK_TIMEOUT_MS2 = 3e3;
7138
7651
  var SDK_SKILL_NAME = "deepline-sdk";
7139
7652
  var SKILL_AGENTS = ["codex", "claude-code", "cursor"];
@@ -7143,22 +7656,22 @@ function shouldSkipSkillsSync() {
7143
7656
  return value === "1" || value === "true" || value === "yes" || value === "on";
7144
7657
  }
7145
7658
  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");
7659
+ const home = process.env.HOME?.trim() || (0, import_node_os8.homedir)();
7660
+ return (0, import_node_path11.join)(home, ".local", "deepline", baseUrlSlug(baseUrl), "sdk-skills", ".version");
7148
7661
  }
7149
7662
  function readLocalSkillsVersion(baseUrl) {
7150
7663
  const path = sdkSkillsVersionPath(baseUrl);
7151
- if (!(0, import_node_fs8.existsSync)(path)) return "";
7664
+ if (!(0, import_node_fs9.existsSync)(path)) return "";
7152
7665
  try {
7153
- return (0, import_node_fs8.readFileSync)(path, "utf-8").trim();
7666
+ return (0, import_node_fs9.readFileSync)(path, "utf-8").trim();
7154
7667
  } catch {
7155
7668
  return "";
7156
7669
  }
7157
7670
  }
7158
7671
  function writeLocalSkillsVersion(baseUrl, version) {
7159
7672
  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}
7673
+ (0, import_node_fs9.mkdirSync)((0, import_node_path11.dirname)(path), { recursive: true });
7674
+ (0, import_node_fs9.writeFileSync)(path, `${version}
7162
7675
  `, "utf-8");
7163
7676
  }
7164
7677
  async function fetchSkillsUpdate(baseUrl, localVersion) {
@@ -7189,9 +7702,26 @@ async function fetchSkillsUpdate(baseUrl, localVersion) {
7189
7702
  clearTimeout(timeout);
7190
7703
  }
7191
7704
  }
7192
- function runSkillsInstall(baseUrl) {
7705
+ function buildSkillsInstallArgs(baseUrl) {
7706
+ const packageUrl = new URL("/.well-known/skills/index.json", baseUrl).toString();
7707
+ return [
7708
+ "--yes",
7709
+ "skills",
7710
+ "add",
7711
+ packageUrl,
7712
+ "--agents",
7713
+ ...SKILL_AGENTS,
7714
+ "--global",
7715
+ "--yes",
7716
+ "--skill",
7717
+ SDK_SKILL_NAME,
7718
+ "--full-depth"
7719
+ ];
7720
+ }
7721
+ function buildBunxSkillsInstallArgs(baseUrl) {
7193
7722
  const packageUrl = new URL("/.well-known/skills/index.json", baseUrl).toString();
7194
- const args = [
7723
+ return [
7724
+ "--bun",
7195
7725
  "skills",
7196
7726
  "add",
7197
7727
  packageUrl,
@@ -7203,8 +7733,40 @@ function runSkillsInstall(baseUrl) {
7203
7733
  SDK_SKILL_NAME,
7204
7734
  "--full-depth"
7205
7735
  ];
7736
+ }
7737
+ function hasCommand(command) {
7738
+ const result = (0, import_node_child_process2.spawnSync)(command, ["--version"], {
7739
+ stdio: "ignore",
7740
+ shell: process.platform === "win32"
7741
+ });
7742
+ return result.status === 0;
7743
+ }
7744
+ function shellQuote2(arg) {
7745
+ return `'${arg.replace(/'/g, `'\\''`)}'`;
7746
+ }
7747
+ function resolveSkillsInstallCommands(baseUrl) {
7748
+ const npxArgs = buildSkillsInstallArgs(baseUrl);
7749
+ const npxInstall = {
7750
+ command: "npx",
7751
+ args: npxArgs,
7752
+ manualCommand: `npx ${npxArgs.map(shellQuote2).join(" ")}`
7753
+ };
7754
+ if (hasCommand("bunx")) {
7755
+ const bunxArgs = buildBunxSkillsInstallArgs(baseUrl);
7756
+ return [
7757
+ {
7758
+ command: "bunx",
7759
+ args: bunxArgs,
7760
+ manualCommand: `bunx ${bunxArgs.map(shellQuote2).join(" ")}`
7761
+ },
7762
+ npxInstall
7763
+ ];
7764
+ }
7765
+ return [npxInstall];
7766
+ }
7767
+ function runOneSkillsInstall(install) {
7206
7768
  return new Promise((resolve8) => {
7207
- const child = (0, import_node_child_process2.spawn)("npx", args, {
7769
+ const child = (0, import_node_child_process2.spawn)(install.command, install.args, {
7208
7770
  stdio: ["ignore", "ignore", "pipe"],
7209
7771
  env: process.env
7210
7772
  });
@@ -7213,37 +7775,63 @@ function runSkillsInstall(baseUrl) {
7213
7775
  stderr += chunk.toString("utf-8");
7214
7776
  });
7215
7777
  child.on("error", (error) => {
7216
- process.stderr.write(`SDK skills sync failed to start: ${error.message}
7217
- `);
7218
- resolve8(false);
7778
+ resolve8({
7779
+ ok: false,
7780
+ detail: `failed to start ${install.command}: ${error.message}`,
7781
+ manualCommand: install.manualCommand
7782
+ });
7219
7783
  });
7220
7784
  child.on("close", (code) => {
7221
7785
  if (code === 0) {
7222
- resolve8(true);
7786
+ resolve8({ ok: true, detail: "", manualCommand: install.manualCommand });
7223
7787
  return;
7224
7788
  }
7225
7789
  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);
7790
+ resolve8({
7791
+ ok: false,
7792
+ detail: detail ? `${install.command}: ${detail}` : `${install.command} exited ${code}`,
7793
+ manualCommand: install.manualCommand
7794
+ });
7232
7795
  });
7233
7796
  });
7234
7797
  }
7798
+ async function runSkillsInstall(baseUrl) {
7799
+ const failures = [];
7800
+ for (const install of resolveSkillsInstallCommands(baseUrl)) {
7801
+ const result = await runOneSkillsInstall(install);
7802
+ if (result.ok) return true;
7803
+ failures.push(result);
7804
+ }
7805
+ const details = failures.map((failure) => failure.detail).filter(Boolean).join("\n");
7806
+ const manualCommand = failures.at(-1)?.manualCommand;
7807
+ process.stderr.write(
7808
+ `SDK skills sync failed${details ? `:
7809
+ ${details}` : ""}
7810
+ ` + (manualCommand ? `Run manually: ${manualCommand}
7811
+ ` : "")
7812
+ );
7813
+ return false;
7814
+ }
7815
+ function writeSdkSkillsStatusLine(line) {
7816
+ const progress = getActiveCliProgress();
7817
+ if (progress) {
7818
+ progress.writeLine(line);
7819
+ return;
7820
+ }
7821
+ process.stderr.write(`${line}
7822
+ `);
7823
+ }
7235
7824
  async function syncSdkSkillsIfNeeded(baseUrl) {
7236
7825
  if (attemptedSync || shouldSkipSkillsSync()) return;
7237
7826
  attemptedSync = true;
7238
7827
  const localVersion = readLocalSkillsVersion(baseUrl);
7239
7828
  const update = await fetchSkillsUpdate(baseUrl, localVersion);
7240
7829
  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");
7830
+ writeSdkSkillsStatusLine("SDK skills changed; syncing deepline-sdk skill...");
7243
7831
  const installed = await runSkillsInstall(baseUrl);
7244
7832
  if (!installed) return;
7245
7833
  writeLocalSkillsVersion(baseUrl, update.remoteVersion);
7246
- progress?.writeLine("SDK skills are up to date.") ?? process.stderr.write("SDK skills are up to date.\n");
7834
+ writeSdkSkillsStatusLine("SDK skills are up to date.");
7247
7835
  }
7248
7836
 
7249
7837
  // src/cli/trace.ts
@@ -7405,4 +7993,3 @@ Output:
7405
7993
  process.exit(process.exitCode ?? 0);
7406
7994
  }
7407
7995
  main();
7408
- //# sourceMappingURL=index.js.map