crewly 1.20.122 → 1.20.124

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 (225) hide show
  1. package/config/skills/agent/core/whatsapp-draft/SKILL.md +80 -0
  2. package/config/skills/agent/core/whatsapp-draft/execute.sh +84 -0
  3. package/config/skills/agent/core/whatsapp-draft/execute.test.sh +70 -0
  4. package/config/skills/agent/core/whatsapp-inbox/SKILL.md +75 -0
  5. package/config/skills/agent/core/whatsapp-inbox/execute.sh +87 -0
  6. package/config/skills/agent/core/whatsapp-inbox/execute.test.sh +68 -0
  7. package/config/skills/agent/core/whatsapp-read/SKILL.md +80 -0
  8. package/config/skills/agent/core/whatsapp-read/execute.sh +116 -0
  9. package/config/skills/agent/core/whatsapp-read/execute.test.sh +84 -0
  10. package/config/skills/agent/core/whatsapp-send/SKILL.md +70 -0
  11. package/config/skills/agent/core/whatsapp-send/execute.sh +84 -0
  12. package/config/skills/agent/core/whatsapp-send/execute.test.sh +79 -0
  13. package/dist/backend/backend/src/constants.d.ts +225 -0
  14. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  15. package/dist/backend/backend/src/constants.js +223 -0
  16. package/dist/backend/backend/src/constants.js.map +1 -1
  17. package/dist/backend/backend/src/controllers/harness/harness.controller.d.ts +54 -0
  18. package/dist/backend/backend/src/controllers/harness/harness.controller.d.ts.map +1 -0
  19. package/dist/backend/backend/src/controllers/harness/harness.controller.js +204 -0
  20. package/dist/backend/backend/src/controllers/harness/harness.controller.js.map +1 -0
  21. package/dist/backend/backend/src/controllers/harness/harness.routes.d.ts +27 -0
  22. package/dist/backend/backend/src/controllers/harness/harness.routes.d.ts.map +1 -0
  23. package/dist/backend/backend/src/controllers/harness/harness.routes.js +43 -0
  24. package/dist/backend/backend/src/controllers/harness/harness.routes.js.map +1 -0
  25. package/dist/backend/backend/src/controllers/whatsapp/whatsapp-inbox.controller.d.ts +73 -0
  26. package/dist/backend/backend/src/controllers/whatsapp/whatsapp-inbox.controller.d.ts.map +1 -0
  27. package/dist/backend/backend/src/controllers/whatsapp/whatsapp-inbox.controller.js +310 -0
  28. package/dist/backend/backend/src/controllers/whatsapp/whatsapp-inbox.controller.js.map +1 -0
  29. package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.d.ts.map +1 -1
  30. package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.js +53 -4
  31. package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.js.map +1 -1
  32. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  33. package/dist/backend/backend/src/routes/api.routes.js +4 -0
  34. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  35. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts +5 -0
  36. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  37. package/dist/backend/backend/src/services/agent/agent-registration.service.js +7 -0
  38. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  39. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  40. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +5 -0
  41. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  42. package/dist/backend/backend/src/services/cloud/cloud-client.service.d.ts +2 -1
  43. package/dist/backend/backend/src/services/cloud/cloud-client.service.d.ts.map +1 -1
  44. package/dist/backend/backend/src/services/cloud/cloud-client.service.js +4 -3
  45. package/dist/backend/backend/src/services/cloud/cloud-client.service.js.map +1 -1
  46. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
  47. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +43 -1
  48. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
  49. package/dist/backend/backend/src/services/harness/claude-config.utils.d.ts +67 -0
  50. package/dist/backend/backend/src/services/harness/claude-config.utils.d.ts.map +1 -0
  51. package/dist/backend/backend/src/services/harness/claude-config.utils.js +118 -0
  52. package/dist/backend/backend/src/services/harness/claude-config.utils.js.map +1 -0
  53. package/dist/backend/backend/src/services/harness/harness-api-key.service.d.ts +99 -0
  54. package/dist/backend/backend/src/services/harness/harness-api-key.service.d.ts.map +1 -0
  55. package/dist/backend/backend/src/services/harness/harness-api-key.service.js +157 -0
  56. package/dist/backend/backend/src/services/harness/harness-api-key.service.js.map +1 -0
  57. package/dist/backend/backend/src/services/harness/harness-credentials.store.d.ts +135 -0
  58. package/dist/backend/backend/src/services/harness/harness-credentials.store.d.ts.map +1 -0
  59. package/dist/backend/backend/src/services/harness/harness-credentials.store.js +208 -0
  60. package/dist/backend/backend/src/services/harness/harness-credentials.store.js.map +1 -0
  61. package/dist/backend/backend/src/services/harness/harness-exec.utils.d.ts +85 -0
  62. package/dist/backend/backend/src/services/harness/harness-exec.utils.d.ts.map +1 -0
  63. package/dist/backend/backend/src/services/harness/harness-exec.utils.js +183 -0
  64. package/dist/backend/backend/src/services/harness/harness-exec.utils.js.map +1 -0
  65. package/dist/backend/backend/src/services/harness/harness-install.service.d.ts +106 -0
  66. package/dist/backend/backend/src/services/harness/harness-install.service.d.ts.map +1 -0
  67. package/dist/backend/backend/src/services/harness/harness-install.service.js +205 -0
  68. package/dist/backend/backend/src/services/harness/harness-install.service.js.map +1 -0
  69. package/dist/backend/backend/src/services/harness/harness-registry.d.ts +86 -0
  70. package/dist/backend/backend/src/services/harness/harness-registry.d.ts.map +1 -0
  71. package/dist/backend/backend/src/services/harness/harness-registry.js +121 -0
  72. package/dist/backend/backend/src/services/harness/harness-registry.js.map +1 -0
  73. package/dist/backend/backend/src/services/harness/harness-status.service.d.ts +173 -0
  74. package/dist/backend/backend/src/services/harness/harness-status.service.d.ts.map +1 -0
  75. package/dist/backend/backend/src/services/harness/harness-status.service.js +324 -0
  76. package/dist/backend/backend/src/services/harness/harness-status.service.js.map +1 -0
  77. package/dist/backend/backend/src/services/harness/harness.service.d.ts +146 -0
  78. package/dist/backend/backend/src/services/harness/harness.service.d.ts.map +1 -0
  79. package/dist/backend/backend/src/services/harness/harness.service.js +193 -0
  80. package/dist/backend/backend/src/services/harness/harness.service.js.map +1 -0
  81. package/dist/backend/backend/src/services/harness/harness.types.d.ts +138 -0
  82. package/dist/backend/backend/src/services/harness/harness.types.d.ts.map +1 -0
  83. package/dist/backend/backend/src/services/harness/harness.types.js +51 -0
  84. package/dist/backend/backend/src/services/harness/harness.types.js.map +1 -0
  85. package/dist/backend/backend/src/services/harness/login-broker.service.d.ts +238 -0
  86. package/dist/backend/backend/src/services/harness/login-broker.service.d.ts.map +1 -0
  87. package/dist/backend/backend/src/services/harness/login-broker.service.js +495 -0
  88. package/dist/backend/backend/src/services/harness/login-broker.service.js.map +1 -0
  89. package/dist/backend/backend/src/services/harness/login-rules.d.ts +162 -0
  90. package/dist/backend/backend/src/services/harness/login-rules.d.ts.map +1 -0
  91. package/dist/backend/backend/src/services/harness/login-rules.js +344 -0
  92. package/dist/backend/backend/src/services/harness/login-rules.js.map +1 -0
  93. package/dist/backend/backend/src/services/harness/orc-harness.store.d.ts +59 -0
  94. package/dist/backend/backend/src/services/harness/orc-harness.store.d.ts.map +1 -0
  95. package/dist/backend/backend/src/services/harness/orc-harness.store.js +108 -0
  96. package/dist/backend/backend/src/services/harness/orc-harness.store.js.map +1 -0
  97. package/dist/backend/backend/src/services/whatsapp/index.d.ts +5 -1
  98. package/dist/backend/backend/src/services/whatsapp/index.d.ts.map +1 -1
  99. package/dist/backend/backend/src/services/whatsapp/index.js +5 -1
  100. package/dist/backend/backend/src/services/whatsapp/index.js.map +1 -1
  101. package/dist/backend/backend/src/services/whatsapp/whatsapp-connection-config.d.ts +59 -0
  102. package/dist/backend/backend/src/services/whatsapp/whatsapp-connection-config.d.ts.map +1 -0
  103. package/dist/backend/backend/src/services/whatsapp/whatsapp-connection-config.js +108 -0
  104. package/dist/backend/backend/src/services/whatsapp/whatsapp-connection-config.js.map +1 -0
  105. package/dist/backend/backend/src/services/whatsapp/whatsapp-draft-gate.d.ts +74 -0
  106. package/dist/backend/backend/src/services/whatsapp/whatsapp-draft-gate.d.ts.map +1 -0
  107. package/dist/backend/backend/src/services/whatsapp/whatsapp-draft-gate.js +82 -0
  108. package/dist/backend/backend/src/services/whatsapp/whatsapp-draft-gate.js.map +1 -0
  109. package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox-capture.d.ts +184 -0
  110. package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox-capture.d.ts.map +1 -0
  111. package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox-capture.js +420 -0
  112. package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox-capture.js.map +1 -0
  113. package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox.store.d.ts +243 -0
  114. package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox.store.d.ts.map +1 -0
  115. package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox.store.js +538 -0
  116. package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox.store.js.map +1 -0
  117. package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.d.ts +32 -2
  118. package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.d.ts.map +1 -1
  119. package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.js +65 -9
  120. package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.js.map +1 -1
  121. package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.d.ts.map +1 -1
  122. package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.js +11 -0
  123. package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.js.map +1 -1
  124. package/dist/backend/backend/src/services/whatsapp/whatsapp.service.d.ts +59 -1
  125. package/dist/backend/backend/src/services/whatsapp/whatsapp.service.d.ts.map +1 -1
  126. package/dist/backend/backend/src/services/whatsapp/whatsapp.service.js +159 -9
  127. package/dist/backend/backend/src/services/whatsapp/whatsapp.service.js.map +1 -1
  128. package/dist/backend/backend/src/types/whatsapp.types.d.ts +108 -0
  129. package/dist/backend/backend/src/types/whatsapp.types.d.ts.map +1 -1
  130. package/dist/backend/backend/src/types/whatsapp.types.js +9 -0
  131. package/dist/backend/backend/src/types/whatsapp.types.js.map +1 -1
  132. package/dist/backend/build-info.json +2 -2
  133. package/dist/cli/backend/src/constants.d.ts +225 -0
  134. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  135. package/dist/cli/backend/src/constants.js +223 -0
  136. package/dist/cli/backend/src/constants.js.map +1 -1
  137. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts +5 -0
  138. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  139. package/dist/cli/backend/src/services/agent/agent-registration.service.js +7 -0
  140. package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
  141. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  142. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +5 -0
  143. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  144. package/dist/cli/backend/src/services/cloud/cloud-client.service.d.ts +2 -1
  145. package/dist/cli/backend/src/services/cloud/cloud-client.service.d.ts.map +1 -1
  146. package/dist/cli/backend/src/services/cloud/cloud-client.service.js +4 -3
  147. package/dist/cli/backend/src/services/cloud/cloud-client.service.js.map +1 -1
  148. package/dist/cli/backend/src/services/harness/claude-config.utils.d.ts +67 -0
  149. package/dist/cli/backend/src/services/harness/claude-config.utils.d.ts.map +1 -0
  150. package/dist/cli/backend/src/services/harness/claude-config.utils.js +118 -0
  151. package/dist/cli/backend/src/services/harness/claude-config.utils.js.map +1 -0
  152. package/dist/cli/backend/src/services/harness/harness-api-key.service.d.ts +99 -0
  153. package/dist/cli/backend/src/services/harness/harness-api-key.service.d.ts.map +1 -0
  154. package/dist/cli/backend/src/services/harness/harness-api-key.service.js +157 -0
  155. package/dist/cli/backend/src/services/harness/harness-api-key.service.js.map +1 -0
  156. package/dist/cli/backend/src/services/harness/harness-credentials.store.d.ts +135 -0
  157. package/dist/cli/backend/src/services/harness/harness-credentials.store.d.ts.map +1 -0
  158. package/dist/cli/backend/src/services/harness/harness-credentials.store.js +208 -0
  159. package/dist/cli/backend/src/services/harness/harness-credentials.store.js.map +1 -0
  160. package/dist/cli/backend/src/services/harness/harness-exec.utils.d.ts +85 -0
  161. package/dist/cli/backend/src/services/harness/harness-exec.utils.d.ts.map +1 -0
  162. package/dist/cli/backend/src/services/harness/harness-exec.utils.js +183 -0
  163. package/dist/cli/backend/src/services/harness/harness-exec.utils.js.map +1 -0
  164. package/dist/cli/backend/src/services/harness/harness-install.service.d.ts +106 -0
  165. package/dist/cli/backend/src/services/harness/harness-install.service.d.ts.map +1 -0
  166. package/dist/cli/backend/src/services/harness/harness-install.service.js +205 -0
  167. package/dist/cli/backend/src/services/harness/harness-install.service.js.map +1 -0
  168. package/dist/cli/backend/src/services/harness/harness-registry.d.ts +86 -0
  169. package/dist/cli/backend/src/services/harness/harness-registry.d.ts.map +1 -0
  170. package/dist/cli/backend/src/services/harness/harness-registry.js +121 -0
  171. package/dist/cli/backend/src/services/harness/harness-registry.js.map +1 -0
  172. package/dist/cli/backend/src/services/harness/harness-status.service.d.ts +173 -0
  173. package/dist/cli/backend/src/services/harness/harness-status.service.d.ts.map +1 -0
  174. package/dist/cli/backend/src/services/harness/harness-status.service.js +324 -0
  175. package/dist/cli/backend/src/services/harness/harness-status.service.js.map +1 -0
  176. package/dist/cli/backend/src/services/harness/harness.service.d.ts +146 -0
  177. package/dist/cli/backend/src/services/harness/harness.service.d.ts.map +1 -0
  178. package/dist/cli/backend/src/services/harness/harness.service.js +193 -0
  179. package/dist/cli/backend/src/services/harness/harness.service.js.map +1 -0
  180. package/dist/cli/backend/src/services/harness/harness.types.d.ts +138 -0
  181. package/dist/cli/backend/src/services/harness/harness.types.d.ts.map +1 -0
  182. package/dist/cli/backend/src/services/harness/harness.types.js +51 -0
  183. package/dist/cli/backend/src/services/harness/harness.types.js.map +1 -0
  184. package/dist/cli/backend/src/services/harness/login-broker.service.d.ts +238 -0
  185. package/dist/cli/backend/src/services/harness/login-broker.service.d.ts.map +1 -0
  186. package/dist/cli/backend/src/services/harness/login-broker.service.js +495 -0
  187. package/dist/cli/backend/src/services/harness/login-broker.service.js.map +1 -0
  188. package/dist/cli/backend/src/services/harness/login-rules.d.ts +162 -0
  189. package/dist/cli/backend/src/services/harness/login-rules.d.ts.map +1 -0
  190. package/dist/cli/backend/src/services/harness/login-rules.js +344 -0
  191. package/dist/cli/backend/src/services/harness/login-rules.js.map +1 -0
  192. package/dist/cli/backend/src/services/harness/orc-harness.store.d.ts +59 -0
  193. package/dist/cli/backend/src/services/harness/orc-harness.store.d.ts.map +1 -0
  194. package/dist/cli/backend/src/services/harness/orc-harness.store.js +108 -0
  195. package/dist/cli/backend/src/services/harness/orc-harness.store.js.map +1 -0
  196. package/dist/cli/cli/src/commands/harness-setup.d.ts +159 -0
  197. package/dist/cli/cli/src/commands/harness-setup.d.ts.map +1 -0
  198. package/dist/cli/cli/src/commands/harness-setup.js +377 -0
  199. package/dist/cli/cli/src/commands/harness-setup.js.map +1 -0
  200. package/dist/cli/cli/src/commands/harness.d.ts +56 -0
  201. package/dist/cli/cli/src/commands/harness.d.ts.map +1 -0
  202. package/dist/cli/cli/src/commands/harness.js +97 -0
  203. package/dist/cli/cli/src/commands/harness.js.map +1 -0
  204. package/dist/cli/cli/src/commands/onboard.d.ts +108 -50
  205. package/dist/cli/cli/src/commands/onboard.d.ts.map +1 -1
  206. package/dist/cli/cli/src/commands/onboard.js +200 -191
  207. package/dist/cli/cli/src/commands/onboard.js.map +1 -1
  208. package/dist/cli/cli/src/constants.d.ts +18 -0
  209. package/dist/cli/cli/src/constants.d.ts.map +1 -1
  210. package/dist/cli/cli/src/constants.js +18 -0
  211. package/dist/cli/cli/src/constants.js.map +1 -1
  212. package/dist/cli/cli/src/index.js +22 -3
  213. package/dist/cli/cli/src/index.js.map +1 -1
  214. package/dist/cli/cli/src/utils/harness-engine.d.ts +99 -0
  215. package/dist/cli/cli/src/utils/harness-engine.d.ts.map +1 -0
  216. package/dist/cli/cli/src/utils/harness-engine.js +143 -0
  217. package/dist/cli/cli/src/utils/harness-engine.js.map +1 -0
  218. package/dist/cli/cli/src/utils/prompt-io.d.ts +25 -0
  219. package/dist/cli/cli/src/utils/prompt-io.d.ts.map +1 -0
  220. package/dist/cli/cli/src/utils/prompt-io.js +47 -0
  221. package/dist/cli/cli/src/utils/prompt-io.js.map +1 -0
  222. package/frontend/dist/assets/{index-8327e63e.css → index-0a2f783c.css} +1 -1
  223. package/frontend/dist/assets/{index-07e338a9.js → index-aaf65055.js} +309 -309
  224. package/frontend/dist/index.html +2 -2
  225. package/package.json +1 -1
