@say8425/cc-statusline 3.0.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/dist/index.js CHANGED
@@ -1,82 +1,542 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
+ var __defProp = Object.defineProperty;
4
+ var __returnValue = (v) => v;
5
+ function __exportSetter(name, newValue) {
6
+ this[name] = __returnValue.bind(null, newValue);
7
+ }
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, {
11
+ get: all[name],
12
+ enumerable: true,
13
+ configurable: true,
14
+ set: __exportSetter.bind(all, name)
15
+ });
16
+ };
17
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
3
18
 
4
- // src/git/branch.ts
19
+ // src/diff-server/config.ts
20
+ import { homedir } from "os";
21
+ import { join } from "path";
22
+ var DEFAULT_DIFF_PORT = 49573, resolveDiffPort = (env = process.env) => {
23
+ const raw = env.CC_STATUSLINE_DIFF_PORT;
24
+ if (!raw)
25
+ return DEFAULT_DIFF_PORT;
26
+ const n = Number.parseInt(raw, 10);
27
+ return Number.isInteger(n) && n > 0 && n < 65536 ? n : DEFAULT_DIFF_PORT;
28
+ }, isDiffViewerDisabled = (env = process.env) => env.CC_STATUSLINE_DIFF_DISABLE === "1", getCacheDir = (env = process.env) => {
29
+ const base = env.XDG_CACHE_HOME || join(env.HOME || homedir(), ".cache");
30
+ return join(base, "cc-statusline");
31
+ };
32
+ var init_config = () => {};
33
+
34
+ // src/diff-server/token.ts
35
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
36
+ import { join as join2 } from "path";
37
+ var getTokenPath = (env = process.env) => join2(getCacheDir(env), "diff-server.token"), generateToken = () => crypto.randomUUID().replaceAll("-", ""), readTokenSync = (env = process.env) => {
38
+ try {
39
+ const value = readFileSync(getTokenPath(env), "utf8").trim();
40
+ return value || null;
41
+ } catch {
42
+ return null;
43
+ }
44
+ }, ensureToken = (env = process.env) => {
45
+ const existing = readTokenSync(env);
46
+ if (existing)
47
+ return existing;
48
+ const token = generateToken();
49
+ mkdirSync(getCacheDir(env), { recursive: true, mode: 448 });
50
+ writeFileSync(getTokenPath(env), token, { mode: 384 });
51
+ return token;
52
+ };
53
+ var init_token = __esm(() => {
54
+ init_config();
55
+ });
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
+
73
+ // src/diff-server/diff.ts
74
+ import { readFileSync as readFileSync2 } from "fs";
75
+ import { isAbsolute, join as join4, resolve } from "path";
5
76
  var {$ } = globalThis.Bun;
