whale-code 6.4.0 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/bin/swagmanager-mcp.js +7 -0
  2. package/dist/cli/app.js +30 -2
  3. package/dist/cli/chat/ChatApp.d.ts +4 -4
  4. package/dist/cli/chat/ChatApp.js +114 -44
  5. package/dist/cli/chat/ChatInput.d.ts +13 -6
  6. package/dist/cli/chat/ChatInput.js +433 -89
  7. package/dist/cli/chat/MemoryManager.d.ts +15 -0
  8. package/dist/cli/chat/MemoryManager.js +61 -0
  9. package/dist/cli/chat/MessageList.d.ts +8 -0
  10. package/dist/cli/chat/MessageList.js +1 -1
  11. package/dist/cli/chat/NodeManager.d.ts +30 -0
  12. package/dist/cli/chat/NodeManager.js +89 -0
  13. package/dist/cli/chat/NodeSelector.d.ts +19 -0
  14. package/dist/cli/chat/NodeSelector.js +37 -0
  15. package/dist/cli/chat/PlanApproval.d.ts +17 -0
  16. package/dist/cli/chat/PlanApproval.js +82 -0
  17. package/dist/cli/chat/SessionManager.d.ts +16 -0
  18. package/dist/cli/chat/SessionManager.js +43 -0
  19. package/dist/cli/chat/SlashMenu.d.ts +38 -0
  20. package/dist/cli/chat/SlashMenu.js +208 -0
  21. package/dist/cli/chat/StatusBar.d.ts +16 -0
  22. package/dist/cli/chat/StatusBar.js +22 -0
  23. package/dist/cli/chat/ThemeSelector.d.ts +14 -0
  24. package/dist/cli/chat/ThemeSelector.js +29 -0
  25. package/dist/cli/chat/ToolIndicator.d.ts +8 -0
  26. package/dist/cli/chat/ToolIndicator.js +33 -9
  27. package/dist/cli/chat/hooks/useAgentLoop.d.ts +2 -1
  28. package/dist/cli/chat/hooks/useAgentLoop.js +22 -17
  29. package/dist/cli/chat/hooks/useSlashCommands.d.ts +19 -0
  30. package/dist/cli/chat/hooks/useSlashCommands.js +254 -15
  31. package/dist/cli/commands/config-cmd.js +4 -25
  32. package/dist/cli/commands/db.d.ts +13 -0
  33. package/dist/cli/commands/db.js +243 -0
  34. package/dist/cli/commands/doctor.js +6 -9
  35. package/dist/cli/commands/mcp.js +1 -20
  36. package/dist/cli/services/agent-events.d.ts +22 -1
  37. package/dist/cli/services/agent-events.js +9 -0
  38. package/dist/cli/services/agent-loop.js +66 -2
  39. package/dist/cli/services/agent-worker-base.js +21 -6
  40. package/dist/cli/services/api-retry.d.ts +25 -0
  41. package/dist/cli/services/api-retry.js +91 -0
  42. package/dist/cli/services/auth-service.d.ts +1 -1
  43. package/dist/cli/services/auth-service.js +40 -19
  44. package/dist/cli/services/background-processes.js +26 -2
  45. package/dist/cli/services/config-store.d.ts +13 -1
  46. package/dist/cli/services/config-store.js +116 -13
  47. package/dist/cli/services/format-server-response.js +12 -6
  48. package/dist/cli/services/ink-resize-fix.d.ts +18 -0
  49. package/dist/cli/services/ink-resize-fix.js +66 -0
  50. package/dist/cli/services/interactive-tools.d.ts +14 -0
  51. package/dist/cli/services/interactive-tools.js +47 -2
  52. package/dist/cli/services/keybinding-manager.js +1 -1
  53. package/dist/cli/services/local-tools.js +35 -2
  54. package/dist/cli/services/server-tools.js +175 -3
  55. package/dist/cli/services/subagent.js +15 -3
  56. package/dist/cli/services/system-prompt.js +5 -3
  57. package/dist/cli/services/task-decomposer.d.ts +35 -0
  58. package/dist/cli/services/task-decomposer.js +199 -0
  59. package/dist/cli/services/team-lead.d.ts +18 -0
  60. package/dist/cli/services/team-lead.js +80 -0
  61. package/dist/cli/services/teammate.js +5 -5
  62. package/dist/cli/services/telemetry.d.ts +8 -2
  63. package/dist/cli/services/telemetry.js +116 -92
  64. package/dist/cli/services/tools/agent-tools.d.ts +1 -0
  65. package/dist/cli/services/tools/agent-tools.js +50 -4
  66. package/dist/cli/services/tools/file-ops.d.ts +2 -0
  67. package/dist/cli/services/tools/file-ops.js +71 -19
  68. package/dist/cli/services/tools/shell-exec.js +22 -12
  69. package/dist/cli/shared/Theme.d.ts +1 -2
  70. package/dist/cli/shared/Theme.js +1 -1
  71. package/dist/cli/shared/WhaleBanner.d.ts +4 -1
  72. package/dist/cli/shared/WhaleBanner.js +12 -8
  73. package/dist/cli/shared/markdown.d.ts +5 -4
  74. package/dist/cli/shared/markdown.js +376 -334
  75. package/dist/cli/shared/theme-manager.d.ts +27 -0
  76. package/dist/cli/shared/theme-manager.js +178 -0
  77. package/dist/cli/shared/theme-presets.d.ts +16 -0
  78. package/dist/cli/shared/theme-presets.js +265 -0
  79. package/dist/index.js +0 -51
  80. package/dist/node/adapters/imessage.d.ts +10 -0
  81. package/dist/node/adapters/imessage.js +45 -6
  82. package/dist/node/cli.js +459 -8
  83. package/dist/node/config.d.ts +17 -0
  84. package/dist/node/gateway-client.d.ts +55 -0
  85. package/dist/node/gateway-client.js +201 -0
  86. package/dist/node/portal/clipboard.d.ts +28 -0
  87. package/dist/node/portal/clipboard.js +183 -0
  88. package/dist/node/portal/discovery.d.ts +29 -0
  89. package/dist/node/portal/discovery.js +61 -0
  90. package/dist/node/portal/forward.d.ts +30 -0
  91. package/dist/node/portal/forward.js +90 -0
  92. package/dist/node/portal/index.d.ts +47 -0
  93. package/dist/node/portal/index.js +250 -0
  94. package/dist/node/portal/multiplexer.d.ts +48 -0
  95. package/dist/node/portal/multiplexer.js +207 -0
  96. package/dist/node/portal/permissions.d.ts +36 -0
  97. package/dist/node/portal/permissions.js +131 -0
  98. package/dist/node/portal/protocol.d.ts +140 -0
  99. package/dist/node/portal/protocol.js +193 -0
  100. package/dist/node/portal/screen.d.ts +18 -0
  101. package/dist/node/portal/screen.js +93 -0
  102. package/dist/node/portal/session.d.ts +68 -0
  103. package/dist/node/portal/session.js +127 -0
  104. package/dist/node/portal/shell.d.ts +26 -0
  105. package/dist/node/portal/shell.js +142 -0
  106. package/dist/node/portal/stream.d.ts +43 -0
  107. package/dist/node/portal/stream.js +90 -0
  108. package/dist/node/portal/transfer.d.ts +33 -0
  109. package/dist/node/portal/transfer.js +231 -0
  110. package/dist/node/portal/ui.d.ts +16 -0
  111. package/dist/node/portal/ui.js +148 -0
  112. package/dist/node/remote-desktop/compile-helper.d.ts +13 -0
  113. package/dist/node/remote-desktop/compile-helper.js +73 -0
  114. package/dist/node/remote-desktop/index.d.ts +67 -0
  115. package/dist/node/remote-desktop/index.js +220 -0
  116. package/dist/node/remote-desktop/protocol.d.ts +96 -0
  117. package/dist/node/remote-desktop/protocol.js +67 -0
  118. package/dist/node/runtime.d.ts +8 -1
  119. package/dist/node/runtime.js +117 -9
  120. package/dist/server/handlers/__test-utils__/test-db.d.ts +25 -0
  121. package/dist/server/handlers/__test-utils__/test-db.js +128 -0
  122. package/dist/server/handlers/api-keys.js +26 -2
  123. package/dist/server/handlers/browser.d.ts +0 -4
  124. package/dist/server/handlers/browser.js +0 -46
  125. package/dist/server/handlers/catalog.js +37 -14
  126. package/dist/server/handlers/clickhouse.d.ts +10 -0
  127. package/dist/server/handlers/clickhouse.js +215 -0
  128. package/dist/server/handlers/comms.d.ts +308 -4
  129. package/dist/server/handlers/comms.js +444 -11
  130. package/dist/server/handlers/creations.js +1 -1
  131. package/dist/server/handlers/crm.d.ts +54 -8
  132. package/dist/server/handlers/crm.js +353 -68
  133. package/dist/server/handlers/embeddings.js +3 -3
  134. package/dist/server/handlers/enrichment.js +39 -55
  135. package/dist/server/handlers/inventory.js +1 -1
  136. package/dist/server/handlers/kali.d.ts +9 -1
  137. package/dist/server/handlers/kali.js +50 -1
  138. package/dist/server/handlers/media.d.ts +8 -0
  139. package/dist/server/handlers/media.js +902 -0
  140. package/dist/server/handlers/meta-ads.js +6 -3
  141. package/dist/server/handlers/nodes.d.ts +2 -0
  142. package/dist/server/handlers/nodes.js +331 -40
  143. package/dist/server/handlers/operations.d.ts +4 -6
  144. package/dist/server/handlers/operations.js +99 -38
  145. package/dist/server/handlers/platform.js +224 -107
  146. package/dist/server/handlers/remove-bg.d.ts +6 -0
  147. package/dist/server/handlers/remove-bg.js +96 -0
  148. package/dist/server/handlers/storefront.d.ts +6 -0
  149. package/dist/server/handlers/storefront.js +477 -0
  150. package/dist/server/handlers/supply-chain.js +21 -3
  151. package/dist/server/handlers/workflow-steps.js +87 -31
  152. package/dist/server/handlers/workflows.js +4 -1
  153. package/dist/server/index.js +334 -88
  154. package/dist/server/lib/clickhouse-buffer.d.ts +48 -0
  155. package/dist/server/lib/clickhouse-buffer.js +175 -0
  156. package/dist/server/lib/clickhouse-client.d.ts +112 -0
  157. package/dist/server/lib/clickhouse-client.js +141 -0
  158. package/dist/server/lib/coa-renderer.d.ts +91 -0
  159. package/dist/server/lib/coa-renderer.js +411 -0
  160. package/dist/server/lib/compaction-service.js +45 -1
  161. package/dist/server/lib/pdf-renderer.d.ts +143 -0
  162. package/dist/server/lib/pdf-renderer.js +867 -0
  163. package/dist/server/lib/react-pdf-layout.d.ts +40 -0
  164. package/dist/server/lib/react-pdf-layout.js +437 -0
  165. package/dist/server/lib/server-agent-loop.d.ts +2 -0
  166. package/dist/server/lib/server-agent-loop.js +61 -15
  167. package/dist/server/lib/server-subagent.d.ts +3 -0
  168. package/dist/server/lib/server-subagent.js +7 -4
  169. package/dist/server/lib/supabase-client.js +51 -3
  170. package/dist/server/lib/template-resolver.js +14 -4
  171. package/dist/server/lib/utils.js +15 -0
  172. package/dist/server/local-agent-gateway.d.ts +44 -0
  173. package/dist/server/local-agent-gateway.js +389 -49
  174. package/dist/server/providers/anthropic.js +12 -2
  175. package/dist/server/providers/gemini.js +17 -2
  176. package/dist/server/proxy-handlers.js +151 -0
  177. package/dist/server/tool-router.d.ts +2 -2
  178. package/dist/server/tool-router.js +25 -35
  179. package/dist/shared/agent-core.d.ts +5 -2
  180. package/dist/shared/agent-core.js +30 -4
  181. package/dist/shared/api-client.js +54 -3
  182. package/dist/shared/sse-parser.d.ts +1 -1
  183. package/dist/shared/sse-parser.js +5 -2
  184. package/dist/shared/tool-dispatch.js +1 -1
  185. package/package.json +16 -10
  186. package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +0 -11
  187. package/dist/server/handlers/__test-utils__/mock-supabase.js +0 -393
