ardent-cli 0.0.2 → 0.0.5

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 +642 -236
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command7 } from "commander";
4
+ import { readFileSync as readFileSync2 } from "fs";
5
+ import { Command as Command6 } from "commander";
5
6
 
6
- // src/commands/branch.ts
7
+ // src/commands/branch/index.ts
7
8
  import { Command } from "commander";
8
9
 
9
10
  // src/lib/config.ts
@@ -95,13 +96,43 @@ function clearCurrentBranch() {
95
96
  delete config.currentBranch;
96
97
  saveConfig(config);
97
98
  }
99
+ async function bootstrapCache() {
100
+ const token = getToken();
101
+ if (!token) throw new Error("Not authenticated");
102
+ const apiUrl = getApiUrl();
103
+ const headers = {
104
+ "Content-Type": "application/json",
105
+ "Authorization": `Bearer ${token}`
106
+ };
107
+ const [connectorsRes, branchesRes] = await Promise.all([
108
+ fetch(`${apiUrl}/v1/cli/connectors`, { headers }),
109
+ fetch(`${apiUrl}/v1/cli/branches`, { headers })
110
+ ]);
111
+ let connectorCount = 0;
112
+ let branchCount = 0;
113
+ if (connectorsRes.ok) {
114
+ const data = await connectorsRes.json();
115
+ const connectors = data.connectors || [];
116
+ setCacheEntry("connectors", connectors);
117
+ connectorCount = connectors.length;
118
+ }
119
+ if (branchesRes.ok) {
120
+ const data = await branchesRes.json();
121
+ const branches = data.branches || [];
122
+ setCacheEntry("branches", branches);
123
+ branchCount = branches.length;
124
+ }
125
+ return { connectors: connectorCount, branches: branchCount };
126
+ }
98
127
 
99
128
  // src/lib/api.ts
