git-workspace-service 0.3.0 → 0.3.2

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/dist/index.cjs CHANGED
@@ -333,15 +333,19 @@ var WorkspaceService = class {
333
333
  userId: config.user?.id,
334
334
  reason: `Workspace for ${config.task.role} in ${config.execution.patternName}`
335
335
  },
336
- userProvided: config.userCredentials
337
- });
338
- await this.emitEvent({
339
- type: "credential:granted",
340
- workspaceId,
341
- credentialId: credential.id,
342
- executionId: config.execution.id,
343
- timestamp: /* @__PURE__ */ new Date()
336
+ userProvided: config.userCredentials,
337
+ // If no userCredentials provided, allow returning null for public repos
338
+ optional: !config.userCredentials
344
339
  });
340
+ if (credential) {
341
+ await this.emitEvent({
342
+ type: "credential:granted",
343
+ workspaceId,
344
+ credentialId: credential.id,
345
+ executionId: config.execution.id,
346
+ timestamp: /* @__PURE__ */ new Date()
347
+ });
348
+ }
345
349
  }
346
350
  const branchInfo = createBranchInfo(
347
351
  {
@@ -357,7 +361,8 @@ var WorkspaceService = class {
357
361
  path: workspacePath,
358
362
  repo: config.repo,
359
363
  branch: branchInfo,
360
- credential,
364
+ credential: credential ?? void 0,
365
+ // Optional for public repos
361
366
  provisionedAt: /* @__PURE__ */ new Date(),
362
367
  status: "provisioning",
363
368
  strategy,
@@ -373,7 +378,17 @@ var WorkspaceService = class {
373
378
  try {
374
379
  if (strategy === "clone") {
375
380
  this.updateProgress(workspace, "cloning", "Cloning repository");
376
- await this.cloneRepo(workspace, credential.token);
381
+ if (!credential) {
382
+ const cloneResult = await this.tryUnauthenticatedClone(workspace);
383
+ if (!cloneResult.success) {
384
+ throw new Error(
385
+ `Repository ${config.repo} requires authentication but no credentials are available. Please provide credentials or configure OAuth.`
386
+ );
387
+ }
388
+ this.log("info", { workspaceId }, "Cloned public repository without authentication");
389
+ } else {
390
+ await this.cloneRepo(workspace, credential.token);
391
+ }
377
392
  this.updateProgress(workspace, "creating_branch", "Creating branch");
378
393
  await this.createBranch(workspace);
379
394
  } else {
@@ -594,7 +609,7 @@ var WorkspaceService = class {
594
609
  });
595
610
  }
596
611
  }
597
- if (workspace.strategy === "clone") {
612
+ if (workspace.strategy === "clone" && workspace.credential) {
598
613
  await this.credentialService.revokeCredential(workspace.credential.id);
599
614
  await this.emitEvent({
600
615
  type: "credential:revoked",
@@ -630,6 +645,36 @@ var WorkspaceService = class {
630
645
  // ─────────────────────────────────────────────────────────────
631
646
  // Private Methods
632
647
  // ─────────────────────────────────────────────────────────────
648
+ /**
649
+ * Try to clone a public repository without authentication
650
+ */
651
+ async tryUnauthenticatedClone(workspace) {
652
+ let cloneUrl = workspace.repo;
653
+ if (cloneUrl.startsWith("git@github.com:")) {
654
+ cloneUrl = cloneUrl.replace("git@github.com:", "https://github.com/");
655
+ }
656
+ if (!cloneUrl.endsWith(".git")) {
657
+ cloneUrl = `${cloneUrl}.git`;
658
+ }
659
+ if (!cloneUrl.startsWith("https://")) {
660
+ cloneUrl = `https://${cloneUrl}`;
661
+ }
662
+ try {
663
+ await this.execInDir(
664
+ workspace.path,
665
+ `git clone --depth 1 --branch ${workspace.branch.baseBranch} ${cloneUrl} .`
666
+ );
667
+ this.log("info", { workspaceId: workspace.id }, "Public repository cloned without authentication");
668
+ return { success: true };
669
+ } catch (error) {
670
+ const errorMessage = error instanceof Error ? error.message : String(error);
671
+ const isAuthError = errorMessage.includes("401") || errorMessage.includes("403") || errorMessage.includes("Authentication failed") || errorMessage.includes("could not read Username") || errorMessage.includes("terminal prompts disabled");
672
+ if (isAuthError) {
673
+ return { success: false, error: "Authentication required" };
674
+ }
675
+ throw error;
676
+ }
677
+ }
633
678
  async cloneRepo(workspace, token) {
634
679
  const cloneUrl = token ? this.buildAuthenticatedUrl(workspace.repo, token) : workspace.repo;
635
680
  await this.execInDir(
@@ -653,11 +698,11 @@ var WorkspaceService = class {
653
698
  async configureGit(workspace) {
654
699
  await this.execInDir(workspace.path, 'git config user.name "Workspace Agent"');
655
700
  await this.execInDir(workspace.path, 'git config user.email "agent@workspace.local"');
656
- if (!workspace.credential.token) {
701
+ if (!workspace.credential || !workspace.credential.token) {
657
702
  this.log(
658
703
  "debug",
659
704
  { workspaceId: workspace.id },
660
- "Using SSH authentication, skipping credential helper"
705
+ workspace.credential ? "Using SSH authentication, skipping credential helper" : "No credentials (public repo), skipping credential helper"
661
706
  );
662
707
  return;
663
708
  }
@@ -683,9 +728,19 @@ var WorkspaceService = class {
683
728
  );
684
729
  }
685
730
  async pushBranch(workspace) {
731
+ if (!workspace.credential) {
732
+ throw new Error(
733
+ "Push requires authentication. This workspace was cloned from a public repository without credentials."
734
+ );
735
+ }
686
736
  await this.execInDir(workspace.path, `git push -u origin ${workspace.branch.name}`);
687
737
  }
688
738
  async createPullRequest(workspace, config) {
739
+ if (!workspace.credential) {
740
+ throw new Error(
741
+ "Pull request creation requires authentication. This workspace was cloned from a public repository without credentials."
742
+ );
743
+ }
689
744
  const repoInfo = this.parseRepo(workspace.repo);
690
745
  if (!repoInfo) {
691
746
  throw new Error(`Invalid repository format: ${workspace.repo}`);
@@ -1284,7 +1339,9 @@ var CredentialService = class {
1284
1339
  return this.providers.get(name);
1285
1340
  }
1286
1341
  /**
1287
- * Request credentials for a repository
1342
+ * Request credentials for a repository.
1343
+ * If request.optional is true, returns null when no credentials available.
1344
+ * Otherwise throws an error.
1288
1345
  */
1289
1346
  async getCredentials(request) {
1290
1347
  const provider = this.detectProvider(request.repo);
@@ -1332,6 +1389,14 @@ var CredentialService = class {
1332
1389
  credential = await this.getOAuthCredentialViaDeviceFlow(provider, request, ttlSeconds);
1333
1390
  }
1334
1391
  if (!credential) {
1392
+ if (request.optional) {
1393
+ this.log(
1394
+ "info",
1395
+ { repo: request.repo },
1396
+ "No credentials available (optional request, returning null)"
1397
+ );
1398
+ return null;
1399
+ }
1335
1400
  throw new Error(
1336
1401
  `No credentials available for repository: ${request.repo}. ` + (this.oauthConfig ? "OAuth device flow failed or was cancelled." : "Configure OAuth to enable interactive authentication.")
1337
1402
  );