77
+ var BUILD_CONCURRENCY = 8, isGitRepo = async (repo) => {
78
+ try {
79
+ const out = await $`git -C ${repo} rev-parse --is-inside-work-tree 2>/dev/null`.text();
80
+ return out.trim() === "true";
81
+ } catch {
82
+ return false;
83
+ }
84
+ }, refExists = async (repo, ref) => {
85
+ const r = await $`git -C ${repo} rev-parse --verify --quiet ${ref}`.nothrow().quiet();
86
+ return r.exitCode === 0;
87
+ }, prBaseName = async (repo) => {
88
+ try {
89
+ const out = await $`gh pr view --json baseRefName -q .baseRefName`.cwd(repo).nothrow().quiet().text();
90
+ return out.trim() || null;
91
+ } catch {
92
+ return null;
93
+ }
94
+ }, defaultBranchName = async (repo) => {
95
+ const r = await $`git -C ${repo} rev-parse --abbrev-ref origin/HEAD 2>/dev/null`.nothrow().quiet();
96
+ if (r.exitCode !== 0)
97
+ return null;
98
+ const t = r.text().trim();
99
+ if (!t.startsWith("origin/"))
100
+ return null;
101
+ const name = t.slice("origin/".length);
102
+ return name && name !== "HEAD" ? name : null;
103
+ }, resolveBaseRef = async (repo) => {
104
+ const named = await prBaseName(repo) ?? await defaultBranchName(repo);
105
+ const candidates = named ? [`origin/${named}`, named] : ["origin/main", "origin/master", "main", "master"];
106
+ for (const ref of candidates) {
107
+ if (await refExists(repo, ref)) {
108
+ const base = ref.startsWith("origin/") ? ref.slice("origin/".length) : ref;
109
+ return { base, ref };
110
+ }
111
+ }
112
+ return { base: named, ref: null };
113
+ }, showBytes = async (repo, rev, path) => {
114
+ const buf = await $`git -C ${repo} show ${`${rev}:${path}`} 2>/dev/null`.nothrow().arrayBuffer();
115
+ return new Uint8Array(buf);
116
+ }, readWorkingBytes = (repo, path) => {
117
+ try {
118
+ return new Uint8Array(readFileSync2(join4(repo, path)));
119
+ } catch {
120
+ return new Uint8Array;
121
+ }
122
+ }, buildFile = async (repo, base, status, name, oldName) => {
123
+ const oldBytes = status === "added" || status === "untracked" ? new Uint8Array : await showBytes(repo, base, oldName ?? name);
124
+ const newBytes = status === "deleted" ? new Uint8Array : readWorkingBytes(repo, name);
125
+ const binary = oldBytes.includes(0) || newBytes.includes(0);
126
+ const decoder = new TextDecoder;
127
+ return {
128
+ name,
129
+ ...oldName ? { oldName } : {},
130
+ status,
131
+ binary,
132
+ oldContents: binary ? "" : decoder.decode(oldBytes),
133
+ newContents: binary ? "" : decoder.decode(newBytes),
134
+ ...binary ? {
135
+ blobVersion: `${Bun.hash(oldBytes).toString(36)}.${Bun.hash(newBytes).toString(36)}`
136
+ } : {}
137
+ };
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);
155
+ const files = [];
156
+ if (base) {
157
+ const nameStatus = await $`git -C ${repo} diff --name-status ${base} 2>/dev/null`.nothrow().text();
158
+ const specs = [];
159
+ for (const line of nameStatus.split(`
160
+ `)) {
161
+ if (!line.trim())
162
+ continue;
163
+ const parts = line.split("\t");
164
+ const code = parts[0] ?? "";
165
+ if (code.startsWith("R")) {
166
+ specs.push({
167
+ status: "renamed",
168
+ name: parts[2] ?? "",
169
+ oldName: parts[1]
170
+ });
171
+ } else if (code.startsWith("A")) {
172
+ specs.push({ status: "added", name: parts[1] ?? "" });
173
+ } else if (code.startsWith("D")) {
174
+ specs.push({ status: "deleted", name: parts[1] ?? "" });
175
+ } else {
176
+ specs.push({ status: "modified", name: parts[1] ?? "" });
177
+ }
178
+ }
179
+ files.push(...await mapWithLimit(specs, BUILD_CONCURRENCY, (spec) => buildFile(repo, base, spec.status, spec.name, spec.oldName)));
180
+ }
181
+ if (opts.untracked) {
182
+ const listed = await $`git -C ${repo} ls-files --others --exclude-standard 2>/dev/null`.nothrow().text();
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)));
186
+ }
187
+ return files;
188
+ };
189
+ var init_diff = () => {};
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
+
217
+ // src/diff-server/server.ts
218
+ var exports_server = {};
219
+ __export(exports_server, {
220
+ startDiffServer: () => startDiffServer
221
+ });
222
+ import { resolve as resolve2 } from "path";
223
+ var BASE_TTL_MS = 1e4, baseCache, resolveBaseCached = async (repo) => {
224
+ const now = Date.now();
225
+ const hit = baseCache.get(repo);
226
+ if (hit && now - hit.at < BASE_TTL_MS)
227
+ return hit.value;
228
+ const value = await resolveBaseRef(repo);
229
+ baseCache.set(repo, { value, at: now });
230
+ return value;
231
+ }, createHandler = (cfg) => {
232
+ const viewerRoot = resolve2(cfg.viewerDir);
233
+ return async (req) => {
234
+ const url = new URL(req.url);
235
+ if (url.pathname === "/api/ping") {
236
+ return new Response(null, {
237
+ status: 204,
238
+ headers: { "x-cc-statusline": "1" }
239
+ });
240
+ }
241
+ if (url.pathname === "/api/diff") {
242
+ if (url.searchParams.get("token") !== cfg.token) {
243
+ return new Response("forbidden", { status: 403 });
244
+ }
245
+ const repo = url.searchParams.get("repo") ?? "";
246
+ if (!repo || !await isGitRepo(repo)) {
247
+ return new Response("not a git repository", { status: 400 });
248
+ }
249
+ const untracked = url.searchParams.get("untracked") === "1";
250
+ const mode = url.searchParams.get("mode") === "base" ? "base" : "working";
251
+ const { base, ref } = await resolveBaseCached(repo);
252
+ const files = mode === "base" ? await getDiffFiles(repo, {
253
+ untracked,
254
+ mode: "base",
255
+ ref: ref ?? undefined
256
+ }) : await getDiffFiles(repo, { untracked });
257
+ return new Response(JSON.stringify(files), {
258
+ headers: {
259
+ "content-type": "application/json; charset=utf-8",
260
+ "x-diff-base": base ?? ""
261
+ }
262
+ });
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
+ }
289
+ const rel = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
290
+ const filePath = resolve2(viewerRoot, rel);
291
+ if (filePath !== viewerRoot && !filePath.startsWith(`${viewerRoot}/`)) {
292
+ return new Response("forbidden", { status: 403 });
293
+ }
294
+ const file = Bun.file(filePath);
295
+ if (await file.exists()) {
296
+ return new Response(file, { headers: { "cache-control": "no-store" } });
297
+ }
298
+ return new Response("not found", { status: 404 });
299
+ };
300
+ }, startDiffServer = (opts) => {
301
+ const env = opts.env ?? process.env;
302
+ const token = ensureToken(env);
303
+ let lastActivity = Date.now();
304
+ const handler = createHandler({ viewerDir: opts.viewerDir, token });
305
+ const server = Bun.serve({
306
+ hostname: "127.0.0.1",
307
+ port: opts.port,
308
+ fetch: (req) => {
309
+ lastActivity = Date.now();
310
+ return handler(req);
311
+ }
312
+ });
313
+ let idleTimer;
314
+ const idleTimeoutMs = opts.idleTimeoutMs;
315
+ if (idleTimeoutMs && idleTimeoutMs > 0) {
316
+ idleTimer = setInterval(() => {
317
+ if (Date.now() - lastActivity > idleTimeoutMs) {
318
+ stop();
319
+ process.exit(0);
320
+ }
321
+ }, 60000);
322
+ idleTimer.unref?.();
323
+ }
324
+ const stop = () => {
325
+ if (idleTimer)
326
+ clearInterval(idleTimer);
327
+ server.stop(true);
328
+ };
329
+ return { server, token, stop };
330
+ };
331
+ var init_server = __esm(() => {
332
+ init_diff();
333
+ init_imageTypes();
334
+ init_token();
335
+ baseCache = new Map;
336
+ });
337
+
338
+ // src/index.ts
339
+ init_config();
340
+ import { join as join6 } from "path";
341
+
342
+ // src/diff-server/ensure.ts
343
+ init_config();
344
+ init_token();
345
+ import { mkdirSync as mkdirSync2, rmSync, statSync } from "fs";
346
+ import { join as join3 } from "path";
347
+ var ENSURE_TTL_MS = 5000;
348
+ var LOCK_STALE_MS = 30000;
349
+ var checkedAt = 0;
350
+ var probeOurServer = async (port) => {
351
+ try {
352
+ const res = await fetch(`http://127.0.0.1:${port}/api/ping`, {
353
+ signal: AbortSignal.timeout(150)
354
+ });
355
+ return res.headers.get("x-cc-statusline") === "1";
356
+ } catch {
357
+ return false;
358
+ }
359
+ };
360
+ var acquireSpawnLock = (env) => {
361
+ const lock = join3(getCacheDir(env), "diff-server.lock");
362
+ mkdirSync2(getCacheDir(env), { recursive: true, mode: 448 });
363
+ try {
364
+ mkdirSync2(lock);
365
+ return true;
366
+ } catch {
367
+ try {
368
+ if (Date.now() - statSync(lock).mtimeMs > LOCK_STALE_MS) {
369
+ rmSync(lock, { recursive: true, force: true });
370
+ mkdirSync2(lock);
371
+ return true;
372
+ }
373
+ } catch {}
374
+ return false;
375
+ }
376
+ };
377
+ var spawnDaemon = (port, env) => {
378
+ const execPath = process.execPath;
379
+ const selfPath = Bun.main;
380
+ Bun.spawn([
381
+ "sh",
382
+ "-c",
383
+ 'nohup "$0" "$1" --diff-server >/dev/null 2>&1 &',
384
+ execPath,
385
+ selfPath
386
+ ], {
387
+ env: { ...process.env, ...env, CC_STATUSLINE_DIFF_PORT: String(port) },
388
+ stdin: "ignore",
389
+ stdout: "ignore",
390
+ stderr: "ignore"
391
+ }).unref();
392
+ };
393
+ var maybeSpawn = async (port, env, spawn = spawnDaemon) => {
394
+ try {
395
+ if (await probeOurServer(port))
396
+ return;
397
+ if (!acquireSpawnLock(env))
398
+ return;
399
+ spawn(port, env);
400
+ } catch {}
401
+ };
402
+ var ensureDiffServer = (_repo, env = process.env, spawn = spawnDaemon) => {
403
+ if (isDiffViewerDisabled(env))
404
+ return null;
405
+ const port = resolveDiffPort(env);
406
+ const now = Date.now();
407
+ if (now - checkedAt >= ENSURE_TTL_MS) {
408
+ checkedAt = now;
409
+ maybeSpawn(port, env, spawn);
410
+ }
411
+ const token = readTokenSync(env);
412
+ return token ? { port, token } : null;
413
+ };
414
+
415
+ // src/diff-server/link.ts
416
+ var buildDiffViewerUrl = (params) => {
417
+ const query = new URLSearchParams({
418
+ repo: params.repo,
419
+ token: params.token
420
+ });
421
+ if (params.mode)
422
+ query.set("mode", params.mode);
423
+ return `http://127.0.0.1:${params.port}/?${query.toString()}`;
424
+ };
425
+
426
+ // src/git/baseChanges.ts
427
+ var {$: $2 } = globalThis.Bun;
6
428
 
