@usesocial/cli 0.3.2 → 0.3.4

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.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [#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.
8
+
9
+ ## 0.3.3
10
+
11
+ ### Patch Changes
12
+
13
+ - [#14](https://github.com/usesocial/monorepo/pull/14) [`22ba757`](https://github.com/usesocial/monorepo/commit/22ba757efd459a0cb3009c91ba833687e2ce2796) Thanks [@CyrusNuevoDia](https://github.com/CyrusNuevoDia)! - Ask for phone number during `social account login`, save it after device approval, and include it in account identity output.
14
+
3
15
  ## 0.3.2
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -212,12 +212,12 @@ jq -r '.items[].id' /tmp/recent-posts.json \
212
212
  default; clear Write in the prompt to grant read-only access.
213
213
  2. Asks for your email address.
214
214
  3. Sends a magic link to that email with the device approval screen already
215
- attached.
215
+ attached while asking for your phone number.
216
216
  4. Waits until you click the magic link and approve the CLI session in the
217
- browser.
218
- 5. Confirms billing checkout in the terminal when a seat is needed.
219
- 6. Stores the returned token in your OS keyring, falling back to
220
- `~/.social/credentials.json` (mode `0600`) when no keyring is available.
217
+ browser, then stores the returned token in your OS keyring.
218
+ 5. Stores the phone number, then confirms billing checkout in the terminal when
219
+ a seat is needed. If no keyring is available, credentials fall back to
220
+ `~/.social/credentials.json` (mode `0600`).
221
221
  Non-production `SOCIAL_API_URL` values use an API-specific credential
222
222
  namespace so staging tests do not overwrite the production session.
223
223
 
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 = {
@@ -9583,6 +9583,7 @@ const user = pgTable("user", {
9583
9583
  emailVerified: boolean("emailVerified").notNull().default(false),
9584
9584
  name: text$1("name").notNull(),
9585
9585
  image: text$1("image"),
9586
+ phoneNumber: text$1("phone_number"),
9586
9587
  createdAt: timestamp("createdAt", { mode: "date" }).notNull().defaultNow(),
9587
9588
  updatedAt: timestamp("updatedAt", { mode: "date" }).notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date())
9588
9589
  });
@@ -21058,7 +21059,7 @@ const createInstance = (defaults) => {
21058
21059
  const ky = createInstance();
21059
21060
  //#endregion
21060
21061
  //#region package.json
21061
- var version$1 = "0.3.2";
21062
+ var version$1 = "0.3.4";
21062
21063
  //#endregion
21063
21064
  //#region src/lib/env.ts
21064
21065
  const URLWithTrailingSlash = url().transform(ensureTrailingSlash);
@@ -27411,6 +27412,32 @@ const accessPhase = async (ctx) => {
27411
27412
  };
27412
27413
  };
27413
27414
  //#endregion
27415
+ //#region ../../packages/lib/src/cli-schema-contract.ts
27416
+ const isZodSchema = (value) => Boolean(value && typeof value === "object" && "safeParse" in value && typeof value.safeParse === "function");
27417
+ const isZodUnknownSchema = (value) => value instanceof ZodUnknown;
27418
+ const zodToJSONSchema = (schema, options) => toJSONSchema(schema, { io: options?.io ?? "output" });
27419
+ const unknownOutputContract = (message = "Output schema is not declared.") => ({
27420
+ outputSchema: { unknown: true },
27421
+ hazards: [{
27422
+ code: "output_schema_unknown",
27423
+ message
27424
+ }]
27425
+ });
27426
+ new TextEncoder();
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
27437
+ //#region ../../packages/lib/src/phone.ts
27438
+ const phoneFormattingRegex = /[\s().-]/g;
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." }));
27440
+ //#endregion
27414
27441
  //#region src/login/phases/0.sign-in.ts
27415
27442
  const CLIENT_ID = "social-cli";
27416
27443
  const DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
@@ -27474,6 +27501,7 @@ const deviceCallbackURLFor = (deviceCode, email) => {
27474
27501
  parsed = new URL("/device", "https://social.local");
27475
27502
  }
27476
27503
  parsed.searchParams.set("user_code", formatUserCode(deviceCode.user_code));
27504
+ parsed.searchParams.set("email_hint", emailHintFromEmail(email));
27477
27505
  parsed.searchParams.set("email", email);
27478
27506
  return `${parsed.pathname}${parsed.search}`;
27479
27507
  };
@@ -27495,7 +27523,30 @@ const sendMagicLinkSignIn = async (email, callbackURL, deps = {}) => {
27495
27523
  throw new Error(authErrorMessage("Could not send sign-in email", response, error));
27496
27524
  }
27497
27525
  };
