pi-async 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -0
- package/LICENSE +21 -0
- package/README.md +100 -2
- package/docs/CONTRACT.md +308 -0
- package/extensions/index.ts +138 -0
- package/package.json +55 -4
- package/src/core/bus.ts +36 -0
- package/src/core/duration.ts +16 -0
- package/src/core/env.ts +23 -0
- package/src/core/notify.ts +57 -0
- package/src/core/output.ts +52 -0
- package/src/core/registry.ts +543 -0
- package/src/core/spawn.ts +138 -0
- package/src/core/task.ts +102 -0
- package/src/core/watch.ts +308 -0
- package/src/guard/settle-guard.ts +75 -0
- package/src/tools/index.ts +131 -0
- package/src/tui/host.ts +30 -0
- package/src/tui/panel.ts +279 -0
- package/src/tui/rail.ts +206 -0
- package/src/tui/status.ts +63 -0
- package/src/tui/width.ts +53 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { PiCtx } from "../core/notify.ts";
|
|
2
|
+
import type { TaskRegistry } from "../core/registry.ts";
|
|
3
|
+
import { reportActivity } from "../core/bus.ts";
|
|
4
|
+
import { tuiHost } from "./host.ts";
|
|
5
|
+
|
|
6
|
+
const TERMINAL = new Set(["completed", "failed", "killed"]);
|
|
7
|
+
|
|
8
|
+
/** Footer: running excludes pending; pending segment only when > 0; done = all terminal. */
|
|
9
|
+
export function formatStatus(active: { running: number; pending: number }, done: number): string {
|
|
10
|
+
const pending = active.pending > 0 ? ` · ${active.pending} pending` : "";
|
|
11
|
+
return ` ${active.running} running${pending} · ${done} done `;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let clearedAt = 0;
|
|
15
|
+
let refreshCurrent: (() => void) | undefined;
|
|
16
|
+
let refreshInterval: ReturnType<typeof setInterval> | undefined;
|
|
17
|
+
|
|
18
|
+
/** Shared /async-clear watermark used by footer and panel (#31). */
|
|
19
|
+
export function clearedAtMs(): number {
|
|
20
|
+
return clearedAt;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Shared clear predicate: running/pending always visible; otherwise compare
|
|
25
|
+
* finishedAt ?? createdAt against the watermark (footer semantics).
|
|
26
|
+
*/
|
|
27
|
+
export function survivesClearCutoff(
|
|
28
|
+
task: { status: string; createdAt: number; finishedAt?: number },
|
|
29
|
+
cutoffMs: number = clearedAt,
|
|
30
|
+
): boolean {
|
|
31
|
+
if (task.status === "running" || task.status === "pending") return true;
|
|
32
|
+
return (task.finishedAt ?? task.createdAt) >= cutoffMs;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function disposeStatus(): void {
|
|
36
|
+
if (refreshInterval) clearInterval(refreshInterval);
|
|
37
|
+
refreshInterval = undefined;
|
|
38
|
+
refreshCurrent = undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function clearDoneStatus(): void {
|
|
42
|
+
clearedAt = Date.now();
|
|
43
|
+
refreshCurrent?.();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function initStatus(pi: PiCtx, registry: TaskRegistry): void {
|
|
47
|
+
disposeStatus();
|
|
48
|
+
// TUI surfaces bind from event contexts; the factory API has no mode/ui.
|
|
49
|
+
const bound = tuiHost();
|
|
50
|
+
if (!bound) return;
|
|
51
|
+
const setStatus = bound.ui["setStatus"] as ((key: string, text: string | undefined) => void) | undefined;
|
|
52
|
+
const refresh = (): void => {
|
|
53
|
+
const active = registry.activeCount();
|
|
54
|
+
const done = registry.list({ includeHistory: true }).filter((task) => TERMINAL.has(task.status) && survivesClearCutoff(task)).length;
|
|
55
|
+
setStatus?.("pi-async", formatStatus(active, done));
|
|
56
|
+
reportActivity(pi, active);
|
|
57
|
+
};
|
|
58
|
+
refreshCurrent = refresh;
|
|
59
|
+
refresh();
|
|
60
|
+
// Registry intentionally exposes no event subscription API; keep the footer current without replacing its core events.
|
|
61
|
+
refreshInterval = setInterval(refresh, 1_000);
|
|
62
|
+
refreshInterval.unref?.();
|
|
63
|
+
}
|
package/src/tui/width.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** East-Asian wide / fullwidth / common emoji ranges → 2 cells; else 1. */
|
|
2
|
+
function charWidth(ch: string): number {
|
|
3
|
+
const cp = ch.codePointAt(0)!;
|
|
4
|
+
const wide =
|
|
5
|
+
(cp >= 0x1100 && cp <= 0x115f) ||
|
|
6
|
+
(cp >= 0x2e80 && cp <= 0xa4cf) ||
|
|
7
|
+
(cp >= 0xac00 && cp <= 0xd7a3) ||
|
|
8
|
+
(cp >= 0xf900 && cp <= 0xfaff) ||
|
|
9
|
+
(cp >= 0xfe30 && cp <= 0xfe6f) ||
|
|
10
|
+
(cp >= 0xff00 && cp <= 0xff60) ||
|
|
11
|
+
(cp >= 0xffe0 && cp <= 0xffe6) ||
|
|
12
|
+
(cp >= 0x1f300 && cp <= 0x1f9ff);
|
|
13
|
+
return wide ? 2 : 1;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Terminal cell width of a string (CJK/emoji ≈ 2 cells). */
|
|
17
|
+
export function displayWidth(text: string): number {
|
|
18
|
+
let w = 0;
|
|
19
|
+
for (const ch of text) w += charWidth(ch);
|
|
20
|
+
return w;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Truncate to a cell budget, never splitting a wide char, appending `…`. */
|
|
24
|
+
export function truncateToWidth(text: string, limit: number): string {
|
|
25
|
+
const budget = Math.max(0, Math.floor(limit));
|
|
26
|
+
if (budget <= 0) return "";
|
|
27
|
+
if (displayWidth(text) <= budget) return text;
|
|
28
|
+
if (budget === 1) return "…";
|
|
29
|
+
let out = "";
|
|
30
|
+
let used = 0;
|
|
31
|
+
for (const ch of text) {
|
|
32
|
+
const cw = charWidth(ch);
|
|
33
|
+
if (used + cw > budget - 1) break;
|
|
34
|
+
out += ch;
|
|
35
|
+
used += cw;
|
|
36
|
+
}
|
|
37
|
+
return `${out}…`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Hard-truncate by cells (no ellipsis) then pad with spaces to exact cell width. */
|
|
41
|
+
export function fitWidth(text: string, limit: number): string {
|
|
42
|
+
const budget = Math.max(0, Math.floor(limit));
|
|
43
|
+
if (budget <= 0) return "";
|
|
44
|
+
let out = "";
|
|
45
|
+
let used = 0;
|
|
46
|
+
for (const ch of text) {
|
|
47
|
+
const cw = charWidth(ch);
|
|
48
|
+
if (used + cw > budget) break;
|
|
49
|
+
out += ch;
|
|
50
|
+
used += cw;
|
|
51
|
+
}
|
|
52
|
+
return out + " ".repeat(budget - used);
|
|
53
|
+
}
|