@@ -66,7 +66,7 @@ export async function handleEnrichment(sb, args, storeId) {
66
66
  const profile = {
67
67
  customer_id: customerId,
68
68
  store_id: sid,
69
- source: "pdl",
69
+ enrichment_source: "pdl",
70
70
  enrichment_data: pdlData,
71
71
  enriched_at: nowISO(),
72
72
  updated_at: nowISO(),
@@ -157,7 +157,7 @@ export async function handleEnrichment(sb, args, storeId) {
157
157
  const profile = {
158
158
  customer_id: customerId,
159
159
  store_id: sid,
160
- source: "brightdata",
160
+ enrichment_source: "brightdata",
161
161
  enrichment_data: profileData,
162
162
  enriched_at: nowISO(),
163
163
  updated_at: nowISO(),
@@ -179,7 +179,7 @@ export async function handleEnrichment(sb, args, storeId) {
179
179
  .select("id")
180
180
  .eq("customer_id", customerId)
181
181
  .eq("store_id", sid)
182
- .eq("source", "brightdata")
182
+ .eq("enrichment_source", "brightdata")
183
183
  .maybeSingle();
184
184
  let result;
185
185
  if (existing) {
@@ -245,17 +245,17 @@ export async function handleEnrichment(sb, args, storeId) {
245
245
  breach_domain: breach.domain || null,
246
246
  breach_date: breach.xposed_date || breach.date || null,
247
247
  data_classes: breach.xposed_data ? breach.xposed_data.split(",").map((s) => s.trim()) : null,
248
- source: "xonplus",
248
+ breach_source: "xonplus",
249
249
  raw_data: breach,
250
250
  discovered_at: nowISO(),
251
251
  created_at: nowISO(),
252
252
  };
253
- // Deduplicate by customer_id + breach_name + source
253
+ // Deduplicate by customer_id + breach_name + breach_source
254
254
  const { data: existing } = await sb.from("customer_breach_records")
255
255
  .select("id")
256
256
  .eq("customer_id", customerId)
257
257
  .eq("breach_name", record.breach_name)
258
- .eq("source", "xonplus")
258
+ .eq("breach_source", "xonplus")
259
259
  .maybeSingle();
260
260
  if (!existing) {
261
261
  const { data, error } = await sb.from("customer_breach_records")
@@ -326,8 +326,7 @@ export async function handleEnrichment(sb, args, storeId) {
326
326
  data_classes: breach.DataClasses,
327
327
  description: breach.Description,
328
328
  is_verified: breach.IsVerified,
329
- is_sensitive: breach.IsSensitive,
330
- source: "hibp",
329
+ breach_source: "hibp",
331
330
  discovered_at: nowISO(),
332
331
  created_at: nowISO(),
333
332
  };
@@ -404,7 +403,7 @@ export async function handleEnrichment(sb, args, storeId) {
404
403
  breach_domain: entry.domain || null,
405
404
  breach_date: entry.obtained_date || null,
406
405
  data_classes: entry.type ? [entry.type] : null,
407
- source: "dehashed",
406
+ breach_source: "dehashed",
408
407
  raw_data: entry,
409
408
  discovered_at: nowISO(),
410
409
  created_at: nowISO(),
@@ -442,7 +441,7 @@ export async function handleEnrichment(sb, args, storeId) {
442
441
  .eq("store_id", sid)
443
442
  .order("enriched_at", { ascending: false });
444
443
  if (args.source)
445
- q = q.eq("source", args.source);
444
+ q = q.eq("enrichment_source", args.source);
446
445
  const limit = args.limit || 10;
447
446
  q = q.limit(limit);
448
447
  const { data, error } = await q;
@@ -461,7 +460,7 @@ export async function handleEnrichment(sb, args, storeId) {
461
460
  .eq("store_id", sid)
462
461
  .order("discovered_at", { ascending: false });
463
462
  if (args.source)
464
- q = q.eq("source", args.source);
463
+ q = q.eq("breach_source", args.source);
465
464
  const limit = args.limit || 50;
466
465
  q = q.limit(limit);
467
466
  const { data, error } = await q;
@@ -478,11 +477,11 @@ export async function handleEnrichment(sb, args, storeId) {
478
477
  .select("*")
479
478
  .eq("customer_id", customerId)
480
479
  .eq("store_id", sid)
481
- .order("discovered_at", { ascending: false });
480
+ .order("first_seen_at", { ascending: false });
482
481
  if (args.status)
483
482
  q = q.eq("status", args.status);
484
- if (args.broker)
485
- q = q.eq("broker", args.broker);
483
+ if (args.source_name || args.broker)
484
+ q = q.eq("source_name", (args.source_name || args.broker));
486
485
  const limit = args.limit || 50;
487
486
  q = q.limit(limit);
488
487
  const { data, error } = await q;
@@ -555,29 +554,27 @@ export async function handleEnrichment(sb, args, storeId) {
555
554
  // ---- STORE_EXPOSURE: Insert a broker exposure ----
556
555
  case "store_exposure": {
557
556
  const customerId = args.customer_id;
558
- const broker = args.broker;
557
+ const sourceName = (args.source_name || args.broker);
559
558
  if (!customerId)
560
559
  return { success: false, error: "customer_id is required" };
561
- if (!broker)
562
- return { success: false, error: "broker is required" };
560
+ if (!sourceName)
561
+ return { success: false, error: "source_name is required" };
563
562
  // Check for existing exposure to avoid duplicates
564
563
  const { data: existing } = await sb.from("customer_exposures")
565
564
  .select("id")
566
565
  .eq("customer_id", customerId)
567
566
  .eq("store_id", sid)
568
- .eq("broker", broker)
567
+ .eq("source_name", sourceName)
569
568
  .maybeSingle();
570
569
  if (existing) {
571
570
  // Update existing record
572
- const updates = { updated_at: nowISO() };
573
- if (args.profile_url)
574
- updates.profile_url = args.profile_url;
575
- if (args.data_found)
576
- updates.data_found = args.data_found;
571
+ const updates = {};
572
+ if (args.source_url)
573
+ updates.source_url = args.source_url;
574
+ if (args.data_types_exposed)
575
+ updates.data_types_exposed = args.data_types_exposed;
577
576
  if (args.status)
578
577
  updates.status = args.status;
579
- if (args.exposure_data)
580
- updates.exposure_data = args.exposure_data;
581
578
  const { data, error } = await sb.from("customer_exposures")
582
579
  .update(updates)
583
580
  .eq("id", existing.id)
@@ -590,17 +587,16 @@ export async function handleEnrichment(sb, args, storeId) {
590
587
  const record = {
591
588
  customer_id: customerId,
592
589
  store_id: sid,
593
- broker,
590
+ source_name: sourceName,
591
+ source_type: args.source_type || "data_broker",
594
592
  status: args.status || "found",
595
- discovered_at: nowISO(),
593
+ first_seen_at: nowISO(),
596
594
  created_at: nowISO(),
597
595
  };
598
- if (args.profile_url)
599
- record.profile_url = args.profile_url;
600
- if (args.data_found)
601
- record.data_found = args.data_found;
602
- if (args.exposure_data)
603
- record.exposure_data = args.exposure_data;
596
+ if (args.source_url)
597
+ record.source_url = args.source_url;
598
+ if (args.data_types_exposed)
599
+ record.data_types_exposed = args.data_types_exposed;
604
600
  if (args.scan_id)
605
601
  record.scan_id = args.scan_id;
606
602
  const { data, error } = await sb.from("customer_exposures")
@@ -616,15 +612,9 @@ export async function handleEnrichment(sb, args, storeId) {
616
612
  const exposureId = args.exposure_id;
617
613
  if (!exposureId)
618
614
  return { success: false, error: "exposure_id is required" };
619
- const updates = { updated_at: nowISO() };
615
+ const updates = {};
620
616
  if (args.status)
621
617
  updates.status = args.status;
622
- if (args.removal_method)
623
- updates.removal_method = args.removal_method;
624
- if (args.removal_confirmed_at)
625
- updates.removal_confirmed_at = args.removal_confirmed_at;
626
- if (args.notes)
627
- updates.notes = args.notes;
628
618
  if (args.status === "removed") {
629
619
  updates.removed_at = nowISO();
630
620
  }
@@ -641,16 +631,16 @@ export async function handleEnrichment(sb, args, storeId) {
641
631
  // ---- STORE_REMOVAL_REQUEST: Insert a removal request ----
642
632
  case "store_removal_request": {
643
633
  const customerId = args.customer_id;
644
- const broker = args.broker;
634
+ const brokerName = (args.broker_name || args.source_name || args.broker);
645
635
  if (!customerId)
646
636
  return { success: false, error: "customer_id is required" };
647
- if (!broker)
648
- return { success: false, error: "broker is required" };
637
+ if (!brokerName)
638
+ return { success: false, error: "source_name is required" };
649
639
  const record = {
650
640
  customer_id: customerId,
651
641
  store_id: sid,
652
- broker,
653
- method: args.method || "manual",
642
+ broker_name: brokerName,
643
+ removal_method: args.removal_method || args.method || "manual",
654
644
  status: args.status || "pending",
655
645
  created_at: nowISO(),
656
646
  updated_at: nowISO(),
@@ -679,15 +669,15 @@ export async function handleEnrichment(sb, args, storeId) {
679
669
  // Gather data for risk calculation
680
670
  const [exposuresResult, breachesResult, removalsResult] = await Promise.all([
681
671
  sb.from("customer_exposures")
682
- .select("id, broker, status, data_found")
672
+ .select("id, source_name, status")
683
673
  .eq("customer_id", customerId)
684
674
  .eq("store_id", sid),
685
675
  sb.from("customer_breach_records")
686
- .select("id, breach_name, data_classes, is_sensitive, is_verified")
676
+ .select("id, breach_name, data_classes, is_verified")
687
677
  .eq("customer_id", customerId)
688
678
  .eq("store_id", sid),
689
679
  sb.from("customer_removal_requests")
690
- .select("id, status, broker")
680
+ .select("id, status, broker_name")
691
681
  .eq("customer_id", customerId)
692
682
  .eq("store_id", sid),
693
683
  ]);
@@ -710,12 +700,7 @@ export async function handleEnrichment(sb, args, storeId) {
710
700
  score += removedExposures.length * 2;
711
701
  // Breach scoring
712
702
  for (const breach of breaches) {
713
- if (breach.is_sensitive) {
714
- score += 25;
715
- }
716
- else {
717
- score += 15;
718
- }
703
+ score += 15;
719
704
  if (breach.is_verified) {
720
705
  score += 5;
721
706
  }
@@ -744,7 +729,6 @@ export async function handleEnrichment(sb, args, storeId) {
744
729
  active_exposures: activeExposures.length,
745
730
  removed_exposures: removedExposures.length,
746
731
  total_breaches: breaches.length,
747
- sensitive_breaches: breaches.filter((b) => b.is_sensitive).length,
748
732
  completed_removals: completedRemovals.length,
749
733
  pending_removals: removals.filter((r) => r.status === "pending").length,
750
734
  },
@@ -415,7 +415,7 @@ export async function handleInventoryAudit(sb, args, storeId) {
415
415
  switch (args.action) {
416
416
  case "start": {
417
417
  const { data, error } = await sb.from("inventory_audits")
418
- .insert({ store_id: sid, location_id: args.location_id, status: "in_progress", started_at: new Date().toISOString() })
418
+ .insert({ store_id: sid, location_id: args.location_id, audit_type: args.audit_type || "full", status: "in_progress", started_at: new Date().toISOString() })
419
419
  .select().single();
420
420
  return error ? { success: false, error: error.message } : { success: true, data };
421
421
  }
@@ -3,7 +3,15 @@ export interface KaliProgressEvent {
3
3
  type: "stdout" | "stderr";
4
4
  data: string;
5
5
  }
6
- export declare function handleKali(_sb: SupabaseClient, args: Record<string, unknown>, _storeId?: string, onToolProgress?: (progress: KaliProgressEvent) => void): Promise<{
6
+ export interface KaliStructuredProgress {
7
+ phase?: string;
8
+ elapsed_s: number;
9
+ stdout_lines: number;
10
+ stderr_lines: number;
11
+ stdout_bytes: number;
12
+ last_line?: string;
13
+ }
14
+ export declare function handleKali(_sb: SupabaseClient, args: Record<string, unknown>, _storeId?: string, onToolProgress?: (progress: KaliProgressEvent) => void, onStructuredProgress?: (progress: KaliStructuredProgress) => void): Promise<{
7
15
  success: boolean;
8
16
  data?: unknown;
9
17
  error?: string;
@@ -5,6 +5,25 @@
5
5
  const KALI_BOX_URL = process.env.KALI_BOX_URL || "http://kali-box.internal:8080";
6
6
  const KALI_AUTH_TOKEN = process.env.KALI_AUTH_TOKEN || "";
7
7
  const MAX_OUTPUT_CHARS = 500 * 1024; // 500KB safety cap — context_management handles limits
8
+ /** Detect the current phase of a command based on output patterns */
9
+ function detectPhase(stdout, stderr) {
10
+ const combined = (stderr + "\n" + stdout).toLowerCase();
11
+ if (combined.includes("compiling") || combined.includes("building"))
12
+ return "compiling";
13
+ if (combined.includes("scanning") || combined.includes("nmap"))
14
+ return "scanning";
15
+ if (combined.includes("downloading") || combined.includes("fetching"))
16
+ return "downloading";
17
+ if (combined.includes("installing") || combined.includes("apt"))
18
+ return "installing";
19
+ if (combined.includes("testing") || combined.includes("exploit"))
20
+ return "testing";
21
+ if (combined.includes("cracking") || combined.includes("hashcat") || combined.includes("john"))
22
+ return "cracking";
23
+ if (combined.includes("enumerating") || combined.includes("enum"))
24
+ return "enumerating";
25
+ return undefined;
26
+ }
8
27
  const VALID_ACTIONS = new Set([
9
28
  "exec", "exec_stream", "exec_bg", "bg_status", "bg_kill", "bg_list",
10
29
  "upload", "download", "info", "sessions", "reset",
@@ -62,7 +81,7 @@ async function* readNDJSON(body) {
62
81
  reader.releaseLock();
63
82
  }
64
83
  }
65
- export async function handleKali(_sb, args, _storeId, onToolProgress) {
84
+ export async function handleKali(_sb, args, _storeId, onToolProgress, onStructuredProgress) {
66
85
  const action = args.action;
67
86
  if (!action) {
68
87
  return { success: false, error: "action is required" };
@@ -112,11 +131,21 @@ export async function handleKali(_sb, args, _storeId, onToolProgress) {
112
131
  // Read NDJSON stream — emit progress for each chunk, accumulate for final result
113
132
  let stdout = "";
114
133
  let stderr = "";
134
+ let stdoutLines = 0;
135
+ let stderrLines = 0;
136
+ let lastLine = "";
115
137
  let doneEvent = null;
138
+ const streamStart = Date.now();
139
+ let lastStatusEmit = 0;
140
+ const STATUS_INTERVAL_MS = 3000;
116
141
  for await (const line of readNDJSON(resp.body)) {
117
142
  if (line.type === "stdout") {
118
143
  const data = String(line.data || "");
119
144
  stdout += data;
145
+ stdoutLines += data.split("\n").filter(l => l.trim()).length;
146
+ const trimmed = data.trim();
147
+ if (trimmed)
148
+ lastLine = trimmed.split("\n").pop() || lastLine;
120
149
  // Filter out the CWD marker line from progress events
121
150
  if (!data.includes("__KALI_END_")) {
122
151
  onToolProgress({ type: "stdout", data });
@@ -125,11 +154,31 @@ export async function handleKali(_sb, args, _storeId, onToolProgress) {
125
154
  else if (line.type === "stderr") {
126
155
  const data = String(line.data || "");
127
156
  stderr += data;
157
+ stderrLines += data.split("\n").filter(l => l.trim()).length;
128
158
  onToolProgress({ type: "stderr", data });
129
159
  }
130
160
  else if (line.type === "done") {
131
161
  doneEvent = line;
132
162
  }
163
+ // Emit structured status every 3 seconds
164
+ const now = Date.now();
165
+ if (now - lastStatusEmit >= STATUS_INTERVAL_MS) {
166
+ lastStatusEmit = now;
167
+ const structuredProgress = {
168
+ phase: detectPhase(stdout, stderr),
169
+ elapsed_s: Math.round((now - streamStart) / 1000),
170
+ stdout_lines: stdoutLines,
171
+ stderr_lines: stderrLines,
172
+ stdout_bytes: stdout.length,
173
+ last_line: lastLine.length > 120 ? lastLine.slice(0, 117) + "..." : lastLine || undefined,
174
+ };
175
+ if (onStructuredProgress) {
176
+ onStructuredProgress(structuredProgress);
177
+ }
178
+ // Also emit as a special "status" event through the standard progress callback
179
+ // so the NDJSON streaming path can relay it to the CLI
180
+ onToolProgress({ type: "status", progress: structuredProgress });
181
+ }
133
182
  }
134
183
  // Strip CWD marker from accumulated stdout (same as non-streaming path)
135
184
  const markerIdx = stdout.lastIndexOf("__KALI_END_");
@@ -0,0 +1,8 @@
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ type Result = {
3
+ success: boolean;
4
+ data?: unknown;
5
+ error?: string;
6
+ };
7
+ export declare function handleMedia(sb: SupabaseClient, args: Record<string, unknown>, storeId?: string): Promise<Result>;
8
+ export {};