@tenderlead/cli 0.4.2 → 0.5.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/README.md +97 -0
- package/bin/tenderlead.js +136 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -200,3 +200,100 @@ Provider acceptance and API responses do not prove email inbox delivery.
|
|
|
200
200
|
## Licence
|
|
201
201
|
|
|
202
202
|
MIT
|
|
203
|
+
|
|
204
|
+
## Multi-company pilot
|
|
205
|
+
|
|
206
|
+
The 0.5.0 candidate adds explicit company commands for enabled portfolio pilot
|
|
207
|
+
accounts. These commands require the shared company API to be deployed. Standard
|
|
208
|
+
Pro remains one company profile; Team access is agreed separately. Existing
|
|
209
|
+
single-profile commands keep their existing behaviour.
|
|
210
|
+
|
|
211
|
+
Start with the saved company IDs. A company name is never a selector:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
tenderlead companies list --json
|
|
215
|
+
tenderlead companies show COMPANY_UUID --json
|
|
216
|
+
tenderlead companies brief COMPANY_UUID --json
|
|
217
|
+
tenderlead companies matches COMPANY_UUID --json
|
|
218
|
+
tenderlead companies pipeline COMPANY_UUID --json
|
|
219
|
+
tenderlead companies matches all --json
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
`list` returns company metadata; `show COMPANY_UUID` returns that company's
|
|
223
|
+
full saved profile. `brief`, `matches` and `pipeline` return one page only:
|
|
224
|
+
10 items by default, with `--page-size 1` to `--page-size 25` supported.
|
|
225
|
+
Human output includes the total, whether more items exist, the next page and
|
|
226
|
+
snapshot token, plus a continuation command. `--json` preserves the same
|
|
227
|
+
`pagination` metadata unchanged. No command automatically fetches later pages.
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
tenderlead companies matches all --page-size 10 --json
|
|
231
|
+
# Use the returned pagination.snapshot for the next page:
|
|
232
|
+
tenderlead companies matches all --page 2 --page-size 10 --snapshot SNAPSHOT_TOKEN --json
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`--page`, `--page-size` and `--snapshot` apply only to those three read commands.
|
|
236
|
+
Page numbers start at 1. Pages after the first require the previous response's
|
|
237
|
+
64-character snapshot token and the same company selector, operation and page
|
|
238
|
+
size. If the server reports a changed snapshot (HTTP 409), restart at page 1
|
|
239
|
+
without `--snapshot`; do not combine pages from different snapshots. The CLI
|
|
240
|
+
reports the conflict and never retries or restarts automatically.
|
|
241
|
+
|
|
242
|
+
`all` is allowed only for `brief`, `matches` and `pipeline` reads. Combined
|
|
243
|
+
responses retain company IDs and names. Reads show saved results, revisions and
|
|
244
|
+
source state; they do not start matching or send alerts. `brief` is a saved
|
|
245
|
+
company brief, not proof of email delivery.
|
|
246
|
+
|
|
247
|
+
Create a token with the explicit company scopes. Existing tokens gain no scopes
|
|
248
|
+
automatically:
|
|
249
|
+
|
|
250
|
+
| Command | Required scopes |
|
|
251
|
+
| --- | --- |
|
|
252
|
+
| `list`, `show` | `companies:read`, `profile:read` |
|
|
253
|
+
| `brief` | `companies:read`, `briefs:read` |
|
|
254
|
+
| `matches` | `companies:read`, `tenders:read` |
|
|
255
|
+
| `pipeline` | `companies:read`, `pipeline:read` |
|
|
256
|
+
| `apply` | `companies:write`, `profile:write` |
|
|
257
|
+
| `save`, `update` | `companies:write`, `pipeline:write` |
|
|
258
|
+
| `review` | `companies:write`, `feedback:write` |
|
|
259
|
+
|
|
260
|
+
Writes first read the selected company for an identity check, so also retain
|
|
261
|
+
`companies:read` and `profile:read`. Review the saved company, notice or pipeline
|
|
262
|
+
record before preparing the JSON. Each write requires typed confirmation, or
|
|
263
|
+
`--yes` after review:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
tenderlead companies apply COMPANY_UUID --file company-changes.json
|
|
267
|
+
tenderlead companies save COMPANY_UUID --file save-notice.json
|
|
268
|
+
tenderlead companies update COMPANY_UUID ITEM_UUID --file pipeline-changes.json
|
|
269
|
+
tenderlead companies review COMPANY_UUID --file notice-review.json
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
`apply` accepts a flat partial company profile with the saved UUID
|
|
273
|
+
`expectedRevision`. Company fields follow the portfolio schema: `name`,
|
|
274
|
+
`websites`, `capabilities`, `keywords`, `industries`, `exclusions`, `limitations`,
|
|
275
|
+
`legal_name`, `company_number`, `team_size`, `specialisms`, `region`, `size_min`,
|
|
276
|
+
`size_max`, `frameworks`, `clearances`, `no_bid_rules`, `past_work` and `geography`.
|
|
277
|
+
This schema differs from the single-profile `profile apply` schema.
|
|
278
|
+
|
|
279
|
+
`save` requires `noticeKey`, UUID `expectedProfileRevision` and the exact
|
|
280
|
+
64-character `expectedSourceFingerprint` from the saved company notice. An
|
|
281
|
+
optional `legacyId` must be a UUID for an existing legacy record. It does not
|
|
282
|
+
create another company or run an assessment.
|
|
283
|
+
|
|
284
|
+
`update` requires UUID `expectedRevision` from the saved pipeline item plus
|
|
285
|
+
`stage` or `notes`. Stages are `watching`, `preparing`, `submitted`, `won` and
|
|
286
|
+
`lost`. Notes accept up to 10,000 characters.
|
|
287
|
+
|
|
288
|
+
`review` requires `noticeKey`, UUID `expectedProfileRevision`,
|
|
289
|
+
`expectedSourceFingerprint`, `decision`, `rationale` and `expectedRevision`.
|
|
290
|
+
Use `expectedRevision: null` for a first review, or the saved review UUID for an
|
|
291
|
+
update. Optional canonical fields are `sourceQuote`, `capability`, `nextAction`
|
|
292
|
+
and `gaps`; the server validates the decision and supporting source evidence.
|
|
293
|
+
|
|
294
|
+
All writes use the company UUID in the command. JSON must not contain an owner,
|
|
295
|
+
account or profile override, `notify`, `monitoring` or `archived`. Company
|
|
296
|
+
creation, deletion, archiving, alert preferences and refreshing are not exposed
|
|
297
|
+
by these commands. On a conflict or uncertain request outcome, reload the
|
|
298
|
+
company and affected record before deciding whether to retry. The CLI never
|
|
299
|
+
automatically retries a write.
|
package/bin/tenderlead.js
CHANGED
|
@@ -10,13 +10,18 @@ const PROFILE_FIELDS = [
|
|
|
10
10
|
'company_name', 'legal_name', 'company_number', 'title', 'website', 'team_size', 'specialisms',
|
|
11
11
|
'past_contracts', 'sectors', 'keywords', 'size_min', 'size_max', 'frameworks',
|
|
12
12
|
'clearance_capability', 'region', 'market_watch_enabled', 'market_watch_terms',
|
|
13
|
-
'market_watch_buyers', 'market_watch_note', 'us_readiness_profile', 'no_bid_rules',
|
|
13
|
+
'market_watch_buyers', 'market_watch_note', 'us_readiness_profile', 'eu_readiness_profile', 'no_bid_rules',
|
|
14
14
|
];
|
|
15
15
|
|
|
16
16
|
function usage() {
|
|
17
17
|
return [
|
|
18
18
|
'Usage:',
|
|
19
19
|
' tenderlead login',
|
|
20
|
+
' tenderlead companies list [--json]',
|
|
21
|
+
' tenderlead companies show <company-id> [--json]',
|
|
22
|
+
' tenderlead companies brief|matches|pipeline <company-id|all> [--page <N>] [--page-size <1-25>] [--snapshot <token>] [--json]',
|
|
23
|
+
' tenderlead companies apply|save|review <company-id> --file <path> [--yes] [--json]',
|
|
24
|
+
' tenderlead companies update <company-id> <item-id> --file <path> [--yes] [--json]',
|
|
20
25
|
' tenderlead whoami [--json]',
|
|
21
26
|
' tenderlead account status [--json]',
|
|
22
27
|
' tenderlead brief [--json]',
|
|
@@ -118,6 +123,7 @@ function parseArgs(argv) {
|
|
|
118
123
|
}
|
|
119
124
|
return options;
|
|
120
125
|
}
|
|
126
|
+
if (command === 'companies') return parseCompanyArgs(argv);
|
|
121
127
|
if (command === 'pipeline') return parsePipelineArgs(argv);
|
|
122
128
|
if (command === 'insights') return parseJsonOnlyArgs(argv, 3, { command: 'insights', json: false });
|
|
123
129
|
if (command === 'feedback') return parseFeedbackArgs(argv);
|
|
@@ -150,6 +156,124 @@ function parseArgs(argv) {
|
|
|
150
156
|
return options;
|
|
151
157
|
}
|
|
152
158
|
|
|
159
|
+
const COMPANY_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
160
|
+
const COMPANY_PAGED_READS = ['brief', 'matches', 'pipeline'];
|
|
161
|
+
const COMPANY_WRITES = ['apply', 'save', 'update', 'review'];
|
|
162
|
+
function companyId(id, allowAll = false) {
|
|
163
|
+
if ((allowAll && id === 'all') || (typeof id === 'string' && COMPANY_UUID.test(id))) return id;
|
|
164
|
+
throw new Error(`Use an explicit company UUID${allowAll ? ' or all for this read' : ''}. Run companies list for IDs.`);
|
|
165
|
+
}
|
|
166
|
+
function parseCompanyArgs(argv) {
|
|
167
|
+
const action = argv[3];
|
|
168
|
+
if (!['list', 'show', 'brief', 'matches', 'pipeline', ...COMPANY_WRITES].includes(action)) throw new Error('Use companies list, show, brief, matches, pipeline, apply, save, update or review.');
|
|
169
|
+
const write = COMPANY_WRITES.includes(action);
|
|
170
|
+
const paged = COMPANY_PAGED_READS.includes(action);
|
|
171
|
+
const options = { command: 'companies', action, json: false, yes: false, file: null };
|
|
172
|
+
let start = 4;
|
|
173
|
+
if (action !== 'list') { options.id = companyId(argv[start++], ['brief', 'matches', 'pipeline'].includes(action)); }
|
|
174
|
+
if (action === 'update') { options.itemId = companyId(argv[start++]); }
|
|
175
|
+
for (let index = start; index < argv.length; index += 1) {
|
|
176
|
+
const arg = argv[index];
|
|
177
|
+
if (arg === '--json') options.json = true;
|
|
178
|
+
else if (paged && ['--page', '--page-size', '--snapshot'].includes(arg)) {
|
|
179
|
+
const key = { '--page': 'page', '--page-size': 'pageSize', '--snapshot': 'snapshot' }[arg];
|
|
180
|
+
if (options[key] !== undefined) throw new Error(`Duplicate argument: ${arg}`);
|
|
181
|
+
options[key] = optionValue(argv, index, arg); index += 1;
|
|
182
|
+
}
|
|
183
|
+
else if (write && arg === '--yes') options.yes = true;
|
|
184
|
+
else if (write && arg === '--file' && !options.file) { options.file = optionValue(argv, index, arg); index += 1; }
|
|
185
|
+
else throw new Error(`Unknown argument: ${arg}`);
|
|
186
|
+
}
|
|
187
|
+
if (paged) Object.assign(options, companyPagination(options));
|
|
188
|
+
if (write && !options.file) throw new Error(`companies ${action} requires --file <path>`);
|
|
189
|
+
return options;
|
|
190
|
+
}
|
|
191
|
+
function companyPagination(options) {
|
|
192
|
+
const pageText = String(options.page ?? 1), sizeText = String(options.pageSize ?? 10);
|
|
193
|
+
if (!/^[1-9][0-9]*$/.test(pageText) || !Number.isSafeInteger(Number(pageText))) throw new Error('--page must be a positive integer.');
|
|
194
|
+
if (!/^[1-9][0-9]*$/.test(sizeText) || Number(sizeText) > 25) throw new Error('--page-size must be an integer from 1 to 25.');
|
|
195
|
+
const snapshot = options.snapshot;
|
|
196
|
+
if (snapshot !== undefined && (typeof snapshot !== 'string' || !/^[0-9a-f]{64}$/.test(snapshot))) throw new Error('--snapshot must be the saved 64-character snapshot token.');
|
|
197
|
+
if (Number(pageText) > 1 && !snapshot) throw new Error('--page greater than 1 requires --snapshot from the previous response.');
|
|
198
|
+
return { page: Number(pageText), pageSize: Number(sizeText), ...(snapshot ? { snapshot } : {}) };
|
|
199
|
+
}
|
|
200
|
+
function validateCompanyBody(action, body) {
|
|
201
|
+
if (!isPlainObject(body)) throw new Error('Company JSON must be an object.');
|
|
202
|
+
const fields = {
|
|
203
|
+
apply: ['expectedRevision', 'name', 'websites', 'capabilities', 'keywords', 'industries', 'exclusions', 'limitations', 'legal_name', 'company_number', 'team_size', 'specialisms', 'region', 'size_min', 'size_max', 'frameworks', 'clearances', 'no_bid_rules', 'past_work', 'geography'],
|
|
204
|
+
save: ['noticeKey', 'expectedProfileRevision', 'expectedSourceFingerprint', 'legacyId'],
|
|
205
|
+
update: ['expectedRevision', 'stage', 'notes'],
|
|
206
|
+
review: ['noticeKey', 'expectedProfileRevision', 'expectedSourceFingerprint', 'expectedRevision', 'decision', 'rationale', 'sourceQuote', 'capability', 'nextAction', 'gaps'],
|
|
207
|
+
}[action];
|
|
208
|
+
if (!fields || Object.keys(body).some(key => !fields.includes(key))) throw new Error('Unsupported company field. Account/profile overrides, alerts and monitoring changes are not accepted.');
|
|
209
|
+
const uuid = key => { if (typeof body[key] !== 'string' || !COMPANY_UUID.test(body[key])) throw new Error(`${key} must be the saved UUID revision.`); };
|
|
210
|
+
if (['apply', 'update'].includes(action)) uuid('expectedRevision');
|
|
211
|
+
if (['save', 'review'].includes(action)) {
|
|
212
|
+
uuid('expectedProfileRevision');
|
|
213
|
+
if (typeof body.noticeKey !== 'string' || !body.noticeKey || body.noticeKey.length > 1000) throw new Error('noticeKey must identify a saved company notice.');
|
|
214
|
+
if (typeof body.expectedSourceFingerprint !== 'string' || !/^[0-9a-f]{64}$/.test(body.expectedSourceFingerprint)) throw new Error('expectedSourceFingerprint must be the saved source fingerprint.');
|
|
215
|
+
}
|
|
216
|
+
if (action === 'save' && body.legacyId !== undefined) uuid('legacyId');
|
|
217
|
+
if (action === 'review' && body.expectedRevision !== null) uuid('expectedRevision');
|
|
218
|
+
if (action === 'update') {
|
|
219
|
+
if (!('stage' in body) && !('notes' in body)) throw new Error('Include stage or notes.');
|
|
220
|
+
if ('stage' in body && !['watching', 'preparing', 'submitted', 'won', 'lost'].includes(body.stage)) throw new Error('Use a valid pipeline stage.');
|
|
221
|
+
if ('notes' in body && (typeof body.notes !== 'string' || body.notes.length > 10000)) throw new Error('notes must be text of at most 10,000 characters.');
|
|
222
|
+
}
|
|
223
|
+
return body;
|
|
224
|
+
}
|
|
225
|
+
async function companyRequest(options, body, fetchImpl = fetch) {
|
|
226
|
+
const { action } = options;
|
|
227
|
+
const id = action === 'list' ? null : companyId(options.id, ['brief', 'matches', 'pipeline'].includes(action));
|
|
228
|
+
const base = '/v1/companies' + (id ? `/${encodeURIComponent(id)}` : '');
|
|
229
|
+
if (!COMPANY_WRITES.includes(action)) {
|
|
230
|
+
const paged = COMPANY_PAGED_READS.includes(action);
|
|
231
|
+
const params = paged ? companyPagination(options) : null;
|
|
232
|
+
const query = params ? new URLSearchParams({ page: String(params.page), page_size: String(params.pageSize), ...(params.snapshot ? { snapshot: params.snapshot } : {}) }) : null;
|
|
233
|
+
try { return await apiRequest(base + (['list', 'show'].includes(action) ? '' : `/${action}`) + (query ? `?${query}` : ''), {}, fetchImpl); }
|
|
234
|
+
catch (error) {
|
|
235
|
+
if (paged && error.status === 409) throw new Error(`${error.message} Restart at --page 1 without --snapshot; do not reuse the previous pages.`);
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
validateCompanyBody(action, body);
|
|
240
|
+
const suffix = action === 'apply' ? '' : action === 'review' ? '/reviews' : '/pipeline' + (action === 'update' ? `/${companyId(options.itemId)}` : '');
|
|
241
|
+
return apiRequest(base + suffix, { method: ['apply', 'update'].includes(action) ? 'PATCH' : 'POST', body: JSON.stringify(body) }, fetchImpl);
|
|
242
|
+
}
|
|
243
|
+
function companyText(value, fallback = '') {
|
|
244
|
+
return line(String(value ?? '').replace(/[\u0000-\u001f\u007f-\u009f]/g, ' '), fallback);
|
|
245
|
+
}
|
|
246
|
+
function formatCompanies(payload, options) {
|
|
247
|
+
const profiles = payload.profiles || (payload.profile ? [payload.profile] : []);
|
|
248
|
+
const heading = options.action === 'list' ? '# TenderLead companies' : `# Company ${companyText(options.id)} · ${companyText(options.action)}`;
|
|
249
|
+
const lines = [heading];
|
|
250
|
+
for (const profile of profiles) lines.push(`Company: ${companyText(profile.name, 'Unnamed')} · ${companyText(profile.id, 'unknown')} · revision ${companyText(profile.revision, 'unknown')}`);
|
|
251
|
+
// Retain every saved field, source check and company label in text output too.
|
|
252
|
+
lines.push(JSON.stringify(payload, null, 2));
|
|
253
|
+
if (payload.pagination) {
|
|
254
|
+
const page = payload.pagination;
|
|
255
|
+
lines.push(`Page: ${companyText(page.page)} · Total: ${companyText(page.total_count)} · Has more: ${Boolean(page.has_more)} · Next page: ${companyText(page.next_page, 'none')} · Snapshot: ${companyText(page.snapshot)}`);
|
|
256
|
+
if (page.has_more && Number.isSafeInteger(page.next_page) && page.next_page > 1 && /^[0-9a-f]{64}$/.test(page.snapshot) && COMPANY_PAGED_READS.includes(options.action)) {
|
|
257
|
+
lines.push(`Continue: tenderlead companies ${options.action} ${companyId(options.id, true)} --page ${page.next_page} --page-size ${companyPagination({ pageSize: page.page_size }).pageSize} --snapshot ${page.snapshot}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return lines.join('\n');
|
|
261
|
+
}
|
|
262
|
+
async function runCompanyCommand(options, dependencies = {}) {
|
|
263
|
+
const fetchImpl = dependencies.fetch || fetch;
|
|
264
|
+
if (!COMPANY_WRITES.includes(options.action)) return companyRequest(options, undefined, fetchImpl);
|
|
265
|
+
companyId(options.id);
|
|
266
|
+
if (options.action === 'update') companyId(options.itemId);
|
|
267
|
+
const body = validateCompanyBody(options.action, (dependencies.read || readJsonFile)(options.file));
|
|
268
|
+
apiToken();
|
|
269
|
+
const current = await companyRequest({ action: 'show', id: options.id }, undefined, fetchImpl);
|
|
270
|
+
const profile = current.profile;
|
|
271
|
+
if (!profile || profile.id !== options.id) throw new Error('Company identity could not be confirmed. Reload companies list.');
|
|
272
|
+
const message = `${options.action} for company ${companyText(profile.name, 'Unnamed')} (${options.id})${options.itemId ? `, pipeline item ${options.itemId}` : ''}.`;
|
|
273
|
+
if (!(await (dependencies.confirm || requireWriteConfirmation)(options, options.action.toUpperCase(), message))) return { unchanged: true, profileId: options.id, companyName: profile.name };
|
|
274
|
+
return companyRequest(options, body, fetchImpl);
|
|
275
|
+
}
|
|
276
|
+
|
|
153
277
|
function optionValue(argv, index, flag) {
|
|
154
278
|
const value = argv[index + 1];
|
|
155
279
|
if (!value || value.startsWith('--')) throw new Error(`${flag} requires a value`);
|
|
@@ -392,7 +516,7 @@ function formatBrief(payload) {
|
|
|
392
516
|
const lines = ['# TenderLead daily brief', '', `Generated: ${line(payload.generated_at, 'Not recorded')}`, `Plan: ${line(payload.tier, 'Not recorded')}`, '', '## Opportunities', ''];
|
|
393
517
|
if (!rows.length) lines.push('No current scored opportunities.');
|
|
394
518
|
for (const row of rows) {
|
|
395
|
-
const score = Number.isFinite(Number(row.score)) ? ` ${Math.round(Number(row.score))}/100` : '';
|
|
519
|
+
const score = row.score != null && row.score !== '' && Number.isFinite(Number(row.score)) ? ` ${Math.round(Number(row.score))}/100` : '';
|
|
396
520
|
lines.push(`- **${line(row.recommendation, 'alert').toUpperCase()}${score}** ${line(row.title, 'Untitled notice')}`);
|
|
397
521
|
lines.push(` ${line(row.buyer, 'Buyer not listed')} · ${line(row.source, 'Source not recorded')} · Deadline: ${line(row.deadline, 'Not recorded')}`);
|
|
398
522
|
if (Array.isArray(row.reasoning) && row.reasoning.length) lines.push(` Why: ${row.reasoning.slice(0, 3).map(String).join(' ')}`);
|
|
@@ -418,7 +542,8 @@ function formatTenders(payload) {
|
|
|
418
542
|
}
|
|
419
543
|
|
|
420
544
|
function formatScore(payload) {
|
|
421
|
-
|
|
545
|
+
if (payload.status === 'processing') return `Fit check in progress. Check its status with GET ${line(payload.status_url, '/v1/tenders/' + encodeURIComponent(payload.contract_id) + '/score')}.`;
|
|
546
|
+
const score = payload.score != null && payload.score !== '' && Number.isFinite(Number(payload.score)) ? `${Math.round(Number(payload.score))}/100` : 'not recorded';
|
|
422
547
|
const lines = [`# TenderLead tender score`, '', `Contract: ${line(payload.contract_id, 'Not recorded')}`, `Recommendation: ${line(payload.recommendation, 'Not recorded').toUpperCase()}`, `Score: ${score}`];
|
|
423
548
|
if (Array.isArray(payload.reasoning) && payload.reasoning.length) lines.push('', 'Why:', ...payload.reasoning.map(item => `- ${String(item)}`));
|
|
424
549
|
if (payload.no_bid_reason) lines.push('', `No-bid reason: ${payload.no_bid_reason}`);
|
|
@@ -443,7 +568,11 @@ async function apiRequest(path, options = {}, fetchImpl = fetch) {
|
|
|
443
568
|
},
|
|
444
569
|
});
|
|
445
570
|
const body = await response.json().catch(() => ({}));
|
|
446
|
-
if (!response.ok)
|
|
571
|
+
if (!response.ok) {
|
|
572
|
+
const error = new Error(body.error || `TenderLead API failed: ${response.status}`);
|
|
573
|
+
error.status = response.status;
|
|
574
|
+
throw error;
|
|
575
|
+
}
|
|
447
576
|
return body;
|
|
448
577
|
}
|
|
449
578
|
|
|
@@ -614,6 +743,7 @@ function validateProfile(profile) {
|
|
|
614
743
|
}
|
|
615
744
|
if ('market_watch_enabled' in profile && typeof profile.market_watch_enabled !== 'boolean') throw new Error('market_watch_enabled must be a boolean');
|
|
616
745
|
if ('us_readiness_profile' in profile && !isPlainObject(profile.us_readiness_profile)) throw new Error('us_readiness_profile must be an object');
|
|
746
|
+
if ('eu_readiness_profile' in profile && !isPlainObject(profile.eu_readiness_profile)) throw new Error('eu_readiness_profile must be an object');
|
|
617
747
|
for (const field of ['size_min', 'size_max']) {
|
|
618
748
|
if (field in profile && !Number.isFinite(Number(profile[field]))) throw new Error(`${field} must be a number`);
|
|
619
749
|
}
|
|
@@ -733,6 +863,7 @@ async function main() {
|
|
|
733
863
|
console.log('TenderLead Settings opened. Sign in, create the required scoped token, then set TENDERLEAD_API_TOKEN in your shell.');
|
|
734
864
|
return;
|
|
735
865
|
}
|
|
866
|
+
if (options.command === 'companies') return printPayload(await runCompanyCommand(options), options.json, payload => formatCompanies(payload, options));
|
|
736
867
|
if (options.command === 'whoami') return printPayload(await fetchWhoami(), options.json, formatWhoami);
|
|
737
868
|
if (options.command === 'account') return printPayload(await fetchAccountStatus(), options.json, formatAccountStatus);
|
|
738
869
|
if (options.command === 'brief') {
|
|
@@ -868,6 +999,7 @@ if (require.main === module) {
|
|
|
868
999
|
}
|
|
869
1000
|
|
|
870
1001
|
module.exports = {
|
|
1002
|
+
parseCompanyArgs, validateCompanyBody, companyRequest, formatCompanies, runCompanyCommand,
|
|
871
1003
|
parseArgs, openLogin, fetchWhoami, fetchAccountStatus, formatWhoami, formatAccountStatus, formatBrief, formatTenders, formatScore, fetchBrief, searchTenders, scoreTender, fetchProfile, updateProfile,
|
|
872
1004
|
validateProfile, readProfileFile, profileTemplate, profileDiff, formatProfile,
|
|
873
1005
|
formatProfileDiff, confirmApply, confirmWord, readJsonFile, contentTypeForFile, appendFiles,
|
package/package.json
CHANGED