glm-coding-router 1.1.2 → 2.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/LICENSE +21 -21
- package/README.md +542 -426
- package/dist/bin/glm-review.js +28 -3
- package/dist/bin/glm-worker.js +30 -4
- package/dist/budget/estimator.js +218 -0
- package/dist/budget/manager.js +223 -0
- package/dist/cli.js +46 -3
- package/dist/commands/dashboard.js +348 -0
- package/dist/commands/doctor-auth.js +107 -0
- package/dist/commands/doctor-command.js +171 -41
- package/dist/commands/landing.js +47 -0
- package/dist/commands/runs.js +568 -0
- package/dist/commands/status.js +28 -15
- package/dist/commands/usage.js +34 -58
- package/dist/commands/watch.js +289 -0
- package/dist/core/config.js +61 -0
- package/dist/core/errors.js +24 -0
- package/dist/core/key-inspector.js +45 -0
- package/dist/core/paths.js +32 -0
- package/dist/core/process.js +83 -0
- package/dist/core/prompt.js +18 -5
- package/dist/core/routing-flags.js +59 -0
- package/dist/core/user-env.js +17 -7
- package/dist/core/zai-quota.js +148 -0
- package/dist/events/bus.js +64 -0
- package/dist/events/claude-adapter.js +416 -0
- package/dist/events/types.js +9 -0
- package/dist/handoff/bundle.js +203 -0
- package/dist/handoff/parent-handoff.js +48 -0
- package/dist/mcp/server.js +45 -1
- package/dist/routing/glm-routing.js +131 -0
- package/dist/runs/checkpoint.js +204 -0
- package/dist/runs/drain.js +165 -0
- package/dist/runs/heartbeat.js +45 -0
- package/dist/runs/registry.js +350 -0
- package/dist/runs/store.js +186 -0
- package/dist/runs/ulid.js +112 -0
- package/dist/runs/worker-run.js +672 -0
- package/dist/templates/agents-block.js +53 -44
- package/dist/templates/claude-block.js +56 -47
- package/dist/templates/glm-delegation-skill.js +76 -65
- package/dist/tui/command-ui.js +158 -0
- package/dist/tui/progress.js +338 -0
- package/dist/tui/render.js +144 -0
- package/package.json +1 -1
|
@@ -1,46 +1,55 @@
|
|
|
1
1
|
/** Managed block content for AGENTS.md (spec §24). Keep in sync with the spec. */
|
|
2
|
-
export const AGENTS_MANAGED_BLOCK = `<!-- glm-coding-router:start -->
|
|
3
|
-
|
|
4
|
-
## GLM Worker Delegation
|
|
5
|
-
|
|
6
|
-
Available commands:
|
|
7
|
-
|
|
8
|
-
- \`glm-worker "<task>"\`
|
|
9
|
-
- \`glm-review "<task>"\`
|
|
10
|
-
|
|
11
|
-
Codex is the primary orchestrator.
|
|
12
|
-
|
|
13
|
-
Delegate:
|
|
14
|
-
- CRUD
|
|
15
|
-
- boilerplate
|
|
16
|
-
- tests
|
|
17
|
-
- documentation
|
|
18
|
-
- mechanical refactoring
|
|
19
|
-
- repository exploration
|
|
20
|
-
- straightforward implementation
|
|
21
|
-
|
|
22
|
-
Keep in Codex:
|
|
23
|
-
- requirements
|
|
24
|
-
- planning
|
|
25
|
-
- architecture
|
|
26
|
-
- ambiguous business logic
|
|
27
|
-
- complex debugging
|
|
28
|
-
- security decisions
|
|
29
|
-
- integration
|
|
30
|
-
- final review
|
|
31
|
-
|
|
32
|
-
Before delegation create a task packet:
|
|
33
|
-
|
|
34
|
-
TASK
|
|
35
|
-
SCOPE
|
|
36
|
-
FILES ALLOWED TO MODIFY
|
|
37
|
-
FILES NOT TO MODIFY
|
|
38
|
-
REQUIREMENTS
|
|
39
|
-
CONSTRAINTS
|
|
40
|
-
ACCEPTANCE CRITERIA
|
|
41
|
-
VALIDATION
|
|
42
|
-
EXPECTED OUTPUT
|
|
43
|
-
|
|
44
|
-
Never trust a worker's success report without inspecting the resulting changes.
|
|
45
|
-
|
|
2
|
+
export const AGENTS_MANAGED_BLOCK = `<!-- glm-coding-router:start -->
|
|
3
|
+
|
|
4
|
+
## GLM Worker Delegation
|
|
5
|
+
|
|
6
|
+
Available commands:
|
|
7
|
+
|
|
8
|
+
- \`glm-worker "<task>"\`
|
|
9
|
+
- \`glm-review "<task>"\`
|
|
10
|
+
|
|
11
|
+
Codex is the primary orchestrator.
|
|
12
|
+
|
|
13
|
+
Delegate:
|
|
14
|
+
- CRUD
|
|
15
|
+
- boilerplate
|
|
16
|
+
- tests
|
|
17
|
+
- documentation
|
|
18
|
+
- mechanical refactoring
|
|
19
|
+
- repository exploration
|
|
20
|
+
- straightforward implementation
|
|
21
|
+
|
|
22
|
+
Keep in Codex:
|
|
23
|
+
- requirements
|
|
24
|
+
- planning
|
|
25
|
+
- architecture
|
|
26
|
+
- ambiguous business logic
|
|
27
|
+
- complex debugging
|
|
28
|
+
- security decisions
|
|
29
|
+
- integration
|
|
30
|
+
- final review
|
|
31
|
+
|
|
32
|
+
Before delegation create a task packet:
|
|
33
|
+
|
|
34
|
+
TASK
|
|
35
|
+
SCOPE
|
|
36
|
+
FILES ALLOWED TO MODIFY
|
|
37
|
+
FILES NOT TO MODIFY
|
|
38
|
+
REQUIREMENTS
|
|
39
|
+
CONSTRAINTS
|
|
40
|
+
ACCEPTANCE CRITERIA
|
|
41
|
+
VALIDATION
|
|
42
|
+
EXPECTED OUTPUT
|
|
43
|
+
|
|
44
|
+
Never trust a worker's success report without inspecting the resulting changes.
|
|
45
|
+
|
|
46
|
+
Worker exit codes 41 and 42 are NOT crashes:
|
|
47
|
+
|
|
48
|
+
- stdout carries one JSON object: \`{"status":"handoff_required", ...}\`
|
|
49
|
+
- read \`handoff_path\` — a handoff.md with what was done, what remains, files
|
|
50
|
+
changed, and untracked files that are NOT in diff.patch
|
|
51
|
+
- continue the task yourself in the SAME worktree, starting from "Remaining"
|
|
52
|
+
- do not re-run the worker until quota resets
|
|
53
|
+
- 41 means nothing was spawned; 42 means work was done and is preserved
|
|
54
|
+
|
|
46
55
|
<!-- glm-coding-router:end -->`;
|
|
@@ -1,49 +1,58 @@
|
|
|
1
1
|
/** Managed block content for CLAUDE.md (spec §20). Keep in sync with the spec. */
|
|
2
|
-
export const CLAUDE_MANAGED_BLOCK = `<!-- glm-coding-router:start -->
|
|
3
|
-
|
|
4
|
-
## GLM Worker Delegation
|
|
5
|
-
|
|
6
|
-
GLM workers available:
|
|
7
|
-
|
|
8
|
-
- \`glm-worker "<task>"\`
|
|
9
|
-
- \`glm-review "<task>"\`
|
|
10
|
-
|
|
11
|
-
Delegate well-scoped, implementation-heavy work to GLM.
|
|
12
|
-
|
|
13
|
-
Use GLM for:
|
|
14
|
-
- repository exploration
|
|
15
|
-
- CRUD
|
|
16
|
-
- boilerplate
|
|
17
|
-
- tests
|
|
18
|
-
- documentation
|
|
19
|
-
- mechanical refactoring
|
|
20
|
-
- straightforward implementation
|
|
21
|
-
|
|
22
|
-
Claude remains responsible for:
|
|
23
|
-
- requirements
|
|
24
|
-
- architecture
|
|
25
|
-
- ambiguous business rules
|
|
26
|
-
- security-sensitive decisions
|
|
27
|
-
- complex debugging
|
|
28
|
-
- integration
|
|
29
|
-
- final review
|
|
30
|
-
|
|
31
|
-
Before delegation, define:
|
|
32
|
-
- task
|
|
33
|
-
- scope
|
|
34
|
-
- allowed files
|
|
35
|
-
- forbidden files
|
|
36
|
-
- requirements
|
|
37
|
-
- constraints
|
|
38
|
-
- acceptance criteria
|
|
39
|
-
- validation command
|
|
40
|
-
- expected output
|
|
41
|
-
|
|
42
|
-
After worker completion:
|
|
43
|
-
1. inspect the actual diff
|
|
44
|
-
2. validate against requirements
|
|
45
|
-
3. run relevant tests
|
|
46
|
-
4. resolve integration problems
|
|
47
|
-
5. accept only after verification
|
|
48
|
-
|
|
2
|
+
export const CLAUDE_MANAGED_BLOCK = `<!-- glm-coding-router:start -->
|
|
3
|
+
|
|
4
|
+
## GLM Worker Delegation
|
|
5
|
+
|
|
6
|
+
GLM workers available:
|
|
7
|
+
|
|
8
|
+
- \`glm-worker "<task>"\`
|
|
9
|
+
- \`glm-review "<task>"\`
|
|
10
|
+
|
|
11
|
+
Delegate well-scoped, implementation-heavy work to GLM.
|
|
12
|
+
|
|
13
|
+
Use GLM for:
|
|
14
|
+
- repository exploration
|
|
15
|
+
- CRUD
|
|
16
|
+
- boilerplate
|
|
17
|
+
- tests
|
|
18
|
+
- documentation
|
|
19
|
+
- mechanical refactoring
|
|
20
|
+
- straightforward implementation
|
|
21
|
+
|
|
22
|
+
Claude remains responsible for:
|
|
23
|
+
- requirements
|
|
24
|
+
- architecture
|
|
25
|
+
- ambiguous business rules
|
|
26
|
+
- security-sensitive decisions
|
|
27
|
+
- complex debugging
|
|
28
|
+
- integration
|
|
29
|
+
- final review
|
|
30
|
+
|
|
31
|
+
Before delegation, define:
|
|
32
|
+
- task
|
|
33
|
+
- scope
|
|
34
|
+
- allowed files
|
|
35
|
+
- forbidden files
|
|
36
|
+
- requirements
|
|
37
|
+
- constraints
|
|
38
|
+
- acceptance criteria
|
|
39
|
+
- validation command
|
|
40
|
+
- expected output
|
|
41
|
+
|
|
42
|
+
After worker completion:
|
|
43
|
+
1. inspect the actual diff
|
|
44
|
+
2. validate against requirements
|
|
45
|
+
3. run relevant tests
|
|
46
|
+
4. resolve integration problems
|
|
47
|
+
5. accept only after verification
|
|
48
|
+
|
|
49
|
+
Worker exit codes 41 and 42 are NOT crashes:
|
|
50
|
+
|
|
51
|
+
- stdout carries one JSON object: \`{"status":"handoff_required", ...}\`
|
|
52
|
+
- read \`handoff_path\` — a handoff.md with what was done, what remains, files
|
|
53
|
+
changed, and untracked files that are NOT in diff.patch
|
|
54
|
+
- continue the task yourself in the SAME worktree, starting from "Remaining"
|
|
55
|
+
- do not re-run the worker until quota resets
|
|
56
|
+
- 41 means nothing was spawned; 42 means work was done and is preserved
|
|
57
|
+
|
|
49
58
|
<!-- glm-coding-router:end -->`;
|
|
@@ -1,68 +1,79 @@
|
|
|
1
1
|
/** Codex skill definition (spec §26). Keep in sync with the spec. */
|
|
2
2
|
export const GLM_DELEGATION_SKILL_NAME = "glm-delegation";
|
|
3
|
-
export const GLM_DELEGATION_SKILL_MD = `---
|
|
4
|
-
name: glm-delegation
|
|
5
|
-
description: >
|
|
6
|
-
Delegate well-scoped implementation, testing,
|
|
7
|
-
repository exploration, boilerplate, CRUD,
|
|
8
|
-
documentation, and mechanical refactoring to
|
|
9
|
-
GLM Coding Plan workers.
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
# GLM Delegation
|
|
13
|
-
|
|
14
|
-
Available commands:
|
|
15
|
-
|
|
16
|
-
glm-worker "<task>"
|
|
17
|
-
glm-review "<task>"
|
|
18
|
-
|
|
19
|
-
## Use glm-review for
|
|
20
|
-
|
|
21
|
-
- repository exploration
|
|
22
|
-
- dependency analysis
|
|
23
|
-
- locating implementations
|
|
24
|
-
- call-chain discovery
|
|
25
|
-
- code review
|
|
26
|
-
|
|
27
|
-
## Use glm-worker for
|
|
28
|
-
|
|
29
|
-
- CRUD
|
|
30
|
-
- unit tests
|
|
31
|
-
- implementation
|
|
32
|
-
- documentation
|
|
33
|
-
- repetitive changes
|
|
34
|
-
- mechanical refactoring
|
|
35
|
-
|
|
36
|
-
## Keep in primary Codex agent
|
|
37
|
-
|
|
38
|
-
- requirements
|
|
39
|
-
- architecture
|
|
40
|
-
- ambiguous rules
|
|
41
|
-
- security-sensitive design
|
|
42
|
-
- difficult debugging
|
|
43
|
-
- integration
|
|
44
|
-
- final acceptance
|
|
45
|
-
|
|
46
|
-
## Delegation packet
|
|
47
|
-
|
|
48
|
-
Always provide:
|
|
49
|
-
|
|
50
|
-
TASK
|
|
51
|
-
SCOPE
|
|
52
|
-
ALLOWED FILES
|
|
53
|
-
FORBIDDEN FILES
|
|
54
|
-
REQUIREMENTS
|
|
55
|
-
CONSTRAINTS
|
|
56
|
-
ACCEPTANCE CRITERIA
|
|
57
|
-
VALIDATION
|
|
58
|
-
EXPECTED OUTPUT
|
|
59
|
-
|
|
60
|
-
## Verification
|
|
61
|
-
|
|
62
|
-
After GLM finishes:
|
|
63
|
-
|
|
64
|
-
- inspect the actual diff
|
|
65
|
-
- independently run relevant validation
|
|
66
|
-
- compare implementation with requirements
|
|
67
|
-
- reject or correct worker output when needed
|
|
3
|
+
export const GLM_DELEGATION_SKILL_MD = `---
|
|
4
|
+
name: glm-delegation
|
|
5
|
+
description: >
|
|
6
|
+
Delegate well-scoped implementation, testing,
|
|
7
|
+
repository exploration, boilerplate, CRUD,
|
|
8
|
+
documentation, and mechanical refactoring to
|
|
9
|
+
GLM Coding Plan workers.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# GLM Delegation
|
|
13
|
+
|
|
14
|
+
Available commands:
|
|
15
|
+
|
|
16
|
+
glm-worker "<task>"
|
|
17
|
+
glm-review "<task>"
|
|
18
|
+
|
|
19
|
+
## Use glm-review for
|
|
20
|
+
|
|
21
|
+
- repository exploration
|
|
22
|
+
- dependency analysis
|
|
23
|
+
- locating implementations
|
|
24
|
+
- call-chain discovery
|
|
25
|
+
- code review
|
|
26
|
+
|
|
27
|
+
## Use glm-worker for
|
|
28
|
+
|
|
29
|
+
- CRUD
|
|
30
|
+
- unit tests
|
|
31
|
+
- implementation
|
|
32
|
+
- documentation
|
|
33
|
+
- repetitive changes
|
|
34
|
+
- mechanical refactoring
|
|
35
|
+
|
|
36
|
+
## Keep in primary Codex agent
|
|
37
|
+
|
|
38
|
+
- requirements
|
|
39
|
+
- architecture
|
|
40
|
+
- ambiguous rules
|
|
41
|
+
- security-sensitive design
|
|
42
|
+
- difficult debugging
|
|
43
|
+
- integration
|
|
44
|
+
- final acceptance
|
|
45
|
+
|
|
46
|
+
## Delegation packet
|
|
47
|
+
|
|
48
|
+
Always provide:
|
|
49
|
+
|
|
50
|
+
TASK
|
|
51
|
+
SCOPE
|
|
52
|
+
ALLOWED FILES
|
|
53
|
+
FORBIDDEN FILES
|
|
54
|
+
REQUIREMENTS
|
|
55
|
+
CONSTRAINTS
|
|
56
|
+
ACCEPTANCE CRITERIA
|
|
57
|
+
VALIDATION
|
|
58
|
+
EXPECTED OUTPUT
|
|
59
|
+
|
|
60
|
+
## Verification
|
|
61
|
+
|
|
62
|
+
After GLM finishes:
|
|
63
|
+
|
|
64
|
+
- inspect the actual diff
|
|
65
|
+
- independently run relevant validation
|
|
66
|
+
- compare implementation with requirements
|
|
67
|
+
- reject or correct worker output when needed
|
|
68
|
+
|
|
69
|
+
## Exit codes 41 and 42 are not crashes
|
|
70
|
+
|
|
71
|
+
A worker may stop because the GLM quota ran low rather than because it broke.
|
|
72
|
+
|
|
73
|
+
- stdout carries one JSON object: \`{"status":"handoff_required", ...}\`
|
|
74
|
+
- read \`handoff_path\` — a handoff.md with what was done, what remains, files
|
|
75
|
+
changed, and untracked files that are NOT in diff.patch
|
|
76
|
+
- continue the task yourself in the SAME worktree, starting from "Remaining"
|
|
77
|
+
- do not re-run the worker until quota resets
|
|
78
|
+
- 41 means nothing was spawned; 42 means work was done and is preserved
|
|
68
79
|
`;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared presentation layer for the management screens — `doctor`, `status`,
|
|
3
|
+
* `usage`, and the root landing page (specs/terminal-ui-doctor.md §A). This
|
|
4
|
+
* module never fetches data, reads credentials, or makes routing decisions;
|
|
5
|
+
* it only turns already-computed values into aligned, width-aware text.
|
|
6
|
+
*
|
|
7
|
+
* All ANSI stays in render.ts (`paint`); this file composes plain strings
|
|
8
|
+
* and colors them through that one choke point.
|
|
9
|
+
*/
|
|
10
|
+
import { displayWidth, padEndDisplay, paint, truncate } from "./render.js";
|
|
11
|
+
const STATUS_TAG = {
|
|
12
|
+
ok: "[OK]",
|
|
13
|
+
warn: "[WARN]",
|
|
14
|
+
fail: "[FAIL]",
|
|
15
|
+
info: "[INFO]",
|
|
16
|
+
};
|
|
17
|
+
const STATUS_STYLE = {
|
|
18
|
+
ok: "green",
|
|
19
|
+
warn: "yellow",
|
|
20
|
+
fail: "red",
|
|
21
|
+
info: "cyan",
|
|
22
|
+
};
|
|
23
|
+
/** Widest tag ("[WARN]"/"[FAIL]"/"[INFO]") plus one separating space. */
|
|
24
|
+
const TAG_COLUMN = 7;
|
|
25
|
+
const BASE_INDENT = " ";
|
|
26
|
+
const LABEL_COLUMN = 24;
|
|
27
|
+
const MIN_BOX_WIDTH = 44;
|
|
28
|
+
const MAX_BOX_WIDTH = 78;
|
|
29
|
+
const BAR_SLOTS = 20;
|
|
30
|
+
function effectiveWidth(writer) {
|
|
31
|
+
return Number.isFinite(writer.columns) && writer.columns > 0 ? Math.floor(writer.columns) : 80;
|
|
32
|
+
}
|
|
33
|
+
/** Strip control characters from text that did not originate in this module (spec §A). */
|
|
34
|
+
function sanitize(text, allowNewline) {
|
|
35
|
+
const pattern = allowNewline ? /[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/g : /[\x00-\x1F\x7F]/g;
|
|
36
|
+
return text.replace(pattern, "");
|
|
37
|
+
}
|
|
38
|
+
function border(width) {
|
|
39
|
+
return `+${"-".repeat(Math.max(width - 2, 0))}+`;
|
|
40
|
+
}
|
|
41
|
+
function boxLine(text, width) {
|
|
42
|
+
const inner = Math.max(width - 4, 0);
|
|
43
|
+
return `| ${padEndDisplay(truncate(text, inner), inner)} |`;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Wrap `text` to `width`-wide lines (measured in display columns, not code
|
|
47
|
+
* units — spec §A "Width uses display cells") without breaking a word
|
|
48
|
+
* mid-way when avoidable.
|
|
49
|
+
*/
|
|
50
|
+
function wrap(text, width) {
|
|
51
|
+
if (width <= 0)
|
|
52
|
+
return [text];
|
|
53
|
+
const words = text.split(/\s+/).filter((w) => w.length > 0);
|
|
54
|
+
const lines = [];
|
|
55
|
+
let current = "";
|
|
56
|
+
for (const word of words) {
|
|
57
|
+
const candidate = current.length === 0 ? word : `${current} ${word}`;
|
|
58
|
+
if (displayWidth(candidate) > width && current.length > 0) {
|
|
59
|
+
lines.push(current);
|
|
60
|
+
current = word;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
current = candidate;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (current.length > 0)
|
|
67
|
+
lines.push(current);
|
|
68
|
+
return lines.length > 0 ? lines : [""];
|
|
69
|
+
}
|
|
70
|
+
export function createCommandUi(writer, options = {}) {
|
|
71
|
+
const quiet = options.quiet ?? false;
|
|
72
|
+
return {
|
|
73
|
+
header(title, subtitle) {
|
|
74
|
+
if (quiet)
|
|
75
|
+
return "";
|
|
76
|
+
const width = effectiveWidth(writer);
|
|
77
|
+
const cleanTitle = sanitize(title, false);
|
|
78
|
+
const cleanSubtitle = subtitle !== undefined ? sanitize(subtitle, false) : undefined;
|
|
79
|
+
if (width < MIN_BOX_WIDTH) {
|
|
80
|
+
const lines = [paint(writer, "cyan", truncate(cleanTitle, width))];
|
|
81
|
+
if (cleanSubtitle)
|
|
82
|
+
lines.push(paint(writer, "dim", truncate(cleanSubtitle, width)));
|
|
83
|
+
return lines.join("\n");
|
|
84
|
+
}
|
|
85
|
+
const boxWidth = Math.min(width, MAX_BOX_WIDTH);
|
|
86
|
+
const lines = [border(boxWidth), boxLine(cleanTitle, boxWidth)];
|
|
87
|
+
if (cleanSubtitle)
|
|
88
|
+
lines.push(boxLine(cleanSubtitle, boxWidth));
|
|
89
|
+
lines.push(border(boxWidth));
|
|
90
|
+
return lines.join("\n");
|
|
91
|
+
},
|
|
92
|
+
section(title) {
|
|
93
|
+
return paint(writer, "bold", sanitize(title, false));
|
|
94
|
+
},
|
|
95
|
+
row(label, value, status) {
|
|
96
|
+
const width = effectiveWidth(writer);
|
|
97
|
+
const cleanLabel = sanitize(label, false);
|
|
98
|
+
const cleanValue = sanitize(value, false);
|
|
99
|
+
const tagField = status !== undefined ? STATUS_TAG[status].padEnd(TAG_COLUMN) : "";
|
|
100
|
+
const labelField = padEndDisplay(cleanLabel, LABEL_COLUMN);
|
|
101
|
+
const plainPrefix = `${BASE_INDENT}${tagField}${labelField}`;
|
|
102
|
+
const singleLine = `${plainPrefix}${cleanValue}`;
|
|
103
|
+
const coloredPrefix = () => {
|
|
104
|
+
if (status === undefined)
|
|
105
|
+
return plainPrefix;
|
|
106
|
+
const tag = STATUS_TAG[status];
|
|
107
|
+
return `${BASE_INDENT}${paint(writer, STATUS_STYLE[status], tag)}${tagField.slice(tag.length)}${labelField}`;
|
|
108
|
+
};
|
|
109
|
+
if (cleanValue.length === 0 || displayWidth(singleLine.replace(/\s+$/, "")) <= width) {
|
|
110
|
+
return `${coloredPrefix()}${cleanValue}`.replace(/\s+$/, "");
|
|
111
|
+
}
|
|
112
|
+
// Stack: tag + label on one line, value wrapped and indented beneath.
|
|
113
|
+
const tagLine = status !== undefined
|
|
114
|
+
? `${BASE_INDENT}${paint(writer, STATUS_STYLE[status], STATUS_TAG[status])} ${cleanLabel}`
|
|
115
|
+
: `${BASE_INDENT}${cleanLabel}`;
|
|
116
|
+
const valueIndent = `${BASE_INDENT}${" ".repeat(TAG_COLUMN)}`;
|
|
117
|
+
const valueWidth = Math.max(width - valueIndent.length, 8);
|
|
118
|
+
const valueLines = wrap(cleanValue, valueWidth).map((line) => `${valueIndent}${line}`);
|
|
119
|
+
return [tagLine, ...valueLines].join("\n");
|
|
120
|
+
},
|
|
121
|
+
detail(text) {
|
|
122
|
+
const width = effectiveWidth(writer);
|
|
123
|
+
const indent = `${BASE_INDENT}${" ".repeat(TAG_COLUMN - 2)}`;
|
|
124
|
+
const availableWidth = Math.max(width - indent.length, 8);
|
|
125
|
+
const cleanText = sanitize(text, true);
|
|
126
|
+
const lines = [];
|
|
127
|
+
for (const paragraph of cleanText.split("\n")) {
|
|
128
|
+
if (paragraph.length === 0) {
|
|
129
|
+
lines.push("");
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
for (const line of wrap(paragraph, availableWidth)) {
|
|
133
|
+
lines.push(`${indent}${line}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return lines.join("\n");
|
|
137
|
+
},
|
|
138
|
+
footer(text) {
|
|
139
|
+
if (quiet)
|
|
140
|
+
return "";
|
|
141
|
+
const cleanText = sanitize(text, true);
|
|
142
|
+
return cleanText
|
|
143
|
+
.split("\n")
|
|
144
|
+
.map((line) => (line.length === 0 ? "" : `${BASE_INDENT}${line}`))
|
|
145
|
+
.join("\n");
|
|
146
|
+
},
|
|
147
|
+
bar(percent) {
|
|
148
|
+
if (percent === undefined || !Number.isFinite(percent)) {
|
|
149
|
+
return `[${"-".repeat(BAR_SLOTS)}] unknown`;
|
|
150
|
+
}
|
|
151
|
+
const clamped = Math.min(100, Math.max(0, percent));
|
|
152
|
+
const filled = Math.round((clamped / 100) * BAR_SLOTS);
|
|
153
|
+
const bar = `[${"#".repeat(filled)}${"-".repeat(BAR_SLOTS - filled)}]`;
|
|
154
|
+
const style = clamped >= 90 ? "red" : clamped >= 70 ? "yellow" : "green";
|
|
155
|
+
return `${paint(writer, style, bar)} ${Math.round(clamped)}% used`;
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|