@matthesketh/fleet 1.2.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/README.md +183 -251
  2. package/dist/adapters/detector/index.d.ts +8 -0
  3. package/dist/adapters/detector/index.js +54 -0
  4. package/dist/adapters/notifier/index.d.ts +2 -0
  5. package/dist/adapters/notifier/index.js +2 -0
  6. package/dist/adapters/notifier/stdout.d.ts +2 -0
  7. package/dist/adapters/notifier/stdout.js +8 -0
  8. package/dist/adapters/notifier/webhook.d.ts +9 -0
  9. package/dist/adapters/notifier/webhook.js +38 -0
  10. package/dist/adapters/runner/claude-cli.d.ts +7 -0
  11. package/dist/adapters/runner/claude-cli.js +231 -0
  12. package/dist/adapters/runner/mcp-call.d.ts +8 -0
  13. package/dist/adapters/runner/mcp-call.js +82 -0
  14. package/dist/adapters/runner/shell.d.ts +2 -0
  15. package/dist/adapters/runner/shell.js +103 -0
  16. package/dist/adapters/scheduler/systemd-timer.d.ts +17 -0
  17. package/dist/adapters/scheduler/systemd-timer.js +149 -0
  18. package/dist/adapters/signals/ci-status.d.ts +2 -0
  19. package/dist/adapters/signals/ci-status.js +79 -0
  20. package/dist/adapters/signals/container-up.d.ts +5 -0
  21. package/dist/adapters/signals/container-up.js +54 -0
  22. package/dist/adapters/signals/git-clean.d.ts +2 -0
  23. package/dist/adapters/signals/git-clean.js +55 -0
  24. package/dist/adapters/signals/index.d.ts +6 -0
  25. package/dist/adapters/signals/index.js +7 -0
  26. package/dist/adapters/types.d.ts +52 -0
  27. package/dist/adapters/types.js +1 -0
  28. package/dist/cli.js +46 -2
  29. package/dist/commands/add.js +0 -6
  30. package/dist/commands/boot-start.d.ts +1 -0
  31. package/dist/commands/boot-start.js +51 -0
  32. package/dist/commands/deploy.js +13 -0
  33. package/dist/commands/deps.js +5 -0
  34. package/dist/commands/egress.d.ts +1 -0
  35. package/dist/commands/egress.js +106 -0
  36. package/dist/commands/freeze.d.ts +4 -0
  37. package/dist/commands/freeze.js +64 -0
  38. package/dist/commands/guard.d.ts +1 -0
  39. package/dist/commands/guard.js +144 -0
  40. package/dist/commands/logs.d.ts +1 -1
  41. package/dist/commands/logs.js +237 -8
  42. package/dist/commands/patch-systemd.d.ts +1 -0
  43. package/dist/commands/patch-systemd.js +126 -0
  44. package/dist/commands/rollback.d.ts +1 -0
  45. package/dist/commands/rollback.js +58 -0
  46. package/dist/commands/routine-run.d.ts +1 -0
  47. package/dist/commands/routine-run.js +122 -0
  48. package/dist/commands/routines.d.ts +1 -0
  49. package/dist/commands/routines.js +25 -0
  50. package/dist/commands/secrets.js +449 -16
  51. package/dist/commands/status.js +7 -3
  52. package/dist/commands/watchdog.d.ts +1 -1
  53. package/dist/commands/watchdog.js +16 -40
  54. package/dist/core/boot-refresh.d.ts +57 -0
  55. package/dist/core/boot-refresh.js +116 -0
  56. package/dist/core/deps/actors/pr-creator.js +11 -9
  57. package/dist/core/deps/collectors/docker-running.js +2 -2
  58. package/dist/core/deps/collectors/github-pr.js +5 -2
  59. package/dist/core/deps/collectors/npm.js +10 -5
  60. package/dist/core/deps/collectors/vulnerability.js +10 -6
  61. package/dist/core/deps/reporters/motd.js +1 -1
  62. package/dist/core/deps/reporters/telegram.js +2 -29
  63. package/dist/core/docker.js +45 -15
  64. package/dist/core/egress.d.ts +41 -0
  65. package/dist/core/egress.js +161 -0
  66. package/dist/core/exec.d.ts +7 -1
  67. package/dist/core/exec.js +25 -17
  68. package/dist/core/git.d.ts +1 -0
  69. package/dist/core/git.js +36 -23
  70. package/dist/core/github.js +27 -8
  71. package/dist/core/health.d.ts +3 -0
  72. package/dist/core/health.js +15 -3
  73. package/dist/core/logs-multi.d.ts +73 -0
  74. package/dist/core/logs-multi.js +163 -0
  75. package/dist/core/logs-policy.d.ts +55 -0
  76. package/dist/core/logs-policy.js +148 -0
  77. package/dist/core/nginx.js +8 -4
  78. package/dist/core/notify.d.ts +15 -0
  79. package/dist/core/notify.js +55 -0
  80. package/dist/core/registry.d.ts +25 -0
  81. package/dist/core/registry.js +57 -10
  82. package/dist/core/routines/cost-queries.d.ts +24 -0
  83. package/dist/core/routines/cost-queries.js +65 -0
  84. package/dist/core/routines/db.d.ts +9 -0
  85. package/dist/core/routines/db.js +126 -0
  86. package/dist/core/routines/defaults.d.ts +2 -0
  87. package/dist/core/routines/defaults.js +72 -0
  88. package/dist/core/routines/engine.d.ts +59 -0
  89. package/dist/core/routines/engine.js +175 -0
  90. package/dist/core/routines/incidents.d.ts +13 -0
  91. package/dist/core/routines/incidents.js +35 -0
  92. package/dist/core/routines/schema.d.ts +418 -0
  93. package/dist/core/routines/schema.js +113 -0
  94. package/dist/core/routines/signals-collector.d.ts +35 -0
  95. package/dist/core/routines/signals-collector.js +114 -0
  96. package/dist/core/routines/store.d.ts +316 -0
  97. package/dist/core/routines/store.js +99 -0
  98. package/dist/core/routines/test-utils.d.ts +2 -0
  99. package/dist/core/routines/test-utils.js +13 -0
  100. package/dist/core/secrets-audit.d.ts +21 -0
  101. package/dist/core/secrets-audit.js +60 -0
  102. package/dist/core/secrets-metadata.d.ts +39 -0
  103. package/dist/core/secrets-metadata.js +82 -0
  104. package/dist/core/secrets-motd.d.ts +20 -0
  105. package/dist/core/secrets-motd.js +72 -0
  106. package/dist/core/secrets-ops.d.ts +3 -1
  107. package/dist/core/secrets-ops.js +78 -13
  108. package/dist/core/secrets-providers.d.ts +50 -0
  109. package/dist/core/secrets-providers.js +291 -0
  110. package/dist/core/secrets-rotation.d.ts +52 -0
  111. package/dist/core/secrets-rotation.js +165 -0
  112. package/dist/core/secrets-snapshots.d.ts +26 -0
  113. package/dist/core/secrets-snapshots.js +95 -0
  114. package/dist/core/secrets-validate.js +2 -1
  115. package/dist/core/secrets.d.ts +12 -1
  116. package/dist/core/secrets.js +35 -24
  117. package/dist/core/self-update.d.ts +41 -0
  118. package/dist/core/self-update.js +73 -0
  119. package/dist/core/systemd.js +29 -12
  120. package/dist/core/telegram.d.ts +6 -0
  121. package/dist/core/telegram.js +32 -0
  122. package/dist/core/validate.d.ts +7 -0
  123. package/dist/core/validate.js +42 -0
  124. package/dist/index.js +0 -4
  125. package/dist/mcp/deps-tools.js +9 -1
  126. package/dist/mcp/git-tools.js +4 -4
  127. package/dist/mcp/server.js +193 -8
  128. package/dist/templates/systemd.js +3 -3
  129. package/dist/templates/unseal.js +5 -1
  130. package/dist/tui/components/KeyHint.js +10 -0
  131. package/dist/tui/exec-bridge.js +26 -12
  132. package/dist/tui/hooks/use-fleet-data.js +5 -2
  133. package/dist/tui/hooks/use-health.js +5 -2
  134. package/dist/tui/router.js +60 -7
  135. package/dist/tui/routines/RoutinesApp.d.ts +8 -0
  136. package/dist/tui/routines/RoutinesApp.js +277 -0
  137. package/dist/tui/routines/components/AlertsPanel.d.ts +7 -0
  138. package/dist/tui/routines/components/AlertsPanel.js +22 -0
  139. package/dist/tui/routines/components/AlertsPanel.test.d.ts +1 -0
  140. package/dist/tui/routines/components/AlertsPanel.test.js +52 -0
  141. package/dist/tui/routines/components/CommandPalette.d.ts +12 -0
  142. package/dist/tui/routines/components/CommandPalette.js +21 -0
  143. package/dist/tui/routines/components/LiveRunPanel.d.ts +12 -0
  144. package/dist/tui/routines/components/LiveRunPanel.js +107 -0
  145. package/dist/tui/routines/components/RoutineForm.d.ts +8 -0
  146. package/dist/tui/routines/components/RoutineForm.js +254 -0
  147. package/dist/tui/routines/components/SignalsGrid.d.ts +13 -0
  148. package/dist/tui/routines/components/SignalsGrid.js +34 -0
  149. package/dist/tui/routines/components/SignalsGrid.test.d.ts +1 -0
  150. package/dist/tui/routines/components/SignalsGrid.test.js +43 -0
  151. package/dist/tui/routines/format.d.ts +7 -0
  152. package/dist/tui/routines/format.js +51 -0
  153. package/dist/tui/routines/hooks/use-git-fleet.d.ts +33 -0
  154. package/dist/tui/routines/hooks/use-git-fleet.js +82 -0
  155. package/dist/tui/routines/hooks/use-logs-stream.d.ts +13 -0
  156. package/dist/tui/routines/hooks/use-logs-stream.js +64 -0
  157. package/dist/tui/routines/hooks/use-ops-fleet.d.ts +20 -0
  158. package/dist/tui/routines/hooks/use-ops-fleet.js +70 -0
  159. package/dist/tui/routines/hooks/use-repo-detail.d.ts +31 -0
  160. package/dist/tui/routines/hooks/use-repo-detail.js +104 -0
  161. package/dist/tui/routines/hooks/use-security.d.ts +33 -0
  162. package/dist/tui/routines/hooks/use-security.js +110 -0
  163. package/dist/tui/routines/hooks/use-signals.d.ts +9 -0
  164. package/dist/tui/routines/hooks/use-signals.js +60 -0
  165. package/dist/tui/routines/runtime.d.ts +20 -0
  166. package/dist/tui/routines/runtime.js +40 -0
  167. package/dist/tui/routines/tabs/CostTab.d.ts +7 -0
  168. package/dist/tui/routines/tabs/CostTab.js +24 -0
  169. package/dist/tui/routines/tabs/DashboardTab.d.ts +15 -0
  170. package/dist/tui/routines/tabs/DashboardTab.js +10 -0
  171. package/dist/tui/routines/tabs/GitTab.d.ts +6 -0
  172. package/dist/tui/routines/tabs/GitTab.js +39 -0
  173. package/dist/tui/routines/tabs/LogsTab.d.ts +6 -0
  174. package/dist/tui/routines/tabs/LogsTab.js +58 -0
  175. package/dist/tui/routines/tabs/OpsTab.d.ts +6 -0
  176. package/dist/tui/routines/tabs/OpsTab.js +34 -0
  177. package/dist/tui/routines/tabs/RepoDetailView.d.ts +6 -0
  178. package/dist/tui/routines/tabs/RepoDetailView.js +12 -0
  179. package/dist/tui/routines/tabs/RoutinesTab.d.ts +10 -0
  180. package/dist/tui/routines/tabs/RoutinesTab.js +58 -0
  181. package/dist/tui/routines/tabs/ScaffoldTab.d.ts +2 -0
  182. package/dist/tui/routines/tabs/ScaffoldTab.js +127 -0
  183. package/dist/tui/routines/tabs/SecurityTab.d.ts +6 -0
  184. package/dist/tui/routines/tabs/SecurityTab.js +31 -0
  185. package/dist/tui/routines/tabs/SettingsTab.d.ts +6 -0
  186. package/dist/tui/routines/tabs/SettingsTab.js +61 -0
  187. package/dist/tui/routines/tabs/TimelineTab.d.ts +7 -0
  188. package/dist/tui/routines/tabs/TimelineTab.js +26 -0
  189. package/dist/tui/state.js +1 -1
  190. package/dist/tui/tests/keyboard-integration.test.js +3 -0
  191. package/dist/tui/tests/test-app.js +1 -1
  192. package/dist/tui/types.d.ts +2 -2
  193. package/dist/tui/views/AppDetail.js +3 -4
  194. package/dist/tui/views/HealthView.js +7 -1
  195. package/dist/tui/views/LogsView.js +24 -1
  196. package/dist/tui/views/MultiLogsView.d.ts +2 -0
  197. package/dist/tui/views/MultiLogsView.js +165 -0
  198. package/dist/tui/views/SecretEdit.js +10 -3
  199. package/dist/tui/views/SecretsView.js +6 -3
  200. package/dist/ui/prompt.d.ts +52 -0
  201. package/dist/ui/prompt.js +169 -0
  202. package/package.json +34 -21
  203. package/scripts/guard/cert-expiry-watch +109 -0
  204. package/scripts/guard/cf-audit-monitor +169 -0
  205. package/scripts/guard/cf-snapshot +124 -0
  206. package/scripts/guard/cron.d-cf-protect +11 -0
  207. package/scripts/guard/dns-drift-watch +138 -0
  208. package/scripts/guard/fleet-guard +282 -0
  209. package/scripts/guard/fleet-guard-execute +197 -0
  210. package/scripts/guard/notify +108 -0
  211. package/dist/commands/motd.d.ts +0 -1
  212. package/dist/commands/motd.js +0 -10
  213. package/dist/templates/motd.d.ts +0 -1
  214. package/dist/templates/motd.js +0 -7
  215. package/dist/tui/components/AppList.d.ts +0 -12
  216. package/dist/tui/components/AppList.js +0 -32
  217. package/dist/tui/hooks/use-keyboard.d.ts +0 -1
  218. package/dist/tui/hooks/use-keyboard.js +0 -44
