@tenderlead/cli 0.4.0 → 0.4.2
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 +66 -0
- package/bin/tenderlead.js +32 -3
- 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
|
|
@@ -110,6 +141,31 @@ For draft workspace edits or evidence-pack workspace state, pass the existing
|
|
|
110
141
|
request JSON with `--file`. The CLI sets `contract_id` from the command and sends
|
|
111
142
|
the remaining fields to the same handler used by the signed-in product.
|
|
112
143
|
|
|
144
|
+
### Prepare a checklist from a public tender link
|
|
145
|
+
|
|
146
|
+
With a Team account and a `workspace:write` token:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
tenderlead drafts fetch CONTRACT_ID --url "https://www.contractsfinder.service.gov.uk/Notice/NOTICE_ID" > documents.json
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
This retrieves public PDF/Word attachments from supported official notices
|
|
153
|
+
(Contracts Finder, Find a Tender and Public Contracts Scotland). It returns
|
|
154
|
+
`documents` and `warnings`, without calling a model or saving a draft. Each
|
|
155
|
+
document retains its source URL. Check the text, warnings and complete buyer
|
|
156
|
+
pack before proceeding. Portal-only files still need manual upload.
|
|
157
|
+
|
|
158
|
+
To build a checklist, put the reviewed `documents` array into a JSON object
|
|
159
|
+
under `preparation_documents`, then run:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
tenderlead drafts create CONTRACT_ID --file reviewed-documents.json
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Checklist generation requires explicit account AI consent and available budget.
|
|
166
|
+
It does not submit a bid or replace saved answers. Imports are limited to five
|
|
167
|
+
documents, 2 MB per file and 60,000 text characters in total.
|
|
168
|
+
|
|
113
169
|
Commands that replace, import, update or remove saved data ask for a typed
|
|
114
170
|
confirmation. Reviewed automation can pass `--yes`. Run `tenderlead --help` for
|
|
115
171
|
the full command list.
|
|
@@ -124,6 +180,16 @@ Revoke exposed tokens in Settings.
|
|
|
124
180
|
Sign-in, token creation and revocation, Stripe checkout and billing, and
|
|
125
181
|
permanent account deletion remain browser tasks.
|
|
126
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
|
+
|
|
127
193
|
Provider acceptance and API responses do not prove email inbox delivery.
|
|
128
194
|
|
|
129
195
|
## Requirements
|
package/bin/tenderlead.js
CHANGED
|
@@ -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]',
|
|
@@ -45,6 +46,7 @@ function usage() {
|
|
|
45
46
|
' tenderlead templates upload --file <path> [--file <path>...] [--role <role>] [--yes] [--json]',
|
|
46
47
|
' tenderlead templates remove <template-id> [--yes]',
|
|
47
48
|
' tenderlead drafts list [--json]',
|
|
49
|
+
' tenderlead drafts fetch <contract-id> --url <official-tender-url> [--json]',
|
|
48
50
|
' tenderlead drafts show <contract-id> [--workspace] [--json]',
|
|
49
51
|
' tenderlead drafts create <contract-id> [--file <request.json>] [--refresh] [--workspace] [--yes] [--json]',
|
|
50
52
|
' tenderlead drafts export <contract-id> --format text|docx|xlsx --output <path> [--yes]',
|
|
@@ -62,6 +64,10 @@ function parseArgs(argv) {
|
|
|
62
64
|
const command = argv[2];
|
|
63
65
|
if (command === 'login') return { command: 'login' };
|
|
64
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
|
+
}
|
|
65
71
|
if (command === 'brief') {
|
|
66
72
|
const options = { command: 'brief', json: false };
|
|
67
73
|
for (let index = 3; index < argv.length; index += 1) {
|
|
@@ -314,13 +320,14 @@ function parseTemplatesArgs(argv) {
|
|
|
314
320
|
function parseDraftArgs(argv) {
|
|
315
321
|
const action = argv[3];
|
|
316
322
|
if (action === 'list') return parseJsonOnlyArgs(argv, 4, { command: 'drafts', action, json: false });
|
|
317
|
-
if (!['show', 'create', 'export', 'import'].includes(action)) throw new Error('Use drafts list, show, create, export or
|
|
323
|
+
if (!['show', 'create', 'export', 'import', 'fetch'].includes(action)) throw new Error('Use drafts list, show, create, export, import or fetch.');
|
|
318
324
|
const contractId = argv[4];
|
|
319
325
|
if (!contractId || contractId.startsWith('--')) throw new Error(`drafts ${action} requires a contract ID`);
|
|
320
326
|
const options = { command: 'drafts', action, contractId, file: null, refresh: false, workspace: false, format: null, output: null, yes: false, json: false };
|
|
321
327
|
for (let index = 5; index < argv.length; index += 1) {
|
|
322
328
|
const arg = argv[index];
|
|
323
|
-
if (arg === '--
|
|
329
|
+
if (arg === '--url' && action === 'fetch') { options.url = optionValue(argv, index, arg); index += 1; }
|
|
330
|
+
else if (arg === '--json') options.json = true;
|
|
324
331
|
else if (arg === '--yes') options.yes = true;
|
|
325
332
|
else if (arg === '--refresh') options.refresh = true;
|
|
326
333
|
else if (arg === '--workspace') options.workspace = true;
|
|
@@ -331,6 +338,7 @@ function parseDraftArgs(argv) {
|
|
|
331
338
|
}
|
|
332
339
|
if (action === 'export' && (!['text', 'docx', 'xlsx'].includes(options.format) || !options.output)) throw new Error('drafts export requires --format text|docx|xlsx and --output <path>');
|
|
333
340
|
if (action === 'import' && !options.file) throw new Error('drafts import requires --file <workspace.xlsx>');
|
|
341
|
+
if (action === 'fetch' && !options.url) throw new Error('drafts fetch requires --url <official-tender-url>');
|
|
334
342
|
return options;
|
|
335
343
|
}
|
|
336
344
|
|
|
@@ -361,6 +369,24 @@ function formatWhoami(payload) {
|
|
|
361
369
|
return rows.map(([label, value]) => `${label.padEnd(width)} ${value}`).join('\n');
|
|
362
370
|
}
|
|
363
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
|
+
|
|
364
390
|
function formatBrief(payload) {
|
|
365
391
|
const rows = Array.isArray(payload.opportunities) ? payload.opportunities : [];
|
|
366
392
|
const lines = ['# TenderLead daily brief', '', `Generated: ${line(payload.generated_at, 'Not recorded')}`, `Plan: ${line(payload.tier, 'Not recorded')}`, '', '## Opportunities', ''];
|
|
@@ -422,6 +448,7 @@ async function apiRequest(path, options = {}, fetchImpl = fetch) {
|
|
|
422
448
|
}
|
|
423
449
|
|
|
424
450
|
async function fetchWhoami(fetchImpl = fetch) { return apiRequest('/api/cli/whoami', {}, fetchImpl); }
|
|
451
|
+
async function fetchAccountStatus(fetchImpl = fetch) { return apiRequest('/v1/account/status', {}, fetchImpl); }
|
|
425
452
|
|
|
426
453
|
async function fetchBrief(_options, fetchImpl = fetch) {
|
|
427
454
|
return apiRequest('/v1/briefs/daily', {}, fetchImpl);
|
|
@@ -707,6 +734,7 @@ async function main() {
|
|
|
707
734
|
return;
|
|
708
735
|
}
|
|
709
736
|
if (options.command === 'whoami') return printPayload(await fetchWhoami(), options.json, formatWhoami);
|
|
737
|
+
if (options.command === 'account') return printPayload(await fetchAccountStatus(), options.json, formatAccountStatus);
|
|
710
738
|
if (options.command === 'brief') {
|
|
711
739
|
const brief = await fetchBrief(options);
|
|
712
740
|
console.log(options.json ? JSON.stringify(brief, null, 2) : formatBrief(brief));
|
|
@@ -774,6 +802,7 @@ async function main() {
|
|
|
774
802
|
return printPayload(await removeTemplate(options.id), options.json, () => `Removed bid template ${options.id}.`);
|
|
775
803
|
}
|
|
776
804
|
if (options.command === 'drafts') {
|
|
805
|
+
if (options.action === 'fetch') return printPayload(await apiRequest('/v1/drafts', {method: 'POST', body: JSON.stringify({contract_id: options.contractId, preparation_url: options.url})}), true);
|
|
777
806
|
if (options.action === 'list') {
|
|
778
807
|
const payload = await fetchDrafts();
|
|
779
808
|
return printPayload(payload, options.json, value => formatList('TenderLead bid drafts', value.drafts || [], row => `${line(row.title, 'Untitled')} · ${line(row.contract_id, 'unknown')}`));
|
|
@@ -839,7 +868,7 @@ if (require.main === module) {
|
|
|
839
868
|
}
|
|
840
869
|
|
|
841
870
|
module.exports = {
|
|
842
|
-
parseArgs, openLogin, fetchWhoami, formatWhoami, formatBrief, formatTenders, formatScore, fetchBrief, searchTenders, scoreTender, fetchProfile, updateProfile,
|
|
871
|
+
parseArgs, openLogin, fetchWhoami, fetchAccountStatus, formatWhoami, formatAccountStatus, formatBrief, formatTenders, formatScore, fetchBrief, searchTenders, scoreTender, fetchProfile, updateProfile,
|
|
843
872
|
validateProfile, readProfileFile, profileTemplate, profileDiff, formatProfile,
|
|
844
873
|
formatProfileDiff, confirmApply, confirmWord, readJsonFile, contentTypeForFile, appendFiles,
|
|
845
874
|
fetchPipeline, addPipelineItem, updatePipelineItem, removePipelineItem, fetchInsights, sendTenderFeedback, sendProductFeedback,
|
package/package.json
CHANGED