sigrank 0.19.0 → 1.0.1
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/CHANGELOG.md +158 -0
- package/MCP_SERVER_DESCRIPTION.md +172 -0
- package/README.md +89 -41
- package/adapters/index.mjs +1100 -0
- package/adapters/tokenpull.mjs +568 -0
- package/adapters.mjs +1 -865
- package/analytics/cascade.mjs +338 -0
- package/cascade.mjs +1 -274
- package/cli.mjs +1 -1853
- package/connect.mjs +1 -13
- package/identity/connect.mjs +13 -0
- package/identity/keystore.mjs +231 -0
- package/identity/sign.mjs +106 -0
- package/index.mjs +47 -162
- package/keystore.mjs +1 -242
- package/lib/constants.mjs +137 -0
- package/lib/pkg-version.mjs +59 -0
- package/manifest.json +46 -0
- package/narrate.mjs +1 -77
- package/package.json +41 -16
- package/preflight.mjs +1 -191
- package/presentation/cli.mjs +1926 -0
- package/presentation/narrate.mjs +219 -0
- package/presentation/tui-themes.mjs +189 -0
- package/presentation/tui.mjs +3316 -0
- package/prompts/check-my-efficiency.md +1 -0
- package/prompts/compare-with-leader.md +1 -0
- package/prompts/simulate-improvement.md +1 -0
- package/proxy.mjs +492 -0
- package/resources/class-tiers.md +81 -0
- package/resources/data-policy.md +31 -0
- package/resources/install-guide.md +59 -0
- package/resources/privacy-model.md +44 -0
- package/resources/scoring-formula.md +24 -0
- package/review.mjs +263 -0
- package/sign.mjs +1 -106
- package/submit/index.mjs +297 -0
- package/submit/preflight.mjs +191 -0
- package/submit.mjs +1 -303
- package/tokenpull.mjs +1 -576
- package/tokscale_analytics.mjs +864 -0
- package/tools/_framing.mjs +217 -0
- package/tools/_helpers.mjs +253 -0
- package/tools/_peer-matching.mjs +361 -0
- package/tools/_report.mjs +262 -0
- package/tools/_schemas.mjs +492 -0
- package/tools/_verifiers.mjs +148 -0
- package/tools/compare-operators.mjs +80 -0
- package/tools/compare-self.mjs +124 -0
- package/tools/describe-power-user.mjs +78 -0
- package/tools/diagnose-cascade.mjs +192 -0
- package/tools/discover-peers.mjs +198 -0
- package/tools/enroll.mjs +119 -0
- package/tools/get-best-operator.mjs +59 -0
- package/tools/get-leaderboard.mjs +27 -0
- package/tools/get-operator.mjs +33 -0
- package/tools/index.mjs +229 -0
- package/tools/optimize-efficiency.mjs +170 -0
- package/tools/rank-paste.mjs +51 -0
- package/tools/rank-windows.mjs +120 -0
- package/tools/self-improve.mjs +423 -0
- package/tools/simulate-change.mjs +207 -0
- package/tools/submit-paste.mjs +121 -0
- package/tools/submit-verified.mjs +153 -0
- package/tools/suggest-improvements.mjs +168 -0
- package/tools/tokenpull-compare.mjs +123 -0
- package/tools/tokenpull-submit.mjs +235 -0
- package/tools/tokenpull.mjs +113 -0
- package/tools/tokscale-breakdown.mjs +119 -0
- package/tools/tokscale-competitive-intel.mjs +45 -0
- package/tools/tokscale-cost-analysis.mjs +31 -0
- package/tools/tokscale-developer-profile.mjs +36 -0
- package/tools/tokscale-device-profile.mjs +35 -0
- package/tools/tokscale-market-share.mjs +38 -0
- package/tools/tokscale-mcp-usage.mjs +33 -0
- package/tools/tokscale-model-trends.mjs +32 -0
- package/tools/watch-tokenpull.mjs +134 -0
- package/tools.mjs +1 -3897
- package/tui.mjs +4 -2650
- package/banner.svg +0 -7
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* narrate.mjs — deterministic prose "card" for a cascade result.
|
|
3
|
+
*
|
|
4
|
+
* Uses the 10 build archetype composition classifier, synced with the app's
|
|
5
|
+
* lib/analytics/build-archetypes.ts. Every operator lands in exactly one
|
|
6
|
+
* archetype that describes their operating shape — not their rank.
|
|
7
|
+
*
|
|
8
|
+
* Classification precedence (first match wins):
|
|
9
|
+
* 1. CONVERGENT — P80+ on all 3 axes (leverage + velocity + construction)
|
|
10
|
+
* 2. KINETIC — velocity >= 0.80
|
|
11
|
+
* 3. Construction — construction >= 0.02 (BUILDER / RECURSIVE / AMPLIFIER)
|
|
12
|
+
* 4. Reuse depth — else (INPUT-BOUND / PRIMING / CONTEXTUAL / DEEP READER / ARCHIVIST)
|
|
13
|
+
*
|
|
14
|
+
* The three derived dimensions:
|
|
15
|
+
* leverage = cache_read / input
|
|
16
|
+
* velocity = output / input
|
|
17
|
+
* construction = cache_write / cache_read
|
|
18
|
+
*
|
|
19
|
+
* Token-only. No network, no randomness.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// Safe formatters: never emit NaN/Infinity/undefined into the card.
|
|
23
|
+
const safeNum = (n) => (Number.isFinite(Number(n)) ? Number(n) : null);
|
|
24
|
+
const comma = (n, dec) => {
|
|
25
|
+
const v = safeNum(n);
|
|
26
|
+
return v !== null
|
|
27
|
+
? v.toLocaleString("en-US", {
|
|
28
|
+
minimumFractionDigits: dec,
|
|
29
|
+
maximumFractionDigits: dec,
|
|
30
|
+
})
|
|
31
|
+
: "—";
|
|
32
|
+
};
|
|
33
|
+
const plain = (n, dec) => {
|
|
34
|
+
const v = safeNum(n);
|
|
35
|
+
return v !== null ? v.toFixed(dec) : "—";
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// P80 thresholds calibrated from HCM cut (1,586 operators).
|
|
39
|
+
const CONVERGENT_T = { levP80: 74.6, velP80: 0.34, constrP80: 0.0431 };
|
|
40
|
+
const VEL_KINETIC = 0.8;
|
|
41
|
+
const LEV_INPUT_BOUND = 5;
|
|
42
|
+
const LEV_PRIMING = 10;
|
|
43
|
+
const LEV_CONTEXTUAL = 15;
|
|
44
|
+
const LEV_DEEP_READER = 23;
|
|
45
|
+
const CONSTR_ACTIVE = 0.02;
|
|
46
|
+
const LEV_BUILDER = 30;
|
|
47
|
+
const LEV_RECURSIVE = 50;
|
|
48
|
+
|
|
49
|
+
/** Classify an operator's cascade into one of 10 build archetypes.
|
|
50
|
+
* Returns { key, name, family, familyLabel, blurb }. */
|
|
51
|
+
export function classifyArchetype(cascade) {
|
|
52
|
+
const v = safeNum(cascade.velocity);
|
|
53
|
+
const l = safeNum(cascade.leverage);
|
|
54
|
+
const cw = cascade.pillars ? Number(cascade.pillars.cacheCreate) : NaN;
|
|
55
|
+
const cr = cascade.pillars ? Number(cascade.pillars.cacheRead) : NaN;
|
|
56
|
+
const input = cascade.pillars ? Number(cascade.pillars.input) : NaN;
|
|
57
|
+
|
|
58
|
+
// Derive construction = cache_write / cache_read
|
|
59
|
+
const constr = Number.isFinite(cr) && cr > 0 && Number.isFinite(cw) ? cw / cr : 0;
|
|
60
|
+
const lev = l ?? 0;
|
|
61
|
+
const vel = v ?? 0;
|
|
62
|
+
|
|
63
|
+
// 1. CONVERGENT — all three axes elevated (P80+)
|
|
64
|
+
if (
|
|
65
|
+
lev > CONVERGENT_T.levP80 &&
|
|
66
|
+
vel > CONVERGENT_T.velP80 &&
|
|
67
|
+
constr > CONVERGENT_T.constrP80
|
|
68
|
+
) {
|
|
69
|
+
return {
|
|
70
|
+
key: "convergent",
|
|
71
|
+
name: "CONVERGENT",
|
|
72
|
+
family: "convergence",
|
|
73
|
+
familyLabel: "Convergence",
|
|
74
|
+
blurb:
|
|
75
|
+
"Deep reuse, active construction, and high generation rise together. A rare composition where all three operating axes are elevated without the usual tradeoffs.",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 2. KINETIC — generation breakout
|
|
80
|
+
if (vel >= VEL_KINETIC) {
|
|
81
|
+
return {
|
|
82
|
+
key: "kinetic",
|
|
83
|
+
name: "KINETIC",
|
|
84
|
+
family: "generation",
|
|
85
|
+
familyLabel: "Generation",
|
|
86
|
+
blurb:
|
|
87
|
+
"Generation has broken out. Output approaches or exceeds fresh input, making transmission the defining feature of the composition.",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 3. Construction branch — active context construction
|
|
92
|
+
if (constr >= CONSTR_ACTIVE) {
|
|
93
|
+
if (lev >= LEV_RECURSIVE) {
|
|
94
|
+
return {
|
|
95
|
+
key: "amplifier",
|
|
96
|
+
name: "AMPLIFIER",
|
|
97
|
+
family: "construction",
|
|
98
|
+
familyLabel: "Active Construction",
|
|
99
|
+
blurb:
|
|
100
|
+
"Deep reuse and active construction are operating together at scale. Existing context produces new work that expands the context available for future cycles.",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (lev >= LEV_BUILDER) {
|
|
104
|
+
return {
|
|
105
|
+
key: "recursive",
|
|
106
|
+
name: "RECURSIVE",
|
|
107
|
+
family: "construction",
|
|
108
|
+
familyLabel: "Active Construction",
|
|
109
|
+
blurb:
|
|
110
|
+
"New context is being built on top of an already substantial reusable base. Construction and reuse are now feeding the same operating loop.",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
key: "builder",
|
|
115
|
+
name: "BUILDER",
|
|
116
|
+
family: "construction",
|
|
117
|
+
familyLabel: "Active Construction",
|
|
118
|
+
blurb:
|
|
119
|
+
"Active context construction has begun. The system is creating material for future reuse while leverage is still developing.",
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 4. Reuse depth branch — passive (construction < 0.02)
|
|
124
|
+
if (lev >= LEV_DEEP_READER) {
|
|
125
|
+
return {
|
|
126
|
+
key: "archivist",
|
|
127
|
+
name: "ARCHIVIST",
|
|
128
|
+
family: "reuse",
|
|
129
|
+
familyLabel: "Reuse Depth",
|
|
130
|
+
blurb:
|
|
131
|
+
"Extreme reuse of accumulated context. A deep context library carries the system while new construction remains limited.",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (lev >= LEV_CONTEXTUAL) {
|
|
135
|
+
return {
|
|
136
|
+
key: "deep-reader",
|
|
137
|
+
name: "DEEP READER",
|
|
138
|
+
family: "reuse",
|
|
139
|
+
familyLabel: "Reuse Depth",
|
|
140
|
+
blurb:
|
|
141
|
+
"Strong accumulated context is carrying the workflow. The operator draws deeply from retained context while creating relatively little new context.",
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (lev >= LEV_PRIMING) {
|
|
145
|
+
return {
|
|
146
|
+
key: "contextual",
|
|
147
|
+
name: "CONTEXTUAL",
|
|
148
|
+
family: "reuse",
|
|
149
|
+
familyLabel: "Reuse Depth",
|
|
150
|
+
blurb:
|
|
151
|
+
"Retained context is now materially supporting the workflow. Reuse is established, while active construction remains limited.",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (lev >= LEV_INPUT_BOUND) {
|
|
155
|
+
return {
|
|
156
|
+
key: "priming",
|
|
157
|
+
name: "PRIMING",
|
|
158
|
+
family: "reuse",
|
|
159
|
+
familyLabel: "Reuse Depth",
|
|
160
|
+
blurb:
|
|
161
|
+
"Reuse is beginning to form. Prior context is returning, but the system has not yet developed deep leverage.",
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
key: "input-bound",
|
|
166
|
+
name: "INPUT-BOUND",
|
|
167
|
+
family: "reuse",
|
|
168
|
+
familyLabel: "Reuse Depth",
|
|
169
|
+
blurb:
|
|
170
|
+
"Fresh input still carries most of the workload. Little prior context is returning, so each cycle depends heavily on new input.",
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Given a cascade result ({ velocity, leverage, dev10x, pillars, class }) and an
|
|
176
|
+
* optional subject name, return "**CLASS.** <archetype blurb with context>".
|
|
177
|
+
* Deterministic — same numbers in → same card out.
|
|
178
|
+
*/
|
|
179
|
+
export function narrate(cascade, name = "This operator") {
|
|
180
|
+
const klass = cascade.class || cascade.klass || "UNCLASSED";
|
|
181
|
+
const v = safeNum(cascade.velocity);
|
|
182
|
+
const l = safeNum(cascade.leverage);
|
|
183
|
+
const cw = cascade.pillars ? Number(cascade.pillars.cacheCreate) : NaN;
|
|
184
|
+
|
|
185
|
+
// Handle non-compounding (stateless pipe) as a special case before the
|
|
186
|
+
// archetype classifier — cascade.mjs leaves dev10x null when cacheCreate is 0.
|
|
187
|
+
const nonCompounding =
|
|
188
|
+
cascade.dev10x == null || !(cw > 0) || v === null || l === null;
|
|
189
|
+
|
|
190
|
+
if (nonCompounding) {
|
|
191
|
+
const leverageStr =
|
|
192
|
+
l !== null
|
|
193
|
+
? `Leverage ${comma(l, 1)}x comes from reuse alone.`
|
|
194
|
+
: "Leverage is undefined (no fresh input recorded).";
|
|
195
|
+
const dev10xNote =
|
|
196
|
+
cascade.dev10x == null
|
|
197
|
+
? " 10xDEV is undefined — the compounding loop has not formed yet."
|
|
198
|
+
: "";
|
|
199
|
+
const body =
|
|
200
|
+
`${name} runs a stateless pipe — no cache commits, so the cascade can't form. ` +
|
|
201
|
+
`High read volume, but nothing is being built forward. ${leverageStr}${dev10xNote}`;
|
|
202
|
+
return `**${klass}.** ${body}`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Classify into one of 10 build archetypes
|
|
206
|
+
const arch = classifyArchetype(cascade);
|
|
207
|
+
|
|
208
|
+
// Build the card with archetype name, family, and context-specific numbers
|
|
209
|
+
const levStr = l !== null ? `${comma(l, 0)}x leverage` : "undefined leverage";
|
|
210
|
+
const velStr = v !== null ? `${plain(v, 2)}x generation` : "undefined generation";
|
|
211
|
+
const cr = cascade.pillars ? Number(cascade.pillars.cacheRead) : NaN;
|
|
212
|
+
const constrStr = `construction ${plain(Number.isFinite(cr) && cr > 0 ? cw / cr : 0, 4)}`;
|
|
213
|
+
|
|
214
|
+
const body =
|
|
215
|
+
`${name} is ${arch.name} — ${arch.blurb} ` +
|
|
216
|
+
`${levStr}, ${velStr}, ${constrStr}.`;
|
|
217
|
+
|
|
218
|
+
return `**${klass}.** ${body}`;
|
|
219
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// tui-themes.mjs — centralized ANSI color palette with multiple themes
|
|
2
|
+
//
|
|
3
|
+
// Tokscale-inspired: all colors live in one module, render code references
|
|
4
|
+
// them through `currentTheme`. Supports dark (default), light, high-contrast,
|
|
5
|
+
// and monochrome themes for accessibility.
|
|
6
|
+
//
|
|
7
|
+
// Usage in tui.mjs:
|
|
8
|
+
// import { currentTheme as c, setTheme } from "./tui-themes.mjs";
|
|
9
|
+
// // c.gold, c.cyan, c.reset, etc. — same interface as the old `c` object
|
|
10
|
+
//
|
|
11
|
+
// CLI:
|
|
12
|
+
// node tui.mjs --theme dark # default
|
|
13
|
+
// node tui.mjs --theme light # light terminal
|
|
14
|
+
// node tui.mjs --theme high-contrast # visually impaired
|
|
15
|
+
// node tui.mjs --theme monochrome # colorblind
|
|
16
|
+
|
|
17
|
+
const ESC = "\x1b[";
|
|
18
|
+
|
|
19
|
+
export const THEMES = {
|
|
20
|
+
// ── Dark (default) — current palette, designed for dark terminals ──
|
|
21
|
+
// green uses 256-color 77 (#5fd75f) for WCAG AA compliance on dark bg (9.0:1).
|
|
22
|
+
// Standard ANSI 32 (#5a8a5a) only achieves 4.1:1 — below the 4.5:1 threshold.
|
|
23
|
+
dark: {
|
|
24
|
+
name: "dark",
|
|
25
|
+
reset: `${ESC}0m`,
|
|
26
|
+
bold: `${ESC}1m`,
|
|
27
|
+
dim: `${ESC}2m`,
|
|
28
|
+
gold: `${ESC}33m`,
|
|
29
|
+
boldGold: `${ESC}1;33m`,
|
|
30
|
+
cyan: `${ESC}36m`,
|
|
31
|
+
boldCyan: `${ESC}1;36m`,
|
|
32
|
+
green: `${ESC}38;5;77m`, // brighter green #5fd75f (9.0:1 on dark — was ANSI 32 at 4.1:1)
|
|
33
|
+
red: `${ESC}38;5;203m`, // brighter red #ff5f5f (5.6:1 on dark — was ANSI 31 at 4.46:1, just below AA)
|
|
34
|
+
white: `${ESC}97m`,
|
|
35
|
+
boldWhite: `${ESC}1;97m`,
|
|
36
|
+
magenta: `${ESC}35m`,
|
|
37
|
+
blue: `${ESC}34m`,
|
|
38
|
+
bgDim: `${ESC}48;5;236m`,
|
|
39
|
+
bgCyan: `${ESC}48;5;23m`,
|
|
40
|
+
// Medal backgrounds (256-color) — gold/silver/bronze podium tints
|
|
41
|
+
medalBg: { 1: 220, 2: 250, 3: 130 },
|
|
42
|
+
medalFg: `${ESC}38;5;232m`, // dark text on medal bg
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// ── Light — dark text on light/white terminals ──
|
|
46
|
+
// Uses 256-color codes (38;5;N) with darker shades that pass WCAG AA (4.5:1)
|
|
47
|
+
// on white backgrounds. Standard ANSI 33/36/32/31/35/34 render as pastel
|
|
48
|
+
// shades that fail contrast on white — these 256-color variants fix that.
|
|
49
|
+
light: {
|
|
50
|
+
name: "light",
|
|
51
|
+
reset: `${ESC}0m`,
|
|
52
|
+
bold: `${ESC}1m`,
|
|
53
|
+
dim: `${ESC}38;5;242m`, // medium gray (4.5:1 on white — passes AA for large text)
|
|
54
|
+
gold: `${ESC}38;5;130m`, // dark orange-gold #af5f00 (4.7:1 — yellow can't pass AA on white, this is the closest)
|
|
55
|
+
boldGold: `${ESC}1;38;5;130m`, // bold dark orange-gold (4.7:1)
|
|
56
|
+
cyan: `${ESC}38;5;24m`, // dark cyan #005f87 (7.0:1)
|
|
57
|
+
boldCyan: `${ESC}1;38;5;24m`, // bold dark cyan (7.0:1)
|
|
58
|
+
green: `${ESC}38;5;28m`, // dark green #008700 (4.7:1)
|
|
59
|
+
red: `${ESC}38;5;124m`, // dark red #af0000 (7.4:1)
|
|
60
|
+
white: `${ESC}30m`, // BLACK text on light bg (21:1)
|
|
61
|
+
boldWhite: `${ESC}1;30m`, // bold black
|
|
62
|
+
magenta: `${ESC}38;5;90m`, // dark magenta #870087 (8.8:1)
|
|
63
|
+
blue: `${ESC}38;5;19m`, // dark blue #0000af (13.0:1)
|
|
64
|
+
bgDim: `${ESC}48;5;250m`, // light grey bg for active tab
|
|
65
|
+
bgCyan: `${ESC}48;5;194m`, // light teal bg for active tab
|
|
66
|
+
medalBg: { 1: 220, 2: 250, 3: 130 },
|
|
67
|
+
medalFg: `${ESC}38;5;232m`,
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// ── High contrast — bold everything, no dim, maximum readability ──
|
|
71
|
+
highContrast: {
|
|
72
|
+
name: "high-contrast",
|
|
73
|
+
reset: `${ESC}0m`,
|
|
74
|
+
bold: `${ESC}1m`,
|
|
75
|
+
dim: `${ESC}0;37m`, // NO dim — full brightness white instead
|
|
76
|
+
gold: `${ESC}1;33m`, // bold gold
|
|
77
|
+
boldGold: `${ESC}1;33m`,
|
|
78
|
+
cyan: `${ESC}1;36m`, // bold cyan
|
|
79
|
+
boldCyan: `${ESC}1;36m`,
|
|
80
|
+
green: `${ESC}1;32m`, // bold green
|
|
81
|
+
red: `${ESC}1;31m`, // bold red
|
|
82
|
+
white: `${ESC}1;97m`, // bold bright white
|
|
83
|
+
boldWhite: `${ESC}1;97m`,
|
|
84
|
+
magenta: `${ESC}1;35m`, // bold magenta
|
|
85
|
+
blue: `${ESC}1;34m`, // bold blue
|
|
86
|
+
bgDim: `${ESC}48;5;238m`, // slightly lighter dark bg
|
|
87
|
+
bgCyan: `${ESC}48;5;24m`, // slightly lighter teal bg
|
|
88
|
+
medalBg: { 1: 220, 2: 250, 3: 130 },
|
|
89
|
+
medalFg: `${ESC}1;38;5;232m`,
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
// ── Monochrome — bold/normal only, no color (colorblind-safe) ──
|
|
93
|
+
monochrome: {
|
|
94
|
+
name: "monochrome",
|
|
95
|
+
reset: `${ESC}0m`,
|
|
96
|
+
bold: `${ESC}1m`,
|
|
97
|
+
dim: `${ESC}2m`, // dim is fine in monochrome (it's weight, not color)
|
|
98
|
+
gold: `${ESC}1m`, // bold only (no color)
|
|
99
|
+
boldGold: `${ESC}1m`,
|
|
100
|
+
cyan: `${ESC}0m`, // normal weight
|
|
101
|
+
boldCyan: `${ESC}1m`, // bold
|
|
102
|
+
green: `${ESC}0m`, // normal
|
|
103
|
+
red: `${ESC}1m`, // bold (errors stand out by weight)
|
|
104
|
+
white: `${ESC}0m`, // normal
|
|
105
|
+
boldWhite: `${ESC}1m`, // bold
|
|
106
|
+
magenta: `${ESC}0m`, // normal
|
|
107
|
+
blue: `${ESC}0m`, // normal
|
|
108
|
+
bgDim: `${ESC}7m`, // reverse video for active tab (no color)
|
|
109
|
+
bgCyan: `${ESC}7m`, // reverse video
|
|
110
|
+
medalBg: {}, // no medal colors in monochrome
|
|
111
|
+
medalFg: `${ESC}1m`, // bold for medal winners
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export let currentTheme = THEMES.dark;
|
|
116
|
+
|
|
117
|
+
export function setTheme(name) {
|
|
118
|
+
if (THEMES[name]) {
|
|
119
|
+
currentTheme = THEMES[name];
|
|
120
|
+
}
|
|
121
|
+
return currentTheme;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function getThemeNames() {
|
|
125
|
+
return Object.keys(THEMES);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Platform color map ─────────────────────────────────────────────────────
|
|
129
|
+
// Each platform gets a distinct 256-color code so platforms are visually
|
|
130
|
+
// distinguishable in the cascade table, token composition bars, board, and
|
|
131
|
+
// watch tabs. The colors are chosen for distinguishability on dark terminals
|
|
132
|
+
// (the default theme). For light theme, darker 256-color variants are used.
|
|
133
|
+
// Monochrome theme returns no color (bold only) — colorblind-safe.
|
|
134
|
+
//
|
|
135
|
+
// Color assignments (dark theme 256-color codes):
|
|
136
|
+
// claude = 75 (blue-cyan)
|
|
137
|
+
// codex = 114 (green)
|
|
138
|
+
// amp = 179 (orange)
|
|
139
|
+
// gemini = 129 (purple-magenta)
|
|
140
|
+
// kimi = 213 (pink)
|
|
141
|
+
// qwen = 68 (steel blue)
|
|
142
|
+
// goose = 142 (olive)
|
|
143
|
+
// kilo = 166 (dark orange)
|
|
144
|
+
// hermes = 96 (plum)
|
|
145
|
+
// droid = 167 (salmon)
|
|
146
|
+
// codebuff = 72 (teal-green)
|
|
147
|
+
// copilot = 33 (bright blue)
|
|
148
|
+
// openclaw = 136 (gold-brown)
|
|
149
|
+
// pi = 177 (light purple)
|
|
150
|
+
// omp = 220 (gold — the "oh my pi" platform gets gold)
|
|
151
|
+
// opencode = 60 (slate)
|
|
152
|
+
// devin = 30 (teal)
|
|
153
|
+
// proxy = 245 (gray — synthetic passthrough)
|
|
154
|
+
// other = 245 (gray — catch-all)
|
|
155
|
+
|
|
156
|
+
const PLATFORM_COLORS_DARK = {
|
|
157
|
+
claude: 75, codex: 114, amp: 179, gemini: 129, kimi: 213,
|
|
158
|
+
qwen: 68, goose: 142, kilo: 166, hermes: 96, droid: 167,
|
|
159
|
+
codebuff: 72, copilot: 33, openclaw: 136, pi: 177, omp: 220,
|
|
160
|
+
opencode: 60, devin: 30, proxy: 245, other: 245,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Light theme: darker shades of the same hues for contrast on white
|
|
164
|
+
const PLATFORM_COLORS_LIGHT = {
|
|
165
|
+
claude: 24, codex: 28, amp: 130, gemini: 54, kimi: 90,
|
|
166
|
+
qwen: 19, goose: 100, kilo: 130, hermes: 54, droid: 124,
|
|
167
|
+
codebuff: 23, copilot: 19, openclaw: 130, pi: 90, omp: 130,
|
|
168
|
+
opencode: 19, devin: 23, proxy: 242, other: 242,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Returns a paint function for the given platform name, theme-aware.
|
|
173
|
+
* Usage: `const pc = platformColor("claude"); pc("claude")` → colored string.
|
|
174
|
+
* Falls back to the theme's cyan for unknown platforms.
|
|
175
|
+
*/
|
|
176
|
+
export function platformColor(platform) {
|
|
177
|
+
const themeName = currentTheme.name;
|
|
178
|
+
// Monochrome: no color, just bold (colorblind-safe)
|
|
179
|
+
if (themeName === "monochrome") {
|
|
180
|
+
return (s) => `${currentTheme.bold}${s}${currentTheme.reset}`;
|
|
181
|
+
}
|
|
182
|
+
const map = themeName === "light" ? PLATFORM_COLORS_LIGHT : PLATFORM_COLORS_DARK;
|
|
183
|
+
const code = map[platform];
|
|
184
|
+
if (code == null) {
|
|
185
|
+
// Unknown platform — fall back to cyan
|
|
186
|
+
return (s) => `${currentTheme.cyan}${s}${currentTheme.reset}`;
|
|
187
|
+
}
|
|
188
|
+
return (s) => `${ESC}38;5;${code}m${s}${currentTheme.reset}`;
|
|
189
|
+
}
|