@shield-agent/kya 0.33.6 → 0.35.3

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 (257) hide show
  1. package/.cursor-plugin/plugin.json +2 -2
  2. package/.mcp.json +1 -1
  3. package/LIMITATIONS.md +2 -1
  4. package/README.md +39 -640
  5. package/amp/settings.example.json +1 -1
  6. package/assets/report-blocked.png +0 -0
  7. package/assets/report-certify.png +0 -0
  8. package/assets/report-overview.png +0 -0
  9. package/assets/screenshot-features.json +18 -0
  10. package/claude/claude_desktop_config.example.json +1 -1
  11. package/cline/cline_mcp_settings.example.json +1 -1
  12. package/copilot/mcp-config.example.json +1 -1
  13. package/dist/alerts/config.d.ts +22 -0
  14. package/dist/alerts/config.d.ts.map +1 -0
  15. package/dist/alerts/config.js +59 -0
  16. package/dist/alerts/config.js.map +1 -0
  17. package/dist/alerts/constants.d.ts +5 -0
  18. package/dist/alerts/constants.d.ts.map +1 -0
  19. package/dist/alerts/constants.js +5 -0
  20. package/dist/alerts/constants.js.map +1 -0
  21. package/dist/alerts/desktop.d.ts +19 -0
  22. package/dist/alerts/desktop.d.ts.map +1 -0
  23. package/dist/alerts/desktop.js +57 -0
  24. package/dist/alerts/desktop.js.map +1 -0
  25. package/dist/alerts/incident-alert.d.ts +42 -0
  26. package/dist/alerts/incident-alert.d.ts.map +1 -0
  27. package/dist/alerts/incident-alert.js +84 -0
  28. package/dist/alerts/incident-alert.js.map +1 -0
  29. package/dist/alerts/scan.d.ts +37 -0
  30. package/dist/alerts/scan.d.ts.map +1 -0
  31. package/dist/alerts/scan.js +188 -0
  32. package/dist/alerts/scan.js.map +1 -0
  33. package/dist/alerts/state.d.ts +55 -0
  34. package/dist/alerts/state.d.ts.map +1 -0
  35. package/dist/alerts/state.js +178 -0
  36. package/dist/alerts/state.js.map +1 -0
  37. package/dist/alerts/triage-input.d.ts +36 -0
  38. package/dist/alerts/triage-input.d.ts.map +1 -0
  39. package/dist/alerts/triage-input.js +159 -0
  40. package/dist/alerts/triage-input.js.map +1 -0
  41. package/dist/alerts/triage-model.d.ts +42 -0
  42. package/dist/alerts/triage-model.d.ts.map +1 -0
  43. package/dist/alerts/triage-model.js +126 -0
  44. package/dist/alerts/triage-model.js.map +1 -0
  45. package/dist/alerts/triage-prompt.d.ts +23 -0
  46. package/dist/alerts/triage-prompt.d.ts.map +1 -0
  47. package/dist/alerts/triage-prompt.js +53 -0
  48. package/dist/alerts/triage-prompt.js.map +1 -0
  49. package/dist/alerts/triage-schema.json +52 -0
  50. package/dist/alerts/triage-setup.d.ts +77 -0
  51. package/dist/alerts/triage-setup.d.ts.map +1 -0
  52. package/dist/alerts/triage-setup.js +290 -0
  53. package/dist/alerts/triage-setup.js.map +1 -0
  54. package/dist/alerts/triage-status.d.ts +26 -0
  55. package/dist/alerts/triage-status.d.ts.map +1 -0
  56. package/dist/alerts/triage-status.js +61 -0
  57. package/dist/alerts/triage-status.js.map +1 -0
  58. package/dist/alerts/triage-tokenizer.d.ts +29 -0
  59. package/dist/alerts/triage-tokenizer.d.ts.map +1 -0
  60. package/dist/alerts/triage-tokenizer.js +82 -0
  61. package/dist/alerts/triage-tokenizer.js.map +1 -0
  62. package/dist/card-png.d.ts +3 -0
  63. package/dist/card-png.d.ts.map +1 -0
  64. package/dist/card-png.js +54 -0
  65. package/dist/card-png.js.map +1 -0
  66. package/dist/card.d.ts +62 -0
  67. package/dist/card.d.ts.map +1 -0
  68. package/dist/card.js +154 -0
  69. package/dist/card.js.map +1 -0
  70. package/dist/certify/evaluate.d.ts +11 -1
  71. package/dist/certify/evaluate.d.ts.map +1 -1
  72. package/dist/certify/evaluate.js +8 -1
  73. package/dist/certify/evaluate.js.map +1 -1
  74. package/dist/certify/render.d.ts.map +1 -1
  75. package/dist/certify/render.js +10 -1
  76. package/dist/certify/render.js.map +1 -1
  77. package/dist/cli.d.ts +2 -0
  78. package/dist/cli.d.ts.map +1 -1
  79. package/dist/cli.js +234 -49
  80. package/dist/cli.js.map +1 -1
  81. package/dist/commands/alerts.d.ts +16 -0
  82. package/dist/commands/alerts.d.ts.map +1 -0
  83. package/dist/commands/alerts.js +112 -0
  84. package/dist/commands/alerts.js.map +1 -0
  85. package/dist/commands/connect.d.ts.map +1 -1
  86. package/dist/commands/connect.js +4 -3
  87. package/dist/commands/connect.js.map +1 -1
  88. package/dist/commands/doctor.d.ts.map +1 -1
  89. package/dist/commands/doctor.js +12 -1
  90. package/dist/commands/doctor.js.map +1 -1
  91. package/dist/commands/hook.d.ts.map +1 -1
  92. package/dist/commands/hook.js +94 -8
  93. package/dist/commands/hook.js.map +1 -1
  94. package/dist/commands/init.d.ts +2 -0
  95. package/dist/commands/init.d.ts.map +1 -1
  96. package/dist/commands/init.js +2 -0
  97. package/dist/commands/init.js.map +1 -1
  98. package/dist/commands/notify-flush.d.ts.map +1 -1
  99. package/dist/commands/notify-flush.js +3 -0
  100. package/dist/commands/notify-flush.js.map +1 -1
  101. package/dist/commands/receipt-serve.d.ts +1 -1
  102. package/dist/commands/receipt-serve.d.ts.map +1 -1
  103. package/dist/commands/receipt-serve.js +3 -2
  104. package/dist/commands/receipt-serve.js.map +1 -1
  105. package/dist/commands/receipt.d.ts +19 -0
  106. package/dist/commands/receipt.d.ts.map +1 -1
  107. package/dist/commands/receipt.js +21 -1
  108. package/dist/commands/receipt.js.map +1 -1
  109. package/dist/commands/serve-mcp.d.ts +2 -0
  110. package/dist/commands/serve-mcp.d.ts.map +1 -1
  111. package/dist/commands/serve-mcp.js +2 -0
  112. package/dist/commands/serve-mcp.js.map +1 -1
  113. package/dist/commands/start.d.ts +50 -2
  114. package/dist/commands/start.d.ts.map +1 -1
  115. package/dist/commands/start.js +233 -27
  116. package/dist/commands/start.js.map +1 -1
  117. package/dist/commands/team.d.ts +19 -2
  118. package/dist/commands/team.d.ts.map +1 -1
  119. package/dist/commands/team.js +93 -13
  120. package/dist/commands/team.js.map +1 -1
  121. package/dist/commands/uninstall.d.ts +20 -0
  122. package/dist/commands/uninstall.d.ts.map +1 -0
  123. package/dist/commands/uninstall.js +378 -0
  124. package/dist/commands/uninstall.js.map +1 -0
  125. package/dist/commands/wire-hooks.d.ts +16 -2
  126. package/dist/commands/wire-hooks.d.ts.map +1 -1
  127. package/dist/commands/wire-hooks.js +86 -21
  128. package/dist/commands/wire-hooks.js.map +1 -1
  129. package/dist/cta.d.ts +3 -1
  130. package/dist/cta.d.ts.map +1 -1
  131. package/dist/cta.js +11 -6
  132. package/dist/cta.js.map +1 -1
  133. package/dist/fs-atomic.d.ts.map +1 -1
  134. package/dist/fs-atomic.js +9 -2
  135. package/dist/fs-atomic.js.map +1 -1
  136. package/dist/guard-demo.d.ts +23 -0
  137. package/dist/guard-demo.d.ts.map +1 -0
  138. package/dist/guard-demo.js +34 -0
  139. package/dist/guard-demo.js.map +1 -0
  140. package/dist/guard-entry.d.ts +2 -0
  141. package/dist/guard-entry.d.ts.map +1 -0
  142. package/dist/guard-entry.js +50 -0
  143. package/dist/guard-entry.js.map +1 -0
  144. package/dist/guard-lite.d.ts +27 -0
  145. package/dist/guard-lite.d.ts.map +1 -0
  146. package/dist/guard-lite.js +274 -0
  147. package/dist/guard-lite.js.map +1 -0
  148. package/dist/guard-pack.d.ts +7 -2
  149. package/dist/guard-pack.d.ts.map +1 -1
  150. package/dist/guard-pack.js +202 -4
  151. package/dist/guard-pack.js.map +1 -1
  152. package/dist/hook-health.d.ts +7 -0
  153. package/dist/hook-health.d.ts.map +1 -0
  154. package/dist/hook-health.js +105 -0
  155. package/dist/hook-health.js.map +1 -0
  156. package/dist/investigate/detectors.d.ts +6 -0
  157. package/dist/investigate/detectors.d.ts.map +1 -1
  158. package/dist/investigate/detectors.js +10 -0
  159. package/dist/investigate/detectors.js.map +1 -1
  160. package/dist/manifest.d.ts +106 -0
  161. package/dist/manifest.d.ts.map +1 -0
  162. package/dist/manifest.js +454 -0
  163. package/dist/manifest.js.map +1 -0
  164. package/dist/mcp/http.d.ts +1 -0
  165. package/dist/mcp/http.d.ts.map +1 -1
  166. package/dist/mcp/http.js +1 -0
  167. package/dist/mcp/http.js.map +1 -1
  168. package/dist/mcp/protocol.d.ts +2 -0
  169. package/dist/mcp/protocol.d.ts.map +1 -1
  170. package/dist/mcp/protocol.js +5 -1
  171. package/dist/mcp/protocol.js.map +1 -1
  172. package/dist/mcp/stdio.d.ts +1 -0
  173. package/dist/mcp/stdio.d.ts.map +1 -1
  174. package/dist/mcp/stdio.js +1 -0
  175. package/dist/mcp/stdio.js.map +1 -1
  176. package/dist/never-list-input.d.ts +2 -0
  177. package/dist/never-list-input.d.ts.map +1 -1
  178. package/dist/never-list-input.js +30 -30
  179. package/dist/never-list-input.js.map +1 -1
  180. package/dist/notify/dispatch.d.ts +1 -1
  181. package/dist/notify/dispatch.d.ts.map +1 -1
  182. package/dist/notify/dispatch.js +4 -1
  183. package/dist/notify/dispatch.js.map +1 -1
  184. package/dist/notify/incident-templates.d.ts +23 -0
  185. package/dist/notify/incident-templates.d.ts.map +1 -0
  186. package/dist/notify/incident-templates.js +128 -0
  187. package/dist/notify/incident-templates.js.map +1 -0
  188. package/dist/notify/index.d.ts +12 -0
  189. package/dist/notify/index.d.ts.map +1 -1
  190. package/dist/notify/index.js +50 -3
  191. package/dist/notify/index.js.map +1 -1
  192. package/dist/notify/templates.d.ts +6 -0
  193. package/dist/notify/templates.d.ts.map +1 -1
  194. package/dist/notify/templates.js +12 -2
  195. package/dist/notify/templates.js.map +1 -1
  196. package/dist/offline-evaluate.d.ts.map +1 -1
  197. package/dist/offline-evaluate.js +2 -1
  198. package/dist/offline-evaluate.js.map +1 -1
  199. package/dist/parse-args.js +1 -1
  200. package/dist/parse-args.js.map +1 -1
  201. package/dist/receipt/changes.d.ts +2 -1
  202. package/dist/receipt/changes.d.ts.map +1 -1
  203. package/dist/receipt/changes.js +9 -0
  204. package/dist/receipt/changes.js.map +1 -1
  205. package/dist/receipt/dashboard.d.ts +14 -1
  206. package/dist/receipt/dashboard.d.ts.map +1 -1
  207. package/dist/receipt/dashboard.js +20 -2
  208. package/dist/receipt/dashboard.js.map +1 -1
  209. package/dist/receipt/live-server.d.ts +19 -0
  210. package/dist/receipt/live-server.d.ts.map +1 -1
  211. package/dist/receipt/live-server.js +119 -0
  212. package/dist/receipt/live-server.js.map +1 -1
  213. package/dist/receipt/receipt-css.d.ts.map +1 -1
  214. package/dist/receipt/receipt-css.js +45 -0
  215. package/dist/receipt/receipt-css.js.map +1 -1
  216. package/dist/receipt/render-receipt.d.ts +17 -0
  217. package/dist/receipt/render-receipt.d.ts.map +1 -1
  218. package/dist/receipt/render-receipt.js +479 -80
  219. package/dist/receipt/render-receipt.js.map +1 -1
  220. package/dist/receipt/search.d.ts.map +1 -1
  221. package/dist/receipt/search.js +2 -0
  222. package/dist/receipt/search.js.map +1 -1
  223. package/dist/receipt/semantic-setup.d.ts +4 -0
  224. package/dist/receipt/semantic-setup.d.ts.map +1 -1
  225. package/dist/receipt/semantic-setup.js +10 -1
  226. package/dist/receipt/semantic-setup.js.map +1 -1
  227. package/dist/receipt/share.d.ts +15 -0
  228. package/dist/receipt/share.d.ts.map +1 -1
  229. package/dist/receipt/share.js +34 -1
  230. package/dist/receipt/share.js.map +1 -1
  231. package/dist/telemetry.d.ts +17 -2
  232. package/dist/telemetry.d.ts.map +1 -1
  233. package/dist/telemetry.js +67 -7
  234. package/dist/telemetry.js.map +1 -1
  235. package/dist/trail.d.ts +45 -0
  236. package/dist/trail.d.ts.map +1 -1
  237. package/dist/trail.js +38 -0
  238. package/dist/trail.js.map +1 -1
  239. package/dist/write-journal.d.ts +39 -0
  240. package/dist/write-journal.d.ts.map +1 -0
  241. package/dist/write-journal.js +107 -0
  242. package/dist/write-journal.js.map +1 -0
  243. package/droid/README.md +1 -1
  244. package/droid/mcp.example.json +1 -1
  245. package/gemini/settings.example.json +1 -1
  246. package/grok/README.md +2 -2
  247. package/kilo/kilo.example.json +1 -1
  248. package/kimi/mcp.example.json +1 -1
  249. package/kiro/mcp.example.json +1 -1
  250. package/manifest.json +2 -2
  251. package/mastracode/mcp.example.json +1 -1
  252. package/mcp.json +1 -1
  253. package/openai/codex.config.example.toml +1 -1
  254. package/opencode/opencode.example.json +1 -1
  255. package/package.json +9 -3
  256. package/qwen/settings.example.json +1 -1
  257. package/server.json +3 -3
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { realpathSync, writeSync } from "node:fs";
11
11
  import { fileURLToPath } from "node:url";
