@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 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 import.');
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 === '--json') options.json = true;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenderlead/cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
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",