@keystrokehq/cli 0.0.131 → 0.0.133
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 +156 -90
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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). */
|
|
@@ -2053,6 +2055,17 @@ object({
|
|
|
2053
2055
|
slug: string().trim().min(1),
|
|
2054
2056
|
excludeOrganizationId: string().min(1).optional()
|
|
2055
2057
|
});
|
|
2058
|
+
const ProjectSlugAvailabilityReasonSchema = _enum(["taken", "invalid"]);
|
|
2059
|
+
const ProjectSlugAvailabilityResponseSchema = object({
|
|
2060
|
+
slug: string(),
|
|
2061
|
+
available: boolean(),
|
|
2062
|
+
reason: ProjectSlugAvailabilityReasonSchema.optional(),
|
|
2063
|
+
suggestion: ProjectSlugSchema.optional()
|
|
2064
|
+
});
|
|
2065
|
+
object({
|
|
2066
|
+
slug: string().trim().min(1),
|
|
2067
|
+
excludeProjectId: string().min(1).optional()
|
|
2068
|
+
});
|
|
2056
2069
|
const ProjectStatusSchema = _enum([
|
|
2057
2070
|
"inactive",
|
|
2058
2071
|
"starting",
|
|
@@ -2076,6 +2089,7 @@ const ProjectSchema = object({
|
|
|
2076
2089
|
id: string(),
|
|
2077
2090
|
organizationId: string(),
|
|
2078
2091
|
name: string(),
|
|
2092
|
+
slug: ProjectSlugSchema,
|
|
2079
2093
|
description: string().nullable(),
|
|
2080
2094
|
status: ProjectStatusSchema,
|
|
2081
2095
|
baseUrl: string().nullable(),
|
|
@@ -2116,8 +2130,9 @@ const CreateProjectRequestSchema = object({
|
|
|
2116
2130
|
});
|
|
2117
2131
|
const UpdateProjectRequestSchema = object({
|
|
2118
2132
|
name: string().trim().min(1).optional(),
|
|
2119
|
-
description: string().trim().optional()
|
|
2120
|
-
|
|
2133
|
+
description: string().trim().optional(),
|
|
2134
|
+
slug: ProjectSlugSchema.optional()
|
|
2135
|
+
}).refine((data) => data.name !== void 0 || data.description !== void 0 || data.slug !== void 0, { message: "At least one field is required" });
|
|
2121
2136
|
const CreateProjectResponseSchema = object({ project: ProjectSchema });
|
|
2122
2137
|
const ProjectResponseSchema = object({ project: ProjectSchema });
|
|
2123
2138
|
const ProjectReachabilityResponseSchema = object({ reachable: boolean() });
|
|
@@ -2670,6 +2685,16 @@ const CompleteProjectArtifactResponseSchema = object({
|
|
|
2670
2685
|
artifact: ProjectArtifactSchema,
|
|
2671
2686
|
project: ProjectSchema
|
|
2672
2687
|
});
|
|
2688
|
+
const ListProjectDeploymentsResponseSchema = object({ deployments: array(object({
|
|
2689
|
+
id: string(),
|
|
2690
|
+
projectId: string(),
|
|
2691
|
+
version: number$1().int().positive(),
|
|
2692
|
+
deployedBy: string().nullable(),
|
|
2693
|
+
deployedByEmail: string().optional(),
|
|
2694
|
+
deployedByAvatarUrl: string().optional(),
|
|
2695
|
+
createdAt: isoDateTime$1,
|
|
2696
|
+
active: boolean()
|
|
2697
|
+
})) });
|
|
2673
2698
|
const optionalCount = number$1().int().nonnegative().nullable().optional();
|
|
2674
2699
|
const optionalTimestamp = string().nullable().optional();
|
|
2675
2700
|
const AgentSummarySchema = object({
|
|
@@ -4979,6 +5004,16 @@ function createArtifactsResource(http) {
|
|
|
4979
5004
|
}
|
|
4980
5005
|
};
|
|
4981
5006
|
}
|
|
5007
|
+
function createDeploymentsResource(http) {
|
|
5008
|
+
return { async listForProject(projectId) {
|
|
5009
|
+
try {
|
|
5010
|
+
const data = await http.get(`/api/projects/${encodeURIComponent(projectId)}/deployments`).json();
|
|
5011
|
+
return ListProjectDeploymentsResponseSchema.parse(data).deployments;
|
|
5012
|
+
} catch (error) {
|
|
5013
|
+
throw await toPlatformError(error);
|
|
5014
|
+
}
|
|
5015
|
+
} };
|
|
5016
|
+
}
|
|
4982
5017
|
function createGatewayAttachmentsResource(http) {
|
|
4983
5018
|
const projectPrefix = (projectId) => `/api/projects/${encodeURIComponent(projectId)}/gateways/slack/attachments`;
|
|
4984
5019
|
return {
|
|
@@ -5154,6 +5189,16 @@ function createProjectsResource(http) {
|
|
|
5154
5189
|
} catch (error) {
|
|
5155
5190
|
throw await toPlatformError(error);
|
|
5156
5191
|
}
|
|
5192
|
+
},
|
|
5193
|
+
async checkSlug(slug, options = {}) {
|
|
5194
|
+
const searchParams = new URLSearchParams({ slug });
|
|
5195
|
+
if (options.excludeProjectId) searchParams.set("excludeProjectId", options.excludeProjectId);
|
|
5196
|
+
try {
|
|
5197
|
+
const data = await http.get(`/api/projects/slug-available?${searchParams}`).json();
|
|
5198
|
+
return ProjectSlugAvailabilityResponseSchema.parse(data);
|
|
5199
|
+
} catch (error) {
|
|
5200
|
+
throw await toPlatformError(error);
|
|
5201
|
+
}
|
|
5157
5202
|
}
|
|
5158
5203
|
};
|
|
5159
5204
|
}
|
|
@@ -6774,27 +6819,6 @@ const workflowSeed = [{
|
|
|
6774
6819
|
stepCount: 4,
|
|
6775
6820
|
lastRunAt: "2026-05-31T16:40:00Z"
|
|
6776
6821
|
}];
|
|
6777
|
-
const deploymentSeed = [{
|
|
6778
|
-
id: "deployment-v002",
|
|
6779
|
-
projectId: "project_personal_repo",
|
|
6780
|
-
version: "v0.0.2",
|
|
6781
|
-
commit: "0842e21",
|
|
6782
|
-
commitSha: "0842e21",
|
|
6783
|
-
deployedBy: "Dallin Bentley",
|
|
6784
|
-
deployedByEmail: "dallin@keystroke.ai",
|
|
6785
|
-
createdAt: "2026-04-20T15:44:00Z",
|
|
6786
|
-
active: true
|
|
6787
|
-
}, {
|
|
6788
|
-
id: "deployment-v001",
|
|
6789
|
-
projectId: "project_personal_repo",
|
|
6790
|
-
version: "v0.0.1",
|
|
6791
|
-
commit: "5aeb762",
|
|
6792
|
-
commitSha: "5aeb762",
|
|
6793
|
-
deployedBy: "Dallin Bentley",
|
|
6794
|
-
deployedByEmail: "dallin@keystroke.ai",
|
|
6795
|
-
createdAt: "2026-04-18T15:44:00Z",
|
|
6796
|
-
active: false
|
|
6797
|
-
}];
|
|
6798
6822
|
/** Review personas for mocked project members and API key authors (not org membership). */
|
|
6799
6823
|
const mockPersonaSeed = [
|
|
6800
6824
|
{
|
|
@@ -6983,18 +7007,6 @@ function maskSecretPreview(value) {
|
|
|
6983
7007
|
if (trimmed.length <= 4) return "•".repeat(Math.max(trimmed.length, 4));
|
|
6984
7008
|
return `${"•".repeat(Math.min(trimmed.length - 4, 20))}${trimmed.slice(-4)}`;
|
|
6985
7009
|
}
|
|
6986
|
-
function createDeploymentsResource() {
|
|
6987
|
-
return { listForProject: mock({
|
|
6988
|
-
domain: "deployments",
|
|
6989
|
-
method: "listForProject",
|
|
6990
|
-
plannedEndpoint: "GET /api/projects/:id/deployments"
|
|
6991
|
-
}, async (projectId) => {
|
|
6992
|
-
return deploymentSeed.map((deployment) => ({
|
|
6993
|
-
...deployment,
|
|
6994
|
-
projectId
|
|
6995
|
-
}));
|
|
6996
|
-
}) };
|
|
6997
|
-
}
|
|
6998
7010
|
const projectMembersStore = /* @__PURE__ */ new Map();
|
|
6999
7011
|
function getProjectMembers(projectId) {
|
|
7000
7012
|
let members = projectMembersStore.get(projectId);
|
|
@@ -8560,7 +8572,7 @@ function createPlatformClient(options) {
|
|
|
8560
8572
|
apps: createAppsResource(),
|
|
8561
8573
|
credentials: createCredentialsResource(),
|
|
8562
8574
|
history: createHistoryResource(),
|
|
8563
|
-
deployments: createDeploymentsResource(),
|
|
8575
|
+
deployments: createDeploymentsResource(http),
|
|
8564
8576
|
gatewayAttachments: createGatewayAttachmentsResource(http),
|
|
8565
8577
|
projectMetrics: createProjectMetricsResource(http),
|
|
8566
8578
|
projectFiles: createProjectFilesResource(http),
|
|
@@ -8600,6 +8612,33 @@ function createCliPlatformClient(config) {
|
|
|
8600
8612
|
});
|
|
8601
8613
|
}
|
|
8602
8614
|
//#endregion
|
|
8615
|
+
//#region src/resolve-resource-ref.ts
|
|
8616
|
+
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;
|
|
8617
|
+
function looksLikeUuid(value) {
|
|
8618
|
+
return UUID_PATTERN.test(value);
|
|
8619
|
+
}
|
|
8620
|
+
function formatProjectLabel(project) {
|
|
8621
|
+
return project.slug;
|
|
8622
|
+
}
|
|
8623
|
+
async function resolveProjectRef(platform, ref) {
|
|
8624
|
+
if (looksLikeUuid(ref)) try {
|
|
8625
|
+
return await platform.projects.get(ref);
|
|
8626
|
+
} catch (error) {
|
|
8627
|
+
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>\`.`);
|
|
8628
|
+
throw error;
|
|
8629
|
+
}
|
|
8630
|
+
const match = (await platform.projects.list()).find((project) => project.slug === ref);
|
|
8631
|
+
if (!match) throw new Error(`Project "${ref}" not found in the active organization. Run \`keystroke project list\`.`);
|
|
8632
|
+
return match;
|
|
8633
|
+
}
|
|
8634
|
+
function resolveOrganizationRef(organizations, ref) {
|
|
8635
|
+
const byId = organizations.find((entry) => entry.organization.id === ref);
|
|
8636
|
+
if (byId) return byId;
|
|
8637
|
+
const bySlug = organizations.find((entry) => entry.organization.slug === ref);
|
|
8638
|
+
if (bySlug) return bySlug;
|
|
8639
|
+
throw new Error(`Organization "${ref}" was not found or you do not have access`);
|
|
8640
|
+
}
|
|
8641
|
+
//#endregion
|
|
8603
8642
|
//#region src/organization/active-organization.ts
|
|
8604
8643
|
function formatOrganization(membership) {
|
|
8605
8644
|
return `${membership.organization.name} (${membership.role})`;
|
|
@@ -8613,15 +8652,11 @@ async function activateOrganization(config, organizationId) {
|
|
|
8613
8652
|
platform.setActiveOrganizationId(membership.organization.id);
|
|
8614
8653
|
return membership;
|
|
8615
8654
|
}
|
|
8616
|
-
async function pickOrganization(config, organizations,
|
|
8617
|
-
if (
|
|
8618
|
-
const membership = organizations.find((entry) => entry.organization.id === organizationId);
|
|
8619
|
-
if (!membership) throw new Error(`Organization ${organizationId} was not found or you do not have access`);
|
|
8620
|
-
return activateOrganization(config, membership.organization.id);
|
|
8621
|
-
}
|
|
8655
|
+
async function pickOrganization(config, organizations, organizationRef) {
|
|
8656
|
+
if (organizationRef) return activateOrganization(config, resolveOrganizationRef(organizations, organizationRef).organization.id);
|
|
8622
8657
|
if (!process.stdin.isTTY) {
|
|
8623
|
-
const names = organizations.map((entry) => entry.organization.
|
|
8624
|
-
throw new Error(`Multiple organizations found. Run \`keystroke config use org <
|
|
8658
|
+
const names = organizations.map((entry) => entry.organization.slug).join(", ");
|
|
8659
|
+
throw new Error(`Multiple organizations found. Run \`keystroke config use org <slug>\`. Options: ${names}`);
|
|
8625
8660
|
}
|
|
8626
8661
|
return activateOrganization(config, await select({
|
|
8627
8662
|
message: "Select an organization",
|
|
@@ -8635,7 +8670,7 @@ async function selectActiveOrganization(config, options = {}) {
|
|
|
8635
8670
|
const organizations = await listOrganizations(config);
|
|
8636
8671
|
if (organizations.length === 0) throw new Error(`No organization found — create one at ${getWebUrl(config)}`);
|
|
8637
8672
|
if (organizations.length === 1) return activateOrganization(config, organizations[0].organization.id);
|
|
8638
|
-
return pickOrganization(config, organizations, options.organizationId);
|
|
8673
|
+
return pickOrganization(config, organizations, options.organizationRef ?? options.organizationId);
|
|
8639
8674
|
}
|
|
8640
8675
|
async function syncActiveOrganizationAfterLogin(config, options = {}) {
|
|
8641
8676
|
const organizations = await listOrganizations(config);
|
|
@@ -8651,8 +8686,8 @@ async function syncActiveOrganizationAfterLogin(config, options = {}) {
|
|
|
8651
8686
|
process.stdout.write(`Using organization ${membership.organization.name}\n`);
|
|
8652
8687
|
return membership;
|
|
8653
8688
|
}
|
|
8654
|
-
if (options.organizationId) {
|
|
8655
|
-
const membership = await pickOrganization(config, organizations, options.organizationId);
|
|
8689
|
+
if (options.organizationRef ?? options.organizationId) {
|
|
8690
|
+
const membership = await pickOrganization(config, organizations, options.organizationRef ?? options.organizationId);
|
|
8656
8691
|
process.stdout.write(`Using organization ${membership.organization.name}\n`);
|
|
8657
8692
|
return membership;
|
|
8658
8693
|
}
|
|
@@ -8725,19 +8760,19 @@ function resolveOrgPlatformTarget(config) {
|
|
|
8725
8760
|
mode: "platform"
|
|
8726
8761
|
};
|
|
8727
8762
|
}
|
|
8728
|
-
async function resolvePlatformTarget(config,
|
|
8763
|
+
async function resolvePlatformTarget(config, projectRef) {
|
|
8729
8764
|
const platform = createCliPlatformClient(config);
|
|
8730
8765
|
let project;
|
|
8731
8766
|
try {
|
|
8732
|
-
project = await platform
|
|
8767
|
+
project = await resolveProjectRef(platform, projectRef);
|
|
8733
8768
|
} catch (error) {
|
|
8734
|
-
if (error instanceof
|
|
8769
|
+
if (error instanceof Error) throw error;
|
|
8735
8770
|
throw error;
|
|
8736
8771
|
}
|
|
8737
|
-
if (project.status !== "active") throw new Error(`Project ${
|
|
8772
|
+
if (project.status !== "active") throw new Error(`Project ${formatProjectLabel(project)} is not active (${project.status})`);
|
|
8738
8773
|
return {
|
|
8739
|
-
baseUrl: `${getPlatformUrl(config).replace(/\/+$/, "")}/api/projects/${encodeURIComponent(
|
|
8740
|
-
projectId,
|
|
8774
|
+
baseUrl: `${getPlatformUrl(config).replace(/\/+$/, "")}/api/projects/${encodeURIComponent(project.id)}`,
|
|
8775
|
+
projectId: project.id,
|
|
8741
8776
|
mode: "platform"
|
|
8742
8777
|
};
|
|
8743
8778
|
}
|
|
@@ -8755,9 +8790,9 @@ async function resolveApiTarget(config, options = {}) {
|
|
|
8755
8790
|
if (options.projectId) return resolvePlatformTarget(config, options.projectId);
|
|
8756
8791
|
if (getEffectiveApiTarget(config) === "local") return resolveLocalTarget(config);
|
|
8757
8792
|
if (!options.projectScoped) return resolveOrgPlatformTarget(config);
|
|
8758
|
-
const
|
|
8759
|
-
if (!
|
|
8760
|
-
return resolvePlatformTarget(config,
|
|
8793
|
+
const projectRef = options.projectId ?? config.get("activeProjectId");
|
|
8794
|
+
if (!projectRef) throw new Error("No project selected. Pass `--project <slug>` or run `keystroke config use project <slug>`.");
|
|
8795
|
+
return resolvePlatformTarget(config, projectRef);
|
|
8761
8796
|
}
|
|
8762
8797
|
//#endregion
|
|
8763
8798
|
//#region src/target-options.ts
|
|
@@ -8768,9 +8803,6 @@ function setCliTargetOptions(options) {
|
|
|
8768
8803
|
function getCliTargetOptions() {
|
|
8769
8804
|
return targetOptions;
|
|
8770
8805
|
}
|
|
8771
|
-
function resolveActiveProjectId(config) {
|
|
8772
|
-
return getCliTargetOptions().projectId ?? config.get("activeProjectId");
|
|
8773
|
-
}
|
|
8774
8806
|
//#endregion
|
|
8775
8807
|
//#region src/errors/example-input.ts
|
|
8776
8808
|
function exampleValueFromValidationMessage(message) {
|
|
@@ -9245,7 +9277,7 @@ function resolveAuthLoginTargets(options, config) {
|
|
|
9245
9277
|
//#endregion
|
|
9246
9278
|
//#region src/commands/auth/login.ts
|
|
9247
9279
|
function registerAuthLoginCommand(auth) {
|
|
9248
|
-
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 <
|
|
9280
|
+
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) => {
|
|
9249
9281
|
const config = createCliConfig();
|
|
9250
9282
|
const { webUrl, platformUrl } = resolveAuthLoginTargets(options, config);
|
|
9251
9283
|
config.set("webUrl", webUrl);
|
|
@@ -9260,7 +9292,7 @@ function registerAuthLoginCommand(auth) {
|
|
|
9260
9292
|
const user = await getSessionWithBearer(platformUrl, result.accessToken);
|
|
9261
9293
|
if (user) process.stdout.write(`Logged in as ${user.name} (${user.email})\n`);
|
|
9262
9294
|
else process.stdout.write(`Logged in to ${webUrl}\n`);
|
|
9263
|
-
await syncActiveOrganizationAfterLogin(config, {
|
|
9295
|
+
await syncActiveOrganizationAfterLogin(config, { organizationRef: options.org });
|
|
9264
9296
|
} catch (error) {
|
|
9265
9297
|
process.stderr.write(`${formatCliError(error, "Login failed", {
|
|
9266
9298
|
webUrl,
|
|
@@ -9480,6 +9512,18 @@ function collectProjectSource(root) {
|
|
|
9480
9512
|
};
|
|
9481
9513
|
}
|
|
9482
9514
|
//#endregion
|
|
9515
|
+
//#region src/resolve-project-target.ts
|
|
9516
|
+
async function resolveActiveProject(config, platform) {
|
|
9517
|
+
const ref = getCliTargetOptions().projectId ?? config.get("activeProjectId");
|
|
9518
|
+
if (!ref) return;
|
|
9519
|
+
const client = platform ?? createCliPlatformClient(config);
|
|
9520
|
+
if (!getCliTargetOptions().projectId && looksLikeUuid(ref)) return resolveProjectRef(client, ref);
|
|
9521
|
+
return resolveProjectRef(client, ref);
|
|
9522
|
+
}
|
|
9523
|
+
async function resolveActiveProjectId(config) {
|
|
9524
|
+
return (await resolveActiveProject(config))?.id;
|
|
9525
|
+
}
|
|
9526
|
+
//#endregion
|
|
9483
9527
|
//#region src/commands/deploy.ts
|
|
9484
9528
|
const POLL_INTERVAL_MS = 2e3;
|
|
9485
9529
|
const DEPLOY_TIMEOUT_MS = 12e4;
|
|
@@ -9570,8 +9614,8 @@ async function runDeploy(options) {
|
|
|
9570
9614
|
function registerDeployCommand(program) {
|
|
9571
9615
|
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) => {
|
|
9572
9616
|
try {
|
|
9573
|
-
const projectId = resolveActiveProjectId(createCliConfig());
|
|
9574
|
-
if (!projectId) throw new Error("Usage: keystroke deploy --project <
|
|
9617
|
+
const projectId = await resolveActiveProjectId(createCliConfig());
|
|
9618
|
+
if (!projectId) throw new Error("Usage: keystroke deploy --project <slug>");
|
|
9575
9619
|
await runDeploy({
|
|
9576
9620
|
dir: options.dir,
|
|
9577
9621
|
projectId,
|
|
@@ -10131,7 +10175,7 @@ function writeInactiveDeployHints(projects) {
|
|
|
10131
10175
|
const bin = cliBinaryName();
|
|
10132
10176
|
for (const project of projects) {
|
|
10133
10177
|
if (project.status !== "inactive") continue;
|
|
10134
|
-
process.stderr.write(`Project ${project
|
|
10178
|
+
process.stderr.write(`Project ${formatProjectLabel(project)} is inactive. Deploy with: ${bin} deploy --project ${formatProjectLabel(project)}\n`);
|
|
10135
10179
|
}
|
|
10136
10180
|
}
|
|
10137
10181
|
async function runProjectList(config) {
|
|
@@ -10149,10 +10193,22 @@ async function runProjectList(config) {
|
|
|
10149
10193
|
async function runProjectMetrics(config, options) {
|
|
10150
10194
|
await withActivePlatformClient(config, async (platform) => {
|
|
10151
10195
|
const metrics = await platform.projectMetrics.list();
|
|
10152
|
-
|
|
10196
|
+
if (options.projectRef === void 0) {
|
|
10197
|
+
process.stdout.write(`${JSON.stringify(metrics, null, 2)}\n`);
|
|
10198
|
+
return;
|
|
10199
|
+
}
|
|
10200
|
+
const project = await resolveProjectRef(platform, options.projectRef);
|
|
10201
|
+
const output = Object.fromEntries(Object.entries(metrics).filter(([projectId]) => projectId === project.id));
|
|
10153
10202
|
process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
|
|
10154
10203
|
});
|
|
10155
10204
|
}
|
|
10205
|
+
async function runProjectDeploymentsList(config, options) {
|
|
10206
|
+
await withActivePlatformClient(config, async (platform) => {
|
|
10207
|
+
const project = await resolveProjectRef(platform, options.projectRef);
|
|
10208
|
+
const deployments = await platform.deployments.listForProject(project.id);
|
|
10209
|
+
process.stdout.write(`${JSON.stringify({ deployments }, null, 2)}\n`);
|
|
10210
|
+
});
|
|
10211
|
+
}
|
|
10156
10212
|
async function runProjectCreate(config, input) {
|
|
10157
10213
|
await withActivePlatformClient(config, async (platform) => {
|
|
10158
10214
|
const project = await platform.projects.create(input);
|
|
@@ -10213,10 +10269,10 @@ function registerProjectCommand(program) {
|
|
|
10213
10269
|
process.exitCode = 1;
|
|
10214
10270
|
}
|
|
10215
10271
|
});
|
|
10216
|
-
project.command("metrics").description("List rollup metrics for projects in the active organization").option("--project <
|
|
10272
|
+
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) => {
|
|
10217
10273
|
const config = createCliConfig();
|
|
10218
10274
|
try {
|
|
10219
|
-
await runProjectMetrics(config, { ...options.project ? {
|
|
10275
|
+
await runProjectMetrics(config, { ...options.project ? { projectRef: options.project } : {} });
|
|
10220
10276
|
} catch (error) {
|
|
10221
10277
|
process.stderr.write(`${formatCliError(error, "List project metrics failed", {
|
|
10222
10278
|
serverUrl: getPlatformUrl(config),
|
|
@@ -10225,6 +10281,20 @@ function registerProjectCommand(program) {
|
|
|
10225
10281
|
process.exitCode = 1;
|
|
10226
10282
|
}
|
|
10227
10283
|
});
|
|
10284
|
+
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) => {
|
|
10285
|
+
const config = createCliConfig();
|
|
10286
|
+
try {
|
|
10287
|
+
const projectRef = options.project ?? getCliTargetOptions().projectId ?? config.get("activeProjectId");
|
|
10288
|
+
if (!projectRef) throw new Error("Usage: keystroke project deployments list --project <slug>");
|
|
10289
|
+
await runProjectDeploymentsList(config, { projectRef });
|
|
10290
|
+
} catch (error) {
|
|
10291
|
+
process.stderr.write(`${formatCliError(error, "List deployments failed", {
|
|
10292
|
+
serverUrl: getPlatformUrl(config),
|
|
10293
|
+
webUrl: getWebUrl(config)
|
|
10294
|
+
})}\n`);
|
|
10295
|
+
process.exitCode = 1;
|
|
10296
|
+
}
|
|
10297
|
+
});
|
|
10228
10298
|
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) => {
|
|
10229
10299
|
const config = createCliConfig();
|
|
10230
10300
|
try {
|
|
@@ -10243,8 +10313,8 @@ function registerProjectCommand(program) {
|
|
|
10243
10313
|
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) => {
|
|
10244
10314
|
const config = createCliConfig();
|
|
10245
10315
|
try {
|
|
10246
|
-
const projectId = resolveActiveProjectId(config);
|
|
10247
|
-
if (!projectId) throw new Error("Usage: keystroke --project <
|
|
10316
|
+
const projectId = await resolveActiveProjectId(config);
|
|
10317
|
+
if (!projectId) throw new Error("Usage: keystroke --project <slug> project update --name <name>");
|
|
10248
10318
|
if (!options.name && options.description === void 0) throw new Error("At least one of --name or --description is required");
|
|
10249
10319
|
await runProjectUpdate(config, {
|
|
10250
10320
|
projectId,
|
|
@@ -10262,8 +10332,8 @@ function registerProjectCommand(program) {
|
|
|
10262
10332
|
project.command("delete").description("Delete a platform project").option("-y, --yes", "Skip confirmation prompt").action(async (options) => {
|
|
10263
10333
|
const config = createCliConfig();
|
|
10264
10334
|
try {
|
|
10265
|
-
const projectId = resolveActiveProjectId(config);
|
|
10266
|
-
if (!projectId) throw new Error("Usage: keystroke --project <
|
|
10335
|
+
const projectId = await resolveActiveProjectId(config);
|
|
10336
|
+
if (!projectId) throw new Error("Usage: keystroke --project <slug> project delete");
|
|
10267
10337
|
if (!options.yes) throw new Error("Pass --yes to confirm project deletion");
|
|
10268
10338
|
await runProjectDelete(config, projectId);
|
|
10269
10339
|
} catch (error) {
|
|
@@ -10424,27 +10494,23 @@ function registerWorkflowCommand(program) {
|
|
|
10424
10494
|
}
|
|
10425
10495
|
//#endregion
|
|
10426
10496
|
//#region src/commands/config/use-project.ts
|
|
10427
|
-
async function fetchProjectInOrg(config,
|
|
10428
|
-
|
|
10429
|
-
try {
|
|
10430
|
-
return await platform.projects.get(projectId);
|
|
10431
|
-
} catch (error) {
|
|
10432
|
-
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>\`.`);
|
|
10433
|
-
throw error;
|
|
10434
|
-
}
|
|
10497
|
+
async function fetchProjectInOrg(config, projectRef) {
|
|
10498
|
+
return resolveProjectRef(createCliPlatformClient(config), projectRef);
|
|
10435
10499
|
}
|
|
10436
10500
|
function assertProjectReadyForConfig(project) {
|
|
10437
|
-
|
|
10438
|
-
if (project.status === "
|
|
10501
|
+
const label = formatProjectLabel(project);
|
|
10502
|
+
if (project.status === "failed") throw new Error(project.lastError ? `Project ${label} deploy failed: ${project.lastError}` : `Project ${label} deploy failed`);
|
|
10503
|
+
if (project.status === "starting") throw new Error(`Project ${label} is still starting. Wait for deploy to finish, then run \`keystroke config use project ${label}\`.`);
|
|
10439
10504
|
}
|
|
10440
|
-
async function runConfigUseProject(config,
|
|
10441
|
-
const project = await fetchProjectInOrg(config,
|
|
10505
|
+
async function runConfigUseProject(config, projectRef) {
|
|
10506
|
+
const project = await fetchProjectInOrg(config, projectRef);
|
|
10442
10507
|
assertProjectReadyForConfig(project);
|
|
10443
|
-
config.set("activeProjectId",
|
|
10508
|
+
config.set("activeProjectId", project.id);
|
|
10444
10509
|
config.set("apiTarget", "platform");
|
|
10510
|
+
const label = formatProjectLabel(project);
|
|
10445
10511
|
return {
|
|
10446
|
-
stdout: project.status === "active" ? `Using platform project ${
|
|
10447
|
-
stderr: project.status === "inactive" ? `Project is not active yet. Deploy with: ${cliBinaryName()} deploy --project ${
|
|
10512
|
+
stdout: project.status === "active" ? `Using platform project ${label}\n` : `Using platform project ${label} (${project.status})\n`,
|
|
10513
|
+
stderr: project.status === "inactive" ? `Project is not active yet. Deploy with: ${cliBinaryName()} deploy --project ${label}\n` : void 0
|
|
10448
10514
|
};
|
|
10449
10515
|
}
|
|
10450
10516
|
//#endregion
|
|
@@ -10473,7 +10539,7 @@ function registerConfigCommand(program) {
|
|
|
10473
10539
|
return;
|
|
10474
10540
|
}
|
|
10475
10541
|
if (mode === "org") {
|
|
10476
|
-
const membership = await selectActiveOrganization(cliConfig, {
|
|
10542
|
+
const membership = await selectActiveOrganization(cliConfig, { organizationRef: id });
|
|
10477
10543
|
process.stdout.write(`Using organization ${membership.organization.name}\n`);
|
|
10478
10544
|
return;
|
|
10479
10545
|
}
|
|
@@ -10483,9 +10549,9 @@ function registerConfigCommand(program) {
|
|
|
10483
10549
|
process.stdout.write(activeProjectId ? `Using platform API (default project ${activeProjectId} for project-scoped commands)\n` : "Using platform API\n");
|
|
10484
10550
|
return;
|
|
10485
10551
|
}
|
|
10486
|
-
if (mode !== "project") throw new Error("Usage: keystroke config use [local|cloud|project [
|
|
10552
|
+
if (mode !== "project") throw new Error("Usage: keystroke config use [local|cloud|project [slug]|org [slug]]");
|
|
10487
10553
|
const targetProjectId = id ?? cliConfig.get("activeProjectId");
|
|
10488
|
-
if (!targetProjectId) throw new Error("Usage: keystroke config use project <
|
|
10554
|
+
if (!targetProjectId) throw new Error("Usage: keystroke config use project <slug>");
|
|
10489
10555
|
const result = await runConfigUseProject(cliConfig, targetProjectId);
|
|
10490
10556
|
process.stdout.write(result.stdout);
|
|
10491
10557
|
if (result.stderr) process.stderr.write(result.stderr);
|
|
@@ -10559,7 +10625,7 @@ async function syncSkills(command) {
|
|
|
10559
10625
|
//#endregion
|
|
10560
10626
|
//#region src/program.ts
|
|
10561
10627
|
function createProgram() {
|
|
10562
|
-
const program = new Command().name("keystroke").description("Build, run, and manage keystroke projects").version(readCliVersion()).option("--project <
|
|
10628
|
+
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) => {
|
|
10563
10629
|
const opts = thisCommand.opts();
|
|
10564
10630
|
setCliTargetOptions({
|
|
10565
10631
|
projectId: opts.project,
|