@say8425/cc-statusline 3.2.0 → 4.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.
package/README.md CHANGED
@@ -56,7 +56,7 @@ Add the following to `~/.claude/settings.json`:
56
56
  - **Context**: Token usage with percentage (color-coded)
57
57
  - **Model**: Current model name and reasoning effort (e.g., `Fable 5 high`; effort shown only for models that support it), with an `⚡ultra` badge when ultracode is enabled in your Claude Code settings and the session reports `xhigh` effort
58
58
  - **Git Diff**: File count, insertions, deletions
59
- - **Clickable Diff Viewer**: Click `✏️` to open a local diff viewer (Pierre `@pierre/diffs` + `@pierre/trees`) in your browser — file tree, working-tree / vs-base modes, watch mode (auto-refresh), file folding, and in-app search (`Cmd/Ctrl+F`) across the full diff, including deleted lines
59
+ - **Clickable Diff Viewer**: Click `✏️` to open a local diff viewer in your browser, powered by [diffdeck](https://github.com/say8425/diffdeck) (installed automatically as a dependency) — file tree, working-tree / vs-base modes, watch mode (auto-refresh), file folding, and in-app search (`Cmd/Ctrl+F`) across the full diff, including deleted lines
60
60
  - **PR URL**: Clickable OSC 8 hyperlink
61
61
  - **Worktree Support**: Shows real project name when running in a `cc --worktree` session
62
62
  - **TrueColor**: Dynamic colors based on thresholds
@@ -85,7 +85,7 @@ Add the following to `~/.claude/settings.json`:
85
85
 
86
86
  ## Diff Viewer
87
87
 
88
- Click `✏️` in the statusline to open a local diff viewer in your browser, rendered with Pierre's [`@pierre/diffs`](https://www.npmjs.com/package/@pierre/diffs) components.
88
+ Click `✏️` in the statusline to open a local diff viewer in your browser. The viewer itself is provided by [diffdeck](https://github.com/say8425/diffdeck) ([`@say8425/diffdeck`](https://www.npmjs.com/package/@say8425/diffdeck) on npm), installed automatically as a runtime dependency of cc-statusline — the statusline spawns it as a background daemon and links to it from the `✏️` entry point.
89
89
 
90
90
  ![diff_viewer](docs/diff_viewer.png)
91
91
 
@@ -109,7 +109,7 @@ Click `✏️` in the statusline to open a local diff viewer in your browser, re
109
109
 
110
110
  ### How It Works (Diff Viewer)
111
111
 
112
- The statusline spawns the viewer server on demand at `127.0.0.1:49573` whenever the repo has something to show. Requests are token-protected and bound to localhost.
112
+ The statusline spawns diffdeck as a background daemon on demand at `127.0.0.1:49573` whenever the repo has something to show. Requests are token-protected and bound to localhost.
113
113
 
114
114
  | Environment variable | Effect |
115
115
  | -------------------- | ------ |
package/dist/index.js CHANGED
@@ -1,412 +1,170 @@
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
+
4
+ // src/diff-server/ensure.ts
5
+ import { mkdirSync, readFileSync as readFileSync2, rmSync, statSync } from "fs";
6
+ import { dirname, join as join3 } from "path";
7
+ import { fileURLToPath } from "url";
18
8
 
19
9
  // src/diff-server/config.ts
20
10
  import { homedir } from "os";
21
11
  import { join } from "path";
22
- var DEFAULT_DIFF_PORT = 49573, resolveDiffPort = (env = process.env) => {
12
+ var DEFAULT_DIFF_PORT = 49573;
13
+ var resolveDiffPort = (env = process.env) => {
23
14
  const raw = env.CC_STATUSLINE_DIFF_PORT;
24
15
  if (!raw)
25
16
  return DEFAULT_DIFF_PORT;
26
17
  const n = Number.parseInt(raw, 10);
27
18
  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) => {
19
+ };
20
+ var isDiffViewerDisabled = (env = process.env) => env.CC_STATUSLINE_DIFF_DISABLE === "1";
21
+ var getCacheDir = (env = process.env) => {
29
22
  const base = env.XDG_CACHE_HOME || join(env.HOME || homedir(), ".cache");
30
23
  return join(base, "cc-statusline");
31
24
  };
32
- var init_config = () => {};
25
+ var getDiffdeckCacheDir = (env = process.env) => {
26
+ const base = env.XDG_CACHE_HOME || join(env.HOME || homedir(), ".cache");
27
+ return join(base, "diffdeck");
28
+ };
33
29
 
34
30
  // src/diff-server/token.ts
35
- import { mkdirSync, readFileSync, writeFileSync } from "fs";
31
+ import { readFileSync } from "fs";
36
32
  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) => {
33
+ var getTokenPath = (env = process.env) => join2(getDiffdeckCacheDir(env), "diff-server.token");
34
+ var readTokenSync = (env = process.env) => {
38
35
  try {
39
36
  const value = readFileSync(getTokenPath(env), "utf8").trim();
40
37
  return value || null;
41
38
  } catch {
42
39
  return null;
43
40
  }
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";
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
41
  };
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
42
 
342
43
  // 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
44
  var ENSURE_TTL_MS = 5000;
348
45
  var LOCK_STALE_MS = 30000;
46
+ var PORT_FREE_TRIES = 20;
47
+ var PORT_FREE_INTERVAL_MS = 100;
349
48
  var checkedAt = 0;
350
- var probeOurServer = async (port) => {
49
+ var probeServer = async (port) => {
351
50
  try {
352
51
  const res = await fetch(`http://127.0.0.1:${port}/api/ping`, {
353
52
  signal: AbortSignal.timeout(150)
354
53
  });
355
- return res.headers.get("x-cc-statusline") === "1";
54
+ const ours = res.headers.get("x-diffdeck") === "1" || res.headers.get("x-cc-statusline") === "1";
55
+ if (!ours)
56
+ return null;
57
+ const version = res.headers.get("x-diffdeck-version");
58
+ const pidRaw = res.headers.get("x-diffdeck-pid");
59
+ const pid = pidRaw == null ? null : Number.parseInt(pidRaw, 10);
60
+ return { version, pid: Number.isInteger(pid) ? pid : null };
356
61
  } catch {
357
- return false;
62
+ return null;
63
+ }
64
+ };
65
+ var lsofPortOwner = (port) => {
66
+ try {
67
+ const out = Bun.spawnSync(["lsof", "-ti", `tcp:${port}`, "-sTCP:LISTEN"]);
68
+ if (out.exitCode !== 0)
69
+ return null;
70
+ const first = out.stdout.toString().trim().split(`
71
+ `)[0] ?? "";
72
+ const pid = Number.parseInt(first, 10);
73
+ return Number.isInteger(pid) ? pid : null;
74
+ } catch {
75
+ return null;
358
76
  }
359
77
  };
360
78
  var acquireSpawnLock = (env) => {
361
79
  const lock = join3(getCacheDir(env), "diff-server.lock");
362
- mkdirSync2(getCacheDir(env), { recursive: true, mode: 448 });
80
+ mkdirSync(getCacheDir(env), { recursive: true, mode: 448 });
363
81
  try {
364
- mkdirSync2(lock);
82
+ mkdirSync(lock);
365
83
  return true;
366
84
  } catch {
367
85
  try {
368
86
  if (Date.now() - statSync(lock).mtimeMs > LOCK_STALE_MS) {
369
87
  rmSync(lock, { recursive: true, force: true });
370
- mkdirSync2(lock);
88
+ mkdirSync(lock);
371
89
  return true;
372
90
  }
373
91
  } catch {}
374
92
  return false;
375
93
  }
376
94
  };
95
+ var resolveDiffdeck = () => {
96
+ const pkgDir = dirname(fileURLToPath(import.meta.resolve("@say8425/diffdeck/package.json")));
97
+ const pkg = JSON.parse(readFileSync2(join3(pkgDir, "package.json"), "utf8"));
98
+ return { cli: join3(pkgDir, pkg.bin.diffdeck), version: pkg.version };
99
+ };
377
100
  var spawnDaemon = (port, env) => {
378
- const execPath = process.execPath;
379
- const selfPath = Bun.main;
101
+ const { cli } = resolveDiffdeck();
380
102
  Bun.spawn([
381
103
  "sh",
382
104
  "-c",
383
- 'nohup "$0" "$1" --diff-server >/dev/null 2>&1 &',
384
- execPath,
385
- selfPath
105
+ 'nohup "$0" "$1" --no-open --port "$2" >/dev/null 2>&1 &',
106
+ process.execPath,
107
+ cli,
108
+ String(port)
386
109
  ], {
387
- env: { ...process.env, ...env, CC_STATUSLINE_DIFF_PORT: String(port) },
110
+ env: { ...process.env, ...env, DIFFDECK_PORT: String(port) },
388
111
  stdin: "ignore",
389
112
  stdout: "ignore",
390
113
  stderr: "ignore"
391
114
  }).unref();
392
115
  };
393
- var maybeSpawn = async (port, env, spawn = spawnDaemon) => {
116
+ var realDeps = {
117
+ spawn: spawnDaemon,
118
+ probe: probeServer,
119
+ portOwner: lsofPortOwner,
120
+ kill: (pid) => process.kill(pid, "SIGTERM"),
121
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
122
+ currentVersion: () => {
123
+ try {
124
+ return resolveDiffdeck().version;
125
+ } catch {
126
+ return null;
127
+ }
128
+ }
129
+ };
130
+ var waitPortFree = async (port, d) => {
131
+ for (let i = 0;i < PORT_FREE_TRIES; i++) {
132
+ await d.sleep(PORT_FREE_INTERVAL_MS);
133
+ if (await d.probe(port) == null)
134
+ return true;
135
+ }
136
+ return false;
137
+ };
138
+ var maybeSpawn = async (port, env, deps = {}) => {
139
+ const d = { ...realDeps, ...deps };
394
140
  try {
395
- if (await probeOurServer(port))
141
+ const found = await d.probe(port);
142
+ const current = d.currentVersion();
143
+ if (found && (current == null || found.version === current))
396
144
  return;
397
145
  if (!acquireSpawnLock(env))
398
146
  return;
399
- spawn(port, env);
147
+ if (found) {
148
+ const owner = d.portOwner(port);
149
+ if (owner == null)
150
+ return;
151
+ if (found.pid != null && found.pid !== owner)
152
+ return;
153
+ d.kill(owner);
154
+ if (!await waitPortFree(port, d))
155
+ return;
156
+ }
157
+ d.spawn(port, env);
400
158
  } catch {}
401
159
  };
402
- var ensureDiffServer = (_repo, env = process.env, spawn = spawnDaemon) => {
160
+ var ensureDiffServer = (_repo, env = process.env, deps = {}) => {
403
161
  if (isDiffViewerDisabled(env))
404
162
  return null;
405
163
  const port = resolveDiffPort(env);
406
164
  const now = Date.now();
407
165
  if (now - checkedAt >= ENSURE_TTL_MS) {
408
166
  checkedAt = now;
409
- maybeSpawn(port, env, spawn);
167
+ maybeSpawn(port, env, deps);
410
168
  }
411
169
  const token = readTokenSync(env);
412
170
  return token ? { port, token } : null;
@@ -447,8 +205,41 @@ var CACHE_TTL = {
447
205
  ultracode: 5000
448
206
  };
449
207
 
450
- // src/git/baseChanges.ts
451
- init_diff();
208
+ // src/git/baseRef.ts
209
+ var {$ } = globalThis.Bun;
210
+ var refExists = async (repo, ref) => {
211
+ const r = await $`git -C ${repo} rev-parse --verify --quiet ${ref}`.nothrow().quiet();
212
+ return r.exitCode === 0;
213
+ };
214
+ var prBaseName = async (repo) => {
215
+ try {
216
+ const out = await $`gh pr view --json baseRefName -q .baseRefName`.cwd(repo).nothrow().quiet().text();
217
+ return out.trim() || null;
218
+ } catch {
219
+ return null;
220
+ }
221
+ };
222
+ var defaultBranchName = async (repo) => {
223
+ const r = await $`git -C ${repo} rev-parse --abbrev-ref origin/HEAD 2>/dev/null`.nothrow().quiet();
224
+ if (r.exitCode !== 0)
225
+ return null;
226
+ const t = r.text().trim();
227
+ if (!t.startsWith("origin/"))
228
+ return null;
229
+ const name = t.slice("origin/".length);
230
+ return name && name !== "HEAD" ? name : null;
231
+ };
232
+ var resolveBaseRef = async (repo) => {
233
+ const named = await prBaseName(repo) ?? await defaultBranchName(repo);
234
+ const candidates = named ? [`origin/${named}`, named] : ["origin/main", "origin/master", "main", "master"];
235
+ for (const ref of candidates) {
236
+ if (await refExists(repo, ref)) {
237
+ const base = ref.startsWith("origin/") ? ref.slice("origin/".length) : ref;
238
+ return { base, ref };
239
+ }
240
+ }
241
+ return { base: named, ref: null };
242
+ };
452
243
 
453
244
  // src/git/shortstat.ts
454
245
  var parseShortstat = (text) => {
@@ -681,14 +472,14 @@ var readStdin = async () => {
681
472
  // src/ultracode.ts
682
473
  import { readFile } from "fs/promises";
683
474
  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";
475
+ import { join as join4 } from "path";
476
+ var MANAGED_SETTINGS_PATH = process.platform === "darwin" ? "/Library/Application Support/ClaudeCode/managed-settings.json" : process.platform === "win32" ? join4(process.env.ProgramData || "C:\\ProgramData", "ClaudeCode", "managed-settings.json") : "/etc/claude-code/managed-settings.json";
686
477
  var ultracodeSettingsPaths = (projectDir, homeDir) => {
687
478
  const paths = [MANAGED_SETTINGS_PATH];
688
479
  if (projectDir) {
689
- paths.push(join5(projectDir, ".claude", "settings.local.json"), join5(projectDir, ".claude", "settings.json"));
480
+ paths.push(join4(projectDir, ".claude", "settings.local.json"), join4(projectDir, ".claude", "settings.json"));
690
481
  }
691
- paths.push(join5(homeDir, ".claude", "settings.json"));
482
+ paths.push(join4(homeDir, ".claude", "settings.json"));
692
483
  return paths;
693
484
  };
694
485
  var resolveUltracodeFromFiles = async (paths) => {
@@ -769,15 +560,7 @@ var main = async () => {
769
560
  }
770
561
  };
771
562
  if (import.meta.main) {
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
- }
563
+ main().catch(console.error);
781
564
  }
782
565
  export {
783
566
  main
package/package.json CHANGED
@@ -1,27 +1,18 @@
1
1
  {
2
2
  "name": "@say8425/cc-statusline",
3
- "version": "3.2.0",
3
+ "version": "4.0.0",
4
+ "description": "Custom statusline for Claude Code",
4
5
  "repository": {
5
6
  "type": "git",
6
7
  "url": "https://github.com/say8425/cc-statusline.git"
7
8
  },
8
- "devDependencies": {
9
- "@pierre/diffs": "^1.2.12",
10
- "@pierre/trees": "^1.0.0-beta.5",
11
- "@types/bun": "^1.3.14",
12
- "oxfmt": "^0.57.0",
13
- "oxlint": "^1.72.0",
14
- "oxlint-tsgolint": "^0.24.0",
15
- "typescript": "^6.0.3"
16
- },
17
9
  "bin": {
18
10
  "cc-statusline": "dist/index.js"
19
11
  },
20
- "description": "Custom statusline for Claude Code",
21
12
  "files": [
22
- "dist/index.js",
23
- "dist/viewer"
13
+ "dist/index.js"
24
14
  ],
15
+ "type": "module",
25
16
  "publishConfig": {
26
17
  "registry": "https://registry.npmjs.org"
27
18
  },
@@ -35,5 +26,14 @@
35
26
  "format:check": "oxfmt --check src/ build.ts",
36
27
  "typecheck": "tsc --noEmit"
37
28
  },
38
- "type": "module"
29
+ "dependencies": {
30
+ "@say8425/diffdeck": "^0.2.2"
31
+ },
32
+ "devDependencies": {
33
+ "@types/bun": "^1.3.14",
34
+ "oxfmt": "^0.57.0",
35
+ "oxlint": "^1.72.0",
36
+ "oxlint-tsgolint": "^0.24.0",
37
+ "typescript": "^6.0.3"
38
+ }
39
39
  }