@usesocial/cli 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @usesocial/cli
2
2
 
3
+ ## 0.3.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [#18](https://github.com/usesocial/monorepo/pull/18) [`831fe99`](https://github.com/usesocial/monorepo/commit/831fe99ed1d8ebc045e053fd53a8c5372ec7f49a) Thanks [@CyrusNuevoDia](https://github.com/CyrusNuevoDia)! - Resume existing CLI login sessions at the billing step when a seat is missing, and show account-connect guidance only after login and seat setup complete.
8
+
9
+ ## 0.3.4
10
+
11
+ ### Patch Changes
12
+
13
+ - [#17](https://github.com/usesocial/monorepo/pull/17) [`677e5a9`](https://github.com/usesocial/monorepo/commit/677e5a98f8c7603ddf5707459340e22736b6057f) Thanks [@CyrusNuevoDia](https://github.com/CyrusNuevoDia)! - Improve CLI onboarding and seat pricing copy.
14
+
3
15
  ## 0.3.3
4
16
 
5
17
  ### Patch Changes
package/dist/index.mjs CHANGED
@@ -32,7 +32,7 @@ const exampleDeviceCode = "KN42-A98N";
32
32
  const brand = {
33
33
  org,
34
34
  name: "social",
35
- tagline: "Let your agent manage LinkedIn & X for you",
35
+ tagline: "Let your agent run LinkedIn & X for you",
36
36
  description: "A CLI for LinkedIn and X: outreach, posting, and audience insights from any shell."
37
37
  };
38
38
  const brandURLs = {
@@ -21059,7 +21059,7 @@ const createInstance = (defaults) => {
21059
21059
  const ky = createInstance();
21060
21060
  //#endregion
21061
21061
  //#region package.json
21062
- var version$1 = "0.3.3";
21062
+ var version$1 = "0.3.5";
21063
21063
  //#endregion
21064
21064
  //#region src/lib/env.ts
21065
21065
  const URLWithTrailingSlash = url().transform(ensureTrailingSlash);
@@ -27425,6 +27425,15 @@ const unknownOutputContract = (message = "Output schema is not declared.") => ({
27425
27425
  });
27426
27426
  new TextEncoder();
27427
27427
  //#endregion
27428
+ //#region ../../packages/lib/src/email-hint.ts
27429
+ const base64URLFromText = (value) => {
27430
+ const bytes = new TextEncoder().encode(value);
27431
+ let binary = "";
27432
+ for (const byte of bytes) binary += String.fromCharCode(byte);
27433
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
27434
+ };
27435
+ const emailHintFromEmail = (email) => base64URLFromText(email);
27436
+ //#endregion
27428
27437
  //#region ../../packages/lib/src/phone.ts
27429
27438
  const phoneFormattingRegex = /[\s().-]/g;
27430
27439
  const PhoneNumber = string().trim().transform((value) => value.replace(phoneFormattingRegex, "")).pipe(string().regex(/^\+[1-9]\d{1,14}$/, { message: "Enter a phone number with +country code." }));
@@ -27435,7 +27444,6 @@ const DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
27435
27444
  const MAGIC_LINK_PATH = "/api/auth/sign-in/magic-link";
27436
27445
  const TERMS_URL = new URL(siteConfig.links.terms, siteConfig.publicWebURL).toString();
27437
27446
  const LOGIN_SUCCESS_MESSAGE = "Logged in! 🎉";
27438
- const NEXT_COMMANDS_MESSAGE = "Now run `social account connect linkedin` or `social account connect x`.";
27439
27447
  const formatUserCode = (code) => `${code.slice(0, 4)}-${code.slice(4)}`;
27440
27448
  const isRecord$2 = (value) => typeof value === "object" && value !== null;
27441
27449
  const isAuthErrorResponse = (data) => isRecord$2(data) && (typeof data.error === "string" || typeof data.error_description === "string" || typeof data.message === "string");
@@ -27492,6 +27500,7 @@ const deviceCallbackURLFor = (deviceCode, email) => {
27492
27500
  parsed = new URL("/device", "https://social.local");
27493
27501
  }
27494
27502
  parsed.searchParams.set("user_code", formatUserCode(deviceCode.user_code));
27503
+ parsed.searchParams.set("email_hint", emailHintFromEmail(email));
27495
27504
  parsed.searchParams.set("email", email);
27496
27505
  return `${parsed.pathname}${parsed.search}`;
27497
27506
  };
@@ -27553,7 +27562,6 @@ const waitForDeviceToken = async (ctx, deviceCode, phoneNumber) => {
27553
27562
  await updatePhoneNumber(ctx, phoneNumber);
27554
27563
  clearTick();
27555
27564
  spinner.stop(LOGIN_SUCCESS_MESSAGE);
27556
- ctx.ui.info(NEXT_COMMANDS_MESSAGE);
27557
27565
  return token;
27558
27566
  } catch (error) {
27559
27567
  clearTick();
@@ -27586,7 +27594,7 @@ const signInPhase = async (ctx) => {
27586
27594
  }
27587
27595
  };
27588
27596
  };
27589
- const onboardingLegalNotice = (termsURL = TERMS_URL) => `By signing up and connecting accounts, you accept the terms and conditions and use this software at your own risk.\nRead the terms: ${termsURL}`;
27597
+ const onboardingLegalNotice = (termsURL = TERMS_URL) => `By signing up and connecting accounts, you accept the terms\nand conditions and use this software at your own risk.\nRead the terms: ${termsURL}`;
27590
27598
  const resolveEmail = async (ctx) => await ctx.ui.text({
27591
27599
  message: "Email",
27592
27600
  placeholder: "you@example.com",
@@ -27700,6 +27708,7 @@ const openBrowser = async (url) => {
27700
27708
  ]);
27701
27709
  return await runCommand(["xdg-open", url]);
27702
27710
  };
27711
+ const SEAT_PRICING_MESSAGE = `social costs $60/month/account + usage. Full details at ${createAbsoluteURL(env.SOCIAL_WEB_URL, siteConfig.links.pricing)}. Continue?`;
27703
27712
  const SEAT_CHECKOUT_RETURN_PATH = "/setup/billing/done";
27704
27713
  const SEAT_POLL_INTERVAL_MS = 2e3;
27705
27714
  const SEAT_POLL_TIMEOUT_MS = 1800 * 1e3;
@@ -27725,7 +27734,7 @@ const runSeatPhase = async (ctx, deps = {}) => {
27725
27734
  data: status
27726
27735
  };
27727
27736
  if (!await ctx.ui.confirm({
27728
- message: "social costs $60/month/account + usage. Continue?",
27737
+ message: SEAT_PRICING_MESSAGE,
27729
27738
  initialValue: true
27730
27739
  })) return {
27731
27740
  status: "needs_input",
@@ -27823,6 +27832,8 @@ const assertInteractiveTerminal = () => {
27823
27832
  if (!(process.stdin.isTTY && process.stdout.isTTY)) throw new UsageError("social account login requires an interactive terminal.");
27824
27833
  };
27825
27834
  const nextCommands = ["social account connect linkedin", "social account connect x"];
27835
+ const nextCommandsMessage = `Now run \`${nextCommands[0]}\` or \`${nextCommands[1]}\`.`;
27836
+ const billingReady = (billing) => billing.seats.available > 0;
27826
27837
  const alreadyLoggedInOutput = ({ credential, session }) => ({
27827
27838
  status: "already_logged_in",
27828
27839
  message: "Already logged in.",
@@ -27838,16 +27849,19 @@ const offlineOutput = () => ({
27838
27849
  code: "offline",
27839
27850
  message: "Couldn't reach the server. Check your network connection and try again."
27840
27851
  });
27841
- const runLogin = async (ctx) => {
27852
+ const runLogin = async (ctx, loginPhases = phases, deps = {}) => {
27853
+ const bearerIsValid = deps.isBearerValid ?? isBearerValid;
27854
+ const readLocalCredentials = deps.readCredentials ?? readCredentials;
27842
27855
  analytics.capture("login_started");
27843
- for (const phase of phases) {
27844
- if (phase.run !== accessPhase && phase.run !== signInPhase && !isBearerValid(await readCredentials())) throw new AuthError("Session expired before login finished. Run `social account login` again.");
27856
+ for (const phase of loginPhases) {
27857
+ if (phase.name !== "access" && phase.name !== "sign-in" && deps.authenticated !== true && !bearerIsValid(await readLocalCredentials())) throw new AuthError("Session expired before login finished. Run `social account login` again.");
27845
27858
  const result = await phase.run(ctx);
27846
27859
  if (result.status !== "done") return result;
27847
27860
  analytics.capture("login_phase_completed", { phase: phase.name });
27848
27861
  }
27849
27862
  const session = await ctx.client.cli.session.me();
27850
27863
  const billing = await ctx.client.billing.status();
27864
+ ctx.ui.info(nextCommandsMessage);
27851
27865
  return {
27852
27866
  status: "ok",
27853
27867
  user: session.user,
@@ -27895,7 +27909,18 @@ const loginCommand = defineCommand({
27895
27909
  try {
27896
27910
  const client = createORPCAPIClient();
27897
27911
  const existing = await readExistingAuthSession(client);
27898
- if (existing.kind === "existing") output = alreadyLoggedInOutput(existing.session);
27912
+ if (existing.kind === "existing") if (billingReady(await client.billing.status())) output = alreadyLoggedInOutput(existing.session);
27913
+ else {
27914
+ assertInteractiveTerminal();
27915
+ output = await runLogin({
27916
+ ui: createUI(process.stdout.isTTY === true),
27917
+ client,
27918
+ args: loginArgs
27919
+ }, [{
27920
+ name: "seat",
27921
+ run: seatPhase
27922
+ }], { authenticated: true });
27923
+ }
27899
27924
  else if (existing.kind === "offline") output = offlineOutput();
27900
27925
  else {
27901
27926
  assertInteractiveTerminal();
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "access": "public"
12
12
  },
13
13
  "private": false,
14
- "version": "0.3.3",
14
+ "version": "0.3.5",
15
15
  "type": "module",
16
16
  "engines": {
17
17
  "node": ">=22.5.0"