@keystrokehq/cli 0.0.129 → 0.0.131

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
@@ -2096,9 +2096,20 @@ const CreateOrganizationRequestSchema = object({
2096
2096
  /** Format-only at the wire boundary; reserved-name checks run in platform-database. */
2097
2097
  slug: OrganizationSlugSchema.optional()
2098
2098
  });
2099
- const UpdateOrganizationRequestSchema = object({ slug: ClaimableOrganizationSlugSchema });
2099
+ const UpdateOrganizationRequestSchema = object({
2100
+ name: string().trim().min(1).optional(),
2101
+ slug: ClaimableOrganizationSlugSchema.optional()
2102
+ }).refine((data) => data.name !== void 0 || data.slug !== void 0, { message: "At least one field is required" });
2100
2103
  const CreateOrganizationResponseSchema = object({ organization: UserOrganizationSchema });
2101
2104
  const ListProjectsResponseSchema = object({ projects: array(ProjectSchema) });
2105
+ const ProjectListMetricsSchema = object({
2106
+ agentCount: number$1(),
2107
+ workflowCount: number$1(),
2108
+ skillCount: number$1(),
2109
+ credentialCount: number$1(),
2110
+ lastDeploymentAt: isoDateTime$3.nullable()
2111
+ });
2112
+ const ListProjectMetricsResponseSchema = object({ metrics: record(string(), ProjectListMetricsSchema) });
2102
2113
  const CreateProjectRequestSchema = object({
2103
2114
  name: string().trim().min(1),
2104
2115
  description: string().trim().min(1).optional()
@@ -2617,6 +2628,29 @@ const PresignUserAvatarResponseSchema = object({
2617
2628
  uploadUrl: url(),
2618
2629
  storageKey: string().min(1)
2619
2630
  });
2631
+ const OrganizationLogoVariantSchema = _enum(["light", "dark"]);
2632
+ const OrganizationSidebarBrandingSchema = object({
2633
+ customLogoEnabled: boolean(),
2634
+ customLogoExplicitlyDisabled: boolean(),
2635
+ logoLightUrl: url().nullable(),
2636
+ logoDarkUrl: url().nullable(),
2637
+ logoHeightPx: number$1().int().min(14).max(32)
2638
+ });
2639
+ const OrganizationSidebarBrandingPatchSchema = object({
2640
+ customLogoEnabled: boolean().optional(),
2641
+ customLogoExplicitlyDisabled: boolean().optional(),
2642
+ logoLightStorageKey: string().min(1).nullable().optional(),
2643
+ logoDarkStorageKey: string().min(1).nullable().optional(),
2644
+ logoHeightPx: number$1().int().min(14).max(32).optional()
2645
+ }).refine((value) => value.customLogoEnabled !== void 0 || value.customLogoExplicitlyDisabled !== void 0 || value.logoLightStorageKey !== void 0 || value.logoDarkStorageKey !== void 0 || value.logoHeightPx !== void 0, { message: "At least one branding field is required" });
2646
+ const PresignOrgLogoRequestSchema = object({
2647
+ variant: OrganizationLogoVariantSchema,
2648
+ contentType: string().trim().min(1)
2649
+ });
2650
+ const PresignOrgLogoResponseSchema = object({
2651
+ uploadUrl: url(),
2652
+ storageKey: string().min(1)
2653
+ });
2620
2654
  const ProjectArtifactStatusSchema = _enum(["pending", "ready"]);
2621
2655
  const isoDateTime$1 = string().datetime();
2622
2656
  const ProjectArtifactSchema = object({
@@ -4998,16 +5032,18 @@ function createHealthResource(http) {
4998
5032
  }
4999
5033
  } };
5000
5034
  }
5001
- const registry = /* @__PURE__ */ new Map();
5002
- /**
5003
- * Wraps a mock implementation, registering it in the manifest. The returned
5004
- * function is the unchanged implementation — `mock()` only adds bookkeeping.
5005
- */
5006
- function mock(entry, impl) {
5007
- registry.set(`${entry.domain}.${entry.method}`, entry);
5008
- return impl;
5009
- }
5010
5035
  function createOrganizationsResource(http, options = {}) {
5036
+ async function update(input) {
5037
+ const body = UpdateOrganizationRequestSchema.parse(input);
5038
+ try {
5039
+ const data = await http.patch("/api/organizations/active", { json: body }).json();
5040
+ const organization = ActiveOrganizationResponseSchema.parse(data).organization;
5041
+ if (!organization) throw new Error("Active organization was not returned");
5042
+ return organization;
5043
+ } catch (error) {
5044
+ throw await toPlatformError(error);
5045
+ }
5046
+ }
5011
5047
  return {
5012
5048
  async list() {
5013
5049
  try {
@@ -5046,17 +5082,7 @@ function createOrganizationsResource(http, options = {}) {
5046
5082
  throw await toPlatformError(error);
5047
5083
  }
5048
5084
  },
5049
- async update(input) {
5050
- const body = UpdateOrganizationRequestSchema.parse(input);
5051
- try {
5052
- const data = await http.patch("/api/organizations/active", { json: body }).json();
5053
- const organization = ActiveOrganizationResponseSchema.parse(data).organization;
5054
- if (!organization) throw new Error("Active organization was not returned");
5055
- return organization;
5056
- } catch (error) {
5057
- throw await toPlatformError(error);
5058
- }
5059
- },
5085
+ update,
5060
5086
  async create(input) {
5061
5087
  const body = CreateOrganizationRequestSchema.parse(input);
5062
5088
  try {
@@ -5066,20 +5092,6 @@ function createOrganizationsResource(http, options = {}) {
5066
5092
  throw await toPlatformError(error);
5067
5093
  }
5068
5094
  },
5069
- updateProfile: mock({
5070
- domain: "organizations",
5071
- method: "updateProfile",
5072
- plannedEndpoint: "PATCH /api/organizations/active (name)"
5073
- }, async (input) => {
5074
- const name = input.name.trim();
5075
- if (!name) throw new Error("Organization name is required");
5076
- const organizationId = options.getActiveOrganizationId?.()?.trim();
5077
- if (!organizationId) throw new Error("Active organization is required to update profile");
5078
- return {
5079
- id: organizationId,
5080
- name
5081
- };
5082
- }),
5083
5095
  async checkSlug(slug, options = {}) {
5084
5096
  const searchParams = new URLSearchParams({ slug });
5085
5097
  if (options.excludeOrganizationId) searchParams.set("excludeOrganizationId", options.excludeOrganizationId);
@@ -5145,6 +5157,16 @@ function createProjectsResource(http) {
5145
5157
  }
5146
5158
  };
5147
5159
  }
5160
+ function createProjectMetricsResource(http) {
5161
+ return { async list() {
5162
+ try {
5163
+ const data = await http.get("/api/projects/metrics").json();
5164
+ return ListProjectMetricsResponseSchema.parse(data).metrics;
5165
+ } catch (error) {
5166
+ throw await toPlatformError(error);
5167
+ }
5168
+ } };
5169
+ }
5148
5170
  function createAgentsResource(http) {
5149
5171
  return {
5150
5172
  async list() {
@@ -5215,13 +5237,22 @@ function createRecentsResource(http) {
5215
5237
  }
5216
5238
  } };
5217
5239
  }
5240
+ const registry = /* @__PURE__ */ new Map();
5241
+ /**
5242
+ * Wraps a mock implementation, registering it in the manifest. The returned
5243
+ * function is the unchanged implementation — `mock()` only adds bookkeeping.
5244
+ */
5245
+ function mock(entry, impl) {
5246
+ registry.set(`${entry.domain}.${entry.method}`, entry);
5247
+ return impl;
5248
+ }
5218
5249
  const DEFAULT_PROJECT_SETTINGS = {
5219
5250
  description: "",
5220
5251
  defaultRole: "editor",
5221
5252
  invitePermission: "admin"
5222
5253
  };
5223
- const MOCK_STORAGE_KEY$3 = "keystroke:mock-project-settings:v1";
5224
- function getBrowserLocalStorage$3() {
5254
+ const MOCK_STORAGE_KEY$2 = "keystroke:mock-project-settings:v1";
5255
+ function getBrowserLocalStorage$2() {
5225
5256
  if (typeof globalThis !== "object") return null;
5226
5257
  return globalThis.localStorage ?? null;
5227
5258
  }
@@ -5247,10 +5278,10 @@ function mergeProjectSettings(current, patch) {
5247
5278
  });
5248
5279
  }
5249
5280
  function readSettingsStore() {
5250
- const localStorage = getBrowserLocalStorage$3();
5281
+ const localStorage = getBrowserLocalStorage$2();
5251
5282
  if (!localStorage) return {};
5252
5283
  try {
5253
- const raw = localStorage.getItem(MOCK_STORAGE_KEY$3);
5284
+ const raw = localStorage.getItem(MOCK_STORAGE_KEY$2);
5254
5285
  if (!raw) return {};
5255
5286
  const parsed = JSON.parse(raw);
5256
5287
  if (!parsed || typeof parsed !== "object") return {};
@@ -5260,10 +5291,10 @@ function readSettingsStore() {
5260
5291
  }
5261
5292
  }
5262
5293
  function writeSettingsStore(store) {
5263
- const localStorage = getBrowserLocalStorage$3();
5294
+ const localStorage = getBrowserLocalStorage$2();
5264
5295
  if (!localStorage) return;
5265
5296
  try {
5266
- localStorage.setItem(MOCK_STORAGE_KEY$3, JSON.stringify(store));
5297
+ localStorage.setItem(MOCK_STORAGE_KEY$2, JSON.stringify(store));
5267
5298
  } catch {}
5268
5299
  }
5269
5300
  function requireProjectId(projectId) {
@@ -6964,13 +6995,6 @@ function createDeploymentsResource() {
6964
6995
  }));
6965
6996
  }) };
