@eyeauras/insight-cli 0.0.0-stage → 0.1.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 (164) hide show
  1. package/.cli-package-build.json +1 -0
  2. package/LICENSE +21 -0
  3. package/README.md +54 -2
  4. package/SKILL.md +55 -0
  5. package/dist/src/administration-results.d.ts +1 -0
  6. package/dist/src/administration-results.js +84 -0
  7. package/dist/src/administration.d.ts +14 -0
  8. package/dist/src/administration.js +120 -0
  9. package/dist/src/archive-contract.d.ts +6 -0
  10. package/dist/src/archive-contract.js +108 -0
  11. package/dist/src/archive.d.ts +12 -0
  12. package/dist/src/archive.js +116 -0
  13. package/dist/src/arguments.d.ts +14 -0
  14. package/dist/src/arguments.js +62 -0
  15. package/dist/src/artifacts.d.ts +17 -0
  16. package/dist/src/artifacts.js +115 -0
  17. package/dist/src/bin.d.ts +2 -0
  18. package/dist/src/bin.js +4 -0
  19. package/dist/src/cli.d.ts +10 -0
  20. package/dist/src/cli.js +387 -0
  21. package/dist/src/comment-effects.d.ts +3 -0
  22. package/dist/src/comment-effects.js +55 -0
  23. package/dist/src/contexts.d.ts +115 -0
  24. package/dist/src/contexts.js +1068 -0
  25. package/dist/src/coordinator.d.ts +41 -0
  26. package/dist/src/coordinator.js +160 -0
  27. package/dist/src/cross-results.d.ts +14 -0
  28. package/dist/src/cross-results.js +71 -0
  29. package/dist/src/cross.d.ts +3 -0
  30. package/dist/src/cross.js +43 -0
  31. package/dist/src/data-effects.d.ts +3 -0
  32. package/dist/src/data-effects.js +103 -0
  33. package/dist/src/debugger-results.d.ts +4 -0
  34. package/dist/src/debugger-results.js +151 -0
  35. package/dist/src/debugger-supervisor.d.ts +1 -0
  36. package/dist/src/debugger-supervisor.js +136 -0
  37. package/dist/src/debugger.d.ts +21 -0
  38. package/dist/src/debugger.js +267 -0
  39. package/dist/src/documentation-effects.d.ts +5 -0
  40. package/dist/src/documentation-effects.js +56 -0
  41. package/dist/src/extensions.d.ts +64 -0
  42. package/dist/src/extensions.js +289 -0
  43. package/dist/src/function-effects.d.ts +4 -0
  44. package/dist/src/function-effects.js +112 -0
  45. package/dist/src/ghidra.d.ts +50 -0
  46. package/dist/src/ghidra.js +468 -0
  47. package/dist/src/index.d.ts +2 -0
  48. package/dist/src/index.js +1 -0
  49. package/dist/src/initial-model.d.ts +6 -0
  50. package/dist/src/initial-model.js +85 -0
  51. package/dist/src/json.d.ts +2 -0
  52. package/dist/src/json.js +8 -0
  53. package/dist/src/lifecycle.d.ts +49 -0
  54. package/dist/src/lifecycle.js +148 -0
  55. package/dist/src/memory-inputs.d.ts +8 -0
  56. package/dist/src/memory-inputs.js +56 -0
  57. package/dist/src/merge.d.ts +29 -0
  58. package/dist/src/merge.js +66 -0
  59. package/dist/src/model.d.ts +74 -0
  60. package/dist/src/model.js +67 -0
  61. package/dist/src/mutation-effects.d.ts +4 -0
  62. package/dist/src/mutation-effects.js +321 -0
  63. package/dist/src/mutation-inputs.d.ts +2 -0
  64. package/dist/src/mutation-inputs.js +71 -0
  65. package/dist/src/mutations.d.ts +4 -0
  66. package/dist/src/mutations.js +53 -0
  67. package/dist/src/native-inputs.d.ts +28 -0
  68. package/dist/src/native-inputs.js +171 -0
  69. package/dist/src/native-queries.d.ts +2 -0
  70. package/dist/src/native-queries.js +29 -0
  71. package/dist/src/projects.d.ts +31 -0
  72. package/dist/src/projects.js +301 -0
  73. package/dist/src/prototype-cli.d.ts +8 -0
  74. package/dist/src/prototype-cli.js +155 -0
  75. package/dist/src/queries.d.ts +5 -0
  76. package/dist/src/queries.js +84 -0
  77. package/dist/src/query-policy.d.ts +6 -0
  78. package/dist/src/query-policy.js +72 -0
  79. package/dist/src/query-results.d.ts +2 -0
  80. package/dist/src/query-results.js +224 -0
  81. package/dist/src/research.d.ts +184 -0
  82. package/dist/src/research.js +808 -0
  83. package/dist/src/saved-materials.d.ts +12 -0
  84. package/dist/src/saved-materials.js +196 -0
  85. package/dist/src/scripts.d.ts +19 -0
  86. package/dist/src/scripts.js +79 -0
  87. package/dist/src/sdk-contracts.d.ts +13 -0
  88. package/dist/src/sdk-contracts.js +37 -0
  89. package/dist/src/sdk-files.d.ts +29 -0
  90. package/dist/src/sdk-files.js +249 -0
  91. package/dist/src/sdk.d.ts +4 -0
  92. package/dist/src/sdk.js +125 -0
  93. package/dist/src/server.d.ts +44 -0
  94. package/dist/src/server.js +455 -0
  95. package/dist/src/store.d.ts +33 -0
  96. package/dist/src/store.js +178 -0
  97. package/dist/src/sync-policy.d.ts +22 -0
  98. package/dist/src/sync-policy.js +123 -0
  99. package/dist/src/transport.d.ts +23 -0
  100. package/dist/src/transport.js +135 -0
  101. package/dist/src/ui-results.d.ts +2 -0
  102. package/dist/src/ui-results.js +80 -0
  103. package/dist/src/ui.d.ts +17 -0
  104. package/dist/src/ui.js +88 -0
  105. package/dist/src/utility.d.ts +14 -0
  106. package/dist/src/utility.js +43 -0
  107. package/dist/src/version-tracking.d.ts +57 -0
  108. package/dist/src/version-tracking.js +200 -0
  109. package/dist/src/workspace.d.ts +199 -0
  110. package/dist/src/workspace.js +564 -0
  111. package/package.json +45 -4
  112. package/references/InsightRepositoryProbe.java +55 -0
  113. package/references/administration.md +33 -0
  114. package/references/archive.md +32 -0
  115. package/references/artifacts.md +47 -0
  116. package/references/debugger.md +40 -0
  117. package/references/extensions-and-sync.md +124 -0
  118. package/references/mutations.json +1424 -0
  119. package/references/native/InsightApplyDocumentation.java +48 -0
  120. package/references/native/InsightArchivePayloads.java +36 -0
  121. package/references/native/InsightBlockTransfer.java +57 -0
  122. package/references/native/InsightEmulation.java +93 -0
  123. package/references/native/InsightExtensions.java +36 -0
  124. package/references/native/InsightFunctionDatabases.java +98 -0
  125. package/references/native/InsightGuiMain.java +32 -0
  126. package/references/native/InsightHeadlessMain.java +10 -0
  127. package/references/native/InsightInitialImport.java +42 -0
  128. package/references/native/InsightInitialJournal.java +48 -0
  129. package/references/native/InsightInitialPublication.java +142 -0
  130. package/references/native/InsightKeytool.java +16 -0
  131. package/references/native/InsightNativeReceipt.java +31 -0
  132. package/references/native/InsightPlugins.java +34 -0
  133. package/references/native/InsightProjectFiles.java +92 -0
  134. package/references/native/InsightPublicationRecovery.java +63 -0
  135. package/references/native/InsightPythonWheels.java +31 -0
  136. package/references/native/InsightResearchQueries.java +188 -0
  137. package/references/native/InsightSdkFiles.java +142 -0
  138. package/references/native/InsightSdkScript.java +31 -0
  139. package/references/native/InsightSdkTools.java +338 -0
  140. package/references/native/InsightSdkTypes.java +132 -0
  141. package/references/native/InsightServerAdministration.java +82 -0
  142. package/references/native/InsightServerInit.java +62 -0
  143. package/references/native/InsightServerMain.java +43 -0
  144. package/references/native/InsightServerProbe.java +29 -0
  145. package/references/native/InsightSnapshotFields.java +68 -0
  146. package/references/native/InsightSnapshotInspect.java +189 -0
  147. package/references/native/InsightSnapshotPublisher.java +123 -0
  148. package/references/native/InsightStartDebugger.java +28 -0
  149. package/references/native/InsightStopDebugger.java +20 -0
  150. package/references/native/InsightTraceInspect.java +24 -0
  151. package/references/native/InsightTraceState.java +130 -0
  152. package/references/native/InsightTraceTools.java +114 -0
  153. package/references/native/InsightVersionTracking.java +91 -0
  154. package/references/native-workflows.md +112 -0
  155. package/references/queries.json +1761 -0
  156. package/references/query-semantics.md +96 -0
  157. package/references/recovery.md +53 -0
  158. package/references/research.md +101 -0
  159. package/references/setup.md +47 -0
  160. package/runtime/GhidraMCP-7.0.0.jar +0 -0
  161. package/runtime/GhidraMCP-LICENSE +202 -0
  162. package/runtime/GhidraMCP-NOTICE +10 -0
  163. package/runtime/provenance.json +11 -0
  164. package/skills/insight-re/SKILL.md +108 -0
