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.
- package/CHANGELOG.md +17 -0
- package/LICENSE +686 -0
- package/LICENSES.md +141 -0
- package/README.md +331 -0
- package/electron/main.cjs +2849 -0
- package/electron/plugin-loader.cjs +184 -0
- package/electron/preload.cjs +108 -0
- package/package.json +216 -0
- package/packages/core/bin/codesynapt-mcp.cjs +611 -0
- package/packages/core/bin/codesynapt.cjs +1933 -0
- package/packages/core/legacy.js +300 -0
- package/packages/core/lib/control-server.cjs +1539 -0
- package/packages/core/lib/embedding.cjs +89 -0
- package/packages/core/lib/logger.cjs +63 -0
- package/packages/core/lib/search-cache.cjs +140 -0
- package/packages/core/lib/search-worker.cjs +255 -0
- package/packages/core/lib/search.cjs +211 -0
- package/packages/core/lib/symbol-graph.cjs +402 -0
- package/packages/core/lib/symbol-parser-js.cjs +542 -0
- package/packages/core/lib/symbol-parser-misc.cjs +394 -0
- package/packages/core/lib/symbol-parser-py.cjs +215 -0
- package/packages/core/lib/symbol-parser-treesitter.cjs +658 -0
- package/packages/core/lib/symbol-parser-tsc.cjs +332 -0
- package/packages/core/monorepo.js +310 -0
- package/packages/core/parser.js +2234 -0
- package/packages/core/scanner.js +623 -0
- package/plugin-api/LICENSE +21 -0
- package/plugin-api/README.md +114 -0
- package/plugin-api/docs/01-getting-started.md +197 -0
- package/plugin-api/docs/02-concepts.md +269 -0
- package/plugin-api/docs/api-reference.md +463 -0
- package/plugin-api/docs/troubleshooting.md +332 -0
- package/plugin-api/docs/types/exporter.md +377 -0
- package/plugin-api/docs/types/theme.md +312 -0
- package/plugin-api/examples/hello-world-plugin/README.md +70 -0
- package/plugin-api/examples/hello-world-plugin/main.js +36 -0
- package/plugin-api/examples/hello-world-plugin/manifest.json +12 -0
- package/plugin-api/examples/mermaid-exporter/README.md +125 -0
- package/plugin-api/examples/mermaid-exporter/main.js +58 -0
- package/plugin-api/examples/mermaid-exporter/manifest.json +12 -0
- package/plugin-api/examples/rust-parser/README.md +71 -0
- package/plugin-api/examples/rust-parser/main.js +123 -0
- package/plugin-api/examples/rust-parser/manifest.json +12 -0
- package/plugin-api/examples/sunset-theme/README.md +95 -0
- package/plugin-api/examples/sunset-theme/manifest.json +12 -0
- package/plugin-api/examples/sunset-theme/theme.css +31 -0
- package/plugin-api/package.json +20 -0
- package/plugin-api/types.d.ts +395 -0
- package/public/app.js +6837 -0
- package/public/backend.js +285 -0
- package/public/index.html +647 -0
- package/public/plugin-host.js +321 -0
- package/public/style.css +4359 -0
- package/public/vendor/three.module.js +53044 -0
- package/scripts/competitor-watch.mjs +144 -0
- package/scripts/copy-vendor.js +21 -0
- package/scripts/download-bundled-node.cjs +53 -0
- package/scripts/fuses-after-pack.cjs +34 -0
- package/scripts/license-check.js +119 -0
- package/scripts/perf-test.js +200 -0
- package/server.js +132 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════
|
|
2
|
+
// Plugin host — renderer process side
|
|
3
|
+
//
|
|
4
|
+
// Activates plugins discovered by the main process. Themes get
|
|
5
|
+
// their CSS injected into the document. Code plugins get an
|
|
6
|
+
// isolated `context` object exposing only the approved API
|
|
7
|
+
// surface; they cannot reach into app internals directly.
|
|
8
|
+
//
|
|
9
|
+
// This is not a hard security boundary (anything in the renderer
|
|
10
|
+
// shares the same JS realm) but it is a clear contract that
|
|
11
|
+
// plugins are expected to use. Anything reading via global window
|
|
12
|
+
// digging is unsupported and may break across versions.
|
|
13
|
+
// ═══════════════════════════════════════════════════════════
|
|
14
|
+
|
|
15
|
+
// Renderer-side plugin host. Uses window.__bus for events.
|
|
16
|
+
|
|
17
|
+
// Internal registries — written by plugins, read by the app
|
|
18
|
+
export const pluginRegistry = {
|
|
19
|
+
themes: new Map(), // id -> { manifest, css }
|
|
20
|
+
exporters: new Map(), // id -> { manifest, opts }
|
|
21
|
+
parsers: new Map(), // id -> { manifest, opts }
|
|
22
|
+
layouts: new Map(), // id -> { manifest, opts }
|
|
23
|
+
panels: new Map(), // id -> { manifest, handle }
|
|
24
|
+
contextMenuItems: [], // list of { plugin, opts }
|
|
25
|
+
commands: new Map(), // id -> { plugin, opts }
|
|
26
|
+
loaded: [], // raw discovery result for the settings UI
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Track plugin lifecycle so we can deactivate cleanly
|
|
30
|
+
const activations = new Map() // pluginId -> { deactivate, disposables }
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Build a per-plugin context object. This is what plugins see via
|
|
34
|
+
* the `ctx` argument to their activate() function.
|
|
35
|
+
*/
|
|
36
|
+
function makeContext(manifest, hostHelpers) {
|
|
37
|
+
const disposables = []
|
|
38
|
+
const perms = new Set(manifest.permissions || [])
|
|
39
|
+
|
|
40
|
+
const requirePerm = (perm, action) => {
|
|
41
|
+
if (!perms.has(perm)) {
|
|
42
|
+
throw new Error(`Plugin "${manifest.id}" attempted "${action}" but does not declare permission "${perm}"`)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Storage scoped to this plugin id
|
|
47
|
+
const storagePrefix = `codesynapt:plugin:${manifest.id}:`
|
|
48
|
+
const storage = {
|
|
49
|
+
get(key) {
|
|
50
|
+
try {
|
|
51
|
+
const raw = localStorage.getItem(storagePrefix + key)
|
|
52
|
+
return raw == null ? null : JSON.parse(raw)
|
|
53
|
+
} catch { return null }
|
|
54
|
+
},
|
|
55
|
+
set(key, value) {
|
|
56
|
+
try {
|
|
57
|
+
localStorage.setItem(storagePrefix + key, JSON.stringify(value))
|
|
58
|
+
} catch { /* quota */ }
|
|
59
|
+
},
|
|
60
|
+
delete(key) {
|
|
61
|
+
try { localStorage.removeItem(storagePrefix + key) } catch {}
|
|
62
|
+
},
|
|
63
|
+
clear() {
|
|
64
|
+
try {
|
|
65
|
+
const remove = []
|
|
66
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
67
|
+
const k = localStorage.key(i)
|
|
68
|
+
if (k && k.startsWith(storagePrefix)) remove.push(k)
|
|
69
|
+
}
|
|
70
|
+
for (const k of remove) localStorage.removeItem(k)
|
|
71
|
+
} catch {}
|
|
72
|
+
},
|
|
73
|
+
keys() {
|
|
74
|
+
const keys = []
|
|
75
|
+
try {
|
|
76
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
77
|
+
const k = localStorage.key(i)
|
|
78
|
+
if (k && k.startsWith(storagePrefix)) keys.push(k.slice(storagePrefix.length))
|
|
79
|
+
}
|
|
80
|
+
} catch {}
|
|
81
|
+
return keys
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Graph API — wraps the app's state in a read-only-looking facade
|
|
86
|
+
const graph = {
|
|
87
|
+
get root() { return hostHelpers.getState().root },
|
|
88
|
+
get nodes() { return hostHelpers.getState().byIdx },
|
|
89
|
+
get edges() { return hostHelpers.getState().edges },
|
|
90
|
+
get selectedId() { return hostHelpers.getState().selectedId },
|
|
91
|
+
get activeSet() { return hostHelpers.getEffectiveActiveSet() },
|
|
92
|
+
async readFile(id) {
|
|
93
|
+
requirePerm('read-files', 'readFile')
|
|
94
|
+
return hostHelpers.readFile(id)
|
|
95
|
+
},
|
|
96
|
+
getNode(id) { return hostHelpers.getState().nodes.get(id) || null },
|
|
97
|
+
outgoing(id) {
|
|
98
|
+
const result = []
|
|
99
|
+
for (const e of hostHelpers.getState().edges) {
|
|
100
|
+
if (e.s === id) result.push(e)
|
|
101
|
+
}
|
|
102
|
+
return result
|
|
103
|
+
},
|
|
104
|
+
incoming(id) {
|
|
105
|
+
const result = []
|
|
106
|
+
for (const e of hostHelpers.getState().edges) {
|
|
107
|
+
if (e.t === id) result.push(e)
|
|
108
|
+
}
|
|
109
|
+
return result
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// UI registration
|
|
114
|
+
const ui = {
|
|
115
|
+
registerPanel(opts) {
|
|
116
|
+
requirePerm('ui-panel', 'registerPanel')
|
|
117
|
+
const handle = hostHelpers.createPanel(manifest, opts)
|
|
118
|
+
disposables.push(() => handle.dispose())
|
|
119
|
+
return handle
|
|
120
|
+
},
|
|
121
|
+
registerContextMenuItem(opts) {
|
|
122
|
+
requirePerm('context-menu', 'registerContextMenuItem')
|
|
123
|
+
const entry = { plugin: manifest.id, opts }
|
|
124
|
+
pluginRegistry.contextMenuItems.push(entry)
|
|
125
|
+
disposables.push(() => {
|
|
126
|
+
const i = pluginRegistry.contextMenuItems.indexOf(entry)
|
|
127
|
+
if (i >= 0) pluginRegistry.contextMenuItems.splice(i, 1)
|
|
128
|
+
})
|
|
129
|
+
return { dispose() {
|
|
130
|
+
const i = pluginRegistry.contextMenuItems.indexOf(entry)
|
|
131
|
+
if (i >= 0) pluginRegistry.contextMenuItems.splice(i, 1)
|
|
132
|
+
}}
|
|
133
|
+
},
|
|
134
|
+
registerCommand(opts) {
|
|
135
|
+
pluginRegistry.commands.set(opts.id, { plugin: manifest.id, opts })
|
|
136
|
+
disposables.push(() => pluginRegistry.commands.delete(opts.id))
|
|
137
|
+
return { dispose() { pluginRegistry.commands.delete(opts.id) } }
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Exporter registry
|
|
142
|
+
const exporters = {
|
|
143
|
+
register(opts) {
|
|
144
|
+
requirePerm('export', 'register exporter')
|
|
145
|
+
pluginRegistry.exporters.set(opts.name, { manifest, opts })
|
|
146
|
+
disposables.push(() => pluginRegistry.exporters.delete(opts.name))
|
|
147
|
+
return { dispose() { pluginRegistry.exporters.delete(opts.name) } }
|
|
148
|
+
},
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Parser registry
|
|
152
|
+
const parsers = {
|
|
153
|
+
register(opts) {
|
|
154
|
+
requirePerm('parse', 'register parser')
|
|
155
|
+
pluginRegistry.parsers.set(opts.name, { manifest, opts })
|
|
156
|
+
disposables.push(() => pluginRegistry.parsers.delete(opts.name))
|
|
157
|
+
return { dispose() { pluginRegistry.parsers.delete(opts.name) } }
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Layout registry
|
|
162
|
+
const layouts = {
|
|
163
|
+
register(opts) {
|
|
164
|
+
pluginRegistry.layouts.set(opts.id, { manifest, opts })
|
|
165
|
+
disposables.push(() => pluginRegistry.layouts.delete(opts.id))
|
|
166
|
+
return { dispose() { pluginRegistry.layouts.delete(opts.id) } }
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Event bus — proxy to the app's bus but restrict to read-only events
|
|
171
|
+
const events = {
|
|
172
|
+
on(name, handler) {
|
|
173
|
+
const off = hostHelpers.busOn(name, handler)
|
|
174
|
+
disposables.push(off)
|
|
175
|
+
return off
|
|
176
|
+
},
|
|
177
|
+
off(name, handler) { hostHelpers.busOff(name, handler) },
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const ctx = {
|
|
181
|
+
manifest,
|
|
182
|
+
appVersion: hostHelpers.appVersion,
|
|
183
|
+
graph,
|
|
184
|
+
ui,
|
|
185
|
+
exporters,
|
|
186
|
+
parsers,
|
|
187
|
+
layouts,
|
|
188
|
+
events,
|
|
189
|
+
storage,
|
|
190
|
+
toast: (msg) => hostHelpers.toast(`[${manifest.id}] ${msg}`),
|
|
191
|
+
log: (...args) => console.log(`[plugin:${manifest.id}]`, ...args),
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { ctx, disposables }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Activate one plugin. Themes just inject CSS; code plugins are
|
|
199
|
+
* compiled as an ESM module via dynamic blob URL.
|
|
200
|
+
*/
|
|
201
|
+
async function activatePlugin(record, hostHelpers) {
|
|
202
|
+
const { manifest, entryContent, error } = record
|
|
203
|
+
if (error) {
|
|
204
|
+
console.warn(`[plugin] skipping ${manifest.id}: ${error}`)
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Theme — inject CSS, register in the theme registry, done
|
|
209
|
+
if (manifest.type === 'theme') {
|
|
210
|
+
const styleId = `plugin-theme-${manifest.id}`
|
|
211
|
+
let style = document.getElementById(styleId)
|
|
212
|
+
if (!style) {
|
|
213
|
+
style = document.createElement('style')
|
|
214
|
+
style.id = styleId
|
|
215
|
+
style.dataset.pluginId = manifest.id
|
|
216
|
+
document.head.appendChild(style)
|
|
217
|
+
}
|
|
218
|
+
style.textContent = entryContent
|
|
219
|
+
pluginRegistry.themes.set(manifest.id, { manifest, css: entryContent })
|
|
220
|
+
activations.set(manifest.id, {
|
|
221
|
+
deactivate: () => style.remove(),
|
|
222
|
+
disposables: [],
|
|
223
|
+
})
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Code plugin — execute as an ES module via a Blob URL.
|
|
228
|
+
// This isolates the plugin from the global window scope (it gets
|
|
229
|
+
// its own module record) while still letting it use modern JS.
|
|
230
|
+
let module
|
|
231
|
+
try {
|
|
232
|
+
const blob = new Blob([entryContent], { type: 'text/javascript' })
|
|
233
|
+
const url = URL.createObjectURL(blob)
|
|
234
|
+
module = await import(/* @vite-ignore */ url)
|
|
235
|
+
URL.revokeObjectURL(url)
|
|
236
|
+
} catch (err) {
|
|
237
|
+
console.error(`[plugin] ${manifest.id} failed to load:`, err)
|
|
238
|
+
hostHelpers.toast(`Plugin "${manifest.name}" failed to load`)
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const plugin = module.default
|
|
243
|
+
if (!plugin || typeof plugin.activate !== 'function') {
|
|
244
|
+
console.warn(`[plugin] ${manifest.id} has no default export with activate()`)
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const { ctx, disposables } = makeContext(manifest, hostHelpers)
|
|
249
|
+
try {
|
|
250
|
+
await plugin.activate(ctx)
|
|
251
|
+
} catch (err) {
|
|
252
|
+
console.error(`[plugin] ${manifest.id} activate() threw:`, err)
|
|
253
|
+
hostHelpers.toast(`Plugin "${manifest.name}" crashed during activation`)
|
|
254
|
+
// Run any cleanup that was registered before the crash
|
|
255
|
+
for (const fn of disposables) {
|
|
256
|
+
try { fn() } catch {}
|
|
257
|
+
}
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
activations.set(manifest.id, {
|
|
262
|
+
deactivate: async () => {
|
|
263
|
+
try {
|
|
264
|
+
if (typeof plugin.deactivate === 'function') await plugin.deactivate()
|
|
265
|
+
} catch (err) {
|
|
266
|
+
console.error(`[plugin] ${manifest.id} deactivate() threw:`, err)
|
|
267
|
+
}
|
|
268
|
+
for (const fn of disposables) {
|
|
269
|
+
try { fn() } catch {}
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
disposables,
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Public entry point — discover all plugins via IPC, then activate
|
|
278
|
+
* each one. Idempotent: calling twice deactivates first.
|
|
279
|
+
*/
|
|
280
|
+
export async function initPlugins(hostHelpers) {
|
|
281
|
+
// Deactivate anything currently running
|
|
282
|
+
for (const [, info] of activations) {
|
|
283
|
+
try { await info.deactivate() } catch {}
|
|
284
|
+
}
|
|
285
|
+
activations.clear()
|
|
286
|
+
pluginRegistry.themes.clear()
|
|
287
|
+
pluginRegistry.exporters.clear()
|
|
288
|
+
pluginRegistry.parsers.clear()
|
|
289
|
+
pluginRegistry.layouts.clear()
|
|
290
|
+
pluginRegistry.panels.clear()
|
|
291
|
+
pluginRegistry.contextMenuItems.length = 0
|
|
292
|
+
pluginRegistry.commands.clear()
|
|
293
|
+
|
|
294
|
+
if (!window.codesynapt || !window.codesynapt.listPlugins) {
|
|
295
|
+
// Browser dev mode — no plugins available
|
|
296
|
+
pluginRegistry.loaded = []
|
|
297
|
+
return
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
let discovered = []
|
|
301
|
+
try {
|
|
302
|
+
discovered = await window.codesynapt.listPlugins()
|
|
303
|
+
} catch (err) {
|
|
304
|
+
console.error('[plugin] discovery failed:', err)
|
|
305
|
+
return
|
|
306
|
+
}
|
|
307
|
+
pluginRegistry.loaded = discovered
|
|
308
|
+
|
|
309
|
+
for (const record of discovered) {
|
|
310
|
+
await activatePlugin(record, hostHelpers)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
console.log(`[plugin] loaded ${activations.size} plugin(s)`)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export async function deactivatePlugin(id) {
|
|
317
|
+
const info = activations.get(id)
|
|
318
|
+
if (!info) return
|
|
319
|
+
await info.deactivate()
|
|
320
|
+
activations.delete(id)
|
|
321
|
+
}
|