7
429
  // src/cache.ts
8
430
  var cache = {
9
431
  branch: { value: "", timestamp: 0 },
10
432
  gitChanges: { files: 0, insertions: 0, deletions: 0, timestamp: 0 },
11
433
  prUrl: { value: null, timestamp: 0 },
12
- mainProjectName: { value: null, timestamp: 0 }
434
+ mainProjectName: { value: null, timestamp: 0 },
435
+ baseRef: {
436
+ value: null,
437
+ timestamp: 0
438
+ },
439
+ ultracode: { value: false, timestamp: 0 }
13
440
  };
14
441
  var CACHE_TTL = {
15
442
  branch: 5000,
16
443
  gitChanges: 0,
17
444
  prUrl: 30000,
18
- mainProjectName: 300000
445
+ mainProjectName: 300000,
446
+ baseRef: 30000,
447
+ ultracode: 5000
448
+ };
449
+
450
+ // src/git/baseChanges.ts
451
+ init_diff();
452
+
453
+ // src/git/shortstat.ts
454
+ var parseShortstat = (text) => {
455
+ const [files, insertions, deletions] = [
456
+ /(\d+) files?/g,
457
+ /(\d+) insertions?/g,
458
+ /(\d+) deletions?/g
459
+ ].map((regex) => (text.match(regex) || []).reduce((sum, m) => sum + Number.parseInt(m, 10), 0));
460
+ return { files, insertions, deletions };
19
461
  };
