@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/auth.d.mts +2 -0
- package/dist/auth.d.ts +2 -0
- package/dist/auth.js +65 -36
- package/dist/auth.js.map +1 -1
- package/dist/auth.mjs +65 -36
- package/dist/auth.mjs.map +1 -1
- package/dist/{index-CrNC-aao.d.ts → index-BmxS1A9G.d.ts} +25 -11
- package/dist/{index-Dmyy4urr.d.mts → index-GAigCT0j.d.mts} +25 -11
- package/dist/index.d.mts +68 -34
- package/dist/index.d.ts +68 -34
- package/dist/index.js +255 -147
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +254 -146
- package/dist/index.mjs.map +1 -1
- package/dist/skill-linker.js.map +1 -1
- package/dist/skill-linker.mjs.map +1 -1
- package/dist/skill-store.js +1 -8
- package/dist/skill-store.js.map +1 -1
- package/dist/skill-store.mjs +1 -8
- package/dist/skill-store.mjs.map +1 -1
- package/dist/submit.d.mts +1 -1
- package/dist/submit.d.ts +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -75,7 +75,6 @@ __export(src_exports, {
|
|
|
75
75
|
MIGRATION_FAILURES_FILENAME: () => MIGRATION_FAILURES_FILENAME,
|
|
76
76
|
MicrosoftAuthProvider: () => MicrosoftAuthProvider,
|
|
77
77
|
MicrosoftProviderNotHostedError: () => MicrosoftProviderNotHostedError,
|
|
78
|
-
MigrateToGlobal: () => MigrateToGlobal,
|
|
79
78
|
NodeGitSpawner: () => NodeGitSpawner,
|
|
80
79
|
PROFILES_JSON_FILENAME: () => PROFILES_JSON_FILENAME,
|
|
81
80
|
PidManager: () => PidManager,
|
|
@@ -174,6 +173,7 @@ __export(src_exports, {
|
|
|
174
173
|
isUserRepo: () => isUserRepo,
|
|
175
174
|
matchesCron: () => matchesCron,
|
|
176
175
|
mergeWithDefaults: () => mergeWithDefaults,
|
|
176
|
+
migrateToGlobal: () => migrateToGlobal,
|
|
177
177
|
moveFiles: () => moveFiles,
|
|
178
178
|
narrowRepoConfig: () => narrowRepoConfig,
|
|
179
179
|
noopLogger: () => noopLogger,
|
|
@@ -1035,6 +1035,11 @@ var DEFAULT_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
|
1035
1035
|
var DEFAULT_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
1036
1036
|
var DEFAULT_USER_URL = "https://api.github.com/user";
|
|
1037
1037
|
var DEFAULT_SCOPE = "read:user user:email";
|
|
1038
|
+
var DEVICE_CODE_MAX_ATTEMPTS = 3;
|
|
1039
|
+
var DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS = 500;
|
|
1040
|
+
function isTransientNetworkError(error) {
|
|
1041
|
+
return error instanceof TypeError;
|
|
1042
|
+
}
|
|
1038
1043
|
var GitHubAuthProvider = class {
|
|
1039
1044
|
constructor(config) {
|
|
1040
1045
|
this.providerId = "github";
|
|
@@ -1050,7 +1055,8 @@ var GitHubAuthProvider = class {
|
|
|
1050
1055
|
minPollIntervalMs: config.minPollIntervalMs ?? 1e3,
|
|
1051
1056
|
maxPollIntervalMs: config.maxPollIntervalMs ?? 15e3,
|
|
1052
1057
|
maxWaitMs: config.maxWaitMs,
|
|
1053
|
-
fetchImpl: config.fetchImpl ?? fetch
|
|
1058
|
+
fetchImpl: config.fetchImpl ?? fetch,
|
|
1059
|
+
deviceCodeRetryBaseDelayMs: config.deviceCodeRetryBaseDelayMs ?? DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS
|
|
1054
1060
|
};
|
|
1055
1061
|
}
|
|
1056
1062
|
async requestDeviceFlow(opts) {
|
|
@@ -1058,29 +1064,43 @@ var GitHubAuthProvider = class {
|
|
|
1058
1064
|
client_id: this.cfg.clientId,
|
|
1059
1065
|
scope: opts?.scope ?? this.cfg.scope
|
|
1060
1066
|
});
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1067
|
+
let lastNetworkError;
|
|
1068
|
+
for (let attempt = 1; attempt <= DEVICE_CODE_MAX_ATTEMPTS; attempt++) {
|
|
1069
|
+
let resp;
|
|
1070
|
+
try {
|
|
1071
|
+
resp = await this.cfg.fetchImpl(this.cfg.deviceCodeUrl, {
|
|
1072
|
+
method: "POST",
|
|
1073
|
+
headers: {
|
|
1074
|
+
Accept: "application/json",
|
|
1075
|
+
"Content-Type": "application/json",
|
|
1076
|
+
"User-Agent": "serviceme-core"
|
|
1077
|
+
},
|
|
1078
|
+
body
|
|
1079
|
+
});
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
if (!isTransientNetworkError(error) || attempt === DEVICE_CODE_MAX_ATTEMPTS) {
|
|
1082
|
+
throw error;
|
|
1083
|
+
}
|
|
1084
|
+
lastNetworkError = error;
|
|
1085
|
+
await sleep(this.cfg.deviceCodeRetryBaseDelayMs * 2 ** (attempt - 1));
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
if (!resp.ok) {
|
|
1089
|
+
throw new Error(`GitHub device-code request failed: HTTP ${resp.status}`);
|
|
1090
|
+
}
|
|
1091
|
+
const data = await resp.json();
|
|
1092
|
+
if (!data.device_code || !data.user_code || !data.verification_uri) {
|
|
1093
|
+
throw new Error("GitHub device-code response missing required fields");
|
|
1094
|
+
}
|
|
1095
|
+
return {
|
|
1096
|
+
provider: this.providerId,
|
|
1097
|
+
userCode: data.user_code,
|
|
1098
|
+
verificationUrl: data.verification_uri,
|
|
1099
|
+
expiresAt: Date.now() + data.expires_in * 1e3,
|
|
1100
|
+
message: `Open ${data.verification_uri} and enter ${data.user_code}`
|
|
1101
|
+
};
|
|
1076
1102
|
}
|
|
1077
|
-
|
|
1078
|
-
provider: this.providerId,
|
|
1079
|
-
userCode: data.user_code,
|
|
1080
|
-
verificationUrl: data.verification_uri,
|
|
1081
|
-
expiresAt: Date.now() + data.expires_in * 1e3,
|
|
1082
|
-
message: `Open ${data.verification_uri} and enter ${data.user_code}`
|
|
1083
|
-
};
|
|
1103
|
+
throw lastNetworkError ?? new Error("GitHub device-code request failed");
|
|
1084
1104
|
}
|
|
1085
1105
|
async completeDeviceFlow(deviceCode, shouldContinue) {
|
|
1086
1106
|
const start = Date.now();
|
|
@@ -1098,19 +1118,28 @@ var GitHubAuthProvider = class {
|
|
|
1098
1118
|
if (shouldContinue && !shouldContinue()) {
|
|
1099
1119
|
throw new Error("GitHub device flow cancelled by caller");
|
|
1100
1120
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1121
|
+
let resp;
|
|
1122
|
+
try {
|
|
1123
|
+
resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {
|
|
1124
|
+
method: "POST",
|
|
1125
|
+
headers: {
|
|
1126
|
+
Accept: "application/json",
|
|
1127
|
+
"Content-Type": "application/json",
|
|
1128
|
+
"User-Agent": "serviceme-core"
|
|
1129
|
+
},
|
|
1130
|
+
body: JSON.stringify({
|
|
1131
|
+
client_id: this.cfg.clientId,
|
|
1132
|
+
device_code: deviceCode,
|
|
1133
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
1134
|
+
})
|
|
1135
|
+
});
|
|
1136
|
+
} catch (error) {
|
|
1137
|
+
if (!isTransientNetworkError(error)) {
|
|
1138
|
+
throw error;
|
|
1139
|
+
}
|
|
1140
|
+
pollIntervalMs = Math.min(Math.round(pollIntervalMs * 1.5), this.cfg.maxPollIntervalMs);
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1114
1143
|
if (!resp.ok) {
|
|
1115
1144
|
pollIntervalMs = Math.min(Math.round(pollIntervalMs * 1.5), this.cfg.maxPollIntervalMs);
|
|
1116
1145
|
continue;
|
|
@@ -2284,14 +2313,7 @@ function extractFrontmatter(raw) {
|
|
|
2284
2313
|
}
|
|
2285
2314
|
return { data, body };
|
|
2286
2315
|
}
|
|
2287
|
-
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2288
|
-
".git",
|
|
2289
|
-
"node_modules",
|
|
2290
|
-
".vscode",
|
|
2291
|
-
"dist",
|
|
2292
|
-
"build",
|
|
2293
|
-
"out"
|
|
2294
|
-
]);
|
|
2316
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".vscode", "dist", "build", "out"]);
|
|
2295
2317
|
var SKIP_TOP_LEVEL_SCAN_DIRS = /* @__PURE__ */ new Set([...SKIP_DIRS, "plugins"]);
|
|
2296
2318
|
|
|
2297
2319
|
// src/drafts/index.ts
|
|
@@ -2708,54 +2730,82 @@ var GitClient = class {
|
|
|
2708
2730
|
* Rewrite an upstream URL to its proxy form.
|
|
2709
2731
|
*
|
|
2710
2732
|
* Examples:
|
|
2711
|
-
* rewriteRemoteUrl("
|
|
2733
|
+
* rewriteRemoteUrl("repo-aHR0cHM6Ly9naXRodWIuY29tL21lZGFsc29mdGNoaW5hL21zLXNraWxscy5naXQ",
|
|
2712
2734
|
* "https://github.com/medalsoftchina/ms-skills.git")
|
|
2713
|
-
* → "http://localhost:3000/git-proxy/
|
|
2735
|
+
* → "http://localhost:3000/git-proxy/repo-aHR0cHM6Ly9naXRodWIuY29tL21lZGFsc29mdGNoaW5hL21zLXNraWxscy5naXQ"
|
|
2714
2736
|
*
|
|
2715
|
-
* rewriteRemoteUrl("
|
|
2737
|
+
* rewriteRemoteUrl("repo-aHR0cHM6Ly9naXRodWIuY29tL3RlYW0vaW50ZXJuYWwuZ2l0",
|
|
2716
2738
|
* "git@github.com:medalsoftchina/ms-skills.git")
|
|
2717
|
-
* → "http://localhost:3000/git-proxy/
|
|
2739
|
+
* → "http://localhost:3000/git-proxy/repo-aHR0cHM6Ly9naXRodWIuY29tL3RlYW0vaW50ZXJuYWwuZ2l0"
|
|
2718
2740
|
*
|
|
2719
2741
|
* The original URL's host/owner is intentionally DROPPED — the proxy
|
|
2720
2742
|
* knows the upstream from the per-repo config, not from the URL. This
|
|
2721
|
-
* means callers can
|
|
2722
|
-
*
|
|
2743
|
+
* means callers can route both default and user repos through the same
|
|
2744
|
+
* dynamic proxy-id scheme.
|
|
2723
2745
|
*/
|
|
2724
|
-
rewriteRemoteUrl(_repoId,
|
|
2746
|
+
rewriteRemoteUrl(_repoId, originalUrl, useProxy = true) {
|
|
2747
|
+
if (!useProxy) {
|
|
2748
|
+
return originalUrl;
|
|
2749
|
+
}
|
|
2725
2750
|
return `${this.serverProxyBase}/${_repoId}`;
|
|
2726
2751
|
}
|
|
2727
2752
|
/**
|
|
2728
2753
|
* `git clone <proxyUrl> <localPath>` — initialize a new local repo
|
|
2729
2754
|
* from the proxy. Returns when the clone succeeds; throws on failure.
|
|
2755
|
+
*
|
|
2756
|
+
* When `branch` is provided, the clone is restricted to that branch
|
|
2757
|
+
* via `git clone -b <branch> -- <proxyUrl> <localPath>` so the local
|
|
2758
|
+
* checkout lands on the configured `repo.branch` instead of the
|
|
2759
|
+
* upstream default branch. The parameter is optional and backward
|
|
2760
|
+
* compatible — omitting it keeps the original behaviour.
|
|
2730
2761
|
*/
|
|
2731
|
-
async clone(repoId, originalUrl, localPath) {
|
|
2732
|
-
const proxyUrl = this.rewriteRemoteUrl(repoId, originalUrl);
|
|
2733
|
-
const args = ["clone", "--", proxyUrl, localPath];
|
|
2762
|
+
async clone(repoId, originalUrl, localPath, branch, useProxy = true) {
|
|
2763
|
+
const proxyUrl = this.rewriteRemoteUrl(repoId, originalUrl, useProxy);
|
|
2764
|
+
const args = branch ? ["clone", "-b", branch, "--", proxyUrl, localPath] : ["clone", "--", proxyUrl, localPath];
|
|
2734
2765
|
await this.runOrThrow(args, { cwd: process.cwd() });
|
|
2735
2766
|
}
|
|
2736
2767
|
/**
|
|
2737
2768
|
* `git fetch <remote> <branch>` + return the resulting commit SHA.
|
|
2738
2769
|
* Operates on an already-cloned repo at `localPath`.
|
|
2770
|
+
*
|
|
2771
|
+
* When `branch` is provided, the pull target is forced to that branch:
|
|
2772
|
+
* if the local working tree is on a different branch, `git checkout
|
|
2773
|
+
* <branch>` is issued first (git auto-tracks `origin/<branch>`), so
|
|
2774
|
+
* the sync leaves the repo parked on the configured `repo.branch`.
|
|
2775
|
+
* When `branch` is omitted the legacy behaviour is preserved — the
|
|
2776
|
+
* current local branch is fast-forwarded. The parameter is optional
|
|
2777
|
+
* and backward compatible.
|
|
2739
2778
|
*/
|
|
2740
|
-
async pull(repoId, localPath) {
|
|
2779
|
+
async pull(repoId, localPath, branch, useProxy = true, originalUrl) {
|
|
2741
2780
|
const remoteUrl = await this.getRemoteUrl(localPath, "origin");
|
|
2742
2781
|
if (!remoteUrl) {
|
|
2743
2782
|
throw new Error(`no 'origin' remote configured at ${localPath}`);
|
|
2744
2783
|
}
|
|
2745
|
-
const
|
|
2784
|
+
const targetUrl = useProxy ? remoteUrl : originalUrl ?? remoteUrl;
|
|
2785
|
+
const proxyUrl = this.rewriteRemoteUrl(repoId, targetUrl, useProxy);
|
|
2746
2786
|
await this.runOrThrow(["remote", "set-url", "origin", proxyUrl], { cwd: localPath });
|
|
2747
2787
|
const before = await this.safeRevParse(localPath);
|
|
2748
2788
|
await this.runOrThrow(["fetch", "origin"], { cwd: localPath });
|
|
2749
2789
|
const after = await this.safeRevParse(localPath);
|
|
2750
|
-
|
|
2751
|
-
if (!
|
|
2790
|
+
let targetBranch = branch;
|
|
2791
|
+
if (!targetBranch) {
|
|
2792
|
+
targetBranch = await this.currentBranch(localPath);
|
|
2793
|
+
} else {
|
|
2794
|
+
const current = await this.currentBranch(localPath);
|
|
2795
|
+
if (current !== targetBranch) {
|
|
2796
|
+
await this.runOrThrow(["checkout", targetBranch], { cwd: localPath });
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
if (!targetBranch) {
|
|
2752
2800
|
throw new Error(`could not determine current branch at ${localPath}`);
|
|
2753
2801
|
}
|
|
2754
|
-
await this.runOrThrow(["merge", "--ff-only", `origin/${
|
|
2802
|
+
await this.runOrThrow(["merge", "--ff-only", `origin/${targetBranch}`], {
|
|
2803
|
+
cwd: localPath
|
|
2804
|
+
});
|
|
2755
2805
|
return {
|
|
2756
2806
|
updated: before !== after,
|
|
2757
2807
|
commitSha: after,
|
|
2758
|
-
branch
|
|
2808
|
+
branch: targetBranch
|
|
2759
2809
|
};
|
|
2760
2810
|
}
|
|
2761
2811
|
/**
|
|
@@ -2763,12 +2813,12 @@ var GitClient = class {
|
|
|
2763
2813
|
* for committing locally first. The server's GitProxy injects the
|
|
2764
2814
|
* PAT on the way upstream.
|
|
2765
2815
|
*/
|
|
2766
|
-
async push(repoId, localPath, branch) {
|
|
2816
|
+
async push(repoId, localPath, branch, useProxy = true) {
|
|
2767
2817
|
const remoteUrl = await this.getRemoteUrl(localPath, "origin");
|
|
2768
2818
|
if (!remoteUrl) {
|
|
2769
2819
|
throw new Error(`no 'origin' remote configured at ${localPath}`);
|
|
2770
2820
|
}
|
|
2771
|
-
const proxyUrl = this.rewriteRemoteUrl(repoId, remoteUrl);
|
|
2821
|
+
const proxyUrl = this.rewriteRemoteUrl(repoId, remoteUrl, useProxy);
|
|
2772
2822
|
await this.runOrThrow(["remote", "set-url", "origin", proxyUrl], { cwd: localPath });
|
|
2773
2823
|
const result = await this.spawner.spawn(
|
|
2774
2824
|
["push", "origin", `refs/heads/${branch}:refs/heads/${branch}`],
|
|
@@ -2788,8 +2838,8 @@ var GitClient = class {
|
|
|
2788
2838
|
* `git ls-remote <proxyUrl>` — list advertised branches without
|
|
2789
2839
|
* cloning. Used by addUserRepo to detect the default branch.
|
|
2790
2840
|
*/
|
|
2791
|
-
async lsRemote(repoId, originalUrl) {
|
|
2792
|
-
const proxyUrl = this.rewriteRemoteUrl(repoId, originalUrl);
|
|
2841
|
+
async lsRemote(repoId, originalUrl, useProxy = true) {
|
|
2842
|
+
const proxyUrl = this.rewriteRemoteUrl(repoId, originalUrl, useProxy);
|
|
2793
2843
|
const result = await this.spawner.spawn(["ls-remote", "--heads", "--", proxyUrl], {
|
|
2794
2844
|
cwd: process.cwd()
|
|
2795
2845
|
});
|
|
@@ -3548,14 +3598,36 @@ function isUserRepo(repo) {
|
|
|
3548
3598
|
|
|
3549
3599
|
// src/repo-manager/index.ts
|
|
3550
3600
|
function idFromUrl(url) {
|
|
3551
|
-
const trimmed = url.trim().replace(/\.git$/, "");
|
|
3552
|
-
const
|
|
3553
|
-
|
|
3601
|
+
const trimmed = url.trim().replace(/\/+$/, "").replace(/\.git$/, "");
|
|
3602
|
+
const sanitize = (value) => value.trim().toLowerCase().replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
3603
|
+
let owner = "";
|
|
3604
|
+
let repo = "";
|
|
3605
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
3606
|
+
const parsed = new URL(trimmed);
|
|
3607
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
3608
|
+
repo = segments.at(-1) ?? "";
|
|
3609
|
+
owner = segments.length >= 2 ? segments.at(-2) ?? "" : "";
|
|
3610
|
+
} else if (trimmed.startsWith("git@")) {
|
|
3611
|
+
const colon = trimmed.indexOf(":");
|
|
3612
|
+
const repoPath = colon >= 0 ? trimmed.slice(colon + 1) : "";
|
|
3613
|
+
const segments = repoPath.split("/").filter(Boolean);
|
|
3614
|
+
repo = segments.at(-1) ?? "";
|
|
3615
|
+
owner = segments.length >= 2 ? segments.at(-2) ?? "" : "";
|
|
3616
|
+
} else {
|
|
3617
|
+
repo = trimmed.split("/").pop() ?? "";
|
|
3618
|
+
}
|
|
3619
|
+
const safeRepo = sanitize(repo);
|
|
3620
|
+
const safeOwner = sanitize(owner);
|
|
3621
|
+
const safe = safeOwner ? `${safeOwner}-${safeRepo}` : safeRepo;
|
|
3554
3622
|
if (!SAFE_REPO_ID_PATTERN.test(safe)) {
|
|
3555
3623
|
throw new InvalidRepoUrlError(`cannot derive a valid repo id from url: ${url}`);
|
|
3556
3624
|
}
|
|
3557
3625
|
return safe;
|
|
3558
3626
|
}
|
|
3627
|
+
function proxyRepoId(url) {
|
|
3628
|
+
const encoded = Buffer.from(url.trim(), "utf8").toString("base64url");
|
|
3629
|
+
return `repo-${encoded}`;
|
|
3630
|
+
}
|
|
3559
3631
|
var RepoManagerError = class extends Error {
|
|
3560
3632
|
constructor(message) {
|
|
3561
3633
|
super(message);
|
|
@@ -3584,7 +3656,7 @@ var RepoManager = class {
|
|
|
3584
3656
|
* but do NOT abort the loop — the UI surfaces per-repo state and the
|
|
3585
3657
|
* user can retry.
|
|
3586
3658
|
*
|
|
3587
|
-
* Per spec §3.1:
|
|
3659
|
+
* Per spec §3.1: iterate all default repos; the loop honors `enabled=false` and
|
|
3588
3660
|
* disables (rather than removes) repos the user has turned off.
|
|
3589
3661
|
*/
|
|
3590
3662
|
async ensureDefaults() {
|
|
@@ -3594,11 +3666,14 @@ var RepoManager = class {
|
|
|
3594
3666
|
if (!repo.enabled) continue;
|
|
3595
3667
|
const localPath = getRepoDir(repo.id);
|
|
3596
3668
|
const exists = await this.pathExists(localPath);
|
|
3597
|
-
if (exists)
|
|
3669
|
+
if (exists) {
|
|
3670
|
+
if (await this.isValidGitRepo(localPath)) continue;
|
|
3671
|
+
await fs8.rm(localPath, { recursive: true, force: true });
|
|
3672
|
+
}
|
|
3598
3673
|
try {
|
|
3599
3674
|
if (!this.skipClone) {
|
|
3600
3675
|
await fs8.mkdir(path11.dirname(localPath), { recursive: true });
|
|
3601
|
-
await this.git.clone(repo.id, repo.url, localPath);
|
|
3676
|
+
await this.git.clone(repo.id, repo.url, localPath, repo.branch, true);
|
|
3602
3677
|
}
|
|
3603
3678
|
await this.store.updateRepo(repo.id, {
|
|
3604
3679
|
lastSyncStatus: "ok",
|
|
@@ -3627,12 +3702,17 @@ var RepoManager = class {
|
|
|
3627
3702
|
async pullOne(repoId) {
|
|
3628
3703
|
const repo = this.store.get(repoId);
|
|
3629
3704
|
if (!repo) throw new RepoNotInStoreError(repoId);
|
|
3705
|
+
const useProxy = repo.useProxy ?? true;
|
|
3706
|
+
const proxyId = useProxy ? proxyRepoId(repo.url) : repo.id;
|
|
3630
3707
|
const localPath = getRepoDir(repoId);
|
|
3631
3708
|
const exists = await this.pathExists(localPath);
|
|
3632
|
-
if (!
|
|
3709
|
+
if (exists && !await this.isValidGitRepo(localPath)) {
|
|
3710
|
+
await fs8.rm(localPath, { recursive: true, force: true });
|
|
3711
|
+
}
|
|
3712
|
+
if (!exists || !await this.pathExists(localPath)) {
|
|
3633
3713
|
await fs8.mkdir(path11.dirname(localPath), { recursive: true });
|
|
3634
3714
|
if (!this.skipClone) {
|
|
3635
|
-
await this.git.clone(
|
|
3715
|
+
await this.git.clone(proxyId, repo.url, localPath, repo.branch, useProxy);
|
|
3636
3716
|
}
|
|
3637
3717
|
const result = {
|
|
3638
3718
|
updated: true,
|
|
@@ -3646,7 +3726,7 @@ var RepoManager = class {
|
|
|
3646
3726
|
return result;
|
|
3647
3727
|
}
|
|
3648
3728
|
try {
|
|
3649
|
-
const result = await this.git.pull(
|
|
3729
|
+
const result = await this.git.pull(proxyId, localPath, repo.branch, useProxy, repo.url);
|
|
3650
3730
|
await this.store.updateRepo(repoId, {
|
|
3651
3731
|
lastSyncStatus: "ok",
|
|
3652
3732
|
lastSyncAt: this.now(),
|
|
@@ -3698,10 +3778,12 @@ var RepoManager = class {
|
|
|
3698
3778
|
throw new InvalidRepoUrlError(`expected https:// or git@ URL, got: ${url}`);
|
|
3699
3779
|
}
|
|
3700
3780
|
const id = idFromUrl(url);
|
|
3781
|
+
const useProxy = input.useProxy ?? true;
|
|
3782
|
+
const userProxyId = useProxy ? proxyRepoId(url) : id;
|
|
3701
3783
|
assertSafeRepoId(id);
|
|
3702
3784
|
let branch = input.branch?.trim();
|
|
3703
3785
|
if (!branch) {
|
|
3704
|
-
const branches = await this.git.lsRemote(
|
|
3786
|
+
const branches = await this.git.lsRemote(userProxyId, url, useProxy);
|
|
3705
3787
|
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"));
|
|
3706
3788
|
if (!head) {
|
|
3707
3789
|
throw new InvalidRepoUrlError(
|
|
@@ -3719,6 +3801,7 @@ var RepoManager = class {
|
|
|
3719
3801
|
url,
|
|
3720
3802
|
branch,
|
|
3721
3803
|
enabled: true,
|
|
3804
|
+
useProxy,
|
|
3722
3805
|
writeEnabled: false,
|
|
3723
3806
|
source: "user",
|
|
3724
3807
|
addedAt: this.now()
|
|
@@ -3728,7 +3811,7 @@ var RepoManager = class {
|
|
|
3728
3811
|
if (!this.skipClone) {
|
|
3729
3812
|
const localPath = getRepoDir(id);
|
|
3730
3813
|
await fs8.mkdir(path11.dirname(localPath), { recursive: true });
|
|
3731
|
-
await this.git.clone(
|
|
3814
|
+
await this.git.clone(userProxyId, url, localPath, branch, useProxy);
|
|
3732
3815
|
cloned = true;
|
|
3733
3816
|
}
|
|
3734
3817
|
return { repo, branch, cloned };
|
|
@@ -3777,6 +3860,14 @@ var RepoManager = class {
|
|
|
3777
3860
|
async ensureHome() {
|
|
3778
3861
|
await fs8.mkdir(getServicemeHome(), { recursive: true });
|
|
3779
3862
|
}
|
|
3863
|
+
/**
|
|
3864
|
+
* Returns `true` when `p` contains a `.git` entry — i.e. it is an
|
|
3865
|
+
* initialised git repository and safe to `pull`. Empty directories
|
|
3866
|
+
* (e.g. from an interrupted clone) return `false`.
|
|
3867
|
+
*/
|
|
3868
|
+
async isValidGitRepo(p) {
|
|
3869
|
+
return this.pathExists(path11.join(p, ".git"));
|
|
3870
|
+
}
|
|
3780
3871
|
async pathExists(p) {
|
|
3781
3872
|
try {
|
|
3782
3873
|
await fs8.stat(p);
|
|
@@ -3793,10 +3884,11 @@ var FIXED_ADDED_AT = "2026-06-29T00:00:00.000Z";
|
|
|
3793
3884
|
var DEFAULT_REPO_SEEDS = [
|
|
3794
3885
|
{
|
|
3795
3886
|
id: "medalsoftchina-ms-skills",
|
|
3796
|
-
name: "
|
|
3887
|
+
name: "SERVICEME (\u7CBE\u9009)",
|
|
3797
3888
|
url: "https://github.com/medalsoftchina/ms-skills.git",
|
|
3798
3889
|
branch: "v2",
|
|
3799
3890
|
enabled: true,
|
|
3891
|
+
useProxy: true,
|
|
3800
3892
|
writeEnabled: true,
|
|
3801
3893
|
source: "default",
|
|
3802
3894
|
description: "Medalsoft \u7CBE\u9009 skill/agent \u4ED3\u5E93\uFF0C\u53EF\u8BFB\u53EF\u5199"
|
|
@@ -3807,6 +3899,7 @@ var DEFAULT_REPO_SEEDS = [
|
|
|
3807
3899
|
url: "https://github.com/anthropics/skills.git",
|
|
3808
3900
|
branch: "main",
|
|
3809
3901
|
enabled: true,
|
|
3902
|
+
useProxy: true,
|
|
3810
3903
|
writeEnabled: false,
|
|
3811
3904
|
source: "default",
|
|
3812
3905
|
description: "Anthropic \u5B98\u65B9 skills \u793A\u4F8B"
|
|
@@ -3817,16 +3910,29 @@ var DEFAULT_REPO_SEEDS = [
|
|
|
3817
3910
|
url: "https://github.com/github/awesome-copilot.git",
|
|
3818
3911
|
branch: "main",
|
|
3819
3912
|
enabled: true,
|
|
3913
|
+
useProxy: true,
|
|
3820
3914
|
writeEnabled: false,
|
|
3821
3915
|
source: "default",
|
|
3822
3916
|
description: "GitHub Copilot \u793E\u533A\u7CBE\u9009 prompts/agents"
|
|
3823
3917
|
},
|
|
3918
|
+
{
|
|
3919
|
+
id: "mattpocock-skills",
|
|
3920
|
+
name: "Matt Pocock Skills",
|
|
3921
|
+
url: "https://github.com/mattpocock/skills.git",
|
|
3922
|
+
branch: "main",
|
|
3923
|
+
enabled: true,
|
|
3924
|
+
useProxy: true,
|
|
3925
|
+
writeEnabled: false,
|
|
3926
|
+
source: "default",
|
|
3927
|
+
description: "Matt Pocock \u793E\u533A skills \u4ED3\u5E93"
|
|
3928
|
+
},
|
|
3824
3929
|
{
|
|
3825
3930
|
id: "composiohq-awesome-claude-skills",
|
|
3826
3931
|
name: "Composio Awesome Claude Skills",
|
|
3827
3932
|
url: "https://github.com/ComposioHQ/awesome-claude-skills.git",
|
|
3828
3933
|
branch: "main",
|
|
3829
3934
|
enabled: true,
|
|
3935
|
+
useProxy: true,
|
|
3830
3936
|
writeEnabled: false,
|
|
3831
3937
|
source: "default",
|
|
3832
3938
|
description: "Composio \u7EF4\u62A4\u7684 Claude skills \u96C6\u5408"
|
|
@@ -3903,6 +4009,7 @@ var baseRepoFields = {
|
|
|
3903
4009
|
url: import_zod.z.string().url(),
|
|
3904
4010
|
branch: import_zod.z.string().min(1).max(200),
|
|
3905
4011
|
enabled: import_zod.z.boolean(),
|
|
4012
|
+
useProxy: import_zod.z.boolean().optional().default(true),
|
|
3906
4013
|
writeEnabled: import_zod.z.boolean(),
|
|
3907
4014
|
addedAt: isoTimestampSchema,
|
|
3908
4015
|
lastSyncAt: isoTimestampSchema.optional(),
|
|
@@ -5949,7 +6056,10 @@ function readV1Config(v1Path) {
|
|
|
5949
6056
|
try {
|
|
5950
6057
|
raw = fs19.readFileSync(v1Path, "utf-8");
|
|
5951
6058
|
} catch (err) {
|
|
5952
|
-
return {
|
|
6059
|
+
return {
|
|
6060
|
+
ok: false,
|
|
6061
|
+
error: `read failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6062
|
+
};
|
|
5953
6063
|
}
|
|
5954
6064
|
let parsed;
|
|
5955
6065
|
try {
|
|
@@ -6004,79 +6114,77 @@ function disambiguateName(task, existingNames, workspaceName) {
|
|
|
6004
6114
|
existingNames.add(candidate);
|
|
6005
6115
|
return { ...task, name: candidate };
|
|
6006
6116
|
}
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
const
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
if (!v1.ok) {
|
|
6030
|
-
failures.push({
|
|
6031
|
-
workspacePath,
|
|
6032
|
-
v1Path,
|
|
6033
|
-
error: v1.error,
|
|
6034
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6035
|
-
});
|
|
6036
|
-
safeDelete(v1Path);
|
|
6037
|
-
continue;
|
|
6038
|
-
}
|
|
6039
|
-
let workspaceRef;
|
|
6040
|
-
try {
|
|
6041
|
-
workspaceRef = await probe(workspacePath);
|
|
6042
|
-
} catch (err) {
|
|
6043
|
-
failures.push({
|
|
6044
|
-
workspacePath,
|
|
6045
|
-
v1Path,
|
|
6046
|
-
error: `probe failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
6047
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6048
|
-
});
|
|
6049
|
-
safeDelete(v1Path);
|
|
6050
|
-
continue;
|
|
6051
|
-
}
|
|
6052
|
-
const { config, issues: taskIssues } = (0, import_devtools_protocol9.migrateV1ToV2)(v1.config, workspaceRef);
|
|
6053
|
-
issues.push(...taskIssues);
|
|
6054
|
-
const newTasks = config.tasks.map((t) => {
|
|
6055
|
-
if (existingNames.has(t.name)) {
|
|
6056
|
-
conflicts.push(t.name);
|
|
6057
|
-
return disambiguateName(t, existingNames, workspaceRef.name);
|
|
6058
|
-
}
|
|
6059
|
-
existingNames.add(t.name);
|
|
6060
|
-
return t;
|
|
6117
|
+
async function migrateToGlobal(options) {
|
|
6118
|
+
const globalConfigPath = options.globalConfigPath ?? getScheduledTasksConfigPath();
|
|
6119
|
+
const migrationFailuresPath = options.migrationFailuresPath ?? getMigrationFailuresPath();
|
|
6120
|
+
const probe = options.probe ?? defaultProbe;
|
|
6121
|
+
const existing = readJsonFile(globalConfigPath);
|
|
6122
|
+
const baseConfig = existing && existing.version === 2 ? existing : { version: 2, tasks: [] };
|
|
6123
|
+
const existingNames = new Set(baseConfig.tasks.map((t) => t.name));
|
|
6124
|
+
const priorFailures = readJsonFile(migrationFailuresPath) ?? [];
|
|
6125
|
+
const failures = [...priorFailures];
|
|
6126
|
+
let migrated = 0;
|
|
6127
|
+
const conflicts = [];
|
|
6128
|
+
const issues = [];
|
|
6129
|
+
for (const workspacePath of options.workspacePaths) {
|
|
6130
|
+
const v1Path = path20.join(workspacePath, WORKSPACE_DIR, V1_FILENAME);
|
|
6131
|
+
if (!fs19.existsSync(v1Path)) continue;
|
|
6132
|
+
const v1 = readV1Config(v1Path);
|
|
6133
|
+
if (!v1.ok) {
|
|
6134
|
+
failures.push({
|
|
6135
|
+
workspacePath,
|
|
6136
|
+
v1Path,
|
|
6137
|
+
error: v1.error,
|
|
6138
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6061
6139
|
});
|
|
6062
|
-
baseConfig.tasks.push(...newTasks);
|
|
6063
|
-
migrated += 1;
|
|
6064
6140
|
safeDelete(v1Path);
|
|
6141
|
+
continue;
|
|
6065
6142
|
}
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6143
|
+
let workspaceRef;
|
|
6144
|
+
try {
|
|
6145
|
+
workspaceRef = await probe(workspacePath);
|
|
6146
|
+
} catch (err) {
|
|
6147
|
+
failures.push({
|
|
6148
|
+
workspacePath,
|
|
6149
|
+
v1Path,
|
|
6150
|
+
error: `probe failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
6151
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6152
|
+
});
|
|
6153
|
+
safeDelete(v1Path);
|
|
6154
|
+
continue;
|
|
6076
6155
|
}
|
|
6077
|
-
|
|
6156
|
+
const { config, issues: taskIssues } = (0, import_devtools_protocol9.migrateV1ToV2)(v1.config, workspaceRef);
|
|
6157
|
+
issues.push(...taskIssues);
|
|
6158
|
+
const newTasks = config.tasks.map((t) => {
|
|
6159
|
+
if (existingNames.has(t.name)) {
|
|
6160
|
+
conflicts.push(t.name);
|
|
6161
|
+
return disambiguateName(t, existingNames, workspaceRef.name);
|
|
6162
|
+
}
|
|
6163
|
+
existingNames.add(t.name);
|
|
6164
|
+
return t;
|
|
6165
|
+
});
|
|
6166
|
+
baseConfig.tasks.push(...newTasks);
|
|
6167
|
+
migrated += 1;
|
|
6168
|
+
safeDelete(v1Path);
|
|
6078
6169
|
}
|
|
6079
|
-
|
|
6170
|
+
if (migrated > 0) {
|
|
6171
|
+
ensureDir(globalConfigPath);
|
|
6172
|
+
const tmp = `${globalConfigPath}.tmp`;
|
|
6173
|
+
fs19.writeFileSync(tmp, JSON.stringify(baseConfig, null, " "), "utf-8");
|
|
6174
|
+
fs19.renameSync(tmp, globalConfigPath);
|
|
6175
|
+
}
|
|
6176
|
+
if (failures.length > priorFailures.length) {
|
|
6177
|
+
writeJsonFile(migrationFailuresPath, failures);
|
|
6178
|
+
} else if (failures.length === 0 && priorFailures.length > 0) {
|
|
6179
|
+
safeDelete(migrationFailuresPath);
|
|
6180
|
+
}
|
|
6181
|
+
return {
|
|
6182
|
+
migrated,
|
|
6183
|
+
failed: failures.length - priorFailures.length,
|
|
6184
|
+
conflicts,
|
|
6185
|
+
issues
|
|
6186
|
+
};
|
|
6187
|
+
}
|
|
6080
6188
|
|
|
6081
6189
|
// src/scheduled-tasks/workspace-probe/WorkspaceProbe.ts
|
|
6082
6190
|
var import_node_child_process5 = require("child_process");
|
|
@@ -6979,7 +7087,6 @@ var ToolboxCore = class {
|
|
|
6979
7087
|
MIGRATION_FAILURES_FILENAME,
|
|
6980
7088
|
MicrosoftAuthProvider,
|
|
6981
7089
|
MicrosoftProviderNotHostedError,
|
|
6982
|
-
MigrateToGlobal,
|
|
6983
7090
|
NodeGitSpawner,
|
|
6984
7091
|
PROFILES_JSON_FILENAME,
|
|
6985
7092
|
PidManager,
|
|
@@ -7078,6 +7185,7 @@ var ToolboxCore = class {
|
|
|
7078
7185
|
isUserRepo,
|
|
7079
7186
|
matchesCron,
|
|
7080
7187
|
mergeWithDefaults,
|
|
7188
|
+
migrateToGlobal,
|
|
7081
7189
|
moveFiles,
|
|
7082
7190
|
narrowRepoConfig,
|
|
7083
7191
|
noopLogger,
|