@keystrokehq/cli 0.1.25 → 0.1.27

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,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { $ as GetCustomAppResponseSchema, $n as slugifyAppName, $t as SlugAvailabilityResponseSchema, A as CreateCustomAppRequestSchema, An as WorkflowRunHooksResponseSchema, At as PollRunResponseSchema, B as CredentialConsumerListQuerySchema, Bt as ProjectReachabilityResponseSchema, C as ConnectAuthorizeUrlResponseSchema, Cn as UploadProjectSourceResponseSchema, Ct as ListProjectsResponseSchema, D as CreateCredentialInstanceBodySchema, Dn as UserPreferencesPatchSchema, Dt as OrganizationSidebarBrandingSchema, E as CreateApiKeyResponseSchema, En as UserAvatarSchema, Et as OrganizationSidebarBrandingPatchSchema, F as CreateProjectRequestSchema, Fn as WorkspaceTriggerFileSchema, Ft as PresignProjectSourceRequestSchema, G as DOCS_QUERY_TOOL, Gt as PromptResponseSchema, H as CredentialInstanceListResponseSchema, Hn as detectProjectPackageManagerFromSnapshot, Ht as ProjectSettingsResponseSchema, I as CreateProjectResponseSchema, In as WorkspaceTriggerListResponseSchema, It as PresignProjectSourceResponseSchema, J as DownloadActiveProjectArtifactResponseSchema, Jn as parseErrorResponse, Jt as QueuedRunResponseSchema, K as DOCS_SEARCH_TOOL, L as CredentialAssignmentListQuerySchema, Ln as WorkspaceTriggerOverviewSchema, Lt as PresignUserAvatarRequestSchema, M as CreateOrganizationRequestSchema, Mn as WorkflowSummaryDetailResponseSchema, Mt as PresignChatAttachmentResponseSchema, N as CreateOrganizationResponseSchema, Nn as WorkflowSummaryListResponseSchema, Nt as PresignOrgLogoRequestSchema, O as CreateCredentialsRequestSchema, On as UserPreferencesSchema, Ot as PROJECT_PULL_STATE_RELATIVE_PATH, P as CreateProjectArtifactResponseSchema, Pn as WorkspaceTriggerDetailSchema, Pt as PresignOrgLogoResponseSchema, Q as GetCredentialResponseSchema, Qn as resolvePublicPlatformOrigin, Qt as SkillSummaryListResponseSchema, R as CredentialAssignmentListResponseSchema, Rn as WorkspaceTriggerRunListResponseSchema, Rt as PresignUserAvatarResponseSchema, S as CompleteProjectArtifactResponseSchema, Sn as UploadProjectSourceManifestRequestSchema, St as ListProjectMetricsResponseSchema, T as CreateApiKeyRequestSchema, Tn as UserAvatarPatchSchema, Tt as OpenApiDiscoverResponseSchema, U as CredentialInstanceRecordSchema, Un as isAcceptableInstallExit, Ut as ProjectSlugAvailabilityResponseSchema, V as CredentialConsumerListResponseSchema, Vn as deriveCustomAppDisplay, Vt as ProjectResponseSchema, Wn as listenPortFromPublicUrl, Wt as PromptInputSchema, X as ErrorResponseSchema, Xn as resolveConnectAppSlug, Xt as RecentResourceListResponseSchema, Y as DownloadActiveProjectSourceResponseSchema, Z as GatewayAttachmentRecordSchema, Zn as resolveDocsMcpUrl, Zt as SkillSummaryDetailResponseSchema, _ as ChannelAccountListResponseSchema, _n as UpdateOrganizationRequestSchema, _t as ListOrganizationMembersResponseSchema, a as AgentSessionDetailResponseSchema, an as StartOAuthConnectionResultSchema, at as HistoryRunListResponseSchema, b as ChannelDirectoryListResponseSchema, bn as UpdateProjectRequestSchema, bt as ListProjectFilesResponseSchema, c as AgentSummaryListResponseSchema, cn as TriggerDetailResponseSchema, ct as InviteProjectMembersRequestSchema, d as AssignCredentialBodySchema, dn as TriggerRunListResponseSchema, dt as ListAgentMemoryFilesResponseSchema, en as StartKeystrokeConnectionInputSchema, et as GraphqlDiscoverResponseSchema, f as BindChannelBodySchema, fn as UpdateChannelBindingBodySchema, ft as ListAgentWorkspaceFilesResponseSchema, g as CatalogAppsPageResponseSchema, gn as UpdateOrganizationMemberResponseSchema, gt as ListOrganizationInvitationsResponseSchema, h as CatalogAppDetailResponseSchema, hn as UpdateOrganizationMemberRequestSchema, ht as ListCredentialsResponseSchema, i as AgentSessionChatStateResponseSchema, in as StartOAuthConnectionInputSchema, it as HistoryRunListQuerySchema, j as CreateCustomAppResponseSchema, jn as WorkflowRunListResponseSchema, jt as PresignChatAttachmentRequestSchema, k as CreateCredentialsResponseSchema, kn as WorkflowRunDetailResponseSchema, kt as PROJECT_REACHABILITY_REQUEST_TIMEOUT_MS, l as AgentTriggerSummaryListResponseSchema, ln as TriggerListResponseSchema, lt as InviteProjectMembersResponseSchema, m as CatalogActionsPageResponseSchema, mn as UpdateCredentialRequestSchema, mt as ListAppsResponseSchema, n as AcceptOrganizationInvitationResponseSchema, nn as StartMcpOAuthConnectionInputSchema, nt as HistoryRunCancelResponseSchema, o as AgentSessionListResponseSchema, on as SubmitMarketingContactRequestSchema, ot as InviteOrganizationMembersRequestSchema, p as CatalogActionDetailResponseSchema, pn as UpdateCredentialInstanceBodySchema, pt as ListApiKeysResponseSchema, q as DeclineOrganizationInvitationResponseSchema, qn as parseAppSlug, qt as QueuedAgentPromptResponseSchema, r as ActiveOrganizationResponseSchema, rn as StartMcpOAuthConnectionResultSchema, rt as HistoryRunDetailResponseSchema, s as AgentSummaryDetailResponseSchema, sn as SubmitTeamRequestRequestSchema, st as InviteOrganizationMembersResponseSchema, t as ACTIVE_ORG_HEADER, tn as StartKeystrokeConnectionResultSchema, tt as HealthResponseSchema, u as AppSlugAvailabilityResponseSchema, un as TriggerRunDetailResponseSchema, v as ChannelConnectionListResponseSchema, vn as UpdateProjectMemberRequestSchema, vt as ListOrganizationsResponseSchema, w as ConnectProvidersResponseSchema, wn as UpsertGatewayAttachmentBodySchema, wt as McpDiscoverResponseSchema, x as ChannelPlatformSchema, xn as UpdateProjectSettingsRequestSchema, xt as ListProjectMembersResponseSchema, y as ChannelConnectionSchema, yn as UpdateProjectMemberResponseSchema, yt as ListProjectDeploymentsResponseSchema, z as CredentialAssignmentRecordSchema, zn as buildConnectDeeplink, zt as ProjectPullStateSchema } from "./dist-BOhrc_Nv.mjs";
3
3
  import { i as packProjectArtifact, n as withMcpReadClient, r as mergeFilteredArtifact, t as mapInParallelBatches } from "./dist-Re6HHSqz.mjs";
