deepline 0.3.124 → 0.3.126

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 (31) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +10 -0
  2. package/dist/bundling-sources/sdk/src/compat.ts +17 -2
  3. package/dist/bundling-sources/sdk/src/play.ts +3 -1
  4. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  5. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +61 -10
  6. package/dist/bundling-sources/shared_libs/observability/redaction.ts +3 -0
  7. package/dist/bundling-sources/shared_libs/observability/runtime-console.ts +174 -0
  8. package/dist/bundling-sources/shared_libs/observability/telemetry.ts +26 -2
  9. package/dist/bundling-sources/shared_libs/play-runtime/play-admission-outcome.ts +3 -1
  10. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/contracts.ts +20 -0
  11. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/index.ts +1 -0
  12. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/postgres-adapter.ts +28 -11
  13. package/dist/bundling-sources/shared_libs/play-runtime/runtime-telemetry.ts +121 -1
  14. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +91 -2
  15. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +87 -41
  16. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +4 -0
  17. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/scheduler-capacity.ts +5 -0
  18. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +1 -0
  19. package/dist/bundling-sources/shared_libs/security/secret-redaction.ts +56 -1
  20. package/dist/cli/index.js +528 -466
  21. package/dist/cli/index.mjs +528 -466
  22. package/dist/index.d.mts +13 -1
  23. package/dist/index.d.ts +13 -1
  24. package/dist/index.js +1 -1
  25. package/dist/index.mjs +1 -1
  26. package/dist/install-integrity.json +1 -0
  27. package/dist/release.d.mts +1 -1
  28. package/dist/release.d.ts +1 -1
  29. package/dist/release.js +1 -1
  30. package/dist/release.mjs +1 -1
  31. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -3068,7 +3068,7 @@ var SDK_RELEASE = {
3068
3068
  // getters keep their established compatibility behavior.
3069
3069
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
3070
3070
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
3071
- version: "0.3.124",
3071
+ version: "0.3.126",
3072
3072
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
3073
3073
  packageCapabilities: {
3074
3074
  updatePreferences: 1
@@ -10303,6 +10303,12 @@ async function checkSdkCompatibility(baseUrl, options = {}) {
10303
10303
  clearTimeout(timeout);
10304
10304
  }
10305
10305
  }
10306
+ function isCiEnvironment() {
10307
+ return ["CI", "GITHUB_ACTIONS"].some((name) => {
10308
+ const value = process.env[name]?.trim().toLowerCase();
10309
+ return value === "1" || value === "true" || value === "yes";
10310
+ });
10311
+ }
10306
10312
  function enforceSdkCompatibilityResponse(response) {
10307
10313
  if (!response) {
10308
10314
  return;
@@ -10310,7 +10316,7 @@ function enforceSdkCompatibilityResponse(response) {
10310
10316
  if (response.update_required) {
10311
10317
  throw new Error(response.message);
10312
10318
  }
10313
- if (response.status === "deprecated" || response.status === "update_available") {
10319
+ if ((response.status === "deprecated" || response.status === "update_available") && !isCiEnvironment()) {
10314
10320
  process.stderr.write(`${response.message}
10315
10321
  `);
10316
10322
  }
@@ -21239,7 +21245,8 @@ function printResult(input2) {
21239
21245
  lines: [
21240
21246
  `${input2.directory}: ${input2.status}`,
21241
21247
  ...input2.outcomes.map(renderOutcome),
21242
- ...failed && input2.deployment.error ? [input2.deployment.error] : []
21248
+ ...failed && input2.deployment.error ? [input2.deployment.error] : [],
21249
+ `Details: deepline plays deployments ${input2.deployment.id}`
21243
21250
  ]
21244
21251
  }
21245
21252
  ]
@@ -21318,6 +21325,461 @@ async function handlePlaysDeploy(directory, options) {
21318
21325
  }
21319
21326
  }
21320
21327
 
21328
+ // src/cli/commands/git.ts
21329
+ var GIT_DEPLOY_FEATURE = "github-deploy";
21330
+ function shellQuote2(value) {
21331
+ return `'${value.replace(/'/g, `'\\''`)}'`;
21332
+ }
21333
+ function renderProject(project) {
21334
+ const root = project.rootDirectory ? ` (${project.rootDirectory})` : "";
21335
+ const flags = [
21336
+ project.disabled ? "unlinked" : "linked",
21337
+ `deploys ${project.productionBranch}`,
21338
+ project.checkOnPullRequest ? "PR checks on" : "PR checks off"
21339
+ ].join(", ");
21340
+ return `${project.id} ${project.repoFullName}${root} - ${flags}`;
21341
+ }
21342
+ function renderDeployment(deployment) {
21343
+ const fromDirectory = deployment.playProjectId === null;
21344
+ const kind = fromDirectory ? "directory deploy" : deployment.isProduction ? "production" : deployment.trigger === "pull_request" ? `pr #${deployment.pullRequestNumber ?? "?"}` : deployment.trigger;
21345
+ const suffix = deployment.error ? ` - ${deployment.error.slice(0, 80)}` : "";
21346
+ const source = [
21347
+ deployment.commitSha?.slice(0, 10),
21348
+ deployment.ref?.replace("refs/heads/", ""),
21349
+ fromDirectory ? deployment.repoFullName : null
21350
+ ].filter(Boolean).join(" ") || "no git metadata";
21351
+ return `${deployment.id} ${source} (${kind}) - ${deployment.status}${suffix}`;
21352
+ }
21353
+ async function handleList(options) {
21354
+ const { http } = getAuthedHttpClient();
21355
+ const payload = await http.get(
21356
+ `/api/v2/play-projects${options.all ? "?includeDisabled=1" : ""}`
21357
+ );
21358
+ printCommandEnvelope(
21359
+ {
21360
+ projects: payload.projects,
21361
+ count: payload.projects.length,
21362
+ render: {
21363
+ sections: [
21364
+ {
21365
+ title: "linked repositories",
21366
+ lines: payload.projects.length ? payload.projects.map(renderProject) : [
21367
+ "No repositories are linked. Run `deepline git repos` to see what the GitHub installation can reach, then `deepline git link`."
21368
+ ]
21369
+ }
21370
+ ]
21371
+ }
21372
+ },
21373
+ { json: options.json }
21374
+ );
21375
+ }
21376
+ function describeInstallationStatus(installation) {
21377
+ switch (installation.status) {
21378
+ case "active":
21379
+ return [
21380
+ `Connected to ${installation.accountLogin} (installation ${installation.installationId}).`
21381
+ ];
21382
+ case "uninstalled":
21383
+ return [
21384
+ `The Deepline GitHub App was uninstalled from ${installation.accountLogin}${installation.uninstalledAt ? ` at ${new Date(installation.uninstalledAt).toISOString()}` : ""}.`,
21385
+ "Deploys are paused for every linked repository. Run `deepline git connect`, then `deepline git link <owner/repo>` again for each repository; links from the old installation cannot be resumed."
21386
+ ];
21387
+ case "suspended":
21388
+ return [
21389
+ `GitHub suspended the Deepline App on ${installation.accountLogin}${installation.suspendedAt ? ` at ${new Date(installation.suspendedAt).toISOString()}` : ""}.`,
21390
+ "Pushes are not delivered while it is suspended; unsuspend it in GitHub settings."
21391
+ ];
21392
+ }
21393
+ }
21394
+ async function handleStatus2(options) {
21395
+ const { http } = getAuthedHttpClient();
21396
+ const payload = await http.get("/api/v2/github/installation");
21397
+ const installation = payload.installation;
21398
+ const others = (payload.installations ?? []).filter(
21399
+ (row) => row.installationId !== installation?.installationId
21400
+ );
21401
+ const lines = installation ? describeInstallationStatus(installation) : [
21402
+ "No GitHub installation is connected to this workspace. Run `deepline git connect`."
21403
+ ];
21404
+ for (const other of others) {
21405
+ lines.push(
21406
+ `Also: ${other.accountLogin} (installation ${other.installationId}) is ${other.status}.`
21407
+ );
21408
+ }
21409
+ const byId = new Map(
21410
+ (payload.installations ?? []).map((row) => [row.installationId, row])
21411
+ );
21412
+ const { projects } = await http.get(
21413
+ "/api/v2/play-projects?includeDisabled=1"
21414
+ );
21415
+ const affected_links = projects.map((project) => {
21416
+ const own = byId.get(project.installationId);
21417
+ const verdict = !own ? "unknown" : own.status === "active" ? null : own.status;
21418
+ return verdict ? {
21419
+ id: project.id,
21420
+ repoId: project.repoId,
21421
+ repoFullName: project.repoFullName,
21422
+ rootDirectory: project.rootDirectory,
21423
+ productionBranch: project.productionBranch,
21424
+ manifestPath: project.manifestPath,
21425
+ checkOnPullRequest: project.checkOnPullRequest,
21426
+ disabled: project.disabled,
21427
+ installationId: project.installationId,
21428
+ installationStatus: verdict
21429
+ } : null;
21430
+ }).filter((row) => row !== null);
21431
+ const activeInstallations = (payload.installations ?? []).filter(
21432
+ (row) => row.status === "active"
21433
+ );
21434
+ const relinkCommand = (link2) => [
21435
+ // The numeric repo id, not the name: a repository renamed while the
21436
+ // App was gone keeps a stale name here, and `git link` accepts the id.
21437
+ `deepline git link ${link2.repoId}`,
21438
+ // Stored values are admin-controlled text and this line is
21439
+ // copy-pasted into a shell by whoever reads it, so every
21440
+ // interpolated value below is quoted.
21441
+ `--branch ${shellQuote2(link2.productionBranch)}`,
21442
+ link2.rootDirectory ? `--root-dir ${shellQuote2(link2.rootDirectory)}` : "",
21443
+ link2.manifestPath && link2.manifestPath !== "deepline.json" ? `--manifest ${shellQuote2(link2.manifestPath)}` : "",
21444
+ link2.checkOnPullRequest ? "" : "--no-pr-checks",
21445
+ // With several active installations no id is guessed: the reader picks.
21446
+ activeInstallations.length > 1 ? "--installation <id>" : ""
21447
+ ].filter(Boolean).join(" ");
21448
+ for (const link2 of affected_links) {
21449
+ const label = `${link2.repoFullName}${link2.rootDirectory ? ` (${link2.rootDirectory})` : ""}`;
21450
+ lines.push(
21451
+ link2.installationStatus !== "suspended" ? `Link again: ${label} \u2014 its installation ${link2.installationId} is ${link2.installationStatus}; run \`${relinkCommand(link2)}\`${activeInstallations.length > 1 ? ` with <id> being the installation that reaches this repo: ${activeInstallations.map((row) => `${row.installationId} (${row.accountLogin})`).join(", ")}` : ""}.` : (
21452
+ // A link disabled during the suspension stays disabled; only an
21453
+ // enabled one picks up again on its own when GitHub unsuspends.
21454
+ link2.disabled ? `Paused and disabled: ${label} \u2014 its installation is suspended, and the link stays disabled after GitHub unsuspends it until you run \`deepline git set ${link2.id} --enabled\`.` : `Paused: ${label} \u2014 its installation is suspended; pushes resume when GitHub unsuspends it.`
21455
+ )
21456
+ );
21457
+ }
21458
+ printCommandEnvelope(
21459
+ {
21460
+ installation,
21461
+ installations: payload.installations ?? [],
21462
+ affected_links,
21463
+ render: { sections: [{ title: "github installation", lines }] }
21464
+ },
21465
+ { json: options.json }
21466
+ );
21467
+ }
21468
+ async function handleRepos(options) {
21469
+ const { http } = getAuthedHttpClient();
21470
+ const query = options.installation ? `?installationId=${encodeURIComponent(options.installation)}` : "";
21471
+ const payload = await http.get(`/api/v2/github/repos${query}`);
21472
+ printCommandEnvelope(
21473
+ {
21474
+ installation: payload.installation,
21475
+ repositories: payload.repositories,
21476
+ count: payload.repositories.length,
21477
+ render: {
21478
+ sections: [
21479
+ {
21480
+ title: `repos on installation @${payload.installation.accountLogin} (${payload.installation.repositorySelection})`,
21481
+ lines: payload.repositories.map(
21482
+ (repo) => `${repo.repoId} ${repo.fullName} (default: ${repo.defaultBranch}${repo.private ? ", private" : ""}${repo.fork ? ", fork" : ""})`
21483
+ )
21484
+ }
21485
+ ]
21486
+ }
21487
+ },
21488
+ { json: options.json }
21489
+ );
21490
+ }
21491
+ async function resolveRepoId(repo, installation) {
21492
+ if (/^\d+$/.test(repo)) return Number(repo);
21493
+ const { http } = getAuthedHttpClient();
21494
+ const query = installation ? `?installationId=${encodeURIComponent(installation)}` : "";
21495
+ const payload = await http.get(
21496
+ `/api/v2/github/repos${query}`
21497
+ );
21498
+ const match = payload.repositories.find(
21499
+ (entry) => entry.fullName.toLowerCase() === repo.toLowerCase()
21500
+ );
21501
+ if (!match) {
21502
+ throw new Error(
21503
+ `Repo "${repo}" is not accessible to the workspace's GitHub installation. Run \`deepline git repos\` to list what is.`
21504
+ );
21505
+ }
21506
+ return match.repoId;
21507
+ }
21508
+ async function handleLink(repo, options) {
21509
+ const repoId = await resolveRepoId(repo, options.installation);
21510
+ const { http } = getAuthedHttpClient();
21511
+ const payload = await http.post("/api/v2/play-projects", {
21512
+ repoId,
21513
+ ...options.installation !== void 0 ? { installationId: options.installation } : {},
21514
+ ...options.rootDir !== void 0 ? { rootDirectory: options.rootDir } : {},
21515
+ ...options.manifest !== void 0 ? { manifestPath: options.manifest } : {},
21516
+ ...options.branch !== void 0 ? { productionBranch: options.branch } : {},
21517
+ ...options.prChecks !== void 0 ? { checkOnPullRequest: options.prChecks } : {}
21518
+ });
21519
+ printCommandEnvelope(
21520
+ {
21521
+ project: payload.project,
21522
+ render: {
21523
+ sections: [
21524
+ {
21525
+ title: "linked",
21526
+ lines: [
21527
+ renderProject(payload.project),
21528
+ ...(payload.warnings ?? []).map(
21529
+ (warning) => `WARNING: ${warning}`
21530
+ ),
21531
+ "Pushes to the production branch now register and promote changed plays.",
21532
+ "Anyone who can push to this repo can change what runs in this workspace."
21533
+ ]
21534
+ }
21535
+ ]
21536
+ }
21537
+ },
21538
+ { json: options.json }
21539
+ );
21540
+ }
21541
+ async function handleGet(id, options) {
21542
+ const { http } = getAuthedHttpClient();
21543
+ const payload = await http.get(
21544
+ `/api/v2/play-projects/${encodeURIComponent(id)}`
21545
+ );
21546
+ printCommandEnvelope(
21547
+ {
21548
+ project: payload.project,
21549
+ render: {
21550
+ sections: [
21551
+ {
21552
+ title: "linked repository",
21553
+ lines: [renderProject(payload.project)]
21554
+ }
21555
+ ]
21556
+ }
21557
+ },
21558
+ { json: options.json }
21559
+ );
21560
+ }
21561
+ async function handleSet(id, options) {
21562
+ const { http } = getAuthedHttpClient();
21563
+ const payload = await http.patch(
21564
+ `/api/v2/play-projects/${encodeURIComponent(id)}`,
21565
+ {
21566
+ ...options.branch !== void 0 ? { productionBranch: options.branch } : {},
21567
+ ...options.rootDir !== void 0 ? { rootDirectory: options.rootDir } : {},
21568
+ ...options.manifest !== void 0 ? { manifestPath: options.manifest } : {},
21569
+ ...options.prChecks !== void 0 ? { checkOnPullRequest: options.prChecks } : {},
21570
+ ...options.disabled !== void 0 ? { disabled: options.disabled } : {}
21571
+ }
21572
+ );
21573
+ printCommandEnvelope(
21574
+ {
21575
+ project: payload.project,
21576
+ render: {
21577
+ sections: [
21578
+ { title: "updated", lines: [renderProject(payload.project)] }
21579
+ ]
21580
+ }
21581
+ },
21582
+ { json: options.json }
21583
+ );
21584
+ }
21585
+ async function handleUnlink(id, options) {
21586
+ const { http } = getAuthedHttpClient();
21587
+ await http.delete(`/api/v2/play-projects/${encodeURIComponent(id)}`);
21588
+ printCommandEnvelope(
21589
+ {
21590
+ ok: true,
21591
+ id,
21592
+ render: {
21593
+ sections: [
21594
+ {
21595
+ title: "unlinked",
21596
+ lines: [
21597
+ `Repository link ${id} removed. Deployments stop immediately and in-flight deploy credentials die; live plays keep running.`
21598
+ ]
21599
+ }
21600
+ ]
21601
+ }
21602
+ },
21603
+ { json: options.json }
21604
+ );
21605
+ }
21606
+ async function handleConnect(options) {
21607
+ const baseUrl = resolveBaseUrlOnly();
21608
+ const connectUrl = `${baseUrl.replace(/\/$/, "")}/api/v2/github/connect`;
21609
+ const opened = openInBrowser(connectUrl);
21610
+ printCommandEnvelope(
21611
+ {
21612
+ connectUrl,
21613
+ browser: opened,
21614
+ render: {
21615
+ sections: [
21616
+ {
21617
+ title: "connect GitHub",
21618
+ lines: [
21619
+ opened === "opened" ? `Opened ${connectUrl} in your browser.` : `Open ${connectUrl} in your browser.`,
21620
+ "Sign in as a workspace admin if prompted, approve the GitHub",
21621
+ "installation, then return here and run `deepline git repos`."
21622
+ ]
21623
+ }
21624
+ ]
21625
+ }
21626
+ },
21627
+ { json: options.json }
21628
+ );
21629
+ }
21630
+ async function handleDeploymentsList(options) {
21631
+ const { http } = getAuthedHttpClient();
21632
+ const query = new URLSearchParams();
21633
+ if (options.repo) query.set("projectId", options.repo);
21634
+ if (options.limit) query.set("limit", options.limit);
21635
+ const suffix = query.size > 0 ? `?${query.toString()}` : "";
21636
+ const payload = await http.get(
21637
+ `/api/v2/deployments${suffix}`
21638
+ );
21639
+ printCommandEnvelope(
21640
+ {
21641
+ deployments: payload.deployments,
21642
+ count: payload.deployments.length,
21643
+ render: {
21644
+ sections: [
21645
+ {
21646
+ title: "deployments",
21647
+ lines: payload.deployments.length ? payload.deployments.map(renderDeployment) : [
21648
+ "No deployments yet. Push to a linked repository, or run `deepline plays deploy`."
21649
+ ]
21650
+ }
21651
+ ]
21652
+ }
21653
+ },
21654
+ { json: options.json }
21655
+ );
21656
+ }
21657
+ function renderDeploymentPlayLines(results) {
21658
+ const plays = results?.plays;
21659
+ if (!Array.isArray(plays)) return [];
21660
+ return plays.flatMap((play) => {
21661
+ const version = play.version === void 0 ? "" : play.previousVersion === void 0 ? ` v${play.version}` : ` v${play.previousVersion} -> v${play.version}`;
21662
+ const file = play.file ? ` (${play.file})` : "";
21663
+ return [
21664
+ `${play.name ?? "?"}${file} - ${play.status ?? "?"}${version}`,
21665
+ ...play.error ? [` error: ${play.error}`] : [],
21666
+ ...play.warning ? [` warning: ${play.warning}`] : []
21667
+ ];
21668
+ });
21669
+ }
21670
+ async function handleDeploymentGet(id, options) {
21671
+ const { http } = getAuthedHttpClient();
21672
+ const payload = await http.get(`/api/v2/deployments/${encodeURIComponent(id)}`);
21673
+ const playLines = renderDeploymentPlayLines(payload.deployment.results);
21674
+ const repository = payload.project?.repoFullName ?? payload.deployment.repoFullName;
21675
+ printCommandEnvelope(
21676
+ {
21677
+ deployment: payload.deployment,
21678
+ project: payload.project,
21679
+ render: {
21680
+ sections: [
21681
+ {
21682
+ title: `deployment ${payload.deployment.id}${repository ? ` (${repository})` : ""}`,
21683
+ lines: [renderDeployment(payload.deployment)]
21684
+ },
21685
+ ...playLines.length ? [{ title: "plays", lines: playLines }] : []
21686
+ ]
21687
+ }
21688
+ },
21689
+ { json: options.json }
21690
+ );
21691
+ }
21692
+ async function handleDeploymentCancel(id, options) {
21693
+ const { http } = getAuthedHttpClient();
21694
+ const payload = await http.post(`/api/v2/deployments/${encodeURIComponent(id)}/cancel`, {});
21695
+ printCommandEnvelope(
21696
+ {
21697
+ cancelled: payload.cancelled,
21698
+ deployment: payload.deployment,
21699
+ render: {
21700
+ sections: [
21701
+ {
21702
+ title: payload.cancelled ? "cancelled" : "already cancelled",
21703
+ lines: [
21704
+ payload.deployment ? renderDeployment(payload.deployment) : `Deployment ${id} cancelled.`,
21705
+ "The build stops at its next step and nothing from it goes live."
21706
+ ]
21707
+ }
21708
+ ]
21709
+ }
21710
+ },
21711
+ { json: options.json }
21712
+ );
21713
+ }
21714
+ function registerGitCommands(program) {
21715
+ const git = program.command("git", { hidden: !cliFeatureEnabled(GIT_DEPLOY_FEATURE) }).alias("projects").description(
21716
+ "Git deploys: link repositories to this workspace and deploy plays on push."
21717
+ ).addHelpText(
21718
+ "after",
21719
+ `
21720
+ Examples:
21721
+ deepline git connect
21722
+ deepline git repos
21723
+ deepline git link acme/growth --branch main
21724
+ deepline git list
21725
+ deepline git set <id> --branch release
21726
+ deepline git unlink <id>
21727
+ deepline git deployments --repo <id>
21728
+ deepline git cancel <deployment-id>
21729
+ `
21730
+ );
21731
+ git.command("connect").description("Connect a GitHub installation (browser step).").option("--json", "Emit JSON output").action(async (options) => handleConnect(options));
21732
+ git.command("status").description(
21733
+ "Show the workspace's GitHub installation and whether it is still usable."
21734
+ ).option("--json", "Emit JSON output").action(async (options) => handleStatus2(options));
21735
+ git.command("repos").description("List repos the workspace's GitHub installation can reach.").option(
21736
+ "--installation <id>",
21737
+ "GitHub installation id (required when several are connected)"
21738
+ ).option("--json", "Emit JSON output").action(
21739
+ async (options) => handleRepos(options)
21740
+ );
21741
+ git.command("list").description("List linked repositories.").option("--all", "Include unlinked repositories").option("--json", "Emit JSON output").action(
21742
+ async (options) => handleList(options)
21743
+ );
21744
+ git.command("link").description("Link a repository to this workspace for deploy-on-push.").argument("<repo>", "Repo full name (owner/name) or numeric repo id").option("--root-dir <path>", "Base path inside the repo for this link").option("--branch <name>", "Production branch (default: repo default)").option(
21745
+ "--manifest <file>",
21746
+ "Manifest file name under the base path (default: deepline.json)"
21747
+ ).option(
21748
+ "--installation <id>",
21749
+ "GitHub installation id (required when several are connected)"
21750
+ ).option("--no-pr-checks", "Skip Check Runs on pull requests").option("--json", "Emit JSON output").action(
21751
+ async (repo, options) => handleLink(repo, options)
21752
+ );
21753
+ git.command("get").description("Show one linked repository.").argument("<id>", "Repository link id").option("--json", "Emit JSON output").action(async (id, options) => handleGet(id, options));
21754
+ git.command("set").description("Update a linked repository.").argument("<id>", "Repository link id").option("--branch <name>", "Production branch").option("--root-dir <path>", "Base path inside the repo for this link").option(
21755
+ "--manifest <file>",
21756
+ "Manifest file name under the base path (default: deepline.json)"
21757
+ ).option("--pr-checks", "Enable Check Runs on pull requests").option("--no-pr-checks", "Disable Check Runs on pull requests").option("--disabled", "Pause deployments for this link").option("--enabled", "Resume deployments for this link").option("--json", "Emit JSON output").action(
21758
+ async (id, options) => handleSet(id, {
21759
+ ...options,
21760
+ disabled: options.enabled === true ? false : options.disabled === true ? true : void 0
21761
+ })
21762
+ );
21763
+ git.command("unlink").description("Unlink a repository (live plays keep running).").argument("<id>", "Repository link id").option("--json", "Emit JSON output").action(
21764
+ async (id, options) => handleUnlink(id, options)
21765
+ );
21766
+ git.command("deployments").description("Deployment history; pass an id for one deployment.").argument("[id]", "Deployment id (omit to list)").option("--repo <id>", "Filter to one repository link").option("--limit <n>", "Max rows (default 50)").option("--json", "Emit JSON output").action(
21767
+ async (id, options) => id ? handleDeploymentGet(id, options) : handleDeploymentsList(options)
21768
+ );
21769
+ git.command("cancel").description(
21770
+ "Cancel a queued or running deployment before its plays go live (admin)."
21771
+ ).argument("<id>", "Deployment id").option("--json", "Emit JSON output").action(
21772
+ async (id, options) => handleDeploymentCancel(id, options)
21773
+ );
21774
+ const deployments = program.command("deployments", { hidden: true });
21775
+ deployments.command("list").option("--project <id>", "Filter to one repository link").option("--limit <n>", "Max rows (default 50)").option("--json", "Emit JSON output").action(
21776
+ async (options) => handleDeploymentsList({ ...options, repo: options.project })
21777
+ );
21778
+ deployments.command("get").argument("<id>", "Deployment id").option("--json", "Emit JSON output").action(
21779
+ async (id, options) => handleDeploymentGet(id, options)
21780
+ );
21781
+ }
21782
+
21321
21783
  // src/cli/commands/play.ts
21322
21784
  var PLAY_BUNDLER_MODULE_PATHS = [
21323
21785
  // Built CLI bundle: dist/cli/index.mjs -> dist/plays/bundle-play-file.mjs.
@@ -21664,7 +22126,7 @@ function rankPlayReferenceSuggestions(input2) {
21664
22126
  function searchQueryForMissingPlayReference(target) {
21665
22127
  return parseReferencedPlayTarget2(target).unqualifiedPlayName.replace(/[-_./]+/g, " ").replace(/\s+/g, " ").trim();
21666
22128
  }
21667
- function shellQuote2(value) {
22129
+ function shellQuote3(value) {
21668
22130
  return `'${value.replace(/'/g, `'\\''`)}'`;
21669
22131
  }
21670
22132
  function buildPlayReferenceNotFoundMessage(input2) {
@@ -21697,7 +22159,7 @@ function buildPlayReferenceNotFoundMessage(input2) {
21697
22159
  lines.push(
21698
22160
  "",
21699
22161
  "Search visible plays:",
21700
- ` deepline plays search ${shellQuote2(searchQuery)} --all --json`
22162
+ ` deepline plays search ${shellQuote3(searchQuery)} --all --json`
21701
22163
  );
21702
22164
  }
21703
22165
  return lines.join("\n");
@@ -26457,7 +26919,7 @@ function printPlayCheckOutcome(outcome, target, prefix) {
26457
26919
  printPlayCheckLimits(result.limits);
26458
26920
  if (result.artifactHash && outcome.exportName === PLAY_DEFAULT_EXPORT) {
26459
26921
  console.log(
26460
- ` publish: deepline plays publish ${shellQuote2(target)} --expected-artifact ${result.artifactHash}`
26922
+ ` publish: deepline plays publish ${shellQuote3(target)} --expected-artifact ${result.artifactHash}`
26461
26923
  );
26462
26924
  }
26463
26925
  printPlayTriggers(result.triggers);
@@ -27090,7 +27552,7 @@ async function handleRunsList(args) {
27090
27552
  return lines2.join("\n");
27091
27553
  });
27092
27554
  const nextOffset = (page.offset ?? offset ?? 0) + runs.length;
27093
- const nextPage = page.hasMore && playName ? `deepline runs list --play ${shellQuote2(playName)}${statusFilter ? ` --status ${shellQuote2(statusFilter)}` : ""} --limit ${page.limit ?? limit ?? 20} --offset ${nextOffset} --json` : null;
27555
+ const nextPage = page.hasMore && playName ? `deepline runs list --play ${shellQuote3(playName)}${statusFilter ? ` --status ${shellQuote3(statusFilter)}` : ""} --limit ${page.limit ?? limit ?? 20} --offset ${nextOffset} --json` : null;
27094
27556
  if (page.hasMore) {
27095
27557
  lines.push("List truncated \u2014 more runs match this filter.");
27096
27558
  lines.push(
@@ -27851,7 +28313,7 @@ async function handlePlayList(args) {
27851
28313
  cursor
27852
28314
  });
27853
28315
  const plays = page.plays;
27854
- const nextPage = page.hasMore && page.cursor ? `deepline plays list${categories ? ` --categories ${shellQuote2(categories)}` : ""}${limit ? ` --limit ${limit}` : ""}${showCost ? " --show-cost" : ""} --cursor ${shellQuote2(page.cursor)} --json` : null;
28316
+ const nextPage = page.hasMore && page.cursor ? `deepline plays list${categories ? ` --categories ${shellQuote3(categories)}` : ""}${limit ? ` --limit ${limit}` : ""}${showCost ? " --show-cost" : ""} --cursor ${shellQuote3(page.cursor)} --json` : null;
27855
28317
  if (page.hasMore && !nextPage) {
27856
28318
  throw new Error(
27857
28319
  "Play list reported more results without a continuation cursor"
@@ -28272,7 +28734,7 @@ async function handlePlaySave(args) {
28272
28734
  message: "Refusing to save: the local file no longer produces the artifact checked earlier.",
28273
28735
  expectedArtifactHash: options.expectedArtifactHash,
28274
28736
  currentArtifactHash: artifactHash,
28275
- next: `deepline plays check ${shellQuote2(options.target)}`
28737
+ next: `deepline plays check ${shellQuote3(options.target)}`
28276
28738
  };
28277
28739
  if (options.jsonOutput) {
28278
28740
  printCompactJson(result);
@@ -28337,7 +28799,7 @@ async function handlePlaySave(args) {
28337
28799
  graphHash: graph.root.artifact.graphHash,
28338
28800
  artifactStorageKey: saved.artifactStorageKey,
28339
28801
  next: {
28340
- publish: `deepline plays publish ${shellQuote2(options.target)} --expected-artifact ${artifactHash}`,
28802
+ publish: `deepline plays publish ${shellQuote3(options.target)} --expected-artifact ${artifactHash}`,
28341
28803
  get: `deepline plays get ${playName} --source --out ./${playName}/ --working`
28342
28804
  }
28343
28805
  };
@@ -28397,7 +28859,7 @@ async function handlePlayPublish(args) {
28397
28859
  message: "Refusing to publish: the local file no longer produces the artifact checked earlier.",
28398
28860
  expectedArtifactHash: options.expectedArtifactHash,
28399
28861
  currentArtifactHash: artifactHash,
28400
- next: `deepline plays check ${shellQuote2(playName)}`
28862
+ next: `deepline plays check ${shellQuote3(playName)}`
28401
28863
  };
28402
28864
  if (options.jsonOutput) {
28403
28865
  printCompactJson(result3);
@@ -29165,6 +29627,22 @@ Examples:
29165
29627
  ).option("--repo <owner/name>", "Repository to record on the deployment").option("--ref <ref>", "Branch or ref to record on the deployment").option("--commit <sha>", "Commit to record on the deployment").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(
29166
29628
  async (directory, options) => handlePlaysDeploy(directory, options)
29167
29629
  );
29630
+ play.command("deployments [id]").description(
29631
+ "Deployment history for this workspace; pass an id for one deployment and its plays."
29632
+ ).addHelpText(
29633
+ "after",
29634
+ `
29635
+ Notes:
29636
+ Lists every deployment, whether \`deepline plays deploy\` or a linked
29637
+ repository's push made it. The same views as \`deepline git deployments\`.
29638
+
29639
+ Examples:
29640
+ deepline plays deployments
29641
+ deepline plays deployments <deployment-id>
29642
+ `
29643
+ ).option("--limit <n>", "Max rows (default 50)").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(
29644
+ async (id, options) => id ? handleDeploymentGet(id, options) : handleDeploymentsList(options)
29645
+ );
29168
29646
  addPublishHelp(
29169
29647
  play.command("publish <target>").description(
29170
29648
  "Promote a saved play revision or publish a local play file."
@@ -37578,6 +38056,7 @@ function renderMonitorJob(payload) {
37578
38056
  if (!job) return void 0;
37579
38057
  const id = asString(job.id) ?? "<unknown>";
37580
38058
  const state = asString(job.state) ?? "unknown";
38059
+ const outcome = asString(job.outcome);
37581
38060
  const phase = asString(job.phase);
37582
38061
  const counts = asRecord2(job.counts);
37583
38062
  const reconciliation = asRecord2(job.reconciliation);
@@ -37585,6 +38064,9 @@ function renderMonitorJob(payload) {
37585
38064
  `Monitor job: ${id}`,
37586
38065
  `State: ${state}${phase ? ` (${phase})` : ""}`
37587
38066
  ];
38067
+ if (outcome) lines.push(`Outcome: ${outcome}`);
38068
+ const operationId = asString(job.operation_id);
38069
+ if (operationId) lines.push(`Operation: ${operationId}`);
37588
38070
  if (counts) {
37589
38071
  lines.push(
37590
38072
  `Counts: requested ${asFiniteNumber(counts.requested) ?? 0}, created ${asFiniteNumber(counts.created) ?? 0}, updated ${asFiniteNumber(counts.updated) ?? 0}, removed ${asFiniteNumber(counts.removed) ?? 0}, unchanged ${asFiniteNumber(counts.unchanged) ?? 0}, verified ${asFiniteNumber(counts.verified) ?? 0}, failed ${asFiniteNumber(counts.failed) ?? 0}`
@@ -40774,448 +41256,6 @@ Examples:
40774
41256
  );
40775
41257
  }
40776
41258
 
40777
- // src/cli/commands/git.ts
40778
- var GIT_DEPLOY_FEATURE = "github-deploy";
40779
- function shellQuote3(value) {
40780
- return `'${value.replace(/'/g, `'\\''`)}'`;
40781
- }
40782
- function renderProject(project) {
40783
- const root = project.rootDirectory ? ` (${project.rootDirectory})` : "";
40784
- const flags = [
40785
- project.disabled ? "unlinked" : "linked",
40786
- `deploys ${project.productionBranch}`,
40787
- project.checkOnPullRequest ? "PR checks on" : "PR checks off"
40788
- ].join(", ");
40789
- return `${project.id} ${project.repoFullName}${root} - ${flags}`;
40790
- }
40791
- function renderDeployment(deployment) {
40792
- const kind = deployment.isProduction ? "production" : deployment.trigger === "pull_request" ? `pr #${deployment.pullRequestNumber ?? "?"}` : deployment.trigger;
40793
- const suffix = deployment.error ? ` - ${deployment.error.slice(0, 80)}` : "";
40794
- const source = [
40795
- deployment.commitSha?.slice(0, 10),
40796
- deployment.ref?.replace("refs/heads/", "") ?? deployment.repoFullName
40797
- ].filter(Boolean).join(" ") || "directory";
40798
- return `${deployment.id} ${source} (${kind}) - ${deployment.status}${suffix}`;
40799
- }
40800
- async function handleList(options) {
40801
- const { http } = getAuthedHttpClient();
40802
- const payload = await http.get(
40803
- `/api/v2/play-projects${options.all ? "?includeDisabled=1" : ""}`
40804
- );
40805
- printCommandEnvelope(
40806
- {
40807
- projects: payload.projects,
40808
- count: payload.projects.length,
40809
- render: {
40810
- sections: [
40811
- {
40812
- title: "linked repositories",
40813
- lines: payload.projects.length ? payload.projects.map(renderProject) : [
40814
- "No repositories are linked. Run `deepline git repos` to see what the GitHub installation can reach, then `deepline git link`."
40815
- ]
40816
- }
40817
- ]
40818
- }
40819
- },
40820
- { json: options.json }
40821
- );
40822
- }
40823
- function describeInstallationStatus(installation) {
40824
- switch (installation.status) {
40825
- case "active":
40826
- return [
40827
- `Connected to ${installation.accountLogin} (installation ${installation.installationId}).`
40828
- ];
40829
- case "uninstalled":
40830
- return [
40831
- `The Deepline GitHub App was uninstalled from ${installation.accountLogin}${installation.uninstalledAt ? ` at ${new Date(installation.uninstalledAt).toISOString()}` : ""}.`,
40832
- "Deploys are paused for every linked repository. Run `deepline git connect`, then `deepline git link <owner/repo>` again for each repository; links from the old installation cannot be resumed."
40833
- ];
40834
- case "suspended":
40835
- return [
40836
- `GitHub suspended the Deepline App on ${installation.accountLogin}${installation.suspendedAt ? ` at ${new Date(installation.suspendedAt).toISOString()}` : ""}.`,
40837
- "Pushes are not delivered while it is suspended; unsuspend it in GitHub settings."
40838
- ];
40839
- }
40840
- }
40841
- async function handleStatus2(options) {
40842
- const { http } = getAuthedHttpClient();
40843
- const payload = await http.get("/api/v2/github/installation");
40844
- const installation = payload.installation;
40845
- const others = (payload.installations ?? []).filter(
40846
- (row) => row.installationId !== installation?.installationId
40847
- );
40848
- const lines = installation ? describeInstallationStatus(installation) : [
40849
- "No GitHub installation is connected to this workspace. Run `deepline git connect`."
40850
- ];
40851
- for (const other of others) {
40852
- lines.push(
40853
- `Also: ${other.accountLogin} (installation ${other.installationId}) is ${other.status}.`
40854
- );
40855
- }
40856
- const byId = new Map(
40857
- (payload.installations ?? []).map((row) => [row.installationId, row])
40858
- );
40859
- const { projects } = await http.get(
40860
- "/api/v2/play-projects?includeDisabled=1"
40861
- );
40862
- const affected_links = projects.map((project) => {
40863
- const own = byId.get(project.installationId);
40864
- const verdict = !own ? "unknown" : own.status === "active" ? null : own.status;
40865
- return verdict ? {
40866
- id: project.id,
40867
- repoId: project.repoId,
40868
- repoFullName: project.repoFullName,
40869
- rootDirectory: project.rootDirectory,
40870
- productionBranch: project.productionBranch,
40871
- manifestPath: project.manifestPath,
40872
- checkOnPullRequest: project.checkOnPullRequest,
40873
- disabled: project.disabled,
40874
- installationId: project.installationId,
40875
- installationStatus: verdict
40876
- } : null;
40877
- }).filter((row) => row !== null);
40878
- const activeInstallations = (payload.installations ?? []).filter(
40879
- (row) => row.status === "active"
40880
- );
40881
- const relinkCommand = (link2) => [
40882
- // The numeric repo id, not the name: a repository renamed while the
40883
- // App was gone keeps a stale name here, and `git link` accepts the id.
40884
- `deepline git link ${link2.repoId}`,
40885
- // Stored values are admin-controlled text and this line is
40886
- // copy-pasted into a shell by whoever reads it, so every
40887
- // interpolated value below is quoted.
40888
- `--branch ${shellQuote3(link2.productionBranch)}`,
40889
- link2.rootDirectory ? `--root-dir ${shellQuote3(link2.rootDirectory)}` : "",
40890
- link2.manifestPath && link2.manifestPath !== "deepline.json" ? `--manifest ${shellQuote3(link2.manifestPath)}` : "",
40891
- link2.checkOnPullRequest ? "" : "--no-pr-checks",
40892
- // With several active installations no id is guessed: the reader picks.
40893
- activeInstallations.length > 1 ? "--installation <id>" : ""
40894
- ].filter(Boolean).join(" ");
40895
- for (const link2 of affected_links) {
40896
- const label = `${link2.repoFullName}${link2.rootDirectory ? ` (${link2.rootDirectory})` : ""}`;
40897
- lines.push(
40898
- link2.installationStatus !== "suspended" ? `Link again: ${label} \u2014 its installation ${link2.installationId} is ${link2.installationStatus}; run \`${relinkCommand(link2)}\`${activeInstallations.length > 1 ? ` with <id> being the installation that reaches this repo: ${activeInstallations.map((row) => `${row.installationId} (${row.accountLogin})`).join(", ")}` : ""}.` : (
40899
- // A link disabled during the suspension stays disabled; only an
40900
- // enabled one picks up again on its own when GitHub unsuspends.
40901
- link2.disabled ? `Paused and disabled: ${label} \u2014 its installation is suspended, and the link stays disabled after GitHub unsuspends it until you run \`deepline git set ${link2.id} --enabled\`.` : `Paused: ${label} \u2014 its installation is suspended; pushes resume when GitHub unsuspends it.`
40902
- )
40903
- );
40904
- }
40905
- printCommandEnvelope(
40906
- {
40907
- installation,
40908
- installations: payload.installations ?? [],
40909
- affected_links,
40910
- render: { sections: [{ title: "github installation", lines }] }
40911
- },
40912
- { json: options.json }
40913
- );
40914
- }
40915
- async function handleRepos(options) {
40916
- const { http } = getAuthedHttpClient();
40917
- const query = options.installation ? `?installationId=${encodeURIComponent(options.installation)}` : "";
40918
- const payload = await http.get(`/api/v2/github/repos${query}`);
40919
- printCommandEnvelope(
40920
- {
40921
- installation: payload.installation,
40922
- repositories: payload.repositories,
40923
- count: payload.repositories.length,
40924
- render: {
40925
- sections: [
40926
- {
40927
- title: `repos on installation @${payload.installation.accountLogin} (${payload.installation.repositorySelection})`,
40928
- lines: payload.repositories.map(
40929
- (repo) => `${repo.repoId} ${repo.fullName} (default: ${repo.defaultBranch}${repo.private ? ", private" : ""}${repo.fork ? ", fork" : ""})`
40930
- )
40931
- }
40932
- ]
40933
- }
40934
- },
40935
- { json: options.json }
40936
- );
40937
- }
40938
- async function resolveRepoId(repo, installation) {
40939
- if (/^\d+$/.test(repo)) return Number(repo);
40940
- const { http } = getAuthedHttpClient();
40941
- const query = installation ? `?installationId=${encodeURIComponent(installation)}` : "";
40942
- const payload = await http.get(
40943
- `/api/v2/github/repos${query}`
40944
- );
40945
- const match = payload.repositories.find(
40946
- (entry) => entry.fullName.toLowerCase() === repo.toLowerCase()
40947
- );
40948
- if (!match) {
40949
- throw new Error(
40950
- `Repo "${repo}" is not accessible to the workspace's GitHub installation. Run \`deepline git repos\` to list what is.`
40951
- );
40952
- }
40953
- return match.repoId;
40954
- }
40955
- async function handleLink(repo, options) {
40956
- const repoId = await resolveRepoId(repo, options.installation);
40957
- const { http } = getAuthedHttpClient();
40958
- const payload = await http.post("/api/v2/play-projects", {
40959
- repoId,
40960
- ...options.installation !== void 0 ? { installationId: options.installation } : {},
40961
- ...options.rootDir !== void 0 ? { rootDirectory: options.rootDir } : {},
40962
- ...options.manifest !== void 0 ? { manifestPath: options.manifest } : {},
40963
- ...options.branch !== void 0 ? { productionBranch: options.branch } : {},
40964
- ...options.prChecks !== void 0 ? { checkOnPullRequest: options.prChecks } : {}
40965
- });
40966
- printCommandEnvelope(
40967
- {
40968
- project: payload.project,
40969
- render: {
40970
- sections: [
40971
- {
40972
- title: "linked",
40973
- lines: [
40974
- renderProject(payload.project),
40975
- ...(payload.warnings ?? []).map(
40976
- (warning) => `WARNING: ${warning}`
40977
- ),
40978
- "Pushes to the production branch now register and promote changed plays.",
40979
- "Anyone who can push to this repo can change what runs in this workspace."
40980
- ]
40981
- }
40982
- ]
40983
- }
40984
- },
40985
- { json: options.json }
40986
- );
40987
- }
40988
- async function handleGet(id, options) {
40989
- const { http } = getAuthedHttpClient();
40990
- const payload = await http.get(
40991
- `/api/v2/play-projects/${encodeURIComponent(id)}`
40992
- );
40993
- printCommandEnvelope(
40994
- {
40995
- project: payload.project,
40996
- render: {
40997
- sections: [
40998
- {
40999
- title: "linked repository",
41000
- lines: [renderProject(payload.project)]
41001
- }
41002
- ]
41003
- }
41004
- },
41005
- { json: options.json }
41006
- );
41007
- }
41008
- async function handleSet(id, options) {
41009
- const { http } = getAuthedHttpClient();
41010
- const payload = await http.patch(
41011
- `/api/v2/play-projects/${encodeURIComponent(id)}`,
41012
- {
41013
- ...options.branch !== void 0 ? { productionBranch: options.branch } : {},
41014
- ...options.rootDir !== void 0 ? { rootDirectory: options.rootDir } : {},
41015
- ...options.manifest !== void 0 ? { manifestPath: options.manifest } : {},
41016
- ...options.prChecks !== void 0 ? { checkOnPullRequest: options.prChecks } : {},
41017
- ...options.disabled !== void 0 ? { disabled: options.disabled } : {}
41018
- }
41019
- );
41020
- printCommandEnvelope(
41021
- {
41022
- project: payload.project,
41023
- render: {
41024
- sections: [
41025
- { title: "updated", lines: [renderProject(payload.project)] }
41026
- ]
41027
- }
41028
- },
41029
- { json: options.json }
41030
- );
41031
- }
41032
- async function handleUnlink(id, options) {
41033
- const { http } = getAuthedHttpClient();
41034
- await http.delete(`/api/v2/play-projects/${encodeURIComponent(id)}`);
41035
- printCommandEnvelope(
41036
- {
41037
- ok: true,
41038
- id,
41039
- render: {
41040
- sections: [
41041
- {
41042
- title: "unlinked",
41043
- lines: [
41044
- `Repository link ${id} removed. Deployments stop immediately and in-flight deploy credentials die; live plays keep running.`
41045
- ]
41046
- }
41047
- ]
41048
- }
41049
- },
41050
- { json: options.json }
41051
- );
41052
- }
41053
- async function handleConnect(options) {
41054
- const baseUrl = resolveBaseUrlOnly();
41055
- const connectUrl = `${baseUrl.replace(/\/$/, "")}/api/v2/github/connect`;
41056
- const opened = openInBrowser(connectUrl);
41057
- printCommandEnvelope(
41058
- {
41059
- connectUrl,
41060
- browser: opened,
41061
- render: {
41062
- sections: [
41063
- {
41064
- title: "connect GitHub",
41065
- lines: [
41066
- opened === "opened" ? `Opened ${connectUrl} in your browser.` : `Open ${connectUrl} in your browser.`,
41067
- "Sign in as a workspace admin if prompted, approve the GitHub",
41068
- "installation, then return here and run `deepline git repos`."
41069
- ]
41070
- }
41071
- ]
41072
- }
41073
- },
41074
- { json: options.json }
41075
- );
41076
- }
41077
- async function handleDeploymentsList(options) {
41078
- const { http } = getAuthedHttpClient();
41079
- const query = new URLSearchParams();
41080
- if (options.repo) query.set("projectId", options.repo);
41081
- if (options.limit) query.set("limit", options.limit);
41082
- const suffix = query.size > 0 ? `?${query.toString()}` : "";
41083
- const payload = await http.get(
41084
- `/api/v2/deployments${suffix}`
41085
- );
41086
- printCommandEnvelope(
41087
- {
41088
- deployments: payload.deployments,
41089
- count: payload.deployments.length,
41090
- render: {
41091
- sections: [
41092
- {
41093
- title: "deployments",
41094
- lines: payload.deployments.length ? payload.deployments.map(renderDeployment) : [
41095
- "No deployments yet. Push to a linked repository to create one."
41096
- ]
41097
- }
41098
- ]
41099
- }
41100
- },
41101
- { json: options.json }
41102
- );
41103
- }
41104
- async function handleDeploymentGet(id, options) {
41105
- const { http } = getAuthedHttpClient();
41106
- const payload = await http.get(`/api/v2/deployments/${encodeURIComponent(id)}`);
41107
- const results = payload.deployment.results;
41108
- const playLines = Array.isArray(results?.plays) ? results.plays.map(
41109
- (play) => `${play.name ?? "?"} - ${play.status ?? "?"}${play.error ? `: ${play.error}` : ""}`
41110
- ) : [];
41111
- printCommandEnvelope(
41112
- {
41113
- deployment: payload.deployment,
41114
- project: payload.project,
41115
- render: {
41116
- sections: [
41117
- {
41118
- title: `deployment ${payload.deployment.id}${payload.project ? ` (${payload.project.repoFullName})` : ""}`,
41119
- lines: [renderDeployment(payload.deployment)]
41120
- },
41121
- ...playLines.length ? [{ title: "plays", lines: playLines }] : []
41122
- ]
41123
- }
41124
- },
41125
- { json: options.json }
41126
- );
41127
- }
41128
- async function handleDeploymentCancel(id, options) {
41129
- const { http } = getAuthedHttpClient();
41130
- const payload = await http.post(`/api/v2/deployments/${encodeURIComponent(id)}/cancel`, {});
41131
- printCommandEnvelope(
41132
- {
41133
- cancelled: payload.cancelled,
41134
- deployment: payload.deployment,
41135
- render: {
41136
- sections: [
41137
- {
41138
- title: payload.cancelled ? "cancelled" : "already cancelled",
41139
- lines: [
41140
- payload.deployment ? renderDeployment(payload.deployment) : `Deployment ${id} cancelled.`,
41141
- "The build stops at its next step and nothing from it goes live."
41142
- ]
41143
- }
41144
- ]
41145
- }
41146
- },
41147
- { json: options.json }
41148
- );
41149
- }
41150
- function registerGitCommands(program) {
41151
- const git = program.command("git", { hidden: !cliFeatureEnabled(GIT_DEPLOY_FEATURE) }).alias("projects").description(
41152
- "Git deploys: link repositories to this workspace and deploy plays on push."
41153
- ).addHelpText(
41154
- "after",
41155
- `
41156
- Examples:
41157
- deepline git connect
41158
- deepline git repos
41159
- deepline git link acme/growth --branch main
41160
- deepline git list
41161
- deepline git set <id> --branch release
41162
- deepline git unlink <id>
41163
- deepline git deployments --repo <id>
41164
- deepline git cancel <deployment-id>
41165
- `
41166
- );
41167
- git.command("connect").description("Connect a GitHub installation (browser step).").option("--json", "Emit JSON output").action(async (options) => handleConnect(options));
41168
- git.command("status").description(
41169
- "Show the workspace's GitHub installation and whether it is still usable."
41170
- ).option("--json", "Emit JSON output").action(async (options) => handleStatus2(options));
41171
- git.command("repos").description("List repos the workspace's GitHub installation can reach.").option(
41172
- "--installation <id>",
41173
- "GitHub installation id (required when several are connected)"
41174
- ).option("--json", "Emit JSON output").action(
41175
- async (options) => handleRepos(options)
41176
- );
41177
- git.command("list").description("List linked repositories.").option("--all", "Include unlinked repositories").option("--json", "Emit JSON output").action(
41178
- async (options) => handleList(options)
41179
- );
41180
- git.command("link").description("Link a repository to this workspace for deploy-on-push.").argument("<repo>", "Repo full name (owner/name) or numeric repo id").option("--root-dir <path>", "Base path inside the repo for this link").option("--branch <name>", "Production branch (default: repo default)").option(
41181
- "--manifest <file>",
41182
- "Manifest file name under the base path (default: deepline.json)"
41183
- ).option(
41184
- "--installation <id>",
41185
- "GitHub installation id (required when several are connected)"
41186
- ).option("--no-pr-checks", "Skip Check Runs on pull requests").option("--json", "Emit JSON output").action(
41187
- async (repo, options) => handleLink(repo, options)
41188
- );
41189
- git.command("get").description("Show one linked repository.").argument("<id>", "Repository link id").option("--json", "Emit JSON output").action(async (id, options) => handleGet(id, options));
41190
- git.command("set").description("Update a linked repository.").argument("<id>", "Repository link id").option("--branch <name>", "Production branch").option("--root-dir <path>", "Base path inside the repo for this link").option(
41191
- "--manifest <file>",
41192
- "Manifest file name under the base path (default: deepline.json)"
41193
- ).option("--pr-checks", "Enable Check Runs on pull requests").option("--no-pr-checks", "Disable Check Runs on pull requests").option("--disabled", "Pause deployments for this link").option("--enabled", "Resume deployments for this link").option("--json", "Emit JSON output").action(
41194
- async (id, options) => handleSet(id, {
41195
- ...options,
41196
- disabled: options.enabled === true ? false : options.disabled === true ? true : void 0
41197
- })
41198
- );
41199
- git.command("unlink").description("Unlink a repository (live plays keep running).").argument("<id>", "Repository link id").option("--json", "Emit JSON output").action(
41200
- async (id, options) => handleUnlink(id, options)
41201
- );
41202
- git.command("deployments").description("Deployment history; pass an id for one deployment.").argument("[id]", "Deployment id (omit to list)").option("--repo <id>", "Filter to one repository link").option("--limit <n>", "Max rows (default 50)").option("--json", "Emit JSON output").action(
41203
- async (id, options) => id ? handleDeploymentGet(id, options) : handleDeploymentsList(options)
41204
- );
41205
- git.command("cancel").description(
41206
- "Cancel a queued or running deployment before its plays go live (admin)."
41207
- ).argument("<id>", "Deployment id").option("--json", "Emit JSON output").action(
41208
- async (id, options) => handleDeploymentCancel(id, options)
41209
- );
41210
- const deployments = program.command("deployments", { hidden: true });
41211
- deployments.command("list").option("--project <id>", "Filter to one repository link").option("--limit <n>", "Max rows (default 50)").option("--json", "Emit JSON output").action(
41212
- async (options) => handleDeploymentsList({ ...options, repo: options.project })
41213
- );
41214
- deployments.command("get").argument("<id>", "Deployment id").option("--json", "Emit JSON output").action(
41215
- async (id, options) => handleDeploymentGet(id, options)
41216
- );
41217
- }
41218
-
41219
41259
  // src/cli/getting-started.ts
41220
41260
  var DEEPLINE_GTM_SKILL = "/deepline-gtm";
41221
41261
  var DEEPLINE_GTM_STARTER_PROMPTS = [
@@ -41310,6 +41350,33 @@ function normalizeSecretName(value) {
41310
41350
  }
41311
41351
  return normalized;
41312
41352
  }
41353
+ function normalizePlaySelector(value) {
41354
+ const normalized = value?.trim();
41355
+ if (value !== void 0 && !normalized) {
41356
+ throw new Error("--play requires a bare Play name.");
41357
+ }
41358
+ if (!normalized) return void 0;
41359
+ if (normalized.includes("/")) {
41360
+ throw new Error(
41361
+ "--play expects the bare Play name, not an org-qualified name."
41362
+ );
41363
+ }
41364
+ return normalized;
41365
+ }
41366
+ function resolveSecretWriteScope(options) {
41367
+ const playName = normalizePlaySelector(options.play);
41368
+ if (options.scope && options.scope !== "org" && options.scope !== "play") {
41369
+ throw new Error("--scope must be either org or play.");
41370
+ }
41371
+ const scope = options.scope === void 0 ? playName ? "play" : "org" : options.scope;
41372
+ if (scope === "play" && !playName) {
41373
+ throw new Error("--play <name> is required when --scope play is used.");
41374
+ }
41375
+ if (scope === "org" && playName) {
41376
+ throw new Error("--play can only be used with --scope play.");
41377
+ }
41378
+ return scope === "play" ? { scope, playName } : { scope };
41379
+ }
41313
41380
  function renderSecret(secret) {
41314
41381
  const scope = secret.scope === "play" && secret.playName ? `play:${secret.playName}` : secret.scope;
41315
41382
  const added = new Date(secret.createdAt).toISOString();
@@ -41479,7 +41546,8 @@ async function handleList2(options) {
41479
41546
  async function handleCheck(nameInput, options) {
41480
41547
  const name = normalizeSecretName(nameInput);
41481
41548
  const client2 = new DeeplineClient();
41482
- const check = options.play !== void 0 ? await client2.inspectSecret(name, { playName: options.play }) : null;
41549
+ const playName = options.play === void 0 ? void 0 : normalizePlaySelector(options.play);
41550
+ const check = playName !== void 0 ? await client2.inspectSecret(name, { playName }) : null;
41483
41551
  const secret = check ? check.secret : await client2.checkSecret(name);
41484
41552
  const ok = check ? check.ok : Boolean(secret);
41485
41553
  const quotedPlay = `'${(options.play ?? "PLAY").replace(/'/g, `'"'"'`)}'`;
@@ -41510,11 +41578,7 @@ async function handleCheck(nameInput, options) {
41510
41578
  async function handleSet2(nameInput, forbidden, options) {
41511
41579
  preventShellHistoryLeak(forbidden);
41512
41580
  const name = normalizeSecretName(nameInput);
41513
- const scope = options.scope === "play" ? "play" : "org";
41514
- const playName = options.play?.trim();
41515
- if (scope === "play" && !playName) {
41516
- throw new Error("--play <name> is required when --scope play is used.");
41517
- }
41581
+ const { scope, playName } = resolveSecretWriteScope(options);
41518
41582
  const value = await readSecretValueFromOptions(options);
41519
41583
  const { http } = getAuthedHttpClient();
41520
41584
  const response = await http.post(
@@ -41606,7 +41670,8 @@ Notes:
41606
41670
  Secret values are never accepted as command arguments. Use the hidden prompt,
41607
41671
  or one explicit non-argv source: --value-stdin, --from-env NAME, or
41608
41672
  --from-file PATH. Values are never printed. secrets list shows only metadata,
41609
- including any note you intentionally added.
41673
+ including any note you intentionally added. Supplying --play implies
41674
+ --scope play; --play expects the bare Play name in the current workspace.
41610
41675
 
41611
41676
  Examples:
41612
41677
  deepline secrets list
@@ -41632,7 +41697,7 @@ Examples:
41632
41697
  ).option("--json", "Emit JSON output").action(async (name, options) => {
41633
41698
  await handleCheck(name, options);
41634
41699
  });
41635
- secrets.command("set").description("Set or rotate a secret without exposing its value in argv.").argument("<name>", "Secret name").argument("[forbidden...]", "Do not pass secret values here").option("--json", "Emit JSON output").option("--scope <scope>", "Secret scope: org or play", "org").option("--play <name>", "Play name for play-scoped secrets").option(
41700
+ secrets.command("set").description("Set or rotate a secret without exposing its value in argv.").argument("<name>", "Secret name").argument("[forbidden...]", "Do not pass secret values here").option("--json", "Emit JSON output").option("--scope <scope>", "Secret scope: org or play; --play implies play").option("--play <name>", "Play name for play-scoped secrets").option(
41636
41701
  "--note <note>",
41637
41702
  "Optional non-sensitive memo (maximum 500 characters)"
41638
41703
  ).option("--value-stdin", "Read the value from piped stdin").option("--from-env <name>", "Read the value from an environment variable").option("--from-file <path>", "Read the value from a file").action(
@@ -42247,7 +42312,7 @@ async function maybeReportSdkCliFailure(input2) {
42247
42312
  // src/cli/skills-sync.ts
42248
42313
  var CHECK_TIMEOUT_MS2 = 3e3;
42249
42314
  function shouldSkipSkillsSync() {
42250
- if (detectAgentRuntime() === "claude_cowork") {
42315
+ if (detectAgentRuntime() === "claude_cowork" || isCiEnvironment()) {
42251
42316
  return true;
42252
42317
  }
42253
42318
  const value = process.env.DEEPLINE_SKIP_SKILLS_SYNC?.trim().toLowerCase();
@@ -49674,11 +49739,8 @@ function envTruthy(name) {
49674
49739
  const value = process.env[name]?.trim().toLowerCase();
49675
49740
  return value === "1" || value === "true" || value === "yes";
49676
49741
  }
49677
- function isCi() {
49678
- return envTruthy("CI") || envTruthy("GITHUB_ACTIONS");
49679
- }
49680
49742
  function shouldSkipSelfUpdate() {
49681
- return envTruthy("DEEPLINE_SKIP_SELF_UPDATE") || envTruthy("DEEPLINE_NO_AUTO_UPDATE") || envTruthy("DEEPLINE_SKIP_SDK_AUTO_UPDATE") || envTruthy("DEEPLINE_DISABLE_AUTO_UPDATE") || !shouldRunCliAutoUpdate() || isCi();
49743
+ return envTruthy("DEEPLINE_SKIP_SELF_UPDATE") || envTruthy("DEEPLINE_NO_AUTO_UPDATE") || envTruthy("DEEPLINE_SKIP_SDK_AUTO_UPDATE") || envTruthy("DEEPLINE_DISABLE_AUTO_UPDATE") || !shouldRunCliAutoUpdate() || isCiEnvironment();
49682
49744
  }
49683
49745
  function parseSemver(version) {
49684
49746
  const trimmed = version?.trim();