20
462
 
463
+ // src/git/baseChanges.ts
464
+ var getBaseChangesCached = async () => {
465
+ try {
466
+ let resolved = cache.baseRef.value;
467
+ if (resolved === null || Date.now() - cache.baseRef.timestamp >= CACHE_TTL.baseRef) {
468
+ resolved = await resolveBaseRef(".");
469
+ cache.baseRef = { value: resolved, timestamp: Date.now() };
470
+ }
471
+ if (!resolved.ref || !resolved.base)
472
+ return null;
473
+ const mb = (await $2`git merge-base ${resolved.ref} HEAD 2>/dev/null`.nothrow().text()).trim();
474
+ if (!mb)
475
+ return null;
476
+ const out = await $2`git diff ${mb} --shortstat 2>/dev/null`.nothrow().text();
477
+ const { files, insertions, deletions } = parseShortstat(out);
478
+ if (files === 0 && insertions === 0 && deletions === 0)
479
+ return null;
480
+ return { base: resolved.base, files, insertions, deletions };
481
+ } catch {
482
+ return null;
483
+ }
484
+ };
21
485
  // src/git/branch.ts
22
- async function getBranchCached() {
486
+ var {$: $3 } = globalThis.Bun;
487
+ var getBranchCached = async () => {
23
488
  if (Date.now() - cache.branch.timestamp < CACHE_TTL.branch) {
24
489
  return cache.branch.value;
25
490
  }
26
491
  try {
27
- const result = await $`git branch --show-current 2>/dev/null`.text();
492
+ const result = await $3`git branch --show-current 2>/dev/null`.text();
28
493
  cache.branch = { value: result.trim(), timestamp: Date.now() };
29
494
  return cache.branch.value;
30
495
  } catch {
31
496
  return cache.branch.value;
32
497
  }
33
- }
498
+ };
34
499
  // src/git/changes.ts
