crewly 1.5.21 → 1.6.0

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 (113) hide show
  1. package/config/roles/orchestrator/prompt.md +182 -25
  2. package/config/skills/agent/core/cancel-followup/SKILL.md +38 -0
  3. package/config/skills/agent/core/cancel-followup/execute.sh +111 -0
  4. package/config/skills/agent/core/cancel-followup/execute.test.sh +42 -0
  5. package/config/skills/agent/core/list-my-followups/SKILL.md +36 -0
  6. package/config/skills/agent/core/list-my-followups/execute.sh +93 -0
  7. package/config/skills/agent/core/list-my-followups/execute.test.sh +41 -0
  8. package/config/skills/agent/core/schedule-followup/SKILL.md +53 -0
  9. package/config/skills/agent/core/schedule-followup/execute.sh +195 -0
  10. package/config/skills/agent/core/schedule-followup/execute.test.sh +48 -0
  11. package/config/skills/agent/core/watch-for-event/SKILL.md +60 -0
  12. package/config/skills/agent/core/watch-for-event/execute.sh +177 -0
  13. package/config/skills/agent/core/watch-for-event/execute.test.sh +43 -0
  14. package/config/skills/orchestrator/credential-manager/SKILL.md +218 -0
  15. package/config/skills/orchestrator/credential-manager/execute.sh +166 -0
  16. package/dist/backend/backend/src/controllers/credentials/credentials.controller.d.ts +80 -0
  17. package/dist/backend/backend/src/controllers/credentials/credentials.controller.d.ts.map +1 -0
  18. package/dist/backend/backend/src/controllers/credentials/credentials.controller.js +365 -0
  19. package/dist/backend/backend/src/controllers/credentials/credentials.controller.js.map +1 -0
  20. package/dist/backend/backend/src/controllers/credentials/credentials.routes.d.ts +26 -0
  21. package/dist/backend/backend/src/controllers/credentials/credentials.routes.d.ts.map +1 -0
  22. package/dist/backend/backend/src/controllers/credentials/credentials.routes.js +40 -0
  23. package/dist/backend/backend/src/controllers/credentials/credentials.routes.js.map +1 -0
  24. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +23 -14
  25. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
  26. package/dist/backend/backend/src/scripts/backfill-mission-priority.d.ts +3 -1
  27. package/dist/backend/backend/src/scripts/backfill-mission-priority.d.ts.map +1 -1
  28. package/dist/backend/backend/src/scripts/backfill-mission-priority.js +16 -4
  29. package/dist/backend/backend/src/scripts/backfill-mission-priority.js.map +1 -1
  30. package/dist/backend/backend/src/services/browser/browser-proxy.service.d.ts.map +1 -1
  31. package/dist/backend/backend/src/services/browser/browser-proxy.service.js +22 -2
  32. package/dist/backend/backend/src/services/browser/browser-proxy.service.js.map +1 -1
  33. package/dist/backend/backend/src/services/credential/credential-store.service.d.ts +161 -0
  34. package/dist/backend/backend/src/services/credential/credential-store.service.d.ts.map +1 -0
  35. package/dist/backend/backend/src/services/credential/credential-store.service.js +298 -0
  36. package/dist/backend/backend/src/services/credential/credential-store.service.js.map +1 -0
  37. package/dist/backend/backend/src/services/credential/helpers/gemini-cli-workspace.helper.d.ts +117 -0
  38. package/dist/backend/backend/src/services/credential/helpers/gemini-cli-workspace.helper.d.ts.map +1 -0
  39. package/dist/backend/backend/src/services/credential/helpers/gemini-cli-workspace.helper.js +293 -0
  40. package/dist/backend/backend/src/services/credential/helpers/gemini-cli-workspace.helper.js.map +1 -0
  41. package/dist/backend/backend/src/services/project/task.service.d.ts +18 -2
  42. package/dist/backend/backend/src/services/project/task.service.d.ts.map +1 -1
  43. package/dist/backend/backend/src/services/project/task.service.js +69 -53
  44. package/dist/backend/backend/src/services/project/task.service.js.map +1 -1
  45. package/dist/backend/backend/src/services/v3/contract-matcher.d.ts +20 -0
  46. package/dist/backend/backend/src/services/v3/contract-matcher.d.ts.map +1 -0
  47. package/dist/backend/backend/src/services/v3/contract-matcher.js +33 -0
  48. package/dist/backend/backend/src/services/v3/contract-matcher.js.map +1 -0
  49. package/dist/backend/backend/src/services/v3/escalation.service.d.ts +20 -1
  50. package/dist/backend/backend/src/services/v3/escalation.service.d.ts.map +1 -1
  51. package/dist/backend/backend/src/services/v3/escalation.service.js +97 -28
  52. package/dist/backend/backend/src/services/v3/escalation.service.js.map +1 -1
  53. package/dist/backend/backend/src/services/v3/service-contract-gate.service.d.ts +6 -4
  54. package/dist/backend/backend/src/services/v3/service-contract-gate.service.d.ts.map +1 -1
  55. package/dist/backend/backend/src/services/v3/service-contract-gate.service.js +18 -28
  56. package/dist/backend/backend/src/services/v3/service-contract-gate.service.js.map +1 -1
  57. package/dist/backend/backend/src/services/v3/team-trigger-reconciler.service.d.ts.map +1 -1
  58. package/dist/backend/backend/src/services/v3/team-trigger-reconciler.service.js +14 -9
  59. package/dist/backend/backend/src/services/v3/team-trigger-reconciler.service.js.map +1 -1
  60. package/dist/backend/backend/src/services/v3/trigger-engine.service.d.ts +34 -1
  61. package/dist/backend/backend/src/services/v3/trigger-engine.service.d.ts.map +1 -1
  62. package/dist/backend/backend/src/services/v3/trigger-engine.service.js +115 -5
  63. package/dist/backend/backend/src/services/v3/trigger-engine.service.js.map +1 -1
  64. package/dist/backend/backend/src/types/credential.types.d.ts +185 -0
  65. package/dist/backend/backend/src/types/credential.types.d.ts.map +1 -0
  66. package/dist/backend/backend/src/types/credential.types.js +76 -0
  67. package/dist/backend/backend/src/types/credential.types.js.map +1 -0
  68. package/dist/backend/backend/src/utils/encryption.utils.d.ts +57 -0
  69. package/dist/backend/backend/src/utils/encryption.utils.d.ts.map +1 -0
  70. package/dist/backend/backend/src/utils/encryption.utils.js +162 -0
  71. package/dist/backend/backend/src/utils/encryption.utils.js.map +1 -0
  72. package/dist/cli/backend/src/services/credential/credential-store.service.d.ts +161 -0
  73. package/dist/cli/backend/src/services/credential/credential-store.service.d.ts.map +1 -0
  74. package/dist/cli/backend/src/services/credential/credential-store.service.js +298 -0
  75. package/dist/cli/backend/src/services/credential/credential-store.service.js.map +1 -0
  76. package/dist/cli/backend/src/services/credential/helpers/gemini-cli-workspace.helper.d.ts +117 -0
  77. package/dist/cli/backend/src/services/credential/helpers/gemini-cli-workspace.helper.d.ts.map +1 -0
  78. package/dist/cli/backend/src/services/credential/helpers/gemini-cli-workspace.helper.js +293 -0
  79. package/dist/cli/backend/src/services/credential/helpers/gemini-cli-workspace.helper.js.map +1 -0
  80. package/dist/cli/backend/src/services/settings/settings.service.d.ts +168 -0
  81. package/dist/cli/backend/src/services/settings/settings.service.d.ts.map +1 -0
  82. package/dist/cli/backend/src/services/settings/settings.service.js +312 -0
  83. package/dist/cli/backend/src/services/settings/settings.service.js.map +1 -0
  84. package/dist/cli/backend/src/services/skill/skill-executor.service.d.ts +159 -0
  85. package/dist/cli/backend/src/services/skill/skill-executor.service.d.ts.map +1 -0
  86. package/dist/cli/backend/src/services/skill/skill-executor.service.js +626 -0
  87. package/dist/cli/backend/src/services/skill/skill-executor.service.js.map +1 -0
  88. package/dist/cli/backend/src/services/skill/skill.service.d.ts +273 -0
  89. package/dist/cli/backend/src/services/skill/skill.service.d.ts.map +1 -0
  90. package/dist/cli/backend/src/services/skill/skill.service.js +655 -0
  91. package/dist/cli/backend/src/services/skill/skill.service.js.map +1 -0
  92. package/dist/cli/backend/src/types/credential.types.d.ts +185 -0
  93. package/dist/cli/backend/src/types/credential.types.d.ts.map +1 -0
  94. package/dist/cli/backend/src/types/credential.types.js +76 -0
  95. package/dist/cli/backend/src/types/credential.types.js.map +1 -0
  96. package/dist/cli/backend/src/utils/encryption.utils.d.ts +57 -0
  97. package/dist/cli/backend/src/utils/encryption.utils.d.ts.map +1 -0
  98. package/dist/cli/backend/src/utils/encryption.utils.js +162 -0
  99. package/dist/cli/backend/src/utils/encryption.utils.js.map +1 -0
  100. package/dist/cli/backend/src/utils/skill-md-parser.d.ts +38 -0
  101. package/dist/cli/backend/src/utils/skill-md-parser.d.ts.map +1 -0
  102. package/dist/cli/backend/src/utils/skill-md-parser.js +47 -0
  103. package/dist/cli/backend/src/utils/skill-md-parser.js.map +1 -0
  104. package/frontend/dist/assets/{index-dc92ab64.css → index-6aaa0630.css} +1 -1
  105. package/frontend/dist/assets/{index-76d76633.js → index-9e6d97d1.js} +334 -328
  106. package/frontend/dist/index.html +2 -2
  107. package/package.json +1 -1
  108. package/config/experts/empathetic-resolver/expert.json +0 -11
  109. package/config/experts/empathetic-resolver.md +0 -32
  110. package/config/experts/pragmatic-architect/expert.json +0 -11
  111. package/config/experts/pragmatic-architect.md +0 -32
  112. package/config/experts/viral-alchemist/expert.json +0 -11
  113. package/config/experts/viral-alchemist.md +0 -32
