@rse/ase 0.0.6 → 0.0.7
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 +2 -2
- package/dst/ase-agent-base.js +16 -25
- package/dst/ase-agent-biz.js +18 -27
- package/dst/ase-agent-dev.js +18 -27
- package/dst/ase-agent-ops.js +18 -27
- package/dst/ase-agent-prd.js +18 -27
- package/dst/ase-agent-prj.js +18 -27
- package/dst/ase-agent.js +19 -29
- package/dst/ase-config.js +175 -48
- package/dst/ase-service.js +77 -84
- package/dst/ase-setup.js +9 -10
- package/dst/ase.1 +40 -4
- package/dst/ase.js +25 -29
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
$ npx @rse/ase init
|
|
3
|
-
$ npx @rse/ase config agent.dev.llm.type
|
|
4
|
-
$ npx @rse/ase config agent.dev.llm.key
|
|
3
|
+
$ npx @rse/ase config agent.dev.llm.type anthropic-claude-sonnet-4.5
|
|
4
|
+
$ npx @rse/ase config agent.dev.llm.key <foo>
|
|
5
5
|
$ npx @rse/ase agent dev start
|
|
6
6
|
|
|
7
7
|
Agents
|
package/dst/ase-agent-base.js
CHANGED
|
@@ -7,37 +7,28 @@
|
|
|
7
7
|
export const createAgentCommandModule = (category, description, subCommands) => {
|
|
8
8
|
/* create sub-command handler */
|
|
9
9
|
const createSubCommandHandler = (subCommand) => {
|
|
10
|
-
return (
|
|
11
|
-
|
|
10
|
+
return (_opts, cmd) => {
|
|
11
|
+
const opts = cmd.optsWithGlobals();
|
|
12
|
+
if (opts.debug)
|
|
12
13
|
console.log(`DEBUG: agent ${category} ${subCommand} command`);
|
|
13
|
-
if (
|
|
14
|
+
if (opts.verbose)
|
|
14
15
|
console.log(`VERBOSE: executing agent ${category} ${subCommand}...`);
|
|
15
16
|
console.log(`Executing agent ${category} ${subCommand}...`);
|
|
16
17
|
/* TODO: implement agent ${category} sub-command logic */
|
|
17
18
|
};
|
|
18
19
|
};
|
|
19
|
-
return {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
.demandCommand(1, `You need to specify a ${category} subcommand`);
|
|
31
|
-
/* register all sub-commands */
|
|
32
|
-
for (const subCmd of subCommands) {
|
|
33
|
-
builder = builder.command(subCmd, `Execute agent ${category} ${subCmd} operation`, () => { }, createSubCommandHandler(subCmd));
|
|
34
|
-
}
|
|
35
|
-
return builder;
|
|
36
|
-
},
|
|
37
|
-
handler: (argv) => {
|
|
38
|
-
/* this handler is not called when sub-commands are used */
|
|
39
|
-
if (argv.debug)
|
|
40
|
-
console.log(`DEBUG: agent ${category} command (no subcommand)`);
|
|
20
|
+
return (parent) => {
|
|
21
|
+
const agent = parent
|
|
22
|
+
.command(`${category}`)
|
|
23
|
+
.description(`Execute ${description} agent operations`)
|
|
24
|
+
.option("-v, --verbose", "Enable verbose output", false);
|
|
25
|
+
/* register all sub-commands */
|
|
26
|
+
for (const subCmd of subCommands) {
|
|
27
|
+
agent
|
|
28
|
+
.command(subCmd)
|
|
29
|
+
.description(`Execute agent ${category} ${subCmd} operation`)
|
|
30
|
+
.action(createSubCommandHandler(subCmd));
|
|
41
31
|
}
|
|
32
|
+
return agent;
|
|
42
33
|
};
|
|
43
34
|
};
|
package/dst/ase-agent-biz.js
CHANGED
|
@@ -12,38 +12,29 @@ const bizSubCommands = [
|
|
|
12
12
|
];
|
|
13
13
|
/* create sub-command handler */
|
|
14
14
|
const createSubCommandHandler = (subCommand) => {
|
|
15
|
-
return (
|
|
16
|
-
|
|
15
|
+
return (_opts, cmd) => {
|
|
16
|
+
const opts = cmd.optsWithGlobals();
|
|
17
|
+
if (opts.debug)
|
|
17
18
|
console.log(`DEBUG: agent biz ${subCommand} command`);
|
|
18
|
-
if (
|
|
19
|
+
if (opts.verbose)
|
|
19
20
|
console.log(`VERBOSE: executing agent biz ${subCommand}...`);
|
|
20
21
|
console.log(`Executing agent biz ${subCommand}...`);
|
|
21
22
|
/* TODO: implement agent biz sub-command logic */
|
|
22
23
|
};
|
|
23
24
|
};
|
|
24
|
-
/*
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.demandCommand(1, "You need to specify a biz subcommand");
|
|
37
|
-
/* register all sub-commands */
|
|
38
|
-
for (const subCmd of bizSubCommands) {
|
|
39
|
-
builder = builder.command(subCmd, `Execute agent biz ${subCmd} operation`, () => { }, createSubCommandHandler(subCmd));
|
|
40
|
-
}
|
|
41
|
-
return builder;
|
|
42
|
-
},
|
|
43
|
-
handler: (argv) => {
|
|
44
|
-
/* this handler is not called when sub-commands are used */
|
|
45
|
-
if (argv.debug)
|
|
46
|
-
console.log("DEBUG: agent biz command (no subcommand)");
|
|
25
|
+
/* register biz command on the given parent */
|
|
26
|
+
const registerBizCommand = (parent) => {
|
|
27
|
+
const biz = parent
|
|
28
|
+
.command("biz")
|
|
29
|
+
.description("Execute business agent operations")
|
|
30
|
+
.option("-v, --verbose", "Enable verbose output", false);
|
|
31
|
+
/* register all sub-commands */
|
|
32
|
+
for (const subCmd of bizSubCommands) {
|
|
33
|
+
biz
|
|
34
|
+
.command(subCmd)
|
|
35
|
+
.description(`Execute agent biz ${subCmd} operation`)
|
|
36
|
+
.action(createSubCommandHandler(subCmd));
|
|
47
37
|
}
|
|
38
|
+
return biz;
|
|
48
39
|
};
|
|
49
|
-
export default
|
|
40
|
+
export default registerBizCommand;
|
package/dst/ase-agent-dev.js
CHANGED
|
@@ -12,38 +12,29 @@ const devSubCommands = [
|
|
|
12
12
|
];
|
|
13
13
|
/* create sub-command handler */
|
|
14
14
|
const createSubCommandHandler = (subCommand) => {
|
|
15
|
-
return (
|
|
16
|
-
|
|
15
|
+
return (_opts, cmd) => {
|
|
16
|
+
const opts = cmd.optsWithGlobals();
|
|
17
|
+
if (opts.debug)
|
|
17
18
|
console.log(`DEBUG: agent dev ${subCommand} command`);
|
|
18
|
-
if (
|
|
19
|
+
if (opts.verbose)
|
|
19
20
|
console.log(`VERBOSE: executing agent dev ${subCommand}...`);
|
|
20
21
|
console.log(`Executing agent dev ${subCommand}...`);
|
|
21
22
|
/* TODO: implement agent dev sub-command logic */
|
|
22
23
|
};
|
|
23
24
|
};
|
|
24
|
-
/*
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.demandCommand(1, "You need to specify a dev subcommand");
|
|
37
|
-
/* register all sub-commands */
|
|
38
|
-
for (const subCmd of devSubCommands) {
|
|
39
|
-
builder = builder.command(subCmd, `Execute agent dev ${subCmd} operation`, () => { }, createSubCommandHandler(subCmd));
|
|
40
|
-
}
|
|
41
|
-
return builder;
|
|
42
|
-
},
|
|
43
|
-
handler: (argv) => {
|
|
44
|
-
/* this handler is not called when sub-commands are used */
|
|
45
|
-
if (argv.debug)
|
|
46
|
-
console.log("DEBUG: agent dev command (no subcommand)");
|
|
25
|
+
/* register dev command on the given parent */
|
|
26
|
+
const registerDevCommand = (parent) => {
|
|
27
|
+
const dev = parent
|
|
28
|
+
.command("dev")
|
|
29
|
+
.description("Execute development agent operations")
|
|
30
|
+
.option("-v, --verbose", "Enable verbose output", false);
|
|
31
|
+
/* register all sub-commands */
|
|
32
|
+
for (const subCmd of devSubCommands) {
|
|
33
|
+
dev
|
|
34
|
+
.command(subCmd)
|
|
35
|
+
.description(`Execute agent dev ${subCmd} operation`)
|
|
36
|
+
.action(createSubCommandHandler(subCmd));
|
|
47
37
|
}
|
|
38
|
+
return dev;
|
|
48
39
|
};
|
|
49
|
-
export default
|
|
40
|
+
export default registerDevCommand;
|
package/dst/ase-agent-ops.js
CHANGED
|
@@ -12,38 +12,29 @@ const opsSubCommands = [
|
|
|
12
12
|
];
|
|
13
13
|
/* create sub-command handler */
|
|
14
14
|
const createSubCommandHandler = (subCommand) => {
|
|
15
|
-
return (
|
|
16
|
-
|
|
15
|
+
return (_opts, cmd) => {
|
|
16
|
+
const opts = cmd.optsWithGlobals();
|
|
17
|
+
if (opts.debug)
|
|
17
18
|
console.log(`DEBUG: agent ops ${subCommand} command`);
|
|
18
|
-
if (
|
|
19
|
+
if (opts.verbose)
|
|
19
20
|
console.log(`VERBOSE: executing agent ops ${subCommand}...`);
|
|
20
21
|
console.log(`Executing agent ops ${subCommand}...`);
|
|
21
22
|
/* TODO: implement agent ops sub-command logic */
|
|
22
23
|
};
|
|
23
24
|
};
|
|
24
|
-
/*
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.demandCommand(1, "You need to specify an ops subcommand");
|
|
37
|
-
/* register all sub-commands */
|
|
38
|
-
for (const subCmd of opsSubCommands) {
|
|
39
|
-
builder = builder.command(subCmd, `Execute agent ops ${subCmd} operation`, () => { }, createSubCommandHandler(subCmd));
|
|
40
|
-
}
|
|
41
|
-
return builder;
|
|
42
|
-
},
|
|
43
|
-
handler: (argv) => {
|
|
44
|
-
/* this handler is not called when sub-commands are used */
|
|
45
|
-
if (argv.debug)
|
|
46
|
-
console.log("DEBUG: agent ops command (no subcommand)");
|
|
25
|
+
/* register ops command on the given parent */
|
|
26
|
+
const registerOpsCommand = (parent) => {
|
|
27
|
+
const ops = parent
|
|
28
|
+
.command("ops")
|
|
29
|
+
.description("Execute operations agent operations")
|
|
30
|
+
.option("-v, --verbose", "Enable verbose output", false);
|
|
31
|
+
/* register all sub-commands */
|
|
32
|
+
for (const subCmd of opsSubCommands) {
|
|
33
|
+
ops
|
|
34
|
+
.command(subCmd)
|
|
35
|
+
.description(`Execute agent ops ${subCmd} operation`)
|
|
36
|
+
.action(createSubCommandHandler(subCmd));
|
|
47
37
|
}
|
|
38
|
+
return ops;
|
|
48
39
|
};
|
|
49
|
-
export default
|
|
40
|
+
export default registerOpsCommand;
|
package/dst/ase-agent-prd.js
CHANGED
|
@@ -12,38 +12,29 @@ const prdSubCommands = [
|
|
|
12
12
|
];
|
|
13
13
|
/* create sub-command handler */
|
|
14
14
|
const createSubCommandHandler = (subCommand) => {
|
|
15
|
-
return (
|
|
16
|
-
|
|
15
|
+
return (_opts, cmd) => {
|
|
16
|
+
const opts = cmd.optsWithGlobals();
|
|
17
|
+
if (opts.debug)
|
|
17
18
|
console.log(`DEBUG: agent prd ${subCommand} command`);
|
|
18
|
-
if (
|
|
19
|
+
if (opts.verbose)
|
|
19
20
|
console.log(`VERBOSE: executing agent prd ${subCommand}...`);
|
|
20
21
|
console.log(`Executing agent prd ${subCommand}...`);
|
|
21
22
|
/* TODO: implement agent prd sub-command logic */
|
|
22
23
|
};
|
|
23
24
|
};
|
|
24
|
-
/*
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.demandCommand(1, "You need to specify a prd subcommand");
|
|
37
|
-
/* register all sub-commands */
|
|
38
|
-
for (const subCmd of prdSubCommands) {
|
|
39
|
-
builder = builder.command(subCmd, `Execute agent prd ${subCmd} operation`, () => { }, createSubCommandHandler(subCmd));
|
|
40
|
-
}
|
|
41
|
-
return builder;
|
|
42
|
-
},
|
|
43
|
-
handler: (argv) => {
|
|
44
|
-
/* this handler is not called when sub-commands are used */
|
|
45
|
-
if (argv.debug)
|
|
46
|
-
console.log("DEBUG: agent prd command (no subcommand)");
|
|
25
|
+
/* register prd command on the given parent */
|
|
26
|
+
const registerPrdCommand = (parent) => {
|
|
27
|
+
const prd = parent
|
|
28
|
+
.command("prd")
|
|
29
|
+
.description("Execute production agent operations")
|
|
30
|
+
.option("-v, --verbose", "Enable verbose output", false);
|
|
31
|
+
/* register all sub-commands */
|
|
32
|
+
for (const subCmd of prdSubCommands) {
|
|
33
|
+
prd
|
|
34
|
+
.command(subCmd)
|
|
35
|
+
.description(`Execute agent prd ${subCmd} operation`)
|
|
36
|
+
.action(createSubCommandHandler(subCmd));
|
|
47
37
|
}
|
|
38
|
+
return prd;
|
|
48
39
|
};
|
|
49
|
-
export default
|
|
40
|
+
export default registerPrdCommand;
|
package/dst/ase-agent-prj.js
CHANGED
|
@@ -12,38 +12,29 @@ const prjSubCommands = [
|
|
|
12
12
|
];
|
|
13
13
|
/* create sub-command handler */
|
|
14
14
|
const createSubCommandHandler = (subCommand) => {
|
|
15
|
-
return (
|
|
16
|
-
|
|
15
|
+
return (_opts, cmd) => {
|
|
16
|
+
const opts = cmd.optsWithGlobals();
|
|
17
|
+
if (opts.debug)
|
|
17
18
|
console.log(`DEBUG: agent prj ${subCommand} command`);
|
|
18
|
-
if (
|
|
19
|
+
if (opts.verbose)
|
|
19
20
|
console.log(`VERBOSE: executing agent prj ${subCommand}...`);
|
|
20
21
|
console.log(`Executing agent prj ${subCommand}...`);
|
|
21
22
|
/* TODO: implement agent prj sub-command logic */
|
|
22
23
|
};
|
|
23
24
|
};
|
|
24
|
-
/*
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.demandCommand(1, "You need to specify a prj subcommand");
|
|
37
|
-
/* register all sub-commands */
|
|
38
|
-
for (const subCmd of prjSubCommands) {
|
|
39
|
-
builder = builder.command(subCmd, `Execute agent prj ${subCmd} operation`, () => { }, createSubCommandHandler(subCmd));
|
|
40
|
-
}
|
|
41
|
-
return builder;
|
|
42
|
-
},
|
|
43
|
-
handler: (argv) => {
|
|
44
|
-
/* this handler is not called when sub-commands are used */
|
|
45
|
-
if (argv.debug)
|
|
46
|
-
console.log("DEBUG: agent prj command (no subcommand)");
|
|
25
|
+
/* register prj command on the given parent */
|
|
26
|
+
const registerPrjCommand = (parent) => {
|
|
27
|
+
const prj = parent
|
|
28
|
+
.command("prj")
|
|
29
|
+
.description("Execute project agent operations")
|
|
30
|
+
.option("-v, --verbose", "Enable verbose output", false);
|
|
31
|
+
/* register all sub-commands */
|
|
32
|
+
for (const subCmd of prjSubCommands) {
|
|
33
|
+
prj
|
|
34
|
+
.command(subCmd)
|
|
35
|
+
.description(`Execute agent prj ${subCmd} operation`)
|
|
36
|
+
.action(createSubCommandHandler(subCmd));
|
|
47
37
|
}
|
|
38
|
+
return prj;
|
|
48
39
|
};
|
|
49
|
-
export default
|
|
40
|
+
export default registerPrjCommand;
|
package/dst/ase-agent.js
CHANGED
|
@@ -3,33 +3,23 @@
|
|
|
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
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
.command(opsCommand)
|
|
25
|
-
.command(prdCommand)
|
|
26
|
-
.command(prjCommand)
|
|
27
|
-
.demandCommand(1, "You need to specify an agent subcommand");
|
|
28
|
-
},
|
|
29
|
-
handler: (argv) => {
|
|
30
|
-
/* this handler is not called when sub-commands are used */
|
|
31
|
-
if (argv.debug)
|
|
32
|
-
console.log("DEBUG: agent command (no subcommand)");
|
|
33
|
-
}
|
|
6
|
+
import registerBizCommand from "./ase-agent-biz.js";
|
|
7
|
+
import registerDevCommand from "./ase-agent-dev.js";
|
|
8
|
+
import registerOpsCommand from "./ase-agent-ops.js";
|
|
9
|
+
import registerPrdCommand from "./ase-agent-prd.js";
|
|
10
|
+
import registerPrjCommand from "./ase-agent-prj.js";
|
|
11
|
+
/* register agent command on the given program */
|
|
12
|
+
const registerAgentCommand = (program) => {
|
|
13
|
+
const agent = program
|
|
14
|
+
.command("agent")
|
|
15
|
+
.description("Execute agent operations")
|
|
16
|
+
.option("-v, --verbose", "Enable verbose output", false);
|
|
17
|
+
/* register all agent sub-commands */
|
|
18
|
+
registerBizCommand(agent);
|
|
19
|
+
registerDevCommand(agent);
|
|
20
|
+
registerOpsCommand(agent);
|
|
21
|
+
registerPrdCommand(agent);
|
|
22
|
+
registerPrjCommand(agent);
|
|
23
|
+
return agent;
|
|
34
24
|
};
|
|
35
|
-
export default
|
|
25
|
+
export default registerAgentCommand;
|
package/dst/ase-config.js
CHANGED
|
@@ -3,59 +3,186 @@
|
|
|
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
6
|
import path from "node:path";
|
|
8
7
|
import fs from "node:fs";
|
|
9
|
-
import { parseDocument, isMap, isScalar } from "yaml";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
8
|
+
import { Document, parseDocument, isMap, isScalar } from "yaml";
|
|
9
|
+
import { execaSync } from "execa";
|
|
10
|
+
import * as v from "valibot";
|
|
11
|
+
import Table from "cli-table3";
|
|
12
|
+
/* schema for ".ase/config.yaml" */
|
|
13
|
+
export const configSchema = v.nullish(v.strictObject({
|
|
14
|
+
project: v.optional(v.strictObject({
|
|
15
|
+
id: v.optional(v.pipe(v.string(), v.minLength(1)))
|
|
16
|
+
}))
|
|
17
|
+
}));
|
|
18
|
+
/* encapsulate read/write access to a project-local ".ase/<name>.yaml" file */
|
|
19
|
+
export class Config {
|
|
20
|
+
filename;
|
|
21
|
+
doc;
|
|
22
|
+
schema;
|
|
23
|
+
constructor(name, schema) {
|
|
24
|
+
const rel = path.join(".ase", `${name}.yaml`);
|
|
25
|
+
const found = this.findUpward(process.cwd(), rel);
|
|
26
|
+
this.filename = found ?? path.join(this.gitToplevel() ?? process.cwd(), rel);
|
|
27
|
+
this.doc = new Document();
|
|
28
|
+
this.schema = schema ?? null;
|
|
29
|
+
}
|
|
30
|
+
/* upward-walk on filesystem for a file path relative to a start directory */
|
|
31
|
+
findUpward(start, rel) {
|
|
32
|
+
let dir = start;
|
|
33
|
+
for (;;) {
|
|
34
|
+
const candidate = path.join(dir, rel);
|
|
35
|
+
if (fs.existsSync(candidate))
|
|
36
|
+
return candidate;
|
|
37
|
+
const parent = path.dirname(dir);
|
|
38
|
+
if (parent === dir)
|
|
39
|
+
return null;
|
|
40
|
+
dir = parent;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/* determine the Git top-level directory, if inside a Git repository */
|
|
44
|
+
gitToplevel() {
|
|
45
|
+
try {
|
|
46
|
+
const result = execaSync("git", ["rev-parse", "--show-toplevel"], {
|
|
47
|
+
stderr: "ignore"
|
|
48
|
+
});
|
|
49
|
+
return result.stdout.trim() || null;
|
|
40
50
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/* read configuration file into memory */
|
|
56
|
+
read() {
|
|
57
|
+
const text = fs.existsSync(this.filename) ? fs.readFileSync(this.filename, "utf8") : "";
|
|
58
|
+
this.doc = parseDocument(text);
|
|
59
|
+
this.validate("lenient");
|
|
60
|
+
}
|
|
61
|
+
/* write in-memory configuration back to file */
|
|
62
|
+
write() {
|
|
63
|
+
this.validate("strict");
|
|
64
|
+
fs.mkdirSync(path.dirname(this.filename), { recursive: true });
|
|
65
|
+
fs.writeFileSync(this.filename, this.doc.toString({ indent: 4 }), "utf8");
|
|
66
|
+
}
|
|
67
|
+
/* validate in-memory configuration against the optional schema */
|
|
68
|
+
validate(mode = "strict") {
|
|
69
|
+
if (this.schema === null)
|
|
70
|
+
return;
|
|
71
|
+
for (;;) {
|
|
72
|
+
const result = v.safeParse(this.schema, this.doc.toJS());
|
|
73
|
+
if (result.success)
|
|
74
|
+
return;
|
|
75
|
+
if (mode === "strict") {
|
|
76
|
+
const issues = result.issues.map((i) => {
|
|
77
|
+
const dotPath = (i.path ?? []).map((p) => String(p.key)).join(".");
|
|
78
|
+
return dotPath ? `${dotPath}: ${i.message}` : i.message;
|
|
79
|
+
}).join("; ");
|
|
80
|
+
throw new Error(`invalid configuration in ${this.filename}: ${issues}`);
|
|
81
|
+
}
|
|
82
|
+
let progressed = false;
|
|
83
|
+
for (const i of result.issues) {
|
|
84
|
+
const segs = (i.path ?? []).map((p) => String(p.key));
|
|
85
|
+
const dotPath = segs.join(".");
|
|
86
|
+
process.stderr.write(`ase: warning: invalid entry in ${this.filename}: ${dotPath ? `${dotPath}: ` : ""}${i.message}\n`);
|
|
87
|
+
if (segs.length > 0) {
|
|
88
|
+
this.doc.deleteIn(segs);
|
|
89
|
+
progressed = true;
|
|
90
|
+
}
|
|
51
91
|
}
|
|
52
|
-
|
|
53
|
-
|
|
92
|
+
if (!progressed)
|
|
93
|
+
return;
|
|
54
94
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
95
|
+
}
|
|
96
|
+
/* retrieve a value at a dotted key, or the root contents if no key given */
|
|
97
|
+
get(key) {
|
|
98
|
+
if (key === undefined)
|
|
99
|
+
return this.doc.contents;
|
|
100
|
+
return this.doc.getIn(key.split("."));
|
|
101
|
+
}
|
|
102
|
+
/* set a value at a dotted key, creating intermediate maps as needed */
|
|
103
|
+
set(key, value) {
|
|
104
|
+
const segments = key.split(".");
|
|
105
|
+
for (let i = 1; i < segments.length; i++) {
|
|
106
|
+
const prefix = segments.slice(0, i);
|
|
107
|
+
const node = this.doc.getIn(prefix, true);
|
|
108
|
+
if (!isMap(node))
|
|
109
|
+
this.doc.setIn(prefix, this.doc.createNode({}));
|
|
58
110
|
}
|
|
111
|
+
this.doc.setIn(segments, value);
|
|
112
|
+
this.validate("strict");
|
|
113
|
+
}
|
|
114
|
+
/* delete a value at a dotted key */
|
|
115
|
+
delete(key) {
|
|
116
|
+
this.doc.deleteIn(key.split("."));
|
|
59
117
|
}
|
|
118
|
+
}
|
|
119
|
+
/* register CLI command "ase config" */
|
|
120
|
+
const registerConfigCommand = (program) => {
|
|
121
|
+
const configCmd = program
|
|
122
|
+
.command("config")
|
|
123
|
+
.description("Manage ASE configuration")
|
|
124
|
+
.action((_opts, cmd) => {
|
|
125
|
+
cmd.help();
|
|
126
|
+
});
|
|
127
|
+
/* register CLI sub-command "ase config get" */
|
|
128
|
+
configCmd
|
|
129
|
+
.command("get")
|
|
130
|
+
.description("Print the value at a dotted configuration key")
|
|
131
|
+
.argument("<key>", "Configuration key (dotted path)")
|
|
132
|
+
.action((key) => {
|
|
133
|
+
const cfg = new Config("config", configSchema);
|
|
134
|
+
cfg.read();
|
|
135
|
+
const v = cfg.get(key);
|
|
136
|
+
if (isMap(v))
|
|
137
|
+
throw new Error(`key "${key}" is not a leaf key`);
|
|
138
|
+
console.log(isScalar(v) ? v.value : v);
|
|
139
|
+
});
|
|
140
|
+
/* register CLI sub-command "ase config set" */
|
|
141
|
+
configCmd
|
|
142
|
+
.command("set")
|
|
143
|
+
.description("Set the value at a dotted configuration key")
|
|
144
|
+
.argument("<key>", "Configuration key (dotted path)")
|
|
145
|
+
.argument("<value>", "Configuration value")
|
|
146
|
+
.action((key, value) => {
|
|
147
|
+
const cfg = new Config("config", configSchema);
|
|
148
|
+
cfg.read();
|
|
149
|
+
console.log(`${key}: ${value}`);
|
|
150
|
+
cfg.set(key, value);
|
|
151
|
+
cfg.write();
|
|
152
|
+
});
|
|
153
|
+
/* register CLI sub-command "ase config list" */
|
|
154
|
+
configCmd
|
|
155
|
+
.command("list")
|
|
156
|
+
.description("List all configured values as flat dotted keys")
|
|
157
|
+
.action(() => {
|
|
158
|
+
const cfg = new Config("config", configSchema);
|
|
159
|
+
cfg.read();
|
|
160
|
+
const table = new Table({ head: ["key", "value"] });
|
|
161
|
+
const list = (node, prefix) => {
|
|
162
|
+
if (isMap(node))
|
|
163
|
+
for (const item of node.items) {
|
|
164
|
+
const k = prefix ? `${prefix}.${item.key}` : String(item.key);
|
|
165
|
+
if (isMap(item.value))
|
|
166
|
+
list(item.value, k);
|
|
167
|
+
else
|
|
168
|
+
table.push([k, String(isScalar(item.value) ? item.value.value : item.value)]);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
list(cfg.get(), "");
|
|
172
|
+
console.log(table.toString());
|
|
173
|
+
});
|
|
174
|
+
/* register CLI sub-command "ase config edit" */
|
|
175
|
+
configCmd
|
|
176
|
+
.command("edit")
|
|
177
|
+
.description("Edit configuration file with $EDITOR")
|
|
178
|
+
.action(() => {
|
|
179
|
+
const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
180
|
+
const cfg = new Config("config", configSchema);
|
|
181
|
+
fs.mkdirSync(path.dirname(cfg.filename), { recursive: true });
|
|
182
|
+
if (!fs.existsSync(cfg.filename))
|
|
183
|
+
fs.writeFileSync(cfg.filename, "", "utf8");
|
|
184
|
+
execaSync(editor, [cfg.filename], { stdio: "inherit" });
|
|
185
|
+
cfg.read();
|
|
186
|
+
});
|
|
60
187
|
};
|
|
61
|
-
export default
|
|
188
|
+
export default registerConfigCommand;
|
package/dst/ase-service.js
CHANGED
|
@@ -7,9 +7,10 @@ import path from "node:path";
|
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import net from "node:net";
|
|
9
9
|
import { spawn } from "node:child_process";
|
|
10
|
-
import { parseDocument } from "yaml";
|
|
11
10
|
import Hapi from "@hapi/hapi";
|
|
12
11
|
import axios from "axios";
|
|
12
|
+
import * as v from "valibot";
|
|
13
|
+
import { Config, configSchema } from "./ase-config.js";
|
|
13
14
|
const SERVE_ENV = "ASE_SERVICE_SERVE";
|
|
14
15
|
const HOST = "127.0.0.1";
|
|
15
16
|
const IDLE_MS = 30 * 60 * 1000;
|
|
@@ -17,56 +18,30 @@ const TICK_MS = 60 * 1000;
|
|
|
17
18
|
const PORT_MIN = 42000;
|
|
18
19
|
const PORT_MAX = 44000;
|
|
19
20
|
const PORT_TRIES = 20;
|
|
20
|
-
/*
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
};
|
|
21
|
+
/* schema for ".ase/service.yaml" */
|
|
22
|
+
const serviceSchema = v.nullish(v.strictObject({
|
|
23
|
+
port: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1024), v.maxValue(65535)))
|
|
24
|
+
}));
|
|
33
25
|
/* load optional ".ase/config.yaml" and ".ase/service.yaml" files */
|
|
34
26
|
const loadContext = () => {
|
|
35
|
-
/*
|
|
36
|
-
const
|
|
37
|
-
|
|
27
|
+
/* load files */
|
|
28
|
+
const cfg = new Config("config", configSchema);
|
|
29
|
+
cfg.read();
|
|
30
|
+
const svc = new Config("service", serviceSchema);
|
|
31
|
+
svc.read();
|
|
38
32
|
/* determine project id */
|
|
39
|
-
|
|
40
|
-
|
|
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>"}`);
|
|
33
|
+
const rawId = cfg.get("project.id");
|
|
34
|
+
const projectId = (rawId === undefined || rawId === null) ? path.basename(process.cwd()) : rawId;
|
|
48
35
|
/* determine service port */
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
}
|
|
36
|
+
const rawPort = svc.get("port");
|
|
37
|
+
const port = (rawPort === undefined || rawPort === null) ? null : rawPort;
|
|
59
38
|
/* determine path to ".ase" directory */
|
|
60
|
-
const aseDir =
|
|
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");
|
|
39
|
+
const aseDir = path.dirname(svc.filename);
|
|
65
40
|
/* return context information */
|
|
66
41
|
return {
|
|
67
42
|
projectId,
|
|
68
43
|
port,
|
|
69
|
-
|
|
44
|
+
svc,
|
|
70
45
|
aseDir
|
|
71
46
|
};
|
|
72
47
|
};
|
|
@@ -93,12 +68,9 @@ const allocatePort = async () => {
|
|
|
93
68
|
throw new Error(`failed to allocate a port in ${PORT_MIN}..${PORT_MAX} after ${PORT_TRIES} attempts`);
|
|
94
69
|
};
|
|
95
70
|
/* persist an allocated port into ".ase/service.yaml" */
|
|
96
|
-
const persistPort = (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const doc = parseDocument(text);
|
|
100
|
-
doc.set("port", port);
|
|
101
|
-
fs.writeFileSync(svcPath, doc.toString(), "utf8");
|
|
71
|
+
const persistPort = (svc, port) => {
|
|
72
|
+
svc.set("port", port);
|
|
73
|
+
svc.write();
|
|
102
74
|
};
|
|
103
75
|
/* distinguish ECONNREFUSED from other Axios transport errors */
|
|
104
76
|
const isConnRefused = (err) => {
|
|
@@ -214,7 +186,7 @@ const doStart = async () => {
|
|
|
214
186
|
let port = ctx.port;
|
|
215
187
|
if (port === null) {
|
|
216
188
|
port = await allocatePort();
|
|
217
|
-
persistPort(ctx.
|
|
189
|
+
persistPort(ctx.svc, port);
|
|
218
190
|
}
|
|
219
191
|
if (process.env[SERVE_ENV] === "1") {
|
|
220
192
|
await runService({ ...ctx, port });
|
|
@@ -227,16 +199,20 @@ const doStart = async () => {
|
|
|
227
199
|
for (let i = 0; i < 50; i++) {
|
|
228
200
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
229
201
|
const s = await probe(port);
|
|
230
|
-
if (s !== null && s >= 200 && s < 300)
|
|
202
|
+
if (s !== null && s >= 200 && s < 300) {
|
|
203
|
+
process.stdout.write(`ase: service: started on port ${port}\n`);
|
|
231
204
|
return 0;
|
|
205
|
+
}
|
|
232
206
|
}
|
|
233
207
|
throw new Error("service failed to start within timeout");
|
|
234
208
|
};
|
|
235
209
|
/* stop flow: no-op if no port configured or connection refused */
|
|
236
210
|
const doStop = async () => {
|
|
237
211
|
const ctx = loadContext();
|
|
238
|
-
if (ctx.port === null)
|
|
212
|
+
if (ctx.port === null) {
|
|
213
|
+
process.stdout.write("ase: service: not running (no port configured)\n");
|
|
239
214
|
return 0;
|
|
215
|
+
}
|
|
240
216
|
try {
|
|
241
217
|
const r = await axios.request({
|
|
242
218
|
method: "GET",
|
|
@@ -247,17 +223,23 @@ const doStop = async () => {
|
|
|
247
223
|
return r.status >= 200 && r.status < 300 ? 0 : 1;
|
|
248
224
|
}
|
|
249
225
|
catch (err) {
|
|
250
|
-
if (isConnRefused(err))
|
|
226
|
+
if (isConnRefused(err)) {
|
|
227
|
+
process.stdout.write(`ase: service: not running (port ${ctx.port} not responding)\n`);
|
|
251
228
|
return 0;
|
|
229
|
+
}
|
|
252
230
|
throw err;
|
|
253
231
|
}
|
|
254
232
|
};
|
|
255
233
|
/* passthrough flow: POST /command with the arbitrary cmd token */
|
|
256
234
|
const doPassthrough = async (cmd) => {
|
|
257
|
-
|
|
258
|
-
if (ctx.port === null)
|
|
259
|
-
|
|
260
|
-
|
|
235
|
+
let ctx = loadContext();
|
|
236
|
+
if (ctx.port === null) {
|
|
237
|
+
await doStart();
|
|
238
|
+
ctx = loadContext();
|
|
239
|
+
if (ctx.port === null)
|
|
240
|
+
throw new Error("service not running (no port configured after auto-start)");
|
|
241
|
+
}
|
|
242
|
+
const send = async () => {
|
|
261
243
|
const r = await axios.request({
|
|
262
244
|
method: "POST",
|
|
263
245
|
url: `http://${HOST}:${ctx.port}/command`,
|
|
@@ -273,37 +255,48 @@ const doPassthrough = async (cmd) => {
|
|
|
273
255
|
if (!body.endsWith("\n"))
|
|
274
256
|
process.stdout.write("\n");
|
|
275
257
|
return r.status >= 200 && r.status < 300 ? 0 : 1;
|
|
258
|
+
};
|
|
259
|
+
try {
|
|
260
|
+
return await send();
|
|
276
261
|
}
|
|
277
262
|
catch (err) {
|
|
278
|
-
if (isConnRefused(err))
|
|
279
|
-
|
|
263
|
+
if (isConnRefused(err)) {
|
|
264
|
+
await doStart();
|
|
265
|
+
return await send();
|
|
266
|
+
}
|
|
280
267
|
throw err;
|
|
281
268
|
}
|
|
282
269
|
};
|
|
283
|
-
/* command
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
270
|
+
/* register CLI command "ase service" */
|
|
271
|
+
const registerServiceCommand = (program) => {
|
|
272
|
+
const service = program
|
|
273
|
+
.command("service")
|
|
274
|
+
.description("Manage per-project background HTTP service")
|
|
275
|
+
.action(() => {
|
|
276
|
+
service.outputHelp();
|
|
277
|
+
process.exit(1);
|
|
278
|
+
});
|
|
279
|
+
/* register CLI sub-command "ase service start" */
|
|
280
|
+
service
|
|
281
|
+
.command("start")
|
|
282
|
+
.description("Start the background service")
|
|
283
|
+
.action(async () => {
|
|
284
|
+
process.exit(await doStart());
|
|
285
|
+
});
|
|
286
|
+
/* register CLI sub-command "ase service stop" */
|
|
287
|
+
service
|
|
288
|
+
.command("stop")
|
|
289
|
+
.description("Stop the background service")
|
|
290
|
+
.action(async () => {
|
|
291
|
+
process.exit(await doStop());
|
|
292
|
+
});
|
|
293
|
+
/* register CLI sub-command "ase service send" */
|
|
294
|
+
service
|
|
295
|
+
.command("send")
|
|
296
|
+
.description("Send a command to the background service")
|
|
297
|
+
.argument("<cmd>", "Command token to dispatch to the service")
|
|
298
|
+
.action(async (cmd) => {
|
|
299
|
+
process.exit(await doPassthrough(cmd));
|
|
300
|
+
});
|
|
308
301
|
};
|
|
309
|
-
export default
|
|
302
|
+
export default registerServiceCommand;
|
package/dst/ase-setup.js
CHANGED
|
@@ -3,17 +3,16 @@
|
|
|
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
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (argv.debug)
|
|
6
|
+
const registerSetupCommand = (program) => {
|
|
7
|
+
program
|
|
8
|
+
.command("setup")
|
|
9
|
+
.description("Setup ASE")
|
|
10
|
+
.action((_opts, cmd) => {
|
|
11
|
+
const debug = Boolean(cmd.optsWithGlobals().debug);
|
|
12
|
+
if (debug)
|
|
14
13
|
console.log("DEBUG: setup command");
|
|
15
14
|
console.log("Setup ASE...");
|
|
16
15
|
/* TODO: implement setup logic */
|
|
17
|
-
}
|
|
16
|
+
});
|
|
18
17
|
};
|
|
19
|
-
export default
|
|
18
|
+
export default registerSetupCommand;
|
package/dst/ase.1
CHANGED
|
@@ -3,23 +3,59 @@
|
|
|
3
3
|
\fBase\fR - Agentic Software Engineering (ASE)
|
|
4
4
|
.SH "SYNOPSIS"
|
|
5
5
|
.P
|
|
6
|
-
\fBase\fR \[lB]\fB-h\fR|\fB--help\fR\[rB] \[lB]\fB-V\fR|\fB--version\fR\[rB] \[lB]\fIcommand\fR \[lB]\fIoptions\fR \[lB]...\[rB]\[rB]
|
|
6
|
+
\fBase\fR \[lB]\fB-h\fR|\fB--help\fR\[rB] \[lB]\fB-V\fR|\fB--version\fR\[rB] \[lB]\fB-d\fR|\fB--debug\fR\[rB] \[lB]\fIcommand\fR \[lB]\fIoptions\fR \[lB]...\[rB]\[rB] \[lB]\fIargs\fR \[lB]...\[rB]\[rB]\[rB]
|
|
7
7
|
.SH "DESCRIPTION"
|
|
8
8
|
.P
|
|
9
|
-
\fBase\fR, \fIAgentic Software
|
|
9
|
+
\fBase\fR, \fIAgentic Software Engineering (ASE)\fR, is the command-line companion tool to the \fIASE\fR Claude Code plugin. It provides project-level configuration management and a small per-project background HTTP service for dispatching commands.
|
|
10
10
|
.SH "OPTIONS"
|
|
11
11
|
.P
|
|
12
|
-
The following command-line options
|
|
12
|
+
The following top-level command-line options exist:
|
|
13
13
|
.RS 0
|
|
14
14
|
.IP \(bu 4
|
|
15
15
|
\[lB]\fB-h\fR|\fB--help\fR\[rB]: Show program usage information only.
|
|
16
16
|
.IP \(bu 4
|
|
17
17
|
\[lB]\fB-V\fR|\fB--version\fR\[rB]: Show program version information only.
|
|
18
|
+
.IP \(bu 4
|
|
19
|
+
\[lB]\fB-d\fR|\fB--debug\fR\[rB]: Enable debug output. The flag is inherited by all subcommands and can be inspected inside handlers via \fBcmd.optsWithGlobals()\fR.
|
|
20
|
+
.RE 0
|
|
21
|
+
|
|
22
|
+
.SH "COMMANDS"
|
|
23
|
+
.P
|
|
24
|
+
The following top-level commands exist:
|
|
25
|
+
.RS 0
|
|
26
|
+
.IP \(bu 4
|
|
27
|
+
\fBase config\fR: Manage \fIASE\fR configuration stored in \fB.ase/config.yaml\fR. Without a subcommand, prints usage information. The file is validated against a schema: on read, unknown or invalid entries are warned about and silently dropped from the in-memory view; on set/write, they cause a fatal error.
|
|
28
|
+
.IP \(bu 4
|
|
29
|
+
\fBase config get\fR \fIkey\fR: Print the value at the given dotted \fIkey\fR. Fails with an error if \fIkey\fR does not resolve to a leaf value.
|
|
30
|
+
.IP \(bu 4
|
|
31
|
+
\fBase config set\fR \fIkey\fR \fIvalue\fR: Set the value at the given dotted \fIkey\fR (creating intermediate maps as needed) and persist the file.
|
|
32
|
+
.IP \(bu 4
|
|
33
|
+
\fBase config list\fR: List all configured values as flat dotted keys, rendered as a two-column table of \fBkey\fR and \fBvalue\fR.
|
|
34
|
+
.IP \(bu 4
|
|
35
|
+
\fBase config edit\fR: Open \fB.ase/config.yaml\fR in the editor defined by the \fB$EDITOR\fR or \fB$VISUAL\fR environment variable (falling back to \fBvi\fR). The file and its parent directory are created if missing. After the editor exits, the file is re-read and schema warnings are reported.
|
|
36
|
+
.IP \(bu 4
|
|
37
|
+
\fBase service\fR: Manage the per-project background HTTP service. The service is bound to \fB127.0.0.1\fR on a port persisted in \fB.ase/service.yaml\fR and stops itself after 30 minutes of idle time. Without a subcommand, the help text is shown.
|
|
38
|
+
.IP \(bu 4
|
|
39
|
+
\fBase service start\fR: Start the background service (detached). Allocates a random port in the range \fB42000\fR..\fB44000\fR if none is persisted yet, writes it to \fB.ase/service.yaml\fR, and probes readiness. Exits silently with status 0 if the service is already running; prints \fBase: service: started on port <port>\fR on a fresh start.
|
|
40
|
+
.IP \(bu 4
|
|
41
|
+
\fBase service stop\fR: Stop the background service via HTTP \fBGET /stop\fR. Exits silently with status 0 on successful stop. If no port is configured or the port is not responding, prints an informational message and exits with status 0.
|
|
42
|
+
.IP \(bu 4
|
|
43
|
+
\fBase service send\fR \fIcmd\fR: Dispatch the \fIcmd\fR token as a passthrough command to the running service via HTTP \fBPOST /command\fR; if the service is not running, it is auto-started first.
|
|
44
|
+
.RE 0
|
|
45
|
+
|
|
46
|
+
.SH "FILES"
|
|
47
|
+
.RS 0
|
|
48
|
+
.IP \(bu 4
|
|
49
|
+
\fB.ase/config.yaml\fR: Per-project \fIASE\fR configuration. Read upward from the current working directory. Recognized key: \fBproject.id\fR (non-empty string).
|
|
50
|
+
.IP \(bu 4
|
|
51
|
+
\fB.ase/service.yaml\fR: Per-project service state. Recognized key: \fBport\fR (integer in \fB1024\fR..\fB65535\fR).
|
|
52
|
+
.IP \(bu 4
|
|
53
|
+
\fB.ase/service.log\fR: Stdout/stderr log of the detached background service.
|
|
18
54
|
.RE 0
|
|
19
55
|
|
|
20
56
|
.SH "HISTORY"
|
|
21
57
|
.P
|
|
22
|
-
\fBase\fR was started to be developed in October 2025
|
|
58
|
+
\fBase\fR was started to be developed in October 2025.
|
|
23
59
|
.SH "AUTHOR"
|
|
24
60
|
.P
|
|
25
61
|
Dr. Ralf S. Engelschall \fI\(larse@engelschall.com\(ra\fR
|
package/dst/ase.js
CHANGED
|
@@ -4,39 +4,35 @@
|
|
|
4
4
|
** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
5
5
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
6
6
|
*/
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import setupCommand from "./ase-setup.js";
|
|
11
|
-
import serviceCommand from "./ase-service.js";
|
|
7
|
+
import { Command, CommanderError } from "commander";
|
|
8
|
+
import registerConfigCommand from "./ase-config.js";
|
|
9
|
+
import registerServiceCommand from "./ase-service.js";
|
|
12
10
|
/* parse CLI arguments */
|
|
13
11
|
try {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
yargs.showHelp();
|
|
31
|
-
process.stderr.write(`\nase: ${msg}\n`);
|
|
32
|
-
process.exit(1);
|
|
33
|
-
})
|
|
34
|
-
.help()
|
|
35
|
-
.version()
|
|
36
|
-
.strict()
|
|
37
|
-
.parseAsync();
|
|
12
|
+
/* establish top-level program */
|
|
13
|
+
const program = new Command();
|
|
14
|
+
program
|
|
15
|
+
.name("ase")
|
|
16
|
+
.usage("<command> [options]")
|
|
17
|
+
.option("-d, --debug", "enable debug output", false)
|
|
18
|
+
.showHelpAfterError()
|
|
19
|
+
.enablePositionalOptions()
|
|
20
|
+
.exitOverride();
|
|
21
|
+
/* register top-level commands */
|
|
22
|
+
registerConfigCommand(program);
|
|
23
|
+
registerServiceCommand(program);
|
|
24
|
+
/* parse program arguments */
|
|
25
|
+
await program.parseAsync(process.argv);
|
|
26
|
+
/* gracefully terminate */
|
|
27
|
+
process.exit(0);
|
|
38
28
|
}
|
|
39
29
|
catch (err) {
|
|
30
|
+
if (err instanceof CommanderError) {
|
|
31
|
+
if (err.exitCode !== 0)
|
|
32
|
+
process.exit(err.exitCode);
|
|
33
|
+
else
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
40
36
|
const message = err instanceof Error ? err.message : String(err);
|
|
41
37
|
process.stderr.write(`ase: ERROR: ${message}\n`);
|
|
42
38
|
process.exit(1);
|
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.7",
|
|
10
10
|
"license": "GPL-3.0-only",
|
|
11
11
|
"author": {
|
|
12
12
|
"name": "Dr. Ralf S. Engelschall",
|
|
@@ -34,16 +34,17 @@
|
|
|
34
34
|
"remark": "15.0.1",
|
|
35
35
|
"remark-man": "9.0.0",
|
|
36
36
|
|
|
37
|
-
"@types/node": "25.6.0"
|
|
38
|
-
"@types/yargs": "17.0.35"
|
|
37
|
+
"@types/node": "25.6.0"
|
|
39
38
|
},
|
|
40
39
|
"dependencies": {
|
|
41
|
-
"
|
|
40
|
+
"commander": "14.0.3",
|
|
42
41
|
"yaml": "2.8.3",
|
|
42
|
+
"valibot": "1.3.1",
|
|
43
43
|
"execa": "9.6.1",
|
|
44
44
|
"mkdirp": "3.0.1",
|
|
45
45
|
"@hapi/hapi": "21.4.8",
|
|
46
|
-
"axios": "1.15.0"
|
|
46
|
+
"axios": "1.15.0",
|
|
47
|
+
"cli-table3": "0.6.5"
|
|
47
48
|
},
|
|
48
49
|
"engines": {
|
|
49
50
|
"npm": ">=10.0.0",
|