palmier 0.1.8 → 0.2.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.
@@ -1,11 +1,20 @@
1
1
  import { randomUUID } from "crypto";
2
- import { execSync } from "child_process";
2
+ import { execSync, exec } from "child_process";
3
+ import { promisify } from "util";
4
+ const execAsync = promisify(exec);
3
5
  import * as fs from "fs";
6
+ import * as path from "path";
7
+ import { fileURLToPath } from "url";
4
8
  import { StringCodec } from "nats";
5
9
  import { loadConfig } from "../config.js";
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ function shellEscape(arg) {
12
+ return "'" + arg.replace(/'/g, "'\\''") + "'";
13
+ }
14
+ const TASK_GENERATION_PROMPT = fs.readFileSync(path.join(__dirname, "task-generation.md"), "utf-8");
6
15
  import { connectNats } from "../nats-client.js";
7
16
  import { listTasks, parseTaskFile, writeTaskFile, getTaskDir } from "../task.js";
8
- import { installTaskTimer, removeTaskTimer, getTaskStatus } from "../systemd.js";
17
+ import { installTaskTimer, removeTaskTimer } from "../systemd.js";
9
18
  /**
10
19
  * Start the persistent NATS RPC handler.
11
20
  */
@@ -25,10 +34,10 @@ export async function serveCommand() {
25
34
  };
26
35
  process.on("SIGINT", shutdown);
27
36
  process.on("SIGTERM", shutdown);
28
- // On startup, clean up orphaned pending-hooks keys for this agent
37
+ // On startup, clean up orphaned pending-confirmation keys for this agent
29
38
  try {
30
39
  const js = nc.jetstream();
31
- const kv = await js.views.kv("pending-hooks");
40
+ const kv = await js.views.kv("pending-confirmation");
32
41
  const keys = await kv.keys();
33
42
  for await (const key of keys) {
34
43
  if (key.startsWith(`${config.agentId}.`)) {
@@ -38,7 +47,7 @@ export async function serveCommand() {
38
47
  }
39
48
  }
40
49
  catch (err) {
41
- console.error(`Warning: could not clean up pending-hooks KV: ${err}`);
50
+ console.error(`Warning: could not clean up pending-confirmation KV: ${err}`);
42
51
  }
43
52
  console.log("Agent serving. Waiting for RPC messages...");
44
53
  for await (const msg of sub) {
@@ -72,19 +81,20 @@ export async function serveCommand() {
72
81
  console.error(`RPC error (${method}):`, err);
73
82
  response = { error: String(err) };
74
83
  }
84
+ console.log(`RPC done: ${method}`, JSON.stringify(response).slice(0, 200));
75
85
  if (msg.reply) {
76
86
  msg.respond(sc.encode(JSON.stringify(response)));
77
87
  }
78
88
  }
79
- function flattenTask(task, status) {
80
- return { ...task.frontmatter, body: task.body, ...(status != null ? { status } : {}) };
89
+ function flattenTask(task) {
90
+ return { ...task.frontmatter, body: task.body };
81
91
  }
82
92
  async function handleRpc(request) {
83
93
  switch (request.method) {
84
94
  case "task.list": {
85
95
  const tasks = listTasks(config.projectRoot);
86
96
  return {
87
- tasks: tasks.map((task) => flattenTask(task, getTaskStatus(task.frontmatter.id))),
97
+ tasks: tasks.map((task) => flattenTask(task)),
88
98
  };
89
99
  }
90
100
  case "task.create": {
@@ -94,17 +104,18 @@ export async function serveCommand() {
94
104
  const task = {
95
105
  frontmatter: {
96
106
  id,
97
- name: params.name,
98
107
  user_prompt: params.user_prompt,
108
+ command_line: params.command_line ?? "claude -p --dangerously-skip-permissions",
99
109
  triggers: params.triggers ?? [],
110
+ triggers_enabled: params.triggers_enabled ?? true,
100
111
  requires_confirmation: params.requires_confirmation ?? true,
101
- suppress_permissions: params.suppress_permissions ?? false,
102
- enabled: params.enabled ?? true,
103
112
  },
104
113
  body: params.body || "",
105
114
  };
106
115
  writeTaskFile(taskDir, task);
107
- installTaskTimer(config, task);
116
+ if (task.frontmatter.triggers_enabled) {
117
+ installTaskTimer(config, task);
118
+ }
108
119
  return flattenTask(task);
109
120
  }
110
121
  case "task.update": {
@@ -112,25 +123,25 @@ export async function serveCommand() {
112
123
  const taskDir = getTaskDir(config.projectRoot, params.id);
113
124
  const existing = parseTaskFile(taskDir);
114
125
  // Merge updates
115
- if (params.name !== undefined)
116
- existing.frontmatter.name = params.name;
117
126
  if (params.user_prompt !== undefined)
118
127
  existing.frontmatter.user_prompt = params.user_prompt;
128
+ if (params.command_line !== undefined)
129
+ existing.frontmatter.command_line = params.command_line;
119
130
  if (params.triggers !== undefined)
120
131
  existing.frontmatter.triggers = params.triggers;
132
+ if (params.triggers_enabled !== undefined)
133
+ existing.frontmatter.triggers_enabled = params.triggers_enabled;
121
134
  if (params.requires_confirmation !== undefined)
122
135
  existing.frontmatter.requires_confirmation = params.requires_confirmation;
123
- if (params.suppress_permissions !== undefined)
124
- existing.frontmatter.suppress_permissions = params.suppress_permissions;
125
- if (params.enabled !== undefined)
126
- existing.frontmatter.enabled = params.enabled;
127
136
  if (params.body !== undefined)
128
137
  existing.body = params.body;
129
138
  writeTaskFile(taskDir, existing);
130
- // Reinstall timer with updated config
139
+ // Reinstall or remove timers based on triggers_enabled
131
140
  removeTaskTimer(params.id);
132
- installTaskTimer(config, existing);
133
- return flattenTask(existing, getTaskStatus(params.id));
141
+ if (existing.frontmatter.triggers_enabled) {
142
+ installTaskTimer(config, existing);
143
+ }
144
+ return flattenTask(existing);
134
145
  }
135
146
  case "task.delete": {
136
147
  const params = request.params;
@@ -144,8 +155,10 @@ export async function serveCommand() {
144
155
  }
145
156
  case "task.generate": {
146
157
  const params = request.params;
158
+ const commandLine = params.command_line || "claude -p --dangerously-skip-permissions";
159
+ const fullPrompt = TASK_GENERATION_PROMPT + params.prompt;
147
160
  try {
148
- const output = execSync(`claude -p "${params.prompt.replace(/"/g, '\\"')}"`, {
161
+ const output = execSync(`${commandLine} ${shellEscape(fullPrompt)}`, {
149
162
  encoding: "utf-8",
150
163
  cwd: config.projectRoot,
151
164
  timeout: 120_000,
@@ -154,24 +167,34 @@ export async function serveCommand() {
154
167
  }
155
168
  catch (err) {
156
169
  const error = err;
157
- return { error: "claude command failed", stdout: error.stdout, stderr: error.stderr };
170
+ return { error: "generation command failed", stdout: error.stdout, stderr: error.stderr };
158
171
  }
159
172
  }
160
173
  case "task.run": {
161
174
  const params = request.params;
162
175
  const serviceName = `palmier-task-${params.id}.service`;
163
176
  try {
164
- execSync(`systemctl --user start ${serviceName}`, { stdio: "inherit" });
177
+ await execAsync(`systemctl --user start --no-block ${serviceName}`);
165
178
  return { ok: true, task_id: params.id };
166
179
  }
167
180
  catch (err) {
168
- return { error: `Failed to start task service: ${err}` };
181
+ const e = err;
182
+ console.error(`task.run failed for ${params.id}: ${e.stderr || e.message}`);
183
+ return { error: `Failed to start task: ${e.stderr || e.message}` };
169
184
  }
170
185
  }
171
- case "task.status": {
186
+ case "task.abort": {
172
187
  const params = request.params;
173
- const status = getTaskStatus(params.id);
174
- return { task_id: params.id, status };
188
+ const serviceName = `palmier-task-${params.id}.service`;
189
+ try {
190
+ await execAsync(`systemctl --user stop ${serviceName}`);
191
+ return { ok: true, task_id: params.id };
192
+ }
193
+ catch (err) {
194
+ const e = err;
195
+ console.error(`task.abort failed for ${params.id}: ${e.stderr || e.message}`);
196
+ return { error: `Failed to abort task: ${e.stderr || e.message}` };
197
+ }
175
198
  }
176
199
  case "task.logs": {
177
200
  const params = request.params;
@@ -185,6 +208,28 @@ export async function serveCommand() {
185
208
  return { task_id: params.id, logs: error.stdout || "", error: error.stderr };
186
209
  }
187
210
  }
211
+ case "task.result": {
212
+ const params = request.params;
213
+ const resultPath = path.join(config.projectRoot, "tasks", params.id, "RESULT.md");
214
+ try {
215
+ const raw = fs.readFileSync(resultPath, "utf-8");
216
+ // Parse optional frontmatter
217
+ const fmMatch = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
218
+ if (fmMatch) {
219
+ const meta = {};
220
+ for (const line of fmMatch[1].split("\n")) {
221
+ const [key, val] = line.split(": ");
222
+ if (key && val)
223
+ meta[key.trim()] = Number(val.trim());
224
+ }
225
+ return { task_id: params.id, content: fmMatch[2], start_time: meta.start_time, end_time: meta.end_time };
226
+ }
227
+ return { task_id: params.id, content: raw };
228
+ }
229
+ catch {
230
+ return { task_id: params.id, error: "No result file found" };
231
+ }
232
+ }
188
233
  default:
189
234
  return { error: `Unknown method: ${request.method}` };
190
235
  }
@@ -0,0 +1,28 @@
1
+ You are a planning agent for a personal computer AI agent. Given a task description, produce a detailed Markdown execution plan that the agent can later follow step by step. **Do not execute any part of the task yourself.**
2
+
3
+ The plan must include the following sections:
4
+
5
+ ### 1. Goal
6
+ What the task accomplishes and the expected end state.
7
+
8
+ ### 2. Prerequisites
9
+ What must be true before starting:
10
+ - Required software and versions
11
+ - Files or data that must be present
12
+ - Permissions or access needed
13
+ - Environment state (e.g., running services, network access)
14
+
15
+ ### 3. Plan
16
+ A numbered sequence of concrete, actionable steps to complete the task.
17
+ Use sub-steps for complex actions. Include conditional branches where behavior may vary (e.g., "If file exists, do A; otherwise, do B"). Each step should be specific enough that the agent can execute it without ambiguity.
18
+
19
+ ### 4. Edge Cases & Risks
20
+ Anything that could go wrong and how to handle it:
21
+ - Common failure modes
22
+ - Platform-specific differences
23
+ - Race conditions or timing issues
24
+ - Data loss risks and mitigation
25
+
26
+ Format the entire document as Markdown with proper headings, code blocks for commands, and tables where appropriate.
27
+
28
+ **Task description:**
package/dist/index.js CHANGED
@@ -6,7 +6,6 @@ import { dirname, join } from "path";
6
6
  import { Command } from "commander";
7
7
  import { initCommand } from "./commands/init.js";
8
8
  import { runCommand } from "./commands/run.js";
9
- import { hookCommand } from "./commands/hook.js";
10
9
  import { serveCommand } from "./commands/serve.js";
11
10
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
11
  const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
@@ -28,12 +27,6 @@ program
28
27
  .action(async (taskId) => {
29
28
  await runCommand(taskId);
30
29
  });
31
- program
32
- .command("hook")
33
- .description("Handle a Claude Code hook event (reads from stdin)")
34
- .action(async () => {
35
- await hookCommand();
36
- });
37
30
  program
38
31
  .command("serve", { isDefault: true })
39
32
  .description("Start the persistent NATS RPC handler")
package/dist/systemd.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { AgentConfig } from "./types.js";
2
- import type { ParsedTask, TaskStatus } from "./types.js";
2
+ import type { ParsedTask } from "./types.js";
3
3
  /**
4
4
  * Convert a cron expression (5-field) to a systemd OnCalendar string.
5
5
  * Handles basic cron patterns: minute hour day-of-month month day-of-week
@@ -13,10 +13,6 @@ export declare function installTaskTimer(config: AgentConfig, task: ParsedTask):
13
13
  * Remove a task's systemd timer and service files.
14
14
  */
15
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
16
  /**
21
17
  * Run systemctl --user daemon-reload.
22
18
  */
package/dist/systemd.js CHANGED
@@ -57,46 +57,49 @@ export function installTaskTimer(config, task) {
57
57
  // Determine the palmier binary path
58
58
  const palmierBin = process.argv[1] || "palmier";
59
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"}
60
+ const serviceContent = `[Unit]
61
+ Description=Palmier Task: ${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
68
  `;
69
- // Generate timer unit with OnCalendar entries for each cron trigger
69
+ // Write service unit (always needed for on-demand runs)
70
+ fs.writeFileSync(path.join(UNIT_DIR, serviceName), serviceContent, "utf-8");
71
+ daemonReload();
72
+ // Only create and enable a timer if there are actual triggers
73
+ const triggers = task.frontmatter.triggers || [];
70
74
  const onCalendarLines = [];
71
- for (const trigger of task.frontmatter.triggers || []) {
75
+ for (const trigger of triggers) {
72
76
  if (trigger.type === "cron") {
73
77
  onCalendarLines.push(`OnCalendar=${cronToOnCalendar(trigger.value)}`);
74
78
  }
75
79
  else if (trigger.type === "once") {
76
- // "once" triggers use OnActiveSec or a specific timestamp
77
80
  onCalendarLines.push(`OnActiveSec=${trigger.value}`);
78
81
  }
79
82
  }
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
83
+ if (onCalendarLines.length > 0) {
84
+ const timerContent = `[Unit]
85
+ Description=Timer for Palmier Task: ${taskId}
86
+
87
+ [Timer]
88
+ ${onCalendarLines.join("\n")}
89
+ Persistent=true
90
+
91
+ [Install]
92
+ WantedBy=timers.target
89
93
  `;
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" });
94
+ fs.writeFileSync(path.join(UNIT_DIR, timerName), timerContent, "utf-8");
95
+ daemonReload();
96
+ try {
97
+ execSync(`systemctl --user enable --now ${timerName}`, { encoding: "utf-8" });
98
+ }
99
+ catch (err) {
100
+ const e = err;
101
+ console.error(`Failed to enable timer ${timerName}: ${e.stderr || err}`);
102
+ }
100
103
  }
101
104
  }
102
105
  /**
@@ -105,101 +108,38 @@ WantedBy=timers.target
105
108
  export function removeTaskTimer(taskId) {
106
109
  const timerName = getTimerName(taskId);
107
110
  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
111
  const timerPath = path.join(UNIT_DIR, timerName);
123
112
  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";
113
+ // Only stop/disable the timer if the file exists
114
+ if (fs.existsSync(timerPath)) {
115
+ try {
116
+ execSync(`systemctl --user stop ${timerName}`, { encoding: "utf-8" });
147
117
  }
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";
118
+ catch {
119
+ // Timer might not be running
159
120
  }
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
- }
121
+ try {
122
+ execSync(`systemctl --user disable ${timerName}`, { encoding: "utf-8" });
178
123
  }
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
- }
124
+ catch {
125
+ // Timer might not be enabled
192
126
  }
127
+ fs.unlinkSync(timerPath);
193
128
  }
194
- catch {
195
- // Couldn't get next run
196
- }
197
- return { state, lastRun, lastResult, nextRun };
129
+ if (fs.existsSync(servicePath))
130
+ fs.unlinkSync(servicePath);
131
+ daemonReload();
198
132
  }
199
133
  /**
200
134
  * Run systemctl --user daemon-reload.
201
135
  */
202
136
  export function daemonReload() {
203
- execSync("systemctl --user daemon-reload", { stdio: "inherit" });
137
+ try {
138
+ execSync("systemctl --user daemon-reload", { encoding: "utf-8" });
139
+ }
140
+ catch (err) {
141
+ const e = err;
142
+ console.error(`daemon-reload failed: ${e.stderr || err}`);
143
+ }
204
144
  }
205
145
  //# sourceMappingURL=systemd.js.map
package/dist/task.js CHANGED
@@ -26,6 +26,8 @@ function parseTaskContent(content) {
26
26
  if (!frontmatter.id) {
27
27
  throw new Error("TASK.md frontmatter must include at least: id");
28
28
  }
29
+ frontmatter.command_line ??= "claude -p --dangerously-skip-permissions";
30
+ frontmatter.triggers_enabled ??= true;
29
31
  return { frontmatter, body };
30
32
  }
31
33
  /**
package/dist/types.d.ts CHANGED
@@ -8,12 +8,11 @@ export interface AgentConfig {
8
8
  }
9
9
  export interface TaskFrontmatter {
10
10
  id: string;
11
- name: string;
12
11
  user_prompt: string;
12
+ command_line: string;
13
13
  triggers: Trigger[];
14
+ triggers_enabled: boolean;
14
15
  requires_confirmation: boolean;
15
- suppress_permissions: boolean;
16
- enabled: boolean;
17
16
  }
18
17
  export interface Trigger {
19
18
  type: "cron" | "once";
@@ -23,31 +22,13 @@ export interface ParsedTask {
23
22
  frontmatter: TaskFrontmatter;
24
23
  body: string;
25
24
  }
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";
25
+ export interface ConfirmPayload {
26
+ type: "confirm";
37
27
  task_id: string;
38
- hook_id: string;
39
28
  agent_id: string;
40
29
  user_id: string;
41
30
  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;
31
+ status: "pending" | "confirmed" | "aborted";
51
32
  }
52
33
  export interface RpcMessage {
53
34
  method: string;
package/package.json CHANGED
@@ -1,35 +1,33 @@
1
- {
2
- "name": "palmier",
3
- "version": "0.1.8",
4
- "description": "Palmier agent CLI - provisions, executes tasks, and serves NATS RPC",
5
- "license": "ISC",
6
- "author": "Hongxu Cai",
7
- "type": "module",
8
- "main": "dist/index.js",
9
- "types": "./dist/index.d.ts",
10
- "bin": {
11
- "palmier": "dist/index.js"
12
- },
13
- "scripts": {
14
- "dev": "tsx src/index.ts",
15
- "build": "tsc",
16
- "start": "node dist/index.js"
17
- },
18
- "dependencies": {
19
- "commander": "^13.1.0",
20
- "dotenv": "^16.4.7",
21
- "nats": "^2.29.1",
22
- "node-pty": "^1.0.0",
23
- "uuid": "^11.1.0",
24
- "yaml": "^2.7.0"
25
- },
26
- "devDependencies": {
27
- "@types/node": "^22.13.0",
28
- "@types/uuid": "^10.0.0",
29
- "tsx": "^4.19.0",
30
- "typescript": "^5.7.0"
31
- },
32
- "engines": {
33
- "node": ">=20.0.0"
34
- }
35
- }
1
+ {
2
+ "name": "palmier",
3
+ "version": "0.2.0",
4
+ "description": "Palmier agent CLI - provisions, executes tasks, and serves NATS RPC",
5
+ "license": "ISC",
6
+ "author": "Hongxu Cai",
7
+ "type": "module",
8
+ "main": "dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "bin": {
11
+ "palmier": "dist/index.js"
12
+ },
13
+ "scripts": {
14
+ "dev": "tsx src/index.ts",
15
+ "build": "tsc && node -e \"require('fs').cpSync('src/commands/task-generation.md','dist/commands/task-generation.md')\"",
16
+ "prepare": "npm run build",
17
+ "start": "node dist/index.js"
18
+ },
19
+ "dependencies": {
20
+ "commander": "^13.1.0",
21
+ "dotenv": "^16.4.7",
22
+ "nats": "^2.29.1",
23
+ "yaml": "^2.7.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^22.13.0",
27
+ "tsx": "^4.19.0",
28
+ "typescript": "^5.7.0"
29
+ },
30
+ "engines": {
31
+ "node": ">=20.0.0"
32
+ }
33
+ }