100
129
  var ApiClient = class {
101
130
  getHeaders() {
102
131
  const token = getToken();
103
132
  if (!token) {
104
- console.error("\u2717 Not authenticated. Run: ardent login");
133
+ const green3 = "\x1B[32m";
134
+ const reset3 = "\x1B[0m";
135
+ console.error(`\u2717 Not authenticated. Run: ${green3}ardent login${reset3}`);
105
136
  process.exit(1);
106
137
  }
107
138
  return {
@@ -147,6 +178,19 @@ var ApiClient = class {
147
178
  }
148
179
  return response.json();
149
180
  }
181
+ async patch(path, body) {
182
+ const url = `${getApiUrl()}${path}`;
183
+ const response = await fetch(url, {
184
+ method: "PATCH",
185
+ headers: this.getHeaders(),
186
+ body: JSON.stringify(body)
187
+ });
188
+ if (!response.ok) {
189
+ const text = await response.text();
190
+ throw new Error(`API error ${response.status}: ${text}`);
191
+ }
192
+ return response.json();
193
+ }
150
194
  };
151
195
  var api = new ApiClient();
152
196
  function isNetworkError(err) {
@@ -159,10 +203,15 @@ function isNetworkError(err) {
159
203
  }
160
204
  return false;
161
205
  }
206
+ function isPermissionError(err) {
207
+ if (err instanceof Error) {
208
+ return err.message.includes("API error 403") || err.message.includes("API error 401");
209
+ }
210
+ return false;
211
+ }
162
212
 
163
- // src/commands/branch.ts
164
- var branchCommand = new Command("branch").description("Manage database branches");
165
- branchCommand.command("create <name>").description("Create a new database branch (e.g., ardent branch create my-feature)").option("-s, --service <type>", "Service type", "postgres").action(async (name, options) => {
213
+ // src/commands/branch/create.ts
214
+ async function createAction(name, options) {
166
215
  try {
167
216
  const job = await api.post("/v1/cli/jobs/create", {
168
217
  name
@@ -204,8 +253,10 @@ ${branch.branch_url}`);
204
253
  console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
205
254
  process.exit(1);
206
255
  }
207
- });
208
- branchCommand.command("list").description("List your branches").action(async () => {
256
+ }
257
+
258
+ // src/commands/branch/list.ts
259
+ async function listAction() {
209
260
  let branches = [];
210
261
  let fromCache = false;
211
262
  let cacheTime = "";
@@ -242,32 +293,34 @@ branchCommand.command("list").description("List your branches").action(async ()
242
293
  return;
243
294
  }
244
295
  const current = getCurrentBranch();
245
- const green2 = "\x1B[32m";
246
- const dim2 = "\x1B[2m";
247
- const reset2 = "\x1B[0m";
296
+ const green3 = "\x1B[32m";
297
+ const dim3 = "\x1B[2m";
298
+ const reset3 = "\x1B[0m";
248
299
  console.log("Branches:\n");
249
300
  for (const branch of branches) {
250
301
  const isCurrent = branch.name === current;
251
302
  const icon = branch.status === "active" || branch.status === "active_degraded" ? "\u25CF" : "\u25CB";
252
303
  if (isCurrent) {
253
- console.log(`${green2}* ${icon} ${branch.name}${reset2}`);
304
+ console.log(`${green3}* ${icon} ${branch.name}${reset3}`);
254
305
  if (branch.branch_url) {
255
- console.log(`${green2} ${branch.branch_url}${reset2}`);
306
+ console.log(`${green3} ${branch.branch_url}${reset3}`);
256
307
  }
257
308
  } else {
258
309
  console.log(` ${icon} ${branch.name}`);
259
310
  if (branch.branch_url) {
260
- console.log(`${dim2} ${branch.branch_url}${reset2}`);
311
+ console.log(`${dim3} ${branch.branch_url}${reset3}`);
261
312
  }
262
313
  }
263
314
  console.log();
264
315
  }
265
- });
266
- branchCommand.command("info [name]").description("Show branch details (defaults to current branch)").action((name) => {
316
+ }
317
+
318
+ // src/commands/branch/info.ts
319
+ function infoAction(name) {
267
320
  const branchName = name || getCurrentBranch();
268
321
  if (!branchName) {
269
322
  console.error("\u2717 No branch specified and no current branch set");
270
- console.log(" Run: ardent switch <name> or specify a branch name");
323
+ console.log(" Run: ardent branch switch <name> or specify a branch name");
271
324
  return;
272
325
  }
273
326
  const cached = getCacheEntry("branches");
@@ -288,8 +341,10 @@ branchCommand.command("info [name]").description("Show branch details (defaults
288
341
  console.log(`
289
342
  URL: ${branch.branch_url}`);
290
343
  }
291
- });
292
- branchCommand.command("delete <name>").description("Delete a branch (e.g., ardent branch delete my-feature)").action(async (name) => {
344
+ }
345
+
346
+ // src/commands/branch/delete.ts
347
+ async function deleteAction(name) {
293
348
  const cached = getCacheEntry("branches");
294
349
  const branch = cached?.data.find((cachedBranch) => cachedBranch.name === name);
295
350
  if (!branch) {
@@ -313,118 +368,52 @@ branchCommand.command("delete <name>").description("Delete a branch (e.g., arden
313
368
  console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
314
369
  process.exit(1);
315
370
  }
316
- });
317
-
318
- // src/commands/connector.ts
319
- import { Command as Command2 } from "commander";
320
- function parsePostgresUrl(url) {
321
- const atIndex = url.lastIndexOf("@");
322
- const credentialsPart = atIndex > 0 ? url.substring(0, atIndex) : url;
323
- const hostPart = atIndex > 0 ? url.substring(atIndex) : "";
324
- const cleanedUrl = credentialsPart.replace(/\\!/g, "!") + hostPart;
325
- const parsed = new URL(cleanedUrl);
326
- const host = parsed.hostname;
327
- const port = parsed.port || "5432";
328
- const username = decodeURIComponent(parsed.username);
329
- const password = parsed.password ? decodeURIComponent(parsed.password) : "";
330
- if (!host) throw new Error("Host required in connection URL");
331
- if (!username) throw new Error("Username required in connection URL");
332
- return { host, port, username, password };
333
371
  }
334
- var connectorCommand = new Command2("connector").description("Manage database connectors");
335
- connectorCommand.command("create <type> <url>").description("Create a new connector (e.g., ardent connector create postgresql postgresql://user:pass@host/db)").option("-n, --name <name>", "Connector name").action(async (type, url, options) => {
336
- const supportedTypes = ["postgresql"];
337
- if (!supportedTypes.includes(type.toLowerCase())) {
338
- console.error(`\u2717 Unsupported type: ${type}`);
339
- console.error(` Supported: ${supportedTypes.join(", ")}`);
340
- process.exit(1);
341
- }
342
- try {
343
- const parsed = parsePostgresUrl(url);
344
- if (!parsed.password) {
345
- console.error("\u2717 Password required in connection URL");
346
- console.error(" Example: postgresql://user:password@host:5432/db");
347
- process.exit(1);
348
- }
349
- const connectionDetails = {
350
- host: parsed.host,
351
- port: parsed.port,
352
- username: parsed.username,
353
- password: parsed.password
354
- };
355
- const connectorName = options.name || "My PostgreSQL Connection";
356
- console.log("Creating connector...");
357
- const created = await api.post("/v1/connectors", {
358
- name: connectorName,
359
- service_name: "postgresql",
360
- connection_details: connectionDetails
361
- });
362
- const connectorId = created.id;
363
- console.log("Discovering schema...");
364
- await api.post(`/v1/connectors/${connectorId}/discover`, {});
365
- console.log("Setting selection...");
366
- await api.post(`/v1/connectors/${connectorId}/selection`, {
367
- selected_paths: ["*"]
368
- });
369
- const connector = await api.get(`/v1/connectors/${connectorId}`);
370
- if (connector.branching_engine_status === "configuration_verified") {
371
- console.log("Setting up branching engine...");
372
- await api.post(`/v1/connectors/${connectorId}/engine-setup`, {});
372
+
373
+ // src/commands/branch/switch.ts
374
+ async function switchAction(name) {
375
+ let cached = getCacheEntry("branches");
376
+ let branch = cached?.data.find((cachedBranch) => cachedBranch.name === name);
377
+ if (!branch) {
378
+ try {
379
+ const result = await api.get("/v1/cli/branches");
380
+ if (!result.branches) {
381
+ throw new Error("API returned invalid response: missing branches array");
382
+ }
383
+ setCacheEntry("branches", result.branches);
384
+ branch = result.branches.find((remoteBranch) => remoteBranch.name === name);
385
+ } catch (err) {
386
+ if (isNetworkError(err)) {
387
+ if (!branch) {
388
+ console.error(`\u2717 Branch "${name}" not found in cache`);
389
+ console.log(" Run 'ardent branch list' when online to refresh");
390
+ process.exit(1);
391
+ }
392
+ } else {
393
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
394
+ process.exit(1);
395
+ }
373
396
  }
374
- console.log("\u2713 Connector created and ready");
375
- console.log(` ID: ${connectorId}`);
376
- } catch (err) {
377
- console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
378
- process.exit(1);
379
397
  }
380
- });
381
- connectorCommand.command("list").description("List your connectors").action(async () => {
382
- try {
383
- const result = await api.get("/v1/cli/connectors");
384
- if (!result.connectors) {
385
- throw new Error("API returned invalid response: missing connectors array");
386
- }
387
- const connectors = result.connectors;
388
- if (connectors.length === 0) {
389
- console.log("No connectors found");
390
- console.log(" Create one with: ardent connector create postgresql <url>");
391
- return;
392
- }
393
- console.log("Connectors:\n");
394
- for (const connector of connectors) {
395
- const icon = connector.status === "healthy" ? "\u25CF" : "\u25CB";
396
- console.log(` ${icon} ${connector.name} (${connector.service_name})`);
397
- console.log(` ID: ${connector.id}`);
398
- console.log();
399
- }
400
- } catch (err) {
401
- console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
398
+ if (!branch) {
399
+ console.error(`\u2717 Branch "${name}" not found`);
400
+ console.log(" Run: ardent branch list");
402
401
  process.exit(1);
403
402
  }
404
- });
405
- connectorCommand.command("delete <name>").description("Delete a connector by name (e.g., ardent connector delete 'My PostgreSQL Connection')").action(async (name) => {
406
- try {
407
- const result = await api.get("/v1/cli/connectors");
408
- if (!result.connectors) {
409
- throw new Error("API returned invalid response: missing connectors array");
410
- }
411
- const connector = result.connectors.find((existingConnector) => existingConnector.name === name);
412
- if (!connector) {
413
- console.error(`\u2717 Connector "${name}" not found`);
414
- console.log(" Run: ardent connector list");
415
- process.exit(1);
416
- }
417
- console.log("Deleting connector...");
418
- await api.delete(`/v1/connectors/${connector.id}`);
419
- console.log("\u2713 Connector deleted");
420
- } catch (err) {
421
- console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
422
- process.exit(1);
403
+ const previous = getCurrentBranch();
404
+ setCurrentBranch(name);
405
+ if (previous && previous !== name) {
406
+ console.log(`Switched from '${previous}' to '${name}'`);
407
+ } else {
408
+ console.log(`Switched to branch '${name}'`);
423
409
  }
424
- });
410
+ if (branch.branch_url) {
411
+ console.log(`
412
+ ${branch.branch_url}`);
413
+ }
414
+ }
425
415
 
426
- // src/commands/diff.ts
427
- import { Command as Command3 } from "commander";
416
+ // src/commands/branch/diff.ts
428
417
  var green = "\x1B[32m";
429
418
  var red = "\x1B[31m";
430
419
  var yellow = "\x1B[33m";
@@ -438,7 +427,18 @@ function truncate(str, maxLen) {
438
427
  if (str.length <= maxLen) return str;
439
428
  return str.slice(0, maxLen - 2) + "..";
440
429
  }
441
- var diffCommand = new Command3("diff").description("Show CDC changes on current branch").option("-b, --branch <name>", "Branch name (defaults to current)").option("--sql", "Output as replay SQL").action(async (options) => {
430
+ function parseDiffRow(raw) {
431
+ return {
432
+ schema_name: raw.schema_name,
433
+ table_name: raw.table_name,
434
+ primary_key: raw.primary_key,
435
+ first_op: raw.first_op,
436
+ last_op: raw.last_op,
437
+ first_data: raw.original_values ? JSON.parse(raw.original_values) : null,
438
+ last_data: raw.final_values ? JSON.parse(raw.final_values) : null
439
+ };
440
+ }
441
+ async function diffAction(options) {
442
442
  let branch;
443
443
  if (options.branch) {
444
444
  branch = getBranchByName(options.branch);
@@ -455,18 +455,20 @@ var diffCommand = new Command3("diff").description("Show CDC changes on current
455
455
  console.log(" Run: ardent branch list");
456
456
  } else {
457
457
  console.error("\u2717 No current branch set");
458
- console.log(" Run: ardent switch <name> or ardent branch create <name>");
458
+ console.log(" Run: ardent branch switch <name> or ardent branch create <name>");
459
459
  }
460
460
  return;
461
461
  }
462
462
  try {
463
- const state = await api.get(`/v1/branches/${branch.job_id}/state`);
464
- if (!state || Object.keys(state).length === 0) {
463
+ const stateRaw = await api.get(`/v1/branches/${branch.job_id}/state`);
464
+ if (!stateRaw || Object.keys(stateRaw).length === 0) {
465
465
  console.log("No changes captured");
466
466
  return;
467
467
  }
468
- for (const [_branchId, branchState] of Object.entries(state)) {
469
- const { diff, ddl, replay_sql } = branchState;
468
+ for (const [_branchId, branchStateRaw] of Object.entries(stateRaw)) {
469
+ const ddl = branchStateRaw.ddl;
470
+ const replay_sql = branchStateRaw.replay_sql;
471
+ const diff = (branchStateRaw.diff || []).map(parseDiffRow);
470
472
  if (options.sql) {
471
473
  if (replay_sql) {
472
474
  console.log(replay_sql);
@@ -619,13 +621,320 @@ var diffCommand = new Command3("diff").description("Show CDC changes on current
619
621
  console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
620
622
  process.exit(1);
621
623
  }
622
- });
624
+ }
623
625
 
624
- // src/commands/invite.ts
625
- import { Command as Command4 } from "commander";
626
- async function sendInvite(email, role) {
626
+ // src/commands/branch/index.ts
627
+ var branchCommand = new Command("branch").description("Manage database branches");
628
+ branchCommand.command("create <name>").description("Create a new database branch").option("-s, --service <type>", "Service type", "postgres").action(createAction);
629
+ branchCommand.command("list").description("List your branches").action(listAction);
630
+ branchCommand.command("info [name]").description("Show branch details (defaults to current branch)").action(infoAction);
631
+ branchCommand.command("delete <name>").description("Delete a branch").action(deleteAction);
632
+ branchCommand.command("switch <name>").description("Switch to a different branch").action(switchAction);
633
+ branchCommand.command("diff").description("Show all changes since branch creation").option("-b, --branch <name>", "Branch name (defaults to current)").option("--sql", "Output as replay SQL").action(diffAction);
634
+
635
+ // src/commands/connector/index.ts
636
+ import { Command as Command2 } from "commander";
637
+
638
+ // src/lib/onboarding.ts
639
+ var BANNER = `
640
+ \u250C\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\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\u2510
641
+ \u2502 \u2502
642
+ \u2502 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2502
643
+ \u2502 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D \u2502
644
+ \u2502 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2502
645
+ \u2502 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2502
646
+ \u2502 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2502
647
+ \u2502 \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u2502
648
+ \u2502 \u2502
649
+ \u2502 Git for your data infrastructure \u2502
650
+ \u2502 \u2502
651
+ \u2514\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\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\u2518
652
+ `;
653
+ function getOnboardingState() {
654
+ const token = getToken();
655
+ if (!token) {
656
+ return { state: "not_logged_in", connectorCount: 0, branchCount: 0, currentBranch: void 0 };
657
+ }
658
+ const connectors = getCacheEntry("connectors");
659
+ const connectorCount = connectors?.data?.length || 0;
660
+ if (connectorCount === 0) {
661
+ return { state: "no_connectors", connectorCount: 0, branchCount: 0, currentBranch: void 0 };
662
+ }
663
+ const branches = getCacheEntry("branches");
664
+ const branchCount = branches?.data?.length || 0;
665
+ const currentBranch = getCurrentBranch();
666
+ if (branchCount === 0) {
667
+ return { state: "no_branches", connectorCount, branchCount: 0, currentBranch: void 0 };
668
+ }
669
+ return { state: "ready", connectorCount, branchCount, currentBranch };
670
+ }
671
+ var dim2 = "\x1B[2m";
672
+ var reset2 = "\x1B[0m";
673
+ var green2 = "\x1B[32m";
674
+ function showOnboarding() {
675
+ const { state, connectorCount, branchCount, currentBranch } = getOnboardingState();
676
+ switch (state) {
677
+ case "not_logged_in":
678
+ console.log(BANNER);
679
+ console.log(`To get started, log in with your GitHub account:
680
+
681
+ ${green2}ardent login${reset2}
682
+
683
+ Run ${dim2}ardent --help${reset2} to see all commands.
684
+ `);
685
+ break;
686
+ case "no_connectors":
687
+ console.log(`
688
+ ${green2}\u2713${reset2} Logged in
689
+
690
+ Next step \u2014 connect your first database:
691
+
692
+ ${green2}ardent connector create <type> <connection-url>${reset2}
693
+
694
+ Example:
695
+ ${dim2}ardent connector create postgresql postgresql://user:password@host:5432/database${reset2}
696
+
697
+ Run ${dim2}ardent --help${reset2} to see all commands.
698
+ `);
699
+ break;
700
+ case "no_branches":
701
+ console.log(`
702
+ ${green2}\u2713${reset2} Logged in ${green2}\u2713${reset2} ${connectorCount} connector${connectorCount !== 1 ? "s" : ""}
703
+
704
+ You're all set! Create your first branch:
705
+
706
+ ${green2}ardent branch create <name>${reset2}
707
+
708
+ Example:
709
+ ${dim2}ardent branch create my-feature${reset2}
710
+
711
+ Run ${dim2}ardent --help${reset2} to see all commands.
712
+ `);
713
+ return true;
714
+ case "ready":
715
+ return false;
716
+ }
717
+ return true;
718
+ }
719
+ function showNextStep() {
720
+ const { state } = getOnboardingState();
721
+ switch (state) {
722
+ case "no_connectors":
723
+ console.log(`
724
+ Next step \u2014 connect your first database:
725
+
726
+ ${green2}ardent connector create <type> <connection-url>${reset2}
727
+
728
+ Example:
729
+ ${dim2}ardent connector create postgresql postgresql://user:password@host:5432/database${reset2}
730
+ `);
731
+ break;
732
+ case "no_branches":
733
+ console.log(`
734
+ Next step \u2014 create your first branch:
735
+
736
+ ${green2}ardent branch create <name>${reset2}
737
+
738
+ Example:
739
+ ${dim2}ardent branch create my-feature${reset2}
740
+ `);
741
+ break;
742
+ case "ready":
743
+ break;
744
+ }
745
+ }
746
+
747
+ // src/commands/connector/create.ts
748
+ function parsePostgresUrl(url) {
749
+ const atIndex = url.lastIndexOf("@");
750
+ const credentialsPart = atIndex > 0 ? url.substring(0, atIndex) : url;
751
+ const hostPart = atIndex > 0 ? url.substring(atIndex) : "";
752
+ const cleanedUrl = credentialsPart.replace(/\\!/g, "!") + hostPart;
753
+ const parsed = new URL(cleanedUrl);
754
+ const host = parsed.hostname;
755
+ const port = parsed.port || "5432";
756
+ const username = decodeURIComponent(parsed.username);
757
+ const password = parsed.password ? decodeURIComponent(parsed.password) : "";
758
+ if (!host) throw new Error("Host required in connection URL");
759
+ if (!username) throw new Error("Username required in connection URL");
760
+ return { host, port, username, password };
761
+ }
762
+ async function createAction2(type, url, options) {
763
+ const supportedTypes = ["postgresql"];
764
+ if (!supportedTypes.includes(type.toLowerCase())) {
765
+ console.error(`\u2717 Unsupported type: ${type}`);
766
+ console.error(` Supported: ${supportedTypes.join(", ")}`);
767
+ process.exit(1);
768
+ }
769
+ try {
770
+ const parsed = parsePostgresUrl(url);
771
+ if (!parsed.password) {
772
+ console.error("\u2717 Password required in connection URL");
773
+ console.error(" Example: postgresql://user:password@host:5432/db");
774
+ process.exit(1);
775
+ }
776
+ const connectionDetails = {
777
+ host: parsed.host,
778
+ port: parsed.port,
779
+ username: parsed.username,
780
+ password: parsed.password
781
+ };
782
+ const connectorName = options.name || "my-postgresql-connection";
783
+ console.log("Creating connector...");
784
+ const created = await api.post("/v1/connectors", {
785
+ name: connectorName,
786
+ service_name: "postgresql",
787
+ connection_details: connectionDetails
788
+ });
789
+ const connectorId = created.id;
790
+ console.log("Discovering schema...");
791
+ await api.post(`/v1/connectors/${connectorId}/discover`, {});
792
+ console.log("Setting selection...");
793
+ await api.post(`/v1/connectors/${connectorId}/selection`, {
794
+ selected_paths: ["*"]
795
+ });
796
+ const connector = await api.get(`/v1/connectors/${connectorId}`);
797
+ if (connector.branching_engine_status === "configuration_verified") {
798
+ console.log("Setting up branching engine...");
799
+ await api.post(`/v1/connectors/${connectorId}/engine-setup`, {});
800
+ }
801
+ const newConnector = {
802
+ id: connectorId,
803
+ name: connectorName,
804
+ service_name: "postgresql",
805
+ status: connector.connection_status === "connected" ? "healthy" : "unknown"
806
+ };
807
+ const cached = getCacheEntry("connectors");
808
+ const cachedConnectors = cached?.data || [];
809
+ cachedConnectors.push(newConnector);
810
+ setCacheEntry("connectors", cachedConnectors);
811
+ console.log("\u2713 Connector created and ready");
812
+ console.log(` ID: ${connectorId}`);
813
+ showNextStep();
814
+ } catch (err) {
815
+ if (isPermissionError(err)) {
816
+ console.error("\u2717 You don't have permission to create connectors.");
817
+ console.error("");
818
+ console.error(" Ask your organization admin to either:");
819
+ console.error(" \u2022 Create the connector for you, or");
820
+ console.error(" \u2022 Upgrade your role to Admin");
821
+ process.exit(1);
822
+ }
823
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
824
+ process.exit(1);
825
+ }
826
+ }
827
+
828
+ // src/commands/connector/list.ts
829
+ async function listAction2() {
830
+ let connectors = [];
831
+ let fromCache = false;
832
+ let cacheTime = "";
833
+ try {
834
+ const result = await api.get("/v1/cli/connectors");
835
+ if (!result.connectors) {
836
+ throw new Error("API returned invalid response: missing connectors array");
837
+ }
838
+ connectors = result.connectors;
839
+ setCacheEntry("connectors", connectors);
840
+ } catch (err) {
841
+ if (isNetworkError(err)) {
842
+ const cached = getCacheEntry("connectors");
843
+ if (cached) {
844
+ connectors = cached.data;
845
+ fromCache = true;
846
+ cacheTime = formatCacheTime(cached.updated_at);
847
+ } else {
848
+ console.error("\u2717 Offline and no cached data available");
849
+ process.exit(1);
850
+ }
851
+ } else {
852
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
853
+ process.exit(1);
854
+ }
855
+ }
856
+ if (fromCache) {
857
+ console.log(`\u26A0 Offline - showing cached data from ${cacheTime}
858
+ `);
859
+ }
860
+ if (connectors.length === 0) {
861
+ console.log("No connectors found");
862
+ console.log(" Create one with: ardent connector create postgresql <url>");
863
+ return;
864
+ }
865
+ console.log("Connectors:\n");
866
+ for (const connector of connectors) {
867
+ const icon = connector.status === "healthy" ? "\u25CF" : "\u25CB";
868
+ console.log(` ${icon} ${connector.service_name}`);
869
+ console.log(` Name: ${connector.name}`);
870
+ console.log();
871
+ }
872
+ }
873
+
874
+ // src/commands/connector/delete.ts
875
+ async function deleteAction2(name) {
876
+ const cached = getCacheEntry("connectors");
877
+ let connector = cached?.data.find((c) => c.name === name);
878
+ if (!connector) {
879
+ try {
880
+ const result = await api.get("/v1/cli/connectors");
881
+ if (result.connectors) {
882
+ setCacheEntry("connectors", result.connectors);
883
+ connector = result.connectors.find((c) => c.name === name);
884
+ }
885
+ } catch (err) {
886
+ if (isNetworkError(err)) {
887
+ console.error("\u2717 Connector not found in cache and offline");
888
+ process.exit(1);
889
+ }
890
+ throw err;
891
+ }
892
+ }
893
+ if (!connector) {
894
+ console.error(`\u2717 Connector "${name}" not found`);
895
+ console.log(" Run: ardent connector list");
896
+ process.exit(1);
897
+ }
898
+ try {
899
+ console.log("Deleting connector...");
900
+ await api.delete(`/v1/connectors/${connector.id}`);
901
+ const currentCache = getCacheEntry("connectors");
902
+ if (currentCache?.data) {
903
+ const updatedConnectors = currentCache.data.filter((c) => c.id !== connector.id);
904
+ setCacheEntry("connectors", updatedConnectors);
905
+ }
906
+ console.log("\u2713 Connector deleted");
907
+ } catch (err) {
908
+ if (isNetworkError(err)) {
909
+ console.error("\u2717 Cannot delete connector while offline");
910
+ process.exit(1);
911
+ }
912
+ if (isPermissionError(err)) {
913
+ console.error("\u2717 You don't have permission to delete connectors.");
914
+ console.error("");
915
+ console.error(" Ask your organization admin to either:");
916
+ console.error(" \u2022 Delete the connector for you, or");
917
+ console.error(" \u2022 Upgrade your role to Admin");
918
+ process.exit(1);
919
+ }
920
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
921
+ process.exit(1);
922
+ }
923
+ }
924
+
925
+ // src/commands/connector/index.ts
926
+ var connectorCommand = new Command2("connector").description("Manage database connectors");
927
+ connectorCommand.command("create <type> <url>").description("Create a new connector").option("-n, --name <name>", "Connector name").action(createAction2);
928
+ connectorCommand.command("list").description("List your connectors").action(listAction2);
929
+ connectorCommand.command("delete <name>").description("Delete a connector by name").action(deleteAction2);
930
+
931
+ // src/commands/invite/index.ts
932
+ import { Command as Command3 } from "commander";
933
+
934
+ // src/commands/invite/send.ts
935
+ async function sendAction(email, role) {
627
936
  const validRoles = ["owner", "admin", "member", "viewer"];
628
- if (!validRoles.includes(role)) {
937
+ if (!validRoles.includes(role.toLowerCase())) {
629
938
  console.error(`\u2717 Invalid role: ${role}`);
630
939
  console.error(` Valid roles: ${validRoles.join(", ")}`);
631
940
  process.exit(1);
@@ -633,30 +942,31 @@ async function sendInvite(email, role) {
633
942
  try {
634
943
  const result = await api.post("/v1/cli/invite", {
635
944
  email,
636
- role
945
+ role: role.toLowerCase()
637
946
  });
638
- console.log(`\u2713 Invitation sent to ${result.email}`);
947
+ console.log(`\u2713 Invite sent to ${result.email}`);
639
948
  console.log(` Role: ${result.role}`);
640
949
  console.log(` Org: ${result.org_name}`);
641
- console.log();
642
- console.log("They'll receive an email with a link to join.");
643
950
  } catch (err) {
644
951
  if (isNetworkError(err)) {
645
952
  console.error("\u2717 Cannot send invite while offline");
646
953
  process.exit(1);
647
954
  }
955
+ if (isPermissionError(err)) {
956
+ console.error("\u2717 You don't have permission to invite users.");
957
+ console.error("");
958
+ console.error(" Ask your organization admin to either:");
959
+ console.error(" \u2022 Send the invite for you, or");
960
+ console.error(" \u2022 Upgrade your role to Admin");
961
+ process.exit(1);
962
+ }
648
963
  console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
649
964
  process.exit(1);
650
965
  }
651
966
  }
652
- var inviteCommand = new Command4("invite").description("Invite a user to your organization").argument("[email]", "Email address to invite").argument("[role]", "Role to assign (owner/admin/member/viewer)", "member").action(async (email, role) => {
653
- if (!email) {
654
- inviteCommand.help();
655
- return;
656
- }
657
- await sendInvite(email, role);
658
- });
659
- inviteCommand.command("list").description("List pending invites").action(async () => {
967
+
968
+ // src/commands/invite/list.ts
969
+ async function listAction3() {
660
970
  try {
661
971
  const result = await api.get("/v1/cli/invites");
662
972
  if (!result.invites) {
@@ -682,8 +992,10 @@ inviteCommand.command("list").description("List pending invites").action(async (
682
992
  console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
683
993
  process.exit(1);
684
994
  }
685
- });
686
- inviteCommand.command("delete <email>").description("Revoke a pending invite").action(async (email) => {
995
+ }
996
+
997
+ // src/commands/invite/delete.ts
998
+ async function deleteAction3(email) {
687
999
  try {
688
1000
  await api.delete("/v1/cli/invites", { email });
689
1001
  console.log(`\u2713 Invite for ${email} deleted`);
@@ -695,58 +1007,114 @@ inviteCommand.command("delete <email>").description("Revoke a pending invite").a
695
1007
  console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
696
1008
  process.exit(1);
697
1009
  }
1010
+ }
1011
+
1012
+ // src/commands/invite/index.ts
1013
+ var inviteCommand = new Command3("invite").description("Invite a user to your organization").argument("[email]", "Email address to invite").argument("[role]", "Role to assign (owner/admin/member/viewer)", "member").action(async (email, role) => {
1014
+ if (!email) {
1015
+ inviteCommand.help();
1016
+ return;
1017
+ }
1018
+ await sendAction(email, role);
698
1019
  });
1020
+ inviteCommand.command("list").description("List pending invites").action(listAction3);
1021
+ inviteCommand.command("delete <email>").description("Revoke a pending invite").action(deleteAction3);
699
1022
 
700
- // src/commands/switch.ts
701
- import { Command as Command5 } from "commander";
702
- var switchCommand = new Command5("switch").description("Switch to a different branch").argument("<name>", "Branch name to switch to").action(async (name) => {
703
- let cached = getCacheEntry("branches");
704
- let branch = cached?.data.find((cachedBranch) => cachedBranch.name === name);
705
- if (!branch) {
706
- try {
707
- const result = await api.get("/v1/cli/branches");
708
- if (!result.branches) {
709
- throw new Error("API returned invalid response: missing branches array");
710
- }
711
- setCacheEntry("branches", result.branches);
712
- branch = result.branches.find((remoteBranch) => remoteBranch.name === name);
713
- } catch (err) {
714
- if (isNetworkError(err)) {
715
- if (!branch) {
716
- console.error(`\u2717 Branch "${name}" not found in cache`);
717
- console.log(" Run 'ardent branch list' when online to refresh");
718
- process.exit(1);
719
- }
720
- } else {
721
- console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
722
- process.exit(1);
723
- }
1023
+ // src/commands/org/index.ts
1024
+ import { Command as Command4 } from "commander";
1025
+
1026
+ // src/commands/org/members.ts
1027
+ async function membersAction() {
1028
+ try {
1029
+ const result = await api.get("/v1/cli/members");
1030
+ if (!result.members || result.members.length === 0) {
1031
+ console.log("No members found");
1032
+ return;
1033
+ }
1034
+ console.log("Organization members:\n");
1035
+ for (const member of result.members) {
1036
+ const name = member.name ? ` (${member.name})` : "";
1037
+ console.log(` ${member.email}${name}`);
1038
+ console.log(` Role: ${member.role}`);
1039
+ console.log();
1040
+ }
1041
+ } catch (err) {
1042
+ if (isNetworkError(err)) {
1043
+ console.error("\u2717 Cannot list members while offline");
1044
+ process.exit(1);
724
1045
  }
1046
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
1047
+ process.exit(1);
725
1048
  }
726
- if (!branch) {
727
- console.error(`\u2717 Branch "${name}" not found`);
728
- console.log(" Run: ardent branch list");
1049
+ }
1050
+
1051
+ // src/commands/org/set-role.ts
1052
+ async function setRoleAction(email, role) {
1053
+ const validRoles = ["owner", "admin", "member", "viewer"];
1054
+ if (!validRoles.includes(role)) {
1055
+ console.error(`\u2717 Invalid role: ${role}`);
1056
+ console.error(` Valid roles: ${validRoles.join(", ")}`);
729
1057
  process.exit(1);
730
1058
  }
731
- const previous = getCurrentBranch();
732
- setCurrentBranch(name);
733
- if (previous && previous !== name) {
734
- console.log(`Switched from '${previous}' to '${name}'`);
735
- } else {
736
- console.log(`Switched to branch '${name}'`);
1059
+ try {
1060
+ const result = await api.patch("/v1/cli/members/role", { email, role });
1061
+ console.log(`\u2713 Updated ${result.email} to ${result.new_role}`);
1062
+ } catch (err) {
1063
+ if (isNetworkError(err)) {
1064
+ console.error("\u2717 Cannot update role while offline");
1065
+ process.exit(1);
1066
+ }
1067
+ if (isPermissionError(err)) {
1068
+ console.error("\u2717 You don't have permission to update member roles.");
1069
+ console.error("");
1070
+ console.error(" Only org admins and owners can change roles.");
1071
+ process.exit(1);
1072
+ }
1073
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
1074
+ process.exit(1);
737
1075
  }
738
- if (branch.branch_url) {
739
- console.log(`
740
- ${branch.branch_url}`);
1076
+ }
1077
+
1078
+ // src/commands/org/remove.ts
1079
+ async function removeAction(email) {
1080
+ try {
1081
+ await api.delete("/v1/cli/members", { email });
1082
+ console.log(`\u2713 Removed ${email} from organization`);
1083
+ } catch (err) {
1084
+ if (isNetworkError(err)) {
1085
+ console.error("\u2717 Cannot remove member while offline");
1086
+ process.exit(1);
1087
+ }
1088
+ if (isPermissionError(err)) {
1089
+ console.error("\u2717 You don't have permission to remove members.");
1090
+ console.error("");
1091
+ console.error(" Only org admins and owners can remove members.");
1092
+ process.exit(1);
1093
+ }
1094
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
1095
+ process.exit(1);
741
1096
  }
742
- });
1097
+ }
743
1098
 
744
- // src/commands/auth.ts
745
- import { Command as Command6 } from "commander";
746
- var loginCommand = new Command6("login").description("Login to Ardent").option("-t, --token <token>", "API token (skip browser login)").action(async (options) => {
1099
+ // src/commands/org/index.ts
1100
+ var orgCommand = new Command4("org").description("Manage organization members");
1101
+ orgCommand.command("members").description("List organization members").action(membersAction);
1102
+ orgCommand.command("set-role <email> <role>").description("Update a member's role (owner/admin/member/viewer)").action(setRoleAction);
1103
+ orgCommand.command("remove <email>").description("Remove a member from the organization").action(removeAction);
1104
+
1105
+ // src/commands/auth/index.ts
1106
+ import { Command as Command5 } from "commander";
1107
+
1108
+ // src/commands/auth/login.ts
1109
+ async function loginAction(options) {
747
1110
  if (options.token) {
748
1111
  setConfig("token", options.token);
749
1112
  console.log("\u2713 Logged in successfully");
1113
+ try {
1114
+ await bootstrapCache();
1115
+ } catch {
1116
+ }
1117
+ showNextStep();
750
1118
  return;
751
1119
  }
752
1120
  console.log("Opening browser for authentication...");
@@ -780,6 +1148,11 @@ var loginCommand = new Command6("login").description("Login to Ardent").option("
780
1148
  if (result.status === "completed") {
781
1149
  setConfig("token", result.token);
782
1150
  console.log("\n\u2713 Logged in successfully");
1151
+ try {
1152
+ await bootstrapCache();
1153
+ } catch {
1154
+ }
1155
+ showNextStep();
783
1156
  return;
784
1157
  }
785
1158
  if (result.status === "expired") {
@@ -798,12 +1171,16 @@ var loginCommand = new Command6("login").description("Login to Ardent").option("
798
1171
  console.error("\u2717 Login failed:", error instanceof Error ? error.message : error);
799
1172
  process.exit(1);
800
1173
  }
801
- });
802
- var logoutCommand = new Command6("logout").description("Logout from Ardent").action(() => {
1174
+ }
1175
+
1176
+ // src/commands/auth/logout.ts
1177
+ function logoutAction() {
803
1178
  clearConfig();
804
1179
  console.log("\u2713 Logged out");
805
- });
806
- var statusCommand = new Command6("status").description("Show status").action(() => {
1180
+ }
1181
+
1182
+ // src/commands/auth/status.ts
1183
+ function statusAction() {
807
1184
  const token = getConfig("token");
808
1185
  if (token) {
809
1186
  console.log("\u2713 Authenticated");
@@ -812,60 +1189,68 @@ var statusCommand = new Command6("status").description("Show status").action(()
812
1189
  console.log("\u2717 Not authenticated");
813
1190
  console.log(" Run: ardent login");
814
1191
  }
815
- });
1192
+ }
1193
+
1194
+ // src/commands/auth/index.ts
1195
+ var loginCommand = new Command5("login").description("Login to Ardent").option("-t, --token <token>", "API token (skip browser login)").action(loginAction);
1196
+ var logoutCommand = new Command5("logout").description("Logout from Ardent").action(logoutAction);
1197
+ var statusCommand = new Command5("status").description("Show status").action(statusAction);
816
1198
 
817
1199
  // src/index.ts
818
- var BANNER = `
819
- \u250C\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\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\u2510
820
- \u2502 \u2502
821
- \u2502 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2502
822
- \u2502 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D \u2502
823
- \u2502 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2502
824
- \u2502 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2502
825
- \u2502 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2502
826
- \u2502 \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u2502
827
- \u2502 \u2502
828
- \u2502 Git for your data infrastructure \u2502
829
- \u2502 \u2502
830
- \u2514\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\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\u2518
831
- `;
832
1200
  var HELP_TEXT = `
833
1201
  USAGE
834
1202
  ardent <command> [options]
835
1203
 
836
- COMMANDS
837
- login Login via browser (GitHub OAuth)
838
- logout Clear stored credentials
839
- status Check authentication status
840
- connector create Connect a database (postgres, snowflake, etc.)
841
- connector list List your connectors
842
- connector delete Delete a connector
843
- branch create Create a new database branch (auto-switches to it)
844
- branch list List your branches (* = current)
845
- branch info Show branch details (URL, status, etc.)
846
- branch delete Delete a branch
847
- switch <name> Switch to a different branch
848
- diff Show CDC changes on a branch
849
- invite <email> Invite a user to your organization
850
- invite list List pending invites
851
- invite delete Revoke a pending invite
1204
+ AUTHENTICATION
1205
+ login Login via browser (GitHub OAuth)
1206
+ logout Clear stored credentials
1207
+ status Check authentication status
1208
+
1209
+ CONNECTORS
1210
+ connector create Connect a database (postgresql, snowflake, etc.)
1211
+ connector list List your connectors
1212
+ connector delete Delete a connector
1213
+
1214
+ BRANCHES
1215
+ branch create Create a new database branch (auto-switches to it)
1216
+ branch list List your branches (* = current)
1217
+ branch info Show branch details (URL, status, etc.)
1218
+ branch delete Delete a branch
1219
+ branch switch Switch to a different branch
1220
+ branch diff Show all changes since branch creation
1221
+
1222
+ TEAM
1223
+ invite <email> Invite a user to your organization
1224
+ invite list List pending invites
1225
+ invite delete Revoke a pending invite
1226
+ org members List organization members
1227
+ org set-role Update a member's role
1228
+ org remove Remove a member from the organization
852
1229
 
853
1230
  OPTIONS
854
- --help Show help for any command
855
- --version Show CLI version
1231
+ --help Show help for any command
1232
+ --version Show CLI version
856
1233
 
857
1234
  EXAMPLES
858
1235
  ardent login
859
- ardent connector create postgresql postgresql://user:pass@host:5432/db
1236
+ ardent connector create postgresql postgresql://user:password@your-db-host.com:5432/database
860
1237
  ardent branch create my-feature
861
- ardent branch list
862
- ardent invite teammate@company.com
863
- ardent invite teammate@company.com admin
864
-
865
- For more help, visit https://docs.tryardent.com/cli
1238
+ ardent branch switch my-feature
1239
+ ardent org members
866
1240
  `;
867
- var program = new Command7();
868
- program.name("ardent").description("CLI for Ardent database branching").version("0.0.1").configureHelp({
1241
+ var CLI_VERSION = getCliVersion();
1242
+ var program = new Command6();
1243
+ function getCliVersion() {
1244
+ try {
1245
+ const packageUrl = new URL("../package.json", import.meta.url);
1246
+ const raw = readFileSync2(packageUrl, "utf-8");
1247
+ const parsed = JSON.parse(raw);
1248
+ return parsed.version ?? "unknown";
1249
+ } catch {
1250
+ return "unknown";
1251
+ }
1252
+ }
1253
+ program.name("ardent").description("CLI for Ardent database branching").version(CLI_VERSION).configureHelp({
869
1254
  formatHelp: () => BANNER + HELP_TEXT
870
1255
  });
871
1256
  program.addCommand(loginCommand);
@@ -873,7 +1258,28 @@ program.addCommand(logoutCommand);
873
1258
  program.addCommand(statusCommand);
874
1259
  program.addCommand(connectorCommand);
875
1260
  program.addCommand(branchCommand);
876
- program.addCommand(switchCommand);
877
- program.addCommand(diffCommand);
878
1261
  program.addCommand(inviteCommand);
1262
+ program.addCommand(orgCommand);
1263
+ program.on("command:*", (operands) => {
1264
+ const unknown = operands[0];
1265
+ console.error(`Unknown command: ${unknown}
1266
+ `);
1267
+ if (unknown === "switch") {
1268
+ console.error(` Did you mean: ardent branch switch <name>
1269
+ `);
1270
+ } else if (unknown === "diff") {
1271
+ console.error(` Did you mean: ardent branch diff
1272
+ `);
1273
+ }
1274
+ console.error(`Run ardent --help for available commands.`);
1275
+ process.exit(1);
1276
+ });
1277
+ var args = process.argv.slice(2);
1278
+ if (args.length === 0) {
1279
+ const showedOnboarding = showOnboarding();
1280
+ if (showedOnboarding) {
1281
+ process.exit(0);
1282
+ }
1283
+ program.help();
1284
+ }
879
1285
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ardent-cli",
3
- "version": "0.0.2",
3
+ "version": "0.0.5",
4
4
  "description": "Git for Data infrastructure",
5
5
  "type": "module",
6
6
  "bin": {