@tenderlead/cli 0.4.1 → 0.4.3

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
@@ -22,12 +22,43 @@ Sign in with Google or an emailed sign-in link, then create a scoped token under
22
22
  ```bash
23
23
  export TENDERLEAD_API_TOKEN="paste-token-here"
24
24
  tenderlead whoami
25
+ tenderlead account status
25
26
  tenderlead brief
26
27
  ```
27
28
 
28
29
  `whoami` confirms which account and token the CLI is using (masked email, plan,
29
30
  token label, scopes and expiry). It works with any valid token.
30
31
 
32
+ `account status` uses a `profile:read` token to report profile readiness,
33
+ personalised preview state and the number of opportunities in a released
34
+ preview. If setup is incomplete, it gives the next browser step. Preview
35
+ requests, trial start, token management and billing remain browser actions.
36
+ Existing profile fields can still be edited with `profile apply`.
37
+
38
+ The `account status` command is included in CLI 0.4.2 and uses the deployed
39
+ read-only route:
40
+
41
+ ```http
42
+ GET /v1/account/status
43
+ Authorization: Bearer TENDERLEAD_API_TOKEN
44
+ ```
45
+
46
+ It requires `profile:read` and returns a compact account-scoped response:
47
+
48
+ ```json
49
+ {
50
+ "account": { "active": false, "plan_tier": null, "plan_status": null, "trial_ends_at": null },
51
+ "profile": { "ready": true },
52
+ "preview": { "status": "sent", "requested_at": "2026-09-20T09:00:00.000Z", "briefing_sent_at": "2026-09-20T09:05:00.000Z" },
53
+ "released_opportunity_count": 3,
54
+ "next_action": { "action": "start_trial", "url": "https://tenderlead.co.uk/personalised-preview", "label": "Open your preview to start a trial or choose a plan" }
55
+ }
56
+ ```
57
+
58
+ The route does not return profile fields, email addresses or tender records.
59
+ Preview requests, trial activation and account-access changes use the returned
60
+ browser URL rather than adding CLI write access.
61
+
31
62
  Use JSON output for scripts and agents:
32
63
 
