@sellable/install 0.1.121 → 0.1.122

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/README.md CHANGED
@@ -6,6 +6,16 @@ Installs Sellable MCP for Claude Code and Codex.
6
6
  npx -y @sellable/install@latest --host all
7
7
  ```
8
8
 
9
+ Paste that command in your terminal, not inside the Codex chat. Mac/Linux
10
+ users can use Terminal. Windows users can use PowerShell or Windows Terminal;
11
+ if PowerShell blocks `npx`, run:
12
+
13
+ ```powershell
14
+ npx.cmd -y @sellable/install@latest --host all
15
+ ```
16
+
17
+ After install, restart Codex Desktop so the Sellable skill appears.
18
+
9
19
  After install, `sellable create` is a terminal helper that prints the correct
10
20
  agent command for launching a campaign:
11
21
 
@@ -60,9 +60,14 @@ currentStep: "signal-discovery" })` before sampling so the watched Signal
60
60
  sampled/projected headline-fit rate clears the 10% planning floor. Treat the
61
61
  10% floor as a reject threshold, not as the scrape-count denominator when the
62
62
  actual sample rate is higher.
63
- 8. Select/promote enough right-content posts to plausibly hit the target. If the
64
- warm Signals pool is useful but too small, return the expected warm range and
65
- recommend Sales Nav/Prospeo for scale instead of padding with noisy posts.
63
+ 8. Select/promote enough right-content posts to plausibly hit the target. After
64
+ the sample math is known, treat the promoted sample set and final scrape set
65
+ as separate: recommend the smallest right-content post subset whose
66
+ scrapable/reachable engagers clears the required engager count, with a modest
67
+ buffer when needed. If one 1,200+ engager post clears a ~1,000-engager target,
68
+ recommend scraping that one post, not all 3 sample posts. If the warm Signals
69
+ pool is useful but too small, return the expected warm range and recommend
70
+ Sales Nav/Prospeo for scale instead of padding with noisy posts.
66
71
  9. Return false positives and dead ends explicitly.
67
72
 
68
73
  Return a concise structured result with:
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process";
3
3
  import {
4
+ cpSync,
4
5
  existsSync,
5
6
  mkdirSync,
6
7
  readdirSync,
@@ -30,16 +31,6 @@ function getInstallVersion() {
30
31
  }
31
32
  }
32
33
 
33
- function getMcpVersion() {
34
- try {
35
- const r = spawnSync("npm", ["view", "@sellable/mcp", "version"], {
36
- encoding: "utf8",
37
- timeout: 5000,
38
- });
39
- if (r.status === 0 && r.stdout) return r.stdout.trim();
40
- } catch {}
41
- return "latest";
42
- }
43
34
  const CODEX_PLUGIN_VERSION = "0.1.33";
