@serviceme/devtools-core 0.2.0 → 0.3.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/dist/index.mjs CHANGED
@@ -842,6 +842,11 @@ var DEFAULT_DEVICE_CODE_URL = "https://github.com/login/device/code";
842
842
  var DEFAULT_TOKEN_URL = "https://github.com/login/oauth/access_token";
843
843
  var DEFAULT_USER_URL = "https://api.github.com/user";
844
844
  var DEFAULT_SCOPE = "read:user user:email";
845
+ var DEVICE_CODE_MAX_ATTEMPTS = 3;
846
+ var DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS = 500;
847
+ function isTransientNetworkError(error) {
848
+ return error instanceof TypeError;
849
+ }
845
850
  var GitHubAuthProvider = class {
846
851
  constructor(config) {
847
852
  this.providerId = "github";
@@ -857,7 +862,8 @@ var GitHubAuthProvider = class {
857
862
  minPollIntervalMs: config.minPollIntervalMs ?? 1e3,
858
863
  maxPollIntervalMs: config.maxPollIntervalMs ?? 15e3,
859
864
  maxWaitMs: config.maxWaitMs,
860
- fetchImpl: config.fetchImpl ?? fetch
865
+ fetchImpl: config.fetchImpl ?? fetch,
866
+ deviceCodeRetryBaseDelayMs: config.deviceCodeRetryBaseDelayMs ?? DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS
861
867
  };
862
868
  }
863
869
  async requestDeviceFlow(opts) {
@@ -865,29 +871,43 @@ var GitHubAuthProvider = class {
865
871
  client_id: this.cfg.clientId,
866
872
  scope: opts?.scope ?? this.cfg.scope
867
873
  });
868
- const resp = await this.cfg.fetchImpl(this.cfg.deviceCodeUrl, {
869
- method: "POST",
870
- headers: {
871
- Accept: "application/json",
872
- "Content-Type": "application/json",
873
- "User-Agent": "serviceme-core"
874
- },
875
- body
876
- });
877
- if (!resp.ok) {
878
- throw new Error(`GitHub device-code request failed: HTTP ${resp.status}`);
879
- }
880
- const data = await resp.json();
881
- if (!data.device_code || !data.user_code || !data.verification_uri) {
882
- throw new Error("GitHub device-code response missing required fields");
874
+ let lastNetworkError;
875
+ for (let attempt = 1; attempt <= DEVICE_CODE_MAX_ATTEMPTS; attempt++) {
876
+ let resp;
877
+ try {
878
+ resp = await this.cfg.fetchImpl(this.cfg.deviceCodeUrl, {
879
+ method: "POST",
880
+ headers: {
881
+ Accept: "application/json",
882
+ "Content-Type": "application/json",
883
+ "User-Agent": "serviceme-core"
884
+ },
885
+ body
886
+ });
887
+ } catch (error) {
888
+ if (!isTransientNetworkError(error) || attempt === DEVICE_CODE_MAX_ATTEMPTS) {
889
+ throw error;
890
+ }
891
+ lastNetworkError = error;
892
+ await sleep(this.cfg.deviceCodeRetryBaseDelayMs * 2 ** (attempt - 1));
893
+ continue;
894
+ }
895
+ if (!resp.ok) {
896
+ throw new Error(`GitHub device-code request failed: HTTP ${resp.status}`);
897
+ }
898
+ const data = await resp.json();
899
+ if (!data.device_code || !data.user_code || !data.verification_uri) {
900
+ throw new Error("GitHub device-code response missing required fields");
901
+ }
902
+ return {
903
+ provider: this.providerId,
904
+ userCode: data.user_code,
905
+ verificationUrl: data.verification_uri,
906
+ expiresAt: Date.now() + data.expires_in * 1e3,
907
+ message: `Open ${data.verification_uri} and enter ${data.user_code}`
908
+ };
883
909
  }
884
- return {
885
- provider: this.providerId,
886
- userCode: data.user_code,
887
- verificationUrl: data.verification_uri,
888
- expiresAt: Date.now() + data.expires_in * 1e3,
889
- message: `Open ${data.verification_uri} and enter ${data.user_code}`
890
- };
910
+ throw lastNetworkError ?? new Error("GitHub device-code request failed");
891
911
  }
892
912
  async completeDeviceFlow(deviceCode, shouldContinue) {
893
913
  const start = Date.now();
@@ -905,19 +925,28 @@ var GitHubAuthProvider = class {
905
925
  if (shouldContinue && !shouldContinue()) {
906
926
  throw new Error("GitHub device flow cancelled by caller");
907
927
  }
908
- const resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {
909
- method: "POST",
910
- headers: {
911
- Accept: "application/json",
912
- "Content-Type": "application/json",
913
- "User-Agent": "serviceme-core"
914
- },
915
- body: JSON.stringify({
916
- client_id: this.cfg.clientId,
917
- device_code: deviceCode,
918
- grant_type: "urn:ietf:params:oauth:grant-type:device_code"
919
- })
920
- });
928
+ let resp;
929
+ try {
930
+ resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {
931
+ method: "POST",
932
+ headers: {
933
+ Accept: "application/json",
934
+ "Content-Type": "application/json",
935
+ "User-Agent": "serviceme-core"
936
+ },
937
+ body: JSON.stringify({
938
+ client_id: this.cfg.clientId,
939
+ device_code: deviceCode,
940
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
941
+ })
942
+ });
943
+ } catch (error) {
944
+ if (!isTransientNetworkError(error)) {
945
+ throw error;
946
+ }
947
+ pollIntervalMs = Math.min(Math.round(pollIntervalMs * 1.5), this.cfg.maxPollIntervalMs);
948
+ continue;
949
+ }
921
950
  if (!resp.ok) {
922
951
  pollIntervalMs = Math.min(Math.round(pollIntervalMs * 1.5), this.cfg.maxPollIntervalMs);
923
952
  continue;
@@ -2098,14 +2127,7 @@ function extractFrontmatter(raw) {
2098
2127
  }
2099
2128
  return { data, body };
2100
2129
  }
2101
- var SKIP_DIRS = /* @__PURE__ */ new Set([
2102
- ".git",
2103
- "node_modules",
2104
- ".vscode",
2105
- "dist",
2106
- "build",
2107
- "out"
2108
- ]);
2130
+ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".vscode", "dist", "build", "out"]);
2109
2131
  var SKIP_TOP_LEVEL_SCAN_DIRS = /* @__PURE__ */ new Set([...SKIP_DIRS, "plugins"]);
