crewly 1.20.245 → 1.20.246

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.
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Scheduled commands runner.
3
+ *
4
+ * Runs host commands listed in `<CREWLY_HOME>/scheduled-commands.json` on an
5
+ * interval. It exists because launchd jobs cannot read ~/Desktop (TCC), while
6
+ * the backend can. Design rules:
7
+ * - The list is read from disk only. There is deliberately no API route or
8
+ * trigger action that adds an entry: that would be arbitrary shell
9
+ * execution through the API.
10
+ * - No file = no jobs = feature off.
11
+ * - Each run is spawned DETACHED (own process group, unref'd, output to a log
12
+ * file) so a Crewly restart cannot kill it mid-way (a rollout cut in half
13
+ * leaves a mixed fleet).
14
+ * - A run never starts while the previous one is alive: checked by the pid we
15
+ * spawned and, when `lockFile` is set, by the pid inside the command's own
16
+ * lock file (a stale pid counts as dead).
17
+ * - Every skip is logged at debug; a run is logged at info only when spawned.
18
+ *
19
+ * Config shape: `[{name, cwd, command, args?, intervalMinutes, enabled?, lockFile?}]`.
20
+ *
21
+ * @module services/system/scheduled-commands
22
+ */
23
+ import { type ChildProcess, type SpawnOptions } from 'child_process';
24
+ /** One validated entry from the config file. */
25
+ export interface ScheduledCommand {
26
+ /** Unique name (used in the log file name) */
27
+ name: string;
28
+ /** Absolute working directory */
29
+ cwd: string;
30
+ /** Executable, run without a shell */
31
+ command: string;
32
+ /** Arguments */
33
+ args: string[];
34
+ /** Minutes between runs */
35
+ intervalMinutes: number;
36
+ /** Absolute path of the command's own lock file (holds a pid), optional */
37
+ lockFile?: string;
38
+ }
39
+ /** Result of reading the config file. */
40
+ export interface LoadedScheduledCommands {
41
+ /** Enabled, valid entries */
42
+ entries: ScheduledCommand[];
43
+ /** Entries in the file that were valid but disabled */
44
+ disabled: number;
45
+ /** Why the file or an entry was not used (one line each) */
46
+ warnings: string[];
47
+ }
48
+ /** Minimal logger contract. */
49
+ export interface ScheduledCommandsLogger {
50
+ debug(message: string, meta?: Record<string, unknown>): void;
51
+ info(message: string, meta?: Record<string, unknown>): void;
52
+ warn(message: string, meta?: Record<string, unknown>): void;
53
+ error(message: string, meta?: Record<string, unknown>): void;
54
+ }
55
+ /** Injection points (tests replace the process and filesystem edges). */
56
+ export interface ScheduledCommandsDeps {
57
+ /** Path of the config file */
58
+ configPath: string;
59
+ /** Directory for per-command log files */
60
+ logDir: string;
61
+ logger: ScheduledCommandsLogger;
62
+ /** child_process.spawn */
63
+ spawn?: (command: string, args: string[], options: SpawnOptions) => ChildProcess;
64
+ /** True when a process with this pid exists */
65
+ isPidAlive?: (pid: number) => boolean;
66
+ /** Reads a text file; throws when missing */
67
+ readFile?: (file: string) => string;
68
+ /** True when the path exists */
69
+ pathExists?: (file: string) => boolean;
70
+ /** Opens the log file for appending and returns an fd */
71
+ openLog?: (file: string) => number;
72
+ /** Closes an fd */
73
+ closeLog?: (fd: number) => void;
74
+ }
75
+ /**
76
+ * True when a process with this pid exists (signal 0 probe).
77
+ *
78
+ * @param pid - Process id
79
+ * @returns Whether it is alive
80
+ */
81
+ export declare function defaultIsPidAlive(pid: number): boolean;
82
+ /**
83
+ * Parse and validate the config text.
84
+ *
85
+ * Invalid input never throws: it yields zero entries and a warning.
86
+ *
87
+ * @param text - File contents
88
+ * @returns Entries, disabled count and warnings
89
+ */
90
+ export declare function parseScheduledCommands(text: string): LoadedScheduledCommands;
91
+ /** Outcome of one tick for one entry (also returned to tests). */
92
+ export type RunOutcome = 'spawned' | 'skipped-running' | 'skipped-lock' | 'skipped-no-cwd' | 'spawn-failed';
93
+ /**
94
+ * Interval runner for owner-configured host commands.
95
+ */
96
+ export declare class ScheduledCommandsService {
97
+ private readonly deps;
98
+ private entries;
99
+ private timers;
100
+ /** Last spawned pid per entry name */
101
+ private readonly lastPid;
102
+ /**
103
+ * @param deps - Paths, logger and optional process/filesystem overrides
104
+ */
105
+ constructor(deps: ScheduledCommandsDeps);
106
+ /**
107
+ * Load the config and schedule every enabled entry.
108
+ *
109
+ * Logs "N scheduled command(s) loaded" every time, including 0.
110
+ * A missing file is the normal off state (info); an unreadable or
111
+ * invalid file logs one warn line.
112
+ *
113
+ * @returns Number of jobs scheduled
114
+ */
115
+ start(): number;
116
+ /** Cancel all timers. Spawned children are NOT touched (they are detached). */
117
+ stop(): void;
118
+ /**
119
+ * Try to run one entry now.
120
+ *
121
+ * Skips (debug log) when the previous run or the command's own lock holder
122
+ * is alive, or the cwd is missing (warn). Never claims a run happened
123
+ * when it was skipped.
124
+ *
125
+ * @param entry - Entry to run
126
+ * @returns What happened
127
+ */
128
+ runEntry(entry: ScheduledCommand): RunOutcome;
129
+ }
130
+ //# sourceMappingURL=scheduled-commands.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-commands.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/system/scheduled-commands.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Scheduled commands runner.
3
+ *
4
+ * Runs host commands listed in `<CREWLY_HOME>/scheduled-commands.json` on an
5
+ * interval. It exists because launchd jobs cannot read ~/Desktop (TCC), while
6
+ * the backend can. Design rules:
7
+ * - The list is read from disk only. There is deliberately no API route or
8
+ * trigger action that adds an entry: that would be arbitrary shell
9
+ * execution through the API.
10
+ * - No file = no jobs = feature off.
11
+ * - Each run is spawned DETACHED (own process group, unref'd, output to a log
12
+ * file) so a Crewly restart cannot kill it mid-way (a rollout cut in half
13
+ * leaves a mixed fleet).
14
+ * - A run never starts while the previous one is alive: checked by the pid we
15
+ * spawned and, when `lockFile` is set, by the pid inside the command's own
16
+ * lock file (a stale pid counts as dead).
17
+ * - Every skip is logged at debug; a run is logged at info only when spawned.
18
+ *
19
+ * Config shape: `[{name, cwd, command, args?, intervalMinutes, enabled?, lockFile?}]`.
20
+ *
21
+ * @module services/system/scheduled-commands
22
+ */
23
+ import { spawn as nodeSpawn } from 'child_process';
24
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync } from 'fs';
25
+ import * as os from 'os';
26
+ import * as path from 'path';
27
+ import { SCHEDULED_COMMANDS } from '../../constants.js';
28
+ /**
29
+ * Expand a leading `~` to the home directory.
30
+ *
31
+ * @param p - Path from the config file
32
+ * @returns Path with `~` expanded
33
+ */
34
+ function expandHome(p) {
35
+ if (p === '~')
36
+ return os.homedir();
37
+ if (p.startsWith('~/'))
38
+ return path.join(os.homedir(), p.slice(2));
39
+ return p;
40
+ }
41
+ /**
42
+ * True when a process with this pid exists (signal 0 probe).
43
+ *
44
+ * @param pid - Process id
45
+ * @returns Whether it is alive
46
+ */
47
+ export function defaultIsPidAlive(pid) {
48
+ if (!Number.isInteger(pid) || pid <= 0)
49
+ return false;
50
+ try {
51
+ process.kill(pid, 0);
52
+ return true;
53
+ }
54
+ catch (err) {
55
+ // EPERM = exists but not ours: still alive.
56
+ return err.code === 'EPERM';
57
+ }
58
+ }
59
+ /**
60
+ * Parse and validate the config text.
61
+ *
62
+ * Invalid input never throws: it yields zero entries and a warning.
63
+ *
64
+ * @param text - File contents
65
+ * @returns Entries, disabled count and warnings
66
+ */
67
+ export function parseScheduledCommands(text) {
68
+ const out = { entries: [], disabled: 0, warnings: [] };
69
+ let raw;
70
+ try {
71
+ raw = JSON.parse(text);
72
+ }
73
+ catch (err) {
74
+ out.warnings.push(`config is not valid JSON (${err instanceof Error ? err.message : String(err)}); 0 jobs`);
75
+ return out;
76
+ }
77
+ if (!Array.isArray(raw)) {
78
+ out.warnings.push('config must be a JSON array; 0 jobs');
79
+ return out;
80
+ }
81
+ const seen = new Set();
82
+ raw.forEach((item, i) => {
83
+ const label = `entry ${i}`;
84
+ if (!item || typeof item !== 'object') {
85
+ out.warnings.push(`${label}: not an object; skipped`);
86
+ return;
87
+ }
88
+ const e = item;
89
+ if (typeof e.name !== 'string' || !SCHEDULED_COMMANDS.NAME_PATTERN.test(e.name)) {
90
+ out.warnings.push(`${label}: name must match ${SCHEDULED_COMMANDS.NAME_PATTERN}; skipped`);
91
+ return;
92
+ }
93
+ if (seen.has(e.name)) {
94
+ out.warnings.push(`${label} (${e.name}): duplicate name; skipped`);
95
+ return;
96
+ }
97
+ if (typeof e.command !== 'string' || e.command.trim() === '') {
98
+ out.warnings.push(`${label} (${e.name}): command is required; skipped`);
99
+ return;
100
+ }
101
+ if (typeof e.cwd !== 'string' || !path.isAbsolute(expandHome(e.cwd))) {
102
+ out.warnings.push(`${label} (${e.name}): cwd must be an absolute path; skipped`);
103
+ return;
104
+ }
105
+ if (e.args !== undefined && (!Array.isArray(e.args) || e.args.some((a) => typeof a !== 'string'))) {
106
+ out.warnings.push(`${label} (${e.name}): args must be an array of strings; skipped`);
107
+ return;
108
+ }
109
+ if (typeof e.intervalMinutes !== 'number' ||
110
+ !Number.isFinite(e.intervalMinutes) ||
111
+ e.intervalMinutes < SCHEDULED_COMMANDS.MIN_INTERVAL_MINUTES) {
112
+ out.warnings.push(`${label} (${e.name}): intervalMinutes must be a number >= ${SCHEDULED_COMMANDS.MIN_INTERVAL_MINUTES}; skipped`);
113
+ return;
114
+ }
115
+ if (e.lockFile !== undefined && (typeof e.lockFile !== 'string' || !path.isAbsolute(expandHome(e.lockFile)))) {
116
+ out.warnings.push(`${label} (${e.name}): lockFile must be an absolute path; skipped`);
117
+ return;
118
+ }
119
+ seen.add(e.name);
120
+ if (e.enabled === false) {
121
+ out.disabled++;
122
+ return;
123
+ }
124
+ out.entries.push({
125
+ name: e.name,
126
+ cwd: expandHome(e.cwd),
127
+ command: e.command,
128
+ args: e.args ?? [],
129
+ intervalMinutes: e.intervalMinutes,
130
+ lockFile: typeof e.lockFile === 'string' ? expandHome(e.lockFile) : undefined,
131
+ });
132
+ });
133
+ return out;
134
+ }
135
+ /**
136
+ * Interval runner for owner-configured host commands.
137
+ */
138
+ export class ScheduledCommandsService {
139
+ deps;
140
+ entries = [];
141
+ timers = [];
142
+ /** Last spawned pid per entry name */
143
+ lastPid = new Map();
144
+ /**
145
+ * @param deps - Paths, logger and optional process/filesystem overrides
146
+ */
147
+ constructor(deps) {
148
+ this.deps = {
149
+ spawn: nodeSpawn,
150
+ isPidAlive: defaultIsPidAlive,
151
+ readFile: (f) => readFileSync(f, 'utf-8'),
152
+ pathExists: existsSync,
153
+ openLog: (f) => {
154
+ mkdirSync(path.dirname(f), { recursive: true });
155
+ return openSync(f, 'a');
156
+ },
157
+ closeLog: closeSync,
158
+ ...deps,
159
+ };
160
+ }
161
+ /**
162
+ * Load the config and schedule every enabled entry.
163
+ *
164
+ * Logs "N scheduled command(s) loaded" every time, including 0.
165
+ * A missing file is the normal off state (info); an unreadable or
166
+ * invalid file logs one warn line.
167
+ *
168
+ * @returns Number of jobs scheduled
169
+ */
170
+ start() {
171
+ this.stop();
172
+ let text;
173
+ try {
174
+ text = this.deps.readFile(this.deps.configPath);
175
+ }
176
+ catch (err) {
177
+ if (err.code === 'ENOENT') {
178
+ this.deps.logger.info('0 scheduled command(s) loaded (no config file)', { path: this.deps.configPath });
179
+ }
180
+ else {
181
+ this.deps.logger.warn('0 scheduled command(s) loaded: config unreadable', {
182
+ path: this.deps.configPath,
183
+ error: err instanceof Error ? err.message : String(err),
184
+ });
185
+ }
186
+ return 0;
187
+ }
188
+ const loaded = parseScheduledCommands(text);
189
+ loaded.warnings.forEach((w) => this.deps.logger.warn(`scheduled-commands: ${w}`));
190
+ this.entries = loaded.entries;
191
+ this.deps.logger.info(`${loaded.entries.length} scheduled command(s) loaded`, {
192
+ disabled: loaded.disabled,
193
+ invalid: loaded.warnings.length,
194
+ names: loaded.entries.map((e) => e.name),
195
+ });
196
+ for (const entry of this.entries) {
197
+ const every = entry.intervalMinutes * SCHEDULED_COMMANDS.MS_PER_MINUTE;
198
+ const first = setTimeout(() => this.runEntry(entry), SCHEDULED_COMMANDS.INITIAL_DELAY_MS);
199
+ const repeat = setInterval(() => this.runEntry(entry), every);
200
+ first.unref();
201
+ repeat.unref();
202
+ this.timers.push(first, repeat);
203
+ }
204
+ return this.entries.length;
205
+ }
206
+ /** Cancel all timers. Spawned children are NOT touched (they are detached). */
207
+ stop() {
208
+ for (const t of this.timers) {
209
+ clearTimeout(t);
210
+ clearInterval(t);
211
+ }
212
+ this.timers = [];
213
+ this.entries = [];
214
+ }
215
+ /**
216
+ * Try to run one entry now.
217
+ *
218
+ * Skips (debug log) when the previous run or the command's own lock holder
219
+ * is alive, or the cwd is missing (warn). Never claims a run happened
220
+ * when it was skipped.
221
+ *
222
+ * @param entry - Entry to run
223
+ * @returns What happened
224
+ */
225
+ runEntry(entry) {
226
+ const { logger, isPidAlive } = this.deps;
227
+ const prev = this.lastPid.get(entry.name);
228
+ if (prev !== undefined && isPidAlive(prev)) {
229
+ logger.debug(`scheduled-commands: ${entry.name} skipped, previous run pid ${prev} still alive`);
230
+ return 'skipped-running';
231
+ }
232
+ if (entry.lockFile) {
233
+ let holder = NaN;
234
+ try {
235
+ holder = parseInt(this.deps.readFile(entry.lockFile).trim(), 10);
236
+ }
237
+ catch {
238
+ // No lock file = nobody holds it.
239
+ }
240
+ if (Number.isInteger(holder) && isPidAlive(holder)) {
241
+ logger.debug(`scheduled-commands: ${entry.name} skipped, lock held by pid ${holder}`);
242
+ return 'skipped-lock';
243
+ }
244
+ }
245
+ if (!this.deps.pathExists(entry.cwd)) {
246
+ logger.warn(`scheduled-commands: ${entry.name} not run, cwd missing`, { cwd: entry.cwd });
247
+ return 'skipped-no-cwd';
248
+ }
249
+ let fd = -1;
250
+ try {
251
+ const logFile = path.join(this.deps.logDir, SCHEDULED_COMMANDS.LOG_FILE_TEMPLATE.replace('{name}', entry.name));
252
+ fd = this.deps.openLog(logFile);
253
+ const child = this.deps.spawn(entry.command, entry.args, {
254
+ cwd: entry.cwd,
255
+ detached: true,
256
+ stdio: ['ignore', fd, fd],
257
+ });
258
+ child.on('error', (err) => logger.error(`scheduled-commands: ${entry.name} failed to start`, { error: err.message }));
259
+ child.unref();
260
+ if (child.pid === undefined) {
261
+ logger.error(`scheduled-commands: ${entry.name} did not start (no pid)`);
262
+ return 'spawn-failed';
263
+ }
264
+ this.lastPid.set(entry.name, child.pid);
265
+ logger.info(`scheduled-commands: ${entry.name} started`, { pid: child.pid, cwd: entry.cwd });
266
+ return 'spawned';
267
+ }
268
+ catch (err) {
269
+ logger.error(`scheduled-commands: ${entry.name} spawn threw`, {
270
+ error: err instanceof Error ? err.message : String(err),
271
+ });
272
+ return 'spawn-failed';
273
+ }
274
+ finally {
275
+ if (fd >= 0) {
276
+ try {
277
+ this.deps.closeLog(fd);
278
+ }
279
+ catch {
280
+ // Already closed.
281
+ }
282
+ }
283
+ }
284
+ }
285
+ }
286
+ //# sourceMappingURL=scheduled-commands.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-commands.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/system/scheduled-commands.service.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "3665a7ae6aa59f814baa1d84c501a1c0135ef4e3",
3
- "builtAt": "2026-10-07T02:38:12.073Z"
2
+ "commit": "fc5d3138f87924ba2a7018dd3f9b31c3618ea654",
3
+ "builtAt": "2026-10-07T03:38:58.515Z"
4
4
  }
