@shield-agent/kya 0.32.7 → 0.33.1

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 (306) hide show
  1. package/.cursor-plugin/plugin.json +1 -1
  2. package/.mcp.json +1 -1
  3. package/LIMITATIONS.md +2 -2
  4. package/README.md +232 -17
  5. package/amp/settings.example.json +1 -1
  6. package/claude/claude_desktop_config.example.json +1 -1
  7. package/cline/cline_mcp_settings.example.json +1 -1
  8. package/copilot/mcp-config.example.json +1 -1
  9. package/data/iocs.json +17 -0
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.d.ts.map +1 -1
  12. package/dist/cli.js +237 -53
  13. package/dist/cli.js.map +1 -1
  14. package/dist/client.d.ts +2 -0
  15. package/dist/client.d.ts.map +1 -1
  16. package/dist/client.js +1 -1
  17. package/dist/client.js.map +1 -1
  18. package/dist/commands/connect.d.ts +1 -1
  19. package/dist/commands/connect.d.ts.map +1 -1
  20. package/dist/commands/connect.js +11 -3
  21. package/dist/commands/connect.js.map +1 -1
  22. package/dist/commands/dash.d.ts.map +1 -1
  23. package/dist/commands/dash.js +25 -1
  24. package/dist/commands/dash.js.map +1 -1
  25. package/dist/commands/doctor.d.ts +8 -0
  26. package/dist/commands/doctor.d.ts.map +1 -0
  27. package/dist/commands/doctor.js +26 -0
  28. package/dist/commands/doctor.js.map +1 -0
  29. package/dist/commands/eval-tool.d.ts +3 -0
  30. package/dist/commands/eval-tool.d.ts.map +1 -1
  31. package/dist/commands/eval-tool.js +2 -1
  32. package/dist/commands/eval-tool.js.map +1 -1
  33. package/dist/commands/export.d.ts +33 -0
  34. package/dist/commands/export.d.ts.map +1 -0
  35. package/dist/commands/export.js +121 -0
  36. package/dist/commands/export.js.map +1 -0
  37. package/dist/commands/hook.d.ts.map +1 -1
  38. package/dist/commands/hook.js +135 -26
  39. package/dist/commands/hook.js.map +1 -1
  40. package/dist/commands/hosted-hook.d.ts +8 -1
  41. package/dist/commands/hosted-hook.d.ts.map +1 -1
  42. package/dist/commands/hosted-hook.js +15 -5
  43. package/dist/commands/hosted-hook.js.map +1 -1
  44. package/dist/commands/login.d.ts +34 -0
  45. package/dist/commands/login.d.ts.map +1 -0
  46. package/dist/commands/login.js +74 -0
  47. package/dist/commands/login.js.map +1 -0
  48. package/dist/commands/map.d.ts +21 -0
  49. package/dist/commands/map.d.ts.map +1 -0
  50. package/dist/commands/map.js +76 -0
  51. package/dist/commands/map.js.map +1 -0
  52. package/dist/commands/notify-flush.d.ts.map +1 -1
  53. package/dist/commands/notify-flush.js +7 -1
  54. package/dist/commands/notify-flush.js.map +1 -1
  55. package/dist/commands/posture.d.ts +21 -0
  56. package/dist/commands/posture.d.ts.map +1 -0
  57. package/dist/commands/posture.js +177 -0
  58. package/dist/commands/posture.js.map +1 -0
  59. package/dist/commands/receipt-serve.d.ts +7 -0
  60. package/dist/commands/receipt-serve.d.ts.map +1 -1
  61. package/dist/commands/receipt-serve.js +50 -0
  62. package/dist/commands/receipt-serve.js.map +1 -1
  63. package/dist/commands/start.d.ts.map +1 -1
  64. package/dist/commands/start.js +5 -1
  65. package/dist/commands/start.js.map +1 -1
  66. package/dist/commands/team.d.ts.map +1 -1
  67. package/dist/commands/team.js +30 -11
  68. package/dist/commands/team.js.map +1 -1
  69. package/dist/commands/wire-hooks.d.ts +5 -1
  70. package/dist/commands/wire-hooks.d.ts.map +1 -1
  71. package/dist/commands/wire-hooks.js +23 -8
  72. package/dist/commands/wire-hooks.js.map +1 -1
  73. package/dist/commands/wrap.d.ts +38 -1
  74. package/dist/commands/wrap.d.ts.map +1 -1
  75. package/dist/commands/wrap.js +82 -6
  76. package/dist/commands/wrap.js.map +1 -1
  77. package/dist/config.d.ts +2 -0
  78. package/dist/config.d.ts.map +1 -1
  79. package/dist/config.js.map +1 -1
  80. package/dist/cta.d.ts +23 -0
  81. package/dist/cta.d.ts.map +1 -0
  82. package/dist/cta.js +86 -0
  83. package/dist/cta.js.map +1 -0
  84. package/dist/daily-slot.d.ts +2 -0
  85. package/dist/daily-slot.d.ts.map +1 -0
  86. package/dist/daily-slot.js +29 -0
  87. package/dist/daily-slot.js.map +1 -0
  88. package/dist/dash/dash.d.ts.map +1 -1
  89. package/dist/dash/dash.js +10 -0
  90. package/dist/dash/dash.js.map +1 -1
  91. package/dist/dash/entitlement.d.ts +1 -1
  92. package/dist/dash/entitlement.d.ts.map +1 -1
  93. package/dist/dash/entitlement.js +1 -0
  94. package/dist/dash/entitlement.js.map +1 -1
  95. package/dist/dash/input.d.ts +1 -1
  96. package/dist/dash/input.d.ts.map +1 -1
  97. package/dist/dash/input.js +3 -0
  98. package/dist/dash/input.js.map +1 -1
  99. package/dist/dash/render.js +1 -1
  100. package/dist/dash/render.js.map +1 -1
  101. package/dist/dash/screens/free.js +1 -1
  102. package/dist/dash/screens/free.js.map +1 -1
  103. package/dist/device-id.d.ts +5 -0
  104. package/dist/device-id.d.ts.map +1 -0
  105. package/dist/device-id.js +78 -0
  106. package/dist/device-id.js.map +1 -0
  107. package/dist/fs-atomic.d.ts +15 -3
  108. package/dist/fs-atomic.d.ts.map +1 -1
  109. package/dist/fs-atomic.js +31 -14
  110. package/dist/fs-atomic.js.map +1 -1
  111. package/dist/gate/config-gen.d.ts +26 -2
  112. package/dist/gate/config-gen.d.ts.map +1 -1
  113. package/dist/gate/config-gen.js +53 -4
  114. package/dist/gate/config-gen.js.map +1 -1
  115. package/dist/gate/otlp-receiver.d.ts.map +1 -1
  116. package/dist/gate/otlp-receiver.js +3 -1
  117. package/dist/gate/otlp-receiver.js.map +1 -1
  118. package/dist/guard-pack.d.ts +58 -0
  119. package/dist/guard-pack.d.ts.map +1 -0
  120. package/dist/guard-pack.js +753 -0
  121. package/dist/guard-pack.js.map +1 -0
  122. package/dist/ioc/exec.d.ts +19 -0
  123. package/dist/ioc/exec.d.ts.map +1 -0
  124. package/dist/ioc/exec.js +49 -0
  125. package/dist/ioc/exec.js.map +1 -0
  126. package/dist/ioc/format.d.ts +4 -0
  127. package/dist/ioc/format.d.ts.map +1 -0
  128. package/dist/ioc/format.js +23 -0
  129. package/dist/ioc/format.js.map +1 -0
  130. package/dist/ioc/list.d.ts +29 -0
  131. package/dist/ioc/list.d.ts.map +1 -0
  132. package/dist/ioc/list.js +25 -0
  133. package/dist/ioc/list.js.map +1 -0
  134. package/dist/ioc/result.d.ts +18 -0
  135. package/dist/ioc/result.d.ts.map +1 -0
  136. package/dist/ioc/result.js +26 -0
  137. package/dist/ioc/result.js.map +1 -0
  138. package/dist/ioc/scan.d.ts +21 -0
  139. package/dist/ioc/scan.d.ts.map +1 -0
  140. package/dist/ioc/scan.js +247 -0
  141. package/dist/ioc/scan.js.map +1 -0
  142. package/dist/ioc/spawn.d.ts +26 -0
  143. package/dist/ioc/spawn.d.ts.map +1 -0
  144. package/dist/ioc/spawn.js +52 -0
  145. package/dist/ioc/spawn.js.map +1 -0
  146. package/dist/login/credentials.d.ts +17 -0
  147. package/dist/login/credentials.d.ts.map +1 -0
  148. package/dist/login/credentials.js +64 -0
  149. package/dist/login/credentials.js.map +1 -0
  150. package/dist/login/device-flow.d.ts +44 -0
  151. package/dist/login/device-flow.d.ts.map +1 -0
  152. package/dist/login/device-flow.js +205 -0
  153. package/dist/login/device-flow.js.map +1 -0
  154. package/dist/map/build.d.ts +58 -0
  155. package/dist/map/build.d.ts.map +1 -0
  156. package/dist/map/build.js +439 -0
  157. package/dist/map/build.js.map +1 -0
  158. package/dist/map/heat.d.ts +44 -0
  159. package/dist/map/heat.d.ts.map +1 -0
  160. package/dist/map/heat.js +96 -0
  161. package/dist/map/heat.js.map +1 -0
  162. package/dist/map/load.d.ts +38 -0
  163. package/dist/map/load.d.ts.map +1 -0
  164. package/dist/map/load.js +38 -0
  165. package/dist/map/load.js.map +1 -0
  166. package/dist/map/page.d.ts +90 -0
  167. package/dist/map/page.d.ts.map +1 -0
  168. package/dist/map/page.js +374 -0
  169. package/dist/map/page.js.map +1 -0
  170. package/dist/map/target-class.d.ts +22 -0
  171. package/dist/map/target-class.d.ts.map +1 -0
  172. package/dist/map/target-class.js +151 -0
  173. package/dist/map/target-class.js.map +1 -0
  174. package/dist/map/types.d.ts +102 -0
  175. package/dist/map/types.d.ts.map +1 -0
  176. package/dist/map/types.js +3 -0
  177. package/dist/map/types.js.map +1 -0
  178. package/dist/mcp/protocol.d.ts.map +1 -1
  179. package/dist/mcp/protocol.js +16 -5
  180. package/dist/mcp/protocol.js.map +1 -1
  181. package/dist/never-code.d.ts +7 -0
  182. package/dist/never-code.d.ts.map +1 -0
  183. package/dist/never-code.js +9 -0
  184. package/dist/never-code.js.map +1 -0
  185. package/dist/never-list-input.d.ts +92 -0
  186. package/dist/never-list-input.d.ts.map +1 -0
  187. package/dist/never-list-input.js +1889 -0
  188. package/dist/never-list-input.js.map +1 -0
  189. package/dist/notify/dispatch.d.ts +4 -0
  190. package/dist/notify/dispatch.d.ts.map +1 -1
  191. package/dist/notify/dispatch.js +3 -1
  192. package/dist/notify/dispatch.js.map +1 -1
  193. package/dist/offline-evaluate.d.ts +42 -5
  194. package/dist/offline-evaluate.d.ts.map +1 -1
  195. package/dist/offline-evaluate.js +96 -9
  196. package/dist/offline-evaluate.js.map +1 -1
  197. package/dist/otel/exporter.d.ts +15 -2
  198. package/dist/otel/exporter.d.ts.map +1 -1
  199. package/dist/otel/exporter.js +52 -6
  200. package/dist/otel/exporter.js.map +1 -1
  201. package/dist/otel/map-attrs-optin.d.ts +39 -0
  202. package/dist/otel/map-attrs-optin.d.ts.map +1 -0
  203. package/dist/otel/map-attrs-optin.js +109 -0
  204. package/dist/otel/map-attrs-optin.js.map +1 -0
  205. package/dist/otel.d.ts +8 -2
  206. package/dist/otel.d.ts.map +1 -1
  207. package/dist/otel.js +32 -16
  208. package/dist/otel.js.map +1 -1
  209. package/dist/parse-args.d.ts +2 -0
  210. package/dist/parse-args.d.ts.map +1 -1
  211. package/dist/parse-args.js +4 -2
  212. package/dist/parse-args.js.map +1 -1
  213. package/dist/posture/card.d.ts +26 -0
  214. package/dist/posture/card.d.ts.map +1 -0
  215. package/dist/posture/card.js +46 -0
  216. package/dist/posture/card.js.map +1 -0
  217. package/dist/posture/collect.d.ts +19 -0
  218. package/dist/posture/collect.d.ts.map +1 -0
  219. package/dist/posture/collect.js +182 -0
  220. package/dist/posture/collect.js.map +1 -0
  221. package/dist/posture/send.d.ts +24 -0
  222. package/dist/posture/send.d.ts.map +1 -0
  223. package/dist/posture/send.js +162 -0
  224. package/dist/posture/send.js.map +1 -0
  225. package/dist/posture/spawn.d.ts +20 -0
  226. package/dist/posture/spawn.d.ts.map +1 -0
  227. package/dist/posture/spawn.js +33 -0
  228. package/dist/posture/spawn.js.map +1 -0
  229. package/dist/posture/store.d.ts +34 -0
  230. package/dist/posture/store.d.ts.map +1 -0
  231. package/dist/posture/store.js +107 -0
  232. package/dist/posture/store.js.map +1 -0
  233. package/dist/posture/validate.d.ts +33 -0
  234. package/dist/posture/validate.d.ts.map +1 -0
  235. package/dist/posture/validate.js +232 -0
  236. package/dist/posture/validate.js.map +1 -0
  237. package/dist/project-policy.d.ts +23 -0
  238. package/dist/project-policy.d.ts.map +1 -0
  239. package/dist/project-policy.js +95 -0
  240. package/dist/project-policy.js.map +1 -0
  241. package/dist/receipt/daemon.d.ts +9 -9
  242. package/dist/receipt/daemon.d.ts.map +1 -1
  243. package/dist/receipt/daemon.js +26 -11
  244. package/dist/receipt/daemon.js.map +1 -1
  245. package/dist/receipt/embeddings.d.ts +29 -7
  246. package/dist/receipt/embeddings.d.ts.map +1 -1
  247. package/dist/receipt/embeddings.js +282 -33
  248. package/dist/receipt/embeddings.js.map +1 -1
  249. package/dist/receipt/gate-page.d.ts.map +1 -1
  250. package/dist/receipt/gate-page.js +5 -22
  251. package/dist/receipt/gate-page.js.map +1 -1
  252. package/dist/receipt/live-server.d.ts +8 -0
  253. package/dist/receipt/live-server.d.ts.map +1 -1
  254. package/dist/receipt/live-server.js +136 -4
  255. package/dist/receipt/live-server.js.map +1 -1
  256. package/dist/receipt/render-receipt.d.ts +7 -1
  257. package/dist/receipt/render-receipt.d.ts.map +1 -1
  258. package/dist/receipt/render-receipt.js +101 -13
  259. package/dist/receipt/render-receipt.js.map +1 -1
  260. package/dist/receipt/search.d.ts +5 -0
  261. package/dist/receipt/search.d.ts.map +1 -1
  262. package/dist/receipt/search.js +23 -2
  263. package/dist/receipt/search.js.map +1 -1
  264. package/dist/receipt/semantic-setup.d.ts +21 -0
  265. package/dist/receipt/semantic-setup.d.ts.map +1 -0
  266. package/dist/receipt/semantic-setup.js +111 -0
  267. package/dist/receipt/semantic-setup.js.map +1 -0
  268. package/dist/receipt/semantic-status.d.ts +7 -0
  269. package/dist/receipt/semantic-status.d.ts.map +1 -0
  270. package/dist/receipt/semantic-status.js +26 -0
  271. package/dist/receipt/semantic-status.js.map +1 -0
  272. package/dist/sdk/govern.d.ts.map +1 -1
  273. package/dist/sdk/govern.js +7 -3
  274. package/dist/sdk/govern.js.map +1 -1
  275. package/dist/telemetry.d.ts.map +1 -1
  276. package/dist/telemetry.js +3 -23
  277. package/dist/telemetry.js.map +1 -1
  278. package/dist/trail-map-fields.d.ts +10 -0
  279. package/dist/trail-map-fields.d.ts.map +1 -0
  280. package/dist/trail-map-fields.js +53 -0
  281. package/dist/trail-map-fields.js.map +1 -0
  282. package/dist/trail-summary.d.ts.map +1 -1
  283. package/dist/trail-summary.js +11 -3
  284. package/dist/trail-summary.js.map +1 -1
  285. package/dist/trail.d.ts +13 -1
  286. package/dist/trail.d.ts.map +1 -1
  287. package/dist/trail.js +38 -1
  288. package/dist/trail.js.map +1 -1
  289. package/droid/README.md +1 -1
  290. package/droid/mcp.example.json +1 -1
  291. package/gemini/settings.example.json +1 -1
  292. package/grok/README.md +1 -1
  293. package/kilo/kilo.example.json +1 -1
  294. package/kimi/mcp.example.json +1 -1
  295. package/kiro/mcp.example.json +1 -1
  296. package/manifest.json +1 -1
  297. package/mastracode/mcp.example.json +1 -1
  298. package/mcp.json +1 -1
  299. package/openai/codex.config.example.toml +1 -1
  300. package/opencode/opencode.example.json +1 -1
  301. package/package.json +8 -7
  302. package/qwen/settings.example.json +1 -1
  303. package/server.json +2 -2
  304. package/vendor/cytoscape/LICENSE +19 -0
  305. package/vendor/cytoscape/README.md +7 -0
  306. package/vendor/cytoscape/cytoscape.min.js +31 -0
