@keystrokehq/cli 0.0.109 → 0.0.111

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.
package/dist/index.mjs CHANGED
@@ -1,15 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import { a as boolean, c as literal, d as record, f as string, h as _coercedNumber, i as array, l as number$1, m as unknown, n as ZodType, o as custom, p as union, r as _enum, s as discriminatedUnion, t as ZodNumber, u as object } from "./schemas-DwAYtN9C.mjs";
2
+ import { a as array, c as discriminatedUnion, d as object, f as record, g as _coercedNumber, h as unknown, i as _enum, l as literal, m as union, n as ZodNumber, o as boolean, p as string, r as ZodType, s as custom, t as entryIdFromFile, u as number$1 } from "./discovery-XqwkB9H_-CQLSzR72.mjs";
3
3
  import { a as installPlaygroundDependencies, i as installDependencies, n as buildPlaygroundWorkspace, o as resolvePackageManager, s as resolveCliRoot, t as readCliVersion } from "./version-DESgLEkE.mjs";
4
4
  import { createRequire } from "node:module";
5
5
  import { Command } from "commander";
6
6
  import { Entry } from "@napi-rs/keyring";
7
7
  import Conf from "conf";
8
8
  import { homedir, platform, tmpdir } from "node:os";
9
- import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
9
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
10
10
  import { confirm, input, select } from "@inquirer/prompts";
11
- import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
11
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
12
12
  import { spawn, spawnSync } from "node:child_process";
13
+ import { createHash } from "node:crypto";
13
14
  import { pathToFileURL } from "node:url";
14
15
  import { access, copyFile, cp, mkdir, readFile, readdir, stat, symlink, unlink, writeFile } from "node:fs/promises";
15
16
  //#region ../../node_modules/.pnpm/ky@2.0.2/node_modules/ky/distribution/errors/KyError.js
@@ -2063,13 +2064,13 @@ const OrganizationUserRoleSchema = _enum([
2063
2064
  "admin",
2064
2065
  "builder"
2065
2066
  ]);
2066
- const isoDateTime$2 = string().datetime();
2067
+ const isoDateTime$3 = string().datetime();
2067
2068
  const OrganizationSchema = object({
2068
2069
  id: string(),
2069
2070
  name: string(),
2070
2071
  slug: OrganizationSlugSchema,
2071
- createdAt: isoDateTime$2,
2072
- updatedAt: isoDateTime$2
2072
+ createdAt: isoDateTime$3,
2073
+ updatedAt: isoDateTime$3
2073
2074
  });
