@provos/ironcurtain 0.5.1 → 0.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 (217) hide show
  1. package/README.md +85 -66
  2. package/dist/cli-help.d.ts +63 -0
  3. package/dist/cli-help.js +102 -0
  4. package/dist/cli-help.js.map +1 -0
  5. package/dist/cli.js +78 -52
  6. package/dist/cli.js.map +1 -1
  7. package/dist/config/config-command.js +136 -2
  8. package/dist/config/config-command.js.map +1 -1
  9. package/dist/config/generated/tool-annotations.json +487 -338
  10. package/dist/config/index.d.ts +21 -1
  11. package/dist/config/index.js +12 -4
  12. package/dist/config/index.js.map +1 -1
  13. package/dist/config/mcp-servers.json +2 -8
  14. package/dist/config/paths.d.ts +30 -0
  15. package/dist/config/paths.js +58 -0
  16. package/dist/config/paths.js.map +1 -1
  17. package/dist/config/tool-description-hints.json +15 -0
  18. package/dist/config/types.d.ts +13 -0
  19. package/dist/config/user-config.d.ts +21 -0
  20. package/dist/config/user-config.js +8 -0
  21. package/dist/config/user-config.js.map +1 -1
  22. package/dist/cron/compile-task-policy.d.ts +17 -0
  23. package/dist/cron/compile-task-policy.js +39 -0
  24. package/dist/cron/compile-task-policy.js.map +1 -0
  25. package/dist/cron/cron-scheduler.d.ts +65 -0
  26. package/dist/cron/cron-scheduler.js +184 -0
  27. package/dist/cron/cron-scheduler.js.map +1 -0
  28. package/dist/cron/file-lock.d.ts +41 -0
  29. package/dist/cron/file-lock.js +156 -0
  30. package/dist/cron/file-lock.js.map +1 -0
  31. package/dist/cron/format-utils.d.ts +18 -0
  32. package/dist/cron/format-utils.js +145 -0
  33. package/dist/cron/format-utils.js.map +1 -0
  34. package/dist/cron/git-sync.d.ts +22 -0
  35. package/dist/cron/git-sync.js +121 -0
  36. package/dist/cron/git-sync.js.map +1 -0
  37. package/dist/cron/headless-transport.d.ts +27 -0
  38. package/dist/cron/headless-transport.js +43 -0
  39. package/dist/cron/headless-transport.js.map +1 -0
  40. package/dist/cron/job-commands.d.ts +39 -0
  41. package/dist/cron/job-commands.js +522 -0
  42. package/dist/cron/job-commands.js.map +1 -0
  43. package/dist/cron/job-store.d.ts +35 -0
  44. package/dist/cron/job-store.js +111 -0
  45. package/dist/cron/job-store.js.map +1 -0
  46. package/dist/cron/types.d.ts +127 -0
  47. package/dist/cron/types.js +25 -0
  48. package/dist/cron/types.js.map +1 -0
  49. package/dist/daemon/control-socket.d.ts +127 -0
  50. package/dist/daemon/control-socket.js +261 -0
  51. package/dist/daemon/control-socket.js.map +1 -0
  52. package/dist/daemon/daemon-command.d.ts +13 -0
  53. package/dist/daemon/daemon-command.js +230 -0
  54. package/dist/daemon/daemon-command.js.map +1 -0
  55. package/dist/daemon/ironcurtain-daemon.d.ts +90 -0
  56. package/dist/daemon/ironcurtain-daemon.js +548 -0
  57. package/dist/daemon/ironcurtain-daemon.js.map +1 -0
  58. package/dist/docker/adapters/claude-code.js +8 -48
  59. package/dist/docker/adapters/claude-code.js.map +1 -1
  60. package/dist/docker/adapters/goose.d.ts +58 -0
  61. package/dist/docker/adapters/goose.js +250 -0
  62. package/dist/docker/adapters/goose.js.map +1 -0
  63. package/dist/docker/adapters/shared-scripts.d.ts +29 -0
  64. package/dist/docker/adapters/shared-scripts.js +80 -0
  65. package/dist/docker/adapters/shared-scripts.js.map +1 -0
  66. package/dist/docker/agent-adapter.d.ts +12 -0
  67. package/dist/docker/agent-adapter.js.map +1 -1
  68. package/dist/docker/agent-registry.d.ts +6 -1
  69. package/dist/docker/agent-registry.js +10 -1
  70. package/dist/docker/agent-registry.js.map +1 -1
  71. package/dist/docker/docker-agent-session.d.ts +2 -25
  72. package/dist/docker/docker-agent-session.js +7 -106
  73. package/dist/docker/docker-agent-session.js.map +1 -1
  74. package/dist/docker/docker-infrastructure.d.ts +8 -0
  75. package/dist/docker/docker-infrastructure.js +22 -14
  76. package/dist/docker/docker-infrastructure.js.map +1 -1
  77. package/dist/docker/mitm-proxy.d.ts +5 -2
  78. package/dist/docker/mitm-proxy.js +169 -46
  79. package/dist/docker/mitm-proxy.js.map +1 -1
  80. package/dist/docker/oauth-credentials.d.ts +15 -2
  81. package/dist/docker/oauth-credentials.js +92 -8
  82. package/dist/docker/oauth-credentials.js.map +1 -1
  83. package/dist/docker/oauth-token-manager.d.ts +80 -0
  84. package/dist/docker/oauth-token-manager.js +133 -0
  85. package/dist/docker/oauth-token-manager.js.map +1 -0
  86. package/dist/docker/pty-session.d.ts +2 -0
  87. package/dist/docker/pty-session.js +17 -8
  88. package/dist/docker/pty-session.js.map +1 -1
  89. package/dist/escalation/escalation-watcher.d.ts +2 -0
  90. package/dist/escalation/escalation-watcher.js +5 -0
  91. package/dist/escalation/escalation-watcher.js.map +1 -1
  92. package/dist/escalation/listener-command.js +9 -58
  93. package/dist/escalation/listener-command.js.map +1 -1
  94. package/dist/escalation/listener-lock.d.ts +20 -0
  95. package/dist/escalation/listener-lock.js +69 -0
  96. package/dist/escalation/listener-lock.js.map +1 -0
  97. package/dist/escalation/session-registry.js +1 -14
  98. package/dist/escalation/session-registry.js.map +1 -1
  99. package/dist/index.js +51 -2
  100. package/dist/index.js.map +1 -1
  101. package/dist/mux/mux-app.d.ts +24 -0
  102. package/dist/mux/mux-app.js +450 -0
  103. package/dist/mux/mux-app.js.map +1 -0
  104. package/dist/mux/mux-command.d.ts +7 -0
  105. package/dist/mux/mux-command.js +104 -0
  106. package/dist/mux/mux-command.js.map +1 -0
  107. package/dist/mux/mux-escalation-manager.d.ts +48 -0
  108. package/dist/mux/mux-escalation-manager.js +162 -0
  109. package/dist/mux/mux-escalation-manager.js.map +1 -0
  110. package/dist/mux/mux-input-handler.d.ts +54 -0
  111. package/dist/mux/mux-input-handler.js +512 -0
  112. package/dist/mux/mux-input-handler.js.map +1 -0
  113. package/dist/mux/mux-renderer.d.ts +106 -0
  114. package/dist/mux/mux-renderer.js +850 -0
  115. package/dist/mux/mux-renderer.js.map +1 -0
  116. package/dist/mux/mux-splash.d.ts +17 -0
  117. package/dist/mux/mux-splash.js +323 -0
  118. package/dist/mux/mux-splash.js.map +1 -0
  119. package/dist/mux/paste-interceptor.d.ts +22 -0
  120. package/dist/mux/paste-interceptor.js +143 -0
  121. package/dist/mux/paste-interceptor.js.map +1 -0
  122. package/dist/mux/pty-bridge.d.ts +79 -0
  123. package/dist/mux/pty-bridge.js +151 -0
  124. package/dist/mux/pty-bridge.js.map +1 -0
  125. package/dist/mux/trusted-input.d.ts +27 -0
  126. package/dist/mux/trusted-input.js +27 -0
  127. package/dist/mux/trusted-input.js.map +1 -0
  128. package/dist/mux/types.d.ts +99 -0
  129. package/dist/mux/types.js +57 -0
  130. package/dist/mux/types.js.map +1 -0
  131. package/dist/pipeline/annotate.js +2 -1
  132. package/dist/pipeline/annotate.js.map +1 -1
  133. package/dist/pipeline/compile.d.ts +4 -34
  134. package/dist/pipeline/compile.js +29 -568
  135. package/dist/pipeline/compile.js.map +1 -1
  136. package/dist/pipeline/constitution-compiler.js +1 -1
  137. package/dist/pipeline/mcp-connections.d.ts +16 -0
  138. package/dist/pipeline/mcp-connections.js +61 -0
  139. package/dist/pipeline/mcp-connections.js.map +1 -0
  140. package/dist/pipeline/pipeline-runner.d.ts +86 -0
  141. package/dist/pipeline/pipeline-runner.js +554 -0
  142. package/dist/pipeline/pipeline-runner.js.map +1 -0
  143. package/dist/pipeline/pipeline-shared.d.ts +29 -2
  144. package/dist/pipeline/pipeline-shared.js +68 -12
  145. package/dist/pipeline/pipeline-shared.js.map +1 -1
  146. package/dist/pipeline/refresh-lists.js +38 -12
  147. package/dist/pipeline/refresh-lists.js.map +1 -1
  148. package/dist/pipeline/scenario-generator.js +2 -1
  149. package/dist/pipeline/scenario-generator.js.map +1 -1
  150. package/dist/pipeline/tool-annotator.d.ts +3 -3
  151. package/dist/pipeline/tool-annotator.js +96 -4
  152. package/dist/pipeline/tool-annotator.js.map +1 -1
  153. package/dist/pipeline/types.d.ts +52 -0
  154. package/dist/sandbox/index.js +40 -1
  155. package/dist/sandbox/index.js.map +1 -1
  156. package/dist/session/agent-session.d.ts +3 -0
  157. package/dist/session/agent-session.js +13 -1
  158. package/dist/session/agent-session.js.map +1 -1
  159. package/dist/session/index.js +65 -9
  160. package/dist/session/index.js.map +1 -1
  161. package/dist/session/preflight.js +26 -6
  162. package/dist/session/preflight.js.map +1 -1
  163. package/dist/session/prompts.d.ts +14 -0
  164. package/dist/session/prompts.js +39 -2
  165. package/dist/session/prompts.js.map +1 -1
  166. package/dist/session/session-manager.d.ts +105 -0
  167. package/dist/session/session-manager.js +134 -0
  168. package/dist/session/session-manager.js.map +1 -0
  169. package/dist/session/types.d.ts +33 -0
  170. package/dist/session/workspace-validation.d.ts +19 -0
  171. package/dist/session/workspace-validation.js +66 -0
  172. package/dist/session/workspace-validation.js.map +1 -0
  173. package/dist/signal/bot-command.d.ts +2 -0
  174. package/dist/signal/bot-command.js +8 -0
  175. package/dist/signal/bot-command.js.map +1 -1
  176. package/dist/signal/format.d.ts +27 -0
  177. package/dist/signal/format.js +59 -0
  178. package/dist/signal/format.js.map +1 -1
  179. package/dist/signal/signal-bot-daemon.d.ts +27 -25
  180. package/dist/signal/signal-bot-daemon.js +145 -130
  181. package/dist/signal/signal-bot-daemon.js.map +1 -1
  182. package/dist/signal/signal-container.js +63 -19
  183. package/dist/signal/signal-container.js.map +1 -1
  184. package/dist/signal/signal-transport.js +3 -8
  185. package/dist/signal/signal-transport.js.map +1 -1
  186. package/dist/trusted-process/auto-approver.d.ts +13 -4
  187. package/dist/trusted-process/auto-approver.js +12 -5
  188. package/dist/trusted-process/auto-approver.js.map +1 -1
  189. package/dist/trusted-process/domain-utils.d.ts +38 -4
  190. package/dist/trusted-process/domain-utils.js +118 -11
  191. package/dist/trusted-process/domain-utils.js.map +1 -1
  192. package/dist/trusted-process/escalation.js +5 -0
  193. package/dist/trusted-process/escalation.js.map +1 -1
  194. package/dist/trusted-process/index.js +6 -2
  195. package/dist/trusted-process/index.js.map +1 -1
  196. package/dist/trusted-process/mcp-client-manager.js +11 -5
  197. package/dist/trusted-process/mcp-client-manager.js.map +1 -1
  198. package/dist/trusted-process/mcp-proxy-server.d.ts +2 -0
  199. package/dist/trusted-process/mcp-proxy-server.js +98 -32
  200. package/dist/trusted-process/mcp-proxy-server.js.map +1 -1
  201. package/dist/trusted-process/permissive-output-validator.d.ts +23 -0
  202. package/dist/trusted-process/permissive-output-validator.js +10 -0
  203. package/dist/trusted-process/permissive-output-validator.js.map +1 -0
  204. package/dist/trusted-process/policy-engine.d.ts +27 -4
  205. package/dist/trusted-process/policy-engine.js +152 -57
  206. package/dist/trusted-process/policy-engine.js.map +1 -1
  207. package/dist/trusted-process/tool-description-hints.d.ts +19 -0
  208. package/dist/trusted-process/tool-description-hints.js +53 -0
  209. package/dist/trusted-process/tool-description-hints.js.map +1 -0
  210. package/dist/types/argument-roles.d.ts +33 -1
  211. package/dist/types/argument-roles.js +129 -1
  212. package/dist/types/argument-roles.js.map +1 -1
  213. package/docker/Dockerfile.goose +34 -0
  214. package/docker/entrypoint-goose.sh +30 -0
  215. package/package.json +6 -1
  216. package/src/config/generated/tool-annotations.json +487 -338
  217. package/src/config/mcp-servers.json +2 -8
