Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@linkedapi/linkedin-cli",
"version": "1.2.21",
"version": "1.2.22",
"description": "AI-agent-friendly CLI for controlling LinkedIn accounts and retrieving real-time data.",
"author": "Linked API",
"license": "MIT",
Expand Down Expand Up @@ -43,7 +43,7 @@
"ai-agent"
],
"dependencies": {
"@linkedapi/node": "^2.3.17",
"@linkedapi/node": "^2.3.19",
"@oclif/core": "^4.2.10"
},
"devDependencies": {
Expand Down Expand Up @@ -74,7 +74,7 @@
"description": "Manage connected LinkedIn accounts"
},
"person": {
"description": "Fetch and search LinkedIn profiles"
"description": "Fetch and search LinkedIn profiles and endorse skills"
},
"company": {
"description": "Fetch and search LinkedIn companies"
Expand Down
10 changes: 5 additions & 5 deletions src/commands/admin/limits/delete.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Args, Flags } from '@oclif/core';
import type { TLimitCategory, TLimitPeriod } from '@linkedapi/node';

import { AdminBaseCommand } from '@admin-base-command';
import { formatAdminVoidOutput } from '@core/output/admin-formatter';
import type { TLimitCategory, TLimitPeriod } from '@linkedapi/node';
import { Args, Flags } from '@oclif/core';

export default class AdminLimitsDelete extends AdminBaseCommand {
static override description = 'Delete specific limits (falls back to defaults)';
static override description =
'Delete a specific limit. The removed category/period has no limit until set again. Use linkedin admin limits reset <accountId> to restore all default limits for the account.';

static override args = {
accountId: Args.string({
Expand All @@ -17,7 +17,7 @@ export default class AdminLimitsDelete extends AdminBaseCommand {
static override flags = {
...AdminBaseCommand.baseFlags,
category: Flags.string({
description: 'Limit category',
description: 'Limit category (e.g. stMessages, stEndorsements)',
required: true,
}),
period: Flags.string({
Expand Down
7 changes: 3 additions & 4 deletions src/commands/admin/limits/set.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Args, Flags } from '@oclif/core';
import type { TLimitCategory, TLimitPeriod } from '@linkedapi/node';

import { AdminBaseCommand } from '@admin-base-command';
import { formatAdminVoidOutput } from '@core/output/admin-formatter';
import type { TLimitCategory, TLimitPeriod } from '@linkedapi/node';
import { Args, Flags } from '@oclif/core';

export default class AdminLimitsSet extends AdminBaseCommand {
static override description = 'Set limits for an account';
Expand All @@ -17,7 +16,7 @@ export default class AdminLimitsSet extends AdminBaseCommand {
static override flags = {
...AdminBaseCommand.baseFlags,
category: Flags.string({
description: 'Limit category',
description: 'Limit category (e.g. stMessages, stEndorsements)',
required: true,
}),
period: Flags.string({
Expand Down
102 changes: 102 additions & 0 deletions src/commands/person/endorse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { BaseCommand } from '@base-command';
import { EXIT_CODE } from '@core/errors/exit-codes';
import { formatOutput, formatVoidOutput } from '@core/output/formatter';
import { runVoidWorkflow, runWorkflow } from '@core/workflow/workflow-runner';
import { Args, Flags } from '@oclif/core';
import { isStdoutTty } from '@utils/tty';

export default class PersonEndorse extends BaseCommand {
static override description =
"Endorse skills of a 1st-degree connection, using names in the profile owner's language. Already-endorsed skills count as success. Each success counts toward the stEndorsements limit.";

static override args = {
url: Args.string({
description: 'LinkedIn profile URL of your 1st-degree connection',
required: true,
}),
};

static override flags = {
...BaseCommand.baseFlags,
skill: Flags.string({
description: 'Exact skill name (case and extra spaces are ignored)',
}),
pattern: Flags.string({
description:
'RE2 pattern, case-insensitive, matching anywhere; use ^…$ for a whole name (repeatable, 1-20 patterns of 1-200 characters)',
multiple: true,
multipleNonGreedy: true,
}),
all: Flags.boolean({
description: 'Endorse all skills',
default: false,
}),
};

static override examples = [
'<%= config.bin %> person endorse https://www.linkedin.com/in/john-doe --skill "Project Management"',
'<%= config.bin %> person endorse https://www.linkedin.com/in/john-doe --pattern manage --pattern "^scrum$"',
'<%= config.bin %> person endorse https://www.linkedin.com/in/john-doe --all',
];

public async run(): Promise<void> {
const { args, flags } = await this.parse(PersonEndorse);
const selectionCount = [
flags.skill !== undefined,
flags.pattern !== undefined,
flags.all,
].filter(Boolean).length;

if (selectionCount !== 1) {
this.error('Provide exactly one of --skill, --pattern or --all.', {
exit: EXIT_CODE.VALIDATION,
});
}

const client = await this.buildAuthenticatedClient();

try {
if (flags.skill !== undefined) {
const result = await runVoidWorkflow(
client.endorsePersonSkill,
{ personUrl: args.url, skillName: flags.skill },
{ isQuiet: flags.quiet },
);

formatVoidOutput({
errors: result.errors,
isJson: flags.json,
isQuiet: flags.quiet,
successMessage: `Endorsed: ${flags.skill}`,
});
return;
}

const result = await runWorkflow(
client.endorsePersonSkills,
{
personUrl: args.url,
...(flags.pattern !== undefined ? { skillNamePatterns: flags.pattern } : {}),
},
{ isQuiet: flags.quiet },
);

const data =
flags.json || !isStdoutTty() || flags.fields !== undefined
? result.data
: result.data?.map(({ skillName, success, error }) =>
success ? `Endorsed: ${skillName}` : `Not endorsed: ${skillName} - ${error?.type}`,
);

formatOutput({
data,
errors: result.errors,
isJson: flags.json,
fields: flags.fields,
isQuiet: flags.quiet,
});
} catch (error) {
this.handleError(error);
}
}
}
Loading