@gavin-hu/my-pi-agent 0.19.0 → 0.21.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 +19 -12
- package/README.zh-CN.md +15 -9
- package/docs/extensions/README.md +2 -1
- package/docs/extensions/board.md +329 -151
- package/docs/extensions/bytefly.md +427 -273
- package/docs/extensions/file-browse.md +46 -44
- package/docs/extensions/job.md +1 -2
- package/docs/extensions/plan.md +86 -24
- package/docs/extensions/serve.md +129 -0
- package/docs/extensions/session-bus.md +23 -10
- package/docs/extensions/team.md +56 -28
- package/docs/extensions/wechat.md +2 -1
- package/docs/extensions/worktree.md +30 -15
- package/extensions/board/commands.ts +48 -17
- package/extensions/board/format.ts +107 -9
- package/extensions/board/index.ts +14 -2
- package/extensions/board/join.ts +31 -10
- package/extensions/board/page.ts +202 -156
- package/extensions/board/result.ts +10 -24
- package/extensions/board/rows.ts +1 -1
- package/extensions/board/schema.ts +31 -10
- package/extensions/board/seat.ts +9 -10
- package/extensions/board/snapshot.ts +7 -5
- package/extensions/board/tools.ts +211 -34
- package/extensions/board/transport/router.ts +27 -110
- package/extensions/board/transport/server.ts +76 -56
- package/extensions/board/types.ts +57 -0
- package/extensions/board/view/client.ts +24 -13
- package/extensions/board/view/dialog.ts +43 -0
- package/extensions/board/view/keys.ts +28 -24
- package/extensions/board/view/live.ts +6 -178
- package/extensions/board/view/names.ts +91 -0
- package/extensions/board/view/page.ts +60 -89
- package/extensions/board/view/render.ts +228 -200
- package/extensions/board/view/styles.ts +72 -49
- package/extensions/board/view/team.ts +63 -41
- package/extensions/bytefly/footer/budget.ts +6 -5
- package/extensions/bytefly/footer/collect/entries.ts +12 -4
- package/extensions/bytefly/footer/collect/snapshot.ts +8 -17
- package/extensions/bytefly/footer/config.ts +19 -11
- package/extensions/bytefly/footer/price/price.ts +30 -8
- package/extensions/bytefly/footer/register.ts +5 -5
- package/extensions/bytefly/footer/render/footer.ts +18 -4
- package/extensions/bytefly/footer/render/format.ts +18 -0
- package/extensions/bytefly/footer/render/key.ts +46 -0
- package/extensions/bytefly/footer/render/layout.ts +13 -3
- package/extensions/bytefly/footer/render/lines.ts +23 -7
- package/extensions/bytefly/footer/render/segments.ts +93 -31
- package/extensions/bytefly/footer/types.ts +29 -5
- package/extensions/bytefly/footer/unpriced.ts +42 -17
- package/extensions/bytefly/header/component.ts +4 -20
- package/extensions/bytefly/panel/commands.ts +28 -1
- package/extensions/bytefly/panel/filter.ts +68 -0
- package/extensions/bytefly/panel/format.ts +16 -15
- package/extensions/bytefly/panel/join.ts +56 -13
- package/extensions/bytefly/panel/prices.ts +43 -0
- package/extensions/bytefly/panel/settings.ts +72 -23
- package/extensions/bytefly/panel/tui/entries.ts +26 -8
- package/extensions/bytefly/panel/tui/frame.ts +130 -93
- package/extensions/bytefly/panel/tui/keys.ts +352 -0
- package/extensions/bytefly/panel/tui/list-view.ts +25 -46
- package/extensions/bytefly/panel/tui/panes.ts +76 -0
- package/extensions/bytefly/panel/tui/section-view.ts +126 -0
- package/extensions/bytefly/panel/tui/settings-section.ts +54 -125
- package/extensions/bytefly/panel/tui/settings-view.ts +30 -131
- package/extensions/bytefly/panel/tui/side-pane.ts +224 -0
- package/extensions/bytefly/panel/tui.ts +306 -259
- package/extensions/check/commands.ts +2 -1
- package/extensions/check/render.ts +10 -1
- package/extensions/file-browse/config/config.ts +4 -19
- package/extensions/file-browse/files/classify.ts +136 -0
- package/extensions/file-browse/files/files.ts +8 -547
- package/extensions/file-browse/files/index.ts +90 -0
- package/extensions/file-browse/files/list.ts +261 -0
- package/extensions/file-browse/files/tree.ts +101 -0
- package/extensions/file-browse/index.ts +27 -48
- package/extensions/file-browse/live/live.ts +1 -1
- package/extensions/file-browse/transport/router.ts +12 -6
- package/extensions/file-browse/transport/server.ts +61 -28
- package/extensions/file-browse/view/chrome.ts +4 -4
- package/extensions/file-browse/view/client.ts +3 -1
- package/extensions/file-browse/view/emit.ts +51 -0
- package/extensions/file-browse/view/html.ts +78 -46
- package/extensions/file-browse/view/live-client.ts +2 -2
- package/extensions/file-browse/view/markdown.ts +23 -26
- package/extensions/file-browse/view/palette.ts +2 -2
- package/extensions/file-browse/view/raw-html.ts +27 -22
- package/extensions/file-browse/view/tree.ts +1 -1
- package/extensions/file-browse/view/urls.ts +20 -6
- package/extensions/goal/tui.ts +12 -2
- package/extensions/job/runtime.ts +4 -4
- package/extensions/job/tools.ts +19 -7
- package/extensions/job/tui.ts +2 -28
- package/extensions/plan/commands.ts +2 -2
- package/extensions/plan/feedback.ts +88 -18
- package/extensions/plan/format.ts +10 -3
- package/extensions/plan/index.ts +24 -2
- package/extensions/plan/plan-files.ts +88 -0
- package/extensions/plan/plan-text.ts +114 -0
- package/extensions/plan/plans.ts +19 -23
- package/extensions/plan/policy.ts +10 -5
- package/extensions/plan/runtime.ts +43 -4
- package/extensions/plan/todos.ts +51 -3
- package/extensions/plan/tools/exit.ts +10 -1
- package/extensions/plan/tools/todo-seed.ts +4 -2
- package/extensions/plan/tools/write.ts +12 -1
- package/extensions/plan/tools.ts +2 -2
- package/extensions/plan/types.ts +15 -0
- package/extensions/plan/web-blocks.ts +160 -0
- package/extensions/plan/web-chrome.ts +7 -5
- package/extensions/plan/web-client.ts +271 -325
- package/extensions/plan/web-collab.ts +7 -7
- package/extensions/plan/web-comments.ts +57 -16
- package/extensions/plan/web-composer.ts +91 -0
- package/extensions/plan/web-draft.ts +40 -0
- package/extensions/plan/web-keys.ts +258 -0
- package/extensions/plan/web-markdown.ts +1 -1
- package/extensions/plan/web-outline.ts +212 -0
- package/extensions/plan/web-page.ts +59 -86
- package/extensions/plan/web-payload.ts +188 -0
- package/extensions/plan/web-plan.ts +160 -0
- package/extensions/plan/web-router.ts +0 -1
- package/extensions/plan/web-settle.ts +201 -0
- package/extensions/plan/web-share-session.ts +24 -55
- package/extensions/plan/web-share.ts +19 -37
- package/extensions/plan/web-write.ts +112 -0
- package/extensions/plan/web.ts +77 -130
- package/extensions/rewind/commands/review.ts +2 -1
- package/extensions/serve/config.ts +37 -0
- package/extensions/serve/index.ts +71 -0
- package/extensions/serve/registry.ts +251 -0
- package/extensions/session-bus/join-request.ts +16 -9
- package/extensions/session-bus/result.ts +1 -27
- package/extensions/session-bus/rows.ts +7 -3
- package/extensions/session-bus/runtime.ts +100 -38
- package/extensions/session-bus/store.ts +14 -2
- package/extensions/session-bus/tools.ts +5 -4
- package/extensions/session-bus/tui.ts +1 -1
- package/extensions/session-bus/types.ts +10 -1
- package/extensions/subagent/commands.ts +2 -1
- package/extensions/team/commands.ts +44 -9
- package/extensions/team/format.ts +14 -0
- package/extensions/team/index.ts +2 -1
- package/extensions/team/tools.ts +30 -9
- package/extensions/team/types.ts +17 -0
- package/extensions/todo/index.ts +4 -1
- package/extensions/todo/nudge.ts +8 -2
- package/extensions/todo/tui.ts +21 -3
- package/extensions/wechat/bridge.ts +43 -172
- package/extensions/wechat/intake.ts +245 -0
- package/extensions/worktree/commands.ts +24 -17
- package/extensions/worktree/exit.ts +12 -6
- package/extensions/worktree/git.ts +22 -14
- package/extensions/worktree/guard.ts +15 -285
- package/extensions/worktree/handoff.ts +10 -2
- package/extensions/worktree/index.ts +2 -1
- package/extensions/worktree/lifecycle.ts +2 -0
- package/extensions/worktree/registry.ts +20 -11
- package/extensions/worktree/root-tools.ts +13 -3
- package/extensions/worktree/runtime.ts +8 -2
- package/extensions/worktree/screen.ts +15 -8
- package/extensions/worktree/shell.ts +303 -0
- package/extensions/worktree/state.ts +1 -1
- package/extensions/worktree/status.ts +42 -9
- package/extensions/worktree/tools.ts +19 -13
- package/lib/README.md +9 -7
- package/lib/board/card.ts +192 -3
- package/lib/board/links.ts +26 -2
- package/lib/board/page-pointer.ts +111 -32
- package/lib/board/round-text.ts +11 -2
- package/lib/board/round.ts +25 -20
- package/lib/board/rules.ts +144 -11
- package/lib/board/session.ts +84 -18
- package/lib/board/store.ts +268 -55
- package/lib/board/view.ts +114 -24
- package/lib/bus-request.ts +83 -0
- package/lib/format.ts +22 -0
- package/lib/http/index.ts +11 -7
- package/lib/http/mount.ts +142 -0
- package/lib/http/port.ts +25 -0
- package/lib/http/serve.ts +170 -43
- package/lib/http/server.ts +3 -3
- package/lib/http/sink.ts +34 -9
- package/lib/plan-entry.ts +20 -13
- package/lib/policy.ts +14 -1
- package/lib/requirements.ts +2 -2
- package/lib/session-label.ts +23 -7
- package/lib/session-roster.ts +74 -3
- package/lib/session-tool.ts +37 -21
- package/lib/spawn.ts +37 -3
- package/lib/stray-fields.ts +45 -0
- package/lib/tool-names.ts +13 -0
- package/lib/tui/README.md +6 -3
- package/lib/tui/glyphs.ts +3 -6
- package/lib/tui/render-cache.ts +29 -0
- package/lib/tui/tool.ts +30 -1
- package/lib/web/README.md +52 -2
- package/lib/web/client-runtime.ts +149 -0
- package/lib/web/index.ts +4 -2
- package/lib/web/markup.ts +106 -2
- package/lib/web/names.ts +42 -0
- package/package.json +3 -2
- package/extensions/board/config.ts +0 -36
- package/extensions/board/view/actions.ts +0 -306
- package/extensions/board/writes.ts +0 -99
- package/extensions/bytefly/panel/tui/extensions-view.ts +0 -40
- package/extensions/bytefly/panel/tui/skills-view.ts +0 -50
- package/extensions/job/process.ts +0 -35
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { resolveEffectiveCwd } from "../../lib/env.ts";
|
|
13
|
+
import { planModeRefusal } from "../../lib/plan-entry.ts";
|
|
13
14
|
import { isPlanModeEnabled } from "../../lib/plan-state.ts";
|
|
14
15
|
import { withRailsSuppressed } from "../../lib/rails.ts";
|
|
15
16
|
import { formatCheckReport, formatCheckSummary } from "./format.ts";
|
|
@@ -107,7 +108,7 @@ export function registerCheckCommand(pi: ExtensionAPI, runtime: CheckRuntime): v
|
|
|
107
108
|
},
|
|
108
109
|
handler: async (args, ctx) => {
|
|
109
110
|
if (isPlanModeEnabled(ctx.sessionManager.getBranch())) {
|
|
110
|
-
ctx.ui.notify("
|
|
111
|
+
ctx.ui.notify(planModeRefusal("running checks"), "warning");
|
|
111
112
|
return;
|
|
112
113
|
}
|
|
113
114
|
const target = parseTarget(args, ctx);
|
|
@@ -16,6 +16,7 @@ import type { Component } from "@earendil-works/pi-tui";
|
|
|
16
16
|
import { sanitizeLine } from "../../lib/format.ts";
|
|
17
17
|
import { clip } from "../../lib/tui/columns.ts";
|
|
18
18
|
import { expandHint, GLYPHS, SEPARATORS } from "../../lib/tui/glyphs.ts";
|
|
19
|
+
import { RenderCache } from "../../lib/tui/render-cache.ts";
|
|
19
20
|
import { listRow } from "../../lib/tui/rows.ts";
|
|
20
21
|
import { paint, type TuiRole } from "../../lib/tui/tokens.ts";
|
|
21
22
|
import { earlierHint } from "../../lib/tui/tool.ts";
|
|
@@ -134,6 +135,7 @@ export interface CheckResultInput {
|
|
|
134
135
|
export class CheckResultBlock implements Component {
|
|
135
136
|
private input: CheckResultInput;
|
|
136
137
|
private theme: Theme;
|
|
138
|
+
private readonly cache = new RenderCache();
|
|
137
139
|
|
|
138
140
|
constructor(input: CheckResultInput, theme: Theme) {
|
|
139
141
|
this.input = input;
|
|
@@ -144,12 +146,19 @@ export class CheckResultBlock implements Component {
|
|
|
144
146
|
setInput(input: CheckResultInput, theme: Theme): void {
|
|
145
147
|
this.input = input;
|
|
146
148
|
this.theme = theme;
|
|
149
|
+
this.cache.invalidate();
|
|
147
150
|
}
|
|
148
151
|
|
|
149
|
-
invalidate(): void {
|
|
152
|
+
invalidate(): void {
|
|
153
|
+
this.cache.invalidate();
|
|
154
|
+
}
|
|
150
155
|
|
|
151
156
|
render(width: number): string[] {
|
|
152
157
|
const w = Math.max(1, width);
|
|
158
|
+
return this.cache.lines(w, undefined, () => this.build(w));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private build(w: number): string[] {
|
|
153
162
|
const theme = this.theme;
|
|
154
163
|
const { details, expanded } = this.input;
|
|
155
164
|
const role = statusRole(details.status);
|
|
@@ -2,16 +2,14 @@
|
|
|
2
2
|
* Configuration for the browse extension.
|
|
3
3
|
*
|
|
4
4
|
* The `file-browse` section of the shared `bytefly.json` (global then project,
|
|
5
|
-
* project wins). Everything
|
|
6
|
-
* is
|
|
5
|
+
* project wins). Everything is validated and clamped so a typo cannot produce a
|
|
6
|
+
* nonsensical page. The port is not here: the session serves every page on one, and
|
|
7
|
+
* `serve.port` is where that is pinned.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
import { randomInt } from "node:crypto";
|
|
10
10
|
import { asBoolean, clampInteger, loadConfigSection } from "../../../lib/config.ts";
|
|
11
11
|
|
|
12
12
|
export interface ServeConfig {
|
|
13
|
-
/** TCP port on 127.0.0.1; `0` picks one random port fixed for the session, a positive value pins it. */
|
|
14
|
-
port: number;
|
|
15
13
|
/** Open the default browser when the server starts. */
|
|
16
14
|
autoOpen: boolean;
|
|
17
15
|
/** Files larger than this are not rendered inline. */
|
|
@@ -43,7 +41,6 @@ export interface ServeConfig {
|
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
export const DEFAULT_CONFIG: ServeConfig = {
|
|
46
|
-
port: 0,
|
|
47
44
|
autoOpen: true,
|
|
48
45
|
maxFileBytes: 1024 * 1024,
|
|
49
46
|
maxThumbBytes: 5 * 1024 * 1024,
|
|
@@ -60,12 +57,6 @@ export const DEFAULT_CONFIG: ServeConfig = {
|
|
|
60
57
|
liveIntervalMs: 5000,
|
|
61
58
|
};
|
|
62
59
|
|
|
63
|
-
/** Base of the per-session port range. */
|
|
64
|
-
export const PORT_BASE = 4780;
|
|
65
|
-
|
|
66
|
-
/** Number of ports in the per-session range. */
|
|
67
|
-
export const PORT_RANGE = 1000;
|
|
68
|
-
|
|
69
60
|
/** Fastest live-status cadence a page may be given: short enough to watch, and a
|
|
70
61
|
* floor so a page cannot outrun the git reads behind each frame. */
|
|
71
62
|
export const LIVE_INTERVAL_MIN = 250;
|
|
@@ -76,7 +67,7 @@ export const LIVE_INTERVAL_MAX = 60_000;
|
|
|
76
67
|
/**
|
|
77
68
|
* How long the server keeps listening once the last page has gone. A reload or a
|
|
78
69
|
* navigation detaches the stream and re-attaches within a moment, so the grace has
|
|
79
|
-
* to outlast one; a closed window never re-attaches, and the
|
|
70
|
+
* to outlast one; a closed window never re-attaches, and the prefix is given back
|
|
80
71
|
* instead of listening for nobody until the session ends.
|
|
81
72
|
*/
|
|
82
73
|
export const IDLE_STOP_MS = 10_000;
|
|
@@ -90,11 +81,6 @@ export const IDLE_STOP_MS = 10_000;
|
|
|
90
81
|
*/
|
|
91
82
|
export const LIVE_MAX_CLIENTS = 8;
|
|
92
83
|
|
|
93
|
-
/** A random port in the per-session range `PORT_BASE`–`PORT_BASE + PORT_RANGE - 1`. */
|
|
94
|
-
export function randomPort(): number {
|
|
95
|
-
return PORT_BASE + randomInt(PORT_RANGE);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
84
|
/** Validate and clamp a raw config object over the defaults. */
|
|
99
85
|
export function normalizeConfig(
|
|
100
86
|
raw: Record<string, unknown> | undefined,
|
|
@@ -102,7 +88,6 @@ export function normalizeConfig(
|
|
|
102
88
|
): ServeConfig {
|
|
103
89
|
if (!raw) return base;
|
|
104
90
|
return {
|
|
105
|
-
port: clampInteger(raw.port, base.port, 0, 65535),
|
|
106
91
|
autoOpen: asBoolean(raw.autoOpen, base.autoOpen),
|
|
107
92
|
maxFileBytes: clampInteger(raw.maxFileBytes, base.maxFileBytes, 0, 1024 * 1024 * 1024),
|
|
108
93
|
maxThumbBytes: clampInteger(raw.maxThumbBytes, base.maxThumbBytes, 0, 1024 * 1024 * 1024),
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a file's kind is, from its name and — for an extensionless one — a byte sample.
|
|
3
|
+
*
|
|
4
|
+
* Every surface that names a kind (the listing's rows, the tree, a preview's routing) asks
|
|
5
|
+
* here, so "is this an image" has one answer.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { basename, extname } from "node:path";
|
|
9
|
+
|
|
10
|
+
export type EntryKind = "directory" | "text" | "image" | "binary";
|
|
11
|
+
|
|
12
|
+
/** Image extension (no dot) to MIME type; the single source for image classification. */
|
|
13
|
+
const IMAGE_MIME: Record<string, string> = {
|
|
14
|
+
png: "image/png",
|
|
15
|
+
jpg: "image/jpeg",
|
|
16
|
+
jpeg: "image/jpeg",
|
|
17
|
+
gif: "image/gif",
|
|
18
|
+
webp: "image/webp",
|
|
19
|
+
svg: "image/svg+xml",
|
|
20
|
+
bmp: "image/bmp",
|
|
21
|
+
ico: "image/x-icon",
|
|
22
|
+
avif: "image/avif",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Image extensions without a leading dot. */
|
|
26
|
+
export const IMAGE_EXTENSIONS: ReadonlySet<string> = new Set(Object.keys(IMAGE_MIME));
|
|
27
|
+
|
|
28
|
+
const MARKDOWN_EXTS = new Set([".md", ".markdown", ".mdown", ".mkd", ".mkdn"]);
|
|
29
|
+
const TEXT_EXTS = new Set([
|
|
30
|
+
".txt",
|
|
31
|
+
".md",
|
|
32
|
+
".markdown",
|
|
33
|
+
".rst",
|
|
34
|
+
".adoc",
|
|
35
|
+
".org",
|
|
36
|
+
".log",
|
|
37
|
+
".json",
|
|
38
|
+
".jsonc",
|
|
39
|
+
".json5",
|
|
40
|
+
".yaml",
|
|
41
|
+
".yml",
|
|
42
|
+
".toml",
|
|
43
|
+
".ini",
|
|
44
|
+
".cfg",
|
|
45
|
+
".conf",
|
|
46
|
+
".xml",
|
|
47
|
+
".csv",
|
|
48
|
+
".tsv",
|
|
49
|
+
".ts",
|
|
50
|
+
".tsx",
|
|
51
|
+
".js",
|
|
52
|
+
".jsx",
|
|
53
|
+
".mjs",
|
|
54
|
+
".cjs",
|
|
55
|
+
".py",
|
|
56
|
+
".go",
|
|
57
|
+
".rs",
|
|
58
|
+
".java",
|
|
59
|
+
".c",
|
|
60
|
+
".h",
|
|
61
|
+
".cc",
|
|
62
|
+
".cpp",
|
|
63
|
+
".hpp",
|
|
64
|
+
".cs",
|
|
65
|
+
".rb",
|
|
66
|
+
".php",
|
|
67
|
+
".sh",
|
|
68
|
+
".bash",
|
|
69
|
+
".zsh",
|
|
70
|
+
".ps1",
|
|
71
|
+
".swift",
|
|
72
|
+
".kt",
|
|
73
|
+
".kts",
|
|
74
|
+
".scala",
|
|
75
|
+
".lua",
|
|
76
|
+
".r",
|
|
77
|
+
".pl",
|
|
78
|
+
".sql",
|
|
79
|
+
".css",
|
|
80
|
+
".scss",
|
|
81
|
+
".sass",
|
|
82
|
+
".less",
|
|
83
|
+
".html",
|
|
84
|
+
".htm",
|
|
85
|
+
".vue",
|
|
86
|
+
".svelte",
|
|
87
|
+
".astro",
|
|
88
|
+
]);
|
|
89
|
+
const TEXT_NAMES = new Set([
|
|
90
|
+
"license",
|
|
91
|
+
"licence",
|
|
92
|
+
"makefile",
|
|
93
|
+
"dockerfile",
|
|
94
|
+
"readme",
|
|
95
|
+
"changelog",
|
|
96
|
+
"authors",
|
|
97
|
+
"notice",
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
export function isImageName(name: string): boolean {
|
|
101
|
+
return IMAGE_EXTENSIONS.has(extname(name).slice(1).toLowerCase());
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Whether the name has a Markdown extension. */
|
|
105
|
+
export function isMarkdownName(name: string): boolean {
|
|
106
|
+
return MARKDOWN_EXTS.has(extname(name).toLowerCase());
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Extension-based classification for listings (no file contents read). */
|
|
110
|
+
export function classifyByName(name: string): EntryKind {
|
|
111
|
+
const ext = extname(name).toLowerCase();
|
|
112
|
+
if (IMAGE_EXTENSIONS.has(ext.slice(1))) return "image";
|
|
113
|
+
if (TEXT_EXTS.has(ext)) return "text";
|
|
114
|
+
const lower = basename(name).toLowerCase();
|
|
115
|
+
if (TEXT_NAMES.has(lower)) return "text";
|
|
116
|
+
if (!ext && name.startsWith(".")) return "text";
|
|
117
|
+
return "binary";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** True when the sample looks like binary data (NUL or many control bytes). */
|
|
121
|
+
export function isProbablyText(sample: Buffer): boolean {
|
|
122
|
+
if (sample.length === 0) return true;
|
|
123
|
+
if (sample.includes(0)) return false;
|
|
124
|
+
let suspicious = 0;
|
|
125
|
+
for (const byte of sample) {
|
|
126
|
+
if (byte === 9 || byte === 10 || byte === 13 || byte === 12 || byte === 8) continue;
|
|
127
|
+
if (byte < 32 || byte === 127) suspicious++;
|
|
128
|
+
}
|
|
129
|
+
return suspicious / sample.length < 0.3;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** MIME type for a name; text is always served as plain text. */
|
|
133
|
+
export function contentTypeFor(name: string): string {
|
|
134
|
+
const ext = extname(name).slice(1).toLowerCase();
|
|
135
|
+
return IMAGE_MIME[ext] ?? "application/octet-stream";
|
|
136
|
+
}
|