@eyeauras/insight-cli 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cli-package-build.json +1 -1
- package/SKILL.md +4 -1
- package/dist/src/analysis-default.d.ts +26 -0
- package/dist/src/analysis-default.js +149 -0
- package/dist/src/analysis-storage.d.ts +39 -20
- package/dist/src/analysis-storage.js +112 -16
- package/dist/src/arguments.js +9 -4
- package/dist/src/cli.js +45 -37
- package/dist/src/code-references.d.ts +48 -0
- package/dist/src/code-references.js +154 -0
- package/dist/src/context-diagnostics.d.ts +25 -0
- package/dist/src/context-diagnostics.js +82 -0
- package/dist/src/contexts.d.ts +13 -1
- package/dist/src/contexts.js +38 -9
- package/dist/src/function-boundaries.d.ts +9 -0
- package/dist/src/function-boundaries.js +68 -0
- package/dist/src/function-entries.d.ts +33 -0
- package/dist/src/function-entries.js +84 -0
- package/dist/src/function-inspection.d.ts +4 -2
- package/dist/src/function-inspection.js +94 -59
- package/dist/src/function-results.d.ts +3 -1
- package/dist/src/function-results.js +9 -3
- package/dist/src/inline-script.d.ts +15 -0
- package/dist/src/inline-script.js +62 -0
- package/dist/src/internal-timing.d.ts +3 -0
- package/dist/src/internal-timing.js +52 -0
- package/dist/src/lifecycle.js +14 -4
- package/dist/src/native-inputs.d.ts +1 -1
- package/dist/src/native-inputs.js +1 -1
- package/dist/src/native-queries.js +1 -1
- package/dist/src/persistent-client.d.ts +30 -0
- package/dist/src/persistent-client.js +125 -0
- package/dist/src/queries.js +4 -1
- package/dist/src/query-policy.js +9 -1
- package/dist/src/research-view.d.ts +6 -0
- package/dist/src/research-view.js +9 -0
- package/dist/src/research.d.ts +38 -0
- package/dist/src/research.js +78 -8
- package/dist/src/string-anchors.d.ts +43 -0
- package/dist/src/string-anchors.js +176 -0
- package/dist/src/sync-policy.js +3 -0
- package/dist/src/timed-backend.d.ts +3 -0
- package/dist/src/timed-backend.js +16 -0
- package/dist/src/transport.d.ts +1 -0
- package/dist/src/transport.js +4 -0
- package/dist/src/workspace.js +5 -2
- package/package.json +12 -1
- package/references/native/InsightAnchorScan.java +135 -0
- package/references/native/InsightCodeReferences.java +129 -0
- package/references/native/InsightFunctionBoundaries.java +56 -0
- package/references/native/InsightInlineExecutor.java +143 -0
- package/references/native/InsightInlineScript.java +14 -0
- package/references/native/InsightPeRanges.java +92 -0
- package/references/native/InsightResearchQueries.java +22 -1
- package/references/native/InsightResearchView.java +69 -0
- package/references/native/InsightStringAnchors.java +49 -0
- package/references/persistent-client.mjs +125 -0
- package/references/persistent.md +50 -0
- package/references/query-semantics.md +12 -0
- package/references/recovery.md +36 -0
- package/references/research.md +87 -0
- package/references/setup.md +1 -1
- package/references/sparse-navigation.md +173 -0
- package/references/storage.md +35 -7
- package/runtime/GhidraMCP-7.0.0.jar +0 -0
- package/runtime/GhidraMCP-NOTICE +5 -0
- package/runtime/provenance.json +17 -3
- package/runtime/research-view.provenance.json +28 -0
- package/runtime/toolbox.json +10 -3
- package/skill-bundle/insight-artifacts/SKILL.md +1 -1
- package/skill-bundle/insight-artifacts/references/recovery.md +36 -0
- package/skill-bundle/insight-artifacts/references/storage.md +35 -7
- package/skill-bundle/insight-binary/SKILL.md +1 -1
- package/skill-bundle/insight-capture/SKILL.md +1 -1
- package/skill-bundle/insight-cli/SKILL.md +4 -1
- package/skill-bundle/insight-cli/references/persistent-client.mjs +125 -0
- package/skill-bundle/insight-cli/references/persistent.md +50 -0
- package/skill-bundle/insight-cli/references/recovery.md +36 -0
- package/skill-bundle/insight-cli/references/setup.md +1 -1
- package/skill-bundle/insight-cli/references/storage.md +35 -7
- package/skill-bundle/insight-die/SKILL.md +1 -1
- package/skill-bundle/insight-dnspy/SKILL.md +1 -1
- package/skill-bundle/insight-dotnet-dump/SKILL.md +1 -1
- package/skill-bundle/insight-dotnet-stack/SKILL.md +1 -1
- package/skill-bundle/insight-dotnet-trace/SKILL.md +1 -1
- package/skill-bundle/insight-frida/SKILL.md +1 -1
- package/skill-bundle/insight-ghidra/SKILL.md +14 -1
- package/skill-bundle/insight-ghidra/references/persistent-client.mjs +125 -0
- package/skill-bundle/insight-ghidra/references/persistent.md +50 -0
- package/skill-bundle/insight-ghidra/references/query-semantics.md +12 -0
- package/skill-bundle/insight-ghidra/references/recovery.md +36 -0
- package/skill-bundle/insight-ghidra/references/research.md +87 -0
- package/skill-bundle/insight-ghidra/references/setup.md +1 -1
- package/skill-bundle/insight-ghidra/references/sparse-navigation.md +173 -0
- package/skill-bundle/insight-ghidra/references/storage.md +35 -7
- package/skill-bundle/insight-ilspy/SKILL.md +1 -1
- package/skill-bundle/insight-interop/SKILL.md +1 -1
- package/skill-bundle/insight-lldb/SKILL.md +1 -1
- package/skill-bundle/insight-llvm/SKILL.md +1 -1
- package/skill-bundle/insight-managed-heap/SKILL.md +1 -1
- package/skill-bundle/insight-managed-metadata/SKILL.md +1 -1
- package/skill-bundle/insight-mitmproxy/SKILL.md +1 -1
- package/skill-bundle/insight-overview/SKILL.md +1 -1
- package/skill-bundle/insight-procmon/SKILL.md +1 -1
- package/skill-bundle/insight-re/SKILL.md +4 -1
- package/skill-bundle/insight-re/references/persistent-client.mjs +125 -0
- package/skill-bundle/insight-re/references/persistent.md +50 -0
- package/skill-bundle/insight-re/references/recovery.md +36 -0
- package/skill-bundle/insight-re/references/storage.md +35 -7
- package/skill-bundle/insight-sevenzip/SKILL.md +1 -1
- package/skill-bundle/insight-strace/SKILL.md +1 -1
- package/skill-bundle/insight-tshark/SKILL.md +1 -1
- package/skill-bundle/insight-windbg/SKILL.md +1 -1
- package/skill-bundle/manifest.json +1 -1
- package/skills/insight-artifacts/SKILL.md +1 -1
- package/skills/insight-artifacts/references/recovery.md +36 -0
- package/skills/insight-artifacts/references/storage.md +35 -7
- package/skills/insight-binary/SKILL.md +1 -1
- package/skills/insight-capture/SKILL.md +1 -1
- package/skills/insight-die/SKILL.md +1 -1
- package/skills/insight-dnspy/SKILL.md +1 -1
- package/skills/insight-dotnet-dump/SKILL.md +1 -1
- package/skills/insight-dotnet-stack/SKILL.md +1 -1
- package/skills/insight-dotnet-trace/SKILL.md +1 -1
- package/skills/insight-frida/SKILL.md +1 -1
- package/skills/insight-ghidra/SKILL.md +14 -1
- package/skills/insight-ghidra/references/persistent-client.mjs +125 -0
- package/skills/insight-ghidra/references/persistent.md +50 -0
- package/skills/insight-ghidra/references/query-semantics.md +12 -0
- package/skills/insight-ghidra/references/recovery.md +36 -0
- package/skills/insight-ghidra/references/research.md +87 -0
- package/skills/insight-ghidra/references/setup.md +1 -1
- package/skills/insight-ghidra/references/sparse-navigation.md +173 -0
- package/skills/insight-ghidra/references/storage.md +35 -7
- package/skills/insight-ilspy/SKILL.md +1 -1
- package/skills/insight-interop/SKILL.md +1 -1
- package/skills/insight-lldb/SKILL.md +1 -1
- package/skills/insight-llvm/SKILL.md +1 -1
- package/skills/insight-managed-heap/SKILL.md +1 -1
- package/skills/insight-managed-metadata/SKILL.md +1 -1
- package/skills/insight-mitmproxy/SKILL.md +1 -1
- package/skills/insight-overview/SKILL.md +1 -1
- package/skills/insight-procmon/SKILL.md +1 -1
- package/skills/insight-re/SKILL.md +4 -1
- package/skills/insight-re/references/persistent-client.mjs +125 -0
- package/skills/insight-re/references/persistent.md +50 -0
- package/skills/insight-re/references/recovery.md +36 -0
- package/skills/insight-re/references/storage.md +35 -7
- package/skills/insight-sevenzip/SKILL.md +1 -1
- package/skills/insight-strace/SKILL.md +1 -1
- package/skills/insight-tshark/SKILL.md +1 -1
- package/skills/insight-windbg/SKILL.md +1 -1
package/.cli-package-build.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":1,"artifact":"
|
|
1
|
+
{"version":1,"artifact":"034bd833a87d3a9c730b486252e2db09f67b3c6da2377c195389b894b26e12f7"}
|
package/SKILL.md
CHANGED
|
@@ -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.
|
|
6
|
+
# insight-cli 0.4.0
|
|
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
|
|
@@ -15,6 +15,9 @@ request/status recovery, never blind retry.
|
|
|
15
15
|
Read the project's RE instructions and existing findings first. They own tool choices,
|
|
16
16
|
evidence layout, formats and source of truth. These skills fill the remaining gaps.
|
|
17
17
|
|
|
18
|
+
For repeated research calls, retain one [persistent CLI client](references/persistent.md)
|
|
19
|
+
in the agent's long-lived runtime. Reuse it across calls to avoid repeated frontend startup.
|
|
20
|
+
|
|
18
21
|
1. Run `insight-cli tools list --json` to discover the capabilities shipped in this version.
|
|
19
22
|
The catalog works before profile configuration, downloads or IPC startup. A downloaded
|
|
20
23
|
runtime is not needed to discover what a tool does.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type CommandContext } from '@eyeauras/cli-factory';
|
|
2
|
+
type Context = Pick<CommandContext, 'profile' | 'appArguments'>;
|
|
3
|
+
export declare const analysisTrees: string[];
|
|
4
|
+
export interface StorageDefault {
|
|
5
|
+
schema: 1;
|
|
6
|
+
base: string;
|
|
7
|
+
application: string;
|
|
8
|
+
binding: string;
|
|
9
|
+
id: string;
|
|
10
|
+
baseInode: string;
|
|
11
|
+
revision: string;
|
|
12
|
+
}
|
|
13
|
+
/** Storage control metadata, independent of Factory's profile/settings document. */
|
|
14
|
+
export declare function storageDefault(c: Context): StorageDefault | undefined;
|
|
15
|
+
export declare function validateStorageDefault(c: Context, value: StorageDefault): void;
|
|
16
|
+
/** Preserve pre-existing local analysis until it is explicitly migrated. */
|
|
17
|
+
export declare function legacyAnalysis(c: Context): boolean;
|
|
18
|
+
export declare function inheritedStorage(c: Context): StorageDefault | undefined;
|
|
19
|
+
export declare function checkStorageDefaultBase(c: Context, base: string): Promise<void>;
|
|
20
|
+
export declare function writeStorageDefault(c: Context, base: string, revision?: string, expected?: string | null): Promise<{
|
|
21
|
+
state: 'configured' | 'superseded';
|
|
22
|
+
base: string | null;
|
|
23
|
+
revision: string | null;
|
|
24
|
+
binding?: StorageDefault;
|
|
25
|
+
}>;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { CliError } from '@eyeauras/cli-factory';
|
|
2
|
+
import { lstatSync, readFileSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { link, lstat, open, realpath, unlink } from 'node:fs/promises';
|
|
4
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
6
|
+
import { digest } from './model.js';
|
|
7
|
+
import { atomicWrite, withToolLock } from './tool-runtime/store.js';
|
|
8
|
+
export const analysisTrees = ['workspace', 'materials', 'temp', 'runtime'];
|
|
9
|
+
const uuid = (value) => typeof value === 'string' && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(value);
|
|
10
|
+
const absent = (error) => error.code === 'ENOENT';
|
|
11
|
+
function fail(code, message) { throw new CliError(message, { code: 'storage.' + code }); }
|
|
12
|
+
const policyPath = (c) => join(c.appArguments.RoamingAppDataDirectory, '.analysis-storage-default.json');
|
|
13
|
+
const markerPath = (base) => join(base, '.insight-storage-root.json');
|
|
14
|
+
const binding = (c) => digest(realpathSync.native(c.appArguments.RoamingAppDataDirectory));
|
|
15
|
+
function readOwnedJson(path) {
|
|
16
|
+
if (!lstatSync(path).isFile())
|
|
17
|
+
fail('configuration', 'Storage default metadata must be an ordinary file.');
|
|
18
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
19
|
+
}
|
|
20
|
+
function valid(c, value) {
|
|
21
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
22
|
+
return false;
|
|
23
|
+
const v = value;
|
|
24
|
+
return v.schema === 1 && v.application === c.appArguments.AppName && v.binding === binding(c) && uuid(v.id) &&
|
|
25
|
+
typeof v.base === 'string' && isAbsolute(v.base) && v.base === resolve(v.base) && !/[\0\r\n]/.test(v.base) &&
|
|
26
|
+
typeof v.baseInode === 'string' && /^\d+$/.test(v.baseInode);
|
|
27
|
+
}
|
|
28
|
+
/** Storage control metadata, independent of Factory's profile/settings document. */
|
|
29
|
+
export function storageDefault(c) {
|
|
30
|
+
let value;
|
|
31
|
+
try {
|
|
32
|
+
value = readOwnedJson(policyPath(c));
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (absent(error))
|
|
36
|
+
return;
|
|
37
|
+
fail('configuration', 'Installation storage default is unreadable; preserve its control metadata.');
|
|
38
|
+
}
|
|
39
|
+
if (!valid(c, value) || !uuid(value.revision))
|
|
40
|
+
fail('configuration', 'Installation storage default failed its application binding.');
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
export function validateStorageDefault(c, value) {
|
|
44
|
+
try {
|
|
45
|
+
if (!valid(c, value) || !uuid(value.revision))
|
|
46
|
+
throw Error('binding');
|
|
47
|
+
const st = lstatSync(value.base, { bigint: true }), marker = readOwnedJson(markerPath(value.base));
|
|
48
|
+
if (!st.isDirectory() || realpathSync.native(value.base) !== value.base || String(st.ino) !== value.baseInode ||
|
|
49
|
+
!valid(c, marker) || marker.id !== value.id || marker.base !== value.base || marker.baseInode !== value.baseInode)
|
|
50
|
+
throw Error('binding');
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
fail('unavailable', 'Installation analysis storage is missing or changed; mount its original volume. No fallback was created.');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Preserve pre-existing local analysis until it is explicitly migrated. */
|
|
57
|
+
export function legacyAnalysis(c) {
|
|
58
|
+
return analysisTrees.some(tree => { try {
|
|
59
|
+
return !lstatSync(join(c.appArguments.AppDataDirectory, tree)).isSymbolicLink();
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (absent(error))
|
|
63
|
+
return false;
|
|
64
|
+
throw error;
|
|
65
|
+
} });
|
|
66
|
+
}
|
|
67
|
+
export function inheritedStorage(c) {
|
|
68
|
+
if (c.profile.values.analysisDirectory !== undefined || legacyAnalysis(c))
|
|
69
|
+
return;
|
|
70
|
+
return storageDefault(c);
|
|
71
|
+
}
|
|
72
|
+
export async function checkStorageDefaultBase(c, base) {
|
|
73
|
+
let marker;
|
|
74
|
+
try {
|
|
75
|
+
marker = readOwnedJson(markerPath(base));
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (absent(error))
|
|
79
|
+
return;
|
|
80
|
+
fail('binding', 'Storage base ownership metadata is unreadable; preserve it.');
|
|
81
|
+
}
|
|
82
|
+
if (!valid(c, marker) || marker.base !== base)
|
|
83
|
+
fail('binding', 'Storage base belongs to another application/control root; no data was adopted.');
|
|
84
|
+
const st = await lstat(base, { bigint: true });
|
|
85
|
+
if (!st.isDirectory() || await realpath(base) !== base || String(st.ino) !== marker.baseInode)
|
|
86
|
+
fail('binding', 'Storage base directory was replaced.');
|
|
87
|
+
}
|
|
88
|
+
export async function writeStorageDefault(c, base, revision = randomUUID(), expected) {
|
|
89
|
+
return withToolLock(c.appArguments.RoamingAppDataDirectory, 'analysis-storage-default', async () => {
|
|
90
|
+
const current = storageDefault(c);
|
|
91
|
+
if (current?.revision === revision) {
|
|
92
|
+
if (current.base !== base)
|
|
93
|
+
fail('binding', 'Storage default intent changed.');
|
|
94
|
+
validateStorageDefault(c, current);
|
|
95
|
+
return { state: 'configured', base, revision, binding: current };
|
|
96
|
+
}
|
|
97
|
+
// A resumed migration must never overwrite a later user's default selection.
|
|
98
|
+
if (expected !== undefined && (current?.revision ?? null) !== expected)
|
|
99
|
+
return { state: 'superseded', base: current?.base ?? null, revision: current?.revision ?? null };
|
|
100
|
+
const st = await lstat(base, { bigint: true });
|
|
101
|
+
if (!st.isDirectory() || await realpath(base) !== base)
|
|
102
|
+
fail('path', 'Choose an existing ordinary storage base directory.');
|
|
103
|
+
await checkStorageDefaultBase(c, base);
|
|
104
|
+
let marker;
|
|
105
|
+
try {
|
|
106
|
+
marker = readOwnedJson(markerPath(base));
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
if (!absent(error))
|
|
110
|
+
throw error;
|
|
111
|
+
marker = { schema: 1, base, application: c.appArguments.AppName, binding: binding(c), id: randomUUID(), baseInode: String(st.ino) };
|
|
112
|
+
const temporary = markerPath(base) + '.new-' + randomUUID(), file = await open(temporary, 'wx', 0o600);
|
|
113
|
+
try {
|
|
114
|
+
try {
|
|
115
|
+
await file.writeFile(JSON.stringify(marker) + '\n');
|
|
116
|
+
await file.sync();
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
await file.close();
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
await link(temporary, markerPath(base));
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
if (e.code !== 'EEXIST')
|
|
126
|
+
throw e;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
await unlink(temporary);
|
|
131
|
+
}
|
|
132
|
+
await checkStorageDefaultBase(c, base);
|
|
133
|
+
marker = readOwnedJson(markerPath(base));
|
|
134
|
+
}
|
|
135
|
+
const next = { ...marker, revision };
|
|
136
|
+
validateStorageDefault(c, next);
|
|
137
|
+
if (process.platform !== 'win32') {
|
|
138
|
+
const dir = await open(base, 'r');
|
|
139
|
+
try {
|
|
140
|
+
await dir.sync();
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
await dir.close();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
await atomicWrite(policyPath(c), next);
|
|
147
|
+
return { state: 'configured', base, revision, binding: next };
|
|
148
|
+
}, c.signal);
|
|
149
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type CommandContext, type CommandDefinition, type ProfileStoreContract } from '@eyeauras/cli-factory';
|
|
2
|
-
|
|
2
|
+
import { type StorageDefault } from './analysis-default.js';
|
|
3
|
+
export { analysisTrees } from './analysis-default.js';
|
|
3
4
|
export declare function analysisDirectory(c: Pick<CommandContext, 'profile' | 'appArguments'>): string;
|
|
4
5
|
/** Factory remains the only owner of profiles, settings, keyring and IPC admission. */
|
|
5
6
|
export declare class AnalysisStorage {
|
|
@@ -16,6 +17,16 @@ export declare class AnalysisStorage {
|
|
|
16
17
|
private save;
|
|
17
18
|
private validate;
|
|
18
19
|
wrap(entry: CommandDefinition): CommandDefinition;
|
|
20
|
+
private prepare;
|
|
21
|
+
private defaultBase;
|
|
22
|
+
setDefault(c: CommandContext, directory: unknown): Promise<{
|
|
23
|
+
defaultDirectory: string;
|
|
24
|
+
appliesTo: string;
|
|
25
|
+
state: "configured" | "superseded";
|
|
26
|
+
base: string | null;
|
|
27
|
+
revision: string | null;
|
|
28
|
+
binding?: StorageDefault;
|
|
29
|
+
}>;
|
|
19
30
|
busy(directory: string): boolean;
|
|
20
31
|
private exclusive;
|
|
21
32
|
private preflight;
|
|
@@ -23,6 +34,7 @@ export declare class AnalysisStorage {
|
|
|
23
34
|
profile: string;
|
|
24
35
|
directory: string;
|
|
25
36
|
configuration: string;
|
|
37
|
+
defaultBaseDirectory: string | null;
|
|
26
38
|
defaultDirectory: string;
|
|
27
39
|
availability: string;
|
|
28
40
|
usage: unknown;
|
|
@@ -43,10 +55,20 @@ export declare class AnalysisStorage {
|
|
|
43
55
|
progress: unknown;
|
|
44
56
|
} | null;
|
|
45
57
|
}>;
|
|
46
|
-
migrate(c: CommandContext, directory: unknown, dryRun?: boolean, emptyOnly?: boolean): Promise<{
|
|
58
|
+
migrate(c: CommandContext, directory: unknown, dryRun?: boolean, emptyOnly?: boolean, profileOnly?: boolean, automatic?: StorageDefault): Promise<{
|
|
59
|
+
state: "rolled_back";
|
|
60
|
+
id: string;
|
|
61
|
+
directory: string;
|
|
62
|
+
destinationRetained: string;
|
|
63
|
+
} | {
|
|
64
|
+
state: "cleaned";
|
|
65
|
+
id: string;
|
|
66
|
+
compatibilityPathsRetained: boolean;
|
|
67
|
+
} | {
|
|
47
68
|
profile: string;
|
|
48
69
|
directory: string;
|
|
49
70
|
configuration: string;
|
|
71
|
+
defaultBaseDirectory: string | null;
|
|
50
72
|
defaultDirectory: string;
|
|
51
73
|
availability: string;
|
|
52
74
|
usage: unknown;
|
|
@@ -66,21 +88,14 @@ export declare class AnalysisStorage {
|
|
|
66
88
|
retainedSourceBytes: number;
|
|
67
89
|
progress: unknown;
|
|
68
90
|
} | null;
|
|
69
|
-
} | {
|
|
70
|
-
state: "rolled_back";
|
|
71
|
-
id: string;
|
|
72
|
-
directory: string;
|
|
73
|
-
destinationRetained: string;
|
|
74
|
-
} | {
|
|
75
|
-
state: "cleaned";
|
|
76
|
-
id: string;
|
|
77
|
-
compatibilityPathsRetained: boolean;
|
|
78
91
|
} | {
|
|
79
92
|
state: "committed" | "rolled_back" | "cleaned";
|
|
80
93
|
id: string;
|
|
81
94
|
directory: string;
|
|
82
95
|
manifest: string;
|
|
83
96
|
sourceRetained: boolean;
|
|
97
|
+
defaultBaseDirectory: string | null;
|
|
98
|
+
installationDefault: string;
|
|
84
99
|
rollback: string;
|
|
85
100
|
cleanup: string;
|
|
86
101
|
} | {
|
|
@@ -96,13 +111,24 @@ export declare class AnalysisStorage {
|
|
|
96
111
|
sourceRetained: boolean;
|
|
97
112
|
compatibilityPathsRetained: boolean;
|
|
98
113
|
state: string;
|
|
114
|
+
setsInstallationDefault: boolean;
|
|
99
115
|
}>;
|
|
100
116
|
private checkCopy;
|
|
101
117
|
private continue;
|
|
102
118
|
resume(c: CommandContext): Promise<{
|
|
119
|
+
state: "rolled_back";
|
|
120
|
+
id: string;
|
|
121
|
+
directory: string;
|
|
122
|
+
destinationRetained: string;
|
|
123
|
+
} | {
|
|
124
|
+
state: "cleaned";
|
|
125
|
+
id: string;
|
|
126
|
+
compatibilityPathsRetained: boolean;
|
|
127
|
+
} | {
|
|
103
128
|
profile: string;
|
|
104
129
|
directory: string;
|
|
105
130
|
configuration: string;
|
|
131
|
+
defaultBaseDirectory: string | null;
|
|
106
132
|
defaultDirectory: string;
|
|
107
133
|
availability: string;
|
|
108
134
|
usage: unknown;
|
|
@@ -122,21 +148,14 @@ export declare class AnalysisStorage {
|
|
|
122
148
|
retainedSourceBytes: number;
|
|
123
149
|
progress: unknown;
|
|
124
150
|
} | null;
|
|
125
|
-
} | {
|
|
126
|
-
state: "rolled_back";
|
|
127
|
-
id: string;
|
|
128
|
-
directory: string;
|
|
129
|
-
destinationRetained: string;
|
|
130
|
-
} | {
|
|
131
|
-
state: "cleaned";
|
|
132
|
-
id: string;
|
|
133
|
-
compatibilityPathsRetained: boolean;
|
|
134
151
|
} | {
|
|
135
152
|
state: "committed" | "rolled_back" | "cleaned";
|
|
136
153
|
id: string;
|
|
137
154
|
directory: string;
|
|
138
155
|
manifest: string;
|
|
139
156
|
sourceRetained: boolean;
|
|
157
|
+
defaultBaseDirectory: string | null;
|
|
158
|
+
installationDefault: string;
|
|
140
159
|
rollback: string;
|
|
141
160
|
cleanup: string;
|
|
142
161
|
}>;
|
|
@@ -7,10 +7,11 @@ import { atomicWrite, readJson, toolProfileBusy } from './tool-runtime/store.js'
|
|
|
7
7
|
import { listProcesses } from './tool-runtime/supervisor.js';
|
|
8
8
|
import { runtimeProcesses } from './runtime-manager.js';
|
|
9
9
|
import { digest } from './model.js';
|
|
10
|
+
import { analysisTrees, inheritedStorage, legacyAnalysis, storageDefault, validateStorageDefault, checkStorageDefaultBase, writeStorageDefault } from './analysis-default.js';
|
|
11
|
+
export { analysisTrees } from './analysis-default.js';
|
|
10
12
|
// Move the complete runtime tree too: installation stages must share a filesystem with
|
|
11
13
|
// installations, and both native checks and process runs can contain large generated DBs.
|
|
12
14
|
// Stable aliases preserve references in immutable native journals, selections and results.
|
|
13
|
-
export const analysisTrees = ['workspace', 'materials', 'temp', 'runtime'];
|
|
14
15
|
const terminal = (state) => ['committed', 'rolled_back', 'cleaned'].includes(state);
|
|
15
16
|
const inside = (parent, child) => { const r = relative(parent, child); return r === '' || !isAbsolute(r) && r !== '..' && !r.startsWith('..' + sep); };
|
|
16
17
|
function fail(code, message) { throw new CliError(message, { code: 'storage.' + code }); }
|
|
@@ -37,8 +38,12 @@ async function sync(path, directory = false) {
|
|
|
37
38
|
}
|
|
38
39
|
export function analysisDirectory(c) {
|
|
39
40
|
const value = c.profile.values.analysisDirectory;
|
|
40
|
-
if (value ===
|
|
41
|
+
if (value === null)
|
|
41
42
|
return c.appArguments.AppDataDirectory;
|
|
43
|
+
if (value === undefined) {
|
|
44
|
+
const shared = storageDefault(c);
|
|
45
|
+
return shared && !legacyAnalysis(c) ? join(shared.base, c.appArguments.AppName, c.profile.name) : c.appArguments.AppDataDirectory;
|
|
46
|
+
}
|
|
42
47
|
if (typeof value !== 'string' || !isAbsolute(value) || value !== resolve(value))
|
|
43
48
|
fail('configuration', 'Invalid analysis directory in the Factory profile.');
|
|
44
49
|
return value;
|
|
@@ -140,6 +145,9 @@ export class AnalysisStorage {
|
|
|
140
145
|
!isAbsolute(j.source) || !isAbsolute(j.destination) || inside(j.source, j.destination) || inside(j.destination, j.source) ||
|
|
141
146
|
!isAbsolute(j.destinationAnchor) || !inside(j.destinationAnchor, j.destination) || typeof j.sourceIdentity !== 'string' || typeof j.destinationIdentity !== 'string' ||
|
|
142
147
|
!['copying', 'verified', 'activating', 'committed', 'rolling_back', 'rolled_back', 'cleaning', 'cleaned'].includes(j.state) ||
|
|
148
|
+
['automatic', 'makeDefault', 'defaultApplied', 'defaultSuperseded'].some(k => { const v = j[k]; return v !== undefined && typeof v !== 'boolean'; }) ||
|
|
149
|
+
j.defaultPreviousRevision !== undefined && j.defaultPreviousRevision !== null && !/^[-a-f0-9]{36}$/.test(j.defaultPreviousRevision) ||
|
|
150
|
+
j.automatic && (!j.defaultBinding || j.makeDefault || j.files !== 0 || j.bytes !== 0 || loadEntries && j.entries.length !== 0) ||
|
|
143
151
|
!Array.isArray(j.trees) || j.trees.some(t => !analysisTrees.includes(t)) || !Array.isArray(j.entries) ||
|
|
144
152
|
j.entries.some(e => !j.trees.some(t => e.path === t || e.path.startsWith(t + '/')) || e.path.split('/').some(p => p === '..' || p === '.' || p === '')) ||
|
|
145
153
|
!/^[a-f0-9]{64}$/.test(j.manifest) || loadEntries && j.manifest !== digest(content(j.entries)))
|
|
@@ -153,7 +161,7 @@ export class AnalysisStorage {
|
|
|
153
161
|
const { entries, ...record } = j;
|
|
154
162
|
await atomicWrite(join(this.control(c), 'migration.json'), record);
|
|
155
163
|
}
|
|
156
|
-
async validate(c) {
|
|
164
|
+
async validate(c, initializing = false) {
|
|
157
165
|
const root = analysisDirectory(c), j = await this.journal(c);
|
|
158
166
|
if (j && !terminal(j.state))
|
|
159
167
|
fail('pending', 'Migration is incomplete. Use storage show, resume or rollback before research.');
|
|
@@ -161,6 +169,12 @@ export class AnalysisStorage {
|
|
|
161
169
|
fail('configuration', 'Factory profile and committed migration differ; preserve both and inspect storage show.');
|
|
162
170
|
if (root === c.appArguments.AppDataDirectory)
|
|
163
171
|
return;
|
|
172
|
+
const shared = inheritedStorage(c);
|
|
173
|
+
if (shared && !j) {
|
|
174
|
+
if (!initializing)
|
|
175
|
+
validateStorageDefault(c, shared);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
164
178
|
const marker = await readJson(join(root, '.insight-analysis.json'));
|
|
165
179
|
if (!marker || marker.schema !== 1 || marker.binding !== await this.binding(c) || await realpath(root) !== root)
|
|
166
180
|
fail('unavailable', 'Configured analysis storage is missing or changed; mount its volume. No fallback was created.');
|
|
@@ -175,12 +189,15 @@ export class AnalysisStorage {
|
|
|
175
189
|
const run = entry.run;
|
|
176
190
|
return { ...entry, ...(entry.children ? { children: entry.children.map(e => this.wrap(e)) } : {}), ...(run ? { run: async (input, c) => {
|
|
177
191
|
const key = c.appArguments.AppDataDirectory;
|
|
192
|
+
if (this.changing.has(key))
|
|
193
|
+
fail('busy', 'Storage migration is in progress.');
|
|
194
|
+
const prepared = await this.prepare(c);
|
|
178
195
|
if (this.changing.has(key))
|
|
179
196
|
fail('busy', 'Storage migration is in progress.');
|
|
180
197
|
this.active.set(key, (this.active.get(key) ?? 0) + 1);
|
|
181
198
|
try {
|
|
182
|
-
await this.validate(
|
|
183
|
-
return await run(input,
|
|
199
|
+
await this.validate(prepared);
|
|
200
|
+
return await run(input, prepared);
|
|
184
201
|
}
|
|
185
202
|
finally {
|
|
186
203
|
const n = this.active.get(key) - 1;
|
|
@@ -191,6 +208,42 @@ export class AnalysisStorage {
|
|
|
191
208
|
}
|
|
192
209
|
} } : {}) };
|
|
193
210
|
}
|
|
211
|
+
async prepare(c) {
|
|
212
|
+
const j = await this.journal(c), shared = inheritedStorage(c);
|
|
213
|
+
if (j?.automatic && !['cleaned', 'rolled_back'].includes(j.state)) {
|
|
214
|
+
await this.resume(c);
|
|
215
|
+
c = { ...c, profile: await this.store(c).get(c.profile.name) };
|
|
216
|
+
}
|
|
217
|
+
else if (!j && shared) {
|
|
218
|
+
validateStorageDefault(c, shared);
|
|
219
|
+
await this.migrate(c, shared.base, false, true, true, shared);
|
|
220
|
+
c = { ...c, profile: await this.store(c).get(c.profile.name) };
|
|
221
|
+
}
|
|
222
|
+
// One command keeps its selected root even if another profile changes the default.
|
|
223
|
+
return { ...c, profile: { ...c.profile, values: { ...c.profile.values, analysisDirectory: analysisDirectory(c) } } };
|
|
224
|
+
}
|
|
225
|
+
async defaultBase(c, directory, existingOnly = false) {
|
|
226
|
+
if (typeof directory !== 'string' || !isAbsolute(directory) || /[\0\r\n]/.test(directory))
|
|
227
|
+
fail('path', 'Supply an absolute destination base directory.');
|
|
228
|
+
const base = await canonicalFuture(resolve(directory)), app = await canonicalFuture(c.appArguments.RoamingAppDataDirectory);
|
|
229
|
+
if (inside(app, base) || inside(base, app))
|
|
230
|
+
fail('path', 'The installation storage base and Factory configuration root must be separate, non-nested directories.');
|
|
231
|
+
for (const profile of (await this.store(c).list()).profiles) {
|
|
232
|
+
if (typeof profile.values.analysisDirectory !== 'string')
|
|
233
|
+
continue;
|
|
234
|
+
const root = await canonicalFuture(profile.values.analysisDirectory);
|
|
235
|
+
if (inside(root, base))
|
|
236
|
+
fail('path', 'The installation storage base cannot be nested inside a profile analysis directory.');
|
|
237
|
+
}
|
|
238
|
+
if (existingOnly && !await present(base))
|
|
239
|
+
fail('path', 'Choose an existing mounted storage base directory.');
|
|
240
|
+
await checkStorageDefaultBase(c, base);
|
|
241
|
+
return base;
|
|
242
|
+
}
|
|
243
|
+
async setDefault(c, directory) {
|
|
244
|
+
const base = await this.defaultBase(c, directory, true), result = await writeStorageDefault(c, base);
|
|
245
|
+
return { ...result, defaultDirectory: join(base, c.appArguments.AppName, c.profile.name), appliesTo: 'new and empty profiles; initialized profile locations are preserved' };
|
|
246
|
+
}
|
|
194
247
|
busy(directory) { return this.changing.has(directory); }
|
|
195
248
|
async exclusive(c, action) {
|
|
196
249
|
const key = c.appArguments.AppDataDirectory;
|
|
@@ -212,7 +265,7 @@ export class AnalysisStorage {
|
|
|
212
265
|
async preflight(c, directory) {
|
|
213
266
|
if (typeof directory !== 'string' || !isAbsolute(directory) || /[\0\r\n]/.test(directory))
|
|
214
267
|
fail('path', 'Supply an absolute destination base directory.');
|
|
215
|
-
const source = await canonicalFuture(analysisDirectory(c)), destination = await canonicalFuture(join(resolve(directory), c.appArguments.AppName, c.profile.name));
|
|
268
|
+
const source = await canonicalFuture(inheritedStorage(c) ? c.appArguments.AppDataDirectory : analysisDirectory(c)), destination = await canonicalFuture(join(resolve(directory), c.appArguments.AppName, c.profile.name));
|
|
216
269
|
const app = await canonicalFuture(c.appArguments.RoamingAppDataDirectory);
|
|
217
270
|
if (inside(source, destination) || inside(destination, source) || inside(app, destination) || inside(destination, app))
|
|
218
271
|
fail('path', 'Source, destination and profile configuration roots must be separate, non-nested directories.');
|
|
@@ -235,7 +288,7 @@ export class AnalysisStorage {
|
|
|
235
288
|
return { source, destination, destinationAnchor: parent, destinationIdentity: await directoryIdentity(parent), bytes, entries: entries.length, availableBytes: available.toString(), requiredBytes: (BigInt(bytes) + reserve).toString(), trees: analysisTrees, sourceRetained: true, compatibilityPathsRetained: true };
|
|
236
289
|
}
|
|
237
290
|
async show(c) {
|
|
238
|
-
const j = await this.journal(c), root = analysisDirectory(c);
|
|
291
|
+
const j = await this.journal(c), root = analysisDirectory(c), shared = storageDefault(c);
|
|
239
292
|
let usage, availability = 'available';
|
|
240
293
|
try {
|
|
241
294
|
await this.validate(c);
|
|
@@ -245,23 +298,34 @@ export class AnalysisStorage {
|
|
|
245
298
|
catch (error) {
|
|
246
299
|
availability = error instanceof CliError ? error.code : (error.code ?? 'unavailable');
|
|
247
300
|
}
|
|
248
|
-
return { profile: c.profile.name, directory: root, configuration: 'Factory profile analysisDirectory', defaultDirectory: c.appArguments.AppDataDirectory, availability, usage,
|
|
301
|
+
return { profile: c.profile.name, directory: root, configuration: c.profile.values.analysisDirectory !== undefined ? 'Factory profile analysisDirectory' : shared && root !== c.appArguments.AppDataDirectory ? 'Installation storage default' : 'Factory profile AppData', defaultBaseDirectory: shared?.base ?? null, defaultDirectory: shared ? join(shared.base, c.appArguments.AppName, c.profile.name) : c.appArguments.AppDataDirectory, availability, usage,
|
|
249
302
|
trees: analysisTrees, appData: c.appArguments.AppDataDirectory, included: ['snapshots, immutable inputs, native projects and local server repositories', 'tool execution/verification working data', 'runtime installations, acquisition caches, selections and process receipts'], excluded: ['profiles, permissions, IPC, logs and OS keyring secrets'],
|
|
250
303
|
migration: j ? { id: j.id, state: j.state, source: j.source, destination: j.destination, manifest: j.manifest, bytes: j.bytes, files: j.files, retainedSource: join(j.source, '.storage-retained', j.id), retainedSourceBytes: j.state === 'committed' ? j.bytes : 0, progress: await readJson(join(this.control(c), 'progress.json')) } : null };
|
|
251
304
|
}
|
|
252
|
-
async migrate(c, directory, dryRun = false, emptyOnly = false) {
|
|
305
|
+
async migrate(c, directory, dryRun = false, emptyOnly = false, profileOnly = false, automatic) {
|
|
253
306
|
return this.exclusive(c, async () => {
|
|
254
|
-
await this.validate(c);
|
|
307
|
+
await this.validate(c, true);
|
|
308
|
+
if (automatic)
|
|
309
|
+
validateStorageDefault(c, automatic);
|
|
255
310
|
const plan = await this.preflight(c, directory);
|
|
311
|
+
if (automatic) {
|
|
312
|
+
validateStorageDefault(c, automatic);
|
|
313
|
+
if (plan.destination !== join(automatic.base, c.appArguments.AppName, c.profile.name))
|
|
314
|
+
fail('binding', 'Automatic initialization changed its destination.');
|
|
315
|
+
}
|
|
316
|
+
if (!profileOnly)
|
|
317
|
+
await this.defaultBase(c, directory);
|
|
256
318
|
if (dryRun)
|
|
257
|
-
return { state: 'preflight', ...plan };
|
|
319
|
+
return { state: 'preflight', setsInstallationDefault: !profileOnly, ...plan };
|
|
258
320
|
if (emptyOnly && plan.entries)
|
|
259
321
|
fail('data', 'This profile already has analysis data. Use storage migrate --directory to preserve it.');
|
|
260
322
|
const entries = await inventory(plan.source, analysisTrees, true, c.signal), id = randomUUID();
|
|
323
|
+
if (emptyOnly && entries.length)
|
|
324
|
+
fail('data', 'Analysis appeared during empty-profile initialization; no migration was started.');
|
|
261
325
|
const binding = await this.binding(c);
|
|
262
326
|
const j = { schema: 1, id, profile: c.profile.name, binding, source: plan.source, destination: plan.destination,
|
|
263
327
|
sourceIdentity: await directoryIdentity(plan.source), destinationAnchor: plan.destinationAnchor, destinationIdentity: plan.destinationIdentity,
|
|
264
|
-
previous: typeof c.profile.values.analysisDirectory === 'string' ? c.profile.values.analysisDirectory : null, state: 'copying', entries, trees: [...analysisTrees], manifest: digest(content(entries)), createdAt: new Date().toISOString(), bytes: entries.reduce((n, e) => n + e.size, 0), files: entries.filter(e => e.kind === 'file').length };
|
|
328
|
+
previous: typeof c.profile.values.analysisDirectory === 'string' ? c.profile.values.analysisDirectory : null, automatic: Boolean(automatic), defaultBinding: automatic, makeDefault: !profileOnly, defaultPreviousRevision: storageDefault(c)?.revision ?? null, state: 'copying', entries, trees: [...analysisTrees], manifest: digest(content(entries)), createdAt: new Date().toISOString(), bytes: entries.reduce((n, e) => n + e.size, 0), files: entries.filter(e => e.kind === 'file').length };
|
|
265
329
|
// Reserve durably before any destination or source changes. All ordinary commands now freeze.
|
|
266
330
|
await this.save(c, j);
|
|
267
331
|
await this.checkpoint?.('reserved');
|
|
@@ -282,12 +346,35 @@ export class AnalysisStorage {
|
|
|
282
346
|
fail('volume', 'A migration volume changed or disappeared. Mount the original volume before resuming; no fallback was created.');
|
|
283
347
|
if (await canonicalFuture(j.destination) !== j.destination || j.targetIdentity && await directoryIdentity(j.destination) !== j.targetIdentity)
|
|
284
348
|
fail('binding', 'Destination directory was replaced; no migration write was made.');
|
|
349
|
+
if (j.defaultBinding)
|
|
350
|
+
validateStorageDefault(c, j.defaultBinding);
|
|
351
|
+
if (j.defaultBaseIdentity && await directoryIdentity(dirname(dirname(j.destination))) !== j.defaultBaseIdentity)
|
|
352
|
+
fail('volume', 'The selected storage base directory was replaced.');
|
|
285
353
|
if (j.state === 'rolling_back')
|
|
286
354
|
return this.restore(c, j);
|
|
287
355
|
if (j.state === 'cleaning')
|
|
288
356
|
return this.finishCleanup(c, j);
|
|
289
|
-
if (terminal(j.state))
|
|
357
|
+
if (terminal(j.state) && !(j.automatic && j.state === 'committed'))
|
|
290
358
|
return this.show(c);
|
|
359
|
+
if (j.makeDefault && !j.defaultApplied) {
|
|
360
|
+
const base = dirname(dirname(j.destination));
|
|
361
|
+
if (await canonicalFuture(base) !== base)
|
|
362
|
+
fail('binding', 'Storage default base was replaced.');
|
|
363
|
+
await mkdir(base, { recursive: true, mode: 0o700 });
|
|
364
|
+
if (await directoryIdentity(j.destinationAnchor) !== j.destinationIdentity)
|
|
365
|
+
fail('volume', 'The default storage volume changed.');
|
|
366
|
+
if (!j.defaultBaseIdentity) {
|
|
367
|
+
j.defaultBaseIdentity = await directoryIdentity(base);
|
|
368
|
+
await this.save(c, j);
|
|
369
|
+
}
|
|
370
|
+
const result = await writeStorageDefault(c, base, j.id, j.defaultPreviousRevision ?? null);
|
|
371
|
+
await this.checkpoint?.('default-selected');
|
|
372
|
+
j.defaultApplied = true;
|
|
373
|
+
j.defaultSuperseded = result.state === 'superseded';
|
|
374
|
+
if (!j.defaultSuperseded)
|
|
375
|
+
j.defaultBinding = result.binding;
|
|
376
|
+
await this.save(c, j);
|
|
377
|
+
}
|
|
291
378
|
if (j.state === 'copying') {
|
|
292
379
|
await privateDirectory(j.destination);
|
|
293
380
|
if (!j.targetIdentity) {
|
|
@@ -439,7 +526,14 @@ export class AnalysisStorage {
|
|
|
439
526
|
await this.save(c, j);
|
|
440
527
|
await atomicWrite(join(this.control(c), 'progress.json'), { completed: j.entries.length, total: j.entries.length, phase: 'committed' });
|
|
441
528
|
}
|
|
442
|
-
|
|
529
|
+
if (j.automatic && j.state === 'committed') {
|
|
530
|
+
if (j.entries.length)
|
|
531
|
+
fail('binding', 'Automatic profile initialization cannot delete existing analysis.');
|
|
532
|
+
j.state = 'cleaning';
|
|
533
|
+
await this.save(c, j);
|
|
534
|
+
return this.finishCleanup(c, j);
|
|
535
|
+
}
|
|
536
|
+
return { state: j.state, id: j.id, directory: j.destination, manifest: j.manifest, sourceRetained: true, defaultBaseDirectory: storageDefault(c)?.base ?? null, installationDefault: j.makeDefault ? (j.defaultSuperseded ? 'superseded' : 'configured') : 'unchanged', rollback: 'storage rollback', cleanup: 'storage cleanup' };
|
|
443
537
|
}
|
|
444
538
|
async resume(c) { return this.exclusive(c, async () => { const j = await this.journal(c, true); if (!j)
|
|
445
539
|
fail('missing', 'No migration journal exists.'); return this.continue(c, j); }); }
|
|
@@ -549,11 +643,13 @@ export class AnalysisStorage {
|
|
|
549
643
|
fail('binding', 'Retained source directory was replaced; no deletion or restore was attempted.');
|
|
550
644
|
}
|
|
551
645
|
commands() {
|
|
646
|
+
const profileOnly = { flags: '--profile-only', description: 'Keep the installation default unchanged; move only this profile' };
|
|
552
647
|
const directory = { flags: '--directory <path>', description: 'Absolute base directory; app name and profile are appended', required: true };
|
|
553
648
|
return command('storage', 'Inspect and migrate profile analysis data', [
|
|
554
649
|
command('show', 'Show effective analysis storage, usage and retained migration', (_i, c) => this.show(c), { permission: Permission.ReadOnly }),
|
|
555
|
-
command('
|
|
556
|
-
command('
|
|
650
|
+
command('default', 'Set the installation storage base for new profiles; preserve existing data locations', ({ options: o }, c) => this.setDefault(c, o.directory), { permission: Permission.Update, options: [directory] }),
|
|
651
|
+
command('configure', 'Choose storage for an empty profile and subsequent new profiles', ({ options: o }, c) => this.migrate(c, o.directory, false, true, Boolean(o.profileOnly)), { permission: Permission.Update, options: [directory, profileOnly] }),
|
|
652
|
+
command('migrate', 'Copy and verify all analysis data, retain source, then switch storage', ({ options: o }, c) => this.migrate(c, o.directory, Boolean(o.dryRun), false, Boolean(o.profileOnly)), { permission: Permission.Update, options: [directory, profileOnly, { flags: '--dry-run', description: 'Read-only path, capacity and quiescence preflight; create no migration' }] }),
|
|
557
653
|
command('resume', 'Resume the retained copy/activation or rollback without replaying research', (_i, c) => this.resume(c), { permission: Permission.Update }),
|
|
558
654
|
command('rollback', 'Restore retained source; refuse after any destination content changes', (_i, c) => this.rollback(c), { permission: Permission.Update }),
|
|
559
655
|
command('cleanup', 'Delete verified retained source copies; retain compatibility paths permanently', (_i, c) => this.cleanup(c), { permission: 'Destructive' }),
|
package/dist/src/arguments.js
CHANGED
|
@@ -23,8 +23,10 @@ export function parsed(p, value) {
|
|
|
23
23
|
fail('invalid', `${flag(p.name)} is outside its supported query range.`);
|
|
24
24
|
if (p.name === 'timeout_seconds' && (result < 1 || result > 1800))
|
|
25
25
|
fail('invalid', 'timeout-seconds must be between 1 and 1800.');
|
|
26
|
-
if (p.name === 'max_scan' && (result < 1 || result > 200000)
|
|
27
|
-
fail('invalid',
|
|
26
|
+
if (p.name === 'max_scan' && (result < 1 || result > 200000))
|
|
27
|
+
fail('invalid', 'max-scan must be between 1 and 200000 visited inputs; result offset does not advance scan coverage.');
|
|
28
|
+
if (p.name === 'max_bytes' && (result < 1 || result > 16777216))
|
|
29
|
+
fail('invalid', 'max-bytes must be between 1 and 16777216 bytes.');
|
|
28
30
|
return result;
|
|
29
31
|
}
|
|
30
32
|
if (['json', 'object', 'array', 'any'].includes(p.type)) {
|
|
@@ -55,8 +57,11 @@ export function option(p) {
|
|
|
55
57
|
const value = p.default;
|
|
56
58
|
const defaultValue = value === undefined || value === '' && !p.allowEmpty ? undefined : parsed(p, value);
|
|
57
59
|
return { flags: `--${flag(p.name)} <${p.type === 'json' ? 'json' : p.type}>`,
|
|
58
|
-
description: p.
|
|
59
|
-
|
|
60
|
+
description: p.name === 'max_scan' ? 'Maximum visited inputs, 1–200000; independent of result offset' :
|
|
61
|
+
p.name === 'max_bytes' ? 'Maximum searched bytes, 1–16777216' :
|
|
62
|
+
p.name === 'offset' && p.default !== '-1' ? 'Skip matching result rows; does not continue an incomplete scan' :
|
|
63
|
+
p.semantic === 'function_ref' ? 'Function name or explicit address' : p.semantic === 'address' ? 'Explicit hexadecimal address' :
|
|
64
|
+
`${flag(p.name)} (${p.type}${p.name === 'limit' ? ', 1–1000' : p.name === 'length' ? ', 1–65536 bytes' : ''})`,
|
|
60
65
|
...(p.required ? { required: true } : {}), ...(defaultValue === undefined ? {} : { defaultValue }),
|
|
61
66
|
...(['json', 'object', 'array', 'any'].includes(p.type) ? {} : { parse: (value) => parsed(p, value) }) };
|
|
62
67
|
}
|