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 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