@@ -6989,4 +6989,24 @@ export declare const TEAM_PAUSE_CONSTANTS: {
6989
6989
  /** Metadata key on a WorkItem unassigned because its team was paused */
6990
6990
  readonly UNASSIGNED_METADATA_KEY: "pausedTeamUnassigned";
6991
6991
  };
6992
+ /**
6993
+ * Scheduled commands: an owner-edited list of host commands the backend runs
6994
+ * on an interval (e.g. the crewly-web release script). Read from disk only.
6995
+ */
6996
+ export declare const SCHEDULED_COMMANDS: {
6997
+ /** Config file under CREWLY_HOME; absent = feature off */
6998
+ readonly CONFIG_FILE: "scheduled-commands.json";
6999
+ /** Directory under CREWLY_HOME that receives each command's stdout/stderr */
7000
+ readonly LOG_DIR: "logs";
7001
+ /** Log file name pattern; `{name}` is the entry name */
7002
+ readonly LOG_FILE_TEMPLATE: "scheduled-{name}.log";
7003
+ /** Shortest interval accepted (minutes); smaller values are rejected */
7004
+ readonly MIN_INTERVAL_MINUTES: 1;
7005
+ /** Delay before the first run after boot (ms), so restarts do not starve a run */
7006
+ readonly INITIAL_DELAY_MS: 30000;
7007
+ /** Entry names: letters, digits, dot, dash, underscore */
7008
+ readonly NAME_PATTERN: RegExp;
7009
+ /** Milliseconds per minute */
7010
+ readonly MS_PER_MINUTE: 60000;
7011
+ };
6992
7012
  //# sourceMappingURL=constants.d.ts.map