4
- import { a as installPlaygroundDependencies, c as createCliConfig, d as getEffectiveApiTarget, f as getPlatformUrl, g as resolvePlatformUrlForWebUrl, i as installDependencies$1, l as getCliConfigDir, m as DEFAULT_PLATFORM_URL, n as buildPlaygroundWorkspace, o as resolvePackageManager, p as getWebUrl, s as resolveCliRoot, t as readCliVersion, u as getConfigDir } from "./version-DcR3O1UD.mjs";
4
+ import { a as installPlaygroundDependencies, c as createCliConfig, d as getEffectiveApiTarget, f as getPlatformUrl, g as resolvePlatformUrlForWebUrl, i as installDependencies$1, l as getCliConfigDir, m as DEFAULT_PLATFORM_URL, n as buildPlaygroundWorkspace, o as resolvePackageManager, p as getWebUrl, s as resolveCliRoot, t as readCliVersion, u as getConfigDir } from "./version-pY9N8XlL.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import { Command } from "commander";
7
7
  import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
@@ -4684,7 +4684,7 @@ async function resolveProjectRef(platform, ref) {
4684
4684
  if (looksLikeUuid(ref)) try {
4685
4685
  return await platform.projects.get(ref);
4686
4686
  } catch (error) {
4687
- if (error instanceof PlatformError && error.status === 404) throw new Error(`Project ${ref} not found in the active organization. Run \`keystroke project list\`, then \`keystroke config use project <slug>\`.`);
4687
+ if (error instanceof PlatformError && error.status === 404) throw new Error(`Project ${ref} not found in the active organization. Run \`keystroke project list\`, then \`keystroke project link --project <slug>\`.`);
4688
4688
  throw error;
4689
4689
  }
4690
4690
  const match = (await platform.projects.list()).find((project) => project.slug === ref);
@@ -4846,6 +4846,78 @@ function readDevSession(configDir = getCliConfigDir()) {
4846
4846
  }
4847
4847
  }
4848
4848
  //#endregion
