@pensar/apex 0.0.101 → 0.0.103-canary.25377b86
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/build/auth.js +1 -1
- package/build/index.js +223 -55
- 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/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.103-canary.25377b86",
|
|
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.103-canary.25377b86",
|
|
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
|
|
|
@@ -194814,7 +194809,8 @@ var init_operator = __esm(() => {
|
|
|
194814
194809
|
|
|
194815
194810
|
// src/core/agents/offSecAgent/offensiveSecurityAgent.ts
|
|
194816
194811
|
import { join as join22 } from "path";
|
|
194817
|
-
import {
|
|
194812
|
+
import { mkdirSync as mkdirSync10, existsSync as existsSync21 } from "fs";
|
|
194813
|
+
import { writeFile as writeFile3 } from "fs/promises";
|
|
194818
194814
|
function wrapToolsWithApprovalGate(tools, gate) {
|
|
194819
194815
|
const wrapped = {};
|
|
194820
194816
|
for (const [name26, coreTool] of Object.entries(tools)) {
|
|
@@ -194968,13 +194964,11 @@ var init_offensiveSecurityAgent = __esm(() => {
|
|
|
194968
194964
|
stopWhen,
|
|
194969
194965
|
toolChoice: "auto",
|
|
194970
194966
|
onStepFinish: (event) => {
|
|
194971
|
-
|
|
194972
|
-
|
|
194973
|
-
|
|
194974
|
-
|
|
194975
|
-
|
|
194976
|
-
writeFileSync15(messagesPath, JSON.stringify(allMessages, null, 2));
|
|
194977
|
-
} catch {}
|
|
194967
|
+
const allMessages = [
|
|
194968
|
+
...initialMessagesRef.current,
|
|
194969
|
+
...event.response.messages
|
|
194970
|
+
];
|
|
194971
|
+
writeFile3(messagesPath, JSON.stringify(allMessages, null, 2)).catch(() => {});
|
|
194978
194972
|
input.onStepFinish?.(event);
|
|
194979
194973
|
},
|
|
194980
194974
|
onSummarized: () => {
|
|
@@ -195498,7 +195492,7 @@ var init_agent4 = __esm(() => {
|
|
|
195498
195492
|
});
|
|
195499
195493
|
|
|
195500
195494
|
// src/core/session/execution-metrics.ts
|
|
195501
|
-
import { existsSync as existsSync24, readFileSync as readFileSync11, writeFileSync as
|
|
195495
|
+
import { existsSync as existsSync24, readFileSync as readFileSync11, writeFileSync as writeFileSync15 } from "fs";
|
|
195502
195496
|
import { join as join25 } from "path";
|
|
195503
195497
|
function toNonNegativeInteger(value) {
|
|
195504
195498
|
const n = Number(value);
|
|
@@ -195544,7 +195538,7 @@ function writeSessionJsonTokenTotals(sessionRootPath, tokenUsage) {
|
|
|
195544
195538
|
const parsed = JSON.parse(readFileSync11(path6, "utf-8"));
|
|
195545
195539
|
parsed.tokensIn = tokenUsage.inputTokens;
|
|
195546
195540
|
parsed.tokensOut = tokenUsage.outputTokens;
|
|
195547
|
-
|
|
195541
|
+
writeFileSync15(path6, JSON.stringify(parsed, null, 2));
|
|
195548
195542
|
} catch {}
|
|
195549
195543
|
}
|
|
195550
195544
|
function writeExecutionMetrics(input) {
|
|
@@ -195559,7 +195553,7 @@ function writeExecutionMetrics(input) {
|
|
|
195559
195553
|
runtime: input.runtime ?? existing?.runtime,
|
|
195560
195554
|
updatedAt: new Date().toISOString()
|
|
195561
195555
|
};
|
|
195562
|
-
|
|
195556
|
+
writeFileSync15(metricsPath(input.sessionRootPath), JSON.stringify(next, null, 2), "utf-8");
|
|
195563
195557
|
writeSessionJsonTokenTotals(input.sessionRootPath, next.tokenUsage);
|
|
195564
195558
|
return next;
|
|
195565
195559
|
}
|
|
@@ -196154,7 +196148,7 @@ __export(exports_pentest, {
|
|
|
196154
196148
|
runPentestSwarm: () => runPentestSwarm,
|
|
196155
196149
|
DEFAULT_CONCURRENCY: () => DEFAULT_CONCURRENCY4
|
|
196156
196150
|
});
|
|
196157
|
-
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";
|
|
196158
196152
|
import { join as join26 } from "path";
|
|
196159
196153
|
function addUsageTotals(totals, usage) {
|
|
196160
196154
|
if (!usage)
|
|
@@ -196324,8 +196318,8 @@ async function runPentestWorkflow(input) {
|
|
|
196324
196318
|
});
|
|
196325
196319
|
const mdPath2 = join26(session.rootPath, REPORT_FILENAME_MD);
|
|
196326
196320
|
const jsonPath2 = join26(session.rootPath, REPORT_FILENAME_JSON);
|
|
196327
|
-
|
|
196328
|
-
|
|
196321
|
+
writeFileSync16(mdPath2, renderMarkdown(report2));
|
|
196322
|
+
writeFileSync16(jsonPath2, renderJson(report2));
|
|
196329
196323
|
return {
|
|
196330
196324
|
findings: [],
|
|
196331
196325
|
findingsPath: session.findingsPath,
|
|
@@ -196364,8 +196358,8 @@ async function runPentestWorkflow(input) {
|
|
|
196364
196358
|
});
|
|
196365
196359
|
const mdPath = join26(session.rootPath, REPORT_FILENAME_MD);
|
|
196366
196360
|
const jsonPath = join26(session.rootPath, REPORT_FILENAME_JSON);
|
|
196367
|
-
|
|
196368
|
-
|
|
196361
|
+
writeFileSync16(mdPath, renderMarkdown(report));
|
|
196362
|
+
writeFileSync16(jsonPath, renderJson(report));
|
|
196369
196363
|
return {
|
|
196370
196364
|
findings,
|
|
196371
196365
|
findingsPath: session.findingsPath,
|
|
@@ -274517,7 +274511,27 @@ function CommandProvider({
|
|
|
274517
274511
|
description: skill.description || "Skill"
|
|
274518
274512
|
});
|
|
274519
274513
|
}
|
|
274520
|
-
|
|
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
|
+
});
|
|
274521
274535
|
}, [router, skills]);
|
|
274522
274536
|
const executeCommand = import_react17.useCallback(async (input) => {
|
|
274523
274537
|
return await router.execute(input, ctx3);
|
|
@@ -275734,6 +275748,40 @@ function computeTab(suggestions, selectedIndex) {
|
|
|
275734
275748
|
function shouldResetHistory(historyIndex, isNavigatingHistory) {
|
|
275735
275749
|
return historyIndex !== -1 && !isNavigatingHistory;
|
|
275736
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
|
+
}
|
|
275737
275785
|
|
|
275738
275786
|
// src/tui/components/shared/use-paste-extmarks.ts
|
|
275739
275787
|
var import_react30 = __toESM(require_react(), 1);
|
|
@@ -275827,6 +275875,7 @@ var PromptInput = import_react31.forwardRef(function PromptInput2({
|
|
|
275827
275875
|
enableAutocomplete = false,
|
|
275828
275876
|
autocompleteOptions = [],
|
|
275829
275877
|
maxSuggestions = 10,
|
|
275878
|
+
maxVisibleSuggestions = 6,
|
|
275830
275879
|
enableCommands = false,
|
|
275831
275880
|
onCommandExecute,
|
|
275832
275881
|
commandHistory = [],
|
|
@@ -275992,34 +276041,72 @@ var PromptInput = import_react31.forwardRef(function PromptInput2({
|
|
|
275992
276041
|
setHistoryIndex(-1);
|
|
275993
276042
|
}
|
|
275994
276043
|
};
|
|
276044
|
+
const windowedView = import_react31.useMemo(() => computeVisibleWindow(suggestions, selectedSuggestionIndex, maxVisibleSuggestions), [suggestions, selectedSuggestionIndex, maxVisibleSuggestions]);
|
|
275995
276045
|
const suggestionsBox = suggestions.length > 0 && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
275996
276046
|
flexDirection: "column",
|
|
275997
276047
|
...autocompletePlacement === "above" ? { marginBottom: 1 } : { marginTop: 1 },
|
|
275998
|
-
children:
|
|
275999
|
-
|
|
276000
|
-
return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
276048
|
+
children: [
|
|
276049
|
+
windowedView.hasMore && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
276001
276050
|
flexDirection: "row",
|
|
276002
276051
|
gap: 1,
|
|
276003
276052
|
children: [
|
|
276004
276053
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
276005
|
-
fg:
|
|
276006
|
-
children:
|
|
276054
|
+
fg: colors2.textMuted,
|
|
276055
|
+
children: " ↑"
|
|
276007
276056
|
}, undefined, false, undefined, this),
|
|
276008
276057
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
276009
|
-
fg:
|
|
276010
|
-
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: " ↓"
|
|
276011
276098
|
}, undefined, false, undefined, this),
|
|
276012
|
-
|
|
276099
|
+
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
276013
276100
|
fg: colors2.textMuted,
|
|
276014
276101
|
children: [
|
|
276015
|
-
|
|
276016
|
-
|
|
276102
|
+
suggestions.length - windowedView.end,
|
|
276103
|
+
" more below..."
|
|
276017
276104
|
]
|
|
276018
276105
|
}, undefined, true, undefined, this)
|
|
276019
276106
|
]
|
|
276020
|
-
},
|
|
276021
|
-
|
|
276022
|
-
}, undefined,
|
|
276107
|
+
}, undefined, true, undefined, this)
|
|
276108
|
+
]
|
|
276109
|
+
}, undefined, true, undefined, this);
|
|
276023
276110
|
return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
276024
276111
|
flexDirection: "column",
|
|
276025
276112
|
children: [
|
|
@@ -279763,7 +279850,7 @@ function ResponsibleUseDisclosure({
|
|
|
279763
279850
|
}, undefined, false, undefined, this),
|
|
279764
279851
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
279765
279852
|
fg: colors2.text,
|
|
279766
|
-
children: "This penetration testing tool is
|
|
279853
|
+
children: "This penetration testing tool is designed for AUTHORIZED security testing only."
|
|
279767
279854
|
}, undefined, false, undefined, this),
|
|
279768
279855
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
279769
279856
|
flexDirection: "column",
|
|
@@ -279806,7 +279893,7 @@ function ResponsibleUseDisclosure({
|
|
|
279806
279893
|
flexDirection: "column",
|
|
279807
279894
|
children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
279808
279895
|
fg: colors2.error,
|
|
279809
|
-
children: "Unauthorized access to computer systems is
|
|
279896
|
+
children: "Unauthorized access to computer systems is illegal and may result in criminal prosecution."
|
|
279810
279897
|
}, undefined, false, undefined, this)
|
|
279811
279898
|
}, undefined, false, undefined, this),
|
|
279812
279899
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
|
|
@@ -280113,18 +280200,23 @@ function HelpDialog() {
|
|
|
280113
280200
|
});
|
|
280114
280201
|
};
|
|
280115
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
|
+
}
|
|
280116
280212
|
if (showDetail) {
|
|
280117
|
-
if (evt.name === "
|
|
280213
|
+
if (evt.name === "return") {
|
|
280118
280214
|
evt.preventDefault();
|
|
280119
280215
|
setShowDetail(false);
|
|
280120
280216
|
}
|
|
280121
280217
|
return;
|
|
280122
280218
|
}
|
|
280123
280219
|
switch (evt.name) {
|
|
280124
|
-
case "escape":
|
|
280125
|
-
evt.preventDefault();
|
|
280126
|
-
handleClose();
|
|
280127
|
-
break;
|
|
280128
280220
|
case "up":
|
|
280129
280221
|
case "k":
|
|
280130
280222
|
evt.preventDefault();
|
|
@@ -281179,9 +281271,10 @@ function createKeybindings(deps) {
|
|
|
281179
281271
|
return;
|
|
281180
281272
|
}
|
|
281181
281273
|
const isHome = route.data.type === "base" && route.data.path === "home";
|
|
281274
|
+
const isHelp = route.data.type === "base" && route.data.path === "help";
|
|
281182
281275
|
const isOperator = route.data.type === "base" && route.data.path === "operator";
|
|
281183
281276
|
const isSession = route.data.type === "pentest" || route.data.type === "operator";
|
|
281184
|
-
if (!isHome && !isOperator && !isSession) {
|
|
281277
|
+
if (!isHome && !isHelp && !isOperator && !isSession) {
|
|
281185
281278
|
route.navigate({
|
|
281186
281279
|
type: "base",
|
|
281187
281280
|
path: "home"
|
|
@@ -282925,6 +283018,15 @@ function getToolSummary(toolName, args) {
|
|
|
282925
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);
|
|
282926
283019
|
return firstArg ? `${toolName} ${String(firstArg).slice(0, 50)}` : toolName;
|
|
282927
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
|
+
}
|
|
282928
283030
|
function getArgsPreview(toolName, args, maxLength = 60) {
|
|
282929
283031
|
const filteredArgs = Object.entries(args).filter(([k3]) => !k3.toLowerCase().includes("description"));
|
|
282930
283032
|
if (filteredArgs.length === 0)
|
|
@@ -283758,7 +283860,9 @@ var ToolRenderer = import_react68.memo(function ToolRenderer2({
|
|
|
283758
283860
|
const isCompleted = message.status === "completed";
|
|
283759
283861
|
const isError = message.status === "error";
|
|
283760
283862
|
const { toolName, args, result, logs, subagentLogs } = message;
|
|
283761
|
-
const summary =
|
|
283863
|
+
const summary = getToolDisplayLabel(toolName, args, {
|
|
283864
|
+
preferDescription: isPending
|
|
283865
|
+
});
|
|
283762
283866
|
const resultDisplay = isCompleted || isError ? getResultSummary(result, toolName, args) : null;
|
|
283763
283867
|
const borderColor = isError ? colors2.error : isPending ? colors2.warning : colors2.info;
|
|
283764
283868
|
const hasSubagentLogs = subagentLogs && Object.keys(subagentLogs).length > 0;
|
|
@@ -286146,7 +286250,7 @@ function MessageList({
|
|
|
286146
286250
|
}, undefined, false, undefined, this),
|
|
286147
286251
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
286148
286252
|
fg: colors2.textMuted,
|
|
286149
|
-
children: " -
|
|
286253
|
+
children: " - Switch between Plan or Default mode"
|
|
286150
286254
|
}, undefined, false, undefined, this)
|
|
286151
286255
|
]
|
|
286152
286256
|
}, undefined, true, undefined, this)
|
|
@@ -286749,7 +286853,7 @@ function navigateDown(selectedIndex, queueLength) {
|
|
|
286749
286853
|
}
|
|
286750
286854
|
|
|
286751
286855
|
// src/tui/components/operator-dashboard/index.tsx
|
|
286752
|
-
import { existsSync as existsSync27, readFileSync as readFileSync14 } from "fs";
|
|
286856
|
+
import { existsSync as existsSync27, readFileSync as readFileSync14, writeFileSync as writeFileSync17 } from "fs";
|
|
286753
286857
|
import { join as join28 } from "path";
|
|
286754
286858
|
function OperatorDashboard({
|
|
286755
286859
|
sessionId,
|
|
@@ -286789,6 +286893,8 @@ function OperatorDashboard({
|
|
|
286789
286893
|
return filterOperatorAutocomplete(allAutocompleteOptions, skillSlugs);
|
|
286790
286894
|
}, [allAutocompleteOptions, skills]);
|
|
286791
286895
|
const [session, setSession] = import_react79.useState(null);
|
|
286896
|
+
const sessionRef = import_react79.useRef(null);
|
|
286897
|
+
sessionRef.current = session;
|
|
286792
286898
|
const [loading, setLoading] = import_react79.useState(true);
|
|
286793
286899
|
const [error40, setError] = import_react79.useState(null);
|
|
286794
286900
|
const pendingNameRef = import_react79.useRef(null);
|
|
@@ -286800,6 +286906,8 @@ function OperatorDashboard({
|
|
|
286800
286906
|
});
|
|
286801
286907
|
const commandCancelledRef = import_react79.useRef(false);
|
|
286802
286908
|
const [messages, setMessages] = import_react79.useState([]);
|
|
286909
|
+
const displayMessagesRef = import_react79.useRef([]);
|
|
286910
|
+
displayMessagesRef.current = messages;
|
|
286803
286911
|
const textRef = import_react79.useRef("");
|
|
286804
286912
|
const conversationRef = import_react79.useRef([]);
|
|
286805
286913
|
const [inputValue, setInputValue] = import_react79.useState("");
|
|
@@ -287183,6 +287291,12 @@ function OperatorDashboard({
|
|
|
287183
287291
|
{ role: "user", content: prompt }
|
|
287184
287292
|
];
|
|
287185
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
|
+
}
|
|
287186
287300
|
const onStepFinish = (event) => {
|
|
287187
287301
|
const nextUsage = accumulateTokenUsage(tokenUsageRef.current, event.usage?.inputTokens ?? 0, event.usage?.outputTokens ?? 0);
|
|
287188
287302
|
if (!nextUsage)
|
|
@@ -287200,22 +287314,32 @@ function OperatorDashboard({
|
|
|
287200
287314
|
};
|
|
287201
287315
|
const callbacks = {
|
|
287202
287316
|
onTextDelta: (d3) => {
|
|
287317
|
+
if (gen !== generationRef.current)
|
|
287318
|
+
return;
|
|
287203
287319
|
setThinking(false);
|
|
287204
287320
|
appendText(d3.text);
|
|
287205
287321
|
},
|
|
287206
287322
|
onToolCallStreaming: (d3) => {
|
|
287323
|
+
if (gen !== generationRef.current)
|
|
287324
|
+
return;
|
|
287207
287325
|
setThinking(false);
|
|
287208
287326
|
addStreamingToolCall(d3.toolCallId, d3.toolName);
|
|
287209
287327
|
},
|
|
287210
287328
|
onToolCallDelta: (d3) => {
|
|
287329
|
+
if (gen !== generationRef.current)
|
|
287330
|
+
return;
|
|
287211
287331
|
appendToolCallDelta(d3.toolCallId, d3.argsTextDelta);
|
|
287212
287332
|
},
|
|
287213
287333
|
onToolCall: (d3) => {
|
|
287334
|
+
if (gen !== generationRef.current)
|
|
287335
|
+
return;
|
|
287214
287336
|
setThinking(false);
|
|
287215
287337
|
commandCancelledRef.current = false;
|
|
287216
287338
|
addToolCall(d3.toolCallId, d3.toolName, d3.input);
|
|
287217
287339
|
},
|
|
287218
287340
|
onToolResult: (d3) => {
|
|
287341
|
+
if (gen !== generationRef.current)
|
|
287342
|
+
return;
|
|
287219
287343
|
flushCommandOutput();
|
|
287220
287344
|
if (cmdFlushTimerRef.current) {
|
|
287221
287345
|
clearInterval(cmdFlushTimerRef.current);
|
|
@@ -287275,6 +287399,8 @@ function OperatorDashboard({
|
|
|
287275
287399
|
callbacks,
|
|
287276
287400
|
onSessionReady: (s2) => {
|
|
287277
287401
|
setSessionCwd(s2.rootPath);
|
|
287402
|
+
sessionRef.current = s2;
|
|
287403
|
+
setSession((prev) => prev ?? s2);
|
|
287278
287404
|
}
|
|
287279
287405
|
};
|
|
287280
287406
|
try {
|
|
@@ -287520,19 +287646,61 @@ function OperatorDashboard({
|
|
|
287520
287646
|
setQueuedMessages([]);
|
|
287521
287647
|
queuedMessagesRef.current = [];
|
|
287522
287648
|
setSelectedQueueIndex(-1);
|
|
287649
|
+
approvalGateRef.current.denyAll();
|
|
287523
287650
|
setStatus("idle");
|
|
287524
287651
|
setThinking(false);
|
|
287525
287652
|
setIsExecuting(false);
|
|
287526
|
-
|
|
287527
|
-
if (
|
|
287653
|
+
const activeSession = sessionRef.current;
|
|
287654
|
+
if (activeSession) {
|
|
287528
287655
|
try {
|
|
287529
|
-
const messagesPath = join28(
|
|
287656
|
+
const messagesPath = join28(activeSession.rootPath, "messages.json");
|
|
287530
287657
|
if (existsSync27(messagesPath)) {
|
|
287531
287658
|
const raw = JSON.parse(readFileSync14(messagesPath, "utf-8"));
|
|
287532
287659
|
if (Array.isArray(raw) && raw.length > 0) {
|
|
287533
287660
|
conversationRef.current = sessions.getResumeMessages(raw);
|
|
287534
287661
|
}
|
|
287535
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
|
+
}
|
|
287536
287704
|
} catch {}
|
|
287537
287705
|
}
|
|
287538
287706
|
setMessages((prev) => {
|
|
@@ -287546,7 +287714,7 @@ function OperatorDashboard({
|
|
|
287546
287714
|
}
|
|
287547
287715
|
];
|
|
287548
287716
|
});
|
|
287549
|
-
}, [
|
|
287717
|
+
}, [setThinking, setIsExecuting]);
|
|
287550
287718
|
const toggleApproval = import_react79.useCallback(() => {
|
|
287551
287719
|
setOperatorState((prev) => {
|
|
287552
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
|
// ---------------------------------------------------------------------------
|