@quadient/skillshare 1.2.0 → 1.4.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 +25 -0
- package/dist/index.js +196 -8
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -148,6 +148,17 @@ export SKILLSHARE_TOKEN="<personal-access-token>"
|
|
|
148
148
|
npx @quadient/skillshare whoami
|
|
149
149
|
```
|
|
150
150
|
|
|
151
|
+
Register the production or local SkillShare host as a generic OAuth provider in Git Credential
|
|
152
|
+
Manager 2.0.931 or newer:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
skillshare git configure
|
|
156
|
+
skillshare git configure local
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Remove only the URL-scoped SkillShare registration with `skillshare git unconfigure [local]`.
|
|
160
|
+
The first Git clone opens the browser login; no SkillShare personal access token needs to be created.
|
|
161
|
+
|
|
151
162
|
Update recorded installs to the latest versions:
|
|
152
163
|
|
|
153
164
|
```bash
|
|
@@ -156,6 +167,20 @@ npx @quadient/skillshare update "Prompt Lint" --dry-run
|
|
|
156
167
|
npx @quadient/skillshare update 12 --target ./installed
|
|
157
168
|
```
|
|
158
169
|
|
|
170
|
+
Publish an authenticated static page. The upload replaces the page's complete
|
|
171
|
+
content, and the directory must contain `index.html` at its root. By default the
|
|
172
|
+
directory name is used as the page slug:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
npx @quadient/skillshare pages upload ./release-notes
|
|
176
|
+
npx @quadient/skillshare pages upload ./site --slug release-notes
|
|
177
|
+
npx @quadient/skillshare pages list
|
|
178
|
+
npx @quadient/skillshare pages show release-notes
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The page is then available at
|
|
182
|
+
`https://skillshare.inspireclouduat.net/p/release-notes/` after SSO sign-in.
|
|
183
|
+
|
|
159
184
|
## Package Distribution
|
|
160
185
|
|
|
161
186
|
The CLI is prepared for publishing as `@quadient/skillshare`.
|
package/dist/index.js
CHANGED
|
@@ -78,6 +78,11 @@ Commands:
|
|
|
78
78
|
install <skillId|skillName> [version] --target <directory>
|
|
79
79
|
install --skill-version-id <id> --target <directory>
|
|
80
80
|
update [skillId|skillName] [--target <directory>] [--dry-run]
|
|
81
|
+
pages list
|
|
82
|
+
pages show <slug>
|
|
83
|
+
pages upload <directory> [--slug <slug>]
|
|
84
|
+
git configure [local]
|
|
85
|
+
git unconfigure [local]
|
|
81
86
|
|
|
82
87
|
Options:
|
|
83
88
|
--token <token> Bearer token for API requests (overrides SKILLSHARE_TOKEN)
|
|
@@ -103,12 +108,27 @@ async function main() {
|
|
|
103
108
|
await logoutSession();
|
|
104
109
|
return;
|
|
105
110
|
}
|
|
111
|
+
case 'git': {
|
|
112
|
+
const action = options.positionals[0];
|
|
113
|
+
const baseUrl = options.positionals[1] === 'local' ? LOCAL_URL : PROD_URL;
|
|
114
|
+
if (action === 'configure') {
|
|
115
|
+
await configureGitCredentialManager(baseUrl);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (action === 'unconfigure') {
|
|
119
|
+
await unconfigureGitCredentialManager(baseUrl);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
throw new Error('git requires "configure [local]" or "unconfigure [local]".');
|
|
123
|
+
}
|
|
106
124
|
case 'whoami':
|
|
107
125
|
case 'list':
|
|
108
126
|
case 'show':
|
|
109
127
|
case 'upload':
|
|
110
128
|
case 'install':
|
|
111
|
-
case 'update':
|
|
129
|
+
case 'update':
|
|
130
|
+
case 'page':
|
|
131
|
+
case 'pages': {
|
|
112
132
|
const activeBaseUrl = await resolveActiveBaseUrl();
|
|
113
133
|
const client = await createClient(activeBaseUrl, options.token);
|
|
114
134
|
switch (command) {
|
|
@@ -254,6 +274,45 @@ async function main() {
|
|
|
254
274
|
});
|
|
255
275
|
return;
|
|
256
276
|
}
|
|
277
|
+
case 'page':
|
|
278
|
+
case 'pages': {
|
|
279
|
+
const action = options.positionals[0] ?? 'list';
|
|
280
|
+
if (action === 'list') {
|
|
281
|
+
const pages = await client.get('/api/pages');
|
|
282
|
+
for (const page of pages) {
|
|
283
|
+
console.log(`${page.slug}\t${page.fileCount}\t${page.ownerDisplayName}`);
|
|
284
|
+
}
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (action === 'show') {
|
|
288
|
+
const slug = options.positionals[1];
|
|
289
|
+
if (!slug)
|
|
290
|
+
throw new Error('pages show requires <slug>');
|
|
291
|
+
console.log(JSON.stringify(await client.get(`/api/pages/${encodeURIComponent(slug)}`), null, 2));
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (action === 'upload') {
|
|
295
|
+
const directory = options.positionals[1];
|
|
296
|
+
if (!directory)
|
|
297
|
+
throw new Error('pages upload requires <directory>');
|
|
298
|
+
const resolvedDirectory = resolve(directory);
|
|
299
|
+
const slug = normalizePageSlug(options.flags.slug ?? basename(resolvedDirectory));
|
|
300
|
+
const pageFiles = await loadLocalPageFiles(resolvedDirectory);
|
|
301
|
+
const uploadStatus = spinner({ indicator: 'timer' });
|
|
302
|
+
uploadStatus.start(`Uploading ${pageFiles.files.length} files (${formatByteSize(pageFiles.totalBytes)}) to /p/${slug}/`);
|
|
303
|
+
try {
|
|
304
|
+
const detail = await client.replacePageContent(slug, pageFiles.files);
|
|
305
|
+
uploadStatus.stop(`Published /p/${detail.slug}/ with ${detail.files.length} files`);
|
|
306
|
+
console.log(JSON.stringify(detail, null, 2));
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
uploadStatus.clear();
|
|
310
|
+
throw error;
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
throw new Error(`Unknown pages command '${action}'. Use list, show, or upload.`);
|
|
315
|
+
}
|
|
257
316
|
}
|
|
258
317
|
return;
|
|
259
318
|
}
|
|
@@ -736,6 +795,20 @@ async function loadLocalSkillContext(directory, baseUrl) {
|
|
|
736
795
|
defaults: deriveSkillDefaults(files, resolvedDirectory),
|
|
737
796
|
};
|
|
738
797
|
}
|
|
798
|
+
async function loadLocalPageFiles(directory) {
|
|
799
|
+
const filePlans = await collectFilePlans(directory, directory, { includeSkillMetadata: true });
|
|
800
|
+
if (filePlans.length === 0) {
|
|
801
|
+
throw new Error('The page directory is empty.');
|
|
802
|
+
}
|
|
803
|
+
if (!filePlans.some((file) => file.relativePath === 'index.html')) {
|
|
804
|
+
throw new Error('The page directory must contain index.html at its root.');
|
|
805
|
+
}
|
|
806
|
+
const totalBytes = filePlans.reduce((sum, file) => sum + file.size, 0);
|
|
807
|
+
return {
|
|
808
|
+
files: await collectFiles(filePlans),
|
|
809
|
+
totalBytes,
|
|
810
|
+
};
|
|
811
|
+
}
|
|
739
812
|
async function createSkillFromDirectory(client, localSkill, input) {
|
|
740
813
|
const name = normalizeText(input.name) ?? localSkill.defaults.name;
|
|
741
814
|
const description = normalizeText(input.description) ?? localSkill.defaults.description;
|
|
@@ -1095,6 +1168,13 @@ function normalizeText(value) {
|
|
|
1095
1168
|
const trimmed = value?.trim();
|
|
1096
1169
|
return trimmed ? trimmed : null;
|
|
1097
1170
|
}
|
|
1171
|
+
function normalizePageSlug(value) {
|
|
1172
|
+
const normalized = value.trim().toLowerCase();
|
|
1173
|
+
if (!/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/.test(normalized)) {
|
|
1174
|
+
throw new Error('Page slug must be 1-64 lowercase letters, numbers, or hyphens, and cannot start or end with a hyphen.');
|
|
1175
|
+
}
|
|
1176
|
+
return normalized;
|
|
1177
|
+
}
|
|
1098
1178
|
async function resolveExistingUploadSkill(client, localSkill, options) {
|
|
1099
1179
|
const explicitSkillId = normalizeText(options?.explicitSkillId);
|
|
1100
1180
|
if (explicitSkillId) {
|
|
@@ -1263,6 +1343,66 @@ async function login(baseUrl) {
|
|
|
1263
1343
|
});
|
|
1264
1344
|
console.log(`Logged in as ${response.user.displayName} (${baseUrl})`);
|
|
1265
1345
|
}
|
|
1346
|
+
const GCM_MINIMUM_VERSION = [2, 0, 931];
|
|
1347
|
+
async function configureGitCredentialManager(baseUrl) {
|
|
1348
|
+
baseUrl = normalizeBaseUrl(baseUrl);
|
|
1349
|
+
const versionResult = await runProcess('git', ['credential-manager', '--version'], { allowFailure: true });
|
|
1350
|
+
const version = parseVersion(`${versionResult.stdout} ${versionResult.stderr}`);
|
|
1351
|
+
if (versionResult.exitCode !== 0 || !version || compareVersions(version, GCM_MINIMUM_VERSION) < 0) {
|
|
1352
|
+
throw new Error('Git Credential Manager 2.0.931 or newer is required. Install or update GCM, then run this command again.');
|
|
1353
|
+
}
|
|
1354
|
+
await runProcess('git', ['credential-manager', 'configure']);
|
|
1355
|
+
const prefix = `credential.${baseUrl}`;
|
|
1356
|
+
const settings = [
|
|
1357
|
+
[`${prefix}.provider`, 'generic'],
|
|
1358
|
+
[`${prefix}.oauthClientId`, 'skillshare-gcm'],
|
|
1359
|
+
[`${prefix}.oauthRedirectUri`, 'http://127.0.0.1'],
|
|
1360
|
+
[`${prefix}.oauthAuthorizeEndpoint`, '/api/git/oauth/authorize'],
|
|
1361
|
+
[`${prefix}.oauthTokenEndpoint`, '/api/git/oauth/token'],
|
|
1362
|
+
[`${prefix}.oauthScopes`, 'git.read'],
|
|
1363
|
+
[`${prefix}.oauthDefaultUserName`, 'skillshare'],
|
|
1364
|
+
[`${prefix}.oauthUseClientAuthHeader`, 'false'],
|
|
1365
|
+
[`${prefix}.useHttpPath`, 'false'],
|
|
1366
|
+
];
|
|
1367
|
+
for (const [key, value] of settings) {
|
|
1368
|
+
await runProcess('git', ['config', '--global', '--replace-all', key, value]);
|
|
1369
|
+
}
|
|
1370
|
+
console.log(`Registered ${baseUrl} in Git Credential Manager ${formatVersion(version)}.`);
|
|
1371
|
+
}
|
|
1372
|
+
async function unconfigureGitCredentialManager(baseUrl) {
|
|
1373
|
+
baseUrl = normalizeBaseUrl(baseUrl);
|
|
1374
|
+
const prefix = `credential.${baseUrl}`;
|
|
1375
|
+
for (const key of [
|
|
1376
|
+
'provider',
|
|
1377
|
+
'oauthClientId',
|
|
1378
|
+
'oauthRedirectUri',
|
|
1379
|
+
'oauthAuthorizeEndpoint',
|
|
1380
|
+
'oauthTokenEndpoint',
|
|
1381
|
+
'oauthScopes',
|
|
1382
|
+
'oauthDefaultUserName',
|
|
1383
|
+
'oauthUseClientAuthHeader',
|
|
1384
|
+
'useHttpPath',
|
|
1385
|
+
]) {
|
|
1386
|
+
await runProcess('git', ['config', '--global', '--unset-all', `${prefix}.${key}`], { allowFailure: true });
|
|
1387
|
+
}
|
|
1388
|
+
console.log(`Removed the Git Credential Manager registration for ${baseUrl}.`);
|
|
1389
|
+
}
|
|
1390
|
+
function parseVersion(value) {
|
|
1391
|
+
const match = value.match(/(?:^|\s)(\d+)\.(\d+)\.(\d+)(?:\D|$)/);
|
|
1392
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
1393
|
+
}
|
|
1394
|
+
function compareVersions(left, right) {
|
|
1395
|
+
for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
|
|
1396
|
+
const difference = (left[index] ?? 0) - (right[index] ?? 0);
|
|
1397
|
+
if (difference !== 0) {
|
|
1398
|
+
return difference;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
return 0;
|
|
1402
|
+
}
|
|
1403
|
+
function formatVersion(version) {
|
|
1404
|
+
return version.join('.');
|
|
1405
|
+
}
|
|
1266
1406
|
async function parseOptions(args) {
|
|
1267
1407
|
const booleanFlags = new Set(['dry-run']);
|
|
1268
1408
|
const flags = {};
|
|
@@ -1424,6 +1564,17 @@ async function createClient(baseUrl, explicitToken) {
|
|
|
1424
1564
|
});
|
|
1425
1565
|
return parseJson(response);
|
|
1426
1566
|
},
|
|
1567
|
+
async replacePageContent(slug, files) {
|
|
1568
|
+
const formData = new FormData();
|
|
1569
|
+
for (const file of files) {
|
|
1570
|
+
formData.append('files', new Blob([toBlobPart(file.content)], { type: file.contentType }), file.relativePath);
|
|
1571
|
+
}
|
|
1572
|
+
const response = await sendAuthorized(`/api/pages/${encodeURIComponent(slug)}/content`, {
|
|
1573
|
+
method: 'PUT',
|
|
1574
|
+
body: formData,
|
|
1575
|
+
});
|
|
1576
|
+
return parseJson(response);
|
|
1577
|
+
},
|
|
1427
1578
|
};
|
|
1428
1579
|
}
|
|
1429
1580
|
async function buildHeaders(getAccessToken, initialHeaders) {
|
|
@@ -1725,13 +1876,13 @@ async function removeLegacyLocalSkillMetadata(directory) {
|
|
|
1725
1876
|
throw error;
|
|
1726
1877
|
}
|
|
1727
1878
|
}
|
|
1728
|
-
async function collectFilePlans(root, current = root) {
|
|
1879
|
+
async function collectFilePlans(root, current = root, options = {}) {
|
|
1729
1880
|
const entries = await readdir(current, { withFileTypes: true });
|
|
1730
1881
|
const files = [];
|
|
1731
1882
|
for (const entry of entries) {
|
|
1732
1883
|
const entryPath = join(current, entry.name);
|
|
1733
1884
|
if (entry.isDirectory()) {
|
|
1734
|
-
files.push(...(await collectFilePlans(root, entryPath)));
|
|
1885
|
+
files.push(...(await collectFilePlans(root, entryPath, options)));
|
|
1735
1886
|
continue;
|
|
1736
1887
|
}
|
|
1737
1888
|
const entryStat = await stat(entryPath);
|
|
@@ -1739,7 +1890,7 @@ async function collectFilePlans(root, current = root) {
|
|
|
1739
1890
|
continue;
|
|
1740
1891
|
}
|
|
1741
1892
|
const relativePath = relative(root, entryPath).replaceAll('\\', '/');
|
|
1742
|
-
if (relativePath === SKILL_METADATA_FILE_NAME) {
|
|
1893
|
+
if (!options.includeSkillMetadata && relativePath === SKILL_METADATA_FILE_NAME) {
|
|
1743
1894
|
continue;
|
|
1744
1895
|
}
|
|
1745
1896
|
files.push({
|
|
@@ -1775,14 +1926,51 @@ function validateSkillDirectory(files) {
|
|
|
1775
1926
|
}
|
|
1776
1927
|
}
|
|
1777
1928
|
function guessContentType(path) {
|
|
1778
|
-
|
|
1929
|
+
const normalized = path.toLowerCase();
|
|
1930
|
+
if (normalized.endsWith('.html') || normalized.endsWith('.htm'))
|
|
1931
|
+
return 'text/html; charset=utf-8';
|
|
1932
|
+
if (normalized.endsWith('.css'))
|
|
1933
|
+
return 'text/css; charset=utf-8';
|
|
1934
|
+
if (normalized.endsWith('.js') || normalized.endsWith('.mjs'))
|
|
1935
|
+
return 'text/javascript; charset=utf-8';
|
|
1936
|
+
if (normalized.endsWith('.md'))
|
|
1779
1937
|
return 'text/markdown; charset=utf-8';
|
|
1780
|
-
if (
|
|
1938
|
+
if (normalized.endsWith('.json') || normalized.endsWith('.webmanifest'))
|
|
1781
1939
|
return 'application/json; charset=utf-8';
|
|
1782
|
-
if (
|
|
1940
|
+
if (normalized.endsWith('.yaml') || normalized.endsWith('.yml'))
|
|
1783
1941
|
return 'application/yaml; charset=utf-8';
|
|
1784
|
-
if (
|
|
1942
|
+
if (normalized.endsWith('.txt') || normalized.endsWith('.csv'))
|
|
1785
1943
|
return 'text/plain; charset=utf-8';
|
|
1944
|
+
if (normalized.endsWith('.svg'))
|
|
1945
|
+
return 'image/svg+xml';
|
|
1946
|
+
if (normalized.endsWith('.png'))
|
|
1947
|
+
return 'image/png';
|
|
1948
|
+
if (normalized.endsWith('.jpg') || normalized.endsWith('.jpeg'))
|
|
1949
|
+
return 'image/jpeg';
|
|
1950
|
+
if (normalized.endsWith('.webp'))
|
|
1951
|
+
return 'image/webp';
|
|
1952
|
+
if (normalized.endsWith('.gif'))
|
|
1953
|
+
return 'image/gif';
|
|
1954
|
+
if (normalized.endsWith('.avif'))
|
|
1955
|
+
return 'image/avif';
|
|
1956
|
+
if (normalized.endsWith('.ico'))
|
|
1957
|
+
return 'image/x-icon';
|
|
1958
|
+
if (normalized.endsWith('.mp4'))
|
|
1959
|
+
return 'video/mp4';
|
|
1960
|
+
if (normalized.endsWith('.webm'))
|
|
1961
|
+
return 'video/webm';
|
|
1962
|
+
if (normalized.endsWith('.mp3'))
|
|
1963
|
+
return 'audio/mpeg';
|
|
1964
|
+
if (normalized.endsWith('.wav'))
|
|
1965
|
+
return 'audio/wav';
|
|
1966
|
+
if (normalized.endsWith('.pdf'))
|
|
1967
|
+
return 'application/pdf';
|
|
1968
|
+
if (normalized.endsWith('.wasm'))
|
|
1969
|
+
return 'application/wasm';
|
|
1970
|
+
if (normalized.endsWith('.woff'))
|
|
1971
|
+
return 'font/woff';
|
|
1972
|
+
if (normalized.endsWith('.woff2'))
|
|
1973
|
+
return 'font/woff2';
|
|
1786
1974
|
return 'application/octet-stream';
|
|
1787
1975
|
}
|
|
1788
1976
|
function formatByteSize(bytes) {
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quadient/skillshare",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Skillshare CLI for installing and publishing Quadient skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"skillshare": "
|
|
7
|
+
"skillshare": "bin/skillshare"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
@@ -21,14 +21,14 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@types/node": "^
|
|
25
|
-
"tsx": "^4.
|
|
26
|
-
"typescript": "^
|
|
24
|
+
"@types/node": "^26.1.1",
|
|
25
|
+
"tsx": "^4.23.1",
|
|
26
|
+
"typescript": "^7.0.2"
|
|
27
27
|
},
|
|
28
28
|
"engines": {
|
|
29
29
|
"node": ">=20"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@clack/prompts": "^1.
|
|
32
|
+
"@clack/prompts": "^1.7.0"
|
|
33
33
|
}
|
|
34
34
|
}
|