@vibeshiphq/cli 0.1.0 → 0.2.1

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/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # VibeShip CLI
2
2
 
3
- Public CLI for initializing the private VibeShip starter and installing VibeShip Pilot for guided setup.
3
+ Public CLI for initializing the private VibeShip starter and installing VibeShip Hyperdrive for guided setup.
4
4
 
5
5
  ```bash
6
6
  npm install -g @vibeshiphq/cli
7
7
  vibeship login
8
8
  vibeship init my-app
9
9
  cd my-app
10
- vibeship pilot install
10
+ vibeship hyperdrive install
11
11
  ```
12
12
 
13
- The CLI does not embed proprietary setup runbooks. It authenticates the user, checks starter/Pilot entitlement through VibeShip, clones the private starter, and writes local Codex MCP config for Pilot.
13
+ The CLI does not embed proprietary setup runbooks. It authenticates the user, checks starter/Hyperdrive entitlement through VibeShip, clones the private starter, and writes local Codex MCP config for Hyperdrive.
14
14
 
15
15
  ## Commands
16
16
 
@@ -18,15 +18,20 @@ The CLI does not embed proprietary setup runbooks. It authenticates the user, ch
18
18
  vibeship login # Authenticate this machine with VibeShip
19
19
  vibeship logout # Remove local CLI auth
20
20
  vibeship whoami # Show account and entitlement status
21
- vibeship doctor # Inspect auth, project, and Pilot config
21
+ vibeship doctor # Inspect auth, project, and Hyperdrive config
22
22
  vibeship init [targetDir] # Clone the private starter into a new app
23
- vibeship pilot install # Install Pilot MCP config into .codex/config.toml
24
- vibeship pilot status # Show Pilot subscription and project config status
23
+ vibeship hyperdrive install # Install Hyperdrive MCP config into .codex/config.toml
24
+ vibeship hyperdrive status # Show Hyperdrive subscription, config, and MCP reachability
25
25
  ```
26
26
 
27
27
  Configuration is stored at `~/.vibeship/config.json`. The default production API is `https://www.vibeship.today`; override it with `VIBESHIP_API_URL` or `--api-url` when dogfooding against a local internal app.
28
28
 
29
- Pilot MCP defaults to `https://pilot.vibeship.today/mcp`; override it with `VIBESHIP_PILOT_MCP_URL` or `--mcp-url`.
29
+ Hyperdrive MCP defaults to `https://hyperdrive.vibeship.today/mcp`; override it with
30
+ `VIBESHIP_HYPERDRIVE_MCP_URL` or `--mcp-url`. `vibeship hyperdrive status` calls Hyperdrive's
31
+ `vibeship_hyperdrive_status` MCP tool with your stored CLI token and reports whether
32
+ the server accepted the token.
33
+
34
+ `vibeship init` checks your VibeShip entitlement first, then tries to clone the private starter over SSH and falls back to HTTPS. If GitHub access fails after entitlement approval, connect the Polar GitHub repository access benefit in the VibeShip customer portal, verify access to `vibeshiphq/vibeship-starter`, and rerun `vibeship init`. Support can provide `--repo-url` or `VIBESHIP_STARTER_REPO_URL` for temporary clone overrides.
30
35
 
31
36
  ## Development
32
37
 
package/dist/api.d.ts CHANGED
@@ -1,11 +1,36 @@
1
1
  import type { AuthState } from "./config.js";
2
+ import { z } from "zod";
2
3
  export type EntitlementStatus = {
3
4
  email?: string;
4
5
  starterAccess: boolean;
5
- pilotActive: boolean;
6
+ hyperdriveActive: boolean;
6
7
  entitlements: string[];
7
8
  };
9
+ declare const hyperdriveMcpStatusSchema: z.ZodObject<{
10
+ authenticated: z.ZodBoolean;
11
+ subject: z.ZodOptional<z.ZodNullable<z.ZodString>>;
12
+ email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
13
+ reason: z.ZodOptional<z.ZodNullable<z.ZodString>>;
14
+ entitlements: z.ZodDefault<z.ZodArray<z.ZodString>>;
15
+ tokenExpiresAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
16
+ subscriptionStatus: z.ZodOptional<z.ZodNullable<z.ZodString>>;
17
+ server: z.ZodObject<{
18
+ name: z.ZodString;
19
+ version: z.ZodString;
20
+ protocolVersion: z.ZodString;
21
+ authority: z.ZodString;
22
+ }, z.core.$loose>;
23
+ providers: z.ZodDefault<z.ZodArray<z.ZodString>>;
24
+ rateLimit: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
25
+ }, z.core.$loose>;
26
+ export type HyperdriveMcpStatus = z.infer<typeof hyperdriveMcpStatusSchema>;
8
27
  export declare function fetchEntitlements({ apiUrl, auth, }: {
9
28
  apiUrl: string;
10
29
  auth: AuthState;
11
30
  }): Promise<EntitlementStatus>;
