@quadient/skillshare 1.2.0 → 1.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/README.md +14 -0
- package/dist/index.js +121 -8
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -156,6 +156,20 @@ npx @quadient/skillshare update "Prompt Lint" --dry-run
|
|
|
156
156
|
npx @quadient/skillshare update 12 --target ./installed
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
+
Publish an authenticated static page. The upload replaces the page's complete
|
|
160
|
+
content, and the directory must contain `index.html` at its root. By default the
|
|
161
|
+
directory name is used as the page slug:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
npx @quadient/skillshare pages upload ./release-notes
|
|
165
|
+
npx @quadient/skillshare pages upload ./site --slug release-notes
|
|
166
|
+
npx @quadient/skillshare pages list
|
|
167
|
+
npx @quadient/skillshare pages show release-notes
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The page is then available at
|
|
171
|
+
`https://skillshare.inspireclouduat.net/p/release-notes/` after SSO sign-in.
|
|
172
|
+
|
|
159
173
|
## Package Distribution
|
|
160
174
|
|
|
161
175
|
The CLI is prepared for publishing as `@quadient/skillshare`.
|
package/dist/index.js
CHANGED
|
@@ -78,6 +78,9 @@ 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>]
|
|
81
84
|
|
|
82
85
|
Options:
|
|
83
86
|
--token <token> Bearer token for API requests (overrides SKILLSHARE_TOKEN)
|
|
@@ -108,7 +111,9 @@ async function main() {
|
|
|
108
111
|
case 'show':
|
|
109
112
|
case 'upload':
|
|
110
113
|
case 'install':
|
|
111
|
-
case 'update':
|
|
114
|
+
case 'update':
|
|
115
|
+
case 'page':
|
|
116
|
+
case 'pages': {
|
|
112
117
|
const activeBaseUrl = await resolveActiveBaseUrl();
|
|
113
118
|
const client = await createClient(activeBaseUrl, options.token);
|
|
114
119
|
switch (command) {
|
|
@@ -254,6 +259,45 @@ async function main() {
|
|
|
254
259
|
});
|
|
255
260
|
return;
|
|
256
261
|
}
|
|
262
|
+
case 'page':
|
|
263
|
+
case 'pages': {
|
|
264
|
+
const action = options.positionals[0] ?? 'list';
|
|
265
|
+
if (action === 'list') {
|
|
266
|
+
const pages = await client.get('/api/pages');
|
|
267
|
+
for (const page of pages) {
|
|
268
|
+
console.log(`${page.slug}\t${page.fileCount}\t${page.ownerDisplayName}`);
|
|
269
|
+
}
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (action === 'show') {
|
|
273
|
+
const slug = options.positionals[1];
|
|
274
|
+
if (!slug)
|
|
275
|
+
throw new Error('pages show requires <slug>');
|
|
276
|
+
console.log(JSON.stringify(await client.get(`/api/pages/${encodeURIComponent(slug)}`), null, 2));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (action === 'upload') {
|
|
280
|
+
const directory = options.positionals[1];
|
|
281
|
+
if (!directory)
|
|
282
|
+
throw new Error('pages upload requires <directory>');
|
|
283
|
+
const resolvedDirectory = resolve(directory);
|
|
284
|
+
const slug = normalizePageSlug(options.flags.slug ?? basename(resolvedDirectory));
|
|
285
|
+
const pageFiles = await loadLocalPageFiles(resolvedDirectory);
|
|
286
|
+
const uploadStatus = spinner({ indicator: 'timer' });
|
|
287
|
+
uploadStatus.start(`Uploading ${pageFiles.files.length} files (${formatByteSize(pageFiles.totalBytes)}) to /p/${slug}/`);
|
|
288
|
+
try {
|
|
289
|
+
const detail = await client.replacePageContent(slug, pageFiles.files);
|
|
290
|
+
uploadStatus.stop(`Published /p/${detail.slug}/ with ${detail.files.length} files`);
|
|
291
|
+
console.log(JSON.stringify(detail, null, 2));
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
uploadStatus.clear();
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
throw new Error(`Unknown pages command '${action}'. Use list, show, or upload.`);
|
|
300
|
+
}
|
|
257
301
|
}
|
|
258
302
|
return;
|
|
259
303
|
}
|
|
@@ -736,6 +780,20 @@ async function loadLocalSkillContext(directory, baseUrl) {
|
|
|
736
780
|
defaults: deriveSkillDefaults(files, resolvedDirectory),
|
|
737
781
|
};
|
|
738
782
|
}
|
|
783
|
+
async function loadLocalPageFiles(directory) {
|
|
784
|
+
const filePlans = await collectFilePlans(directory, directory, { includeSkillMetadata: true });
|
|
785
|
+
if (filePlans.length === 0) {
|
|
786
|
+
throw new Error('The page directory is empty.');
|
|
787
|
+
}
|
|
788
|
+
if (!filePlans.some((file) => file.relativePath === 'index.html')) {
|
|
789
|
+
throw new Error('The page directory must contain index.html at its root.');
|
|
790
|
+
}
|
|
791
|
+
const totalBytes = filePlans.reduce((sum, file) => sum + file.size, 0);
|
|
792
|
+
return {
|
|
793
|
+
files: await collectFiles(filePlans),
|
|
794
|
+
totalBytes,
|
|
795
|
+
};
|
|
796
|
+
}
|
|
739
797
|
async function createSkillFromDirectory(client, localSkill, input) {
|
|
740
798
|
const name = normalizeText(input.name) ?? localSkill.defaults.name;
|
|
741
799
|
const description = normalizeText(input.description) ?? localSkill.defaults.description;
|
|
@@ -1095,6 +1153,13 @@ function normalizeText(value) {
|
|
|
1095
1153
|
const trimmed = value?.trim();
|
|
1096
1154
|
return trimmed ? trimmed : null;
|
|
1097
1155
|
}
|
|
1156
|
+
function normalizePageSlug(value) {
|
|
1157
|
+
const normalized = value.trim().toLowerCase();
|
|
1158
|
+
if (!/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/.test(normalized)) {
|
|
1159
|
+
throw new Error('Page slug must be 1-64 lowercase letters, numbers, or hyphens, and cannot start or end with a hyphen.');
|
|
1160
|
+
}
|
|
1161
|
+
return normalized;
|
|
1162
|
+
}
|
|
1098
1163
|
async function resolveExistingUploadSkill(client, localSkill, options) {
|
|
1099
1164
|
const explicitSkillId = normalizeText(options?.explicitSkillId);
|
|
1100
1165
|
if (explicitSkillId) {
|
|
@@ -1424,6 +1489,17 @@ async function createClient(baseUrl, explicitToken) {
|
|
|
1424
1489
|
});
|
|
1425
1490
|
return parseJson(response);
|
|
1426
1491
|
},
|
|
1492
|
+
async replacePageContent(slug, files) {
|
|
1493
|
+
const formData = new FormData();
|
|
1494
|
+
for (const file of files) {
|
|
1495
|
+
formData.append('files', new Blob([toBlobPart(file.content)], { type: file.contentType }), file.relativePath);
|
|
1496
|
+
}
|
|
1497
|
+
const response = await sendAuthorized(`/api/pages/${encodeURIComponent(slug)}/content`, {
|
|
1498
|
+
method: 'PUT',
|
|
1499
|
+
body: formData,
|
|
1500
|
+
});
|
|
1501
|
+
return parseJson(response);
|
|
1502
|
+
},
|
|
1427
1503
|
};
|
|
1428
1504
|
}
|
|
1429
1505
|
async function buildHeaders(getAccessToken, initialHeaders) {
|
|
@@ -1725,13 +1801,13 @@ async function removeLegacyLocalSkillMetadata(directory) {
|
|
|
1725
1801
|
throw error;
|
|
1726
1802
|
}
|
|
1727
1803
|
}
|
|
1728
|
-
async function collectFilePlans(root, current = root) {
|
|
1804
|
+
async function collectFilePlans(root, current = root, options = {}) {
|
|
1729
1805
|
const entries = await readdir(current, { withFileTypes: true });
|
|
1730
1806
|
const files = [];
|
|
1731
1807
|
for (const entry of entries) {
|
|
1732
1808
|
const entryPath = join(current, entry.name);
|
|
1733
1809
|
if (entry.isDirectory()) {
|
|
1734
|
-
files.push(...(await collectFilePlans(root, entryPath)));
|
|
1810
|
+
files.push(...(await collectFilePlans(root, entryPath, options)));
|
|
1735
1811
|
continue;
|
|
1736
1812
|
}
|
|
1737
1813
|
const entryStat = await stat(entryPath);
|
|
@@ -1739,7 +1815,7 @@ async function collectFilePlans(root, current = root) {
|
|
|
1739
1815
|
continue;
|
|
1740
1816
|
}
|
|
1741
1817
|
const relativePath = relative(root, entryPath).replaceAll('\\', '/');
|
|
1742
|
-
if (relativePath === SKILL_METADATA_FILE_NAME) {
|
|
1818
|
+
if (!options.includeSkillMetadata && relativePath === SKILL_METADATA_FILE_NAME) {
|
|
1743
1819
|
continue;
|
|
1744
1820
|
}
|
|
1745
1821
|
files.push({
|
|
@@ -1775,14 +1851,51 @@ function validateSkillDirectory(files) {
|
|
|
1775
1851
|
}
|
|
1776
1852
|
}
|
|
1777
1853
|
function guessContentType(path) {
|
|
1778
|
-
|
|
1854
|
+
const normalized = path.toLowerCase();
|
|
1855
|
+
if (normalized.endsWith('.html') || normalized.endsWith('.htm'))
|
|
1856
|
+
return 'text/html; charset=utf-8';
|
|
1857
|
+
if (normalized.endsWith('.css'))
|
|
1858
|
+
return 'text/css; charset=utf-8';
|
|
1859
|
+
if (normalized.endsWith('.js') || normalized.endsWith('.mjs'))
|
|
1860
|
+
return 'text/javascript; charset=utf-8';
|
|
1861
|
+
if (normalized.endsWith('.md'))
|
|
1779
1862
|
return 'text/markdown; charset=utf-8';
|
|
1780
|
-
if (
|
|
1863
|
+
if (normalized.endsWith('.json') || normalized.endsWith('.webmanifest'))
|
|
1781
1864
|
return 'application/json; charset=utf-8';
|
|
1782
|
-
if (
|
|
1865
|
+
if (normalized.endsWith('.yaml') || normalized.endsWith('.yml'))
|
|
1783
1866
|
return 'application/yaml; charset=utf-8';
|
|
1784
|
-
if (
|
|
1867
|
+
if (normalized.endsWith('.txt') || normalized.endsWith('.csv'))
|
|
1785
1868
|
return 'text/plain; charset=utf-8';
|
|
1869
|
+
if (normalized.endsWith('.svg'))
|
|
1870
|
+
return 'image/svg+xml';
|
|
1871
|
+
if (normalized.endsWith('.png'))
|
|
1872
|
+
return 'image/png';
|
|
1873
|
+
if (normalized.endsWith('.jpg') || normalized.endsWith('.jpeg'))
|
|
1874
|
+
return 'image/jpeg';
|
|
1875
|
+
if (normalized.endsWith('.webp'))
|
|
1876
|
+
return 'image/webp';
|
|
1877
|
+
if (normalized.endsWith('.gif'))
|
|
1878
|
+
return 'image/gif';
|
|
1879
|
+
if (normalized.endsWith('.avif'))
|
|
1880
|
+
return 'image/avif';
|
|
1881
|
+
if (normalized.endsWith('.ico'))
|
|
1882
|
+
return 'image/x-icon';
|
|
1883
|
+
if (normalized.endsWith('.mp4'))
|
|
1884
|
+
return 'video/mp4';
|
|
1885
|
+
if (normalized.endsWith('.webm'))
|
|
1886
|
+
return 'video/webm';
|
|
1887
|
+
if (normalized.endsWith('.mp3'))
|
|
1888
|
+
return 'audio/mpeg';
|
|
1889
|
+
if (normalized.endsWith('.wav'))
|
|
1890
|
+
return 'audio/wav';
|
|
1891
|
+
if (normalized.endsWith('.pdf'))
|
|
1892
|
+
return 'application/pdf';
|
|
1893
|
+
if (normalized.endsWith('.wasm'))
|
|
1894
|
+
return 'application/wasm';
|
|
1895
|
+
if (normalized.endsWith('.woff'))
|
|
1896
|
+
return 'font/woff';
|
|
1897
|
+
if (normalized.endsWith('.woff2'))
|
|
1898
|
+
return 'font/woff2';
|
|
1786
1899
|
return 'application/octet-stream';
|
|
1787
1900
|
}
|
|
1788
1901
|
function formatByteSize(bytes) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quadient/skillshare",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Skillshare CLI for installing and publishing Quadient skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"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
|
}
|