@rosenfan/matrix 0.1.1 → 0.1.3
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/skills/matrix/SKILL.md +60 -17
- package/.claude/skills/matrix/agents/openai.yaml +4 -4
- package/.claude/skills/matrix/scripts/matrix_claude.py +3 -158
- package/.claude/skills/matrix/scripts/matrix_state.py +53 -107
- package/.claude/skills/matrix-archive/SKILL.md +23 -13
- package/.claude/skills/matrix-build/SKILL.md +24 -35
- package/.claude/skills/matrix-claude/SKILL.md +26 -24
- package/.claude/skills/matrix-design/SKILL.md +29 -26
- package/.claude/skills/matrix-hotfix/SKILL.md +8 -8
- package/.claude/skills/matrix-open/SKILL.md +25 -18
- package/.claude/skills/matrix-status/SKILL.md +3 -2
- package/.claude/skills/matrix-tweak/SKILL.md +8 -8
- package/.claude/skills/matrix-verify/SKILL.md +21 -30
- package/LICENSE +21 -21
- package/README.md +294 -255
- package/README.zh-CN.md +293 -255
- package/assets/skills-zh-CN/matrix/SKILL.md +80 -25
- package/assets/skills-zh-CN/matrix-archive/SKILL.md +33 -9
- package/assets/skills-zh-CN/matrix-build/SKILL.md +36 -10
- package/assets/skills-zh-CN/matrix-claude/SKILL.md +31 -28
- package/assets/skills-zh-CN/matrix-design/SKILL.md +19 -10
- package/assets/skills-zh-CN/matrix-hotfix/SKILL.md +8 -8
- package/assets/skills-zh-CN/matrix-open/SKILL.md +22 -7
- package/assets/skills-zh-CN/matrix-status/SKILL.md +15 -14
- package/assets/skills-zh-CN/matrix-tweak/SKILL.md +8 -8
- package/assets/skills-zh-CN/matrix-verify/SKILL.md +26 -10
- package/bin/matrix.js +15 -15
- package/package.json +56 -56
- package/src/catalog.js +92 -87
- package/src/cli.js +159 -101
- package/src/distribution.js +281 -186
- package/src/installer.js +45 -45
- package/src/matt.js +45 -45
- package/src/messages.js +33 -33
- package/src/self-update.js +81 -0
- package/src/ui.js +89 -89
- package/src/workflow-transaction.js +622 -0
- package/src/workflow.js +787 -82
package/src/matt.js
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { PLATFORMS, MATT_SKILLS } from "./catalog.js";
|
|
5
|
-
|
|
6
|
-
export function buildProcessInvocation({ command, args, platform = process.platform, nodeExecutable = process.execPath, npmCliPath }) {
|
|
7
|
-
if (platform !== "win32" || command !== "npx") return { executable: command, args, shell: false };
|
|
8
|
-
const candidates = [npmCliPath, process.env.npm_execpath, path.join(path.dirname(nodeExecutable), "node_modules", "npm", "bin", "npm-cli.js")].filter(Boolean);
|
|
9
|
-
const cli = candidates.find((candidate) => npmCliPath || fs.existsSync(candidate));
|
|
10
|
-
if (!cli) throw new Error("npm CLI was not found beside the Node runtime.");
|
|
11
|
-
const [yes, packageSpec, ...commandArgs] = args;
|
|
12
|
-
if (yes !== "--yes" || !packageSpec) throw new Error("Windows npx invocation must use fixed npm exec arguments.");
|
|
13
|
-
return { executable: nodeExecutable, args: [cli, "exec", yes, packageSpec, "--", ...commandArgs], shell: false };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function defaultProcessRunner({ command, args, cwd, timeout }) {
|
|
17
|
-
const invocation = buildProcessInvocation({ command, args });
|
|
18
|
-
return execFileSync(invocation.executable, invocation.args, { cwd, stdio: "inherit", timeout, shell: invocation.shell });
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function createMattAdapter({ run = defaultProcessRunner } = {}) {
|
|
22
|
-
return {
|
|
23
|
-
installMissing({ projectRoot, platforms, skills = MATT_SKILLS, global = false, timeout = 300_000 }) {
|
|
24
|
-
const attempted = [];
|
|
25
|
-
try {
|
|
26
|
-
for (const platform of platforms) {
|
|
27
|
-
const args = ["--yes", "skills@latest", "add", "mattpocock/skills", "--yes", "--agent", PLATFORMS[platform].skillsCliAgent];
|
|
28
|
-
for (const skill of skills) args.push("--skill", skill);
|
|
29
|
-
if (global) args.push("--global");
|
|
30
|
-
run({ command: "npx", args, cwd: projectRoot, timeout });
|
|
31
|
-
attempted.push(platform);
|
|
32
|
-
}
|
|
33
|
-
return { ok: true, code: "OK", attempted };
|
|
34
|
-
} catch (error) {
|
|
35
|
-
return {
|
|
36
|
-
ok: false,
|
|
37
|
-
code: "MATT_INSTALL_FAILED",
|
|
38
|
-
attempted,
|
|
39
|
-
message: error.message,
|
|
40
|
-
recovery: "Run matrix init --with-mattpocock after resolving the skills CLI error."
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
}
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { PLATFORMS, MATT_SKILLS } from "./catalog.js";
|
|
5
|
+
|
|
6
|
+
export function buildProcessInvocation({ command, args, platform = process.platform, nodeExecutable = process.execPath, npmCliPath }) {
|
|
7
|
+
if (platform !== "win32" || command !== "npx") return { executable: command, args, shell: false };
|
|
8
|
+
const candidates = [npmCliPath, process.env.npm_execpath, path.join(path.dirname(nodeExecutable), "node_modules", "npm", "bin", "npm-cli.js")].filter(Boolean);
|
|
9
|
+
const cli = candidates.find((candidate) => npmCliPath || fs.existsSync(candidate));
|
|
10
|
+
if (!cli) throw new Error("npm CLI was not found beside the Node runtime.");
|
|
11
|
+
const [yes, packageSpec, ...commandArgs] = args;
|
|
12
|
+
if (yes !== "--yes" || !packageSpec) throw new Error("Windows npx invocation must use fixed npm exec arguments.");
|
|
13
|
+
return { executable: nodeExecutable, args: [cli, "exec", yes, packageSpec, "--", ...commandArgs], shell: false };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function defaultProcessRunner({ command, args, cwd, timeout }) {
|
|
17
|
+
const invocation = buildProcessInvocation({ command, args });
|
|
18
|
+
return execFileSync(invocation.executable, invocation.args, { cwd, stdio: "inherit", timeout, shell: invocation.shell });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createMattAdapter({ run = defaultProcessRunner } = {}) {
|
|
22
|
+
return {
|
|
23
|
+
installMissing({ projectRoot, platforms, skills = MATT_SKILLS, global = false, timeout = 300_000 }) {
|
|
24
|
+
const attempted = [];
|
|
25
|
+
try {
|
|
26
|
+
for (const platform of platforms) {
|
|
27
|
+
const args = ["--yes", "skills@latest", "add", "mattpocock/skills", "--yes", "--agent", PLATFORMS[platform].skillsCliAgent];
|
|
28
|
+
for (const skill of skills) args.push("--skill", skill);
|
|
29
|
+
if (global) args.push("--global");
|
|
30
|
+
run({ command: "npx", args, cwd: projectRoot, timeout });
|
|
31
|
+
attempted.push(platform);
|
|
32
|
+
}
|
|
33
|
+
return { ok: true, code: "OK", attempted };
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
code: "MATT_INSTALL_FAILED",
|
|
38
|
+
attempted,
|
|
39
|
+
message: error.message,
|
|
40
|
+
recovery: "Run matrix init --with-mattpocock after resolving the skills CLI error."
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
package/src/messages.js
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
export const messages = {
|
|
2
|
-
en: {
|
|
3
|
-
title: "Evidence-driven workflow
|
|
4
|
-
recommended: "Recommended setup", apply: "Apply", customize: "Customize", cancel: "Cancel",
|
|
5
|
-
project: "Project (current directory)", global: "Global (all projects)",
|
|
6
|
-
copy: "Copy (recommended)", symlink: "Symlink (advanced)",
|
|
7
|
-
matrix: "Matrix", matt: "Matt Pocock skills", installed: "installed", ready: "Matrix is ready.",
|
|
8
|
-
noPlatform: "No verified platform was detected. Select Claude Code, Codex, or both.",
|
|
9
|
-
preview: "Planned changes", confirm: "Apply these changes?", doctor: "Installation diagnosis",
|
|
10
|
-
languageQuestion: "Language", scopeQuestion: "Installation scope", platformsQuestion: "Platforms",
|
|
11
|
-
mattQuestion: "Install missing Matt Pocock skills?", replaceQuestion: "Existing Matrix files are not proven managed. Back up and replace?",
|
|
12
|
-
yes: "Yes", no: "No", stateMissing: "missing", stateMatching: "up to date", stateComplete: "complete", stateUserModified: "user-modified", statePartial: "partial", stateOutdated: "outdated",
|
|
13
|
-
actionInstall: "install", actionKeep: "keep", actionBackupReplace: "back up and replace", actionRepair: "repair", actionBlocked: "requires approval",
|
|
14
|
-
dryRunComplete: "Dry run complete.", manifestMissing: "No Matrix installation manifest was found for this scope.", transactionPending: "An interrupted Matrix transaction is awaiting recovery. Run matrix init again before applying a new plan.", runtimeMissing: "Bundled Matrix runtime is missing.", brokenLink: "Broken Matrix link detected.", mattInherited: "Matt skills are satisfied by the global installation."
|
|
15
|
-
},
|
|
16
|
-
"zh-CN": {
|
|
17
|
-
manifestMissing: "当前安装范围没有 Matrix 安装清单。", transactionPending: "发现中断的 Matrix 安装事务。请再次运行 matrix init 后再应用新计划。", runtimeMissing: "缺少随附的 Matrix runtime。", brokenLink: "检测到断开的 Matrix 链接。", mattInherited: "Matt skills 由全局安装提供。",
|
|
18
|
-
languageQuestion: "语言", scopeQuestion: "安装范围", platformsQuestion: "要配置的平台",
|
|
19
|
-
mattQuestion: "是否安装缺失的 Matt Pocock skills?", replaceQuestion: "现有 Matrix 文件无法确认由 Matrix 管理。是否备份并替换?",
|
|
20
|
-
yes: "是", no: "否", stateMissing: "未安装", stateMatching: "已是最新", stateComplete: "完整", stateUserModified: "已被用户修改", statePartial: "不完整", stateOutdated: "可更新",
|
|
21
|
-
actionInstall: "安装", actionKeep: "保留", actionBackupReplace: "备份并替换", actionRepair: "修复", actionBlocked: "需要确认",
|
|
22
|
-
dryRunComplete: "预览完成。",
|
|
23
|
-
title: "
|
|
24
|
-
recommended: "推荐配置", apply: "开始安装", customize: "自定义", cancel: "取消",
|
|
25
|
-
project: "项目(当前目录)", global: "全局(所有项目)",
|
|
26
|
-
copy: "复制(推荐)", symlink: "符号链接(高级)",
|
|
27
|
-
matrix: "Matrix", matt: "Matt Pocock skills", installed: "已安装", ready: "Matrix 已就绪。",
|
|
28
|
-
noPlatform: "未检测到已验证的平台。请选择 Claude Code、Codex,或两者。",
|
|
29
|
-
preview: "计划变更", confirm: "确认应用这些变更?", doctor: "安装诊断"
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export function text(language, key) { return messages[language]?.[key] ?? messages.en[key] ?? key; }
|
|
1
|
+
export const messages = {
|
|
2
|
+
en: {
|
|
3
|
+
title: "Evidence-driven workflow with Prim and Arch orchestration",
|
|
4
|
+
recommended: "Recommended setup", apply: "Apply", customize: "Customize", cancel: "Cancel",
|
|
5
|
+
project: "Project (current directory)", global: "Global (all projects)",
|
|
6
|
+
copy: "Copy (recommended)", symlink: "Symlink (advanced)",
|
|
7
|
+
matrix: "Matrix", matt: "Matt Pocock skills", installed: "installed", ready: "Matrix is ready.",
|
|
8
|
+
noPlatform: "No verified platform was detected. Select Claude Code, Codex, or both.",
|
|
9
|
+
preview: "Planned changes", confirm: "Apply these changes?", doctor: "Installation diagnosis",
|
|
10
|
+
languageQuestion: "Language", scopeQuestion: "Installation scope", platformsQuestion: "Platforms", orchestrationQuestion: "Default orchestration",
|
|
11
|
+
mattQuestion: "Install missing Matt Pocock skills?", replaceQuestion: "Existing Matrix files are not proven managed. Back up and replace?",
|
|
12
|
+
yes: "Yes", no: "No", stateMissing: "missing", stateMatching: "up to date", stateComplete: "complete", stateUserModified: "user-modified", statePartial: "partial", stateOutdated: "outdated",
|
|
13
|
+
actionInstall: "install", actionKeep: "keep", actionBackupReplace: "back up and replace", actionRepair: "repair", actionBlocked: "requires approval",
|
|
14
|
+
dryRunComplete: "Dry run complete.", manifestMissing: "No Matrix installation manifest was found for this scope.", transactionPending: "An interrupted Matrix transaction is awaiting recovery. Run matrix init again before applying a new plan.", runtimeMissing: "Bundled Matrix runtime is missing.", brokenLink: "Broken Matrix link detected.", mattInherited: "Matt skills are satisfied by the global installation."
|
|
15
|
+
},
|
|
16
|
+
"zh-CN": {
|
|
17
|
+
manifestMissing: "当前安装范围没有 Matrix 安装清单。", transactionPending: "发现中断的 Matrix 安装事务。请再次运行 matrix init 后再应用新计划。", runtimeMissing: "缺少随附的 Matrix runtime。", brokenLink: "检测到断开的 Matrix 链接。", mattInherited: "Matt skills 由全局安装提供。",
|
|
18
|
+
languageQuestion: "语言", scopeQuestion: "安装范围", platformsQuestion: "要配置的平台", orchestrationQuestion: "默认编排模式",
|
|
19
|
+
mattQuestion: "是否安装缺失的 Matt Pocock skills?", replaceQuestion: "现有 Matrix 文件无法确认由 Matrix 管理。是否备份并替换?",
|
|
20
|
+
yes: "是", no: "否", stateMissing: "未安装", stateMatching: "已是最新", stateComplete: "完整", stateUserModified: "已被用户修改", statePartial: "不完整", stateOutdated: "可更新",
|
|
21
|
+
actionInstall: "安装", actionKeep: "保留", actionBackupReplace: "备份并替换", actionRepair: "修复", actionBlocked: "需要确认",
|
|
22
|
+
dryRunComplete: "预览完成。",
|
|
23
|
+
title: "提供 Prim 与 Arch 编排的证据驱动工作流",
|
|
24
|
+
recommended: "推荐配置", apply: "开始安装", customize: "自定义", cancel: "取消",
|
|
25
|
+
project: "项目(当前目录)", global: "全局(所有项目)",
|
|
26
|
+
copy: "复制(推荐)", symlink: "符号链接(高级)",
|
|
27
|
+
matrix: "Matrix", matt: "Matt Pocock skills", installed: "已安装", ready: "Matrix 已就绪。",
|
|
28
|
+
noPlatform: "未检测到已验证的平台。请选择 Claude Code、Codex,或两者。",
|
|
29
|
+
preview: "计划变更", confirm: "确认应用这些变更?", doctor: "安装诊断"
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function text(language, key) { return messages[language]?.[key] ?? messages.en[key] ?? key; }
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
export const PACKAGE_NAME = "@rosenfan/matrix";
|
|
7
|
+
const REGISTRY = "https://registry.npmjs.org/@rosenfan%2fmatrix/latest";
|
|
8
|
+
|
|
9
|
+
function parse(version) {
|
|
10
|
+
const match = String(version ?? "").match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
|
|
11
|
+
return match ? { numeric: match.slice(1, 4).map(Number), prerelease: match[4] ?? null } : null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function compareVersions(left, right) {
|
|
15
|
+
const a = parse(left); const b = parse(right);
|
|
16
|
+
if (!a || !b) return null;
|
|
17
|
+
for (let index = 0; index < 3; index += 1) if (a.numeric[index] !== b.numeric[index]) return a.numeric[index] - b.numeric[index];
|
|
18
|
+
if (a.prerelease === b.prerelease) return 0;
|
|
19
|
+
if (a.prerelease === null) return 1;
|
|
20
|
+
if (b.prerelease === null) return -1;
|
|
21
|
+
return a.prerelease.localeCompare(b.prerelease, undefined, { numeric: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function latestVersion(fetchImpl = fetch) {
|
|
25
|
+
const response = await fetchImpl(REGISTRY, { headers: { accept: "application/json" } });
|
|
26
|
+
if (!response.ok) throw new Error(`registry returned ${response.status}`);
|
|
27
|
+
const body = await response.json();
|
|
28
|
+
if (!parse(body.version)) throw new Error("registry returned an invalid version");
|
|
29
|
+
return body.version;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function run(command, args, options = {}) {
|
|
33
|
+
const result = spawnSync(command, args, { encoding: "utf8", ...options });
|
|
34
|
+
return { ok: !result.error && result.status === 0, status: result.status, error: result.error?.message, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function validateCandidate(packageRoot, expectedVersion) {
|
|
38
|
+
const manifestFile = path.join(packageRoot, "package.json");
|
|
39
|
+
try {
|
|
40
|
+
const manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
|
|
41
|
+
if (manifest.name !== PACKAGE_NAME || manifest.version !== expectedVersion) return { ok: false, reason: "candidate package identity mismatch" };
|
|
42
|
+
for (const directory of [".claude/skills", "assets/skills-zh-CN", "src", "bin"]) if (!fs.existsSync(path.join(packageRoot, directory))) return { ok: false, reason: `candidate missing ${directory}` };
|
|
43
|
+
for (const languageRoot of [path.join(packageRoot, ".claude", "skills"), path.join(packageRoot, "assets", "skills-zh-CN")]) if (!fs.existsSync(path.join(languageRoot, "matrix", "SKILL.md"))) return { ok: false, reason: "candidate Matrix assets are incomplete" };
|
|
44
|
+
for (const args of [["--version"], ["--help"], ["workflow", "doctor", "--json"]]) {
|
|
45
|
+
const result = run(process.execPath, [path.join(packageRoot, "bin", "matrix.js"), ...args], { cwd: os.tmpdir() });
|
|
46
|
+
if (!result.ok) return { ok: false, reason: `candidate command failed: matrix ${args.join(" ")}` };
|
|
47
|
+
}
|
|
48
|
+
return { ok: true };
|
|
49
|
+
} catch (error) { return { ok: false, reason: error.message }; }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function npmGlobalRoot() {
|
|
53
|
+
const result = run("npm", ["root", "-g"]);
|
|
54
|
+
if (!result.ok) throw new Error(result.error ?? result.stderr.trim() ?? "npm root -g failed");
|
|
55
|
+
return result.stdout.trim();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function validateRegistryCandidate(version) {
|
|
59
|
+
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "matrix-update-"));
|
|
60
|
+
try {
|
|
61
|
+
const install = run("npm", ["install", "--prefix", temporary, "--ignore-scripts", "--no-save", "--package-lock=false", `${PACKAGE_NAME}@${version}`], { cwd: temporary });
|
|
62
|
+
if (!install.ok) return { ok: false, reason: install.error ?? install.stderr.trim() ?? "candidate installation failed" };
|
|
63
|
+
return validateCandidate(path.join(temporary, "node_modules", "@rosenfan", "matrix"), version);
|
|
64
|
+
} finally { fs.rmSync(temporary, { recursive: true, force: true }); }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function installGlobal(version) {
|
|
68
|
+
const result = run("npm", ["install", "--global", `${PACKAGE_NAME}@${version}`]);
|
|
69
|
+
return result.ok ? { ok: true } : { ok: false, reason: result.error ?? result.stderr.trim() ?? "npm install failed" };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function selfUpdate(currentVersion, targetVersion) {
|
|
73
|
+
const candidate = validateRegistryCandidate(targetVersion);
|
|
74
|
+
if (!candidate.ok) return candidate;
|
|
75
|
+
const install = installGlobal(targetVersion);
|
|
76
|
+
if (install.ok) return install;
|
|
77
|
+
const rollback = installGlobal(currentVersion);
|
|
78
|
+
return { ok: false, reason: `${install.reason}; ${rollback.ok ? `restored ${currentVersion}` : `rollback failed: ${rollback.reason}`}` };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function globalBinPath() { return path.join(npmGlobalRoot(), "@rosenfan", "matrix", "bin", "matrix.js"); }
|
package/src/ui.js
CHANGED
|
@@ -1,89 +1,89 @@
|
|
|
1
|
-
import readline from "node:readline";
|
|
2
|
-
import { stdin, stdout } from "node:process";
|
|
3
|
-
|
|
4
|
-
const codes = { cyan: "\u001b[96m", blue: "\u001b[94m", green: "\u001b[92m", yellow: "\u001b[93m", magenta: "\u001b[95m", dim: "\u001b[2m", reset: "\u001b[0m" };
|
|
5
|
-
const pause = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
6
|
-
const logo = String.raw`███╗ ███╗ █████╗ ████████╗██████╗ ██╗██╗ ██╗
|
|
7
|
-
████╗ ████║██╔══██╗╚══██╔══╝██╔══██╗██║╚██╗██╔╝
|
|
8
|
-
██╔████╔██║███████║ ██║ ██████╔╝██║ ╚███╔╝
|
|
9
|
-
██║╚██╔╝██║██╔══██║ ██║ ██╔══██╗██║ ██╔██╗
|
|
10
|
-
██║ ╚═╝ ██║██║ ██║ ██║ ██║ ██║██║██╔╝ ██╗
|
|
11
|
-
╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═╝`;
|
|
12
|
-
const bannerLines = 9;
|
|
13
|
-
|
|
14
|
-
function cancelled() {
|
|
15
|
-
const error = new Error("Cancelled.");
|
|
16
|
-
error.code = "CANCELLED";
|
|
17
|
-
return error;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function createUi({ color = stdout.isTTY, animate = true } = {}) {
|
|
21
|
-
const paint = (name, value) => color ? `${codes[name]}${value}${codes.reset}` : value;
|
|
22
|
-
const shouldAnimate = animate && color && stdout.isTTY;
|
|
23
|
-
const writeFrame = (stage, status, clearPrevious = false) => {
|
|
24
|
-
if (clearPrevious) stdout.write(`\u001b[${bannerLines}A`);
|
|
25
|
-
stdout.write("\u001b[2K" + paint("green", `[ MATRIX BOOT // ${status} ]`) + "\n");
|
|
26
|
-
for (const line of logo.split("\n")) {
|
|
27
|
-
const reveal = Math.floor(line.length * stage);
|
|
28
|
-
const stable = line.slice(0, reveal);
|
|
29
|
-
const interference = stage === 1 ? "" : "#@%/\\|~".repeat(Math.ceil((line.length - reveal) / 7)).slice(0, line.length - reveal);
|
|
30
|
-
stdout.write("\u001b[2K" + paint(stage < 0.7 ? "green" : "cyan", stable + interference) + "\n");
|
|
31
|
-
}
|
|
32
|
-
stdout.write("\u001b[2K" + paint("yellow", ` ${Math.round(stage * 100)}% ${status}`) + "\n\n");
|
|
33
|
-
};
|
|
34
|
-
return {
|
|
35
|
-
async banner() {
|
|
36
|
-
if (shouldAnimate) {
|
|
37
|
-
const sequence = [
|
|
38
|
-
[0.04, "SIGNAL LOST", 260],
|
|
39
|
-
[0.28, "MAGNETIC INTERFERENCE", 320],
|
|
40
|
-
[0.62, "NEURAL LINK FORMING", 360],
|
|
41
|
-
[1, "SIGNAL LOCKED", 420]
|
|
42
|
-
];
|
|
43
|
-
stdout.write("\u001b[?25l");
|
|
44
|
-
try {
|
|
45
|
-
for (let index = 0; index < sequence.length; index += 1) {
|
|
46
|
-
const [stage, status, duration] = sequence[index];
|
|
47
|
-
writeFrame(stage, status, index > 0);
|
|
48
|
-
await pause(duration);
|
|
49
|
-
}
|
|
50
|
-
} finally {
|
|
51
|
-
stdout.write("\u001b[?25h");
|
|
52
|
-
}
|
|
53
|
-
stdout.write(`\u001b[${bannerLines}A`);
|
|
54
|
-
}
|
|
55
|
-
console.log(paint("cyan", logo));
|
|
56
|
-
console.log(paint("yellow", "Evidence-driven workflow
|
|
57
|
-
console.log();
|
|
58
|
-
},
|
|
59
|
-
info(message) { console.log(`${paint("blue", "> ")} ${message}`); },
|
|
60
|
-
success(message) { console.log(`${paint("green", "OK")} ${message}`); },
|
|
61
|
-
warn(message) { console.log(`${paint("yellow", "!")} ${message}`); },
|
|
62
|
-
muted(message) { console.log(paint("dim", message)); },
|
|
63
|
-
async select(question, choices, initial = 0) {
|
|
64
|
-
if (!stdin.isTTY || !stdout.isTTY) return choices[initial].value;
|
|
65
|
-
let selected = initial;
|
|
66
|
-
readline.emitKeypressEvents(stdin); const wasRaw = stdin.isRaw; stdin.setRawMode(true); stdin.resume(); stdout.write(`? ${question}\n`);
|
|
67
|
-
const render = (clear = false) => { if (clear) stdout.write(`\u001b[${choices.length}A`); choices.forEach((choice, index) => { stdout.write("\u001b[2K"); stdout.write(`${index === selected ? paint("blue", ">") : " "} ${index === selected ? paint("blue", choice.label) : choice.label}\n`); }); };
|
|
68
|
-
render();
|
|
69
|
-
return new Promise((resolve, reject) => {
|
|
70
|
-
const cleanup = () => { stdin.off("keypress", onKeypress); stdin.setRawMode(Boolean(wasRaw)); stdin.pause(); };
|
|
71
|
-
const onKeypress = (_value, key = {}) => { if (key.ctrl && key.name === "c") { cleanup(); reject(cancelled()); } else if (["up", "k"].includes(key.name)) { selected = (selected - 1 + choices.length) % choices.length; render(true); } else if (["down", "j"].includes(key.name)) { selected = (selected + 1) % choices.length; render(true); } else if (key.name === "return") { cleanup(); resolve(choices[selected].value); } };
|
|
72
|
-
stdin.on("keypress", onKeypress);
|
|
73
|
-
});
|
|
74
|
-
},
|
|
75
|
-
async selectMany(question, choices) {
|
|
76
|
-
if (!stdin.isTTY || !stdout.isTTY) return choices.map((choice) => choice.value);
|
|
77
|
-
let selected = 0; const checked = new Set(choices.map((choice) => choice.value));
|
|
78
|
-
readline.emitKeypressEvents(stdin); const wasRaw = stdin.isRaw; stdin.setRawMode(true); stdin.resume(); stdout.write(`? ${question}\n`);
|
|
79
|
-
const render = (clear = false) => { if (clear) stdout.write(`\u001b[${choices.length}A`); choices.forEach((choice, index) => { stdout.write("\u001b[2K"); const marker = checked.has(choice.value) ? "[x]" : "[ ]"; stdout.write(`${index === selected ? paint("blue", ">") : " "} ${marker} ${index === selected ? paint("blue", choice.label) : choice.label}\n`); }); };
|
|
80
|
-
render();
|
|
81
|
-
return new Promise((resolve, reject) => {
|
|
82
|
-
const cleanup = () => { stdin.off("keypress", onKeypress); stdin.setRawMode(Boolean(wasRaw)); stdin.pause(); };
|
|
83
|
-
const onKeypress = (_value, key = {}) => { if (key.ctrl && key.name === "c") { cleanup(); reject(cancelled()); } else if (["up", "k"].includes(key.name)) { selected = (selected - 1 + choices.length) % choices.length; render(true); } else if (["down", "j"].includes(key.name)) { selected = (selected + 1) % choices.length; render(true); } else if (key.name === "space") { const value = choices[selected].value; checked.has(value) ? checked.delete(value) : checked.add(value); render(true); } else if (key.name === "return") { cleanup(); resolve(choices.filter((choice) => checked.has(choice.value)).map((choice) => choice.value)); } };
|
|
84
|
-
stdin.on("keypress", onKeypress);
|
|
85
|
-
});
|
|
86
|
-
},
|
|
87
|
-
async confirm(question, defaultValue = true, labels = { yes: "Yes", no: "No" }) { return this.select(question, [{ label: defaultValue ? labels.yes : labels.no, value: defaultValue }, { label: defaultValue ? labels.no : labels.yes, value: !defaultValue }]); }
|
|
88
|
-
};
|
|
89
|
-
}
|
|
1
|
+
import readline from "node:readline";
|
|
2
|
+
import { stdin, stdout } from "node:process";
|
|
3
|
+
|
|
4
|
+
const codes = { cyan: "\u001b[96m", blue: "\u001b[94m", green: "\u001b[92m", yellow: "\u001b[93m", magenta: "\u001b[95m", dim: "\u001b[2m", reset: "\u001b[0m" };
|
|
5
|
+
const pause = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
6
|
+
const logo = String.raw`███╗ ███╗ █████╗ ████████╗██████╗ ██╗██╗ ██╗
|
|
7
|
+
████╗ ████║██╔══██╗╚══██╔══╝██╔══██╗██║╚██╗██╔╝
|
|
8
|
+
██╔████╔██║███████║ ██║ ██████╔╝██║ ╚███╔╝
|
|
9
|
+
██║╚██╔╝██║██╔══██║ ██║ ██╔══██╗██║ ██╔██╗
|
|
10
|
+
██║ ╚═╝ ██║██║ ██║ ██║ ██║ ██║██║██╔╝ ██╗
|
|
11
|
+
╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═╝`;
|
|
12
|
+
const bannerLines = 9;
|
|
13
|
+
|
|
14
|
+
function cancelled() {
|
|
15
|
+
const error = new Error("Cancelled.");
|
|
16
|
+
error.code = "CANCELLED";
|
|
17
|
+
return error;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createUi({ color = stdout.isTTY, animate = true } = {}) {
|
|
21
|
+
const paint = (name, value) => color ? `${codes[name]}${value}${codes.reset}` : value;
|
|
22
|
+
const shouldAnimate = animate && color && stdout.isTTY;
|
|
23
|
+
const writeFrame = (stage, status, clearPrevious = false) => {
|
|
24
|
+
if (clearPrevious) stdout.write(`\u001b[${bannerLines}A`);
|
|
25
|
+
stdout.write("\u001b[2K" + paint("green", `[ MATRIX BOOT // ${status} ]`) + "\n");
|
|
26
|
+
for (const line of logo.split("\n")) {
|
|
27
|
+
const reveal = Math.floor(line.length * stage);
|
|
28
|
+
const stable = line.slice(0, reveal);
|
|
29
|
+
const interference = stage === 1 ? "" : "#@%/\\|~".repeat(Math.ceil((line.length - reveal) / 7)).slice(0, line.length - reveal);
|
|
30
|
+
stdout.write("\u001b[2K" + paint(stage < 0.7 ? "green" : "cyan", stable + interference) + "\n");
|
|
31
|
+
}
|
|
32
|
+
stdout.write("\u001b[2K" + paint("yellow", ` ${Math.round(stage * 100)}% ${status}`) + "\n\n");
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
async banner() {
|
|
36
|
+
if (shouldAnimate) {
|
|
37
|
+
const sequence = [
|
|
38
|
+
[0.04, "SIGNAL LOST", 260],
|
|
39
|
+
[0.28, "MAGNETIC INTERFERENCE", 320],
|
|
40
|
+
[0.62, "NEURAL LINK FORMING", 360],
|
|
41
|
+
[1, "SIGNAL LOCKED", 420]
|
|
42
|
+
];
|
|
43
|
+
stdout.write("\u001b[?25l");
|
|
44
|
+
try {
|
|
45
|
+
for (let index = 0; index < sequence.length; index += 1) {
|
|
46
|
+
const [stage, status, duration] = sequence[index];
|
|
47
|
+
writeFrame(stage, status, index > 0);
|
|
48
|
+
await pause(duration);
|
|
49
|
+
}
|
|
50
|
+
} finally {
|
|
51
|
+
stdout.write("\u001b[?25h");
|
|
52
|
+
}
|
|
53
|
+
stdout.write(`\u001b[${bannerLines}A`);
|
|
54
|
+
}
|
|
55
|
+
console.log(paint("cyan", logo));
|
|
56
|
+
console.log(paint("yellow", "Evidence-driven workflow with Prim and Arch orchestration"));
|
|
57
|
+
console.log();
|
|
58
|
+
},
|
|
59
|
+
info(message) { console.log(`${paint("blue", "> ")} ${message}`); },
|
|
60
|
+
success(message) { console.log(`${paint("green", "OK")} ${message}`); },
|
|
61
|
+
warn(message) { console.log(`${paint("yellow", "!")} ${message}`); },
|
|
62
|
+
muted(message) { console.log(paint("dim", message)); },
|
|
63
|
+
async select(question, choices, initial = 0) {
|
|
64
|
+
if (!stdin.isTTY || !stdout.isTTY) return choices[initial].value;
|
|
65
|
+
let selected = initial;
|
|
66
|
+
readline.emitKeypressEvents(stdin); const wasRaw = stdin.isRaw; stdin.setRawMode(true); stdin.resume(); stdout.write(`? ${question}\n`);
|
|
67
|
+
const render = (clear = false) => { if (clear) stdout.write(`\u001b[${choices.length}A`); choices.forEach((choice, index) => { stdout.write("\u001b[2K"); stdout.write(`${index === selected ? paint("blue", ">") : " "} ${index === selected ? paint("blue", choice.label) : choice.label}\n`); }); };
|
|
68
|
+
render();
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const cleanup = () => { stdin.off("keypress", onKeypress); stdin.setRawMode(Boolean(wasRaw)); stdin.pause(); };
|
|
71
|
+
const onKeypress = (_value, key = {}) => { if (key.ctrl && key.name === "c") { cleanup(); reject(cancelled()); } else if (["up", "k"].includes(key.name)) { selected = (selected - 1 + choices.length) % choices.length; render(true); } else if (["down", "j"].includes(key.name)) { selected = (selected + 1) % choices.length; render(true); } else if (key.name === "return") { cleanup(); resolve(choices[selected].value); } };
|
|
72
|
+
stdin.on("keypress", onKeypress);
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
async selectMany(question, choices) {
|
|
76
|
+
if (!stdin.isTTY || !stdout.isTTY) return choices.map((choice) => choice.value);
|
|
77
|
+
let selected = 0; const checked = new Set(choices.map((choice) => choice.value));
|
|
78
|
+
readline.emitKeypressEvents(stdin); const wasRaw = stdin.isRaw; stdin.setRawMode(true); stdin.resume(); stdout.write(`? ${question}\n`);
|
|
79
|
+
const render = (clear = false) => { if (clear) stdout.write(`\u001b[${choices.length}A`); choices.forEach((choice, index) => { stdout.write("\u001b[2K"); const marker = checked.has(choice.value) ? "[x]" : "[ ]"; stdout.write(`${index === selected ? paint("blue", ">") : " "} ${marker} ${index === selected ? paint("blue", choice.label) : choice.label}\n`); }); };
|
|
80
|
+
render();
|
|
81
|
+
return new Promise((resolve, reject) => {
|
|
82
|
+
const cleanup = () => { stdin.off("keypress", onKeypress); stdin.setRawMode(Boolean(wasRaw)); stdin.pause(); };
|
|
83
|
+
const onKeypress = (_value, key = {}) => { if (key.ctrl && key.name === "c") { cleanup(); reject(cancelled()); } else if (["up", "k"].includes(key.name)) { selected = (selected - 1 + choices.length) % choices.length; render(true); } else if (["down", "j"].includes(key.name)) { selected = (selected + 1) % choices.length; render(true); } else if (key.name === "space") { const value = choices[selected].value; checked.has(value) ? checked.delete(value) : checked.add(value); render(true); } else if (key.name === "return") { cleanup(); resolve(choices.filter((choice) => checked.has(choice.value)).map((choice) => choice.value)); } };
|
|
84
|
+
stdin.on("keypress", onKeypress);
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
async confirm(question, defaultValue = true, labels = { yes: "Yes", no: "No" }) { return this.select(question, [{ label: defaultValue ? labels.yes : labels.no, value: defaultValue }, { label: defaultValue ? labels.no : labels.yes, value: !defaultValue }]); }
|
|
88
|
+
};
|
|
89
|
+
}
|