@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 +12 -0
- package/README.md +5 -5
- package/dist/index.mjs +78 -21
- package/package.json +1 -1
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.
|
|
219
|
-
|
|
220
|
-
`~/.social/credentials.json` (mode `0600`)
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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",
|