creek 0.3.0-alpha.11 → 0.3.0-alpha.13

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.
@@ -1,4 +1,14 @@
1
1
  export declare const claimCommand: import("citty").CommandDef<{
2
+ json: {
3
+ type: "boolean";
4
+ description: string;
5
+ default: boolean;
6
+ };
7
+ yes: {
8
+ type: "boolean";
9
+ description: string;
10
+ default: boolean;
11
+ };
2
12
  sandboxId: {
3
13
  type: "positional";
4
14
  description: string;
@@ -2,6 +2,7 @@ import { defineCommand } from "citty";
2
2
  import consola from "consola";
3
3
  import { CreekClient } from "@solcreek/sdk";
4
4
  import { getToken, getApiUrl, getSandboxApiUrl } from "../utils/config.js";
5
+ import { globalArgs, resolveJsonMode, jsonOutput } from "../utils/output.js";
5
6
  export const claimCommand = defineCommand({
6
7
  meta: {
7
8
  name: "claim",
@@ -13,10 +14,14 @@ export const claimCommand = defineCommand({
13
14
  description: "Sandbox ID to claim (shown after sandbox deploy)",
14
15
  required: true,
15
16
  },
17
+ ...globalArgs,
16
18
  },
17
19
  async run({ args }) {
20
+ const jsonMode = resolveJsonMode(args);
18
21
  const token = getToken();
19
22
  if (!token) {
23
+ if (jsonMode)
24
+ jsonOutput({ ok: false, error: "not_authenticated", message: "Run `creek login` first" }, 1);
20
25
  consola.error("You need to be logged in to claim a sandbox.");
21
26
  consola.info("Run `creek login` first, then `creek claim` again.");
22
27
  process.exit(1);
@@ -32,13 +37,14 @@ export const claimCommand = defineCommand({
32
37
  }
33
38
  const sandbox = (await statusRes.json());
34
39
  if (!sandbox.claimable) {
35
- if (sandbox.status === "expired") {
36
- consola.error("This sandbox has expired and can no longer be claimed.");
40
+ const msg = sandbox.status === "expired"
41
+ ? "This sandbox has expired and can no longer be claimed."
42
+ : `Sandbox is in '${sandbox.status}' state and cannot be claimed.`;
43
+ if (jsonMode)
44
+ jsonOutput({ ok: false, error: "not_claimable", status: sandbox.status, message: msg }, 1);
45
+ consola.error(msg);
46
+ if (sandbox.status === "expired")
37
47
  consola.info("Run `creek deploy` to deploy your project permanently.");
38
- }
39
- else {
40
- consola.error(`Sandbox is in '${sandbox.status}' state and cannot be claimed.`);
41
- }
42
48
  process.exit(1);
43
49
  }
44
50
  // 2. Create permanent project
@@ -73,6 +79,9 @@ export const claimCommand = defineCommand({
73
79
  catch {
74
80
  // Best effort — claim status update is non-critical
75
81
  }
82
+ if (jsonMode) {
83
+ jsonOutput({ ok: true, sandboxId, project: project.slug, projectId: project.id });
84
+ }
76
85
  consola.success("Sandbox claimed!");
77
86
  consola.info("");
78
87
  consola.info("Next steps:");
@@ -1,4 +1,19 @@
1
1
  export declare const deployCommand: import("citty").CommandDef<{
2
+ template: {
3
+ type: "string";
4
+ description: string;
5
+ required: false;
6
+ };
7
+ json: {
8
+ type: "boolean";
9
+ description: string;
10
+ default: boolean;
11
+ };
12
+ yes: {
13
+ type: "boolean";
14
+ description: string;
15
+ default: boolean;
16
+ };
2
17
  dir: {
3
18
  type: "positional";
4
19
  description: string;
@@ -14,10 +29,5 @@ export declare const deployCommand: import("citty").CommandDef<{
14
29
  description: string;
15
30
  default: false;
16
31
  };
17
- template: {
18
- type: "string";
19
- description: string;
20
- required: false;
21
- };
22
32
  }>;
23
33
  //# sourceMappingURL=deploy.d.ts.map
@@ -8,6 +8,22 @@ import { getToken, getApiUrl } from "../utils/config.js";
8
8
  import { collectAssets } from "../utils/bundle.js";
9
9
  import { bundleSSRServer } from "../utils/ssr-bundle.js";
10
10
  import { sandboxDeploy, pollSandboxStatus, printSandboxSuccess } from "../utils/sandbox.js";
11
+ import { isTTY, jsonOutput, resolveJsonMode, globalArgs } from "../utils/output.js";
12
+ function section(name) {
13
+ consola.log(`\n \x1b[2m[${name}]\x1b[0m`);
14
+ }
15
+ function assetSummary(fileList) {
16
+ const byExt = {};
17
+ for (const f of fileList) {
18
+ const ext = f.includes(".") ? `.${f.split(".").pop()}` : "(other)";
19
+ byExt[ext] = (byExt[ext] ?? 0) + 1;
20
+ }
21
+ const parts = Object.entries(byExt)
22
+ .sort((a, b) => b[1] - a[1])
23
+ .slice(0, 4)
24
+ .map(([ext, n]) => `${n} ${ext}`);
25
+ return parts.join(", ");
26
+ }
11
27
  export const deployCommand = defineCommand({
12
28
  meta: {
13
29
  name: "deploy",
@@ -29,6 +45,7 @@ export const deployCommand = defineCommand({
29
45
  description: "Deploy a sample site to see Creek in action",
30
46
  default: false,
31
47
  },
48
+ ...globalArgs,
32
49
  template: {
33
50
  type: "string",
34
51
  description: "Deploy a template (e.g., react-dashboard, astro-landing)",
@@ -36,9 +53,10 @@ export const deployCommand = defineCommand({
36
53
  },
37
54
  },
38
55
  async run({ args }) {
56
+ const jsonMode = resolveJsonMode(args);
39
57
  // --- Demo deploy (zero-friction showcase) ---
40
58
  if (args.demo) {
41
- return await deployDemo();
59
+ return await deployDemo(jsonMode);
42
60
  }
43
61
  // --- Template deploy ---
44
62
  if (args.template) {
@@ -52,33 +70,40 @@ export const deployCommand = defineCommand({
52
70
  // --- Explicit directory (creek deploy ./dist) ---
53
71
  if (args.dir) {
54
72
  if (!existsSync(cwd)) {
73
+ if (jsonMode)
74
+ jsonOutput({ error: "not_found", message: `Directory not found: ${args.dir}` }, 1);
55
75
  consola.error(`Directory not found: ${args.dir}`);
56
76
  process.exit(1);
57
77
  }
58
- return await deployDirectory(cwd);
78
+ return await deployDirectory(cwd, jsonMode);
59
79
  }
60
80
  // --- Authenticated deploy (creek.toml present) ---
61
81
  if (hasConfig) {
62
82
  if (!token) {
83
+ if (jsonMode)
84
+ jsonOutput({ error: "not_authenticated", message: "Run `creek login` first" }, 1);
63
85
  consola.error("Not authenticated. Run `creek login` first.");
64
86
  consola.info("Or deploy without an account: remove creek.toml and run `creek deploy` again.");
65
87
  process.exit(1);
66
88
  }
67
- return await deployAuthenticated(cwd, configPath, token, args["skip-build"]);
89
+ return await deployAuthenticated(cwd, configPath, token, args["skip-build"], jsonMode);
68
90
  }
69
91
  // --- Sandbox deploy (has package.json → auto-detect + build) ---
70
92
  if (existsSync(join(cwd, "package.json"))) {
71
- return await deploySandbox(cwd, args["skip-build"]);
93
+ return await deploySandbox(cwd, args["skip-build"], jsonMode);
72
94
  }
73
95
  // --- Auto-detect build output dirs ---
74
96
  for (const dir of ["dist", "build", "out", ".output/public"]) {
75
97
  const fullPath = resolve(cwd, dir);
76
98
  if (existsSync(fullPath)) {
77
- consola.info(`Found build output: ${dir}/`);
78
- return await deployDirectory(fullPath);
99
+ if (!jsonMode)
100
+ consola.info(`Found build output: ${dir}/`);
101
+ return await deployDirectory(fullPath, jsonMode);
79
102
  }
80
103
  }
81
104
  // --- Nothing found → guide the user ---
105
+ if (jsonMode)
106
+ jsonOutput({ error: "no_project", message: "No project found in this directory" }, 1);
82
107
  consola.info("No project found in this directory.\n");
83
108
  consola.info(" creek deploy ./dist Deploy a build output directory");
84
109
  consola.info(" creek deploy --demo Deploy a sample site (see Creek in 5 seconds)");
@@ -124,15 +149,27 @@ code{background:#1a1a2e;padding:2px 8px;border-radius:4px;font-size:0.85rem;colo
124
149
  <script>document.title="Creek Deploy Demo — "+new Date().toLocaleTimeString()</script>
125
150
  </body>
126
151
  </html>`;
127
- async function deployDemo() {
128
- consola.start("Deploying demo site...");
152
+ async function deployDemo(jsonMode) {
153
+ if (!jsonMode)
154
+ consola.start("Deploying demo site...");
129
155
  try {
130
156
  const result = await sandboxDeploy({
131
157
  assets: { "index.html": Buffer.from(DEMO_HTML).toString("base64") },
132
158
  source: "cli-demo",
133
159
  });
134
- consola.start("Waiting for deployment...");
160
+ if (!jsonMode)
161
+ consola.start("Waiting for deployment...");
135
162
  const status = await pollSandboxStatus(result.statusUrl);
163
+ if (jsonMode) {
164
+ jsonOutput({
165
+ ok: true,
166
+ sandboxId: result.sandboxId,
167
+ url: status.previewUrl,
168
+ deployDurationMs: status.deployDurationMs,
169
+ expiresAt: result.expiresAt,
170
+ mode: "demo",
171
+ });
172
+ }
136
173
  const duration = status.deployDurationMs
137
174
  ? `in ${(status.deployDurationMs / 1000).toFixed(1)}s`
138
175
  : "in seconds";
@@ -142,50 +179,67 @@ async function deployDemo() {
142
179
  consola.info(" cd your-project && npx creek deploy");
143
180
  }
144
181
  catch (err) {
145
- consola.error(err instanceof Error ? err.message : "Demo deploy failed");
182
+ const message = err instanceof Error ? err.message : "Demo deploy failed";
183
+ if (jsonMode)
184
+ jsonOutput({ ok: false, error: "deploy_failed", message }, 1);
185
+ consola.error(message);
146
186
  process.exit(1);
147
187
  }
148
188
  }
149
189
  // ============================================================================
150
190
  // Directory deploy — deploy pre-built static files directly
151
191
  // ============================================================================
152
- async function deployDirectory(dir) {
153
- consola.info("Deploying to sandbox (60 min preview).");
154
- consola.start("Collecting assets...");
192
+ async function deployDirectory(dir, jsonMode) {
193
+ if (!jsonMode)
194
+ section("Upload");
155
195
  const { assets, fileList } = collectAssets(dir);
156
196
  if (fileList.length === 0) {
197
+ if (jsonMode)
198
+ jsonOutput({ ok: false, error: "no_files", message: `No files found in ${dir}` }, 1);
157
199
  consola.error(`No files found in ${dir}\n`);
158
200
  consola.info(" creek deploy ./dist Deploy a build output directory");
159
201
  consola.info(" creek deploy --demo Deploy a sample site (see Creek in 5 seconds)");
160
202
  process.exit(1);
161
203
  }
162
- consola.info(`Found ${fileList.length} files`);
163
- consola.start("Deploying...");
204
+ if (!jsonMode) {
205
+ consola.info(` ${fileList.length} files (${assetSummary(fileList)})`);
206
+ consola.info(" Mode: sandbox (60 min preview)");
207
+ section("Deploy");
208
+ consola.start(" Deploying to edge...");
209
+ }
164
210
  try {
165
211
  const result = await sandboxDeploy({ assets, source: "cli" });
166
- consola.start("Waiting for deployment...");
167
212
  const status = await pollSandboxStatus(result.statusUrl);
213
+ if (jsonMode) {
214
+ jsonOutput({
215
+ ok: true,
216
+ sandboxId: result.sandboxId,
217
+ url: status.previewUrl,
218
+ deployDurationMs: status.deployDurationMs,
219
+ expiresAt: result.expiresAt,
220
+ assetCount: fileList.length,
221
+ mode: "sandbox",
222
+ });
223
+ }
168
224
  printSandboxSuccess(status.previewUrl, result.expiresAt, result.sandboxId);
169
225
  }
170
226
  catch (err) {
171
- consola.error(err instanceof Error ? err.message : "Deploy failed");
227
+ const message = err instanceof Error ? err.message : "Deploy failed";
228
+ if (jsonMode)
229
+ jsonOutput({ ok: false, error: "deploy_failed", message }, 1);
230
+ consola.error(message);
172
231
  process.exit(1);
173
232
  }
174
233
  }
175
234
  // ============================================================================
176
235
  // Sandbox deploy — auto-detect framework, build, deploy
177
236
  // ============================================================================
178
- async function deploySandbox(cwd, skipBuild) {
179
- consola.info("Deploying to sandbox (60 min preview).");
180
- consola.info("");
237
+ async function deploySandbox(cwd, skipBuild, jsonMode = false) {
181
238
  const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf-8"));
182
239
  const framework = detectFramework(pkg);
183
- if (framework) {
184
- consola.info(`Detected: ${framework}`);
185
- }
186
- else {
187
- consola.info("Framework: auto (static site)");
188
- }
240
+ section("Detect");
241
+ consola.info(` Framework: ${framework ?? "static site"}`);
242
+ consola.info(" Mode: sandbox (60 min preview)");
189
243
  // Build
190
244
  const buildCommand = pkg.scripts?.build ? "npm run build" : null;
191
245
  const outputDir = resolve(cwd, getDefaultBuildOutput(framework));
@@ -195,20 +249,20 @@ async function deploySandbox(cwd, skipBuild) {
195
249
  consola.info("Add a 'build' script or use --skip-build if already built.");
196
250
  process.exit(1);
197
251
  }
198
- consola.start(`Building with: ${buildCommand}`);
252
+ section("Build");
253
+ consola.start(` ${buildCommand}`);
199
254
  try {
200
255
  execSync(buildCommand, { cwd, stdio: "inherit" });
201
256
  }
202
257
  catch {
203
258
  consola.error("Build failed");
204
259
  consola.info("");
205
- consola.info("Common fixes:");
206
- consola.info("npm install (missing dependencies?)");
207
- consola.info("Check for TypeScript errors");
208
- consola.info(` • Verify build works: ${buildCommand}`);
260
+ consola.info(" Common fixes:");
261
+ consola.info(" npm install (missing dependencies?)");
262
+ consola.info(" Check for TypeScript errors");
209
263
  process.exit(1);
210
264
  }
211
- consola.success("Build complete");
265
+ consola.success(" Build complete");
212
266
  }
213
267
  if (!existsSync(outputDir)) {
214
268
  consola.error(`Build output not found: ${outputDir}`);
@@ -226,24 +280,27 @@ async function deploySandbox(cwd, skipBuild) {
226
280
  if (subdir)
227
281
  clientAssetsDir = resolve(outputDir, subdir);
228
282
  }
229
- consola.start("Collecting assets...");
283
+ section("Upload");
230
284
  const { assets: clientAssets, fileList } = collectAssets(clientAssetsDir);
231
- consola.info(`Found ${fileList.length} assets`);
285
+ consola.info(` ${fileList.length} assets (${assetSummary(fileList)})`);
232
286
  let serverFiles;
233
287
  if (isSSR && framework) {
234
288
  const serverEntry = getSSRServerEntry(framework);
235
289
  if (serverEntry) {
236
290
  const serverEntryPath = resolve(outputDir, serverEntry);
237
291
  if (existsSync(serverEntryPath)) {
238
- consola.start("Bundling SSR server...");
292
+ consola.start(" Bundling SSR server...");
239
293
  const bundled = await bundleSSRServer(serverEntryPath);
240
294
  serverFiles = { "server.js": Buffer.from(bundled).toString("base64") };
241
- consola.success(`SSR bundled (${Math.round(bundled.length / 1024)}KB)`);
295
+ consola.success(` SSR bundled (${Math.round(bundled.length / 1024)}KB)`);
242
296
  }
243
297
  }
244
298
  }
245
299
  // Deploy to sandbox
246
- consola.start("Deploying...");
300
+ if (!jsonMode) {
301
+ section("Deploy");
302
+ consola.start(" Deploying to edge...");
303
+ }
247
304
  try {
248
305
  const result = await sandboxDeploy({
249
306
  assets: clientAssets,
@@ -251,12 +308,26 @@ async function deploySandbox(cwd, skipBuild) {
251
308
  framework: framework ?? undefined,
252
309
  source: "cli",
253
310
  });
254
- consola.start("Waiting for deployment...");
255
311
  const status = await pollSandboxStatus(result.statusUrl);
312
+ if (jsonMode) {
313
+ jsonOutput({
314
+ ok: true,
315
+ sandboxId: result.sandboxId,
316
+ url: status.previewUrl,
317
+ deployDurationMs: status.deployDurationMs,
318
+ expiresAt: result.expiresAt,
319
+ framework: framework ?? null,
320
+ assetCount: fileList.length,
321
+ mode: "sandbox",
322
+ });
323
+ }
256
324
  printSandboxSuccess(status.previewUrl, result.expiresAt, result.sandboxId);
257
325
  }
258
326
  catch (err) {
259
- consola.error(err instanceof Error ? err.message : "Sandbox deploy failed");
327
+ const message = err instanceof Error ? err.message : "Sandbox deploy failed";
328
+ if (jsonMode)
329
+ jsonOutput({ ok: false, error: "deploy_failed", message }, 1);
330
+ consola.error(message);
260
331
  process.exit(1);
261
332
  }
262
333
  }
@@ -325,44 +396,49 @@ function cleanupDir(dir) {
325
396
  // ============================================================================
326
397
  // Authenticated deploy — existing flow
327
398
  // ============================================================================
328
- async function deployAuthenticated(cwd, configPath, token, skipBuild) {
399
+ async function deployAuthenticated(cwd, configPath, token, skipBuild, jsonMode = false) {
329
400
  try {
330
401
  const config = parseConfig(readFileSync(configPath, "utf-8"));
331
402
  const client = new CreekClient(getApiUrl(), token);
332
- // Show who we're deploying as
403
+ section("Auth");
333
404
  const session = await client.getSession();
334
405
  if (!session?.user) {
335
406
  consola.error("Token is invalid or expired. Run `creek login` to re-authenticate.");
336
407
  process.exit(1);
337
408
  }
338
- consola.info(`Deploying as ${session.user.email}`);
339
- consola.info(`Project: ${config.project.name}`);
409
+ consola.info(` Deploying as ${session.user.email}`);
410
+ consola.info(` Project: ${config.project.name}`);
340
411
  // Ensure project exists — confirm before auto-creating
341
412
  let project;
342
413
  try {
343
414
  project = await client.getProject(config.project.name);
344
415
  }
345
416
  catch {
346
- const confirm = await consola.prompt(`Project "${config.project.name}" does not exist. Create it?`, { type: "confirm" });
347
- if (!confirm) {
348
- consola.info("Deploy cancelled.");
349
- process.exit(0);
417
+ // In --yes / non-TTY / JSON mode, auto-create without prompting (agent-friendly)
418
+ if (!jsonMode && isTTY) {
419
+ const confirm = await consola.prompt(`Project "${config.project.name}" does not exist. Create it?`, { type: "confirm" });
420
+ if (!confirm) {
421
+ consola.info("Deploy cancelled.");
422
+ process.exit(0);
423
+ }
350
424
  }
351
425
  const res = await client.createProject({
352
426
  slug: config.project.name,
353
427
  framework: config.project.framework,
354
428
  });
355
429
  project = res.project;
356
- consola.success(`Created project: ${project.slug}`);
430
+ if (!jsonMode)
431
+ consola.success(` Created project: ${project.slug}`);
357
432
  }
358
- // Build — creek.toml build.command is user-defined, analogous to npm scripts
433
+ // Build
359
434
  if (!skipBuild) {
360
435
  const buildCmd = config.build.command;
361
436
  if (!buildCmd || typeof buildCmd !== "string" || buildCmd.length > 500) {
362
437
  consola.error("Invalid build command in creek.toml");
363
438
  process.exit(1);
364
439
  }
365
- consola.start(`Building with: ${buildCmd}`);
440
+ section("Build");
441
+ consola.start(` ${buildCmd}`);
366
442
  try {
367
443
  execSync(buildCmd, { cwd, stdio: "inherit" });
368
444
  }
@@ -370,7 +446,7 @@ async function deployAuthenticated(cwd, configPath, token, skipBuild) {
370
446
  consola.error("Build failed");
371
447
  process.exit(1);
372
448
  }
373
- consola.success("Build complete");
449
+ consola.success(" Build complete");
374
450
  }
375
451
  const outputDir = resolve(cwd, config.build.output);
376
452
  if (!existsSync(outputDir)) {
@@ -387,27 +463,28 @@ async function deployAuthenticated(cwd, configPath, token, skipBuild) {
387
463
  clientAssetsDir = resolve(outputDir, clientSubdir);
388
464
  }
389
465
  }
390
- consola.start("Collecting assets...");
466
+ section("Upload");
391
467
  const { assets: clientAssets, fileList } = collectAssets(clientAssetsDir);
392
- consola.info(`Found ${fileList.length} client assets`);
468
+ consola.info(` ${fileList.length} assets (${assetSummary(fileList)})`);
393
469
  let serverFiles;
394
470
  if (isSSR && framework) {
395
471
  const serverEntry = getSSRServerEntry(framework);
396
472
  if (serverEntry) {
397
473
  const serverEntryPath = resolve(outputDir, serverEntry);
398
474
  if (existsSync(serverEntryPath)) {
399
- consola.start("Bundling SSR server...");
475
+ consola.start(" Bundling SSR server...");
400
476
  const bundled = await bundleSSRServer(serverEntryPath);
401
477
  serverFiles = {
402
478
  "server.js": Buffer.from(bundled).toString("base64"),
403
479
  };
404
- consola.success(`SSR server bundled (${Math.round(bundled.length / 1024)}KB)`);
480
+ consola.success(` SSR bundled (${Math.round(bundled.length / 1024)}KB)`);
405
481
  }
406
482
  }
407
483
  }
408
- consola.start("Creating deployment...");
484
+ section("Deploy");
485
+ consola.start(" Creating deployment...");
409
486
  const { deployment } = await client.createDeployment(project.id);
410
- consola.start("Uploading...");
487
+ consola.start(" Uploading bundle...");
411
488
  const bundle = {
412
489
  manifest: {
413
490
  assets: fileList,
@@ -426,10 +503,10 @@ async function deployAuthenticated(cwd, configPath, token, skipBuild) {
426
503
  const POLL_TIMEOUT = 120_000;
427
504
  const TERMINAL = new Set(["active", "failed", "cancelled"]);
428
505
  const STEP_LABELS = {
429
- queued: "Waiting...",
430
- uploading: "Uploading bundle...",
431
- provisioning: "Provisioning resources...",
432
- deploying: "Deploying to edge...",
506
+ queued: " Waiting...",
507
+ uploading: " Uploading...",
508
+ provisioning: " Provisioning resources...",
509
+ deploying: " Deploying to edge...",
433
510
  };
434
511
  let lastStatus = "";
435
512
  const start = Date.now();
@@ -446,24 +523,40 @@ async function deployAuthenticated(cwd, configPath, token, skipBuild) {
446
523
  lastStatus = status;
447
524
  }
448
525
  if (status === "active") {
449
- consola.success(`Deployed! ${res.url ?? res.previewUrl}`);
526
+ if (jsonMode) {
527
+ jsonOutput({
528
+ ok: true,
529
+ url: res.url ?? res.previewUrl,
530
+ previewUrl: res.previewUrl,
531
+ deploymentId: deployment.id,
532
+ project: project.slug,
533
+ mode: "production",
534
+ });
535
+ }
536
+ consola.success(` Deployed! ${res.url ?? res.previewUrl}`);
450
537
  if (res.url && res.previewUrl) {
451
- consola.info(`Preview: ${res.previewUrl}`);
538
+ consola.info(` Preview: ${res.previewUrl}`);
452
539
  }
453
540
  return;
454
541
  }
455
542
  if (status === "failed") {
456
543
  const step = failed_step ? ` at ${failed_step}` : "";
457
544
  const msg = error_message ?? "Unknown error";
545
+ if (jsonMode)
546
+ jsonOutput({ ok: false, error: "deploy_failed", message: msg, failedStep: failed_step }, 1);
458
547
  consola.error(`Deploy failed${step}: ${msg}`);
459
548
  process.exit(1);
460
549
  }
461
550
  if (status === "cancelled") {
551
+ if (jsonMode)
552
+ jsonOutput({ ok: false, error: "cancelled", message: "Deploy was cancelled" }, 1);
462
553
  consola.warn("Deploy was cancelled");
463
554
  process.exit(1);
464
555
  }
465
556
  await new Promise((r) => setTimeout(r, POLL_INTERVAL));
466
557
  }
558
+ if (jsonMode)
559
+ jsonOutput({ ok: false, error: "timeout", message: "Deploy timed out after 2 minutes" }, 1);
467
560
  consola.error("Deploy timed out after 2 minutes");
468
561
  process.exit(1);
469
562
  }
@@ -1,10 +1,10 @@
1
1
  import { defineCommand } from "citty";
2
2
  import consola from "consola";
3
- import { CreekClient } from "@solcreek/sdk";
3
+ import { CreekClient, parseConfig } from "@solcreek/sdk";
4
4
  import { getToken, getApiUrl } from "../utils/config.js";
5
5
  import { existsSync, readFileSync } from "node:fs";
6
6
  import { join } from "node:path";
7
- import { parseConfig } from "@solcreek/sdk";
7
+ import { globalArgs, resolveJsonMode, jsonOutput } from "../utils/output.js";
8
8
  function getProjectSlug() {
9
9
  const configPath = join(process.cwd(), "creek.toml");
10
10
  if (!existsSync(configPath)) {
@@ -26,11 +26,15 @@ const envSet = defineCommand({
26
26
  args: {
27
27
  key: { type: "positional", description: "Variable name (e.g. DATABASE_URL)", required: true },
28
28
  value: { type: "positional", description: "Variable value", required: true },
29
+ ...globalArgs,
29
30
  },
30
31
  async run({ args }) {
32
+ const jsonMode = resolveJsonMode(args);
31
33
  const client = getClient();
32
34
  const slug = getProjectSlug();
33
35
  await client.setEnvVar(slug, args.key, args.value);
36
+ if (jsonMode)
37
+ jsonOutput({ ok: true, key: args.key, project: slug });
34
38
  consola.success(`Set ${args.key}`);
35
39
  },
36
40
  });
@@ -43,11 +47,20 @@ const envGet = defineCommand({
43
47
  meta: { name: "ls", description: "List environment variables" },
44
48
  args: {
45
49
  show: { type: "boolean", description: "Show values in plaintext (default: redacted)", default: false },
50
+ ...globalArgs,
46
51
  },
47
52
  async run({ args }) {
53
+ const jsonMode = resolveJsonMode(args);
48
54
  const client = getClient();
49
55
  const slug = getProjectSlug();
50
56
  const vars = await client.listEnvVars(slug);
57
+ if (jsonMode) {
58
+ jsonOutput({
59
+ ok: true,
60
+ project: slug,
61
+ vars: vars.map((v) => ({ key: v.key, value: args.show ? v.value : redact(v.value) })),
62
+ });
63
+ }
51
64
  if (vars.length === 0) {
52
65
  consola.info("No environment variables set.");
53
66
  return;
@@ -65,11 +78,15 @@ const envRm = defineCommand({
65
78
  meta: { name: "rm", description: "Remove an environment variable" },
66
79
  args: {
67
80
  key: { type: "positional", description: "Variable name to remove", required: true },
81
+ ...globalArgs,
68
82
  },
69
83
  async run({ args }) {
84
+ const jsonMode = resolveJsonMode(args);
70
85
  const client = getClient();
71
86
  const slug = getProjectSlug();
72
87
  await client.deleteEnvVar(slug, args.key);
88
+ if (jsonMode)
89
+ jsonOutput({ ok: true, key: args.key, removed: true, project: slug });
73
90
  consola.success(`Removed ${args.key}`);
74
91
  },
75
92
  });
@@ -1,4 +1,14 @@
1
1
  export declare const initCommand: import("citty").CommandDef<{
2
+ json: {
3
+ type: "boolean";
4
+ description: string;
5
+ default: boolean;
6
+ };
7
+ yes: {
8
+ type: "boolean";
9
+ description: string;
10
+ default: boolean;
11
+ };
2
12
  name: {
3
13
  type: "string";
4
14
  description: string;
@@ -4,6 +4,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
4
4
  import { join, basename } from "node:path";
5
5
  import { stringify } from "smol-toml";
6
6
  import { detectFramework } from "@solcreek/sdk";
7
+ import { globalArgs, resolveJsonMode, jsonOutput, shouldAutoConfirm } from "../utils/output.js";
7
8
  export const initCommand = defineCommand({
8
9
  meta: {
9
10
  name: "init",
@@ -15,15 +16,19 @@ export const initCommand = defineCommand({
15
16
  description: "Project name",
16
17
  required: false,
17
18
  },
19
+ ...globalArgs,
18
20
  },
19
21
  async run({ args }) {
22
+ const jsonMode = resolveJsonMode(args);
20
23
  const cwd = process.cwd();
21
24
  const configPath = join(cwd, "creek.toml");
22
25
  if (existsSync(configPath)) {
23
- consola.warn("creek.toml already exists");
24
- const overwrite = await consola.prompt("Overwrite?", { type: "confirm" });
25
- if (!overwrite)
26
- return;
26
+ if (!shouldAutoConfirm(args)) {
27
+ consola.warn("creek.toml already exists");
28
+ const overwrite = await consola.prompt("Overwrite?", { type: "confirm" });
29
+ if (!overwrite)
30
+ return;
31
+ }
27
32
  }
28
33
  // Detect framework
29
34
  const pkgPath = join(cwd, "package.json");
@@ -33,7 +38,8 @@ export const initCommand = defineCommand({
33
38
  const detected = detectFramework(pkg);
34
39
  if (detected) {
35
40
  framework = detected;
36
- consola.info(`Detected framework: ${framework}`);
41
+ if (!jsonMode)
42
+ consola.info(`Detected framework: ${framework}`);
37
43
  }
38
44
  }
39
45
  const defaultName = basename(cwd).toLowerCase().replace(/[^a-z0-9-]/g, "-");
@@ -54,6 +60,9 @@ export const initCommand = defineCommand({
54
60
  },
55
61
  };
56
62
  writeFileSync(configPath, stringify(config));
63
+ if (jsonMode) {
64
+ jsonOutput({ ok: true, name, framework: framework ?? null, path: configPath });
65
+ }
57
66
  consola.success(`Created creek.toml for "${name}"`);
58
67
  },
59
68
  });
@@ -1,4 +1,14 @@
1
1
  export declare const loginCommand: import("citty").CommandDef<{
2
+ json: {
3
+ type: "boolean";
4
+ description: string;
5
+ default: boolean;
6
+ };
7
+ yes: {
8
+ type: "boolean";
9
+ description: string;
10
+ default: boolean;
11
+ };
2
12
  token: {
3
13
  type: "string";
4
14
  description: string;
@@ -4,6 +4,7 @@ import { execFileSync } from "node:child_process";
4
4
  import { CreekClient } from "@solcreek/sdk";
5
5
  import { writeCliConfig, readCliConfig, getApiUrl } from "../utils/config.js";
6
6
  import { startAuthServer } from "../utils/auth-server.js";
7
+ import { globalArgs, resolveJsonMode, jsonOutput } from "../utils/output.js";
7
8
  function getDashboardUrl() {
8
9
  const apiUrl = getApiUrl();
9
10
  // http://localhost:8787 → http://localhost:3000
@@ -41,11 +42,13 @@ export const loginCommand = defineCommand({
41
42
  description: "Use headless mode (paste API key manually, for SSH/remote)",
42
43
  default: false,
43
44
  },
45
+ ...globalArgs,
44
46
  },
45
47
  async run({ args }) {
48
+ const jsonMode = resolveJsonMode(args);
46
49
  // Mode 1: --token (CI/CD)
47
50
  if (args.token) {
48
- return await saveAndVerify(args.token);
51
+ return await saveAndVerify(args.token, jsonMode);
49
52
  }
50
53
  // Mode 2: --headless (SSH/remote — prompt for API key)
51
54
  if (args.headless) {
@@ -96,16 +99,22 @@ async function headlessLogin() {
96
99
  /**
97
100
  * Validate key against API, save to config, print success.
98
101
  */
99
- async function saveAndVerify(apiKey) {
100
- consola.start("Verifying...");
102
+ async function saveAndVerify(apiKey, jsonMode = false) {
103
+ if (!jsonMode)
104
+ consola.start("Verifying...");
101
105
  const client = new CreekClient(getApiUrl(), apiKey);
102
106
  const session = await client.getSession();
103
107
  if (!session?.user) {
108
+ if (jsonMode)
109
+ jsonOutput({ ok: false, error: "invalid_token", message: "Invalid API key" }, 1);
104
110
  consola.error("Invalid API key. Please check and try again.");
105
111
  process.exit(1);
106
112
  }
107
113
  const config = readCliConfig();
108
114
  writeCliConfig({ ...config, token: apiKey });
115
+ if (jsonMode) {
116
+ jsonOutput({ ok: true, user: session.user.name, email: session.user.email });
117
+ }
109
118
  consola.success(`Logged in as ${session.user.name} (${session.user.email})`);
110
119
  }
111
120
  //# sourceMappingURL=login.js.map
@@ -1,2 +1,13 @@
1
- export declare const whoamiCommand: import("citty").CommandDef<import("citty").ArgsDef>;
1
+ export declare const whoamiCommand: import("citty").CommandDef<{
2
+ json: {
3
+ type: "boolean";
4
+ description: string;
5
+ default: boolean;
6
+ };
7
+ yes: {
8
+ type: "boolean";
9
+ description: string;
10
+ default: boolean;
11
+ };
12
+ }>;
2
13
  //# sourceMappingURL=whoami.d.ts.map
@@ -2,23 +2,39 @@ import { defineCommand } from "citty";
2
2
  import consola from "consola";
3
3
  import { CreekClient } from "@solcreek/sdk";
4
4
  import { getToken, getApiUrl } from "../utils/config.js";
5
+ import { globalArgs, resolveJsonMode, jsonOutput } from "../utils/output.js";
5
6
  export const whoamiCommand = defineCommand({
6
7
  meta: {
7
8
  name: "whoami",
8
9
  description: "Show the currently authenticated user",
9
10
  },
10
- async run() {
11
+ args: { ...globalArgs },
12
+ async run({ args }) {
13
+ const jsonMode = resolveJsonMode(args);
11
14
  const token = getToken();
12
15
  if (!token) {
16
+ if (jsonMode)
17
+ jsonOutput({ ok: false, authenticated: false, error: "not_authenticated" }, 1);
13
18
  consola.error("Not authenticated. Run `creek login` first.");
14
19
  process.exit(1);
15
20
  }
16
21
  const client = new CreekClient(getApiUrl(), token);
17
22
  const session = await client.getSession();
18
23
  if (!session?.user) {
24
+ if (jsonMode)
25
+ jsonOutput({ ok: false, authenticated: false, error: "session_expired" }, 1);
19
26
  consola.error("Session expired or invalid. Run `creek login` to re-authenticate.");
20
27
  process.exit(1);
21
28
  }
29
+ if (jsonMode) {
30
+ jsonOutput({
31
+ ok: true,
32
+ authenticated: true,
33
+ user: session.user.name,
34
+ email: session.user.email,
35
+ api: getApiUrl(),
36
+ });
37
+ }
22
38
  consola.log(` User: ${session.user.name}`);
23
39
  consola.log(` Email: ${session.user.email}`);
24
40
  consola.log(` API: ${getApiUrl()}`);
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Shared output utilities for agent-friendly CLI.
3
+ *
4
+ * --json flag + non-TTY auto-detection ensures every command
5
+ * can produce structured output for agents, CI/CD, and pipes.
6
+ */
7
+ export declare const isTTY: boolean;
8
+ /** Output structured JSON and exit. */
9
+ export declare function jsonOutput(data: Record<string, unknown>, exitCode?: number): never;
10
+ /** Resolve JSON mode: explicit --json flag OR non-TTY environment. */
11
+ export declare function resolveJsonMode(args: {
12
+ json?: boolean;
13
+ }): boolean;
14
+ /**
15
+ * Common --json and --yes args to spread into any command's args definition.
16
+ *
17
+ * Usage:
18
+ * args: { ...globalArgs, myArg: { ... } }
19
+ */
20
+ export declare const globalArgs: {
21
+ json: {
22
+ type: "boolean";
23
+ description: string;
24
+ default: boolean;
25
+ };
26
+ yes: {
27
+ type: "boolean";
28
+ description: string;
29
+ default: boolean;
30
+ };
31
+ };
32
+ /** Should we skip interactive prompts? */
33
+ export declare function shouldAutoConfirm(args: {
34
+ yes?: boolean;
35
+ }): boolean;
36
+ /** Output an error in the appropriate format and exit. */
37
+ export declare function exitError(jsonMode: boolean, error: string, message: string, exitCode?: number): never;
38
+ //# sourceMappingURL=output.d.ts.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Shared output utilities for agent-friendly CLI.
3
+ *
4
+ * --json flag + non-TTY auto-detection ensures every command
5
+ * can produce structured output for agents, CI/CD, and pipes.
6
+ */
7
+ export const isTTY = process.stdout.isTTY ?? false;
8
+ /** Output structured JSON and exit. */
9
+ export function jsonOutput(data, exitCode = 0) {
10
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
11
+ process.exit(exitCode);
12
+ }
13
+ /** Resolve JSON mode: explicit --json flag OR non-TTY environment. */
14
+ export function resolveJsonMode(args) {
15
+ return args.json === true || !isTTY;
16
+ }
17
+ /**
18
+ * Common --json and --yes args to spread into any command's args definition.
19
+ *
20
+ * Usage:
21
+ * args: { ...globalArgs, myArg: { ... } }
22
+ */
23
+ export const globalArgs = {
24
+ json: {
25
+ type: "boolean",
26
+ description: "Output results as JSON (auto-enabled in CI/CD and pipes)",
27
+ default: false,
28
+ },
29
+ yes: {
30
+ type: "boolean",
31
+ description: "Skip confirmation prompts (auto-enabled in non-TTY)",
32
+ default: false,
33
+ },
34
+ };
35
+ /** Should we skip interactive prompts? */
36
+ export function shouldAutoConfirm(args) {
37
+ return args.yes === true || !isTTY;
38
+ }
39
+ /** Output an error in the appropriate format and exit. */
40
+ export function exitError(jsonMode, error, message, exitCode = 1) {
41
+ if (jsonMode)
42
+ jsonOutput({ ok: false, error, message }, exitCode);
43
+ return process.exit(exitCode);
44
+ }
45
+ //# sourceMappingURL=output.js.map
@@ -46,12 +46,9 @@ export async function pollSandboxStatus(statusUrl) {
46
46
  * Print sandbox success message with claim instructions.
47
47
  */
48
48
  export function printSandboxSuccess(previewUrl, expiresAt, sandboxId) {
49
- consola.success(`Deployed! ${previewUrl}`);
49
+ consola.success(` Live ${previewUrl}`);
50
50
  consola.info("");
51
- consola.info("This is a free sandbox preview — it will be available for 60 minutes.");
52
- consola.info("");
53
- consola.info("Want to keep it? Make it permanent:");
54
- consola.info(` creek login`);
55
- consola.info(` creek claim ${sandboxId}`);
51
+ consola.info(" Free preview — available for 60 minutes.");
52
+ consola.info(" Make it permanent: creek login && creek claim " + sandboxId);
56
53
  }
57
54
  //# sourceMappingURL=sandbox.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "creek",
3
- "version": "0.3.0-alpha.11",
3
+ "version": "0.3.0-alpha.13",
4
4
  "description": "CLI for the Creek deployment platform",
5
5
  "type": "module",
6
6
  "bin": {