deploro 0.2.0

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.
Files changed (2) hide show
  1. package/dist/index.js +591 -0
  2. package/package.json +44 -0
package/dist/index.js ADDED
@@ -0,0 +1,591 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/login.ts
7
+ import { createInterface } from "readline";
8
+
9
+ // src/lib/config.ts
10
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync, chmodSync } from "fs";
11
+ import { homedir } from "os";
12
+ import { join, dirname } from "path";
13
+ var CONFIG_PATH = join(homedir(), ".deploro", "credentials.json");
14
+ function readConfig() {
15
+ try {
16
+ if (!existsSync(CONFIG_PATH)) return null;
17
+ const parsed = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
18
+ if (!parsed.accessToken && parsed.sessionToken) parsed.accessToken = parsed.sessionToken;
19
+ return parsed.accessToken ? parsed : null;
20
+ } catch {
21
+ return null;
22
+ }
23
+ }
24
+ function writeConfig(config) {
25
+ const dir = dirname(CONFIG_PATH);
26
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
27
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
28
+ try {
29
+ chmodSync(CONFIG_PATH, 384);
30
+ } catch {
31
+ }
32
+ }
33
+ function requireConfig() {
34
+ const config = readConfig();
35
+ if (!config) {
36
+ console.error("Not logged in. Run: deploro login");
37
+ process.exit(1);
38
+ }
39
+ return config;
40
+ }
41
+ function requireProject() {
42
+ const config = requireConfig();
43
+ if (!config.projectId) {
44
+ console.error("No active project. Run: deploro use <project-id>");
45
+ process.exit(1);
46
+ }
47
+ return config;
48
+ }
49
+ function clearConfig() {
50
+ try {
51
+ unlinkSync(CONFIG_PATH);
52
+ } catch {
53
+ }
54
+ }
55
+
56
+ // src/commands/login.ts
57
+ var DEFAULT_BASE_URL = "https://gallium-worker.calebmensah.workers.dev";
58
+ function ask(rl, question) {
59
+ return new Promise((resolve) => rl.question(question, resolve));
60
+ }
61
+ function askHidden(question) {
62
+ return new Promise((resolve) => {
63
+ process.stdout.write(question);
64
+ process.stdin.setRawMode?.(true);
65
+ process.stdin.resume();
66
+ process.stdin.setEncoding("utf8");
67
+ let value = "";
68
+ const onData = (ch) => {
69
+ if (ch === "\r" || ch === "\n") {
70
+ process.stdin.setRawMode?.(false);
71
+ process.stdin.pause();
72
+ process.stdin.removeListener("data", onData);
73
+ process.stdout.write("\n");
74
+ resolve(value);
75
+ } else if (ch === "") {
76
+ process.exit(1);
77
+ } else if (ch === "\x7F") {
78
+ if (value.length) value = value.slice(0, -1);
79
+ } else {
80
+ value += ch;
81
+ }
82
+ };
83
+ process.stdin.on("data", onData);
84
+ });
85
+ }
86
+ async function loginCommand(opts) {
87
+ const baseUrl = opts.url ?? DEFAULT_BASE_URL;
88
+ if (opts.token) {
89
+ const verify = await fetch(`${baseUrl}/api/projects`, {
90
+ headers: { Authorization: `Bearer ${opts.token}` }
91
+ });
92
+ if (!verify.ok) {
93
+ const body = await verify.json().catch(() => ({}));
94
+ console.error(`Token login failed: ${body.error ?? verify.statusText}`);
95
+ process.exit(1);
96
+ }
97
+ const existing2 = readConfig();
98
+ const visible = (await verify.json().catch(() => ({ projects: [] }))).projects ?? [];
99
+ const projectId = existing2?.projectId && visible.some((p) => p.id === existing2.projectId) ? existing2.projectId : void 0;
100
+ writeConfig({ baseUrl, accessToken: opts.token, projectId });
101
+ console.log(`\u2713 Access token verified and saved for ${baseUrl}`);
102
+ if (!projectId) {
103
+ console.log(" Run 'deploro projects' to list projects, then 'deploro use <slug>' to set the active one.");
104
+ }
105
+ return;
106
+ }
107
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
108
+ const email = await ask(rl, "Email: ");
109
+ rl.close();
110
+ const password = await askHidden("Password: ");
111
+ const res = await fetch(`${baseUrl}/api/auth/login`, {
112
+ method: "POST",
113
+ headers: { "Content-Type": "application/json" },
114
+ body: JSON.stringify({ email, password })
115
+ });
116
+ if (!res.ok) {
117
+ const body = await res.json().catch(() => ({}));
118
+ console.error(`Login failed: ${body.error ?? res.statusText}`);
119
+ process.exit(1);
120
+ }
121
+ const setCookie = res.headers.get("set-cookie") ?? "";
122
+ const match = setCookie.match(/(?:^|,\s*)deploro_session=([^;]+)/);
123
+ if (!match) {
124
+ console.error("Login failed: no session token in response");
125
+ process.exit(1);
126
+ }
127
+ const existing = readConfig();
128
+ writeConfig({ baseUrl, accessToken: match[1], projectId: existing?.projectId });
129
+ console.log(`\u2713 Logged in as ${email}`);
130
+ console.log(` Worker: ${baseUrl}`);
131
+ if (!existing?.projectId) {
132
+ console.log(" Run 'deploro projects' to list projects, then 'deploro use <id>' to set the active one.");
133
+ }
134
+ }
135
+ async function logoutCommand() {
136
+ const config = readConfig();
137
+ if (!config) {
138
+ console.log("Not logged in.");
139
+ return;
140
+ }
141
+ await fetch(`${config.baseUrl}/api/auth/logout`, {
142
+ method: "POST",
143
+ headers: { Cookie: `deploro_session=${config.accessToken}` }
144
+ }).catch(() => {
145
+ });
146
+ clearConfig();
147
+ console.log("\u2713 Logged out \u2014 local credentials cleared");
148
+ }
149
+
150
+ // src/lib/api.ts
151
+ async function apiRequest(path, options, configOverride) {
152
+ const config = configOverride ?? requireConfig();
153
+ const headers = {
154
+ "Content-Type": "application/json",
155
+ Authorization: `Bearer ${config.accessToken}`,
156
+ ...config.projectId ? { "x-project-id": config.projectId } : {},
157
+ ...options?.headers ?? {}
158
+ };
159
+ const res = await fetch(`${config.baseUrl}${path}`, { ...options, headers });
160
+ if (!res.ok) {
161
+ const body = await res.json().catch(() => ({ error: res.statusText }));
162
+ throw new Error(`${res.status} ${body.error ?? res.statusText}`);
163
+ }
164
+ return res.json();
165
+ }
166
+ async function apiRequestNoProject(path, options) {
167
+ const config = requireConfig();
168
+ const res = await fetch(`${config.baseUrl}${path}`, {
169
+ ...options,
170
+ headers: {
171
+ "Content-Type": "application/json",
172
+ Authorization: `Bearer ${config.accessToken}`,
173
+ ...options?.headers ?? {}
174
+ }
175
+ });
176
+ if (!res.ok) {
177
+ const body = await res.json().catch(() => ({ error: res.statusText }));
178
+ throw new Error(`${res.status} ${body.error ?? res.statusText}`);
179
+ }
180
+ return res.json();
181
+ }
182
+
183
+ // src/lib/confirm.ts
184
+ import { createInterface as createInterface2 } from "readline";
185
+ async function confirmOrDie(question, yes) {
186
+ if (yes) return;
187
+ if (!process.stdin.isTTY) {
188
+ console.error("Refusing without confirmation. Re-run with --yes to confirm.");
189
+ process.exit(1);
190
+ }
191
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
192
+ const answer = await new Promise(
193
+ (resolve) => rl.question(`${question} [y/N] `, resolve)
194
+ );
195
+ rl.close();
196
+ const a = answer.trim().toLowerCase();
197
+ if (a !== "y" && a !== "yes") {
198
+ console.log("Aborted.");
199
+ process.exit(0);
200
+ }
201
+ }
202
+
203
+ // src/lib/format.ts
204
+ function table(rows) {
205
+ if (rows.length === 0) {
206
+ console.log("(no results)");
207
+ return;
208
+ }
209
+ const keys = Object.keys(rows[0]);
210
+ const widths = keys.map(
211
+ (k) => Math.max(k.length, ...rows.map((r) => String(r[k] ?? "").length))
212
+ );
213
+ const divider = widths.map((w) => "-".repeat(w + 2)).join("+");
214
+ const header = keys.map((k, i) => ` ${k.padEnd(widths[i])} `).join("|");
215
+ console.log(divider);
216
+ console.log(header);
217
+ console.log(divider);
218
+ for (const row of rows) {
219
+ const line = keys.map((k, i) => ` ${String(row[k] ?? "").padEnd(widths[i])} `).join("|");
220
+ console.log(line);
221
+ }
222
+ console.log(divider);
223
+ }
224
+ function json(data) {
225
+ console.log(JSON.stringify(data, null, 2));
226
+ }
227
+
228
+ // src/commands/db.ts
229
+ async function dbTablesCommand() {
230
+ requireProject();
231
+ const data = await apiRequest("/api/db/tables");
232
+ table(data.tables);
233
+ }
234
+ async function dbQueryCommand(sql, opts) {
235
+ requireProject();
236
+ const data = await apiRequest(
237
+ "/api/db/query",
238
+ { method: "POST", body: JSON.stringify({ sql }) }
239
+ );
240
+ if (opts.json) {
241
+ json(data.rows);
242
+ } else {
243
+ table(data.rows);
244
+ console.log(`
245
+ (${data.rows.length} row${data.rows.length !== 1 ? "s" : ""})`);
246
+ }
247
+ }
248
+ async function dbSchemaCommand(tableName) {
249
+ requireProject();
250
+ const data = await apiRequest(
251
+ `/api/db/schema/${encodeURIComponent(tableName)}`
252
+ );
253
+ table(data.columns);
254
+ }
255
+ async function dbRowsCommand(tableName, opts) {
256
+ requireProject();
257
+ const limit = Math.min(Math.max(1, Number(opts.limit ?? 20) || 20), 200);
258
+ const page = Math.max(1, Number(opts.page ?? 1) || 1);
259
+ const data = await apiRequest(
260
+ `/api/db/tables/${encodeURIComponent(tableName)}?page=${page}&limit=${limit}`
261
+ );
262
+ if (opts.json) {
263
+ json(data.rows);
264
+ } else {
265
+ table(data.rows);
266
+ console.log(`
267
+ (${data.total} total row${data.total !== 1 ? "s" : ""}, page ${data.page})`);
268
+ }
269
+ }
270
+ function parseRowData(raw) {
271
+ let parsed;
272
+ try {
273
+ parsed = JSON.parse(raw);
274
+ } catch {
275
+ console.error(`--data must be a valid JSON object, e.g. '{"name":"Ada"}'`);
276
+ process.exit(1);
277
+ }
278
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
279
+ console.error(`--data must be a JSON object, e.g. '{"name":"Ada"}'`);
280
+ process.exit(1);
281
+ }
282
+ return parsed;
283
+ }
284
+ async function dbInsertCommand(tableName, opts) {
285
+ requireProject();
286
+ const data = await apiRequest(
287
+ `/api/db/rows/${encodeURIComponent(tableName)}`,
288
+ { method: "POST", body: JSON.stringify({ data: parseRowData(opts.data) }) }
289
+ );
290
+ console.log("\u2713 Row inserted:");
291
+ json(data.row);
292
+ }
293
+ async function dbUpdateCommand(tableName, id, opts) {
294
+ requireProject();
295
+ const data = await apiRequest(
296
+ `/api/db/rows/${encodeURIComponent(tableName)}/${encodeURIComponent(id)}`,
297
+ { method: "PATCH", body: JSON.stringify({ data: parseRowData(opts.data) }) }
298
+ );
299
+ console.log("\u2713 Row updated:");
300
+ json(data.row);
301
+ }
302
+ async function dbDeleteRowCommand(tableName, id, opts) {
303
+ requireProject();
304
+ await confirmOrDie(`Delete row ${id} from "${tableName}"?`, opts.yes);
305
+ await apiRequest(
306
+ `/api/db/rows/${encodeURIComponent(tableName)}/${encodeURIComponent(id)}`,
307
+ { method: "DELETE" }
308
+ );
309
+ console.log(`\u2713 Row ${id} deleted from "${tableName}"`);
310
+ }
311
+ async function dbDropTableCommand(tableName, opts) {
312
+ requireProject();
313
+ await confirmOrDie(
314
+ `Drop table "${tableName}" and ALL its data? This cannot be undone.`,
315
+ opts.yes
316
+ );
317
+ await apiRequest(`/api/db/tables/${encodeURIComponent(tableName)}`, { method: "DELETE" });
318
+ console.log(`\u2713 Table "${tableName}" dropped`);
319
+ }
320
+
321
+ // src/commands/auth.ts
322
+ async function authUsersCommand() {
323
+ requireConfig();
324
+ const data = await apiRequest("/api/auth/users");
325
+ table(
326
+ data.users.map((u) => ({
327
+ id: u.id,
328
+ name: u.name,
329
+ email: u.email,
330
+ role: u.role,
331
+ createdAt: typeof u.createdAt === "string" ? u.createdAt.slice(0, 10) : ""
332
+ }))
333
+ );
334
+ }
335
+ async function authInviteCommand(email, opts) {
336
+ requireConfig();
337
+ const role = opts.admin ? "admin" : "member";
338
+ const data = await apiRequest(
339
+ "/api/auth/invite",
340
+ { method: "POST", body: JSON.stringify({ email, role }) }
341
+ );
342
+ console.log(`\u2713 Invite sent to ${email} (${role})`);
343
+ if (data.acceptUrl) {
344
+ console.log(` Accept URL: ${data.acceptUrl}`);
345
+ }
346
+ }
347
+ async function authWhoamiCommand() {
348
+ const config = requireConfig();
349
+ if (!config.accessToken.startsWith("gallium_pat_") && !config.accessToken.startsWith("deploro_pat_")) {
350
+ const res = await fetch(`${config.baseUrl}/api/auth/session`, {
351
+ headers: {
352
+ Authorization: `Bearer ${config.accessToken}`,
353
+ Cookie: `deploro_session=${config.accessToken}`
354
+ }
355
+ });
356
+ const body = await res.json().catch(() => null);
357
+ if (body?.data?.user) {
358
+ const u = body.data.user;
359
+ table([{ id: u.id, name: u.name, email: u.email, role: u.role }]);
360
+ if (body.data.session?.expiresAt) {
361
+ console.log(`Session expires: ${new Date(body.data.session.expiresAt).toLocaleString()}`);
362
+ }
363
+ return;
364
+ }
365
+ console.error("Session is expired or invalid. Run: deploro login");
366
+ process.exit(1);
367
+ }
368
+ const data = await apiRequestNoProject("/api/projects");
369
+ console.log("Authenticated via personal access token.");
370
+ console.log(` Worker : ${config.baseUrl}`);
371
+ console.log(` Projects : ${data.projects.length} visible (the token is scoped to the projects chosen at creation)`);
372
+ }
373
+
374
+ // src/commands/hosting.ts
375
+ async function hostingStatusCommand() {
376
+ requireConfig();
377
+ const data = await apiRequest(
378
+ "/api/hosting/status"
379
+ );
380
+ console.log("\n\u2500\u2500 WORKER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
381
+ if (data.worker) {
382
+ console.log(` Script : ${data.worker.name}`);
383
+ console.log(` Created : ${new Date(data.worker.created_on).toLocaleString()}`);
384
+ console.log(` Last deployed : ${new Date(data.worker.modified_on).toLocaleString()}`);
385
+ } else {
386
+ console.log(" (no worker info \u2014 set an active project with a deployed worker)");
387
+ }
388
+ console.log("\n\u2500\u2500 VPS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
389
+ if (data.vpsError) {
390
+ console.log(` Error: ${data.vpsError}`);
391
+ } else if (data.vps) {
392
+ const v = data.vps;
393
+ const upH = Math.floor(v.uptime / 3600);
394
+ const upM = Math.floor(v.uptime % 3600 / 60);
395
+ console.log(` CPU : ${v.cpu.toFixed(1)}%`);
396
+ console.log(` Memory : ${v.memory.percent.toFixed(1)}% (${v.memory.used} / ${v.memory.total} MB)`);
397
+ console.log(` Disk : ${v.disk.percent.toFixed(1)}% (${v.disk.used} / ${v.disk.total} GB)`);
398
+ console.log(` Uptime : ${upH}h ${upM}m`);
399
+ if (v.docker.length > 0) {
400
+ console.log(` Docker :`);
401
+ for (const c of v.docker) {
402
+ const running = c.status.toLowerCase().includes("up");
403
+ console.log(` ${running ? "\u25CF" : "\u25CB"} ${c.name} ${c.status}`);
404
+ }
405
+ }
406
+ } else {
407
+ console.log(" (no VPS info)");
408
+ }
409
+ console.log("");
410
+ }
411
+ async function hostingSetEnvCommand(key, value) {
412
+ requireConfig();
413
+ await apiRequest("/api/hosting/env", {
414
+ method: "POST",
415
+ body: JSON.stringify({ key, value })
416
+ });
417
+ console.log(`\u2713 Secret '${key}' updated`);
418
+ }
419
+ var BARS = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
420
+ function spark(values) {
421
+ if (values.length === 0) return "(no data)";
422
+ const max = Math.max(...values, 1);
423
+ return values.map((v) => BARS[Math.min(BARS.length - 1, Math.round(v / max * (BARS.length - 1)))]).join("");
424
+ }
425
+ async function hostingMetricsCommand() {
426
+ requireConfig();
427
+ const data = await apiRequest("/api/hosting/metrics");
428
+ const m = data.metrics;
429
+ if (m.length === 0) {
430
+ console.log("No metrics yet.");
431
+ return;
432
+ }
433
+ const last = m[m.length - 1];
434
+ console.log(`
435
+ \u2500\u2500 VPS METRICS (last ${m.length} snapshots, oldest \u2192 newest) \u2500\u2500`);
436
+ console.log(` CPU ${spark(m.map((x) => x.cpu))} ${last.cpu.toFixed(1)}%`);
437
+ console.log(` Memory ${spark(m.map((x) => x.memory_percent))} ${last.memory_percent.toFixed(1)}%`);
438
+ console.log(` Disk ${spark(m.map((x) => x.disk_percent))} ${last.disk_percent.toFixed(1)}%`);
439
+ console.log(` Since ${new Date(m[0].recorded_at).toLocaleString()}`);
440
+ console.log("");
441
+ }
442
+
443
+ // src/commands/projects.ts
444
+ async function projectsListCommand() {
445
+ requireConfig();
446
+ const data = await apiRequestNoProject("/api/projects");
447
+ table(
448
+ data.projects.map((p) => ({
449
+ id: p.id,
450
+ name: p.name,
451
+ slug: p.slug,
452
+ worker: p.cf_worker_name ?? "(none)"
453
+ }))
454
+ );
455
+ console.log("\nTip: 'deploro use <slug>' works with the slug, name, or full id.");
456
+ }
457
+ async function projectsCreateCommand(name, opts) {
458
+ const config = requireConfig();
459
+ const slug = opts.slug ?? name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
460
+ if (!slug) {
461
+ console.error("Could not derive a slug from that name \u2014 pass one with --slug");
462
+ process.exit(1);
463
+ }
464
+ const data = await apiRequestNoProject(
465
+ "/api/projects",
466
+ {
467
+ method: "POST",
468
+ body: JSON.stringify({
469
+ name,
470
+ slug,
471
+ git_repo_url: opts.repo,
472
+ git_branch: opts.branch
473
+ })
474
+ }
475
+ );
476
+ console.log(`\u2713 Project created: ${data.project.name} (${data.project.slug})`);
477
+ console.log(` id : ${data.project.id}`);
478
+ console.log(` database : ${data.project.database_name ?? "(not created \u2014 use the dashboard Database section)"}`);
479
+ writeConfig({ ...config, projectId: data.project.id });
480
+ console.log(" (set as active project)");
481
+ }
482
+ async function useProjectCommand(idOrSlug) {
483
+ const config = requireConfig();
484
+ const data = await apiRequestNoProject("/api/projects");
485
+ const q = idOrSlug.toLowerCase();
486
+ const matches = data.projects.filter(
487
+ (p) => p.id === idOrSlug || p.slug.toLowerCase() === q || p.name.toLowerCase() === q
488
+ );
489
+ if (matches.length === 0) {
490
+ console.error(`No project matches "${idOrSlug}". Run: deploro projects`);
491
+ process.exit(1);
492
+ }
493
+ if (matches.length > 1) {
494
+ console.error(
495
+ `"${idOrSlug}" is ambiguous (matches: ${matches.map((m) => m.slug).join(", ")}). Use the full id.`
496
+ );
497
+ process.exit(1);
498
+ }
499
+ writeConfig({ ...config, projectId: matches[0].id });
500
+ console.log(`\u2713 Active project: ${matches[0].name} (${matches[0].slug})`);
501
+ }
502
+ async function deployCommand() {
503
+ const config = requireProject();
504
+ const data = await apiRequest(
505
+ `/api/projects/${config.projectId}/deployments`,
506
+ { method: "POST" }
507
+ );
508
+ console.log(`\u2713 Deployment triggered: ${data.deployment.id}`);
509
+ console.log(" Run 'deploro deploy status' to poll progress, or 'deploro deploy logs' for output.");
510
+ }
511
+ async function deployStatusCommand() {
512
+ const config = requireProject();
513
+ const data = await apiRequest(
514
+ `/api/projects/${config.projectId}/deployments`
515
+ );
516
+ const latest = data.deployments[0];
517
+ if (!latest) {
518
+ console.log("No deployments yet.");
519
+ return;
520
+ }
521
+ console.log(`
522
+ \u2500\u2500 LATEST DEPLOYMENT \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
523
+ console.log(` ID : ${latest.id}`);
524
+ console.log(` Status : ${latest.status}`);
525
+ console.log(` Branch : ${latest.git_branch ?? "(none)"}`);
526
+ console.log(` Started : ${new Date(latest.created_at).toLocaleString()}`);
527
+ console.log(` Finished: ${latest.finished_at ? new Date(latest.finished_at).toLocaleString() : "(in progress)"}`);
528
+ console.log("");
529
+ }
530
+ async function deployLogsCommand(deploymentId) {
531
+ const config = requireProject();
532
+ let id = deploymentId;
533
+ if (!id) {
534
+ const list = await apiRequest(
535
+ `/api/projects/${config.projectId}/deployments`
536
+ );
537
+ id = list.deployments[0]?.id;
538
+ if (!id) {
539
+ console.log("No deployments yet.");
540
+ return;
541
+ }
542
+ }
543
+ const data = await apiRequest(
544
+ `/api/projects/${config.projectId}/deployments/${id}`
545
+ );
546
+ const d = data.deployment;
547
+ console.log(`
548
+ \u2500\u2500 DEPLOYMENT ${d.id} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
549
+ console.log(` Status : ${d.status}`);
550
+ console.log(
551
+ ` Commit : ${d.git_branch ?? "(none)"}${d.git_sha ? ` @ ${String(d.git_sha).slice(0, 7)}` : ""}`
552
+ );
553
+ if (d.ai_error_summary) {
554
+ console.log(` AI summary: ${d.ai_error_summary}`);
555
+ }
556
+ console.log(`
557
+ \u2500\u2500 LOGS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
558
+ console.log(typeof d.logs === "string" && d.logs.trim().length > 0 ? d.logs : "(no logs)");
559
+ console.log("");
560
+ }
561
+
562
+ // src/index.ts
563
+ var program = new Command();
564
+ program.name("deploro").description("Deploro developer platform CLI").version("0.2.0");
565
+ program.command("login").description("Log in to Deploro").option("--url <url>", "Worker base URL").option("--token <token>", "Personal access token from the Deploro dashboard").action(loginCommand);
566
+ program.command("logout").description("Log out and clear local credentials").action(logoutCommand);
567
+ program.command("projects").description("List projects").action(projectsListCommand);
568
+ program.command("create <name>").description("Create a new project (and set it active)").option("--slug <slug>", "Project slug (derived from name if omitted)").option("--repo <url>", "GitHub repo URL to link for deployments").option("--branch <branch>", "Git branch to deploy from").action(projectsCreateCommand);
569
+ program.command("use <project>").description("Set the active project (accepts id, slug, or name)").action(useProjectCommand);
570
+ var db = program.command("db").description("Database commands");
571
+ db.command("tables").description("List tables in the active project").action(dbTablesCommand);
572
+ db.command("rows <table>").description("List rows in a table").option("--limit <n>", "Rows per page (max 200)", "20").option("--page <n>", "Page number", "1").option("--json", "Output raw JSON").action(dbRowsCommand);
573
+ db.command("query <sql>").description("Run a SQL query (project admin)").option("--json", "Output raw JSON").action(dbQueryCommand);
574
+ db.command("schema <table>").description("Show schema for a table").action(dbSchemaCommand);
575
+ db.command("insert <table>").description("Insert a row").requiredOption("--data <json>", "Row data as a JSON object").action(dbInsertCommand);
576
+ db.command("update <table> <id>").description("Update a row by id").requiredOption("--data <json>", "Fields to update as a JSON object").action(dbUpdateCommand);
577
+ db.command("delete-row <table> <id>").description("Delete a row by id").option("--yes", "Skip confirmation prompt").action(dbDeleteRowCommand);
578
+ db.command("drop <table>").description("Drop a table and all its data (project admin)").option("--yes", "Skip confirmation prompt").action(dbDropTableCommand);
579
+ var auth = program.command("auth").description("Auth management commands");
580
+ auth.command("whoami").description("Show the current identity").action(authWhoamiCommand);
581
+ auth.command("users").description("List platform users").action(authUsersCommand);
582
+ auth.command("invite <email>").description("Invite a user by email").option("--admin", "Grant admin role").action(authInviteCommand);
583
+ var hosting = program.command("hosting").description("Hosting commands");
584
+ hosting.command("status").description("Show Worker and VPS health").action(hostingStatusCommand);
585
+ hosting.command("metrics").description("Show recent VPS CPU/memory/disk sparklines").action(hostingMetricsCommand);
586
+ hosting.command("set-env <key> <value>").description("Set a Worker secret").action(hostingSetEnvCommand);
587
+ var deploy = program.command("deploy").description("Deployment commands");
588
+ deploy.command("trigger").description("Trigger a manual deployment").action(deployCommand);
589
+ deploy.command("status").description("Show latest deployment status").action(deployStatusCommand);
590
+ deploy.command("logs [deployment-id]").description("Show logs for a deployment (defaults to latest)").action(deployLogsCommand);
591
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "deploro",
3
+ "version": "0.2.0",
4
+ "description": "Command-line interface for the Deploro developer platform — manage projects, databases, deployments, and hosting from your terminal.",
5
+ "type": "module",
6
+ "bin": {
7
+ "deploro": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "engines": {
13
+ "node": ">=18"
14
+ },
15
+ "keywords": [
16
+ "deploro",
17
+ "cli",
18
+ "cloudflare",
19
+ "postgres",
20
+ "deployment"
21
+ ],
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/yawdeals1/deploro.git",
26
+ "directory": "packages/cli"
27
+ },
28
+ "homepage": "https://deploro.com",
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "scripts": {
33
+ "build": "tsup src/index.ts --format esm --no-splitting --target node18 --out-dir dist",
34
+ "dev": "tsup src/index.ts --format esm --no-splitting --target node18 --out-dir dist --watch"
35
+ },
36
+ "dependencies": {
37
+ "commander": "^13.0.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^22.0.0",
41
+ "tsup": "^8.0.0",
42
+ "typescript": "^5.5.0"
43
+ }
44
+ }