@delorenj/pjangler 1.5.38 → 1.5.40
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/crank/tui.js +63 -6
- package/dist/index.js +582 -57
- package/package.json +1 -1
package/dist/crank/tui.js
CHANGED
|
@@ -213,6 +213,59 @@ var PJANGLER_VERSION = (() => {
|
|
|
213
213
|
return "0.0.0";
|
|
214
214
|
})();
|
|
215
215
|
|
|
216
|
+
// src/crank/engine.ts
|
|
217
|
+
var WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
218
|
+
var TICK_MS = 5 * 60 * 1e3;
|
|
219
|
+
var STALL_MS = 3 * TICK_MS;
|
|
220
|
+
var ENGINE_AUDIT_TAIL_BYTES = 1024 * 1024;
|
|
221
|
+
var FUTURE_SKEW_MS = 5 * 6e4;
|
|
222
|
+
function clockTime(ms) {
|
|
223
|
+
const d = new Date(ms);
|
|
224
|
+
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
225
|
+
}
|
|
226
|
+
function ago(thenMs, nowMs) {
|
|
227
|
+
const s = Math.max(0, Math.round((nowMs - thenMs) / 1e3));
|
|
228
|
+
if (s < 60) return `${s}s ago`;
|
|
229
|
+
if (s < 3600) return `${Math.floor(s / 60)}m ago`;
|
|
230
|
+
if (s < 86400) return `${Math.floor(s / 3600)}h ${String(Math.floor(s % 3600 / 60)).padStart(2, "0")}m ago`;
|
|
231
|
+
return `${Math.floor(s / 86400)}d ${Math.floor(s % 86400 / 3600)}h ago`;
|
|
232
|
+
}
|
|
233
|
+
function compactTokens(n) {
|
|
234
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
235
|
+
if (n >= 1e3) return `${Math.round(n / 1e3)}k`;
|
|
236
|
+
return String(n);
|
|
237
|
+
}
|
|
238
|
+
function nextTickAtOrAfter(ms) {
|
|
239
|
+
return Math.ceil(ms / TICK_MS) * TICK_MS;
|
|
240
|
+
}
|
|
241
|
+
function engineStalled(status) {
|
|
242
|
+
return status.known && status.lastTickMs !== void 0 && status.readAtMs - status.lastTickMs > STALL_MS;
|
|
243
|
+
}
|
|
244
|
+
function engineBlockedLabel(status) {
|
|
245
|
+
if (!status.known) return void 0;
|
|
246
|
+
if (engineStalled(status) && status.lastTickMs !== void 0) return `blocked: engine not ticking (last tick ${ago(status.lastTickMs, status.readAtMs)})`;
|
|
247
|
+
if (status.outcome !== "refused") return void 0;
|
|
248
|
+
if (status.tokenBlocked) {
|
|
249
|
+
const resume = status.resumeMs !== void 0 ? ` \u2014 resumes \u2248 ${clockTime(nextTickAtOrAfter(status.resumeMs))}` : "";
|
|
250
|
+
return `blocked: daily token cap${resume}`;
|
|
251
|
+
}
|
|
252
|
+
return `blocked: ${status.reason ?? "engine refused the last tick"}`;
|
|
253
|
+
}
|
|
254
|
+
function engineSummary(status) {
|
|
255
|
+
if (!status.known || status.lastTickMs === void 0) return { text: "engine status unknown", tone: "unknown" };
|
|
256
|
+
const now = status.readAtMs;
|
|
257
|
+
const usage = `${status.beats24h} beat${status.beats24h === 1 ? "" : "s"} / ${compactTokens(status.tokens24h)} tokens in 24h${status.cap ? ` of ${compactTokens(status.cap)}` : ""}`;
|
|
258
|
+
const tick = `last tick ${ago(status.lastTickMs, now)}`;
|
|
259
|
+
const stalled = engineStalled(status) ? " - not ticking" : "";
|
|
260
|
+
if (status.outcome === "cranked") return { text: `${tick} (cranked)${stalled} \xB7 ${usage}`, tone: stalled ? "warn" : "ok" };
|
|
261
|
+
if (status.outcome === "refused") {
|
|
262
|
+
const since = status.refusedSinceMs !== void 0 && status.refusedSinceMs < status.lastTickMs ? ` since ${ago(status.refusedSinceMs, now)}` : "";
|
|
263
|
+
const resume = status.tokenBlocked && status.resumeMs !== void 0 ? ` \xB7 resumes \u2248 ${clockTime(nextTickAtOrAfter(status.resumeMs))}` : "";
|
|
264
|
+
return { text: `REFUSING${since}: ${status.reason ?? "refused"} \xB7 ${tick}${stalled} \xB7 ${usage}${resume}`, tone: "warn" };
|
|
265
|
+
}
|
|
266
|
+
return { text: `${tick} (${status.lastStatus ?? "other"}${status.reason ? `: ${status.reason}` : ""})${stalled} \xB7 ${usage}`, tone: "warn" };
|
|
267
|
+
}
|
|
268
|
+
|
|
216
269
|
// src/crank/index.ts
|
|
217
270
|
var ZCRANK_INTERVALS = ["5m", "1h", "6h", "1d"];
|
|
218
271
|
var INTERVAL_SECONDS = { "5m": 300, "1h": 3600, "6h": 21600, "1d": 86400 };
|
|
@@ -239,6 +292,7 @@ function formatAge(thenMs, nowMs) {
|
|
|
239
292
|
if (s < 60) return `${s}s ago`;
|
|
240
293
|
return `${formatDuration(s)} ago`;
|
|
241
294
|
}
|
|
295
|
+
var PANE_CWD_TTL_SECONDS = 6 * 3600;
|
|
242
296
|
function renderLogRow(raw) {
|
|
243
297
|
try {
|
|
244
298
|
const parsed = JSON.parse(raw);
|
|
@@ -294,10 +348,6 @@ async function pickInterval(props, run = {}) {
|
|
|
294
348
|
const choice = await runInk((finish) => /* @__PURE__ */ jsx3(IntervalPicker, { ...props, onDone: finish }), run);
|
|
295
349
|
return choice ?? null;
|
|
296
350
|
}
|
|
297
|
-
function clockTime(ms) {
|
|
298
|
-
const d = new Date(ms);
|
|
299
|
-
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
300
|
-
}
|
|
301
351
|
function describeMatch(view) {
|
|
302
352
|
const { resolution } = view;
|
|
303
353
|
if (resolution.storedAsIs) return `no live tab or key matches "${view.name}" - stored under the project name`;
|
|
@@ -346,6 +396,9 @@ function CrankDashboard({ controller, refreshMs = 5e3, clock = Date.now, onQuit
|
|
|
346
396
|
});
|
|
347
397
|
const onInterval = (interval) => act(`interval ${interval}`, () => controller.setInterval(interval));
|
|
348
398
|
const next = nextRunMs({ enabled: isOn, interval: snap.interval, lastRun: snap.lastRunMs, nowMs: now });
|
|
399
|
+
const engineLine = engineSummary(view.engine);
|
|
400
|
+
const blocked = isOn ? engineBlockedLabel(view.engine) : void 0;
|
|
401
|
+
const engineColor = engineLine.tone === "ok" ? themeColor("ok") : engineLine.tone === "warn" ? themeColor("warn") : themeColor("muted");
|
|
349
402
|
const logRows = Math.max(3, (rows || 24) - 19);
|
|
350
403
|
const tail = snap.log.slice(-logRows);
|
|
351
404
|
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 1, children: [
|
|
@@ -390,7 +443,7 @@ function CrankDashboard({ controller, refreshMs = 5e3, clock = Date.now, onQuit
|
|
|
390
443
|
] }),
|
|
391
444
|
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
392
445
|
/* @__PURE__ */ jsx3(Text3, { color: themeColor("muted"), children: "Next run " }),
|
|
393
|
-
next === void 0 ? /* @__PURE__ */ jsx3(Text3, { color: themeColor("muted"), children: "off" }) : /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
446
|
+
next === void 0 ? /* @__PURE__ */ jsx3(Text3, { color: themeColor("muted"), children: "off" }) : blocked ? /* @__PURE__ */ jsx3(Text3, { color: themeColor("warn"), children: blocked }) : /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
394
447
|
next <= now ? "now" : `in ${formatDuration((next - now) / 1e3)}`,
|
|
395
448
|
" ",
|
|
396
449
|
/* @__PURE__ */ jsxs3(Text3, { color: themeColor("muted"), children: [
|
|
@@ -400,8 +453,12 @@ function CrankDashboard({ controller, refreshMs = 5e3, clock = Date.now, onQuit
|
|
|
400
453
|
] })
|
|
401
454
|
] })
|
|
402
455
|
] }),
|
|
456
|
+
/* @__PURE__ */ jsxs3(Text3, { wrap: "truncate-end", children: [
|
|
457
|
+
/* @__PURE__ */ jsx3(Text3, { color: themeColor("muted"), children: "Engine " }),
|
|
458
|
+
/* @__PURE__ */ jsx3(Text3, { color: engineColor, children: engineLine.text })
|
|
459
|
+
] }),
|
|
403
460
|
/* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, children: [
|
|
404
|
-
/* @__PURE__ */ jsx3(Text3, { color: themeColor("muted"), children: "Last runs" }),
|
|
461
|
+
/* @__PURE__ */ jsx3(Text3, { color: themeColor("muted"), children: "Last runs of this tab (beats that touched it, not engine ticks)" }),
|
|
405
462
|
snap.beats.length === 0 ? /* @__PURE__ */ jsx3(Text3, { color: themeColor("muted"), children: " none yet" }) : snap.beats.map((beat, i) => /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate-end", children: [
|
|
406
463
|
" ",
|
|
407
464
|
formatAge(beat.tsMs, now).padEnd(10),
|
package/dist/index.js
CHANGED
|
@@ -6064,8 +6064,8 @@ var init_capture = __esm({
|
|
|
6064
6064
|
|
|
6065
6065
|
// src/index.ts
|
|
6066
6066
|
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
6067
|
-
import { existsSync as
|
|
6068
|
-
import { basename as basename11, join as
|
|
6067
|
+
import { existsSync as existsSync26, readdirSync as readdirSync13, readFileSync as readFileSync31, statSync as statSync9 } from "node:fs";
|
|
6068
|
+
import { basename as basename11, join as join36, resolve as resolve25 } from "node:path";
|
|
6069
6069
|
import { Command as Command3, CommanderError, Option } from "commander";
|
|
6070
6070
|
|
|
6071
6071
|
// src/recipes/Recipe.ts
|
|
@@ -20943,8 +20943,204 @@ async function loadScreen(relativeToDist, base) {
|
|
|
20943
20943
|
init_boardQuery();
|
|
20944
20944
|
init_style();
|
|
20945
20945
|
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
20946
|
-
import { readFileSync as
|
|
20946
|
+
import { mkdirSync as mkdirSync12, readFileSync as readFileSync30, realpathSync as realpathSync12, renameSync as renameSync10, writeFileSync as writeFileSync15 } from "node:fs";
|
|
20947
|
+
import { homedir as homedir15 } from "node:os";
|
|
20948
|
+
import { dirname as dirname22, join as join35 } from "node:path";
|
|
20949
|
+
|
|
20950
|
+
// src/crank/engine.ts
|
|
20951
|
+
import { closeSync as closeSync9, constants as constants7, fstatSync as fstatSync5, openSync as openSync9, readFileSync as readFileSync29, readSync as readSync5 } from "node:fs";
|
|
20952
|
+
import { homedir as homedir14 } from "node:os";
|
|
20947
20953
|
import { join as join34 } from "node:path";
|
|
20954
|
+
var ENGINE_DEFAULT_JOB_ID = "777c511a009c";
|
|
20955
|
+
var WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
20956
|
+
var TICK_MS = 5 * 60 * 1e3;
|
|
20957
|
+
var STALL_MS = 3 * TICK_MS;
|
|
20958
|
+
var ENGINE_AUDIT_TAIL_BYTES = 1024 * 1024;
|
|
20959
|
+
function unknownEngineStatus(nowMs, jobId = ENGINE_DEFAULT_JOB_ID) {
|
|
20960
|
+
return { known: false, jobId, readAtMs: nowMs, outcome: "unknown", beats24h: 0, tokens24h: 0, tokenBlocked: false };
|
|
20961
|
+
}
|
|
20962
|
+
var FUTURE_SKEW_MS = 5 * 6e4;
|
|
20963
|
+
function parseAuditRows(text2, jobId) {
|
|
20964
|
+
const rows = [];
|
|
20965
|
+
for (const line of text2.split("\n")) {
|
|
20966
|
+
const trimmed2 = line.trim();
|
|
20967
|
+
if (!trimmed2) continue;
|
|
20968
|
+
let parsed;
|
|
20969
|
+
try {
|
|
20970
|
+
parsed = JSON.parse(trimmed2);
|
|
20971
|
+
} catch {
|
|
20972
|
+
continue;
|
|
20973
|
+
}
|
|
20974
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) continue;
|
|
20975
|
+
const record3 = parsed;
|
|
20976
|
+
if (String(record3.job_id ?? "") !== jobId) continue;
|
|
20977
|
+
const ts = typeof record3.ts === "string" ? Date.parse(record3.ts) : Number.NaN;
|
|
20978
|
+
if (!Number.isFinite(ts)) continue;
|
|
20979
|
+
const tokens = Number(record3.total_tokens ?? 0);
|
|
20980
|
+
const reasonRaw = record3.economics_reason ?? record3.error;
|
|
20981
|
+
rows.push({
|
|
20982
|
+
tsMs: ts,
|
|
20983
|
+
status: typeof record3.status === "string" ? record3.status : "",
|
|
20984
|
+
tokens: Number.isFinite(tokens) && tokens > 0 ? tokens : 0,
|
|
20985
|
+
reason: typeof reasonRaw === "string" && reasonRaw.trim() ? reasonRaw.trim() : void 0
|
|
20986
|
+
});
|
|
20987
|
+
}
|
|
20988
|
+
return rows.sort((a, b) => a.tsMs - b.tsMs);
|
|
20989
|
+
}
|
|
20990
|
+
function capFromReason(reason) {
|
|
20991
|
+
const match = /daily token limit reached \(([\d,]+)\)/i.exec(reason ?? "");
|
|
20992
|
+
if (!match) return void 0;
|
|
20993
|
+
const n = Number.parseInt((match[1] ?? "").replace(/,/g, ""), 10);
|
|
20994
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
20995
|
+
}
|
|
20996
|
+
function parseEngineStatus(auditText, input) {
|
|
20997
|
+
const jobId = input.jobId ?? ENGINE_DEFAULT_JOB_ID;
|
|
20998
|
+
try {
|
|
20999
|
+
const rows = parseAuditRows(auditText, jobId).filter((r) => r.tsMs <= input.nowMs + FUTURE_SKEW_MS);
|
|
21000
|
+
const last = rows.at(-1);
|
|
21001
|
+
if (!last) return unknownEngineStatus(input.nowMs, jobId);
|
|
21002
|
+
const windowStart = input.nowMs - WINDOW_MS;
|
|
21003
|
+
const counted = rows.filter((r) => (r.status === "completed" || r.status === "started") && r.tsMs >= windowStart);
|
|
21004
|
+
const tokens24h = counted.reduce((sum, r) => sum + r.tokens, 0);
|
|
21005
|
+
const ran = last.status === "completed" || last.status === "started";
|
|
21006
|
+
const refused = !ran && last.status !== "failed" && (last.status === "skipped" || last.status === "blocked" || !!last.reason);
|
|
21007
|
+
const outcome = ran ? "cranked" : refused ? "refused" : "other";
|
|
21008
|
+
const tokenBlocked = outcome === "refused" && /daily token limit/i.test(last.reason ?? "");
|
|
21009
|
+
let refusedSinceMs;
|
|
21010
|
+
if (outcome === "refused") {
|
|
21011
|
+
refusedSinceMs = last.tsMs;
|
|
21012
|
+
for (let i = rows.length - 2; i >= 0; i--) {
|
|
21013
|
+
const r = rows[i];
|
|
21014
|
+
if (!r) break;
|
|
21015
|
+
if (r.status === "completed" || r.status === "started" || r.status === "failed") break;
|
|
21016
|
+
refusedSinceMs = r.tsMs;
|
|
21017
|
+
}
|
|
21018
|
+
}
|
|
21019
|
+
const cap = input.cap && input.cap > 0 ? input.cap : capFromReason(last.reason);
|
|
21020
|
+
let resumeMs;
|
|
21021
|
+
if (tokenBlocked && cap) {
|
|
21022
|
+
let remaining = tokens24h;
|
|
21023
|
+
resumeMs = remaining < cap ? input.nowMs : void 0;
|
|
21024
|
+
for (const row of counted) {
|
|
21025
|
+
if (remaining < cap) break;
|
|
21026
|
+
remaining -= row.tokens;
|
|
21027
|
+
if (remaining < cap) {
|
|
21028
|
+
resumeMs = row.tsMs + WINDOW_MS;
|
|
21029
|
+
break;
|
|
21030
|
+
}
|
|
21031
|
+
}
|
|
21032
|
+
}
|
|
21033
|
+
return {
|
|
21034
|
+
known: true,
|
|
21035
|
+
jobId,
|
|
21036
|
+
readAtMs: input.nowMs,
|
|
21037
|
+
outcome,
|
|
21038
|
+
lastStatus: last.status || void 0,
|
|
21039
|
+
lastTickMs: last.tsMs,
|
|
21040
|
+
reason: outcome === "cranked" ? void 0 : last.reason,
|
|
21041
|
+
refusedSinceMs,
|
|
21042
|
+
beats24h: counted.length,
|
|
21043
|
+
tokens24h,
|
|
21044
|
+
cap,
|
|
21045
|
+
tokenBlocked,
|
|
21046
|
+
resumeMs
|
|
21047
|
+
};
|
|
21048
|
+
} catch {
|
|
21049
|
+
return unknownEngineStatus(input.nowMs, jobId);
|
|
21050
|
+
}
|
|
21051
|
+
}
|
|
21052
|
+
function readTail(path, maxBytes) {
|
|
21053
|
+
const fd = openSync9(path, constants7.O_RDONLY | constants7.O_NONBLOCK);
|
|
21054
|
+
try {
|
|
21055
|
+
const stat = fstatSync5(fd);
|
|
21056
|
+
if (!stat.isFile()) throw new Error(`${path} is not a regular file`);
|
|
21057
|
+
const size = stat.size;
|
|
21058
|
+
const length = Math.min(size, maxBytes);
|
|
21059
|
+
const buffer = Buffer.alloc(length);
|
|
21060
|
+
let read = 0;
|
|
21061
|
+
while (read < length) {
|
|
21062
|
+
const n = readSync5(fd, buffer, read, length - read, size - length + read);
|
|
21063
|
+
if (n <= 0) break;
|
|
21064
|
+
read += n;
|
|
21065
|
+
}
|
|
21066
|
+
const text2 = buffer.subarray(0, read).toString("utf8");
|
|
21067
|
+
if (size <= maxBytes) return text2;
|
|
21068
|
+
const newline = text2.indexOf("\n");
|
|
21069
|
+
return newline === -1 ? "" : text2.slice(newline + 1);
|
|
21070
|
+
} finally {
|
|
21071
|
+
closeSync9(fd);
|
|
21072
|
+
}
|
|
21073
|
+
}
|
|
21074
|
+
function engineSources(env2 = process.env) {
|
|
21075
|
+
const cronDir = join34(homedir14(), ".hermes", "profiles", "33god-pm", "cron");
|
|
21076
|
+
return {
|
|
21077
|
+
auditPath: env2.PJ_CRANK_USAGE_AUDIT?.trim() || join34(cronDir, "usage_audit.jsonl"),
|
|
21078
|
+
jobsPath: join34(cronDir, "jobs.json"),
|
|
21079
|
+
jobId: env2.PJ_CRANK_JOB_ID?.trim() || ENGINE_DEFAULT_JOB_ID
|
|
21080
|
+
};
|
|
21081
|
+
}
|
|
21082
|
+
function readCap(jobsPath, jobId) {
|
|
21083
|
+
try {
|
|
21084
|
+
const parsed = JSON.parse(readFileSync29(jobsPath, "utf8"));
|
|
21085
|
+
const jobs = Array.isArray(parsed.jobs) ? parsed.jobs : [];
|
|
21086
|
+
const job = jobs.find((j) => typeof j === "object" && j !== null && String(j.id ?? "") === jobId);
|
|
21087
|
+
const economics = job?.economics;
|
|
21088
|
+
if (typeof economics !== "object" || economics === null) return void 0;
|
|
21089
|
+
const cap = Number(economics.max_daily_tokens);
|
|
21090
|
+
return Number.isFinite(cap) && cap > 0 ? cap : void 0;
|
|
21091
|
+
} catch {
|
|
21092
|
+
return void 0;
|
|
21093
|
+
}
|
|
21094
|
+
}
|
|
21095
|
+
function readEngineStatus(options = {}) {
|
|
21096
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
21097
|
+
const base = engineSources(options.env);
|
|
21098
|
+
const sources = { ...base, ...options.sources };
|
|
21099
|
+
try {
|
|
21100
|
+
const text2 = readTail(sources.auditPath, options.tailBytes ?? ENGINE_AUDIT_TAIL_BYTES);
|
|
21101
|
+
return parseEngineStatus(text2, { jobId: sources.jobId, cap: readCap(sources.jobsPath, sources.jobId), nowMs });
|
|
21102
|
+
} catch {
|
|
21103
|
+
return unknownEngineStatus(nowMs, sources.jobId);
|
|
21104
|
+
}
|
|
21105
|
+
}
|
|
21106
|
+
function clockTime(ms) {
|
|
21107
|
+
const d = new Date(ms);
|
|
21108
|
+
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
21109
|
+
}
|
|
21110
|
+
function ago(thenMs, nowMs) {
|
|
21111
|
+
const s = Math.max(0, Math.round((nowMs - thenMs) / 1e3));
|
|
21112
|
+
if (s < 60) return `${s}s ago`;
|
|
21113
|
+
if (s < 3600) return `${Math.floor(s / 60)}m ago`;
|
|
21114
|
+
if (s < 86400) return `${Math.floor(s / 3600)}h ${String(Math.floor(s % 3600 / 60)).padStart(2, "0")}m ago`;
|
|
21115
|
+
return `${Math.floor(s / 86400)}d ${Math.floor(s % 86400 / 3600)}h ago`;
|
|
21116
|
+
}
|
|
21117
|
+
function compactTokens(n) {
|
|
21118
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
21119
|
+
if (n >= 1e3) return `${Math.round(n / 1e3)}k`;
|
|
21120
|
+
return String(n);
|
|
21121
|
+
}
|
|
21122
|
+
function nextTickAtOrAfter(ms) {
|
|
21123
|
+
return Math.ceil(ms / TICK_MS) * TICK_MS;
|
|
21124
|
+
}
|
|
21125
|
+
function engineStalled(status) {
|
|
21126
|
+
return status.known && status.lastTickMs !== void 0 && status.readAtMs - status.lastTickMs > STALL_MS;
|
|
21127
|
+
}
|
|
21128
|
+
function engineSummary(status) {
|
|
21129
|
+
if (!status.known || status.lastTickMs === void 0) return { text: "engine status unknown", tone: "unknown" };
|
|
21130
|
+
const now = status.readAtMs;
|
|
21131
|
+
const usage = `${status.beats24h} beat${status.beats24h === 1 ? "" : "s"} / ${compactTokens(status.tokens24h)} tokens in 24h${status.cap ? ` of ${compactTokens(status.cap)}` : ""}`;
|
|
21132
|
+
const tick = `last tick ${ago(status.lastTickMs, now)}`;
|
|
21133
|
+
const stalled = engineStalled(status) ? " - not ticking" : "";
|
|
21134
|
+
if (status.outcome === "cranked") return { text: `${tick} (cranked)${stalled} \xB7 ${usage}`, tone: stalled ? "warn" : "ok" };
|
|
21135
|
+
if (status.outcome === "refused") {
|
|
21136
|
+
const since = status.refusedSinceMs !== void 0 && status.refusedSinceMs < status.lastTickMs ? ` since ${ago(status.refusedSinceMs, now)}` : "";
|
|
21137
|
+
const resume = status.tokenBlocked && status.resumeMs !== void 0 ? ` \xB7 resumes \u2248 ${clockTime(nextTickAtOrAfter(status.resumeMs))}` : "";
|
|
21138
|
+
return { text: `REFUSING${since}: ${status.reason ?? "refused"} \xB7 ${tick}${stalled} \xB7 ${usage}${resume}`, tone: "warn" };
|
|
21139
|
+
}
|
|
21140
|
+
return { text: `${tick} (${status.lastStatus ?? "other"}${status.reason ? `: ${status.reason}` : ""})${stalled} \xB7 ${usage}`, tone: "warn" };
|
|
21141
|
+
}
|
|
21142
|
+
|
|
21143
|
+
// src/crank/index.ts
|
|
20948
21144
|
var ZCRANK_REDIS_HOST = "127.0.0.1";
|
|
20949
21145
|
var ZCRANK_REDIS_PORT = 6379;
|
|
20950
21146
|
function redisEndpoint(env2 = process.env) {
|
|
@@ -21095,6 +21291,15 @@ function redis(deps = {}) {
|
|
|
21095
21291
|
setTabEnabled: (tab, enabled2) => {
|
|
21096
21292
|
run("HSET", `zcrank:tab:${tab}`, "enabled", enabled2 ? "1" : "0");
|
|
21097
21293
|
},
|
|
21294
|
+
/** HGET zcrank:tab:<tab> root -> the owning project identity, or undefined (legacy key / absent). */
|
|
21295
|
+
tabRoot: (tab) => {
|
|
21296
|
+
const v = run("HGET", `zcrank:tab:${tab}`, "root");
|
|
21297
|
+
return v === "" || v == null ? void 0 : v;
|
|
21298
|
+
},
|
|
21299
|
+
/** HSET zcrank:tab:<tab> root <project identity> (PJAN-201). */
|
|
21300
|
+
setTabRoot: (tab, root) => {
|
|
21301
|
+
run("HSET", `zcrank:tab:${tab}`, "root", root);
|
|
21302
|
+
},
|
|
21098
21303
|
/** HGET zcrank:tab:<tab> interval -> a valid interval, else undefined (absent or garbage). */
|
|
21099
21304
|
tabInterval: (tab) => parseInterval(run("HGET", `zcrank:tab:${tab}`, "interval")),
|
|
21100
21305
|
/** HSET zcrank:tab:<tab> interval <5m|1h|6h|1d>. */
|
|
@@ -21165,11 +21370,193 @@ function liveTabs(deps = {}) {
|
|
|
21165
21370
|
try {
|
|
21166
21371
|
const parsed = JSON.parse(result2.stdout);
|
|
21167
21372
|
if (!Array.isArray(parsed)) return [];
|
|
21168
|
-
return parsed.filter((entry) => typeof entry === "object" && entry !== null).map((entry) =>
|
|
21373
|
+
return parsed.filter((entry) => typeof entry === "object" && entry !== null).map((entry) => {
|
|
21374
|
+
const id = Number(entry.tab_id);
|
|
21375
|
+
return {
|
|
21376
|
+
name: String(entry.name ?? ""),
|
|
21377
|
+
position: Number(entry.position ?? 0),
|
|
21378
|
+
...entry.tab_id !== void 0 && entry.tab_id !== null && Number.isInteger(id) ? { id } : {}
|
|
21379
|
+
};
|
|
21380
|
+
});
|
|
21381
|
+
} catch {
|
|
21382
|
+
return [];
|
|
21383
|
+
}
|
|
21384
|
+
}
|
|
21385
|
+
function livePanes(deps = {}) {
|
|
21386
|
+
const shell = deps.shell ?? defaultShell;
|
|
21387
|
+
const result2 = shell("env", [
|
|
21388
|
+
"-u",
|
|
21389
|
+
"ZELLIJ",
|
|
21390
|
+
"-u",
|
|
21391
|
+
"ZELLIJ_SESSION_NAME",
|
|
21392
|
+
"timeout",
|
|
21393
|
+
"8s",
|
|
21394
|
+
"zellij",
|
|
21395
|
+
"-s",
|
|
21396
|
+
ZCRANK_SESSION,
|
|
21397
|
+
"action",
|
|
21398
|
+
"list-panes",
|
|
21399
|
+
"--json",
|
|
21400
|
+
"--all"
|
|
21401
|
+
]);
|
|
21402
|
+
if (result2.status !== 0 || !result2.stdout.trim()) return [];
|
|
21403
|
+
try {
|
|
21404
|
+
const parsed = JSON.parse(result2.stdout);
|
|
21405
|
+
if (!Array.isArray(parsed)) return [];
|
|
21406
|
+
const panes = [];
|
|
21407
|
+
for (const entry of parsed) {
|
|
21408
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
21409
|
+
const e = entry;
|
|
21410
|
+
const id = Number(e.id);
|
|
21411
|
+
if (!Number.isFinite(id)) continue;
|
|
21412
|
+
const tabId = Number(e.tab_id);
|
|
21413
|
+
const tabPosition = Number(e.tab_position);
|
|
21414
|
+
panes.push({
|
|
21415
|
+
id,
|
|
21416
|
+
tabId: e.tab_id !== void 0 && e.tab_id !== null && Number.isInteger(tabId) ? tabId : void 0,
|
|
21417
|
+
tabPosition: e.tab_position !== void 0 && e.tab_position !== null && Number.isInteger(tabPosition) ? tabPosition : void 0,
|
|
21418
|
+
tabName: String(e.tab_name ?? ""),
|
|
21419
|
+
isPlugin: e.is_plugin === true,
|
|
21420
|
+
cwd: typeof e.pane_cwd === "string" && e.pane_cwd.trim() ? e.pane_cwd : void 0
|
|
21421
|
+
});
|
|
21422
|
+
}
|
|
21423
|
+
return panes;
|
|
21169
21424
|
} catch {
|
|
21170
21425
|
return [];
|
|
21171
21426
|
}
|
|
21172
21427
|
}
|
|
21428
|
+
function projectIdentity(dir, deps = {}) {
|
|
21429
|
+
const target = (dir ?? "").trim();
|
|
21430
|
+
if (!target) return void 0;
|
|
21431
|
+
const cached = deps.cache?.get(target);
|
|
21432
|
+
if (cached !== void 0) return cached ?? void 0;
|
|
21433
|
+
if (deps.budget) {
|
|
21434
|
+
deps.budget.untilMs ??= Date.now() + deps.budget.totalMs;
|
|
21435
|
+
if (Date.now() > deps.budget.untilMs) return void 0;
|
|
21436
|
+
}
|
|
21437
|
+
const shell = deps.shell ?? defaultShell;
|
|
21438
|
+
let identity2;
|
|
21439
|
+
try {
|
|
21440
|
+
const result2 = shell("timeout", ["2s", "git", "-C", target, "worktree", "list", "--porcelain"]);
|
|
21441
|
+
if (result2.status === 0) {
|
|
21442
|
+
const first = result2.stdout.split("\n").find((line) => line.startsWith("worktree "));
|
|
21443
|
+
const path = first?.slice("worktree ".length).replace(/\r$/, "").trim();
|
|
21444
|
+
if (path) identity2 = realpathSync12(path);
|
|
21445
|
+
}
|
|
21446
|
+
} catch {
|
|
21447
|
+
identity2 = void 0;
|
|
21448
|
+
}
|
|
21449
|
+
deps.cache?.set(target, identity2 ?? null);
|
|
21450
|
+
return identity2;
|
|
21451
|
+
}
|
|
21452
|
+
function withTabOwners(tabs, panes, identify, claimed = []) {
|
|
21453
|
+
const claimedSet = new Set(claimed);
|
|
21454
|
+
return tabs.map((tab) => {
|
|
21455
|
+
const counts = /* @__PURE__ */ new Map();
|
|
21456
|
+
for (const pane of panes) {
|
|
21457
|
+
if (pane.isPlugin || !pane.cwd) continue;
|
|
21458
|
+
const inTab = tab.id !== void 0 && pane.tabId !== void 0 ? pane.tabId === tab.id : pane.tabPosition === tab.position;
|
|
21459
|
+
if (!inTab) continue;
|
|
21460
|
+
const identity2 = identify(pane.cwd);
|
|
21461
|
+
if (identity2) counts.set(identity2, (counts.get(identity2) ?? 0) + 1);
|
|
21462
|
+
}
|
|
21463
|
+
const ranked = [...counts.entries()].sort((a, b) => b[1] - a[1]);
|
|
21464
|
+
const tied = ranked.filter(([, n]) => n === ranked[0]?.[1]).map(([identity2]) => identity2);
|
|
21465
|
+
const claimedTied = tied.filter((identity2) => claimedSet.has(identity2));
|
|
21466
|
+
const owner = tied.length === 1 ? tied[0] : claimedTied.length === 1 ? claimedTied[0] : void 0;
|
|
21467
|
+
const enriched = { ...tab };
|
|
21468
|
+
if (owner) enriched.owner = owner;
|
|
21469
|
+
if (ranked.length) enriched.ownedPanes = Object.fromEntries(ranked);
|
|
21470
|
+
return enriched;
|
|
21471
|
+
});
|
|
21472
|
+
}
|
|
21473
|
+
var PANE_CWD_TTL_SECONDS = 6 * 3600;
|
|
21474
|
+
function paneCwdCachePath(env2 = process.env) {
|
|
21475
|
+
return env2.ZCRANK_CWD_CACHE?.trim() || join35(homedir15(), ".local/state/zcrank/pane-cwds.json");
|
|
21476
|
+
}
|
|
21477
|
+
function loadCwdCache(path) {
|
|
21478
|
+
try {
|
|
21479
|
+
const parsed = JSON.parse(readFileSync30(path, "utf8"));
|
|
21480
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
|
|
21481
|
+
} catch {
|
|
21482
|
+
return {};
|
|
21483
|
+
}
|
|
21484
|
+
}
|
|
21485
|
+
function saveCwdCache(path, cache) {
|
|
21486
|
+
try {
|
|
21487
|
+
mkdirSync12(dirname22(path), { recursive: true });
|
|
21488
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
21489
|
+
writeFileSync15(tmp, JSON.stringify(cache));
|
|
21490
|
+
renameSync10(tmp, path);
|
|
21491
|
+
} catch {
|
|
21492
|
+
}
|
|
21493
|
+
}
|
|
21494
|
+
function cwdEntryFresh(entry, nowSeconds) {
|
|
21495
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return false;
|
|
21496
|
+
const ts = Number(entry.ts ?? 0);
|
|
21497
|
+
return Number.isFinite(ts) && nowSeconds - ts < PANE_CWD_TTL_SECONDS;
|
|
21498
|
+
}
|
|
21499
|
+
function resolvePaneCwds(fetch2, opts = {}) {
|
|
21500
|
+
const now = (opts.nowMs ?? Date.now()) / 1e3;
|
|
21501
|
+
const path = opts.cachePath ?? paneCwdCachePath();
|
|
21502
|
+
const get = () => {
|
|
21503
|
+
try {
|
|
21504
|
+
const rows = fetch2();
|
|
21505
|
+
return Array.isArray(rows) ? rows : [];
|
|
21506
|
+
} catch {
|
|
21507
|
+
return [];
|
|
21508
|
+
}
|
|
21509
|
+
};
|
|
21510
|
+
const panes = get();
|
|
21511
|
+
if (!panes.length) return panes;
|
|
21512
|
+
const cache = loadCwdCache(path);
|
|
21513
|
+
const sameTab = (entry, pane) => (entry.tab_id ?? null) === (pane.tabId ?? null);
|
|
21514
|
+
const fill = () => {
|
|
21515
|
+
let unknown2 = 0;
|
|
21516
|
+
for (const pane of panes) {
|
|
21517
|
+
if (pane.isPlugin) continue;
|
|
21518
|
+
const key = String(pane.id);
|
|
21519
|
+
if (pane.cwd) {
|
|
21520
|
+
cache[key] = { cwd: pane.cwd, tab_id: pane.tabId ?? null, ts: now };
|
|
21521
|
+
continue;
|
|
21522
|
+
}
|
|
21523
|
+
const entry = cache[key];
|
|
21524
|
+
if (cwdEntryFresh(entry, now) && sameTab(entry, pane) && typeof entry.cwd === "string" && entry.cwd) {
|
|
21525
|
+
pane.cwd = entry.cwd;
|
|
21526
|
+
} else {
|
|
21527
|
+
unknown2++;
|
|
21528
|
+
}
|
|
21529
|
+
}
|
|
21530
|
+
return unknown2;
|
|
21531
|
+
};
|
|
21532
|
+
let unknown = fill();
|
|
21533
|
+
for (let attempt = 0; attempt < (opts.retries ?? 0) && unknown; attempt++) {
|
|
21534
|
+
const again = new Map(get().filter((pane) => pane.cwd).map((pane) => [pane.id, pane.cwd]));
|
|
21535
|
+
for (const pane of panes) {
|
|
21536
|
+
const cwd = again.get(pane.id);
|
|
21537
|
+
if (!pane.isPlugin && !pane.cwd && cwd) pane.cwd = cwd;
|
|
21538
|
+
}
|
|
21539
|
+
unknown = fill();
|
|
21540
|
+
}
|
|
21541
|
+
const live = new Set(panes.filter((pane) => !pane.isPlugin).map((pane) => String(pane.id)));
|
|
21542
|
+
for (const key of Object.keys(cache)) {
|
|
21543
|
+
if (!live.has(key) || !cwdEntryFresh(cache[key], now)) delete cache[key];
|
|
21544
|
+
}
|
|
21545
|
+
saveCwdCache(path, cache);
|
|
21546
|
+
return panes;
|
|
21547
|
+
}
|
|
21548
|
+
function liveTabsWithOwners(deps = {}) {
|
|
21549
|
+
const tabs = liveTabs(deps);
|
|
21550
|
+
if (!tabs.length) return tabs;
|
|
21551
|
+
const cache = /* @__PURE__ */ new Map();
|
|
21552
|
+
const budget = { totalMs: IDENTITY_BUDGET_MS };
|
|
21553
|
+
const panes = resolvePaneCwds(() => livePanes(deps), { retries: deps.retries ?? 1, nowMs: deps.nowMs, cachePath: deps.cachePath });
|
|
21554
|
+
return withTabOwners(tabs, panes, (cwd) => projectIdentity(cwd, { shell: deps.shell, cache, budget }), deps.claimed ?? []);
|
|
21555
|
+
}
|
|
21556
|
+
function claimedRoots(store, keys = storedTabKeys(store)) {
|
|
21557
|
+
return [...new Set(Object.values(storedTabRoots(store, keys)))].sort();
|
|
21558
|
+
}
|
|
21559
|
+
var IDENTITY_BUDGET_MS = 2e4;
|
|
21173
21560
|
function candidateNames(project) {
|
|
21174
21561
|
const out = [project.name.trim()].filter(Boolean);
|
|
21175
21562
|
for (const agent of project.agents) {
|
|
@@ -21182,13 +21569,41 @@ function candidateNames(project) {
|
|
|
21182
21569
|
}
|
|
21183
21570
|
return out;
|
|
21184
21571
|
}
|
|
21185
|
-
function
|
|
21572
|
+
function keyForLiveTab(tab, storedTabs) {
|
|
21573
|
+
const display = tabDisplay(tab.name);
|
|
21574
|
+
if (storedTabs.includes(display)) return display;
|
|
21575
|
+
return storedTabs.find((s) => canon(s) && canon(s) === canon(display)) ?? display;
|
|
21576
|
+
}
|
|
21577
|
+
function ownerOfLiveTab(live, liveTab) {
|
|
21578
|
+
if (liveTab === void 0) return void 0;
|
|
21579
|
+
return live.find((tab) => tabDisplay(tab.name) === liveTab)?.owner;
|
|
21580
|
+
}
|
|
21581
|
+
function resolveTabForProject(project, live, storedTabs, scope = {}) {
|
|
21582
|
+
const byCwd = resolveByCwd(project, live, storedTabs, scope);
|
|
21583
|
+
if (byCwd) return byCwd;
|
|
21584
|
+
const resolution = resolveByName(project, live, storedTabs);
|
|
21585
|
+
const tabOwner = ownerOfLiveTab(live, resolution.liveTab);
|
|
21586
|
+
return tabOwner ? { ...resolution, tabOwner } : resolution;
|
|
21587
|
+
}
|
|
21588
|
+
function resolveByCwd(project, live, storedTabs, scope) {
|
|
21589
|
+
const identity2 = scope.identity;
|
|
21590
|
+
if (!identity2) return void 0;
|
|
21591
|
+
const owned = live.filter((tab2) => tab2.owner === identity2);
|
|
21592
|
+
if (!owned.length) return void 0;
|
|
21593
|
+
const candidateCanons = new Set(candidateNames(project).map((name) => canon(name)).filter(Boolean));
|
|
21594
|
+
const ranked = [...owned].sort((a, b) => Number(candidateCanons.has(canon(b.name))) - Number(candidateCanons.has(canon(a.name))) || (b.ownedPanes?.[identity2] ?? 0) - (a.ownedPanes?.[identity2] ?? 0) || a.position - b.position);
|
|
21595
|
+
const tab = ranked[0];
|
|
21596
|
+
if (!tab) return void 0;
|
|
21597
|
+
let key = keyForLiveTab(tab, storedTabs);
|
|
21598
|
+
if (!storedTabs.includes(key)) {
|
|
21599
|
+
const rooted = storedTabs.find((stored) => scope.roots?.[stored] === identity2);
|
|
21600
|
+
if (rooted !== void 0) key = rooted;
|
|
21601
|
+
}
|
|
21602
|
+
return { tab: key, source: "live-cwd", matched: identity2, storedAsIs: false, liveTab: tabDisplay(tab.name), tabOwner: identity2 };
|
|
21603
|
+
}
|
|
21604
|
+
function resolveByName(project, live, storedTabs) {
|
|
21186
21605
|
const candidates = candidateNames(project);
|
|
21187
|
-
const keyFor = (tab) =>
|
|
21188
|
-
const display = tabDisplay(tab.name);
|
|
21189
|
-
if (storedTabs.includes(display)) return display;
|
|
21190
|
-
return storedTabs.find((s) => canon(s) && canon(s) === canon(display)) ?? display;
|
|
21191
|
-
};
|
|
21606
|
+
const keyFor = (tab) => keyForLiveTab(tab, storedTabs);
|
|
21192
21607
|
const sideSets = [
|
|
21193
21608
|
(tab) => [tab.name],
|
|
21194
21609
|
(tab) => tabDisplay(tab.name).split(":")
|
|
@@ -21224,7 +21639,7 @@ function resolveTabForProject(project, live, storedTabs) {
|
|
|
21224
21639
|
function projectNameFor(handle, registryName) {
|
|
21225
21640
|
if (registryName && registryName.trim()) return registryName.trim();
|
|
21226
21641
|
try {
|
|
21227
|
-
const manifest = JSON.parse(
|
|
21642
|
+
const manifest = JSON.parse(readFileSync30(join35(handle.root, ".project.json"), "utf8"));
|
|
21228
21643
|
const name = manifest.project_name;
|
|
21229
21644
|
if (typeof name === "string" && name.trim()) return name.trim();
|
|
21230
21645
|
} catch {
|
|
@@ -21235,7 +21650,7 @@ function projectAgentsFor(handle, registryAgents) {
|
|
|
21235
21650
|
const fromRecord = Object.entries(registryAgents ?? {}).map(([agentHandle, value]) => ({ handle: agentHandle, role: String(value?.role ?? "") }));
|
|
21236
21651
|
if (fromRecord.length) return fromRecord;
|
|
21237
21652
|
try {
|
|
21238
|
-
const manifest = JSON.parse(
|
|
21653
|
+
const manifest = JSON.parse(readFileSync30(join35(handle.root, ".project.json"), "utf8"));
|
|
21239
21654
|
const agents = manifest.agents;
|
|
21240
21655
|
if (agents && typeof agents === "object" && !Array.isArray(agents)) {
|
|
21241
21656
|
return Object.entries(agents).map(([agentHandle, value]) => ({ handle: agentHandle, role: typeof value?.role === "string" ? value.role : "" }));
|
|
@@ -21244,17 +21659,22 @@ function projectAgentsFor(handle, registryAgents) {
|
|
|
21244
21659
|
}
|
|
21245
21660
|
return [];
|
|
21246
21661
|
}
|
|
21247
|
-
function keysNamingTab(tab, storedKeys) {
|
|
21662
|
+
function keysNamingTab(tab, storedKeys, scope = {}) {
|
|
21248
21663
|
const c = canon(tab);
|
|
21249
21664
|
const out = [];
|
|
21250
21665
|
if (storedKeys.includes(tab)) out.push(tab);
|
|
21251
21666
|
for (const key of storedKeys) {
|
|
21252
21667
|
if (key !== tab && c && canon(key) === c) out.push(key);
|
|
21253
21668
|
}
|
|
21669
|
+
if (scope.owner) {
|
|
21670
|
+
for (const key of storedKeys) {
|
|
21671
|
+
if (scope.roots?.[key] === scope.owner && !out.includes(key)) out.push(key);
|
|
21672
|
+
}
|
|
21673
|
+
}
|
|
21254
21674
|
return out;
|
|
21255
21675
|
}
|
|
21256
|
-
function snapshotCrank(store, tab, storedKeys, nowMs, beatLimit = 5) {
|
|
21257
|
-
const keys = keysNamingTab(tab, storedKeys);
|
|
21676
|
+
function snapshotCrank(store, tab, storedKeys, nowMs, beatLimit = 5, scope = {}) {
|
|
21677
|
+
const keys = keysNamingTab(tab, storedKeys, scope);
|
|
21258
21678
|
const readKeys = keys.length ? keys : [tab];
|
|
21259
21679
|
const enabledValues = keys.map((key) => store.tabEnabled(key)).filter((v) => v !== void 0);
|
|
21260
21680
|
const enabled2 = enabledValues.length ? keys.length > 0 && keys.every((key) => store.tabEnabled(key) === "1") : void 0;
|
|
@@ -21280,12 +21700,13 @@ function snapshotCrank(store, tab, storedKeys, nowMs, beatLimit = 5) {
|
|
|
21280
21700
|
}
|
|
21281
21701
|
function planRenameMigration(input) {
|
|
21282
21702
|
const target = input.resolution.liveTab;
|
|
21283
|
-
if (!target || input.resolution.source !== "live-exact" && input.resolution.source !== "live-prefix") return void 0;
|
|
21703
|
+
if (!target || input.resolution.source !== "live-exact" && input.resolution.source !== "live-prefix" && input.resolution.source !== "live-cwd") return void 0;
|
|
21284
21704
|
const targetCanon = canon(target);
|
|
21285
21705
|
const wanted = new Set([targetCanon, ...input.candidates.map((name) => canon(name))].filter(Boolean));
|
|
21286
21706
|
const otherLive = new Set(input.live.map((tab) => canon(tab.name)).filter((c) => c && c !== targetCanon));
|
|
21287
21707
|
const oldKeys = [...new Set(input.storedKeys)].filter((key) => {
|
|
21288
21708
|
if (key === target) return false;
|
|
21709
|
+
if (input.identity && input.roots?.[key] === input.identity) return true;
|
|
21289
21710
|
const c = canon(key);
|
|
21290
21711
|
return c !== "" && wanted.has(c) && !otherLive.has(c);
|
|
21291
21712
|
});
|
|
@@ -21316,12 +21737,36 @@ function applyRenameMigration(store, plan) {
|
|
|
21316
21737
|
store.deleteKeys(`zcrank:tab:${old}`, ...oldPanes, `zcrank:log:${old}`);
|
|
21317
21738
|
}
|
|
21318
21739
|
}
|
|
21319
|
-
function buildCrankContext(project, live, storedKeys) {
|
|
21320
|
-
|
|
21740
|
+
function buildCrankContext(project, live, storedKeys, scope = {}) {
|
|
21741
|
+
const roots = scope.roots ?? {};
|
|
21742
|
+
return {
|
|
21743
|
+
resolution: resolveTabForProject(project, live, storedKeys, { identity: scope.identity, roots }),
|
|
21744
|
+
candidates: candidateNames(project),
|
|
21745
|
+
live,
|
|
21746
|
+
storedKeys,
|
|
21747
|
+
identity: scope.identity,
|
|
21748
|
+
roots
|
|
21749
|
+
};
|
|
21750
|
+
}
|
|
21751
|
+
function keyScopeOf(ctx) {
|
|
21752
|
+
return { owner: ctx.resolution.tabOwner, roots: ctx.roots };
|
|
21321
21753
|
}
|
|
21322
21754
|
function storedTabKeys(store) {
|
|
21323
21755
|
return store.tabKeys().map((key) => key.split(":").slice(2).join(":"));
|
|
21324
21756
|
}
|
|
21757
|
+
function storedTabRoots(store, keys) {
|
|
21758
|
+
const roots = {};
|
|
21759
|
+
for (const key of keys) {
|
|
21760
|
+
let root;
|
|
21761
|
+
try {
|
|
21762
|
+
root = store.tabRoot(key);
|
|
21763
|
+
} catch (error) {
|
|
21764
|
+
if (!(error instanceof Error) || !error.message.startsWith("Redis error reply")) throw error;
|
|
21765
|
+
}
|
|
21766
|
+
if (root) roots[key] = root;
|
|
21767
|
+
}
|
|
21768
|
+
return roots;
|
|
21769
|
+
}
|
|
21325
21770
|
function enableCrank(store, ctx, options = {}) {
|
|
21326
21771
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
21327
21772
|
const ts = crankTimestamp(now);
|
|
@@ -21331,16 +21776,20 @@ function enableCrank(store, ctx, options = {}) {
|
|
|
21331
21776
|
storedKeys: ctx.storedKeys,
|
|
21332
21777
|
live: ctx.live,
|
|
21333
21778
|
intervals: (key) => store.tabInterval(key),
|
|
21334
|
-
chosenInterval: options.interval
|
|
21779
|
+
chosenInterval: options.interval,
|
|
21780
|
+
identity: ctx.identity,
|
|
21781
|
+
roots: ctx.roots
|
|
21335
21782
|
});
|
|
21336
21783
|
const tab = plan?.target ?? ctx.resolution.tab;
|
|
21337
21784
|
store.setTabEnabled(tab, true);
|
|
21785
|
+
writeRoot(store, tab, ctx.identity);
|
|
21338
21786
|
if (options.interval) store.setTabInterval(tab, options.interval);
|
|
21339
21787
|
if (plan) applyRenameMigration(store, plan);
|
|
21340
21788
|
else {
|
|
21341
|
-
for (const sibling of keysNamingTab(tab, ctx.storedKeys)) {
|
|
21789
|
+
for (const sibling of keysNamingTab(tab, ctx.storedKeys, keyScopeOf(ctx))) {
|
|
21342
21790
|
if (sibling === tab) continue;
|
|
21343
21791
|
store.setTabEnabled(sibling, true);
|
|
21792
|
+
writeRoot(store, sibling, ctx.identity);
|
|
21344
21793
|
if (options.interval) store.clearTabInterval(sibling);
|
|
21345
21794
|
}
|
|
21346
21795
|
}
|
|
@@ -21350,42 +21799,54 @@ function enableCrank(store, ctx, options = {}) {
|
|
|
21350
21799
|
for (const { from, to } of renamed) store.pushLog(tab, "renamed", `pj crank enable: ${from} -> ${to}`, ts);
|
|
21351
21800
|
return { tab, renamed, interval: options.interval ?? plan?.carryInterval };
|
|
21352
21801
|
}
|
|
21353
|
-
function
|
|
21802
|
+
function writeRoot(store, tab, root) {
|
|
21803
|
+
if (root) store.setTabRoot(tab, root);
|
|
21804
|
+
}
|
|
21805
|
+
function disableCrank(store, tab, now = /* @__PURE__ */ new Date(), root) {
|
|
21354
21806
|
store.setTabEnabled(tab, false);
|
|
21807
|
+
writeRoot(store, tab, root);
|
|
21355
21808
|
store.pushLog(tab, "disabled", "pj crank disable", crankTimestamp(now));
|
|
21356
21809
|
}
|
|
21357
|
-
function setCrankInterval(store, tab, storedKeys, interval, now = /* @__PURE__ */ new Date()) {
|
|
21358
|
-
for (const key of keysNamingTab(tab, storedKeys)) {
|
|
21810
|
+
function setCrankInterval(store, tab, storedKeys, interval, now = /* @__PURE__ */ new Date(), scope = {}) {
|
|
21811
|
+
for (const key of keysNamingTab(tab, storedKeys, scope)) {
|
|
21359
21812
|
if (key !== tab) store.clearTabInterval(key);
|
|
21360
21813
|
}
|
|
21361
21814
|
if (interval) store.setTabInterval(tab, interval);
|
|
21362
21815
|
else store.clearTabInterval(tab);
|
|
21816
|
+
writeRoot(store, tab, scope.root);
|
|
21363
21817
|
const action = interval ? `pj crank tui: interval ${interval}` : `pj crank tui: interval reset to global (${store.globalInterval()})`;
|
|
21364
21818
|
store.pushLog(tab, "interval", action, crankTimestamp(now));
|
|
21365
21819
|
}
|
|
21366
21820
|
function buildCrankList(input) {
|
|
21367
21821
|
const global = input.globalInterval ?? ZCRANK_DEFAULT_INTERVAL;
|
|
21368
|
-
const
|
|
21369
|
-
|
|
21370
|
-
|
|
21371
|
-
|
|
21822
|
+
const roots = input.roots ?? {};
|
|
21823
|
+
const intervalOf = (tab, owner) => {
|
|
21824
|
+
const keys = keysNamingTab(tab, input.storedTabs, { owner, roots });
|
|
21825
|
+
return effectiveInterval((keys.length ? keys : [tab]).map((key) => input.tabInterval?.(key)), global);
|
|
21826
|
+
};
|
|
21372
21827
|
const rows = [];
|
|
21373
21828
|
const consumed = /* @__PURE__ */ new Set();
|
|
21829
|
+
const consumedKeys = /* @__PURE__ */ new Set();
|
|
21374
21830
|
for (const project of input.projects) {
|
|
21375
|
-
const
|
|
21831
|
+
const identity2 = project.repoPath ? input.identityOf?.(project.repoPath) : void 0;
|
|
21832
|
+
const resolution = resolveTabForProject(project, input.live, input.storedTabs, { identity: identity2, roots });
|
|
21376
21833
|
consumed.add(canon(resolution.tab));
|
|
21377
|
-
const
|
|
21834
|
+
const keys = keysNamingTab(resolution.tab, input.storedTabs, { owner: resolution.tabOwner, roots });
|
|
21835
|
+
for (const key of keys) consumedKeys.add(key);
|
|
21836
|
+
const values = (keys.length ? keys : [resolution.tab]).map((key) => input.tabEnabled(key)).filter((v) => v !== void 0);
|
|
21837
|
+
const enabled2 = values.length ? values.every((v) => v === "1") ? "1" : "0" : void 0;
|
|
21378
21838
|
rows.push({
|
|
21379
21839
|
slug: project.slug,
|
|
21380
21840
|
name: project.name,
|
|
21381
21841
|
tab: resolution.tab,
|
|
21382
21842
|
crank: enabled2 === "1" ? "ON" : enabled2 === "0" ? "OFF" : "\u2014",
|
|
21383
21843
|
source: resolution.source,
|
|
21384
|
-
...
|
|
21844
|
+
...resolution.liveTab !== void 0 ? { liveTab: resolution.liveTab } : {},
|
|
21845
|
+
...intervalOf(resolution.tab, resolution.tabOwner)
|
|
21385
21846
|
});
|
|
21386
21847
|
}
|
|
21387
|
-
const orphans = input.storedTabs.filter((stored) => !consumed.has(canon(stored))).map((stored) => ({ tab: stored, enabled: input.tabEnabled(stored) === "1", ...intervalOf(stored) }));
|
|
21388
|
-
return { rows, orphans };
|
|
21848
|
+
const orphans = input.storedTabs.filter((stored) => !consumed.has(canon(stored)) && !consumedKeys.has(stored)).map((stored) => ({ tab: stored, enabled: input.tabEnabled(stored) === "1", ...intervalOf(stored) }));
|
|
21849
|
+
return { engine: input.engine, rows, orphans };
|
|
21389
21850
|
}
|
|
21390
21851
|
function crankTimestamp(now = /* @__PURE__ */ new Date()) {
|
|
21391
21852
|
return now.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
@@ -21401,17 +21862,25 @@ function renderLogRow(raw) {
|
|
|
21401
21862
|
return null;
|
|
21402
21863
|
}
|
|
21403
21864
|
}
|
|
21865
|
+
function tabCell(row) {
|
|
21866
|
+
if (row.source !== "live-cwd" || row.liveTab === void 0) return row.tab;
|
|
21867
|
+
return canon(row.tab) === canon(row.liveTab) ? row.liveTab : `${row.liveTab} (key ${row.tab})`;
|
|
21868
|
+
}
|
|
21404
21869
|
function formatCrankList(report) {
|
|
21405
21870
|
const lines = [""];
|
|
21406
21871
|
lines.push(` ${heading("Crank by project")}`);
|
|
21872
|
+
if (report.engine) {
|
|
21873
|
+
const summary = engineSummary(report.engine);
|
|
21874
|
+
lines.push(summary.tone === "unknown" ? ` ${dim(summary.text)}` : ` ${summary.tone === "ok" ? green("Engine") : yellow("Engine")} ${summary.tone === "warn" ? yellow(summary.text) : dim(summary.text)}`);
|
|
21875
|
+
}
|
|
21407
21876
|
lines.push("");
|
|
21408
21877
|
const slugWidth = Math.max("PROJECT".length, ...report.rows.map((row) => row.slug.length));
|
|
21409
|
-
const tabWidth = Math.max("TAB".length, ...report.rows.map((row) => row.
|
|
21878
|
+
const tabWidth = Math.max("TAB".length, ...report.rows.map((row) => tabCell(row).length), ...report.orphans.map((orphan) => orphan.tab.length));
|
|
21410
21879
|
const intervalCell = (row) => row.inherited ? dim(`${row.interval} (global)`) : row.interval;
|
|
21411
21880
|
lines.push(` ${"PROJECT".padEnd(slugWidth)} ${"TAB".padEnd(tabWidth)} ${"CRANK".padEnd(5)} INTERVAL`);
|
|
21412
21881
|
for (const row of report.rows) {
|
|
21413
21882
|
const state = row.crank === "ON" ? green("ON") : row.crank === "OFF" ? dim("OFF") : dim("\u2014");
|
|
21414
|
-
lines.push(` ${cyan(row.slug.padEnd(slugWidth))} ${row.
|
|
21883
|
+
lines.push(` ${cyan(row.slug.padEnd(slugWidth))} ${tabCell(row).padEnd(tabWidth)} ${state}${" ".repeat(Math.max(0, 5 - (row.crank === "ON" ? 2 : row.crank === "OFF" ? 3 : 1)))} ${intervalCell(row)}`);
|
|
21415
21884
|
}
|
|
21416
21885
|
if (report.orphans.length) {
|
|
21417
21886
|
lines.push("");
|
|
@@ -21426,30 +21895,74 @@ function formatCrankList(report) {
|
|
|
21426
21895
|
|
|
21427
21896
|
// src/crank/controller.ts
|
|
21428
21897
|
var LIVE_TABS_TTL_MS = 3e4;
|
|
21898
|
+
var LIVE_OWNERS_TTL_MS = 3e5;
|
|
21899
|
+
function ownerMemoReader(deps = {}) {
|
|
21900
|
+
const clock = deps.clock ?? Date.now;
|
|
21901
|
+
const read = deps.read ?? ((claimed) => liveTabsWithOwners({ claimed }));
|
|
21902
|
+
const listTabs = deps.tabs ?? (() => liveTabs());
|
|
21903
|
+
const readClaimed = () => {
|
|
21904
|
+
try {
|
|
21905
|
+
return deps.claimed?.() ?? [];
|
|
21906
|
+
} catch {
|
|
21907
|
+
return [];
|
|
21908
|
+
}
|
|
21909
|
+
};
|
|
21910
|
+
let memo;
|
|
21911
|
+
return () => {
|
|
21912
|
+
const now = clock();
|
|
21913
|
+
const tabs = listTabs();
|
|
21914
|
+
const claimed = readClaimed();
|
|
21915
|
+
const claimedSig = claimed.join("\n");
|
|
21916
|
+
if (memo && memo.claimed === claimedSig && now - memo.at <= LIVE_OWNERS_TTL_MS && tabs.length && tabs.every((tab) => tab.id !== void 0 && memo?.byId.has(tab.id))) {
|
|
21917
|
+
const known = memo.byId;
|
|
21918
|
+
return tabs.map((tab) => ({ ...tab, ...known.get(tab.id) }));
|
|
21919
|
+
}
|
|
21920
|
+
const full = read(claimed);
|
|
21921
|
+
memo = full.length && full.every((tab) => tab.id !== void 0) ? { at: now, claimed: claimedSig, byId: new Map(full.map((tab) => [tab.id, { owner: tab.owner, ownedPanes: tab.ownedPanes }])) } : void 0;
|
|
21922
|
+
return full;
|
|
21923
|
+
};
|
|
21924
|
+
}
|
|
21429
21925
|
function createCrankController(deps) {
|
|
21430
21926
|
const clock = deps.clock ?? Date.now;
|
|
21431
|
-
const readLive = deps.live ?? (() =>
|
|
21927
|
+
const readLive = deps.live ?? ownerMemoReader({ clock, claimed: () => claimedRoots(deps.store) });
|
|
21928
|
+
const readEngine = (nowMs) => {
|
|
21929
|
+
try {
|
|
21930
|
+
return (deps.engine ?? ((now) => readEngineStatus({ nowMs: now })))(nowMs);
|
|
21931
|
+
} catch {
|
|
21932
|
+
return unknownEngineStatus(nowMs);
|
|
21933
|
+
}
|
|
21934
|
+
};
|
|
21432
21935
|
let cachedLive;
|
|
21433
21936
|
const live = (fresh) => {
|
|
21434
21937
|
const now = clock();
|
|
21435
21938
|
if (fresh || !cachedLive || now - cachedLive.at > LIVE_TABS_TTL_MS) cachedLive = { at: now, tabs: readLive() };
|
|
21436
21939
|
return cachedLive.tabs;
|
|
21437
21940
|
};
|
|
21438
|
-
const context = (fresh) =>
|
|
21941
|
+
const context = (fresh) => {
|
|
21942
|
+
const keys = storedTabKeys(deps.store);
|
|
21943
|
+
return buildCrankContext(deps.project, live(fresh), keys, { identity: deps.identity, roots: storedTabRoots(deps.store, keys) });
|
|
21944
|
+
};
|
|
21439
21945
|
return {
|
|
21440
21946
|
slug: deps.slug,
|
|
21441
21947
|
name: deps.project.name,
|
|
21442
21948
|
read(nowMs = clock()) {
|
|
21443
21949
|
const ctx = context(false);
|
|
21444
|
-
return {
|
|
21950
|
+
return {
|
|
21951
|
+
slug: deps.slug,
|
|
21952
|
+
name: deps.project.name,
|
|
21953
|
+
resolution: ctx.resolution,
|
|
21954
|
+
snapshot: snapshotCrank(deps.store, ctx.resolution.tab, ctx.storedKeys, nowMs, 5, keyScopeOf(ctx)),
|
|
21955
|
+
engine: readEngine(nowMs)
|
|
21956
|
+
};
|
|
21445
21957
|
},
|
|
21446
21958
|
enable: () => enableCrank(deps.store, context(true), { now: new Date(clock()) }),
|
|
21447
21959
|
disable() {
|
|
21448
|
-
|
|
21960
|
+
const ctx = context(true);
|
|
21961
|
+
disableCrank(deps.store, ctx.resolution.tab, new Date(clock()), ctx.identity);
|
|
21449
21962
|
},
|
|
21450
21963
|
setInterval(interval) {
|
|
21451
21964
|
const ctx = context(true);
|
|
21452
|
-
setCrankInterval(deps.store, ctx.resolution.tab, ctx.storedKeys, interval, new Date(clock()));
|
|
21965
|
+
setCrankInterval(deps.store, ctx.resolution.tab, ctx.storedKeys, interval, new Date(clock()), { ...keyScopeOf(ctx), root: ctx.identity });
|
|
21453
21966
|
}
|
|
21454
21967
|
};
|
|
21455
21968
|
}
|
|
@@ -21470,13 +21983,20 @@ function currentProjectNames() {
|
|
|
21470
21983
|
names: {
|
|
21471
21984
|
name: projectNameFor(handle, record3?.name),
|
|
21472
21985
|
agents: projectAgentsFor(handle, record3?.agents)
|
|
21473
|
-
}
|
|
21986
|
+
},
|
|
21987
|
+
// PJAN-201: where the project lives (its main worktree), independent of any tab name.
|
|
21988
|
+
identity: projectIdentity(handle.root)
|
|
21474
21989
|
};
|
|
21475
21990
|
}
|
|
21991
|
+
function contextFor(project, store) {
|
|
21992
|
+
const keys = storedTabKeys(store);
|
|
21993
|
+
const roots = storedTabRoots(store, keys);
|
|
21994
|
+
return buildCrankContext(project.names, liveTabsWithOwners({ claimed: Object.values(roots) }), keys, { identity: project.identity, roots });
|
|
21995
|
+
}
|
|
21476
21996
|
function resolveCurrentTab() {
|
|
21477
21997
|
const project = currentProjectNames();
|
|
21478
21998
|
const store = redis();
|
|
21479
|
-
const ctx =
|
|
21999
|
+
const ctx = contextFor(project, store);
|
|
21480
22000
|
return { slug: project.slug, tab: ctx.resolution.tab, storedAsIs: ctx.resolution.storedAsIs };
|
|
21481
22001
|
}
|
|
21482
22002
|
function registerCrankCli(program2) {
|
|
@@ -21507,12 +22027,16 @@ function registerCrankCli(program2) {
|
|
|
21507
22027
|
role: String(value?.role ?? "")
|
|
21508
22028
|
}))
|
|
21509
22029
|
}));
|
|
21510
|
-
const live = liveTabs();
|
|
21511
22030
|
const storedTabs = storedTabKeys(store);
|
|
22031
|
+
const live = liveTabsWithOwners({ claimed: Object.values(storedTabRoots(store, storedTabs)) });
|
|
22032
|
+
const identityCache = /* @__PURE__ */ new Map();
|
|
21512
22033
|
const report = buildCrankList({
|
|
21513
22034
|
projects,
|
|
21514
22035
|
live,
|
|
21515
22036
|
storedTabs,
|
|
22037
|
+
identityOf: (repoPath) => projectIdentity(repoPath, { cache: identityCache }),
|
|
22038
|
+
roots: storedTabRoots(store, storedTabs),
|
|
22039
|
+
engine: readEngineStatus(),
|
|
21516
22040
|
tabEnabled: (tab) => store.tabEnabled(tab),
|
|
21517
22041
|
tabInterval: (tab) => store.tabInterval(tab),
|
|
21518
22042
|
globalInterval: store.globalInterval()
|
|
@@ -21576,9 +22100,9 @@ async function runEnable(options) {
|
|
|
21576
22100
|
}
|
|
21577
22101
|
const project = currentProjectNames();
|
|
21578
22102
|
const store = liveStore();
|
|
21579
|
-
const ctx =
|
|
22103
|
+
const ctx = contextFor(project, store);
|
|
21580
22104
|
if (!chosen && isInteractive()) {
|
|
21581
|
-
const snap = snapshotCrank(store, ctx.resolution.tab, ctx.storedKeys, Date.now());
|
|
22105
|
+
const snap = snapshotCrank(store, ctx.resolution.tab, ctx.storedKeys, Date.now(), 5, keyScopeOf(ctx));
|
|
21582
22106
|
const screens = await loadCrankScreens();
|
|
21583
22107
|
const picked = await screens.pickInterval({ slug: project.slug, tab: ctx.resolution.liveTab ?? ctx.resolution.tab, effective: snap.interval, inherited: snap.inherited, global: snap.global });
|
|
21584
22108
|
if (!picked) {
|
|
@@ -21592,7 +22116,8 @@ async function runEnable(options) {
|
|
|
21592
22116
|
for (const { from, to } of result2.renamed) {
|
|
21593
22117
|
console.log(` ${yellow(glyph.warn)} tab renamed ${dim(glyph.dot)} ${from} ${dim(glyph.pointer)} ${bold(to)} ${dim("(old key merged into the new one and removed)")}`);
|
|
21594
22118
|
}
|
|
21595
|
-
const
|
|
22119
|
+
const afterKeys = storedTabKeys(store);
|
|
22120
|
+
const live = snapshotCrank(store, result2.tab, afterKeys, Date.now(), 5, { owner: ctx.resolution.tabOwner, roots: storedTabRoots(store, afterKeys) });
|
|
21596
22121
|
console.log(` ${dim("interval")} ${live.interval}${live.inherited ? dim(` (inherits global ${live.global})`) : ""}`);
|
|
21597
22122
|
if (ctx.resolution.storedAsIs) {
|
|
21598
22123
|
console.log(` ${yellow(glyph.warn)} ${dim(`no live zellij tab or zcrank key matches "${project.names.name}" \u2014 stored under the project name as-is`)}`);
|
|
@@ -21605,8 +22130,8 @@ function runDisable() {
|
|
|
21605
22130
|
try {
|
|
21606
22131
|
const project = currentProjectNames();
|
|
21607
22132
|
const store = liveStore();
|
|
21608
|
-
const ctx =
|
|
21609
|
-
disableCrank(store, ctx.resolution.tab);
|
|
22133
|
+
const ctx = contextFor(project, store);
|
|
22134
|
+
disableCrank(store, ctx.resolution.tab, /* @__PURE__ */ new Date(), ctx.identity);
|
|
21610
22135
|
console.log(` ${dim("OFF")} ${cyan(project.slug)} ${dim(glyph.pointer)} ${bold(ctx.resolution.tab)}`);
|
|
21611
22136
|
if (ctx.resolution.storedAsIs) {
|
|
21612
22137
|
console.log(` ${yellow(glyph.warn)} ${dim(`no live zellij tab or zcrank key matches "${project.names.name}" \u2014 stored under the project name as-is`)}`);
|
|
@@ -21623,7 +22148,7 @@ async function runTui() {
|
|
|
21623
22148
|
}
|
|
21624
22149
|
const project = currentProjectNames();
|
|
21625
22150
|
const store = liveStore();
|
|
21626
|
-
const controller = createCrankController({ slug: project.slug, project: project.names, store });
|
|
22151
|
+
const controller = createCrankController({ slug: project.slug, project: project.names, store, identity: project.identity });
|
|
21627
22152
|
const screens = await loadCrankScreens();
|
|
21628
22153
|
await screens.runCrankDashboard(controller);
|
|
21629
22154
|
} catch (error) {
|
|
@@ -21658,9 +22183,9 @@ async function promptForRuleIds(rules) {
|
|
|
21658
22183
|
return selected;
|
|
21659
22184
|
}
|
|
21660
22185
|
function readJson2(path) {
|
|
21661
|
-
if (!
|
|
22186
|
+
if (!existsSync26(path)) return void 0;
|
|
21662
22187
|
try {
|
|
21663
|
-
const parsed = JSON.parse(
|
|
22188
|
+
const parsed = JSON.parse(readFileSync31(path, "utf8"));
|
|
21664
22189
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
21665
22190
|
} catch {
|
|
21666
22191
|
return void 0;
|
|
@@ -21677,8 +22202,8 @@ function packageNameToProjectName(value) {
|
|
|
21677
22202
|
return name.replace(/[-_]+/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase()).trim();
|
|
21678
22203
|
}
|
|
21679
22204
|
function deriveProjectDefaults(targetDir) {
|
|
21680
|
-
const manifest = readJson2(
|
|
21681
|
-
const pkg = readJson2(
|
|
22205
|
+
const manifest = readJson2(join36(targetDir, ".project.json"));
|
|
22206
|
+
const pkg = readJson2(join36(targetDir, "package.json"));
|
|
21682
22207
|
const name = String(manifest?.project_name ?? "").trim() || packageNameToProjectName(typeof pkg?.name === "string" ? pkg.name : void 0) || packageNameToProjectName(basename11(targetDir)) || "Project";
|
|
21683
22208
|
const ticketProvider = manifest?.ticket_provider && typeof manifest.ticket_provider === "object" ? manifest.ticket_provider : {};
|
|
21684
22209
|
return {
|
|
@@ -21753,7 +22278,7 @@ function actionNeedsRun(plan, kind, syncMode) {
|
|
|
21753
22278
|
if (!action || action.kind !== "project.write-manifest") return false;
|
|
21754
22279
|
const next = `${JSON.stringify(action.manifest, null, 2)}
|
|
21755
22280
|
`;
|
|
21756
|
-
return !
|
|
22281
|
+
return !existsSync26(action.path) || readFileSync31(action.path, "utf8") !== next;
|
|
21757
22282
|
}
|
|
21758
22283
|
if (kind === "copier.copy.commonproject") return true;
|
|
21759
22284
|
if (kind === "ticket-provider.create-or-link") return plan.actions.some((action) => action.kind === kind && action.enabled);
|
|
@@ -21810,7 +22335,7 @@ async function resolveProjectInitTarget(name, options) {
|
|
|
21810
22335
|
if (!targetDir && cwdGitRoot) {
|
|
21811
22336
|
targetDir = cwdGitRoot;
|
|
21812
22337
|
}
|
|
21813
|
-
if (!targetDir && !name &&
|
|
22338
|
+
if (!targetDir && !name && existsSync26(join36(cwd, ".project.json"))) {
|
|
21814
22339
|
targetDir = cwd;
|
|
21815
22340
|
}
|
|
21816
22341
|
if (!targetDir && interactive) {
|
|
@@ -21821,10 +22346,10 @@ async function resolveProjectInitTarget(name, options) {
|
|
|
21821
22346
|
if (!targetDir) {
|
|
21822
22347
|
throw new Error("Project name or --target-dir is required when project init is not run inside a git repo or a directory carrying .project.json");
|
|
21823
22348
|
}
|
|
21824
|
-
const targetExists =
|
|
22349
|
+
const targetExists = existsSync26(targetDir);
|
|
21825
22350
|
if (targetExists && !statSync9(targetDir).isDirectory()) throw new Error(`Target path is not a directory: ${targetDir}`);
|
|
21826
22351
|
const targetGitRoot = targetExists ? findGitRoot(targetDir) : void 0;
|
|
21827
|
-
const alreadyScaffolded = targetExists && (
|
|
22352
|
+
const alreadyScaffolded = targetExists && (existsSync26(join36(targetDir, ".project.json")) || existsSync26(join36(targetDir, ".copier-answers.yml")));
|
|
21828
22353
|
const syncMode = Boolean(
|
|
21829
22354
|
targetGitRoot && resolve25(targetGitRoot) === resolve25(targetDir) || alreadyScaffolded
|
|
21830
22355
|
);
|
|
@@ -22346,7 +22871,7 @@ program.command("reindex").argument("[project-id]", "Canonical project ID (defau
|
|
|
22346
22871
|
else if (options.all) result2 = registryRequest(location, "POST", "/v1/reindex", {});
|
|
22347
22872
|
else {
|
|
22348
22873
|
const repo = projectId ? getProject(loadProjectRegistry(location), projectId).repo_path : resolveProject(process.cwd()).root;
|
|
22349
|
-
result2 = registryRequest(location, "POST", "/v1/index", { manifest_path:
|
|
22874
|
+
result2 = registryRequest(location, "POST", "/v1/index", { manifest_path: join36(repo, ".project.json") });
|
|
22350
22875
|
}
|
|
22351
22876
|
const statuses = result2.__registry_status;
|
|
22352
22877
|
const stale = Object.entries(statuses ?? {}).filter(([, item]) => item.status !== "ok");
|