tor-to-proposal 2.1.1 → 2.1.5
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/bin/bid-pack.mjs +57 -4
- package/mcp/server.mjs +1 -1
- package/package.json +1 -1
package/bin/bid-pack.mjs
CHANGED
|
@@ -117,7 +117,19 @@ function buildQuestions() {
|
|
|
117
117
|
: f === 'financial-proposal.md' && /tax/.test(detail) ? 'TAXES'
|
|
118
118
|
: null;
|
|
119
119
|
const id = known ? `Q-FILL-${known}` : 'Q-FILL-' + crypto.createHash('md5').update(f + '|' + excerpt).digest('hex').slice(0, 8);
|
|
120
|
-
|
|
120
|
+
// known slots get plain-language questions; unknown ones keep the raw excerpt
|
|
121
|
+
const friendly = known === 'CONSULTANT'
|
|
122
|
+
? `financial-proposal.md: whose name goes in the consultant line of the financial proposal? (Usually you — exactly as on your CV.)`
|
|
123
|
+
: known === 'REIMBURSABLES'
|
|
124
|
+
? `financial-proposal.md: which costs does the client reimburse on top of your fee? Write ONE clear sentence — or write none if nothing is reimbursable (the row is then removed).`
|
|
125
|
+
: known === 'TAXES'
|
|
126
|
+
? `financial-proposal.md: what is the tax treatment of your prices? ONE sentence, e.g. "All amounts are exclusive of VAT" or "All amounts are tax-inclusive".`
|
|
127
|
+
: null;
|
|
128
|
+
add({
|
|
129
|
+
id, area: 'draft-fill', type: 'string',
|
|
130
|
+
question: friendly || `${f}: a placeholder needs a real fact — “…${excerpt}…”. What is the fact?`,
|
|
131
|
+
why: 'No invented facts. If you cannot supply it, it stays [FILL] and the audit blocks submission.',
|
|
132
|
+
});
|
|
121
133
|
}
|
|
122
134
|
}
|
|
123
135
|
// cv-tailor unanchored bullets -> evidence questions
|
|
@@ -139,6 +151,29 @@ function buildQuestions() {
|
|
|
139
151
|
return { generatedAt: new Date().toISOString(), dir: dir, openCount: items.length, items };
|
|
140
152
|
}
|
|
141
153
|
|
|
154
|
+
// Per-question "how to answer" hint, shown in the wizard prompt and the
|
|
155
|
+
// questions table so users know the accepted format before replying.
|
|
156
|
+
function answerFormat(item) {
|
|
157
|
+
switch (item.id) {
|
|
158
|
+
case 'Q-PRICING-CONFIRM': return 'type yes to use the saved rates — or new numbers like: base 420 loading 0.25 contingency 0.1';
|
|
159
|
+
case 'Q-PRICING-BASE': return 'a number, e.g. 400';
|
|
160
|
+
case 'Q-PRICING-LOADING': return 'a decimal, e.g. 0.25 (= 25%)';
|
|
161
|
+
case 'Q-PRICING-CONTINGENCY': return 'a decimal, e.g. 0.10 (= 10%)';
|
|
162
|
+
case 'Q-PRICING-CURRENCY': return 'a currency code, e.g. USD';
|
|
163
|
+
case 'Q-EFFORT-DAYS': return 'a number of person-days, e.g. 25';
|
|
164
|
+
case 'Q-AVAILABILITY': return 'a date, e.g. 1 November 2026';
|
|
165
|
+
case 'Q-VALIDITY': return 'a phrase, e.g. 90 days from submission';
|
|
166
|
+
case 'Q-FILL-CONSULTANT': return 'the consultant name as it should appear in the document';
|
|
167
|
+
case 'Q-FILL-REIMBURSABLES': return 'a sentence about which costs are reimbursable';
|
|
168
|
+
case 'Q-FILL-TAXES': return 'a sentence about your tax status';
|
|
169
|
+
}
|
|
170
|
+
if (item.id.startsWith('Q-CVE-')) return 'the exact true wording that supports the line — or DELETE to remove the line';
|
|
171
|
+
if (item.type === 'number') return `a number${item.unit ? ` in ${item.unit}` : ''}`;
|
|
172
|
+
if (item.type === 'date') return 'a date, e.g. 1 November 2026';
|
|
173
|
+
if (item.type === 'confirm') return 'yes or no';
|
|
174
|
+
return 'a short sentence';
|
|
175
|
+
}
|
|
176
|
+
|
|
142
177
|
function writeQuestions(q) {
|
|
143
178
|
fs.writeFileSync(path.join(outDir, 'questions.json'), JSON.stringify(q, null, 2));
|
|
144
179
|
const yours = q.items.filter((i) => i.type !== 'client');
|
|
@@ -147,7 +182,7 @@ function writeQuestions(q) {
|
|
|
147
182
|
const intro = `Reply with the numbered answers; your assistant writes them to answers.json and runs:\n\n tor-to-proposal bid-pack apply --answers answers.json --dir ${dir}\n`;
|
|
148
183
|
const md =
|
|
149
184
|
`# Bid questions — answer these in one go\n\n` +
|
|
150
|
-
(yours.length ? intro + '\n' + mdTable(['#', 'Question', 'Why it is asked'], yours.map((i) => [++n, `**${i.id}** — ${i.question}${i.default !== undefined && i.default !== null ? ` (default: ${i.default})` : ''}`, i.why || ''])) + '\n' : 'None — everything mechanical is resolved.\n') +
|
|
185
|
+
(yours.length ? intro + '\n' + mdTable(['#', 'Question', 'How to answer', 'Why it is asked'], yours.map((i) => [++n, `**${i.id}** — ${i.question}${i.default !== undefined && i.default !== null ? ` (default: ${i.default})` : ''}`, answerFormat(i), i.why || ''])) + '\n' : 'None — everything mechanical is resolved.\n') +
|
|
151
186
|
(clients.length ? `\n## To send to the CLIENT (clarifications, before the cutoff)\n${clients.map((i) => `- ${i.question}`).join('\n')}\n` : '');
|
|
152
187
|
writeOut(path.join(outDir, 'questions.md'), md);
|
|
153
188
|
}
|
|
@@ -336,6 +371,8 @@ function apply() {
|
|
|
336
371
|
const r = run('pricing-model.mjs', argv, { label: 'pricing-model' });
|
|
337
372
|
pricingRan = r !== null;
|
|
338
373
|
process.stderr.write(r === null ? 'pricing NOT written — check the values; the pricing question stays open.\n' : `pricing written from user-confirmed numbers -> ${outDir}/pricing.json\n`);
|
|
374
|
+
} else if (answers['Q-PRICING-CONFIRM'] !== undefined) {
|
|
375
|
+
process.stderr.write('Q-PRICING-CONFIRM: no usable pricing in that answer — reply "yes" to use your saved rates, or new numbers like "base 420 loading 0.25". The pricing question stays open.\n');
|
|
339
376
|
}
|
|
340
377
|
|
|
341
378
|
// side effect: (re)render the financial proposal when its inputs are ready
|
|
@@ -355,6 +392,8 @@ function apply() {
|
|
|
355
392
|
fresh.items = fresh.items.filter((i) => i.answer === null);
|
|
356
393
|
fresh.openCount = fresh.items.length;
|
|
357
394
|
writeQuestions(fresh);
|
|
395
|
+
const stillYours = fresh.items.filter((i) => i.type !== 'client');
|
|
396
|
+
const stillClient = fresh.items.filter((i) => i.type === 'client');
|
|
358
397
|
const state = readState();
|
|
359
398
|
state.steps.questions = { status: 'done', open: fresh.openCount };
|
|
360
399
|
state.steps.pricing = { status: inDir('pricing.json') ? 'done' : 'awaiting-user-numbers' };
|
|
@@ -363,7 +402,13 @@ function apply() {
|
|
|
363
402
|
|
|
364
403
|
process.stdout.write(
|
|
365
404
|
`# Apply: ${applied} answer(s) recorded, ${fresh.openCount} still open\n\n` +
|
|
366
|
-
(
|
|
405
|
+
(stillYours.length
|
|
406
|
+
? `## Still open — answer these (bid-pack ask)\n${mdTable(['#', 'Question'], stillYours.map((i, ix) => [ix + 1, `**${i.id}** — ${i.question}`]))}\nNEXT STEP: run npx tor-to-proposal bid-pack ask --dir ${dir} — it asks each of these in plain language, one at a time.\n`
|
|
407
|
+
: `NEXT STEP: run npx tor-to-proposal bid-pack pack --dir ${dir} --fallback-pdf to build the submission pack.\n`) +
|
|
408
|
+
(stillClient.length
|
|
409
|
+
? `## Reminders — ACTIONS for you, not answers\n(These describe things to DO outside the tool: contact the client, verify a fact. They never close by typing an answer here.)\n${mdTable(['#', 'Action'], stillClient.map((i, ix) => [ix + 1, `**${i.id}** — ${i.question}`]))}\n`
|
|
410
|
+
: '') +
|
|
411
|
+
(!fresh.openCount ? `All questions resolved.\n` : '') +
|
|
367
412
|
(fills.length ? `\nFill answers recorded in out/fill-answers.json — apply them to the drafts, re-run the lints, and the questions disappear once the [FILL]s are gone.\n` : '') +
|
|
368
413
|
(evidence.length ? `\nCV evidence entries: ${evidence.length} (out/cv-evidence.json) — cv-tailor lint accepts these as user-input anchors.\n` : '')
|
|
369
414
|
);
|
|
@@ -414,12 +459,16 @@ async function askWizard() {
|
|
|
414
459
|
);
|
|
415
460
|
|
|
416
461
|
const answers = {};
|
|
462
|
+
// a pricing answer is only usable if it is "yes" or contains a base number —
|
|
463
|
+
// anything else would silently skip pricing and re-open the question later,
|
|
464
|
+
// so the wizard rejects it on the spot and re-asks
|
|
465
|
+
const pricingAnswerUsable = (a) => /^yes$/i.test(a) || /\bbase\b\s*[\d]/i.test(a);
|
|
417
466
|
let i = 0;
|
|
418
467
|
while (i < yours.length) {
|
|
419
468
|
const item = yours[i];
|
|
420
469
|
const cur = item.default !== undefined && item.default !== null ? String(item.default) : '';
|
|
421
470
|
const tail = item.unit ? ` (${item.unit})` : '';
|
|
422
|
-
const a = await askLine(`(${i + 1}/${yours.length}) ${item.question}${tail}${cur ? ` [${cur}]` : ''}\n> `);
|
|
471
|
+
const a = await askLine(`(${i + 1}/${yours.length}) ${item.question}${tail}${cur ? ` [${cur}]` : ''}\n How to answer: ${answerFormat(item)}\n> `);
|
|
423
472
|
if (a === 'EOF' || a.toLowerCase() === 'quit') {
|
|
424
473
|
rl.close();
|
|
425
474
|
process.stdout.write('\nCancelled — nothing was saved. (Your documents are untouched; run bid-pack ask again anytime.)\n');
|
|
@@ -436,6 +485,10 @@ async function askWizard() {
|
|
|
436
485
|
process.stdout.write(' This one needs an answer from you (no suggested default). Type it, or "quit" to stop.\n');
|
|
437
486
|
continue;
|
|
438
487
|
}
|
|
488
|
+
if (item.id === 'Q-PRICING-CONFIRM' && a !== '' && !pricingAnswerUsable(a)) {
|
|
489
|
+
process.stdout.write(' I can\'t price from that alone. Either type "yes" to use the saved rates shown above, or give new numbers like: base 420 loading 0.25 contingency 0.1\n');
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
439
492
|
answers[item.id] = a === '' ? item.default : a;
|
|
440
493
|
i += 1;
|
|
441
494
|
}
|
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.1.
|
|
325
|
+
serverInfo: { name: 'tor-to-proposal', version: '2.1.5' },
|
|
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.1.
|
|
3
|
+
"version": "2.1.5",
|
|
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": {
|