2110
2132
 
2111
2133
  // src/drafts/index.ts
@@ -2525,54 +2547,82 @@ var GitClient = class {
2525
2547
  * Rewrite an upstream URL to its proxy form.
2526
2548
  *
2527
2549
  * Examples:
2528
- * rewriteRemoteUrl("medalsoftchina-ms-skills",
2550
+ * rewriteRemoteUrl("repo-aHR0cHM6Ly9naXRodWIuY29tL21lZGFsc29mdGNoaW5hL21zLXNraWxscy5naXQ",
2529
2551
  * "https://github.com/medalsoftchina/ms-skills.git")
2530
- * → "http://localhost:3000/git-proxy/medalsoftchina-ms-skills"
2552
+ * → "http://localhost:3000/git-proxy/repo-aHR0cHM6Ly9naXRodWIuY29tL21lZGFsc29mdGNoaW5hL21zLXNraWxscy5naXQ"
2531
2553
  *
2532
- * rewriteRemoteUrl("my-team-internal",
2554
+ * rewriteRemoteUrl("repo-aHR0cHM6Ly9naXRodWIuY29tL3RlYW0vaW50ZXJuYWwuZ2l0",
2533
2555
  * "git@github.com:medalsoftchina/ms-skills.git")
2534
- * → "http://localhost:3000/git-proxy/my-team-internal"
2556
+ * → "http://localhost:3000/git-proxy/repo-aHR0cHM6Ly9naXRodWIuY29tL3RlYW0vaW50ZXJuYWwuZ2l0"
2535
2557
  *
2536
2558
  * The original URL's host/owner is intentionally DROPPED — the proxy
2537
2559
  * knows the upstream from the per-repo config, not from the URL. This
2538
- * means callers can't accidentally route a user-repo URL through the
2539
- * default-repo proxy slot.
2560
+ * means callers can route both default and user repos through the same
2561
+ * dynamic proxy-id scheme.
2540
2562
  */
2541
- rewriteRemoteUrl(_repoId, _originalUrl) {
2563
+ rewriteRemoteUrl(_repoId, originalUrl, useProxy = true) {
2564
+ if (!useProxy) {
2565
+ return originalUrl;
2566
+ }
2542
2567
  return `${this.serverProxyBase}/${_repoId}`;
2543
2568
  }
2544
2569
  /**
2545
2570
  * `git clone <proxyUrl> <localPath>` — initialize a new local repo
2546
2571
  * from the proxy. Returns when the clone succeeds; throws on failure.
2572
+ *
2573
+ * When `branch` is provided, the clone is restricted to that branch
2574
+ * via `git clone -b <branch> -- <proxyUrl> <localPath>` so the local
2575
+ * checkout lands on the configured `repo.branch` instead of the
2576
+ * upstream default branch. The parameter is optional and backward
2577
+ * compatible — omitting it keeps the original behaviour.
2547
2578
  */
2548
- async clone(repoId, originalUrl, localPath) {
2549
- const proxyUrl = this.rewriteRemoteUrl(repoId, originalUrl);
2550
- const args = ["clone", "--", proxyUrl, localPath];
2579
+ async clone(repoId, originalUrl, localPath, branch, useProxy = true) {
2580
+ const proxyUrl = this.rewriteRemoteUrl(repoId, originalUrl, useProxy);
2581
+ const args = branch ? ["clone", "-b", branch, "--", proxyUrl, localPath] : ["clone", "--", proxyUrl, localPath];
2551
2582
  await this.runOrThrow(args, { cwd: process.cwd() });
2552
2583
  }
2553
2584
  /**
2554
2585
  * `git fetch <remote> <branch>` + return the resulting commit SHA.
2555
2586
  * Operates on an already-cloned repo at `localPath`.
2587
+ *
2588
+ * When `branch` is provided, the pull target is forced to that branch:
2589
+ * if the local working tree is on a different branch, `git checkout
2590
+ * <branch>` is issued first (git auto-tracks `origin/<branch>`), so
2591
+ * the sync leaves the repo parked on the configured `repo.branch`.
2592
+ * When `branch` is omitted the legacy behaviour is preserved — the
2593
+ * current local branch is fast-forwarded. The parameter is optional
2594
+ * and backward compatible.
2556
2595
  */
2557
- async pull(repoId, localPath) {
2596
+ async pull(repoId, localPath, branch, useProxy = true, originalUrl) {
2558
2597
  const remoteUrl = await this.getRemoteUrl(localPath, "origin");
2559
2598
  if (!remoteUrl) {
2560
2599
  throw new Error(`no 'origin' remote configured at ${localPath}`);
2561
2600
  }
2562
- const proxyUrl = this.rewriteRemoteUrl(repoId, remoteUrl);
2601
+ const targetUrl = useProxy ? remoteUrl : originalUrl ?? remoteUrl;
2602
+ const proxyUrl = this.rewriteRemoteUrl(repoId, targetUrl, useProxy);
2563
2603
  await this.runOrThrow(["remote", "set-url", "origin", proxyUrl], { cwd: localPath });
2564
2604
  const before = await this.safeRevParse(localPath);
2565
2605
  await this.runOrThrow(["fetch", "origin"], { cwd: localPath });
2566
2606
  const after = await this.safeRevParse(localPath);
2567
- const branch = await this.currentBranch(localPath);
2568
- if (!branch) {
2607
+ let targetBranch = branch;
2608
+ if (!targetBranch) {
2609
+ targetBranch = await this.currentBranch(localPath);
2610
+ } else {
2611
+ const current = await this.currentBranch(localPath);
2612
+ if (current !== targetBranch) {
2613
+ await this.runOrThrow(["checkout", targetBranch], { cwd: localPath });
2614
+ }
2615
+ }
2616
+ if (!targetBranch) {
2569
2617
  throw new Error(`could not determine current branch at ${localPath}`);
2570
2618
  }
2571
- await this.runOrThrow(["merge", "--ff-only", `origin/${branch}`], { cwd: localPath });
2619
+ await this.runOrThrow(["merge", "--ff-only", `origin/${targetBranch}`], {
2620
+ cwd: localPath
2621
+ });
2572
2622
  return {
2573
2623
  updated: before !== after,
2574
2624
  commitSha: after,
2575
- branch
2625
+ branch: targetBranch
2576
2626
  };
2577
2627
  }
2578
2628
  /**
@@ -2580,12 +2630,12 @@ var GitClient = class {
2580
2630
  * for committing locally first. The server's GitProxy injects the
2581
2631
  * PAT on the way upstream.
2582
2632
  */
2583
- async push(repoId, localPath, branch) {
2633
+ async push(repoId, localPath, branch, useProxy = true) {
2584
2634
  const remoteUrl = await this.getRemoteUrl(localPath, "origin");
2585
2635
  if (!remoteUrl) {
2586
2636
  throw new Error(`no 'origin' remote configured at ${localPath}`);
2587
2637
  }
2588
- const proxyUrl = this.rewriteRemoteUrl(repoId, remoteUrl);
2638
+ const proxyUrl = this.rewriteRemoteUrl(repoId, remoteUrl, useProxy);
2589
2639
  await this.runOrThrow(["remote", "set-url", "origin", proxyUrl], { cwd: localPath });
2590
2640
  const result = await this.spawner.spawn(
2591
2641
  ["push", "origin", `refs/heads/${branch}:refs/heads/${branch}`],
@@ -2605,8 +2655,8 @@ var GitClient = class {
2605
2655
  * `git ls-remote <proxyUrl>` — list advertised branches without
2606
2656
  * cloning. Used by addUserRepo to detect the default branch.
2607
2657
  */
2608
- async lsRemote(repoId, originalUrl) {
2609
- const proxyUrl = this.rewriteRemoteUrl(repoId, originalUrl);
2658
+ async lsRemote(repoId, originalUrl, useProxy = true) {
2659
+ const proxyUrl = this.rewriteRemoteUrl(repoId, originalUrl, useProxy);
2610
2660
  const result = await this.spawner.spawn(["ls-remote", "--heads", "--", proxyUrl], {
2611
2661
  cwd: process.cwd()
2612
2662
  });
@@ -3372,14 +3422,36 @@ function isUserRepo(repo) {
3372
3422
 
3373
3423
  // src/repo-manager/index.ts
3374
3424
  function idFromUrl(url) {
3375
- const trimmed = url.trim().replace(/\.git$/, "");
3376
- const last = trimmed.split("/").pop() ?? "";
3377
- const safe = last.replace(/[^a-zA-Z0-9_-]/g, "-").toLowerCase();
3425
+ const trimmed = url.trim().replace(/\/+$/, "").replace(/\.git$/, "");
3426
+ const sanitize = (value) => value.trim().toLowerCase().replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
3427
+ let owner = "";
3428
+ let repo = "";
3429
+ if (/^https?:\/\//i.test(trimmed)) {
3430
+ const parsed = new URL(trimmed);
3431
+ const segments = parsed.pathname.split("/").filter(Boolean);
3432
+ repo = segments.at(-1) ?? "";
3433
+ owner = segments.length >= 2 ? segments.at(-2) ?? "" : "";
3434
+ } else if (trimmed.startsWith("git@")) {
3435
+ const colon = trimmed.indexOf(":");
3436
+ const repoPath = colon >= 0 ? trimmed.slice(colon + 1) : "";
3437
+ const segments = repoPath.split("/").filter(Boolean);
3438
+ repo = segments.at(-1) ?? "";
3439
+ owner = segments.length >= 2 ? segments.at(-2) ?? "" : "";
3440
+ } else {
3441
+ repo = trimmed.split("/").pop() ?? "";
3442
+ }
3443
+ const safeRepo = sanitize(repo);
3444
+ const safeOwner = sanitize(owner);
3445
+ const safe = safeOwner ? `${safeOwner}-${safeRepo}` : safeRepo;
3378
3446
  if (!SAFE_REPO_ID_PATTERN.test(safe)) {
3379
3447
  throw new InvalidRepoUrlError(`cannot derive a valid repo id from url: ${url}`);
3380
3448
  }
3381
3449
  return safe;
3382
3450
  }
3451
+ function proxyRepoId(url) {
3452
+ const encoded = Buffer.from(url.trim(), "utf8").toString("base64url");
3453
+ return `repo-${encoded}`;
3454
+ }
3383
3455
  var RepoManagerError = class extends Error {
3384
3456
  constructor(message) {
3385
3457
  super(message);
@@ -3408,7 +3480,7 @@ var RepoManager = class {
3408
3480
  * but do NOT abort the loop — the UI surfaces per-repo state and the
3409
3481
  * user can retry.
3410
3482
  *
3411
- * Per spec §3.1: 4 default repos; the loop honors `enabled=false` and
3483
+ * Per spec §3.1: iterate all default repos; the loop honors `enabled=false` and
3412
3484
  * disables (rather than removes) repos the user has turned off.
3413
3485
  */
3414
3486
  async ensureDefaults() {
@@ -3418,11 +3490,14 @@ var RepoManager = class {
3418
3490
  if (!repo.enabled) continue;
3419
3491
  const localPath = getRepoDir(repo.id);
3420
3492
  const exists = await this.pathExists(localPath);
3421
- if (exists) continue;
3493
+ if (exists) {
3494
+ if (await this.isValidGitRepo(localPath)) continue;
3495
+ await fs8.rm(localPath, { recursive: true, force: true });
3496
+ }
3422
3497
  try {
3423
3498
  if (!this.skipClone) {
3424
3499
  await fs8.mkdir(path11.dirname(localPath), { recursive: true });
3425
- await this.git.clone(repo.id, repo.url, localPath);
3500
+ await this.git.clone(repo.id, repo.url, localPath, repo.branch, true);
3426
3501
  }
3427
3502
  await this.store.updateRepo(repo.id, {
3428
3503
  lastSyncStatus: "ok",
@@ -3451,12 +3526,17 @@ var RepoManager = class {
3451
3526
  async pullOne(repoId) {
3452
3527
  const repo = this.store.get(repoId);
3453
3528
  if (!repo) throw new RepoNotInStoreError(repoId);
3529
+ const useProxy = repo.useProxy ?? true;
3530
+ const proxyId = useProxy ? proxyRepoId(repo.url) : repo.id;
3454
3531
  const localPath = getRepoDir(repoId);
3455
3532
  const exists = await this.pathExists(localPath);
3456
- if (!exists) {
3533
+ if (exists && !await this.isValidGitRepo(localPath)) {
3534
+ await fs8.rm(localPath, { recursive: true, force: true });
3535
+ }
3536
+ if (!exists || !await this.pathExists(localPath)) {
3457
3537
  await fs8.mkdir(path11.dirname(localPath), { recursive: true });
3458
3538
  if (!this.skipClone) {
3459
- await this.git.clone(repoId, repo.url, localPath);
3539
+ await this.git.clone(proxyId, repo.url, localPath, repo.branch, useProxy);
3460
3540
  }
3461
3541
  const result = {
3462
3542
  updated: true,
@@ -3470,7 +3550,7 @@ var RepoManager = class {
3470
3550
  return result;
3471
3551
  }
3472
3552
  try {
3473
- const result = await this.git.pull(repoId, localPath);
3553
+ const result = await this.git.pull(proxyId, localPath, repo.branch, useProxy, repo.url);
3474
3554
  await this.store.updateRepo(repoId, {
3475
3555
  lastSyncStatus: "ok",
3476
3556
  lastSyncAt: this.now(),
@@ -3522,10 +3602,12 @@ var RepoManager = class {
3522
3602
  throw new InvalidRepoUrlError(`expected https:// or git@ URL, got: ${url}`);
3523
3603
  }
3524
3604
  const id = idFromUrl(url);
3605
+ const useProxy = input.useProxy ?? true;
3606
+ const userProxyId = useProxy ? proxyRepoId(url) : id;
3525
3607
  assertSafeRepoId(id);
3526
3608
  let branch = input.branch?.trim();
3527
3609
  if (!branch) {
3528
- const branches = await this.git.lsRemote(id, url);
3610
+ const branches = await this.git.lsRemote(userProxyId, url, useProxy);
3529
3611
  const head = branches.find((b) => b.ref === "refs/heads/main") ?? branches.find((b) => b.ref === "refs/heads/master") ?? branches.find((b) => b.ref.endsWith("/v2"));
3530
3612
  if (!head) {
3531
3613
  throw new InvalidRepoUrlError(
@@ -3543,6 +3625,7 @@ var RepoManager = class {
3543
3625
  url,
3544
3626
  branch,
3545
3627
  enabled: true,
3628
+ useProxy,
3546
3629
  writeEnabled: false,
3547
3630
  source: "user",
3548
3631
  addedAt: this.now()
@@ -3552,7 +3635,7 @@ var RepoManager = class {
3552
3635
  if (!this.skipClone) {
3553
3636
  const localPath = getRepoDir(id);
3554
3637
  await fs8.mkdir(path11.dirname(localPath), { recursive: true });
3555
- await this.git.clone(id, url, localPath);
3638
+ await this.git.clone(userProxyId, url, localPath, branch, useProxy);
3556
3639
  cloned = true;
3557
3640
  }
3558
3641
  return { repo, branch, cloned };
@@ -3601,6 +3684,14 @@ var RepoManager = class {
3601
3684
  async ensureHome() {
3602
3685
  await fs8.mkdir(getServicemeHome(), { recursive: true });
3603
3686
  }
3687
+ /**
3688
+ * Returns `true` when `p` contains a `.git` entry — i.e. it is an
3689
+ * initialised git repository and safe to `pull`. Empty directories
3690
+ * (e.g. from an interrupted clone) return `false`.
3691
+ */
3692
+ async isValidGitRepo(p) {
3693
+ return this.pathExists(path11.join(p, ".git"));
3694
+ }
3604
3695
  async pathExists(p) {
3605
3696
  try {
3606
3697
  await fs8.stat(p);
@@ -3617,10 +3708,11 @@ var FIXED_ADDED_AT = "2026-06-29T00:00:00.000Z";
3617
3708
  var DEFAULT_REPO_SEEDS = [
3618
3709
  {
3619
3710
  id: "medalsoftchina-ms-skills",
3620
- name: "MS DevTools Skills (\u7CBE\u9009)",
3711
+ name: "SERVICEME (\u7CBE\u9009)",
3621
3712
  url: "https://github.com/medalsoftchina/ms-skills.git",
3622
3713
  branch: "v2",
3623
3714
  enabled: true,
3715
+ useProxy: true,
3624
3716
  writeEnabled: true,
3625
3717
  source: "default",
3626
3718
  description: "Medalsoft \u7CBE\u9009 skill/agent \u4ED3\u5E93\uFF0C\u53EF\u8BFB\u53EF\u5199"
@@ -3631,6 +3723,7 @@ var DEFAULT_REPO_SEEDS = [
3631
3723
  url: "https://github.com/anthropics/skills.git",
3632
3724
  branch: "main",
3633
3725
  enabled: true,
3726
+ useProxy: true,
3634
3727
  writeEnabled: false,
3635
3728
  source: "default",
3636
3729
  description: "Anthropic \u5B98\u65B9 skills \u793A\u4F8B"
@@ -3641,16 +3734,29 @@ var DEFAULT_REPO_SEEDS = [
3641
3734
  url: "https://github.com/github/awesome-copilot.git",
3642
3735
  branch: "main",
3643
3736
  enabled: true,
3737
+ useProxy: true,
3644
3738
  writeEnabled: false,
3645
3739
  source: "default",
3646
3740
  description: "GitHub Copilot \u793E\u533A\u7CBE\u9009 prompts/agents"
3647
3741
  },
3742
+ {
3743
+ id: "mattpocock-skills",
3744
+ name: "Matt Pocock Skills",
3745
+ url: "https://github.com/mattpocock/skills.git",
3746
+ branch: "main",
3747
+ enabled: true,
3748
+ useProxy: true,
3749
+ writeEnabled: false,
3750
+ source: "default",
3751
+ description: "Matt Pocock \u793E\u533A skills \u4ED3\u5E93"
3752
+ },
3648
3753
  {
3649
3754
  id: "composiohq-awesome-claude-skills",
3650
3755
  name: "Composio Awesome Claude Skills",
3651
3756
  url: "https://github.com/ComposioHQ/awesome-claude-skills.git",
3652
3757
  branch: "main",
3653
3758
  enabled: true,
3759
+ useProxy: true,
3654
3760
  writeEnabled: false,
3655
3761
  source: "default",
3656
3762
  description: "Composio \u7EF4\u62A4\u7684 Claude skills \u96C6\u5408"
@@ -3727,6 +3833,7 @@ var baseRepoFields = {
3727
3833
  url: z.string().url(),
3728
3834
  branch: z.string().min(1).max(200),
3729
3835
  enabled: z.boolean(),
3836
+ useProxy: z.boolean().optional().default(true),
3730
3837
  writeEnabled: z.boolean(),
3731
3838
  addedAt: isoTimestampSchema,
3732
3839
  lastSyncAt: isoTimestampSchema.optional(),
@@ -5779,7 +5886,10 @@ function readV1Config(v1Path) {
5779
5886
  try {
5780
5887
  raw = fs19.readFileSync(v1Path, "utf-8");
5781
5888
  } catch (err) {
5782
- return { ok: false, error: `read failed: ${err instanceof Error ? err.message : String(err)}` };
5889
+ return {
5890
+ ok: false,
5891
+ error: `read failed: ${err instanceof Error ? err.message : String(err)}`
5892
+ };
5783
5893
  }
5784
5894
  let parsed;
5785
5895
  try {
@@ -5834,79 +5944,77 @@ function disambiguateName(task, existingNames, workspaceName) {
5834
5944
  existingNames.add(candidate);
5835
5945
  return { ...task, name: candidate };
5836
5946
  }
5837
- var MigrateToGlobal = class {
5838
- /**
5839
- * Run the migration. Idempotent: each v1 file is deleted after processing,
5840
- * so re-running this method on a partially-migrated state is safe (the
5841
- * already-migrated files no longer exist; only the remainder is processed).
5842
- */
5843
- static async run(options) {
5844
- const globalConfigPath = options.globalConfigPath ?? getScheduledTasksConfigPath();
5845
- const migrationFailuresPath = options.migrationFailuresPath ?? getMigrationFailuresPath();
5846
- const probe = options.probe ?? defaultProbe;
5847
- const existing = readJsonFile(globalConfigPath);
5848
- const baseConfig = existing && existing.version === 2 ? existing : { version: 2, tasks: [] };
5849
- const existingNames = new Set(baseConfig.tasks.map((t) => t.name));
5850
- const priorFailures = readJsonFile(migrationFailuresPath) ?? [];
5851
- const failures = [...priorFailures];
5852
- let migrated = 0;
5853
- const conflicts = [];
5854
- const issues = [];
5855
- for (const workspacePath of options.workspacePaths) {
5856
- const v1Path = path20.join(workspacePath, WORKSPACE_DIR, V1_FILENAME);
5857
- if (!fs19.existsSync(v1Path)) continue;
5858
- const v1 = readV1Config(v1Path);
5859
- if (!v1.ok) {
5860
- failures.push({
5861
- workspacePath,
5862
- v1Path,
5863
- error: v1.error,
5864
- at: (/* @__PURE__ */ new Date()).toISOString()
5865
- });
5866
- safeDelete(v1Path);
5867
- continue;
5868
- }
5869
- let workspaceRef;
5870
- try {
5871
- workspaceRef = await probe(workspacePath);
5872
- } catch (err) {
5873
- failures.push({
5874
- workspacePath,
5875
- v1Path,
5876
- error: `probe failed: ${err instanceof Error ? err.message : String(err)}`,
5877
- at: (/* @__PURE__ */ new Date()).toISOString()
5878
- });
5879
- safeDelete(v1Path);
5880
- continue;
5881
- }
5882
- const { config, issues: taskIssues } = migrateV1ToV22(v1.config, workspaceRef);
5883
- issues.push(...taskIssues);
5884
- const newTasks = config.tasks.map((t) => {
5885
- if (existingNames.has(t.name)) {
5886
- conflicts.push(t.name);
5887
- return disambiguateName(t, existingNames, workspaceRef.name);
5888
- }
5889
- existingNames.add(t.name);
5890
- return t;
5947
+ async function migrateToGlobal(options) {
5948
+ const globalConfigPath = options.globalConfigPath ?? getScheduledTasksConfigPath();
5949
+ const migrationFailuresPath = options.migrationFailuresPath ?? getMigrationFailuresPath();
5950
+ const probe = options.probe ?? defaultProbe;
5951
+ const existing = readJsonFile(globalConfigPath);
5952
+ const baseConfig = existing && existing.version === 2 ? existing : { version: 2, tasks: [] };
5953
+ const existingNames = new Set(baseConfig.tasks.map((t) => t.name));
5954
+ const priorFailures = readJsonFile(migrationFailuresPath) ?? [];
5955
+ const failures = [...priorFailures];
5956
+ let migrated = 0;
5957
+ const conflicts = [];
5958
+ const issues = [];
5959
+ for (const workspacePath of options.workspacePaths) {
5960
+ const v1Path = path20.join(workspacePath, WORKSPACE_DIR, V1_FILENAME);
5961
+ if (!fs19.existsSync(v1Path)) continue;
5962
+ const v1 = readV1Config(v1Path);
5963
+ if (!v1.ok) {
5964
+ failures.push({
5965
+ workspacePath,
5966
+ v1Path,
5967
+ error: v1.error,
5968
+ at: (/* @__PURE__ */ new Date()).toISOString()
5891
5969
  });
5892
- baseConfig.tasks.push(...newTasks);
5893
- migrated += 1;
5894
5970
  safeDelete(v1Path);
5971
+ continue;
5895
5972
  }
5896
- if (migrated > 0) {
5897
- ensureDir(globalConfigPath);
5898
- const tmp = `${globalConfigPath}.tmp`;
5899
- fs19.writeFileSync(tmp, JSON.stringify(baseConfig, null, " "), "utf-8");
5900
- fs19.renameSync(tmp, globalConfigPath);
5901
- }
5902
- if (failures.length > priorFailures.length) {
5903
- writeJsonFile(migrationFailuresPath, failures);
5904
- } else if (failures.length === 0 && priorFailures.length > 0) {
5905
- safeDelete(migrationFailuresPath);
5973
+ let workspaceRef;
5974
+ try {
5975
+ workspaceRef = await probe(workspacePath);
5976
+ } catch (err) {
5977
+ failures.push({
5978
+ workspacePath,
5979
+ v1Path,
5980
+ error: `probe failed: ${err instanceof Error ? err.message : String(err)}`,
5981
+ at: (/* @__PURE__ */ new Date()).toISOString()
5982
+ });
5983
+ safeDelete(v1Path);
5984
+ continue;
5906
5985
  }
5907
- return { migrated, failed: failures.length - priorFailures.length, conflicts, issues };
5986
+ const { config, issues: taskIssues } = migrateV1ToV22(v1.config, workspaceRef);
5987
+ issues.push(...taskIssues);
5988
+ const newTasks = config.tasks.map((t) => {
5989
+ if (existingNames.has(t.name)) {
5990
+ conflicts.push(t.name);
5991
+ return disambiguateName(t, existingNames, workspaceRef.name);
5992
+ }
5993
+ existingNames.add(t.name);
5994
+ return t;
5995
+ });
5996
+ baseConfig.tasks.push(...newTasks);
5997
+ migrated += 1;
5998
+ safeDelete(v1Path);
5908
5999
  }
5909
- };
6000
+ if (migrated > 0) {
6001
+ ensureDir(globalConfigPath);
6002
+ const tmp = `${globalConfigPath}.tmp`;
6003
+ fs19.writeFileSync(tmp, JSON.stringify(baseConfig, null, " "), "utf-8");
6004
+ fs19.renameSync(tmp, globalConfigPath);
6005
+ }
6006
+ if (failures.length > priorFailures.length) {
6007
+ writeJsonFile(migrationFailuresPath, failures);
6008
+ } else if (failures.length === 0 && priorFailures.length > 0) {
6009
+ safeDelete(migrationFailuresPath);
6010
+ }
6011
+ return {
6012
+ migrated,
6013
+ failed: failures.length - priorFailures.length,
6014
+ conflicts,
6015
+ issues
6016
+ };
6017
+ }
5910
6018
 
5911
6019
  // src/scheduled-tasks/workspace-probe/WorkspaceProbe.ts
5912
6020
  import { spawn as spawn5 } from "child_process";
@@ -6808,7 +6916,6 @@ export {
6808
6916
  MIGRATION_FAILURES_FILENAME,
6809
6917
  MicrosoftAuthProvider,
6810
6918
  MicrosoftProviderNotHostedError,
6811
- MigrateToGlobal,
6812
6919
  NodeGitSpawner,
6813
6920
  PROFILES_JSON_FILENAME,
6814
6921
  PidManager,
@@ -6907,6 +7014,7 @@ export {
6907
7014
  isUserRepo,
6908
7015
  matchesCron,
6909
7016
  mergeWithDefaults,
7017
+ migrateToGlobal,
6910
7018
  moveFiles,
6911
7019
  narrowRepoConfig,
6912
7020
  noopLogger,