@specific.dev/cli 0.1.69 → 0.1.71

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 (68) hide show
  1. package/dist/admin/404/index.html +1 -1
  2. package/dist/admin/404.html +1 -1
  3. package/dist/admin/__next.!KGRlZmF1bHQp.__PAGE__.txt +1 -1
  4. package/dist/admin/__next.!KGRlZmF1bHQp.txt +1 -1
  5. package/dist/admin/__next._full.txt +1 -1
  6. package/dist/admin/__next._head.txt +1 -1
  7. package/dist/admin/__next._index.txt +1 -1
  8. package/dist/admin/__next._tree.txt +1 -1
  9. package/dist/admin/_not-found/__next._full.txt +1 -1
  10. package/dist/admin/_not-found/__next._head.txt +1 -1
  11. package/dist/admin/_not-found/__next._index.txt +1 -1
  12. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
  13. package/dist/admin/_not-found/__next._not-found.txt +1 -1
  14. package/dist/admin/_not-found/__next._tree.txt +1 -1
  15. package/dist/admin/_not-found/index.html +1 -1
  16. package/dist/admin/_not-found/index.txt +1 -1
  17. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.__PAGE__.txt +1 -1
  18. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.txt +1 -1
  19. package/dist/admin/databases/__next.!KGRlZmF1bHQp.txt +1 -1
  20. package/dist/admin/databases/__next._full.txt +1 -1
  21. package/dist/admin/databases/__next._head.txt +1 -1
  22. package/dist/admin/databases/__next._index.txt +1 -1
  23. package/dist/admin/databases/__next._tree.txt +1 -1
  24. package/dist/admin/databases/index.html +1 -1
  25. package/dist/admin/databases/index.txt +1 -1
  26. package/dist/admin/fullscreen/__next._full.txt +1 -1
  27. package/dist/admin/fullscreen/__next._head.txt +1 -1
  28. package/dist/admin/fullscreen/__next._index.txt +1 -1
  29. package/dist/admin/fullscreen/__next._tree.txt +1 -1
  30. package/dist/admin/fullscreen/__next.fullscreen.__PAGE__.txt +1 -1
  31. package/dist/admin/fullscreen/__next.fullscreen.txt +1 -1
  32. package/dist/admin/fullscreen/databases/__next._full.txt +1 -1
  33. package/dist/admin/fullscreen/databases/__next._head.txt +1 -1
  34. package/dist/admin/fullscreen/databases/__next._index.txt +1 -1
  35. package/dist/admin/fullscreen/databases/__next._tree.txt +1 -1
  36. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.__PAGE__.txt +1 -1
  37. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.txt +1 -1
  38. package/dist/admin/fullscreen/databases/__next.fullscreen.txt +1 -1
  39. package/dist/admin/fullscreen/databases/index.html +1 -1
  40. package/dist/admin/fullscreen/databases/index.txt +1 -1
  41. package/dist/admin/fullscreen/index.html +1 -1
  42. package/dist/admin/fullscreen/index.txt +1 -1
  43. package/dist/admin/index.html +1 -1
  44. package/dist/admin/index.txt +1 -1
  45. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.__PAGE__.txt +1 -1
  46. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.txt +1 -1
  47. package/dist/admin/mail/__next.!KGRlZmF1bHQp.txt +1 -1
  48. package/dist/admin/mail/__next._full.txt +1 -1
  49. package/dist/admin/mail/__next._head.txt +1 -1
  50. package/dist/admin/mail/__next._index.txt +1 -1
  51. package/dist/admin/mail/__next._tree.txt +1 -1
  52. package/dist/admin/mail/index.html +1 -1
  53. package/dist/admin/mail/index.txt +1 -1
  54. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.txt +1 -1
  55. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.__PAGE__.txt +1 -1
  56. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.txt +1 -1
  57. package/dist/admin/workflows/__next._full.txt +1 -1
  58. package/dist/admin/workflows/__next._head.txt +1 -1
  59. package/dist/admin/workflows/__next._index.txt +1 -1
  60. package/dist/admin/workflows/__next._tree.txt +1 -1
  61. package/dist/admin/workflows/index.html +1 -1
  62. package/dist/admin/workflows/index.txt +1 -1
  63. package/dist/cli.js +213 -174
  64. package/dist/postinstall.js +18 -4
  65. package/package.json +1 -1
  66. /package/dist/admin/_next/static/{gnD4mZgR72Rwh8wIqk2oN → 3nqvPNOX7a-_9HydleH6d}/_buildManifest.js +0 -0
  67. /package/dist/admin/_next/static/{gnD4mZgR72Rwh8wIqk2oN → 3nqvPNOX7a-_9HydleH6d}/_clientMiddlewareManifest.json +0 -0
  68. /package/dist/admin/_next/static/{gnD4mZgR72Rwh8wIqk2oN → 3nqvPNOX7a-_9HydleH6d}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -183842,6 +183842,18 @@ var RefreshTokenExpiredError = class extends Error {
183842
183842
 
183843
183843
  // src/lib/auth/workos.ts
183844
183844
  var WORKOS_CLIENT_ID = "client_01K4HSP8CE0R73V7Z67WE9CC86";
183845
+ function getTokenExpiresAt(accessToken) {
183846
+ try {
183847
+ const payload = accessToken.split(".")[1];
183848
+ const decoded = JSON.parse(Buffer.from(payload, "base64url").toString());
183849
+ if (typeof decoded.exp === "number") {
183850
+ return decoded.exp * 1e3;
183851
+ }
183852
+ return null;
183853
+ } catch {
183854
+ return null;
183855
+ }
183856
+ }
183845
183857
  async function initiateDeviceAuthorization() {
183846
183858
  const response = await fetch(
183847
183859
  "https://api.workos.com/user_management/authorize/device",
@@ -183906,6 +183918,157 @@ import React from "react";
183906
183918
  import { render, Box, Text } from "ink";
183907
183919
  import Spinner from "ink-spinner";
183908
183920
 
183921
+ // src/lib/auth/login-flow.ts
183922
+ function runLoginFlow(options2) {
183923
+ const { setState, isReauthentication } = options2;
183924
+ let cancelled = false;
183925
+ async function run() {
183926
+ try {
183927
+ setState({ phase: "initiating" });
183928
+ writeLog("auth", "Starting login flow");
183929
+ if (isReauthentication) {
183930
+ clearUserCredentials();
183931
+ }
183932
+ writeLog("auth", "Initiating device authorization with WorkOS");
183933
+ const deviceAuth = await initiateDeviceAuthorization();
183934
+ writeLog(
183935
+ "auth",
183936
+ `Device authorization received: user_code=${deviceAuth.user_code}`
183937
+ );
183938
+ if (cancelled) return;
183939
+ setState({
183940
+ phase: "waiting-for-browser",
183941
+ userCode: deviceAuth.user_code,
183942
+ verificationUri: deviceAuth.verification_uri_complete
183943
+ });
183944
+ const { default: open3 } = await Promise.resolve().then(() => (init_open(), open_exports));
183945
+ await open3(deviceAuth.verification_uri_complete);
183946
+ const token = await pollUntilToken(deviceAuth, () => cancelled);
183947
+ if (cancelled || !token) return;
183948
+ writeLog("auth", "Fetching user info and saving credentials...");
183949
+ await saveCredentialsFromToken(token);
183950
+ if (cancelled) return;
183951
+ writeLog("auth", "Credentials written successfully");
183952
+ setState({ phase: "success", email: token.user.email });
183953
+ writeLog("auth", "Login flow completed successfully");
183954
+ } catch (err) {
183955
+ if (cancelled) return;
183956
+ const message = err instanceof Error ? err.message : String(err);
183957
+ writeLog("auth", `Login error: ${message}`);
183958
+ setState({ phase: "error", message });
183959
+ }
183960
+ }
183961
+ run();
183962
+ return {
183963
+ cancel: () => {
183964
+ cancelled = true;
183965
+ }
183966
+ };
183967
+ }
183968
+ async function pollUntilToken(deviceAuth, isCancelled) {
183969
+ let interval = deviceAuth.interval * 1e3;
183970
+ const expiresAt = Date.now() + deviceAuth.expires_in * 1e3;
183971
+ while (!isCancelled() && Date.now() < expiresAt) {
183972
+ await sleep(interval);
183973
+ if (isCancelled()) return null;
183974
+ writeLog(
183975
+ "auth",
183976
+ `Polling for token (timeRemaining=${Math.round((expiresAt - Date.now()) / 1e3)}s)`
183977
+ );
183978
+ const response = await pollForToken(deviceAuth.device_code);
183979
+ writeLog(
183980
+ "auth",
183981
+ `Poll response: ${JSON.stringify(response).substring(0, 200)}`
183982
+ );
183983
+ if (!("error" in response)) {
183984
+ writeLog("auth", "Token received successfully from WorkOS");
183985
+ return response;
183986
+ }
183987
+ if (response.error === "slow_down") {
183988
+ interval += 1e3;
183989
+ writeLog("auth", `Slowing down, new interval: ${interval}ms`);
183990
+ } else if (response.error !== "authorization_pending") {
183991
+ throw new Error(`Authentication failed: ${response.error}`);
183992
+ }
183993
+ }
183994
+ if (!isCancelled()) {
183995
+ throw new Error("Authentication timed out. Please try again.");
183996
+ }
183997
+ return null;
183998
+ }
183999
+ function sleep(ms) {
184000
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
184001
+ }
184002
+
184003
+ // src/lib/auth/login.tsx
184004
+ function LoginUI({
184005
+ state,
184006
+ isReauthentication
184007
+ }) {
184008
+ if (state.phase === "error") {
184009
+ return /* @__PURE__ */ React.createElement(Text, { color: "red" }, "Error: ", state.message);
184010
+ }
184011
+ if (state.phase === "success") {
184012
+ return /* @__PURE__ */ React.createElement(Text, { color: "green" }, isReauthentication ? "Re-authenticated" : "Logged in", " as ", state.email);
184013
+ }
184014
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, isReauthentication && /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "Session expired. Please log in again."), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Log in to Specific"), state.phase === "waiting-for-browser" ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, null, "Your authentication code:", " ", /* @__PURE__ */ React.createElement(Text, { color: "cyan", bold: true }, state.userCode))), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "blue" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React.createElement(Text, null, " Waiting for authentication in browser...")), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "If the browser didn't open, visit: ", state.verificationUri)) : /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "blue" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React.createElement(Text, null, " Initiating login...")));
184015
+ }
184016
+ function performLogin(options2 = {}) {
184017
+ return new Promise((resolve10) => {
184018
+ let currentState = { phase: "initiating" };
184019
+ let flowHandle;
184020
+ const instance = render(
184021
+ /* @__PURE__ */ React.createElement(
184022
+ LoginUI,
184023
+ {
184024
+ state: currentState,
184025
+ isReauthentication: options2.isReauthentication
184026
+ }
184027
+ )
184028
+ );
184029
+ const cleanup = () => {
184030
+ flowHandle?.cancel();
184031
+ instance.unmount();
184032
+ };
184033
+ const handleExit = () => {
184034
+ cleanup();
184035
+ process.exit(0);
184036
+ };
184037
+ process.on("SIGINT", handleExit);
184038
+ process.on("SIGTERM", handleExit);
184039
+ flowHandle = runLoginFlow({
184040
+ isReauthentication: options2.isReauthentication,
184041
+ setState: (newState) => {
184042
+ currentState = newState;
184043
+ instance.rerender(
184044
+ /* @__PURE__ */ React.createElement(
184045
+ LoginUI,
184046
+ {
184047
+ state: currentState,
184048
+ isReauthentication: options2.isReauthentication
184049
+ }
184050
+ )
184051
+ );
184052
+ if (newState.phase === "success") {
184053
+ setTimeout(() => {
184054
+ process.off("SIGINT", handleExit);
184055
+ process.off("SIGTERM", handleExit);
184056
+ instance.unmount();
184057
+ resolve10({ success: true, userEmail: newState.email });
184058
+ }, 100);
184059
+ } else if (newState.phase === "error") {
184060
+ setTimeout(() => {
184061
+ process.off("SIGINT", handleExit);
184062
+ process.off("SIGTERM", handleExit);
184063
+ instance.unmount();
184064
+ resolve10({ success: false, error: new Error(newState.message) });
184065
+ }, 100);
184066
+ }
184067
+ }
184068
+ });
184069
+ });
184070
+ }
184071
+
183909
184072
  // src/lib/api/client.ts
183910
184073
  var ApiClient = class {
183911
184074
  baseUrl;
@@ -184217,166 +184380,6 @@ var ApiClient = class {
184217
184380
  }
184218
184381
  };
184219
184382
 
184220
- // src/lib/auth/login-flow.ts
184221
- function runLoginFlow(options2) {
184222
- const { setState, isReauthentication } = options2;
184223
- let cancelled = false;
184224
- async function run() {
184225
- try {
184226
- setState({ phase: "initiating" });
184227
- writeLog("auth", "Starting login flow");
184228
- if (isReauthentication) {
184229
- clearUserCredentials();
184230
- }
184231
- writeLog("auth", "Initiating device authorization with WorkOS");
184232
- const deviceAuth = await initiateDeviceAuthorization();
184233
- writeLog(
184234
- "auth",
184235
- `Device authorization received: user_code=${deviceAuth.user_code}`
184236
- );
184237
- if (cancelled) return;
184238
- setState({
184239
- phase: "waiting-for-browser",
184240
- userCode: deviceAuth.user_code,
184241
- verificationUri: deviceAuth.verification_uri_complete
184242
- });
184243
- const { default: open3 } = await Promise.resolve().then(() => (init_open(), open_exports));
184244
- await open3(deviceAuth.verification_uri_complete);
184245
- const token = await pollUntilToken(deviceAuth, () => cancelled);
184246
- if (cancelled || !token) return;
184247
- writeLog("auth", "Fetching user info from platform API...");
184248
- const client2 = new ApiClient({ token: token.access_token });
184249
- const user = await client2.getMe();
184250
- writeLog("auth", `User info received: id=${user.id}`);
184251
- if (cancelled) return;
184252
- writeLog("auth", "Writing credentials to disk...");
184253
- writeUserCredentials({
184254
- accessToken: token.access_token,
184255
- refreshToken: token.refresh_token,
184256
- expiresAt: Date.now() + token.expires_in * 1e3,
184257
- userId: user.id
184258
- });
184259
- writeLog("auth", "Credentials written successfully");
184260
- setState({ phase: "success", email: token.user.email });
184261
- writeLog("auth", "Login flow completed successfully");
184262
- } catch (err) {
184263
- if (cancelled) return;
184264
- const message = err instanceof Error ? err.message : String(err);
184265
- writeLog("auth", `Login error: ${message}`);
184266
- setState({ phase: "error", message });
184267
- }
184268
- }
184269
- run();
184270
- return {
184271
- cancel: () => {
184272
- cancelled = true;
184273
- }
184274
- };
184275
- }
184276
- async function pollUntilToken(deviceAuth, isCancelled) {
184277
- let interval = deviceAuth.interval * 1e3;
184278
- const expiresAt = Date.now() + deviceAuth.expires_in * 1e3;
184279
- while (!isCancelled() && Date.now() < expiresAt) {
184280
- await sleep(interval);
184281
- if (isCancelled()) return null;
184282
- writeLog(
184283
- "auth",
184284
- `Polling for token (timeRemaining=${Math.round((expiresAt - Date.now()) / 1e3)}s)`
184285
- );
184286
- const response = await pollForToken(deviceAuth.device_code);
184287
- writeLog(
184288
- "auth",
184289
- `Poll response: ${JSON.stringify(response).substring(0, 200)}`
184290
- );
184291
- if (!("error" in response)) {
184292
- writeLog("auth", "Token received successfully from WorkOS");
184293
- return response;
184294
- }
184295
- if (response.error === "slow_down") {
184296
- interval += 1e3;
184297
- writeLog("auth", `Slowing down, new interval: ${interval}ms`);
184298
- } else if (response.error !== "authorization_pending") {
184299
- throw new Error(`Authentication failed: ${response.error}`);
184300
- }
184301
- }
184302
- if (!isCancelled()) {
184303
- throw new Error("Authentication timed out. Please try again.");
184304
- }
184305
- return null;
184306
- }
184307
- function sleep(ms) {
184308
- return new Promise((resolve10) => setTimeout(resolve10, ms));
184309
- }
184310
-
184311
- // src/lib/auth/login.tsx
184312
- function LoginUI({
184313
- state,
184314
- isReauthentication
184315
- }) {
184316
- if (state.phase === "error") {
184317
- return /* @__PURE__ */ React.createElement(Text, { color: "red" }, "Error: ", state.message);
184318
- }
184319
- if (state.phase === "success") {
184320
- return /* @__PURE__ */ React.createElement(Text, { color: "green" }, isReauthentication ? "Re-authenticated" : "Logged in", " as ", state.email);
184321
- }
184322
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", gap: 1 }, isReauthentication && /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "Session expired. Please log in again."), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Log in to Specific"), state.phase === "waiting-for-browser" ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, null, "Your authentication code:", " ", /* @__PURE__ */ React.createElement(Text, { color: "cyan", bold: true }, state.userCode))), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "blue" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React.createElement(Text, null, " Waiting for authentication in browser...")), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "If the browser didn't open, visit: ", state.verificationUri)) : /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "blue" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React.createElement(Text, null, " Initiating login...")));
184323
- }
184324
- function performLogin(options2 = {}) {
184325
- return new Promise((resolve10) => {
184326
- let currentState = { phase: "initiating" };
184327
- let flowHandle;
184328
- const instance = render(
184329
- /* @__PURE__ */ React.createElement(
184330
- LoginUI,
184331
- {
184332
- state: currentState,
184333
- isReauthentication: options2.isReauthentication
184334
- }
184335
- )
184336
- );
184337
- const cleanup = () => {
184338
- flowHandle?.cancel();
184339
- instance.unmount();
184340
- };
184341
- const handleExit = () => {
184342
- cleanup();
184343
- process.exit(0);
184344
- };
184345
- process.on("SIGINT", handleExit);
184346
- process.on("SIGTERM", handleExit);
184347
- flowHandle = runLoginFlow({
184348
- isReauthentication: options2.isReauthentication,
184349
- setState: (newState) => {
184350
- currentState = newState;
184351
- instance.rerender(
184352
- /* @__PURE__ */ React.createElement(
184353
- LoginUI,
184354
- {
184355
- state: currentState,
184356
- isReauthentication: options2.isReauthentication
184357
- }
184358
- )
184359
- );
184360
- if (newState.phase === "success") {
184361
- setTimeout(() => {
184362
- process.off("SIGINT", handleExit);
184363
- process.off("SIGTERM", handleExit);
184364
- instance.unmount();
184365
- resolve10({ success: true, userEmail: newState.email });
184366
- }, 100);
184367
- } else if (newState.phase === "error") {
184368
- setTimeout(() => {
184369
- process.off("SIGINT", handleExit);
184370
- process.off("SIGTERM", handleExit);
184371
- instance.unmount();
184372
- resolve10({ success: false, error: new Error(newState.message) });
184373
- }, 100);
184374
- }
184375
- }
184376
- });
184377
- });
184378
- }
184379
-
184380
184383
  // src/lib/auth/credentials.ts
