@say8425/cc-statusline 3.1.0 → 3.2.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/README.md +42 -1
- package/dist/index.js +219 -107
- package/dist/viewer/index.html +486 -195
- package/dist/viewer/main.js +44 -44
- package/package.json +9 -5
package/dist/index.js
CHANGED
|
@@ -19,41 +19,29 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
19
19
|
// src/diff-server/config.ts
|
|
20
20
|
import { homedir } from "os";
|
|
21
21
|
import { join } from "path";
|
|
22
|
-
|
|
22
|
+
var DEFAULT_DIFF_PORT = 49573, resolveDiffPort = (env = process.env) => {
|
|
23
23
|
const raw = env.CC_STATUSLINE_DIFF_PORT;
|
|
24
24
|
if (!raw)
|
|
25
25
|
return DEFAULT_DIFF_PORT;
|
|
26
26
|
const n = Number.parseInt(raw, 10);
|
|
27
27
|
return Number.isInteger(n) && n > 0 && n < 65536 ? n : DEFAULT_DIFF_PORT;
|
|
28
|
-
}
|
|
29
|
-
function isDiffViewerDisabled(env = process.env) {
|
|
30
|
-
return env.CC_STATUSLINE_DIFF_DISABLE === "1";
|
|
31
|
-
}
|
|
32
|
-
function getCacheDir(env = process.env) {
|
|
28
|
+
}, isDiffViewerDisabled = (env = process.env) => env.CC_STATUSLINE_DIFF_DISABLE === "1", getCacheDir = (env = process.env) => {
|
|
33
29
|
const base = env.XDG_CACHE_HOME || join(env.HOME || homedir(), ".cache");
|
|
34
30
|
return join(base, "cc-statusline");
|
|
35
|
-
}
|
|
36
|
-
var DEFAULT_DIFF_PORT = 49573;
|
|
31
|
+
};
|
|
37
32
|
var init_config = () => {};
|
|
38
33
|
|
|
39
34
|
// src/diff-server/token.ts
|
|
40
35
|
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
41
36
|
import { join as join2 } from "path";
|
|
42
|
-
|
|
43
|
-
return join2(getCacheDir(env), "diff-server.token");
|
|
44
|
-
}
|
|
45
|
-
function generateToken() {
|
|
46
|
-
return crypto.randomUUID().replaceAll("-", "");
|
|
47
|
-
}
|
|
48
|
-
function readTokenSync(env = process.env) {
|
|
37
|
+
var getTokenPath = (env = process.env) => join2(getCacheDir(env), "diff-server.token"), generateToken = () => crypto.randomUUID().replaceAll("-", ""), readTokenSync = (env = process.env) => {
|
|
49
38
|
try {
|
|
50
39
|
const value = readFileSync(getTokenPath(env), "utf8").trim();
|
|
51
40
|
return value || null;
|
|
52
41
|
} catch {
|
|
53
42
|
return null;
|
|
54
43
|
}
|
|
55
|
-
}
|
|
56
|
-
function ensureToken(env = process.env) {
|
|
44
|
+
}, ensureToken = (env = process.env) => {
|
|
57
45
|
const existing = readTokenSync(env);
|
|
58
46
|
if (existing)
|
|
59
47
|
return existing;
|
|
@@ -61,36 +49,49 @@ function ensureToken(env = process.env) {
|
|
|
61
49
|
mkdirSync(getCacheDir(env), { recursive: true, mode: 448 });
|
|
62
50
|
writeFileSync(getTokenPath(env), token, { mode: 384 });
|
|
63
51
|
return token;
|
|
64
|
-
}
|
|
52
|
+
};
|
|
65
53
|
var init_token = __esm(() => {
|
|
66
54
|
init_config();
|
|
67
55
|
});
|
|
68
56
|
|
|
57
|
+
// src/diff-server/mapLimit.ts
|
|
58
|
+
var mapWithLimit = async (items, limit, fn) => {
|
|
59
|
+
const results = [];
|
|
60
|
+
let next = 0;
|
|
61
|
+
const worker = async () => {
|
|
62
|
+
while (next < items.length) {
|
|
63
|
+
const index = next;
|
|
64
|
+
next += 1;
|
|
65
|
+
results[index] = await fn(items[index], index);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const workers = Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, () => worker());
|
|
69
|
+
await Promise.all(workers);
|
|
70
|
+
return results;
|
|
71
|
+
};
|
|
72
|
+
|
|
69
73
|
// src/diff-server/diff.ts
|
|
70
74
|
import { readFileSync as readFileSync2 } from "fs";
|
|
71
|
-
import { join as join4 } from "path";
|
|
75
|
+
import { isAbsolute, join as join4, resolve } from "path";
|
|
72
76
|
var {$ } = globalThis.Bun;
|
|
73
|
-
|
|
77
|
+
var BUILD_CONCURRENCY = 8, isGitRepo = async (repo) => {
|
|
74
78
|
try {
|
|
75
79
|
const out = await $`git -C ${repo} rev-parse --is-inside-work-tree 2>/dev/null`.text();
|
|
76
80
|
return out.trim() === "true";
|
|
77
81
|
} catch {
|
|
78
82
|
return false;
|
|
79
83
|
}
|
|
80
|
-
}
|
|
81
|
-
async function refExists(repo, ref) {
|
|
84
|
+
}, refExists = async (repo, ref) => {
|
|
82
85
|
const r = await $`git -C ${repo} rev-parse --verify --quiet ${ref}`.nothrow().quiet();
|
|
83
86
|
return r.exitCode === 0;
|
|
84
|
-
}
|
|
85
|
-
async function prBaseName(repo) {
|
|
87
|
+
}, prBaseName = async (repo) => {
|
|
86
88
|
try {
|
|
87
89
|
const out = await $`gh pr view --json baseRefName -q .baseRefName`.cwd(repo).nothrow().quiet().text();
|
|
88
90
|
return out.trim() || null;
|
|
89
91
|
} catch {
|
|
90
92
|
return null;
|
|
91
93
|
}
|
|
92
|
-
}
|
|
93
|
-
async function defaultBranchName(repo) {
|
|
94
|
+
}, defaultBranchName = async (repo) => {
|
|
94
95
|
const r = await $`git -C ${repo} rev-parse --abbrev-ref origin/HEAD 2>/dev/null`.nothrow().quiet();
|
|
95
96
|
if (r.exitCode !== 0)
|
|
96
97
|
return null;
|
|
@@ -99,8 +100,7 @@ async function defaultBranchName(repo) {
|
|
|
99
100
|
return null;
|
|
100
101
|
const name = t.slice("origin/".length);
|
|
101
102
|
return name && name !== "HEAD" ? name : null;
|
|
102
|
-
}
|
|
103
|
-
async function resolveBaseRef(repo) {
|
|
103
|
+
}, resolveBaseRef = async (repo) => {
|
|
104
104
|
const named = await prBaseName(repo) ?? await defaultBranchName(repo);
|
|
105
105
|
const candidates = named ? [`origin/${named}`, named] : ["origin/main", "origin/master", "main", "master"];
|
|
106
106
|
for (const ref of candidates) {
|
|
@@ -110,19 +110,16 @@ async function resolveBaseRef(repo) {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
return { base: named, ref: null };
|
|
113
|
-
}
|
|
114
|
-
async function showBytes(repo, rev, path) {
|
|
113
|
+
}, showBytes = async (repo, rev, path) => {
|
|
115
114
|
const buf = await $`git -C ${repo} show ${`${rev}:${path}`} 2>/dev/null`.nothrow().arrayBuffer();
|
|
116
115
|
return new Uint8Array(buf);
|
|
117
|
-
}
|
|
118
|
-
function readWorkingBytes(repo, path) {
|
|
116
|
+
}, readWorkingBytes = (repo, path) => {
|
|
119
117
|
try {
|
|
120
118
|
return new Uint8Array(readFileSync2(join4(repo, path)));
|
|
121
119
|
} catch {
|
|
122
120
|
return new Uint8Array;
|
|
123
121
|
}
|
|
124
|
-
}
|
|
125
|
-
async function buildFile(repo, base, status, name, oldName) {
|
|
122
|
+
}, buildFile = async (repo, base, status, name, oldName) => {
|
|
126
123
|
const oldBytes = status === "added" || status === "untracked" ? new Uint8Array : await showBytes(repo, base, oldName ?? name);
|
|
127
124
|
const newBytes = status === "deleted" ? new Uint8Array : readWorkingBytes(repo, name);
|
|
128
125
|
const binary = oldBytes.includes(0) || newBytes.includes(0);
|
|
@@ -133,14 +130,32 @@ async function buildFile(repo, base, status, name, oldName) {
|
|
|
133
130
|
status,
|
|
134
131
|
binary,
|
|
135
132
|
oldContents: binary ? "" : decoder.decode(oldBytes),
|
|
136
|
-
newContents: binary ? "" : decoder.decode(newBytes)
|
|
133
|
+
newContents: binary ? "" : decoder.decode(newBytes),
|
|
134
|
+
...binary ? {
|
|
135
|
+
blobVersion: `${Bun.hash(oldBytes).toString(36)}.${Bun.hash(newBytes).toString(36)}`
|
|
136
|
+
} : {}
|
|
137
137
|
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const
|
|
138
|
+
}, resolveDiffBaseRev = async (repo, opts) => opts.mode === "base" && opts.ref ? (await $`git -C ${repo} merge-base ${opts.ref} HEAD 2>/dev/null`.nothrow().text()).trim() : "HEAD", getFileBytes = async (repo, path, side, opts = {}) => {
|
|
139
|
+
const root = resolve(repo);
|
|
140
|
+
const target = resolve(root, path);
|
|
141
|
+
if (!path || isAbsolute(path) || target !== root && !target.startsWith(`${root}/`)) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
if (side === "new") {
|
|
145
|
+
const bytes2 = readWorkingBytes(repo, path);
|
|
146
|
+
return bytes2.length > 0 ? bytes2 : null;
|
|
147
|
+
}
|
|
148
|
+
const base = await resolveDiffBaseRev(repo, opts);
|
|
149
|
+
if (!base)
|
|
150
|
+
return null;
|
|
151
|
+
const bytes = await showBytes(repo, base, path);
|
|
152
|
+
return bytes.length > 0 ? bytes : null;
|
|
153
|
+
}, getDiffFiles = async (repo, opts = {}) => {
|
|
154
|
+
const base = await resolveDiffBaseRev(repo, opts);
|
|
141
155
|
const files = [];
|
|
142
156
|
if (base) {
|
|
143
157
|
const nameStatus = await $`git -C ${repo} diff --name-status ${base} 2>/dev/null`.nothrow().text();
|
|
158
|
+
const specs = [];
|
|
144
159
|
for (const line of nameStatus.split(`
|
|
145
160
|
`)) {
|
|
146
161
|
if (!line.trim())
|
|
@@ -148,34 +163,64 @@ async function getDiffFiles(repo, opts = {}) {
|
|
|
148
163
|
const parts = line.split("\t");
|
|
149
164
|
const code = parts[0] ?? "";
|
|
150
165
|
if (code.startsWith("R")) {
|
|
151
|
-
|
|
166
|
+
specs.push({
|
|
167
|
+
status: "renamed",
|
|
168
|
+
name: parts[2] ?? "",
|
|
169
|
+
oldName: parts[1]
|
|
170
|
+
});
|
|
152
171
|
} else if (code.startsWith("A")) {
|
|
153
|
-
|
|
172
|
+
specs.push({ status: "added", name: parts[1] ?? "" });
|
|
154
173
|
} else if (code.startsWith("D")) {
|
|
155
|
-
|
|
174
|
+
specs.push({ status: "deleted", name: parts[1] ?? "" });
|
|
156
175
|
} else {
|
|
157
|
-
|
|
176
|
+
specs.push({ status: "modified", name: parts[1] ?? "" });
|
|
158
177
|
}
|
|
159
178
|
}
|
|
179
|
+
files.push(...await mapWithLimit(specs, BUILD_CONCURRENCY, (spec) => buildFile(repo, base, spec.status, spec.name, spec.oldName)));
|
|
160
180
|
}
|
|
161
181
|
if (opts.untracked) {
|
|
162
182
|
const listed = await $`git -C ${repo} ls-files --others --exclude-standard 2>/dev/null`.nothrow().text();
|
|
163
|
-
|
|
164
|
-
`).map((s) => s.trim()).filter(Boolean)
|
|
165
|
-
|
|
166
|
-
}
|
|
183
|
+
const paths = listed.split(`
|
|
184
|
+
`).map((s) => s.trim()).filter(Boolean);
|
|
185
|
+
files.push(...await mapWithLimit(paths, BUILD_CONCURRENCY, (path) => buildFile(repo, base, "untracked", path)));
|
|
167
186
|
}
|
|
168
187
|
return files;
|
|
169
|
-
}
|
|
188
|
+
};
|
|
170
189
|
var init_diff = () => {};
|
|
171
190
|
|
|
191
|
+
// src/diff-server/imageTypes.ts
|
|
192
|
+
var IMAGE_MIME, extensionOf = (name) => {
|
|
193
|
+
const dot = name.lastIndexOf(".");
|
|
194
|
+
if (dot <= 0 || dot === name.length - 1)
|
|
195
|
+
return null;
|
|
196
|
+
return name.slice(dot + 1).toLowerCase();
|
|
197
|
+
}, isImagePath = (name) => {
|
|
198
|
+
const ext = extensionOf(name);
|
|
199
|
+
return ext !== null && ext in IMAGE_MIME;
|
|
200
|
+
}, imageContentType = (name) => {
|
|
201
|
+
const ext = extensionOf(name);
|
|
202
|
+
return ext && IMAGE_MIME[ext] || "application/octet-stream";
|
|
203
|
+
};
|
|
204
|
+
var init_imageTypes = __esm(() => {
|
|
205
|
+
IMAGE_MIME = {
|
|
206
|
+
png: "image/png",
|
|
207
|
+
jpg: "image/jpeg",
|
|
208
|
+
jpeg: "image/jpeg",
|
|
209
|
+
gif: "image/gif",
|
|
210
|
+
webp: "image/webp",
|
|
211
|
+
avif: "image/avif",
|
|
212
|
+
bmp: "image/bmp",
|
|
213
|
+
ico: "image/x-icon"
|
|
214
|
+
};
|
|
215
|
+
});
|
|
216
|
+
|
|
172
217
|
// src/diff-server/server.ts
|
|
173
218
|
var exports_server = {};
|
|
174
219
|
__export(exports_server, {
|
|
175
220
|
startDiffServer: () => startDiffServer
|
|
176
221
|
});
|
|
177
|
-
import { resolve } from "path";
|
|
178
|
-
|
|
222
|
+
import { resolve as resolve2 } from "path";
|
|
223
|
+
var BASE_TTL_MS = 1e4, baseCache, resolveBaseCached = async (repo) => {
|
|
179
224
|
const now = Date.now();
|
|
180
225
|
const hit = baseCache.get(repo);
|
|
181
226
|
if (hit && now - hit.at < BASE_TTL_MS)
|
|
@@ -183,9 +228,8 @@ async function resolveBaseCached(repo) {
|
|
|
183
228
|
const value = await resolveBaseRef(repo);
|
|
184
229
|
baseCache.set(repo, { value, at: now });
|
|
185
230
|
return value;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const viewerRoot = resolve(cfg.viewerDir);
|
|
231
|
+
}, createHandler = (cfg) => {
|
|
232
|
+
const viewerRoot = resolve2(cfg.viewerDir);
|
|
189
233
|
return async (req) => {
|
|
190
234
|
const url = new URL(req.url);
|
|
191
235
|
if (url.pathname === "/api/ping") {
|
|
@@ -217,8 +261,33 @@ function createHandler(cfg) {
|
|
|
217
261
|
}
|
|
218
262
|
});
|
|
219
263
|
}
|
|
264
|
+
if (url.pathname === "/api/blob") {
|
|
265
|
+
if (url.searchParams.get("token") !== cfg.token) {
|
|
266
|
+
return new Response("forbidden", { status: 403 });
|
|
267
|
+
}
|
|
268
|
+
const repo = url.searchParams.get("repo") ?? "";
|
|
269
|
+
if (!repo || !await isGitRepo(repo)) {
|
|
270
|
+
return new Response("not a git repository", { status: 400 });
|
|
271
|
+
}
|
|
272
|
+
const path = url.searchParams.get("path") ?? "";
|
|
273
|
+
if (!isImagePath(path)) {
|
|
274
|
+
return new Response("not found", { status: 404 });
|
|
275
|
+
}
|
|
276
|
+
const side = url.searchParams.get("side") === "old" ? "old" : "new";
|
|
277
|
+
const mode = url.searchParams.get("mode") === "base" ? "base" : "working";
|
|
278
|
+
const ref = mode === "base" ? (await resolveBaseCached(repo)).ref : null;
|
|
279
|
+
const bytes = await getFileBytes(repo, path, side, mode === "base" && ref ? { mode, ref } : {});
|
|
280
|
+
if (!bytes)
|
|
281
|
+
return new Response("not found", { status: 404 });
|
|
282
|
+
return new Response(bytes, {
|
|
283
|
+
headers: {
|
|
284
|
+
"content-type": imageContentType(path),
|
|
285
|
+
"cache-control": "no-store"
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
220
289
|
const rel = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
|
|
221
|
-
const filePath =
|
|
290
|
+
const filePath = resolve2(viewerRoot, rel);
|
|
222
291
|
if (filePath !== viewerRoot && !filePath.startsWith(`${viewerRoot}/`)) {
|
|
223
292
|
return new Response("forbidden", { status: 403 });
|
|
224
293
|
}
|
|
@@ -228,8 +297,7 @@ function createHandler(cfg) {
|
|
|
228
297
|
}
|
|
229
298
|
return new Response("not found", { status: 404 });
|
|
230
299
|
};
|
|
231
|
-
}
|
|
232
|
-
function startDiffServer(opts) {
|
|
300
|
+
}, startDiffServer = (opts) => {
|
|
233
301
|
const env = opts.env ?? process.env;
|
|
234
302
|
const token = ensureToken(env);
|
|
235
303
|
let lastActivity = Date.now();
|
|
@@ -253,23 +321,23 @@ function startDiffServer(opts) {
|
|
|
253
321
|
}, 60000);
|
|
254
322
|
idleTimer.unref?.();
|
|
255
323
|
}
|
|
256
|
-
|
|
324
|
+
const stop = () => {
|
|
257
325
|
if (idleTimer)
|
|
258
326
|
clearInterval(idleTimer);
|
|
259
327
|
server.stop(true);
|
|
260
|
-
}
|
|
328
|
+
};
|
|
261
329
|
return { server, token, stop };
|
|
262
|
-
}
|
|
263
|
-
var BASE_TTL_MS = 1e4, baseCache;
|
|
330
|
+
};
|
|
264
331
|
var init_server = __esm(() => {
|
|
265
332
|
init_diff();
|
|
333
|
+
init_imageTypes();
|
|
266
334
|
init_token();
|
|
267
335
|
baseCache = new Map;
|
|
268
336
|
});
|
|
269
337
|
|
|
270
338
|
// src/index.ts
|
|
271
339
|
init_config();
|
|
272
|
-
import { join as
|
|
340
|
+
import { join as join6 } from "path";
|
|
273
341
|
|
|
274
342
|
// src/diff-server/ensure.ts
|
|
275
343
|
init_config();
|
|
@@ -279,7 +347,7 @@ import { join as join3 } from "path";
|
|
|
279
347
|
var ENSURE_TTL_MS = 5000;
|
|
280
348
|
var LOCK_STALE_MS = 30000;
|
|
281
349
|
var checkedAt = 0;
|
|
282
|
-
async
|
|
350
|
+
var probeOurServer = async (port) => {
|
|
283
351
|
try {
|
|
284
352
|
const res = await fetch(`http://127.0.0.1:${port}/api/ping`, {
|
|
285
353
|
signal: AbortSignal.timeout(150)
|
|
@@ -288,8 +356,8 @@ async function probeOurServer(port) {
|
|
|
288
356
|
} catch {
|
|
289
357
|
return false;
|
|
290
358
|
}
|
|
291
|
-
}
|
|
292
|
-
|
|
359
|
+
};
|
|
360
|
+
var acquireSpawnLock = (env) => {
|
|
293
361
|
const lock = join3(getCacheDir(env), "diff-server.lock");
|
|
294
362
|
mkdirSync2(getCacheDir(env), { recursive: true, mode: 448 });
|
|
295
363
|
try {
|
|
@@ -305,8 +373,8 @@ function acquireSpawnLock(env) {
|
|
|
305
373
|
} catch {}
|
|
306
374
|
return false;
|
|
307
375
|
}
|
|
308
|
-
}
|
|
309
|
-
|
|
376
|
+
};
|
|
377
|
+
var spawnDaemon = (port, env) => {
|
|
310
378
|
const execPath = process.execPath;
|
|
311
379
|
const selfPath = Bun.main;
|
|
312
380
|
Bun.spawn([
|
|
@@ -321,8 +389,8 @@ function spawnDaemon(port, env) {
|
|
|
321
389
|
stdout: "ignore",
|
|
322
390
|
stderr: "ignore"
|
|
323
391
|
}).unref();
|
|
324
|
-
}
|
|
325
|
-
async
|
|
392
|
+
};
|
|
393
|
+
var maybeSpawn = async (port, env, spawn = spawnDaemon) => {
|
|
326
394
|
try {
|
|
327
395
|
if (await probeOurServer(port))
|
|
328
396
|
return;
|
|
@@ -330,8 +398,8 @@ async function maybeSpawn(port, env, spawn = spawnDaemon) {
|
|
|
330
398
|
return;
|
|
331
399
|
spawn(port, env);
|
|
332
400
|
} catch {}
|
|
333
|
-
}
|
|
334
|
-
|
|
401
|
+
};
|
|
402
|
+
var ensureDiffServer = (_repo, env = process.env, spawn = spawnDaemon) => {
|
|
335
403
|
if (isDiffViewerDisabled(env))
|
|
336
404
|
return null;
|
|
337
405
|
const port = resolveDiffPort(env);
|
|
@@ -342,10 +410,10 @@ async function ensureDiffServer(_repo, env = process.env, spawn = spawnDaemon) {
|
|
|
342
410
|
}
|
|
343
411
|
const token = readTokenSync(env);
|
|
344
412
|
return token ? { port, token } : null;
|
|
345
|
-
}
|
|
413
|
+
};
|
|
346
414
|
|
|
347
415
|
// src/diff-server/link.ts
|
|
348
|
-
|
|
416
|
+
var buildDiffViewerUrl = (params) => {
|
|
349
417
|
const query = new URLSearchParams({
|
|
350
418
|
repo: params.repo,
|
|
351
419
|
token: params.token
|
|
@@ -353,7 +421,7 @@ function buildDiffViewerUrl(params) {
|
|
|
353
421
|
if (params.mode)
|
|
354
422
|
query.set("mode", params.mode);
|
|
355
423
|
return `http://127.0.0.1:${params.port}/?${query.toString()}`;
|
|
356
|
-
}
|
|
424
|
+
};
|
|
357
425
|
|
|
358
426
|
// src/git/baseChanges.ts
|
|
359
427
|
var {$: $2 } = globalThis.Bun;
|
|
@@ -367,31 +435,33 @@ var cache = {
|
|
|
367
435
|
baseRef: {
|
|
368
436
|
value: null,
|
|
369
437
|
timestamp: 0
|
|
370
|
-
}
|
|
438
|
+
},
|
|
439
|
+
ultracode: { value: false, timestamp: 0 }
|
|
371
440
|
};
|
|
372
441
|
var CACHE_TTL = {
|
|
373
442
|
branch: 5000,
|
|
374
443
|
gitChanges: 0,
|
|
375
444
|
prUrl: 30000,
|
|
376
445
|
mainProjectName: 300000,
|
|
377
|
-
baseRef: 30000
|
|
446
|
+
baseRef: 30000,
|
|
447
|
+
ultracode: 5000
|
|
378
448
|
};
|
|
379
449
|
|
|
380
450
|
// src/git/baseChanges.ts
|
|
381
451
|
init_diff();
|
|
382
452
|
|
|
383
453
|
// src/git/shortstat.ts
|
|
384
|
-
|
|
454
|
+
var parseShortstat = (text) => {
|
|
385
455
|
const [files, insertions, deletions] = [
|
|
386
456
|
/(\d+) files?/g,
|
|
387
457
|
/(\d+) insertions?/g,
|
|
388
458
|
/(\d+) deletions?/g
|
|
389
459
|
].map((regex) => (text.match(regex) || []).reduce((sum, m) => sum + Number.parseInt(m, 10), 0));
|
|
390
460
|
return { files, insertions, deletions };
|
|
391
|
-
}
|
|
461
|
+
};
|
|
392
462
|
|
|
393
463
|
// src/git/baseChanges.ts
|
|
394
|
-
async
|
|
464
|
+
var getBaseChangesCached = async () => {
|
|
395
465
|
try {
|
|
396
466
|
let resolved = cache.baseRef.value;
|
|
397
467
|
if (resolved === null || Date.now() - cache.baseRef.timestamp >= CACHE_TTL.baseRef) {
|
|
@@ -411,10 +481,10 @@ async function getBaseChangesCached() {
|
|
|
411
481
|
} catch {
|
|
412
482
|
return null;
|
|
413
483
|
}
|
|
414
|
-
}
|
|
484
|
+
};
|
|
415
485
|
// src/git/branch.ts
|
|
416
486
|
var {$: $3 } = globalThis.Bun;
|
|
417
|
-
async
|
|
487
|
+
var getBranchCached = async () => {
|
|
418
488
|
if (Date.now() - cache.branch.timestamp < CACHE_TTL.branch) {
|
|
419
489
|
return cache.branch.value;
|
|
420
490
|
}
|
|
@@ -425,10 +495,10 @@ async function getBranchCached() {
|
|
|
425
495
|
} catch {
|
|
426
496
|
return cache.branch.value;
|
|
427
497
|
}
|
|
428
|
-
}
|
|
498
|
+
};
|
|
429
499
|
// src/git/changes.ts
|
|
430
500
|
var {$: $4 } = globalThis.Bun;
|
|
431
|
-
async
|
|
501
|
+
var getGitChangesCached = async () => {
|
|
432
502
|
if (Date.now() - cache.gitChanges.timestamp < CACHE_TTL.gitChanges) {
|
|
433
503
|
return cache.gitChanges;
|
|
434
504
|
}
|
|
@@ -444,10 +514,10 @@ ${staged}`);
|
|
|
444
514
|
} catch {
|
|
445
515
|
return cache.gitChanges;
|
|
446
516
|
}
|
|
447
|
-
}
|
|
517
|
+
};
|
|
448
518
|
// src/git/pr.ts
|
|
449
519
|
var {$: $5 } = globalThis.Bun;
|
|
450
|
-
async
|
|
520
|
+
var getPrUrlCached = async () => {
|
|
451
521
|
if (Date.now() - cache.prUrl.timestamp < CACHE_TTL.prUrl) {
|
|
452
522
|
return cache.prUrl.value;
|
|
453
523
|
}
|
|
@@ -458,10 +528,10 @@ async function getPrUrlCached() {
|
|
|
458
528
|
} catch {
|
|
459
529
|
return cache.prUrl.value;
|
|
460
530
|
}
|
|
461
|
-
}
|
|
531
|
+
};
|
|
462
532
|
// src/git/worktree.ts
|
|
463
533
|
var {$: $6 } = globalThis.Bun;
|
|
464
|
-
async
|
|
534
|
+
var getMainProjectNameCached = async () => {
|
|
465
535
|
if (Date.now() - cache.mainProjectName.timestamp < CACHE_TTL.mainProjectName) {
|
|
466
536
|
return cache.mainProjectName.value;
|
|
467
537
|
}
|
|
@@ -479,7 +549,7 @@ async function getMainProjectNameCached() {
|
|
|
479
549
|
} catch {
|
|
480
550
|
return cache.mainProjectName.value;
|
|
481
551
|
}
|
|
482
|
-
}
|
|
552
|
+
};
|
|
483
553
|
// src/colors.ts
|
|
484
554
|
var C = {
|
|
485
555
|
RESET: "\x1B[0m",
|
|
@@ -492,32 +562,28 @@ var C = {
|
|
|
492
562
|
WHITE: "\x1B[38;2;200;200;200m",
|
|
493
563
|
UNDERLINE: "\x1B[4m"
|
|
494
564
|
};
|
|
495
|
-
|
|
565
|
+
var getUsageColor = (pct) => {
|
|
496
566
|
if (pct < 50)
|
|
497
567
|
return C.WHITE;
|
|
498
568
|
if (pct < 80)
|
|
499
569
|
return C.YELLOW;
|
|
500
570
|
return C.RED;
|
|
501
|
-
}
|
|
571
|
+
};
|
|
502
572
|
|
|
503
573
|
// src/format/formatNumber.ts
|
|
504
|
-
|
|
505
|
-
return n.toLocaleString("en-US");
|
|
506
|
-
}
|
|
574
|
+
var formatNumber = (n) => n.toLocaleString("en-US");
|
|
507
575
|
// src/format/formatResetDate.ts
|
|
508
|
-
|
|
576
|
+
var formatResetDate = (date) => {
|
|
509
577
|
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
510
578
|
const day = date.getDate().toString().padStart(2, "0");
|
|
511
579
|
const hours = date.getHours().toString().padStart(2, "0");
|
|
512
580
|
const minutes = date.getMinutes().toString().padStart(2, "0");
|
|
513
581
|
return `${month}/${day} ${hours}:${minutes}`;
|
|
514
|
-
}
|
|
582
|
+
};
|
|
515
583
|
// src/format/formatTime.ts
|
|
516
|
-
|
|
517
|
-
return `${hours.toString().padStart(2, "0")}:${mins.toString().padStart(2, "0")}`;
|
|
518
|
-
}
|
|
584
|
+
var formatTime = (hours, mins) => `${hours.toString().padStart(2, "0")}:${mins.toString().padStart(2, "0")}`;
|
|
519
585
|
// src/render.ts
|
|
520
|
-
|
|
586
|
+
var renderStatusLine = (ctx) => {
|
|
521
587
|
const lines = [];
|
|
522
588
|
const folder = ctx.claudeJson.workspace?.project_dir?.split("/").pop() || "";
|
|
523
589
|
const sessionMs = ctx.claudeJson.cost?.total_duration_ms || 0;
|
|
@@ -544,6 +610,14 @@ function renderStatusLine(ctx) {
|
|
|
544
610
|
const ctxColor = getUsageColor(contextPct);
|
|
545
611
|
line2 += ` | ${ctxColor}\uD83E\uDDE0 ${formatNumber(totalTokens)} (${contextPct}%)${C.RESET}`;
|
|
546
612
|
}
|
|
613
|
+
const modelName = ctx.claudeJson.model?.display_name;
|
|
614
|
+
if (modelName) {
|
|
615
|
+
const effortLevel = ctx.claudeJson.effort?.level;
|
|
616
|
+
let modelText = effortLevel ? `${modelName} ${effortLevel}` : modelName;
|
|
617
|
+
if (ctx.ultracode && effortLevel === "xhigh")
|
|
618
|
+
modelText += " \u26A1ultra";
|
|
619
|
+
line2 += ` | ${C.WHITE}\uD83E\uDD16 ${modelText}${C.RESET}`;
|
|
620
|
+
}
|
|
547
621
|
lines.push(line2);
|
|
548
622
|
if (ctx.rateLimits) {
|
|
549
623
|
const parts = [];
|
|
@@ -593,27 +667,64 @@ function renderStatusLine(ctx) {
|
|
|
593
667
|
lines.push(line4);
|
|
594
668
|
}
|
|
595
669
|
return lines;
|
|
596
|
-
}
|
|
670
|
+
};
|
|
597
671
|
|
|
598
672
|
// src/stdin.ts
|
|
599
|
-
async
|
|
673
|
+
var readStdin = async () => {
|
|
600
674
|
const chunks = [];
|
|
601
675
|
for await (const chunk of Bun.stdin.stream()) {
|
|
602
676
|
chunks.push(chunk);
|
|
603
677
|
}
|
|
604
678
|
return Buffer.concat(chunks).toString("utf-8");
|
|
605
|
-
}
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
// src/ultracode.ts
|
|
682
|
+
import { readFile } from "fs/promises";
|
|
683
|
+
import { homedir as homedir2 } from "os";
|
|
684
|
+
import { join as join5 } from "path";
|
|
685
|
+
var MANAGED_SETTINGS_PATH = process.platform === "darwin" ? "/Library/Application Support/ClaudeCode/managed-settings.json" : process.platform === "win32" ? join5(process.env.ProgramData || "C:\\ProgramData", "ClaudeCode", "managed-settings.json") : "/etc/claude-code/managed-settings.json";
|
|
686
|
+
var ultracodeSettingsPaths = (projectDir, homeDir) => {
|
|
687
|
+
const paths = [MANAGED_SETTINGS_PATH];
|
|
688
|
+
if (projectDir) {
|
|
689
|
+
paths.push(join5(projectDir, ".claude", "settings.local.json"), join5(projectDir, ".claude", "settings.json"));
|
|
690
|
+
}
|
|
691
|
+
paths.push(join5(homeDir, ".claude", "settings.json"));
|
|
692
|
+
return paths;
|
|
693
|
+
};
|
|
694
|
+
var resolveUltracodeFromFiles = async (paths) => {
|
|
695
|
+
const contents = await Promise.all(paths.map((path) => readFile(path, "utf8").catch(() => null)));
|
|
696
|
+
for (const content of contents) {
|
|
697
|
+
if (content === null)
|
|
698
|
+
continue;
|
|
699
|
+
try {
|
|
700
|
+
const parsed = JSON.parse(content);
|
|
701
|
+
if (typeof parsed === "object" && parsed !== null && "ultracode" in parsed && typeof parsed.ultracode === "boolean") {
|
|
702
|
+
return parsed.ultracode;
|
|
703
|
+
}
|
|
704
|
+
} catch {}
|
|
705
|
+
}
|
|
706
|
+
return false;
|
|
707
|
+
};
|
|
708
|
+
var getUltracodeCached = async (projectDir, homeDir = homedir2()) => {
|
|
709
|
+
if (Date.now() - cache.ultracode.timestamp < CACHE_TTL.ultracode) {
|
|
710
|
+
return cache.ultracode.value;
|
|
711
|
+
}
|
|
712
|
+
const value = await resolveUltracodeFromFiles(ultracodeSettingsPaths(projectDir, homeDir));
|
|
713
|
+
cache.ultracode = { value, timestamp: Date.now() };
|
|
714
|
+
return value;
|
|
715
|
+
};
|
|
606
716
|
|
|
607
717
|
// src/index.ts
|
|
608
|
-
async
|
|
718
|
+
var main = async () => {
|
|
609
719
|
const claudeJson = JSON.parse(await readStdin() || "{}");
|
|
610
|
-
const
|
|
720
|
+
const repo = claudeJson.workspace?.project_dir || claudeJson.workspace?.current_dir || "";
|
|
721
|
+
const [branch, gitChanges, prUrl, mainProjectName, ultracode] = await Promise.all([
|
|
611
722
|
getBranchCached(),
|
|
612
723
|
getGitChangesCached(),
|
|
613
724
|
getPrUrlCached(),
|
|
614
|
-
getMainProjectNameCached()
|
|
725
|
+
getMainProjectNameCached(),
|
|
726
|
+
getUltracodeCached(repo)
|
|
615
727
|
]);
|
|
616
|
-
const repo = claudeJson.workspace?.project_dir || claudeJson.workspace?.current_dir || "";
|
|
617
728
|
const hasChanges = gitChanges.files > 0 || gitChanges.insertions > 0 || gitChanges.deletions > 0;
|
|
618
729
|
let diffViewerUrl = null;
|
|
619
730
|
let baseDiffViewerUrl = null;
|
|
@@ -622,7 +733,7 @@ async function main() {
|
|
|
622
733
|
baseChanges = await getBaseChangesCached();
|
|
623
734
|
}
|
|
624
735
|
if (repo && (hasChanges || baseChanges)) {
|
|
625
|
-
const ensured =
|
|
736
|
+
const ensured = ensureDiffServer(repo);
|
|
626
737
|
if (ensured) {
|
|
627
738
|
if (hasChanges) {
|
|
628
739
|
diffViewerUrl = buildDiffViewerUrl({
|
|
@@ -646,6 +757,7 @@ async function main() {
|
|
|
646
757
|
branch,
|
|
647
758
|
gitChanges,
|
|
648
759
|
prUrl,
|
|
760
|
+
ultracode,
|
|
649
761
|
rateLimits: claudeJson.rate_limits ?? null,
|
|
650
762
|
mainProjectName,
|
|
651
763
|
diffViewerUrl,
|
|
@@ -655,13 +767,13 @@ async function main() {
|
|
|
655
767
|
for (const line of lines) {
|
|
656
768
|
console.log(line);
|
|
657
769
|
}
|
|
658
|
-
}
|
|
770
|
+
};
|
|
659
771
|
if (import.meta.main) {
|
|
660
772
|
if (process.argv.includes("--diff-server")) {
|
|
661
773
|
const { startDiffServer: startDiffServer2 } = await Promise.resolve().then(() => (init_server(), exports_server));
|
|
662
774
|
startDiffServer2({
|
|
663
775
|
port: resolveDiffPort(),
|
|
664
|
-
viewerDir:
|
|
776
|
+
viewerDir: join6(import.meta.dir, "viewer")
|
|
665
777
|
});
|
|
666
778
|
} else {
|
|
667
779
|
main().catch(console.error);
|