35
- var {$: $2 } = globalThis.Bun;
36
- async function getGitChangesCached() {
500
+ var {$: $4 } = globalThis.Bun;
501
+ var getGitChangesCached = async () => {
37
502
  if (Date.now() - cache.gitChanges.timestamp < CACHE_TTL.gitChanges) {
38
503
  return cache.gitChanges;
39
504
  }
40
505
  try {
41
506
  const [diff, staged] = await Promise.all([
42
- $2`git diff --shortstat 2>/dev/null`.text(),
43
- $2`git diff --cached --shortstat 2>/dev/null`.text()
507
+ $4`git diff --shortstat 2>/dev/null`.text(),
508
+ $4`git diff --cached --shortstat 2>/dev/null`.text()
44
509
  ]);
45
- const combined = `${diff}
46
- ${staged}`;
47
- const [files, insertions, deletions] = [
48
- /(\d+) files?/g,
49
- /(\d+) insertions?/g,
50
- /(\d+) deletions?/g
51
- ].map((regex) => (combined.match(regex) || []).reduce((sum, m) => sum + Number.parseInt(m, 10), 0));
510
+ const { files, insertions, deletions } = parseShortstat(`${diff}
511
+ ${staged}`);
52
512
  cache.gitChanges = { files, insertions, deletions, timestamp: Date.now() };
53
513
  return cache.gitChanges;
54
514
  } catch {
55
515
  return cache.gitChanges;
56
516
  }
57
- }
517
+ };
58
518
  // src/git/pr.ts
59
- var {$: $3 } = globalThis.Bun;
60
- async function getPrUrlCached() {
519
+ var {$: $5 } = globalThis.Bun;
520
+ var getPrUrlCached = async () => {
61
521
  if (Date.now() - cache.prUrl.timestamp < CACHE_TTL.prUrl) {
62
522
  return cache.prUrl.value;
63
523
  }
64
524
  try {
65
- const result = await $3`gh pr view --json url -q .url 2>/dev/null`.text();
525
+ const result = await $5`gh pr view --json url -q .url 2>/dev/null`.text();
66
526
  cache.prUrl = { value: result.trim() || null, timestamp: Date.now() };
67
527
  return cache.prUrl.value;
68
528
  } catch {
69
529
  return cache.prUrl.value;
70
530
  }
71
- }
531
+ };
72
532
  // src/git/worktree.ts
