@datasynx/agentic-ai-cartography 0.2.3 → 0.2.5

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 CHANGED
@@ -1,38 +1,76 @@
1
- # @datasynx-ai/agentic-ai-cartography
1
+ <div align="center">
2
+
3
+ # 🗺️ Datasynx Cartography
2
4
 
3
5
  **AI-powered Infrastructure Cartography & SOP Generation**
4
6
 
5
- Cartography uses the **Claude Agent SDK** to automatically discover your infrastructure, map dependencies, and generate Standard Operating Procedures from observed workflows — all from your terminal.
7
+ [![npm version](https://img.shields.io/npm/v/@datasynx/agentic-ai-cartography?style=flat-square&color=CB3837&logo=npm&logoColor=white)](https://www.npmjs.com/package/@datasynx/agentic-ai-cartography)
8
+ [![npm downloads](https://img.shields.io/npm/dm/@datasynx/agentic-ai-cartography?style=flat-square&color=CB3837&logo=npm&logoColor=white)](https://www.npmjs.com/package/@datasynx/agentic-ai-cartography)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
10
+ [![Node.js ≥18](https://img.shields.io/badge/Node.js-%E2%89%A518-339933?style=flat-square&logo=node.js&logoColor=white)](https://nodejs.org)
11
+ [![Built with Claude](https://img.shields.io/badge/Built_with-Claude_Agent_SDK-D4A017?style=flat-square&logo=anthropic&logoColor=white)](https://github.com/anthropics/claude-code)
12
+ [![LinkedIn](https://img.shields.io/badge/LinkedIn-Datasynx_AI-0077B5?style=flat-square&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/datasynx-ai/)
6
13
 
7
- ```
8
- $ cartography discover
9
- 🔍 Scanning localhost...
10
- ├── postgres:5432 (3 databases, 47 tables)
11
- ├── redis:6379 (standalone, 12 keys)
12
- ├── nginx:80 → upstream:3000 (express)
13
- │ └── GET /api/users, POST /api/auth, ...
14
- ├── rabbitmq:5672 (3 queues)
15
- └── grafana:3000 → prometheus:9090
16
- ✓ 8 nodes, 11 edges discovered
17
- ✓ Exported: catalog.json, topology.mermaid, catalog-info.yaml
14
+ <br/>
15
+
16
+ *Claude IS the agent — it decides which read-only commands to run, analyses the output, and stores results via custom MCP tools into SQLite. No hand-written parsers, diff logic, or decision trees.*
17
+
18
+ <br/>
18
19
 
19
- $ cartography shadow start
20
- 👁 Shadow daemon started (PID 48291)
21
- Observing network + processes every 30s...
20
+ **[📦 npm](https://www.npmjs.com/package/@datasynx/agentic-ai-cartography) · [💼 LinkedIn](https://www.linkedin.com/company/datasynx-ai/) · [🐛 Issues](https://github.com/datasynx/agentic-ai-cartography/issues)**
21
+
22
+ </div>
23
+
24
+ ---
22
25
 
23
- $ cartography shadow stop
24
- ✓ Shadow stopped. 142 events, 3 tasks, 2 workflows detected.
25
- ✓ Generated: sops/deploy-check.md, sops/db-migration.md
26
+ ## What it does
27
+
28
+ ```
29
+ $ datasynx-cartography discover
30
+
31
+ CARTOGRAPHY localhost
32
+ ─────────────────────────────────────────────
33
+ 🔖 Browser bookmarks scanned…
34
+ 🖥 All installed apps scanned…
35
+ + Node saas_tool:vscode [saas_tool] 90%
36
+ + Node saas_tool:cursor [saas_tool] 90%
37
+ + Node saas_tool:docker-desktop [saas_tool] 90%
38
+ + Node saas_tool:github.com [saas_tool] 70% 🔖
39
+ + Node web_service:localhost:5432 [database] 90%
40
+ + Node web_service:localhost:6379 [cache] 90%
41
+ ~ Edge web_service:app → web_service:localhost:5432 uses
42
+ ─────────────────────────────────────────────
43
+ DONE 9 nodes, 3 edges in 38.4s
44
+
45
+ WEITERSUCHEN — Discovery interaktiv verfeinern
46
+ → Suche nach (Enter = Beenden): hubspot windsurf
47
+ ⟳ Suche nach: hubspot windsurf
48
+ + Node saas_tool:hubspot.com [saas_tool] 70% 🔖
49
+ + Node saas_tool:windsurf [saas_tool] 90%
26
50
  ```
27
51
 
28
- Claude **is** the agent — it decides which read-only commands to run, analyses the output, and stores results via custom MCP tools into SQLite. No hand-written parsers, diff logic, or decision trees.
52
+ ---
53
+
54
+ ## Features
55
+
56
+ | Feature | Details |
57
+ |---------|---------|
58
+ | **Installed App Scan** | Scans `/Applications`, Homebrew, dpkg/snap/flatpak, Spotlight + 60 known tools via `which` |
59
+ | **Browser Bookmarks** | Chrome, Firefox, Safari, Brave, Edge — extracts business/SaaS domains automatically |
60
+ | **Cloud Scanning** | AWS (EC2/RDS/EKS/S3), GCP (Compute/GKE/Cloud Run), Azure (AKS/WebApps), Kubernetes |
61
+ | **Human-in-the-Loop** | Chat with the agent mid-discovery: type `"hubspot windsurf"` to search for specific tools |
62
+ | **Shadow Daemon** | Background observer every 30s — detects new services, ports, processes |
63
+ | **SOP Generation** | Automatically generates Standard Operating Procedures from observed workflows |
64
+ | **SOP Dashboard** | HTML dashboard with all SOPs, step details, frequency stats |
65
+ | **Export Formats** | Mermaid topology, D3.js interactive graph, Backstage YAML, JSON, SOP Markdown |
66
+ | **Safety First** | `PreToolUse` hook blocks all destructive Bash commands — 100% read-only |
29
67
 
30
68
  ---
31
69
 
32
70
  ## Requirements
33
71
 
34
72
  - **Node.js ≥ 18**
35
- - **Claude CLI** (runtime dependency — the Agent SDK starts it as a child process)
73
+ - **Claude CLI** — the Agent SDK starts it as a subprocess
36
74
 
37
75
  ```bash
38
76
  npm install -g @anthropic-ai/claude-code
@@ -44,55 +82,81 @@ claude login
44
82
  ## Install
45
83
 
46
84
  ```bash
47
- npm install -g @datasynx-ai/agentic-ai-cartography
85
+ npm install -g @datasynx/agentic-ai-cartography
48
86
  ```
49
87
 
88
+ [![npm](https://img.shields.io/badge/npm-@datasynx%2Fagentic--ai--cartography-CB3837?style=for-the-badge&logo=npm&logoColor=white)](https://www.npmjs.com/package/@datasynx/agentic-ai-cartography)
89
+
50
90
  ---
51
91
 
52
92
  ## Quick Start
53
93
 
54
94
  ```bash
55
- # Discover your infrastructure (one-shot, Claude Sonnet)
56
- cartography discover
95
+ # Check all requirements
96
+ datasynx-cartography doctor
97
+
98
+ # Discover your full infrastructure (one-shot, Claude Sonnet)
99
+ # → scans bookmarks, installed apps, local services, cloud, config files
100
+ # → then interactive follow-up: type tool names to search further
101
+ datasynx-cartography discover
102
+
103
+ # Seed infrastructure manually (JSON file or interactive)
104
+ datasynx-cartography seed --file infra.json
105
+ datasynx-cartography seed
106
+
107
+ # View all browser bookmarks
108
+ datasynx-cartography bookmarks
57
109
 
58
110
  # Start background observer (Claude Haiku, every 30s)
59
- cartography shadow start
111
+ datasynx-cartography shadow start
112
+
113
+ # Attach to live event feed
114
+ datasynx-cartography shadow attach # [P] pause [T] new task [D] detach [Q] stop
60
115
 
61
- # Attach to see live events
62
- cartography shadow attach
116
+ # Pause / resume the daemon
117
+ datasynx-cartography shadow pause
118
+ datasynx-cartography shadow resume
63
119
 
64
- # After observing: generate SOPs from workflows
65
- cartography sops
120
+ # Stop daemon + generate SOPs + open HTML dashboard
121
+ datasynx-cartography shadow stop
66
122
 
67
- # Stop daemon
68
- cartography shadow stop
123
+ # Generate SOPs manually from a session
124
+ datasynx-cartography sops [session-id]
69
125
 
70
- # Full feature overview
71
- cartography docs
126
+ # Full feature reference
127
+ datasynx-cartography docs
72
128
  ```
73
129
 
74
130
  ---
75
131
 
76
132
  ## Commands
77
133
 
78
- ### Discovery
134
+ ### Cartography (Discovery)
79
135
 
80
136
  ```
81
- cartography discover [options]
137
+ datasynx-cartography discover [options]
82
138
 
83
139
  --entry <hosts...> Start hosts (default: localhost)
84
140
  --depth <n> Max crawl depth (default: 8)
85
141
  --max-turns <n> Max agent turns (default: 50)
86
142
  --model <m> Claude model (default: claude-sonnet-4-5-...)
87
143
  --org <name> Org name for Backstage YAML
88
- -o, --output <dir> Output directory (default: ./cartography-output)
144
+ -o, --output <dir> Output directory (default: ./datasynx-output)
89
145
  -v, --verbose Show agent reasoning
90
146
  ```
91
147
 
148
+ Discovery pipeline (automatic, in order):
149
+ 1. **Browser bookmarks** — every domain classified as saas_tool or web_service
150
+ 2. **Installed apps** — all IDEs, business tools, dev tools, browsers
151
+ 3. **Local services** — `ss`, `ps`, port-to-service mapping
152
+ 4. **Cloud & Kubernetes** — AWS/GCP/Azure/k8s (skipped gracefully if not configured)
153
+ 5. **Config files** — `.env`, `docker-compose.yml`, etc.
154
+ 6. **Human-in-the-loop** — interactive follow-up after initial scan
155
+
92
156
  ### Shadow Daemon
93
157
 
94
158
  ```
95
- cartography shadow start [options]
159
+ datasynx-cartography shadow start [options]
96
160
 
97
161
  --interval <ms> Poll interval (default: 30000, min: 15000)
98
162
  --inactivity <ms> Task boundary gap (default: 300000)
@@ -101,21 +165,26 @@ cartography shadow start [options]
101
165
  --auto-save Save nodes without prompting
102
166
  --foreground Run in foreground (no fork)
103
167
 
104
- cartography shadow stop
105
- cartography shadow status
106
- cartography shadow attach # hotkeys: [T] new task [S] status [D] detach [Q] stop
168
+ datasynx-cartography shadow stop # stops + generates SOPs + opens dashboard
169
+ datasynx-cartography shadow pause # SIGUSR1
170
+ datasynx-cartography shadow resume # SIGUSR2
171
+ datasynx-cartography shadow status
172
+ datasynx-cartography shadow attach
107
173
  ```
108
174
 
109
175
  ### Analysis & Export
110
176
 
111
177
  ```
112
- cartography sops [session-id] Generate SOPs from observed workflows
113
- cartography export [session-id] [options] Export all formats
178
+ datasynx-cartography sops [session-id] Generate SOPs from observed workflows
179
+ datasynx-cartography export [session-id] [options]
114
180
  --format <fmt...> mermaid, json, yaml, html, sops (default: all)
115
181
  -o, --output <dir> Output directory
116
- cartography show [session-id] Session details + node list
117
- cartography sessions List all sessions
118
- cartography docs Full feature reference
182
+ datasynx-cartography show [session-id] Session details + node list
183
+ datasynx-cartography sessions List all sessions
184
+ datasynx-cartography bookmarks View all browser bookmarks
185
+ datasynx-cartography seed [--file <path>] Manually add infrastructure nodes
186
+ datasynx-cartography doctor Check all requirements + cloud CLIs
187
+ datasynx-cartography docs Full feature reference
119
188
  ```
120
189
 
121
190
  ---
@@ -123,12 +192,13 @@ cartography docs Full feature reference
123
192
  ## Output Files
124
193
 
125
194
  ```
126
- cartography-output/
195
+ datasynx-output/
127
196
  ├── catalog.json Full machine-readable dump
128
197
  ├── catalog-info.yaml Backstage service catalog
129
198
  ├── topology.mermaid Infrastructure topology (graph TB)
130
199
  ├── dependencies.mermaid Service dependencies (graph LR)
131
200
  ├── topology.html Interactive D3.js force graph
201
+ ├── sop-dashboard.html HTML dashboard with all SOPs + frequency stats
132
202
  ├── sops/
133
203
  │ ├── deploy-check.md
134
204
  │ └── db-migration.md
@@ -138,11 +208,11 @@ cartography-output/
138
208
 
139
209
  ---
140
210
 
141
- ## Costs
211
+ ## Cost Estimate
142
212
 
143
213
  | Mode | Model | Interval | per Hour | per 8h Day |
144
214
  |------|-------|----------|----------|------------|
145
- | Discovery | Sonnet | one-shot | $0.15–0.50 | one-shot |
215
+ | Discover | Sonnet | one-shot | $0.15–0.50 | one-shot |
146
216
  | Shadow | Haiku | 30s | $0.12–0.36 | $0.96–2.88 |
147
217
  | Shadow | Haiku | 60s | $0.06–0.18 | $0.48–1.44 |
148
218
  | Shadow (quiet)* | Haiku | 30s | ~$0.02 | ~$0.16 |
@@ -155,28 +225,32 @@ cartography-output/
155
225
  ## Architecture
156
226
 
157
227
  ```
158
- CLI (Commander)
159
- └── Preflight: Claude CLI check + API key + interval validation
160
- └── Agent Orchestrator
228
+ CLI (Commander.js)
229
+ └── Preflight: Claude CLI + API key + interval validation
230
+ └── Agent Orchestrator (src/agent.ts)
161
231
  ├── runDiscovery() Claude Sonnet + Bash + MCP Tools
162
- ├── runShadowCycle() Claude Haiku + MCP Tools only (no Bash!)
232
+ ├── scan_bookmarks() browser bookmark extraction
233
+ │ ├── scan_installed_apps() /Applications, brew, dpkg, which
234
+ │ ├── scan_k8s_resources() kubectl (readonly)
235
+ │ ├── scan_aws/gcp/azure() cloud CLI scans (readonly)
236
+ │ └── ask_user() human-in-the-loop questions
237
+ ├── runShadowCycle() Claude Haiku + MCP Tools only (no Bash)
163
238
  └── generateSOPs() Anthropic Messages API (no agent loop)
164
- └── Custom MCP Tools: save_node, save_edge, save_event,
165
- get_catalog, manage_task, save_sop
166
- └── CartographyDB (SQLite WAL, ~/.cartography/cartography.db)
167
-
168
- Shadow Daemon
169
- ├── takeSnapshot() ss + ps (no Claude!)
170
- ├── Diff-check only calls Claude when something changed
171
- ├── IPC Server Unix socket ~/.cartography/daemon.sock
172
- └── Notifications → desktop alerts when no client attached
239
+ └── Custom MCP Tools CartographyDB (SQLite WAL)
240
+
241
+ Shadow Daemon (src/daemon.ts)
242
+ ├── takeSnapshot() → ss + ps (no Claude!)
243
+ ├── Diff-check → calls Claude only when something changed
244
+ ├── SIGUSR1/2 pause / resume
245
+ ├── IPC Server Unix socket ~/.cartography/daemon.sock
246
+ └── Notifications desktop alerts when no client attached
173
247
  ```
174
248
 
175
249
  ### Safety
176
250
 
177
- Every Bash call is guarded by a `PreToolUse` hook that blocks any destructive command:
178
- `rm`, `mv`, `dd`, `chmod`, `kill`, `docker rm/run/exec`, `kubectl delete/apply/exec`, redirects (`>`), and more.
179
- Claude only reads — never writes, never deletes.
251
+ Every Bash call is guarded by a `PreToolUse` hook that blocks destructive commands:
252
+ `rm`, `mv`, `dd`, `chmod`, `kill`, `docker rm/run/exec`, `kubectl delete/apply/exec`, redirects (`>`), pipes to shell, and more.
253
+ **Claude only reads — never writes, never deletes.**
180
254
 
181
255
  ---
182
256
 
@@ -189,13 +263,27 @@ import {
189
263
  runShadowCycle,
190
264
  generateSOPs,
191
265
  exportAll,
266
+ exportSOPDashboard,
192
267
  safetyHook,
193
268
  defaultConfig,
194
- } from '@datasynx-ai/agentic-ai-cartography';
269
+ } from '@datasynx/agentic-ai-cartography';
270
+
271
+ // Run a discovery pass with optional user hint
272
+ await runDiscovery(config, db, sessionId, onEvent, onAskUser, 'hubspot windsurf');
195
273
  ```
196
274
 
197
275
  ---
198
276
 
277
+ ## Built by
278
+
279
+ <div align="center">
280
+
281
+ [![Datasynx AI on LinkedIn](https://img.shields.io/badge/Datasynx_AI-Follow_on_LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/datasynx-ai/)
282
+
283
+ </div>
284
+
285
+ ---
286
+
199
287
  ## License
200
288
 
201
- MIT — © Datasynx AI
289
+ MIT — © [Datasynx AI](https://www.linkedin.com/company/datasynx-ai/)
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ readFirefoxHistory,
4
+ scanAllBookmarks,
5
+ scanAllHistory
6
+ } from "./chunk-MZUF7KGX.js";
7
+ export {
8
+ readFirefoxHistory,
9
+ scanAllBookmarks,
10
+ scanAllHistory
11
+ };
12
+ //# sourceMappingURL=bookmarks-B2LUZQGG.js.map
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/bookmarks.ts
4
+ import { homedir, tmpdir } from "os";
5
+ import { existsSync, readFileSync, readdirSync, copyFileSync, statSync } from "fs";
6
+ import { join } from "path";
7
+ function extractHost(rawUrl, source) {
8
+ try {
9
+ const u = new URL(rawUrl);
10
+ if (u.protocol !== "http:" && u.protocol !== "https:") return null;
11
+ const protocol = u.protocol === "https:" ? "https" : "http";
12
+ const port = u.port ? parseInt(u.port, 10) : protocol === "https" ? 443 : 80;
13
+ const hostname = u.hostname.toLowerCase();
14
+ if (!hostname || hostname === "localhost" || hostname === "127.0.0.1") return null;
15
+ return { hostname, port, protocol, source };
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+ function walkChrome(node, source, out) {
21
+ if (node.type === "url" && node.url) {
22
+ const h = extractHost(node.url, source);
23
+ if (h) out.push(h);
24
+ }
25
+ if (node.children) {
26
+ for (const child of node.children) walkChrome(child, source, out);
27
+ }
28
+ }
29
+ function readChromeLike(filePath, source) {
30
+ if (!existsSync(filePath)) return [];
31
+ try {
32
+ const raw = JSON.parse(readFileSync(filePath, "utf8"));
33
+ const out = [];
34
+ for (const root of Object.values(raw.roots)) {
35
+ if (root) walkChrome(root, source, out);
36
+ }
37
+ return out;
38
+ } catch {
39
+ return [];
40
+ }
41
+ }
42
+ async function readFirefoxBookmarks(profileDir) {
43
+ const src = join(profileDir, "places.sqlite");
44
+ if (!existsSync(src)) return [];
45
+ const tmp = join(tmpdir(), `cartograph_ff_bm_${Date.now()}.sqlite`);
46
+ try {
47
+ copyFileSync(src, tmp);
48
+ const { default: Database } = await import("better-sqlite3");
49
+ const db = new Database(tmp, { readonly: true, fileMustExist: true });
50
+ const rows = db.prepare(`
51
+ SELECT DISTINCT p.url
52
+ FROM moz_places p
53
+ JOIN moz_bookmarks b ON b.fk = p.id
54
+ WHERE b.type = 1 AND p.url NOT LIKE 'place:%'
55
+ LIMIT 3000
56
+ `).all();
57
+ db.close();
58
+ return rows.map((r) => extractHost(r.url, "firefox")).filter((h) => h !== null);
59
+ } catch {
60
+ return [];
61
+ } finally {
62
+ try {
63
+ (await import("fs")).unlinkSync(tmp);
64
+ } catch {
65
+ }
66
+ }
67
+ }
68
+ async function readFirefoxHistory(profileDir) {
69
+ const src = join(profileDir, "places.sqlite");
70
+ if (!existsSync(src)) return [];
71
+ const tmp = join(tmpdir(), `cartograph_ff_hist_${Date.now()}.sqlite`);
72
+ try {
73
+ copyFileSync(src, tmp);
74
+ const { default: Database } = await import("better-sqlite3");
75
+ const db = new Database(tmp, { readonly: true, fileMustExist: true });
76
+ const rows = db.prepare(`
77
+ SELECT url, visit_count
78
+ FROM moz_places
79
+ WHERE url NOT LIKE 'place:%'
80
+ AND visit_count > 0
81
+ ORDER BY visit_count DESC
82
+ LIMIT 5000
83
+ `).all();
84
+ db.close();
85
+ return rows.map((r) => {
86
+ const h = extractHost(r.url, "firefox");
87
+ if (!h) return null;
88
+ return { ...h, visitCount: r.visit_count };
89
+ }).filter((h) => h !== null);
90
+ } catch {
91
+ return [];
92
+ } finally {
93
+ try {
94
+ (await import("fs")).unlinkSync(tmp);
95
+ } catch {
96
+ }
97
+ }
98
+ }
99
+ async function readChromiumHistory(historyPath, source) {
100
+ if (!existsSync(historyPath)) return [];
101
+ const tmp = join(tmpdir(), `cartograph_ch_hist_${Date.now()}.sqlite`);
102
+ try {
103
+ copyFileSync(historyPath, tmp);
104
+ const { default: Database } = await import("better-sqlite3");
105
+ const db = new Database(tmp, { readonly: true, fileMustExist: true });
106
+ const rows = db.prepare(`
107
+ SELECT url, visit_count
108
+ FROM urls
109
+ WHERE hidden = 0
110
+ AND visit_count > 0
111
+ ORDER BY visit_count DESC
112
+ LIMIT 5000
113
+ `).all();
114
+ db.close();
115
+ return rows.map((r) => {
116
+ const h = extractHost(r.url, source);
117
+ if (!h) return null;
118
+ return { ...h, visitCount: r.visit_count };
119
+ }).filter((h) => h !== null);
120
+ } catch {
121
+ return [];
122
+ } finally {
123
+ try {
124
+ (await import("fs")).unlinkSync(tmp);
125
+ } catch {
126
+ }
127
+ }
128
+ }
129
+ var HOME = homedir();
130
+ var IS_MAC = process.platform === "darwin";
131
+ function chromeLikePaths(base) {
132
+ const paths = [];
133
+ const defaultPath = join(base, "Default", "Bookmarks");
134
+ if (existsSync(defaultPath)) paths.push(defaultPath);
135
+ if (existsSync(base)) {
136
+ try {
137
+ for (const entry of readdirSync(base)) {
138
+ if (entry.startsWith("Profile ")) {
139
+ const p = join(base, entry, "Bookmarks");
140
+ if (existsSync(p)) paths.push(p);
141
+ }
142
+ }
143
+ } catch {
144
+ }
145
+ }
146
+ return paths;
147
+ }
148
+ function chromeLikeHistoryPaths(base) {
149
+ const paths = [];
150
+ const defaultPath = join(base, "Default", "History");
151
+ if (existsSync(defaultPath)) paths.push(defaultPath);
152
+ if (existsSync(base)) {
153
+ try {
154
+ for (const entry of readdirSync(base)) {
155
+ if (entry.startsWith("Profile ")) {
156
+ const p = join(base, entry, "History");
157
+ if (existsSync(p)) paths.push(p);
158
+ }
159
+ }
160
+ } catch {
161
+ }
162
+ }
163
+ return paths;
164
+ }
165
+ var CHROME_BASE = IS_MAC ? `${HOME}/Library/Application Support/Google/Chrome` : `${HOME}/.config/google-chrome`;
166
+ var CHROMIUM_BASE = IS_MAC ? `${HOME}/Library/Application Support/Chromium` : `${HOME}/.config/chromium`;
167
+ var EDGE_BASE = IS_MAC ? `${HOME}/Library/Application Support/Microsoft Edge` : `${HOME}/.config/microsoft-edge`;
168
+ var BRAVE_BASE = IS_MAC ? `${HOME}/Library/Application Support/BraveSoftware/Brave-Browser` : `${HOME}/.config/BraveSoftware/Brave-Browser`;
169
+ var VIVALDI_BASE = IS_MAC ? `${HOME}/Library/Application Support/Vivaldi` : `${HOME}/.config/vivaldi`;
170
+ var OPERA_BASE = IS_MAC ? `${HOME}/Library/Application Support/com.operasoftware.Opera` : `${HOME}/.config/opera`;
171
+ function firefoxProfileDirs() {
172
+ const base = IS_MAC ? `${HOME}/Library/Application Support/Firefox/Profiles` : `${HOME}/.mozilla/firefox`;
173
+ if (!existsSync(base)) return [];
174
+ try {
175
+ return readdirSync(base).map((d) => join(base, d)).filter((d) => {
176
+ try {
177
+ return statSync(d).isDirectory() && existsSync(join(d, "places.sqlite"));
178
+ } catch {
179
+ return false;
180
+ }
181
+ });
182
+ } catch {
183
+ return [];
184
+ }
185
+ }
186
+ async function scanAllBookmarks() {
187
+ const all = [];
188
+ for (const p of chromeLikePaths(CHROME_BASE)) all.push(...readChromeLike(p, "chrome"));
189
+ for (const p of chromeLikePaths(CHROMIUM_BASE)) all.push(...readChromeLike(p, "chromium"));
190
+ for (const p of chromeLikePaths(EDGE_BASE)) all.push(...readChromeLike(p, "edge"));
191
+ for (const p of chromeLikePaths(BRAVE_BASE)) all.push(...readChromeLike(p, "brave"));
192
+ for (const p of chromeLikePaths(VIVALDI_BASE)) all.push(...readChromeLike(p, "vivaldi"));
193
+ for (const p of chromeLikePaths(OPERA_BASE)) all.push(...readChromeLike(p, "opera"));
194
+ for (const dir of firefoxProfileDirs()) {
195
+ all.push(...await readFirefoxBookmarks(dir));
196
+ }
197
+ const seen = /* @__PURE__ */ new Set();
198
+ return all.filter((h) => {
199
+ if (seen.has(h.hostname)) return false;
200
+ seen.add(h.hostname);
201
+ return true;
202
+ });
203
+ }
204
+ async function scanAllHistory() {
205
+ const all = [];
206
+ for (const p of chromeLikeHistoryPaths(CHROME_BASE)) all.push(...await readChromiumHistory(p, "chrome"));
207
+ for (const p of chromeLikeHistoryPaths(CHROMIUM_BASE)) all.push(...await readChromiumHistory(p, "chromium"));
208
+ for (const p of chromeLikeHistoryPaths(EDGE_BASE)) all.push(...await readChromiumHistory(p, "edge"));
209
+ for (const p of chromeLikeHistoryPaths(BRAVE_BASE)) all.push(...await readChromiumHistory(p, "brave"));
210
+ for (const p of chromeLikeHistoryPaths(VIVALDI_BASE)) all.push(...await readChromiumHistory(p, "vivaldi"));
211
+ for (const p of chromeLikeHistoryPaths(OPERA_BASE)) all.push(...await readChromiumHistory(p, "opera"));
212
+ for (const dir of firefoxProfileDirs()) {
213
+ all.push(...await readFirefoxHistory(dir));
214
+ }
215
+ const byHost = /* @__PURE__ */ new Map();
216
+ for (const h of all) {
217
+ const existing = byHost.get(h.hostname);
218
+ if (existing) {
219
+ existing.visitCount += h.visitCount;
220
+ } else {
221
+ byHost.set(h.hostname, { ...h });
222
+ }
223
+ }
224
+ return [...byHost.values()].sort((a, b) => b.visitCount - a.visitCount);
225
+ }
226
+
227
+ export {
228
+ readFirefoxHistory,
229
+ scanAllBookmarks,
230
+ scanAllHistory
231
+ };
232
+ //# sourceMappingURL=chunk-MZUF7KGX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bookmarks.ts"],"sourcesContent":["import { homedir, tmpdir } from 'node:os';\nimport { existsSync, readFileSync, readdirSync, copyFileSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface BookmarkHost {\n hostname: string;\n port: number;\n protocol: 'http' | 'https';\n source: string;\n}\n\nexport interface HistoryHost extends BookmarkHost {\n visitCount: number;\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction extractHost(rawUrl: string, source: string): BookmarkHost | null {\n try {\n const u = new URL(rawUrl);\n if (u.protocol !== 'http:' && u.protocol !== 'https:') return null;\n const protocol = u.protocol === 'https:' ? 'https' as const : 'http' as const;\n // Strip: no paths, no params, no credentials — hostname only\n const port = u.port ? parseInt(u.port, 10) : (protocol === 'https' ? 443 : 80);\n const hostname = u.hostname.toLowerCase();\n if (!hostname || hostname === 'localhost' || hostname === '127.0.0.1') return null;\n return { hostname, port, protocol, source };\n } catch {\n return null;\n }\n}\n\n// Chrome/Edge/Brave JSON format\ninterface ChromeNode {\n type?: string;\n url?: string;\n children?: ChromeNode[];\n}\n\nfunction walkChrome(node: ChromeNode, source: string, out: BookmarkHost[]): void {\n if (node.type === 'url' && node.url) {\n const h = extractHost(node.url, source);\n if (h) out.push(h);\n }\n if (node.children) {\n for (const child of node.children) walkChrome(child, source, out);\n }\n}\n\nfunction readChromeLike(filePath: string, source: string): BookmarkHost[] {\n if (!existsSync(filePath)) return [];\n try {\n const raw = JSON.parse(readFileSync(filePath, 'utf8')) as {\n roots: Record<string, ChromeNode>;\n };\n const out: BookmarkHost[] = [];\n for (const root of Object.values(raw.roots)) {\n if (root) walkChrome(root, source, out);\n }\n return out;\n } catch {\n return [];\n }\n}\n\nasync function readFirefoxBookmarks(profileDir: string): Promise<BookmarkHost[]> {\n const src = join(profileDir, 'places.sqlite');\n if (!existsSync(src)) return [];\n const tmp = join(tmpdir(), `cartograph_ff_bm_${Date.now()}.sqlite`);\n try {\n copyFileSync(src, tmp);\n const { default: Database } = await import('better-sqlite3');\n const db = new Database(tmp, { readonly: true, fileMustExist: true });\n const rows = db.prepare(`\n SELECT DISTINCT p.url\n FROM moz_places p\n JOIN moz_bookmarks b ON b.fk = p.id\n WHERE b.type = 1 AND p.url NOT LIKE 'place:%'\n LIMIT 3000\n `).all() as { url: string }[];\n db.close();\n return rows.map(r => extractHost(r.url, 'firefox')).filter((h): h is BookmarkHost => h !== null);\n } catch {\n return [];\n } finally {\n try { (await import('node:fs')).unlinkSync(tmp); } catch { /* ignore */ }\n }\n}\n\nexport async function readFirefoxHistory(profileDir: string): Promise<HistoryHost[]> {\n const src = join(profileDir, 'places.sqlite');\n if (!existsSync(src)) return [];\n const tmp = join(tmpdir(), `cartograph_ff_hist_${Date.now()}.sqlite`);\n try {\n copyFileSync(src, tmp);\n const { default: Database } = await import('better-sqlite3');\n const db = new Database(tmp, { readonly: true, fileMustExist: true });\n const rows = db.prepare(`\n SELECT url, visit_count\n FROM moz_places\n WHERE url NOT LIKE 'place:%'\n AND visit_count > 0\n ORDER BY visit_count DESC\n LIMIT 5000\n `).all() as { url: string; visit_count: number }[];\n db.close();\n return rows\n .map(r => {\n const h = extractHost(r.url, 'firefox');\n if (!h) return null;\n return { ...h, visitCount: r.visit_count };\n })\n .filter((h): h is HistoryHost => h !== null);\n } catch {\n return [];\n } finally {\n try { (await import('node:fs')).unlinkSync(tmp); } catch { /* ignore */ }\n }\n}\n\nasync function readChromiumHistory(historyPath: string, source: string): Promise<HistoryHost[]> {\n if (!existsSync(historyPath)) return [];\n const tmp = join(tmpdir(), `cartograph_ch_hist_${Date.now()}.sqlite`);\n try {\n copyFileSync(historyPath, tmp);\n const { default: Database } = await import('better-sqlite3');\n const db = new Database(tmp, { readonly: true, fileMustExist: true });\n const rows = db.prepare(`\n SELECT url, visit_count\n FROM urls\n WHERE hidden = 0\n AND visit_count > 0\n ORDER BY visit_count DESC\n LIMIT 5000\n `).all() as { url: string; visit_count: number }[];\n db.close();\n return rows\n .map(r => {\n const h = extractHost(r.url, source);\n if (!h) return null;\n return { ...h, visitCount: r.visit_count };\n })\n .filter((h): h is HistoryHost => h !== null);\n } catch {\n return [];\n } finally {\n try { (await import('node:fs')).unlinkSync(tmp); } catch { /* ignore */ }\n }\n}\n\n// ── Platform paths ────────────────────────────────────────────────────────────\n\nconst HOME = homedir();\nconst IS_MAC = process.platform === 'darwin';\n\n// Browser bookmark file paths (multiple profiles supported)\nfunction chromeLikePaths(base: string): string[] {\n const paths: string[] = [];\n const defaultPath = join(base, 'Default', 'Bookmarks');\n if (existsSync(defaultPath)) paths.push(defaultPath);\n // Also check Profile 1, Profile 2, etc.\n if (existsSync(base)) {\n try {\n for (const entry of readdirSync(base)) {\n if (entry.startsWith('Profile ')) {\n const p = join(base, entry, 'Bookmarks');\n if (existsSync(p)) paths.push(p);\n }\n }\n } catch { /* ignore */ }\n }\n return paths;\n}\n\nfunction chromeLikeHistoryPaths(base: string): string[] {\n const paths: string[] = [];\n const defaultPath = join(base, 'Default', 'History');\n if (existsSync(defaultPath)) paths.push(defaultPath);\n if (existsSync(base)) {\n try {\n for (const entry of readdirSync(base)) {\n if (entry.startsWith('Profile ')) {\n const p = join(base, entry, 'History');\n if (existsSync(p)) paths.push(p);\n }\n }\n } catch { /* ignore */ }\n }\n return paths;\n}\n\nconst CHROME_BASE = IS_MAC\n ? `${HOME}/Library/Application Support/Google/Chrome`\n : `${HOME}/.config/google-chrome`;\n\nconst CHROMIUM_BASE = IS_MAC\n ? `${HOME}/Library/Application Support/Chromium`\n : `${HOME}/.config/chromium`;\n\nconst EDGE_BASE = IS_MAC\n ? `${HOME}/Library/Application Support/Microsoft Edge`\n : `${HOME}/.config/microsoft-edge`;\n\nconst BRAVE_BASE = IS_MAC\n ? `${HOME}/Library/Application Support/BraveSoftware/Brave-Browser`\n : `${HOME}/.config/BraveSoftware/Brave-Browser`;\n\nconst VIVALDI_BASE = IS_MAC\n ? `${HOME}/Library/Application Support/Vivaldi`\n : `${HOME}/.config/vivaldi`;\n\nconst OPERA_BASE = IS_MAC\n ? `${HOME}/Library/Application Support/com.operasoftware.Opera`\n : `${HOME}/.config/opera`;\n\nfunction firefoxProfileDirs(): string[] {\n const base = IS_MAC\n ? `${HOME}/Library/Application Support/Firefox/Profiles`\n : `${HOME}/.mozilla/firefox`;\n if (!existsSync(base)) return [];\n try {\n return readdirSync(base)\n .map(d => join(base, d))\n .filter(d => {\n try {\n return statSync(d).isDirectory() && existsSync(join(d, 'places.sqlite'));\n } catch { return false; }\n });\n } catch {\n return [];\n }\n}\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\nexport async function scanAllBookmarks(): Promise<BookmarkHost[]> {\n const all: BookmarkHost[] = [];\n\n for (const p of chromeLikePaths(CHROME_BASE)) all.push(...readChromeLike(p, 'chrome'));\n for (const p of chromeLikePaths(CHROMIUM_BASE)) all.push(...readChromeLike(p, 'chromium'));\n for (const p of chromeLikePaths(EDGE_BASE)) all.push(...readChromeLike(p, 'edge'));\n for (const p of chromeLikePaths(BRAVE_BASE)) all.push(...readChromeLike(p, 'brave'));\n for (const p of chromeLikePaths(VIVALDI_BASE)) all.push(...readChromeLike(p, 'vivaldi'));\n for (const p of chromeLikePaths(OPERA_BASE)) all.push(...readChromeLike(p, 'opera'));\n\n for (const dir of firefoxProfileDirs()) {\n all.push(...await readFirefoxBookmarks(dir));\n }\n\n // Deduplicate by hostname\n const seen = new Set<string>();\n return all.filter(h => {\n if (seen.has(h.hostname)) return false;\n seen.add(h.hostname);\n return true;\n });\n}\n\nexport async function scanAllHistory(): Promise<HistoryHost[]> {\n const all: HistoryHost[] = [];\n\n for (const p of chromeLikeHistoryPaths(CHROME_BASE)) all.push(...await readChromiumHistory(p, 'chrome'));\n for (const p of chromeLikeHistoryPaths(CHROMIUM_BASE)) all.push(...await readChromiumHistory(p, 'chromium'));\n for (const p of chromeLikeHistoryPaths(EDGE_BASE)) all.push(...await readChromiumHistory(p, 'edge'));\n for (const p of chromeLikeHistoryPaths(BRAVE_BASE)) all.push(...await readChromiumHistory(p, 'brave'));\n for (const p of chromeLikeHistoryPaths(VIVALDI_BASE)) all.push(...await readChromiumHistory(p, 'vivaldi'));\n for (const p of chromeLikeHistoryPaths(OPERA_BASE)) all.push(...await readChromiumHistory(p, 'opera'));\n\n for (const dir of firefoxProfileDirs()) {\n all.push(...await readFirefoxHistory(dir));\n }\n\n // Deduplicate by hostname, summing visit counts\n const byHost = new Map<string, HistoryHost>();\n for (const h of all) {\n const existing = byHost.get(h.hostname);\n if (existing) {\n existing.visitCount += h.visitCount;\n } else {\n byHost.set(h.hostname, { ...h });\n }\n }\n\n // Sort by visit count descending\n return [...byHost.values()].sort((a, b) => b.visitCount - a.visitCount);\n}\n"],"mappings":"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","names":[]}