ardent-cli 0.0.10 → 0.0.11

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 +437 -56
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,12 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync as readFileSync2 } from "fs";
5
- import { Command as Command6 } from "commander";
4
+ import { readFileSync as readFileSync4 } from "fs";
5
+ import { Command as Command7 } from "commander";
6
6
 
7
7
  // src/commands/branch/index.ts
8
8
  import { Command } from "commander";
9
9
 
10
+ // src/lib/api.ts
11
+ import { readFileSync as readFileSync2 } from "fs";
12
+
10
13
  // src/lib/config.ts
11
14
  import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
12
15
  import { homedir } from "os";
@@ -104,9 +107,8 @@ async function bootstrapCache() {
104
107
  "Content-Type": "application/json",
105
108
  "Authorization": `Bearer ${token}`
106
109
  };
107
- const [connectorsRes, branchesRes, meRes] = await Promise.all([
110
+ const [connectorsRes, meRes] = await Promise.all([
108
111
  fetch(`${apiUrl}/v1/cli/connectors`, { headers }),
109
- fetch(`${apiUrl}/v1/cli/branches`, { headers }),
110
112
  fetch(`${apiUrl}/v1/cli/me`, { headers })
111
113
  ]);
112
114
  let connectorCount = 0;
@@ -117,20 +119,38 @@ async function bootstrapCache() {
117
119
  setCacheEntry("connectors", connectors);
118
120
  connectorCount = connectors.length;
119
121
  }
120
- if (branchesRes.ok) {
121
- const data = await branchesRes.json();
122
- const branches = data.branches || [];
123
- setCacheEntry("branches", branches);
124
- branchCount = branches.length;
125
- }
126
122
  if (meRes.ok) {
127
123
  const user = await meRes.json();
128
124
  setConfig("user", user);
129
125
  }
126
+ const currentConnectorId = getConfig("currentConnectorId");
127
+ if (currentConnectorId) {
128
+ const branchesRes = await fetch(
129
+ `${apiUrl}/v1/cli/branches?connector_id=${currentConnectorId}`,
130
+ { headers }
131
+ );
132
+ if (branchesRes.ok) {
133
+ const data = await branchesRes.json();
134
+ const branches = data.branches || [];
135
+ setCacheEntry("branches", branches);
136
+ branchCount = branches.length;
137
+ }
138
+ }
130
139
  return { connectors: connectorCount, branches: branchCount };
131
140
  }
132
141
 
133
142
  // src/lib/api.ts
143
+ function getCliVersion() {
144
+ try {
145
+ const packageUrl = new URL("../package.json", import.meta.url);
146
+ const raw = readFileSync2(packageUrl, "utf-8");
147
+ const parsed = JSON.parse(raw);
148
+ return parsed.version ?? "unknown";
149
+ } catch {
150
+ return "unknown";
151
+ }
152
+ }
153
+ var CLI_VERSION = getCliVersion();
134
154
  function parseApiError(status, text) {
135
155
  try {
136
156
  const json = JSON.parse(text);
@@ -139,6 +159,20 @@ function parseApiError(status, text) {
139
159
  }
140
160
  return `API error ${status}: ${text}`;
141
161
  }
162
+ function handleUpgradeRequired(text) {
163
+ try {
164
+ const json = JSON.parse(text);
165
+ if (json.detail) {
166
+ console.error(`
167
+ \u2717 ${json.detail}
168
+ `);
169
+ }
170
+ } catch {
171
+ console.error("\n\u2717 Your CLI is outdated. Please update:\n");
172
+ }
173
+ console.error(" npm install -g ardent-cli@latest\n");
174
+ process.exit(1);
175
+ }
142
176
  var ApiClient = class {
143
177
  getHeaders() {
144
178
  const token = getToken();
@@ -150,20 +184,28 @@ var ApiClient = class {
150
184
  }
151
185
  return {
152
186
  "Content-Type": "application/json",
153
- "Authorization": `Bearer ${token}`
187
+ "Authorization": `Bearer ${token}`,
188
+ "X-CLI-Version": CLI_VERSION
154
189
  };
155
190
  }
191
+ async handleResponse(response) {
192
+ if (response.status === 426) {
193
+ const text = await response.text();
194
+ handleUpgradeRequired(text);
195
+ }
196
+ if (!response.ok) {
197
+ const text = await response.text();
198
+ throw new Error(parseApiError(response.status, text));
199
+ }
200
+ return response.json();
201
+ }
156
202
  async get(path) {
157
203
  const url = `${getApiUrl()}${path}`;
158
204
  const response = await fetch(url, {
159
205
  method: "GET",
160
206
  headers: this.getHeaders()
161
207
  });
162
- if (!response.ok) {
163
- const text = await response.text();
164
- throw new Error(parseApiError(response.status, text));
165
- }
166
- return response.json();
208
+ return this.handleResponse(response);
167
209
  }
168
210
  async post(path, body) {
169
211
  const url = `${getApiUrl()}${path}`;
@@ -172,11 +214,7 @@ var ApiClient = class {
172
214
  headers: this.getHeaders(),
173
215
  body: JSON.stringify(body)
174
216
  });
175
- if (!response.ok) {
176
- const text = await response.text();
177
- throw new Error(parseApiError(response.status, text));
178
- }
179
- return response.json();
217
+ return this.handleResponse(response);
180
218
  }
181
219
  async delete(path, body) {
182
220
  const url = `${getApiUrl()}${path}`;
@@ -185,11 +223,7 @@ var ApiClient = class {
185
223
  headers: this.getHeaders(),
186
224
  body: body ? JSON.stringify(body) : void 0
187
225
  });
188
- if (!response.ok) {
189
- const text = await response.text();
190
- throw new Error(parseApiError(response.status, text));
191
- }
192
- return response.json();
226
+ return this.handleResponse(response);
193
227
  }
194
228
  async patch(path, body) {
195
229
  const url = `${getApiUrl()}${path}`;
@@ -198,11 +232,7 @@ var ApiClient = class {
198
232
  headers: this.getHeaders(),
199
233
  body: JSON.stringify(body)
200
234
  });
201
- if (!response.ok) {
202
- const text = await response.text();
203
- throw new Error(parseApiError(response.status, text));
204
- }
205
- return response.json();
235
+ return this.handleResponse(response);
206
236
  }
