@rse/ase 0.0.4 → 0.0.6

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-config.js CHANGED
@@ -3,33 +3,58 @@
3
3
  ** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
4
  ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
5
  */
6
+ import os from "node:os";
7
+ import path from "node:path";
8
+ import fs from "node:fs";
9
+ import { parseDocument, isMap, isScalar } from "yaml";
6
10
  const configCommand = {
7
- command: "config [assignment]",
11
+ command: "config [query]",
8
12
  describe: "Manage ASE configuration",
9
13
  builder: (yargs) => {
10
14
  return yargs
11
- .positional("assignment", {
15
+ .positional("query", {
12
16
  type: "string",
13
- describe: "Configuration assignment (key=value) or key to query"
17
+ describe: "Configuration query (none, <key>, or <key>=<value>)"
14
18
  });
15
19
  },
16
20
  handler: (argv) => {
17
21
  if (argv.debug)
18
22
  console.log("DEBUG: config command", argv);
19
- const assignment = argv.assignment;
20
- if (!assignment) {
21
- console.log("Listing all configuration...");
22
- /* TODO: implement configuration listing logic */
23
+ const query = argv.query;
24
+ const filename = path.join(os.homedir(), ".ase.yaml");
25
+ const text = fs.existsSync(filename) ? fs.readFileSync(filename, "utf8") : "";
26
+ const doc = parseDocument(text);
27
+ if (!query) {
28
+ /* list all values as flat dotted keys */
29
+ const list = (node, prefix) => {
30
+ if (isMap(node))
31
+ for (const item of node.items) {
32
+ const key = prefix ? `${prefix}.${item.key}` : String(item.key);
33
+ if (isMap(item.value))
34
+ list(item.value, key);
35
+ else
36
+ console.log(`${key} = ${isScalar(item.value) ? item.value.value : item.value}`);
37
+ }
38
+ };
39
+ list(doc.contents, "");
23
40
  }
24
- else if (assignment.includes("=")) {
25
- const [key, ...valueParts] = assignment.split("=");
41
+ else if (query.includes("=")) {
42
+ const [key, ...valueParts] = query.split("=");
26
43
  const value = valueParts.join("=");
27
44
  console.log(`Setting configuration: ${key} = ${value}`);
28
- /* TODO: implement configuration storage logic */
45
+ const segments = key.split(".");
46
+ for (let i = 1; i < segments.length; i++) {
47
+ const prefix = segments.slice(0, i);
48
+ const node = doc.getIn(prefix, true);
49
+ if (!isMap(node))
50
+ doc.setIn(prefix, doc.createNode({}));
51
+ }
52
+ doc.setIn(segments, value);
53
+ fs.writeFileSync(filename, doc.toString(), "utf8");
29
54
  }
30
55
  else {
31
- console.log(`Getting configuration: ${assignment}`);
32
- /* TODO: implement configuration retrieval logic */
56
+ const value = doc.getIn(query.split("."));
57
+ console.log(value);
33
58
  }
34
59
  }
35
60
  };
@@ -0,0 +1,78 @@
1
+ /*
2
+ ** Agentic Software Engineering (ASE)
3
+ ** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
+ ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
+ */
6
+ import path from "node:path";
7
+ import fs from "node:fs";
8
+ import { execa } from "execa";
9
+ import { mkdirp } from "mkdirp";
10
+ const configCommand = {
11
+ command: "meta-plan <subcommand>",
12
+ describe: "Manage plans",
13
+ builder: (yargs) => {
14
+ return yargs
15
+ .command({
16
+ command: "init",
17
+ describe: "ensure plan directory exists",
18
+ handler: async () => {
19
+ const { stdout: root } = await execa("git", ["rev-parse", "--show-toplevel"]);
20
+ const planDir = `${root}/.plan/`;
21
+ if (!fs.existsSync(planDir))
22
+ await mkdirp(planDir);
23
+ }
24
+ })
25
+ .command({
26
+ command: "load <id>",
27
+ describe: "load a plan",
28
+ builder: (yargs) => yargs.positional("id", {
29
+ type: "string",
30
+ describe: "plan identifier"
31
+ }),
32
+ handler: async (argv) => {
33
+ const { stdout: root } = await execa("git", ["rev-parse", "--show-toplevel"]);
34
+ const planDir = `${root}/.plan/`;
35
+ const planFile = path.join(planDir, `${argv.id}.md`);
36
+ const text = fs.existsSync(planFile) ? fs.readFileSync(planFile, "utf8") : "";
37
+ process.stdout.write(text);
38
+ }
39
+ })
40
+ .command({
41
+ command: "save <id>",
42
+ describe: "save a plan",
43
+ builder: (yargs) => yargs.positional("id", {
44
+ type: "string",
45
+ describe: "plan identifier"
46
+ }),
47
+ handler: async (argv) => {
48
+ const { stdout: root } = await execa("git", ["rev-parse", "--show-toplevel"]);
49
+ const planDir = `${root}/.plan/`;
50
+ if (!fs.existsSync(planDir))
51
+ await mkdirp(planDir);
52
+ const planFile = path.join(planDir, `${argv.id}.md`);
53
+ const text = fs.readFileSync(0, "utf8");
54
+ fs.writeFileSync(planFile, text);
55
+ }
56
+ })
57
+ .command({
58
+ command: "edit <id>",
59
+ describe: "edit a plan interactively with $EDITOR",
60
+ builder: (yargs) => yargs.positional("id", {
61
+ type: "string",
62
+ describe: "plan identifier"
63
+ }),
64
+ handler: async (argv) => {
65
+ const { stdout: root } = await execa("git", ["rev-parse", "--show-toplevel"]);
66
+ const planDir = `${root}/.plan/`;
67
+ const planFile = path.join(planDir, `${argv.id}.md`);
68
+ if (!fs.existsSync(planDir))
69
+ await mkdirp(planDir);
70
+ const editor = process.env.EDITOR ?? "vi";
71
+ await execa(editor, [planFile], { stdio: "inherit" });
72
+ }
73
+ })
74
+ .demandCommand(1, "You need to specify a sub-command");
75
+ },
76
+ handler: () => { }
77
+ };
78
+ export default configCommand;
@@ -0,0 +1,82 @@
1
+ /*
2
+ ** Agentic Software Engineering (ASE)
3
+ ** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
+ ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
+ */
6
+ import path from "node:path";
7
+ import fs from "node:fs";
8
+ import { execa } from "execa";
9
+ import { mkdirp } from "mkdirp";
10
+ const planCommand = {
11
+ command: "plan <subcommand>",
12
+ describe: "Manage plans",
13
+ builder: (yargs) => {
14
+ return yargs
15
+ .command({
16
+ command: "ensure",
17
+ describe: "ensure plan directory exists",
18
+ handler: async () => {
19
+ const { stdout: root } = await execa("git", ["rev-parse", "--show-toplevel"]);
20
+ const planDir = `${root}/.ase/plan/`;
21
+ if (!fs.existsSync(planDir))
22
+ await mkdirp(planDir);
23
+ process.stdout.write(planDir);
24
+ }
25
+ })
26
+ .command({
27
+ command: "load <id>",
28
+ describe: "load a plan",
29
+ builder: (yargs) => yargs.positional("id", {
30
+ type: "string",
31
+ describe: "plan identifier"
32
+ }),
33
+ handler: async (argv) => {
34
+ const { stdout: root } = await execa("git", ["rev-parse", "--show-toplevel"]);
35
+ const planDir = `${root}/.ase/plan/`;
36
+ const planFile = path.join(planDir, `${argv.id}.md`);
37
+ const text = fs.existsSync(planFile) ? fs.readFileSync(planFile, "utf8") : "";
38
+ process.stdout.write(text);
39
+ }
40
+ })
41
+ .command({
42
+ command: "save <id>",
43
+ describe: "save a plan",
44
+ builder: (yargs) => yargs.positional("id", {
45
+ type: "string",
46
+ describe: "plan identifier"
47
+ }),
48
+ handler: async (argv) => {
49
+ const { stdout: root } = await execa("git", ["rev-parse", "--show-toplevel"]);
50
+ const planDir = `${root}/.ase/plan/`;
51
+ if (!fs.existsSync(planDir))
52
+ await mkdirp(planDir);
53
+ const planFile = path.join(planDir, `${argv.id}.md`);
54
+ const chunks = [];
55
+ for await (const chunk of process.stdin)
56
+ chunks.push(chunk);
57
+ const text = Buffer.concat(chunks).toString("utf8");
58
+ fs.writeFileSync(planFile, text);
59
+ }
60
+ })
61
+ .command({
62
+ command: "edit <id>",
63
+ describe: "edit a plan interactively with $EDITOR",
64
+ builder: (yargs) => yargs.positional("id", {
65
+ type: "string",
66
+ describe: "plan identifier"
67
+ }),
68
+ handler: async (argv) => {
69
+ const { stdout: root } = await execa("git", ["rev-parse", "--show-toplevel"]);
70
+ const planDir = `${root}/.ase/plan/`;
71
+ const planFile = path.join(planDir, `${argv.id}.md`);
72
+ if (!fs.existsSync(planDir))
73
+ await mkdirp(planDir);
74
+ const editor = process.env.EDITOR ?? "vi";
75
+ await execa(editor, [planFile], { stdio: "inherit" });
76
+ }
77
+ })
78
+ .demandCommand(1, "You need to specify a sub-command");
79
+ },
80
+ handler: () => { }
81
+ };
82
+ export default planCommand;
@@ -0,0 +1,309 @@
1
+ /*
2
+ ** Agentic Software Engineering (ASE)
3
+ ** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
+ ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
+ */
6
+ import path from "node:path";
7
+ import fs from "node:fs";
8
+ import net from "node:net";
9
+ import { spawn } from "node:child_process";
10
+ import { parseDocument } from "yaml";
11
+ import Hapi from "@hapi/hapi";
12
+ import axios from "axios";
13
+ const SERVE_ENV = "ASE_SERVICE_SERVE";
14
+ const HOST = "127.0.0.1";
15
+ const IDLE_MS = 30 * 60 * 1000;
16
+ const TICK_MS = 60 * 1000;
17
+ const PORT_MIN = 42000;
18
+ const PORT_MAX = 44000;
19
+ const PORT_TRIES = 20;
20
+ /* upward-walk on filesystem for a file path relative to a start directory */
21
+ const findUpward = (start, rel) => {
22
+ let dir = start;
23
+ for (;;) {
24
+ const candidate = path.join(dir, rel);
25
+ if (fs.existsSync(candidate))
26
+ return candidate;
27
+ const parent = path.dirname(dir);
28
+ if (parent === dir)
29
+ return null;
30
+ dir = parent;
31
+ }
32
+ };
33
+ /* load optional ".ase/config.yaml" and ".ase/service.yaml" files */
34
+ const loadContext = () => {
35
+ /* find files */
36
+ const cfgPath = findUpward(process.cwd(), ".ase/config.yaml");
37
+ const svcPath = findUpward(process.cwd(), ".ase/service.yaml");
38
+ /* determine project id */
39
+ let projectId;
40
+ if (cfgPath !== null) {
41
+ const doc = parseDocument(fs.readFileSync(cfgPath, "utf8"));
42
+ projectId = doc.get("project-id");
43
+ }
44
+ if (projectId === undefined || projectId === null)
45
+ projectId = path.basename(process.cwd());
46
+ if (typeof projectId !== "string" || projectId.length === 0)
47
+ throw new Error(`invalid "project-id" in ${cfgPath ?? "<cwd basename>"}`);
48
+ /* determine service port */
49
+ let port = null;
50
+ if (svcPath !== null) {
51
+ const doc = parseDocument(fs.readFileSync(svcPath, "utf8"));
52
+ const raw = doc.get("port");
53
+ if (raw !== undefined && raw !== null) {
54
+ if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1024 || raw > 65535)
55
+ throw new Error(`invalid "port" in ${svcPath} (expected integer 1024..65535)`);
56
+ port = raw;
57
+ }
58
+ }
59
+ /* determine path to ".ase" directory */
60
+ const aseDir = cfgPath !== null ? path.dirname(cfgPath) :
61
+ svcPath !== null ? path.dirname(svcPath) :
62
+ path.join(process.cwd(), ".ase");
63
+ /* determine path to final ".ase/service.yaml" file */
64
+ const finalSvc = svcPath !== null ? svcPath : path.join(aseDir, "service.yaml");
65
+ /* return context information */
66
+ return {
67
+ projectId,
68
+ port,
69
+ svcPath: finalSvc,
70
+ aseDir
71
+ };
72
+ };
73
+ /* try binding a single candidate port to verify availability */
74
+ const tryBind = (port) => {
75
+ return new Promise((resolve) => {
76
+ const s = net.createServer();
77
+ s.once("error", () => {
78
+ resolve(false);
79
+ });
80
+ s.once("listening", () => {
81
+ s.close(() => resolve(true));
82
+ });
83
+ s.listen(port, HOST);
84
+ });
85
+ };
86
+ /* allocate a fresh random port in PORT_MIN..PORT_MAX */
87
+ const allocatePort = async () => {
88
+ for (let i = 0; i < PORT_TRIES; i++) {
89
+ const p = PORT_MIN + Math.floor(Math.random() * (PORT_MAX - PORT_MIN + 1));
90
+ if (await tryBind(p))
91
+ return p;
92
+ }
93
+ throw new Error(`failed to allocate a port in ${PORT_MIN}..${PORT_MAX} after ${PORT_TRIES} attempts`);
94
+ };
95
+ /* persist an allocated port into ".ase/service.yaml" */
96
+ const persistPort = (svcPath, port) => {
97
+ fs.mkdirSync(path.dirname(svcPath), { recursive: true });
98
+ const text = fs.existsSync(svcPath) ? fs.readFileSync(svcPath, "utf8") : "";
99
+ const doc = parseDocument(text);
100
+ doc.set("port", port);
101
+ fs.writeFileSync(svcPath, doc.toString(), "utf8");
102
+ };
103
+ /* distinguish ECONNREFUSED from other Axios transport errors */
104
+ const isConnRefused = (err) => {
105
+ const e = err;
106
+ return e?.code === "ECONNREFUSED" || e?.cause?.code === "ECONNREFUSED";
107
+ };
108
+ /* probe the service */
109
+ const probe = async (port) => {
110
+ try {
111
+ const r = await axios.request({
112
+ method: "OPTIONS",
113
+ url: `http://${HOST}:${port}/`,
114
+ timeout: 2000,
115
+ validateStatus: () => true
116
+ });
117
+ return r.status;
118
+ }
119
+ catch (err) {
120
+ if (isConnRefused(err))
121
+ return null;
122
+ throw err;
123
+ }
124
+ };
125
+ /* service-side: bind HAPI server until "/stop" command is received or idle timeout happens */
126
+ const runService = async (ctx) => {
127
+ /* establish HAPI HTTP/REST service */
128
+ const server = Hapi.server({ host: HOST, port: ctx.port });
129
+ /* track last activity */
130
+ let lastActivity = Date.now();
131
+ server.ext("onRequest", (_request, h) => {
132
+ lastActivity = Date.now();
133
+ return h.continue;
134
+ });
135
+ /* listen to HTTP/REST endpoints */
136
+ server.route({
137
+ method: "OPTIONS",
138
+ path: "/",
139
+ handler: (_request, h) => {
140
+ return h.response().code(204);
141
+ }
142
+ });
143
+ server.route({
144
+ method: "GET",
145
+ path: "/stop",
146
+ handler: (_request, h) => {
147
+ setImmediate(async () => {
148
+ await server.stop({ timeout: 1000 });
149
+ process.exit(0);
150
+ });
151
+ return h.response({ ok: true }).code(200);
152
+ }
153
+ });
154
+ server.route({
155
+ method: "POST",
156
+ path: "/command",
157
+ options: { payload: { parse: true, allow: "application/json" } },
158
+ handler: (request, h) => {
159
+ const payload = request.payload;
160
+ if (!payload || typeof payload.command !== "string")
161
+ return h.response({ error: "missing or invalid 'command' field" }).code(400);
162
+ if (payload.command === "foo") {
163
+ return h.response({
164
+ ok: true,
165
+ projectId: ctx.projectId,
166
+ command: "Hello World" // FIXME
167
+ }).code(200);
168
+ }
169
+ else
170
+ return h.response({ error: "invalid 'command' field" }).code(400);
171
+ }
172
+ });
173
+ /* start service */
174
+ try {
175
+ await server.start();
176
+ }
177
+ catch (err) {
178
+ const e = err;
179
+ if (e.code === "EADDRINUSE") {
180
+ /* race-loser re-probe: another "ase service start" won the race */
181
+ const status = await probe(ctx.port).catch(() => null);
182
+ if (status !== null && status >= 200 && status < 300)
183
+ process.exit(0);
184
+ process.stderr.write(`ase: service: port ${ctx.port} in use, but not responding!\n`);
185
+ process.exit(1);
186
+ }
187
+ process.stderr.write(`ase: service: ${e.message}\n`);
188
+ process.exit(1);
189
+ }
190
+ /* stop service after idle timeout */
191
+ setInterval(() => {
192
+ if (Date.now() - lastActivity > IDLE_MS) {
193
+ server.stop({ timeout: 1000 }).then(() => {
194
+ process.exit(0);
195
+ });
196
+ }
197
+ }, TICK_MS).unref();
198
+ };
199
+ /* spawn the current executable detached as a background service */
200
+ const spawnDetached = (aseDir) => {
201
+ fs.mkdirSync(aseDir, { recursive: true });
202
+ const logFile = path.join(aseDir, "service.log");
203
+ const log = fs.openSync(logFile, "a");
204
+ const child = spawn(process.execPath, [process.argv[1], "service", "start"], {
205
+ detached: true,
206
+ env: { ...process.env, [SERVE_ENV]: "1" },
207
+ stdio: ["ignore", log, log]
208
+ });
209
+ child.unref();
210
+ };
211
+ /* start flow: ensure port, probe, optionally detach */
212
+ const doStart = async () => {
213
+ const ctx = loadContext();
214
+ let port = ctx.port;
215
+ if (port === null) {
216
+ port = await allocatePort();
217
+ persistPort(ctx.svcPath, port);
218
+ }
219
+ if (process.env[SERVE_ENV] === "1") {
220
+ await runService({ ...ctx, port });
221
+ return await new Promise(() => { });
222
+ }
223
+ const status = await probe(port);
224
+ if (status !== null && status >= 200 && status < 300)
225
+ return 0;
226
+ spawnDetached(ctx.aseDir);
227
+ for (let i = 0; i < 50; i++) {
228
+ await new Promise((resolve) => setTimeout(resolve, 100));
229
+ const s = await probe(port);
230
+ if (s !== null && s >= 200 && s < 300)
231
+ return 0;
232
+ }
233
+ throw new Error("service failed to start within timeout");
234
+ };
235
+ /* stop flow: no-op if no port configured or connection refused */
236
+ const doStop = async () => {
237
+ const ctx = loadContext();
238
+ if (ctx.port === null)
239
+ return 0;
240
+ try {
241
+ const r = await axios.request({
242
+ method: "GET",
243
+ url: `http://${HOST}:${ctx.port}/stop`,
244
+ timeout: 5000,
245
+ validateStatus: () => true
246
+ });
247
+ return r.status >= 200 && r.status < 300 ? 0 : 1;
248
+ }
249
+ catch (err) {
250
+ if (isConnRefused(err))
251
+ return 0;
252
+ throw err;
253
+ }
254
+ };
255
+ /* passthrough flow: POST /command with the arbitrary cmd token */
256
+ const doPassthrough = async (cmd) => {
257
+ const ctx = loadContext();
258
+ if (ctx.port === null)
259
+ throw new Error("service not running (no port configured)");
260
+ try {
261
+ const r = await axios.request({
262
+ method: "POST",
263
+ url: `http://${HOST}:${ctx.port}/command`,
264
+ headers: { "Content-Type": "application/json" },
265
+ data: { command: cmd },
266
+ timeout: 0,
267
+ validateStatus: () => true,
268
+ responseType: "text",
269
+ transformResponse: [(x) => x]
270
+ });
271
+ const body = typeof r.data === "string" ? r.data : JSON.stringify(r.data);
272
+ process.stdout.write(body);
273
+ if (!body.endsWith("\n"))
274
+ process.stdout.write("\n");
275
+ return r.status >= 200 && r.status < 300 ? 0 : 1;
276
+ }
277
+ catch (err) {
278
+ if (isConnRefused(err))
279
+ throw new Error("service not running (connection refused)");
280
+ throw err;
281
+ }
282
+ };
283
+ /* command-line handling */
284
+ const serviceCommand = {
285
+ command: "service",
286
+ describe: "Manage per-project background HTTP service",
287
+ builder: (yargs) => {
288
+ return yargs
289
+ .command("start", "Start the background service", {}, async () => {
290
+ process.exit(await doStart());
291
+ })
292
+ .command("stop", "Stop the background service", {}, async () => {
293
+ process.exit(await doStop());
294
+ })
295
+ .command("$0 <cmd>", "Send an arbitrary command to the service", (y) => {
296
+ return y.positional("cmd", {
297
+ type: "string",
298
+ describe: "Command token to dispatch"
299
+ });
300
+ }, async (argv) => {
301
+ process.exit(await doPassthrough(String(argv.cmd)));
302
+ })
303
+ .demandCommand(1, "You need to specify a service subcommand");
304
+ },
305
+ handler: () => {
306
+ /* dispatched by nested subcommands */
307
+ }
308
+ };
309
+ export default serviceCommand;
@@ -0,0 +1,19 @@
1
+ /*
2
+ ** Agentic Software Engineering (ASE)
3
+ ** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
+ ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
5
+ */
6
+ const setupCommand = {
7
+ command: "setup",
8
+ describe: "Setup ASE",
9
+ builder: (yargs) => {
10
+ return yargs;
11
+ },
12
+ handler: (argv) => {
13
+ if (argv.debug)
14
+ console.log("DEBUG: setup command");
15
+ console.log("Setup ASE...");
16
+ /* TODO: implement setup logic */
17
+ }
18
+ };
19
+ export default setupCommand;
package/dst/ase.js CHANGED
@@ -6,24 +6,38 @@
6
6
  */
7
7
  import yargs from "yargs";
8
8
  import { hideBin } from "yargs/helpers";
9
- import initCommand from "./ase-init.js";
10
9
  import configCommand from "./ase-config.js";
11
- import agentCommand from "./ase-agent.js";
10
+ import setupCommand from "./ase-setup.js";
11
+ import serviceCommand from "./ase-service.js";
12
12
  /* parse CLI arguments */
13
- yargs(hideBin(process.argv))
14
- .scriptName("ase")
15
- .usage("Usage: $0 <command> [options]")
16
- .option("debug", {
17
- alias: "d",
18
- type: "boolean",
19
- describe: "Enable debug output",
20
- default: false
21
- })
22
- .command(initCommand)
23
- .command(configCommand)
24
- .command(agentCommand)
25
- .demandCommand(1, "You need to specify a command")
26
- .help()
27
- .version()
28
- .strict()
29
- .parse();
13
+ try {
14
+ await yargs(hideBin(process.argv))
15
+ .scriptName("ase")
16
+ .usage("Usage: $0 <command> [options]")
17
+ .option("debug", {
18
+ alias: "d",
19
+ type: "boolean",
20
+ describe: "Enable debug output",
21
+ default: false
22
+ })
23
+ .command(configCommand)
24
+ .command(setupCommand)
25
+ .command(serviceCommand)
26
+ .demandCommand(1, "You need to specify a command")
27
+ .fail((msg, err, yargs) => {
28
+ if (err)
29
+ throw err;
30
+ yargs.showHelp();
31
+ process.stderr.write(`\nase: ${msg}\n`);
32
+ process.exit(1);
33
+ })
34
+ .help()
35
+ .version()
36
+ .strict()
37
+ .parseAsync();
38
+ }
39
+ catch (err) {
40
+ const message = err instanceof Error ? err.message : String(err);
41
+ process.stderr.write(`ase: ERROR: ${message}\n`);
42
+ process.exit(1);
43
+ }
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.4",
9
+ "version": "0.0.6",
10
10
  "license": "GPL-3.0-only",
11
11
  "author": {
12
12
  "name": "Dr. Ralf S. Engelschall",
@@ -18,27 +18,32 @@
18
18
  "devDependencies": {
19
19
  "eslint": "9.39.4",
20
20
  "@eslint/js": "9.39.4",
21
- "@typescript-eslint/parser": "8.58.0",
22
- "@typescript-eslint/eslint-plugin": "8.58.0",
21
+ "@typescript-eslint/parser": "8.58.2",
22
+ "@typescript-eslint/eslint-plugin": "8.58.2",
23
23
  "eslint-plugin-n": "17.24.0",
24
24
  "eslint-plugin-promise": "7.2.1",
25
25
  "eslint-plugin-import": "2.32.0",
26
26
  "neostandard": "0.13.0",
27
- "globals": "17.4.0",
28
- "typescript": "6.0.2",
27
+ "globals": "17.5.0",
28
+ "typescript": "6.0.3",
29
29
 
30
- "@rse/stx": "1.1.4",
30
+ "@rse/stx": "1.1.5",
31
31
  "nodemon": "3.1.14",
32
32
  "shx": "0.4.0",
33
33
  "remark-cli": "12.0.1",
34
34
  "remark": "15.0.1",
35
35
  "remark-man": "9.0.0",
36
36
 
37
- "@types/node": "25.5.2",
37
+ "@types/node": "25.6.0",
38
38
  "@types/yargs": "17.0.35"
39
39
  },
40
40
  "dependencies": {
41
- "yargs": "18.0.0"
41
+ "yargs": "18.0.0",
42
+ "yaml": "2.8.3",
43
+ "execa": "9.6.1",
44
+ "mkdirp": "3.0.1",
45
+ "@hapi/hapi": "21.4.8",
46
+ "axios": "1.15.0"
42
47
  },
43
48
  "engines": {
44
49
  "npm": ">=10.0.0",