27498
- const waitForDeviceToken = async (ctx, deviceCode) => {
27526
+ const startMagicLinkSignIn = (email, callbackURL) => sendMagicLinkSignIn(email, callbackURL).then(() => ({ status: "fulfilled" }), (error) => ({
27527
+ status: "rejected",
27528
+ error
27529
+ }));
27530
+ const waitForMagicLinkEmail = async (ctx, emailPromise) => {
27531
+ const result = await emailPromise;
27532
+ if (result.status === "rejected") {
27533
+ ctx.ui.error("Could not send sign-in email");
27534
+ throw result.error;
27535
+ }
27536
+ };
27537
+ const isMissingPhoneEndpointError = (error) => isRecord$2(error) && "status" in error && error.status === 404 && "message" in error && typeof error.message === "string" && error.message.toLowerCase().includes("not found");
27538
+ const updatePhoneNumber = async (ctx, phoneNumber) => {
27539
+ try {
27540
+ await ctx.client.cli.user.updatePhone({ phoneNumber });
27541
+ } catch (error) {
27542
+ if (isMissingPhoneEndpointError(error)) {
27543
+ ctx.ui.warn("Phone number could not be saved yet; login is complete.");
27544
+ return;
27545
+ }
27546
+ throw error;
27547
+ }
27548
+ };
27549
+ const waitForDeviceToken = async (ctx, deviceCode, phoneNumber) => {
27499
27550
  const spinner = ctx.ui.spinner();
27500
27551
  const message = "Waiting for magic link approval";
27501
27552
  spinner.start(message);
@@ -27509,6 +27560,7 @@ const waitForDeviceToken = async (ctx, deviceCode) => {
27509
27560
  expiresAt: tokenExpiresAt,
27510
27561
  scope: token.scope
27511
27562
  });
27563
+ await updatePhoneNumber(ctx, phoneNumber);
27512
27564
  clearTick();
27513
27565
  spinner.stop(LOGIN_SUCCESS_MESSAGE);
27514
27566
  ctx.ui.info(NEXT_COMMANDS_MESSAGE);
@@ -27530,23 +27582,37 @@ const signInPhase = async (ctx) => {
27530
27582
  const expiresAt = new Date(Date.now() + deviceCode.expires_in * 1e3).toISOString();
27531
27583
  const callbackURL = deviceCallbackURLFor(deviceCode, email);
27532
27584
  const userCode = formatUserCode(deviceCode.user_code);
27533
- await ctx.ui.spin("Sending sign-in email", () => sendMagicLinkSignIn(email, callbackURL), "Magic link sent");
27585
+ const emailPromise = startMagicLinkSignIn(email, callbackURL);
27586
+ const phoneNumber = await resolvePhoneNumber(ctx);
27587
+ await waitForMagicLinkEmail(ctx, emailPromise);
27534
27588
  ctx.ui.note(`Code: ${userCode}\nEmail: ${email}\n\nClick the magic link in your inbox to approve this CLI session.\n\n${onboardingLegalNotice()}`, "Check your email");
27535
27589
  return {
27536
27590
  status: "done",
27537
27591
  data: {
27538
27592
  email,
27539
27593
  expiresAt,
27540
- scope: (await waitForDeviceToken(ctx, deviceCode)).scope
27594
+ phoneNumber,
27595
+ scope: (await waitForDeviceToken(ctx, deviceCode, phoneNumber)).scope
27541
27596
  }
27542
27597
  };
27543
27598
  };
27544
- 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}`;
27599
+ 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}`;
27545
27600
  const resolveEmail = async (ctx) => await ctx.ui.text({
27546
27601
  message: "Email",
27547
27602
  placeholder: "you@example.com",
27548
27603
  validate: (value) => value?.includes("@") ? void 0 : "Enter an email address."
27549
27604
  });