184381
184384
  function getUserCredentialsDir() {
184382
184385
  return path6.join(os5.homedir(), ".specific");
@@ -184419,11 +184422,33 @@ function getUserId() {
184419
184422
  const credentials = readUserCredentials();
184420
184423
  return credentials?.userId ?? null;
184421
184424
  }
184425
+ async function saveCredentialsFromToken(token) {
184426
+ const client2 = new ApiClient({ token: token.access_token });
184427
+ const user = await client2.getMe();
184428
+ const expiresAt = getTokenExpiresAt(token.access_token) ?? Date.now() + 3600 * 1e3;
184429
+ writeUserCredentials({
184430
+ accessToken: token.access_token,
184431
+ refreshToken: token.refresh_token,
184432
+ expiresAt,
184433
+ userId: user.id
184434
+ });
184435
+ }
184422
184436
  async function getValidAccessToken() {
184423
184437
  const credentials = readUserCredentials();
184424
184438
  if (!credentials) {
184425
184439
  throw new Error("Not logged in. Run 'specific login' first.");
184426
184440
  }
184441
+ if (!credentials.userId) {
184442
+ const result = await performLogin({ isReauthentication: true });
184443
+ if (!result.success) {
184444
+ throw result.error || new Error("Re-authentication failed");
184445
+ }
184446
+ const newCredentials = readUserCredentials();
184447
+ if (!newCredentials) {
184448
+ throw new Error("Failed to read credentials after re-authentication");
184449
+ }
184450
+ return newCredentials.accessToken;
184451
+ }
184427
184452
  const now = Date.now();
184428
184453
  const bufferMs = 5 * 60 * 1e3;
184429
184454
  if (credentials.expiresAt - bufferMs > now) {
@@ -184431,10 +184456,12 @@ async function getValidAccessToken() {
184431
184456
  }
184432
184457
  try {
184433
184458
  const response = await refreshAccessToken(credentials.refreshToken);
184459
+ const expiresAt = getTokenExpiresAt(response.access_token) ?? Date.now() + 3600 * 1e3;
184434
184460
  const updatedCredentials = {
184435
184461
  accessToken: response.access_token,
184436
184462
  refreshToken: response.refresh_token,
184437
- expiresAt: now + response.expires_in * 1e3
184463
+ expiresAt,
184464
+ userId: credentials.userId
184438
184465
  };
184439
184466
  writeUserCredentials(updatedCredentials);
184440
184467
  return updatedCredentials.accessToken;
@@ -184458,6 +184485,7 @@ async function getValidAccessToken() {
184458
184485
  var POSTHOG_HOST = "https://eu.i.posthog.com";
184459
184486
  var client = null;
184460
184487
  var anonymousId = null;
184488
+ var identified = false;
184461
184489
  function isEnabled() {
184462
184490
  return true;
184463
184491
  }
@@ -184491,16 +184519,29 @@ function getClient() {
184491
184519
  function trackEvent(event, properties) {
184492
184520
  const ph = getClient();
184493
184521
  if (!ph) return;
184522
+ const userId = getUserId();
184523
+ if (userId && !identified) {
184524
+ identified = true;
184525
+ ph.identify({
184526
+ distinctId: userId,
184527
+ properties: {
184528
+ anonymous_id: getAnonymousId()
184529
+ }
184530
+ });
184531
+ ph.alias({
184532
+ distinctId: userId,
184533
+ alias: getAnonymousId()
184534
+ });
184535
+ }
184494
184536
  ph.capture({
184495
- distinctId: getAnonymousId(),
184537
+ distinctId: userId ?? getAnonymousId(),
184496
184538
  event,
184497
184539
  properties: {
184498
184540
  ...properties,
184499
- cli_version: "0.1.69",
184541
+ cli_version: "0.1.71",
184500
184542
  platform: process.platform,
184501
184543
  node_version: process.version,
184502
- project_id: getProjectId(),
184503
- user_id: getUserId() ?? void 0
184544
+ project_id: getProjectId()
184504
184545
  }
184505
184546
  });
184506
184547
  }
@@ -193090,11 +193131,7 @@ function DeployUI({ environment, config, skipBuildTest }) {
193090
193131
  }
193091
193132
  } else {
193092
193133
  const successResponse = response;
193093
- writeUserCredentials({
193094
- accessToken: successResponse.access_token,
193095
- refreshToken: successResponse.refresh_token,
193096
- expiresAt: Date.now() + successResponse.expires_in * 1e3
193097
- });
193134
+ await saveCredentialsFromToken(successResponse);
193098
193135
  setState(
193099
193136
  (s) => s.projectId ? { phase: "testing-builds", projectId: s.projectId } : { phase: "loading-projects" }
193100
193137
  );
@@ -193472,7 +193509,8 @@ ${errorMsg}`
193472
193509
  ...s,
193473
193510
  phase: "error",
193474
193511
  deployment: status,
193475
- error: `Build "${failedBuild.serviceName}" failed: ${failedBuild.error}`
193512
+ error: `Build "${failedBuild.serviceName}" failed: ${failedBuild.error}`,
193513
+ buildOutput: failedBuild.output
193476
193514
  }));
193477
193515
  return;
193478
193516
  }
@@ -193652,6 +193690,7 @@ ${errorMsg}`
193652
193690
  phase,
193653
193691
  deployment,
193654
193692
  error,
193693
+ buildOutput,
193655
193694
  tarballSize,
193656
193695
  pendingActions,
193657
193696
  missingSecrets,
@@ -193782,7 +193821,7 @@ ${errorMsg}`
193782
193821
  onSubmit: handleConfigSubmit,
193783
193822
  onCancel: handleConfigCancel
193784
193823
  }
193785
- ), phase === "error" && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, deployment?.error ? /* @__PURE__ */ React7.createElement(StructuredError, { error: deployment.error }) : /* @__PURE__ */ React7.createElement(Text7, { color: "red" }, "Error: ", error)), phase === "success" && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { color: "green" }, "Deployment successful!"), deployment?.publicUrls && Object.keys(deployment.publicUrls).length > 0 && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Public URLs:"), Object.entries(deployment.publicUrls).map(([name, url]) => /* @__PURE__ */ React7.createElement(Text7, { key: name }, " ", name, ": ", /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, url))))));
193824
+ ), phase === "error" && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, deployment?.error ? /* @__PURE__ */ React7.createElement(StructuredError, { error: deployment.error }) : /* @__PURE__ */ React7.createElement(Text7, { color: "red", bold: true }, error), buildOutput && /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Build output:"), /* @__PURE__ */ React7.createElement(Text7, null, buildOutput))), phase === "success" && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { color: "green" }, "Deployment successful!"), deployment?.publicUrls && Object.keys(deployment.publicUrls).length > 0 && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Public URLs:"), Object.entries(deployment.publicUrls).map(([name, url]) => /* @__PURE__ */ React7.createElement(Text7, { key: name }, " ", name, ": ", /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, url))))));
193786
193825
  }
193787
193826
  async function deployCommand(environment, options2) {
193788
193827
  const configPath = path23.join(process.cwd(), "specific.hcl");
@@ -194601,7 +194640,7 @@ function compareVersions(a, b) {
194601
194640
  return 0;
194602
194641
  }
194603
194642
  async function checkForUpdate() {
194604
- const currentVersion = "0.1.69";
194643
+ const currentVersion = "0.1.71";
194605
194644
  const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
194606
194645
  if (!response.ok) {
194607
194646
  throw new Error(`Failed to check for updates: HTTP ${response.status}`);
@@ -194800,7 +194839,7 @@ function updateCommand() {
194800
194839
  var program = new Command();
194801
194840
  var env = "production";
194802
194841
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
194803
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.69").enablePositionalOptions();
194842
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.71").enablePositionalOptions();
194804
194843
  program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").action((options2) => initCommand(options2));
194805
194844
  program.command("docs [topic]").description("Fetch LLM-optimized documentation").action(docsCommand);
194806
194845
  program.command("check").description("Validate specific.hcl configuration").action(checkCommand);
@@ -73,6 +73,7 @@ function getUserId() {
73
73
  var POSTHOG_HOST = "https://eu.i.posthog.com";
74
74
  var client = null;
75
75
  var anonymousId = null;
76
+ var identified = false;
76
77
  function isEnabled() {
77
78
  return true;
78
79
  }
@@ -106,16 +107,29 @@ function getClient() {
106
107
  function trackEvent(event, properties) {
107
108
  const ph = getClient();
108
109
  if (!ph) return;
110
+ const userId = getUserId();
111
+ if (userId && !identified) {
112
+ identified = true;
113
+ ph.identify({
114
+ distinctId: userId,
115
+ properties: {
116
+ anonymous_id: getAnonymousId()
117
+ }
118
+ });
119
+ ph.alias({
120
+ distinctId: userId,
121
+ alias: getAnonymousId()
122
+ });
123
+ }
109
124
  ph.capture({
110
- distinctId: getAnonymousId(),
125
+ distinctId: userId ?? getAnonymousId(),
111
126
  event,
112
127
  properties: {
113
128
  ...properties,
114
- cli_version: "0.1.69",
129
+ cli_version: "0.1.71",
115
130
  platform: process.platform,
116
131
  node_version: process.version,
117
- project_id: getProjectId(),
118
- user_id: getUserId() ?? void 0
132
+ project_id: getProjectId()
119
133
  }
120
134
  });
121
135
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specific.dev/cli",
3
- "version": "0.1.69",
3
+ "version": "0.1.71",
4
4
  "description": "CLI for Specific infrastructure-as-code",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",