palmier 0.1.4 → 0.1.6

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.
@@ -41,49 +41,64 @@ export async function serveCommand() {
41
41
  }
42
42
  console.log("Agent serving. Waiting for RPC messages...");
43
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" })));
44
+ // Derive RPC method from subject: ...rpc.<method parts>
45
+ const subjectTokens = msg.subject.split(".");
46
+ const rpcIdx = subjectTokens.indexOf("rpc");
47
+ const method = rpcIdx >= 0 ? subjectTokens.slice(rpcIdx + 1).join(".") : "";
48
+ // Parse params from message body (the PWA sends params directly, no wrapper)
49
+ let params = {};
50
+ if (msg.data && msg.data.length > 0) {
51
+ const raw = sc.decode(msg.data).trim();
52
+ if (raw.length > 0) {
53
+ try {
54
+ params = JSON.parse(raw);
55
+ }
56
+ catch {
57
+ console.error(`Failed to parse RPC params for ${method}`);
58
+ if (msg.reply) {
59
+ msg.respond(sc.encode(JSON.stringify({ error: "Invalid JSON" })));
60
+ }
61
+ continue;
62
+ }
52
63
  }
53
- continue;
54
64
  }
55
- console.log(`RPC: ${request.method}`);
65
+ console.log(`RPC: ${method}`);
56
66
  let response;
57
67
  try {
58
- response = await handleRpc(request);
68
+ response = await handleRpc({ method, params });
59
69
  }
60
70
  catch (err) {
61
- console.error(`RPC error (${request.method}):`, err);
71
+ console.error(`RPC error (${method}):`, err);
62
72
  response = { error: String(err) };
63
73
  }
64
74
  if (msg.reply) {
65
75
  msg.respond(sc.encode(JSON.stringify(response)));
66
76
  }
67
77
  }
