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
@@ -47,6 +47,16 @@ var ConfigError = class extends DeeplineError {
47
47
  var PROD_URL = "https://code.deepline.com";
48
48
  var DEFAULT_TIMEOUT = 6e4;
49
49
  var DEFAULT_MAX_RETRIES = 3;
50
+ var ACTIVE_DEEPLINE_ENV_FILE = ".env.deepline";
51
+ function isProdBaseUrl(baseUrl) {
52
+ return baseUrl.trim().replace(/\/$/, "") === PROD_URL;
53
+ }
54
+ function profileNameForBaseUrl(baseUrl) {
55
+ return isProdBaseUrl(baseUrl) ? "prod" : "dev";
56
+ }
57
+ function projectEnvStartDir() {
58
+ return process.env.DEEPLINE_PROJECT_ENV_DIR?.trim() || process.cwd();
59
+ }
50
60
  function baseUrlSlug(baseUrl) {
51
61
  let url;
52
62
  try {
@@ -82,16 +92,52 @@ function parseEnvFile(filePath) {
82
92
  }
83
93
  return env;
84
94
  }
85
- function findNearestWorktreeEnv(startDir = process.cwd()) {
95
+ function findNearestEnvFile(names, startDir = process.cwd()) {
86
96
  let current = resolve(startDir);
87
97
  while (true) {
88
- const values = parseEnvFile(join(current, ".env.worktree"));
89
- if (Object.keys(values).length > 0) return values;
98
+ for (const name of names) {
99
+ const filePath = join(current, name);
100
+ if (existsSync(filePath)) return filePath;
101
+ }
90
102
  const parent = dirname(current);
91
- if (parent === current) return {};
103
+ if (parent === current) return null;
92
104
  current = parent;
93
105
  }
94
106
  }
107
+ function findNearestEnv(names, startDir = process.cwd()) {
108
+ const filePath = findNearestEnvFile(names, startDir);
109
+ return filePath ? parseEnvFile(filePath) : {};
110
+ }
111
+ function findNearestWorktreeEnv(startDir = process.cwd()) {
112
+ return findNearestEnv([".env.worktree"], startDir);
113
+ }
114
+ function resolveProfileEnvFileNames() {
115
+ const explicitProfile = process.env.DEEPLINE_ENV_PROFILE?.trim() || process.env.DEEPLINE_PROFILE?.trim() || "";
116
+ const names = [];
117
+ if (explicitProfile) names.push(`.env.deepline.${explicitProfile}`);
118
+ const nodeEnv = process.env.NODE_ENV?.trim();
119
+ if (nodeEnv === "production") names.push(".env.deepline.prod");
120
+ else if (nodeEnv === "staging") names.push(".env.deepline.staging");
121
+ names.push(ACTIVE_DEEPLINE_ENV_FILE);
122
+ return names;
123
+ }
124
+ function resolveProjectAppEnvFileNames() {
125
+ const nodeEnv = process.env.NODE_ENV?.trim();
126
+ const names = [];
127
+ if (nodeEnv === "production") names.push(".env.prod");
128
+ if (nodeEnv === "staging") names.push(".env.staging");
129
+ names.push(".env.local", ".env");
130
+ return names;
131
+ }
132
+ function resolveBaseUrlFromEnvValues(env) {
133
+ return env.DEEPLINE_ORIGIN_URL?.trim() || env.DEEPLINE_API_BASE_URL?.trim() || "";
134
+ }
135
+ function loadProjectDeeplineEnv() {
136
+ return findNearestEnv(resolveProfileEnvFileNames(), projectEnvStartDir());
137
+ }
138
+ function loadProjectAppEnv() {
139
+ return findNearestEnv(resolveProjectAppEnvFileNames(), projectEnvStartDir());
140
+ }
95
141
  function normalizeWorktreeBaseUrl(baseUrl, worktreeEnv = findNearestWorktreeEnv()) {
96
142
  const trimmed = baseUrl.trim().replace(/\/$/, "");
97
143
  if (!trimmed) return trimmed;
@@ -143,6 +189,10 @@ function autoDetectBaseUrl() {
143
189
  if (envOrigin) return normalizeWorktreeBaseUrl(envOrigin);
144
190
  const envBase = process.env.DEEPLINE_API_BASE_URL?.trim();
145
191
  if (envBase) return normalizeWorktreeBaseUrl(envBase);
192
+ const projectDeeplineBaseUrl = resolveBaseUrlFromEnvValues(loadProjectDeeplineEnv());
193
+ if (projectDeeplineBaseUrl) return normalizeWorktreeBaseUrl(projectDeeplineBaseUrl);
194
+ const projectAppBaseUrl = resolveBaseUrlFromEnvValues(loadProjectAppEnv());
195
+ if (projectAppBaseUrl) return normalizeWorktreeBaseUrl(projectAppBaseUrl);
146
196
  const worktreeBaseUrl = resolveWorktreeBaseUrl();
147
197
  if (worktreeBaseUrl) return worktreeBaseUrl;
148
198
  const globalEnv = loadGlobalCliEnv();
@@ -154,7 +204,9 @@ function resolveConfig(options) {
154
204
  const requestedBaseUrl = options?.baseUrl?.trim() || autoDetectBaseUrl();
155
205
  const baseUrl = normalizeWorktreeBaseUrl(requestedBaseUrl);
156
206
  const cliEnv = loadCliEnv(baseUrl);
157
- const apiKey = options?.apiKey?.trim() || process.env.DEEPLINE_API_KEY?.trim() || cliEnv.DEEPLINE_API_KEY || "";
207
+ const projectDeeplineEnv = loadProjectDeeplineEnv();
208
+ const projectAppEnv = loadProjectAppEnv();
209
+ const apiKey = options?.apiKey?.trim() || process.env.DEEPLINE_API_KEY?.trim() || projectDeeplineEnv.DEEPLINE_API_KEY || projectAppEnv.DEEPLINE_API_KEY || cliEnv.DEEPLINE_API_KEY || "";
158
210
  if (!apiKey) {
159
211
  throw new ConfigError(
160
212
  `No API key found. Set DEEPLINE_API_KEY env var, pass apiKey option, or run: deepline auth register`
@@ -167,10 +219,32 @@ function resolveConfig(options) {
167
219
  maxRetries: options?.maxRetries ?? DEFAULT_MAX_RETRIES
168
220
  };
169
221
  }
222
+ function mergeEnvFile(filePath, values) {
223
+ const existing = existsSync(filePath) ? parseEnvFile(filePath) : {};
224
+ const merged = { ...existing, ...values };
225
+ const dir = dirname(filePath);
226
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
227
+ const lines = Object.entries(merged).filter(([, value]) => value !== "").map(([key, value]) => `${key}=${value}`);
228
+ writeFileSync(filePath, `${lines.join("\n")}
229
+ `, "utf-8");
230
+ }
231
+ function saveProjectDeeplineEnvValues(baseUrl, values, startDir = projectEnvStartDir()) {
232
+ const root = resolve(startDir);
233
+ const profile = profileNameForBaseUrl(baseUrl);
234
+ const files = [
235
+ join(root, ACTIVE_DEEPLINE_ENV_FILE),
236
+ join(root, `.env.deepline.${profile}`)
237
+ ];
238
+ if (profile === "dev") files.push(join(root, ".env"));
239
+ for (const filePath of files) {
240
+ mergeEnvFile(filePath, values);
241
+ }
242
+ return files;
243
+ }
170
244
 
171
245
  // src/version.ts
172
- var SDK_VERSION = "0.1.12";
173
- var SDK_API_CONTRACT = "2026-04-plays-v1";
246
+ var SDK_VERSION = "0.1.19";
247
+ var SDK_API_CONTRACT = "2026-05-runs-v2";
174
248
 
175
249
  // ../shared_libs/play-runtime/coordinator-headers.ts
176
250
  var COORDINATOR_INTERNAL_TOKEN_HEADER = "x-deepline-internal-token";
@@ -363,8 +437,12 @@ var HttpClient = class {
363
437
  * @param path - API path
364
438
  * @param body - Request body (will be JSON-serialized)
365
439
  */
366
- async post(path, body) {
367
- return this.request(path, { method: "POST", body });
440
+ async post(path, body, headers) {
441
+ return this.request(path, {
442
+ method: "POST",
443
+ body,
444
+ headers
445
+ });
368
446
  }
369
447
  /**
370
448
  * Send a DELETE request.
@@ -444,6 +522,7 @@ function sleep(ms) {
444
522
 
445
523
  // src/client.ts
446
524
  var TERMINAL_PLAY_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
525
+ var INCLUDE_TOOL_METADATA_HEADER = "x-deepline-include-tool-metadata";
447
526
  function isRecord(value) {
448
527
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
449
528
  }
@@ -476,6 +555,7 @@ function mapLegacyTemporalStatus(status) {
476
555
  var DeeplineClient = class {
477
556
  http;
478
557
  config;
558
+ runs;
479
559
  /**
480
560
  * @param options - Optional overrides for API key, base URL, timeout, and retries.
481
561
  * @throws {@link ConfigError} if no API key can be resolved from any source.
@@ -483,6 +563,13 @@ var DeeplineClient = class {
483
563
  constructor(options) {
484
564
  this.config = resolveConfig(options);
485
565
  this.http = new HttpClient(this.config);
566
+ this.runs = {
567
+ get: (runId) => this.getRunStatus(runId),
568
+ list: (options2) => this.listRuns(options2),
569
+ tail: (runId, options2) => this.tailRun(runId, options2),
570
+ logs: (runId, options2) => this.getRunLogs(runId, options2),
571
+ stop: (runId, options2) => this.stopRun(runId, options2)
572
+ };
486
573
  }
487
574
  /** The resolved base URL this client is targeting (e.g. `"http://localhost:3000"`). */
488
575
  get baseUrl() {
@@ -571,6 +658,31 @@ var DeeplineClient = class {
571
658
  );
572
659
  return res.tools;
573
660
  }
661
+ /**
662
+ * Search available tools using Deepline's ranked backend search.
663
+ *
664
+ * This is the same discovery surface used by the legacy CLI: it ranks across
665
+ * tool metadata, categories, agent guidance, and input schema fields.
666
+ */
667
+ async searchTools(options = {}) {
668
+ const params = new URLSearchParams();
669
+ const query = options.query?.trim() ?? "";
670
+ params.set("q", query);
671
+ params.set(
672
+ "include_search_debug",
673
+ options.includeSearchDebug ? "true" : "false"
674
+ );
675
+ params.set("search_mode", options.searchMode ?? "v2");
676
+ if (options.categories?.trim()) {
677
+ params.set("categories", options.categories.trim());
678
+ }
679
+ if (options.searchTerms?.trim()) {
680
+ params.set("search_terms", options.searchTerms.trim());
681
+ }
682
+ return this.http.get(
683
+ `/api/v2/integrations/list?${params.toString()}`
684
+ );
685
+ }
574
686
  /**
575
687
  * Get detailed metadata for a single tool.
576
688
  *
@@ -606,12 +718,17 @@ var DeeplineClient = class {
606
718
  * Top-level fields such as `status`, `job_id`, and `billing` describe the
607
719
  * Deepline execution.
608
720
  */
609
- async executeTool(toolId, input) {
721
+ async executeTool(toolId, input, options) {
722
+ const headers = options?.includeToolMetadata ? { [INCLUDE_TOOL_METADATA_HEADER]: "true" } : void 0;
610
723
  return this.http.post(
611
724
  `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
612
- { payload: input }
725
+ { payload: input },
726
+ headers
613
727
  );
614
728
  }
729
+ async executeToolRaw(toolId, input, options) {
730
+ return this.executeTool(toolId, input, options);
731
+ }
615
732
  async queryCustomerDb(input) {
616
733
  return this.http.post("/api/v2/db/query", {
617
734
  sql: input.sql,
@@ -660,6 +777,7 @@ var DeeplineClient = class {
660
777
  ...request.revisionId ? { revisionId: request.revisionId } : {},
661
778
  ...request.artifactStorageKey ? { artifactStorageKey: request.artifactStorageKey } : {},
662
779
  ...request.sourceCode ? { sourceCode: request.sourceCode } : {},
780
+ ...request.sourceFiles ? { sourceFiles: request.sourceFiles } : {},
663
781
  ..."staticPipeline" in request ? { staticPipeline: request.staticPipeline } : {},
664
782
  ...request.artifactHash ? { artifactHash: request.artifactHash } : {},
665
783
  ...request.graphHash ? { graphHash: request.graphHash } : {},
@@ -684,6 +802,7 @@ var DeeplineClient = class {
684
802
  ...request.revisionId ? { revisionId: request.revisionId } : {},
685
803
  ...request.artifactStorageKey ? { artifactStorageKey: request.artifactStorageKey } : {},
686
804
  ...request.sourceCode ? { sourceCode: request.sourceCode } : {},
805
+ ...request.sourceFiles ? { sourceFiles: request.sourceFiles } : {},
687
806
  ..."staticPipeline" in request ? { staticPipeline: request.staticPipeline } : {},
688
807
  ...request.artifactHash ? { artifactHash: request.artifactHash } : {},
689
808
  ...request.graphHash ? { graphHash: request.graphHash } : {},
@@ -720,6 +839,7 @@ var DeeplineClient = class {
720
839
  const compilerManifest = input.compilerManifest ?? await this.compilePlayManifest({
721
840
  name: input.name,
722
841
  sourceCode: input.sourceCode,
842
+ sourceFiles: input.sourceFiles,
723
843
  artifact: input.artifact
724
844
  });
725
845
  return this.http.post("/api/v2/plays/artifacts", {
@@ -734,6 +854,7 @@ var DeeplineClient = class {
734
854
  compilerManifest: artifact.compilerManifest ?? await this.compilePlayManifest({
735
855
  name: artifact.name,
736
856
  sourceCode: artifact.sourceCode,
857
+ sourceFiles: artifact.sourceFiles,
737
858
  artifact: artifact.artifact
738
859
  })
739
860
  }))
@@ -760,11 +881,13 @@ var DeeplineClient = class {
760
881
  const compilerManifest = input.compilerManifest ?? await this.compilePlayManifest({
761
882
  name: input.name,
762
883
  sourceCode: input.sourceCode,
884
+ sourceFiles: input.sourceFiles,
763
885
  artifact: input.artifact
764
886
  });
765
887
  const registeredArtifact = await this.registerPlayArtifact({
766
888
  name: input.name,
767
889
  sourceCode: input.sourceCode,
890
+ sourceFiles: input.sourceFiles,
768
891
  artifact: input.artifact,
769
892
  compilerManifest,
770
893
  publish: false
@@ -824,11 +947,13 @@ var DeeplineClient = class {
824
947
  const compilerManifest = options?.compilerManifest ?? await this.compilePlayManifest({
825
948
  name,
826
949
  sourceCode,
950
+ sourceFiles: options?.sourceFiles,
827
951
  artifact
828
952
  });
829
953
  const registeredArtifact = await this.registerPlayArtifact({
830
954
  name,
831
955
  sourceCode,
956
+ sourceFiles: options?.sourceFiles,
832
957
  artifact,
833
958
  compilerManifest,
834
959
  publish: false
@@ -1012,6 +1137,112 @@ var DeeplineClient = class {
1012
1137
  );
1013
1138
  return response.runs ?? [];
1014
1139
  }
1140
+ /**
1141
+ * Get a run by id using the public runs resource model.
1142
+ *
1143
+ * This is the SDK equivalent of:
1144
+ *
1145
+ * ```bash
1146
+ * deepline runs get <run-id> --json
1147
+ * ```
1148
+ */
1149
+ async getRunStatus(runId) {
1150
+ const response = await this.http.get(
1151
+ `/api/v2/runs/${encodeURIComponent(runId)}`
1152
+ );
1153
+ return normalizePlayStatus(response);
1154
+ }
1155
+ /**
1156
+ * List play runs using the public runs resource model.
1157
+ *
1158
+ * This is the SDK equivalent of:
1159
+ *
1160
+ * ```bash
1161
+ * deepline runs list --play <play-name> --status failed --json
1162
+ * ```
1163
+ */
1164
+ async listRuns(options) {
1165
+ const playName = options.play.trim();
1166
+ if (!playName) {
1167
+ throw new Error("runs.list requires options.play.");
1168
+ }
1169
+ const params = new URLSearchParams({ play: playName });
1170
+ const status = options.status?.trim();
1171
+ if (status) {
1172
+ params.set("status", status);
1173
+ }
1174
+ const response = await this.http.get(
1175
+ `/api/v2/runs?${params.toString()}`
1176
+ );
1177
+ return response.runs ?? [];
1178
+ }
1179
+ /**
1180
+ * Fetch the lightweight tail status for a run using the public runs resource model.
1181
+ *
1182
+ * This is the SDK equivalent of:
1183
+ *
1184
+ * ```bash
1185
+ * deepline runs tail <run-id> --json
1186
+ * ```
1187
+ */
1188
+ async tailRun(runId, options) {
1189
+ const afterLogIndex = typeof options?.afterLogIndex === "number" ? options.afterLogIndex : typeof options?.cursor === "number" ? options.cursor : typeof options?.cursor === "string" && options.cursor.trim() ? Number(options.cursor) : void 0;
1190
+ const params = new URLSearchParams();
1191
+ if (Number.isFinite(afterLogIndex)) {
1192
+ params.set("afterLogIndex", String(Number(afterLogIndex)));
1193
+ }
1194
+ if (typeof options?.waitMs === "number") {
1195
+ params.set("waitMs", String(options.waitMs));
1196
+ }
1197
+ if (options?.terminalOnly) {
1198
+ params.set("terminalOnly", "true");
1199
+ }
1200
+ const suffix = params.toString() ? `?${params.toString()}` : "";
1201
+ const response = await this.http.get(
1202
+ `/api/v2/runs/${encodeURIComponent(runId)}/tail${suffix}`
1203
+ );
1204
+ return normalizePlayStatus(response);
1205
+ }
1206
+ /**
1207
+ * Fetch persisted logs for a run using the public runs resource model.
1208
+ *
1209
+ * This is the SDK equivalent of:
1210
+ *
1211
+ * ```bash
1212
+ * deepline runs logs <run-id> --limit 200 --json
1213
+ * ```
1214
+ */
1215
+ async getRunLogs(runId, options) {
1216
+ const status = await this.getRunStatus(runId);
1217
+ const logs = status.progress?.logs ?? [];
1218
+ const limit = typeof options?.limit === "number" && Number.isFinite(options.limit) ? Math.max(0, Math.trunc(options.limit)) : 200;
1219
+ const entries = logs.slice(Math.max(0, logs.length - limit));
1220
+ return {
1221
+ runId: status.runId,
1222
+ totalCount: logs.length,
1223
+ returnedCount: entries.length,
1224
+ firstSequence: logs.length === 0 ? null : logs.length - entries.length + 1,
1225
+ lastSequence: logs.length === 0 ? null : logs.length,
1226
+ truncated: logs.length > entries.length,
1227
+ hasMore: logs.length > entries.length,
1228
+ entries
1229
+ };
1230
+ }
1231
+ /**
1232
+ * Stop a run by id using the public runs resource model.
1233
+ *
1234
+ * This is the SDK equivalent of:
1235
+ *
1236
+ * ```bash
1237
+ * deepline runs stop <run-id> --reason "stale lock" --json
1238
+ * ```
1239
+ */
1240
+ async stopRun(runId, options) {
1241
+ return this.http.post(
1242
+ `/api/v2/runs/${encodeURIComponent(runId)}/stop`,
1243
+ options?.reason ? { reason: options.reason } : {}
1244
+ );
1245
+ }
1015
1246
  async listPlays() {
1016
1247
  const response = await this.http.get(
1017
1248
  "/api/v2/plays"
@@ -1398,6 +1629,7 @@ function saveEnvValues(values, baseUrl) {
1398
1629
  const merged = { ...existing, ...values };
1399
1630
  const lines = Object.entries(merged).filter(([, v]) => v !== "").map(([k, v]) => `${k}=${v}`);
1400
1631
  writeFileSync2(filePath, lines.join("\n") + "\n", "utf-8");
1632
+ saveProjectDeeplineEnvValues(baseUrl, values);
1401
1633
  }
1402
1634
  async function httpJson(method, url, apiKey, body) {
1403
1635
  const headers = { "Content-Type": "application/json" };
@@ -1998,6 +2230,9 @@ function rowArray(value) {
1998
2230
  function readNumber(value) {
1999
2231
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : null;
2000
2232
  }
2233
+ function numericStat(value) {
2234
+ return readNumber(value) ?? 0;
2235
+ }
2001
2236
  function inferColumns(rows) {
2002
2237
  const columns = [];
2003
2238
  const seen = /* @__PURE__ */ new Set();
@@ -2077,6 +2312,31 @@ function extractCanonicalRowsInfo(statusOrResult) {
2077
2312
  function percentText(numerator, denominator) {
2078
2313
  return denominator > 0 ? `${numerator}/${denominator} (${Math.round(100 * numerator / denominator)}%)` : "0/0 (0%)";
2079
2314
  }
2315
+ function isDatasetExecutionStatsInput(value) {
2316
+ return isRecord2(value) && isRecord2(value.columnStats) && Object.values(value.columnStats).every(isRecord2);
2317
+ }
2318
+ function extractDatasetExecutionStats(statusOrResult) {
2319
+ if (!isRecord2(statusOrResult)) {
2320
+ return null;
2321
+ }
2322
+ const direct = statusOrResult.dataset_execution_stats;
2323
+ if (isDatasetExecutionStatsInput(direct)) {
2324
+ return direct;
2325
+ }
2326
+ const nested = isRecord2(statusOrResult.result) ? statusOrResult.result.dataset_execution_stats : null;
2327
+ return isDatasetExecutionStatsInput(nested) ? nested : null;
2328
+ }
2329
+ function formatExecutionStats(raw, denominator) {
2330
+ return {
2331
+ queued: percentText(numericStat(raw.queued), denominator),
2332
+ running: percentText(numericStat(raw.running), denominator),
2333
+ "completed:executed": percentText(numericStat(raw.completed), denominator),
2334
+ "completed:reused": percentText(numericStat(raw.cached), denominator),
2335
+ "skipped:condition": percentText(numericStat(raw.skipped), denominator),
2336
+ "skipped:missed": percentText(numericStat(raw.missed), denominator),
2337
+ failed: percentText(numericStat(raw.failed), denominator)
2338
+ };
2339
+ }
2080
2340
  function countPercentText(count, denominator) {
2081
2341
  return denominator > 0 ? `${count} (${Math.round(100 * count / denominator)}%)` : "0 (0%)";
2082
2342
  }
@@ -2169,7 +2429,7 @@ function compactCell(value) {
2169
2429
  }
2170
2430
  return compactScalar(parsed, 120);
2171
2431
  }
2172
- function buildDatasetStats(rows, totalRows = rows.length, columns = inferColumns(rows)) {
2432
+ function buildDatasetStats(rows, totalRows = rows.length, columns = inferColumns(rows), executionStats) {
2173
2433
  const sanitized = sanitizeCsvProjectionInfo({ rows, columns });
2174
2434
  const columnStats = {};
2175
2435
  for (const column of sanitized.columns) {
@@ -2197,6 +2457,10 @@ function buildDatasetStats(rows, totalRows = rows.length, columns = inferColumns
2197
2457
  non_empty: percentText(nonEmpty, denominator),
2198
2458
  unique: valueCounts.size
2199
2459
  };
2460
+ const rawExecutionStats = executionStats?.columnStats[column];
2461
+ if (rawExecutionStats) {
2462
+ stat3.execution = formatExecutionStats(rawExecutionStats, totalRows);
2463
+ }
2200
2464
  if (sampleValue !== void 0 && sampleValueType) {
2201
2465
  stat3.sample_value = sampleValue;
2202
2466
  stat3.sample_type = sampleValueType;
@@ -2549,7 +2813,6 @@ import { tmpdir } from "os";
2549
2813
  import { basename, dirname as dirname3, extname, isAbsolute, join as join3, resolve as resolve4 } from "path";
2550
2814
  import { builtinModules, createRequire } from "module";
2551
2815
  import { build } from "esbuild";
2552
- import ts from "typescript";
2553
2816
 
2554
2817
  // ../shared_libs/play-runtime/backend.ts
2555
2818
  var PLAY_RUNTIME_BACKENDS = {
@@ -2633,56 +2896,6 @@ function formatEsbuildMessage(message) {
2633
2896
  const location = message.location ? `${message.location.file}:${message.location.line}:${message.location.column}` : null;
2634
2897
  return location ? `${location} ${message.text}` : message.text;
2635
2898
  }
2636
- function formatTypeScriptDiagnostic(diagnostic) {
2637
- if (!diagnostic.file) {
2638
- const message2 = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n").trim();
2639
- return message2 || null;
2640
- }
2641
- const start = diagnostic.start ?? 0;
2642
- const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(start);
2643
- const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n").trim();
2644
- if (!message) {
2645
- return null;
2646
- }
2647
- return `${diagnostic.file.fileName}:${line + 1}:${character + 1} ${message}`;
2648
- }
2649
- function resolveBundledTypeRoots() {
2650
- try {
2651
- return [dirname3(dirname3(playArtifactRequire.resolve("@types/node/package.json")))];
2652
- } catch {
2653
- return [];
2654
- }
2655
- }
2656
- function typecheckPlaySource(input, adapter) {
2657
- const rootNames = Array.from(
2658
- /* @__PURE__ */ new Set([
2659
- ...input.importPolicy.localFiles,
2660
- ...input.importedPlayDependencies.map((dependency) => dependency.filePath)
2661
- ])
2662
- );
2663
- const sdkTypesPath = adapter.sdkTypesEntryFile ?? adapter.sdkEntryFile;
2664
- const program = ts.createProgram(rootNames, {
2665
- target: ts.ScriptTarget.ES2023,
2666
- // SDK source uses fetch/RequestInit/URL and node-aware config helpers.
2667
- // The play runtime import policy below still bans Node modules from play
2668
- // source for workers_edge bundles.
2669
- lib: ["lib.es2023.d.ts", "lib.dom.d.ts"],
2670
- module: ts.ModuleKind.ESNext,
2671
- moduleResolution: ts.ModuleResolutionKind.Bundler,
2672
- paths: { deepline: [sdkTypesPath] },
2673
- strict: true,
2674
- skipLibCheck: true,
2675
- noEmit: true,
2676
- esModuleInterop: true,
2677
- allowSyntheticDefaultImports: true,
2678
- allowImportingTsExtensions: true,
2679
- allowJs: true,
2680
- resolveJsonModule: true,
2681
- types: ["node"],
2682
- typeRoots: resolveBundledTypeRoots()
2683
- });
2684
- return ts.getPreEmitDiagnostics(program).map(formatTypeScriptDiagnostic).filter((message) => Boolean(message));
2685
- }
2686
2899
  function isLocalSpecifier(specifier) {
2687
2900
  return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/") || specifier.startsWith("file:");
2688
2901
  }
@@ -2706,11 +2919,8 @@ function assertWithinPlayWorkspace(input) {
2706
2919
  if (isPathInsideDirectory(input.resolvedPath, input.workspace.rootDir)) {
2707
2920
  return;
2708
2921
  }
2709
- const position = input.sourceFile.getLineAndCharacterOfPosition(
2710
- input.node.getStart(input.sourceFile)
2711
- );
2712
2922
  throw new Error(
2713
- `${input.importer}:${position.line + 1}:${position.character + 1} Local play imports must stay inside the play workspace (${input.workspace.rootDir}). Import "${input.specifier}" resolved to ${input.resolvedPath}, which crosses into app/backend code. Use the public SDK/API surface or move shared helpers into the play workspace.`
2923
+ `${input.importer}:${input.line}:${input.column} Local play imports must stay inside the play workspace (${input.workspace.rootDir}). Import "${input.specifier}" resolved to ${input.resolvedPath}, which crosses into app/backend code. Use the public SDK/API surface or move shared helpers into the play workspace.`
2714
2924
  );
2715
2925
  }
2716
2926
  function getPackageName(specifier) {
@@ -2720,72 +2930,135 @@ function getPackageName(specifier) {
2720
2930
  }
2721
2931
  return specifier.split("/")[0] ?? specifier;
2722
2932
  }
2723
- function scriptKindForFile(filePath) {
2724
- const extension = extname(filePath).toLowerCase();
2725
- switch (extension) {
2726
- case ".tsx":
2727
- return ts.ScriptKind.TSX;
2728
- case ".jsx":
2729
- return ts.ScriptKind.JSX;
2730
- case ".js":
2731
- case ".mjs":
2732
- case ".cjs":
2733
- return ts.ScriptKind.JS;
2734
- case ".json":
2735
- return ts.ScriptKind.JSON;
2736
- default:
2737
- return ts.ScriptKind.TS;
2738
- }
2739
- }
2740
2933
  function isPlaySourceFile(filePath) {
2741
2934
  return PLAY_SOURCE_FILE_PATTERN.test(filePath);
2742
2935
  }
2743
- function extractStringLiteralProperty(objectLiteral, propertyName) {
2744
- for (const property of objectLiteral.properties) {
2745
- if (!ts.isPropertyAssignment(property)) {
2936
+ function stripCommentsToSpaces(source) {
2937
+ return source.replace(/\/\*[\s\S]*?\*\//g, (match) => match.replace(/[^\n]/g, " ")).replace(
2938
+ /(^|[^:])\/\/.*$/gm,
2939
+ (match, prefix) => prefix + " ".repeat(Math.max(0, match.length - prefix.length))
2940
+ );
2941
+ }
2942
+ function lineAndColumnAt(source, index) {
2943
+ const prefix = source.slice(0, index);
2944
+ const lines = prefix.split("\n");
2945
+ return { line: lines.length, column: lines[lines.length - 1].length + 1 };
2946
+ }
2947
+ function findSourceImportReferences(sourceCode) {
2948
+ const source = stripCommentsToSpaces(sourceCode);
2949
+ const references = [];
2950
+ const addReference = (specifier, specifierIndex, kind) => {
2951
+ if (!specifier) return;
2952
+ const position = lineAndColumnAt(sourceCode, specifierIndex);
2953
+ references.push({
2954
+ specifier,
2955
+ line: position.line,
2956
+ column: position.column,
2957
+ kind
2958
+ });
2959
+ };
2960
+ const staticImportPattern = /\b(?:import|export)\s+(?!type\b)(?:[\s\S]*?\s+from\s*)?(['"])([^'"\n]+)\1/g;
2961
+ for (const match of source.matchAll(staticImportPattern)) {
2962
+ addReference(match[2], match.index + match[0].lastIndexOf(match[1]), "static");
2963
+ }
2964
+ const dynamicImportPattern = /\bimport\s*\(\s*(['"])([^'"\n]+)\1/g;
2965
+ for (const match of source.matchAll(dynamicImportPattern)) {
2966
+ addReference(match[2], match.index + match[0].lastIndexOf(match[1]), "dynamic-import");
2967
+ }
2968
+ const requirePattern = /\brequire\s*\(\s*(['"])([^'"\n]+)\1/g;
2969
+ for (const match of source.matchAll(requirePattern)) {
2970
+ addReference(match[2], match.index + match[0].lastIndexOf(match[1]), "require");
2971
+ }
2972
+ const literalDynamicImportIndexes = new Set(
2973
+ [...source.matchAll(dynamicImportPattern)].map((match) => match.index)
2974
+ );
2975
+ for (const match of source.matchAll(/\bimport\s*\(/g)) {
2976
+ if (literalDynamicImportIndexes.has(match.index)) continue;
2977
+ const position = lineAndColumnAt(sourceCode, match.index);
2978
+ throw new Error(
2979
+ `:${position.line}:${position.column} Dynamic import() is not allowed in plays. Use static imports instead.`
2980
+ );
2981
+ }
2982
+ const literalRequireIndexes = new Set(
2983
+ [...source.matchAll(requirePattern)].map((match) => match.index)
2984
+ );
2985
+ for (const match of source.matchAll(/\brequire\s*\(/g)) {
2986
+ if (literalRequireIndexes.has(match.index)) continue;
2987
+ const position = lineAndColumnAt(sourceCode, match.index);
2988
+ throw new Error(
2989
+ `:${position.line}:${position.column} Dynamic require() is not allowed in plays. Use static imports or require("literal") only.`
2990
+ );
2991
+ }
2992
+ return references.sort(
2993
+ (left, right) => left.line === right.line ? left.column - right.column : left.line - right.line
2994
+ );
2995
+ }
2996
+ function unquoteStringLiteral(literal) {
2997
+ const trimmed = literal.trim();
2998
+ const quote = trimmed[0];
2999
+ if (quote !== '"' && quote !== "'" || trimmed[trimmed.length - 1] !== quote) {
3000
+ return null;
3001
+ }
3002
+ try {
3003
+ return JSON.parse(quote === '"' ? trimmed : `"${trimmed.slice(1, -1).replace(/"/g, '\\"')}"`);
3004
+ } catch {
3005
+ return trimmed.slice(1, -1);
3006
+ }
3007
+ }
3008
+ function findMatchingBrace(source, openIndex) {
3009
+ let depth = 0;
3010
+ let quote = null;
3011
+ let escaped = false;
3012
+ for (let index = openIndex; index < source.length; index += 1) {
3013
+ const char = source[index];
3014
+ if (quote) {
3015
+ if (escaped) {
3016
+ escaped = false;
3017
+ } else if (char === "\\") {
3018
+ escaped = true;
3019
+ } else if (char === quote) {
3020
+ quote = null;
3021
+ }
2746
3022
  continue;
2747
3023
  }
2748
- const name = property.name;
2749
- const matches = ts.isIdentifier(name) && name.text === propertyName || ts.isStringLiteralLike(name) && name.text === propertyName;
2750
- if (!matches) {
3024
+ if (char === '"' || char === "'" || char === "`") {
3025
+ quote = char;
2751
3026
  continue;
2752
3027
  }
2753
- return ts.isStringLiteralLike(property.initializer) ? property.initializer.text.trim() : null;
2754
- }
2755
- return null;
2756
- }
2757
- function extractDefinedPlayName(sourceCode, filePath) {
2758
- const sourceFile = ts.createSourceFile(
2759
- filePath,
2760
- sourceCode,
2761
- ts.ScriptTarget.Latest,
2762
- true,
2763
- scriptKindForFile(filePath)
2764
- );
2765
- let detectedPlayName = null;
2766
- const visit = (node) => {
2767
- if (detectedPlayName) {
2768
- return;
2769
- }
2770
- if (ts.isCallExpression(node)) {
2771
- const expression = node.expression;
2772
- const isDefinePlayCall = ts.isIdentifier(expression) && (expression.text === "definePlay" || expression.text === "defineWorkflow") || ts.isPropertyAccessExpression(expression) && (expression.name.text === "definePlay" || expression.name.text === "defineWorkflow");
2773
- if (isDefinePlayCall) {
2774
- const firstArgument = node.arguments[0];
2775
- if (firstArgument && ts.isStringLiteralLike(firstArgument)) {
2776
- detectedPlayName = firstArgument.text.trim() || null;
2777
- return;
2778
- }
2779
- if (firstArgument && ts.isObjectLiteralExpression(firstArgument)) {
2780
- detectedPlayName = extractStringLiteralProperty(firstArgument, "id");
2781
- return;
2782
- }
3028
+ if (char === "{") depth += 1;
3029
+ if (char === "}") {
3030
+ depth -= 1;
3031
+ if (depth === 0) return index;
3032
+ }
3033
+ }
3034
+ return -1;
3035
+ }
3036
+ function extractDefinedPlayName(sourceCode, _filePath) {
3037
+ const source = stripCommentsToSpaces(sourceCode);
3038
+ const callPattern = /(?:\b[A-Za-z_$][\w$]*\s*\.\s*)?\b(?:definePlay|defineWorkflow)\s*\(/g;
3039
+ for (const match of source.matchAll(callPattern)) {
3040
+ const openParen = match.index + match[0].length - 1;
3041
+ const firstArgStart = openParen + 1;
3042
+ const firstNonSpace = source.slice(firstArgStart).search(/\S/);
3043
+ if (firstNonSpace < 0) continue;
3044
+ const argIndex = firstArgStart + firstNonSpace;
3045
+ const quote = source[argIndex];
3046
+ if (quote === '"' || quote === "'") {
3047
+ const literalMatch = source.slice(argIndex).match(/^(['"])(?:\\.|(?!\1)[\s\S])*\1/);
3048
+ const value = literalMatch ? unquoteStringLiteral(literalMatch[0]) : null;
3049
+ if (value?.trim()) return value.trim();
3050
+ }
3051
+ if (quote === "{") {
3052
+ const closeBrace = findMatchingBrace(source, argIndex);
3053
+ if (closeBrace < 0) continue;
3054
+ const objectSource = source.slice(argIndex + 1, closeBrace);
3055
+ const idMatch = objectSource.match(/(?:^|[,{\s])(?:id|['"]id['"])\s*:\s*(['"])([\s\S]*?)\1/);
3056
+ if (idMatch?.[2]?.trim()) {
3057
+ return idMatch[2].trim();
2783
3058
  }
2784
3059
  }
2785
- ts.forEachChild(node, visit);
2786
- };
2787
- visit(sourceFile);
2788
- return detectedPlayName;
3060
+ }
3061
+ return null;
2789
3062
  }
2790
3063
  function getPackageRequireCandidates(fromFile) {
2791
3064
  const candidates = [
@@ -3095,18 +3368,10 @@ async function analyzeSourceGraph(entryFile, adapter) {
3095
3368
  if (extname(absolutePath).toLowerCase() === ".json") {
3096
3369
  return;
3097
3370
  }
3098
- const sourceFile = ts.createSourceFile(
3099
- absolutePath,
3100
- sourceCode2,
3101
- ts.ScriptTarget.Latest,
3102
- true,
3103
- scriptKindForFile(absolutePath)
3104
- );
3105
- const handleSpecifier = async (specifier, node, kind) => {
3371
+ const handleSpecifier = async (specifier, line, column, kind) => {
3106
3372
  if (kind === "dynamic-import") {
3107
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3108
3373
  throw new Error(
3109
- `${absolutePath}:${position.line + 1}:${position.character + 1} Dynamic import() is not allowed in plays. Use static imports instead.`
3374
+ `${absolutePath}:${line}:${column} Dynamic import() is not allowed in plays. Use static imports instead.`
3110
3375
  );
3111
3376
  }
3112
3377
  if (NODE_BUILTIN_SET.has(specifier)) {
@@ -3120,16 +3385,15 @@ async function analyzeSourceGraph(entryFile, adapter) {
3120
3385
  specifier,
3121
3386
  resolvedPath: resolved,
3122
3387
  workspace,
3123
- sourceFile,
3124
- node
3388
+ line,
3389
+ column
3125
3390
  });
3126
3391
  if (resolved !== absoluteEntryFile && isPlaySourceFile(resolved)) {
3127
3392
  const importedSource = await readFile(resolved, "utf-8");
3128
3393
  const importedPlayName = extractDefinedPlayName(importedSource, resolved);
3129
3394
  if (!importedPlayName) {
3130
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3131
3395
  throw new Error(
3132
- `${absolutePath}:${position.line + 1}:${position.character + 1} Imported play file "${specifier}" must export definePlay(...) so it can be runtime-composed.`
3396
+ `${absolutePath}:${line}:${column} Imported play file "${specifier}" must export definePlay(...) so it can be runtime-composed.`
3133
3397
  );
3134
3398
  }
3135
3399
  importedPlayDependencies.set(resolved, {
@@ -3142,44 +3406,28 @@ async function analyzeSourceGraph(entryFile, adapter) {
3142
3406
  return;
3143
3407
  }
3144
3408
  if (specifier.includes(":")) {
3145
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3146
3409
  throw new Error(
3147
- `${absolutePath}:${position.line + 1}:${position.character + 1} Unsupported import specifier "${specifier}". Allowed imports are relative files, Node builtins, and installed packages.`
3410
+ `${absolutePath}:${line}:${column} Unsupported import specifier "${specifier}". Allowed imports are relative files, Node builtins, and installed packages.`
3148
3411
  );
3149
3412
  }
3150
3413
  const packageImport = resolvePackageImport(specifier, absolutePath, adapter);
3151
3414
  packages.set(packageImport.name, packageImport.version);
3152
3415
  };
3153
- const walk = async (node) => {
3154
- if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier && !(ts.isImportDeclaration(node) && node.importClause?.isTypeOnly || ts.isExportDeclaration(node) && node.isTypeOnly) && ts.isStringLiteralLike(node.moduleSpecifier)) {
3155
- await handleSpecifier(node.moduleSpecifier.text, node, "static");
3156
- }
3157
- if (ts.isCallExpression(node)) {
3158
- if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
3159
- if (node.arguments.length !== 1 || !ts.isStringLiteralLike(node.arguments[0])) {
3160
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3161
- throw new Error(
3162
- `${absolutePath}:${position.line + 1}:${position.character + 1} Dynamic import() is not allowed in plays. Use static imports instead.`
3163
- );
3164
- }
3165
- await handleSpecifier(node.arguments[0].text, node, "dynamic-import");
3166
- }
3167
- if (ts.isIdentifier(node.expression) && node.expression.text === "require") {
3168
- const firstArgument = node.arguments[0];
3169
- if (node.arguments.length !== 1 || !firstArgument || !ts.isStringLiteralLike(firstArgument)) {
3170
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3171
- throw new Error(
3172
- `${absolutePath}:${position.line + 1}:${position.character + 1} Dynamic require() is not allowed in plays. Use static imports or require("literal") only.`
3173
- );
3174
- }
3175
- await handleSpecifier(firstArgument.text, node, "require");
3176
- }
3416
+ try {
3417
+ for (const reference of findSourceImportReferences(sourceCode2)) {
3418
+ await handleSpecifier(
3419
+ reference.specifier,
3420
+ reference.line,
3421
+ reference.column,
3422
+ reference.kind
3423
+ );
3177
3424
  }
3178
- for (const child of node.getChildren(sourceFile)) {
3179
- await walk(child);
3425
+ } catch (error) {
3426
+ if (error instanceof Error && error.message.startsWith(":")) {
3427
+ throw new Error(`${absolutePath}${error.message}`);
3180
3428
  }
3181
- };
3182
- await walk(sourceFile);
3429
+ throw error;
3430
+ }
3183
3431
  };
3184
3432
  await visitFile(absoluteEntryFile);
3185
3433
  const sourceCode = localFiles.get(absoluteEntryFile) ?? "";
@@ -3199,6 +3447,11 @@ async function analyzeSourceGraph(entryFile, adapter) {
3199
3447
  const playName = extractDefinedPlayName(sourceCode, absoluteEntryFile);
3200
3448
  return {
3201
3449
  sourceCode,
3450
+ sourceFiles: Object.fromEntries(
3451
+ [...localFiles.entries()].sort(
3452
+ (left, right) => left[0].localeCompare(right[0])
3453
+ )
3454
+ ),
3202
3455
  sourceHash,
3203
3456
  graphHash,
3204
3457
  importPolicy: {
@@ -3262,8 +3515,7 @@ function normalizeSourceMapForRuntime(sourceMapText) {
3262
3515
  parsed.sourceRoot = void 0;
3263
3516
  return JSON.stringify(parsed);
3264
3517
  }
3265
- function getBundleSizeError(filePath, bundledCode, artifactKind) {
3266
- const bundleBytes = Buffer.byteLength(bundledCode, "utf8");
3518
+ function getBundleSizeErrorForBytes(filePath, bundleBytes, artifactKind) {
3267
3519
  if (bundleBytes > MAX_PLAY_BUNDLE_BYTES) {
3268
3520
  return `${filePath} Play bundle exceeds the 30 MiB limit (${bundleBytes} bytes > ${MAX_PLAY_BUNDLE_BYTES} bytes).`;
3269
3521
  }
@@ -3274,6 +3526,13 @@ function getBundleSizeError(filePath, bundledCode, artifactKind) {
3274
3526
  }
3275
3527
  return null;
3276
3528
  }
3529
+ function getBundleSizeError(filePath, bundledCode, artifactKind) {
3530
+ return getBundleSizeErrorForBytes(
3531
+ filePath,
3532
+ Buffer.byteLength(bundledCode, "utf8"),
3533
+ artifactKind
3534
+ );
3535
+ }
3277
3536
  async function runEsbuildForCjsNode(entryFile, importedPlayDependencies, adapter, exportName) {
3278
3537
  const sdkAliasPlugin = localSdkAliasPlugin(adapter);
3279
3538
  const playProxyPlugin = importedPlayProxyPlugin(importedPlayDependencies);
@@ -3406,6 +3665,23 @@ entry-export:${exportName}`
3406
3665
  workers-harness:${harnessFingerprint}`
3407
3666
  );
3408
3667
  }
3668
+ const typecheckErrors = [
3669
+ ...await adapter.typecheckPlaySource?.({
3670
+ sourceCode: analysis.sourceCode,
3671
+ sourcePath: absolutePath,
3672
+ importedFilePaths: [
3673
+ ...analysis.importPolicy.localFiles,
3674
+ ...analysis.importedPlayDependencies.map((dependency) => dependency.filePath)
3675
+ ]
3676
+ }) ?? []
3677
+ ];
3678
+ if (typecheckErrors.length > 0) {
3679
+ return {
3680
+ success: false,
3681
+ filePath: absolutePath,
3682
+ errors: typecheckErrors
3683
+ };
3684
+ }
3409
3685
  const cachedArtifact = await readArtifactCache(analysis.graphHash, target, adapter);
3410
3686
  const discoveredFiles = await adapter.discoverPackagedLocalFiles(absolutePath);
3411
3687
  if (cachedArtifact) {
@@ -3425,6 +3701,7 @@ workers-harness:${harnessFingerprint}`
3425
3701
  success: true,
3426
3702
  artifact: { ...cachedArtifact, cacheHit: true },
3427
3703
  sourceCode: analysis.sourceCode,
3704
+ sourceFiles: analysis.sourceFiles,
3428
3705
  filePath: absolutePath,
3429
3706
  playName: analysis.playName,
3430
3707
  packagedFiles: discoveredFiles.files,
@@ -3432,24 +3709,6 @@ workers-harness:${harnessFingerprint}`
3432
3709
  importedPlayDependencies: analysis.importedPlayDependencies
3433
3710
  };
3434
3711
  }
3435
- const typecheckErrors = [
3436
- ...adapter.typecheckSdkTypes === false ? [] : typecheckPlaySource(analysis, adapter),
3437
- ...await adapter.typecheckPlaySource?.({
3438
- sourceCode: analysis.sourceCode,
3439
- sourcePath: absolutePath,
3440
- importedFilePaths: [
3441
- ...analysis.importPolicy.localFiles,
3442
- ...analysis.importedPlayDependencies.map((dependency) => dependency.filePath)
3443
- ]
3444
- }) ?? []
3445
- ];
3446
- if (typecheckErrors.length > 0) {
3447
- return {
3448
- success: false,
3449
- filePath: absolutePath,
3450
- errors: typecheckErrors
3451
- };
3452
- }
3453
3712
  const buildOutcome = target === PLAY_ARTIFACT_KINDS.esmWorkers ? await runEsbuildForEsmWorkers(absolutePath, analysis.importedPlayDependencies, adapter, exportName) : await runEsbuildForCjsNode(absolutePath, analysis.importedPlayDependencies, adapter, exportName);
3454
3713
  if (Array.isArray(buildOutcome)) {
3455
3714
  return {
@@ -3499,6 +3758,7 @@ workers-harness:${harnessFingerprint}`
3499
3758
  success: true,
3500
3759
  artifact,
3501
3760
  sourceCode: analysis.sourceCode,
3761
+ sourceFiles: analysis.sourceFiles,
3502
3762
  filePath: absolutePath,
3503
3763
  playName: analysis.playName,
3504
3764
  packagedFiles: discoveredFiles.files,
@@ -3580,7 +3840,6 @@ function resolveExecutionProfile(override) {
3580
3840
  import { createHash as createHash2 } from "crypto";
3581
3841
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
3582
3842
  import { basename as basename2, dirname as dirname4, extname as extname2, isAbsolute as isAbsolute2, join as join4, relative, resolve as resolve5 } from "path";
3583
- import ts2 from "typescript";
3584
3843
  var SOURCE_EXTENSIONS2 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".json"];
3585
3844
  function sha2562(buffer) {
3586
3845
  return createHash2("sha256").update(buffer).digest("hex");
@@ -3592,94 +3851,181 @@ function contentTypeForFile(filePath) {
3592
3851
  if (extension === ".txt") return "text/plain";
3593
3852
  return "application/octet-stream";
3594
3853
  }
3595
- function isCtxCsvCall(node) {
3596
- if (!ts2.isPropertyAccessExpression(node.expression)) {
3597
- return false;
3598
- }
3599
- const target = node.expression.expression;
3600
- return ts2.isIdentifier(target) && (target.text === "ctx" || target.text.endsWith("Ctx")) && node.expression.name.text === "csv";
3601
- }
3602
- function extractSourceFragment(source, node) {
3603
- return source.slice(node.getStart(), node.getEnd()).trim();
3604
- }
3605
- function referencesInputIdentifier(node) {
3606
- if (ts2.isIdentifier(node) && node.text === "input") {
3607
- return true;
3608
- }
3609
- return node.getChildren().some((child) => referencesInputIdentifier(child));
3854
+ function stripCommentsToSpaces2(source) {
3855
+ return source.replace(/\/\*[\s\S]*?\*\//g, (match) => match.replace(/[^\n]/g, " ")).replace(
3856
+ /(^|[^:])\/\/.*$/gm,
3857
+ (match, prefix) => prefix + " ".repeat(Math.max(0, match.length - prefix.length))
3858
+ );
3610
3859
  }
3611
- function isRuntimeInputExpression(node) {
3612
- if (ts2.isPropertyAccessExpression(node)) {
3613
- return ts2.isIdentifier(node.expression) && node.expression.text === "input";
3614
- }
3615
- if (ts2.isElementAccessExpression(node)) {
3616
- return ts2.isIdentifier(node.expression) && node.expression.text === "input";
3617
- }
3618
- if (ts2.isIdentifier(node)) {
3619
- return node.text === "input";
3620
- }
3621
- if (ts2.isParenthesizedExpression(node)) {
3622
- return isRuntimeInputExpression(node.expression);
3860
+ function unquoteStringLiteral2(literal) {
3861
+ const trimmed = literal.trim();
3862
+ const quote = trimmed[0];
3863
+ if (quote !== '"' && quote !== "'" || trimmed[trimmed.length - 1] !== quote) {
3864
+ return null;
3623
3865
  }
3624
- if (ts2.isBinaryExpression(node) && (node.operatorToken.kind === ts2.SyntaxKind.QuestionQuestionToken || node.operatorToken.kind === ts2.SyntaxKind.BarBarToken)) {
3625
- return isRuntimeInputExpression(node.left) || isRuntimeInputExpression(node.right);
3866
+ try {
3867
+ return JSON.parse(quote === '"' ? trimmed : `"${trimmed.slice(1, -1).replace(/"/g, '\\"')}"`);
3868
+ } catch {
3869
+ return trimmed.slice(1, -1);
3626
3870
  }
3627
- if (ts2.isConditionalExpression(node)) {
3628
- return isRuntimeInputExpression(node.condition) || isRuntimeInputExpression(node.whenTrue) || isRuntimeInputExpression(node.whenFalse);
3871
+ }
3872
+ function splitTopLevelPlus(expression) {
3873
+ const parts = [];
3874
+ let start = 0;
3875
+ let depth = 0;
3876
+ let quote = null;
3877
+ let escaped = false;
3878
+ for (let index = 0; index < expression.length; index += 1) {
3879
+ const char = expression[index];
3880
+ if (quote) {
3881
+ if (escaped) {
3882
+ escaped = false;
3883
+ } else if (char === "\\") {
3884
+ escaped = true;
3885
+ } else if (char === quote) {
3886
+ quote = null;
3887
+ }
3888
+ continue;
3889
+ }
3890
+ if (char === '"' || char === "'" || char === "`") {
3891
+ quote = char;
3892
+ continue;
3893
+ }
3894
+ if (char === "(" || char === "[" || char === "{") depth += 1;
3895
+ if (char === ")" || char === "]" || char === "}") depth -= 1;
3896
+ if (char === "+" && depth === 0) {
3897
+ parts.push(expression.slice(start, index));
3898
+ start = index + 1;
3899
+ }
3629
3900
  }
3630
- return referencesInputIdentifier(node);
3901
+ if (parts.length === 0) return null;
3902
+ parts.push(expression.slice(start));
3903
+ return parts;
3631
3904
  }
3632
- function resolveStringExpression(node, constants) {
3633
- if (ts2.isStringLiteralLike(node) || ts2.isNoSubstitutionTemplateLiteral(node)) {
3634
- return node.text;
3905
+ function stripOuterParens(expression) {
3906
+ let value = expression.trim();
3907
+ while (value.startsWith("(") && value.endsWith(")")) {
3908
+ value = value.slice(1, -1).trim();
3635
3909
  }
3636
- if (ts2.isParenthesizedExpression(node)) {
3637
- return resolveStringExpression(node.expression, constants);
3910
+ return value;
3911
+ }
3912
+ function isRuntimeInputExpression(expression) {
3913
+ return /(^|[^\w$])input([^\w$]|$)/.test(expression);
3914
+ }
3915
+ function resolveStringExpression(expression, constants) {
3916
+ const value = stripOuterParens(expression);
3917
+ if (/^(['"])(?:\\.|(?!\1)[\s\S])*\1$/.test(value)) {
3918
+ return unquoteStringLiteral2(value);
3638
3919
  }
3639
- if (ts2.isIdentifier(node)) {
3640
- return constants.get(node.text) ?? null;
3920
+ if (/^`(?:\\.|[^`$]|\$(?!\{))*`$/.test(value)) {
3921
+ return value.slice(1, -1);
3641
3922
  }
3642
- if (ts2.isTemplateExpression(node)) {
3643
- let value = node.head.text;
3644
- for (const span of node.templateSpans) {
3645
- const resolved = resolveStringExpression(span.expression, constants);
3646
- if (resolved == null) {
3647
- return null;
3648
- }
3649
- value += resolved + span.literal.text;
3650
- }
3651
- return value;
3923
+ if (/^[A-Za-z_$][\w$]*$/.test(value)) {
3924
+ return constants.get(value) ?? null;
3652
3925
  }
3653
- if (ts2.isBinaryExpression(node) && node.operatorToken.kind === ts2.SyntaxKind.PlusToken) {
3654
- const left = resolveStringExpression(node.left, constants);
3655
- const right = resolveStringExpression(node.right, constants);
3656
- if (left == null || right == null) {
3657
- return null;
3658
- }
3659
- return left + right;
3926
+ const parts = splitTopLevelPlus(value);
3927
+ if (parts) {
3928
+ const resolved = parts.map((part) => resolveStringExpression(part, constants));
3929
+ return resolved.every((part) => part != null) ? resolved.join("") : null;
3660
3930
  }
3661
3931
  return null;
3662
3932
  }
3663
- function collectTopLevelStringConstants(sourceFile) {
3933
+ function collectTopLevelStringConstants(sourceCode) {
3664
3934
  const constants = /* @__PURE__ */ new Map();
3665
- for (const statement of sourceFile.statements) {
3666
- if (!ts2.isVariableStatement(statement)) {
3935
+ const source = stripCommentsToSpaces2(sourceCode);
3936
+ for (const match of source.matchAll(/(?:^|\n)\s*const\s+([A-Za-z_$][\w$]*)\s*=\s*([^;\n]+)/g)) {
3937
+ const resolved = resolveStringExpression(match[2], constants);
3938
+ if (resolved != null) {
3939
+ constants.set(match[1], resolved);
3940
+ }
3941
+ }
3942
+ return constants;
3943
+ }
3944
+ function findMatchingGenericEnd(source, openIndex) {
3945
+ let depth = 0;
3946
+ let quote = null;
3947
+ let escaped = false;
3948
+ for (let index = openIndex; index < source.length; index += 1) {
3949
+ const char = source[index];
3950
+ if (quote) {
3951
+ if (escaped) {
3952
+ escaped = false;
3953
+ } else if (char === "\\") {
3954
+ escaped = true;
3955
+ } else if (char === quote) {
3956
+ quote = null;
3957
+ }
3667
3958
  continue;
3668
3959
  }
3669
- if (!(statement.declarationList.flags & ts2.NodeFlags.Const)) {
3960
+ if (char === '"' || char === "'" || char === "`") {
3961
+ quote = char;
3670
3962
  continue;
3671
3963
  }
3672
- for (const declaration of statement.declarationList.declarations) {
3673
- if (!ts2.isIdentifier(declaration.name) || !declaration.initializer) {
3674
- continue;
3675
- }
3676
- const resolved = resolveStringExpression(declaration.initializer, constants);
3677
- if (resolved != null) {
3678
- constants.set(declaration.name.text, resolved);
3964
+ if (char === "<") depth += 1;
3965
+ if (char === ">") {
3966
+ depth -= 1;
3967
+ if (depth === 0) return index;
3968
+ }
3969
+ }
3970
+ return -1;
3971
+ }
3972
+ function findCallOpenParen(source, afterCsvIndex) {
3973
+ let index = afterCsvIndex;
3974
+ while (/\s/.test(source[index] ?? "")) index += 1;
3975
+ if (source[index] === "<") {
3976
+ const genericEnd = findMatchingGenericEnd(source, index);
3977
+ if (genericEnd < 0) return -1;
3978
+ index = genericEnd + 1;
3979
+ while (/\s/.test(source[index] ?? "")) index += 1;
3980
+ }
3981
+ return source[index] === "(" ? index : -1;
3982
+ }
3983
+ function firstCallArgument(source, openParen) {
3984
+ let depth = 0;
3985
+ let quote = null;
3986
+ let escaped = false;
3987
+ const start = openParen + 1;
3988
+ for (let index = start; index < source.length; index += 1) {
3989
+ const char = source[index];
3990
+ if (quote) {
3991
+ if (escaped) {
3992
+ escaped = false;
3993
+ } else if (char === "\\") {
3994
+ escaped = true;
3995
+ } else if (char === quote) {
3996
+ quote = null;
3679
3997
  }
3998
+ continue;
3999
+ }
4000
+ if (char === '"' || char === "'" || char === "`") {
4001
+ quote = char;
4002
+ continue;
4003
+ }
4004
+ if (char === "(" || char === "[" || char === "{") depth += 1;
4005
+ if (char === ")" && depth === 0) {
4006
+ const text = source.slice(start, index).trim();
4007
+ return text ? { text, start, end: index } : null;
3680
4008
  }
4009
+ if (char === "," && depth === 0) {
4010
+ const text = source.slice(start, index).trim();
4011
+ return text ? { text, start, end: index } : null;
4012
+ }
4013
+ if (char === ")" || char === "]" || char === "}") depth -= 1;
3681
4014
  }
3682
- return constants;
4015
+ return null;
4016
+ }
4017
+ function localImportSpecifiers(sourceCode) {
4018
+ const source = stripCommentsToSpaces2(sourceCode);
4019
+ const specifiers = [];
4020
+ for (const match of source.matchAll(
4021
+ /\b(?:import|export)\s+(?!type\b)(?:[\s\S]*?\s+from\s*)?['"]([^'"]+)['"]/g
4022
+ )) {
4023
+ if (match[1]?.startsWith(".")) specifiers.push(match[1]);
4024
+ }
4025
+ for (const match of source.matchAll(/\brequire\s*\(\s*(['"])(\.[^'"]*)\1\s*\)/g)) {
4026
+ specifiers.push(match[2]);
4027
+ }
4028
+ return specifiers;
3683
4029
  }
3684
4030
  async function fileExists2(filePath) {
3685
4031
  try {
@@ -3724,69 +4070,60 @@ async function discoverPackagedLocalFiles(entryFile) {
3724
4070
  }
3725
4071
  visitedFiles.add(absolutePath);
3726
4072
  const sourceCode = await readFile2(absolutePath, "utf-8");
3727
- const sourceFile = ts2.createSourceFile(
3728
- absolutePath,
3729
- sourceCode,
3730
- ts2.ScriptTarget.Latest,
3731
- true,
3732
- ts2.ScriptKind.TS
3733
- );
3734
- const constants = collectTopLevelStringConstants(sourceFile);
4073
+ const scanSource = stripCommentsToSpaces2(sourceCode);
4074
+ const constants = collectTopLevelStringConstants(sourceCode);
3735
4075
  const childVisits = [];
3736
- const visitNode = async (node) => {
3737
- if (ts2.isCallExpression(node) && isCtxCsvCall(node)) {
3738
- const argument = node.arguments[0];
3739
- if (!argument) {
4076
+ for (const match of scanSource.matchAll(/\b([A-Za-z_$][\w$]*)\s*\.\s*csv\b/g)) {
4077
+ const target = match[1];
4078
+ if (target !== "ctx" && !target.endsWith("Ctx")) {
4079
+ continue;
4080
+ }
4081
+ const openParen = findCallOpenParen(scanSource, match.index + match[0].length);
4082
+ if (openParen < 0) {
4083
+ continue;
4084
+ }
4085
+ const argument = firstCallArgument(scanSource, openParen);
4086
+ if (!argument) {
4087
+ unresolved.push({
4088
+ sourceFragment: "ctx.csv()",
4089
+ message: "ctx.csv() requires a file path string or input reference."
4090
+ });
4091
+ } else if (!isRuntimeInputExpression(argument.text)) {
4092
+ const resolvedPath = resolveStringExpression(argument.text, constants);
4093
+ if (resolvedPath == null) {
3740
4094
  unresolved.push({
3741
- sourceFragment: "ctx.csv()",
3742
- message: "ctx.csv() requires a file path string or input reference."
4095
+ sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
4096
+ message: "Could not resolve this ctx.csv(...) path at submit time. Use a string literal, a top-level const string, or pass a runtime input like input.file."
3743
4097
  });
3744
- } else if (!isRuntimeInputExpression(argument)) {
3745
- const resolvedPath = resolveStringExpression(argument, constants);
3746
- if (resolvedPath == null) {
4098
+ } else {
4099
+ const absoluteCsvPath = resolve5(dirname4(absolutePath), resolvedPath);
4100
+ if (isAbsolute2(resolvedPath) || !isPathInsideDirectory2(absoluteCsvPath, packagingRoot)) {
3747
4101
  unresolved.push({
3748
- sourceFragment: extractSourceFragment(sourceCode, argument),
3749
- 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."
3750
- });
3751
- } else {
3752
- const absoluteCsvPath = resolve5(dirname4(absolutePath), resolvedPath);
3753
- if (isAbsolute2(resolvedPath) || !isPathInsideDirectory2(absoluteCsvPath, packagingRoot)) {
3754
- unresolved.push({
3755
- sourceFragment: extractSourceFragment(sourceCode, argument),
3756
- message: "ctx.csv(...) packaged file paths must be relative paths inside the play directory. Pass external files at runtime with input.file instead."
3757
- });
3758
- return;
3759
- }
3760
- const buffer = await readFile2(absoluteCsvPath);
3761
- const stats = await stat2(absoluteCsvPath);
3762
- files.set(absoluteCsvPath, {
3763
- sourceFragment: extractSourceFragment(sourceCode, argument),
3764
- logicalPath: resolvedPath,
3765
- absolutePath: absoluteCsvPath,
3766
- bytes: stats.size,
3767
- contentHash: sha2562(buffer),
3768
- contentType: contentTypeForFile(absoluteCsvPath)
4102
+ sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
4103
+ message: "ctx.csv(...) packaged file paths must be relative paths inside the play directory. Pass external files at runtime with input.file instead."
3769
4104
  });
4105
+ continue;
3770
4106
  }
4107
+ const buffer = await readFile2(absoluteCsvPath);
4108
+ const stats = await stat2(absoluteCsvPath);
4109
+ files.set(absoluteCsvPath, {
4110
+ sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
4111
+ logicalPath: resolvedPath,
4112
+ absolutePath: absoluteCsvPath,
4113
+ bytes: stats.size,
4114
+ contentHash: sha2562(buffer),
4115
+ contentType: contentTypeForFile(absoluteCsvPath)
4116
+ });
3771
4117
  }
3772
4118
  }
3773
- if (ts2.isImportDeclaration(node) && !node.importClause?.isTypeOnly && ts2.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text.startsWith(".")) {
3774
- childVisits.push(
3775
- resolveLocalImport2(absolutePath, node.moduleSpecifier.text).then(
3776
- (resolvedImport) => visitSourceFile(resolvedImport)
3777
- )
3778
- );
3779
- }
3780
- if (ts2.isCallExpression(node) && ts2.isIdentifier(node.expression) && node.expression.text === "require" && node.arguments.length === 1 && ts2.isStringLiteral(node.arguments[0]) && node.arguments[0].text.startsWith(".")) {
3781
- childVisits.push(
3782
- resolveLocalImport2(absolutePath, node.arguments[0].text).then(
3783
- (resolvedImport) => visitSourceFile(resolvedImport)
3784
- )
3785
- );
3786
- }
3787
- await Promise.all(node.getChildren(sourceFile).map((child) => visitNode(child)));
3788
- };
3789
- await visitNode(sourceFile);
4119
+ }
4120
+ for (const specifier of localImportSpecifiers(sourceCode)) {
4121
+ childVisits.push(
4122
+ resolveLocalImport2(absolutePath, specifier).then(
4123
+ (resolvedImport) => visitSourceFile(resolvedImport)
4124
+ )
4125
+ );
4126
+ }
3790
4127
  await Promise.all(childVisits);
3791
4128
  };
3792
4129
  await visitSourceFile(absoluteEntryFile);
@@ -3797,7 +4134,7 @@ async function discoverPackagedLocalFiles(entryFile) {
3797
4134
  }
3798
4135
 
3799
4136
  // src/plays/bundle-play-file.ts
3800
- var PLAY_BUNDLE_CACHE_VERSION2 = 24;
4137
+ var PLAY_BUNDLE_CACHE_VERSION2 = 26;
3801
4138
  var MODULE_DIR = dirname5(fileURLToPath(import.meta.url));
3802
4139
  var SDK_PACKAGE_ROOT = resolve6(MODULE_DIR, "..", "..");
3803
4140
  var SOURCE_REPO_ROOT = resolve6(SDK_PACKAGE_ROOT, "..");
@@ -3812,7 +4149,7 @@ var PROJECT_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? SOURCE_REPO_ROOT : HAS_PACKAGED
3812
4149
  var SDK_SOURCE_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? resolve6(SOURCE_REPO_ROOT, "sdk", "src") : HAS_PACKAGED_BUNDLING_SOURCES ? resolve6(PACKAGED_REPO_ROOT, "sdk", "src") : resolve6(SDK_PACKAGE_ROOT, "src");
3813
4150
  var SDK_PACKAGE_JSON = resolve6(SDK_PACKAGE_ROOT, "package.json");
3814
4151
  var SDK_ENTRY_FILE = resolve6(SDK_SOURCE_ROOT, "index.ts");
3815
- var SDK_TYPES_ENTRY_FILE = resolve6(SDK_PACKAGE_ROOT, "dist", "index.d.ts");
4152
+ var SDK_TYPES_ENTRY_FILE = HAS_SOURCE_BUNDLING_SOURCES ? SDK_ENTRY_FILE : resolve6(SDK_PACKAGE_ROOT, "dist", "index.d.ts");
3816
4153
  var SDK_WORKERS_ENTRY_FILE = resolve6(SDK_SOURCE_ROOT, "worker-play-entry.ts");
3817
4154
  var WORKERS_HARNESS_ENTRY_FILE = resolve6(PROJECT_ROOT, "apps", "play-runner-workers", "src", "entry.ts");
3818
4155
  var WORKERS_HARNESS_FILES_DIR = resolve6(PROJECT_ROOT, "apps", "play-runner-workers", "src");
@@ -3844,7 +4181,7 @@ function createSdkPlayBundlingAdapter() {
3844
4181
  sdkSourceRoot: SDK_SOURCE_ROOT,
3845
4182
  sdkPackageJson: SDK_PACKAGE_JSON,
3846
4183
  sdkEntryFile: SDK_ENTRY_FILE,
3847
- sdkTypesEntryFile: HAS_SOURCE_BUNDLING_SOURCES ? SDK_ENTRY_FILE : existsSync3(SDK_TYPES_ENTRY_FILE) ? SDK_TYPES_ENTRY_FILE : SDK_ENTRY_FILE,
4184
+ sdkTypesEntryFile: HAS_SOURCE_BUNDLING_SOURCES || !existsSync3(SDK_TYPES_ENTRY_FILE) ? SDK_ENTRY_FILE : SDK_TYPES_ENTRY_FILE,
3848
4185
  sdkWorkersEntryFile: SDK_WORKERS_ENTRY_FILE,
3849
4186
  workersHarnessEntryFile: WORKERS_HARNESS_ENTRY_FILE,
3850
4187
  workersHarnessFilesDir: WORKERS_HARNESS_FILES_DIR,
@@ -4081,13 +4418,15 @@ function formatLoadedPlayMessage(materializedFile) {
4081
4418
  return `Loaded play here: ${materializedFile.path}`;
4082
4419
  }
4083
4420
  function buildReadonlyPrebuiltPlayError(reference) {
4421
+ const localName = reference.split("/").slice(1).join("/") || "custom-play";
4084
4422
  return new Error(
4085
4423
  `Cannot edit or push ${reference} because Deepline prebuilt plays are read-only.
4086
4424
  To make your own version:
4087
- 1. Copy the source into a new local file.
4088
- 2. Change definePlay('${reference.split("/").slice(1).join("/")}', ...) to a new play name you own.
4089
- 3. Run: deepline plays publish <your-file.play.ts>
4090
- 4. Your play will then live under your workspace namespace.`
4425
+ 1. Run: deepline plays get ${reference} --source --out ./${localName}.play.ts
4426
+ 2. Change definePlay('${localName}', ...) to a new play name you own.
4427
+ 3. Run: deepline plays check ./${localName}.play.ts
4428
+ 4. Run: deepline plays publish ./${localName}.play.ts
4429
+ 5. Your play will then live under your workspace namespace.`
4091
4430
  );
4092
4431
  }
4093
4432
  async function ensureEditableRemotePlay(client, target) {
@@ -4228,6 +4567,15 @@ function fileInputBindingsFromStaticPipeline(staticPipeline) {
4228
4567
  );
4229
4568
  return inputField ? [{ inputPath: inputField }] : [];
4230
4569
  }
4570
+ function applyCsvShortcutInput(input) {
4571
+ const csvValue = getDottedInputValue(input.runtimeInput, "csv");
4572
+ if (csvValue == null || csvValue === "") return;
4573
+ const candidate = input.bindings.find((binding) => binding.inputPath !== "csv")?.inputPath ?? input.fallbackInputPath ?? null;
4574
+ if (!candidate || candidate === "csv") return;
4575
+ const existing = getDottedInputValue(input.runtimeInput, candidate);
4576
+ if (existing != null && existing !== "") return;
4577
+ setDottedInputValue(input.runtimeInput, candidate, csvValue);
4578
+ }
4231
4579
  function isLocalFilePathValue(value) {
4232
4580
  if (typeof value !== "string" || !value.trim()) return false;
4233
4581
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim())) return false;
@@ -4351,6 +4699,7 @@ async function compileBundledPlayGraphManifests(client, graph) {
4351
4699
  node.compilerManifest = await client.compilePlayManifest({
4352
4700
  name,
4353
4701
  sourceCode: node.sourceCode,
4702
+ sourceFiles: node.sourceFiles,
4354
4703
  artifact: node.artifact,
4355
4704
  importedPlayDependencies: node.importedPlayDependencies.map(
4356
4705
  (dependency) => {
@@ -4400,6 +4749,7 @@ async function publishImportedPlayDependencies(client, graph) {
4400
4749
  await client.registerPlayArtifact({
4401
4750
  name: node.playName,
4402
4751
  sourceCode: node.sourceCode,
4752
+ sourceFiles: node.sourceFiles,
4403
4753
  artifact: node.artifact,
4404
4754
  compilerManifest: requireCompilerManifest(node),
4405
4755
  publish: true
@@ -4417,67 +4767,6 @@ function formatTimestamp(value) {
4417
4767
  function formatRunLine(run) {
4418
4768
  return `${run.workflowId} ${run.status} ${formatTimestamp(run.startTime)}`;
4419
4769
  }
4420
- function parsePlayRunTarget(input) {
4421
- const { args, usage } = input;
4422
- let runId = null;
4423
- let playName = null;
4424
- for (let index = 0; index < args.length; index += 1) {
4425
- const arg = args[index];
4426
- if (arg === "--json") {
4427
- continue;
4428
- }
4429
- if (arg === "--reason" || arg === "--interval-ms" || arg === "--poll-interval-ms") {
4430
- index += 1;
4431
- continue;
4432
- }
4433
- if (arg === "--run-id" && args[index + 1]) {
4434
- runId = args[++index].trim();
4435
- continue;
4436
- }
4437
- if (arg === "--name" && args[index + 1] && input.allowName) {
4438
- playName = parseReferencedPlayTarget(args[++index]).playName;
4439
- continue;
4440
- }
4441
- if (arg.startsWith("--")) {
4442
- continue;
4443
- }
4444
- throw new DeeplineError(
4445
- `Unexpected positional target "${arg}". Use --run-id for run ids.
4446
- ${usage}`
4447
- );
4448
- }
4449
- const explicitTargets = [runId, playName].filter(Boolean).length;
4450
- if (explicitTargets > 1) {
4451
- throw new DeeplineError(`Choose exactly one play run target.
4452
- ${usage}`);
4453
- }
4454
- if (runId) {
4455
- return { kind: "run", runId };
4456
- }
4457
- if (playName) {
4458
- return { kind: "name", name: playName };
4459
- }
4460
- throw new DeeplineError(usage);
4461
- }
4462
- async function resolvePlayRunId(client, target) {
4463
- if (target.kind === "run") {
4464
- try {
4465
- const status = await client.getPlayStatus(target.runId);
4466
- return status.runId;
4467
- } catch (error) {
4468
- if (!(error instanceof DeeplineError) || error.statusCode !== 404) {
4469
- throw error;
4470
- }
4471
- throw new DeeplineError(`No play run found for run id: ${target.runId}`);
4472
- }
4473
- }
4474
- const runs = await client.listPlayRuns(target.name);
4475
- const workflowId = runs[0]?.workflowId ?? "";
4476
- if (!workflowId) {
4477
- throw new DeeplineError(`No runs found for play: ${target.name}`);
4478
- }
4479
- return workflowId;
4480
- }
4481
4770
  function isTransientPlayStatusPollError(error) {
4482
4771
  if (error instanceof DeeplineError && typeof error.statusCode === "number") {
4483
4772
  return error.statusCode >= 500 && error.statusCode < 600;
@@ -4581,7 +4870,7 @@ function assertPlayWaitNotTimedOut(input) {
4581
4870
  if (input.waitTimeoutMs !== null && Date.now() - input.startedAt >= input.waitTimeoutMs) {
4582
4871
  const hasRealRunId = input.workflowId.length > 0 && input.workflowId !== "pending";
4583
4872
  const phaseSuffix = input.lastPhase && input.lastPhase.trim() ? ` (last observed phase: ${input.lastPhase.trim()})` : "";
4584
- const tailHint = hasRealRunId ? ` Run 'deepline play tail --run-id ${input.workflowId} --json' to inspect it, or rerun with a larger --tail-timeout-ms.` : ` The run never reported a workflow id \u2014 the start request likely failed before reaching the scheduler. Check server logs and rerun with a larger --tail-timeout-ms.`;
4873
+ const tailHint = hasRealRunId ? ` Run 'deepline runs tail ${input.workflowId} --json' to inspect it, or rerun with a larger --tail-timeout-ms.` : ` The run never reported a workflow id \u2014 the start request likely failed before reaching the scheduler. Check server logs and rerun with a larger --tail-timeout-ms.`;
4585
4874
  throw new DeeplineError(
4586
4875
  `Timed out waiting for play ${hasRealRunId ? input.workflowId : "<no run id>"} after ${Math.ceil(input.waitTimeoutMs / 1e3)}s${phaseSuffix}.${tailHint}`,
4587
4876
  void 0,
@@ -4594,66 +4883,6 @@ function assertPlayWaitNotTimedOut(input) {
4594
4883
  );
4595
4884
  }
4596
4885
  }
4597
- async function waitForPlayCompletionByStream(input) {
4598
- const controller = new AbortController();
4599
- let timedOut = false;
4600
- let lastPhase = null;
4601
- const timeout = input.waitTimeoutMs === null ? null : setTimeout(
4602
- () => {
4603
- timedOut = true;
4604
- controller.abort();
4605
- },
4606
- Math.max(1, input.waitTimeoutMs - (Date.now() - input.startedAt))
4607
- );
4608
- try {
4609
- for await (const event of input.client.streamPlayRunEvents(
4610
- input.workflowId,
4611
- { signal: controller.signal }
4612
- )) {
4613
- assertPlayWaitNotTimedOut({ ...input, lastPhase });
4614
- const phase = describeLiveEventPhase(event);
4615
- if (phase) {
4616
- lastPhase = phase;
4617
- input.progress.phase(phase);
4618
- }
4619
- printPlayLogLines({
4620
- lines: getLogLinesFromLiveEvent(event),
4621
- status: null,
4622
- jsonOutput: input.jsonOutput,
4623
- emitLogs: input.emitLogs,
4624
- state: input.state,
4625
- progress: input.progress
4626
- });
4627
- const status = getStatusFromLiveEvent(event);
4628
- if (status && TERMINAL_PLAY_STATUSES2.has(status)) {
4629
- const finalStatus = await input.client.getPlayStatus(input.workflowId);
4630
- if (TERMINAL_PLAY_STATUSES2.has(finalStatus.status)) {
4631
- return finalStatus;
4632
- }
4633
- }
4634
- }
4635
- } catch (error) {
4636
- if (timedOut) {
4637
- assertPlayWaitNotTimedOut({ ...input, lastPhase });
4638
- }
4639
- throw error;
4640
- } finally {
4641
- if (timeout) {
4642
- clearTimeout(timeout);
4643
- }
4644
- }
4645
- const phaseSuffix = lastPhase && lastPhase.trim() ? ` (last observed phase: ${lastPhase.trim()})` : "";
4646
- throw new DeeplineError(
4647
- `Play live stream ended before the run reached a terminal state runId=${input.workflowId}${phaseSuffix}.`,
4648
- void 0,
4649
- "PLAY_LIVE_STREAM_ENDED",
4650
- {
4651
- runId: input.workflowId,
4652
- workflowId: input.workflowId,
4653
- ...lastPhase ? { phase: lastPhase } : {}
4654
- }
4655
- );
4656
- }
4657
4886
  async function startAndWaitForPlayCompletionByStream(input) {
4658
4887
  const startedAt = Date.now();
4659
4888
  const state = {
@@ -4733,10 +4962,12 @@ async function startAndWaitForPlayCompletionByStream(input) {
4733
4962
  clearTimeout(timeout);
4734
4963
  }
4735
4964
  const reason = error instanceof Error ? error.message : String(error);
4736
- process.stderr.write(
4737
- `[play watch] start stream failed after run ${lastKnownWorkflowId}; falling back to polling (${reason})
4965
+ if (!input.jsonOutput) {
4966
+ process.stderr.write(
4967
+ `[play watch] start stream failed after run ${lastKnownWorkflowId}; falling back to polling (${reason})
4738
4968
  `
4739
- );
4969
+ );
4970
+ }
4740
4971
  return waitForPlayCompletionByPolling({
4741
4972
  client: input.client,
4742
4973
  workflowId: lastKnownWorkflowId,
@@ -4755,6 +4986,24 @@ async function startAndWaitForPlayCompletionByStream(input) {
4755
4986
  clearTimeout(timeout);
4756
4987
  }
4757
4988
  }
4989
+ if (lastKnownWorkflowId) {
4990
+ if (!input.jsonOutput) {
4991
+ input.progress.writeLine(
4992
+ `[play watch] start stream ended after run ${lastKnownWorkflowId}; falling back to polling`
4993
+ );
4994
+ }
4995
+ return waitForPlayCompletionByPolling({
4996
+ client: input.client,
4997
+ workflowId: lastKnownWorkflowId,
4998
+ pollIntervalMs: 500,
4999
+ jsonOutput: input.jsonOutput,
5000
+ emitLogs: input.emitLogs,
5001
+ waitTimeoutMs: input.waitTimeoutMs,
5002
+ startedAt,
5003
+ state,
5004
+ progress: input.progress
5005
+ });
5006
+ }
4758
5007
  const phaseSuffix = lastPhase && lastPhase.trim() ? ` (last observed phase: ${lastPhase.trim()})` : "";
4759
5008
  const idSuffix = lastKnownWorkflowId ? ` runId=${lastKnownWorkflowId}` : "";
4760
5009
  throw new DeeplineError(
@@ -4832,38 +5081,6 @@ async function waitForPlayCompletionByPolling(input) {
4832
5081
  }
4833
5082
  }
4834
5083
  }
4835
- async function waitForPlayCompletion(input) {
4836
- const startedAt = Date.now();
4837
- const state = {
4838
- lastLogIndex: 0,
4839
- emittedRunnerStarted: false
4840
- };
4841
- try {
4842
- return await waitForPlayCompletionByStream({
4843
- ...input,
4844
- startedAt,
4845
- state,
4846
- progress: input.progress
4847
- });
4848
- } catch (error) {
4849
- assertPlayWaitNotTimedOut({
4850
- workflowId: input.workflowId,
4851
- startedAt,
4852
- waitTimeoutMs: input.waitTimeoutMs
4853
- });
4854
- const reason = error instanceof Error ? error.message : String(error);
4855
- process.stderr.write(
4856
- `[play watch] SSE stream failed; falling back to polling (${reason})
4857
- `
4858
- );
4859
- return waitForPlayCompletionByPolling({
4860
- ...input,
4861
- startedAt,
4862
- state,
4863
- progress: input.progress
4864
- });
4865
- }
4866
- }
4867
5084
  function formatInteger(value) {
4868
5085
  return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : String(value ?? "-");
4869
5086
  }
@@ -4992,17 +5209,153 @@ function buildRunWarnings(status, rowsInfo) {
4992
5209
  }
4993
5210
  function buildRunNextCommands(runId) {
4994
5211
  return {
4995
- exportCsv: `deepline runs export ${runId} --out output.csv`,
4996
- status: `deepline runs status ${runId} --json`,
4997
- logs: `deepline runs logs ${runId}`
5212
+ get: `deepline runs get ${runId} --json`,
5213
+ tail: `deepline runs tail ${runId} --json`,
5214
+ stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
5215
+ logs: `deepline runs logs ${runId} --out run.log --json`,
5216
+ exportCsv: `deepline runs export ${runId} --out output.csv`
5217
+ };
5218
+ }
5219
+ var RUN_LOG_PREVIEW_LIMIT = 20;
5220
+ function getRecordField(value, key) {
5221
+ return value && typeof value === "object" && !Array.isArray(value) ? value[key] : void 0;
5222
+ }
5223
+ function getNumericField(value, key) {
5224
+ const field = getRecordField(value, key);
5225
+ return typeof field === "number" && Number.isFinite(field) ? field : null;
5226
+ }
5227
+ function getStringField(value, key) {
5228
+ const field = getRecordField(value, key);
5229
+ return typeof field === "string" && field.trim() ? field : null;
5230
+ }
5231
+ function normalizeRunStatusForEnvelope(status) {
5232
+ const run = status.run ?? null;
5233
+ return {
5234
+ id: status.runId,
5235
+ playName: status.playName ?? status.name ?? getStringField(run, "playName") ?? null,
5236
+ status: status.status,
5237
+ runtime: getStringField(status, "runtime") ?? getStringField(status, "runtimeBackend") ?? getStringField(run, "runtime") ?? null,
5238
+ startedAt: getStringField(run, "startTime") ?? getStringField(run, "startedAt") ?? null,
5239
+ updatedAt: getStringField(status, "updatedAt") ?? getStringField(run, "updatedAt") ?? null,
5240
+ finishedAt: getStringField(run, "closeTime") ?? getStringField(run, "finishedAt") ?? null,
5241
+ source: getRecordField(status, "source") ?? getRecordField(status, "artifact") ?? null
5242
+ };
5243
+ }
5244
+ function normalizeProgressForEnvelope(status, rowsInfo) {
5245
+ const progress = status.progress;
5246
+ const total = getNumericField(progress, "totalRows") ?? getNumericField(progress, "total") ?? rowsInfo?.totalRows ?? null;
5247
+ const failed = getNumericField(progress, "failed") ?? getNumericField(progress, "failedRows") ?? null;
5248
+ const completed = getNumericField(progress, "completed") ?? getNumericField(progress, "completedRows") ?? (status.status === "completed" ? total : null);
5249
+ const pending = getNumericField(progress, "pending") ?? (typeof total === "number" && typeof completed === "number" && typeof failed === "number" ? Math.max(0, total - completed - failed) : null);
5250
+ return {
5251
+ total,
5252
+ completed,
5253
+ pending,
5254
+ failed,
5255
+ executed: getNumericField(progress, "executed"),
5256
+ reused: getNumericField(progress, "reused"),
5257
+ skipped: getNumericField(progress, "skipped"),
5258
+ retried: getNumericField(progress, "retried"),
5259
+ degraded: typeof getRecordField(progress, "degraded") === "boolean" ? getRecordField(progress, "degraded") : null,
5260
+ duplicates: getRecordField(progress, "duplicates") ?? null,
5261
+ active: getStringField(progress, "status") ?? getStringField(status, "activeStep") ?? getStringField(status, "activeNodeId") ?? null,
5262
+ wait: status.wait ?? null
5263
+ };
5264
+ }
5265
+ function normalizeOutputsForEnvelope(rowsInfo, exportedPath) {
5266
+ if (!rowsInfo) {
5267
+ return exportedPath ? [{ name: "output", kind: "file", path: exportedPath }] : [];
5268
+ }
5269
+ return [
5270
+ {
5271
+ name: "rows",
5272
+ kind: "dataset",
5273
+ rowCount: rowsInfo.totalRows,
5274
+ columns: rowsInfo.columns,
5275
+ preview: rowsInfo.rows.slice(0, 5),
5276
+ previewRowCount: Math.min(rowsInfo.rows.length, 5),
5277
+ previewLimit: 5,
5278
+ complete: rowsInfo.complete,
5279
+ source: rowsInfo.source,
5280
+ ...exportedPath ? { csv_path: exportedPath } : {}
5281
+ }
5282
+ ];
5283
+ }
5284
+ function normalizeStepsForEnvelope(status) {
5285
+ const directSteps = getRecordField(status, "steps");
5286
+ if (Array.isArray(directSteps)) {
5287
+ return directSteps;
5288
+ }
5289
+ const timeline = getRecordField(status, "timeline");
5290
+ if (Array.isArray(timeline)) {
5291
+ return timeline;
5292
+ }
5293
+ return [];
5294
+ }
5295
+ function normalizeErrorsForEnvelope(status, error) {
5296
+ const directErrors = getRecordField(status, "errors");
5297
+ if (Array.isArray(directErrors)) {
5298
+ return directErrors.filter(
5299
+ (entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)
5300
+ );
5301
+ }
5302
+ if (!error) {
5303
+ return [];
5304
+ }
5305
+ return [
5306
+ {
5307
+ code: getStringField(status, "errorCode") ?? "RUN_FAILED",
5308
+ phase: getStringField(status, "errorPhase") ?? "runtime",
5309
+ message: error,
5310
+ retryable: typeof getRecordField(status, "retryable") === "boolean" ? getRecordField(status, "retryable") : null,
5311
+ nextAction: `deepline runs get ${status.runId} --json`
5312
+ }
5313
+ ];
5314
+ }
5315
+ function normalizeLogsForEnvelope(status) {
5316
+ const logs = Array.isArray(status.progress?.logs) ? status.progress.logs : [];
5317
+ const offset = typeof status.progress?.logOffset === "number" && Number.isFinite(status.progress.logOffset) ? Math.max(0, Math.trunc(status.progress.logOffset)) : 0;
5318
+ const totalCount = offset + logs.length;
5319
+ const entries = logs.slice(Math.max(0, logs.length - RUN_LOG_PREVIEW_LIMIT));
5320
+ const firstSequence = entries.length === 0 ? null : offset + logs.length - entries.length + 1;
5321
+ const lastSequence = totalCount === 0 ? null : totalCount;
5322
+ return {
5323
+ totalCount,
5324
+ returnedCount: entries.length,
5325
+ firstSequence,
5326
+ lastSequence,
5327
+ truncated: totalCount > entries.length,
5328
+ hasMore: totalCount > entries.length,
5329
+ entries,
5330
+ nextCursor: lastSequence
4998
5331
  };
4999
5332
  }
5333
+ function stripProviderSpendFromBilling(value) {
5334
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
5335
+ return value;
5336
+ }
5337
+ const next = {};
5338
+ for (const [key, item] of Object.entries(value)) {
5339
+ if (key === "providerCostUsd" || key === "totalProviderCostUsd") {
5340
+ continue;
5341
+ }
5342
+ next[key] = item;
5343
+ }
5344
+ return next;
5345
+ }
5000
5346
  function compactPlayStatus(status, options) {
5001
5347
  const rowsInfo = extractCanonicalRowsInfo(status);
5002
5348
  const result = status && typeof status === "object" ? status.result : null;
5003
5349
  const warnings = buildRunWarnings(status, rowsInfo);
5004
- const datasetStats = rowsInfo && rowsInfo.complete ? buildDatasetStats(rowsInfo.rows, rowsInfo.totalRows, rowsInfo.columns) : null;
5005
- const billing = status && typeof status === "object" ? status.billing : null;
5350
+ const datasetStats = rowsInfo && rowsInfo.complete ? buildDatasetStats(
5351
+ rowsInfo.rows,
5352
+ rowsInfo.totalRows,
5353
+ rowsInfo.columns,
5354
+ extractDatasetExecutionStats(status)
5355
+ ) : null;
5356
+ const billing = status && typeof status === "object" ? stripProviderSpendFromBilling(
5357
+ status.billing
5358
+ ) : null;
5006
5359
  const progressError = status.progress?.error;
5007
5360
  const error = typeof progressError === "string" ? progressError : typeof status.error === "string" ? String(status.error) : null;
5008
5361
  return {
@@ -5011,6 +5364,12 @@ function compactPlayStatus(status, options) {
5011
5364
  ...typeof status.name === "string" ? { name: status.name } : {},
5012
5365
  ...typeof status.playName === "string" ? { playName: status.playName } : {},
5013
5366
  status: status.status,
5367
+ run: normalizeRunStatusForEnvelope(status),
5368
+ progress: normalizeProgressForEnvelope(status, rowsInfo),
5369
+ outputs: normalizeOutputsForEnvelope(rowsInfo, options?.exportedPath),
5370
+ steps: normalizeStepsForEnvelope(status),
5371
+ errors: normalizeErrorsForEnvelope(status, error),
5372
+ logs: normalizeLogsForEnvelope(status),
5014
5373
  ...error ? { error } : {},
5015
5374
  ...warnings.length > 0 ? { warnings } : {},
5016
5375
  output: buildOutputSummary(rowsInfo, options?.exportedPath) ?? result ?? null,
@@ -5019,7 +5378,6 @@ function compactPlayStatus(status, options) {
5019
5378
  ...datasetStats ? { dataset_stats: datasetStats } : {},
5020
5379
  ...rowsInfo ? { previewRows: rowsInfo.rows.slice(0, 5) } : {},
5021
5380
  ...billing ? { billing } : {},
5022
- ...status.run ? { run: status.run } : {},
5023
5381
  next: buildRunNextCommands(status.runId)
5024
5382
  };
5025
5383
  }
@@ -5033,7 +5391,8 @@ function enrichPlayStatusWithDatasetStats(status) {
5033
5391
  dataset_stats: buildDatasetStats(
5034
5392
  rowsInfo.rows,
5035
5393
  rowsInfo.totalRows,
5036
- rowsInfo.columns
5394
+ rowsInfo.columns,
5395
+ extractDatasetExecutionStats(status)
5037
5396
  )
5038
5397
  };
5039
5398
  }
@@ -5048,8 +5407,9 @@ function formatDatasetStatsLines(datasetStats) {
5048
5407
  )) {
5049
5408
  const topValues = stat3.top_values ? `, top_values=${Object.entries(stat3.top_values).slice(0, 3).map(([value, count]) => `${value}=${count}`).join(", ")}` : "";
5050
5409
  const sample = stat3.sample_value !== void 0 ? `, sample_value=${JSON.stringify(stat3.sample_value)}` : "";
5410
+ const execution = stat3.execution ? `, execution=${Object.entries(stat3.execution).map(([bucket, count]) => `${bucket}=${count}`).join(", ")}` : "";
5051
5411
  lines.push(
5052
- ` ${column}: non_empty=${stat3.non_empty}, unique=${stat3.unique}${topValues}${sample}`
5412
+ ` ${column}: non_empty=${stat3.non_empty}, unique=${stat3.unique}${topValues}${sample}${execution}`
5053
5413
  );
5054
5414
  }
5055
5415
  return lines;
@@ -5072,7 +5432,12 @@ function writePlayResult(status, jsonOutput, options) {
5072
5432
  lines.push(`${success ? "\u2713" : "\u2717"} ${publicStatus} ${runId}`);
5073
5433
  const rowsInfo = extractCanonicalRowsInfo(status);
5074
5434
  const warnings = buildRunWarnings(status, rowsInfo);
5075
- const datasetStats = rowsInfo && rowsInfo.complete ? buildDatasetStats(rowsInfo.rows, rowsInfo.totalRows, rowsInfo.columns) : null;
5435
+ const datasetStats = rowsInfo && rowsInfo.complete ? buildDatasetStats(
5436
+ rowsInfo.rows,
5437
+ rowsInfo.totalRows,
5438
+ rowsInfo.columns,
5439
+ extractDatasetExecutionStats(status)
5440
+ ) : null;
5076
5441
  const outputSummary = buildOutputSummary(rowsInfo, options?.exportedPath);
5077
5442
  if (outputSummary) {
5078
5443
  const columns = Array.isArray(outputSummary.columns) ? outputSummary.columns.length : 0;
@@ -5191,10 +5556,10 @@ function writeStartedPlayRun(input) {
5191
5556
  const lines = [
5192
5557
  `Started ${input.playName}`,
5193
5558
  ` run id: ${input.runId}`,
5194
- ` check status: deepline play status --run-id ${input.runId}`,
5195
- ` tail logs: deepline play tail --run-id ${input.runId}`,
5196
- ` stop run: deepline play stop --run-id ${input.runId}`,
5197
- ` result JSON: deepline play status --run-id ${input.runId} --json`
5559
+ ` get status: deepline runs get ${input.runId} --json`,
5560
+ ` tail logs: deepline runs tail ${input.runId} --json`,
5561
+ ` stop run: deepline runs stop ${input.runId} --reason "stale lock" --json`,
5562
+ ` result JSON: deepline runs get ${input.runId} --json`
5198
5563
  ];
5199
5564
  if (input.dashboardUrl) {
5200
5565
  lines.push(` play page: ${input.dashboardUrl}`);
@@ -5341,6 +5706,10 @@ function parsePlayCheckOptions(args) {
5341
5706
  const jsonOutput = argsWantJson(args);
5342
5707
  return { target, jsonOutput };
5343
5708
  }
5709
+ function shouldUseLocalOnlyPlayCheck() {
5710
+ const value = process.env.DEEPLINE_PLAY_CHECK_LOCAL_ONLY?.trim().toLowerCase();
5711
+ return value === "1" || value === "true" || value === "yes" || value === "on";
5712
+ }
5344
5713
  async function handlePlayCheck(args) {
5345
5714
  const options = parsePlayCheckOptions(args);
5346
5715
  if (!isFileTarget(options.target)) {
@@ -5366,10 +5735,28 @@ async function handlePlayCheck(args) {
5366
5735
  return 1;
5367
5736
  }
5368
5737
  const playName = graph.root.playName ?? extractPlayName(sourceCode, absolutePlayPath);
5738
+ if (shouldUseLocalOnlyPlayCheck()) {
5739
+ const result2 = {
5740
+ valid: true,
5741
+ errors: [],
5742
+ staticPipeline: graph.root.compilerManifest?.staticPipeline ?? null,
5743
+ artifactHash: graph.root.artifact.artifactHash,
5744
+ graphHash: graph.root.artifact.graphHash
5745
+ };
5746
+ if (options.jsonOutput) {
5747
+ process.stdout.write(`${JSON.stringify({ name: playName, ...result2 })}
5748
+ `);
5749
+ } else {
5750
+ console.log(`\u2713 ${playName} passed local play check`);
5751
+ console.log(` artifact: ${result2.artifactHash.slice(0, 12)}`);
5752
+ }
5753
+ return 0;
5754
+ }
5369
5755
  const client = new DeeplineClient();
5370
5756
  const result = await client.checkPlayArtifact({
5371
5757
  name: playName,
5372
5758
  sourceCode: graph.root.sourceCode,
5759
+ sourceFiles: graph.root.sourceFiles,
5373
5760
  artifact: graph.root.artifact
5374
5761
  });
5375
5762
  if (options.jsonOutput) {
@@ -5421,17 +5808,24 @@ async function handleFileBackedRun(options) {
5421
5808
  const packagedFileUploads = bundleResult.packagedFiles.map(
5422
5809
  (file) => stageFile(file.logicalPath, file.absolutePath)
5423
5810
  );
5811
+ const fileInputBindings = fileInputBindingsFromStaticPipeline(
5812
+ requireCompilerManifest(bundleResult).staticPipeline
5813
+ );
5814
+ applyCsvShortcutInput({
5815
+ runtimeInput,
5816
+ bindings: fileInputBindings,
5817
+ fallbackInputPath: "file"
5818
+ });
5424
5819
  const stagedFileInputs = await stageFileInputArgs({
5425
5820
  client,
5426
5821
  runtimeInput,
5427
- bindings: fileInputBindingsFromStaticPipeline(
5428
- requireCompilerManifest(bundleResult).staticPipeline
5429
- ),
5822
+ bindings: fileInputBindings,
5430
5823
  progress
5431
5824
  });
5432
5825
  const startRequest = {
5433
5826
  name: playName,
5434
5827
  sourceCode: bundleResult.sourceCode,
5828
+ sourceFiles: bundleResult.sourceFiles,
5435
5829
  runtimeArtifact: bundleResult.artifact,
5436
5830
  compilerManifest: requireCompilerManifest(bundleResult),
5437
5831
  packagedFileUploads,
@@ -5506,13 +5900,18 @@ async function handleNamedRun(options) {
5506
5900
  selector: options.revisionSelector
5507
5901
  });
5508
5902
  const runtimeInput = options.input ? { ...options.input } : {};
5903
+ const fileInputBindings = [
5904
+ ...fileInputBindingsFromPlaySchema(playDetail.play.inputSchema),
5905
+ ...fileInputBindingsFromStaticPipeline(playDetail.play.staticPipeline)
5906
+ ];
5907
+ applyCsvShortcutInput({
5908
+ runtimeInput,
5909
+ bindings: fileInputBindings
5910
+ });
5509
5911
  const stagedFileInputs = await stageFileInputArgs({
5510
5912
  client,
5511
5913
  runtimeInput,
5512
- bindings: [
5513
- ...fileInputBindingsFromPlaySchema(playDetail.play.inputSchema),
5514
- ...fileInputBindingsFromStaticPipeline(playDetail.play.staticPipeline)
5515
- ],
5914
+ bindings: fileInputBindings,
5516
5915
  progress
5517
5916
  });
5518
5917
  const startRequest = {
@@ -5590,80 +5989,101 @@ async function handlePlayRun(args) {
5590
5989
  }
5591
5990
  return handleNamedRun(options);
5592
5991
  }
5593
- async function handlePlayTail(args) {
5594
- const usage = "Usage: deepline play tail --run-id <run-id> [--interval-ms 1000] [--json]\n deepline play tail --name <name> [--interval-ms 1000] [--json]";
5595
- let target;
5596
- try {
5597
- target = parsePlayRunTarget({ args, usage, allowName: true });
5598
- } catch (error) {
5599
- console.error(error instanceof Error ? error.message : usage);
5600
- return 1;
5601
- }
5602
- const client = new DeeplineClient();
5603
- const jsonOutput = argsWantJson(args);
5604
- const emitLogs = !jsonOutput || args.includes("--logs");
5605
- let intervalMs = 500;
5992
+ function parseRunIdPositional(args, usage) {
5606
5993
  for (let index = 0; index < args.length; index += 1) {
5607
5994
  const arg = args[index];
5608
- if ((arg === "--interval-ms" || arg === "--poll-interval-ms") && args[index + 1]) {
5609
- intervalMs = parsePositiveInteger2(args[++index], arg);
5995
+ if (arg === "--json" || arg === "--full" || arg === "--logs" || arg === "--compact" || arg === "--limit") {
5996
+ if (arg === "--limit" && args[index + 1]) {
5997
+ index += 1;
5998
+ }
5999
+ continue;
6000
+ }
6001
+ if ((arg === "--out" || arg === "--cursor" || arg === "--reason" || arg === "--interval-ms" || arg === "--poll-interval-ms") && args[index + 1]) {
6002
+ index += 1;
6003
+ continue;
6004
+ }
6005
+ if (!arg.startsWith("--")) {
6006
+ return arg;
5610
6007
  }
5611
6008
  }
5612
- const workflowId = await resolvePlayRunId(client, target);
5613
- const progress = getActiveCliProgress() ?? createCliProgress(!jsonOutput);
5614
- progress.phase(`tailing ${workflowId}`);
5615
- const finalStatus = await waitForPlayCompletion({
5616
- client,
5617
- workflowId,
5618
- pollIntervalMs: intervalMs,
5619
- jsonOutput,
5620
- emitLogs,
5621
- waitTimeoutMs: null,
5622
- progress
5623
- });
5624
- if (finalStatus.status === "completed") {
5625
- progress.complete();
5626
- } else {
5627
- progress.fail();
5628
- }
5629
- writePlayResult(finalStatus, jsonOutput);
5630
- return finalStatus.status === "completed" ? 0 : 1;
6009
+ throw new DeeplineError(usage);
5631
6010
  }
5632
- async function handlePlayStatus(args) {
5633
- const usage = "Usage: deepline play status --run-id <run-id> [--json] [--full]\n deepline play status --name <name> [--json] [--full]";
5634
- let target;
6011
+ async function handleRunGet(args) {
6012
+ const usage = "Usage: deepline runs get <run-id> [--json] [--full]";
6013
+ let runId;
5635
6014
  try {
5636
- target = parsePlayRunTarget({ args, usage, allowName: true });
6015
+ runId = parseRunIdPositional(args, usage);
5637
6016
  } catch (error) {
5638
6017
  console.error(error instanceof Error ? error.message : usage);
5639
6018
  return 1;
5640
6019
  }
5641
6020
  const client = new DeeplineClient();
5642
- const workflowId = await resolvePlayRunId(client, target);
5643
- const status = await client.getPlayStatus(workflowId);
6021
+ const status = await client.runs.get(runId);
5644
6022
  writePlayResult(status, argsWantJson(args), {
5645
6023
  fullJson: args.includes("--full")
5646
6024
  });
5647
6025
  return 0;
5648
6026
  }
5649
- function parseRunIdPositional(args, usage) {
6027
+ async function handleRunsList(args) {
6028
+ const usage = "Usage: deepline runs list --play <play-name> [--status <status>] [--json]";
6029
+ let playName = null;
6030
+ let statusFilter = null;
5650
6031
  for (let index = 0; index < args.length; index += 1) {
5651
6032
  const arg = args[index];
5652
- if (arg === "--json" || arg === "--full" || arg === "--logs") {
6033
+ if ((arg === "--play" || arg === "--name") && args[index + 1]) {
6034
+ playName = parseReferencedPlayTarget(args[++index]).playName;
5653
6035
  continue;
5654
6036
  }
5655
- if (arg === "--out" && args[index + 1]) {
5656
- index += 1;
6037
+ if (arg === "--status" && args[index + 1]) {
6038
+ statusFilter = args[++index].trim().toLowerCase();
5657
6039
  continue;
5658
6040
  }
5659
- if (!arg.startsWith("--")) {
5660
- return arg;
6041
+ if (arg === "--json" || arg === "--compact") {
6042
+ continue;
5661
6043
  }
5662
6044
  }
5663
- throw new DeeplineError(usage);
6045
+ if (!playName) {
6046
+ console.error(usage);
6047
+ return 1;
6048
+ }
6049
+ const client = new DeeplineClient();
6050
+ const runs = (await client.runs.list({
6051
+ play: playName,
6052
+ ...statusFilter ? { status: statusFilter } : {}
6053
+ })).map((run) => ({
6054
+ runId: run.workflowId,
6055
+ workflowId: run.workflowId,
6056
+ temporalRunId: run.runId,
6057
+ status: String(run.status ?? "").toLowerCase(),
6058
+ startedAt: run.startTime,
6059
+ finishedAt: run.closeTime,
6060
+ executionTime: run.executionTime,
6061
+ playName: run.memo?.playName ?? playName
6062
+ }));
6063
+ if (argsWantJson(args)) {
6064
+ process.stdout.write(
6065
+ `${JSON.stringify({
6066
+ runs,
6067
+ count: runs.length,
6068
+ next: {
6069
+ get: runs[0]?.runId ? `deepline runs get ${runs[0].runId} --json` : null
6070
+ }
6071
+ })}
6072
+ `
6073
+ );
6074
+ } else {
6075
+ if (runs.length === 0) {
6076
+ console.log(`No runs found for ${playName}.`);
6077
+ } else {
6078
+ for (const run of runs) {
6079
+ console.log(`${run.runId} ${run.status} ${formatTimestamp(run.startedAt)}`);
6080
+ }
6081
+ }
6082
+ }
6083
+ return 0;
5664
6084
  }
5665
- async function handleRunStatus(args) {
5666
- const usage = "Usage: deepline runs status <run-id> [--json] [--full]";
6085
+ async function handleRunTail(args) {
6086
+ const usage = "Usage: deepline runs tail <run-id> [--json] [--compact] [--cursor <cursor>]";
5667
6087
  let runId;
5668
6088
  try {
5669
6089
  runId = parseRunIdPositional(args, usage);
@@ -5672,14 +6092,24 @@ async function handleRunStatus(args) {
5672
6092
  return 1;
5673
6093
  }
5674
6094
  const client = new DeeplineClient();
5675
- const status = await client.getPlayStatus(runId);
5676
- writePlayResult(status, argsWantJson(args), {
5677
- fullJson: args.includes("--full")
6095
+ let afterLogIndex;
6096
+ for (let index = 0; index < args.length; index += 1) {
6097
+ const arg = args[index];
6098
+ if (arg === "--cursor" && args[index + 1]) {
6099
+ const parsed = Number(args[++index]);
6100
+ if (Number.isInteger(parsed) && parsed >= 0) {
6101
+ afterLogIndex = parsed;
6102
+ }
6103
+ }
6104
+ }
6105
+ const status = await client.runs.tail(runId, {
6106
+ ...afterLogIndex !== void 0 ? { afterLogIndex } : {}
5678
6107
  });
5679
- return 0;
6108
+ writePlayResult(status, argsWantJson(args));
6109
+ return status.status === "failed" ? 1 : 0;
5680
6110
  }
5681
6111
  async function handleRunLogs(args) {
5682
- const usage = "Usage: deepline runs logs <run-id> [--json]";
6112
+ const usage = "Usage: deepline runs logs <run-id> [--limit 200] [--out run.log] [--json]";
5683
6113
  let runId;
5684
6114
  try {
5685
6115
  runId = parseRunIdPositional(args, usage);
@@ -5687,14 +6117,86 @@ async function handleRunLogs(args) {
5687
6117
  console.error(error instanceof Error ? error.message : usage);
5688
6118
  return 1;
5689
6119
  }
6120
+ let limit = 200;
6121
+ let outPath = null;
6122
+ for (let index = 0; index < args.length; index += 1) {
6123
+ const arg = args[index];
6124
+ if (arg === "--limit" && args[index + 1]) {
6125
+ limit = parsePositiveInteger2(args[++index], "--limit");
6126
+ continue;
6127
+ }
6128
+ if (arg === "--out" && args[index + 1]) {
6129
+ outPath = resolve7(args[++index]);
6130
+ }
6131
+ }
5690
6132
  const client = new DeeplineClient();
5691
- const status = await client.getPlayStatus(runId);
6133
+ const status = await client.runs.get(runId);
5692
6134
  const logs = status.progress?.logs ?? [];
6135
+ if (outPath) {
6136
+ writeFileSync4(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
6137
+ if (argsWantJson(args)) {
6138
+ process.stdout.write(
6139
+ `${JSON.stringify({
6140
+ runId: status.runId,
6141
+ log_path: outPath,
6142
+ lineCount: logs.length
6143
+ })}
6144
+ `
6145
+ );
6146
+ } else {
6147
+ console.log(`Wrote ${logs.length} log lines to ${outPath}`);
6148
+ }
6149
+ return 0;
6150
+ }
6151
+ const entries = logs.slice(Math.max(0, logs.length - limit));
6152
+ if (argsWantJson(args)) {
6153
+ process.stdout.write(
6154
+ `${JSON.stringify({
6155
+ runId: status.runId,
6156
+ totalCount: logs.length,
6157
+ returnedCount: entries.length,
6158
+ firstSequence: logs.length === 0 ? null : logs.length - entries.length + 1,
6159
+ lastSequence: logs.length === 0 ? null : logs.length,
6160
+ truncated: logs.length > entries.length,
6161
+ hasMore: logs.length > entries.length,
6162
+ entries,
6163
+ next: {
6164
+ export: `deepline runs logs ${status.runId} --out run.log --json`
6165
+ }
6166
+ })}
6167
+ `
6168
+ );
6169
+ } else {
6170
+ process.stdout.write(`${entries.join("\n")}${entries.length > 0 ? "\n" : ""}`);
6171
+ }
6172
+ return 0;
6173
+ }
6174
+ async function handleRunStop(args) {
6175
+ const usage = 'Usage: deepline runs stop <run-id> [--reason "text"] [--json]';
6176
+ let runId;
6177
+ try {
6178
+ runId = parseRunIdPositional(args, usage);
6179
+ } catch (error) {
6180
+ console.error(error instanceof Error ? error.message : usage);
6181
+ return 1;
6182
+ }
6183
+ let reason;
6184
+ for (let index = 0; index < args.length; index += 1) {
6185
+ const arg = args[index];
6186
+ if (arg === "--reason" && args[index + 1]) {
6187
+ reason = args[++index];
6188
+ }
6189
+ }
6190
+ const client = new DeeplineClient();
6191
+ const result = await client.runs.stop(runId, { reason });
5693
6192
  if (argsWantJson(args)) {
5694
- process.stdout.write(`${JSON.stringify({ runId: status.runId, logs })}
6193
+ process.stdout.write(`${JSON.stringify(result)}
5695
6194
  `);
5696
6195
  } else {
5697
- process.stdout.write(`${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
6196
+ console.log(`Stopped ${result.runId}`);
6197
+ if (result.hitlCancelledCount > 0) {
6198
+ console.log(` cancelled HITL waits: ${result.hitlCancelledCount}`);
6199
+ }
5698
6200
  }
5699
6201
  return 0;
5700
6202
  }
@@ -5737,37 +6239,6 @@ async function handleRunExport(args) {
5737
6239
  }
5738
6240
  return 0;
5739
6241
  }
5740
- async function handlePlayStop(args) {
5741
- const usage = 'Usage: deepline play stop --run-id <run-id> [--reason "text"] [--json]';
5742
- let target;
5743
- try {
5744
- target = parsePlayRunTarget({ args, usage, allowName: false });
5745
- } catch (error) {
5746
- console.error(error instanceof Error ? error.message : usage);
5747
- return 1;
5748
- }
5749
- const client = new DeeplineClient();
5750
- const jsonOutput = argsWantJson(args);
5751
- let reason;
5752
- for (let index = 0; index < args.length; index += 1) {
5753
- const arg = args[index];
5754
- if (arg === "--reason" && args[index + 1]) {
5755
- reason = args[++index];
5756
- }
5757
- }
5758
- const workflowId = await resolvePlayRunId(client, target);
5759
- const result = await client.stopPlay(workflowId, { reason });
5760
- if (jsonOutput) {
5761
- process.stdout.write(`${JSON.stringify(result)}
5762
- `);
5763
- } else {
5764
- console.log(`Stopped ${result.runId}`);
5765
- if (result.hitlCancelledCount > 0) {
5766
- console.log(` cancelled HITL waits: ${result.hitlCancelledCount}`);
5767
- }
5768
- }
5769
- return 0;
5770
- }
5771
6242
  async function handlePlayGet(args) {
5772
6243
  const target = args[0];
5773
6244
  if (!target) {
@@ -5855,33 +6326,6 @@ async function handlePlayGet(args) {
5855
6326
  }
5856
6327
  return 0;
5857
6328
  }
5858
- async function handlePlayRuns(args) {
5859
- const nameIndex = args.indexOf("--name");
5860
- const name = nameIndex >= 0 ? args[nameIndex + 1] : void 0;
5861
- if (!name) {
5862
- console.error("Usage: deepline play runs --name <name> [--json]");
5863
- return 1;
5864
- }
5865
- const client = new DeeplineClient();
5866
- const jsonOutput = argsWantJson(args);
5867
- await assertCanonicalNamedPlayReference(client, name);
5868
- const runs = await client.listPlayRuns(
5869
- parseReferencedPlayTarget(name).playName
5870
- );
5871
- if (jsonOutput) {
5872
- process.stdout.write(`${JSON.stringify({ runs })}
5873
- `);
5874
- return 0;
5875
- }
5876
- if (runs.length === 0) {
5877
- console.log(`No runs found for ${name}.`);
5878
- return 0;
5879
- }
5880
- for (const run of runs) {
5881
- console.log(formatRunLine(run));
5882
- }
5883
- return 0;
5884
- }
5885
6329
  function formatVersionLine(version) {
5886
6330
  const revisionLabel = version.artifactHash?.slice(0, 12) ?? "unknown-revision";
5887
6331
  return `v${version.version} ${revisionLabel} ${formatTimestamp(version.createdAt)}`;
@@ -6013,6 +6457,11 @@ function printPlayDescription(play) {
6013
6457
  }
6014
6458
  }
6015
6459
  console.log(` Run: ${play.runCommand}`);
6460
+ if (play.origin === "prebuilt" && !play.canEdit) {
6461
+ console.log(
6462
+ ` Customize: deepline plays get ${reference} --source --out ./my-play.play.ts`
6463
+ );
6464
+ }
6016
6465
  }
6017
6466
  async function handlePlaySearch(args) {
6018
6467
  let options;
@@ -6110,6 +6559,7 @@ async function handlePlayPublish(args) {
6110
6559
  const published = await client.registerPlayArtifact({
6111
6560
  name: rootPlayName,
6112
6561
  sourceCode: graph.root.sourceCode,
6562
+ sourceFiles: graph.root.sourceFiles,
6113
6563
  artifact: graph.root.artifact,
6114
6564
  compilerManifest: requireCompilerManifest(graph.root),
6115
6565
  publish: true
@@ -6330,50 +6780,12 @@ Examples:
6330
6780
  ...options.json ? ["--json"] : []
6331
6781
  ]);
6332
6782
  });
6333
- play.command("runs").description("List runs for a named play.").option("--name <name>", "Saved play name").option("--json", "Emit JSON output").action(async (options) => {
6334
- process.exitCode = await handlePlayRuns([
6335
- ...options.name ? ["--name", options.name] : [],
6336
- ...options.json ? ["--json"] : []
6337
- ]);
6338
- });
6339
6783
  play.command("versions").description("List revisions for a named play.").option("--name <name>", "Saved play name").option("--json", "Emit JSON output").action(async (options) => {
6340
6784
  process.exitCode = await handlePlayVersions([
6341
6785
  ...options.name ? ["--name", options.name] : [],
6342
6786
  ...options.json ? ["--json"] : []
6343
6787
  ]);
6344
6788
  });
6345
- 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) => {
6346
- process.exitCode = await handlePlayTail([
6347
- ...options.runId ? ["--run-id", options.runId] : [],
6348
- ...options.name ? ["--name", options.name] : [],
6349
- ...options.intervalMs ? ["--interval-ms", options.intervalMs] : [],
6350
- ...options.logs ? ["--logs"] : [],
6351
- ...options.json ? ["--json"] : []
6352
- ]);
6353
- });
6354
- 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) => {
6355
- process.exitCode = await handlePlayStatus([
6356
- ...options.runId ? ["--run-id", options.runId] : [],
6357
- ...options.name ? ["--name", options.name] : [],
6358
- ...options.json ? ["--json"] : [],
6359
- ...options.full ? ["--full"] : []
6360
- ]);
6361
- });
6362
- 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) => {
6363
- process.exitCode = await handleRunExport([
6364
- options.runId,
6365
- "--out",
6366
- options.out,
6367
- ...options.json ? ["--json"] : []
6368
- ]);
6369
- });
6370
- 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) => {
6371
- process.exitCode = await handlePlayStop([
6372
- ...options.runId ? ["--run-id", options.runId] : [],
6373
- ...options.reason ? ["--reason", options.reason] : [],
6374
- ...options.json ? ["--json"] : []
6375
- ]);
6376
- });
6377
6789
  play.command("publish <target>").description("Bundle, validate, save, and publish a play.").option("--latest", "Promote the newest saved revision").option("--revision-id <id>", "Revision to promote").option("--json", "Emit JSON output").action(async (target, options) => {
6378
6790
  process.exitCode = await handlePlayPublish([
6379
6791
  target,
@@ -6389,38 +6801,72 @@ Examples:
6389
6801
  ...options.json ? ["--json"] : []
6390
6802
  ]);
6391
6803
  });
6392
- const runs = program.command("runs").description("Inspect and export play runs.").addHelpText(
6804
+ const runs = program.command("runs").description("Inspect, tail, stop, and export play runs.").addHelpText(
6393
6805
  "after",
6394
6806
  `
6395
6807
  Examples:
6396
- deepline runs status play/my-play/run/20260501t000000-000
6808
+ deepline runs get play/my-play/run/20260501t000000-000 --json
6809
+ deepline runs tail play/my-play/run/20260501t000000-000
6810
+ deepline runs logs play/my-play/run/20260501t000000-000 --out run.log --json
6811
+ deepline runs list --play my-play --status failed --json
6812
+ deepline runs stop play/my-play/run/20260501t000000-000 --reason "stale lock" --json
6397
6813
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv
6398
- deepline runs logs play/my-play/run/20260501t000000-000
6399
6814
  `
6400
6815
  );
6401
- 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) => {
6402
- process.exitCode = await handleRunStatus([
6816
+ runs.command("get <runId>").description("Get status, progress, outputs, errors, and recovery metadata for a play run.").option("--json", "Emit JSON output. Also automatic when stdout is piped").option("--full", "Debug only: with --json, emit the raw status payload").action(async (runId, options) => {
6817
+ process.exitCode = await handleRunGet([
6403
6818
  runId,
6404
6819
  ...options.json ? ["--json"] : [],
6405
6820
  ...options.full ? ["--full"] : []
6406
6821
  ]);
6407
6822
  });
6408
- 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) => {
6409
- process.exitCode = await handleRunExport([
6410
- runId,
6411
- "--out",
6412
- options.out,
6823
+ runs.command("list").description("List play runs.").requiredOption("--play <name>", "Play name to filter runs").option("--status <status>", "Filter by run status").option("--compact", "Drop verbose fields from JSON output").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
6824
+ process.exitCode = await handleRunsList([
6825
+ "--play",
6826
+ options.play,
6827
+ ...options.status ? ["--status", options.status] : [],
6828
+ ...options.compact ? ["--compact"] : [],
6413
6829
  ...options.json ? ["--json"] : []
6414
6830
  ]);
6415
6831
  });
6416
- runs.command("logs <runId>").description("Print logs for a play run.").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (runId, options) => {
6832
+ runs.command("tail <runId>").description("Tail or fetch recent live events for a play run.").option("--json", "Emit JSON output. Also automatic when stdout is piped").option("--compact", "Drop verbose fields from JSON output").option("--cursor <cursor>", "Resume after a previously returned event cursor").action(async (runId, options) => {
6833
+ process.exitCode = await handleRunTail([
6834
+ runId,
6835
+ ...options.json ? ["--json"] : [],
6836
+ ...options.compact ? ["--compact"] : [],
6837
+ ...options.cursor ? ["--cursor", options.cursor] : []
6838
+ ]);
6839
+ });
6840
+ runs.command("logs <runId>").description("Fetch persisted logs for a play run.").option("--limit <count>", "Maximum recent log lines to print without --out", "200").option("--out <path>", "Write the full persisted log stream to a file").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (runId, options) => {
6417
6841
  process.exitCode = await handleRunLogs([
6418
6842
  runId,
6843
+ ...options.limit ? ["--limit", options.limit] : [],
6844
+ ...options.out ? ["--out", options.out] : [],
6845
+ ...options.json ? ["--json"] : []
6846
+ ]);
6847
+ });
6848
+ runs.command("stop <runId>").description("Stop a play run.").option("--reason <text>", "Reason to include with the stop request").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (runId, options) => {
6849
+ process.exitCode = await handleRunStop([
6850
+ runId,
6851
+ ...options.reason ? ["--reason", options.reason] : [],
6852
+ ...options.json ? ["--json"] : []
6853
+ ]);
6854
+ });
6855
+ runs.command("export <runId>").description("Export the completed row output for a play run to CSV.").requiredOption("--out <path>", "Output CSV path").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (runId, options) => {
6856
+ process.exitCode = await handleRunExport([
6857
+ runId,
6858
+ "--out",
6859
+ options.out,
6419
6860
  ...options.json ? ["--json"] : []
6420
6861
  ]);
6421
6862
  });
6422
6863
  }
6423
6864
 
6865
+ // src/cli/commands/tools.ts
6866
+ import { chmodSync, mkdtempSync, writeFileSync as writeFileSync6 } from "fs";
6867
+ import { tmpdir as tmpdir3 } from "os";
6868
+ import { join as join8 } from "path";
6869
+
6424
6870
  // src/tool-output.ts
6425
6871
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
6426
6872
  import { homedir as homedir3 } from "os";
@@ -6589,31 +7035,23 @@ async function listTools(args) {
6589
7035
  }
6590
7036
  return 0;
6591
7037
  }
6592
- function toolMatchesQuery(tool, terms) {
6593
- const haystack = [
6594
- tool.toolId,
6595
- tool.provider,
6596
- tool.displayName,
6597
- tool.description,
6598
- tool.operation,
6599
- tool.operationId,
6600
- ...tool.operationAliases ?? [],
6601
- ...tool.categories ?? [],
6602
- tool.inputSchema ? JSON.stringify(tool.inputSchema) : ""
6603
- ].filter(Boolean).join(" ").toLowerCase();
6604
- return terms.every((term) => haystack.includes(term));
6605
- }
6606
- async function searchTools(args) {
6607
- const query = args[0]?.trim();
7038
+ async function searchTools(queryInput, options = {}) {
7039
+ const query = queryInput.trim();
6608
7040
  if (!query) {
6609
7041
  console.error("Usage: deepline tools search <query> [--json]");
6610
7042
  return 1;
6611
7043
  }
6612
- const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
6613
7044
  const client = new DeeplineClient();
6614
- const items = (await client.listTools()).filter((tool) => toolMatchesQuery(tool, terms)).map(toListedTool);
6615
- if (argsWantJson(args)) {
6616
- process.stdout.write(`${JSON.stringify({ tools: items })}
7045
+ const result = await client.searchTools({
7046
+ query,
7047
+ categories: options.categories,
7048
+ searchTerms: options.searchTerms,
7049
+ searchMode: options.searchMode,
7050
+ includeSearchDebug: options.includeSearchDebug
7051
+ });
7052
+ const items = result.tools.map(toListedTool);
7053
+ if (options.json || shouldEmitJson()) {
7054
+ process.stdout.write(`${JSON.stringify({ ...result, tools: items })}
6617
7055
  `);
6618
7056
  return 0;
6619
7057
  }
@@ -6667,11 +7105,14 @@ Common commands:
6667
7105
  ...options.json ? ["--json"] : []
6668
7106
  ]);
6669
7107
  });
6670
- tools.command("search <query>").description("Search available tools.").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (query, options) => {
6671
- process.exitCode = await searchTools([
6672
- query,
6673
- ...options.json ? ["--json"] : []
6674
- ]);
7108
+ tools.command("search <query>").description("Search available tools.").option("--categories <categories>", "Comma-separated categories to filter ranked search").option("--search_terms <terms>", "Structured search terms for ranked search").option("--search-terms <terms>", "Structured search terms for ranked search").option("--search-mode <mode>", "Ranked search mode: v1 or v2").option("--include-search-debug", "Include ranked search debug metadata").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (query, options) => {
7109
+ process.exitCode = await searchTools(query, {
7110
+ json: options.json,
7111
+ categories: options.categories,
7112
+ searchTerms: options.searchTerms ?? options.search_terms,
7113
+ searchMode: options.searchMode === "v1" || options.searchMode === "v2" ? options.searchMode : void 0,
7114
+ includeSearchDebug: Boolean(options.includeSearchDebug)
7115
+ });
6675
7116
  });
6676
7117
  tools.command("get <toolId>").alias("describe").description("Show metadata for a tool.").addHelpText(
6677
7118
  "after",
@@ -7029,6 +7470,61 @@ function parseExecuteOptions(args) {
7029
7470
  }
7030
7471
  return { toolId, params, outputFormat, noPreview };
7031
7472
  }
7473
+ function safeFileStem(value) {
7474
+ return value.trim().replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "tool";
7475
+ }
7476
+ function shellQuote(value) {
7477
+ return `'${value.replace(/'/g, `'\\''`)}'`;
7478
+ }
7479
+ function powerShellQuote(value) {
7480
+ return `'${value.replace(/'/g, "''")}'`;
7481
+ }
7482
+ function seedToolListScript(input) {
7483
+ const stem = safeFileStem(input.toolId);
7484
+ const fileName = `${stem}-workflow-seed-${Date.now()}.play.ts`;
7485
+ const scriptDir = mkdtempSync(join8(tmpdir3(), "deepline-workflow-seed-"));
7486
+ chmodSync(scriptDir, 448);
7487
+ const scriptPath = join8(scriptDir, fileName);
7488
+ const projectDir = `deepline/projects/${stem}-workflow`;
7489
+ const playName = `${stem}-workflow`;
7490
+ const sampleRows = input.rows.length > 0 ? `${JSON.stringify(input.rows.slice(0, 2)).replace(/\]$/, "")}, ...]` : "[]";
7491
+ const columns = Object.keys(input.rows[0] ?? {}).join(", ");
7492
+ const rowKey = Object.prototype.hasOwnProperty.call(input.rows[0] ?? {}, "id") ? '"id"' : "(row) => JSON.stringify(row)";
7493
+ const script = `import { definePlay } from 'deepline';
7494
+
7495
+ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
7496
+ const result = await ctx.tools.execute({
7497
+ id: ${JSON.stringify(input.toolId)},
7498
+ tool: ${JSON.stringify(input.toolId)},
7499
+ input: ${JSON.stringify(input.payload)},
7500
+ description: ${JSON.stringify(`Seed ${input.toolId} rows for workflow expansion.`)},
7501
+ });
7502
+
7503
+ const list = Object.values(result.lists)[0];
7504
+ const rows = (list?.get() ?? []).slice(0, 100);
7505
+ // ${sampleRows}
7506
+ // columns: ${columns}
7507
+ // .step('email_waterfall', (row, rowCtx) => rowCtx.runPlay('name_domain_email', 'name-and-domain-to-email', { first_name: String(row.first_name ?? ''), last_name: String(row.last_name ?? ''), domain: String(row.domain ?? '') }, { description: 'Resolve email.' }))
7508
+ // .step('phone_waterfall', (row, rowCtx) => rowCtx.runPlay('contact_phone', 'contact-to-phone', { first_name: String(row.first_name ?? ''), last_name: String(row.last_name ?? ''), email: String(row.email ?? '') }, { description: 'Resolve phone.' }))
7509
+ // ctx.map is idempotent by map key + row key; reruns reuse completed rows.
7510
+ const enrichedData = await ctx
7511
+ .map('enriched_data', rows, { key: ${rowKey} })
7512
+ .run({ description: 'Enrich seeded rows.' });
7513
+
7514
+ return {
7515
+ rows: enrichedData,
7516
+ count: await enrichedData.count(),
7517
+ };
7518
+ });
7519
+ `;
7520
+ writeFileSync6(scriptPath, script, { encoding: "utf-8", mode: 384 });
7521
+ return {
7522
+ path: scriptPath,
7523
+ projectDir,
7524
+ macCopyCommand: `mkdir -p ${shellQuote(projectDir)} && cp ${shellQuote(scriptPath)} ${shellQuote(`${projectDir}/${fileName}`)}`,
7525
+ windowsCopyCommand: `New-Item -ItemType Directory -Force -Path ${powerShellQuote(projectDir.replace(/\//g, "\\"))} | Out-Null; Copy-Item -LiteralPath ${powerShellQuote(scriptPath)} -Destination ${powerShellQuote(`${projectDir.replace(/\//g, "\\")}\\${fileName}`)}`
7526
+ };
7527
+ }
7032
7528
  async function executeTool(args) {
7033
7529
  let parsed;
7034
7530
  try {
@@ -7082,6 +7578,11 @@ async function executeTool(args) {
7082
7578
  return 0;
7083
7579
  }
7084
7580
  const csv = writeCsvOutputFile(listConversion.rows, `${parsed.toolId}_output`);
7581
+ const seededScript = seedToolListScript({
7582
+ toolId: parsed.toolId,
7583
+ payload: parsed.params,
7584
+ rows: listConversion.rows
7585
+ });
7085
7586
  if (parsed.outputFormat === "csv_file") {
7086
7587
  process.stdout.write(`${JSON.stringify({
7087
7588
  extracted_csv: csv.path,
@@ -7090,6 +7591,12 @@ async function executeTool(args) {
7090
7591
  preview: csv.preview,
7091
7592
  list_strategy: listConversion.strategy,
7092
7593
  list_source_path: listConversion.sourcePath,
7594
+ starter_script: seededScript.path,
7595
+ project_dir: seededScript.projectDir,
7596
+ copy_to_project: {
7597
+ macos_linux: seededScript.macCopyCommand,
7598
+ windows_powershell: seededScript.windowsCopyCommand
7599
+ },
7093
7600
  summary
7094
7601
  })}
7095
7602
  `);
@@ -7107,14 +7614,20 @@ async function executeTool(args) {
7107
7614
  console.log(`summary: ${Object.entries(summary).map(([key, value]) => `${key}=${String(value)}`).join(", ")}`);
7108
7615
  }
7109
7616
  console.log(`preview: ${JSON.stringify(csv.preview)}`);
7617
+ console.log(`starter script: ${seededScript.path}`);
7618
+ console.log(
7619
+ "next: Move the script into a project folder and expand it into a Deepline play. Use stable map and step keys so reruns are idempotent: completed rows are reused, and only missing or stale work runs again."
7620
+ );
7621
+ console.log(`macOS/Linux: ${seededScript.macCopyCommand}`);
7622
+ console.log(`Windows PowerShell: ${seededScript.windowsCopyCommand}`);
7110
7623
  return 0;
7111
7624
  }
7112
7625
 
7113
7626
  // src/cli/skills-sync.ts
7114
- import { spawn } from "child_process";
7115
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "fs";
7627
+ import { spawn, spawnSync } from "child_process";
7628
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync7 } from "fs";
7116
7629
  import { homedir as homedir4 } from "os";
7117
- import { dirname as dirname7, join as join8 } from "path";
7630
+ import { dirname as dirname7, join as join9 } from "path";
7118
7631
  var CHECK_TIMEOUT_MS2 = 3e3;
7119
7632
  var SDK_SKILL_NAME = "deepline-sdk";
7120
7633
  var SKILL_AGENTS = ["codex", "claude-code", "cursor"];
@@ -7125,7 +7638,7 @@ function shouldSkipSkillsSync() {
7125
7638
  }
7126
7639
  function sdkSkillsVersionPath(baseUrl) {
7127
7640
  const home = process.env.HOME?.trim() || homedir4();
7128
- return join8(home, ".local", "deepline", baseUrlSlug(baseUrl), "sdk-skills", ".version");
7641
+ return join9(home, ".local", "deepline", baseUrlSlug(baseUrl), "sdk-skills", ".version");
7129
7642
  }
7130
7643
  function readLocalSkillsVersion(baseUrl) {
7131
7644
  const path = sdkSkillsVersionPath(baseUrl);
@@ -7139,7 +7652,7 @@ function readLocalSkillsVersion(baseUrl) {
7139
7652
  function writeLocalSkillsVersion(baseUrl, version) {
7140
7653
  const path = sdkSkillsVersionPath(baseUrl);
7141
7654
  mkdirSync4(dirname7(path), { recursive: true });
7142
- writeFileSync6(path, `${version}
7655
+ writeFileSync7(path, `${version}
7143
7656
  `, "utf-8");
7144
7657
  }
7145
7658
  async function fetchSkillsUpdate(baseUrl, localVersion) {
@@ -7170,9 +7683,26 @@ async function fetchSkillsUpdate(baseUrl, localVersion) {
7170
7683
  clearTimeout(timeout);
7171
7684
  }
7172
7685
  }
7173
- function runSkillsInstall(baseUrl) {
7686
+ function buildSkillsInstallArgs(baseUrl) {
7687
+ const packageUrl = new URL("/.well-known/skills/index.json", baseUrl).toString();
7688
+ return [
7689
+ "--yes",
7690
+ "skills",
7691
+ "add",
7692
+ packageUrl,
7693
+ "--agents",
7694
+ ...SKILL_AGENTS,
7695
+ "--global",
7696
+ "--yes",
7697
+ "--skill",
7698
+ SDK_SKILL_NAME,
7699
+ "--full-depth"
7700
+ ];
7701
+ }
7702
+ function buildBunxSkillsInstallArgs(baseUrl) {
7174
7703
  const packageUrl = new URL("/.well-known/skills/index.json", baseUrl).toString();
7175
- const args = [
7704
+ return [
7705
+ "--bun",
7176
7706
  "skills",
7177
7707
  "add",
7178
7708
  packageUrl,
@@ -7184,8 +7714,40 @@ function runSkillsInstall(baseUrl) {
7184
7714
  SDK_SKILL_NAME,
7185
7715
  "--full-depth"
7186
7716
  ];
7717
+ }
7718
+ function hasCommand(command) {
7719
+ const result = spawnSync(command, ["--version"], {
7720
+ stdio: "ignore",
7721
+ shell: process.platform === "win32"
7722
+ });
7723
+ return result.status === 0;
7724
+ }
7725
+ function shellQuote2(arg) {
7726
+ return `'${arg.replace(/'/g, `'\\''`)}'`;
7727
+ }
7728
+ function resolveSkillsInstallCommands(baseUrl) {
7729
+ const npxArgs = buildSkillsInstallArgs(baseUrl);
7730
+ const npxInstall = {
7731
+ command: "npx",
7732
+ args: npxArgs,
7733
+ manualCommand: `npx ${npxArgs.map(shellQuote2).join(" ")}`
7734
+ };
7735
+ if (hasCommand("bunx")) {
7736
+ const bunxArgs = buildBunxSkillsInstallArgs(baseUrl);
7737
+ return [
7738
+ {
7739
+ command: "bunx",
7740
+ args: bunxArgs,
7741
+ manualCommand: `bunx ${bunxArgs.map(shellQuote2).join(" ")}`
7742
+ },
7743
+ npxInstall
7744
+ ];
7745
+ }
7746
+ return [npxInstall];
7747
+ }
7748
+ function runOneSkillsInstall(install) {
7187
7749
  return new Promise((resolve8) => {
7188
- const child = spawn("npx", args, {
7750
+ const child = spawn(install.command, install.args, {
7189
7751
  stdio: ["ignore", "ignore", "pipe"],
7190
7752
  env: process.env
7191
7753
  });
@@ -7194,37 +7756,63 @@ function runSkillsInstall(baseUrl) {
7194
7756
  stderr += chunk.toString("utf-8");
7195
7757
  });
7196
7758
  child.on("error", (error) => {
7197
- process.stderr.write(`SDK skills sync failed to start: ${error.message}
7198
- `);
7199
- resolve8(false);
7759
+ resolve8({
7760
+ ok: false,
7761
+ detail: `failed to start ${install.command}: ${error.message}`,
7762
+ manualCommand: install.manualCommand
7763
+ });
7200
7764
  });
7201
7765
  child.on("close", (code) => {
7202
7766
  if (code === 0) {
7203
- resolve8(true);
7767
+ resolve8({ ok: true, detail: "", manualCommand: install.manualCommand });
7204
7768
  return;
7205
7769
  }
7206
7770
  const detail = stderr.trim();
7207
- process.stderr.write(
7208
- `SDK skills sync failed${detail ? `: ${detail}` : ""}
7209
- Run manually: npx ${args.map((arg) => arg.includes(" ") ? JSON.stringify(arg) : arg).join(" ")}
7210
- `
7211
- );
7212
- resolve8(false);
7771
+ resolve8({
7772
+ ok: false,
7773
+ detail: detail ? `${install.command}: ${detail}` : `${install.command} exited ${code}`,
7774
+ manualCommand: install.manualCommand
7775
+ });
7213
7776
  });
7214
7777
  });
7215
7778
  }
7779
+ async function runSkillsInstall(baseUrl) {
7780
+ const failures = [];
7781
+ for (const install of resolveSkillsInstallCommands(baseUrl)) {
7782
+ const result = await runOneSkillsInstall(install);
7783
+ if (result.ok) return true;
7784
+ failures.push(result);
7785
+ }
7786
+ const details = failures.map((failure) => failure.detail).filter(Boolean).join("\n");
7787
+ const manualCommand = failures.at(-1)?.manualCommand;
7788
+ process.stderr.write(
7789
+ `SDK skills sync failed${details ? `:
7790
+ ${details}` : ""}
7791
+ ` + (manualCommand ? `Run manually: ${manualCommand}
7792
+ ` : "")
7793
+ );
7794
+ return false;
7795
+ }
7796
+ function writeSdkSkillsStatusLine(line) {
7797
+ const progress = getActiveCliProgress();
7798
+ if (progress) {
7799
+ progress.writeLine(line);
7800
+ return;
7801
+ }
7802
+ process.stderr.write(`${line}
7803
+ `);
7804
+ }
7216
7805
  async function syncSdkSkillsIfNeeded(baseUrl) {
7217
7806
  if (attemptedSync || shouldSkipSkillsSync()) return;
7218
7807
  attemptedSync = true;
7219
7808
  const localVersion = readLocalSkillsVersion(baseUrl);
7220
7809
  const update = await fetchSkillsUpdate(baseUrl, localVersion);
7221
7810
  if (!update?.needsUpdate || !update.remoteVersion) return;
7222
- const progress = getActiveCliProgress();
7223
- progress?.writeLine("SDK skills changed; syncing deepline-sdk skill...") ?? process.stderr.write("SDK skills changed; syncing deepline-sdk skill...\n");
7811
+ writeSdkSkillsStatusLine("SDK skills changed; syncing deepline-sdk skill...");
7224
7812
  const installed = await runSkillsInstall(baseUrl);
7225
7813
  if (!installed) return;
7226
7814
  writeLocalSkillsVersion(baseUrl, update.remoteVersion);
7227
- progress?.writeLine("SDK skills are up to date.") ?? process.stderr.write("SDK skills are up to date.\n");
7815
+ writeSdkSkillsStatusLine("SDK skills are up to date.");
7228
7816
  }
7229
7817
 
7230
7818
  // src/cli/trace.ts
@@ -7386,4 +7974,3 @@ Output:
7386
7974
  process.exit(process.exitCode ?? 0);
7387
7975
  }
7388
7976
  main();
7389
- //# sourceMappingURL=index.mjs.map