@jobshimo/browser-link 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +102 -83
- package/dist/cli.js +50 -36
- package/dist/cli.js.map +1 -1
- package/dist/commands/about.d.ts +17 -18
- package/dist/commands/about.js +149 -190
- package/dist/commands/about.js.map +1 -1
- package/dist/commands/updates.d.ts +19 -0
- package/dist/commands/updates.js +83 -0
- package/dist/commands/updates.js.map +1 -0
- package/dist/commands/welcome.d.ts +10 -8
- package/dist/commands/welcome.js +49 -128
- 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/copilot.d.ts +2 -0
- package/dist/installers/copilot.js +72 -0
- package/dist/installers/copilot.js.map +1 -0
- package/dist/installers/index.d.ts +2 -3
- package/dist/installers/index.js +5 -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/installers/types.d.ts +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/dist/ui/app.d.ts +7 -0
- package/dist/ui/app.js +62 -0
- package/dist/ui/app.js.map +1 -0
- package/dist/ui/components.d.ts +18 -0
- package/dist/ui/components.js +27 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/screens.d.ts +48 -0
- package/dist/ui/screens.js +291 -0
- package/dist/ui/screens.js.map +1 -0
- package/dist/ui/start.d.ts +6 -0
- package/dist/ui/start.js +19 -0
- package/dist/ui/start.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +15 -0
- package/dist/version.js.map +1 -0
- package/package.json +69 -61
- package/dist/commands/menu.d.ts +0 -26
- package/dist/commands/menu.js +0 -187
- package/dist/commands/menu.js.map +0 -1
- package/dist/commands/tty.d.ts +0 -51
- package/dist/commands/tty.js +0 -148
- package/dist/commands/tty.js.map +0 -1
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="es">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<title>browser-link</title>
|
|
6
|
-
<style>
|
|
7
|
-
:root {
|
|
8
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
9
|
-
}
|
|
10
|
-
body {
|
|
11
|
-
margin: 0;
|
|
12
|
-
padding: 14px;
|
|
13
|
-
width: 280px;
|
|
14
|
-
color: #1f2937;
|
|
15
|
-
}
|
|
16
|
-
h1 {
|
|
17
|
-
font-size: 14px;
|
|
18
|
-
font-weight: 600;
|
|
19
|
-
margin: 0 0 10px;
|
|
20
|
-
letter-spacing: 0.3px;
|
|
21
|
-
}
|
|
22
|
-
.status {
|
|
23
|
-
padding: 8px 10px;
|
|
24
|
-
border-radius: 6px;
|
|
25
|
-
margin-bottom: 8px;
|
|
26
|
-
font-size: 12px;
|
|
27
|
-
line-height: 1.4;
|
|
28
|
-
}
|
|
29
|
-
.status.connected {
|
|
30
|
-
background: #d1fae5;
|
|
31
|
-
color: #065f46;
|
|
32
|
-
}
|
|
33
|
-
.status.disconnected {
|
|
34
|
-
background: #fef3c7;
|
|
35
|
-
color: #92400e;
|
|
36
|
-
}
|
|
37
|
-
.status.error {
|
|
38
|
-
background: #fee2e2;
|
|
39
|
-
color: #991b1b;
|
|
40
|
-
}
|
|
41
|
-
.url {
|
|
42
|
-
font-size: 11px;
|
|
43
|
-
color: #6b7280;
|
|
44
|
-
word-break: break-all;
|
|
45
|
-
margin-bottom: 10px;
|
|
46
|
-
max-height: 36px;
|
|
47
|
-
overflow: hidden;
|
|
48
|
-
}
|
|
49
|
-
.tab-id {
|
|
50
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
51
|
-
font-weight: 600;
|
|
52
|
-
}
|
|
53
|
-
button {
|
|
54
|
-
width: 100%;
|
|
55
|
-
padding: 9px 12px;
|
|
56
|
-
border: none;
|
|
57
|
-
border-radius: 6px;
|
|
58
|
-
cursor: pointer;
|
|
59
|
-
font-size: 13px;
|
|
60
|
-
font-weight: 500;
|
|
61
|
-
transition: opacity 0.15s ease;
|
|
62
|
-
}
|
|
63
|
-
button.primary {
|
|
64
|
-
background: #2563eb;
|
|
65
|
-
color: white;
|
|
66
|
-
}
|
|
67
|
-
button.danger {
|
|
68
|
-
background: #dc2626;
|
|
69
|
-
color: white;
|
|
70
|
-
}
|
|
71
|
-
button:hover {
|
|
72
|
-
opacity: 0.9;
|
|
73
|
-
}
|
|
74
|
-
button:disabled {
|
|
75
|
-
background: #9ca3af;
|
|
76
|
-
cursor: not-allowed;
|
|
77
|
-
opacity: 0.6;
|
|
78
|
-
}
|
|
79
|
-
</style>
|
|
80
|
-
</head>
|
|
81
|
-
<body>
|
|
82
|
-
<h1>browser-link</h1>
|
|
83
|
-
<div id="status" class="status disconnected">Cargando…</div>
|
|
84
|
-
<div id="url" class="url"></div>
|
|
85
|
-
<button id="action" class="primary" disabled>…</button>
|
|
86
|
-
<script type="module" src="popup.js"></script>
|
|
87
|
-
</body>
|
|
88
|
-
</html>
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="es">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>browser-link</title>
|
|
6
|
+
<style>
|
|
7
|
+
:root {
|
|
8
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
9
|
+
}
|
|
10
|
+
body {
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 14px;
|
|
13
|
+
width: 280px;
|
|
14
|
+
color: #1f2937;
|
|
15
|
+
}
|
|
16
|
+
h1 {
|
|
17
|
+
font-size: 14px;
|
|
18
|
+
font-weight: 600;
|
|
19
|
+
margin: 0 0 10px;
|
|
20
|
+
letter-spacing: 0.3px;
|
|
21
|
+
}
|
|
22
|
+
.status {
|
|
23
|
+
padding: 8px 10px;
|
|
24
|
+
border-radius: 6px;
|
|
25
|
+
margin-bottom: 8px;
|
|
26
|
+
font-size: 12px;
|
|
27
|
+
line-height: 1.4;
|
|
28
|
+
}
|
|
29
|
+
.status.connected {
|
|
30
|
+
background: #d1fae5;
|
|
31
|
+
color: #065f46;
|
|
32
|
+
}
|
|
33
|
+
.status.disconnected {
|
|
34
|
+
background: #fef3c7;
|
|
35
|
+
color: #92400e;
|
|
36
|
+
}
|
|
37
|
+
.status.error {
|
|
38
|
+
background: #fee2e2;
|
|
39
|
+
color: #991b1b;
|
|
40
|
+
}
|
|
41
|
+
.url {
|
|
42
|
+
font-size: 11px;
|
|
43
|
+
color: #6b7280;
|
|
44
|
+
word-break: break-all;
|
|
45
|
+
margin-bottom: 10px;
|
|
46
|
+
max-height: 36px;
|
|
47
|
+
overflow: hidden;
|
|
48
|
+
}
|
|
49
|
+
.tab-id {
|
|
50
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
51
|
+
font-weight: 600;
|
|
52
|
+
}
|
|
53
|
+
button {
|
|
54
|
+
width: 100%;
|
|
55
|
+
padding: 9px 12px;
|
|
56
|
+
border: none;
|
|
57
|
+
border-radius: 6px;
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
font-size: 13px;
|
|
60
|
+
font-weight: 500;
|
|
61
|
+
transition: opacity 0.15s ease;
|
|
62
|
+
}
|
|
63
|
+
button.primary {
|
|
64
|
+
background: #2563eb;
|
|
65
|
+
color: white;
|
|
66
|
+
}
|
|
67
|
+
button.danger {
|
|
68
|
+
background: #dc2626;
|
|
69
|
+
color: white;
|
|
70
|
+
}
|
|
71
|
+
button:hover {
|
|
72
|
+
opacity: 0.9;
|
|
73
|
+
}
|
|
74
|
+
button:disabled {
|
|
75
|
+
background: #9ca3af;
|
|
76
|
+
cursor: not-allowed;
|
|
77
|
+
opacity: 0.6;
|
|
78
|
+
}
|
|
79
|
+
</style>
|
|
80
|
+
</head>
|
|
81
|
+
<body>
|
|
82
|
+
<h1>browser-link</h1>
|
|
83
|
+
<div id="status" class="status disconnected">Cargando…</div>
|
|
84
|
+
<div id="url" class="url"></div>
|
|
85
|
+
<button id="action" class="primary" disabled>…</button>
|
|
86
|
+
<script type="module" src="popup.js"></script>
|
|
87
|
+
</body>
|
|
88
|
+
</html>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
const SERVER_NAME = 'browser-link';
|
|
5
|
+
function configFile() {
|
|
6
|
+
// GitHub Copilot CLI reads from ~/.copilot/mcp-config.json by default.
|
|
7
|
+
// COPILOT_HOME overrides the directory (the same env var the CLI honours).
|
|
8
|
+
const root = process.env.COPILOT_HOME ?? join(homedir(), '.copilot');
|
|
9
|
+
return join(root, 'mcp-config.json');
|
|
10
|
+
}
|
|
11
|
+
function readConfig(path) {
|
|
12
|
+
if (!existsSync(path))
|
|
13
|
+
return {};
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
throw new Error(`Could not parse Copilot config at ${path}. Fix the file or delete it.`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function writeConfig(path, cfg) {
|
|
22
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
23
|
+
writeFileSync(path, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
|
|
24
|
+
}
|
|
25
|
+
function isRegistered(cfg) {
|
|
26
|
+
return !!cfg.mcpServers?.[SERVER_NAME];
|
|
27
|
+
}
|
|
28
|
+
export const copilotInstaller = {
|
|
29
|
+
id: 'copilot',
|
|
30
|
+
displayName: 'GitHub Copilot CLI',
|
|
31
|
+
configPath() {
|
|
32
|
+
return configFile();
|
|
33
|
+
},
|
|
34
|
+
detect() {
|
|
35
|
+
const path = configFile();
|
|
36
|
+
if (!existsSync(path)) {
|
|
37
|
+
return { installed: false, registered: false, configPath: path };
|
|
38
|
+
}
|
|
39
|
+
const cfg = readConfig(path);
|
|
40
|
+
return { installed: true, registered: isRegistered(cfg), configPath: path };
|
|
41
|
+
},
|
|
42
|
+
install(command, args) {
|
|
43
|
+
const path = configFile();
|
|
44
|
+
const cfg = readConfig(path);
|
|
45
|
+
cfg.mcpServers = cfg.mcpServers ?? {};
|
|
46
|
+
const existing = cfg.mcpServers[SERVER_NAME];
|
|
47
|
+
// Copilot requires `env` and `tools` even when empty/wildcard.
|
|
48
|
+
cfg.mcpServers[SERVER_NAME] = {
|
|
49
|
+
type: 'local',
|
|
50
|
+
command,
|
|
51
|
+
args,
|
|
52
|
+
env: {},
|
|
53
|
+
tools: ['*'],
|
|
54
|
+
};
|
|
55
|
+
writeConfig(path, cfg);
|
|
56
|
+
return existing
|
|
57
|
+
? `Updated ${SERVER_NAME} entry in ${path}.`
|
|
58
|
+
: `Added ${SERVER_NAME} entry to ${path}.`;
|
|
59
|
+
},
|
|
60
|
+
uninstall() {
|
|
61
|
+
const path = configFile();
|
|
62
|
+
if (!existsSync(path))
|
|
63
|
+
return `No Copilot CLI config at ${path}; nothing to remove.`;
|
|
64
|
+
const cfg = readConfig(path);
|
|
65
|
+
if (!cfg.mcpServers?.[SERVER_NAME])
|
|
66
|
+
return `${SERVER_NAME} was not registered in ${path}.`;
|
|
67
|
+
delete cfg.mcpServers[SERVER_NAME];
|
|
68
|
+
writeConfig(path, cfg);
|
|
69
|
+
return `Removed ${SERVER_NAME} entry from ${path}.`;
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=copilot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copilot.js","sourceRoot":"","sources":["../../src/installers/copilot.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { ClientId, Installer } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Installers
|
|
4
|
-
*
|
|
5
|
-
* will never invoke its (throwing) install/uninstall stubs.
|
|
3
|
+
* Installers wired into the CLI surface (`install`, `install --client X`,
|
|
4
|
+
* the interactive menu, and `doctor`). Order here is the display order.
|
|
6
5
|
*/
|
|
7
6
|
export declare const INSTALLERS: Installer[];
|
|
8
7
|
export declare function getInstaller(id: ClientId): Installer;
|
package/dist/installers/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { claudeInstaller } from './claude.js';
|
|
2
|
+
import { copilotInstaller } from './copilot.js';
|
|
3
|
+
import { opencodeInstaller } from './opencode.js';
|
|
2
4
|
/**
|
|
3
|
-
* Installers
|
|
4
|
-
*
|
|
5
|
-
* will never invoke its (throwing) install/uninstall stubs.
|
|
5
|
+
* Installers wired into the CLI surface (`install`, `install --client X`,
|
|
6
|
+
* the interactive menu, and `doctor`). Order here is the display order.
|
|
6
7
|
*/
|
|
7
|
-
export const INSTALLERS = [claudeInstaller];
|
|
8
|
+
export const INSTALLERS = [claudeInstaller, opencodeInstaller, copilotInstaller];
|
|
8
9
|
export function getInstaller(id) {
|
|
9
10
|
const found = INSTALLERS.find((i) => i.id === id);
|
|
10
11
|
if (!found)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/installers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/installers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGlD;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAgB,CAAC,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,CAAC,CAAC;AAE9F,MAAM,UAAU,YAAY,CAAC,EAAY;IACvC,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IAClD,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,EAAE,EAAE,CAAC,CAAC;IACpE,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -1,23 +1,29 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { homedir
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* detect() looks for the conventional config path per OS; install/uninstall
|
|
7
|
-
* throw with a clear message so the CLI surface is consistent.
|
|
8
|
-
*/
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
const SERVER_NAME = 'browser-link';
|
|
5
|
+
const SCHEMA_URL = 'https://opencode.ai/config.json';
|
|
9
6
|
function configFile() {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
// OpenCode uses ~/.config/opencode/opencode.json on every OS, Windows included
|
|
8
|
+
// (verified against an actual install — not %APPDATA% as it might seem).
|
|
9
|
+
return join(homedir(), '.config', 'opencode', 'opencode.json');
|
|
10
|
+
}
|
|
11
|
+
function readConfig(path) {
|
|
12
|
+
if (!existsSync(path))
|
|
13
|
+
return {};
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
13
16
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return join(xdg, 'opencode', 'opencode.json');
|
|
17
|
-
if (platform() === 'darwin') {
|
|
18
|
-
return join(homedir(), 'Library', 'Application Support', 'opencode', 'opencode.json');
|
|
17
|
+
catch {
|
|
18
|
+
throw new Error(`Could not parse OpenCode config at ${path}. Fix the file or delete it.`);
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
}
|
|
21
|
+
function writeConfig(path, cfg) {
|
|
22
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
23
|
+
writeFileSync(path, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
|
|
24
|
+
}
|
|
25
|
+
function isRegistered(cfg) {
|
|
26
|
+
return !!cfg.mcp?.[SERVER_NAME];
|
|
21
27
|
}
|
|
22
28
|
export const opencodeInstaller = {
|
|
23
29
|
id: 'opencode',
|
|
@@ -27,13 +33,35 @@ export const opencodeInstaller = {
|
|
|
27
33
|
},
|
|
28
34
|
detect() {
|
|
29
35
|
const path = configFile();
|
|
30
|
-
|
|
36
|
+
if (!existsSync(path)) {
|
|
37
|
+
return { installed: false, registered: false, configPath: path };
|
|
38
|
+
}
|
|
39
|
+
const cfg = readConfig(path);
|
|
40
|
+
return { installed: true, registered: isRegistered(cfg), configPath: path };
|
|
31
41
|
},
|
|
32
|
-
install() {
|
|
33
|
-
|
|
42
|
+
install(command, args) {
|
|
43
|
+
const path = configFile();
|
|
44
|
+
const cfg = readConfig(path);
|
|
45
|
+
if (!cfg.$schema)
|
|
46
|
+
cfg.$schema = SCHEMA_URL;
|
|
47
|
+
cfg.mcp = cfg.mcp ?? {};
|
|
48
|
+
const existing = cfg.mcp[SERVER_NAME];
|
|
49
|
+
cfg.mcp[SERVER_NAME] = { type: 'local', command: [command, ...args] };
|
|
50
|
+
writeConfig(path, cfg);
|
|
51
|
+
return existing
|
|
52
|
+
? `Updated ${SERVER_NAME} entry in ${path}.`
|
|
53
|
+
: `Added ${SERVER_NAME} entry to ${path}.`;
|
|
34
54
|
},
|
|
35
55
|
uninstall() {
|
|
36
|
-
|
|
56
|
+
const path = configFile();
|
|
57
|
+
if (!existsSync(path))
|
|
58
|
+
return `No OpenCode config at ${path}; nothing to remove.`;
|
|
59
|
+
const cfg = readConfig(path);
|
|
60
|
+
if (!cfg.mcp?.[SERVER_NAME])
|
|
61
|
+
return `${SERVER_NAME} was not registered in ${path}.`;
|
|
62
|
+
delete cfg.mcp[SERVER_NAME];
|
|
63
|
+
writeConfig(path, cfg);
|
|
64
|
+
return `Removed ${SERVER_NAME} entry from ${path}.`;
|
|
37
65
|
},
|
|
38
66
|
};
|
|
39
67
|
//# sourceMappingURL=opencode.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opencode.js","sourceRoot":"","sources":["../../src/installers/opencode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"opencode.js","sourceRoot":"","sources":["../../src/installers/opencode.ts"],"names":[],"mappings":"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"}
|
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/dist/ui/app.d.ts
ADDED