ardent-cli 0.0.10 → 0.0.12

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 +449 -57
  2. package/package.json +4 -2
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,28 +119,71 @@ 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();
154
+ function formatErrorDetail(detail) {
155
+ if (typeof detail === "string") return detail;
156
+ if (Array.isArray(detail)) {
157
+ return detail.map((entry) => {
158
+ const location = Array.isArray(entry.loc) ? entry.loc.join(" \u2192 ") : "";
159
+ const message = entry.msg ?? "unknown error";
160
+ return location ? `${location}: ${message}` : String(message);
161
+ }).join("; ");
162
+ }
163
+ return JSON.stringify(detail);
164
+ }
134
165
  function parseApiError(status, text) {
135
166
  try {
136
167
  const json = JSON.parse(text);
137
- if (json.detail) return `API error ${status}: ${json.detail}`;
168
+ if (json.detail) return `API error ${status}: ${formatErrorDetail(json.detail)}`;
138
169
  } catch {
139
170
  }
140
171
  return `API error ${status}: ${text}`;
141
172
  }
173
+ function handleUpgradeRequired(text) {
174
+ try {
175
+ const json = JSON.parse(text);
176
+ if (json.detail) {
177
+ console.error(`
178
+ \u2717 ${json.detail}
179
+ `);
180
+ }
181
+ } catch {
182
+ console.error("\n\u2717 Your CLI is outdated. Please update:\n");
183
+ }
184
+ console.error(" npm install -g ardent-cli@latest\n");
185
+ process.exit(1);
186
+ }
142
187
  var ApiClient = class {
143
188
  getHeaders() {
144
189
  const token = getToken();
@@ -150,20 +195,28 @@ var ApiClient = class {
150
195
  }
151
196
  return {
152
197
  "Content-Type": "application/json",
153
- "Authorization": `Bearer ${token}`
198
+ "Authorization": `Bearer ${token}`,
199
+ "X-CLI-Version": CLI_VERSION
154
200
  };
155
201
  }
202
+ async handleResponse(response) {
203
+ if (response.status === 426) {
204
+ const text = await response.text();
205
+ handleUpgradeRequired(text);
206
+ }
207
+ if (!response.ok) {
208
+ const text = await response.text();
209
+ throw new Error(parseApiError(response.status, text));
210
+ }
211
+ return response.json();
212
+ }
156
213
  async get(path) {
157
214
  const url = `${getApiUrl()}${path}`;
158
215
  const response = await fetch(url, {
159
216
  method: "GET",
160
217
  headers: this.getHeaders()
161
218
  });
162
- if (!response.ok) {
163
- const text = await response.text();
164
- throw new Error(parseApiError(response.status, text));
165
- }
166
- return response.json();
219
+ return this.handleResponse(response);
167
220
  }
168
221
  async post(path, body) {
169
222
  const url = `${getApiUrl()}${path}`;
@@ -172,11 +225,7 @@ var ApiClient = class {
172
225
  headers: this.getHeaders(),
173
226
  body: JSON.stringify(body)
174
227
  });
175
- if (!response.ok) {
176
- const text = await response.text();
177
- throw new Error(parseApiError(response.status, text));
178
- }
179
- return response.json();
228
+ return this.handleResponse(response);
180
229
  }
181
230
  async delete(path, body) {
182
231
  const url = `${getApiUrl()}${path}`;
@@ -185,11 +234,7 @@ var ApiClient = class {
185
234
  headers: this.getHeaders(),
186
235
  body: body ? JSON.stringify(body) : void 0
187
236
  });
188
- if (!response.ok) {
189
- const text = await response.text();
190
- throw new Error(parseApiError(response.status, text));
191
- }
192
- return response.json();
237
+ return this.handleResponse(response);
193
238
  }
194
239
  async patch(path, body) {
195
240
  const url = `${getApiUrl()}${path}`;
@@ -198,11 +243,7 @@ var ApiClient = class {
198
243
  headers: this.getHeaders(),
199
244
  body: JSON.stringify(body)
200
245
  });
201
- if (!response.ok) {
202
- const text = await response.text();
203
- throw new Error(parseApiError(response.status, text));
204
- }
205
- return response.json();
246
+ return this.handleResponse(response);
206
247
  }
