@ronaldjdevfs/forge 1.1.0 → 1.3.0-beta
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 +38 -23
- package/package.json +11 -3
- package/skills/forge/SKILL.md +11 -6
- package/skills/forge/scripts/forgeSentinel-lib.mjs +86 -0
- package/skills/forge/scripts/forgeSentinel.mjs +184 -0
- package/skills/forge/scripts/forgeSmith-admin.mjs +104 -0
- package/skills/forge/scripts/forgeSmith.mjs +164 -0
- package/skills/forge/scripts/pin.mjs +10 -3
- package/skills/forge/scripts/posttool.mjs +17 -211
- package/skills/forge/templates/agents/SKILL.md.template +283 -0
- package/skills/forge/templates/agents/agents/hooks.json +18 -0
- package/skills/forge/templates/agents/claude/CLAUDE.md +99 -0
- package/skills/forge/templates/agents/claude/settings.local.json +18 -0
- package/skills/forge/templates/agents/codex/hooks.json +18 -0
- package/skills/forge/templates/agents/cursor/.cursorrules +97 -0
- package/skills/forge/templates/agents/cursor/hooks.json +11 -0
- package/skills/forge/templates/agents/gemini/SKILL.md +13 -0
- package/skills/forge/tests/core.test.mjs +4 -4
- package/src/agents.mjs +88 -0
- package/src/cli.js +187 -54
- package/src/wizard.mjs +526 -0
- package/logo.png +0 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { buildGraph } from "./graph.mjs";
|
|
6
|
+
import { buildContext } from "./context.mjs";
|
|
7
|
+
import { detectFeaturesOnSrc, allChecks } from "./detect.mjs";
|
|
8
|
+
import { evaluateRules } from "./registry/rules.mjs";
|
|
9
|
+
import {
|
|
10
|
+
CYAN, GREEN, RED, YELLOW, BOLD, RESET, DIM,
|
|
11
|
+
} from "./formatter.mjs";
|
|
12
|
+
import { writeAuditLog } from "./forgeSentinel-lib.mjs";
|
|
13
|
+
|
|
14
|
+
const ROOT = process.cwd();
|
|
15
|
+
|
|
16
|
+
const ALLOWED_EXTS = [".ts", ".mjs", ".js", ".tsx", ".jsx"];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Parse Cursor preToolUse event from stdin.
|
|
20
|
+
* Cursor sends: { toolName, args: { filePath, content } }
|
|
21
|
+
*/
|
|
22
|
+
function parseCursorEvent(stdin) {
|
|
23
|
+
if (!stdin) return null;
|
|
24
|
+
try {
|
|
25
|
+
const event = JSON.parse(stdin);
|
|
26
|
+
|
|
27
|
+
// Cursor preToolUse format
|
|
28
|
+
if (event.toolName && event.args?.filePath) {
|
|
29
|
+
return { filePath: event.args.filePath, content: event.args.content || "" };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Alternative: direct filePath + content
|
|
33
|
+
if (event.filePath && event.content !== undefined) {
|
|
34
|
+
return { filePath: event.filePath, content: event.content };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Array of proposed edits
|
|
38
|
+
if (Array.isArray(event.edits)) {
|
|
39
|
+
return event.edits.map(e => ({
|
|
40
|
+
filePath: e.filePath || e.path || "",
|
|
41
|
+
content: e.content || e.newContent || "",
|
|
42
|
+
})).filter(e => e.filePath)[0] || null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return null;
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isSourceFile(filePath) {
|
|
52
|
+
return filePath.startsWith("src/") && ALLOWED_EXTS.some(ext => filePath.endsWith(ext));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function hasCriticalViolations(result) {
|
|
56
|
+
return result.violations.some(v => v.severity === "CRITICAL" || v.severity === "ERROR");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Quick check against proposed content.
|
|
61
|
+
* Returns violations that would be introduced by the proposed content.
|
|
62
|
+
*/
|
|
63
|
+
async function checkProposedFile(filePath, content) {
|
|
64
|
+
if (!isSourceFile(filePath)) {
|
|
65
|
+
return { violations: [], total: 0, hasCritical: false, hasErrors: false };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const ctx = await buildContext();
|
|
69
|
+
const features = detectFeaturesOnSrc();
|
|
70
|
+
const graph = buildGraph(ROOT);
|
|
71
|
+
const results = allChecks(features, graph, ctx);
|
|
72
|
+
|
|
73
|
+
const violations = [];
|
|
74
|
+
for (const [, cat] of Object.entries(results)) {
|
|
75
|
+
for (const check of cat.checks) {
|
|
76
|
+
if (check.pass) continue;
|
|
77
|
+
|
|
78
|
+
const touchesFile = (check.detail && check.detail.includes(filePath)) ||
|
|
79
|
+
(check.label && check.label.includes(filePath));
|
|
80
|
+
|
|
81
|
+
if (!touchesFile) continue;
|
|
82
|
+
|
|
83
|
+
violations.push(check);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const graphViolations = evaluateRules(graph, ctx).filter(v => {
|
|
88
|
+
return (v.file && v.file.includes(filePath)) ||
|
|
89
|
+
(v.from && v.from.includes(filePath)) ||
|
|
90
|
+
(v.to && v.to.includes(filePath));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const allViolations = [...violations, ...graphViolations];
|
|
94
|
+
const hasCritical = allViolations.some(v => v.severity === "CRITICAL");
|
|
95
|
+
const hasErrors = allViolations.some(v => v.severity === "ERROR");
|
|
96
|
+
|
|
97
|
+
return { violations: allViolations, total: allViolations.length, hasCritical, hasErrors };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function buildDenyPayload(result) {
|
|
101
|
+
const criticalCount = result.violations.filter(v => v.severity === "CRITICAL").length;
|
|
102
|
+
const errorCount = result.violations.filter(v => v.severity === "ERROR").length;
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
deny: true,
|
|
106
|
+
severity: criticalCount > 0 ? "CRITICAL" : "ERROR",
|
|
107
|
+
message: `[forgeSmith] ⚠ ${result.total} violación(es) arquitectónica(s) (${criticalCount} CRITICAL, ${errorCount} ERROR). Corregí antes de escribir.`,
|
|
108
|
+
violations: result.violations.slice(0, 10).map(v => ({
|
|
109
|
+
severity: v.severity,
|
|
110
|
+
label: v.label,
|
|
111
|
+
rule: v.rule,
|
|
112
|
+
fix: v.fix,
|
|
113
|
+
})),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function buildAllowPayload() {
|
|
118
|
+
return { deny: false };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function main() {
|
|
122
|
+
const chunks = [];
|
|
123
|
+
if (!process.stdin.isTTY) {
|
|
124
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
125
|
+
}
|
|
126
|
+
const stdin = Buffer.concat(chunks).toString("utf-8");
|
|
127
|
+
|
|
128
|
+
if (!stdin) {
|
|
129
|
+
process.stdout.write(JSON.stringify(buildAllowPayload()));
|
|
130
|
+
process.exit(0);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const edit = parseCursorEvent(stdin);
|
|
134
|
+
if (!edit || !edit.filePath) {
|
|
135
|
+
process.stdout.write(JSON.stringify(buildAllowPayload()));
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const result = await checkProposedFile(edit.filePath, edit.content || "");
|
|
140
|
+
|
|
141
|
+
writeAuditLog(process.env, {
|
|
142
|
+
ts: new Date().toISOString(),
|
|
143
|
+
hook: "forgeSmith",
|
|
144
|
+
file: edit.filePath,
|
|
145
|
+
total: result.total,
|
|
146
|
+
hasCritical: result.hasCritical,
|
|
147
|
+
hasErrors: result.hasErrors,
|
|
148
|
+
}, ROOT);
|
|
149
|
+
|
|
150
|
+
if (result.total > 0 && hasCriticalViolations(result)) {
|
|
151
|
+
process.stdout.write(JSON.stringify(buildDenyPayload(result)));
|
|
152
|
+
} else {
|
|
153
|
+
process.stdout.write(JSON.stringify(buildAllowPayload()));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
process.exit(0);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (process.argv[1]?.endsWith("forgeSmith.mjs")) {
|
|
160
|
+
main().catch(() => {
|
|
161
|
+
process.stdout.write(JSON.stringify({ deny: false }));
|
|
162
|
+
process.exit(0);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
@@ -18,9 +18,16 @@ import { fileURLToPath } from 'node:url';
|
|
|
18
18
|
|
|
19
19
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
20
20
|
|
|
21
|
-
const HARNESS_DIRS = [
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
const HARNESS_DIRS = [
|
|
22
|
+
'.claude', '.cursor', '.gemini', '.codex', '.agents',
|
|
23
|
+
'.opencode', '.kiro', '.rovodev', '.trae', '.trae-cn', '.pi',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const VALID_COMMANDS = [
|
|
27
|
+
'forge', 'cast', 'inspect', 'quench', 'temper', 'chain', 'graph',
|
|
28
|
+
'relocate', 'reforge', 'smelt', 'inscribe', 'armorer',
|
|
29
|
+
'assay', 'nail', 'unnail', 'hook', 'api', 'rollback', 'state',
|
|
30
|
+
];
|
|
24
31
|
|
|
25
32
|
const PIN_MARKER = '<!-- forge-pinned-skill -->';
|
|
26
33
|
|
|
@@ -1,224 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* posttool.mjs —
|
|
4
|
+
* posttool.mjs — DEPRECATED: Backward-compat wrapper.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Use forgeSentinel.mjs en su lugar.
|
|
7
|
+
* node forgeSentinel.mjs [options]
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* node posttool.mjs <file1> [file2...] → analiza archivos específicos
|
|
12
|
-
* node posttool.mjs --diff → solo archivos changed vs default branch
|
|
13
|
-
* node posttool.mjs --json → salida JSON
|
|
14
|
-
* node posttool.mjs --strict → exit code 1 si hay CRITICAL/ERROR
|
|
15
|
-
* node posttool.mjs --reminder → solo recordatorio (no bloquea)
|
|
16
|
-
*
|
|
17
|
-
* Uso (API):
|
|
18
|
-
* import { postToolCheck } from "./posttool.mjs";
|
|
19
|
-
* const result = await postToolCheck([...files]);
|
|
20
|
-
*
|
|
21
|
-
* Integración en SKILL.md (Fase 7 del pipeline):
|
|
22
|
-
* node .opencode/skills/forge/scripts/posttool.mjs --reminder
|
|
9
|
+
* Este wrapper delega en forgeSentinel.mjs para mantener compatibilidad
|
|
10
|
+
* con scripts y referencias existentes.
|
|
23
11
|
*/
|
|
24
12
|
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import { execFileSync } from "child_process";
|
|
28
|
-
import { buildGraph } from "./graph.mjs";
|
|
29
|
-
import { buildContext } from "./context.mjs";
|
|
30
|
-
import { detectFeaturesOnSrc, allChecks, loadAllInlineIgnores, isIgnored } from "./detect.mjs";
|
|
31
|
-
import { evaluateRules } from "./registry/rules.mjs";
|
|
32
|
-
import {
|
|
33
|
-
CYAN, GREEN, RED, YELLOW, BOLD, RESET, DIM, GRAY,
|
|
34
|
-
formatViolation, formatCheck, formatJson,
|
|
35
|
-
} from "./formatter.mjs";
|
|
36
|
-
|
|
37
|
-
const ROOT = process.cwd();
|
|
38
|
-
|
|
39
|
-
function getChangedFiles() {
|
|
40
|
-
let files = [];
|
|
41
|
-
try {
|
|
42
|
-
const defaultBranch = execFileSync("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] })
|
|
43
|
-
.trim().replace("refs/remotes/origin/", "");
|
|
44
|
-
const raw = execFileSync("git", ["diff", "--name-only", "--diff-filter=ACMR", `${defaultBranch}...HEAD`], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
|
|
45
|
-
files = raw.trim().split("\n").filter(Boolean);
|
|
46
|
-
} catch {}
|
|
47
|
-
|
|
48
|
-
if (files.length === 0) {
|
|
49
|
-
try {
|
|
50
|
-
const raw = execFileSync("git", ["status", "--porcelain"], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
|
|
51
|
-
files = raw.split("\n").filter(Boolean).map(l => l.slice(3));
|
|
52
|
-
} catch {}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return files.filter(f => f.startsWith("src/"));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function readFile(path) {
|
|
59
|
-
try { return readFileSync(path, "utf-8"); } catch { return null; }
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Check modified files for violations.
|
|
64
|
-
* Returns { violations, graphViolations, total, hasCritical, hasErrors, summary }
|
|
65
|
-
*/
|
|
66
|
-
export async function postToolCheck(files, opts = {}) {
|
|
67
|
-
const { strict = false, reminder = false } = opts;
|
|
68
|
-
|
|
69
|
-
if (!files || files.length === 0) {
|
|
70
|
-
files = getChangedFiles();
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Only check source files
|
|
74
|
-
files = files.filter(f =>
|
|
75
|
-
f.endsWith(".ts") || f.endsWith(".mjs") || f.endsWith(".js") || f.endsWith(".tsx")
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
if (files.length === 0) {
|
|
79
|
-
return { violations: [], graphViolations: [], total: 0, hasCritical: false, hasErrors: false, filesChecked: 0, summary: "Sin archivos fuente modificados" };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Build context and run full audit
|
|
83
|
-
const ctx = await buildContext();
|
|
84
|
-
const features = detectFeaturesOnSrc();
|
|
85
|
-
const graph = buildGraph(ROOT);
|
|
86
|
-
const results = allChecks(features, graph, ctx);
|
|
87
|
-
|
|
88
|
-
// Load inline ignores
|
|
89
|
-
const allIgnores = loadAllInlineIgnores(join(ROOT, "src"));
|
|
90
|
-
|
|
91
|
-
// Collect violations that touch the modified files
|
|
92
|
-
const violations = [];
|
|
93
|
-
for (const [, cat] of Object.entries(results)) {
|
|
94
|
-
for (const check of cat.checks) {
|
|
95
|
-
if (check.pass) continue;
|
|
96
|
-
|
|
97
|
-
// Only include violations touching modified files
|
|
98
|
-
const touchesFile = files.some(f =>
|
|
99
|
-
(check.detail && check.detail.includes(f)) ||
|
|
100
|
-
(check.label && check.label.includes(f))
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
if (!touchesFile && !reminder) continue;
|
|
104
|
-
|
|
105
|
-
// Check inline ignores
|
|
106
|
-
if (isIgnored(check, allIgnores)) continue;
|
|
107
|
-
|
|
108
|
-
violations.push(check);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Also evaluate graph rules for changed files
|
|
113
|
-
const graphViolations = evaluateRules(graph, ctx).filter(v => {
|
|
114
|
-
const touchesFile = files.some(f =>
|
|
115
|
-
(v.file && v.file.includes(f)) ||
|
|
116
|
-
(v.from && v.from.includes(f)) ||
|
|
117
|
-
(v.to && v.to.includes(f))
|
|
118
|
-
);
|
|
119
|
-
return touchesFile || reminder;
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const allViolations = [...violations, ...graphViolations];
|
|
123
|
-
const hasCritical = allViolations.some(v => v.severity === "CRITICAL");
|
|
124
|
-
const hasErrors = allViolations.some(v => v.severity === "ERROR");
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
violations: allViolations,
|
|
128
|
-
total: allViolations.length,
|
|
129
|
-
hasCritical,
|
|
130
|
-
hasErrors,
|
|
131
|
-
filesChecked: files.length,
|
|
132
|
-
summary: hasCritical
|
|
133
|
-
? `⚠ ${allViolations.length} violación(es) — ${allViolations.filter(v => v.severity === "CRITICAL").length} CRITICAL, ${allViolations.filter(v => v.severity === "ERROR").length} ERROR`
|
|
134
|
-
: hasErrors
|
|
135
|
-
? `⚠ ${allViolations.length} violación(es) — ${allViolations.filter(v => v.severity === "ERROR").length} ERROR`
|
|
136
|
-
: `${allViolations.length} violación(es) menores (WARNING/INFO)`,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function printResult(result, opts = {}) {
|
|
141
|
-
const { reminder = false } = opts;
|
|
142
|
-
|
|
143
|
-
console.log(`\n${BOLD}${CYAN}═══ Forge PostTool Hook ═══${RESET}`);
|
|
144
|
-
|
|
145
|
-
if (result.total === 0) {
|
|
146
|
-
console.log(` ${GREEN}✔${RESET} Sin violaciones arquitectónicas en archivos modificados`);
|
|
147
|
-
console.log(` ${DIM}Archivos revisados: ${result.filesChecked}${RESET}\n`);
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
console.log(` ${DIM}Archivos revisados: ${result.filesChecked}${RESET}`);
|
|
152
|
-
console.log(` ${DIM}Violaciones: ${result.total}${RESET}\n`);
|
|
13
|
+
import { join, dirname } from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
153
15
|
|
|
154
|
-
|
|
155
|
-
console.log(` ${YELLOW}Recordatorio:${RESET} Los archivos modificados tienen ${result.total} violación(es) arquitectónica(s).`);
|
|
156
|
-
console.log(` Ejecuta ${CYAN}forge quench${RESET} para ver el detalle completo.\n`);
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
157
17
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
console.log(` ${RED}⚠ ${criticalCount} violación(es) CRITICAL detectadas${RESET}`);
|
|
161
|
-
}
|
|
162
|
-
if (result.hasErrors) {
|
|
163
|
-
const errorCount = result.violations.filter(v => v.severity === "ERROR").length;
|
|
164
|
-
console.log(` ${RED}⚠ ${errorCount} violación(es) ERROR detectadas${RESET}`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Print first 5 violations as summary
|
|
168
|
-
const toShow = result.violations.slice(0, 5);
|
|
169
|
-
for (const v of toShow) {
|
|
170
|
-
console.log(formatCheck(v));
|
|
171
|
-
}
|
|
172
|
-
if (result.violations.length > 5) {
|
|
173
|
-
console.log(` ${DIM}... y ${result.violations.length - 5} más${RESET}`);
|
|
174
|
-
}
|
|
175
|
-
console.log();
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
18
|
+
// Re-export for backward compat
|
|
19
|
+
export { runSentinelCheck as postToolCheck } from "./forgeSentinel-lib.mjs";
|
|
178
20
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
console.log(formatCheck(v));
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
console.log(`\n ${BOLD}Resumen:${RESET} ${result.summary}`);
|
|
185
|
-
if (result.hasCritical) {
|
|
186
|
-
console.log(` ${RED}⚠ Se requiere acción correctiva antes de continuar${RESET}`);
|
|
187
|
-
}
|
|
188
|
-
console.log();
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async function main() {
|
|
21
|
+
if (process.argv[1]?.endsWith("posttool.mjs")) {
|
|
22
|
+
const forgeSentinel = join(__dirname, "forgeSentinel.mjs");
|
|
192
23
|
const args = process.argv.slice(2);
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
let files;
|
|
199
|
-
if (isDiff) {
|
|
200
|
-
files = getChangedFiles();
|
|
201
|
-
} else {
|
|
202
|
-
// Files passed as arguments
|
|
203
|
-
files = args.filter(a => !a.startsWith("--"));
|
|
204
|
-
if (files.length === 0) {
|
|
205
|
-
files = getChangedFiles();
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const result = await postToolCheck(files, { strict, reminder });
|
|
210
|
-
|
|
211
|
-
if (isJson) {
|
|
212
|
-
console.log(formatJson(result));
|
|
213
|
-
} else {
|
|
214
|
-
printResult(result, { reminder });
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (strict && (result.hasCritical || result.hasErrors)) {
|
|
218
|
-
process.exit(1);
|
|
24
|
+
const { execFileSync } = await import("child_process");
|
|
25
|
+
try {
|
|
26
|
+
execFileSync(process.execPath, [forgeSentinel, ...args], { stdio: "inherit" });
|
|
27
|
+
} catch {
|
|
28
|
+
process.exit(0);
|
|
219
29
|
}
|
|
220
30
|
}
|
|
221
|
-
|
|
222
|
-
if (process.argv[1]?.endsWith("posttool.mjs")) {
|
|
223
|
-
main().catch(console.error);
|
|
224
|
-
}
|