@quadient/skillshare 1.6.2 → 1.8.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 +37 -0
- package/dist/cli-args.js +86 -0
- package/dist/help.js +167 -0
- package/dist/index.js +294 -126
- package/dist/install-files.js +111 -0
- package/dist/teams-auth.js +304 -0
- package/dist/teams-watch.js +45 -0
- package/dist/teams.js +142 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -19,8 +19,45 @@ bun run index.ts install 12 0.1.0 --target ./installed
|
|
|
19
19
|
bun run index.ts install --skill-version-id 42 --target ./installed
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
## Teams MCP
|
|
23
|
+
|
|
24
|
+
Starting with 1.7.0, `skillshare teams login` opens a dedicated local browser profile,
|
|
25
|
+
connects your own Teams account to Skillshare and verifies the connection. Sign in with
|
|
26
|
+
the same Microsoft account as Skillshare. The server refreshes captured tokens in the
|
|
27
|
+
background. `teams watch` renews an expired Microsoft grant through local browser SSO.
|
|
28
|
+
Keep that process running for automatic recovery beyond the SPA refresh token lifetime;
|
|
29
|
+
another interactive CLI login is needed only when SSO cannot complete, for example MFA.
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
skillshare login
|
|
33
|
+
skillshare teams login
|
|
34
|
+
skillshare teams status
|
|
35
|
+
skillshare teams test
|
|
36
|
+
skillshare teams watch
|
|
37
|
+
skillshare teams access fullAccess
|
|
38
|
+
skillshare teams upload ./report.pdf
|
|
39
|
+
skillshare teams download '<file-webUrl-or-download-link>' --output ./report.pdf
|
|
40
|
+
skillshare teams access readOnly
|
|
41
|
+
skillshare teams logout
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Use `teams login local` for localhost or `--server <https-origin>` for another host.
|
|
45
|
+
`login` accepts `--server` too. Browser options: `--browser chrome|msedge|chromium`,
|
|
46
|
+
`--headless`, and `--browser-profile <dedicated-profile-directory>`.
|
|
47
|
+
Chrome is the default on macOS/Linux, Edge on Windows; bundled Chromium needs
|
|
48
|
+
`npx playwright install chromium` first. See [Teams MCP](../docs/teams-mcp.md) for
|
|
49
|
+
permissions, Microsoft session lifetime and file-tool differences in the hosted server.
|
|
50
|
+
|
|
22
51
|
## Behavior
|
|
23
52
|
|
|
53
|
+
Starting with 1.8.0:
|
|
54
|
+
|
|
55
|
+
- `install` and `update` download into a staging directory first, so a failed transfer leaves the existing install untouched. Files that a new version no longer contains are removed.
|
|
56
|
+
- `installed.json` records a SHA-256 manifest of installed files. `update` skips installs with local changes and `install` refuses to overwrite them, or a directory that Skillshare did not install, unless you confirm interactively or pass `--force`. Installs recorded by older versions are not checked until they are updated once.
|
|
57
|
+
- Interactive mode shows the signed-in user and server, checks installed skills in parallel, and reports removed skills or failed checks as warnings instead of stopping.
|
|
58
|
+
- `login` prints the sign-in URL in case no browser opens, and stops waiting after 5 minutes.
|
|
59
|
+
- Unknown options are rejected with a suggestion. `skillshare <command> --help`, `skillshare help <command>` and `skillshare --version` are available.
|
|
60
|
+
|
|
24
61
|
- Starting with 1.6.2, `login`, `git configure` and API commands without a stored session default to `https://skillshare.quadientcloudpreprod.eu`. `login local` and `git configure local` still select `http://localhost:5050`.
|
|
25
62
|
- Existing sessions keep their saved server. After preprod cutover, run `skillshare login` again to replace a UAT session with a preprod session; stored tokens are not reused across hosts.
|
|
26
63
|
- Uploaded/installed records retain their registry URL. Legacy records without a URL still belong to UAT and are not silently reassigned to preprod.
|
package/dist/cli-args.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { BOOLEAN_FLAGS, COMMANDS, GLOBAL_FLAGS, findCommand } from './help.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parses options for a command. With a command name, unknown options are rejected
|
|
4
|
+
* before a value is read, so a typo gets a suggestion instead of a missing-value error.
|
|
5
|
+
*/
|
|
6
|
+
export function parseCliArgs(args, commandName) {
|
|
7
|
+
const flags = {};
|
|
8
|
+
const positionals = [];
|
|
9
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
10
|
+
const current = args[index];
|
|
11
|
+
if (!current)
|
|
12
|
+
continue;
|
|
13
|
+
if (current === '-h') {
|
|
14
|
+
flags.help = 'true';
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (!current.startsWith('--') || current === '--') {
|
|
18
|
+
positionals.push(current);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const separator = current.indexOf('=');
|
|
22
|
+
const key = separator > 0 ? current.slice(2, separator) : current.slice(2);
|
|
23
|
+
if (commandName) {
|
|
24
|
+
assertKnownFlag(commandName, key);
|
|
25
|
+
}
|
|
26
|
+
if (separator > 0) {
|
|
27
|
+
flags[key] = current.slice(separator + 1);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const value = args[index + 1];
|
|
31
|
+
if (BOOLEAN_FLAGS.has(key)) {
|
|
32
|
+
// Booleans never consume a following positional, only an explicit true/false.
|
|
33
|
+
if (value === 'true' || value === 'false') {
|
|
34
|
+
flags[key] = value;
|
|
35
|
+
index += 1;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
flags[key] = 'true';
|
|
39
|
+
}
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (value === undefined || value.startsWith('--')) {
|
|
43
|
+
throw new Error(`Missing value for ${current}.`);
|
|
44
|
+
}
|
|
45
|
+
flags[key] = value;
|
|
46
|
+
index += 1;
|
|
47
|
+
}
|
|
48
|
+
return { flags, positionals };
|
|
49
|
+
}
|
|
50
|
+
function assertKnownFlag(commandName, name) {
|
|
51
|
+
const command = findCommand(commandName);
|
|
52
|
+
if (!command)
|
|
53
|
+
return;
|
|
54
|
+
const allowed = [...GLOBAL_FLAGS, ...command.flags];
|
|
55
|
+
if (allowed.includes(name))
|
|
56
|
+
return;
|
|
57
|
+
const suggestion = suggest(name, allowed);
|
|
58
|
+
throw new Error(`Unknown option --${name} for "${command.name}".${suggestion ? ` Did you mean --${suggestion}?` : ''} Run "skillshare ${command.name} --help" for options.`);
|
|
59
|
+
}
|
|
60
|
+
export function formatUnknownCommand(name) {
|
|
61
|
+
const suggestion = suggest(name, COMMANDS.map((command) => command.name));
|
|
62
|
+
return `Unknown command '${name}'.${suggestion ? ` Did you mean '${suggestion}'?` : ''} Run "skillshare --help" for the list of commands.`;
|
|
63
|
+
}
|
|
64
|
+
export function suggest(input, candidates) {
|
|
65
|
+
let best = null;
|
|
66
|
+
for (const candidate of candidates) {
|
|
67
|
+
const distance = candidate.startsWith(input) || input.startsWith(candidate) ? 1 : levenshtein(input, candidate);
|
|
68
|
+
if (distance <= 2 && (!best || distance < best.distance)) {
|
|
69
|
+
best = { candidate, distance };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return best?.candidate ?? null;
|
|
73
|
+
}
|
|
74
|
+
function levenshtein(left, right) {
|
|
75
|
+
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
76
|
+
for (let i = 1; i <= left.length; i += 1) {
|
|
77
|
+
let diagonal = previous[0];
|
|
78
|
+
previous[0] = i;
|
|
79
|
+
for (let j = 1; j <= right.length; j += 1) {
|
|
80
|
+
const above = previous[j];
|
|
81
|
+
previous[j] = Math.min(above + 1, previous[j - 1] + 1, diagonal + (left[i - 1] === right[j - 1] ? 0 : 1));
|
|
82
|
+
diagonal = above;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return previous[right.length];
|
|
86
|
+
}
|
package/dist/help.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
export const GLOBAL_FLAGS = ['token', 'server', 'help'];
|
|
3
|
+
export const BOOLEAN_FLAGS = new Set(['dry-run', 'force', 'headless', 'help']);
|
|
4
|
+
export const COMMANDS = [
|
|
5
|
+
{
|
|
6
|
+
name: 'login',
|
|
7
|
+
summary: 'Sign in through the browser and store a CLI session',
|
|
8
|
+
usage: ['login [local] [--server <url>]'],
|
|
9
|
+
description: 'Opens the Skillshare sign-in page. If no browser opens, copy the printed URL into one.',
|
|
10
|
+
examples: ['skillshare login', 'skillshare login local'],
|
|
11
|
+
flags: [],
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'logout',
|
|
15
|
+
summary: 'Remove the stored CLI session',
|
|
16
|
+
usage: ['logout'],
|
|
17
|
+
flags: [],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'whoami',
|
|
21
|
+
summary: 'Show the signed-in user',
|
|
22
|
+
usage: ['whoami'],
|
|
23
|
+
flags: [],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'list',
|
|
27
|
+
summary: 'List skills in the registry',
|
|
28
|
+
usage: ['list'],
|
|
29
|
+
flags: [],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'show',
|
|
33
|
+
summary: 'Show a skill or one of its versions',
|
|
34
|
+
usage: ['show <skillId> [version]'],
|
|
35
|
+
flags: [],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'install',
|
|
39
|
+
summary: 'Install a skill for your coding agents',
|
|
40
|
+
usage: [
|
|
41
|
+
'install [skillId|skillName] [version]',
|
|
42
|
+
'install <skillId|skillName> [version] --target <directory> [--force]',
|
|
43
|
+
'install --skill-version-id <id> --target <directory> [--force]',
|
|
44
|
+
],
|
|
45
|
+
description: 'Without --target the CLI asks for the version, scope and agents. Files that you changed locally are not overwritten unless you confirm or pass --force.',
|
|
46
|
+
options: [
|
|
47
|
+
['--target <directory>', 'Install into this directory instead of agent skill folders'],
|
|
48
|
+
['--skill-version-id <id>', 'Install an exact published skill version'],
|
|
49
|
+
['--force', 'Overwrite local changes in the target directory'],
|
|
50
|
+
],
|
|
51
|
+
examples: ['skillshare install', 'skillshare install "Prompt Lint"', 'skillshare install 12 0.1.0 --target ./installed'],
|
|
52
|
+
flags: ['target', 'skill-version-id', 'force'],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'update',
|
|
56
|
+
summary: 'Update installed skills to their latest versions',
|
|
57
|
+
usage: ['update [skillId|skillName] [--target <directory>] [--dry-run] [--force]'],
|
|
58
|
+
description: 'Removes files that the new version no longer contains. Installs with local changes are skipped unless you pass --force.',
|
|
59
|
+
options: [
|
|
60
|
+
['--target <directory>', 'Only update the install in this directory'],
|
|
61
|
+
['--dry-run', 'Print planned changes without writing files'],
|
|
62
|
+
['--force', 'Overwrite local changes'],
|
|
63
|
+
],
|
|
64
|
+
examples: ['skillshare update', 'skillshare update "Prompt Lint" --dry-run'],
|
|
65
|
+
flags: ['target', 'dry-run', 'force'],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'upload',
|
|
69
|
+
summary: 'Create a skill or publish a new version from a directory',
|
|
70
|
+
usage: ['upload <directory> --version <version> [--skill-id <id>] [--name <name>] [--description <text>]'],
|
|
71
|
+
options: [
|
|
72
|
+
['--version <version>', 'Version to publish'],
|
|
73
|
+
['--skill-id <id>', 'Publish to this existing skill'],
|
|
74
|
+
['--name <name>', 'Name for a new skill (defaults to SKILL.md)'],
|
|
75
|
+
['--description <text>', 'Description for a new skill (defaults to SKILL.md)'],
|
|
76
|
+
],
|
|
77
|
+
examples: ['skillshare upload ./prompt-lint --version 0.2.0'],
|
|
78
|
+
flags: ['version', 'skill-id', 'name', 'description'],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'pages',
|
|
82
|
+
summary: 'Publish and inspect authenticated pages',
|
|
83
|
+
usage: [
|
|
84
|
+
'pages list',
|
|
85
|
+
'pages show <slug>',
|
|
86
|
+
'pages runtime <slug>',
|
|
87
|
+
'pages upload <directory> [--slug <slug>]',
|
|
88
|
+
'pages dev [directory] [--slug <slug>] [--port <port>] [--build <command> --watch-dir <sources>]',
|
|
89
|
+
' [--celld <executable>] [--user-id <id>] [--user-name <name>] [--user-email <email>]',
|
|
90
|
+
],
|
|
91
|
+
examples: ['skillshare pages upload ./site --slug release-notes', 'skillshare pages dev . --slug my-app'],
|
|
92
|
+
flags: ['slug'],
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'teams',
|
|
96
|
+
summary: 'Connect Microsoft Teams to the Skillshare Teams MCP',
|
|
97
|
+
usage: [
|
|
98
|
+
'teams login [local] [--browser chrome|msedge|chromium] [--headless] [--browser-profile <directory>]',
|
|
99
|
+
'teams watch [local] [--browser chrome|msedge|chromium]',
|
|
100
|
+
'teams status | test | logout',
|
|
101
|
+
'teams access readOnly|fullAccess',
|
|
102
|
+
'teams upload <file>',
|
|
103
|
+
'teams download <url> --output <new-file>',
|
|
104
|
+
],
|
|
105
|
+
flags: ['browser', 'headless', 'browser-profile', 'output'],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'git',
|
|
109
|
+
summary: 'Register Skillshare in Git Credential Manager',
|
|
110
|
+
usage: ['git configure [local]', 'git unconfigure [local]'],
|
|
111
|
+
flags: [],
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
const COMMAND_ALIASES = { page: 'pages' };
|
|
115
|
+
export function findCommand(name) {
|
|
116
|
+
if (!name)
|
|
117
|
+
return undefined;
|
|
118
|
+
const resolved = COMMAND_ALIASES[name] ?? name;
|
|
119
|
+
return COMMANDS.find((command) => command.name === resolved);
|
|
120
|
+
}
|
|
121
|
+
export function renderUsage(defaultUrl) {
|
|
122
|
+
const width = Math.max(...COMMANDS.map((command) => command.name.length));
|
|
123
|
+
return [
|
|
124
|
+
'Usage: skillshare [command] [options]',
|
|
125
|
+
'',
|
|
126
|
+
'Run without a command for the interactive mode.',
|
|
127
|
+
'',
|
|
128
|
+
'Commands:',
|
|
129
|
+
...COMMANDS.map((command) => ` ${command.name.padEnd(width)} ${command.summary}`),
|
|
130
|
+
'',
|
|
131
|
+
'Options:',
|
|
132
|
+
' --token <token> Bearer token for API requests (overrides SKILLSHARE_TOKEN)',
|
|
133
|
+
' --server <url> Skillshare origin for login and Teams commands',
|
|
134
|
+
' -h, --help Show help for a command',
|
|
135
|
+
' -v, --version Show the CLI version',
|
|
136
|
+
'',
|
|
137
|
+
`Default server: ${defaultUrl}`,
|
|
138
|
+
'A stored session keeps its server; run "skillshare login" to switch to the default.',
|
|
139
|
+
'Run "skillshare <command> --help" for details.',
|
|
140
|
+
].join('\n');
|
|
141
|
+
}
|
|
142
|
+
export function renderCommandHelp(command) {
|
|
143
|
+
const lines = [`${command.summary}.`, '', 'Usage:', ...command.usage.map((usage) => ` skillshare ${usage}`)];
|
|
144
|
+
if (command.description)
|
|
145
|
+
lines.push('', command.description);
|
|
146
|
+
if (command.options?.length) {
|
|
147
|
+
const width = Math.max(...command.options.map(([flag]) => flag.length));
|
|
148
|
+
lines.push('', 'Options:', ...command.options.map(([flag, text]) => ` ${flag.padEnd(width)} ${text}`));
|
|
149
|
+
}
|
|
150
|
+
if (command.examples?.length)
|
|
151
|
+
lines.push('', 'Examples:', ...command.examples.map((example) => ` ${example}`));
|
|
152
|
+
return lines.join('\n');
|
|
153
|
+
}
|
|
154
|
+
export function readCliVersion() {
|
|
155
|
+
// dist/index.js and the source index.ts sit at different depths below package.json.
|
|
156
|
+
for (const candidate of ['../package.json', './package.json']) {
|
|
157
|
+
try {
|
|
158
|
+
const parsed = JSON.parse(readFileSync(new URL(candidate, import.meta.url), 'utf8'));
|
|
159
|
+
if (parsed.name === '@quadient/skillshare' && parsed.version)
|
|
160
|
+
return parsed.version;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Try the next location.
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return 'unknown';
|
|
167
|
+
}
|