33
64
  ```bash
@@ -149,6 +180,16 @@ Revoke exposed tokens in Settings.
149
180
  Sign-in, token creation and revocation, Stripe checkout and billing, and
150
181
  permanent account deletion remain browser tasks.
151
182
 
183
+ ## OpenAI and Claude
184
+
185
+ TenderLead's published OpenAI integration connects through OAuth and provides
186
+ three read-only tools for the current brief, matched-tender search and saved
187
+ tender decisions. If the connected account has no active trial or paid plan,
188
+ the tool response links to the browser setup journey. Complete the company
189
+ profile there, request the free personalised preview or start a trial, then
190
+ retry the tool. The integration cannot change a profile, start a trial or alter
191
+ billing. Claude access remains a private beta.
192
+
152
193
  Provider acceptance and API responses do not prove email inbox delivery.
153
194
 
154
195
  ## Requirements
package/bin/tenderlead.js CHANGED
@@ -10,7 +10,7 @@ const PROFILE_FIELDS = [
10
10
  'company_name', 'legal_name', 'company_number', 'title', 'website', 'team_size', 'specialisms',
11
11
  'past_contracts', 'sectors', 'keywords', 'size_min', 'size_max', 'frameworks',
12
12
  'clearance_capability', 'region', 'market_watch_enabled', 'market_watch_terms',
13
- 'market_watch_buyers', 'market_watch_note', 'us_readiness_profile', 'no_bid_rules',
13
+ 'market_watch_buyers', 'market_watch_note', 'us_readiness_profile', 'eu_readiness_profile', 'no_bid_rules',
14
14
  ];
15
15
 
16
16
  function usage() {
@@ -18,6 +18,7 @@ function usage() {
18
18
  'Usage:',
19
19
  ' tenderlead login',
20
20
  ' tenderlead whoami [--json]',
21
+ ' tenderlead account status [--json]',
21
22
  ' tenderlead brief [--json]',
22
23
  ' tenderlead tenders search [--query <text>] [--recommendation bid|skip] [--all] [--limit <1-50>] [--json]',
23
24
  ' tenderlead tender score <contract-id> [--json]',
@@ -63,6 +64,10 @@ function parseArgs(argv) {
63
64
  const command = argv[2];
64
65
  if (command === 'login') return { command: 'login' };
65
66
  if (command === 'whoami') return parseJsonOnlyArgs(argv, 3, { command: 'whoami', json: false });
67
+ if (command === 'account') {
68
+ if (argv[3] !== 'status') throw new Error('Use account status.');
69
+ return parseJsonOnlyArgs(argv, 4, { command: 'account', action: 'status', json: false });
70
+ }
66
71
  if (command === 'brief') {
67
72
  const options = { command: 'brief', json: false };
68
73
  for (let index = 3; index < argv.length; index += 1) {
@@ -364,12 +369,30 @@ function formatWhoami(payload) {
364
369
  return rows.map(([label, value]) => `${label.padEnd(width)} ${value}`).join('\n');
365
370
  }
366
371
 
372
+ function formatAccountStatus(payload) {
373
+ const account = payload.account || {};
374
+ const profile = payload.profile || {};
375
+ const preview = payload.preview || null;
376
+ const nextAction = payload.next_action || {};
377
+ return [
378
+ '# TenderLead account status',
379
+ '',
380
+ `Access: ${account.active ? 'active' : 'inactive'}`,
381
+ `Plan: ${line(account.plan_tier, 'not set')} (${line(account.plan_status, 'not active')})`,
382
+ `Profile: ${profile.ready ? 'ready' : 'incomplete'}`,
383
+ `Preview: ${preview ? line(preview.status, 'unknown') : 'not requested'}`,
384
+ `Released opportunities: ${Number(payload.released_opportunity_count) || 0}`,
385
+ '',
386
+ `Next: ${line(nextAction.label, 'Open TenderLead in your browser')}${nextAction.url ? `: ${nextAction.url}` : ''}`,
387
+ ].join('\n');
388
+ }
389
+
367
390
  function formatBrief(payload) {
368
391
  const rows = Array.isArray(payload.opportunities) ? payload.opportunities : [];
369
392
  const lines = ['# TenderLead daily brief', '', `Generated: ${line(payload.generated_at, 'Not recorded')}`, `Plan: ${line(payload.tier, 'Not recorded')}`, '', '## Opportunities', ''];
370
393
  if (!rows.length) lines.push('No current scored opportunities.');
371
394
  for (const row of rows) {
372
- const score = Number.isFinite(Number(row.score)) ? ` ${Math.round(Number(row.score))}/100` : '';
395
+ const score = row.score != null && row.score !== '' && Number.isFinite(Number(row.score)) ? ` ${Math.round(Number(row.score))}/100` : '';
373
396
  lines.push(`- **${line(row.recommendation, 'alert').toUpperCase()}${score}** ${line(row.title, 'Untitled notice')}`);
374
397
  lines.push(` ${line(row.buyer, 'Buyer not listed')} · ${line(row.source, 'Source not recorded')} · Deadline: ${line(row.deadline, 'Not recorded')}`);
375
398
  if (Array.isArray(row.reasoning) && row.reasoning.length) lines.push(` Why: ${row.reasoning.slice(0, 3).map(String).join(' ')}`);
@@ -395,7 +418,8 @@ function formatTenders(payload) {
395
418
  }
396
419
 
397
420
  function formatScore(payload) {
398
- const score = Number.isFinite(Number(payload.score)) ? `${Math.round(Number(payload.score))}/100` : 'not recorded';
421
+ if (payload.status === 'processing') return `Fit check in progress. Check its status with GET ${line(payload.status_url, '/v1/tenders/' + encodeURIComponent(payload.contract_id) + '/score')}.`;
422
+ const score = payload.score != null && payload.score !== '' && Number.isFinite(Number(payload.score)) ? `${Math.round(Number(payload.score))}/100` : 'not recorded';
399
423
  const lines = [`# TenderLead tender score`, '', `Contract: ${line(payload.contract_id, 'Not recorded')}`, `Recommendation: ${line(payload.recommendation, 'Not recorded').toUpperCase()}`, `Score: ${score}`];