@@ -0,0 +1,157 @@
1
+ /**
2
+ * API-key login for harnesses.
3
+ *
4
+ * - Claude Code: the Anthropic key is checked against the Anthropic API (a
5
+ * 401/403 rejects it; a network problem does not block saving a
6
+ * well-formed key), stored in the harness credentials store and exported
7
+ * to agents as `ANTHROPIC_API_KEY`. Claude's config is updated so the key
8
+ * is pre-approved (claude-config.utils).
9
+ * - Codex: `codex login --with-api-key` with the key on **stdin** (never in
10
+ * argv); Codex saves it in its own `auth.json`.
11
+ *
12
+ * The key is never logged, echoed back or put in an error message.
13
+ *
14
+ * @module services/harness/harness-api-key.service
15
+ */
16
+ import { HARNESS_CONSTANTS } from '../../constants.js';
17
+ import { prepareClaudeConfigForCrewlyLogin } from './claude-config.utils.js';
18
+ import { getHarnessCredentialsStore } from './harness-credentials.store.js';
19
+ import { buildHarnessPath, resolveExecutable, runCommand } from './harness-exec.utils.js';
20
+ import { getHarnessDefinition, getLoginMethod } from './harness-registry.js';
21
+ import { SILENT_HARNESS_LOGGER } from './harness.types.js';
22
+ import { redactSecrets } from './login-rules.js';
23
+ /** API-key login error. The message never contains the key. */
24
+ export class HarnessApiKeyError extends Error {
25
+ code;
26
+ /**
27
+ * @param code - Machine-readable code
28
+ * @param message - Human-readable message
29
+ */
30
+ constructor(code, message) {
31
+ super(message);
32
+ this.code = code;
33
+ this.name = 'HarnessApiKeyError';
34
+ }
35
+ }
36
+ /**
37
+ * Basic shape check shared by every provider.
38
+ *
39
+ * @param key - Candidate key (already trimmed)
40
+ * @returns True when the length is plausible and it has no whitespace
41
+ */
42
+ export function isPlausibleApiKey(key) {
43
+ return key.length >= HARNESS_CONSTANTS.API_KEY_MIN_LENGTH && key.length <= HARNESS_CONSTANTS.API_KEY_MAX_LENGTH && !/\s/.test(key);
44
+ }
45
+ /** Validates and stores harness API keys. */
46
+ export class HarnessApiKeyService {
47
+ run;
48
+ fetchFn;
49
+ env;
50
+ credentials;
51
+ resolveCommand;
52
+ prepareClaudeConfig;
53
+ logger;
54
+ /**
55
+ * @param deps - Injectable dependencies (all optional)
56
+ */
57
+ constructor(deps = {}) {
58
+ this.run = deps.run ?? runCommand;
59
+ this.fetchFn = deps.fetchFn ?? ((url, init) => fetch(url, init));
60
+ this.env = deps.env ?? process.env;
61
+ this.credentials = deps.credentials ?? getHarnessCredentialsStore();
62
+ this.resolveCommand = deps.resolveCommand ?? ((command) => resolveExecutable(command, buildHarnessPath(this.env.PATH)));
63
+ this.prepareClaudeConfig = deps.prepareClaudeConfig ?? ((apiKey) => void prepareClaudeConfigForCrewlyLogin({ apiKey }));
64
+ this.logger = deps.logger ?? SILENT_HARNESS_LOGGER;
65
+ }
66
+ /**
67
+ * Validate and store an API key for a harness.
68
+ *
69
+ * @param harnessId - Harness id
70
+ * @param rawKey - The key as pasted
71
+ * @throws HarnessApiKeyError unknown_harness | unsupported | invalid_key | not_installed | login_failed
72
+ */
73
+ async submit(harnessId, rawKey) {
74
+ const def = getHarnessDefinition(harnessId);
75
+ if (!def)
76
+ throw new HarnessApiKeyError('unknown_harness', `Unknown harness: ${harnessId}`);
77
+ if (!getLoginMethod(def.id, 'api_key')) {
78
+ throw new HarnessApiKeyError('unsupported', `${def.displayName} does not take an API key in Crewly`);
79
+ }
80
+ const key = typeof rawKey === 'string' ? rawKey.trim() : '';
81
+ if (!isPlausibleApiKey(key)) {
82
+ throw new HarnessApiKeyError('invalid_key', 'That does not look like an API key');
83
+ }
84
+ if (def.id === HARNESS_CONSTANTS.IDS.CLAUDE_CODE) {
85
+ await this.submitAnthropicKey(key);
86
+ return;
87
+ }
88
+ await this.submitCodexKey(key);
89
+ }
90
+ /**
91
+ * Check an Anthropic key against the API.
92
+ *
93
+ * @param key - The key
94
+ * @returns valid | invalid | unverified (network error, unexpected status)
95
+ */
96
+ async checkAnthropicKey(key) {
97
+ try {
98
+ const response = await this.fetchFn(HARNESS_CONSTANTS.CLAUDE.API_KEY_CHECK_URL, {
99
+ method: 'GET',
100
+ headers: { 'x-api-key': key, 'anthropic-version': HARNESS_CONSTANTS.CLAUDE.API_VERSION },
101
+ signal: AbortSignal.timeout(HARNESS_CONSTANTS.CLAUDE.API_KEY_CHECK_TIMEOUT_MS),
102
+ });
103
+ if (response.status >= 200 && response.status < 300)
104
+ return 'valid';
105
+ if (response.status === 401 || response.status === 403)
106
+ return 'invalid';
107
+ return 'unverified';
108
+ }
109
+ catch {
110
+ return 'unverified';
111
+ }
112
+ }
113
+ /**
114
+ * Validate and store an Anthropic key.
115
+ *
116
+ * @param key - The key
117
+ * @throws HarnessApiKeyError invalid_key
118
+ */
119
+ async submitAnthropicKey(key) {
120
+ if (!key.startsWith(HARNESS_CONSTANTS.CLAUDE.API_KEY_PREFIX)) {
121
+ throw new HarnessApiKeyError('invalid_key', `Anthropic API keys start with ${HARNESS_CONSTANTS.CLAUDE.API_KEY_PREFIX}`);
122
+ }
123
+ const check = await this.checkAnthropicKey(key);
124
+ if (check === 'invalid')
125
+ throw new HarnessApiKeyError('invalid_key', 'Anthropic rejected this API key');
126
+ this.credentials.setAnthropicApiKey(key);
127
+ try {
128
+ this.prepareClaudeConfig(key);
129
+ }
130
+ catch (error) {
131
+ this.logger.warn('Could not update Claude config after API key login', { error: error instanceof Error ? error.message : String(error) });
132
+ }
133
+ this.logger.info('Anthropic API key stored', { verified: check === 'valid' });
134
+ }
135
+ /**
136
+ * Log Codex in with an OpenAI key (key on stdin).
137
+ *
138
+ * @param key - The key
139
+ * @throws HarnessApiKeyError not_installed | login_failed
140
+ */
141
+ async submitCodexKey(key) {
142
+ const binary = this.resolveCommand('codex');
143
+ if (!binary)
144
+ throw new HarnessApiKeyError('not_installed', 'Codex is not installed');
145
+ const result = await this.run(binary, ['login', '--with-api-key'], {
146
+ env: { ...this.env, PATH: buildHarnessPath(this.env.PATH) },
147
+ stdin: `${key}\n`,
148
+ timeoutMs: HARNESS_CONSTANTS.LOGIN.VERIFY_TIMEOUT_MS,
149
+ });
150
+ if (result.code !== 0) {
151
+ const detail = redactSecrets(`${result.stdout}\n${result.stderr}\n${result.error ?? ''}`, [key]).trim();
152
+ throw new HarnessApiKeyError('login_failed', detail ? `Codex did not accept the key: ${detail}` : 'Codex did not accept the key');
153
+ }
154
+ this.logger.info('Codex logged in with an API key');
155
+ }
156
+ }
157
+ //# sourceMappingURL=harness-api-key.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness-api-key.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/harness/harness-api-key.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Harness credentials store — the harness logins Crewly itself holds.
3
+ *
4
+ * Claude Code's `setup-token` prints a long-lived OAuth token instead of
5
+ * saving it, and an Anthropic API key has to reach agents somehow; both live
6
+ * here, in `<crewlyHome>/harness-credentials.json` with mode 0600. Codex
7
+ * writes its own credentials (`$CODEX_HOME/auth.json`), so nothing is stored
8
+ * for it unless a future flow needs the key.
9
+ *
10
+ * {@link harnessEnvForAgents} turns the stored credentials into the env vars
11
+ * injected into every agent PTY (and adds the user npm prefix to PATH).
12
+ *
13
+ * Secrets are never logged, never returned by the REST API and never put in
14
+ * an error message.
15
+ *
16
+ * @module services/harness/harness-credentials.store
17
+ */
18
+ /** Stored credentials. Every field is optional. */
19
+ export interface HarnessCredentials {
20
+ claude?: {
21
+ /** Long-lived token from `claude setup-token` (exported as CLAUDE_CODE_OAUTH_TOKEN) */
22
+ oauthToken?: string;
23
+ /** Anthropic API key (exported as ANTHROPIC_API_KEY) */
24
+ anthropicApiKey?: string;
25
+ updatedAt?: string;
26
+ };
27
+ codex?: {
28
+ /** OpenAI API key — only kept if a flow needs it (Codex stores its own login) */
29
+ openaiApiKey?: string;
30
+ updatedAt?: string;
31
+ };
32
+ }
33
+ /** Which stored Claude credential is active. */
34
+ export type StoredClaudeCredential = 'oauth_token' | 'api_key' | null;
35
+ /**
36
+ * Reads and writes `harness-credentials.json`.
37
+ *
38
+ * Reads the file on every call (no cache), so the CLI and a running backend
39
+ * always see each other's writes.
40
+ */
41
+ export declare class HarnessCredentialsStore {
42
+ private readonly filePath?;
43
+ /**
44
+ * @param filePath - Credentials file; defaults to `<crewlyHome>/harness-credentials.json`
45
+ * (resolved per call so `CREWLY_HOME` changes in tests are honoured)
46
+ */
47
+ constructor(filePath?: string | undefined);
48
+ /**
49
+ * Absolute path of the credentials file.
50
+ *
51
+ * @returns The path
52
+ */
53
+ getFilePath(): string;
54
+ /**
55
+ * Read the stored credentials.
56
+ *
57
+ * @returns The credentials; an empty object when the file is missing or unreadable
58
+ */
59
+ read(): HarnessCredentials;
60
+ /**
61
+ * Write credentials atomically with mode 0600.
62
+ *
63
+ * The temp file is created with 0600 before any secret is written to it,
64
+ * then renamed over the target, and the target is chmod-ed again in case
65
+ * it pre-existed with a wider mode.
66
+ *
67
+ * @param credentials - Full credentials object
68
+ */
69
+ write(credentials: HarnessCredentials): void;
70
+ /**
71
+ * Store Claude's long-lived OAuth token. Replaces a stored API key, so
72
+ * agents use exactly the login the owner chose last.
73
+ *
74
+ * @param token - Token printed by `claude setup-token`
75
+ * @throws Error when the token is empty
76
+ */
77
+ setClaudeOauthToken(token: string): void;
78
+ /**
79
+ * Store an Anthropic API key. Replaces a stored OAuth token.
80
+ *
81
+ * @param key - Anthropic API key
82
+ * @throws Error when the key is empty
83
+ */
84
+ setAnthropicApiKey(key: string): void;
85
+ /**
86
+ * Store an OpenAI API key (only when a flow needs Crewly to hold it).
87
+ *
88
+ * @param key - OpenAI API key
89
+ * @throws Error when the key is empty
90
+ */
91
+ setOpenaiApiKey(key: string): void;
92
+ /**
93
+ * Forget the stored Claude credential.
94
+ */
95
+ clearClaude(): void;
96
+ /**
97
+ * Which Claude credential Crewly holds (never the value).
98
+ *
99
+ * @returns `oauth_token`, `api_key` or null
100
+ */
101
+ getClaudeCredentialKind(): StoredClaudeCredential;
102
+ /**
103
+ * Env vars to inject into agent sessions.
104
+ *
105
+ * Always sets PATH (user npm prefix bin first, see harness-exec.utils).
106
+ * Adds `CLAUDE_CODE_OAUTH_TOKEN` or `ANTHROPIC_API_KEY` when Crewly holds
107
+ * a Claude credential. Nothing is added for Codex: it reads its own
108
+ * `auth.json`.
109
+ *
110
+ * @param baseEnv - Environment whose PATH is extended (defaults to process.env)
111
+ * @returns Env map to merge into the agent's spawn env
112
+ */
113
+ harnessEnvForAgents(baseEnv?: NodeJS.ProcessEnv): Record<string, string>;
114
+ }
115
+ /**
116
+ * The default credentials store.
117
+ *
118
+ * @returns Store backed by `<crewlyHome>/harness-credentials.json`
119
+ */
120
+ export declare function getHarnessCredentialsStore(): HarnessCredentialsStore;
121
+ /**
122
+ * Env vars to inject into agent sessions, from the default store.
123
+ *
124
+ * Never throws: a broken credentials file yields just the PATH.
125
+ *
126
+ * @param baseEnv - Environment whose PATH is extended (defaults to process.env)
127
+ * @returns Env map (PATH plus any Claude credential env var)
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * const env = { ...identityEnv, ...harnessEnvForAgents() };
132
+ * ```
133
+ */
134
+ export declare function harnessEnvForAgents(baseEnv?: NodeJS.ProcessEnv): Record<string, string>;
135
+ //# sourceMappingURL=harness-credentials.store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness-credentials.store.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/harness/harness-credentials.store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAQH,mDAAmD;AACnD,MAAM,WAAW,kBAAkB;IAClC,MAAM,CAAC,EAAE;QACR,uFAAuF;QACvF,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,wDAAwD;QACxD,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,SAAS,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,KAAK,CAAC,EAAE;QACP,iFAAiF;QACjF,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,SAAS,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACF;AAED,gDAAgD;AAChD,MAAM,MAAM,sBAAsB,GAAG,aAAa,GAAG,SAAS,GAAG,IAAI,CAAC;AAYtE;;;;;GAKG;AACH,qBAAa,uBAAuB;IAKvB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAJtC;;;OAGG;gBAC0B,QAAQ,CAAC,EAAE,MAAM,YAAA;IAE9C;;;;OAIG;IACH,WAAW,IAAI,MAAM;IAIrB;;;;OAIG;IACH,IAAI,IAAI,kBAAkB;IAS1B;;;;;;;;OAQG;IACH,KAAK,CAAC,WAAW,EAAE,kBAAkB,GAAG,IAAI;IAe5C;;;;;;OAMG;IACH,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAMxC;;;;;OAKG;IACH,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAMrC;;;;;OAKG;IACH,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAMlC;;OAEG;IACH,WAAW,IAAI,IAAI;IAQnB;;;;OAIG;IACH,uBAAuB,IAAI,sBAAsB;IAOjD;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,OAAO,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CAUrF;AAKD;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,uBAAuB,CAEpE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMpG"}
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Harness credentials store — the harness logins Crewly itself holds.
3
+ *
4
+ * Claude Code's `setup-token` prints a long-lived OAuth token instead of
5
+ * saving it, and an Anthropic API key has to reach agents somehow; both live
6
+ * here, in `<crewlyHome>/harness-credentials.json` with mode 0600. Codex
7
+ * writes its own credentials (`$CODEX_HOME/auth.json`), so nothing is stored
8
+ * for it unless a future flow needs the key.
9
+ *
10
+ * {@link harnessEnvForAgents} turns the stored credentials into the env vars
11
+ * injected into every agent PTY (and adds the user npm prefix to PATH).
12
+ *
13
+ * Secrets are never logged, never returned by the REST API and never put in
14
+ * an error message.
15
+ *
16
+ * @module services/harness/harness-credentials.store
17
+ */
18
+ import * as fs from 'fs';
19
+ import * as path from 'path';
20
+ import { HARNESS_CONSTANTS } from '../../constants.js';
21
+ import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
22
+ import { buildHarnessPath } from './harness-exec.utils.js';
23
+ /**
24
+ * Whether a value is a non-empty string.
25
+ *
26
+ * @param value - Candidate
27
+ * @returns True for a string with non-whitespace content
28
+ */
29
+ function isNonEmptyString(value) {
30
+ return typeof value === 'string' && value.trim().length > 0;
31
+ }
32
+ /**
33
+ * Reads and writes `harness-credentials.json`.
34
+ *
35
+ * Reads the file on every call (no cache), so the CLI and a running backend
36
+ * always see each other's writes.
37
+ */
38
+ export class HarnessCredentialsStore {
39
+ filePath;
40
+ /**
41
+ * @param filePath - Credentials file; defaults to `<crewlyHome>/harness-credentials.json`
42
+ * (resolved per call so `CREWLY_HOME` changes in tests are honoured)
43
+ */
44
+ constructor(filePath) {
45
+ this.filePath = filePath;
46
+ }
47
+ /**
48
+ * Absolute path of the credentials file.
49
+ *
50
+ * @returns The path
51
+ */
52
+ getFilePath() {
53
+ return this.filePath ?? path.join(getCrewlyHomePath(), HARNESS_CONSTANTS.CREDENTIALS_FILE);
54
+ }
55
+ /**
56
+ * Read the stored credentials.
57
+ *
58
+ * @returns The credentials; an empty object when the file is missing or unreadable
59
+ */
60
+ read() {
61
+ try {
62
+ const parsed = JSON.parse(fs.readFileSync(this.getFilePath(), 'utf-8'));
63
+ return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
64
+ }
65
+ catch {
66
+ return {};
67
+ }
68
+ }
69
+ /**
70
+ * Write credentials atomically with mode 0600.
71
+ *
72
+ * The temp file is created with 0600 before any secret is written to it,
73
+ * then renamed over the target, and the target is chmod-ed again in case
74
+ * it pre-existed with a wider mode.
75
+ *
76
+ * @param credentials - Full credentials object
77
+ */
78
+ write(credentials) {
79
+ const file = this.getFilePath();
80
+ fs.mkdirSync(path.dirname(file), { recursive: true });
81
+ const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
82
+ const fd = fs.openSync(tmp, 'w', HARNESS_CONSTANTS.CREDENTIALS_FILE_MODE);
83
+ try {
84
+ fs.writeSync(fd, `${JSON.stringify(credentials, null, 2)}\n`);
85
+ }
86
+ finally {
87
+ fs.closeSync(fd);
88
+ }
89
+ fs.chmodSync(tmp, HARNESS_CONSTANTS.CREDENTIALS_FILE_MODE);
90
+ fs.renameSync(tmp, file);
91
+ fs.chmodSync(file, HARNESS_CONSTANTS.CREDENTIALS_FILE_MODE);
92
+ }
93
+ /**
94
+ * Store Claude's long-lived OAuth token. Replaces a stored API key, so
95
+ * agents use exactly the login the owner chose last.
96
+ *
97
+ * @param token - Token printed by `claude setup-token`
98
+ * @throws Error when the token is empty
99
+ */
100
+ setClaudeOauthToken(token) {
101
+ if (!isNonEmptyString(token))
102
+ throw new Error('Claude OAuth token is empty');
103
+ const current = this.read();
104
+ this.write({ ...current, claude: { oauthToken: token.trim(), updatedAt: new Date().toISOString() } });
105
+ }
106
+ /**
107
+ * Store an Anthropic API key. Replaces a stored OAuth token.
108
+ *
109
+ * @param key - Anthropic API key
110
+ * @throws Error when the key is empty
111
+ */
112
+ setAnthropicApiKey(key) {
113
+ if (!isNonEmptyString(key))
114
+ throw new Error('Anthropic API key is empty');
115
+ const current = this.read();
116
+ this.write({ ...current, claude: { anthropicApiKey: key.trim(), updatedAt: new Date().toISOString() } });
117
+ }
118
+ /**
119
+ * Store an OpenAI API key (only when a flow needs Crewly to hold it).
120
+ *
121
+ * @param key - OpenAI API key
122
+ * @throws Error when the key is empty
123
+ */
124
+ setOpenaiApiKey(key) {
125
+ if (!isNonEmptyString(key))
126
+ throw new Error('OpenAI API key is empty');
127
+ const current = this.read();
128
+ this.write({ ...current, codex: { openaiApiKey: key.trim(), updatedAt: new Date().toISOString() } });
129
+ }
130
+ /**
131
+ * Forget the stored Claude credential.
132
+ */
133
+ clearClaude() {
134
+ const current = this.read();
135
+ if (!current.claude)
136
+ return;
137
+ const next = { ...current };
138
+ delete next.claude;
139
+ this.write(next);
140
+ }
141
+ /**
142
+ * Which Claude credential Crewly holds (never the value).
143
+ *
144
+ * @returns `oauth_token`, `api_key` or null
145
+ */
146
+ getClaudeCredentialKind() {
147
+ const claude = this.read().claude;
148
+ if (isNonEmptyString(claude?.oauthToken))
149
+ return 'oauth_token';
150
+ if (isNonEmptyString(claude?.anthropicApiKey))
151
+ return 'api_key';
152
+ return null;
153
+ }
154
+ /**
155
+ * Env vars to inject into agent sessions.
156
+ *
157
+ * Always sets PATH (user npm prefix bin first, see harness-exec.utils).
158
+ * Adds `CLAUDE_CODE_OAUTH_TOKEN` or `ANTHROPIC_API_KEY` when Crewly holds
159
+ * a Claude credential. Nothing is added for Codex: it reads its own
160
+ * `auth.json`.
161
+ *
162
+ * @param baseEnv - Environment whose PATH is extended (defaults to process.env)
163
+ * @returns Env map to merge into the agent's spawn env
164
+ */
165
+ harnessEnvForAgents(baseEnv = process.env) {
166
+ const env = { PATH: buildHarnessPath(baseEnv.PATH) };
167
+ const claude = this.read().claude;
168
+ if (isNonEmptyString(claude?.oauthToken)) {
169
+ env[HARNESS_CONSTANTS.CLAUDE.OAUTH_TOKEN_ENV] = claude.oauthToken;
170
+ }
171
+ else if (isNonEmptyString(claude?.anthropicApiKey)) {
172
+ env[HARNESS_CONSTANTS.CLAUDE.API_KEY_ENV] = claude.anthropicApiKey;
173
+ }
174
+ return env;
175
+ }
176
+ }
177
+ /** Process-wide default store (path resolved per call from CREWLY_HOME). */
178
+ const defaultStore = new HarnessCredentialsStore();
179
+ /**
180
+ * The default credentials store.
181
+ *
182
+ * @returns Store backed by `<crewlyHome>/harness-credentials.json`
183
+ */
184
+ export function getHarnessCredentialsStore() {
185
+ return defaultStore;
186
+ }
187
+ /**
188
+ * Env vars to inject into agent sessions, from the default store.
189
+ *
190
+ * Never throws: a broken credentials file yields just the PATH.
191
+ *
192
+ * @param baseEnv - Environment whose PATH is extended (defaults to process.env)
193
+ * @returns Env map (PATH plus any Claude credential env var)
194
+ *
195
+ * @example
196
+ * ```ts
197
+ * const env = { ...identityEnv, ...harnessEnvForAgents() };
198
+ * ```
199
+ */
200
+ export function harnessEnvForAgents(baseEnv = process.env) {
201
+ try {
202
+ return defaultStore.harnessEnvForAgents(baseEnv);
203
+ }
204
+ catch {
205
+ return { PATH: buildHarnessPath(baseEnv.PATH) };
206
+ }
207
+ }
208
+ //# sourceMappingURL=harness-credentials.store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness-credentials.store.js","sourceRoot":"","sources":["../../../../../../backend/src/services/harness/harness-credentials.store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Process helpers for harness onboarding: the PATH every harness process gets
3
+ * and a shell-free command runner.
4
+ *
5
+ * PATH: when `npm install -g` hits EACCES, Crewly installs the harness under a
6
+ * user-owned prefix (`<crewlyHome>/npm-global`). Its `bin` dir is prepended to
7
+ * PATH for everything Crewly spawns — status probes, the login broker and agent
8
+ * sessions — so a harness installed there is found without touching the
9
+ * user's shell profile. Claude Code's native installer dir (`~/.local/bin`) is
10
+ * appended when missing, because a backend started by launchd/systemd often
11
+ * lacks it.
12
+ *
13
+ * @module services/harness/harness-exec.utils
14
+ */
15
+ import type { RunCommand } from './harness.types.js';
16
+ /**
17
+ * The user-owned npm prefix used when a global install is not permitted.
18
+ *
19
+ * @returns Absolute path, e.g. `~/.crewly/npm-global`
20
+ */
21
+ export declare function getUserNpmPrefix(): string;
22
+ /**
23
+ * The bin directory of the user-owned npm prefix.
24
+ *
25
+ * @returns Absolute path, e.g. `~/.crewly/npm-global/bin`
26
+ */
27
+ export declare function getUserNpmBinDir(): string;
28
+ /**
29
+ * Build the PATH value harness processes run with.
30
+ *
31
+ * Prepends the user npm prefix bin dir and appends `~/.local/bin`, each only
32
+ * when not already present. Order of the existing entries is preserved.
33
+ *
34
+ * @param currentPath - Existing PATH (may be undefined)
35
+ * @param homeDir - Home directory (injectable for tests)
36
+ * @returns The PATH string
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * buildHarnessPath('/usr/bin', '/home/me');
41
+ * // '/home/me/.crewly/npm-global/bin:/usr/bin:/home/me/.local/bin'
42
+ * ```
43
+ */
44
+ export declare function buildHarnessPath(currentPath: string | undefined, homeDir?: string): string;
45
+ /**
46
+ * PATH for running `npm`: the harness PATH plus the directory of the running
47
+ * Node binary, where npm lives in nvm / installer layouts. A backend started
48
+ * by launchd or systemd often has neither on its PATH.
49
+ *
50
+ * @param currentPath - Existing PATH
51
+ * @param homeDir - Home directory (injectable for tests)
52
+ * @param nodeBinDir - Directory of the Node binary (injectable for tests)
53
+ * @returns The PATH string
54
+ */
55
+ export declare function buildNpmPath(currentPath: string | undefined, homeDir?: string, nodeBinDir?: string): string;
56
+ /**
57
+ * Copy an environment with PATH set to {@link buildHarnessPath}.
58
+ *
59
+ * @param env - Source environment (defaults to process.env)
60
+ * @returns A new environment object
61
+ */
62
+ export declare function withHarnessPath(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
63
+ /**
64
+ * Find an executable on a PATH, like `which`.
65
+ *
66
+ * @param command - Bare command name (or an absolute path, returned when executable)
67
+ * @param envPath - PATH to search
68
+ * @param isExecutable - Executable check (injectable for tests)
69
+ * @returns Absolute path, or null when not found
70
+ */
71
+ export declare function resolveExecutable(command: string, envPath: string | undefined, isExecutable?: (file: string) => boolean): string | null;
72
+ /**
73
+ * Run a command without a shell and collect its output.
74
+ *
75
+ * Never rejects: a spawn failure (ENOENT) or timeout comes back as
76
+ * `{ code: null, error }`. Secrets must go through `options.stdin`, never
77
+ * through `args`, so they cannot appear in `ps` output.
78
+ *
79
+ * @param command - Executable
80
+ * @param args - Arguments
81
+ * @param options - env, timeout, stdin, streaming output callback
82
+ * @returns Exit code and output
83
+ */
84
+ export declare const runCommand: RunCommand;
85
+ //# sourceMappingURL=harness-exec.utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness-exec.utils.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/harness/harness-exec.utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,OAAO,KAAK,EAAiB,UAAU,EAAqB,MAAM,oBAAoB,CAAC;AAEvF;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,GAAE,MAAqB,GAAG,MAAM,CAOxG;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC3B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,GAAE,MAAqB,EAC9B,UAAU,GAAE,MAAuC,GACjD,MAAM,CAGR;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAAC,UAAU,CAEvF;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,YAAY,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAA6B,GAC3D,MAAM,GAAG,IAAI,CAUf;AAiBD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,UAAU,EAAE,UAsDtB,CAAC"}