@eyeauras/insight-cli 0.3.0 → 0.3.2

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 (80) hide show
  1. package/.cli-package-build.json +1 -1
  2. package/SKILL.md +6 -1
  3. package/dist/src/analysis-default.d.ts +26 -0
  4. package/dist/src/analysis-default.js +149 -0
  5. package/dist/src/analysis-storage.d.ts +39 -20
  6. package/dist/src/analysis-storage.js +112 -16
  7. package/dist/src/cli.js +9 -2
  8. package/dist/src/initial-model.js +26 -5
  9. package/dist/src/initial-progress.d.ts +4 -0
  10. package/dist/src/initial-progress.js +64 -0
  11. package/dist/src/native-inputs.js +2 -1
  12. package/dist/src/server.js +14 -0
  13. package/package.json +1 -1
  14. package/references/native/InsightImportProgress.java +42 -0
  15. package/references/native/InsightInitialImport.java +8 -2
  16. package/references/native/InsightSdkFiles.java +13 -10
  17. package/references/setup.md +27 -0
  18. package/references/storage.md +35 -7
  19. package/skill-bundle/insight-artifacts/SKILL.md +1 -1
  20. package/skill-bundle/insight-artifacts/references/storage.md +35 -7
  21. package/skill-bundle/insight-binary/SKILL.md +1 -1
  22. package/skill-bundle/insight-capture/SKILL.md +1 -1
  23. package/skill-bundle/insight-cli/SKILL.md +6 -1
  24. package/skill-bundle/insight-cli/references/archive.md +32 -0
  25. package/skill-bundle/insight-cli/references/managed-runtime.md +162 -0
  26. package/skill-bundle/insight-cli/references/setup.md +98 -0
  27. package/skill-bundle/insight-cli/references/storage.md +35 -7
  28. package/skill-bundle/insight-die/SKILL.md +1 -1
  29. package/skill-bundle/insight-dnspy/SKILL.md +1 -1
  30. package/skill-bundle/insight-dotnet-dump/SKILL.md +1 -1
  31. package/skill-bundle/insight-dotnet-stack/SKILL.md +1 -1
  32. package/skill-bundle/insight-dotnet-trace/SKILL.md +1 -1
  33. package/skill-bundle/insight-frida/SKILL.md +1 -1
  34. package/skill-bundle/insight-ghidra/SKILL.md +27 -1
  35. package/skill-bundle/insight-ghidra/references/setup.md +27 -0
  36. package/skill-bundle/insight-ghidra/references/storage.md +35 -7
  37. package/skill-bundle/insight-ilspy/SKILL.md +1 -1
  38. package/skill-bundle/insight-interop/SKILL.md +1 -1
  39. package/skill-bundle/insight-lldb/SKILL.md +1 -1
  40. package/skill-bundle/insight-llvm/SKILL.md +1 -1
  41. package/skill-bundle/insight-managed-heap/SKILL.md +1 -1
  42. package/skill-bundle/insight-managed-metadata/SKILL.md +1 -1
  43. package/skill-bundle/insight-mitmproxy/SKILL.md +1 -1
  44. package/skill-bundle/insight-overview/SKILL.md +1 -1
  45. package/skill-bundle/insight-procmon/SKILL.md +1 -1
  46. package/skill-bundle/insight-re/SKILL.md +6 -1
  47. package/skill-bundle/insight-re/references/storage.md +35 -7
  48. package/skill-bundle/insight-sevenzip/SKILL.md +1 -1
  49. package/skill-bundle/insight-strace/SKILL.md +1 -1
  50. package/skill-bundle/insight-tshark/SKILL.md +1 -1
  51. package/skill-bundle/insight-windbg/SKILL.md +1 -1
  52. package/skill-bundle/manifest.json +1 -1
  53. package/skills/insight-artifacts/SKILL.md +1 -1
  54. package/skills/insight-artifacts/references/storage.md +35 -7
  55. package/skills/insight-binary/SKILL.md +1 -1
  56. package/skills/insight-capture/SKILL.md +1 -1
  57. package/skills/insight-die/SKILL.md +1 -1
  58. package/skills/insight-dnspy/SKILL.md +1 -1
  59. package/skills/insight-dotnet-dump/SKILL.md +1 -1
  60. package/skills/insight-dotnet-stack/SKILL.md +1 -1
  61. package/skills/insight-dotnet-trace/SKILL.md +1 -1
  62. package/skills/insight-frida/SKILL.md +1 -1
  63. package/skills/insight-ghidra/SKILL.md +27 -1
  64. package/skills/insight-ghidra/references/setup.md +27 -0
  65. package/skills/insight-ghidra/references/storage.md +35 -7
  66. package/skills/insight-ilspy/SKILL.md +1 -1
  67. package/skills/insight-interop/SKILL.md +1 -1
  68. package/skills/insight-lldb/SKILL.md +1 -1
  69. package/skills/insight-llvm/SKILL.md +1 -1
  70. package/skills/insight-managed-heap/SKILL.md +1 -1
  71. package/skills/insight-managed-metadata/SKILL.md +1 -1
  72. package/skills/insight-mitmproxy/SKILL.md +1 -1
  73. package/skills/insight-overview/SKILL.md +1 -1
  74. package/skills/insight-procmon/SKILL.md +1 -1
  75. package/skills/insight-re/SKILL.md +6 -1
  76. package/skills/insight-re/references/storage.md +35 -7
  77. package/skills/insight-sevenzip/SKILL.md +1 -1
  78. package/skills/insight-strace/SKILL.md +1 -1
  79. package/skills/insight-tshark/SKILL.md +1 -1
  80. package/skills/insight-windbg/SKILL.md +1 -1
