deepline 0.1.12 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +14 -6
  2. package/dist/cli/index.js +1346 -717
  3. package/dist/cli/index.mjs +1342 -713
  4. package/dist/index.d.mts +199 -23
  5. package/dist/index.d.ts +199 -23
  6. package/dist/index.js +221 -14
  7. package/dist/index.mjs +221 -14
  8. package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +214 -77
  9. package/dist/repo/apps/play-runner-workers/src/dedup-do.ts +85 -60
  10. package/dist/repo/apps/play-runner-workers/src/entry.ts +385 -66
  11. package/dist/repo/sdk/src/client.ts +237 -0
  12. package/dist/repo/sdk/src/config.ts +125 -8
  13. package/dist/repo/sdk/src/http.ts +29 -5
  14. package/dist/repo/sdk/src/play.ts +19 -36
  15. package/dist/repo/sdk/src/plays/bundle-play-file.ts +22 -8
  16. package/dist/repo/sdk/src/plays/local-file-discovery.ts +207 -160
  17. package/dist/repo/sdk/src/types.ts +25 -0
  18. package/dist/repo/sdk/src/version.ts +2 -2
  19. package/dist/repo/shared_libs/play-runtime/tool-result.ts +237 -145
  20. package/dist/repo/shared_libs/plays/bundling/index.ts +206 -229
  21. package/dist/repo/shared_libs/plays/dataset.ts +28 -0
  22. package/dist/repo/shared_libs/plays/row-identity.ts +59 -4
  23. package/package.json +5 -4
  24. package/dist/cli/index.js.map +0 -1
  25. package/dist/cli/index.mjs.map +0 -1
  26. package/dist/index.js.map +0 -1
  27. package/dist/index.mjs.map +0 -1
  28. package/dist/repo/apps/play-runner-workers/src/runtime/README.md +0 -21
  29. package/dist/repo/apps/play-runner-workers/src/runtime/batching.ts +0 -177
  30. package/dist/repo/apps/play-runner-workers/src/runtime/execution-plan.ts +0 -52
  31. package/dist/repo/apps/play-runner-workers/src/runtime/tool-batch.ts +0 -100
  32. package/dist/repo/sdk/src/cli/commands/auth.ts +0 -500
  33. package/dist/repo/sdk/src/cli/commands/billing.ts +0 -188
  34. package/dist/repo/sdk/src/cli/commands/csv.ts +0 -123
  35. package/dist/repo/sdk/src/cli/commands/db.ts +0 -119
  36. package/dist/repo/sdk/src/cli/commands/feedback.ts +0 -40
  37. package/dist/repo/sdk/src/cli/commands/org.ts +0 -117
  38. package/dist/repo/sdk/src/cli/commands/play.ts +0 -3441
  39. package/dist/repo/sdk/src/cli/commands/tools.ts +0 -687
  40. package/dist/repo/sdk/src/cli/dataset-stats.ts +0 -415
  41. package/dist/repo/sdk/src/cli/index.ts +0 -148
  42. package/dist/repo/sdk/src/cli/progress.ts +0 -149
  43. package/dist/repo/sdk/src/cli/skills-sync.ts +0 -141
  44. package/dist/repo/sdk/src/cli/trace.ts +0 -61
  45. package/dist/repo/sdk/src/cli/utils.ts +0 -145
  46. package/dist/repo/sdk/src/compat.ts +0 -77
  47. package/dist/repo/shared_libs/observability/node-tracing.ts +0 -129
  48. package/dist/repo/shared_libs/observability/tracing.ts +0 -98
  49. package/dist/repo/shared_libs/play-runtime/context.ts +0 -4242
  50. package/dist/repo/shared_libs/play-runtime/ctx-contract.ts +0 -250
  51. package/dist/repo/shared_libs/play-runtime/ctx-types.ts +0 -725
  52. package/dist/repo/shared_libs/play-runtime/dataset-id.ts +0 -10
  53. package/dist/repo/shared_libs/play-runtime/db-session-crypto.ts +0 -304
  54. package/dist/repo/shared_libs/play-runtime/db-session.ts +0 -462
  55. package/dist/repo/shared_libs/play-runtime/live-events.ts +0 -214
  56. package/dist/repo/shared_libs/play-runtime/live-state-contract.ts +0 -50
  57. package/dist/repo/shared_libs/play-runtime/map-execution-frame.ts +0 -114
  58. package/dist/repo/shared_libs/play-runtime/map-row-identity.ts +0 -158
  59. package/dist/repo/shared_libs/play-runtime/progress-emitter.ts +0 -172
  60. package/dist/repo/shared_libs/play-runtime/protocol.ts +0 -121
  61. package/dist/repo/shared_libs/play-runtime/public-play-contract.ts +0 -42
  62. package/dist/repo/shared_libs/play-runtime/result-normalization.ts +0 -33
  63. package/dist/repo/shared_libs/play-runtime/runtime-api.ts +0 -1873
  64. package/dist/repo/shared_libs/play-runtime/runtime-constraints.ts +0 -2
  65. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-neon-serverless.ts +0 -201
  66. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-pg.ts +0 -48
  67. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver.ts +0 -84
  68. package/dist/repo/shared_libs/play-runtime/static-pipeline-types.ts +0 -147
  69. package/dist/repo/shared_libs/play-runtime/suspension.ts +0 -68
  70. package/dist/repo/shared_libs/play-runtime/tracing.ts +0 -31
  71. package/dist/repo/shared_libs/play-runtime/waterfall-replay.ts +0 -75
  72. package/dist/repo/shared_libs/play-runtime/worker-api-types.ts +0 -140
  73. package/dist/repo/shared_libs/plays/artifact-transport.ts +0 -14
  74. package/dist/repo/shared_libs/plays/artifact-types.ts +0 -49
  75. package/dist/repo/shared_libs/plays/compiler-manifest.ts +0 -186
  76. package/dist/repo/shared_libs/plays/definition.ts +0 -264
  77. package/dist/repo/shared_libs/plays/file-refs.ts +0 -11
  78. package/dist/repo/shared_libs/plays/rate-limit-scheduler.ts +0 -206
  79. package/dist/repo/shared_libs/plays/resolve-static-pipeline.ts +0 -164
  80. package/dist/repo/shared_libs/plays/runtime-validation.ts +0 -395
  81. package/dist/repo/shared_libs/temporal/constants.ts +0 -39
  82. package/dist/repo/shared_libs/temporal/preview-config.ts +0 -153
@@ -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.20";
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";
@@ -276,7 +350,8 @@ var HttpClient = class {
276
350
  parsed = body;
277
351
  }
