@pensar/apex 0.0.93 → 0.0.96-canary.2ce26987

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/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.93",
31974
+ version: "0.0.96-canary.2ce26987",
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",
@@ -31997,7 +31991,7 @@ var init_package = __esm(() => {
31997
31991
  "LICENSE"
31998
31992
  ],
31999
31993
  scripts: {
32000
- build: "bun build src/tui/index.tsx --outdir build --target node --format esm --external sharp",
31994
+ build: "bun build src/tui/index.tsx --outdir build --target node --format esm --external sharp && bun build src/cli/auth.ts --outdir build --target node --format esm",
32001
31995
  "generate:ascii": "bun run scripts/generate-ascii-art.ts",
32002
31996
  "generate:models": "bun run scripts/generate-models.ts",
32003
31997
  "build:binary": "bun run generate:ascii && bun build src/cli.ts --compile --outfile pensar",
@@ -37045,6 +37039,22 @@ var init_toolset = __esm(() => {
37045
37039
  detail: "Retrieve the full content of a saved memory by its id. Use List Memories first to discover available memory ids.",
37046
37040
  category: "utility",
37047
37041
  defaultEnabled: true
37042
+ },
37043
+ {
37044
+ id: "web_search",
37045
+ name: "Web Search",
37046
+ description: "Search the web",
37047
+ detail: "Search the web for real-time information about CVEs, security advisories, exploit techniques, and vulnerability details. Requires a Pensar account.",
37048
+ category: "reconnaissance",
37049
+ defaultEnabled: true
37050
+ },
37051
+ {
37052
+ id: "get_page",
37053
+ name: "Get Page",
37054
+ description: "Fetch page content",
37055
+ detail: "Fetch and extract readable content from a web page. Use after web_search to read full details from found URLs. Requires a Pensar account.",
37056
+ category: "reconnaissance",
37057
+ defaultEnabled: true
37048
37058
  }
37049
37059
  ];
37050
37060
  TOOLSETS = [
@@ -87793,6 +87803,26 @@ async function* parseSSE(stream) {
87793
87803
  }
87794
87804
  }
87795
87805
 
87806
+ // src/core/auth/signing.ts
87807
+ import { createHmac, createHash, randomUUID } from "crypto";
87808
+ function signGatewayRequest(signingKey, modelId, body) {
87809
+ const timestamp = String(Math.floor(Date.now() / 1000));
87810
+ const nonce = randomUUID();
87811
+ const bodyHash = createHash("sha256").update(body).digest("hex");
87812
+ const payload = `${timestamp}
87813
+ ${nonce}
87814
+ ${modelId}
87815
+ ${bodyHash}`;
87816
+ const signature = createHmac("sha256", signingKey).update(payload).digest("base64");
87817
+ return { signature, timestamp, nonce };
87818
+ }
87819
+ var init_signing = () => {};
87820
+
87821
+ // src/core/ai/providers/pensarSigning.ts
87822
+ var init_pensarSigning = __esm(() => {
87823
+ init_signing();
87824
+ });
87825
+
87796
87826
  // src/core/ai/providers/pensar.ts
87797
87827
  function log(...args) {
87798
87828
  if (DEBUG)
@@ -87911,7 +87941,17 @@ function createPensarModel(bedrockModelId, config2) {
87911
87941
  logInfo(`doStream → ${bedrockModelId} (${url2})`);
87912
87942
  log(` messages: ${body.messages?.length ?? 0}, tools: ${body.tools?.length ?? 0}`);
87913
87943
  const startTime = Date.now();
87944
+ const serializedBody = JSON.stringify({
87945
+ modelId: bedrockModelId,
87946
+ body
87947
+ });
87914
87948
  const headers = await buildHeaders();
87949
+ if (config2.signingKey) {
87950
+ const sig = signGatewayRequest(config2.signingKey, bedrockModelId, serializedBody);
87951
+ headers["X-Pensar-Signature"] = sig.signature;
87952
+ headers["X-Pensar-Timestamp"] = sig.timestamp;
87953
+ headers["X-Pensar-Nonce"] = sig.nonce;
87954
+ }
87915
87955
  log(` headers: ${Object.keys(headers).join(", ")}`);
87916
87956
  let response;
87917
87957
  try {
@@ -87919,10 +87959,7 @@ function createPensarModel(bedrockModelId, config2) {
87919
87959
  method: "POST",
87920
87960
  headers,
87921
87961
  signal: options.abortSignal,
87922
- body: JSON.stringify({
87923
- modelId: bedrockModelId,
87924
- body
87925
- })
87962
+ body: serializedBody
87926
87963
  });
87927
87964
  } catch (err) {
87928
87965
  logError(` SSE fetch failed (${Date.now() - startTime}ms):`, err);
@@ -88124,17 +88161,11 @@ function mapStopReason(reason) {
88124
88161
  }
88125
88162
  var DEBUG;
88126
88163
  var init_pensar2 = __esm(() => {
88164
+ init_pensarSigning();
88127
88165
  DEBUG = process.env.PENSAR_DEBUG === "1" || process.env.PENSAR_DEBUG === "true";
88128
88166
  });
88129
88167
 
88130
88168
  // src/core/api/constants.ts
88131
- var exports_constants = {};
88132
- __export(exports_constants, {
88133
- getPensarConsoleUrl: () => getPensarConsoleUrl,
88134
- getPensarApiUrl: () => getPensarApiUrl,
88135
- PENSAR_CONSOLE_BASE_URL: () => PENSAR_CONSOLE_BASE_URL,
88136
- PENSAR_API_BASE_URL: () => PENSAR_API_BASE_URL
88137
- });
88138
88169
  function getPensarApiUrl() {
88139
88170
  return PENSAR_API_BASE_URL;
88140
88171
  }
@@ -88154,7 +88185,7 @@ var init_config2 = __esm(() => {
88154
88185
  };
88155
88186
  });
88156
88187
 
88157
- // src/core/api/tokenRefresh.ts
88188
+ // src/core/auth/token.ts
88158
88189
  function decodeJwtPayload(token) {
88159
88190
  try {
88160
88191
  const parts = token.split(".");
@@ -88173,6 +88204,21 @@ function isTokenExpired(token, bufferSeconds = 60) {
88173
88204
  const nowSeconds = Math.floor(Date.now() / 1000);
88174
88205
  return payload.exp - nowSeconds < bufferSeconds;
88175
88206
  }
88207
+ async function fetchWorkOSClientId() {
88208
+ if (cachedClientId)
88209
+ return cachedClientId;
88210
+ try {
88211
+ const apiUrl = getPensarApiUrl();
88212
+ const response = await fetch(`${apiUrl}/api/cli/config`);
88213
+ if (!response.ok)
88214
+ return null;
88215
+ const data = await response.json();
88216
+ cachedClientId = data.workosClientId;
88217
+ return cachedClientId;
88218
+ } catch {
88219
+ return null;
88220
+ }
88221
+ }
88176
88222
  async function refreshAccessToken(clientId, refreshToken) {
88177
88223
  try {
88178
88224
  const response = await fetch("https://api.workos.com/user_management/authenticate", {
@@ -88219,27 +88265,203 @@ async function ensureValidToken(cfg) {
88219
88265
  }
88220
88266
  return null;
88221
88267
  }
88222
- async function fetchWorkOSClientId() {
88223
- if (cachedClientId)
88224
- return cachedClientId;
88268
+ var cachedClientId = null;
88269
+ var init_token = __esm(() => {
88270
+ init_config2();
88271
+ });
88272
+
88273
+ // src/core/auth/device-flow.ts
88274
+ function sleep2(ms) {
88275
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
88276
+ }
88277
+ async function startDeviceFlow(apiUrl) {
88278
+ const url2 = apiUrl ?? getPensarApiUrl();
88225
88279
  try {
88226
- const { getPensarApiUrl: getPensarApiUrl2 } = await Promise.resolve().then(() => exports_constants);
88227
- const apiUrl = getPensarApiUrl2();
88228
- const response = await fetch(`${apiUrl}/api/cli/config`);
88229
- if (!response.ok)
88230
- return null;
88231
- const data = await response.json();
88232
- cachedClientId = data.workosClientId;
88233
- return cachedClientId;
88234
- } catch {
88235
- return null;
88280
+ const configResponse = await fetch(`${url2}/api/cli/config`);
88281
+ if (configResponse.ok) {
88282
+ const cliConfig = await configResponse.json();
88283
+ const response2 = await fetch("https://api.workos.com/user_management/authorize/device", {
88284
+ method: "POST",
88285
+ headers: { "Content-Type": "application/json" },
88286
+ body: JSON.stringify({ client_id: cliConfig.workosClientId })
88287
+ });
88288
+ if (response2.ok) {
88289
+ const deviceInfo2 = await response2.json();
88290
+ return {
88291
+ mode: "workos",
88292
+ clientId: cliConfig.workosClientId,
88293
+ deviceInfo: deviceInfo2
88294
+ };
88295
+ }
88296
+ }
88297
+ } catch {}
88298
+ const response = await fetch(`${url2}/auth/device/code`, {
88299
+ method: "POST",
88300
+ headers: { "Content-Type": "application/json" }
88301
+ });
88302
+ if (!response.ok) {
88303
+ throw new Error("Failed to start device authorization");
88304
+ }
88305
+ const deviceInfo = await response.json();
88306
+ return { mode: "legacy", deviceInfo };
88307
+ }
88308
+ async function pollWorkOSToken(params) {
88309
+ const { clientId, deviceCode, interval, expiresIn, signal } = params;
88310
+ const deadline = Date.now() + expiresIn * 1000;
88311
+ while (Date.now() < deadline) {
88312
+ if (signal?.aborted)
88313
+ throw new Error("Authorization cancelled");
88314
+ await sleep2(interval * 1000);
88315
+ if (signal?.aborted)
88316
+ throw new Error("Authorization cancelled");
88317
+ try {
88318
+ const response = await fetch("https://api.workos.com/user_management/authenticate", {
88319
+ method: "POST",
88320
+ headers: { "Content-Type": "application/json" },
88321
+ body: JSON.stringify({
88322
+ client_id: clientId,
88323
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
88324
+ device_code: deviceCode
88325
+ })
88326
+ });
88327
+ if (response.status === 400) {
88328
+ continue;
88329
+ }
88330
+ if (!response.ok) {
88331
+ throw new Error("Authentication failed");
88332
+ }
88333
+ const data = await response.json();
88334
+ return {
88335
+ accessToken: data.access_token,
88336
+ refreshToken: data.refresh_token
88337
+ };
88338
+ } catch (err) {
88339
+ if (err instanceof Error && (err.message === "Authentication failed" || err.message === "Authorization cancelled")) {
88340
+ throw err;
88341
+ }
88342
+ }
88236
88343
  }
88344
+ throw new Error("Authorization timed out. Please try again.");
88237
88345
  }
88238
- var cachedClientId = null;
88239
- var init_tokenRefresh = __esm(() => {
88346
+ async function pollLegacyToken(params) {
88347
+ const { apiUrl, deviceCode, interval, expiresIn, signal } = params;
88348
+ const deadline = Date.now() + expiresIn * 1000;
88349
+ while (Date.now() < deadline) {
88350
+ if (signal?.aborted)
88351
+ throw new Error("Authorization cancelled");
88352
+ await sleep2(interval * 1000);
88353
+ if (signal?.aborted)
88354
+ throw new Error("Authorization cancelled");
88355
+ try {
88356
+ const response = await fetch(`${apiUrl}/auth/device/token`, {
88357
+ method: "POST",
88358
+ headers: { "Content-Type": "application/json" },
88359
+ body: JSON.stringify({ deviceCode })
88360
+ });
88361
+ if (!response.ok)
88362
+ continue;
88363
+ const data = await response.json();
88364
+ if (data.status === "complete" && data.apiKey) {
88365
+ return data;
88366
+ }
88367
+ if (data.status === "expired") {
88368
+ throw new Error("Authorization expired. Please try again.");
88369
+ }
88370
+ if (data.status === "not_found") {
88371
+ throw new Error("Invalid authorization session. Please try again.");
88372
+ }
88373
+ } catch (err) {
88374
+ if (signal?.aborted) {
88375
+ throw new Error("Authorization cancelled", { cause: err });
88376
+ }
88377
+ if (err instanceof Error && err.message.includes("Please try again")) {
88378
+ throw err;
88379
+ }
88380
+ }
88381
+ }
88382
+ throw new Error("Authorization timed out. Please try again.");
88383
+ }
88384
+ var init_device_flow = () => {};
88385
+
88386
+ // src/core/auth/workspaces.ts
88387
+ function sleep3(ms) {
88388
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
88389
+ }
88390
+ async function fetchWorkspaces(apiUrl, accessToken) {
88391
+ const response = await fetch(`${apiUrl}/api/cli/workspaces`, {
88392
+ headers: { Authorization: `Bearer ${accessToken}` }
88393
+ });
88394
+ if (!response.ok) {
88395
+ throw new Error(`Failed to fetch workspaces (${response.status})`);
88396
+ }
88397
+ const data = await response.json();
88398
+ return data.workspaces;
88399
+ }
88400
+ async function pollForWorkspaceCreation(apiUrl, accessToken, signal) {
88401
+ const POLL_INTERVAL = 3000;
88402
+ const TIMEOUT = 5 * 60 * 1000;
88403
+ const deadline = Date.now() + TIMEOUT;
88404
+ while (Date.now() < deadline) {
88405
+ if (signal?.aborted)
88406
+ throw new Error("Cancelled");
88407
+ await sleep3(POLL_INTERVAL);
88408
+ if (signal?.aborted)
88409
+ throw new Error("Cancelled");
88410
+ try {
88411
+ const response = await fetch(`${apiUrl}/api/cli/workspaces`, {
88412
+ headers: { Authorization: `Bearer ${accessToken}` }
88413
+ });
88414
+ if (!response.ok)
88415
+ continue;
88416
+ const data = await response.json();
88417
+ if (data.workspaces.length > 0) {
88418
+ return data.workspaces;
88419
+ }
88420
+ } catch {}
88421
+ }
88422
+ throw new Error("Workspace creation timed out. Please try again.");
88423
+ }
88424
+ async function selectWorkspace(apiUrl, accessToken, workspaceId) {
88425
+ const response = await fetch(`${apiUrl}/api/cli/select-workspace`, {
88426
+ method: "POST",
88427
+ headers: {
88428
+ "Content-Type": "application/json",
88429
+ Authorization: `Bearer ${accessToken}`
88430
+ },
88431
+ body: JSON.stringify({ workspaceId })
88432
+ });
88433
+ if (!response.ok) {
88434
+ throw new Error("Failed to select workspace");
88435
+ }
88436
+ return await response.json();
88437
+ }
88438
+
88439
+ // src/core/auth/connection.ts
88440
+ function isConnected(cfg) {
88441
+ return !!(cfg.accessToken || cfg.pensarAPIKey);
88442
+ }
88443
+ async function disconnect() {
88444
+ await config2.update({
88445
+ pensarAPIKey: null,
88446
+ accessToken: null,
88447
+ refreshToken: null,
88448
+ workspaceId: null,
88449
+ workspaceSlug: null,
88450
+ gatewaySigningKey: null
88451
+ });
88452
+ }
88453
+ var init_connection = __esm(() => {
88240
88454
  init_config2();
88241
88455
  });
88242
88456
 
88457
+ // src/core/auth/index.ts
88458
+ var init_auth = __esm(() => {
88459
+ init_signing();
88460
+ init_token();
88461
+ init_device_flow();
88462
+ init_connection();
88463
+ });
88464
+
88243
88465
  // src/core/ai/utils.ts
88244
88466
  function buildAuthConfig(cfg) {
88245
88467
  return {
@@ -88252,6 +88474,7 @@ function buildAuthConfig(cfg) {
88252
88474
  accessToken: cfg.accessToken ?? undefined,
88253
88475
  refreshToken: cfg.refreshToken ?? undefined,
88254
88476
  workspaceId: cfg.workspaceId ?? undefined,
88477
+ gatewaySigningKey: cfg.gatewaySigningKey ?? undefined,
88255
88478
  bedrock: cfg.bedrockAPIKey ? { apiKey: cfg.bedrockAPIKey } : undefined,
88256
88479
  local: cfg.localModelUrl ? { baseURL: cfg.localModelUrl } : undefined
88257
88480
  };
@@ -88332,7 +88555,8 @@ function getProviderModel(model, authConfig) {
88332
88555
  const modelConfig = {
88333
88556
  apiKey: pensarApiKey || authConfig?.accessToken || "",
88334
88557
  baseUrl: pensarApiUrl,
88335
- workspaceId: authConfig?.workspaceId
88558
+ workspaceId: authConfig?.workspaceId,
88559
+ signingKey: authConfig?.gatewaySigningKey
88336
88560
  };
88337
88561
  if (hasWorkOSAuth) {
88338
88562
  modelConfig.getToken = async () => {
@@ -88523,7 +88747,7 @@ var init_utils = __esm(() => {
88523
88747
  init_dist13();
88524
88748
  init_models();
88525
88749
  init_pensar2();
88526
- init_tokenRefresh();
88750
+ init_auth();
88527
88751
  init_config2();
88528
88752
  init_dist5();
88529
88753
  });
@@ -108198,7 +108422,7 @@ var init_cvssScorer = __esm(() => {
108198
108422
 
108199
108423
  // src/core/agents/offSecAgent/tools/documentFinding.ts
108200
108424
  import { join as join11 } from "path";
108201
- import { writeFileSync as writeFileSync7, appendFileSync as appendFileSync2 } from "fs";
108425
+ import { writeFileSync as writeFileSync7, appendFileSync as appendFileSync2, readFileSync as readFileSync7 } from "fs";
108202
108426
  function documentVulnerability(ctx4) {
108203
108427
  const { session } = ctx4;
108204
108428
  return tool({
@@ -108271,12 +108495,21 @@ FINDING STRUCTURE:
108271
108495
  };
108272
108496
  }
108273
108497
  }
108498
+ let pocOutput = null;
108499
+ if (input.pocPath) {
108500
+ try {
108501
+ const pocBasename = input.pocPath.replace(/^pocs\//, "");
108502
+ const outputPath = join11(session.pocsPath, `${pocBasename}.output.json`);
108503
+ pocOutput = JSON.parse(readFileSync7(outputPath, "utf-8"));
108504
+ } catch {}
108505
+ }
108274
108506
  const findingWithMeta = {
108275
108507
  ...finding,
108276
108508
  timestamp,
108277
108509
  sessionId: session.id,
108278
108510
  target: session.targets[0],
108279
108511
  ...evidenceFilePath && { evidenceFile: evidenceFilePath },
108512
+ ...pocOutput && { pocOutput },
108280
108513
  cwes: cvssResult.cwes,
108281
108514
  cvss: {
108282
108515
  score: cvssResult.score,
@@ -108515,6 +108748,8 @@ async function executeLocalPoc(ctx4, poc, currentAttempts) {
108515
108748
  try {
108516
108749
  unlinkSync(pocPath);
108517
108750
  } catch {}
108751
+ } else {
108752
+ writePocOutputSidecar(pocsPath, filename, stdout, stderr, exitCode, poc.description);
108518
108753
  }
108519
108754
  return {
108520
108755
  success: exitCode === 0,
@@ -108563,6 +108798,8 @@ async function executeSandboxPoc(ctx4, poc, currentAttempts) {
108563
108798
  try {
108564
108799
  unlinkSync(localPocPath);
108565
108800
  } catch {}
108801
+ } else {
108802
+ writePocOutputSidecar(localPocsPath, filename, result.stdout || "", result.stderr || "", result.exitCode, poc.description);
108566
108803
  }
108567
108804
  return {
108568
108805
  success: executionSuccess,
@@ -108584,6 +108821,19 @@ POC file has been deleted.`,
108584
108821
  };
108585
108822
  }
108586
108823
  }
108824
+ function writePocOutputSidecar(pocsPath, filename, stdout, stderr, exitCode, description) {
108825
+ try {
108826
+ const outputPath = join12(pocsPath, `${filename}.output.json`);
108827
+ writeFileSync8(outputPath, JSON.stringify({
108828
+ stdout,
108829
+ stderr,
108830
+ exitCode,
108831
+ executedAt: new Date().toISOString(),
108832
+ pocFile: filename,
108833
+ description
108834
+ }, null, 2));
108835
+ } catch {}
108836
+ }
108587
108837
  function preparePoc(poc, currentAttempts) {
108588
108838
  const extension = poc.pocType === "bash" ? ".sh" : poc.pocType === "python" ? ".py" : ".js";
108589
108839
  const sanitizedName = sanitizeFilename(poc.pocName);
@@ -109758,7 +110008,7 @@ If you encounter rate-limiting errors (e.g. "Rate limit exceeded", HTTP 429, "to
109758
110008
  `;
109759
110009
 
109760
110010
  // src/core/agents/specialized/authenticationAgent/agent.ts
109761
- import { existsSync as existsSync18, readFileSync as readFileSync7 } from "fs";
110011
+ import { existsSync as existsSync18, readFileSync as readFileSync8 } from "fs";
109762
110012
  import { join as join16 } from "path";
109763
110013
  function loadAuthResult(authDataPath) {
109764
110014
  if (!existsSync18(authDataPath)) {
@@ -109773,7 +110023,7 @@ function loadAuthResult(authDataPath) {
109773
110023
  };
109774
110024
  }
109775
110025
  try {
109776
- const raw = JSON.parse(readFileSync7(authDataPath, "utf-8"));
110026
+ const raw = JSON.parse(readFileSync8(authDataPath, "utf-8"));
109777
110027
  return {
109778
110028
  success: raw.authenticated ?? false,
109779
110029
  summary: raw.summary ?? "Authentication process completed.",
@@ -109894,7 +110144,9 @@ var init_agent = __esm(() => {
109894
110144
  "email_list_inboxes",
109895
110145
  "email_list_messages",
109896
110146
  "email_search_messages",
109897
- "email_get_message"
110147
+ "email_get_message",
110148
+ "web_search",
110149
+ "get_page"
109898
110150
  ],
109899
110151
  stopWhen: hasToolCall("complete_authentication"),
109900
110152
  resolveResult: () => {
@@ -111386,7 +111638,7 @@ var init_runAttackSurface = __esm(() => {
111386
111638
  });
111387
111639
 
111388
111640
  // src/core/findings/registry.ts
111389
- import { existsSync as existsSync20, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "fs";
111641
+ import { existsSync as existsSync20, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
111390
111642
  import { join as join20 } from "path";
111391
111643
  function extractVulnClass(title) {
111392
111644
  const t2 = title.trim();
@@ -111466,7 +111718,7 @@ class FindingsRegistry {
111466
111718
  const files = readdirSync3(findingsPath).filter((f) => f.endsWith(".json"));
111467
111719
  for (const file2 of files) {
111468
111720
  try {
111469
- const raw = readFileSync8(join20(findingsPath, file2), "utf-8");
111721
+ const raw = readFileSync9(join20(findingsPath, file2), "utf-8");
111470
111722
  const finding = JSON.parse(raw);
111471
111723
  if (finding && typeof finding.title === "string" && typeof finding.endpoint === "string") {
111472
111724
  registry2.indexFinding(finding);
@@ -111791,7 +112043,9 @@ var init_agent3 = __esm(() => {
111791
112043
  "list_files",
111792
112044
  "grep",
111793
112045
  "execute_command",
111794
- "document_asset"
112046
+ "document_asset",
112047
+ "web_search",
112048
+ "get_page"
111795
112049
  ];
111796
112050
  if (responseSchema2) {
111797
112051
  activeTools.push("response");
@@ -157509,7 +157763,7 @@ var require_gaxios = __commonJS((exports) => {
157509
157763
  var retry_js_1 = require_retry();
157510
157764
  var stream_1 = __require("stream");
157511
157765
  var interceptor_js_1 = require_interceptor();
157512
- var randomUUID = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
157766
+ var randomUUID2 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
157513
157767
  var HTTP_STATUS_NO_CONTENT = 204;
157514
157768
 
157515
157769
  class Gaxios {
@@ -157720,7 +157974,7 @@ var require_gaxios = __commonJS((exports) => {
157720
157974
  }
157721
157975
  const shouldDirectlyPassData = typeof opts.data === "string" || opts.data instanceof ArrayBuffer || opts.data instanceof Blob || globalThis.File && opts.data instanceof File || opts.data instanceof FormData || opts.data instanceof stream_1.Readable || opts.data instanceof ReadableStream || opts.data instanceof String || opts.data instanceof URLSearchParams || ArrayBuffer.isView(opts.data) || ["Blob", "File", "FormData"].includes(opts.data?.constructor?.name || "");
157722
157976
  if (opts.multipart?.length) {
157723
- const boundary = await randomUUID();
157977
+ const boundary = await randomUUID2();
157724
157978
  preparedHeaders.set("content-type", `multipart/related; boundary=${boundary}`);
157725
157979
  opts.body = stream_1.Readable.from(this.getMultipartRequest(opts.multipart, boundary));
157726
157980
  } else if (shouldDirectlyPassData) {
@@ -168699,7 +168953,7 @@ var require_apirequest = __commonJS((exports) => {
168699
168953
  var h22 = require_http2();
168700
168954
  var util_1 = require_util7();
168701
168955
  var pkg = require_package4();
168702
- var randomUUID = () => globalThis.crypto?.randomUUID() || __require("crypto").randomUUID();
168956
+ var randomUUID2 = () => globalThis.crypto?.randomUUID() || __require("crypto").randomUUID();
168703
168957
  function isReadableStream(obj) {
168704
168958
  return obj !== null && typeof obj === "object" && typeof obj.pipe === "function" && obj.readable !== false && typeof obj._read === "function" && typeof obj._readableState === "object";
168705
168959
  }
@@ -168772,7 +169026,7 @@ var require_apirequest = __commonJS((exports) => {
168772
169026
  authClient = undefined;
168773
169027
  }
168774
169028
  function multipartUpload(multipart) {
168775
- const boundary = randomUUID();
169029
+ const boundary = randomUUID2();
168776
169030
  const finale = `--${boundary}--`;
168777
169031
  const rStream = new stream.PassThrough({
168778
169032
  flush(callback) {
@@ -168811,7 +169065,7 @@ content-type: ${part["content-type"]}\r
168811
169065
  options.data = rStream;
168812
169066
  }
168813
169067
  function browserMultipartUpload(multipart) {
168814
- const boundary = randomUUID();
169068
+ const boundary = randomUUID2();
168815
169069
  const finale = `--${boundary}--`;
168816
169070
  headers["content-type"] = `multipart/related; boundary=${boundary}`;
168817
169071
  let content = "";
@@ -176322,7 +176576,7 @@ var require_quick_format_unescaped = __commonJS((exports, module2) => {
176322
176576
  // node_modules/atomic-sleep/index.js
176323
176577
  var require_atomic_sleep = __commonJS((exports, module2) => {
176324
176578
  if (typeof SharedArrayBuffer !== "undefined" && typeof Atomics !== "undefined") {
176325
- let sleep2 = function(ms) {
176579
+ let sleep4 = function(ms) {
176326
176580
  const valid = ms > 0 && ms < Infinity;
176327
176581
  if (valid === false) {
176328
176582
  if (typeof ms !== "number" && typeof ms !== "bigint") {
@@ -176333,9 +176587,9 @@ var require_atomic_sleep = __commonJS((exports, module2) => {
176333
176587
  Atomics.wait(nil, 0, 0, Number(ms));
176334
176588
  };
176335
176589
  const nil = new Int32Array(new SharedArrayBuffer(4));
176336
- module2.exports = sleep2;
176590
+ module2.exports = sleep4;
176337
176591
  } else {
176338
- let sleep2 = function(ms) {
176592
+ let sleep4 = function(ms) {
176339
176593
  const valid = ms > 0 && ms < Infinity;
176340
176594
  if (valid === false) {
176341
176595
  if (typeof ms !== "number" && typeof ms !== "bigint") {
@@ -176346,7 +176600,7 @@ var require_atomic_sleep = __commonJS((exports, module2) => {
176346
176600
  const target = Date.now() + Number(ms);
176347
176601
  while (target > Date.now()) {}
176348
176602
  };
176349
- module2.exports = sleep2;
176603
+ module2.exports = sleep4;
176350
176604
  }
176351
176605
  });
176352
176606
 
@@ -176356,7 +176610,7 @@ var require_sonic_boom = __commonJS((exports, module2) => {
176356
176610
  var EventEmitter11 = __require("events");
176357
176611
  var inherits = __require("util").inherits;
176358
176612
  var path6 = __require("path");
176359
- var sleep2 = require_atomic_sleep();
176613
+ var sleep4 = require_atomic_sleep();
176360
176614
  var assert2 = __require("assert");
176361
176615
  var BUSY_WRITE_TIMEOUT = 100;
176362
176616
  var kEmptyBuffer = Buffer.allocUnsafe(0);
@@ -176504,7 +176758,7 @@ var require_sonic_boom = __commonJS((exports, module2) => {
176504
176758
  if ((err.code === "EAGAIN" || err.code === "EBUSY") && this.retryEAGAIN(err, this._writingBuf.length, this._len - this._writingBuf.length)) {
176505
176759
  if (this.sync) {
176506
176760
  try {
176507
- sleep2(BUSY_WRITE_TIMEOUT);
176761
+ sleep4(BUSY_WRITE_TIMEOUT);
176508
176762
  this.release(undefined, 0);
176509
176763
  } catch (err2) {
176510
176764
  this.release(err2);
@@ -176818,7 +177072,7 @@ var require_sonic_boom = __commonJS((exports, module2) => {
176818
177072
  if (shouldRetry && !this.retryEAGAIN(err, buf.length, this._len - buf.length)) {
176819
177073
  throw err;
176820
177074
  }
176821
- sleep2(BUSY_WRITE_TIMEOUT);
177075
+ sleep4(BUSY_WRITE_TIMEOUT);
176822
177076
  }
176823
177077
  }
176824
177078
  try {
@@ -176854,7 +177108,7 @@ var require_sonic_boom = __commonJS((exports, module2) => {
176854
177108
  if (shouldRetry && !this.retryEAGAIN(err, buf.length, this._len - buf.length)) {
176855
177109
  throw err;
176856
177110
  }
176857
- sleep2(BUSY_WRITE_TIMEOUT);
177111
+ sleep4(BUSY_WRITE_TIMEOUT);
176858
177112
  }
176859
177113
  }
176860
177114
  }
@@ -177575,7 +177829,7 @@ var require_transport = __commonJS((exports, module2) => {
177575
177829
  var getCallers = require_caller();
177576
177830
  var { join: join22, isAbsolute: isAbsolute6, sep } = __require("node:path");
177577
177831
  var { fileURLToPath: fileURLToPath2 } = __require("node:url");
177578
- var sleep2 = require_atomic_sleep();
177832
+ var sleep4 = require_atomic_sleep();
177579
177833
  var onExit = require_on_exit_leak_free();
177580
177834
  var ThreadStream = require_thread_stream();
177581
177835
  function setupOnExit(stream) {
@@ -177698,7 +177952,7 @@ var require_transport = __commonJS((exports, module2) => {
177698
177952
  return;
177699
177953
  }
177700
177954
  stream.flushSync();
177701
- sleep2(100);
177955
+ sleep4(100);
177702
177956
  stream.end();
177703
177957
  }
177704
177958
  return stream;
@@ -187450,7 +187704,7 @@ var require_tools2 = __commonJS((exports, module2) => {
187450
187704
  var libmime = require_libmime();
187451
187705
  var { resolveCharset } = require_charsets2();
187452
187706
  var { compiler } = require_imap_handler();
187453
- var { createHash } = __require("crypto");
187707
+ var { createHash: createHash2 } = __require("crypto");
187454
187708
  var { JPDecoder } = require_jp_decoder();
187455
187709
  var iconv = require_lib10();
187456
187710
  var FLAG_COLORS = ["red", "orange", "yellow", "green", "blue", "purple", "grey"];
@@ -187757,7 +188011,7 @@ var require_tools2 = __commonJS((exports, module2) => {
187757
188011
  path6 = iconv.encode(path6, "utf-7-imap").toString();
187758
188012
  } catch {}
187759
188013
  }
187760
- map3.id = map3.emailId || createHash("md5").update([path6, mailbox.uidValidity?.toString() || "", map3.uid.toString()].join(":")).digest("hex");
188014
+ map3.id = map3.emailId || createHash2("md5").update([path6, mailbox.uidValidity?.toString() || "", map3.uid.toString()].join(":")).digest("hex");
187761
188015
  }
187762
188016
  if (map3.flags) {
187763
188017
  let flagColor = tools.getFlagColor(map3.flags);
@@ -193506,6 +193760,235 @@ var init_email = __esm(() => {
193506
193760
  ];
193507
193761
  });
193508
193762
 
193763
+ // src/core/agents/offSecAgent/tools/webSearch.ts
193764
+ function webSearch2(_ctx) {
193765
+ return tool({
193766
+ description: `Search the web for real-time information about any topic. Returns summarized information from search results.
193767
+
193768
+ USAGE GUIDANCE:
193769
+ - Use this tool to look up CVEs, security advisories, and vulnerability details
193770
+ - Search for exploit techniques, payloads, and bypass methods
193771
+ - Research target technologies, frameworks, and their known vulnerabilities
193772
+ - Find documentation for tools, APIs, and security testing techniques
193773
+ - Look up default credentials, common misconfigurations, and hardening guides
193774
+
193775
+ IMPORTANT: This tool requires a Pensar account. If you're not signed in, you'll receive an error message with instructions to sign in.
193776
+
193777
+ COMMON SEARCH PATTERNS:
193778
+ - "CVE-2024-XXXX exploit" — Find details about specific CVEs
193779
+ - "Apache Struts RCE vulnerability" — Research known vulnerabilities in specific software
193780
+ - "SSRF bypass techniques" — Find security testing techniques
193781
+ - "Spring Boot actuator default credentials" — Look up default credentials
193782
+ - "JWT token security vulnerabilities" — Research vulnerability classes`,
193783
+ inputSchema: webSearchInputSchema2,
193784
+ execute: async ({ query }) => {
193785
+ try {
193786
+ const cfg = await config2.get();
193787
+ const tokenResult = await ensureValidToken({
193788
+ accessToken: cfg.accessToken,
193789
+ refreshToken: cfg.refreshToken,
193790
+ pensarAPIKey: cfg.pensarAPIKey
193791
+ });
193792
+ if (!tokenResult) {
193793
+ return {
193794
+ success: false,
193795
+ results: [],
193796
+ error: "Web search requires a Pensar account. Please sign in to your Pensar account to use this feature. You can sign in via the TUI settings or by running 'pensar auth login'."
193797
+ };
193798
+ }
193799
+ if (!cfg.workspaceId) {
193800
+ return {
193801
+ success: false,
193802
+ results: [],
193803
+ error: "Web search requires a workspace. Please sign in to your Pensar account."
193804
+ };
193805
+ }
193806
+ if (!cfg.gatewaySigningKey) {
193807
+ return {
193808
+ success: false,
193809
+ results: [],
193810
+ error: "Web search requires authentication. Please sign in again to your Pensar account."
193811
+ };
193812
+ }
193813
+ const apiUrl = getPensarApiUrl();
193814
+ const body = JSON.stringify({ query });
193815
+ const { signature, timestamp, nonce } = signGatewayRequest(cfg.gatewaySigningKey, "web_search", body);
193816
+ const response = await fetch(`${apiUrl}/agents/web_search`, {
193817
+ method: "POST",
193818
+ headers: {
193819
+ "Content-Type": "application/json",
193820
+ Authorization: `Bearer ${tokenResult.token}`,
193821
+ "X-Workspace-Id": cfg.workspaceId,
193822
+ "X-Pensar-Timestamp": timestamp,
193823
+ "X-Pensar-Nonce": nonce,
193824
+ "X-Pensar-Signature": signature
193825
+ },
193826
+ body
193827
+ });
193828
+ if (!response.ok) {
193829
+ if (response.status === 401) {
193830
+ return {
193831
+ success: false,
193832
+ results: [],
193833
+ error: "Authentication failed. Please sign in again to your Pensar account."
193834
+ };
193835
+ }
193836
+ if (response.status === 429) {
193837
+ return {
193838
+ success: false,
193839
+ results: [],
193840
+ error: "Rate limit exceeded. Please wait a moment before searching again."
193841
+ };
193842
+ }
193843
+ const errorText = await response.text().catch(() => "Unknown error");
193844
+ return {
193845
+ success: false,
193846
+ results: [],
193847
+ error: `Web search failed: ${response.status} ${response.statusText}. ${errorText}`
193848
+ };
193849
+ }
193850
+ const data = await response.json();
193851
+ if (data.error) {
193852
+ return {
193853
+ success: false,
193854
+ results: [],
193855
+ error: data.error
193856
+ };
193857
+ }
193858
+ return {
193859
+ success: true,
193860
+ results: data.results || []
193861
+ };
193862
+ } catch (error40) {
193863
+ const errorMsg = error40 instanceof Error ? error40.message : String(error40);
193864
+ return {
193865
+ success: false,
193866
+ results: [],
193867
+ error: `Web search failed: ${errorMsg}`
193868
+ };
193869
+ }
193870
+ }
193871
+ });
193872
+ }
193873
+ var webSearchInputSchema2;
193874
+ var init_webSearch = __esm(() => {
193875
+ init_dist5();
193876
+ init_zod();
193877
+ init_config2();
193878
+ init_auth();
193879
+ webSearchInputSchema2 = exports_external.object({
193880
+ query: exports_external.string().describe("The search query to look up. Be specific and include relevant keywords for better results."),
193881
+ toolCallDescription: exports_external.string().describe("A concise, human-readable description of what this tool call is doing (e.g., 'Searching for CVE-2024-1234 details')")
193882
+ });
193883
+ });
193884
+
193885
+ // src/core/agents/offSecAgent/tools/getPage.ts
193886
+ function extractTitle(html) {
193887
+ const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/i);
193888
+ return titleMatch?.[1]?.trim();
193889
+ }
193890
+ function extractTextContent2(html) {
193891
+ let text2 = html;
193892
+ text2 = text2.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
193893
+ text2 = text2.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
193894
+ text2 = text2.replace(/<noscript[^>]*>[\s\S]*?<\/noscript>/gi, "");
193895
+ text2 = text2.replace(/<!--[\s\S]*?-->/g, "");
193896
+ text2 = text2.replace(/<[^>]+>/g, " ");
193897
+ text2 = text2.replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&apos;/g, "'");
193898
+ text2 = text2.replace(/\s+/g, " ").trim();
193899
+ const lines = text2.split(/[.\n]/).map((line) => line.trim()).filter((line) => line.length > 0);
193900
+ return lines.join(`
193901
+ `);
193902
+ }
193903
+ function getPage(ctx4) {
193904
+ return tool({
193905
+ description: `Fetch and extract readable content from a web page. Returns the page title and main text content.
193906
+
193907
+ USAGE GUIDANCE:
193908
+ - Use this tool to read full content from URLs found via web_search
193909
+ - Fetch CVE details, security advisories, and vulnerability write-ups
193910
+ - Read documentation, API references, and technical guides
193911
+ - Extract exploit code, payloads, and proof-of-concept details from security blogs
193912
+
193913
+ BEST PRACTICES:
193914
+ - First use web_search to find relevant URLs, then use get_page to read the full content
193915
+ - Prefer authoritative sources (NVD, vendor advisories, security researcher blogs)
193916
+ - For large pages, focus on the most relevant sections
193917
+ - If content is truncated, the important information is usually near the beginning`,
193918
+ inputSchema: getPageInputSchema,
193919
+ execute: async ({ url: url2 }) => {
193920
+ try {
193921
+ const controller = new AbortController;
193922
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);
193923
+ const combinedSignal = ctx4.abortSignal ? AbortSignal.any([ctx4.abortSignal, controller.signal]) : controller.signal;
193924
+ const response = await fetch(url2, {
193925
+ method: "GET",
193926
+ headers: {
193927
+ "User-Agent": "Mozilla/5.0 (compatible; PensarBot/1.0; +https://pensar.dev)",
193928
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
193929
+ "Accept-Language": "en-US,en;q=0.5"
193930
+ },
193931
+ signal: combinedSignal,
193932
+ redirect: "follow"
193933
+ });
193934
+ clearTimeout(timeoutId);
193935
+ if (!response.ok) {
193936
+ return {
193937
+ success: false,
193938
+ url: url2,
193939
+ error: `Failed to fetch page: ${response.status} ${response.statusText}`
193940
+ };
193941
+ }
193942
+ const contentType = response.headers.get("content-type") || "";
193943
+ if (!contentType.includes("text/html") && !contentType.includes("text/plain") && !contentType.includes("application/xhtml")) {
193944
+ return {
193945
+ success: false,
193946
+ url: url2,
193947
+ error: `Unsupported content type: ${contentType}. This tool only supports HTML and text pages.`
193948
+ };
193949
+ }
193950
+ const html = await response.text();
193951
+ const title = extractTitle(html);
193952
+ let content = extractTextContent2(html);
193953
+ if (content.length > MAX_CONTENT_LENGTH) {
193954
+ content = content.substring(0, MAX_CONTENT_LENGTH) + `
193955
+
193956
+ ... (content truncated — page exceeded maximum length)`;
193957
+ }
193958
+ return {
193959
+ success: true,
193960
+ url: url2,
193961
+ title,
193962
+ content
193963
+ };
193964
+ } catch (error40) {
193965
+ if (error40 instanceof Error && error40.name === "AbortError") {
193966
+ return {
193967
+ success: false,
193968
+ url: url2,
193969
+ error: ctx4.abortSignal?.aborted ? "Request aborted by user" : `Request timeout after ${REQUEST_TIMEOUT / 1000}s`
193970
+ };
193971
+ }
193972
+ const errorMsg = error40 instanceof Error ? error40.message : String(error40);
193973
+ return {
193974
+ success: false,
193975
+ url: url2,
193976
+ error: `Failed to fetch page: ${errorMsg}`
193977
+ };
193978
+ }
193979
+ }
193980
+ });
193981
+ }
193982
+ var MAX_CONTENT_LENGTH = 50000, REQUEST_TIMEOUT = 30000, getPageInputSchema;
193983
+ var init_getPage = __esm(() => {
193984
+ init_dist5();
193985
+ init_zod();
193986
+ getPageInputSchema = exports_external.object({
193987
+ url: exports_external.string().url().describe("The URL of the page to fetch and extract content from."),
193988
+ toolCallDescription: exports_external.string().describe("A concise, human-readable description of what this tool call is doing (e.g., 'Fetching CVE details from NVD')")
193989
+ });
193990
+ });
193991
+
193509
193992
  // src/core/agents/offSecAgent/tools/index.ts
193510
193993
  function createAllTools(ctx4) {
193511
193994
  return {
@@ -193541,7 +194024,9 @@ function createAllTools(ctx4) {
193541
194024
  email_list_inboxes: emailListInboxes(ctx4),
193542
194025
  email_list_messages: emailListMessages(ctx4),
193543
194026
  email_search_messages: emailSearchMessages(ctx4),
193544
- email_get_message: emailGetMessage(ctx4)
194027
+ email_get_message: emailGetMessage(ctx4),
194028
+ web_search: webSearch2(ctx4),
194029
+ get_page: getPage(ctx4)
193545
194030
  };
193546
194031
  }
193547
194032
  var ALL_TOOL_NAMES;
@@ -193576,6 +194061,8 @@ var init_tools = __esm(() => {
193576
194061
  init_listMemories();
193577
194062
  init_getMemory();
193578
194063
  init_email();
194064
+ init_webSearch();
194065
+ init_getPage();
193579
194066
  init_browserTools();
193580
194067
  init_executeCommand();
193581
194068
  init_httpRequest();
@@ -193609,6 +194096,8 @@ var init_tools = __esm(() => {
193609
194096
  init_listMessages();
193610
194097
  init_searchMessages();
193611
194098
  init_getMessage();
194099
+ init_webSearch();
194100
+ init_getPage();
193612
194101
  init_email();
193613
194102
  ALL_TOOL_NAMES = [
193614
194103
  "browser_navigate",
@@ -193645,7 +194134,9 @@ var init_tools = __esm(() => {
193645
194134
  "email_get_message",
193646
194135
  "email_search_messages",
193647
194136
  "email_get_attachments",
193648
- "email_mark_read"
194137
+ "email_mark_read",
194138
+ "web_search",
194139
+ "get_page"
193649
194140
  ];
193650
194141
  });
193651
194142
 
@@ -194617,7 +195108,9 @@ var init_blackboxAgent = __esm(() => {
194617
195108
  "email_list_inboxes",
194618
195109
  "email_list_messages",
194619
195110
  "email_search_messages",
194620
- "email_get_message"
195111
+ "email_get_message",
195112
+ "web_search",
195113
+ "get_page"
194621
195114
  ],
194622
195115
  stopWhen: [
194623
195116
  hasToolCall("create_attack_surface_report"),
@@ -194641,7 +195134,7 @@ var init_blackboxAgent = __esm(() => {
194641
195134
  });
194642
195135
 
194643
195136
  // src/core/agents/specialized/pentest/agent.ts
194644
- import { existsSync as existsSync23, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "fs";
195137
+ import { existsSync as existsSync23, readdirSync as readdirSync4, readFileSync as readFileSync10 } from "fs";
194645
195138
  import { join as join24 } from "path";
194646
195139
  function buildSystemPrompt(session) {
194647
195140
  return session.config?.exfilMode ? PENTEST_SYSTEM_PROMPT_EXFIL : PENTEST_SYSTEM_PROMPT_BASE;
@@ -194656,7 +195149,7 @@ function buildPrompt4(target, objectives, session, findingsRegistry) {
194656
195149
  const authDataPath = join24(sessionRootPath, "auth", "auth-data.json");
194657
195150
  if (existsSync23(authDataPath)) {
194658
195151
  try {
194659
- const raw = readFileSync9(authDataPath, "utf-8");
195152
+ const raw = readFileSync10(authDataPath, "utf-8");
194660
195153
  const authData = JSON.parse(raw);
194661
195154
  if (authData.authenticated) {
194662
195155
  const parts = [
@@ -194697,7 +195190,7 @@ ${existing}`;
194697
195190
  const markerPath = join24(sessionRootPath, "scratchpad", ".knowledge-populated");
194698
195191
  if (existsSync23(markerPath)) {
194699
195192
  try {
194700
- const count = parseInt(readFileSync9(markerPath, "utf-8").trim(), 10) || 0;
195193
+ const count = parseInt(readFileSync10(markerPath, "utf-8").trim(), 10) || 0;
194701
195194
  if (count > 0) {
194702
195195
  knowledgeBaseSection = `
194703
195196
 
@@ -194710,7 +195203,7 @@ There are ${count} project knowledge entries in the memory system from previous
194710
195203
  const knowledgePath = join24(sessionRootPath, "scratchpad", "knowledge-base.md");
194711
195204
  if (existsSync23(knowledgePath)) {
194712
195205
  try {
194713
- const knowledgeContent = readFileSync9(knowledgePath, "utf-8").trim();
195206
+ const knowledgeContent = readFileSync10(knowledgePath, "utf-8").trim();
194714
195207
  if (knowledgeContent) {
194715
195208
  knowledgeBaseSection = `
194716
195209
 
@@ -194730,21 +195223,25 @@ ${outcomeGuidance}
194730
195223
  Your target is a flag with the format FLAG{...}. Locate and extract it.
194731
195224
 
194732
195225
  ## Instructions
194733
- 1. State the objectives and outline your testing plan describe which techniques and payloads you will use for each objective before executing any tools
194734
- 2. Verify the target endpoint is reachable and understand its baseline behavior
194735
- 3. For each objective, research and craft targeted payloads appropriate to the technology
194736
- 4. Test systematically vary payloads, encoding, and bypass techniques
194737
- 5. Create POC scripts to reliably demonstrate any confirmed vulnerabilities
194738
- 6. When a vulnerability provides internal access, pivot through it to discover reachable services and extract sensitive data
194739
- 7. Document every confirmed vulnerability (using document_vulnerability) with evidence and remediation steps
194740
- 8. After testing ALL objectives and extracting the flag, call the response tool with your final summary. Do NOT call response until you have completed all testing and extraction.` : `## Instructions
194741
- 1. State the objectives and outline your testing plan describe which techniques and payloads you will use for each objective before executing any tools
194742
- 2. Verify the target endpoint is reachable and understand its baseline behavior
194743
- 3. For each objective, research and craft targeted payloads appropriate to the technology
194744
- 4. Test systematicallyvary payloads, encoding, and bypass techniques
194745
- 5. Create POC scripts to reliably demonstrate any confirmed vulnerabilities
194746
- 6. Document every confirmed vulnerability (using document_vulnerability) with evidence and remediation steps only if you have a working PoC
194747
- 7. After testing ALL objectives, call the response tool with your final summary
195226
+ 1. Call list_memories to review any prior knowledge relevant to this target or engagement
195227
+ 2. State the objectives and outline your testing plan — describe which techniques and payloads you will use for each objective before executing any tools
195228
+ 3. Verify the target endpoint is reachable and understand its baseline behavior
195229
+ 4. For each objective, research and craft targeted payloads appropriate to the technology
195230
+ 5. Test systematically vary payloads, encoding, and bypass techniques
195231
+ 6. Create POC scripts to reliably demonstrate any confirmed vulnerabilities
195232
+ 7. When a vulnerability provides internal access, pivot through it to discover reachable services and extract sensitive data
195233
+ 8. Document every confirmed vulnerability (using document_vulnerability) with evidence and remediation steps
195234
+ 9. Use add_memory to save reusable learnings from this engagement (effective techniques, target behaviors, technology details)
195235
+ 10. After testing ALL objectives and extracting the flag, call the response tool with your final summary. Do NOT call response until you have completed all testing and extraction.` : `## Instructions
195236
+ 1. Call list_memories to review any prior knowledge relevant to this target or engagement
195237
+ 2. State the objectives and outline your testing plan describe which techniques and payloads you will use for each objective before executing any tools
195238
+ 3. Verify the target endpoint is reachable and understand its baseline behavior
195239
+ 4. For each objective, research and craft targeted payloads appropriate to the technology
195240
+ 5. Test systematically vary payloads, encoding, and bypass techniques
195241
+ 6. Create POC scripts to reliably demonstrate any confirmed vulnerabilities
195242
+ 7. Document every confirmed vulnerability (using document_vulnerability) with evidence and remediation steps — only if you have a working PoC
195243
+ 8. Use add_memory to save reusable learnings from this engagement (effective techniques, target behaviors, technology details, false positive patterns)
195244
+ 9. After testing ALL objectives, call the response tool with your final summary
194748
195245
 
194749
195246
  Do NOT discover or enumerate other endpoints or services. Focus exclusively on the target and objectives above.`;
194750
195247
  return `# Testing Assignment
@@ -194766,7 +195263,7 @@ function loadFindings(findingsPath) {
194766
195263
  }
194767
195264
  return readdirSync4(findingsPath).filter((f3) => f3.endsWith(".json")).map((f3) => {
194768
195265
  try {
194769
- const content = readFileSync9(join24(findingsPath, f3), "utf-8");
195266
+ const content = readFileSync10(join24(findingsPath, f3), "utf-8");
194770
195267
  return JSON.parse(content);
194771
195268
  } catch {
194772
195269
  return null;
@@ -194785,22 +195282,26 @@ CRITICAL — Source Code Prohibition:
194785
195282
  - Your testing must rely exclusively on external interaction: sending requests, observing responses, and analyzing observable behavior.
194786
195283
 
194787
195284
  Your methodology:
194788
- 1. PLANBegin by stating the objectives you have been given and outlining your testing plan. For each objective, describe which attack techniques, payloads, and tools you intend to use. Output this plan as text before making any tool calls.
194789
- 2. VERIFYConfirm the target endpoint exists and is reachable. Understand its basic behavior (response format, parameters, auth requirements).
194790
- 3. PREPAREResearch applicable payloads and attack techniques for the given objectives. Craft payloads tailored to the target's technology and behavior.
194791
- 4. TESTExecute targeted attacks methodically, one payload/technique at a time. Observe responses carefully for indicators of vulnerability.
194792
- 5. EXPLOITWhen a vulnerability is confirmed, create a proof-of-concept script that reliably demonstrates it.
194793
- 6. DOCUMENTDocument every confirmed finding with evidence, impact assessment, and remediation steps.
194794
- 7. FINISHAfter testing ALL objectives, call the response tool to submit your final summary. You may document multiple findings before finishing.
195285
+ 1. ORIENTStart by calling list_memories to review any existing knowledge from previous engagements (target-specific notes, successful techniques, false positive patterns, technology context). Use relevant findings to inform your testing plan.
195286
+ 2. PLANState the objectives you have been given and outline your testing plan. For each objective, describe which attack techniques, payloads, and tools you intend to use. Output this plan as text before making any tool calls.
195287
+ 3. VERIFYConfirm the target endpoint exists and is reachable. Understand its basic behavior (response format, parameters, auth requirements).
195288
+ 4. PREPAREResearch applicable payloads and attack techniques for the given objectives. Craft payloads tailored to the target's technology and behavior.
195289
+ 5. TESTExecute targeted attacks methodically, one payload/technique at a time. Observe responses carefully for indicators of vulnerability.
195290
+ 6. EXPLOITWhen a vulnerability is confirmed, create a proof-of-concept script that reliably demonstrates it.
195291
+ 7. DOCUMENTDocument every confirmed finding with evidence, impact assessment, and remediation steps.
195292
+ 8. LEARN — Before finishing, use add_memory to persist reusable learnings from this engagement (e.g. target-specific behaviors, successful payload patterns, technology fingerprints, false positive patterns, credential formats). This knowledge helps future engagements be more efficient.
195293
+ 9. FINISH — After testing ALL objectives and saving learnings, call the response tool to submit your final summary. You may document multiple findings before finishing.
194795
195294
 
194796
195295
  Guidelines:
194797
- - Always start by stating your objectives and plan before executing any tools
195296
+ - Always call list_memories first to check for relevant knowledge before planning your approach
195297
+ - State your objectives and plan before executing any attack tools
194798
195298
  - Stay focused on the provided objectives — do not scan for other services or enumerate additional endpoints
194799
195299
  - Be methodical and thorough — test one payload at a time and observe the response
194800
195300
  - Use execute_command for crafting/running exploit scripts and http_request for targeted web tests
194801
195301
  - Always create POC scripts to confirm vulnerabilities before documenting
194802
195302
  - Document every confirmed vulnerability with document_vulnerability — you can document multiple vulnerabilities in a single run
194803
195303
  - Include clear remediation steps in every finding
195304
+ - Before finishing, use add_memory to save reusable learnings (target behaviors, effective techniques, technology details, false positive patterns)
194804
195305
  - When you have finished testing ALL objectives, call the response tool with a summary of your results. Do NOT call response until you have completed all testing.
194805
195306
 
194806
195307
  Rate Limiting:
@@ -194832,24 +195333,28 @@ Authentication:
194832
195333
  You are given a specific target and specific objectives. Your job is to deeply test the provided target, and when vulnerabilities are confirmed, pivot through them to discover and extract sensitive data.
194833
195334
 
194834
195335
  Your methodology:
194835
- 1. PLANBegin by stating the objectives you have been given and outlining your testing plan. For each objective, describe which attack techniques, payloads, and tools you intend to use. Output this plan as text before making any tool calls.
194836
- 2. VERIFYConfirm the target endpoint exists and is reachable. Understand its basic behavior (response format, parameters, auth requirements).
194837
- 3. PREPAREResearch applicable payloads and attack techniques for the given objectives. Craft payloads tailored to the target's technology and behavior.
194838
- 4. TESTExecute targeted attacks methodically, one payload/technique at a time. Observe responses carefully for indicators of vulnerability.
194839
- 5. EXPLOITWhen a vulnerability is confirmed, create a proof-of-concept script that reliably demonstrates it.
194840
- 6. PIVOT — When a vulnerability grants access to internal resources (e.g. SSRF, RCE, LFI), use it to discover and map what's accessible. Reason about what internal services, APIs, and data stores may exist behind the vulnerability — consider common internal hostnames, ports, and paths (e.g. internal DNS names in Docker/Kubernetes environments, common service ports, admin endpoints). Actively explore through the vulnerability to find reachable services.
194841
- 7. EXTRACTThe primary goal is to locate and extract a flag, secret, or sensitive data. Don't stop at proving the vulnerability exists demonstrate full impact by retrieving the target data through the confirmed attack vector.
194842
- 8. DOCUMENTDocument every confirmed finding with evidence, impact assessment, and remediation steps. You can document multiple findings before finishing.
194843
- 9. FINISHAfter testing ALL objectives and completing extraction, call the response tool to submit your final summary. Do NOT call response until you have completed all testing and extraction.
195336
+ 1. ORIENTStart by calling list_memories to review any existing knowledge from previous engagements (target-specific notes, successful techniques, false positive patterns, technology context). Use relevant findings to inform your testing plan.
195337
+ 2. PLANState the objectives you have been given and outline your testing plan. For each objective, describe which attack techniques, payloads, and tools you intend to use. Output this plan as text before making any tool calls.
195338
+ 3. VERIFYConfirm the target endpoint exists and is reachable. Understand its basic behavior (response format, parameters, auth requirements).
195339
+ 4. PREPAREResearch applicable payloads and attack techniques for the given objectives. Craft payloads tailored to the target's technology and behavior.
195340
+ 5. TESTExecute targeted attacks methodically, one payload/technique at a time. Observe responses carefully for indicators of vulnerability.
195341
+ 6. EXPLOIT — When a vulnerability is confirmed, create a proof-of-concept script that reliably demonstrates it.
195342
+ 7. PIVOTWhen a vulnerability grants access to internal resources (e.g. SSRF, RCE, LFI), use it to discover and map what's accessible. Reason about what internal services, APIs, and data stores may exist behind the vulnerability — consider common internal hostnames, ports, and paths (e.g. internal DNS names in Docker/Kubernetes environments, common service ports, admin endpoints). Actively explore through the vulnerability to find reachable services.
195343
+ 8. EXTRACTThe primary goal is to locate and extract a flag, secret, or sensitive data. Don't stop at proving the vulnerability exists — demonstrate full impact by retrieving the target data through the confirmed attack vector.
195344
+ 9. DOCUMENTDocument every confirmed finding with evidence, impact assessment, and remediation steps. You can document multiple findings before finishing.
195345
+ 10. LEARN — Before finishing, use add_memory to persist reusable learnings from this engagement (e.g. target-specific behaviors, successful payload patterns, technology fingerprints, false positive patterns, credential formats). This knowledge helps future engagements be more efficient.
195346
+ 11. FINISH — After testing ALL objectives, completing extraction, and saving learnings, call the response tool to submit your final summary. Do NOT call response until you have completed all testing and extraction.
194844
195347
 
194845
195348
  Guidelines:
194846
- - Always start by stating your objectives and plan before executing any tools
195349
+ - Always call list_memories first to check for relevant knowledge before planning your approach
195350
+ - State your objectives and plan before executing any attack tools
194847
195351
  - When you confirm a vulnerability that provides internal access, think through what lies behind it and actively explore through the vulnerability to maximize impact
194848
195352
  - Be methodical and thorough — test one payload at a time and observe the response
194849
195353
  - Use execute_command for crafting/running exploit scripts and http_request for targeted web tests
194850
195354
  - Always create POC scripts to confirm vulnerabilities before documenting
194851
195355
  - Document every confirmed vulnerability with document_vulnerability — you can document multiple vulnerabilities in a single run
194852
195356
  - Include clear remediation steps in every finding
195357
+ - Before finishing, use add_memory to save reusable learnings (target behaviors, effective techniques, technology details, false positive patterns)
194853
195358
  - When you have finished testing ALL objectives and extracting data, call the response tool with a summary of your results. Do NOT call response until you have completed all testing and extraction.
194854
195359
 
194855
195360
  Authentication:
@@ -194908,7 +195413,10 @@ var init_agent4 = __esm(() => {
194908
195413
  "email_search_messages",
194909
195414
  "email_get_message",
194910
195415
  "list_memories",
194911
- "get_memory"
195416
+ "get_memory",
195417
+ "add_memory",
195418
+ "web_search",
195419
+ "get_page"
194912
195420
  ],
194913
195421
  responseSchema: PentestResponseSchema,
194914
195422
  resolveResult: () => {
@@ -194925,7 +195433,7 @@ var init_agent4 = __esm(() => {
194925
195433
  });
194926
195434
 
194927
195435
  // src/core/session/execution-metrics.ts
194928
- import { existsSync as existsSync24, readFileSync as readFileSync10, writeFileSync as writeFileSync16 } from "fs";
195436
+ import { existsSync as existsSync24, readFileSync as readFileSync11, writeFileSync as writeFileSync16 } from "fs";
194929
195437
  import { join as join25 } from "path";
194930
195438
  function toNonNegativeInteger(value) {
194931
195439
  const n = Number(value);
@@ -194953,7 +195461,7 @@ function readExecutionMetrics(sessionRootPath) {
194953
195461
  if (!existsSync24(path6))
194954
195462
  return null;
194955
195463
  try {
194956
- const parsed = JSON.parse(readFileSync10(path6, "utf-8"));
195464
+ const parsed = JSON.parse(readFileSync11(path6, "utf-8"));
194957
195465
  return {
194958
195466
  tokenUsage: normalizeTokenUsage(parsed.tokenUsage),
194959
195467
  runtime: typeof parsed.runtime === "string" ? parsed.runtime : undefined,
@@ -194968,7 +195476,7 @@ function writeSessionJsonTokenTotals(sessionRootPath, tokenUsage) {
194968
195476
  if (!existsSync24(path6))
194969
195477
  return;
194970
195478
  try {
194971
- const parsed = JSON.parse(readFileSync10(path6, "utf-8"));
195479
+ const parsed = JSON.parse(readFileSync11(path6, "utf-8"));
194972
195480
  parsed.tokensIn = tokenUsage.inputTokens;
194973
195481
  parsed.tokensOut = tokenUsage.outputTokens;
194974
195482
  writeFileSync16(path6, JSON.stringify(parsed, null, 2));
@@ -195581,7 +196089,7 @@ __export(exports_pentest, {
195581
196089
  runPentestSwarm: () => runPentestSwarm,
195582
196090
  DEFAULT_CONCURRENCY: () => DEFAULT_CONCURRENCY4
195583
196091
  });
195584
- import { existsSync as existsSync25, readdirSync as readdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync17 } from "fs";
196092
+ import { existsSync as existsSync25, readdirSync as readdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync17 } from "fs";
195585
196093
  import { join as join26 } from "path";
195586
196094
  function addUsageTotals(totals, usage) {
195587
196095
  if (!usage)
@@ -195878,7 +196386,7 @@ function loadFindings2(findingsPath) {
195878
196386
  }
195879
196387
  return readdirSync5(findingsPath).filter((f3) => f3.endsWith(".json")).map((f3) => {
195880
196388
  try {
195881
- const content = readFileSync11(join26(findingsPath, f3), "utf-8");
196389
+ const content = readFileSync12(join26(findingsPath, f3), "utf-8");
195882
196390
  return JSON.parse(content);
195883
196391
  } catch {
195884
196392
  return null;
@@ -272501,7 +273009,7 @@ var useTerminalDimensions = () => {
272501
273009
  };
272502
273010
 
272503
273011
  // src/tui/index.tsx
272504
- var import_react87 = __toESM(require_react(), 1);
273012
+ var import_react88 = __toESM(require_react(), 1);
272505
273013
 
272506
273014
  // src/tui/components/footer.tsx
272507
273015
  import os6 from "os";
@@ -276900,6 +277408,7 @@ function WebWizard({
276900
277408
  const [headerNameInput, setHeaderNameInput] = import_react42.useState("");
276901
277409
  const [headerValueInput, setHeaderValueInput] = import_react42.useState("");
276902
277410
  const [error40, setError] = import_react42.useState(null);
277411
+ const [targetError, setTargetError] = import_react42.useState(null);
276903
277412
  async function createSessionAndNavigate() {
276904
277413
  if (!state.target.trim())
276905
277414
  return;
@@ -276968,20 +277477,36 @@ function WebWizard({
276968
277477
  return;
276969
277478
  if (currentStep === "target") {
276970
277479
  const maxTargetField = state.sourceCodeAccess ? 2 : 1;
276971
- if (key.name === "tab") {
277480
+ if (key.name === "tab" && !key.shift) {
276972
277481
  key.preventDefault();
276973
- if (key.shift) {
276974
- setTargetFocusedField((prev) => Math.max(0, prev - 1));
277482
+ if (state.target.trim()) {
277483
+ setTargetError(null);
277484
+ setCurrentStep("configure");
276975
277485
  } else {
276976
- if (targetFocusedField === maxTargetField && state.target.trim()) {
276977
- setCurrentStep("configure");
276978
- } else {
276979
- setTargetFocusedField((prev) => Math.min(maxTargetField, prev + 1));
276980
- }
277486
+ setTargetError("Target URL is required");
277487
+ }
277488
+ return;
277489
+ }
277490
+ if (key.name === "tab" && key.shift) {
277491
+ key.preventDefault();
277492
+ setTargetFocusedField((prev) => Math.max(0, prev - 1));
277493
+ return;
277494
+ }
277495
+ if (key.name === "down") {
277496
+ key.preventDefault();
277497
+ if (targetFocusedField < maxTargetField) {
277498
+ setTargetFocusedField((prev) => prev + 1);
277499
+ }
277500
+ return;
277501
+ }
277502
+ if (key.name === "up") {
277503
+ key.preventDefault();
277504
+ if (targetFocusedField > 0) {
277505
+ setTargetFocusedField((prev) => prev - 1);
276981
277506
  }
276982
277507
  return;
276983
277508
  }
276984
- if (targetFocusedField === 1 && (key.name === "up" || key.name === "down")) {
277509
+ if (key.sequence === " " && targetFocusedField === 1) {
276985
277510
  key.preventDefault();
276986
277511
  setState((prev) => ({
276987
277512
  ...prev,
@@ -277248,9 +277773,24 @@ function WebWizard({
277248
277773
  description: "e.g., https://example.com",
277249
277774
  placeholder: "https://example.com",
277250
277775
  value: state.target,
277251
- onInput: (v2) => setState((prev) => ({ ...prev, target: v2 })),
277776
+ onInput: (v2) => {
277777
+ setTargetError(null);
277778
+ setState((prev) => ({ ...prev, target: v2 }));
277779
+ },
277780
+ onSubmit: () => {
277781
+ if (state.target.trim()) {
277782
+ setTargetError(null);
277783
+ setCurrentStep("configure");
277784
+ } else {
277785
+ setTargetError("Target URL is required");
277786
+ }
277787
+ },
277252
277788
  focused: targetFocusedField === 0
277253
277789
  }, undefined, false, undefined, this),
277790
+ targetError && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
277791
+ fg: colors2.error,
277792
+ children: targetError
277793
+ }, undefined, false, undefined, this),
277254
277794
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
277255
277795
  flexDirection: "column",
277256
277796
  gap: 1,
@@ -277269,7 +277809,7 @@ function WebWizard({
277269
277809
  }, undefined, false, undefined, this),
277270
277810
  targetFocusedField === 1 && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
277271
277811
  fg: colors2.textMuted,
277272
- children: "(↑/↓ to toggle)"
277812
+ children: "(Space to toggle)"
277273
277813
  }, undefined, false, undefined, this)
277274
277814
  ]
277275
277815
  }, undefined, true, undefined, this),
@@ -277855,11 +278395,12 @@ function WebWizard({
277855
278395
  }
277856
278396
 
277857
278397
  // src/tui/components/commands/provider-manager.tsx
277858
- var import_react48 = __toESM(require_react(), 1);
278398
+ var import_react50 = __toESM(require_react(), 1);
277859
278399
  init_config2();
277860
278400
  // src/tui/components/commands/provider-selection.tsx
277861
278401
  var import_react45 = __toESM(require_react(), 1);
277862
278402
  function ProviderSelection({
278403
+ providers,
277863
278404
  onProviderSelected,
277864
278405
  onClose
277865
278406
  }) {
@@ -277867,6 +278408,7 @@ function ProviderSelection({
277867
278408
  const route = useRoute();
277868
278409
  const _config = useConfig();
277869
278410
  const [highlightedIndex, setHighlightedIndex] = import_react45.useState(0);
278411
+ const providerList = providers ?? AVAILABLE_PROVIDERS;
277870
278412
  const configuredProviders = getConfiguredProviders(_config.data);
277871
278413
  useKeyboard((key) => {
277872
278414
  if (key.name === "escape") {
@@ -277874,15 +278416,15 @@ function ProviderSelection({
277874
278416
  return;
277875
278417
  }
277876
278418
  if (key.name === "up") {
277877
- setHighlightedIndex((prev) => prev > 0 ? prev - 1 : AVAILABLE_PROVIDERS.length - 1);
278419
+ setHighlightedIndex((prev) => prev > 0 ? prev - 1 : providerList.length - 1);
277878
278420
  return;
277879
278421
  }
277880
278422
  if (key.name === "down") {
277881
- setHighlightedIndex((prev) => prev < AVAILABLE_PROVIDERS.length - 1 ? prev + 1 : 0);
278423
+ setHighlightedIndex((prev) => prev < providerList.length - 1 ? prev + 1 : 0);
277882
278424
  return;
277883
278425
  }
277884
278426
  if (key.name === "return") {
277885
- const selected = AVAILABLE_PROVIDERS[highlightedIndex];
278427
+ const selected = providerList[highlightedIndex];
277886
278428
  if (selected) {
277887
278429
  onProviderSelected(selected.id);
277888
278430
  }
@@ -277952,7 +278494,7 @@ function ProviderSelection({
277952
278494
  marginBottom: 1,
277953
278495
  children: "Popular providers"
277954
278496
  }, undefined, false, undefined, this),
277955
- AVAILABLE_PROVIDERS.map((provider, index) => {
278497
+ providerList.map((provider, index) => {
277956
278498
  const isHighlighted = index === highlightedIndex;
277957
278499
  const configured = configuredProviders.find((p) => p.id === provider.id)?.configured;
277958
278500
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
@@ -278139,12 +278681,649 @@ function APIKeyInput({
278139
278681
  }, undefined, false, undefined, this);
278140
278682
  }
278141
278683
 
278684
+ // src/tui/components/commands/auth-flow.tsx
278685
+ var import_react48 = __toESM(require_react(), 1);
278686
+ init_config2();
278687
+ init_auth();
278688
+ function AuthFlow({ onClose }) {
278689
+ const { colors: colors2 } = useTheme();
278690
+ const appConfig = useConfig();
278691
+ const alreadyConnected = isConnected(appConfig.data);
278692
+ const [step, setStep] = import_react48.useState(alreadyConnected ? "success" : "start");
278693
+ const [error40, setError] = import_react48.useState(null);
278694
+ const [flowInfo, setFlowInfo] = import_react48.useState(null);
278695
+ const [workspaces, setWorkspaces] = import_react48.useState([]);
278696
+ const [selectedWorkspace, setSelectedWorkspace] = import_react48.useState(null);
278697
+ const [selectedIndex, setSelectedIndex] = import_react48.useState(0);
278698
+ const [billingUrl, setBillingUrl] = import_react48.useState(null);
278699
+ const [balance, setBalance] = import_react48.useState(null);
278700
+ const abortRef = import_react48.useRef(null);
278701
+ const connectedWorkspace = appConfig.data.workspaceSlug ? {
278702
+ name: appConfig.data.workspaceSlug,
278703
+ slug: appConfig.data.workspaceSlug
278704
+ } : null;
278705
+ const goHome = () => {
278706
+ onClose();
278707
+ };
278708
+ const cleanup = () => {
278709
+ abortRef.current?.abort();
278710
+ abortRef.current = null;
278711
+ };
278712
+ import_react48.useEffect(() => {
278713
+ return cleanup;
278714
+ }, []);
278715
+ const openUrl = (url2) => {
278716
+ try {
278717
+ const platform = process.platform;
278718
+ if (platform === "darwin") {
278719
+ Bun.spawn(["open", url2]);
278720
+ } else if (platform === "win32") {
278721
+ Bun.spawn(["cmd", "/c", "start", url2]);
278722
+ } else {
278723
+ Bun.spawn(["xdg-open", url2]);
278724
+ }
278725
+ } catch {}
278726
+ };
278727
+ const startFlow = async () => {
278728
+ setStep("requesting");
278729
+ setError(null);
278730
+ cleanup();
278731
+ const ac = new AbortController;
278732
+ abortRef.current = ac;
278733
+ const apiUrl = getPensarApiUrl();
278734
+ try {
278735
+ const info = await startDeviceFlow(apiUrl);
278736
+ if (ac.signal.aborted)
278737
+ return;
278738
+ setFlowInfo(info);
278739
+ if (info.mode === "workos") {
278740
+ openUrl(info.deviceInfo.verification_uri_complete);
278741
+ } else {
278742
+ openUrl(info.deviceInfo.verificationUriComplete);
278743
+ }
278744
+ setStep("polling");
278745
+ if (info.mode === "workos") {
278746
+ handleWorkOSPoll(apiUrl, info, ac);
278747
+ } else {
278748
+ handleLegacyPoll(apiUrl, info, ac);
278749
+ }
278750
+ } catch (err) {
278751
+ if (ac.signal.aborted)
278752
+ return;
278753
+ setError(err instanceof Error ? err.message : "Failed to start authorization");
278754
+ setStep("error");
278755
+ }
278756
+ };
278757
+ const handleWorkOSPoll = async (apiUrl, info, ac) => {
278758
+ try {
278759
+ const tokens = await pollWorkOSToken({
278760
+ clientId: info.clientId,
278761
+ deviceCode: info.deviceInfo.device_code,
278762
+ interval: info.deviceInfo.interval,
278763
+ expiresIn: info.deviceInfo.expires_in,
278764
+ signal: ac.signal
278765
+ });
278766
+ if (ac.signal.aborted)
278767
+ return;
278768
+ await config2.update({
278769
+ accessToken: tokens.accessToken,
278770
+ refreshToken: tokens.refreshToken
278771
+ });
278772
+ await appConfig.reload();
278773
+ await handleFetchWorkspaces(apiUrl, tokens.accessToken, ac);
278774
+ } catch (err) {
278775
+ if (ac.signal.aborted)
278776
+ return;
278777
+ setError(err instanceof Error ? err.message : "Authentication failed");
278778
+ setStep("error");
278779
+ }
278780
+ };
278781
+ const handleLegacyPoll = async (apiUrl, info, ac) => {
278782
+ try {
278783
+ const data = await pollLegacyToken({
278784
+ apiUrl,
278785
+ deviceCode: info.deviceInfo.deviceCode,
278786
+ interval: info.deviceInfo.interval,
278787
+ expiresIn: info.deviceInfo.expiresIn,
278788
+ signal: ac.signal
278789
+ });
278790
+ if (ac.signal.aborted)
278791
+ return;
278792
+ await config2.update({
278793
+ pensarAPIKey: data.apiKey,
278794
+ gatewaySigningKey: data.signingKey ?? null
278795
+ });
278796
+ if (data.workspace) {
278797
+ await config2.update({
278798
+ workspaceId: data.workspace.id,
278799
+ workspaceSlug: data.workspace.slug
278800
+ });
278801
+ }
278802
+ appConfig.reload();
278803
+ setSelectedWorkspace(data.workspace ? {
278804
+ ...data.workspace,
278805
+ balance: data.credits?.balance ?? 0,
278806
+ hasPaymentMethod: true
278807
+ } : null);
278808
+ setBalance(data.credits?.balance ?? null);
278809
+ setStep("success");
278810
+ } catch (err) {
278811
+ if (ac.signal.aborted)
278812
+ return;
278813
+ setError(err instanceof Error ? err.message : "Authentication failed");
278814
+ setStep("error");
278815
+ }
278816
+ };
278817
+ const handleFetchWorkspaces = async (apiUrl, accessToken, ac) => {
278818
+ try {
278819
+ const ws = await fetchWorkspaces(apiUrl, accessToken);
278820
+ if (ac.signal.aborted)
278821
+ return;
278822
+ if (ws.length === 0) {
278823
+ const consoleUrl = getPensarConsoleUrl();
278824
+ openUrl(`${consoleUrl}/credits`);
278825
+ setStep("creating-workspace");
278826
+ handleWorkspaceCreationPoll(apiUrl, accessToken, ac);
278827
+ return;
278828
+ }
278829
+ if (ws.length === 1) {
278830
+ await handleSelectWorkspace(apiUrl, accessToken, ws[0], ac);
278831
+ return;
278832
+ }
278833
+ setWorkspaces(ws);
278834
+ setSelectedIndex(0);
278835
+ setStep("select-workspace");
278836
+ } catch (err) {
278837
+ if (ac.signal.aborted)
278838
+ return;
278839
+ setError(err instanceof Error ? err.message : "Failed to fetch workspaces");
278840
+ setStep("error");
278841
+ }
278842
+ };
278843
+ const handleWorkspaceCreationPoll = async (apiUrl, accessToken, ac) => {
278844
+ try {
278845
+ const ws = await pollForWorkspaceCreation(apiUrl, accessToken, ac.signal);
278846
+ if (ac.signal.aborted)
278847
+ return;
278848
+ if (ws.length === 1) {
278849
+ await handleSelectWorkspace(apiUrl, accessToken, ws[0], ac);
278850
+ } else {
278851
+ setWorkspaces(ws);
278852
+ setSelectedIndex(0);
278853
+ setStep("select-workspace");
278854
+ }
278855
+ } catch (err) {
278856
+ if (ac.signal.aborted)
278857
+ return;
278858
+ setError(err instanceof Error ? err.message : "Workspace creation timed out");
278859
+ setStep("error");
278860
+ }
278861
+ };
278862
+ const handleSelectWorkspace = async (apiUrl, accessToken, workspace, ac) => {
278863
+ setSelectedWorkspace(workspace);
278864
+ setStep("checking-billing");
278865
+ try {
278866
+ const data = await selectWorkspace(apiUrl, accessToken, workspace.id);
278867
+ if (ac.signal.aborted)
278868
+ return;
278869
+ await config2.update({
278870
+ workspaceId: workspace.id,
278871
+ workspaceSlug: workspace.slug,
278872
+ gatewaySigningKey: data.signingKey ?? null
278873
+ });
278874
+ appConfig.reload();
278875
+ setBalance(data.billing.balance);
278876
+ if (!data.confirmed && data.billingUrl) {
278877
+ setBillingUrl(data.billingUrl);
278878
+ }
278879
+ setStep("success");
278880
+ } catch (err) {
278881
+ if (ac.signal.aborted)
278882
+ return;
278883
+ setError(err instanceof Error ? err.message : "Failed to select workspace");
278884
+ setStep("error");
278885
+ }
278886
+ };
278887
+ const handleDisconnect = async () => {
278888
+ await disconnect();
278889
+ appConfig.reload();
278890
+ setFlowInfo(null);
278891
+ setSelectedWorkspace(null);
278892
+ setBalance(null);
278893
+ setBillingUrl(null);
278894
+ setStep("start");
278895
+ };
278896
+ const hasLowBalance = balance !== null && balance < 1;
278897
+ const effectiveBillingUrl = billingUrl || (selectedWorkspace?.slug ? `${getPensarConsoleUrl()}/${selectedWorkspace.slug}/settings/billing` : connectedWorkspace?.slug ? `${getPensarConsoleUrl()}/${connectedWorkspace.slug}/settings/billing` : `${getPensarConsoleUrl()}/credits`);
278898
+ const openBillingPage = () => {
278899
+ openUrl(effectiveBillingUrl);
278900
+ goHome();
278901
+ };
278902
+ const userCode = flowInfo?.mode === "workos" ? flowInfo.deviceInfo.user_code : flowInfo?.mode === "legacy" ? flowInfo.deviceInfo.userCode : null;
278903
+ const verificationUrl = flowInfo?.mode === "workos" ? flowInfo.deviceInfo.verification_uri_complete : flowInfo?.mode === "legacy" ? flowInfo.deviceInfo.verificationUriComplete : null;
278904
+ useKeyboard((key) => {
278905
+ key.preventDefault();
278906
+ if (key.name === "escape") {
278907
+ cleanup();
278908
+ goHome();
278909
+ return;
278910
+ }
278911
+ if (step === "start") {
278912
+ if (key.name === "return") {
278913
+ startFlow();
278914
+ }
278915
+ }
278916
+ if (step === "select-workspace") {
278917
+ if (key.name === "up" && selectedIndex > 0) {
278918
+ setSelectedIndex((i) => i - 1);
278919
+ }
278920
+ if (key.name === "down" && selectedIndex < workspaces.length - 1) {
278921
+ setSelectedIndex((i) => i + 1);
278922
+ }
278923
+ if (key.name === "return" && workspaces[selectedIndex]) {
278924
+ const currentConfig = appConfig.data;
278925
+ const apiUrl = getPensarApiUrl();
278926
+ const accessToken = currentConfig.accessToken;
278927
+ const ac = abortRef.current ?? new AbortController;
278928
+ handleSelectWorkspace(apiUrl, accessToken, workspaces[selectedIndex], ac);
278929
+ }
278930
+ }
278931
+ if (step === "error") {
278932
+ if (key.name === "return") {
278933
+ startFlow();
278934
+ }
278935
+ }
278936
+ if (step === "success") {
278937
+ if (key.name === "return") {
278938
+ if (hasLowBalance || billingUrl) {
278939
+ openBillingPage();
278940
+ } else {
278941
+ goHome();
278942
+ }
278943
+ }
278944
+ if (key.raw === "d" || key.raw === "D") {
278945
+ handleDisconnect();
278946
+ }
278947
+ }
278948
+ });
278949
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(Dialog, {
278950
+ size: "large",
278951
+ onClose: goHome,
278952
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
278953
+ flexDirection: "column",
278954
+ width: "100%",
278955
+ alignItems: "flex-start",
278956
+ padding: 1,
278957
+ children: [
278958
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
278959
+ marginBottom: 1,
278960
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
278961
+ fg: colors2.primary,
278962
+ children: "Pensar Console — Managed Inference"
278963
+ }, undefined, false, undefined, this)
278964
+ }, undefined, false, undefined, this),
278965
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
278966
+ marginBottom: 1,
278967
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
278968
+ fg: colors2.textMuted,
278969
+ children: [
278970
+ "Connect to Pensar Console for usage-based AI inference.",
278971
+ `
278972
+ `,
278973
+ "No API keys needed — just a Pensar account with credits."
278974
+ ]
278975
+ }, undefined, true, undefined, this)
278976
+ }, undefined, false, undefined, this),
278977
+ step === "start" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
278978
+ flexDirection: "column",
278979
+ gap: 1,
278980
+ children: [
278981
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
278982
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
278983
+ fg: colors2.text,
278984
+ children: [
278985
+ "Press ",
278986
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
278987
+ fg: colors2.primary,
278988
+ children: "[ENTER]"
278989
+ }, undefined, false, undefined, this),
278990
+ " to authorize via your browser."
278991
+ ]
278992
+ }, undefined, true, undefined, this)
278993
+ }, undefined, false, undefined, this),
278994
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
278995
+ marginTop: 1,
278996
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
278997
+ fg: colors2.textMuted,
278998
+ children: [
278999
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279000
+ fg: colors2.primary,
279001
+ children: "[ENTER]"
279002
+ }, undefined, false, undefined, this),
279003
+ " Connect ·",
279004
+ " ",
279005
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279006
+ fg: colors2.primary,
279007
+ children: "[ESC]"
279008
+ }, undefined, false, undefined, this),
279009
+ " Cancel"
279010
+ ]
279011
+ }, undefined, true, undefined, this)
279012
+ }, undefined, false, undefined, this)
279013
+ ]
279014
+ }, undefined, true, undefined, this),
279015
+ step === "requesting" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279016
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279017
+ fg: colors2.warning,
279018
+ children: "Starting authorization..."
279019
+ }, undefined, false, undefined, this)
279020
+ }, undefined, false, undefined, this),
279021
+ step === "polling" && flowInfo && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279022
+ flexDirection: "column",
279023
+ gap: 1,
279024
+ children: [
279025
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279026
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279027
+ fg: colors2.warning,
279028
+ children: "Waiting for browser authorization..."
279029
+ }, undefined, false, undefined, this)
279030
+ }, undefined, false, undefined, this),
279031
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279032
+ marginTop: 1,
279033
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279034
+ fg: colors2.text,
279035
+ children: [
279036
+ "Your code: ",
279037
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279038
+ fg: colors2.primary,
279039
+ children: userCode
279040
+ }, undefined, false, undefined, this)
279041
+ ]
279042
+ }, undefined, true, undefined, this)
279043
+ }, undefined, false, undefined, this),
279044
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279045
+ marginTop: 1,
279046
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279047
+ fg: colors2.textMuted,
279048
+ children: [
279049
+ "If the browser didn't open, visit:",
279050
+ `
279051
+ `,
279052
+ verificationUrl
279053
+ ]
279054
+ }, undefined, true, undefined, this)
279055
+ }, undefined, false, undefined, this),
279056
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279057
+ marginTop: 1,
279058
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279059
+ fg: colors2.textMuted,
279060
+ children: [
279061
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279062
+ fg: colors2.primary,
279063
+ children: "[ESC]"
279064
+ }, undefined, false, undefined, this),
279065
+ " Cancel"
279066
+ ]
279067
+ }, undefined, true, undefined, this)
279068
+ }, undefined, false, undefined, this)
279069
+ ]
279070
+ }, undefined, true, undefined, this),
279071
+ step === "select-workspace" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279072
+ flexDirection: "column",
279073
+ gap: 1,
279074
+ children: [
279075
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279076
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279077
+ fg: colors2.text,
279078
+ children: "Select a workspace:"
279079
+ }, undefined, false, undefined, this)
279080
+ }, undefined, false, undefined, this),
279081
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279082
+ flexDirection: "column",
279083
+ marginTop: 1,
279084
+ children: workspaces.map((ws, i) => /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279085
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279086
+ fg: i === selectedIndex ? colors2.primary : colors2.textMuted,
279087
+ children: [
279088
+ i === selectedIndex ? "▸ " : " ",
279089
+ ws.name,
279090
+ " ",
279091
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279092
+ fg: colors2.textMuted,
279093
+ children: [
279094
+ "(",
279095
+ ws.slug,
279096
+ ") — $",
279097
+ ws.balance.toFixed(2)
279098
+ ]
279099
+ }, undefined, true, undefined, this)
279100
+ ]
279101
+ }, undefined, true, undefined, this)
279102
+ }, ws.id, false, undefined, this))
279103
+ }, undefined, false, undefined, this),
279104
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279105
+ marginTop: 1,
279106
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279107
+ fg: colors2.textMuted,
279108
+ children: [
279109
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279110
+ fg: colors2.primary,
279111
+ children: "[↑/↓]"
279112
+ }, undefined, false, undefined, this),
279113
+ " Navigate ·",
279114
+ " ",
279115
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279116
+ fg: colors2.primary,
279117
+ children: "[ENTER]"
279118
+ }, undefined, false, undefined, this),
279119
+ " Select ·",
279120
+ " ",
279121
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279122
+ fg: colors2.primary,
279123
+ children: "[ESC]"
279124
+ }, undefined, false, undefined, this),
279125
+ " Cancel"
279126
+ ]
279127
+ }, undefined, true, undefined, this)
279128
+ }, undefined, false, undefined, this)
279129
+ ]
279130
+ }, undefined, true, undefined, this),
279131
+ step === "creating-workspace" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279132
+ flexDirection: "column",
279133
+ gap: 1,
279134
+ children: [
279135
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279136
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279137
+ fg: colors2.warning,
279138
+ children: "No workspaces found. Opening browser to create one..."
279139
+ }, undefined, false, undefined, this)
279140
+ }, undefined, false, undefined, this),
279141
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279142
+ marginTop: 1,
279143
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279144
+ fg: colors2.text,
279145
+ children: "Create a workspace and add credits in your browser."
279146
+ }, undefined, false, undefined, this)
279147
+ }, undefined, false, undefined, this),
279148
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279149
+ marginTop: 1,
279150
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279151
+ fg: colors2.textMuted,
279152
+ children: "Waiting for workspace creation..."
279153
+ }, undefined, false, undefined, this)
279154
+ }, undefined, false, undefined, this),
279155
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279156
+ marginTop: 1,
279157
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279158
+ fg: colors2.textMuted,
279159
+ children: [
279160
+ "If the browser didn't open, visit:",
279161
+ `
279162
+ `,
279163
+ getPensarConsoleUrl(),
279164
+ "/credits"
279165
+ ]
279166
+ }, undefined, true, undefined, this)
279167
+ }, undefined, false, undefined, this),
279168
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279169
+ marginTop: 1,
279170
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279171
+ fg: colors2.textMuted,
279172
+ children: [
279173
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279174
+ fg: colors2.primary,
279175
+ children: "[ESC]"
279176
+ }, undefined, false, undefined, this),
279177
+ " Cancel"
279178
+ ]
279179
+ }, undefined, true, undefined, this)
279180
+ }, undefined, false, undefined, this)
279181
+ ]
279182
+ }, undefined, true, undefined, this),
279183
+ step === "checking-billing" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279184
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279185
+ fg: colors2.warning,
279186
+ children: [
279187
+ "Checking billing for ",
279188
+ selectedWorkspace?.name,
279189
+ "..."
279190
+ ]
279191
+ }, undefined, true, undefined, this)
279192
+ }, undefined, false, undefined, this),
279193
+ step === "success" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279194
+ flexDirection: "column",
279195
+ gap: 1,
279196
+ children: [
279197
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279198
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279199
+ fg: colors2.success,
279200
+ children: "Connected to Pensar Console"
279201
+ }, undefined, false, undefined, this)
279202
+ }, undefined, false, undefined, this),
279203
+ (selectedWorkspace || connectedWorkspace) && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279204
+ flexDirection: "column",
279205
+ children: [
279206
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279207
+ fg: colors2.text,
279208
+ children: [
279209
+ "Workspace:",
279210
+ " ",
279211
+ selectedWorkspace?.name || connectedWorkspace?.name
279212
+ ]
279213
+ }, undefined, true, undefined, this),
279214
+ balance !== null && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279215
+ fg: colors2.text,
279216
+ children: [
279217
+ "Credits:",
279218
+ " ",
279219
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279220
+ fg: hasLowBalance ? colors2.warning : colors2.text,
279221
+ children: [
279222
+ "$",
279223
+ balance.toFixed(2)
279224
+ ]
279225
+ }, undefined, true, undefined, this)
279226
+ ]
279227
+ }, undefined, true, undefined, this)
279228
+ ]
279229
+ }, undefined, true, undefined, this),
279230
+ (hasLowBalance || billingUrl) && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279231
+ marginTop: 1,
279232
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279233
+ fg: colors2.warning,
279234
+ children: [
279235
+ billingUrl ? "Your workspace needs credits to use Apex CLI." : "Your credit balance is very low. We recommend at least $30 to run",
279236
+ `
279237
+ `,
279238
+ billingUrl ? "Press ENTER to open billing and add credits." : "pentests without interruptions. Press ENTER to open billing."
279239
+ ]
279240
+ }, undefined, true, undefined, this)
279241
+ }, undefined, false, undefined, this),
279242
+ !selectedWorkspace && !connectedWorkspace && appConfig.data.pensarAPIKey && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279243
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279244
+ fg: colors2.textMuted,
279245
+ children: "Already connected (legacy key saved in config)"
279246
+ }, undefined, false, undefined, this)
279247
+ }, undefined, false, undefined, this),
279248
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279249
+ marginTop: 1,
279250
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279251
+ fg: colors2.textMuted,
279252
+ children: "Pensar models are now available in the model selector."
279253
+ }, undefined, false, undefined, this)
279254
+ }, undefined, false, undefined, this),
279255
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279256
+ marginTop: 1,
279257
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279258
+ fg: colors2.textMuted,
279259
+ children: [
279260
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279261
+ fg: colors2.primary,
279262
+ children: "[ENTER]"
279263
+ }, undefined, false, undefined, this),
279264
+ " ",
279265
+ hasLowBalance || billingUrl ? "Open billing" : "Done",
279266
+ " ·",
279267
+ " ",
279268
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279269
+ fg: colors2.error,
279270
+ children: "[D]"
279271
+ }, undefined, false, undefined, this),
279272
+ " Disconnect ·",
279273
+ " ",
279274
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279275
+ fg: colors2.primary,
279276
+ children: "[ESC]"
279277
+ }, undefined, false, undefined, this),
279278
+ " Back"
279279
+ ]
279280
+ }, undefined, true, undefined, this)
279281
+ }, undefined, false, undefined, this)
279282
+ ]
279283
+ }, undefined, true, undefined, this),
279284
+ step === "error" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279285
+ flexDirection: "column",
279286
+ gap: 1,
279287
+ children: [
279288
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279289
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279290
+ fg: colors2.error,
279291
+ children: error40
279292
+ }, undefined, false, undefined, this)
279293
+ }, undefined, false, undefined, this),
279294
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279295
+ marginTop: 1,
279296
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279297
+ fg: colors2.textMuted,
279298
+ children: [
279299
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279300
+ fg: colors2.primary,
279301
+ children: "[ENTER]"
279302
+ }, undefined, false, undefined, this),
279303
+ " Try again ·",
279304
+ " ",
279305
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279306
+ fg: colors2.primary,
279307
+ children: "[ESC]"
279308
+ }, undefined, false, undefined, this),
279309
+ " Cancel"
279310
+ ]
279311
+ }, undefined, true, undefined, this)
279312
+ }, undefined, false, undefined, this)
279313
+ ]
279314
+ }, undefined, true, undefined, this)
279315
+ ]
279316
+ }, undefined, true, undefined, this)
279317
+ }, undefined, false, undefined, this);
279318
+ }
279319
+
278142
279320
  // src/tui/components/commands/provider-manager.tsx
278143
279321
  function ProviderManager() {
278144
279322
  const route = useRoute();
278145
279323
  const _config = useConfig();
278146
- const [flowState, setFlowState] = import_react48.useState("selecting");
278147
- const [selectedProvider, setSelectedProvider] = import_react48.useState(null);
279324
+ const isOnboarding = !hasAnyProviderConfigured(_config.data);
279325
+ const [flowState, setFlowState] = import_react50.useState(isOnboarding ? "choosing" : "selecting");
279326
+ const [selectedProvider, setSelectedProvider] = import_react50.useState(null);
278148
279327
  const handleProviderSelected = (providerId) => {
278149
279328
  setSelectedProvider(providerId);
278150
279329
  setFlowState("inputting");
@@ -278178,7 +279357,11 @@ function ProviderManager() {
278178
279357
  });
278179
279358
  };
278180
279359
  const handleAPIKeyCancel = () => {
278181
- setFlowState("selecting");
279360
+ if (isOnboarding) {
279361
+ setFlowState("choosing");
279362
+ } else {
279363
+ setFlowState("selecting");
279364
+ }
278182
279365
  setSelectedProvider(null);
278183
279366
  };
278184
279367
  const handleClose = () => {
@@ -278187,28 +279370,158 @@ function ProviderManager() {
278187
279370
  path: "home"
278188
279371
  });
278189
279372
  };
279373
+ const handleAuthClose = () => {
279374
+ route.navigate({ type: "base", path: "home" });
279375
+ };
279376
+ const otherProviders = AVAILABLE_PROVIDERS.filter((p) => p.id !== "pensar");
278190
279377
  const selectedProviderInfo = AVAILABLE_PROVIDERS.find((p) => p.id === selectedProvider);
278191
279378
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(import_jsx_dev_runtime2.Fragment, {
278192
279379
  children: [
279380
+ flowState === "choosing" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(OnboardingChoice, {
279381
+ onPensarSelected: () => setFlowState("auth"),
279382
+ onOtherSelected: () => setFlowState("selecting")
279383
+ }, undefined, false, undefined, this),
278193
279384
  flowState === "selecting" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ProviderSelection, {
279385
+ providers: isOnboarding ? otherProviders : undefined,
278194
279386
  onProviderSelected: handleProviderSelected,
278195
- onClose: handleClose
279387
+ onClose: isOnboarding ? () => setFlowState("choosing") : handleClose
278196
279388
  }, undefined, false, undefined, this),
278197
279389
  flowState === "inputting" && selectedProvider && selectedProviderInfo && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(APIKeyInput, {
278198
279390
  provider: selectedProvider,
278199
279391
  providerName: selectedProviderInfo.name,
278200
279392
  onSubmit: handleAPIKeySubmit,
278201
279393
  onCancel: handleAPIKeyCancel
279394
+ }, undefined, false, undefined, this),
279395
+ flowState === "auth" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(AuthFlow, {
279396
+ onClose: handleAuthClose
278202
279397
  }, undefined, false, undefined, this)
278203
279398
  ]
278204
279399
  }, undefined, true, undefined, this);
278205
279400
  }
279401
+ function OnboardingChoice({
279402
+ onPensarSelected,
279403
+ onOtherSelected
279404
+ }) {
279405
+ const { colors: colors2 } = useTheme();
279406
+ const [highlightedIndex, setHighlightedIndex] = import_react50.useState(0);
279407
+ const choices = [
279408
+ {
279409
+ label: "Pensar",
279410
+ description: "Managed inference — no API keys needed",
279411
+ action: onPensarSelected
279412
+ },
279413
+ {
279414
+ label: "Use other provider",
279415
+ description: "Connect with your own API key (Anthropic, OpenAI, etc.)",
279416
+ action: onOtherSelected
279417
+ }
279418
+ ];
279419
+ useKeyboard((key) => {
279420
+ if (key.name === "up") {
279421
+ setHighlightedIndex((prev) => prev > 0 ? prev - 1 : choices.length - 1);
279422
+ return;
279423
+ }
279424
+ if (key.name === "down") {
279425
+ setHighlightedIndex((prev) => prev < choices.length - 1 ? prev + 1 : 0);
279426
+ return;
279427
+ }
279428
+ if (key.name === "return") {
279429
+ choices[highlightedIndex].action();
279430
+ return;
279431
+ }
279432
+ });
279433
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279434
+ position: "absolute",
279435
+ top: 0,
279436
+ left: 0,
279437
+ zIndex: 1000,
279438
+ width: "100%",
279439
+ height: "100%",
279440
+ justifyContent: "center",
279441
+ alignItems: "center",
279442
+ backgroundColor: "transparent",
279443
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279444
+ width: 70,
279445
+ border: true,
279446
+ borderColor: colors2.primary,
279447
+ backgroundColor: colors2.backgroundPanel,
279448
+ flexDirection: "column",
279449
+ padding: 2,
279450
+ children: [
279451
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279452
+ flexDirection: "row",
279453
+ marginBottom: 2,
279454
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279455
+ fg: colors2.primary,
279456
+ children: "Get Started"
279457
+ }, undefined, false, undefined, this)
279458
+ }, undefined, false, undefined, this),
279459
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279460
+ marginBottom: 2,
279461
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279462
+ fg: colors2.textMuted,
279463
+ children: "Choose how to connect an AI provider."
279464
+ }, undefined, false, undefined, this)
279465
+ }, undefined, false, undefined, this),
279466
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279467
+ flexDirection: "column",
279468
+ gap: 1,
279469
+ children: choices.map((choice2, index) => {
279470
+ const isHighlighted = index === highlightedIndex;
279471
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279472
+ flexDirection: "column",
279473
+ paddingLeft: 1,
279474
+ paddingRight: 1,
279475
+ backgroundColor: isHighlighted ? colors2.backgroundSelected : undefined,
279476
+ onMouseDown: choice2.action,
279477
+ children: [
279478
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279479
+ fg: isHighlighted ? colors2.primary : colors2.text,
279480
+ children: [
279481
+ isHighlighted ? "▸ " : " ",
279482
+ choice2.label
279483
+ ]
279484
+ }, undefined, true, undefined, this),
279485
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279486
+ fg: colors2.textMuted,
279487
+ children: [
279488
+ " ",
279489
+ choice2.description
279490
+ ]
279491
+ }, undefined, true, undefined, this)
279492
+ ]
279493
+ }, choice2.label, true, undefined, this);
279494
+ })
279495
+ }, undefined, false, undefined, this),
279496
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279497
+ marginTop: 2,
279498
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279499
+ fg: colors2.textMuted,
279500
+ children: [
279501
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279502
+ fg: colors2.primary,
279503
+ children: "[↑↓]"
279504
+ }, undefined, false, undefined, this),
279505
+ " Navigate ·",
279506
+ " ",
279507
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279508
+ fg: colors2.primary,
279509
+ children: "[ENTER]"
279510
+ }, undefined, false, undefined, this),
279511
+ " Select"
279512
+ ]
279513
+ }, undefined, true, undefined, this)
279514
+ }, undefined, false, undefined, this)
279515
+ ]
279516
+ }, undefined, true, undefined, this)
279517
+ }, undefined, false, undefined, this);
279518
+ }
278206
279519
 
278207
279520
  // src/tui/index.tsx
278208
279521
  init_config2();
278209
279522
 
278210
279523
  // src/tui/components/switch.tsx
278211
- var import_react49 = __toESM(require_react(), 1);
279524
+ var import_react52 = __toESM(require_react(), 1);
278212
279525
  var CaseSymbol = Symbol("Switch.Case");
278213
279526
  var DefaultSymbol = Symbol("Switch.Default");
278214
279527
  function CaseComponent({
@@ -278231,8 +279544,8 @@ function SwitchComponent({
278231
279544
  }) {
278232
279545
  let matchedChild = null;
278233
279546
  let defaultChild = null;
278234
- import_react49.default.Children.forEach(children, (child) => {
278235
- if (import_react49.default.isValidElement(child)) {
279547
+ import_react52.default.Children.forEach(children, (child) => {
279548
+ if (import_react52.default.isValidElement(child)) {
278236
279549
  if (child.type[CaseSymbol]) {
278237
279550
  const caseChild = child;
278238
279551
  if (caseChild.props.when === condition) {
@@ -278345,8 +279658,8 @@ function ResponsibleUseDisclosure({
278345
279658
  }
278346
279659
 
278347
279660
  // src/tui/context/toast.tsx
278348
- var import_react51 = __toESM(require_react(), 1);
278349
- var ToastContext = import_react51.createContext(null);
279661
+ var import_react54 = __toESM(require_react(), 1);
279662
+ var ToastContext = import_react54.createContext(null);
278350
279663
  var nextId = 0;
278351
279664
  var DEFAULT_DURATION = {
278352
279665
  default: 3000,
@@ -278354,24 +279667,24 @@ var DEFAULT_DURATION = {
278354
279667
  error: 5000
278355
279668
  };
278356
279669
  function ToastProvider({ children }) {
278357
- const [toasts, setToasts] = import_react51.useState([]);
278358
- const dismiss = import_react51.useCallback((id) => {
279670
+ const [toasts, setToasts] = import_react54.useState([]);
279671
+ const dismiss = import_react54.useCallback((id) => {
278359
279672
  setToasts((prev) => prev.filter((t2) => t2.id !== id));
278360
279673
  }, []);
278361
- const toast = import_react51.useCallback((message, variant = "default", duration3) => {
279674
+ const toast = import_react54.useCallback((message, variant = "default", duration3) => {
278362
279675
  const id = nextId++;
278363
279676
  const ms = duration3 ?? DEFAULT_DURATION[variant];
278364
279677
  setToasts((prev) => [...prev, { id, message, variant, duration: ms }]);
278365
279678
  setTimeout(() => dismiss(id), ms);
278366
279679
  }, [dismiss]);
278367
- const value = import_react51.useMemo(() => ({ toasts, toast, dismiss }), [toasts, toast, dismiss]);
279680
+ const value = import_react54.useMemo(() => ({ toasts, toast, dismiss }), [toasts, toast, dismiss]);
278368
279681
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ToastContext.Provider, {
278369
279682
  value,
278370
279683
  children
278371
279684
  }, undefined, false, undefined, this);
278372
279685
  }
278373
279686
  function useToast() {
278374
- const ctx4 = import_react51.useContext(ToastContext);
279687
+ const ctx4 = import_react54.useContext(ToastContext);
278375
279688
  if (!ctx4)
278376
279689
  throw new Error("useToast() must be used within <ToastProvider>");
278377
279690
  return ctx4;
@@ -278444,11 +279757,11 @@ function ToastContainer() {
278444
279757
  }
278445
279758
 
278446
279759
  // src/tui/components/error-boundary.tsx
278447
- var import_react52 = __toESM(require_react(), 1);
279760
+ var import_react55 = __toESM(require_react(), 1);
278448
279761
  var MAX_ERRORS = 3;
278449
279762
  var ERROR_WINDOW_MS = 5000;
278450
279763
 
278451
- class ErrorBoundaryInner extends import_react52.default.Component {
279764
+ class ErrorBoundaryInner extends import_react55.default.Component {
278452
279765
  state = {
278453
279766
  hasError: false,
278454
279767
  errorTimestamps: [],
@@ -278479,10 +279792,10 @@ class ErrorBoundaryInner extends import_react52.default.Component {
278479
279792
  }
278480
279793
  function ErrorBoundary2({ children }) {
278481
279794
  const { toast } = useToast();
278482
- const handleError = import_react52.useCallback((message) => {
279795
+ const handleError = import_react55.useCallback((message) => {
278483
279796
  toast(message, "error");
278484
279797
  }, [toast]);
278485
- return import_react52.default.createElement(ErrorBoundaryInner, { onError: handleError }, children);
279798
+ return import_react55.default.createElement(ErrorBoundaryInner, { onError: handleError }, children);
278486
279799
  }
278487
279800
 
278488
279801
  // src/tui/index.tsx
@@ -278593,16 +279906,16 @@ function ShortcutsDialog({
278593
279906
  }
278594
279907
 
278595
279908
  // src/tui/components/commands/help-dialog.tsx
278596
- var import_react54 = __toESM(require_react(), 1);
279909
+ var import_react57 = __toESM(require_react(), 1);
278597
279910
  function HelpDialog() {
278598
279911
  const { colors: colors2 } = useTheme();
278599
279912
  const { commands: commands2 } = useCommand();
278600
279913
  const route = useRoute();
278601
279914
  const dimensions = useDimensions();
278602
- const [selectedIndex, setSelectedIndex] = import_react54.useState(0);
278603
- const [showDetail, setShowDetail] = import_react54.useState(false);
278604
- const scrollboxRef = import_react54.useRef(null);
278605
- const commandsByCategory = import_react54.useMemo(() => {
279915
+ const [selectedIndex, setSelectedIndex] = import_react57.useState(0);
279916
+ const [showDetail, setShowDetail] = import_react57.useState(false);
279917
+ const scrollboxRef = import_react57.useRef(null);
279918
+ const commandsByCategory = import_react57.useMemo(() => {
278606
279919
  const grouped = {};
278607
279920
  for (const cmd of commands2) {
278608
279921
  const category = cmd.category || "Other";
@@ -278613,15 +279926,15 @@ function HelpDialog() {
278613
279926
  }
278614
279927
  return grouped;
278615
279928
  }, [commands2]);
278616
- const flatCommands = import_react54.useMemo(() => {
279929
+ const flatCommands = import_react57.useMemo(() => {
278617
279930
  return commands2;
278618
279931
  }, [commands2]);
278619
- import_react54.useEffect(() => {
279932
+ import_react57.useEffect(() => {
278620
279933
  if (selectedIndex >= flatCommands.length) {
278621
279934
  setSelectedIndex(Math.max(0, flatCommands.length - 1));
278622
279935
  }
278623
279936
  }, [flatCommands.length, selectedIndex]);
278624
- import_react54.useEffect(() => {
279937
+ import_react57.useEffect(() => {
278625
279938
  scrollToIndex(scrollboxRef.current, selectedIndex, flatCommands, (cmd) => cmd.name);
278626
279939
  }, [selectedIndex, flatCommands]);
278627
279940
  const handleClose = () => {
@@ -279055,677 +280368,16 @@ function ModelsDisplay() {
279055
280368
  }, undefined, true, undefined, this);
279056
280369
  }
279057
280370
 
279058
- // src/tui/components/commands/auth-flow.tsx
279059
- var import_react57 = __toESM(require_react(), 1);
279060
- init_config2();
279061
- function AuthFlow({ onClose }) {
279062
- const { colors: colors2 } = useTheme();
279063
- const appConfig = useConfig();
279064
- const isConnected = !!(appConfig.data.accessToken || appConfig.data.pensarAPIKey);
279065
- const [step, setStep] = import_react57.useState(isConnected ? "success" : "start");
279066
- const [error40, setError] = import_react57.useState(null);
279067
- const [authMode, setAuthMode] = import_react57.useState(null);
279068
- const [deviceInfo, setDeviceInfo] = import_react57.useState(null);
279069
- const [legacyDeviceInfo, setLegacyDeviceInfo] = import_react57.useState(null);
279070
- const [workspaces, setWorkspaces] = import_react57.useState([]);
279071
- const [selectedWorkspace, setSelectedWorkspace] = import_react57.useState(null);
279072
- const [selectedIndex, setSelectedIndex] = import_react57.useState(0);
279073
- const [billingUrl, setBillingUrl] = import_react57.useState(null);
279074
- const [balance, setBalance] = import_react57.useState(null);
279075
- const pollingRef = import_react57.useRef(null);
279076
- const cancelledRef = import_react57.useRef(false);
279077
- const connectedWorkspace = appConfig.data.workspaceSlug ? { name: appConfig.data.workspaceSlug, slug: appConfig.data.workspaceSlug } : null;
279078
- const goHome = () => {
279079
- onClose();
279080
- };
279081
- const cleanup = () => {
279082
- cancelledRef.current = true;
279083
- if (pollingRef.current) {
279084
- clearTimeout(pollingRef.current);
279085
- pollingRef.current = null;
279086
- }
279087
- };
279088
- import_react57.useEffect(() => {
279089
- return cleanup;
279090
- }, []);
279091
- const openUrl = (url2) => {
279092
- try {
279093
- const platform = process.platform;
279094
- if (platform === "darwin") {
279095
- Bun.spawn(["open", url2]);
279096
- } else if (platform === "win32") {
279097
- Bun.spawn(["cmd", "/c", "start", url2]);
279098
- } else {
279099
- Bun.spawn(["xdg-open", url2]);
279100
- }
279101
- } catch {}
279102
- };
279103
- const startDeviceFlow = async () => {
279104
- setStep("requesting");
279105
- setError(null);
279106
- cancelledRef.current = false;
279107
- const apiUrl = getPensarApiUrl();
279108
- console.error(`[auth] apiUrl=${apiUrl}`);
279109
- try {
279110
- console.error(`[auth] fetching ${apiUrl}/api/cli/config`);
279111
- const configResponse = await fetch(`${apiUrl}/api/cli/config`);
279112
- console.error(`[auth] config response: ${configResponse.status} ${configResponse.statusText}`);
279113
- if (configResponse.ok) {
279114
- const cliConfig = await configResponse.json();
279115
- console.error(`[auth] got workosClientId=${cliConfig.workosClientId.slice(0, 12)}...`);
279116
- const response = await fetch("https://api.workos.com/user_management/authorize/device", {
279117
- method: "POST",
279118
- headers: { "Content-Type": "application/json" },
279119
- body: JSON.stringify({ client_id: cliConfig.workosClientId })
279120
- });
279121
- console.error(`[auth] device authorize response: ${response.status}`);
279122
- if (response.ok) {
279123
- const data = await response.json();
279124
- console.error(`[auth] device code obtained, polling interval=${data.interval}s, expires=${data.expires_in}s`);
279125
- setAuthMode("workos");
279126
- setDeviceInfo(data);
279127
- openUrl(data.verification_uri_complete);
279128
- setStep("polling");
279129
- pollForToken(apiUrl, cliConfig.workosClientId, data.device_code, data.interval, data.expires_in);
279130
- return;
279131
- }
279132
- }
279133
- } catch (e) {
279134
- console.error(`[auth] WorkOS flow failed, falling back to legacy:`, e);
279135
- }
279136
- try {
279137
- const response = await fetch(`${apiUrl}/auth/device/code`, {
279138
- method: "POST",
279139
- headers: { "Content-Type": "application/json" }
279140
- });
279141
- if (!response.ok) {
279142
- throw new Error("Failed to start device authorization");
279143
- }
279144
- const data = await response.json();
279145
- setAuthMode("legacy");
279146
- setLegacyDeviceInfo(data);
279147
- openUrl(data.verificationUriComplete);
279148
- setStep("polling");
279149
- pollForLegacyToken(apiUrl, data.deviceCode, data.interval, data.expiresIn);
279150
- } catch (err) {
279151
- setError(err instanceof Error ? err.message : "Failed to start authorization");
279152
- setStep("error");
279153
- }
279154
- };
279155
- const pollForToken = (apiUrl, clientId, deviceCode, interval, expiresIn) => {
279156
- const deadline = Date.now() + expiresIn * 1000;
279157
- const poll = async () => {
279158
- if (cancelledRef.current)
279159
- return;
279160
- if (Date.now() > deadline) {
279161
- setError("Authorization timed out. Please try again.");
279162
- setStep("error");
279163
- return;
279164
- }
279165
- try {
279166
- console.error(`[auth] polling WorkOS authenticate...`);
279167
- const response = await fetch("https://api.workos.com/user_management/authenticate", {
279168
- method: "POST",
279169
- headers: { "Content-Type": "application/json" },
279170
- body: JSON.stringify({
279171
- client_id: clientId,
279172
- grant_type: "urn:ietf:params:oauth:grant-type:device_code",
279173
- device_code: deviceCode
279174
- })
279175
- });
279176
- if (cancelledRef.current)
279177
- return;
279178
- console.error(`[auth] poll response: ${response.status}`);
279179
- if (response.status === 400) {
279180
- pollingRef.current = setTimeout(poll, interval * 1000);
279181
- return;
279182
- }
279183
- if (!response.ok) {
279184
- const body = await response.text();
279185
- console.error(`[auth] auth failed: ${response.status} ${body}`);
279186
- throw new Error("Authentication failed");
279187
- }
279188
- const data = await response.json();
279189
- console.error(`[auth] got tokens, access_token=${data.access_token.length} chars`);
279190
- await config2.update({
279191
- accessToken: data.access_token,
279192
- refreshToken: data.refresh_token
279193
- });
279194
- await appConfig.reload();
279195
- console.error(`[auth] tokens saved, fetching workspaces...`);
279196
- await fetchWorkspaces(apiUrl, data.access_token);
279197
- } catch (err) {
279198
- if (cancelledRef.current)
279199
- return;
279200
- console.error(`[auth] poll error, retrying:`, err);
279201
- pollingRef.current = setTimeout(poll, interval * 1000);
279202
- }
279203
- };
279204
- pollingRef.current = setTimeout(poll, interval * 1000);
279205
- };
279206
- const pollForLegacyToken = (apiUrl, deviceCode, interval, expiresIn) => {
279207
- const deadline = Date.now() + expiresIn * 1000;
279208
- const poll = async () => {
279209
- if (cancelledRef.current)
279210
- return;
279211
- if (Date.now() > deadline) {
279212
- setError("Authorization timed out. Please try again.");
279213
- setStep("error");
279214
- return;
279215
- }
279216
- try {
279217
- const response = await fetch(`${apiUrl}/auth/device/token`, {
279218
- method: "POST",
279219
- headers: { "Content-Type": "application/json" },
279220
- body: JSON.stringify({ deviceCode })
279221
- });
279222
- if (!response.ok) {
279223
- throw new Error("Failed to check authorization status");
279224
- }
279225
- const data = await response.json();
279226
- if (cancelledRef.current)
279227
- return;
279228
- if (data.status === "complete" && data.apiKey) {
279229
- await config2.update({ pensarAPIKey: data.apiKey });
279230
- if (data.workspace) {
279231
- await config2.update({
279232
- workspaceId: data.workspace.id,
279233
- workspaceSlug: data.workspace.slug
279234
- });
279235
- }
279236
- appConfig.reload();
279237
- setSelectedWorkspace(data.workspace ? {
279238
- ...data.workspace,
279239
- balance: data.credits?.balance ?? 0,
279240
- hasPaymentMethod: true
279241
- } : null);
279242
- setBalance(data.credits?.balance ?? null);
279243
- setStep("success");
279244
- return;
279245
- }
279246
- if (data.status === "expired") {
279247
- setError("Authorization expired. Please try again.");
279248
- setStep("error");
279249
- return;
279250
- }
279251
- if (data.status === "not_found") {
279252
- setError("Invalid authorization session. Please try again.");
279253
- setStep("error");
279254
- return;
279255
- }
279256
- pollingRef.current = setTimeout(poll, interval * 1000);
279257
- } catch (err) {
279258
- if (cancelledRef.current)
279259
- return;
279260
- pollingRef.current = setTimeout(poll, interval * 1000);
279261
- }
279262
- };
279263
- pollingRef.current = setTimeout(poll, interval * 1000);
279264
- };
279265
- const fetchWorkspaces = async (apiUrl, accessToken) => {
279266
- try {
279267
- const url2 = `${apiUrl}/api/cli/workspaces`;
279268
- console.error(`[auth] fetchWorkspaces: ${url2} (token=${accessToken.length} chars)`);
279269
- const response = await fetch(url2, {
279270
- headers: { Authorization: `Bearer ${accessToken}` }
279271
- });
279272
- console.error(`[auth] workspaces response: ${response.status} ${response.statusText}`);
279273
- if (!response.ok) {
279274
- const body = await response.text();
279275
- console.error(`[auth] workspaces error body: ${body}`);
279276
- throw new Error(`Failed to fetch workspaces (${response.status})`);
279277
- }
279278
- const data = await response.json();
279279
- console.error(`[auth] got ${data.workspaces.length} workspace(s)`);
279280
- if (data.workspaces.length === 0) {
279281
- setError("No workspaces found. Create one at console.pensar.dev");
279282
- setStep("error");
279283
- return;
279284
- }
279285
- if (data.workspaces.length === 1) {
279286
- await selectWorkspace(apiUrl, accessToken, data.workspaces[0]);
279287
- return;
279288
- }
279289
- setWorkspaces(data.workspaces);
279290
- setSelectedIndex(0);
279291
- setStep("select-workspace");
279292
- } catch (err) {
279293
- console.error(`[auth] fetchWorkspaces error:`, err);
279294
- setError(err instanceof Error ? err.message : "Failed to fetch workspaces");
279295
- setStep("error");
279296
- }
279297
- };
279298
- const selectWorkspace = async (apiUrl, accessToken, workspace) => {
279299
- setSelectedWorkspace(workspace);
279300
- setStep("checking-billing");
279301
- try {
279302
- console.error(`[auth] selectWorkspace: ${workspace.id} (${workspace.name})`);
279303
- const response = await fetch(`${apiUrl}/api/cli/select-workspace`, {
279304
- method: "POST",
279305
- headers: {
279306
- "Content-Type": "application/json",
279307
- Authorization: `Bearer ${accessToken}`
279308
- },
279309
- body: JSON.stringify({ workspaceId: workspace.id })
279310
- });
279311
- console.error(`[auth] select-workspace response: ${response.status}`);
279312
- if (!response.ok) {
279313
- const body = await response.text();
279314
- console.error(`[auth] select-workspace error: ${body}`);
279315
- throw new Error("Failed to select workspace");
279316
- }
279317
- const data = await response.json();
279318
- await config2.update({
279319
- workspaceId: workspace.id,
279320
- workspaceSlug: workspace.slug
279321
- });
279322
- appConfig.reload();
279323
- setBalance(data.billing.balance);
279324
- if (!data.confirmed && data.billingUrl) {
279325
- setBillingUrl(data.billingUrl);
279326
- }
279327
- setStep("success");
279328
- } catch (err) {
279329
- setError(err instanceof Error ? err.message : "Failed to select workspace");
279330
- setStep("error");
279331
- }
279332
- };
279333
- const handleDisconnect = async () => {
279334
- await config2.update({
279335
- pensarAPIKey: null,
279336
- accessToken: null,
279337
- refreshToken: null,
279338
- workspaceId: null,
279339
- workspaceSlug: null
279340
- });
279341
- appConfig.reload();
279342
- setAuthMode(null);
279343
- setSelectedWorkspace(null);
279344
- setBalance(null);
279345
- setBillingUrl(null);
279346
- setDeviceInfo(null);
279347
- setLegacyDeviceInfo(null);
279348
- setStep("start");
279349
- };
279350
- const hasLowBalance = balance !== null && balance < 1;
279351
- const effectiveBillingUrl = billingUrl || (selectedWorkspace?.slug ? `${getPensarConsoleUrl()}/${selectedWorkspace.slug}/settings/billing` : connectedWorkspace?.slug ? `${getPensarConsoleUrl()}/${connectedWorkspace.slug}/settings/billing` : `${getPensarConsoleUrl()}/credits`);
279352
- const openBillingPage = () => {
279353
- openUrl(effectiveBillingUrl);
279354
- goHome();
279355
- };
279356
- useKeyboard((key) => {
279357
- key.preventDefault();
279358
- if (key.name === "escape") {
279359
- cleanup();
279360
- goHome();
279361
- return;
279362
- }
279363
- if (step === "start") {
279364
- if (key.name === "return") {
279365
- startDeviceFlow();
279366
- }
279367
- }
279368
- if (step === "select-workspace") {
279369
- if (key.name === "up" && selectedIndex > 0) {
279370
- setSelectedIndex((i) => i - 1);
279371
- }
279372
- if (key.name === "down" && selectedIndex < workspaces.length - 1) {
279373
- setSelectedIndex((i) => i + 1);
279374
- }
279375
- if (key.name === "return" && workspaces[selectedIndex]) {
279376
- const currentConfig = appConfig.data;
279377
- const apiUrl = getPensarApiUrl();
279378
- const accessToken = currentConfig.accessToken;
279379
- selectWorkspace(apiUrl, accessToken, workspaces[selectedIndex]);
279380
- }
279381
- }
279382
- if (step === "error") {
279383
- if (key.name === "return") {
279384
- startDeviceFlow();
279385
- }
279386
- }
279387
- if (step === "success") {
279388
- if (key.name === "return") {
279389
- if (hasLowBalance || billingUrl) {
279390
- openBillingPage();
279391
- } else {
279392
- goHome();
279393
- }
279394
- }
279395
- if (key.raw === "d" || key.raw === "D") {
279396
- handleDisconnect();
279397
- }
279398
- }
279399
- });
279400
- return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(Dialog, {
279401
- size: "large",
279402
- onClose: goHome,
279403
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279404
- flexDirection: "column",
279405
- width: "100%",
279406
- alignItems: "flex-start",
279407
- padding: 1,
279408
- children: [
279409
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279410
- marginBottom: 1,
279411
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279412
- fg: colors2.primary,
279413
- children: "Pensar Console — Managed Inference"
279414
- }, undefined, false, undefined, this)
279415
- }, undefined, false, undefined, this),
279416
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279417
- marginBottom: 1,
279418
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279419
- fg: colors2.textMuted,
279420
- children: [
279421
- "Connect to Pensar Console for usage-based AI inference.",
279422
- `
279423
- `,
279424
- "No API keys needed — just a Pensar account with credits."
279425
- ]
279426
- }, undefined, true, undefined, this)
279427
- }, undefined, false, undefined, this),
279428
- step === "start" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279429
- flexDirection: "column",
279430
- gap: 1,
279431
- children: [
279432
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279433
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279434
- fg: colors2.text,
279435
- children: [
279436
- "Press ",
279437
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279438
- fg: colors2.primary,
279439
- children: "[ENTER]"
279440
- }, undefined, false, undefined, this),
279441
- " to authorize via your browser."
279442
- ]
279443
- }, undefined, true, undefined, this)
279444
- }, undefined, false, undefined, this),
279445
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279446
- marginTop: 1,
279447
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279448
- fg: colors2.textMuted,
279449
- children: [
279450
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279451
- fg: colors2.primary,
279452
- children: "[ENTER]"
279453
- }, undefined, false, undefined, this),
279454
- " Connect ·",
279455
- " ",
279456
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279457
- fg: colors2.primary,
279458
- children: "[ESC]"
279459
- }, undefined, false, undefined, this),
279460
- " Cancel"
279461
- ]
279462
- }, undefined, true, undefined, this)
279463
- }, undefined, false, undefined, this)
279464
- ]
279465
- }, undefined, true, undefined, this),
279466
- step === "requesting" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279467
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279468
- fg: colors2.warning,
279469
- children: "Starting authorization..."
279470
- }, undefined, false, undefined, this)
279471
- }, undefined, false, undefined, this),
279472
- step === "polling" && (deviceInfo || legacyDeviceInfo) && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279473
- flexDirection: "column",
279474
- gap: 1,
279475
- children: [
279476
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279477
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279478
- fg: colors2.warning,
279479
- children: "Waiting for browser authorization..."
279480
- }, undefined, false, undefined, this)
279481
- }, undefined, false, undefined, this),
279482
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279483
- marginTop: 1,
279484
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279485
- fg: colors2.text,
279486
- children: [
279487
- "Your code:",
279488
- " ",
279489
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279490
- fg: colors2.primary,
279491
- children: deviceInfo?.user_code || legacyDeviceInfo?.userCode
279492
- }, undefined, false, undefined, this)
279493
- ]
279494
- }, undefined, true, undefined, this)
279495
- }, undefined, false, undefined, this),
279496
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279497
- marginTop: 1,
279498
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279499
- fg: colors2.textMuted,
279500
- children: [
279501
- "If the browser didn't open, visit:",
279502
- `
279503
- `,
279504
- deviceInfo?.verification_uri_complete || legacyDeviceInfo?.verificationUriComplete
279505
- ]
279506
- }, undefined, true, undefined, this)
279507
- }, undefined, false, undefined, this),
279508
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279509
- marginTop: 1,
279510
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279511
- fg: colors2.textMuted,
279512
- children: [
279513
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279514
- fg: colors2.primary,
279515
- children: "[ESC]"
279516
- }, undefined, false, undefined, this),
279517
- " Cancel"
279518
- ]
279519
- }, undefined, true, undefined, this)
279520
- }, undefined, false, undefined, this)
279521
- ]
279522
- }, undefined, true, undefined, this),
279523
- step === "select-workspace" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279524
- flexDirection: "column",
279525
- gap: 1,
279526
- children: [
279527
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279528
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279529
- fg: colors2.text,
279530
- children: "Select a workspace:"
279531
- }, undefined, false, undefined, this)
279532
- }, undefined, false, undefined, this),
279533
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279534
- flexDirection: "column",
279535
- marginTop: 1,
279536
- children: workspaces.map((ws, i) => /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279537
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279538
- fg: i === selectedIndex ? colors2.primary : colors2.textMuted,
279539
- children: [
279540
- i === selectedIndex ? "▸ " : " ",
279541
- ws.name,
279542
- " ",
279543
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279544
- fg: colors2.textMuted,
279545
- children: [
279546
- "(",
279547
- ws.slug,
279548
- ") — $",
279549
- ws.balance.toFixed(2)
279550
- ]
279551
- }, undefined, true, undefined, this)
279552
- ]
279553
- }, undefined, true, undefined, this)
279554
- }, ws.id, false, undefined, this))
279555
- }, undefined, false, undefined, this),
279556
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279557
- marginTop: 1,
279558
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279559
- fg: colors2.textMuted,
279560
- children: [
279561
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279562
- fg: colors2.primary,
279563
- children: "[↑/↓]"
279564
- }, undefined, false, undefined, this),
279565
- " Navigate ·",
279566
- " ",
279567
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279568
- fg: colors2.primary,
279569
- children: "[ENTER]"
279570
- }, undefined, false, undefined, this),
279571
- " Select ·",
279572
- " ",
279573
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279574
- fg: colors2.primary,
279575
- children: "[ESC]"
279576
- }, undefined, false, undefined, this),
279577
- " Cancel"
279578
- ]
279579
- }, undefined, true, undefined, this)
279580
- }, undefined, false, undefined, this)
279581
- ]
279582
- }, undefined, true, undefined, this),
279583
- step === "checking-billing" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279584
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279585
- fg: colors2.warning,
279586
- children: [
279587
- "Checking billing for ",
279588
- selectedWorkspace?.name,
279589
- "..."
279590
- ]
279591
- }, undefined, true, undefined, this)
279592
- }, undefined, false, undefined, this),
279593
- step === "success" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279594
- flexDirection: "column",
279595
- gap: 1,
279596
- children: [
279597
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279598
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279599
- fg: colors2.success,
279600
- children: "Connected to Pensar Console"
279601
- }, undefined, false, undefined, this)
279602
- }, undefined, false, undefined, this),
279603
- (selectedWorkspace || connectedWorkspace) && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279604
- flexDirection: "column",
279605
- children: [
279606
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279607
- fg: colors2.text,
279608
- children: [
279609
- "Workspace:",
279610
- " ",
279611
- selectedWorkspace?.name || connectedWorkspace?.name
279612
- ]
279613
- }, undefined, true, undefined, this),
279614
- balance !== null && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279615
- fg: colors2.text,
279616
- children: [
279617
- "Credits:",
279618
- " ",
279619
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279620
- fg: hasLowBalance ? colors2.warning : colors2.text,
279621
- children: [
279622
- "$",
279623
- balance.toFixed(2)
279624
- ]
279625
- }, undefined, true, undefined, this)
279626
- ]
279627
- }, undefined, true, undefined, this)
279628
- ]
279629
- }, undefined, true, undefined, this),
279630
- (hasLowBalance || billingUrl) && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279631
- marginTop: 1,
279632
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279633
- fg: colors2.warning,
279634
- children: [
279635
- billingUrl ? "Your workspace needs credits to use Apex CLI." : "Your credit balance is very low. We recommend at least $30 to run",
279636
- `
279637
- `,
279638
- billingUrl ? "Press ENTER to open billing and add credits." : "pentests without interruptions. Press ENTER to open billing."
279639
- ]
279640
- }, undefined, true, undefined, this)
279641
- }, undefined, false, undefined, this),
279642
- !selectedWorkspace && !connectedWorkspace && appConfig.data.pensarAPIKey && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279643
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279644
- fg: colors2.textMuted,
279645
- children: "Already connected (legacy key saved in config)"
279646
- }, undefined, false, undefined, this)
279647
- }, undefined, false, undefined, this),
279648
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279649
- marginTop: 1,
279650
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279651
- fg: colors2.textMuted,
279652
- children: "Pensar models are now available in the model selector."
279653
- }, undefined, false, undefined, this)
279654
- }, undefined, false, undefined, this),
279655
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279656
- marginTop: 1,
279657
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279658
- fg: colors2.textMuted,
279659
- children: [
279660
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279661
- fg: colors2.primary,
279662
- children: "[ENTER]"
279663
- }, undefined, false, undefined, this),
279664
- " ",
279665
- hasLowBalance || billingUrl ? "Open billing" : "Done",
279666
- " ·",
279667
- " ",
279668
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279669
- fg: colors2.error,
279670
- children: "[D]"
279671
- }, undefined, false, undefined, this),
279672
- " Disconnect ·",
279673
- " ",
279674
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279675
- fg: colors2.primary,
279676
- children: "[ESC]"
279677
- }, undefined, false, undefined, this),
279678
- " Back"
279679
- ]
279680
- }, undefined, true, undefined, this)
279681
- }, undefined, false, undefined, this)
279682
- ]
279683
- }, undefined, true, undefined, this),
279684
- step === "error" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279685
- flexDirection: "column",
279686
- gap: 1,
279687
- children: [
279688
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279689
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279690
- fg: colors2.error,
279691
- children: error40
279692
- }, undefined, false, undefined, this)
279693
- }, undefined, false, undefined, this),
279694
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
279695
- marginTop: 1,
279696
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
279697
- fg: colors2.textMuted,
279698
- children: [
279699
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279700
- fg: colors2.primary,
279701
- children: "[ENTER]"
279702
- }, undefined, false, undefined, this),
279703
- " Try again ·",
279704
- " ",
279705
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
279706
- fg: colors2.primary,
279707
- children: "[ESC]"
279708
- }, undefined, false, undefined, this),
279709
- " Cancel"
279710
- ]
279711
- }, undefined, true, undefined, this)
279712
- }, undefined, false, undefined, this)
279713
- ]
279714
- }, undefined, true, undefined, this)
279715
- ]
279716
- }, undefined, true, undefined, this)
279717
- }, undefined, false, undefined, this);
279718
- }
279719
-
279720
280371
  // src/tui/components/commands/credits-flow.tsx
279721
- var import_react59 = __toESM(require_react(), 1);
279722
- init_tokenRefresh();
280372
+ var import_react60 = __toESM(require_react(), 1);
280373
+ init_auth();
280374
+ init_config2();
279723
280375
  function CreditsFlow({ onOpenAuthDialog }) {
279724
280376
  const route = useRoute();
279725
280377
  const appConfig = useConfig();
279726
- const [step, setStep] = import_react59.useState("loading");
279727
- const [credits, setCredits] = import_react59.useState(null);
279728
- const [error40, setError] = import_react59.useState(null);
280378
+ const [step, setStep] = import_react60.useState("loading");
280379
+ const [credits, setCredits] = import_react60.useState(null);
280380
+ const [error40, setError] = import_react60.useState(null);
279729
280381
  const creditsUrl = `${getPensarConsoleUrl()}/credits`;
279730
280382
  const goHome = () => {
279731
280383
  route.navigate({ type: "base", path: "home" });
@@ -279772,6 +280424,9 @@ function CreditsFlow({ onOpenAuthDialog }) {
279772
280424
  throw new Error("Failed to fetch balance");
279773
280425
  }
279774
280426
  const result = await response.json();
280427
+ if (result.signingKey) {
280428
+ await config2.update({ gatewaySigningKey: result.signingKey });
280429
+ }
279775
280430
  setCredits({
279776
280431
  balance: result.credits.balance,
279777
280432
  workspace: result.workspace.name
@@ -279782,7 +280437,7 @@ function CreditsFlow({ onOpenAuthDialog }) {
279782
280437
  setStep("display");
279783
280438
  }
279784
280439
  };
279785
- import_react59.useEffect(() => {
280440
+ import_react60.useEffect(() => {
279786
280441
  fetchBalance();
279787
280442
  }, []);
279788
280443
  useKeyboard((key) => {
@@ -280021,10 +280676,10 @@ function CreditsFlow({ onOpenAuthDialog }) {
280021
280676
  }
280022
280677
 
280023
280678
  // src/tui/context/keybinding.tsx
280024
- var import_react65 = __toESM(require_react(), 1);
280679
+ var import_react66 = __toESM(require_react(), 1);
280025
280680
 
280026
280681
  // src/tui/keybindings/keybind.tsx
280027
- var import_react61 = __toESM(require_react(), 1);
280682
+ var import_react62 = __toESM(require_react(), 1);
280028
280683
 
280029
280684
  // src/tui/keybindings/actions.ts
280030
280685
  var movementActions = [
@@ -280276,7 +280931,7 @@ var allActions = [
280276
280931
  var actionsByKey = new Map(allActions.map((action) => [action.key, action]));
280277
280932
  var actionsById = new Map(allActions.map((action) => [action.id, action]));
280278
280933
  // src/tui/keybindings/keybind.tsx
280279
- var LeaderKeyContext = import_react61.createContext(null);
280934
+ var LeaderKeyContext = import_react62.createContext(null);
280280
280935
  // src/tui/keybindings/registry.ts
280281
280936
  function createKeybindings(deps) {
280282
280937
  const {
@@ -280457,7 +281112,7 @@ function matchesKeybind(pressed, combo) {
280457
281112
  }
280458
281113
 
280459
281114
  // src/tui/context/keybinding.tsx
280460
- var KeybindingContext = import_react65.createContext(undefined);
281115
+ var KeybindingContext = import_react66.createContext(undefined);
280461
281116
  function KeybindingProvider({
280462
281117
  children,
280463
281118
  deps
@@ -280497,9 +281152,9 @@ function KeybindingProvider({
280497
281152
  }
280498
281153
 
280499
281154
  // src/tui/components/pentest/pentest.tsx
280500
- var import_react73 = __toESM(require_react(), 1);
281155
+ var import_react74 = __toESM(require_react(), 1);
280501
281156
  init_report();
280502
- import { existsSync as existsSync26, readdirSync as readdirSync6, readFileSync as readFileSync12 } from "fs";
281157
+ import { existsSync as existsSync26, readdirSync as readdirSync6, readFileSync as readFileSync13 } from "fs";
280503
281158
  import { join as join27 } from "path";
280504
281159
  init_session();
280505
281160
  init_loader();
@@ -280529,7 +281184,7 @@ Found ${findings.length} vulnerabilities`);
280529
281184
  init_utils();
280530
281185
 
280531
281186
  // src/tui/components/agent-display.tsx
280532
- var import_react72 = __toESM(require_react(), 1);
281187
+ var import_react73 = __toESM(require_react(), 1);
280533
281188
 
280534
281189
  // node_modules/marked/lib/marked.esm.js
280535
281190
  function L2() {
@@ -281905,7 +282560,7 @@ function markdownToStyledText(content, colors2) {
281905
282560
  }
281906
282561
  }
281907
282562
  // src/tui/components/shared/message-utils.ts
281908
- import { createHash } from "crypto";
282563
+ import { createHash as createHash2 } from "crypto";
281909
282564
 
281910
282565
  // src/tui/components/shared/type-guards.ts
281911
282566
  function isToolMessage(msg) {
@@ -281914,7 +282569,7 @@ function isToolMessage(msg) {
281914
282569
 
281915
282570
  // src/tui/components/shared/message-utils.ts
281916
282571
  function hashContent(content) {
281917
- return createHash("sha256").update(content).digest("hex").slice(0, 8);
282572
+ return createHash2("sha256").update(content).digest("hex").slice(0, 8);
281918
282573
  }
281919
282574
  function getStableMessageKey(item, contextId = "root") {
281920
282575
  if (isToolMessage(item)) {
@@ -282861,14 +283516,14 @@ ${preview}${suffix}` : preview + suffix || "POC passed",
282861
283516
  return null;
282862
283517
  }
282863
283518
  // src/tui/components/shared/ascii-spinner.tsx
282864
- var import_react66 = __toESM(require_react(), 1);
283519
+ var import_react67 = __toESM(require_react(), 1);
282865
283520
  var SPINNER_FRAMES = ["/", "-", "\\", "|"];
282866
283521
  var SPINNER_INTERVAL = 100;
282867
283522
  function AsciiSpinner({ label, fg: fg2 }) {
282868
283523
  const { colors: colors2 } = useTheme();
282869
283524
  const spinnerColor = fg2 ?? colors2.info;
282870
- const [frame, setFrame] = import_react66.useState(0);
282871
- import_react66.useEffect(() => {
283525
+ const [frame, setFrame] = import_react67.useState(0);
283526
+ import_react67.useEffect(() => {
282872
283527
  const interval = setInterval(() => {
282873
283528
  setFrame((f3) => (f3 + 1) % SPINNER_FRAMES.length);
282874
283529
  }, SPINNER_INTERVAL);
@@ -282880,7 +283535,7 @@ function AsciiSpinner({ label, fg: fg2 }) {
282880
283535
  }, undefined, false, undefined, this);
282881
283536
  }
282882
283537
  // src/tui/components/shared/tool-renderer.tsx
282883
- var import_react67 = __toESM(require_react(), 1);
283538
+ var import_react68 = __toESM(require_react(), 1);
282884
283539
  var TOOLS_WITH_LOG_WINDOW = new Set([
282885
283540
  "execute_command",
282886
283541
  "run_attack_surface",
@@ -282893,13 +283548,13 @@ var TOOLS_WITH_LOG_WINDOW = new Set([
282893
283548
  "document_vulnerability"
282894
283549
  ]);
282895
283550
  var DEFAULT_SUBAGENT_LOG_LINES = 5;
282896
- var ToolRenderer = import_react67.memo(function ToolRenderer2({
283551
+ var ToolRenderer = import_react68.memo(function ToolRenderer2({
282897
283552
  message,
282898
283553
  verbose = false,
282899
283554
  expandedLogs = false
282900
283555
  }) {
282901
283556
  const { colors: colors2 } = useTheme();
282902
- const [showOutput, setShowOutput] = import_react67.useState(false);
283557
+ const [showOutput, setShowOutput] = import_react68.useState(false);
282903
283558
  if (!isToolMessage(message)) {
282904
283559
  return null;
282905
283560
  }
@@ -283039,7 +283694,7 @@ var ToolRenderer = import_react67.memo(function ToolRenderer2({
283039
283694
  ]
283040
283695
  }, undefined, true, undefined, this);
283041
283696
  });
283042
- var SubagentLogWindow = import_react67.memo(function SubagentLogWindow2({
283697
+ var SubagentLogWindow = import_react68.memo(function SubagentLogWindow2({
283043
283698
  subagentId,
283044
283699
  entry,
283045
283700
  expandedLogs
@@ -283096,8 +283751,8 @@ var SubagentLogWindow = import_react67.memo(function SubagentLogWindow2({
283096
283751
  }, undefined, true, undefined, this);
283097
283752
  });
283098
283753
  // src/tui/components/shared/message-renderer.tsx
283099
- var import_react68 = __toESM(require_react(), 1);
283100
- var MessageRenderer = import_react68.memo(function MessageRenderer2({
283754
+ var import_react69 = __toESM(require_react(), 1);
283755
+ var MessageRenderer = import_react69.memo(function MessageRenderer2({
283101
283756
  message,
283102
283757
  isStreaming = false,
283103
283758
  verbose = false,
@@ -283107,7 +283762,7 @@ var MessageRenderer = import_react68.memo(function MessageRenderer2({
283107
283762
  }) {
283108
283763
  const { colors: colors2 } = useTheme();
283109
283764
  const content = typeof message.content === "string" ? message.content : JSON.stringify(message.content);
283110
- const displayContent = import_react68.useMemo(() => message.role === "assistant" ? markdownToStyledText(content, colors2) : content, [content, message.role, colors2]);
283765
+ const displayContent = import_react69.useMemo(() => message.role === "assistant" ? markdownToStyledText(content, colors2) : content, [content, message.role, colors2]);
283111
283766
  if (isToolMessage(message)) {
283112
283767
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ToolRenderer, {
283113
283768
  message,
@@ -283219,9 +283874,9 @@ var MessageRenderer = import_react68.memo(function MessageRenderer2({
283219
283874
  }, undefined, false, undefined, this);
283220
283875
  });
283221
283876
  // src/tui/components/shared/approval-prompt.tsx
283222
- var import_react69 = __toESM(require_react(), 1);
283877
+ var import_react70 = __toESM(require_react(), 1);
283223
283878
  // src/tui/components/shared/message-reducer.ts
283224
- var import_react71 = __toESM(require_react(), 1);
283879
+ var import_react72 = __toESM(require_react(), 1);
283225
283880
  // src/tui/components/agent-display.tsx
283226
283881
  function getStableKey(item, contextId = "root") {
283227
283882
  if ("messages" in item) {
@@ -283297,11 +283952,11 @@ function AgentDisplay({
283297
283952
  ]
283298
283953
  }, undefined, true, undefined, this);
283299
283954
  }
283300
- var SubAgentDisplay = import_react72.memo(function SubAgentDisplay2({
283955
+ var SubAgentDisplay = import_react73.memo(function SubAgentDisplay2({
283301
283956
  subagent
283302
283957
  }) {
283303
283958
  const { colors: colors2 } = useTheme();
283304
- const [open, setOpen] = import_react72.useState(false);
283959
+ const [open, setOpen] = import_react73.useState(false);
283305
283960
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
283306
283961
  height: open ? 40 : "auto",
283307
283962
  onMouseDown: () => setOpen(!open),
@@ -283356,7 +284011,7 @@ var SubAgentDisplay = import_react72.memo(function SubAgentDisplay2({
283356
284011
  ]
283357
284012
  }, undefined, true, undefined, this);
283358
284013
  });
283359
- var AgentMessage = import_react72.memo(function AgentMessage2({
284014
+ var AgentMessage = import_react73.memo(function AgentMessage2({
283360
284015
  message
283361
284016
  }) {
283362
284017
  const { colors: colors2 } = useTheme();
@@ -283465,8 +284120,8 @@ var AgentMessage = import_react72.memo(function AgentMessage2({
283465
284120
  });
283466
284121
  function ToolDetails({ message }) {
283467
284122
  const { colors: colors2 } = useTheme();
283468
- const [showArgs, setShowArgs] = import_react72.useState(false);
283469
- const [showResult, setShowResult] = import_react72.useState(false);
284123
+ const [showArgs, setShowArgs] = import_react73.useState(false);
284124
+ const [showResult, setShowResult] = import_react73.useState(false);
283470
284125
  if (message.role !== "tool") {
283471
284126
  return null;
283472
284127
  }
@@ -283572,27 +284227,27 @@ function Pentest({
283572
284227
  const config3 = useConfig();
283573
284228
  const { model, setThinking, setIsExecuting, isExecuting } = useAgent();
283574
284229
  const { stack, externalDialogOpen } = useDialog();
283575
- const [session, setSession] = import_react73.useState(null);
283576
- const [error40, setError] = import_react73.useState(null);
283577
- const [phase, setPhase] = import_react73.useState("loading");
283578
- const [abortController, setAbortController] = import_react73.useState(null);
283579
- const [panelMessages, setPanelMessages] = import_react73.useState([]);
283580
- const panelTextRef = import_react73.useRef("");
283581
- const panelSourceRef = import_react73.useRef(null);
283582
- const [pentestAgents, setPentestAgents] = import_react73.useState({});
283583
- const pentestTextRefs = import_react73.useRef({});
283584
- const [assets, setAssets] = import_react73.useState([]);
283585
- const [viewMode, setViewMode] = import_react73.useState("overview");
283586
- const [selectedAgentId, setSelectedAgentId] = import_react73.useState(null);
283587
- const [focusedIndex, setFocusedIndex] = import_react73.useState(0);
283588
- const [showOrchestratorPanel, setShowOrchestratorPanel] = import_react73.useState(false);
283589
- const [startTime, setStartTime] = import_react73.useState(null);
283590
- const pentestAgentList = import_react73.useMemo(() => Object.values(pentestAgents).sort((a, b3) => a.createdAt.getTime() - b3.createdAt.getTime()), [pentestAgents]);
283591
- const selectedAgent = import_react73.useMemo(() => selectedAgentId ? pentestAgents[selectedAgentId] ?? null : null, [pentestAgents, selectedAgentId]);
284230
+ const [session, setSession] = import_react74.useState(null);
284231
+ const [error40, setError] = import_react74.useState(null);
284232
+ const [phase, setPhase] = import_react74.useState("loading");
284233
+ const [abortController, setAbortController] = import_react74.useState(null);
284234
+ const [panelMessages, setPanelMessages] = import_react74.useState([]);
284235
+ const panelTextRef = import_react74.useRef("");
284236
+ const panelSourceRef = import_react74.useRef(null);
284237
+ const [pentestAgents, setPentestAgents] = import_react74.useState({});
284238
+ const pentestTextRefs = import_react74.useRef({});
284239
+ const [assets, setAssets] = import_react74.useState([]);
284240
+ const [viewMode, setViewMode] = import_react74.useState("overview");
284241
+ const [selectedAgentId, setSelectedAgentId] = import_react74.useState(null);
284242
+ const [focusedIndex, setFocusedIndex] = import_react74.useState(0);
284243
+ const [showOrchestratorPanel, setShowOrchestratorPanel] = import_react74.useState(false);
284244
+ const [startTime, setStartTime] = import_react74.useState(null);
284245
+ const pentestAgentList = import_react74.useMemo(() => Object.values(pentestAgents).sort((a, b3) => a.createdAt.getTime() - b3.createdAt.getTime()), [pentestAgents]);
284246
+ const selectedAgent = import_react74.useMemo(() => selectedAgentId ? pentestAgents[selectedAgentId] ?? null : null, [pentestAgents, selectedAgentId]);
283592
284247
  const { width: termWidth } = useDimensions();
283593
284248
  const gridAvailableWidth = showOrchestratorPanel ? Math.floor((termWidth - 4) / 2) - 2 : termWidth - ORCHESTRATOR_PANEL_WIDTH - GRID_OUTER_PADDING;
283594
284249
  const gridColumns = Math.max(1, Math.floor((gridAvailableWidth + GRID_GAP) / (CARD_MIN_WIDTH + GRID_GAP)));
283595
- import_react73.useEffect(() => {
284250
+ import_react74.useEffect(() => {
283596
284251
  async function load2() {
283597
284252
  try {
283598
284253
  let s2;
@@ -283653,7 +284308,7 @@ function Pentest({
283653
284308
  }
283654
284309
  load2();
283655
284310
  }, [sessionId]);
283656
- import_react73.useEffect(() => {
284311
+ import_react74.useEffect(() => {
283657
284312
  if (!session)
283658
284313
  return;
283659
284314
  const assetsPath = join27(session.rootPath, "assets");
@@ -283665,7 +284320,7 @@ function Pentest({
283665
284320
  const loaded = [];
283666
284321
  for (const file2 of files) {
283667
284322
  try {
283668
- const content = readFileSync12(join27(assetsPath, file2), "utf-8");
284323
+ const content = readFileSync13(join27(assetsPath, file2), "utf-8");
283669
284324
  loaded.push(JSON.parse(content));
283670
284325
  } catch {}
283671
284326
  }
@@ -283676,12 +284331,12 @@ function Pentest({
283676
284331
  const interval = setInterval(readAssets, 2000);
283677
284332
  return () => clearInterval(interval);
283678
284333
  }, [session]);
283679
- import_react73.useEffect(() => {
284334
+ import_react74.useEffect(() => {
283680
284335
  return () => {
283681
284336
  abortController?.abort();
283682
284337
  };
283683
284338
  }, [abortController]);
283684
- const ensurePentestAgent = import_react73.useCallback((subagentId) => {
284339
+ const ensurePentestAgent = import_react74.useCallback((subagentId) => {
283685
284340
  setPentestAgents((prev) => {
283686
284341
  if (prev[subagentId])
283687
284342
  return prev;
@@ -283698,7 +284353,7 @@ function Pentest({
283698
284353
  };
283699
284354
  });
283700
284355
  }, []);
283701
- const handleSubagentSpawn = import_react73.useCallback(({
284356
+ const handleSubagentSpawn = import_react74.useCallback(({
283702
284357
  subagentId,
283703
284358
  input
283704
284359
  }) => {
@@ -283718,7 +284373,7 @@ function Pentest({
283718
284373
  }
283719
284374
  }));
283720
284375
  }, []);
283721
- const handleSubagentComplete = import_react73.useCallback(({ subagentId, status }) => {
284376
+ const handleSubagentComplete = import_react74.useCallback(({ subagentId, status }) => {
283722
284377
  if (!subagentId.startsWith("pentest-agent-"))
283723
284378
  return;
283724
284379
  setPentestAgents((prev) => {
@@ -283731,7 +284386,7 @@ function Pentest({
283731
284386
  };
283732
284387
  });
283733
284388
  }, []);
283734
- const appendPanelText = import_react73.useCallback((source, text2) => {
284389
+ const appendPanelText = import_react74.useCallback((source, text2) => {
283735
284390
  if (panelSourceRef.current !== source) {
283736
284391
  panelTextRef.current = "";
283737
284392
  panelSourceRef.current = source;
@@ -283756,7 +284411,7 @@ function Pentest({
283756
284411
  ];
283757
284412
  });
283758
284413
  }, []);
283759
- const appendPentestText = import_react73.useCallback((subagentId, text2) => {
284414
+ const appendPentestText = import_react74.useCallback((subagentId, text2) => {
283760
284415
  ensurePentestAgent(subagentId);
283761
284416
  if (!pentestTextRefs.current[subagentId]) {
283762
284417
  pentestTextRefs.current[subagentId] = "";
@@ -283789,8 +284444,8 @@ function Pentest({
283789
284444
  };
283790
284445
  });
283791
284446
  }, [ensurePentestAgent]);
283792
- const toolArgsDeltaRef = import_react73.useRef(new Map);
283793
- const addPanelStreamingToolCall = import_react73.useCallback((toolCallId, toolName) => {
284447
+ const toolArgsDeltaRef = import_react74.useRef(new Map);
284448
+ const addPanelStreamingToolCall = import_react74.useCallback((toolCallId, toolName) => {
283794
284449
  panelTextRef.current = "";
283795
284450
  panelSourceRef.current = null;
283796
284451
  toolArgsDeltaRef.current.set(toolCallId, "");
@@ -283809,7 +284464,7 @@ function Pentest({
283809
284464
  return [...prev, msg];
283810
284465
  });
283811
284466
  }, []);
283812
- const appendPanelToolCallDelta = import_react73.useCallback((toolCallId, argsTextDelta) => {
284467
+ const appendPanelToolCallDelta = import_react74.useCallback((toolCallId, argsTextDelta) => {
283813
284468
  const prev = toolArgsDeltaRef.current.get(toolCallId) ?? "";
283814
284469
  const accumulated = prev + argsTextDelta;
283815
284470
  toolArgsDeltaRef.current.set(toolCallId, accumulated);
@@ -283832,7 +284487,7 @@ function Pentest({
283832
284487
  return updated;
283833
284488
  });
283834
284489
  }, []);
283835
- const addPanelToolCall = import_react73.useCallback((toolCallId, toolName, args) => {
284490
+ const addPanelToolCall = import_react74.useCallback((toolCallId, toolName, args) => {
283836
284491
  panelTextRef.current = "";
283837
284492
  panelSourceRef.current = null;
283838
284493
  toolArgsDeltaRef.current.delete(toolCallId);
@@ -283864,7 +284519,7 @@ function Pentest({
283864
284519
  ];
283865
284520
  });
283866
284521
  }, []);
283867
- const addPentestStreamingToolCall = import_react73.useCallback((subagentId, toolCallId, toolName) => {
284522
+ const addPentestStreamingToolCall = import_react74.useCallback((subagentId, toolCallId, toolName) => {
283868
284523
  pentestTextRefs.current[subagentId] = "";
283869
284524
  ensurePentestAgent(subagentId);
283870
284525
  toolArgsDeltaRef.current.set(toolCallId, "");
@@ -283889,7 +284544,7 @@ function Pentest({
283889
284544
  };
283890
284545
  });
283891
284546
  }, [ensurePentestAgent]);
283892
- const appendPentestToolCallDelta = import_react73.useCallback((subagentId, toolCallId, argsTextDelta) => {
284547
+ const appendPentestToolCallDelta = import_react74.useCallback((subagentId, toolCallId, argsTextDelta) => {
283893
284548
  const prev = toolArgsDeltaRef.current.get(toolCallId) ?? "";
283894
284549
  const accumulated = prev + argsTextDelta;
283895
284550
  toolArgsDeltaRef.current.set(toolCallId, accumulated);
@@ -283915,7 +284570,7 @@ function Pentest({
283915
284570
  return { ...agents, [subagentId]: { ...agent, messages: updatedMsgs } };
283916
284571
  });
283917
284572
  }, []);
283918
- const addPentestToolCall = import_react73.useCallback((subagentId, toolCallId, toolName, args) => {
284573
+ const addPentestToolCall = import_react74.useCallback((subagentId, toolCallId, toolName, args) => {
283919
284574
  pentestTextRefs.current[subagentId] = "";
283920
284575
  ensurePentestAgent(subagentId);
283921
284576
  toolArgsDeltaRef.current.delete(toolCallId);
@@ -283976,12 +284631,12 @@ function Pentest({
283976
284631
  }
283977
284632
  ];
283978
284633
  };
283979
- const updatePanelToolResult = import_react73.useCallback((toolCallId, toolName, result) => {
284634
+ const updatePanelToolResult = import_react74.useCallback((toolCallId, toolName, result) => {
283980
284635
  panelTextRef.current = "";
283981
284636
  panelSourceRef.current = null;
283982
284637
  setPanelMessages((prev) => toolResultUpdater(prev, toolCallId, toolName, result));
283983
284638
  }, []);
283984
- const updatePentestToolResult = import_react73.useCallback((subagentId, toolCallId, toolName, result) => {
284639
+ const updatePentestToolResult = import_react74.useCallback((subagentId, toolCallId, toolName, result) => {
283985
284640
  pentestTextRefs.current[subagentId] = "";
283986
284641
  setPentestAgents((prev) => {
283987
284642
  const agent = prev[subagentId];
@@ -283996,7 +284651,7 @@ function Pentest({
283996
284651
  };
283997
284652
  });
283998
284653
  }, []);
283999
- const startPentest = import_react73.useCallback(async (s2) => {
284654
+ const startPentest = import_react74.useCallback(async (s2) => {
284000
284655
  setPhase((prev) => prev === "pentesting" || prev === "reporting" ? prev : "discovery");
284001
284656
  setStartTime(new Date);
284002
284657
  setIsExecuting(true);
@@ -284124,7 +284779,7 @@ function Pentest({
284124
284779
  handleSubagentSpawn,
284125
284780
  handleSubagentComplete
284126
284781
  ]);
284127
- import_react73.useEffect(() => {
284782
+ import_react74.useEffect(() => {
284128
284783
  if (phase === "completed") {
284129
284784
  setFocusedIndex(pentestAgentList.length);
284130
284785
  }
@@ -284187,7 +284842,7 @@ function Pentest({
284187
284842
  }
284188
284843
  }
284189
284844
  });
284190
- const openReport = import_react73.useCallback(() => {
284845
+ const openReport = import_react74.useCallback(() => {
284191
284846
  if (!session)
284192
284847
  return;
284193
284848
  const err = openSessionReport(session.rootPath);
@@ -284372,7 +285027,7 @@ function OrchestratorPanel({
284372
285027
  }) {
284373
285028
  const { colors: colors2 } = useTheme();
284374
285029
  const isRunning = phase !== "loading" && phase !== "completed" && phase !== "error";
284375
- const assetSummary = import_react73.useMemo(() => {
285030
+ const assetSummary = import_react74.useMemo(() => {
284376
285031
  const byType = {};
284377
285032
  for (const a of assets) {
284378
285033
  const key = a.assetType;
@@ -284615,13 +285270,13 @@ function AgentCardGrid({
284615
285270
  onSelectAgent
284616
285271
  }) {
284617
285272
  const { colors: colors2 } = useTheme();
284618
- const scrollRef = import_react73.useRef(null);
284619
- import_react73.useEffect(() => {
285273
+ const scrollRef = import_react74.useRef(null);
285274
+ import_react74.useEffect(() => {
284620
285275
  const agent = agents[focusedIndex];
284621
285276
  if (agent)
284622
285277
  scrollToChild(scrollRef.current, agent.id);
284623
285278
  }, [focusedIndex, agents]);
284624
- const rows = import_react73.useMemo(() => {
285279
+ const rows = import_react74.useMemo(() => {
284625
285280
  const result = [];
284626
285281
  for (let i2 = 0;i2 < agents.length; i2 += gridColumns) {
284627
285282
  result.push(agents.slice(i2, i2 + gridColumns));
@@ -284678,14 +285333,14 @@ function AgentCard({
284678
285333
  completed: colors2.primary,
284679
285334
  failed: colors2.error
284680
285335
  }[agent.status];
284681
- const lastActivity = import_react73.useMemo(() => {
285336
+ const lastActivity = import_react74.useMemo(() => {
284682
285337
  const last = agent.messages[agent.messages.length - 1];
284683
285338
  if (!last)
284684
285339
  return "Starting...";
284685
285340
  const text2 = typeof last.content === "string" ? last.content.replace(/\n/g, " ").trim() : "Working...";
284686
285341
  return text2.length > 50 ? text2.substring(0, 47) + "..." : text2;
284687
285342
  }, [agent.messages]);
284688
- const toolCalls = import_react73.useMemo(() => agent.messages.filter((m4) => m4.role === "tool").length, [agent.messages]);
285343
+ const toolCalls = import_react74.useMemo(() => agent.messages.filter((m4) => m4.role === "tool").length, [agent.messages]);
284689
285344
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
284690
285345
  id: agent.id,
284691
285346
  flexGrow: 1,
@@ -284873,8 +285528,8 @@ function MetricsBar({
284873
285528
  isExecuting
284874
285529
  }) {
284875
285530
  const { colors: colors2 } = useTheme();
284876
- const [now2, setNow] = import_react73.useState(Date.now());
284877
- import_react73.useEffect(() => {
285531
+ const [now2, setNow] = import_react74.useState(Date.now());
285532
+ import_react74.useEffect(() => {
284878
285533
  if (!isExecuting)
284879
285534
  return;
284880
285535
  const interval = setInterval(() => setNow(Date.now()), 1000);
@@ -285017,7 +285672,7 @@ function MetricsBar({
285017
285672
  }
285018
285673
 
285019
285674
  // src/tui/components/operator-dashboard/index.tsx
285020
- var import_react78 = __toESM(require_react(), 1);
285675
+ var import_react79 = __toESM(require_react(), 1);
285021
285676
  init_session();
285022
285677
 
285023
285678
  // src/core/api/offesecAgent.ts
@@ -285120,7 +285775,7 @@ function InlineApprovalPrompt2({ approval }) {
285120
285775
  }
285121
285776
 
285122
285777
  // src/tui/components/chat/loading-indicator.tsx
285123
- var import_react75 = __toESM(require_react(), 1);
285778
+ var import_react76 = __toESM(require_react(), 1);
285124
285779
  var SPINNER_FRAMES2 = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
285125
285780
  var SPINNER_INTERVAL2 = 80;
285126
285781
  var DOTS_FRAMES = ["", ".", "..", "..."];
@@ -285131,15 +285786,15 @@ function LoadingIndicator({
285131
285786
  toolName
285132
285787
  }) {
285133
285788
  const { colors: colors2 } = useTheme();
285134
- const [spinnerFrame, setSpinnerFrame] = import_react75.useState(0);
285135
- const [dotsFrame, setDotsFrame] = import_react75.useState(0);
285136
- import_react75.useEffect(() => {
285789
+ const [spinnerFrame, setSpinnerFrame] = import_react76.useState(0);
285790
+ const [dotsFrame, setDotsFrame] = import_react76.useState(0);
285791
+ import_react76.useEffect(() => {
285137
285792
  const interval = setInterval(() => {
285138
285793
  setSpinnerFrame((f3) => (f3 + 1) % SPINNER_FRAMES2.length);
285139
285794
  }, SPINNER_INTERVAL2);
285140
285795
  return () => clearInterval(interval);
285141
285796
  }, []);
285142
- import_react75.useEffect(() => {
285797
+ import_react76.useEffect(() => {
285143
285798
  const interval = setInterval(() => {
285144
285799
  setDotsFrame((f3) => (f3 + 1) % DOTS_FRAMES.length);
285145
285800
  }, DOTS_INTERVAL);
@@ -285393,7 +286048,7 @@ function MessageList({
285393
286048
  }
285394
286049
 
285395
286050
  // src/tui/components/chat/input-area.tsx
285396
- var import_react76 = __toESM(require_react(), 1);
286051
+ var import_react77 = __toESM(require_react(), 1);
285397
286052
  function NormalInputAreaInner({
285398
286053
  value,
285399
286054
  onChange,
@@ -285415,10 +286070,10 @@ function NormalInputAreaInner({
285415
286070
  }) {
285416
286071
  const { colors: colors2, theme, mode: colorMode } = useTheme();
285417
286072
  const { inputValue, setInputValue } = useInput();
285418
- const promptRef = import_react76.useRef(null);
285419
- const isExternalUpdate = import_react76.useRef(false);
285420
- const prevValueRef = import_react76.useRef(value);
285421
- import_react76.useEffect(() => {
286073
+ const promptRef = import_react77.useRef(null);
286074
+ const isExternalUpdate = import_react77.useRef(false);
286075
+ const prevValueRef = import_react77.useRef(value);
286076
+ import_react77.useEffect(() => {
285422
286077
  const prevValue = prevValueRef.current;
285423
286078
  prevValueRef.current = value;
285424
286079
  if (value !== prevValue && value !== inputValue) {
@@ -285427,7 +286082,7 @@ function NormalInputAreaInner({
285427
286082
  promptRef.current?.setValue(value);
285428
286083
  }
285429
286084
  }, [value, inputValue, setInputValue]);
285430
- import_react76.useEffect(() => {
286085
+ import_react77.useEffect(() => {
285431
286086
  if (isExternalUpdate.current) {
285432
286087
  isExternalUpdate.current = false;
285433
286088
  return;
@@ -285602,7 +286257,7 @@ function ApprovalInputArea2({
285602
286257
  lastDeclineNote
285603
286258
  }) {
285604
286259
  const { colors: colors2 } = useTheme();
285605
- const [focusedElement, setFocusedElement] = import_react76.useState(0);
286260
+ const [focusedElement, setFocusedElement] = import_react77.useState(0);
285606
286261
  useKeyboard((key) => {
285607
286262
  if (key.name === "up") {
285608
286263
  setFocusedElement((prev) => Math.max(0, prev - 1));
@@ -285895,7 +286550,7 @@ function navigateDown(selectedIndex, queueLength) {
285895
286550
  }
285896
286551
 
285897
286552
  // src/tui/components/operator-dashboard/index.tsx
285898
- import { existsSync as existsSync27, readFileSync as readFileSync13 } from "fs";
286553
+ import { existsSync as existsSync27, readFileSync as readFileSync14 } from "fs";
285899
286554
  import { join as join28 } from "path";
285900
286555
  function OperatorDashboard({
285901
286556
  sessionId,
@@ -285929,29 +286584,29 @@ function OperatorDashboard({
285929
286584
  clear: clearDialog,
285930
286585
  setSize: setDialogSize
285931
286586
  } = useDialog();
285932
- const autocompleteOptions = import_react78.useMemo(() => {
286587
+ const autocompleteOptions = import_react79.useMemo(() => {
285933
286588
  const skillSlugs = new Set(skills.map((s2) => `/${slugify(s2.name)}`));
285934
286589
  return filterOperatorAutocomplete(allAutocompleteOptions, skillSlugs);
285935
286590
  }, [allAutocompleteOptions, skills]);
285936
- const [session, setSession] = import_react78.useState(null);
285937
- const [loading, setLoading] = import_react78.useState(true);
285938
- const [error40, setError] = import_react78.useState(null);
285939
- const pendingNameRef = import_react78.useRef(null);
285940
- const [status, setStatus] = import_react78.useState("idle");
285941
- const abortControllerRef = import_react78.useRef(null);
285942
- const generationRef = import_react78.useRef(0);
285943
- const cancelHandleRef = import_react78.useRef({
286591
+ const [session, setSession] = import_react79.useState(null);
286592
+ const [loading, setLoading] = import_react79.useState(true);
286593
+ const [error40, setError] = import_react79.useState(null);
286594
+ const pendingNameRef = import_react79.useRef(null);
286595
+ const [status, setStatus] = import_react79.useState("idle");
286596
+ const abortControllerRef = import_react79.useRef(null);
286597
+ const generationRef = import_react79.useRef(0);
286598
+ const cancelHandleRef = import_react79.useRef({
285944
286599
  cancel: () => false
285945
286600
  });
285946
- const commandCancelledRef = import_react78.useRef(false);
285947
- const [messages, setMessages] = import_react78.useState([]);
285948
- const textRef = import_react78.useRef("");
285949
- const conversationRef = import_react78.useRef([]);
285950
- const [inputValue, setInputValue] = import_react78.useState("");
285951
- const [queuedMessages, setQueuedMessages] = import_react78.useState([]);
285952
- const [selectedQueueIndex, setSelectedQueueIndex] = import_react78.useState(-1);
285953
- const queuedMessagesRef = import_react78.useRef([]);
285954
- import_react78.useEffect(() => {
286601
+ const commandCancelledRef = import_react79.useRef(false);
286602
+ const [messages, setMessages] = import_react79.useState([]);
286603
+ const textRef = import_react79.useRef("");
286604
+ const conversationRef = import_react79.useRef([]);
286605
+ const [inputValue, setInputValue] = import_react79.useState("");
286606
+ const [queuedMessages, setQueuedMessages] = import_react79.useState([]);
286607
+ const [selectedQueueIndex, setSelectedQueueIndex] = import_react79.useState(-1);
286608
+ const queuedMessagesRef = import_react79.useRef([]);
286609
+ import_react79.useEffect(() => {
285955
286610
  queuedMessagesRef.current = queuedMessages;
285956
286611
  if (queuedMessages.length === 0) {
285957
286612
  setSelectedQueueIndex(-1);
@@ -285959,17 +286614,17 @@ function OperatorDashboard({
285959
286614
  setSelectedQueueIndex(queuedMessages.length - 1);
285960
286615
  }
285961
286616
  }, [queuedMessages, selectedQueueIndex]);
285962
- const [operatorState, setOperatorState] = import_react78.useState(() => createInitialOperatorState("manual", true));
285963
- const approvalGateRef = import_react78.useRef(new ApprovalGate({ requireApproval: true }));
285964
- const [pendingApprovals, setPendingApprovals] = import_react78.useState([]);
285965
- const [lastApprovedAction, setLastApprovedAction] = import_react78.useState(null);
285966
- const [verboseMode, setVerboseMode] = import_react78.useState(false);
285967
- const [expandedLogs, setExpandedLogs] = import_react78.useState(false);
285968
- const tokenUsageRef = import_react78.useRef(tokenUsage);
285969
- import_react78.useEffect(() => {
286617
+ const [operatorState, setOperatorState] = import_react79.useState(() => createInitialOperatorState("manual", true));
286618
+ const approvalGateRef = import_react79.useRef(new ApprovalGate({ requireApproval: true }));
286619
+ const [pendingApprovals, setPendingApprovals] = import_react79.useState([]);
286620
+ const [lastApprovedAction, setLastApprovedAction] = import_react79.useState(null);
286621
+ const [verboseMode, setVerboseMode] = import_react79.useState(false);
286622
+ const [expandedLogs, setExpandedLogs] = import_react79.useState(false);
286623
+ const tokenUsageRef = import_react79.useRef(tokenUsage);
286624
+ import_react79.useEffect(() => {
285970
286625
  tokenUsageRef.current = tokenUsage;
285971
286626
  }, [tokenUsage]);
285972
- import_react78.useEffect(() => {
286627
+ import_react79.useEffect(() => {
285973
286628
  const gate = approvalGateRef.current;
285974
286629
  const onApprovalNeeded = () => {
285975
286630
  setPendingApprovals(gate.getPendingApprovals());
@@ -285991,7 +286646,7 @@ function OperatorDashboard({
285991
286646
  gate.off("approval-resolved", onApprovalResolved);
285992
286647
  };
285993
286648
  }, []);
285994
- import_react78.useEffect(() => {
286649
+ import_react79.useEffect(() => {
285995
286650
  async function loadSession() {
285996
286651
  try {
285997
286652
  if (sessionId) {
@@ -286048,10 +286703,10 @@ function OperatorDashboard({
286048
286703
  }
286049
286704
  loadSession();
286050
286705
  }, [sessionId]);
286051
- import_react78.useEffect(() => {
286706
+ import_react79.useEffect(() => {
286052
286707
  return () => setSessionCwd(null);
286053
286708
  }, [setSessionCwd]);
286054
- import_react78.useEffect(() => {
286709
+ import_react79.useEffect(() => {
286055
286710
  if (!session)
286056
286711
  return;
286057
286712
  resetTokenUsage();
@@ -286069,7 +286724,7 @@ function OperatorDashboard({
286069
286724
  });
286070
286725
  } catch {}
286071
286726
  }, [session, addTokenUsage, resetTokenUsage]);
286072
- const appendText = import_react78.useCallback((text2) => {
286727
+ const appendText = import_react79.useCallback((text2) => {
286073
286728
  textRef.current += text2;
286074
286729
  const accumulated = textRef.current;
286075
286730
  setMessages((prev) => {
@@ -286085,8 +286740,8 @@ function OperatorDashboard({
286085
286740
  ];
286086
286741
  });
286087
286742
  }, []);
286088
- const toolArgsDeltaRef = import_react78.useRef(new Map);
286089
- const addStreamingToolCall = import_react78.useCallback((toolCallId, toolName) => {
286743
+ const toolArgsDeltaRef = import_react79.useRef(new Map);
286744
+ const addStreamingToolCall = import_react79.useCallback((toolCallId, toolName) => {
286090
286745
  textRef.current = "";
286091
286746
  toolArgsDeltaRef.current.set(toolCallId, {
286092
286747
  toolName,
@@ -286105,7 +286760,7 @@ function OperatorDashboard({
286105
286760
  }
286106
286761
  ]);
286107
286762
  }, []);
286108
- const appendToolCallDelta = import_react78.useCallback((toolCallId, argsTextDelta) => {
286763
+ const appendToolCallDelta = import_react79.useCallback((toolCallId, argsTextDelta) => {
286109
286764
  const entry = toolArgsDeltaRef.current.get(toolCallId);
286110
286765
  const accumulated = (entry?.accumulated ?? "") + argsTextDelta;
286111
286766
  toolArgsDeltaRef.current.set(toolCallId, {
@@ -286127,7 +286782,7 @@ function OperatorDashboard({
286127
286782
  return updated;
286128
286783
  });
286129
286784
  }, []);
286130
- const addToolCall = import_react78.useCallback((toolCallId, toolName, args) => {
286785
+ const addToolCall = import_react79.useCallback((toolCallId, toolName, args) => {
286131
286786
  textRef.current = "";
286132
286787
  toolArgsDeltaRef.current.delete(toolCallId);
286133
286788
  setMessages((prev) => {
@@ -286156,7 +286811,7 @@ function OperatorDashboard({
286156
286811
  ];
286157
286812
  });
286158
286813
  }, []);
286159
- const updateToolResult = import_react78.useCallback((toolCallId, _toolName, result) => {
286814
+ const updateToolResult = import_react79.useCallback((toolCallId, _toolName, result) => {
286160
286815
  textRef.current = "";
286161
286816
  setMessages((prev) => {
286162
286817
  const idx = prev.findIndex((m4) => isToolMessage(m4) && m4.toolCallId === toolCallId);
@@ -286167,10 +286822,10 @@ function OperatorDashboard({
286167
286822
  return updated;
286168
286823
  });
286169
286824
  }, []);
286170
- const cmdOutputBufRef = import_react78.useRef("");
286171
- const cmdFlushTimerRef = import_react78.useRef(null);
286825
+ const cmdOutputBufRef = import_react79.useRef("");
286826
+ const cmdFlushTimerRef = import_react79.useRef(null);
286172
286827
  const MAX_LOG_LINES = 200;
286173
- const flushCommandOutput = import_react78.useCallback(() => {
286828
+ const flushCommandOutput = import_react79.useCallback(() => {
286174
286829
  const buf = cmdOutputBufRef.current;
286175
286830
  if (!buf)
286176
286831
  return;
@@ -286200,7 +286855,7 @@ function OperatorDashboard({
286200
286855
  return updated;
286201
286856
  });
286202
286857
  }, []);
286203
- const onCommandOutput = import_react78.useCallback((data) => {
286858
+ const onCommandOutput = import_react79.useCallback((data) => {
286204
286859
  cmdOutputBufRef.current += data;
286205
286860
  if (!cmdFlushTimerRef.current) {
286206
286861
  cmdFlushTimerRef.current = setInterval(() => {
@@ -286208,7 +286863,7 @@ function OperatorDashboard({
286208
286863
  }, 150);
286209
286864
  }
286210
286865
  }, [flushCommandOutput]);
286211
- import_react78.useEffect(() => {
286866
+ import_react79.useEffect(() => {
286212
286867
  return () => {
286213
286868
  if (cmdFlushTimerRef.current) {
286214
286869
  clearInterval(cmdFlushTimerRef.current);
@@ -286216,7 +286871,7 @@ function OperatorDashboard({
286216
286871
  }
286217
286872
  };
286218
286873
  }, []);
286219
- const appendLogToActiveTool = import_react78.useCallback((line) => {
286874
+ const appendLogToActiveTool = import_react79.useCallback((line) => {
286220
286875
  setMessages((prev) => {
286221
286876
  const idx = prev.findLastIndex((m4) => isToolMessage(m4) && (m4.status === "pending" || m4.status === "streaming"));
286222
286877
  if (idx === -1)
@@ -286231,7 +286886,7 @@ function OperatorDashboard({
286231
286886
  return updated;
286232
286887
  });
286233
286888
  }, []);
286234
- const initSubagent = import_react78.useCallback((subagentId, name26) => {
286889
+ const initSubagent = import_react79.useCallback((subagentId, name26) => {
286235
286890
  setMessages((prev) => {
286236
286891
  const idx = prev.findLastIndex((m4) => isToolMessage(m4) && (m4.status === "pending" || m4.status === "streaming"));
286237
286892
  if (idx === -1)
@@ -286246,7 +286901,7 @@ function OperatorDashboard({
286246
286901
  return updated;
286247
286902
  });
286248
286903
  }, []);
286249
- const completeSubagent = import_react78.useCallback((subagentId, status2) => {
286904
+ const completeSubagent = import_react79.useCallback((subagentId, status2) => {
286250
286905
  setMessages((prev) => {
286251
286906
  const idx = prev.findLastIndex((m4) => isToolMessage(m4) && (m4.status === "pending" || m4.status === "streaming"));
286252
286907
  if (idx === -1)
@@ -286264,7 +286919,7 @@ function OperatorDashboard({
286264
286919
  return updated;
286265
286920
  });
286266
286921
  }, []);
286267
- const appendLogToSubagent = import_react78.useCallback((subagentId, line) => {
286922
+ const appendLogToSubagent = import_react79.useCallback((subagentId, line) => {
286268
286923
  setMessages((prev) => {
286269
286924
  const idx = prev.findLastIndex((m4) => isToolMessage(m4) && (m4.status === "pending" || m4.status === "streaming"));
286270
286925
  if (idx === -1)
@@ -286286,13 +286941,13 @@ function OperatorDashboard({
286286
286941
  return updated;
286287
286942
  });
286288
286943
  }, []);
286289
- const handleApprove = import_react78.useCallback(() => {
286944
+ const handleApprove = import_react79.useCallback(() => {
286290
286945
  const pending = approvalGateRef.current.getPendingApprovals();
286291
286946
  if (pending.length > 0) {
286292
286947
  approvalGateRef.current.approve(pending[0].id);
286293
286948
  }
286294
286949
  }, []);
286295
- const handleAutoApprove = import_react78.useCallback(() => {
286950
+ const handleAutoApprove = import_react79.useCallback(() => {
286296
286951
  approvalGateRef.current.updateConfig({ requireApproval: false });
286297
286952
  setOperatorState((prev) => ({ ...prev, requireApproval: false }));
286298
286953
  const pending = approvalGateRef.current.getPendingApprovals();
@@ -286300,7 +286955,7 @@ function OperatorDashboard({
286300
286955
  approvalGateRef.current.approve(p.id);
286301
286956
  }
286302
286957
  }, []);
286303
- const runAgent = import_react78.useCallback(async (prompt) => {
286958
+ const runAgent = import_react79.useCallback(async (prompt) => {
286304
286959
  if (abortControllerRef.current) {
286305
286960
  abortControllerRef.current.abort();
286306
286961
  abortControllerRef.current = null;
@@ -286454,7 +287109,7 @@ function OperatorDashboard({
286454
287109
  const rootPath = agentResult.session.rootPath;
286455
287110
  const mp = join28(rootPath, "messages.json");
286456
287111
  if (existsSync27(mp)) {
286457
- const raw = JSON.parse(readFileSync13(mp, "utf-8"));
287112
+ const raw = JSON.parse(readFileSync14(mp, "utf-8"));
286458
287113
  if (Array.isArray(raw) && raw.length > 0) {
286459
287114
  conversationRef.current = sessions.getResumeMessages(raw);
286460
287115
  }
@@ -286514,7 +287169,7 @@ function OperatorDashboard({
286514
287169
  setThinking,
286515
287170
  setIsExecuting
286516
287171
  ]);
286517
- const handleSubmit = import_react78.useCallback((value) => {
287172
+ const handleSubmit = import_react79.useCallback((value) => {
286518
287173
  const pending = approvalGateRef.current.getPendingApprovals();
286519
287174
  const result = resolveSubmit(value, status, pending.length > 0);
286520
287175
  if (result.denyPending) {
@@ -286532,16 +287187,16 @@ function OperatorDashboard({
286532
287187
  setInputValue("");
286533
287188
  runAgent(result.prompt);
286534
287189
  }, [status, runAgent]);
286535
- const initialMessageSentRef = import_react78.useRef(false);
286536
- const runAgentRef = import_react78.useRef(runAgent);
287190
+ const initialMessageSentRef = import_react79.useRef(false);
287191
+ const runAgentRef = import_react79.useRef(runAgent);
286537
287192
  runAgentRef.current = runAgent;
286538
- import_react78.useEffect(() => {
287193
+ import_react79.useEffect(() => {
286539
287194
  if (!loading && initialMessage && !initialMessageSentRef.current) {
286540
287195
  initialMessageSentRef.current = true;
286541
287196
  runAgentRef.current(initialMessage);
286542
287197
  }
286543
287198
  }, [loading, initialMessage]);
286544
- import_react78.useEffect(() => {
287199
+ import_react79.useEffect(() => {
286545
287200
  if (status !== "idle")
286546
287201
  return;
286547
287202
  const queue = queuedMessagesRef.current;
@@ -286552,7 +287207,7 @@ function OperatorDashboard({
286552
287207
  setSelectedQueueIndex(-1);
286553
287208
  runAgentRef.current(next);
286554
287209
  }, [status]);
286555
- const showModelPicker = import_react78.useCallback(() => {
287210
+ const showModelPicker = import_react79.useCallback(() => {
286556
287211
  setDialogSize("large");
286557
287212
  showDialog(/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
286558
287213
  flexDirection: "column",
@@ -286614,7 +287269,7 @@ function OperatorDashboard({
286614
287269
  clearDialog,
286615
287270
  setDialogSize
286616
287271
  ]);
286617
- const handleCommandExecute = import_react78.useCallback(async (command) => {
287272
+ const handleCommandExecute = import_react79.useCallback(async (command) => {
286618
287273
  const action = routeCommand(command, resolveSkillContent);
286619
287274
  switch (action.type) {
286620
287275
  case "show-models":
@@ -286632,7 +287287,7 @@ function OperatorDashboard({
286632
287287
  return;
286633
287288
  }
286634
287289
  }, [resolveSkillContent, handleSubmit, executeCommand2, showModelPicker]);
286635
- const handleAbort = import_react78.useCallback(() => {
287290
+ const handleAbort = import_react79.useCallback(() => {
286636
287291
  if (!abortControllerRef.current)
286637
287292
  return;
286638
287293
  const action = resolveAbortAction(commandCancelledRef.current, () => cancelHandleRef.current.cancel());
@@ -286663,7 +287318,7 @@ function OperatorDashboard({
286663
287318
  try {
286664
287319
  const messagesPath = join28(session.rootPath, "messages.json");
286665
287320
  if (existsSync27(messagesPath)) {
286666
- const raw = JSON.parse(readFileSync13(messagesPath, "utf-8"));
287321
+ const raw = JSON.parse(readFileSync14(messagesPath, "utf-8"));
286667
287322
  if (Array.isArray(raw) && raw.length > 0) {
286668
287323
  conversationRef.current = sessions.getResumeMessages(raw);
286669
287324
  }
@@ -286682,7 +287337,7 @@ function OperatorDashboard({
286682
287337
  ];
286683
287338
  });
286684
287339
  }, [session, setThinking, setIsExecuting]);
286685
- const toggleApproval = import_react78.useCallback(() => {
287340
+ const toggleApproval = import_react79.useCallback(() => {
286686
287341
  setOperatorState((prev) => {
286687
287342
  const newVal = !prev.requireApproval;
286688
287343
  approvalGateRef.current.updateConfig({ requireApproval: newVal });
@@ -286916,7 +287571,7 @@ function OperatorDashboard({
286916
287571
  }
286917
287572
 
286918
287573
  // src/tui/components/commands/theme-picker.tsx
286919
- var import_react80 = __toESM(require_react(), 1);
287574
+ var import_react81 = __toESM(require_react(), 1);
286920
287575
  init_config2();
286921
287576
  function ThemePicker({ onClose }) {
286922
287577
  const dimensions = useDimensions();
@@ -286929,15 +287584,15 @@ function ThemePicker({ onClose }) {
286929
287584
  toggleMode,
286930
287585
  setMode
286931
287586
  } = useTheme();
286932
- const [selectedIndex, setSelectedIndex] = import_react80.useState(() => Math.max(0, availableThemes.indexOf(theme.name)));
286933
- const originalThemeRef = import_react80.useRef(theme.name);
286934
- const originalModeRef = import_react80.useRef(mode);
286935
- const handleClose = import_react80.useCallback(() => {
287587
+ const [selectedIndex, setSelectedIndex] = import_react81.useState(() => Math.max(0, availableThemes.indexOf(theme.name)));
287588
+ const originalThemeRef = import_react81.useRef(theme.name);
287589
+ const originalModeRef = import_react81.useRef(mode);
287590
+ const handleClose = import_react81.useCallback(() => {
286936
287591
  setTheme(originalThemeRef.current);
286937
287592
  setMode(originalModeRef.current);
286938
287593
  onClose();
286939
287594
  }, [setTheme, setMode, onClose]);
286940
- const handleConfirm = import_react80.useCallback(async () => {
287595
+ const handleConfirm = import_react81.useCallback(async () => {
286941
287596
  const currentThemeName = availableThemes[selectedIndex];
286942
287597
  if (currentThemeName) {
286943
287598
  await config2.update({ theme: currentThemeName });
@@ -287066,16 +287721,16 @@ function ThemePicker({ onClose }) {
287066
287721
  }
287067
287722
 
287068
287723
  // src/tui/components/commands/create-skill-wizard.tsx
287069
- var import_react82 = __toESM(require_react(), 1);
287724
+ var import_react83 = __toESM(require_react(), 1);
287070
287725
  function CreateSkillWizard() {
287071
287726
  const { colors: colors2 } = useTheme();
287072
287727
  const route = useRoute();
287073
- const [step, setStep] = import_react82.useState("name");
287074
- const [name26, setName] = import_react82.useState("");
287075
- const [description, setDescription] = import_react82.useState("");
287076
- const [content, setContent] = import_react82.useState("");
287077
- const [error40, setError] = import_react82.useState(null);
287078
- const [confirmFocused, setConfirmFocused] = import_react82.useState(0);
287728
+ const [step, setStep] = import_react83.useState("name");
287729
+ const [name26, setName] = import_react83.useState("");
287730
+ const [description, setDescription] = import_react83.useState("");
287731
+ const [content, setContent] = import_react83.useState("");
287732
+ const [error40, setError] = import_react83.useState(null);
287733
+ const [confirmFocused, setConfirmFocused] = import_react83.useState(0);
287079
287734
  const slug = slugify(name26);
287080
287735
  async function handleSave() {
287081
287736
  if (!name26.trim() || !content.trim())
@@ -289951,7 +290606,7 @@ async function detectTerminalMode(timeoutMs = 1000) {
289951
290606
  }
289952
290607
 
289953
290608
  // src/tui/console-theme.ts
289954
- var import_react84 = __toESM(require_react(), 1);
290609
+ var import_react85 = __toESM(require_react(), 1);
289955
290610
  var withAlpha = (rgba, a) => RGBA.fromValues(rgba.r, rgba.g, rgba.b, a);
289956
290611
  var overlayThemeRef = {
289957
290612
  current: null
@@ -289974,7 +290629,7 @@ function buildConsoleOptions(themeColors) {
289974
290629
  function ConsoleThemeSync() {
289975
290630
  const { colors: colors2 } = useTheme();
289976
290631
  const renderer = useRenderer();
289977
- import_react84.useEffect(() => {
290632
+ import_react85.useEffect(() => {
289978
290633
  overlayThemeRef.current = colors2;
289979
290634
  const c = renderer.console;
289980
290635
  c.backgroundColor = withAlpha(colors2.backgroundPanel, 0.85);
@@ -290062,17 +290717,17 @@ function setupAutoCopy(renderer, copyToClipboard) {
290062
290717
 
290063
290718
  // src/tui/index.tsx
290064
290719
  function App({ appConfig }) {
290065
- const [focusIndex, setFocusIndex] = import_react87.useState(0);
290066
- const [cwd, setCwd] = import_react87.useState(process.cwd());
290067
- const [ctrlCPressTime, setCtrlCPressTime] = import_react87.useState(null);
290068
- const [showExitWarning, setShowExitWarning] = import_react87.useState(false);
290069
- const [inputKey, setInputKey] = import_react87.useState(0);
290070
- const [showSessionsDialog, setShowSessionsDialog] = import_react87.useState(false);
290071
- const [showShortcutsDialog, setShowShortcutsDialog] = import_react87.useState(false);
290072
- const [showThemeDialog, setShowThemeDialog] = import_react87.useState(false);
290073
- const [showAuthDialog, setShowAuthDialog] = import_react87.useState(false);
290074
- const [showPentestDialog, setShowPentestDialog] = import_react87.useState(false);
290075
- const [pendingPentestFlags, setPendingPentestFlags] = import_react87.useState(undefined);
290720
+ const [focusIndex, setFocusIndex] = import_react88.useState(0);
290721
+ const [cwd, setCwd] = import_react88.useState(process.cwd());
290722
+ const [ctrlCPressTime, setCtrlCPressTime] = import_react88.useState(null);
290723
+ const [showExitWarning, setShowExitWarning] = import_react88.useState(false);
290724
+ const [inputKey, setInputKey] = import_react88.useState(0);
290725
+ const [showSessionsDialog, setShowSessionsDialog] = import_react88.useState(false);
290726
+ const [showShortcutsDialog, setShowShortcutsDialog] = import_react88.useState(false);
290727
+ const [showThemeDialog, setShowThemeDialog] = import_react88.useState(false);
290728
+ const [showAuthDialog, setShowAuthDialog] = import_react88.useState(false);
290729
+ const [showPentestDialog, setShowPentestDialog] = import_react88.useState(false);
290730
+ const [pendingPentestFlags, setPendingPentestFlags] = import_react88.useState(undefined);
290076
290731
  const navigableItems = ["command-input"];
290077
290732
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(ConfigProvider, {
290078
290733
  config: appConfig,
@@ -290159,14 +290814,14 @@ function AppContent({
290159
290814
  const { toast } = useToast();
290160
290815
  const { refocusPrompt } = useFocus();
290161
290816
  const { setExternalDialogOpen } = useDialog();
290162
- import_react87.useEffect(() => {
290817
+ import_react88.useEffect(() => {
290163
290818
  checkForUpdate().then(({ updateAvailable, currentVersion, latestVersion }) => {
290164
290819
  if (!updateAvailable)
290165
290820
  return;
290166
290821
  toast(`Update available: v${currentVersion} → v${latestVersion}. Run: pensar upgrade`, "warn", 8000);
290167
290822
  });
290168
290823
  }, []);
290169
- import_react87.useEffect(() => {
290824
+ import_react88.useEffect(() => {
290170
290825
  if (route.data.type !== "base")
290171
290826
  return;
290172
290827
  if (!config3.data.responsibleUseAccepted && route.data.path !== "disclosure") {
@@ -290175,12 +290830,12 @@ function AppContent({
290175
290830
  route.navigate({ type: "base", path: "providers" });
290176
290831
  }
290177
290832
  }, [config3.data.responsibleUseAccepted, route.data]);
290178
- import_react87.useEffect(() => {
290833
+ import_react88.useEffect(() => {
290179
290834
  if (showThemeDialog || showAuthDialog || showPentestDialog) {
290180
290835
  setExternalDialogOpen(true);
290181
290836
  }
290182
290837
  }, [showThemeDialog, showAuthDialog, showPentestDialog]);
290183
- import_react87.useEffect(() => {
290838
+ import_react88.useEffect(() => {
290184
290839
  if (showExitWarning) {
290185
290840
  const timer = setTimeout(() => {
290186
290841
  setShowExitWarning(false);
@@ -290300,7 +290955,7 @@ function CommandDisplay({
290300
290955
  await config3.update({ responsibleUseAccepted: true });
290301
290956
  route.navigate({
290302
290957
  type: "base",
290303
- path: "home"
290958
+ path: "providers"
290304
290959
  });
290305
290960
  };
290306
290961
  if (route.data.type === "base") {