palmier 0.1.5 → 0.1.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 +4 -1
- package/dist/commands/serve.js +13 -10
- package/package.json +1 -1
- package/src/commands/serve.ts +22 -17
package/README.md
CHANGED
|
@@ -63,7 +63,10 @@ cat ~/.config/palmier/agent.json
|
|
|
63
63
|
## How It Works
|
|
64
64
|
|
|
65
65
|
- The agent runs as a **systemd user service**, staying alive in the background.
|
|
66
|
-
-
|
|
66
|
+
- The persistent process (`palmier serve`) is a NATS RPC handler. It derives the RPC method from the NATS subject (e.g., `...rpc.task.create` → `task.create`) and treats the message body as request parameters.
|
|
67
|
+
- **Task IDs** are generated by the agent as UUIDs.
|
|
68
|
+
- All RPC responses (`task.list`, `task.create`, `task.update`) return **flat task objects** — frontmatter fields at the top level, not nested under a `frontmatter` key.
|
|
69
|
+
- Incoming tasks are stored as `TASK.md` files in a local `tasks/` directory.
|
|
67
70
|
- Task execution spawns **Claude Code in a PTY**, giving the AI full CLI access within the project.
|
|
68
71
|
- **Hooks** intercept Claude Code permission, confirmation, and input prompts, resolving them remotely via NATS KV so tasks can run unattended.
|
|
69
72
|
|
package/dist/commands/serve.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
1
2
|
import { execSync } from "child_process";
|
|
2
3
|
import * as fs from "fs";
|
|
3
4
|
import { StringCodec } from "nats";
|
|
@@ -75,25 +76,27 @@ export async function serveCommand() {
|
|
|
75
76
|
msg.respond(sc.encode(JSON.stringify(response)));
|
|
76
77
|
}
|
|
77
78
|
}
|
|
79
|
+
function flattenTask(task, status) {
|
|
80
|
+
return { ...task.frontmatter, body: task.body, ...(status != null ? { status } : {}) };
|
|
81
|
+
}
|
|
78
82
|
async function handleRpc(request) {
|
|
79
83
|
switch (request.method) {
|
|
80
84
|
case "task.list": {
|
|
81
85
|
const tasks = listTasks(config.projectRoot);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}));
|
|
86
|
-
return { tasks: tasksWithStatus };
|
|
86
|
+
return {
|
|
87
|
+
tasks: tasks.map((task) => flattenTask(task, getTaskStatus(task.frontmatter.id))),
|
|
88
|
+
};
|
|
87
89
|
}
|
|
88
90
|
case "task.create": {
|
|
89
91
|
const params = request.params;
|
|
90
|
-
const
|
|
92
|
+
const id = randomUUID();
|
|
93
|
+
const taskDir = getTaskDir(config.projectRoot, id);
|
|
91
94
|
const task = {
|
|
92
95
|
frontmatter: {
|
|
93
|
-
id
|
|
96
|
+
id,
|
|
94
97
|
name: params.name,
|
|
95
98
|
user_prompt: params.user_prompt,
|
|
96
|
-
triggers: params.triggers
|
|
99
|
+
triggers: params.triggers ?? [],
|
|
97
100
|
requires_confirmation: params.requires_confirmation ?? true,
|
|
98
101
|
suppress_permissions: params.suppress_permissions ?? false,
|
|
99
102
|
enabled: params.enabled ?? true,
|
|
@@ -102,7 +105,7 @@ export async function serveCommand() {
|
|
|
102
105
|
};
|
|
103
106
|
writeTaskFile(taskDir, task);
|
|
104
107
|
installTaskTimer(config, task);
|
|
105
|
-
return
|
|
108
|
+
return flattenTask(task);
|
|
106
109
|
}
|
|
107
110
|
case "task.update": {
|
|
108
111
|
const params = request.params;
|
|
@@ -127,7 +130,7 @@ export async function serveCommand() {
|
|
|
127
130
|
// Reinstall timer with updated config
|
|
128
131
|
removeTaskTimer(params.id);
|
|
129
132
|
installTaskTimer(config, existing);
|
|
130
|
-
return
|
|
133
|
+
return flattenTask(existing, getTaskStatus(params.id));
|
|
131
134
|
}
|
|
132
135
|
case "task.delete": {
|
|
133
136
|
const params = request.params;
|
package/package.json
CHANGED
package/src/commands/serve.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
1
2
|
import { execSync } from "child_process";
|
|
2
3
|
import * as fs from "fs";
|
|
3
4
|
import { StringCodec } from "nats";
|
|
@@ -5,7 +6,7 @@ import { loadConfig } from "../config.js";
|
|
|
5
6
|
import { connectNats } from "../nats-client.js";
|
|
6
7
|
import { listTasks, parseTaskFile, writeTaskFile, getTaskDir } from "../task.js";
|
|
7
8
|
import { installTaskTimer, removeTaskTimer, getTaskStatus } from "../systemd.js";
|
|
8
|
-
import type {
|
|
9
|
+
import type { ParsedTask, RpcMessage } from "../types.js";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Start the persistent NATS RPC handler.
|
|
@@ -86,36 +87,40 @@ export async function serveCommand(): Promise<void> {
|
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
function flattenTask(task: ParsedTask, status?: unknown) {
|
|
91
|
+
return { ...task.frontmatter, body: task.body, ...(status != null ? { status } : {}) };
|
|
92
|
+
}
|
|
93
|
+
|
|
89
94
|
async function handleRpc(request: RpcMessage): Promise<unknown> {
|
|
90
95
|
switch (request.method) {
|
|
91
96
|
case "task.list": {
|
|
92
97
|
const tasks = listTasks(config.projectRoot);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
return {
|
|
99
|
+
tasks: tasks.map((task) =>
|
|
100
|
+
flattenTask(task, getTaskStatus(task.frontmatter.id))
|
|
101
|
+
),
|
|
102
|
+
};
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
case "task.create": {
|
|
101
106
|
const params = request.params as {
|
|
102
|
-
id: string;
|
|
103
107
|
name: string;
|
|
104
108
|
user_prompt: string;
|
|
105
|
-
triggers
|
|
106
|
-
requires_confirmation
|
|
107
|
-
suppress_permissions
|
|
108
|
-
enabled
|
|
109
|
-
body
|
|
109
|
+
triggers?: Array<{ type: "cron" | "once"; value: string }>;
|
|
110
|
+
requires_confirmation?: boolean;
|
|
111
|
+
suppress_permissions?: boolean;
|
|
112
|
+
enabled?: boolean;
|
|
113
|
+
body?: string;
|
|
110
114
|
};
|
|
111
115
|
|
|
112
|
-
const
|
|
116
|
+
const id = randomUUID();
|
|
117
|
+
const taskDir = getTaskDir(config.projectRoot, id);
|
|
113
118
|
const task = {
|
|
114
119
|
frontmatter: {
|
|
115
|
-
id
|
|
120
|
+
id,
|
|
116
121
|
name: params.name,
|
|
117
122
|
user_prompt: params.user_prompt,
|
|
118
|
-
triggers: params.triggers
|
|
123
|
+
triggers: params.triggers ?? [],
|
|
119
124
|
requires_confirmation: params.requires_confirmation ?? true,
|
|
120
125
|
suppress_permissions: params.suppress_permissions ?? false,
|
|
121
126
|
enabled: params.enabled ?? true,
|
|
@@ -126,7 +131,7 @@ export async function serveCommand(): Promise<void> {
|
|
|
126
131
|
writeTaskFile(taskDir, task);
|
|
127
132
|
installTaskTimer(config, task);
|
|
128
133
|
|
|
129
|
-
return
|
|
134
|
+
return flattenTask(task);
|
|
130
135
|
}
|
|
131
136
|
|
|
132
137
|
case "task.update": {
|
|
@@ -161,7 +166,7 @@ export async function serveCommand(): Promise<void> {
|
|
|
161
166
|
removeTaskTimer(params.id);
|
|
162
167
|
installTaskTimer(config, existing);
|
|
163
168
|
|
|
164
|
-
return
|
|
169
|
+
return flattenTask(existing, getTaskStatus(params.id));
|
|
165
170
|
}
|
|
166
171
|
|
|
167
172
|
case "task.delete": {
|