@say8425/cc-statusline 3.2.0 → 4.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/README.md +7 -7
- package/dist/index.js +329 -414
- package/package.json +14 -14
- package/dist/viewer/index.html +0 -490
- package/dist/viewer/main.js +0 -2842
package/dist/index.js
CHANGED
|
@@ -1,412 +1,170 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
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
|
-
}
|
|
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
|
|
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 {
|
|
31
|
+
import { readFileSync } from "fs";
|
|
36
32
|
import { join as join2 } from "path";
|
|
37
|
-
var getTokenPath = (env = process.env) => join2(
|
|
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
41
|
};
|
|
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
|
-
};
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
80
|
+
mkdirSync(getCacheDir(env), { recursive: true, mode: 448 });
|
|
363
81
|
try {
|
|
364
|
-
|
|
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
|
-
|
|
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
|
|
379
|
-
const selfPath = Bun.main;
|
|
101
|
+
const { cli } = resolveDiffdeck();
|
|
380
102
|
Bun.spawn([
|
|
381
103
|
"sh",
|
|
382
104
|
"-c",
|
|
383
|
-
'nohup "$0" "$1" --
|
|
384
|
-
execPath,
|
|
385
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
167
|
+
maybeSpawn(port, env, deps);
|
|
410
168
|
}
|
|
411
169
|
const token = readTokenSync(env);
|
|
412
170
|
return token ? { port, token } : null;
|
|
@@ -423,6 +181,96 @@ var buildDiffViewerUrl = (params) => {
|
|
|
423
181
|
return `http://127.0.0.1:${params.port}/?${query.toString()}`;
|
|
424
182
|
};
|
|
425
183
|
|
|
184
|
+
// src/format/formatNumber.ts
|
|
185
|
+
var formatNumber = (n) => n.toLocaleString("en-US");
|
|
186
|
+
// src/format/formatResetDate.ts
|
|
187
|
+
var formatResetDate = (date) => {
|
|
188
|
+
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
189
|
+
const day = date.getDate().toString().padStart(2, "0");
|
|
190
|
+
const hours = date.getHours().toString().padStart(2, "0");
|
|
191
|
+
const minutes = date.getMinutes().toString().padStart(2, "0");
|
|
192
|
+
return `${month}/${day} ${hours}:${minutes}`;
|
|
193
|
+
};
|
|
194
|
+
// src/format/formatTime.ts
|
|
195
|
+
var formatTime = (hours, mins) => `${hours.toString().padStart(2, "0")}:${mins.toString().padStart(2, "0")}`;
|
|
196
|
+
// src/colors.ts
|
|
197
|
+
var C = {
|
|
198
|
+
RESET: "\x1B[0m",
|
|
199
|
+
CYAN: "\x1B[38;2;0;255;255m",
|
|
200
|
+
MAGENTA: "\x1B[38;2;255;100;200m",
|
|
201
|
+
GREEN: "\x1B[38;2;100;255;100m",
|
|
202
|
+
YELLOW: "\x1B[38;2;255;220;100m",
|
|
203
|
+
RED: "\x1B[38;2;255;100;100m",
|
|
204
|
+
BLUE: "\x1B[38;2;100;150;255m",
|
|
205
|
+
WHITE: "\x1B[38;2;200;200;200m",
|
|
206
|
+
GREEN_MUTED: "\x1B[38;2;140;195;140m",
|
|
207
|
+
YELLOW_MUTED: "\x1B[38;2;210;185;130m",
|
|
208
|
+
RED_MUTED: "\x1B[38;2;210;130;130m",
|
|
209
|
+
MAGENTA_MUTED: "\x1B[38;2;195;140;175m",
|
|
210
|
+
UNDERLINE: "\x1B[4m"
|
|
211
|
+
};
|
|
212
|
+
var getUsageColor = (pct) => {
|
|
213
|
+
if (pct < 50)
|
|
214
|
+
return C.WHITE;
|
|
215
|
+
if (pct < 80)
|
|
216
|
+
return C.YELLOW;
|
|
217
|
+
return C.RED;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// src/format/prStatus.ts
|
|
221
|
+
var prStateText = (state, isDraft) => {
|
|
222
|
+
if (isDraft)
|
|
223
|
+
return "Draft";
|
|
224
|
+
if (state === "OPEN")
|
|
225
|
+
return "Open";
|
|
226
|
+
if (state === "MERGED")
|
|
227
|
+
return "Merged";
|
|
228
|
+
return "Closed";
|
|
229
|
+
};
|
|
230
|
+
var prStateColor = (state, isDraft) => {
|
|
231
|
+
if (isDraft)
|
|
232
|
+
return C.WHITE;
|
|
233
|
+
if (state === "OPEN")
|
|
234
|
+
return C.GREEN_MUTED;
|
|
235
|
+
if (state === "MERGED")
|
|
236
|
+
return C.MAGENTA_MUTED;
|
|
237
|
+
return C.RED_MUTED;
|
|
238
|
+
};
|
|
239
|
+
var ciSummaryText = (ci) => {
|
|
240
|
+
if (ci === null)
|
|
241
|
+
return "";
|
|
242
|
+
if (ci.conclusion === "failure")
|
|
243
|
+
return `${ci.count} failed`;
|
|
244
|
+
if (ci.conclusion === "pending")
|
|
245
|
+
return `${ci.count} running`;
|
|
246
|
+
return `${ci.count} passed`;
|
|
247
|
+
};
|
|
248
|
+
var ciSummaryColor = (ci) => {
|
|
249
|
+
if (ci === null)
|
|
250
|
+
return "";
|
|
251
|
+
if (ci.conclusion === "failure")
|
|
252
|
+
return C.RED_MUTED;
|
|
253
|
+
if (ci.conclusion === "pending")
|
|
254
|
+
return C.YELLOW_MUTED;
|
|
255
|
+
return C.GREEN_MUTED;
|
|
256
|
+
};
|
|
257
|
+
// src/format/toFileUrl.ts
|
|
258
|
+
var toFileUrl = (absolutePath) => {
|
|
259
|
+
const normalized = absolutePath.replace(/\\/g, "/");
|
|
260
|
+
const withLeadingSlash = normalized.startsWith("/") ? normalized : `/${normalized}`;
|
|
261
|
+
try {
|
|
262
|
+
return `file://${encodeURI(withLeadingSlash)}`;
|
|
263
|
+
} catch {
|
|
264
|
+
const safe = Array.from(withLeadingSlash).map((ch) => {
|
|
265
|
+
try {
|
|
266
|
+
return encodeURI(ch);
|
|
267
|
+
} catch {
|
|
268
|
+
return "%EF%BF%BD";
|
|
269
|
+
}
|
|
270
|
+
}).join("");
|
|
271
|
+
return `file://${safe}`;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
426
274
|
// src/git/baseChanges.ts
|
|
427
275
|
var {$: $2 } = globalThis.Bun;
|
|
428
276
|
|
|
@@ -430,8 +278,11 @@ var {$: $2 } = globalThis.Bun;
|
|
|
430
278
|
var cache = {
|
|
431
279
|
branch: { value: "", timestamp: 0 },
|
|
432
280
|
gitChanges: { files: 0, insertions: 0, deletions: 0, timestamp: 0 },
|
|
433
|
-
|
|
434
|
-
|
|
281
|
+
prInfo: { value: null, timestamp: 0 },
|
|
282
|
+
mainProject: {
|
|
283
|
+
value: null,
|
|
284
|
+
timestamp: 0
|
|
285
|
+
},
|
|
435
286
|
baseRef: {
|
|
436
287
|
value: null,
|
|
437
288
|
timestamp: 0
|
|
@@ -441,14 +292,47 @@ var cache = {
|
|
|
441
292
|
var CACHE_TTL = {
|
|
442
293
|
branch: 5000,
|
|
443
294
|
gitChanges: 0,
|
|
444
|
-
|
|
445
|
-
|
|
295
|
+
prInfo: 30000,
|
|
296
|
+
mainProject: 300000,
|
|
446
297
|
baseRef: 30000,
|
|
447
298
|
ultracode: 5000
|
|
448
299
|
};
|
|
449
300
|
|
|
450
|
-
// src/git/
|
|
451
|
-
|
|
301
|
+
// src/git/baseRef.ts
|
|
302
|
+
var {$ } = globalThis.Bun;
|
|
303
|
+
var refExists = async (repo, ref) => {
|
|
304
|
+
const r = await $`git -C ${repo} rev-parse --verify --quiet ${ref}`.nothrow().quiet();
|
|
305
|
+
return r.exitCode === 0;
|
|
306
|
+
};
|
|
307
|
+
var prBaseName = async (repo) => {
|
|
308
|
+
try {
|
|
309
|
+
const out = await $`gh pr view --json baseRefName -q .baseRefName`.cwd(repo).nothrow().quiet().text();
|
|
310
|
+
return out.trim() || null;
|
|
311
|
+
} catch {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
var defaultBranchName = async (repo) => {
|
|
316
|
+
const r = await $`git -C ${repo} rev-parse --abbrev-ref origin/HEAD 2>/dev/null`.nothrow().quiet();
|
|
317
|
+
if (r.exitCode !== 0)
|
|
318
|
+
return null;
|
|
319
|
+
const t = r.text().trim();
|
|
320
|
+
if (!t.startsWith("origin/"))
|
|
321
|
+
return null;
|
|
322
|
+
const name = t.slice("origin/".length);
|
|
323
|
+
return name && name !== "HEAD" ? name : null;
|
|
324
|
+
};
|
|
325
|
+
var resolveBaseRef = async (repo) => {
|
|
326
|
+
const named = await prBaseName(repo) ?? await defaultBranchName(repo);
|
|
327
|
+
const candidates = named ? [`origin/${named}`, named] : ["origin/main", "origin/master", "main", "master"];
|
|
328
|
+
for (const ref of candidates) {
|
|
329
|
+
if (await refExists(repo, ref)) {
|
|
330
|
+
const base = ref.startsWith("origin/") ? ref.slice("origin/".length) : ref;
|
|
331
|
+
return { base, ref };
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return { base: named, ref: null };
|
|
335
|
+
};
|
|
452
336
|
|
|
453
337
|
// src/git/shortstat.ts
|
|
454
338
|
var parseShortstat = (text) => {
|
|
@@ -517,71 +401,91 @@ ${staged}`);
|
|
|
517
401
|
};
|
|
518
402
|
// src/git/pr.ts
|
|
519
403
|
var {$: $5 } = globalThis.Bun;
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
404
|
+
|
|
405
|
+
// src/git/ciStatus.ts
|
|
406
|
+
var FAILURE_CONCLUSIONS = new Set([
|
|
407
|
+
"FAILURE",
|
|
408
|
+
"TIMED_OUT",
|
|
409
|
+
"CANCELLED",
|
|
410
|
+
"ACTION_REQUIRED",
|
|
411
|
+
"STALE"
|
|
412
|
+
]);
|
|
413
|
+
var PASS_CONCLUSIONS = new Set(["SUCCESS", "NEUTRAL", "SKIPPED"]);
|
|
414
|
+
var FAILURE_STATES = new Set(["FAILURE", "ERROR"]);
|
|
415
|
+
var aggregateCiStatus = (checks) => {
|
|
416
|
+
if (checks.length === 0)
|
|
417
|
+
return null;
|
|
418
|
+
let failedCount = 0;
|
|
419
|
+
let pendingCount = 0;
|
|
420
|
+
let passedCount = 0;
|
|
421
|
+
for (const check of checks) {
|
|
422
|
+
if (check.conclusion != null) {
|
|
423
|
+
if (FAILURE_CONCLUSIONS.has(check.conclusion))
|
|
424
|
+
failedCount++;
|
|
425
|
+
else if (PASS_CONCLUSIONS.has(check.conclusion))
|
|
426
|
+
passedCount++;
|
|
427
|
+
else
|
|
428
|
+
pendingCount++;
|
|
429
|
+
} else if (check.status != null && check.status !== "COMPLETED") {
|
|
430
|
+
pendingCount++;
|
|
431
|
+
} else if (check.state != null) {
|
|
432
|
+
if (FAILURE_STATES.has(check.state))
|
|
433
|
+
failedCount++;
|
|
434
|
+
else if (check.state === "SUCCESS")
|
|
435
|
+
passedCount++;
|
|
436
|
+
else
|
|
437
|
+
pendingCount++;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (failedCount > 0)
|
|
441
|
+
return { conclusion: "failure", count: failedCount };
|
|
442
|
+
if (pendingCount > 0)
|
|
443
|
+
return { conclusion: "pending", count: pendingCount };
|
|
444
|
+
return { conclusion: "success", count: passedCount };
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
// src/git/pr.ts
|
|
448
|
+
var getPrInfoCached = async () => {
|
|
449
|
+
if (Date.now() - cache.prInfo.timestamp < CACHE_TTL.prInfo) {
|
|
450
|
+
return cache.prInfo.value;
|
|
523
451
|
}
|
|
524
452
|
try {
|
|
525
|
-
const result = await $5`gh pr view --json url
|
|
526
|
-
|
|
527
|
-
|
|
453
|
+
const result = await $5`gh pr view --json url,state,isDraft,statusCheckRollup 2>/dev/null`.text();
|
|
454
|
+
const parsed = JSON.parse(result);
|
|
455
|
+
const value = {
|
|
456
|
+
url: parsed.url,
|
|
457
|
+
state: parsed.state,
|
|
458
|
+
isDraft: parsed.isDraft,
|
|
459
|
+
ciStatus: aggregateCiStatus(parsed.statusCheckRollup ?? [])
|
|
460
|
+
};
|
|
461
|
+
cache.prInfo = { value, timestamp: Date.now() };
|
|
462
|
+
return value;
|
|
528
463
|
} catch {
|
|
529
|
-
return cache.
|
|
464
|
+
return cache.prInfo.value;
|
|
530
465
|
}
|
|
531
466
|
};
|
|
532
467
|
// src/git/worktree.ts
|
|
533
468
|
var {$: $6 } = globalThis.Bun;
|
|
534
|
-
var
|
|
535
|
-
if (Date.now() - cache.
|
|
536
|
-
return cache.
|
|
469
|
+
var getMainProjectCached = async () => {
|
|
470
|
+
if (Date.now() - cache.mainProject.timestamp < CACHE_TTL.mainProject) {
|
|
471
|
+
return cache.mainProject.value;
|
|
537
472
|
}
|
|
538
473
|
try {
|
|
539
474
|
const result = await $6`git rev-parse --git-common-dir 2>/dev/null`.text();
|
|
540
475
|
const gitCommonDir = result.trim();
|
|
541
476
|
if (gitCommonDir === ".git") {
|
|
542
|
-
cache.
|
|
477
|
+
cache.mainProject = { value: null, timestamp: Date.now() };
|
|
543
478
|
return null;
|
|
544
479
|
}
|
|
545
480
|
const projectRoot = gitCommonDir.replace(/\/.git$/, "");
|
|
546
|
-
const projectName = projectRoot.split("/").pop()
|
|
547
|
-
|
|
548
|
-
|
|
481
|
+
const projectName = projectRoot.split("/").pop();
|
|
482
|
+
const value = projectName ? { name: projectName, path: projectRoot } : null;
|
|
483
|
+
cache.mainProject = { value, timestamp: Date.now() };
|
|
484
|
+
return value;
|
|
549
485
|
} catch {
|
|
550
|
-
return cache.
|
|
486
|
+
return cache.mainProject.value;
|
|
551
487
|
}
|
|
552
488
|
};
|
|
553
|
-
// src/colors.ts
|
|
554
|
-
var C = {
|
|
555
|
-
RESET: "\x1B[0m",
|
|
556
|
-
CYAN: "\x1B[38;2;0;255;255m",
|
|
557
|
-
MAGENTA: "\x1B[38;2;255;100;200m",
|
|
558
|
-
GREEN: "\x1B[38;2;100;255;100m",
|
|
559
|
-
YELLOW: "\x1B[38;2;255;220;100m",
|
|
560
|
-
RED: "\x1B[38;2;255;100;100m",
|
|
561
|
-
BLUE: "\x1B[38;2;100;150;255m",
|
|
562
|
-
WHITE: "\x1B[38;2;200;200;200m",
|
|
563
|
-
UNDERLINE: "\x1B[4m"
|
|
564
|
-
};
|
|
565
|
-
var getUsageColor = (pct) => {
|
|
566
|
-
if (pct < 50)
|
|
567
|
-
return C.WHITE;
|
|
568
|
-
if (pct < 80)
|
|
569
|
-
return C.YELLOW;
|
|
570
|
-
return C.RED;
|
|
571
|
-
};
|
|
572
|
-
|
|
573
|
-
// src/format/formatNumber.ts
|
|
574
|
-
var formatNumber = (n) => n.toLocaleString("en-US");
|
|
575
|
-
// src/format/formatResetDate.ts
|
|
576
|
-
var formatResetDate = (date) => {
|
|
577
|
-
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
578
|
-
const day = date.getDate().toString().padStart(2, "0");
|
|
579
|
-
const hours = date.getHours().toString().padStart(2, "0");
|
|
580
|
-
const minutes = date.getMinutes().toString().padStart(2, "0");
|
|
581
|
-
return `${month}/${day} ${hours}:${minutes}`;
|
|
582
|
-
};
|
|
583
|
-
// src/format/formatTime.ts
|
|
584
|
-
var formatTime = (hours, mins) => `${hours.toString().padStart(2, "0")}:${mins.toString().padStart(2, "0")}`;
|
|
585
489
|
// src/render.ts
|
|
586
490
|
var renderStatusLine = (ctx) => {
|
|
587
491
|
const lines = [];
|
|
@@ -593,9 +497,17 @@ var renderStatusLine = (ctx) => {
|
|
|
593
497
|
const costUsd = ctx.claudeJson.cost?.total_cost_usd || 0;
|
|
594
498
|
let line1;
|
|
595
499
|
if (ctx.mainProjectName) {
|
|
596
|
-
|
|
500
|
+
const mainU = ctx.mainProjectUrl ? C.UNDERLINE : "";
|
|
501
|
+
const mainText = `${C.WHITE}\uD83D\uDCC1 ${mainU}${ctx.mainProjectName}${C.RESET}`;
|
|
502
|
+
const mainSeg = ctx.mainProjectUrl ? `\x1B]8;;${ctx.mainProjectUrl}\x07${mainText}\x1B]8;;\x07` : mainText;
|
|
503
|
+
const folderU = ctx.projectDirUrl ? C.UNDERLINE : "";
|
|
504
|
+
const folderText = `${C.WHITE}\uD83C\uDF32 ${folderU}${folder}${C.RESET}`;
|
|
505
|
+
const folderSeg = ctx.projectDirUrl ? `\x1B]8;;${ctx.projectDirUrl}\x07${folderText}\x1B]8;;\x07` : folderText;
|
|
506
|
+
line1 = `${mainSeg} | ${folderSeg}`;
|
|
597
507
|
} else {
|
|
598
|
-
|
|
508
|
+
const folderU = ctx.projectDirUrl ? C.UNDERLINE : "";
|
|
509
|
+
const folderText = `${C.WHITE}\uD83D\uDCC1 ${folderU}${folder}${C.RESET}`;
|
|
510
|
+
line1 = ctx.projectDirUrl ? `\x1B]8;;${ctx.projectDirUrl}\x07${folderText}\x1B]8;;\x07` : folderText;
|
|
599
511
|
}
|
|
600
512
|
if (ctx.branch) {
|
|
601
513
|
line1 += ` | ${C.WHITE}\uD83C\uDF3F ${ctx.branch}${C.RESET}`;
|
|
@@ -646,7 +558,7 @@ var renderStatusLine = (ctx) => {
|
|
|
646
558
|
}
|
|
647
559
|
}
|
|
648
560
|
const hasGitChanges = ctx.gitChanges.files > 0 || ctx.gitChanges.insertions > 0 || ctx.gitChanges.deletions > 0;
|
|
649
|
-
if (hasGitChanges || ctx.baseChanges || ctx.
|
|
561
|
+
if (hasGitChanges || ctx.baseChanges || ctx.prInfo) {
|
|
650
562
|
let line4 = "";
|
|
651
563
|
if (hasGitChanges) {
|
|
652
564
|
const u = ctx.diffViewerUrl ? C.UNDERLINE : "";
|
|
@@ -658,11 +570,17 @@ var renderStatusLine = (ctx) => {
|
|
|
658
570
|
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
571
|
line4 += ctx.baseDiffViewerUrl ? `\x1B]8;;${ctx.baseDiffViewerUrl}\x07${baseText}\x1B]8;;\x07` : baseText;
|
|
660
572
|
}
|
|
661
|
-
if (ctx.
|
|
662
|
-
const
|
|
573
|
+
if (ctx.prInfo) {
|
|
574
|
+
const { url, state, isDraft, ciStatus } = ctx.prInfo;
|
|
575
|
+
const prLabel = url.replace(/^https?:\/\/[^/]+\//, "").replace("/pull/", "#");
|
|
576
|
+
const stateText = prStateText(state, isDraft);
|
|
577
|
+
const stateColor = prStateColor(state, isDraft);
|
|
578
|
+
const ciText = ciSummaryText(ciStatus);
|
|
579
|
+
const ciColor = ciSummaryColor(ciStatus);
|
|
580
|
+
const ciSuffix = ciText ? `${ciColor}${C.UNDERLINE}(${ciText})` : "";
|
|
663
581
|
if (line4)
|
|
664
582
|
line4 += " | ";
|
|
665
|
-
line4 += `\uD83D\uDCCE ${C.WHITE}${C.UNDERLINE}\x1B]8;;${
|
|
583
|
+
line4 += `\uD83D\uDCCE ${C.WHITE}${C.UNDERLINE}\x1B]8;;${url}\x07${prLabel} ` + `${stateColor}${C.UNDERLINE}[${stateText}]${ciSuffix}${C.RESET}\x1B]8;;\x07`;
|
|
666
584
|
}
|
|
667
585
|
lines.push(line4);
|
|
668
586
|
}
|
|
@@ -681,14 +599,14 @@ var readStdin = async () => {
|
|
|
681
599
|
// src/ultracode.ts
|
|
682
600
|
import { readFile } from "fs/promises";
|
|
683
601
|
import { homedir as homedir2 } from "os";
|
|
684
|
-
import { join as
|
|
685
|
-
var MANAGED_SETTINGS_PATH = process.platform === "darwin" ? "/Library/Application Support/ClaudeCode/managed-settings.json" : process.platform === "win32" ?
|
|
602
|
+
import { join as join4 } from "path";
|
|
603
|
+
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
604
|
var ultracodeSettingsPaths = (projectDir, homeDir) => {
|
|
687
605
|
const paths = [MANAGED_SETTINGS_PATH];
|
|
688
606
|
if (projectDir) {
|
|
689
|
-
paths.push(
|
|
607
|
+
paths.push(join4(projectDir, ".claude", "settings.local.json"), join4(projectDir, ".claude", "settings.json"));
|
|
690
608
|
}
|
|
691
|
-
paths.push(
|
|
609
|
+
paths.push(join4(homeDir, ".claude", "settings.json"));
|
|
692
610
|
return paths;
|
|
693
611
|
};
|
|
694
612
|
var resolveUltracodeFromFiles = async (paths) => {
|
|
@@ -718,13 +636,16 @@ var getUltracodeCached = async (projectDir, homeDir = homedir2()) => {
|
|
|
718
636
|
var main = async () => {
|
|
719
637
|
const claudeJson = JSON.parse(await readStdin() || "{}");
|
|
720
638
|
const repo = claudeJson.workspace?.project_dir || claudeJson.workspace?.current_dir || "";
|
|
721
|
-
const [branch, gitChanges,
|
|
639
|
+
const [branch, gitChanges, prInfo, mainProject, ultracode] = await Promise.all([
|
|
722
640
|
getBranchCached(),
|
|
723
641
|
getGitChangesCached(),
|
|
724
|
-
|
|
725
|
-
|
|
642
|
+
getPrInfoCached(),
|
|
643
|
+
getMainProjectCached(),
|
|
726
644
|
getUltracodeCached(repo)
|
|
727
645
|
]);
|
|
646
|
+
const mainProjectName = mainProject?.name ?? null;
|
|
647
|
+
const projectDirUrl = repo ? toFileUrl(repo) : null;
|
|
648
|
+
const mainProjectUrl = mainProject ? toFileUrl(mainProject.path) : null;
|
|
728
649
|
const hasChanges = gitChanges.files > 0 || gitChanges.insertions > 0 || gitChanges.deletions > 0;
|
|
729
650
|
let diffViewerUrl = null;
|
|
730
651
|
let baseDiffViewerUrl = null;
|
|
@@ -756,28 +677,22 @@ var main = async () => {
|
|
|
756
677
|
claudeJson,
|
|
757
678
|
branch,
|
|
758
679
|
gitChanges,
|
|
759
|
-
|
|
680
|
+
prInfo,
|
|
760
681
|
ultracode,
|
|
761
682
|
rateLimits: claudeJson.rate_limits ?? null,
|
|
762
683
|
mainProjectName,
|
|
763
684
|
diffViewerUrl,
|
|
764
685
|
baseChanges,
|
|
765
|
-
baseDiffViewerUrl
|
|
686
|
+
baseDiffViewerUrl,
|
|
687
|
+
projectDirUrl,
|
|
688
|
+
mainProjectUrl
|
|
766
689
|
});
|
|
767
690
|
for (const line of lines) {
|
|
768
691
|
console.log(line);
|
|
769
692
|
}
|
|
770
693
|
};
|
|
771
694
|
if (import.meta.main) {
|
|
772
|
-
|
|
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
|
-
}
|
|
695
|
+
main().catch(console.error);
|
|
781
696
|
}
|
|
782
697
|
export {
|
|
783
698
|
main
|