package/dist/cli.js CHANGED
@@ -10,45 +10,12 @@
10
10
  import { realpathSync, writeSync } from "node:fs";
11
11
  import { fileURLToPath } from "node:url";
12
12
  import { resolveConfig } from "./config.js";
13
- import { KyaError } from "./errors.js";
13
+ import { AuthRequiredError, KyaError } from "./errors.js";
14
14
  import { flagBool, flagString, parseArgs } from "./parse-args.js";
15
- import { initFromArgs } from "./commands/init.js";
16
- import { registerAgentInputFromArgs, runRegisterAgent, } from "./commands/register-agent.js";
17
- import { evalToolInputFromArgs, formatEvalHuman, runEvalTool, } from "./commands/eval-tool.js";
18
- import { runServeMcp, serveMcpOptionsFromArgs, } from "./commands/serve-mcp.js";
19
- import { orrRunOptionsFromArgs, runOrr, } from "./commands/orr.js";
20
- import { certifyOptionsFromArgs, formatCertifySummary, runCertify, } from "./commands/certify.js";
21
- import { runDash } from "./commands/dash.js";
22
- import { formatAgentTable, formatApprovalTable, formatSessionTable, requireId, runGetAgent, runGetPassport, runKillAgent, runListAgents, runListApprovals, runListSessions, runShrinkSession, shrinkToFromArgs, } from "./commands/ops.js";
23
- import { formatWrapHuman, runWrap, verdictExitCode, wrapExitCode, wrapInputFromArgs, } from "./commands/wrap.js";
24
- import { formatReceiptHuman, openPath, receiptInputFromArgs, runReceipt, runReceiptShare, } from "./commands/receipt.js";
25
- import { formatShareLine, SHARE_HOSTED_HINT } from "./receipt/share.js";
26
- import { formatStartHuman, runStart } from "./commands/start.js";
27
- import { connectableHosts, formatConnectHuman, runConnect, } from "./commands/connect.js";
28
- import { appendTrail, defaultSessionId } from "./trail.js";
29
- import { deriveTargetPath } from "./trail-summary.js";
30
- import { decideIdFromArgs, runDecide } from "./commands/decide.js";
31
- import { formatInvokeHuman, invokeInputFromArgs, runInvoke, } from "./commands/invoke.js";
32
- import { runSandboxCommand } from "./commands/sandbox.js";
33
- import { runHook } from "./commands/hook.js";
34
- import { crashDecision, guardStream, hostedCrashHandler, planeBudgetMs } from "./commands/hosted-hook.js";
35
- import { importOptionsFromArgs, runImport } from "./commands/import-traces.js";
36
- import { investigateOptionsFromArgs, runInvestigate, } from "./commands/investigate.js";
37
- import { discoverOptionsFromArgs, runDiscover, } from "./commands/discover.js";
38
- import { runNotifyFlush } from "./commands/notify-flush.js";
39
- import { formatStopHuman, runStop } from "./commands/stop.js";
40
- import { runReceiptServe } from "./commands/receipt-serve.js";
41
- import { maybePrintStarCta } from "./star-cta.js";
42
- import { formatGateDoctorHuman, gateMcpUrl, gateSubcommand, runGateDoctor, runGateInit, runGateRun, runGateSetup, runGateStop, } from "./commands/gate.js";
43
- import { triggerHostedSyncFromEnv } from "./gate/hosted-sync.js";
44
- import { runGateServe } from "./commands/gate-serve.js";
45
15
  import { ensureSupportedNode } from "./node-upgrade.js";