12
- import { resolveConfig } from "./config.js";
12
+ import { globalConfigDir, resolveConfig } from "./config.js";
13
13
  import { AuthRequiredError, KyaError } from "./errors.js";
14
14
  import { flagBool, flagString, parseArgs } from "./parse-args.js";
15
15
  import { ensureSupportedNode } from "./node-upgrade.js";
@@ -21,11 +21,19 @@ Usage:
21
21
  kya <command> [options]
22
22
 
23
23
  Commands:
24
+ Guard:
24
25
  version Print the package version (same as --version)
25
- start ONE LINER: init + wire local MCP + open live report
26
- (report runs in the background - returns your terminal)
27
- Auto-bootstraps the gateway for third-party MCP servers
28
- found in host configs (--no-gate or KYA_GATE=off skips)
26
+ start ONE LINER: wire local MCP + open live report. Prints a plan first
27
+ (each file, created or merged, exact keys) and asks "Apply? [Y/n]".
28
+ --yes or KYA_YES=1 skips the question; off a terminal or in CI it
29
+ applies only with --yes. --dry-run prints the plan, writes nothing.
30
+ Wires only hosts found on this machine. Report runs in the
31
+ background - returns your terminal. Auto-bootstraps the gateway for
32
+ third-party MCP servers found in host configs (--no-gate or
33
+ KYA_GATE=off skips)
34
+ uninstall Undo kya start / kya connect from ~/.kya/manifest.json: removes only
35
+ the keys KYA added, deletes files KYA created if unchanged, prints
36
+ what it left alone (--project: this directory only, --dry-run)
29
37
  stop Stop the background report server started by kya start