@@ -0,0 +1,184 @@
1
+ /**
2
+ * CronScheduler -- manages cron schedules for all enabled jobs.
3
+ *
4
+ * Uses a polling approach with 60-second intervals to check
5
+ * whether each job's cron expression matches the current time.
6
+ * When node-cron is available, the implementation can be swapped
7
+ * to use it for more efficient scheduling.
8
+ */
9
+ import * as logger from '../logger.js';
10
+ /**
11
+ * Thrown when a cron expression cannot be parsed.
12
+ */
13
+ export class InvalidCronExpressionError extends Error {
14
+ expression;
15
+ constructor(expression, cause) {
16
+ super(`Invalid cron expression "${expression}": ${cause instanceof Error ? cause.message : String(cause)}`);
17
+ this.expression = expression;
18
+ this.name = 'InvalidCronExpressionError';
19
+ }
20
+ }
21
+ function parseField(field, min, max) {
22
+ const values = new Set();
23
+ for (const part of field.split(',')) {
24
+ const stepMatch = part.match(/^(.+)\/(\d+)$/);
25
+ const step = stepMatch ? parseInt(stepMatch[2], 10) : 1;
26
+ if (stepMatch && (isNaN(step) || step < 1)) {
27
+ throw new Error(`Invalid step "${stepMatch[2]}" in "${part}" (expected integer >= 1)`);
28
+ }
29
+ const range = stepMatch ? stepMatch[1] : part;
30
+ if (range === '*') {
31
+ for (let i = min; i <= max; i += step)
32
+ values.add(i);
33
+ }
34
+ else if (range.includes('-')) {
35
+ const [startStr, endStr] = range.split('-');
36
+ const start = parseInt(startStr, 10);
37
+ const end = parseInt(endStr, 10);
38
+ if (isNaN(start) || isNaN(end) || start < min || end > max || start > end) {
39
+ throw new Error(`Invalid range "${range}" (expected ${min}-${max})`);
40
+ }
41
+ for (let i = start; i <= end; i += step)
42
+ values.add(i);
43
+ }
44
+ else {
45
+ const val = parseInt(range, 10);
46
+ if (isNaN(val) || val < min || val > max) {
47
+ throw new Error(`Invalid value "${range}" (expected ${min}-${max})`);
48
+ }
49
+ values.add(val);
50
+ }
51
+ }
52
+ return values;
53
+ }
54
+ function parseCronExpression(expression) {
55
+ const parts = expression.trim().split(/\s+/);
56
+ if (parts.length !== 5) {
57
+ throw new Error(`Expected 5 fields (min hour dom mon dow), got ${parts.length}`);
58
+ }
59
+ const daysOfWeek = parseField(parts[4], 0, 7); // 0 and 7 both mean Sunday
60
+ if (daysOfWeek.has(7)) {
61
+ daysOfWeek.add(0);
62
+ daysOfWeek.delete(7);
63
+ }
64
+ return {
65
+ minutes: parseField(parts[0], 0, 59),
66
+ hours: parseField(parts[1], 0, 23),
67
+ daysOfMonth: parseField(parts[2], 1, 31),
68
+ months: parseField(parts[3], 1, 12),
69
+ daysOfWeek,
70
+ };
71
+ }
72
+ function cronMatchesTime(fields, date) {
73
+ const minute = date.getMinutes();
74
+ const hour = date.getHours();
75
+ const dayOfMonth = date.getDate();
76
+ const month = date.getMonth() + 1; // 0-indexed -> 1-indexed
77
+ const dayOfWeek = date.getDay(); // 0 = Sunday
78
+ if (!fields.minutes.has(minute))
79
+ return false;
80
+ if (!fields.hours.has(hour))
81
+ return false;
82
+ if (!fields.months.has(month))
83
+ return false;
84
+ // Standard cron: if both dom and dow are restricted (not *), match either
85
+ const domIsWildcard = fields.daysOfMonth.size === 31;
86
+ const dowIsWildcard = fields.daysOfWeek.size === 7;
87
+ if (domIsWildcard && dowIsWildcard)
88
+ return true;
89
+ if (domIsWildcard)
90
+ return fields.daysOfWeek.has(dayOfWeek);
91
+ if (dowIsWildcard)
92
+ return fields.daysOfMonth.has(dayOfMonth);
93
+ // Both restricted: OR logic (standard cron behavior)
94
+ return fields.daysOfMonth.has(dayOfMonth) || fields.daysOfWeek.has(dayOfWeek);
95
+ }
96
+ /**
97
+ * Calculates the next time a cron expression would fire after the given date.
98
+ * Scans forward minute-by-minute (up to 366 days).
99
+ */
100
+ function getNextFireTime(fields, after) {
101
+ const candidate = new Date(after);
102
+ // Start from the next minute
103
+ candidate.setSeconds(0, 0);
104
+ candidate.setMinutes(candidate.getMinutes() + 1);
105
+ const maxIterations = 366 * 24 * 60; // One year of minutes
106
+ for (let i = 0; i < maxIterations; i++) {
107
+ if (cronMatchesTime(fields, candidate)) {
108
+ return candidate;
109
+ }
110
+ candidate.setMinutes(candidate.getMinutes() + 1);
111
+ }
112
+ // Should not happen for valid expressions, but return a far-future date
113
+ return new Date(after.getTime() + 366 * 24 * 60 * 60 * 1000);
114
+ }
115
+ /** Creates a CronScheduler with built-in cron expression parsing. */
116
+ export function createCronScheduler() {
117
+ const jobs = new Map();
118
+ let pollInterval = null;
119
+ function startPolling() {
120
+ if (pollInterval)
121
+ return;
122
+ // Poll every 30 seconds (within the 60-second cron granularity)
123
+ pollInterval = setInterval(() => tick(), 30_000);
124
+ pollInterval.unref(); // Don't keep the process alive just for polling
125
+ }
126
+ function stopPolling() {
127
+ if (pollInterval) {
128
+ clearInterval(pollInterval);
129
+ pollInterval = null;
130
+ }
131
+ }
132
+ function tick() {
133
+ const now = new Date();
134
+ // Truncate to the current minute for comparison
135
+ now.setSeconds(0, 0);
136
+ for (const [jobId, scheduled] of jobs.entries()) {
137
+ if (scheduled.lastFired && scheduled.lastFired.getTime() === now.getTime()) {
138
+ continue; // Already fired this minute
139
+ }
140
+ if (cronMatchesTime(scheduled.fields, now)) {
141
+ scheduled.lastFired = now;
142
+ logger.info(`[CronScheduler] Firing job ${jobId} at ${now.toISOString()}`);
143
+ scheduled.callback(scheduled.job).catch((err) => {
144
+ logger.warn(`[CronScheduler] Job ${jobId} callback failed: ${String(err)}`);
145
+ });
146
+ }
147
+ }
148
+ }
149
+ return {
150
+ schedule(job, onTrigger) {
151
+ let fields;
152
+ try {
153
+ fields = parseCronExpression(job.schedule);
154
+ }
155
+ catch (err) {
156
+ throw new InvalidCronExpressionError(job.schedule, err);
157
+ }
158
+ // Replace any existing schedule for this job
159
+ jobs.set(job.id, { job, fields, callback: onTrigger, lastFired: null });
160
+ if (jobs.size === 1) {
161
+ startPolling();
162
+ }
163
+ },
164
+ unschedule(jobId) {
165
+ jobs.delete(jobId);
166
+ if (jobs.size === 0) {
167
+ stopPolling();
168
+ }
169
+ },
170
+ unscheduleAll() {
171
+ jobs.clear();
172
+ stopPolling();
173
+ },
174
+ getNextRun(jobId) {
175
+ const scheduled = jobs.get(jobId);
176
+ if (!scheduled)
177
+ return undefined;
178
+ return getNextFireTime(scheduled.fields, new Date());
179
+ },
180
+ };
181
+ }
182
+ // Export for testing
183
+ export { parseCronExpression, cronMatchesTime, getNextFireTime };
184
+ //# sourceMappingURL=cron-scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cron-scheduler.js","sourceRoot":"","sources":["../../src/cron/cron-scheduler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Advisory file locking for the job store.
3
+ *
4
+ * Uses mkdir (atomic on POSIX) to create a lock directory next to the
5
+ * target file. The lock directory contains a metadata file with the
6
+ * owning PID and timestamp, enabling stale lock detection when a
7
+ * process crashes without releasing.
8
+ *
9
+ * This is designed for short-lived critical sections (read-modify-write),
10
+ * not long-held process-level locks. For process singleton enforcement,
11
+ * see src/escalation/listener-lock.ts.
12
+ */
13
+ /**
14
+ * Releases an advisory lock. Safe to call even if the lock is not held.
15
+ */
16
+ export declare function releaseLock(lockDir: string): void;
17
+ /**
18
+ * Acquires an advisory lock, retrying until timeout.
19
+ *
20
+ * @param lockDir Path to the lock directory (typically `{target}.lock`)
21
+ * @param staleMs Time in ms after which an unreleased lock is considered stale
22
+ * @returns true if acquired, false if timed out
23
+ */
24
+ export declare function acquireLock(lockDir: string, staleMs?: number): Promise<boolean>;
25
+ /**
26
+ * Executes a function while holding an advisory file lock.
27
+ *
28
+ * Acquires the lock, runs the function, and releases the lock in a
29
+ * finally block. Throws if the lock cannot be acquired within the
30
+ * timeout period.
31
+ *
32
+ * @param lockDir Path to the lock directory
33
+ * @param fn The function to execute under lock
34
+ * @param staleMs Stale lock timeout (default 30s)
35
+ */
36
+ export declare function withFileLock<T>(lockDir: string, fn: () => T | Promise<T>, staleMs?: number): Promise<T>;
37
+ /**
38
+ * Returns the conventional lock directory path for a job directory.
39
+ * The lock is placed at `{jobDir}/job.json.lock/`.
40
+ */
41
+ export declare function getJobLockDir(jobDir: string): string;
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Advisory file locking for the job store.
3
+ *
4
+ * Uses mkdir (atomic on POSIX) to create a lock directory next to the
5
+ * target file. The lock directory contains a metadata file with the
6
+ * owning PID and timestamp, enabling stale lock detection when a
7
+ * process crashes without releasing.
8
+ *
9
+ * This is designed for short-lived critical sections (read-modify-write),
10
+ * not long-held process-level locks. For process singleton enforcement,
11
+ * see src/escalation/listener-lock.ts.
12
+ */
13
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
14
+ import { resolve } from 'node:path';
15
+ import { isPidAlive } from '../escalation/listener-lock.js';
16
+ /** Default stale timeout: 30 seconds. */
17
+ const DEFAULT_STALE_MS = 30_000;
18
+ /** Retry interval when waiting for a lock. */
19
+ const RETRY_INTERVAL_MS = 50;
20
+ /** Maximum time to wait for a lock before giving up. */
21
+ const MAX_WAIT_MS = 10_000;
22
+ /**
23
+ * Attempts to acquire an advisory lock for the given path.
24
+ * Returns true if the lock was acquired, false otherwise.
25
+ *
26
+ * The lock is a directory at `{path}.lock/` containing a `meta.json`
27
+ * with the owning PID and creation timestamp.
28
+ */
29
+ function tryAcquire(lockDir, staleMs) {
30
+ // Attempt atomic mkdir first — this is the authoritative lock acquisition.
31
+ // Only check for stale locks after EEXIST, avoiding a TOCTOU race.
32
+ try {
33
+ mkdirSync(lockDir);
34
+ }
35
+ catch (err) {
36
+ if (err.code !== 'EEXIST') {
37
+ throw err;
38
+ }
39
+ // Lock dir exists — check if it's stale
40
+ if (isLockStale(lockDir, staleMs)) {
41
+ forceRelease(lockDir);
42
+ // Retry mkdir after releasing stale lock
43
+ try {
44
+ mkdirSync(lockDir);
45
+ }
46
+ catch (retryErr) {
47
+ if (retryErr.code === 'EEXIST') {
48
+ return false; // Another process grabbed it
49
+ }
50
+ throw retryErr;
51
+ }
52
+ }
53
+ else {
54
+ return false;
55
+ }
56
+ }
57
+ // Write metadata for stale detection
58
+ const meta = { pid: process.pid, timestamp: Date.now() };
59
+ try {
60
+ writeFileSync(resolve(lockDir, 'meta.json'), JSON.stringify(meta));
61
+ }
62
+ catch {
63
+ // If we can't write metadata, release and fail
64
+ forceRelease(lockDir);
65
+ return false;
66
+ }
67
+ return true;
68
+ }
69
+ /**
70
+ * Checks whether an existing lock is stale (owner dead or timed out).
71
+ */
72
+ function isLockStale(lockDir, staleMs) {
73
+ const metaPath = resolve(lockDir, 'meta.json');
74
+ try {
75
+ const raw = readFileSync(metaPath, 'utf-8');
76
+ const meta = JSON.parse(raw);
77
+ // Check if the owning process is still alive
78
+ if (!isPidAlive(meta.pid)) {
79
+ return true;
80
+ }
81
+ // Check if the lock has exceeded the stale timeout
82
+ if (Date.now() - meta.timestamp > staleMs) {
83
+ return true;
84
+ }
85
+ return false;
86
+ }
87
+ catch {
88
+ // No metadata or unreadable -- treat as stale
89
+ return true;
90
+ }
91
+ }
92
+ /**
93
+ * Force-removes a lock directory. Best-effort.
94
+ */
95
+ function forceRelease(lockDir) {
96
+ try {
97
+ rmSync(lockDir, { recursive: true, force: true });
98
+ }
99
+ catch {
100
+ /* best effort */
101
+ }
102
+ }
103
+ /**
104
+ * Releases an advisory lock. Safe to call even if the lock is not held.
105
+ */
106
+ export function releaseLock(lockDir) {
107
+ forceRelease(lockDir);
108
+ }
109
+ /**
110
+ * Acquires an advisory lock, retrying until timeout.
111
+ *
112
+ * @param lockDir Path to the lock directory (typically `{target}.lock`)
113
+ * @param staleMs Time in ms after which an unreleased lock is considered stale
114
+ * @returns true if acquired, false if timed out
115
+ */
116
+ export async function acquireLock(lockDir, staleMs = DEFAULT_STALE_MS) {
117
+ const deadline = Date.now() + MAX_WAIT_MS;
118
+ while (Date.now() < deadline) {
119
+ if (tryAcquire(lockDir, staleMs)) {
120
+ return true;
121
+ }
122
+ await new Promise((resolve) => setTimeout(resolve, RETRY_INTERVAL_MS));
123
+ }
124
+ return false;
125
+ }
126
+ /**
127
+ * Executes a function while holding an advisory file lock.
128
+ *
129
+ * Acquires the lock, runs the function, and releases the lock in a
130
+ * finally block. Throws if the lock cannot be acquired within the
131
+ * timeout period.
132
+ *
133
+ * @param lockDir Path to the lock directory
134
+ * @param fn The function to execute under lock
135
+ * @param staleMs Stale lock timeout (default 30s)
136
+ */
137
+ export async function withFileLock(lockDir, fn, staleMs = DEFAULT_STALE_MS) {
138
+ const acquired = await acquireLock(lockDir, staleMs);
139
+ if (!acquired) {
140
+ throw new Error(`Failed to acquire file lock: ${lockDir} (timed out after ${MAX_WAIT_MS}ms)`);
141
+ }
142
+ try {
143
+ return await fn();
144
+ }
145
+ finally {
146
+ releaseLock(lockDir);
147
+ }
148
+ }
149
+ /**
150
+ * Returns the conventional lock directory path for a job directory.
151
+ * The lock is placed at `{jobDir}/job.json.lock/`.
152
+ */
153
+ export function getJobLockDir(jobDir) {
154
+ return resolve(jobDir, 'job.json.lock');
155
+ }
156
+ //# sourceMappingURL=file-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-lock.js","sourceRoot":"","sources":["../../src/cron/file-lock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Formatting utilities for cron job CLI output.
3
+ */
4
+ /**
5
+ * Formats a duration in seconds as a compact string like "1d 2h 30m".
6
+ * Shows up to two units for brevity (e.g., "2h 15m" not "2h 15m 30s").
7
+ */
8
+ export declare function formatDuration(seconds: number): string;
9
+ /**
10
+ * Formats a date as a relative time string with an absolute time suffix.
11
+ * Examples: "in 2h 15m (Mar 5, 09:00)", "3h ago (Mar 5, 06:00)", "just now (Mar 5, 09:00)"
12
+ */
13
+ export declare function formatRelativeTime(date: Date, now?: Date): string;
14
+ /**
15
+ * Converts a 5-field cron expression into a human-readable English description.
16
+ * Covers common patterns well; exotic expressions get a best-effort description.
17
+ */
18
+ export declare function describeCronExpression(expr: string): string;
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Formatting utilities for cron job CLI output.
3
+ */
4
+ import { parseCronExpression } from './cron-scheduler.js';
5
+ // ---------------------------------------------------------------------------
6
+ // Shared constants
7
+ // ---------------------------------------------------------------------------
8
+ const MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
9
+ // ---------------------------------------------------------------------------
10
+ // Duration and relative time display
11
+ // ---------------------------------------------------------------------------
12
+ /**
13
+ * Formats a duration in seconds as a compact string like "1d 2h 30m".
14
+ * Shows up to two units for brevity (e.g., "2h 15m" not "2h 15m 30s").
15
+ */
16
+ export function formatDuration(seconds) {
17
+ const minutes = Math.floor(seconds / 60);
18
+ const hours = Math.floor(minutes / 60);
19
+ const days = Math.floor(hours / 24);
20
+ const parts = [];
21
+ if (days > 0)
22
+ parts.push(`${days}d`);
23
+ if (hours % 24 > 0)
24
+ parts.push(`${hours % 24}h`);
25
+ if (minutes % 60 > 0 && days === 0)
26
+ parts.push(`${minutes % 60}m`);
27
+ if (parts.length === 0)
28
+ parts.push('0m');
29
+ return parts.join(' ');
30
+ }
31
+ /**
32
+ * Formats a date as a relative time string with an absolute time suffix.
33
+ * Examples: "in 2h 15m (Mar 5, 09:00)", "3h ago (Mar 5, 06:00)", "just now (Mar 5, 09:00)"
34
+ */
35
+ export function formatRelativeTime(date, now = new Date()) {
36
+ const diffMs = date.getTime() - now.getTime();
37
+ const absDiffSec = Math.floor(Math.abs(diffMs) / 1000);
38
+ const absolute = formatAbsoluteTime(date);
39
+ if (absDiffSec < 60)
40
+ return `just now (${absolute})`;
41
+ const dur = formatDuration(absDiffSec);
42
+ const relative = diffMs >= 0 ? `in ${dur}` : `${dur} ago`;
43
+ return `${relative} (${absolute})`;
44
+ }
45
+ function formatAbsoluteTime(date) {
46
+ const month = MONTH_NAMES[date.getMonth()];
47
+ const day = date.getDate();
48
+ const hours = date.getHours().toString().padStart(2, '0');
49
+ const minutes = date.getMinutes().toString().padStart(2, '0');
50
+ return `${month} ${day}, ${hours}:${minutes}`;
51
+ }
52
+ // ---------------------------------------------------------------------------
53
+ // Cron expression description
54
+ // ---------------------------------------------------------------------------
55
+ const DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
56
+ /**
57
+ * Converts a 5-field cron expression into a human-readable English description.
58
+ * Covers common patterns well; exotic expressions get a best-effort description.
59
+ */
60
+ export function describeCronExpression(expr) {
61
+ let fields;
62
+ try {
63
+ fields = parseCronExpression(expr);
64
+ }
65
+ catch {
66
+ return expr; // fallback to raw expression if unparseable
67
+ }
68
+ const parts = [];
69
+ // Time part
70
+ parts.push(describeTimePart(expr, fields));
71
+ // Day-of-week part
72
+ const dowPart = describeDaysOfWeek(fields);
73
+ if (dowPart)
74
+ parts.push(dowPart);
75
+ // Day-of-month part
76
+ const domPart = describeDaysOfMonth(fields);
77
+ if (domPart)
78
+ parts.push(domPart);
79
+ // Month part
80
+ const monthPart = describeMonths(fields);
81
+ if (monthPart)
82
+ parts.push(monthPart);
83
+ return parts.join(', ');
84
+ }
85
+ function describeTimePart(expr, fields) {
86
+ const rawParts = expr.trim().split(/\s+/);
87
+ const minField = rawParts[0];
88
+ const hourField = rawParts[1];
89
+ // Every N minutes
90
+ const minStep = minField.match(/^\*\/(\d+)$/);
91
+ if (minStep && hourField === '*') {
92
+ return `Every ${minStep[1]} minutes`;
93
+ }
94
+ // Every hour at specific minute
95
+ if (fields.minutes.size === 1 && hourField === '*') {
96
+ const min = [...fields.minutes][0];
97
+ return min === 0 ? 'Every hour' : `Every hour at :${min.toString().padStart(2, '0')}`;
98
+ }
99
+ // Every N hours
100
+ const hourStep = hourField.match(/^\*\/(\d+)$/);
101
+ if (hourStep && fields.minutes.size === 1) {
102
+ const min = [...fields.minutes][0];
103
+ return `Every ${hourStep[1]} hours at :${min.toString().padStart(2, '0')}`;
104
+ }
105
+ // Specific time(s)
106
+ if (fields.hours.size <= 3 && fields.minutes.size === 1) {
107
+ const min = [...fields.minutes][0];
108
+ const times = [...fields.hours]
109
+ .sort((a, b) => a - b)
110
+ .map((h) => `${h.toString().padStart(2, '0')}:${min.toString().padStart(2, '0')}`);
111
+ return `At ${times.join(', ')}`;
112
+ }
113
+ // Fallback: describe minutes and hours separately
114
+ return `At minute ${[...fields.minutes].sort((a, b) => a - b).join(',')} of hour ${[...fields.hours].sort((a, b) => a - b).join(',')}`;
115
+ }
116
+ function describeDaysOfWeek(fields) {
117
+ if (fields.daysOfWeek.size === 7)
118
+ return null; // wildcard
119
+ const days = [...fields.daysOfWeek].sort((a, b) => a - b);
120
+ // Check for contiguous range
121
+ if (days.length >= 2 && isContiguous(days)) {
122
+ return `${DAY_NAMES[days[0]]}--${DAY_NAMES[days[days.length - 1]]}`;
123
+ }
124
+ return days.map((d) => DAY_NAMES[d]).join(', ');
125
+ }
126
+ function describeDaysOfMonth(fields) {
127
+ if (fields.daysOfMonth.size === 31)
128
+ return null; // wildcard
129
+ const days = [...fields.daysOfMonth].sort((a, b) => a - b);
130
+ return `on day ${days.join(', ')} of the month`;
131
+ }
132
+ function describeMonths(fields) {
133
+ if (fields.months.size === 12)
134
+ return null; // wildcard
135
+ const months = [...fields.months].sort((a, b) => a - b);
136
+ return `in ${months.map((m) => MONTH_NAMES[m - 1]).join(', ')}`;
137
+ }
138
+ function isContiguous(sorted) {
139
+ for (let i = 1; i < sorted.length; i++) {
140
+ if (sorted[i] !== sorted[i - 1] + 1)
141
+ return false;
142
+ }
143
+ return true;
144
+ }
145
+ //# sourceMappingURL=format-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-utils.js","sourceRoot":"","sources":["../../src/cron/format-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,mBAAmB,EAAmB,MAAM,qBAAqB,CAAC;AAE3E,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAEzG,8EAA8E;AAC9E,qCAAqC;AACrC,8EAA8E;AAE9E;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;IAEpC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,IAAI,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;IACrC,IAAI,KAAK,GAAG,EAAE,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,GAAG,EAAE,GAAG,CAAC,CAAC;IACjD,IAAI,OAAO,GAAG,EAAE,GAAG,CAAC,IAAI,IAAI,KAAK,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,GAAG,EAAE,GAAG,CAAC,CAAC;IACnE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAU,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE;IAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;IAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAE1C,IAAI,UAAU,GAAG,EAAE;QAAE,OAAO,aAAa,QAAQ,GAAG,CAAC;IAErD,MAAM,GAAG,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,MAAM,CAAC;IAC1D,OAAO,GAAG,QAAQ,KAAK,QAAQ,GAAG,CAAC;AACrC,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAU;IACpC,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC9D,OAAO,GAAG,KAAK,IAAI,GAAG,KAAK,KAAK,IAAI,OAAO,EAAE,CAAC;AAChD,CAAC;AAED,8EAA8E;AAC9E,8BAA8B;AAC9B,8EAA8E;AAE9E,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAEpE;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,IAAI,MAAkB,CAAC;IACvB,IAAI,CAAC;QACH,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,4CAA4C;IAC3D,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,YAAY;IACZ,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAE3C,mBAAmB;IACnB,MAAM,OAAO,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC3C,IAAI,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAEjC,oBAAoB;IACpB,MAAM,OAAO,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAEjC,aAAa;IACb,MAAM,SAAS,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAErC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAY,EAAE,MAAkB;IACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC7B,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAE9B,kBAAkB;IAClB,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAC9C,IAAI,OAAO,IAAI,SAAS,KAAK,GAAG,EAAE,CAAC;QACjC,OAAO,SAAS,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;IACvC,CAAC;IAED,gCAAgC;IAChC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,IAAI,SAAS,KAAK,GAAG,EAAE,CAAC;QACnD,MAAM,GAAG,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QACnC,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,kBAAkB,GAAG,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IACxF,CAAC;IAED,gBAAgB;IAChB,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAChD,IAAI,QAAQ,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QAC1C,MAAM,GAAG,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QACnC,OAAO,SAAS,QAAQ,CAAC,CAAC,CAAC,cAAc,GAAG,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IAC7E,CAAC;IAED,mBAAmB;IACnB,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACxD,MAAM,GAAG,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC;aAC5B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;aACrB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QACrF,OAAO,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAClC,CAAC;IAED,kDAAkD;IAClD,OAAO,aAAa,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;AACzI,CAAC;AAED,SAAS,kBAAkB,CAAC,MAAkB;IAC5C,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,WAAW;IAE1D,MAAM,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAE1D,6BAA6B;IAC7B,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACtE,CAAC;IAED,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,mBAAmB,CAAC,MAAkB;IAC7C,IAAI,MAAM,CAAC,WAAW,CAAC,IAAI,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC,CAAC,WAAW;IAE5D,MAAM,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3D,OAAO,UAAU,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC;AAClD,CAAC;AAED,SAAS,cAAc,CAAC,MAAkB;IACxC,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC,CAAC,WAAW;IAEvD,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACxD,OAAO,MAAM,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAClE,CAAC;AAED,SAAS,YAAY,CAAC,MAAgB;IACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;IACpD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Validates that a git URI uses a safe transport protocol.
3
+ *
4
+ * Rejects dangerous protocols like ext:: which allow arbitrary
5
+ * command execution, and any other unrecognized scheme.
6
+ *
7
+ * @throws {Error} if the URI uses a disallowed protocol
8
+ */
9
+ export declare function validateGitUri(uri: string): void;
10
+ /**
11
+ * Clones or refreshes a git repository in the given directory.
12
+ *
13
+ * - First call: clones the repo into dir (dir must exist and be empty)
14
+ * - Subsequent calls: fetches from origin and resets tracked files to
15
+ * FETCH_HEAD, leaving untracked files (agent artifacts) intact.
16
+ *
17
+ * @param verbose Pass true to inherit stdio (show git output in terminal)
18
+ * @returns diff --stat of tracked-file changes discarded by a hard reset,
19
+ * or null on first clone / when there are no local changes.
20
+ * @throws {Error} if the URI uses a disallowed protocol
21
+ */
22
+ export declare function syncGitRepo(uri: string, dir: string, verbose?: boolean): string | null;