@eyeauras/insight-cli 0.0.0-stage → 0.2.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 (174) hide show
  1. package/.cli-package-build.json +1 -0
  2. package/LICENSE +21 -0
  3. package/README.md +60 -2
  4. package/SKILL.md +58 -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 +134 -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 +419 -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 +1078 -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 +154 -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 +182 -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/runtime-archives.d.ts +5 -0
  84. package/dist/src/runtime-archives.js +127 -0
  85. package/dist/src/runtime-catalog.d.ts +18 -0
  86. package/dist/src/runtime-catalog.js +27 -0
  87. package/dist/src/runtime-manager.d.ts +101 -0
  88. package/dist/src/runtime-manager.js +444 -0
  89. package/dist/src/runtime-selection.d.ts +13 -0
  90. package/dist/src/runtime-selection.js +25 -0
  91. package/dist/src/saved-materials.d.ts +12 -0
  92. package/dist/src/saved-materials.js +196 -0
  93. package/dist/src/scripts.d.ts +19 -0
  94. package/dist/src/scripts.js +79 -0
  95. package/dist/src/sdk-contracts.d.ts +13 -0
  96. package/dist/src/sdk-contracts.js +37 -0
  97. package/dist/src/sdk-files.d.ts +29 -0
  98. package/dist/src/sdk-files.js +249 -0
  99. package/dist/src/sdk.d.ts +4 -0
  100. package/dist/src/sdk.js +125 -0
  101. package/dist/src/server.d.ts +46 -0
  102. package/dist/src/server.js +460 -0
  103. package/dist/src/store.d.ts +33 -0
  104. package/dist/src/store.js +178 -0
  105. package/dist/src/sync-policy.d.ts +22 -0
  106. package/dist/src/sync-policy.js +123 -0
  107. package/dist/src/transport.d.ts +23 -0
  108. package/dist/src/transport.js +135 -0
  109. package/dist/src/ui-results.d.ts +2 -0
  110. package/dist/src/ui-results.js +80 -0
  111. package/dist/src/ui.d.ts +17 -0
  112. package/dist/src/ui.js +88 -0
  113. package/dist/src/utility.d.ts +14 -0
  114. package/dist/src/utility.js +43 -0
  115. package/dist/src/version-tracking.d.ts +57 -0
  116. package/dist/src/version-tracking.js +200 -0
  117. package/dist/src/workspace.d.ts +199 -0
  118. package/dist/src/workspace.js +564 -0
  119. package/package.json +47 -4
  120. package/references/InsightRepositoryProbe.java +55 -0
  121. package/references/administration.md +34 -0
  122. package/references/archive.md +32 -0
  123. package/references/artifacts.md +47 -0
  124. package/references/debugger.md +40 -0
  125. package/references/extensions-and-sync.md +124 -0
  126. package/references/managed-runtime.md +116 -0
  127. package/references/mutations.json +1424 -0
  128. package/references/native/InsightApplyDocumentation.java +48 -0
  129. package/references/native/InsightArchivePayloads.java +36 -0
  130. package/references/native/InsightBlockTransfer.java +57 -0
  131. package/references/native/InsightEmulation.java +93 -0
  132. package/references/native/InsightExtensions.java +36 -0
  133. package/references/native/InsightFunctionDatabases.java +98 -0
  134. package/references/native/InsightGuiMain.java +32 -0
  135. package/references/native/InsightHeadlessMain.java +10 -0
  136. package/references/native/InsightInitialImport.java +42 -0
  137. package/references/native/InsightInitialJournal.java +48 -0
  138. package/references/native/InsightInitialPublication.java +142 -0
  139. package/references/native/InsightKeytool.java +16 -0
  140. package/references/native/InsightNativeReceipt.java +31 -0
  141. package/references/native/InsightPlugins.java +34 -0
  142. package/references/native/InsightProjectFiles.java +92 -0
  143. package/references/native/InsightPublicationRecovery.java +63 -0
  144. package/references/native/InsightPythonWheels.java +31 -0
  145. package/references/native/InsightResearchQueries.java +188 -0
  146. package/references/native/InsightRuntimeDoctor.java +33 -0
  147. package/references/native/InsightSdkFiles.java +142 -0
  148. package/references/native/InsightSdkScript.java +31 -0
  149. package/references/native/InsightSdkTools.java +338 -0
  150. package/references/native/InsightSdkTypes.java +132 -0
  151. package/references/native/InsightServerAdministration.java +82 -0
  152. package/references/native/InsightServerInit.java +62 -0
  153. package/references/native/InsightServerMain.java +43 -0
  154. package/references/native/InsightServerProbe.java +29 -0
  155. package/references/native/InsightSnapshotFields.java +68 -0
  156. package/references/native/InsightSnapshotInspect.java +189 -0
  157. package/references/native/InsightSnapshotPublisher.java +123 -0
  158. package/references/native/InsightStartDebugger.java +28 -0
  159. package/references/native/InsightStopDebugger.java +20 -0
  160. package/references/native/InsightTraceInspect.java +24 -0
  161. package/references/native/InsightTraceState.java +130 -0
  162. package/references/native/InsightTraceTools.java +114 -0
  163. package/references/native/InsightVersionTracking.java +91 -0
  164. package/references/native-workflows.md +112 -0
  165. package/references/queries.json +1761 -0
  166. package/references/query-semantics.md +96 -0
  167. package/references/recovery.md +53 -0
  168. package/references/research.md +101 -0
  169. package/references/setup.md +49 -0
  170. package/runtime/GhidraMCP-7.0.0.jar +0 -0
  171. package/runtime/GhidraMCP-LICENSE +202 -0
  172. package/runtime/GhidraMCP-NOTICE +10 -0
  173. package/runtime/provenance.json +11 -0
  174. package/skills/insight-re/SKILL.md +108 -0
