@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 +29 -0
- package/bin/tenderlead.js +25 -3
- package/package.json +1 -1
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
|
|
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 === '--
|
|
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