ccglance 1.0.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.
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SEGMENTS = void 0;
4
+ const colors_1 = require("../render/colors");
5
+ const format_1 = require("../utils/format");
6
+ const icons_1 = require("../render/icons");
7
+ const opt = (ctx, id) => ctx.spec.segments[id] || {};
8
+ const optIcon = (ctx, id, fallback) => opt(ctx, id).icon || (0, icons_1.icon)(fallback);
9
+ const optIconColor = (ctx, id, fallback) => opt(ctx, id).iconColor != null ? opt(ctx, id).iconColor : fallback;
10
+ const optTextColor = (ctx, id, fallback) => opt(ctx, id).textColor != null ? opt(ctx, id).textColor : fallback;
11
+ const optBold = (ctx, id) => opt(ctx, id).textBold === true;
12
+ function iconSeg(ctx, id, fallbackColor, fallbackIcon, text) {
13
+ return (0, colors_1.seg)(fallbackColor, optIcon(ctx, id, fallbackIcon), text, optIconColor(ctx, id, fallbackColor), optTextColor(ctx, id, fallbackColor), optBold(ctx, id));
14
+ }
15
+ // 阈值染色:达到 danger 变红、达到 warn 变黄、否则常态色。
16
+ function pctColor(v, warn, danger, normal) {
17
+ return v >= danger ? colors_1.C.danger : v >= warn ? colors_1.C.caution : normal;
18
+ }
19
+ function num(v) {
20
+ return typeof v === 'number' && Number.isFinite(v) ? v : null;
21
+ }
22
+ function nonNegative(v) {
23
+ const n = num(v);
24
+ return n != null && n >= 0 ? n : null;
25
+ }
26
+ function positive(v) {
27
+ const n = num(v);
28
+ return n != null && n > 0 ? n : null;
29
+ }
30
+ // ── 第一行:运行时 ────────────────────────────────────────────────────────────
31
+ function statusIcon(ctx, key, fallback) {
32
+ return opt(ctx, 'status').statusIcons?.[key] || fallback;
33
+ }
34
+ function fmtRatePct(v) {
35
+ return Math.max(0, Math.min(100, Math.round(v))) + '%';
36
+ }
37
+ // 会话状态:读 transcript 尾部推断;默认仅显示图标,工具态附带工具名。
38
+ const status = (ctx) => {
39
+ const s = ctx.previewStatus || (ctx.data.transcript_path
40
+ ? require('../readers/transcript').getRecentStatus(ctx.data.transcript_path)
41
+ : null);
42
+ if (!s)
43
+ return null;
44
+ switch (s.state) {
45
+ case 'tool': return (0, colors_1.seg)(colors_1.C.stTool, statusIcon(ctx, 'tool', '🔧'), s.tool || 'tool');
46
+ case 'idle': return opt(ctx, 'status').showIdle === false ? null : (0, colors_1.paint)(colors_1.C.idle, statusIcon(ctx, 'idle', '✅'));
47
+ case 'paused': return (0, colors_1.paint)(colors_1.C.paused, statusIcon(ctx, 'paused', '⏸️'));
48
+ case 'working': return (0, colors_1.paint)(colors_1.C.working, statusIcon(ctx, 'working', '⚙️'));
49
+ case 'thinking': return (0, colors_1.paint)(colors_1.C.stThink, statusIcon(ctx, 'thinking', '💭'));
50
+ }
51
+ return null;
52
+ };
53
+ const model = (ctx) => ctx.data.model && (ctx.data.model.display_name || ctx.data.model.id)
54
+ ? iconSeg(ctx, 'model', colors_1.C.model, 'model', (0, format_1.modelName)(ctx.data.model)) : null;
55
+ const effort = ({ data }) => data.effort && data.effort.level ? (0, colors_1.seg)(colors_1.C.effort, '🧠', data.effort.level) : null;
56
+ const fast = (ctx) => ctx.data.fast_mode ? iconSeg(ctx, 'fast', colors_1.C.fast, 'fast', 'fast') : null;
57
+ const context = (ctx) => {
58
+ const cw = ctx.data.context_window;
59
+ if (!cw)
60
+ return null;
61
+ const cu = cw.current_usage || {};
62
+ const used = nonNegative(cw.total_input_tokens) ?? nonNegative(cu.input_tokens) ?? 0;
63
+ const out = nonNegative(cu.output_tokens) ?? nonNegative(cw.total_output_tokens) ?? 0;
64
+ const size = positive(cw.context_window_size);
65
+ if (!size)
66
+ return null;
67
+ const pct = num(cw.used_percentage) ?? used / size * 100;
68
+ const remain = cw.remaining_percentage != null
69
+ ? Math.round(cw.remaining_percentage / 100 * size)
70
+ : Math.max(0, size - used);
71
+ const o = opt(ctx, 'context');
72
+ const normal = optTextColor(ctx, 'context', colors_1.C.ctx);
73
+ const col = pctColor(pct, o.warnPct != null ? o.warnPct : 75, o.dangerPct != null ? o.dangerPct : 90, normal);
74
+ if (o.percentOnly)
75
+ return iconSeg(ctx, 'context', col, 'context', (0, format_1.fmtPct)(pct));
76
+ const parts = [(0, format_1.fmtPct)(pct), `↓${(0, format_1.fmtTokens)(used)}`, `↑${(0, format_1.fmtTokens)(out)}`];
77
+ if (remain != null)
78
+ parts.push(`→${(0, format_1.fmtTokens)(remain)}`);
79
+ return iconSeg(ctx, 'context', col, 'context', parts.join(colors_1.DOT));
80
+ };
81
+ const cache = (ctx) => {
82
+ const cw = ctx.data.context_window;
83
+ if (!cw)
84
+ return null;
85
+ const cu = cw.current_usage || {};
86
+ const used = nonNegative(cw.total_input_tokens) ?? nonNegative(cu.input_tokens) ?? 0;
87
+ const read = nonNegative(cu.cache_read_input_tokens) ?? 0;
88
+ const write = nonNegative(cu.cache_creation_input_tokens) ?? 0;
89
+ const hitPct = used > 0 && read > 0 ? read / used * 100 : 0;
90
+ return iconSeg(ctx, 'cache', colors_1.C.cache, 'cache', `${(0, format_1.fmtPct)(hitPct)}${colors_1.DOT}↓${(0, format_1.fmtTokens)(read)}${colors_1.DOT}↑${(0, format_1.fmtTokens)(write)}`);
91
+ };
92
+ const rateLimits = (ctx) => {
93
+ const rl = ctx.data.rate_limits;
94
+ if (!rl)
95
+ return null;
96
+ const o = opt(ctx, 'rate_limits');
97
+ const show = o.show || 'all';
98
+ const showReset = o.showReset !== false;
99
+ const labels = {
100
+ five_hour: o.labels?.five_hour || 'Hour',
101
+ seven_day: o.labels?.seven_day || 'Week',
102
+ };
103
+ const normal = optTextColor(ctx, 'rate_limits', colors_1.C.rate);
104
+ const bold = optBold(ctx, 'rate_limits');
105
+ const parts = [];
106
+ let maxUsed = 0;
107
+ const wants = (ids) => ids.includes(show);
108
+ const meter = (pct, color) => {
109
+ const glyph = pct >= 90 ? '🌕'
110
+ : pct >= 60 ? '🌔'
111
+ : pct >= 30 ? '🌓'
112
+ : pct >= 10 ? '🌒' : '🌑';
113
+ return (0, colors_1.paint)(color, glyph, bold);
114
+ };
115
+ const pushWindow = (label, win) => {
116
+ if (!win || win.used_percentage == null)
117
+ return;
118
+ const pct = win.used_percentage;
119
+ const warn = o.warnPct != null ? o.warnPct : 70;
120
+ const danger = o.dangerPct != null ? o.dangerPct : 90;
121
+ const winColor = pctColor(pct, warn, danger, normal);
122
+ const reset = showReset ? (0, format_1.fmtResetIn)(win.resets_at, ctx.nowMs) : null;
123
+ const detail = [
124
+ (0, colors_1.paint)(normal, label, bold),
125
+ meter(pct, winColor),
126
+ (0, colors_1.paint)(winColor, fmtRatePct(pct), bold),
127
+ ];
128
+ if (reset)
129
+ detail.push((0, colors_1.paint)(colors_1.C.rateReset, '⏳', bold), (0, colors_1.paint)(colors_1.C.rateReset, reset, bold));
130
+ parts.push(detail.join(' '));
131
+ maxUsed = Math.max(maxUsed, pct);
132
+ };
133
+ if (wants(['all', 'both', 'five_hour'])) {
134
+ pushWindow(labels.five_hour, rl.five_hour);
135
+ }
136
+ if (wants(['all', 'both', 'seven_day'])) {
137
+ pushWindow(labels.seven_day, rl.seven_day);
138
+ }
139
+ if (!parts.length)
140
+ return null;
141
+ const col = pctColor(maxUsed, o.warnPct != null ? o.warnPct : 70, o.dangerPct != null ? o.dangerPct : 90, normal);
142
+ const rate = optIcon(ctx, 'rate_limits', 'rate_limits');
143
+ return (0, colors_1.paint)(optIconColor(ctx, 'rate_limits', col), rate, bold) + ' ' + parts.join(colors_1.DOT);
144
+ };
145
+ const style = (ctx) => ctx.data.output_style && ctx.data.output_style.name ? iconSeg(ctx, 'style', colors_1.C.style, 'style', ctx.data.output_style.name) : null;
146
+ // ── 第二行:项目 / 会话 ──────────────────────────────────────────────────────
147
+ const dir = (ctx) => {
148
+ const cwd = (ctx.data.workspace && ctx.data.workspace.current_dir) || ctx.data.cwd || '';
149
+ if (!cwd)
150
+ return null;
151
+ const ic = optIcon(ctx, 'dir', 'dir');
152
+ const iconColor = optIconColor(ctx, 'dir', colors_1.C.dirIcon);
153
+ const textColor = optTextColor(ctx, 'dir', colors_1.C.dirText);
154
+ const bold = optBold(ctx, 'dir');
155
+ if (opt(ctx, 'dir').fullPath)
156
+ return (0, colors_1.paint)(iconColor, ic, bold) + ' ' + (0, colors_1.paint)(textColor, cwd, bold);
157
+ const name = cwd.replace(/[\\/]+$/, '').split(/[\\/]/).pop();
158
+ return name ? (0, colors_1.paint)(iconColor, ic, bold) + ' ' + (0, colors_1.paint)(textColor, name, bold) : null;
159
+ };
160
+ const git = (ctx) => {
161
+ const cwd = (ctx.data.workspace && ctx.data.workspace.current_dir) || ctx.data.cwd || '';
162
+ if (!cwd)
163
+ return null;
164
+ const { gitSegment } = require('../runtime/git');
165
+ return gitSegment(cwd, optIcon(ctx, 'git', 'git'), opt(ctx, 'git'), ctx.data.worktree?.branch, ctx.data.worktree?.name);
166
+ };
167
+ const sessionName = ({ data }) => data.session_name ? (0, colors_1.seg)(colors_1.C.name, '🏷️', data.session_name) : null;
168
+ const session = (ctx) => {
169
+ const cost = ctx.data.cost;
170
+ if (!cost || cost.total_duration_ms == null)
171
+ return null;
172
+ const dur = (0, format_1.fmtDuration)(cost.total_duration_ms);
173
+ if (!dur)
174
+ return null;
175
+ const added = cost.total_lines_added || 0;
176
+ const removed = cost.total_lines_removed || 0;
177
+ const diff = [(0, colors_1.paint)(colors_1.C.add, '+' + added), (0, colors_1.paint)(colors_1.C.del, '-' + removed)];
178
+ return iconSeg(ctx, 'session', colors_1.C.sess, 'session', dur) + ' ' + diff.join(' ');
179
+ };
180
+ const cost = (ctx) => {
181
+ const usd = ctx.data.cost && ctx.data.cost.total_cost_usd;
182
+ return usd != null && usd > 0 ? iconSeg(ctx, 'cost', colors_1.C.cost, 'cost', (0, format_1.fmtCost)(usd)) : null;
183
+ };
184
+ const version = (ctx) => {
185
+ const current = ctx.data.version;
186
+ if (!current)
187
+ return null;
188
+ const bold = optBold(ctx, 'version');
189
+ const iconColor = optIconColor(ctx, 'version', colors_1.C.ver);
190
+ const textColor = optTextColor(ctx, 'version', colors_1.C.ver);
191
+ let text = (0, colors_1.paint)(iconColor, optIcon(ctx, 'version', 'version'), bold) + ' ' + (0, colors_1.paint)(textColor, 'v' + current, bold);
192
+ if (ctx.latest && (0, format_1.semverGt)(ctx.latest, current))
193
+ text += ' ' + (0, colors_1.paint)(colors_1.C.upd, '↑' + ctx.latest);
194
+ return text;
195
+ };
196
+ exports.SEGMENTS = {
197
+ // 运行时
198
+ status, model, effort, fast, context, cache, style,
199
+ // 订阅
200
+ rate_limits: rateLimits,
201
+ // 项目 / 会话
202
+ dir, git, session_name: sessionName, session, cost, version,
203
+ };
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ // Claude Code 经 stdin 传入的 JSON 形状(字段均按可选处理,缺失时静默降级)。
3
+ // 依据官方 statusline 文档 + 实测;不同 Claude Code 版本字段可能增减。
4
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fmtCost = exports.semverGt = exports.fmtPct = exports.fmtTokens = void 0;
4
+ exports.modelName = modelName;
5
+ exports.fmtDuration = fmtDuration;
6
+ exports.fmtResetIn = fmtResetIn;
7
+ const TOKEN_CASE = {
8
+ glm: 'GLM',
9
+ chatglm: 'ChatGLM',
10
+ kimi: 'Kimi',
11
+ qwen: 'Qwen',
12
+ deepseek: 'DeepSeek',
13
+ doubao: 'Doubao',
14
+ minimax: 'MiniMax',
15
+ coder: 'Coder',
16
+ turbo: 'Turbo',
17
+ chat: 'Chat',
18
+ instruct: 'Instruct',
19
+ thinking: 'Thinking',
20
+ gpt: 'GPT',
21
+ llm: 'LLM',
22
+ vl: 'VL',
23
+ };
24
+ function contextSuffix(id) {
25
+ return /\[1m\]/i.test(id) ? '1M' : '';
26
+ }
27
+ function appendContextSuffix(name, id) {
28
+ const suffix = contextSuffix(id);
29
+ return suffix && !new RegExp(`\\b${suffix}\\b`, 'i').test(name) ? `${name} ${suffix}` : name;
30
+ }
31
+ function matchClaudeFamily(raw) {
32
+ const source = raw.toLowerCase();
33
+ for (const [keyword, label] of [['sonnet', 'Sonnet'], ['opus', 'Opus'], ['haiku', 'Haiku']]) {
34
+ const post = new RegExp(`${keyword}[-_\\s]+(\\d{1,2})(?:[-_.\\s]+(\\d{1,2}))?(?=$|[-_\\s\\[])`).exec(source);
35
+ const pre = new RegExp(`(\\d{1,2})(?:[-_.\\s]+(\\d{1,2}))?[-_\\s]+${keyword}(?=$|[-_\\s\\[])`).exec(source);
36
+ const match = post || pre;
37
+ if (!match)
38
+ continue;
39
+ const major = match[1];
40
+ const minor = match[2];
41
+ return `${label} ${major}${minor ? `.${minor}` : ''}`;
42
+ }
43
+ return null;
44
+ }
45
+ function normalizeDisplayName(displayName) {
46
+ return displayName
47
+ .replace(/^Claude\s+/i, '')
48
+ .replace(/\s*\((\d+)\s*([km])(?:\s+context)?\)/i, ' $1$2')
49
+ .replace(/\s+/g, ' ')
50
+ .trim();
51
+ }
52
+ function titleToken(token) {
53
+ const lower = token.toLowerCase();
54
+ if (TOKEN_CASE[lower])
55
+ return TOKEN_CASE[lower];
56
+ const compact = /^([a-z]+)(\d+(?:\.\d+)?)$/i.exec(token);
57
+ if (compact) {
58
+ const head = TOKEN_CASE[compact[1].toLowerCase()]
59
+ || (compact[1][0].toUpperCase() + compact[1].slice(1).toLowerCase());
60
+ return head + compact[2];
61
+ }
62
+ if (/^[rv]\d+(?:\.\d+)?$/i.test(token))
63
+ return token.toUpperCase();
64
+ if (/^[a-z]{2,3}$/i.test(token))
65
+ return token.toUpperCase();
66
+ return token[0] ? token[0].toUpperCase() + token.slice(1).toLowerCase() : token;
67
+ }
68
+ function readableModelId(id) {
69
+ const base = id
70
+ .replace(/\[[^\]]+\]/g, '')
71
+ .split('/')
72
+ .filter(Boolean)
73
+ .pop() || id;
74
+ const rawTokens = base.split(/[-_\s]+/).filter(Boolean);
75
+ const tokens = [];
76
+ for (let i = 0; i < rawTokens.length; i++) {
77
+ if (/^\d+$/.test(rawTokens[i]) && i + 1 < rawTokens.length && /^\d+$/.test(rawTokens[i + 1])) {
78
+ tokens.push(`${rawTokens[i]}.${rawTokens[i + 1]}`);
79
+ i++;
80
+ }
81
+ else {
82
+ tokens.push(rawTokens[i]);
83
+ }
84
+ }
85
+ return tokens.map(titleToken).join(' ').trim();
86
+ }
87
+ // 模型名以 Claude Code stdin 的 display_name 为准;id 只在 display_name 缺失时兜底。
88
+ function modelName(model) {
89
+ const id = (model.id || '').trim();
90
+ const displayName = (model.display_name || '').trim();
91
+ const normalizedDisplay = normalizeDisplayName(displayName);
92
+ const baseName = normalizedDisplay || matchClaudeFamily(id) || readableModelId(id);
93
+ return appendContextSuffix(baseName, id);
94
+ }
95
+ // 12345 → 12.3k
96
+ const fmtTokens = (n) => n == null ? '0' : n < 1000 ? String(n)
97
+ : (Number.isInteger(n / 1000) ? (n / 1000).toFixed(0) : (n / 1000).toFixed(1)) + 'k';
98
+ exports.fmtTokens = fmtTokens;
99
+ // 整数省小数: 12 → 12% / 12.3 → 12.3%
100
+ const fmtPct = (v) => (Number.isInteger(v) ? v.toFixed(0) : v.toFixed(1)) + '%';
101
+ exports.fmtPct = fmtPct;
102
+ function fmtDuration(ms) {
103
+ if (ms == null)
104
+ return null;
105
+ if (ms < 1000)
106
+ return ms + 'ms';
107
+ if (ms < 60000)
108
+ return Math.floor(ms / 1000) + 's';
109
+ if (ms < 3600000) {
110
+ const m = Math.floor(ms / 60000), s = Math.floor((ms % 60000) / 1000);
111
+ return s ? `${m}m${s}s` : `${m}m`;
112
+ }
113
+ const h = Math.floor(ms / 3600000), m = Math.floor((ms % 3600000) / 60000);
114
+ return m ? `${h}h${m}m` : `${h}h`;
115
+ }
116
+ // a > b ?
117
+ const semverGt = (a, b) => {
118
+ const pa = String(a).split('.').map(Number), pb = String(b).split('.').map(Number);
119
+ for (let i = 0; i < 3; i++) {
120
+ const x = pa[i] || 0, y = pb[i] || 0;
121
+ if (x !== y)
122
+ return x > y;
123
+ }
124
+ return false;
125
+ };
126
+ exports.semverGt = semverGt;
127
+ // 美元成本: <1 保留 3 位、否则 2 位。 0.0123→$0.012 1.5→$1.50
128
+ const fmtCost = (usd) => '$' + (usd < 1 ? usd.toFixed(3) : usd.toFixed(2));
129
+ exports.fmtCost = fmtCost;
130
+ // 距重置的剩余时长(epoch 秒 → 短格式 "2h" / "3d" / "45m");已过期或无值返回 null
131
+ function fmtResetIn(epochSec, nowMs) {
132
+ if (epochSec == null)
133
+ return null;
134
+ const ms = epochSec * 1000 - nowMs;
135
+ if (ms <= 0)
136
+ return null;
137
+ const d = Math.floor(ms / 86400000);
138
+ if (d >= 1) {
139
+ const h = Math.floor((ms % 86400000) / 3600000);
140
+ return h ? `${d}d${h}h` : `${d}d`;
141
+ }
142
+ return fmtDuration(ms);
143
+ }
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.appCacheDir = appCacheDir;
7
+ exports.cacheDir = cacheDir;
8
+ const os_1 = __importDefault(require("os"));
9
+ const path_1 = __importDefault(require("path"));
10
+ function envPath(name) {
11
+ const value = process.env[name];
12
+ return value && value.trim() ? value : null;
13
+ }
14
+ // ccglance 依赖 Claude Code 运行,缓存统一落在 Claude 的配置目录下(而非系统级 cache),
15
+ // 便于随 Claude 目录一起备份/迁移/清理:~/.claude/ccglance/<git|version|transcript>。
16
+ // 尊重 Claude Code 的 CLAUDE_CONFIG_DIR 覆盖(未设置时用 ~/.claude)。
17
+ function claudeConfigDir() {
18
+ return envPath('CLAUDE_CONFIG_DIR') || path_1.default.join(os_1.default.homedir(), '.claude');
19
+ }
20
+ function appCacheDir() {
21
+ return path_1.default.join(claudeConfigDir(), 'ccglance');
22
+ }
23
+ function cacheDir(...parts) {
24
+ return path_1.default.join(appCacheDir(), ...parts);
25
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sanitizeText = sanitizeText;
4
+ const MAX_TEXT_CHARS = 200;
5
+ function sanitizeText(value) {
6
+ let s = String(value);
7
+ s = s
8
+ .replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)?/g, '')
9
+ .replace(/\x9d[^\x07\x9c]*(?:\x07|\x9c)?/g, '')
10
+ .replace(/\x1b[P^_][\s\S]*?(?:\x1b\\|$)/g, '')
11
+ .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '')
12
+ .replace(/\x9b[0-?]*[ -/]*[@-~]/g, '')
13
+ .replace(/\x1b[@-Z\\-_]/g, '')
14
+ .replace(/[\r\n\t]+/g, ' ')
15
+ .replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/g, '');
16
+ const chars = Array.from(s);
17
+ return chars.length > MAX_TEXT_CHARS ? chars.slice(0, MAX_TEXT_CHARS - 3).join('') + '...' : s;
18
+ }
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ // 显示宽度计算 —— 响应式折行的基础。零依赖,按项目内置图标做保守估算。
3
+ // stripAnsi:去掉 ANSI 颜色转义(不占显示宽度)。
4
+ // stringWidth:CJK/emoji 记 2 列,VS16/ZWJ/组合符记 0 列,其余 1 列。
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.stripAnsi = stripAnsi;
7
+ exports.stringWidth = stringWidth;
8
+ // eslint-disable-next-line no-control-regex
9
+ const ANSI_RE = /\x1b\[[0-9;]*m/g;
10
+ function stripAnsi(s) {
11
+ return s.replace(ANSI_RE, '');
12
+ }
13
+ // 记 0 宽的码点:变体选择符(FE00–FE0F)、ZWJ(200D)、组合用记号(0300–036F)、
14
+ // 零宽空格/连接符(200B–200F)。
15
+ function isZeroWidth(cp) {
16
+ return ((cp >= 0x0300 && cp <= 0x036f) ||
17
+ (cp >= 0x200b && cp <= 0x200f) ||
18
+ (cp >= 0xfe00 && cp <= 0xfe0f) ||
19
+ cp === 0x2060 ||
20
+ cp === 0xfeff);
21
+ }
22
+ function isVariationSelector(cp) {
23
+ return cp >= 0xfe00 && cp <= 0xfe0f;
24
+ }
25
+ function isCjkWide(cp) {
26
+ return ((cp >= 0x1100 && cp <= 0x115f) || // Hangul Jamo
27
+ (cp >= 0x2e80 && cp <= 0x303e) || // CJK 部首 / 康熙
28
+ (cp >= 0x3041 && cp <= 0x33ff) || // 平假名/片假名/CJK 符号
29
+ (cp >= 0x3400 && cp <= 0x4dbf) || // CJK 扩展 A
30
+ (cp >= 0x4e00 && cp <= 0x9fff) || // CJK 统一表意
31
+ (cp >= 0xa000 && cp <= 0xa4cf) || // 彝文
32
+ (cp >= 0xac00 && cp <= 0xd7a3) || // 谚文音节
33
+ (cp >= 0xf900 && cp <= 0xfaff) || // CJK 兼容表意
34
+ (cp >= 0xfe30 && cp <= 0xfe4f) || // CJK 兼容形式
35
+ (cp >= 0xff00 && cp <= 0xff60) || // 全角 ASCII
36
+ (cp >= 0xffe0 && cp <= 0xffe6) || // 全角符号
37
+ (cp >= 0x20000 && cp <= 0x3fffd) // CJK 扩展 B+
38
+ );
39
+ }
40
+ function isEmojiPresentation(cp) {
41
+ return ((cp >= 0x1f300 && cp <= 0x1faff) || // emoji & 符号扩展
42
+ (cp >= 0x1f000 && cp <= 0x1f2ff) || // 麻将/多米诺/扑克/字母符号
43
+ cp === 0x231a || cp === 0x231b ||
44
+ (cp >= 0x23e9 && cp <= 0x23ec) ||
45
+ cp === 0x23f0 || cp === 0x23f3 ||
46
+ cp === 0x25fd || cp === 0x25fe ||
47
+ cp === 0x2614 || cp === 0x2615 ||
48
+ (cp >= 0x2648 && cp <= 0x2653) ||
49
+ cp === 0x267f || cp === 0x2693 || cp === 0x26a1 ||
50
+ cp === 0x26aa || cp === 0x26ab ||
51
+ cp === 0x26bd || cp === 0x26be ||
52
+ cp === 0x26c4 || cp === 0x26c5 || cp === 0x26ce ||
53
+ cp === 0x26d4 || cp === 0x26ea ||
54
+ cp === 0x26f2 || cp === 0x26f3 || cp === 0x26f5 ||
55
+ cp === 0x26fa || cp === 0x26fd ||
56
+ cp === 0x2705 || cp === 0x270a || cp === 0x270b ||
57
+ cp === 0x2728 || cp === 0x274c || cp === 0x274e ||
58
+ (cp >= 0x2753 && cp <= 0x2755) ||
59
+ cp === 0x2757 ||
60
+ (cp >= 0x2795 && cp <= 0x2797) ||
61
+ cp === 0x27b0 || cp === 0x27bf ||
62
+ cp === 0x2b50 || cp === 0x2b55);
63
+ }
64
+ function isEmojiCapable(cp) {
65
+ return isEmojiPresentation(cp) ||
66
+ cp === 0x23f1 || cp === 0x23f2 || cp === 0x23f8 ||
67
+ (cp >= 0x2600 && cp <= 0x27bf);
68
+ }
69
+ // 记 2 宽的码点:东亚全角/宽字符 + emoji presentation。
70
+ function isWide(cp) {
71
+ return (isCjkWide(cp) ||
72
+ isEmojiPresentation(cp));
73
+ }
74
+ function stringWidth(s) {
75
+ const plain = stripAnsi(s);
76
+ let w = 0;
77
+ const chars = Array.from(plain);
78
+ for (let i = 0; i < chars.length; i++) {
79
+ const cp = chars[i].codePointAt(0);
80
+ if (cp == null)
81
+ continue;
82
+ if (isZeroWidth(cp))
83
+ continue;
84
+ const next = chars[i + 1]?.codePointAt(0);
85
+ if (next != null && isVariationSelector(next) && isEmojiCapable(cp)) {
86
+ w += 2;
87
+ i++;
88
+ continue;
89
+ }
90
+ w += isWide(cp) ? 2 : 1;
91
+ }
92
+ return w;
93
+ }
Binary file
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "ccglance",
3
+ "version": "1.0.0",
4
+ "description": "A fast, zero-dependency, multi-line status line for Claude Code — model, effort, context, cache, git and session, all at a glance.",
5
+ "bin": {
6
+ "ccglance": "./dist/cli.js"
7
+ },
8
+ "main": "./dist/cli.js",
9
+ "exports": {
10
+ ".": "./dist/cli.js",
11
+ "./package.json": "./package.json"
12
+ },
13
+ "type": "commonjs",
14
+ "files": [
15
+ "dist",
16
+ "docs/assets/preview.png"
17
+ ],
18
+ "engines": {
19
+ "node": ">=22"
20
+ },
21
+ "scripts": {
22
+ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
23
+ "build": "npm run clean && tsc",
24
+ "typecheck": "tsc --noEmit",
25
+ "test": "npm run build && node --test test/*.test.js",
26
+ "benchmark": "npm run build && node bench/latency.js",
27
+ "prepare": "npm run build",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "keywords": [
31
+ "claude-code",
32
+ "claude",
33
+ "anthropic",
34
+ "statusline",
35
+ "status-line",
36
+ "statusbar",
37
+ "cli",
38
+ "terminal"
39
+ ],
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/CxMYu/CcGlanceLine.git"
43
+ },
44
+ "homepage": "https://github.com/CxMYu/CcGlanceLine#readme",
45
+ "bugs": {
46
+ "url": "https://github.com/CxMYu/CcGlanceLine/issues"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "author": "CxMYu",
52
+ "license": "MIT",
53
+ "devDependencies": {
54
+ "@types/node": "^22",
55
+ "typescript": "^5"
56
+ }
57
+ }