@@ -0,0 +1,289 @@
1
+ import { command, Permission } from '@eyeauras/cli-factory';
2
+ import { readFile, readdir, lstat, realpath, mkdir, open, rename, rm, utimes, chmod } from 'node:fs/promises';
3
+ import { readFileSync, mkdirSync, realpathSync, constants } from 'node:fs';
4
+ import { join, resolve, basename, delimiter } from 'node:path';
5
+ import { createHash, randomUUID } from 'node:crypto';
6
+ import { fail, id, object, stable, text } from './model.js';
7
+ const empty = () => ({ schema: 1, modules: [] });
8
+ const hex = (value) => { if (typeof value !== 'string' || !/^[a-f0-9]{64}$/.test(value))
9
+ fail('invalid', 'Expected a lowercase SHA256.'); return value; };
10
+ export function extensionState(directory) {
11
+ let data;
12
+ try {
13
+ const bytes = readFileSync(join(directory, 'extensions', 'active.json'));
14
+ if (bytes.length > 2097152)
15
+ fail('limit', 'Extension manifest exceeds 2 MiB.');
16
+ data = JSON.parse(bytes.toString());
17
+ }
18
+ catch (error) {
19
+ if (error.code === 'ENOENT')
20
+ return empty();
21
+ throw error;
22
+ }
23
+ const state = object(data);
24
+ if (state.schema !== 1 || !Array.isArray(state.modules) || state.modules.length > 32)
25
+ fail('binding', 'Managed extension manifest is malformed.');
26
+ const names = new Set();
27
+ for (const item of state.modules) {
28
+ const m = object(item);
29
+ if (!/^[A-Za-z][A-Za-z0-9_-]{0,79}$/.test(String(m.name)) || names.has(String(m.name)) || m.version !== '12.1.2' || typeof m.enabled !== 'boolean' || !Array.isArray(m.files) || !m.files.length || m.files.length > 8192)
30
+ fail('binding', 'Managed extension identity is malformed.');
31
+ names.add(String(m.name));
32
+ hex(m.sha256);
33
+ const paths = new Set();
34
+ let total = 0;
35
+ for (const item of m.files) {
36
+ const f = object(item);
37
+ modulePath(f.name);
38
+ hex(f.sha256);
39
+ if (paths.has(String(f.name)) || !Number.isSafeInteger(f.size) || Number(f.size) < 0)
40
+ fail('binding', 'Managed extension file manifest is malformed.');
41
+ paths.add(String(f.name));
42
+ total += Number(f.size);
43
+ }
44
+ if (total > 268435456 || !paths.has('Module.manifest') || !paths.has('extension.properties'))
45
+ fail('binding', 'Managed extension omitted its module metadata or exceeded 256 MiB.');
46
+ if (createHash('sha256').update(stable(m.files)).digest('hex') !== m.sha256)
47
+ fail('binding', 'Managed extension content identity changed.');
48
+ }
49
+ return state;
50
+ }
51
+ function modulePath(value) {
52
+ const name = text(value, 1024);
53
+ if (name.includes('\\') || name.includes(':') || name.split('/').some(p => !p || p === '.' || p === '..' || p.startsWith('.')) || name.split('/').length > 16)
54
+ fail('invalid', 'Extension files require ordinary relative module paths.');
55
+ return name;
56
+ }
57
+ async function tree(directory) {
58
+ if ((await lstat(directory)).isSymbolicLink() || !(await lstat(directory)).isDirectory())
59
+ fail('invalid', 'Extension input must be an ordinary unpacked SDK module directory.');
60
+ const files = [];
61
+ let size = 0;
62
+ async function walk(path, prefix) {
63
+ for (const entry of (await readdir(path, { withFileTypes: true })).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
64
+ const name = modulePath(prefix + entry.name), file = join(path, entry.name);
65
+ if (entry.isDirectory())
66
+ await walk(file, name + '/');
67
+ else if (entry.isFile()) {
68
+ if (files.length >= 8192)
69
+ fail('limit', 'Extension exceeds 8192 files.');
70
+ const handle = await open(file, 'r');
71
+ try {
72
+ const s = await handle.stat();
73
+ size += s.size;
74
+ if (!s.isFile() || size > 268435456)
75
+ fail('limit', 'Extension exceeds 256 MiB.');
76
+ const hash = createHash('sha256');
77
+ for await (const bytes of handle.createReadStream({ autoClose: false }))
78
+ hash.update(bytes);
79
+ files.push({ name, sha256: hash.digest('hex'), size: s.size });
80
+ }
81
+ finally {
82
+ await handle.close();
83
+ }
84
+ }
85
+ else
86
+ fail('invalid', 'Extension symlinks and special files are unsupported.');
87
+ }
88
+ }
89
+ await walk(directory, '');
90
+ return files;
91
+ }
92
+ export async function inspectExtension(directory) {
93
+ const files = await tree(directory);
94
+ if (!files.some(f => f.name === 'Module.manifest') || !files.some(f => f.name === 'extension.properties'))
95
+ fail('invalid', 'Expected Module.manifest and extension.properties at the module root.');
96
+ const properties = await readFile(join(directory, 'extension.properties'), 'utf8');
97
+ const property = (name) => new RegExp('^' + name + '\\s*=\\s*([^\\r\\n]+)', 'm').exec(properties)?.[1]?.trim();
98
+ const name = property('name'), version = property('version');
99
+ if (!name || !/^[A-Za-z][A-Za-z0-9_-]{0,79}$/.test(name) || basename(directory) !== name || version !== '12.1.2')
100
+ fail('configuration', 'Extension directory/name must agree and version must be exactly 12.1.2.');
101
+ return { name, version, sha256: createHash('sha256').update(stable(files)).digest('hex'), files, enabled: true };
102
+ }
103
+ export function extensionDirectory(directory, module) { return join(directory, 'extensions', 'modules', module.sha256, module.name); }
104
+ /** Verify immutable owned copies; a changed input never joins an existing JVM's classpath. */
105
+ export async function managedExtensions(directory) {
106
+ if (!directory)
107
+ return [];
108
+ const result = [];
109
+ for (const module of extensionState(directory).modules.filter(m => m.enabled)) {
110
+ const root = extensionDirectory(directory, module);
111
+ if (await realpath(root) !== resolve(root) || stable(await tree(root)) !== stable(module.files))
112
+ fail('binding', 'Managed extension bytes changed; restore the original module before continuing.');
113
+ result.push({ ...module, root });
114
+ }
115
+ return result;
116
+ }
117
+ const mutations = new Map();
118
+ export class Extensions {
119
+ directory;
120
+ constructor(directory) { mkdirSync(directory, { recursive: true, mode: 0o700 }); this.directory = realpathSync.native(directory); }
121
+ list() { return { items: extensionState(this.directory).modules.map(({ files, ...m }) => ({ ...m, fileCount: files.length })), runtimeBoundary: 'Stop the IPC owner after changing the enabled module set. Saved contexts and pending work remain retained.' }; }
122
+ async write(state) {
123
+ const root = join(this.directory, 'extensions');
124
+ await mkdir(root, { recursive: true, mode: 0o700 });
125
+ if (await realpath(root) !== resolve(root))
126
+ fail('binding', 'Extension storage moved outside the profile.');
127
+ if (Buffer.byteLength(stable(state)) > 2097152)
128
+ fail('limit', 'Combined extension manifest exceeds 2 MiB.');
129
+ const temp = join(root, 'active-' + randomUUID() + '.json'), file = await open(temp, 'wx', 0o600);
130
+ try {
131
+ await file.writeFile(stable(state));
132
+ await file.sync();
133
+ }
134
+ finally {
135
+ await file.close();
136
+ }
137
+ await rename(temp, join(root, 'active.json'));
138
+ if (process.platform !== 'win32') {
139
+ const dir = await open(root, 'r');
140
+ try {
141
+ await dir.sync();
142
+ }
143
+ finally {
144
+ await dir.close();
145
+ }
146
+ }
147
+ }
148
+ async ordered(action) { const previous = mutations.get(this.directory), task = (previous ? previous.catch(() => { }) : Promise.resolve()).then(action); mutations.set(this.directory, task); try {
149
+ return await task;
150
+ }
151
+ finally {
152
+ if (mutations.get(this.directory) === task)
153
+ mutations.delete(this.directory);
154
+ } }
155
+ install(directory, expected) { return this.ordered(() => this.consume(directory, expected)); }
156
+ async consume(directory, expected) {
157
+ hex(expected);
158
+ const module = await inspectExtension(directory);
159
+ if (module.sha256 !== expected)
160
+ fail('binding', 'Extension differs from the inspected content SHA256.');
161
+ const state = extensionState(this.directory), existing = state.modules.find(m => m.name === module.name);
162
+ if (existing && existing.sha256 !== module.sha256)
163
+ fail('conflict', 'Extension name is already pinned to different bytes; use another profile or module name.');
164
+ const root = extensionDirectory(this.directory, module), stage = join(this.directory, 'extensions', 'staging-' + randomUUID(), module.name);
165
+ await mkdir(stage, { recursive: true, mode: 0o700 });
166
+ try {
167
+ const directories = new Set([stage]);
168
+ for (const f of module.files) {
169
+ const path = join(stage, f.name), parent = resolve(path, '..');
170
+ await mkdir(parent, { recursive: true, mode: 0o700 });
171
+ for (let d = parent; d.startsWith(stage); d = resolve(d, '..'))
172
+ directories.add(d);
173
+ const source = await open(join(directory, f.name), constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)), out = await open(path, 'wx', 0o600);
174
+ let count = 0;
175
+ const hash = createHash('sha256');
176
+ try {
177
+ if (!(await source.stat()).isFile())
178
+ fail('binding', 'Extension input changed its file kind.');
179
+ for await (const chunk of source.createReadStream({ autoClose: false })) {
180
+ count += chunk.length;
181
+ if (count > f.size)
182
+ fail('binding', 'Extension input grew during consumption.');
183
+ hash.update(chunk);
184
+ await out.writeFile(chunk);
185
+ }
186
+ if (count !== f.size || hash.digest('hex') !== f.sha256)
187
+ fail('binding', 'Extension input changed during consumption.');
188
+ await utimes(path, 946684800, 946684800);
189
+ await chmod(path, 0o400);
190
+ await out.sync();
191
+ }
192
+ finally {
193
+ await source.close();
194
+ await out.close();
195
+ }
196
+ }
197
+ if (process.platform !== 'win32')
198
+ for (const path of [...directories].sort((a, b) => b.length - a.length)) {
199
+ const dir = await open(path, 'r');
200
+ try {
201
+ await dir.sync();
202
+ }
203
+ finally {
204
+ await dir.close();
205
+ }
206
+ }
207
+ if (stable(await tree(stage)) !== stable(module.files))
208
+ fail('binding', 'Extension input changed while being consumed.');
209
+ await mkdir(resolve(root, '..'), { recursive: true, mode: 0o700 });
210
+ let present;
211
+ try {
212
+ present = await lstat(root);
213
+ }
214
+ catch (error) {
215
+ if (error.code !== 'ENOENT')
216
+ throw error;
217
+ }
218
+ if (present) {
219
+ if (present.isSymbolicLink() || !present.isDirectory() || stable(await tree(root)) !== stable(module.files))
220
+ fail('binding', 'Existing extension storage differs from its content identity.');
221
+ }
222
+ else
223
+ await rename(stage, root);
224
+ if (process.platform !== 'win32')
225
+ for (const path of [resolve(root, '..'), resolve(root, '../..')]) {
226
+ const dir = await open(path, 'r');
227
+ try {
228
+ await dir.sync();
229
+ }
230
+ finally {
231
+ await dir.close();
232
+ }
233
+ }
234
+ if (!existing) {
235
+ if (state.modules.length >= 32)
236
+ fail('limit', 'At most 32 managed modules per profile.');
237
+ state.modules.push(module);
238
+ state.modules.sort((a, b) => a.name.localeCompare(b.name));
239
+ await this.write(state);
240
+ }
241
+ return { state: 'installed', name: module.name, sha256: module.sha256, enabled: existing?.enabled ?? true, restartRequired: !existing };
242
+ }
243
+ finally {
244
+ await rm(resolve(stage, '..'), { recursive: true, force: true });
245
+ }
246
+ }
247
+ enable(name, enabled) { return this.ordered(() => this.change(name, enabled)); }
248
+ async change(name, enabled) {
249
+ id(name);
250
+ const state = extensionState(this.directory), module = state.modules.find(m => m.name === name);
251
+ if (!module)
252
+ fail('notFound', 'Managed extension is not installed.');
253
+ if (enabled) {
254
+ const root = extensionDirectory(this.directory, module);
255
+ if (stable(await tree(root)) !== stable(module.files))
256
+ fail('binding', 'Managed extension bytes changed.');
257
+ }
258
+ const changed = module.enabled !== enabled;
259
+ module.enabled = enabled;
260
+ if (changed)
261
+ await this.write(state);
262
+ return { state: enabled ? 'enabled' : 'disabled', name, sha256: module.sha256, restartRequired: changed };
263
+ }
264
+ }
265
+ export function extensionCommands(service) {
266
+ return [command('extensions', 'Pin unpacked Ghidra SDK extension modules in this profile', [
267
+ command('inspect', 'Hash a ready-built module without executing extension code', async ({ options: o }, c) => { const { files, ...m } = await inspectExtension(resolve(c.cwd, text(o.directory, 4096))); return { ...m, fileCount: files.length, size: files.reduce((n, f) => n + f.size, 0) }; }, { permission: Permission.ReadOnly, options: [{ flags: '--directory <path>', description: 'Unpacked standard SDK extension module', required: true }] }),
268
+ command('install', 'Consume the inspected module; stop the IPC owner before using changed runtime inputs', ({ options: o }, c) => service(c).install(resolve(c.cwd, text(o.directory, 4096)), text(o.sha256, 64)), { permission: 'Destructive', options: [{ flags: '--directory <path>', description: 'Unpacked standard SDK extension module', required: true }, { flags: '--sha256 <sha>', description: 'Content SHA256 returned by extensions inspect', required: true }] }),
269
+ command('list', 'List pinned managed modules and enablement', (_i, c) => service(c).list(), { permission: Permission.ReadOnly }),
270
+ ...['enable', 'disable'].map(name => command(name, 'Change the enabled module set for the next IPC owner', ({ options: o }, c) => service(c).enable(text(o.name, 80), name === 'enable'), { permission: 'Destructive', options: [{ flags: '--name <name>', description: 'Installed managed module name', required: true }] })),
271
+ ])];
272
+ }
273
+ export const extensionJvmArguments = (roots) => roots.length ? ['-Dghidra.external.modules=' + roots.join(delimiter)] : [];
274
+ export function extensionInventoryCommands(backend) {
275
+ return [command('extensions', 'Inspect enabled SDK capabilities', [
276
+ command('capabilities', 'List actual native language/compiler, analyzer or plugin identities', async ({ options: o }, c) => {
277
+ const kind = text(o.kind);
278
+ if (!['languages', 'analyzers', 'plugins'].includes(kind))
279
+ fail('invalid', 'kind must be languages, analyzers or plugins.');
280
+ const limit = Number(o.limit), offset = Number(o.offset);
281
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !Number.isSafeInteger(offset) || offset < 0 || offset > 2147483647)
282
+ fail('invalid', 'Invalid inventory page.');
283
+ const native = await backend(c)();
284
+ if (!native.sdkFile)
285
+ fail('configuration', 'Native extension discovery is unavailable.');
286
+ return native.sdkFile('extension-inventory', { kind, filter: o.filter === undefined ? '' : text(o.filter, 512), limit, offset });
287
+ }, { permission: Permission.ReadOnly, options: [{ flags: '--kind <kind>', description: 'languages, analyzers or plugins', required: true }, { flags: '--filter <text>', description: 'Literal identity or description filter' }, { flags: '--limit <count>', description: 'Page size 1–100', defaultValue: 50 }, { flags: '--offset <count>', description: 'Page offset', defaultValue: 0 }] })
288
+ ])];
289
+ }
@@ -0,0 +1,4 @@
1
+ import type { ResearchBackend } from './research.js';
2
+ import type { Artifact } from './artifacts.js';
3
+ /** Function edits are verified in the saved private database, including each batch member. */
4
+ export declare function verifyFunctionMutation(backend: ResearchBackend, snapshot: Artifact, path: string, p: Record<string, unknown>, response: Record<string, unknown>): Promise<Record<string, unknown>>;
@@ -0,0 +1,112 @@
1
+ import { address } from './research.js';
2
+ import { object, fail } from './model.js';
3
+ import { parseDomainJson } from './json.js';
4
+ const equal = (actual, expected, label) => { if (actual !== expected)
5
+ fail('partial', `Saved ${label} does not match the requested effect.`); };
6
+ const list = (value, label) => { if (!Array.isArray(value))
7
+ fail('partial', `Saved ${label} is absent.`); return value.map(object); };
8
+ const json = (value) => typeof value === 'string' ? parseDomainJson(value) : value;
9
+ /** Function edits are verified in the saved private database, including each batch member. */
10
+ export async function verifyFunctionMutation(backend, snapshot, path, p, response) {
11
+ if (!backend.inspect)
12
+ fail('configuration', 'Function edits require saved-state inspection.');
13
+ const inspect = (kind, parameters = {}) => backend.inspect(snapshot, { kind, ...parameters });
14
+ const functionAt = async (ref, exists = true) => {
15
+ if (typeof ref !== 'string' || !ref)
16
+ fail('invalid', 'A function reference is required.');
17
+ let selector;
18
+ try {
19
+ selector = { address: address(ref) };
20
+ }
21
+ catch {
22
+ selector = { name: ref };
23
+ }
24
+ const value = await inspect('function', selector);
25
+ equal(value.exists, exists, 'function presence');
26
+ return value;
27
+ };
28
+ const checkType = async (actual, wanted) => {
29
+ const resolved = await inspect('resolve_type', { name: wanted });
30
+ equal(resolved.exists, true, 'variable type presence');
31
+ equal(actual, object(resolved.type).name, 'variable type');
32
+ };
33
+ if (path === '/create_function' || path === '/delete_function') {
34
+ const value = await functionAt(p.address ?? p.function, path === '/create_function');
35
+ if (path === '/create_function' && p.name)
36
+ equal(value.name, p.name, 'function name');
37
+ return value;
38
+ }
39
+ if (path === '/clear_instruction_flow_override' || path === '/disassemble_bytes') {
40
+ const value = await inspect('instructions', { address: p.address ?? p.start_address });
41
+ equal(value.exists, true, 'instruction presence');
42
+ if (path === '/clear_instruction_flow_override')
43
+ equal(value.flowOverride, 'NONE', 'instruction flow override');
44
+ return value;
45
+ }
46
+ if (['/add_function_tag', '/remove_function_tag'].includes(path)) {
47
+ const assignments = Array.isArray(p.assignments) && p.assignments.length ? p.assignments : [{ function: p.function, tags: p.tags }];
48
+ const results = [];
49
+ for (const assignment of assignments) {
50
+ const requested = object(assignment), value = await functionAt(requested.function), tags = list(value.tags, 'function tags');
51
+ const names = String(requested.tags ?? '').split(',').map(name => name.trim()).filter(Boolean);
52
+ if (!names.length)
53
+ fail('invalid', 'At least one tag name is required.');
54
+ for (const name of names)
55
+ equal(tags.some(tag => tag.name === name), path === '/add_function_tag', 'function tag ' + name);
56
+ results.push(value);
57
+ }
58
+ return { functions: results };
59
+ }
60
+ if (path === '/delete_function_tag' || path === '/set_function_tag_comment') {
61
+ const value = await inspect('function_tags'), tags = list(value.tags, 'program function tags'), tag = tags.find(tag => tag.name === p.name);
62
+ equal(!!tag, path === '/set_function_tag_comment', 'program tag presence');
63
+ if (tag)
64
+ equal(tag.comment, p.comment, 'program tag comment');
65
+ return value;
66
+ }
67
+ const value = await functionAt(p.function ?? p.function_address), variables = list(value.variables, 'function variables');
68
+ const variable = (name) => { const found = variables.find(item => item.name === name); if (!found)
69
+ fail('partial', 'Requested variable is absent from saved state.'); return found; };
70
+ if (path === '/set_function_no_return') {
71
+ equal(value.noReturn, p.no_return, 'no-return flag');
72
+ return value;
73
+ }
74
+ if (path === '/set_variable_type') {
75
+ await checkType(variable(p.variable_name).type, p.new_type);
76
+ return value;
77
+ }
78
+ if (path === '/set_variable_storage') {
79
+ const stored = variable(p.variable_name).storage;
80
+ const canonical = String(stored).replace(/\s+/g, '').toLowerCase(), wanted = String(p.storage).replace(/\s+/g, '').toLowerCase();
81
+ // A size omitted by the caller is inferred from the saved variable type.
82
+ if (canonical !== wanted && !(!wanted.includes(':') && canonical.startsWith(wanted + ':')))
83
+ fail('partial', 'Saved variable storage differs from the requested location.');
84
+ return value;
85
+ }
86
+ if (path === '/rename_variables' || path === '/batch_rename_function_components') {
87
+ const maps = path === '/rename_variables' ? [p.variable_renames] : [p.parameter_renames, p.local_renames];
88
+ for (const mapping of maps)
89
+ for (const [oldName, newName] of Object.entries(object(mapping ?? {}))) {
90
+ variable(newName);
91
+ if (oldName !== newName && variables.some(item => item.name === oldName))
92
+ fail('partial', 'Original variable name remains after renaming.');
93
+ }
94
+ if (p.function_name)
95
+ equal(value.name, p.function_name, 'function name');
96
+ if (p.return_type)
97
+ await checkType(value.returnType, p.return_type);
98
+ return value;
99
+ }
100
+ if (path === '/set_variables') {
101
+ for (const [oldName, raw] of Object.entries(object(json(p.variables)))) {
102
+ const spec = typeof raw === 'string' ? { name: raw } : Array.isArray(raw) ? { name: raw[0], type: raw[1] } : object(raw);
103
+ const saved = variable(spec.name || oldName);
104
+ if (spec.type)
105
+ await checkType(saved.type, spec.type);
106
+ if (spec.name && spec.name !== oldName && variables.some(item => item.name === oldName))
107
+ fail('partial', 'Original variable name remains after editing.');
108
+ }
109
+ return value;
110
+ }
111
+ fail('schema', 'Function mutation lacks its saved effect contract.');
112
+ }
@@ -0,0 +1,50 @@
1
+ import { type ChildProcess } from 'node:child_process';
2
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
3
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
4
+ import type { CommandContext } from '@eyeauras/cli-factory';
5
+ import { type Batch, type Binding, type FindingRecord, type Snapshot, type Target } from './model.js';
6
+ import { HttpBridge } from './transport.js';
7
+ import type { Publisher } from './coordinator.js';
8
+ interface ProcessRecord {
9
+ binding: Binding;
10
+ http: HttpBridge;
11
+ child?: ChildProcess;
12
+ client?: Client;
13
+ transport?: StdioClientTransport;
14
+ }
15
+ export declare class GhidraRuntime implements Publisher {
16
+ private directory;
17
+ private context;
18
+ private processes;
19
+ constructor(directory: string, context: CommandContext, processes?: Map<string, ProcessRecord>);
20
+ fork(context: CommandContext): GhidraRuntime;
21
+ private launch;
22
+ open(t: Target, batchId: string): Promise<Binding>;
23
+ private receipt;
24
+ private record;
25
+ private boundIdentity;
26
+ private latest;
27
+ private localVersion;
28
+ checkout(t: Target, b: Binding): Promise<Snapshot>;
29
+ read(t: Target, b: Binding, findings: FindingRecord[]): Promise<Snapshot>;
30
+ apply(t: Target, b: Binding, f: FindingRecord): Promise<void>;
31
+ save(t: Target, b: Binding): Promise<void>;
32
+ checkIn(t: Target, b: Binding, batchId: string): Promise<number>;
33
+ private independent;
34
+ published(t: Target, findings: FindingRecord[]): Promise<Snapshot>;
35
+ private probe;
36
+ history(t: Target, batchId: string): Promise<number[]>;
37
+ released(t: Target): Promise<boolean>;
38
+ complete(t: Target, b: Binding): Promise<void>;
39
+ release(t: Target, b: Binding): Promise<void>;
40
+ abandon(t: Target, batch: Batch): Promise<void>;
41
+ startReader(t: Target, label: string): Promise<Binding>;
42
+ private abortReader;
43
+ inspect(t: Target, b: Binding, functions: string): Promise<{
44
+ binding: Binding;
45
+ functions: Record<string, unknown>;
46
+ }>;
47
+ stop(t: Target, b: Binding): Promise<void>;
48
+ dispose(): Promise<void>;
49
+ }
50
+ export {};