palmier 0.1.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.
@@ -0,0 +1,180 @@
1
+ import { execSync } from "child_process";
2
+ import * as fs from "fs";
3
+ import { StringCodec } from "nats";
4
+ import { loadConfig } from "../config.js";
5
+ import { connectNats } from "../nats-client.js";
6
+ import { listTasks, parseTaskFile, writeTaskFile, getTaskDir } from "../task.js";
7
+ import { installTaskTimer, removeTaskTimer, getTaskStatus } from "../systemd.js";
8
+ /**
9
+ * Start the persistent NATS RPC handler.
10
+ */
11
+ export async function serveCommand() {
12
+ const config = loadConfig();
13
+ const nc = await connectNats(config);
14
+ const sc = StringCodec();
15
+ const subject = `user.${config.userId}.agent.${config.agentId}.rpc.*`;
16
+ console.log(`Subscribing to: ${subject}`);
17
+ const sub = nc.subscribe(subject);
18
+ // Graceful shutdown
19
+ const shutdown = async () => {
20
+ console.log("Shutting down...");
21
+ sub.unsubscribe();
22
+ await nc.drain();
23
+ process.exit(0);
24
+ };
25
+ process.on("SIGINT", shutdown);
26
+ process.on("SIGTERM", shutdown);
27
+ // On startup, clean up orphaned pending-hooks keys for this agent
28
+ try {
29
+ const js = nc.jetstream();
30
+ const kv = await js.views.kv("pending-hooks");
31
+ const keys = await kv.keys();
32
+ for await (const key of keys) {
33
+ if (key.startsWith(`${config.agentId}.`)) {
34
+ console.log(`Cleaning up orphaned hook key: ${key}`);
35
+ await kv.delete(key);
36
+ }
37
+ }
38
+ }
39
+ catch (err) {
40
+ console.error(`Warning: could not clean up pending-hooks KV: ${err}`);
41
+ }
42
+ console.log("Agent serving. Waiting for RPC messages...");
43
+ for await (const msg of sub) {
44
+ let request;
45
+ try {
46
+ request = JSON.parse(sc.decode(msg.data));
47
+ }
48
+ catch {
49
+ console.error("Failed to parse RPC message");
50
+ if (msg.reply) {
51
+ msg.respond(sc.encode(JSON.stringify({ error: "Invalid JSON" })));
52
+ }
53
+ continue;
54
+ }
55
+ console.log(`RPC: ${request.method}`);
56
+ let response;
57
+ try {
58
+ response = await handleRpc(request);
59
+ }
60
+ catch (err) {
61
+ console.error(`RPC error (${request.method}):`, err);
62
+ response = { error: String(err) };
63
+ }
64
+ if (msg.reply) {
65
+ msg.respond(sc.encode(JSON.stringify(response)));
66
+ }
67
+ }
68
+ async function handleRpc(request) {
69
+ switch (request.method) {
70
+ case "task.list": {
71
+ const tasks = listTasks(config.projectRoot);
72
+ const tasksWithStatus = tasks.map((task) => ({
73
+ ...task,
74
+ status: getTaskStatus(task.frontmatter.id),
75
+ }));
76
+ return { tasks: tasksWithStatus };
77
+ }
78
+ case "task.create": {
79
+ const params = request.params;
80
+ const taskDir = getTaskDir(config.projectRoot, params.id);
81
+ const task = {
82
+ frontmatter: {
83
+ id: params.id,
84
+ name: params.name,
85
+ user_prompt: params.user_prompt,
86
+ triggers: params.triggers || [],
87
+ requires_confirmation: params.requires_confirmation ?? true,
88
+ suppress_permissions: params.suppress_permissions ?? false,
89
+ enabled: params.enabled ?? true,
90
+ },
91
+ body: params.body || "",
92
+ };
93
+ writeTaskFile(taskDir, task);
94
+ installTaskTimer(config, task);
95
+ return { ok: true, task_id: params.id };
96
+ }
97
+ case "task.update": {
98
+ const params = request.params;
99
+ const taskDir = getTaskDir(config.projectRoot, params.id);
100
+ const existing = parseTaskFile(taskDir);
101
+ // Merge updates
102
+ if (params.name !== undefined)
103
+ existing.frontmatter.name = params.name;
104
+ if (params.user_prompt !== undefined)
105
+ existing.frontmatter.user_prompt = params.user_prompt;
106
+ if (params.triggers !== undefined)
107
+ existing.frontmatter.triggers = params.triggers;
108
+ if (params.requires_confirmation !== undefined)
109
+ existing.frontmatter.requires_confirmation = params.requires_confirmation;
110
+ if (params.suppress_permissions !== undefined)
111
+ existing.frontmatter.suppress_permissions = params.suppress_permissions;
112
+ if (params.enabled !== undefined)
113
+ existing.frontmatter.enabled = params.enabled;
114
+ if (params.body !== undefined)
115
+ existing.body = params.body;
116
+ writeTaskFile(taskDir, existing);
117
+ // Reinstall timer with updated config
118
+ removeTaskTimer(params.id);
119
+ installTaskTimer(config, existing);
120
+ return { ok: true, task_id: params.id };
121
+ }
122
+ case "task.delete": {
123
+ const params = request.params;
124
+ const taskDir = getTaskDir(config.projectRoot, params.id);
125
+ removeTaskTimer(params.id);
126
+ // Remove task directory
127
+ if (fs.existsSync(taskDir)) {
128
+ fs.rmSync(taskDir, { recursive: true, force: true });
129
+ }
130
+ return { ok: true, task_id: params.id };
131
+ }
132
+ case "task.generate": {
133
+ const params = request.params;
134
+ try {
135
+ const output = execSync(`claude -p "${params.prompt.replace(/"/g, '\\"')}"`, {
136
+ encoding: "utf-8",
137
+ cwd: config.projectRoot,
138
+ timeout: 120_000,
139
+ });
140
+ return { ok: true, output };
141
+ }
142
+ catch (err) {
143
+ const error = err;
144
+ return { error: "claude command failed", stdout: error.stdout, stderr: error.stderr };
145
+ }
146
+ }
147
+ case "task.run": {
148
+ const params = request.params;
149
+ const serviceName = `palmier-task-${params.id}.service`;
150
+ try {
151
+ execSync(`systemctl --user start ${serviceName}`, { stdio: "inherit" });
152
+ return { ok: true, task_id: params.id };
153
+ }
154
+ catch (err) {
155
+ return { error: `Failed to start task service: ${err}` };
156
+ }
157
+ }
158
+ case "task.status": {
159
+ const params = request.params;
160
+ const status = getTaskStatus(params.id);
161
+ return { task_id: params.id, status };
162
+ }
163
+ case "task.logs": {
164
+ const params = request.params;
165
+ const serviceName = `palmier-task-${params.id}.service`;
166
+ try {
167
+ const logs = execSync(`journalctl --user -u ${serviceName} -n 100 --no-pager`, { encoding: "utf-8" });
168
+ return { task_id: params.id, logs };
169
+ }
170
+ catch (err) {
171
+ const error = err;
172
+ return { task_id: params.id, logs: error.stdout || "", error: error.stderr };
173
+ }
174
+ }
175
+ default:
176
+ return { error: `Unknown method: ${request.method}` };
177
+ }
178
+ }
179
+ }
180
+ //# sourceMappingURL=serve.js.map
@@ -0,0 +1,15 @@
1
+ import type { AgentConfig } from "./types.js";
2
+ declare const CONFIG_DIR: string;
3
+ declare const CONFIG_FILE: string;
4
+ /**
5
+ * Load agent configuration from ~/.config/palmier/agent.json.
6
+ * Throws if the file is missing or invalid.
7
+ */
8
+ export declare function loadConfig(): AgentConfig;
9
+ /**
10
+ * Persist agent configuration to ~/.config/palmier/agent.json.
11
+ * Creates parent directories if needed.
12
+ */
13
+ export declare function saveConfig(config: AgentConfig): void;
14
+ export { CONFIG_DIR, CONFIG_FILE };
15
+ //# sourceMappingURL=config.d.ts.map
package/dist/config.js ADDED
@@ -0,0 +1,31 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { homedir } from "os";
4
+ const CONFIG_DIR = path.join(homedir(), ".config", "palmier");
5
+ const CONFIG_FILE = path.join(CONFIG_DIR, "agent.json");
6
+ /**
7
+ * Load agent configuration from ~/.config/palmier/agent.json.
8
+ * Throws if the file is missing or invalid.
9
+ */
10
+ export function loadConfig() {
11
+ if (!fs.existsSync(CONFIG_FILE)) {
12
+ throw new Error("Agent not provisioned. Run `palmier init --token <token>` first.\n" +
13
+ `Expected config at: ${CONFIG_FILE}`);
14
+ }
15
+ const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
16
+ const config = JSON.parse(raw);
17
+ if (!config.agentId || !config.natsUrl || !config.natsToken) {
18
+ throw new Error("Invalid agent config: missing required fields (agentId, natsUrl, natsToken)");
19
+ }
20
+ return config;
21
+ }
22
+ /**
23
+ * Persist agent configuration to ~/.config/palmier/agent.json.
24
+ * Creates parent directories if needed.
25
+ */
26
+ export function saveConfig(config) {
27
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
28
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
29
+ }
30
+ export { CONFIG_DIR, CONFIG_FILE };
31
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ import { Command } from "commander";
4
+ import { initCommand } from "./commands/init.js";
5
+ import { runCommand } from "./commands/run.js";
6
+ import { hookCommand } from "./commands/hook.js";
7
+ import { serveCommand } from "./commands/serve.js";
8
+ const program = new Command();
9
+ program
10
+ .name("palmier")
11
+ .description("Palmier agent CLI")
12
+ .version("0.1.0");
13
+ program
14
+ .command("init")
15
+ .description("Provision this agent with a provisioning token")
16
+ .requiredOption("--token <token>", "Provisioning token from palmier dashboard")
17
+ .action(async (options) => {
18
+ await initCommand({ token: options.token });
19
+ });
20
+ program
21
+ .command("run <task-id>")
22
+ .description("Execute a task by ID")
23
+ .action(async (taskId) => {
24
+ await runCommand(taskId);
25
+ });
26
+ program
27
+ .command("hook")
28
+ .description("Handle a Claude Code hook event (reads from stdin)")
29
+ .action(async () => {
30
+ await hookCommand();
31
+ });
32
+ program
33
+ .command("serve", { isDefault: true })
34
+ .description("Start the persistent NATS RPC handler")
35
+ .action(async () => {
36
+ await serveCommand();
37
+ });
38
+ program.parseAsync(process.argv).catch((err) => {
39
+ console.error(err);
40
+ process.exit(1);
41
+ });
42
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ import { type NatsConnection } from "nats";
2
+ import type { AgentConfig } from "./types.js";
3
+ /**
4
+ * Connect to NATS using the agent config's TCP URL and token auth.
5
+ */
6
+ export declare function connectNats(config: AgentConfig): Promise<NatsConnection>;
7
+ //# sourceMappingURL=nats-client.d.ts.map
@@ -0,0 +1,13 @@
1
+ import { connect } from "nats";
2
+ /**
3
+ * Connect to NATS using the agent config's TCP URL and token auth.
4
+ */
5
+ export async function connectNats(config) {
6
+ const nc = await connect({
7
+ servers: config.natsUrl,
8
+ token: config.natsToken,
9
+ });
10
+ console.log(`Connected to NATS at ${config.natsUrl}`);
11
+ return nc;
12
+ }
13
+ //# sourceMappingURL=nats-client.js.map
@@ -0,0 +1,24 @@
1
+ import type { AgentConfig } from "./types.js";
2
+ import type { ParsedTask, TaskStatus } from "./types.js";
3
+ /**
4
+ * Convert a cron expression (5-field) to a systemd OnCalendar string.
5
+ * Handles basic cron patterns: minute hour day-of-month month day-of-week
6
+ */
7
+ export declare function cronToOnCalendar(cron: string): string;
8
+ /**
9
+ * Install a systemd user timer + service for a task.
10
+ */
11
+ export declare function installTaskTimer(config: AgentConfig, task: ParsedTask): void;
12
+ /**
13
+ * Remove a task's systemd timer and service files.
14
+ */
15
+ export declare function removeTaskTimer(taskId: string): void;
16
+ /**
17
+ * Query systemd for task status information.
18
+ */
19
+ export declare function getTaskStatus(taskId: string): TaskStatus;
20
+ /**
21
+ * Run systemctl --user daemon-reload.
22
+ */
23
+ export declare function daemonReload(): void;
24
+ //# sourceMappingURL=systemd.d.ts.map
@@ -0,0 +1,205 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { homedir } from "os";
4
+ import { execSync } from "child_process";
5
+ const UNIT_DIR = path.join(homedir(), ".config", "systemd", "user");
6
+ function getTimerName(taskId) {
7
+ return `palmier-task-${taskId}.timer`;
8
+ }
9
+ function getServiceName(taskId) {
10
+ return `palmier-task-${taskId}.service`;
11
+ }
12
+ /**
13
+ * Convert a cron expression (5-field) to a systemd OnCalendar string.
14
+ * Handles basic cron patterns: minute hour day-of-month month day-of-week
15
+ */
16
+ export function cronToOnCalendar(cron) {
17
+ const parts = cron.trim().split(/\s+/);
18
+ if (parts.length !== 5) {
19
+ throw new Error(`Invalid cron expression (expected 5 fields): ${cron}`);
20
+ }
21
+ const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
22
+ // Map cron day-of-week names/numbers to systemd abbreviated names
23
+ const dowMap = {
24
+ "0": "Sun",
25
+ "1": "Mon",
26
+ "2": "Tue",
27
+ "3": "Wed",
28
+ "4": "Thu",
29
+ "5": "Fri",
30
+ "6": "Sat",
31
+ "7": "Sun",
32
+ };
33
+ // Convert day-of-week
34
+ let dow = dayOfWeek === "*" ? "*" : dayOfWeek;
35
+ if (dowMap[dow]) {
36
+ dow = dowMap[dow];
37
+ }
38
+ // Build OnCalendar string
39
+ // Format: DayOfWeek Year-Month-Day Hour:Minute:Second
40
+ const monthPart = month === "*" ? "*" : month.padStart(2, "0");
41
+ const dayPart = dayOfMonth === "*" ? "*" : dayOfMonth.padStart(2, "0");
42
+ const hourPart = hour === "*" ? "*" : hour.padStart(2, "0");
43
+ const minutePart = minute === "*" ? "*" : minute.padStart(2, "0");
44
+ if (dow === "*") {
45
+ return `*-${monthPart}-${dayPart} ${hourPart}:${minutePart}:00`;
46
+ }
47
+ return `${dow} *-${monthPart}-${dayPart} ${hourPart}:${minutePart}:00`;
48
+ }
49
+ /**
50
+ * Install a systemd user timer + service for a task.
51
+ */
52
+ export function installTaskTimer(config, task) {
53
+ fs.mkdirSync(UNIT_DIR, { recursive: true });
54
+ const taskId = task.frontmatter.id;
55
+ const serviceName = getServiceName(taskId);
56
+ const timerName = getTimerName(taskId);
57
+ // Determine the palmier binary path
58
+ const palmierBin = process.argv[1] || "palmier";
59
+ // Generate service unit
60
+ const serviceContent = `[Unit]
61
+ Description=Palmier Task: ${task.frontmatter.name || taskId}
62
+
63
+ [Service]
64
+ Type=oneshot
65
+ ExecStart=${palmierBin} run ${taskId}
66
+ WorkingDirectory=${config.projectRoot}
67
+ Environment=PATH=${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}
68
+ `;
69
+ // Generate timer unit with OnCalendar entries for each cron trigger
70
+ const onCalendarLines = [];
71
+ for (const trigger of task.frontmatter.triggers || []) {
72
+ if (trigger.type === "cron") {
73
+ onCalendarLines.push(`OnCalendar=${cronToOnCalendar(trigger.value)}`);
74
+ }
75
+ else if (trigger.type === "once") {
76
+ // "once" triggers use OnActiveSec or a specific timestamp
77
+ onCalendarLines.push(`OnActiveSec=${trigger.value}`);
78
+ }
79
+ }
80
+ const timerContent = `[Unit]
81
+ Description=Timer for Palmier Task: ${task.frontmatter.name || taskId}
82
+
83
+ [Timer]
84
+ ${onCalendarLines.join("\n")}
85
+ Persistent=true
86
+
87
+ [Install]
88
+ WantedBy=timers.target
89
+ `;
90
+ // Write unit files
91
+ fs.writeFileSync(path.join(UNIT_DIR, serviceName), serviceContent, "utf-8");
92
+ fs.writeFileSync(path.join(UNIT_DIR, timerName), timerContent, "utf-8");
93
+ // Reload and enable
94
+ daemonReload();
95
+ if (task.frontmatter.enabled) {
96
+ execSync(`systemctl --user enable --now ${timerName}`, { stdio: "inherit" });
97
+ }
98
+ else {
99
+ execSync(`systemctl --user enable ${timerName}`, { stdio: "inherit" });
100
+ }
101
+ }
102
+ /**
103
+ * Remove a task's systemd timer and service files.
104
+ */
105
+ export function removeTaskTimer(taskId) {
106
+ const timerName = getTimerName(taskId);
107
+ const serviceName = getServiceName(taskId);
108
+ // Stop and disable
109
+ try {
110
+ execSync(`systemctl --user stop ${timerName}`, { stdio: "inherit" });
111
+ }
112
+ catch {
113
+ // Timer might not be running
114
+ }
115
+ try {
116
+ execSync(`systemctl --user disable ${timerName}`, { stdio: "inherit" });
117
+ }
118
+ catch {
119
+ // Timer might not be enabled
120
+ }
121
+ // Remove unit files
122
+ const timerPath = path.join(UNIT_DIR, timerName);
123
+ const servicePath = path.join(UNIT_DIR, serviceName);
124
+ if (fs.existsSync(timerPath))
125
+ fs.unlinkSync(timerPath);
126
+ if (fs.existsSync(servicePath))
127
+ fs.unlinkSync(servicePath);
128
+ daemonReload();
129
+ }
130
+ /**
131
+ * Query systemd for task status information.
132
+ */
133
+ export function getTaskStatus(taskId) {
134
+ const timerName = getTimerName(taskId);
135
+ const serviceName = getServiceName(taskId);
136
+ let state = "inactive";
137
+ let lastRun;
138
+ let lastResult;
139
+ let nextRun;
140
+ // Check if timer is active
141
+ try {
142
+ const activeState = execSync(`systemctl --user is-active ${timerName}`, {
143
+ encoding: "utf-8",
144
+ }).trim();
145
+ if (activeState === "active") {
146
+ state = "active";
147
+ }
148
+ }
149
+ catch {
150
+ // Not active
151
+ }
152
+ // Check if service has failed
153
+ try {
154
+ const serviceState = execSync(`systemctl --user is-failed ${serviceName}`, {
155
+ encoding: "utf-8",
156
+ }).trim();
157
+ if (serviceState === "failed") {
158
+ state = "failed";
159
+ }
160
+ }
161
+ catch {
162
+ // Not failed
163
+ }
164
+ // Get last run time and result
165
+ try {
166
+ const props = execSync(`systemctl --user show ${serviceName} --property=ExecMainStartTimestamp,ExecMainStatus`, { encoding: "utf-8" });
167
+ for (const line of props.split("\n")) {
168
+ if (line.startsWith("ExecMainStartTimestamp=") && line.trim() !== "ExecMainStartTimestamp=") {
169
+ const val = line.split("=", 2)[1].trim();
170
+ if (val)
171
+ lastRun = val;
172
+ }
173
+ if (line.startsWith("ExecMainStatus=")) {
174
+ const val = parseInt(line.split("=", 2)[1].trim(), 10);
175
+ if (!isNaN(val))
176
+ lastResult = val;
177
+ }
178
+ }
179
+ }
180
+ catch {
181
+ // Couldn't get properties
182
+ }
183
+ // Get next run time from timer
184
+ try {
185
+ const timerProps = execSync(`systemctl --user show ${timerName} --property=NextElapseUSecRealtime`, { encoding: "utf-8" });
186
+ for (const line of timerProps.split("\n")) {
187
+ if (line.startsWith("NextElapseUSecRealtime=")) {
188
+ const val = line.split("=", 2)[1].trim();
189
+ if (val)
190
+ nextRun = val;
191
+ }
192
+ }
193
+ }
194
+ catch {
195
+ // Couldn't get next run
196
+ }
197
+ return { state, lastRun, lastResult, nextRun };
198
+ }
199
+ /**
200
+ * Run systemctl --user daemon-reload.
201
+ */
202
+ export function daemonReload() {
203
+ execSync("systemctl --user daemon-reload", { stdio: "inherit" });
204
+ }
205
+ //# sourceMappingURL=systemd.js.map
package/dist/task.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import type { ParsedTask } from "./types.js";
2
+ /**
3
+ * Parse a TASK.md file from the given task directory.
4
+ */
5
+ export declare function parseTaskFile(taskDir: string): ParsedTask;
6
+ /**
7
+ * Write a TASK.md file to the given task directory.
8
+ * Creates the directory if it doesn't exist.
9
+ */
10
+ export declare function writeTaskFile(taskDir: string, task: ParsedTask): void;
11
+ /**
12
+ * List all tasks from projectRoot/tasks/{id}/TASK.md.
13
+ */
14
+ export declare function listTasks(projectRoot: string): ParsedTask[];
15
+ /**
16
+ * Get the directory path for a task by its ID.
17
+ */
18
+ export declare function getTaskDir(projectRoot: string, taskId: string): string;
19
+ //# sourceMappingURL=task.d.ts.map
package/dist/task.js ADDED
@@ -0,0 +1,74 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
4
+ /**
5
+ * Parse a TASK.md file from the given task directory.
6
+ */
7
+ export function parseTaskFile(taskDir) {
8
+ const filePath = path.join(taskDir, "TASK.md");
9
+ if (!fs.existsSync(filePath)) {
10
+ throw new Error(`TASK.md not found at: ${filePath}`);
11
+ }
12
+ const content = fs.readFileSync(filePath, "utf-8");
13
+ return parseTaskContent(content);
14
+ }
15
+ /**
16
+ * Parse TASK.md content string into frontmatter + body.
17
+ */
18
+ function parseTaskContent(content) {
19
+ const fmRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
20
+ const match = content.match(fmRegex);
21
+ if (!match) {
22
+ throw new Error("TASK.md is missing valid YAML frontmatter delimiters (---)");
23
+ }
24
+ const frontmatter = parseYaml(match[1]);
25
+ const body = (match[2] || "").trim();
26
+ if (!frontmatter.id) {
27
+ throw new Error("TASK.md frontmatter must include at least: id");
28
+ }
29
+ return { frontmatter, body };
30
+ }
31
+ /**
32
+ * Write a TASK.md file to the given task directory.
33
+ * Creates the directory if it doesn't exist.
34
+ */
35
+ export function writeTaskFile(taskDir, task) {
36
+ fs.mkdirSync(taskDir, { recursive: true });
37
+ const yamlStr = stringifyYaml(task.frontmatter).trim();
38
+ const content = `---\n${yamlStr}\n---\n${task.body}\n`;
39
+ const filePath = path.join(taskDir, "TASK.md");
40
+ fs.writeFileSync(filePath, content, "utf-8");
41
+ }
42
+ /**
43
+ * List all tasks from projectRoot/tasks/{id}/TASK.md.
44
+ */
45
+ export function listTasks(projectRoot) {
46
+ const tasksDir = path.join(projectRoot, "tasks");
47
+ if (!fs.existsSync(tasksDir)) {
48
+ return [];
49
+ }
50
+ const entries = fs.readdirSync(tasksDir, { withFileTypes: true });
51
+ const tasks = [];
52
+ for (const entry of entries) {
53
+ if (!entry.isDirectory())
54
+ continue;
55
+ const taskDir = path.join(tasksDir, entry.name);
56
+ const taskFile = path.join(taskDir, "TASK.md");
57
+ if (!fs.existsSync(taskFile))
58
+ continue;
59
+ try {
60
+ tasks.push(parseTaskFile(taskDir));
61
+ }
62
+ catch (err) {
63
+ console.error(`Warning: failed to parse task in ${taskDir}: ${err}`);
64
+ }
65
+ }
66
+ return tasks;
67
+ }
68
+ /**
69
+ * Get the directory path for a task by its ID.
70
+ */
71
+ export function getTaskDir(projectRoot, taskId) {
72
+ return path.join(projectRoot, "tasks", taskId);
73
+ }
74
+ //# sourceMappingURL=task.js.map
@@ -0,0 +1,56 @@
1
+ export interface AgentConfig {
2
+ agentId: string;
3
+ userId: string;
4
+ natsUrl: string;
5
+ natsWsUrl: string;
6
+ natsToken: string;
7
+ projectRoot: string;
8
+ }
9
+ export interface TaskFrontmatter {
10
+ id: string;
11
+ name: string;
12
+ user_prompt: string;
13
+ triggers: Trigger[];
14
+ requires_confirmation: boolean;
15
+ suppress_permissions: boolean;
16
+ enabled: boolean;
17
+ }
18
+ export interface Trigger {
19
+ type: "cron" | "once";
20
+ value: string;
21
+ }
22
+ export interface ParsedTask {
23
+ frontmatter: TaskFrontmatter;
24
+ body: string;
25
+ }
26
+ export interface TaskStatus {
27
+ state: "active" | "inactive" | "failed";
28
+ lastRun?: string;
29
+ lastResult?: number;
30
+ nextRun?: string;
31
+ }
32
+ export interface TaskWithStatus extends ParsedTask {
33
+ status: TaskStatus;
34
+ }
35
+ export interface HookPayload {
36
+ type: "confirm" | "permission" | "input";
37
+ task_id: string;
38
+ hook_id: string;
39
+ agent_id: string;
40
+ user_id: string;
41
+ details: Record<string, unknown>;
42
+ status: string;
43
+ }
44
+ export interface ClaudeHookEvent {
45
+ hook_name: string;
46
+ session_id: string;
47
+ tool_name?: string;
48
+ tool_input?: Record<string, unknown>;
49
+ message?: string;
50
+ [key: string]: unknown;
51
+ }
52
+ export interface RpcMessage {
53
+ method: string;
54
+ params: Record<string, unknown>;
55
+ }
56
+ //# sourceMappingURL=types.d.ts.map
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map