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 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
- - Incoming tasks from the platform are stored as `TASK.md` files in a local `tasks/` directory.
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
 
@@ -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
- const tasksWithStatus = tasks.map((task) => ({
83
- ...task,
84
- status: getTaskStatus(task.frontmatter.id),
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 taskDir = getTaskDir(config.projectRoot, params.id);
92
+ const id = randomUUID();
93
+ const taskDir = getTaskDir(config.projectRoot, id);
91
94
  const task = {
92
95
  frontmatter: {
93
- id: params.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 { ok: true, task_id: params.id };
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 { ok: true, task_id: params.id };
133
+ return flattenTask(existing, getTaskStatus(params.id));
131
134
  }
132
135
  case "task.delete": {
133
136
  const params = request.params;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "palmier",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Palmier agent CLI - provisions, executes tasks, and serves NATS RPC",
5
5
  "license": "ISC",
6
6
  "author": "Hongxu Cai",
@@ -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 { RpcMessage, TaskWithStatus } from "../types.js";
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
- const tasksWithStatus: TaskWithStatus[] = tasks.map((task) => ({
94
- ...task,
95
- status: getTaskStatus(task.frontmatter.id),
96
- }));
97
- return { tasks: tasksWithStatus };
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: Array<{ type: "cron" | "once"; value: string }>;
106
- requires_confirmation: boolean;
107
- suppress_permissions: boolean;
108
- enabled: boolean;
109
- body: string;
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 taskDir = getTaskDir(config.projectRoot, params.id);
116
+ const id = randomUUID();
117
+ const taskDir = getTaskDir(config.projectRoot, id);
113
118
  const task = {
114
119
  frontmatter: {
115
- id: params.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 { ok: true, task_id: params.id };
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 { ok: true, task_id: params.id };
169
+ return flattenTask(existing, getTaskStatus(params.id));
165
170
  }
166
171
 
167
172
  case "task.delete": {