git-workspace-service 0.1.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.
- package/README.md +416 -0
- package/dist/index.cjs +2425 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1518 -0
- package/dist/index.d.ts +1518 -0
- package/dist/index.js +2377 -0
- package/dist/index.js.map +1 -0
- package/package.json +82 -0
package/README.md
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# git-workspace-service
|
|
2
|
+
|
|
3
|
+
Git workspace provisioning and credential management service. Handles cloning repositories, managing branches, credentials, and PR creation.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Workspace provisioning** - Clone repos, create branches, configure git
|
|
8
|
+
- **Credential management** - Secure credential handling with TTL and revocation
|
|
9
|
+
- **Multiple providers** - Support for GitHub, GitLab, Bitbucket, Azure DevOps
|
|
10
|
+
- **GitHub App support** - First-class GitHub App authentication
|
|
11
|
+
- **OAuth Device Flow** - Interactive authentication for CLI/agents (RFC 8628)
|
|
12
|
+
- **Token caching** - Encrypted persistent storage for OAuth tokens
|
|
13
|
+
- **User credentials** - Support for PAT, OAuth tokens, and SSH
|
|
14
|
+
- **Fine-grained permissions** - Control what agents can do
|
|
15
|
+
- **Branch naming** - Automatic branch naming with execution context
|
|
16
|
+
- **PR & Issue management** - Create PRs, manage issues, add comments
|
|
17
|
+
- **Event system** - Subscribe to workspace lifecycle events
|
|
18
|
+
- **TypeScript-first** - Full type definitions included
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install git-workspace-service
|
|
24
|
+
# or
|
|
25
|
+
pnpm add git-workspace-service
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
For GitHub App support, also install:
|
|
29
|
+
```bash
|
|
30
|
+
npm install @octokit/rest @octokit/auth-app
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import {
|
|
37
|
+
WorkspaceService,
|
|
38
|
+
CredentialService,
|
|
39
|
+
GitHubProvider,
|
|
40
|
+
} from 'git-workspace-service';
|
|
41
|
+
|
|
42
|
+
// Set up credential service with GitHub provider
|
|
43
|
+
const githubProvider = new GitHubProvider({
|
|
44
|
+
appId: process.env.GITHUB_APP_ID!,
|
|
45
|
+
privateKey: process.env.GITHUB_PRIVATE_KEY!,
|
|
46
|
+
});
|
|
47
|
+
await githubProvider.initialize();
|
|
48
|
+
|
|
49
|
+
const credentialService = new CredentialService({
|
|
50
|
+
defaultTtlSeconds: 3600,
|
|
51
|
+
maxTtlSeconds: 7200,
|
|
52
|
+
});
|
|
53
|
+
credentialService.registerProvider(githubProvider);
|
|
54
|
+
|
|
55
|
+
// Create workspace service
|
|
56
|
+
const workspaceService = new WorkspaceService({
|
|
57
|
+
config: {
|
|
58
|
+
baseDir: '/tmp/workspaces',
|
|
59
|
+
},
|
|
60
|
+
credentialService,
|
|
61
|
+
});
|
|
62
|
+
await workspaceService.initialize();
|
|
63
|
+
|
|
64
|
+
// Provision a workspace
|
|
65
|
+
const workspace = await workspaceService.provision({
|
|
66
|
+
repo: 'https://github.com/owner/repo',
|
|
67
|
+
branchStrategy: 'feature_branch',
|
|
68
|
+
baseBranch: 'main',
|
|
69
|
+
execution: {
|
|
70
|
+
id: 'exec-123',
|
|
71
|
+
patternName: 'code-review',
|
|
72
|
+
},
|
|
73
|
+
task: {
|
|
74
|
+
id: 'task-456',
|
|
75
|
+
role: 'engineer',
|
|
76
|
+
slug: 'auth-feature',
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
console.log(`Workspace created at: ${workspace.path}`);
|
|
81
|
+
console.log(`Branch: ${workspace.branch.name}`);
|
|
82
|
+
|
|
83
|
+
// Do work in the workspace...
|
|
84
|
+
|
|
85
|
+
// Finalize with PR
|
|
86
|
+
const pr = await workspaceService.finalize(workspace.id, {
|
|
87
|
+
push: true,
|
|
88
|
+
createPr: true,
|
|
89
|
+
pr: {
|
|
90
|
+
title: 'Add authentication feature',
|
|
91
|
+
body: 'Implements OAuth login',
|
|
92
|
+
targetBranch: 'main',
|
|
93
|
+
labels: ['enhancement'],
|
|
94
|
+
},
|
|
95
|
+
cleanup: true,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
console.log(`PR created: ${pr?.url}`);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## User-Provided Credentials
|
|
102
|
+
|
|
103
|
+
Instead of using GitHub App, users can provide their own PAT or OAuth token:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
const workspace = await workspaceService.provision({
|
|
107
|
+
repo: 'https://github.com/owner/repo',
|
|
108
|
+
branchStrategy: 'feature_branch',
|
|
109
|
+
baseBranch: 'main',
|
|
110
|
+
execution: {
|
|
111
|
+
id: 'exec-123',
|
|
112
|
+
patternName: 'my-pattern',
|
|
113
|
+
},
|
|
114
|
+
task: {
|
|
115
|
+
id: 'task-456',
|
|
116
|
+
role: 'engineer',
|
|
117
|
+
},
|
|
118
|
+
userCredentials: {
|
|
119
|
+
type: 'pat',
|
|
120
|
+
token: 'ghp_xxxx...',
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Or use SSH authentication (relies on system SSH agent):
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
const workspace = await workspaceService.provision({
|
|
129
|
+
repo: 'git@github.com:owner/repo.git',
|
|
130
|
+
// ...
|
|
131
|
+
userCredentials: {
|
|
132
|
+
type: 'ssh',
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## OAuth Device Flow
|
|
138
|
+
|
|
139
|
+
For CLI applications and AI agents, use the OAuth Device Code Flow for interactive authentication:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import {
|
|
143
|
+
CredentialService,
|
|
144
|
+
OAuthDeviceFlow,
|
|
145
|
+
FileTokenStore,
|
|
146
|
+
DEFAULT_AGENT_PERMISSIONS,
|
|
147
|
+
} from 'git-workspace-service';
|
|
148
|
+
|
|
149
|
+
// Set up token store for persistent caching (with encryption)
|
|
150
|
+
const tokenStore = new FileTokenStore({
|
|
151
|
+
directory: '~/.myapp/tokens',
|
|
152
|
+
encryptionKey: process.env.TOKEN_ENCRYPTION_KEY,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Create credential service with OAuth support
|
|
156
|
+
const credentialService = new CredentialService({
|
|
157
|
+
tokenStore,
|
|
158
|
+
oauth: {
|
|
159
|
+
clientId: process.env.GITHUB_CLIENT_ID!,
|
|
160
|
+
permissions: DEFAULT_AGENT_PERMISSIONS,
|
|
161
|
+
promptEmitter: {
|
|
162
|
+
onAuthRequired(prompt) {
|
|
163
|
+
console.log(`Visit: ${prompt.verificationUri}`);
|
|
164
|
+
console.log(`Enter code: ${prompt.userCode}`);
|
|
165
|
+
},
|
|
166
|
+
onAuthComplete(result) {
|
|
167
|
+
if (result.success) {
|
|
168
|
+
console.log('Authenticated!');
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Request credentials - will use cached token or trigger device flow
|
|
176
|
+
const credential = await credentialService.getCredentials({
|
|
177
|
+
repo: 'https://github.com/owner/repo',
|
|
178
|
+
access: 'write',
|
|
179
|
+
context: {
|
|
180
|
+
executionId: 'exec-123',
|
|
181
|
+
taskId: 'task-456',
|
|
182
|
+
reason: 'Code review',
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Credential Priority
|
|
188
|
+
|
|
189
|
+
When requesting credentials, the service checks sources in this order:
|
|
190
|
+
|
|
191
|
+
1. **User-provided** - PAT, OAuth token, or SSH credentials passed directly
|
|
192
|
+
2. **Cached OAuth** - Valid token from TokenStore (with auto-refresh)
|
|
193
|
+
3. **Provider** - GitHub App or other registered provider
|
|
194
|
+
4. **OAuth Device Flow** - Interactive authentication (if configured)
|
|
195
|
+
|
|
196
|
+
### Fine-Grained Permissions
|
|
197
|
+
|
|
198
|
+
Control what agents can do with `AgentPermissions`:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { DEFAULT_AGENT_PERMISSIONS } from 'git-workspace-service';
|
|
202
|
+
|
|
203
|
+
const permissions = {
|
|
204
|
+
repositories: { type: 'selected', repos: ['owner/repo'] },
|
|
205
|
+
contents: 'write', // 'none' | 'read' | 'write'
|
|
206
|
+
pullRequests: 'write',
|
|
207
|
+
issues: 'read',
|
|
208
|
+
metadata: 'read',
|
|
209
|
+
canDeleteBranch: true,
|
|
210
|
+
canForcePush: false, // Dangerous operations off by default
|
|
211
|
+
canDeleteRepository: false,
|
|
212
|
+
canAdminister: false,
|
|
213
|
+
};
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Event System
|
|
217
|
+
|
|
218
|
+
Subscribe to workspace lifecycle events:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
const unsubscribe = workspaceService.onEvent((event) => {
|
|
222
|
+
switch (event.type) {
|
|
223
|
+
case 'workspace:provisioning':
|
|
224
|
+
console.log('Provisioning workspace...');
|
|
225
|
+
break;
|
|
226
|
+
case 'workspace:ready':
|
|
227
|
+
console.log('Workspace ready!');
|
|
228
|
+
break;
|
|
229
|
+
case 'credential:granted':
|
|
230
|
+
console.log(`Credential ${event.credentialId} granted`);
|
|
231
|
+
break;
|
|
232
|
+
case 'pr:created':
|
|
233
|
+
console.log(`PR created: ${event.data?.prUrl}`);
|
|
234
|
+
break;
|
|
235
|
+
case 'workspace:cleaned_up':
|
|
236
|
+
console.log('Workspace cleaned up');
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Later, unsubscribe
|
|
242
|
+
unsubscribe();
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Branch Naming
|
|
246
|
+
|
|
247
|
+
Automatic branch naming with customizable prefix:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
import { generateBranchName, parseBranchName } from 'git-workspace-service';
|
|
251
|
+
|
|
252
|
+
// Generate branch name
|
|
253
|
+
const branchName = generateBranchName({
|
|
254
|
+
executionId: 'exec-123',
|
|
255
|
+
role: 'engineer',
|
|
256
|
+
slug: 'auth-feature',
|
|
257
|
+
baseBranch: 'main',
|
|
258
|
+
});
|
|
259
|
+
// Result: 'parallax/exec-123/engineer-auth-feature'
|
|
260
|
+
|
|
261
|
+
// Parse branch name
|
|
262
|
+
const parsed = parseBranchName('parallax/exec-123/engineer-auth-feature');
|
|
263
|
+
// Result: { executionId: 'exec-123', role: 'engineer', slug: 'auth-feature' }
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## API Reference
|
|
267
|
+
|
|
268
|
+
### WorkspaceService
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
class WorkspaceService {
|
|
272
|
+
constructor(options: WorkspaceServiceOptions);
|
|
273
|
+
|
|
274
|
+
// Initialize the service
|
|
275
|
+
initialize(): Promise<void>;
|
|
276
|
+
|
|
277
|
+
// Provision a new workspace
|
|
278
|
+
provision(config: WorkspaceConfig): Promise<Workspace>;
|
|
279
|
+
|
|
280
|
+
// Finalize workspace (push, create PR, cleanup)
|
|
281
|
+
finalize(workspaceId: string, options: WorkspaceFinalization): Promise<PullRequestInfo | void>;
|
|
282
|
+
|
|
283
|
+
// Get workspace by ID
|
|
284
|
+
get(workspaceId: string): Workspace | null;
|
|
285
|
+
|
|
286
|
+
// Get all workspaces for an execution
|
|
287
|
+
getForExecution(executionId: string): Workspace[];
|
|
288
|
+
|
|
289
|
+
// Clean up a workspace
|
|
290
|
+
cleanup(workspaceId: string): Promise<void>;
|
|
291
|
+
|
|
292
|
+
// Clean up all workspaces for an execution
|
|
293
|
+
cleanupForExecution(executionId: string): Promise<void>;
|
|
294
|
+
|
|
295
|
+
// Subscribe to events
|
|
296
|
+
onEvent(handler: WorkspaceEventHandler): () => void;
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### CredentialService
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
class CredentialService {
|
|
304
|
+
constructor(options?: CredentialServiceOptions);
|
|
305
|
+
|
|
306
|
+
// Register a provider adapter
|
|
307
|
+
registerProvider(provider: GitProviderAdapter): void;
|
|
308
|
+
|
|
309
|
+
// Get credentials for a repository
|
|
310
|
+
getCredentials(request: GitCredentialRequest): Promise<GitCredential>;
|
|
311
|
+
|
|
312
|
+
// Revoke a credential
|
|
313
|
+
revokeCredential(grantId: string): Promise<void>;
|
|
314
|
+
|
|
315
|
+
// Revoke all credentials for an execution
|
|
316
|
+
revokeForExecution(executionId: string): Promise<number>;
|
|
317
|
+
|
|
318
|
+
// Check if a credential is valid
|
|
319
|
+
isValid(grantId: string): boolean;
|
|
320
|
+
|
|
321
|
+
// Get grant info
|
|
322
|
+
getGrant(grantId: string): Promise<CredentialGrant | null>;
|
|
323
|
+
|
|
324
|
+
// Get all grants for an execution
|
|
325
|
+
getGrantsForExecution(executionId: string): Promise<CredentialGrant[]>;
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### GitHubProvider
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
class GitHubProvider implements GitProviderAdapter {
|
|
333
|
+
constructor(config: GitHubProviderConfig, logger?: GitHubProviderLogger);
|
|
334
|
+
|
|
335
|
+
// Initialize and fetch installations
|
|
336
|
+
initialize(): Promise<void>;
|
|
337
|
+
|
|
338
|
+
// Register an installation
|
|
339
|
+
registerInstallation(installationId: number): Promise<GitHubAppInstallation>;
|
|
340
|
+
|
|
341
|
+
// Get credentials for a repo
|
|
342
|
+
getCredentialsForRepo(owner: string, repo: string, access: 'read' | 'write', ttlSeconds?: number): Promise<GitCredential>;
|
|
343
|
+
|
|
344
|
+
// Create a PR
|
|
345
|
+
createPullRequestForRepo(owner: string, repo: string, options: { title, body, head, base, draft?, labels?, reviewers? }): Promise<PullRequestInfo>;
|
|
346
|
+
|
|
347
|
+
// Delete a branch
|
|
348
|
+
deleteBranch(owner: string, repo: string, branch: string): Promise<void>;
|
|
349
|
+
|
|
350
|
+
// List managed branches
|
|
351
|
+
listManagedBranches(owner: string, repo: string, prefix?: string): Promise<string[]>;
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### OAuthDeviceFlow
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
class OAuthDeviceFlow {
|
|
359
|
+
constructor(config: OAuthDeviceFlowConfig, logger?: OAuthDeviceFlowLogger);
|
|
360
|
+
|
|
361
|
+
// Start device flow and wait for authorization
|
|
362
|
+
authorize(): Promise<OAuthToken>;
|
|
363
|
+
|
|
364
|
+
// Request a device code (step 1)
|
|
365
|
+
requestDeviceCode(): Promise<DeviceCodeResponse>;
|
|
366
|
+
|
|
367
|
+
// Poll for token (step 2)
|
|
368
|
+
pollForToken(deviceCode: DeviceCodeResponse): Promise<OAuthToken>;
|
|
369
|
+
|
|
370
|
+
// Refresh an expired token
|
|
371
|
+
refreshToken(refreshToken: string): Promise<OAuthToken>;
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### TokenStore
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
// Abstract base class
|
|
379
|
+
abstract class TokenStore {
|
|
380
|
+
save(provider: GitProvider, token: OAuthToken): Promise<void>;
|
|
381
|
+
get(provider: GitProvider): Promise<OAuthToken | null>;
|
|
382
|
+
clear(provider?: GitProvider): Promise<void>;
|
|
383
|
+
list(): Promise<GitProvider[]>;
|
|
384
|
+
isExpired(token: OAuthToken): boolean;
|
|
385
|
+
needsRefresh(token: OAuthToken): boolean;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// In-memory store (for testing)
|
|
389
|
+
class MemoryTokenStore extends TokenStore { }
|
|
390
|
+
|
|
391
|
+
// File-based store with encryption
|
|
392
|
+
class FileTokenStore extends TokenStore {
|
|
393
|
+
constructor(options?: {
|
|
394
|
+
directory?: string; // Default: ~/.parallax/tokens
|
|
395
|
+
encryptionKey?: string; // AES-256-CBC encryption
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Event Types
|
|
401
|
+
|
|
402
|
+
| Event | Description |
|
|
403
|
+
|-------|-------------|
|
|
404
|
+
| `workspace:provisioning` | Workspace provisioning started |
|
|
405
|
+
| `workspace:ready` | Workspace is ready for use |
|
|
406
|
+
| `workspace:error` | Workspace provisioning failed |
|
|
407
|
+
| `workspace:finalizing` | Workspace finalization started |
|
|
408
|
+
| `workspace:cleaned_up` | Workspace has been cleaned up |
|
|
409
|
+
| `credential:granted` | Credential was granted |
|
|
410
|
+
| `credential:revoked` | Credential was revoked |
|
|
411
|
+
| `pr:created` | Pull request was created |
|
|
412
|
+
| `pr:merged` | Pull request was merged |
|
|
413
|
+
|
|
414
|
+
## License
|
|
415
|
+
|
|
416
|
+
MIT
|