@rse/ase 0.0.26 → 0.0.28
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 +6 -5
- package/dst/ase-diagram.js +184 -115
- package/dst/ase-hook.js +42 -10
- package/dst/ase-mcp.js +1 -32
- package/dst/ase-persona.js +87 -0
- package/dst/ase-service-probe.js +38 -0
- package/dst/ase-service.js +168 -401
- package/dst/ase-setup.js +6 -5
- package/dst/ase-statusline.js +13 -3
- package/dst/ase-task.js +314 -95
- package/dst/ase.js +3 -3
- package/package.json +3 -3
package/dst/ase-setup.js
CHANGED
|
@@ -55,15 +55,16 @@ export default class SetupCommand {
|
|
|
55
55
|
this.log.write("info", `setup: ${ignoreError} (skipped)`);
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
|
-
const
|
|
58
|
+
const e = err;
|
|
59
|
+
const exitCode = typeof e.exitCode === "number" ? e.exitCode : -1;
|
|
59
60
|
this.log.write("error", `setup: command failed: exit code: ${exitCode}`);
|
|
60
|
-
if (typeof
|
|
61
|
+
if (typeof e.stdout === "string" && e.stdout.length > 0) {
|
|
61
62
|
this.log.write("error", "setup: command failed: stdout:");
|
|
62
|
-
process.stdout.write(
|
|
63
|
+
process.stdout.write(e.stdout);
|
|
63
64
|
}
|
|
64
|
-
if (typeof
|
|
65
|
+
if (typeof e.stderr === "string" && e.stderr.length > 0) {
|
|
65
66
|
this.log.write("error", "setup: command failed: stderr:");
|
|
66
|
-
process.stderr.write(
|
|
67
|
+
process.stderr.write(e.stderr);
|
|
67
68
|
}
|
|
68
69
|
throw err;
|
|
69
70
|
}
|
package/dst/ase-statusline.js
CHANGED
|
@@ -37,15 +37,25 @@ const readStdin = async () => {
|
|
|
37
37
|
/* detect terminal column width via /dev/tty (stdout is a pipe under Claude Code) */
|
|
38
38
|
const detectTermWidth = () => {
|
|
39
39
|
let width = 0;
|
|
40
|
+
let tty = null;
|
|
40
41
|
try {
|
|
41
|
-
|
|
42
|
+
tty = fs.openSync("/dev/tty", "r");
|
|
42
43
|
const out = execFileSync("tput", ["cols"], { stdio: [tty, "pipe", "ignore"] });
|
|
43
|
-
|
|
44
|
-
width = parseInt(out.toString("utf8").trim()) || 0;
|
|
44
|
+
width = Number.parseInt(out.toString("utf8").trim(), 10) || 0;
|
|
45
45
|
}
|
|
46
46
|
catch (_e) {
|
|
47
47
|
/* no controlling terminal */
|
|
48
48
|
}
|
|
49
|
+
finally {
|
|
50
|
+
if (tty !== null) {
|
|
51
|
+
try {
|
|
52
|
+
fs.closeSync(tty);
|
|
53
|
+
}
|
|
54
|
+
catch (_e) {
|
|
55
|
+
/* best-effort */
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
49
59
|
return width;
|
|
50
60
|
};
|
|
51
61
|
/* format a token count as a compact human-readable string (e.g. 334k, 104.9M) */
|
package/dst/ase-task.js
CHANGED
|
@@ -4,92 +4,138 @@
|
|
|
4
4
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
5
5
|
*/
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import os from "node:os";
|
|
8
7
|
import fs from "node:fs";
|
|
9
8
|
import { execaSync } from "execa";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
/*
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const dir = path.join(os.homedir(), ".ase", "task");
|
|
51
|
-
if (!fs.existsSync(dir))
|
|
52
|
-
return [];
|
|
53
|
-
const ids = [];
|
|
54
|
-
for (const entry of fs.readdirSync(dir)) {
|
|
55
|
-
if (!/^[A-Za-z0-9-]+$/.test(entry))
|
|
56
|
-
continue;
|
|
57
|
-
const file = path.join(dir, entry, "plan.md");
|
|
9
|
+
import { DateTime } from "luxon";
|
|
10
|
+
import { isScalar } from "yaml";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { Config, configSchema, parseScope } from "./ase-config.js";
|
|
13
|
+
/* reusable functionality: persisted task plans under
|
|
14
|
+
<project>/.ase/task/<id>/plan.md */
|
|
15
|
+
export class Task {
|
|
16
|
+
/* validate the task id to keep it safe as a filename component */
|
|
17
|
+
static validateId(id) {
|
|
18
|
+
if (typeof id !== "string" || id.length === 0)
|
|
19
|
+
throw new Error("task: id must be a non-empty string");
|
|
20
|
+
if (!/^[A-Za-z0-9-]+$/.test(id))
|
|
21
|
+
throw new Error("task: id must match [A-Za-z0-9-]+");
|
|
22
|
+
}
|
|
23
|
+
/* determine the project root (Git top-level if inside a Git
|
|
24
|
+
working tree, otherwise the current working directory) */
|
|
25
|
+
static projectRoot() {
|
|
26
|
+
try {
|
|
27
|
+
const result = execaSync("git", ["rev-parse", "--show-toplevel"], { stderr: "ignore" });
|
|
28
|
+
const top = result.stdout.trim();
|
|
29
|
+
if (top !== "")
|
|
30
|
+
return top;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
/* not inside a Git working tree */
|
|
34
|
+
}
|
|
35
|
+
return process.cwd();
|
|
36
|
+
}
|
|
37
|
+
/* resolve the on-disk base directory for task storage */
|
|
38
|
+
static baseDir() {
|
|
39
|
+
return path.join(Task.projectRoot(), ".ase", "task");
|
|
40
|
+
}
|
|
41
|
+
/* resolve the on-disk path for a given task id */
|
|
42
|
+
static path(id) {
|
|
43
|
+
Task.validateId(id);
|
|
44
|
+
return path.join(Task.baseDir(), id, "plan.md");
|
|
45
|
+
}
|
|
46
|
+
/* load a task; returns empty string if no task exists */
|
|
47
|
+
static load(id) {
|
|
48
|
+
const file = Task.path(id);
|
|
58
49
|
if (!fs.existsSync(file))
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (!st.isFile())
|
|
62
|
-
continue;
|
|
63
|
-
ids.push(entry);
|
|
50
|
+
return "";
|
|
51
|
+
return fs.readFileSync(file, "utf8");
|
|
64
52
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const sub = path.join(dir, entry);
|
|
80
|
-
const file = path.join(sub, "plan.md");
|
|
53
|
+
/* save a task as UTF-8 text under the given id; the task's home
|
|
54
|
+
directory <project>/.ase/task/<id>/ is owned by ASE and removed
|
|
55
|
+
in full by Task.delete, so callers must not place foreign files there */
|
|
56
|
+
static save(id, text) {
|
|
57
|
+
if (typeof text !== "string")
|
|
58
|
+
throw new Error("task: text must be a string");
|
|
59
|
+
const file = Task.path(id);
|
|
60
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
61
|
+
fs.writeFileSync(file, text, "utf8");
|
|
62
|
+
}
|
|
63
|
+
/* delete a task by id; removes the entire task home directory
|
|
64
|
+
<project>/.ase/task/<id>/ (owned by ASE); returns true if a task existed */
|
|
65
|
+
static delete(id) {
|
|
66
|
+
const file = Task.path(id);
|
|
81
67
|
if (!fs.existsSync(file))
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
68
|
+
return false;
|
|
69
|
+
fs.rmSync(path.dirname(file), { recursive: true, force: true });
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
/* list all persisted tasks in lexicographic id order; if verbose is true,
|
|
73
|
+
each entry's `mtime` is set to the `plan.md` modification time formatted
|
|
74
|
+
as "YYYY-MM-DD HH:MM", otherwise it is left undefined */
|
|
75
|
+
static list(verbose = false) {
|
|
76
|
+
const dir = Task.baseDir();
|
|
77
|
+
if (!fs.existsSync(dir))
|
|
78
|
+
return [];
|
|
79
|
+
const out = [];
|
|
80
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
81
|
+
if (!/^[A-Za-z0-9-]+$/.test(entry))
|
|
82
|
+
continue;
|
|
83
|
+
const file = path.join(dir, entry, "plan.md");
|
|
84
|
+
if (!fs.existsSync(file))
|
|
85
|
+
continue;
|
|
86
|
+
const st = fs.statSync(file);
|
|
87
|
+
if (!st.isFile())
|
|
88
|
+
continue;
|
|
89
|
+
const mtime = verbose ? DateTime.fromJSDate(st.mtime).toFormat("yyyy-LL-dd HH:mm") : undefined;
|
|
90
|
+
out.push({ id: entry, mtime });
|
|
89
91
|
}
|
|
92
|
+
out.sort((a, b) => a.id.localeCompare(b.id));
|
|
93
|
+
return out;
|
|
90
94
|
}
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
/* purge tasks whose modification time is older than the given cutoff in
|
|
96
|
+
milliseconds; returns the list of removed task ids */
|
|
97
|
+
static purge(maxAgeMs) {
|
|
98
|
+
const dir = Task.baseDir();
|
|
99
|
+
if (!fs.existsSync(dir))
|
|
100
|
+
return [];
|
|
101
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
102
|
+
const removed = [];
|
|
103
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
104
|
+
if (!/^[A-Za-z0-9-]+$/.test(entry))
|
|
105
|
+
continue;
|
|
106
|
+
const sub = path.join(dir, entry);
|
|
107
|
+
const file = path.join(sub, "plan.md");
|
|
108
|
+
if (!fs.existsSync(file))
|
|
109
|
+
continue;
|
|
110
|
+
const st = fs.statSync(file);
|
|
111
|
+
if (!st.isFile())
|
|
112
|
+
continue;
|
|
113
|
+
if (st.mtimeMs < cutoff) {
|
|
114
|
+
fs.rmSync(sub, { recursive: true, force: true });
|
|
115
|
+
removed.push(entry);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return removed;
|
|
119
|
+
}
|
|
120
|
+
/* get the active task id for a given session, or empty string if none */
|
|
121
|
+
static getId(log, session) {
|
|
122
|
+
const scope = parseScope(`session:${session}`);
|
|
123
|
+
const cfg = new Config("config", configSchema, log, scope);
|
|
124
|
+
cfg.read();
|
|
125
|
+
const val = cfg.get("agent.task");
|
|
126
|
+
if (val === undefined)
|
|
127
|
+
return "";
|
|
128
|
+
return String(isScalar(val) ? val.value : val);
|
|
129
|
+
}
|
|
130
|
+
/* set the active task id for a given session */
|
|
131
|
+
static setId(log, session, id) {
|
|
132
|
+
const scope = parseScope(`session:${session}`);
|
|
133
|
+
const cfg = new Config("config", configSchema, log, scope);
|
|
134
|
+
cfg.read();
|
|
135
|
+
cfg.set("agent.task", id);
|
|
136
|
+
cfg.write();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
93
139
|
/* read all of stdin as a UTF-8 string */
|
|
94
140
|
const readStdin = () => {
|
|
95
141
|
return new Promise((resolve, reject) => {
|
|
@@ -110,7 +156,7 @@ export default class TaskCommand {
|
|
|
110
156
|
/* register CLI top-level command "ase task" */
|
|
111
157
|
const task = program
|
|
112
158
|
.command("task")
|
|
113
|
-
.description("Manage persisted tasks under
|
|
159
|
+
.description("Manage persisted tasks under <project>/.ase/task/<id>/plan.md")
|
|
114
160
|
.action(() => {
|
|
115
161
|
task.outputHelp();
|
|
116
162
|
process.exit(1);
|
|
@@ -119,10 +165,15 @@ export default class TaskCommand {
|
|
|
119
165
|
task
|
|
120
166
|
.command("list")
|
|
121
167
|
.description("List all persisted task ids, one per line")
|
|
122
|
-
.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
168
|
+
.option("-v, --verbose", "also show the plan.md modification time as (YYYY-MM-DD HH:MM)")
|
|
169
|
+
.action((opts) => {
|
|
170
|
+
const items = Task.list(opts.verbose ?? false);
|
|
171
|
+
for (const item of items) {
|
|
172
|
+
if (opts.verbose)
|
|
173
|
+
process.stdout.write(`${item.id}\t(${item.mtime})\n`);
|
|
174
|
+
else
|
|
175
|
+
process.stdout.write(`${item.id}\n`);
|
|
176
|
+
}
|
|
126
177
|
process.exit(0);
|
|
127
178
|
});
|
|
128
179
|
/* register CLI sub-command "ase task load" */
|
|
@@ -131,7 +182,7 @@ export default class TaskCommand {
|
|
|
131
182
|
.description("Load a task by id and write it to stdout")
|
|
132
183
|
.argument("<id>", "Task identifier")
|
|
133
184
|
.action((id) => {
|
|
134
|
-
const text =
|
|
185
|
+
const text = Task.load(id);
|
|
135
186
|
process.stdout.write(text);
|
|
136
187
|
process.exit(0);
|
|
137
188
|
});
|
|
@@ -141,7 +192,7 @@ export default class TaskCommand {
|
|
|
141
192
|
.description("Edit a task by id with $EDITOR")
|
|
142
193
|
.argument("<id>", "Task identifier")
|
|
143
194
|
.action((id) => {
|
|
144
|
-
const file =
|
|
195
|
+
const file = Task.path(id);
|
|
145
196
|
const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
146
197
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
147
198
|
if (!fs.existsSync(file))
|
|
@@ -157,7 +208,7 @@ export default class TaskCommand {
|
|
|
157
208
|
.argument("<id>", "Task identifier")
|
|
158
209
|
.action(async (id) => {
|
|
159
210
|
const text = await readStdin();
|
|
160
|
-
|
|
211
|
+
Task.save(id, text);
|
|
161
212
|
this.log.write("info", `task: saved "${id}"`);
|
|
162
213
|
process.exit(0);
|
|
163
214
|
});
|
|
@@ -167,7 +218,7 @@ export default class TaskCommand {
|
|
|
167
218
|
.description("Delete a task by id")
|
|
168
219
|
.argument("<id>", "Task identifier")
|
|
169
220
|
.action((id) => {
|
|
170
|
-
const removed =
|
|
221
|
+
const removed = Task.delete(id);
|
|
171
222
|
if (removed)
|
|
172
223
|
this.log.write("info", `task: removed "${id}"`);
|
|
173
224
|
else
|
|
@@ -177,13 +228,24 @@ export default class TaskCommand {
|
|
|
177
228
|
/* register CLI sub-command "ase task purge" */
|
|
178
229
|
task
|
|
179
230
|
.command("purge")
|
|
180
|
-
.description("Remove all tasks with a modification time older than <
|
|
181
|
-
|
|
182
|
-
.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
231
|
+
.description("Remove all tasks with a modification time older than <age> (default: 31d); " +
|
|
232
|
+
"<age> is <number><unit> with unit h (hour), d (day), m (month), y (year)")
|
|
233
|
+
.argument("[<age>]", "Maximum task age as <number><unit>", "31d")
|
|
234
|
+
.action((age) => {
|
|
235
|
+
const m = /^(\d+)([hdmy])$/.exec(age);
|
|
236
|
+
if (m === null)
|
|
237
|
+
throw new Error("task: <age> must match <number><unit> with unit h, d, m, or y");
|
|
238
|
+
const n = Number.parseInt(m[1], 10);
|
|
239
|
+
const unit = m[2];
|
|
240
|
+
const hour = 60 * 60 * 1000;
|
|
241
|
+
const day = 24 * hour;
|
|
242
|
+
const month = 30 * day;
|
|
243
|
+
const year = 365 * day;
|
|
244
|
+
const factor = unit === "h" ? hour :
|
|
245
|
+
unit === "d" ? day :
|
|
246
|
+
unit === "m" ? month :
|
|
247
|
+
year;
|
|
248
|
+
const removed = Task.purge(n * factor);
|
|
187
249
|
if (removed.length === 0)
|
|
188
250
|
this.log.write("info", "task: no tasks to purge");
|
|
189
251
|
else
|
|
@@ -193,3 +255,160 @@ export default class TaskCommand {
|
|
|
193
255
|
});
|
|
194
256
|
}
|
|
195
257
|
}
|
|
258
|
+
/* MCP registration entry point for task tools */
|
|
259
|
+
export class TaskMCP {
|
|
260
|
+
log;
|
|
261
|
+
constructor(log) {
|
|
262
|
+
this.log = log;
|
|
263
|
+
}
|
|
264
|
+
register(mcp) {
|
|
265
|
+
mcp.registerTool("task_list", {
|
|
266
|
+
title: "ASE task list",
|
|
267
|
+
description: "List all persisted tasks. " +
|
|
268
|
+
"Returns a `tasks` array (in lexicographic `id` order) where each item has the " +
|
|
269
|
+
"task `id`. If `verbose` is `true`, each item additionally has an `mtime` field " +
|
|
270
|
+
"(last modification time of the task's `plan.md`, formatted as `YYYY-MM-DD HH:MM`). " +
|
|
271
|
+
"Returns an empty array if no tasks exist.",
|
|
272
|
+
inputSchema: {
|
|
273
|
+
verbose: z.boolean().optional()
|
|
274
|
+
.describe("if true, also include the `mtime` field per task (default: false)")
|
|
275
|
+
},
|
|
276
|
+
outputSchema: {
|
|
277
|
+
tasks: z.array(z.object({
|
|
278
|
+
id: z.string().describe("task identifier"),
|
|
279
|
+
mtime: z.string().optional()
|
|
280
|
+
.describe("plan.md modification time (`YYYY-MM-DD HH:MM`); only present if `verbose` is true")
|
|
281
|
+
})).describe("all persisted tasks in lexicographic id order")
|
|
282
|
+
}
|
|
283
|
+
}, async (args) => {
|
|
284
|
+
try {
|
|
285
|
+
const verbose = args.verbose ?? false;
|
|
286
|
+
const items = Task.list(verbose);
|
|
287
|
+
const tasks = verbose ?
|
|
288
|
+
items.map((item) => ({ id: item.id, mtime: item.mtime })) :
|
|
289
|
+
items.map((item) => ({ id: item.id }));
|
|
290
|
+
const result = { tasks };
|
|
291
|
+
return {
|
|
292
|
+
structuredContent: result,
|
|
293
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
298
|
+
return {
|
|
299
|
+
isError: true,
|
|
300
|
+
content: [{ type: "text", text: `task_list: ERROR: ${message}` }]
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
mcp.registerTool("task_load", {
|
|
305
|
+
title: "ASE task load",
|
|
306
|
+
description: "Load a previously persisted task by `id`. " +
|
|
307
|
+
"Returns the task as `text`; returns an empty string if no task exists for the `id`.",
|
|
308
|
+
inputSchema: {
|
|
309
|
+
id: z.string()
|
|
310
|
+
.describe("task identifier (allowed characters: A-Z, a-z, 0-9, '-')")
|
|
311
|
+
}
|
|
312
|
+
}, async (args) => {
|
|
313
|
+
try {
|
|
314
|
+
const text = Task.load(args.id);
|
|
315
|
+
return {
|
|
316
|
+
content: [{ type: "text", text }]
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
321
|
+
return {
|
|
322
|
+
isError: true,
|
|
323
|
+
content: [{ type: "text", text: `task_load: ERROR: ${message}` }]
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
mcp.registerTool("task_save", {
|
|
328
|
+
title: "ASE task save",
|
|
329
|
+
description: "Persist a task as `text` under `id`. " +
|
|
330
|
+
"Overwrites any existing task for the same `id`.",
|
|
331
|
+
inputSchema: {
|
|
332
|
+
id: z.string()
|
|
333
|
+
.describe("task identifier (allowed characters: A-Z, a-z, 0-9, '-')"),
|
|
334
|
+
text: z.string()
|
|
335
|
+
.describe("text content of the task")
|
|
336
|
+
}
|
|
337
|
+
}, async (args) => {
|
|
338
|
+
try {
|
|
339
|
+
Task.save(args.id, args.text);
|
|
340
|
+
return {
|
|
341
|
+
content: [{ type: "text", text: `task_save: OK: saved task "${args.id}"` }]
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
catch (err) {
|
|
345
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
346
|
+
return {
|
|
347
|
+
isError: true,
|
|
348
|
+
content: [{ type: "text", text: `task_save: ERROR: ${message}` }]
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
mcp.registerTool("task_delete", {
|
|
353
|
+
title: "ASE task delete",
|
|
354
|
+
description: "Delete a previously persisted task by `id`. " +
|
|
355
|
+
"Returns a status `text` indicating whether a task existed and was removed.",
|
|
356
|
+
inputSchema: {
|
|
357
|
+
id: z.string()
|
|
358
|
+
.describe("task identifier (allowed characters: A-Z, a-z, 0-9, '-')")
|
|
359
|
+
}
|
|
360
|
+
}, async (args) => {
|
|
361
|
+
try {
|
|
362
|
+
const removed = Task.delete(args.id);
|
|
363
|
+
const msg = removed ?
|
|
364
|
+
`task_delete: OK: removed task "${args.id}"` :
|
|
365
|
+
`task_delete: WARNING: no task "${args.id}" to remove`;
|
|
366
|
+
return {
|
|
367
|
+
content: [{ type: "text", text: msg }]
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
catch (err) {
|
|
371
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
372
|
+
return {
|
|
373
|
+
isError: true,
|
|
374
|
+
content: [{ type: "text", text: `task_delete: ERROR: ${message}` }]
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
mcp.registerTool("task_id", {
|
|
379
|
+
title: "ASE task id get/set",
|
|
380
|
+
description: "Get or set the active ASE task `id` for a given `session`. " +
|
|
381
|
+
"If `id` is provided, it sets the task id in the given `session`, " +
|
|
382
|
+
"otherwise it returns the current task `id` of the `session`.",
|
|
383
|
+
inputSchema: {
|
|
384
|
+
id: z.string().optional()
|
|
385
|
+
.describe("task identifier to set (allowed characters: A-Z, a-z, 0-9, '-'); " +
|
|
386
|
+
"if omitted, the current task id is returned"),
|
|
387
|
+
session: z.string()
|
|
388
|
+
.describe("session identifier (allowed characters: A-Z, a-z, 0-9, '-')")
|
|
389
|
+
}
|
|
390
|
+
}, async (args) => {
|
|
391
|
+
try {
|
|
392
|
+
if (args.id !== undefined) {
|
|
393
|
+
Task.setId(this.log, args.session, args.id);
|
|
394
|
+
const msg = `task_id: OK: set agent.task to "${args.id}" ` +
|
|
395
|
+
`for session "${args.session}"`;
|
|
396
|
+
return {
|
|
397
|
+
content: [{ type: "text", text: msg }]
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
const text = Task.getId(this.log, args.session);
|
|
401
|
+
return {
|
|
402
|
+
content: [{ type: "text", text }]
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
catch (err) {
|
|
406
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
407
|
+
return {
|
|
408
|
+
isError: true,
|
|
409
|
+
content: [{ type: "text", text: `task_id: ERROR: ${message}` }]
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
package/dst/ase.js
CHANGED
|
@@ -42,14 +42,14 @@ const main = async () => {
|
|
|
42
42
|
log.logFile(opts.logFile);
|
|
43
43
|
});
|
|
44
44
|
/* register top-level commands */
|
|
45
|
+
new SetupCommand(log).register(program);
|
|
45
46
|
new ConfigCommand(log).register(program);
|
|
46
|
-
new ServiceCommand(log).register(program);
|
|
47
47
|
new MCPCommand(log).register(program);
|
|
48
|
+
new ServiceCommand(log).register(program);
|
|
48
49
|
new HookCommand(log).register(program);
|
|
49
|
-
new DiagramCommand(log).register(program);
|
|
50
|
-
new SetupCommand(log).register(program);
|
|
51
50
|
new StatuslineCommand(log).register(program);
|
|
52
51
|
new TaskCommand(log).register(program);
|
|
52
|
+
new DiagramCommand(log).register(program);
|
|
53
53
|
/* parse program arguments */
|
|
54
54
|
await program.parseAsync(process.argv);
|
|
55
55
|
/* 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.28",
|
|
10
10
|
"license": "GPL-3.0-only",
|
|
11
11
|
"author": {
|
|
12
12
|
"name": "Dr. Ralf S. Engelschall",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"nodemon": "3.1.14",
|
|
31
31
|
"shx": "0.4.0",
|
|
32
32
|
|
|
33
|
-
"@types/node": "25.
|
|
33
|
+
"@types/node": "25.8.0",
|
|
34
34
|
"@types/luxon": "3.7.1",
|
|
35
35
|
"@types/which": "3.0.4",
|
|
36
36
|
"@types/update-notifier": "6.0.8"
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"execa": "9.6.1",
|
|
43
43
|
"mkdirp": "3.0.1",
|
|
44
44
|
"@hapi/hapi": "21.4.9",
|
|
45
|
-
"axios": "1.16.
|
|
45
|
+
"axios": "1.16.1",
|
|
46
46
|
"beautiful-mermaid": "1.1.3",
|
|
47
47
|
"cli-table3": "0.6.5",
|
|
48
48
|
"chalk": "5.6.2",
|