crewly 1.20.156 → 1.20.158

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 (213) hide show
  1. package/README.md +1 -0
  2. package/config/templates/growth-marketing-team.json +5 -5
  3. package/config/templates/personal-assistant-team.json +5 -5
  4. package/dist/backend/backend/src/constants.d.ts +161 -6
  5. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  6. package/dist/backend/backend/src/constants.js +188 -11
  7. package/dist/backend/backend/src/constants.js.map +1 -1
  8. package/dist/backend/backend/src/controllers/owner-receipt/owner-receipt.controller.d.ts.map +1 -1
  9. package/dist/backend/backend/src/controllers/owner-receipt/owner-receipt.controller.js +5 -0
  10. package/dist/backend/backend/src/controllers/owner-receipt/owner-receipt.controller.js.map +1 -1
  11. package/dist/backend/backend/src/controllers/tickets/tickets.controller.d.ts +18 -0
  12. package/dist/backend/backend/src/controllers/tickets/tickets.controller.d.ts.map +1 -1
  13. package/dist/backend/backend/src/controllers/tickets/tickets.controller.js +42 -0
  14. package/dist/backend/backend/src/controllers/tickets/tickets.controller.js.map +1 -1
  15. package/dist/backend/backend/src/controllers/tickets/tickets.routes.d.ts +1 -0
  16. package/dist/backend/backend/src/controllers/tickets/tickets.routes.d.ts.map +1 -1
  17. package/dist/backend/backend/src/controllers/tickets/tickets.routes.js +3 -1
  18. package/dist/backend/backend/src/controllers/tickets/tickets.routes.js.map +1 -1
  19. package/dist/backend/backend/src/index.d.ts.map +1 -1
  20. package/dist/backend/backend/src/index.js +8 -2
  21. package/dist/backend/backend/src/index.js.map +1 -1
  22. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts +9 -0
  23. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  24. package/dist/backend/backend/src/services/agent/agent-registration.service.js +34 -29
  25. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  26. package/dist/backend/backend/src/services/agent/codex-runtime.service.d.ts +4 -3
  27. package/dist/backend/backend/src/services/agent/codex-runtime.service.d.ts.map +1 -1
  28. package/dist/backend/backend/src/services/agent/codex-runtime.service.js +48 -3
  29. package/dist/backend/backend/src/services/agent/codex-runtime.service.js.map +1 -1
  30. package/dist/backend/backend/src/services/agent/crewly-agent/in-process-log-buffer.d.ts +9 -0
  31. package/dist/backend/backend/src/services/agent/crewly-agent/in-process-log-buffer.d.ts.map +1 -1
  32. package/dist/backend/backend/src/services/agent/crewly-agent/in-process-log-buffer.js +18 -1
  33. package/dist/backend/backend/src/services/agent/crewly-agent/in-process-log-buffer.js.map +1 -1
  34. package/dist/backend/backend/src/services/agent/oauth-relogin-monitor.service.d.ts.map +1 -1
  35. package/dist/backend/backend/src/services/agent/oauth-relogin-monitor.service.js +5 -1
  36. package/dist/backend/backend/src/services/agent/oauth-relogin-monitor.service.js.map +1 -1
  37. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -1
  38. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js +14 -4
  39. package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
  40. package/dist/backend/backend/src/services/agent/tmux-command.service.d.ts +14 -2
  41. package/dist/backend/backend/src/services/agent/tmux-command.service.d.ts.map +1 -1
  42. package/dist/backend/backend/src/services/agent/tmux-command.service.js +24 -5
  43. package/dist/backend/backend/src/services/agent/tmux-command.service.js.map +1 -1
  44. package/dist/backend/backend/src/services/agent/tmux.service.d.ts.map +1 -1
  45. package/dist/backend/backend/src/services/agent/tmux.service.js +14 -9
  46. package/dist/backend/backend/src/services/agent/tmux.service.js.map +1 -1
  47. package/dist/backend/backend/src/services/backup/backup-archive.service.d.ts +8 -0
  48. package/dist/backend/backend/src/services/backup/backup-archive.service.d.ts.map +1 -1
  49. package/dist/backend/backend/src/services/backup/backup-archive.service.js +16 -8
  50. package/dist/backend/backend/src/services/backup/backup-archive.service.js.map +1 -1
  51. package/dist/backend/backend/src/services/backup/backup.types.d.ts +19 -0
  52. package/dist/backend/backend/src/services/backup/backup.types.d.ts.map +1 -1
  53. package/dist/backend/backend/src/services/backup/backup.types.js +19 -0
  54. package/dist/backend/backend/src/services/backup/backup.types.js.map +1 -1
  55. package/dist/backend/backend/src/services/security/secret-scrub.service.d.ts +108 -0
  56. package/dist/backend/backend/src/services/security/secret-scrub.service.d.ts.map +1 -0
  57. package/dist/backend/backend/src/services/security/secret-scrub.service.js +200 -0
  58. package/dist/backend/backend/src/services/security/secret-scrub.service.js.map +1 -0
  59. package/dist/backend/backend/src/services/session/pty/pty-session-backend.d.ts +13 -9
  60. package/dist/backend/backend/src/services/session/pty/pty-session-backend.d.ts.map +1 -1
  61. package/dist/backend/backend/src/services/session/pty/pty-session-backend.js +30 -14
  62. package/dist/backend/backend/src/services/session/pty/pty-session-backend.js.map +1 -1
  63. package/dist/backend/backend/src/services/session/pty/pty-session.d.ts.map +1 -1
  64. package/dist/backend/backend/src/services/session/pty/pty-session.js +6 -1
  65. package/dist/backend/backend/src/services/session/pty/pty-session.js.map +1 -1
  66. package/dist/backend/backend/src/services/session/session-command-helper.d.ts.map +1 -1
  67. package/dist/backend/backend/src/services/session/session-command-helper.js +3 -2
  68. package/dist/backend/backend/src/services/session/session-command-helper.js.map +1 -1
  69. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt-data.d.ts +84 -0
  70. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt-data.d.ts.map +1 -1
  71. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt-data.js +301 -6
  72. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt-data.js.map +1 -1
  73. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt.boot.d.ts +8 -0
  74. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt.boot.d.ts.map +1 -1
  75. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt.boot.js +16 -0
  76. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt.boot.js.map +1 -1
  77. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt.renderer.d.ts +28 -10
  78. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt.renderer.d.ts.map +1 -1
  79. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt.renderer.js +46 -193
  80. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt.renderer.js.map +1 -1
  81. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt.service.d.ts +15 -2
  82. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt.service.d.ts.map +1 -1
  83. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt.service.js +26 -4
  84. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt.service.js.map +1 -1
  85. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt.types.d.ts +35 -0
  86. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt.types.d.ts.map +1 -1
  87. package/dist/backend/backend/src/services/v3/owner-receipt/owner-receipt.types.js.map +1 -1
  88. package/dist/backend/backend/src/services/v3/request.service.d.ts +2 -0
  89. package/dist/backend/backend/src/services/v3/request.service.d.ts.map +1 -1
  90. package/dist/backend/backend/src/services/v3/request.service.js +16 -4
  91. package/dist/backend/backend/src/services/v3/request.service.js.map +1 -1
  92. package/dist/backend/backend/src/services/v3/ticket-ask-classifier.d.ts.map +1 -1
  93. package/dist/backend/backend/src/services/v3/ticket-ask-classifier.js +2 -0
  94. package/dist/backend/backend/src/services/v3/ticket-ask-classifier.js.map +1 -1
  95. package/dist/backend/backend/src/services/v3/ticket-channel-hooks.d.ts.map +1 -1
  96. package/dist/backend/backend/src/services/v3/ticket-channel-hooks.js +2 -1
  97. package/dist/backend/backend/src/services/v3/ticket-channel-hooks.js.map +1 -1
  98. package/dist/backend/backend/src/services/v3/ticket-hygiene.d.ts +162 -0
  99. package/dist/backend/backend/src/services/v3/ticket-hygiene.d.ts.map +1 -0
  100. package/dist/backend/backend/src/services/v3/ticket-hygiene.js +221 -0
  101. package/dist/backend/backend/src/services/v3/ticket-hygiene.js.map +1 -0
  102. package/dist/backend/backend/src/services/v3/ticket-intake.service.d.ts +71 -6
  103. package/dist/backend/backend/src/services/v3/ticket-intake.service.d.ts.map +1 -1
  104. package/dist/backend/backend/src/services/v3/ticket-intake.service.js +151 -25
  105. package/dist/backend/backend/src/services/v3/ticket-intake.service.js.map +1 -1
  106. package/dist/backend/backend/src/services/v3/ticket-review.service.d.ts +22 -3
  107. package/dist/backend/backend/src/services/v3/ticket-review.service.d.ts.map +1 -1
  108. package/dist/backend/backend/src/services/v3/ticket-review.service.js +99 -40
  109. package/dist/backend/backend/src/services/v3/ticket-review.service.js.map +1 -1
  110. package/dist/backend/backend/src/types/team-template.types.d.ts +1 -1
  111. package/dist/backend/backend/src/types/team-template.types.d.ts.map +1 -1
  112. package/dist/backend/backend/src/types/v2/request.types.d.ts +13 -0
  113. package/dist/backend/backend/src/types/v2/request.types.d.ts.map +1 -1
  114. package/dist/backend/backend/src/types/v2/request.types.js.map +1 -1
  115. package/dist/backend/backend/src/utils/secret-redactor.d.ts +131 -0
  116. package/dist/backend/backend/src/utils/secret-redactor.d.ts.map +1 -0
  117. package/dist/backend/backend/src/utils/secret-redactor.js +233 -0
  118. package/dist/backend/backend/src/utils/secret-redactor.js.map +1 -0
  119. package/dist/backend/backend/src/utils/shell-history.d.ts +48 -0
  120. package/dist/backend/backend/src/utils/shell-history.d.ts.map +1 -0
  121. package/dist/backend/backend/src/utils/shell-history.js +65 -0
  122. package/dist/backend/backend/src/utils/shell-history.js.map +1 -0
  123. package/dist/backend/build-info.json +2 -2
  124. package/dist/cli/backend/src/constants.d.ts +161 -6
  125. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  126. package/dist/cli/backend/src/constants.js +188 -11
  127. package/dist/cli/backend/src/constants.js.map +1 -1
  128. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts +9 -0
  129. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  130. package/dist/cli/backend/src/services/agent/agent-registration.service.js +34 -29
  131. package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
  132. package/dist/cli/backend/src/services/agent/codex-runtime.service.d.ts +4 -3
  133. package/dist/cli/backend/src/services/agent/codex-runtime.service.d.ts.map +1 -1
  134. package/dist/cli/backend/src/services/agent/codex-runtime.service.js +48 -3
  135. package/dist/cli/backend/src/services/agent/codex-runtime.service.js.map +1 -1
  136. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.d.ts +9 -0
  137. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.d.ts.map +1 -1
  138. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.js +18 -1
  139. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.js.map +1 -1
  140. package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.d.ts.map +1 -1
  141. package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.js +5 -1
  142. package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.js.map +1 -1
  143. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -1
  144. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js +14 -4
  145. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
  146. package/dist/cli/backend/src/services/backup/backup-archive.service.d.ts +8 -0
  147. package/dist/cli/backend/src/services/backup/backup-archive.service.d.ts.map +1 -1
  148. package/dist/cli/backend/src/services/backup/backup-archive.service.js +16 -8
  149. package/dist/cli/backend/src/services/backup/backup-archive.service.js.map +1 -1
  150. package/dist/cli/backend/src/services/backup/backup.types.d.ts +19 -0
  151. package/dist/cli/backend/src/services/backup/backup.types.d.ts.map +1 -1
  152. package/dist/cli/backend/src/services/backup/backup.types.js +19 -0
  153. package/dist/cli/backend/src/services/backup/backup.types.js.map +1 -1
  154. package/dist/cli/backend/src/services/security/secret-scrub.service.d.ts +108 -0
  155. package/dist/cli/backend/src/services/security/secret-scrub.service.d.ts.map +1 -0
  156. package/dist/cli/backend/src/services/security/secret-scrub.service.js +200 -0
  157. package/dist/cli/backend/src/services/security/secret-scrub.service.js.map +1 -0
  158. package/dist/cli/backend/src/services/session/pty/pty-session-backend.d.ts +13 -9
  159. package/dist/cli/backend/src/services/session/pty/pty-session-backend.d.ts.map +1 -1
  160. package/dist/cli/backend/src/services/session/pty/pty-session-backend.js +30 -14
  161. package/dist/cli/backend/src/services/session/pty/pty-session-backend.js.map +1 -1
  162. package/dist/cli/backend/src/services/session/pty/pty-session.d.ts.map +1 -1
  163. package/dist/cli/backend/src/services/session/pty/pty-session.js +6 -1
  164. package/dist/cli/backend/src/services/session/pty/pty-session.js.map +1 -1
  165. package/dist/cli/backend/src/services/session/session-command-helper.d.ts.map +1 -1
  166. package/dist/cli/backend/src/services/session/session-command-helper.js +3 -2
  167. package/dist/cli/backend/src/services/session/session-command-helper.js.map +1 -1
  168. package/dist/cli/backend/src/services/v3/request.service.d.ts +2 -0
  169. package/dist/cli/backend/src/services/v3/request.service.d.ts.map +1 -1
  170. package/dist/cli/backend/src/services/v3/request.service.js +16 -4
  171. package/dist/cli/backend/src/services/v3/request.service.js.map +1 -1
  172. package/dist/cli/backend/src/services/v3/ticket-ask-classifier.d.ts.map +1 -1
  173. package/dist/cli/backend/src/services/v3/ticket-ask-classifier.js +2 -0
  174. package/dist/cli/backend/src/services/v3/ticket-ask-classifier.js.map +1 -1
  175. package/dist/cli/backend/src/services/v3/ticket-channel-hooks.d.ts.map +1 -1
  176. package/dist/cli/backend/src/services/v3/ticket-channel-hooks.js +2 -1
  177. package/dist/cli/backend/src/services/v3/ticket-channel-hooks.js.map +1 -1
  178. package/dist/cli/backend/src/services/v3/ticket-hygiene.d.ts +162 -0
  179. package/dist/cli/backend/src/services/v3/ticket-hygiene.d.ts.map +1 -0
  180. package/dist/cli/backend/src/services/v3/ticket-hygiene.js +221 -0
  181. package/dist/cli/backend/src/services/v3/ticket-hygiene.js.map +1 -0
  182. package/dist/cli/backend/src/services/v3/ticket-intake.service.d.ts +71 -6
  183. package/dist/cli/backend/src/services/v3/ticket-intake.service.d.ts.map +1 -1
  184. package/dist/cli/backend/src/services/v3/ticket-intake.service.js +151 -25
  185. package/dist/cli/backend/src/services/v3/ticket-intake.service.js.map +1 -1
  186. package/dist/cli/backend/src/services/v3/ticket-review.service.d.ts +22 -3
  187. package/dist/cli/backend/src/services/v3/ticket-review.service.d.ts.map +1 -1
  188. package/dist/cli/backend/src/services/v3/ticket-review.service.js +99 -40
  189. package/dist/cli/backend/src/services/v3/ticket-review.service.js.map +1 -1
  190. package/dist/cli/backend/src/types/team-template.types.d.ts +1 -1
  191. package/dist/cli/backend/src/types/team-template.types.d.ts.map +1 -1
  192. package/dist/cli/backend/src/types/v2/request.types.d.ts +13 -0
  193. package/dist/cli/backend/src/types/v2/request.types.d.ts.map +1 -1
  194. package/dist/cli/backend/src/types/v2/request.types.js.map +1 -1
  195. package/dist/cli/backend/src/utils/secret-redactor.d.ts +131 -0
  196. package/dist/cli/backend/src/utils/secret-redactor.d.ts.map +1 -0
  197. package/dist/cli/backend/src/utils/secret-redactor.js +233 -0
  198. package/dist/cli/backend/src/utils/secret-redactor.js.map +1 -0
  199. package/dist/cli/backend/src/utils/shell-history.d.ts +48 -0
  200. package/dist/cli/backend/src/utils/shell-history.d.ts.map +1 -0
  201. package/dist/cli/backend/src/utils/shell-history.js +65 -0
  202. package/dist/cli/backend/src/utils/shell-history.js.map +1 -0
  203. package/dist/cli/cli/src/commands/security.d.ts +41 -0
  204. package/dist/cli/cli/src/commands/security.d.ts.map +1 -0
  205. package/dist/cli/cli/src/commands/security.js +81 -0
  206. package/dist/cli/cli/src/commands/security.js.map +1 -0
  207. package/dist/cli/cli/src/index.js +9 -0
  208. package/dist/cli/cli/src/index.js.map +1 -1
  209. package/frontend/dist/assets/index-edd6f262.css +42 -0
  210. package/frontend/dist/assets/{index-8d4b3ad2.js → index-f79676aa.js} +268 -268
  211. package/frontend/dist/index.html +2 -2
  212. package/package.json +1 -1
  213. package/frontend/dist/assets/index-b6aded4c.css +0 -42
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Secret redactor for persisted terminal output.
3
+ *
4
+ * Every byte of agent PTY output that Crewly writes to disk
5
+ * (~/.crewly/logs/sessions/*.log) passes through here, and so do the
6
+ * one-time scrub of existing logs and shell history files
7
+ * (`crewly security scrub-logs`). Three layers, applied in this order:
8
+ *
9
+ * 1. **Known values** — the exact value of every secret Crewly holds for the
10
+ * session (backend env + the session's spawn env, see
11
+ * collectSecretEnvValues). Catches secrets no pattern recognises (a Slack
12
+ * signing secret is plain hex). Masked as `[REDACTED <NAME>]`.
13
+ * 2. **Secret-named assignments** — `NAME=value` where NAME is a secret name
14
+ * (isSecretEnvKey: `…API_KEY`, `…TOKEN`, `…SECRET`, `…PASSWORD`,
15
+ * `…PRIVATE_KEY`, … plus SECRET_REDACTION_CONSTANTS.KNOWN_SECRET_ENV_NAMES).
16
+ * The value is masked whatever it looks like. Names are matched on the
17
+ * whole identifier, so `TOKEN_COUNT=5` or `MAX_TOKENS=4096` are left alone
18
+ * (the name does not END in a secret suffix); `$VAR` references and
19
+ * already-masked values are left alone too.
20
+ * 3. **Token shapes** — well-known raw credential formats wherever they
21
+ * appear: Slack `xoxb-/xoxp-/xoxa-/xoxe-/xapp-…`, OpenAI/DeepSeek `sk-…`,
22
+ * Anthropic `sk-ant-…`, Google `AIza…`, GitHub `ghp_/gho_/…` and
23
+ * `github_pat_…`, plus the wiki confidentiality patterns (JWTs, bearer
24
+ * headers, private-key blocks, connection strings).
25
+ *
26
+ * Redaction is idempotent: running it over already-redacted text changes
27
+ * nothing and reports a count of 0.
28
+ *
29
+ * Output is streamed in arbitrary chunks, so a secret can be split between
30
+ * two chunks. StreamingSecretRedactor holds back the tail after the last
31
+ * whitespace (secrets never contain whitespace) and prepends it to the next
32
+ * chunk, which keeps the whole thing linear with a small bounded carry.
33
+ *
34
+ * @module utils/secret-redactor
35
+ */
36
+ import { type SecretEnvValue } from './secret-env.js';
37
+ /**
38
+ * Whether an assignment's name marks its value as secret.
39
+ *
40
+ * @param name - Variable name as it appeared (any case)
41
+ * @returns True for secret names (see module docs)
42
+ */
43
+ export declare function isSecretAssignmentName(name: string): boolean;
44
+ /** Result of a counted redaction. */
45
+ export interface RedactionResult {
46
+ /** The masked text */
47
+ text: string;
48
+ /** Number of secrets masked (0 when the text was already clean) */
49
+ count: number;
50
+ }
51
+ /**
52
+ * Masks every secret in a complete piece of text and counts what was masked.
53
+ *
54
+ * @param text - Text to mask
55
+ * @param knownSecrets - Exact secret values to mask (see collectSecretEnvValues)
56
+ * @returns Masked text and the number of secrets masked
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * redactSecretsWithCount('export OPENAI_API_KEY=sk-abc…').count; // 1
61
+ * redactSecretsWithCount('TOKEN_COUNT=5').count; // 0
62
+ * ```
63
+ */
64
+ export declare function redactSecretsWithCount(text: string, knownSecrets?: readonly SecretEnvValue[]): RedactionResult;
65
+ /**
66
+ * Masks every secret in a complete piece of text.
67
+ *
68
+ * @param text - Text to mask
69
+ * @param knownSecrets - Exact secret values to mask
70
+ * @returns The masked text
71
+ */
72
+ export declare function redactSecrets(text: string, knownSecrets?: readonly SecretEnvValue[]): string;
73
+ /**
74
+ * Chunk-boundary-safe redactor for streamed output.
75
+ *
76
+ * `push()` returns the redacted text that is safe to emit now and holds back
77
+ * the tail after the last whitespace; `flush()` returns whatever is held back
78
+ * (call it when the stream closes). Only the new chunk is scanned for
79
+ * whitespace (the carry never contains any), so work is linear in the input.
80
+ * A run of more than MAX_CARRY_CHARS non-whitespace characters is cut at a
81
+ * non-token character near its end (or hard-cut when there is none), keeping
82
+ * the carry bounded.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const r = new StreamingSecretRedactor();
87
+ * log.write(r.push('export GEMINI_API_KEY=AIzaSy'));
88
+ * log.write(r.push('…rest of key\n'));
89
+ * log.write(r.flush());
90
+ * ```
91
+ */
92
+ export declare class StreamingSecretRedactor {
93
+ private carry;
94
+ private knownSecrets;
95
+ private readonly maxCarry;
96
+ private readonly minCarry;
97
+ /**
98
+ * @param knownSecrets - Exact secret values to mask
99
+ * @param maxCarry - Longest whitespace-free tail held back before forcing a cut
100
+ */
101
+ constructor(knownSecrets?: readonly SecretEnvValue[], maxCarry?: number);
102
+ /**
103
+ * Replaces the exact-value secret set (e.g. after the session's env changes).
104
+ *
105
+ * @param knownSecrets - Exact secret values to mask
106
+ */
107
+ setKnownSecrets(knownSecrets: readonly SecretEnvValue[]): void;
108
+ /**
109
+ * Feeds one chunk and returns the redacted text that can be emitted now.
110
+ *
111
+ * @param chunk - Next piece of output
112
+ * @returns Redacted text (possibly empty while a word is still arriving)
113
+ */
114
+ push(chunk: string): string;
115
+ /**
116
+ * Returns (redacted) whatever is still held back and resets the carry.
117
+ *
118
+ * @returns Remaining redacted text
119
+ */
120
+ flush(): string;
121
+ /**
122
+ * Picks a cut point for a whitespace-free run that outgrew the carry: the
123
+ * last non-token character before the final minCarry characters, so a
124
+ * credential near the end is not split; hard cut when there is none.
125
+ *
126
+ * @param buf - Buffered text whose tail has no whitespace
127
+ * @returns Index to cut at
128
+ */
129
+ private forcedCut;
130
+ }
131
+ //# sourceMappingURL=secret-redactor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-redactor.d.ts","sourceRoot":"","sources":["../../../../../backend/src/utils/secret-redactor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AA6CtE;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAO5D;AAED,qCAAqC;AACrC,MAAM,WAAW,eAAe;IAC/B,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,GAAE,SAAS,cAAc,EAAO,GAAG,eAAe,CA2BlH;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,GAAE,SAAS,cAAc,EAAO,GAAG,MAAM,CAEhG;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,uBAAuB;IACnC,OAAO,CAAC,KAAK,CAAM;IACnB,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC;;;OAGG;gBAEF,YAAY,GAAE,SAAS,cAAc,EAAO,EAC5C,QAAQ,GAAE,MAAmD;IAO9D;;;;OAIG;IACH,eAAe,CAAC,YAAY,EAAE,SAAS,cAAc,EAAE,GAAG,IAAI;IAI9D;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAyB3B;;;;OAIG;IACH,KAAK,IAAI,MAAM;IAMf;;;;;;;OAOG;IACH,OAAO,CAAC,SAAS;CAQjB"}
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Secret redactor for persisted terminal output.
3
+ *
4
+ * Every byte of agent PTY output that Crewly writes to disk
5
+ * (~/.crewly/logs/sessions/*.log) passes through here, and so do the
6
+ * one-time scrub of existing logs and shell history files
7
+ * (`crewly security scrub-logs`). Three layers, applied in this order:
8
+ *
9
+ * 1. **Known values** — the exact value of every secret Crewly holds for the
10
+ * session (backend env + the session's spawn env, see
11
+ * collectSecretEnvValues). Catches secrets no pattern recognises (a Slack
12
+ * signing secret is plain hex). Masked as `[REDACTED <NAME>]`.
13
+ * 2. **Secret-named assignments** — `NAME=value` where NAME is a secret name
14
+ * (isSecretEnvKey: `…API_KEY`, `…TOKEN`, `…SECRET`, `…PASSWORD`,
15
+ * `…PRIVATE_KEY`, … plus SECRET_REDACTION_CONSTANTS.KNOWN_SECRET_ENV_NAMES).
16
+ * The value is masked whatever it looks like. Names are matched on the
17
+ * whole identifier, so `TOKEN_COUNT=5` or `MAX_TOKENS=4096` are left alone
18
+ * (the name does not END in a secret suffix); `$VAR` references and
19
+ * already-masked values are left alone too.
20
+ * 3. **Token shapes** — well-known raw credential formats wherever they
21
+ * appear: Slack `xoxb-/xoxp-/xoxa-/xoxe-/xapp-…`, OpenAI/DeepSeek `sk-…`,
22
+ * Anthropic `sk-ant-…`, Google `AIza…`, GitHub `ghp_/gho_/…` and
23
+ * `github_pat_…`, plus the wiki confidentiality patterns (JWTs, bearer
24
+ * headers, private-key blocks, connection strings).
25
+ *
26
+ * Redaction is idempotent: running it over already-redacted text changes
27
+ * nothing and reports a count of 0.
28
+ *
29
+ * Output is streamed in arbitrary chunks, so a secret can be split between
30
+ * two chunks. StreamingSecretRedactor holds back the tail after the last
31
+ * whitespace (secrets never contain whitespace) and prepends it to the next
32
+ * chunk, which keeps the whole thing linear with a small bounded carry.
33
+ *
34
+ * @module utils/secret-redactor
35
+ */
36
+ import { SECRET_REDACTION_CONSTANTS } from '../constants.js';
37
+ import { SECRET_PATTERNS } from '../services/wiki/wiki-redaction.js';
38
+ import { isSecretEnvKey } from './secret-env.js';
39
+ /**
40
+ * Raw token shapes beyond the wiki SECRET_PATTERNS (which require a numeric
41
+ * team segment for Slack tokens and do not know `xapp-` or `github_pat_`).
42
+ */
43
+ const EXTRA_TOKEN_PATTERNS = [
44
+ { name: 'slack_token', regex: /\bxox[abposre]-[A-Za-z0-9-]{10,}/g, mask: '[REDACTED slack_token]' },
45
+ { name: 'slack_app_token', regex: /\bxapp-[A-Za-z0-9-]{10,}/g, mask: '[REDACTED slack_app_token]' },
46
+ { name: 'anthropic_key', regex: /\bsk-ant-[A-Za-z0-9_-]{20,}/g, mask: '[REDACTED api_key]' },
47
+ { name: 'github_pat', regex: /\bgithub_pat_[A-Za-z0-9_]{20,}/g, mask: '[REDACTED github_token]' },
48
+ ];
49
+ /** Every token-shape detector, most specific first. */
50
+ const TOKEN_PATTERNS = [...EXTRA_TOKEN_PATTERNS, ...SECRET_PATTERNS];
51
+ /**
52
+ * `NAME=value` (optionally quoted value). The value stops at whitespace,
53
+ * quotes, backticks and shell separators. The identifier must start at a
54
+ * word boundary so it is matched whole.
55
+ */
56
+ const ASSIGNMENT_RE = /\b([A-Za-z_][A-Za-z0-9_]*)=(["']?)([^\s"'`;&|]+)/g;
57
+ /** Prefix every mask this module emits starts with. */
58
+ const REDACTED_PREFIX = '[REDACTED';
59
+ /** Mask written in place of a secret-named assignment's value. */
60
+ const ASSIGNMENT_MASK = '[REDACTED]';
61
+ /** Characters that can appear inside a credential or a `NAME=value` pair. */
62
+ const TOKEN_CHAR_RE = /[A-Za-z0-9_\-=.:/+"'[\]]/;
63
+ /** Known secret names, upper-cased, for O(1) lookup. */
64
+ const KNOWN_SECRET_NAMES = new Set(SECRET_REDACTION_CONSTANTS.KNOWN_SECRET_ENV_NAMES);
65
+ /**
66
+ * Whether an assignment's name marks its value as secret.
67
+ *
68
+ * @param name - Variable name as it appeared (any case)
69
+ * @returns True for secret names (see module docs)
70
+ */
71
+ export function isSecretAssignmentName(name) {
72
+ const upper = name.toUpperCase();
73
+ return (KNOWN_SECRET_NAMES.has(upper) ||
74
+ isSecretEnvKey(name) ||
75
+ SECRET_REDACTION_CONSTANTS.SECRET_NAME_PATTERNS.some((re) => re.test(upper)));
76
+ }
77
+ /**
78
+ * Masks every secret in a complete piece of text and counts what was masked.
79
+ *
80
+ * @param text - Text to mask
81
+ * @param knownSecrets - Exact secret values to mask (see collectSecretEnvValues)
82
+ * @returns Masked text and the number of secrets masked
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * redactSecretsWithCount('export OPENAI_API_KEY=sk-abc…').count; // 1
87
+ * redactSecretsWithCount('TOKEN_COUNT=5').count; // 0
88
+ * ```
89
+ */
90
+ export function redactSecretsWithCount(text, knownSecrets = []) {
91
+ let out = text;
92
+ let count = 0;
93
+ for (const { name, value } of knownSecrets) {
94
+ if (!value || !out.includes(value))
95
+ continue;
96
+ const parts = out.split(value);
97
+ count += parts.length - 1;
98
+ out = parts.join(`${REDACTED_PREFIX} ${name}]`);
99
+ }
100
+ out = out.replace(ASSIGNMENT_RE, (match, name, quote, value) => {
101
+ if (!isSecretAssignmentName(name) || value.startsWith('$') || value.startsWith(REDACTED_PREFIX))
102
+ return match;
103
+ count++;
104
+ return `${name}=${quote}${ASSIGNMENT_MASK}`;
105
+ });
106
+ for (const p of TOKEN_PATTERNS) {
107
+ p.regex.lastIndex = 0;
108
+ out = out.replace(p.regex, (match) => {
109
+ if (match === p.mask)
110
+ return match;
111
+ count++;
112
+ return p.mask;
113
+ });
114
+ }
115
+ return { text: out, count };
116
+ }
117
+ /**
118
+ * Masks every secret in a complete piece of text.
119
+ *
120
+ * @param text - Text to mask
121
+ * @param knownSecrets - Exact secret values to mask
122
+ * @returns The masked text
123
+ */
124
+ export function redactSecrets(text, knownSecrets = []) {
125
+ return redactSecretsWithCount(text, knownSecrets).text;
126
+ }
127
+ /**
128
+ * Chunk-boundary-safe redactor for streamed output.
129
+ *
130
+ * `push()` returns the redacted text that is safe to emit now and holds back
131
+ * the tail after the last whitespace; `flush()` returns whatever is held back
132
+ * (call it when the stream closes). Only the new chunk is scanned for
133
+ * whitespace (the carry never contains any), so work is linear in the input.
134
+ * A run of more than MAX_CARRY_CHARS non-whitespace characters is cut at a
135
+ * non-token character near its end (or hard-cut when there is none), keeping
136
+ * the carry bounded.
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * const r = new StreamingSecretRedactor();
141
+ * log.write(r.push('export GEMINI_API_KEY=AIzaSy'));
142
+ * log.write(r.push('…rest of key\n'));
143
+ * log.write(r.flush());
144
+ * ```
145
+ */
146
+ export class StreamingSecretRedactor {
147
+ carry = '';
148
+ knownSecrets;
149
+ maxCarry;
150
+ minCarry;
151
+ /**
152
+ * @param knownSecrets - Exact secret values to mask
153
+ * @param maxCarry - Longest whitespace-free tail held back before forcing a cut
154
+ */
155
+ constructor(knownSecrets = [], maxCarry = SECRET_REDACTION_CONSTANTS.MAX_CARRY_CHARS) {
156
+ this.knownSecrets = knownSecrets;
157
+ this.maxCarry = Math.max(maxCarry, 2);
158
+ this.minCarry = Math.max(1, Math.min(SECRET_REDACTION_CONSTANTS.MIN_FORCED_CARRY_CHARS, Math.floor(this.maxCarry / 2)));
159
+ }
160
+ /**
161
+ * Replaces the exact-value secret set (e.g. after the session's env changes).
162
+ *
163
+ * @param knownSecrets - Exact secret values to mask
164
+ */
165
+ setKnownSecrets(knownSecrets) {
166
+ this.knownSecrets = knownSecrets;
167
+ }
168
+ /**
169
+ * Feeds one chunk and returns the redacted text that can be emitted now.
170
+ *
171
+ * @param chunk - Next piece of output
172
+ * @returns Redacted text (possibly empty while a word is still arriving)
173
+ */
174
+ push(chunk) {
175
+ if (!chunk)
176
+ return '';
177
+ const carryLength = this.carry.length;
178
+ const buf = this.carry + chunk;
179
+ let cut = -1;
180
+ for (let i = buf.length - 1; i >= carryLength; i--) {
181
+ if (isWhitespace(buf.charCodeAt(i))) {
182
+ cut = i + 1;
183
+ break;
184
+ }
185
+ }
186
+ if (cut === -1 || buf.length - cut > this.maxCarry) {
187
+ if (buf.length <= this.maxCarry) {
188
+ this.carry = buf;
189
+ return '';
190
+ }
191
+ cut = this.forcedCut(buf);
192
+ }
193
+ this.carry = buf.slice(cut);
194
+ return cut > 0 ? redactSecrets(buf.slice(0, cut), this.knownSecrets) : '';
195
+ }
196
+ /**
197
+ * Returns (redacted) whatever is still held back and resets the carry.
198
+ *
199
+ * @returns Remaining redacted text
200
+ */
201
+ flush() {
202
+ const rest = this.carry;
203
+ this.carry = '';
204
+ return rest ? redactSecrets(rest, this.knownSecrets) : '';
205
+ }
206
+ /**
207
+ * Picks a cut point for a whitespace-free run that outgrew the carry: the
208
+ * last non-token character before the final minCarry characters, so a
209
+ * credential near the end is not split; hard cut when there is none.
210
+ *
211
+ * @param buf - Buffered text whose tail has no whitespace
212
+ * @returns Index to cut at
213
+ */
214
+ forcedCut(buf) {
215
+ const target = buf.length - this.minCarry;
216
+ const floor = Math.max(0, target - this.minCarry);
217
+ for (let i = target; i > floor; i--) {
218
+ if (!TOKEN_CHAR_RE.test(buf[i - 1]))
219
+ return i;
220
+ }
221
+ return target;
222
+ }
223
+ }
224
+ /**
225
+ * Whitespace test on a UTF-16 code unit (space, tab, CR, LF, VT, FF).
226
+ *
227
+ * @param code - Character code
228
+ * @returns True for ASCII whitespace
229
+ */
230
+ function isWhitespace(code) {
231
+ return code === 32 || (code >= 9 && code <= 13);
232
+ }
233
+ //# sourceMappingURL=secret-redactor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-redactor.js","sourceRoot":"","sources":["../../../../../backend/src/utils/secret-redactor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Keeps agent shells out of the user's shell history.
3
+ *
4
+ * Crewly used to type `export GEMINI_API_KEY=…` into agent shells, and bash
5
+ * wrote those lines to ~/.bash_history hundreds of times. Secrets now travel
6
+ * only in the spawn env (utils/secret-env), and on top of that agent shells
7
+ * never write a history file at all:
8
+ *
9
+ * - historyOffSpawnEnv() goes into every PTY's spawn environment;
10
+ * - shellHistoryDisableLine() is typed first by the runtime init sequence,
11
+ * after the rc files ran (they may set HISTFILE themselves);
12
+ * - quietShellLine() prefixes every other typed line with a space, which
13
+ * bash (HISTCONTROL=ignorespace) and zsh (HIST_IGNORE_SPACE) skip.
14
+ *
15
+ * See SHELL_HISTORY_CONSTANTS for why HISTSIZE/HISTFILESIZE are not touched.
16
+ *
17
+ * @module utils/shell-history
18
+ */
19
+ /**
20
+ * Env vars that keep a freshly spawned bash/zsh from writing history.
21
+ *
22
+ * @returns A fresh copy (callers may spread or mutate it)
23
+ */
24
+ export declare function historyOffSpawnEnv(): Record<string, string>;
25
+ /**
26
+ * Prefixes a line typed into a shell with a space so it is not recorded.
27
+ * Already-prefixed lines are returned unchanged.
28
+ *
29
+ * @param line - Shell command line
30
+ * @returns The line, space-prefixed
31
+ */
32
+ export declare function quietShellLine(line: string): string;
33
+ /**
34
+ * The (space-prefixed) line that turns history off inside a running shell.
35
+ *
36
+ * @param shell - Shell binary path (defaults to $SHELL, then /bin/bash — what
37
+ * agent PTYs are spawned with)
38
+ * @returns The line to type, or null for a shell we do not know how to quiet
39
+ */
40
+ export declare function shellHistoryDisableLine(shell?: string | undefined): string | null;
41
+ /**
42
+ * Quotes a value for a POSIX shell word (single quotes).
43
+ *
44
+ * @param value - Raw value
45
+ * @returns Single-quoted value safe for `sh -c`
46
+ */
47
+ export declare function shellQuote(value: string): string;
48
+ //# sourceMappingURL=shell-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell-history.d.ts","sourceRoot":"","sources":["../../../../../backend/src/utils/shell-history.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAE3D;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,GAAE,MAAM,GAAG,SAA4C,GAAG,MAAM,GAAG,IAAI,CAOnH;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Keeps agent shells out of the user's shell history.
3
+ *
4
+ * Crewly used to type `export GEMINI_API_KEY=…` into agent shells, and bash
5
+ * wrote those lines to ~/.bash_history hundreds of times. Secrets now travel
6
+ * only in the spawn env (utils/secret-env), and on top of that agent shells
7
+ * never write a history file at all:
8
+ *
9
+ * - historyOffSpawnEnv() goes into every PTY's spawn environment;
10
+ * - shellHistoryDisableLine() is typed first by the runtime init sequence,
11
+ * after the rc files ran (they may set HISTFILE themselves);
12
+ * - quietShellLine() prefixes every other typed line with a space, which
13
+ * bash (HISTCONTROL=ignorespace) and zsh (HIST_IGNORE_SPACE) skip.
14
+ *
15
+ * See SHELL_HISTORY_CONSTANTS for why HISTSIZE/HISTFILESIZE are not touched.
16
+ *
17
+ * @module utils/shell-history
18
+ */
19
+ import * as path from 'path';
20
+ import { SHELL_HISTORY_CONSTANTS } from '../constants.js';
21
+ /**
22
+ * Env vars that keep a freshly spawned bash/zsh from writing history.
23
+ *
24
+ * @returns A fresh copy (callers may spread or mutate it)
25
+ */
26
+ export function historyOffSpawnEnv() {
27
+ return { ...SHELL_HISTORY_CONSTANTS.SPAWN_ENV };
28
+ }
29
+ /**
30
+ * Prefixes a line typed into a shell with a space so it is not recorded.
31
+ * Already-prefixed lines are returned unchanged.
32
+ *
33
+ * @param line - Shell command line
34
+ * @returns The line, space-prefixed
35
+ */
36
+ export function quietShellLine(line) {
37
+ const prefix = SHELL_HISTORY_CONSTANTS.TYPED_LINE_PREFIX;
38
+ return line.startsWith(prefix) ? line : `${prefix}${line}`;
39
+ }
40
+ /**
41
+ * The (space-prefixed) line that turns history off inside a running shell.
42
+ *
43
+ * @param shell - Shell binary path (defaults to $SHELL, then /bin/bash — what
44
+ * agent PTYs are spawned with)
45
+ * @returns The line to type, or null for a shell we do not know how to quiet
46
+ */
47
+ export function shellHistoryDisableLine(shell = process.env.SHELL || '/bin/bash') {
48
+ const name = path.basename(shell || '').toLowerCase();
49
+ if (SHELL_HISTORY_CONSTANTS.POSIX_SHELLS.includes(name)) {
50
+ return quietShellLine(SHELL_HISTORY_CONSTANTS.DISABLE_COMMAND);
51
+ }
52
+ if (name === 'fish')
53
+ return quietShellLine(SHELL_HISTORY_CONSTANTS.FISH_DISABLE_COMMAND);
54
+ return null;
55
+ }
56
+ /**
57
+ * Quotes a value for a POSIX shell word (single quotes).
58
+ *
59
+ * @param value - Raw value
60
+ * @returns Single-quoted value safe for `sh -c`
61
+ */
62
+ export function shellQuote(value) {
63
+ return `'${value.replace(/'/g, `'\\''`)}'`;
64
+ }
65
+ //# sourceMappingURL=shell-history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell-history.js","sourceRoot":"","sources":["../../../../../backend/src/utils/shell-history.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,UAAU,kBAAkB;IACjC,OAAO,EAAE,GAAG,uBAAuB,CAAC,SAAS,EAAE,CAAC;AACjD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IAC1C,MAAM,MAAM,GAAG,uBAAuB,CAAC,iBAAiB,CAAC;IACzD,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,EAAE,CAAC;AAC5D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,QAA4B,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,WAAW;IACnG,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACtD,IAAI,uBAAuB,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACzD,OAAO,cAAc,CAAC,uBAAuB,CAAC,eAAe,CAAC,CAAC;IAChE,CAAC;IACD,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,cAAc,CAAC,uBAAuB,CAAC,oBAAoB,CAAC,CAAC;IACzF,OAAO,IAAI,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa;IACvC,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AAC5C,CAAC"}
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "5233785771aa0a83f6d21cb505b71ed364400071",
3
- "builtAt": "2026-09-28T23:47:02.182Z"
2
+ "commit": "3559f14554ede3b919f8d8650ced719af929e1a1",
3
+ "builtAt": "2026-09-29T05:28:20.365Z"
4
4
  }