@@ -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":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,OAAO,EASL,uBAAuB,GACxB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EACL,SAAS,EACT,eAAe,EACf,eAAe,EACf,YAAY,EACZ,iBAAiB,GAClB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACL,OAAO,EACP,OAAO,EACP,eAAe,EACf,qBAAqB,GAEtB,MAAM,iCAAiC,CAAC;AAEzC,+EAA+E;AAC/E,YAAY;AACZ,+EAA+E;AAE/E,MAAM,iBAAiB,GAAG,eAAe,CAAC;AAC1C,MAAM,cAAc,GAAuB;IACzC,aAAa,EAAE,CAAC;IAChB,WAAW,EAAE,EAAE;CAChB,CAAC;AA6DF;;GAEG;AACH,MAAM,OAAO,sBAAsB;IAChB,GAAG,CAAS;IACZ,YAAY,CAAS;IACrB,aAAa,CAAS;IAC/B,WAAW,GAAG,KAAK,CAAC;IAE5B,YAAY,OAAgC;QAC1C,IAAI,CAAC,GAAG,GAAG,OAAO,EAAE,GAAG,IAAI,qBAAqB,EAAE,CAAC;QACnD,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;QAC3D,IAAI,CAAC,aAAa;YAChB,OAAO,EAAE,aAAa,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IAChE,CAAC;IAED,qEAAqE;IACrE,qCAAqC;IACrC,qEAAqE;IAErE;;;;OAIG;IACH,KAAK,CAAC,IAAI;QACR,IAAI,IAAI,CAAC,WAAW;YAAE,OAAO;QAC7B,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC1B,MAAM,eAAe,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC1C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;IAC1B,CAAC;IAED,qEAAqE;IACrE,8CAA8C;IAC9C,qEAAqE;IAErE;;OAEG;IACH,KAAK,CAAC,eAAe;QACnB,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QACtC,OAAO,GAAG,CAAC,WAAW,CAAC;IACzB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,aAAa,CAAC,EAAU;QAC5B,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QACtC,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACvD,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,uBAAuB,CAAC,EAAE,CAAC,CAAC;QAClD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,yBAAyB,CAC7B,QAAgB,EAChB,IAAqB;QAErB,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QACtC,OAAO,GAAG,CAAC,WAAW,CAAC,MAAM,CAC3B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAC7D,CAAC;IACJ,CAAC;IAED,qEAAqE;IACrE,OAAO;IACP,qEAAqE;IAErE;;;OAGG;IACH,KAAK,CAAC,SAAS,CACb,KAAqB;QAErB,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,GAAG,EAAE,MAAM,CAAC;QAC5B,MAAM,OAAO,GAAkB,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;QAEvE,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrC,MAAM,KAAK,GAA4B;YACrC,EAAE;YACF,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,SAAS;YACf,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,SAAS,EAAE,GAAG;YACd,SAAS,EAAE,GAAG;YACd,MAAM,EAAE,QAAQ;YAChB,OAAO;SACR,CAAC;QAEF,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC1C,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAE9B,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,QAAQ,CAAC,KAAoB;QACjC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,GAAG,EAAE,MAAM,CAAC;QAE5B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrC,MAAM,KAAK,GAA4B;YACrC,EAAE;YACF,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,cAAc;YACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM;YAC5B,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,YAAY;YACxC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,SAAS;YAClC,SAAS,EAAE,GAAG;YACd,SAAS,EAAE,GAAG;YACd,MAAM,EAAE,QAAQ;YAChB,OAAO;SACR,CAAC;QAEF,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAChD,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAE9B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,qEAAqE;IACrE,UAAU;IACV,qEAAqE;IAErE;;;;;OAKG;IACH,KAAK,CAAC,gBAAgB,CACpB,EAAU,EACV,KAA+B;QAE/B,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,IAAI,OAAiC,CAAC;QACtC,MAAM,iBAAiB,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,IAAI,EAAE;YACpD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;YACtC,MAAM,GAAG,GAAG,GAAG,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;YAC1D,IAAI,GAAG,GAAG,CAAC;gBAAE,MAAM,IAAI,uBAAuB,CAAC,EAAE,CAAC,CAAC;YACnD,OAAO,GAAG;gBACR,GAAG,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC;gBACvB,GAAG,KAAK;gBACR,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACpC,CAAC;YACF,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;YAC/B,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;QACH,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,qEAAqE;IACrE,UAAU;IACV,qEAAqE;IAErE;;;;;;OAMG;IACH,KAAK,CAAC,gBAAgB,CAAC,EAAU;QAC/B,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,IAAI,eAAe,GAAkB,IAAI,CAAC;QAC1C,MAAM,iBAAiB,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,IAAI,EAAE;YACpD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;YACtC,MAAM,GAAG,GAAG,GAAG,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;YAC1D,IAAI,GAAG,GAAG,CAAC;gBAAE,MAAM,IAAI,uBAAuB,CAAC,EAAE,CAAC,CAAC;YACnD,eAAe,GAAG,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;YAC/C,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YAC/B,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;QACH,IAAI,eAAe,EAAE,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC,CAAC;YACrD,CAAC;YAAC,MAAM,CAAC;gBACP,yDAAyD;YAC3D,CAAC;QACH,CAAC;IACH,CAAC;IAED,qEAAqE;IACrE,oDAAoD;IACpD,qEAAqE;IAErE;;;;;;OAMG;IACH,KAAK,CAAC,UAAU,CAAC,EAAU;QACzB,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;QAC3C,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;QACvE,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAC7C,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;QAC/D,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAsB,CAAC;IACpD,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,UAAU,CAAC,EAAU,EAAE,OAA0B;QACrD,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;QAC3C,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAED,qEAAqE;IACrE,aAAa;IACb,qEAAqE;IAE7D,KAAK,CAAC,YAAY;QACxB,OAAO,YAAY,CAAqB,IAAI,CAAC,YAAY,EAAE;YACzD,GAAG,cAAc;YACjB,WAAW,EAAE,EAAE;SAChB,CAAC,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,GAAuB;QACjD,MAAM,eAAe,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;IAChD,CAAC;IAEO,KAAK,CAAC,WAAW,CAAC,KAA8B;QACtD,MAAM,iBAAiB,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,IAAI,EAAE;YACpD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;YACtC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC5B,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,WAAW,CAAC,OAAe;QACjC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACtC,CAAC;IAEO,KAAK,CAAC,YAAY,CACxB,OAAe,EACf,OAA0B;QAE1B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;QACpD,+DAA+D;QAC/D,4CAA4C;QAC5C,MAAM,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC3E,CAAC;IAEO,KAAK;QACX,OAAO,QAAQ,MAAM,CAAC,UAAU,EAAE,EAAE,CAAC;IACvC,CAAC;CACF;AAED,+EAA+E;AAC/E,YAAY;AACZ,+EAA+E;AAE/E,IAAI,QAAQ,GAAkC,IAAI,CAAC;AAEnD;;GAEG;AACH,MAAM,UAAU,yBAAyB;IACvC,IAAI,CAAC,QAAQ;QAAE,QAAQ,GAAG,IAAI,sBAAsB,EAAE,CAAC;IACvD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,2BAA2B;IACzC,QAAQ,GAAG,IAAI,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,6BAA6B,CAC3C,GAA2B;IAE3B,QAAQ,GAAG,GAAG,CAAC;AACjB,CAAC"}
@@ -0,0 +1,117 @@
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
+ /**
23
+ * Minimal fetch-compatible function shape (for test injection).
24
+ */
25
+ export type FetchLike = (url: string, init?: {
26
+ method?: string;
27
+ headers?: Record<string, string>;
28
+ body?: string;
29
+ }) => Promise<{
30
+ ok: boolean;
31
+ status: number;
32
+ text(): Promise<string>;
33
+ json(): Promise<unknown>;
34
+ }>;
35
+ /**
36
+ * Helper configuration — all fields optional; defaults match production.
37
+ */
38
+ export interface GeminiCliHelperConfig {
39
+ /** Path to the extension install directory (default: `~/.gemini/extensions/google-workspace`). */
40
+ extensionPath?: string;
41
+ /** Cloud Function base URL. */
42
+ cloudFunctionUrl?: string;
43
+ /** Path on the cloud function for refresh calls. */
44
+ refreshPath?: string;
45
+ /** Client ID to store on the credential (for later refresh identification). */
46
+ clientId?: string;
47
+ /** Refresh buffer in ms — refresh if token expires within this window. */
48
+ expiryBufferMs?: number;
49
+ /** Fetch implementation (injectable for tests). */
50
+ fetch?: FetchLike;
51
+ }
52
+ /**
53
+ * Credential helper that reads tokens from the gemini-cli-workspace extension
54
+ * and refreshes them via the extension's Cloud Function.
55
+ */
56
+ export declare class GeminiCliWorkspaceHelper implements CredentialHelper {
57
+ readonly name: CredentialHelperName;
58
+ private readonly extensionPath;
59
+ private readonly cloudFunctionUrl;
60
+ private readonly refreshPath;
61
+ private readonly clientId;
62
+ private readonly expiryBufferMs;
63
+ private readonly fetchFn;
64
+ private readonly store;
65
+ /** Per-credential refresh in-flight promises (serializes concurrent refreshes). */
66
+ private readonly refreshInFlight;
67
+ /** Per-credential last refresh attempt timestamp (for cooldown). */
68
+ private readonly lastRefreshAttempt;
69
+ constructor(store: CredentialStoreService, config?: GeminiCliHelperConfig);
70
+ /**
71
+ * Read the extension's current token file and return its contents as a
72
+ * `GoogleOAuthPayload` ready to persist in Crewly's store.
73
+ *
74
+ * The user must have completed extension login with
75
+ * `GEMINI_CLI_WORKSPACE_FORCE_FILE_STORAGE=true` before calling this.
76
+ */
77
+ captureFromFile(): Promise<GoogleOAuthPayload>;
78
+ /**
79
+ * Remove the extension's token file. Call after `captureFromFile()` to
80
+ * prepare for the next account's login (the extension otherwise returns
81
+ * cached credentials if scopes match, skipping the login prompt).
82
+ * Leaves the master.key file untouched so existing ciphertexts remain
83
+ * decryptable if needed.
84
+ */
85
+ clearExtensionFile(): Promise<void>;
86
+ /**
87
+ * Return a valid `GoogleOAuthPayload`, refreshing via the extension's
88
+ * cloud function if the access token is within the expiry buffer.
89
+ *
90
+ * Concurrent calls for the same credential share a single refresh.
91
+ *
92
+ * @throws CredentialRevokedError if the refresh token is no longer valid
93
+ */
94
+ getAccessToken(entry: CredentialRegistryEntry, payload: GoogleOAuthPayload): Promise<GoogleOAuthPayload>;
95
+ private doRefresh;
96
+ /**
97
+ * Decrypt the extension's `{iv_hex}:{authTag_hex}:{ciphertext_hex}`
98
+ * format (AES-256-GCM).
99
+ */
100
+ private decryptExtensionFormat;
101
+ /**
102
+ * Fetch the user's email via Google's userinfo endpoint. Best-effort;
103
+ * returns undefined on failure.
104
+ */
105
+ private fetchUserEmail;
106
+ }
107
+ /**
108
+ * Encrypt a plaintext string with the extension's file format. Used in
109
+ * tests to generate fake token files.
110
+ */
111
+ export declare function encryptExtensionFormatForTesting(plaintext: string, key: Buffer): string;
112
+ /**
113
+ * Derive the extension's encryption key using the real scrypt + salt.
114
+ * Test-only export.
115
+ */
116
+ export declare function deriveExtensionKeyForTesting(masterKey: Buffer): Buffer;
117
+ //# 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;AAmDxE;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,CACtB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KACxE,OAAO,CAAC;IACX,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACxB,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1B,CAAC,CAAC;AAEH;;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;IAoB9B;;;OAGG;YACW,cAAc;CAe7B;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"}