crewly 1.5.22 → 1.6.1

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 (180) hide show
  1. package/config/roles/orchestrator/fragments/role-boundary.md +4 -1
  2. package/config/roles/orchestrator/prompt.md +219 -25
  3. package/config/roles/orchestrator/soul.md +47 -10
  4. package/config/skills/_common/lib.sh +28 -0
  5. package/config/skills/agent/core/cancel-followup/SKILL.md +38 -0
  6. package/config/skills/agent/core/cancel-followup/execute.sh +92 -0
  7. package/config/skills/agent/core/cancel-followup/execute.test.sh +42 -0
  8. package/config/skills/agent/core/list-my-followups/SKILL.md +36 -0
  9. package/config/skills/agent/core/list-my-followups/execute.sh +74 -0
  10. package/config/skills/agent/core/list-my-followups/execute.test.sh +41 -0
  11. package/config/skills/agent/core/schedule-followup/SKILL.md +53 -0
  12. package/config/skills/agent/core/schedule-followup/execute.sh +176 -0
  13. package/config/skills/agent/core/schedule-followup/execute.test.sh +48 -0
  14. package/config/skills/agent/core/watch-for-event/SKILL.md +60 -0
  15. package/config/skills/agent/core/watch-for-event/execute.sh +158 -0
  16. package/config/skills/agent/core/watch-for-event/execute.test.sh +43 -0
  17. package/config/skills/orchestrator/credential-manager/SKILL.md +218 -0
  18. package/config/skills/orchestrator/credential-manager/execute.sh +166 -0
  19. package/config/skills/orchestrator/credential-manager/execute.test.sh +88 -0
  20. package/dist/backend/backend/src/config/oauth.config.d.ts +33 -0
  21. package/dist/backend/backend/src/config/oauth.config.d.ts.map +1 -0
  22. package/dist/backend/backend/src/config/oauth.config.js +45 -0
  23. package/dist/backend/backend/src/config/oauth.config.js.map +1 -0
  24. package/dist/backend/backend/src/controllers/credentials/credentials.controller.d.ts +54 -0
  25. package/dist/backend/backend/src/controllers/credentials/credentials.controller.d.ts.map +1 -0
  26. package/dist/backend/backend/src/controllers/credentials/credentials.controller.js +228 -0
  27. package/dist/backend/backend/src/controllers/credentials/credentials.controller.js.map +1 -0
  28. package/dist/backend/backend/src/controllers/credentials/credentials.routes.d.ts +26 -0
  29. package/dist/backend/backend/src/controllers/credentials/credentials.routes.d.ts.map +1 -0
  30. package/dist/backend/backend/src/controllers/credentials/credentials.routes.js +41 -0
  31. package/dist/backend/backend/src/controllers/credentials/credentials.routes.js.map +1 -0
  32. package/dist/backend/backend/src/controllers/credentials/google-oauth.controller.d.ts +40 -0
  33. package/dist/backend/backend/src/controllers/credentials/google-oauth.controller.d.ts.map +1 -0
  34. package/dist/backend/backend/src/controllers/credentials/google-oauth.controller.js +162 -0
  35. package/dist/backend/backend/src/controllers/credentials/google-oauth.controller.js.map +1 -0
  36. package/dist/backend/backend/src/controllers/skill/skill.controller.d.ts.map +1 -1
  37. package/dist/backend/backend/src/controllers/skill/skill.controller.js +1 -0
  38. package/dist/backend/backend/src/controllers/skill/skill.controller.js.map +1 -1
  39. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +23 -14
  40. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
  41. package/dist/backend/backend/src/index.d.ts.map +1 -1
  42. package/dist/backend/backend/src/index.js +23 -4
  43. package/dist/backend/backend/src/index.js.map +1 -1
  44. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  45. package/dist/backend/backend/src/routes/api.routes.js +3 -0
  46. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  47. package/dist/backend/backend/src/scripts/backfill-mission-priority.d.ts +3 -1
  48. package/dist/backend/backend/src/scripts/backfill-mission-priority.d.ts.map +1 -1
  49. package/dist/backend/backend/src/scripts/backfill-mission-priority.js +16 -4
  50. package/dist/backend/backend/src/scripts/backfill-mission-priority.js.map +1 -1
  51. package/dist/backend/backend/src/services/ai/prompt-modules/role-boundary.module.d.ts.map +1 -1
  52. package/dist/backend/backend/src/services/ai/prompt-modules/role-boundary.module.js +4 -1
  53. package/dist/backend/backend/src/services/ai/prompt-modules/role-boundary.module.js.map +1 -1
  54. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  55. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +17 -0
  56. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  57. package/dist/backend/backend/src/services/browser/browser-proxy.service.js +1 -1
  58. package/dist/backend/backend/src/services/browser/browser-proxy.service.js.map +1 -1
  59. package/dist/backend/backend/src/services/credential/credential-store.service.d.ts +161 -0
  60. package/dist/backend/backend/src/services/credential/credential-store.service.d.ts.map +1 -0
  61. package/dist/backend/backend/src/services/credential/credential-store.service.js +298 -0
  62. package/dist/backend/backend/src/services/credential/credential-store.service.js.map +1 -0
  63. package/dist/backend/backend/src/services/credential/helpers/gemini-cli-workspace.helper.d.ts +105 -0
  64. package/dist/backend/backend/src/services/credential/helpers/gemini-cli-workspace.helper.d.ts.map +1 -0
  65. package/dist/backend/backend/src/services/credential/helpers/gemini-cli-workspace.helper.js +272 -0
  66. package/dist/backend/backend/src/services/credential/helpers/gemini-cli-workspace.helper.js.map +1 -0
  67. package/dist/backend/backend/src/services/mcp-server.d.ts +46 -2
  68. package/dist/backend/backend/src/services/mcp-server.d.ts.map +1 -1
  69. package/dist/backend/backend/src/services/mcp-server.js +216 -211
  70. package/dist/backend/backend/src/services/mcp-server.js.map +1 -1
  71. package/dist/backend/backend/src/services/mcp-tool-definitions.d.ts +254 -0
  72. package/dist/backend/backend/src/services/mcp-tool-definitions.d.ts.map +1 -0
  73. package/dist/backend/backend/src/services/mcp-tool-definitions.js +285 -0
  74. package/dist/backend/backend/src/services/mcp-tool-definitions.js.map +1 -0
  75. package/dist/backend/backend/src/services/project/task.service.d.ts +18 -2
  76. package/dist/backend/backend/src/services/project/task.service.d.ts.map +1 -1
  77. package/dist/backend/backend/src/services/project/task.service.js +74 -53
  78. package/dist/backend/backend/src/services/project/task.service.js.map +1 -1
  79. package/dist/backend/backend/src/services/skill/skill-executor.service.d.ts +41 -0
  80. package/dist/backend/backend/src/services/skill/skill-executor.service.d.ts.map +1 -1
  81. package/dist/backend/backend/src/services/skill/skill-executor.service.js +136 -7
  82. package/dist/backend/backend/src/services/skill/skill-executor.service.js.map +1 -1
  83. package/dist/backend/backend/src/services/skill/skill.service.d.ts.map +1 -1
  84. package/dist/backend/backend/src/services/skill/skill.service.js +1 -0
  85. package/dist/backend/backend/src/services/skill/skill.service.js.map +1 -1
  86. package/dist/backend/backend/src/services/v3/contract-matcher.d.ts +20 -0
  87. package/dist/backend/backend/src/services/v3/contract-matcher.d.ts.map +1 -0
  88. package/dist/backend/backend/src/services/v3/contract-matcher.js +33 -0
  89. package/dist/backend/backend/src/services/v3/contract-matcher.js.map +1 -0
  90. package/dist/backend/backend/src/services/v3/escalation.service.d.ts +20 -1
  91. package/dist/backend/backend/src/services/v3/escalation.service.d.ts.map +1 -1
  92. package/dist/backend/backend/src/services/v3/escalation.service.js +97 -28
  93. package/dist/backend/backend/src/services/v3/escalation.service.js.map +1 -1
  94. package/dist/backend/backend/src/services/v3/service-contract-gate.service.d.ts +6 -4
  95. package/dist/backend/backend/src/services/v3/service-contract-gate.service.d.ts.map +1 -1
  96. package/dist/backend/backend/src/services/v3/service-contract-gate.service.js +18 -28
  97. package/dist/backend/backend/src/services/v3/service-contract-gate.service.js.map +1 -1
  98. package/dist/backend/backend/src/services/v3/team-trigger-reconciler.service.d.ts.map +1 -1
  99. package/dist/backend/backend/src/services/v3/team-trigger-reconciler.service.js +14 -9
  100. package/dist/backend/backend/src/services/v3/team-trigger-reconciler.service.js.map +1 -1
  101. package/dist/backend/backend/src/services/v3/trigger-engine.service.d.ts +34 -1
  102. package/dist/backend/backend/src/services/v3/trigger-engine.service.d.ts.map +1 -1
  103. package/dist/backend/backend/src/services/v3/trigger-engine.service.js +115 -5
  104. package/dist/backend/backend/src/services/v3/trigger-engine.service.js.map +1 -1
  105. package/dist/backend/backend/src/types/credential.types.d.ts +185 -0
  106. package/dist/backend/backend/src/types/credential.types.d.ts.map +1 -0
  107. package/dist/backend/backend/src/types/credential.types.js +76 -0
  108. package/dist/backend/backend/src/types/credential.types.js.map +1 -0
  109. package/dist/backend/backend/src/types/skill.types.d.ts +9 -0
  110. package/dist/backend/backend/src/types/skill.types.d.ts.map +1 -1
  111. package/dist/backend/backend/src/types/skill.types.js.map +1 -1
  112. package/dist/backend/backend/src/utils/encryption.utils.d.ts +57 -0
  113. package/dist/backend/backend/src/utils/encryption.utils.d.ts.map +1 -0
  114. package/dist/backend/backend/src/utils/encryption.utils.js +162 -0
  115. package/dist/backend/backend/src/utils/encryption.utils.js.map +1 -0
  116. package/dist/backend/backend/src/utils/google-userinfo.utils.d.ts +41 -0
  117. package/dist/backend/backend/src/utils/google-userinfo.utils.d.ts.map +1 -0
  118. package/dist/backend/backend/src/utils/google-userinfo.utils.js +44 -0
  119. package/dist/backend/backend/src/utils/google-userinfo.utils.js.map +1 -0
  120. package/dist/cli/backend/src/config/oauth.config.d.ts +33 -0
  121. package/dist/cli/backend/src/config/oauth.config.d.ts.map +1 -0
  122. package/dist/cli/backend/src/config/oauth.config.js +45 -0
  123. package/dist/cli/backend/src/config/oauth.config.js.map +1 -0
  124. package/dist/cli/backend/src/services/credential/credential-store.service.d.ts +161 -0
  125. package/dist/cli/backend/src/services/credential/credential-store.service.d.ts.map +1 -0
  126. package/dist/cli/backend/src/services/credential/credential-store.service.js +298 -0
  127. package/dist/cli/backend/src/services/credential/credential-store.service.js.map +1 -0
  128. package/dist/cli/backend/src/services/credential/helpers/gemini-cli-workspace.helper.d.ts +105 -0
  129. package/dist/cli/backend/src/services/credential/helpers/gemini-cli-workspace.helper.d.ts.map +1 -0
  130. package/dist/cli/backend/src/services/credential/helpers/gemini-cli-workspace.helper.js +272 -0
  131. package/dist/cli/backend/src/services/credential/helpers/gemini-cli-workspace.helper.js.map +1 -0
  132. package/dist/cli/backend/src/services/mcp-server.d.ts +46 -2
  133. package/dist/cli/backend/src/services/mcp-server.d.ts.map +1 -1
  134. package/dist/cli/backend/src/services/mcp-server.js +216 -211
  135. package/dist/cli/backend/src/services/mcp-server.js.map +1 -1
  136. package/dist/cli/backend/src/services/mcp-tool-definitions.d.ts +254 -0
  137. package/dist/cli/backend/src/services/mcp-tool-definitions.d.ts.map +1 -0
  138. package/dist/cli/backend/src/services/mcp-tool-definitions.js +285 -0
  139. package/dist/cli/backend/src/services/mcp-tool-definitions.js.map +1 -0
  140. package/dist/cli/backend/src/services/settings/settings.service.d.ts +168 -0
  141. package/dist/cli/backend/src/services/settings/settings.service.d.ts.map +1 -0
  142. package/dist/cli/backend/src/services/settings/settings.service.js +312 -0
  143. package/dist/cli/backend/src/services/settings/settings.service.js.map +1 -0
  144. package/dist/cli/backend/src/services/skill/skill-executor.service.d.ts +177 -0
  145. package/dist/cli/backend/src/services/skill/skill-executor.service.d.ts.map +1 -0
  146. package/dist/cli/backend/src/services/skill/skill-executor.service.js +624 -0
  147. package/dist/cli/backend/src/services/skill/skill-executor.service.js.map +1 -0
  148. package/dist/cli/backend/src/services/skill/skill.service.d.ts +273 -0
  149. package/dist/cli/backend/src/services/skill/skill.service.d.ts.map +1 -0
  150. package/dist/cli/backend/src/services/skill/skill.service.js +655 -0
  151. package/dist/cli/backend/src/services/skill/skill.service.js.map +1 -0
  152. package/dist/cli/backend/src/types/credential.types.d.ts +185 -0
  153. package/dist/cli/backend/src/types/credential.types.d.ts.map +1 -0
  154. package/dist/cli/backend/src/types/credential.types.js +76 -0
  155. package/dist/cli/backend/src/types/credential.types.js.map +1 -0
  156. package/dist/cli/backend/src/types/skill.types.d.ts +9 -0
  157. package/dist/cli/backend/src/types/skill.types.d.ts.map +1 -1
  158. package/dist/cli/backend/src/types/skill.types.js.map +1 -1
  159. package/dist/cli/backend/src/utils/encryption.utils.d.ts +57 -0
  160. package/dist/cli/backend/src/utils/encryption.utils.d.ts.map +1 -0
  161. package/dist/cli/backend/src/utils/encryption.utils.js +162 -0
  162. package/dist/cli/backend/src/utils/encryption.utils.js.map +1 -0
  163. package/dist/cli/backend/src/utils/google-userinfo.utils.d.ts +41 -0
  164. package/dist/cli/backend/src/utils/google-userinfo.utils.d.ts.map +1 -0
  165. package/dist/cli/backend/src/utils/google-userinfo.utils.js +44 -0
  166. package/dist/cli/backend/src/utils/google-userinfo.utils.js.map +1 -0
  167. package/dist/cli/backend/src/utils/skill-md-parser.d.ts +38 -0
  168. package/dist/cli/backend/src/utils/skill-md-parser.d.ts.map +1 -0
  169. package/dist/cli/backend/src/utils/skill-md-parser.js +47 -0
  170. package/dist/cli/backend/src/utils/skill-md-parser.js.map +1 -0
  171. package/frontend/dist/assets/{index-dc92ab64.css → index-6aaa0630.css} +1 -1
  172. package/frontend/dist/assets/{index-76d76633.js → index-70356616.js} +334 -328
  173. package/frontend/dist/index.html +2 -2
  174. package/package.json +1 -1
  175. package/config/experts/empathetic-resolver/expert.json +0 -11
  176. package/config/experts/empathetic-resolver.md +0 -32
  177. package/config/experts/pragmatic-architect/expert.json +0 -11
  178. package/config/experts/pragmatic-architect.md +0 -32
  179. package/config/experts/viral-alchemist/expert.json +0 -11
  180. package/config/experts/viral-alchemist.md +0 -32
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Credential Store Service
3
+ *
4
+ * Manages the workspace-level credential registry and per-credential
5
+ * encrypted payload files under `~/.crewly/credentials/`.
6
+ *
7
+ * The store is split into two surfaces:
8
+ *
9
+ * - **Metadata API** (`listCredentials`, `getCredential`, `findCredentialsByProvider`):
10
+ * returns `CredentialRegistryEntry` objects containing only non-secret
11
+ * information (id, name, provider, scopes, email, timestamps). Safe to
12
+ * expose to agent / LLM context and over MCP.
13
+ *
14
+ * - **Payload API** (`getPayload`, `setPayload`): decrypts the per-credential
15
+ * `.enc` file to return the actual token value(s). **Intended only for
16
+ * credential helpers and the skill executor** (which injects values into
17
+ * skill subprocess env vars). Callers must never return payload contents
18
+ * to agent/LLM context.
19
+ *
20
+ * Encrypted file format and threat model: see `utils/encryption.utils.ts`.
21
+ *
22
+ * @module services/credential/credential-store.service
23
+ */
24
+ import { CredentialRegistryEntry, CredentialPayload, GoogleOAuthPayload, CredentialType, CredentialHelperName, CredentialStatus } from '../../types/credential.types.js';
25
+ /**
26
+ * Arguments for adding an API key credential.
27
+ */
28
+ export interface AddApiKeyInput {
29
+ /** User-provided display name (must be unique recommended, not enforced). */
30
+ name: string;
31
+ /** Provider identifier (e.g., "gemini", "openai"). */
32
+ provider: string;
33
+ /** The actual API key value — never logged or returned to agent. */
34
+ value: string;
35
+ }
36
+ /**
37
+ * Arguments for adding an OAuth credential (usually produced by a helper's
38
+ * capture flow).
39
+ */
40
+ export interface AddOAuthInput {
41
+ /** User-provided display name. */
42
+ name: string;
43
+ /** Provider identifier (e.g., "google"). */
44
+ provider: string;
45
+ /** Which helper manages refresh for this credential. */
46
+ helper: CredentialHelperName;
47
+ /** Full OAuth token payload captured from the helper's login flow. */
48
+ payload: GoogleOAuthPayload;
49
+ }
50
+ /**
51
+ * Updatable metadata fields on a credential. Other fields are managed
52
+ * internally (id, type, provider, encFile, createdAt).
53
+ */
54
+ export interface UpdateCredentialMetadata {
55
+ name?: string;
56
+ status?: CredentialStatus;
57
+ lastUsedAt?: string;
58
+ expiresAt?: number;
59
+ scopes?: string[];
60
+ accountEmail?: string;
61
+ }
62
+ /**
63
+ * Options for constructing a `CredentialStoreService`. Intended mainly for
64
+ * tests — production uses the default paths under `~/.crewly/credentials/`.
65
+ */
66
+ export interface CredentialStoreOptions {
67
+ /** Override the credentials directory (default: `~/.crewly/credentials`). */
68
+ dir?: string;
69
+ /** Override the master.key file path (default: `<dir>/master.key`). */
70
+ masterKeyPath?: string;
71
+ }
72
+ /**
73
+ * File-based credential store.
74
+ */
75
+ export declare class CredentialStoreService {
76
+ private readonly dir;
77
+ private readonly registryPath;
78
+ private readonly masterKeyPath;
79
+ private initialized;
80
+ constructor(options?: CredentialStoreOptions);
81
+ /**
82
+ * Ensure the credentials directory and master.key exist. Called
83
+ * automatically by every public method — safe to invoke explicitly
84
+ * for eager initialization.
85
+ */
86
+ init(): Promise<void>;
87
+ /**
88
+ * List all credentials' metadata. Does not include secret values.
89
+ */
90
+ listCredentials(): Promise<CredentialRegistryEntry[]>;
91
+ /**
92
+ * Get a single credential's metadata by id.
93
+ *
94
+ * @throws CredentialNotFoundError if no entry with that id exists
95
+ */
96
+ getCredential(id: string): Promise<CredentialRegistryEntry>;
97
+ /**
98
+ * Find all credentials for a given provider, optionally filtered by type.
99
+ */
100
+ findCredentialsByProvider(provider: string, type?: CredentialType): Promise<CredentialRegistryEntry[]>;
101
+ /**
102
+ * Add a new API key credential. The value is encrypted on disk and
103
+ * never exposed back through the metadata API.
104
+ */
105
+ addApiKey(input: AddApiKeyInput): Promise<CredentialRegistryEntry>;
106
+ /**
107
+ * Add a new OAuth credential. Typically called by a helper's capture
108
+ * flow after completing an OAuth exchange.
109
+ */
110
+ addOAuth(input: AddOAuthInput): Promise<CredentialRegistryEntry>;
111
+ /**
112
+ * Update a credential's metadata. Does not touch the encrypted payload.
113
+ * Use `setPayload` to rewrite the secret material (e.g., after refresh).
114
+ *
115
+ * @throws CredentialNotFoundError if no entry with that id exists
116
+ */
117
+ updateCredential(id: string, patch: UpdateCredentialMetadata): Promise<CredentialRegistryEntry>;
118
+ /**
119
+ * Delete a credential (removes registry entry and encrypted payload).
120
+ * The registry removal is authoritative — if the `.enc` file is missing
121
+ * for any reason, that is not an error.
122
+ *
123
+ * @throws CredentialNotFoundError if no entry with that id exists
124
+ */
125
+ deleteCredential(id: string): Promise<void>;
126
+ /**
127
+ * Decrypt and return the credential's payload. **Do not return the
128
+ * result to agent / LLM contexts** — intended only for helpers
129
+ * refreshing tokens and the skill executor injecting env vars.
130
+ *
131
+ * @throws CredentialNotFoundError if no entry with that id exists
132
+ */
133
+ getPayload(id: string): Promise<CredentialPayload>;
134
+ /**
135
+ * Encrypt and write a new payload for an existing credential. Used by
136
+ * helpers after a successful token refresh.
137
+ *
138
+ * @throws CredentialNotFoundError if no entry with that id exists
139
+ */
140
+ setPayload(id: string, payload: CredentialPayload): Promise<void>;
141
+ private readRegistry;
142
+ private writeRegistry;
143
+ private appendEntry;
144
+ private encFilePath;
145
+ private writePayload;
146
+ private newId;
147
+ }
148
+ /**
149
+ * Get the process-wide CredentialStoreService singleton.
150
+ */
151
+ export declare function getCredentialStoreService(): CredentialStoreService;
152
+ /**
153
+ * Reset the singleton — for tests only.
154
+ */
155
+ export declare function resetCredentialStoreService(): void;
156
+ /**
157
+ * Replace the singleton with a specific instance — for tests only.
158
+ * Allows route/controller tests to point the store at a scratch directory.
159
+ */
160
+ export declare function _setCredentialStoreForTesting(svc: CredentialStoreService): void;
161
+ //# sourceMappingURL=credential-store.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-store.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/credential/credential-store.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Credential Store Service
3
+ *
4
+ * Manages the workspace-level credential registry and per-credential
5
+ * encrypted payload files under `~/.crewly/credentials/`.
6
+ *
7
+ * The store is split into two surfaces:
8
+ *
9
+ * - **Metadata API** (`listCredentials`, `getCredential`, `findCredentialsByProvider`):
10
+ * returns `CredentialRegistryEntry` objects containing only non-secret
11
+ * information (id, name, provider, scopes, email, timestamps). Safe to
12
+ * expose to agent / LLM context and over MCP.
13
+ *
14
+ * - **Payload API** (`getPayload`, `setPayload`): decrypts the per-credential
15
+ * `.enc` file to return the actual token value(s). **Intended only for
16
+ * credential helpers and the skill executor** (which injects values into
17
+ * skill subprocess env vars). Callers must never return payload contents
18
+ * to agent/LLM context.
19
+ *
20
+ * Encrypted file format and threat model: see `utils/encryption.utils.ts`.
21
+ *
22
+ * @module services/credential/credential-store.service
23
+ */
24
+ import { promises as fs } from 'fs';
25
+ import path from 'path';
26
+ import crypto from 'crypto';
27
+ import { CredentialNotFoundError, } from '../../types/credential.types.js';
28
+ import { ensureDir, atomicWriteFile, atomicWriteJson, safeReadJson, withOperationLock, } from '../../utils/file-io.utils.js';
29
+ import { encrypt, decrypt, ensureMasterKey, defaultCredentialsDir, } from '../../utils/encryption.utils.js';
30
+ // ============================================================================
31
+ // Constants
32
+ // ============================================================================
33
+ const REGISTRY_FILENAME = 'registry.json';
34
+ const EMPTY_REGISTRY = {
35
+ schemaVersion: 1,
36
+ credentials: [],
37
+ };
38
+ /**
39
+ * File-based credential store.
40
+ */
41
+ export class CredentialStoreService {
42
+ dir;
43
+ registryPath;
44
+ masterKeyPath;
45
+ initialized = false;
46
+ constructor(options) {
47
+ this.dir = options?.dir ?? defaultCredentialsDir();
48
+ this.registryPath = path.join(this.dir, REGISTRY_FILENAME);
49
+ this.masterKeyPath =
50
+ options?.masterKeyPath ?? path.join(this.dir, 'master.key');
51
+ }
52
+ // ------------------------------------------------------------------
53
+ // Initialization (idempotent, lazy)
54
+ // ------------------------------------------------------------------
55
+ /**
56
+ * Ensure the credentials directory and master.key exist. Called
57
+ * automatically by every public method — safe to invoke explicitly
58
+ * for eager initialization.
59
+ */
60
+ async init() {
61
+ if (this.initialized)
62
+ return;
63
+ await ensureDir(this.dir);
64
+ await ensureMasterKey(this.masterKeyPath);
65
+ this.initialized = true;
66
+ }
67
+ // ------------------------------------------------------------------
68
+ // Metadata API (safe to expose to agent/LLM)
69
+ // ------------------------------------------------------------------
70
+ /**
71
+ * List all credentials' metadata. Does not include secret values.
72
+ */
73
+ async listCredentials() {
74
+ await this.init();
75
+ const reg = await this.readRegistry();
76
+ return reg.credentials;
77
+ }
78
+ /**
79
+ * Get a single credential's metadata by id.
80
+ *
81
+ * @throws CredentialNotFoundError if no entry with that id exists
82
+ */
83
+ async getCredential(id) {
84
+ await this.init();
85
+ const reg = await this.readRegistry();
86
+ const entry = reg.credentials.find((c) => c.id === id);
87
+ if (!entry)
88
+ throw new CredentialNotFoundError(id);
89
+ return entry;
90
+ }
91
+ /**
92
+ * Find all credentials for a given provider, optionally filtered by type.
93
+ */
94
+ async findCredentialsByProvider(provider, type) {
95
+ await this.init();
96
+ const reg = await this.readRegistry();
97
+ return reg.credentials.filter((c) => c.provider === provider && (!type || c.type === type));
98
+ }
99
+ // ------------------------------------------------------------------
100
+ // Add
101
+ // ------------------------------------------------------------------
102
+ /**
103
+ * Add a new API key credential. The value is encrypted on disk and
104
+ * never exposed back through the metadata API.
105
+ */
106
+ async addApiKey(input) {
107
+ await this.init();
108
+ const id = this.newId();
109
+ const encFile = `${id}.enc`;
110
+ const payload = { type: 'api-key', value: input.value };
111
+ const now = new Date().toISOString();
112
+ const entry = {
113
+ id,
114
+ name: input.name,
115
+ type: 'api-key',
116
+ provider: input.provider,
117
+ createdAt: now,
118
+ updatedAt: now,
119
+ status: 'active',
120
+ encFile,
121
+ };
122
+ await this.writePayload(encFile, payload);
123
+ await this.appendEntry(entry);
124
+ return entry;
125
+ }
126
+ /**
127
+ * Add a new OAuth credential. Typically called by a helper's capture
128
+ * flow after completing an OAuth exchange.
129
+ */
130
+ async addOAuth(input) {
131
+ await this.init();
132
+ const id = this.newId();
133
+ const encFile = `${id}.enc`;
134
+ const now = new Date().toISOString();
135
+ const entry = {
136
+ id,
137
+ name: input.name,
138
+ type: 'google-oauth',
139
+ provider: input.provider,
140
+ helper: input.helper,
141
+ scopes: input.payload.scopes,
142
+ accountEmail: input.payload.accountEmail,
143
+ expiresAt: input.payload.expiresAt,
144
+ createdAt: now,
145
+ updatedAt: now,
146
+ status: 'active',
147
+ encFile,
148
+ };
149
+ await this.writePayload(encFile, input.payload);
150
+ await this.appendEntry(entry);
151
+ return entry;
152
+ }
153
+ // ------------------------------------------------------------------
154
+ // Update
155
+ // ------------------------------------------------------------------
156
+ /**
157
+ * Update a credential's metadata. Does not touch the encrypted payload.
158
+ * Use `setPayload` to rewrite the secret material (e.g., after refresh).
159
+ *
160
+ * @throws CredentialNotFoundError if no entry with that id exists
161
+ */
162
+ async updateCredential(id, patch) {
163
+ await this.init();
164
+ let updated;
165
+ await withOperationLock(this.registryPath, async () => {
166
+ const reg = await this.readRegistry();
167
+ const idx = reg.credentials.findIndex((c) => c.id === id);
168
+ if (idx < 0)
169
+ throw new CredentialNotFoundError(id);
170
+ updated = {
171
+ ...reg.credentials[idx],
172
+ ...patch,
173
+ updatedAt: new Date().toISOString(),
174
+ };
175
+ reg.credentials[idx] = updated;
176
+ await this.writeRegistry(reg);
177
+ });
178
+ return updated;
179
+ }
180
+ // ------------------------------------------------------------------
181
+ // Delete
182
+ // ------------------------------------------------------------------
183
+ /**
184
+ * Delete a credential (removes registry entry and encrypted payload).
185
+ * The registry removal is authoritative — if the `.enc` file is missing
186
+ * for any reason, that is not an error.
187
+ *
188
+ * @throws CredentialNotFoundError if no entry with that id exists
189
+ */
190
+ async deleteCredential(id) {
191
+ await this.init();
192
+ let encFileToDelete = null;
193
+ await withOperationLock(this.registryPath, async () => {
194
+ const reg = await this.readRegistry();
195
+ const idx = reg.credentials.findIndex((c) => c.id === id);
196
+ if (idx < 0)
197
+ throw new CredentialNotFoundError(id);
198
+ encFileToDelete = reg.credentials[idx].encFile;
199
+ reg.credentials.splice(idx, 1);
200
+ await this.writeRegistry(reg);
201
+ });
202
+ if (encFileToDelete) {
203
+ try {
204
+ await fs.unlink(this.encFilePath(encFileToDelete));
205
+ }
206
+ catch {
207
+ // registry is source of truth — ignore missing .enc file
208
+ }
209
+ }
210
+ }
211
+ // ------------------------------------------------------------------
212
+ // Payload API (INTERNAL — helpers + executor only)
213
+ // ------------------------------------------------------------------
214
+ /**
215
+ * Decrypt and return the credential's payload. **Do not return the
216
+ * result to agent / LLM contexts** — intended only for helpers
217
+ * refreshing tokens and the skill executor injecting env vars.
218
+ *
219
+ * @throws CredentialNotFoundError if no entry with that id exists
220
+ */
221
+ async getPayload(id) {
222
+ await this.init();
223
+ const entry = await this.getCredential(id);
224
+ const b64 = await fs.readFile(this.encFilePath(entry.encFile), 'utf8');
225
+ const encBuffer = Buffer.from(b64, 'base64');
226
+ const plaintext = await decrypt(encBuffer, this.masterKeyPath);
227
+ return JSON.parse(plaintext);
228
+ }
229
+ /**
230
+ * Encrypt and write a new payload for an existing credential. Used by
231
+ * helpers after a successful token refresh.
232
+ *
233
+ * @throws CredentialNotFoundError if no entry with that id exists
234
+ */
235
+ async setPayload(id, payload) {
236
+ await this.init();
237
+ const entry = await this.getCredential(id);
238
+ await this.writePayload(entry.encFile, payload);
239
+ }
240
+ // ------------------------------------------------------------------
241
+ // Internals
242
+ // ------------------------------------------------------------------
243
+ async readRegistry() {
244
+ return safeReadJson(this.registryPath, {
245
+ ...EMPTY_REGISTRY,
246
+ credentials: [],
247
+ });
248
+ }
249
+ async writeRegistry(reg) {
250
+ await atomicWriteJson(this.registryPath, reg);
251
+ }
252
+ async appendEntry(entry) {
253
+ await withOperationLock(this.registryPath, async () => {
254
+ const reg = await this.readRegistry();
255
+ reg.credentials.push(entry);
256
+ await this.writeRegistry(reg);
257
+ });
258
+ }
259
+ encFilePath(encFile) {
260
+ return path.join(this.dir, encFile);
261
+ }
262
+ async writePayload(encFile, payload) {
263
+ const json = JSON.stringify(payload);
264
+ const enc = await encrypt(json, this.masterKeyPath);
265
+ // Encrypted bytes are stored base64-encoded so they go through
266
+ // atomicWriteFile's utf8 serializer safely.
267
+ await atomicWriteFile(this.encFilePath(encFile), enc.toString('base64'));
268
+ }
269
+ newId() {
270
+ return `cred-${crypto.randomUUID()}`;
271
+ }
272
+ }
273
+ // ============================================================================
274
+ // Singleton
275
+ // ============================================================================
276
+ let instance = null;
277
+ /**
278
+ * Get the process-wide CredentialStoreService singleton.
279
+ */
280
+ export function getCredentialStoreService() {
281
+ if (!instance)
282
+ instance = new CredentialStoreService();
283
+ return instance;
284
+ }
285
+ /**
286
+ * Reset the singleton — for tests only.
287
+ */
288
+ export function resetCredentialStoreService() {
289
+ instance = null;
290
+ }
291
+ /**
292
+ * Replace the singleton with a specific instance — for tests only.
293
+ * Allows route/controller tests to point the store at a scratch directory.
294
+ */
295
+ export function _setCredentialStoreForTesting(svc) {
296
+ instance = svc;
297
+ }
298
+ //# sourceMappingURL=credential-store.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-store.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/credential/credential-store.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Gemini CLI Workspace Credential Helper
3
+ *
4
+ * Piggybacks on the Google Workspace extension for Gemini CLI
5
+ * (https://github.com/gemini-cli-extensions/workspace) for Google OAuth
6
+ * credential acquisition and refresh.
7
+ *
8
+ * **Capture** reads the extension's file-storage token file (written when
9
+ * the user runs the extension with `GEMINI_CLI_WORKSPACE_FORCE_FILE_STORAGE=true`)
10
+ * and imports the tokens into Crewly's own encrypted store.
11
+ *
12
+ * **Refresh** POSTs the stored `refresh_token` to the extension's Cloud
13
+ * Function `/refreshToken` endpoint — no client_secret needed on our side.
14
+ *
15
+ * After capture, Crewly owns the tokens; the extension's own state is not
16
+ * depended on for day-to-day operation.
17
+ *
18
+ * @module services/credential/helpers/gemini-cli-workspace.helper
19
+ */
20
+ import { CredentialHelper, CredentialHelperName, CredentialRegistryEntry, GoogleOAuthPayload } from '../../../types/credential.types.js';
21
+ import { CredentialStoreService } from '../credential-store.service.js';
22
+ import { FetchLike as UserinfoFetchLike } from '../../../utils/google-userinfo.utils.js';
23
+ /**
24
+ * Minimal fetch-compatible function shape (for test injection). Re-exported
25
+ * from the google-userinfo utility so callers can import a single type.
26
+ */
27
+ export type FetchLike = UserinfoFetchLike;
28
+ /**
29
+ * Helper configuration — all fields optional; defaults match production.
30
+ */
31
+ export interface GeminiCliHelperConfig {
32
+ /** Path to the extension install directory (default: `~/.gemini/extensions/google-workspace`). */
33
+ extensionPath?: string;
34
+ /** Cloud Function base URL. */
35
+ cloudFunctionUrl?: string;
36
+ /** Path on the cloud function for refresh calls. */
37
+ refreshPath?: string;
38
+ /** Client ID to store on the credential (for later refresh identification). */
39
+ clientId?: string;
40
+ /** Refresh buffer in ms — refresh if token expires within this window. */
41
+ expiryBufferMs?: number;
42
+ /** Fetch implementation (injectable for tests). */
43
+ fetch?: FetchLike;
44
+ }
45
+ /**
46
+ * Credential helper that reads tokens from the gemini-cli-workspace extension
47
+ * and refreshes them via the extension's Cloud Function.
48
+ */
49
+ export declare class GeminiCliWorkspaceHelper implements CredentialHelper {
50
+ readonly name: CredentialHelperName;
51
+ private readonly extensionPath;
52
+ private readonly cloudFunctionUrl;
53
+ private readonly refreshPath;
54
+ private readonly clientId;
55
+ private readonly expiryBufferMs;
56
+ private readonly fetchFn;
57
+ private readonly store;
58
+ /** Per-credential refresh in-flight promises (serializes concurrent refreshes). */
59
+ private readonly refreshInFlight;
60
+ /** Per-credential last refresh attempt timestamp (for cooldown). */
61
+ private readonly lastRefreshAttempt;
62
+ constructor(store: CredentialStoreService, config?: GeminiCliHelperConfig);
63
+ /**
64
+ * Read the extension's current token file and return its contents as a
65
+ * `GoogleOAuthPayload` ready to persist in Crewly's store.
66
+ *
67
+ * The user must have completed extension login with
68
+ * `GEMINI_CLI_WORKSPACE_FORCE_FILE_STORAGE=true` before calling this.
69
+ */
70
+ captureFromFile(): Promise<GoogleOAuthPayload>;
71
+ /**
72
+ * Remove the extension's token file. Call after `captureFromFile()` to
73
+ * prepare for the next account's login (the extension otherwise returns
74
+ * cached credentials if scopes match, skipping the login prompt).
75
+ * Leaves the master.key file untouched so existing ciphertexts remain
76
+ * decryptable if needed.
77
+ */
78
+ clearExtensionFile(): Promise<void>;
79
+ /**
80
+ * Return a valid `GoogleOAuthPayload`, refreshing via the extension's
81
+ * cloud function if the access token is within the expiry buffer.
82
+ *
83
+ * Concurrent calls for the same credential share a single refresh.
84
+ *
85
+ * @throws CredentialRevokedError if the refresh token is no longer valid
86
+ */
87
+ getAccessToken(entry: CredentialRegistryEntry, payload: GoogleOAuthPayload): Promise<GoogleOAuthPayload>;
88
+ private doRefresh;
89
+ /**
90
+ * Decrypt the extension's `{iv_hex}:{authTag_hex}:{ciphertext_hex}`
91
+ * format (AES-256-GCM).
92
+ */
93
+ private decryptExtensionFormat;
94
+ }
95
+ /**
96
+ * Encrypt a plaintext string with the extension's file format. Used in
97
+ * tests to generate fake token files.
98
+ */
99
+ export declare function encryptExtensionFormatForTesting(plaintext: string, key: Buffer): string;
100
+ /**
101
+ * Derive the extension's encryption key using the real scrypt + salt.
102
+ * Test-only export.
103
+ */
104
+ export declare function deriveExtensionKeyForTesting(masterKey: Buffer): Buffer;
105
+ //# sourceMappingURL=gemini-cli-workspace.helper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini-cli-workspace.helper.d.ts","sourceRoot":"","sources":["../../../../../../../backend/src/services/credential/helpers/gemini-cli-workspace.helper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAOH,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAEnB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAMxE,OAAO,EAEL,SAAS,IAAI,iBAAiB,EAC/B,MAAM,yCAAyC,CAAC;AA2CjD;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,iBAAiB,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,kGAAkG;IAClG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+BAA+B;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB;AAMD;;;GAGG;AACH,qBAAa,wBAAyB,YAAW,gBAAgB;IAC/D,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAA0B;IAE7D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAY;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyB;IAE/C,mFAAmF;IACnF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAG5B;IACJ,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA6B;gBAG9D,KAAK,EAAE,sBAAsB,EAC7B,MAAM,CAAC,EAAE,qBAAqB;IAkBhC;;;;;;OAMG;IACG,eAAe,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAoEpD;;;;;;OAMG;IACG,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAczC;;;;;;;OAOG;IACG,cAAc,CAClB,KAAK,EAAE,uBAAuB,EAC9B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,kBAAkB,CAAC;YA4BhB,SAAS;IAuEvB;;;OAGG;IACH,OAAO,CAAC,sBAAsB;CAoB/B;AAMD;;;GAGG;AACH,wBAAgB,gCAAgC,CAC9C,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,MAAM,CAOR;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAGtE"}