31
+ export declare function fetchHyperdriveMcpStatus({ mcpUrl, auth, fetchImpl, }: {
32
+ mcpUrl: string;
33
+ auth: AuthState;
34
+ fetchImpl?: typeof fetch;
35
+ }): Promise<HyperdriveMcpStatus>;
36
+ export {};
package/dist/api.js CHANGED
@@ -1,10 +1,32 @@
1
+ import { z } from "zod";
2
+ const hyperdriveMcpStatusSchema = z
3
+ .object({
4
+ authenticated: z.boolean(),
5
+ subject: z.string().nullable().optional(),
6
+ email: z.string().nullable().optional(),
7
+ reason: z.string().nullable().optional(),
8
+ entitlements: z.array(z.string()).default([]),
9
+ tokenExpiresAt: z.string().nullable().optional(),
10
+ subscriptionStatus: z.string().nullable().optional(),
11
+ server: z
12
+ .object({
13
+ name: z.string(),
14
+ version: z.string(),
15
+ protocolVersion: z.string(),
16
+ authority: z.string(),
17
+ })
18
+ .passthrough(),
19
+ providers: z.array(z.string()).default([]),
20
+ rateLimit: z.unknown().nullable().optional(),
21
+ })
22
+ .passthrough();
1
23
  export async function fetchEntitlements({ apiUrl, auth, }) {
2
24
  if (process.env.VIBESHIP_CLI_OFFLINE === "1") {
3
25
  return {
4
26
  email: auth.email,
5
27
  starterAccess: true,
6
- pilotActive: true,
7
- entitlements: ["license:starter", "pilot:active"],
28
+ hyperdriveActive: true,
29
+ entitlements: ["license:starter", "hyperdrive:active"],
8
30
  };
9
31
  }
10
32
  const url = new URL("/api/cli/entitlements", apiUrl);
@@ -28,6 +50,61 @@ export async function fetchEntitlements({ apiUrl, auth, }) {
28
50
  }
29
51
  return (await response.json());
30
52
  }
