ardent-cli 0.0.19 → 0.0.20

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 +267 -58
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -59,7 +59,9 @@ function getToken() {
59
59
  }
60
60
  function setCacheEntry(key, data) {
61
61
  const config = loadConfig();
62
- if (!config.cache) config.cache = {};
62
+ if (!config.cache) {
63
+ config.cache = {};
64
+ }
63
65
  config.cache[key] = {
64
66
  data,
65
67
  updated_at: (/* @__PURE__ */ new Date()).toISOString()
@@ -75,10 +77,16 @@ function formatCacheTime(isoString) {
75
77
  const now = /* @__PURE__ */ new Date();
76
78
  const diffMs = now.getTime() - date.getTime();
77
79
  const diffMins = Math.floor(diffMs / 6e4);
78
- if (diffMins < 1) return "just now";
79
- if (diffMins < 60) return `${diffMins}m ago`;
80
+ if (diffMins < 1) {
81
+ return "just now";
82
+ }
83
+ if (diffMins < 60) {
84
+ return `${diffMins}m ago`;
85
+ }
80
86
  const diffHours = Math.floor(diffMins / 60);
81
- if (diffHours < 24) return `${diffHours}h ago`;
87
+ if (diffHours < 24) {
88
+ return `${diffHours}h ago`;
89
+ }
82
90
  return date.toLocaleDateString();
83
91
  }
84
92
  function getCurrentBranch() {
@@ -94,42 +102,84 @@ function clearCurrentBranch() {
94
102
  }
95
103
  async function bootstrapCache() {
96
104
  const token = getToken();
97
- if (!token) throw new Error("Not authenticated");
105
+ if (!token) {
106
+ throw new Error("Not authenticated");
107
+ }
108
+ const staleConfig = loadConfig();
109
+ delete staleConfig.user;
110
+ delete staleConfig.userId;
111
+ delete staleConfig.currentProjectId;
112
+ delete staleConfig.currentProjectName;
113
+ delete staleConfig.currentConnectorId;
114
+ delete staleConfig.currentConnectorName;
115
+ delete staleConfig.currentBranch;
116
+ delete staleConfig.cache;
117
+ saveConfig(staleConfig);
98
118
  const apiUrl = getApiUrl();
99
119
  const headers = {
100
120
  "Content-Type": "application/json",
101
121
  "Authorization": `Bearer ${token}`
102
122
  };
103
- const [connectorsRes, meRes] = await Promise.all([
104
- fetch(`${apiUrl}/v1/cli/connectors`, { headers }),
105
- fetch(`${apiUrl}/v1/cli/me`, { headers })
106
- ]);
107
- let connectorCount = 0;
108
- let branchCount = 0;
109
- if (connectorsRes.ok) {
110
- const data = await connectorsRes.json();
111
- const connectors = data.connectors || [];
112
- setCacheEntry("connectors", connectors);
113
- connectorCount = connectors.length;
123
+ const userInfoResponse = await fetch(`${apiUrl}/v1/cli/me`, { headers });
124
+ if (!userInfoResponse.ok) {
125
+ throw new Error("Failed to fetch user info");
126
+ }
127
+ const user = await userInfoResponse.json();
128
+ setConfig("user", user);
129
+ const bootstrap = { projects: [], connectors: [], branches: [] };
130
+ if (!user.org_id) {
131
+ return bootstrap;
132
+ }
133
+ const projectsResponse = await fetch(`${apiUrl}/v1/projects?org_id=${user.org_id}`, { headers });
134
+ if (projectsResponse.ok) {
135
+ const projectsPayload = await projectsResponse.json();
136
+ if (!Array.isArray(projectsPayload.projects)) {
137
+ throw new Error("API returned invalid response: missing projects array");
138
+ }
139
+ bootstrap.projects = projectsPayload.projects;
140
+ setCacheEntry("projects", bootstrap.projects);
141
+ }
142
+ if (bootstrap.projects.length === 1) {
143
+ const project = bootstrap.projects[0];
144
+ setConfig("currentProjectId", project.id);
145
+ setConfig("currentProjectName", project.name);
114
146
  }
115
- if (meRes.ok) {
116
- const user = await meRes.json();
117
- setConfig("user", user);
147
+ const currentProjectId = getConfig("currentProjectId");
148
+ if (currentProjectId) {
149
+ const connectorsResponse = await fetch(
150
+ `${apiUrl}/v1/cli/connectors?project_id=${currentProjectId}`,
151
+ { headers }
152
+ );
153
+ if (connectorsResponse.ok) {
154
+ const connectorsPayload = await connectorsResponse.json();
155
+ if (!Array.isArray(connectorsPayload.connectors)) {
156
+ throw new Error("API returned invalid response: missing connectors array");
157
+ }
158
+ bootstrap.connectors = connectorsPayload.connectors;
159
+ setCacheEntry("connectors", bootstrap.connectors);
160
+ }
161
+ if (bootstrap.connectors.length === 1) {
162
+ const connector = bootstrap.connectors[0];
163
+ setConfig("currentConnectorId", connector.id);
164
+ setConfig("currentConnectorName", connector.name);
165
+ }
118
166
  }
119
167
  const currentConnectorId = getConfig("currentConnectorId");
120
168
  if (currentConnectorId) {
121
- const branchesRes = await fetch(
169
+ const branchesResponse = await fetch(
122
170
  `${apiUrl}/v1/cli/branches?connector_id=${currentConnectorId}`,
123
171
  { headers }
124
172
  );
125
- if (branchesRes.ok) {
126
- const data = await branchesRes.json();
127
- const branches = data.branches || [];
128
- setCacheEntry("branches", branches);
129
- branchCount = branches.length;
173
+ if (branchesResponse.ok) {
174
+ const branchesPayload = await branchesResponse.json();
175
+ if (!Array.isArray(branchesPayload.branches)) {
176
+ throw new Error("API returned invalid response: missing branches array");
177
+ }
178
+ bootstrap.branches = branchesPayload.branches;
179
+ setCacheEntry("branches", bootstrap.branches);
130
180
  }
131
181
  }
132
- return { connectors: connectorCount, branches: branchCount };
182
+ return bootstrap;
133
183
  }
134
184
 
135
185
  // src/lib/api.ts
@@ -915,8 +965,10 @@ async function listAction2() {
915
965
  `);
916
966
  }
917
967
  if (connectors.length === 0) {
968
+ const green3 = "\x1B[32m";
969
+ const reset3 = "\x1B[0m";
918
970
  console.log("No connectors found");
919
- console.log(" Create one with: ardent connector create postgresql <url>");
971
+ console.log(`${green3} Create one with: ardent connector create postgresql <url>${reset3}`);
920
972
  return;
921
973
  }
922
974
  const currentConnectorId = getConfig("currentConnectorId");
@@ -1290,9 +1342,11 @@ async function createAction3(name) {
1290
1342
  setCacheEntry("projects", cachedProjects);
1291
1343
  setConfig("currentProjectId", project.id);
1292
1344
  setConfig("currentProjectName", project.name);
1345
+ setConfig("currentConnectorId", void 0);
1346
+ setConfig("currentConnectorName", void 0);
1347
+ clearCurrentBranch();
1293
1348
  trackEvent("CLI: project create succeeded");
1294
1349
  console.log(`\u2713 Project '${name}' created`);
1295
- console.log(` ID: ${project.id}`);
1296
1350
  } catch (err) {
1297
1351
  if (isNetworkError(err)) {
1298
1352
  trackEvent("CLI: project create failed", { reason: "offline" });
@@ -1368,10 +1422,8 @@ async function listAction4() {
1368
1422
  const isCurrent = project.id === currentProjectId;
1369
1423
  if (isCurrent) {
1370
1424
  console.log(`${green2}* ${project.name}${reset2}`);
1371
- console.log(`${green2} ${project.id}${reset2}`);
1372
1425
  } else {
1373
1426
  console.log(` ${project.name}`);
1374
- console.log(`${dim2} ${project.id}${reset2}`);
1375
1427
  }
1376
1428
  console.log();
1377
1429
  }
@@ -1434,9 +1486,114 @@ async function switchAction3(name) {
1434
1486
  } else {
1435
1487
  console.log(`Switched to project '${name}'`);
1436
1488
  }
1489
+ try {
1490
+ const result = await api.get(`/v1/cli/connectors?project_id=${project.id}`);
1491
+ if (!Array.isArray(result.connectors)) {
1492
+ throw new Error("API returned invalid response: missing connectors array");
1493
+ }
1494
+ const connectors = result.connectors;
1495
+ setCacheEntry("connectors", connectors);
1496
+ if (connectors.length === 1) {
1497
+ const connector = connectors[0];
1498
+ setConfig("currentConnectorId", connector.id);
1499
+ setConfig("currentConnectorName", connector.name);
1500
+ console.log("");
1501
+ console.log(` Auto-selected connector '${connector.name}'`);
1502
+ console.log("");
1503
+ } else if (connectors.length === 0) {
1504
+ const green2 = "\x1B[32m";
1505
+ const reset2 = "\x1B[0m";
1506
+ console.log("");
1507
+ console.log(" No connectors found.");
1508
+ console.log(`${green2} Create one with: ardent connector create postgresql <url>${reset2}`);
1509
+ console.log("");
1510
+ }
1511
+ } catch (err) {
1512
+ let reason;
1513
+ if (isNetworkError(err)) {
1514
+ reason = "offline";
1515
+ console.error(" \u26A0 Couldn't fetch connectors (offline). Run 'ardent connector list' when online.");
1516
+ } else if (isPermissionError(err)) {
1517
+ reason = "permission_denied";
1518
+ console.error(" \u26A0 No permission to list connectors in this project.");
1519
+ } else {
1520
+ reason = "api_error";
1521
+ console.error(" \u26A0 Couldn't fetch connectors:", err instanceof Error ? err.message : err);
1522
+ }
1523
+ trackEvent("CLI: project switch autoselect failed", { reason });
1524
+ }
1437
1525
  trackEvent("CLI: project switch");
1438
1526
  }
1439
1527
 
1528
+ // src/commands/project/delete.ts
1529
+ async function deleteAction4(name) {
1530
+ const user = getConfig("user");
1531
+ if (!user?.org_id) {
1532
+ console.error("\u2717 No organization found. Run: ardent login");
1533
+ process.exit(1);
1534
+ }
1535
+ const cached = getCacheEntry("projects");
1536
+ let project = cached?.data.find((p) => p.name === name);
1537
+ if (!project) {
1538
+ try {
1539
+ const result = await api.get(`/v1/projects?org_id=${user.org_id}`);
1540
+ if (!Array.isArray(result.projects)) {
1541
+ throw new Error("API returned invalid response: missing projects array");
1542
+ }
1543
+ project = result.projects.find((p) => p.name === name);
1544
+ } catch (err) {
1545
+ if (isNetworkError(err)) {
1546
+ console.error(`\u2717 Project "${name}" not found in cache (offline)`);
1547
+ console.error(" Run 'ardent project list' when online to refresh");
1548
+ process.exit(1);
1549
+ }
1550
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
1551
+ process.exit(1);
1552
+ }
1553
+ }
1554
+ if (!project) {
1555
+ console.error(`\u2717 Project "${name}" not found`);
1556
+ console.error(" Run: ardent project list");
1557
+ process.exit(1);
1558
+ }
1559
+ const projectId = project.id;
1560
+ const projectName = project.name;
1561
+ try {
1562
+ console.log("Deleting project...");
1563
+ await api.delete(`/v1/projects/${projectId}`);
1564
+ const cachedAfter = getCacheEntry("projects");
1565
+ if (cachedAfter?.data) {
1566
+ const remaining = cachedAfter.data.filter((p) => p.id !== projectId);
1567
+ setCacheEntry("projects", remaining);
1568
+ }
1569
+ const currentId = getConfig("currentProjectId");
1570
+ if (currentId === projectId) {
1571
+ setConfig("currentProjectId", void 0);
1572
+ setConfig("currentProjectName", void 0);
1573
+ setConfig("currentConnectorId", void 0);
1574
+ setConfig("currentConnectorName", void 0);
1575
+ clearCurrentBranch();
1576
+ }
1577
+ trackEvent("CLI: project delete succeeded");
1578
+ console.log("\u2713 Project deleted");
1579
+ console.log(` Name: ${projectName}`);
1580
+ } catch (err) {
1581
+ if (isNetworkError(err)) {
1582
+ trackEvent("CLI: project delete failed", { reason: "offline" });
1583
+ console.error("\u2717 Cannot delete project while offline");
1584
+ process.exit(1);
1585
+ }
1586
+ if (isPermissionError(err)) {
1587
+ trackEvent("CLI: project delete failed", { reason: "permission_denied" });
1588
+ console.error("\u2717 You don't have permission to delete this project.");
1589
+ process.exit(1);
1590
+ }
1591
+ trackEvent("CLI: project delete failed", { reason: "api_error" });
1592
+ console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
1593
+ process.exit(1);
1594
+ }
1595
+ }
1596
+
1440
1597
  // src/commands/project/index.ts
1441
1598
  var projectCommand = new Command5("project").description(
1442
1599
  "Manage projects"
@@ -1444,6 +1601,7 @@ var projectCommand = new Command5("project").description(
1444
1601
  projectCommand.command("create <name>").description("Create a new project").action(createAction3);
1445
1602
  projectCommand.command("list").description("List your projects").action(listAction4);
1446
1603
  projectCommand.command("switch <name>").description("Switch to a different project").action(switchAction3);
1604
+ projectCommand.command("delete <name>").description("Delete a project by name").action(deleteAction4);
1447
1605
 
1448
1606
  // src/commands/settings/index.ts
1449
1607
  import { Command as Command6 } from "commander";
@@ -1614,26 +1772,82 @@ settingsCommand.command("remove <key>").description("Remove a setting. Keys: def
1614
1772
  import { Command as Command7 } from "commander";
1615
1773
 
1616
1774
  // src/commands/auth/login.ts
1775
+ function identifyAndTrack(userId) {
1776
+ const user = getConfig("user");
1777
+ const personProperties = {};
1778
+ if (user?.email) {
1779
+ personProperties.email = user.email;
1780
+ }
1781
+ if (user?.full_name) {
1782
+ personProperties.$name = user.full_name;
1783
+ }
1784
+ if (user?.org_name) {
1785
+ personProperties.org_name = user.org_name;
1786
+ }
1787
+ const resolvedUserId = userId || user?.user_id;
1788
+ if (resolvedUserId) {
1789
+ identifyUser(resolvedUserId, personProperties);
1790
+ }
1791
+ }
1792
+ function printBootstrapFailureGuidance() {
1793
+ console.log("\n Couldn't load your projects. Try:");
1794
+ console.log(" ardent project list");
1795
+ }
1796
+ function printAutoSelection(bootstrap) {
1797
+ const { projects, connectors } = bootstrap;
1798
+ const green2 = "\x1B[32m";
1799
+ const reset2 = "\x1B[0m";
1800
+ if (projects.length === 0) {
1801
+ console.log(`
1802
+ ${green2}No projects found. Create one:${reset2}`);
1803
+ console.log(`${green2} ardent project create <name>${reset2}`);
1804
+ return;
1805
+ }
1806
+ if (projects.length === 1) {
1807
+ console.log(` Auto-selected project '${projects[0].name}'`);
1808
+ } else {
1809
+ console.log(`
1810
+ ${projects.length} projects found.`);
1811
+ console.log("");
1812
+ console.log(`${green2} Next step: select a project: ardent project switch <name>${reset2}`);
1813
+ console.log("");
1814
+ console.log(` List your projects: ardent project list`);
1815
+ console.log("");
1816
+ return;
1817
+ }
1818
+ if (connectors.length === 0) {
1819
+ console.log(`
1820
+ ${green2}No connectors found. Create one:${reset2}`);
1821
+ console.log(`${green2} ardent connector create postgresql <url>${reset2}`);
1822
+ return;
1823
+ }
1824
+ if (connectors.length === 1) {
1825
+ console.log(` Auto-selected connector '${connectors[0].name}'`);
1826
+ console.log(`
1827
+ ${green2}Ready to branch:${reset2}`);
1828
+ console.log(`${green2} ardent branch create <name>${reset2}`);
1829
+ } else {
1830
+ console.log(`
1831
+ ${connectors.length} connectors found.`);
1832
+ console.log("");
1833
+ console.log(`${green2} Next step: select a connector: ardent connector switch <name>${reset2}`);
1834
+ console.log("");
1835
+ console.log(` List your connectors: ardent connector list`);
1836
+ console.log("");
1837
+ }
1838
+ }
1617
1839
  async function loginAction(options) {
1618
1840
  if (options.token) {
1619
1841
  setConfig("token", options.token);
1620
1842
  console.log("\u2713 Logged in successfully");
1621
1843
  trackEvent("CLI: login succeeded", { method: "token" });
1622
1844
  try {
1623
- await bootstrapCache();
1624
- const user = getConfig("user");
1625
- if (user) {
1626
- const tokenPersonProperties = {};
1627
- if (user.email) tokenPersonProperties.email = user.email;
1628
- if (user.full_name) tokenPersonProperties.$name = user.full_name;
1629
- if (user.org_name) tokenPersonProperties.org_name = user.org_name;
1630
- if (user.user_id) {
1631
- identifyUser(user.user_id, tokenPersonProperties);
1632
- }
1633
- }
1845
+ const bootstrap = await bootstrapCache();
1846
+ identifyAndTrack(null);
1847
+ printAutoSelection(bootstrap);
1634
1848
  } catch {
1849
+ printBootstrapFailureGuidance();
1635
1850
  }
1636
- showNextStep();
1637
1851
  return;
1638
1852
  }
1639
1853
  console.log("Opening browser for authentication...");
@@ -1665,34 +1879,28 @@ async function loginAction(options) {
1665
1879
  console.error("\u2717 Poll failed");
1666
1880
  process.exit(1);
1667
1881
  }
1668
- const result = await pollResponse.json();
1669
- if (result.status === "completed") {
1670
- setConfig("token", result.token);
1882
+ const pollResult = await pollResponse.json();
1883
+ if (pollResult.status === "completed") {
1884
+ setConfig("token", pollResult.token);
1671
1885
  console.log("\n\u2713 Logged in successfully");
1672
1886
  trackEvent("CLI: login succeeded", { method: "browser" });
1673
1887
  try {
1674
- await bootstrapCache();
1888
+ const bootstrap = await bootstrapCache();
1889
+ identifyAndTrack(pollResult.user_id);
1890
+ printAutoSelection(bootstrap);
1675
1891
  } catch {
1892
+ printBootstrapFailureGuidance();
1676
1893
  }
1677
- const user = getConfig("user");
1678
- const personProperties = {};
1679
- if (user?.email) personProperties.email = user.email;
1680
- if (user?.full_name) personProperties.$name = user.full_name;
1681
- if (user?.org_name) personProperties.org_name = user.org_name;
1682
- if (result.user_id) {
1683
- identifyUser(result.user_id, personProperties);
1684
- }
1685
- showNextStep();
1686
1894
  return;
1687
1895
  }
1688
- if (result.status === "expired") {
1896
+ if (pollResult.status === "expired") {
1689
1897
  trackEvent("CLI: login failed", { method: "browser", reason: "expired" });
1690
1898
  console.error("\n\u2717 Session expired. Please try again.");
1691
1899
  process.exit(1);
1692
1900
  }
1693
- if (result.status === "error") {
1901
+ if (pollResult.status === "error") {
1694
1902
  trackEvent("CLI: login failed", { method: "browser", reason: "error" });
1695
- console.error("\n\u2717 Error:", result.message);
1903
+ console.error("\n\u2717 Error:", pollResult.message);
1696
1904
  process.exit(1);
1697
1905
  }
1698
1906
  process.stdout.write(".");
@@ -1835,6 +2043,7 @@ PROJECTS
1835
2043
  project create Create a new project
1836
2044
  project list List your projects (* = current)
1837
2045
  project switch Switch to a different project
2046
+ project delete Delete a project by name
1838
2047
 
1839
2048
  CONNECTORS
1840
2049
  connector create Connect a database (postgresql, snowflake, etc.)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ardent-cli",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "description": "Git for Data infrastructure",
5
5
  "type": "module",
6
6
  "bin": {