crewly 1.20.303 → 1.20.305

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 (139) hide show
  1. package/config/skills/orchestrator/move-team/SKILL.md +53 -0
  2. package/config/skills/orchestrator/move-team/execute.sh +42 -0
  3. package/dist/backend/backend/src/constants.d.ts +82 -0
  4. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  5. package/dist/backend/backend/src/constants.js +82 -0
  6. package/dist/backend/backend/src/constants.js.map +1 -1
  7. package/dist/backend/backend/src/controllers/team-move/team-move.controller.d.ts +39 -0
  8. package/dist/backend/backend/src/controllers/team-move/team-move.controller.d.ts.map +1 -0
  9. package/dist/backend/backend/src/controllers/team-move/team-move.controller.js +211 -0
  10. package/dist/backend/backend/src/controllers/team-move/team-move.controller.js.map +1 -0
  11. package/dist/backend/backend/src/index.d.ts.map +1 -1
  12. package/dist/backend/backend/src/index.js +22 -3
  13. package/dist/backend/backend/src/index.js.map +1 -1
  14. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  15. package/dist/backend/backend/src/routes/api.routes.js +3 -0
  16. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  17. package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
  18. package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
  19. package/dist/backend/backend/src/services/cloud/cloud-sync.types.js +1 -0
  20. package/dist/backend/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
  21. package/dist/backend/backend/src/services/core/storage.service.d.ts +18 -0
  22. package/dist/backend/backend/src/services/core/storage.service.d.ts.map +1 -1
  23. package/dist/backend/backend/src/services/core/storage.service.js +16 -0
  24. package/dist/backend/backend/src/services/core/storage.service.js.map +1 -1
  25. package/dist/backend/backend/src/services/decisions/decision.service.d.ts +19 -0
  26. package/dist/backend/backend/src/services/decisions/decision.service.d.ts.map +1 -1
  27. package/dist/backend/backend/src/services/decisions/decision.service.js +46 -0
  28. package/dist/backend/backend/src/services/decisions/decision.service.js.map +1 -1
  29. package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
  30. package/dist/backend/backend/src/services/slack/slack-initializer.js +2 -1
  31. package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
  32. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +9 -0
  33. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  34. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +25 -0
  35. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  36. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +19 -0
  37. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  38. package/dist/backend/backend/src/services/task-pool/task-pool.service.js +41 -0
  39. package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  40. package/dist/backend/backend/src/services/team-move/team-move-archive.d.ts +122 -0
  41. package/dist/backend/backend/src/services/team-move/team-move-archive.d.ts.map +1 -0
  42. package/dist/backend/backend/src/services/team-move/team-move-archive.js +202 -0
  43. package/dist/backend/backend/src/services/team-move/team-move-archive.js.map +1 -0
  44. package/dist/backend/backend/src/services/team-move/team-move-chat.d.ts +59 -0
  45. package/dist/backend/backend/src/services/team-move/team-move-chat.d.ts.map +1 -0
  46. package/dist/backend/backend/src/services/team-move/team-move-chat.js +133 -0
  47. package/dist/backend/backend/src/services/team-move/team-move-chat.js.map +1 -0
  48. package/dist/backend/backend/src/services/team-move/team-move-cloud.client.d.ts +102 -0
  49. package/dist/backend/backend/src/services/team-move/team-move-cloud.client.d.ts.map +1 -0
  50. package/dist/backend/backend/src/services/team-move/team-move-cloud.client.js +217 -0
  51. package/dist/backend/backend/src/services/team-move/team-move-cloud.client.js.map +1 -0
  52. package/dist/backend/backend/src/services/team-move/team-move-crypto.d.ts +102 -0
  53. package/dist/backend/backend/src/services/team-move/team-move-crypto.d.ts.map +1 -0
  54. package/dist/backend/backend/src/services/team-move/team-move-crypto.js +348 -0
  55. package/dist/backend/backend/src/services/team-move/team-move-crypto.js.map +1 -0
  56. package/dist/backend/backend/src/services/team-move/team-move-driver.service.d.ts +209 -0
  57. package/dist/backend/backend/src/services/team-move/team-move-driver.service.d.ts.map +1 -0
  58. package/dist/backend/backend/src/services/team-move/team-move-driver.service.js +504 -0
  59. package/dist/backend/backend/src/services/team-move/team-move-driver.service.js.map +1 -0
  60. package/dist/backend/backend/src/services/team-move/team-move-exporter.d.ts +182 -0
  61. package/dist/backend/backend/src/services/team-move/team-move-exporter.d.ts.map +1 -0
  62. package/dist/backend/backend/src/services/team-move/team-move-exporter.js +537 -0
  63. package/dist/backend/backend/src/services/team-move/team-move-exporter.js.map +1 -0
  64. package/dist/backend/backend/src/services/team-move/team-move-fs.d.ts +157 -0
  65. package/dist/backend/backend/src/services/team-move/team-move-fs.d.ts.map +1 -0
  66. package/dist/backend/backend/src/services/team-move/team-move-fs.js +298 -0
  67. package/dist/backend/backend/src/services/team-move/team-move-fs.js.map +1 -0
  68. package/dist/backend/backend/src/services/team-move/team-move-git.d.ts +69 -0
  69. package/dist/backend/backend/src/services/team-move/team-move-git.d.ts.map +1 -0
  70. package/dist/backend/backend/src/services/team-move/team-move-git.js +198 -0
  71. package/dist/backend/backend/src/services/team-move/team-move-git.js.map +1 -0
  72. package/dist/backend/backend/src/services/team-move/team-move-importer.d.ts +121 -0
  73. package/dist/backend/backend/src/services/team-move/team-move-importer.d.ts.map +1 -0
  74. package/dist/backend/backend/src/services/team-move/team-move-importer.js +473 -0
  75. package/dist/backend/backend/src/services/team-move/team-move-importer.js.map +1 -0
  76. package/dist/backend/backend/src/services/team-move/team-move-journal.d.ts +110 -0
  77. package/dist/backend/backend/src/services/team-move/team-move-journal.d.ts.map +1 -0
  78. package/dist/backend/backend/src/services/team-move/team-move-journal.js +198 -0
  79. package/dist/backend/backend/src/services/team-move/team-move-journal.js.map +1 -0
  80. package/dist/backend/backend/src/services/team-move/team-move-machines.d.ts +62 -0
  81. package/dist/backend/backend/src/services/team-move/team-move-machines.d.ts.map +1 -0
  82. package/dist/backend/backend/src/services/team-move/team-move-machines.js +95 -0
  83. package/dist/backend/backend/src/services/team-move/team-move-machines.js.map +1 -0
  84. package/dist/backend/backend/src/services/team-move/team-move-preflight.d.ts +71 -0
  85. package/dist/backend/backend/src/services/team-move/team-move-preflight.d.ts.map +1 -0
  86. package/dist/backend/backend/src/services/team-move/team-move-preflight.js +102 -0
  87. package/dist/backend/backend/src/services/team-move/team-move-preflight.js.map +1 -0
  88. package/dist/backend/backend/src/services/team-move/team-move-runtime.d.ts +29 -0
  89. package/dist/backend/backend/src/services/team-move/team-move-runtime.d.ts.map +1 -0
  90. package/dist/backend/backend/src/services/team-move/team-move-runtime.js +173 -0
  91. package/dist/backend/backend/src/services/team-move/team-move-runtime.js.map +1 -0
  92. package/dist/backend/backend/src/services/team-move/team-move-secret-scan.d.ts +50 -0
  93. package/dist/backend/backend/src/services/team-move/team-move-secret-scan.d.ts.map +1 -0
  94. package/dist/backend/backend/src/services/team-move/team-move-secret-scan.js +96 -0
  95. package/dist/backend/backend/src/services/team-move/team-move-secret-scan.js.map +1 -0
  96. package/dist/backend/backend/src/services/team-move/team-move-select.d.ts +94 -0
  97. package/dist/backend/backend/src/services/team-move/team-move-select.d.ts.map +1 -0
  98. package/dist/backend/backend/src/services/team-move/team-move-select.js +124 -0
  99. package/dist/backend/backend/src/services/team-move/team-move-select.js.map +1 -0
  100. package/dist/backend/backend/src/services/team-move/team-move.fixture.d.ts +46 -0
  101. package/dist/backend/backend/src/services/team-move/team-move.fixture.d.ts.map +1 -0
  102. package/dist/backend/backend/src/services/team-move/team-move.fixture.js +202 -0
  103. package/dist/backend/backend/src/services/team-move/team-move.fixture.js.map +1 -0
  104. package/dist/backend/backend/src/services/team-move/team-move.types.d.ts +279 -0
  105. package/dist/backend/backend/src/services/team-move/team-move.types.d.ts.map +1 -0
  106. package/dist/backend/backend/src/services/team-move/team-move.types.js +44 -0
  107. package/dist/backend/backend/src/services/team-move/team-move.types.js.map +1 -0
  108. package/dist/backend/build-info.json +2 -2
  109. package/dist/cli/backend/src/constants.d.ts +82 -0
  110. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  111. package/dist/cli/backend/src/constants.js +82 -0
  112. package/dist/cli/backend/src/constants.js.map +1 -1
  113. package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
  114. package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
  115. package/dist/cli/backend/src/services/cloud/cloud-sync.types.js +1 -0
  116. package/dist/cli/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
  117. package/dist/cli/backend/src/services/core/storage.service.d.ts +18 -0
  118. package/dist/cli/backend/src/services/core/storage.service.d.ts.map +1 -1
  119. package/dist/cli/backend/src/services/core/storage.service.js +16 -0
  120. package/dist/cli/backend/src/services/core/storage.service.js.map +1 -1
  121. package/dist/cli/backend/src/services/decisions/decision.service.d.ts +19 -0
  122. package/dist/cli/backend/src/services/decisions/decision.service.d.ts.map +1 -1
  123. package/dist/cli/backend/src/services/decisions/decision.service.js +46 -0
  124. package/dist/cli/backend/src/services/decisions/decision.service.js.map +1 -1
  125. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +9 -0
  126. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  127. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +25 -0
  128. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  129. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +19 -0
  130. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  131. package/dist/cli/backend/src/services/task-pool/task-pool.service.js +41 -0
  132. package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  133. package/dist/cli/cli/src/commands/team-move.d.ts +68 -0
  134. package/dist/cli/cli/src/commands/team-move.d.ts.map +1 -0
  135. package/dist/cli/cli/src/commands/team-move.js +159 -0
  136. package/dist/cli/cli/src/commands/team-move.js.map +1 -0
  137. package/dist/cli/cli/src/index.js +33 -0
  138. package/dist/cli/cli/src/index.js.map +1 -1
  139. 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":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGjE,2DAA2D;AAC3D,MAAM,eAAe,GAAG,uBAAuB,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAClE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe,iBAAiB,EAAE;IAC9D,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAC;AACvD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,OAAe,iBAAiB,EAAE;IAC3E,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAC1D,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;AAChD,CAAC;AAED,qCAAqC;AACrC,MAAM,OAAO,WAAW;IAaX;IAZF,GAAG,CAAS;IACJ,WAAW,CAAS;IACpB,SAAS,CAAS;IAC3B,OAAO,GAA0B,IAAI,CAAC;IAC7B,GAAG,CAAe;IAEnC;;;;OAIG;IACH,YACW,MAAc,EACvB,OAAe,iBAAiB,EAAE,EAClC,MAAoB,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAFzC,WAAM,GAAN,MAAM,CAAQ;QAIvB,IAAI,CAAC,GAAG,GAAG,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QACvD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;QACnD,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAED,iCAAiC;IACjC,KAAK,CAAC,MAAM;QACV,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAEO,KAAK,CAAC,IAAI;QAChB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,MAAM,GAAG,GAAG,MAAM,YAAY,CAA+B,IAAI,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;YAChG,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAEO,KAAK,CAAC,MAAM,CAAC,KAAmB;QACtC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpB,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACpB,MAAM,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IACvD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,IAAI;QACR,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAClC,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,MAAM,CAAC,IAAY;QACvB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClC,KAAK,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,IAAI,OAAO,CAAC,CAAC,CAAE,CAAC,IAAI,KAAK,IAAI;gBAAE,OAAO,OAAO,CAAC,CAAC,CAAE,CAAC,KAAK,KAAK,MAAM,CAAC;QACrE,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,QAAQ,CAAc,IAAY;QACtC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClC,KAAK,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;YACtB,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;gBAAE,OAAO,CAAC,CAAC,MAAuB,CAAC;QAC9E,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,GAAG,CAAI,IAAY,EAAE,EAAoB;QAC7C,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAI,IAAI,CAAC,CAAM,CAAC;QACxE,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC9D,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,EAAE,EAAE,CAAC;YAC1B,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;YACxG,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC5H,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,KAAK,CAAC,IAAY;QACtB,MAAM,OAAO,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QACnE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACpB,MAAM,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IACvD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,SAAS;QACb,OAAO,YAAY,CAAwB,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACnE,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,UAAU,CAAC,KAA6D;QAC5E,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QACvC,MAAM,IAAI,GAAmB;YAC3B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,KAAK,CAAC,MAAM,IAAI,EAAE;YAC7C,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,KAAK,CAAC,QAAQ,IAAI,EAAE;YACnD,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,IAAI;YAC/B,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,MAAM;YAC/B,GAAG,OAAO;YACV,GAAG,KAAK;YACR,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC;QACF,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACpB,MAAM,eAAe,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,OAAe,iBAAiB,EAAE;IACvE,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/F,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACvF,CAAC"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The machines a team can be moved to, from Crewly Cloud's instance registry