46
16
  import { CLI_VERSION } from "./version.js";
47
- import { checkForUpdate, formatUpdateBanner, shouldSkipUpdateCheck, } from "./update-check.js";
48
- import { runUpdate } from "./commands/update.js";
49
- import { formatTeamInitHuman, managedSettings, runTeamInit } from "./commands/team.js";
50
- import { maybePromptConsent, maybeSpawnHookPing, runTelemetryCommand, startSessionBeacon, } from "./telemetry.js";
51
- const HELP = `Shield KYA light CLI - Know Your Agent (provider-agnostic)
17
+ import { isNeverCode } from "./never-code.js";
18
+ const helpFor = (hosts) => `Shield KYA light CLI - Know Your Agent (provider-agnostic)
52
19
 
53
20
  Usage:
54
21
  kya <command> [options]
@@ -63,9 +30,9 @@ Commands:
63
30
  (--all also stops orphaned report daemons from other
64
31
  directories or older installs)
65
32
  connect <host> Wire KYA MCP into a coding host config
66
- (${connectableHosts().join("|")}; --project for project scope, --force to overwrite)
33
+ (${hosts}; --project for project scope, --force to overwrite)
67
34
  --hooks also wires the host's PreToolUse hook (claude|grok|kimi)
68
- --hooks --hosted --plane-config <abs path> wires the hosted hook (claude, beta)
35
+ --hooks --hosted --plane-config <abs path> wires the hosted hook (claude, preview)
69
36
  --gate points the host at the local kya gateway (kya gate run)
