codesynapt 0.0.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 (61) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE +686 -0
  3. package/LICENSES.md +141 -0
  4. package/README.md +331 -0
  5. package/electron/main.cjs +2849 -0
  6. package/electron/plugin-loader.cjs +184 -0
  7. package/electron/preload.cjs +108 -0
  8. package/package.json +216 -0
  9. package/packages/core/bin/codesynapt-mcp.cjs +611 -0
  10. package/packages/core/bin/codesynapt.cjs +1933 -0
  11. package/packages/core/legacy.js +300 -0
  12. package/packages/core/lib/control-server.cjs +1539 -0
  13. package/packages/core/lib/embedding.cjs +89 -0
  14. package/packages/core/lib/logger.cjs +63 -0
  15. package/packages/core/lib/search-cache.cjs +140 -0
  16. package/packages/core/lib/search-worker.cjs +255 -0
  17. package/packages/core/lib/search.cjs +211 -0
  18. package/packages/core/lib/symbol-graph.cjs +402 -0
  19. package/packages/core/lib/symbol-parser-js.cjs +542 -0
  20. package/packages/core/lib/symbol-parser-misc.cjs +394 -0
  21. package/packages/core/lib/symbol-parser-py.cjs +215 -0
  22. package/packages/core/lib/symbol-parser-treesitter.cjs +658 -0
  23. package/packages/core/lib/symbol-parser-tsc.cjs +332 -0
  24. package/packages/core/monorepo.js +310 -0
  25. package/packages/core/parser.js +2234 -0
  26. package/packages/core/scanner.js +623 -0
  27. package/plugin-api/LICENSE +21 -0
  28. package/plugin-api/README.md +114 -0
  29. package/plugin-api/docs/01-getting-started.md +197 -0
  30. package/plugin-api/docs/02-concepts.md +269 -0
  31. package/plugin-api/docs/api-reference.md +463 -0
  32. package/plugin-api/docs/troubleshooting.md +332 -0
  33. package/plugin-api/docs/types/exporter.md +377 -0
  34. package/plugin-api/docs/types/theme.md +312 -0
  35. package/plugin-api/examples/hello-world-plugin/README.md +70 -0
  36. package/plugin-api/examples/hello-world-plugin/main.js +36 -0
  37. package/plugin-api/examples/hello-world-plugin/manifest.json +12 -0
  38. package/plugin-api/examples/mermaid-exporter/README.md +125 -0
  39. package/plugin-api/examples/mermaid-exporter/main.js +58 -0
  40. package/plugin-api/examples/mermaid-exporter/manifest.json +12 -0
  41. package/plugin-api/examples/rust-parser/README.md +71 -0
  42. package/plugin-api/examples/rust-parser/main.js +123 -0
  43. package/plugin-api/examples/rust-parser/manifest.json +12 -0
  44. package/plugin-api/examples/sunset-theme/README.md +95 -0
  45. package/plugin-api/examples/sunset-theme/manifest.json +12 -0
  46. package/plugin-api/examples/sunset-theme/theme.css +31 -0
  47. package/plugin-api/package.json +20 -0
  48. package/plugin-api/types.d.ts +395 -0
  49. package/public/app.js +6837 -0
  50. package/public/backend.js +285 -0
  51. package/public/index.html +647 -0
  52. package/public/plugin-host.js +321 -0
  53. package/public/style.css +4359 -0
  54. package/public/vendor/three.module.js +53044 -0
  55. package/scripts/competitor-watch.mjs +144 -0
  56. package/scripts/copy-vendor.js +21 -0
  57. package/scripts/download-bundled-node.cjs +53 -0
  58. package/scripts/fuses-after-pack.cjs +34 -0
  59. package/scripts/license-check.js +119 -0
  60. package/scripts/perf-test.js +200 -0
  61. package/server.js +132 -0