207
237
  };
208
238
  var api = new ApiClient();
@@ -277,23 +307,33 @@ function identifyUser(userId, personProperties = {}) {
277
307
  async function createAction(name, options) {
278
308
  try {
279
309
  const startTime = performance.now();
280
- const job = await api.post("/v1/cli/jobs/create", {
281
- name
282
- });
310
+ const connectorId = getConfig("currentConnectorId");
311
+ if (!connectorId) {
312
+ console.error("\u2717 No connector selected. Run 'ardent connector switch' first.");
313
+ process.exit(1);
314
+ }
283
315
  await api.post("/v1/branch/create", {
284
- job_id: job.job_id,
285
- service_type: options.service
316
+ connector_id: connectorId,
317
+ service_type: options.service,
318
+ name
286
319
  });
287
- const apiBranches = await api.get(`/v1/branches/${job.job_id}`);
288
- const apiBranch = apiBranches.find((branch2) => branch2.service_type === options.service);
320
+ const response = await api.get(`/v1/cli/branches?connector_id=${connectorId}`);
321
+ const apiBranches = response.branches || [];
322
+ let apiBranch;
323
+ for (const branch2 of apiBranches) {
324
+ if (branch2.name === name) {
325
+ apiBranch = branch2;
326
+ break;
327
+ }
328
+ }
289
329
  if (!apiBranch) {
290
330
  console.error("\u2717 Branch created but could not fetch details");
291
331
  process.exit(1);
292
332
  }
293
333
  const branch = {
294
334
  id: apiBranch.id,
295
- job_id: job.job_id,
296
- name,
335
+ connector_id: apiBranch.connector_id,
336
+ name: apiBranch.name,
297
337
  service_type: apiBranch.service_type,
298
338
  branch_url: apiBranch.branch_url || "",
299
339
  status: apiBranch.status,
@@ -329,7 +369,12 @@ async function listAction() {
329
369
  let fromCache = false;
330
370
  let cacheTime = "";
331
371
  try {
332
- const result = await api.get("/v1/cli/branches");
372
+ const currentConnectorId = getConfig("currentConnectorId");
373
+ if (!currentConnectorId) {
374
+ console.error("\u2717 No connector selected. Run 'ardent connector switch' first.");
375
+ process.exit(1);
376
+ }
377
+ const result = await api.get(`/v1/cli/branches?connector_id=${currentConnectorId}`);
333
378
  if (!result.branches) {
334
379
  throw new Error("API returned invalid response: missing branches array");
335
380
  }
@@ -426,7 +471,7 @@ async function deleteAction(name) {
426
471
  process.exit(1);
427
472
  }
428
473
  try {
429
- await api.delete(`/v1/cli/jobs/${branch.job_id}`);
474
+ await api.delete(`/v1/cli/branches/${branch.id}`);
430
475
  const updatedBranches = cached.data.filter((cachedBranch) => cachedBranch.id !== branch.id);
431
476
  setCacheEntry("branches", updatedBranches);
432
477
  if (getCurrentBranch() === name) {
@@ -537,7 +582,7 @@ async function diffAction(options) {
537
582
  return;
538
583
  }
539
584
  try {
540
- const stateRaw = await api.get(`/v1/branches/${branch.job_id}/state`);
585
+ const stateRaw = await api.get(`/v1/branches/${branch.id}/state`);
541
586
  if (!stateRaw || Object.keys(stateRaw).length === 0) {
542
587
  console.log("No changes captured");
543
588
  return;
@@ -867,12 +912,20 @@ async function createAction2(type, url, options) {
867
912
  }
868
913
  try {
869
914
  const connectorName = options.name || (isByoc ? "my-neon-connection" : "my-postgresql-connection");
915
+ const currentProjectId = getConfig("currentProjectId");
916
+ if (!currentProjectId) {
917
+ console.error("\u2717 No current project set. Switch to a project first:");
918
+ console.error(" ardent project list");
919
+ console.error(" ardent project switch <name>");
920
+ process.exit(1);
921
+ }
870
922
  let createPayload;
871
923
  if (isByoc) {
872
924
  console.log("Creating connector (BYOC Neon)...");
873
925
  createPayload = {
874
926
  name: connectorName,
875
927
  service_name: "postgresql",
928
+ project_id: currentProjectId,
876
929
  byoc: options.byoc,
877
930
  neon_api_key: options.apiKey,
878
931
  neon_project_id: options.projectId,
@@ -889,6 +942,7 @@ async function createAction2(type, url, options) {
889
942
  createPayload = {
890
943
  name: connectorName,
891
944
  service_name: "postgresql",
945
+ project_id: currentProjectId,
892
946
  connection_details: {
893
947
  host: parsed.host,
894
948
  port: parsed.port,
@@ -932,6 +986,8 @@ async function createAction2(type, url, options) {
932
986
  const cachedConnectors = cached?.data || [];
933
987
  cachedConnectors.push(newConnector);
934
988
  setCacheEntry("connectors", cachedConnectors);
989
+ setConfig("currentConnectorId", connectorId);
990
+ setConfig("currentConnectorName", connectorName);
935
991
  trackEvent("CLI: connector create succeeded", { db_type: type, byoc: isByoc });
936
992
  console.log("\u2713 Connector created and ready");
937
993
  console.log(` ID: ${connectorId}`);
@@ -958,7 +1014,9 @@ async function listAction2() {
958
1014
  let fromCache = false;
959
1015
  let cacheTime = "";
960
1016
  try {
961
- const result = await api.get("/v1/cli/connectors");
1017
+ const currentProjectId = getConfig("currentProjectId");
1018
+ const projectFilter = currentProjectId ? `?project_id=${currentProjectId}` : "";
1019
+ const result = await api.get(`/v1/cli/connectors${projectFilter}`);
962
1020
  if (!result.connectors) {
963
1021
  throw new Error("API returned invalid response: missing connectors array");
964
1022
  }
@@ -992,11 +1050,21 @@ async function listAction2() {
992
1050
  console.log(" Create one with: ardent connector create postgresql <url>");
993
1051
  return;
994
1052
  }
1053
+ const currentConnectorId = getConfig("currentConnectorId");
1054
+ const green3 = "\x1B[32m";
1055
+ const dim3 = "\x1B[2m";
1056
+ const reset3 = "\x1B[0m";
995
1057
  console.log("Connectors:\n");
996
1058
  for (const connector of connectors) {
1059
+ const isCurrent = connector.id === currentConnectorId;
997
1060
  const icon = connector.status === "healthy" ? "\u25CF" : "\u25CB";
998
- console.log(` ${icon} ${connector.service_name}`);
999
- console.log(` Name: ${connector.name}`);
1061
+ if (isCurrent) {
1062
+ console.log(`${green3}* ${icon} ${connector.name}${reset3}`);
1063
+ console.log(`${green3} ${connector.service_name}${reset3}`);
1064
+ } else {
1065
+ console.log(` ${icon} ${connector.name}`);
1066
+ console.log(`${dim3} ${connector.service_name}${reset3}`);
1067
+ }
1000
1068
  console.log();
1001
1069
  }
1002
1070
  }
@@ -1056,10 +1124,64 @@ async function deleteAction2(name) {
1056
1124
  }
1057
1125
  }
1058
1126
 
1127
+ // src/commands/connector/switch.ts
1128
+ async function switchAction2(name) {
1129
+ const cached = getCacheEntry("connectors");
1130
+ let connector = cached?.data.find(
1131
+ (cachedConnector) => cachedConnector.name === name
1132
+ );
1133
+ if (!connector) {
1134
+ try {
1135
+ const result = await api.get("/v1/cli/connectors");
1136
+ if (!result.connectors) {
1137
+ throw new Error("API returned invalid response: missing connectors array");
1138
+ }
1139
+ setCacheEntry("connectors", result.connectors);
1140
+ connector = result.connectors.find(
1141
+ (remoteConnector) => remoteConnector.name === name
1142
+ );
1143
+ } catch (err) {
1144
+ if (isNetworkError(err)) {
1145
+ if (!connector) {
1146
+ console.error(`\u2717 Connector "${name}" not found in cache`);
1147
+ console.log(" Run 'ardent connector list' when online to refresh");
1148
+ process.exit(1);
1149
+ }
1150
+ } else {
1151
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
1152
+ process.exit(1);
1153
+ }
1154
+ }
1155
+ }
1156
+ if (!connector) {
1157
+ console.error(`\u2717 Connector "${name}" not found`);
1158
+ console.log(" Run: ardent connector list");
1159
+ process.exit(1);
1160
+ }
1161
+ const previousName = getConfig("currentConnectorName");
1162
+ setConfig("currentConnectorId", connector.id);
1163
+ setConfig("currentConnectorName", connector.name);
1164
+ clearCurrentBranch();
1165
+ if (previousName && previousName !== name) {
1166
+ console.log(`Switched from '${previousName}' to '${name}'`);
1167
+ } else {
1168
+ console.log(`Switched to connector '${name}'`);
1169
+ }
1170
+ try {
1171
+ const branchResult = await api.get(
1172
+ `/v1/cli/branches?connector_id=${connector.id}`
1173
+ );
1174
+ setCacheEntry("branches", branchResult.branches || []);
1175
+ } catch {
1176
+ }
1177
+ trackEvent("CLI: connector switch");
1178
+ }
1179
+
1059
1180
  // src/commands/connector/index.ts
1060
1181
  var connectorCommand = new Command2("connector").description("Manage database connectors");
1061
1182
  connectorCommand.command("create <type> [url]").description("Create a new connector").option("-n, --name <name>", "Connector name").option("--byoc <provider>", "Bring your own Neon project (e.g. neon)").option("--api-key <key>", "Neon API key (required with --byoc)").option("--project-id <id>", "Neon project ID (required with --byoc)").action(createAction2);
1062
1183
  connectorCommand.command("list").description("List your connectors").action(listAction2);
1184
+ connectorCommand.command("switch <name>").description("Switch to a different connector").action(switchAction2);
1063
1185
  connectorCommand.command("delete <name>").description("Delete a connector by name").action(deleteAction2);
1064
1186
 
1065
1187
  // src/commands/invite/index.ts
@@ -1257,9 +1379,185 @@ orgCommand.command("members").description("List organization members").action(me
1257
1379
  orgCommand.command("set-role <email> <role>").description("Update a member's role (owner/admin/member/viewer)").action(setRoleAction);
1258
1380
  orgCommand.command("remove <email>").description("Remove a member from the organization").action(removeAction);
1259
1381
 
1260
- // src/commands/auth/index.ts
1382
+ // src/commands/project/index.ts
1261
1383
  import { Command as Command5 } from "commander";
1262
1384
 
1385
+ // src/commands/project/create.ts
1386
+ async function createAction3(name) {
1387
+ const user = getConfig("user");
1388
+ if (!user?.org_id) {
1389
+ console.error("\u2717 No organization found. Run: ardent login");
1390
+ process.exit(1);
1391
+ }
1392
+ try {
1393
+ const project = await api.post("/v1/projects", {
1394
+ name,
1395
+ org_id: user.org_id
1396
+ });
1397
+ const cached = getCacheEntry("projects");
1398
+ const cachedProjects = cached?.data || [];
1399
+ cachedProjects.push(project);
1400
+ setCacheEntry("projects", cachedProjects);
1401
+ setConfig("currentProjectId", project.id);
1402
+ setConfig("currentProjectName", project.name);
1403
+ trackEvent("CLI: project create succeeded");
1404
+ console.log(`\u2713 Project '${name}' created`);
1405
+ console.log(` ID: ${project.id}`);
1406
+ } catch (err) {
1407
+ if (isNetworkError(err)) {
1408
+ trackEvent("CLI: project create failed", { reason: "offline" });
1409
+ console.error("\u2717 Cannot create project while offline");
1410
+ process.exit(1);
1411
+ }
1412
+ if (isPermissionError(err)) {
1413
+ trackEvent("CLI: project create failed", { reason: "permission_denied" });
1414
+ console.error("\u2717 You don't have permission to create projects.");
1415
+ process.exit(1);
1416
+ }
1417
+ trackEvent("CLI: project create failed", { reason: "api_error" });
1418
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
1419
+ process.exit(1);
1420
+ }
1421
+ }
1422
+
1423
+ // src/commands/project/list.ts
1424
+ async function listAction4() {
1425
+ let projects = [];
1426
+ let fromCache = false;
1427
+ let cacheTime = "";
1428
+ try {
1429
+ const user = getConfig("user");
1430
+ if (!user?.org_id) {
1431
+ console.error("\u2717 No organization found. Run: ardent login");
1432
+ process.exit(1);
1433
+ }
1434
+ const result = await api.get(
1435
+ `/v1/projects?org_id=${user.org_id}`
1436
+ );
1437
+ if (!result.projects) {
1438
+ throw new Error("API returned invalid response: missing projects array");
1439
+ }
1440
+ projects = result.projects;
1441
+ setCacheEntry("projects", projects);
1442
+ } catch (err) {
1443
+ if (isNetworkError(err)) {
1444
+ const cached = getCacheEntry("projects");
1445
+ if (cached) {
1446
+ projects = cached.data;
1447
+ fromCache = true;
1448
+ cacheTime = formatCacheTime(cached.updated_at);
1449
+ } else {
1450
+ trackEvent("CLI: project list failed", { reason: "offline_no_cache" });
1451
+ console.error("\u2717 Offline and no cached data available");
1452
+ process.exit(1);
1453
+ }
1454
+ } else {
1455
+ trackEvent("CLI: project list failed", { reason: "api_error" });
1456
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
1457
+ process.exit(1);
1458
+ }
1459
+ }
1460
+ trackEvent("CLI: project list succeeded", {
1461
+ project_count: projects.length,
1462
+ from_cache: fromCache
1463
+ });
1464
+ if (fromCache) {
1465
+ console.log(`\u26A0 Offline - showing cached data from ${cacheTime}
1466
+ `);
1467
+ }
1468
+ if (projects.length === 0) {
1469
+ console.log("No projects found");
1470
+ return;
1471
+ }
1472
+ const currentProjectId = getConfig("currentProjectId");
1473
+ const green3 = "\x1B[32m";
1474
+ const dim3 = "\x1B[2m";
1475
+ const reset3 = "\x1B[0m";
1476
+ console.log("Projects:\n");
1477
+ for (const project of projects) {
1478
+ const isCurrent = project.id === currentProjectId;
1479
+ if (isCurrent) {
1480
+ console.log(`${green3}* ${project.name}${reset3}`);
1481
+ console.log(`${green3} ${project.id}${reset3}`);
1482
+ } else {
1483
+ console.log(` ${project.name}`);
1484
+ console.log(`${dim3} ${project.id}${reset3}`);
1485
+ }
1486
+ console.log();
1487
+ }
1488
+ }
1489
+
1490
+ // src/commands/project/switch.ts
1491
+ async function switchAction3(name) {
1492
+ let projects = [];
1493
+ const cached = getCacheEntry("projects");
1494
+ let project = cached?.data.find(
1495
+ (cachedProject) => cachedProject.name === name
1496
+ );
1497
+ if (!project) {
1498
+ try {
1499
+ const user = getConfig("user");
1500
+ if (!user?.org_id) {
1501
+ console.error("\u2717 No organization found. Run: ardent login");
1502
+ process.exit(1);
1503
+ }
1504
+ const result = await api.get(
1505
+ `/v1/projects?org_id=${user.org_id}`
1506
+ );
1507
+ if (!result.projects) {
1508
+ throw new Error(
1509
+ "API returned invalid response: missing projects array"
1510
+ );
1511
+ }
1512
+ projects = result.projects;
1513
+ setCacheEntry("projects", projects);
1514
+ project = projects.find(
1515
+ (remoteProject) => remoteProject.name === name
1516
+ );
1517
+ } catch (err) {
1518
+ if (isNetworkError(err)) {
1519
+ if (!project) {
1520
+ console.error(`\u2717 Project "${name}" not found in cache`);
1521
+ console.log(" Run 'ardent project list' when online to refresh");
1522
+ process.exit(1);
1523
+ }
1524
+ } else {
1525
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
1526
+ process.exit(1);
1527
+ }
1528
+ }
1529
+ }
1530
+ if (!project) {
1531
+ console.error(`\u2717 Project "${name}" not found`);
1532
+ console.log(" Run: ardent project list");
1533
+ process.exit(1);
1534
+ }
1535
+ const previousId = getConfig("currentProjectId");
1536
+ const previousName = getConfig("currentProjectName");
1537
+ setConfig("currentProjectId", project.id);
1538
+ setConfig("currentProjectName", project.name);
1539
+ setConfig("currentConnectorId", void 0);
1540
+ setConfig("currentConnectorName", void 0);
1541
+ clearCurrentBranch();
1542
+ if (previousName && previousName !== name) {
1543
+ console.log(`Switched from '${previousName}' to '${name}'`);
1544
+ } else {
1545
+ console.log(`Switched to project '${name}'`);
1546
+ }
1547
+ trackEvent("CLI: project switch");
1548
+ }
1549
+
1550
+ // src/commands/project/index.ts
1551
+ var projectCommand = new Command5("project").description(
1552
+ "Manage projects"
1553
+ );
1554
+ projectCommand.command("create <name>").description("Create a new project").action(createAction3);
1555
+ projectCommand.command("list").description("List your projects").action(listAction4);
1556
+ projectCommand.command("switch <name>").description("Switch to a different project").action(switchAction3);
1557
+
1558
+ // src/commands/auth/index.ts
1559
+ import { Command as Command6 } from "commander";
1560
+
1263
1561
  // src/commands/auth/login.ts
1264
1562
  async function loginAction(options) {
1265
1563
  if (options.token) {
@@ -1389,9 +1687,84 @@ function statusAction() {
1389
1687
  }
1390
1688
 
1391
1689
  // src/commands/auth/index.ts
1392
- var loginCommand = new Command5("login").description("Login to Ardent").option("-t, --token <token>", "API token (skip browser login)").action(loginAction);
1393
- var logoutCommand = new Command5("logout").description("Logout from Ardent").action(logoutAction);
1394
- var statusCommand = new Command5("status").description("Show status").action(statusAction);
1690
+ var loginCommand = new Command6("login").description("Login to Ardent").option("-t, --token <token>", "API token (skip browser login)").action(loginAction);
1691
+ var logoutCommand = new Command6("logout").description("Logout from Ardent").action(logoutAction);
1692
+ var statusCommand = new Command6("status").description("Show status").action(statusAction);
1693
+
1694
+ // src/lib/update-check.ts
1695
+ import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1696
+ import { join as join2 } from "path";
1697
+ import { homedir as homedir2 } from "os";
1698
+ var UPDATE_CHECK_FILE = join2(homedir2(), ".ardent", "update-check.json");
1699
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1700
+ var PACKAGE_NAME = "ardent-cli";
1701
+ function loadCache() {
1702
+ try {
1703
+ if (existsSync2(UPDATE_CHECK_FILE)) {
1704
+ return JSON.parse(readFileSync3(UPDATE_CHECK_FILE, "utf-8"));
1705
+ }
1706
+ } catch {
1707
+ }
1708
+ return null;
1709
+ }
1710
+ function saveCache(latestVersion) {
1711
+ try {
1712
+ const data = {
1713
+ latest_version: latestVersion,
1714
+ checked_at: (/* @__PURE__ */ new Date()).toISOString()
1715
+ };
1716
+ writeFileSync2(UPDATE_CHECK_FILE, JSON.stringify(data));
1717
+ } catch {
1718
+ }
1719
+ }
1720
+ function isNewerVersion(current, latest) {
1721
+ const currentParts = current.split(".").map(Number);
1722
+ const latestParts = latest.split(".").map(Number);
1723
+ for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
1724
+ const currentPart = currentParts[i] || 0;
1725
+ const latestPart = latestParts[i] || 0;
1726
+ if (latestPart > currentPart) return true;
1727
+ if (latestPart < currentPart) return false;
1728
+ }
1729
+ return false;
1730
+ }
1731
+ async function checkForUpdate(currentVersion) {
1732
+ try {
1733
+ const cache = loadCache();
1734
+ if (cache) {
1735
+ const elapsed = Date.now() - new Date(cache.checked_at).getTime();
1736
+ if (elapsed < CHECK_INTERVAL_MS) {
1737
+ if (isNewerVersion(currentVersion, cache.latest_version)) {
1738
+ printUpdateNotice(currentVersion, cache.latest_version);
1739
+ }
1740
+ return;
1741
+ }
1742
+ }
1743
+ const controller = new AbortController();
1744
+ const timeout = setTimeout(() => controller.abort(), 3e3);
1745
+ const response = await fetch(
1746
+ `https://registry.npmjs.org/${PACKAGE_NAME}/latest`,
1747
+ { signal: controller.signal }
1748
+ );
1749
+ clearTimeout(timeout);
1750
+ if (!response.ok) return;
1751
+ const data = await response.json();
1752
+ const latestVersion = data.version;
1753
+ saveCache(latestVersion);
1754
+ if (isNewerVersion(currentVersion, latestVersion)) {
1755
+ printUpdateNotice(currentVersion, latestVersion);
1756
+ }
1757
+ } catch {
1758
+ }
1759
+ }
1760
+ function printUpdateNotice(current, latest) {
1761
+ const yellow2 = "\x1B[33m";
1762
+ const green3 = "\x1B[32m";
1763
+ const reset3 = "\x1B[0m";
1764
+ console.error(
1765
+ `${yellow2}Update available: ${current} \u2192 ${latest}${reset3} \u2014 run ${green3}npm install -g ardent-cli@latest${reset3}`
1766
+ );
1767
+ }
1395
1768
 
1396
1769
  // src/index.ts
1397
1770
  var HELP_TEXT = `
@@ -1403,9 +1776,15 @@ AUTHENTICATION
1403
1776
  logout Clear stored credentials
1404
1777
  status Check authentication status
1405
1778
 
1779
+ PROJECTS
1780
+ project create Create a new project
1781
+ project list List your projects (* = current)
1782
+ project switch Switch to a different project
1783
+
1406
1784
  CONNECTORS
1407
1785
  connector create Connect a database (postgresql, snowflake, etc.)
1408
- connector list List your connectors
1786
+ connector list List your connectors (* = current)
1787
+ connector switch Switch to a different connector
1409
1788
  connector delete Delete a connector
1410
1789
 
1411
1790
  BRANCHES
@@ -1435,24 +1814,25 @@ EXAMPLES
1435
1814
  ardent branch switch my-feature
1436
1815
  ardent org members
1437
1816
  `;
1438
- var CLI_VERSION = getCliVersion();
1439
- var program = new Command6();
1440
- function getCliVersion() {
1817
+ var CLI_VERSION2 = getCliVersion2();
1818
+ var program = new Command7();
1819
+ function getCliVersion2() {
1441
1820
  try {
1442
1821
  const packageUrl = new URL("../package.json", import.meta.url);
1443
- const raw = readFileSync2(packageUrl, "utf-8");
1822
+ const raw = readFileSync4(packageUrl, "utf-8");
1444
1823
  const parsed = JSON.parse(raw);
1445
1824
  return parsed.version ?? "unknown";
1446
1825
  } catch {
1447
1826
  return "unknown";
1448
1827
  }
1449
1828
  }
1450
- program.name("ardent").description("CLI for Ardent database branching").version(CLI_VERSION).configureHelp({
1829
+ program.name("ardent").description("CLI for Ardent database branching").version(CLI_VERSION2).configureHelp({
1451
1830
  formatHelp: () => BANNER + HELP_TEXT
1452
1831
  });
1453
1832
  program.addCommand(loginCommand);
1454
1833
  program.addCommand(logoutCommand);
1455
1834
  program.addCommand(statusCommand);
1835
+ program.addCommand(projectCommand);
1456
1836
  program.addCommand(connectorCommand);
1457
1837
  program.addCommand(branchCommand);
1458
1838
  program.addCommand(inviteCommand);
@@ -1480,4 +1860,5 @@ if (args.length === 0) {
1480
1860
  program.help();
1481
1861
  }
1482
1862
  getAnonymousId();
1863
+ checkForUpdate(CLI_VERSION2);
1483
1864
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ardent-cli",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "Git for Data infrastructure",
5
5
  "type": "module",
6
6
  "bin": {