@skillmarkdown/cli 0.2.1 → 0.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 +31 -2
- package/dist/cli.js +6 -2
- package/dist/commands/init.js +41 -10
- package/dist/commands/login.js +23 -114
- package/dist/commands/logout.js +16 -9
- package/dist/commands/validate.js +56 -49
- package/dist/lib/{auth-config.js → auth/config.js} +4 -4
- package/dist/lib/{firebase-auth.js → auth/firebase-auth.js} +1 -1
- package/dist/lib/{github-device-flow.js → auth/github-device-flow.js} +1 -1
- package/dist/lib/auth/login-flags.js +22 -0
- package/dist/lib/auth/login-flow.js +56 -0
- package/dist/lib/auth/login-status.js +34 -0
- package/dist/lib/scaffold/scaffold.js +79 -0
- package/dist/lib/{skill-spec.js → scaffold/skill-spec.js} +2 -1
- package/dist/lib/scaffold/templates/index.js +18 -0
- package/dist/lib/scaffold/templates/resources.js +169 -0
- package/dist/lib/scaffold/templates/skill-markdown.js +38 -0
- package/dist/lib/{cli-text.js → shared/cli-text.js} +1 -1
- package/dist/lib/{upstream-validator.js → validation/upstream-validator.js} +8 -0
- package/dist/lib/{validator.js → validation/validator.js} +1 -1
- package/package.json +2 -2
- package/dist/lib/scaffold.js +0 -34
- package/dist/lib/templates.js +0 -23
- /package/dist/lib/{auth-defaults.js → auth/defaults.js} +0 -0
- /package/dist/lib/{auth-session.js → auth/session.js} +0 -0
- /package/dist/lib/{normalize-name.js → scaffold/normalize-name.js} +0 -0
- /package/dist/lib/{command-output.js → shared/command-output.js} +0 -0
- /package/dist/lib/{http.js → shared/http.js} +0 -0
package/README.md
CHANGED
|
@@ -28,7 +28,31 @@ npx @skillmarkdown/cli init
|
|
|
28
28
|
skillmd init
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Default `init` creates the minimal filesystem scaffold:
|
|
32
|
+
|
|
33
|
+
- `SKILL.md` only
|
|
34
|
+
|
|
35
|
+
and runs spec validation.
|
|
36
|
+
|
|
37
|
+
To scaffold the full verbose template:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
skillmd init --template verbose
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The `verbose` template includes `SKILL.md`, `.gitignore`, and optional directories (`scripts/`, `references/`, `assets/`) with starter placeholder content and `.gitkeep`, then runs strict validation.
|
|
44
|
+
|
|
45
|
+
Included starter files:
|
|
46
|
+
|
|
47
|
+
- `scripts/README.md`
|
|
48
|
+
- `scripts/extract.py`
|
|
49
|
+
- `references/REFERENCE.md`
|
|
50
|
+
- `references/FORMS.md`
|
|
51
|
+
- `assets/README.md`
|
|
52
|
+
- `assets/report-template.md`
|
|
53
|
+
- `assets/lookup-table.csv`
|
|
54
|
+
|
|
55
|
+
`SKILL.md` content is the same across `minimal` and `verbose` templates. Template selection only changes extra scaffold files/directories around `SKILL.md`.
|
|
32
56
|
|
|
33
57
|
To skip validation during init:
|
|
34
58
|
|
|
@@ -36,6 +60,11 @@ To skip validation during init:
|
|
|
36
60
|
skillmd init --no-validate
|
|
37
61
|
```
|
|
38
62
|
|
|
63
|
+
Supported templates:
|
|
64
|
+
|
|
65
|
+
- `minimal` (default)
|
|
66
|
+
- `verbose`
|
|
67
|
+
|
|
39
68
|
### Validate a skill folder
|
|
40
69
|
|
|
41
70
|
```bash
|
|
@@ -77,7 +106,7 @@ By default, `login` uses the project’s built-in development config. You can ov
|
|
|
77
106
|
- `SKILLMD_FIREBASE_PROJECT_ID`
|
|
78
107
|
|
|
79
108
|
See `.env.example` for the expected keys.
|
|
80
|
-
Maintainers: built-in defaults are defined in `src/lib/auth
|
|
109
|
+
Maintainers: built-in defaults are defined in `src/lib/auth/defaults.ts`.
|
|
81
110
|
|
|
82
111
|
Example override file:
|
|
83
112
|
|
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ const init_1 = require("./commands/init");
|
|
|
5
5
|
const login_1 = require("./commands/login");
|
|
6
6
|
const logout_1 = require("./commands/logout");
|
|
7
7
|
const validate_1 = require("./commands/validate");
|
|
8
|
-
const cli_text_1 = require("./lib/cli-text");
|
|
8
|
+
const cli_text_1 = require("./lib/shared/cli-text");
|
|
9
9
|
const COMMAND_HANDLERS = {
|
|
10
10
|
init: init_1.runInitCommand,
|
|
11
11
|
validate: validate_1.runValidateCommand,
|
|
@@ -30,4 +30,8 @@ async function main() {
|
|
|
30
30
|
console.error(cli_text_1.ROOT_USAGE);
|
|
31
31
|
process.exitCode = 1;
|
|
32
32
|
}
|
|
33
|
-
void main()
|
|
33
|
+
void main().catch((error) => {
|
|
34
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
35
|
+
console.error(`skillmd: ${message}`);
|
|
36
|
+
process.exitCode = 1;
|
|
37
|
+
});
|
package/dist/commands/init.js
CHANGED
|
@@ -1,26 +1,57 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runInitCommand = runInitCommand;
|
|
4
|
-
const cli_text_1 = require("../lib/cli-text");
|
|
5
|
-
const command_output_1 = require("../lib/command-output");
|
|
6
|
-
const scaffold_1 = require("../lib/scaffold");
|
|
7
|
-
const validator_1 = require("../lib/validator");
|
|
4
|
+
const cli_text_1 = require("../lib/shared/cli-text");
|
|
5
|
+
const command_output_1 = require("../lib/shared/command-output");
|
|
6
|
+
const scaffold_1 = require("../lib/scaffold/scaffold");
|
|
7
|
+
const validator_1 = require("../lib/validation/validator");
|
|
8
|
+
function parseInitArgs(args) {
|
|
9
|
+
let skipValidation = false;
|
|
10
|
+
let template = "minimal";
|
|
11
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
12
|
+
const arg = args[index];
|
|
13
|
+
if (arg === "--no-validate") {
|
|
14
|
+
skipValidation = true;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (arg === "--template") {
|
|
18
|
+
const nextValue = args[index + 1];
|
|
19
|
+
const resolved = nextValue ? (0, scaffold_1.resolveInitTemplateId)(nextValue) : null;
|
|
20
|
+
if (!resolved) {
|
|
21
|
+
return { skipValidation: false, template: "minimal", valid: false };
|
|
22
|
+
}
|
|
23
|
+
template = resolved;
|
|
24
|
+
index += 1;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (arg.startsWith("--template=")) {
|
|
28
|
+
const value = arg.slice("--template=".length);
|
|
29
|
+
const resolved = (0, scaffold_1.resolveInitTemplateId)(value);
|
|
30
|
+
if (!resolved) {
|
|
31
|
+
return { skipValidation: false, template: "minimal", valid: false };
|
|
32
|
+
}
|
|
33
|
+
template = resolved;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
return { skipValidation: false, template: "minimal", valid: false };
|
|
37
|
+
}
|
|
38
|
+
return { skipValidation, template, valid: true };
|
|
39
|
+
}
|
|
8
40
|
function runInitCommand(args, options = {}) {
|
|
9
41
|
const cwd = options.cwd ?? process.cwd();
|
|
10
|
-
const validateSkillFn = options.validateSkill ??
|
|
11
|
-
const skipValidation = args
|
|
12
|
-
|
|
13
|
-
if (hasUnsupportedArgs) {
|
|
42
|
+
const validateSkillFn = options.validateSkill ?? validator_1.validateSkill;
|
|
43
|
+
const { skipValidation, template, valid } = parseInitArgs(args);
|
|
44
|
+
if (!valid) {
|
|
14
45
|
return (0, command_output_1.failWithUsage)("skillmd init: unsupported argument(s)", cli_text_1.INIT_USAGE);
|
|
15
46
|
}
|
|
16
47
|
try {
|
|
17
|
-
const result = (0, scaffold_1.scaffoldSkillInDirectory)(cwd);
|
|
48
|
+
const result = (0, scaffold_1.scaffoldSkillInDirectory)(cwd, { template });
|
|
18
49
|
console.log(`Initialized skill '${result.skillName}'.`);
|
|
19
50
|
if (skipValidation) {
|
|
20
51
|
console.log("Validation skipped (--no-validate).");
|
|
21
52
|
return 0;
|
|
22
53
|
}
|
|
23
|
-
const validation = validateSkillFn(cwd);
|
|
54
|
+
const validation = validateSkillFn(cwd, { strict: result.template === "verbose" });
|
|
24
55
|
(0, command_output_1.printValidationResult)(validation);
|
|
25
56
|
if (validation.status === "passed") {
|
|
26
57
|
return 0;
|
package/dist/commands/login.js
CHANGED
|
@@ -1,64 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runLoginCommand = runLoginCommand;
|
|
4
|
-
const cli_text_1 = require("../lib/cli-text");
|
|
5
|
-
const command_output_1 = require("../lib/command-output");
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const github_device_flow_1 = require("../lib/github-device-flow");
|
|
9
|
-
const firebase_auth_1 = require("../lib/firebase-auth");
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
for (const arg of args) {
|
|
14
|
-
if (arg === "--status") {
|
|
15
|
-
status = true;
|
|
16
|
-
continue;
|
|
17
|
-
}
|
|
18
|
-
if (arg === "--reauth") {
|
|
19
|
-
reauth = true;
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
|
-
return { status: false, reauth: false, valid: false };
|
|
23
|
-
}
|
|
24
|
-
if (status && reauth) {
|
|
25
|
-
return { status: false, reauth: false, valid: false };
|
|
26
|
-
}
|
|
27
|
-
return { status, reauth, valid: true };
|
|
28
|
-
}
|
|
29
|
-
function formatSessionProject(session, currentConfigProjectId) {
|
|
30
|
-
if (!session.projectId) {
|
|
31
|
-
if (currentConfigProjectId) {
|
|
32
|
-
return { label: `unknown (current config: ${currentConfigProjectId})`, mismatch: false };
|
|
33
|
-
}
|
|
34
|
-
return { label: "unknown", mismatch: false };
|
|
35
|
-
}
|
|
36
|
-
return {
|
|
37
|
-
label: session.projectId,
|
|
38
|
-
mismatch: Boolean(currentConfigProjectId && session.projectId !== currentConfigProjectId),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
function printSessionStatus(session, currentConfigProjectId) {
|
|
42
|
-
if (!session) {
|
|
43
|
-
console.log("Not logged in.");
|
|
44
|
-
return 1;
|
|
45
|
-
}
|
|
46
|
-
const project = formatSessionProject(session, currentConfigProjectId);
|
|
47
|
-
if (session.email) {
|
|
48
|
-
console.log(`Logged in with GitHub as ${session.email} (project: ${project.label}).`);
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
console.log(`Logged in with GitHub (uid: ${session.uid}, project: ${project.label}).`);
|
|
52
|
-
}
|
|
53
|
-
if (project.mismatch && currentConfigProjectId) {
|
|
54
|
-
console.log(`Current CLI config targets project '${currentConfigProjectId}'. ` +
|
|
55
|
-
"Run 'skillmd login --reauth' to switch projects.");
|
|
56
|
-
}
|
|
57
|
-
return 0;
|
|
58
|
-
}
|
|
4
|
+
const cli_text_1 = require("../lib/shared/cli-text");
|
|
5
|
+
const command_output_1 = require("../lib/shared/command-output");
|
|
6
|
+
const config_1 = require("../lib/auth/config");
|
|
7
|
+
const session_1 = require("../lib/auth/session");
|
|
8
|
+
const github_device_flow_1 = require("../lib/auth/github-device-flow");
|
|
9
|
+
const firebase_auth_1 = require("../lib/auth/firebase-auth");
|
|
10
|
+
const login_flow_1 = require("../lib/auth/login-flow");
|
|
11
|
+
const login_flags_1 = require("../lib/auth/login-flags");
|
|
12
|
+
const login_status_1 = require("../lib/auth/login-status");
|
|
59
13
|
function requireConfig(env) {
|
|
60
14
|
try {
|
|
61
|
-
return (0,
|
|
15
|
+
return (0, config_1.getLoginEnvConfig)(env);
|
|
62
16
|
}
|
|
63
17
|
catch (error) {
|
|
64
18
|
const message = error instanceof Error ? error.message : "invalid login configuration";
|
|
@@ -68,72 +22,27 @@ function requireConfig(env) {
|
|
|
68
22
|
}
|
|
69
23
|
}
|
|
70
24
|
async function runLoginCommand(args, options = {}) {
|
|
71
|
-
const { status, reauth, valid } =
|
|
25
|
+
const { status, reauth, valid } = (0, login_flags_1.parseLoginFlags)(args);
|
|
72
26
|
if (!valid) {
|
|
73
27
|
return (0, command_output_1.failWithUsage)("skillmd login: unsupported argument(s)", cli_text_1.LOGIN_USAGE);
|
|
74
28
|
}
|
|
75
|
-
const readSessionFn = options.readSession ??
|
|
76
|
-
const writeSessionFn = options.writeSession ??
|
|
77
|
-
const clearSessionFn = options.clearSession ??
|
|
29
|
+
const readSessionFn = options.readSession ?? session_1.readAuthSession;
|
|
30
|
+
const writeSessionFn = options.writeSession ?? session_1.writeAuthSession;
|
|
31
|
+
const clearSessionFn = options.clearSession ?? session_1.clearAuthSession;
|
|
78
32
|
try {
|
|
79
33
|
const config = requireConfig(options.env ?? process.env);
|
|
80
34
|
if (status) {
|
|
81
|
-
return printSessionStatus(readSessionFn(), config.firebaseProjectId);
|
|
82
|
-
}
|
|
83
|
-
const existingSession = readSessionFn();
|
|
84
|
-
if (existingSession && !reauth) {
|
|
85
|
-
const verifyRefreshTokenFn = options.verifyRefreshToken ?? firebase_auth_1.verifyFirebaseRefreshToken;
|
|
86
|
-
try {
|
|
87
|
-
const validation = await verifyRefreshTokenFn(config.firebaseApiKey, existingSession.refreshToken);
|
|
88
|
-
if (validation.valid) {
|
|
89
|
-
const project = formatSessionProject(existingSession, config.firebaseProjectId);
|
|
90
|
-
if (existingSession.email) {
|
|
91
|
-
console.log(`Already logged in as ${existingSession.email} (project: ${project.label}). ` +
|
|
92
|
-
"Run 'skillmd logout' first.");
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
console.log(`Already logged in (uid: ${existingSession.uid}, project: ${project.label}). ` +
|
|
96
|
-
"Run 'skillmd logout' first.");
|
|
97
|
-
}
|
|
98
|
-
if (project.mismatch) {
|
|
99
|
-
console.log(`Current CLI config targets project '${config.firebaseProjectId}'. ` +
|
|
100
|
-
"Run 'skillmd login --reauth' to switch projects.");
|
|
101
|
-
}
|
|
102
|
-
return 0;
|
|
103
|
-
}
|
|
104
|
-
clearSessionFn();
|
|
105
|
-
console.log("Existing session is no longer valid. Starting re-authentication.");
|
|
106
|
-
}
|
|
107
|
-
catch (error) {
|
|
108
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
109
|
-
console.error(`skillmd login: unable to verify existing session (${message}). ` +
|
|
110
|
-
"Keeping current session. Run 'skillmd login --reauth' to force reauthentication.");
|
|
111
|
-
return 1;
|
|
112
|
-
}
|
|
35
|
+
return (0, login_status_1.printSessionStatus)(readSessionFn(), config.firebaseProjectId);
|
|
113
36
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const firebaseSession = await signInFn(config.firebaseApiKey, token.accessToken);
|
|
123
|
-
writeSessionFn({
|
|
124
|
-
provider: "github",
|
|
125
|
-
uid: firebaseSession.localId,
|
|
126
|
-
email: firebaseSession.email,
|
|
127
|
-
refreshToken: firebaseSession.refreshToken,
|
|
128
|
-
projectId: config.firebaseProjectId,
|
|
37
|
+
return await (0, login_flow_1.executeLoginFlow)(config, reauth, {
|
|
38
|
+
readSession: readSessionFn,
|
|
39
|
+
writeSession: writeSessionFn,
|
|
40
|
+
clearSession: clearSessionFn,
|
|
41
|
+
requestDeviceCode: options.requestDeviceCode ?? github_device_flow_1.requestDeviceCode,
|
|
42
|
+
pollForAccessToken: options.pollForAccessToken ?? github_device_flow_1.pollForAccessToken,
|
|
43
|
+
signInWithGitHubAccessToken: options.signInWithGitHubAccessToken ?? firebase_auth_1.signInWithGitHubAccessToken,
|
|
44
|
+
verifyRefreshToken: options.verifyRefreshToken ?? firebase_auth_1.verifyFirebaseRefreshToken,
|
|
129
45
|
});
|
|
130
|
-
if (firebaseSession.email) {
|
|
131
|
-
console.log(`Login successful. Signed in as ${firebaseSession.email} (project: ${config.firebaseProjectId}).`);
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
console.log(`Login successful (project: ${config.firebaseProjectId}).`);
|
|
135
|
-
}
|
|
136
|
-
return 0;
|
|
137
46
|
}
|
|
138
47
|
catch (error) {
|
|
139
48
|
const message = error instanceof Error ? error.message : "Unknown error";
|
package/dist/commands/logout.js
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runLogoutCommand = runLogoutCommand;
|
|
4
|
-
const cli_text_1 = require("../lib/cli-text");
|
|
5
|
-
const command_output_1 = require("../lib/command-output");
|
|
6
|
-
const
|
|
4
|
+
const cli_text_1 = require("../lib/shared/cli-text");
|
|
5
|
+
const command_output_1 = require("../lib/shared/command-output");
|
|
6
|
+
const session_1 = require("../lib/auth/session");
|
|
7
7
|
function runLogoutCommand(args, options = {}) {
|
|
8
8
|
if (args.length > 0) {
|
|
9
9
|
return (0, command_output_1.failWithUsage)("skillmd logout: unsupported argument(s)", cli_text_1.LOGOUT_USAGE);
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
try {
|
|
12
|
+
const clearSessionFn = options.clearSession ?? session_1.clearAuthSession;
|
|
13
|
+
const removed = clearSessionFn();
|
|
14
|
+
if (removed) {
|
|
15
|
+
console.log("Logged out.");
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
console.log("No active session to log out.");
|
|
15
19
|
return 0;
|
|
16
20
|
}
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
catch (error) {
|
|
22
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
23
|
+
console.error(`skillmd logout: ${message}`);
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
19
26
|
}
|
|
@@ -2,61 +2,68 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runValidateCommand = runValidateCommand;
|
|
4
4
|
const node_path_1 = require("node:path");
|
|
5
|
-
const cli_text_1 = require("../lib/cli-text");
|
|
6
|
-
const command_output_1 = require("../lib/command-output");
|
|
7
|
-
const upstream_validator_1 = require("../lib/upstream-validator");
|
|
8
|
-
const validator_1 = require("../lib/validator");
|
|
5
|
+
const cli_text_1 = require("../lib/shared/cli-text");
|
|
6
|
+
const command_output_1 = require("../lib/shared/command-output");
|
|
7
|
+
const upstream_validator_1 = require("../lib/validation/upstream-validator");
|
|
8
|
+
const validator_1 = require("../lib/validation/validator");
|
|
9
9
|
function runValidateCommand(args, options = {}) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
strict
|
|
19
|
-
|
|
10
|
+
try {
|
|
11
|
+
const cwd = options.cwd ?? process.cwd();
|
|
12
|
+
const validateLocal = options.validateLocal ?? validator_1.validateSkill;
|
|
13
|
+
const validateUpstream = options.validateUpstream ?? upstream_validator_1.validateWithSkillsRef;
|
|
14
|
+
let strict = false;
|
|
15
|
+
let parity = false;
|
|
16
|
+
let pathArg;
|
|
17
|
+
for (const arg of args) {
|
|
18
|
+
if (arg === "--strict") {
|
|
19
|
+
strict = true;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (arg === "--parity") {
|
|
23
|
+
parity = true;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (arg.startsWith("-")) {
|
|
27
|
+
return (0, command_output_1.failWithUsage)(`skillmd validate: unsupported flag '${arg}'`, cli_text_1.VALIDATE_USAGE);
|
|
28
|
+
}
|
|
29
|
+
if (pathArg) {
|
|
30
|
+
return (0, command_output_1.failWithUsage)("skillmd validate: accepts at most one path argument", cli_text_1.VALIDATE_USAGE);
|
|
31
|
+
}
|
|
32
|
+
pathArg = arg;
|
|
20
33
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
console.error(`Validation parity unavailable: ${upstream.message}. Install skills-ref to use --parity.`);
|
|
39
|
-
return 1;
|
|
40
|
-
}
|
|
41
|
-
if (validation.status === "passed" && upstream.status !== "passed") {
|
|
42
|
-
console.error("Validation parity mismatch: local validation passed but skills-ref failed.");
|
|
43
|
-
console.error(`skills-ref: ${upstream.message}`);
|
|
44
|
-
return 1;
|
|
34
|
+
const targetDir = pathArg ? (0, node_path_1.resolve)(cwd, pathArg) : cwd;
|
|
35
|
+
const validation = validateLocal(targetDir, { strict });
|
|
36
|
+
if (parity) {
|
|
37
|
+
const upstream = validateUpstream(targetDir);
|
|
38
|
+
if (upstream.status === "unavailable") {
|
|
39
|
+
console.error(`Validation parity unavailable: ${upstream.message}. Install skills-ref to use --parity.`);
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
if (validation.status === "passed" && upstream.status !== "passed") {
|
|
43
|
+
console.error("Validation parity mismatch: local validation passed but skills-ref failed.");
|
|
44
|
+
console.error(`skills-ref: ${upstream.message}`);
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
if (validation.status === "failed" && upstream.status !== "failed") {
|
|
48
|
+
console.error("Validation parity mismatch: local validation failed but skills-ref passed.");
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
45
51
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
(0, command_output_1.printValidationResult)(validation);
|
|
53
|
+
if (validation.status === "passed") {
|
|
54
|
+
if (parity) {
|
|
55
|
+
console.log("Validation parity passed (skills-ref).");
|
|
56
|
+
}
|
|
57
|
+
return 0;
|
|
49
58
|
}
|
|
50
|
-
}
|
|
51
|
-
(0, command_output_1.printValidationResult)(validation);
|
|
52
|
-
if (validation.status === "passed") {
|
|
53
59
|
if (parity) {
|
|
54
|
-
console.
|
|
60
|
+
console.error("Validation parity matched (skills-ref also failed).");
|
|
55
61
|
}
|
|
56
|
-
return
|
|
62
|
+
return 1;
|
|
57
63
|
}
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
catch (error) {
|
|
65
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
66
|
+
console.error(`skillmd validate: ${message}`);
|
|
67
|
+
return 1;
|
|
60
68
|
}
|
|
61
|
-
return 1;
|
|
62
69
|
}
|
|
@@ -5,7 +5,7 @@ exports.getDefaultUserEnvPath = getDefaultUserEnvPath;
|
|
|
5
5
|
const node_fs_1 = require("node:fs");
|
|
6
6
|
const node_os_1 = require("node:os");
|
|
7
7
|
const node_path_1 = require("node:path");
|
|
8
|
-
const
|
|
8
|
+
const defaults_1 = require("./defaults");
|
|
9
9
|
const USER_ENV_RELATIVE_PATH = ".skillmd/.env";
|
|
10
10
|
function parseDotEnv(content) {
|
|
11
11
|
const values = {};
|
|
@@ -52,9 +52,9 @@ function pickValue(...candidates) {
|
|
|
52
52
|
}
|
|
53
53
|
function getLoginEnvConfig(env = process.env, options = {}) {
|
|
54
54
|
const dotEnv = loadDotEnv(getDefaultUserEnvPath(options));
|
|
55
|
-
const githubClientId = pickValue(env.SKILLMD_GITHUB_CLIENT_ID, dotEnv.SKILLMD_GITHUB_CLIENT_ID,
|
|
56
|
-
const firebaseApiKey = pickValue(env.SKILLMD_FIREBASE_API_KEY, dotEnv.SKILLMD_FIREBASE_API_KEY,
|
|
57
|
-
const firebaseProjectId = pickValue(env.SKILLMD_FIREBASE_PROJECT_ID, dotEnv.SKILLMD_FIREBASE_PROJECT_ID,
|
|
55
|
+
const githubClientId = pickValue(env.SKILLMD_GITHUB_CLIENT_ID, dotEnv.SKILLMD_GITHUB_CLIENT_ID, defaults_1.DEFAULT_LOGIN_AUTH_CONFIG.githubClientId);
|
|
56
|
+
const firebaseApiKey = pickValue(env.SKILLMD_FIREBASE_API_KEY, dotEnv.SKILLMD_FIREBASE_API_KEY, defaults_1.DEFAULT_LOGIN_AUTH_CONFIG.firebaseApiKey);
|
|
57
|
+
const firebaseProjectId = pickValue(env.SKILLMD_FIREBASE_PROJECT_ID, dotEnv.SKILLMD_FIREBASE_PROJECT_ID, defaults_1.DEFAULT_LOGIN_AUTH_CONFIG.firebaseProjectId);
|
|
58
58
|
if (!githubClientId || !firebaseApiKey || !firebaseProjectId) {
|
|
59
59
|
throw new Error("missing login configuration");
|
|
60
60
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.signInWithGitHubAccessToken = signInWithGitHubAccessToken;
|
|
4
4
|
exports.verifyFirebaseRefreshToken = verifyFirebaseRefreshToken;
|
|
5
|
-
const http_1 = require("
|
|
5
|
+
const http_1 = require("../shared/http");
|
|
6
6
|
const FIREBASE_HTTP_TIMEOUT_MS = 10000;
|
|
7
7
|
async function parseJsonApiResponse(response, apiLabel) {
|
|
8
8
|
const text = await response.text();
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.requestDeviceCode = requestDeviceCode;
|
|
4
4
|
exports.pollForAccessToken = pollForAccessToken;
|
|
5
|
-
const http_1 = require("
|
|
5
|
+
const http_1 = require("../shared/http");
|
|
6
6
|
const GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
7
7
|
const GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
8
8
|
const GITHUB_HTTP_TIMEOUT_MS = 10000;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseLoginFlags = parseLoginFlags;
|
|
4
|
+
function parseLoginFlags(args) {
|
|
5
|
+
let status = false;
|
|
6
|
+
let reauth = false;
|
|
7
|
+
for (const arg of args) {
|
|
8
|
+
if (arg === "--status") {
|
|
9
|
+
status = true;
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
if (arg === "--reauth") {
|
|
13
|
+
reauth = true;
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
return { status: false, reauth: false, valid: false };
|
|
17
|
+
}
|
|
18
|
+
if (status && reauth) {
|
|
19
|
+
return { status: false, reauth: false, valid: false };
|
|
20
|
+
}
|
|
21
|
+
return { status, reauth, valid: true };
|
|
22
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeLoginFlow = executeLoginFlow;
|
|
4
|
+
const login_status_1 = require("./login-status");
|
|
5
|
+
async function executeLoginFlow(config, reauth, dependencies) {
|
|
6
|
+
const existingSession = dependencies.readSession();
|
|
7
|
+
if (existingSession && !reauth) {
|
|
8
|
+
try {
|
|
9
|
+
const validation = await dependencies.verifyRefreshToken(config.firebaseApiKey, existingSession.refreshToken);
|
|
10
|
+
if (validation.valid) {
|
|
11
|
+
const project = (0, login_status_1.formatSessionProject)(existingSession, config.firebaseProjectId);
|
|
12
|
+
if (existingSession.email) {
|
|
13
|
+
console.log(`Already logged in as ${existingSession.email} (project: ${project.label}). ` +
|
|
14
|
+
"Run 'skillmd logout' first.");
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
console.log(`Already logged in (uid: ${existingSession.uid}, project: ${project.label}). ` +
|
|
18
|
+
"Run 'skillmd logout' first.");
|
|
19
|
+
}
|
|
20
|
+
if (project.mismatch) {
|
|
21
|
+
console.log(`Current CLI config targets project '${config.firebaseProjectId}'. ` +
|
|
22
|
+
"Run 'skillmd login --reauth' to switch projects.");
|
|
23
|
+
}
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
dependencies.clearSession();
|
|
27
|
+
console.log("Existing session is no longer valid. Starting re-authentication.");
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
31
|
+
console.error(`skillmd login: unable to verify existing session (${message}). ` +
|
|
32
|
+
"Keeping current session. Run 'skillmd login --reauth' to force reauthentication.");
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const deviceCode = await dependencies.requestDeviceCode(config.githubClientId);
|
|
37
|
+
console.log("Open this URL in your browser to authorize skillmd:");
|
|
38
|
+
console.log(deviceCode.verificationUriComplete ?? deviceCode.verificationUri);
|
|
39
|
+
console.log(`Then enter code: ${deviceCode.userCode}`);
|
|
40
|
+
const token = await dependencies.pollForAccessToken(config.githubClientId, deviceCode.deviceCode, deviceCode.interval, deviceCode.expiresIn);
|
|
41
|
+
const firebaseSession = await dependencies.signInWithGitHubAccessToken(config.firebaseApiKey, token.accessToken);
|
|
42
|
+
dependencies.writeSession({
|
|
43
|
+
provider: "github",
|
|
44
|
+
uid: firebaseSession.localId,
|
|
45
|
+
email: firebaseSession.email,
|
|
46
|
+
refreshToken: firebaseSession.refreshToken,
|
|
47
|
+
projectId: config.firebaseProjectId,
|
|
48
|
+
});
|
|
49
|
+
if (firebaseSession.email) {
|
|
50
|
+
console.log(`Login successful. Signed in as ${firebaseSession.email} (project: ${config.firebaseProjectId}).`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
console.log(`Login successful (project: ${config.firebaseProjectId}).`);
|
|
54
|
+
}
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatSessionProject = formatSessionProject;
|
|
4
|
+
exports.printSessionStatus = printSessionStatus;
|
|
5
|
+
function formatSessionProject(session, currentConfigProjectId) {
|
|
6
|
+
if (!session.projectId) {
|
|
7
|
+
if (currentConfigProjectId) {
|
|
8
|
+
return { label: `unknown (current config: ${currentConfigProjectId})`, mismatch: false };
|
|
9
|
+
}
|
|
10
|
+
return { label: "unknown", mismatch: false };
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
label: session.projectId,
|
|
14
|
+
mismatch: Boolean(currentConfigProjectId && session.projectId !== currentConfigProjectId),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function printSessionStatus(session, currentConfigProjectId) {
|
|
18
|
+
if (!session) {
|
|
19
|
+
console.log("Not logged in.");
|
|
20
|
+
return 1;
|
|
21
|
+
}
|
|
22
|
+
const project = formatSessionProject(session, currentConfigProjectId);
|
|
23
|
+
if (session.email) {
|
|
24
|
+
console.log(`Logged in with GitHub as ${session.email} (project: ${project.label}).`);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.log(`Logged in with GitHub (uid: ${session.uid}, project: ${project.label}).`);
|
|
28
|
+
}
|
|
29
|
+
if (project.mismatch && currentConfigProjectId) {
|
|
30
|
+
console.log(`Current CLI config targets project '${currentConfigProjectId}'. ` +
|
|
31
|
+
"Run 'skillmd login --reauth' to switch projects.");
|
|
32
|
+
}
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isInitTemplateId = isInitTemplateId;
|
|
4
|
+
exports.resolveInitTemplateId = resolveInitTemplateId;
|
|
5
|
+
exports.scaffoldSkillInDirectory = scaffoldSkillInDirectory;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const normalize_name_1 = require("./normalize-name");
|
|
9
|
+
const skill_spec_1 = require("./skill-spec");
|
|
10
|
+
const templates_1 = require("./templates");
|
|
11
|
+
function assertDirectoryEmpty(targetDir) {
|
|
12
|
+
const entries = (0, node_fs_1.readdirSync)(targetDir);
|
|
13
|
+
if (entries.length > 0) {
|
|
14
|
+
throw new Error(`target directory is not empty (${entries.length} item(s) found); run 'skillmd init' in an empty directory`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function assertDirectoryNameMatchesNormalized(targetDir) {
|
|
18
|
+
const dirName = (0, node_path_1.basename)(targetDir);
|
|
19
|
+
const normalizedName = (0, normalize_name_1.normalizeSkillName)(dirName);
|
|
20
|
+
if (dirName !== normalizedName) {
|
|
21
|
+
throw new Error(`directory name '${dirName}' must already be normalized. Rename it to '${normalizedName}' and retry`);
|
|
22
|
+
}
|
|
23
|
+
return normalizedName;
|
|
24
|
+
}
|
|
25
|
+
function isInitTemplateId(value) {
|
|
26
|
+
return skill_spec_1.INIT_TEMPLATE_IDS.includes(value);
|
|
27
|
+
}
|
|
28
|
+
function resolveInitTemplateId(value) {
|
|
29
|
+
return isInitTemplateId(value) ? value : null;
|
|
30
|
+
}
|
|
31
|
+
function createDirectory(path, createdPaths) {
|
|
32
|
+
(0, node_fs_1.mkdirSync)(path, { recursive: false, mode: 0o755 });
|
|
33
|
+
createdPaths.push({ kind: "dir", path });
|
|
34
|
+
}
|
|
35
|
+
function createFile(path, content, createdPaths) {
|
|
36
|
+
(0, node_fs_1.writeFileSync)(path, content, { encoding: "utf8", flag: "wx", mode: 0o644 });
|
|
37
|
+
(0, node_fs_1.chmodSync)(path, 0o644);
|
|
38
|
+
createdPaths.push({ kind: "file", path });
|
|
39
|
+
}
|
|
40
|
+
function rollbackCreatedPaths(createdPaths) {
|
|
41
|
+
for (const entry of [...createdPaths].reverse()) {
|
|
42
|
+
if (entry.kind === "file") {
|
|
43
|
+
(0, node_fs_1.rmSync)(entry.path, { force: true });
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
(0, node_fs_1.rmSync)(entry.path, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function scaffoldSkillInDirectory(targetDir, options = {}) {
|
|
50
|
+
const template = options.template ?? "minimal";
|
|
51
|
+
const skillName = assertDirectoryNameMatchesNormalized(targetDir);
|
|
52
|
+
assertDirectoryEmpty(targetDir);
|
|
53
|
+
const createdPaths = [];
|
|
54
|
+
try {
|
|
55
|
+
if (template === "verbose") {
|
|
56
|
+
for (const directory of skill_spec_1.SCAFFOLD_DIRECTORIES) {
|
|
57
|
+
const fullPath = (0, node_path_1.join)(targetDir, directory);
|
|
58
|
+
createDirectory(fullPath, createdPaths);
|
|
59
|
+
createFile((0, node_path_1.join)(fullPath, ".gitkeep"), "", createdPaths);
|
|
60
|
+
}
|
|
61
|
+
createFile((0, node_path_1.join)(targetDir, ".gitignore"), (0, templates_1.buildGitignore)(), createdPaths);
|
|
62
|
+
createFile((0, node_path_1.join)(targetDir, "SKILL.md"), (0, templates_1.buildVerboseSkillMarkdown)(skillName), createdPaths);
|
|
63
|
+
createFile((0, node_path_1.join)(targetDir, "scripts", "README.md"), (0, templates_1.buildScriptsReadme)(), createdPaths);
|
|
64
|
+
createFile((0, node_path_1.join)(targetDir, "scripts", "extract.py"), (0, templates_1.buildExtractScriptPython)(), createdPaths);
|
|
65
|
+
createFile((0, node_path_1.join)(targetDir, "references", "REFERENCE.md"), (0, templates_1.buildReferenceGuide)(), createdPaths);
|
|
66
|
+
createFile((0, node_path_1.join)(targetDir, "references", "FORMS.md"), (0, templates_1.buildFormsReference)(), createdPaths);
|
|
67
|
+
createFile((0, node_path_1.join)(targetDir, "assets", "README.md"), (0, templates_1.buildAssetsReadme)(), createdPaths);
|
|
68
|
+
createFile((0, node_path_1.join)(targetDir, "assets", "report-template.md"), (0, templates_1.buildReportTemplate)(), createdPaths);
|
|
69
|
+
createFile((0, node_path_1.join)(targetDir, "assets", "lookup-table.csv"), (0, templates_1.buildLookupTableCsv)(), createdPaths);
|
|
70
|
+
return { skillName, template };
|
|
71
|
+
}
|
|
72
|
+
createFile((0, node_path_1.join)(targetDir, "SKILL.md"), (0, templates_1.buildMinimalSkillMarkdown)(skillName), createdPaths);
|
|
73
|
+
return { skillName, template };
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
rollbackCreatedPaths(createdPaths);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.STRICT_SECTION_HEADINGS = exports.STRICT_SECTION_TITLES = exports.STRICT_REQUIRED_FILES = exports.SCAFFOLD_DIRECTORIES = exports.MAX_SKILL_NAME_LENGTH = void 0;
|
|
3
|
+
exports.STRICT_SECTION_HEADINGS = exports.STRICT_SECTION_TITLES = exports.STRICT_REQUIRED_FILES = exports.SCAFFOLD_DIRECTORIES = exports.INIT_TEMPLATE_IDS = exports.MAX_SKILL_NAME_LENGTH = void 0;
|
|
4
4
|
exports.MAX_SKILL_NAME_LENGTH = 64;
|
|
5
|
+
exports.INIT_TEMPLATE_IDS = ["minimal", "verbose"];
|
|
5
6
|
exports.SCAFFOLD_DIRECTORIES = ["scripts", "references", "assets"];
|
|
6
7
|
exports.STRICT_REQUIRED_FILES = [
|
|
7
8
|
".gitignore",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./skill-markdown"), exports);
|
|
18
|
+
__exportStar(require("./resources"), exports);
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildGitignore = buildGitignore;
|
|
4
|
+
exports.buildScriptsReadme = buildScriptsReadme;
|
|
5
|
+
exports.buildExtractScriptPython = buildExtractScriptPython;
|
|
6
|
+
exports.buildReferenceGuide = buildReferenceGuide;
|
|
7
|
+
exports.buildFormsReference = buildFormsReference;
|
|
8
|
+
exports.buildAssetsReadme = buildAssetsReadme;
|
|
9
|
+
exports.buildReportTemplate = buildReportTemplate;
|
|
10
|
+
exports.buildLookupTableCsv = buildLookupTableCsv;
|
|
11
|
+
function buildGitignore() {
|
|
12
|
+
return `node_modules/
|
|
13
|
+
dist/
|
|
14
|
+
.DS_Store
|
|
15
|
+
npm-debug.log*
|
|
16
|
+
`;
|
|
17
|
+
}
|
|
18
|
+
function buildScriptsReadme() {
|
|
19
|
+
return `# scripts/
|
|
20
|
+
|
|
21
|
+
Use this folder for executable code that agents can run.
|
|
22
|
+
|
|
23
|
+
Guidelines:
|
|
24
|
+
- Keep scripts self-contained or clearly document dependencies.
|
|
25
|
+
- Emit actionable error messages.
|
|
26
|
+
- Handle edge cases (missing files, invalid input, empty data).
|
|
27
|
+
|
|
28
|
+
Starter script:
|
|
29
|
+
- \`scripts/extract.py\`
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
function buildExtractScriptPython() {
|
|
33
|
+
return `#!/usr/bin/env python3
|
|
34
|
+
"""Example extraction script with defensive error handling.
|
|
35
|
+
|
|
36
|
+
Usage:
|
|
37
|
+
python3 scripts/extract.py --input ./input.txt --output ./output.json
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from __future__ import annotations
|
|
41
|
+
|
|
42
|
+
import argparse
|
|
43
|
+
import json
|
|
44
|
+
import os
|
|
45
|
+
import sys
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def parse_args() -> argparse.Namespace:
|
|
49
|
+
parser = argparse.ArgumentParser(description="Extract line and word statistics.")
|
|
50
|
+
parser.add_argument("--input", required=True, help="Path to input text file.")
|
|
51
|
+
parser.add_argument("--output", required=True, help="Path to output JSON file.")
|
|
52
|
+
return parser.parse_args()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def main() -> int:
|
|
56
|
+
args = parse_args()
|
|
57
|
+
|
|
58
|
+
if not os.path.exists(args.input):
|
|
59
|
+
print(f"error: input file does not exist: {args.input}", file=sys.stderr)
|
|
60
|
+
return 1
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
with open(args.input, "r", encoding="utf-8") as source:
|
|
64
|
+
content = source.read()
|
|
65
|
+
except OSError as error:
|
|
66
|
+
print(f"error: failed to read input file: {error}", file=sys.stderr)
|
|
67
|
+
return 1
|
|
68
|
+
|
|
69
|
+
lines = [line for line in content.splitlines() if line.strip()]
|
|
70
|
+
result = {
|
|
71
|
+
"line_count": len(lines),
|
|
72
|
+
"word_count": len(content.split()),
|
|
73
|
+
"preview": lines[:3],
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
with open(args.output, "w", encoding="utf-8") as destination:
|
|
78
|
+
json.dump(result, destination, indent=2)
|
|
79
|
+
destination.write("\\n")
|
|
80
|
+
except OSError as error:
|
|
81
|
+
print(f"error: failed to write output file: {error}", file=sys.stderr)
|
|
82
|
+
return 1
|
|
83
|
+
|
|
84
|
+
print(f"wrote extraction summary to {args.output}")
|
|
85
|
+
return 0
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
raise SystemExit(main())
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
function buildReferenceGuide() {
|
|
93
|
+
return `# Reference Guide
|
|
94
|
+
|
|
95
|
+
Use this file for detailed technical reference that should not live in \`SKILL.md\`.
|
|
96
|
+
|
|
97
|
+
Suggested sections:
|
|
98
|
+
- Data model and field definitions
|
|
99
|
+
- Validation rules and constraints
|
|
100
|
+
- External API contract notes
|
|
101
|
+
- Error catalog and recovery guidance
|
|
102
|
+
|
|
103
|
+
Keep this file focused and concise. Add deeper detail in additional reference files when needed.
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
function buildFormsReference() {
|
|
107
|
+
return `# Forms and Structured Templates
|
|
108
|
+
|
|
109
|
+
Use this file to store reusable form structures and payload templates.
|
|
110
|
+
|
|
111
|
+
## Intake Form (Example)
|
|
112
|
+
|
|
113
|
+
\`\`\`yaml
|
|
114
|
+
request_id: "REQ-0001"
|
|
115
|
+
request_type: "analysis"
|
|
116
|
+
priority: "normal"
|
|
117
|
+
inputs:
|
|
118
|
+
source_path: "./input.txt"
|
|
119
|
+
output_path: "./output.json"
|
|
120
|
+
\`\`\`
|
|
121
|
+
|
|
122
|
+
## Result Envelope (Example)
|
|
123
|
+
|
|
124
|
+
\`\`\`json
|
|
125
|
+
{
|
|
126
|
+
"status": "ok",
|
|
127
|
+
"summary": "One-line result summary",
|
|
128
|
+
"artifacts": []
|
|
129
|
+
}
|
|
130
|
+
\`\`\`
|
|
131
|
+
`;
|
|
132
|
+
}
|
|
133
|
+
function buildAssetsReadme() {
|
|
134
|
+
return `# assets/
|
|
135
|
+
|
|
136
|
+
Use this folder for static resources that scripts or instructions can reference:
|
|
137
|
+
- templates
|
|
138
|
+
- lookup tables
|
|
139
|
+
- example data
|
|
140
|
+
- diagrams
|
|
141
|
+
|
|
142
|
+
Keep asset files small and task-focused.
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
function buildReportTemplate() {
|
|
146
|
+
return `# Report Template
|
|
147
|
+
|
|
148
|
+
## Summary
|
|
149
|
+
- Status:
|
|
150
|
+
- Owner:
|
|
151
|
+
- Updated:
|
|
152
|
+
|
|
153
|
+
## Findings
|
|
154
|
+
1.
|
|
155
|
+
2.
|
|
156
|
+
3.
|
|
157
|
+
|
|
158
|
+
## Next Steps
|
|
159
|
+
1.
|
|
160
|
+
2.
|
|
161
|
+
`;
|
|
162
|
+
}
|
|
163
|
+
function buildLookupTableCsv() {
|
|
164
|
+
return `code,label,description
|
|
165
|
+
E001,missing_input,Required input file is missing
|
|
166
|
+
E002,invalid_format,Input format is invalid or unsupported
|
|
167
|
+
E003,write_failed,Output could not be written
|
|
168
|
+
`;
|
|
169
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildSkillMarkdown = buildSkillMarkdown;
|
|
4
|
+
exports.buildMinimalSkillMarkdown = buildMinimalSkillMarkdown;
|
|
5
|
+
exports.buildVerboseSkillMarkdown = buildVerboseSkillMarkdown;
|
|
6
|
+
const skill_spec_1 = require("../skill-spec");
|
|
7
|
+
const SECTION_PLACEHOLDERS = {
|
|
8
|
+
Scope: "Define the boundaries of this skill: what it should and should not handle.",
|
|
9
|
+
"When to use": "Describe the signals or request patterns that should trigger this skill. Keep SKILL.md focused and move deep details into references/ files.",
|
|
10
|
+
Inputs: "List required and optional inputs, expected formats, and assumptions. Add structured templates to references/FORMS.md when helpful.",
|
|
11
|
+
Outputs: "Describe expected outputs, side effects, and completion criteria. Use assets/report-template.md when a fixed output structure is useful.",
|
|
12
|
+
"Steps / Procedure": "Provide ordered steps the agent should follow, including key decision points. See [the reference guide](references/REFERENCE.md) for detailed rules. Run helper scripts such as scripts/extract.py when needed.",
|
|
13
|
+
Examples: "Add one or two realistic examples of inputs and expected outputs.",
|
|
14
|
+
"Limitations / Failure modes": "Document known limitations, failure cases, and recommended recovery actions.",
|
|
15
|
+
"Security / Tool access": "State required tools/permissions and any security constraints. If needed, set frontmatter allowed-tools to pre-approve tool usage.",
|
|
16
|
+
};
|
|
17
|
+
function buildSkillMarkdown(name) {
|
|
18
|
+
const sections = skill_spec_1.STRICT_SECTION_TITLES.map((title) => `## ${title}\n\n${SECTION_PLACEHOLDERS[title]}`).join("\n\n");
|
|
19
|
+
return `---
|
|
20
|
+
name: ${name}
|
|
21
|
+
description: Explain what this skill does and when an agent should use it.
|
|
22
|
+
# compatibility: "Optional: environment requirements (products, packages, network access)."
|
|
23
|
+
# metadata:
|
|
24
|
+
# author: "your-org"
|
|
25
|
+
# version: "1.0.0"
|
|
26
|
+
# allowed-tools: "Optional: space-delimited pre-approved tools."
|
|
27
|
+
license: Optional. Add a license name or reference to a bundled license file.
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
${sections}
|
|
31
|
+
`;
|
|
32
|
+
}
|
|
33
|
+
function buildMinimalSkillMarkdown(name) {
|
|
34
|
+
return buildSkillMarkdown(name);
|
|
35
|
+
}
|
|
36
|
+
function buildVerboseSkillMarkdown(name) {
|
|
37
|
+
return buildSkillMarkdown(name);
|
|
38
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LOGOUT_USAGE = exports.LOGIN_USAGE = exports.VALIDATE_USAGE = exports.INIT_USAGE = exports.ROOT_USAGE = void 0;
|
|
4
4
|
exports.ROOT_USAGE = "Usage: skillmd <init|validate|login|logout>";
|
|
5
|
-
exports.INIT_USAGE = "Usage: skillmd init [--no-validate]";
|
|
5
|
+
exports.INIT_USAGE = "Usage: skillmd init [--no-validate] [--template <minimal|verbose>]";
|
|
6
6
|
exports.VALIDATE_USAGE = "Usage: skillmd validate [path] [--strict] [--parity]";
|
|
7
7
|
exports.LOGIN_USAGE = "Usage: skillmd login [--status|--reauth]";
|
|
8
8
|
exports.LOGOUT_USAGE = "Usage: skillmd logout";
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validateWithSkillsRef = validateWithSkillsRef;
|
|
4
4
|
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
const SKILLS_REF_TIMEOUT_MS = 10000;
|
|
5
6
|
function formatOutput(stdout, stderr) {
|
|
6
7
|
return [stdout.trim(), stderr.trim()].filter((part) => part.length > 0).join("\n");
|
|
7
8
|
}
|
|
8
9
|
function validateWithSkillsRef(targetDir) {
|
|
9
10
|
const result = (0, node_child_process_1.spawnSync)("skills-ref", ["validate", targetDir], {
|
|
10
11
|
encoding: "utf8",
|
|
12
|
+
timeout: SKILLS_REF_TIMEOUT_MS,
|
|
11
13
|
});
|
|
12
14
|
if (result.error) {
|
|
13
15
|
if ("code" in result.error && result.error.code === "ENOENT") {
|
|
@@ -16,6 +18,12 @@ function validateWithSkillsRef(targetDir) {
|
|
|
16
18
|
message: "skills-ref is not installed or not on PATH",
|
|
17
19
|
};
|
|
18
20
|
}
|
|
21
|
+
if ("code" in result.error && result.error.code === "ETIMEDOUT") {
|
|
22
|
+
return {
|
|
23
|
+
status: "unavailable",
|
|
24
|
+
message: `skills-ref timed out after ${SKILLS_REF_TIMEOUT_MS}ms`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
19
27
|
return {
|
|
20
28
|
status: "unavailable",
|
|
21
29
|
message: `skills-ref execution failed: ${result.error.message}`,
|
|
@@ -4,7 +4,7 @@ exports.validateSkill = validateSkill;
|
|
|
4
4
|
const node_fs_1 = require("node:fs");
|
|
5
5
|
const node_path_1 = require("node:path");
|
|
6
6
|
const yaml_1 = require("yaml");
|
|
7
|
-
const skill_spec_1 = require("
|
|
7
|
+
const skill_spec_1 = require("../scaffold/skill-spec");
|
|
8
8
|
const SKILL_FILE = "SKILL.md";
|
|
9
9
|
function stripUtf8Bom(content) {
|
|
10
10
|
return content.replace(/^\uFEFF/, "");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skillmarkdown/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLI for scaffolding SKILL.md-based AI skills",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"prepublishOnly": "npm run clean && npm run build",
|
|
26
26
|
"smoke:link": "bash ./scripts/smoke-link.sh",
|
|
27
27
|
"smoke:pack": "bash ./scripts/smoke-pack.sh",
|
|
28
|
-
"test": "npm run build && node --test tests
|
|
28
|
+
"test": "npm run build && node --test \"tests/**/*.test.js\""
|
|
29
29
|
},
|
|
30
30
|
"engines": {
|
|
31
31
|
"node": ">=18"
|
package/dist/lib/scaffold.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.scaffoldSkillInDirectory = scaffoldSkillInDirectory;
|
|
4
|
-
const node_fs_1 = require("node:fs");
|
|
5
|
-
const node_path_1 = require("node:path");
|
|
6
|
-
const normalize_name_1 = require("./normalize-name");
|
|
7
|
-
const skill_spec_1 = require("./skill-spec");
|
|
8
|
-
const templates_1 = require("./templates");
|
|
9
|
-
function assertDirectoryEmpty(targetDir) {
|
|
10
|
-
const entries = (0, node_fs_1.readdirSync)(targetDir);
|
|
11
|
-
if (entries.length > 0) {
|
|
12
|
-
throw new Error(`target directory is not empty (${entries.length} item(s) found); run 'skillmd init' in an empty directory`);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
function assertDirectoryNameMatchesNormalized(targetDir) {
|
|
16
|
-
const dirName = (0, node_path_1.basename)(targetDir);
|
|
17
|
-
const normalizedName = (0, normalize_name_1.normalizeSkillName)(dirName);
|
|
18
|
-
if (dirName !== normalizedName) {
|
|
19
|
-
throw new Error(`directory name '${dirName}' must already be normalized. Rename it to '${normalizedName}' and retry`);
|
|
20
|
-
}
|
|
21
|
-
return normalizedName;
|
|
22
|
-
}
|
|
23
|
-
function scaffoldSkillInDirectory(targetDir) {
|
|
24
|
-
const skillName = assertDirectoryNameMatchesNormalized(targetDir);
|
|
25
|
-
assertDirectoryEmpty(targetDir);
|
|
26
|
-
for (const directory of skill_spec_1.SCAFFOLD_DIRECTORIES) {
|
|
27
|
-
const fullPath = (0, node_path_1.join)(targetDir, directory);
|
|
28
|
-
(0, node_fs_1.mkdirSync)(fullPath, { recursive: true });
|
|
29
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(fullPath, ".gitkeep"), "", "utf8");
|
|
30
|
-
}
|
|
31
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(targetDir, "SKILL.md"), (0, templates_1.buildSkillMarkdown)(skillName), "utf8");
|
|
32
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(targetDir, ".gitignore"), (0, templates_1.buildGitignore)(), "utf8");
|
|
33
|
-
return { skillName };
|
|
34
|
-
}
|
package/dist/lib/templates.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildSkillMarkdown = buildSkillMarkdown;
|
|
4
|
-
exports.buildGitignore = buildGitignore;
|
|
5
|
-
const skill_spec_1 = require("./skill-spec");
|
|
6
|
-
function buildSkillMarkdown(name) {
|
|
7
|
-
const sections = skill_spec_1.STRICT_SECTION_TITLES.map((title) => `## ${title}\nTODO`).join("\n\n");
|
|
8
|
-
return `---
|
|
9
|
-
name: ${name}
|
|
10
|
-
description: "TODO: Describe what this skill does and when to use it."
|
|
11
|
-
license: TODO
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
${sections}
|
|
15
|
-
`;
|
|
16
|
-
}
|
|
17
|
-
function buildGitignore() {
|
|
18
|
-
return `node_modules/
|
|
19
|
-
dist/
|
|
20
|
-
.DS_Store
|
|
21
|
-
npm-debug.log*
|
|
22
|
-
`;
|
|
23
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|