73
- var {$: $4 } = globalThis.Bun;
74
- async function getMainProjectNameCached() {
533
+ var {$: $6 } = globalThis.Bun;
534
+ var getMainProjectNameCached = async () => {
75
535
  if (Date.now() - cache.mainProjectName.timestamp < CACHE_TTL.mainProjectName) {
76
536
  return cache.mainProjectName.value;
77
537
  }
78
538
  try {
79
- const result = await $4`git rev-parse --git-common-dir 2>/dev/null`.text();
539
+ const result = await $6`git rev-parse --git-common-dir 2>/dev/null`.text();
80
540
  const gitCommonDir = result.trim();
81
541
  if (gitCommonDir === ".git") {
82
542
  cache.mainProjectName = { value: null, timestamp: Date.now() };
@@ -89,7 +549,7 @@ async function getMainProjectNameCached() {
89
549
  } catch {
90
550
  return cache.mainProjectName.value;
91
551
  }
92
- }
552
+ };
93
553
  // src/colors.ts
94
554
  var C = {
95
555
  RESET: "\x1B[0m",
@@ -102,32 +562,28 @@ var C = {
102
562
  WHITE: "\x1B[38;2;200;200;200m",
103
563
  UNDERLINE: "\x1B[4m"
104
564
  };
105
- function getUsageColor(pct) {
565
+ var getUsageColor = (pct) => {
106
566
  if (pct < 50)
107
567
  return C.WHITE;
108
568
  if (pct < 80)
109
569
  return C.YELLOW;
110
570
  return C.RED;
111
- }
571
+ };
112
572
 
113
573
  // src/format/formatNumber.ts
114
- function formatNumber(n) {
115
- return n.toLocaleString("en-US");
116
- }
574
+ var formatNumber = (n) => n.toLocaleString("en-US");
117
575
  // src/format/formatResetDate.ts
118
- function formatResetDate(date) {
576
+ var formatResetDate = (date) => {
119
577
  const month = (date.getMonth() + 1).toString().padStart(2, "0");
120
578
  const day = date.getDate().toString().padStart(2, "0");
121
579
  const hours = date.getHours().toString().padStart(2, "0");
122
580
  const minutes = date.getMinutes().toString().padStart(2, "0");
123
581
  return `${month}/${day} ${hours}:${minutes}`;
124
- }
582
+ };
125
583
  // src/format/formatTime.ts
126
- function formatTime(hours, mins) {
127
- return `${hours.toString().padStart(2, "0")}:${mins.toString().padStart(2, "0")}`;
128
- }
584
+ var formatTime = (hours, mins) => `${hours.toString().padStart(2, "0")}:${mins.toString().padStart(2, "0")}`;
129
585
  // src/render.ts
130
- function renderStatusLine(ctx) {
586
+ var renderStatusLine = (ctx) => {
131
587
  const lines = [];
132
588
  const folder = ctx.claudeJson.workspace?.project_dir?.split("/").pop() || "";
133
589
  const sessionMs = ctx.claudeJson.cost?.total_duration_ms || 0;
@@ -154,6 +610,14 @@ function renderStatusLine(ctx) {
154
610
  const ctxColor = getUsageColor(contextPct);
155
611
  line2 += ` | ${ctxColor}\uD83E\uDDE0 ${formatNumber(totalTokens)} (${contextPct}%)${C.RESET}`;
156
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
+ }
157
621
  lines.push(line2);
158
622
  if (ctx.rateLimits) {
159
623
  const parts = [];
@@ -182,10 +646,17 @@ function renderStatusLine(ctx) {
182
646
  }
183
647
  }
184
648
  const hasGitChanges = ctx.gitChanges.files > 0 || ctx.gitChanges.insertions > 0 || ctx.gitChanges.deletions > 0;
185
- if (hasGitChanges || ctx.prUrl) {
649
+ if (hasGitChanges || ctx.baseChanges || ctx.prUrl) {
186
650
  let line4 = "";
187
651
  if (hasGitChanges) {
188
- line4 += `\u270F\uFE0F ${C.WHITE}${ctx.gitChanges.files} files${C.RESET} ${C.GREEN}+${ctx.gitChanges.insertions}${C.RESET} ${C.RED}-${ctx.gitChanges.deletions}${C.RESET}`;
652
+ const u = ctx.diffViewerUrl ? C.UNDERLINE : "";
653
+ const changesText = `\u270F\uFE0F ${u}${C.WHITE}${ctx.gitChanges.files} files${C.RESET}` + `${u} ${C.GREEN}+${ctx.gitChanges.insertions}${C.RESET}` + `${u} ${C.RED}-${ctx.gitChanges.deletions}${C.RESET}`;
654
+ line4 += ctx.diffViewerUrl ? `\x1B]8;;${ctx.diffViewerUrl}\x07${changesText}\x1B]8;;\x07` : changesText;
655
+ } else if (ctx.baseChanges) {
656
+ const bc = ctx.baseChanges;
657
+ const u = ctx.baseDiffViewerUrl ? C.UNDERLINE : "";
658
+ const baseText = `\u270F\uFE0F ${u}vs ${bc.base} ${C.WHITE}${bc.files} files${C.RESET}` + `${u} ${C.GREEN}+${bc.insertions}${C.RESET}` + `${u} ${C.RED}-${bc.deletions}${C.RESET}`;
659
+ line4 += ctx.baseDiffViewerUrl ? `\x1B]8;;${ctx.baseDiffViewerUrl}\x07${baseText}\x1B]8;;\x07` : baseText;
189
660
  }
190
661
  if (ctx.prUrl) {
191
662
  const prLabel = ctx.prUrl.replace(/^https?:\/\/[^/]+\//, "").replace("/pull/", "#");
@@ -196,40 +667,117 @@ function renderStatusLine(ctx) {
196
667
  lines.push(line4);
197
668
  }
198
669
  return lines;
199
- }
670
+ };
200
671
 
201
672
  // src/stdin.ts
202
- async function readStdin() {
673
+ var readStdin = async () => {
203
674
  const chunks = [];
204
675
  for await (const chunk of Bun.stdin.stream()) {
205
676
  chunks.push(chunk);
206
677
  }
207
678
  return Buffer.concat(chunks).toString("utf-8");
208
- }
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
+ };
209
716
 
210
717
  // src/index.ts
211
- async function main() {
718
+ var main = async () => {
212
719
  const claudeJson = JSON.parse(await readStdin() || "{}");
213
- const [branch, gitChanges, prUrl, mainProjectName] = await Promise.all([
720
+ const repo = claudeJson.workspace?.project_dir || claudeJson.workspace?.current_dir || "";
721
+ const [branch, gitChanges, prUrl, mainProjectName, ultracode] = await Promise.all([
214
722
  getBranchCached(),
215
723
  getGitChangesCached(),
216
724
  getPrUrlCached(),
217
- getMainProjectNameCached()
725
+ getMainProjectNameCached(),
726
+ getUltracodeCached(repo)
218
727
  ]);
728
+ const hasChanges = gitChanges.files > 0 || gitChanges.insertions > 0 || gitChanges.deletions > 0;
729
+ let diffViewerUrl = null;
730
+ let baseDiffViewerUrl = null;
731
+ let baseChanges = null;
732
+ if (!hasChanges && repo) {
733
+ baseChanges = await getBaseChangesCached();
734
+ }
735
+ if (repo && (hasChanges || baseChanges)) {
736
+ const ensured = ensureDiffServer(repo);
737
+ if (ensured) {
738
+ if (hasChanges) {
739
+ diffViewerUrl = buildDiffViewerUrl({
740
+ port: ensured.port,
741
+ repo,
742
+ token: ensured.token,
743
+ mode: "working"
744
+ });
745
+ } else if (baseChanges) {
746
+ baseDiffViewerUrl = buildDiffViewerUrl({
747
+ port: ensured.port,
748
+ repo,
749
+ token: ensured.token,
750
+ mode: "base"
751
+ });
752
+ }
753
+ }
754
+ }
219
755
  const lines = renderStatusLine({
220
756
  claudeJson,
221
757
  branch,
222
758
  gitChanges,
223
759
  prUrl,
760
+ ultracode,
224
761
  rateLimits: claudeJson.rate_limits ?? null,
225
- mainProjectName
762
+ mainProjectName,
763
+ diffViewerUrl,
764
+ baseChanges,
765
+ baseDiffViewerUrl
226
766
  });
227
767
  for (const line of lines) {
228
768
  console.log(line);
229
769
  }
230
- }
770
+ };
231
771
  if (import.meta.main) {
232
- main().catch(console.error);
772
+ if (process.argv.includes("--diff-server")) {
773
+ const { startDiffServer: startDiffServer2 } = await Promise.resolve().then(() => (init_server(), exports_server));
774
+ startDiffServer2({
775
+ port: resolveDiffPort(),
776
+ viewerDir: join6(import.meta.dir, "viewer")
777
+ });
778
+ } else {
779
+ main().catch(console.error);
780
+ }
233
781
  }
234
782
  export {
235
783
  main