@tenderlead/cli 0.3.0 → 0.4.1

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
@@ -21,9 +21,13 @@ Sign in with Google or an emailed sign-in link, then create a scoped token under
21
21
 
22
22
  ```bash
23
23
  export TENDERLEAD_API_TOKEN="paste-token-here"
24
+ tenderlead whoami
24
25
  tenderlead brief
25
26
  ```
26
27
 
28
+ `whoami` confirms which account and token the CLI is using (masked email, plan,
29
+ token label, scopes and expiry). It works with any valid token.
30
+
27
31
  Use JSON output for scripts and agents:
28
32
 
29
33
  ```bash
@@ -106,6 +110,31 @@ For draft workspace edits or evidence-pack workspace state, pass the existing
106
110
  request JSON with `--file`. The CLI sets `contract_id` from the command and sends
107
111
  the remaining fields to the same handler used by the signed-in product.
108
112
 
113
+ ### Prepare a checklist from a public tender link
114
+
115
+ With a Team account and a `workspace:write` token:
116
+
117
+ ```bash
118
+ tenderlead drafts fetch CONTRACT_ID --url "https://www.contractsfinder.service.gov.uk/Notice/NOTICE_ID" > documents.json
119
+ ```
120
+
121
+ This retrieves public PDF/Word attachments from supported official notices
122
+ (Contracts Finder, Find a Tender and Public Contracts Scotland). It returns
123
+ `documents` and `warnings`, without calling a model or saving a draft. Each
124
+ document retains its source URL. Check the text, warnings and complete buyer
125
+ pack before proceeding. Portal-only files still need manual upload.
126
+
127
+ To build a checklist, put the reviewed `documents` array into a JSON object
128
+ under `preparation_documents`, then run:
129
+
130
+ ```bash
131
+ tenderlead drafts create CONTRACT_ID --file reviewed-documents.json
132
+ ```
133
+
134
+ Checklist generation requires explicit account AI consent and available budget.
135
+ It does not submit a bid or replace saved answers. Imports are limited to five
136
+ documents, 2 MB per file and 60,000 text characters in total.
137
+
109
138
  Commands that replace, import, update or remove saved data ask for a typed
110
139
  confirmation. Reviewed automation can pass `--yes`. Run `tenderlead --help` for
111
140
  the full command list.
package/bin/tenderlead.js CHANGED
@@ -17,6 +17,7 @@ function usage() {
17
17
  return [
18
18
  'Usage:',
19
19
  ' tenderlead login',
20
+ ' tenderlead whoami [--json]',
20
21
  ' tenderlead brief [--json]',
21
22
  ' tenderlead tenders search [--query <text>] [--recommendation bid|skip] [--all] [--limit <1-50>] [--json]',
22
23
  ' tenderlead tender score <contract-id> [--json]',
@@ -44,6 +45,7 @@ function usage() {
44
45
  ' tenderlead templates upload --file <path> [--file <path>...] [--role <role>] [--yes] [--json]',
45
46
  ' tenderlead templates remove <template-id> [--yes]',
46
47
  ' tenderlead drafts list [--json]',
48
+ ' tenderlead drafts fetch <contract-id> --url <official-tender-url> [--json]',
47
49
  ' tenderlead drafts show <contract-id> [--workspace] [--json]',
48
50
  ' tenderlead drafts create <contract-id> [--file <request.json>] [--refresh] [--workspace] [--yes] [--json]',
49
51
  ' tenderlead drafts export <contract-id> --format text|docx|xlsx --output <path> [--yes]',
@@ -60,6 +62,7 @@ function usage() {
60
62
  function parseArgs(argv) {
61
63
  const command = argv[2];
62
64
  if (command === 'login') return { command: 'login' };
65
+ if (command === 'whoami') return parseJsonOnlyArgs(argv, 3, { command: 'whoami', json: false });
63
66
  if (command === 'brief') {
64
67
  const options = { command: 'brief', json: false };
65
68
  for (let index = 3; index < argv.length; index += 1) {
@@ -312,13 +315,14 @@ function parseTemplatesArgs(argv) {
312
315
  function parseDraftArgs(argv) {
313
316
  const action = argv[3];
314
317
  if (action === 'list') return parseJsonOnlyArgs(argv, 4, { command: 'drafts', action, json: false });
315
- if (!['show', 'create', 'export', 'import'].includes(action)) throw new Error('Use drafts list, show, create, export or import.');
318
+ if (!['show', 'create', 'export', 'import', 'fetch'].includes(action)) throw new Error('Use drafts list, show, create, export, import or fetch.');
316
319
  const contractId = argv[4];
317
320
  if (!contractId || contractId.startsWith('--')) throw new Error(`drafts ${action} requires a contract ID`);
318
321
  const options = { command: 'drafts', action, contractId, file: null, refresh: false, workspace: false, format: null, output: null, yes: false, json: false };
319
322
  for (let index = 5; index < argv.length; index += 1) {
320
323
  const arg = argv[index];
321
- if (arg === '--json') options.json = true;
324
+ if (arg === '--url' && action === 'fetch') { options.url = optionValue(argv, index, arg); index += 1; }
325
+ else if (arg === '--json') options.json = true;
322
326
  else if (arg === '--yes') options.yes = true;
323
327
  else if (arg === '--refresh') options.refresh = true;
324
328
  else if (arg === '--workspace') options.workspace = true;
@@ -329,6 +333,7 @@ function parseDraftArgs(argv) {
329
333
  }
330
334
  if (action === 'export' && (!['text', 'docx', 'xlsx'].includes(options.format) || !options.output)) throw new Error('drafts export requires --format text|docx|xlsx and --output <path>');
331
335
  if (action === 'import' && !options.file) throw new Error('drafts import requires --file <workspace.xlsx>');
336
+ if (action === 'fetch' && !options.url) throw new Error('drafts fetch requires --url <official-tender-url>');
332
337
  return options;
333
338
  }
334
339
 
@@ -346,6 +351,19 @@ function line(value, fallback) {
346
351
  return text || fallback;
347
352
  }
348
353
 
354
+ function formatWhoami(payload) {
355
+ const token = payload.token || {};
356
+ const rows = [
357
+ ['Account', line(payload.email, 'unknown')],
358
+ ['Plan', `${line(payload.plan_tier, 'unknown')} (${line(payload.plan_status, 'unknown')})`],
359
+ ['Token', line(token.label, 'unnamed')],
360
+ ['Scopes', Array.isArray(token.scopes) && token.scopes.length ? token.scopes.join(', ') : 'none'],
361
+ ['Expires', token.expires_at ? String(token.expires_at).slice(0, 10) : 'unknown'],
362
+ ];
363
+ const width = Math.max(...rows.map(([label]) => label.length));
364
+ return rows.map(([label, value]) => `${label.padEnd(width)} ${value}`).join('\n');
365
+ }
366
+
349
367
  function formatBrief(payload) {
350
368
  const rows = Array.isArray(payload.opportunities) ? payload.opportunities : [];
351
369
  const lines = ['# TenderLead daily brief', '', `Generated: ${line(payload.generated_at, 'Not recorded')}`, `Plan: ${line(payload.tier, 'Not recorded')}`, '', '## Opportunities', ''];
@@ -406,6 +424,8 @@ async function apiRequest(path, options = {}, fetchImpl = fetch) {
406
424
  return body;
407
425
  }
408
426
 
427
+ async function fetchWhoami(fetchImpl = fetch) { return apiRequest('/api/cli/whoami', {}, fetchImpl); }
428
+
409
429
  async function fetchBrief(_options, fetchImpl = fetch) {
410
430
  return apiRequest('/v1/briefs/daily', {}, fetchImpl);
411
431
  }
@@ -689,6 +709,7 @@ async function main() {
689
709
  console.log('TenderLead Settings opened. Sign in, create the required scoped token, then set TENDERLEAD_API_TOKEN in your shell.');
690
710
  return;
691
711
  }
712
+ if (options.command === 'whoami') return printPayload(await fetchWhoami(), options.json, formatWhoami);
692
713
  if (options.command === 'brief') {
693
714
  const brief = await fetchBrief(options);
694
715
  console.log(options.json ? JSON.stringify(brief, null, 2) : formatBrief(brief));
@@ -756,6 +777,7 @@ async function main() {
756
777
  return printPayload(await removeTemplate(options.id), options.json, () => `Removed bid template ${options.id}.`);
757
778
  }
758
779
  if (options.command === 'drafts') {
780
+ if (options.action === 'fetch') return printPayload(await apiRequest('/v1/drafts', {method: 'POST', body: JSON.stringify({contract_id: options.contractId, preparation_url: options.url})}), true);
759
781
  if (options.action === 'list') {
760
782
  const payload = await fetchDrafts();
761
783
  return printPayload(payload, options.json, value => formatList('TenderLead bid drafts', value.drafts || [], row => `${line(row.title, 'Untitled')} · ${line(row.contract_id, 'unknown')}`));
@@ -821,7 +843,7 @@ if (require.main === module) {
821
843
  }
822
844
 
823
845
  module.exports = {
824
- parseArgs, openLogin, formatBrief, formatTenders, formatScore, fetchBrief, searchTenders, scoreTender, fetchProfile, updateProfile,
846
+ parseArgs, openLogin, fetchWhoami, formatWhoami, formatBrief, formatTenders, formatScore, fetchBrief, searchTenders, scoreTender, fetchProfile, updateProfile,
825
847
  validateProfile, readProfileFile, profileTemplate, profileDiff, formatProfile,
826
848
  formatProfileDiff, confirmApply, confirmWord, readJsonFile, contentTypeForFile, appendFiles,
827
849
  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.3.0",
3
+ "version": "0.4.1",
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",