53
+ export async function fetchHyperdriveMcpStatus({ mcpUrl, auth, fetchImpl = fetch, }) {
54
+ const url = new URL(mcpUrl);
55
+ let response;
56
+ try {
57
+ response = await fetchImpl(url, {
58
+ method: "POST",
59
+ headers: {
60
+ authorization: `Bearer ${auth.token}`,
61
+ "content-type": "application/json",
62
+ accept: "application/json",
63
+ },
64
+ body: JSON.stringify({
65
+ jsonrpc: "2.0",
66
+ id: "vibeship-cli-status",
67
+ method: "tools/call",
68
+ params: {
69
+ name: "vibeship_hyperdrive_status",
70
+ arguments: {},
71
+ },
72
+ }),
73
+ });
74
+ }
75
+ catch (error) {
76
+ const message = error instanceof Error ? error.message : String(error);
77
+ throw new Error(`Could not reach Hyperdrive MCP at ${url.origin}: ${message}`);
78
+ }
79
+ if (!response.ok) {
80
+ throw new Error(`Hyperdrive MCP check failed with HTTP ${response.status}${await responseSuffix(response)}.`);
81
+ }
82
+ const body = (await response.json());
83
+ if (body.error) {
84
+ const message = typeof body.error.message === "string"
85
+ ? body.error.message
86
+ : "JSON-RPC error";
87
+ throw new Error(`Hyperdrive MCP check failed: ${message}`);
88
+ }
89
+ const status = body.result?.structuredContent ??
90
+ parseTextContent(body.result?.content?.find((item) => item.type === "text"));
91
+ const parsed = hyperdriveMcpStatusSchema.safeParse(status);
92
+ if (!parsed.success) {
93
+ throw new Error("Hyperdrive MCP status response was not recognized.");
94
+ }
95
+ return parsed.data;
96
+ }
97
+ function parseTextContent(item) {
98
+ if (typeof item?.text !== "string") {
99
+ return null;
100
+ }
101
+ try {
102
+ return JSON.parse(item.text);
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ }
31
108
  async function responseSuffix(response) {
32
109
  const text = (await response.text()).trim();
33
110
  if (!text) {
package/dist/codex.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export declare function projectCodexConfigPath(projectDir: string): string;
2
- export declare function pilotCodexConfig({ mcpUrl, }: {
2
+ export declare function hyperdriveCodexConfig({ mcpUrl, }: {
3
3
  mcpUrl: string;
4
4
  }): string;
5
- export declare function installPilotCodexConfig({ projectDir, mcpUrl, }: {
5
+ export declare function installHyperdriveCodexConfig({ projectDir, mcpUrl, }: {
6
6
  projectDir: string;
7
7
  mcpUrl: string;
8
8
  }): string;
package/dist/codex.js CHANGED
@@ -3,21 +3,21 @@ import path from "node:path";
3
3
  export function projectCodexConfigPath(projectDir) {
4
4
  return path.join(projectDir, ".codex", "config.toml");
5
5
  }
6
- export function pilotCodexConfig({ mcpUrl, }) {
7
- return `[mcp_servers.vibeship-pilot]
6
+ export function hyperdriveCodexConfig({ mcpUrl, }) {
7
+ return `[mcp_servers.vibeship-hyperdrive]
8
8
  url = "${mcpUrl}"
9
- bearer_token_env_var = "VIBESHIP_PILOT_TOKEN"
9
+ bearer_token_env_var = "VIBESHIP_HYPERDRIVE_TOKEN"
10
10
  default_tools_approval_mode = "prompt"
11
11
  `;
12
12
  }
13
- export function installPilotCodexConfig({ projectDir, mcpUrl, }) {
13
+ export function installHyperdriveCodexConfig({ projectDir, mcpUrl, }) {
14
14
  const file = projectCodexConfigPath(projectDir);
15
15
  fs.mkdirSync(path.dirname(file), { recursive: true });
16
16
  const existing = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
17
- const marker = "[mcp_servers.vibeship-pilot]";
17
+ const marker = "[mcp_servers.vibeship-hyperdrive]";
18
18
  const next = existing.includes(marker)
19
- ? existing.replace(/\[mcp_servers\.vibeship-pilot\][\s\S]*?(?=\n\[|$)/, pilotCodexConfig({ mcpUrl }).trimEnd())
20
- : `${existing.trimEnd()}${existing.trim() ? "\n\n" : ""}${pilotCodexConfig({
19
+ ? existing.replace(/\[mcp_servers\.vibeship-hyperdrive\][\s\S]*?(?=\n\[|$)/, hyperdriveCodexConfig({ mcpUrl }).trimEnd())
20
+ : `${existing.trimEnd()}${existing.trim() ? "\n\n" : ""}${hyperdriveCodexConfig({
21
21
  mcpUrl,
22
22
  }).trimEnd()}\n`;
23
23
  fs.writeFileSync(file, next);
package/dist/config.d.ts CHANGED
@@ -9,7 +9,7 @@ export type AuthState = z.infer<typeof authStateSchema>;
9
9
  export type CliConfig = {
10
10
  auth?: AuthState;
11
11
  apiUrl?: string;
12
- pilotMcpUrl?: string;
12
+ hyperdriveMcpUrl?: string;
13
13
  };
14
14
  export declare function configDir(): string;
15
15
  export declare function configPath(): string;
@@ -18,5 +18,5 @@ export declare function writeConfig(config: CliConfig): void;
18
18
  export declare function clearAuth(): void;
19
19
  export declare function requireAuth(config?: CliConfig): AuthState;
20
20
  export declare function defaultApiUrl(): string;
21
- export declare function defaultPilotMcpUrl(): string;
21
+ export declare function defaultHyperdriveMcpUrl(): string;
22
22
  export {};
package/dist/config.js CHANGED
@@ -11,7 +11,7 @@ const authStateSchema = z.object({
11
11
  const configSchema = z.object({
12
12
  auth: authStateSchema.optional(),
13
13
  apiUrl: z.string().url().optional(),
14
- pilotMcpUrl: z.string().url().optional(),
14
+ hyperdriveMcpUrl: z.string().url().optional(),
15
15
  });
16
16
  export function configDir() {
17
17
  return path.join(os.homedir(), ".vibeship");
@@ -52,6 +52,6 @@ export function defaultApiUrl() {
52
52
  }
53
53
  return "https://www.vibeship.today";
54
54
  }
55
- export function defaultPilotMcpUrl() {
56
- return process.env.VIBESHIP_PILOT_MCP_URL ?? "https://pilot.vibeship.today/mcp";
55
+ export function defaultHyperdriveMcpUrl() {
56
+ return process.env.VIBESHIP_HYPERDRIVE_MCP_URL ?? "https://hyperdrive.vibeship.today/mcp";
57
57
  }
package/dist/index.js CHANGED
@@ -16,9 +16,9 @@ function recoveryActions(message) {
16
16
  "Then run vibeship login again.",
17
17
  ];
18
18
  }
19
- if (message.includes("Pilot subscription")) {
19
+ if (message.includes("Hyperdrive subscription")) {
20
20
  return [
21
- "Confirm this account has an active Pilot subscription at https://www.vibeship.today.",
21
+ "Confirm this account has an active Hyperdrive subscription at https://www.vibeship.today.",
22
22
  "Then run vibeship login again.",
23
23
  ];
24
24
  }
@@ -34,5 +34,12 @@ function recoveryActions(message) {
34
34
  if (message.includes("already exists and is not empty")) {
35
35
  return ["Choose a new directory: vibeship init my-app"];
36
36
  }
37
+ if (message.includes("Could not clone the VibeShip starter")) {
38
+ return [
39
+ "Confirm the Polar GitHub repository access benefit is connected to your GitHub account.",
40
+ "Check SSH with `ssh -T git@github.com` or HTTPS auth with `gh auth status`.",
41
+ "Use --repo-url or VIBESHIP_STARTER_REPO_URL if support gave you a temporary clone URL.",
42
+ ];
43
+ }
37
44
  return ["Run with --help to inspect command options."];
38
45
  }
package/dist/program.d.ts CHANGED
@@ -1,2 +1,23 @@
1
+ import { type SetupConfig } from "./project-config.js";
2
+ type CloneSource = {
3
+ label: string;
4
+ url: string;
5
+ };
6
+ type CloneFailure = CloneSource & {
7
+ message: string;
8
+ };
9
+ export declare function starterCloneSources(overrideUrl?: string): CloneSource[];
10
+ export declare function starterCloneRecoveryActions(failures: CloneFailure[]): string[];
11
+ export declare function shouldPromptForSetup(options: {
12
+ workflow?: string;
13
+ integrations?: string;
14
+ interactive?: boolean;
15
+ }): boolean;
16
+ export declare function resolveInitSetupConfig(options: {
17
+ workflow?: string;
18
+ integrations?: string;
19
+ interactive?: boolean;
20
+ }): Promise<SetupConfig>;
1
21
  export declare function run(argv: string[]): Promise<void>;
2
22
  export declare function normalizeArgv(argv: string[]): string[];
23
+ export {};
package/dist/program.js CHANGED
@@ -1,14 +1,46 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import readline from "node:readline/promises";
3
4
  import { Command } from "commander";
4
5
  import { execa } from "execa";
5
6
  import open from "open";
6
- import { fetchEntitlements } from "./api.js";
7
- import { clearAuth, defaultApiUrl, defaultPilotMcpUrl, readConfig, requireAuth, writeConfig, } from "./config.js";
8
- import { installPilotCodexConfig } from "./codex.js";
7
+ import { fetchEntitlements, fetchHyperdriveMcpStatus } from "./api.js";
8
+ import { clearAuth, defaultApiUrl, defaultHyperdriveMcpUrl, readConfig, requireAuth, writeConfig, } from "./config.js";
9
+ import { installHyperdriveCodexConfig } from "./codex.js";
9
10
  import { startBrowserLogin } from "./login.js";
11
+ import { parseIntegrations, parseWorkflow, setupConfigForWorkflow, setupIntegrations, writeProjectSetupConfig, } from "./project-config.js";
10
12
  import { line, renderDone, renderInfo } from "./ui.js";
11
- const STARTER_REPO = "git@github.com:vibeshiphq/vibeship-starter.git";
13
+ const STARTER_REPO_SSH = "git@github.com:vibeshiphq/vibeship-starter.git";
14
+ const STARTER_REPO_HTTPS = "https://github.com/vibeshiphq/vibeship-starter.git";
15
+ export function starterCloneSources(overrideUrl) {
16
+ const configured = overrideUrl ?? process.env.VIBESHIP_STARTER_REPO_URL?.trim();
17
+ if (configured) {
18
+ return [{ label: "custom", url: configured }];
19
+ }
20
+ return [
21
+ { label: "ssh", url: STARTER_REPO_SSH },
22
+ { label: "https", url: STARTER_REPO_HTTPS },
23
+ ];
24
+ }
25
+ function cloneErrorMessage(error) {
26
+ if (error instanceof Error) {
27
+ return error.message;
28
+ }
29
+ return String(error);
30
+ }
31
+ export function starterCloneRecoveryActions(failures) {
32
+ const attempted = failures
33
+ .map((failure) => `${failure.label}: ${failure.url}`)
34
+ .join("; ");
35
+ return [
36
+ `Clone attempts failed (${attempted}).`,
37
+ "Open the Polar customer portal from VibeShip and confirm the GitHub repository access benefit is connected to the right GitHub account.",
38
+ "Confirm that account can access vibeshiphq/vibeship-starter in GitHub.",
39
+ "For SSH, run `ssh -T git@github.com` and confirm your key is accepted.",
40
+ "For HTTPS, run `gh auth status` or sign in to GitHub in your credential manager.",
41
+ "If access was just granted, wait a minute and rerun `vibeship init`.",
42
+ ];
43
+ }
12
44
  async function commandLogin(options) {
13
45
  const config = readConfig();
14
46
  const apiUrl = options.apiUrl ?? config.apiUrl ?? defaultApiUrl();
@@ -67,7 +99,7 @@ async function commandWhoami(options = {}) {
67
99
  line("email", status.email ?? auth.email ?? "unknown"),
68
100
  line("api", apiUrl, "muted"),
69
101
  line("starter", status.starterAccess ? "ready" : "missing", status.starterAccess ? "success" : "warning"),
70
- line("pilot", status.pilotActive ? "active" : "inactive", status.pilotActive ? "success" : "warning"),
102
+ line("hyperdrive", status.hyperdriveActive ? "active" : "inactive", status.hyperdriveActive ? "success" : "warning"),
71
103
  ]);
72
104
  }
73
105
  async function commandDoctor(options) {
@@ -77,15 +109,15 @@ async function commandDoctor(options) {
77
109
  const codexConfig = path.join(projectDir, ".codex", "config.toml");
78
110
  const hasAuth = Boolean(config.auth?.token);
79
111
  const hasProject = fs.existsSync(marker);
80
- const hasPilotConfig = fs.existsSync(codexConfig);
112
+ const hasHyperdriveConfig = fs.existsSync(codexConfig);
81
113
  renderInfo("Doctor", [
82
114
  line("auth", hasAuth ? "present" : "missing", hasAuth ? "success" : "warning"),
83
115
  line("api", options.apiUrl ?? config.apiUrl ?? defaultApiUrl(), "muted"),
84
116
  line("project", hasProject ? "vibeship starter" : "not detected", hasProject ? "success" : "warning"),
85
- line("pilot config", hasPilotConfig ? codexConfig : "not installed", hasPilotConfig ? "success" : "warning"),
86
- ], doctorActions({ hasAuth, hasProject, hasPilotConfig, projectDir }));
117
+ line("hyperdrive config", hasHyperdriveConfig ? codexConfig : "not installed", hasHyperdriveConfig ? "success" : "warning"),
118
+ ], doctorActions({ hasAuth, hasProject, hasHyperdriveConfig, projectDir }));
87
119
  }
88
- async function cloneStarter({ targetDir, localStarter, }) {
120
+ async function cloneStarter({ targetDir, localStarter, repoUrl, }) {
89
121
  if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
90
122
  throw new Error(`${targetDir} already exists and is not empty.`);
91
123
  }
@@ -93,7 +125,66 @@ async function cloneStarter({ targetDir, localStarter, }) {
93
125
  await execa("cp", ["-R", `${path.resolve(localStarter)}/.`, targetDir]);
94
126
  return;
95
127
  }
96
- await execa("git", ["clone", STARTER_REPO, targetDir]);
128
+ const failures = [];
129
+ for (const source of starterCloneSources(repoUrl)) {
130
+ try {
131
+ await execa("git", ["clone", source.url, targetDir]);
132
+ return;
133
+ }
134
+ catch (error) {
135
+ failures.push({ ...source, message: cloneErrorMessage(error) });
136
+ fs.rmSync(targetDir, { recursive: true, force: true });
137
+ }
138
+ }
139
+ throw new Error([
140
+ "Could not clone the VibeShip starter after entitlement approval.",
141
+ ...starterCloneRecoveryActions(failures),
142
+ "Raw git errors:",
143
+ ...failures.map((failure) => `- ${failure.label}: ${failure.message.slice(0, 500)}`),
144
+ ].join("\n"));
145
+ }
146
+ export function shouldPromptForSetup(options) {
147
+ if (options.interactive === false) {
148
+ return false;
149
+ }
150
+ return (!options.workflow &&
151
+ !options.integrations &&
152
+ Boolean(process.stdin.isTTY && process.stdout.isTTY));
153
+ }
154
+ function parseIntegrationAnswer(answer) {
155
+ return parseIntegrations(answer.trim() || undefined);
156
+ }
157
+ async function promptForSetupConfig() {
158
+ const rl = readline.createInterface({
159
+ input: process.stdin,
160
+ output: process.stdout,
161
+ });
162
+ try {
163
+ const workflowAnswer = await rl.question("Setup workflow [1 local-first, 2 launch-ready] (1): ");
164
+ const workflow = workflowAnswer.trim() === "2" || workflowAnswer.trim() === "launch-ready"
165
+ ? "launch-ready"
166
+ : "local-first";
167
+ const defaultList = setupIntegrations
168
+ .filter((integration) => ["clerk", "convex", "polar", "resend", "vercel"].includes(integration))
169
+ .join(",");
170
+ const integrationsAnswer = await rl.question(`Enabled integrations (${defaultList}): `);
171
+ return setupConfigForWorkflow({
172
+ workflow,
173
+ integrations: parseIntegrationAnswer(integrationsAnswer) ?? undefined,
174
+ });
175
+ }
176
+ finally {
177
+ rl.close();
178
+ }
179
+ }
180
+ export async function resolveInitSetupConfig(options) {
181
+ if (shouldPromptForSetup(options)) {
182
+ return promptForSetupConfig();
183
+ }
184
+ return setupConfigForWorkflow({
185
+ workflow: parseWorkflow(options.workflow),
186
+ integrations: parseIntegrations(options.integrations),
187
+ });
97
188
  }
98
189
  async function commandInit(options) {
99
190
  const config = readConfig();
@@ -107,18 +198,33 @@ async function commandInit(options) {
107
198
  throw new Error("This account does not have VibeShip starter access.");
108
199
  }
109
200
  const targetDir = path.resolve(options.dir ?? options.targetDir ?? "vibeship-app");
201
+ const setup = await resolveInitSetupConfig({
202
+ workflow: options.workflow,
203
+ integrations: options.integrations,
204
+ });
110
205
  renderInfo("Initializing starter", [
111
206
  line("directory", targetDir),
112
- line("source", options.localStarter ? path.resolve(options.localStarter) : STARTER_REPO),
207
+ line("source", options.localStarter
208
+ ? path.resolve(options.localStarter)
209
+ : starterCloneSources(options.repoUrl)
210
+ .map((source) => source.url)
211
+ .join(" then ")),
113
212
  line("install", options.skipInstall ? "skipped" : "pnpm install"),
213
+ line("workflow", setup.workflow),
214
+ line("environments", setup.environments.join(", ")),
114
215
  ]);
115
- await cloneStarter({ targetDir, localStarter: options.localStarter });
216
+ await cloneStarter({
217
+ targetDir,
218
+ localStarter: options.localStarter,
219
+ repoUrl: options.repoUrl,
220
+ });
221
+ writeProjectSetupConfig({ projectDir: targetDir, setup });
116
222
  if (!options.skipInstall) {
117
223
  await execa("pnpm", ["install"], { cwd: targetDir, stdio: "inherit" });
118
224
  }
119
- renderDone("Starter initialized", [line("directory", targetDir)], [`cd ${targetDir}`, "vibeship pilot install"]);
225
+ renderDone("Starter initialized", [line("directory", targetDir)], [`cd ${targetDir}`, "vibeship hyperdrive install"]);
120
226
  }
121
- async function commandPilotInstall(options) {
227
+ async function commandHyperdriveInstall(options) {
122
228
  const config = readConfig();
123
229
  const auth = requireAuth(config);
124
230
  const apiUrl = options.apiUrl ?? config.apiUrl ?? defaultApiUrl();
@@ -126,22 +232,22 @@ async function commandPilotInstall(options) {
126
232
  apiUrl,
127
233
  auth,
128
234
  });
129
- if (!status.pilotActive) {
130
- throw new Error("This account does not have an active VibeShip Pilot subscription.");
235
+ if (!status.hyperdriveActive) {
236
+ throw new Error("This account does not have an active VibeShip Hyperdrive subscription.");
131
237
  }
132
238
  const projectDir = path.resolve(options.projectDir ?? process.cwd());
133
- const mcpUrl = options.mcpUrl ?? config.pilotMcpUrl ?? defaultPilotMcpUrl();
134
- const file = installPilotCodexConfig({ projectDir, mcpUrl });
135
- renderDone("Pilot installed", [
239
+ const mcpUrl = options.mcpUrl ?? config.hyperdriveMcpUrl ?? defaultHyperdriveMcpUrl();
240
+ const file = installHyperdriveCodexConfig({ projectDir, mcpUrl });
241
+ renderDone("Hyperdrive installed", [
136
242
  line("project", projectDir),
137
243
  line("config", file),
138
244
  line("mcp", mcpUrl),
139
245
  ], [
140
- "export VIBESHIP_PILOT_TOKEN=$(vibeship whoami --token-only)",
141
- "Open Codex in this project and use VibeShip Pilot.",
246
+ "export VIBESHIP_HYPERDRIVE_TOKEN=$(vibeship whoami --token-only)",
247
+ "Open Codex in this project and use VibeShip Hyperdrive.",
142
248
  ]);
143
249
  }
144
- async function commandPilotStatus(options) {
250
+ async function commandHyperdriveStatus(options) {
145
251
  const config = readConfig();
146
252
  const auth = requireAuth(config);
147
253
  const apiUrl = options.apiUrl ?? config.apiUrl ?? defaultApiUrl();
@@ -151,19 +257,32 @@ async function commandPilotStatus(options) {
151
257
  });
152
258
  const projectDir = path.resolve(options.projectDir ?? process.cwd());
153
259
  const codexConfig = path.join(projectDir, ".codex", "config.toml");
154
- const hasPilotConfig = fs.existsSync(codexConfig);
155
- renderDone("Pilot status", [
156
- line("subscription", status.pilotActive ? "active" : "inactive", status.pilotActive ? "success" : "warning"),
260
+ const hasHyperdriveConfig = fs.existsSync(codexConfig);
261
+ const mcpUrl = options.mcpUrl ?? config.hyperdriveMcpUrl ?? defaultHyperdriveMcpUrl();
262
+ const details = [
263
+ line("subscription", status.hyperdriveActive ? "active" : "inactive", status.hyperdriveActive ? "success" : "warning"),
157
264
  line("project", projectDir),
158
- line("config", hasPilotConfig ? codexConfig : "missing", hasPilotConfig ? "success" : "warning"),
159
- ]);
265
+ line("config", hasHyperdriveConfig ? codexConfig : "missing", hasHyperdriveConfig ? "success" : "warning"),
266
+ line("mcp", mcpUrl, "muted"),
267
+ ];
268
+ try {
269
+ const hyperdriveStatus = await fetchHyperdriveMcpStatus({ mcpUrl, auth });
270
+ details.push(line("mcp auth", hyperdriveStatus.authenticated
271
+ ? "accepted"
272
+ : `rejected: ${hyperdriveStatus.reason ?? "unknown"}`, hyperdriveStatus.authenticated ? "success" : "warning"), line("server", `${hyperdriveStatus.server.name}@${hyperdriveStatus.server.version}`, "muted"));
273
+ }
274
+ catch (error) {
275
+ const message = error instanceof Error ? error.message : String(error);
276
+ details.push(line("mcp check", message.slice(0, 140), "warning"));
277
+ }
278
+ renderDone("Hyperdrive status", details);
160
279
  }
161
280
  export async function run(argv) {
162
281
  const program = new Command();
163
282
  program
164
283
  .name("vibeship")
165
- .description("Initialize VibeShip starter apps and install VibeShip Pilot.")
166
- .version("0.1.0")
284
+ .description("Initialize VibeShip starter apps and install VibeShip Hyperdrive.")
285
+ .version("0.2.1")
167
286
  .showHelpAfterError()
168
287
  .showSuggestionAfterError()
169
288
  .configureHelp({ sortSubcommands: true })
@@ -171,12 +290,14 @@ export async function run(argv) {
171
290
  Examples:
172
291
  $ vibeship login
173
292
  $ vibeship init my-app
174
- $ vibeship pilot install --project-dir ./my-app
293
+ $ vibeship init my-app --workflow local-first --integrations clerk,convex,polar,resend,vercel
294
+ $ vibeship hyperdrive install --project-dir ./my-app
175
295
  $ vibeship doctor
176
296
 
177
297
  Environment:
178
298
  VIBESHIP_API_URL Override the VibeShip API URL.
179
- VIBESHIP_PILOT_MCP_URL Override the Pilot MCP URL.
299
+ VIBESHIP_HYPERDRIVE_MCP_URL Override the Hyperdrive MCP URL.
300
+ VIBESHIP_STARTER_REPO_URL Override the starter repository clone URL.
180
301
  VIBESHIP_CLI_OFFLINE=1 Use local entitlement fixtures for development.
181
302
  `);
182
303
  program
@@ -204,7 +325,7 @@ Environment:
204
325
  });
205
326
  program
206
327
  .command("doctor")
207
- .description("Inspect auth, project, and Pilot config for this directory.")
328
+ .description("Inspect auth, project, and Hyperdrive config for this directory.")
208
329
  .option("--project-dir <dir>", "Project directory", process.cwd())
209
330
  .option("--api-url <url>", "VibeShip API URL")
210
331
  .action(commandDoctor);
@@ -213,23 +334,27 @@ Environment:
213
334
  .description("Clone the private starter into a new app directory.")
214
335
  .option("--dir <dir>", "Target directory")
215
336
  .option("--local-starter <dir>", "Use a local starter checkout")
337
+ .option("--repo-url <url>", "Override the starter repository clone URL")
216
338
  .option("--skip-install", "Skip pnpm install")
217
339
  .option("--api-url <url>", "VibeShip API URL")
340
+ .option("--workflow <workflow>", "Setup workflow: local-first or launch-ready")
341
+ .option("--integrations <list>", "Comma-separated setup integrations to enable")
218
342
  .action((targetDir, options) => commandInit({ ...options, targetDir }));
219
- const pilot = program.command("pilot").description("Manage VibeShip Pilot setup.");
220
- pilot
343
+ const hyperdrive = program.command("hyperdrive").description("Manage VibeShip Hyperdrive setup.");
344
+ hyperdrive
221
345
  .command("install")
222
- .description("Install Pilot MCP config into a Codex project.")
346
+ .description("Install Hyperdrive MCP config into a Codex project.")
223
347
  .option("--project-dir <dir>", "Project directory", process.cwd())
224
- .option("--mcp-url <url>", "Pilot MCP URL")
348
+ .option("--mcp-url <url>", "Hyperdrive MCP URL")
225
349
  .option("--api-url <url>", "VibeShip API URL")
226
- .action(commandPilotInstall);
227
- pilot
350
+ .action(commandHyperdriveInstall);
351
+ hyperdrive
228
352
  .command("status")
229
- .description("Show Pilot subscription and project config status.")
353
+ .description("Show Hyperdrive subscription and project config status.")
230
354
  .option("--project-dir <dir>", "Project directory", process.cwd())
231
355
  .option("--api-url <url>", "VibeShip API URL")
232
- .action(commandPilotStatus);
356
+ .option("--mcp-url <url>", "Hyperdrive MCP URL")
357
+ .action(commandHyperdriveStatus);
233
358
  await program.parseAsync(normalizeArgv(argv));
234
359
  }
235
360
  export function normalizeArgv(argv) {
@@ -239,7 +364,7 @@ export function normalizeArgv(argv) {
239
364
  }
240
365
  return argv;
241
366
  }
242
- function doctorActions({ hasAuth, hasProject, hasPilotConfig, projectDir, }) {
367
+ function doctorActions({ hasAuth, hasProject, hasHyperdriveConfig, projectDir, }) {
243
368
  const actions = [];
244
369
  if (!hasAuth) {
245
370
  actions.push("vibeship login");
@@ -247,10 +372,10 @@ function doctorActions({ hasAuth, hasProject, hasPilotConfig, projectDir, }) {
247
372
  if (!hasProject) {
248
373
  actions.push("Run from a starter app, or create one with vibeship init my-app.");
249
374
  }
250
- if (!hasPilotConfig) {
375
+ if (!hasHyperdriveConfig) {
251
376
  actions.push(projectDir === process.cwd()
252
- ? "vibeship pilot install"
253
- : `vibeship pilot install --project-dir ${projectDir}`);
377
+ ? "vibeship hyperdrive install"
378
+ : `vibeship hyperdrive install --project-dir ${projectDir}`);
254
379
  }
255
380
  return actions;
256
381
  }
@@ -0,0 +1,31 @@
1
+ export declare const setupWorkflows: readonly ["local-first", "launch-ready"];
2
+ export declare const setupEnvironments: readonly ["development", "preview", "production"];
3
+ export declare const setupIntegrations: readonly ["clerk", "convex", "polar", "resend", "posthog", "vercel", "calCom"];
4
+ export type SetupWorkflow = (typeof setupWorkflows)[number];
5
+ export type SetupEnvironment = (typeof setupEnvironments)[number];
6
+ export type SetupIntegration = (typeof setupIntegrations)[number];
7
+ export type SetupConfig = {
8
+ workflow: SetupWorkflow;
9
+ environments: SetupEnvironment[];
10
+ integrations: Record<SetupIntegration, boolean>;
11
+ };
12
+ export declare function defaultIntegrations(): {
13
+ clerk: true;
14
+ convex: true;
15
+ polar: true;
16
+ resend: true;
17
+ posthog: false;
18
+ vercel: true;
19
+ calCom: false;
20
+ };
21
+ export declare function environmentsForWorkflow(workflow: SetupWorkflow): SetupEnvironment[];
22
+ export declare function setupConfigForWorkflow({ workflow, integrations, }: {
23
+ workflow: SetupWorkflow;
24
+ integrations?: SetupIntegration[];
25
+ }): SetupConfig;
26
+ export declare function parseWorkflow(value: string | undefined): SetupWorkflow;
27
+ export declare function parseIntegrations(value: string | undefined): ("clerk" | "convex" | "polar" | "resend" | "posthog" | "vercel" | "calCom")[] | undefined;
28
+ export declare function writeProjectSetupConfig({ projectDir, setup, }: {
29
+ projectDir: string;
30
+ setup: SetupConfig;
31
+ }): void;
@@ -0,0 +1,75 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { z } from "zod";
4
+ export const setupWorkflows = ["local-first", "launch-ready"];
5
+ export const setupEnvironments = [
6
+ "development",
7
+ "preview",
8
+ "production",
9
+ ];
10
+ export const setupIntegrations = [
11
+ "clerk",
12
+ "convex",
13
+ "polar",
14
+ "resend",
15
+ "posthog",
16
+ "vercel",
17
+ "calCom",
18
+ ];
19
+ const workflowSchema = z.enum(setupWorkflows);
20
+ const integrationsSchema = z
21
+ .array(z.enum(setupIntegrations))
22
+ .min(1, "Select at least one integration.");
23
+ export function defaultIntegrations() {
24
+ return {
25
+ clerk: true,
26
+ convex: true,
27
+ polar: true,
28
+ resend: true,
29
+ posthog: false,
30
+ vercel: true,
31
+ calCom: false,
32
+ };
33
+ }
34
+ export function environmentsForWorkflow(workflow) {
35
+ return workflow === "local-first"
36
+ ? ["development"]
37
+ : ["development", "preview", "production"];
38
+ }
39
+ export function setupConfigForWorkflow({ workflow, integrations, }) {
40
+ const enabled = {
41
+ ...defaultIntegrations(),
42
+ };
43
+ if (integrations) {
44
+ for (const integration of setupIntegrations) {
45
+ enabled[integration] = integrations.includes(integration);
46
+ }
47
+ }
48
+ return {
49
+ workflow,
50
+ environments: environmentsForWorkflow(workflow),
51
+ integrations: enabled,
52
+ };
53
+ }
54
+ export function parseWorkflow(value) {
55
+ return workflowSchema.parse(value ?? "local-first");
56
+ }
57
+ export function parseIntegrations(value) {
58
+ if (!value) {
59
+ return undefined;
60
+ }
61
+ const parsed = value
62
+ .split(",")
63
+ .map((entry) => entry.trim())
64
+ .filter(Boolean);
65
+ return integrationsSchema.parse(parsed);
66
+ }
67
+ export function writeProjectSetupConfig({ projectDir, setup, }) {
68
+ const vibeshipDir = path.join(projectDir, ".vibeship");
69
+ const marker = path.join(vibeshipDir, "project.json");
70
+ const existing = fs.existsSync(marker)
71
+ ? JSON.parse(fs.readFileSync(marker, "utf8"))
72
+ : {};
73
+ fs.mkdirSync(vibeshipDir, { recursive: true });
74
+ fs.writeFileSync(marker, `${JSON.stringify({ ...existing, setup }, null, 2)}\n`);
75
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vibeshiphq/cli",
3
- "version": "0.1.0",
4
- "description": "VibeShip CLI for starter initialization and Pilot setup.",
3
+ "version": "0.2.1",
4
+ "description": "VibeShip CLI for starter initialization and Hyperdrive setup.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
7
7
  "homepage": "https://www.vibeship.today",