@@ -0,0 +1,184 @@
1
+ // ═══════════════════════════════════════════════════════════
2
+ // Plugin loader — main process side
3
+ //
4
+ // Responsibilities:
5
+ // - Discover plugin folders under the user's plugin directory
6
+ // - Read and validate each manifest.json
7
+ // - Hand the list back to the renderer; the renderer activates
8
+ // code plugins inside its own context (sandboxed by Electron's
9
+ // contextIsolation) and loads theme CSS directly.
10
+ //
11
+ // The plugin directory is one of:
12
+ // macOS: ~/Library/Application Support/codesynapt/plugins
13
+ // Windows: %APPDATA%\codesynapt\plugins
14
+ // Linux: ~/.config/codesynapt/plugins
15
+ //
16
+ // Each plugin is its own folder containing manifest.json plus
17
+ // the entry file (theme.css or main.js).
18
+ // ═══════════════════════════════════════════════════════════
19
+ const fs = require('fs')
20
+ const path = require('path')
21
+ const { app } = require('electron')
22
+
23
+ const VALID_TYPES = new Set(['theme', 'exporter', 'parser', 'layout', 'panel', 'action'])
24
+ const VALID_PERMISSIONS = new Set([
25
+ 'read-files', 'read-graph', 'modify-graph',
26
+ 'ui-panel', 'context-menu', 'export', 'parse'
27
+ ])
28
+
29
+ // Resolve the per-OS plugin folder. We pin it under userData so the
30
+ // app handles cross-platform paths for us.
31
+ function getPluginDir() {
32
+ return path.join(app.getPath('userData'), 'plugins')
33
+ }
34
+
35
+ // Make sure the directory exists so users can drop plugins into it
36
+ // without having to mkdir manually.
37
+ function ensurePluginDir() {
38
+ const dir = getPluginDir()
39
+ try {
40
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
41
+ } catch (err) {
42
+ console.warn('[plugin-loader] could not create plugin dir:', err.message)
43
+ }
44
+ return dir
45
+ }
46
+
47
+ // Validate a parsed manifest against the schema. Returns either
48
+ // { ok: true, manifest } or { ok: false, reason: string }.
49
+ function validateManifest(raw) {
50
+ if (!raw || typeof raw !== 'object') return { ok: false, reason: 'not an object' }
51
+ const required = ['id', 'name', 'version', 'type', 'main']
52
+ for (const k of required) {
53
+ if (!raw[k] || typeof raw[k] !== 'string') {
54
+ return { ok: false, reason: `missing or invalid "${k}"` }
55
+ }
56
+ }
57
+ if (!/^[a-z0-9][a-z0-9-_]*$/i.test(raw.id)) {
58
+ return { ok: false, reason: `invalid id "${raw.id}" (use alphanumeric + dash/underscore)` }
59
+ }
60
+ if (!VALID_TYPES.has(raw.type)) {
61
+ return { ok: false, reason: `unknown type "${raw.type}"` }
62
+ }
63
+ // Permissions are optional; if present must be an array of known strings
64
+ if (raw.permissions !== undefined) {
65
+ if (!Array.isArray(raw.permissions)) {
66
+ return { ok: false, reason: 'permissions must be an array' }
67
+ }
68
+ for (const p of raw.permissions) {
69
+ if (!VALID_PERMISSIONS.has(p)) {
70
+ return { ok: false, reason: `unknown permission "${p}"` }
71
+ }
72
+ }
73
+ }
74
+ // Provide defaults for optional fields so the renderer doesn't
75
+ // have to null-check them
76
+ return {
77
+ ok: true,
78
+ manifest: {
79
+ id: raw.id,
80
+ name: raw.name,
81
+ version: raw.version,
82
+ author: raw.author || 'unknown',
83
+ description: raw.description || '',
84
+ type: raw.type,
85
+ main: raw.main,
86
+ minAppVersion: raw.minAppVersion || '0.0.0',
87
+ license: raw.license || 'unknown',
88
+ homepage: raw.homepage || null,
89
+ permissions: raw.permissions || [],
90
+ }
91
+ }
92
+ }
93
+
94
+ // Discover all plugins. Returns an array of:
95
+ // { manifest, folder, entryPath, entryContent?, error? }
96
+ //
97
+ // For theme plugins we read the CSS file directly here so the renderer
98
+ // can just inject it. For code plugins we read the JS source string and
99
+ // pass it to the renderer to execute in its sandbox.
100
+ function discoverPlugins() {
101
+ const dir = ensurePluginDir()
102
+ let entries
103
+ try {
104
+ entries = fs.readdirSync(dir, { withFileTypes: true })
105
+ } catch (err) {
106
+ console.warn('[plugin-loader] cannot read plugin dir:', err.message)
107
+ return []
108
+ }
109
+
110
+ const results = []
111
+ for (const entry of entries) {
112
+ if (!entry.isDirectory()) continue
113
+ if (entry.name.startsWith('.') || entry.name.startsWith('_')) continue
114
+ const folder = path.join(dir, entry.name)
115
+ const manifestPath = path.join(folder, 'manifest.json')
116
+
117
+ let raw
118
+ try {
119
+ raw = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
120
+ } catch (err) {
121
+ results.push({
122
+ folder, error: `manifest unreadable: ${err.message}`,
123
+ manifest: { id: entry.name, name: entry.name, type: 'unknown' }
124
+ })
125
+ continue
126
+ }
127
+
128
+ const validation = validateManifest(raw)
129
+ if (!validation.ok) {
130
+ results.push({
131
+ folder, error: `invalid manifest: ${validation.reason}`,
132
+ manifest: { id: raw.id || entry.name, name: raw.name || entry.name, type: raw.type || 'unknown' }
133
+ })
134
+ continue
135
+ }
136
+
137
+ const { manifest } = validation
138
+ // Security: the entry file must live inside the plugin folder.
139
+ // Reject anything escaping with .. or absolute paths.
140
+ const entryPath = path.resolve(folder, manifest.main)
141
+ const rel = path.relative(folder, entryPath)
142
+ if (rel.startsWith('..') || path.isAbsolute(rel)) {
143
+ results.push({
144
+ folder, manifest,
145
+ error: `entry path escapes plugin folder: ${manifest.main}`
146
+ })
147
+ continue
148
+ }
149
+ if (!fs.existsSync(entryPath)) {
150
+ results.push({
151
+ folder, manifest,
152
+ error: `entry file not found: ${manifest.main}`
153
+ })
154
+ continue
155
+ }
156
+
157
+ let entryContent = null
158
+ try {
159
+ entryContent = fs.readFileSync(entryPath, 'utf8')
160
+ } catch (err) {
161
+ results.push({
162
+ folder, manifest,
163
+ error: `cannot read entry: ${err.message}`
164
+ })
165
+ continue
166
+ }
167
+
168
+ results.push({
169
+ folder,
170
+ manifest,
171
+ entryPath,
172
+ entryContent,
173
+ error: null,
174
+ })
175
+ }
176
+ return results
177
+ }
178
+
179
+ module.exports = {
180
+ getPluginDir,
181
+ ensurePluginDir,
182
+ discoverPlugins,
183
+ validateManifest,
184
+ }
@@ -0,0 +1,108 @@
1
+ // Preload — safe IPC bridge between renderer (untrusted) and main (privileged)
2
+ const { contextBridge, ipcRenderer } = require('electron')
3
+
4
+ const csApi = {
5
+ // Imperative
6
+ pickFolder: () => ipcRenderer.invoke('pick-folder'),
7
+ loadFolder: (path) => ipcRenderer.invoke('load-folder', path),
8
+ closeFolder: () => ipcRenderer.invoke('close-folder'),
9
+ getState: () => ipcRenderer.invoke('get-state'),
10
+ readFile: (id) => ipcRenderer.invoke('read-file', id),
11
+ writeFile: (id, content) => ipcRenderer.invoke('write-file', id, content),
12
+ listHistory: (id) => ipcRenderer.invoke('list-history', id),
13
+ readHistory: (id, ts) => ipcRenderer.invoke('read-history', id, ts),
14
+ restoreHistory: (id, ts) => ipcRenderer.invoke('restore-history', id, ts),
15
+ setHistoryEnabled: (enabled) => ipcRenderer.invoke('set-history-enabled', enabled),
16
+ getHistoryEnabled: () => ipcRenderer.invoke('get-history-enabled'),
17
+ controlPort: () => ipcRenderer.invoke('control-port'),
18
+ onControl: (cb) => {
19
+ const onFocus = (_e, data) => cb({ type: 'focus', ...data })
20
+ const onOpen = (_e, data) => cb({ type: 'open', ...data })
21
+ const onTrace = (_e, data) => cb({ type: 'trace', ...data })
22
+ const onBlast = (_e, data) => cb({ type: 'blast', ...data })
23
+ ipcRenderer.on('control:focus', onFocus)
24
+ ipcRenderer.on('control:open', onOpen)
25
+ ipcRenderer.on('control:trace', onTrace)
26
+ ipcRenderer.on('control:blast', onBlast)
27
+ return () => {
28
+ ipcRenderer.off('control:focus', onFocus)
29
+ ipcRenderer.off('control:open', onOpen)
30
+ ipcRenderer.off('control:trace', onTrace)
31
+ ipcRenderer.off('control:blast', onBlast)
32
+ }
33
+ },
34
+ revealInOS: (id) => ipcRenderer.invoke('reveal-in-os', id),
35
+ openInEditor: (id) => ipcRenderer.invoke('open-in-editor', id),
36
+
37
+ // Event subscriptions (main → renderer)
38
+ onSnapshot: (cb) => {
39
+ const handler = (_e, data) => cb(data)
40
+ ipcRenderer.on('snapshot', handler)
41
+ return () => ipcRenderer.off('snapshot', handler)
42
+ },
43
+ onFolderLoaded: (cb) => {
44
+ const handler = (_e, data) => cb(data)
45
+ ipcRenderer.on('folder-loaded', handler)
46
+ return () => ipcRenderer.off('folder-loaded', handler)
47
+ },
48
+ onNoFolder: (cb) => {
49
+ const handler = () => cb()
50
+ ipcRenderer.on('no-folder', handler)
51
+ return () => ipcRenderer.off('no-folder', handler)
52
+ },
53
+ onError: (cb) => {
54
+ const handler = (_e, data) => cb(data)
55
+ ipcRenderer.on('error', handler)
56
+ return () => ipcRenderer.off('error', handler)
57
+ },
58
+ onWindowVisibility: (cb) => {
59
+ const handler = (_e, data) => cb(data)
60
+ ipcRenderer.on('window-visibility', handler)
61
+ return () => ipcRenderer.off('window-visibility', handler)
62
+ },
63
+ onScanProgress: (cb) => {
64
+ const handler = (_e, data) => cb(data)
65
+ ipcRenderer.on('scan-progress', handler)
66
+ return () => ipcRenderer.off('scan-progress', handler)
67
+ },
68
+
69
+ // Panel data — bypasses fetch/CSP for tour, timeline, changes
70
+ getTour: () => ipcRenderer.invoke('panel:tour'),
71
+ getTimeline: () => ipcRenderer.invoke('panel:timeline'),
72
+ getChanges: () => ipcRenderer.invoke('panel:changes'),
73
+ getChangeDiff: (id) => ipcRenderer.invoke('panel:change-diff', id),
74
+ getPackages: () => ipcRenderer.invoke('panel:packages'),
75
+ getPackage: (name) => ipcRenderer.invoke('panel:package', name),
76
+ getLegacy: () => ipcRenderer.invoke('panel:legacy'),
77
+ // Trace session — live AI activity log
78
+ traceLog: (opts) => ipcRenderer.invoke('trace:log', opts || {}),
79
+ traceStats: () => ipcRenderer.invoke('trace:stats'),
80
+ traceSessions: () => ipcRenderer.invoke('trace:sessions'),
81
+ traceSession: (id) => ipcRenderer.invoke('trace:session', id),
82
+ traceClear: () => ipcRenderer.invoke('trace:clear'),
83
+ traceExport: (path) => ipcRenderer.invoke('trace:export', path),
84
+
85
+ // Pinned projects (multi-folder workspace)
86
+ listProjects: () => ipcRenderer.invoke('list-projects'),
87
+ pinProject: (path, name, color) => ipcRenderer.invoke('pin-project', { path, name, color }),
88
+ unpinProject: (path) => ipcRenderer.invoke('unpin-project', path),
89
+ renameProject: (path, name) => ipcRenderer.invoke('rename-project', { path, name }),
90
+
91
+ // Plugin system
92
+ listPlugins: () => ipcRenderer.invoke('list-plugins'),
93
+ openPluginDir: () => ipcRenderer.invoke('open-plugin-dir'),
94
+ pluginDir: () => ipcRenderer.invoke('plugin-dir'),
95
+ }
96
+
97
+ // Expose under both names: 'codesynapt' is the canonical namespace from 0.14.6+;
98
+ // 'fg3d' is a legacy alias kept for backward compat (plugin authors, etc.).
99
+ // Both refer to the SAME object — no extra memory.
100
+ contextBridge.exposeInMainWorld('codesynapt', csApi)
101
+ contextBridge.exposeInMainWorld('fg3d', csApi)
102
+
103
+ // Platform info for renderer
104
+ contextBridge.exposeInMainWorld('platform', {
105
+ os: process.platform,
106
+ isMac: process.platform === 'darwin',
107
+ isElectron: true,
108
+ })
package/package.json ADDED
@@ -0,0 +1,216 @@
1
+ {
2
+ "name": "codesynapt",
3
+ "version": "0.0.0",
4
+ "description": "MCP-native code graph for AI agents — see blast radius live. 3D visualizer + CLI + MCP server, one package.",
5
+ "license": "AGPL-3.0-or-later",
6
+ "author": {
7
+ "name": "wing1008",
8
+ "url": "https://github.com/wing1008"
9
+ },
10
+ "funding": [
11
+ {
12
+ "type": "github",
13
+ "url": "https://github.com/sponsors/wing1008"
14
+ },
15
+ {
16
+ "type": "individual",
17
+ "url": "https://buymeacoffee.com/wing1008"
18
+ }
19
+ ],
20
+ "homepage": "https://github.com/wing1008/codesynapt#readme",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/wing1008/codesynapt.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/wing1008/codesynapt/issues"
27
+ },
28
+ "keywords": [
29
+ "code-visualization",
30
+ "dependency-graph",
31
+ "mcp",
32
+ "ai-coding-agent",
33
+ "blast-radius",
34
+ "3d",
35
+ "electron",
36
+ "three.js",
37
+ "developer-tools"
38
+ ],
39
+ "type": "module",
40
+ "main": "electron/main.cjs",
41
+ "bin": {
42
+ "codesynapt-server": "server.js",
43
+ "codesynapt": "packages/core/bin/codesynapt.cjs",
44
+ "cs": "packages/core/bin/codesynapt.cjs",
45
+ "codesynapt-mcp": "packages/core/bin/codesynapt-mcp.cjs"
46
+ },
47
+ "files": [
48
+ "packages/core/",
49
+ "electron/",
50
+ "public/",
51
+ "scripts/",
52
+ "plugin-api/",
53
+ "server.js",
54
+ "LICENSE",
55
+ "LICENSES.md",
56
+ "CHANGELOG.md",
57
+ "README.md"
58
+ ],
59
+ "scripts": {
60
+ "postinstall": "node scripts/copy-vendor.js",
61
+ "start": "electron .",
62
+ "electron": "electron .",
63
+ "server": "node server.js",
64
+ "test": "vitest run",
65
+ "test:watch": "vitest",
66
+ "test:smoke": "node test.js",
67
+ "bench": "node scripts/perf-test.js",
68
+ "license-check": "node scripts/license-check.js",
69
+ "dist": "electron-builder",
70
+ "dist:mac": "electron-builder --mac",
71
+ "dist:win": "node scripts/download-bundled-node.cjs && electron-builder --win",
72
+ "dist:linux": "electron-builder --linux"
73
+ },
74
+ "dependencies": {
75
+ "@babel/parser": "^7.24.0",
76
+ "@babel/traverse": "^7.24.0",
77
+ "chokidar": "^3.6.0",
78
+ "tree-sitter-wasms": "^0.1.13",
79
+ "typescript": "^6.0.3",
80
+ "web-tree-sitter": "^0.20.8"
81
+ },
82
+ "optionalDependencies": {
83
+ "@xenova/transformers": "^2.17.2",
84
+ "three": "^0.160.0",
85
+ "ws": "^8.16.0"
86
+ },
87
+ "devDependencies": {
88
+ "@electron/fuses": "^2.1.1",
89
+ "autocannon": "^8.0.0",
90
+ "clinic": "^13.0.0",
91
+ "electron": "^41.0.0",
92
+ "electron-builder": "^25.0.0",
93
+ "electron-updater": "^6.8.3",
94
+ "memlab": "^2.0.3",
95
+ "playwright": "^1.60.0",
96
+ "vitest": "^2.1.9"
97
+ },
98
+ "build": {
99
+ "appId": "io.codesynapt.desktop",
100
+ "productName": "CodeSynapt",
101
+ "artifactName": "${productName}-Setup-${version}.${ext}",
102
+ "afterPack": "scripts/fuses-after-pack.cjs",
103
+ "asar": false,
104
+ "files": [
105
+ "electron/**/*",
106
+ "public/**/*",
107
+ "packages/core/**/*",
108
+ "plugin-api/**/*",
109
+ "scripts/**/*",
110
+ "server.js",
111
+ "package.json"
112
+ ],
113
+ "extraResources": [
114
+ {
115
+ "from": "build/bundled-node/",
116
+ "to": "bundled-node/",
117
+ "filter": [
118
+ "**/*"
119
+ ]
120
+ },
121
+ {
122
+ "from": "build/installer-bin/",
123
+ "to": "installer-bin/",
124
+ "filter": [
125
+ "**/*"
126
+ ]
127
+ }
128
+ ],
129
+ "directories": {
130
+ "buildResources": "build",
131
+ "output": "dist"
132
+ },
133
+ "mac": {
134
+ "category": "public.app-category.developer-tools",
135
+ "target": [
136
+ {
137
+ "target": "dmg",
138
+ "arch": [
139
+ "x64",
140
+ "arm64"
141
+ ]
142
+ },
143
+ {
144
+ "target": "zip",
145
+ "arch": [
146
+ "x64",
147
+ "arm64"
148
+ ]
149
+ }
150
+ ],
151
+ "hardenedRuntime": true,
152
+ "gatekeeperAssess": false,
153
+ "entitlements": "build/entitlements.mac.plist",
154
+ "entitlementsInherit": "build/entitlements.mac.plist"
155
+ },
156
+ "win": {
157
+ "target": [
158
+ {
159
+ "target": "nsis",
160
+ "arch": [
161
+ "x64"
162
+ ]
163
+ }
164
+ ]
165
+ },
166
+ "nsis": {
167
+ "oneClick": false,
168
+ "perMachine": false,
169
+ "allowToChangeInstallationDirectory": true,
170
+ "allowElevation": false,
171
+ "createDesktopShortcut": true,
172
+ "createStartMenuShortcut": true,
173
+ "shortcutName": "CodeSynapt",
174
+ "uninstallDisplayName": "CodeSynapt ${version}",
175
+ "deleteAppDataOnUninstall": false,
176
+ "differentialPackage": false,
177
+ "runAfterFinish": true,
178
+ "include": "build/installer.nsh"
179
+ },
180
+ "linux": {
181
+ "target": [
182
+ {
183
+ "target": "AppImage",
184
+ "arch": [
185
+ "x64"
186
+ ]
187
+ },
188
+ {
189
+ "target": "deb",
190
+ "arch": [
191
+ "x64"
192
+ ]
193
+ },
194
+ {
195
+ "target": "tar.gz",
196
+ "arch": [
197
+ "x64"
198
+ ]
199
+ }
200
+ ],
201
+ "category": "Development",
202
+ "synopsis": "MCP-native code graph visualizer for AI coding agents",
203
+ "description": "CodeSynapt visualizes how files in a codebase connect to each other in 3D, and exposes the dependency graph to AI coding agents via MCP — so agents can see blast radius before editing."
204
+ },
205
+ "publish": [
206
+ {
207
+ "provider": "github",
208
+ "owner": "wing1008",
209
+ "repo": "codesynapt"
210
+ }
211
+ ]
212
+ },
213
+ "engines": {
214
+ "node": ">=20"
215
+ }
216
+ }