@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.
Files changed (51) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +102 -83
  3. package/dist/cli.js +50 -36
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/about.d.ts +17 -18
  6. package/dist/commands/about.js +149 -190
  7. package/dist/commands/about.js.map +1 -1
  8. package/dist/commands/updates.d.ts +19 -0
  9. package/dist/commands/updates.js +83 -0
  10. package/dist/commands/updates.js.map +1 -0
  11. package/dist/commands/welcome.d.ts +10 -8
  12. package/dist/commands/welcome.js +49 -128
  13. package/dist/commands/welcome.js.map +1 -1
  14. package/dist/extension/background.js +90 -90
  15. package/dist/extension/icons/icon.svg +14 -14
  16. package/dist/extension/manifest.json +28 -28
  17. package/dist/extension/popup.html +88 -88
  18. package/dist/installers/copilot.d.ts +2 -0
  19. package/dist/installers/copilot.js +72 -0
  20. package/dist/installers/copilot.js.map +1 -0
  21. package/dist/installers/index.d.ts +2 -3
  22. package/dist/installers/index.js +5 -4
  23. package/dist/installers/index.js.map +1 -1
  24. package/dist/installers/opencode.js +49 -21
  25. package/dist/installers/opencode.js.map +1 -1
  26. package/dist/installers/types.d.ts +1 -1
  27. package/dist/map/db.js +28 -28
  28. package/dist/map/queries.js +4 -4
  29. package/dist/tools/server-instructions.js +46 -46
  30. package/dist/ui/app.d.ts +7 -0
  31. package/dist/ui/app.js +62 -0
  32. package/dist/ui/app.js.map +1 -0
  33. package/dist/ui/components.d.ts +18 -0
  34. package/dist/ui/components.js +27 -0
  35. package/dist/ui/components.js.map +1 -0
  36. package/dist/ui/screens.d.ts +48 -0
  37. package/dist/ui/screens.js +291 -0
  38. package/dist/ui/screens.js.map +1 -0
  39. package/dist/ui/start.d.ts +6 -0
  40. package/dist/ui/start.js +19 -0
  41. package/dist/ui/start.js.map +1 -0
  42. package/dist/version.d.ts +2 -0
  43. package/dist/version.js +15 -0
  44. package/dist/version.js.map +1 -0
  45. package/package.json +69 -61
  46. package/dist/commands/menu.d.ts +0 -26
  47. package/dist/commands/menu.js +0 -187
  48. package/dist/commands/menu.js.map +0 -1
  49. package/dist/commands/tty.d.ts +0 -51
  50. package/dist/commands/tty.js +0 -148
  51. 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,2 @@
1
+ import type { Installer } from './types.js';
2
+ export declare const copilotInstaller: Installer;
@@ -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 that are currently shippable. OpenCode lives in opencode.ts as
4
- * a scaffold but is not part of this array yet — installFor / installAll
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;
@@ -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 that are currently shippable. OpenCode lives in opencode.ts as
4
- * a scaffold but is not part of this array yet — installFor / installAll
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;AAG9C;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAgB,CAAC,eAAe,CAAC,CAAC;AAEzD,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
+ {"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, platform } from 'node:os';
3
- import { join } from 'node:path';
4
- /**
5
- * OpenCode integration is a placeholder until we wire up its config format.
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
- if (platform() === 'win32') {
11
- const appData = process.env.APPDATA ?? join(homedir(), 'AppData', 'Roaming');
12
- return join(appData, 'opencode', 'opencode.json');
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
- const xdg = process.env.XDG_CONFIG_HOME;
15
- if (xdg && xdg.trim().length > 0)
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
- return join(homedir(), '.config', 'opencode', 'opencode.json');
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
- return { installed: existsSync(path), registered: false, configPath: path };
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
- throw new Error(`OpenCode installer is not implemented yet. Edit ${configFile()} manually or open an issue.`);
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
- throw new Error(`OpenCode installer is not implemented yet. Edit ${configFile()} manually or open an issue.`);
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;AACrC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC;;;;GAIG;AACH,SAAS,UAAU;IACjB,IAAI,QAAQ,EAAE,KAAK,OAAO,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;QAC7E,OAAO,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;IACpD,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IACxC,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;IAChF,IAAI,QAAQ,EAAE,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,qBAAqB,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAc;IAC1C,EAAE,EAAE,UAAU;IACd,WAAW,EAAE,UAAU;IAEvB,UAAU;QACR,OAAO,UAAU,EAAE,CAAC;IACtB,CAAC;IAED,MAAM;QACJ,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;QAC1B,OAAO,EAAE,SAAS,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IAC9E,CAAC;IAED,OAAO;QACL,MAAM,IAAI,KAAK,CACb,mDAAmD,UAAU,EAAE,6BAA6B,CAC7F,CAAC;IACJ,CAAC;IAED,SAAS;QACP,MAAM,IAAI,KAAK,CACb,mDAAmD,UAAU,EAAE,6BAA6B,CAC7F,CAAC;IACJ,CAAC;CACF,CAAC"}
1
+ {"version":3,"file":"opencode.js","sourceRoot":"","sources":["../../src/installers/opencode.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- export type ClientId = 'claude' | 'opencode';
1
+ export type ClientId = 'claude' | 'opencode' | 'copilot';
2
2
  export interface ClientInfo {
3
3
  id: ClientId;
4
4
  displayName: string;
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() {
@@ -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
@@ -0,0 +1,7 @@
1
+ import type { Language } from '../commands/welcome.js';
2
+ interface AppProps {
3
+ initialLanguage: Language;
4
+ skipWelcome: boolean;
5
+ }
6
+ export declare function App({ initialLanguage, skipWelcome }: AppProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};