@@ -0,0 +1,564 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ import { chmodSync, mkdirSync, realpathSync } from 'node:fs';
3
+ import { basename, join } from 'node:path';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { Artifacts } from './artifacts.js';
6
+ import { digest, fail, id, stable, text, version } from './model.js';
7
+ const now = () => new Date().toISOString();
8
+ function loadSettings(value) {
9
+ if (typeof value.autoAnalyze !== 'boolean' || Object.keys(value).some(key => !['autoAnalyze', 'language', 'compilerSpec', 'imageBase', 'loader', 'loaderOptions'].includes(key)))
10
+ fail('invalid', 'Ghidra load settings are malformed.');
11
+ const result = { autoAnalyze: value.autoAnalyze };
12
+ if (value.language !== undefined)
13
+ result.language = text(value.language, 128);
14
+ if (value.compilerSpec !== undefined)
15
+ result.compilerSpec = text(value.compilerSpec, 128);
16
+ if (value.loader !== undefined)
17
+ result.loader = text(value.loader, 512);
18
+ if (value.loaderOptions !== undefined) {
19
+ if (!value.loader || !value.loaderOptions || Array.isArray(value.loaderOptions) || typeof value.loaderOptions !== 'object' || Object.keys(value.loaderOptions).length > 64)
20
+ fail('invalid', 'Loader options require an explicit loader and at most 64 named primitive values.');
21
+ const options = {};
22
+ for (const [key, item] of Object.entries(value.loaderOptions)) {
23
+ text(key, 512);
24
+ if (!['string', 'number', 'boolean'].includes(typeof item) || typeof item === 'number' && !Number.isSafeInteger(item))
25
+ fail('invalid', 'Loader options require exact primitive values.');
26
+ if (typeof item === 'string' && (item.length > 4096 || /[\u0000-\u0008\u000b\u000c\u000e-\u001f]/.test(item)))
27
+ fail('invalid', 'Loader option text exceeds its bound.');
28
+ options[key] = item;
29
+ }
30
+ result.loaderOptions = options;
31
+ }
32
+ if (value.imageBase !== undefined) {
33
+ const address = text(value.imageBase, 128).toLowerCase().replace(/^0x/, '');
34
+ if (!/^[a-f0-9]+$/.test(address))
35
+ fail('invalid', 'Image base must be hexadecimal.');
36
+ result.imageBase = address.replace(/^0+(?=.)/, '');
37
+ }
38
+ return result;
39
+ }
40
+ function bounded(value) {
41
+ const result = stable(value);
42
+ if (typeof result !== 'string' || Buffer.byteLength(result) > 1024 * 1024)
43
+ fail('limit', 'Durable request or context exceeds 1 MiB.');
44
+ return result;
45
+ }
46
+ function page(limit, offset) {
47
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !Number.isSafeInteger(offset) || offset < 0)
48
+ fail('invalid', 'Pagination requires a limit of 1–100 and a nonnegative offset.');
49
+ }
50
+ /** Profile root state, deliberately separate from the earlier coordinator.sqlite journals. */
51
+ export class Workspace {
52
+ profile;
53
+ directory;
54
+ artifacts;
55
+ db;
56
+ namespace;
57
+ constructor(directory, profile) {
58
+ this.profile = profile;
59
+ text(profile);
60
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
61
+ this.directory = realpathSync.native(directory);
62
+ this.artifacts = new Artifacts(this.directory);
63
+ const path = join(this.directory, 'workspace.sqlite');
64
+ this.db = new DatabaseSync(path);
65
+ chmodSync(path, 0o600);
66
+ try {
67
+ if (this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='metadata'").get()) {
68
+ const binding = this.db.prepare('SELECT value FROM metadata WHERE key=?').get('profile');
69
+ if (binding && binding.value !== profile)
70
+ fail('binding', 'Workspace belongs to a different profile; restore its original profile.');
71
+ }
72
+ if (Number(this.db.prepare('PRAGMA user_version').get()?.user_version) > 5)
73
+ fail('binding', 'Workspace schema is newer than this build; preserve it and use the matching build.');
74
+ this.db.exec(`PRAGMA busy_timeout=5000; PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL; PRAGMA foreign_keys=ON;
75
+ CREATE TABLE IF NOT EXISTS metadata(key TEXT PRIMARY KEY, value TEXT NOT NULL);
76
+ CREATE TABLE IF NOT EXISTS artifacts(id TEXT PRIMARY KEY, data TEXT NOT NULL);
77
+ CREATE TABLE IF NOT EXISTS targets(id TEXT PRIMARY KEY, sha TEXT NOT NULL UNIQUE, data TEXT NOT NULL);
78
+ CREATE TABLE IF NOT EXISTS sessions(id TEXT PRIMARY KEY, data TEXT NOT NULL);
79
+ CREATE TABLE IF NOT EXISTS ghidra_models(target TEXT PRIMARY KEY REFERENCES targets(id), data TEXT NOT NULL);
80
+ CREATE TABLE IF NOT EXISTS contexts(id TEXT PRIMARY KEY, session TEXT NOT NULL REFERENCES sessions(id),
81
+ target TEXT NOT NULL REFERENCES ghidra_models(target), generation INTEGER NOT NULL, data TEXT NOT NULL,
82
+ UNIQUE(session,target));
83
+ CREATE TABLE IF NOT EXISTS requests(id TEXT PRIMARY KEY, caller_id TEXT UNIQUE, hash TEXT NOT NULL,
84
+ context TEXT NOT NULL REFERENCES contexts(id), state TEXT NOT NULL, data TEXT NOT NULL);
85
+ CREATE TABLE IF NOT EXISTS publications(id TEXT PRIMARY KEY, target TEXT NOT NULL REFERENCES ghidra_models(target), state TEXT NOT NULL, data TEXT NOT NULL);
86
+ CREATE TABLE IF NOT EXISTS administration_requests(id TEXT PRIMARY KEY,caller_id TEXT UNIQUE,hash TEXT NOT NULL,state TEXT NOT NULL,data TEXT NOT NULL);
87
+ CREATE UNIQUE INDEX IF NOT EXISTS active_publication ON publications(target) WHERE state IN ('preparing','commit_intent','unknown');
88
+ PRAGMA user_version=5;`);
89
+ this.transaction(() => {
90
+ const binding = this.db.prepare('SELECT value FROM metadata WHERE key=?').get('profile');
91
+ if (binding && binding.value !== profile)
92
+ fail('binding', 'Workspace belongs to a different profile; restore its original profile.');
93
+ if (!binding) {
94
+ this.db.prepare('INSERT INTO metadata VALUES(?,?)').run('profile', profile);
95
+ this.db.prepare('INSERT INTO metadata VALUES(?,?)').run('namespace', randomUUID());
96
+ }
97
+ this.namespace = String(this.db.prepare('SELECT value FROM metadata WHERE key=?').get('namespace').value);
98
+ });
99
+ }
100
+ catch (error) {
101
+ this.db.close();
102
+ throw error;
103
+ }
104
+ }
105
+ close() { this.db.close(); }
106
+ transaction(action) {
107
+ this.db.exec('BEGIN IMMEDIATE');
108
+ try {
109
+ const result = action();
110
+ this.db.exec('COMMIT');
111
+ return result;
112
+ }
113
+ catch (error) {
114
+ this.db.exec('ROLLBACK');
115
+ throw error;
116
+ }
117
+ }
118
+ rememberArtifact(artifact) {
119
+ this.artifacts.path(artifact);
120
+ const row = this.db.prepare('SELECT data FROM artifacts WHERE id=?').get(artifact.id);
121
+ if (row && row.data !== stable(artifact))
122
+ fail('artifact', 'Artifact identity has inconsistent metadata.');
123
+ this.db.prepare('INSERT OR IGNORE INTO artifacts VALUES(?,?)').run(artifact.id, stable(artifact));
124
+ }
125
+ knownSnapshot(artifact) {
126
+ this.artifacts.path(artifact);
127
+ if (artifact.kind !== 'snapshot' || this.db.prepare('SELECT data FROM artifacts WHERE id=?').get(artifact.id)?.data !== stable(artifact))
128
+ fail('artifact', 'Context requires a captured immutable snapshot.');
129
+ }
130
+ async captureSnapshot(path) {
131
+ const artifact = await this.artifacts.capture(path, 'snapshot');
132
+ this.rememberArtifact(artifact);
133
+ return artifact;
134
+ }
135
+ async importTarget(path) {
136
+ const name = text(basename(path), 512);
137
+ const input = await this.artifacts.capture(path, 'input');
138
+ return this.transaction(() => {
139
+ this.rememberArtifact(input);
140
+ const row = this.db.prepare('SELECT id FROM targets WHERE sha=?').get(input.sha256);
141
+ if (row)
142
+ return this.target(String(row.id));
143
+ const target = {
144
+ id: 't_' + digest([this.namespace, input.sha256]).slice(0, 32), name,
145
+ binarySha256: input.sha256, size: input.size, input, createdAt: now(),
146
+ };
147
+ this.db.prepare('INSERT INTO targets VALUES(?,?,?)').run(target.id, input.sha256, stable(target));
148
+ return target;
149
+ });
150
+ }
151
+ target(target) {
152
+ const row = this.db.prepare('SELECT data FROM targets WHERE id=?').get(id(target));
153
+ if (!row)
154
+ fail('notFound', 'Target was not found in the selected profile.');
155
+ return JSON.parse(String(row.data));
156
+ }
157
+ targets(limit = 100, offset = 0) {
158
+ page(limit, offset);
159
+ return this.db.prepare('SELECT data FROM targets ORDER BY rowid LIMIT ? OFFSET ?').all(limit, offset).map(row => JSON.parse(String(row.data)));
160
+ }
161
+ session(session) {
162
+ const row = this.db.prepare('SELECT data FROM sessions WHERE id=?').get(id(session));
163
+ if (!row)
164
+ fail('notFound', 'Session was not found in the selected profile.');
165
+ return JSON.parse(String(row.data));
166
+ }
167
+ sessions(limit = 100, offset = 0) {
168
+ page(limit, offset);
169
+ return this.db.prepare('SELECT data FROM sessions ORDER BY rowid LIMIT ? OFFSET ?').all(limit, offset).map(row => JSON.parse(String(row.data)));
170
+ }
171
+ model(target) {
172
+ this.target(target);
173
+ const row = this.db.prepare('SELECT data FROM ghidra_models WHERE target=?').get(target);
174
+ if (!row)
175
+ fail('notFound', 'Target has no Ghidra model.');
176
+ return JSON.parse(String(row.data));
177
+ }
178
+ context(scope) {
179
+ this.session(scope.session);
180
+ this.target(scope.target);
181
+ const row = this.db.prepare('SELECT data FROM contexts WHERE session=? AND target=?').get(scope.session, scope.target);
182
+ if (!row)
183
+ fail('notFound', 'No Ghidra context exists for this session and target; open it first.');
184
+ return JSON.parse(String(row.data));
185
+ }
186
+ contexts(session, limit = 100, offset = 0) {
187
+ this.session(session);
188
+ page(limit, offset);
189
+ return this.db.prepare('SELECT data FROM contexts WHERE session=? ORDER BY rowid LIMIT ? OFFSET ?').all(session, limit, offset).map(row => JSON.parse(String(row.data)));
190
+ }
191
+ existingRequest(callerId, intentHash) {
192
+ if (callerId === undefined)
193
+ return;
194
+ if (this.db.prepare('SELECT id FROM administration_requests WHERE caller_id=?').get(id(callerId)))
195
+ fail('idempotency', 'Request ID already identifies a profile administration operation.');
196
+ const row = this.db.prepare('SELECT id,hash FROM requests WHERE caller_id=?').get(id(callerId));
197
+ if (!row)
198
+ return;
199
+ if (row.hash !== intentHash)
200
+ fail('idempotency', 'Request ID was already used with different effective input.');
201
+ return this.request(String(row.id));
202
+ }
203
+ insertRequest(context, operation, intent, callerId, hash) {
204
+ const request = { id: 'q_' + randomUUID(), operation: text(operation, 128), intent,
205
+ context, state: 'accepted', ...(callerId === undefined ? {} : { callerId: id(callerId) }), createdAt: now(), updatedAt: now() };
206
+ this.db.prepare('INSERT INTO requests VALUES(?,?,?,?,?,?)').run(request.id, callerId ?? null, hash, context, request.state, bounded(request));
207
+ return request;
208
+ }
209
+ reserveOpen(target, session, settings, callerId, mode) {
210
+ this.target(target);
211
+ const normalized = loadSettings(settings);
212
+ if (session !== 'new')
213
+ this.session(session);
214
+ if (mode !== undefined && !['headless', 'gui'].includes(mode))
215
+ fail('invalid', 'Requested tool mode is invalid.');
216
+ const intent = { operation: 'ghidra.open', target, session, settings: normalized, ...(mode ? { mode } : {}) };
217
+ const hash = digest(intent);
218
+ return this.transaction(() => {
219
+ const previous = this.existingRequest(callerId, hash);
220
+ if (previous) {
221
+ const row = this.db.prepare('SELECT data FROM contexts WHERE id=?').get(previous.context);
222
+ return { context: JSON.parse(String(row.data)), request: previous, replay: true };
223
+ }
224
+ const row = this.db.prepare('SELECT data FROM ghidra_models WHERE target=?').get(target);
225
+ if (row) {
226
+ if (stable(JSON.parse(String(row.data)).settings) !== stable(normalized))
227
+ fail('binding', 'Target already uses different Ghidra load settings.');
228
+ }
229
+ else {
230
+ const model = { target, settings: normalized, state: 'opening' };
231
+ this.db.prepare('INSERT INTO ghidra_models VALUES(?,?)').run(target, stable(model));
232
+ }
233
+ if (session === 'new') {
234
+ const selected = { id: 's_' + randomUUID(), createdAt: now() };
235
+ this.db.prepare('INSERT INTO sessions VALUES(?,?)').run(selected.id, stable(selected));
236
+ session = selected.id;
237
+ }
238
+ const existing = this.db.prepare('SELECT data FROM contexts WHERE session=? AND target=?').get(session, target);
239
+ const context = existing ? JSON.parse(String(existing.data)) : {
240
+ id: 'c_' + randomUUID(), session, target, tool: 'ghidra', state: 'opening', generation: 0, scalars: [], complexChanges: [],
241
+ };
242
+ if (!existing)
243
+ this.db.prepare('INSERT INTO contexts VALUES(?,?,?,?,?)').run(context.id, session, target, 0, stable(context));
244
+ return { context, request: this.insertRequest(context.id, 'ghidra.open', intent, callerId, hash), replay: false };
245
+ });
246
+ }
247
+ reserveRequest(scope, operation, parameters, callerId) {
248
+ const context = this.context(scope);
249
+ const intent = { operation: text(operation, 128), session: scope.session, target: scope.target, parameters };
250
+ bounded(intent);
251
+ const hash = digest(intent);
252
+ return this.transaction(() => {
253
+ const previous = this.existingRequest(callerId, hash);
254
+ return previous ? { request: previous, replay: true } : { request: this.insertRequest(context.id, operation, intent, callerId, hash), replay: false };
255
+ });
256
+ }
257
+ request(request) {
258
+ const row = this.db.prepare('SELECT data FROM requests WHERE id=?').get(id(request));
259
+ if (!row)
260
+ fail('notFound', 'Request was not found in the selected profile.');
261
+ return JSON.parse(String(row.data));
262
+ }
263
+ reserveAdministration(operation, parameters, callerId) {
264
+ const intent = { operation: text(operation, 128), parameters }, hash = digest(intent);
265
+ bounded(intent);
266
+ return this.transaction(() => {
267
+ if (callerId !== undefined) {
268
+ id(callerId);
269
+ if (this.db.prepare('SELECT id FROM requests WHERE caller_id=?').get(callerId))
270
+ fail('idempotency', 'Request ID already identifies scoped research.');
271
+ const row = this.db.prepare('SELECT id,hash FROM administration_requests WHERE caller_id=?').get(callerId);
272
+ if (row) {
273
+ if (row.hash !== hash)
274
+ fail('idempotency', 'Request ID was already used with different effective input.');
275
+ return { request: this.administration(String(row.id)), replay: true };
276
+ }
277
+ }
278
+ const request = { id: 'a_' + randomUUID(), operation, intent, state: 'accepted', ...(callerId ? { callerId } : {}), createdAt: now(), updatedAt: now() };
279
+ this.db.prepare('INSERT INTO administration_requests VALUES(?,?,?,?,?)').run(request.id, callerId ?? null, hash, request.state, bounded(request));
280
+ return { request, replay: false };
281
+ });
282
+ }
283
+ administrationByCaller(callerId) { const row = this.db.prepare('SELECT id FROM administration_requests WHERE caller_id=?').get(id(callerId)); return row ? this.administration(String(row.id)) : undefined; }
284
+ requestByCaller(callerId) { const row = this.db.prepare('SELECT id FROM requests WHERE caller_id=?').get(id(callerId)); return row ? this.request(String(row.id)) : undefined; }
285
+ administration(request) {
286
+ const row = this.db.prepare('SELECT data FROM administration_requests WHERE id=?').get(id(request));
287
+ if (!row)
288
+ fail('notFound', 'Administration request was not found in this profile.');
289
+ return JSON.parse(String(row.data));
290
+ }
291
+ unresolvedAdministration() {
292
+ return this.db.prepare("SELECT data FROM administration_requests WHERE state IN ('running','unknown') ORDER BY rowid LIMIT 100")
293
+ .all().map(row => JSON.parse(String(row.data)));
294
+ }
295
+ unresolvedSessionAdministration(session, target) {
296
+ this.session(session);
297
+ if (target)
298
+ this.target(target);
299
+ return this.db.prepare("SELECT data FROM administration_requests WHERE state IN ('running','unknown') AND json_extract(data,'$.intent.parameters.session')=? AND (? IS NULL OR json_extract(data,'$.intent.parameters.target') IS NULL OR json_extract(data,'$.intent.parameters.target')=?) ORDER BY rowid LIMIT 100")
300
+ .all(session, target ?? null, target ?? null).map(row => JSON.parse(String(row.data)));
301
+ }
302
+ unresolvedServer() {
303
+ return this.db.prepare("SELECT data FROM administration_requests WHERE state IN ('running','unknown') AND json_extract(data,'$.operation') LIKE 'server.%' ORDER BY rowid LIMIT 100")
304
+ .all().map(row => JSON.parse(String(row.data)));
305
+ }
306
+ unresolvedProject(project) {
307
+ return this.db.prepare("SELECT data FROM administration_requests WHERE state IN ('running','unknown') AND json_extract(data,'$.intent.parameters.project')=? ORDER BY rowid LIMIT 100")
308
+ .all(project).map(row => JSON.parse(String(row.data)));
309
+ }
310
+ administrationState(request, state, result) {
311
+ return this.transaction(() => {
312
+ const previous = this.administration(request);
313
+ if (previous.state === 'applied' || previous.state === 'failed') {
314
+ if (previous.state !== state || stable(previous.result) !== stable(result))
315
+ fail('idempotency', 'A completed administration outcome is immutable.');
316
+ return previous;
317
+ }
318
+ if (state === 'running' && previous.state !== 'accepted')
319
+ fail('blocked', 'Administration operation already started; do not replay it.');
320
+ const next = { ...previous, state, ...(result === undefined ? {} : { result }), updatedAt: now() };
321
+ this.db.prepare('UPDATE administration_requests SET state=?,data=? WHERE id=?').run(state, bounded(next), request);
322
+ return next;
323
+ });
324
+ }
325
+ unresolved(scope) {
326
+ const context = this.context(scope);
327
+ return this.db.prepare("SELECT data FROM requests WHERE context=? AND state IN ('running','unknown') ORDER BY rowid LIMIT 100")
328
+ .all(context.id).map(row => JSON.parse(String(row.data)));
329
+ }
330
+ startRequest(request, execution) {
331
+ return this.transaction(() => {
332
+ const previous = this.request(request);
333
+ if (previous.state !== 'accepted')
334
+ fail('blocked', 'Request already started; reconcile its recorded outcome before continuing.');
335
+ const next = { ...previous, state: 'running', ...(execution === undefined ? {} : { execution }), updatedAt: now() };
336
+ this.db.prepare('UPDATE requests SET state=?,data=? WHERE id=?').run(next.state, bounded(next), next.id);
337
+ return next;
338
+ });
339
+ }
340
+ recordRequest(request, state, result) {
341
+ return this.transaction(() => this.writeRequest(request, state, result));
342
+ }
343
+ writeRequest(request, state, result) {
344
+ const previous = this.request(request);
345
+ if (previous.state === 'applied' || previous.state === 'failed') {
346
+ if (state !== previous.state || stable(result) !== stable(previous.result))
347
+ fail('idempotency', 'A completed request outcome is immutable.');
348
+ return previous;
349
+ }
350
+ if (state === 'applied' && previous.state === 'accepted')
351
+ fail('binding', 'Request must start before an effect can be acknowledged.');
352
+ const next = { ...previous, state, result, updatedAt: now() };
353
+ this.db.prepare('UPDATE requests SET state=?,data=? WHERE id=?').run(next.state, bounded(next), next.id);
354
+ return next;
355
+ }
356
+ establishModel(target, revision, snapshot) {
357
+ version(revision);
358
+ this.knownSnapshot(snapshot);
359
+ return this.transaction(() => {
360
+ const model = this.model(target);
361
+ if (model.state === 'ready')
362
+ fail('binding', 'Ghidra model was already initialized; publication must verify its shared base.');
363
+ const next = { ...model, state: 'ready', sharedRevision: revision, sharedSnapshot: snapshot };
364
+ this.db.prepare('UPDATE ghidra_models SET data=? WHERE target=?').run(stable(next), target);
365
+ return next;
366
+ });
367
+ }
368
+ beginModelInitialization(target) {
369
+ return this.transaction(() => {
370
+ const model = this.model(target);
371
+ if (model.state !== 'opening')
372
+ fail('blocked', 'Shared model setup already started; reconcile its persisted outcome.');
373
+ const next = { ...model, state: 'unknown' };
374
+ this.db.prepare('UPDATE ghidra_models SET data=? WHERE target=?').run(stable(next), target);
375
+ return next;
376
+ });
377
+ }
378
+ observeShared(target, revision, snapshot) {
379
+ version(revision);
380
+ this.knownSnapshot(snapshot);
381
+ return this.transaction(() => {
382
+ const model = this.model(target);
383
+ if (model.state !== 'ready')
384
+ fail('blocked', 'Shared model outcome requires reconciliation before accepting another baseline.');
385
+ if (revision < (model.sharedRevision ?? 1))
386
+ fail('binding', 'Shared version history regressed.');
387
+ const next = { ...model, sharedRevision: revision, sharedSnapshot: snapshot };
388
+ this.db.prepare('UPDATE ghidra_models SET data=? WHERE target=?').run(stable(next), target);
389
+ return next;
390
+ });
391
+ }
392
+ publication(publication) {
393
+ const row = this.db.prepare('SELECT data FROM publications WHERE id=?').get(id(publication));
394
+ if (!row)
395
+ fail('notFound', 'Publication was not found in this profile.');
396
+ return JSON.parse(String(row.data));
397
+ }
398
+ activePublication(target) {
399
+ this.target(target);
400
+ const row = this.db.prepare("SELECT data FROM publications WHERE target=? AND state IN ('preparing','commit_intent','unknown')").get(target);
401
+ return row ? JSON.parse(String(row.data)) : undefined;
402
+ }
403
+ beginPublication(scope, request, expectedRevision, snapshot) {
404
+ version(expectedRevision);
405
+ this.knownSnapshot(snapshot);
406
+ return this.transaction(() => {
407
+ const context = this.context(scope), intent = this.request(request), model = this.model(scope.target);
408
+ if (intent.context !== context.id || intent.state !== 'running')
409
+ fail('binding', 'Publication does not match its running context request.');
410
+ const active = this.activePublication(scope.target);
411
+ if (active || model.state !== 'ready')
412
+ fail('blocked', 'Shared model has an unresolved publication; reconcile it before another merge.');
413
+ const publication = { ...scope, id: request, request, state: 'preparing', expectedRevision, snapshot,
414
+ generation: context.generation, scalars: context.scalars, complex: context.complexChanges.length > 0, complexChanges: context.complexChanges };
415
+ this.db.prepare('INSERT INTO publications VALUES(?,?,?,?)').run(publication.id, scope.target, publication.state, bounded(publication));
416
+ return publication;
417
+ });
418
+ }
419
+ publicationIntent(publication) {
420
+ return this.transaction(() => {
421
+ const previous = this.publication(publication);
422
+ if (previous.state !== 'preparing')
423
+ fail('blocked', 'Publication intent was already issued; verify its history instead of retrying.');
424
+ const next = { ...previous, state: 'commit_intent' };
425
+ const model = this.model(previous.target);
426
+ this.db.prepare('UPDATE ghidra_models SET data=? WHERE target=?').run(stable({ ...model, state: 'unknown' }), previous.target);
427
+ this.db.prepare('UPDATE publications SET state=?,data=? WHERE id=?').run(next.state, bounded(next), next.id);
428
+ return next;
429
+ });
430
+ }
431
+ unknownPublication(publication, detail) {
432
+ return this.transaction(() => {
433
+ const previous = this.publication(publication);
434
+ if (!['commit_intent', 'unknown'].includes(previous.state))
435
+ fail('binding', 'Uncertainty must retain an actual publication intent.');
436
+ const next = { ...previous, state: 'unknown', detail };
437
+ this.db.prepare('UPDATE publications SET state=?,data=? WHERE id=?').run(next.state, bounded(next), next.id);
438
+ return next;
439
+ });
440
+ }
441
+ finishPublication(publication, publishedRevision, result) {
442
+ version(publishedRevision);
443
+ return this.transaction(() => {
444
+ const previous = this.publication(publication);
445
+ if (!['commit_intent', 'unknown'].includes(previous.state) || publishedRevision !== previous.expectedRevision + 1)
446
+ fail('binding', 'Verified publication has an unexpected state or version.');
447
+ const context = this.context(previous), model = this.model(previous.target);
448
+ if (context.generation !== previous.generation)
449
+ fail('binding', 'Private context changed during publication; preserve both saved versions.');
450
+ this.writeContext(previous, previous.generation, { state: 'ready', baseRevision: publishedRevision, baseSnapshot: previous.snapshot,
451
+ workingSnapshot: previous.snapshot, viewEpoch: (context.viewEpoch ?? 0) + 1, scalars: [], complexChanges: [] });
452
+ this.db.prepare('UPDATE ghidra_models SET data=? WHERE target=?').run(stable({ ...model, state: 'ready', sharedRevision: publishedRevision, sharedSnapshot: previous.snapshot }), previous.target);
453
+ this.writeRequest(previous.request, 'applied', result);
454
+ const next = { ...previous, state: 'verified', publishedRevision };
455
+ this.db.prepare('UPDATE publications SET state=?,data=? WHERE id=?').run(next.state, bounded(next), next.id);
456
+ return next;
457
+ });
458
+ }
459
+ rejectPublication(publication, result) {
460
+ return this.transaction(() => {
461
+ const previous = this.publication(publication);
462
+ if (!['preparing', 'commit_intent'].includes(previous.state))
463
+ fail('blocked', 'An uncertain publication cannot be abandoned without proof.');
464
+ const model = this.model(previous.target), next = { ...previous, state: 'conflict', detail: result };
465
+ this.db.prepare('UPDATE ghidra_models SET data=? WHERE target=?').run(stable({ ...model, state: 'ready' }), previous.target);
466
+ this.db.prepare('UPDATE publications SET state=?,data=? WHERE id=?').run(next.state, bounded(next), next.id);
467
+ this.writeRequest(previous.request, 'failed', result);
468
+ return next;
469
+ });
470
+ }
471
+ publishedSince(target, revision) {
472
+ this.target(target);
473
+ version(revision);
474
+ return this.db.prepare("SELECT data FROM publications WHERE target=? AND state='verified' ORDER BY rowid").all(target)
475
+ .map(row => JSON.parse(String(row.data))).filter(item => item.publishedRevision > revision);
476
+ }
477
+ saveContext(scope, expectedGeneration, patch) {
478
+ return this.transaction(() => this.writeContext(scope, expectedGeneration, patch));
479
+ }
480
+ /** Snapshot and successful request acknowledgment enter the same durable transaction. */
481
+ completeSavedRequest(scope, request, expectedGeneration, patch, result) {
482
+ return this.transaction(() => {
483
+ const previous = this.context(scope), intent = this.request(request);
484
+ if (intent.context !== previous.id)
485
+ fail('binding', 'Saved operation belongs to a different working context.');
486
+ if (intent.state === 'applied' || intent.state === 'failed')
487
+ fail('idempotency', 'Completed request cannot change its working context.');
488
+ const context = this.writeContext(scope, expectedGeneration, patch);
489
+ const outcome = this.writeRequest(request, 'applied', result);
490
+ return { context, request: outcome };
491
+ });
492
+ }
493
+ writeContext(scope, expectedGeneration, patch) {
494
+ if (!Number.isSafeInteger(expectedGeneration) || expectedGeneration < 0)
495
+ fail('invalid', 'Invalid context generation.');
496
+ if (Object.keys(patch).some(key => !['state', 'baseRevision', 'baseSnapshot', 'workingSnapshot', 'viewEpoch', 'scalars', 'complexChanges', 'debugger', 'syncPolicy'].includes(key)) ||
497
+ patch.state !== undefined && !['opening', 'ready', 'stopped', 'needs_recovery'].includes(patch.state))
498
+ fail('invalid', 'Invalid context state update.');
499
+ if (patch.baseRevision !== undefined)
500
+ version(patch.baseRevision);
501
+ if (patch.viewEpoch !== undefined && (!Number.isSafeInteger(patch.viewEpoch) || patch.viewEpoch < 0))
502
+ fail('invalid', 'View epoch must be a nonnegative integer.');
503
+ if (patch.baseSnapshot)
504
+ this.knownSnapshot(patch.baseSnapshot);
505
+ if (patch.workingSnapshot)
506
+ this.knownSnapshot(patch.workingSnapshot);
507
+ if (patch.syncPolicy) {
508
+ const p = patch.syncPolicy;
509
+ if (Object.keys(p).some(k => !['publish', 'refresh'].includes(k)) || !['manual', 'after-edit', 'after-save'].includes(p.publish) || !['manual', 'before-edit'].includes(p.refresh))
510
+ fail('invalid', 'Invalid synchronization policy.');
511
+ }
512
+ if (patch.debugger) {
513
+ const state = patch.debugger;
514
+ if (typeof state.connected !== 'boolean' || !Array.isArray(state.traces) || state.traces.length > 64)
515
+ fail('invalid', 'Debugger trace state is malformed.');
516
+ const ids = new Set();
517
+ for (const trace of state.traces) {
518
+ if (!/^trace_[a-f0-9-]{36}$/.test(trace.id) || ids.has(trace.id) || !/^\d+$/.test(trace.snap) || !Number.isSafeInteger(trace.modules) || trace.modules < 0 || !Number.isSafeInteger(trace.threads) || trace.threads < 0 || typeof trace.connected !== 'boolean')
519
+ fail('invalid', 'Saved trace identity or counts are malformed.');
520
+ text(trace.name, 512);
521
+ this.knownSnapshot(trace.snapshot);
522
+ ids.add(trace.id);
523
+ if (trace.thread !== undefined && !/^\d+$/.test(trace.thread))
524
+ fail('invalid', 'Saved debugger thread is malformed.');
525
+ }
526
+ if (state.active !== undefined && !ids.has(state.active))
527
+ fail('invalid', 'Active trace must belong to the selected context.');
528
+ }
529
+ const previous = this.context(scope);
530
+ if (previous.generation !== expectedGeneration)
531
+ fail('binding', 'Context changed before its saved result could be recorded.');
532
+ const next = { ...previous, ...patch, generation: expectedGeneration + 1 };
533
+ if (next.state === 'ready' && (!next.baseRevision || !next.baseSnapshot || !next.workingSnapshot))
534
+ fail('binding', 'A ready context requires durable private and base snapshots.');
535
+ if (!Array.isArray(next.scalars) || !Array.isArray(next.complexChanges))
536
+ fail('invalid', 'Pending changes must be arrays.');
537
+ for (const request of next.complexChanges)
538
+ id(request);
539
+ if (new Set(next.complexChanges).size !== next.complexChanges.length)
540
+ fail('invalid', 'Complex change identities must be distinct.');
541
+ const fields = new Set();
542
+ for (const change of next.scalars) {
543
+ if (!/^(?:[A-Za-z0-9_]+:)?[a-f0-9]+$/.test(text(change.address, 128)) ||
544
+ !['function.name', 'comment.eol', 'comment.pre', 'comment.post', 'comment.plate', 'comment.repeatable'].includes(change.field))
545
+ fail('invalid', 'Scalar changes require a canonical address and a supported field.');
546
+ for (const value of [change.original, change.local, ...(change.conflict ? [change.conflict.shared] : [])]) {
547
+ if (value !== null && (typeof value !== 'string' || value.length > 16000 || value.includes('\0')))
548
+ fail('invalid', 'Scalar values must be bounded strings or null.');
549
+ }
550
+ if (change.removed !== undefined && (change.removed !== true || change.local !== null || !next.complexChanges.length))
551
+ fail('invalid', 'Removed scalar fields require retained complex program state.');
552
+ if (change.field === 'function.name' && !change.removed && (change.local === null || !change.local.length || change.local.length > 512))
553
+ fail('invalid', 'Function name must be a nonempty bounded string.');
554
+ const field = change.address + ':' + change.field;
555
+ if (change.dependency !== undefined && !/^[a-f0-9]{64}$/.test(change.dependency))
556
+ fail('invalid', 'Scalar dependency fingerprint is malformed.');
557
+ if (fields.has(field))
558
+ fail('invalid', 'Scalar field must have one original/local comparison.');
559
+ fields.add(field);
560
+ }
561
+ this.db.prepare('UPDATE contexts SET generation=?,data=? WHERE id=?').run(next.generation, bounded(next), next.id);
562
+ return next;
563
+ }
564
+ }
package/package.json CHANGED
@@ -1,6 +1,49 @@
1
1
  {
2
2
  "name": "@eyeauras/insight-cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "description": "Agent-facing durable sessions and isolated Ghidra research contexts",
6
+ "main": "./dist/src/index.js",
7
+ "types": "./dist/src/index.d.ts",
8
+ "bin": {
9
+ "insight-cli": "./dist/src/bin.js"
10
+ },
11
+ "files": [
12
+ "README.md",
13
+ "dist/src",
14
+ "SKILL.md",
15
+ "skills",
16
+ "references",
17
+ "runtime",
18
+ "LICENSE",
19
+ ".cli-package-build.json"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.json && node ../../scripts/prepare-package.mjs",
23
+ "prepack": "npm run build"
24
+ },
25
+ "dependencies": {
26
+ "@eyeauras/cli-factory": "0.4.1",
27
+ "@eyeauras/cli-factory-ipc": "https://raw.githubusercontent.com/iXab3r/insight-cli/43f815a006711418bcffa47a7d99ef5c42424ca5/vendor/factory-ipc-a2094fb.tgz",
28
+ "@modelcontextprotocol/sdk": "1.32.1",
29
+ "tar": "7.5.22",
30
+ "yauzl": "3.4.0"
31
+ },
32
+ "engines": {
33
+ "node": ">=24"
34
+ },
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/iXab3r/insight-cli.git",
39
+ "directory": "skills/ghidra-coordinator"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public",
43
+ "registry": "https://registry.npmjs.org/"
44
+ },
45
+ "homepage": "https://github.com/iXab3r/insight-cli#readme",
46
+ "bugs": {
47
+ "url": "https://github.com/iXab3r/insight-cli/issues"
48
+ }
49
+ }