@v0idd0/message-next-step 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 +264 -0
- package/bin/message-next-step.js +86 -0
- package/compare-chatgpt-gemini.md +82 -0
- package/from-the-studio.md +69 -0
- package/package.json +117 -0
- package/src/index.js +247 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 vøiddo
|
|
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,264 @@
|
|
|
1
|
+
# message-next-step
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](package.json)
|
|
5
|
+
|
|
6
|
+
**[Web app](https://tells.voiddo.com/message-next-step/?ref=message-next-step-readme)** · **[Live compare page](https://tells.voiddo.com/message-next-step/compare-chatgpt-gemini.html?ref=message-next-step-readme)** · **[Packaged compare brief](compare-chatgpt-gemini.md)** · **[Deep Dive](https://tells.voiddo.com/deep-dive/?ref=message-next-step-readme)** · **[Signal toolkit](https://tells.voiddo.com/signal-toolkit/?ref=message-next-step-readme)** · **[GitHub](https://github.com/voidd0/message-next-step)** · **[npm](https://www.npmjs.com/package/@v0idd0/message-next-step)** · **[All tools](https://tools.voiddo.com/?ref=message-next-step-catalog-readme)** · **[Contact](mailto:support@voiddo.com)**
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
`message-next-step` is a deterministic checker for one narrow question:
|
|
11
|
+
|
|
12
|
+
What should you do with this message right now?
|
|
13
|
+
|
|
14
|
+
It reads one incoming message and suggests one next step:
|
|
15
|
+
|
|
16
|
+
- `reply now`
|
|
17
|
+
- `wait`
|
|
18
|
+
- `ask one concrete question`
|
|
19
|
+
- `move to a call`
|
|
20
|
+
- `let it go`
|
|
21
|
+
|
|
22
|
+
It does not pretend to read the whole relationship. It gives a fast, explainable first pass when you are stuck between overreacting and overinterpreting in dating, recruiter, workplace, client, support, or family threads.
|
|
23
|
+
|
|
24
|
+
It also works as a narrower, faster alternative to ChatGPT or Gemini when the real question is not "help me think forever" but "what is the next move on this one message?"
|
|
25
|
+
|
|
26
|
+
## Why this exists
|
|
27
|
+
|
|
28
|
+
The paid product is [`tells`](https://tells.voiddo.com/?ref=message-next-step-readme), which reads what people leave unsaid across messages, people, and profiles.
|
|
29
|
+
|
|
30
|
+
But many users do not start with "analyze the whole pattern." They start with a smaller operational question:
|
|
31
|
+
|
|
32
|
+
- Should I answer this now?
|
|
33
|
+
- Are they asking for something real, or just creating pressure?
|
|
34
|
+
- Is this vague enough that I need one clarifying question?
|
|
35
|
+
- Is texting the wrong bandwidth for this?
|
|
36
|
+
|
|
37
|
+
That same question shows up outside dating too:
|
|
38
|
+
|
|
39
|
+
- recruiter loops where timing and seriousness are unclear
|
|
40
|
+
- client or account messages that may need a cleaner boundary
|
|
41
|
+
- support escalations that are getting too heated for chat
|
|
42
|
+
- family or cofounder threads where the real question is whether text is still working
|
|
43
|
+
|
|
44
|
+
`message-next-step` is that free first step. It gives a deterministic action recommendation before the user commits to a deeper paid read.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install -g @v0idd0/message-next-step
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Web app
|
|
53
|
+
|
|
54
|
+
Use the browser version here:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
https://tells.voiddo.com/message-next-step/?ref=message-next-step-readme
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
If you want the browser-side AI-alternative framing first:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
https://tells.voiddo.com/message-next-step/compare-chatgpt-gemini.html?ref=message-next-step-readme
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
If you want the packaged compare brief for npm/GitHub readers:
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
compare-chatgpt-gemini.md
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
If the blocker is follow-up timing rather than reply content, use the sibling checker here:
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
https://tells.voiddo.com/double-text-risk/?ref=message-next-step-readme
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
If the incoming message is warm but still too vague to treat as a real yes, check that layer here first:
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
https://tells.voiddo.com/soft-yes-or-no/?ref=message-next-step-readme
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Usage
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
message-next-step "Can you confirm by Friday whether you want me to send the draft?"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
cat incoming.txt | message-next-step
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
message-next-step --json "Maybe later. We'll see when things calm down."
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
message-next-step --file incoming.txt
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Example output
|
|
103
|
+
|
|
104
|
+
```text
|
|
105
|
+
posture: direct
|
|
106
|
+
directness: 7.9/10
|
|
107
|
+
ambiguity: 2.5/10
|
|
108
|
+
pressure: 3.2/10
|
|
109
|
+
escalation: 1/10
|
|
110
|
+
avoidance: 1/10
|
|
111
|
+
call shift: 1/10
|
|
112
|
+
|
|
113
|
+
next step: reply now
|
|
114
|
+
why: The text names a usable next step without much ambiguity.
|
|
115
|
+
- There is enough concrete signal to answer directly.
|
|
116
|
+
|
|
117
|
+
next:
|
|
118
|
+
Use tells when one message is not enough and you need the thread, the person, or the pattern over time.
|
|
119
|
+
- quick next paid step: https://tells.voiddo.com/deep-dive/?ref=message-next-step-cli
|
|
120
|
+
- recurring reads: https://tells.voiddo.com/?ref=message-next-step-cli
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## What it scores
|
|
124
|
+
|
|
125
|
+
The tool uses transparent heuristics around six dimensions:
|
|
126
|
+
|
|
127
|
+
- `directness` — is there a real ask, timing, or next step?
|
|
128
|
+
- `ambiguity` — is timing or intent being left blurry?
|
|
129
|
+
- `pressure` — is force doing more work than content?
|
|
130
|
+
- `escalation` — is this message getting too hot for text?
|
|
131
|
+
- `avoidance` — is the sender stalling instead of deciding?
|
|
132
|
+
- `call shift` — are there enough cues that text is the wrong channel?
|
|
133
|
+
|
|
134
|
+
The recommendation is intentionally conservative. It is designed to stop bad reactive moves, not to invent certainty.
|
|
135
|
+
|
|
136
|
+
## Good use cases
|
|
137
|
+
|
|
138
|
+
- tense client, recruiter, or support texts
|
|
139
|
+
- dating or relationship messages with mixed clarity
|
|
140
|
+
- workplace follow-ups where you are unsure whether to push
|
|
141
|
+
- family messages where a call may be better than more texting
|
|
142
|
+
- any incoming message where your real question is "what do I do next?"
|
|
143
|
+
|
|
144
|
+
## Bad use cases
|
|
145
|
+
|
|
146
|
+
- therapy, legal, or crisis guidance
|
|
147
|
+
- deception detection
|
|
148
|
+
- full relationship analysis
|
|
149
|
+
- multi-message pattern reading
|
|
150
|
+
|
|
151
|
+
Those are outside this tool's scope. If the stakes are real and one message is not enough, use `tells`.
|
|
152
|
+
|
|
153
|
+
## Why not just use ChatGPT or Gemini?
|
|
154
|
+
|
|
155
|
+
Because the first question is often operational, not interpretive.
|
|
156
|
+
|
|
157
|
+
For a narrow next-step call, a deterministic tool is faster and easier to trust:
|
|
158
|
+
|
|
159
|
+
- no API key
|
|
160
|
+
- no prompt fiddling
|
|
161
|
+
- no latency roulette
|
|
162
|
+
- no surprise token bill
|
|
163
|
+
- same input gives the same output
|
|
164
|
+
|
|
165
|
+
Then, if the user needs the deeper read, the next step is not "more tokens." It is `tells`, which is built around subtext, conspicuous absence, pressure, and likely next move.
|
|
166
|
+
|
|
167
|
+
If you want the side-by-side framing inside the live browser route:
|
|
168
|
+
|
|
169
|
+
```text
|
|
170
|
+
https://tells.voiddo.com/message-next-step/compare-chatgpt-gemini.html?ref=message-next-step-readme
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
If you want the package-side compare brief instead:
|
|
174
|
+
|
|
175
|
+
```text
|
|
176
|
+
compare-chatgpt-gemini.md
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Paid next step
|
|
180
|
+
|
|
181
|
+
When the one-message answer is not enough, the paid ladder is simple:
|
|
182
|
+
|
|
183
|
+
- `Deep Dive` — `$19 once` for one loaded thread or one recurring person
|
|
184
|
+
- `Starter` — `$14.99/mo` for repeated message reading
|
|
185
|
+
- `Practitioner` — `$99.99/mo` for coaches, recruiters, mediators, trainers, or client-facing teams using this with clients
|
|
186
|
+
|
|
187
|
+
If the message-level answer is not enough, the fastest paid handoff is usually `Deep Dive`:
|
|
188
|
+
|
|
189
|
+
```text
|
|
190
|
+
https://tells.voiddo.com/deep-dive/?ref=message-next-step-readme
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
If you still want the wider browser-first free path first:
|
|
194
|
+
|
|
195
|
+
```text
|
|
196
|
+
https://tells.voiddo.com/signal-toolkit/?ref=message-next-step-readme
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Start here:
|
|
200
|
+
|
|
201
|
+
```text
|
|
202
|
+
https://tells.voiddo.com/message-next-step/?ref=message-next-step-readme
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Related free checkers
|
|
206
|
+
|
|
207
|
+
If the next-step question turns out to be the wrong layer, route into the narrower free tool first:
|
|
208
|
+
|
|
209
|
+
- `double-text-risk` for deciding whether another outbound follow-up is too soon or too pushy: `https://tells.voiddo.com/double-text-risk/?ref=message-next-step-related-readme`
|
|
210
|
+
- `soft-yes-or-no` for deciding whether the incoming message is polite interest, real intent, or a warm stall before you commit to a direct reply: `https://tells.voiddo.com/soft-yes-or-no/?ref=message-next-step-related-readme`
|
|
211
|
+
- `replytone` for checking whether your drafted response sounds clear, warm, or too forceful: `https://tells.voiddo.com/replytone/?ref=message-next-step-related-readme`
|
|
212
|
+
- `ambiguity-meter` for scoring whether the incoming message is vague, evasive, or mixed: `https://tells.voiddo.com/ambiguity-meter/?ref=message-next-step-related-readme`
|
|
213
|
+
- `call-not-text` for deciding whether the thread is already too compressed or heated for another message: `https://tells.voiddo.com/call-not-text/?ref=message-next-step-related-readme`
|
|
214
|
+
- `ghost-or-go` for deciding whether ongoing silence means wait, one final ping, or close the loop: `https://tells.voiddo.com/ghost-or-go/?ref=message-next-step-related-readme`
|
|
215
|
+
- `raincheck-or-run` for deciding whether repeated reschedules and no-new-time messages are still workable or already a stall pattern: `https://tells.voiddo.com/raincheck-or-run/?ref=message-next-step-related-readme`
|
|
216
|
+
|
|
217
|
+
## Programmatic API
|
|
218
|
+
|
|
219
|
+
```javascript
|
|
220
|
+
import { analyzeMessageNextStep, formatReport } from "@v0idd0/message-next-step";
|
|
221
|
+
|
|
222
|
+
const result = analyzeMessageNextStep("You always do this. Call me right now!!!");
|
|
223
|
+
|
|
224
|
+
console.log(result.decision.action);
|
|
225
|
+
console.log(formatReport(result));
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Development
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
npm test
|
|
232
|
+
node bin/message-next-step.js "Maybe later. We'll see when things calm down."
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## More from the studio
|
|
236
|
+
|
|
237
|
+
See [`from-the-studio.md`](from-the-studio.md) for the wider vøiddo catalogue.
|
|
238
|
+
|
|
239
|
+
## Compare surface
|
|
240
|
+
|
|
241
|
+
- live compare page: `https://tells.voiddo.com/message-next-step/compare-chatgpt-gemini.html?ref=message-next-step-readme`
|
|
242
|
+
- packaged compare brief: [`compare-chatgpt-gemini.md`](compare-chatgpt-gemini.md)
|
|
243
|
+
|
|
244
|
+
If you are positioning this tool against general-purpose AI assistants, use the dedicated comparison asset:
|
|
245
|
+
|
|
246
|
+
[`compare-chatgpt-gemini.md`](compare-chatgpt-gemini.md)
|
|
247
|
+
|
|
248
|
+
## Best next free exits
|
|
249
|
+
|
|
250
|
+
- `replytone` for checking whether your drafted response sounds warm, clear, or too forceful before you send it: `https://tells.voiddo.com/replytone/?ref=message-next-step-readme`
|
|
251
|
+
- `double-text-risk` for deciding whether a follow-up is timely, needy, or better left unsent: `https://tells.voiddo.com/double-text-risk/?ref=message-next-step-readme`
|
|
252
|
+
- `soft-yes-or-no` for deciding whether the incoming wording is a real yes, a warm maybe, or a polite stall before you over-commit to a reply: `https://tells.voiddo.com/soft-yes-or-no/?ref=message-next-step-readme`
|
|
253
|
+
- `ambiguity-meter` for checking whether the incoming message is vague, evasive, or mixed enough to justify a clarifier instead of a direct answer: `https://tells.voiddo.com/ambiguity-meter/?ref=message-next-step-readme`
|
|
254
|
+
- `call-not-text` for deciding whether the thread has outgrown text and needs a call, pause, or firmer channel switch: `https://tells.voiddo.com/call-not-text/?ref=message-next-step-readme`
|
|
255
|
+
- `ghost-or-go` for deciding whether ongoing silence after this message means wait, one final ping, or close the loop: `https://tells.voiddo.com/ghost-or-go/?ref=message-next-step-readme`
|
|
256
|
+
- `raincheck-or-run` for deciding whether repeated schedule slips, delays, or "maybe later" loops are still genuine or already a stall pattern: `https://tells.voiddo.com/raincheck-or-run/?ref=message-next-step-readme`
|
|
257
|
+
|
|
258
|
+
## License
|
|
259
|
+
|
|
260
|
+
MIT.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
Built by [vøiddo](https://voiddo.com/) — a small studio shipping AI-flavoured products, free dev tools, Chrome extensions and weird browser games.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { analyzeMessageNextStep, formatReport } from "../src/index.js";
|
|
5
|
+
|
|
6
|
+
function printHelp() {
|
|
7
|
+
console.log(`message-next-step
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
message-next-step "text to analyze"
|
|
11
|
+
cat message.txt | message-next-step
|
|
12
|
+
message-next-step --file message.txt
|
|
13
|
+
message-next-step --json "Can we talk later?"
|
|
14
|
+
`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function parseArgs(argv) {
|
|
18
|
+
const args = [...argv];
|
|
19
|
+
let json = false;
|
|
20
|
+
let file = null;
|
|
21
|
+
const textParts = [];
|
|
22
|
+
|
|
23
|
+
while (args.length) {
|
|
24
|
+
const arg = args.shift();
|
|
25
|
+
if (arg === "--help" || arg === "-h") {
|
|
26
|
+
return { help: true };
|
|
27
|
+
}
|
|
28
|
+
if (arg === "--json") {
|
|
29
|
+
json = true;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (arg === "--file") {
|
|
33
|
+
file = args.shift();
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
textParts.push(arg);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { help: false, json, file, text: textParts.join(" ").trim() };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function readStdin() {
|
|
43
|
+
if (process.stdin.isTTY) {
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
let data = "";
|
|
48
|
+
process.stdin.setEncoding("utf8");
|
|
49
|
+
process.stdin.on("data", (chunk) => {
|
|
50
|
+
data += chunk;
|
|
51
|
+
});
|
|
52
|
+
process.stdin.on("end", () => resolve(data.trim()));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function main() {
|
|
57
|
+
const options = parseArgs(process.argv.slice(2));
|
|
58
|
+
if (options.help) {
|
|
59
|
+
printHelp();
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let input = options.text;
|
|
64
|
+
if (options.file) {
|
|
65
|
+
input = fs.readFileSync(options.file, "utf8").trim();
|
|
66
|
+
}
|
|
67
|
+
if (!input) {
|
|
68
|
+
input = await readStdin();
|
|
69
|
+
}
|
|
70
|
+
if (!input) {
|
|
71
|
+
printHelp();
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const result = analyzeMessageNextStep(input);
|
|
76
|
+
if (options.json) {
|
|
77
|
+
console.log(JSON.stringify(result, null, 2));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
console.log(formatReport(result));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
main().catch((error) => {
|
|
84
|
+
console.error(`message-next-step: ${error.message}`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# message-next-step vs ChatGPT or Gemini
|
|
2
|
+
|
|
3
|
+
`message-next-step` is not trying to replace a general-purpose model.
|
|
4
|
+
|
|
5
|
+
It is trying to win a narrower, high-frequency moment:
|
|
6
|
+
|
|
7
|
+
> "I already have the incoming message. I just need to know whether to reply now, wait, ask one clarifier, move to a call, or let it go."
|
|
8
|
+
|
|
9
|
+
That is where a deterministic checker beats a blank prompt box.
|
|
10
|
+
|
|
11
|
+
## When message-next-step is the better first step
|
|
12
|
+
|
|
13
|
+
- You want a stable `reply now`, `wait`, `ask one question`, `move to a call`, or `let it go` call.
|
|
14
|
+
- You do not want to write prompts, add fake context, or compare three slightly different model answers.
|
|
15
|
+
- You are checking a dating, recruiter, client, support, family, or workplace thread where the immediate next move matters more than broad interpretation.
|
|
16
|
+
- You want the same message to score the same way every time.
|
|
17
|
+
|
|
18
|
+
## When ChatGPT or Gemini is still useful
|
|
19
|
+
|
|
20
|
+
- You want rewrites, brainstorming, or roleplay.
|
|
21
|
+
- You want a broad assistant rather than a narrow next-step call.
|
|
22
|
+
- You need long-form drafting more than decision support.
|
|
23
|
+
|
|
24
|
+
## The practical difference
|
|
25
|
+
|
|
26
|
+
| question | message-next-step | ChatGPT / Gemini |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| "What should I do with this message right now?" | deterministic action + why | depends on prompt and model variance |
|
|
29
|
+
| "Can I re-check the same message later?" | yes, same input = same output | not reliably |
|
|
30
|
+
| "Do I need an account or API key?" | no | usually yes |
|
|
31
|
+
| "Can it rewrite five responses for me?" | no, narrow checker by design | yes |
|
|
32
|
+
| "Can it read the deeper recurring pattern behind this person?" | no, move into tells for that | not from one prompt alone |
|
|
33
|
+
|
|
34
|
+
## What each tool is actually good at
|
|
35
|
+
|
|
36
|
+
- Use `message-next-step` when the immediate question is the next action on one incoming message and you want a stable `reply now`, `wait`, `ask one question`, `move to a call`, or `let it go` call.
|
|
37
|
+
- Use ChatGPT or Gemini when you already know the decision and want rewrite options, brainstorming, roleplay, or longer drafting help.
|
|
38
|
+
- Use `ambiguity-meter` when the main question is whether the message is genuinely clear, vague, or evasive before you decide what to do with it.
|
|
39
|
+
- Use `soft-yes-or-no` when the message sounds warm but still might be a polite stall instead of a real commitment.
|
|
40
|
+
- Use `replytone` when you already wrote the answer and want to know whether it sounds warm, clear, or pushy.
|
|
41
|
+
- Use `double-text-risk` when the timing of your next outbound follow-up matters more than the meaning of the current message.
|
|
42
|
+
- Use `call-not-text` when the real question is no longer the reply itself, but whether text is the wrong channel for the pressure, heat, or complexity in the thread.
|
|
43
|
+
- Use `raincheck-or-run` when the message is mainly another reschedule, delay, or "maybe later" loop and the decision is whether to rebook or walk away.
|
|
44
|
+
- Use `tells` when one message is not enough and the issue is the recurring person, loaded thread, or wider pattern over time.
|
|
45
|
+
|
|
46
|
+
## Where tells fits
|
|
47
|
+
|
|
48
|
+
`message-next-step` is the free first door.
|
|
49
|
+
|
|
50
|
+
If the real problem is not one message but one recurring person, one loaded thread, or one pattern of pressure, avoidance, or mixed signals, the better next step is `tells`.
|
|
51
|
+
|
|
52
|
+
- `Deep Dive` — `$19 once` for one loaded case
|
|
53
|
+
- `Starter` — `$14.99/mo` for recurring message reads
|
|
54
|
+
- `Practitioner` — `$99.99/mo` for coaches, recruiters, mediators, trainers, and client-facing teams
|
|
55
|
+
|
|
56
|
+
Use the browser checker:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
https://tells.voiddo.com/message-next-step/?ref=message-next-step-compare-md
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
If you want the browser-side compare page first:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
https://tells.voiddo.com/message-next-step/compare-chatgpt-gemini.html?ref=message-next-step-compare-md
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
If one message is not enough:
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
https://tells.voiddo.com/deep-dive/?ref=message-next-step-compare-md
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
If you want the wider free cluster first:
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
https://tells.voiddo.com/signal-toolkit/?ref=message-next-step-compare-md
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
Built by [vøiddo](https://voiddo.com/) — a small studio shipping AI-flavoured products, free dev tools, Chrome extensions and weird browser games.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# From the same studio
|
|
2
|
+
|
|
3
|
+
This package is one piece of [vøiddo](https://voiddo.com/), a small studio that ships AI-flavoured products, free dev tools, Chrome extensions and weird browser games.
|
|
4
|
+
|
|
5
|
+
## Flagship path
|
|
6
|
+
|
|
7
|
+
| product | what it does |
|
|
8
|
+
|---|---|
|
|
9
|
+
| [**tells**](https://tells.voiddo.com/?ref=message-next-step-studio) | opinionated AI that reads what people do not say in messages, threads, and profiles |
|
|
10
|
+
| [**Deep Dive**](https://tells.voiddo.com/deep-dive/?ref=message-next-step-studio) | the fastest paid handoff when one message-level action call is not enough and the user needs the fuller thread or person read |
|
|
11
|
+
| [**Signal toolkit**](https://tells.voiddo.com/signal-toolkit/?ref=message-next-step-studio) | browser-first hub for the tells micro-tools when the user still wants a free/cheap checkpoint first |
|
|
12
|
+
| [**scrb**](https://scrb.voiddo.com/?ref=message-next-step-studio) | AI product description generator for Amazon, Etsy, Shopify, and eBay |
|
|
13
|
+
| [**rankd**](https://rankd.voiddo.com/?ref=message-next-step-studio) | opinionated cultural verdicts on music, books, and films |
|
|
14
|
+
|
|
15
|
+
## Related free tools
|
|
16
|
+
|
|
17
|
+
| tool | one-line |
|
|
18
|
+
|---|---|
|
|
19
|
+
| [message-next-step vs ChatGPT/Gemini](https://tells.voiddo.com/message-next-step/compare-chatgpt-gemini.html?ref=message-next-step-studio) | comparison page for people deciding whether they need a deterministic next-step checker or a broader general AI assistant |
|
|
20
|
+
| [`message-next-step` packaged compare brief](compare-chatgpt-gemini.md) | npm/GitHub-ready acquisition brief for install-surface readers deciding whether they need a deterministic next-step checker or a broader general model |
|
|
21
|
+
| [replytone](https://tells.voiddo.com/replytone/?ref=message-next-step-studio) | score an outgoing draft for warmth, clarity, pressure, and urgency |
|
|
22
|
+
| [soft-yes-or-no](https://tells.voiddo.com/soft-yes-or-no/?ref=message-next-step-studio) | decide whether a warm-but-vague message is real interest, a soft no, or a polite stall |
|
|
23
|
+
| [ambiguity-meter](https://tells.voiddo.com/ambiguity-meter/?ref=message-next-step-studio) | check one incoming message for mixed signals and missing next steps |
|
|
24
|
+
| [double-text-risk](https://tells.voiddo.com/double-text-risk/?ref=message-next-step-studio) | score whether a follow-up is timely, needy, or better left unsent |
|
|
25
|
+
| [call-not-text](https://tells.voiddo.com/call-not-text/?ref=message-next-step-studio) | decide whether the thread is already too messy, heated, or compressed for more texting |
|
|
26
|
+
| [ghost-or-go](https://tells.voiddo.com/ghost-or-go/?ref=message-next-step-studio) | decide whether silence means wait, send one final ping, or close the loop |
|
|
27
|
+
| [raincheck-or-run](https://tells.voiddo.com/raincheck-or-run/?ref=message-next-step-studio) | decide whether repeated reschedules are still workable or already a stall pattern |
|
|
28
|
+
| [profile-red-flags](https://tells.voiddo.com/profile-red-flags/?ref=message-next-step-studio) | triage one profile for vagueness, pressure, cynicism, and effort |
|
|
29
|
+
| [All free tools](https://tools.voiddo.com/?ref=message-next-step-studio) | the wider vøiddo CLI and browser-tools catalogue |
|
|
30
|
+
|
|
31
|
+
## When the free pass is not enough
|
|
32
|
+
|
|
33
|
+
`message-next-step` is meant to stop one bad reactive move before the user needs a fuller read of the thread or person.
|
|
34
|
+
|
|
35
|
+
That applies across dating, recruiter loops, workplace friction, client or support escalations, and family threads where the next move matters more than a broad interpretation.
|
|
36
|
+
|
|
37
|
+
The paid next steps are explicit:
|
|
38
|
+
|
|
39
|
+
- `Deep Dive` — `$19 once` for one loaded thread or one recurring person
|
|
40
|
+
- `Starter` — `$14.99/mo` for repeat message reads
|
|
41
|
+
- `Practitioner` — `$99.99/mo` for coach, recruiter, mediator, trainer, or client-facing team workflows
|
|
42
|
+
|
|
43
|
+
If the message-level answer is not enough, the best next stop is usually `Deep Dive`:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
https://tells.voiddo.com/deep-dive/?ref=message-next-step-studio
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If the user still wants a wider browser-first free path before buying, send them here instead:
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
https://tells.voiddo.com/signal-toolkit/?ref=message-next-step-studio
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Best next free exits
|
|
56
|
+
|
|
57
|
+
- [replytone](https://tells.voiddo.com/replytone/?ref=message-next-step-studio) if the user already drafted the response and wants a warmth / clarity / pressure check before sending
|
|
58
|
+
- [double-text-risk](https://tells.voiddo.com/double-text-risk/?ref=message-next-step-studio) if the real blocker is follow-up timing instead of reply content
|
|
59
|
+
- [soft-yes-or-no](https://tells.voiddo.com/soft-yes-or-no/?ref=message-next-step-studio) if the incoming message sounds warm but still not concrete enough to treat as a real yes
|
|
60
|
+
- [ambiguity-meter](https://tells.voiddo.com/ambiguity-meter/?ref=message-next-step-studio) if the incoming wording is so vague that the user first needs an ambiguity read
|
|
61
|
+
- [call-not-text](https://tells.voiddo.com/call-not-text/?ref=message-next-step-studio) if the thread has outgrown text and needs a call / pause / channel switch decision
|
|
62
|
+
- [ghost-or-go](https://tells.voiddo.com/ghost-or-go/?ref=message-next-step-studio) if the next question becomes silence management after this message lands
|
|
63
|
+
- [raincheck-or-run](https://tells.voiddo.com/raincheck-or-run/?ref=message-next-step-studio) if the message is mostly another delay, cancellation, or no-new-time reschedule loop
|
|
64
|
+
|
|
65
|
+
Need support or a custom workflow? Contact `support@voiddo.com`.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
Built by [vøiddo](https://voiddo.com/) — a small studio shipping AI-flavoured products, free dev tools, Chrome extensions and weird browser games.
|
package/package.json
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@v0idd0/message-next-step",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "message-next-step — free deterministic incoming-message checker and faster ChatGPT alternative for dating, workplace, recruiter, client, and family threads when you need to decide whether to reply now, wait, ask one question, move to a call, or let it go.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"message-next-step": "./bin/message-next-step.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node test.js",
|
|
12
|
+
"stage:site": "bash ./scripts/stage-site.sh"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"message-next-step",
|
|
16
|
+
"next-step-checker",
|
|
17
|
+
"texting",
|
|
18
|
+
"message-analysis",
|
|
19
|
+
"incoming-message-analyzer",
|
|
20
|
+
"mixed-signals",
|
|
21
|
+
"incoming-message",
|
|
22
|
+
"incoming-text",
|
|
23
|
+
"reply-or-wait",
|
|
24
|
+
"wait-or-reply",
|
|
25
|
+
"should-i-reply",
|
|
26
|
+
"should-i-answer-this-message",
|
|
27
|
+
"should-i-respond-now",
|
|
28
|
+
"reply-checker",
|
|
29
|
+
"reply-decision",
|
|
30
|
+
"reply-now-or-wait",
|
|
31
|
+
"what-should-i-do-with-this-message",
|
|
32
|
+
"how-to-answer-this-text",
|
|
33
|
+
"follow-up-decision",
|
|
34
|
+
"follow-up-triage",
|
|
35
|
+
"how-to-respond",
|
|
36
|
+
"message-decision",
|
|
37
|
+
"message-triage",
|
|
38
|
+
"message-response-decision",
|
|
39
|
+
"chatgpt-alternative",
|
|
40
|
+
"gemini-alternative",
|
|
41
|
+
"ai-writing-alternative",
|
|
42
|
+
"email-reply-decision",
|
|
43
|
+
"incoming-email-next-step",
|
|
44
|
+
"recruiter-message",
|
|
45
|
+
"recruiter-follow-up",
|
|
46
|
+
"client-message",
|
|
47
|
+
"client-follow-up",
|
|
48
|
+
"support-escalation",
|
|
49
|
+
"support-reply",
|
|
50
|
+
"family-conflict",
|
|
51
|
+
"family-thread-next-step",
|
|
52
|
+
"move-to-call",
|
|
53
|
+
"move-this-to-a-call",
|
|
54
|
+
"clarifying-question",
|
|
55
|
+
"need-one-clarifying-question",
|
|
56
|
+
"support-thread-next-step",
|
|
57
|
+
"customer-thread-next-step",
|
|
58
|
+
"recruiter-next-step",
|
|
59
|
+
"client-next-step",
|
|
60
|
+
"email-next-step",
|
|
61
|
+
"reply-later-or-now",
|
|
62
|
+
"respond-or-wait",
|
|
63
|
+
"when-to-reply-to-a-text",
|
|
64
|
+
"when-to-reply-to-an-email",
|
|
65
|
+
"date-text-next-step",
|
|
66
|
+
"vague-text-next-step",
|
|
67
|
+
"mixed-signal-reply",
|
|
68
|
+
"reschedule-message-decision",
|
|
69
|
+
"message-escalation-checker",
|
|
70
|
+
"what-should-i-reply",
|
|
71
|
+
"what-do-i-do-with-this-text",
|
|
72
|
+
"what-do-i-do-with-this-email",
|
|
73
|
+
"should-i-call-instead-of-text",
|
|
74
|
+
"texting-anxiety",
|
|
75
|
+
"communication",
|
|
76
|
+
"relationships",
|
|
77
|
+
"dating",
|
|
78
|
+
"workplace",
|
|
79
|
+
"recruiting",
|
|
80
|
+
"customer-support",
|
|
81
|
+
"account-management",
|
|
82
|
+
"coaching",
|
|
83
|
+
"heuristics",
|
|
84
|
+
"cli",
|
|
85
|
+
"voiddo",
|
|
86
|
+
"tells"
|
|
87
|
+
],
|
|
88
|
+
"author": "vøiddo <support@voiddo.com> (https://voiddo.com)",
|
|
89
|
+
"license": "MIT",
|
|
90
|
+
"homepage": "https://tells.voiddo.com/message-next-step/?ref=message-next-step-npm",
|
|
91
|
+
"repository": {
|
|
92
|
+
"type": "git",
|
|
93
|
+
"url": "git+https://github.com/voidd0/message-next-step.git"
|
|
94
|
+
},
|
|
95
|
+
"bugs": {
|
|
96
|
+
"url": "https://github.com/voidd0/message-next-step/issues",
|
|
97
|
+
"email": "support@voiddo.com"
|
|
98
|
+
},
|
|
99
|
+
"publishConfig": {
|
|
100
|
+
"access": "public"
|
|
101
|
+
},
|
|
102
|
+
"engines": {
|
|
103
|
+
"node": ">=18"
|
|
104
|
+
},
|
|
105
|
+
"files": [
|
|
106
|
+
"bin",
|
|
107
|
+
"src",
|
|
108
|
+
"README.md",
|
|
109
|
+
"compare-chatgpt-gemini.md",
|
|
110
|
+
"LICENSE",
|
|
111
|
+
"from-the-studio.md"
|
|
112
|
+
],
|
|
113
|
+
"funding": {
|
|
114
|
+
"type": "individual",
|
|
115
|
+
"url": "https://voiddo.com/contact/"
|
|
116
|
+
}
|
|
117
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
const URGENCY_PHRASES = [
|
|
2
|
+
"asap",
|
|
3
|
+
"urgent",
|
|
4
|
+
"today",
|
|
5
|
+
"by tonight",
|
|
6
|
+
"right now",
|
|
7
|
+
"need an answer",
|
|
8
|
+
"let me know today",
|
|
9
|
+
"before friday",
|
|
10
|
+
"can you confirm"
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const DELAY_PHRASES = [
|
|
14
|
+
"maybe later",
|
|
15
|
+
"we'll see",
|
|
16
|
+
"not right now",
|
|
17
|
+
"another time",
|
|
18
|
+
"sometime",
|
|
19
|
+
"for now",
|
|
20
|
+
"eventually",
|
|
21
|
+
"when things calm down"
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const SPECIFICITY_PHRASES = [
|
|
25
|
+
"tomorrow",
|
|
26
|
+
"friday",
|
|
27
|
+
"monday",
|
|
28
|
+
"confirm by",
|
|
29
|
+
"at 3",
|
|
30
|
+
"call at",
|
|
31
|
+
"meet at",
|
|
32
|
+
"send me",
|
|
33
|
+
"i can do",
|
|
34
|
+
"let's talk",
|
|
35
|
+
"reply by",
|
|
36
|
+
"next week"
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const CALL_PHRASES = [
|
|
40
|
+
"call me",
|
|
41
|
+
"can we talk",
|
|
42
|
+
"let's talk",
|
|
43
|
+
"jump on a call",
|
|
44
|
+
"phone call",
|
|
45
|
+
"talk later",
|
|
46
|
+
"need to talk"
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const BLAME_PHRASES = [
|
|
50
|
+
"you're overthinking",
|
|
51
|
+
"you always do this",
|
|
52
|
+
"don't start",
|
|
53
|
+
"calm down",
|
|
54
|
+
"that's not what i meant",
|
|
55
|
+
"why are you making this about you",
|
|
56
|
+
"you're making this harder"
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const SOFTENERS = [
|
|
60
|
+
"thanks",
|
|
61
|
+
"appreciate",
|
|
62
|
+
"glad",
|
|
63
|
+
"happy to",
|
|
64
|
+
"makes sense",
|
|
65
|
+
"understand"
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
function clamp(value, min = 0, max = 10) {
|
|
69
|
+
return Math.max(min, Math.min(max, value));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function round1(value) {
|
|
73
|
+
return Math.round(value * 10) / 10;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function countMatches(text, phrases) {
|
|
77
|
+
return phrases.reduce((count, phrase) => {
|
|
78
|
+
const escaped = phrase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
79
|
+
const regex = new RegExp(`\\b${escaped}\\b`, "gi");
|
|
80
|
+
const hits = text.match(regex);
|
|
81
|
+
return count + (hits ? hits.length : 0);
|
|
82
|
+
}, 0);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function buildDecision(metrics, counters) {
|
|
86
|
+
const reasons = [];
|
|
87
|
+
let action = "ask one concrete question";
|
|
88
|
+
let summary =
|
|
89
|
+
"The message is not clear enough on timing or intent to justify filling the blanks with optimism.";
|
|
90
|
+
|
|
91
|
+
if (
|
|
92
|
+
metrics.escalation >= 6 ||
|
|
93
|
+
(metrics.callShift >= 5.5 && metrics.pressure >= 5) ||
|
|
94
|
+
(metrics.callShift >= 4.5 && counters.exclamations >= 2)
|
|
95
|
+
) {
|
|
96
|
+
action = "move to a call";
|
|
97
|
+
summary =
|
|
98
|
+
"The text is carrying enough heat or compression that another round of texting will probably distort it further.";
|
|
99
|
+
reasons.push("Escalation is high enough that text is the wrong bandwidth.");
|
|
100
|
+
} else if (metrics.directness >= 6.5 && metrics.pressure <= 5 && metrics.ambiguity <= 4.5) {
|
|
101
|
+
action = "reply now";
|
|
102
|
+
summary = "The text names a usable next step without much ambiguity.";
|
|
103
|
+
reasons.push("There is enough concrete signal to answer directly.");
|
|
104
|
+
} else if (metrics.avoidance >= 5 && metrics.pressure <= 4.5) {
|
|
105
|
+
action = "wait";
|
|
106
|
+
summary = "The message leans toward delay rather than a clean request. Pushing now is unlikely to improve clarity.";
|
|
107
|
+
reasons.push("Delay language is stronger than commitment language.");
|
|
108
|
+
} else if (metrics.blame >= 6.5 && metrics.directness <= 4.5) {
|
|
109
|
+
action = "let it go";
|
|
110
|
+
summary = "The text shifts the frame onto your reaction without offering much constructive direction back.";
|
|
111
|
+
reasons.push("Blame or minimising language is doing more work than the actual issue.");
|
|
112
|
+
} else {
|
|
113
|
+
reasons.push("One concrete follow-up will surface whether the sender has a real next step or not.");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (metrics.ambiguity >= 5) {
|
|
117
|
+
reasons.push("Timing or intent stays unresolved in the current wording.");
|
|
118
|
+
}
|
|
119
|
+
if (metrics.pressure >= 6) {
|
|
120
|
+
reasons.push("The message carries more force than detail.");
|
|
121
|
+
}
|
|
122
|
+
if (counters.questions >= 3) {
|
|
123
|
+
reasons.push("Stacked questions increase friction without adding clarity.");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
action,
|
|
128
|
+
summary,
|
|
129
|
+
reasons: [...new Set(reasons)].slice(0, 3)
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function buildFollowUpPrompts(metrics) {
|
|
134
|
+
const prompts = [];
|
|
135
|
+
|
|
136
|
+
if (metrics.ambiguity >= 5) {
|
|
137
|
+
prompts.push("What exactly are you asking me to decide or confirm?");
|
|
138
|
+
}
|
|
139
|
+
if (metrics.directness < 5.5) {
|
|
140
|
+
prompts.push("What timing are you actually proposing?");
|
|
141
|
+
}
|
|
142
|
+
if (metrics.pressure >= 6) {
|
|
143
|
+
prompts.push("I can answer this better on a call than in fragments here.");
|
|
144
|
+
}
|
|
145
|
+
if (metrics.avoidance >= 5) {
|
|
146
|
+
prompts.push("If now is not the right time, when should I revisit this?");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return [...new Set(prompts)].slice(0, 3);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function analyzeMessageNextStep(input) {
|
|
153
|
+
const text = String(input || "").trim();
|
|
154
|
+
if (!text) {
|
|
155
|
+
throw new Error("message-next-step needs message text");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const lower = text.toLowerCase();
|
|
159
|
+
const exclamations = (text.match(/!/g) || []).length;
|
|
160
|
+
const questions = (text.match(/\?/g) || []).length;
|
|
161
|
+
const uppercaseWords = (text.match(/\b[A-Z]{3,}\b/g) || []).length;
|
|
162
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
163
|
+
const sentences = Math.max(
|
|
164
|
+
1,
|
|
165
|
+
text.split(/[.!?]+/).map((part) => part.trim()).filter(Boolean).length
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const urgencyHits = countMatches(lower, URGENCY_PHRASES);
|
|
169
|
+
const delayHits = countMatches(lower, DELAY_PHRASES);
|
|
170
|
+
const specificityHits = countMatches(lower, SPECIFICITY_PHRASES);
|
|
171
|
+
const callHits = countMatches(lower, CALL_PHRASES);
|
|
172
|
+
const blameHits = countMatches(lower, BLAME_PHRASES);
|
|
173
|
+
const softenerHits = countMatches(lower, SOFTENERS);
|
|
174
|
+
|
|
175
|
+
const directness = clamp(4 + specificityHits * 1.35 + urgencyHits * 0.8 - delayHits * 0.6);
|
|
176
|
+
const ambiguity = clamp(3 + delayHits * 1.25 - specificityHits * 0.5);
|
|
177
|
+
const pressure = clamp(2 + urgencyHits * 1.25 + blameHits * 0.8 + uppercaseWords * 0.9 + exclamations * 0.45 - softenerHits * 0.35);
|
|
178
|
+
const escalation = clamp(1 + blameHits * 1.8 + callHits * 0.9 + exclamations * 0.8 + uppercaseWords * 0.9);
|
|
179
|
+
const avoidance = clamp(1 + delayHits * 1.35 - specificityHits * 0.25);
|
|
180
|
+
const callShift = clamp(1 + callHits * 2.4 + Math.max(0, escalation - 5) * 0.45);
|
|
181
|
+
|
|
182
|
+
const metrics = {
|
|
183
|
+
directness: round1(directness),
|
|
184
|
+
ambiguity: round1(ambiguity),
|
|
185
|
+
pressure: round1(pressure),
|
|
186
|
+
escalation: round1(escalation),
|
|
187
|
+
avoidance: round1(avoidance),
|
|
188
|
+
callShift: round1(callShift)
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
let posture = "mixed";
|
|
192
|
+
if (metrics.directness >= 6.5 && metrics.ambiguity <= 4.5) {
|
|
193
|
+
posture = "direct";
|
|
194
|
+
} else if (metrics.avoidance >= 5) {
|
|
195
|
+
posture = "avoidant";
|
|
196
|
+
} else if (metrics.escalation >= 6 || metrics.callShift >= 5.5) {
|
|
197
|
+
posture = "heated";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
posture,
|
|
202
|
+
metrics,
|
|
203
|
+
decision: buildDecision(metrics, { questions, exclamations }),
|
|
204
|
+
followUps: buildFollowUpPrompts(metrics),
|
|
205
|
+
counters: {
|
|
206
|
+
words: words.length,
|
|
207
|
+
sentences,
|
|
208
|
+
questions
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function formatReport(result) {
|
|
214
|
+
const lines = [
|
|
215
|
+
`posture: ${result.posture}`,
|
|
216
|
+
`directness: ${result.metrics.directness}/10`,
|
|
217
|
+
`ambiguity: ${result.metrics.ambiguity}/10`,
|
|
218
|
+
`pressure: ${result.metrics.pressure}/10`,
|
|
219
|
+
`escalation: ${result.metrics.escalation}/10`,
|
|
220
|
+
`avoidance: ${result.metrics.avoidance}/10`,
|
|
221
|
+
`call shift: ${result.metrics.callShift}/10`,
|
|
222
|
+
"",
|
|
223
|
+
`next step: ${result.decision.action}`,
|
|
224
|
+
`why: ${result.decision.summary}`
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
for (const reason of result.decision.reasons) {
|
|
228
|
+
lines.push(`- ${reason}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (result.followUps.length) {
|
|
232
|
+
lines.push("", "follow-up prompts:");
|
|
233
|
+
result.followUps.forEach((prompt, index) => {
|
|
234
|
+
lines.push(`${index + 1}. ${prompt}`);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
lines.push(
|
|
239
|
+
"",
|
|
240
|
+
"next:",
|
|
241
|
+
"Use tells when one message is not enough and you need the thread, the person, or the pattern over time.",
|
|
242
|
+
"- quick next paid step: https://tells.voiddo.com/deep-dive/?ref=message-next-step-cli",
|
|
243
|
+
"- recurring reads: https://tells.voiddo.com/?ref=message-next-step-cli"
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
return lines.join("\n");
|
|
247
|
+
}
|