2074
2075
  const ProjectSchema = object({
2075
2076
  id: string(),
@@ -2080,8 +2081,8 @@ const ProjectSchema = object({
2080
2081
  baseUrl: string().nullable(),
2081
2082
  runtimeId: string().nullable(),
2082
2083
  lastError: string().nullable(),
2083
- createdAt: isoDateTime$2,
2084
- updatedAt: isoDateTime$2
2084
+ createdAt: isoDateTime$3,
2085
+ updatedAt: isoDateTime$3
2085
2086
  });
2086
2087
  const UserOrganizationSchema = object({
2087
2088
  organization: OrganizationSchema,
@@ -2419,7 +2420,7 @@ const GatewayAttachmentSourceSchema = object({
2419
2420
  channelIds: array(string()).optional(),
2420
2421
  subscribeOnMention: boolean()
2421
2422
  });
2422
- object({ attachments: array(object({
2423
+ const GatewayAttachmentRecordSchema = object({
2423
2424
  id: string(),
2424
2425
  key: string(),
2425
2426
  agentKey: string(),
@@ -2428,13 +2429,14 @@ object({ attachments: array(object({
2428
2429
  source: GatewayAttachmentSourceSchema,
2429
2430
  registeredAt: string(),
2430
2431
  updatedAt: string()
2431
- })) });
2432
- object({
2432
+ });
2433
+ object({ attachments: array(GatewayAttachmentRecordSchema) });
2434
+ const UpsertGatewayAttachmentBodySchema = object({
2433
2435
  agentKey: string().min(1),
2434
2436
  mode: SlackGatewayModeSchema.optional(),
2435
2437
  subscribeOnMention: boolean().optional()
2436
2438
  });
2437
- object({ agents: array(object({
2439
+ const AgentListResponseSchema = object({ agents: array(object({
2438
2440
  key: string(),
2439
2441
  name: string().optional(),
2440
2442
  route: string()
@@ -2560,7 +2562,7 @@ const OrganizationMemberStatusSchema = _enum([
2560
2562
  "suspended"
2561
2563
  ]);
2562
2564
  const InvitableOrganizationMemberRoleSchema = _enum(["admin", "builder"]);
2563
- const isoDateTime$1 = string().datetime();
2565
+ const isoDateTime$2 = string().datetime();
2564
2566
  const OrganizationMemberSchema = object({
2565
2567
  id: string(),
2566
2568
  name: string(),
@@ -2568,8 +2570,8 @@ const OrganizationMemberSchema = object({
2568
2570
  role: OrganizationMemberRoleSchema,
2569
2571
  status: OrganizationMemberStatusSchema,
2570
2572
  avatarUrl: string().optional(),
2571
- joinedAt: isoDateTime$1.optional(),
2572
- lastActiveAt: isoDateTime$1.nullable().optional()
2573
+ joinedAt: isoDateTime$2.optional(),
2574
+ lastActiveAt: isoDateTime$2.nullable().optional()
2573
2575
  });
2574
2576
  const ListOrganizationMembersResponseSchema = object({ members: array(OrganizationMemberSchema) });
2575
2577
  const InviteOrganizationMembersRequestSchema = object({
@@ -2600,14 +2602,14 @@ const ListOrganizationInvitationsResponseSchema = object({ invitations: array(ob
2600
2602
  const AcceptOrganizationInvitationResponseSchema = object({ organization: UserOrganizationSchema });
2601
2603
  const DeclineOrganizationInvitationResponseSchema = object({ success: literal(true) });
2602
2604
  const ProjectArtifactStatusSchema = _enum(["pending", "ready"]);
2603
- const isoDateTime = string().datetime();
2605
+ const isoDateTime$1 = string().datetime();
2604
2606
  const ProjectArtifactSchema = object({
2605
2607
  id: string(),
2606
2608
  projectId: string(),
2607
2609
  storageKey: string(),
2608
2610
  status: ProjectArtifactStatusSchema,
2609
- createdAt: isoDateTime,
2610
- updatedAt: isoDateTime
2611
+ createdAt: isoDateTime$1,
2612
+ updatedAt: isoDateTime$1
2611
2613
  });
2612
2614
  const CreateProjectArtifactResponseSchema = object({
2613
2615
  artifact: ProjectArtifactSchema,
@@ -2618,6 +2620,76 @@ const CompleteProjectArtifactResponseSchema = object({
2618
2620
  artifact: ProjectArtifactSchema,
2619
2621
  project: ProjectSchema
2620
2622
  });
2623
+ const isoDateTime = string().datetime();
2624
+ const sha256Hex = string().regex(/^[a-f0-9]{64}$/, "Expected a lowercase hex sha256 digest");
2625
+ /**
2626
+ * A single entry in a deploy's file tree, as served to the dashboard. Carries
2627
+ * no contents: `id` is a stable, path-derived handle used for selection and
2628
+ * `?file=` deep links; `hash` is the content address used to fetch the bytes
2629
+ * lazily and to cache them immutably in the browser.
2630
+ */
2631
+ const ProjectFileSummarySchema = object({
2632
+ id: string(),
2633
+ path: string().min(1),
2634
+ hash: sha256Hex
2635
+ });
2636
+ /** Maps a runtime resource key (agent/workflow/skill) to its source file path. */
2637
+ const ProjectSourceResourceRefSchema = object({
2638
+ key: string().min(1),
2639
+ path: string().min(1)
2640
+ });
2641
+ const ProjectSourceResourcesSchema = object({
2642
+ agents: array(ProjectSourceResourceRefSchema).default([]),
2643
+ workflows: array(ProjectSourceResourceRefSchema).default([]),
2644
+ skills: array(ProjectSourceResourceRefSchema).default([])
2645
+ });
2646
+ const emptyResources = () => ({
2647
+ agents: [],
2648
+ workflows: [],
2649
+ skills: []
2650
+ });
2651
+ /** Response for the file-tree listing of a project's active deploy. */
2652
+ const ListProjectFilesResponseSchema = object({
2653
+ artifactId: string().nullable(),
2654
+ files: array(ProjectFileSummarySchema),
2655
+ resources: ProjectSourceResourcesSchema.default(emptyResources())
2656
+ });
2657
+ object({
2658
+ path: string().min(1),
2659
+ contents: string(),
2660
+ hash: sha256Hex
2661
+ });
2662
+ /** Request: ask the platform which blobs are missing and get presigned PUTs. */
2663
+ const PresignProjectSourceRequestSchema = object({ blobs: array(object({
2664
+ hash: sha256Hex,
2665
+ size: number$1().int().nonnegative()
2666
+ })) });
2667
+ /** Response: presigned PUTs for the subset of blobs not already stored. */
2668
+ const PresignProjectSourceResponseSchema = object({ uploads: array(object({
2669
+ hash: sha256Hex,
2670
+ url: string().url()
2671
+ })) });
2672
+ /** Request body the CLI sends to finalize a deploy's source manifest. */
2673
+ const UploadProjectSourceManifestRequestSchema = object({
2674
+ files: array(object({
2675
+ path: string().min(1),
2676
+ hash: sha256Hex
2677
+ })),
2678
+ resources: ProjectSourceResourcesSchema.default(emptyResources())
2679
+ });
2680
+ const UploadProjectSourceResponseSchema = object({
2681
+ ok: literal(true),
2682
+ fileCount: number$1().int().nonnegative()
2683
+ });
2684
+ object({
2685
+ files: array(object({
2686
+ id: string(),
2687
+ path: string().min(1),
2688
+ hash: sha256Hex
2689
+ })),
2690
+ resources: ProjectSourceResourcesSchema.default(emptyResources()),
2691
+ generatedAt: isoDateTime
2692
+ });
2621
2693
  //#endregion
2622
2694
  //#region ../../packages/sdk/dist/index.mjs
2623
2695
  function normalizeBaseUrl$1(baseUrl) {
@@ -3122,6 +3194,69 @@ function createArtifactsResource(http) {
3122
3194
  } catch (error) {
3123
3195
  throw await toPlatformError(error);
3124
3196
  }
3197
+ },
3198
+ /** Ask which source blobs are missing and get presigned PUTs for them. */
3199
+ async presignSource(projectId, artifactId, blobs) {
3200
+ const body = PresignProjectSourceRequestSchema.parse({ blobs });
3201
+ try {
3202
+ const data = await http.post(`/api/projects/${encodeURIComponent(projectId)}/artifacts/${encodeURIComponent(artifactId)}/source/presign`, { json: body }).json();
3203
+ return PresignProjectSourceResponseSchema.parse(data);
3204
+ } catch (error) {
3205
+ throw await toPlatformError(error);
3206
+ }
3207
+ },
3208
+ /** Finalize the deploy's source manifest (path -> hash tree + resource refs). */
3209
+ async uploadManifest(projectId, artifactId, manifest) {
3210
+ const body = UploadProjectSourceManifestRequestSchema.parse(manifest);
3211
+ try {
3212
+ const data = await http.post(`/api/projects/${encodeURIComponent(projectId)}/artifacts/${encodeURIComponent(artifactId)}/source/manifest`, { json: body }).json();
3213
+ return UploadProjectSourceResponseSchema.parse(data);
3214
+ } catch (error) {
3215
+ throw await toPlatformError(error);
3216
+ }
3217
+ }
3218
+ };
3219
+ }
3220
+ function createGatewayAttachmentsResource(http) {
3221
+ const projectPrefix = (projectId) => `/api/projects/${encodeURIComponent(projectId)}/gateways/slack/attachments`;
3222
+ return {
3223
+ async listProjectAgents(projectId) {
3224
+ try {
3225
+ const data = await http.get(`/api/projects/${encodeURIComponent(projectId)}/agents`).json();
3226
+ return AgentListResponseSchema.parse(data).agents;
3227
+ } catch (error) {
3228
+ throw await toPlatformError(error);
3229
+ }
3230
+ },
3231
+ async upsertSlackAttachment(projectId, channelId, body) {
3232
+ const payload = UpsertGatewayAttachmentBodySchema.parse(body);
3233
+ try {
3234
+ const data = await http.put(`${projectPrefix(projectId)}/${encodeURIComponent(channelId)}`, { json: payload }).json();
3235
+ return GatewayAttachmentRecordSchema.parse(data);
3236
+ } catch (error) {
3237
+ throw await toPlatformError(error);
3238
+ }
3239
+ }
3240
+ };
3241
+ }
3242
+ function createProjectFilesResource(http) {
3243
+ return {
3244
+ /** File tree (no contents) for a project's active deploy; empty before first deploy. */
3245
+ async listForProject(projectId) {
3246
+ try {
3247
+ const data = await http.get(`/api/projects/${encodeURIComponent(projectId)}/files`).json();
3248
+ return ListProjectFilesResponseSchema.parse(data);
3249
+ } catch (error) {
3250
+ throw await toPlatformError(error);
3251
+ }
3252
+ },
3253
+ /** Lazily fetch a single file's contents by path for a specific deploy. */
3254
+ async getContent(projectId, artifactId, path) {
3255
+ try {
3256
+ return await http.get(`/api/projects/${encodeURIComponent(projectId)}/artifacts/${encodeURIComponent(artifactId)}/files/content`, { searchParams: { path } }).text();
3257
+ } catch (error) {
3258
+ throw await toPlatformError(error);
3259
+ }
3125
3260
  }
3126
3261
  };
3127
3262
  }
@@ -4840,141 +4975,6 @@ const deploymentSeed = [{
4840
4975
  createdAt: "2026-04-18T15:44:00Z",
4841
4976
  active: false
4842
4977
  }];
4843
- const projectFileSeed = [
4844
- {
4845
- id: "file-agents-support-agent",
4846
- projectId: "project_personal_repo",
4847
- path: "src/agents/support-agent.ts",
4848
- language: "typescript",
4849
- updatedAt: "2026-04-20T15:44:00Z",
4850
- contents: `import { defineAgent } from "@keystrokehq/agent";
4851
-
4852
- export const supportAgent = defineAgent({
4853
- key: "support-agent",
4854
- name: "Support Agent",
4855
- model: "claude-4-sonnet",
4856
- instructions: "Triage support requests and draft helpful replies.",
4857
- });
4858
- `
4859
- },
4860
- {
4861
- id: "file-workflows-daily-digest",
4862
- projectId: "project_personal_repo",
4863
- path: "src/workflows/daily-digest.ts",
4864
- language: "typescript",
4865
- updatedAt: "2026-04-20T15:12:00Z",
4866
- contents: `import { defineWorkflow } from "@keystrokehq/workflow";
4867
-
4868
- export const dailyDigest = defineWorkflow({
4869
- key: "daily-digest",
4870
- name: "Daily Digest",
4871
- trigger: "schedule.daily",
4872
- async run(ctx) {
4873
- await ctx.step("collect-updates");
4874
- await ctx.step("send-summary");
4875
- },
4876
- });
4877
- `
4878
- },
4879
- {
4880
- id: "file-skills-attio-sync",
4881
- projectId: "project_personal_repo",
4882
- path: ".agents/skills/attio-sync/SKILL.md",
4883
- language: "markdown",
4884
- updatedAt: "2026-04-19T18:05:00Z",
4885
- contents: `# Attio Sync
4886
-
4887
- Use this skill when a workflow needs to enrich company or person records from Attio.
4888
-
4889
- ## Inputs
4890
-
4891
- - company domain
4892
- - contact email
4893
- - enrichment depth
4894
-
4895
- ## Output
4896
-
4897
- Normalized CRM metadata ready for downstream workflow steps.
4898
- `
4899
- },
4900
- {
4901
- id: "file-skills-doc-coauthor",
4902
- projectId: "project_personal_repo",
4903
- path: ".agents/skills/doc-coauthor/SKILL.md",
4904
- language: "markdown",
4905
- updatedAt: "2026-05-30T10:24:00Z",
4906
- contents: `# Doc Co-author
4907
-
4908
- Use this skill when a workflow needs to collaboratively write, edit, and track
4909
- versions of a document.
4910
-
4911
- ## Inputs
4912
-
4913
- - document title
4914
- - source outline or draft
4915
- - target audience
4916
-
4917
- ## Output
4918
-
4919
- A revised document with tracked changes and a short summary of edits.
4920
- `
4921
- },
4922
- {
4923
- id: "file-skills-doc-coauthor-reference",
4924
- projectId: "project_personal_repo",
4925
- path: ".agents/skills/doc-coauthor/reference.md",
4926
- language: "markdown",
4927
- updatedAt: "2026-05-30T10:24:00Z",
4928
- contents: `# Reference
4929
-
4930
- Style and structure guidance the co-author follows when editing docs.
4931
-
4932
- ## Voice
4933
-
4934
- - Prefer active voice and short sentences.
4935
- - Keep headings consistent with the existing document.
4936
-
4937
- ## Versioning
4938
-
4939
- - Summarize each revision pass.
4940
- - Never drop the reader's original intent.
4941
- `
4942
- },
4943
- {
4944
- id: "file-skills-doc-coauthor-template",
4945
- projectId: "project_personal_repo",
4946
- path: ".agents/skills/doc-coauthor/templates/outline.md",
4947
- language: "markdown",
4948
- updatedAt: "2026-05-30T10:24:00Z",
4949
- contents: `# {{title}}
4950
-
4951
- ## Summary
4952
-
4953
- ## Background
4954
-
4955
- ## Details
4956
-
4957
- ## Next steps
4958
- `
4959
- },
4960
- {
4961
- id: "file-actions-send-slack",
4962
- projectId: "project_personal_repo",
4963
- path: "src/actions/send-slack-message.ts",
4964
- language: "typescript",
4965
- updatedAt: "2026-04-18T12:30:00Z",
4966
- contents: `import { defineAction } from "@keystrokehq/action";
4967
-
4968
- export const sendSlackMessage = defineAction({
4969
- key: "send-slack-message",
4970
- name: "Send Slack message",
4971
- async run({ channel, text }) {
4972
- return { channel, text, sent: true };
4973
- },
4974
- });
4975
- `
4976
- }
4977
- ];
4978
4978
  /** Review personas for mocked project members and API key authors (not org membership). */
4979
4979
  const mockPersonaSeed = [
4980
4980
  {
@@ -5293,16 +5293,6 @@ function createProjectMetricsResource() {
5293
5293
  plannedEndpoint: "GET /api/projects/metrics"
5294
5294
  }, async () => ({})) };
5295
5295
  }
5296
- function createProjectFilesResource() {
5297
- return { listForProject: mock({
5298
- domain: "projectFiles",
5299
- method: "listForProject",
5300
- plannedEndpoint: "GET /api/projects/:id/files"
5301
- }, async (projectId) => projectFileSeed.map((file) => ({
5302
- ...file,
5303
- projectId
5304
- }))) };
5305
- }
5306
5296
  const projectMembersStore = /* @__PURE__ */ new Map();
5307
5297
  function getProjectMembers(projectId) {
5308
5298
  let members = projectMembersStore.get(projectId);
@@ -6916,8 +6906,9 @@ function createPlatformClient(options) {
6916
6906
  credentials: createCredentialsResource(),
6917
6907
  history: createHistoryResource(),
6918
6908
  deployments: createDeploymentsResource(),
6909
+ gatewayAttachments: createGatewayAttachmentsResource(http),
6919
6910
  projectMetrics: createProjectMetricsResource(),
6920
- projectFiles: createProjectFilesResource(),
6911
+ projectFiles: createProjectFilesResource(http),
6921
6912
  channels: createChannelsResource(),
6922
6913
  members: createMembersResource(http),
6923
6914
  invitations: createInvitationsResource(http),
@@ -9254,7 +9245,7 @@ function registerBuildCommand(program) {
9254
9245
  program.command("build").description("Build the keystroke project for production").option("--dir <path>", "Project directory", process.cwd()).action(async (options) => {
9255
9246
  try {
9256
9247
  const root = resolveProjectRoot(options.dir);
9257
- const { buildApp } = await import("./dist-D1ip549D.mjs");
9248
+ const { buildApp } = await import("./dist-IKyaKHgU.mjs");
9258
9249
  await buildApp({ root });
9259
9250
  process.stdout.write(`Built ${root}\n`);
9260
9251
  } catch (error) {
@@ -9297,9 +9288,140 @@ function packProjectArtifact(projectRoot) {
9297
9288
  }
9298
9289
  }
9299
9290
  //#endregion
9291
+ //#region src/project/collect-source.ts
9292
+ const IGNORED_DIRS = new Set([
9293
+ "node_modules",
9294
+ "dist",
9295
+ ".git",
9296
+ ".turbo",
9297
+ "build",
9298
+ "coverage",
9299
+ ".keystroke",
9300
+ ".cache",
9301
+ "tmp"
9302
+ ]);
9303
+ const MAX_FILE_BYTES = 256 * 1024;
9304
+ const MAX_TOTAL_BYTES = 8 * 1024 * 1024;
9305
+ const MAX_FILES = 2e3;
9306
+ function toPosix(path) {
9307
+ return path.split(sep).join("/");
9308
+ }
9309
+ function isProbablyBinary(buffer) {
9310
+ const sampleLength = Math.min(buffer.length, 8e3);
9311
+ for (let index = 0; index < sampleLength; index += 1) if (buffer[index] === 0) return true;
9312
+ return false;
9313
+ }
9314
+ function walkFiles(root, dir, out, totals) {
9315
+ for (const name of readdirSync(dir).sort()) {
9316
+ if (out.length >= MAX_FILES || totals.bytes >= MAX_TOTAL_BYTES) return;
9317
+ const absolute = join(dir, name);
9318
+ const stats = statSync(absolute);
9319
+ if (stats.isDirectory()) {
9320
+ if (IGNORED_DIRS.has(name)) continue;
9321
+ walkFiles(root, absolute, out, totals);
9322
+ continue;
9323
+ }
9324
+ if (!stats.isFile() || stats.size > MAX_FILE_BYTES) continue;
9325
+ const buffer = readFileSync(absolute);
9326
+ if (isProbablyBinary(buffer)) continue;
9327
+ totals.bytes += buffer.byteLength;
9328
+ out.push({
9329
+ path: toPosix(relative(root, absolute)),
9330
+ contents: buffer.toString("utf8"),
9331
+ hash: createHash("sha256").update(buffer).digest("hex")
9332
+ });
9333
+ }
9334
+ }
9335
+ function posixPrefix(root, srcRoot, subdir) {
9336
+ return `${toPosix(relative(root, join(srcRoot, subdir)))}/`;
9337
+ }
9338
+ function collectModuleRefs(srcRoot, root, files, subdir, nestedEntry) {
9339
+ const dir = join(srcRoot, subdir);
9340
+ const prefix = posixPrefix(root, srcRoot, subdir);
9341
+ const refs = [];
9342
+ for (const file of files) {
9343
+ if (!file.path.startsWith(prefix) || !/\.(ts|mts)$/.test(file.path)) continue;
9344
+ const key = entryIdFromFile(dir, join(root, file.path), { nestedEntry });
9345
+ if (key) refs.push({
9346
+ key,
9347
+ path: file.path
9348
+ });
9349
+ }
9350
+ return refs;
9351
+ }
9352
+ function collectSkillRefs(srcRoot, root, files) {
9353
+ const dir = join(srcRoot, "skills");
9354
+ const prefix = posixPrefix(root, srcRoot, "skills");
9355
+ const refs = [];
9356
+ for (const file of files) {
9357
+ if (!file.path.startsWith(prefix) || !file.path.endsWith("SKILL.md")) continue;
9358
+ const key = toPosix(relative(dir, join(root, file.path)).replace(/\/?SKILL\.md$/, ""));
9359
+ if (key) refs.push({
9360
+ key,
9361
+ path: file.path
9362
+ });
9363
+ }
9364
+ return refs;
9365
+ }
9366
+ /**
9367
+ * Collect the project's actual source code into a deploy snapshot: every text
9368
+ * file under the project root (minus build output / deps) plus a map of each
9369
+ * agent / workflow / skill key to its source path. The map mirrors the same
9370
+ * path-derived ids the build step uses.
9371
+ */
9372
+ function collectProjectSource(root) {
9373
+ const files = [];
9374
+ walkFiles(root, root, files, { bytes: 0 });
9375
+ const srcRoot = join(root, "src");
9376
+ return {
9377
+ files,
9378
+ resources: {
9379
+ agents: collectModuleRefs(srcRoot, root, files, "agents", "agent"),
9380
+ workflows: collectModuleRefs(srcRoot, root, files, "workflows", "workflow"),
9381
+ skills: collectSkillRefs(srcRoot, root, files)
9382
+ }
9383
+ };
9384
+ }
9385
+ //#endregion
9300
9386
  //#region src/commands/deploy.ts
9301
9387
  const POLL_INTERVAL_MS = 2e3;
9302
9388
  const DEPLOY_TIMEOUT_MS = 12e4;
9389
+ const SOURCE_BLOB_CONTENT_TYPE = "text/plain; charset=utf-8";
9390
+ /**
9391
+ * Upload the project's source so the dashboard can render a read-only file
9392
+ * view. Content-addressed: only blobs the platform reports missing are PUT, so
9393
+ * unchanged files are never re-uploaded. The manifest (path -> hash) is sent
9394
+ * last and marks the snapshot complete.
9395
+ */
9396
+ async function uploadProjectSourceSnapshot(client, projectId, artifactId, source) {
9397
+ const contentsByHash = /* @__PURE__ */ new Map();
9398
+ const blobsByHash = /* @__PURE__ */ new Map();
9399
+ for (const file of source.files) {
9400
+ contentsByHash.set(file.hash, file.contents);
9401
+ if (!blobsByHash.has(file.hash)) blobsByHash.set(file.hash, {
9402
+ hash: file.hash,
9403
+ size: Buffer.byteLength(file.contents)
9404
+ });
9405
+ }
9406
+ const { uploads } = await client.artifacts.presignSource(projectId, artifactId, [...blobsByHash.values()]);
9407
+ await Promise.all(uploads.map(async (upload) => {
9408
+ const contents = contentsByHash.get(upload.hash);
9409
+ if (contents === void 0) return;
9410
+ const response = await fetch(upload.url, {
9411
+ method: "PUT",
9412
+ body: contents,
9413
+ headers: { "Content-Type": SOURCE_BLOB_CONTENT_TYPE }
9414
+ });
9415
+ if (!response.ok) throw new Error(`Source blob upload failed (${response.status})`);
9416
+ }));
9417
+ await client.artifacts.uploadManifest(projectId, artifactId, {
9418
+ files: source.files.map((file) => ({
9419
+ path: file.path,
9420
+ hash: file.hash
9421
+ })),
9422
+ resources: source.resources
9423
+ });
9424
+ }
9303
9425
  async function sleep(ms) {
9304
9426
  await new Promise((resolve) => {
9305
9427
  setTimeout(resolve, ms);
@@ -9310,7 +9432,7 @@ async function runDeploy(options) {
9310
9432
  const config = createCliConfig();
9311
9433
  const client = createCliPlatformClient(config);
9312
9434
  if (!options.skipBuild) {
9313
- const { buildApp } = await import("./dist-D1ip549D.mjs");
9435
+ const { buildApp } = await import("./dist-IKyaKHgU.mjs");
9314
9436
  await buildApp({ root });
9315
9437
  }
9316
9438
  const archive = packProjectArtifact(root);
@@ -9318,12 +9440,19 @@ async function runDeploy(options) {
9318
9440
  if (!active) active = await selectActiveOrganization(config);
9319
9441
  client.setActiveOrganizationId(active.organization.id);
9320
9442
  const project = await client.projects.get(options.projectId);
9443
+ const source = collectProjectSource(root);
9321
9444
  const created = await client.artifacts.create(project.id);
9322
- const upload = await fetch(created.uploadUrl, {
9445
+ const uploadPromise = fetch(created.uploadUrl, {
9323
9446
  method: "PUT",
9324
9447
  body: archive,
9325
9448
  headers: { "Content-Type": "application/gzip" }
9326
9449
  });
9450
+ const sourcePromise = uploadProjectSourceSnapshot(client, project.id, created.artifact.id, source).catch((error) => {
9451
+ const message = error instanceof Error ? error.message : "unknown error";
9452
+ process.stderr.write(`Warning: failed to upload project source files: ${message}\n`);
9453
+ });
9454
+ const upload = await uploadPromise;
9455
+ await sourcePromise;
9327
9456
  if (!upload.ok) throw new Error(`Artifact upload failed (${upload.status})`);
9328
9457
  const completed = await client.artifacts.complete(project.id, created.artifact.id);
9329
9458
  process.stdout.write(`Deploy ${completed.artifact.id} uploaded\n`);
@@ -9401,7 +9530,7 @@ function runtimeChildEnv(parentEnv, overrides) {
9401
9530
  //#region src/project/bootstrap-run.ts
9402
9531
  /** Node args + env for `@keystrokehq/build` bootstrap (shared by start + dev). */
9403
9532
  async function resolveBootstrapRun(options) {
9404
- const { resolveRuntimeBuildArtifact } = await import("./dist-D1ip549D.mjs");
9533
+ const { resolveRuntimeBuildArtifact } = await import("./dist-IKyaKHgU.mjs");
9405
9534
  const loader = pathToFileURL(resolveRuntimeBuildArtifact(options.runtimeNodeModules, "dist/runtime-loader.mjs")).href;
9406
9535
  const bootstrap = resolveRuntimeBuildArtifact(options.runtimeNodeModules, "dist/bootstrap.mjs");
9407
9536
  const args = [`--import=${loader}`];
@@ -9551,7 +9680,7 @@ async function runDev(options) {
9551
9680
  process.on("SIGINT", shutdown);
9552
9681
  process.on("SIGTERM", shutdown);
9553
9682
  try {
9554
- const { watchApp } = await import("./dist-D1ip549D.mjs");
9683
+ const { watchApp } = await import("./dist-IKyaKHgU.mjs");
9555
9684
  await watchApp({
9556
9685
  root,
9557
9686
  clean: false,
@@ -9946,7 +10075,7 @@ async function runStart(options) {
9946
10075
  const apiPort = Number(new URL(serverUrl).port || 80);
9947
10076
  const runtimeNodeModules = resolveCliRuntimeNodeModules(resolveCliRoot(import.meta.url));
9948
10077
  ensureNativeDeps(runtimeNodeModules);
9949
- const { buildApp } = await import("./dist-D1ip549D.mjs");
10078
+ const { buildApp } = await import("./dist-IKyaKHgU.mjs");
9950
10079
  await buildApp({
9951
10080
  root,
9952
10081
  clean: false