@gavin-hu/my-pi-agent 0.14.0 → 0.15.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 +55 -33
- package/README.zh-CN.md +44 -27
- package/extensions/bytefly/README.md +193 -140
- package/extensions/bytefly/block/component.ts +7 -15
- package/extensions/bytefly/block/register.ts +6 -1
- package/extensions/bytefly/doctor/register.ts +24 -5
- package/extensions/bytefly/doctor/tools.ts +4 -0
- package/extensions/bytefly/footer/collect/snapshot.ts +6 -1
- package/extensions/bytefly/footer/config.ts +10 -6
- package/extensions/bytefly/footer/price/price.ts +5 -3
- package/extensions/bytefly/footer/price/rates.ts +63 -26
- package/extensions/bytefly/footer/render/status.ts +3 -3
- package/extensions/bytefly/header/component.ts +3 -2
- package/extensions/bytefly/index.ts +3 -3
- package/extensions/bytefly/notice/component.ts +8 -3
- package/extensions/bytefly/notify/config.ts +6 -11
- package/extensions/bytefly/notify/dispatch.ts +13 -5
- package/extensions/bytefly/panel/commands.ts +21 -1
- package/extensions/bytefly/panel/format.ts +5 -2
- package/extensions/bytefly/panel/settings-runtime.ts +26 -6
- package/extensions/bytefly/panel/settings.ts +41 -4
- package/extensions/bytefly/panel/themes.ts +32 -0
- package/extensions/bytefly/panel/tui/editing.ts +77 -0
- package/extensions/bytefly/panel/tui/entries.ts +24 -0
- package/extensions/bytefly/panel/tui/extensions-view.ts +13 -8
- package/extensions/bytefly/panel/tui/frame.ts +121 -4
- package/extensions/bytefly/panel/tui/settings-view.ts +54 -15
- package/extensions/bytefly/panel/tui.ts +94 -152
- package/extensions/bytefly/picker/discovery.ts +1 -0
- package/extensions/bytefly/picker/types.ts +2 -0
- package/extensions/bytefly/separator/component.ts +14 -4
- package/extensions/bytefly/update/cache.ts +58 -19
- package/extensions/bytefly/update/check.ts +35 -0
- package/extensions/bytefly/update/format.ts +76 -35
- package/extensions/bytefly/update/register.ts +144 -55
- package/extensions/bytefly/update/state.ts +4 -2
- package/extensions/bytefly/update/suppress.ts +66 -0
- package/extensions/bytefly/update/types.ts +15 -0
- package/extensions/check/README.md +253 -0
- package/extensions/check/commands.ts +128 -0
- package/extensions/check/config.ts +39 -0
- package/extensions/check/detect.ts +269 -0
- package/extensions/check/failures.ts +230 -0
- package/extensions/check/format.ts +135 -0
- package/extensions/check/index.ts +26 -0
- package/extensions/check/render.ts +185 -0
- package/extensions/check/runner.ts +176 -0
- package/extensions/check/runtime.ts +244 -0
- package/extensions/check/source.ts +80 -0
- package/extensions/check/tools.ts +173 -0
- package/extensions/check/tui.ts +195 -0
- package/extensions/check/types.ts +99 -0
- package/extensions/document/README.md +1 -1
- package/extensions/document/extract/pdf.ts +8 -16
- package/extensions/document/formats.ts +2 -2
- package/extensions/file-browse/README.md +66 -32
- package/extensions/file-browse/client.ts +95 -32
- package/extensions/file-browse/config.ts +9 -9
- package/extensions/file-browse/git.ts +37 -47
- package/extensions/file-browse/html.ts +39 -58
- package/extensions/file-browse/markdown.ts +110 -92
- package/extensions/file-browse/raw-html.ts +478 -0
- package/extensions/file-browse/router.ts +32 -70
- package/extensions/file-browse/server.ts +18 -56
- package/extensions/file-browse/urls.ts +80 -0
- package/extensions/goal/README.md +103 -47
- package/extensions/goal/clarify-parse.ts +4 -9
- package/extensions/goal/clarify.ts +27 -4
- package/extensions/goal/commands.ts +24 -11
- package/extensions/goal/format.ts +41 -10
- package/extensions/goal/index.ts +39 -16
- package/extensions/goal/plan-route.ts +51 -0
- package/extensions/goal/schema.ts +6 -23
- package/extensions/goal/state.ts +39 -57
- package/extensions/goal/tools.ts +26 -20
- package/extensions/goal/types.ts +11 -1
- package/extensions/job/README.md +20 -6
- package/extensions/job/format.ts +18 -68
- package/extensions/job/index.ts +26 -41
- package/extensions/job/log-writer.ts +208 -0
- package/extensions/job/logs.ts +3 -2
- package/extensions/job/process.ts +6 -99
- package/extensions/job/runtime.ts +42 -181
- package/extensions/job/schema.ts +1 -4
- package/extensions/job/tools.ts +1 -15
- package/extensions/job/tui.ts +117 -307
- package/extensions/job/ui.ts +7 -33
- package/extensions/memory/README.md +1 -1
- package/extensions/memory/errors.ts +5 -6
- package/extensions/memory/tui.ts +14 -24
- package/extensions/plan/README.md +94 -18
- package/extensions/plan/commands.ts +9 -5
- package/extensions/plan/format.ts +87 -0
- package/extensions/plan/index.ts +31 -15
- package/extensions/plan/plans.ts +2 -3
- package/extensions/plan/policy.ts +0 -6
- package/extensions/plan/runtime.ts +4 -2
- package/extensions/plan/steps.ts +37 -8
- package/extensions/plan/todos.ts +44 -3
- package/extensions/plan/tools/enter.ts +94 -0
- package/extensions/plan/tools/exit.ts +296 -0
- package/extensions/plan/tools/nested.ts +21 -0
- package/extensions/plan/tools/steps.ts +136 -0
- package/extensions/plan/tools/todo-seed.ts +76 -0
- package/extensions/plan/tools/write.ts +127 -0
- package/extensions/plan/tools.ts +18 -703
- package/extensions/plan/web-client.ts +6 -22
- package/extensions/plan/web-page.ts +19 -30
- package/extensions/plan/web-router.ts +34 -71
- package/extensions/plan/web.ts +13 -40
- package/extensions/question/answers.ts +3 -8
- package/extensions/question/tui.ts +6 -6
- package/extensions/rewind/README.md +95 -22
- package/extensions/rewind/checklist.ts +185 -0
- package/extensions/rewind/commands.ts +8 -0
- package/extensions/rewind/config.ts +3 -11
- package/extensions/rewind/format.ts +220 -2
- package/extensions/rewind/git.ts +74 -7
- package/extensions/rewind/hunk-picker.ts +114 -0
- package/extensions/rewind/patch.ts +88 -0
- package/extensions/rewind/path-picker.ts +67 -100
- package/extensions/rewind/paths.ts +93 -19
- package/extensions/rewind/restore.ts +53 -10
- package/extensions/rewind/review-tui.ts +320 -0
- package/extensions/rewind/review.ts +397 -0
- package/extensions/rewind/rewind-tui.ts +4 -3
- package/extensions/rewind/runtime.ts +94 -17
- package/extensions/subagent/README.md +61 -6
- package/extensions/subagent/background-tui.ts +208 -0
- package/extensions/subagent/background.ts +470 -0
- package/extensions/subagent/commands.ts +80 -0
- package/extensions/subagent/config.ts +4 -0
- package/extensions/subagent/format.ts +24 -1
- package/extensions/subagent/index.ts +91 -2
- package/extensions/subagent/render.ts +13 -0
- package/extensions/subagent/run.ts +17 -16
- package/extensions/subagent/schema.ts +12 -0
- package/extensions/subagent/types.ts +11 -0
- package/extensions/subagent/ui.ts +50 -0
- package/extensions/todo/README.md +18 -13
- package/extensions/todo/format.ts +3 -2
- package/extensions/todo/runtime.ts +3 -2
- package/extensions/todo/schema.ts +8 -84
- package/extensions/todo/state.ts +5 -46
- package/extensions/todo/tools.ts +2 -1
- package/extensions/todo/types.ts +6 -15
- package/extensions/watch/README.md +14 -6
- package/extensions/watch/child.ts +150 -0
- package/extensions/watch/format.ts +8 -48
- package/extensions/watch/index.ts +42 -58
- package/extensions/watch/runtime.ts +49 -128
- package/extensions/watch/schema.ts +1 -4
- package/extensions/watch/tools.ts +1 -11
- package/extensions/watch/tui.ts +103 -273
- package/extensions/watch/ui.ts +5 -32
- package/extensions/web-access/README.md +1 -1
- package/extensions/web-access/fetch/page.ts +1 -1
- package/extensions/web-access/fetch/tool.ts +6 -2
- package/extensions/web-access/search/search.ts +2 -5
- package/extensions/wechat/README.md +1 -1
- package/extensions/wechat/client.ts +23 -0
- package/extensions/wechat/commands.ts +57 -42
- package/extensions/wechat/deliver.ts +5 -9
- package/extensions/wechat/errors.ts +3 -6
- package/extensions/wechat/inbound.ts +2 -5
- package/extensions/wechat/index.ts +2 -8
- package/extensions/wechat/outbound.ts +2 -4
- package/extensions/wechat/peer-worker.ts +3 -8
- package/extensions/wechat/poll.ts +10 -9
- package/extensions/wechat/presence.ts +35 -62
- package/extensions/wechat/reply-pipeline.ts +3 -1
- package/extensions/wechat/status-screen.ts +1 -1
- package/extensions/wechat/timer.ts +57 -0
- package/extensions/worktree/config.ts +8 -20
- package/extensions/worktree/enter.ts +7 -12
- package/extensions/worktree/git.ts +38 -69
- package/extensions/worktree/index.ts +5 -4
- package/extensions/worktree/registry.ts +7 -7
- package/extensions/worktree/root-tools.ts +2 -2
- package/extensions/worktree/status.ts +3 -2
- package/lib/README.md +22 -8
- package/lib/config.ts +50 -3
- package/lib/errors.ts +12 -0
- package/lib/format.ts +50 -0
- package/lib/git/read.ts +8 -0
- package/lib/goal-state.ts +23 -8
- package/lib/http/headers.ts +54 -0
- package/lib/http/index.ts +13 -0
- package/lib/http/response.ts +46 -0
- package/lib/http/router.ts +52 -0
- package/lib/http/server.ts +91 -0
- package/lib/lines.ts +77 -0
- package/lib/pdf.ts +82 -0
- package/lib/plan-entry.ts +71 -0
- package/lib/plan-state.ts +34 -0
- package/lib/report.ts +99 -0
- package/lib/requirements.ts +25 -19
- package/lib/spawn.ts +170 -0
- package/lib/todo-state.ts +110 -23
- package/lib/tool-names.ts +7 -0
- package/lib/tool-result.ts +33 -0
- package/lib/tui/README.md +68 -3
- package/lib/tui/chrome.ts +28 -4
- package/lib/tui/columns.ts +6 -1
- package/lib/tui/confirm.ts +65 -0
- package/lib/tui/dock-screen.ts +410 -0
- package/lib/tui/glyphs.ts +2 -1
- package/lib/tui/indent.ts +1 -1
- package/lib/tui/index.ts +2 -0
- package/lib/tui/rows.ts +21 -0
- package/lib/tui/status-controller.ts +35 -1
- package/lib/tui/tool.ts +59 -1
- package/lib/web/components.ts +20 -1
- package/lib/web/markup.ts +32 -0
- package/lib/web/runtime.ts +50 -0
- package/package.json +22 -5
- package/skills/init/SKILL.md +165 -0
- package/skills/init/references/agents-md-quality.md +103 -0
- package/skills/init/references/interview-questions.md +81 -0
- package/skills/refactor/SKILL.md +141 -0
- package/extensions/watch/process.ts +0 -63
- package/extensions/web-access/fetch/pdf.ts +0 -45
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `run_check` tool.
|
|
3
|
+
*
|
|
4
|
+
* It returns a normal result for a failing check (the command ran and the model
|
|
5
|
+
* should fix it), and an error result only when the check could not run at all.
|
|
6
|
+
* The structured content is the machine-readable mirror of the text; the tail is
|
|
7
|
+
* extra detail for the transcript and is deliberately not in that contract.
|
|
8
|
+
*
|
|
9
|
+
* A run can take minutes and can print nothing for long stretches, so `execute`
|
|
10
|
+
* publishes one partial result as soon as the runtime starts. The renderer turns
|
|
11
|
+
* that into a running line and repaints its elapsed time once a second instead
|
|
12
|
+
* of waiting for the first chunk.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { ExtensionAPI, ExtensionToolContext } from "@earendil-works/pi-coding-agent";
|
|
16
|
+
import { Type, type Static } from "typebox";
|
|
17
|
+
import { sanitizeLine } from "../../lib/format.ts";
|
|
18
|
+
import { reuseText, toolDetails } from "../../lib/tui/chrome.ts";
|
|
19
|
+
import {
|
|
20
|
+
elapsedSuffix,
|
|
21
|
+
errorLine,
|
|
22
|
+
rejectionLine,
|
|
23
|
+
reuseCallText,
|
|
24
|
+
syncElapsedTimer,
|
|
25
|
+
toolCallTitle,
|
|
26
|
+
} from "../../lib/tui/tool.ts";
|
|
27
|
+
import { paint } from "../../lib/tui/tokens.ts";
|
|
28
|
+
import { formatCheckReport } from "./format.ts";
|
|
29
|
+
import { CheckResultBlock } from "./render.ts";
|
|
30
|
+
import { checkErrorResult, type CheckRuntime } from "./runtime.ts";
|
|
31
|
+
import type { ActiveCheck, CheckResult, RunCheckDetails } from "./types.ts";
|
|
32
|
+
|
|
33
|
+
/** The tool's registered name, shared with the naming guard. */
|
|
34
|
+
export const TOOL_NAME = "run_check";
|
|
35
|
+
|
|
36
|
+
const TargetSchema = Type.Union(
|
|
37
|
+
[Type.Literal("test"), Type.Literal("lint"), Type.Literal("typecheck"), Type.Literal("all")],
|
|
38
|
+
{ description: "Which detected command to run; defaults to all." },
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const FailureSchema = Type.Object({
|
|
42
|
+
path: Type.Optional(Type.String()),
|
|
43
|
+
line: Type.Optional(Type.Integer()),
|
|
44
|
+
column: Type.Optional(Type.Integer()),
|
|
45
|
+
test: Type.Optional(Type.String()),
|
|
46
|
+
message: Type.String(),
|
|
47
|
+
command: Type.String(),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export const RunCheckParams = Type.Object({
|
|
51
|
+
target: Type.Optional(TargetSchema),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export type RunCheckArgs = Static<typeof RunCheckParams>;
|
|
55
|
+
|
|
56
|
+
export const RunCheckOutput = Type.Object({
|
|
57
|
+
target: TargetSchema,
|
|
58
|
+
status: Type.Union([
|
|
59
|
+
Type.Literal("passed"),
|
|
60
|
+
Type.Literal("failed"),
|
|
61
|
+
Type.Literal("timeout"),
|
|
62
|
+
Type.Literal("cancelled"),
|
|
63
|
+
Type.Literal("error"),
|
|
64
|
+
]),
|
|
65
|
+
commands: Type.Array(Type.String()),
|
|
66
|
+
exitCode: Type.Union([Type.Integer(), Type.Null()]),
|
|
67
|
+
durationMs: Type.Integer(),
|
|
68
|
+
truncated: Type.Boolean(),
|
|
69
|
+
failures: Type.Array(FailureSchema),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export type RunCheckStructured = Static<typeof RunCheckOutput>;
|
|
73
|
+
|
|
74
|
+
/** One settled run as Pi stores it: model text, transcript details, structured mirror. */
|
|
75
|
+
function resultPayload(result: CheckResult) {
|
|
76
|
+
const details: RunCheckDetails = { ...result };
|
|
77
|
+
const structured: RunCheckStructured = {
|
|
78
|
+
target: result.target,
|
|
79
|
+
status: result.status,
|
|
80
|
+
commands: result.commands,
|
|
81
|
+
exitCode: result.exitCode,
|
|
82
|
+
durationMs: result.durationMs,
|
|
83
|
+
truncated: result.truncated,
|
|
84
|
+
failures: result.failures,
|
|
85
|
+
};
|
|
86
|
+
return {
|
|
87
|
+
content: [{ type: "text" as const, text: formatCheckReport(result) }],
|
|
88
|
+
details,
|
|
89
|
+
structuredContent: structured,
|
|
90
|
+
isError: result.status === "error",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The partial result's details: no failures yet, and the commands now running. */
|
|
95
|
+
function runningDetails(active: ActiveCheck): RunCheckDetails {
|
|
96
|
+
return {
|
|
97
|
+
target: active.target,
|
|
98
|
+
status: "running",
|
|
99
|
+
commands: [...active.commands],
|
|
100
|
+
exitCode: null,
|
|
101
|
+
durationMs: active.elapsedMs(),
|
|
102
|
+
truncated: false,
|
|
103
|
+
failures: [],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function registerCheckTool(pi: ExtensionAPI, runtime: CheckRuntime): void {
|
|
108
|
+
pi.registerTool({
|
|
109
|
+
name: TOOL_NAME,
|
|
110
|
+
label: "Run checks",
|
|
111
|
+
description:
|
|
112
|
+
"Run the project's detected test, lint, or typecheck command (or check.command) in a supervised " +
|
|
113
|
+
"process and return structured failures. The command is a shell command from the project config or " +
|
|
114
|
+
"manifest, with a timeout and a bounded log; it can write to the project just like a shell command.",
|
|
115
|
+
promptSnippet: "Run the project's checks and return structured failures.",
|
|
116
|
+
promptGuidelines: [
|
|
117
|
+
"Use run_check before claiming a change is complete.",
|
|
118
|
+
"Prefer run_check over an ad-hoc test command so failures come back with file and line locations.",
|
|
119
|
+
"Do not start a check while another check is running.",
|
|
120
|
+
],
|
|
121
|
+
parameters: RunCheckParams,
|
|
122
|
+
outputSchema: RunCheckOutput,
|
|
123
|
+
executionMode: "sequential",
|
|
124
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
125
|
+
|
|
126
|
+
async execute(_toolCallId, params, _signal, onUpdate, ctx: ExtensionToolContext) {
|
|
127
|
+
const target = params.target ?? "all";
|
|
128
|
+
let active: ActiveCheck;
|
|
129
|
+
try {
|
|
130
|
+
active = runtime.start(target, ctx);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
133
|
+
return resultPayload(checkErrorResult(target, message));
|
|
134
|
+
}
|
|
135
|
+
onUpdate?.({ content: [], details: runningDetails(active) });
|
|
136
|
+
return resultPayload(await active.promise);
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
renderCall(args, theme, context) {
|
|
140
|
+
const subject = context.argsComplete ? (args.target ?? "all") : "…";
|
|
141
|
+
const text = reuseCallText(context.lastComponent, "", "middle");
|
|
142
|
+
text.setText(toolCallTitle(theme, `${TOOL_NAME} `) + paint(theme, "meta", sanitizeLine(subject)));
|
|
143
|
+
return text;
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
renderResult(result, options, theme, context) {
|
|
147
|
+
const details = toolDetails<RunCheckDetails>(result.details);
|
|
148
|
+
const startedAt = syncElapsedTimer(options.isPartial, context);
|
|
149
|
+
if (!details) {
|
|
150
|
+
const text = reuseText(context.lastComponent);
|
|
151
|
+
text.setText(rejectionLine(theme, result, options.expanded));
|
|
152
|
+
return text;
|
|
153
|
+
}
|
|
154
|
+
if (details.status === "running") {
|
|
155
|
+
const text = reuseText(context.lastComponent);
|
|
156
|
+
const commands = details.commands.length > 0 ? sanitizeLine(details.commands.join(", ")) : "checks";
|
|
157
|
+
const elapsed = startedAt === undefined ? "" : elapsedSuffix(theme, Date.now() - startedAt);
|
|
158
|
+
text.setText(paint(theme, "info", `Running ${commands}…`) + elapsed);
|
|
159
|
+
return text;
|
|
160
|
+
}
|
|
161
|
+
if (details.status === "error") {
|
|
162
|
+
const text = reuseText(context.lastComponent);
|
|
163
|
+
text.setText(errorLine(theme, details.error ?? "the check could not run"));
|
|
164
|
+
return text;
|
|
165
|
+
}
|
|
166
|
+
const input = { details, expanded: options.expanded };
|
|
167
|
+
const view =
|
|
168
|
+
context.lastComponent instanceof CheckResultBlock ? context.lastComponent : new CheckResultBlock(input, theme);
|
|
169
|
+
view.setInput(input, theme);
|
|
170
|
+
return view;
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two check screens: a live run tail and a navigable failure list.
|
|
3
|
+
*
|
|
4
|
+
* `CheckRunComponent` owns only a `LinePane` over the runtime's live lines;
|
|
5
|
+
* `CheckResultComponent` is a `DockListScreen`, so the frame, the id-anchored
|
|
6
|
+
* cursor, the navigation keys, the range row, and the detail pane come from the
|
|
7
|
+
* shared base and nothing here draws chrome. Git reads, parsing, and process
|
|
8
|
+
* lifecycle stay in `runtime.ts`, so both components can be tested without a
|
|
9
|
+
* process.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import { type Component, type TuiMouseEvent, type TuiMouseEventResult } from "@earendil-works/pi-tui";
|
|
14
|
+
import { formatDuration, sanitizeLine } from "../../lib/format.ts";
|
|
15
|
+
import { dockListFrame, screenHint, type ScreenHint, type ViewportRowsSource } from "../../lib/tui/chrome.ts";
|
|
16
|
+
import { clip } from "../../lib/tui/columns.ts";
|
|
17
|
+
import { fitRows, formatRange, isCloseKey, wheelDelta } from "../../lib/tui/cursor.ts";
|
|
18
|
+
import { DockListScreen, type DetailContents } from "../../lib/tui/dock-screen.ts";
|
|
19
|
+
import { SEPARATORS } from "../../lib/tui/glyphs.ts";
|
|
20
|
+
import { LinePane } from "../../lib/tui/line-pane.ts";
|
|
21
|
+
import { emptyState, listRow } from "../../lib/tui/rows.ts";
|
|
22
|
+
import { titleRule } from "../../lib/tui/rules.ts";
|
|
23
|
+
import { paint } from "../../lib/tui/tokens.ts";
|
|
24
|
+
import { formatFailureDetail, formatFailureLocation, formatResultSummary } from "./format.ts";
|
|
25
|
+
import { readSourceExcerpt } from "./source.ts";
|
|
26
|
+
import type { CheckResult, Failure } from "./types.ts";
|
|
27
|
+
|
|
28
|
+
const RUN_DEFAULT_ROWS = 20;
|
|
29
|
+
const RUN_CHROME_ROWS = 5;
|
|
30
|
+
|
|
31
|
+
export interface CheckRunOptions {
|
|
32
|
+
command: string;
|
|
33
|
+
lines: () => readonly string[];
|
|
34
|
+
elapsedMs: () => number;
|
|
35
|
+
theme: Theme;
|
|
36
|
+
onCancel: () => void;
|
|
37
|
+
requestRender: () => void;
|
|
38
|
+
viewportRows?: ViewportRowsSource;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The live run screen: a tail, a status summary, and a cancel key. */
|
|
42
|
+
export class CheckRunComponent implements Component {
|
|
43
|
+
private readonly pane = new LinePane();
|
|
44
|
+
|
|
45
|
+
constructor(private readonly options: CheckRunOptions) {
|
|
46
|
+
this.pane.open("check", { title: options.command, lines: [] });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
handleInput(data: string): void {
|
|
50
|
+
if (isCloseKey(data)) {
|
|
51
|
+
this.options.onCancel();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (this.pane.handleKey(data)) this.options.requestRender();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
handleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined {
|
|
58
|
+
const delta = wheelDelta(event);
|
|
59
|
+
if (delta === undefined) return undefined;
|
|
60
|
+
this.pane.setScroll(this.pane.scroll + delta * 3);
|
|
61
|
+
this.options.requestRender();
|
|
62
|
+
return { handled: true };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
dispose(): void {}
|
|
66
|
+
|
|
67
|
+
invalidate(): void {}
|
|
68
|
+
|
|
69
|
+
render(width: number): string[] {
|
|
70
|
+
const w = Math.max(1, width);
|
|
71
|
+
const lines = [...this.options.lines()];
|
|
72
|
+
this.pane.update({ lines });
|
|
73
|
+
this.pane.visible = fitRows(this.options.viewportRows, lines.length, RUN_CHROME_ROWS, RUN_DEFAULT_ROWS);
|
|
74
|
+
this.pane.fit(this.pane.visible);
|
|
75
|
+
const { start, end } = this.pane.window();
|
|
76
|
+
const body = lines.slice(start, end).map((line) => {
|
|
77
|
+
const role = line.startsWith("$ ") ? "accent" : "label";
|
|
78
|
+
return clip(paint(this.theme, role, line), w);
|
|
79
|
+
});
|
|
80
|
+
if (body.length === 0) body.push(...emptyState(this.theme, w, { label: "Starting…" }));
|
|
81
|
+
return dockListFrame({
|
|
82
|
+
header: titleRule(this.theme, w, "Check"),
|
|
83
|
+
summary: clip(
|
|
84
|
+
` ${paint(this.theme, "meta", `running${SEPARATORS.item}${sanitizeLine(this.options.command)}${SEPARATORS.item}${formatDuration(this.options.elapsedMs())}`)}`,
|
|
85
|
+
w,
|
|
86
|
+
),
|
|
87
|
+
body,
|
|
88
|
+
range:
|
|
89
|
+
start > 0 || end < lines.length
|
|
90
|
+
? formatRange(this.theme, w, { start, end, total: lines.length, label: "line" })
|
|
91
|
+
: undefined,
|
|
92
|
+
hint: screenHint(this.theme, w, ["Esc cancel", "j/k scroll", "g/G top/end"]),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private get theme(): Theme {
|
|
97
|
+
return this.options.theme;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface CheckResultOptions {
|
|
102
|
+
result: CheckResult;
|
|
103
|
+
/** Project root used to read a failure's source excerpt. */
|
|
104
|
+
cwd: string;
|
|
105
|
+
theme: Theme;
|
|
106
|
+
onRerun: () => void;
|
|
107
|
+
onClose: () => void;
|
|
108
|
+
requestRender: () => void;
|
|
109
|
+
viewportRows?: ViewportRowsSource;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The failure list, with a per-failure detail pane.
|
|
114
|
+
*
|
|
115
|
+
* A failure carries no id of its own, but a finished run's `failures` array is
|
|
116
|
+
* fixed for the life of this screen, so the index is a stable cursor key. It is
|
|
117
|
+
* mapped once in the constructor: an `indexOf` per row per frame would be
|
|
118
|
+
* quadratic on the 200-failure cap.
|
|
119
|
+
*/
|
|
120
|
+
export class CheckResultComponent extends DockListScreen<Failure> {
|
|
121
|
+
private readonly ids = new Map<Failure, string>();
|
|
122
|
+
|
|
123
|
+
constructor(private readonly view: CheckResultOptions) {
|
|
124
|
+
super({
|
|
125
|
+
theme: view.theme,
|
|
126
|
+
title: "Check",
|
|
127
|
+
detailTitle: "Failure",
|
|
128
|
+
emptyList: "No failures parsed.",
|
|
129
|
+
emptyDetail: "Nothing to show.",
|
|
130
|
+
requestRender: view.requestRender,
|
|
131
|
+
onClose: view.onClose,
|
|
132
|
+
viewportRowsSource: view.viewportRows,
|
|
133
|
+
});
|
|
134
|
+
view.result.failures.forEach((failure, index) => this.ids.set(failure, String(index)));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
protected rows(): readonly Failure[] {
|
|
138
|
+
return this.view.result.failures;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
protected idOf(failure: Failure): string {
|
|
142
|
+
return this.ids.get(failure) ?? "0";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
protected rowLine(failure: Failure, width: number, selected: boolean): string {
|
|
146
|
+
return listRow(this.theme, width, {
|
|
147
|
+
selected,
|
|
148
|
+
glyph: "✗",
|
|
149
|
+
glyphRole: "bad",
|
|
150
|
+
label: formatFailureLocation(failure),
|
|
151
|
+
labelRole: selected ? "accent" : "label",
|
|
152
|
+
trailing: "",
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
protected detailBlock(failure: Failure): string[] {
|
|
157
|
+
return [sanitizeLine(failure.message)];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
protected summaryCells(): string[] {
|
|
161
|
+
return [paint(this.theme, "meta", formatResultSummary(this.view.result))];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
protected listHints(rows: readonly Failure[]): ScreenHint[] {
|
|
165
|
+
// An empty list has nothing to open, so it offers only the two ways out.
|
|
166
|
+
if (rows.length === 0) return ["Esc close", { text: "r rerun", color: "accent" }];
|
|
167
|
+
return [
|
|
168
|
+
"Esc close",
|
|
169
|
+
{ text: "Enter detail", color: "accent" },
|
|
170
|
+
{ text: "r rerun", color: "warning" },
|
|
171
|
+
"↑/↓ select",
|
|
172
|
+
];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
protected detailHints(): ScreenHint[] {
|
|
176
|
+
return ["Esc back", "j/k scroll", "g/G top/end"];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
protected detailFor(failure: Failure): DetailContents {
|
|
180
|
+
// The excerpt is read once, when the pane opens, as it always has been.
|
|
181
|
+
const source = failure.path ? readSourceExcerpt(this.view.cwd, failure) : undefined;
|
|
182
|
+
return {
|
|
183
|
+
title: formatFailureLocation(failure),
|
|
184
|
+
// The base clips a detail line but never paints it, so the body keeps the
|
|
185
|
+
// role this screen gave it.
|
|
186
|
+
lines: formatFailureDetail(failure, this.view.result.tail, source).map((line) =>
|
|
187
|
+
paint(this.theme, "label", line),
|
|
188
|
+
),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
protected handleVerb(key: string): void {
|
|
193
|
+
if (key === "r") this.view.onRerun();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the check extension.
|
|
3
|
+
*
|
|
4
|
+
* The extension is deliberately language-neutral: a command is a shell string,
|
|
5
|
+
* a failure is a location plus a message, and nothing here names an ecosystem.
|
|
6
|
+
* Detection and parsing are the only places that know about manifests or test
|
|
7
|
+
* runner output.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** The targets `/check` and `run_check` accept. */
|
|
11
|
+
export type CheckTarget = "test" | "lint" | "typecheck" | "all";
|
|
12
|
+
|
|
13
|
+
/** A command the detector or the config selected. */
|
|
14
|
+
export interface CommandSpec {
|
|
15
|
+
/** Shell command, run in the project root. */
|
|
16
|
+
command: string;
|
|
17
|
+
/** The same text for display and model-facing results. */
|
|
18
|
+
label: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The commands detected for one project, keyed by target. */
|
|
22
|
+
export type DetectedCommands = Partial<Record<Exclude<CheckTarget, "all">, CommandSpec>> & {
|
|
23
|
+
all?: CommandSpec;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** One parsed failure, independent of the runner that produced it. */
|
|
27
|
+
export interface Failure {
|
|
28
|
+
/** Repository-relative or absolute path, when the runner printed one. */
|
|
29
|
+
path?: string;
|
|
30
|
+
line?: number;
|
|
31
|
+
column?: number;
|
|
32
|
+
/** Test name, when the runner prints a name without a location. */
|
|
33
|
+
test?: string;
|
|
34
|
+
/** The human-readable reason, already collapsed to one line. */
|
|
35
|
+
message: string;
|
|
36
|
+
/** The command that produced this failure. */
|
|
37
|
+
command: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The terminal state of a check run. */
|
|
41
|
+
export type CheckStatus = "passed" | "failed" | "timeout" | "cancelled" | "error";
|
|
42
|
+
|
|
43
|
+
/** What one command did. */
|
|
44
|
+
export interface CommandRun {
|
|
45
|
+
command: string;
|
|
46
|
+
exitCode: number | null;
|
|
47
|
+
signal: string | null;
|
|
48
|
+
timedOut: boolean;
|
|
49
|
+
cancelled: boolean;
|
|
50
|
+
output: string;
|
|
51
|
+
truncated: boolean;
|
|
52
|
+
durationMs: number;
|
|
53
|
+
error?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The aggregate result of one `/check` or `run_check` call. */
|
|
57
|
+
export interface CheckResult {
|
|
58
|
+
target: CheckTarget;
|
|
59
|
+
status: CheckStatus;
|
|
60
|
+
commands: string[];
|
|
61
|
+
/** Last non-null exit code, or null for timeout/cancel/spawn failure. */
|
|
62
|
+
exitCode: number | null;
|
|
63
|
+
durationMs: number;
|
|
64
|
+
truncated: boolean;
|
|
65
|
+
failures: Failure[];
|
|
66
|
+
/** Sanitized tail of the bounded output, for a detail view or fallback. */
|
|
67
|
+
tail: string;
|
|
68
|
+
error?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The model-facing details and structuredContent mirror. */
|
|
72
|
+
export interface RunCheckDetails {
|
|
73
|
+
target: CheckTarget;
|
|
74
|
+
/** A terminal state, or "running" for the partial result the host replaces. */
|
|
75
|
+
status: CheckStatus | "running";
|
|
76
|
+
commands: string[];
|
|
77
|
+
exitCode: number | null;
|
|
78
|
+
durationMs: number;
|
|
79
|
+
truncated: boolean;
|
|
80
|
+
failures: Failure[];
|
|
81
|
+
/** Extra detail for the transcript renderer; not in structuredContent. */
|
|
82
|
+
tail?: string;
|
|
83
|
+
/** Set when the run could not start or a command failed to spawn. */
|
|
84
|
+
error?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** A run that can be observed while it is still active. */
|
|
88
|
+
export interface ActiveCheck {
|
|
89
|
+
target: CheckTarget;
|
|
90
|
+
commands: string[];
|
|
91
|
+
/** Sanitized lines for the live screen, oldest first. */
|
|
92
|
+
lines(): readonly string[];
|
|
93
|
+
/** Milliseconds since the run started. */
|
|
94
|
+
elapsedMs(): number;
|
|
95
|
+
/** Resolves once the run settles; never rejects. */
|
|
96
|
+
promise: Promise<CheckResult>;
|
|
97
|
+
/** Kill the active process tree and settle as cancelled. */
|
|
98
|
+
cancel(): void;
|
|
99
|
+
}
|
|
@@ -281,7 +281,7 @@ repeating it would be pure duplication.
|
|
|
281
281
|
| `paging.ts` | `formatDoc`: code-point slicing and header/truncation formatting; `summarizeDoc`: the theme-free transcript summary. |
|
|
282
282
|
| `extract/file.ts` | `DocFile` / `DocExtractor`, `readDocBytes` (abort-aware read), `normalizeBodyText`. |
|
|
283
283
|
| `extract/vendor.d.ts` | Ambient types for `word-extractor` and `rtf-parser` (no bundled declarations). |
|
|
284
|
-
| `extract/pdf.ts` | `
|
|
284
|
+
| `extract/pdf.ts` | Adapter over the shared `lib/pdf.ts` seam, plus the PDF body normalization. |
|
|
285
285
|
| `extract/docx.ts` | `mammoth` extractor. |
|
|
286
286
|
| `extract/doc.ts` | `word-extractor` extractor for legacy `.doc`. |
|
|
287
287
|
| `extract/odt.ts` | `fflate` + `fast-xml-parser` extractor and `odtXmlToText`. |
|
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PDF text extraction
|
|
2
|
+
* PDF text extraction, over the shared `unpdf` seam.
|
|
3
3
|
*
|
|
4
|
-
* `
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* image-only PDF yields little or no text.
|
|
4
|
+
* `lib/pdf.ts` owns the lazy import, the module-shape handling, and the
|
|
5
|
+
* guidance when the package is absent; this adapter reads the file and applies
|
|
6
|
+
* the body normalization `read_doc` returns for a PDF.
|
|
8
7
|
*/
|
|
9
8
|
|
|
9
|
+
import { extractPdfText } from "../../../lib/pdf.ts";
|
|
10
10
|
import { readDocBytes, type DocExtractor } from "./file.ts";
|
|
11
11
|
|
|
12
|
-
/** Pull the merged text out of `unpdf`'s `{ totalPages, text }` result. Pure. */
|
|
13
|
-
export function pdfTextFromResult(result: unknown): string {
|
|
14
|
-
if (typeof result === "string") return result;
|
|
15
|
-
const text = (result as { text?: unknown } | undefined)?.text;
|
|
16
|
-
return typeof text === "string" ? text : "";
|
|
17
|
-
}
|
|
18
|
-
|
|
19
12
|
/** Extract text from every page of a PDF. */
|
|
20
|
-
export const
|
|
13
|
+
export const extractPdfDocument: DocExtractor = async (file) => {
|
|
21
14
|
const bytes = await readDocBytes(file);
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
return pdfTextFromResult(result).replace(/\r\n?/g, "\n").replace(/\n+$/, "");
|
|
15
|
+
const text = await extractPdfText(bytes);
|
|
16
|
+
return text.replace(/\r\n?/g, "\n").replace(/\n+$/, "");
|
|
25
17
|
};
|
|
@@ -12,7 +12,7 @@ import type { DocExtractor } from "./extract/file.ts";
|
|
|
12
12
|
import { extractDocText } from "./extract/doc.ts";
|
|
13
13
|
import { extractDocxText } from "./extract/docx.ts";
|
|
14
14
|
import { extractOdtText } from "./extract/odt.ts";
|
|
15
|
-
import {
|
|
15
|
+
import { extractPdfDocument } from "./extract/pdf.ts";
|
|
16
16
|
import { extractRtfText } from "./extract/rtf.ts";
|
|
17
17
|
import { extractXlsxText } from "./extract/xlsx.ts";
|
|
18
18
|
|
|
@@ -30,7 +30,7 @@ export interface DocumentFormat {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export const FORMATS = [
|
|
33
|
-
{ id: "pdf", label: "PDF", extensions: [".pdf"], defaultEnabled: true, extract:
|
|
33
|
+
{ id: "pdf", label: "PDF", extensions: [".pdf"], defaultEnabled: true, extract: extractPdfDocument },
|
|
34
34
|
{ id: "docx", label: "DOCX", extensions: [".docx"], defaultEnabled: true, extract: extractDocxText },
|
|
35
35
|
{ id: "doc", label: "DOC", extensions: [".doc"], defaultEnabled: true, extract: extractDocText },
|
|
36
36
|
{ id: "odt", label: "ODT", extensions: [".odt"], defaultEnabled: true, extract: extractOdtText },
|