70
37
  gate Local MCP gateway: govern + audit tool calls to any MCP server
71
38
  (init | setup | doctor | status | run | stop)
@@ -74,11 +41,16 @@ Commands:
74
41
  merges a tag-pinned marketplace + enabledPlugins into the project's
75
42
  .claude/settings.json (--dry-run shows the change, writes nothing)
76
43
  team managed-settings Print the managed-settings JSON an admin deploys
44
+ login Link this machine to the hosted console (device code). Saves a 0600
45
+ key in ~/.kya/hosted-credentials.json. --base-url <url> or KYA_BASE_URL
46
+ logout Delete the saved hosted key (the key stays active until you revoke it)
77
47
  update Self-update to the latest npm version (kya start warns when
78
48
  a newer release exists; --no-update-check / KYA_UPDATE_CHECK=0 opts out)
79
49
  register-agent POST /api/v1/kya/agents (human mint; server applies allow/break-glass/approve)
80
50
  eval-tool Policy evaluate (HTTP plane or --offline sample)
81
- wrap Evaluate + record trail. Never executes.
51
+ wrap Evaluate + record trail. With --tool-id it never executes.
52
+ wrap [--offline] -- <cmd...>: gate the command as a shell call, run it
53
+ only if allowed (exit = the command's; 1 denied, 4 Hold pending).
82
54
  Default observe: no Hold ticket (no second approve). --hold / KYA_HOLD=1 for org Hold.
83
55
  hook PreToolUse interception for native-hook hosts (claude|grok|kimi|…)
84
56
  Reads the hook payload from stdin; exit 2 blocks. --strict denies advisory.
@@ -87,12 +59,26 @@ Commands:
87
59
  timeout needs onFailure=block in the plugin hooks.json.
88
60
  Records once per tool_use_id (dual-hook safe). Never-list DENY
89
61
  blocks in both modes.
90
- --plane-config <abs path> (beta): evaluate on the hosted plane,
62
+ Guard pack (local, default on, exit 2, rule id in the message):
63
+ NEVER_SHELL rm of home/root, terraform destroy, SQL drops, force
64
+ push, delete main, docker volumes, kubectl delete ns,
65
+ cloud deletes, git hard reset plus git clean
66
+ NEVER_SECRET_READ ssh/cloud/kube/docker/railway credentials, .env
67
+ NEVER_SECRET_WRITE AWS/GitHub/Slack/Stripe keys, private key headers
68
+ NEVER_EXFIL env or credential files sent to a non-loopback host
69
+ Config in ~/.kya/config.json only: shellNeverList, secretsGuard,
70
+ exfilGuard (default true), guardAllow [{rule, path|command}].
71
+ A project .kya/config.json cannot lift a deny. docs/guard-pack.md.
72
+ --plane-config <abs path> (preview): evaluate on the hosted plane,
91
73
  fail closed; key/URL/agent come only from that 0600 file.
92
74
  --plane-shadow records hosted verdicts without blocking;
93
75
  --plane-budget-ms <n> total plane budget (default 3000, max 4000).
94
76
  receipt Activity report HTML/JSON/MD (default: last 3 days, all tools; --open)
95
77
  --q pre-fills feed search · --share publishes a redacted summary (see --share-url)
78
+ map Map of agent actions: device, agent, tool and target graph with KYA verdicts (local only)
79
+ --days <n> window (default 3) · --json · --html <file> · --session <id>
80
+ export Local trail as an agent-flow JSONL event log (classes only, no args; local only)
81
+ --format agent-flow · --days <n> (default 3) · --session <id> · --out <file>
96
82
  invoke Authorize on the plane after Allow or APPROVED. Never runs the write here.
97
83
  approve Human APPROVE an approval id (kya.approve scope)
98
84
  reject Human REJECT an approval id (kya.approve scope)
@@ -109,6 +95,12 @@ Commands:
109
95
  orr run Read-only ORR board (reporting only - not a second PEP)
110
96
  Optional: --producer harness.agentshield [--agentshield-json <file>]
111
97
  dash Terminal desk (FREE panes; actions on a TTY, --once for CI)
98
+ doctor Local checks. doctor --iocs scans this machine now for known supply-chain
99
+ indicators (advisory; read-only, offline). --json for scripts. Last result: kya dash/receipt
100
+ Background daily scan (from kya hook) is off with KYA_IOC=0 or ioc.enabled=false in ~/.kya/config.json
101
+ posture Developer posture report, OFF by default. status | on [--yes] | off |
102
+ send [--dry-run --json] | label NAME. "on" prints the exact report first.
103
+ Sent once a day only when the local toggle AND the workspace allow it.
112
104
  sandbox Opt-in Firecracker wrap (spawn|exec|kill|status). Not MCP.
113
105
  Requires KYA_SANDBOX=mock|firecracker. MCP still never execs.
114
106
  certify Agent Trust Baseline gap report from local evidence
@@ -183,14 +175,21 @@ Examples:
183
175
  Docs: https://shield-agent.com/install · docs/guides/kya-light-install.md
184
176
  Doctrine: sole PEP is Shield; DENY is hard; missing APPROVED ⇒ no irreversible side effect.
185
177
  `;
178
+ /** Help text. The host list comes from the connect registry, loaded on demand so `kya hook` stays light. */
179
+ async function helpText() {
180
+ const { connectableHosts } = await import("./commands/connect.js");
181
+ return helpFor(connectableHosts().join("|"));
182
+ }
186
183
  const defaultIo = {
187
184
  log: (m) => console.log(m),
188
185
  error: (m) => console.error(m),
189
186
  exit: (c) => process.exit(c),
190
187
  };
191
- /** Set by the hosted hook: the CLI entry exits right after the answer is flushed. */
188
+ /** The hook decides on the bytes it has after this long when the host never closes stdin. */
189
+ const HOOK_STDIN_TIMEOUT_MS = 10_000;
190
+ /** Set by the hook command: the CLI entry exits right after the answer is flushed. */
192
191
  let exitWhenDone = false;
193
- /** Whether the CLI entry will exit right after the hook answer (hosted mode). Exposed for tests. */
192
+ /** Whether the CLI entry will exit right after the hook answer. Exposed for tests. */
194
193
  export function hostedExitPending() {
195
194
  return exitWhenDone;
196
195
  }
@@ -210,7 +209,7 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
210
209
  parsed.command === "help" ||
211
210
  parsed.flags["help"] === true ||
212
211
  parsed.flags["h"] === true) {
213
- io.log(HELP);
212
+ io.log(await helpText());
214
213
  return 0;
215
214
  }
216
215
  try {
@@ -219,6 +218,10 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
219
218
  return nodeHandoff;
220
219
  switch (parsed.command) {
221
220
  case "start": {
221
+ const { formatStartHuman, runStart } = await import("./commands/start.js");
222
+ const { maybePrintStarCta } = await import("./star-cta.js");
223
+ const { checkForUpdate, formatUpdateBanner, shouldSkipUpdateCheck } = await import("./update-check.js");
224
+ const { maybePromptConsent } = await import("./telemetry.js");
222
225
  const config = resolveConfig({
223
226
  cwd,
224
227
  env,
@@ -232,6 +235,11 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
232
235
  const noGate = parsed.flags["no-gate"] === true || parsed.flags["no-gate"] === "true";
233
236
  if (!config.json)
234
237
  await maybePromptConsent(io, env);
238
+ // Before the report server starts, so the first search already sees the runtime. Never fails start.
239
+ if (!config.json) {
240
+ const { maybeOfferSemanticSearch } = await import("./receipt/semantic-setup.js");
241
+ await maybeOfferSemanticSearch(io, env).catch(() => undefined);
242
+ }
235
243
  const result = await runStart(config, { force, open: !noOpen, gate: !noGate });
236
244
  if (config.json) {
237
245
  io.log(JSON.stringify(result, null, 2));
@@ -249,10 +257,26 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
249
257
  }
250
258
  return 0;
251
259
  }
260
+ case "login": {
261
+ const { loginBrowserOpener, runLogin } = await import("./commands/login.js");
262
+ const { openPath } = await import("./commands/receipt.js");
263
+ return await runLogin({
264
+ io,
265
+ env,
266
+ baseUrl: flagString(parsed.flags, "base-url"),
267
+ open: loginBrowserOpener(env, io.isTty ?? Boolean(process.stdout.isTTY), (url) => openPath(url)),
268
+ });
269
+ }
270
+ case "logout": {
271
+ const { runLogout } = await import("./commands/login.js");
272
+ return runLogout({ io, env });
273
+ }
252
274
  case "update": {
275
+ const { runUpdate } = await import("./commands/update.js");
253
276
  return await runUpdate();
254
277
  }
255
278
  case "stop": {
279
+ const { formatStopHuman, runStop } = await import("./commands/stop.js");
256
280
  const result = await runStop(cwd, { all: flagBool(parsed.flags, "all") });
257
281
  if (parsed.flags["json"] === true || parsed.flags["json"] === "true") {
258
282
  io.log(JSON.stringify(result, null, 2));
@@ -263,6 +287,9 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
263
287
  return 0;
264
288
  }
265
289
  case "receipt-serve": {
290
+ const { receiptInputFromArgs } = await import("./commands/receipt.js");
291
+ const { runReceiptServe } = await import("./commands/receipt-serve.js");
292
+ const { startSessionBeacon } = await import("./telemetry.js");
266
293
  // Internal: detached child of `kya start` / `kya receipt --open`.
267
294
  const config = resolveConfig({
268
295
  cwd,
@@ -283,9 +310,15 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
283
310
  finally {
284
311
  await beacon.stop();
285
312
  }
313
+ // The server is closed (signal, idle, folder gone, memory ceiling). A running search warm-up or
314
+ // another handle must not keep this process alive after its state file is gone.
315
+ exitWhenDone = true;
286
316
  return 0;
287
317
  }
288
318
  case "connect": {
319
+ const { formatConnectHuman, runConnect } = await import("./commands/connect.js");
320
+ const { gateMcpUrl } = await import("./commands/gate.js");
321
+ const { maybePromptConsent } = await import("./telemetry.js");
289
322
  if (parsed.flags["plane-config"] !== undefined && !flagBool(parsed.flags, "hosted")) {
290
323
  // Without --hosted this would quietly wire the local, fail-open hook.
291
324
  io.error("--plane-config needs --hosted (kya connect claude --hooks --hosted --plane-config <absolute path>)");
@@ -293,6 +326,7 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
293
326
  }
294
327
  const host = parsed.positionals[0];
295
328
  if (!host) {
329
+ const { connectableHosts } = await import("./commands/connect.js");
296
330
  io.error(`Usage: kya connect <host> (${connectableHosts().join("|")})`);
297
331
  return 2;
298
332
  }
@@ -329,6 +363,8 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
329
363
  return 0;
330
364
  }
331
365
  case "gate": {
366
+ const { formatGateDoctorHuman, gateSubcommand, runGateDoctor, runGateInit, runGateRun, runGateSetup, runGateStop } = await import("./commands/gate.js");
367
+ const { triggerHostedSyncFromEnv } = await import("./gate/hosted-sync.js");
332
368
  const sub = gateSubcommand(parsed);
333
369
  if (sub === "init") {
334
370
  const result = runGateInit(env);
@@ -399,6 +435,8 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
399
435
  return 0;
400
436
  }
401
437
  case "gate-serve": {
438
+ const { runGateServe } = await import("./commands/gate-serve.js");
439
+ const { startSessionBeacon } = await import("./telemetry.js");
402
440
  // Internal: detached supervisor child of `kya gate run`.
403
441
  const beacon = startSessionBeacon("gateway", { env, cwd });
404
442
  try {
@@ -410,6 +448,8 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
410
448
  return 0;
411
449
  }
412
450
  case "init": {
451
+ const { initFromArgs } = await import("./commands/init.js");
452
+ const { maybePromptConsent } = await import("./telemetry.js");
413
453
  if (!flagBool(parsed.flags, "json"))
414
454
  await maybePromptConsent(io, env);
415
455
  const result = initFromArgs(parsed, cwd);
@@ -427,6 +467,7 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
427
467
  return 0;
428
468
  }
429
469
  case "team": {
470
+ const { formatTeamInitHuman, managedSettings, runTeamInit } = await import("./commands/team.js");
430
471
  const sub = parsed.positionals[0];
431
472
  if (sub === "managed-settings") {
432
473
  io.log(JSON.stringify(managedSettings(), null, 2));
@@ -444,6 +485,7 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
444
485
  return 0;
445
486
  }
446
487
  case "register-agent": {
488
+ const { registerAgentInputFromArgs, runRegisterAgent } = await import("./commands/register-agent.js");
447
489
  const config = resolveConfig({
448
490
  cwd,
449
491
  env,
@@ -463,6 +505,11 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
463
505
  return 0;
464
506
  }
465
507
  case "eval-tool": {
508
+ const { evalToolInputFromArgs, formatEvalHuman, runEvalTool } = await import("./commands/eval-tool.js");
509
+ const { verdictExitCode } = await import("./commands/wrap.js");
510
+ const { appendTrail, defaultSessionId } = await import("./trail.js");
511
+ const { deriveTargetPath } = await import("./trail-summary.js");
512
+ const { trailMapFields } = await import("./trail-map-fields.js");
466
513
  const input = evalToolInputFromArgs(parsed);
467
514
  const config = resolveConfig({
468
515
  cwd,
@@ -481,8 +528,9 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
481
528
  verdict: result.response.verdict,
482
529
  reasonCode: result.response.reasonCode ?? "",
483
530
  mode: result.offline ? "offline" : config.holdEnabled ? "hold" : "observe",
484
- neverEvent: result.response.reasonCode === "NEVER_EVENT",
531
+ neverEvent: isNeverCode(result.response.reasonCode),
485
532
  argsHash: result.argsHash,
533
+ ...trailMapFields(result.response.toolId ?? input.toolId, input.args, config.cwd, env),
486
534
  ...(targetPath ? { targetPath } : {}),
487
535
  }, env);
488
536
  if (config.json) {
@@ -494,6 +542,9 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
494
542
  return verdictExitCode(result.response.verdict);
495
543
  }
496
544
  case "receipt": {
545
+ const { formatReceiptHuman, receiptInputFromArgs, runReceipt, runReceiptShare } = await import("./commands/receipt.js");
546
+ const { SHARE_HOSTED_HINT, formatShareLine } = await import("./receipt/share.js");
547
+ const { maybePrintStarCta } = await import("./star-cta.js");
497
548
  const config = resolveConfig({
498
549
  cwd,
499
550
  env,
@@ -531,10 +582,60 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
531
582
  io.log(formatReceiptHuman(result));
532
583
  if (result.liveUrl)
533
584
  maybePrintStarCta(cwd, result.days, io.log);
585
+ // One CTA per day at most: the truncation pitch wins over the --share hint.
586
+ const { maybeCta } = await import("./cta.js");
587
+ const { trailTruncated } = await import("./trail.js");
588
+ maybeCta(trailTruncated(cwd, env) ? "receipt-truncated" : "receipt-share", { env, cwd, isTty: io.isTty ?? Boolean(process.stdout.isTTY) }, io.log);
534
589
  }
535
590
  return 0;
536
591
  }
592
+ case "map": {
593
+ const { mapOptionsFromArgs, runMap } = await import("./commands/map.js");
594
+ return await runMap(mapOptionsFromArgs(parsed), io, cwd, env);
595
+ }
596
+ case "export": {
597
+ const { exportOptionsFromArgs, runExport } = await import("./commands/export.js");
598
+ return await runExport(exportOptionsFromArgs(parsed), io, cwd, env);
599
+ }
537
600
  case "wrap": {
601
+ const { formatWrapHuman, runWrap, runWrapCommand, spawnWrapped, wrapCommandFromArgs, wrapCommandInputFromArgs, wrapExitCode, wrapInputFromArgs } = await import("./commands/wrap.js");
602
+ const argv = wrapCommandFromArgs(parsed);
603
+ if (argv) {
604
+ // Command form: verdict lines go to stderr so the child's stdout stays clean.
605
+ const cmdInput = wrapCommandInputFromArgs(parsed);
606
+ // No key and no --offline: gate with the local sample policy (never-list included) instead of refusing to run.
607
+ const cmdBase = { cwd, env, flags: parsed.flags };
608
+ const cmdConfig = (() => {
609
+ if (cmdInput.offline)
610
+ return resolveConfig({ ...cmdBase, requireApiKey: false, offline: true });
611
+ try {
612
+ return resolveConfig({ ...cmdBase, requireApiKey: true });
613
+ }
614
+ catch (e) {
615
+ if (!(e instanceof AuthRequiredError))
616
+ throw e;
617
+ return resolveConfig({ ...cmdBase, requireApiKey: false, offline: true });
618
+ }
619
+ })();
620
+ const { exitCode } = await runWrapCommand(cmdConfig, argv, cmdInput, {
621
+ run: (a) => spawnWrapped(a, io.error),
622
+ onEvaluated: async (w) => {
623
+ io.error(cmdConfig.json ? JSON.stringify(w, null, 2) : formatWrapHuman(w));
624
+ try {
625
+ const { autoOpenReceiptAfterWrap } = await import("./receipt/auto-open.js");
626
+ const force = w.eval.response.verdict.toUpperCase() === "DENY" ||
627
+ w.eval.response.reasonCode === "NEVER_EVENT";
628
+ const auto = await autoOpenReceiptAfterWrap(cmdConfig, { force, env });
629
+ if (!cmdConfig.json && auto.hint)
630
+ io.error(auto.hint);
631
+ }
632
+ catch {
633
+ /* receipt open is best-effort */
634
+ }
635
+ },
636
+ });
637
+ return exitCode;
638
+ }
538
639
  const input = wrapInputFromArgs(parsed);
539
640
  const config = resolveConfig({
540
641
  cwd,
@@ -554,7 +655,7 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
554
655
  try {
555
656
  const { autoOpenReceiptAfterWrap } = await import("./receipt/auto-open.js");
556
657
  const force = result.eval.response.verdict.toUpperCase() === "DENY" ||
557
- result.eval.response.reasonCode === "NEVER_EVENT";
658
+ isNeverCode(result.eval.response.reasonCode);
558
659
  const auto = await autoOpenReceiptAfterWrap(config, { force, env });
559
660
  if (!config.json && auto.hint)
560
661
  io.log(auto.hint);
@@ -565,6 +666,8 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
565
666
  return wrapExitCode(result);
566
667
  }
567
668
  case "invoke": {
669
+ const { verdictExitCode } = await import("./commands/wrap.js");
670
+ const { formatInvokeHuman, invokeInputFromArgs, runInvoke } = await import("./commands/invoke.js");
568
671
  const input = invokeInputFromArgs(parsed);
569
672
  const config = resolveConfig({
570
673
  cwd,
@@ -583,6 +686,7 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
583
686
  }
584
687
  case "approve":
585
688
  case "reject": {
689
+ const { decideIdFromArgs, runDecide } = await import("./commands/decide.js");
586
690
  const id = decideIdFromArgs(parsed, parsed.command);
587
691
  const config = resolveConfig({
588
692
  cwd,
@@ -603,6 +707,8 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
603
707
  return 0;
604
708
  }
605
709
  case "serve-mcp": {
710
+ const { runServeMcp, serveMcpOptionsFromArgs } = await import("./commands/serve-mcp.js");
711
+ const { startSessionBeacon } = await import("./telemetry.js");
606
712
  const config = resolveConfig({
607
713
  cwd,
608
714
  env,
@@ -642,6 +748,7 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
642
748
  return 0;
643
749
  }
644
750
  case "orr": {
751
+ const { orrRunOptionsFromArgs, runOrr } = await import("./commands/orr.js");
645
752
  const sub = parsed.positionals[0];
646
753
  if (sub !== "run") {
647
754
  io.error('Usage: kya orr run --path <dir> [options]');
@@ -661,6 +768,8 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
661
768
  return result.exitCode;
662
769
  }
663
770
  case "certify": {
771
+ const { certifyOptionsFromArgs, formatCertifySummary, runCertify } = await import("./commands/certify.js");
772
+ const { openPath } = await import("./commands/receipt.js");
664
773
  // Local-first, evidence-only: no resolveConfig, no API key, no network.
665
774
  const opts = certifyOptionsFromArgs(parsed);
666
775
  const result = runCertify({ ...opts, cwd, env });
@@ -675,6 +784,11 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
675
784
  io.log(` wrote ${p}`);
676
785
  }
677
786
  io.log("(evidence only - sole PEP remains Shield KYA; certify never allows or blocks)");
787
+ if (result.report.overall.result === "gap") {
788
+ // One-line CTA, silent in CI, non-TTY, quiet, opt-out and within 24 h of the last one. Never changes exit code.
789
+ const { maybeCta } = await import("./cta.js");
790
+ maybeCta("certify-gaps", { env, cwd, isTty: io.isTty ?? Boolean(process.stdout.isTTY), json: jsonOut, quiet: opts.quiet }, io.log);
791
+ }
678
792
  }
679
793
  // Attestation confirmation is safety-relevant (proof the attest write
680
794
  // happened): it prints even under --quiet, like the key notice;
@@ -703,6 +817,7 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
703
817
  return result.exitCode;
704
818
  }
705
819
  case "agents": {
820
+ const { formatAgentTable, runListAgents } = await import("./commands/ops.js");
706
821
  const config = resolveConfig({
707
822
  cwd,
708
823
  env,
@@ -714,6 +829,7 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
714
829
  return 0;
715
830
  }
716
831
  case "agent": {
832
+ const { formatAgentTable, requireId, runGetAgent } = await import("./commands/ops.js");
717
833
  const id = requireId(parsed, "agent");
718
834
  const config = resolveConfig({
719
835
  cwd,
@@ -726,6 +842,7 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
726
842
  return 0;
727
843
  }
728
844
  case "passport": {
845
+ const { requireId, runGetPassport } = await import("./commands/ops.js");
729
846
  const id = requireId(parsed, "passport");
730
847
  const config = resolveConfig({
731
848
  cwd,
@@ -738,6 +855,7 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
738
855
  return 0;
739
856
  }
740
857
  case "kill": {
858
+ const { requireId, runKillAgent } = await import("./commands/ops.js");
741
859
  const id = requireId(parsed, "kill");
742
860
  const config = resolveConfig({
743
861
  cwd,
@@ -753,6 +871,7 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
753
871
  return 0;
754
872
  }
755
873
  case "approvals": {
874
+ const { formatApprovalTable, runListApprovals } = await import("./commands/ops.js");
756
875
  const config = resolveConfig({
757
876
  cwd,
758
877
  env,
@@ -764,6 +883,7 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
764
883
  return 0;
765
884
  }
766
885
  case "sessions": {
886
+ const { formatSessionTable, runListSessions } = await import("./commands/ops.js");
767
887
  const config = resolveConfig({
768
888
  cwd,
769
889
  env,
@@ -775,6 +895,7 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
775
895
  return 0;
776
896
  }
777
897
  case "shrink": {
898
+ const { requireId, runShrinkSession, shrinkToFromArgs } = await import("./commands/ops.js");
778
899
  const id = requireId(parsed, "shrink");
779
900
  const to = shrinkToFromArgs(parsed);
780
901
  const config = resolveConfig({
@@ -791,6 +912,7 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
791
912
  return 0;
792
913
  }
793
914
  case "dash": {
915
+ const { runDash } = await import("./commands/dash.js");
794
916
  const config = resolveConfig({
795
917
  cwd,
796
918
  env,
@@ -802,9 +924,11 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
802
924
  return await runDash(config, parsed, io, env);
803
925
  }
804
926
  case "sandbox": {
927
+ const { runSandboxCommand } = await import("./commands/sandbox.js");
805
928
  return await runSandboxCommand(parsed);
806
929
  }
807
930
  case "telemetry": {
931
+ const { runTelemetryCommand } = await import("./telemetry.js");
808
932
  return runTelemetryCommand({
809
933
  sub: parsed.positionals[0],
810
934
  purge: flagBool(parsed.flags, "purge"),
@@ -812,6 +936,9 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
812
936
  }, io, env, cwd);
813
937
  }
814
938
  case "hook": {
939
+ const { runHook } = await import("./commands/hook.js");
940
+ const { crashDecision, guardStream, hostedCrashHandler, planeBudgetMs } = await import("./commands/hosted-hook.js");
941
+ const { maybeSpawnHookPing } = await import("./telemetry.js");
815
942
  const host = flagString(parsed.flags, "host") ?? "other";
816
943
  const strict = flagBool(parsed.flags, "strict");
817
944
  // Strict boolean: parseArgs would otherwise swallow a following word
@@ -819,7 +946,7 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
819
946
  // Present means on, except an explicit false/0.
820
947
  const fcFlag = parsed.flags["fail-closed"];
821
948
  const failClosed = fcFlag !== undefined && fcFlag !== false && !/^(false|0)$/i.test(String(fcFlag));
822
- // Hosted plane (beta): argv only. Missing or empty path still means hosted (it denies PLANE_CONFIG).
949
+ // Hosted plane (preview): argv only. Missing or empty path still means hosted (it denies PLANE_CONFIG).
823
950
  const planeFlag = parsed.flags["plane-config"];
824
951
  const plane = planeFlag === undefined
825
952
  ? undefined
@@ -842,16 +969,29 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
842
969
  process.on("uncaughtException", crash);
843
970
  process.on("unhandledRejection", crash);
844
971
  }
972
+ // A host that closed its end of stdout must not turn the answer into a crash (EPIPE).
973
+ if (isCliEntry()) {
974
+ process.stdout.on("error", () => undefined);
975
+ process.stderr.on("error", () => undefined);
976
+ }
845
977
  let stdinText = "";
846
978
  if (!process.stdin.isTTY) {
847
979
  stdinText = await new Promise((res) => {
848
980
  // Buffer chunks, not string concat - a multibyte UTF-8 char can
849
981
  // straddle a chunk boundary.
850
982
  const chunks = [];
983
+ const done = () => {
984
+ clearTimeout(timer);
985
+ res(Buffer.concat(chunks).toString("utf8"));
986
+ };
987
+ // A host that never closes stdin would hold the hook for days: decide on what arrived.
988
+ // Fixed on purpose: an env value (a repo can set it in committed settings) must not shorten it
989
+ // and so turn a slow payload into an empty one that the OSS hook allows.
990
+ const timer = setTimeout(done, HOOK_STDIN_TIMEOUT_MS);
851
991
  process.stdin.on("data", (c) => chunks.push(c));
852
992
  // Pipe errors must not hang the hook - resolve with what arrived.
853
- process.stdin.on("error", () => res(Buffer.concat(chunks).toString("utf8")));
854
- process.stdin.on("end", () => res(Buffer.concat(chunks).toString("utf8")));
993
+ process.stdin.on("error", done);
994
+ process.stdin.on("end", done);
855
995
  });
856
996
  }
857
997
  let r;
@@ -871,36 +1011,80 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
871
1011
  }
872
1012
  return d.code;
873
1013
  }
874
- // Await the write callbacks: pipes can be async, and hosted mode exits explicitly below.
1014
+ // Await the write callbacks: pipes can be async, and the exit below must not cut them off.
875
1015
  if (r.stdout)
876
1016
  await new Promise((ok) => process.stdout.write(r.stdout, () => ok()));
877
1017
  if (r.stderr)
878
1018
  await new Promise((ok) => process.stderr.write(r.stderr, () => ok()));
879
- // Hosted: no lingering socket may hold the hook past the host timeout.
880
- if (plane)
881
- exitWhenDone = true;
1019
+ // Exit with the same code once the answer is out and the spawns below are issued. Detached
1020
+ // children (ping, IoC, posture, notify) are unref'd and keep running; no other handle may hold a hook.
1021
+ exitWhenDone = true;
882
1022
  // After the answer is out. Opt-in, at most daily, detached: the hook never waits on the network.
883
1023
  // Skipped when another hook copy already handled this tool_use_id.
884
1024
  if (r.firstSeen !== false)
885
1025
  maybeSpawnHookPing({ env, cwd, host });
1026
+ // Daily IoC scan: detached, at most once per 24 h, never awaited. Fail open.
1027
+ const { maybeSpawnIocScan } = await import("./ioc/spawn.js");
1028
+ maybeSpawnIocScan({ env });
1029
+ // Daily posture send: only when the local toggle is on. Detached, at most once per 24 h, never awaited.
1030
+ const { maybeSpawnPostureSend } = await import("./posture/spawn.js");
1031
+ maybeSpawnPostureSend({ env });
886
1032
  return r.exitCode;
887
1033
  }
1034
+ case "posture": {
1035
+ const { runPosture } = await import("./commands/posture.js");
1036
+ const [sub, ...rest] = parsed.positionals;
1037
+ return await runPosture({
1038
+ io: { log: (line) => io.log(line), error: (line) => io.error(line) },
1039
+ env,
1040
+ sub: parsed.command === "posture" ? sub : undefined,
1041
+ args: rest,
1042
+ flags: parsed.flags,
1043
+ isTty: io.isTty ?? Boolean(process.stdin.isTTY && process.stdout.isTTY),
1044
+ });
1045
+ }
1046
+ case "posture-send": {
1047
+ // Internal: detached daily posture send started by the hook (posture/spawn.ts). Not in help.
1048
+ const { runPostureSendDetached } = await import("./commands/posture.js");
1049
+ await runPostureSendDetached(env);
1050
+ return 0;
1051
+ }
1052
+ case "doctor": {
1053
+ const { runDoctor } = await import("./commands/doctor.js");
1054
+ return await runDoctor((line) => io.log(line), {
1055
+ env,
1056
+ iocs: flagBool(parsed.flags, "iocs"),
1057
+ json: flagBool(parsed.flags, "json"),
1058
+ cwd,
1059
+ warn: (line) => io.error(line),
1060
+ });
1061
+ }
1062
+ case "ioc-scan": {
1063
+ // Internal: detached daily IoC scan started by kya hook (ioc/spawn.ts). Not in help.
1064
+ const { runDetachedIocScan } = await import("./ioc/scan.js");
1065
+ await runDetachedIocScan(env);
1066
+ return 0;
1067
+ }
888
1068
  case "import": {
1069
+ const { importOptionsFromArgs, runImport } = await import("./commands/import-traces.js");
889
1070
  return await runImport(importOptionsFromArgs(parsed), io, cwd, env);
890
1071
  }
891
1072
  case "investigate": {
1073
+ const { investigateOptionsFromArgs, runInvestigate } = await import("./commands/investigate.js");
892
1074
  return await runInvestigate(investigateOptionsFromArgs(parsed), io, cwd, env);
893
1075
  }
894
1076
  case "discover": {
1077
+ const { discoverOptionsFromArgs, runDiscover } = await import("./commands/discover.js");
895
1078
  return await runDiscover(discoverOptionsFromArgs(parsed), io, cwd, env);
896
1079
  }
897
1080
  case "notify-flush": {
1081
+ const { runNotifyFlush } = await import("./commands/notify-flush.js");
898
1082
  // Internal: spawned detached by hook/wrap (src/notify/dispatch.ts).
899
1083
  return await runNotifyFlush(env, cwd);
900
1084
  }
901
1085
  default:
902
1086
  io.error(`Unknown command: ${parsed.command}`);
903
- io.log(HELP);
1087
+ io.log(await helpText());
904
1088
  return 2;
905
1089
  }
906
1090
  }