4849
+ //#region src/project/resolve-keystroke-config-root.ts
4850
+ const KEYSTROKE_CONFIG$2 = "keystroke.config.ts";
4851
+ /** Walk up from `fromDir` and return the directory containing `keystroke.config.ts`, if any. */
4852
+ function resolveKeystrokeConfigRoot(fromDir = process.cwd()) {
4853
+ let dir = resolve(fromDir);
4854
+ while (dir !== dirname(dir)) {
4855
+ if (existsSync(join(dir, KEYSTROKE_CONFIG$2))) return dir;
4856
+ dir = dirname(dir);
4857
+ }
4858
+ if (existsSync(join(dir, KEYSTROKE_CONFIG$2))) return dir;
4859
+ return null;
4860
+ }
4861
+ //#endregion
4862
+ //#region src/project/read-project-config.ts
4863
+ const KEYSTROKE_CONFIG$1 = "keystroke.config.ts";
4864
+ /** Remove line and block comments before scanning config literals. */
4865
+ function stripConfigComments(source) {
4866
+ return source.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
4867
+ }
4868
+ /** Mask comments while preserving string length so match indices stay aligned. */
4869
+ function stripConfigCommentsPreservingLength(source) {
4870
+ return source.replace(/\/\/[^\n]*/g, (match) => " ".repeat(match.length)).replace(/\/\*[\s\S]*?\*\//g, (match) => " ".repeat(match.length));
4871
+ }
4872
+ function readStringLiteral(source, key) {
4873
+ const stripped = stripConfigComments(source);
4874
+ const pattern = new RegExp(`\\b${key}\\s*:\\s*(['"])([^'"]*)\\1`);
4875
+ return stripped.match(pattern)?.[2]?.trim() || void 0;
4876
+ }
4877
+ function parseLinkedProjectConfig(source) {
4878
+ const project = readStringLiteral(source, "project");
4879
+ const organization = readStringLiteral(source, "organization");
4880
+ return {
4881
+ ...project ? { project } : {},
4882
+ ...organization ? { organization } : {}
4883
+ };
4884
+ }
4885
+ /** Read optional project/organization slugs from keystroke.config.ts without executing it. */
4886
+ function readLinkedProjectConfig(fromDir = process.cwd()) {
4887
+ const root = resolveKeystrokeConfigRoot(fromDir);
4888
+ if (!root) return {};
4889
+ return parseLinkedProjectConfig(readFileSync(join(root, KEYSTROKE_CONFIG$1), "utf8"));
4890
+ }
4891
+ //#endregion
4892
+ //#region src/target-options.ts
4893
+ let targetOptions = {};
4894
+ function setCliTargetOptions(options) {
4895
+ targetOptions = options;
4896
+ }
4897
+ function getCliTargetOptions() {
4898
+ return targetOptions;
4899
+ }
4900
+ //#endregion
4901
+ //#region src/resolve-project-target.ts
4902
+ function resolveLinkedProjectRef(fromDir = process.cwd()) {
4903
+ return readLinkedProjectConfig(fromDir).project;
4904
+ }
4905
+ /** Activate the organization slug from keystroke.config.ts when present. */
4906
+ async function ensureLinkedOrganization(config, fromDir = process.cwd()) {
4907
+ const linked = readLinkedProjectConfig(fromDir);
4908
+ if (!linked.organization) return;
4909
+ await activateOrganization(config, resolveOrganizationRef(await listOrganizations(config), linked.organization).organization.id);
4910
+ }
4911
+ async function resolveActiveProject(config, platform, fromDir = process.cwd()) {
4912
+ const ref = getCliTargetOptions().projectId ?? readLinkedProjectConfig(fromDir).project;
4913
+ if (!ref) return;
4914
+ await ensureLinkedOrganization(config, fromDir);
4915
+ return resolveProjectRef(platform ?? createCliPlatformClient(config), ref);
4916
+ }
4917
+ async function resolveActiveProjectId(config, fromDir = process.cwd()) {
4918
+ return (await resolveActiveProject(config, void 0, fromDir))?.id;
4919
+ }
4920
+ //#endregion
4849
4921
  //#region src/resolve-api-target.ts
4850
4922
  /** Standalone local server default project id — keep in sync with @keystrokehq/database. */
4851
4923
  const DEFAULT_LOCAL_PROJECT_ID = "default";
@@ -4878,6 +4950,7 @@ function resolveOrgPlatformTarget(config) {
4878
4950
  };
4879
4951
  }