27605
+ const resolvePhoneNumber = async (ctx) => {
27606
+ const phoneNumber = await ctx.ui.text({
27607
+ message: "Phone number",
27608
+ placeholder: "+1 555 123 4567",
27609
+ validate: (value) => {
27610
+ const result = PhoneNumber.safeParse(value);
27611
+ return result.success ? void 0 : result.error.issues[0]?.message;
27612
+ }
27613
+ });
27614
+ return PhoneNumber.parse(phoneNumber);
27615
+ };
27550
27616
  //#endregion
27551
27617
  //#region src/login/phases/1.scope.ts
27552
27618
  const DEFAULT_SCOPE_ALIAS = "read,write";
@@ -27644,6 +27710,7 @@ const openBrowser = async (url) => {
27644
27710
  ]);
27645
27711
  return await runCommand(["xdg-open", url]);
27646
27712
  };
27713
+ const SEAT_PRICING_MESSAGE = `social costs $60/month/account + usage. Full details at ${createAbsoluteURL(env.SOCIAL_WEB_URL, siteConfig.links.pricing)}. Continue?`;
27647
27714
  const SEAT_CHECKOUT_RETURN_PATH = "/setup/billing/done";
27648
27715
  const SEAT_POLL_INTERVAL_MS = 2e3;
27649
27716
  const SEAT_POLL_TIMEOUT_MS = 1800 * 1e3;
@@ -27669,7 +27736,7 @@ const runSeatPhase = async (ctx, deps = {}) => {
27669
27736
  data: status
27670
27737
  };
27671
27738
  if (!await ctx.ui.confirm({
27672
- message: "social costs $60/month/account + usage. Continue?",
27739
+ message: SEAT_PRICING_MESSAGE,
27673
27740
  initialValue: true
27674
27741
  })) return {
27675
27742
  status: "needs_input",
@@ -27691,7 +27758,8 @@ const ScopeAlias = _enum(["read", "read,write"]);
27691
27758
  const LoginUserOutput = object({
27692
27759
  id: string(),
27693
27760
  email: string().nullable(),
27694
- name: string().nullable()
27761
+ name: string().nullable(),
27762
+ phoneNumber: string().nullable()
27695
27763
  });
27696
27764
  const LoginSeatsOutput = object({
27697
27765
  total: number().int().nonnegative(),
@@ -28111,7 +28179,8 @@ const AccountSessionOutput = object({
28111
28179
  user: object({
28112
28180
  id: string(),
28113
28181
  email: string().nullable(),
28114
- name: string().nullable()
28182
+ name: string().nullable(),
28183
+ phoneNumber: string().nullable()
28115
28184
  }),
28116
28185
  scope: _enum(["read", "read,write"]),
28117
28186
  capabilities: array(string())
@@ -28591,18 +28660,6 @@ const createCLIDeps = () => {
28591
28660
  };
28592
28661
  };
28593
28662
  //#endregion
28594
- //#region ../../packages/lib/src/cli-schema-contract.ts
28595
- const isZodSchema = (value) => Boolean(value && typeof value === "object" && "safeParse" in value && typeof value.safeParse === "function");
28596
- const isZodUnknownSchema = (value) => value instanceof ZodUnknown;
28597
- const zodToJSONSchema = (schema, options) => toJSONSchema(schema, { io: options?.io ?? "output" });
28598
- const unknownOutputContract = (message = "Output schema is not declared.") => ({
28599
- outputSchema: { unknown: true },
28600
- hazards: [{
28601
- code: "output_schema_unknown",
28602
- message
28603
- }]
28604
- });
28605
- //#endregion
28606
28663
  //#region src/schema/index.ts
28607
28664
  const HTTPMethod = _enum([
28608
28665
  "DELETE",
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "access": "public"
12
12
  },
13
13
  "private": false,
14
- "version": "0.3.2",
14
+ "version": "0.3.4",
15
15
  "type": "module",
16
16
  "engines": {
17
17
  "node": ">=22.5.0"