@tenderlead/cli 0.1.0
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/LICENSE +21 -0
- package/README.md +47 -0
- package/bin/tenderlead.js +86 -0
- package/package.json +19 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Studea Limited
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# TenderLead CLI
|
|
2
|
+
|
|
3
|
+
Read your TenderLead daily briefing from a terminal or another tool you control.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install --global @tenderlead/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Connect your account
|
|
12
|
+
|
|
13
|
+
Open the account page from the CLI:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
tenderlead login
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Sign in with Google or an emailed sign-in link, then create a token under
|
|
20
|
+
**API access**. Store it in the `TENDERLEAD_API_TOKEN` environment variable and
|
|
21
|
+
run the brief command:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
export TENDERLEAD_API_TOKEN="paste-token-here"
|
|
25
|
+
tenderlead brief
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Use JSON output for scripts and agents:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
tenderlead brief --json
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Tokens are customer-scoped, read-only, revocable, expire after 90 days, and are
|
|
35
|
+
subject to the account's current TenderLead plan. Do not commit a token to a
|
|
36
|
+
repository or paste it into a shared prompt. Revoke exposed tokens in Settings.
|
|
37
|
+
|
|
38
|
+
Provider acceptance and API responses do not prove email inbox delivery.
|
|
39
|
+
|
|
40
|
+
## Requirements
|
|
41
|
+
|
|
42
|
+
- Node.js 18 or newer
|
|
43
|
+
- An active TenderLead account
|
|
44
|
+
|
|
45
|
+
## Licence
|
|
46
|
+
|
|
47
|
+
MIT
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const DEFAULT_BASE_URL = 'https://tenderlead.co.uk';
|
|
4
|
+
const LOGIN_PATH = '/app/settings#api-access';
|
|
5
|
+
|
|
6
|
+
function usage() {
|
|
7
|
+
return [
|
|
8
|
+
'Usage:',
|
|
9
|
+
' tenderlead login',
|
|
10
|
+
' tenderlead brief [--json]',
|
|
11
|
+
'',
|
|
12
|
+
'Login opens TenderLead Settings so you can create a customer-scoped token.',
|
|
13
|
+
'Set TENDERLEAD_API_TOKEN to that token before running brief.',
|
|
14
|
+
].join('\n');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function parseArgs(argv) {
|
|
18
|
+
const command = argv[2];
|
|
19
|
+
if (command === 'login') return { command: 'login' };
|
|
20
|
+
if (command !== 'brief') throw new Error('Use the login or brief command.');
|
|
21
|
+
const options = { command: 'brief', json: false };
|
|
22
|
+
for (let index = 3; index < argv.length; index += 1) {
|
|
23
|
+
const arg = argv[index];
|
|
24
|
+
if (arg === '--json') options.json = true;
|
|
25
|
+
else if (arg === '--help' || arg === '-h') options.help = true;
|
|
26
|
+
else throw new Error(`Unknown argument: ${arg}`);
|
|
27
|
+
}
|
|
28
|
+
return options;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function openLogin(spawnImpl = require('node:child_process').spawnSync) {
|
|
32
|
+
const url = `${DEFAULT_BASE_URL}${LOGIN_PATH}`;
|
|
33
|
+
const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';
|
|
34
|
+
const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
35
|
+
const result = spawnImpl(command, args, { stdio: 'ignore' });
|
|
36
|
+
if (result.error || result.status !== 0) throw new Error(`Open ${url} in your browser.`);
|
|
37
|
+
return url;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function line(value, fallback) {
|
|
41
|
+
const text = String(value || '').trim();
|
|
42
|
+
return text || fallback;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function formatBrief(payload) {
|
|
46
|
+
const rows = Array.isArray(payload.opportunities) ? payload.opportunities : [];
|
|
47
|
+
const lines = ['# TenderLead daily brief', '', `Generated: ${line(payload.generated_at, 'Not recorded')}`, `Plan: ${line(payload.tier, 'Not recorded')}`, '', '## Opportunities', ''];
|
|
48
|
+
if (!rows.length) lines.push('No current scored opportunities.');
|
|
49
|
+
for (const row of rows) {
|
|
50
|
+
const score = Number.isFinite(Number(row.score)) ? ` ${Math.round(Number(row.score))}/100` : '';
|
|
51
|
+
lines.push(`- **${line(row.recommendation, 'alert').toUpperCase()}${score}** ${line(row.title, 'Untitled notice')}`);
|
|
52
|
+
lines.push(` ${line(row.buyer, 'Buyer not listed')} · ${line(row.source, 'Source not recorded')} · Deadline: ${line(row.deadline, 'Not recorded')}`);
|
|
53
|
+
if (Array.isArray(row.reasoning) && row.reasoning.length) lines.push(` Why: ${row.reasoning.slice(0, 3).map(String).join(' ')}`);
|
|
54
|
+
}
|
|
55
|
+
lines.push('', 'This response is current TenderLead data. It does not prove email inbox delivery or replace bid judgement.');
|
|
56
|
+
return lines.join('\n');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function fetchBrief(_options, fetchImpl = fetch) {
|
|
60
|
+
const token = process.env.TENDERLEAD_API_TOKEN;
|
|
61
|
+
if (!token) throw new Error('TENDERLEAD_API_TOKEN is required');
|
|
62
|
+
const response = await fetchImpl(`${DEFAULT_BASE_URL}/v1/briefs/daily`, {
|
|
63
|
+
headers: { Accept: 'application/json', Authorization: `Bearer ${token}` },
|
|
64
|
+
});
|
|
65
|
+
const body = await response.json().catch(() => ({}));
|
|
66
|
+
if (!response.ok) throw new Error(body.error || `TenderLead API failed: ${response.status}`);
|
|
67
|
+
return body;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function main() {
|
|
71
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) return console.log(usage());
|
|
72
|
+
const options = parseArgs(process.argv);
|
|
73
|
+
if (options.command === 'login') {
|
|
74
|
+
openLogin();
|
|
75
|
+
console.log('TenderLead Settings opened. Sign in, create a CLI token, then set TENDERLEAD_API_TOKEN in your shell.');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const brief = await fetchBrief(options);
|
|
79
|
+
console.log(options.json ? JSON.stringify(brief, null, 2) : formatBrief(brief));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (require.main === module) {
|
|
83
|
+
main().catch((error) => { console.error(error.message); process.exitCode = 1; });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = { parseArgs, openLogin, formatBrief, fetchBrief };
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tenderlead/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Read-only TenderLead daily briefing CLI",
|
|
6
|
+
"homepage": "https://tenderlead.co.uk",
|
|
7
|
+
"files": [
|
|
8
|
+
"bin",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
12
|
+
"bin": {
|
|
13
|
+
"tenderlead": "bin/tenderlead.js"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT"
|
|
19
|
+
}
|