crewly 1.20.303 → 1.20.304
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.
- package/config/skills/orchestrator/move-team/SKILL.md +53 -0
- package/config/skills/orchestrator/move-team/execute.sh +42 -0
- package/dist/backend/backend/src/constants.d.ts +69 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +69 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/team-move/team-move.controller.d.ts +45 -0
- package/dist/backend/backend/src/controllers/team-move/team-move.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/team-move/team-move.controller.js +214 -0
- package/dist/backend/backend/src/controllers/team-move/team-move.controller.js.map +1 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +22 -3
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +3 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.js +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
- package/dist/backend/backend/src/services/core/storage.service.d.ts +18 -0
- package/dist/backend/backend/src/services/core/storage.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/core/storage.service.js +16 -0
- package/dist/backend/backend/src/services/core/storage.service.js.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision.service.d.ts +19 -0
- package/dist/backend/backend/src/services/decisions/decision.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision.service.js +46 -0
- package/dist/backend/backend/src/services/decisions/decision.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js +2 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +9 -0
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +25 -0
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +19 -0
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js +41 -0
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/backend/backend/src/services/team-move/team-move-archive.d.ts +122 -0
- package/dist/backend/backend/src/services/team-move/team-move-archive.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-archive.js +202 -0
- package/dist/backend/backend/src/services/team-move/team-move-archive.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-chat.d.ts +59 -0
- package/dist/backend/backend/src/services/team-move/team-move-chat.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-chat.js +133 -0
- package/dist/backend/backend/src/services/team-move/team-move-chat.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-cloud.client.d.ts +102 -0
- package/dist/backend/backend/src/services/team-move/team-move-cloud.client.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-cloud.client.js +217 -0
- package/dist/backend/backend/src/services/team-move/team-move-cloud.client.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-crypto.d.ts +102 -0
- package/dist/backend/backend/src/services/team-move/team-move-crypto.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-crypto.js +348 -0
- package/dist/backend/backend/src/services/team-move/team-move-crypto.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-driver.service.d.ts +209 -0
- package/dist/backend/backend/src/services/team-move/team-move-driver.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-driver.service.js +504 -0
- package/dist/backend/backend/src/services/team-move/team-move-driver.service.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-exporter.d.ts +156 -0
- package/dist/backend/backend/src/services/team-move/team-move-exporter.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-exporter.js +462 -0
- package/dist/backend/backend/src/services/team-move/team-move-exporter.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-fs.d.ts +157 -0
- package/dist/backend/backend/src/services/team-move/team-move-fs.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-fs.js +298 -0
- package/dist/backend/backend/src/services/team-move/team-move-fs.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-git.d.ts +69 -0
- package/dist/backend/backend/src/services/team-move/team-move-git.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-git.js +198 -0
- package/dist/backend/backend/src/services/team-move/team-move-git.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-importer.d.ts +121 -0
- package/dist/backend/backend/src/services/team-move/team-move-importer.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-importer.js +462 -0
- package/dist/backend/backend/src/services/team-move/team-move-importer.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-journal.d.ts +110 -0
- package/dist/backend/backend/src/services/team-move/team-move-journal.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-journal.js +198 -0
- package/dist/backend/backend/src/services/team-move/team-move-journal.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-preflight.d.ts +71 -0
- package/dist/backend/backend/src/services/team-move/team-move-preflight.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-preflight.js +102 -0
- package/dist/backend/backend/src/services/team-move/team-move-preflight.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-runtime.d.ts +29 -0
- package/dist/backend/backend/src/services/team-move/team-move-runtime.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-runtime.js +173 -0
- package/dist/backend/backend/src/services/team-move/team-move-runtime.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-secret-scan.d.ts +50 -0
- package/dist/backend/backend/src/services/team-move/team-move-secret-scan.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-secret-scan.js +96 -0
- package/dist/backend/backend/src/services/team-move/team-move-secret-scan.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-select.d.ts +94 -0
- package/dist/backend/backend/src/services/team-move/team-move-select.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move-select.js +124 -0
- package/dist/backend/backend/src/services/team-move/team-move-select.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move.fixture.d.ts +46 -0
- package/dist/backend/backend/src/services/team-move/team-move.fixture.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move.fixture.js +202 -0
- package/dist/backend/backend/src/services/team-move/team-move.fixture.js.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move.types.d.ts +269 -0
- package/dist/backend/backend/src/services/team-move/team-move.types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-move/team-move.types.js +44 -0
- package/dist/backend/backend/src/services/team-move/team-move.types.js.map +1 -0
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +69 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +69 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.js +1 -0
- package/dist/cli/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
- package/dist/cli/backend/src/services/core/storage.service.d.ts +18 -0
- package/dist/cli/backend/src/services/core/storage.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/core/storage.service.js +16 -0
- package/dist/cli/backend/src/services/core/storage.service.js.map +1 -1
- package/dist/cli/backend/src/services/decisions/decision.service.d.ts +19 -0
- package/dist/cli/backend/src/services/decisions/decision.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/decisions/decision.service.js +46 -0
- package/dist/cli/backend/src/services/decisions/decision.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +9 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +25 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +19 -0
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js +41 -0
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/cli/cli/src/commands/team-move.d.ts +68 -0
- package/dist/cli/cli/src/commands/team-move.d.ts.map +1 -0
- package/dist/cli/cli/src/commands/team-move.js +155 -0
- package/dist/cli/cli/src/commands/team-move.js.map +1 -0
- package/dist/cli/cli/src/index.js +33 -0
- package/dist/cli/cli/src/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Team move journal — resumable, idempotent steps.
|
|
3
|
+
*
|
|
4
|
+
* Every move keeps a directory `~/.crewly/team-moves/<moveId>/` with
|
|
5
|
+
* `journal.json` (which steps finished and what they produced) and
|
|
6
|
+
* `state.json` (what Cloud last asked for). A step that is recorded as done is
|
|
7
|
+
* skipped when the move is resumed after a crash or restart, and the step
|
|
8
|
+
* functions themselves are written so that running one twice is harmless.
|
|
9
|
+
*
|
|
10
|
+
* @module services/team-move/team-move-journal
|
|
11
|
+
*/
|
|
12
|
+
import * as fs from 'node:fs/promises';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import { TEAM_MOVE_CONSTANTS } from '../../constants.js';
|
|
15
|
+
import { atomicWriteJson, safeReadJson } from '../../utils/file-io.utils.js';
|
|
16
|
+
import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
|
|
17
|
+
/** Move ids are Cloud-made; accept only path-safe ones. */
|
|
18
|
+
const MOVE_ID_PATTERN = /^[A-Za-z0-9_-]{8,64}$/;
|
|
19
|
+
/**
|
|
20
|
+
* Whether a string is a usable move id.
|
|
21
|
+
*
|
|
22
|
+
* @param value - Candidate
|
|
23
|
+
* @returns True for path-safe ids
|
|
24
|
+
*/
|
|
25
|
+
export function isMoveId(value) {
|
|
26
|
+
return typeof value === 'string' && MOVE_ID_PATTERN.test(value);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Directory of all moves on this machine.
|
|
30
|
+
*
|
|
31
|
+
* @param home - Crewly home (default: the process's)
|
|
32
|
+
* @returns Absolute path
|
|
33
|
+
*/
|
|
34
|
+
export function teamMovesRoot(home = getCrewlyHomePath()) {
|
|
35
|
+
return path.join(home, TEAM_MOVE_CONSTANTS.HOME_DIR);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Directory of one move.
|
|
39
|
+
*
|
|
40
|
+
* @param moveId - Move id
|
|
41
|
+
* @param home - Crewly home
|
|
42
|
+
* @returns Absolute path
|
|
43
|
+
* @throws Error for an id that is not path-safe
|
|
44
|
+
*/
|
|
45
|
+
export function moveDirFor(moveId, home = getCrewlyHomePath()) {
|
|
46
|
+
if (!isMoveId(moveId))
|
|
47
|
+
throw new Error('invalid move id');
|
|
48
|
+
return path.join(teamMovesRoot(home), moveId);
|
|
49
|
+
}
|
|
50
|
+
/** Journal and state of one move. */
|
|
51
|
+
export class MoveJournal {
|
|
52
|
+
moveId;
|
|
53
|
+
dir;
|
|
54
|
+
journalFile;
|
|
55
|
+
stateFile;
|
|
56
|
+
entries = null;
|
|
57
|
+
now;
|
|
58
|
+
/**
|
|
59
|
+
* @param moveId - Move id
|
|
60
|
+
* @param home - Crewly home
|
|
61
|
+
* @param now - Clock (tests)
|
|
62
|
+
*/
|
|
63
|
+
constructor(moveId, home = getCrewlyHomePath(), now = () => new Date().toISOString()) {
|
|
64
|
+
this.moveId = moveId;
|
|
65
|
+
this.dir = moveDirFor(moveId, home);
|
|
66
|
+
this.journalFile = path.join(this.dir, 'journal.json');
|
|
67
|
+
this.stateFile = path.join(this.dir, 'state.json');
|
|
68
|
+
this.now = now;
|
|
69
|
+
}
|
|
70
|
+
/** Create the move directory. */
|
|
71
|
+
async ensure() {
|
|
72
|
+
await fs.mkdir(this.dir, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
async load() {
|
|
75
|
+
if (!this.entries) {
|
|
76
|
+
const raw = await safeReadJson(this.journalFile, { entries: [] });
|
|
77
|
+
this.entries = Array.isArray(raw.entries) ? raw.entries : [];
|
|
78
|
+
}
|
|
79
|
+
return this.entries;
|
|
80
|
+
}
|
|
81
|
+
async append(entry) {
|
|
82
|
+
const entries = await this.load();
|
|
83
|
+
entries.push(entry);
|
|
84
|
+
await this.ensure();
|
|
85
|
+
await atomicWriteJson(this.journalFile, { entries });
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* All entries, oldest first.
|
|
89
|
+
*
|
|
90
|
+
* @returns Journal entries
|
|
91
|
+
*/
|
|
92
|
+
async list() {
|
|
93
|
+
return [...(await this.load())];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Whether a step finished.
|
|
97
|
+
*
|
|
98
|
+
* @param step - Step name
|
|
99
|
+
* @returns True when its last entry is `done`
|
|
100
|
+
*/
|
|
101
|
+
async isDone(step) {
|
|
102
|
+
const entries = await this.load();
|
|
103
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
104
|
+
if (entries[i].step === step)
|
|
105
|
+
return entries[i].state === 'done';
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* What a finished step recorded.
|
|
111
|
+
*
|
|
112
|
+
* @param step - Step name
|
|
113
|
+
* @returns The `detail` of its last `done` entry, or undefined
|
|
114
|
+
*/
|
|
115
|
+
async detailOf(step) {
|
|
116
|
+
const entries = await this.load();
|
|
117
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
118
|
+
const e = entries[i];
|
|
119
|
+
if (e.step === step && e.state === 'done')
|
|
120
|
+
return e.detail;
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Run a step once. A step already `done` is skipped and its recorded detail
|
|
126
|
+
* returned; a failure is recorded and rethrown.
|
|
127
|
+
*
|
|
128
|
+
* @param step - Step name
|
|
129
|
+
* @param fn - The step; its return value is stored as the step's detail
|
|
130
|
+
* @returns The step's detail (fresh or recorded)
|
|
131
|
+
*/
|
|
132
|
+
async run(step, fn) {
|
|
133
|
+
if (await this.isDone(step))
|
|
134
|
+
return (await this.detailOf(step));
|
|
135
|
+
await this.append({ step, state: 'started', at: this.now() });
|
|
136
|
+
try {
|
|
137
|
+
const detail = await fn();
|
|
138
|
+
await this.append({ step, state: 'done', at: this.now(), ...(detail === undefined ? {} : { detail }) });
|
|
139
|
+
return detail;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
await this.append({ step, state: 'failed', at: this.now(), error: error instanceof Error ? error.message : String(error) });
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Forget a step so it runs again (e.g. a retry after the owner fixed something).
|
|
148
|
+
*
|
|
149
|
+
* @param step - Step name
|
|
150
|
+
*/
|
|
151
|
+
async reset(step) {
|
|
152
|
+
const entries = (await this.load()).filter((e) => e.step !== step);
|
|
153
|
+
this.entries = entries;
|
|
154
|
+
await this.ensure();
|
|
155
|
+
await atomicWriteJson(this.journalFile, { entries });
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Read `state.json`.
|
|
159
|
+
*
|
|
160
|
+
* @returns The state, or null before the first write
|
|
161
|
+
*/
|
|
162
|
+
async readState() {
|
|
163
|
+
return safeReadJson(this.stateFile, null);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Merge a patch into `state.json`.
|
|
167
|
+
*
|
|
168
|
+
* @param patch - Fields to set
|
|
169
|
+
* @returns The new state
|
|
170
|
+
*/
|
|
171
|
+
async writeState(patch) {
|
|
172
|
+
const current = await this.readState();
|
|
173
|
+
const next = {
|
|
174
|
+
moveId: this.moveId,
|
|
175
|
+
teamId: current?.teamId ?? patch.teamId ?? '',
|
|
176
|
+
teamName: current?.teamName ?? patch.teamName ?? '',
|
|
177
|
+
action: current?.action ?? null,
|
|
178
|
+
phase: current?.phase ?? 'idle',
|
|
179
|
+
...current,
|
|
180
|
+
...patch,
|
|
181
|
+
updatedAt: this.now(),
|
|
182
|
+
};
|
|
183
|
+
await this.ensure();
|
|
184
|
+
await atomicWriteJson(this.stateFile, next);
|
|
185
|
+
return next;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Ids of the moves that have a directory on this machine.
|
|
190
|
+
*
|
|
191
|
+
* @param home - Crewly home
|
|
192
|
+
* @returns Move ids
|
|
193
|
+
*/
|
|
194
|
+
export async function listLocalMoveIds(home = getCrewlyHomePath()) {
|
|
195
|
+
const entries = await fs.readdir(teamMovesRoot(home), { withFileTypes: true }).catch(() => []);
|
|
196
|
+
return entries.filter((e) => e.isDirectory() && isMoveId(e.name)).map((e) => e.name);
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=team-move-journal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team-move-journal.js","sourceRoot":"","sources":["../../../../../../backend/src/services/team-move/team-move-journal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Target preflight: can this machine take a team? Reports its Crewly version,
|
|
3
|
+
* free disk space, the machine-only tools the team needs and which agent
|
|
4
|
+
* runtimes are logged in. Cloud shows the result to the owner and refuses a
|
|
5
|
+
* target that is too old or full.
|
|
6
|
+
*
|
|
7
|
+
* @module services/team-move/team-move-preflight
|
|
8
|
+
*/
|
|
9
|
+
import type { TeamMovePreflight } from './team-move.types.js';
|
|
10
|
+
/** Collaborators of the preflight (all injectable for tests). */
|
|
11
|
+
export interface PreflightDeps {
|
|
12
|
+
crewlyVersion: string;
|
|
13
|
+
/** Directory whose file system must hold the package (default: the home). */
|
|
14
|
+
diskPath: string;
|
|
15
|
+
/** Free bytes under `diskPath`; null when it cannot be read. */
|
|
16
|
+
freeDisk?: (dir: string) => Promise<number | null>;
|
|
17
|
+
/** Whether a command is on PATH. */
|
|
18
|
+
hasCommand?: (binary: string) => boolean;
|
|
19
|
+
/** Which runtimes are installed and logged in, by runtime id. */
|
|
20
|
+
runtimeLogins?: () => Promise<Record<string, boolean>>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Free bytes of the file system holding a directory.
|
|
24
|
+
*
|
|
25
|
+
* @param dir - Any path on it (the nearest existing parent is used)
|
|
26
|
+
* @returns Bytes available to this user, or null
|
|
27
|
+
*/
|
|
28
|
+
export declare function freeDiskBytes(dir: string): Promise<number | null>;
|
|
29
|
+
/**
|
|
30
|
+
* Whether a command is on PATH (including the directories Crewly adds for its
|
|
31
|
+
* harnesses and the usual Homebrew/Linux locations a launchd-started backend
|
|
32
|
+
* does not inherit).
|
|
33
|
+
*
|
|
34
|
+
* @param binary - Command name
|
|
35
|
+
* @returns True when found
|
|
36
|
+
*/
|
|
37
|
+
export declare function commandOnPath(binary: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Check which of a list of tools exist.
|
|
40
|
+
*
|
|
41
|
+
* @param tools - Tool names (keys of TEAM_MOVE_CONSTANTS.TOOL_BINARIES)
|
|
42
|
+
* @param hasCommand - PATH check
|
|
43
|
+
* @returns Tool name to present/absent
|
|
44
|
+
*/
|
|
45
|
+
export declare function checkTools(tools: readonly string[], hasCommand?: (binary: string) => boolean): Record<string, boolean>;
|
|
46
|
+
/**
|
|
47
|
+
* Preflight of this machine as the target of a move.
|
|
48
|
+
*
|
|
49
|
+
* @param deps - Version, disk path and probes
|
|
50
|
+
* @param requiredTools - Tools to check (the full known list when the package is not known yet)
|
|
51
|
+
* @returns What Cloud receives with the target's public key
|
|
52
|
+
*/
|
|
53
|
+
export declare function runPreflight(deps: PreflightDeps, requiredTools?: readonly string[]): Promise<TeamMovePreflight>;
|
|
54
|
+
/**
|
|
55
|
+
* Whether a version can take part in a move.
|
|
56
|
+
*
|
|
57
|
+
* @param version - Crewly version
|
|
58
|
+
* @returns True when at least {@link TEAM_MOVE_CONSTANTS.MIN_CREWLY_VERSION}
|
|
59
|
+
*/
|
|
60
|
+
export declare function isVersionCompatible(version: string): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Whether a disk can hold a package of the given size once it is downloaded
|
|
63
|
+
* and unpacked (ciphertext + unpacked copy + headroom).
|
|
64
|
+
*
|
|
65
|
+
* @param freeBytes - Free bytes
|
|
66
|
+
* @param packageBytes - Ciphertext size
|
|
67
|
+
* @param unpackedBytes - Uncompressed size from the manifest
|
|
68
|
+
* @returns True when there is room
|
|
69
|
+
*/
|
|
70
|
+
export declare function hasRoomFor(freeBytes: number, packageBytes: number, unpackedBytes: number): boolean;
|
|
71
|
+
//# sourceMappingURL=team-move-preflight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team-move-preflight.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/team-move/team-move-preflight.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,iEAAiE;AACjE,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACnD,oCAAoC;IACpC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC,iEAAiE;IACjE,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACxD;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAavE;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAIrD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,UAAU,GAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAuB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKrI;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,aAAa,EAAE,aAAa,GAAE,SAAS,MAAM,EAAoC,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAStJ;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAElG"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Target preflight: can this machine take a team? Reports its Crewly version,
|
|
3
|
+
* free disk space, the machine-only tools the team needs and which agent
|
|
4
|
+
* runtimes are logged in. Cloud shows the result to the owner and refuses a
|
|
5
|
+
* target that is too old or full.
|
|
6
|
+
*
|
|
7
|
+
* @module services/team-move/team-move-preflight
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from 'node:fs/promises';
|
|
10
|
+
import * as os from 'node:os';
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
import { TEAM_MOVE_CONSTANTS } from '../../constants.js';
|
|
13
|
+
import { buildHarnessPath, resolveExecutable } from '../harness/harness-exec.utils.js';
|
|
14
|
+
import { compareVersions } from '../harness/harness-status.service.js';
|
|
15
|
+
/**
|
|
16
|
+
* Free bytes of the file system holding a directory.
|
|
17
|
+
*
|
|
18
|
+
* @param dir - Any path on it (the nearest existing parent is used)
|
|
19
|
+
* @returns Bytes available to this user, or null
|
|
20
|
+
*/
|
|
21
|
+
export async function freeDiskBytes(dir) {
|
|
22
|
+
let probe = dir;
|
|
23
|
+
for (let i = 0; i < 10; i++) {
|
|
24
|
+
try {
|
|
25
|
+
const stat = await fs.statfs(probe);
|
|
26
|
+
return Number(stat.bavail) * Number(stat.bsize);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
const parent = path.dirname(probe);
|
|
30
|
+
if (parent === probe)
|
|
31
|
+
return null;
|
|
32
|
+
probe = parent;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Whether a command is on PATH (including the directories Crewly adds for its
|
|
39
|
+
* harnesses and the usual Homebrew/Linux locations a launchd-started backend
|
|
40
|
+
* does not inherit).
|
|
41
|
+
*
|
|
42
|
+
* @param binary - Command name
|
|
43
|
+
* @returns True when found
|
|
44
|
+
*/
|
|
45
|
+
export function commandOnPath(binary) {
|
|
46
|
+
const extra = ['/opt/homebrew/bin', '/usr/local/bin', '/usr/bin', '/bin', path.join(os.homedir(), '.local', 'bin')];
|
|
47
|
+
const searchPath = [buildHarnessPath(process.env['PATH']), ...extra].join(path.delimiter);
|
|
48
|
+
return resolveExecutable(binary, searchPath) !== null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Check which of a list of tools exist.
|
|
52
|
+
*
|
|
53
|
+
* @param tools - Tool names (keys of TEAM_MOVE_CONSTANTS.TOOL_BINARIES)
|
|
54
|
+
* @param hasCommand - PATH check
|
|
55
|
+
* @returns Tool name to present/absent
|
|
56
|
+
*/
|
|
57
|
+
export function checkTools(tools, hasCommand = commandOnPath) {
|
|
58
|
+
const binaries = TEAM_MOVE_CONSTANTS.TOOL_BINARIES;
|
|
59
|
+
const out = {};
|
|
60
|
+
for (const tool of tools)
|
|
61
|
+
out[tool] = hasCommand(binaries[tool] ?? tool);
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Preflight of this machine as the target of a move.
|
|
66
|
+
*
|
|
67
|
+
* @param deps - Version, disk path and probes
|
|
68
|
+
* @param requiredTools - Tools to check (the full known list when the package is not known yet)
|
|
69
|
+
* @returns What Cloud receives with the target's public key
|
|
70
|
+
*/
|
|
71
|
+
export async function runPreflight(deps, requiredTools = TEAM_MOVE_CONSTANTS.KNOWN_TOOLS) {
|
|
72
|
+
const free = await (deps.freeDisk ?? freeDiskBytes)(deps.diskPath);
|
|
73
|
+
const runtimes = deps.runtimeLogins ? await deps.runtimeLogins().catch(() => ({})) : {};
|
|
74
|
+
return {
|
|
75
|
+
crewlyVersion: deps.crewlyVersion,
|
|
76
|
+
freeDiskBytes: free ?? 0,
|
|
77
|
+
tools: checkTools(requiredTools, deps.hasCommand),
|
|
78
|
+
runtimes,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Whether a version can take part in a move.
|
|
83
|
+
*
|
|
84
|
+
* @param version - Crewly version
|
|
85
|
+
* @returns True when at least {@link TEAM_MOVE_CONSTANTS.MIN_CREWLY_VERSION}
|
|
86
|
+
*/
|
|
87
|
+
export function isVersionCompatible(version) {
|
|
88
|
+
return /^\d+\.\d+\.\d+/.test(version) && compareVersions(version, TEAM_MOVE_CONSTANTS.MIN_CREWLY_VERSION) >= 0;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Whether a disk can hold a package of the given size once it is downloaded
|
|
92
|
+
* and unpacked (ciphertext + unpacked copy + headroom).
|
|
93
|
+
*
|
|
94
|
+
* @param freeBytes - Free bytes
|
|
95
|
+
* @param packageBytes - Ciphertext size
|
|
96
|
+
* @param unpackedBytes - Uncompressed size from the manifest
|
|
97
|
+
* @returns True when there is room
|
|
98
|
+
*/
|
|
99
|
+
export function hasRoomFor(freeBytes, packageBytes, unpackedBytes) {
|
|
100
|
+
return freeBytes >= packageBytes + unpackedBytes + TEAM_MOVE_CONSTANTS.DISK_HEADROOM_BYTES;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=team-move-preflight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team-move-preflight.js","sourceRoot":"","sources":["../../../../../../backend/src/services/team-move/team-move-preflight.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composition of the team move for the running backend: builds the exporter,
|
|
3
|
+
* importer, archiver and driver from the backend's own singletons (storage,
|
|
4
|
+
* task pool, decision cards, Slack team channels, harness status, device
|
|
5
|
+
* identity) so a move changes the live stores through their services and not
|
|
6
|
+
* behind their back.
|
|
7
|
+
*
|
|
8
|
+
* Starting and stopping a team goes through this backend's own REST API with
|
|
9
|
+
* the internal Cloud credential: the owner approved the move in Cloud, and the
|
|
10
|
+
* regular start/stop routes do the tmux and status bookkeeping.
|
|
11
|
+
*
|
|
12
|
+
* @module services/team-move/team-move-runtime
|
|
13
|
+
*/
|
|
14
|
+
import type { StorageService } from '../core/storage.service.js';
|
|
15
|
+
import { TeamMoveDriver, type TeamMoveRelaySource } from './team-move-driver.service.js';
|
|
16
|
+
/** Inputs of the composition. */
|
|
17
|
+
export interface TeamMoveRuntimeInput {
|
|
18
|
+
storage: StorageService;
|
|
19
|
+
relay: TeamMoveRelaySource;
|
|
20
|
+
home?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build the driver for the running backend.
|
|
24
|
+
*
|
|
25
|
+
* @param input - Storage and the relay event source
|
|
26
|
+
* @returns The driver (not started)
|
|
27
|
+
*/
|
|
28
|
+
export declare function createTeamMoveDriver(input: TeamMoveRuntimeInput): TeamMoveDriver;
|
|
29
|
+
//# sourceMappingURL=team-move-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team-move-runtime.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/team-move/team-move-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAWH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAUjE,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAKzF,iCAAiC;AACjC,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,cAAc,CAAC;IACxB,KAAK,EAAE,mBAAmB,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA0CD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,oBAAoB,GAAG,cAAc,CAkGhF"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composition of the team move for the running backend: builds the exporter,
|
|
3
|
+
* importer, archiver and driver from the backend's own singletons (storage,
|
|
4
|
+
* task pool, decision cards, Slack team channels, harness status, device
|
|
5
|
+
* identity) so a move changes the live stores through their services and not
|
|
6
|
+
* behind their back.
|
|
7
|
+
*
|
|
8
|
+
* Starting and stopping a team goes through this backend's own REST API with
|
|
9
|
+
* the internal Cloud credential: the owner approved the move in Cloud, and the
|
|
10
|
+
* regular start/stop routes do the tmux and status bookkeeping.
|
|
11
|
+
*
|
|
12
|
+
* @module services/team-move/team-move-runtime
|
|
13
|
+
*/
|
|
14
|
+
import * as path from 'node:path';
|
|
15
|
+
import * as fs from 'node:fs/promises';
|
|
16
|
+
import { safeReadJson, atomicWriteJson } from '../../utils/file-io.utils.js';
|
|
17
|
+
import { collectSecretEnvValues } from '../../utils/secret-env.js';
|
|
18
|
+
import { getLocalApiBaseUrl } from '../../utils/local-api-url.utils.js';
|
|
19
|
+
import { CloudClientService } from '../cloud/cloud-client.service.js';
|
|
20
|
+
import { DeviceIdentityService } from '../cloud/device-identity.service.js';
|
|
21
|
+
import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
|
|
22
|
+
import { internalCredentialHeaders } from '../core/owner-auth.service.js';
|
|
23
|
+
import { DecisionService } from '../decisions/decision.service.js';
|
|
24
|
+
import { getHarnessService } from '../harness/harness.service.js';
|
|
25
|
+
import { collectSettingsSecretValues } from '../security/secret-scrub.service.js';
|
|
26
|
+
import { getSlackTeamChannelService } from '../slack/slack-team-channel.service.js';
|
|
27
|
+
import { TaskPoolService } from '../task-pool/task-pool.service.js';
|
|
28
|
+
import { VersionCheckService } from '../system/version-check.service.js';
|
|
29
|
+
import { TeamMoveArchiver } from './team-move-archive.js';
|
|
30
|
+
import { TeamMoveCloudClient } from './team-move-cloud.client.js';
|
|
31
|
+
import { TeamMoveDriver } from './team-move-driver.service.js';
|
|
32
|
+
import { TeamMoveExporter } from './team-move-exporter.js';
|
|
33
|
+
import { TeamMoveImporter } from './team-move-importer.js';
|
|
34
|
+
import { freeDiskBytes, runPreflight } from './team-move-preflight.js';
|
|
35
|
+
/**
|
|
36
|
+
* Call a team route of this backend as the owner's Cloud-approved action.
|
|
37
|
+
*
|
|
38
|
+
* @param teamId - Team id
|
|
39
|
+
* @param action - `start` or `stop`
|
|
40
|
+
* @returns ok/error from the route
|
|
41
|
+
*/
|
|
42
|
+
async function teamRoute(teamId, action) {
|
|
43
|
+
try {
|
|
44
|
+
const res = await fetch(`${getLocalApiBaseUrl()}/api/teams/${encodeURIComponent(teamId)}/${action}`, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: { 'Content-Type': 'application/json', ...internalCredentialHeaders('cloud') },
|
|
47
|
+
body: '{}',
|
|
48
|
+
signal: AbortSignal.timeout(120_000),
|
|
49
|
+
});
|
|
50
|
+
const body = (await res.json().catch(() => ({})));
|
|
51
|
+
return res.ok && body.success !== false ? { ok: true } : { ok: false, error: body.error ?? `HTTP ${res.status}` };
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Add a Slack channel mapping to the mapping file when the Slack service is
|
|
59
|
+
* not running (it loads the file when it starts).
|
|
60
|
+
*
|
|
61
|
+
* @param home - Crewly home
|
|
62
|
+
* @param mapping - Mapping to add
|
|
63
|
+
* @returns True when added
|
|
64
|
+
*/
|
|
65
|
+
async function adoptMappingInFile(home, mapping) {
|
|
66
|
+
const file = path.join(home, 'slack-team-channels.json');
|
|
67
|
+
const store = await safeReadJson(file, {});
|
|
68
|
+
const mappings = Array.isArray(store.mappings) ? store.mappings : [];
|
|
69
|
+
if (mappings.some((m) => m['teamId'] === mapping['teamId'] || m['slackChannelId'] === mapping['slackChannelId']))
|
|
70
|
+
return false;
|
|
71
|
+
await fs.mkdir(home, { recursive: true });
|
|
72
|
+
await atomicWriteJson(file, { version: 1, autoCreate: store.autoCreate !== false, channelPrefix: store.channelPrefix ?? '', ...store, mappings: [...mappings, mapping] });
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build the driver for the running backend.
|
|
77
|
+
*
|
|
78
|
+
* @param input - Storage and the relay event source
|
|
79
|
+
* @returns The driver (not started)
|
|
80
|
+
*/
|
|
81
|
+
export function createTeamMoveDriver(input) {
|
|
82
|
+
const home = input.home ?? getCrewlyHomePath();
|
|
83
|
+
const storage = input.storage;
|
|
84
|
+
const cloud = CloudClientService.getInstance();
|
|
85
|
+
const identity = async () => {
|
|
86
|
+
const id = await DeviceIdentityService.getInstance().getOrCreateIdentity();
|
|
87
|
+
return { instanceId: id.deviceId, deviceName: id.deviceName };
|
|
88
|
+
};
|
|
89
|
+
const version = () => VersionCheckService.getInstance().getLocalVersion();
|
|
90
|
+
const client = new TeamMoveCloudClient({
|
|
91
|
+
session: { getToken: () => cloud.getToken(), getCloudUrl: () => cloud.getCloudUrl(), tryRefreshToken: () => cloud.tryRefreshToken() },
|
|
92
|
+
getInstanceId: async () => (await identity()).instanceId,
|
|
93
|
+
});
|
|
94
|
+
const knownSecrets = async () => [...collectSecretEnvValues(process.env), ...(await collectSettingsSecretValues(home))];
|
|
95
|
+
const exporter = new TeamMoveExporter({
|
|
96
|
+
home,
|
|
97
|
+
getTeams: () => storage.getTeams(),
|
|
98
|
+
getProjects: () => storage.getProjects(),
|
|
99
|
+
crewlyVersion: version(),
|
|
100
|
+
source: { instanceId: null, deviceName: null },
|
|
101
|
+
knownSecrets,
|
|
102
|
+
});
|
|
103
|
+
const pool = {
|
|
104
|
+
read: async () => {
|
|
105
|
+
const svc = TaskPoolService.getInstance();
|
|
106
|
+
return { workItems: await svc.getAllItems(), claims: (await svc.getActiveClaims()) };
|
|
107
|
+
},
|
|
108
|
+
removeWorkItems: (ids) => TaskPoolService.getInstance().removeWorkItemsForMove(ids),
|
|
109
|
+
addWorkItems: (items) => TaskPoolService.getInstance().importWorkItems(items),
|
|
110
|
+
};
|
|
111
|
+
const archiver = new TeamMoveArchiver({
|
|
112
|
+
home,
|
|
113
|
+
storage,
|
|
114
|
+
runtime: {
|
|
115
|
+
stopTeam: async (teamId) => {
|
|
116
|
+
await teamRoute(teamId, 'stop');
|
|
117
|
+
},
|
|
118
|
+
startTeam: (teamId) => teamRoute(teamId, 'start'),
|
|
119
|
+
},
|
|
120
|
+
pool,
|
|
121
|
+
decisions: {
|
|
122
|
+
list: async () => ((await DecisionService.getInstance()?.list('all')) ?? []),
|
|
123
|
+
setStatus: async (id, status) => {
|
|
124
|
+
await DecisionService.getInstance()?.setMovedStatus([id], status === 'open');
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
slack: {
|
|
128
|
+
unlink: async (teamId) => {
|
|
129
|
+
const service = getSlackTeamChannelService();
|
|
130
|
+
const mapping = service?.findByTeamId(teamId) ?? null;
|
|
131
|
+
if (service && mapping)
|
|
132
|
+
await service.unlinkTeam(teamId, { archiveSlackChannel: false });
|
|
133
|
+
return mapping;
|
|
134
|
+
},
|
|
135
|
+
adopt: async (mapping) => {
|
|
136
|
+
const service = getSlackTeamChannelService();
|
|
137
|
+
return service ? service.adoptMapping(mapping) : adoptMappingInFile(home, mapping);
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
const runtimeLogins = async () => {
|
|
142
|
+
const statuses = await getHarnessService().status.listStatuses();
|
|
143
|
+
return Object.fromEntries(statuses.map((s) => [s.id, s.installed && s.loginState === 'logged_in']));
|
|
144
|
+
};
|
|
145
|
+
return new TeamMoveDriver({
|
|
146
|
+
home,
|
|
147
|
+
cloud: client,
|
|
148
|
+
source: input.relay,
|
|
149
|
+
identity,
|
|
150
|
+
exporter,
|
|
151
|
+
archiver,
|
|
152
|
+
importer: () => new TeamMoveImporter({
|
|
153
|
+
home,
|
|
154
|
+
crewlyVersion: version(),
|
|
155
|
+
storage,
|
|
156
|
+
addWorkItems: pool.addWorkItems,
|
|
157
|
+
addDecisions: async (decisions) => (await DecisionService.getInstance()?.importMoved(decisions)) ?? 0,
|
|
158
|
+
adoptSlackMapping: async (mapping) => {
|
|
159
|
+
const service = getSlackTeamChannelService();
|
|
160
|
+
return service ? service.adoptMapping(mapping) : adoptMappingInFile(home, mapping);
|
|
161
|
+
},
|
|
162
|
+
startTeam: (teamId) => teamRoute(teamId, 'start'),
|
|
163
|
+
runtimeLogins,
|
|
164
|
+
freeDisk: freeDiskBytes,
|
|
165
|
+
}),
|
|
166
|
+
preflight: (tools) => runPreflight({ crewlyVersion: version(), diskPath: home, runtimeLogins }, tools),
|
|
167
|
+
isTeamIdle: async (teamId) => {
|
|
168
|
+
const team = (await storage.getTeams()).find((t) => t.id === teamId);
|
|
169
|
+
return !team || team.members.every((m) => m.workingStatus !== 'in_progress');
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=team-move-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team-move-runtime.js","sourceRoot":"","sources":["../../../../../../backend/src/services/team-move/team-move-runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret scan of a package before it leaves the machine.
|
|
3
|
+
*
|
|
4
|
+
* Runs the same detectors as the session-log redactor (token shapes such as
|
|
5
|
+
* Slack, OpenAI/Anthropic, GitHub, Google keys, JWTs, bearer headers, private
|
|
6
|
+
* key blocks, connection strings and `SECRET_NAME=value` assignments) plus the
|
|
7
|
+
* exact secret values this machine holds in its environment and settings. It
|
|
8
|
+
* reports file paths and pattern counts only, never a matched value.
|
|
9
|
+
*
|
|
10
|
+
* @module services/team-move/team-move-secret-scan
|
|
11
|
+
*/
|
|
12
|
+
import type { SecretEnvValue } from '../../utils/secret-env.js';
|
|
13
|
+
import type { PackageFileEntry } from './team-move.types.js';
|
|
14
|
+
import type { StagingArea } from './team-move-fs.js';
|
|
15
|
+
/** One file that holds secrets. */
|
|
16
|
+
export interface SecretFinding {
|
|
17
|
+
/** Package path. */
|
|
18
|
+
path: string;
|
|
19
|
+
/** Number of secret matches (values are never returned). */
|
|
20
|
+
matches: number;
|
|
21
|
+
}
|
|
22
|
+
/** Result of a scan. */
|
|
23
|
+
export interface SecretScanResult {
|
|
24
|
+
filesScanned: number;
|
|
25
|
+
findings: SecretFinding[];
|
|
26
|
+
}
|
|
27
|
+
/** Thrown when a package would carry secrets. */
|
|
28
|
+
export declare class TeamMoveSecretScanError extends Error {
|
|
29
|
+
readonly findings: SecretFinding[];
|
|
30
|
+
constructor(findings: SecretFinding[]);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Scan every file of a staging area.
|
|
34
|
+
*
|
|
35
|
+
* @param staging - The package being built
|
|
36
|
+
* @param files - Its files
|
|
37
|
+
* @param known - Exact secret values (env, settings) to look for
|
|
38
|
+
* @returns Files scanned and the findings
|
|
39
|
+
*/
|
|
40
|
+
export declare function scanStaging(staging: StagingArea, files: readonly PackageFileEntry[], known?: readonly SecretEnvValue[]): Promise<SecretScanResult>;
|
|
41
|
+
/**
|
|
42
|
+
* Whether a flagged file may be left out of the package instead of aborting.
|
|
43
|
+
* Core records (the team config, task pool, decisions, chat records) can never
|
|
44
|
+
* be skipped; free-form notes and project files can.
|
|
45
|
+
*
|
|
46
|
+
* @param packagePath - Package path of the flagged file
|
|
47
|
+
* @returns True when skipping is allowed
|
|
48
|
+
*/
|
|
49
|
+
export declare function isSkippableFinding(packagePath: string): boolean;
|
|
50
|
+
//# sourceMappingURL=team-move-secret-scan.d.ts.map
|