standout 0.1.0 → 0.5.15
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 +2 -1
- package/dist/ai-usage.d.ts +164 -0
- package/dist/ai-usage.js +1303 -0
- package/dist/api.d.ts +10 -0
- package/dist/api.js +86 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +351 -0
- package/dist/cursor.d.ts +54 -0
- package/dist/cursor.js +487 -0
- package/dist/dev-env.d.ts +28 -0
- package/dist/dev-env.js +264 -0
- package/dist/gather.d.ts +81 -0
- package/dist/gather.js +885 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +25 -0
- package/dist/prompt.d.ts +1 -0
- package/dist/prompt.js +247 -0
- package/dist/redact.d.ts +1 -0
- package/dist/redact.js +37 -0
- package/dist/tools.d.ts +4 -0
- package/dist/tools.js +182 -0
- package/dist/twitter-scrape.d.ts +1 -0
- package/dist/twitter-scrape.js +267 -0
- package/dist/wrapped/aggregate.d.ts +6 -0
- package/dist/wrapped/aggregate.js +792 -0
- package/dist/wrapped/chrono-critters.d.ts +8 -0
- package/dist/wrapped/chrono-critters.js +32 -0
- package/dist/wrapped/index.d.ts +3 -0
- package/dist/wrapped/index.js +2 -0
- package/dist/wrapped/mascots.d.ts +2 -0
- package/dist/wrapped/mascots.js +35 -0
- package/dist/wrapped/preview.d.ts +1 -0
- package/dist/wrapped/preview.js +93 -0
- package/dist/wrapped/render.d.ts +14 -0
- package/dist/wrapped/render.js +996 -0
- package/dist/wrapped/tiers.d.ts +9 -0
- package/dist/wrapped/tiers.js +73 -0
- package/dist/wrapped/types.d.ts +184 -0
- package/dist/wrapped/types.js +4 -0
- package/dist/wrapped-client.d.ts +10 -0
- package/dist/wrapped-client.js +36 -0
- package/dist/wrapped-share.d.ts +3 -0
- package/dist/wrapped-share.js +27 -0
- package/package.json +35 -8
- package/bin/cli.mjs +0 -30
|
@@ -0,0 +1,996 @@
|
|
|
1
|
+
import boxen from "boxen";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import gradient from "gradient-string";
|
|
4
|
+
import { createInterface } from "readline";
|
|
5
|
+
import { chronoCritterForHour } from "./chrono-critters.js";
|
|
6
|
+
import { MASCOTS } from "./mascots.js";
|
|
7
|
+
import { tierForScore } from "./tiers.js";
|
|
8
|
+
const BOX_WIDTH = 56;
|
|
9
|
+
const SPARK_BARS = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
10
|
+
const TITLE_GRADIENT = gradient(["#ff8a00", "#ff4d6d", "#ad5cff"]);
|
|
11
|
+
// ───────────────── input helpers ─────────────────
|
|
12
|
+
async function waitForKey() {
|
|
13
|
+
if (!process.stdin.isTTY) {
|
|
14
|
+
// No keypresses to wait for when captured (e.g. inside Claude Code); advance
|
|
15
|
+
// immediately so the full wrapped prints without seconds of dead time.
|
|
16
|
+
return "\r";
|
|
17
|
+
}
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
process.stdin.setRawMode(true);
|
|
20
|
+
process.stdin.resume();
|
|
21
|
+
const handler = (data) => {
|
|
22
|
+
process.stdin.setRawMode(false);
|
|
23
|
+
process.stdin.pause();
|
|
24
|
+
process.stdin.removeListener("data", handler);
|
|
25
|
+
const key = data.toString();
|
|
26
|
+
// Ctrl-C → exit cleanly
|
|
27
|
+
if (key === "\u0003") {
|
|
28
|
+
process.stdout.write("\n");
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
resolve(key);
|
|
32
|
+
};
|
|
33
|
+
process.stdin.on("data", handler);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async function askLine(prompt) {
|
|
37
|
+
if (!process.stdin.isTTY)
|
|
38
|
+
return "y";
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
const rl = createInterface({
|
|
41
|
+
input: process.stdin,
|
|
42
|
+
output: process.stderr,
|
|
43
|
+
});
|
|
44
|
+
rl.question(prompt, (ans) => {
|
|
45
|
+
rl.close();
|
|
46
|
+
resolve(ans.trim());
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// ───────────────── rendering helpers ─────────────────
|
|
51
|
+
function box(content, opts = {}) {
|
|
52
|
+
return boxen(content, {
|
|
53
|
+
padding: { top: 1, bottom: 1, left: 2, right: 2 },
|
|
54
|
+
margin: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
55
|
+
width: BOX_WIDTH,
|
|
56
|
+
borderStyle: "round",
|
|
57
|
+
borderColor: opts.borderColor ?? "gray",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function usageReady(view) {
|
|
61
|
+
return view.readiness.usage_stats_status === "ready";
|
|
62
|
+
}
|
|
63
|
+
function hasHistogramData(view) {
|
|
64
|
+
return view.hour_histogram.some((v) => v > 0);
|
|
65
|
+
}
|
|
66
|
+
function divider(label) {
|
|
67
|
+
return chalk.dim(`\n ${label.padEnd(48, " ")} ${chalk.dim("[press ↵]")}\n`);
|
|
68
|
+
}
|
|
69
|
+
function wrapWords(text, width) {
|
|
70
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
71
|
+
const lines = [];
|
|
72
|
+
let line = "";
|
|
73
|
+
for (const w of words) {
|
|
74
|
+
if (line && line.length + w.length + 1 > width) {
|
|
75
|
+
lines.push(line);
|
|
76
|
+
line = w;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
line = line ? `${line} ${w}` : w;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (line)
|
|
83
|
+
lines.push(line);
|
|
84
|
+
return lines;
|
|
85
|
+
}
|
|
86
|
+
function bigNumber(s) {
|
|
87
|
+
return TITLE_GRADIENT(s);
|
|
88
|
+
}
|
|
89
|
+
// Inner content width of a box() — BOX_WIDTH minus borders (2) and h-padding (4).
|
|
90
|
+
const INNER_WIDTH = BOX_WIDTH - 6;
|
|
91
|
+
function leftPad(visibleLen) {
|
|
92
|
+
return " ".repeat(Math.max(0, Math.floor((INNER_WIDTH - visibleLen) / 2)));
|
|
93
|
+
}
|
|
94
|
+
function centerText(raw, color) {
|
|
95
|
+
const pad = leftPad(raw.length);
|
|
96
|
+
return pad + (color ? color(raw) : raw);
|
|
97
|
+
}
|
|
98
|
+
// Center a multi-line ASCII figure as a block — one shared left margin keeps the
|
|
99
|
+
// art's internal alignment intact.
|
|
100
|
+
function centerBlock(art, color) {
|
|
101
|
+
const lines = art.split("\n");
|
|
102
|
+
const width = Math.max(...lines.map((l) => l.length));
|
|
103
|
+
const pad = leftPad(width);
|
|
104
|
+
return lines.map((l) => pad + (color ? color(l) : l)).join("\n");
|
|
105
|
+
}
|
|
106
|
+
function bar(value, max, width) {
|
|
107
|
+
if (max === 0)
|
|
108
|
+
return " ".repeat(width);
|
|
109
|
+
const filled = Math.round((value / max) * width);
|
|
110
|
+
return (chalk.hex("#ff8a00")("█".repeat(filled)) +
|
|
111
|
+
chalk.gray("░".repeat(width - filled)));
|
|
112
|
+
}
|
|
113
|
+
// Compact line/number formatting: 230482 → "230.5k", 3200 → "3.2k".
|
|
114
|
+
function compactNum(n) {
|
|
115
|
+
const sign = n < 0 ? "-" : "";
|
|
116
|
+
const abs = Math.abs(n);
|
|
117
|
+
if (abs >= 1000)
|
|
118
|
+
return `${sign}${(abs / 1000).toFixed(abs >= 100000 ? 0 : 1)}k`;
|
|
119
|
+
return `${sign}${abs}`;
|
|
120
|
+
}
|
|
121
|
+
function coloredBar(value, max, width, hex) {
|
|
122
|
+
if (max <= 0)
|
|
123
|
+
return chalk.gray("░".repeat(width));
|
|
124
|
+
const filled = Math.max(0, Math.min(width, Math.round((value / max) * width)));
|
|
125
|
+
return (chalk.hex(hex)("█".repeat(filled)) + chalk.gray("░".repeat(width - filled)));
|
|
126
|
+
}
|
|
127
|
+
function sparkline(buckets) {
|
|
128
|
+
if (!buckets.length)
|
|
129
|
+
return "";
|
|
130
|
+
const max = Math.max(...buckets, 1);
|
|
131
|
+
return buckets
|
|
132
|
+
.map((v) => SPARK_BARS[Math.min(SPARK_BARS.length - 1, Math.floor((v / max) * (SPARK_BARS.length - 1)))])
|
|
133
|
+
.join("");
|
|
134
|
+
}
|
|
135
|
+
function hourlyChart(buckets, critter = null) {
|
|
136
|
+
const spark = sparkline(buckets);
|
|
137
|
+
const width = 24;
|
|
138
|
+
const axis = new Array(width).fill(" ");
|
|
139
|
+
for (const label of [
|
|
140
|
+
{ at: 0, text: "00" },
|
|
141
|
+
{ at: 6, text: "06" },
|
|
142
|
+
{ at: 12, text: "12" },
|
|
143
|
+
{ at: 18, text: "18" },
|
|
144
|
+
{ at: 22, text: "23" },
|
|
145
|
+
]) {
|
|
146
|
+
for (let i = 0; i < label.text.length; i++) {
|
|
147
|
+
axis[label.at + i] = label.text[i];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const left = [
|
|
151
|
+
chalk.hex("#ad5cff")(spark.padEnd(width, " ")),
|
|
152
|
+
chalk.dim(axis.join("")),
|
|
153
|
+
];
|
|
154
|
+
if (!critter)
|
|
155
|
+
return left.map((l) => ` ${l}`);
|
|
156
|
+
const critterLines = critter.art.split("\n");
|
|
157
|
+
const blank = " ".repeat(width);
|
|
158
|
+
const rows = Math.max(left.length, critterLines.length);
|
|
159
|
+
// Bottom-align the chart within the (taller) critter so the graph + axis sit
|
|
160
|
+
// low, right above the caption, instead of floating at the top with a gap.
|
|
161
|
+
const topPad = rows - left.length;
|
|
162
|
+
return Array.from({ length: rows }, (_unused, i) => {
|
|
163
|
+
const leftIdx = i - topPad;
|
|
164
|
+
const leftStr = leftIdx >= 0 ? (left[leftIdx] ?? blank) : blank;
|
|
165
|
+
const right = critterLines[i] ? " " + critter.color(critterLines[i]) : "";
|
|
166
|
+
return ` ${leftStr}${right}`;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function monthShort(month) {
|
|
170
|
+
const monthIndex = Number(month.slice(5, 7)) - 1;
|
|
171
|
+
return ([
|
|
172
|
+
"Jan",
|
|
173
|
+
"Feb",
|
|
174
|
+
"Mar",
|
|
175
|
+
"Apr",
|
|
176
|
+
"May",
|
|
177
|
+
"Jun",
|
|
178
|
+
"Jul",
|
|
179
|
+
"Aug",
|
|
180
|
+
"Sep",
|
|
181
|
+
"Oct",
|
|
182
|
+
"Nov",
|
|
183
|
+
"Dec",
|
|
184
|
+
][monthIndex] ?? month);
|
|
185
|
+
}
|
|
186
|
+
function monthYearShort(month) {
|
|
187
|
+
const year = month.slice(2, 4);
|
|
188
|
+
return `${monthShort(month)} ${year}`;
|
|
189
|
+
}
|
|
190
|
+
// ───────────────── card builders ─────────────────
|
|
191
|
+
function card1Open(view) {
|
|
192
|
+
if (!usageReady(view) || view.total_sessions <= 0)
|
|
193
|
+
return "";
|
|
194
|
+
const hours = Math.round(view.total_hours).toLocaleString();
|
|
195
|
+
const perDay = view.total_hours / 30;
|
|
196
|
+
const verdict = perDay >= 6
|
|
197
|
+
? "that's a second full-time job. 996 who?"
|
|
198
|
+
: perDay >= 3
|
|
199
|
+
? "basically a part-time gig with the machines."
|
|
200
|
+
: perDay >= 1
|
|
201
|
+
? "a daily habit at this point."
|
|
202
|
+
: "just getting warmed up.";
|
|
203
|
+
const lines = [
|
|
204
|
+
chalk.dim("YOU MANAGED"),
|
|
205
|
+
"",
|
|
206
|
+
bigNumber(` ${hours} session-hours`),
|
|
207
|
+
"",
|
|
208
|
+
chalk.white(` ${verdict}`),
|
|
209
|
+
chalk.dim(" across parallel agent sessions, last 30 days"),
|
|
210
|
+
].join("\n");
|
|
211
|
+
return box(lines, { borderColor: "yellow" });
|
|
212
|
+
}
|
|
213
|
+
function cardAllTime(view) {
|
|
214
|
+
if (!usageReady(view) || view.all_time.total_sessions <= 0)
|
|
215
|
+
return "";
|
|
216
|
+
const months = view.all_time.monthly_buckets.slice(-12);
|
|
217
|
+
const max = Math.max(...months.map((m) => m.duration_hours), 1);
|
|
218
|
+
const rows = months.map((m) => {
|
|
219
|
+
const label = monthYearShort(m.month).padEnd(6);
|
|
220
|
+
return ` ${chalk.dim(label)} ${bar(m.duration_hours, max, 20)} ${chalk.dim(`${Math.round(m.duration_hours)}h`)}`;
|
|
221
|
+
});
|
|
222
|
+
const since = view.all_time.first_session
|
|
223
|
+
? new Date(view.all_time.first_session).toISOString().slice(0, 7)
|
|
224
|
+
: "your first local log";
|
|
225
|
+
const latest = months[months.length - 1];
|
|
226
|
+
const prior = months[months.length - 2];
|
|
227
|
+
const best = [...months].sort((a, b) => b.duration_hours - a.duration_hours)[0];
|
|
228
|
+
const trend = latest && prior && prior.duration_hours > 0
|
|
229
|
+
? `${monthShort(latest.month)}: ${latest.duration_hours >= prior.duration_hours ? "+" : ""}${Math.round(((latest.duration_hours - prior.duration_hours) / prior.duration_hours) * 100)}% vs previous month`
|
|
230
|
+
: latest && prior && latest.duration_hours > 0
|
|
231
|
+
? `${monthShort(latest.month)}: active after a quiet month`
|
|
232
|
+
: null;
|
|
233
|
+
const lines = [
|
|
234
|
+
chalk.dim("YOUR AI CODING YEAR"),
|
|
235
|
+
"",
|
|
236
|
+
` ${bigNumber(`~${Math.round(view.all_time.total_hours).toLocaleString()} hours`)} from local logs`,
|
|
237
|
+
` ${chalk.white(view.all_time.total_sessions.toLocaleString())} sessions detected since ${since}`,
|
|
238
|
+
...(trend ? [` ${chalk.dim(trend)}`] : []),
|
|
239
|
+
"",
|
|
240
|
+
...rows,
|
|
241
|
+
best
|
|
242
|
+
? ` ${chalk.dim(`best local-log month: ${monthShort(best.month)} · ~${Math.round(best.duration_hours)}h`)}`
|
|
243
|
+
: "",
|
|
244
|
+
].join("\n");
|
|
245
|
+
return box(lines, { borderColor: "cyan" });
|
|
246
|
+
}
|
|
247
|
+
// WHEN YOU CODE: time-of-day rhythm + the weekday/weekend split. The peak hour
|
|
248
|
+
// resolves to a chrono-critter (morning bird / daytimer / night owl) drawn to
|
|
249
|
+
// the right of the graph in the card's border color, with a caption below. The
|
|
250
|
+
// LLM rhythm line is preferred; the deterministic subhead is the fallback.
|
|
251
|
+
function cardRhythm(view) {
|
|
252
|
+
if (!usageReady(view) || !hasHistogramData(view))
|
|
253
|
+
return "";
|
|
254
|
+
const borderColor = "magenta";
|
|
255
|
+
const peak = view.peak_hour;
|
|
256
|
+
const critter = peak !== null ? chronoCritterForHour(peak) : null;
|
|
257
|
+
const caption = critter
|
|
258
|
+
? `${critter.label}: peak contributions at ${peak}:00`
|
|
259
|
+
: "no clear peak";
|
|
260
|
+
const weekendPct = Math.round(view.weekend_ratio * 100);
|
|
261
|
+
const weekdayPct = 100 - weekendPct;
|
|
262
|
+
const isNightOwl = critter?.key === "night_owl";
|
|
263
|
+
const subhead = isNightOwl
|
|
264
|
+
? "while the 9-5ers sleep, you ship."
|
|
265
|
+
: weekendPct < 10
|
|
266
|
+
? "locked in on weekdays, weekends are yours."
|
|
267
|
+
: weekendPct >= 35
|
|
268
|
+
? "weekends are just more reps for you."
|
|
269
|
+
: peak !== null && peak < 9
|
|
270
|
+
? "up before standup. menace."
|
|
271
|
+
: "you keep your own hours.";
|
|
272
|
+
const lines = [
|
|
273
|
+
chalk.dim("WHEN YOU CODE"),
|
|
274
|
+
"",
|
|
275
|
+
...hourlyChart(view.hour_histogram, critter ? { art: critter.art, color: chalk.magenta } : null),
|
|
276
|
+
"",
|
|
277
|
+
` ${chalk.white(caption)}`,
|
|
278
|
+
"",
|
|
279
|
+
` ${chalk.white("weekdays".padEnd(9))} ${bar(weekdayPct, 100, 20)} ${chalk.dim(weekdayPct + "%")}`,
|
|
280
|
+
` ${chalk.white("weekends".padEnd(9))} ${bar(weekendPct, 100, 20)} ${chalk.dim(weekendPct + "%")}`,
|
|
281
|
+
"",
|
|
282
|
+
];
|
|
283
|
+
if (view.rhythm_comment) {
|
|
284
|
+
for (const l of wrapWords(view.rhythm_comment, 47))
|
|
285
|
+
lines.push(chalk.italic.white(" " + l));
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
lines.push(chalk.dim(" " + subhead));
|
|
289
|
+
}
|
|
290
|
+
return box(lines.join("\n"), { borderColor });
|
|
291
|
+
}
|
|
292
|
+
// Capitalize each word of a repo slug: "idss-backend" → "Idss-Backend".
|
|
293
|
+
function titleCaseName(name) {
|
|
294
|
+
return name
|
|
295
|
+
.split(/([-_ ])/)
|
|
296
|
+
.map((part) => /[-_ ]/.test(part) ? part : part.charAt(0).toUpperCase() + part.slice(1))
|
|
297
|
+
.join("");
|
|
298
|
+
}
|
|
299
|
+
// Top 3 projects across AI sessions AND git repos you committed to. The #1
|
|
300
|
+
// project is the hero (name + commits in gradient, description in white); the
|
|
301
|
+
// rest are white titles with faint one-liners. Leads with the shipped summary.
|
|
302
|
+
function cardProjects(view) {
|
|
303
|
+
if (!usageReady(view))
|
|
304
|
+
return "";
|
|
305
|
+
const projects = view.top_projects
|
|
306
|
+
.filter((p) => p.commits > 0 || p.sessions > 0)
|
|
307
|
+
.slice(0, 3);
|
|
308
|
+
if (projects.length === 0)
|
|
309
|
+
return "";
|
|
310
|
+
const lines = [chalk.dim("YOUR TOP PROJECTS"), ""];
|
|
311
|
+
const c = view.contributions;
|
|
312
|
+
if (c && c.one_liner) {
|
|
313
|
+
for (const l of wrapWords(c.one_liner, 50))
|
|
314
|
+
lines.push(chalk.dim(l));
|
|
315
|
+
lines.push("");
|
|
316
|
+
}
|
|
317
|
+
projects.forEach((p, i) => {
|
|
318
|
+
const count = p.commits > 0
|
|
319
|
+
? `${p.commits.toLocaleString()} commit${p.commits === 1 ? "" : "s"}`
|
|
320
|
+
: `${p.sessions} session${p.sessions === 1 ? "" : "s"}`;
|
|
321
|
+
const name = titleCaseName(p.name);
|
|
322
|
+
if (i === 0) {
|
|
323
|
+
lines.push(` ${bigNumber(`▸ ${name}`)} ${TITLE_GRADIENT(count)}`);
|
|
324
|
+
if (p.blurb)
|
|
325
|
+
for (const l of wrapWords(p.blurb, 44))
|
|
326
|
+
lines.push(` ${chalk.white(l)}`);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
const namePart = `▸ ${name}`;
|
|
330
|
+
const pad = " ".repeat(Math.max(2, 24 - namePart.length));
|
|
331
|
+
lines.push(` ${chalk.white(namePart)}${pad}${chalk.dim(count)}`);
|
|
332
|
+
if (p.blurb)
|
|
333
|
+
for (const l of wrapWords(p.blurb, 44))
|
|
334
|
+
lines.push(` ${chalk.dim(l)}`);
|
|
335
|
+
}
|
|
336
|
+
lines.push("");
|
|
337
|
+
});
|
|
338
|
+
return box(lines.join("\n").replace(/\n+$/, ""), { borderColor: "green" });
|
|
339
|
+
}
|
|
340
|
+
function card7AINative(view) {
|
|
341
|
+
if (view.total_commits <= 0)
|
|
342
|
+
return "";
|
|
343
|
+
const pct = Math.round(view.ai_assisted_pct * 100);
|
|
344
|
+
const subhead = pct >= 50
|
|
345
|
+
? "cyborg arc: most commits ship with Claude now."
|
|
346
|
+
: pct > 0
|
|
347
|
+
? "Claude's in your commit history. no going back."
|
|
348
|
+
: "no AI-co-authored commits yet.";
|
|
349
|
+
const lines = [
|
|
350
|
+
chalk.dim("YOU'RE AI-NATIVE"),
|
|
351
|
+
"",
|
|
352
|
+
` commits since 2024: ${chalk.white(view.total_commits.toLocaleString())}`,
|
|
353
|
+
` AI co-authored: ${chalk.white(view.ai_assisted_commits.toLocaleString())}`,
|
|
354
|
+
"",
|
|
355
|
+
` ${bar(pct, 100, 26)} ${chalk.white(pct + "%")}`,
|
|
356
|
+
"",
|
|
357
|
+
chalk.dim(subhead),
|
|
358
|
+
].join("\n");
|
|
359
|
+
return box(lines, { borderColor: "yellow" });
|
|
360
|
+
}
|
|
361
|
+
function card8Standing(view) {
|
|
362
|
+
if (view.early_adopter || !view.percentile_bands) {
|
|
363
|
+
const lines = [
|
|
364
|
+
chalk.dim("YOUR STANDING"),
|
|
365
|
+
"",
|
|
366
|
+
` ${bigNumber("EARLY ADOPTER")}`,
|
|
367
|
+
"",
|
|
368
|
+
chalk.dim(`you're 1 of ${view.cohort_size} devs in the network so far.`),
|
|
369
|
+
chalk.dim("percentile rankings unlock at 100 wrapped profiles."),
|
|
370
|
+
].join("\n");
|
|
371
|
+
return box(lines, { borderColor: "magenta" });
|
|
372
|
+
}
|
|
373
|
+
const bands = view.percentile_bands;
|
|
374
|
+
const row = (label, percentile) => {
|
|
375
|
+
if (percentile === null)
|
|
376
|
+
return null;
|
|
377
|
+
const top = 100 - percentile;
|
|
378
|
+
const w = Math.max(2, Math.round((top / 100) * 28));
|
|
379
|
+
return ` ${chalk.hex("#ff8a00")("█".repeat(w))} ${chalk.dim("TOP " + top + "%")}\n ${chalk.dim(label)}`;
|
|
380
|
+
};
|
|
381
|
+
const rows = [
|
|
382
|
+
row("TypeScript shippers", bands.typescript_shippers),
|
|
383
|
+
row("AI-native engineers", bands.ai_native),
|
|
384
|
+
row("Open-source contributors", bands.open_source),
|
|
385
|
+
].filter(Boolean);
|
|
386
|
+
const lines = [chalk.dim("YOUR STANDING"), "", ...rows].join("\n\n");
|
|
387
|
+
return box(lines, { borderColor: "magenta" });
|
|
388
|
+
}
|
|
389
|
+
function topPercentNumber(percentile) {
|
|
390
|
+
if (percentile === null)
|
|
391
|
+
return null;
|
|
392
|
+
return Math.max(1, 100 - percentile);
|
|
393
|
+
}
|
|
394
|
+
function topPercent(percentile) {
|
|
395
|
+
const top = topPercentNumber(percentile);
|
|
396
|
+
return top === null ? null : `top ${top}%`;
|
|
397
|
+
}
|
|
398
|
+
function cardRankSummary(view) {
|
|
399
|
+
if (!usageReady(view) || view.total_sessions <= 0)
|
|
400
|
+
return "";
|
|
401
|
+
const rank = view.rank_summary;
|
|
402
|
+
const stats = [
|
|
403
|
+
{
|
|
404
|
+
label: "session-hrs",
|
|
405
|
+
value: `~${Math.round(view.total_hours)}h`,
|
|
406
|
+
bestText: `~${Math.round(view.total_hours)}h of session time`,
|
|
407
|
+
percentile: rank.hours_percentile,
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
label: "sessions",
|
|
411
|
+
value: view.total_sessions.toLocaleString(),
|
|
412
|
+
bestText: `${view.total_sessions.toLocaleString()} sessions`,
|
|
413
|
+
percentile: rank.sessions_percentile,
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
label: "active days",
|
|
417
|
+
value: view.active_days.toLocaleString(),
|
|
418
|
+
bestText: `${view.active_days.toLocaleString()} active days`,
|
|
419
|
+
percentile: rank.active_days_percentile,
|
|
420
|
+
},
|
|
421
|
+
];
|
|
422
|
+
const ranked = stats.map((stat) => ({
|
|
423
|
+
...stat,
|
|
424
|
+
top: topPercent(stat.percentile),
|
|
425
|
+
topNumber: topPercentNumber(stat.percentile),
|
|
426
|
+
}));
|
|
427
|
+
const best = ranked
|
|
428
|
+
.filter((stat) => stat.topNumber !== null)
|
|
429
|
+
.sort((a, b) => a.topNumber - b.topNumber)[0];
|
|
430
|
+
const cohort = rank.sample_size > 0
|
|
431
|
+
? `across ${rank.sample_size.toLocaleString()} Standout users`
|
|
432
|
+
: view.cohort_size > 0
|
|
433
|
+
? `among ${view.cohort_size.toLocaleString()} Standout users so far`
|
|
434
|
+
: "Standout profile summary";
|
|
435
|
+
const row = (stat) => {
|
|
436
|
+
const top = "top" in stat && stat.top
|
|
437
|
+
? chalk.hex("#ff8a00")(stat.top)
|
|
438
|
+
: chalk.dim("warming up");
|
|
439
|
+
return ` ${chalk.white(stat.label.padEnd(12))} ${chalk.dim(stat.value.padStart(10))} ${top}`;
|
|
440
|
+
};
|
|
441
|
+
const lines = [
|
|
442
|
+
chalk.dim("WHERE YOU STAND"),
|
|
443
|
+
"",
|
|
444
|
+
best
|
|
445
|
+
? ` ${bigNumber(`YOU ARE TOP ${best.topNumber}%`)}`
|
|
446
|
+
: ` ${bigNumber("EARLY")}`,
|
|
447
|
+
` ${chalk.dim(cohort)}`,
|
|
448
|
+
"",
|
|
449
|
+
best
|
|
450
|
+
? ` best stat: ${chalk.white(best.bestText)} last 30 days`
|
|
451
|
+
: ` best stats are still warming up`,
|
|
452
|
+
"",
|
|
453
|
+
...ranked.map(row),
|
|
454
|
+
"",
|
|
455
|
+
` ${chalk.dim(`~${Math.round(view.all_time.total_hours).toLocaleString()}h from local logs · ${view.all_time.total_sessions.toLocaleString()} sessions`)}`,
|
|
456
|
+
].join("\n");
|
|
457
|
+
return box(lines, { borderColor: "magenta" });
|
|
458
|
+
}
|
|
459
|
+
function formatTokens(n) {
|
|
460
|
+
if (n >= 1_000_000_000)
|
|
461
|
+
return (n / 1_000_000_000).toFixed(1) + "B";
|
|
462
|
+
if (n >= 1_000_000)
|
|
463
|
+
return (n / 1_000_000).toFixed(1) + "M";
|
|
464
|
+
if (n >= 1_000)
|
|
465
|
+
return (n / 1_000).toFixed(1) + "K";
|
|
466
|
+
return n.toLocaleString();
|
|
467
|
+
}
|
|
468
|
+
function cardTokens(view) {
|
|
469
|
+
const t = view.tokens;
|
|
470
|
+
if (!usageReady(view) || (t.work_tokens <= 0 && t.cache_tokens <= 0))
|
|
471
|
+
return "";
|
|
472
|
+
const totalLine = bigNumber(formatTokens(t.total_30d) + " tokens");
|
|
473
|
+
const max = Math.max(...t.by_tool.map((x) => x.tokens), 1);
|
|
474
|
+
const toolRows = t.by_tool.length > 0
|
|
475
|
+
? t.by_tool
|
|
476
|
+
.slice(0, 3)
|
|
477
|
+
.map((x) => {
|
|
478
|
+
const padTool = x.tool.padEnd(13, " ");
|
|
479
|
+
return ` ${chalk.white(padTool)} ${bar(x.tokens, max, 16)} ${chalk.dim(formatTokens(x.tokens))}`;
|
|
480
|
+
})
|
|
481
|
+
.join("\n")
|
|
482
|
+
: chalk.dim(" (no tool breakdown available)");
|
|
483
|
+
const deltaLine = t.multiplier_vs_prior !== null && t.multiplier_vs_prior >= 1.05
|
|
484
|
+
? chalk.hex("#ff8a00")(` ↑ ${t.multiplier_vs_prior.toFixed(1)}× vs last period`)
|
|
485
|
+
: t.multiplier_vs_prior !== null && t.multiplier_vs_prior < 0.95
|
|
486
|
+
? chalk.dim(` ↓ ${(1 / t.multiplier_vs_prior).toFixed(1)}× less than last period`)
|
|
487
|
+
: t.total_prior_30d > 0
|
|
488
|
+
? chalk.dim(` ≈ same as last period`)
|
|
489
|
+
: chalk.dim(` rolling 30-day window`);
|
|
490
|
+
const cost = t.estimated_retail_cost_usd;
|
|
491
|
+
const costLine = cost > 0
|
|
492
|
+
? ` ${chalk.dim("would have cost")} ${TITLE_GRADIENT(`~$${cost.toLocaleString()}`)} ${chalk.dim("at retail API rates")}`
|
|
493
|
+
: "";
|
|
494
|
+
const cachedNote = t.cache_tokens > t.work_tokens
|
|
495
|
+
? chalk.dim(" thankfully, most of them were cached :)")
|
|
496
|
+
: t.cache_tokens > 0
|
|
497
|
+
? chalk.dim(" (includes cached context, re-read each turn)")
|
|
498
|
+
: "";
|
|
499
|
+
const generatedLine = t.work_tokens > 0
|
|
500
|
+
? ` ${chalk.white(formatTokens(t.work_tokens))} ${chalk.dim("were actually generated fresh")}`
|
|
501
|
+
: "";
|
|
502
|
+
const lines = [
|
|
503
|
+
chalk.dim("YOU BURNED THIS MANY TOKENS"),
|
|
504
|
+
"",
|
|
505
|
+
` ${totalLine}`,
|
|
506
|
+
"",
|
|
507
|
+
cachedNote,
|
|
508
|
+
generatedLine,
|
|
509
|
+
costLine,
|
|
510
|
+
"",
|
|
511
|
+
" last 30 days, across:",
|
|
512
|
+
toolRows,
|
|
513
|
+
view.tokens.cursor_untracked
|
|
514
|
+
? chalk.dim(" Cursor used too (its tokens aren't tracked locally)")
|
|
515
|
+
: "",
|
|
516
|
+
"",
|
|
517
|
+
deltaLine,
|
|
518
|
+
]
|
|
519
|
+
.filter(Boolean)
|
|
520
|
+
.join("\n");
|
|
521
|
+
return box(lines, { borderColor: "yellow" });
|
|
522
|
+
}
|
|
523
|
+
function cardToolRelationship(view) {
|
|
524
|
+
if (!usageReady(view))
|
|
525
|
+
return "";
|
|
526
|
+
const r = view.tool_relationship;
|
|
527
|
+
if (r.kind === "insufficient") {
|
|
528
|
+
return ""; // empty card — won't render
|
|
529
|
+
}
|
|
530
|
+
// Longest consecutive-day streak — a commitment stat that rides along with the
|
|
531
|
+
// tool-loyalty story (verbatim from the old standalone streak card).
|
|
532
|
+
const streakLine = view.streak_days >= 2
|
|
533
|
+
? ` ${chalk.white(`${view.streak_days}-day streak, coding without missing a day.`)}`
|
|
534
|
+
: null;
|
|
535
|
+
if (r.kind === "switch") {
|
|
536
|
+
const rows = r.timeline.slice(-6).map((t) => {
|
|
537
|
+
const monthShort = t.month.slice(2).replace("-", "/");
|
|
538
|
+
const filled = Math.round(t.share * 10);
|
|
539
|
+
const bar = chalk.hex("#ff8a00")("█".repeat(filled)) +
|
|
540
|
+
chalk.gray("░".repeat(10 - filled));
|
|
541
|
+
const label = t.dominant === r.from_tool
|
|
542
|
+
? chalk.dim(t.dominant)
|
|
543
|
+
: chalk.white(t.dominant);
|
|
544
|
+
return ` ${chalk.dim(monthShort)} ${bar} ${label}`;
|
|
545
|
+
});
|
|
546
|
+
return box([
|
|
547
|
+
chalk.dim("YOU SWITCHED TOOLS"),
|
|
548
|
+
"",
|
|
549
|
+
...rows,
|
|
550
|
+
"",
|
|
551
|
+
chalk.dim(` joined the "${r.from_tool} → ${r.to_tool}" club`),
|
|
552
|
+
chalk.dim(` in ${r.switch_month}.`),
|
|
553
|
+
...(streakLine ? ["", streakLine] : []),
|
|
554
|
+
].join("\n"), { borderColor: "cyan" });
|
|
555
|
+
}
|
|
556
|
+
if (r.kind === "loyalist") {
|
|
557
|
+
return box([
|
|
558
|
+
chalk.dim("YOU'RE A LOYALIST"),
|
|
559
|
+
"",
|
|
560
|
+
` ${bigNumber(r.tool.toUpperCase())}`,
|
|
561
|
+
"",
|
|
562
|
+
chalk.dim(` ${r.sessions_count} sessions across ${r.months_count} month${r.months_count === 1 ? "" : "s"}.`),
|
|
563
|
+
chalk.dim(" no second-guessing. you knew what you wanted."),
|
|
564
|
+
...(streakLine ? ["", streakLine] : []),
|
|
565
|
+
].join("\n"), { borderColor: "cyan" });
|
|
566
|
+
}
|
|
567
|
+
if (r.kind === "polyglot") {
|
|
568
|
+
const rows = r.tools.slice(0, 4).map((t) => {
|
|
569
|
+
const filled = Math.round(t.share * 22);
|
|
570
|
+
const bar = chalk.hex("#ff8a00")("█".repeat(filled)) +
|
|
571
|
+
chalk.gray("░".repeat(22 - filled));
|
|
572
|
+
return ` ${chalk.white(t.tool.padEnd(12, " "))} ${bar} ${chalk.dim(Math.round(t.share * 100) + "%")}`;
|
|
573
|
+
});
|
|
574
|
+
return box([
|
|
575
|
+
chalk.dim("YOU'RE A POLYGLOT"),
|
|
576
|
+
"",
|
|
577
|
+
...rows,
|
|
578
|
+
"",
|
|
579
|
+
chalk.dim(" you don't pick favorites. respect."),
|
|
580
|
+
...(streakLine ? ["", streakLine] : []),
|
|
581
|
+
].join("\n"), { borderColor: "cyan" });
|
|
582
|
+
}
|
|
583
|
+
return "";
|
|
584
|
+
}
|
|
585
|
+
function shortenModelName(name) {
|
|
586
|
+
const n = name.toLowerCase();
|
|
587
|
+
const ver = (s) => s.replace(/-/g, ".");
|
|
588
|
+
let mm;
|
|
589
|
+
if ((mm = n.match(/^claude-(opus|sonnet|haiku)-(\d+(?:-\d+)*)/)))
|
|
590
|
+
return `${mm[1][0].toUpperCase()}${mm[1].slice(1)} ${ver(mm[2])}`;
|
|
591
|
+
if ((mm = n.match(/^gpt-(\d+(?:[.-]\d+)*)(?:-(.+))?$/)))
|
|
592
|
+
return `GPT-${ver(mm[1])}${mm[2] ? " " + mm[2] : ""}`;
|
|
593
|
+
if ((mm = n.match(/^(o\d+)(?:-(.+))?$/)))
|
|
594
|
+
return `${mm[1]}${mm[2] ? " " + mm[2] : ""}`;
|
|
595
|
+
if ((mm = n.match(/^gemini-(\d+(?:[.-]\d+)*)/)))
|
|
596
|
+
return `Gemini ${ver(mm[1])}`;
|
|
597
|
+
return name.length > 11 ? name.slice(0, 10) + "…" : name;
|
|
598
|
+
}
|
|
599
|
+
function modelProvider(name) {
|
|
600
|
+
const n = name.toLowerCase();
|
|
601
|
+
if (/claude|opus|sonnet|haiku/.test(n))
|
|
602
|
+
return "anthropic";
|
|
603
|
+
if (/gpt|^o\d/.test(n))
|
|
604
|
+
return "openai";
|
|
605
|
+
if (n.includes("gemini"))
|
|
606
|
+
return "gemini";
|
|
607
|
+
return "other";
|
|
608
|
+
}
|
|
609
|
+
function modelProviderColor(provider) {
|
|
610
|
+
if (provider === "anthropic")
|
|
611
|
+
return "#ff8a00";
|
|
612
|
+
if (provider === "openai")
|
|
613
|
+
return "#00d563";
|
|
614
|
+
if (provider === "gemini")
|
|
615
|
+
return "#4f8cff";
|
|
616
|
+
return "#888888";
|
|
617
|
+
}
|
|
618
|
+
// A 1st/2nd/3rd podium: champion on the tallest center pedestal under a crown,
|
|
619
|
+
// runners-up stepped down on the sides. Pedestals are tinted by provider.
|
|
620
|
+
function cardModelsStack(view) {
|
|
621
|
+
if (!usageReady(view))
|
|
622
|
+
return "";
|
|
623
|
+
const m = view.models;
|
|
624
|
+
if (!m.top_model)
|
|
625
|
+
return "";
|
|
626
|
+
const totalSessions = Math.max(m.by_provider.reduce((s, p) => s + p.sessions, 0), 1);
|
|
627
|
+
const ranked = m.by_provider
|
|
628
|
+
.flatMap((p) => p.models)
|
|
629
|
+
.sort((a, b) => b.sessions - a.sessions)
|
|
630
|
+
.slice(0, 3)
|
|
631
|
+
.map((mm, i) => ({
|
|
632
|
+
name: shortenModelName(mm.name),
|
|
633
|
+
pct: Math.round((mm.sessions / totalSessions) * 100),
|
|
634
|
+
prov: modelProvider(mm.name),
|
|
635
|
+
rank: i + 1,
|
|
636
|
+
}));
|
|
637
|
+
if (ranked.length === 0)
|
|
638
|
+
return "";
|
|
639
|
+
const [first, second, third] = ranked;
|
|
640
|
+
const COLW = 12;
|
|
641
|
+
const blankCol = " ".repeat(COLW);
|
|
642
|
+
const heights = { 1: 4, 2: 2, 3: 1 };
|
|
643
|
+
const center = (s, w = COLW) => {
|
|
644
|
+
if (s.length >= w)
|
|
645
|
+
return s.slice(0, w);
|
|
646
|
+
const l = Math.floor((w - s.length) / 2);
|
|
647
|
+
return " ".repeat(l) + s + " ".repeat(w - s.length - l);
|
|
648
|
+
};
|
|
649
|
+
// Build a pedestal column top→bottom: [name, pct, cap, body…, base]. Text is
|
|
650
|
+
// centered BEFORE coloring so chalk's ANSI codes don't break the width math.
|
|
651
|
+
const column = (mm) => {
|
|
652
|
+
if (!mm)
|
|
653
|
+
return [];
|
|
654
|
+
const c = modelProviderColor(mm.prov);
|
|
655
|
+
const hex = (s) => chalk.hex(c)(s);
|
|
656
|
+
const cap = " " + hex("╭" + "─".repeat(8) + "╮") + " ";
|
|
657
|
+
const base = " " + hex("╰" + "─".repeat(8) + "╯") + " ";
|
|
658
|
+
const side = " " + hex("│") + " ".repeat(8) + hex("│") + " ";
|
|
659
|
+
const num = " " +
|
|
660
|
+
hex("│") +
|
|
661
|
+
chalk.bold.white(center("#" + mm.rank, 8)) +
|
|
662
|
+
hex("│") +
|
|
663
|
+
" ";
|
|
664
|
+
const h = heights[mm.rank];
|
|
665
|
+
const mid = Math.floor((h - 1) / 2);
|
|
666
|
+
const body = [];
|
|
667
|
+
for (let i = 0; i < h; i++)
|
|
668
|
+
body.push(i === mid ? num : side);
|
|
669
|
+
const label = mm.rank === 1 ? bigNumber(center(mm.name)) : chalk.white(center(mm.name));
|
|
670
|
+
return [label, chalk.dim(center(mm.pct + "%")), cap, ...body, base];
|
|
671
|
+
};
|
|
672
|
+
// left = #2, center = #1, right = #3; bottom-align so the bases line up.
|
|
673
|
+
const cols = [column(second), column(first), column(third)];
|
|
674
|
+
const maxH = Math.max(...cols.map((col) => col.length));
|
|
675
|
+
const padded = cols.map((col) => Array(maxH - col.length)
|
|
676
|
+
.fill(blankCol)
|
|
677
|
+
.concat(col));
|
|
678
|
+
const header = ranked.length === 1 ? "YOUR MODEL & STACK" : "YOUR MODELS & STACK";
|
|
679
|
+
const lines = [chalk.dim(header), ""];
|
|
680
|
+
lines.push(" " + [blankCol, chalk.hex("#ffd700")(center("♛")), blankCol].join(" "));
|
|
681
|
+
for (let r = 0; r < maxH; r++)
|
|
682
|
+
lines.push(" " + [padded[0][r], padded[1][r], padded[2][r]].join(" "));
|
|
683
|
+
lines.push("");
|
|
684
|
+
lines.push(` top pick: ${chalk.white(first.name)} ${chalk.dim(`— ${first.pct}% of sessions`)}`);
|
|
685
|
+
// The "what you built in" line: an LLM read of the dominant languages + real
|
|
686
|
+
// frameworks (no raw file-type list — it was mostly CI/tooling noise). Wrap
|
|
687
|
+
// narrow enough that the 3-space indent keeps every line within the box's inner
|
|
688
|
+
// width (50) — otherwise boxen re-wraps the whole card and trims the podium.
|
|
689
|
+
if (view.stack_comment) {
|
|
690
|
+
lines.push("");
|
|
691
|
+
for (const l of wrapWords(view.stack_comment, 46))
|
|
692
|
+
lines.push(chalk.italic.white(" " + l));
|
|
693
|
+
}
|
|
694
|
+
return box(lines.join("\n"), { borderColor: "magenta" });
|
|
695
|
+
}
|
|
696
|
+
// Net code footprint: are you a Shipper (mostly new code), a Refactorer (rework),
|
|
697
|
+
// or a Cleaner (deletes more than adds)? Red/green +/- line breakdown.
|
|
698
|
+
function cardCodeFootprint(view) {
|
|
699
|
+
const f = view.code_footprint;
|
|
700
|
+
if (!f)
|
|
701
|
+
return "";
|
|
702
|
+
const max = Math.max(f.lines_added, f.lines_deleted, 1);
|
|
703
|
+
const netLabel = `${f.net >= 0 ? "+" : ""}${compactNum(f.net)}`;
|
|
704
|
+
const subhead = f.label === "Cleaner"
|
|
705
|
+
? "you delete more than you add. the slop stops with you."
|
|
706
|
+
: f.label === "Shipper"
|
|
707
|
+
? `${f.additive_ratio >= 0 ? Math.round(f.additive_ratio * 100) : 0}% of your churn is brand-new code.`
|
|
708
|
+
: "you rework as much as you write. nothing ships half-baked.";
|
|
709
|
+
const lines = [
|
|
710
|
+
chalk.dim("YOUR CODE FOOTPRINT"),
|
|
711
|
+
"",
|
|
712
|
+
` ${bigNumber(f.label.toUpperCase())}`,
|
|
713
|
+
"",
|
|
714
|
+
` ${chalk.green("+ " + compactNum(f.lines_added).padEnd(7))} ${coloredBar(f.lines_added, max, 22, "#22c55e")} ${chalk.dim("added")}`,
|
|
715
|
+
` ${chalk.red("- " + compactNum(f.lines_deleted).padEnd(7))} ${coloredBar(f.lines_deleted, max, 22, "#ef4444")} ${chalk.dim("deleted")}`,
|
|
716
|
+
"",
|
|
717
|
+
` ${chalk.white("net " + netLabel)} ${chalk.dim("lines since 2024")}`,
|
|
718
|
+
];
|
|
719
|
+
if (f.median_commit_size > 0)
|
|
720
|
+
lines.push(` ${chalk.dim(`typical commit ~${compactNum(f.median_commit_size)} lines · biggest ${compactNum(f.biggest_commit_size)}`)}`);
|
|
721
|
+
lines.push("");
|
|
722
|
+
if (f.line) {
|
|
723
|
+
for (const l of wrapWords(f.line, 47))
|
|
724
|
+
lines.push(chalk.italic.white(" " + l));
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
lines.push(chalk.dim(" " + subhead));
|
|
728
|
+
}
|
|
729
|
+
return box(lines.join("\n"), { borderColor: "green" });
|
|
730
|
+
}
|
|
731
|
+
// How many agents you run at once. Open-tab span (idle included) vs the peak and
|
|
732
|
+
// the longest-lived session, plus the share of time juggling 2+ at once.
|
|
733
|
+
function cardConcurrency(view) {
|
|
734
|
+
const c = view.concurrency;
|
|
735
|
+
if (!c || c.open_tab_peak <= 1)
|
|
736
|
+
return "";
|
|
737
|
+
const days = c.longest_session_hours / 24;
|
|
738
|
+
const longest = days >= 1.5
|
|
739
|
+
? `${days.toFixed(0)} days`
|
|
740
|
+
: `${Math.round(c.longest_session_hours)}h`;
|
|
741
|
+
const lines = [
|
|
742
|
+
chalk.dim("HOW MANY AGENTS YOU JUGGLE"),
|
|
743
|
+
"",
|
|
744
|
+
` ${bigNumber(`~${c.open_tab_avg}`)} ${chalk.white("agents open at once")}`,
|
|
745
|
+
"",
|
|
746
|
+
` ${chalk.white("peak".padEnd(14))} ${chalk.dim(`${c.open_tab_peak} at once`)}`,
|
|
747
|
+
` ${chalk.white("longest tab".padEnd(14))} ${chalk.dim(`${longest} open`)}`,
|
|
748
|
+
` ${chalk.white("2+ active".padEnd(14))} ${chalk.dim(`${Math.round(c.active_juggle_pct * 100)}% of your coding time`)}`,
|
|
749
|
+
"",
|
|
750
|
+
];
|
|
751
|
+
if (c.line) {
|
|
752
|
+
for (const l of wrapWords(c.line, 47))
|
|
753
|
+
lines.push(chalk.italic.white(" " + l));
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
lines.push(chalk.dim(" a one-agent setup could never."));
|
|
757
|
+
}
|
|
758
|
+
return box(lines.join("\n"), { borderColor: "blue" });
|
|
759
|
+
}
|
|
760
|
+
// How you talk to the agent — LLM read of your real messages, plus the cheap
|
|
761
|
+
// counters (question ratio, message length) and the recurring themes.
|
|
762
|
+
// Trim a verbose collaboration signal to a brief clause: drop everything after
|
|
763
|
+
// the first comma / parenthesis / colon (which is usually examples + quotes),
|
|
764
|
+
// then clean-cut to a short length. Also drops privacy-sensitive quoted phrases.
|
|
765
|
+
function briefSignal(text) {
|
|
766
|
+
// Stop at the first structural break (paren/colon/semicolon/comma/dash) so a
|
|
767
|
+
// long signal with examples or quotes gets trimmed to its lead clause.
|
|
768
|
+
let s = text.split(/[(:;,]| - /)[0].trim();
|
|
769
|
+
if (s.length > 34) {
|
|
770
|
+
const cut = s.slice(0, 34);
|
|
771
|
+
const sp = cut.lastIndexOf(" ");
|
|
772
|
+
s = sp > 18 ? cut.slice(0, sp) : cut;
|
|
773
|
+
}
|
|
774
|
+
return s;
|
|
775
|
+
}
|
|
776
|
+
// HOW YOU TALK + HOW YOU VIBE-CODE merged: the witty prompting-style one-liner,
|
|
777
|
+
// the collaboration identity (label + one-line summary + 2 brief signals), the
|
|
778
|
+
// counters, and recurring themes. No verbatim "weirdest ask" quote (privacy).
|
|
779
|
+
function cardTalk(view) {
|
|
780
|
+
const c = view.conversation;
|
|
781
|
+
const v = view.collaboration;
|
|
782
|
+
if ((!c || c.total_prompts < 10) && !v)
|
|
783
|
+
return "";
|
|
784
|
+
const lines = [chalk.dim("HOW YOU TALK"), ""];
|
|
785
|
+
if (c?.line) {
|
|
786
|
+
for (const l of wrapWords(c.line, 47))
|
|
787
|
+
lines.push(chalk.italic.white(" " + l));
|
|
788
|
+
lines.push("");
|
|
789
|
+
}
|
|
790
|
+
if (v) {
|
|
791
|
+
lines.push(` ${bigNumber(v.style_label)}`);
|
|
792
|
+
if (v.summary)
|
|
793
|
+
for (const l of wrapWords(v.summary, 47))
|
|
794
|
+
lines.push(chalk.white(" " + l));
|
|
795
|
+
const sigs = [
|
|
796
|
+
["reads", v.signals.fact_checking],
|
|
797
|
+
["drives", v.signals.autonomy],
|
|
798
|
+
]
|
|
799
|
+
.filter(([, t]) => t)
|
|
800
|
+
.map(([label, t]) => ` ${chalk.dim(label.padEnd(8))} ${chalk.white(briefSignal(t))}`);
|
|
801
|
+
if (sigs.length > 0) {
|
|
802
|
+
lines.push("");
|
|
803
|
+
lines.push(...sigs);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (c) {
|
|
807
|
+
lines.push("");
|
|
808
|
+
lines.push(` ${chalk.white(c.total_prompts.toLocaleString())} ${chalk.dim("prompts")} · ${chalk.white(Math.round(c.question_ratio * 100) + "%")} ${chalk.dim("questions")} · ${chalk.white("~" + c.avg_prompt_chars)} ${chalk.dim("chars")}`);
|
|
809
|
+
if (c.themes.length > 0)
|
|
810
|
+
lines.push(` ${chalk.dim("recurring: " + c.themes.slice(0, 4).join(", "))}`);
|
|
811
|
+
}
|
|
812
|
+
return box(lines.join("\n"), { borderColor: "magenta" });
|
|
813
|
+
}
|
|
814
|
+
function card9Reveal(view) {
|
|
815
|
+
const mascot = MASCOTS[view.mascot_pose] ?? MASCOTS.default;
|
|
816
|
+
const archetype = bigNumber(view.archetype_label.toUpperCase());
|
|
817
|
+
const lines = [
|
|
818
|
+
chalk.dim(" you are…"),
|
|
819
|
+
"",
|
|
820
|
+
chalk.hex("#ff8a00")(mascot),
|
|
821
|
+
"",
|
|
822
|
+
` ${archetype}`,
|
|
823
|
+
"",
|
|
824
|
+
` ${chalk.italic.white(`"${view.zinger}"`)}`,
|
|
825
|
+
"",
|
|
826
|
+
chalk.dim(" " + view.share_url),
|
|
827
|
+
].join("\n");
|
|
828
|
+
return box(lines, { borderColor: "yellow" });
|
|
829
|
+
}
|
|
830
|
+
// Finale: the shareable /100. Fuses the cohort ranking and the persona reveal —
|
|
831
|
+
// score + standing + mascot + archetype + zinger + the three score bars + callout.
|
|
832
|
+
export function cardProficiency(view) {
|
|
833
|
+
const mascot = MASCOTS[view.mascot_pose] ?? MASCOTS.default;
|
|
834
|
+
const archetype = bigNumber(view.archetype_label.toUpperCase());
|
|
835
|
+
const p = view.proficiency;
|
|
836
|
+
// Persona-only fallback when the server didn't compute a score (older payloads).
|
|
837
|
+
if (!p) {
|
|
838
|
+
return box([
|
|
839
|
+
chalk.dim(" you are…"),
|
|
840
|
+
"",
|
|
841
|
+
chalk.hex("#ff8a00")(mascot),
|
|
842
|
+
"",
|
|
843
|
+
` ${archetype}`,
|
|
844
|
+
"",
|
|
845
|
+
` ${chalk.italic.white(`"${view.zinger}"`)}`,
|
|
846
|
+
"",
|
|
847
|
+
chalk.dim(" " + view.share_url),
|
|
848
|
+
].join("\n"), { borderColor: "yellow" });
|
|
849
|
+
}
|
|
850
|
+
const scoreTop = topPercentNumber(p.score_percentile ?? null);
|
|
851
|
+
const sampleSize = view.rank_summary.sample_size;
|
|
852
|
+
const cohort = scoreTop != null && sampleSize > 0
|
|
853
|
+
? `top ${scoreTop}% of ${sampleSize.toLocaleString()} Standout users`
|
|
854
|
+
: view.cohort_size > 0
|
|
855
|
+
? `among ${view.cohort_size.toLocaleString()} Standout users so far`
|
|
856
|
+
: "";
|
|
857
|
+
const metricRow = (label, value) => value === null
|
|
858
|
+
? null
|
|
859
|
+
: ` ${chalk.white(label.padEnd(12))} ${bar(value, 100, 10)} ${chalk.dim(String(value))}`;
|
|
860
|
+
// Craft is shown only as its bonus contribution (+N), never its raw score, so
|
|
861
|
+
// the headline never looks smaller than a sub-bar it's built from.
|
|
862
|
+
const bars = [
|
|
863
|
+
metricRow("intensity", p.intensity),
|
|
864
|
+
metricRow("consistency", p.consistency),
|
|
865
|
+
p.craft_bonus > 0
|
|
866
|
+
? ` ${chalk.white("craft".padEnd(12))} ${chalk.hex("#ff8a00")(`+${p.craft_bonus}`)} ${chalk.dim("clean prompting")}`
|
|
867
|
+
: null,
|
|
868
|
+
].filter((r) => r !== null);
|
|
869
|
+
// Pre-wrap variable-length copy: a line wider than the box makes boxen re-wrap
|
|
870
|
+
// the WHOLE card through wrap-ansi, which trims the leading spaces our centering
|
|
871
|
+
// relies on. Keeping every line within INNER_WIDTH avoids that.
|
|
872
|
+
const zingerLines = wrapWords(`"${view.zinger}"`, INNER_WIDTH).map((l) => centerText(l, chalk.italic.white));
|
|
873
|
+
const commentLines = p.comment
|
|
874
|
+
? wrapWords(p.comment, INNER_WIDTH - 4).map((l, i) => i === 0
|
|
875
|
+
? ` ${chalk.hex("#ff8a00")(">")} ${chalk.white(l)}`
|
|
876
|
+
: ` ${chalk.white(l)}`)
|
|
877
|
+
: [];
|
|
878
|
+
// The tier figure is the hero of the finale — it replaces the mascot pose here
|
|
879
|
+
// so the card gains a reward reveal without growing taller.
|
|
880
|
+
const tier = tierForScore(p.score);
|
|
881
|
+
const lines = [
|
|
882
|
+
centerBlock(tier.art, chalk.hex("#ff8a00")),
|
|
883
|
+
"",
|
|
884
|
+
centerText(`── ${tier.label} ──`, bigNumber),
|
|
885
|
+
centerText(`${p.score} / 100`, bigNumber),
|
|
886
|
+
cohort ? centerText(cohort, chalk.dim) : null,
|
|
887
|
+
"",
|
|
888
|
+
centerText(view.archetype_label, chalk.bold.hex("#ad5cff")),
|
|
889
|
+
...zingerLines,
|
|
890
|
+
"",
|
|
891
|
+
...bars,
|
|
892
|
+
...commentLines,
|
|
893
|
+
"",
|
|
894
|
+
centerText(view.share_url, chalk.dim),
|
|
895
|
+
].filter((l) => l !== null);
|
|
896
|
+
return box(lines.join("\n"), { borderColor: "yellow" });
|
|
897
|
+
}
|
|
898
|
+
// Instant stats from local logs — shown before submit. The personality cards
|
|
899
|
+
// (rhythm, models+stack, footprint, etc.) moved to the finale so they can carry
|
|
900
|
+
// their LLM one-liners; the preview is the quick raw-numbers teaser.
|
|
901
|
+
const PREVIEW_CARDS = [
|
|
902
|
+
card1Open,
|
|
903
|
+
cardAllTime,
|
|
904
|
+
cardTokens,
|
|
905
|
+
cardToolRelationship,
|
|
906
|
+
];
|
|
907
|
+
// Server-enriched cards — shown as the finale once createWrapped returns: top
|
|
908
|
+
// projects (README blurbs), when-you-code rhythm, footprint, concurrency, the
|
|
909
|
+
// models+stack podium, how-you-talk, collaboration, AI-native, standing, reveal.
|
|
910
|
+
// The combined cards carry their LLM lines here.
|
|
911
|
+
const FINALE_CARDS = [
|
|
912
|
+
cardProjects,
|
|
913
|
+
cardRhythm,
|
|
914
|
+
cardCodeFootprint,
|
|
915
|
+
cardConcurrency,
|
|
916
|
+
cardModelsStack,
|
|
917
|
+
cardTalk,
|
|
918
|
+
card7AINative,
|
|
919
|
+
cardProficiency,
|
|
920
|
+
];
|
|
921
|
+
function getCardFns(mode) {
|
|
922
|
+
if (mode === "preview")
|
|
923
|
+
return PREVIEW_CARDS;
|
|
924
|
+
if (mode === "finale")
|
|
925
|
+
return FINALE_CARDS;
|
|
926
|
+
return [...PREVIEW_CARDS, ...FINALE_CARDS];
|
|
927
|
+
}
|
|
928
|
+
export function buildRenderableCards(view, mode = "final") {
|
|
929
|
+
return getCardFns(mode)
|
|
930
|
+
.map((fn) => fn(view))
|
|
931
|
+
.filter((s) => s.length > 0);
|
|
932
|
+
}
|
|
933
|
+
export async function renderWrapped(view) {
|
|
934
|
+
return renderWrappedCards(view, "final");
|
|
935
|
+
}
|
|
936
|
+
export async function renderWrappedPreview(view) {
|
|
937
|
+
return renderWrappedCards(view, "preview");
|
|
938
|
+
}
|
|
939
|
+
// Single-pass render: everything is generated up front (during the spinner), then
|
|
940
|
+
// the user pages the WHOLE deck (banner + all cards) with ↵. No share prompt - the
|
|
941
|
+
// CLI runs that separately. Returns whether the user quit early.
|
|
942
|
+
export async function renderWrappedAll(view) {
|
|
943
|
+
process.stdout.write("\n");
|
|
944
|
+
process.stdout.write(" " + TITLE_GRADIENT.multiline("░▒▓ STANDOUT WRAPPED ▓▒░") + "\n");
|
|
945
|
+
process.stdout.write(chalk.dim(` ${view.name.toLowerCase()} · last 30 days\n\n`));
|
|
946
|
+
const cards = buildRenderableCards(view, "final");
|
|
947
|
+
if (cards.length === 0) {
|
|
948
|
+
process.stderr.write(" No complete local AI coding activity found yet.\n");
|
|
949
|
+
return { cancelled: false };
|
|
950
|
+
}
|
|
951
|
+
for (let i = 0; i < cards.length; i++) {
|
|
952
|
+
process.stdout.write(cards[i]);
|
|
953
|
+
process.stdout.write(divider(`[${i + 1}/${cards.length}]`));
|
|
954
|
+
if (i === cards.length - 1)
|
|
955
|
+
break;
|
|
956
|
+
const key = await waitForKey();
|
|
957
|
+
if (key === "q" || key === "Q")
|
|
958
|
+
return { cancelled: true };
|
|
959
|
+
}
|
|
960
|
+
return { cancelled: false };
|
|
961
|
+
}
|
|
962
|
+
async function renderWrappedCards(view, mode) {
|
|
963
|
+
// Title banner
|
|
964
|
+
process.stdout.write("\n");
|
|
965
|
+
process.stdout.write(" " + TITLE_GRADIENT.multiline("░▒▓ STANDOUT WRAPPED ▓▒░") + "\n");
|
|
966
|
+
process.stdout.write(chalk.dim(` ${view.name.toLowerCase()} · last 30 days\n\n`));
|
|
967
|
+
// Render each card; cards that return empty string get skipped (e.g. tool
|
|
968
|
+
// relationship when there's not enough data, models when no model captured).
|
|
969
|
+
let rendered = 0;
|
|
970
|
+
const renderable = buildRenderableCards(view, mode);
|
|
971
|
+
if (renderable.length === 0) {
|
|
972
|
+
process.stderr.write(" No complete local AI coding activity found yet; skipping instant cards.\n");
|
|
973
|
+
return { shared: false, cancelled: false };
|
|
974
|
+
}
|
|
975
|
+
for (let i = 0; i < renderable.length; i++) {
|
|
976
|
+
const isLast = i === renderable.length - 1;
|
|
977
|
+
process.stdout.write(renderable[i]);
|
|
978
|
+
rendered = i + 1;
|
|
979
|
+
process.stdout.write(divider(`[${rendered}/${renderable.length}]`));
|
|
980
|
+
if (isLast)
|
|
981
|
+
break;
|
|
982
|
+
const key = await waitForKey();
|
|
983
|
+
if (key === "q" || key === "Q") {
|
|
984
|
+
return { shared: false, cancelled: true };
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if (mode === "preview") {
|
|
988
|
+
return { shared: false, cancelled: false };
|
|
989
|
+
}
|
|
990
|
+
// Share prompt
|
|
991
|
+
process.stdout.write("\n");
|
|
992
|
+
const ans = await askLine(chalk.white(" share to X? [Y/n] "));
|
|
993
|
+
process.stdout.write("\n");
|
|
994
|
+
const shared = !ans || ans.toLowerCase() !== "n";
|
|
995
|
+
return { shared, cancelled: false };
|
|
996
|
+
}
|