botholomew 0.12.5 → 0.13.0
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 +91 -68
- package/package.json +2 -2
- package/src/chat/agent.ts +42 -82
- package/src/chat/session.ts +29 -25
- package/src/commands/capabilities.ts +1 -1
- package/src/commands/context.ts +177 -926
- package/src/commands/db.ts +9 -13
- package/src/commands/init.ts +4 -1
- package/src/commands/nuke.ts +57 -90
- package/src/commands/schedule.ts +103 -124
- package/src/commands/skill.ts +2 -2
- package/src/commands/task.ts +86 -95
- package/src/commands/thread.ts +107 -112
- package/src/commands/worker.ts +88 -88
- package/src/constants.ts +93 -16
- package/src/context/capabilities.ts +10 -10
- package/src/context/fetcher.ts +9 -10
- package/src/context/reindex.ts +189 -0
- package/src/context/store.ts +630 -0
- package/src/db/doctor.ts +1 -8
- package/src/db/embeddings.ts +227 -175
- package/src/db/sql/19-disk_backed_index.sql +36 -0
- package/src/db/sql/20-drop_db_tables_for_files.sql +19 -0
- package/src/fs/atomic.ts +217 -0
- package/src/fs/compat.ts +86 -0
- package/src/fs/sandbox.ts +279 -0
- package/src/init/index.ts +69 -52
- package/src/init/templates.ts +1 -1
- package/src/mcpx/client.ts +1 -1
- package/src/schedules/schema.ts +19 -0
- package/src/schedules/store.ts +296 -0
- package/src/skills/commands.ts +1 -3
- package/src/tasks/schema.ts +47 -0
- package/src/tasks/store.ts +486 -0
- package/src/threads/store.ts +559 -0
- package/src/tools/capabilities/refresh.ts +42 -21
- package/src/tools/context/pipe.ts +15 -71
- package/src/tools/context/update-beliefs.ts +3 -3
- package/src/tools/context/update-goals.ts +3 -3
- package/src/tools/dir/create.ts +26 -23
- package/src/tools/dir/size.ts +46 -17
- package/src/tools/dir/tree.ts +73 -279
- package/src/tools/file/copy.ts +50 -24
- package/src/tools/file/count-lines.ts +34 -10
- package/src/tools/file/delete.ts +44 -23
- package/src/tools/file/edit.ts +39 -14
- package/src/tools/file/exists.ts +12 -26
- package/src/tools/file/info.ts +25 -85
- package/src/tools/file/move.ts +39 -24
- package/src/tools/file/read.ts +32 -80
- package/src/tools/file/write.ts +14 -91
- package/src/tools/registry.ts +3 -7
- package/src/tools/schedule/create.ts +2 -2
- package/src/tools/schedule/list.ts +7 -3
- package/src/tools/search/fuse.ts +12 -33
- package/src/tools/search/index.ts +36 -43
- package/src/tools/search/regexp.ts +29 -17
- package/src/tools/search/semantic.ts +137 -51
- package/src/tools/skill/delete.ts +1 -1
- package/src/tools/skill/list.ts +1 -1
- package/src/tools/skill/write.ts +1 -1
- package/src/tools/task/create.ts +41 -16
- package/src/tools/task/delete.ts +3 -3
- package/src/tools/task/list.ts +6 -3
- package/src/tools/task/update.ts +31 -9
- package/src/tools/task/view.ts +6 -6
- package/src/tools/thread/list.ts +2 -2
- package/src/tools/thread/search.ts +208 -0
- package/src/tools/thread/view.ts +50 -5
- package/src/tools/worker/spawn.ts +28 -14
- package/src/tui/App.tsx +12 -19
- package/src/tui/components/ContextPanel.tsx +83 -316
- package/src/tui/components/SchedulePanel.tsx +34 -48
- package/src/tui/components/StatusBar.tsx +15 -15
- package/src/tui/components/TaskPanel.tsx +34 -38
- package/src/tui/components/ThreadPanel.tsx +29 -38
- package/src/tui/components/WorkerPanel.tsx +21 -19
- package/src/tui/markdown.ts +2 -8
- package/src/utils/title.ts +5 -7
- package/src/utils/v7-date.ts +47 -0
- package/src/worker/heartbeat.ts +46 -24
- package/src/worker/index.ts +13 -15
- package/src/worker/llm.ts +30 -37
- package/src/worker/prompt.ts +19 -41
- package/src/worker/schedules.ts +48 -69
- package/src/worker/spawn.ts +11 -11
- package/src/worker/tick.ts +39 -43
- package/src/workers/store.ts +247 -0
- package/src/commands/tools.ts +0 -367
- package/src/context/describer.ts +0 -140
- package/src/context/drives.ts +0 -110
- package/src/context/ingest.ts +0 -162
- package/src/context/refresh.ts +0 -183
- package/src/db/context.ts +0 -637
- package/src/db/daemon-state.ts +0 -6
- package/src/db/reembed.ts +0 -113
- package/src/db/schedules.ts +0 -213
- package/src/db/tasks.ts +0 -347
- package/src/db/threads.ts +0 -276
- package/src/db/workers.ts +0 -212
- package/src/tools/context/list-drives.ts +0 -36
- package/src/tools/context/refresh.ts +0 -165
- package/src/tools/context/search.ts +0 -54
package/src/commands/db.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import ansis from "ansis";
|
|
2
2
|
import type { Command } from "commander";
|
|
3
3
|
import { getDbPath } from "../constants.ts";
|
|
4
|
-
import { withDb as coreWithDb } from "../db/connection.ts";
|
|
5
4
|
import {
|
|
6
5
|
isPidAlive,
|
|
7
6
|
type ProbeResult,
|
|
8
7
|
probeAllTables,
|
|
9
8
|
repairDatabase,
|
|
10
9
|
} from "../db/doctor.ts";
|
|
11
|
-
import { listWorkers, type Worker } from "../db/workers.ts";
|
|
12
10
|
import { logger } from "../utils/logger.ts";
|
|
11
|
+
import { listWorkers, type Worker } from "../workers/store.ts";
|
|
13
12
|
|
|
14
13
|
function statusBadge(status: ProbeResult["status"]): string {
|
|
15
14
|
switch (status) {
|
|
@@ -81,17 +80,14 @@ async function doctor(program: Command, repair: boolean): Promise<void> {
|
|
|
81
80
|
|
|
82
81
|
// Repair requires exclusive access — refuse if any worker is actually
|
|
83
82
|
// running, otherwise the EXPORT would race with the worker's writes.
|
|
84
|
-
// Stale
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return [] as Worker[];
|
|
93
|
-
}
|
|
94
|
-
});
|
|
83
|
+
// Stale 'running' worker JSON files whose PID is dead are reported but
|
|
84
|
+
// don't block repair.
|
|
85
|
+
let running: Worker[];
|
|
86
|
+
try {
|
|
87
|
+
running = await listWorkers(dir, { status: "running" });
|
|
88
|
+
} catch {
|
|
89
|
+
running = [];
|
|
90
|
+
}
|
|
95
91
|
const live = running.filter((w) => isPidAlive(w.pid));
|
|
96
92
|
const stale = running.filter((w) => !isPidAlive(w.pid));
|
|
97
93
|
if (live.length > 0) {
|
package/src/commands/init.ts
CHANGED
|
@@ -6,7 +6,10 @@ export function registerInitCommand(program: Command) {
|
|
|
6
6
|
program
|
|
7
7
|
.command("init")
|
|
8
8
|
.description("Initialize a new Botholomew project in the current directory")
|
|
9
|
-
.option(
|
|
9
|
+
.option(
|
|
10
|
+
"--force",
|
|
11
|
+
"overwrite existing project files; also bypass the unsupported-filesystem check (iCloud/Dropbox/etc)",
|
|
12
|
+
)
|
|
10
13
|
.action(async (opts) => {
|
|
11
14
|
const dir = program.opts().dir;
|
|
12
15
|
try {
|
package/src/commands/nuke.ts
CHANGED
|
@@ -1,56 +1,23 @@
|
|
|
1
|
+
import { rm } from "node:fs/promises";
|
|
1
2
|
import ansis from "ansis";
|
|
2
3
|
import type { Command } from "commander";
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import {
|
|
5
|
+
CONTEXT_DIR,
|
|
6
|
+
getContextDir,
|
|
7
|
+
SCHEDULES_DIR,
|
|
8
|
+
TASKS_DIR,
|
|
9
|
+
THREADS_DIR,
|
|
10
|
+
} from "../constants.ts";
|
|
11
|
+
import { deleteAllSchedules } from "../schedules/store.ts";
|
|
12
|
+
import { deleteAllTasks } from "../tasks/store.ts";
|
|
13
|
+
import { deleteAllThreads } from "../threads/store.ts";
|
|
10
14
|
import { logger } from "../utils/logger.ts";
|
|
11
|
-
import {
|
|
15
|
+
import { listWorkers } from "../workers/store.ts";
|
|
12
16
|
|
|
13
17
|
type NukeScope = "context" | "tasks" | "schedules" | "threads" | "all";
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
tasks: ["tasks"],
|
|
18
|
-
schedules: ["schedules"],
|
|
19
|
-
threads: ["threads", "interactions"],
|
|
20
|
-
all: [
|
|
21
|
-
"context_items",
|
|
22
|
-
"embeddings",
|
|
23
|
-
"tasks",
|
|
24
|
-
"schedules",
|
|
25
|
-
"threads",
|
|
26
|
-
"interactions",
|
|
27
|
-
"daemon_state",
|
|
28
|
-
],
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
async function countRows(conn: DbConnection, table: string): Promise<number> {
|
|
32
|
-
const row = await conn.queryGet<{ cnt: number }>(
|
|
33
|
-
`SELECT COUNT(*) AS cnt FROM ${table}`,
|
|
34
|
-
);
|
|
35
|
-
return row ? Number(row.cnt) : 0;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function printDryRun(scope: NukeScope, counts: Record<string, number>) {
|
|
39
|
-
console.log(ansis.red.bold(`Nuke scope: ${scope}`));
|
|
40
|
-
console.log("Would delete:");
|
|
41
|
-
const nameWidth = Math.max(...Object.keys(counts).map((k) => k.length));
|
|
42
|
-
for (const [table, count] of Object.entries(counts)) {
|
|
43
|
-
const padded = table.padEnd(nameWidth + 2);
|
|
44
|
-
console.log(` ${padded}${ansis.dim(`${count} rows`)}`);
|
|
45
|
-
}
|
|
46
|
-
console.log("");
|
|
47
|
-
console.log(
|
|
48
|
-
ansis.yellow("Re-run with --yes to confirm. This cannot be undone."),
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function ensureNoRunningWorkers(conn: DbConnection): Promise<boolean> {
|
|
53
|
-
const running = await listWorkers(conn, { status: "running" });
|
|
19
|
+
async function ensureNoRunningWorkers(projectDir: string): Promise<boolean> {
|
|
20
|
+
const running = await listWorkers(projectDir, { status: "running" });
|
|
54
21
|
if (running.length > 0) {
|
|
55
22
|
logger.error(
|
|
56
23
|
`${running.length} worker(s) running. Stop them first: botholomew worker stop <id>`,
|
|
@@ -63,33 +30,24 @@ async function ensureNoRunningWorkers(conn: DbConnection): Promise<boolean> {
|
|
|
63
30
|
return true;
|
|
64
31
|
}
|
|
65
32
|
|
|
66
|
-
async function runNuke(
|
|
67
|
-
// Not wrapped in a transaction: DuckDB's FK index checks on DELETE FROM
|
|
68
|
-
// threads inside a transaction see stale interactions rows even after
|
|
69
|
-
// DELETE FROM interactions ran in the same transaction. Each helper is
|
|
70
|
-
// already a small sequence of statements, so auto-commit is fine for a
|
|
71
|
-
// destructive dev-time tool.
|
|
33
|
+
async function runNuke(projectDir: string, scope: NukeScope): Promise<void> {
|
|
72
34
|
if (scope === "context" || scope === "all") {
|
|
73
|
-
|
|
74
|
-
logger.success(
|
|
75
|
-
`Deleted ${contextItems} context_items, ${embeddings} embeddings`,
|
|
76
|
-
);
|
|
35
|
+
await rm(getContextDir(projectDir), { recursive: true, force: true });
|
|
36
|
+
logger.success(`Removed ${CONTEXT_DIR}/ directory`);
|
|
77
37
|
}
|
|
78
38
|
if (scope === "tasks" || scope === "all") {
|
|
79
|
-
const n = await deleteAllTasks(
|
|
80
|
-
logger.success(`Deleted ${n}
|
|
39
|
+
const n = await deleteAllTasks(projectDir);
|
|
40
|
+
logger.success(`Deleted ${n} task file(s) from ${TASKS_DIR}/`);
|
|
81
41
|
}
|
|
82
42
|
if (scope === "schedules" || scope === "all") {
|
|
83
|
-
const n = await deleteAllSchedules(
|
|
84
|
-
logger.success(`Deleted ${n}
|
|
43
|
+
const n = await deleteAllSchedules(projectDir);
|
|
44
|
+
logger.success(`Deleted ${n} schedule file(s) from ${SCHEDULES_DIR}/`);
|
|
85
45
|
}
|
|
86
46
|
if (scope === "threads" || scope === "all") {
|
|
87
|
-
const { threads, interactions } = await deleteAllThreads(
|
|
88
|
-
logger.success(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const n = await deleteAllDaemonState(conn);
|
|
92
|
-
logger.success(`Deleted ${n} daemon_state entries`);
|
|
47
|
+
const { threads, interactions } = await deleteAllThreads(projectDir);
|
|
48
|
+
logger.success(
|
|
49
|
+
`Deleted ${threads} threads (${interactions} interactions) from ${THREADS_DIR}/`,
|
|
50
|
+
);
|
|
93
51
|
}
|
|
94
52
|
}
|
|
95
53
|
|
|
@@ -103,50 +61,59 @@ function registerScope(
|
|
|
103
61
|
.command(scope)
|
|
104
62
|
.description(description)
|
|
105
63
|
.option("-y, --yes", "confirm the deletion (required)")
|
|
106
|
-
.action((opts) =>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const tables = TABLES_BY_SCOPE[scope];
|
|
112
|
-
const counts: Record<string, number> = {};
|
|
113
|
-
for (const t of tables) {
|
|
114
|
-
counts[t] = await countRows(conn, t);
|
|
115
|
-
}
|
|
64
|
+
.action(async (opts) => {
|
|
65
|
+
const dir = program.opts().dir;
|
|
66
|
+
if (!(await ensureNoRunningWorkers(dir))) {
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
116
69
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
70
|
+
if (!opts.yes) {
|
|
71
|
+
console.log(ansis.red.bold(`Nuke scope: ${scope}`));
|
|
72
|
+
console.log(
|
|
73
|
+
ansis.yellow(
|
|
74
|
+
`Re-run with --yes to confirm. This will delete files on disk; cannot be undone.`,
|
|
75
|
+
),
|
|
76
|
+
);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
121
79
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
);
|
|
80
|
+
await runNuke(dir, scope);
|
|
81
|
+
});
|
|
125
82
|
}
|
|
126
83
|
|
|
127
84
|
export function registerNukeCommand(program: Command) {
|
|
128
85
|
const nuke = program
|
|
129
86
|
.command("nuke")
|
|
130
|
-
.description("Bulk-erase sections of the
|
|
87
|
+
.description("Bulk-erase sections of the project");
|
|
131
88
|
|
|
132
89
|
registerScope(
|
|
133
90
|
program,
|
|
134
91
|
nuke,
|
|
135
92
|
"context",
|
|
136
|
-
|
|
93
|
+
`Erase the entire ${CONTEXT_DIR}/ directory (user-curated knowledge)`,
|
|
94
|
+
);
|
|
95
|
+
registerScope(
|
|
96
|
+
program,
|
|
97
|
+
nuke,
|
|
98
|
+
"tasks",
|
|
99
|
+
`Delete all task files in ${TASKS_DIR}/`,
|
|
100
|
+
);
|
|
101
|
+
registerScope(
|
|
102
|
+
program,
|
|
103
|
+
nuke,
|
|
104
|
+
"schedules",
|
|
105
|
+
`Delete all schedule files in ${SCHEDULES_DIR}/`,
|
|
137
106
|
);
|
|
138
|
-
registerScope(program, nuke, "tasks", "Erase all tasks");
|
|
139
|
-
registerScope(program, nuke, "schedules", "Erase all schedules");
|
|
140
107
|
registerScope(
|
|
141
108
|
program,
|
|
142
109
|
nuke,
|
|
143
110
|
"threads",
|
|
144
|
-
|
|
111
|
+
`Delete all conversation history in ${THREADS_DIR}/`,
|
|
145
112
|
);
|
|
146
113
|
registerScope(
|
|
147
114
|
program,
|
|
148
115
|
nuke,
|
|
149
116
|
"all",
|
|
150
|
-
"Erase
|
|
117
|
+
"Erase all agent-writable data: context/, tasks/, schedules/, threads/",
|
|
151
118
|
);
|
|
152
119
|
}
|
package/src/commands/schedule.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import ansis from "ansis";
|
|
2
2
|
import type { Command } from "commander";
|
|
3
|
-
import type { Schedule } from "../
|
|
3
|
+
import type { Schedule } from "../schedules/schema.ts";
|
|
4
4
|
import {
|
|
5
5
|
createSchedule,
|
|
6
6
|
deleteSchedule,
|
|
7
7
|
getSchedule,
|
|
8
8
|
listSchedules,
|
|
9
|
+
markScheduleRun,
|
|
9
10
|
updateSchedule,
|
|
10
|
-
} from "../
|
|
11
|
+
} from "../schedules/store.ts";
|
|
11
12
|
import { logger } from "../utils/logger.ts";
|
|
12
|
-
import { withDb } from "./with-db.ts";
|
|
13
13
|
|
|
14
14
|
export function registerScheduleCommand(program: Command) {
|
|
15
15
|
const schedule = program.command("schedule").description("Manage schedules");
|
|
@@ -21,31 +21,22 @@ export function registerScheduleCommand(program: Command) {
|
|
|
21
21
|
.option("--disabled", "show only disabled schedules")
|
|
22
22
|
.option("-l, --limit <n>", "max number of schedules", Number.parseInt)
|
|
23
23
|
.option("-o, --offset <n>", "skip first N schedules", Number.parseInt)
|
|
24
|
-
.action((opts) =>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
logger.dim("No schedules found.");
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
for (const s of schedules) {
|
|
45
|
-
printSchedule(s);
|
|
46
|
-
}
|
|
47
|
-
}),
|
|
48
|
-
);
|
|
24
|
+
.action(async (opts) => {
|
|
25
|
+
const dir = program.opts().dir;
|
|
26
|
+
const filters: { enabled?: boolean; limit?: number; offset?: number } = {
|
|
27
|
+
limit: opts.limit,
|
|
28
|
+
offset: opts.offset,
|
|
29
|
+
};
|
|
30
|
+
if (opts.enabled) filters.enabled = true;
|
|
31
|
+
if (opts.disabled) filters.enabled = false;
|
|
32
|
+
|
|
33
|
+
const schedules = await listSchedules(dir, filters);
|
|
34
|
+
if (schedules.length === 0) {
|
|
35
|
+
logger.dim("No schedules found.");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
for (const s of schedules) printSchedule(s);
|
|
39
|
+
});
|
|
49
40
|
|
|
50
41
|
schedule
|
|
51
42
|
.command("add <name>")
|
|
@@ -55,116 +46,108 @@ export function registerScheduleCommand(program: Command) {
|
|
|
55
46
|
"how often to run (e.g. 'every morning')",
|
|
56
47
|
)
|
|
57
48
|
.option("--description <text>", "schedule description", "")
|
|
58
|
-
.action((name, opts) =>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
);
|
|
49
|
+
.action(async (name, opts) => {
|
|
50
|
+
const dir = program.opts().dir;
|
|
51
|
+
const s = await createSchedule(dir, {
|
|
52
|
+
name,
|
|
53
|
+
description: opts.description,
|
|
54
|
+
frequency: opts.frequency,
|
|
55
|
+
});
|
|
56
|
+
logger.success(`Created schedule: ${s.name} (${s.id})`);
|
|
57
|
+
});
|
|
68
58
|
|
|
69
59
|
schedule
|
|
70
60
|
.command("view <id>")
|
|
71
61
|
.description("View schedule details")
|
|
72
|
-
.action((id) =>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
);
|
|
62
|
+
.action(async (id) => {
|
|
63
|
+
const dir = program.opts().dir;
|
|
64
|
+
const s = await getSchedule(dir, id);
|
|
65
|
+
if (!s) {
|
|
66
|
+
logger.error(`Schedule not found: ${id}`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
printScheduleDetail(s);
|
|
70
|
+
});
|
|
82
71
|
|
|
83
72
|
schedule
|
|
84
73
|
.command("enable <id>")
|
|
85
74
|
.description("Enable a schedule")
|
|
86
|
-
.action((id) =>
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
);
|
|
75
|
+
.action(async (id) => {
|
|
76
|
+
const dir = program.opts().dir;
|
|
77
|
+
const s = await updateSchedule(dir, id, { enabled: true });
|
|
78
|
+
if (!s) {
|
|
79
|
+
logger.error(`Schedule not found: ${id}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
logger.success(`Enabled schedule: ${s.name}`);
|
|
83
|
+
});
|
|
96
84
|
|
|
97
85
|
schedule
|
|
98
86
|
.command("disable <id>")
|
|
99
87
|
.description("Disable a schedule")
|
|
100
|
-
.action((id) =>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
);
|
|
88
|
+
.action(async (id) => {
|
|
89
|
+
const dir = program.opts().dir;
|
|
90
|
+
const s = await updateSchedule(dir, id, { enabled: false });
|
|
91
|
+
if (!s) {
|
|
92
|
+
logger.error(`Schedule not found: ${id}`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
logger.success(`Disabled schedule: ${s.name}`);
|
|
96
|
+
});
|
|
110
97
|
|
|
111
98
|
schedule
|
|
112
99
|
.command("delete <id>")
|
|
113
100
|
.description("Delete a schedule")
|
|
114
|
-
.action((id) =>
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
);
|
|
101
|
+
.action(async (id) => {
|
|
102
|
+
const dir = program.opts().dir;
|
|
103
|
+
const deleted = await deleteSchedule(dir, id);
|
|
104
|
+
if (!deleted) {
|
|
105
|
+
logger.error(`Schedule not found: ${id}`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
logger.success(`Deleted schedule: ${id}`);
|
|
109
|
+
});
|
|
124
110
|
|
|
125
111
|
schedule
|
|
126
112
|
.command("trigger <id>")
|
|
127
113
|
.description("Manually trigger a schedule (creates tasks immediately)")
|
|
128
|
-
.action((id) =>
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
createdIds.push(t.id);
|
|
160
|
-
logger.success(`Created task: ${t.name} (${t.id})`);
|
|
161
|
-
}
|
|
114
|
+
.action(async (id) => {
|
|
115
|
+
const dir = program.opts().dir;
|
|
116
|
+
const s = await getSchedule(dir, id);
|
|
117
|
+
if (!s) {
|
|
118
|
+
logger.error(`Schedule not found: ${id}`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const { evaluateSchedule } = await import("../worker/schedules.ts");
|
|
123
|
+
const { loadConfig } = await import("../config/loader.ts");
|
|
124
|
+
const { createTask } = await import("../tasks/store.ts");
|
|
125
|
+
|
|
126
|
+
const config = await loadConfig(dir);
|
|
127
|
+
const evaluation = await evaluateSchedule(config, s);
|
|
128
|
+
|
|
129
|
+
if (evaluation.tasksToCreate.length === 0) {
|
|
130
|
+
logger.dim("Schedule evaluated but produced no tasks.");
|
|
131
|
+
} else {
|
|
132
|
+
const createdIds: string[] = [];
|
|
133
|
+
for (const taskDef of evaluation.tasksToCreate) {
|
|
134
|
+
const blockedBy = (taskDef.depends_on ?? [])
|
|
135
|
+
.map((i: number) => createdIds[i])
|
|
136
|
+
.filter(Boolean) as string[];
|
|
137
|
+
const t = await createTask(dir, {
|
|
138
|
+
name: taskDef.name,
|
|
139
|
+
description: taskDef.description,
|
|
140
|
+
priority: taskDef.priority,
|
|
141
|
+
blocked_by: blockedBy,
|
|
142
|
+
});
|
|
143
|
+
createdIds.push(t.id);
|
|
144
|
+
logger.success(`Created task: ${t.name} (${t.id})`);
|
|
162
145
|
}
|
|
146
|
+
}
|
|
163
147
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
);
|
|
148
|
+
await markScheduleRun(dir, s.id);
|
|
149
|
+
logger.info(`Marked schedule "${s.name}" as run.`);
|
|
150
|
+
});
|
|
168
151
|
}
|
|
169
152
|
|
|
170
153
|
function enabledColor(enabled: boolean): string {
|
|
@@ -173,9 +156,7 @@ function enabledColor(enabled: boolean): string {
|
|
|
173
156
|
|
|
174
157
|
function printSchedule(s: Schedule) {
|
|
175
158
|
const id = ansis.dim(s.id);
|
|
176
|
-
const lastRun = s.last_run_at
|
|
177
|
-
? s.last_run_at.toISOString()
|
|
178
|
-
: ansis.dim("never");
|
|
159
|
+
const lastRun = s.last_run_at ?? ansis.dim("never");
|
|
179
160
|
console.log(
|
|
180
161
|
` ${id} ${enabledColor(s.enabled)} ${s.frequency} ${s.name} (last: ${lastRun})`,
|
|
181
162
|
);
|
|
@@ -187,9 +168,7 @@ function printScheduleDetail(s: Schedule) {
|
|
|
187
168
|
console.log(` Status: ${enabledColor(s.enabled)}`);
|
|
188
169
|
console.log(` Frequency: ${s.frequency}`);
|
|
189
170
|
if (s.description) console.log(` Description: ${s.description}`);
|
|
190
|
-
console.log(
|
|
191
|
-
|
|
192
|
-
);
|
|
193
|
-
console.log(` Created: ${s.created_at.toISOString()}`);
|
|
194
|
-
console.log(` Updated: ${s.updated_at.toISOString()}`);
|
|
171
|
+
console.log(` Last run: ${s.last_run_at ?? ansis.dim("never")}`);
|
|
172
|
+
console.log(` Created: ${s.created_at}`);
|
|
173
|
+
console.log(` Updated: ${s.updated_at}`);
|
|
195
174
|
}
|
package/src/commands/skill.ts
CHANGED
|
@@ -13,7 +13,7 @@ export function registerSkillCommand(program: Command) {
|
|
|
13
13
|
|
|
14
14
|
skill
|
|
15
15
|
.command("validate [file]")
|
|
16
|
-
.description("Validate skill files in
|
|
16
|
+
.description("Validate skill files in skills/")
|
|
17
17
|
.action(async (file?: string) => {
|
|
18
18
|
const dir = program.opts().dir;
|
|
19
19
|
|
|
@@ -26,7 +26,7 @@ export function registerSkillCommand(program: Command) {
|
|
|
26
26
|
|
|
27
27
|
skill
|
|
28
28
|
.command("list")
|
|
29
|
-
.description("List all skills loaded from
|
|
29
|
+
.description("List all skills loaded from skills/")
|
|
30
30
|
.option("-l, --limit <n>", "max number of skills", Number.parseInt)
|
|
31
31
|
.option("-o, --offset <n>", "skip first N skills", Number.parseInt)
|
|
32
32
|
.action(async (opts: { limit?: number; offset?: number }) => {
|