4880
4952
  async function resolvePlatformTarget(config, projectRef) {
4953
+ await ensureLinkedOrganization(config);
4881
4954
  const platform = createCliPlatformClient(config);
4882
4955
  let project;
4883
4956
  try {
@@ -4905,20 +4978,11 @@ async function resolveApiTarget(config, options = {}) {
4905
4978
  if (options.projectId) return resolvePlatformTarget(config, options.projectId);
4906
4979
  if (getEffectiveApiTarget(config) === "local") return resolveLocalTarget(options);
4907
4980
  if (!options.projectScoped) return resolveOrgPlatformTarget(config);
4908
- const projectRef = options.projectId ?? config.get("activeProjectId");
4909
- if (!projectRef) throw new Error("No project selected. Pass `--project <slug>` or run `keystroke config use project <slug>`.");
4981
+ const projectRef = options.projectId ?? readLinkedProjectConfig().project;
4982
+ if (!projectRef) throw new Error("No project selected. Pass `--project <slug>` or run `keystroke project link --project <slug>`.");
4910
4983
  return resolvePlatformTarget(config, projectRef);
4911
4984
  }
4912
4985
  //#endregion
4913
- //#region src/target-options.ts
4914
- let targetOptions = {};
4915
- function setCliTargetOptions(options) {
4916
- targetOptions = options;
4917
- }
4918
- function getCliTargetOptions() {
4919
- return targetOptions;
4920
- }
4921
- //#endregion
4922
4986
  //#region src/errors/example-input.ts
4923
4987
  function exampleValueFromValidationMessage(message) {
4924
4988
  if (/expected number/i.test(message)) return 0;
@@ -5088,18 +5152,6 @@ async function requireAdminRole(config) {
5088
5152
  if (!active || active.role !== "owner" && active.role !== "admin") throw new Error("--admin requires org owner or admin");
5089
5153
  }
5090
5154
  //#endregion
5091
- //#region src/resolve-project-target.ts
5092
- async function resolveActiveProject(config, platform) {
5093
- const ref = getCliTargetOptions().projectId ?? config.get("activeProjectId");
5094
- if (!ref) return;
5095
- const client = platform ?? createCliPlatformClient(config);
5096
- if (!getCliTargetOptions().projectId && looksLikeUuid(ref)) return resolveProjectRef(client, ref);
5097
- return resolveProjectRef(client, ref);
5098
- }
5099
- async function resolveActiveProjectId(config) {
5100
- return (await resolveActiveProject(config))?.id;
5101
- }
5102
- //#endregion
5103
5155
  //#region src/commands/agent/list.ts
5104
5156
  function registerAgentListCommand(agent) {
5105
5157
  agent.command("list").description("List agents in the active organization").option("--admin", "List all org resources (org owner or admin only)").action((options) => runCliCommand("List agents failed", async () => {
@@ -5128,7 +5180,8 @@ function registerAgentPromptCommand(agent) {
5128
5180
  }
5129
5181
  //#endregion
5130
5182
  //#region src/commands/project/run-project.ts
5131
- async function withActivePlatformClient(config, fn) {
5183
+ async function withActivePlatformClient(config, fn, fromDir = process.cwd(), applyLinkedOrganization = true) {
5184
+ if (applyLinkedOrganization) await ensureLinkedOrganization(config, fromDir);
5132
5185
  const platform = createCliPlatformClient(config);
5133
5186
  let active = await platform.organizations.getActive();
5134
5187
  if (!active) active = await selectActiveOrganization(config);
@@ -5139,7 +5192,7 @@ function writeInactiveDeployHints(projects) {
5139
5192
  const bin = cliBinaryName();
5140
5193
  for (const project of projects) {
5141
5194
  if (project.status !== "inactive") continue;
5142
- process.stderr.write(`Project ${formatProjectLabel(project)} is inactive. Deploy with: ${bin} deploy --project ${formatProjectLabel(project)}\n`);
5195
+ process.stderr.write(`Project ${formatProjectLabel(project)} is inactive. Deploy with: ${bin} project link --project ${formatProjectLabel(project)}, then ${bin} deploy\n`);
5143
5196
  }
5144
5197
  }
5145
5198
  async function runProjectList(config, options = {}) {
@@ -5543,19 +5596,6 @@ function registerAppShowCommand(app) {
5543
5596
  }, void 0, { orgScoped: true }));
5544
5597
  }
5545
5598
  //#endregion
5546
- //#region src/project/resolve-keystroke-config-root.ts
5547
- const KEYSTROKE_CONFIG = "keystroke.config.ts";
5548
- /** Walk up from `fromDir` and return the directory containing `keystroke.config.ts`, if any. */
5549
- function resolveKeystrokeConfigRoot(fromDir = process.cwd()) {
5550
- let dir = resolve(fromDir);
5551
- while (dir !== dirname(dir)) {
5552
- if (existsSync(join(dir, KEYSTROKE_CONFIG))) return dir;
5553
- dir = dirname(dir);
5554
- }
5555
- if (existsSync(join(dir, KEYSTROKE_CONFIG))) return dir;
5556
- return null;
5557
- }
5558
- //#endregion
5559
5599
  //#region src/commands/app/generate-app-stub.ts
5560
5600
  function slugPartToExportName(part) {
5561
5601
  return part.split(/[-_]+/).filter(Boolean).map((segment, index) => index === 0 ? segment.charAt(0).toLowerCase() + segment.slice(1) : segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
@@ -6314,6 +6354,7 @@ async function runDeploy(options) {
6314
6354
  const root = resolveProjectRoot(options.dir);
6315
6355
  const config = createCliConfig();
6316
6356
  const client = createCliPlatformClient(config);
6357
+ await ensureLinkedOrganization(config, root);
6317
6358
  let active = await client.organizations.getActive();
6318
6359
  if (!active) active = await selectActiveOrganization(config);
6319
6360
  client.setActiveOrganizationId(active.organization.id);
@@ -6338,7 +6379,6 @@ async function runDeploy(options) {
6338
6379
  while (Date.now() < deadline) {
6339
6380
  const current = await client.projects.get(project.id);
6340
6381
  if (current.status === "active") {
6341
- config.set("activeProjectId", project.id);
6342
6382
  config.set("apiTarget", "platform");
6343
6383
  process.stdout.write(`Project active${current.baseUrl ? `: ${current.baseUrl}` : ""}\n`);
6344
6384
  return;
@@ -6351,8 +6391,8 @@ async function runDeploy(options) {
6351
6391
  function registerDeployCommand(program) {
6352
6392
  program.command("deploy").description("Build, upload, and deploy the project to the platform").option("--dir <path>", "Project directory", process.cwd()).option("--filter <entry>", "Build and deploy only matching module entry keys (repeatable)", (value, previous) => [...previous, value], []).action(async (options) => {
6353
6393
  try {
6354
- const projectId = await resolveActiveProjectId(createCliConfig());
6355
- if (!projectId) throw new Error("Usage: keystroke deploy --project <slug>");
6394
+ const projectId = await resolveActiveProjectId(createCliConfig(), resolveProjectRoot(options.dir));
6395
+ if (!projectId) throw new Error("Usage: keystroke deploy --project <slug> (or run `keystroke project link --project <slug>`)");
6356
6396
  await runDeploy({
6357
6397
  dir: options.dir,
6358
6398
  projectId,
@@ -6773,7 +6813,7 @@ const INIT_CATALOG_VERSIONS = {
6773
6813
  vitest: "^4.1.7",
6774
6814
  "@types/node": "^25.9.1"
6775
6815
  };
6776
- const INIT_KEYSTROKE_VERSION = "^0.1.22";
6816
+ const INIT_KEYSTROKE_VERSION = "^0.1.23";
6777
6817
  //#endregion
6778
6818
  //#region src/init/copy-template.ts
6779
6819
  function renderTemplate(content, variables) {
@@ -6916,6 +6956,39 @@ async function scaffoldAgentsGuide(projectRoot, fromModuleUrl = import.meta.url)
6916
6956
  await writeBundleVersion(projectRoot);
6917
6957
  }
6918
6958
  //#endregion
6959
+ //#region src/project/write-project-config.ts
6960
+ const KEYSTROKE_CONFIG = "keystroke.config.ts";
6961
+ function upsertStringLiteral(source, key, value) {
6962
+ const stripped = stripConfigCommentsPreservingLength(source);
6963
+ const entryPattern = new RegExp(`\\b${key}\\s*:\\s*(['"])[^'"]*\\1,?\\s*\\n?`);
6964
+ const entry = value === void 0 ? void 0 : ` ${key}: "${value}",\n`;
6965
+ const match = stripped.match(entryPattern);
6966
+ if (match?.index !== void 0) {
6967
+ const start = match.index;
6968
+ const end = start + match[0].length;
6969
+ if (value === void 0) return source.slice(0, start) + source.slice(end);
6970
+ return source.slice(0, start) + entry + source.slice(end);
6971
+ }
6972
+ if (value === void 0) return source;
6973
+ const defineConfigPattern = /defineConfig\(\{\s*\n?/;
6974
+ if (defineConfigPattern.test(source)) return source.replace(defineConfigPattern, `defineConfig({\n${entry}`);
6975
+ throw new Error(`Could not find defineConfig({ ... }) in ${KEYSTROKE_CONFIG}`);
6976
+ }
6977
+ function upsertLinkedProjectConfig(source, input) {
6978
+ let next = source;
6979
+ if (input.project !== void 0) next = upsertStringLiteral(next, "project", input.project);
6980
+ if (input.organization !== void 0) next = upsertStringLiteral(next, "organization", input.organization);
6981
+ return next;
6982
+ }
6983
+ /** Write project/organization slugs into keystroke.config.ts. */
6984
+ function writeLinkedProjectConfig(input, fromDir = process.cwd()) {
6985
+ const root = resolveKeystrokeConfigRoot(fromDir);
6986
+ if (!root) throw new Error(`No ${KEYSTROKE_CONFIG} found — run from a keystroke project directory`);
6987
+ const configPath = join(root, KEYSTROKE_CONFIG);
6988
+ writeFileSync(configPath, upsertLinkedProjectConfig(readFileSync(configPath, "utf8"), input));
6989
+ return configPath;
6990
+ }
6991
+ //#endregion
6919
6992
  //#region src/init/scaffold-empty-src-dirs.ts
6920
6993
  const EMPTY_SRC_DIRS = [
6921
6994
  "files",
@@ -7016,6 +7089,10 @@ async function runInit(options) {
7016
7089
  else await scaffoldProjectDotfiles(targetDir);
7017
7090
  await scaffoldAgentsGuide(targetDir);
7018
7091
  await copyFile(join(targetDir, ".env.example"), join(targetDir, ".env"));
7092
+ if (options.project || options.organization) writeLinkedProjectConfig({
7093
+ ...options.project ? { project: options.project } : {},
7094
+ ...options.organization ? { organization: options.organization } : {}
7095
+ }, targetDir);
7019
7096
  if (!options.skipInstall) if (playgroundRoot) {
7020
7097
  installPlaygroundDependencies(targetDir);
7021
7098
  buildPlaygroundWorkspace(playgroundRoot);
@@ -7028,7 +7105,7 @@ async function runInit(options) {
7028
7105
  //#endregion
7029
7106
  //#region src/commands/init.ts
7030
7107
  function registerInitCommand(program) {
7031
- program.command("init").description("Create a new keystroke project from a template").argument("[directory]", "Directory to create the project in").option("--name <name>", "Project name (kebab-case)").option("--template <name>", "Template to use", "hello-world").option("-y, --yes", "Skip prompts (headless mode for scripts and AI agents)").option("--skip-install", "Skip dependency installation").option("--pm <manager>", "Package manager: npm, pnpm, yarn, or bun").option("--no-example", "Scaffold without the example action, agent, and workflow").action(async (directory, options) => {
7108
+ program.command("init").description("Create a new keystroke project from a template").argument("[directory]", "Directory to create the project in").option("--name <name>", "Project name (kebab-case)").option("--template <name>", "Template to use", "hello-world").option("-y, --yes", "Skip prompts (headless mode for scripts and AI agents)").option("--skip-install", "Skip dependency installation").option("--pm <manager>", "Package manager: npm, pnpm, yarn, or bun").option("--no-example", "Scaffold without the example action, agent, and workflow").option("--project <slug>", "Platform project slug to write into keystroke.config.ts").option("--organization <slug>", "Platform organization slug to write into keystroke.config.ts").action(async (directory, options) => {
7032
7109
  try {
7033
7110
  const result = await runInit({
7034
7111
  directory,
@@ -7038,14 +7115,16 @@ function registerInitCommand(program) {
7038
7115
  skipInstall: options.skipInstall,
7039
7116
  packageManager: options.pm,
7040
7117
  noExample: options.example === false,
7118
+ project: options.project,
7119
+ organization: options.organization,
7041
7120
  version: readCliVersion()
7042
7121
  });
7043
7122
  process.stdout.write(`Created keystroke project "${result.projectName}" at ${result.targetDir}\n`);
7044
7123
  process.stdout.write("\nNext steps:\n");
7045
7124
  process.stdout.write(` cd ${result.targetDir}\n`);
7046
7125
  process.stdout.write(" keystroke auth login # once\n");
7047
- process.stdout.write(" keystroke project list # pick a project (or: project create)\n");
7048
- process.stdout.write(" keystroke deploy --project <id> # build + ship src/ to the platform\n");
7126
+ if (!options.project) process.stdout.write(" keystroke project link --project <slug> # link this directory to a platform project\n");
7127
+ process.stdout.write(" keystroke deploy # build + ship src/ to the platform\n");
7049
7128
  } catch (error) {
7050
7129
  const message = error instanceof Error ? error.message : "Init failed";
7051
7130
  process.stderr.write(`${message}\n`);
@@ -7054,15 +7133,6 @@ function registerInitCommand(program) {
7054
7133
  });
7055
7134
  }
7056
7135
  //#endregion
7057
- //#region src/active-project.ts
7058
- function clearActiveProjectIfMatches(config, project) {
7059
- const activeProjectId = config.get("activeProjectId");
7060
- if (activeProjectId === project.id || activeProjectId === project.slug) config.delete("activeProjectId");
7061
- }
7062
- function clearActiveProject(config) {
7063
- if (config.get("activeProjectId") !== void 0) config.delete("activeProjectId");
7064
- }
7065
- //#endregion
7066
7136
  //#region src/commands/organization/run-organization.ts
7067
7137
  async function runOrganizationUpdate(config, input) {
7068
7138
  await withActivePlatformClient(config, async (platform) => {
@@ -7104,7 +7174,6 @@ async function runOrganizationLeave(config) {
7104
7174
  await platform.members.removeOrganizationMember(user.id);
7105
7175
  platform.setActiveOrganizationId(null);
7106
7176
  config.delete("activeOrganizationId");
7107
- clearActiveProject(config);
7108
7177
  process.stdout.write(`${JSON.stringify({ left: user.id }, null, 2)}\n`);
7109
7178
  });
7110
7179
  }
@@ -7177,9 +7246,23 @@ function registerOrganizationCommand(program) {
7177
7246
  });
7178
7247
  }
7179
7248
  //#endregion
7249
+ //#region src/commands/project/run-project-link.ts
7250
+ async function runProjectLink(config, projectRef, fromDir = process.cwd()) {
7251
+ await withActivePlatformClient(config, async (platform) => {
7252
+ const project = await resolveProjectRef(platform, projectRef);
7253
+ const membership = (await listOrganizations(config)).find((entry) => entry.organization.id === project.organizationId);
7254
+ if (!membership) throw new Error(`Organization for project ${formatProjectLabel(project)} was not found or you do not have access`);
7255
+ const configPath = writeLinkedProjectConfig({
7256
+ project: project.slug,
7257
+ organization: membership.organization.slug
7258
+ }, fromDir);
7259
+ process.stdout.write(`Linked ${configPath} to ${membership.organization.slug}/${formatProjectLabel(project)}\nDeploy with: ${cliBinaryName()} deploy\n`);
7260
+ }, fromDir, false);
7261
+ }
7262
+ //#endregion
7180
7263
  //#region src/commands/project/run-project-members.ts
7181
7264
  async function resolveMembersProject(platform, config, projectRef) {
7182
- const ref = projectRef ?? getCliTargetOptions().projectId ?? config.get("activeProjectId");
7265
+ const ref = projectRef ?? getCliTargetOptions().projectId ?? readLinkedProjectConfig().project;
7183
7266
  if (!ref) throw new Error("Usage: keystroke --project <slug> project members <command>");
7184
7267
  return resolveProjectRef(platform, ref);
7185
7268
  }
@@ -7219,7 +7302,6 @@ async function runProjectMembersLeave(config, options) {
7219
7302
  await withActivePlatformClient(config, async (platform) => {
7220
7303
  const project = await resolveMembersProject(platform, config, options.projectRef);
7221
7304
  await platform.members.leaveProject(project.id);
7222
- clearActiveProjectIfMatches(config, project);
7223
7305
  process.stdout.write(`${JSON.stringify({ left: project.id }, null, 2)}\n`);
7224
7306
  });
7225
7307
  }
@@ -7233,7 +7315,7 @@ function parseProjectRole$1(role) {
7233
7315
  throw new Error("Role must be admin or builder");
7234
7316
  }
7235
7317
  function resolveProjectRefOption$1(config, optionsProject) {
7236
- return optionsProject ?? getCliTargetOptions().projectId ?? config.get("activeProjectId");
7318
+ return optionsProject ?? getCliTargetOptions().projectId ?? readLinkedProjectConfig().project;
7237
7319
  }
7238
7320
  function projectRefInput$1(config, optionsProject) {
7239
7321
  const projectRef = resolveProjectRefOption$1(config, optionsProject);
@@ -7299,7 +7381,7 @@ function registerProjectMembersCommand(project) {
7299
7381
  //#endregion
7300
7382
  //#region src/commands/project/run-project-settings.ts
7301
7383
  async function resolveSettingsProject(platform, config, projectRef) {
7302
- const ref = projectRef ?? getCliTargetOptions().projectId ?? config.get("activeProjectId");
7384
+ const ref = projectRef ?? getCliTargetOptions().projectId ?? readLinkedProjectConfig().project;
7303
7385
  if (!ref) throw new Error("Usage: keystroke --project <slug> project settings <command>");
7304
7386
  return resolveProjectRef(platform, ref);
7305
7387
  }
@@ -7321,7 +7403,7 @@ async function runProjectSettingsUpdate(config, input) {
7321
7403
  //#endregion
7322
7404
  //#region src/commands/project/settings.ts
7323
7405
  function resolveProjectRefOption(config, optionsProject) {
7324
- return optionsProject ?? getCliTargetOptions().projectId ?? config.get("activeProjectId");
7406
+ return optionsProject ?? getCliTargetOptions().projectId ?? readLinkedProjectConfig().project;
7325
7407
  }
7326
7408
  function projectRefInput(config, optionsProject) {
7327
7409
  const projectRef = resolveProjectRefOption(config, optionsProject);
@@ -7402,8 +7484,8 @@ function registerProjectCommand(program) {
7402
7484
  project.command("deployments").description("View deployment history for platform projects").command("list").description("List deployment history for a project").option("--project <slug>", "Project slug").action(async (options) => {
7403
7485
  const config = createCliConfig();
7404
7486
  try {
7405
- const projectRef = options.project ?? getCliTargetOptions().projectId ?? config.get("activeProjectId");
7406
- if (!projectRef) throw new Error("Usage: keystroke project deployments list --project <slug>");
7487
+ const projectRef = options.project ?? getCliTargetOptions().projectId ?? resolveLinkedProjectRef();
7488
+ if (!projectRef) throw new Error("Usage: keystroke project deployments list --project <slug> (or run `keystroke project link`)");
7407
7489
  await runProjectDeploymentsList(config, { projectRef });
7408
7490
  } catch (error) {
7409
7491
  process.stderr.write(`${formatCliError(error, "List deployments failed", {
@@ -7413,6 +7495,18 @@ function registerProjectCommand(program) {
7413
7495
  process.exitCode = 1;
7414
7496
  }
7415
7497
  });
7498
+ project.command("link").description("Link this directory to a platform project in keystroke.config.ts").requiredOption("--project <slug>", "Platform project slug").option("--dir <path>", "Project directory", process.cwd()).action(async (options) => {
7499
+ const config = createCliConfig();
7500
+ try {
7501
+ await runProjectLink(config, options.project, options.dir);
7502
+ } catch (error) {
7503
+ process.stderr.write(`${formatCliError(error, "Link project failed", {
7504
+ serverUrl: getPlatformUrl(config),
7505
+ webUrl: getWebUrl(config)
7506
+ })}\n`);
7507
+ process.exitCode = 1;
7508
+ }
7509
+ });
7416
7510
  project.command("create").description("Create a platform project in the active organization").requiredOption("--name <name>", "Project name").option("--description <description>", "Project description").action(async (options) => {
7417
7511
  const config = createCliConfig();
7418
7512
  try {
@@ -7528,7 +7622,7 @@ async function runPull(options) {
7528
7622
  function registerPullCommand(program) {
7529
7623
  program.command("pull").description("Download the active deploy source snapshot into a local directory").option("--dir <path>", "Target directory", process.cwd()).option("--force", "Overwrite an existing local project tree").action(async (options) => runCliCommand("Pull failed", async () => {
7530
7624
  const config = createCliConfig();
7531
- const projectId = await resolveActiveProjectId(config);
7625
+ const projectId = await resolveActiveProjectId(config, resolveProjectRoot(options.dir));
7532
7626
  if (!projectId) throw new Error("Usage: keystroke pull --project <slug>");
7533
7627
  await runPull({
7534
7628
  client: createCliPlatformClient(config),
@@ -7820,9 +7914,10 @@ function registerChannelPlatformsCommand(channel) {
7820
7914
  //#region src/commands/channel/resolve-channel-project.ts
7821
7915
  async function resolveChannelProjectId(config, projectRef) {
7822
7916
  const platform = createCliPlatformClient(config);
7917
+ await ensureLinkedOrganization(config);
7823
7918
  await ensureActiveOrganization(config);
7824
- const ref = projectRef?.trim() || getCliTargetOptions().projectId?.trim() || config.get("activeProjectId")?.trim();
7825
- if (!ref) throw new Error("Project is required. Pass --project <slug> or set an active project.");
7919
+ const ref = projectRef?.trim() || getCliTargetOptions().projectId?.trim() || readLinkedProjectConfig().project?.trim();
7920
+ if (!ref) throw new Error("Project is required. Pass --project <slug> or run `keystroke project link --project <slug>`.");
7826
7921
  return (await resolveProjectRef(platform, ref)).id;
7827
7922
  }
7828
7923
  //#endregion
@@ -7920,27 +8015,6 @@ function registerChannelCommand(program) {
7920
8015
  registerChannelUpdateBindingCommand(channel);
7921
8016
  }
7922
8017
  //#endregion
7923
- //#region src/commands/config/use-project.ts
7924
- async function fetchProjectInOrg(config, projectRef) {
7925
- return resolveProjectRef(createCliPlatformClient(config), projectRef);
7926
- }
7927
- function assertProjectReadyForConfig(project) {
7928
- const label = formatProjectLabel(project);
7929
- if (project.status === "failed") throw new Error(project.lastError ? `Project ${label} deploy failed: ${project.lastError}` : `Project ${label} deploy failed`);
7930
- if (project.status === "starting") throw new Error(`Project ${label} is still starting. Wait for deploy to finish, then run \`keystroke config use project ${label}\`.`);
7931
- }
7932
- async function runConfigUseProject(config, projectRef) {
7933
- const project = await fetchProjectInOrg(config, projectRef);
7934
- assertProjectReadyForConfig(project);
7935
- config.set("activeProjectId", project.id);
7936
- config.set("apiTarget", "platform");
7937
- const label = formatProjectLabel(project);
7938
- return {
7939
- stdout: project.status === "active" ? `Using platform project ${label}\n` : `Using platform project ${label} (${project.status})\n`,
7940
- stderr: project.status === "inactive" ? `Project is not active yet. Deploy with: ${cliBinaryName()} deploy --project ${label}\n` : void 0
7941
- };
7942
- }
7943
- //#endregion
7944
8018
  //#region src/commands/config/preferences-patch.ts
7945
8019
  function parsePreferenceValue(rawValue) {
7946
8020
  try {
@@ -7994,22 +8068,23 @@ function registerConfigCommand(program) {
7994
8068
  config.command("show").description("Show CLI configuration").action(() => {
7995
8069
  const cliConfig = createCliConfig();
7996
8070
  const configDir = getConfigDir(cliConfig);
8071
+ const linked = readLinkedProjectConfig();
7997
8072
  process.stdout.write(`${JSON.stringify({
7998
8073
  webUrl: cliConfig.get("webUrl"),
7999
8074
  platformUrl: getPlatformUrl(cliConfig),
8000
8075
  localApiUrl: resolveLocalApiOrigin(process.cwd()),
8001
8076
  activeOrganizationId: cliConfig.get("activeOrganizationId"),
8002
- activeProjectId: cliConfig.get("activeProjectId"),
8077
+ linkedProject: linked.project ?? null,
8078
+ linkedOrganization: linked.organization ?? null,
8003
8079
  apiTarget: getEffectiveApiTarget(cliConfig),
8004
8080
  devSession: readDevSession(configDir) ?? null
8005
8081
  }, null, 2)}\n`);
8006
8082
  });
8007
- config.command("use").description("Switch API target (local/cloud/project) or active organization").argument("[mode]", "local, cloud, project, or org").argument("[id]", "Project or organization id when switching cloud target or org").action(async (mode, id) => {
8083
+ config.command("use").description("Switch API target (local/cloud) or active organization").argument("[mode]", "local, cloud, or org").argument("[id]", "Organization slug when switching org").action(async (mode, id) => {
8008
8084
  const cliConfig = createCliConfig();
8009
8085
  if (!mode || mode === "local") {
8010
8086
  cliConfig.set("apiTarget", "local");
8011
- const activeProjectId = cliConfig.get("activeProjectId");
8012
- process.stdout.write(activeProjectId ? `Using local keystroke server (cloud project ${activeProjectId} unchanged)\n` : "Using local keystroke server\n");
8087
+ process.stdout.write("Using local keystroke server\n");
8013
8088
  return;
8014
8089
  }
8015
8090
  if (mode === "org") {
@@ -8019,16 +8094,10 @@ function registerConfigCommand(program) {
8019
8094
  }
8020
8095
  if (mode === "cloud") {
8021
8096
  cliConfig.set("apiTarget", "platform");
8022
- const activeProjectId = cliConfig.get("activeProjectId");
8023
- process.stdout.write(activeProjectId ? `Using platform API (default project ${activeProjectId} for project-scoped commands)\n` : "Using platform API\n");
8097
+ process.stdout.write("Using platform API\n");
8024
8098
  return;
8025
8099
  }
8026
- if (mode !== "project") throw new Error("Usage: keystroke config use [local|cloud|project [slug]|org [slug]]");
8027
- const targetProjectId = id ?? cliConfig.get("activeProjectId");
8028
- if (!targetProjectId) throw new Error("Usage: keystroke config use project <slug>");
8029
- const result = await runConfigUseProject(cliConfig, targetProjectId);
8030
- process.stdout.write(result.stdout);
8031
- if (result.stderr) process.stderr.write(result.stderr);
8100
+ throw new Error("Usage: keystroke config use [local|cloud|org [slug]]");
8032
8101
  });
8033
8102
  config.command("org").description("List organizations you belong to").action(async () => {
8034
8103
  const organizations = await listOrganizations(createCliConfig());
@@ -8138,7 +8207,7 @@ function createProgram() {
8138
8207
  return program;
8139
8208
  }
8140
8209
  async function runCli(argv) {
8141
- const { maybeAutoUpdate } = await import("./maybe-auto-update-q5MthdI8.mjs");
8210
+ const { maybeAutoUpdate } = await import("./maybe-auto-update-BDvSKDZp.mjs");
8142
8211
  await maybeAutoUpdate(argv);
8143
8212
  createProgram().parse(argv);
8144
8213
  }