@pensar/apex 0.0.100 → 0.0.101-canary.5bc4cfc4
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/README.md +1 -30
- package/bin/pensar.js +16 -0
- package/build/auth.js +1 -1
- package/build/index.js +273 -91
- package/package.json +1 -1
- package/src/core/installation/index.ts +12 -0
- package/src/core/installation/installation.test.ts +34 -2
package/README.md
CHANGED
|
@@ -18,14 +18,6 @@
|
|
|
18
18
|
|
|
19
19
|
## Installation
|
|
20
20
|
|
|
21
|
-
### Prerequisites
|
|
22
|
-
|
|
23
|
-
- **API Key** for your chosen AI provider
|
|
24
|
-
|
|
25
|
-
After installing, run `pensar doctor` to check for optional dependencies (like nmap) and install them.
|
|
26
|
-
|
|
27
|
-
### Install Apex
|
|
28
|
-
|
|
29
21
|
#### macOS / Linux (Quick Install)
|
|
30
22
|
|
|
31
23
|
```bash
|
|
@@ -42,7 +34,7 @@ brew install apex
|
|
|
42
34
|
#### Windows (PowerShell)
|
|
43
35
|
|
|
44
36
|
```powershell
|
|
45
|
-
irm https://pensarai.com/apex.ps1 | iex
|
|
37
|
+
irm https://www.pensarai.com/apex.ps1 | iex
|
|
46
38
|
```
|
|
47
39
|
|
|
48
40
|
#### npm
|
|
@@ -51,27 +43,6 @@ irm https://pensarai.com/apex.ps1 | iex
|
|
|
51
43
|
npm install -g @pensar/apex
|
|
52
44
|
```
|
|
53
45
|
|
|
54
|
-
### Configuration
|
|
55
|
-
|
|
56
|
-
Set your AI provider API key as an environment variable:
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
export ANTHROPIC_API_KEY="your-api-key-here"
|
|
60
|
-
# or for other providers:
|
|
61
|
-
export OPENAI_API_KEY="your-api-key-here"
|
|
62
|
-
export OPENROUTER_API_KEY="your-api-key-here"
|
|
63
|
-
|
|
64
|
-
# AWS Bedrock (bearer token auth):
|
|
65
|
-
export BEDROCK_API_KEY="your-bearer-token"
|
|
66
|
-
export AWS_REGION="us-east-1"
|
|
67
|
-
|
|
68
|
-
# AWS Bedrock (IAM credentials):
|
|
69
|
-
export AWS_ACCESS_KEY_ID="..."
|
|
70
|
-
export AWS_SECRET_ACCESS_KEY="..."
|
|
71
|
-
export AWS_SESSION_TOKEN="..." # optional, for temporary credentials
|
|
72
|
-
export AWS_REGION="us-east-1"
|
|
73
|
-
```
|
|
74
|
-
|
|
75
46
|
## Usage
|
|
76
47
|
|
|
77
48
|
Run Apex:
|
package/bin/pensar.js
CHANGED
|
@@ -66,6 +66,13 @@ if (command === "benchmark") {
|
|
|
66
66
|
|
|
67
67
|
// Import and run auth
|
|
68
68
|
await import(authPath);
|
|
69
|
+
} else if (command === "uninstall") {
|
|
70
|
+
// Run uninstall CLI
|
|
71
|
+
const uninstallPath = join(__dirname, "..", "build", "uninstall.js");
|
|
72
|
+
|
|
73
|
+
process.argv = [process.argv[0], uninstallPath, ...args.slice(1)];
|
|
74
|
+
|
|
75
|
+
await import(uninstallPath);
|
|
69
76
|
} else if (command === "upgrade" || command === "update") {
|
|
70
77
|
const currentVersion = getCurrentVersion();
|
|
71
78
|
console.log(`Current version: v${currentVersion}`);
|
|
@@ -89,6 +96,7 @@ if (command === "benchmark") {
|
|
|
89
96
|
console.log();
|
|
90
97
|
console.log("Usage:");
|
|
91
98
|
console.log(" pensar Launch the TUI (Terminal User Interface)");
|
|
99
|
+
console.log(" pensar uninstall Uninstall Pensar (keeps sessions, memories, skills)");
|
|
92
100
|
console.log(" pensar upgrade Update pensar to the latest version");
|
|
93
101
|
console.log(" pensar help Show this help message");
|
|
94
102
|
console.log(" pensar version Show version number");
|
|
@@ -181,6 +189,14 @@ if (command === "benchmark") {
|
|
|
181
189
|
console.log(" pensar auth logout Disconnect from Pensar Console");
|
|
182
190
|
console.log(" pensar auth status Show connection status");
|
|
183
191
|
console.log();
|
|
192
|
+
console.log("Uninstall Usage:");
|
|
193
|
+
console.log(
|
|
194
|
+
" pensar uninstall Fully uninstall Pensar"
|
|
195
|
+
);
|
|
196
|
+
console.log(
|
|
197
|
+
" pensar uninstall --force Skip confirmation prompt"
|
|
198
|
+
);
|
|
199
|
+
console.log();
|
|
184
200
|
console.log("Header Modes (for quicktest, pentest, swarm):");
|
|
185
201
|
console.log(" none No custom headers added to requests");
|
|
186
202
|
console.log(
|
package/build/auth.js
CHANGED
|
@@ -8,7 +8,7 @@ import fs from "fs/promises";
|
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "@pensar/apex",
|
|
11
|
-
version: "0.0.
|
|
11
|
+
version: "0.0.101-canary.5bc4cfc4",
|
|
12
12
|
description: "AI-powered penetration testing CLI tool with terminal UI",
|
|
13
13
|
module: "src/tui/index.tsx",
|
|
14
14
|
main: "build/index.js",
|
package/build/index.js
CHANGED
|
@@ -31880,12 +31880,6 @@ var init_openrouter = __esm(() => {
|
|
|
31880
31880
|
var PENSAR_MODELS;
|
|
31881
31881
|
var init_pensar = __esm(() => {
|
|
31882
31882
|
PENSAR_MODELS = [
|
|
31883
|
-
{
|
|
31884
|
-
id: "pensar:anthropic.claude-opus-4-6-v1",
|
|
31885
|
-
name: "Claude Opus 4.6 (Pensar)",
|
|
31886
|
-
provider: "pensar",
|
|
31887
|
-
contextLength: 200000
|
|
31888
|
-
},
|
|
31889
31883
|
{
|
|
31890
31884
|
id: "pensar:anthropic.claude-sonnet-4-5-20250929-v1:0",
|
|
31891
31885
|
name: "Claude Sonnet 4.5 (Pensar)",
|
|
@@ -31977,7 +31971,7 @@ var package_default2;
|
|
|
31977
31971
|
var init_package = __esm(() => {
|
|
31978
31972
|
package_default2 = {
|
|
31979
31973
|
name: "@pensar/apex",
|
|
31980
|
-
version: "0.0.
|
|
31974
|
+
version: "0.0.101-canary.5bc4cfc4",
|
|
31981
31975
|
description: "AI-powered penetration testing CLI tool with terminal UI",
|
|
31982
31976
|
module: "src/tui/index.tsx",
|
|
31983
31977
|
main: "build/index.js",
|
|
@@ -89913,6 +89907,7 @@ function convertMessagesToUI(messages, baseTime) {
|
|
|
89913
89907
|
const input = part.input;
|
|
89914
89908
|
const toolDescription = typeof input?.toolCallDescription === "string" ? input.toolCallDescription : part.toolName || "tool";
|
|
89915
89909
|
const result = part.toolCallId ? toolResults.get(part.toolCallId) : undefined;
|
|
89910
|
+
const cancelled = typeof result === "string" && result.toLowerCase().includes("cancelled");
|
|
89916
89911
|
uiMessages.push({
|
|
89917
89912
|
role: "tool",
|
|
89918
89913
|
content: toolDescription,
|
|
@@ -89921,7 +89916,7 @@ function convertMessagesToUI(messages, baseTime) {
|
|
|
89921
89916
|
toolName: part.toolName,
|
|
89922
89917
|
args: input,
|
|
89923
89918
|
result,
|
|
89924
|
-
status: "completed"
|
|
89919
|
+
status: cancelled ? "error" : "completed"
|
|
89925
89920
|
});
|
|
89926
89921
|
}
|
|
89927
89922
|
}
|
|
@@ -107298,9 +107293,9 @@ var init_executeCommand = __esm(() => {
|
|
|
107298
107293
|
init_dist5();
|
|
107299
107294
|
init_zod();
|
|
107300
107295
|
executeCommandInputSchema = exports_external.object({
|
|
107296
|
+
toolCallDescription: exports_external.string().describe("A concise, human-readable description of what this tool call is doing (e.g., 'Scanning for open ports on target')"),
|
|
107301
107297
|
command: exports_external.string().describe("The shell command to execute"),
|
|
107302
|
-
timeout: exports_external.number().optional().describe("Timeout in seconds. If omitted, the command runs until completion or abort.")
|
|
107303
|
-
toolCallDescription: exports_external.string().describe("A concise, human-readable description of what this tool call is doing (e.g., 'Scanning for open ports on target')")
|
|
107298
|
+
timeout: exports_external.number().optional().describe("Timeout in seconds. If omitted, the command runs until completion or abort.")
|
|
107304
107299
|
});
|
|
107305
107300
|
});
|
|
107306
107301
|
|
|
@@ -193797,6 +193792,19 @@ COMMON SEARCH PATTERNS:
|
|
|
193797
193792
|
execute: async ({ query }) => {
|
|
193798
193793
|
try {
|
|
193799
193794
|
const cfg = await config2.get();
|
|
193795
|
+
const apiUrl = getPensarApiUrl();
|
|
193796
|
+
const body = JSON.stringify({ query });
|
|
193797
|
+
if (cfg.pensarAPIKey && !cfg.accessToken) {
|
|
193798
|
+
const response2 = await fetch(`${apiUrl}/agents/web_search`, {
|
|
193799
|
+
method: "POST",
|
|
193800
|
+
headers: {
|
|
193801
|
+
"Content-Type": "application/json",
|
|
193802
|
+
"x-api-key": cfg.pensarAPIKey
|
|
193803
|
+
},
|
|
193804
|
+
body
|
|
193805
|
+
});
|
|
193806
|
+
return handleSearchResponse(response2);
|
|
193807
|
+
}
|
|
193800
193808
|
const tokenResult = await ensureValidToken({
|
|
193801
193809
|
accessToken: cfg.accessToken,
|
|
193802
193810
|
refreshToken: cfg.refreshToken,
|
|
@@ -193823,8 +193831,6 @@ COMMON SEARCH PATTERNS:
|
|
|
193823
193831
|
error: "Web search requires authentication. Please sign in again to your Pensar account."
|
|
193824
193832
|
};
|
|
193825
193833
|
}
|
|
193826
|
-
const apiUrl = getPensarApiUrl();
|
|
193827
|
-
const body = JSON.stringify({ query });
|
|
193828
193834
|
const { signature, timestamp, nonce } = signGatewayRequest(cfg.gatewaySigningKey, "web_search", body);
|
|
193829
193835
|
const response = await fetch(`${apiUrl}/agents/web_search`, {
|
|
193830
193836
|
method: "POST",
|
|
@@ -193838,40 +193844,7 @@ COMMON SEARCH PATTERNS:
|
|
|
193838
193844
|
},
|
|
193839
193845
|
body
|
|
193840
193846
|
});
|
|
193841
|
-
|
|
193842
|
-
if (response.status === 401) {
|
|
193843
|
-
return {
|
|
193844
|
-
success: false,
|
|
193845
|
-
results: [],
|
|
193846
|
-
error: "Authentication failed. Please sign in again to your Pensar account."
|
|
193847
|
-
};
|
|
193848
|
-
}
|
|
193849
|
-
if (response.status === 429) {
|
|
193850
|
-
return {
|
|
193851
|
-
success: false,
|
|
193852
|
-
results: [],
|
|
193853
|
-
error: "Rate limit exceeded. Please wait a moment before searching again."
|
|
193854
|
-
};
|
|
193855
|
-
}
|
|
193856
|
-
const errorText = await response.text().catch(() => "Unknown error");
|
|
193857
|
-
return {
|
|
193858
|
-
success: false,
|
|
193859
|
-
results: [],
|
|
193860
|
-
error: `Web search failed: ${response.status} ${response.statusText}. ${errorText}`
|
|
193861
|
-
};
|
|
193862
|
-
}
|
|
193863
|
-
const data = await response.json();
|
|
193864
|
-
if (data.error) {
|
|
193865
|
-
return {
|
|
193866
|
-
success: false,
|
|
193867
|
-
results: [],
|
|
193868
|
-
error: data.error
|
|
193869
|
-
};
|
|
193870
|
-
}
|
|
193871
|
-
return {
|
|
193872
|
-
success: true,
|
|
193873
|
-
results: data.results || []
|
|
193874
|
-
};
|
|
193847
|
+
return handleSearchResponse(response);
|
|
193875
193848
|
} catch (error40) {
|
|
193876
193849
|
const errorMsg = error40 instanceof Error ? error40.message : String(error40);
|
|
193877
193850
|
return {
|
|
@@ -193883,6 +193856,42 @@ COMMON SEARCH PATTERNS:
|
|
|
193883
193856
|
}
|
|
193884
193857
|
});
|
|
193885
193858
|
}
|
|
193859
|
+
async function handleSearchResponse(response) {
|
|
193860
|
+
if (!response.ok) {
|
|
193861
|
+
if (response.status === 401) {
|
|
193862
|
+
return {
|
|
193863
|
+
success: false,
|
|
193864
|
+
results: [],
|
|
193865
|
+
error: "Authentication failed. Please sign in again to your Pensar account."
|
|
193866
|
+
};
|
|
193867
|
+
}
|
|
193868
|
+
if (response.status === 429) {
|
|
193869
|
+
return {
|
|
193870
|
+
success: false,
|
|
193871
|
+
results: [],
|
|
193872
|
+
error: "Rate limit exceeded. Please wait a moment before searching again."
|
|
193873
|
+
};
|
|
193874
|
+
}
|
|
193875
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
193876
|
+
return {
|
|
193877
|
+
success: false,
|
|
193878
|
+
results: [],
|
|
193879
|
+
error: `Web search failed: ${response.status} ${response.statusText}. ${errorText}`
|
|
193880
|
+
};
|
|
193881
|
+
}
|
|
193882
|
+
const data = await response.json();
|
|
193883
|
+
if (data.error) {
|
|
193884
|
+
return {
|
|
193885
|
+
success: false,
|
|
193886
|
+
results: [],
|
|
193887
|
+
error: data.error
|
|
193888
|
+
};
|
|
193889
|
+
}
|
|
193890
|
+
return {
|
|
193891
|
+
success: true,
|
|
193892
|
+
results: data.results || []
|
|
193893
|
+
};
|
|
193894
|
+
}
|
|
193886
193895
|
var webSearchInputSchema2;
|
|
193887
193896
|
var init_webSearch = __esm(() => {
|
|
193888
193897
|
init_dist5();
|
|
@@ -194800,7 +194809,8 @@ var init_operator = __esm(() => {
|
|
|
194800
194809
|
|
|
194801
194810
|
// src/core/agents/offSecAgent/offensiveSecurityAgent.ts
|
|
194802
194811
|
import { join as join22 } from "path";
|
|
194803
|
-
import {
|
|
194812
|
+
import { mkdirSync as mkdirSync10, existsSync as existsSync21 } from "fs";
|
|
194813
|
+
import { writeFile as writeFile3 } from "fs/promises";
|
|
194804
194814
|
function wrapToolsWithApprovalGate(tools, gate) {
|
|
194805
194815
|
const wrapped = {};
|
|
194806
194816
|
for (const [name26, coreTool] of Object.entries(tools)) {
|
|
@@ -194954,13 +194964,11 @@ var init_offensiveSecurityAgent = __esm(() => {
|
|
|
194954
194964
|
stopWhen,
|
|
194955
194965
|
toolChoice: "auto",
|
|
194956
194966
|
onStepFinish: (event) => {
|
|
194957
|
-
|
|
194958
|
-
|
|
194959
|
-
|
|
194960
|
-
|
|
194961
|
-
|
|
194962
|
-
writeFileSync15(messagesPath, JSON.stringify(allMessages, null, 2));
|
|
194963
|
-
} catch {}
|
|
194967
|
+
const allMessages = [
|
|
194968
|
+
...initialMessagesRef.current,
|
|
194969
|
+
...event.response.messages
|
|
194970
|
+
];
|
|
194971
|
+
writeFile3(messagesPath, JSON.stringify(allMessages, null, 2)).catch(() => {});
|
|
194964
194972
|
input.onStepFinish?.(event);
|
|
194965
194973
|
},
|
|
194966
194974
|
onSummarized: () => {
|
|
@@ -195484,7 +195492,7 @@ var init_agent4 = __esm(() => {
|
|
|
195484
195492
|
});
|
|
195485
195493
|
|
|
195486
195494
|
// src/core/session/execution-metrics.ts
|
|
195487
|
-
import { existsSync as existsSync24, readFileSync as readFileSync11, writeFileSync as
|
|
195495
|
+
import { existsSync as existsSync24, readFileSync as readFileSync11, writeFileSync as writeFileSync15 } from "fs";
|
|
195488
195496
|
import { join as join25 } from "path";
|
|
195489
195497
|
function toNonNegativeInteger(value) {
|
|
195490
195498
|
const n = Number(value);
|
|
@@ -195530,7 +195538,7 @@ function writeSessionJsonTokenTotals(sessionRootPath, tokenUsage) {
|
|
|
195530
195538
|
const parsed = JSON.parse(readFileSync11(path6, "utf-8"));
|
|
195531
195539
|
parsed.tokensIn = tokenUsage.inputTokens;
|
|
195532
195540
|
parsed.tokensOut = tokenUsage.outputTokens;
|
|
195533
|
-
|
|
195541
|
+
writeFileSync15(path6, JSON.stringify(parsed, null, 2));
|
|
195534
195542
|
} catch {}
|
|
195535
195543
|
}
|
|
195536
195544
|
function writeExecutionMetrics(input) {
|
|
@@ -195545,7 +195553,7 @@ function writeExecutionMetrics(input) {
|
|
|
195545
195553
|
runtime: input.runtime ?? existing?.runtime,
|
|
195546
195554
|
updatedAt: new Date().toISOString()
|
|
195547
195555
|
};
|
|
195548
|
-
|
|
195556
|
+
writeFileSync15(metricsPath(input.sessionRootPath), JSON.stringify(next, null, 2), "utf-8");
|
|
195549
195557
|
writeSessionJsonTokenTotals(input.sessionRootPath, next.tokenUsage);
|
|
195550
195558
|
return next;
|
|
195551
195559
|
}
|
|
@@ -196140,7 +196148,7 @@ __export(exports_pentest, {
|
|
|
196140
196148
|
runPentestSwarm: () => runPentestSwarm,
|
|
196141
196149
|
DEFAULT_CONCURRENCY: () => DEFAULT_CONCURRENCY4
|
|
196142
196150
|
});
|
|
196143
|
-
import { existsSync as existsSync25, readdirSync as readdirSync5, readFileSync as readFileSync12, writeFileSync as
|
|
196151
|
+
import { existsSync as existsSync25, readdirSync as readdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync16 } from "fs";
|
|
196144
196152
|
import { join as join26 } from "path";
|
|
196145
196153
|
function addUsageTotals(totals, usage) {
|
|
196146
196154
|
if (!usage)
|
|
@@ -196310,8 +196318,8 @@ async function runPentestWorkflow(input) {
|
|
|
196310
196318
|
});
|
|
196311
196319
|
const mdPath2 = join26(session.rootPath, REPORT_FILENAME_MD);
|
|
196312
196320
|
const jsonPath2 = join26(session.rootPath, REPORT_FILENAME_JSON);
|
|
196313
|
-
|
|
196314
|
-
|
|
196321
|
+
writeFileSync16(mdPath2, renderMarkdown(report2));
|
|
196322
|
+
writeFileSync16(jsonPath2, renderJson(report2));
|
|
196315
196323
|
return {
|
|
196316
196324
|
findings: [],
|
|
196317
196325
|
findingsPath: session.findingsPath,
|
|
@@ -196350,8 +196358,8 @@ async function runPentestWorkflow(input) {
|
|
|
196350
196358
|
});
|
|
196351
196359
|
const mdPath = join26(session.rootPath, REPORT_FILENAME_MD);
|
|
196352
196360
|
const jsonPath = join26(session.rootPath, REPORT_FILENAME_JSON);
|
|
196353
|
-
|
|
196354
|
-
|
|
196361
|
+
writeFileSync16(mdPath, renderMarkdown(report));
|
|
196362
|
+
writeFileSync16(jsonPath, renderJson(report));
|
|
196355
196363
|
return {
|
|
196356
196364
|
findings,
|
|
196357
196365
|
findingsPath: session.findingsPath,
|
|
@@ -274503,7 +274511,27 @@ function CommandProvider({
|
|
|
274503
274511
|
description: skill.description || "Skill"
|
|
274504
274512
|
});
|
|
274505
274513
|
}
|
|
274506
|
-
|
|
274514
|
+
const priorityOrder = [
|
|
274515
|
+
"/pentest",
|
|
274516
|
+
"/operator",
|
|
274517
|
+
"/auth",
|
|
274518
|
+
"/models",
|
|
274519
|
+
"/sessions",
|
|
274520
|
+
"/themes",
|
|
274521
|
+
"/help"
|
|
274522
|
+
];
|
|
274523
|
+
return options.sort((a, b2) => {
|
|
274524
|
+
const aIndex = priorityOrder.indexOf(a.value);
|
|
274525
|
+
const bIndex = priorityOrder.indexOf(b2.value);
|
|
274526
|
+
if (aIndex !== -1 && bIndex !== -1) {
|
|
274527
|
+
return aIndex - bIndex;
|
|
274528
|
+
}
|
|
274529
|
+
if (aIndex !== -1)
|
|
274530
|
+
return -1;
|
|
274531
|
+
if (bIndex !== -1)
|
|
274532
|
+
return 1;
|
|
274533
|
+
return a.value.localeCompare(b2.value);
|
|
274534
|
+
});
|
|
274507
274535
|
}, [router, skills]);
|
|
274508
274536
|
const executeCommand = import_react17.useCallback(async (input) => {
|
|
274509
274537
|
return await router.execute(input, ctx3);
|
|
@@ -275720,6 +275748,40 @@ function computeTab(suggestions, selectedIndex) {
|
|
|
275720
275748
|
function shouldResetHistory(historyIndex, isNavigatingHistory) {
|
|
275721
275749
|
return historyIndex !== -1 && !isNavigatingHistory;
|
|
275722
275750
|
}
|
|
275751
|
+
function computeVisibleWindow(suggestions, selectedIndex, maxVisible) {
|
|
275752
|
+
if (suggestions.length === 0) {
|
|
275753
|
+
return {
|
|
275754
|
+
start: 0,
|
|
275755
|
+
end: 0,
|
|
275756
|
+
visibleSuggestions: [],
|
|
275757
|
+
hasMore: false,
|
|
275758
|
+
hasMoreBelow: false
|
|
275759
|
+
};
|
|
275760
|
+
}
|
|
275761
|
+
if (suggestions.length <= maxVisible) {
|
|
275762
|
+
return {
|
|
275763
|
+
start: 0,
|
|
275764
|
+
end: suggestions.length,
|
|
275765
|
+
visibleSuggestions: suggestions,
|
|
275766
|
+
hasMore: false,
|
|
275767
|
+
hasMoreBelow: false
|
|
275768
|
+
};
|
|
275769
|
+
}
|
|
275770
|
+
const safeSelectedIndex = Math.max(0, selectedIndex);
|
|
275771
|
+
let start = Math.max(0, safeSelectedIndex - Math.floor(maxVisible / 2));
|
|
275772
|
+
let end = start + maxVisible;
|
|
275773
|
+
if (end > suggestions.length) {
|
|
275774
|
+
end = suggestions.length;
|
|
275775
|
+
start = Math.max(0, end - maxVisible);
|
|
275776
|
+
}
|
|
275777
|
+
return {
|
|
275778
|
+
start,
|
|
275779
|
+
end,
|
|
275780
|
+
visibleSuggestions: suggestions.slice(start, end),
|
|
275781
|
+
hasMore: start > 0,
|
|
275782
|
+
hasMoreBelow: end < suggestions.length
|
|
275783
|
+
};
|
|
275784
|
+
}
|
|
275723
275785
|
|
|
275724
275786
|
// src/tui/components/shared/use-paste-extmarks.ts
|
|
275725
275787
|
var import_react30 = __toESM(require_react(), 1);
|
|
@@ -275813,6 +275875,7 @@ var PromptInput = import_react31.forwardRef(function PromptInput2({
|
|
|
275813
275875
|
enableAutocomplete = false,
|
|
275814
275876
|
autocompleteOptions = [],
|
|
275815
275877
|
maxSuggestions = 10,
|
|
275878
|
+
maxVisibleSuggestions = 6,
|
|
275816
275879
|
enableCommands = false,
|
|
275817
275880
|
onCommandExecute,
|
|
275818
275881
|
commandHistory = [],
|
|
@@ -275978,34 +276041,72 @@ var PromptInput = import_react31.forwardRef(function PromptInput2({
|
|
|
275978
276041
|
setHistoryIndex(-1);
|
|
275979
276042
|
}
|
|
275980
276043
|
};
|
|
276044
|
+
const windowedView = import_react31.useMemo(() => computeVisibleWindow(suggestions, selectedSuggestionIndex, maxVisibleSuggestions), [suggestions, selectedSuggestionIndex, maxVisibleSuggestions]);
|
|
275981
276045
|
const suggestionsBox = suggestions.length > 0 && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
275982
276046
|
flexDirection: "column",
|
|
275983
276047
|
...autocompletePlacement === "above" ? { marginBottom: 1 } : { marginTop: 1 },
|
|
275984
|
-
children:
|
|
275985
|
-
|
|
275986
|
-
return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
276048
|
+
children: [
|
|
276049
|
+
windowedView.hasMore && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
275987
276050
|
flexDirection: "row",
|
|
275988
276051
|
gap: 1,
|
|
275989
276052
|
children: [
|
|
275990
276053
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
275991
|
-
fg:
|
|
275992
|
-
children:
|
|
276054
|
+
fg: colors2.textMuted,
|
|
276055
|
+
children: " ↑"
|
|
275993
276056
|
}, undefined, false, undefined, this),
|
|
275994
276057
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
275995
|
-
fg:
|
|
275996
|
-
children:
|
|
276058
|
+
fg: colors2.textMuted,
|
|
276059
|
+
children: [
|
|
276060
|
+
windowedView.start,
|
|
276061
|
+
" more above..."
|
|
276062
|
+
]
|
|
276063
|
+
}, undefined, true, undefined, this)
|
|
276064
|
+
]
|
|
276065
|
+
}, undefined, true, undefined, this),
|
|
276066
|
+
windowedView.visibleSuggestions.map((suggestion, windowIndex) => {
|
|
276067
|
+
const actualIndex = windowedView.start + windowIndex;
|
|
276068
|
+
const isSelected = actualIndex === selectedSuggestionIndex;
|
|
276069
|
+
return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
276070
|
+
flexDirection: "row",
|
|
276071
|
+
gap: 1,
|
|
276072
|
+
children: [
|
|
276073
|
+
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
276074
|
+
fg: isSelected ? colors2.primary : colors2.textMuted,
|
|
276075
|
+
children: isSelected ? " ▸" : " "
|
|
276076
|
+
}, undefined, false, undefined, this),
|
|
276077
|
+
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
276078
|
+
fg: isSelected ? colors2.text : colors2.textMuted,
|
|
276079
|
+
children: suggestion.label
|
|
276080
|
+
}, undefined, false, undefined, this),
|
|
276081
|
+
suggestion.description && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
276082
|
+
fg: colors2.textMuted,
|
|
276083
|
+
children: [
|
|
276084
|
+
" ",
|
|
276085
|
+
suggestion.description
|
|
276086
|
+
]
|
|
276087
|
+
}, undefined, true, undefined, this)
|
|
276088
|
+
]
|
|
276089
|
+
}, suggestion.value, true, undefined, this);
|
|
276090
|
+
}),
|
|
276091
|
+
windowedView.hasMoreBelow && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
276092
|
+
flexDirection: "row",
|
|
276093
|
+
gap: 1,
|
|
276094
|
+
children: [
|
|
276095
|
+
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
276096
|
+
fg: colors2.textMuted,
|
|
276097
|
+
children: " ↓"
|
|
275997
276098
|
}, undefined, false, undefined, this),
|
|
275998
|
-
|
|
276099
|
+
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
275999
276100
|
fg: colors2.textMuted,
|
|
276000
276101
|
children: [
|
|
276001
|
-
|
|
276002
|
-
|
|
276102
|
+
suggestions.length - windowedView.end,
|
|
276103
|
+
" more below..."
|
|
276003
276104
|
]
|
|
276004
276105
|
}, undefined, true, undefined, this)
|
|
276005
276106
|
]
|
|
276006
|
-
},
|
|
276007
|
-
|
|
276008
|
-
}, undefined,
|
|
276107
|
+
}, undefined, true, undefined, this)
|
|
276108
|
+
]
|
|
276109
|
+
}, undefined, true, undefined, this);
|
|
276009
276110
|
return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
276010
276111
|
flexDirection: "column",
|
|
276011
276112
|
children: [
|
|
@@ -279749,7 +279850,7 @@ function ResponsibleUseDisclosure({
|
|
|
279749
279850
|
}, undefined, false, undefined, this),
|
|
279750
279851
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
279751
279852
|
fg: colors2.text,
|
|
279752
|
-
children: "This penetration testing tool is
|
|
279853
|
+
children: "This penetration testing tool is designed for AUTHORIZED security testing only."
|
|
279753
279854
|
}, undefined, false, undefined, this),
|
|
279754
279855
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
279755
279856
|
flexDirection: "column",
|
|
@@ -279792,7 +279893,7 @@ function ResponsibleUseDisclosure({
|
|
|
279792
279893
|
flexDirection: "column",
|
|
279793
279894
|
children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
279794
279895
|
fg: colors2.error,
|
|
279795
|
-
children: "Unauthorized access to computer systems is
|
|
279896
|
+
children: "Unauthorized access to computer systems is illegal and may result in criminal prosecution."
|
|
279796
279897
|
}, undefined, false, undefined, this)
|
|
279797
279898
|
}, undefined, false, undefined, this),
|
|
279798
279899
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
@@ -280099,18 +280200,23 @@ function HelpDialog() {
|
|
|
280099
280200
|
});
|
|
280100
280201
|
};
|
|
280101
280202
|
useKeyboard((evt) => {
|
|
280203
|
+
if (evt.name === "escape") {
|
|
280204
|
+
evt.preventDefault();
|
|
280205
|
+
if (showDetail) {
|
|
280206
|
+
setShowDetail(false);
|
|
280207
|
+
} else {
|
|
280208
|
+
handleClose();
|
|
280209
|
+
}
|
|
280210
|
+
return;
|
|
280211
|
+
}
|
|
280102
280212
|
if (showDetail) {
|
|
280103
|
-
if (evt.name === "
|
|
280213
|
+
if (evt.name === "return") {
|
|
280104
280214
|
evt.preventDefault();
|
|
280105
280215
|
setShowDetail(false);
|
|
280106
280216
|
}
|
|
280107
280217
|
return;
|
|
280108
280218
|
}
|
|
280109
280219
|
switch (evt.name) {
|
|
280110
|
-
case "escape":
|
|
280111
|
-
evt.preventDefault();
|
|
280112
|
-
handleClose();
|
|
280113
|
-
break;
|
|
280114
280220
|
case "up":
|
|
280115
280221
|
case "k":
|
|
280116
280222
|
evt.preventDefault();
|
|
@@ -281165,9 +281271,10 @@ function createKeybindings(deps) {
|
|
|
281165
281271
|
return;
|
|
281166
281272
|
}
|
|
281167
281273
|
const isHome = route.data.type === "base" && route.data.path === "home";
|
|
281274
|
+
const isHelp = route.data.type === "base" && route.data.path === "help";
|
|
281168
281275
|
const isOperator = route.data.type === "base" && route.data.path === "operator";
|
|
281169
281276
|
const isSession = route.data.type === "pentest" || route.data.type === "operator";
|
|
281170
|
-
if (!isHome && !isOperator && !isSession) {
|
|
281277
|
+
if (!isHome && !isHelp && !isOperator && !isSession) {
|
|
281171
281278
|
route.navigate({
|
|
281172
281279
|
type: "base",
|
|
281173
281280
|
path: "home"
|
|
@@ -282911,6 +283018,15 @@ function getToolSummary(toolName, args) {
|
|
|
282911
283018
|
const firstArg = Object.entries(args).filter(([k3]) => k3 !== "toolCallDescription").map(([, v3]) => typeof v3 === "string" ? v3 : JSON.stringify(v3)).find((v3) => v3 && v3.length > 0);
|
|
282912
283019
|
return firstArg ? `${toolName} ${String(firstArg).slice(0, 50)}` : toolName;
|
|
282913
283020
|
}
|
|
283021
|
+
function getToolDisplayLabel(toolName, args, options = {}) {
|
|
283022
|
+
if (options.preferDescription && toolName === "execute_command") {
|
|
283023
|
+
const description = args.toolCallDescription;
|
|
283024
|
+
if (typeof description === "string" && description.trim().length > 0) {
|
|
283025
|
+
return description.trim();
|
|
283026
|
+
}
|
|
283027
|
+
}
|
|
283028
|
+
return getToolSummary(toolName, args);
|
|
283029
|
+
}
|
|
282914
283030
|
function getArgsPreview(toolName, args, maxLength = 60) {
|
|
282915
283031
|
const filteredArgs = Object.entries(args).filter(([k3]) => !k3.toLowerCase().includes("description"));
|
|
282916
283032
|
if (filteredArgs.length === 0)
|
|
@@ -283744,7 +283860,9 @@ var ToolRenderer = import_react68.memo(function ToolRenderer2({
|
|
|
283744
283860
|
const isCompleted = message.status === "completed";
|
|
283745
283861
|
const isError = message.status === "error";
|
|
283746
283862
|
const { toolName, args, result, logs, subagentLogs } = message;
|
|
283747
|
-
const summary =
|
|
283863
|
+
const summary = getToolDisplayLabel(toolName, args, {
|
|
283864
|
+
preferDescription: isPending
|
|
283865
|
+
});
|
|
283748
283866
|
const resultDisplay = isCompleted || isError ? getResultSummary(result, toolName, args) : null;
|
|
283749
283867
|
const borderColor = isError ? colors2.error : isPending ? colors2.warning : colors2.info;
|
|
283750
283868
|
const hasSubagentLogs = subagentLogs && Object.keys(subagentLogs).length > 0;
|
|
@@ -286132,7 +286250,7 @@ function MessageList({
|
|
|
286132
286250
|
}, undefined, false, undefined, this),
|
|
286133
286251
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
286134
286252
|
fg: colors2.textMuted,
|
|
286135
|
-
children: " -
|
|
286253
|
+
children: " - Switch between Plan or Default mode"
|
|
286136
286254
|
}, undefined, false, undefined, this)
|
|
286137
286255
|
]
|
|
286138
286256
|
}, undefined, true, undefined, this)
|
|
@@ -286735,7 +286853,7 @@ function navigateDown(selectedIndex, queueLength) {
|
|
|
286735
286853
|
}
|
|
286736
286854
|
|
|
286737
286855
|
// src/tui/components/operator-dashboard/index.tsx
|
|
286738
|
-
import { existsSync as existsSync27, readFileSync as readFileSync14 } from "fs";
|
|
286856
|
+
import { existsSync as existsSync27, readFileSync as readFileSync14, writeFileSync as writeFileSync17 } from "fs";
|
|
286739
286857
|
import { join as join28 } from "path";
|
|
286740
286858
|
function OperatorDashboard({
|
|
286741
286859
|
sessionId,
|
|
@@ -286775,6 +286893,8 @@ function OperatorDashboard({
|
|
|
286775
286893
|
return filterOperatorAutocomplete(allAutocompleteOptions, skillSlugs);
|
|
286776
286894
|
}, [allAutocompleteOptions, skills]);
|
|
286777
286895
|
const [session, setSession] = import_react79.useState(null);
|
|
286896
|
+
const sessionRef = import_react79.useRef(null);
|
|
286897
|
+
sessionRef.current = session;
|
|
286778
286898
|
const [loading, setLoading] = import_react79.useState(true);
|
|
286779
286899
|
const [error40, setError] = import_react79.useState(null);
|
|
286780
286900
|
const pendingNameRef = import_react79.useRef(null);
|
|
@@ -286786,6 +286906,8 @@ function OperatorDashboard({
|
|
|
286786
286906
|
});
|
|
286787
286907
|
const commandCancelledRef = import_react79.useRef(false);
|
|
286788
286908
|
const [messages, setMessages] = import_react79.useState([]);
|
|
286909
|
+
const displayMessagesRef = import_react79.useRef([]);
|
|
286910
|
+
displayMessagesRef.current = messages;
|
|
286789
286911
|
const textRef = import_react79.useRef("");
|
|
286790
286912
|
const conversationRef = import_react79.useRef([]);
|
|
286791
286913
|
const [inputValue, setInputValue] = import_react79.useState("");
|
|
@@ -287169,6 +287291,12 @@ function OperatorDashboard({
|
|
|
287169
287291
|
{ role: "user", content: prompt }
|
|
287170
287292
|
];
|
|
287171
287293
|
conversationRef.current = nextMessages;
|
|
287294
|
+
if (sessionRef.current) {
|
|
287295
|
+
try {
|
|
287296
|
+
const mp = join28(sessionRef.current.rootPath, "messages.json");
|
|
287297
|
+
writeFileSync17(mp, JSON.stringify(nextMessages, null, 2));
|
|
287298
|
+
} catch {}
|
|
287299
|
+
}
|
|
287172
287300
|
const onStepFinish = (event) => {
|
|
287173
287301
|
const nextUsage = accumulateTokenUsage(tokenUsageRef.current, event.usage?.inputTokens ?? 0, event.usage?.outputTokens ?? 0);
|
|
287174
287302
|
if (!nextUsage)
|
|
@@ -287186,22 +287314,32 @@ function OperatorDashboard({
|
|
|
287186
287314
|
};
|
|
287187
287315
|
const callbacks = {
|
|
287188
287316
|
onTextDelta: (d3) => {
|
|
287317
|
+
if (gen !== generationRef.current)
|
|
287318
|
+
return;
|
|
287189
287319
|
setThinking(false);
|
|
287190
287320
|
appendText(d3.text);
|
|
287191
287321
|
},
|
|
287192
287322
|
onToolCallStreaming: (d3) => {
|
|
287323
|
+
if (gen !== generationRef.current)
|
|
287324
|
+
return;
|
|
287193
287325
|
setThinking(false);
|
|
287194
287326
|
addStreamingToolCall(d3.toolCallId, d3.toolName);
|
|
287195
287327
|
},
|
|
287196
287328
|
onToolCallDelta: (d3) => {
|
|
287329
|
+
if (gen !== generationRef.current)
|
|
287330
|
+
return;
|
|
287197
287331
|
appendToolCallDelta(d3.toolCallId, d3.argsTextDelta);
|
|
287198
287332
|
},
|
|
287199
287333
|
onToolCall: (d3) => {
|
|
287334
|
+
if (gen !== generationRef.current)
|
|
287335
|
+
return;
|
|
287200
287336
|
setThinking(false);
|
|
287201
287337
|
commandCancelledRef.current = false;
|
|
287202
287338
|
addToolCall(d3.toolCallId, d3.toolName, d3.input);
|
|
287203
287339
|
},
|
|
287204
287340
|
onToolResult: (d3) => {
|
|
287341
|
+
if (gen !== generationRef.current)
|
|
287342
|
+
return;
|
|
287205
287343
|
flushCommandOutput();
|
|
287206
287344
|
if (cmdFlushTimerRef.current) {
|
|
287207
287345
|
clearInterval(cmdFlushTimerRef.current);
|
|
@@ -287261,6 +287399,8 @@ function OperatorDashboard({
|
|
|
287261
287399
|
callbacks,
|
|
287262
287400
|
onSessionReady: (s2) => {
|
|
287263
287401
|
setSessionCwd(s2.rootPath);
|
|
287402
|
+
sessionRef.current = s2;
|
|
287403
|
+
setSession((prev) => prev ?? s2);
|
|
287264
287404
|
}
|
|
287265
287405
|
};
|
|
287266
287406
|
try {
|
|
@@ -287506,19 +287646,61 @@ function OperatorDashboard({
|
|
|
287506
287646
|
setQueuedMessages([]);
|
|
287507
287647
|
queuedMessagesRef.current = [];
|
|
287508
287648
|
setSelectedQueueIndex(-1);
|
|
287649
|
+
approvalGateRef.current.denyAll();
|
|
287509
287650
|
setStatus("idle");
|
|
287510
287651
|
setThinking(false);
|
|
287511
287652
|
setIsExecuting(false);
|
|
287512
|
-
|
|
287513
|
-
if (
|
|
287653
|
+
const activeSession = sessionRef.current;
|
|
287654
|
+
if (activeSession) {
|
|
287514
287655
|
try {
|
|
287515
|
-
const messagesPath = join28(
|
|
287656
|
+
const messagesPath = join28(activeSession.rootPath, "messages.json");
|
|
287516
287657
|
if (existsSync27(messagesPath)) {
|
|
287517
287658
|
const raw = JSON.parse(readFileSync14(messagesPath, "utf-8"));
|
|
287518
287659
|
if (Array.isArray(raw) && raw.length > 0) {
|
|
287519
287660
|
conversationRef.current = sessions.getResumeMessages(raw);
|
|
287520
287661
|
}
|
|
287521
287662
|
}
|
|
287663
|
+
const last = conversationRef.current[conversationRef.current.length - 1];
|
|
287664
|
+
if (last?.role === "user") {
|
|
287665
|
+
const partial2 = textRef.current.trim();
|
|
287666
|
+
const pendingTools = displayMessagesRef.current.filter((m4) => isToolMessage(m4) && (m4.status === "pending" || m4.status === "streaming"));
|
|
287667
|
+
const assistantContent = [
|
|
287668
|
+
{
|
|
287669
|
+
type: "text",
|
|
287670
|
+
text: partial2 || "[Response interrupted by user.]"
|
|
287671
|
+
}
|
|
287672
|
+
];
|
|
287673
|
+
for (const t3 of pendingTools) {
|
|
287674
|
+
assistantContent.push({
|
|
287675
|
+
type: "tool-call",
|
|
287676
|
+
toolCallId: t3.toolCallId,
|
|
287677
|
+
toolName: t3.toolName,
|
|
287678
|
+
input: t3.args ?? {}
|
|
287679
|
+
});
|
|
287680
|
+
}
|
|
287681
|
+
conversationRef.current = [
|
|
287682
|
+
...conversationRef.current,
|
|
287683
|
+
{
|
|
287684
|
+
role: "assistant",
|
|
287685
|
+
content: assistantContent
|
|
287686
|
+
}
|
|
287687
|
+
];
|
|
287688
|
+
if (pendingTools.length > 0) {
|
|
287689
|
+
conversationRef.current = [
|
|
287690
|
+
...conversationRef.current,
|
|
287691
|
+
{
|
|
287692
|
+
role: "tool",
|
|
287693
|
+
content: pendingTools.map((t3) => ({
|
|
287694
|
+
type: "tool-result",
|
|
287695
|
+
toolCallId: t3.toolCallId,
|
|
287696
|
+
toolName: t3.toolName ?? "unknown",
|
|
287697
|
+
output: "Cancelled by user."
|
|
287698
|
+
}))
|
|
287699
|
+
}
|
|
287700
|
+
];
|
|
287701
|
+
}
|
|
287702
|
+
writeFileSync17(join28(activeSession.rootPath, "messages.json"), JSON.stringify(conversationRef.current, null, 2));
|
|
287703
|
+
}
|
|
287522
287704
|
} catch {}
|
|
287523
287705
|
}
|
|
287524
287706
|
setMessages((prev) => {
|
|
@@ -287532,7 +287714,7 @@ function OperatorDashboard({
|
|
|
287532
287714
|
}
|
|
287533
287715
|
];
|
|
287534
287716
|
});
|
|
287535
|
-
}, [
|
|
287717
|
+
}, [setThinking, setIsExecuting]);
|
|
287536
287718
|
const toggleApproval = import_react79.useCallback(() => {
|
|
287537
287719
|
setOperatorState((prev) => {
|
|
287538
287720
|
const newVal = !prev.requireApproval;
|
package/package.json
CHANGED
|
@@ -66,6 +66,18 @@ export function detectInstallMethod(): InstallMethod {
|
|
|
66
66
|
return "npm";
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
// Determine if we're running as a compiled binary vs via an interpreter.
|
|
70
|
+
// Compiled Bun binaries have process.execPath pointing to the binary itself
|
|
71
|
+
// (e.g. ~/.local/bin/pensar), while interpreted scripts have it pointing to
|
|
72
|
+
// the runtime (e.g. ~/.bun/bin/bun or /usr/local/bin/node).
|
|
73
|
+
const execName = execPath.split("/").pop()?.replace(/\.exe$/, "") ?? "";
|
|
74
|
+
const isInterpreter =
|
|
75
|
+
execName === "bun" || execName === "node" || execName === "bun-debug";
|
|
76
|
+
|
|
77
|
+
if (!isInterpreter) {
|
|
78
|
+
return "binary";
|
|
79
|
+
}
|
|
80
|
+
|
|
69
81
|
const npmCheck = spawnSync(
|
|
70
82
|
"npm",
|
|
71
83
|
["list", "-g", "@pensar/apex", "--depth=0"],
|
|
@@ -177,7 +177,7 @@ describe("detectInstallMethod", () => {
|
|
|
177
177
|
expect(detectInstallMethod()).toBe("npm");
|
|
178
178
|
});
|
|
179
179
|
|
|
180
|
-
it("detects npm via spawnSync fallback when
|
|
180
|
+
it("detects npm via spawnSync fallback when running under interpreter", async () => {
|
|
181
181
|
Object.defineProperty(process, "execPath", {
|
|
182
182
|
value: "/usr/local/bin/node",
|
|
183
183
|
writable: true,
|
|
@@ -203,7 +203,7 @@ describe("detectInstallMethod", () => {
|
|
|
203
203
|
);
|
|
204
204
|
});
|
|
205
205
|
|
|
206
|
-
it("returns binary when all heuristics fail", async () => {
|
|
206
|
+
it("returns binary when all heuristics fail under interpreter", async () => {
|
|
207
207
|
Object.defineProperty(process, "execPath", {
|
|
208
208
|
value: "/usr/local/bin/node",
|
|
209
209
|
writable: true,
|
|
@@ -223,6 +223,38 @@ describe("detectInstallMethod", () => {
|
|
|
223
223
|
|
|
224
224
|
expect(detectInstallMethod()).toBe("binary");
|
|
225
225
|
});
|
|
226
|
+
|
|
227
|
+
it("returns binary for compiled binary even when npm global package exists", async () => {
|
|
228
|
+
Object.defineProperty(process, "execPath", {
|
|
229
|
+
value: "/home/user/.local/bin/pensar",
|
|
230
|
+
writable: true,
|
|
231
|
+
});
|
|
232
|
+
process.argv[1] = "upgrade";
|
|
233
|
+
|
|
234
|
+
const { spawnSync } = await import("child_process");
|
|
235
|
+
const mockedSpawnSync = vi.mocked(spawnSync);
|
|
236
|
+
mockedSpawnSync.mockReturnValue({
|
|
237
|
+
status: 0,
|
|
238
|
+
stdout: "└── @pensar/apex@0.1.0",
|
|
239
|
+
stderr: "",
|
|
240
|
+
pid: 0,
|
|
241
|
+
output: [],
|
|
242
|
+
signal: null,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
expect(detectInstallMethod()).toBe("binary");
|
|
246
|
+
expect(mockedSpawnSync).not.toHaveBeenCalled();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("returns binary for compiled binary without npm fallback", () => {
|
|
250
|
+
Object.defineProperty(process, "execPath", {
|
|
251
|
+
value: "/usr/local/bin/pensar",
|
|
252
|
+
writable: true,
|
|
253
|
+
});
|
|
254
|
+
process.argv[1] = "uninstall";
|
|
255
|
+
|
|
256
|
+
expect(detectInstallMethod()).toBe("binary");
|
|
257
|
+
});
|
|
226
258
|
});
|
|
227
259
|
|
|
228
260
|
// ---------------------------------------------------------------------------
|