crewly 1.20.131 → 1.20.133

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 (186) hide show
  1. package/config/constants.ts +49 -0
  2. package/config/index.ts +2 -0
  3. package/config/skills/agent/core/find-skill/SKILL.md +58 -0
  4. package/config/skills/agent/core/find-skill/execute.sh +67 -0
  5. package/config/skills/agent/core/find-skill/execute.test.sh +69 -0
  6. package/config/skills/agent/core/generate-pdf/SKILL.md +24 -29
  7. package/config/skills/agent/core/generate-pdf/execute.sh +22 -78
  8. package/config/skills/agent/core/generate-pdf/execute.test.sh +45 -0
  9. package/config/skills/agent/core/install-skill/SKILL.md +67 -0
  10. package/config/skills/agent/core/install-skill/execute.sh +80 -0
  11. package/config/skills/agent/core/install-skill/execute.test.sh +74 -0
  12. package/config/skills/agent/pdf-tools/SKILL.md +103 -0
  13. package/config/skills/agent/pdf-tools/execute.sh +301 -0
  14. package/config/skills/agent/pdf-tools/execute.test.sh +218 -0
  15. package/config/skills/agent/pdf-tools/instructions.md +14 -0
  16. package/config/skills/agent/pdf-tools/pdf_tools.py +268 -0
  17. package/config/skills/agent/pdf-tools/skill.json +103 -0
  18. package/config/skills/agent/pdf-tools/style.css +143 -0
  19. package/config/skills/agent/transcribe-audio/SKILL.md +42 -5
  20. package/config/skills/agent/transcribe-audio/execute.sh +44 -11
  21. package/config/skills/agent/transcribe-audio/execute.test.sh +90 -0
  22. package/config/skills/agent/transcribe-audio/install-whisper-cpp.sh +126 -0
  23. package/config/skills/agent/transcribe-audio/install-whisper-cpp.test.sh +69 -0
  24. package/config/skills/agent/transcribe-audio/skill.json +67 -6
  25. package/config/skills/marketplace-registry.ts +8 -1
  26. package/config/skills/registry.json +337 -3
  27. package/dist/backend/backend/src/constants.d.ts +71 -0
  28. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  29. package/dist/backend/backend/src/constants.js +96 -0
  30. package/dist/backend/backend/src/constants.js.map +1 -1
  31. package/dist/backend/backend/src/controllers/cloud/cloud-device-pairing.controller.d.ts +56 -0
  32. package/dist/backend/backend/src/controllers/cloud/cloud-device-pairing.controller.d.ts.map +1 -0
  33. package/dist/backend/backend/src/controllers/cloud/cloud-device-pairing.controller.js +114 -0
  34. package/dist/backend/backend/src/controllers/cloud/cloud-device-pairing.controller.js.map +1 -0
  35. package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts +25 -0
  36. package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts.map +1 -1
  37. package/dist/backend/backend/src/controllers/cloud/cloud.controller.js +73 -55
  38. package/dist/backend/backend/src/controllers/cloud/cloud.controller.js.map +1 -1
  39. package/dist/backend/backend/src/controllers/cloud/cloud.routes.d.ts +3 -0
  40. package/dist/backend/backend/src/controllers/cloud/cloud.routes.d.ts.map +1 -1
  41. package/dist/backend/backend/src/controllers/cloud/cloud.routes.js +9 -0
  42. package/dist/backend/backend/src/controllers/cloud/cloud.routes.js.map +1 -1
  43. package/dist/backend/backend/src/controllers/skill-setup/skill-setup.controller.d.ts +49 -0
  44. package/dist/backend/backend/src/controllers/skill-setup/skill-setup.controller.d.ts.map +1 -0
  45. package/dist/backend/backend/src/controllers/skill-setup/skill-setup.controller.js +185 -0
  46. package/dist/backend/backend/src/controllers/skill-setup/skill-setup.controller.js.map +1 -0
  47. package/dist/backend/backend/src/controllers/skill-setup/skill-setup.routes.d.ts +21 -0
  48. package/dist/backend/backend/src/controllers/skill-setup/skill-setup.routes.d.ts.map +1 -0
  49. package/dist/backend/backend/src/controllers/skill-setup/skill-setup.routes.js +29 -0
  50. package/dist/backend/backend/src/controllers/skill-setup/skill-setup.routes.js.map +1 -0
  51. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  52. package/dist/backend/backend/src/routes/api.routes.js +5 -0
  53. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  54. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts +14 -0
  55. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  56. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +33 -1
  57. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  58. package/dist/backend/backend/src/services/cloud/cloud-device-pairing.client.d.ts +132 -0
  59. package/dist/backend/backend/src/services/cloud/cloud-device-pairing.client.d.ts.map +1 -0
  60. package/dist/backend/backend/src/services/cloud/cloud-device-pairing.client.js +228 -0
  61. package/dist/backend/backend/src/services/cloud/cloud-device-pairing.client.js.map +1 -0
  62. package/dist/backend/backend/src/services/cloud/cloud-device-pairing.service.d.ts +115 -0
  63. package/dist/backend/backend/src/services/cloud/cloud-device-pairing.service.d.ts.map +1 -0
  64. package/dist/backend/backend/src/services/cloud/cloud-device-pairing.service.js +160 -0
  65. package/dist/backend/backend/src/services/cloud/cloud-device-pairing.service.js.map +1 -0
  66. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
  67. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +8 -0
  68. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
  69. package/dist/backend/backend/src/services/marketplace/marketplace.service.d.ts.map +1 -1
  70. package/dist/backend/backend/src/services/marketplace/marketplace.service.js +11 -3
  71. package/dist/backend/backend/src/services/marketplace/marketplace.service.js.map +1 -1
  72. package/dist/backend/backend/src/services/skill-setup/skill-discovery.service.d.ts +281 -0
  73. package/dist/backend/backend/src/services/skill-setup/skill-discovery.service.d.ts.map +1 -0
  74. package/dist/backend/backend/src/services/skill-setup/skill-discovery.service.js +559 -0
  75. package/dist/backend/backend/src/services/skill-setup/skill-discovery.service.js.map +1 -0
  76. package/dist/backend/backend/src/services/skill-setup/skill-install-job.service.d.ts +243 -0
  77. package/dist/backend/backend/src/services/skill-setup/skill-install-job.service.d.ts.map +1 -0
  78. package/dist/backend/backend/src/services/skill-setup/skill-install-job.service.js +439 -0
  79. package/dist/backend/backend/src/services/skill-setup/skill-install-job.service.js.map +1 -0
  80. package/dist/backend/backend/src/services/skill-setup/skill-setup-manifest.d.ts +138 -0
  81. package/dist/backend/backend/src/services/skill-setup/skill-setup-manifest.d.ts.map +1 -0
  82. package/dist/backend/backend/src/services/skill-setup/skill-setup-manifest.js +281 -0
  83. package/dist/backend/backend/src/services/skill-setup/skill-setup-manifest.js.map +1 -0
  84. package/dist/backend/backend/src/services/skill-setup/skill-setup-runner.service.d.ts +338 -0
  85. package/dist/backend/backend/src/services/skill-setup/skill-setup-runner.service.d.ts.map +1 -0
  86. package/dist/backend/backend/src/services/skill-setup/skill-setup-runner.service.js +761 -0
  87. package/dist/backend/backend/src/services/skill-setup/skill-setup-runner.service.js.map +1 -0
  88. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  89. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +6 -0
  90. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  91. package/dist/backend/backend/src/types/marketplace.types.d.ts +16 -1
  92. package/dist/backend/backend/src/types/marketplace.types.d.ts.map +1 -1
  93. package/dist/backend/backend/src/utils/inbound-file-hint.utils.d.ts +55 -0
  94. package/dist/backend/backend/src/utils/inbound-file-hint.utils.d.ts.map +1 -0
  95. package/dist/backend/backend/src/utils/inbound-file-hint.utils.js +78 -0
  96. package/dist/backend/backend/src/utils/inbound-file-hint.utils.js.map +1 -0
  97. package/dist/backend/build-info.json +2 -2
  98. package/dist/backend/config/constants.d.ts +48 -0
  99. package/dist/backend/config/constants.d.ts.map +1 -1
  100. package/dist/backend/config/constants.js +48 -0
  101. package/dist/backend/config/constants.js.map +1 -1
  102. package/dist/backend/config/index.d.ts +2 -2
  103. package/dist/backend/config/index.d.ts.map +1 -1
  104. package/dist/backend/config/index.js +2 -2
  105. package/dist/backend/config/index.js.map +1 -1
  106. package/dist/cli/backend/src/constants.d.ts +71 -0
  107. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  108. package/dist/cli/backend/src/constants.js +96 -0
  109. package/dist/cli/backend/src/constants.js.map +1 -1
  110. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts +14 -0
  111. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  112. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +33 -1
  113. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  114. package/dist/cli/backend/src/services/cloud/cloud-device-pairing.client.d.ts +132 -0
  115. package/dist/cli/backend/src/services/cloud/cloud-device-pairing.client.d.ts.map +1 -0
  116. package/dist/cli/backend/src/services/cloud/cloud-device-pairing.client.js +228 -0
  117. package/dist/cli/backend/src/services/cloud/cloud-device-pairing.client.js.map +1 -0
  118. package/dist/cli/backend/src/services/marketplace/marketplace.service.d.ts +101 -0
  119. package/dist/cli/backend/src/services/marketplace/marketplace.service.d.ts.map +1 -0
  120. package/dist/cli/backend/src/services/marketplace/marketplace.service.js +392 -0
  121. package/dist/cli/backend/src/services/marketplace/marketplace.service.js.map +1 -0
  122. package/dist/cli/backend/src/services/skill-setup/skill-discovery.service.d.ts +281 -0
  123. package/dist/cli/backend/src/services/skill-setup/skill-discovery.service.d.ts.map +1 -0
  124. package/dist/cli/backend/src/services/skill-setup/skill-discovery.service.js +559 -0
  125. package/dist/cli/backend/src/services/skill-setup/skill-discovery.service.js.map +1 -0
  126. package/dist/cli/backend/src/services/skill-setup/skill-setup-manifest.d.ts +138 -0
  127. package/dist/cli/backend/src/services/skill-setup/skill-setup-manifest.d.ts.map +1 -0
  128. package/dist/cli/backend/src/services/skill-setup/skill-setup-manifest.js +281 -0
  129. package/dist/cli/backend/src/services/skill-setup/skill-setup-manifest.js.map +1 -0
  130. package/dist/cli/backend/src/services/skill-setup/skill-setup-runner.service.d.ts +338 -0
  131. package/dist/cli/backend/src/services/skill-setup/skill-setup-runner.service.d.ts.map +1 -0
  132. package/dist/cli/backend/src/services/skill-setup/skill-setup-runner.service.js +761 -0
  133. package/dist/cli/backend/src/services/skill-setup/skill-setup-runner.service.js.map +1 -0
  134. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  135. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +6 -0
  136. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  137. package/dist/cli/backend/src/types/marketplace.types.d.ts +219 -0
  138. package/dist/cli/backend/src/types/marketplace.types.d.ts.map +1 -0
  139. package/dist/cli/backend/src/types/marketplace.types.js +6 -0
  140. package/dist/cli/backend/src/types/marketplace.types.js.map +1 -0
  141. package/dist/cli/backend/src/utils/inbound-file-hint.utils.d.ts +55 -0
  142. package/dist/cli/backend/src/utils/inbound-file-hint.utils.d.ts.map +1 -0
  143. package/dist/cli/backend/src/utils/inbound-file-hint.utils.js +78 -0
  144. package/dist/cli/backend/src/utils/inbound-file-hint.utils.js.map +1 -0
  145. package/dist/cli/backend/src/utils/package-root.d.ts +15 -0
  146. package/dist/cli/backend/src/utils/package-root.d.ts.map +1 -0
  147. package/dist/cli/backend/src/utils/package-root.js +38 -0
  148. package/dist/cli/backend/src/utils/package-root.js.map +1 -0
  149. package/dist/cli/cli/src/commands/cloud.d.ts +45 -25
  150. package/dist/cli/cli/src/commands/cloud.d.ts.map +1 -1
  151. package/dist/cli/cli/src/commands/cloud.js +153 -47
  152. package/dist/cli/cli/src/commands/cloud.js.map +1 -1
  153. package/dist/cli/cli/src/commands/install.d.ts +13 -1
  154. package/dist/cli/cli/src/commands/install.d.ts.map +1 -1
  155. package/dist/cli/cli/src/commands/install.js +40 -3
  156. package/dist/cli/cli/src/commands/install.js.map +1 -1
  157. package/dist/cli/cli/src/commands/onboard-checklist.js +3 -3
  158. package/dist/cli/cli/src/commands/onboard-checklist.js.map +1 -1
  159. package/dist/cli/cli/src/commands/skills.d.ts +58 -0
  160. package/dist/cli/cli/src/commands/skills.d.ts.map +1 -0
  161. package/dist/cli/cli/src/commands/skills.js +132 -0
  162. package/dist/cli/cli/src/commands/skills.js.map +1 -0
  163. package/dist/cli/cli/src/index.js +18 -7
  164. package/dist/cli/cli/src/index.js.map +1 -1
  165. package/dist/cli/cli/src/utils/archive-creator.d.ts +14 -0
  166. package/dist/cli/cli/src/utils/archive-creator.d.ts.map +1 -1
  167. package/dist/cli/cli/src/utils/archive-creator.js +24 -3
  168. package/dist/cli/cli/src/utils/archive-creator.js.map +1 -1
  169. package/dist/cli/cli/src/utils/cloud-submit.d.ts.map +1 -1
  170. package/dist/cli/cli/src/utils/cloud-submit.js +14 -5
  171. package/dist/cli/cli/src/utils/cloud-submit.js.map +1 -1
  172. package/dist/cli/cli/src/utils/package-validator.d.ts +2 -0
  173. package/dist/cli/cli/src/utils/package-validator.d.ts.map +1 -1
  174. package/dist/cli/cli/src/utils/package-validator.js +16 -0
  175. package/dist/cli/cli/src/utils/package-validator.js.map +1 -1
  176. package/dist/cli/config/constants.d.ts +48 -0
  177. package/dist/cli/config/constants.d.ts.map +1 -1
  178. package/dist/cli/config/constants.js +48 -0
  179. package/dist/cli/config/constants.js.map +1 -1
  180. package/dist/cli/config/index.d.ts +2 -2
  181. package/dist/cli/config/index.d.ts.map +1 -1
  182. package/dist/cli/config/index.js +2 -2
  183. package/dist/cli/config/index.js.map +1 -1
  184. package/frontend/dist/assets/{index-5b98bf77.js → index-e6c403b6.js} +265 -265
  185. package/frontend/dist/index.html +1 -1
  186. package/package.json +1 -1
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Cloud device-code pairing client.
3
+ *
4
+ * Talks to crewly-auth's `/api/cloud/device/*` endpoints so this machine can
5
+ * sign in to the owner's Crewly account without anyone at it:
6
+ *
7
+ * 1. {@link startCloudDevicePairing} — get a secret `deviceCode`, a short
8
+ * `userCode` (`ABCD-2345`) and the link the owner opens
9
+ * (`crewlyai.com/cloud/pair?code=ABCD-2345`).
10
+ * 2. {@link waitForCloudDeviceApproval} — poll at the Cloud's interval
11
+ * (backing off on `slow_down` and rate limits) until the owner approves,
12
+ * denies, or the pairing expires. On approval the Cloud hands over the
13
+ * token pair exactly once.
14
+ *
15
+ * Shared by `crewly cloud login` (CLI) and {@link CloudDevicePairingService}
16
+ * (backend, for `/setup` and Settings → Cloud). No state; the device code
17
+ * never leaves the caller's memory.
18
+ *
19
+ * @module services/cloud/cloud-device-pairing.client
20
+ */
21
+ import { CLOUD_DEVICE_PAIRING_CONSTANTS } from '../../../../config/constants.js';
22
+ const C = CLOUD_DEVICE_PAIRING_CONSTANTS;
23
+ /** Milliseconds per second. */
24
+ const MS_PER_S = 1000;
25
+ /** HTTP 429. */
26
+ const HTTP_TOO_MANY_REQUESTS = 429;
27
+ /** Lowest HTTP status treated as a server-side (retryable) failure. */
28
+ const HTTP_SERVER_ERROR_MIN = 500;
29
+ /** A request the Cloud refused (or could not answer). */
30
+ export class DevicePairingError extends Error {
31
+ status;
32
+ retryable;
33
+ /**
34
+ * @param message - Human-readable reason (never contains a secret)
35
+ * @param status - HTTP status, 0 when the Cloud was unreachable
36
+ * @param retryable - True for network errors, 5xx and 429
37
+ */
38
+ constructor(message, status, retryable) {
39
+ super(message);
40
+ this.status = status;
41
+ this.retryable = retryable;
42
+ this.name = 'DevicePairingError';
43
+ }
44
+ }
45
+ /**
46
+ * POST JSON to crewly-auth and unwrap `{ success, data }`.
47
+ *
48
+ * @param fetchImpl - fetch
49
+ * @param url - Absolute URL
50
+ * @param body - JSON body
51
+ * @returns `data`
52
+ * @throws DevicePairingError on transport errors and non-success replies
53
+ */
54
+ async function postJson(fetchImpl, url, body) {
55
+ let response;
56
+ try {
57
+ response = await fetchImpl(url, {
58
+ method: 'POST',
59
+ headers: { 'Content-Type': 'application/json' },
60
+ body: JSON.stringify(body),
61
+ signal: AbortSignal.timeout(C.REQUEST_TIMEOUT_MS),
62
+ });
63
+ }
64
+ catch (err) {
65
+ throw new DevicePairingError(`Crewly Cloud unreachable: ${err instanceof Error ? err.message : String(err)}`, 0, true);
66
+ }
67
+ let parsed = {};
68
+ try {
69
+ parsed = (await response.json());
70
+ }
71
+ catch {
72
+ // handled below
73
+ }
74
+ if (!response.ok || !parsed.success || parsed.data === undefined) {
75
+ const retryable = response.status === HTTP_TOO_MANY_REQUESTS || response.status >= HTTP_SERVER_ERROR_MIN;
76
+ throw new DevicePairingError(parsed.error || `Crewly Cloud returned HTTP ${response.status}`, response.status, retryable);
77
+ }
78
+ return parsed.data;
79
+ }
80
+ /**
81
+ * Join the Cloud base URL and an endpoint path.
82
+ *
83
+ * @param cloudUrl - Cloud API base URL
84
+ * @param path - Endpoint path
85
+ * @returns Absolute URL
86
+ */
87
+ function endpoint(cloudUrl, path) {
88
+ return `${cloudUrl.replace(/\/+$/, '')}${path}`;
89
+ }
90
+ /**
91
+ * Ask the Cloud for a pairing.
92
+ *
93
+ * @param cloudUrl - Cloud API base URL (e.g. https://api.crewlyai.com)
94
+ * @param input - How the device introduces itself on the approve page
95
+ * @param fetchImpl - fetch (tests)
96
+ * @returns Device code, user code, link, lifetime and interval
97
+ * @throws DevicePairingError when the Cloud refuses or is unreachable
98
+ */
99
+ export async function startCloudDevicePairing(cloudUrl, input, fetchImpl = fetch) {
100
+ const data = await postJson(fetchImpl, endpoint(cloudUrl, C.CLOUD_ENDPOINTS.START), input);
101
+ if (!data.deviceCode || !data.userCode || !data.verificationUrl) {
102
+ throw new DevicePairingError('Crewly Cloud returned an incomplete pairing', 0, false);
103
+ }
104
+ return {
105
+ deviceCode: data.deviceCode,
106
+ userCode: data.userCode,
107
+ verificationUrl: data.verificationUrl,
108
+ ...(data.verificationUri ? { verificationUri: data.verificationUri } : {}),
109
+ expiresIn: typeof data.expiresIn === 'number' ? data.expiresIn : C.DEFAULT_EXPIRES_IN_S,
110
+ interval: typeof data.interval === 'number' ? data.interval : C.DEFAULT_INTERVAL_S,
111
+ };
112
+ }
113
+ /**
114
+ * Poll a pairing once.
115
+ *
116
+ * @param cloudUrl - Cloud API base URL
117
+ * @param deviceCode - Secret from {@link startCloudDevicePairing}
118
+ * @param fetchImpl - fetch (tests)
119
+ * @returns The Cloud's answer
120
+ * @throws DevicePairingError when the request fails (see `retryable`)
121
+ */
122
+ export async function pollCloudDevicePairing(cloudUrl, deviceCode, fetchImpl = fetch) {
123
+ const data = await postJson(fetchImpl, endpoint(cloudUrl, C.CLOUD_ENDPOINTS.POLL), { deviceCode });
124
+ switch (data.status) {
125
+ case C.POLL_STATUS.APPROVED:
126
+ if (!data.token || !data.refreshToken) {
127
+ throw new DevicePairingError('Crewly Cloud approved the device but sent no tokens', 0, false);
128
+ }
129
+ return {
130
+ status: 'approved',
131
+ credentials: { token: data.token, refreshToken: data.refreshToken, tier: data.tier || 'free', ...(data.email ? { email: data.email } : {}) },
132
+ };
133
+ case C.POLL_STATUS.PENDING:
134
+ case C.POLL_STATUS.SLOW_DOWN:
135
+ return { status: data.status, ...(typeof data.interval === 'number' ? { interval: data.interval } : {}) };
136
+ case C.POLL_STATUS.DENIED:
137
+ return { status: 'denied' };
138
+ case C.POLL_STATUS.EXPIRED:
139
+ return { status: 'expired' };
140
+ default:
141
+ throw new DevicePairingError(`Unexpected pairing status: ${String(data.status)}`, 0, false);
142
+ }
143
+ }
144
+ /**
145
+ * Wait `ms`, resolving early when `signal` aborts.
146
+ *
147
+ * @param ms - Milliseconds
148
+ * @param signal - Optional abort signal
149
+ * @returns Resolves after the delay or on abort
150
+ */
151
+ export function abortableSleep(ms, signal) {
152
+ return new Promise((resolve) => {
153
+ if (signal?.aborted) {
154
+ resolve();
155
+ return;
156
+ }
157
+ const timer = setTimeout(() => {
158
+ signal?.removeEventListener('abort', onAbort);
159
+ resolve();
160
+ }, ms);
161
+ const onAbort = () => {
162
+ clearTimeout(timer);
163
+ resolve();
164
+ };
165
+ signal?.addEventListener('abort', onAbort, { once: true });
166
+ });
167
+ }
168
+ /**
169
+ * Poll until the owner approves or denies, or the pairing expires.
170
+ *
171
+ * Honours the Cloud's interval, adds {@link CLOUD_DEVICE_PAIRING_CONSTANTS.SLOW_DOWN_INCREMENT_S}
172
+ * on `slow_down` / HTTP 429 (capped), and rides out up to
173
+ * {@link CLOUD_DEVICE_PAIRING_CONSTANTS.MAX_CONSECUTIVE_POLL_ERRORS} network
174
+ * or server errors in a row (a laptop changing Wi-Fi mid-login).
175
+ *
176
+ * @param options - Cloud URL, the started pairing and hooks
177
+ * @returns How it ended; `approved` carries the token pair
178
+ * @throws DevicePairingError on a non-retryable refusal or too many errors in a row
179
+ */
180
+ export async function waitForCloudDeviceApproval(options) {
181
+ const { cloudUrl, start, signal, onPoll } = options;
182
+ const fetchImpl = options.fetchImpl ?? fetch;
183
+ const sleep = options.sleep ?? abortableSleep;
184
+ const now = options.now ?? (() => Date.now());
185
+ const deadline = now() + start.expiresIn * MS_PER_S;
186
+ let intervalS = Math.max(1, start.interval || C.DEFAULT_INTERVAL_S);
187
+ let consecutiveErrors = 0;
188
+ for (;;) {
189
+ await sleep(intervalS * MS_PER_S, signal);
190
+ if (signal?.aborted)
191
+ return { status: 'cancelled' };
192
+ if (now() >= deadline)
193
+ return { status: 'expired' };
194
+ let result;
195
+ try {
196
+ result = await pollCloudDevicePairing(cloudUrl, start.deviceCode, fetchImpl);
197
+ consecutiveErrors = 0;
198
+ }
199
+ catch (err) {
200
+ if (!(err instanceof DevicePairingError) || !err.retryable)
201
+ throw err;
202
+ consecutiveErrors++;
203
+ if (consecutiveErrors > C.MAX_CONSECUTIVE_POLL_ERRORS)
204
+ throw err;
205
+ if (err.status === HTTP_TOO_MANY_REQUESTS) {
206
+ intervalS = Math.min(C.MAX_INTERVAL_S, intervalS + C.SLOW_DOWN_INCREMENT_S);
207
+ }
208
+ continue;
209
+ }
210
+ if (signal?.aborted)
211
+ return { status: 'cancelled' };
212
+ onPoll?.(result.status, intervalS);
213
+ switch (result.status) {
214
+ case 'approved':
215
+ return result;
216
+ case 'expired':
217
+ case 'denied':
218
+ return { status: result.status };
219
+ case 'slow_down':
220
+ intervalS = Math.min(C.MAX_INTERVAL_S, result.interval ?? intervalS + C.SLOW_DOWN_INCREMENT_S);
221
+ break;
222
+ default:
223
+ if (typeof result.interval === 'number')
224
+ intervalS = Math.min(C.MAX_INTERVAL_S, Math.max(1, result.interval));
225
+ }
226
+ }
227
+ }
228
+ //# sourceMappingURL=cloud-device-pairing.client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud-device-pairing.client.js","sourceRoot":"","sources":["../../../../../../backend/src/services/cloud/cloud-device-pairing.client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Marketplace Service
3
+ *
4
+ * Handles fetching the marketplace registry from the Crewly webapp API,
5
+ * managing the local installed-items manifest, and enriching items
6
+ * with install status information.
7
+ *
8
+ * @module services/marketplace/marketplace.service
9
+ */
10
+ import { MarketplaceRegistry, MarketplaceItemWithStatus, MarketplaceFilter, InstalledItemsManifest, InstalledItemRecord, type MarketplaceItemType } from '../../types/marketplace.types.js';
11
+ /**
12
+ * Fetches the marketplace registry from the Crewly webapp API.
13
+ * Uses in-memory caching with a 1-hour TTL. Concurrent callers share
14
+ * the same in-flight request to avoid duplicate network fetches.
15
+ *
16
+ * On network failure, falls back to the previously cached registry
17
+ * if one is available. Throws only when no cached data exists.
18
+ *
19
+ * @param forceRefresh - If true, bypasses the cache and fetches fresh data
20
+ * @returns The marketplace registry containing all available items
21
+ * @throws Error if the fetch fails and no cached registry is available
22
+ */
23
+ export declare function fetchRegistry(forceRefresh?: boolean): Promise<MarketplaceRegistry>;
24
+ /**
25
+ * Loads the locally installed items manifest from disk.
26
+ *
27
+ * Returns an empty manifest (schemaVersion 1, no items) if the
28
+ * manifest file does not exist or cannot be parsed.
29
+ *
30
+ * @returns The installed items manifest
31
+ */
32
+ export declare function loadManifest(): Promise<InstalledItemsManifest>;
33
+ /**
34
+ * Saves the installed items manifest to disk.
35
+ *
36
+ * Creates the marketplace directory if it does not exist.
37
+ *
38
+ * @param manifest - The manifest to persist
39
+ */
40
+ export declare function saveManifest(manifest: InstalledItemsManifest): Promise<void>;
41
+ /**
42
+ * Returns a single marketplace item by ID, enriched with install status.
43
+ *
44
+ * @param id - The marketplace item ID to look up
45
+ * @returns The item with install status, or null if not found in the registry
46
+ */
47
+ export declare function getItem(id: string): Promise<MarketplaceItemWithStatus | null>;
48
+ /**
49
+ * Lists marketplace items with optional filtering, enriched with install status.
50
+ *
51
+ * Supports filtering by type, category, and free-text search (matches name,
52
+ * description, and tags). Results are sorted by the specified sort option,
53
+ * defaulting to most popular (highest downloads).
54
+ *
55
+ * @param filter - Optional filter and sort criteria
56
+ * @returns Filtered and sorted list of items with install status
57
+ */
58
+ export declare function listItems(filter?: MarketplaceFilter): Promise<MarketplaceItemWithStatus[]>;
59
+ /**
60
+ * Returns marketplace items that have updates available.
61
+ *
62
+ * An item has an update available when the installed version differs
63
+ * from the latest version in the registry.
64
+ *
65
+ * @returns Items with installStatus 'update_available'
66
+ */
67
+ export declare function getUpdatableItems(): Promise<MarketplaceItemWithStatus[]>;
68
+ /**
69
+ * Returns locally installed items from the manifest.
70
+ *
71
+ * @returns Array of installed item records
72
+ */
73
+ export declare function getInstalledItems(): Promise<InstalledItemRecord[]>;
74
+ /**
75
+ * Searches marketplace items by query string.
76
+ *
77
+ * Convenience wrapper around listItems with a search filter.
78
+ *
79
+ * @param query - Free-text search query
80
+ * @returns Matching items with install status
81
+ */
82
+ export declare function searchItems(query: string): Promise<MarketplaceItemWithStatus[]>;
83
+ export declare function getInstallPath(type: MarketplaceItemType, id: string): string;
84
+ /**
85
+ * Retrieves the README.md content for a marketplace item.
86
+ *
87
+ * Looks for a README.md file in the item's install directory. Tries multiple
88
+ * filenames (README.md, readme.md, SKILL.md) for flexibility.
89
+ *
90
+ * @param id - The marketplace item ID
91
+ * @returns The README content as a string, or null if not found
92
+ * @throws Error if the item ID is invalid (path traversal protection)
93
+ */
94
+ export declare function getItemReadme(id: string): Promise<string | null>;
95
+ /**
96
+ * Resets the in-memory registry cache.
97
+ *
98
+ * Used primarily for testing to ensure a clean state between test runs.
99
+ */
100
+ export declare function resetRegistryCache(): void;
101
+ //# sourceMappingURL=marketplace.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"marketplace.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/marketplace/marketplace.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EACL,mBAAmB,EAEnB,yBAAyB,EACzB,iBAAiB,EACjB,sBAAsB,EACtB,mBAAmB,EACnB,KAAK,mBAAmB,EACzB,MAAM,kCAAkC,CAAC;AAyB1C;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,YAAY,UAAQ,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAuBtF;AAwID;;;;;;;GAOG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,sBAAsB,CAAC,CAOpE;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAGlF;AAED;;;;;GAKG;AACH,wBAAsB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAAC,CAOnF;AAED;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,yBAAyB,EAAE,CAAC,CAuChG;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,yBAAyB,EAAE,CAAC,CAG9E;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAGxE;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,EAAE,CAAC,CAErF;AA2CD,wBAAgB,cAAc,CAAC,IAAI,EAAE,mBAAmB,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAM5E;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsBtE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAIzC"}