6966
6997
  }
6967
- function createProjectMetricsResource() {
6968
- return { list: mock({
6969
- domain: "projectMetrics",
6970
- method: "list",
6971
- plannedEndpoint: "GET /api/projects/metrics"
6972
- }, async () => ({})) };
6973
- }
6974
6998
  const projectMembersStore = /* @__PURE__ */ new Map();
6975
6999
  function getProjectMembers(projectId) {
6976
7000
  let members = projectMembersStore.get(projectId);
@@ -7675,98 +7699,67 @@ function createHistoryResource() {
7675
7699
  }, async (runId) => cancelHistoryRun(runId))
7676
7700
  };
7677
7701
  }
7678
- const MOCK_STORAGE_KEY$2 = "keystroke:mock-organization-sidebar-branding:v1";
7679
- function getBrowserLocalStorage$2() {
7680
- if (typeof globalThis !== "object") return null;
7681
- return globalThis.localStorage ?? null;
7682
- }
7683
- const DEFAULT_ORGANIZATION_SIDEBAR_BRANDING = {
7684
- customSidebarLogoEnabled: false,
7685
- customSidebarLogoExplicitlyDisabled: false,
7686
- sidebarLogoLightDataUrl: null,
7687
- sidebarLogoDarkDataUrl: null,
7688
- sidebarLogoHeightPx: 20
7689
- };
7690
- function normalizeSidebarLogoDataUrl(value) {
7691
- if (typeof value !== "string" || !value.startsWith("data:image/")) return null;
7692
- return value;
7693
- }
7694
- function normalizeCustomSidebarLogoEnabled(value, branding) {
7695
- const hasCustomLogo = Boolean(normalizeSidebarLogoDataUrl(branding.sidebarLogoLightDataUrl) || normalizeSidebarLogoDataUrl(branding.sidebarLogoDarkDataUrl));
7696
- if (branding.customSidebarLogoExplicitlyDisabled === true) return false;
7697
- if (typeof value === "boolean") return value || hasCustomLogo;
7698
- return hasCustomLogo;
7699
- }
7700
- function normalizeSidebarLogoHeightPx(value) {
7701
- if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_ORGANIZATION_SIDEBAR_BRANDING.sidebarLogoHeightPx;
7702
- return Math.min(32, Math.max(14, Math.round(value)));
7703
- }
7704
- function normalizeOrganizationSidebarBranding(value) {
7705
- if (!value || typeof value !== "object") return DEFAULT_ORGANIZATION_SIDEBAR_BRANDING;
7706
- const branding = value;
7707
- return {
7708
- customSidebarLogoEnabled: normalizeCustomSidebarLogoEnabled(branding.customSidebarLogoEnabled, branding),
7709
- customSidebarLogoExplicitlyDisabled: branding.customSidebarLogoExplicitlyDisabled === true,
7710
- sidebarLogoLightDataUrl: normalizeSidebarLogoDataUrl(branding.sidebarLogoLightDataUrl),
7711
- sidebarLogoDarkDataUrl: normalizeSidebarLogoDataUrl(branding.sidebarLogoDarkDataUrl),
7712
- sidebarLogoHeightPx: normalizeSidebarLogoHeightPx(branding.sidebarLogoHeightPx)
7713
- };
7714
- }
7715
- function mergeOrganizationSidebarBranding(current, patch) {
7716
- return normalizeOrganizationSidebarBranding({
7717
- ...current,
7718
- ...patch
7719
- });
7720
- }
7721
- function readBrandingStore() {
7722
- const localStorage = getBrowserLocalStorage$2();
7723
- if (!localStorage) return {};
7702
+ async function presignPutAndFinalize(input) {
7703
+ const contentType = input.file.type?.trim() || "application/octet-stream";
7704
+ const uploadFailedMessage = input.uploadFailedMessage ?? "Upload failed";
7724
7705
  try {
7725
- const raw = localStorage.getItem(MOCK_STORAGE_KEY$2);
7726
- if (!raw) return {};
7727
- const parsed = JSON.parse(raw);
7728
- if (!parsed || typeof parsed !== "object") return {};
7729
- return Object.fromEntries(Object.entries(parsed).map(([organizationId, value]) => [organizationId, normalizeOrganizationSidebarBranding(value)]));
7730
- } catch {
7731
- return {};
7706
+ const { uploadUrl, storageKey } = await input.presign(contentType);
7707
+ if (!(await fetch(uploadUrl, {
7708
+ method: "PUT",
7709
+ body: input.file,
7710
+ headers: { "Content-Type": contentType }
7711
+ })).ok) throw new Error(uploadFailedMessage);
7712
+ return await input.finalize(storageKey);
7713
+ } catch (error) {
7714
+ if (error instanceof Error && error.message === uploadFailedMessage) throw error;
7715
+ throw await input.toPlatformError(error);
7732
7716
  }
7733
7717
  }
7734
- function writeBrandingStore(store) {
7735
- const localStorage = getBrowserLocalStorage$2();
7736
- if (!localStorage) return;
7737
- try {
7738
- localStorage.setItem(MOCK_STORAGE_KEY$2, JSON.stringify(store));
7739
- } catch {}
7740
- }
7741
- function resolveOrganizationId(getActiveOrganizationId) {
7742
- const organizationId = getActiveOrganizationId()?.trim();
7743
- if (!organizationId) throw new Error("Organization sidebar branding requires an active organization");
7744
- return organizationId;
7745
- }
7746
- function createOrganizationSidebarBrandingResource(options) {
7718
+ function createOrganizationSidebarBrandingResource(http) {
7719
+ async function presignLogoUpload(variant, contentType) {
7720
+ const body = PresignOrgLogoRequestSchema.parse({
7721
+ variant,
7722
+ contentType
7723
+ });
7724
+ const data = await http.post("/api/organizations/active/branding/logo/presign", { json: body }).json();
7725
+ return PresignOrgLogoResponseSchema.parse(data);
7726
+ }
7747
7727
  return {
7748
- get: mock({
7749
- domain: "organizationSidebarBranding",
7750
- method: "get",
7751
- plannedEndpoint: "GET /api/organizations/active/branding"
7752
- }, async () => {
7753
- const organizationId = resolveOrganizationId(options.getActiveOrganizationId ?? (() => null));
7754
- return readBrandingStore()[organizationId] ?? DEFAULT_ORGANIZATION_SIDEBAR_BRANDING;
7755
- }),
7756
- update: mock({
7757
- domain: "organizationSidebarBranding",
7758
- method: "update",
7759
- plannedEndpoint: "PATCH /api/organizations/active/branding"
7760
- }, async (patch) => {
7761
- const organizationId = resolveOrganizationId(options.getActiveOrganizationId ?? (() => null));
7762
- const store = readBrandingStore();
7763
- const next = mergeOrganizationSidebarBranding(store[organizationId] ?? DEFAULT_ORGANIZATION_SIDEBAR_BRANDING, patch);
7764
- writeBrandingStore({
7765
- ...store,
7766
- [organizationId]: next
7728
+ async get() {
7729
+ try {
7730
+ const data = await http.get("/api/organizations/active/branding").json();
7731
+ return OrganizationSidebarBrandingSchema.parse(data);
7732
+ } catch (error) {
7733
+ throw await toPlatformError(error);
7734
+ }
7735
+ },
7736
+ async uploadLogo(file, variant) {
7737
+ const storageKeyField = variant === "light" ? "logoLightStorageKey" : "logoDarkStorageKey";
7738
+ return presignPutAndFinalize({
7739
+ file,
7740
+ presign: (contentType) => presignLogoUpload(variant, contentType),
7741
+ finalize: (storageKey) => this.update({
7742
+ [storageKeyField]: storageKey,
7743
+ customLogoEnabled: true,
7744
+ customLogoExplicitlyDisabled: false
7745
+ }),
7746
+ uploadFailedMessage: "Logo upload failed",
7747
+ toPlatformError
7767
7748
  });
7768
- return next;
7769
- })
7749
+ },
7750
+ async removeLogo(variant) {
7751
+ const storageKeyField = variant === "light" ? "logoLightStorageKey" : "logoDarkStorageKey";
7752
+ return this.update({ [storageKeyField]: null });
7753
+ },
7754
+ async update(patch) {
7755
+ const body = OrganizationSidebarBrandingPatchSchema.parse(patch);
7756
+ try {
7757
+ const data = await http.patch("/api/organizations/active/branding", { json: body }).json();
7758
+ return OrganizationSidebarBrandingSchema.parse(data);
7759
+ } catch (error) {
7760
+ throw await toPlatformError(error);
7761
+ }
7762
+ }
7770
7763
  };
7771
7764
  }
7772
7765
  const MOCK_STORAGE_KEY$1 = "keystroke:mock-user-preferences:v1";
@@ -7945,19 +7938,13 @@ function createUserAvatarResource(http) {
7945
7938
  }
7946
7939
  },
7947
7940
  async upload(file) {
7948
- const contentType = file.type?.trim() || "application/octet-stream";
7949
- try {
7950
- const { uploadUrl, storageKey } = await presignUpload(contentType);
7951
- if (!(await fetch(uploadUrl, {
7952
- method: "PUT",
7953
- body: file,
7954
- headers: { "Content-Type": contentType }
7955
- })).ok) throw new Error("Avatar upload failed");
7956
- return await this.update({ storageKey });
7957
- } catch (error) {
7958
- if (error instanceof Error && error.message === "Avatar upload failed") throw error;
7959
- throw await toPlatformError(error);
7960
- }
7941
+ return presignPutAndFinalize({
7942
+ file,
7943
+ presign: presignUpload,
7944
+ finalize: (storageKey) => this.update({ storageKey }),
7945
+ uploadFailedMessage: "Avatar upload failed",
7946
+ toPlatformError
7947
+ });
7961
7948
  },
7962
7949
  async remove() {
7963
7950
  return this.update({ storageKey: null });
@@ -8575,7 +8562,7 @@ function createPlatformClient(options) {
8575
8562
  history: createHistoryResource(),
8576
8563
  deployments: createDeploymentsResource(),
8577
8564
  gatewayAttachments: createGatewayAttachmentsResource(http),
8578
- projectMetrics: createProjectMetricsResource(),
8565
+ projectMetrics: createProjectMetricsResource(http),
8579
8566
  projectFiles: createProjectFilesResource(http),
8580
8567
  channels: createChannelsResource(),
8581
8568
  members: createMembersResource(http),
@@ -8584,7 +8571,7 @@ function createPlatformClient(options) {
8584
8571
  recents: createRecentsResource(http),
8585
8572
  userPreferences: createUserPreferencesResource({ getCurrentUserId: options.getCurrentUserId }),
8586
8573
  userAvatar: createUserAvatarResource(http),
8587
- organizationSidebarBranding: createOrganizationSidebarBrandingResource({ getActiveOrganizationId: () => activeOrganizationId }),
8574
+ organizationSidebarBranding: createOrganizationSidebarBrandingResource(http),
8588
8575
  getActiveOrganizationId: () => activeOrganizationId,
8589
8576
  setActiveOrganizationId
8590
8577
  };
@@ -10159,6 +10146,13 @@ async function runProjectList(config) {
10159
10146
  writeInactiveDeployHints(projects);
10160
10147
  });
10161
10148
  }
10149
+ async function runProjectMetrics(config, options) {
10150
+ await withActivePlatformClient(config, async (platform) => {
10151
+ const metrics = await platform.projectMetrics.list();
10152
+ const output = options.projectId === void 0 ? metrics : Object.fromEntries(Object.entries(metrics).filter(([projectId]) => projectId === options.projectId));
10153
+ process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
10154
+ });
10155
+ }
10162
10156
  async function runProjectCreate(config, input) {
10163
10157
  await withActivePlatformClient(config, async (platform) => {
10164
10158
  const project = await platform.projects.create(input);
@@ -10180,6 +10174,30 @@ async function runProjectDelete(config, projectId) {
10180
10174
  });
10181
10175
  }
10182
10176
  //#endregion
10177
+ //#region src/commands/organization/run-organization.ts
10178
+ async function runOrganizationUpdate(config, input) {
10179
+ await withActivePlatformClient(config, async (platform) => {
10180
+ const organization = await platform.organizations.update(input);
10181
+ process.stdout.write(`${JSON.stringify({ organization }, null, 2)}\n`);
10182
+ });
10183
+ }
10184
+ //#endregion
10185
+ //#region src/commands/organization/index.ts
10186
+ function registerOrganizationCommand(program) {
10187
+ program.command("organization").description("Manage the active organization").command("update").description("Update the active organization's display name").requiredOption("--name <name>", "Organization display name").action(async (options) => {
10188
+ const config = createCliConfig();
10189
+ try {
10190
+ await runOrganizationUpdate(config, { name: options.name });
10191
+ } catch (error) {
10192
+ process.stderr.write(`${formatCliError(error, "Update organization failed", {
10193
+ serverUrl: getPlatformUrl(config),
10194
+ webUrl: getWebUrl(config)
10195
+ })}\n`);
10196
+ process.exitCode = 1;
10197
+ }
10198
+ });
10199
+ }
10200
+ //#endregion
10183
10201
  //#region src/commands/project/index.ts
10184
10202
  function registerProjectCommand(program) {
10185
10203
  const project = program.command("project").description("List, create, update, and delete platform projects");
@@ -10195,6 +10213,18 @@ function registerProjectCommand(program) {
10195
10213
  process.exitCode = 1;
10196
10214
  }
10197
10215
  });
10216
+ 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) => {
10217
+ const config = createCliConfig();
10218
+ try {
10219
+ await runProjectMetrics(config, { ...options.project ? { projectId: options.project } : {} });
10220
+ } catch (error) {
10221
+ process.stderr.write(`${formatCliError(error, "List project metrics failed", {
10222
+ serverUrl: getPlatformUrl(config),
10223
+ webUrl: getWebUrl(config)
10224
+ })}\n`);
10225
+ process.exitCode = 1;
10226
+ }
10227
+ });
10198
10228
  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) => {
10199
10229
  const config = createCliConfig();
10200
10230
  try {
@@ -10540,6 +10570,7 @@ function createProgram() {
10540
10570
  registerAuthCommand(program);
10541
10571
  registerConnectCommand(program);
10542
10572
  registerConfigCommand(program);
10573
+ registerOrganizationCommand(program);
10543
10574
  registerProjectCommand(program);
10544
10575
  registerCredentialsCommand(program);
10545
10576
  registerHealthCommand(program);