ali-skills 0.0.2 → 0.0.3
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/add.js +39 -22
- package/dist/source-parser.js +53 -2
- package/dist/telemetry.js +19 -0
- package/package.json +2 -3
package/dist/add.js
CHANGED
|
@@ -3,7 +3,7 @@ import pc from 'picocolors';
|
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
import { sep } from 'path';
|
|
6
|
-
import { parseSource, getOwnerRepo, parseOwnerRepo, isRepoPrivate } from './source-parser.js';
|
|
6
|
+
import { parseSource, getOwnerRepo, parseOwnerRepo, isRepoPrivate, isInternalGitUrl, } from './source-parser.js';
|
|
7
7
|
import { searchMultiselect, cancelSymbol } from './prompts/search-multiselect.js';
|
|
8
8
|
// Helper to check if a value is a cancel symbol (works with both clack and our custom prompts)
|
|
9
9
|
const isCancelled = (value) => typeof value === 'symbol';
|
|
@@ -27,7 +27,6 @@ import { track, setVersion, fetchAuditData, } from './telemetry.js';
|
|
|
27
27
|
import { wellKnownProvider } from './providers/index.js';
|
|
28
28
|
import { addSkillToLock, fetchSkillFolderHash, getGitHubToken, isPromptDismissed, dismissPrompt, getLastSelectedAgents, saveSelectedAgents, } from './skill-lock.js';
|
|
29
29
|
import { addSkillToLocalLock, computeSkillFolderHash } from './local-lock.js';
|
|
30
|
-
import packageJson from '../package.json' with { type: 'json' };
|
|
31
30
|
export function initTelemetry(version) {
|
|
32
31
|
setVersion(version);
|
|
33
32
|
}
|
|
@@ -308,8 +307,6 @@ async function selectAgentsInteractive(options) {
|
|
|
308
307
|
}
|
|
309
308
|
return selected;
|
|
310
309
|
}
|
|
311
|
-
const version = packageJson.version;
|
|
312
|
-
setVersion(version);
|
|
313
310
|
/**
|
|
314
311
|
* Handle skills from a well-known endpoint (RFC 8615).
|
|
315
312
|
* Discovers skills from /.well-known/skills/index.json
|
|
@@ -1172,15 +1169,45 @@ export async function runAdd(args, options = {}) {
|
|
|
1172
1169
|
// breaking restore for private repos that require SSH authentication.
|
|
1173
1170
|
const isSSH = parsed.url.startsWith('git@');
|
|
1174
1171
|
const lockSource = isSSH ? parsed.url : normalizedSource;
|
|
1172
|
+
const isInternalSource = isInternalGitUrl(parsed.url);
|
|
1173
|
+
const telemetrySourceType = isInternalSource ? 'internal-git' : parsed.type;
|
|
1175
1174
|
// Only track if we have a valid remote source and it's not a private repo
|
|
1176
1175
|
if (normalizedSource) {
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
//
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1176
|
+
if (isInternalSource) {
|
|
1177
|
+
// Internal repositories: do not send to public skills telemetry.
|
|
1178
|
+
// The telemetry router in telemetry.ts will route sourceType=internal-* to
|
|
1179
|
+
// the internal placeholder hook.
|
|
1180
|
+
track({
|
|
1181
|
+
event: 'install',
|
|
1182
|
+
source: normalizedSource,
|
|
1183
|
+
skills: selectedSkills.map((s) => s.name).join(','),
|
|
1184
|
+
agents: targetAgents.join(','),
|
|
1185
|
+
...(installGlobally && { global: '1' }),
|
|
1186
|
+
skillFiles: JSON.stringify(skillFiles),
|
|
1187
|
+
sourceType: telemetrySourceType,
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
else {
|
|
1191
|
+
const ownerRepo = parseOwnerRepo(normalizedSource);
|
|
1192
|
+
if (ownerRepo) {
|
|
1193
|
+
// Check if repo is private - skip telemetry for private repos
|
|
1194
|
+
const isPrivate = await isRepoPrivate(ownerRepo.owner, ownerRepo.repo);
|
|
1195
|
+
// Only send telemetry if repo is public (isPrivate === false)
|
|
1196
|
+
// If we can't determine (null), err on the side of caution and skip telemetry
|
|
1197
|
+
if (isPrivate === false) {
|
|
1198
|
+
track({
|
|
1199
|
+
event: 'install',
|
|
1200
|
+
source: normalizedSource,
|
|
1201
|
+
skills: selectedSkills.map((s) => s.name).join(','),
|
|
1202
|
+
agents: targetAgents.join(','),
|
|
1203
|
+
...(installGlobally && { global: '1' }),
|
|
1204
|
+
skillFiles: JSON.stringify(skillFiles),
|
|
1205
|
+
sourceType: telemetrySourceType,
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
else {
|
|
1210
|
+
// If we can't parse owner/repo, still send telemetry (for non-GitHub sources)
|
|
1184
1211
|
track({
|
|
1185
1212
|
event: 'install',
|
|
1186
1213
|
source: normalizedSource,
|
|
@@ -1188,20 +1215,10 @@ export async function runAdd(args, options = {}) {
|
|
|
1188
1215
|
agents: targetAgents.join(','),
|
|
1189
1216
|
...(installGlobally && { global: '1' }),
|
|
1190
1217
|
skillFiles: JSON.stringify(skillFiles),
|
|
1218
|
+
sourceType: telemetrySourceType,
|
|
1191
1219
|
});
|
|
1192
1220
|
}
|
|
1193
1221
|
}
|
|
1194
|
-
else {
|
|
1195
|
-
// If we can't parse owner/repo, still send telemetry (for non-GitHub sources)
|
|
1196
|
-
track({
|
|
1197
|
-
event: 'install',
|
|
1198
|
-
source: normalizedSource,
|
|
1199
|
-
skills: selectedSkills.map((s) => s.name).join(','),
|
|
1200
|
-
agents: targetAgents.join(','),
|
|
1201
|
-
...(installGlobally && { global: '1' }),
|
|
1202
|
-
skillFiles: JSON.stringify(skillFiles),
|
|
1203
|
-
});
|
|
1204
|
-
}
|
|
1205
1222
|
}
|
|
1206
1223
|
// Add to skill lock file for update tracking (only for global installs)
|
|
1207
1224
|
if (successful.length > 0 && installGlobally && normalizedSource) {
|
package/dist/source-parser.js
CHANGED
|
@@ -1,4 +1,31 @@
|
|
|
1
1
|
import { isAbsolute, resolve } from 'path';
|
|
2
|
+
const INTERNAL_GIT_HOSTS = new Set(['code.alibaba-inc.com', 'gitlab.alibaba-inc.com']);
|
|
3
|
+
const INTERNAL_GIT_CANONICAL_HOST = 'gitlab.alibaba-inc.com';
|
|
4
|
+
const INTERNAL_GIT_CANONICAL_PROTOCOL = 'http';
|
|
5
|
+
function toInternalCanonicalGitUrl(repoPath) {
|
|
6
|
+
const cleanPath = repoPath.replace(/^\/+/, '').replace(/\.git$/, '').replace(/\/+$/, '');
|
|
7
|
+
return `${INTERNAL_GIT_CANONICAL_PROTOCOL}://${INTERNAL_GIT_CANONICAL_HOST}/${cleanPath}.git`;
|
|
8
|
+
}
|
|
9
|
+
export function isInternalGitHost(hostname) {
|
|
10
|
+
return INTERNAL_GIT_HOSTS.has(hostname.toLowerCase());
|
|
11
|
+
}
|
|
12
|
+
export function isInternalGitUrl(input) {
|
|
13
|
+
// SSH style: git@code.alibaba-inc.com:group/repo.git
|
|
14
|
+
const sshMatch = input.match(/^git@([^:]+):.+$/);
|
|
15
|
+
if (sshMatch && isInternalGitHost(sshMatch[1])) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
if (!input.startsWith('http://') && !input.startsWith('https://')) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const parsed = new URL(input);
|
|
23
|
+
return isInternalGitHost(parsed.hostname);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
2
29
|
/**
|
|
3
30
|
* Extract owner/repo (or group/subgroup/repo for GitLab) from a parsed source
|
|
4
31
|
* for lockfile tracking and telemetry.
|
|
@@ -173,9 +200,12 @@ export function parseSource(input) {
|
|
|
173
200
|
if (gitlabTreeWithPathMatch) {
|
|
174
201
|
const [, protocol, hostname, repoPath, ref, subpath] = gitlabTreeWithPathMatch;
|
|
175
202
|
if (hostname !== 'github.com' && repoPath) {
|
|
203
|
+
const normalizedUrl = isInternalGitHost(hostname)
|
|
204
|
+
? toInternalCanonicalGitUrl(repoPath)
|
|
205
|
+
: `${protocol}://${hostname}/${repoPath.replace(/\.git$/, '')}.git`;
|
|
176
206
|
return {
|
|
177
207
|
type: 'gitlab',
|
|
178
|
-
url:
|
|
208
|
+
url: normalizedUrl,
|
|
179
209
|
ref,
|
|
180
210
|
subpath: subpath ? sanitizeSubpath(subpath) : subpath,
|
|
181
211
|
};
|
|
@@ -186,9 +216,12 @@ export function parseSource(input) {
|
|
|
186
216
|
if (gitlabTreeMatch) {
|
|
187
217
|
const [, protocol, hostname, repoPath, ref] = gitlabTreeMatch;
|
|
188
218
|
if (hostname !== 'github.com' && repoPath) {
|
|
219
|
+
const normalizedUrl = isInternalGitHost(hostname)
|
|
220
|
+
? toInternalCanonicalGitUrl(repoPath)
|
|
221
|
+
: `${protocol}://${hostname}/${repoPath.replace(/\.git$/, '')}.git`;
|
|
189
222
|
return {
|
|
190
223
|
type: 'gitlab',
|
|
191
|
-
url:
|
|
224
|
+
url: normalizedUrl,
|
|
192
225
|
ref,
|
|
193
226
|
};
|
|
194
227
|
}
|
|
@@ -207,6 +240,21 @@ export function parseSource(input) {
|
|
|
207
240
|
};
|
|
208
241
|
}
|
|
209
242
|
}
|
|
243
|
+
// Internal Git host URL (e.g., https://code.alibaba-inc.com/group/repo/)
|
|
244
|
+
// Treat as GitLab-style repository URL for clone/discovery compatibility.
|
|
245
|
+
const internalGitRepoMatch = input.match(/^(https?):\/\/([^/]+)\/(.+?)(?:\.git)?\/?$/);
|
|
246
|
+
if (internalGitRepoMatch) {
|
|
247
|
+
const [, _protocol, hostname, repoPath] = internalGitRepoMatch;
|
|
248
|
+
if (repoPath && isInternalGitHost(hostname)) {
|
|
249
|
+
// Must have at least owner/repo (one slash)
|
|
250
|
+
if (repoPath.includes('/')) {
|
|
251
|
+
return {
|
|
252
|
+
type: 'gitlab',
|
|
253
|
+
url: toInternalCanonicalGitUrl(repoPath),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
210
258
|
// GitHub shorthand: owner/repo, owner/repo/path/to/skill, or owner/repo@skill-name
|
|
211
259
|
// Exclude paths that start with . or / to avoid matching local paths
|
|
212
260
|
// First check for @skill syntax: owner/repo@skill-name
|
|
@@ -258,6 +306,9 @@ function isWellKnownUrl(input) {
|
|
|
258
306
|
if (excludedHosts.includes(parsed.hostname)) {
|
|
259
307
|
return false;
|
|
260
308
|
}
|
|
309
|
+
if (isInternalGitHost(parsed.hostname)) {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
261
312
|
// Don't match URLs that look like git repos (should be handled by git type)
|
|
262
313
|
if (input.endsWith('.git')) {
|
|
263
314
|
return false;
|
package/dist/telemetry.js
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
const TELEMETRY_URL = 'https://add-skill.vercel.sh/t';
|
|
2
2
|
const AUDIT_URL = 'https://add-skill.vercel.sh/audit';
|
|
3
|
+
const INTERNAL_TELEMETRY_SOURCE_PREFIX = 'internal-';
|
|
3
4
|
let cliVersion = null;
|
|
5
|
+
function shouldRouteToInternalTelemetry(data) {
|
|
6
|
+
// Internal telemetry routing hook:
|
|
7
|
+
// Any event tagged with sourceType starting with "internal-" is considered
|
|
8
|
+
// an intranet source and must not be sent to public skills telemetry.
|
|
9
|
+
return 'sourceType' in data && typeof data.sourceType === 'string'
|
|
10
|
+
? data.sourceType.startsWith(INTERNAL_TELEMETRY_SOURCE_PREFIX)
|
|
11
|
+
: false;
|
|
12
|
+
}
|
|
13
|
+
function trackInternalPlaceholder(_data) {
|
|
14
|
+
// TODO(internal-telemetry):
|
|
15
|
+
// Implement intranet telemetry reporting endpoint here.
|
|
16
|
+
// Keep this as no-op for now so internal repository installs are not reported
|
|
17
|
+
// to public skills.sh telemetry.
|
|
18
|
+
}
|
|
4
19
|
function isCI() {
|
|
5
20
|
return !!(process.env.CI ||
|
|
6
21
|
process.env.GITHUB_ACTIONS ||
|
|
@@ -46,6 +61,10 @@ export async function fetchAuditData(source, skillSlugs, timeoutMs = 3000) {
|
|
|
46
61
|
export function track(data) {
|
|
47
62
|
if (!isEnabled())
|
|
48
63
|
return;
|
|
64
|
+
if (shouldRouteToInternalTelemetry(data)) {
|
|
65
|
+
trackInternalPlaceholder(data);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
49
68
|
try {
|
|
50
69
|
const params = new URLSearchParams();
|
|
51
70
|
// Add version
|
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ali-skills",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "The open agent skills ecosystem",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ali-skills": "./bin/cli.mjs",
|
|
8
|
-
"skills": "./bin/cli.mjs"
|
|
9
|
-
"add-skill": "./bin/cli.mjs"
|
|
8
|
+
"skills": "./bin/cli.mjs"
|
|
10
9
|
},
|
|
11
10
|
"files": [
|
|
12
11
|
"dist",
|