@keystrokehq/cli 0.0.132 → 0.0.134

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
@@ -1852,6 +1852,8 @@ const OrganizationSlugSchema = string().trim().toLowerCase().min(2, "Slug must b
1852
1852
  * fail to load.
1853
1853
  */
1854
1854
  const ClaimableOrganizationSlugSchema = OrganizationSlugSchema.refine((slug) => !RESERVED_ORGANIZATION_SLUGS.has(slug), "This slug is reserved");
1855
+ /** Same format as organization slugs — org-scoped, no global reserved list. */
1856
+ const ProjectSlugSchema = OrganizationSlugSchema;
1855
1857
  /** Browser→platform request budget (ping + small platform overhead). */
1856
1858
  const PROJECT_REACHABILITY_REQUEST_TIMEOUT_MS = 17e3;
1857
1859
  /** Normalize a public origin URL (no trailing slash). */
@@ -1891,6 +1893,7 @@ const serializedRouteManifestEntrySchema = discriminatedUnion("kind", [
1891
1893
  kind: literal("agent"),
1892
1894
  method: literal("POST"),
1893
1895
  path: string(),
1896
+ agentSlug: string(),
1894
1897
  moduleFile: string(),
1895
1898
  requestSchema: record(string(), unknown()),
1896
1899
  responseSchema: record(string(), unknown())
@@ -1899,21 +1902,21 @@ const serializedRouteManifestEntrySchema = discriminatedUnion("kind", [
1899
1902
  kind: literal("agent-sessions-list"),
1900
1903
  method: literal("GET"),
1901
1904
  path: string(),
1902
- agentId: string(),
1905
+ agentSlug: string(),
1903
1906
  moduleFile: string()
1904
1907
  }),
1905
1908
  object({
1906
1909
  kind: literal("agent-session-detail"),
1907
1910
  method: literal("GET"),
1908
1911
  path: string(),
1909
- agentId: string(),
1912
+ agentSlug: string(),
1910
1913
  moduleFile: string()
1911
1914
  }),
1912
1915
  object({
1913
1916
  kind: literal("workflow"),
1914
1917
  method: literal("POST"),
1915
1918
  path: string(),
1916
- workflowId: string(),
1919
+ workflowSlug: string(),
1917
1920
  workflowName: string().optional(),
1918
1921
  moduleFile: string(),
1919
1922
  requestSchema: record(string(), unknown()),
@@ -1923,7 +1926,7 @@ const serializedRouteManifestEntrySchema = discriminatedUnion("kind", [
1923
1926
  kind: literal("workflow-runs-list"),
1924
1927
  method: literal("GET"),
1925
1928
  path: string(),
1926
- workflowId: string(),
1929
+ workflowSlug: string(),
1927
1930
  workflowName: string().optional(),
1928
1931
  moduleFile: string()
1929
1932
  }),
@@ -1931,7 +1934,7 @@ const serializedRouteManifestEntrySchema = discriminatedUnion("kind", [
1931
1934
  kind: literal("workflow-run-detail"),
1932
1935
  method: literal("GET"),
1933
1936
  path: string(),
1934
- workflowId: string(),
1937
+ workflowSlug: string(),
1935
1938
  workflowName: string().optional(),
1936
1939
  moduleFile: string()
1937
1940
  }),
@@ -2053,6 +2056,17 @@ object({
2053
2056
  slug: string().trim().min(1),
2054
2057
  excludeOrganizationId: string().min(1).optional()
2055
2058
  });
2059
+ const ProjectSlugAvailabilityReasonSchema = _enum(["taken", "invalid"]);
2060
+ const ProjectSlugAvailabilityResponseSchema = object({
2061
+ slug: string(),
2062
+ available: boolean(),
2063
+ reason: ProjectSlugAvailabilityReasonSchema.optional(),
2064
+ suggestion: ProjectSlugSchema.optional()
2065
+ });
2066
+ object({
2067
+ slug: string().trim().min(1),
2068
+ excludeProjectId: string().min(1).optional()
2069
+ });
2056
2070
  const ProjectStatusSchema = _enum([
2057
2071
  "inactive",
2058
2072
  "starting",
@@ -2076,6 +2090,7 @@ const ProjectSchema = object({
2076
2090
  id: string(),
2077
2091
  organizationId: string(),
2078
2092
  name: string(),
2093
+ slug: ProjectSlugSchema,
2079
2094
  description: string().nullable(),
2080
2095
  status: ProjectStatusSchema,
2081
2096
  baseUrl: string().nullable(),
@@ -2116,8 +2131,9 @@ const CreateProjectRequestSchema = object({
2116
2131
  });
2117
2132
  const UpdateProjectRequestSchema = object({
2118
2133
  name: string().trim().min(1).optional(),
2119
- description: string().trim().optional()
2120
- }).refine((data) => data.name !== void 0 || data.description !== void 0, { message: "At least one field is required" });
2134
+ description: string().trim().optional(),
2135
+ slug: ProjectSlugSchema.optional()
2136
+ }).refine((data) => data.name !== void 0 || data.description !== void 0 || data.slug !== void 0, { message: "At least one field is required" });
2121
2137
  const CreateProjectResponseSchema = object({ project: ProjectSchema });
2122
2138
  const ProjectResponseSchema = object({ project: ProjectSchema });
2123
2139
  const ProjectReachabilityResponseSchema = object({ reachable: boolean() });
@@ -2255,7 +2271,7 @@ const WorkflowRunSummarySchema = object({
2255
2271
  });
2256
2272
  const WorkflowRunRecordSchema = object({
2257
2273
  id: string(),
2258
- workflowKey: string(),
2274
+ workflowSlug: string(),
2259
2275
  status: WorkflowRunStatusSchema,
2260
2276
  trigger: WorkflowRunTriggerSchema,
2261
2277
  triggeredAt: string(),
@@ -2290,7 +2306,7 @@ object({ include: string().optional() });
2290
2306
  const TriggerRunDetailSchema = object({
2291
2307
  id: string(),
2292
2308
  attachmentId: string(),
2293
- attachmentKey: string().nullable(),
2309
+ attachmentSlug: string().nullable(),
2294
2310
  triggerType: _enum([
2295
2311
  "cron",
2296
2312
  "webhook",
@@ -2309,10 +2325,10 @@ const WorkflowStepRunSchema = object({
2309
2325
  const TraceTriggerSummarySchema = object({
2310
2326
  triggerType: string(),
2311
2327
  triggerRunId: string(),
2312
- workflowKey: string().optional(),
2313
- agentKey: string().optional(),
2328
+ workflowSlug: string().optional(),
2329
+ agentSlug: string().optional(),
2314
2330
  attachmentId: string().nullable().optional(),
2315
- attachmentKey: string().nullable().optional(),
2331
+ attachmentSlug: string().nullable().optional(),
2316
2332
  route: string().optional(),
2317
2333
  sourcePath: string().nullable().optional()
2318
2334
  }).nullable();
@@ -2376,7 +2392,7 @@ const AgentSessionSummarySchema = object({
2376
2392
  });
2377
2393
  const AgentSessionRecordSchema = object({
2378
2394
  id: string(),
2379
- agentKey: string(),
2395
+ agentSlug: string(),
2380
2396
  status: AgentSessionStatusSchema,
2381
2397
  source: RunSourceKindSchema,
2382
2398
  sourceId: string().nullable(),
@@ -2406,7 +2422,7 @@ object({ include: string().optional() });
2406
2422
  const GatewaySessionDetailSchema = object({
2407
2423
  threadId: string(),
2408
2424
  attachmentId: string(),
2409
- attachmentKey: string().nullable()
2425
+ attachmentSlug: string().nullable()
2410
2426
  });
2411
2427
  const AgentEventSchema = object({
2412
2428
  id: string(),
@@ -2501,11 +2517,11 @@ const TriggerRunTypeSchema = _enum([
2501
2517
  const TriggerRunWorkflowSummarySchema = object({
2502
2518
  id: string(),
2503
2519
  status: WorkflowRunStatusSchema,
2504
- workflowKey: string()
2520
+ workflowSlug: string()
2505
2521
  });
2506
2522
  const TriggerRunAgentSessionSummarySchema = object({
2507
2523
  id: string(),
2508
- agentKey: string()
2524
+ agentSlug: string()
2509
2525
  });
2510
2526
  const TriggerRunSummarySchema = object({
2511
2527
  id: string(),
@@ -2518,7 +2534,7 @@ const TriggerRunSummarySchema = object({
2518
2534
  const TriggerRunRecordSchema = object({
2519
2535
  id: string(),
2520
2536
  attachmentId: string(),
2521
- attachmentKey: string(),
2537
+ attachmentSlug: string(),
2522
2538
  triggerType: TriggerRunTypeSchema,
2523
2539
  sourcePath: string().nullable(),
2524
2540
  payload: unknown().nullable(),
@@ -2541,7 +2557,7 @@ _enum([
2541
2557
  object({ include: string().optional() });
2542
2558
  const TriggerRunAgentSessionDetailSchema = object({
2543
2559
  id: string(),
2544
- agentKey: string()
2560
+ agentSlug: string()
2545
2561
  });
2546
2562
  const TriggerRunDetailResponseSchema = object({
2547
2563
  run: TriggerRunRecordSchema,
@@ -2684,6 +2700,7 @@ const optionalCount = number$1().int().nonnegative().nullable().optional();
2684
2700
  const optionalTimestamp = string().nullable().optional();
2685
2701
  const AgentSummarySchema = object({
2686
2702
  id: string().min(1),
2703
+ slug: string().min(1),
2687
2704
  name: string().min(1),
2688
2705
  projectId: string().min(1),
2689
2706
  updatedAt: string().min(1),
@@ -2696,16 +2713,17 @@ const AgentSummarySchema = object({
2696
2713
  });
2697
2714
  const WorkflowSummarySchema = object({
2698
2715
  id: string().min(1),
2716
+ slug: string().min(1),
2699
2717
  name: string().min(1),
2700
2718
  projectId: string().min(1),
2701
2719
  updatedAt: string().min(1),
2702
2720
  description: string().optional(),
2703
2721
  sourcePath: string().min(1).optional(),
2704
- stepCount: optionalCount,
2705
2722
  lastRunAt: optionalTimestamp
2706
2723
  });
2707
2724
  const SkillSummarySchema = object({
2708
2725
  id: string().min(1),
2726
+ slug: string().min(1),
2709
2727
  name: string().min(1),
2710
2728
  projectId: string().min(1),
2711
2729
  updatedAt: string().min(1),
@@ -2750,9 +2768,9 @@ const ProjectFileSummarySchema = object({
2750
2768
  path: string().min(1),
2751
2769
  hash: sha256Hex
2752
2770
  });
2753
- /** Maps a runtime resource key (agent/workflow/skill) to its source file path. */
2771
+ /** Maps a runtime resource slug (agent/workflow/skill) to its source file path. */
2754
2772
  const ProjectSourceResourceRefSchema = object({
2755
- key: string().min(1),
2773
+ slug: string().min(1),
2756
2774
  path: string().min(1)
2757
2775
  });
2758
2776
  const ProjectSourceResourcesSchema = object({
@@ -5174,6 +5192,16 @@ function createProjectsResource(http) {
5174
5192
  } catch (error) {
5175
5193
  throw await toPlatformError(error);
5176
5194
  }
5195
+ },
5196
+ async checkSlug(slug, options = {}) {
5197
+ const searchParams = new URLSearchParams({ slug });
5198
+ if (options.excludeProjectId) searchParams.set("excludeProjectId", options.excludeProjectId);
5199
+ try {
5200
+ const data = await http.get(`/api/projects/slug-available?${searchParams}`).json();
5201
+ return ProjectSlugAvailabilityResponseSchema.parse(data);
5202
+ } catch (error) {
5203
+ throw await toPlatformError(error);
5204
+ }
5177
5205
  }
5178
5206
  };
5179
5207
  }
@@ -5187,11 +5215,15 @@ function createProjectMetricsResource(http) {
5187
5215
  }
5188
5216
  } };
5189
5217
  }
5218
+ function listSearchParams$2(options) {
5219
+ const projectId = options?.projectId?.trim();
5220
+ return projectId ? { projectId } : void 0;
5221
+ }
5190
5222
  function createAgentsResource(http) {
5191
5223
  return {
5192
- async list() {
5224
+ async list(options) {
5193
5225
  try {
5194
- const data = await http.get("/api/agents").json();
5226
+ const data = await http.get("/api/agents", { searchParams: listSearchParams$2(options) }).json();
5195
5227
  return AgentSummaryListResponseSchema.parse(data);
5196
5228
  } catch (error) {
5197
5229
  throw await toPlatformError(error);
@@ -5207,11 +5239,15 @@ function createAgentsResource(http) {
5207
5239
  }
5208
5240
  };
5209
5241
  }
5242
+ function listSearchParams$1(options) {
5243
+ const projectId = options?.projectId?.trim();
5244
+ return projectId ? { projectId } : void 0;
5245
+ }
5210
5246
  function createWorkflowsResource(http) {
5211
5247
  return {
5212
- async list() {
5248
+ async list(options) {
5213
5249
  try {
5214
- const data = await http.get("/api/workflows").json();
5250
+ const data = await http.get("/api/workflows", { searchParams: listSearchParams$1(options) }).json();
5215
5251
  return WorkflowSummaryListResponseSchema.parse(data);
5216
5252
  } catch (error) {
5217
5253
  throw await toPlatformError(error);
@@ -5227,11 +5263,15 @@ function createWorkflowsResource(http) {
5227
5263
  }
5228
5264
  };
5229
5265
  }
5266
+ function listSearchParams(options) {
5267
+ const projectId = options?.projectId?.trim();
5268
+ return projectId ? { projectId } : void 0;
5269
+ }
5230
5270
  function createSkillsResource(http) {
5231
5271
  return {
5232
- async list() {
5272
+ async list(options) {
5233
5273
  try {
5234
- const data = await http.get("/api/skills").json();
5274
+ const data = await http.get("/api/skills", { searchParams: listSearchParams(options) }).json();
5235
5275
  return SkillSummaryListResponseSchema.parse(data);
5236
5276
  } catch (error) {
5237
5277
  throw await toPlatformError(error);
@@ -6754,6 +6794,7 @@ const credentialSeed = [
6754
6794
  ];
6755
6795
  const agentSeed = [{
6756
6796
  id: "agent-support",
6797
+ slug: "support-agent",
6757
6798
  name: "Support Agent",
6758
6799
  projectId: "project_personal_repo",
6759
6800
  updatedAt: "2026-06-02T14:20:00Z",
@@ -6765,6 +6806,7 @@ const agentSeed = [{
6765
6806
  lastRunAt: "2026-06-02T14:20:00Z"
6766
6807
  }, {
6767
6808
  id: "agent-research",
6809
+ slug: "research-agent",
6768
6810
  name: "Research Agent",
6769
6811
  projectId: "project_personal_repo",
6770
6812
  updatedAt: "2026-06-01T09:15:00Z",
@@ -6777,21 +6819,21 @@ const agentSeed = [{
6777
6819
  }];
6778
6820
  const workflowSeed = [{
6779
6821
  id: "workflow-daily-digest",
6822
+ slug: "daily-digest",
6780
6823
  name: "Daily Digest",
6781
6824
  projectId: "project_personal_repo",
6782
6825
  updatedAt: "2026-06-02T11:05:00Z",
6783
6826
  description: "Collect project updates and send a daily summary.",
6784
6827
  sourcePath: "src/workflows/daily-digest.ts",
6785
- stepCount: 2,
6786
6828
  lastRunAt: "2026-06-02T11:05:00Z"
6787
6829
  }, {
6788
6830
  id: "workflow-email-triage",
6831
+ slug: "email-triage",
6789
6832
  name: "Email Triage",
6790
6833
  projectId: "project_personal_repo",
6791
6834
  updatedAt: "2026-05-31T16:40:00Z",
6792
6835
  description: "Label, prioritize, and draft replies for inbound email.",
6793
6836
  sourcePath: "src/workflows/email-triage.ts",
6794
- stepCount: 4,
6795
6837
  lastRunAt: "2026-05-31T16:40:00Z"
6796
6838
  }];
6797
6839
  /** Review personas for mocked project members and API key authors (not org membership). */
@@ -7124,15 +7166,11 @@ function buildFailedUsage(durationMs) {
7124
7166
  totalExcludingDurationUsd: .021
7125
7167
  };
7126
7168
  }
7127
- function workflowKeyFromId(workflowId) {
7128
- const row = workflowSeed.find((workflow) => workflow.id === workflowId);
7129
- if (!row?.sourcePath) return workflowId;
7130
- return row.sourcePath.match(/\/([^/]+)\.ts$/)?.[1] ?? workflowId;
7169
+ function workflowSlugFromId(workflowId) {
7170
+ return workflowSeed.find((workflow) => workflow.id === workflowId)?.slug ?? workflowId;
7131
7171
  }
7132
- function agentKeyFromId(agentId) {
7133
- const row = agentSeed.find((agent) => agent.id === agentId);
7134
- if (!row?.sourcePath) return agentId;
7135
- return row.sourcePath.match(/\/([^/]+)\.ts$/)?.[1] ?? agentId;
7172
+ function agentSlugFromId(agentId) {
7173
+ return agentSeed.find((agent) => agent.id === agentId)?.slug ?? agentId;
7136
7174
  }
7137
7175
  const historyRunSeed = [
7138
7176
  {
@@ -7141,7 +7179,7 @@ const historyRunSeed = [
7141
7179
  traceId: "run-support-agent-001",
7142
7180
  projectId: "project_personal_repo",
7143
7181
  projectName: historyProjectNameById.project_personal_repo ?? "Project",
7144
- resourceKey: agentKeyFromId("agent-support"),
7182
+ resourceKey: agentSlugFromId("agent-support"),
7145
7183
  resourceId: "agent-support",
7146
7184
  resourceName: "Support Agent",
7147
7185
  status: "completed",
@@ -7164,7 +7202,7 @@ const historyRunSeed = [
7164
7202
  traceId: "run-daily-digest-001",
7165
7203
  projectId: "project_personal_repo",
7166
7204
  projectName: historyProjectNameById.project_personal_repo ?? "Project",
7167
- resourceKey: workflowKeyFromId("workflow-daily-digest"),
7205
+ resourceKey: workflowSlugFromId("workflow-daily-digest"),
7168
7206
  resourceId: "workflow-daily-digest",
7169
7207
  resourceName: "Daily Digest",
7170
7208
  status: "running",
@@ -7187,7 +7225,7 @@ const historyRunSeed = [
7187
7225
  traceId: "run-support-agent-000",
7188
7226
  projectId: "project_personal_repo",
7189
7227
  projectName: historyProjectNameById.project_personal_repo ?? "Project",
7190
- resourceKey: agentKeyFromId("agent-support"),
7228
+ resourceKey: agentSlugFromId("agent-support"),
7191
7229
  resourceId: "agent-support",
7192
7230
  resourceName: "Support Agent",
7193
7231
  status: "failed",
@@ -7210,7 +7248,7 @@ const historyRunSeed = [
7210
7248
  traceId: "run-email-triage-002",
7211
7249
  projectId: "project_personal_repo",
7212
7250
  projectName: historyProjectNameById.project_personal_repo ?? "Project",
7213
- resourceKey: workflowKeyFromId("workflow-email-triage"),
7251
+ resourceKey: workflowSlugFromId("workflow-email-triage"),
7214
7252
  resourceId: "workflow-email-triage",
7215
7253
  resourceName: "Email Triage",
7216
7254
  status: "failed",
@@ -7233,7 +7271,7 @@ const historyRunSeed = [
7233
7271
  traceId: "run-research-agent-003",
7234
7272
  projectId: "project_personal_repo",
7235
7273
  projectName: historyProjectNameById.project_personal_repo ?? "Project",
7236
- resourceKey: agentKeyFromId("agent-research"),
7274
+ resourceKey: agentSlugFromId("agent-research"),
7237
7275
  resourceId: "agent-research",
7238
7276
  resourceName: "Research Agent",
7239
7277
  status: "completed",
@@ -8587,6 +8625,33 @@ function createCliPlatformClient(config) {
8587
8625
  });
8588
8626
  }
8589
8627
  //#endregion
8628
+ //#region src/resolve-resource-ref.ts
8629
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
8630
+ function looksLikeUuid(value) {
8631
+ return UUID_PATTERN.test(value);
8632
+ }
8633
+ function formatProjectLabel(project) {
8634
+ return project.slug;
8635
+ }
8636
+ async function resolveProjectRef(platform, ref) {
8637
+ if (looksLikeUuid(ref)) try {
8638
+ return await platform.projects.get(ref);
8639
+ } catch (error) {
8640
+ 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>\`.`);
8641
+ throw error;
8642
+ }
8643
+ const match = (await platform.projects.list()).find((project) => project.slug === ref);
8644
+ if (!match) throw new Error(`Project "${ref}" not found in the active organization. Run \`keystroke project list\`.`);
8645
+ return match;
8646
+ }
8647
+ function resolveOrganizationRef(organizations, ref) {
8648
+ const byId = organizations.find((entry) => entry.organization.id === ref);
8649
+ if (byId) return byId;
8650
+ const bySlug = organizations.find((entry) => entry.organization.slug === ref);
8651
+ if (bySlug) return bySlug;
8652
+ throw new Error(`Organization "${ref}" was not found or you do not have access`);
8653
+ }
8654
+ //#endregion
8590
8655
  //#region src/organization/active-organization.ts
8591
8656
  function formatOrganization(membership) {
8592
8657
  return `${membership.organization.name} (${membership.role})`;
@@ -8600,15 +8665,11 @@ async function activateOrganization(config, organizationId) {
8600
8665
  platform.setActiveOrganizationId(membership.organization.id);
8601
8666
  return membership;
8602
8667
  }
8603
- async function pickOrganization(config, organizations, organizationId) {
8604
- if (organizationId) {
8605
- const membership = organizations.find((entry) => entry.organization.id === organizationId);
8606
- if (!membership) throw new Error(`Organization ${organizationId} was not found or you do not have access`);
8607
- return activateOrganization(config, membership.organization.id);
8608
- }
8668
+ async function pickOrganization(config, organizations, organizationRef) {
8669
+ if (organizationRef) return activateOrganization(config, resolveOrganizationRef(organizations, organizationRef).organization.id);
8609
8670
  if (!process.stdin.isTTY) {
8610
- const names = organizations.map((entry) => entry.organization.id).join(", ");
8611
- throw new Error(`Multiple organizations found. Run \`keystroke config use org <id>\`. Options: ${names}`);
8671
+ const names = organizations.map((entry) => entry.organization.slug).join(", ");
8672
+ throw new Error(`Multiple organizations found. Run \`keystroke config use org <slug>\`. Options: ${names}`);
8612
8673
  }
8613
8674
  return activateOrganization(config, await select({
8614
8675
  message: "Select an organization",
@@ -8622,7 +8683,7 @@ async function selectActiveOrganization(config, options = {}) {
8622
8683
  const organizations = await listOrganizations(config);
8623
8684
  if (organizations.length === 0) throw new Error(`No organization found — create one at ${getWebUrl(config)}`);
8624
8685
  if (organizations.length === 1) return activateOrganization(config, organizations[0].organization.id);
8625
- return pickOrganization(config, organizations, options.organizationId);
8686
+ return pickOrganization(config, organizations, options.organizationRef ?? options.organizationId);
8626
8687
  }
8627
8688
  async function syncActiveOrganizationAfterLogin(config, options = {}) {
8628
8689
  const organizations = await listOrganizations(config);
@@ -8638,8 +8699,8 @@ async function syncActiveOrganizationAfterLogin(config, options = {}) {
8638
8699
  process.stdout.write(`Using organization ${membership.organization.name}\n`);
8639
8700
  return membership;
8640
8701
  }
8641
- if (options.organizationId) {
8642
- const membership = await pickOrganization(config, organizations, options.organizationId);
8702
+ if (options.organizationRef ?? options.organizationId) {
8703
+ const membership = await pickOrganization(config, organizations, options.organizationRef ?? options.organizationId);
8643
8704
  process.stdout.write(`Using organization ${membership.organization.name}\n`);
8644
8705
  return membership;
8645
8706
  }
@@ -8712,19 +8773,19 @@ function resolveOrgPlatformTarget(config) {
8712
8773
  mode: "platform"
8713
8774
  };
8714
8775
  }
8715
- async function resolvePlatformTarget(config, projectId) {
8776
+ async function resolvePlatformTarget(config, projectRef) {
8716
8777
  const platform = createCliPlatformClient(config);
8717
8778
  let project;
8718
8779
  try {
8719
- project = await platform.projects.get(projectId);
8780
+ project = await resolveProjectRef(platform, projectRef);
8720
8781
  } catch (error) {
8721
- if (error instanceof PlatformError && error.status === 404) throw new Error(`Project ${projectId} not found in the active organization. Run \`keystroke config use project <id>\` or pass \`--project\` with a valid project id.`);
8782
+ if (error instanceof Error) throw error;
8722
8783
  throw error;
8723
8784
  }
8724
- if (project.status !== "active") throw new Error(`Project ${projectId} is not active (${project.status})`);
8785
+ if (project.status !== "active") throw new Error(`Project ${formatProjectLabel(project)} is not active (${project.status})`);
8725
8786
  return {
8726
- baseUrl: `${getPlatformUrl(config).replace(/\/+$/, "")}/api/projects/${encodeURIComponent(projectId)}`,
8727
- projectId,
8787
+ baseUrl: `${getPlatformUrl(config).replace(/\/+$/, "")}/api/projects/${encodeURIComponent(project.id)}`,
8788
+ projectId: project.id,
8728
8789
  mode: "platform"
8729
8790
  };
8730
8791
  }
@@ -8742,9 +8803,9 @@ async function resolveApiTarget(config, options = {}) {
8742
8803
  if (options.projectId) return resolvePlatformTarget(config, options.projectId);
8743
8804
  if (getEffectiveApiTarget(config) === "local") return resolveLocalTarget(config);
8744
8805
  if (!options.projectScoped) return resolveOrgPlatformTarget(config);
8745
- const projectId = options.projectId ?? config.get("activeProjectId");
8746
- if (!projectId) throw new Error("No project selected. Pass `--project <id>` or run `keystroke config use project <id>`.");
8747
- return resolvePlatformTarget(config, projectId);
8806
+ const projectRef = options.projectId ?? config.get("activeProjectId");
8807
+ if (!projectRef) throw new Error("No project selected. Pass `--project <slug>` or run `keystroke config use project <slug>`.");
8808
+ return resolvePlatformTarget(config, projectRef);
8748
8809
  }
8749
8810
  //#endregion
8750
8811
  //#region src/target-options.ts
@@ -8755,9 +8816,6 @@ function setCliTargetOptions(options) {
8755
8816
  function getCliTargetOptions() {
8756
8817
  return targetOptions;
8757
8818
  }
8758
- function resolveActiveProjectId(config) {
8759
- return getCliTargetOptions().projectId ?? config.get("activeProjectId");
8760
- }
8761
8819
  //#endregion
8762
8820
  //#region src/errors/example-input.ts
8763
8821
  function exampleValueFromValidationMessage(message) {
@@ -9232,7 +9290,7 @@ function resolveAuthLoginTargets(options, config) {
9232
9290
  //#endregion
9233
9291
  //#region src/commands/auth/login.ts
9234
9292
  function registerAuthLoginCommand(auth) {
9235
- auth.command("login").description("Authenticate via browser device flow").option("--web-url <url>", "Web dashboard origin (omit to use production defaults; local dev: http://localhost:3000)").option("--platform-url <url>", "Platform API origin (derived from web URL when omitted)").option("--org <id>", "Active organization id (skips prompt when you belong to several)").action(async (options) => {
9293
+ auth.command("login").description("Authenticate via browser device flow").option("--web-url <url>", "Web dashboard origin (omit to use production defaults; local dev: http://localhost:3000)").option("--platform-url <url>", "Platform API origin (derived from web URL when omitted)").option("--org <slug>", "Active organization slug (skips prompt when you belong to several)").action(async (options) => {
9236
9294
  const config = createCliConfig();
9237
9295
  const { webUrl, platformUrl } = resolveAuthLoginTargets(options, config);
9238
9296
  config.set("webUrl", webUrl);
@@ -9247,7 +9305,7 @@ function registerAuthLoginCommand(auth) {
9247
9305
  const user = await getSessionWithBearer(platformUrl, result.accessToken);
9248
9306
  if (user) process.stdout.write(`Logged in as ${user.name} (${user.email})\n`);
9249
9307
  else process.stdout.write(`Logged in to ${webUrl}\n`);
9250
- await syncActiveOrganizationAfterLogin(config, { organizationId: options.org });
9308
+ await syncActiveOrganizationAfterLogin(config, { organizationRef: options.org });
9251
9309
  } catch (error) {
9252
9310
  process.stderr.write(`${formatCliError(error, "Login failed", {
9253
9311
  webUrl,
@@ -9425,9 +9483,9 @@ function collectModuleRefs(srcRoot, root, files, subdir, nestedEntry) {
9425
9483
  const refs = [];
9426
9484
  for (const file of files) {
9427
9485
  if (!file.path.startsWith(prefix) || !/\.(ts|mts)$/.test(file.path)) continue;
9428
- const key = entryIdFromFile(dir, join(root, file.path), { nestedEntry });
9429
- if (key) refs.push({
9430
- key,
9486
+ const slug = entryIdFromFile(dir, join(root, file.path), { nestedEntry });
9487
+ if (slug) refs.push({
9488
+ slug,
9431
9489
  path: file.path
9432
9490
  });
9433
9491
  }
@@ -9439,9 +9497,9 @@ function collectSkillRefs(srcRoot, root, files) {
9439
9497
  const refs = [];
9440
9498
  for (const file of files) {
9441
9499
  if (!file.path.startsWith(prefix) || !file.path.endsWith("SKILL.md")) continue;
9442
- const key = toPosix(relative(dir, join(root, file.path)).replace(/\/?SKILL\.md$/, ""));
9443
- if (key) refs.push({
9444
- key,
9500
+ const slug = toPosix(relative(dir, join(root, file.path)).replace(/\/?SKILL\.md$/, ""));
9501
+ if (slug) refs.push({
9502
+ slug,
9445
9503
  path: file.path
9446
9504
  });
9447
9505
  }
@@ -9467,6 +9525,18 @@ function collectProjectSource(root) {
9467
9525
  };
9468
9526
  }
9469
9527
  //#endregion
9528
+ //#region src/resolve-project-target.ts
9529
+ async function resolveActiveProject(config, platform) {
9530
+ const ref = getCliTargetOptions().projectId ?? config.get("activeProjectId");
9531
+ if (!ref) return;
9532
+ const client = platform ?? createCliPlatformClient(config);
9533
+ if (!getCliTargetOptions().projectId && looksLikeUuid(ref)) return resolveProjectRef(client, ref);
9534
+ return resolveProjectRef(client, ref);
9535
+ }
9536
+ async function resolveActiveProjectId(config) {
9537
+ return (await resolveActiveProject(config))?.id;
9538
+ }
9539
+ //#endregion
9470
9540
  //#region src/commands/deploy.ts
9471
9541
  const POLL_INTERVAL_MS = 2e3;
9472
9542
  const DEPLOY_TIMEOUT_MS = 12e4;
@@ -9557,8 +9627,8 @@ async function runDeploy(options) {
9557
9627
  function registerDeployCommand(program) {
9558
9628
  program.command("deploy").description("Build, upload, and deploy the project to the platform").option("--dir <path>", "Project directory", process.cwd()).option("--skip-build", "Upload the existing dist/ without rebuilding").action(async (options) => {
9559
9629
  try {
9560
- const projectId = resolveActiveProjectId(createCliConfig());
9561
- if (!projectId) throw new Error("Usage: keystroke deploy --project <id>");
9630
+ const projectId = await resolveActiveProjectId(createCliConfig());
9631
+ if (!projectId) throw new Error("Usage: keystroke deploy --project <slug>");
9562
9632
  await runDeploy({
9563
9633
  dir: options.dir,
9564
9634
  projectId,
@@ -10118,7 +10188,7 @@ function writeInactiveDeployHints(projects) {
10118
10188
  const bin = cliBinaryName();
10119
10189
  for (const project of projects) {
10120
10190
  if (project.status !== "inactive") continue;
10121
- process.stderr.write(`Project ${project.id} is inactive. Deploy with: ${bin} deploy --project ${project.id}\n`);
10191
+ process.stderr.write(`Project ${formatProjectLabel(project)} is inactive. Deploy with: ${bin} deploy --project ${formatProjectLabel(project)}\n`);
10122
10192
  }
10123
10193
  }
10124
10194
  async function runProjectList(config) {
@@ -10136,13 +10206,19 @@ async function runProjectList(config) {
10136
10206
  async function runProjectMetrics(config, options) {
10137
10207
  await withActivePlatformClient(config, async (platform) => {
10138
10208
  const metrics = await platform.projectMetrics.list();
10139
- const output = options.projectId === void 0 ? metrics : Object.fromEntries(Object.entries(metrics).filter(([projectId]) => projectId === options.projectId));
10209
+ if (options.projectRef === void 0) {
10210
+ process.stdout.write(`${JSON.stringify(metrics, null, 2)}\n`);
10211
+ return;
10212
+ }
10213
+ const project = await resolveProjectRef(platform, options.projectRef);
10214
+ const output = Object.fromEntries(Object.entries(metrics).filter(([projectId]) => projectId === project.id));
10140
10215
  process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
10141
10216
  });
10142
10217
  }
10143
10218
  async function runProjectDeploymentsList(config, options) {
10144
10219
  await withActivePlatformClient(config, async (platform) => {
10145
- const deployments = await platform.deployments.listForProject(options.projectId);
10220
+ const project = await resolveProjectRef(platform, options.projectRef);
10221
+ const deployments = await platform.deployments.listForProject(project.id);
10146
10222
  process.stdout.write(`${JSON.stringify({ deployments }, null, 2)}\n`);
10147
10223
  });
10148
10224
  }
@@ -10206,10 +10282,10 @@ function registerProjectCommand(program) {
10206
10282
  process.exitCode = 1;
10207
10283
  }
10208
10284
  });
10209
- project.command("metrics").description("List rollup metrics for projects in the active organization").option("--project <id>", "Return metrics for a single project").action(async (options) => {
10285
+ project.command("metrics").description("List rollup metrics for projects in the active organization").option("--project <slug>", "Return metrics for a single project").action(async (options) => {
10210
10286
  const config = createCliConfig();
10211
10287
  try {
10212
- await runProjectMetrics(config, { ...options.project ? { projectId: options.project } : {} });
10288
+ await runProjectMetrics(config, { ...options.project ? { projectRef: options.project } : {} });
10213
10289
  } catch (error) {
10214
10290
  process.stderr.write(`${formatCliError(error, "List project metrics failed", {
10215
10291
  serverUrl: getPlatformUrl(config),
@@ -10218,12 +10294,12 @@ function registerProjectCommand(program) {
10218
10294
  process.exitCode = 1;
10219
10295
  }
10220
10296
  });
10221
- project.command("deployments").description("View deployment history for platform projects").command("list").description("List deployment history for a project").option("--project <id>", "Project ID").action(async (options) => {
10297
+ 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) => {
10222
10298
  const config = createCliConfig();
10223
10299
  try {
10224
- const projectId = options.project ?? resolveActiveProjectId(config);
10225
- if (!projectId) throw new Error("Usage: keystroke project deployments list --project <id>");
10226
- await runProjectDeploymentsList(config, { projectId });
10300
+ const projectRef = options.project ?? getCliTargetOptions().projectId ?? config.get("activeProjectId");
10301
+ if (!projectRef) throw new Error("Usage: keystroke project deployments list --project <slug>");
10302
+ await runProjectDeploymentsList(config, { projectRef });
10227
10303
  } catch (error) {
10228
10304
  process.stderr.write(`${formatCliError(error, "List deployments failed", {
10229
10305
  serverUrl: getPlatformUrl(config),
@@ -10250,8 +10326,8 @@ function registerProjectCommand(program) {
10250
10326
  project.command("update").description("Update a platform project's name and/or description").option("--name <name>", "Project name").option("--description <description>", "Project description").action(async (options) => {
10251
10327
  const config = createCliConfig();
10252
10328
  try {
10253
- const projectId = resolveActiveProjectId(config);
10254
- if (!projectId) throw new Error("Usage: keystroke --project <id> project update --name <name>");
10329
+ const projectId = await resolveActiveProjectId(config);
10330
+ if (!projectId) throw new Error("Usage: keystroke --project <slug> project update --name <name>");
10255
10331
  if (!options.name && options.description === void 0) throw new Error("At least one of --name or --description is required");
10256
10332
  await runProjectUpdate(config, {
10257
10333
  projectId,
@@ -10269,8 +10345,8 @@ function registerProjectCommand(program) {
10269
10345
  project.command("delete").description("Delete a platform project").option("-y, --yes", "Skip confirmation prompt").action(async (options) => {
10270
10346
  const config = createCliConfig();
10271
10347
  try {
10272
- const projectId = resolveActiveProjectId(config);
10273
- if (!projectId) throw new Error("Usage: keystroke --project <id> project delete");
10348
+ const projectId = await resolveActiveProjectId(config);
10349
+ if (!projectId) throw new Error("Usage: keystroke --project <slug> project delete");
10274
10350
  if (!options.yes) throw new Error("Pass --yes to confirm project deletion");
10275
10351
  await runProjectDelete(config, projectId);
10276
10352
  } catch (error) {
@@ -10431,27 +10507,23 @@ function registerWorkflowCommand(program) {
10431
10507
  }
10432
10508
  //#endregion
10433
10509
  //#region src/commands/config/use-project.ts
10434
- async function fetchProjectInOrg(config, projectId) {
10435
- const platform = createCliPlatformClient(config);
10436
- try {
10437
- return await platform.projects.get(projectId);
10438
- } catch (error) {
10439
- if (error instanceof PlatformError && error.status === 404) throw new Error(`Project ${projectId} not found in the active organization. Run \`keystroke project list\`, then \`keystroke config use project <id>\`.`);
10440
- throw error;
10441
- }
10510
+ async function fetchProjectInOrg(config, projectRef) {
10511
+ return resolveProjectRef(createCliPlatformClient(config), projectRef);
10442
10512
  }
10443
10513
  function assertProjectReadyForConfig(project) {
10444
- if (project.status === "failed") throw new Error(project.lastError ? `Project ${project.id} deploy failed: ${project.lastError}` : `Project ${project.id} deploy failed`);
10445
- if (project.status === "starting") throw new Error(`Project ${project.id} is still starting. Wait for deploy to finish, then run \`keystroke config use project ${project.id}\`.`);
10514
+ const label = formatProjectLabel(project);
10515
+ if (project.status === "failed") throw new Error(project.lastError ? `Project ${label} deploy failed: ${project.lastError}` : `Project ${label} deploy failed`);
10516
+ if (project.status === "starting") throw new Error(`Project ${label} is still starting. Wait for deploy to finish, then run \`keystroke config use project ${label}\`.`);
10446
10517
  }
10447
- async function runConfigUseProject(config, projectId) {
10448
- const project = await fetchProjectInOrg(config, projectId);
10518
+ async function runConfigUseProject(config, projectRef) {
10519
+ const project = await fetchProjectInOrg(config, projectRef);
10449
10520
  assertProjectReadyForConfig(project);
10450
- config.set("activeProjectId", projectId);
10521
+ config.set("activeProjectId", project.id);
10451
10522
  config.set("apiTarget", "platform");
10523
+ const label = formatProjectLabel(project);
10452
10524
  return {
10453
- stdout: project.status === "active" ? `Using platform project ${projectId}\n` : `Using platform project ${projectId} (${project.status})\n`,
10454
- stderr: project.status === "inactive" ? `Project is not active yet. Deploy with: ${cliBinaryName()} deploy --project ${projectId}\n` : void 0
10525
+ stdout: project.status === "active" ? `Using platform project ${label}\n` : `Using platform project ${label} (${project.status})\n`,
10526
+ stderr: project.status === "inactive" ? `Project is not active yet. Deploy with: ${cliBinaryName()} deploy --project ${label}\n` : void 0
10455
10527
  };
10456
10528
  }
10457
10529
  //#endregion
@@ -10480,7 +10552,7 @@ function registerConfigCommand(program) {
10480
10552
  return;
10481
10553
  }
10482
10554
  if (mode === "org") {
10483
- const membership = await selectActiveOrganization(cliConfig, { organizationId: id });
10555
+ const membership = await selectActiveOrganization(cliConfig, { organizationRef: id });
10484
10556
  process.stdout.write(`Using organization ${membership.organization.name}\n`);
10485
10557
  return;
10486
10558
  }
@@ -10490,9 +10562,9 @@ function registerConfigCommand(program) {
10490
10562
  process.stdout.write(activeProjectId ? `Using platform API (default project ${activeProjectId} for project-scoped commands)\n` : "Using platform API\n");
10491
10563
  return;
10492
10564
  }
10493
- if (mode !== "project") throw new Error("Usage: keystroke config use [local|cloud|project [projectId]|org [organizationId]]");
10565
+ if (mode !== "project") throw new Error("Usage: keystroke config use [local|cloud|project [slug]|org [slug]]");
10494
10566
  const targetProjectId = id ?? cliConfig.get("activeProjectId");
10495
- if (!targetProjectId) throw new Error("Usage: keystroke config use project <projectId>");
10567
+ if (!targetProjectId) throw new Error("Usage: keystroke config use project <slug>");
10496
10568
  const result = await runConfigUseProject(cliConfig, targetProjectId);
10497
10569
  process.stdout.write(result.stdout);
10498
10570
  if (result.stderr) process.stderr.write(result.stderr);
@@ -10566,7 +10638,7 @@ async function syncSkills(command) {
10566
10638
  //#endregion
10567
10639
  //#region src/program.ts
10568
10640
  function createProgram() {
10569
- const program = new Command().name("keystroke").description("Build, run, and manage keystroke projects").version(readCliVersion()).option("--project <id>", "Platform project id (deploy target or one-off API target)").option("--local", "Force local keystroke server URL").hook("preAction", async (thisCommand, actionCommand) => {
10641
+ const program = new Command().name("keystroke").description("Build, run, and manage keystroke projects").version(readCliVersion()).option("--project <slug>", "Platform project slug (deploy target or one-off API target)").option("--local", "Force local keystroke server URL").hook("preAction", async (thisCommand, actionCommand) => {
10570
10642
  const opts = thisCommand.opts();
10571
10643
  setCliTargetOptions({
10572
10644
  projectId: opts.project,