@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.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
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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
|
-
|
|
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
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
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("
|
|
2550
|
+
* rewriteRemoteUrl("repo-aHR0cHM6Ly9naXRodWIuY29tL21lZGFsc29mdGNoaW5hL21zLXNraWxscy5naXQ",
|
|
2529
2551
|
* "https://github.com/medalsoftchina/ms-skills.git")
|
|
2530
|
-
* → "http://localhost:3000/git-proxy/
|
|
2552
|
+
* → "http://localhost:3000/git-proxy/repo-aHR0cHM6Ly9naXRodWIuY29tL21lZGFsc29mdGNoaW5hL21zLXNraWxscy5naXQ"
|
|
2531
2553
|
*
|
|
2532
|
-
* rewriteRemoteUrl("
|
|
2554
|
+
* rewriteRemoteUrl("repo-aHR0cHM6Ly9naXRodWIuY29tL3RlYW0vaW50ZXJuYWwuZ2l0",
|
|
2533
2555
|
* "git@github.com:medalsoftchina/ms-skills.git")
|
|
2534
|
-
* → "http://localhost:3000/git-proxy/
|
|
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
|
|
2539
|
-
*
|
|
2560
|
+
* means callers can route both default and user repos through the same
|
|
2561
|
+
* dynamic proxy-id scheme.
|
|
2540
2562
|
*/
|
|
2541
|
-
rewriteRemoteUrl(_repoId,
|
|
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
|
|
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
|
-
|
|
2568
|
-
if (!
|
|
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/${
|
|
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
|
|
3377
|
-
|
|
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:
|
|
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)
|
|
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 (!
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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: "
|
|
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 {
|
|
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
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
const
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
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
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
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
|
-
|
|
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,
|