@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 +154 -66
- package/dist/bookmarks-B2LUZQGG.js +12 -0
- package/dist/chunk-MZUF7KGX.js +232 -0
- package/dist/chunk-MZUF7KGX.js.map +1 -0
- package/dist/{chunk-GUZXO6PM.js → chunk-NEB52VYQ.js} +258 -67
- package/dist/chunk-NEB52VYQ.js.map +1 -0
- package/dist/cli.js +414 -310
- package/dist/cli.js.map +1 -1
- package/dist/{exporter-BDVDYA3K.js → exporter-LRHXMSKN.js} +2 -2
- package/dist/index.js +602 -194
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/bookmarks-O7KNR7D3.js +0 -8
- package/dist/chunk-GUZXO6PM.js.map +0 -1
- package/dist/chunk-JAFRT2R6.js +0 -97
- package/dist/chunk-JAFRT2R6.js.map +0 -1
- /package/dist/{bookmarks-O7KNR7D3.js.map → bookmarks-B2LUZQGG.js.map} +0 -0
- /package/dist/{exporter-BDVDYA3K.js.map → exporter-LRHXMSKN.js.map} +0 -0
package/README.md
CHANGED
|
@@ -1,38 +1,76 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# 🗺️ Datasynx Cartography
|
|
2
4
|
|
|
3
5
|
**AI-powered Infrastructure Cartography & SOP Generation**
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/@datasynx/agentic-ai-cartography)
|
|
8
|
+
[](https://www.npmjs.com/package/@datasynx/agentic-ai-cartography)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
[](https://nodejs.org)
|
|
11
|
+
[](https://github.com/anthropics/claude-code)
|
|
12
|
+
[](https://www.linkedin.com/company/datasynx-ai/)
|
|
6
13
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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**
|
|
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
|
|
85
|
+
npm install -g @datasynx/agentic-ai-cartography
|
|
48
86
|
```
|
|
49
87
|
|
|
88
|
+
[](https://www.npmjs.com/package/@datasynx/agentic-ai-cartography)
|
|
89
|
+
|
|
50
90
|
---
|
|
51
91
|
|
|
52
92
|
## Quick Start
|
|
53
93
|
|
|
54
94
|
```bash
|
|
55
|
-
#
|
|
56
|
-
cartography
|
|
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
|
-
#
|
|
62
|
-
cartography shadow
|
|
116
|
+
# Pause / resume the daemon
|
|
117
|
+
datasynx-cartography shadow pause
|
|
118
|
+
datasynx-cartography shadow resume
|
|
63
119
|
|
|
64
|
-
#
|
|
65
|
-
cartography
|
|
120
|
+
# Stop daemon + generate SOPs + open HTML dashboard
|
|
121
|
+
datasynx-cartography shadow stop
|
|
66
122
|
|
|
67
|
-
#
|
|
68
|
-
cartography
|
|
123
|
+
# Generate SOPs manually from a session
|
|
124
|
+
datasynx-cartography sops [session-id]
|
|
69
125
|
|
|
70
|
-
# Full feature
|
|
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: ./
|
|
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
|
|
106
|
-
cartography shadow
|
|
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]
|
|
113
|
-
cartography export [session-id] [options]
|
|
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]
|
|
117
|
-
cartography sessions
|
|
118
|
-
cartography
|
|
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
|
-
|
|
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
|
-
##
|
|
211
|
+
## Cost Estimate
|
|
142
212
|
|
|
143
213
|
| Mode | Model | Interval | per Hour | per 8h Day |
|
|
144
214
|
|------|-------|----------|----------|------------|
|
|
145
|
-
|
|
|
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
|
|
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
|
-
├──
|
|
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
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
├──
|
|
170
|
-
├──
|
|
171
|
-
|
|
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
|
|
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
|
|
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
|
+
[](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,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":[]}
|