@@ -2,6 +2,7 @@ import { mkdir, realpath, readFile, open, link, unlink } from 'node:fs/promises'
2
2
  import { join, resolve } from 'node:path';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { stable, object, fail } from './model.js';
5
+ import { initialProgress, progressPhase, readInitialProgress } from './initial-progress.js';
5
6
  export const initialRoot = (workspace, target) => join(workspace.directory, 'ghidra-native', 'initial', target);
6
7
  export async function initialIntent(workspace, target) {
7
8
  try {
@@ -15,6 +16,24 @@ export async function initialIntent(workspace, target) {
15
16
  }
16
17
  /** Immutable input/authority binding; private attempts and conditional first creation share it. */
17
18
  export async function initializeModel(workspace, target, binding, server, fingerprint) {
19
+ try {
20
+ return await initialize(workspace, target, binding, server, fingerprint);
21
+ }
22
+ catch (error) {
23
+ try {
24
+ const progress = await readInitialProgress(workspace.directory, target.id);
25
+ // A busy or disconnected caller must not overwrite another live native worker's observation.
26
+ if (progress && progress.state !== 'complete' && (progress.pid === process.pid || progress.workerAlive === false)) {
27
+ const code = String(error.code ?? '');
28
+ const state = code === 'tool.timeout' ? 'timed_out' : code === 'tool.cancelled' ? 'cancelled' : progress.state === 'failed' || progress.state === 'cancelled' ? progress.state : 'unknown';
29
+ await initialProgress(initialRoot(workspace, target.id), target.id, progress.phase, state);
30
+ }
31
+ }
32
+ catch { /* Preserve the authoritative operation failure when observation storage also fails. */ }
33
+ throw error;
34
+ }
35
+ }
36
+ async function initialize(workspace, target, binding, server, fingerprint) {
18
37
  const root = initialRoot(workspace, target.id);
19
38
  await mkdir(root, { recursive: true, mode: 0o700 });
20
39
  if (await realpath(root) !== resolve(root))
@@ -67,20 +86,22 @@ export async function initializeModel(workspace, target, binding, server, finger
67
86
  const receipt = object(JSON.parse(await readFile(join(root, 'import.json'), 'utf8'))), name = receipt.snapshot;
68
87
  if (typeof name !== 'string' || !/^attempt-[a-f0-9-]{36}\.gzf$/.test(name))
69
88
  fail('binding', 'Initial model snapshot receipt is malformed.');
70
- const snapshot = await workspace.captureSnapshot(join(root, name));
89
+ const snapshot = await progressPhase(root, target.id, 'snapshot-verification', () => workspace.captureSnapshot(join(root, name)));
71
90
  if (snapshot.sha256 !== receipt.snapshotSha256)
72
91
  fail('binding', 'Initial saved snapshot changed before publication.');
73
- let result = await server.initialPublication(binding, root, 'stage');
92
+ const publication = (operation) => progressPhase(root, target.id, 'publication-' + operation, () => server.initialPublication(binding, root, operation));
93
+ let result = await publication('stage');
74
94
  if (result.state === 'staged') {
75
- const proof = await server.initialPublication(binding, root, 'verify-stage');
95
+ const proof = await publication('verify-stage');
76
96
  if (proof.state !== 'stage_verified' || proof.version !== 1 || proof.snapshotBuffersVerified !== true)
77
97
  fail('unknown', 'Independent initial staging readback did not establish the complete snapshot.');
78
- result = await server.initialPublication(binding, root, 'promote');
98
+ result = await publication('promote');
79
99
  }
80
100
  if (result.state !== 'published' || result.version !== 1 || result.snapshotBuffersVerified !== true)
81
101
  fail('unknown', 'Initial publication requires reconciliation against its saved snapshot.');
82
- const verified = await server.initialPublication(binding, root, 'verify');
102
+ const verified = await publication('verify');
83
103
  if (verified.state !== 'verified' || verified.version !== 1 || verified.snapshotBuffersVerified !== true)
84
104
  fail('unknown', 'Independent initial-model readback did not establish the complete saved buffers.');
85
105
  workspace.establishModel(target.id, 1, snapshot);
106
+ await initialProgress(root, target.id, 'complete', 'complete');
86
107
  }
@@ -0,0 +1,4 @@
1
+ /** Observation only. Completion receipts and the workspace remain the outcome authority. */
2
+ export declare function readInitialProgress(directory: string, target: string): Promise<any>;
3
+ export declare function initialProgress(root: string, target: string, phase: string, state?: string): Promise<void>;
4
+ export declare function progressPhase<T>(root: string, target: string, phase: string, action: () => Promise<T>): Promise<T>;
@@ -0,0 +1,64 @@
1
+ import { readFile, mkdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { atomicWrite } from './tool-runtime/store.js';
4
+ /** Observation only. Completion receipts and the workspace remain the outcome authority. */
5
+ export async function readInitialProgress(directory, target) {
6
+ try {
7
+ const value = JSON.parse(await readFile(join(directory, 'ghidra-native', 'initial', target, 'progress.json'), 'utf8'));
8
+ if (value.schema !== 1 || value.target !== target)
9
+ throw Error('Invalid initial progress binding');
10
+ const ageMs = Math.max(0, Date.now() - Date.parse(value.updatedAt));
11
+ let workerAlive;
12
+ if (Number.isSafeInteger(value.pid) && value.pid > 0)
13
+ try {
14
+ process.kill(value.pid, 0);
15
+ workerAlive = true;
16
+ }
17
+ catch (error) {
18
+ if (error.code === 'ESRCH')
19
+ workerAlive = false;
20
+ }
21
+ return { ...value, operation: 'initial-model', ageMs, elapsedMs: Math.max(0, (value.state === 'running' ? Date.now() : Date.parse(value.updatedAt)) - Date.parse(value.startedAt)),
22
+ ...(workerAlive === undefined ? {} : { workerAlive }),
23
+ observation: value.state === 'running' && (workerAlive === false || ageMs > 15000) ? 'stale' : 'current' };
24
+ }
25
+ catch (error) {
26
+ if (error.code === 'ENOENT')
27
+ return null;
28
+ throw error;
29
+ }
30
+ }
31
+ export async function initialProgress(root, target, phase, state = 'running') {
32
+ await mkdir(root, { recursive: true, mode: 0o700 });
33
+ let startedAt = new Date().toISOString();
34
+ try {
35
+ const previous = JSON.parse(await readFile(join(root, 'progress.json'), 'utf8'));
36
+ if (previous.target === target)
37
+ startedAt = previous.startedAt;
38
+ }
39
+ catch (error) {
40
+ if (error.code !== 'ENOENT')
41
+ throw error;
42
+ }
43
+ await atomicWrite(join(root, 'progress.json'), { schema: 1, target, phase, state, pid: process.pid, startedAt, updatedAt: new Date().toISOString(), indeterminate: true });
44
+ }
45
+ export async function progressPhase(root, target, phase, action) {
46
+ await initialProgress(root, target, phase);
47
+ let pending = Promise.resolve(), failure;
48
+ const timer = setInterval(() => { pending = pending.then(() => initialProgress(root, target, phase)).catch(error => { failure = error; }); }, 2000);
49
+ timer.unref();
50
+ let actionFailed = false;
51
+ try {
52
+ return await action();
53
+ }
54
+ catch (error) {
55
+ actionFailed = true;
56
+ throw error;
57
+ }
58
+ finally {
59
+ clearInterval(timer);
60
+ await pending;
61
+ if (failure && !actionFailed)
62
+ throw failure;
63
+ }
64
+ }
@@ -1,4 +1,5 @@
1
1
  import { runTool } from './tool-runtime/run.js';
2
+ import { javaClasspathArguments } from './tool-runtime/java-arguments.js';
2
3
  import { createHash, randomUUID } from 'node:crypto';
3
4
  import { readFile, writeFile, readdir, mkdir, open, rename, rm, realpath, stat } from 'node:fs/promises';
4
5
  import { basename, delimiter, dirname, isAbsolute, join, relative, sep } from 'node:path';
@@ -158,7 +159,7 @@ export async function nativeHelpers(directory, inputs, c) {
158
159
  if (c)
159
160
  await runTool(c, { tool: 'ghidra', role: 'compile-helpers', installation: inputs.fingerprint, executable, arguments: args, timeoutMs: 60000, maxOutputBytes: 262144 });
160
161
  else
161
- await run(executable, args, { timeout: 60000, maxBuffer: 262144 });
162
+ await run(executable, await javaClasspathArguments(temporary, 'ghidra', executable, args), { timeout: 60000, maxBuffer: 262144 });
162
163
  const complete = await open(join(temporary, 'complete'), 'wx', 0o600);
163
164
  try {
164
165
  await complete.writeFile(key);
@@ -203,6 +203,16 @@ export class GhidraServer {
203
203
  }
204
204
  async initialImport(root) {
205
205
  const timeoutMilliseconds = ghidraBudget(this.c.profile.values, 'import');
206
+ const report = async () => {
207
+ try {
208
+ const p = object(JSON.parse(await readFile(join(root, 'progress.json'), 'utf8')));
209
+ const elapsed = Math.round((Date.now() - Date.parse(String(p.startedAt))) / 1000);
210
+ this.c.progress(`Ghidra ${String(p.phase)}: ${elapsed}s${typeof p.total === 'number' ? `; task ${String(p.completed)}/${p.total}` : ''}${p.message ? `; ${String(p.message).replace(/[\r\n\x00-\x1f]/g, ' ')}` : ''}`);
211
+ }
212
+ catch { /* Progress observation never changes the native operation's outcome. */ }
213
+ };
214
+ const timer = setInterval(() => { void report(); }, 5000);
215
+ timer.unref();
206
216
  try {
207
217
  return await this.run('initial-import', 'InsightInitialImport', [root], {}, undefined, [0], timeoutMilliseconds);
208
218
  }
@@ -211,8 +221,12 @@ export class GhidraServer {
211
221
  throw new CliError(error.message + ' Initial import remains unproved. Increase profile initialImportSeconds and continue the original scope; incomplete private analysis restarts from the retained input.', { code: error.code, result: { phase: 'initial-import', timeoutMilliseconds, budgetSetting: 'initialImportSeconds', outcome: 'unknown', diagnostics: error.result } });
212
222
  throw error;
213
223
  }
224
+ finally {
225
+ clearInterval(timer);
226
+ }
214
227
  }
215
228
  async initialPublication(binding, root, operation = 'stage') {
229
+ this.c.progress('Ghidra initial publication: ' + operation);
216
230
  return this.run('initial-publication', 'db.InsightInitialPublication', [root, operation], await this.credentials(binding, operation.startsWith('verify') ? 'reader' : 'publisher'));
217
231
  }
218
232
  async publish(binding, path, snapshot, expected, request, journal, sourceSha) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eyeauras/insight-cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "type": "module",
5
5
  "description": "Agent-facing durable sessions and isolated Ghidra research contexts",
6
6
  "main": "./dist/src/index.js",
@@ -0,0 +1,42 @@
1
+ import com.google.gson.*;
2
+ import ghidra.util.task.TaskMonitorAdapter;
3
+ import insight.InsightInitialJournal;
4
+ import java.nio.file.*;
5
+ import java.time.Instant;
6
+ import java.util.*;
7
+
8
+ /** A bounded observation channel, never a completion receipt or an overall percentage. */
9
+ class InsightImportProgress extends TaskMonitorAdapter implements AutoCloseable {
10
+ private final Path root;
11
+ private final String target,started=Instant.now().toString();
12
+ private final Timer heartbeat=new Timer("insight-import-progress",true);
13
+ private String phase="starting",state="running";
14
+ private String message="";
15
+ private long completed,total;
16
+ private boolean indeterminate=true;
17
+ private volatile Throwable writeFailure;
18
+ InsightImportProgress(Path root,String target)throws Exception{
19
+ super(true);this.root=root;this.target=target;emit();
20
+ heartbeat.scheduleAtFixedRate(new TimerTask(){public void run(){try{emit();}catch(Throwable failure){writeFailure=failure;}}},2000,2000);
21
+ }
22
+ synchronized void phase(String value)throws Exception{phase=value;setMessage("");initialize(0);setIndeterminate(true);emit();}
23
+ public synchronized void setMessage(String value){message=Objects.toString(value,"");}
24
+ public synchronized String getMessage(){return message;}
25
+ public synchronized void initialize(long maximum){completed=0;total=maximum;indeterminate=maximum<=0;}
26
+ public synchronized void setProgress(long value){completed=value;}
27
+ public synchronized void incrementProgress(long value){completed+=value;}
28
+ public synchronized long getProgress(){return completed;}
29
+ public synchronized void setMaximum(long value){total=value;}
30
+ public synchronized long getMaximum(){return total;}
31
+ public synchronized void setIndeterminate(boolean value){indeterminate=value;}
32
+ public synchronized boolean isIndeterminate(){return indeterminate;}
33
+ synchronized void state(String value)throws Exception{state=value;emit();}
34
+ private synchronized void emit()throws Exception{
35
+ JsonObject p=new JsonObject();p.addProperty("schema",1);p.addProperty("target",target);p.addProperty("phase",phase);p.addProperty("state",state);
36
+ p.addProperty("pid",ProcessHandle.current().pid());p.addProperty("startedAt",started);p.addProperty("updatedAt",Instant.now().toString());
37
+ p.addProperty("indeterminate",isIndeterminate());String message=Objects.toString(getMessage(),"");p.addProperty("message",message.substring(0,Math.min(message.length(),1024)));
38
+ if(!isIndeterminate()&&getMaximum()>0){p.addProperty("completed",getProgress());p.addProperty("total",getMaximum());}
39
+ InsightInitialJournal.write(root.resolve("progress.json"),p);
40
+ }
41
+ public synchronized void close()throws Exception{heartbeat.cancel();if(state.equals("running"))state=isCancelled()?"cancelled":"failed";emit();if(writeFailure!=null)throw new java.io.IOException("Progress persistence failed",writeFailure);}
42
+ }
@@ -23,6 +23,8 @@ class InsightInitialImport {
23
23
  JsonObject s=new JsonObject();s.addProperty("intentHash",InsightInitialJournal.hash(root.resolve("intent.json")));
24
24
  s.addProperty("phase","importing");s.addProperty("pid",ProcessHandle.current().pid());s.addProperty("snapshot","attempt-"+UUID.randomUUID()+".gzf");
25
25
  Path attempt=root.resolve(InsightInitialJournal.text(s,"snapshot")+".json");InsightInitialJournal.write(attempt,s);InsightInitialJournal.write(journal,s);
26
+ try(var progress=new InsightImportProgress(root,InsightInitialJournal.text(p,"target"))){
27
+ progress.phase("runtime-initialization");
26
28
  Application.initializeApplication(new GhidraApplicationLayout(),new HeadlessGhidraApplicationConfiguration());
27
29
  JsonObject settings=p.getAsJsonObject("settings"),request=new JsonObject();
28
30
  request.addProperty("input",input.toString());request.addProperty("output",root.resolve(InsightInitialJournal.text(s,"snapshot")).toString());
@@ -31,10 +33,14 @@ class InsightInitialImport {
31
33
  if(settings.has("compilerSpec"))request.add("compiler_spec",settings.get("compilerSpec"));
32
34
  if(settings.has("imageBase"))request.add("image_base",settings.get("imageBase"));
33
35
  if(settings.has("loaderOptions"))request.add("options",settings.get("loaderOptions"));
34
- JsonObject metadata=new Gson().toJsonTree(InsightSdkFiles.importProgram(request)).getAsJsonObject();
36
+ JsonObject metadata;
37
+ try{metadata=new Gson().toJsonTree(InsightSdkFiles.importProgram(request,progress)).getAsJsonObject();}
38
+ catch(Throwable failure){progress.state(progress.isCancelled()?"cancelled":"failed");throw failure;}
39
+ progress.phase("snapshot-verification");
35
40
  Path file=root.resolve(InsightInitialJournal.text(s,"snapshot"));InsightInitialJournal.sync(file);
36
41
  s.add("metadata",metadata);s.addProperty("snapshotSha256",InsightInitialJournal.hash(file));s.addProperty("phase","prepared");InsightInitialJournal.write(attempt,s);InsightInitialJournal.write(journal,s);
37
- InsightInitialJournal.prepared(root);out.println("{\"state\":\"prepared\"}");
42
+ InsightInitialJournal.prepared(root);progress.state("prepared");out.println("{\"state\":\"prepared\"}");
43
+ }
38
44
  }
39
45
  System.exit(0);
40
46
  }catch(Throwable failure){failure.printStackTrace(System.err);out.println("{\"state\":\"failed\"}");System.exit(1);}
@@ -54,14 +54,17 @@ class InsightSdkFiles {
54
54
  return Map.of("supported",rows,"all_loader_names",LoaderService.getAllLoaderNames());
55
55
  }
56
56
  }
57
- static Object importProgram(JsonObject p) throws Exception {
57
+ static void phase(TaskMonitor monitor,String phase)throws Exception{if(monitor instanceof InsightImportProgress progress)progress.phase(phase);}
58
+ static Object importProgram(JsonObject p) throws Exception {return importProgram(p,TaskMonitor.DUMMY);}
59
+ static Object importProgram(JsonObject p,TaskMonitor monitor) throws Exception {
60
+ phase(monitor,"import");
58
61
  String selected=text(p,"loader");Object consumer=new Object();Loader loader=null;
59
62
  if(selected.isEmpty()){
60
63
  Language language=text(p,"language").isEmpty()?null:DefaultLanguageService.getLanguageService().getLanguage(new LanguageID(text(p,"language")));
61
64
  CompilerSpec compiler=language==null?null:text(p,"compiler_spec").isEmpty()?language.getDefaultCompilerSpec():language.getCompilerSpecByID(new CompilerSpecID(text(p,"compiler_spec")));
62
- var loaded=language==null?AutoImporter.importByUsingBestGuess(Path.of(text(p,"input")).toFile(),null,"/",consumer,new MessageLog(),TaskMonitor.DUMMY):
63
- AutoImporter.importByLookingForLcs(Path.of(text(p,"input")).toFile(),null,"/",language,compiler,consumer,new MessageLog(),TaskMonitor.DUMMY);
64
- try{if(loaded==null||loaded.size()!=1)throw new IllegalArgumentException("Expected one imported program");return saveImport(loaded.getPrimaryDomainObject(),p,"automatic");}
65
+ var loaded=language==null?AutoImporter.importByUsingBestGuess(Path.of(text(p,"input")).toFile(),null,"/",consumer,new MessageLog(),monitor):
66
+ AutoImporter.importByLookingForLcs(Path.of(text(p,"input")).toFile(),null,"/",language,compiler,consumer,new MessageLog(),monitor);
67
+ try{if(loaded==null||loaded.size()!=1)throw new IllegalArgumentException("Expected one imported program");return saveImport(loaded.getPrimaryDomainObject(),p,"automatic",monitor);}
65
68
  finally{if(loaded!=null)loaded.release(consumer);}
66
69
  }
67
70
  for(Loader candidate:ClassSearcher.getInstances(Loader.class))if(candidate.getClass().getName().equals(selected)||candidate.getClass().getSimpleName().equals(selected)||candidate.getName().equals(selected)){
@@ -77,24 +80,24 @@ class InsightSdkFiles {
77
80
  Language effectiveLanguage=language;if(effectiveLanguage==null&&selectedSpec.getLanguageCompilerSpec()!=null)effectiveLanguage=DefaultLanguageService.getLanguageService().getLanguage(selectedSpec.getLanguageCompilerSpec().languageID);
78
81
  var options=configured(loader.getDefaultOptions(provider,selectedSpec,null,false,false),optionValues(p),effectiveLanguage==null?null:effectiveLanguage.getAddressFactory());
79
82
  String error=loader.validateOptions(provider,selectedSpec,options,null);if(error!=null)throw new IllegalArgumentException("Native loader rejected options: "+error);
80
- var settings=new Loader.ImporterSettings(provider,"OwnedInput",null,"/",false,selectedSpec,options,consumer,new MessageLog(),TaskMonitor.DUMMY);
83
+ var settings=new Loader.ImporterSettings(provider,"OwnedInput",null,"/",false,selectedSpec,options,consumer,new MessageLog(),monitor);
81
84
  var result=loader.load(settings);
82
85
  if(result==null)throw new IllegalStateException("Native loader did not return a program");
83
86
  try {
84
87
  if(result.size()!=1||!(result.getPrimaryDomainObject() instanceof Program))throw new IllegalArgumentException("Loader must produce one program per explicit target");
85
- return saveImport((Program)result.getPrimaryDomainObject(),p,loader.getClass().getName());
88
+ return saveImport((Program)result.getPrimaryDomainObject(),p,loader.getClass().getName(),monitor);
86
89
  } finally {result.release(consumer);}
87
90
  }
88
91
  }
89
- static Object saveImport(Program program,JsonObject p,String loader)throws Exception{
92
+ static Object saveImport(Program program,JsonObject p,String loader,TaskMonitor monitor)throws Exception{
90
93
  boolean analyze=p.has("auto_analyze")&&p.get("auto_analyze").getAsBoolean();
91
- if(analyze){ghidra.app.script.GhidraScriptUtil.acquireBundleHostReference();int tx=program.startTransaction("Initial analysis");boolean success=false;
92
- try{AutoAnalysisManager analysis=AutoAnalysisManager.getAnalysisManager(program);analysis.initializeOptions();analysis.reAnalyzeAll(null);analysis.startAnalysis(TaskMonitor.DUMMY);success=true;}
94
+ if(analyze){phase(monitor,"analysis");ghidra.app.script.GhidraScriptUtil.acquireBundleHostReference();int tx=program.startTransaction("Initial analysis");boolean success=false;
95
+ try{AutoAnalysisManager analysis=AutoAnalysisManager.getAnalysisManager(program);analysis.initializeOptions();analysis.reAnalyzeAll(null);analysis.startAnalysis(monitor);monitor.checkCancelled();success=true;}
93
96
  finally{program.endTransaction(tx,success);ghidra.app.script.GhidraScriptUtil.releaseBundleHostReference();}}
94
97
  if(p.has("image_base")){int tx=program.startTransaction("Initial image base");boolean success=false;
95
98
  try{program.setImageBase(program.getAddressFactory().getDefaultAddressSpace().getAddress(new BigInteger(text(p,"image_base"),16).longValue()),true);success=true;}
96
99
  finally{program.endTransaction(tx,success);}}
97
- program.saveToPackedFile(Path.of(text(p,"output")).toFile(),TaskMonitor.DUMMY);
100
+ phase(monitor,"save");program.saveToPackedFile(Path.of(text(p,"output")).toFile(),monitor);monitor.checkCancelled();
98
101
  return Map.of("state","imported","loader",loader,"language",program.getLanguageID().toString(),"base_address",program.getImageBase().toString(),
99
102
  "compiler",program.getCompilerSpec().getCompilerSpecID().toString(),"sha256",program.getExecutableSHA256(),"auto_analyzed",analyze);
100
103
  }
@@ -69,3 +69,30 @@ After an import timeout, wait for the original worker to end, raise its budget a
69
69
  the original scope. Incomplete private analysis restarts; a completed receipt is reused.
70
70
  A save timeout reports `phase: save`, the effective budget and `saveSeconds`. Inspect
71
71
  `sessions status`; a native log or broken response stream cannot prove a saved snapshot.
72
+
73
+ ## Initial import and progress
74
+
75
+ New targets import **without automatic analysis**. Reuse existing selectors/projects before
76
+ opening another copy; `ghidra open --session <session> --target <target>` preserves its bound
77
+ load settings, including older analysis choices. `--no-analysis` makes the fast choice explicit;
78
+ `--analysis` opts into a full analysis only during the first import of a new target.
79
+ After a fast import, use scoped `ghidra analysis run` to process pending analysis, or
80
+ `ghidra analysis reanalyze` to deliberately queue full reanalysis. These are saved private
81
+ mutations; merge deliberately. For a narrow question prefer bounded function disassembly,
82
+ function creation and decompilation first; inspect help for the exact operation.
83
+
84
+ For long first opens, `ghidra progress --session <session> --target <target> --json`
85
+ reads retained progress without starting Java or repeating the operation. It reports the
86
+ current stage, elapsed time, heartbeat age and native task message/counters when available;
87
+ those counters belong to the current task, not the whole import. Human output also reports
88
+ progress through Factory; `--json` keeps one final JSON result on stdout and stays quiet.
89
+ If the execution host yields, keep its same running execution and inspect progress every
90
+ 5-10 seconds when useful; relay stage changes or meaningful stalls, and do other independent
91
+ work. Do not silently extend waits to minutes or start a second import. Until selectors are
92
+ returned, use the existing `sessions list/status` and `tools processes` inventory to locate the
93
+ reserved scope. A stale heartbeat, dead worker or unknown result is not completion: inspect
94
+ `sessions status` and recovery before continuing. Pre-upgrade workers have no progress record;
95
+ null progress means unavailable, never success. Progress is observational, not a completion receipt. This command tracks first open only;
96
+ its old complete record does not describe later `analysis run`/`reanalyze` operations.
97
+ `operation: initial-model` completes after verified shared-model publication; the private
98
+ session context can still be opening. Inspect `sessions status` for context readiness.
@@ -1,10 +1,19 @@
1
1
  # Analysis storage and migration
2
2
 
3
3
  `storage show --json` reports effective profile storage, availability, active file/byte usage,
4
- exact included trees and retained migration. The default is Factory profile AppData. No
5
- checkout, environment or automatic disk fallback changes it. The non-secret setting is
6
- `analysisDirectory` in Factory's profile (`profile show` also displays it). Use storage commands
7
- to change it safely.
4
+ exact included trees, retained migration and installation default base. Before selecting a
5
+ base, storage starts in Factory profile AppData. `configure` and `migrate` select that base
6
+ for subsequent new/empty profiles as well as moving the current profile. Add `--profile-only`
7
+ to keep the installation default unchanged. The effective root appends `insight-cli/<profile>`.
8
+ Initialized profiles keep their existing locations when the default changes; profiles with
9
+ ordinary AppData analysis trees stay there until explicitly migrated.
10
+
11
+ The per-profile non-secret `analysisDirectory` setting remains in Factory's profile. The
12
+ installation default is bound storage-control metadata beside Factory's profile store, paired
13
+ with ownership metadata on the chosen base. It is not an environment or checkout setting.
14
+ Missing/replaced storage fails before creating analysis data; there is no system-disk fallback.
15
+ New profiles initialize on their first ordinary command, durably bind their own empty directory,
16
+ and keep permanent compatibility paths. `storage show` remains passive.
8
17
 
9
18
  ```sh
10
19
  insight-cli permissions grant Update
@@ -29,7 +38,20 @@ user-selected outputs outside these trees keep their locations.
29
38
 
30
39
  Factory keeps profiles, permissions, IPC election/socket, migration-control metadata and
31
40
  application logs in current-user AppData. Secrets stay in the OS keyring. Credential/profile
32
- identities do not change. Updating/installing npm never migrates a profile automatically.
41
+ identities do not change. Updating/installing npm preserves existing profile locations and does not select a disk on its own.
42
+
43
+ ## Select a default after an earlier migration
44
+
45
+ For installations migrated with an older CLI, select their existing mounted base once:
46
+
47
+ ```sh
48
+ insight-cli storage default --directory "/mnt/analysis volume" --json
49
+ ```
50
+
51
+ This Update operation changes the installation default without moving existing data. Future
52
+ profiles inherit it without repeating configuration. The base cannot be inside a profile data
53
+ root or overlap Factory control storage. A base owned by another installation is rejected.
54
+ A missing volume or replaced base/ownership marker is never adopted automatically.
33
55
 
34
56
  ## Move an existing profile
35
57
 
@@ -44,7 +66,7 @@ insight-cli storage migrate --directory "/mnt/analysis volume" --json
44
66
  insight-cli storage show --json
45
67
  ```
46
68
 
47
- Dry-run checks separation, destination contents, access, conservative capacity and quiescence
69
+ Dry-run reports whether it would select the installation default and checks separation, destination contents, access, conservative capacity and quiescence
48
70
  without reserving/copying a migration. Source/destination/configuration roots cannot be equal
49
71
  or nested. Destination symlink roots, escaping source links, special files, unresolved links
50
72
  and inventories above one million entries or 128 directory levels are rejected. Internal
@@ -72,7 +94,9 @@ file, rechecks capacity and finishes the retained transaction. Source and destin
72
94
  anchor identities are bound durably. Missing/replaced volumes fail without creating a system-disk
73
95
  fallback; mount the original volume before resuming.
74
96
 
75
- Rollback restores retained source and the prior Factory setting. After activation it requires
97
+ Rollback restores retained source and the prior Factory setting. The installation default stays
98
+ selected because subsequent profiles may already use it; a resumed older migration cannot
99
+ overwrite a newer default selection. After activation it requires
76
100
  an unchanged destination and a verified retained source. Native reopening or later findings can
77
101
  make rollback unsafe, in which case it refuses and preserves both copies. Use forward migration
78
102
  to another empty base after using the new location. A rolled-back destination remains for manual
@@ -97,3 +121,7 @@ before starting another. This release provides no nonempty-destination adoption,
97
121
  synchronization, live-DB snapshotting or network-filesystem guarantees. POSIX directory fsync
98
122
  is used. Windows has file fsync and rename/junction semantics; Node cannot fsync directories
99
123
  there, and internal symlinks can require OS privileges.
124
+
125
+ Empty-profile initialization resumes automatically on its next command after interruption. Its
126
+ retained source contains no analysis entries, so the CLI cleans only that owned empty backup
127
+ automatically. Migration of existing data always retains its source for explicit cleanup.
@@ -3,7 +3,7 @@ name: insight-artifacts
3
3
  description: Retain a completed reverse-engineering evidence file or directory as an ordinary checkpoint using insight-cli artifacts save. Use after Ghidra or any external tool; preserve existing project formats and source of truth.
4
4
  ---
5
5
 
6
- # Insight Artifacts 0.3.0
6
+ # Insight Artifacts 0.3.2
7
7
 
8
8
  If local help/status reports AppData or IPC access denied, retry that **read-only probe**
9
9
  with the agent host's normal permission escalation, then use that route for later authorized
@@ -1,10 +1,19 @@
1
1
  # Analysis storage and migration
2
2
 
3
3
  `storage show --json` reports effective profile storage, availability, active file/byte usage,
4
- exact included trees and retained migration. The default is Factory profile AppData. No
5
- checkout, environment or automatic disk fallback changes it. The non-secret setting is
6
- `analysisDirectory` in Factory's profile (`profile show` also displays it). Use storage commands
7
- to change it safely.
4
+ exact included trees, retained migration and installation default base. Before selecting a
5
+ base, storage starts in Factory profile AppData. `configure` and `migrate` select that base
6
+ for subsequent new/empty profiles as well as moving the current profile. Add `--profile-only`
7
+ to keep the installation default unchanged. The effective root appends `insight-cli/<profile>`.
8
+ Initialized profiles keep their existing locations when the default changes; profiles with
9
+ ordinary AppData analysis trees stay there until explicitly migrated.
10
+
11
+ The per-profile non-secret `analysisDirectory` setting remains in Factory's profile. The
12
+ installation default is bound storage-control metadata beside Factory's profile store, paired
13
+ with ownership metadata on the chosen base. It is not an environment or checkout setting.
14
+ Missing/replaced storage fails before creating analysis data; there is no system-disk fallback.
15
+ New profiles initialize on their first ordinary command, durably bind their own empty directory,
16
+ and keep permanent compatibility paths. `storage show` remains passive.
8
17
 
9
18
  ```sh
10
19
  insight-cli permissions grant Update
@@ -29,7 +38,20 @@ user-selected outputs outside these trees keep their locations.
29
38
 
30
39
  Factory keeps profiles, permissions, IPC election/socket, migration-control metadata and
31
40
  application logs in current-user AppData. Secrets stay in the OS keyring. Credential/profile
32
- identities do not change. Updating/installing npm never migrates a profile automatically.
41
+ identities do not change. Updating/installing npm preserves existing profile locations and does not select a disk on its own.
42
+
43
+ ## Select a default after an earlier migration
44
+
45
+ For installations migrated with an older CLI, select their existing mounted base once:
46
+
47
+ ```sh
48
+ insight-cli storage default --directory "/mnt/analysis volume" --json
49
+ ```
50
+
51
+ This Update operation changes the installation default without moving existing data. Future
52
+ profiles inherit it without repeating configuration. The base cannot be inside a profile data
53
+ root or overlap Factory control storage. A base owned by another installation is rejected.
54
+ A missing volume or replaced base/ownership marker is never adopted automatically.
33
55
 
34
56
  ## Move an existing profile
35
57
 
@@ -44,7 +66,7 @@ insight-cli storage migrate --directory "/mnt/analysis volume" --json
44
66
  insight-cli storage show --json
45
67
  ```
46
68
 
47
- Dry-run checks separation, destination contents, access, conservative capacity and quiescence
69
+ Dry-run reports whether it would select the installation default and checks separation, destination contents, access, conservative capacity and quiescence
48
70
  without reserving/copying a migration. Source/destination/configuration roots cannot be equal
49
71
  or nested. Destination symlink roots, escaping source links, special files, unresolved links
50
72
  and inventories above one million entries or 128 directory levels are rejected. Internal
@@ -72,7 +94,9 @@ file, rechecks capacity and finishes the retained transaction. Source and destin
72
94
  anchor identities are bound durably. Missing/replaced volumes fail without creating a system-disk
73
95
  fallback; mount the original volume before resuming.
74
96
 
75
- Rollback restores retained source and the prior Factory setting. After activation it requires
97
+ Rollback restores retained source and the prior Factory setting. The installation default stays
98
+ selected because subsequent profiles may already use it; a resumed older migration cannot
99
+ overwrite a newer default selection. After activation it requires
76
100
  an unchanged destination and a verified retained source. Native reopening or later findings can
77
101
  make rollback unsafe, in which case it refuses and preserves both copies. Use forward migration
78
102
  to another empty base after using the new location. A rolled-back destination remains for manual
@@ -97,3 +121,7 @@ before starting another. This release provides no nonempty-destination adoption,
97
121
  synchronization, live-DB snapshotting or network-filesystem guarantees. POSIX directory fsync
98
122
  is used. Windows has file fsync and rename/junction semantics; Node cannot fsync directories
99
123
  there, and internal symlinks can require OS privileges.
124
+
125
+ Empty-profile initialization resumes automatically on its next command after interruption. Its
126
+ retained source contains no analysis entries, so the CLI cleans only that owned empty backup
127
+ automatically. Migration of existing data always retains its source for explicit cleanup.
@@ -3,7 +3,7 @@ name: insight-binary
3
3
  description: Query structured PE, ELF and thin Mach-O headers, sections, imports and exports, or make an explicitly guarded new PE subsystem copy through insight-cli binary. Use for scoped binary structure, linkage and copy-only header edits without target execution.
4
4
  ---
5
5
 
6
- # Insight Binary 0.3.0
6
+ # Insight Binary 0.3.2
7
7
 
8
8
  If local help/status reports AppData or IPC access denied, retry that **read-only probe**
9
9
  with the agent host's normal permission escalation, then use that route for later authorized
@@ -3,7 +3,7 @@ name: insight-capture
3
3
  description: Capture one explicitly authorized native program run, exact stdout/stderr and declared file snapshots through insight-cli capture, then inspect or compare retained reports without replay. Use for controlled input/output research, preserving original files and interrupted or unknown outcomes.
4
4
  ---
5
5
 
6
- # Insight Capture 0.3.0
6
+ # Insight Capture 0.3.2
7
7
 
8
8
  If local help/status reports AppData or IPC access denied, retry that **read-only probe**
9
9
  with the agent host's normal permission escalation, then use that route for later authorized
@@ -3,7 +3,7 @@ name: insight-cli
3
3
  description: Choose and prepare reverse-engineering capabilities from the insight-cli toolbox for binary, managed-code and runtime investigations. Use to discover the appropriate tool or its packaged skill before installing native runtimes.
4
4
  ---
5
5
 
6
- # insight-cli 0.3.0
6
+ # insight-cli 0.3.2
7
7
 
8
8
  If local help/status reports AppData or IPC access denied, retry that **read-only probe**
9
9
  with the agent host's normal permission escalation, then use that route for later authorized
@@ -39,6 +39,11 @@ installing npm binaries does not register them in an agent host. See
39
39
  Inspect `storage show --json` before relocating a profile. Use the supported
40
40
  [storage workflow](references/storage.md), preserving unknown operations and native folders.
41
41
 
42
+
43
+ New targets import without full auto-analysis; `--analysis` opts in at first import.
44
+ Reuse existing session/target selectors before opening another copy. Read [setup](references/setup.md)
45
+ for explicit later analysis and passive first-open progress (`ghidra progress`).
46
+
42
47
  Use generated help as the syntax authority. Catalog metadata describes shipped capabilities;
43
48
  profile runtime readiness comes from the selected tool's `status`. Unavailable profile/IPC
44
49
  access means execution readiness is unknown. Preserve work and diagnose access before trying
@@ -0,0 +1,32 @@
1
+ # Exchange private documentation with an archive
2
+
3
+ Archive exchange is disabled until the selected Factory profile has an explicit `archiveUrl`.
4
+ Use HTTPS for a remote service; local HTTP is supported for local services. URLs cannot contain
5
+ credentials, query parameters or fragments. An optional bearer token is stored with
6
+ `auth login --role archive --password-stdin`; it is a Factory secret and is never passed to
7
+ Ghidra or the debuggee. `auth logout` clears this token as well as external server credentials.
8
+
9
+ Use `ghidra archive ingest-function --address <entry>` or `ingest-program`, with the usual
10
+ `--session` and `--target`. Both export the selected private view. `--version-label` supplies
11
+ the archive version; its default is `unknown`. Internal project directories are not used as
12
+ version labels. Bulk selection skips default function names unless `--include-default-named`
13
+ is set; `--limit` bounds visited functions. Zero selects all, subject to the documented bound.
14
+ `--dry-run` validates payloads and returns would-be counts without a network request.
15
+
16
+ The service contract is `POST <archiveUrl>/v1/doc_archive/upsert` with JSON and an optional
17
+ `Authorization: Bearer ...` header. The payload matches the pinned GhidraMCP builder: binary
18
+ name, explicit version, function address, run/tool identity, name/prototype, calling convention,
19
+ flags, matching hash/shape metadata, tags, named locals and instruction comments when present.
20
+ The binary name is the logical target name. Each successful JSON response must contain a
21
+ boolean `created` and a nonnegative integer `conflicts_enqueued`. Redirects are refused.
22
+
23
+ Results retain created, updated, conflict, skipped and error counts. Archive conflict counts
24
+ describe the external service's decisions; they do not resolve program merge conflicts.
25
+ Use an optional stable `--request-id` when a response might be lost. A repeated ID returns
26
+ the saved successful result or the retained uncertain outcome, without sending again.
27
+ Each outbound payload identity and the source snapshot are retained before dispatch. Unknown
28
+ external outcomes stay observable and do not block unrelated private research. A new request ID
29
+ expresses another explicit submission; the facade does not assume the service can deduplicate it.
30
+
31
+ Archive requests do not publish the program or alter another session. Developing or deploying
32
+ the external archive service is outside this package; acceptance uses an owned contract fixture.