278
352
  if (!response.ok) {
279
- const msg = typeof parsed === "object" && parsed && "error" in parsed ? String(parsed.error) : `HTTP ${response.status}`;
353
+ const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
354
+ const msg = typeof errorValue === "string" ? errorValue : errorValue && typeof errorValue === "object" && "message" in errorValue && typeof errorValue.message === "string" ? errorValue.message : typeof parsed === "object" && parsed && "message" in parsed && typeof parsed.message === "string" ? parsed.message : `HTTP ${response.status}`;
280
355
  throw new DeeplineError(msg, response.status, "API_ERROR", {
281
356
  response: parsed
282
357
  });
@@ -363,8 +438,12 @@ var HttpClient = class {
363
438
  * @param path - API path
364
439
  * @param body - Request body (will be JSON-serialized)
365
440
  */
366
- async post(path, body) {
367
- return this.request(path, { method: "POST", body });
441
+ async post(path, body, headers) {
442
+ return this.request(path, {
443
+ method: "POST",
444
+ body,
445
+ headers
446
+ });
368
447
  }
369
448
  /**
370
449
  * Send a DELETE request.
@@ -444,6 +523,7 @@ function sleep(ms) {
444
523
 
445
524
  // src/client.ts
446
525
  var TERMINAL_PLAY_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
526
+ var INCLUDE_TOOL_METADATA_HEADER = "x-deepline-include-tool-metadata";
447
527
  function isRecord(value) {
448
528
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
449
529
  }
@@ -476,6 +556,7 @@ function mapLegacyTemporalStatus(status) {
476
556
  var DeeplineClient = class {
477
557
  http;
478
558
  config;
559
+ runs;
479
560
  /**
480
561
  * @param options - Optional overrides for API key, base URL, timeout, and retries.
481
562
  * @throws {@link ConfigError} if no API key can be resolved from any source.
@@ -483,6 +564,13 @@ var DeeplineClient = class {
483
564
  constructor(options) {
484
565
  this.config = resolveConfig(options);
485
566
  this.http = new HttpClient(this.config);
567
+ this.runs = {
568
+ get: (runId) => this.getRunStatus(runId),
569
+ list: (options2) => this.listRuns(options2),
570
+ tail: (runId, options2) => this.tailRun(runId, options2),
571
+ logs: (runId, options2) => this.getRunLogs(runId, options2),
572
+ stop: (runId, options2) => this.stopRun(runId, options2)
573
+ };
486
574
  }
487
575
  /** The resolved base URL this client is targeting (e.g. `"http://localhost:3000"`). */
488
576
  get baseUrl() {
@@ -571,6 +659,31 @@ var DeeplineClient = class {
571
659
  );
572
660
  return res.tools;
573
661
  }
662
+ /**
663
+ * Search available tools using Deepline's ranked backend search.
664
+ *
665
+ * This is the same discovery surface used by the legacy CLI: it ranks across
666
+ * tool metadata, categories, agent guidance, and input schema fields.
667
+ */
668
+ async searchTools(options = {}) {
669
+ const params = new URLSearchParams();
670
+ const query = options.query?.trim() ?? "";
671
+ params.set("q", query);
672
+ params.set(
673
+ "include_search_debug",
674
+ options.includeSearchDebug ? "true" : "false"
675
+ );
676
+ params.set("search_mode", options.searchMode ?? "v2");
677
+ if (options.categories?.trim()) {
678
+ params.set("categories", options.categories.trim());
679
+ }
680
+ if (options.searchTerms?.trim()) {
681
+ params.set("search_terms", options.searchTerms.trim());
682
+ }
683
+ return this.http.get(
684
+ `/api/v2/integrations/list?${params.toString()}`
685
+ );
686
+ }
574
687
  /**
575
688
  * Get detailed metadata for a single tool.
576
689
  *
@@ -606,12 +719,17 @@ var DeeplineClient = class {
606
719
  * Top-level fields such as `status`, `job_id`, and `billing` describe the
607
720
  * Deepline execution.
608
721
  */
609
- async executeTool(toolId, input) {
722
+ async executeTool(toolId, input, options) {
723
+ const headers = options?.includeToolMetadata ? { [INCLUDE_TOOL_METADATA_HEADER]: "true" } : void 0;
610
724
  return this.http.post(
611
725
  `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
612
- { payload: input }
726
+ { payload: input },
727
+ headers
613
728
  );
614
729
  }
730
+ async executeToolRaw(toolId, input, options) {
731
+ return this.executeTool(toolId, input, options);
732
+ }
615
733
  async queryCustomerDb(input) {
616
734
  return this.http.post("/api/v2/db/query", {
617
735
  sql: input.sql,
@@ -660,6 +778,7 @@ var DeeplineClient = class {
660
778
  ...request.revisionId ? { revisionId: request.revisionId } : {},
661
779
  ...request.artifactStorageKey ? { artifactStorageKey: request.artifactStorageKey } : {},
662
780
  ...request.sourceCode ? { sourceCode: request.sourceCode } : {},
781
+ ...request.sourceFiles ? { sourceFiles: request.sourceFiles } : {},
663
782
  ..."staticPipeline" in request ? { staticPipeline: request.staticPipeline } : {},
664
783
  ...request.artifactHash ? { artifactHash: request.artifactHash } : {},
665
784
  ...request.graphHash ? { graphHash: request.graphHash } : {},
@@ -684,6 +803,7 @@ var DeeplineClient = class {
684
803
  ...request.revisionId ? { revisionId: request.revisionId } : {},
685
804
  ...request.artifactStorageKey ? { artifactStorageKey: request.artifactStorageKey } : {},
686
805
  ...request.sourceCode ? { sourceCode: request.sourceCode } : {},
806
+ ...request.sourceFiles ? { sourceFiles: request.sourceFiles } : {},
687
807
  ..."staticPipeline" in request ? { staticPipeline: request.staticPipeline } : {},
688
808
  ...request.artifactHash ? { artifactHash: request.artifactHash } : {},
689
809
  ...request.graphHash ? { graphHash: request.graphHash } : {},
@@ -720,6 +840,7 @@ var DeeplineClient = class {
720
840
  const compilerManifest = input.compilerManifest ?? await this.compilePlayManifest({
721
841
  name: input.name,
722
842
  sourceCode: input.sourceCode,
843
+ sourceFiles: input.sourceFiles,
723
844
  artifact: input.artifact
724
845
  });
725
846
  return this.http.post("/api/v2/plays/artifacts", {
@@ -734,6 +855,7 @@ var DeeplineClient = class {
734
855
  compilerManifest: artifact.compilerManifest ?? await this.compilePlayManifest({
735
856
  name: artifact.name,
736
857
  sourceCode: artifact.sourceCode,
858
+ sourceFiles: artifact.sourceFiles,
737
859
  artifact: artifact.artifact
738
860
  })
739
861
  }))
@@ -760,11 +882,13 @@ var DeeplineClient = class {
760
882
  const compilerManifest = input.compilerManifest ?? await this.compilePlayManifest({
761
883
  name: input.name,
762
884
  sourceCode: input.sourceCode,
885
+ sourceFiles: input.sourceFiles,
763
886
  artifact: input.artifact
764
887
  });
765
888
  const registeredArtifact = await this.registerPlayArtifact({
766
889
  name: input.name,
767
890
  sourceCode: input.sourceCode,
891
+ sourceFiles: input.sourceFiles,
768
892
  artifact: input.artifact,
769
893
  compilerManifest,
770
894
  publish: false
@@ -824,11 +948,13 @@ var DeeplineClient = class {
824
948
  const compilerManifest = options?.compilerManifest ?? await this.compilePlayManifest({
825
949
  name,
826
950
  sourceCode,
951
+ sourceFiles: options?.sourceFiles,
827
952
  artifact
828
953
  });
829
954
  const registeredArtifact = await this.registerPlayArtifact({
830
955
  name,
831
956
  sourceCode,
957
+ sourceFiles: options?.sourceFiles,
832
958
  artifact,
833
959
  compilerManifest,
834
960
  publish: false
@@ -1012,6 +1138,112 @@ var DeeplineClient = class {
1012
1138
  );
1013
1139
  return response.runs ?? [];
1014
1140
  }
1141
+ /**
1142
+ * Get a run by id using the public runs resource model.
1143
+ *
1144
+ * This is the SDK equivalent of:
1145
+ *
1146
+ * ```bash
1147
+ * deepline runs get <run-id> --json
1148
+ * ```
1149
+ */
1150
+ async getRunStatus(runId) {
1151
+ const response = await this.http.get(
1152
+ `/api/v2/runs/${encodeURIComponent(runId)}`
1153
+ );
1154
+ return normalizePlayStatus(response);
1155
+ }
1156
+ /**
1157
+ * List play runs using the public runs resource model.
1158
+ *
1159
+ * This is the SDK equivalent of:
1160
+ *
1161
+ * ```bash
1162
+ * deepline runs list --play <play-name> --status failed --json
1163
+ * ```
1164
+ */
1165
+ async listRuns(options) {
1166
+ const playName = options.play.trim();
1167
+ if (!playName) {
1168
+ throw new Error("runs.list requires options.play.");
1169
+ }
1170
+ const params = new URLSearchParams({ play: playName });
1171
+ const status = options.status?.trim();
1172
+ if (status) {
1173
+ params.set("status", status);
1174
+ }
1175
+ const response = await this.http.get(
1176
+ `/api/v2/runs?${params.toString()}`
1177
+ );
1178
+ return response.runs ?? [];
1179
+ }
1180
+ /**
1181
+ * Fetch the lightweight tail status for a run using the public runs resource model.
1182
+ *
1183
+ * This is the SDK equivalent of:
1184
+ *
1185
+ * ```bash
1186
+ * deepline runs tail <run-id> --json
1187
+ * ```
1188
+ */
1189
+ async tailRun(runId, options) {
1190
+ 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;
1191
+ const params = new URLSearchParams();
1192
+ if (Number.isFinite(afterLogIndex)) {
1193
+ params.set("afterLogIndex", String(Number(afterLogIndex)));
1194
+ }
1195
+ if (typeof options?.waitMs === "number") {
1196
+ params.set("waitMs", String(options.waitMs));
1197
+ }
1198
+ if (options?.terminalOnly) {
1199
+ params.set("terminalOnly", "true");
1200
+ }
1201
+ const suffix = params.toString() ? `?${params.toString()}` : "";
1202
+ const response = await this.http.get(
1203
+ `/api/v2/runs/${encodeURIComponent(runId)}/tail${suffix}`
1204
+ );
1205
+ return normalizePlayStatus(response);
1206
+ }
1207
+ /**
1208
+ * Fetch persisted logs for a run using the public runs resource model.
1209
+ *
1210
+ * This is the SDK equivalent of:
1211
+ *
1212
+ * ```bash
1213
+ * deepline runs logs <run-id> --limit 200 --json
1214
+ * ```
1215
+ */
1216
+ async getRunLogs(runId, options) {
1217
+ const status = await this.getRunStatus(runId);
1218
+ const logs = status.progress?.logs ?? [];
1219
+ const limit = typeof options?.limit === "number" && Number.isFinite(options.limit) ? Math.max(0, Math.trunc(options.limit)) : 200;
1220
+ const entries = logs.slice(Math.max(0, logs.length - limit));
1221
+ return {
1222
+ runId: status.runId,
1223
+ totalCount: logs.length,
1224
+ returnedCount: entries.length,
1225
+ firstSequence: logs.length === 0 ? null : logs.length - entries.length + 1,
1226
+ lastSequence: logs.length === 0 ? null : logs.length,
1227
+ truncated: logs.length > entries.length,
1228
+ hasMore: logs.length > entries.length,
1229
+ entries
1230
+ };
1231
+ }
1232
+ /**
1233
+ * Stop a run by id using the public runs resource model.
1234
+ *
1235
+ * This is the SDK equivalent of:
1236
+ *
1237
+ * ```bash
1238
+ * deepline runs stop <run-id> --reason "stale lock" --json
1239
+ * ```
1240
+ */
1241
+ async stopRun(runId, options) {
1242
+ return this.http.post(
1243
+ `/api/v2/runs/${encodeURIComponent(runId)}/stop`,
1244
+ options?.reason ? { reason: options.reason } : {}
1245
+ );
1246
+ }
1015
1247
  async listPlays() {
1016
1248
  const response = await this.http.get(
1017
1249
  "/api/v2/plays"
@@ -1398,6 +1630,7 @@ function saveEnvValues(values, baseUrl) {
1398
1630
  const merged = { ...existing, ...values };
1399
1631
  const lines = Object.entries(merged).filter(([, v]) => v !== "").map(([k, v]) => `${k}=${v}`);
1400
1632
  writeFileSync2(filePath, lines.join("\n") + "\n", "utf-8");
1633
+ saveProjectDeeplineEnvValues(baseUrl, values);
1401
1634
  }
1402
1635
  async function httpJson(method, url, apiKey, body) {
1403
1636
  const headers = { "Content-Type": "application/json" };
@@ -1998,6 +2231,9 @@ function rowArray(value) {
1998
2231
  function readNumber(value) {
1999
2232
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : null;
2000
2233
  }
2234
+ function numericStat(value) {
2235
+ return readNumber(value) ?? 0;
2236
+ }
2001
2237
  function inferColumns(rows) {
2002
2238
  const columns = [];
2003
2239
  const seen = /* @__PURE__ */ new Set();
@@ -2077,6 +2313,31 @@ function extractCanonicalRowsInfo(statusOrResult) {
2077
2313
  function percentText(numerator, denominator) {
2078
2314
  return denominator > 0 ? `${numerator}/${denominator} (${Math.round(100 * numerator / denominator)}%)` : "0/0 (0%)";
2079
2315
  }
2316
+ function isDatasetExecutionStatsInput(value) {
2317
+ return isRecord2(value) && isRecord2(value.columnStats) && Object.values(value.columnStats).every(isRecord2);
2318
+ }
2319
+ function extractDatasetExecutionStats(statusOrResult) {
2320
+ if (!isRecord2(statusOrResult)) {
2321
+ return null;
2322
+ }
2323
+ const direct = statusOrResult.dataset_execution_stats;
2324
+ if (isDatasetExecutionStatsInput(direct)) {
2325
+ return direct;
2326
+ }
2327
+ const nested = isRecord2(statusOrResult.result) ? statusOrResult.result.dataset_execution_stats : null;
2328
+ return isDatasetExecutionStatsInput(nested) ? nested : null;
2329
+ }
2330
+ function formatExecutionStats(raw, denominator) {
2331
+ return {
2332
+ queued: percentText(numericStat(raw.queued), denominator),
2333
+ running: percentText(numericStat(raw.running), denominator),
2334
+ "completed:executed": percentText(numericStat(raw.completed), denominator),
2335
+ "completed:reused": percentText(numericStat(raw.cached), denominator),
2336
+ "skipped:condition": percentText(numericStat(raw.skipped), denominator),
2337
+ "skipped:missed": percentText(numericStat(raw.missed), denominator),
2338
+ failed: percentText(numericStat(raw.failed), denominator)
2339
+ };
2340
+ }
2080
2341
  function countPercentText(count, denominator) {
2081
2342
  return denominator > 0 ? `${count} (${Math.round(100 * count / denominator)}%)` : "0 (0%)";
2082
2343
  }
@@ -2169,7 +2430,7 @@ function compactCell(value) {
2169
2430
  }
2170
2431
  return compactScalar(parsed, 120);
2171
2432
  }
2172
- function buildDatasetStats(rows, totalRows = rows.length, columns = inferColumns(rows)) {
2433
+ function buildDatasetStats(rows, totalRows = rows.length, columns = inferColumns(rows), executionStats) {
2173
2434
  const sanitized = sanitizeCsvProjectionInfo({ rows, columns });
2174
2435
  const columnStats = {};
2175
2436
  for (const column of sanitized.columns) {
@@ -2197,6 +2458,10 @@ function buildDatasetStats(rows, totalRows = rows.length, columns = inferColumns
2197
2458
  non_empty: percentText(nonEmpty, denominator),
2198
2459
  unique: valueCounts.size
2199
2460
  };
2461
+ const rawExecutionStats = executionStats?.columnStats[column];
2462
+ if (rawExecutionStats) {
2463
+ stat3.execution = formatExecutionStats(rawExecutionStats, totalRows);
2464
+ }
2200
2465
  if (sampleValue !== void 0 && sampleValueType) {
2201
2466
  stat3.sample_value = sampleValue;
2202
2467
  stat3.sample_type = sampleValueType;
@@ -2549,7 +2814,6 @@ import { tmpdir } from "os";
2549
2814
  import { basename, dirname as dirname3, extname, isAbsolute, join as join3, resolve as resolve4 } from "path";
2550
2815
  import { builtinModules, createRequire } from "module";
2551
2816
  import { build } from "esbuild";
2552
- import ts from "typescript";
2553
2817
 
2554
2818
  // ../shared_libs/play-runtime/backend.ts
2555
2819
  var PLAY_RUNTIME_BACKENDS = {
@@ -2633,56 +2897,6 @@ function formatEsbuildMessage(message) {
2633
2897
  const location = message.location ? `${message.location.file}:${message.location.line}:${message.location.column}` : null;
2634
2898
  return location ? `${location} ${message.text}` : message.text;
2635
2899
  }
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
2900
  function isLocalSpecifier(specifier) {
2687
2901
  return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/") || specifier.startsWith("file:");
2688
2902
  }
@@ -2706,11 +2920,8 @@ function assertWithinPlayWorkspace(input) {
2706
2920
  if (isPathInsideDirectory(input.resolvedPath, input.workspace.rootDir)) {
2707
2921
  return;
2708
2922
  }
2709
- const position = input.sourceFile.getLineAndCharacterOfPosition(
2710
- input.node.getStart(input.sourceFile)
2711
- );
2712
2923
  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.`
2924
+ `${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
2925
  );
2715
2926
  }
2716
2927
  function getPackageName(specifier) {
@@ -2720,72 +2931,135 @@ function getPackageName(specifier) {
2720
2931
  }
2721
2932
  return specifier.split("/")[0] ?? specifier;
2722
2933
  }
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
2934
  function isPlaySourceFile(filePath) {
2741
2935
  return PLAY_SOURCE_FILE_PATTERN.test(filePath);
2742
2936
  }
2743
- function extractStringLiteralProperty(objectLiteral, propertyName) {
2744
- for (const property of objectLiteral.properties) {
2745
- if (!ts.isPropertyAssignment(property)) {
2937
+ function stripCommentsToSpaces(source) {
2938
+ return source.replace(/\/\*[\s\S]*?\*\//g, (match) => match.replace(/[^\n]/g, " ")).replace(
2939
+ /(^|[^:])\/\/.*$/gm,
2940
+ (match, prefix) => prefix + " ".repeat(Math.max(0, match.length - prefix.length))
2941
+ );
2942
+ }
2943
+ function lineAndColumnAt(source, index) {
2944
+ const prefix = source.slice(0, index);
2945
+ const lines = prefix.split("\n");
2946
+ return { line: lines.length, column: lines[lines.length - 1].length + 1 };
2947
+ }
2948
+ function findSourceImportReferences(sourceCode) {
2949
+ const source = stripCommentsToSpaces(sourceCode);
2950
+ const references = [];
2951
+ const addReference = (specifier, specifierIndex, kind) => {
2952
+ if (!specifier) return;
2953
+ const position = lineAndColumnAt(sourceCode, specifierIndex);
2954
+ references.push({
2955
+ specifier,
2956
+ line: position.line,
2957
+ column: position.column,
2958
+ kind
2959
+ });
2960
+ };
2961
+ const staticImportPattern = /\b(?:import|export)\s+(?!type\b)(?:[\s\S]*?\s+from\s*)?(['"])([^'"\n]+)\1/g;
2962
+ for (const match of source.matchAll(staticImportPattern)) {
2963
+ addReference(match[2], match.index + match[0].lastIndexOf(match[1]), "static");
2964
+ }
2965
+ const dynamicImportPattern = /\bimport\s*\(\s*(['"])([^'"\n]+)\1/g;
2966
+ for (const match of source.matchAll(dynamicImportPattern)) {
2967
+ addReference(match[2], match.index + match[0].lastIndexOf(match[1]), "dynamic-import");
2968
+ }
2969
+ const requirePattern = /\brequire\s*\(\s*(['"])([^'"\n]+)\1/g;
2970
+ for (const match of source.matchAll(requirePattern)) {
2971
+ addReference(match[2], match.index + match[0].lastIndexOf(match[1]), "require");
2972
+ }
2973
+ const literalDynamicImportIndexes = new Set(
2974
+ [...source.matchAll(dynamicImportPattern)].map((match) => match.index)
2975
+ );
2976
+ for (const match of source.matchAll(/\bimport\s*\(/g)) {
2977
+ if (literalDynamicImportIndexes.has(match.index)) continue;
2978
+ const position = lineAndColumnAt(sourceCode, match.index);
2979
+ throw new Error(
2980
+ `:${position.line}:${position.column} Dynamic import() is not allowed in plays. Use static imports instead.`
2981
+ );
2982
+ }
2983
+ const literalRequireIndexes = new Set(
2984
+ [...source.matchAll(requirePattern)].map((match) => match.index)
2985
+ );
2986
+ for (const match of source.matchAll(/\brequire\s*\(/g)) {
2987
+ if (literalRequireIndexes.has(match.index)) continue;
2988
+ const position = lineAndColumnAt(sourceCode, match.index);
2989
+ throw new Error(
2990
+ `:${position.line}:${position.column} Dynamic require() is not allowed in plays. Use static imports or require("literal") only.`
2991
+ );
2992
+ }
2993
+ return references.sort(
2994
+ (left, right) => left.line === right.line ? left.column - right.column : left.line - right.line
2995
+ );
2996
+ }
2997
+ function unquoteStringLiteral(literal) {
2998
+ const trimmed = literal.trim();
2999
+ const quote = trimmed[0];
3000
+ if (quote !== '"' && quote !== "'" || trimmed[trimmed.length - 1] !== quote) {
3001
+ return null;
3002
+ }
3003
+ try {
3004
+ return JSON.parse(quote === '"' ? trimmed : `"${trimmed.slice(1, -1).replace(/"/g, '\\"')}"`);
3005
+ } catch {
3006
+ return trimmed.slice(1, -1);
3007
+ }
3008
+ }
3009
+ function findMatchingBrace(source, openIndex) {
3010
+ let depth = 0;
3011
+ let quote = null;
3012
+ let escaped = false;
3013
+ for (let index = openIndex; index < source.length; index += 1) {
3014
+ const char = source[index];
3015
+ if (quote) {
3016
+ if (escaped) {
3017
+ escaped = false;
3018
+ } else if (char === "\\") {
3019
+ escaped = true;
3020
+ } else if (char === quote) {
3021
+ quote = null;
3022
+ }
2746
3023
  continue;
2747
3024
  }
2748
- const name = property.name;
2749
- const matches = ts.isIdentifier(name) && name.text === propertyName || ts.isStringLiteralLike(name) && name.text === propertyName;
2750
- if (!matches) {
3025
+ if (char === '"' || char === "'" || char === "`") {
3026
+ quote = char;
2751
3027
  continue;
2752
3028
  }
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
- }
3029
+ if (char === "{") depth += 1;
3030
+ if (char === "}") {
3031
+ depth -= 1;
3032
+ if (depth === 0) return index;
3033
+ }
3034
+ }
3035
+ return -1;
3036
+ }
3037
+ function extractDefinedPlayName(sourceCode, _filePath) {
3038
+ const source = stripCommentsToSpaces(sourceCode);
3039
+ const callPattern = /(?:\b[A-Za-z_$][\w$]*\s*\.\s*)?\b(?:definePlay|defineWorkflow)\s*\(/g;
3040
+ for (const match of source.matchAll(callPattern)) {
3041
+ const openParen = match.index + match[0].length - 1;
3042
+ const firstArgStart = openParen + 1;
3043
+ const firstNonSpace = source.slice(firstArgStart).search(/\S/);
3044
+ if (firstNonSpace < 0) continue;
3045
+ const argIndex = firstArgStart + firstNonSpace;
3046
+ const quote = source[argIndex];
3047
+ if (quote === '"' || quote === "'") {
3048
+ const literalMatch = source.slice(argIndex).match(/^(['"])(?:\\.|(?!\1)[\s\S])*\1/);
3049
+ const value = literalMatch ? unquoteStringLiteral(literalMatch[0]) : null;
3050
+ if (value?.trim()) return value.trim();
3051
+ }
3052
+ if (quote === "{") {
3053
+ const closeBrace = findMatchingBrace(source, argIndex);
3054
+ if (closeBrace < 0) continue;
3055
+ const objectSource = source.slice(argIndex + 1, closeBrace);
3056
+ const idMatch = objectSource.match(/(?:^|[,{\s])(?:id|['"]id['"])\s*:\s*(['"])([\s\S]*?)\1/);
3057
+ if (idMatch?.[2]?.trim()) {
3058
+ return idMatch[2].trim();
2783
3059
  }
2784
3060
  }
2785
- ts.forEachChild(node, visit);
2786
- };
2787
- visit(sourceFile);
2788
- return detectedPlayName;
3061
+ }
3062
+ return null;
2789
3063
  }
2790
3064
  function getPackageRequireCandidates(fromFile) {
2791
3065
  const candidates = [
@@ -3095,18 +3369,10 @@ async function analyzeSourceGraph(entryFile, adapter) {
3095
3369
  if (extname(absolutePath).toLowerCase() === ".json") {
3096
3370
  return;
3097
3371
  }
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) => {
3372
+ const handleSpecifier = async (specifier, line, column, kind) => {
3106
3373
  if (kind === "dynamic-import") {
3107
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3108
3374
  throw new Error(
3109
- `${absolutePath}:${position.line + 1}:${position.character + 1} Dynamic import() is not allowed in plays. Use static imports instead.`
3375
+ `${absolutePath}:${line}:${column} Dynamic import() is not allowed in plays. Use static imports instead.`
3110
3376
  );
3111
3377
  }
3112
3378
  if (NODE_BUILTIN_SET.has(specifier)) {
@@ -3120,16 +3386,15 @@ async function analyzeSourceGraph(entryFile, adapter) {
3120
3386
  specifier,
3121
3387
  resolvedPath: resolved,
3122
3388
  workspace,
3123
- sourceFile,
3124
- node
3389
+ line,
3390
+ column
3125
3391
  });
3126
3392
  if (resolved !== absoluteEntryFile && isPlaySourceFile(resolved)) {
3127
3393
  const importedSource = await readFile(resolved, "utf-8");
3128
3394
  const importedPlayName = extractDefinedPlayName(importedSource, resolved);
3129
3395
  if (!importedPlayName) {
3130
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3131
3396
  throw new Error(
3132
- `${absolutePath}:${position.line + 1}:${position.character + 1} Imported play file "${specifier}" must export definePlay(...) so it can be runtime-composed.`
3397
+ `${absolutePath}:${line}:${column} Imported play file "${specifier}" must export definePlay(...) so it can be runtime-composed.`
3133
3398
  );
3134
3399
  }
3135
3400
  importedPlayDependencies.set(resolved, {
@@ -3142,44 +3407,28 @@ async function analyzeSourceGraph(entryFile, adapter) {
3142
3407
  return;
3143
3408
  }
3144
3409
  if (specifier.includes(":")) {
3145
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
3146
3410
  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.`
3411
+ `${absolutePath}:${line}:${column} Unsupported import specifier "${specifier}". Allowed imports are relative files, Node builtins, and installed packages.`
3148
3412
  );
3149
3413
  }
3150
3414
  const packageImport = resolvePackageImport(specifier, absolutePath, adapter);
3151
3415
  packages.set(packageImport.name, packageImport.version);
3152
3416
  };
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
- }
3417
+ try {
3418
+ for (const reference of findSourceImportReferences(sourceCode2)) {
3419
+ await handleSpecifier(
3420
+ reference.specifier,
3421
+ reference.line,
3422
+ reference.column,
3423
+ reference.kind
3424
+ );
3177
3425
  }
3178
- for (const child of node.getChildren(sourceFile)) {
3179
- await walk(child);
3426
+ } catch (error) {
3427
+ if (error instanceof Error && error.message.startsWith(":")) {
3428
+ throw new Error(`${absolutePath}${error.message}`);
3180
3429
  }
3181
- };
3182
- await walk(sourceFile);
3430
+ throw error;
3431
+ }
3183
3432
  };
3184
3433
  await visitFile(absoluteEntryFile);
3185
3434
  const sourceCode = localFiles.get(absoluteEntryFile) ?? "";
@@ -3199,6 +3448,11 @@ async function analyzeSourceGraph(entryFile, adapter) {
3199
3448
  const playName = extractDefinedPlayName(sourceCode, absoluteEntryFile);
3200
3449
  return {
3201
3450
  sourceCode,
3451
+ sourceFiles: Object.fromEntries(
3452
+ [...localFiles.entries()].sort(
3453
+ (left, right) => left[0].localeCompare(right[0])
3454
+ )
3455
+ ),
3202
3456
  sourceHash,
3203
3457
  graphHash,
3204
3458
  importPolicy: {
@@ -3262,8 +3516,7 @@ function normalizeSourceMapForRuntime(sourceMapText) {
3262
3516
  parsed.sourceRoot = void 0;
3263
3517
  return JSON.stringify(parsed);
3264
3518
  }
3265
- function getBundleSizeError(filePath, bundledCode, artifactKind) {
3266
- const bundleBytes = Buffer.byteLength(bundledCode, "utf8");
3519
+ function getBundleSizeErrorForBytes(filePath, bundleBytes, artifactKind) {
3267
3520
  if (bundleBytes > MAX_PLAY_BUNDLE_BYTES) {
3268
3521
  return `${filePath} Play bundle exceeds the 30 MiB limit (${bundleBytes} bytes > ${MAX_PLAY_BUNDLE_BYTES} bytes).`;
3269
3522
  }
@@ -3274,6 +3527,13 @@ function getBundleSizeError(filePath, bundledCode, artifactKind) {
3274
3527
  }
3275
3528
  return null;
3276
3529
  }
3530
+ function getBundleSizeError(filePath, bundledCode, artifactKind) {
3531
+ return getBundleSizeErrorForBytes(
3532
+ filePath,
3533
+ Buffer.byteLength(bundledCode, "utf8"),
3534
+ artifactKind
3535
+ );
3536
+ }
3277
3537
  async function runEsbuildForCjsNode(entryFile, importedPlayDependencies, adapter, exportName) {
3278
3538
  const sdkAliasPlugin = localSdkAliasPlugin(adapter);
3279
3539
  const playProxyPlugin = importedPlayProxyPlugin(importedPlayDependencies);
@@ -3406,6 +3666,23 @@ entry-export:${exportName}`
3406
3666
  workers-harness:${harnessFingerprint}`
3407
3667
  );
3408
3668
  }
3669
+ const typecheckErrors = [
3670
+ ...await adapter.typecheckPlaySource?.({
3671
+ sourceCode: analysis.sourceCode,
3672
+ sourcePath: absolutePath,
3673
+ importedFilePaths: [
3674
+ ...analysis.importPolicy.localFiles,
3675
+ ...analysis.importedPlayDependencies.map((dependency) => dependency.filePath)
3676
+ ]
3677
+ }) ?? []
3678
+ ];
3679
+ if (typecheckErrors.length > 0) {
3680
+ return {
3681
+ success: false,
3682
+ filePath: absolutePath,
3683
+ errors: typecheckErrors
3684
+ };
3685
+ }
3409
3686
  const cachedArtifact = await readArtifactCache(analysis.graphHash, target, adapter);
3410
3687
  const discoveredFiles = await adapter.discoverPackagedLocalFiles(absolutePath);
3411
3688
  if (cachedArtifact) {
@@ -3425,6 +3702,7 @@ workers-harness:${harnessFingerprint}`
3425
3702
  success: true,
3426
3703
  artifact: { ...cachedArtifact, cacheHit: true },
3427
3704
  sourceCode: analysis.sourceCode,
3705
+ sourceFiles: analysis.sourceFiles,
3428
3706
  filePath: absolutePath,
3429
3707
  playName: analysis.playName,
3430
3708
  packagedFiles: discoveredFiles.files,
@@ -3432,24 +3710,6 @@ workers-harness:${harnessFingerprint}`
3432
3710
  importedPlayDependencies: analysis.importedPlayDependencies
3433
3711
  };
3434
3712
  }
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
3713
  const buildOutcome = target === PLAY_ARTIFACT_KINDS.esmWorkers ? await runEsbuildForEsmWorkers(absolutePath, analysis.importedPlayDependencies, adapter, exportName) : await runEsbuildForCjsNode(absolutePath, analysis.importedPlayDependencies, adapter, exportName);
3454
3714
  if (Array.isArray(buildOutcome)) {
3455
3715
  return {
@@ -3499,6 +3759,7 @@ workers-harness:${harnessFingerprint}`
3499
3759
  success: true,
3500
3760
  artifact,
3501
3761
  sourceCode: analysis.sourceCode,
3762
+ sourceFiles: analysis.sourceFiles,
3502
3763
  filePath: absolutePath,
3503
3764
  playName: analysis.playName,
3504
3765
  packagedFiles: discoveredFiles.files,
@@ -3580,7 +3841,6 @@ function resolveExecutionProfile(override) {
3580
3841
  import { createHash as createHash2 } from "crypto";
3581
3842
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
3582
3843
  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
3844
  var SOURCE_EXTENSIONS2 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".json"];
3585
3845
  function sha2562(buffer) {
3586
3846
  return createHash2("sha256").update(buffer).digest("hex");
@@ -3592,94 +3852,181 @@ function contentTypeForFile(filePath) {
3592
3852
  if (extension === ".txt") return "text/plain";
3593
3853
  return "application/octet-stream";
3594
3854
  }
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));
3855
+ function stripCommentsToSpaces2(source) {
3856
+ return source.replace(/\/\*[\s\S]*?\*\//g, (match) => match.replace(/[^\n]/g, " ")).replace(
3857
+ /(^|[^:])\/\/.*$/gm,
3858
+ (match, prefix) => prefix + " ".repeat(Math.max(0, match.length - prefix.length))
3859
+ );
3610
3860
  }
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);
3861
+ function unquoteStringLiteral2(literal) {
3862
+ const trimmed = literal.trim();
3863
+ const quote = trimmed[0];
3864
+ if (quote !== '"' && quote !== "'" || trimmed[trimmed.length - 1] !== quote) {
3865
+ return null;
3623
3866
  }
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);
3867
+ try {
3868
+ return JSON.parse(quote === '"' ? trimmed : `"${trimmed.slice(1, -1).replace(/"/g, '\\"')}"`);
3869
+ } catch {
3870
+ return trimmed.slice(1, -1);
3626
3871
  }
3627
- if (ts2.isConditionalExpression(node)) {
3628
- return isRuntimeInputExpression(node.condition) || isRuntimeInputExpression(node.whenTrue) || isRuntimeInputExpression(node.whenFalse);
3872
+ }
3873
+ function splitTopLevelPlus(expression) {
3874
+ const parts = [];
3875
+ let start = 0;
3876
+ let depth = 0;
3877
+ let quote = null;
3878
+ let escaped = false;
3879
+ for (let index = 0; index < expression.length; index += 1) {
3880
+ const char = expression[index];
3881
+ if (quote) {
3882
+ if (escaped) {
3883
+ escaped = false;
3884
+ } else if (char === "\\") {
3885
+ escaped = true;
3886
+ } else if (char === quote) {
3887
+ quote = null;
3888
+ }
3889
+ continue;
3890
+ }
3891
+ if (char === '"' || char === "'" || char === "`") {
3892
+ quote = char;
3893
+ continue;
3894
+ }
3895
+ if (char === "(" || char === "[" || char === "{") depth += 1;
3896
+ if (char === ")" || char === "]" || char === "}") depth -= 1;
3897
+ if (char === "+" && depth === 0) {
3898
+ parts.push(expression.slice(start, index));
3899
+ start = index + 1;
3900
+ }
3629
3901
  }
3630
- return referencesInputIdentifier(node);
3902
+ if (parts.length === 0) return null;
3903
+ parts.push(expression.slice(start));
3904
+ return parts;
3631
3905
  }
3632
- function resolveStringExpression(node, constants) {
3633
- if (ts2.isStringLiteralLike(node) || ts2.isNoSubstitutionTemplateLiteral(node)) {
3634
- return node.text;
3906
+ function stripOuterParens(expression) {
3907
+ let value = expression.trim();
3908
+ while (value.startsWith("(") && value.endsWith(")")) {
3909
+ value = value.slice(1, -1).trim();
3635
3910
  }
3636
- if (ts2.isParenthesizedExpression(node)) {
3637
- return resolveStringExpression(node.expression, constants);
3911
+ return value;
3912
+ }
3913
+ function isRuntimeInputExpression(expression) {
3914
+ return /(^|[^\w$])input([^\w$]|$)/.test(expression);
3915
+ }
3916
+ function resolveStringExpression(expression, constants) {
3917
+ const value = stripOuterParens(expression);
3918
+ if (/^(['"])(?:\\.|(?!\1)[\s\S])*\1$/.test(value)) {
3919
+ return unquoteStringLiteral2(value);
3638
3920
  }
3639
- if (ts2.isIdentifier(node)) {
3640
- return constants.get(node.text) ?? null;
3921
+ if (/^`(?:\\.|[^`$]|\$(?!\{))*`$/.test(value)) {
3922
+ return value.slice(1, -1);
3641
3923
  }
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;
3924
+ if (/^[A-Za-z_$][\w$]*$/.test(value)) {
3925
+ return constants.get(value) ?? null;
3652
3926
  }
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;
3927
+ const parts = splitTopLevelPlus(value);
3928
+ if (parts) {
3929
+ const resolved = parts.map((part) => resolveStringExpression(part, constants));
3930
+ return resolved.every((part) => part != null) ? resolved.join("") : null;
3660
3931
  }
3661
3932
  return null;
3662
3933
  }
3663
- function collectTopLevelStringConstants(sourceFile) {
3934
+ function collectTopLevelStringConstants(sourceCode) {
3664
3935
  const constants = /* @__PURE__ */ new Map();
3665
- for (const statement of sourceFile.statements) {
3666
- if (!ts2.isVariableStatement(statement)) {
3936
+ const source = stripCommentsToSpaces2(sourceCode);
3937
+ for (const match of source.matchAll(/(?:^|\n)\s*const\s+([A-Za-z_$][\w$]*)\s*=\s*([^;\n]+)/g)) {
3938
+ const resolved = resolveStringExpression(match[2], constants);
3939
+ if (resolved != null) {
3940
+ constants.set(match[1], resolved);
3941
+ }
3942
+ }
3943
+ return constants;
3944
+ }
3945
+ function findMatchingGenericEnd(source, openIndex) {
3946
+ let depth = 0;
3947
+ let quote = null;
3948
+ let escaped = false;
3949
+ for (let index = openIndex; index < source.length; index += 1) {
3950
+ const char = source[index];
3951
+ if (quote) {
3952
+ if (escaped) {
3953
+ escaped = false;
3954
+ } else if (char === "\\") {
3955
+ escaped = true;
3956
+ } else if (char === quote) {
3957
+ quote = null;
3958
+ }
3667
3959
  continue;
3668
3960
  }
3669
- if (!(statement.declarationList.flags & ts2.NodeFlags.Const)) {
3961
+ if (char === '"' || char === "'" || char === "`") {
3962
+ quote = char;
3670
3963
  continue;
3671
3964
  }
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);
3965
+ if (char === "<") depth += 1;
3966
+ if (char === ">") {
3967
+ depth -= 1;
3968
+ if (depth === 0) return index;
3969
+ }
3970
+ }
3971
+ return -1;
3972
+ }
3973
+ function findCallOpenParen(source, afterCsvIndex) {
3974
+ let index = afterCsvIndex;
3975
+ while (/\s/.test(source[index] ?? "")) index += 1;
3976
+ if (source[index] === "<") {
3977
+ const genericEnd = findMatchingGenericEnd(source, index);
3978
+ if (genericEnd < 0) return -1;
3979
+ index = genericEnd + 1;
3980
+ while (/\s/.test(source[index] ?? "")) index += 1;
3981
+ }
3982
+ return source[index] === "(" ? index : -1;
3983
+ }
3984
+ function firstCallArgument(source, openParen) {
3985
+ let depth = 0;
3986
+ let quote = null;
3987
+ let escaped = false;
3988
+ const start = openParen + 1;
3989
+ for (let index = start; index < source.length; index += 1) {
3990
+ const char = source[index];
3991
+ if (quote) {
3992
+ if (escaped) {
3993
+ escaped = false;
3994
+ } else if (char === "\\") {
3995
+ escaped = true;
3996
+ } else if (char === quote) {
3997
+ quote = null;
3679
3998
  }
3999
+ continue;
4000
+ }
4001
+ if (char === '"' || char === "'" || char === "`") {
4002
+ quote = char;
4003
+ continue;
4004
+ }
4005
+ if (char === "(" || char === "[" || char === "{") depth += 1;
4006
+ if (char === ")" && depth === 0) {
4007
+ const text = source.slice(start, index).trim();
4008
+ return text ? { text, start, end: index } : null;
3680
4009
  }
4010
+ if (char === "," && depth === 0) {
4011
+ const text = source.slice(start, index).trim();
4012
+ return text ? { text, start, end: index } : null;
4013
+ }
4014
+ if (char === ")" || char === "]" || char === "}") depth -= 1;
3681
4015
  }
3682
- return constants;
4016
+ return null;
4017
+ }
4018
+ function localImportSpecifiers(sourceCode) {
4019
+ const source = stripCommentsToSpaces2(sourceCode);
4020
+ const specifiers = [];
4021
+ for (const match of source.matchAll(
4022
+ /\b(?:import|export)\s+(?!type\b)(?:[\s\S]*?\s+from\s*)?['"]([^'"]+)['"]/g
4023
+ )) {
4024
+ if (match[1]?.startsWith(".")) specifiers.push(match[1]);
4025
+ }
4026
+ for (const match of source.matchAll(/\brequire\s*\(\s*(['"])(\.[^'"]*)\1\s*\)/g)) {
4027
+ specifiers.push(match[2]);
4028
+ }
4029
+ return specifiers;
3683
4030
  }
3684
4031
  async function fileExists2(filePath) {
3685
4032
  try {
@@ -3724,69 +4071,60 @@ async function discoverPackagedLocalFiles(entryFile) {
3724
4071
  }
3725
4072
  visitedFiles.add(absolutePath);
3726
4073
  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);
4074
+ const scanSource = stripCommentsToSpaces2(sourceCode);
4075
+ const constants = collectTopLevelStringConstants(sourceCode);
3735
4076
  const childVisits = [];
3736
- const visitNode = async (node) => {
3737
- if (ts2.isCallExpression(node) && isCtxCsvCall(node)) {
3738
- const argument = node.arguments[0];
3739
- if (!argument) {
4077
+ for (const match of scanSource.matchAll(/\b([A-Za-z_$][\w$]*)\s*\.\s*csv\b/g)) {
4078
+ const target = match[1];
4079
+ if (target !== "ctx" && !target.endsWith("Ctx")) {
4080
+ continue;
4081
+ }
4082
+ const openParen = findCallOpenParen(scanSource, match.index + match[0].length);
4083
+ if (openParen < 0) {
4084
+ continue;
4085
+ }
4086
+ const argument = firstCallArgument(scanSource, openParen);
4087
+ if (!argument) {
4088
+ unresolved.push({
4089
+ sourceFragment: "ctx.csv()",
4090
+ message: "ctx.csv() requires a file path string or input reference."
4091
+ });
4092
+ } else if (!isRuntimeInputExpression(argument.text)) {
4093
+ const resolvedPath = resolveStringExpression(argument.text, constants);
4094
+ if (resolvedPath == null) {
3740
4095
  unresolved.push({
3741
- sourceFragment: "ctx.csv()",
3742
- message: "ctx.csv() requires a file path string or input reference."
4096
+ sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
4097
+ 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
4098
  });
3744
- } else if (!isRuntimeInputExpression(argument)) {
3745
- const resolvedPath = resolveStringExpression(argument, constants);
3746
- if (resolvedPath == null) {
4099
+ } else {
4100
+ const absoluteCsvPath = resolve5(dirname4(absolutePath), resolvedPath);
4101
+ if (isAbsolute2(resolvedPath) || !isPathInsideDirectory2(absoluteCsvPath, packagingRoot)) {
3747
4102
  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)
4103
+ sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
4104
+ message: "ctx.csv(...) packaged file paths must be relative paths inside the play directory. Pass external files at runtime with input.file instead."
3769
4105
  });
4106
+ continue;
3770
4107
  }
4108
+ const buffer = await readFile2(absoluteCsvPath);
4109
+ const stats = await stat2(absoluteCsvPath);
4110
+ files.set(absoluteCsvPath, {
4111
+ sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
4112
+ logicalPath: resolvedPath,
4113
+ absolutePath: absoluteCsvPath,
4114
+ bytes: stats.size,
4115
+ contentHash: sha2562(buffer),
4116
+ contentType: contentTypeForFile(absoluteCsvPath)
4117
+ });
3771
4118
  }
3772
4119
  }
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);
4120
+ }
4121
+ for (const specifier of localImportSpecifiers(sourceCode)) {
4122
+ childVisits.push(
4123
+ resolveLocalImport2(absolutePath, specifier).then(
4124
+ (resolvedImport) => visitSourceFile(resolvedImport)
4125
+ )
4126
+ );
4127
+ }
3790
4128
  await Promise.all(childVisits);
3791
4129
  };
3792
4130
  await visitSourceFile(absoluteEntryFile);
@@ -3797,7 +4135,7 @@ async function discoverPackagedLocalFiles(entryFile) {
3797
4135
  }
3798
4136
 
3799
4137
  // src/plays/bundle-play-file.ts
3800
- var PLAY_BUNDLE_CACHE_VERSION2 = 24;
4138
+ var PLAY_BUNDLE_CACHE_VERSION2 = 26;
3801
4139
  var MODULE_DIR = dirname5(fileURLToPath(import.meta.url));
3802
4140
  var SDK_PACKAGE_ROOT = resolve6(MODULE_DIR, "..", "..");
3803
4141
  var SOURCE_REPO_ROOT = resolve6(SDK_PACKAGE_ROOT, "..");
@@ -3812,7 +4150,7 @@ var PROJECT_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? SOURCE_REPO_ROOT : HAS_PACKAGED
3812
4150
  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
4151
  var SDK_PACKAGE_JSON = resolve6(SDK_PACKAGE_ROOT, "package.json");
3814
4152
  var SDK_ENTRY_FILE = resolve6(SDK_SOURCE_ROOT, "index.ts");
3815
- var SDK_TYPES_ENTRY_FILE = resolve6(SDK_PACKAGE_ROOT, "dist", "index.d.ts");
4153
+ var SDK_TYPES_ENTRY_FILE = HAS_SOURCE_BUNDLING_SOURCES ? SDK_ENTRY_FILE : resolve6(SDK_PACKAGE_ROOT, "dist", "index.d.ts");
3816
4154
  var SDK_WORKERS_ENTRY_FILE = resolve6(SDK_SOURCE_ROOT, "worker-play-entry.ts");
3817
4155
  var WORKERS_HARNESS_ENTRY_FILE = resolve6(PROJECT_ROOT, "apps", "play-runner-workers", "src", "entry.ts");
3818
4156
  var WORKERS_HARNESS_FILES_DIR = resolve6(PROJECT_ROOT, "apps", "play-runner-workers", "src");
@@ -3844,7 +4182,7 @@ function createSdkPlayBundlingAdapter() {
3844
4182
  sdkSourceRoot: SDK_SOURCE_ROOT,
3845
4183
  sdkPackageJson: SDK_PACKAGE_JSON,
3846
4184
  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,
4185
+ sdkTypesEntryFile: HAS_SOURCE_BUNDLING_SOURCES || !existsSync3(SDK_TYPES_ENTRY_FILE) ? SDK_ENTRY_FILE : SDK_TYPES_ENTRY_FILE,
3848
4186
  sdkWorkersEntryFile: SDK_WORKERS_ENTRY_FILE,
3849
4187
  workersHarnessEntryFile: WORKERS_HARNESS_ENTRY_FILE,
3850
4188
  workersHarnessFilesDir: WORKERS_HARNESS_FILES_DIR,
@@ -4081,13 +4419,15 @@ function formatLoadedPlayMessage(materializedFile) {
4081
4419
  return `Loaded play here: ${materializedFile.path}`;
4082
4420
  }
4083
4421
  function buildReadonlyPrebuiltPlayError(reference) {
4422
+ const localName = reference.split("/").slice(1).join("/") || "custom-play";
4084
4423
  return new Error(
4085
4424
  `Cannot edit or push ${reference} because Deepline prebuilt plays are read-only.
4086
4425
  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.`
4426
+ 1. Run: deepline plays get ${reference} --source --out ./${localName}.play.ts
4427
+ 2. Change definePlay('${localName}', ...) to a new play name you own.
4428
+ 3. Run: deepline plays check ./${localName}.play.ts
4429
+ 4. Run: deepline plays publish ./${localName}.play.ts
4430
+ 5. Your play will then live under your workspace namespace.`
4091
4431
  );
4092
4432
  }
4093
4433
  async function ensureEditableRemotePlay(client, target) {
@@ -4228,6 +4568,15 @@ function fileInputBindingsFromStaticPipeline(staticPipeline) {
4228
4568
  );
4229
4569
  return inputField ? [{ inputPath: inputField }] : [];
4230
4570
  }
4571
+ function applyCsvShortcutInput(input) {
4572
+ const csvValue = getDottedInputValue(input.runtimeInput, "csv");
4573
+ if (csvValue == null || csvValue === "") return;
4574
+ const candidate = input.bindings.find((binding) => binding.inputPath !== "csv")?.inputPath ?? input.fallbackInputPath ?? null;
4575
+ if (!candidate || candidate === "csv") return;
4576
+ const existing = getDottedInputValue(input.runtimeInput, candidate);
4577
+ if (existing != null && existing !== "") return;
4578
+ setDottedInputValue(input.runtimeInput, candidate, csvValue);
4579
+ }
4231
4580
  function isLocalFilePathValue(value) {
4232
4581
  if (typeof value !== "string" || !value.trim()) return false;
4233
4582
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim())) return false;
@@ -4351,6 +4700,7 @@ async function compileBundledPlayGraphManifests(client, graph) {
4351
4700
  node.compilerManifest = await client.compilePlayManifest({
4352
4701
  name,
4353
4702
  sourceCode: node.sourceCode,
4703
+ sourceFiles: node.sourceFiles,
4354
4704
  artifact: node.artifact,
4355
4705
  importedPlayDependencies: node.importedPlayDependencies.map(
4356
4706
  (dependency) => {
@@ -4400,6 +4750,7 @@ async function publishImportedPlayDependencies(client, graph) {
4400
4750
  await client.registerPlayArtifact({
4401
4751
  name: node.playName,
4402
4752
  sourceCode: node.sourceCode,
4753
+ sourceFiles: node.sourceFiles,
4403
4754
  artifact: node.artifact,
4404
4755
  compilerManifest: requireCompilerManifest(node),
4405
4756
  publish: true
@@ -4417,67 +4768,6 @@ function formatTimestamp(value) {
4417
4768
  function formatRunLine(run) {
4418
4769
  return `${run.workflowId} ${run.status} ${formatTimestamp(run.startTime)}`;
4419
4770
  }
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
4771
  function isTransientPlayStatusPollError(error) {
4482
4772
  if (error instanceof DeeplineError && typeof error.statusCode === "number") {
4483
4773
  return error.statusCode >= 500 && error.statusCode < 600;
@@ -4581,7 +4871,7 @@ function assertPlayWaitNotTimedOut(input) {
4581
4871
  if (input.waitTimeoutMs !== null && Date.now() - input.startedAt >= input.waitTimeoutMs) {
4582
4872
  const hasRealRunId = input.workflowId.length > 0 && input.workflowId !== "pending";
4583
4873
  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.`;
4874
+ 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
4875
  throw new DeeplineError(
4586
4876
  `Timed out waiting for play ${hasRealRunId ? input.workflowId : "<no run id>"} after ${Math.ceil(input.waitTimeoutMs / 1e3)}s${phaseSuffix}.${tailHint}`,
4587
4877
  void 0,
@@ -4594,66 +4884,6 @@ function assertPlayWaitNotTimedOut(input) {
4594
4884
  );
4595
4885
  }
4596
4886
  }
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
4887
  async function startAndWaitForPlayCompletionByStream(input) {
4658
4888
  const startedAt = Date.now();
4659
4889
  const state = {
@@ -4733,10 +4963,12 @@ async function startAndWaitForPlayCompletionByStream(input) {
4733
4963
  clearTimeout(timeout);
4734
4964
  }
4735
4965
  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})
4966
+ if (!input.jsonOutput) {
4967
+ process.stderr.write(
4968
+ `[play watch] start stream failed after run ${lastKnownWorkflowId}; falling back to polling (${reason})
4738
4969
  `
4739
- );
4970
+ );
4971
+ }
4740
4972
  return waitForPlayCompletionByPolling({
4741
4973
  client: input.client,
4742
4974
  workflowId: lastKnownWorkflowId,
@@ -4755,6 +4987,24 @@ async function startAndWaitForPlayCompletionByStream(input) {
4755
4987
  clearTimeout(timeout);
4756
4988
  }
4757
4989
  }
4990
+ if (lastKnownWorkflowId) {
4991
+ if (!input.jsonOutput) {
4992
+ input.progress.writeLine(
4993
+ `[play watch] start stream ended after run ${lastKnownWorkflowId}; falling back to polling`
4994
+ );
4995
+ }
4996
+ return waitForPlayCompletionByPolling({
4997
+ client: input.client,
4998
+ workflowId: lastKnownWorkflowId,
4999
+ pollIntervalMs: 500,
5000
+ jsonOutput: input.jsonOutput,
5001
+ emitLogs: input.emitLogs,
5002
+ waitTimeoutMs: input.waitTimeoutMs,
5003
+ startedAt,
5004
+ state,
5005
+ progress: input.progress
5006
+ });
5007
+ }
4758
5008
  const phaseSuffix = lastPhase && lastPhase.trim() ? ` (last observed phase: ${lastPhase.trim()})` : "";
4759
5009
  const idSuffix = lastKnownWorkflowId ? ` runId=${lastKnownWorkflowId}` : "";
4760
5010
  throw new DeeplineError(
@@ -4832,38 +5082,6 @@ async function waitForPlayCompletionByPolling(input) {
4832
5082
  }
4833
5083
  }
4834
5084
  }
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
5085
  function formatInteger(value) {
4868
5086
  return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : String(value ?? "-");
4869
5087
  }
@@ -4970,14 +5188,20 @@ function formatReturnValue(result) {
4970
5188
  }
4971
5189
  return lines;
4972
5190
  }
4973
- function buildOutputSummary(rowsInfo, exportedPath) {
5191
+ function buildOutputSummary(rowsInfo, runId, exportedPath) {
4974
5192
  if (!rowsInfo) {
4975
5193
  return exportedPath ? { csv_path: exportedPath } : null;
4976
5194
  }
5195
+ const isPartial = !rowsInfo.complete;
4977
5196
  return {
4978
5197
  kind: "rows",
4979
5198
  rowCount: rowsInfo.totalRows,
4980
5199
  previewRowCount: rowsInfo.rows.length,
5200
+ ...isPartial ? {
5201
+ isPartial: true,
5202
+ previewCount: rowsInfo.rows.length,
5203
+ totalCount: rowsInfo.totalRows
5204
+ } : { isPartial: false },
4981
5205
  complete: rowsInfo.complete,
4982
5206
  columns: rowsInfo.columns,
4983
5207
  source: rowsInfo.source,
@@ -4988,21 +5212,174 @@ function buildRunWarnings(status, rowsInfo) {
4988
5212
  if (status.status === "completed" && rowsInfo?.totalRows === 0) {
4989
5213
  return ["Run completed with 0 output rows."];
4990
5214
  }
5215
+ if (rowsInfo && !rowsInfo.complete) {
5216
+ return [
5217
+ `Run output is partial: showing ${rowsInfo.rows.length} preview row(s) of ${rowsInfo.totalRows}.`
5218
+ ];
5219
+ }
5220
+ return [];
5221
+ }
5222
+ function buildRunNextCommands(runId, rowsInfo) {
5223
+ const commands = {
5224
+ get: `deepline runs get ${runId} --json`,
5225
+ tail: `deepline runs tail ${runId} --json`,
5226
+ stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
5227
+ logs: `deepline runs logs ${runId} --out run.log --json`
5228
+ };
5229
+ if (!rowsInfo || rowsInfo.complete) {
5230
+ commands.exportCsv = buildRunExportCommand(runId);
5231
+ }
5232
+ return commands;
5233
+ }
5234
+ function buildRunExportCommand(runId) {
5235
+ return `deepline runs export ${runId} --out output.csv`;
5236
+ }
5237
+ var RUN_LOG_PREVIEW_LIMIT = 20;
5238
+ function getRecordField(value, key) {
5239
+ return value && typeof value === "object" && !Array.isArray(value) ? value[key] : void 0;
5240
+ }
5241
+ function getNumericField(value, key) {
5242
+ const field = getRecordField(value, key);
5243
+ return typeof field === "number" && Number.isFinite(field) ? field : null;
5244
+ }
5245
+ function getStringField(value, key) {
5246
+ const field = getRecordField(value, key);
5247
+ return typeof field === "string" && field.trim() ? field : null;
5248
+ }
5249
+ function normalizeRunStatusForEnvelope(status) {
5250
+ const run = status.run ?? null;
5251
+ return {
5252
+ id: status.runId,
5253
+ playName: status.playName ?? status.name ?? getStringField(run, "playName") ?? null,
5254
+ status: status.status,
5255
+ runtime: getStringField(status, "runtime") ?? getStringField(status, "runtimeBackend") ?? getStringField(run, "runtime") ?? null,
5256
+ startedAt: getStringField(run, "startTime") ?? getStringField(run, "startedAt") ?? null,
5257
+ updatedAt: getStringField(status, "updatedAt") ?? getStringField(run, "updatedAt") ?? null,
5258
+ finishedAt: getStringField(run, "closeTime") ?? getStringField(run, "finishedAt") ?? null,
5259
+ source: getRecordField(status, "source") ?? getRecordField(status, "artifact") ?? null
5260
+ };
5261
+ }
5262
+ function normalizeProgressForEnvelope(status, rowsInfo) {
5263
+ const progress = status.progress;
5264
+ const total = getNumericField(progress, "totalRows") ?? getNumericField(progress, "total") ?? rowsInfo?.totalRows ?? null;
5265
+ const failed = getNumericField(progress, "failed") ?? getNumericField(progress, "failedRows") ?? null;
5266
+ const completed = getNumericField(progress, "completed") ?? getNumericField(progress, "completedRows") ?? (status.status === "completed" ? total : null);
5267
+ const pending = getNumericField(progress, "pending") ?? (typeof total === "number" && typeof completed === "number" && typeof failed === "number" ? Math.max(0, total - completed - failed) : null);
5268
+ return {
5269
+ total,
5270
+ completed,
5271
+ pending,
5272
+ failed,
5273
+ executed: getNumericField(progress, "executed"),
5274
+ reused: getNumericField(progress, "reused"),
5275
+ skipped: getNumericField(progress, "skipped"),
5276
+ retried: getNumericField(progress, "retried"),
5277
+ degraded: typeof getRecordField(progress, "degraded") === "boolean" ? getRecordField(progress, "degraded") : null,
5278
+ duplicates: getRecordField(progress, "duplicates") ?? null,
5279
+ active: getStringField(progress, "status") ?? getStringField(status, "activeStep") ?? getStringField(status, "activeNodeId") ?? null,
5280
+ wait: status.wait ?? null
5281
+ };
5282
+ }
5283
+ function normalizeOutputsForEnvelope(rowsInfo, runId, exportedPath) {
5284
+ if (!rowsInfo) {
5285
+ return exportedPath ? [{ name: "output", kind: "file", path: exportedPath }] : [];
5286
+ }
5287
+ const isPartial = !rowsInfo.complete;
5288
+ return [
5289
+ {
5290
+ name: "rows",
5291
+ kind: "dataset",
5292
+ rowCount: rowsInfo.totalRows,
5293
+ columns: rowsInfo.columns,
5294
+ preview: rowsInfo.rows.slice(0, 5),
5295
+ previewRowCount: Math.min(rowsInfo.rows.length, 5),
5296
+ previewLimit: 5,
5297
+ ...isPartial ? {
5298
+ isPartial: true,
5299
+ previewCount: rowsInfo.rows.length,
5300
+ totalCount: rowsInfo.totalRows
5301
+ } : { isPartial: false },
5302
+ complete: rowsInfo.complete,
5303
+ source: rowsInfo.source,
5304
+ ...exportedPath ? { csv_path: exportedPath } : {}
5305
+ }
5306
+ ];
5307
+ }
5308
+ function normalizeStepsForEnvelope(status) {
5309
+ const directSteps = getRecordField(status, "steps");
5310
+ if (Array.isArray(directSteps)) {
5311
+ return directSteps;
5312
+ }
5313
+ const timeline = getRecordField(status, "timeline");
5314
+ if (Array.isArray(timeline)) {
5315
+ return timeline;
5316
+ }
4991
5317
  return [];
4992
5318
  }
4993
- function buildRunNextCommands(runId) {
5319
+ function normalizeErrorsForEnvelope(status, error) {
5320
+ const directErrors = getRecordField(status, "errors");
5321
+ if (Array.isArray(directErrors)) {
5322
+ return directErrors.filter(
5323
+ (entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)
5324
+ );
5325
+ }
5326
+ if (!error) {
5327
+ return [];
5328
+ }
5329
+ return [
5330
+ {
5331
+ code: getStringField(status, "errorCode") ?? "RUN_FAILED",
5332
+ phase: getStringField(status, "errorPhase") ?? "runtime",
5333
+ message: error,
5334
+ retryable: typeof getRecordField(status, "retryable") === "boolean" ? getRecordField(status, "retryable") : null,
5335
+ nextAction: `deepline runs get ${status.runId} --json`
5336
+ }
5337
+ ];
5338
+ }
5339
+ function normalizeLogsForEnvelope(status) {
5340
+ const logs = Array.isArray(status.progress?.logs) ? status.progress.logs : [];
5341
+ const offset = typeof status.progress?.logOffset === "number" && Number.isFinite(status.progress.logOffset) ? Math.max(0, Math.trunc(status.progress.logOffset)) : 0;
5342
+ const totalCount = offset + logs.length;
5343
+ const entries = logs.slice(Math.max(0, logs.length - RUN_LOG_PREVIEW_LIMIT));
5344
+ const firstSequence = entries.length === 0 ? null : offset + logs.length - entries.length + 1;
5345
+ const lastSequence = totalCount === 0 ? null : totalCount;
4994
5346
  return {
4995
- exportCsv: `deepline runs export ${runId} --out output.csv`,
4996
- status: `deepline runs status ${runId} --json`,
4997
- logs: `deepline runs logs ${runId}`
5347
+ totalCount,
5348
+ returnedCount: entries.length,
5349
+ firstSequence,
5350
+ lastSequence,
5351
+ truncated: totalCount > entries.length,
5352
+ hasMore: totalCount > entries.length,
5353
+ entries,
5354
+ nextCursor: lastSequence
4998
5355
  };
4999
5356
  }
5357
+ function stripProviderSpendFromBilling(value) {
5358
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
5359
+ return value;
5360
+ }
5361
+ const next = {};
5362
+ for (const [key, item] of Object.entries(value)) {
5363
+ if (key === "providerCostUsd" || key === "totalProviderCostUsd") {
5364
+ continue;
5365
+ }
5366
+ next[key] = item;
5367
+ }
5368
+ return next;
5369
+ }
5000
5370
  function compactPlayStatus(status, options) {
5001
5371
  const rowsInfo = extractCanonicalRowsInfo(status);
5002
5372
  const result = status && typeof status === "object" ? status.result : null;
5003
5373
  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;
5374
+ const datasetStats = rowsInfo && rowsInfo.complete ? buildDatasetStats(
5375
+ rowsInfo.rows,
5376
+ rowsInfo.totalRows,
5377
+ rowsInfo.columns,
5378
+ extractDatasetExecutionStats(status)
5379
+ ) : null;
5380
+ const billing = status && typeof status === "object" ? stripProviderSpendFromBilling(
5381
+ status.billing
5382
+ ) : null;
5006
5383
  const progressError = status.progress?.error;
5007
5384
  const error = typeof progressError === "string" ? progressError : typeof status.error === "string" ? String(status.error) : null;
5008
5385
  return {
@@ -5011,16 +5388,25 @@ function compactPlayStatus(status, options) {
5011
5388
  ...typeof status.name === "string" ? { name: status.name } : {},
5012
5389
  ...typeof status.playName === "string" ? { playName: status.playName } : {},
5013
5390
  status: status.status,
5391
+ run: normalizeRunStatusForEnvelope(status),
5392
+ progress: normalizeProgressForEnvelope(status, rowsInfo),
5393
+ outputs: normalizeOutputsForEnvelope(
5394
+ rowsInfo,
5395
+ status.runId,
5396
+ options?.exportedPath
5397
+ ),
5398
+ steps: normalizeStepsForEnvelope(status),
5399
+ errors: normalizeErrorsForEnvelope(status, error),
5400
+ logs: normalizeLogsForEnvelope(status),
5014
5401
  ...error ? { error } : {},
5015
5402
  ...warnings.length > 0 ? { warnings } : {},
5016
- output: buildOutputSummary(rowsInfo, options?.exportedPath) ?? result ?? null,
5403
+ output: buildOutputSummary(rowsInfo, status.runId, options?.exportedPath) ?? result ?? null,
5017
5404
  ...result !== void 0 ? { result } : {},
5018
5405
  ...status.resultView ? { resultView: status.resultView } : {},
5019
5406
  ...datasetStats ? { dataset_stats: datasetStats } : {},
5020
5407
  ...rowsInfo ? { previewRows: rowsInfo.rows.slice(0, 5) } : {},
5021
5408
  ...billing ? { billing } : {},
5022
- ...status.run ? { run: status.run } : {},
5023
- next: buildRunNextCommands(status.runId)
5409
+ next: buildRunNextCommands(status.runId, rowsInfo)
5024
5410
  };
5025
5411
  }
5026
5412
  function enrichPlayStatusWithDatasetStats(status) {
@@ -5033,7 +5419,8 @@ function enrichPlayStatusWithDatasetStats(status) {
5033
5419
  dataset_stats: buildDatasetStats(
5034
5420
  rowsInfo.rows,
5035
5421
  rowsInfo.totalRows,
5036
- rowsInfo.columns
5422
+ rowsInfo.columns,
5423
+ extractDatasetExecutionStats(status)
5037
5424
  )
5038
5425
  };
5039
5426
  }
@@ -5048,8 +5435,9 @@ function formatDatasetStatsLines(datasetStats) {
5048
5435
  )) {
5049
5436
  const topValues = stat3.top_values ? `, top_values=${Object.entries(stat3.top_values).slice(0, 3).map(([value, count]) => `${value}=${count}`).join(", ")}` : "";
5050
5437
  const sample = stat3.sample_value !== void 0 ? `, sample_value=${JSON.stringify(stat3.sample_value)}` : "";
5438
+ const execution = stat3.execution ? `, execution=${Object.entries(stat3.execution).map(([bucket, count]) => `${bucket}=${count}`).join(", ")}` : "";
5051
5439
  lines.push(
5052
- ` ${column}: non_empty=${stat3.non_empty}, unique=${stat3.unique}${topValues}${sample}`
5440
+ ` ${column}: non_empty=${stat3.non_empty}, unique=${stat3.unique}${topValues}${sample}${execution}`
5053
5441
  );
5054
5442
  }
5055
5443
  return lines;
@@ -5072,14 +5460,28 @@ function writePlayResult(status, jsonOutput, options) {
5072
5460
  lines.push(`${success ? "\u2713" : "\u2717"} ${publicStatus} ${runId}`);
5073
5461
  const rowsInfo = extractCanonicalRowsInfo(status);
5074
5462
  const warnings = buildRunWarnings(status, rowsInfo);
5075
- const datasetStats = rowsInfo && rowsInfo.complete ? buildDatasetStats(rowsInfo.rows, rowsInfo.totalRows, rowsInfo.columns) : null;
5076
- const outputSummary = buildOutputSummary(rowsInfo, options?.exportedPath);
5463
+ const datasetStats = rowsInfo && rowsInfo.complete ? buildDatasetStats(
5464
+ rowsInfo.rows,
5465
+ rowsInfo.totalRows,
5466
+ rowsInfo.columns,
5467
+ extractDatasetExecutionStats(status)
5468
+ ) : null;
5469
+ const outputSummary = buildOutputSummary(
5470
+ rowsInfo,
5471
+ runId,
5472
+ options?.exportedPath
5473
+ );
5077
5474
  if (outputSummary) {
5078
5475
  const columns = Array.isArray(outputSummary.columns) ? outputSummary.columns.length : 0;
5079
5476
  const path = typeof outputSummary.csv_path === "string" ? ` file=${outputSummary.csv_path}` : "";
5080
5477
  lines.push(
5081
5478
  ` output: rows=${formatInteger(outputSummary.rowCount)} columns=${formatInteger(columns)}${path}`
5082
5479
  );
5480
+ if (outputSummary.isPartial === true) {
5481
+ lines.push(
5482
+ ` partial output: showing ${formatInteger(outputSummary.previewCount)} preview row(s) of ${formatInteger(outputSummary.totalCount)}`
5483
+ );
5484
+ }
5083
5485
  }
5084
5486
  for (const warning of warnings) {
5085
5487
  lines.push(` warning: ${warning}`);
@@ -5109,6 +5511,11 @@ function exportPlayStatusRows(status, outPath) {
5109
5511
  `Run ${status.runId} did not expose a row-shaped final output to export.`
5110
5512
  );
5111
5513
  }
5514
+ if (!rowsInfo.complete) {
5515
+ throw new DeeplineError(
5516
+ `Run output only includes ${rowsInfo.rows.length} preview row(s) of ${rowsInfo.totalRows}; full dataset export is not available from this status response yet.`
5517
+ );
5518
+ }
5112
5519
  return writeCanonicalRowsCsv(rowsInfo, outPath);
5113
5520
  }
5114
5521
  function renderServerResultView(value) {
@@ -5191,10 +5598,10 @@ function writeStartedPlayRun(input) {
5191
5598
  const lines = [
5192
5599
  `Started ${input.playName}`,
5193
5600
  ` 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`
5601
+ ` get status: deepline runs get ${input.runId} --json`,
5602
+ ` tail logs: deepline runs tail ${input.runId} --json`,
5603
+ ` stop run: deepline runs stop ${input.runId} --reason "stale lock" --json`,
5604
+ ` result JSON: deepline runs get ${input.runId} --json`
5198
5605
  ];
5199
5606
  if (input.dashboardUrl) {
5200
5607
  lines.push(` play page: ${input.dashboardUrl}`);
@@ -5341,6 +5748,10 @@ function parsePlayCheckOptions(args) {
5341
5748
  const jsonOutput = argsWantJson(args);
5342
5749
  return { target, jsonOutput };
5343
5750
  }
5751
+ function shouldUseLocalOnlyPlayCheck() {
5752
+ const value = process.env.DEEPLINE_PLAY_CHECK_LOCAL_ONLY?.trim().toLowerCase();
5753
+ return value === "1" || value === "true" || value === "yes" || value === "on";
5754
+ }
5344
5755
  async function handlePlayCheck(args) {
5345
5756
  const options = parsePlayCheckOptions(args);
5346
5757
  if (!isFileTarget(options.target)) {
@@ -5366,10 +5777,28 @@ async function handlePlayCheck(args) {
5366
5777
  return 1;
5367
5778
  }
5368
5779
  const playName = graph.root.playName ?? extractPlayName(sourceCode, absolutePlayPath);
5780
+ if (shouldUseLocalOnlyPlayCheck()) {
5781
+ const result2 = {
5782
+ valid: true,
5783
+ errors: [],
5784
+ staticPipeline: graph.root.compilerManifest?.staticPipeline ?? null,
5785
+ artifactHash: graph.root.artifact.artifactHash,
5786
+ graphHash: graph.root.artifact.graphHash
5787
+ };
5788
+ if (options.jsonOutput) {
5789
+ process.stdout.write(`${JSON.stringify({ name: playName, ...result2 })}
5790
+ `);
5791
+ } else {
5792
+ console.log(`\u2713 ${playName} passed local play check`);
5793
+ console.log(` artifact: ${result2.artifactHash.slice(0, 12)}`);
5794
+ }
5795
+ return 0;
5796
+ }
5369
5797
  const client = new DeeplineClient();
5370
5798
  const result = await client.checkPlayArtifact({
5371
5799
  name: playName,
5372
5800
  sourceCode: graph.root.sourceCode,
5801
+ sourceFiles: graph.root.sourceFiles,
5373
5802
  artifact: graph.root.artifact
5374
5803
  });
5375
5804
  if (options.jsonOutput) {
@@ -5421,17 +5850,24 @@ async function handleFileBackedRun(options) {
5421
5850
  const packagedFileUploads = bundleResult.packagedFiles.map(
5422
5851
  (file) => stageFile(file.logicalPath, file.absolutePath)
5423
5852
  );
5853
+ const fileInputBindings = fileInputBindingsFromStaticPipeline(
5854
+ requireCompilerManifest(bundleResult).staticPipeline
5855
+ );
5856
+ applyCsvShortcutInput({
5857
+ runtimeInput,
5858
+ bindings: fileInputBindings,
5859
+ fallbackInputPath: "file"
5860
+ });
5424
5861
  const stagedFileInputs = await stageFileInputArgs({
5425
5862
  client,
5426
5863
  runtimeInput,
5427
- bindings: fileInputBindingsFromStaticPipeline(
5428
- requireCompilerManifest(bundleResult).staticPipeline
5429
- ),
5864
+ bindings: fileInputBindings,
5430
5865
  progress
5431
5866
  });
5432
5867
  const startRequest = {
5433
5868
  name: playName,
5434
5869
  sourceCode: bundleResult.sourceCode,
5870
+ sourceFiles: bundleResult.sourceFiles,
5435
5871
  runtimeArtifact: bundleResult.artifact,
5436
5872
  compilerManifest: requireCompilerManifest(bundleResult),
5437
5873
  packagedFileUploads,
@@ -5506,13 +5942,18 @@ async function handleNamedRun(options) {
5506
5942
  selector: options.revisionSelector
5507
5943
  });
5508
5944
  const runtimeInput = options.input ? { ...options.input } : {};
5945
+ const fileInputBindings = [
5946
+ ...fileInputBindingsFromPlaySchema(playDetail.play.inputSchema),
5947
+ ...fileInputBindingsFromStaticPipeline(playDetail.play.staticPipeline)
5948
+ ];
5949
+ applyCsvShortcutInput({
5950
+ runtimeInput,
5951
+ bindings: fileInputBindings
5952
+ });
5509
5953
  const stagedFileInputs = await stageFileInputArgs({
5510
5954
  client,
5511
5955
  runtimeInput,
5512
- bindings: [
5513
- ...fileInputBindingsFromPlaySchema(playDetail.play.inputSchema),
5514
- ...fileInputBindingsFromStaticPipeline(playDetail.play.staticPipeline)
5515
- ],
5956
+ bindings: fileInputBindings,
5516
5957
  progress
5517
5958
  });
5518
5959
  const startRequest = {
@@ -5590,80 +6031,101 @@ async function handlePlayRun(args) {
5590
6031
  }
5591
6032
  return handleNamedRun(options);
5592
6033
  }
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;
6034
+ function parseRunIdPositional(args, usage) {
5606
6035
  for (let index = 0; index < args.length; index += 1) {
5607
6036
  const arg = args[index];
5608
- if ((arg === "--interval-ms" || arg === "--poll-interval-ms") && args[index + 1]) {
5609
- intervalMs = parsePositiveInteger2(args[++index], arg);
6037
+ if (arg === "--json" || arg === "--full" || arg === "--logs" || arg === "--compact" || arg === "--limit") {
6038
+ if (arg === "--limit" && args[index + 1]) {
6039
+ index += 1;
6040
+ }
6041
+ continue;
6042
+ }
6043
+ if ((arg === "--out" || arg === "--cursor" || arg === "--reason" || arg === "--interval-ms" || arg === "--poll-interval-ms") && args[index + 1]) {
6044
+ index += 1;
6045
+ continue;
6046
+ }
6047
+ if (!arg.startsWith("--")) {
6048
+ return arg;
5610
6049
  }
5611
6050
  }
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;
6051
+ throw new DeeplineError(usage);
5631
6052
  }
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;
6053
+ async function handleRunGet(args) {
6054
+ const usage = "Usage: deepline runs get <run-id> [--json] [--full]";
6055
+ let runId;
5635
6056
  try {
5636
- target = parsePlayRunTarget({ args, usage, allowName: true });
6057
+ runId = parseRunIdPositional(args, usage);
5637
6058
  } catch (error) {
5638
6059
  console.error(error instanceof Error ? error.message : usage);
5639
6060
  return 1;
5640
6061
  }
5641
6062
  const client = new DeeplineClient();
5642
- const workflowId = await resolvePlayRunId(client, target);
5643
- const status = await client.getPlayStatus(workflowId);
6063
+ const status = await client.runs.get(runId);
5644
6064
  writePlayResult(status, argsWantJson(args), {
5645
6065
  fullJson: args.includes("--full")
5646
6066
  });
5647
6067
  return 0;
5648
6068
  }
5649
- function parseRunIdPositional(args, usage) {
6069
+ async function handleRunsList(args) {
6070
+ const usage = "Usage: deepline runs list --play <play-name> [--status <status>] [--json]";
6071
+ let playName = null;
6072
+ let statusFilter = null;
5650
6073
  for (let index = 0; index < args.length; index += 1) {
5651
6074
  const arg = args[index];
5652
- if (arg === "--json" || arg === "--full" || arg === "--logs") {
6075
+ if ((arg === "--play" || arg === "--name") && args[index + 1]) {
6076
+ playName = parseReferencedPlayTarget(args[++index]).playName;
5653
6077
  continue;
5654
6078
  }
5655
- if (arg === "--out" && args[index + 1]) {
5656
- index += 1;
6079
+ if (arg === "--status" && args[index + 1]) {
6080
+ statusFilter = args[++index].trim().toLowerCase();
5657
6081
  continue;
5658
6082
  }
5659
- if (!arg.startsWith("--")) {
5660
- return arg;
6083
+ if (arg === "--json" || arg === "--compact") {
6084
+ continue;
5661
6085
  }
5662
6086
  }
5663
- throw new DeeplineError(usage);
6087
+ if (!playName) {
6088
+ console.error(usage);
6089
+ return 1;
6090
+ }
6091
+ const client = new DeeplineClient();
6092
+ const runs = (await client.runs.list({
6093
+ play: playName,
6094
+ ...statusFilter ? { status: statusFilter } : {}
6095
+ })).map((run) => ({
6096
+ runId: run.workflowId,
6097
+ workflowId: run.workflowId,
6098
+ temporalRunId: run.runId,
6099
+ status: String(run.status ?? "").toLowerCase(),
6100
+ startedAt: run.startTime,
6101
+ finishedAt: run.closeTime,
6102
+ executionTime: run.executionTime,
6103
+ playName: run.memo?.playName ?? playName
6104
+ }));
6105
+ if (argsWantJson(args)) {
6106
+ process.stdout.write(
6107
+ `${JSON.stringify({
6108
+ runs,
6109
+ count: runs.length,
6110
+ next: {
6111
+ get: runs[0]?.runId ? `deepline runs get ${runs[0].runId} --json` : null
6112
+ }
6113
+ })}
6114
+ `
6115
+ );
6116
+ } else {
6117
+ if (runs.length === 0) {
6118
+ console.log(`No runs found for ${playName}.`);
6119
+ } else {
6120
+ for (const run of runs) {
6121
+ console.log(`${run.runId} ${run.status} ${formatTimestamp(run.startedAt)}`);
6122
+ }
6123
+ }
6124
+ }
6125
+ return 0;
5664
6126
  }
5665
- async function handleRunStatus(args) {
5666
- const usage = "Usage: deepline runs status <run-id> [--json] [--full]";
6127
+ async function handleRunTail(args) {
6128
+ const usage = "Usage: deepline runs tail <run-id> [--json] [--compact] [--cursor <cursor>]";
5667
6129
  let runId;
5668
6130
  try {
5669
6131
  runId = parseRunIdPositional(args, usage);
@@ -5672,14 +6134,24 @@ async function handleRunStatus(args) {
5672
6134
  return 1;
5673
6135
  }
5674
6136
  const client = new DeeplineClient();
5675
- const status = await client.getPlayStatus(runId);
5676
- writePlayResult(status, argsWantJson(args), {
5677
- fullJson: args.includes("--full")
6137
+ let afterLogIndex;
6138
+ for (let index = 0; index < args.length; index += 1) {
6139
+ const arg = args[index];
6140
+ if (arg === "--cursor" && args[index + 1]) {
6141
+ const parsed = Number(args[++index]);
6142
+ if (Number.isInteger(parsed) && parsed >= 0) {
6143
+ afterLogIndex = parsed;
6144
+ }
6145
+ }
6146
+ }
6147
+ const status = await client.runs.tail(runId, {
6148
+ ...afterLogIndex !== void 0 ? { afterLogIndex } : {}
5678
6149
  });
5679
- return 0;
6150
+ writePlayResult(status, argsWantJson(args));
6151
+ return status.status === "failed" ? 1 : 0;
5680
6152
  }
5681
6153
  async function handleRunLogs(args) {
5682
- const usage = "Usage: deepline runs logs <run-id> [--json]";
6154
+ const usage = "Usage: deepline runs logs <run-id> [--limit 200] [--out run.log] [--json]";
5683
6155
  let runId;
5684
6156
  try {
5685
6157
  runId = parseRunIdPositional(args, usage);
@@ -5687,14 +6159,86 @@ async function handleRunLogs(args) {
5687
6159
  console.error(error instanceof Error ? error.message : usage);
5688
6160
  return 1;
5689
6161
  }
6162
+ let limit = 200;
6163
+ let outPath = null;
6164
+ for (let index = 0; index < args.length; index += 1) {
6165
+ const arg = args[index];
6166
+ if (arg === "--limit" && args[index + 1]) {
6167
+ limit = parsePositiveInteger2(args[++index], "--limit");
6168
+ continue;
6169
+ }
6170
+ if (arg === "--out" && args[index + 1]) {
6171
+ outPath = resolve7(args[++index]);
6172
+ }
6173
+ }
5690
6174
  const client = new DeeplineClient();
5691
- const status = await client.getPlayStatus(runId);
6175
+ const status = await client.runs.get(runId);
5692
6176
  const logs = status.progress?.logs ?? [];
6177
+ if (outPath) {
6178
+ writeFileSync4(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
6179
+ if (argsWantJson(args)) {
6180
+ process.stdout.write(
6181
+ `${JSON.stringify({
6182
+ runId: status.runId,
6183
+ log_path: outPath,
6184
+ lineCount: logs.length
6185
+ })}
6186
+ `
6187
+ );
6188
+ } else {
6189
+ console.log(`Wrote ${logs.length} log lines to ${outPath}`);
6190
+ }
6191
+ return 0;
6192
+ }
6193
+ const entries = logs.slice(Math.max(0, logs.length - limit));
5693
6194
  if (argsWantJson(args)) {
5694
- process.stdout.write(`${JSON.stringify({ runId: status.runId, logs })}
6195
+ process.stdout.write(
6196
+ `${JSON.stringify({
6197
+ runId: status.runId,
6198
+ totalCount: logs.length,
6199
+ returnedCount: entries.length,
6200
+ firstSequence: logs.length === 0 ? null : logs.length - entries.length + 1,
6201
+ lastSequence: logs.length === 0 ? null : logs.length,
6202
+ truncated: logs.length > entries.length,
6203
+ hasMore: logs.length > entries.length,
6204
+ entries,
6205
+ next: {
6206
+ export: `deepline runs logs ${status.runId} --out run.log --json`
6207
+ }
6208
+ })}
6209
+ `
6210
+ );
6211
+ } else {
6212
+ process.stdout.write(`${entries.join("\n")}${entries.length > 0 ? "\n" : ""}`);
6213
+ }
6214
+ return 0;
6215
+ }
6216
+ async function handleRunStop(args) {
6217
+ const usage = 'Usage: deepline runs stop <run-id> [--reason "text"] [--json]';
6218
+ let runId;
6219
+ try {
6220
+ runId = parseRunIdPositional(args, usage);
6221
+ } catch (error) {
6222
+ console.error(error instanceof Error ? error.message : usage);
6223
+ return 1;
6224
+ }
6225
+ let reason;
6226
+ for (let index = 0; index < args.length; index += 1) {
6227
+ const arg = args[index];
6228
+ if (arg === "--reason" && args[index + 1]) {
6229
+ reason = args[++index];
6230
+ }
6231
+ }
6232
+ const client = new DeeplineClient();
6233
+ const result = await client.runs.stop(runId, { reason });
6234
+ if (argsWantJson(args)) {
6235
+ process.stdout.write(`${JSON.stringify(result)}
5695
6236
  `);
5696
6237
  } else {
5697
- process.stdout.write(`${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
6238
+ console.log(`Stopped ${result.runId}`);
6239
+ if (result.hitlCancelledCount > 0) {
6240
+ console.log(` cancelled HITL waits: ${result.hitlCancelledCount}`);
6241
+ }
5698
6242
  }
5699
6243
  return 0;
5700
6244
  }
@@ -5737,37 +6281,6 @@ async function handleRunExport(args) {
5737
6281
  }
5738
6282
  return 0;
5739
6283
  }
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
6284
  async function handlePlayGet(args) {
5772
6285
  const target = args[0];
5773
6286
  if (!target) {
@@ -5855,33 +6368,6 @@ async function handlePlayGet(args) {
5855
6368
  }
5856
6369
  return 0;
5857
6370
  }
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
6371
  function formatVersionLine(version) {
5886
6372
  const revisionLabel = version.artifactHash?.slice(0, 12) ?? "unknown-revision";
5887
6373
  return `v${version.version} ${revisionLabel} ${formatTimestamp(version.createdAt)}`;
@@ -6013,6 +6499,11 @@ function printPlayDescription(play) {
6013
6499
  }
6014
6500
  }
6015
6501
  console.log(` Run: ${play.runCommand}`);
6502
+ if (play.origin === "prebuilt" && !play.canEdit) {
6503
+ console.log(
6504
+ ` Customize: deepline plays get ${reference} --source --out ./my-play.play.ts`
6505
+ );
6506
+ }
6016
6507
  }
6017
6508
  async function handlePlaySearch(args) {
6018
6509
  let options;
@@ -6110,6 +6601,7 @@ async function handlePlayPublish(args) {
6110
6601
  const published = await client.registerPlayArtifact({
6111
6602
  name: rootPlayName,
6112
6603
  sourceCode: graph.root.sourceCode,
6604
+ sourceFiles: graph.root.sourceFiles,
6113
6605
  artifact: graph.root.artifact,
6114
6606
  compilerManifest: requireCompilerManifest(graph.root),
6115
6607
  publish: true
@@ -6330,50 +6822,12 @@ Examples:
6330
6822
  ...options.json ? ["--json"] : []
6331
6823
  ]);
6332
6824
  });
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
6825
  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
6826
  process.exitCode = await handlePlayVersions([
6341
6827
  ...options.name ? ["--name", options.name] : [],
6342
6828
  ...options.json ? ["--json"] : []
6343
6829
  ]);
6344
6830
  });
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
6831
  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
6832
  process.exitCode = await handlePlayPublish([
6379
6833
  target,
@@ -6389,38 +6843,72 @@ Examples:
6389
6843
  ...options.json ? ["--json"] : []
6390
6844
  ]);
6391
6845
  });
6392
- const runs = program.command("runs").description("Inspect and export play runs.").addHelpText(
6846
+ const runs = program.command("runs").description("Inspect, tail, stop, and export play runs.").addHelpText(
6393
6847
  "after",
6394
6848
  `
6395
6849
  Examples:
6396
- deepline runs status play/my-play/run/20260501t000000-000
6850
+ deepline runs get play/my-play/run/20260501t000000-000 --json
6851
+ deepline runs tail play/my-play/run/20260501t000000-000
6852
+ deepline runs logs play/my-play/run/20260501t000000-000 --out run.log --json
6853
+ deepline runs list --play my-play --status failed --json
6854
+ deepline runs stop play/my-play/run/20260501t000000-000 --reason "stale lock" --json
6397
6855
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv
6398
- deepline runs logs play/my-play/run/20260501t000000-000
6399
6856
  `
6400
6857
  );
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([
6858
+ 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) => {
6859
+ process.exitCode = await handleRunGet([
6403
6860
  runId,
6404
6861
  ...options.json ? ["--json"] : [],
6405
6862
  ...options.full ? ["--full"] : []
6406
6863
  ]);
6407
6864
  });
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,
6865
+ 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) => {
6866
+ process.exitCode = await handleRunsList([
6867
+ "--play",
6868
+ options.play,
6869
+ ...options.status ? ["--status", options.status] : [],
6870
+ ...options.compact ? ["--compact"] : [],
6413
6871
  ...options.json ? ["--json"] : []
6414
6872
  ]);
6415
6873
  });
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) => {
6874
+ 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) => {
6875
+ process.exitCode = await handleRunTail([
6876
+ runId,
6877
+ ...options.json ? ["--json"] : [],
6878
+ ...options.compact ? ["--compact"] : [],
6879
+ ...options.cursor ? ["--cursor", options.cursor] : []
6880
+ ]);
6881
+ });
6882
+ 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
6883
  process.exitCode = await handleRunLogs([
6418
6884
  runId,
6885
+ ...options.limit ? ["--limit", options.limit] : [],
6886
+ ...options.out ? ["--out", options.out] : [],
6887
+ ...options.json ? ["--json"] : []
6888
+ ]);
6889
+ });
6890
+ 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) => {
6891
+ process.exitCode = await handleRunStop([
6892
+ runId,
6893
+ ...options.reason ? ["--reason", options.reason] : [],
6894
+ ...options.json ? ["--json"] : []
6895
+ ]);
6896
+ });
6897
+ 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) => {
6898
+ process.exitCode = await handleRunExport([
6899
+ runId,
6900
+ "--out",
6901
+ options.out,
6419
6902
  ...options.json ? ["--json"] : []
6420
6903
  ]);
6421
6904
  });
6422
6905
  }
6423
6906
 
6907
+ // src/cli/commands/tools.ts
6908
+ import { chmodSync, mkdtempSync, writeFileSync as writeFileSync6 } from "fs";
6909
+ import { tmpdir as tmpdir3 } from "os";
6910
+ import { join as join8 } from "path";
6911
+
6424
6912
  // src/tool-output.ts
6425
6913
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
6426
6914
  import { homedir as homedir3 } from "os";
@@ -6589,31 +7077,23 @@ async function listTools(args) {
6589
7077
  }
6590
7078
  return 0;
6591
7079
  }
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();
7080
+ async function searchTools(queryInput, options = {}) {
7081
+ const query = queryInput.trim();
6608
7082
  if (!query) {
6609
7083
  console.error("Usage: deepline tools search <query> [--json]");
6610
7084
  return 1;
6611
7085
  }
6612
- const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
6613
7086
  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 })}
7087
+ const result = await client.searchTools({
7088
+ query,
7089
+ categories: options.categories,
7090
+ searchTerms: options.searchTerms,
7091
+ searchMode: options.searchMode,
7092
+ includeSearchDebug: options.includeSearchDebug
7093
+ });
7094
+ const items = result.tools.map(toListedTool);
7095
+ if (options.json || shouldEmitJson()) {
7096
+ process.stdout.write(`${JSON.stringify({ ...result, tools: items })}
6617
7097
  `);
6618
7098
  return 0;
6619
7099
  }
@@ -6667,11 +7147,14 @@ Common commands:
6667
7147
  ...options.json ? ["--json"] : []
6668
7148
  ]);
6669
7149
  });
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
- ]);
7150
+ 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) => {
7151
+ process.exitCode = await searchTools(query, {
7152
+ json: options.json,
7153
+ categories: options.categories,
7154
+ searchTerms: options.searchTerms ?? options.search_terms,
7155
+ searchMode: options.searchMode === "v1" || options.searchMode === "v2" ? options.searchMode : void 0,
7156
+ includeSearchDebug: Boolean(options.includeSearchDebug)
7157
+ });
6675
7158
  });
6676
7159
  tools.command("get <toolId>").alias("describe").description("Show metadata for a tool.").addHelpText(
6677
7160
  "after",
@@ -7029,6 +7512,61 @@ function parseExecuteOptions(args) {
7029
7512
  }
7030
7513
  return { toolId, params, outputFormat, noPreview };
7031
7514
  }
7515
+ function safeFileStem(value) {
7516
+ return value.trim().replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "tool";
7517
+ }
7518
+ function shellQuote(value) {
7519
+ return `'${value.replace(/'/g, `'\\''`)}'`;
7520
+ }
7521
+ function powerShellQuote(value) {
7522
+ return `'${value.replace(/'/g, "''")}'`;
7523
+ }
7524
+ function seedToolListScript(input) {
7525
+ const stem = safeFileStem(input.toolId);
7526
+ const fileName = `${stem}-workflow-seed-${Date.now()}.play.ts`;
7527
+ const scriptDir = mkdtempSync(join8(tmpdir3(), "deepline-workflow-seed-"));
7528
+ chmodSync(scriptDir, 448);
7529
+ const scriptPath = join8(scriptDir, fileName);
7530
+ const projectDir = `deepline/projects/${stem}-workflow`;
7531
+ const playName = `${stem}-workflow`;
7532
+ const sampleRows = input.rows.length > 0 ? `${JSON.stringify(input.rows.slice(0, 2)).replace(/\]$/, "")}, ...]` : "[]";
7533
+ const columns = Object.keys(input.rows[0] ?? {}).join(", ");
7534
+ const rowKey = Object.prototype.hasOwnProperty.call(input.rows[0] ?? {}, "id") ? '"id"' : "(row) => JSON.stringify(row)";
7535
+ const script = `import { definePlay } from 'deepline';
7536
+
7537
+ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
7538
+ const result = await ctx.tools.execute({
7539
+ id: ${JSON.stringify(input.toolId)},
7540
+ tool: ${JSON.stringify(input.toolId)},
7541
+ input: ${JSON.stringify(input.payload)},
7542
+ description: ${JSON.stringify(`Seed ${input.toolId} rows for workflow expansion.`)},
7543
+ });
7544
+
7545
+ const list = Object.values(result.lists)[0];
7546
+ const rows = (list?.get() ?? []).slice(0, 100);
7547
+ // ${sampleRows}
7548
+ // columns: ${columns}
7549
+ // .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.' }))
7550
+ // .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.' }))
7551
+ // ctx.map is idempotent by map key + row key; reruns reuse completed rows.
7552
+ const enrichedData = await ctx
7553
+ .map('enriched_data', rows, { key: ${rowKey} })
7554
+ .run({ description: 'Enrich seeded rows.' });
7555
+
7556
+ return {
7557
+ rows: enrichedData,
7558
+ count: await enrichedData.count(),
7559
+ };
7560
+ });
7561
+ `;
7562
+ writeFileSync6(scriptPath, script, { encoding: "utf-8", mode: 384 });
7563
+ return {
7564
+ path: scriptPath,
7565
+ projectDir,
7566
+ macCopyCommand: `mkdir -p ${shellQuote(projectDir)} && cp ${shellQuote(scriptPath)} ${shellQuote(`${projectDir}/${fileName}`)}`,
7567
+ windowsCopyCommand: `New-Item -ItemType Directory -Force -Path ${powerShellQuote(projectDir.replace(/\//g, "\\"))} | Out-Null; Copy-Item -LiteralPath ${powerShellQuote(scriptPath)} -Destination ${powerShellQuote(`${projectDir.replace(/\//g, "\\")}\\${fileName}`)}`
7568
+ };
7569
+ }
7032
7570
  async function executeTool(args) {
7033
7571
  let parsed;
7034
7572
  try {
@@ -7082,6 +7620,11 @@ async function executeTool(args) {
7082
7620
  return 0;
7083
7621
  }
7084
7622
  const csv = writeCsvOutputFile(listConversion.rows, `${parsed.toolId}_output`);
7623
+ const seededScript = seedToolListScript({
7624
+ toolId: parsed.toolId,
7625
+ payload: parsed.params,
7626
+ rows: listConversion.rows
7627
+ });
7085
7628
  if (parsed.outputFormat === "csv_file") {
7086
7629
  process.stdout.write(`${JSON.stringify({
7087
7630
  extracted_csv: csv.path,
@@ -7090,6 +7633,12 @@ async function executeTool(args) {
7090
7633
  preview: csv.preview,
7091
7634
  list_strategy: listConversion.strategy,
7092
7635
  list_source_path: listConversion.sourcePath,
7636
+ starter_script: seededScript.path,
7637
+ project_dir: seededScript.projectDir,
7638
+ copy_to_project: {
7639
+ macos_linux: seededScript.macCopyCommand,
7640
+ windows_powershell: seededScript.windowsCopyCommand
7641
+ },
7093
7642
  summary
7094
7643
  })}
7095
7644
  `);
@@ -7107,14 +7656,20 @@ async function executeTool(args) {
7107
7656
  console.log(`summary: ${Object.entries(summary).map(([key, value]) => `${key}=${String(value)}`).join(", ")}`);
7108
7657
  }
7109
7658
  console.log(`preview: ${JSON.stringify(csv.preview)}`);
7659
+ console.log(`starter script: ${seededScript.path}`);
7660
+ console.log(
7661
+ "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."
7662
+ );
7663
+ console.log(`macOS/Linux: ${seededScript.macCopyCommand}`);
7664
+ console.log(`Windows PowerShell: ${seededScript.windowsCopyCommand}`);
7110
7665
  return 0;
7111
7666
  }
7112
7667
 
7113
7668
  // 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";
7669
+ import { spawn, spawnSync } from "child_process";
7670
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync7 } from "fs";
7116
7671
  import { homedir as homedir4 } from "os";
7117
- import { dirname as dirname7, join as join8 } from "path";
7672
+ import { dirname as dirname7, join as join9 } from "path";
7118
7673
  var CHECK_TIMEOUT_MS2 = 3e3;
7119
7674
  var SDK_SKILL_NAME = "deepline-sdk";
7120
7675
  var SKILL_AGENTS = ["codex", "claude-code", "cursor"];
@@ -7125,7 +7680,7 @@ function shouldSkipSkillsSync() {
7125
7680
  }
7126
7681
  function sdkSkillsVersionPath(baseUrl) {
7127
7682
  const home = process.env.HOME?.trim() || homedir4();
7128
- return join8(home, ".local", "deepline", baseUrlSlug(baseUrl), "sdk-skills", ".version");
7683
+ return join9(home, ".local", "deepline", baseUrlSlug(baseUrl), "sdk-skills", ".version");
7129
7684
  }
7130
7685
  function readLocalSkillsVersion(baseUrl) {
7131
7686
  const path = sdkSkillsVersionPath(baseUrl);
@@ -7139,7 +7694,7 @@ function readLocalSkillsVersion(baseUrl) {
7139
7694
  function writeLocalSkillsVersion(baseUrl, version) {
7140
7695
  const path = sdkSkillsVersionPath(baseUrl);
7141
7696
  mkdirSync4(dirname7(path), { recursive: true });
7142
- writeFileSync6(path, `${version}
7697
+ writeFileSync7(path, `${version}
7143
7698
  `, "utf-8");
7144
7699
  }
7145
7700
  async function fetchSkillsUpdate(baseUrl, localVersion) {
@@ -7170,9 +7725,10 @@ async function fetchSkillsUpdate(baseUrl, localVersion) {
7170
7725
  clearTimeout(timeout);
7171
7726
  }
7172
7727
  }
7173
- function runSkillsInstall(baseUrl) {
7728
+ function buildSkillsInstallArgs(baseUrl) {
7174
7729
  const packageUrl = new URL("/.well-known/skills/index.json", baseUrl).toString();
7175
- const args = [
7730
+ return [
7731
+ "--yes",
7176
7732
  "skills",
7177
7733
  "add",
7178
7734
  packageUrl,
@@ -7184,8 +7740,56 @@ function runSkillsInstall(baseUrl) {
7184
7740
  SDK_SKILL_NAME,
7185
7741
  "--full-depth"
7186
7742
  ];
7743
+ }
7744
+ function buildBunxSkillsInstallArgs(baseUrl) {
7745
+ const packageUrl = new URL("/.well-known/skills/index.json", baseUrl).toString();
7746
+ return [
7747
+ "--bun",
7748
+ "skills",
7749
+ "add",
7750
+ packageUrl,
7751
+ "--agents",
7752
+ ...SKILL_AGENTS,
7753
+ "--global",
7754
+ "--yes",
7755
+ "--skill",
7756
+ SDK_SKILL_NAME,
7757
+ "--full-depth"
7758
+ ];
7759
+ }
7760
+ function hasCommand(command) {
7761
+ const result = spawnSync(command, ["--version"], {
7762
+ stdio: "ignore",
7763
+ shell: process.platform === "win32"
7764
+ });
7765
+ return result.status === 0;
7766
+ }
7767
+ function shellQuote2(arg) {
7768
+ return `'${arg.replace(/'/g, `'\\''`)}'`;
7769
+ }
7770
+ function resolveSkillsInstallCommands(baseUrl) {
7771
+ const npxArgs = buildSkillsInstallArgs(baseUrl);
7772
+ const npxInstall = {
7773
+ command: "npx",
7774
+ args: npxArgs,
7775
+ manualCommand: `npx ${npxArgs.map(shellQuote2).join(" ")}`
7776
+ };
7777
+ if (hasCommand("bunx")) {
7778
+ const bunxArgs = buildBunxSkillsInstallArgs(baseUrl);
7779
+ return [
7780
+ {
7781
+ command: "bunx",
7782
+ args: bunxArgs,
7783
+ manualCommand: `bunx ${bunxArgs.map(shellQuote2).join(" ")}`
7784
+ },
7785
+ npxInstall
7786
+ ];
7787
+ }
7788
+ return [npxInstall];
7789
+ }
7790
+ function runOneSkillsInstall(install) {
7187
7791
  return new Promise((resolve8) => {
7188
- const child = spawn("npx", args, {
7792
+ const child = spawn(install.command, install.args, {
7189
7793
  stdio: ["ignore", "ignore", "pipe"],
7190
7794
  env: process.env
7191
7795
  });
@@ -7194,37 +7798,63 @@ function runSkillsInstall(baseUrl) {
7194
7798
  stderr += chunk.toString("utf-8");
7195
7799
  });
7196
7800
  child.on("error", (error) => {
7197
- process.stderr.write(`SDK skills sync failed to start: ${error.message}
7198
- `);
7199
- resolve8(false);
7801
+ resolve8({
7802
+ ok: false,
7803
+ detail: `failed to start ${install.command}: ${error.message}`,
7804
+ manualCommand: install.manualCommand
7805
+ });
7200
7806
  });
7201
7807
  child.on("close", (code) => {
7202
7808
  if (code === 0) {
7203
- resolve8(true);
7809
+ resolve8({ ok: true, detail: "", manualCommand: install.manualCommand });
7204
7810
  return;
7205
7811
  }
7206
7812
  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);
7813
+ resolve8({
7814
+ ok: false,
7815
+ detail: detail ? `${install.command}: ${detail}` : `${install.command} exited ${code}`,
7816
+ manualCommand: install.manualCommand
7817
+ });
7213
7818
  });
7214
7819
  });
7215
7820
  }
7821
+ async function runSkillsInstall(baseUrl) {
7822
+ const failures = [];
7823
+ for (const install of resolveSkillsInstallCommands(baseUrl)) {
7824
+ const result = await runOneSkillsInstall(install);
7825
+ if (result.ok) return true;
7826
+ failures.push(result);
7827
+ }
7828
+ const details = failures.map((failure) => failure.detail).filter(Boolean).join("\n");
7829
+ const manualCommand = failures.at(-1)?.manualCommand;
7830
+ process.stderr.write(
7831
+ `SDK skills sync failed${details ? `:
7832
+ ${details}` : ""}
7833
+ ` + (manualCommand ? `Run manually: ${manualCommand}
7834
+ ` : "")
7835
+ );
7836
+ return false;
7837
+ }
7838
+ function writeSdkSkillsStatusLine(line) {
7839
+ const progress = getActiveCliProgress();
7840
+ if (progress) {
7841
+ progress.writeLine(line);
7842
+ return;
7843
+ }
7844
+ process.stderr.write(`${line}
7845
+ `);
7846
+ }
7216
7847
  async function syncSdkSkillsIfNeeded(baseUrl) {
7217
7848
  if (attemptedSync || shouldSkipSkillsSync()) return;
7218
7849
  attemptedSync = true;
7219
7850
  const localVersion = readLocalSkillsVersion(baseUrl);
7220
7851
  const update = await fetchSkillsUpdate(baseUrl, localVersion);
7221
7852
  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");
7853
+ writeSdkSkillsStatusLine("SDK skills changed; syncing deepline-sdk skill...");
7224
7854
  const installed = await runSkillsInstall(baseUrl);
7225
7855
  if (!installed) return;
7226
7856
  writeLocalSkillsVersion(baseUrl, update.remoteVersion);
7227
- progress?.writeLine("SDK skills are up to date.") ?? process.stderr.write("SDK skills are up to date.\n");
7857
+ writeSdkSkillsStatusLine("SDK skills are up to date.");
7228
7858
  }
7229
7859
 
7230
7860
  // src/cli/trace.ts
@@ -7386,4 +8016,3 @@ Output:
7386
8016
  process.exit(process.exitCode ?? 0);
7387
8017
  }
7388
8018
  main();
7389
- //# sourceMappingURL=index.mjs.map