@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,249 @@
1
+ import { command, Permission } from '@eyeauras/cli-factory';
2
+ import { mkdir, readdir, lstat, readFile, open, rename, copyFile, realpath, chmod } from 'node:fs/promises';
3
+ import { constants } from 'node:fs';
4
+ import { join, resolve, dirname } from 'node:path';
5
+ import { scope, address, number } from './research.js';
6
+ import { object, fail, text, id, stable } from './model.js';
7
+ import { parseDomainJson } from './json.js';
8
+ import { selectedOptions } from './arguments.js';
9
+ import { randomUUID } from 'node:crypto';
10
+ async function persist(path, value) { const temp = path + '.new-' + randomUUID(), file = await open(temp, 'wx', 0o600); try {
11
+ await file.writeFile(stable(value));
12
+ await file.sync();
13
+ }
14
+ finally {
15
+ await file.close();
16
+ } await rename(temp, path); if (process.platform !== 'win32') {
17
+ const directory = await open(dirname(path), 'r');
18
+ try {
19
+ await directory.sync();
20
+ }
21
+ finally {
22
+ await directory.close();
23
+ }
24
+ } }
25
+ /** Native SDK file effects have retained input/output bytes and never repeat an uncertain call. */
26
+ export class SdkFiles {
27
+ workspace;
28
+ research;
29
+ backend;
30
+ constructor(workspace, research, backend) {
31
+ this.workspace = workspace;
32
+ this.research = research;
33
+ this.backend = backend;
34
+ }
35
+ async loaders(input) {
36
+ const artifact = await this.workspace.artifacts.capture(input, 'input'), backend = await this.backend();
37
+ if (!backend.sdkFile)
38
+ fail('configuration', 'Native SDK file workflows are unavailable.');
39
+ return { inputSha256: artifact.sha256, ...await backend.sdkFile('loaders', { input: await this.workspace.artifacts.verify(artifact) }) };
40
+ }
41
+ async exporters(selected) {
42
+ return this.research.ordered(selected, async () => {
43
+ const backend = await this.backend();
44
+ if (!backend.sdkFile)
45
+ fail('configuration', 'Native SDK file workflows are unavailable.');
46
+ await backend.open(selected);
47
+ const snapshot = await backend.saved(selected);
48
+ return { ...selected, value: await backend.sdkFile('exporters', { snapshot: await this.workspace.artifacts.verify(snapshot) }) };
49
+ });
50
+ }
51
+ async copy(files, directory, recovery = false) {
52
+ try {
53
+ await mkdir(directory, { mode: 0o700 });
54
+ }
55
+ catch (error) {
56
+ if (!recovery || error.code !== 'EEXIST')
57
+ throw error;
58
+ }
59
+ if (await realpath(directory) !== resolve(directory) || (await lstat(directory)).isSymbolicLink())
60
+ fail('binding', 'Export destination is not the requested ordinary directory.');
61
+ for (const file of files) {
62
+ if (!/^[a-zA-Z0-9_.-]{1,160}$/.test(file.name) || file.name === '.' || file.name === '..')
63
+ fail('binding', 'Exporter produced an unsupported output filename.');
64
+ const source = await this.workspace.artifacts.verify(file.artifact), output = join(directory, file.name);
65
+ try {
66
+ await copyFile(source, output, constants.COPYFILE_EXCL);
67
+ }
68
+ catch (error) {
69
+ if (!recovery || error.code !== 'EEXIST')
70
+ throw error;
71
+ }
72
+ if ((await this.workspace.artifacts.capture(output, 'input')).id !== file.artifact.id)
73
+ fail('partial', 'Exported file bytes differ from retained native output.');
74
+ // Artifact copies retain their read-only mode. The owned user output needs a writable
75
+ // descriptor for FlushFileBuffers on Windows; verify existing recovery bytes first.
76
+ await chmod(output, 0o600);
77
+ const handle = await open(output, 'r+');
78
+ try {
79
+ await handle.sync();
80
+ }
81
+ finally {
82
+ await handle.close();
83
+ }
84
+ }
85
+ if (process.platform !== 'win32')
86
+ for (const path of [directory, dirname(directory)]) {
87
+ const handle = await open(path, 'r');
88
+ try {
89
+ await handle.sync();
90
+ }
91
+ finally {
92
+ await handle.close();
93
+ }
94
+ }
95
+ }
96
+ async completedOutput(stage, request, operation) {
97
+ const marker = object(JSON.parse(await readFile(join(stage, 'native-result.json'), 'utf8')));
98
+ if (marker.request !== request || marker.operation !== operation || object(marker.value).state !== 'exported' || !Array.isArray(marker.files) || !marker.files.length || marker.files.length > 64)
99
+ fail('binding', 'Native file completion receipt differs from the retained request.');
100
+ const files = [];
101
+ for (const item of marker.files) {
102
+ const row = object(item), name = text(row.name, 160);
103
+ if (!/^[a-zA-Z0-9_.-]+$/.test(name) || name === '.' || name === '..')
104
+ fail('binding', 'Native output name is not a simple owned file.');
105
+ const path = join(stage, 'outputs', name);
106
+ if (!(await lstat(path)).isFile() || (await lstat(path)).isSymbolicLink())
107
+ fail('binding', 'Native output is not a regular owned file.');
108
+ const artifact = await this.workspace.artifacts.capture(path, 'input');
109
+ if (artifact.sha256 !== row.sha256 || artifact.size !== row.size)
110
+ fail('partial', 'Native output changed after its completion receipt.');
111
+ files.push({ name, artifact });
112
+ }
113
+ return { files, value: marker.value };
114
+ }
115
+ async databaseQuery(selected, operation, database, p) {
116
+ const artifact = await this.workspace.artifacts.capture(database, 'input');
117
+ if (artifact.size > 268435456)
118
+ fail('limit', 'Function database exceeds 256 MiB.');
119
+ return this.research.ordered(selected, async () => {
120
+ const backend = await this.backend();
121
+ if (!backend.sdkFile)
122
+ fail('configuration', 'Native function databases are unavailable.');
123
+ await backend.open(selected);
124
+ const snapshot = await backend.saved(selected), value = await backend.sdkFile(operation, { ...p, database: await this.workspace.artifacts.verify(artifact), snapshot: await this.workspace.artifacts.verify(snapshot) });
125
+ return { ...selected, databaseSha256: artifact.sha256, sourceSnapshotSha256: snapshot.sha256, value };
126
+ });
127
+ }
128
+ async export(selected, operation, parameters, directory, callerId) {
129
+ const reserved = this.workspace.reserveRequest(selected, 'ghidra.sdk-file.' + operation, { ...parameters, outputDirectory: directory }, callerId);
130
+ return this.research.ordered(selected, async () => {
131
+ const request = this.workspace.request(reserved.request.id), stage = join(this.workspace.directory, 'sdk-exports', request.id), receiptPath = join(stage, 'receipt.json');
132
+ if (reserved.replay && request.state !== 'accepted') {
133
+ if (request.state === 'applied')
134
+ return request.result;
135
+ let saved;
136
+ try {
137
+ saved = object(JSON.parse(await readFile(receiptPath, 'utf8')));
138
+ }
139
+ catch {
140
+ let completed;
141
+ try {
142
+ completed = await this.completedOutput(stage, request.id, operation);
143
+ }
144
+ catch {
145
+ fail('unknown', 'Native export has no verified completed receipt; the original call is not repeated.');
146
+ }
147
+ saved = { request: request.id, operation, ...completed };
148
+ await persist(receiptPath, saved);
149
+ }
150
+ if (saved.request !== request.id || saved.operation !== operation || !Array.isArray(saved.files))
151
+ fail('binding', 'Native export receipt differs from the retained intent.');
152
+ const files = saved.files;
153
+ await this.copy(files, directory, true);
154
+ const current = this.workspace.context(selected);
155
+ const result = { ...selected, state: 'exported', outputDirectory: directory, files: files.map(file => ({ name: file.name, sha256: file.artifact.sha256, size: file.artifact.size })), value: saved.value };
156
+ this.workspace.completeSavedRequest(selected, request.id, current.generation, {}, result);
157
+ return result;
158
+ }
159
+ try {
160
+ await lstat(directory);
161
+ fail('exists', 'Export directory already exists; choose a new directory.');
162
+ }
163
+ catch (error) {
164
+ if (error.code !== 'ENOENT')
165
+ throw error;
166
+ }
167
+ const backend = await this.backend();
168
+ if (!backend.sdkFile)
169
+ fail('configuration', 'Native SDK file workflows are unavailable.');
170
+ await backend.open(selected);
171
+ const snapshot = await backend.saved(selected), current = this.workspace.context(selected);
172
+ await mkdir(stage, { recursive: true, mode: 0o700 });
173
+ const outputs = join(stage, 'outputs');
174
+ await mkdir(outputs, { recursive: true, mode: 0o700 });
175
+ if (await realpath(outputs) !== resolve(outputs) || (await lstat(outputs)).isSymbolicLink() || (await readdir(outputs)).length)
176
+ fail('binding', 'Accepted file preparation must retain an empty owned output directory.');
177
+ this.workspace.startRequest(request.id, { kind: 'sdk-file', operation, snapshot, generation: current.generation });
178
+ try {
179
+ const filename = operation === 'archive-export' ? 'types.gdt' : operation === 'fid-build' ? 'database.fidb' : operation === 'bsim-build' ? 'database.mv.db' : 'program.out';
180
+ const value = await backend.sdkFile(operation, { ...parameters, request_id: request.id, receipt: join(stage, 'native-result.json'), snapshot: await this.workspace.artifacts.verify(snapshot), output: join(outputs, filename) });
181
+ if (value.state !== 'exported')
182
+ fail('partial', 'Native exporter did not establish a completed output.');
183
+ const completed = await this.completedOutput(stage, request.id, operation), files = completed.files;
184
+ if (stable(completed.value) !== stable(value))
185
+ fail('partial', 'Native file reply differs from its durable receipt.');
186
+ await persist(receiptPath, { request: request.id, operation, files, value });
187
+ await this.copy(files, directory);
188
+ const result = { ...selected, state: 'exported', outputDirectory: directory, files: files.map(file => ({ name: file.name, sha256: file.artifact.sha256, size: file.artifact.size })), value };
189
+ this.workspace.completeSavedRequest(selected, request.id, current.generation, { workingSnapshot: snapshot }, result);
190
+ return result;
191
+ }
192
+ catch (error) {
193
+ this.workspace.recordRequest(request.id, 'unknown', { ...selected, state: 'unknown' });
194
+ throw error;
195
+ }
196
+ });
197
+ }
198
+ }
199
+ function json(value) { return parseDomainJson(text(value, 16000)); }
200
+ export function sdkFileCommands(service) {
201
+ const outputs = [...selectedOptions, { flags: '--output-directory <directory>', description: 'New output directory including all exporter companion files', required: true },
202
+ { flags: '--request-id <id>', description: 'Retrieve retained export bytes without repeating native export' }];
203
+ const databases = [];
204
+ for (const kind of ['fid', 'bsim']) {
205
+ databases.push(command('build-' + kind, 'Build a genuine native ' + kind + ' database from the saved private snapshot', ({ options: o }, c) => service(c).export(scope(o), kind + '-build', { library: text(o.library, 512), ...(kind === 'fid' ? { version: text(o.libraryVersion, 128) } : {}) }, resolve(c.cwd, text(o.outputDirectory, 4096)), o.requestId === undefined ? undefined : id(o.requestId)), { permission: Permission.Update, options: [...outputs, { flags: '--library <name>', description: 'Native library/database name', required: true }, ...(kind === 'fid' ? [{ flags: '--library-version <version>', description: 'Native library version', required: true }] : [])] }));
206
+ databases.push(command(kind + '-query', 'Query one explicitly consumed native ' + kind + ' database with provenance and no-match semantics', ({ options: o }, c) => {
207
+ const p = { limit: number(o.limit, 1000, 1) };
208
+ if (o.function !== undefined)
209
+ p.function = address(o.function);
210
+ if (kind === 'fid') {
211
+ const threshold = Number(o.threshold);
212
+ if (!Number.isFinite(threshold) || threshold < 0 || threshold > 100000)
213
+ fail('invalid', 'FID threshold must be 0–100000 native code units.');
214
+ p.threshold = threshold;
215
+ }
216
+ else {
217
+ const similarity = Number(o.similarity), significance = Number(o.significance);
218
+ if (!Number.isFinite(similarity) || similarity < 0 || similarity > 1 || !Number.isFinite(significance) || significance < 0 || significance > 1000000)
219
+ fail('invalid', 'Invalid native BSim similarity/significance threshold.');
220
+ p.similarity = similarity;
221
+ p.significance = significance;
222
+ }
223
+ return service(c).databaseQuery(scope(o), kind + '-query', resolve(c.cwd, text(o.database, 4096)), p);
224
+ }, { permission: Permission.ReadOnly, options: [...selectedOptions, { flags: '--database <file>', description: 'Native FID or BSim/H2 file; immutable bytes are consumed first', required: true },
225
+ { flags: '--function <address>', description: 'Exact function entry', ...(kind === 'bsim' ? { required: true } : {}) }, { flags: '--limit <count>', description: 'Maximum returned matches', defaultValue: 20 },
226
+ ...(kind === 'fid' ? [{ flags: '--threshold <score>', description: 'Native matching code-unit threshold', defaultValue: 14.6 }] : [{ flags: '--similarity <number>', description: 'Native feature similarity 0–1', defaultValue: 0.7 }, { flags: '--significance <number>', description: 'Native feature significance', defaultValue: 0 }])] }));
227
+ }
228
+ return [command('libraries', 'Native FID and BSim/H2 library identity/similarity databases', databases), command('formats', 'Native loaders and exporters', [
229
+ command('loaders', 'Read installed loader names and supported specs/options for consumed bytes', ({ options: o }, c) => service(c).loaders(resolve(c.cwd, text(o.input, 4096))), { permission: Permission.ReadOnly, options: [{ flags: '--input <file>', description: 'Input file to inspect', required: true }] }),
230
+ command('exporters', 'Read installed exporters and their actual options for this program', ({ options: o }, c) => service(c).exporters(scope(o)), { permission: Permission.ReadOnly, options: selectedOptions }),
231
+ command('export', 'Export through an explicitly selected native exporter with typed options/ranges', ({ options: o }, c) => {
232
+ const p = { exporter: text(o.exporter, 512), options: object(json(o.options)) };
233
+ if (o.ranges !== undefined) {
234
+ const ranges = json(o.ranges);
235
+ if (!Array.isArray(ranges) || !ranges.length || ranges.length > 64)
236
+ fail('invalid', 'Export ranges require 1–64 start/end objects.');
237
+ p.ranges = ranges;
238
+ }
239
+ return service(c).export(scope(o), 'export', p, resolve(c.cwd, text(o.outputDirectory, 4096)), o.requestId === undefined ? undefined : id(o.requestId));
240
+ }, { permission: Permission.Update, options: [...outputs, { flags: '--exporter <id>', description: 'Explicit native exporter name/ID', required: true }, { flags: '--options <json>', description: 'Named primitive native options', defaultValue: '{}' }, { flags: '--ranges <json>', description: 'Address ranges with start/end; exporter must support restricted output' }] }),
241
+ ]), command('types', 'Native type archives', [
242
+ command('archive-export', 'Export 1–256 explicit type paths to a native GDT with checked layout', ({ options: o }, c) => {
243
+ const types = json(o.types);
244
+ if (!Array.isArray(types) || !types.length || types.length > 256 || types.some(v => typeof v !== 'string' || !v.startsWith('/')))
245
+ fail('invalid', 'Types must be 1–256 absolute type paths.');
246
+ return service(c).export(scope(o), 'archive-export', { types }, resolve(c.cwd, text(o.outputDirectory, 4096)), o.requestId === undefined ? undefined : id(o.requestId));
247
+ }, { permission: Permission.Update, options: [...outputs, { flags: '--types <json>', description: 'Selected native type paths', required: true }] }),
248
+ ])];
249
+ }
@@ -0,0 +1,4 @@
1
+ import { type CommandContext, type CommandDefinition } from '@eyeauras/cli-factory';
2
+ import { Research } from './research.js';
3
+ export declare function sdkInputs(operation: string, p: Record<string, unknown>): void;
4
+ export declare function sdkCommands(research: (c: CommandContext) => Research): CommandDefinition[];
@@ -0,0 +1,125 @@
1
+ import { command, Permission } from '@eyeauras/cli-factory';
2
+ import { sdkOperations } from './sdk-contracts.js';
3
+ import { option, key, parsed, selectedOptions } from './arguments.js';
4
+ import { scope, address, number } from './research.js';
5
+ import { id, fail, object, text } from './model.js';
6
+ import { verifyMutation } from './mutation-effects.js';
7
+ import { resolve } from 'node:path';
8
+ export function sdkInputs(operation, p) {
9
+ if (['type-structure', 'type-layout', 'type-settings'].includes(operation) && !/^\/(?:[^/\0]+\/)*[^/\0]+$/.test(String(p.path)))
10
+ fail('invalid', 'Types require an absolute native type path.');
11
+ for (const name of ['packing', 'alignment'])
12
+ if (p[name] !== undefined) {
13
+ const n = number(Number(p[name]) + 1, 65) - 1;
14
+ if (n > 0 && (n & (n - 1)) !== 0)
15
+ fail('invalid', 'Packing/alignment must be -1, zero or a power of two through 64.');
16
+ }
17
+ if (p.types !== undefined && (!Array.isArray(p.types) || !p.types.length || p.types.length > 256 || p.types.some(v => typeof v !== 'string' || !v.startsWith('/'))))
18
+ fail('invalid', 'Select 1–256 explicit type paths.');
19
+ if (operation === 'type-structure' && (!Array.isArray(p.fields) || !p.fields.length || p.fields.length > 256))
20
+ fail('invalid', 'A structure needs 1–256 field objects.');
21
+ if (operation === 'type-structure') {
22
+ const names = new Set();
23
+ for (const item of p.fields) {
24
+ const field = object(item), name = text(field.name, 512);
25
+ text(field.type, 512);
26
+ if (names.has(name) || Object.keys(field).some(key => !['name', 'type', 'comment', 'offset', 'bit_size', 'bit_offset', 'byte_width'].includes(key)))
27
+ fail('invalid', 'Fields require unique names and known layout properties.');
28
+ names.add(name);
29
+ if (field.comment !== undefined && (typeof field.comment !== 'string' || field.comment.length > 4096 || field.comment.includes('\0')))
30
+ fail('invalid', 'Field comment exceeds its text bound.');
31
+ for (const [key, max, min] of [['offset', 1048576, 0], ['bit_size', 512, 1], ['bit_offset', 511, 0], ['byte_width', 64, 1]])
32
+ if (field[key] !== undefined) {
33
+ if (typeof field[key] !== 'number')
34
+ fail('invalid', 'Field numeric values must be exact JSON integers.');
35
+ number(field[key], max, min);
36
+ }
37
+ if (field.offset !== undefined && Number(p.packing ?? 0) >= 0)
38
+ fail('invalid', 'Explicit field offsets require disabled packing (-1).');
39
+ if ((field.bit_offset !== undefined || field.byte_width !== undefined) && (field.bit_size === undefined || field.offset === undefined))
40
+ fail('invalid', 'Explicit bit placement requires a bitfield and byte offset.');
41
+ }
42
+ }
43
+ if (operation === 'type-settings')
44
+ for (const value of Object.values(object(p.settings))) {
45
+ if (value !== null && (!['string', 'number', 'boolean'].includes(typeof value) || typeof value === 'number' && !Number.isSafeInteger(value) || typeof value === 'string' && (value.length > 4096 || value.includes('\0'))))
46
+ fail('invalid', 'Type settings require bounded primitive values or null.');
47
+ }
48
+ if (operation === 'decompiler-ir')
49
+ for (const [key, value] of Object.entries(object(p.options ?? {})))
50
+ if (!['eliminate_unreachable', 'respect_read_only', 'infer_constant_pointers', 'split_structures', 'split_arrays'].includes(key) || typeof value !== 'boolean')
51
+ fail('invalid', 'Decompiler options require known Boolean properties.');
52
+ if (operation === 'changes') {
53
+ const categories = p.categories;
54
+ if (!Array.isArray(categories) || !categories.length || categories.length > 8 || new Set(categories).size !== categories.length || categories.some(value => !['bytes', 'code_units', 'comments', 'symbols', 'functions', 'references', 'context', 'properties'].includes(String(value))))
55
+ fail('invalid', 'Select 1–8 distinct supported diff categories.');
56
+ }
57
+ for (const name of ['expect_hex', 'bytes_hex'])
58
+ if (p[name] !== undefined &&
59
+ (typeof p[name] !== 'string' || !/^(?:[a-fA-F0-9]{2}){1,4096}$/.test(p[name])))
60
+ fail('invalid', name + ' needs 1–4096 complete hex bytes.');
61
+ if (operation === 'patch-bytes' && String(p.expect_hex).length !== String(p.bytes_hex).length)
62
+ fail('invalid', 'Expected and replacement lengths must match.');
63
+ for (const name of ['expect_sha256', 'second_sha256'])
64
+ if (p[name] !== undefined && !/^[a-f0-9]{64}$/.test(String(p[name])))
65
+ fail('invalid', 'Memory preconditions require SHA256.');
66
+ for (const name of ['expect_size', 'second_size'])
67
+ if (p[name] !== undefined)
68
+ p[name] = number(p[name], 16777216, 1);
69
+ for (const [name, max] of [['max_ranges', 1000], ['max_ops', 10000], ['timeout_seconds', 120]])
70
+ if (p[name] !== undefined)
71
+ p[name] = number(p[name], max, 1);
72
+ if (p.instructions !== undefined && (!Array.isArray(p.instructions) || !p.instructions.length || p.instructions.length > 64 || p.instructions.some(v => typeof v !== 'string' || v.length > 512 || /[\r\n\0]/.test(v))))
73
+ fail('invalid', 'Assembly requires 1–64 bounded instruction strings.');
74
+ if (operation === 'library-update' && p.new_name === undefined && p.path === undefined)
75
+ fail('invalid', 'Supply a library name or path update.');
76
+ if (operation.startsWith('context-') && p.end_address !== undefined) {
77
+ const start = address(p.address), end = address(p.end_address), parts = (v) => v.split(':');
78
+ const a = parts(start), b = parts(end);
79
+ if (a.length !== b.length || a.length === 2 && a[0] !== b[0])
80
+ fail('invalid', 'Processor context needs one address space.');
81
+ const length = BigInt('0x' + b.at(-1)) - BigInt('0x' + a.at(-1)) + 1n;
82
+ if (length < 1n || length > 65536n)
83
+ fail('invalid', 'Processor context range is 1–65536 bytes.');
84
+ }
85
+ if (operation === 'context-set' && !/^(?:0x)?[a-f0-9]{1,128}$/i.test(String(p.value)))
86
+ fail('invalid', 'Context value must be an unsigned hex integer.');
87
+ if (operation === 'context-set')
88
+ p.value = String(p.value).toLowerCase();
89
+ }
90
+ export function sdkCommands(research) {
91
+ const groups = new Map();
92
+ for (const entry of sdkOperations) {
93
+ const children = groups.get(entry.group) ?? [];
94
+ groups.set(entry.group, children);
95
+ children.push(command(entry.name, entry.description, async ({ options: o }, c) => {
96
+ const selected = scope(o), p = {};
97
+ for (const parameter of entry.params) {
98
+ const value = o[key(parameter.name)];
99
+ if (value !== undefined)
100
+ p[parameter.name] = parsed(parameter, value);
101
+ else if (parameter.required)
102
+ fail('invalid', 'Missing ' + parameter.name);
103
+ }
104
+ sdkInputs(entry.operation, p);
105
+ const api = research(c), operation = '/sdk/' + entry.operation;
106
+ if (entry.operation === 'type-archive-import') {
107
+ const archive = await api.workspace.artifacts.capture(resolve(c.cwd, String(p.input)), 'input');
108
+ if (archive.size > 67108864)
109
+ fail('limit', 'Type archive exceeds 64 MiB.');
110
+ delete p.input;
111
+ p.archive = archive;
112
+ }
113
+ if (entry.write)
114
+ return api.mutate(selected, operation, p, async () => ({}), o.requestId === undefined ? undefined : id(o.requestId), (backend, snapshot, response) => verifyMutation(backend, snapshot, operation, p, response));
115
+ const other = entry.pair ? { session: o.otherSession === undefined ? selected.session : id(o.otherSession), target: id(o.otherTarget) } : undefined;
116
+ const value = object(other ? await api.crossRead(selected, other, operation, p) : await api.read(selected, operation, p));
117
+ if (Buffer.byteLength(JSON.stringify(value)) > 1048576)
118
+ fail('limit', 'SDK result exceeds 1 MiB; narrow the operation.');
119
+ return { ...selected, ...(other ? { other } : {}), value };
120
+ }, { permission: entry.write ? 'Destructive' : Permission.ReadOnly, options: [...selectedOptions, ...entry.params.map(option),
121
+ ...(entry.pair ? [{ flags: '--other-target <id>', description: 'Second immutable target', required: true }, { flags: '--other-session <id>', description: 'Second session (default selected session)' }] : []),
122
+ ...(entry.write ? [{ flags: '--request-id <id>', description: 'Retrieve this changing intent without repeating it' }] : [])] }));
123
+ }
124
+ return [...groups].map(([name, children]) => command(name, name + ' native SDK workflows', children));
125
+ }
@@ -0,0 +1,44 @@
1
+ import type { CommandContext } from '@eyeauras/cli-factory';
2
+ import { type ChildProcess } from 'node:child_process';
3
+ import { type NativeInputs } from './native-inputs.js';
4
+ export interface ServerBinding {
5
+ mode: 'local' | 'external';
6
+ origin: string;
7
+ repository: string;
8
+ readerUser: string;
9
+ publisherUser: string;
10
+ readerSecret: string;
11
+ publisherSecret: string;
12
+ }
13
+ /** Own local startup; external profiles connect without taking ownership of their server. */
14
+ export declare class GhidraServer {
15
+ readonly directory: string;
16
+ private readonly inputs;
17
+ private readonly c;
18
+ private readonly state;
19
+ constructor(directory: string, inputs: NativeInputs, c: CommandContext, state?: {
20
+ initializing?: Promise<ServerBinding>;
21
+ child?: ChildProcess;
22
+ });
23
+ fork(c: CommandContext): GhidraServer;
24
+ private root;
25
+ private run;
26
+ credentials(binding: ServerBinding, role: 'reader' | 'publisher'): Promise<Record<string, string>>;
27
+ fields(snapshot: string, selectors: string): Promise<Record<string, unknown>>;
28
+ inspect(snapshot: string, request: string): Promise<Record<string, unknown>>;
29
+ sdkFile(operation: string, p: Record<string, unknown>): Promise<Record<string, unknown>>;
30
+ traceInspect(snapshot: string): Promise<Record<string, unknown>>;
31
+ project(operation: string, parameters: Record<string, unknown>): Promise<Record<string, unknown>>;
32
+ projectOutcome(requestId: string, expected: Record<string, unknown>): Promise<Record<string, unknown> | undefined>;
33
+ administration(operation: string, parameters: Record<string, unknown>): Promise<Record<string, unknown>>;
34
+ programStatus(binding: ServerBinding, path: string): Promise<Record<string, unknown>>;
35
+ initialImport(root: string): Promise<Record<string, unknown>>;
36
+ initialPublication(binding: ServerBinding, root: string, operation?: 'stage' | 'verify-stage' | 'promote' | 'verify'): Promise<Record<string, unknown>>;
37
+ publish(binding: ServerBinding, path: string, snapshot: string, expected: number, request: string, journal: string, sourceSha: string): Promise<Record<string, unknown>>;
38
+ recoverPublication(binding: ServerBinding, path: string, snapshot: string, expected: number, request: string, journal: string, sourceSha: string): Promise<Record<string, unknown>>;
39
+ private authority;
40
+ ensure(): Promise<ServerBinding>;
41
+ private initialize;
42
+ private identity;
43
+ stop(): Promise<void>;
44
+ }