30
38
  (--all also stops orphaned report daemons from other
31
39
  directories or older installs)
@@ -37,21 +45,8 @@ Commands:
37
45
  gate Local MCP gateway: govern + audit tool calls to any MCP server
38
46
  (init | setup | doctor | status | run | stop)
39
47
  init Scaffold .kya/ config + sample tools + .env.example
40
- team init Enable the Claude Code plugin for everyone who opens this repo:
41
- merges a tag-pinned marketplace + enabledPlugins into the project's
42
- .claude/settings.json (--dry-run shows the change, writes nothing)
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)
47
48
  update Self-update to the latest npm version (kya start warns when
48
49
  a newer release exists; --no-update-check / KYA_UPDATE_CHECK=0 opts out)
49
- register-agent POST /api/v1/kya/agents (human mint; server applies allow/break-glass/approve)
50
- eval-tool Policy evaluate (HTTP plane or --offline sample)
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).
54
- Default observe: no Hold ticket (no second approve). --hold / KYA_HOLD=1 for org Hold.
55
50
  hook PreToolUse interception for native-hook hosts (claude|grok|kimi|…)
56
51
  Reads the hook payload from stdin; exit 2 blocks. --strict denies advisory.
57
52
  --fail-closed (hosted): internal error, malformed payload or async
