@rse/ase 0.0.13 → 0.0.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/dst/ase-diagram.js +130 -0
- package/dst/ase-hook.js +37 -0
- package/dst/ase.js +2 -0
- package/package.json +6 -5
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/*
|
|
2
|
+
** Agentic Software Engineering (ASE)
|
|
3
|
+
** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>, Matthias Brusdeylins <matthias@brusdeylins.info>
|
|
4
|
+
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
|
+
*/
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import tty from "node:tty";
|
|
8
|
+
import { renderMermaidASCII } from "beautiful-mermaid";
|
|
9
|
+
/* detect terminal color capability via /dev/tty */
|
|
10
|
+
/* (stdout is piped to capture diagram output, so query the controlling terminal directly) */
|
|
11
|
+
const detectColorMode = () => {
|
|
12
|
+
let fd = -1;
|
|
13
|
+
try {
|
|
14
|
+
fd = fs.openSync("/dev/tty", "r+");
|
|
15
|
+
const stream = new tty.WriteStream(fd);
|
|
16
|
+
const depth = stream.getColorDepth();
|
|
17
|
+
stream.destroy();
|
|
18
|
+
if (depth >= 8)
|
|
19
|
+
return "ansi256";
|
|
20
|
+
if (depth >= 4)
|
|
21
|
+
return "ansi16";
|
|
22
|
+
return "none";
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
if (fd >= 0) {
|
|
26
|
+
try {
|
|
27
|
+
fs.closeSync(fd);
|
|
28
|
+
}
|
|
29
|
+
catch { /* ignore */ }
|
|
30
|
+
}
|
|
31
|
+
return "none";
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
/* read stdin into a single string */
|
|
35
|
+
const readStdin = async () => {
|
|
36
|
+
const chunks = [];
|
|
37
|
+
for await (const chunk of process.stdin)
|
|
38
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
39
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
40
|
+
};
|
|
41
|
+
/* command-line handling */
|
|
42
|
+
export default class DiagramCommand {
|
|
43
|
+
log;
|
|
44
|
+
constructor(log) {
|
|
45
|
+
this.log = log;
|
|
46
|
+
}
|
|
47
|
+
/* register commands */
|
|
48
|
+
register(program) {
|
|
49
|
+
program
|
|
50
|
+
.command("diagram")
|
|
51
|
+
.description("Render Mermaid source (stdin or --input) to aligned Unicode/ASCII diagram")
|
|
52
|
+
.option("-a, --ascii", "emit plain ASCII (+-|) instead of Unicode box-drawing", false)
|
|
53
|
+
.option("-c, --color-mode <mode>", "force particular color mode to use (\"none\", \"ansi16\", or \"ansi256\")")
|
|
54
|
+
.option("-i, --input <file>", "read Mermaid source from file instead of stdin")
|
|
55
|
+
.option("-x, --pad-x <n>", "horizontal spacing between nodes", "3")
|
|
56
|
+
.option("-y, --pad-y <n>", "vertical spacing between nodes", "3")
|
|
57
|
+
.option("-b, --pad-box <n>", "inner node box spacing", "1")
|
|
58
|
+
.action(async (opts) => {
|
|
59
|
+
/* load Mermaid source */
|
|
60
|
+
let src;
|
|
61
|
+
if (opts.input !== undefined)
|
|
62
|
+
src = fs.readFileSync(opts.input, "utf8");
|
|
63
|
+
else
|
|
64
|
+
src = await readStdin();
|
|
65
|
+
if (src.trim() === "") {
|
|
66
|
+
this.log.write("error", "diagram: empty Mermaid source");
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
/* parse spacing options */
|
|
70
|
+
const paddingX = Number.parseInt(opts.padX ?? "3", 10);
|
|
71
|
+
if (!Number.isFinite(paddingX)) {
|
|
72
|
+
this.log.write("error", "diagram: --pad-x must be integer");
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
const paddingY = Number.parseInt(opts.padY ?? "3", 10);
|
|
76
|
+
if (!Number.isFinite(paddingY)) {
|
|
77
|
+
this.log.write("error", "diagram: --pad-y must be integer");
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
const boxBorderPadding = Number.parseInt(opts.padBox ?? "1", 10);
|
|
81
|
+
if (!Number.isFinite(boxBorderPadding)) {
|
|
82
|
+
this.log.write("error", "diagram: --pad-box must be integer");
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
/* determine color mode (explicit option overrides auto-detection) */
|
|
86
|
+
let colorMode;
|
|
87
|
+
if (opts.colorMode === "none" || opts.colorMode === "ansi16" || opts.colorMode === "ansi256")
|
|
88
|
+
colorMode = opts.colorMode;
|
|
89
|
+
else if (opts.colorMode === undefined)
|
|
90
|
+
colorMode = detectColorMode();
|
|
91
|
+
else {
|
|
92
|
+
this.log.write("error", "diagram: --color-mode must be \"none\", \"ansi16\", or \"ansi256\"");
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
/* render to ASCII */
|
|
96
|
+
try {
|
|
97
|
+
const out = renderMermaidASCII(src, {
|
|
98
|
+
useAscii: opts.ascii ?? false,
|
|
99
|
+
paddingX,
|
|
100
|
+
paddingY,
|
|
101
|
+
boxBorderPadding,
|
|
102
|
+
colorMode,
|
|
103
|
+
theme: colorMode !== "none" ? {
|
|
104
|
+
fg: "#000000",
|
|
105
|
+
border: "#a0a0a0",
|
|
106
|
+
junction: "#a0a0a0",
|
|
107
|
+
arrow: "#404040",
|
|
108
|
+
line: "#707070",
|
|
109
|
+
corner: "#707070"
|
|
110
|
+
} : {
|
|
111
|
+
fg: "#000000",
|
|
112
|
+
border: "#000000",
|
|
113
|
+
junction: "#000000",
|
|
114
|
+
arrow: "#000000",
|
|
115
|
+
line: "#000000",
|
|
116
|
+
corner: "#000000"
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
process.stdout.write(out);
|
|
120
|
+
if (!out.endsWith("\n"))
|
|
121
|
+
process.stdout.write("\n");
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
125
|
+
this.log.write("error", `diagram: render failed: ${message}`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
package/dst/ase-hook.js
CHANGED
|
@@ -62,6 +62,36 @@ export default class HookCommand {
|
|
|
62
62
|
}));
|
|
63
63
|
return 0;
|
|
64
64
|
}
|
|
65
|
+
/* handler for "ase hook pre-tool-use" */
|
|
66
|
+
doPreToolUse() {
|
|
67
|
+
/* read tool invocation information */
|
|
68
|
+
const stdin = fs.readFileSync(0, "utf8");
|
|
69
|
+
const input = stdin.trim() !== "" ? JSON.parse(stdin) : {};
|
|
70
|
+
/* determine whether to auto-approve the tool invocation */
|
|
71
|
+
const toolName = input.tool_name ?? "";
|
|
72
|
+
const toolInput = input.tool_input ?? {};
|
|
73
|
+
let approve = false;
|
|
74
|
+
let reason = "";
|
|
75
|
+
if (toolName === "Bash" && /^ase(\s|$)/.test(toolInput.command ?? "")) {
|
|
76
|
+
approve = true;
|
|
77
|
+
reason = "ASE CLI invocation auto-approved";
|
|
78
|
+
}
|
|
79
|
+
else if (toolName === "Skill" && /^(?:ase:)?ase-.+/.test(toolInput.skill ?? "")) {
|
|
80
|
+
approve = true;
|
|
81
|
+
reason = "ASE skill invocation auto-approved";
|
|
82
|
+
}
|
|
83
|
+
/* emit permission decision (or stay silent to defer to default flow) */
|
|
84
|
+
if (approve) {
|
|
85
|
+
process.stdout.write(JSON.stringify({
|
|
86
|
+
"hookSpecificOutput": {
|
|
87
|
+
"hookEventName": "PreToolUse",
|
|
88
|
+
"permissionDecision": "allow",
|
|
89
|
+
"permissionDecisionReason": reason
|
|
90
|
+
}
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
65
95
|
/* register commands */
|
|
66
96
|
register(program) {
|
|
67
97
|
/* register CLI top-level command "ase hook" */
|
|
@@ -79,5 +109,12 @@ export default class HookCommand {
|
|
|
79
109
|
.action(() => {
|
|
80
110
|
process.exit(this.doSessionStart());
|
|
81
111
|
});
|
|
112
|
+
/* register CLI sub-command "ase hook pre-tool-use" */
|
|
113
|
+
hookCmd
|
|
114
|
+
.command("pre-tool-use")
|
|
115
|
+
.description("handle Claude Code PreToolUse hook event")
|
|
116
|
+
.action(() => {
|
|
117
|
+
process.exit(this.doPreToolUse());
|
|
118
|
+
});
|
|
82
119
|
}
|
|
83
120
|
}
|
package/dst/ase.js
CHANGED
|
@@ -9,6 +9,7 @@ import Log from "./ase-log.js";
|
|
|
9
9
|
import ConfigCommand from "./ase-config.js";
|
|
10
10
|
import ServiceCommand from "./ase-service.js";
|
|
11
11
|
import HookCommand from "./ase-hook.js";
|
|
12
|
+
import DiagramCommand from "./ase-diagram.js";
|
|
12
13
|
import pkg from "../package.json" with { type: "json" };
|
|
13
14
|
/* globally initialize logger */
|
|
14
15
|
const log = new Log("ase", "warning", "-");
|
|
@@ -40,6 +41,7 @@ const main = async () => {
|
|
|
40
41
|
new ConfigCommand(log).register(program);
|
|
41
42
|
new ServiceCommand(log).register(program);
|
|
42
43
|
new HookCommand(log).register(program);
|
|
44
|
+
new DiagramCommand(log).register(program);
|
|
43
45
|
/* parse program arguments */
|
|
44
46
|
await program.parseAsync(process.argv);
|
|
45
47
|
/* gracefully terminate */
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"homepage": "http://github.com/rse/ase",
|
|
7
7
|
"repository": { "url": "git+https://github.com/rse/ase.git", "type": "git" },
|
|
8
8
|
"bugs": { "url": "http://github.com/rse/ase/issues" },
|
|
9
|
-
"version": "0.0.
|
|
9
|
+
"version": "0.0.14",
|
|
10
10
|
"license": "GPL-3.0-only",
|
|
11
11
|
"author": {
|
|
12
12
|
"name": "Dr. Ralf S. Engelschall",
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"eslint": "9.39.4",
|
|
20
20
|
"@eslint/js": "9.39.4",
|
|
21
|
-
"@typescript-eslint/parser": "8.
|
|
22
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
21
|
+
"@typescript-eslint/parser": "8.59.1",
|
|
22
|
+
"@typescript-eslint/eslint-plugin": "8.59.1",
|
|
23
23
|
"eslint-plugin-n": "17.24.0",
|
|
24
|
-
"eslint-plugin-promise": "7.
|
|
24
|
+
"eslint-plugin-promise": "7.3.0",
|
|
25
25
|
"eslint-plugin-import": "2.32.0",
|
|
26
26
|
"neostandard": "0.13.0",
|
|
27
27
|
"globals": "17.5.0",
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
"execa": "9.6.1",
|
|
42
42
|
"mkdirp": "3.0.1",
|
|
43
43
|
"@hapi/hapi": "21.4.8",
|
|
44
|
-
"axios": "1.15.
|
|
44
|
+
"axios": "1.15.2",
|
|
45
|
+
"beautiful-mermaid": "1.1.3",
|
|
45
46
|
"cli-table3": "0.6.5",
|
|
46
47
|
"chalk": "5.6.2",
|
|
47
48
|
"pretty-ms": "9.3.0",
|