400
424
  if (Array.isArray(payload.reasoning) && payload.reasoning.length) lines.push('', 'Why:', ...payload.reasoning.map(item => `- ${String(item)}`));
401
425
  if (payload.no_bid_reason) lines.push('', `No-bid reason: ${payload.no_bid_reason}`);
@@ -425,6 +449,7 @@ async function apiRequest(path, options = {}, fetchImpl = fetch) {
425
449
  }
426
450
 
427
451
  async function fetchWhoami(fetchImpl = fetch) { return apiRequest('/api/cli/whoami', {}, fetchImpl); }
452
+ async function fetchAccountStatus(fetchImpl = fetch) { return apiRequest('/v1/account/status', {}, fetchImpl); }
428
453
 
429
454
  async function fetchBrief(_options, fetchImpl = fetch) {
430
455
  return apiRequest('/v1/briefs/daily', {}, fetchImpl);
@@ -590,6 +615,7 @@ function validateProfile(profile) {
590
615
  }
591
616
  if ('market_watch_enabled' in profile && typeof profile.market_watch_enabled !== 'boolean') throw new Error('market_watch_enabled must be a boolean');
592
617
  if ('us_readiness_profile' in profile && !isPlainObject(profile.us_readiness_profile)) throw new Error('us_readiness_profile must be an object');
618
+ if ('eu_readiness_profile' in profile && !isPlainObject(profile.eu_readiness_profile)) throw new Error('eu_readiness_profile must be an object');
593
619
  for (const field of ['size_min', 'size_max']) {
594
620
  if (field in profile && !Number.isFinite(Number(profile[field]))) throw new Error(`${field} must be a number`);
595
621
  }
@@ -710,6 +736,7 @@ async function main() {
710
736
  return;
711
737
  }
712
738
  if (options.command === 'whoami') return printPayload(await fetchWhoami(), options.json, formatWhoami);
739
+ if (options.command === 'account') return printPayload(await fetchAccountStatus(), options.json, formatAccountStatus);
713
740
  if (options.command === 'brief') {
714
741
  const brief = await fetchBrief(options);
715
742
  console.log(options.json ? JSON.stringify(brief, null, 2) : formatBrief(brief));
@@ -843,7 +870,7 @@ if (require.main === module) {
843
870
  }
844
871
 
845
872
  module.exports = {
846
- parseArgs, openLogin, fetchWhoami, formatWhoami, formatBrief, formatTenders, formatScore, fetchBrief, searchTenders, scoreTender, fetchProfile, updateProfile,
873
+ parseArgs, openLogin, fetchWhoami, fetchAccountStatus, formatWhoami, formatAccountStatus, formatBrief, formatTenders, formatScore, fetchBrief, searchTenders, scoreTender, fetchProfile, updateProfile,
847
874
  validateProfile, readProfileFile, profileTemplate, profileDiff, formatProfile,
848
875
  formatProfileDiff, confirmApply, confirmWord, readJsonFile, contentTypeForFile, appendFiles,
849
876
  fetchPipeline, addPipelineItem, updatePipelineItem, removePipelineItem, fetchInsights, sendTenderFeedback, sendProductFeedback,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenderlead/cli",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "private": false,
5
5
  "description": "Run TenderLead tender, profile, pipeline, insight and bid-workspace workflows from the terminal",
6
6
  "homepage": "https://tenderlead.co.uk",