207
248
  };
208
249
  var api = new ApiClient();
@@ -277,23 +318,33 @@ function identifyUser(userId, personProperties = {}) {
277
318
  async function createAction(name, options) {
278
319
  try {
279
320
  const startTime = performance.now();
280
- const job = await api.post("/v1/cli/jobs/create", {
281
- name
282
- });
321
+ const connectorId = getConfig("currentConnectorId");
322
+ if (!connectorId) {
323
+ console.error("\u2717 No connector selected. Run 'ardent connector switch' first.");
324
+ process.exit(1);
325
+ }
283
326
  await api.post("/v1/branch/create", {
284
- job_id: job.job_id,
285
- service_type: options.service
327
+ connector_id: connectorId,
328
+ service_type: options.service,
329
+ name
286
330
  });
287
- const apiBranches = await api.get(`/v1/branches/${job.job_id}`);
288
- const apiBranch = apiBranches.find((branch2) => branch2.service_type === options.service);
331
+ const response = await api.get(`/v1/cli/branches?connector_id=${connectorId}`);
332
+ const apiBranches = response.branches || [];
333
+ let apiBranch;
334
+ for (const branch2 of apiBranches) {
335
+ if (branch2.name === name) {
336
+ apiBranch = branch2;
337
+ break;
338
+ }
339
+ }
289
340
  if (!apiBranch) {
290
341
  console.error("\u2717 Branch created but could not fetch details");
291
342
  process.exit(1);
292
343
  }
293
344
  const branch = {
294
345
  id: apiBranch.id,
295
- job_id: job.job_id,
296
- name,
346
+ connector_id: apiBranch.connector_id,
347
+ name: apiBranch.name,
297
348
  service_type: apiBranch.service_type,
298
349
  branch_url: apiBranch.branch_url || "",
299
350
  status: apiBranch.status,
@@ -329,7 +380,12 @@ async function listAction() {
329
380
  let fromCache = false;
330
381
  let cacheTime = "";
331
382
  try {
332
- const result = await api.get("/v1/cli/branches");
383
+ const currentConnectorId = getConfig("currentConnectorId");
384
+ if (!currentConnectorId) {
385
+ console.error("\u2717 No connector selected. Run 'ardent connector switch' first.");
386
+ process.exit(1);
387
+ }
388
+ const result = await api.get(`/v1/cli/branches?connector_id=${currentConnectorId}`);
333
389
  if (!result.branches) {
334
390
  throw new Error("API returned invalid response: missing branches array");
335
391
  }
@@ -426,7 +482,7 @@ async function deleteAction(name) {
426
482
  process.exit(1);
427
483
  }
428
484
  try {
429
- await api.delete(`/v1/cli/jobs/${branch.job_id}`);
485
+ await api.delete(`/v1/cli/branches/${branch.id}`);
430
486
  const updatedBranches = cached.data.filter((cachedBranch) => cachedBranch.id !== branch.id);
431
487
  setCacheEntry("branches", updatedBranches);
432
488
  if (getCurrentBranch() === name) {
@@ -537,7 +593,7 @@ async function diffAction(options) {
537
593
  return;
538
594
  }
539
595
  try {
540
- const stateRaw = await api.get(`/v1/branches/${branch.job_id}/state`);
596
+ const stateRaw = await api.get(`/v1/branches/${branch.id}/state`);
541
597
  if (!stateRaw || Object.keys(stateRaw).length === 0) {
542
598
  console.log("No changes captured");
543
599
  return;
@@ -867,12 +923,20 @@ async function createAction2(type, url, options) {
867
923
  }
868
924
  try {
869
925
  const connectorName = options.name || (isByoc ? "my-neon-connection" : "my-postgresql-connection");
926
+ const currentProjectId = getConfig("currentProjectId");
927
+ if (!currentProjectId) {
928
+ console.error("\u2717 No current project set. Switch to a project first:");
929
+ console.error(" ardent project list");
930
+ console.error(" ardent project switch <name>");
931
+ process.exit(1);
932
+ }
870
933
  let createPayload;
871
934
  if (isByoc) {
872
935
  console.log("Creating connector (BYOC Neon)...");
873
936
  createPayload = {
874
937
  name: connectorName,
875
938
  service_name: "postgresql",
939
+ project_id: currentProjectId,
876
940
  byoc: options.byoc,
877
941
  neon_api_key: options.apiKey,
878
942
  neon_project_id: options.projectId,
@@ -889,6 +953,7 @@ async function createAction2(type, url, options) {
889
953
  createPayload = {
890
954
  name: connectorName,
891
955
  service_name: "postgresql",
956
+ project_id: currentProjectId,
892
957
  connection_details: {
893
958
  host: parsed.host,
894
959
  port: parsed.port,
@@ -932,6 +997,8 @@ async function createAction2(type, url, options) {
932
997
  const cachedConnectors = cached?.data || [];
933
998
  cachedConnectors.push(newConnector);
934
999
  setCacheEntry("connectors", cachedConnectors);
1000
+ setConfig("currentConnectorId", connectorId);
1001
+ setConfig("currentConnectorName", connectorName);
935
1002
  trackEvent("CLI: connector create succeeded", { db_type: type, byoc: isByoc });
936
1003
  console.log("\u2713 Connector created and ready");
937
1004
  console.log(` ID: ${connectorId}`);
@@ -958,7 +1025,9 @@ async function listAction2() {
958
1025
  let fromCache = false;
959
1026
  let cacheTime = "";
960
1027
  try {
961
- const result = await api.get("/v1/cli/connectors");
1028
+ const currentProjectId = getConfig("currentProjectId");
1029
+ const projectFilter = currentProjectId ? `?project_id=${currentProjectId}` : "";
1030
+ const result = await api.get(`/v1/cli/connectors${projectFilter}`);
962
1031
  if (!result.connectors) {
963
1032
  throw new Error("API returned invalid response: missing connectors array");
964
1033
  }
@@ -992,11 +1061,21 @@ async function listAction2() {
992
1061
  console.log(" Create one with: ardent connector create postgresql <url>");
993
1062
  return;
994
1063
  }
1064
+ const currentConnectorId = getConfig("currentConnectorId");
1065
+ const green3 = "\x1B[32m";
1066
+ const dim3 = "\x1B[2m";
1067
+ const reset3 = "\x1B[0m";
995
1068
  console.log("Connectors:\n");
996
1069
  for (const connector of connectors) {
1070
+ const isCurrent = connector.id === currentConnectorId;
997
1071
  const icon = connector.status === "healthy" ? "\u25CF" : "\u25CB";
998
- console.log(` ${icon} ${connector.service_name}`);
999
- console.log(` Name: ${connector.name}`);
1072
+ if (isCurrent) {
1073
+ console.log(`${green3}* ${icon} ${connector.name}${reset3}`);
1074
+ console.log(`${green3} ${connector.service_name}${reset3}`);
1075
+ } else {
1076
+ console.log(` ${icon} ${connector.name}`);
1077
+ console.log(`${dim3} ${connector.service_name}${reset3}`);
1078
+ }
1000
1079
  console.log();
1001
1080
  }
1002
1081
  }
@@ -1056,10 +1135,64 @@ async function deleteAction2(name) {
1056
1135
  }
1057
1136
  }
1058
1137
 
1138
+ // src/commands/connector/switch.ts
1139
+ async function switchAction2(name) {
1140
+ const cached = getCacheEntry("connectors");
1141
+ let connector = cached?.data.find(
1142
+ (cachedConnector) => cachedConnector.name === name
1143
+ );
1144
+ if (!connector) {
1145
+ try {
1146
+ const result = await api.get("/v1/cli/connectors");
1147
+ if (!result.connectors) {
1148
+ throw new Error("API returned invalid response: missing connectors array");
1149
+ }
1150
+ setCacheEntry("connectors", result.connectors);
1151
+ connector = result.connectors.find(
1152
+ (remoteConnector) => remoteConnector.name === name
1153
+ );
1154
+ } catch (err) {
1155
+ if (isNetworkError(err)) {
1156
+ if (!connector) {
1157
+ console.error(`\u2717 Connector "${name}" not found in cache`);
1158
+ console.log(" Run 'ardent connector list' when online to refresh");
1159
+ process.exit(1);
1160
+ }
1161
+ } else {
1162
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
1163
+ process.exit(1);
1164
+ }
1165
+ }
1166
+ }
1167
+ if (!connector) {
1168
+ console.error(`\u2717 Connector "${name}" not found`);
1169
+ console.log(" Run: ardent connector list");
1170
+ process.exit(1);
1171
+ }
1172
+ const previousName = getConfig("currentConnectorName");
1173
+ setConfig("currentConnectorId", connector.id);
1174
+ setConfig("currentConnectorName", connector.name);
1175
+ clearCurrentBranch();
1176
+ if (previousName && previousName !== name) {
1177
+ console.log(`Switched from '${previousName}' to '${name}'`);
1178
+ } else {
1179
+ console.log(`Switched to connector '${name}'`);
1180
+ }
1181
+ try {
1182
+ const branchResult = await api.get(
1183
+ `/v1/cli/branches?connector_id=${connector.id}`
1184
+ );
1185
+ setCacheEntry("branches", branchResult.branches || []);
1186
+ } catch {
1187
+ }
1188
+ trackEvent("CLI: connector switch");
1189
+ }
1190
+
1059
1191
  // src/commands/connector/index.ts
1060
1192
  var connectorCommand = new Command2("connector").description("Manage database connectors");
1061
1193
  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
1194
  connectorCommand.command("list").description("List your connectors").action(listAction2);
1195
+ connectorCommand.command("switch <name>").description("Switch to a different connector").action(switchAction2);
1063
1196
  connectorCommand.command("delete <name>").description("Delete a connector by name").action(deleteAction2);
1064
1197
 
1065
1198
  // src/commands/invite/index.ts
@@ -1257,9 +1390,185 @@ orgCommand.command("members").description("List organization members").action(me
1257
1390
  orgCommand.command("set-role <email> <role>").description("Update a member's role (owner/admin/member/viewer)").action(setRoleAction);
1258
1391
  orgCommand.command("remove <email>").description("Remove a member from the organization").action(removeAction);
1259
1392
 
1260
- // src/commands/auth/index.ts
1393
+ // src/commands/project/index.ts
1261
1394
  import { Command as Command5 } from "commander";
1262
1395
 
1396
+ // src/commands/project/create.ts
1397
+ async function createAction3(name) {
1398
+ const user = getConfig("user");
1399
+ if (!user?.org_id) {
1400
+ console.error("\u2717 No organization found. Run: ardent login");
1401
+ process.exit(1);
1402
+ }
1403
+ try {
1404
+ const project = await api.post("/v1/projects", {
1405
+ name,
1406
+ org_id: user.org_id
1407
+ });
1408
+ const cached = getCacheEntry("projects");
1409
+ const cachedProjects = cached?.data || [];
1410
+ cachedProjects.push(project);
1411
+ setCacheEntry("projects", cachedProjects);
1412
+ setConfig("currentProjectId", project.id);
1413
+ setConfig("currentProjectName", project.name);
1414
+ trackEvent("CLI: project create succeeded");
1415
+ console.log(`\u2713 Project '${name}' created`);
1416
+ console.log(` ID: ${project.id}`);
1417
+ } catch (err) {
1418
+ if (isNetworkError(err)) {
1419
+ trackEvent("CLI: project create failed", { reason: "offline" });
1420
+ console.error("\u2717 Cannot create project while offline");
1421
+ process.exit(1);
1422
+ }
1423
+ if (isPermissionError(err)) {
1424
+ trackEvent("CLI: project create failed", { reason: "permission_denied" });
1425
+ console.error("\u2717 You don't have permission to create projects.");
1426
+ process.exit(1);
1427
+ }
1428
+ trackEvent("CLI: project create failed", { reason: "api_error" });
1429
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
1430
+ process.exit(1);
1431
+ }
1432
+ }
1433
+
1434
+ // src/commands/project/list.ts
1435
+ async function listAction4() {
1436
+ let projects = [];
1437
+ let fromCache = false;
1438
+ let cacheTime = "";
1439
+ try {
1440
+ const user = getConfig("user");
1441
+ if (!user?.org_id) {
1442
+ console.error("\u2717 No organization found. Run: ardent login");
1443
+ process.exit(1);
1444
+ }
1445
+ const result = await api.get(
1446
+ `/v1/projects?org_id=${user.org_id}`
1447
+ );
1448
+ if (!result.projects) {
1449
+ throw new Error("API returned invalid response: missing projects array");
1450
+ }
1451
+ projects = result.projects;
1452
+ setCacheEntry("projects", projects);
1453
+ } catch (err) {
1454
+ if (isNetworkError(err)) {
1455
+ const cached = getCacheEntry("projects");
1456
+ if (cached) {
1457
+ projects = cached.data;
1458
+ fromCache = true;
1459
+ cacheTime = formatCacheTime(cached.updated_at);
1460
+ } else {
1461
+ trackEvent("CLI: project list failed", { reason: "offline_no_cache" });
1462
+ console.error("\u2717 Offline and no cached data available");
1463
+ process.exit(1);
1464
+ }
1465
+ } else {
1466
+ trackEvent("CLI: project list failed", { reason: "api_error" });
1467
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
1468
+ process.exit(1);
1469
+ }
1470
+ }
1471
+ trackEvent("CLI: project list succeeded", {
1472
+ project_count: projects.length,
1473
+ from_cache: fromCache
1474
+ });
1475
+ if (fromCache) {
1476
+ console.log(`\u26A0 Offline - showing cached data from ${cacheTime}
1477
+ `);
1478
+ }
1479
+ if (projects.length === 0) {
1480
+ console.log("No projects found");
1481
+ return;
1482
+ }
1483
+ const currentProjectId = getConfig("currentProjectId");
1484
+ const green3 = "\x1B[32m";
1485
+ const dim3 = "\x1B[2m";
1486
+ const reset3 = "\x1B[0m";
1487
+ console.log("Projects:\n");
1488
+ for (const project of projects) {
1489
+ const isCurrent = project.id === currentProjectId;
1490
+ if (isCurrent) {
1491
+ console.log(`${green3}* ${project.name}${reset3}`);
1492
+ console.log(`${green3} ${project.id}${reset3}`);
1493
+ } else {
1494
+ console.log(` ${project.name}`);
1495
+ console.log(`${dim3} ${project.id}${reset3}`);
1496
+ }
1497
+ console.log();
1498
+ }
1499
+ }
1500
+
1501
+ // src/commands/project/switch.ts
1502
+ async function switchAction3(name) {
1503
+ let projects = [];
1504
+ const cached = getCacheEntry("projects");
1505
+ let project = cached?.data.find(
1506
+ (cachedProject) => cachedProject.name === name
1507
+ );
1508
+ if (!project) {
1509
+ try {
1510
+ const user = getConfig("user");
1511
+ if (!user?.org_id) {
1512
+ console.error("\u2717 No organization found. Run: ardent login");
1513
+ process.exit(1);
1514
+ }
1515
+ const result = await api.get(
1516
+ `/v1/projects?org_id=${user.org_id}`
1517
+ );
1518
+ if (!result.projects) {
1519
+ throw new Error(
1520
+ "API returned invalid response: missing projects array"
1521
+ );
1522
+ }
1523
+ projects = result.projects;
1524
+ setCacheEntry("projects", projects);
1525
+ project = projects.find(
1526
+ (remoteProject) => remoteProject.name === name
1527
+ );
1528
+ } catch (err) {
1529
+ if (isNetworkError(err)) {
1530
+ if (!project) {
1531
+ console.error(`\u2717 Project "${name}" not found in cache`);
1532
+ console.log(" Run 'ardent project list' when online to refresh");
1533
+ process.exit(1);
1534
+ }
1535
+ } else {
1536
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
1537
+ process.exit(1);
1538
+ }
1539
+ }
1540
+ }
1541
+ if (!project) {
1542
+ console.error(`\u2717 Project "${name}" not found`);
1543
+ console.log(" Run: ardent project list");
1544
+ process.exit(1);
1545
+ }
1546
+ const previousId = getConfig("currentProjectId");
1547
+ const previousName = getConfig("currentProjectName");
1548
+ setConfig("currentProjectId", project.id);
1549
+ setConfig("currentProjectName", project.name);
1550
+ setConfig("currentConnectorId", void 0);
1551
+ setConfig("currentConnectorName", void 0);
1552
+ clearCurrentBranch();
1553
+ if (previousName && previousName !== name) {
1554
+ console.log(`Switched from '${previousName}' to '${name}'`);
1555
+ } else {
1556
+ console.log(`Switched to project '${name}'`);
1557
+ }
1558
+ trackEvent("CLI: project switch");
1559
+ }
1560
+
1561
+ // src/commands/project/index.ts
1562
+ var projectCommand = new Command5("project").description(
1563
+ "Manage projects"
1564
+ );
1565
+ projectCommand.command("create <name>").description("Create a new project").action(createAction3);
1566
+ projectCommand.command("list").description("List your projects").action(listAction4);
1567
+ projectCommand.command("switch <name>").description("Switch to a different project").action(switchAction3);
1568
+
1569
+ // src/commands/auth/index.ts
1570
+ import { Command as Command6 } from "commander";
1571
+
1263
1572
  // src/commands/auth/login.ts
1264
1573
  async function loginAction(options) {
1265
1574
  if (options.token) {
@@ -1389,9 +1698,84 @@ function statusAction() {
1389
1698
  }
1390
1699
 
1391
1700
  // 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);
1701
+ var loginCommand = new Command6("login").description("Login to Ardent").option("-t, --token <token>", "API token (skip browser login)").action(loginAction);
1702
+ var logoutCommand = new Command6("logout").description("Logout from Ardent").action(logoutAction);
1703
+ var statusCommand = new Command6("status").description("Show status").action(statusAction);
1704
+
1705
+ // src/lib/update-check.ts
1706
+ import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1707
+ import { join as join2 } from "path";
1708
+ import { homedir as homedir2 } from "os";
1709
+ var UPDATE_CHECK_FILE = join2(homedir2(), ".ardent", "update-check.json");
1710
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1711
+ var PACKAGE_NAME = "ardent-cli";
1712
+ function loadCache() {
1713
+ try {
1714
+ if (existsSync2(UPDATE_CHECK_FILE)) {
1715
+ return JSON.parse(readFileSync3(UPDATE_CHECK_FILE, "utf-8"));
1716
+ }
1717
+ } catch {
1718
+ }
1719
+ return null;
1720
+ }
1721
+ function saveCache(latestVersion) {
1722
+ try {
1723
+ const data = {
1724
+ latest_version: latestVersion,
1725
+ checked_at: (/* @__PURE__ */ new Date()).toISOString()
1726
+ };
1727
+ writeFileSync2(UPDATE_CHECK_FILE, JSON.stringify(data));
1728
+ } catch {
1729
+ }
1730
+ }
1731
+ function isNewerVersion(current, latest) {
1732
+ const currentParts = current.split(".").map(Number);
1733
+ const latestParts = latest.split(".").map(Number);
1734
+ for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
1735
+ const currentPart = currentParts[i] || 0;
1736
+ const latestPart = latestParts[i] || 0;
1737
+ if (latestPart > currentPart) return true;
1738
+ if (latestPart < currentPart) return false;
1739
+ }
1740
+ return false;
1741
+ }
1742
+ async function checkForUpdate(currentVersion) {
1743
+ try {
1744
+ const cache = loadCache();
1745
+ if (cache) {
1746
+ const elapsed = Date.now() - new Date(cache.checked_at).getTime();
1747
+ if (elapsed < CHECK_INTERVAL_MS) {
1748
+ if (isNewerVersion(currentVersion, cache.latest_version)) {
1749
+ printUpdateNotice(currentVersion, cache.latest_version);
1750
+ }
1751
+ return;
1752
+ }
1753
+ }
1754
+ const controller = new AbortController();
1755
+ const timeout = setTimeout(() => controller.abort(), 3e3);
1756
+ const response = await fetch(
1757
+ `https://registry.npmjs.org/${PACKAGE_NAME}/latest`,
1758
+ { signal: controller.signal }
1759
+ );
1760
+ clearTimeout(timeout);
1761
+ if (!response.ok) return;
1762
+ const data = await response.json();
1763
+ const latestVersion = data.version;
1764
+ saveCache(latestVersion);
1765
+ if (isNewerVersion(currentVersion, latestVersion)) {
1766
+ printUpdateNotice(currentVersion, latestVersion);
1767
+ }
1768
+ } catch {
1769
+ }
1770
+ }
1771
+ function printUpdateNotice(current, latest) {
1772
+ const yellow2 = "\x1B[33m";
1773
+ const green3 = "\x1B[32m";
1774
+ const reset3 = "\x1B[0m";
1775
+ console.error(
1776
+ `${yellow2}Update available: ${current} \u2192 ${latest}${reset3} \u2014 run ${green3}npm install -g ardent-cli@latest${reset3}`
1777
+ );
1778
+ }
1395
1779
 
1396
1780
  // src/index.ts
1397
1781
  var HELP_TEXT = `
@@ -1403,9 +1787,15 @@ AUTHENTICATION
1403
1787
  logout Clear stored credentials
1404
1788
  status Check authentication status
1405
1789
 
1790
+ PROJECTS
1791
+ project create Create a new project
1792
+ project list List your projects (* = current)
1793
+ project switch Switch to a different project
1794
+
1406
1795
  CONNECTORS
1407
1796
  connector create Connect a database (postgresql, snowflake, etc.)
1408
- connector list List your connectors
1797
+ connector list List your connectors (* = current)
1798
+ connector switch Switch to a different connector
1409
1799
  connector delete Delete a connector
1410
1800
 
1411
1801
  BRANCHES
@@ -1435,24 +1825,25 @@ EXAMPLES
1435
1825
  ardent branch switch my-feature
1436
1826
  ardent org members
1437
1827
  `;
1438
- var CLI_VERSION = getCliVersion();
1439
- var program = new Command6();
1440
- function getCliVersion() {
1828
+ var CLI_VERSION2 = getCliVersion2();
1829
+ var program = new Command7();
1830
+ function getCliVersion2() {
1441
1831
  try {
1442
1832
  const packageUrl = new URL("../package.json", import.meta.url);
1443
- const raw = readFileSync2(packageUrl, "utf-8");
1833
+ const raw = readFileSync4(packageUrl, "utf-8");
1444
1834
  const parsed = JSON.parse(raw);
1445
1835
  return parsed.version ?? "unknown";
1446
1836
  } catch {
1447
1837
  return "unknown";
1448
1838
  }
1449
1839
  }
1450
- program.name("ardent").description("CLI for Ardent database branching").version(CLI_VERSION).configureHelp({
1840
+ program.name("ardent").description("CLI for Ardent database branching").version(CLI_VERSION2).configureHelp({
1451
1841
  formatHelp: () => BANNER + HELP_TEXT
1452
1842
  });
1453
1843
  program.addCommand(loginCommand);
1454
1844
  program.addCommand(logoutCommand);
1455
1845
  program.addCommand(statusCommand);
1846
+ program.addCommand(projectCommand);
1456
1847
  program.addCommand(connectorCommand);
1457
1848
  program.addCommand(branchCommand);
1458
1849
  program.addCommand(inviteCommand);
@@ -1480,4 +1871,5 @@ if (args.length === 0) {
1480
1871
  program.help();
1481
1872
  }
1482
1873
  getAnonymousId();
1874
+ checkForUpdate(CLI_VERSION2);
1483
1875
  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.12",
4
4
  "description": "Git for Data infrastructure",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,7 +9,8 @@
9
9
  "scripts": {
10
10
  "build": "tsup",
11
11
  "dev": "tsup --watch",
12
- "start": "node dist/index.js"
12
+ "start": "node dist/index.js",
13
+ "test": "tsx --test src/lib/*.test.ts"
13
14
  },
14
15
  "files": [
15
16
  "dist"
@@ -24,6 +25,7 @@
24
25
  "devDependencies": {
25
26
  "@types/node": "^20.0.0",
26
27
  "tsup": "^8.0.0",
28
+ "tsx": "^4.21.0",
27
29
  "typescript": "^5.0.0"
28
30
  }
29
31
  }