78
+ function toKebab(str) {
79
+ return str.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
80
+ }
81
+ function flattenTask(task, status) {
82
+ return { ...task.frontmatter, body: task.body, ...(status != null ? { status } : {}) };
83
+ }
68
84
  async function handleRpc(request) {
69
85
  switch (request.method) {
70
86
  case "task.list": {
71
87
  const tasks = listTasks(config.projectRoot);
72
- const tasksWithStatus = tasks.map((task) => ({
73
- ...task,
74
- status: getTaskStatus(task.frontmatter.id),
75
- }));
76
- return { tasks: tasksWithStatus };
88
+ return {
89
+ tasks: tasks.map((task) => flattenTask(task, getTaskStatus(task.frontmatter.id))),
90
+ };
77
91
  }
78
92
  case "task.create": {
79
93
  const params = request.params;
80
- const taskDir = getTaskDir(config.projectRoot, params.id);
94
+ const id = toKebab(params.name);
95
+ const taskDir = getTaskDir(config.projectRoot, id);
81
96
  const task = {
82
97
  frontmatter: {
83
- id: params.id,
98
+ id,
84
99
  name: params.name,
85
100
  user_prompt: params.user_prompt,
86
- triggers: params.triggers || [],
101
+ triggers: params.triggers ?? [],
87
102
  requires_confirmation: params.requires_confirmation ?? true,
88
103
  suppress_permissions: params.suppress_permissions ?? false,
89
104
  enabled: params.enabled ?? true,
@@ -92,7 +107,7 @@ export async function serveCommand() {
92
107
  };
93
108
  writeTaskFile(taskDir, task);
94
109
  installTaskTimer(config, task);
95
- return { ok: true, task_id: params.id };
110
+ return flattenTask(task);
96
111
  }
97
112
  case "task.update": {
98
113
  const params = request.params;
@@ -117,7 +132,7 @@ export async function serveCommand() {
117
132
  // Reinstall timer with updated config
118
133
  removeTaskTimer(params.id);
119
134
  installTaskTimer(config, existing);
120
- return { ok: true, task_id: params.id };
135
+ return flattenTask(existing, getTaskStatus(params.id));
121
136
  }
122
137
  case "task.delete": {
123
138
  const params = request.params;
@@ -137,7 +152,7 @@ export async function serveCommand() {
137
152
  cwd: config.projectRoot,
138
153
  timeout: 120_000,
139
154
  });
140
- return { ok: true, output };
155
+ return { ok: true, body: output };
141
156
  }
142
157
  catch (err) {
143
158
  const error = err;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "palmier",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Palmier agent CLI - provisions, executes tasks, and serves NATS RPC",
5
5
  "license": "ISC",
6
6
  "author": "Hongxu Cai",
@@ -5,7 +5,7 @@ import { loadConfig } from "../config.js";
5
5
  import { connectNats } from "../nats-client.js";
6
6
  import { listTasks, parseTaskFile, writeTaskFile, getTaskDir } from "../task.js";
7
7
  import { installTaskTimer, removeTaskTimer, getTaskStatus } from "../systemd.js";
8
- import type { RpcMessage, TaskWithStatus } from "../types.js";
8
+ import type { ParsedTask, RpcMessage } from "../types.js";
9
9
 
10
10
  /**
11
11
  * Start the persistent NATS RPC handler.
@@ -49,24 +49,35 @@ export async function serveCommand(): Promise<void> {
49
49
  console.log("Agent serving. Waiting for RPC messages...");
50
50
 
51
51
  for await (const msg of sub) {
52
- let request: RpcMessage;
53
- try {
54
- request = JSON.parse(sc.decode(msg.data)) as RpcMessage;
55
- } catch {
56
- console.error("Failed to parse RPC message");
57
- if (msg.reply) {
58
- msg.respond(sc.encode(JSON.stringify({ error: "Invalid JSON" })));
52
+ // Derive RPC method from subject: ...rpc.<method parts>
53
+ const subjectTokens = msg.subject.split(".");
54
+ const rpcIdx = subjectTokens.indexOf("rpc");
55
+ const method = rpcIdx >= 0 ? subjectTokens.slice(rpcIdx + 1).join(".") : "";
56
+
57
+ // Parse params from message body (the PWA sends params directly, no wrapper)
58
+ let params: Record<string, unknown> = {};
59
+ if (msg.data && msg.data.length > 0) {
60
+ const raw = sc.decode(msg.data).trim();
61
+ if (raw.length > 0) {
62
+ try {
63
+ params = JSON.parse(raw);
64
+ } catch {
65
+ console.error(`Failed to parse RPC params for ${method}`);
66
+ if (msg.reply) {
67
+ msg.respond(sc.encode(JSON.stringify({ error: "Invalid JSON" })));
68
+ }
69
+ continue;
70
+ }
59
71
  }
60
- continue;
61
72
  }
62
73
 
63
- console.log(`RPC: ${request.method}`);
74
+ console.log(`RPC: ${method}`);
64
75
 
65
76
  let response: unknown;
66
77
  try {
67
- response = await handleRpc(request);
78
+ response = await handleRpc({ method, params });
68
79
  } catch (err) {
69
- console.error(`RPC error (${request.method}):`, err);
80
+ console.error(`RPC error (${method}):`, err);
70
81
  response = { error: String(err) };
71
82
  }
72
83
 
@@ -75,36 +86,44 @@ export async function serveCommand(): Promise<void> {
75
86
  }
76
87
  }
77
88
 
89
+ function toKebab(str: string): string {
90
+ return str.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
91
+ }
92
+
93
+ function flattenTask(task: ParsedTask, status?: unknown) {
94
+ return { ...task.frontmatter, body: task.body, ...(status != null ? { status } : {}) };
95
+ }
96
+
78
97
  async function handleRpc(request: RpcMessage): Promise<unknown> {
79
98
  switch (request.method) {
80
99
  case "task.list": {
81
100
  const tasks = listTasks(config.projectRoot);
82
- const tasksWithStatus: TaskWithStatus[] = tasks.map((task) => ({
83
- ...task,
84
- status: getTaskStatus(task.frontmatter.id),
85
- }));
86
- return { tasks: tasksWithStatus };
101
+ return {
102
+ tasks: tasks.map((task) =>
103
+ flattenTask(task, getTaskStatus(task.frontmatter.id))
104
+ ),
105
+ };
87
106
  }
88
107
 
89
108
  case "task.create": {
90
109
  const params = request.params as {
91
- id: string;
92
110
  name: string;
93
111
  user_prompt: string;
94
- triggers: Array<{ type: "cron" | "once"; value: string }>;
95
- requires_confirmation: boolean;
96
- suppress_permissions: boolean;
97
- enabled: boolean;
98
- body: string;
112
+ triggers?: Array<{ type: "cron" | "once"; value: string }>;
113
+ requires_confirmation?: boolean;
114
+ suppress_permissions?: boolean;
115
+ enabled?: boolean;
116
+ body?: string;
99
117
  };
100
118
 
101
- const taskDir = getTaskDir(config.projectRoot, params.id);
119
+ const id = toKebab(params.name);
120
+ const taskDir = getTaskDir(config.projectRoot, id);
102
121
  const task = {
103
122
  frontmatter: {
104
- id: params.id,
123
+ id,
105
124
  name: params.name,
106
125
  user_prompt: params.user_prompt,
107
- triggers: params.triggers || [],
126
+ triggers: params.triggers ?? [],
108
127
  requires_confirmation: params.requires_confirmation ?? true,
109
128
  suppress_permissions: params.suppress_permissions ?? false,
110
129
  enabled: params.enabled ?? true,
@@ -115,7 +134,7 @@ export async function serveCommand(): Promise<void> {
115
134
  writeTaskFile(taskDir, task);
116
135
  installTaskTimer(config, task);
117
136
 
118
- return { ok: true, task_id: params.id };
137
+ return flattenTask(task);
119
138
  }
120
139
 
121
140
  case "task.update": {
@@ -150,7 +169,7 @@ export async function serveCommand(): Promise<void> {
150
169
  removeTaskTimer(params.id);
151
170
  installTaskTimer(config, existing);
152
171
 
153
- return { ok: true, task_id: params.id };
172
+ return flattenTask(existing, getTaskStatus(params.id));
154
173
  }
155
174
 
156
175
  case "task.delete": {
@@ -176,7 +195,7 @@ export async function serveCommand(): Promise<void> {
176
195
  cwd: config.projectRoot,
177
196
  timeout: 120_000,
178
197
  });
179
- return { ok: true, output };
198
+ return { ok: true, body: output };
180
199
  } catch (err: unknown) {
181
200
  const error = err as { stdout?: string; stderr?: string };
182
201
  return { error: "claude command failed", stdout: error.stdout, stderr: error.stderr };