44
35
  const CODEX_PLUGIN_COMPAT_VERSIONS = [
45
36
  "0.1.8",
@@ -282,13 +273,59 @@ function run(command, args, opts = {}) {
282
273
  return result;
283
274
  }
284
275
 
285
- function commandExists(command) {
286
- const result = spawnSync("sh", ["-lc", `command -v ${command}`], {
287
- encoding: "utf8",
288
- });
276
+ function isWindowsPlatform(platform = process.platform) {
277
+ return platform === "win32";
278
+ }
279
+
280
+ function packageRunnerCommand(platform = process.platform) {
281
+ return isWindowsPlatform(platform) ? "npx.cmd" : "npx";
282
+ }
283
+
284
+ function packageManagerCommand(platform = process.platform) {
285
+ return isWindowsPlatform(platform) ? "npm.cmd" : "npm";
286
+ }
287
+
288
+ function commandExists(command, platform = process.platform) {
289
+ const result = isWindowsPlatform(platform)
290
+ ? spawnSync("where.exe", [command], {
291
+ encoding: "utf8",
292
+ })
293
+ : spawnSync("sh", ["-lc", `command -v ${command}`], {
294
+ encoding: "utf8",
295
+ });
289
296
  return result.status === 0;
290
297
  }
291
298
 
299
+ function commandExistsDetails(command, platform = process.platform) {
300
+ const result = isWindowsPlatform(platform)
301
+ ? spawnSync("where.exe", [command], {
302
+ encoding: "utf8",
303
+ })
304
+ : spawnSync("sh", ["-lc", `command -v ${command}`], {
305
+ encoding: "utf8",
306
+ });
307
+ return {
308
+ exists: result.status === 0,
309
+ stdout: (result.stdout || "").trim(),
310
+ platform,
311
+ };
312
+ }
313
+
314
+ function getMcpVersion() {
315
+ try {
316
+ const r = spawnSync(
317
+ packageManagerCommand(),
318
+ ["view", "@sellable/mcp", "version"],
319
+ {
320
+ encoding: "utf8",
321
+ timeout: 5000,
322
+ }
323
+ );
324
+ if (r.status === 0 && r.stdout) return r.stdout.trim();
325
+ } catch {}
326
+ return "latest";
327
+ }
328
+
292
329
  function authPath() {
293
330
  return join(homedir(), ".sellable", "config.json");
294
331
  }
@@ -420,7 +457,26 @@ function ensureSymlink(target, linkPath, opts) {
420
457
  // ignore
421
458
  }
422
459
  const rel = relative(dirname(linkPath), target);
423
- symlinkSync(rel, linkPath, "dir");
460
+ try {
461
+ symlinkSync(rel, linkPath, "dir");
462
+ } catch (err) {
463
+ const code = err && typeof err === "object" ? err.code : "";
464
+ const message = err instanceof Error ? err.message : String(err);
465
+ const isDenied =
466
+ code === "EPERM" ||
467
+ code === "EACCES" ||
468
+ /privilege|permission|operation not permitted/i.test(message);
469
+
470
+ if (!isWindowsPlatform() || !isDenied) {
471
+ throw err;
472
+ }
473
+
474
+ logVerbose(
475
+ `${C.grey}+ symlink denied on Windows; copying compatibility cache ${linkPath}${C.reset}`
476
+ );
477
+ rmSync(linkPath, { recursive: true, force: true });
478
+ cpSync(target, linkPath, { recursive: true, force: true });
479
+ }
424
480
  }
425
481
 
426
482
  function codexPluginManifest(opts) {
@@ -492,7 +548,7 @@ function codexPluginMcp(opts) {
492
548
  mcpServers: {
493
549
  sellable: {
494
550
  type: "stdio",
495
- command: "npx",
551
+ command: packageRunnerCommand(),
496
552
  args: ["-y", opts.mcpPackage],
497
553
  env: {
498
554
  SELLABLE_WATCH_MODE_DRIVER: "codex",
@@ -608,8 +664,8 @@ instruction loading, file lookup, plugin cache versions, missing linked files,
608
664
  or tool discovery. Start in product language:
609
665
 
610
666
  \`\`\`text
611
- I’ll help you launch this as a Sellable campaign. First I’ll resolve the
612
- client/company this campaign is for, then I’ll turn that into a campaign brief
667
+ I’ll help you launch this as a Sellable campaign. First I’ll research the
668
+ person/company this campaign is for, then I’ll turn that into a campaign brief
613
669
  before anything is created.
614
670
  \`\`\`
615
671
 
@@ -679,10 +735,12 @@ precision, and referral paths, but it does not provide hiring-by-role filters;
679
735
  say that distinction plainly in the source-plan gate.
680
736
 
681
737
  After scouting, ask for a second approval on the concrete source action. For
682
- LinkedIn engagement (\`signal-discovery\` internally), name how many selected
738
+ LinkedIn engagement (\`signal-discovery\` internally), name how many recommended
683
739
  posts will be scraped, the target engager/source-candidate volume, and the
684
- internal campaign-table execution-slice size. For Sales Nav or Prospeo, name the
685
- specific approved import lane and source lead count.
740
+ internal campaign-table execution-slice size. N must be the smallest
741
+ right-content post set that clears the source target, not the default 3
742
+ promoted sample posts. For Sales Nav or Prospeo, name the specific approved
743
+ import lane and source lead count.
686
744
  Do not call \`import_leads\` or \`confirm_lead_list\` until this second approval is
687
745
  granted.
688
746
 
@@ -841,10 +899,10 @@ customer-facing progress copy.
841
899
 
842
900
  Do not treat the active Sellable workspace as the campaign subject. The
843
901
  workspace only tells you where the campaign will be saved. Before buyer, CTA,
844
- proof, or source questions, identify the campaign identity: the person/profile
845
- or company this campaign is for, plus enough company/product context to build
846
- the brief. This is only the client-prospect/bootstrap identity for
847
- \`clientProspectId\` or \`senderLinkedinUrl\`; it is not a connected-sender check.
902
+ proof, or source questions, identify the person/profile or company this
903
+ campaign is for, plus enough current company/product context to build the
904
+ brief. This client/company lookup feeds \`clientProspectId\` or
905
+ \`senderLinkedinUrl\`; it is not a connected-sender check.
848
906
 
849
907
  Do not call \`mcp__sellable__list_senders\`, \`mcp__sellable__get_sender\`, or
850
908
  surface connected/missing sender state during setup, brief, source, filter, or
@@ -868,19 +926,20 @@ first:
868
926
  only if a URL/domain is also available.
869
927
 
870
928
  Then summarize what you found in one or two lines and ask the user to confirm
871
- the campaign identity/focus before continuing. Do not mention connected sender
872
- availability in this confirmation.
929
+ the current company/focus before continuing, especially if public website data
930
+ may be stale. Do not mention connected sender availability in this confirmation.
873
931
 
874
932
  If the user did not provide the launch identity, ask in normal chat for the
875
- LinkedIn profile or company website to use as the campaign identity. Do not ask
933
+ LinkedIn profile URL first, with the company website as the fallback. Do not ask
876
934
  them to choose an input type with the structured question tool:
877
935
 
878
936
  \`\`\`text
879
937
  I’m ready to build this in {workspace}.
880
938
 
881
- First, paste the LinkedIn profile or company website for the client/company this
882
- campaign is for. I’ll use that to resolve the campaign identity before we pick
883
- the target, offer, proof, and lead source.
939
+ What is your LinkedIn profile URL? If you do not have it handy, send your
940
+ company website instead. I’ll research the person and company from that, then
941
+ ask you to correct anything stale before we pick the target, offer, proof, and
942
+ lead source.
884
943
  \`\`\`
885
944
 
886
945
  After the user pastes a URL/domain, do the lightweight lookup. For a LinkedIn
@@ -890,18 +949,28 @@ most recent company from the profile. For a company website, call
890
949
  LinkedIn profile URL is available, retain it as \`senderLinkedinUrl\` for
891
950
  \`create_campaign\`; if a \`clientProspectId\` is available, pass that instead.
892
951
 
893
- After the user confirms the campaign identity, run one lightweight company
894
- lookup if it has not already run, then ask the campaign setup questions. The
895
- setup questions should use the confirmed company context so they do not feel
896
- generic.
952
+ After the user confirms the company/focus, run one lightweight company lookup
953
+ if it has not already run, then ask an offer-readiness question before inferred
954
+ strategy hardens:
955
+
956
+ \`\`\`text
957
+ Do you already know the offer for this campaign, should I use the researched
958
+ recommendation, or should we shape the offer together? If the website is stale,
959
+ tell me what is current before I build the brief.
960
+ \`\`\`
961
+
962
+ The setup questions should use the confirmed company context so they do not feel
963
+ generic. When you present a recommendation, introduce it as based on the
964
+ research you just did and keep it editable.
897
965
 
898
966
  Before the identity gate, use this customer-facing shape:
899
967
 
900
968
  \`\`\`text
901
969
  I’m ready to build the campaign in {workspace}.
902
970
 
903
- First I’ll resolve the client/company this campaign is for. I’ll use that
904
- context to choose the target, offer, proof, and lead source.
971
+ First I’ll research the person/company this campaign is for. I’ll use that
972
+ context to recommend a target, offer, proof, and lead source, then you can
973
+ correct anything stale before I build from it.
905
974
 
906
975
  Then I’ll turn that into a campaign brief for you to approve before anything is created.
907
976
  \`\`\`
@@ -1269,17 +1338,17 @@ show you the draft next so you can approve it or change it.
1269
1338
  Good opening:
1270
1339
 
1271
1340
  \`\`\`text
1272
- I’ll help you launch this as a Sellable campaign. First I’ll resolve the
1273
- client/company this campaign is for, then I’ll turn that into a campaign brief
1274
- before any leads are imported or anything can send.
1341
+ I’ll help you launch this as a Sellable campaign. First I’ll research the
1342
+ person/company this is for, then I’ll turn that into a campaign brief before
1343
+ any leads are imported or anything can send.
1275
1344
  \`\`\`
1276
1345
 
1277
1346
  Good identity setup:
1278
1347
 
1279
1348
  \`\`\`text
1280
- I’ll confirm the client/company this campaign is for before we pick the target,
1281
- offer, proof, and lead source. Paste a LinkedIn profile or company website and
1282
- I’ll resolve the campaign identity before the setup choices.
1349
+ I’ll ask for the LinkedIn profile URL first, with the company website as a
1350
+ fallback. Then I’ll show the researched recommendation and you can correct
1351
+ anything stale before we pick the target, offer, proof, and lead source.
1283
1352
  \`\`\`
1284
1353
 
1285
1354
  Bad:
@@ -1770,7 +1839,7 @@ function withWatchModeDriver(command, args, driver) {
1770
1839
 
1771
1840
  function mcpCommand(opts) {
1772
1841
  if (opts.server === "package") {
1773
- return ["npx", ["-y", opts.mcpPackage]];
1842
+ return [packageRunnerCommand(), ["-y", opts.mcpPackage]];
1774
1843
  }
1775
1844
  if (opts.server === "local") {
1776
1845
  if (!opts.localCommand) {
@@ -1792,6 +1861,33 @@ function mcpCommandForHost(opts, driver) {
1792
1861
  return withWatchModeDriver(command, args, driver);
1793
1862
  }
1794
1863
 
1864
+ function codexMcpAddArgs(opts) {
1865
+ const [command, args] = mcpCommand(opts);
1866
+ if (command === "hosted") {
1867
+ return ["mcp", "add", "sellable", "--url", args[0]];
1868
+ }
1869
+
1870
+ if (isWindowsPlatform()) {
1871
+ return [
1872
+ "mcp",
1873
+ "add",
1874
+ "sellable",
1875
+ "--env",
1876
+ WATCH_MODE_DRIVER_ENV.codex,
1877
+ "--",
1878
+ command,
1879
+ ...args,
1880
+ ];
1881
+ }
1882
+
1883
+ const [wrappedCommand, wrappedArgs] = withWatchModeDriver(
1884
+ command,
1885
+ args,
1886
+ "codex"
1887
+ );
1888
+ return ["mcp", "add", "sellable", "--", wrappedCommand, ...wrappedArgs];
1889
+ }
1890
+
1795
1891
  function installClaude(opts) {
1796
1892
  if (!commandExists("claude")) {
1797
1893
  const message =
@@ -1890,17 +1986,16 @@ function installCodex(opts) {
1890
1986
  throw new Error(message);
1891
1987
  }
1892
1988
  if (opts.server === "hosted") {
1893
- run("codex", ["mcp", "add", "sellable", "--url", opts.hostedUrl], opts);
1989
+ run("codex", codexMcpAddArgs(opts), opts);
1894
1990
  const info = installCodexDesktopPlugin(opts);
1895
1991
  return { installed: true, ...info };
1896
1992
  }
1897
- const [command, args] = mcpCommandForHost(opts, "codex");
1898
1993
  run("codex", ["mcp", "remove", "sellable"], {
1899
1994
  ...opts,
1900
1995
  dryRun: opts.dryRun,
1901
1996
  allowFail: true,
1902
1997
  });
1903
- run("codex", ["mcp", "add", "sellable", "--", command, ...args], opts);
1998
+ run("codex", codexMcpAddArgs(opts), opts);
1904
1999
  const info = installCodexDesktopPlugin(opts);
1905
2000
  return { installed: true, ...info };
1906
2001
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.121",
3
+ "version": "0.1.122",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code and Codex",
6
6
  "bin": {
@@ -86,8 +86,8 @@ instruction loading, file lookup, plugin cache versions, missing linked files,
86
86
  or tool discovery. Start in product language:
87
87
 
88
88
  ```text
89
- I’ll help you launch this as a Sellable campaign. First I’ll resolve the
90
- client/company this campaign is for, then I’ll turn that into a campaign brief
89
+ I’ll help you launch this as a Sellable campaign. First I’ll research the
90
+ person/company this campaign is for, then I’ll turn that into a campaign brief
91
91
  before we move into lead sourcing.
92
92
  ```
93
93
 
@@ -163,11 +163,13 @@ precision, and referral paths, but it does not provide hiring-by-role filters;
163
163
  say that distinction plainly in the source-plan gate.
164
164
 
165
165
  After scouting, ask for a second approval on the concrete source action. For
166
- LinkedIn engagement (`signal-discovery` internally), name how many selected
167
- posts will be scraped and the target engager/source-candidate volume. For
168
- Sales Nav or Prospeo, name the specific approved import lane and source lead
169
- count. Keep the internal 15-row campaign-table execution slice separate from
170
- source sampling.
166
+ LinkedIn engagement (`signal-discovery` internally), name how many
167
+ recommended posts will be scraped and the target engager/source-candidate
168
+ volume. N must be the smallest right-content post set that clears the source
169
+ target, not the default 3 promoted sample posts. For Sales Nav or Prospeo,
170
+ name the specific approved import lane and source lead count. Keep the
171
+ internal 15-row campaign-table execution slice separate from source
172
+ sampling.
171
173
 
172
174
  Do not call `import_leads` or `confirm_lead_list` until this second approval is
173
175
  granted.
@@ -185,8 +187,8 @@ granted.
185
187
  the campaign table and return the initial campaign-table execution slice rows.
186
188
 
187
189
  For LinkedIn engagement, the customer-facing approval card must use the exact
188
- action shape "Approve scraping N selected LinkedIn posts?" and the chat summary
189
- should be a compact `## Source Recommendation` block with:
190
+ action shape "Approve scraping N recommended LinkedIn posts?" and the chat
191
+ summary should be a compact `## Source Recommendation` block with:
190
192
 
191
193
  - goal: about 300 headline-fit prospects from relevant LinkedIn engagement
192
194
  - source-candidate plan: use sample math first: target headline-fit prospects
@@ -396,10 +398,10 @@ customer-facing progress copy.
396
398
 
397
399
  Do not treat the active Sellable workspace as the campaign subject. The
398
400
  workspace only tells you where the campaign will be saved. Before buyer, CTA,
399
- proof, or source questions, identify the campaign identity: the person/profile
400
- or company this campaign is for, plus enough company/product context to build
401
- the brief. This is only the client-prospect/bootstrap identity for
402
- `clientProspectId` or `senderLinkedinUrl`; it is not a connected-sender check.
401
+ proof, or source questions, identify the person/profile or company this
402
+ campaign is for, plus enough current company/product context to build the
403
+ brief. This client/company lookup feeds `clientProspectId` or
404
+ `senderLinkedinUrl`; it is not a connected-sender check.
403
405
 
404
406
  Do not call `mcp__sellable__list_senders`, `mcp__sellable__get_sender`, or
405
407
  surface connected/missing sender state during setup, brief, source, filter, or
@@ -423,19 +425,20 @@ first:
423
425
  only if a URL/domain is also available.
424
426
 
425
427
  Then summarize what you found in one or two lines and ask the user to confirm
426
- the campaign identity/focus before continuing. Do not mention connected sender
427
- availability in this confirmation.
428
+ the current company/focus before continuing, especially if public website data
429
+ may be stale. Do not mention connected sender availability in this confirmation.
428
430
 
429
431
  If the user did not provide the launch identity, ask in normal chat for the
430
- LinkedIn profile or company website to use as the campaign identity. Do not ask
432
+ LinkedIn profile URL first, with the company website as the fallback. Do not ask
431
433
  them to choose an input type with the structured question tool:
432
434
 
433
435
  ```text
434
436
  I’m ready to build this in {workspace}.
435
437
 
436
- First, paste the LinkedIn profile or company website for the client/company this
437
- campaign is for. I’ll use that to resolve the campaign identity before we pick
438
- the target, offer, proof, and lead source.
438
+ What is your LinkedIn profile URL? If you do not have it handy, send your
439
+ company website instead. I’ll research the person and company from that, then
440
+ ask you to correct anything stale before we pick the target, offer, proof, and
441
+ lead source.
439
442
  ```
440
443
 
441
444
  After the user pastes a URL/domain, do the lightweight lookup. For a LinkedIn
@@ -445,10 +448,19 @@ most recent company from the profile. For a company website, call
445
448
  LinkedIn profile URL is available, retain it as `senderLinkedinUrl` for
446
449
  `create_campaign`; if a `clientProspectId` is available, pass that instead.
447
450
 
448
- After the user confirms the campaign identity, run one lightweight company
449
- lookup if it has not already run, then ask the campaign setup questions. The
450
- setup questions should use the confirmed company context so they do not feel
451
- generic.
451
+ After the user confirms the company/focus, run one lightweight company lookup
452
+ if it has not already run, then ask an offer-readiness question before inferred
453
+ strategy hardens:
454
+
455
+ ```text
456
+ Do you already know the offer for this campaign, should I use the researched
457
+ recommendation, or should we shape the offer together? If the website is stale,
458
+ tell me what is current before I build the brief.
459
+ ```
460
+
461
+ The setup questions should use the confirmed company context so they do not feel
462
+ generic. When you present a recommendation, introduce it as based on the
463
+ research you just did and keep it editable.
452
464
 
453
465
  ### Sufficient Intake Bypass
454
466
 
@@ -476,9 +488,9 @@ If the invocation or any later user message explicitly asks for "yolo mode",
476
488
  me", "use best estimates", or "just run it", enable YOLO mode for the rest of
477
489
  the run. Treat YOLO as `interactionMode: "autonomous"` plus an intake policy:
478
490
 
479
- - If campaign identity is missing, ask only for the LinkedIn profile or company
480
- website in normal chat; do not ask buyer, offer, proof, source, or filter setup
481
- questions before that.
491
+ - If the campaign subject is missing, ask only for the LinkedIn profile URL
492
+ first, with company website as the fallback, in normal chat; do not ask buyer,
493
+ offer, proof, source, or filter setup questions before that.
482
494
  - Treat any freeform directions already provided, or added later by the user, as
483
495
  operator directions for the rest of the run. If directions conflict, the newest
484
496
  user direction wins.
@@ -500,8 +512,9 @@ Before the identity gate, use this customer-facing shape:
500
512
  ```text
501
513
  I’m ready to build the campaign in {workspace}.
502
514
 
503
- First I’ll resolve the client/company this campaign is for. I’ll use that
504
- context to choose the target, offer, proof, and lead source.
515
+ First I’ll research the person/company this campaign is for. I’ll use that
516
+ context to recommend a target, offer, proof, and lead source, then you can
517
+ correct anything stale before I build from it.
505
518
 
506
519
  Then I’ll turn that into a campaign brief for you to approve before any leads
507
520
  are sourced.
@@ -636,9 +649,9 @@ updates.
636
649
  ```text
637
650
  You're in — {activeWorkspaceName} workspace, ready to roll.
638
651
 
639
- Now — paste the LinkedIn profile or company website for the client/company this campaign is for. I’ll use that to resolve the campaign identity before we pick the target, offer, proof, and lead source.
652
+ Now — what is your LinkedIn profile URL? If you do not have it handy, send your company website instead. I’ll research the person and company from that, then ask you to correct anything stale before we pick the target, offer, proof, and lead source.
640
653
 
641
- e.g. https://example.com or https://www.linkedin.com/in/client-handle
654
+ e.g. https://www.linkedin.com/in/client-handle or https://example.com
642
655
  ```
643
656
 
644
657
  - If `isReturningUser === false`, prepend ONE line confirming the new
@@ -647,9 +660,9 @@ updates.
647
660
  ```text
648
661
  You're set up — your {activeWorkspaceName} workspace is ready.
649
662
 
650
- Now — paste the LinkedIn profile or company website for the client/company this campaign is for. I’ll use that to resolve the campaign identity before we pick the target, offer, proof, and lead source.
663
+ Now — what is your LinkedIn profile URL? If you do not have it handy, send your company website instead. I’ll research the person and company from that, then ask you to correct anything stale before we pick the target, offer, proof, and lead source.
651
664
 
652
- e.g. https://example.com or https://www.linkedin.com/in/client-handle
665
+ e.g. https://www.linkedin.com/in/client-handle or https://example.com
653
666
  ```
654
667
 
655
668
  No other lines. No "all set", no "signed in", no other acknowledgement.