rulesync 7.15.2 → 7.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-WY325EI7.js → chunk-E5YWRHGW.js} +142 -39
- package/dist/cli/index.cjs +309 -173
- package/dist/cli/index.js +173 -138
- package/dist/index.cjs +140 -40
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -408,6 +408,7 @@ var import_node_path5 = require("path");
|
|
|
408
408
|
|
|
409
409
|
// src/utils/frontmatter.ts
|
|
410
410
|
var import_gray_matter = __toESM(require("gray-matter"), 1);
|
|
411
|
+
var import_js_yaml = require("js-yaml");
|
|
411
412
|
function isPlainObject(value) {
|
|
412
413
|
if (value === null || typeof value !== "object") return false;
|
|
413
414
|
const prototype = Object.getPrototypeOf(value);
|
|
@@ -446,8 +447,55 @@ function deepRemoveNullishObject(obj) {
|
|
|
446
447
|
}
|
|
447
448
|
return result;
|
|
448
449
|
}
|
|
449
|
-
function
|
|
450
|
-
|
|
450
|
+
function deepFlattenStringsValue(value) {
|
|
451
|
+
if (value === null || value === void 0) {
|
|
452
|
+
return void 0;
|
|
453
|
+
}
|
|
454
|
+
if (typeof value === "string") {
|
|
455
|
+
return value.replace(/\n+/g, " ").trim();
|
|
456
|
+
}
|
|
457
|
+
if (Array.isArray(value)) {
|
|
458
|
+
const cleanedArray = value.map((item) => deepFlattenStringsValue(item)).filter((item) => item !== void 0);
|
|
459
|
+
return cleanedArray;
|
|
460
|
+
}
|
|
461
|
+
if (isPlainObject(value)) {
|
|
462
|
+
const result = {};
|
|
463
|
+
for (const [key, val] of Object.entries(value)) {
|
|
464
|
+
const cleaned = deepFlattenStringsValue(val);
|
|
465
|
+
if (cleaned !== void 0) {
|
|
466
|
+
result[key] = cleaned;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return result;
|
|
470
|
+
}
|
|
471
|
+
return value;
|
|
472
|
+
}
|
|
473
|
+
function deepFlattenStringsObject(obj) {
|
|
474
|
+
if (!obj || typeof obj !== "object") {
|
|
475
|
+
return {};
|
|
476
|
+
}
|
|
477
|
+
const result = {};
|
|
478
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
479
|
+
const cleaned = deepFlattenStringsValue(val);
|
|
480
|
+
if (cleaned !== void 0) {
|
|
481
|
+
result[key] = cleaned;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return result;
|
|
485
|
+
}
|
|
486
|
+
function stringifyFrontmatter(body, frontmatter, options) {
|
|
487
|
+
const { avoidBlockScalars = false } = options ?? {};
|
|
488
|
+
const cleanFrontmatter = avoidBlockScalars ? deepFlattenStringsObject(frontmatter) : deepRemoveNullishObject(frontmatter);
|
|
489
|
+
if (avoidBlockScalars) {
|
|
490
|
+
return import_gray_matter.default.stringify(body, cleanFrontmatter, {
|
|
491
|
+
engines: {
|
|
492
|
+
yaml: {
|
|
493
|
+
parse: (input) => (0, import_js_yaml.load)(input) ?? {},
|
|
494
|
+
stringify: (data) => (0, import_js_yaml.dump)(data, { lineWidth: -1 })
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
}
|
|
451
499
|
return import_gray_matter.default.stringify(body, cleanFrontmatter);
|
|
452
500
|
}
|
|
453
501
|
function parseFrontmatter(content, filePath) {
|
|
@@ -1599,7 +1647,7 @@ var CursorCommand = class _CursorCommand extends ToolCommand {
|
|
|
1599
1647
|
}
|
|
1600
1648
|
super({
|
|
1601
1649
|
...rest,
|
|
1602
|
-
fileContent: stringifyFrontmatter(body, frontmatter)
|
|
1650
|
+
fileContent: stringifyFrontmatter(body, frontmatter, { avoidBlockScalars: true })
|
|
1603
1651
|
});
|
|
1604
1652
|
this.frontmatter = frontmatter;
|
|
1605
1653
|
this.body = body;
|
|
@@ -6025,12 +6073,26 @@ var CursorMcp = class _CursorMcp extends ToolMcp {
|
|
|
6025
6073
|
json;
|
|
6026
6074
|
constructor(params) {
|
|
6027
6075
|
super(params);
|
|
6028
|
-
|
|
6076
|
+
if (this.fileContent !== void 0) {
|
|
6077
|
+
try {
|
|
6078
|
+
this.json = JSON.parse(this.fileContent);
|
|
6079
|
+
} catch (error) {
|
|
6080
|
+
throw new Error(
|
|
6081
|
+
`Failed to parse Cursor MCP config at ${(0, import_node_path46.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(error)}`,
|
|
6082
|
+
{ cause: error }
|
|
6083
|
+
);
|
|
6084
|
+
}
|
|
6085
|
+
} else {
|
|
6086
|
+
this.json = {};
|
|
6087
|
+
}
|
|
6029
6088
|
}
|
|
6030
6089
|
getJson() {
|
|
6031
6090
|
return this.json;
|
|
6032
6091
|
}
|
|
6033
|
-
|
|
6092
|
+
isDeletable() {
|
|
6093
|
+
return !this.global;
|
|
6094
|
+
}
|
|
6095
|
+
static getSettablePaths(_options) {
|
|
6034
6096
|
return {
|
|
6035
6097
|
relativeDirPath: ".cursor",
|
|
6036
6098
|
relativeFilePath: "mcp.json"
|
|
@@ -6038,41 +6100,62 @@ var CursorMcp = class _CursorMcp extends ToolMcp {
|
|
|
6038
6100
|
}
|
|
6039
6101
|
static async fromFile({
|
|
6040
6102
|
baseDir = process.cwd(),
|
|
6041
|
-
validate = true
|
|
6103
|
+
validate = true,
|
|
6104
|
+
global = false
|
|
6042
6105
|
}) {
|
|
6043
|
-
const
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
)
|
|
6049
|
-
)
|
|
6106
|
+
const paths = this.getSettablePaths({ global });
|
|
6107
|
+
const filePath = (0, import_node_path46.join)(baseDir, paths.relativeDirPath, paths.relativeFilePath);
|
|
6108
|
+
const fileContent = await readFileContentOrNull(filePath) ?? '{"mcpServers":{}}';
|
|
6109
|
+
let json;
|
|
6110
|
+
try {
|
|
6111
|
+
json = JSON.parse(fileContent);
|
|
6112
|
+
} catch (error) {
|
|
6113
|
+
throw new Error(
|
|
6114
|
+
`Failed to parse Cursor MCP config at ${(0, import_node_path46.join)(paths.relativeDirPath, paths.relativeFilePath)}: ${formatError(error)}`,
|
|
6115
|
+
{ cause: error }
|
|
6116
|
+
);
|
|
6117
|
+
}
|
|
6118
|
+
const newJson = { ...json, mcpServers: json.mcpServers ?? {} };
|
|
6050
6119
|
return new _CursorMcp({
|
|
6051
6120
|
baseDir,
|
|
6052
|
-
relativeDirPath:
|
|
6053
|
-
relativeFilePath:
|
|
6054
|
-
fileContent,
|
|
6055
|
-
validate
|
|
6121
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6122
|
+
relativeFilePath: paths.relativeFilePath,
|
|
6123
|
+
fileContent: JSON.stringify(newJson, null, 2),
|
|
6124
|
+
validate,
|
|
6125
|
+
global
|
|
6056
6126
|
});
|
|
6057
6127
|
}
|
|
6058
|
-
static fromRulesyncMcp({
|
|
6128
|
+
static async fromRulesyncMcp({
|
|
6059
6129
|
baseDir = process.cwd(),
|
|
6060
6130
|
rulesyncMcp,
|
|
6061
|
-
validate = true
|
|
6131
|
+
validate = true,
|
|
6132
|
+
global = false
|
|
6062
6133
|
}) {
|
|
6063
|
-
const
|
|
6064
|
-
const
|
|
6134
|
+
const paths = this.getSettablePaths({ global });
|
|
6135
|
+
const fileContent = await readOrInitializeFileContent(
|
|
6136
|
+
(0, import_node_path46.join)(baseDir, paths.relativeDirPath, paths.relativeFilePath),
|
|
6137
|
+
JSON.stringify({ mcpServers: {} }, null, 2)
|
|
6138
|
+
);
|
|
6139
|
+
let json;
|
|
6140
|
+
try {
|
|
6141
|
+
json = JSON.parse(fileContent);
|
|
6142
|
+
} catch (error) {
|
|
6143
|
+
throw new Error(
|
|
6144
|
+
`Failed to parse Cursor MCP config at ${(0, import_node_path46.join)(paths.relativeDirPath, paths.relativeFilePath)}: ${formatError(error)}`,
|
|
6145
|
+
{ cause: error }
|
|
6146
|
+
);
|
|
6147
|
+
}
|
|
6148
|
+
const rulesyncJson = rulesyncMcp.getJson();
|
|
6149
|
+
const mcpServers = isMcpServers(rulesyncJson.mcpServers) ? rulesyncJson.mcpServers : {};
|
|
6065
6150
|
const transformedServers = convertEnvToCursorFormat(mcpServers);
|
|
6066
|
-
const cursorConfig = {
|
|
6067
|
-
mcpServers: transformedServers
|
|
6068
|
-
};
|
|
6069
|
-
const fileContent = JSON.stringify(cursorConfig, null, 2);
|
|
6151
|
+
const cursorConfig = { ...json, mcpServers: transformedServers };
|
|
6070
6152
|
return new _CursorMcp({
|
|
6071
6153
|
baseDir,
|
|
6072
|
-
relativeDirPath:
|
|
6073
|
-
relativeFilePath:
|
|
6074
|
-
fileContent,
|
|
6075
|
-
validate
|
|
6154
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6155
|
+
relativeFilePath: paths.relativeFilePath,
|
|
6156
|
+
fileContent: JSON.stringify(cursorConfig, null, 2),
|
|
6157
|
+
validate,
|
|
6158
|
+
global
|
|
6076
6159
|
});
|
|
6077
6160
|
}
|
|
6078
6161
|
toRulesyncMcp() {
|
|
@@ -6096,14 +6179,16 @@ var CursorMcp = class _CursorMcp extends ToolMcp {
|
|
|
6096
6179
|
static forDeletion({
|
|
6097
6180
|
baseDir = process.cwd(),
|
|
6098
6181
|
relativeDirPath,
|
|
6099
|
-
relativeFilePath
|
|
6182
|
+
relativeFilePath,
|
|
6183
|
+
global = false
|
|
6100
6184
|
}) {
|
|
6101
6185
|
return new _CursorMcp({
|
|
6102
6186
|
baseDir,
|
|
6103
6187
|
relativeDirPath,
|
|
6104
6188
|
relativeFilePath,
|
|
6105
6189
|
fileContent: "{}",
|
|
6106
|
-
validate: false
|
|
6190
|
+
validate: false,
|
|
6191
|
+
global
|
|
6107
6192
|
});
|
|
6108
6193
|
}
|
|
6109
6194
|
};
|
|
@@ -6932,7 +7017,7 @@ var toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
6932
7017
|
class: CursorMcp,
|
|
6933
7018
|
meta: {
|
|
6934
7019
|
supportsProject: true,
|
|
6935
|
-
supportsGlobal:
|
|
7020
|
+
supportsGlobal: true,
|
|
6936
7021
|
supportsEnabledTools: false,
|
|
6937
7022
|
supportsDisabledTools: false
|
|
6938
7023
|
}
|
|
@@ -7645,9 +7730,15 @@ var import_node_path59 = require("path");
|
|
|
7645
7730
|
var DirFeatureProcessor = class {
|
|
7646
7731
|
baseDir;
|
|
7647
7732
|
dryRun;
|
|
7648
|
-
|
|
7733
|
+
avoidBlockScalars;
|
|
7734
|
+
constructor({
|
|
7735
|
+
baseDir = process.cwd(),
|
|
7736
|
+
dryRun = false,
|
|
7737
|
+
avoidBlockScalars = false
|
|
7738
|
+
}) {
|
|
7649
7739
|
this.baseDir = baseDir;
|
|
7650
7740
|
this.dryRun = dryRun;
|
|
7741
|
+
this.avoidBlockScalars = avoidBlockScalars;
|
|
7651
7742
|
}
|
|
7652
7743
|
/**
|
|
7653
7744
|
* Return tool targets that this feature supports.
|
|
@@ -7673,7 +7764,9 @@ var DirFeatureProcessor = class {
|
|
|
7673
7764
|
let mainFileContent;
|
|
7674
7765
|
if (mainFile) {
|
|
7675
7766
|
const mainFilePath = (0, import_node_path59.join)(dirPath, mainFile.name);
|
|
7676
|
-
const content = stringifyFrontmatter(mainFile.body, mainFile.frontmatter
|
|
7767
|
+
const content = stringifyFrontmatter(mainFile.body, mainFile.frontmatter, {
|
|
7768
|
+
avoidBlockScalars: this.avoidBlockScalars
|
|
7769
|
+
});
|
|
7677
7770
|
mainFileContent = addTrailingNewline(content);
|
|
7678
7771
|
const existingContent = await readFileContentOrNull(mainFilePath);
|
|
7679
7772
|
if (existingContent !== mainFileContent) {
|
|
@@ -10412,7 +10505,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
10412
10505
|
getFactory = defaultGetFactory4,
|
|
10413
10506
|
dryRun = false
|
|
10414
10507
|
}) {
|
|
10415
|
-
super({ baseDir, dryRun });
|
|
10508
|
+
super({ baseDir, dryRun, avoidBlockScalars: toolTarget === "cursor" });
|
|
10416
10509
|
const result = SkillsProcessorToolTargetSchema.safeParse(toolTarget);
|
|
10417
10510
|
if (!result.success) {
|
|
10418
10511
|
throw new Error(
|
|
@@ -11493,7 +11586,7 @@ var CursorSubagent = class _CursorSubagent extends ToolSubagent {
|
|
|
11493
11586
|
...cursorSection
|
|
11494
11587
|
};
|
|
11495
11588
|
const body = rulesyncSubagent.getBody();
|
|
11496
|
-
const fileContent = stringifyFrontmatter(body, cursorFrontmatter);
|
|
11589
|
+
const fileContent = stringifyFrontmatter(body, cursorFrontmatter, { avoidBlockScalars: true });
|
|
11497
11590
|
const paths = this.getSettablePaths({ global });
|
|
11498
11591
|
return new _CursorSubagent({
|
|
11499
11592
|
baseDir,
|
|
@@ -16932,13 +17025,22 @@ var import_jsonc_parser2 = require("jsonc-parser");
|
|
|
16932
17025
|
// src/config/config.ts
|
|
16933
17026
|
var import_node_path117 = require("path");
|
|
16934
17027
|
var import_mini59 = require("zod/mini");
|
|
16935
|
-
|
|
17028
|
+
|
|
17029
|
+
// src/utils/validation.ts
|
|
17030
|
+
function findControlCharacter(value) {
|
|
16936
17031
|
for (let i = 0; i < value.length; i++) {
|
|
16937
17032
|
const code = value.charCodeAt(i);
|
|
16938
|
-
if (code >= 0 && code <= 31 || code === 127)
|
|
17033
|
+
if (code >= 0 && code <= 31 || code === 127) {
|
|
17034
|
+
return { position: i, hex: `0x${code.toString(16).padStart(2, "0")}` };
|
|
17035
|
+
}
|
|
16939
17036
|
}
|
|
16940
|
-
return
|
|
17037
|
+
return null;
|
|
16941
17038
|
}
|
|
17039
|
+
function hasControlCharacters(value) {
|
|
17040
|
+
return findControlCharacter(value) !== null;
|
|
17041
|
+
}
|
|
17042
|
+
|
|
17043
|
+
// src/config/config.ts
|
|
16942
17044
|
var SourceEntrySchema = import_mini59.z.object({
|
|
16943
17045
|
source: import_mini59.z.string().check((0, import_mini59.minLength)(1, "source must be a non-empty string")),
|
|
16944
17046
|
skills: (0, import_mini59.optional)(import_mini59.z.array(import_mini59.z.string())),
|
|
@@ -18458,17 +18560,8 @@ var import_node_path122 = require("path");
|
|
|
18458
18560
|
var import_node_util = require("util");
|
|
18459
18561
|
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
18460
18562
|
var GIT_TIMEOUT_MS = 6e4;
|
|
18461
|
-
var ALLOWED_URL_SCHEMES = /^(https?:\/\/|ssh:\/\/|git:\/\/|file
|
|
18563
|
+
var ALLOWED_URL_SCHEMES = /^(https?:\/\/|ssh:\/\/|git:\/\/|file:\/\/\/).+$|^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9.-]+:[a-zA-Z0-9_.+/~-]+$/;
|
|
18462
18564
|
var INSECURE_URL_SCHEMES = /^(git:\/\/|http:\/\/)/;
|
|
18463
|
-
function findControlCharacter(value) {
|
|
18464
|
-
for (let i = 0; i < value.length; i++) {
|
|
18465
|
-
const code = value.charCodeAt(i);
|
|
18466
|
-
if (code >= 0 && code <= 31 || code === 127) {
|
|
18467
|
-
return { position: i, hex: `0x${code.toString(16).padStart(2, "0")}` };
|
|
18468
|
-
}
|
|
18469
|
-
}
|
|
18470
|
-
return null;
|
|
18471
|
-
}
|
|
18472
18565
|
var GitClientError = class extends Error {
|
|
18473
18566
|
constructor(message, cause) {
|
|
18474
18567
|
super(message, { cause });
|
|
@@ -18524,6 +18617,7 @@ async function resolveDefaultRef(url) {
|
|
|
18524
18617
|
const ref = stdout.match(/^ref: refs\/heads\/(.+)\tHEAD$/m)?.[1];
|
|
18525
18618
|
const sha = stdout.match(/^([0-9a-f]{40})\tHEAD$/m)?.[1];
|
|
18526
18619
|
if (!ref || !sha) throw new GitClientError(`Could not parse default branch from: ${url}`);
|
|
18620
|
+
validateRef(ref);
|
|
18527
18621
|
return { ref, sha };
|
|
18528
18622
|
} catch (error) {
|
|
18529
18623
|
if (error instanceof GitClientError) throw error;
|
|
@@ -18550,6 +18644,17 @@ async function fetchSkillFiles(params) {
|
|
|
18550
18644
|
const { url, ref, skillsPath } = params;
|
|
18551
18645
|
validateGitUrl(url);
|
|
18552
18646
|
validateRef(ref);
|
|
18647
|
+
if (skillsPath.split(/[/\\]/).includes("..") || (0, import_node_path122.isAbsolute)(skillsPath)) {
|
|
18648
|
+
throw new GitClientError(
|
|
18649
|
+
`Invalid skillsPath "${skillsPath}": must be a relative path without ".."`
|
|
18650
|
+
);
|
|
18651
|
+
}
|
|
18652
|
+
const ctrl = findControlCharacter(skillsPath);
|
|
18653
|
+
if (ctrl) {
|
|
18654
|
+
throw new GitClientError(
|
|
18655
|
+
`skillsPath contains control character ${ctrl.hex} at position ${ctrl.position}`
|
|
18656
|
+
);
|
|
18657
|
+
}
|
|
18553
18658
|
await checkGitAvailable();
|
|
18554
18659
|
const tmpDir = await createTempDirectory("rulesync-git-");
|
|
18555
18660
|
try {
|
|
@@ -18584,7 +18689,9 @@ async function fetchSkillFiles(params) {
|
|
|
18584
18689
|
}
|
|
18585
18690
|
}
|
|
18586
18691
|
var MAX_WALK_DEPTH = 20;
|
|
18587
|
-
|
|
18692
|
+
var MAX_TOTAL_FILES = 1e4;
|
|
18693
|
+
var MAX_TOTAL_SIZE = 100 * 1024 * 1024;
|
|
18694
|
+
async function walkDirectory(dir, baseDir, depth = 0, ctx = { totalFiles: 0, totalSize: 0 }) {
|
|
18588
18695
|
if (depth > MAX_WALK_DEPTH) {
|
|
18589
18696
|
throw new GitClientError(
|
|
18590
18697
|
`Directory tree exceeds max depth of ${MAX_WALK_DEPTH}: "${dir}". Aborting to prevent resource exhaustion.`
|
|
@@ -18599,7 +18706,7 @@ async function walkDirectory(dir, baseDir, depth = 0) {
|
|
|
18599
18706
|
continue;
|
|
18600
18707
|
}
|
|
18601
18708
|
if (await directoryExists(fullPath)) {
|
|
18602
|
-
results.push(...await walkDirectory(fullPath, baseDir, depth + 1));
|
|
18709
|
+
results.push(...await walkDirectory(fullPath, baseDir, depth + 1, ctx));
|
|
18603
18710
|
} else {
|
|
18604
18711
|
const size = await getFileSize(fullPath);
|
|
18605
18712
|
if (size > MAX_FILE_SIZE) {
|
|
@@ -18608,8 +18715,20 @@ async function walkDirectory(dir, baseDir, depth = 0) {
|
|
|
18608
18715
|
);
|
|
18609
18716
|
continue;
|
|
18610
18717
|
}
|
|
18718
|
+
ctx.totalFiles++;
|
|
18719
|
+
ctx.totalSize += size;
|
|
18720
|
+
if (ctx.totalFiles >= MAX_TOTAL_FILES) {
|
|
18721
|
+
throw new GitClientError(
|
|
18722
|
+
`Repository exceeds max file count of ${MAX_TOTAL_FILES}. Aborting to prevent resource exhaustion.`
|
|
18723
|
+
);
|
|
18724
|
+
}
|
|
18725
|
+
if (ctx.totalSize >= MAX_TOTAL_SIZE) {
|
|
18726
|
+
throw new GitClientError(
|
|
18727
|
+
`Repository exceeds max total size of ${MAX_TOTAL_SIZE / 1024 / 1024}MB. Aborting to prevent resource exhaustion.`
|
|
18728
|
+
);
|
|
18729
|
+
}
|
|
18611
18730
|
const content = await readFileContent(fullPath);
|
|
18612
|
-
results.push({ relativePath:
|
|
18731
|
+
results.push({ relativePath: (0, import_node_path122.relative)(baseDir, fullPath), content, size });
|
|
18613
18732
|
}
|
|
18614
18733
|
}
|
|
18615
18734
|
return results;
|
|
@@ -18625,7 +18744,7 @@ var LockedSkillSchema = import_mini60.z.object({
|
|
|
18625
18744
|
});
|
|
18626
18745
|
var LockedSourceSchema = import_mini60.z.object({
|
|
18627
18746
|
requestedRef: (0, import_mini60.optional)(import_mini60.z.string()),
|
|
18628
|
-
resolvedRef: import_mini60.z.string(),
|
|
18747
|
+
resolvedRef: import_mini60.z.string().check((0, import_mini60.refine)((v) => /^[0-9a-f]{40}$/.test(v), "resolvedRef must be a 40-character hex SHA")),
|
|
18629
18748
|
resolvedAt: (0, import_mini60.optional)(import_mini60.z.string()),
|
|
18630
18749
|
skills: import_mini60.z.record(import_mini60.z.string(), LockedSkillSchema)
|
|
18631
18750
|
});
|
|
@@ -18828,6 +18947,8 @@ async function resolveAndFetchSources(params) {
|
|
|
18828
18947
|
logger.error(`Failed to fetch source "${sourceEntry.source}": ${formatError(error)}`);
|
|
18829
18948
|
if (error instanceof GitHubClientError) {
|
|
18830
18949
|
logGitHubAuthHints(error);
|
|
18950
|
+
} else if (error instanceof GitClientError) {
|
|
18951
|
+
logGitClientHints(error);
|
|
18831
18952
|
}
|
|
18832
18953
|
}
|
|
18833
18954
|
}
|
|
@@ -18848,6 +18969,13 @@ async function resolveAndFetchSources(params) {
|
|
|
18848
18969
|
}
|
|
18849
18970
|
return { fetchedSkillCount: totalSkillCount, sourcesProcessed: sources.length };
|
|
18850
18971
|
}
|
|
18972
|
+
function logGitClientHints(error) {
|
|
18973
|
+
if (error.message.includes("not installed")) {
|
|
18974
|
+
logger.info("Hint: Install git and ensure it is available on your PATH.");
|
|
18975
|
+
} else {
|
|
18976
|
+
logger.info("Hint: Check your git credentials (SSH keys, credential helper, or access token).");
|
|
18977
|
+
}
|
|
18978
|
+
}
|
|
18851
18979
|
async function checkLockedSkillsExist(curatedDir, skillNames) {
|
|
18852
18980
|
if (skillNames.length === 0) return true;
|
|
18853
18981
|
for (const name of skillNames) {
|
|
@@ -18857,9 +18985,88 @@ async function checkLockedSkillsExist(curatedDir, skillNames) {
|
|
|
18857
18985
|
}
|
|
18858
18986
|
return true;
|
|
18859
18987
|
}
|
|
18988
|
+
async function cleanPreviousCuratedSkills(curatedDir, lockedSkillNames) {
|
|
18989
|
+
const resolvedCuratedDir = (0, import_node_path124.resolve)(curatedDir);
|
|
18990
|
+
for (const prevSkill of lockedSkillNames) {
|
|
18991
|
+
const prevDir = (0, import_node_path124.join)(curatedDir, prevSkill);
|
|
18992
|
+
if (!(0, import_node_path124.resolve)(prevDir).startsWith(resolvedCuratedDir + import_node_path124.sep)) {
|
|
18993
|
+
logger.warn(
|
|
18994
|
+
`Skipping removal of "${prevSkill}": resolved path is outside the curated directory.`
|
|
18995
|
+
);
|
|
18996
|
+
continue;
|
|
18997
|
+
}
|
|
18998
|
+
if (await directoryExists(prevDir)) {
|
|
18999
|
+
await removeDirectory(prevDir);
|
|
19000
|
+
}
|
|
19001
|
+
}
|
|
19002
|
+
}
|
|
19003
|
+
function shouldSkipSkill(params) {
|
|
19004
|
+
const { skillName, sourceKey, localSkillNames, alreadyFetchedSkillNames } = params;
|
|
19005
|
+
if (skillName.includes("..") || skillName.includes("/") || skillName.includes("\\")) {
|
|
19006
|
+
logger.warn(
|
|
19007
|
+
`Skipping skill with invalid name "${skillName}" from ${sourceKey}: contains path traversal characters.`
|
|
19008
|
+
);
|
|
19009
|
+
return true;
|
|
19010
|
+
}
|
|
19011
|
+
if (localSkillNames.has(skillName)) {
|
|
19012
|
+
logger.debug(
|
|
19013
|
+
`Skipping remote skill "${skillName}" from ${sourceKey}: local skill takes precedence.`
|
|
19014
|
+
);
|
|
19015
|
+
return true;
|
|
19016
|
+
}
|
|
19017
|
+
if (alreadyFetchedSkillNames.has(skillName)) {
|
|
19018
|
+
logger.warn(
|
|
19019
|
+
`Skipping duplicate skill "${skillName}" from ${sourceKey}: already fetched from another source.`
|
|
19020
|
+
);
|
|
19021
|
+
return true;
|
|
19022
|
+
}
|
|
19023
|
+
return false;
|
|
19024
|
+
}
|
|
19025
|
+
async function writeSkillAndComputeIntegrity(params) {
|
|
19026
|
+
const { skillName, files, curatedDir, locked, resolvedSha, sourceKey } = params;
|
|
19027
|
+
const written = [];
|
|
19028
|
+
for (const file of files) {
|
|
19029
|
+
checkPathTraversal({
|
|
19030
|
+
relativePath: file.relativePath,
|
|
19031
|
+
intendedRootDir: (0, import_node_path124.join)(curatedDir, skillName)
|
|
19032
|
+
});
|
|
19033
|
+
await writeFileContent((0, import_node_path124.join)(curatedDir, skillName, file.relativePath), file.content);
|
|
19034
|
+
written.push({ path: file.relativePath, content: file.content });
|
|
19035
|
+
}
|
|
19036
|
+
const integrity = computeSkillIntegrity(written);
|
|
19037
|
+
const lockedSkillEntry = locked?.skills[skillName];
|
|
19038
|
+
if (lockedSkillEntry?.integrity && lockedSkillEntry.integrity !== integrity && resolvedSha === locked?.resolvedRef) {
|
|
19039
|
+
logger.warn(
|
|
19040
|
+
`Integrity mismatch for skill "${skillName}" from ${sourceKey}: expected "${lockedSkillEntry.integrity}", got "${integrity}". Content may have been tampered with.`
|
|
19041
|
+
);
|
|
19042
|
+
}
|
|
19043
|
+
return { integrity };
|
|
19044
|
+
}
|
|
19045
|
+
function buildLockUpdate(params) {
|
|
19046
|
+
const { lock, sourceKey, fetchedSkills, locked, requestedRef, resolvedSha } = params;
|
|
19047
|
+
const fetchedNames = Object.keys(fetchedSkills);
|
|
19048
|
+
const mergedSkills = { ...fetchedSkills };
|
|
19049
|
+
if (locked) {
|
|
19050
|
+
for (const [skillName, skillEntry] of Object.entries(locked.skills)) {
|
|
19051
|
+
if (!(skillName in mergedSkills)) {
|
|
19052
|
+
mergedSkills[skillName] = skillEntry;
|
|
19053
|
+
}
|
|
19054
|
+
}
|
|
19055
|
+
}
|
|
19056
|
+
const updatedLock = setLockedSource(lock, sourceKey, {
|
|
19057
|
+
requestedRef,
|
|
19058
|
+
resolvedRef: resolvedSha,
|
|
19059
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19060
|
+
skills: mergedSkills
|
|
19061
|
+
});
|
|
19062
|
+
logger.info(
|
|
19063
|
+
`Fetched ${fetchedNames.length} skill(s) from ${sourceKey}: ${fetchedNames.join(", ") || "(none)"}`
|
|
19064
|
+
);
|
|
19065
|
+
return { updatedLock, fetchedNames };
|
|
19066
|
+
}
|
|
18860
19067
|
async function fetchSource(params) {
|
|
18861
19068
|
const { sourceEntry, client, baseDir, localSkillNames, alreadyFetchedSkillNames, updateSources } = params;
|
|
18862
|
-
|
|
19069
|
+
const { lock } = params;
|
|
18863
19070
|
const parsed = parseSource(sourceEntry.source);
|
|
18864
19071
|
if (parsed.provider === "gitlab") {
|
|
18865
19072
|
logger.warn(`GitLab sources are not yet supported. Skipping "${sourceEntry.source}".`);
|
|
@@ -18912,37 +19119,15 @@ async function fetchSource(params) {
|
|
|
18912
19119
|
const semaphore = new import_promise2.Semaphore(FETCH_CONCURRENCY_LIMIT);
|
|
18913
19120
|
const fetchedSkills = {};
|
|
18914
19121
|
if (locked) {
|
|
18915
|
-
|
|
18916
|
-
for (const prevSkill of lockedSkillNames) {
|
|
18917
|
-
const prevDir = (0, import_node_path124.join)(curatedDir, prevSkill);
|
|
18918
|
-
if (!(0, import_node_path124.resolve)(prevDir).startsWith(resolvedCuratedDir + import_node_path124.sep)) {
|
|
18919
|
-
logger.warn(
|
|
18920
|
-
`Skipping removal of "${prevSkill}": resolved path is outside the curated directory.`
|
|
18921
|
-
);
|
|
18922
|
-
continue;
|
|
18923
|
-
}
|
|
18924
|
-
if (await directoryExists(prevDir)) {
|
|
18925
|
-
await removeDirectory(prevDir);
|
|
18926
|
-
}
|
|
18927
|
-
}
|
|
19122
|
+
await cleanPreviousCuratedSkills(curatedDir, lockedSkillNames);
|
|
18928
19123
|
}
|
|
18929
19124
|
for (const skillDir of filteredDirs) {
|
|
18930
|
-
if (
|
|
18931
|
-
|
|
18932
|
-
|
|
18933
|
-
|
|
18934
|
-
|
|
18935
|
-
}
|
|
18936
|
-
if (localSkillNames.has(skillDir.name)) {
|
|
18937
|
-
logger.debug(
|
|
18938
|
-
`Skipping remote skill "${skillDir.name}" from ${sourceKey}: local skill takes precedence.`
|
|
18939
|
-
);
|
|
18940
|
-
continue;
|
|
18941
|
-
}
|
|
18942
|
-
if (alreadyFetchedSkillNames.has(skillDir.name)) {
|
|
18943
|
-
logger.warn(
|
|
18944
|
-
`Skipping duplicate skill "${skillDir.name}" from ${sourceKey}: already fetched from another source.`
|
|
18945
|
-
);
|
|
19125
|
+
if (shouldSkipSkill({
|
|
19126
|
+
skillName: skillDir.name,
|
|
19127
|
+
sourceKey,
|
|
19128
|
+
localSkillNames,
|
|
19129
|
+
alreadyFetchedSkillNames
|
|
19130
|
+
})) {
|
|
18946
19131
|
continue;
|
|
18947
19132
|
}
|
|
18948
19133
|
const allFiles = await listDirectoryRecursive({
|
|
@@ -18965,55 +19150,39 @@ async function fetchSource(params) {
|
|
|
18965
19150
|
const skillFiles = [];
|
|
18966
19151
|
for (const file of files) {
|
|
18967
19152
|
const relativeToSkill = file.path.substring(skillDir.path.length + 1);
|
|
18968
|
-
const localFilePath = (0, import_node_path124.join)(curatedDir, skillDir.name, relativeToSkill);
|
|
18969
|
-
checkPathTraversal({
|
|
18970
|
-
relativePath: relativeToSkill,
|
|
18971
|
-
intendedRootDir: (0, import_node_path124.join)(curatedDir, skillDir.name)
|
|
18972
|
-
});
|
|
18973
19153
|
const content = await withSemaphore(
|
|
18974
19154
|
semaphore,
|
|
18975
19155
|
() => client.getFileContent(parsed.owner, parsed.repo, file.path, ref)
|
|
18976
19156
|
);
|
|
18977
|
-
|
|
18978
|
-
skillFiles.push({ path: relativeToSkill, content });
|
|
19157
|
+
skillFiles.push({ relativePath: relativeToSkill, content });
|
|
18979
19158
|
}
|
|
18980
|
-
|
|
18981
|
-
|
|
18982
|
-
|
|
18983
|
-
|
|
18984
|
-
|
|
18985
|
-
|
|
18986
|
-
|
|
18987
|
-
|
|
19159
|
+
fetchedSkills[skillDir.name] = await writeSkillAndComputeIntegrity({
|
|
19160
|
+
skillName: skillDir.name,
|
|
19161
|
+
files: skillFiles,
|
|
19162
|
+
curatedDir,
|
|
19163
|
+
locked,
|
|
19164
|
+
resolvedSha,
|
|
19165
|
+
sourceKey
|
|
19166
|
+
});
|
|
18988
19167
|
logger.debug(`Fetched skill "${skillDir.name}" from ${sourceKey}`);
|
|
18989
19168
|
}
|
|
18990
|
-
const
|
|
18991
|
-
|
|
18992
|
-
|
|
18993
|
-
|
|
18994
|
-
|
|
18995
|
-
mergedSkills[skillName] = skillEntry;
|
|
18996
|
-
}
|
|
18997
|
-
}
|
|
18998
|
-
}
|
|
18999
|
-
lock = setLockedSource(lock, sourceKey, {
|
|
19169
|
+
const result = buildLockUpdate({
|
|
19170
|
+
lock,
|
|
19171
|
+
sourceKey,
|
|
19172
|
+
fetchedSkills,
|
|
19173
|
+
locked,
|
|
19000
19174
|
requestedRef,
|
|
19001
|
-
|
|
19002
|
-
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19003
|
-
skills: mergedSkills
|
|
19175
|
+
resolvedSha
|
|
19004
19176
|
});
|
|
19005
|
-
logger.info(
|
|
19006
|
-
`Fetched ${fetchedNames.length} skill(s) from ${sourceKey}: ${fetchedNames.join(", ") || "(none)"}`
|
|
19007
|
-
);
|
|
19008
19177
|
return {
|
|
19009
|
-
skillCount: fetchedNames.length,
|
|
19010
|
-
fetchedSkillNames: fetchedNames,
|
|
19011
|
-
updatedLock:
|
|
19178
|
+
skillCount: result.fetchedNames.length,
|
|
19179
|
+
fetchedSkillNames: result.fetchedNames,
|
|
19180
|
+
updatedLock: result.updatedLock
|
|
19012
19181
|
};
|
|
19013
19182
|
}
|
|
19014
19183
|
async function fetchSourceViaGit(params) {
|
|
19015
19184
|
const { sourceEntry, baseDir, localSkillNames, alreadyFetchedSkillNames, updateSources, frozen } = params;
|
|
19016
|
-
|
|
19185
|
+
const { lock } = params;
|
|
19017
19186
|
const url = sourceEntry.source;
|
|
19018
19187
|
const locked = getLockedSource(lock, url);
|
|
19019
19188
|
const lockedSkillNames = locked ? getLockedSkillNames(locked) : [];
|
|
@@ -19069,68 +19238,35 @@ async function fetchSourceViaGit(params) {
|
|
|
19069
19238
|
const allNames = [...skillFileMap.keys()];
|
|
19070
19239
|
const filteredNames = isWildcard ? allNames : allNames.filter((n) => skillFilter.includes(n));
|
|
19071
19240
|
if (locked) {
|
|
19072
|
-
|
|
19073
|
-
for (const prev of lockedSkillNames) {
|
|
19074
|
-
const dir = (0, import_node_path124.join)(curatedDir, prev);
|
|
19075
|
-
if ((0, import_node_path124.resolve)(dir).startsWith(base + import_node_path124.sep) && await directoryExists(dir)) {
|
|
19076
|
-
await removeDirectory(dir);
|
|
19077
|
-
}
|
|
19078
|
-
}
|
|
19241
|
+
await cleanPreviousCuratedSkills(curatedDir, lockedSkillNames);
|
|
19079
19242
|
}
|
|
19080
19243
|
const fetchedSkills = {};
|
|
19081
19244
|
for (const skillName of filteredNames) {
|
|
19082
|
-
if (
|
|
19083
|
-
logger.warn(
|
|
19084
|
-
`Skipping skill with invalid name "${skillName}" from ${url}: contains path traversal characters.`
|
|
19085
|
-
);
|
|
19245
|
+
if (shouldSkipSkill({ skillName, sourceKey: url, localSkillNames, alreadyFetchedSkillNames })) {
|
|
19086
19246
|
continue;
|
|
19087
19247
|
}
|
|
19088
|
-
|
|
19089
|
-
|
|
19090
|
-
|
|
19091
|
-
|
|
19092
|
-
|
|
19093
|
-
|
|
19094
|
-
|
|
19095
|
-
|
|
19096
|
-
`Skipping duplicate skill "${skillName}" from ${url}: already fetched from another source.`
|
|
19097
|
-
);
|
|
19098
|
-
continue;
|
|
19099
|
-
}
|
|
19100
|
-
const files = skillFileMap.get(skillName) ?? [];
|
|
19101
|
-
const written = [];
|
|
19102
|
-
for (const file of files) {
|
|
19103
|
-
checkPathTraversal({
|
|
19104
|
-
relativePath: file.relativePath,
|
|
19105
|
-
intendedRootDir: (0, import_node_path124.join)(curatedDir, skillName)
|
|
19106
|
-
});
|
|
19107
|
-
await writeFileContent((0, import_node_path124.join)(curatedDir, skillName, file.relativePath), file.content);
|
|
19108
|
-
written.push({ path: file.relativePath, content: file.content });
|
|
19109
|
-
}
|
|
19110
|
-
const integrity = computeSkillIntegrity(written);
|
|
19111
|
-
const lockedSkillEntry = locked?.skills[skillName];
|
|
19112
|
-
if (lockedSkillEntry?.integrity && lockedSkillEntry.integrity !== integrity && resolvedSha === locked?.resolvedRef) {
|
|
19113
|
-
logger.warn(`Integrity mismatch for skill "${skillName}" from ${url}.`);
|
|
19114
|
-
}
|
|
19115
|
-
fetchedSkills[skillName] = { integrity };
|
|
19116
|
-
}
|
|
19117
|
-
const fetchedNames = Object.keys(fetchedSkills);
|
|
19118
|
-
const mergedSkills = { ...fetchedSkills };
|
|
19119
|
-
if (locked) {
|
|
19120
|
-
for (const [k, v] of Object.entries(locked.skills)) {
|
|
19121
|
-
if (!(k in mergedSkills)) mergedSkills[k] = v;
|
|
19122
|
-
}
|
|
19248
|
+
fetchedSkills[skillName] = await writeSkillAndComputeIntegrity({
|
|
19249
|
+
skillName,
|
|
19250
|
+
files: skillFileMap.get(skillName) ?? [],
|
|
19251
|
+
curatedDir,
|
|
19252
|
+
locked,
|
|
19253
|
+
resolvedSha,
|
|
19254
|
+
sourceKey: url
|
|
19255
|
+
});
|
|
19123
19256
|
}
|
|
19124
|
-
|
|
19257
|
+
const result = buildLockUpdate({
|
|
19258
|
+
lock,
|
|
19259
|
+
sourceKey: url,
|
|
19260
|
+
fetchedSkills,
|
|
19261
|
+
locked,
|
|
19125
19262
|
requestedRef,
|
|
19126
|
-
|
|
19127
|
-
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19128
|
-
skills: mergedSkills
|
|
19263
|
+
resolvedSha
|
|
19129
19264
|
});
|
|
19130
|
-
|
|
19131
|
-
|
|
19132
|
-
|
|
19133
|
-
|
|
19265
|
+
return {
|
|
19266
|
+
skillCount: result.fetchedNames.length,
|
|
19267
|
+
fetchedSkillNames: result.fetchedNames,
|
|
19268
|
+
updatedLock: result.updatedLock
|
|
19269
|
+
};
|
|
19134
19270
|
}
|
|
19135
19271
|
|
|
19136
19272
|
// src/cli/commands/install.ts
|
|
@@ -20946,7 +21082,7 @@ async function updateCommand(currentVersion, options) {
|
|
|
20946
21082
|
}
|
|
20947
21083
|
|
|
20948
21084
|
// src/cli/index.ts
|
|
20949
|
-
var getVersion = () => "7.
|
|
21085
|
+
var getVersion = () => "7.16.0";
|
|
20950
21086
|
var main = async () => {
|
|
20951
21087
|
const program = new import_commander.Command();
|
|
20952
21088
|
const version = getVersion();
|