ccus-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +178 -0
- package/dist/cli.js +555 -0
- package/dist/lib/aggregate-dashboard.js +749 -0
- package/dist/lib/aggregate.js +168 -0
- package/dist/lib/claude.js +199 -0
- package/dist/lib/dashboard.js +394 -0
- package/dist/lib/debug.js +61 -0
- package/dist/lib/export.js +275 -0
- package/dist/lib/git.js +39 -0
- package/dist/lib/install.js +73 -0
- package/dist/lib/io.js +16 -0
- package/dist/lib/open.js +26 -0
- package/dist/lib/paths.js +56 -0
- package/dist/lib/payload.js +219 -0
- package/dist/lib/storage.js +217 -0
- package/dist/lib/time.js +154 -0
- package/dist/types.js +3 -0
- package/package.json +35 -0
|
@@ -0,0 +1,275 @@
|
|
|
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.buildRawCsv = buildRawCsv;
|
|
7
|
+
exports.buildAggregatedDetailCsv = buildAggregatedDetailCsv;
|
|
8
|
+
exports.buildRawJsonl = buildRawJsonl;
|
|
9
|
+
exports.buildWeeklySummaryJson = buildWeeklySummaryJson;
|
|
10
|
+
exports.buildWeeklyExportBundleJson = buildWeeklyExportBundleJson;
|
|
11
|
+
exports.buildSummaryRows = buildSummaryRows;
|
|
12
|
+
exports.buildSummaryCsv = buildSummaryCsv;
|
|
13
|
+
exports.buildAggregatedDailyCsv = buildAggregatedDailyCsv;
|
|
14
|
+
exports.buildAggregatedWeeklyCsv = buildAggregatedWeeklyCsv;
|
|
15
|
+
exports.writeTextFile = writeTextFile;
|
|
16
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
17
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
18
|
+
const time_1 = require("./time");
|
|
19
|
+
/** CSV 字符串字段统一做转义,避免逗号和引号破坏列结构。 */
|
|
20
|
+
function quoteCsv(value) {
|
|
21
|
+
return `"${value.replaceAll("\"", "\"\"")}"`;
|
|
22
|
+
}
|
|
23
|
+
/** token / context 计数统一换算成百万(M)单位,避免 CSV 里出现长串数字。 */
|
|
24
|
+
function toMillions(value) {
|
|
25
|
+
return value === null ? null : (0, time_1.roundNumber)(value / 1_000_000, 6);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 清理控制字符,并防止 Excel/Sheets 把值解释成公式。
|
|
29
|
+
*
|
|
30
|
+
* 只对字符串字段生效,数字字段保持原样。
|
|
31
|
+
*/
|
|
32
|
+
function sanitizeCsvValue(value) {
|
|
33
|
+
const cleaned = value.replaceAll(/[\r\n\0]+/g, " ").trim();
|
|
34
|
+
if (/^[=+\-@]/.test(cleaned)) {
|
|
35
|
+
return `'${cleaned}`;
|
|
36
|
+
}
|
|
37
|
+
return cleaned;
|
|
38
|
+
}
|
|
39
|
+
/** 把一组值序列化成一行 CSV。 */
|
|
40
|
+
function toCsvLine(values) {
|
|
41
|
+
return values
|
|
42
|
+
.map((value) => {
|
|
43
|
+
if (value === null || value === undefined) {
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
if (typeof value === "number") {
|
|
47
|
+
return String(value);
|
|
48
|
+
}
|
|
49
|
+
return quoteCsv(sanitizeCsvValue(value));
|
|
50
|
+
})
|
|
51
|
+
.join(",");
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 导出原始事件明细,便于后续在 Excel、BI 或脚本里二次分析。
|
|
55
|
+
*/
|
|
56
|
+
function buildRawCsv(events) {
|
|
57
|
+
const header = [
|
|
58
|
+
"timestamp",
|
|
59
|
+
"sessionId",
|
|
60
|
+
"workspaceDir",
|
|
61
|
+
"workspaceName",
|
|
62
|
+
"modelName",
|
|
63
|
+
"gitUserName",
|
|
64
|
+
"gitUserEmail",
|
|
65
|
+
"fiveHourUsagePct",
|
|
66
|
+
"contextWindowPct",
|
|
67
|
+
"contextUsed",
|
|
68
|
+
"contextMax",
|
|
69
|
+
"statusLine",
|
|
70
|
+
];
|
|
71
|
+
const rows = events.map((event) => toCsvLine([
|
|
72
|
+
event.timestamp,
|
|
73
|
+
event.sessionId,
|
|
74
|
+
event.workspaceDir,
|
|
75
|
+
event.workspaceName,
|
|
76
|
+
event.modelName,
|
|
77
|
+
event.gitUserName,
|
|
78
|
+
event.gitUserEmail,
|
|
79
|
+
event.usagePct,
|
|
80
|
+
event.contextWindowPct,
|
|
81
|
+
event.contextUsed,
|
|
82
|
+
event.contextMax,
|
|
83
|
+
event.statusLine,
|
|
84
|
+
]));
|
|
85
|
+
return [header.join(","), ...rows].join("\n");
|
|
86
|
+
}
|
|
87
|
+
/** 多人汇总的 detail.csv。 */
|
|
88
|
+
function buildAggregatedDetailCsv(events) {
|
|
89
|
+
const header = [
|
|
90
|
+
"personKey",
|
|
91
|
+
"timestamp",
|
|
92
|
+
"week",
|
|
93
|
+
"date",
|
|
94
|
+
"sessionId",
|
|
95
|
+
"workspaceName",
|
|
96
|
+
"modelName",
|
|
97
|
+
"fiveHourUsagePct",
|
|
98
|
+
"contextWindowPct",
|
|
99
|
+
"contextUsedM",
|
|
100
|
+
"contextMaxM",
|
|
101
|
+
"inputTokensM",
|
|
102
|
+
"outputTokensM",
|
|
103
|
+
"cacheReadInputTokensM",
|
|
104
|
+
];
|
|
105
|
+
const rows = events.map((event) => toCsvLine([
|
|
106
|
+
event.personKey,
|
|
107
|
+
event.timestamp,
|
|
108
|
+
event.weekKey,
|
|
109
|
+
event.dateKey,
|
|
110
|
+
event.sessionId,
|
|
111
|
+
event.workspaceName,
|
|
112
|
+
event.modelName,
|
|
113
|
+
event.usagePct,
|
|
114
|
+
event.contextWindowPct,
|
|
115
|
+
toMillions(event.contextUsed),
|
|
116
|
+
toMillions(event.contextMax),
|
|
117
|
+
toMillions(event.inputTokens),
|
|
118
|
+
toMillions(event.outputTokens),
|
|
119
|
+
toMillions(event.cacheReadInputTokens),
|
|
120
|
+
]));
|
|
121
|
+
return [header.join(","), ...rows].join("\n");
|
|
122
|
+
}
|
|
123
|
+
/** 原始 JSONL 导出适合程序继续消费。 */
|
|
124
|
+
function buildRawJsonl(events) {
|
|
125
|
+
return events.map((event) => JSON.stringify(event)).join("\n");
|
|
126
|
+
}
|
|
127
|
+
/** 默认导出把本周汇总序列化成可读 JSON。 */
|
|
128
|
+
function buildWeeklySummaryJson(summary) {
|
|
129
|
+
return `${JSON.stringify(summary, null, 2)}\n`;
|
|
130
|
+
}
|
|
131
|
+
/** 默认导出把原始事件与周汇总一起打包成一个 JSON 文件。 */
|
|
132
|
+
function buildWeeklyExportBundleJson(bundle) {
|
|
133
|
+
return `${JSON.stringify(bundle, null, 2)}\n`;
|
|
134
|
+
}
|
|
135
|
+
/** 按天汇总 usage 数据,生成 summary 模式的中间结果。 */
|
|
136
|
+
function buildSummaryRows(events) {
|
|
137
|
+
const grouped = new Map();
|
|
138
|
+
for (const event of events) {
|
|
139
|
+
const key = event.timestamp.slice(0, 10);
|
|
140
|
+
const items = grouped.get(key);
|
|
141
|
+
if (items) {
|
|
142
|
+
items.push(event);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
grouped.set(key, [event]);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return [...grouped.entries()]
|
|
149
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
150
|
+
.map(([date, items]) => {
|
|
151
|
+
const usages = items.map((item) => item.usagePct).filter((value) => value !== null);
|
|
152
|
+
const sevenDayUsages = items.map((item) => item.sevenDayUsagePct).filter((value) => value !== null);
|
|
153
|
+
const latestUsage = [...items]
|
|
154
|
+
.sort((left, right) => new Date(right.timestamp).getTime() - new Date(left.timestamp).getTime())
|
|
155
|
+
.find((item) => item.usagePct !== null)?.usagePct ?? null;
|
|
156
|
+
const latestSevenDayUsage = [...items]
|
|
157
|
+
.sort((left, right) => new Date(right.timestamp).getTime() - new Date(left.timestamp).getTime())
|
|
158
|
+
.find((item) => item.sevenDayUsagePct !== null)?.sevenDayUsagePct ?? null;
|
|
159
|
+
return {
|
|
160
|
+
date,
|
|
161
|
+
sampleCount: items.length,
|
|
162
|
+
fiveHourPeakUsagePct: usages.length > 0 ? (0, time_1.roundNumber)(Math.max(...usages), 1) : null,
|
|
163
|
+
minimumUsagePct: usages.length > 0 ? (0, time_1.roundNumber)(Math.min(...usages), 1) : null,
|
|
164
|
+
fiveHourLatestUsagePct: latestUsage,
|
|
165
|
+
sevenDayLatestUsagePct: latestSevenDayUsage,
|
|
166
|
+
sevenDayPeakUsagePct: sevenDayUsages.length > 0 ? (0, time_1.roundNumber)(Math.max(...sevenDayUsages), 1) : null,
|
|
167
|
+
uniqueSessions: new Set(items.map((item) => item.sessionId).filter(Boolean)).size,
|
|
168
|
+
uniqueWorkspaces: new Set(items.map((item) => item.workspaceDir).filter(Boolean)).size,
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
/** 把 summary 结果导出为 CSV。 */
|
|
173
|
+
function buildSummaryCsv(rows) {
|
|
174
|
+
const header = [
|
|
175
|
+
"date",
|
|
176
|
+
"sampleCount",
|
|
177
|
+
"fiveHourPeakUsagePct",
|
|
178
|
+
"minimumUsagePct",
|
|
179
|
+
"fiveHourLatestUsagePct",
|
|
180
|
+
"sevenDayPeakUsagePct",
|
|
181
|
+
"sevenDayLatestUsagePct",
|
|
182
|
+
"uniqueSessions",
|
|
183
|
+
"uniqueWorkspaces",
|
|
184
|
+
];
|
|
185
|
+
const lines = rows.map((row) => toCsvLine([
|
|
186
|
+
row.date,
|
|
187
|
+
row.sampleCount,
|
|
188
|
+
row.fiveHourPeakUsagePct,
|
|
189
|
+
row.minimumUsagePct,
|
|
190
|
+
row.fiveHourLatestUsagePct,
|
|
191
|
+
row.sevenDayPeakUsagePct,
|
|
192
|
+
row.sevenDayLatestUsagePct,
|
|
193
|
+
row.uniqueSessions,
|
|
194
|
+
row.uniqueWorkspaces,
|
|
195
|
+
]));
|
|
196
|
+
return [header.join(","), ...lines].join("\n");
|
|
197
|
+
}
|
|
198
|
+
/** 多人汇总的 daily.csv。 */
|
|
199
|
+
function buildAggregatedDailyCsv(rows) {
|
|
200
|
+
const header = [
|
|
201
|
+
"personKey",
|
|
202
|
+
"date",
|
|
203
|
+
"userMessageCount",
|
|
204
|
+
"apiRequestCount",
|
|
205
|
+
"inputTokensM",
|
|
206
|
+
"outputTokensM",
|
|
207
|
+
"cacheReadInputTokensM",
|
|
208
|
+
"sampleCount",
|
|
209
|
+
"fiveHourPeakUsagePct",
|
|
210
|
+
"fiveHourLatestUsagePct",
|
|
211
|
+
"sevenDayPeakUsagePct",
|
|
212
|
+
"sevenDayLatestUsagePct",
|
|
213
|
+
"uniqueSessions",
|
|
214
|
+
"uniqueWorkspaces",
|
|
215
|
+
];
|
|
216
|
+
const lines = rows.map((row) => toCsvLine([
|
|
217
|
+
row.personKey,
|
|
218
|
+
row.date,
|
|
219
|
+
row.userMessageCount,
|
|
220
|
+
row.apiRequestCount,
|
|
221
|
+
toMillions(row.inputTokens),
|
|
222
|
+
toMillions(row.outputTokens),
|
|
223
|
+
toMillions(row.cacheReadInputTokens),
|
|
224
|
+
row.sampleCount,
|
|
225
|
+
row.fiveHourPeakUsagePct,
|
|
226
|
+
row.fiveHourLatestUsagePct,
|
|
227
|
+
row.sevenDayPeakUsagePct,
|
|
228
|
+
row.sevenDayLatestUsagePct,
|
|
229
|
+
row.uniqueSessions,
|
|
230
|
+
row.uniqueWorkspaces,
|
|
231
|
+
]));
|
|
232
|
+
return [header.join(","), ...lines].join("\n");
|
|
233
|
+
}
|
|
234
|
+
/** 多人汇总的 weekly.csv。 */
|
|
235
|
+
function buildAggregatedWeeklyCsv(rows) {
|
|
236
|
+
const header = [
|
|
237
|
+
"personKey",
|
|
238
|
+
"week",
|
|
239
|
+
"userMessageCount",
|
|
240
|
+
"apiRequestCount",
|
|
241
|
+
"inputTokensM",
|
|
242
|
+
"outputTokensM",
|
|
243
|
+
"cacheReadInputTokensM",
|
|
244
|
+
"sampleCount",
|
|
245
|
+
"fiveHourPeakUsagePct",
|
|
246
|
+
"fiveHourLatestUsagePct",
|
|
247
|
+
"sevenDayPeakUsagePct",
|
|
248
|
+
"sevenDayLatestUsagePct",
|
|
249
|
+
"uniqueSessions",
|
|
250
|
+
"uniqueWorkspaces",
|
|
251
|
+
];
|
|
252
|
+
const lines = rows.map((row) => toCsvLine([
|
|
253
|
+
row.personKey,
|
|
254
|
+
row.week,
|
|
255
|
+
row.userMessageCount,
|
|
256
|
+
row.apiRequestCount,
|
|
257
|
+
toMillions(row.inputTokens),
|
|
258
|
+
toMillions(row.outputTokens),
|
|
259
|
+
toMillions(row.cacheReadInputTokens),
|
|
260
|
+
row.sampleCount,
|
|
261
|
+
row.fiveHourPeakUsagePct,
|
|
262
|
+
row.fiveHourLatestUsagePct,
|
|
263
|
+
row.sevenDayPeakUsagePct,
|
|
264
|
+
row.sevenDayLatestUsagePct,
|
|
265
|
+
row.uniqueSessions,
|
|
266
|
+
row.uniqueWorkspaces,
|
|
267
|
+
]));
|
|
268
|
+
return [header.join(","), ...lines].join("\n");
|
|
269
|
+
}
|
|
270
|
+
/** 统一负责写文本文件,顺带确保父目录存在。 */
|
|
271
|
+
async function writeTextFile(outputPath, content) {
|
|
272
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(outputPath), { recursive: true });
|
|
273
|
+
await promises_1.default.writeFile(outputPath, content, "utf8");
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=export.js.map
|
package/dist/lib/git.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readGitIdentity = readGitIdentity;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
const node_util_1 = require("node:util");
|
|
6
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
7
|
+
/** 去掉命令输出中的空白,空字符串视为 null。 */
|
|
8
|
+
function normalizeGitValue(value) {
|
|
9
|
+
const trimmed = value.replaceAll(/[\r\n\0]+/g, " ").trim();
|
|
10
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
11
|
+
}
|
|
12
|
+
/** 读取一次全局 git config 键值。 */
|
|
13
|
+
async function readGitConfigValue(args) {
|
|
14
|
+
try {
|
|
15
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
16
|
+
windowsHide: true,
|
|
17
|
+
});
|
|
18
|
+
return normalizeGitValue(stdout);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 读取全局 Git 用户名和邮箱。
|
|
26
|
+
*
|
|
27
|
+
* 只读取全局 git config,不再读取仓库级配置。
|
|
28
|
+
*/
|
|
29
|
+
async function readGitIdentity() {
|
|
30
|
+
const [globalUserName, globalUserEmail] = await Promise.all([
|
|
31
|
+
readGitConfigValue(["config", "--global", "--get", "user.name"]),
|
|
32
|
+
readGitConfigValue(["config", "--global", "--get", "user.email"]),
|
|
33
|
+
]);
|
|
34
|
+
return {
|
|
35
|
+
userName: globalUserName,
|
|
36
|
+
userEmail: globalUserEmail,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=git.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
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.installStatusline = installStatusline;
|
|
7
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
/** 把 settings.json 解析成对象;文件缺失或为空时返回空对象,无法解析时直接报错。 */
|
|
10
|
+
async function readExistingSettings(settingsPath) {
|
|
11
|
+
let raw;
|
|
12
|
+
try {
|
|
13
|
+
raw = await promises_1.default.readFile(settingsPath, "utf8");
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (error.code === "ENOENT") {
|
|
17
|
+
return { settings: {}, existed: false };
|
|
18
|
+
}
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
const trimmed = raw.trim();
|
|
22
|
+
if (trimmed.length === 0) {
|
|
23
|
+
return { settings: {}, existed: true };
|
|
24
|
+
}
|
|
25
|
+
let parsed;
|
|
26
|
+
try {
|
|
27
|
+
parsed = JSON.parse(trimmed);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// 不要覆盖无法解析的配置文件,否则可能连带破坏用户的其它 Claude 设置。
|
|
31
|
+
throw new Error(`Cannot parse Claude settings as JSON: ${settingsPath}. Fix or remove it, then retry.`);
|
|
32
|
+
}
|
|
33
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
34
|
+
throw new Error(`Claude settings is not a JSON object: ${settingsPath}`);
|
|
35
|
+
}
|
|
36
|
+
return { settings: parsed, existed: true };
|
|
37
|
+
}
|
|
38
|
+
/** 读取现有 statusLine.command(如果有的话),用于回显被替换的旧值。 */
|
|
39
|
+
function readStatuslineCommand(settings) {
|
|
40
|
+
const statusLine = settings.statusLine;
|
|
41
|
+
if (typeof statusLine !== "object" || statusLine === null || Array.isArray(statusLine)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const command = statusLine.command;
|
|
45
|
+
return typeof command === "string" ? command : null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 把 ccus 的 statusLine 命令写进 Claude Code 的 settings.json。
|
|
49
|
+
*
|
|
50
|
+
* 只覆盖 statusLine 字段(并保留其下已有的其它键,如 padding),其它顶层设置原样保留。
|
|
51
|
+
*/
|
|
52
|
+
async function installStatusline(settingsPath, command) {
|
|
53
|
+
const { settings, existed } = await readExistingSettings(settingsPath);
|
|
54
|
+
const previousCommand = readStatuslineCommand(settings);
|
|
55
|
+
const existingStatusLine = typeof settings.statusLine === "object" && settings.statusLine !== null && !Array.isArray(settings.statusLine)
|
|
56
|
+
? settings.statusLine
|
|
57
|
+
: {};
|
|
58
|
+
settings.statusLine = {
|
|
59
|
+
...existingStatusLine,
|
|
60
|
+
type: "command",
|
|
61
|
+
command,
|
|
62
|
+
};
|
|
63
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(settingsPath), { recursive: true });
|
|
64
|
+
await promises_1.default.writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
|
|
65
|
+
return {
|
|
66
|
+
settingsPath,
|
|
67
|
+
command,
|
|
68
|
+
previousCommand,
|
|
69
|
+
created: !existed,
|
|
70
|
+
unchanged: previousCommand === command,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=install.js.map
|
package/dist/lib/io.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readStdin = readStdin;
|
|
4
|
+
/**
|
|
5
|
+
* 读取完整 stdin 内容。
|
|
6
|
+
*
|
|
7
|
+
* Claude Code statusline 官方契约就是把 session JSON 通过 stdin 传给命令。
|
|
8
|
+
*/
|
|
9
|
+
async function readStdin() {
|
|
10
|
+
const chunks = [];
|
|
11
|
+
for await (const chunk of process.stdin) {
|
|
12
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
13
|
+
}
|
|
14
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=io.js.map
|
package/dist/lib/open.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.openInBrowser = openInBrowser;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
/**
|
|
6
|
+
* 用系统默认浏览器打开生成好的 dashboard。
|
|
7
|
+
*
|
|
8
|
+
* 这里不拼接 shell 字符串,而是显式传参数,降低路径注入风险。
|
|
9
|
+
*/
|
|
10
|
+
async function openInBrowser(filePath) {
|
|
11
|
+
const command = process.platform === "win32"
|
|
12
|
+
? { program: "cmd", args: ["/c", "start", "", filePath] }
|
|
13
|
+
: process.platform === "darwin"
|
|
14
|
+
? { program: "open", args: [filePath] }
|
|
15
|
+
: { program: "xdg-open", args: [filePath] };
|
|
16
|
+
await new Promise((resolve, reject) => {
|
|
17
|
+
const child = (0, node_child_process_1.spawn)(command.program, command.args, {
|
|
18
|
+
detached: true,
|
|
19
|
+
stdio: "ignore",
|
|
20
|
+
});
|
|
21
|
+
child.on("error", reject);
|
|
22
|
+
child.unref();
|
|
23
|
+
resolve();
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=open.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
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.getDefaultDataDir = getDefaultDataDir;
|
|
7
|
+
exports.getEventsDir = getEventsDir;
|
|
8
|
+
exports.getDashboardDir = getDashboardDir;
|
|
9
|
+
exports.getClaudeDataDir = getClaudeDataDir;
|
|
10
|
+
exports.getClaudeSettingsPath = getClaudeSettingsPath;
|
|
11
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
/**
|
|
14
|
+
* 按平台推导默认数据目录,同时允许通过环境变量显式覆盖。
|
|
15
|
+
*/
|
|
16
|
+
function getDefaultDataDir() {
|
|
17
|
+
const appData = process.env.CCUS_DATA_DIR;
|
|
18
|
+
if (appData) {
|
|
19
|
+
return node_path_1.default.resolve(appData);
|
|
20
|
+
}
|
|
21
|
+
if (process.platform === "win32") {
|
|
22
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
23
|
+
if (localAppData) {
|
|
24
|
+
return node_path_1.default.join(localAppData, "ccus");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (process.platform === "darwin") {
|
|
28
|
+
return node_path_1.default.join(node_os_1.default.homedir(), "Library", "Application Support", "ccus");
|
|
29
|
+
}
|
|
30
|
+
const xdgDataHome = process.env.XDG_DATA_HOME;
|
|
31
|
+
if (xdgDataHome) {
|
|
32
|
+
return node_path_1.default.join(xdgDataHome, "ccus");
|
|
33
|
+
}
|
|
34
|
+
return node_path_1.default.join(node_os_1.default.homedir(), ".local", "share", "ccus");
|
|
35
|
+
}
|
|
36
|
+
/** 所有采样事件的根目录。 */
|
|
37
|
+
function getEventsDir(dataDir) {
|
|
38
|
+
return node_path_1.default.join(dataDir, "events");
|
|
39
|
+
}
|
|
40
|
+
/** 生成 dashboard 文件的默认目录。 */
|
|
41
|
+
function getDashboardDir(dataDir) {
|
|
42
|
+
return node_path_1.default.join(dataDir, "dashboard");
|
|
43
|
+
}
|
|
44
|
+
/** Claude Code 默认本地数据目录。 */
|
|
45
|
+
function getClaudeDataDir() {
|
|
46
|
+
const configured = process.env.CCUS_CLAUDE_DATA_DIR;
|
|
47
|
+
if (configured) {
|
|
48
|
+
return node_path_1.default.resolve(configured);
|
|
49
|
+
}
|
|
50
|
+
return node_path_1.default.join(node_os_1.default.homedir(), ".claude");
|
|
51
|
+
}
|
|
52
|
+
/** Claude Code 用户级 settings.json 路径,install 命令会往里写 statusLine 配置。 */
|
|
53
|
+
function getClaudeSettingsPath() {
|
|
54
|
+
return node_path_1.default.join(getClaudeDataDir(), "settings.json");
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=paths.js.map
|