@@ -73,12 +68,49 @@ Commands:
73
68
  fail closed; key/URL/agent come only from that 0600 file.
74
69
  --plane-shadow records hosted verdicts without blocking;
75
70
  --plane-budget-ms <n> total plane budget (default 3000, max 4000).
71
+ wrap Evaluate + record trail. With --tool-id it never executes.
72
+ wrap [--offline] -- <cmd...>: gate the command as a shell call, run it
73
+ only if allowed (exit = the command's; 1 denied, 4 Hold pending).
74
+ Default observe: no Hold ticket (no second approve). --hold / KYA_HOLD=1 for org Hold.
75
+ doctor Local checks. doctor --iocs scans this machine now for known supply-chain
76
+ indicators (advisory; read-only, offline). --json for scripts. Last result: kya dash/receipt
77
+ Background daily scan (from kya hook) is off with KYA_IOC=0 or ioc.enabled=false in ~/.kya/config.json
78
+ serve-mcp Local MCP gate (HTTP default; --stdio for hosts)
79
+ sandbox Opt-in Firecracker wrap (spawn|exec|kill|status). Not MCP.
80
+ Requires KYA_SANDBOX=mock|firecracker. MCP still never execs.
81
+ telemetry Opt-in anonymous usage stats, OFF by default
82
+ (status | on | off [--purge] | show | reset) - see docs/telemetry.md
83
+ posture Developer posture report, OFF by default. status | on [--yes] | off |
84
+ send [--dry-run --json] | label NAME. "on" prints the exact report first.
85
+ Sent once a day only when the local toggle AND the workspace allow it.
86
+
87
+ Report:
76
88
  receipt Activity report HTML/JSON/MD (default: last 3 days, all tools; --open)
77
89
  --q pre-fills feed search · --share publishes a redacted summary (see --share-url)
78
90
  map Map of agent actions: device, agent, tool and target graph with KYA verdicts (local only)
79
91
  --days <n> window (default 3) · --json · --html <file> · --session <id>
80
92
  export Local trail as an agent-flow JSONL event log (classes only, no args; local only)
81
93
  --format agent-flow · --days <n> (default 3) · --session <id> · --out <file>
94
+ dash Terminal desk (FREE panes; actions on a TTY, --once for CI)
95
+ investigate Deterministic trail analysis: detectors, grouped incidents,
96
+ markdown fix briefs for coding agents (--json)
97
+ alerts Security alerts for detected incidents, optional local triage model, off by default, advisory
98
+ (status [--json] | test | on | off) - see docs/alerts.md
99
+ discover Scan this endpoint for AI agents, IDEs, runtimes, and MCP servers
100
+ (--json, --diff <snapshot>, --push)
101
+
102
+ Team and hosted:
103
+ team init Enable the Claude Code plugin for everyone who opens this repo:
104
+ merges a tag-pinned marketplace + enabledPlugins into the project's
105
+ .claude/settings.json (--dry-run shows the change, writes nothing).
106
+ --guard enables the kya-guard plugin instead: prints the diff and a README badge,
107
+ kya uninstall --project reverses it
108
+ team managed-settings Print the managed-settings JSON an admin deploys
109
+ login Link this machine to the hosted console (device code). Saves a 0600
110
+ key in ~/.kya/hosted-credentials.json. --base-url <url> or KYA_BASE_URL
111
+ logout Delete the saved hosted key (the key stays active until you revoke it)
112
+ register-agent POST /api/v1/kya/agents (human mint; server applies allow/break-glass/approve)
113
+ eval-tool Policy evaluate (HTTP plane or --offline sample)
82
114
  invoke Authorize on the plane after Allow or APPROVED. Never runs the write here.
83
115
  approve Human APPROVE an approval id (kya.approve scope)
84
116
  reject Human REJECT an approval id (kya.approve scope)
@@ -89,32 +121,18 @@ Commands:
89
121
  approvals List the human queue
90
122
  sessions List observed sessions
91
123
  shrink Drop session clearance (--id --to BUILD|READ|DEPLOY)
92
- serve-mcp Local MCP gate (HTTP default; --stdio for hosts)
93
- telemetry Opt-in anonymous usage stats, OFF by default
94
- (status | on | off [--purge] | show | reset) - see docs/telemetry.md
95
- orr run Read-only ORR board (reporting only - not a second PEP)
96
- Optional: --producer harness.agentshield [--agentshield-json <file>]
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.
104
- sandbox Opt-in Firecracker wrap (spawn|exec|kill|status). Not MCP.
105
- Requires KYA_SANDBOX=mock|firecracker. MCP still never execs.
124
+
125
+ Compliance and evidence:
106
126
  certify Agent Trust Baseline gap report from local evidence
107
127
  (JSON/MD/HTML to .kya/certify/; exit 1 on gaps, exit 2 usage)
108
128
  --attest REQ-ID --text "…" records a local attestation
109
129
  --sign also writes an ed25519-signed evidence-bundle.json
110
130
  (self-signed: integrity + key continuity, NOT identity)
131
+ orr run Read-only ORR board (reporting only - not a second PEP)
132
+ Optional: --producer harness.agentshield [--agentshield-json <file>]
111
133
  import Import foreign traces into the local trail so the receipt
112
134
  and certify see them (--from langsmith|langfuse|phoenix|otel|adr-sensor|openshell
113
135
  <file>, or --from agt [--file <path>] for the AGT audit log)
114
- investigate Deterministic trail analysis: detectors, grouped incidents,
115
- markdown fix briefs for coding agents (--json)
116
- discover Scan this endpoint for AI agents, IDEs, runtimes, and MCP servers
117
- (--json, --diff <snapshot>, --push)
118
136
 
119
137
  Options (shared):
120
138
  --base-url <url> Control plane origin (or KYA_BASE_URL)
@@ -126,6 +144,7 @@ Options (shared):
126
144
  --days <n> receipt: history window (default 3)
127
145
  --session <id> receipt: single session only (optional)
128
146
  --q <query> receipt: pre-fill the activity-feed search
147
+ --card receipt: make a near-miss card (image, no private data) for the last denied call; --id <id> picks another
129
148
  --share receipt: publish a redacted summary, print the public URL
130
149
  --share-url <url> receipt: share endpoint base (or KYA_SHARE_URL; default https://shield-agent.com)
131
150
  --share-retries <n> receipt: retry --share on 429/5xx/network with backoff (max 3; default 0 = single attempt)
@@ -141,6 +160,9 @@ Options (shared):
141
160
 
142
161
  Examples:
143
162
  kya start
163
+ kya start --dry-run
164
+ kya start --yes
165
+ kya uninstall
144
166
  kya connect qwen
145
167
  kya connect opencode --project
146
168
  npx @shield-agent/kya init --base-url http://127.0.0.1:8090 --host ide
@@ -175,11 +197,59 @@ Examples:
175
197
  Docs: https://shield-agent.com/install · docs/guides/kya-light-install.md
176
198
  Doctrine: sole PEP is Shield; DENY is hard; missing APPROVED ⇒ no irreversible side effect.
177
199
  `;
178
- /** Help text. The host list comes from the connect registry, loaded on demand so `kya hook` stays light. */
200
+ /** Default help: the six commands a new user needs. `kya help --all` prints the full text above. */
201
+ const SHORT_HELP = `Shield KYA - stops your coding agent from deleting, leaking or dropping things.
202
+
203
+ Usage:
204
+ kya <command>
205
+
206
+ kya start Guard this machine and open the report
207
+ kya receipt Open or share the report
208
+ kya connect <host> Add another coding agent
209
+ kya doctor Check that the guard is live
210
+ kya stop Stop the report server
211
+ kya uninstall Remove everything KYA added
212
+
213
+ More commands: kya help --all
214
+ One command: kya help <command>
215
+ Docs: https://shield-agent.com/install
216
+ `;
217
+ /** Full help text. The host list comes from the connect registry, loaded on demand so `kya hook` stays light. */
179
218
  async function helpText() {
180
219
  const { connectableHosts } = await import("./commands/connect.js");
181
220
  return helpFor(connectableHosts().join("|"));
182
221
  }
222
+ /** The Commands entries whose first word is `name` (`orr` finds "orr run"), cut from the full help text. */
223
+ export function helpSection(full, name) {
224
+ const start = full.indexOf("\nCommands:\n") + "\nCommands:\n".length;
225
+ const entries = [];
226
+ for (const line of full.slice(start, full.indexOf("\nOptions (shared):")).split("\n")) {
227
+ if (/^ {2}\S/.test(line))
228
+ entries.push([line]);
229
+ else if (/^\s+\S/.test(line) && entries.length > 0)
230
+ entries[entries.length - 1].push(line);
231
+ }
232
+ const hits = entries.filter((e) => e[0].trim().split(/\s+/)[0] === name);
233
+ return hits.length > 0 ? hits.map((e) => e.join("\n")).join("\n") : undefined;
234
+ }
235
+ /** What `kya help`, `kya --help` and `kya <command> --help` print. Returns the exit code. */
236
+ async function printHelp(io, command, all) {
237
+ if (all) {
238
+ io.log(await helpText());
239
+ return 0;
240
+ }
241
+ if (command === undefined) {
242
+ io.log(SHORT_HELP);
243
+ return 0;
244
+ }
245
+ const section = helpSection(await helpText(), command);
246
+ if (section === undefined) {
247
+ io.error(`No help for "${command}". Run kya help --all to list every command.`);
248
+ return 2;
249
+ }
250
+ io.log(`${section}\n\nEvery command and option: kya help --all`);
251
+ return 0;
252
+ }
183
253
  const defaultIo = {
184
254
  log: (m) => console.log(m),
185
255
  error: (m) => console.error(m),
@@ -209,8 +279,10 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
209
279
  parsed.command === "help" ||
210
280
  parsed.flags["help"] === true ||
211
281
  parsed.flags["h"] === true) {
212
- io.log(await helpText());
213
- return 0;
282
+ const all = parsed.flags["all"] !== undefined && parsed.flags["all"] !== false;
283
+ // `kya help <command>` names it in the first positional; `kya <command> --help` is the command itself.
284
+ const target = parsed.command === "help" ? parsed.positionals[0] : parsed.command;
285
+ return printHelp(io, target, all);
214
286
  }
215
287
  try {
216
288
  const nodeHandoff = await ensureSupportedNode(io, env, argv, parsed.command);
@@ -218,7 +290,8 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
218
290
  return nodeHandoff;
219
291
  switch (parsed.command) {
220
292
  case "start": {
221
- const { formatStartHuman, runStart } = await import("./commands/start.js");
293
+ const { askApply, envYes, formatPlanHuman, formatStartHuman, planStart, runStart } = await import("./commands/start.js");
294
+ const { homedir } = await import("node:os");
222
295
  const { maybePrintStarCta } = await import("./star-cta.js");
223
296
  const { checkForUpdate, formatUpdateBanner, shouldSkipUpdateCheck } = await import("./update-check.js");
224
297
  const { maybePromptConsent } = await import("./telemetry.js");
@@ -233,21 +306,66 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
233
306
  const force = parsed.flags["force"] === true || parsed.flags["force"] === "true";
234
307
  const noOpen = parsed.flags["no-open"] === true || parsed.flags["no-open"] === "true";
235
308
  const noGate = parsed.flags["no-gate"] === true || parsed.flags["no-gate"] === "true";
309
+ const switchOn = (v) => v !== undefined && v !== false && !/^(false|0)$/i.test(String(v));
310
+ const dryRun = switchOn(parsed.flags["dry-run"]);
311
+ const yes = switchOn(parsed.flags["yes"]) || envYes(env);
312
+ const home = env.KYA_HOME?.trim() || undefined;
313
+ const plan = await planStart(config, { force, open: !noOpen, gate: !noGate, ...(home ? { home } : {}) });
314
+ const planHome = home ?? homedir();
315
+ if (!config.json)
316
+ io.log(formatPlanHuman(plan, cwd, planHome));
317
+ if (dryRun) {
318
+ if (config.json)
319
+ io.log(JSON.stringify({ applied: false, dryRun: true, plan }, null, 2));
320
+ else
321
+ io.log("Dry run: nothing was written.");
322
+ return 0;
323
+ }
324
+ if (plan.needsConsent && !yes) {
325
+ // A terminal gets one question; a pipe, CI or --json never applies without --yes / KYA_YES=1.
326
+ const interactive = (io.isTty ?? Boolean(process.stdin.isTTY && process.stdout.isTTY)) && !config.json && !env.CI;
327
+ const apply = interactive && /^(|y|yes)$/i.test((await (io.ask ?? askApply)("Apply? [Y/n]")).trim());
328
+ if (!apply) {
329
+ if (config.json)
330
+ io.log(JSON.stringify({ applied: false, dryRun: false, plan }, null, 2));
331
+ if (interactive) {
332
+ io.log("Nothing was changed.");
333
+ }
334
+ else {
335
+ // On stderr (and on stdout in text mode), so a wrapper that only shows stderr still says why nothing happened.
336
+ const line = "Nothing was written (not a terminal). Run again with --yes (or KYA_YES=1) to apply, or --dry-run to preview.";
337
+ if (!config.json)
338
+ io.log(line);
339
+ io.error(line);
340
+ }
341
+ return 0;
342
+ }
343
+ }
236
344
  if (!config.json)
237
345
  await maybePromptConsent(io, env);
238
346
  // Before the report server starts, so the first search already sees the runtime. Never fails start.
239
347
  if (!config.json) {
240
348
  const { maybeOfferSemanticSearch } = await import("./receipt/semantic-setup.js");
241
- await maybeOfferSemanticSearch(io, env).catch(() => undefined);
349
+ // The "is off" hints are not printed here: `kya doctor` shows them (quietHint).
350
+ await maybeOfferSemanticSearch({ ...io, quietHint: true }, env).catch(() => undefined);
351
+ // Security alerts with optional local triage: asked once, after the search offer, same rules. Never fails start.
352
+ const { maybeOfferTriageAlerts } = await import("./alerts/triage-setup.js");
353
+ await maybeOfferTriageAlerts({ ...io, quietHint: true }, env).catch(() => undefined);
242
354
  }
243
- const result = await runStart(config, { force, open: !noOpen, gate: !noGate });
355
+ const result = await runStart(config, { force, open: !noOpen, gate: !noGate, ...(home ? { home } : {}) });
244
356
  if (config.json) {
245
- io.log(JSON.stringify(result, null, 2));
357
+ io.log(JSON.stringify({ ...result, applied: true, plan }, null, 2));
246
358
  }
247
359
  else {
248
360
  io.log(formatStartHuman(result));
249
361
  if (result.liveUrl)
250
362
  maybePrintStarCta(cwd, 3, io.log);
363
+ // F4: once there is something worth sharing. Same once-a-day, terminal-only rules as every CTA.
364
+ const { maybeCta, START_SHARE_MIN_EVENTS } = await import("./cta.js");
365
+ const { readTrail } = await import("./trail.js");
366
+ if (readTrail(cwd, env).length >= START_SHARE_MIN_EVENTS) {
367
+ maybeCta("start-share", { env, cwd, isTty: io.isTty ?? Boolean(process.stdout.isTTY) }, io.log);
368
+ }
251
369
  // Cached 24h; silent on any failure. Skipped in CI / --json / opt-out.
252
370
  if (!shouldSkipUpdateCheck(parsed.command, parsed.flags, env)) {
253
371
  const update = await checkForUpdate({ env });
@@ -257,6 +375,13 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
257
375
  }
258
376
  return 0;
259
377
  }
378
+ case "uninstall": {
379
+ const { formatUninstallHuman, runUninstall } = await import("./commands/uninstall.js");
380
+ const sw = (v) => v !== undefined && v !== false && !/^(false|0)$/i.test(String(v));
381
+ const result = runUninstall({ env, cwd, project: sw(parsed.flags["project"]), dryRun: sw(parsed.flags["dry-run"]) });
382
+ io.log(flagBool(parsed.flags, "json") ? JSON.stringify(result, null, 2) : formatUninstallHuman(result, cwd));
383
+ return 0;
384
+ }
260
385
  case "login": {
261
386
  const { loginBrowserOpener, runLogin } = await import("./commands/login.js");
262
387
  const { openPath } = await import("./commands/receipt.js");
@@ -353,7 +478,17 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
353
478
  shadow: flagBool(parsed.flags, "plane-shadow"),
354
479
  }
355
480
  : undefined;
356
- const result = await runConnect(config, { host, scope, force, hooks, hosted, ...(gate ? { gateUrl: gateMcpUrl(env) } : {}) }, env);
481
+ // Every write lands in ~/.kya/manifest.json so `kya uninstall` can undo it.
482
+ const { liveJournal } = await import("./manifest.js");
483
+ const { withJournal } = await import("./write-journal.js");
484
+ const { journal, finish } = liveJournal({ globalDir: globalConfigDir(env), cwd });
485
+ let result;
486
+ try {
487
+ result = await withJournal(journal, () => runConnect(config, { host, scope, force, hooks, hosted, ...(gate ? { gateUrl: gateMcpUrl(env) } : {}) }, env));
488
+ }
489
+ finally {
490
+ finish();
491
+ }
357
492
  if (config.json) {
358
493
  io.log(JSON.stringify(result, null, 2));
359
494
  }
@@ -474,13 +609,29 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
474
609
  return 0;
475
610
  }
476
611
  if (sub !== "init") {
477
- io.error("Usage: kya team init [--dry-run] [--json] | kya team managed-settings");
612
+ io.error("Usage: kya team init [--guard] [--dry-run] [--json] | kya team managed-settings");
478
613
  return 2;
479
614
  }
480
615
  // Present means on (like --fail-closed): parseArgs would let `--dry-run x` swallow x.
481
616
  const dr = parsed.flags["dry-run"];
482
617
  const dryRun = dr !== undefined && dr !== false && !/^(false|0)$/i.test(String(dr));
483
- const result = runTeamInit({ cwd, env, dryRun });
618
+ const guard = parsed.flags["guard"] !== undefined && parsed.flags["guard"] !== false && !/^(false|0)$/i.test(String(parsed.flags["guard"]));
619
+ let result;
620
+ if (guard && !dryRun) {
621
+ // Every write lands in ~/.kya/manifest.json so `kya uninstall --project` can undo it.
622
+ const { liveJournal } = await import("./manifest.js");
623
+ const { withJournal } = await import("./write-journal.js");
624
+ const { journal, finish } = liveJournal({ globalDir: globalConfigDir(env), cwd });
625
+ try {
626
+ result = await withJournal(journal, () => runTeamInit({ cwd, env, dryRun, guard }));
627
+ }
628
+ finally {
629
+ finish();
630
+ }
631
+ }
632
+ else {
633
+ result = runTeamInit({ cwd, env, dryRun, guard });
634
+ }
484
635
  io.log(flagBool(parsed.flags, "json") ? JSON.stringify(result, null, 2) : formatTeamInitHuman(result));
485
636
  return 0;
486
637
  }
@@ -543,7 +694,7 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
543
694
  }
544
695
  case "receipt": {
545
696
  const { formatReceiptHuman, receiptInputFromArgs, runReceipt, runReceiptShare } = await import("./commands/receipt.js");
546
- const { SHARE_HOSTED_HINT, formatShareLine } = await import("./receipt/share.js");
697
+ const { SHARE_CONNECT_HINT, SHARE_HOSTED_HINT, formatShareLine } = await import("./receipt/share.js");
547
698
  const { maybePrintStarCta } = await import("./star-cta.js");
548
699
  const config = resolveConfig({
549
700
  cwd,
@@ -554,6 +705,17 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
554
705
  offline: true,
555
706
  });
556
707
  const input = receiptInputFromArgs(parsed);
708
+ if (flagBool(parsed.flags, "card")) {
709
+ const { runReceiptCard } = await import("./commands/receipt.js");
710
+ const { cardCaption } = await import("./card.js");
711
+ const made = runReceiptCard(env, { id: flagString(parsed.flags, "id") });
712
+ if (!made.ok) {
713
+ io.error(made.message);
714
+ return 1;
715
+ }
716
+ io.log(config.json ? JSON.stringify({ id: made.id, svg: made.svgPath, png: made.pngPath ?? null, caption: cardCaption(made.input) }) : made.text.trimEnd());
717
+ return 0;
718
+ }
557
719
  if (flagBool(parsed.flags, "share")) {
558
720
  const shared = await runReceiptShare(config, {
559
721
  sessionId: input.sessionId,
@@ -571,6 +733,7 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
571
733
  else {
572
734
  io.log(formatShareLine(shared.url, shared.expiresAt));
573
735
  io.log(SHARE_HOSTED_HINT);
736
+ io.log(SHARE_CONNECT_HINT);
574
737
  }
575
738
  return 0;
576
739
  }
@@ -715,11 +878,23 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
715
878
  flags: parsed.flags,
716
879
  requireApiKey: true,
717
880
  });
718
- const opts = serveMcpOptionsFromArgs(parsed);
881
+ // The client may say who it is before the beacon exists (it starts after the server): keep the name.
882
+ let client;
883
+ let setClient;
884
+ const opts = {
885
+ ...serveMcpOptionsFromArgs(parsed),
886
+ onClientInfo: (name) => {
887
+ client ??= name;
888
+ setClient?.(name);
889
+ },
890
+ };
719
891
  const result = await runServeMcp(config, opts);
720
892
  // Opt-in anonymous session beacon. Silent by construction: no stdout/stderr, because an MCP
721
893
  // stdio process owns stdout as its protocol channel.
722
894
  const beacon = startSessionBeacon(result.mode === "http" ? "mcp-http" : "mcp-stdio", { env, cwd });
895
+ setClient = (name) => beacon.setClient(name);
896
+ if (client !== undefined)
897
+ beacon.setClient(client);
723
898
  try {
724
899
  if (result.mode === "http" && result.http) {
725
900
  io.log(`KYA MCP gate listening on ${result.http.url} (host=${config.host})`);
@@ -1077,6 +1252,15 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
1077
1252
  const { discoverOptionsFromArgs, runDiscover } = await import("./commands/discover.js");
1078
1253
  return await runDiscover(discoverOptionsFromArgs(parsed), io, cwd, env);
1079
1254
  }
1255
+ case "alerts": {
1256
+ const { runAlerts } = await import("./commands/alerts.js");
1257
+ return await runAlerts({
1258
+ io: { log: (line) => io.log(line), error: (line) => io.error(line) },
1259
+ env,
1260
+ sub: parsed.positionals[0],
1261
+ flags: parsed.flags,
1262
+ });
1263
+ }
1080
1264
  case "notify-flush": {
1081
1265
  const { runNotifyFlush } = await import("./commands/notify-flush.js");
1082
1266
  // Internal: spawned detached by hook/wrap (src/notify/dispatch.ts).
@@ -1084,7 +1268,8 @@ export async function runCli(argv, io = defaultIo, env = process.env, cwd = proc
1084
1268
  }
1085
1269
  default:
1086
1270
  io.error(`Unknown command: ${parsed.command}`);
1087
- io.log(await helpText());
1271
+ io.error("Run kya help --all to list every command.");
1272
+ io.log(SHORT_HELP);
1088
1273
  return 2;
1089
1274
  }
1090
1275
  }