@jobshimo/browser-link 0.0.1 → 0.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 +102 -83
- package/dist/cli.js +19 -19
- package/dist/commands/about.d.ts +17 -15
- package/dist/commands/about.js +161 -186
- package/dist/commands/about.js.map +1 -1
- package/dist/commands/menu.d.ts +2 -3
- package/dist/commands/menu.js +111 -131
- package/dist/commands/menu.js.map +1 -1
- package/dist/commands/welcome.d.ts +8 -7
- package/dist/commands/welcome.js +78 -121
- package/dist/commands/welcome.js.map +1 -1
- package/dist/extension/background.js +90 -90
- package/dist/extension/icons/icon.svg +14 -14
- package/dist/extension/manifest.json +28 -28
- package/dist/extension/popup.html +88 -88
- package/dist/installers/index.d.ts +2 -3
- package/dist/installers/index.js +4 -4
- package/dist/installers/index.js.map +1 -1
- package/dist/installers/opencode.js +49 -21
- package/dist/installers/opencode.js.map +1 -1
- package/dist/map/db.js +28 -28
- package/dist/map/queries.js +4 -4
- package/dist/tools/server-instructions.js +46 -46
- package/package.json +64 -61
- package/dist/commands/tty.d.ts +0 -51
- package/dist/commands/tty.js +0 -148
- package/dist/commands/tty.js.map +0 -1
package/dist/map/db.js
CHANGED
|
@@ -42,34 +42,34 @@ function migrateLegacyDb(targetPath) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
function runMigrations(db) {
|
|
45
|
-
db.exec(`
|
|
46
|
-
CREATE TABLE IF NOT EXISTS apps (
|
|
47
|
-
id INTEGER PRIMARY KEY,
|
|
48
|
-
origin TEXT NOT NULL,
|
|
49
|
-
app_key TEXT NOT NULL,
|
|
50
|
-
title TEXT,
|
|
51
|
-
notes TEXT,
|
|
52
|
-
created_at TEXT NOT NULL,
|
|
53
|
-
last_seen_at TEXT NOT NULL,
|
|
54
|
-
UNIQUE(origin, app_key)
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
CREATE TABLE IF NOT EXISTS entries (
|
|
58
|
-
id INTEGER PRIMARY KEY,
|
|
59
|
-
app_id INTEGER NOT NULL REFERENCES apps(id) ON DELETE CASCADE,
|
|
60
|
-
url_pattern TEXT NOT NULL,
|
|
61
|
-
kind TEXT NOT NULL CHECK (kind IN ('selector', 'flow', 'gotcha')),
|
|
62
|
-
purpose TEXT NOT NULL,
|
|
63
|
-
payload TEXT NOT NULL,
|
|
64
|
-
verified_at TEXT,
|
|
65
|
-
failed_at TEXT,
|
|
66
|
-
notes TEXT,
|
|
67
|
-
created_at TEXT NOT NULL,
|
|
68
|
-
updated_at TEXT NOT NULL,
|
|
69
|
-
UNIQUE(app_id, url_pattern, kind, purpose)
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
CREATE INDEX IF NOT EXISTS idx_entries_lookup ON entries(app_id, url_pattern);
|
|
45
|
+
db.exec(`
|
|
46
|
+
CREATE TABLE IF NOT EXISTS apps (
|
|
47
|
+
id INTEGER PRIMARY KEY,
|
|
48
|
+
origin TEXT NOT NULL,
|
|
49
|
+
app_key TEXT NOT NULL,
|
|
50
|
+
title TEXT,
|
|
51
|
+
notes TEXT,
|
|
52
|
+
created_at TEXT NOT NULL,
|
|
53
|
+
last_seen_at TEXT NOT NULL,
|
|
54
|
+
UNIQUE(origin, app_key)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
CREATE TABLE IF NOT EXISTS entries (
|
|
58
|
+
id INTEGER PRIMARY KEY,
|
|
59
|
+
app_id INTEGER NOT NULL REFERENCES apps(id) ON DELETE CASCADE,
|
|
60
|
+
url_pattern TEXT NOT NULL,
|
|
61
|
+
kind TEXT NOT NULL CHECK (kind IN ('selector', 'flow', 'gotcha')),
|
|
62
|
+
purpose TEXT NOT NULL,
|
|
63
|
+
payload TEXT NOT NULL,
|
|
64
|
+
verified_at TEXT,
|
|
65
|
+
failed_at TEXT,
|
|
66
|
+
notes TEXT,
|
|
67
|
+
created_at TEXT NOT NULL,
|
|
68
|
+
updated_at TEXT NOT NULL,
|
|
69
|
+
UNIQUE(app_id, url_pattern, kind, purpose)
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_entries_lookup ON entries(app_id, url_pattern);
|
|
73
73
|
`);
|
|
74
74
|
}
|
|
75
75
|
export function closeDb() {
|
package/dist/map/queries.js
CHANGED
|
@@ -49,7 +49,7 @@ export function upsertApp(input) {
|
|
|
49
49
|
return db.prepare('SELECT * FROM apps WHERE id = ?').get(existing.id);
|
|
50
50
|
}
|
|
51
51
|
const info = db
|
|
52
|
-
.prepare(`INSERT INTO apps (origin, app_key, title, notes, created_at, last_seen_at)
|
|
52
|
+
.prepare(`INSERT INTO apps (origin, app_key, title, notes, created_at, last_seen_at)
|
|
53
53
|
VALUES (?, ?, ?, ?, ?, ?)`)
|
|
54
54
|
.run(input.origin, app_key, input.title ?? null, input.notes ?? null, ts, ts);
|
|
55
55
|
return db.prepare('SELECT * FROM apps WHERE id = ?').get(info.lastInsertRowid);
|
|
@@ -67,8 +67,8 @@ export function saveEntry(input) {
|
|
|
67
67
|
.prepare(`SELECT * FROM entries WHERE app_id = ? AND url_pattern = ? AND kind = ? AND purpose = ?`)
|
|
68
68
|
.get(app.id, input.url_pattern, input.kind, input.purpose);
|
|
69
69
|
if (existing) {
|
|
70
|
-
db.prepare(`UPDATE entries
|
|
71
|
-
SET payload = ?, notes = COALESCE(?, notes), verified_at = ?, failed_at = NULL, updated_at = ?
|
|
70
|
+
db.prepare(`UPDATE entries
|
|
71
|
+
SET payload = ?, notes = COALESCE(?, notes), verified_at = ?, failed_at = NULL, updated_at = ?
|
|
72
72
|
WHERE id = ?`).run(payloadJson, input.notes ?? null, ts, ts, existing.id);
|
|
73
73
|
const updated = db
|
|
74
74
|
.prepare('SELECT * FROM entries WHERE id = ?')
|
|
@@ -76,7 +76,7 @@ export function saveEntry(input) {
|
|
|
76
76
|
return { app, entry: hydrate(updated) };
|
|
77
77
|
}
|
|
78
78
|
const info = db
|
|
79
|
-
.prepare(`INSERT INTO entries (app_id, url_pattern, kind, purpose, payload, verified_at, notes, created_at, updated_at)
|
|
79
|
+
.prepare(`INSERT INTO entries (app_id, url_pattern, kind, purpose, payload, verified_at, notes, created_at, updated_at)
|
|
80
80
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
81
81
|
.run(app.id, input.url_pattern, input.kind, input.purpose, payloadJson, ts, input.notes ?? null, ts, ts);
|
|
82
82
|
const inserted = db
|
|
@@ -1,50 +1,50 @@
|
|
|
1
1
|
/** Usage protocol pushed to the MCP client on `initialize`. Plain string,
|
|
2
2
|
* intentionally kept short. Edit here when the protocol changes. */
|
|
3
|
-
export const SERVER_INSTRUCTIONS = `browser-link bridges Claude Code to the Chrome tabs the user has
|
|
4
|
-
explicitly connected through the companion extension, and ships a
|
|
5
|
-
persistent UI map backed by a local SQLite DB. The data dir resolves
|
|
6
|
-
per-OS via env-paths (\$XDG_DATA_HOME/browser-link on Linux,
|
|
7
|
-
~/Library/Application Support/browser-link on macOS, %APPDATA%/browser-link
|
|
8
|
-
on Windows). Override with \$BROWSER_LINK_DATA_DIR. The map is private
|
|
9
|
-
and per-machine; never persisted in any repo.
|
|
10
|
-
|
|
11
|
-
## When operating on a tab
|
|
12
|
-
|
|
13
|
-
1. Before doing anything on a tab whose URL you don't already know,
|
|
14
|
-
call \`browser.map.recall\` with { origin } (and optionally url) to load
|
|
15
|
-
selectors, flows and gotchas previously learned for that app.
|
|
16
|
-
2. If recall returns entries with \`failed_at\` more recent than
|
|
17
|
-
\`verified_at\`, treat them as suspect: re-verify (snapshot / evaluate)
|
|
18
|
-
before reusing, or replace them.
|
|
19
|
-
3. After every interaction that used a map entry, call
|
|
20
|
-
\`browser.map.record_use\` with { entry_id, ok }. ok=true updates
|
|
21
|
-
verified_at; ok=false updates failed_at. Keep the map honest.
|
|
22
|
-
4. After a non-trivial flow that worked end-to-end, persist it with
|
|
23
|
-
\`browser.map.save\`. Three \`kind\` values:
|
|
24
|
-
- selector: { selector, evidence? } — a CSS selector tied to a purpose.
|
|
25
|
-
- flow: { steps: [...] } — an ordered list of actions to reach an outcome.
|
|
26
|
-
- gotcha: { body } — free-form note about something non-obvious.
|
|
27
|
-
Use \`url_pattern\` = pathname (exact). Promote to glob only if you have
|
|
28
|
-
evidence of a parametric route. Provide \`purpose\` as a stable, reusable
|
|
29
|
-
label ("open task detail dialog", not "open IB0311 detail").
|
|
30
|
-
5. Never save selectors or flows you have not just successfully executed.
|
|
31
|
-
6. Never store domain data (IDs, user names, dates, etc.). The map captures
|
|
32
|
-
UI structure only.
|
|
33
|
-
|
|
34
|
-
## Identifying the app
|
|
35
|
-
|
|
36
|
-
- \`origin\` = scheme://host:port of the tab.
|
|
37
|
-
- \`app_key\` distinguishes apps that share an origin over time. On first
|
|
38
|
-
save you may omit it; it will be derived from the page title (slugified).
|
|
39
|
-
Use \`browser.map.rename_app\` if that initial guess is poor.
|
|
40
|
-
|
|
41
|
-
## When something is wrong
|
|
42
|
-
|
|
43
|
-
- A selector from recall fails → record_use({ok:false}), learn the new
|
|
44
|
-
one, save it (upsert on purpose).
|
|
45
|
-
- A whole app got refactored → \`browser.map.forget\` the app_id and let
|
|
46
|
-
the map repopulate as you learn the new structure.
|
|
47
|
-
|
|
48
|
-
The map is a cache of navigation, not a substitute for \`browser.snapshot\`.
|
|
3
|
+
export const SERVER_INSTRUCTIONS = `browser-link bridges Claude Code to the Chrome tabs the user has
|
|
4
|
+
explicitly connected through the companion extension, and ships a
|
|
5
|
+
persistent UI map backed by a local SQLite DB. The data dir resolves
|
|
6
|
+
per-OS via env-paths (\$XDG_DATA_HOME/browser-link on Linux,
|
|
7
|
+
~/Library/Application Support/browser-link on macOS, %APPDATA%/browser-link
|
|
8
|
+
on Windows). Override with \$BROWSER_LINK_DATA_DIR. The map is private
|
|
9
|
+
and per-machine; never persisted in any repo.
|
|
10
|
+
|
|
11
|
+
## When operating on a tab
|
|
12
|
+
|
|
13
|
+
1. Before doing anything on a tab whose URL you don't already know,
|
|
14
|
+
call \`browser.map.recall\` with { origin } (and optionally url) to load
|
|
15
|
+
selectors, flows and gotchas previously learned for that app.
|
|
16
|
+
2. If recall returns entries with \`failed_at\` more recent than
|
|
17
|
+
\`verified_at\`, treat them as suspect: re-verify (snapshot / evaluate)
|
|
18
|
+
before reusing, or replace them.
|
|
19
|
+
3. After every interaction that used a map entry, call
|
|
20
|
+
\`browser.map.record_use\` with { entry_id, ok }. ok=true updates
|
|
21
|
+
verified_at; ok=false updates failed_at. Keep the map honest.
|
|
22
|
+
4. After a non-trivial flow that worked end-to-end, persist it with
|
|
23
|
+
\`browser.map.save\`. Three \`kind\` values:
|
|
24
|
+
- selector: { selector, evidence? } — a CSS selector tied to a purpose.
|
|
25
|
+
- flow: { steps: [...] } — an ordered list of actions to reach an outcome.
|
|
26
|
+
- gotcha: { body } — free-form note about something non-obvious.
|
|
27
|
+
Use \`url_pattern\` = pathname (exact). Promote to glob only if you have
|
|
28
|
+
evidence of a parametric route. Provide \`purpose\` as a stable, reusable
|
|
29
|
+
label ("open task detail dialog", not "open IB0311 detail").
|
|
30
|
+
5. Never save selectors or flows you have not just successfully executed.
|
|
31
|
+
6. Never store domain data (IDs, user names, dates, etc.). The map captures
|
|
32
|
+
UI structure only.
|
|
33
|
+
|
|
34
|
+
## Identifying the app
|
|
35
|
+
|
|
36
|
+
- \`origin\` = scheme://host:port of the tab.
|
|
37
|
+
- \`app_key\` distinguishes apps that share an origin over time. On first
|
|
38
|
+
save you may omit it; it will be derived from the page title (slugified).
|
|
39
|
+
Use \`browser.map.rename_app\` if that initial guess is poor.
|
|
40
|
+
|
|
41
|
+
## When something is wrong
|
|
42
|
+
|
|
43
|
+
- A selector from recall fails → record_use({ok:false}), learn the new
|
|
44
|
+
one, save it (upsert on purpose).
|
|
45
|
+
- A whole app got refactored → \`browser.map.forget\` the app_id and let
|
|
46
|
+
the map repopulate as you learn the new structure.
|
|
47
|
+
|
|
48
|
+
The map is a cache of navigation, not a substitute for \`browser.snapshot\`.
|
|
49
49
|
The live snapshot is always the source of truth.`;
|
|
50
50
|
//# sourceMappingURL=server-instructions.js.map
|
package/package.json
CHANGED
|
@@ -1,61 +1,64 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@jobshimo/browser-link",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "MCP server that bridges Claude
|
|
5
|
-
"keywords": [
|
|
6
|
-
"mcp",
|
|
7
|
-
"model-context-protocol",
|
|
8
|
-
"claude",
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@jobshimo/browser-link",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server that bridges Claude Code, OpenCode and other MCP clients to a Chrome tab, with a persistent UI map of selectors, flows and gotchas across sessions.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"model-context-protocol",
|
|
8
|
+
"claude",
|
|
9
|
+
"claude-code",
|
|
10
|
+
"opencode",
|
|
11
|
+
"chrome",
|
|
12
|
+
"browser-automation",
|
|
13
|
+
"ai-tools"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/jobshimo/browser-link#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/jobshimo/browser-link/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/jobshimo/browser-link.git",
|
|
22
|
+
"directory": "packages/server"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "Martín Miguel Bernal",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"bin": {
|
|
29
|
+
"browser-link": "./dist/cli.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc",
|
|
41
|
+
"dev": "tsx watch src/cli.ts",
|
|
42
|
+
"start": "node dist/cli.js",
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
44
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"prepublishOnly": "npm run clean && npm run build && node ./scripts/prepare-publish.mjs"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@clack/prompts": "^1.3.0",
|
|
51
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
52
|
+
"better-sqlite3": "^11.5.0",
|
|
53
|
+
"env-paths": "^3.0.0",
|
|
54
|
+
"ws": "^8.18.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/better-sqlite3": "^7.6.11",
|
|
58
|
+
"@types/node": "^22.0.0",
|
|
59
|
+
"@types/ws": "^8.5.13",
|
|
60
|
+
"tsx": "^4.19.0",
|
|
61
|
+
"typescript": "^5.6.0",
|
|
62
|
+
"vitest": "^3.1.4"
|
|
63
|
+
}
|
|
64
|
+
}
|
package/dist/commands/tty.d.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
export declare const ansi: {
|
|
2
|
-
reset: string;
|
|
3
|
-
bold: string;
|
|
4
|
-
dim: string;
|
|
5
|
-
italic: string;
|
|
6
|
-
underline: string;
|
|
7
|
-
black: string;
|
|
8
|
-
red: string;
|
|
9
|
-
green: string;
|
|
10
|
-
yellow: string;
|
|
11
|
-
blue: string;
|
|
12
|
-
magenta: string;
|
|
13
|
-
cyan: string;
|
|
14
|
-
white: string;
|
|
15
|
-
gray: string;
|
|
16
|
-
bgYellow: string;
|
|
17
|
-
bgRed: string;
|
|
18
|
-
clearScreen: string;
|
|
19
|
-
hideCursor: string;
|
|
20
|
-
showCursor: string;
|
|
21
|
-
};
|
|
22
|
-
export declare const KEY: {
|
|
23
|
-
CTRL_C: string;
|
|
24
|
-
ESC: string;
|
|
25
|
-
ENTER_CR: string;
|
|
26
|
-
ENTER_LF: string;
|
|
27
|
-
UP: string;
|
|
28
|
-
DOWN: string;
|
|
29
|
-
RIGHT: string;
|
|
30
|
-
LEFT: string;
|
|
31
|
-
};
|
|
32
|
-
/** Visible width of a string (ignores ANSI escape sequences). Treats every
|
|
33
|
-
* other code point as 1 column — fine for ASCII + the Latin range we use. */
|
|
34
|
-
export declare function visibleWidth(s: string): number;
|
|
35
|
-
export declare function padRight(s: string, width: number): string;
|
|
36
|
-
/** Wrap a line at the given width, preserving simple ANSI runs. */
|
|
37
|
-
export declare function wrap(text: string, width: number): string[];
|
|
38
|
-
export interface BoxOptions {
|
|
39
|
-
width?: number;
|
|
40
|
-
borderColor?: string;
|
|
41
|
-
}
|
|
42
|
-
/** Render an array of lines inside a rounded Unicode box. */
|
|
43
|
-
export declare function renderBox(lines: string[], opts?: BoxOptions): string;
|
|
44
|
-
/** Read a single keypress from stdin without echoing. Returns the raw key
|
|
45
|
-
* (a control sequence for arrows, '\r' for enter, etc.). */
|
|
46
|
-
export declare function readKey(): Promise<string>;
|
|
47
|
-
/** Classify a raw key into a logical name. */
|
|
48
|
-
export declare function classifyKey(raw: string): string;
|
|
49
|
-
export declare function clearScreen(): void;
|
|
50
|
-
export declare function hideCursor(): void;
|
|
51
|
-
export declare function showCursor(): void;
|
package/dist/commands/tty.js
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { stdin, stdout } from 'node:process';
|
|
2
|
-
/* ANSI helpers — no dependencies. Modern terminals (macOS Terminal, iTerm,
|
|
3
|
-
* Windows Terminal, PowerShell 7+, every Linux TTY) understand these.
|
|
4
|
-
* Old cmd.exe may render the box characters poorly but the flow still works. */
|
|
5
|
-
const ESC = '\x1b';
|
|
6
|
-
export const ansi = {
|
|
7
|
-
reset: `${ESC}[0m`,
|
|
8
|
-
bold: `${ESC}[1m`,
|
|
9
|
-
dim: `${ESC}[2m`,
|
|
10
|
-
italic: `${ESC}[3m`,
|
|
11
|
-
underline: `${ESC}[4m`,
|
|
12
|
-
black: `${ESC}[30m`,
|
|
13
|
-
red: `${ESC}[31m`,
|
|
14
|
-
green: `${ESC}[32m`,
|
|
15
|
-
yellow: `${ESC}[33m`,
|
|
16
|
-
blue: `${ESC}[34m`,
|
|
17
|
-
magenta: `${ESC}[35m`,
|
|
18
|
-
cyan: `${ESC}[36m`,
|
|
19
|
-
white: `${ESC}[37m`,
|
|
20
|
-
gray: `${ESC}[90m`,
|
|
21
|
-
bgYellow: `${ESC}[43m`,
|
|
22
|
-
bgRed: `${ESC}[41m`,
|
|
23
|
-
clearScreen: `${ESC}[2J${ESC}[H`,
|
|
24
|
-
hideCursor: `${ESC}[?25l`,
|
|
25
|
-
showCursor: `${ESC}[?25h`,
|
|
26
|
-
};
|
|
27
|
-
export const KEY = {
|
|
28
|
-
CTRL_C: '\x03',
|
|
29
|
-
ESC: '\x1b',
|
|
30
|
-
ENTER_CR: '\r',
|
|
31
|
-
ENTER_LF: '\n',
|
|
32
|
-
UP: '\x1b[A',
|
|
33
|
-
DOWN: '\x1b[B',
|
|
34
|
-
RIGHT: '\x1b[C',
|
|
35
|
-
LEFT: '\x1b[D',
|
|
36
|
-
};
|
|
37
|
-
/** Visible width of a string (ignores ANSI escape sequences). Treats every
|
|
38
|
-
* other code point as 1 column — fine for ASCII + the Latin range we use. */
|
|
39
|
-
export function visibleWidth(s) {
|
|
40
|
-
// eslint-disable-next-line no-control-regex
|
|
41
|
-
return s.replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, '').length;
|
|
42
|
-
}
|
|
43
|
-
export function padRight(s, width) {
|
|
44
|
-
const diff = width - visibleWidth(s);
|
|
45
|
-
return diff > 0 ? s + ' '.repeat(diff) : s;
|
|
46
|
-
}
|
|
47
|
-
/** Wrap a line at the given width, preserving simple ANSI runs. */
|
|
48
|
-
export function wrap(text, width) {
|
|
49
|
-
if (width <= 0)
|
|
50
|
-
return [text];
|
|
51
|
-
const lines = [];
|
|
52
|
-
for (const paragraph of text.split('\n')) {
|
|
53
|
-
if (visibleWidth(paragraph) <= width) {
|
|
54
|
-
lines.push(paragraph);
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
const words = paragraph.split(/\s+/);
|
|
58
|
-
let cur = '';
|
|
59
|
-
for (const w of words) {
|
|
60
|
-
if (cur.length === 0) {
|
|
61
|
-
cur = w;
|
|
62
|
-
}
|
|
63
|
-
else if (visibleWidth(cur) + 1 + visibleWidth(w) > width) {
|
|
64
|
-
lines.push(cur);
|
|
65
|
-
cur = w;
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
cur += ' ' + w;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
if (cur.length > 0)
|
|
72
|
-
lines.push(cur);
|
|
73
|
-
}
|
|
74
|
-
return lines;
|
|
75
|
-
}
|
|
76
|
-
const B = {
|
|
77
|
-
tl: '╭',
|
|
78
|
-
tr: '╮',
|
|
79
|
-
bl: '╰',
|
|
80
|
-
br: '╯',
|
|
81
|
-
h: '─',
|
|
82
|
-
v: '│',
|
|
83
|
-
};
|
|
84
|
-
/** Render an array of lines inside a rounded Unicode box. */
|
|
85
|
-
export function renderBox(lines, opts = {}) {
|
|
86
|
-
const termWidth = stdout.columns ?? 80;
|
|
87
|
-
// 86 cols comfortably fits the longest pre-formatted line in About
|
|
88
|
-
// (tool name + padded description). Smaller terminals still get the cap.
|
|
89
|
-
const width = Math.min(opts.width ?? 86, Math.max(40, termWidth - 2));
|
|
90
|
-
const inner = width - 2; // 2 columns for the vertical borders
|
|
91
|
-
const wrapped = lines.flatMap((l) => wrap(l, inner - 2)); // padding 1 each side
|
|
92
|
-
const color = opts.borderColor ?? '';
|
|
93
|
-
const reset = color ? ansi.reset : '';
|
|
94
|
-
const top = `${color}${B.tl}${B.h.repeat(inner)}${B.tr}${reset}`;
|
|
95
|
-
const bottom = `${color}${B.bl}${B.h.repeat(inner)}${B.br}${reset}`;
|
|
96
|
-
const body = wrapped.map((l) => `${color}${B.v}${reset} ${padRight(l, inner - 2)} ${color}${B.v}${reset}`);
|
|
97
|
-
return [top, ...body, bottom].join('\n');
|
|
98
|
-
}
|
|
99
|
-
/** Read a single keypress from stdin without echoing. Returns the raw key
|
|
100
|
-
* (a control sequence for arrows, '\r' for enter, etc.). */
|
|
101
|
-
export function readKey() {
|
|
102
|
-
return new Promise((resolve) => {
|
|
103
|
-
const wasRaw = stdin.isTTY ? stdin.isRaw : false;
|
|
104
|
-
if (stdin.isTTY)
|
|
105
|
-
stdin.setRawMode(true);
|
|
106
|
-
stdin.resume();
|
|
107
|
-
stdin.setEncoding('utf8');
|
|
108
|
-
const onData = (data) => {
|
|
109
|
-
const key = data.toString();
|
|
110
|
-
stdin.off('data', onData);
|
|
111
|
-
if (stdin.isTTY)
|
|
112
|
-
stdin.setRawMode(wasRaw);
|
|
113
|
-
stdin.pause();
|
|
114
|
-
resolve(key);
|
|
115
|
-
};
|
|
116
|
-
stdin.on('data', onData);
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
/** Classify a raw key into a logical name. */
|
|
120
|
-
export function classifyKey(raw) {
|
|
121
|
-
if (raw === KEY.CTRL_C)
|
|
122
|
-
return 'ctrl-c';
|
|
123
|
-
if (raw === KEY.ESC)
|
|
124
|
-
return 'esc';
|
|
125
|
-
if (raw === KEY.ENTER_CR || raw === KEY.ENTER_LF)
|
|
126
|
-
return 'enter';
|
|
127
|
-
if (raw === KEY.UP)
|
|
128
|
-
return 'up';
|
|
129
|
-
if (raw === KEY.DOWN)
|
|
130
|
-
return 'down';
|
|
131
|
-
if (raw === KEY.LEFT)
|
|
132
|
-
return 'left';
|
|
133
|
-
if (raw === KEY.RIGHT)
|
|
134
|
-
return 'right';
|
|
135
|
-
if (raw.length === 1)
|
|
136
|
-
return raw.toLowerCase();
|
|
137
|
-
return raw;
|
|
138
|
-
}
|
|
139
|
-
export function clearScreen() {
|
|
140
|
-
stdout.write(ansi.clearScreen);
|
|
141
|
-
}
|
|
142
|
-
export function hideCursor() {
|
|
143
|
-
stdout.write(ansi.hideCursor);
|
|
144
|
-
}
|
|
145
|
-
export function showCursor() {
|
|
146
|
-
stdout.write(ansi.showCursor);
|
|
147
|
-
}
|
|
148
|
-
//# sourceMappingURL=tty.js.map
|
package/dist/commands/tty.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tty.js","sourceRoot":"","sources":["../../src/commands/tty.ts"],"names":[],"mappings":"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"}
|