@@ -0,0 +1,418 @@
1
+ import { z } from 'zod';
2
+ export declare const RoutineTaskSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
3
+ kind: z.ZodLiteral<"claude-cli">;
4
+ prompt: z.ZodString;
5
+ outputFormat: z.ZodDefault<z.ZodLiteral<"json">>;
6
+ tokenCap: z.ZodDefault<z.ZodNumber>;
7
+ wallClockMs: z.ZodDefault<z.ZodNumber>;
8
+ maxUsd: z.ZodDefault<z.ZodNumber>;
9
+ model: z.ZodOptional<z.ZodString>;
10
+ appendSystem: z.ZodOptional<z.ZodString>;
11
+ allowedTools: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ kind: "claude-cli";
14
+ prompt: string;
15
+ outputFormat: "json";
16
+ tokenCap: number;
17
+ wallClockMs: number;
18
+ maxUsd: number;
19
+ model?: string | undefined;
20
+ appendSystem?: string | undefined;
21
+ allowedTools?: string[] | undefined;
22
+ }, {
23
+ kind: "claude-cli";
24
+ prompt: string;
25
+ outputFormat?: "json" | undefined;
26
+ tokenCap?: number | undefined;
27
+ wallClockMs?: number | undefined;
28
+ maxUsd?: number | undefined;
29
+ model?: string | undefined;
30
+ appendSystem?: string | undefined;
31
+ allowedTools?: string[] | undefined;
32
+ }>, z.ZodObject<{
33
+ kind: z.ZodLiteral<"shell">;
34
+ argv: z.ZodArray<z.ZodString, "many">;
35
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
36
+ wallClockMs: z.ZodDefault<z.ZodNumber>;
37
+ }, "strip", z.ZodTypeAny, {
38
+ kind: "shell";
39
+ wallClockMs: number;
40
+ argv: string[];
41
+ env?: Record<string, string> | undefined;
42
+ }, {
43
+ kind: "shell";
44
+ argv: string[];
45
+ env?: Record<string, string> | undefined;
46
+ wallClockMs?: number | undefined;
47
+ }>, z.ZodObject<{
48
+ kind: z.ZodLiteral<"mcp-call">;
49
+ tool: z.ZodString;
50
+ args: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
51
+ wallClockMs: z.ZodDefault<z.ZodNumber>;
52
+ }, "strip", z.ZodTypeAny, {
53
+ kind: "mcp-call";
54
+ wallClockMs: number;
55
+ tool: string;
56
+ args: Record<string, unknown>;
57
+ }, {
58
+ kind: "mcp-call";
59
+ tool: string;
60
+ wallClockMs?: number | undefined;
61
+ args?: Record<string, unknown> | undefined;
62
+ }>]>;
63
+ export type RoutineTask = z.infer<typeof RoutineTaskSchema>;
64
+ export declare const NotifyConfigSchema: z.ZodObject<{
65
+ kind: z.ZodEnum<["stdout", "webhook", "slack", "email"]>;
66
+ on: z.ZodDefault<z.ZodEnum<["always", "failure", "success"]>>;
67
+ config: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
68
+ }, "strip", z.ZodTypeAny, {
69
+ config: Record<string, unknown>;
70
+ kind: "email" | "stdout" | "webhook" | "slack";
71
+ on: "always" | "failure" | "success";
72
+ }, {
73
+ kind: "email" | "stdout" | "webhook" | "slack";
74
+ config?: Record<string, unknown> | undefined;
75
+ on?: "always" | "failure" | "success" | undefined;
76
+ }>;
77
+ export declare const RoutineScheduleSchema: z.ZodUnion<[z.ZodObject<{
78
+ kind: z.ZodLiteral<"manual">;
79
+ }, "strip", z.ZodTypeAny, {
80
+ kind: "manual";
81
+ }, {
82
+ kind: "manual";
83
+ }>, z.ZodObject<{
84
+ kind: z.ZodLiteral<"calendar">;
85
+ onCalendar: z.ZodString;
86
+ randomizedDelaySec: z.ZodDefault<z.ZodNumber>;
87
+ persistent: z.ZodDefault<z.ZodBoolean>;
88
+ }, "strip", z.ZodTypeAny, {
89
+ kind: "calendar";
90
+ onCalendar: string;
91
+ randomizedDelaySec: number;
92
+ persistent: boolean;
93
+ }, {
94
+ kind: "calendar";
95
+ onCalendar: string;
96
+ randomizedDelaySec?: number | undefined;
97
+ persistent?: boolean | undefined;
98
+ }>]>;
99
+ export type RoutineSchedule = z.infer<typeof RoutineScheduleSchema>;
100
+ export declare const RoutineSchema: z.ZodObject<{
101
+ id: z.ZodString;
102
+ name: z.ZodString;
103
+ description: z.ZodDefault<z.ZodString>;
104
+ schedule: z.ZodUnion<[z.ZodObject<{
105
+ kind: z.ZodLiteral<"manual">;
106
+ }, "strip", z.ZodTypeAny, {
107
+ kind: "manual";
108
+ }, {
109
+ kind: "manual";
110
+ }>, z.ZodObject<{
111
+ kind: z.ZodLiteral<"calendar">;
112
+ onCalendar: z.ZodString;
113
+ randomizedDelaySec: z.ZodDefault<z.ZodNumber>;
114
+ persistent: z.ZodDefault<z.ZodBoolean>;
115
+ }, "strip", z.ZodTypeAny, {
116
+ kind: "calendar";
117
+ onCalendar: string;
118
+ randomizedDelaySec: number;
119
+ persistent: boolean;
120
+ }, {
121
+ kind: "calendar";
122
+ onCalendar: string;
123
+ randomizedDelaySec?: number | undefined;
124
+ persistent?: boolean | undefined;
125
+ }>]>;
126
+ enabled: z.ZodDefault<z.ZodBoolean>;
127
+ targets: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
128
+ perTarget: z.ZodDefault<z.ZodBoolean>;
129
+ task: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
130
+ kind: z.ZodLiteral<"claude-cli">;
131
+ prompt: z.ZodString;
132
+ outputFormat: z.ZodDefault<z.ZodLiteral<"json">>;
133
+ tokenCap: z.ZodDefault<z.ZodNumber>;
134
+ wallClockMs: z.ZodDefault<z.ZodNumber>;
135
+ maxUsd: z.ZodDefault<z.ZodNumber>;
136
+ model: z.ZodOptional<z.ZodString>;
137
+ appendSystem: z.ZodOptional<z.ZodString>;
138
+ allowedTools: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
139
+ }, "strip", z.ZodTypeAny, {
140
+ kind: "claude-cli";
141
+ prompt: string;
142
+ outputFormat: "json";
143
+ tokenCap: number;
144
+ wallClockMs: number;
145
+ maxUsd: number;
146
+ model?: string | undefined;
147
+ appendSystem?: string | undefined;
148
+ allowedTools?: string[] | undefined;
149
+ }, {
150
+ kind: "claude-cli";
151
+ prompt: string;
152
+ outputFormat?: "json" | undefined;
153
+ tokenCap?: number | undefined;
154
+ wallClockMs?: number | undefined;
155
+ maxUsd?: number | undefined;
156
+ model?: string | undefined;
157
+ appendSystem?: string | undefined;
158
+ allowedTools?: string[] | undefined;
159
+ }>, z.ZodObject<{
160
+ kind: z.ZodLiteral<"shell">;
161
+ argv: z.ZodArray<z.ZodString, "many">;
162
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
163
+ wallClockMs: z.ZodDefault<z.ZodNumber>;
164
+ }, "strip", z.ZodTypeAny, {
165
+ kind: "shell";
166
+ wallClockMs: number;
167
+ argv: string[];
168
+ env?: Record<string, string> | undefined;
169
+ }, {
170
+ kind: "shell";
171
+ argv: string[];
172
+ env?: Record<string, string> | undefined;
173
+ wallClockMs?: number | undefined;
174
+ }>, z.ZodObject<{
175
+ kind: z.ZodLiteral<"mcp-call">;
176
+ tool: z.ZodString;
177
+ args: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
178
+ wallClockMs: z.ZodDefault<z.ZodNumber>;
179
+ }, "strip", z.ZodTypeAny, {
180
+ kind: "mcp-call";
181
+ wallClockMs: number;
182
+ tool: string;
183
+ args: Record<string, unknown>;
184
+ }, {
185
+ kind: "mcp-call";
186
+ tool: string;
187
+ wallClockMs?: number | undefined;
188
+ args?: Record<string, unknown> | undefined;
189
+ }>]>;
190
+ notify: z.ZodDefault<z.ZodArray<z.ZodObject<{
191
+ kind: z.ZodEnum<["stdout", "webhook", "slack", "email"]>;
192
+ on: z.ZodDefault<z.ZodEnum<["always", "failure", "success"]>>;
193
+ config: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
194
+ }, "strip", z.ZodTypeAny, {
195
+ config: Record<string, unknown>;
196
+ kind: "email" | "stdout" | "webhook" | "slack";
197
+ on: "always" | "failure" | "success";
198
+ }, {
199
+ kind: "email" | "stdout" | "webhook" | "slack";
200
+ config?: Record<string, unknown> | undefined;
201
+ on?: "always" | "failure" | "success" | undefined;
202
+ }>, "many">>;
203
+ tags: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
204
+ createdAt: z.ZodOptional<z.ZodString>;
205
+ updatedAt: z.ZodOptional<z.ZodString>;
206
+ }, "strip", z.ZodTypeAny, {
207
+ enabled: boolean;
208
+ name: string;
209
+ id: string;
210
+ notify: {
211
+ config: Record<string, unknown>;
212
+ kind: "email" | "stdout" | "webhook" | "slack";
213
+ on: "always" | "failure" | "success";
214
+ }[];
215
+ description: string;
216
+ schedule: {
217
+ kind: "manual";
218
+ } | {
219
+ kind: "calendar";
220
+ onCalendar: string;
221
+ randomizedDelaySec: number;
222
+ persistent: boolean;
223
+ };
224
+ targets: string[];
225
+ perTarget: boolean;
226
+ task: {
227
+ kind: "claude-cli";
228
+ prompt: string;
229
+ outputFormat: "json";
230
+ tokenCap: number;
231
+ wallClockMs: number;
232
+ maxUsd: number;
233
+ model?: string | undefined;
234
+ appendSystem?: string | undefined;
235
+ allowedTools?: string[] | undefined;
236
+ } | {
237
+ kind: "shell";
238
+ wallClockMs: number;
239
+ argv: string[];
240
+ env?: Record<string, string> | undefined;
241
+ } | {
242
+ kind: "mcp-call";
243
+ wallClockMs: number;
244
+ tool: string;
245
+ args: Record<string, unknown>;
246
+ };
247
+ tags: string[];
248
+ updatedAt?: string | undefined;
249
+ createdAt?: string | undefined;
250
+ }, {
251
+ name: string;
252
+ id: string;
253
+ schedule: {
254
+ kind: "manual";
255
+ } | {
256
+ kind: "calendar";
257
+ onCalendar: string;
258
+ randomizedDelaySec?: number | undefined;
259
+ persistent?: boolean | undefined;
260
+ };
261
+ task: {
262
+ kind: "claude-cli";
263
+ prompt: string;
264
+ outputFormat?: "json" | undefined;
265
+ tokenCap?: number | undefined;
266
+ wallClockMs?: number | undefined;
267
+ maxUsd?: number | undefined;
268
+ model?: string | undefined;
269
+ appendSystem?: string | undefined;
270
+ allowedTools?: string[] | undefined;
271
+ } | {
272
+ kind: "shell";
273
+ argv: string[];
274
+ env?: Record<string, string> | undefined;
275
+ wallClockMs?: number | undefined;
276
+ } | {
277
+ kind: "mcp-call";
278
+ tool: string;
279
+ wallClockMs?: number | undefined;
280
+ args?: Record<string, unknown> | undefined;
281
+ };
282
+ enabled?: boolean | undefined;
283
+ updatedAt?: string | undefined;
284
+ notify?: {
285
+ kind: "email" | "stdout" | "webhook" | "slack";
286
+ config?: Record<string, unknown> | undefined;
287
+ on?: "always" | "failure" | "success" | undefined;
288
+ }[] | undefined;
289
+ description?: string | undefined;
290
+ targets?: string[] | undefined;
291
+ perTarget?: boolean | undefined;
292
+ tags?: string[] | undefined;
293
+ createdAt?: string | undefined;
294
+ }>;
295
+ export type Routine = z.infer<typeof RoutineSchema>;
296
+ export declare const RunStatusSchema: z.ZodEnum<["queued", "running", "ok", "failed", "timeout", "aborted"]>;
297
+ export type RunStatus = z.infer<typeof RunStatusSchema>;
298
+ export declare const RunEventSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
299
+ kind: z.ZodLiteral<"start">;
300
+ routineId: z.ZodString;
301
+ target: z.ZodNullable<z.ZodString>;
302
+ at: z.ZodString;
303
+ }, "strip", z.ZodTypeAny, {
304
+ at: string;
305
+ kind: "start";
306
+ routineId: string;
307
+ target: string | null;
308
+ }, {
309
+ at: string;
310
+ kind: "start";
311
+ routineId: string;
312
+ target: string | null;
313
+ }>, z.ZodObject<{
314
+ kind: z.ZodLiteral<"stdout">;
315
+ chunk: z.ZodString;
316
+ }, "strip", z.ZodTypeAny, {
317
+ kind: "stdout";
318
+ chunk: string;
319
+ }, {
320
+ kind: "stdout";
321
+ chunk: string;
322
+ }>, z.ZodObject<{
323
+ kind: z.ZodLiteral<"stderr">;
324
+ chunk: z.ZodString;
325
+ }, "strip", z.ZodTypeAny, {
326
+ kind: "stderr";
327
+ chunk: string;
328
+ }, {
329
+ kind: "stderr";
330
+ chunk: string;
331
+ }>, z.ZodObject<{
332
+ kind: z.ZodLiteral<"tool-call">;
333
+ name: z.ZodString;
334
+ argsPreview: z.ZodOptional<z.ZodString>;
335
+ }, "strip", z.ZodTypeAny, {
336
+ name: string;
337
+ kind: "tool-call";
338
+ argsPreview?: string | undefined;
339
+ }, {
340
+ name: string;
341
+ kind: "tool-call";
342
+ argsPreview?: string | undefined;
343
+ }>, z.ZodObject<{
344
+ kind: z.ZodLiteral<"cost">;
345
+ inputTokens: z.ZodNumber;
346
+ outputTokens: z.ZodNumber;
347
+ cacheCreateTokens: z.ZodDefault<z.ZodNumber>;
348
+ cacheReadTokens: z.ZodDefault<z.ZodNumber>;
349
+ usd: z.ZodNumber;
350
+ }, "strip", z.ZodTypeAny, {
351
+ kind: "cost";
352
+ inputTokens: number;
353
+ outputTokens: number;
354
+ cacheCreateTokens: number;
355
+ cacheReadTokens: number;
356
+ usd: number;
357
+ }, {
358
+ kind: "cost";
359
+ inputTokens: number;
360
+ outputTokens: number;
361
+ usd: number;
362
+ cacheCreateTokens?: number | undefined;
363
+ cacheReadTokens?: number | undefined;
364
+ }>, z.ZodObject<{
365
+ kind: z.ZodLiteral<"end">;
366
+ status: z.ZodEnum<["queued", "running", "ok", "failed", "timeout", "aborted"]>;
367
+ exitCode: z.ZodNumber;
368
+ durationMs: z.ZodNumber;
369
+ at: z.ZodString;
370
+ error: z.ZodOptional<z.ZodString>;
371
+ }, "strip", z.ZodTypeAny, {
372
+ at: string;
373
+ status: "timeout" | "ok" | "failed" | "aborted" | "queued" | "running";
374
+ kind: "end";
375
+ exitCode: number;
376
+ durationMs: number;
377
+ error?: string | undefined;
378
+ }, {
379
+ at: string;
380
+ status: "timeout" | "ok" | "failed" | "aborted" | "queued" | "running";
381
+ kind: "end";
382
+ exitCode: number;
383
+ durationMs: number;
384
+ error?: string | undefined;
385
+ }>]>;
386
+ export type RunEvent = z.infer<typeof RunEventSchema>;
387
+ export declare const SignalKindSchema: z.ZodEnum<["git-clean", "git-ahead", "git-behind", "open-prs", "pr-age-max", "deps-outdated", "deps-vulns", "build-ok", "tests-ok", "env-schema-ok", "container-up", "ci-status", "cache-age"]>;
388
+ export type SignalKind = z.infer<typeof SignalKindSchema>;
389
+ export declare const SignalStateSchema: z.ZodEnum<["ok", "warn", "error", "unknown"]>;
390
+ export type SignalState = z.infer<typeof SignalStateSchema>;
391
+ export declare const SignalSchema: z.ZodObject<{
392
+ repo: z.ZodString;
393
+ kind: z.ZodEnum<["git-clean", "git-ahead", "git-behind", "open-prs", "pr-age-max", "deps-outdated", "deps-vulns", "build-ok", "tests-ok", "env-schema-ok", "container-up", "ci-status", "cache-age"]>;
394
+ state: z.ZodEnum<["ok", "warn", "error", "unknown"]>;
395
+ value: z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>;
396
+ detail: z.ZodDefault<z.ZodString>;
397
+ collectedAt: z.ZodString;
398
+ ttlMs: z.ZodNumber;
399
+ }, "strip", z.ZodTypeAny, {
400
+ detail: string;
401
+ kind: "git-clean" | "git-ahead" | "git-behind" | "open-prs" | "pr-age-max" | "deps-outdated" | "deps-vulns" | "build-ok" | "tests-ok" | "env-schema-ok" | "container-up" | "ci-status" | "cache-age";
402
+ value: string | number | boolean | null;
403
+ repo: string;
404
+ state: "warn" | "error" | "unknown" | "ok";
405
+ collectedAt: string;
406
+ ttlMs: number;
407
+ }, {
408
+ kind: "git-clean" | "git-ahead" | "git-behind" | "open-prs" | "pr-age-max" | "deps-outdated" | "deps-vulns" | "build-ok" | "tests-ok" | "env-schema-ok" | "container-up" | "ci-status" | "cache-age";
409
+ value: string | number | boolean | null;
410
+ repo: string;
411
+ state: "warn" | "error" | "unknown" | "ok";
412
+ collectedAt: string;
413
+ ttlMs: number;
414
+ detail?: string | undefined;
415
+ }>;
416
+ export type Signal = z.infer<typeof SignalSchema>;
417
+ export declare function validateRoutine(input: unknown): Routine;
418
+ export declare function isExpired(signal: Signal, now?: number): boolean;
@@ -0,0 +1,113 @@
1
+ import { z } from 'zod';
2
+ const ROUTINE_ID_REGEX = /^[a-z][a-z0-9-]{0,62}$/;
3
+ const NO_SHELL_META = /^[^`$;&|><\n\r\\"]*$/;
4
+ const DEFAULT_WALLCLOCK_MS = 15 * 60 * 1000;
5
+ const DEFAULT_TOKEN_CAP = 100_000;
6
+ const DEFAULT_MAX_USD = 5;
7
+ export const RoutineTaskSchema = z.discriminatedUnion('kind', [
8
+ z.object({
9
+ kind: z.literal('claude-cli'),
10
+ prompt: z.string().min(1).max(8000),
11
+ outputFormat: z.literal('json').default('json'),
12
+ tokenCap: z.number().int().positive().max(1_000_000).default(DEFAULT_TOKEN_CAP),
13
+ wallClockMs: z.number().int().positive().max(60 * 60 * 1000).default(DEFAULT_WALLCLOCK_MS),
14
+ maxUsd: z.number().positive().max(100).default(DEFAULT_MAX_USD),
15
+ model: z.string().optional(),
16
+ appendSystem: z.string().max(2000).optional(),
17
+ allowedTools: z.array(z.string().regex(/^[A-Za-z0-9_:*\-]+$/)).optional(),
18
+ }),
19
+ z.object({
20
+ kind: z.literal('shell'),
21
+ argv: z.array(z.string().min(1).regex(NO_SHELL_META)).min(1).max(64),
22
+ env: z.record(z.string().regex(/^[A-Z_][A-Z0-9_]*$/), z.string()).optional(),
23
+ wallClockMs: z.number().int().positive().max(60 * 60 * 1000).default(DEFAULT_WALLCLOCK_MS),
24
+ }),
25
+ z.object({
26
+ kind: z.literal('mcp-call'),
27
+ tool: z.string().regex(/^[a-z][a-z0-9_.-]*$/i),
28
+ args: z.record(z.string(), z.unknown()).default({}),
29
+ wallClockMs: z.number().int().positive().max(60 * 60 * 1000).default(DEFAULT_WALLCLOCK_MS),
30
+ }),
31
+ ]);
32
+ export const NotifyConfigSchema = z.object({
33
+ kind: z.enum(['stdout', 'webhook', 'slack', 'email']),
34
+ on: z.enum(['always', 'failure', 'success']).default('failure'),
35
+ config: z.record(z.string(), z.unknown()).default({}),
36
+ });
37
+ export const RoutineScheduleSchema = z.union([
38
+ z.object({ kind: z.literal('manual') }),
39
+ z.object({
40
+ kind: z.literal('calendar'),
41
+ onCalendar: z.string().min(1).max(200),
42
+ randomizedDelaySec: z.number().int().nonnegative().max(3600).default(0),
43
+ persistent: z.boolean().default(true),
44
+ }),
45
+ ]);
46
+ export const RoutineSchema = z.object({
47
+ id: z.string().regex(ROUTINE_ID_REGEX, 'lowercase alphanumeric and dashes only'),
48
+ name: z.string().min(1).max(100),
49
+ description: z.string().max(2000).default(''),
50
+ schedule: RoutineScheduleSchema,
51
+ enabled: z.boolean().default(true),
52
+ targets: z.array(z.string().min(1)).default([]),
53
+ perTarget: z.boolean().default(false),
54
+ task: RoutineTaskSchema,
55
+ notify: z.array(NotifyConfigSchema).default([]),
56
+ tags: z.array(z.string().max(32)).max(16).default([]),
57
+ createdAt: z.string().datetime().optional(),
58
+ updatedAt: z.string().datetime().optional(),
59
+ });
60
+ export const RunStatusSchema = z.enum(['queued', 'running', 'ok', 'failed', 'timeout', 'aborted']);
61
+ export const RunEventSchema = z.discriminatedUnion('kind', [
62
+ z.object({ kind: z.literal('start'), routineId: z.string(), target: z.string().nullable(), at: z.string().datetime() }),
63
+ z.object({ kind: z.literal('stdout'), chunk: z.string() }),
64
+ z.object({ kind: z.literal('stderr'), chunk: z.string() }),
65
+ z.object({ kind: z.literal('tool-call'), name: z.string(), argsPreview: z.string().max(500).optional() }),
66
+ z.object({
67
+ kind: z.literal('cost'),
68
+ inputTokens: z.number().int().nonnegative(),
69
+ outputTokens: z.number().int().nonnegative(),
70
+ cacheCreateTokens: z.number().int().nonnegative().default(0),
71
+ cacheReadTokens: z.number().int().nonnegative().default(0),
72
+ usd: z.number().nonnegative(),
73
+ }),
74
+ z.object({
75
+ kind: z.literal('end'),
76
+ status: RunStatusSchema,
77
+ exitCode: z.number().int(),
78
+ durationMs: z.number().int().nonnegative(),
79
+ at: z.string().datetime(),
80
+ error: z.string().optional(),
81
+ }),
82
+ ]);
83
+ export const SignalKindSchema = z.enum([
84
+ 'git-clean',
85
+ 'git-ahead',
86
+ 'git-behind',
87
+ 'open-prs',
88
+ 'pr-age-max',
89
+ 'deps-outdated',
90
+ 'deps-vulns',
91
+ 'build-ok',
92
+ 'tests-ok',
93
+ 'env-schema-ok',
94
+ 'container-up',
95
+ 'ci-status',
96
+ 'cache-age',
97
+ ]);
98
+ export const SignalStateSchema = z.enum(['ok', 'warn', 'error', 'unknown']);
99
+ export const SignalSchema = z.object({
100
+ repo: z.string(),
101
+ kind: SignalKindSchema,
102
+ state: SignalStateSchema,
103
+ value: z.union([z.string(), z.number(), z.boolean(), z.null()]),
104
+ detail: z.string().default(''),
105
+ collectedAt: z.string().datetime(),
106
+ ttlMs: z.number().int().nonnegative(),
107
+ });
108
+ export function validateRoutine(input) {
109
+ return RoutineSchema.parse(input);
110
+ }
111
+ export function isExpired(signal, now = Date.now()) {
112
+ return new Date(signal.collectedAt).getTime() + signal.ttlMs <= now;
113
+ }
@@ -0,0 +1,35 @@
1
+ import type Database from 'better-sqlite3';
2
+ import type { SignalProvider } from '../../adapters/types.js';
3
+ import type { Signal, SignalKind } from './schema.js';
4
+ export interface SignalTarget {
5
+ repoName: string;
6
+ repoPath: string;
7
+ }
8
+ export interface CollectorOptions {
9
+ providers: readonly SignalProvider[];
10
+ db: Database.Database;
11
+ concurrency?: number;
12
+ now?: () => number;
13
+ }
14
+ export interface CollectRequest {
15
+ target: SignalTarget;
16
+ kinds?: readonly SignalKind[];
17
+ force?: boolean;
18
+ }
19
+ export interface CollectSummary {
20
+ total: number;
21
+ fromCache: number;
22
+ collected: number;
23
+ errors: number;
24
+ }
25
+ export declare class SignalCollector {
26
+ private readonly providers;
27
+ private readonly db;
28
+ private readonly now;
29
+ private readonly concurrency;
30
+ constructor(opts: CollectorOptions);
31
+ readCached(repoName: string, kinds?: readonly SignalKind[]): Signal[];
32
+ private persist;
33
+ collect(requests: readonly CollectRequest[]): Promise<CollectSummary>;
34
+ snapshot(targets: readonly SignalTarget[]): Promise<Map<string, Signal[]>>;
35
+ }
@@ -0,0 +1,114 @@
1
+ function isFresh(signal, nowMs) {
2
+ return new Date(signal.collectedAt).getTime() + signal.ttlMs > nowMs;
3
+ }
4
+ function rowToSignal(row) {
5
+ let parsed = null;
6
+ if (row.value !== null) {
7
+ try {
8
+ parsed = JSON.parse(row.value);
9
+ }
10
+ catch {
11
+ parsed = row.value;
12
+ }
13
+ }
14
+ return {
15
+ repo: row.repo,
16
+ kind: row.kind,
17
+ state: row.state,
18
+ value: parsed,
19
+ detail: row.detail,
20
+ collectedAt: row.collected_at,
21
+ ttlMs: row.ttl_ms,
22
+ };
23
+ }
24
+ export class SignalCollector {
25
+ providers;
26
+ db;
27
+ now;
28
+ concurrency;
29
+ constructor(opts) {
30
+ this.providers = new Map(opts.providers.map(p => [p.kind, p]));
31
+ this.db = opts.db;
32
+ this.now = opts.now ?? (() => Date.now());
33
+ this.concurrency = opts.concurrency ?? 4;
34
+ }
35
+ readCached(repoName, kinds) {
36
+ const params = [repoName];
37
+ let sql = 'SELECT repo, kind, state, value, detail, collected_at, ttl_ms FROM signal_cache WHERE repo = ?';
38
+ if (kinds && kinds.length > 0) {
39
+ sql += ` AND kind IN (${kinds.map(() => '?').join(', ')})`;
40
+ params.push(...kinds);
41
+ }
42
+ const rows = this.db.prepare(sql).all(...params);
43
+ return rows.map(rowToSignal);
44
+ }
45
+ persist(signal) {
46
+ const stmt = this.db.prepare(`
47
+ INSERT INTO signal_cache (repo, kind, state, value, detail, collected_at, ttl_ms)
48
+ VALUES (?, ?, ?, ?, ?, ?, ?)
49
+ ON CONFLICT(repo, kind) DO UPDATE SET
50
+ state = excluded.state,
51
+ value = excluded.value,
52
+ detail = excluded.detail,
53
+ collected_at = excluded.collected_at,
54
+ ttl_ms = excluded.ttl_ms
55
+ `);
56
+ stmt.run(signal.repo, signal.kind, signal.state, signal.value === null ? null : JSON.stringify(signal.value), signal.detail, signal.collectedAt, signal.ttlMs);
57
+ this.db.prepare(`
58
+ INSERT INTO signal_history (repo, kind, state, value, detail, collected_at)
59
+ VALUES (?, ?, ?, ?, ?, ?)
60
+ `).run(signal.repo, signal.kind, signal.state, signal.value === null ? null : JSON.stringify(signal.value), signal.detail, signal.collectedAt);
61
+ }
62
+ async collect(requests) {
63
+ const summary = { total: 0, fromCache: 0, collected: 0, errors: 0 };
64
+ const queue = [];
65
+ const nowMs = this.now();
66
+ for (const req of requests) {
67
+ const kinds = req.kinds ?? Array.from(this.providers.keys());
68
+ const cached = req.force ? [] : this.readCached(req.target.repoName, kinds);
69
+ const cachedByKind = new Map(cached.map(s => [s.kind, s]));
70
+ for (const kind of kinds) {
71
+ summary.total++;
72
+ const c = cachedByKind.get(kind);
73
+ if (c && isFresh(c, nowMs)) {
74
+ summary.fromCache++;
75
+ continue;
76
+ }
77
+ const provider = this.providers.get(kind);
78
+ if (!provider)
79
+ continue;
80
+ queue.push({ req, kind, provider });
81
+ }
82
+ }
83
+ let i = 0;
84
+ const workers = [];
85
+ for (let w = 0; w < Math.min(this.concurrency, queue.length); w++) {
86
+ workers.push((async () => {
87
+ while (true) {
88
+ const idx = i++;
89
+ if (idx >= queue.length)
90
+ return;
91
+ const { req, provider } = queue[idx];
92
+ try {
93
+ const signal = await provider.collect(req.target.repoPath, req.target.repoName);
94
+ this.persist(signal);
95
+ summary.collected++;
96
+ }
97
+ catch {
98
+ summary.errors++;
99
+ }
100
+ }
101
+ })());
102
+ }
103
+ await Promise.all(workers);
104
+ return summary;
105
+ }
106
+ async snapshot(targets) {
107
+ await this.collect(targets.map(target => ({ target })));
108
+ const result = new Map();
109
+ for (const target of targets) {
110
+ result.set(target.repoName, this.readCached(target.repoName));
111
+ }
112
+ return result;
113
+ }
114
+ }