From e16704d96e4e65d9021e7efcb9017e544560476a Mon Sep 17 00:00:00 2001 From: James Armstead Date: Fri, 27 Feb 2026 00:03:10 -0600 Subject: [PATCH] feat: add follower and connection count to shake me Fetches /identity/profiles/{handle}/networkinfo after /me to get follower and connection counts, then displays them in the output. Connections show "500+" when count >= 500 (matching LinkedIn's display). Both counts are also included in --json output. Closes #1 --- src/commands/me.ts | 48 +++++++++++++++++++++++++++++++++------------- src/lib/client.ts | 4 ++++ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/commands/me.ts b/src/commands/me.ts index cdba576..dc90dcf 100644 --- a/src/commands/me.ts +++ b/src/commands/me.ts @@ -14,16 +14,34 @@ meCommand const client = await LinkedInAPIClient.create(); const response = await client.getMe(); + // LinkedIn normalized format: profile info in `included` array + const resp = response as any; + const miniProfile = resp.included?.find((item: any) => + item.$type === 'com.linkedin.voyager.identity.shared.MiniProfile' || + item.entityUrn?.includes('fs_miniProfile') + ) || resp.data?.miniProfile || resp.miniProfile; + + const publicId = miniProfile?.publicIdentifier; + + // Fetch network info (followers + connections) if we have a public ID + let followersCount: number | null = null; + let connectionsCount: number | null = null; + if (publicId) { + try { + const networkResp = await client.getNetworkInfo(publicId) as any; + followersCount = networkResp.data?.followersCount ?? networkResp.followersCount ?? null; + connectionsCount = networkResp.data?.connectionsCount ?? networkResp.connectionsCount ?? null; + } catch { + // Non-fatal: network info is best-effort + } + } + if (json) { - console.log(JSON.stringify(response, null, 2)); + const out: any = { ...resp }; + if (followersCount !== null) out.followersCount = followersCount; + if (connectionsCount !== null) out.connectionsCount = connectionsCount; + console.log(JSON.stringify(out, null, 2)); } else { - // LinkedIn normalized format: profile info in `included` array - const resp = response as any; - const miniProfile = resp.included?.find((item: any) => - item.$type === 'com.linkedin.voyager.identity.shared.MiniProfile' || - item.entityUrn?.includes('fs_miniProfile') - ) || resp.data?.miniProfile || resp.miniProfile; - if (!miniProfile) { console.log(chalk.yellow('No profile data found')); return; @@ -32,16 +50,20 @@ meCommand const firstName = miniProfile.firstName || 'Unknown'; const lastName = miniProfile.lastName || ''; const occupation = miniProfile.occupation || resp.data?.occupation || 'No headline'; - const publicId = miniProfile.publicIdentifier || 'unknown'; console.log(chalk.blue.bold(`${firstName} ${lastName}`)); console.log(chalk.gray(occupation)); + + if (followersCount !== null) { + console.log(`Followers: ${chalk.white(followersCount.toLocaleString())}`); + } + if (connectionsCount !== null) { + const connectionsDisplay = connectionsCount >= 500 ? '500+' : String(connectionsCount); + console.log(`Connections: ${chalk.white(connectionsDisplay)}`); + } + console.log(); console.log(`Profile: ${chalk.cyan('https://linkedin.com/in/' + publicId)}`); - - if (resp.data?.plainId) { - console.log(`Member ID: ${chalk.gray(resp.data.plainId)}`); - } const twitter = resp.data?.publicContactInfo?.twitterHandles?.[0]?.name; if (twitter) { diff --git a/src/lib/client.ts b/src/lib/client.ts index 07e4007..b67d3b1 100644 --- a/src/lib/client.ts +++ b/src/lib/client.ts @@ -134,6 +134,10 @@ export class LinkedInAPIClient { return this.request('/me'); } + async getNetworkInfo(handle: string): Promise { + return this.request(`/identity/profiles/${handle}/networkinfo`); + } + async getProfile(handle: string): Promise { return this.request('/identity/dash/profiles', { params: {