tor-to-proposal 2.0.1 → 2.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/SKILL.md +7 -3
- package/bin/bid-pack.mjs +113 -3
- package/bin/profile.mjs +2 -1
- package/bin/tor-to-proposal.mjs +2 -2
- package/docs/USER-GUIDE.md +79 -0
- package/mcp/server.mjs +1 -1
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -82,9 +82,13 @@ Go/no-go is THEIR call. If no-go: stop and say so.
|
|
|
82
82
|
### Phase 2 — The ONE questionnaire (no scattered questions, ever)
|
|
83
83
|
|
|
84
84
|
`out/questions.md` consolidates every open decision: pricing, availability,
|
|
85
|
-
validity, draft `[FILL]`s, CV evidence.
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
validity, draft `[FILL]`s, CV evidence. Two routes, in order of preference:
|
|
86
|
+
|
|
87
|
+
1. **Interactive wizard (non-technical users):** `node bin/bid-pack.mjs ask
|
|
88
|
+
--dir <bid-dir>` — asks each open question in the terminal, saves
|
|
89
|
+
answers.json and applies it. Run again; it only asks what is still open.
|
|
90
|
+
2. **Assistant-mediated:** put all questions to the user in one message,
|
|
91
|
+
never answer one for the user, then:
|
|
88
92
|
|
|
89
93
|
```bash
|
|
90
94
|
# write the answers verbatim:
|
package/bin/bid-pack.mjs
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
//
|
|
6
6
|
// start --tor <file> [--cv <file>] [--dir bid/] extract + screen + cv-gap
|
|
7
7
|
// + cv-tailor build + profile import -> questions.json/questions.md
|
|
8
|
+
// ask [--dir bid/] interactive wizard: answers
|
|
9
|
+
// the open questions one at a time in the terminal, writes answers.json
|
|
10
|
+
// and applies them (no editor or JSON knowledge needed)
|
|
8
11
|
// apply --answers answers.json [--dir bid/] store answers, run pricing,
|
|
9
12
|
// record evidence + fill answers, regenerate the open-question list
|
|
10
13
|
// pack [--dir bid/] [--fallback-pdf] [--no-zip] verify + audit + zip (-> package.mjs)
|
|
@@ -14,6 +17,7 @@
|
|
|
14
17
|
import fs from 'node:fs';
|
|
15
18
|
import path from 'node:path';
|
|
16
19
|
import crypto from 'node:crypto';
|
|
20
|
+
import readline from 'node:readline/promises';
|
|
17
21
|
import { spawnSync } from 'node:child_process';
|
|
18
22
|
import { fileURLToPath } from 'node:url';
|
|
19
23
|
import { fail, parseArgs, readText, writeOut, helpText, mdTable } from './lib.mjs';
|
|
@@ -21,19 +25,23 @@ import { loadProfile, profilePath } from './profile.mjs';
|
|
|
21
25
|
|
|
22
26
|
const HELP = helpText('bid-pack', [
|
|
23
27
|
'start --tor <file.(pdf|docx|txt)> [--cv <file>] [--dir bid/] [--profile] [--no-cv-tailor]',
|
|
28
|
+
'ask [--dir bid/]',
|
|
24
29
|
'apply --answers answers.json [--dir bid/]',
|
|
25
30
|
'pack [--dir bid/] [--fallback-pdf] [--no-zip]',
|
|
26
31
|
'',
|
|
27
32
|
'start runs every mechanical step (pdf-extract -> extract -> cv-gap -> cv-tailor)',
|
|
28
33
|
'and consolidates ALL open questions into out/questions.md. Answer them in one go,',
|
|
29
34
|
'have your assistant write answers.json, then apply. Repeat apply until 0 open.',
|
|
35
|
+
'ask is the human-friendly route: an interactive wizard that asks each open',
|
|
36
|
+
'question in the terminal (Enter = suggested default, "back" to redo, "quit" to',
|
|
37
|
+
'cancel), saves answers.json and applies it — no editor or JSON needed.',
|
|
30
38
|
'pack verifies the final documents (re-extract + re-audit) and builds the zip.',
|
|
31
39
|
]);
|
|
32
40
|
|
|
33
41
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
34
42
|
const args = parseArgs(process.argv.slice(2));
|
|
35
43
|
const cmd = args._[0];
|
|
36
|
-
if (args.__help || !['start', 'apply', 'pack'].includes(cmd)) {
|
|
44
|
+
if (args.__help || !['start', 'ask', 'apply', 'pack'].includes(cmd)) {
|
|
37
45
|
process.stdout.write(HELP);
|
|
38
46
|
process.exit(args.__help ? 0 : 1);
|
|
39
47
|
}
|
|
@@ -144,6 +152,21 @@ function writeQuestions(q) {
|
|
|
144
152
|
writeOut(path.join(outDir, 'questions.md'), md);
|
|
145
153
|
}
|
|
146
154
|
|
|
155
|
+
// Human-readable PDF companions for the working files a non-technical user is
|
|
156
|
+
// most likely to be pointed at (regenerated after start/apply; the .md stays
|
|
157
|
+
// the source of truth — the PDFs are for reading, not editing).
|
|
158
|
+
function renderPreviews() {
|
|
159
|
+
for (const [file, label] of [
|
|
160
|
+
['bid-screen.md', 'Bid-Screen'],
|
|
161
|
+
['questions.md', 'Questions'],
|
|
162
|
+
['cv-gap-report.md', 'CV-Gap-Report'],
|
|
163
|
+
['cv-tailor-report.md', 'CV-Tailor-Report'],
|
|
164
|
+
]) {
|
|
165
|
+
if (!inDir(file)) continue;
|
|
166
|
+
run('render.mjs', [path.join(outDir, file), '--out', path.join(outDir, 'preview', `${label}.pdf`), '--footer', `preview of out/${file} — the .md file is the source of truth`], { label: `render preview ${label}` });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
147
170
|
// ---------------- context ----------------
|
|
148
171
|
function writeContext() {
|
|
149
172
|
const extractPath = inDir('tor-extract.json');
|
|
@@ -214,6 +237,7 @@ function start() {
|
|
|
214
237
|
writeQuestions(q);
|
|
215
238
|
state.steps.questions = { status: 'done', open: q.openCount };
|
|
216
239
|
writeState(state);
|
|
240
|
+
renderPreviews();
|
|
217
241
|
|
|
218
242
|
// report
|
|
219
243
|
const extract = JSON.parse(fs.readFileSync(path.join(outDir, 'tor-extract.json'), 'utf8'));
|
|
@@ -226,7 +250,8 @@ function start() {
|
|
|
226
250
|
`- bid-screen.md — go/no-go decision (the USER decides; read it together)\n` +
|
|
227
251
|
`- compliance-matrix.md, tor-extract.json\n` +
|
|
228
252
|
(args.cv ? `- cv-gap-report.md, cv-tailored.md (+ report)\n` : '') +
|
|
229
|
-
`- questions.json / questions.md — **${q.items.length} open question(s)**\n
|
|
253
|
+
`- questions.json / questions.md — **${q.items.length} open question(s)**\n` +
|
|
254
|
+
`- preview/ — readable PDFs of the bid screen + questionnaire (just double-click)\n\n` +
|
|
230
255
|
`## Next\n1. Review bid-screen.md with the user. No-go -> stop here.\n` +
|
|
231
256
|
`2. Put every open question to the user IN ONE MESSAGE (questions.md has them).\n` +
|
|
232
257
|
`3. Write answers.json {"Q-...": value, ...} -> bid-pack apply --answers answers.json --dir ${dir}\n` +
|
|
@@ -238,7 +263,11 @@ function start() {
|
|
|
238
263
|
|
|
239
264
|
// ---------------- apply ----------------
|
|
240
265
|
function apply() {
|
|
241
|
-
if (!args.answers) {
|
|
266
|
+
if (!args.answers) {
|
|
267
|
+
process.stdout.write(HELP);
|
|
268
|
+
process.stdout.write(`\nTip: run "bid-pack ask --dir ${dir}" to answer the open questions interactively in this terminal — no JSON file needed.\n`);
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
242
271
|
const answersPath = path.resolve(String(args.answers));
|
|
243
272
|
const answers = JSON.parse(readText(answersPath, '--answers'));
|
|
244
273
|
const qPath = inDir('questions.json');
|
|
@@ -326,6 +355,7 @@ function apply() {
|
|
|
326
355
|
state.steps.questions = { status: 'done', open: fresh.openCount };
|
|
327
356
|
state.steps.pricing = { status: inDir('pricing.json') ? 'done' : 'awaiting-user-numbers' };
|
|
328
357
|
writeState(state);
|
|
358
|
+
renderPreviews();
|
|
329
359
|
|
|
330
360
|
process.stdout.write(
|
|
331
361
|
`# Apply: ${applied} answer(s) recorded, ${fresh.openCount} still open\n\n` +
|
|
@@ -335,6 +365,85 @@ function apply() {
|
|
|
335
365
|
);
|
|
336
366
|
}
|
|
337
367
|
|
|
368
|
+
// ---------------- ask (interactive question wizard) ----------------
|
|
369
|
+
async function askWizard() {
|
|
370
|
+
const qPath = inDir('questions.json');
|
|
371
|
+
if (!qPath) fail(`no questions.json in ${dir}/out — run: bid-pack start --tor <file> --dir ${dir}`, 2);
|
|
372
|
+
const q = JSON.parse(fs.readFileSync(qPath, 'utf8'));
|
|
373
|
+
const yours = q.items.filter((i) => i.type !== 'client');
|
|
374
|
+
const clients = q.items.filter((i) => i.type === 'client');
|
|
375
|
+
if (!yours.length) {
|
|
376
|
+
process.stdout.write(
|
|
377
|
+
(clients.length
|
|
378
|
+
? `Nothing for you to answer here — but send these to the CLIENT before the cutoff:\n${clients.map((c) => `- ${c.question}`).join('\n')}\n`
|
|
379
|
+
: 'No open questions — everything is already resolved.\n') +
|
|
380
|
+
(inDir('pricing.json') ? `\nNext: bid-pack pack --dir ${dir} [--fallback-pdf]\n` : '')
|
|
381
|
+
);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Same terminal behavior as the profile wizard: interactive when run by a
|
|
386
|
+
// human; piped lines are consumed in order (automation/tests).
|
|
387
|
+
const piped = !process.stdin.isTTY;
|
|
388
|
+
let pipedLines = [];
|
|
389
|
+
if (piped) {
|
|
390
|
+
try {
|
|
391
|
+
const raw = fs.readFileSync(0, 'utf8');
|
|
392
|
+
pipedLines = raw.trim() ? raw.split('\n').map((l) => l.trim()) : [];
|
|
393
|
+
} catch { /* empty stdin */ }
|
|
394
|
+
}
|
|
395
|
+
let pipedPos = 0;
|
|
396
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
397
|
+
const askLine = async (prompt) => {
|
|
398
|
+
if (piped) return pipedPos < pipedLines.length ? pipedLines[pipedPos++] : 'EOF';
|
|
399
|
+
try {
|
|
400
|
+
return (await rl.question(prompt)).trim();
|
|
401
|
+
} catch (err) {
|
|
402
|
+
if (err && (err.code === 'ABORT_ERR' || err.name === 'AbortError')) return 'EOF';
|
|
403
|
+
throw err;
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
process.stdout.write(
|
|
408
|
+
`Bid questions — ${yours.length} to answer, one at a time.\n` +
|
|
409
|
+
'Press Enter to accept the [suggested] value when one is shown. Type "back" to redo the previous question, "quit" to cancel.\n\n'
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
const answers = {};
|
|
413
|
+
let i = 0;
|
|
414
|
+
while (i < yours.length) {
|
|
415
|
+
const item = yours[i];
|
|
416
|
+
const cur = item.default !== undefined && item.default !== null ? String(item.default) : '';
|
|
417
|
+
const tail = item.unit ? ` (${item.unit})` : '';
|
|
418
|
+
const a = await askLine(`(${i + 1}/${yours.length}) ${item.question}${tail}${cur ? ` [${cur}]` : ''}\n> `);
|
|
419
|
+
if (a === 'EOF' || a.toLowerCase() === 'quit') {
|
|
420
|
+
rl.close();
|
|
421
|
+
process.stdout.write('\nCancelled — nothing was saved. (Your documents are untouched; run bid-pack ask again anytime.)\n');
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (a.toLowerCase() === 'back') {
|
|
425
|
+
if (i === 0) { process.stdout.write(' (already at the first question)\n'); continue; }
|
|
426
|
+
const prev = yours[i - 1];
|
|
427
|
+
delete answers[prev.id];
|
|
428
|
+
i -= 1;
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (a === '' && cur === '') {
|
|
432
|
+
process.stdout.write(' This one needs an answer from you (no suggested default). Type it, or "quit" to stop.\n');
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
answers[item.id] = a === '' ? item.default : a;
|
|
436
|
+
i += 1;
|
|
437
|
+
}
|
|
438
|
+
rl.close();
|
|
439
|
+
|
|
440
|
+
const answersPath = path.join(dir, 'answers.json');
|
|
441
|
+
fs.writeFileSync(answersPath, JSON.stringify(answers, null, 2));
|
|
442
|
+
process.stdout.write(`\nSaved your ${Object.keys(answers).length} answer(s) to ${answersPath}\nApplying them now...\n\n`);
|
|
443
|
+
args.answers = answersPath;
|
|
444
|
+
apply();
|
|
445
|
+
}
|
|
446
|
+
|
|
338
447
|
// ---------------- pack ----------------
|
|
339
448
|
function pack() {
|
|
340
449
|
const passThrough = ['--dir', dir, ...(args['fallback-pdf'] ? ['--fallback-pdf'] : []), ...(args['no-zip'] ? ['--no-zip'] : []), ...(args.profile ? ['--profile', String(args.profile)] : [])];
|
|
@@ -343,5 +452,6 @@ function pack() {
|
|
|
343
452
|
}
|
|
344
453
|
|
|
345
454
|
if (cmd === 'start') start();
|
|
455
|
+
else if (cmd === 'ask') await askWizard();
|
|
346
456
|
else if (cmd === 'apply') apply();
|
|
347
457
|
else pack();
|
package/bin/profile.mjs
CHANGED
|
@@ -127,7 +127,8 @@ async function wizard() {
|
|
|
127
127
|
let pipedLines = [];
|
|
128
128
|
if (piped) {
|
|
129
129
|
try {
|
|
130
|
-
|
|
130
|
+
const raw = fs.readFileSync(0, 'utf8');
|
|
131
|
+
pipedLines = raw.trim() ? raw.split('\n').map((l) => l.trim()) : [];
|
|
131
132
|
} catch { /* empty stdin -> quit below */ }
|
|
132
133
|
}
|
|
133
134
|
let pipedPos = 0;
|
package/bin/tor-to-proposal.mjs
CHANGED
|
@@ -39,14 +39,14 @@ const DESCRIPTIONS = {
|
|
|
39
39
|
audit: 'pre-submission hard-fail gate',
|
|
40
40
|
render: 'fallback markdown -> PDF emitter (no document tooling needed)',
|
|
41
41
|
package: 'verify final docs (re-extract + re-audit) -> pack/ + checklist + zip',
|
|
42
|
-
'bid-pack': 'one-shot orchestrator: start | apply | pack + the consolidated questionnaire',
|
|
42
|
+
'bid-pack': 'one-shot orchestrator: start | ask | apply | pack + the consolidated questionnaire',
|
|
43
43
|
profile: 'consultant profile wizard (.tor-to-proposal/profile.json in the current folder)',
|
|
44
44
|
init: 'alias for profile — one-time setup wizard',
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
48
48
|
if (!cmd || cmd === 'help' || cmd === '--help') {
|
|
49
|
-
process.stdout.write(`tor-to-proposal — the bid tool that refuses to lie.\n\nUsage: tor-to-proposal <command> [options]\n\nQuick start (plug and play):\n tor-to-proposal init one-time profile + setup wizard\n tor-to-proposal bid-pack start --tor tor.pdf --cv cv.txt --dir mybid/\n tor-to-proposal bid-pack
|
|
49
|
+
process.stdout.write(`tor-to-proposal — the bid tool that refuses to lie.\n\nUsage: tor-to-proposal <command> [options]\n\nQuick start (plug and play):\n tor-to-proposal init one-time profile + setup wizard\n tor-to-proposal bid-pack start --tor tor.pdf --cv cv.txt --dir mybid/\n tor-to-proposal bid-pack ask --dir mybid/ answer open questions interactively\n tor-to-proposal bid-pack pack --dir mybid/ --fallback-pdf\n\nCommands:\n${Object.keys(TOOLS).map((k) => ` ${k.padEnd(19)} ${DESCRIPTIONS[k]}`).join('\n')}\n\nPer-command help: tor-to-proposal <command> --help\nAgent workflow: see SKILL.md\n`);
|
|
50
50
|
process.exit(cmd ? 0 : 1);
|
|
51
51
|
}
|
|
52
52
|
if (!TOOLS[cmd]) {
|
package/docs/USER-GUIDE.md
CHANGED
|
@@ -193,6 +193,82 @@ you will ever own.
|
|
|
193
193
|
|
|
194
194
|
---
|
|
195
195
|
|
|
196
|
+
## 6b. How to answer the questions (`out/questions.md` → `answers.json`)
|
|
197
|
+
|
|
198
|
+
After `bid-pack start`, the tool writes **`out/questions.md`** — a numbered
|
|
199
|
+
table of everything it cannot decide for you (your price confirmation, effort
|
|
200
|
+
days, availability date, and every `[FILL]` placeholder it found).
|
|
201
|
+
|
|
202
|
+
**You do not edit questions.md itself.** You answer in one of three ways:
|
|
203
|
+
|
|
204
|
+
### Way 1 (easiest, no editor needed): the terminal wizard
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
npx tor-to-proposal bid-pack ask --dir .
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
It asks every open question **one at a time, right in the Terminal** — exactly
|
|
211
|
+
like the `init` wizard. Press Enter to accept a suggested value, type `back`
|
|
212
|
+
to redo the previous question, `quit` to stop. At the end it saves your
|
|
213
|
+
answers and applies them automatically. Run it again anytime; it only asks
|
|
214
|
+
what is still open.
|
|
215
|
+
|
|
216
|
+
### Way 2: reply to your AI assistant
|
|
217
|
+
questions.md ends with the exact instruction. Just type your numbered answers
|
|
218
|
+
in the chat, e.g.:
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
1. yes
|
|
222
|
+
2. 25 person-days
|
|
223
|
+
3. 1 November 2026
|
|
224
|
+
4. 90 days from submission
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The assistant writes them into `answers.json` for you and runs
|
|
228
|
+
`bid-pack apply`.
|
|
229
|
+
|
|
230
|
+
### Way 3 (by hand): write answers.json yourself
|
|
231
|
+
Create a file called `answers.json` **in your bid folder** (next to
|
|
232
|
+
`tor.pdf`). It is a simple JSON object: **question ID → your answer**.
|
|
233
|
+
Copy the IDs exactly from `out/questions.json` or the **bold** codes in
|
|
234
|
+
questions.md. A complete sample:
|
|
235
|
+
|
|
236
|
+
```json
|
|
237
|
+
{
|
|
238
|
+
"Q-PRICING-CONFIRM": "yes",
|
|
239
|
+
"Q-EFFORT-DAYS": "25 person-days",
|
|
240
|
+
"Q-AVAILABILITY": "1 November 2026",
|
|
241
|
+
"Q-VALIDITY": "90 days from submission",
|
|
242
|
+
"Q-FILL-TAXES": "Prices exclude VAT; consultant is registered with FBR.",
|
|
243
|
+
"Q-FILL-REIMBURSABLES": "Travel and interpretation costs are reimbursable at cost against receipts.",
|
|
244
|
+
"Q-CVE-1": "Led three UNDP M&E assignments (2022–2025) as team lead.",
|
|
245
|
+
"Q-CLAR-1": "Requested the final evaluation grid by email on 3 Oct 2026."
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
What the special IDs mean:
|
|
250
|
+
|
|
251
|
+
| ID pattern | Meaning | What to answer |
|
|
252
|
+
| --- | --- | --- |
|
|
253
|
+
| `Q-PRICING-CONFIRM` | Your saved profile rate offered for this bid | `yes` to use it, or new numbers: `"base 420 loading 0.25 contingency 0.1"` |
|
|
254
|
+
| `Q-EFFORT-DAYS` | Person-days you will quote | e.g. `"25 person-days"` |
|
|
255
|
+
| `Q-AVAILABILITY` | When you can start | any honest date |
|
|
256
|
+
| `Q-VALIDITY` | How long your offer stays valid | e.g. `"90 days from submission"` |
|
|
257
|
+
| `Q-FILL-...` | A `[FILL]` box in a draft document | the real fact that belongs there |
|
|
258
|
+
| `Q-CVE-n` | A CV line needs proof (or removal) | the true fact, or `"delete"` |
|
|
259
|
+
| `Q-CLAR-n` | Something to ask the CLIENT | what you asked them and when |
|
|
260
|
+
|
|
261
|
+
Then run:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
npx tor-to-proposal bid-pack apply --answers answers.json --dir .
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
`apply` re-prints the questionnaire — repeat until it says **0 open
|
|
268
|
+
questions**, then run `bid-pack pack`.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
196
272
|
## 7. If something goes wrong
|
|
197
273
|
|
|
198
274
|
| What you see | What it means | What to do |
|
|
@@ -222,6 +298,9 @@ you will ever own.
|
|
|
222
298
|
- **Master CV** — your full, real CV. The tailored one is derived from it and
|
|
223
299
|
verified against it.
|
|
224
300
|
- **`[FILL]`** — a yellow flag meaning "a real fact goes here — yours".
|
|
301
|
+
- **`out/preview/`** — readable PDF copies of the bid screen, the questions
|
|
302
|
+
and the CV reports, regenerated after every `start` and `apply`. Double-click
|
|
303
|
+
to read them; the `.md` originals are the working files the tool edits.
|
|
225
304
|
- **Floor rate** — the lowest daily rate that still pays your bills. Below it,
|
|
226
305
|
you're paying the client.
|
|
227
306
|
- **MCP / skill / plugin** — three plumbing formats for connecting the tool to
|
package/mcp/server.mjs
CHANGED
|
@@ -322,7 +322,7 @@ function handle(msg) {
|
|
|
322
322
|
return {
|
|
323
323
|
protocolVersion: typeof params?.protocolVersion === 'string' ? params.protocolVersion : '2024-11-05',
|
|
324
324
|
capabilities: { tools: {} },
|
|
325
|
-
serverInfo: { name: 'tor-to-proposal', version: '2.0
|
|
325
|
+
serverInfo: { name: 'tor-to-proposal', version: '2.1.0' },
|
|
326
326
|
};
|
|
327
327
|
}
|
|
328
328
|
if (typeof method === 'string' && method.startsWith('notifications/')) return undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tor-to-proposal",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "The bid tool that refuses to lie. ToR + CV in -> verified bid pack out: cover letter, tailored CV, technical response, financial proposal, submission checklist. Deterministic CLI + agent skill for UN / NGO / government consultants.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|