3
+ * (`GET /api/cloud/slack/instances`).
4
+ *
5
+ * The registry is noisy: it keeps stale and duplicate rows, nameless ids and
6
+ * virtual instances (the Cloud Slack bot). A machine is listed only when it
7
+ * heartbeated recently; rows are de-duplicated by name keeping the freshest;
8
+ * the local machine and virtual instances are left out. `canReceive` says
9
+ * whether the machine advertises the `teamMove:1` capability.
10
+ *
11
+ * @module services/team-move/team-move-machines
12
+ */
13
+ import { type TeamMoveCloudSession } from './team-move-cloud.client.js';
14
+ /** An instance as Cloud lists it (the fields used here). */
15
+ export interface CloudInstanceRow {
16
+ instanceId?: string;
17
+ deviceName?: string;
18
+ crewlyVersion?: string;
19
+ capabilities?: string[];
20
+ lastSeenAt?: string;
21
+ }
22
+ /** A machine of the account. */
23
+ export interface MachineView {
24
+ instanceId: string;
25
+ name: string;
26
+ /** Always true for a listed machine: only recently seen machines are listed. */
27
+ online: boolean;
28
+ isLocal: boolean;
29
+ crewlyVersion?: string;
30
+ /** The machine advertises `teamMove:1`; otherwise Crewly has to be updated there. */
31
+ canReceive: boolean;
32
+ lastSeenAt?: string;
33
+ }
34
+ /**
35
+ * Reduce Cloud's instance rows to the machines that matter.
36
+ *
37
+ * @param rows - Rows from the registry
38
+ * @param localInstanceId - This machine's instance id (excluded from the result)
39
+ * @param now - Current time (ms)
40
+ * @param liveWindowMs - Heartbeat age that still counts as online
41
+ * @returns Receivable and non-receivable remote machines, freshest first
42
+ */
43
+ export declare function selectMachines(rows: readonly CloudInstanceRow[], localInstanceId: string | null, now: number, liveWindowMs?: number): MachineView[];
44
+ /**
45
+ * Owner-readable hints for machines that cannot take a team yet.
46
+ *
47
+ * @param machines - Machines from {@link selectMachines}
48
+ * @returns One line per machine that needs a Crewly update
49
+ */
50
+ export declare function updateHints(machines: readonly MachineView[]): string[];
51
+ /**
52
+ * Read the account's live machines from Cloud.
53
+ *
54
+ * @param session - Cloud session (token and URL)
55
+ * @param localInstanceId - This machine's instance id
56
+ * @param fetchImpl - fetch (tests)
57
+ * @param now - Current time (ms)
58
+ * @returns Remote machines
59
+ * @throws TeamMoveCloudError when not signed in or Cloud refuses
60
+ */
61
+ export declare function fetchMachines(session: TeamMoveCloudSession, localInstanceId: string | null, fetchImpl?: typeof fetch, now?: number): Promise<MachineView[]>;
62
+ //# sourceMappingURL=team-move-machines.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team-move-machines.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/team-move/team-move-machines.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAsB,KAAK,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAI5F,4DAA4D;AAC5D,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,gCAAgC;AAChC,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qFAAqF;IACrF,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,gBAAgB,EAAE,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,GAAE,MAAiC,GAAG,WAAW,EAAE,CA0B7K;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,EAAE,CAEtE;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI,EAAE,SAAS,GAAE,OAAO,KAAa,EAAE,GAAG,GAAE,MAAmB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAiBpL"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The machines a team can be moved to, from Crewly Cloud's instance registry
3
+ * (`GET /api/cloud/slack/instances`).
4
+ *
5
+ * The registry is noisy: it keeps stale and duplicate rows, nameless ids and
6
+ * virtual instances (the Cloud Slack bot). A machine is listed only when it
7
+ * heartbeated recently; rows are de-duplicated by name keeping the freshest;
8
+ * the local machine and virtual instances are left out. `canReceive` says
9
+ * whether the machine advertises the `teamMove:1` capability.
10
+ *
11
+ * @module services/team-move/team-move-machines
12
+ */
13
+ import { TEAM_MOVE_CONSTANTS } from '../../constants.js';
14
+ import { TeamMoveCloudError } from './team-move-cloud.client.js';
15
+ const C = TEAM_MOVE_CONSTANTS;
16
+ /**
17
+ * Reduce Cloud's instance rows to the machines that matter.
18
+ *
19
+ * @param rows - Rows from the registry
20
+ * @param localInstanceId - This machine's instance id (excluded from the result)
21
+ * @param now - Current time (ms)
22
+ * @param liveWindowMs - Heartbeat age that still counts as online
23
+ * @returns Receivable and non-receivable remote machines, freshest first
24
+ */
25
+ export function selectMachines(rows, localInstanceId, now, liveWindowMs = C.MACHINE_LIVE_WINDOW_MS) {
26
+ const virtual = new RegExp(C.NON_MACHINE_INSTANCE_PATTERN, 'i');
27
+ const fresh = new Map();
28
+ for (const row of rows) {
29
+ const id = row.instanceId?.trim();
30
+ const name = row.deviceName?.trim();
31
+ if (!id || !name || virtual.test(id) || virtual.test(name))
32
+ continue;
33
+ const seen = row.lastSeenAt ? Date.parse(row.lastSeenAt) : NaN;
34
+ if (!Number.isFinite(seen) || now - seen > liveWindowMs)
35
+ continue;
36
+ // De-duplicate by name over all live rows (the local machine included), freshest wins.
37
+ const key = name.toLowerCase();
38
+ const current = fresh.get(key);
39
+ if (!current || seen > current.seen)
40
+ fresh.set(key, { row, seen });
41
+ }
42
+ return [...fresh.values()]
43
+ .filter(({ row }) => row.instanceId.trim() !== localInstanceId)
44
+ .sort((a, b) => b.seen - a.seen)
45
+ .map(({ row, seen }) => ({
46
+ instanceId: row.instanceId.trim(),
47
+ name: row.deviceName.trim(),
48
+ online: true,
49
+ isLocal: false,
50
+ ...(row.crewlyVersion && row.crewlyVersion !== 'unknown' ? { crewlyVersion: row.crewlyVersion } : {}),
51
+ canReceive: Array.isArray(row.capabilities) && row.capabilities.includes(C.CAPABILITY),
52
+ lastSeenAt: new Date(seen).toISOString(),
53
+ }));
54
+ }
55
+ /**
56
+ * Owner-readable hints for machines that cannot take a team yet.
57
+ *
58
+ * @param machines - Machines from {@link selectMachines}
59
+ * @returns One line per machine that needs a Crewly update
60
+ */
61
+ export function updateHints(machines) {
62
+ return machines.filter((m) => !m.canReceive).map((m) => `Update Crewly on ${m.name}${m.crewlyVersion ? ` (now ${m.crewlyVersion}, needs ${C.MIN_CREWLY_VERSION} or newer)` : ''} to move teams to it.`);
63
+ }
64
+ /**
65
+ * Read the account's live machines from Cloud.
66
+ *
67
+ * @param session - Cloud session (token and URL)
68
+ * @param localInstanceId - This machine's instance id
69
+ * @param fetchImpl - fetch (tests)
70
+ * @param now - Current time (ms)
71
+ * @returns Remote machines
72
+ * @throws TeamMoveCloudError when not signed in or Cloud refuses
73
+ */
74
+ export async function fetchMachines(session, localInstanceId, fetchImpl = fetch, now = Date.now()) {
75
+ const token = session.getToken();
76
+ const base = session.getCloudUrl();
77
+ if (!token || !base)
78
+ throw new TeamMoveCloudError('Not signed in to Crewly Cloud', 401, 'not_logged_in');
79
+ let response;
80
+ try {
81
+ response = await fetchImpl(`${base.replace(/\/$/, '')}/api/cloud/slack/instances`, {
82
+ headers: { Authorization: `Bearer ${token}` },
83
+ signal: AbortSignal.timeout(C.REQUEST_TIMEOUT_MS),
84
+ });
85
+ }
86
+ catch (error) {
87
+ throw new TeamMoveCloudError(`Cloud unreachable: ${error instanceof Error ? error.message : String(error)}`, 0);
88
+ }
89
+ if (!response.ok)
90
+ throw new TeamMoveCloudError(`Cloud could not list machines (HTTP ${response.status})`, response.status);
91
+ const body = (await response.json().catch(() => ({})));
92
+ const rows = Array.isArray(body.data) ? body.data : [];
93
+ return selectMachines(rows, localInstanceId, now);
94
+ }
95
+ //# sourceMappingURL=team-move-machines.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team-move-machines.js","sourceRoot":"","sources":["../../../../../../backend/src/services/team-move/team-move-machines.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"}