agentinit 1.20.0 → 1.20.1
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/CHANGELOG.md +8 -0
- package/README.md +17 -3
- package/dist/cli.js +863 -293
- package/dist/commands/lock.d.ts.map +1 -1
- package/dist/commands/lock.js +2 -14
- package/dist/commands/lock.js.map +1 -1
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +79 -37
- package/dist/commands/skills.js.map +1 -1
- package/dist/core/skillSecurityScanner.d.ts +28 -0
- package/dist/core/skillSecurityScanner.d.ts.map +1 -0
- package/dist/core/skillSecurityScanner.js +167 -0
- package/dist/core/skillSecurityScanner.js.map +1 -0
- package/dist/core/skillsManager.d.ts +11 -0
- package/dist/core/skillsManager.d.ts.map +1 -1
- package/dist/core/skillsManager.js +286 -10
- package/dist/core/skillsManager.js.map +1 -1
- package/dist/types/lockfile.d.ts +2 -1
- package/dist/types/lockfile.d.ts.map +1 -1
- package/dist/types/plugins.d.ts +1 -1
- package/dist/types/plugins.d.ts.map +1 -1
- package/dist/types/skills.d.ts +4 -1
- package/dist/types/skills.d.ts.map +1 -1
- package/dist/utils/lockSource.d.ts +9 -0
- package/dist/utils/lockSource.d.ts.map +1 -0
- package/dist/utils/lockSource.js +59 -0
- package/dist/utils/lockSource.js.map +1 -0
- package/dist/utils/promptUtils.d.ts +13 -2
- package/dist/utils/promptUtils.d.ts.map +1 -1
- package/dist/utils/promptUtils.js +61 -3
- package/dist/utils/promptUtils.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { join, relative } from 'path';
|
|
3
|
+
const SCAN_RULES = [
|
|
4
|
+
{
|
|
5
|
+
id: 'AI001',
|
|
6
|
+
title: 'Prompt override language',
|
|
7
|
+
severity: 'medium',
|
|
8
|
+
regex: /\b(ignore|disregard|override|bypass)\b.{0,40}\b(previous|prior|system|safety|guardrails?|instructions?)\b/i,
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
id: 'AI002',
|
|
12
|
+
title: 'Secret or credential exfiltration',
|
|
13
|
+
severity: 'high',
|
|
14
|
+
regex: /\b(exfiltrat\w*|upload|send|post|curl|wget)\b.{0,80}\b(secret|token|key|credential|cookie|session|\.ssh|id_rsa|env(?:ironment)? variables?)\b/i,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'AI003',
|
|
18
|
+
title: 'Destructive shell command',
|
|
19
|
+
severity: 'high',
|
|
20
|
+
regex: /\b(rm\s+-rf\s+\/|sudo\s+rm\s+-rf|mkfs\b|dd\s+if=\/dev\/zero|chmod\s+-R\s+777\s+\/)\b/i,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'AI004',
|
|
24
|
+
title: 'Remote shell execution pipeline',
|
|
25
|
+
severity: 'high',
|
|
26
|
+
regex: /\b(curl|wget)\b[^\n|]{0,160}\|\s*(sh|bash|zsh)\b/i,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'AI005',
|
|
30
|
+
title: 'Hardcoded credential material',
|
|
31
|
+
severity: 'low',
|
|
32
|
+
regex: /\b(api[_-]?key|token|secret|password)\b\s*[:=]\s*['"][^'"]{8,}['"]/i,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'AI006',
|
|
36
|
+
title: 'Unicode bidi control characters',
|
|
37
|
+
severity: 'low',
|
|
38
|
+
regex: /[\u202A-\u202E\u2066-\u2069]/,
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
const MAX_TEXT_FILE_BYTES = 1024 * 1024;
|
|
42
|
+
const SCANNED_TEXT_EXTENSIONS = new Set([
|
|
43
|
+
'',
|
|
44
|
+
'.md',
|
|
45
|
+
'.txt',
|
|
46
|
+
'.json',
|
|
47
|
+
'.yaml',
|
|
48
|
+
'.yml',
|
|
49
|
+
'.toml',
|
|
50
|
+
'.js',
|
|
51
|
+
'.ts',
|
|
52
|
+
'.mjs',
|
|
53
|
+
'.cjs',
|
|
54
|
+
'.sh',
|
|
55
|
+
'.bash',
|
|
56
|
+
'.zsh',
|
|
57
|
+
'.py',
|
|
58
|
+
'.ps1',
|
|
59
|
+
]);
|
|
60
|
+
const EXECUTABLE_TEXT_EXTENSIONS = new Set([
|
|
61
|
+
'.js',
|
|
62
|
+
'.ts',
|
|
63
|
+
'.mjs',
|
|
64
|
+
'.cjs',
|
|
65
|
+
'.sh',
|
|
66
|
+
'.bash',
|
|
67
|
+
'.zsh',
|
|
68
|
+
'.py',
|
|
69
|
+
'.ps1',
|
|
70
|
+
]);
|
|
71
|
+
export class SkillSecurityScanner {
|
|
72
|
+
async scanSkill(skill) {
|
|
73
|
+
const findings = skill.generatedContent
|
|
74
|
+
? this.scanText(skill.generatedContent, 'SKILL.md')
|
|
75
|
+
: await this.scanDirectory(skill.path);
|
|
76
|
+
const stats = findings.reduce((acc, finding) => {
|
|
77
|
+
acc[finding.severity] += 1;
|
|
78
|
+
return acc;
|
|
79
|
+
}, { high: 0, medium: 0, low: 0 });
|
|
80
|
+
return {
|
|
81
|
+
blocked: findings.some(finding => finding.blocking),
|
|
82
|
+
findings,
|
|
83
|
+
stats,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
formatShortSummary(result) {
|
|
87
|
+
const parts = [
|
|
88
|
+
result.stats.high ? `${result.stats.high} high` : null,
|
|
89
|
+
result.stats.medium ? `${result.stats.medium} medium` : null,
|
|
90
|
+
result.stats.low ? `${result.stats.low} low` : null,
|
|
91
|
+
].filter((value) => !!value);
|
|
92
|
+
return parts.length > 0 ? parts.join(', ') : 'no findings';
|
|
93
|
+
}
|
|
94
|
+
formatBlockingReason(result) {
|
|
95
|
+
const finding = result.findings.find(entry => entry.blocking)
|
|
96
|
+
|| result.findings.find(entry => entry.severity === 'high')
|
|
97
|
+
|| result.findings[0];
|
|
98
|
+
if (!finding) {
|
|
99
|
+
return 'Security scan failed';
|
|
100
|
+
}
|
|
101
|
+
return `Security scan failed: ${finding.title} (${finding.ruleId}) at ${finding.filePath}:${finding.line}`;
|
|
102
|
+
}
|
|
103
|
+
async scanDirectory(rootPath) {
|
|
104
|
+
const findings = [];
|
|
105
|
+
await this.walk(rootPath, rootPath, findings);
|
|
106
|
+
return findings;
|
|
107
|
+
}
|
|
108
|
+
async walk(rootPath, currentPath, findings) {
|
|
109
|
+
const entries = await fs.readdir(currentPath, { withFileTypes: true });
|
|
110
|
+
for (const entry of entries) {
|
|
111
|
+
if (entry.name === '.git' || entry.name === 'node_modules') {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const entryPath = join(currentPath, entry.name);
|
|
115
|
+
if (entry.isDirectory()) {
|
|
116
|
+
await this.walk(rootPath, entryPath, findings);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const relativePath = relative(rootPath, entryPath).replace(/\\/g, '/');
|
|
120
|
+
const content = await this.readTextFile(entryPath);
|
|
121
|
+
if (content === null) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
findings.push(...this.scanText(content, relativePath, {
|
|
125
|
+
blockHighRisk: this.isExecutableTextFile(entryPath, content),
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async readTextFile(filePath) {
|
|
130
|
+
const extension = filePath.includes('.') ? filePath.slice(filePath.lastIndexOf('.')).toLowerCase() : '';
|
|
131
|
+
if (!SCANNED_TEXT_EXTENSIONS.has(extension)) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
const buffer = await fs.readFile(filePath);
|
|
135
|
+
if (buffer.length > MAX_TEXT_FILE_BYTES || buffer.includes(0)) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
return buffer.toString('utf8');
|
|
139
|
+
}
|
|
140
|
+
isExecutableTextFile(filePath, content) {
|
|
141
|
+
const extension = filePath.includes('.') ? filePath.slice(filePath.lastIndexOf('.')).toLowerCase() : '';
|
|
142
|
+
return EXECUTABLE_TEXT_EXTENSIONS.has(extension) || content.startsWith('#!');
|
|
143
|
+
}
|
|
144
|
+
scanText(content, filePath, options = {}) {
|
|
145
|
+
const findings = [];
|
|
146
|
+
const lines = content.split(/\r?\n/);
|
|
147
|
+
lines.forEach((line, index) => {
|
|
148
|
+
for (const rule of SCAN_RULES) {
|
|
149
|
+
rule.regex.lastIndex = 0;
|
|
150
|
+
if (!rule.regex.test(line)) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
findings.push({
|
|
154
|
+
ruleId: rule.id,
|
|
155
|
+
title: rule.title,
|
|
156
|
+
severity: rule.severity,
|
|
157
|
+
filePath,
|
|
158
|
+
line: index + 1,
|
|
159
|
+
snippet: line.trim().slice(0, 160),
|
|
160
|
+
blocking: rule.severity === 'high' && options.blockHighRisk === true,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
return findings;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=skillSecurityScanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillSecurityScanner.js","sourceRoot":"","sources":["../../src/core/skillSecurityScanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ import type { SkillInfo, InstalledSkill, SkillInstallResult, SkillsAddOptions, S
|
|
|
4
4
|
export declare class SkillsManager {
|
|
5
5
|
private agentManager;
|
|
6
6
|
private preparedSourceContexts;
|
|
7
|
+
private readonly skillScanner;
|
|
7
8
|
constructor(agentManager?: AgentManager);
|
|
8
9
|
/**
|
|
9
10
|
* Parse a source string into a structured SkillSource
|
|
@@ -14,7 +15,13 @@ export declare class SkillsManager {
|
|
|
14
15
|
private isImplicitCatalogSkillSource;
|
|
15
16
|
private resolveSourceRequest;
|
|
16
17
|
private parseGitHubHttpSource;
|
|
18
|
+
private parseGitLabHttpSource;
|
|
19
|
+
private parseBitbucketHttpSource;
|
|
20
|
+
private parseHttpRepositorySource;
|
|
21
|
+
private parseSshRepositorySource;
|
|
17
22
|
private parseGitHubShorthandSource;
|
|
23
|
+
private parseGitLabShorthandSource;
|
|
24
|
+
private parseBitbucketShorthandSource;
|
|
18
25
|
/**
|
|
19
26
|
* Parse a SKILL.md file and extract name + description from frontmatter
|
|
20
27
|
*/
|
|
@@ -95,6 +102,10 @@ export declare class SkillsManager {
|
|
|
95
102
|
global?: boolean;
|
|
96
103
|
}): Promise<SkillInstallResult>;
|
|
97
104
|
private getCanonicalInstallPlan;
|
|
105
|
+
private normalizeSkillPrefix;
|
|
106
|
+
private withSkillPrefix;
|
|
107
|
+
private rewriteSkillFileName;
|
|
108
|
+
private applyPrefixToSkills;
|
|
98
109
|
private normalizeSkillName;
|
|
99
110
|
private resolveInstallPath;
|
|
100
111
|
getInstallPath(skillName: string, targetDir: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skillsManager.d.ts","sourceRoot":"","sources":["../../src/core/skillsManager.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"skillsManager.d.ts","sourceRoot":"","sources":["../../src/core/skillsManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,7 @@ import { createRelativeSymlink, fileExists, isDirectory, listFiles, readFileIfEx
|
|
|
9
9
|
import { expandTilde } from '../utils/paths.js';
|
|
10
10
|
import { AgentManager } from './agentManager.js';
|
|
11
11
|
import { getConfiguredDefaultMarketplaceId, getMarketplace, getMarketplaceIds } from './marketplaceRegistry.js';
|
|
12
|
+
import { SkillSecurityScanner } from './skillSecurityScanner.js';
|
|
12
13
|
import { SHARED_SKILLS_TARGET_ID } from '../types/skills.js';
|
|
13
14
|
import { InstallLock, hashDirectory, logLockWriteWarning } from './installLock.js';
|
|
14
15
|
const execFileAsync = promisify(execFile);
|
|
@@ -33,6 +34,7 @@ const SKILL_SEARCH_DIRS = [
|
|
|
33
34
|
export class SkillsManager {
|
|
34
35
|
agentManager;
|
|
35
36
|
preparedSourceContexts = new Map();
|
|
37
|
+
skillScanner = new SkillSecurityScanner();
|
|
36
38
|
constructor(agentManager) {
|
|
37
39
|
this.agentManager = agentManager || new AgentManager();
|
|
38
40
|
}
|
|
@@ -45,12 +47,16 @@ export class SkillsManager {
|
|
|
45
47
|
return { type: 'local', path: source };
|
|
46
48
|
}
|
|
47
49
|
// Full GitHub URL
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
50
|
-
return
|
|
50
|
+
const httpSource = this.parseHttpRepositorySource(source);
|
|
51
|
+
if (httpSource) {
|
|
52
|
+
return httpSource;
|
|
51
53
|
}
|
|
52
54
|
// Full git URL
|
|
53
|
-
|
|
55
|
+
const sshSource = this.parseSshRepositorySource(source);
|
|
56
|
+
if (sshSource) {
|
|
57
|
+
return sshSource;
|
|
58
|
+
}
|
|
59
|
+
if (source.endsWith('.git')) {
|
|
54
60
|
return { type: 'github', url: source };
|
|
55
61
|
}
|
|
56
62
|
if (options?.from) {
|
|
@@ -63,6 +69,14 @@ export class SkillsManager {
|
|
|
63
69
|
pluginName: source,
|
|
64
70
|
};
|
|
65
71
|
}
|
|
72
|
+
const gitLabShorthandSource = this.parseGitLabShorthandSource(source);
|
|
73
|
+
if (gitLabShorthandSource) {
|
|
74
|
+
return gitLabShorthandSource;
|
|
75
|
+
}
|
|
76
|
+
const bitbucketShorthandSource = this.parseBitbucketShorthandSource(source);
|
|
77
|
+
if (bitbucketShorthandSource) {
|
|
78
|
+
return bitbucketShorthandSource;
|
|
79
|
+
}
|
|
66
80
|
const githubShorthandSource = this.parseGitHubShorthandSource(source);
|
|
67
81
|
if (githubShorthandSource?.subpath) {
|
|
68
82
|
return githubShorthandSource;
|
|
@@ -163,6 +177,110 @@ export class SkillsManager {
|
|
|
163
177
|
return null;
|
|
164
178
|
}
|
|
165
179
|
}
|
|
180
|
+
parseGitLabHttpSource(source) {
|
|
181
|
+
if (!source.startsWith('https://gitlab.com/') && !source.startsWith('http://gitlab.com/')) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
const parsedUrl = new URL(source);
|
|
186
|
+
const segments = parsedUrl.pathname.replace(/\/+$/, '').split('/').filter(Boolean);
|
|
187
|
+
const dashIndex = segments.indexOf('-');
|
|
188
|
+
const repoBoundary = dashIndex >= 0 ? dashIndex : segments.length;
|
|
189
|
+
if (repoBoundary < 2) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const repo = segments[repoBoundary - 1];
|
|
193
|
+
const owner = segments.slice(0, repoBoundary - 1).join('/');
|
|
194
|
+
if (!owner || !repo) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
let subpath;
|
|
198
|
+
if (dashIndex >= 0) {
|
|
199
|
+
const marker = segments[dashIndex + 1];
|
|
200
|
+
if ((marker === 'tree' || marker === 'blob') && segments.length > dashIndex + 3) {
|
|
201
|
+
subpath = segments.slice(dashIndex + 3).join('/');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
type: 'gitlab',
|
|
206
|
+
url: `https://gitlab.com/${owner}/${repo}.git`,
|
|
207
|
+
owner,
|
|
208
|
+
repo,
|
|
209
|
+
...(subpath ? { subpath } : {}),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
parseBitbucketHttpSource(source) {
|
|
217
|
+
if (!source.startsWith('https://bitbucket.org/') && !source.startsWith('http://bitbucket.org/')) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
const parsedUrl = new URL(source);
|
|
222
|
+
const segments = parsedUrl.pathname.replace(/\/+$/, '').split('/').filter(Boolean);
|
|
223
|
+
if (segments.length < 2) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
const [owner, repo, marker, _commitish, ...rest] = segments;
|
|
227
|
+
if (!owner || !repo) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
let subpath;
|
|
231
|
+
if (marker === 'src' && rest.length > 0) {
|
|
232
|
+
subpath = rest.join('/');
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
type: 'bitbucket',
|
|
236
|
+
url: `https://bitbucket.org/${owner}/${repo}.git`,
|
|
237
|
+
owner,
|
|
238
|
+
repo,
|
|
239
|
+
...(subpath ? { subpath } : {}),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
parseHttpRepositorySource(source) {
|
|
247
|
+
return this.parseGitHubHttpSource(source)
|
|
248
|
+
|| this.parseGitLabHttpSource(source)
|
|
249
|
+
|| this.parseBitbucketHttpSource(source);
|
|
250
|
+
}
|
|
251
|
+
parseSshRepositorySource(source) {
|
|
252
|
+
const githubMatch = source.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
253
|
+
if (githubMatch) {
|
|
254
|
+
const [, owner, repo] = githubMatch;
|
|
255
|
+
return {
|
|
256
|
+
type: 'github',
|
|
257
|
+
url: `git@github.com:${owner}/${repo}.git`,
|
|
258
|
+
owner,
|
|
259
|
+
repo,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
const gitlabMatch = source.match(/^git@gitlab\.com:(.+)\/([^/]+?)(?:\.git)?$/);
|
|
263
|
+
if (gitlabMatch) {
|
|
264
|
+
const [, owner, repo] = gitlabMatch;
|
|
265
|
+
return {
|
|
266
|
+
type: 'gitlab',
|
|
267
|
+
url: `git@gitlab.com:${owner}/${repo}.git`,
|
|
268
|
+
owner,
|
|
269
|
+
repo,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const bitbucketMatch = source.match(/^git@bitbucket\.org:([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
273
|
+
if (bitbucketMatch) {
|
|
274
|
+
const [, owner, repo] = bitbucketMatch;
|
|
275
|
+
return {
|
|
276
|
+
type: 'bitbucket',
|
|
277
|
+
url: `git@bitbucket.org:${owner}/${repo}.git`,
|
|
278
|
+
owner,
|
|
279
|
+
repo,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
166
284
|
parseGitHubShorthandSource(source) {
|
|
167
285
|
const githubShorthandMatch = source.match(/^([a-zA-Z0-9._-]+)\/([a-zA-Z0-9._-]+)(?:\/(.+))?$/);
|
|
168
286
|
if (!githubShorthandMatch) {
|
|
@@ -177,6 +295,58 @@ export class SkillsManager {
|
|
|
177
295
|
...(subpath ? { subpath } : {}),
|
|
178
296
|
};
|
|
179
297
|
}
|
|
298
|
+
parseGitLabShorthandSource(source) {
|
|
299
|
+
const normalized = source.startsWith('gitlab:')
|
|
300
|
+
? source.slice('gitlab:'.length)
|
|
301
|
+
: source.startsWith('gitlab.com/')
|
|
302
|
+
? source.slice('gitlab.com/'.length)
|
|
303
|
+
: null;
|
|
304
|
+
if (!normalized) {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
const [repoSpec = normalized, subpathSpec] = normalized.split('//', 2);
|
|
308
|
+
const segments = repoSpec.split('/').filter(Boolean);
|
|
309
|
+
if (segments.length < 2) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
const repo = segments[segments.length - 1];
|
|
313
|
+
const owner = segments.slice(0, segments.length - 1).join('/');
|
|
314
|
+
if (!owner || !repo) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
type: 'gitlab',
|
|
319
|
+
url: `https://gitlab.com/${owner}/${repo}.git`,
|
|
320
|
+
owner,
|
|
321
|
+
repo,
|
|
322
|
+
...(subpathSpec ? { subpath: subpathSpec } : {}),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
parseBitbucketShorthandSource(source) {
|
|
326
|
+
const normalized = source.startsWith('bitbucket:')
|
|
327
|
+
? source.slice('bitbucket:'.length)
|
|
328
|
+
: source.startsWith('bitbucket.org/')
|
|
329
|
+
? source.slice('bitbucket.org/'.length)
|
|
330
|
+
: null;
|
|
331
|
+
if (!normalized) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
const segments = normalized.split('/').filter(Boolean);
|
|
335
|
+
if (segments.length < 2) {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
const [owner, repo, ...rest] = segments;
|
|
339
|
+
if (!owner || !repo) {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
type: 'bitbucket',
|
|
344
|
+
url: `https://bitbucket.org/${owner}/${repo}.git`,
|
|
345
|
+
owner,
|
|
346
|
+
repo,
|
|
347
|
+
...(rest.length > 0 ? { subpath: rest.join('/') } : {}),
|
|
348
|
+
};
|
|
349
|
+
}
|
|
180
350
|
/**
|
|
181
351
|
* Parse a SKILL.md file and extract name + description from frontmatter
|
|
182
352
|
*/
|
|
@@ -324,7 +494,7 @@ export class SkillsManager {
|
|
|
324
494
|
}
|
|
325
495
|
async resolveDiscoveryRoot(repoPath, source, sourceLabel) {
|
|
326
496
|
const resolvedRepoPath = resolve(repoPath);
|
|
327
|
-
if (source.type !== 'github' || !source.subpath) {
|
|
497
|
+
if ((source.type !== 'github' && source.type !== 'gitlab' && source.type !== 'bitbucket') || !source.subpath) {
|
|
328
498
|
return resolvedRepoPath;
|
|
329
499
|
}
|
|
330
500
|
const discoveryRoot = resolve(resolvedRepoPath, source.subpath);
|
|
@@ -369,7 +539,7 @@ export class SkillsManager {
|
|
|
369
539
|
};
|
|
370
540
|
}
|
|
371
541
|
let repoPath;
|
|
372
|
-
if (resolved.type === 'github') {
|
|
542
|
+
if (resolved.type === 'github' || resolved.type === 'gitlab' || resolved.type === 'bitbucket') {
|
|
373
543
|
if (!resolved.url) {
|
|
374
544
|
throw new Error(`Invalid source: ${source}`);
|
|
375
545
|
}
|
|
@@ -627,6 +797,76 @@ export class SkillsManager {
|
|
|
627
797
|
mode: 'symlink',
|
|
628
798
|
};
|
|
629
799
|
}
|
|
800
|
+
normalizeSkillPrefix(prefix) {
|
|
801
|
+
const normalized = prefix?.trim() ?? '';
|
|
802
|
+
if (normalized.includes('/') || normalized.includes('\\')) {
|
|
803
|
+
throw new Error(`Invalid skill prefix: ${prefix}`);
|
|
804
|
+
}
|
|
805
|
+
return normalized;
|
|
806
|
+
}
|
|
807
|
+
withSkillPrefix(skillName, prefix) {
|
|
808
|
+
const normalizedPrefix = this.normalizeSkillPrefix(prefix);
|
|
809
|
+
return normalizedPrefix ? `${normalizedPrefix}${skillName}` : skillName;
|
|
810
|
+
}
|
|
811
|
+
async rewriteSkillFileName(filePath, skillName) {
|
|
812
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
813
|
+
const parsed = matter(content);
|
|
814
|
+
const nextContent = matter.stringify(parsed.content, {
|
|
815
|
+
...parsed.data,
|
|
816
|
+
name: skillName,
|
|
817
|
+
});
|
|
818
|
+
await fs.writeFile(filePath, nextContent, 'utf8');
|
|
819
|
+
}
|
|
820
|
+
async applyPrefixToSkills(skills, prefix) {
|
|
821
|
+
const normalizedPrefix = this.normalizeSkillPrefix(prefix);
|
|
822
|
+
if (!normalizedPrefix) {
|
|
823
|
+
return { skills, cleanup: async () => { } };
|
|
824
|
+
}
|
|
825
|
+
const tempDirs = [];
|
|
826
|
+
try {
|
|
827
|
+
const prefixedSkills = await Promise.all(skills.map(async (skill) => {
|
|
828
|
+
const name = this.withSkillPrefix(skill.name, normalizedPrefix);
|
|
829
|
+
if (skill.generatedContent) {
|
|
830
|
+
return {
|
|
831
|
+
...skill,
|
|
832
|
+
name,
|
|
833
|
+
generatedContent: matter.stringify(matter(skill.generatedContent).content, {
|
|
834
|
+
...matter(skill.generatedContent).data,
|
|
835
|
+
name,
|
|
836
|
+
}),
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
const tempRoot = await fs.mkdtemp(join(tmpdir(), 'agentinit-prefixed-skill-'));
|
|
840
|
+
const tempSkillPath = join(tempRoot, basename(skill.path));
|
|
841
|
+
tempDirs.push(tempRoot);
|
|
842
|
+
await this.copyDir(skill.path, tempSkillPath);
|
|
843
|
+
const skillMdPath = join(tempSkillPath, 'SKILL.md');
|
|
844
|
+
const skillMdPathLower = join(tempSkillPath, 'skill.md');
|
|
845
|
+
const skillFile = (await fileExists(skillMdPath)) ? skillMdPath
|
|
846
|
+
: (await fileExists(skillMdPathLower)) ? skillMdPathLower
|
|
847
|
+
: null;
|
|
848
|
+
if (!skillFile) {
|
|
849
|
+
throw new Error(`Skill "${skill.name}" is missing SKILL.md`);
|
|
850
|
+
}
|
|
851
|
+
await this.rewriteSkillFileName(skillFile, name);
|
|
852
|
+
return {
|
|
853
|
+
...skill,
|
|
854
|
+
name,
|
|
855
|
+
path: tempSkillPath,
|
|
856
|
+
};
|
|
857
|
+
}));
|
|
858
|
+
return {
|
|
859
|
+
skills: prefixedSkills,
|
|
860
|
+
cleanup: async () => {
|
|
861
|
+
await Promise.all(tempDirs.map(dir => fs.rm(dir, { recursive: true, force: true }).catch(() => { })));
|
|
862
|
+
},
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
catch (error) {
|
|
866
|
+
await Promise.all(tempDirs.map(dir => fs.rm(dir, { recursive: true, force: true }).catch(() => { })));
|
|
867
|
+
throw error;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
630
870
|
normalizeSkillName(skillName) {
|
|
631
871
|
const normalized = skillName.trim();
|
|
632
872
|
if (!normalized) {
|
|
@@ -732,11 +972,13 @@ export class SkillsManager {
|
|
|
732
972
|
* Add skills from a source (GitHub repo or local path)
|
|
733
973
|
*/
|
|
734
974
|
async addFromSource(source, projectPath, options = {}) {
|
|
975
|
+
const normalizedPrefix = this.normalizeSkillPrefix(options.prefix);
|
|
735
976
|
const context = this.takePreparedSourceContext(source, projectPath, options.from)
|
|
736
977
|
|| await this.loadDiscoveredSkillsContext(source, projectPath, {
|
|
737
978
|
...(options.from !== undefined ? { from: options.from } : {}),
|
|
738
979
|
...(options.pluginName !== undefined ? { pluginName: options.pluginName } : {}),
|
|
739
980
|
});
|
|
981
|
+
let prefixedCleanup = async () => { };
|
|
740
982
|
try {
|
|
741
983
|
let skills = context.skills;
|
|
742
984
|
if (skills.length === 0) {
|
|
@@ -744,7 +986,37 @@ export class SkillsManager {
|
|
|
744
986
|
}
|
|
745
987
|
if (options.skills && options.skills.length > 0) {
|
|
746
988
|
const names = new Set(options.skills.map(skill => skill.toLowerCase()));
|
|
747
|
-
skills = skills.filter(skill => names.has(skill.name.toLowerCase())
|
|
989
|
+
skills = skills.filter(skill => names.has(skill.name.toLowerCase())
|
|
990
|
+
|| names.has(this.withSkillPrefix(skill.name, normalizedPrefix).toLowerCase()));
|
|
991
|
+
}
|
|
992
|
+
const prefixed = await this.applyPrefixToSkills(skills, normalizedPrefix);
|
|
993
|
+
skills = prefixed.skills;
|
|
994
|
+
prefixedCleanup = prefixed.cleanup;
|
|
995
|
+
const result = { installed: [], updated: [], unchanged: [], skipped: [], warnings: [...context.warnings] };
|
|
996
|
+
if (options.scan !== false) {
|
|
997
|
+
const scannedWarnings = new Set();
|
|
998
|
+
const scannableSkills = [];
|
|
999
|
+
for (const skill of skills) {
|
|
1000
|
+
const scan = await this.skillScanner.scanSkill(skill);
|
|
1001
|
+
if (scan.findings.length === 0) {
|
|
1002
|
+
scannableSkills.push(skill);
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
if (scan.blocked && !options.allowRisky) {
|
|
1006
|
+
result.skipped.push({
|
|
1007
|
+
skill,
|
|
1008
|
+
reason: this.skillScanner.formatBlockingReason(scan),
|
|
1009
|
+
});
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
const summary = this.skillScanner.formatShortSummary(scan);
|
|
1013
|
+
scannedWarnings.add(scan.blocked
|
|
1014
|
+
? `Proceeding with "${skill.name}" despite high-risk findings: ${summary}`
|
|
1015
|
+
: `Security warnings for "${skill.name}": ${summary}`);
|
|
1016
|
+
scannableSkills.push(skill);
|
|
1017
|
+
}
|
|
1018
|
+
skills = scannableSkills;
|
|
1019
|
+
result.warnings.push(...scannedWarnings);
|
|
748
1020
|
}
|
|
749
1021
|
const installToSharedStore = options.agents?.includes(SHARED_SKILLS_TARGET_ID) ?? false;
|
|
750
1022
|
const agents = await this.getTargetAgents(projectPath, options);
|
|
@@ -753,11 +1025,13 @@ export class SkillsManager {
|
|
|
753
1025
|
installed: [],
|
|
754
1026
|
updated: [],
|
|
755
1027
|
unchanged: [],
|
|
756
|
-
skipped:
|
|
757
|
-
|
|
1028
|
+
skipped: [
|
|
1029
|
+
...result.skipped,
|
|
1030
|
+
...skills.map(skill => ({ skill, reason: 'No target agents found' })),
|
|
1031
|
+
],
|
|
1032
|
+
warnings: result.warnings,
|
|
758
1033
|
};
|
|
759
1034
|
}
|
|
760
|
-
const result = { installed: [], updated: [], unchanged: [], skipped: [], warnings: context.warnings };
|
|
761
1035
|
const installableAgents = [];
|
|
762
1036
|
// Cache comparison results by install path to avoid re-comparing the same target
|
|
763
1037
|
const comparisonCache = new Map();
|
|
@@ -885,6 +1159,7 @@ export class SkillsManager {
|
|
|
885
1159
|
type: resolvedSource.type,
|
|
886
1160
|
...(resolvedSource.marketplace ? { marketplace: resolvedSource.marketplace } : {}),
|
|
887
1161
|
...(resolvedSource.pluginName ? { pluginName: resolvedSource.pluginName } : {}),
|
|
1162
|
+
...(normalizedPrefix ? { prefix: normalizedPrefix } : {}),
|
|
888
1163
|
...(resolvedSource.url ? { url: resolvedSource.url } : {}),
|
|
889
1164
|
...(resolvedSource.path ? { path: resolve(projectPath, expandTilde(resolvedSource.path)) } : {}),
|
|
890
1165
|
...(resolvedSource.owner ? { owner: resolvedSource.owner } : {}),
|
|
@@ -918,6 +1193,7 @@ export class SkillsManager {
|
|
|
918
1193
|
return result;
|
|
919
1194
|
}
|
|
920
1195
|
finally {
|
|
1196
|
+
await prefixedCleanup();
|
|
921
1197
|
await context.cleanup();
|
|
922
1198
|
}
|
|
923
1199
|
}
|