@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 +41 -0
- package/bin/tenderlead.js +31 -4
- package/package.json +1 -1
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
|
-
|
|
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