master-skill 0.12.12 → 0.12.14
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/SKILL.md +6 -4
- package/bin/cli.mjs +116 -4
- package/gemini-extension.json +1 -1
- package/hooks/session-start +2 -1
- package/hooks/session_start.py +11 -1
- package/package.json +1 -1
- package/tools/check_deps.py +95 -0
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
{
|
|
10
10
|
"name": "master-skill",
|
|
11
11
|
"description": "FoJin-powered Buddhist AI persona framework — source-grounded, boundary-aware, fidelity-tested, runtime-ready. 15 prebuilt masters across 印度/汉传/藏传/南传 plus compare, debate, and curriculum meta-skills.",
|
|
12
|
-
"version": "0.12.
|
|
12
|
+
"version": "0.12.14",
|
|
13
13
|
"source": "./",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "xr843",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "master-skill",
|
|
3
3
|
"description": "FoJin-powered Buddhist AI persona framework — source-grounded, boundary-aware, fidelity-tested, runtime-ready. 15 prebuilt masters across 印度/汉传/藏传/南传 plus compare, debate, and curriculum meta-skills.",
|
|
4
|
-
"version": "0.12.
|
|
4
|
+
"version": "0.12.14",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "xr843",
|
|
7
7
|
"email": "xr843@users.noreply.github.com"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "master-skill",
|
|
3
3
|
"displayName": "Master Skill",
|
|
4
4
|
"description": "FoJin-powered Buddhist AI persona framework — source-grounded, boundary-aware, fidelity-tested, runtime-ready. 15 prebuilt masters across 印度/汉传/藏传/南传.",
|
|
5
|
-
"version": "0.12.
|
|
5
|
+
"version": "0.12.14",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "xr843",
|
|
8
8
|
"email": "xr843@users.noreply.github.com"
|
package/SKILL.md
CHANGED
|
@@ -5,13 +5,11 @@ argument-hint: <法师名称>
|
|
|
5
5
|
version: 1.0.0
|
|
6
6
|
user-invocable: true
|
|
7
7
|
allowed-tools:
|
|
8
|
-
- Bash
|
|
9
8
|
- Read
|
|
10
|
-
- Write
|
|
11
|
-
- Edit
|
|
12
9
|
- Glob
|
|
13
10
|
- Grep
|
|
14
|
-
-
|
|
11
|
+
- Bash(python3 ${CLAUDE_SKILL_DIR}/tools/*)
|
|
12
|
+
- Bash(python3 "${CLAUDE_SKILL_DIR}/tools/*)
|
|
15
13
|
---
|
|
16
14
|
|
|
17
15
|
# Master-skill — 佛教法师教学角色生成器
|
|
@@ -64,6 +62,10 @@ allowed-tools:
|
|
|
64
62
|
|
|
65
63
|
## 主流程(生成新法师)
|
|
66
64
|
|
|
65
|
+
### Step 0:检查运行环境
|
|
66
|
+
|
|
67
|
+
先运行 `python3 "${CLAUDE_SKILL_DIR}/tools/check_deps.py"`。生成器的每个工具启动时都要导入 `requests`、`pyyaml`、`pypinyin`,缺任何一个都会直接报 `ModuleNotFoundError`,连离线步骤也跑不了。退出码非 0 时停下,把它打印的安装方法原样告诉用户(系统 Python 拒绝 pip 时改用虚拟环境);**不要擅自安装**,那会改动用户的环境。
|
|
68
|
+
|
|
67
69
|
### Step 1:信息录入
|
|
68
70
|
|
|
69
71
|
加载 `${CLAUDE_SKILL_DIR}/prompts/intake.md`,3 问模式收集:①法师名称(FoJin KG 自动匹配) ②关注方面(教义/修行/讲解/全部) ③语言偏好(按传承默认)。
|
package/bin/cli.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import fs from "fs";
|
|
|
4
4
|
import path from "path";
|
|
5
5
|
import os from "os";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
+
import { spawnSync } from "child_process";
|
|
7
8
|
|
|
8
9
|
// fileURLToPath (not new URL().pathname) — on Windows the URL pathname is
|
|
9
10
|
// "/C:/…", which fs cannot resolve, so every command saw an empty prebuilt/.
|
|
@@ -151,6 +152,14 @@ function cpR(src, dest) {
|
|
|
151
152
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
152
153
|
const s = path.join(src, entry.name);
|
|
153
154
|
const d = path.join(dest, entry.name);
|
|
155
|
+
// The package ships no links. One in the source is left over from running
|
|
156
|
+
// the clone instructions' `ln -sf` twice, which — with the target already a
|
|
157
|
+
// link to a directory — creates prebuilt/<skill>/<skill> pointing at its own
|
|
158
|
+
// parent. copyFileSync on it threw EISDIR and the install crashed.
|
|
159
|
+
if (entry.isSymbolicLink()) {
|
|
160
|
+
console.error(` (skipped link ${s} — not part of the package)`);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
154
163
|
if (entry.isDirectory()) cpR(s, d);
|
|
155
164
|
else fs.copyFileSync(s, d);
|
|
156
165
|
}
|
|
@@ -365,6 +374,35 @@ function resolveMasterDir(input) {
|
|
|
365
374
|
|
|
366
375
|
// Returns the number of failures so main can set a non-zero exit code —
|
|
367
376
|
// scripted callers must be able to tell a typo from a clean install.
|
|
377
|
+
// create-master's tools import requests, pyyaml and pypinyin at startup and exit
|
|
378
|
+
// with ModuleNotFoundError without them. tools/check_deps.py (standard library
|
|
379
|
+
// only) reports which are missing and how to install them.
|
|
380
|
+
function pythonBin() {
|
|
381
|
+
return process.env.PYTHON || (process.platform === "win32" ? "python" : "python3");
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function generatorDependencies(generatorDir) {
|
|
385
|
+
const script = path.join(generatorDir, "tools", "check_deps.py");
|
|
386
|
+
if (!fs.existsSync(script)) return null;
|
|
387
|
+
const result = spawnSync(pythonBin(), [script, "--json"], { encoding: "utf8", timeout: 30000 });
|
|
388
|
+
if (result.error) {
|
|
389
|
+
return { ok: false, pythonMissing: true, message: `${pythonBin()} was not found; create-master needs Python 3.9+` };
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
const report = JSON.parse(result.stdout);
|
|
393
|
+
if (report.ok) return { ok: true };
|
|
394
|
+
const what = report.python_too_old
|
|
395
|
+
? `Python ${report.python} is older than 3.9`
|
|
396
|
+
: `missing Python package(s) ${report.missing.join(", ")}`;
|
|
397
|
+
return {
|
|
398
|
+
ok: false,
|
|
399
|
+
message: `create-master: ${what} — run: ${pythonBin()} "${script}" for install steps`,
|
|
400
|
+
};
|
|
401
|
+
} catch {
|
|
402
|
+
return { ok: false, message: `create-master: could not check Python dependencies (${(result.stderr || "").trim()})` };
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
368
406
|
function cmdInstall(names) {
|
|
369
407
|
fs.mkdirSync(SKILLS_DIR, { recursive: true });
|
|
370
408
|
let failed = 0;
|
|
@@ -382,8 +420,10 @@ function cmdInstall(names) {
|
|
|
382
420
|
}
|
|
383
421
|
const src = path.join(PACKAGE_ROOT, skill.source);
|
|
384
422
|
const dest = path.join(SKILLS_DIR, skill.install_dir);
|
|
423
|
+
let deps = null;
|
|
385
424
|
if (skill.kind === "generator") {
|
|
386
425
|
replaceGeneratorInstall(skill, src, dest);
|
|
426
|
+
deps = generatorDependencies(dest);
|
|
387
427
|
} else {
|
|
388
428
|
// Clear any previous install first: files renamed or removed upstream
|
|
389
429
|
// must not linger as stale skill content under ~/.claude/skills/.
|
|
@@ -391,6 +431,7 @@ function cmdInstall(names) {
|
|
|
391
431
|
cpR(src, dest);
|
|
392
432
|
}
|
|
393
433
|
console.log(` ✓ ${name} → ${dest}`);
|
|
434
|
+
if (deps && !deps.ok) console.log(` ! ${deps.message}`);
|
|
394
435
|
}
|
|
395
436
|
return failed;
|
|
396
437
|
}
|
|
@@ -405,7 +446,40 @@ function cmdInstallAll(label = "Installing") {
|
|
|
405
446
|
return cmdInstall(all);
|
|
406
447
|
}
|
|
407
448
|
|
|
408
|
-
|
|
449
|
+
// Directories under create-master/masters/ — personas the user generated. `update`
|
|
450
|
+
// carries them across (replaceGeneratorInstall); `uninstall` must not quietly
|
|
451
|
+
// delete them either.
|
|
452
|
+
function generatedPersonas(generatorDir) {
|
|
453
|
+
const mastersDir = path.join(generatorDir, "masters");
|
|
454
|
+
if (!fs.existsSync(mastersDir)) return [];
|
|
455
|
+
return fs
|
|
456
|
+
.readdirSync(mastersDir, { withFileTypes: true })
|
|
457
|
+
.filter((entry) => entry.isDirectory())
|
|
458
|
+
.map((entry) => entry.name)
|
|
459
|
+
.sort();
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Links in the skills directory that `master_builder.py --register` made into
|
|
463
|
+
// this generator's masters/. Resolved with realpath so a Windows junction is
|
|
464
|
+
// recognised as well as a symlink.
|
|
465
|
+
function registrationsInto(generatorDir) {
|
|
466
|
+
const mastersDir = path.join(generatorDir, "masters");
|
|
467
|
+
if (!fs.existsSync(mastersDir) || !fs.existsSync(SKILLS_DIR)) return [];
|
|
468
|
+
const realMasters = fs.realpathSync(mastersDir);
|
|
469
|
+
return fs
|
|
470
|
+
.readdirSync(SKILLS_DIR, { withFileTypes: true })
|
|
471
|
+
.filter((entry) => entry.isSymbolicLink())
|
|
472
|
+
.map((entry) => path.join(SKILLS_DIR, entry.name))
|
|
473
|
+
.filter((link) => {
|
|
474
|
+
try {
|
|
475
|
+
return fs.realpathSync(link).startsWith(realMasters + path.sep);
|
|
476
|
+
} catch {
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function cmdUninstall(names, { force = false } = {}) {
|
|
409
483
|
let failed = 0;
|
|
410
484
|
for (const name of names) {
|
|
411
485
|
if (!isSafeName(name)) {
|
|
@@ -429,6 +503,26 @@ function cmdUninstall(names) {
|
|
|
429
503
|
failed++;
|
|
430
504
|
continue;
|
|
431
505
|
}
|
|
506
|
+
if (skill.kind === "generator") {
|
|
507
|
+
// Until 2026-09-17 this removed the whole directory, masters/ included:
|
|
508
|
+
// every persona the user had generated was deleted with "✓ removed" and
|
|
509
|
+
// exit 0, and the links registered for them were left pointing at nothing.
|
|
510
|
+
const generated = generatedPersonas(dest);
|
|
511
|
+
if (generated.length && !force) {
|
|
512
|
+
console.log(
|
|
513
|
+
` ✗ ${name} — holds ${generated.length} persona(s) you generated: ${generated.join(", ")}`
|
|
514
|
+
);
|
|
515
|
+
console.log(
|
|
516
|
+
` Uninstalling deletes ${path.join(dest, "masters")}. Move it somewhere safe first, or rerun with --force.`
|
|
517
|
+
);
|
|
518
|
+
failed++;
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
for (const link of registrationsInto(dest)) {
|
|
522
|
+
fs.unlinkSync(link);
|
|
523
|
+
console.log(` ✓ ${path.basename(link)} link removed (${link})`);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
432
526
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
433
527
|
console.log(` ✓ ${name} removed (${dest})`);
|
|
434
528
|
}
|
|
@@ -450,6 +544,10 @@ function expectedInstallFiles(skill) {
|
|
|
450
544
|
const walk = (rel) => {
|
|
451
545
|
const abs = path.join(src, rel);
|
|
452
546
|
if (!fs.existsSync(abs)) return;
|
|
547
|
+
// Same rule as cpR: install skips links, so they are not expected files.
|
|
548
|
+
// Following a self-referencing one walked the loop to the OS link limit and
|
|
549
|
+
// reported hundreds of phantom missing files.
|
|
550
|
+
if (fs.lstatSync(abs).isSymbolicLink()) return;
|
|
453
551
|
if (fs.statSync(abs).isDirectory()) {
|
|
454
552
|
if (path.basename(abs) === "__pycache__") return;
|
|
455
553
|
for (const entry of fs.readdirSync(abs)) {
|
|
@@ -512,8 +610,19 @@ function installedProblems() {
|
|
|
512
610
|
}
|
|
513
611
|
}
|
|
514
612
|
|
|
613
|
+
const generator = CATALOG.skills.find((skill) => skill.kind === "generator");
|
|
614
|
+
const generatorDir = generator && path.join(SKILLS_DIR, generator.install_dir);
|
|
615
|
+
if (generatorDir && fs.existsSync(path.join(generatorDir, "SKILL.md"))) {
|
|
616
|
+
const deps = generatorDependencies(generatorDir);
|
|
617
|
+
if (deps && !deps.ok) {
|
|
618
|
+
problems.push({ code: "generator-dependencies", name: generator.name, message: deps.message });
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
515
622
|
// Personas registered by create-master are links into create-master/masters/;
|
|
516
|
-
//
|
|
623
|
+
// deleting or moving a generated persona by hand leaves its link pointing at
|
|
624
|
+
// nothing. (`uninstall create-master` refuses while personas exist, and with
|
|
625
|
+
// --force removes their links itself.)
|
|
517
626
|
if (fs.existsSync(SKILLS_DIR)) {
|
|
518
627
|
for (const entry of fs.readdirSync(SKILLS_DIR, { withFileTypes: true })) {
|
|
519
628
|
if (!entry.isSymbolicLink()) continue;
|
|
@@ -909,6 +1018,8 @@ Usage:
|
|
|
909
1018
|
master-skill doctor Check local install and runtime paths
|
|
910
1019
|
master-skill doctor --json Print diagnostics as JSON
|
|
911
1020
|
master-skill uninstall <name...> Remove installed skills
|
|
1021
|
+
master-skill uninstall create-master --force
|
|
1022
|
+
Also delete the personas you generated (refused otherwise)
|
|
912
1023
|
master-skill --version Print version
|
|
913
1024
|
master-skill --help Show this help
|
|
914
1025
|
|
|
@@ -936,7 +1047,8 @@ Examples:
|
|
|
936
1047
|
if (CATALOG) {
|
|
937
1048
|
const args = process.argv.slice(2);
|
|
938
1049
|
const json = args.includes("--json");
|
|
939
|
-
const
|
|
1050
|
+
const force = args.includes("--force");
|
|
1051
|
+
const positionalArgs = args.filter((arg) => arg !== "--json" && arg !== "--force");
|
|
940
1052
|
const cmd = positionalArgs[0];
|
|
941
1053
|
|
|
942
1054
|
if (!cmd || cmd === "--help" || cmd === "-h") {
|
|
@@ -977,7 +1089,7 @@ if (CATALOG) {
|
|
|
977
1089
|
console.log("Usage: master-skill uninstall <name...>");
|
|
978
1090
|
process.exitCode = 1;
|
|
979
1091
|
} else {
|
|
980
|
-
if (cmdUninstall(rest) > 0) process.exitCode = 1;
|
|
1092
|
+
if (cmdUninstall(rest, { force }) > 0) process.exitCode = 1;
|
|
981
1093
|
}
|
|
982
1094
|
} else {
|
|
983
1095
|
console.log(`Unknown command: ${cmd}\nRun master-skill --help for usage.`);
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "master-skill",
|
|
3
3
|
"description": "FoJin-powered Buddhist AI persona framework — source-grounded, boundary-aware, fidelity-tested, runtime-ready. 15 prebuilt masters across 印度/汉传/藏传/南传.",
|
|
4
|
-
"version": "0.12.
|
|
4
|
+
"version": "0.12.14",
|
|
5
5
|
"contextFileName": "GEMINI.md"
|
|
6
6
|
}
|
package/hooks/session-start
CHANGED
|
@@ -61,7 +61,8 @@ emit_modes_only() {
|
|
|
61
61
|
if [ -n "${CURSOR_PLUGIN_ROOT:-}" ]; then
|
|
62
62
|
printf '{"additional_context": %s}\n' "$MODES_JSON"
|
|
63
63
|
elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -z "${COPILOT_CLI:-}" ]; then
|
|
64
|
-
|
|
64
|
+
# hookEventName is required; without it Claude Code rejects the payload.
|
|
65
|
+
printf '{"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": %s}}\n' "$MODES_JSON"
|
|
65
66
|
else
|
|
66
67
|
printf '{"additionalContext": %s}\n' "$MODES_JSON"
|
|
67
68
|
fi
|
package/hooks/session_start.py
CHANGED
|
@@ -136,7 +136,17 @@ def wrap_for_host(context: str, env: dict) -> dict:
|
|
|
136
136
|
if env.get("CURSOR_PLUGIN_ROOT"):
|
|
137
137
|
return {"additional_context": context}
|
|
138
138
|
if env.get("CLAUDE_PLUGIN_ROOT") and not env.get("COPILOT_CLI"):
|
|
139
|
-
|
|
139
|
+
# `hookEventName` is required. Without it Claude Code 2.1.273 rejects the
|
|
140
|
+
# whole payload — "Hook JSON output validation failed — hookSpecificOutput
|
|
141
|
+
# is missing required field hookEventName" — shows that error at every
|
|
142
|
+
# session start, and injects nothing. Measured 2026-09-17 in an isolated
|
|
143
|
+
# plugin install; the transcript recorded `hook_non_blocking_error`.
|
|
144
|
+
return {
|
|
145
|
+
"hookSpecificOutput": {
|
|
146
|
+
"hookEventName": "SessionStart",
|
|
147
|
+
"additionalContext": context,
|
|
148
|
+
}
|
|
149
|
+
}
|
|
140
150
|
return {"additionalContext": context}
|
|
141
151
|
|
|
142
152
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "master-skill",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "FoJin-powered Buddhist AI persona framework — source-grounded, boundary-aware, fidelity-tested, runtime-ready. 15 pre-built masters across 印度 / 汉传 / 藏传 / 南传, plus /compare-masters, /master-debate, and /master-curriculum.",
|
|
6
6
|
"bin": {
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Check the Python packages create-master's tools import at startup.
|
|
3
|
+
|
|
4
|
+
Every generator tool imports `fojin_bridge` (which imports `requests`) or
|
|
5
|
+
`skill_writer` (which imports `yaml` and `pypinyin`) at module level. Without those
|
|
6
|
+
packages each one exits with `ModuleNotFoundError` before doing anything — even
|
|
7
|
+
`master_builder.py --offline-smoke`, which never touches the network. Measured
|
|
8
|
+
2026-09-17 in a clean venv: `rag_query.py`, `sutra_collector.py` and
|
|
9
|
+
`master_builder.py` all failed that way.
|
|
10
|
+
|
|
11
|
+
Nothing told an npx user to install them. The only mention was the clone guide's
|
|
12
|
+
`pip install -r requirements.txt`, and on recent Debian, Ubuntu and Homebrew Pythons
|
|
13
|
+
that command is itself refused as an externally-managed environment (PEP 668).
|
|
14
|
+
|
|
15
|
+
Standard library only, so it runs where the tools cannot.
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
python3 tools/check_deps.py # exit 1 and print how to install if missing
|
|
19
|
+
python3 tools/check_deps.py --json
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import importlib.util
|
|
25
|
+
import json
|
|
26
|
+
import platform
|
|
27
|
+
import sys
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
# import name -> distribution name in requirements.txt
|
|
31
|
+
REQUIRED = {"requests": "requests", "yaml": "pyyaml", "pypinyin": "pypinyin"}
|
|
32
|
+
MIN_PYTHON = (3, 9)
|
|
33
|
+
REQUIREMENTS = Path(__file__).resolve().parent.parent / "requirements.txt"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def missing(find_spec=importlib.util.find_spec) -> list[str]:
|
|
37
|
+
"""Distributions whose import name cannot be found."""
|
|
38
|
+
return [dist for module, dist in REQUIRED.items() if find_spec(module) is None]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def guidance(missing_dists: list[str], requirements: Path = REQUIREMENTS) -> str:
|
|
42
|
+
venv_python = (
|
|
43
|
+
r"%USERPROFILE%\.venvs\master-skill\Scripts\python"
|
|
44
|
+
if sys.platform == "win32"
|
|
45
|
+
else "~/.venvs/master-skill/bin/python"
|
|
46
|
+
)
|
|
47
|
+
return "\n".join(
|
|
48
|
+
[
|
|
49
|
+
"create-master needs Python packages that are not installed: "
|
|
50
|
+
+ ", ".join(missing_dists),
|
|
51
|
+
"",
|
|
52
|
+
f' python3 -m pip install -r "{requirements}"',
|
|
53
|
+
"",
|
|
54
|
+
'If pip refuses with "externally-managed-environment" (PEP 668), use a virtual environment:',
|
|
55
|
+
"",
|
|
56
|
+
" python3 -m venv ~/.venvs/master-skill",
|
|
57
|
+
f' {venv_python} -m pip install -r "{requirements}"',
|
|
58
|
+
"",
|
|
59
|
+
"and start Claude Code with that environment active, so that `python3` is its interpreter.",
|
|
60
|
+
]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def main(argv: list[str] | None = None) -> int:
|
|
65
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
66
|
+
parser.add_argument("--json", action="store_true", help="print a JSON result")
|
|
67
|
+
args = parser.parse_args(argv)
|
|
68
|
+
|
|
69
|
+
too_old = sys.version_info < MIN_PYTHON
|
|
70
|
+
absent = missing()
|
|
71
|
+
ok = not absent and not too_old
|
|
72
|
+
if args.json:
|
|
73
|
+
print(
|
|
74
|
+
json.dumps(
|
|
75
|
+
{
|
|
76
|
+
"ok": ok,
|
|
77
|
+
"python": platform.python_version(),
|
|
78
|
+
"python_too_old": too_old,
|
|
79
|
+
"missing": absent,
|
|
80
|
+
"requirements": str(REQUIREMENTS),
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
elif ok:
|
|
85
|
+
print(f"OK: Python {platform.python_version()} with {', '.join(REQUIRED.values())}")
|
|
86
|
+
else:
|
|
87
|
+
if too_old:
|
|
88
|
+
print(f"create-master needs Python {'.'.join(map(str, MIN_PYTHON))}+; this is {platform.python_version()}.")
|
|
89
|
+
if absent:
|
|
90
|
+
print(guidance(absent))
|
|
91
|
+
return 0 if ok else 1
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
if __name__ == "__main__":
|
|
95
|
+
sys.exit(main())
|