palmier 0.2.0 → 0.2.1

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.
Files changed (79) hide show
  1. package/CLAUDE.md +5 -1
  2. package/README.md +135 -45
  3. package/dist/agents/agent.d.ts +26 -0
  4. package/dist/agents/agent.js +32 -0
  5. package/dist/agents/claude.d.ts +8 -0
  6. package/dist/agents/claude.js +35 -0
  7. package/dist/agents/codex.d.ts +8 -0
  8. package/dist/agents/codex.js +41 -0
  9. package/dist/agents/gemini.d.ts +8 -0
  10. package/dist/agents/gemini.js +39 -0
  11. package/dist/agents/openclaw.d.ts +8 -0
  12. package/dist/agents/openclaw.js +25 -0
  13. package/dist/agents/shared-prompt.d.ts +11 -0
  14. package/dist/agents/shared-prompt.js +26 -0
  15. package/dist/commands/agents.d.ts +2 -0
  16. package/dist/commands/agents.js +19 -0
  17. package/dist/commands/info.d.ts +5 -0
  18. package/dist/commands/info.js +40 -0
  19. package/dist/commands/init.d.ts +7 -2
  20. package/dist/commands/init.js +139 -49
  21. package/dist/commands/mcpserver.d.ts +2 -0
  22. package/dist/commands/mcpserver.js +75 -0
  23. package/dist/commands/pair.d.ts +6 -0
  24. package/dist/commands/pair.js +166 -0
  25. package/dist/commands/plan-generation.md +32 -0
  26. package/dist/commands/run.d.ts +0 -1
  27. package/dist/commands/run.js +258 -114
  28. package/dist/commands/serve.d.ts +1 -1
  29. package/dist/commands/serve.js +16 -228
  30. package/dist/commands/sessions.d.ts +4 -0
  31. package/dist/commands/sessions.js +30 -0
  32. package/dist/commands/task-generation.md +1 -1
  33. package/dist/config.d.ts +5 -5
  34. package/dist/config.js +24 -6
  35. package/dist/index.js +58 -5
  36. package/dist/nats-client.d.ts +3 -3
  37. package/dist/nats-client.js +2 -2
  38. package/dist/rpc-handler.d.ts +6 -0
  39. package/dist/rpc-handler.js +367 -0
  40. package/dist/session-store.d.ts +12 -0
  41. package/dist/session-store.js +57 -0
  42. package/dist/spawn-command.d.ts +26 -0
  43. package/dist/spawn-command.js +48 -0
  44. package/dist/systemd.d.ts +2 -2
  45. package/dist/task.d.ts +45 -2
  46. package/dist/task.js +155 -14
  47. package/dist/transports/http-transport.d.ts +6 -0
  48. package/dist/transports/http-transport.js +157 -0
  49. package/dist/transports/nats-transport.d.ts +6 -0
  50. package/dist/transports/nats-transport.js +69 -0
  51. package/dist/types.d.ts +30 -13
  52. package/package.json +4 -3
  53. package/src/agents/agent.ts +62 -0
  54. package/src/agents/claude.ts +39 -0
  55. package/src/agents/codex.ts +46 -0
  56. package/src/agents/gemini.ts +43 -0
  57. package/src/agents/openclaw.ts +29 -0
  58. package/src/agents/shared-prompt.ts +26 -0
  59. package/src/commands/agents.ts +20 -0
  60. package/src/commands/info.ts +44 -0
  61. package/src/commands/init.ts +229 -121
  62. package/src/commands/mcpserver.ts +92 -0
  63. package/src/commands/pair.ts +195 -0
  64. package/src/commands/plan-generation.md +32 -0
  65. package/src/commands/run.ts +323 -129
  66. package/src/commands/serve.ts +26 -287
  67. package/src/commands/sessions.ts +32 -0
  68. package/src/config.ts +30 -10
  69. package/src/index.ts +67 -6
  70. package/src/nats-client.ts +4 -4
  71. package/src/rpc-handler.ts +421 -0
  72. package/src/session-store.ts +68 -0
  73. package/src/spawn-command.ts +78 -0
  74. package/src/systemd.ts +2 -2
  75. package/src/task.ts +166 -16
  76. package/src/transports/http-transport.ts +180 -0
  77. package/src/transports/nats-transport.ts +82 -0
  78. package/src/types.ts +36 -13
  79. package/src/commands/task-generation.md +0 -28
@@ -0,0 +1,195 @@
1
+ import * as os from "os";
2
+ import * as http from "node:http";
3
+ import { StringCodec } from "nats";
4
+ import { loadConfig } from "../config.js";
5
+ import { connectNats } from "../nats-client.js";
6
+ import { addSession } from "../session-store.js";
7
+ import type { HostConfig } from "../types.js";
8
+
9
+ const CODE_CHARS = "ABCDEFGHJKMNPQRSTUVWXYZ23456789"; // no O/0/I/1/L
10
+ const CODE_LENGTH = 6;
11
+ const EXPIRY_MS = 5 * 60 * 1000; // 5 minutes
12
+
13
+ function generateCode(): string {
14
+ const bytes = new Uint8Array(CODE_LENGTH);
15
+ crypto.getRandomValues(bytes);
16
+ return Array.from(bytes, (b) => CODE_CHARS[b % CODE_CHARS.length]).join("");
17
+ }
18
+
19
+ function detectLanIp(): string {
20
+ const interfaces = os.networkInterfaces();
21
+ for (const name of Object.keys(interfaces)) {
22
+ for (const iface of interfaces[name] ?? []) {
23
+ if (iface.family === "IPv4" && !iface.internal) {
24
+ return iface.address;
25
+ }
26
+ }
27
+ }
28
+ return "127.0.0.1";
29
+ }
30
+
31
+ function buildPairResponse(config: HostConfig, label?: string) {
32
+ const session = addSession(label);
33
+ const response: Record<string, unknown> = {
34
+ hostId: config.hostId,
35
+ sessionToken: session.token,
36
+ };
37
+
38
+ if (config.mode === "lan" || config.mode === "auto") {
39
+ const ip = detectLanIp();
40
+ response.directUrl = `http://${ip}:${config.directPort ?? 7400}`;
41
+ response.directToken = config.directToken;
42
+ }
43
+
44
+ return response;
45
+ }
46
+
47
+ /**
48
+ * Generate an OTP code and wait for a PWA client to pair.
49
+ * Listens on NATS (nats/auto modes) and/or HTTP (lan/auto modes).
50
+ */
51
+ export async function pairCommand(): Promise<void> {
52
+ const config = loadConfig();
53
+ const code = generateCode();
54
+ const mode = config.mode ?? "nats";
55
+
56
+ let paired = false;
57
+
58
+ function onPaired() {
59
+ paired = true;
60
+ console.log("Paired successfully!");
61
+ }
62
+
63
+ const cleanups: Array<() => void | Promise<void>> = [];
64
+
65
+ // NATS pairing (nats or auto mode)
66
+ if (mode === "nats" || mode === "auto") {
67
+ const nc = await connectNats(config);
68
+ const sc = StringCodec();
69
+ const subject = `pair.${code}`;
70
+ const sub = nc.subscribe(subject, { max: 1 });
71
+
72
+ cleanups.push(async () => {
73
+ sub.unsubscribe();
74
+ await nc.drain();
75
+ });
76
+
77
+ (async () => {
78
+ for await (const msg of sub) {
79
+ if (paired) break;
80
+ let label: string | undefined;
81
+ try {
82
+ if (msg.data && msg.data.length > 0) {
83
+ const body = JSON.parse(sc.decode(msg.data)) as { label?: string };
84
+ label = body.label;
85
+ }
86
+ } catch { /* empty body is fine */ }
87
+
88
+ const response = buildPairResponse(config, label);
89
+ if (msg.reply) {
90
+ msg.respond(sc.encode(JSON.stringify(response)));
91
+ }
92
+ onPaired();
93
+ }
94
+ })();
95
+ }
96
+
97
+ // HTTP pairing (lan or auto mode)
98
+ if (mode === "lan" || mode === "auto") {
99
+ const port = config.directPort ?? 7400;
100
+ const server = http.createServer(async (req, res) => {
101
+ res.setHeader("Access-Control-Allow-Origin", "*");
102
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
103
+ res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
104
+
105
+ if (req.method === "OPTIONS") {
106
+ res.writeHead(204);
107
+ res.end();
108
+ return;
109
+ }
110
+
111
+ if (req.method === "POST" && req.url === "/pair") {
112
+ const chunks: Buffer[] = [];
113
+ req.on("data", (chunk: Buffer) => chunks.push(chunk));
114
+ req.on("end", () => {
115
+ try {
116
+ const body = JSON.parse(Buffer.concat(chunks).toString("utf-8")) as {
117
+ code: string;
118
+ label?: string;
119
+ };
120
+
121
+ if (body.code !== code) {
122
+ res.writeHead(401, { "Content-Type": "application/json" });
123
+ res.end(JSON.stringify({ error: "Invalid code" }));
124
+ return;
125
+ }
126
+
127
+ if (paired) {
128
+ res.writeHead(410, { "Content-Type": "application/json" });
129
+ res.end(JSON.stringify({ error: "Code already used" }));
130
+ return;
131
+ }
132
+
133
+ const response = buildPairResponse(config, body.label);
134
+ res.writeHead(200, { "Content-Type": "application/json" });
135
+ res.end(JSON.stringify(response));
136
+ onPaired();
137
+ } catch {
138
+ res.writeHead(400, { "Content-Type": "application/json" });
139
+ res.end(JSON.stringify({ error: "Invalid JSON" }));
140
+ }
141
+ });
142
+ return;
143
+ }
144
+
145
+ res.writeHead(404, { "Content-Type": "application/json" });
146
+ res.end(JSON.stringify({ error: "Not found" }));
147
+ });
148
+
149
+ await new Promise<void>((resolve, reject) => {
150
+ server.listen(port + 1, () => resolve());
151
+ server.on("error", reject);
152
+ });
153
+
154
+ cleanups.push(() => {
155
+ server.close();
156
+ });
157
+ }
158
+
159
+ // Display pairing info
160
+ console.log("");
161
+ console.log("Enter this code in your Palmier app:");
162
+ console.log("");
163
+ console.log(` ${code}`);
164
+ console.log("");
165
+
166
+ if (mode === "lan" || mode === "auto") {
167
+ const ip = detectLanIp();
168
+ const port = config.directPort ?? 7400;
169
+ console.log(` Address: ${ip}:${port + 1}`);
170
+ console.log("");
171
+ }
172
+
173
+ console.log("Code expires in 5 minutes.");
174
+
175
+ // Wait for pairing or timeout
176
+ const start = Date.now();
177
+ await new Promise<void>((resolve) => {
178
+ const interval = setInterval(() => {
179
+ if (paired || Date.now() - start >= EXPIRY_MS) {
180
+ clearInterval(interval);
181
+ resolve();
182
+ }
183
+ }, 500);
184
+ });
185
+
186
+ // Cleanup
187
+ for (const cleanup of cleanups) {
188
+ await cleanup();
189
+ }
190
+
191
+ if (!paired) {
192
+ console.log("Code expired. Run `palmier pair` to try again.");
193
+ process.exit(1);
194
+ }
195
+ }
@@ -0,0 +1,32 @@
1
+ You are a task planning assistant. Given a task description, produce a detailed Markdown execution plan that an agent can follow step by step. **Do not execute any part of the plan yourself.**
2
+
3
+ Your output must begin with a raw YAML frontmatter block (delimited by `---`), followed by the plan body in Markdown. Do NOT wrap the frontmatter in code fences. The very first line of your output must be `---`.
4
+
5
+ **Output format:**
6
+
7
+ ---
8
+ task_name: <short descriptive name, 3-6 words>
9
+ ---
10
+
11
+ <plan body in Markdown>
12
+
13
+ **Frontmatter fields:**
14
+
15
+ - `task_name`: A concise label for the task (e.g., "Clean up temp files", "Update system packages", "Backup database daily").
16
+
17
+ **Plan body sections:**
18
+
19
+ ### 1. Goal
20
+ State what the task accomplishes and the expected end state.
21
+
22
+ ### 2. Plan
23
+ A numbered sequence of concrete, actionable steps. Use sub-steps for complex actions and include conditional branches where behavior may vary (e.g., "If file exists, do A; otherwise, do B"). Each step should be specific enough for the agent to execute without ambiguity.
24
+
25
+ ### 3. Output Format (if applicable)
26
+ If the task produces a report, email, or other formatted output, specify the exact structure, sections, tone, and any templates the agent should follow.
27
+
28
+ Use Markdown formatting throughout — headings, code blocks, and tables where appropriate.
29
+
30
+ **Important:** Any relative times or dates in the task description (e.g., "yesterday", "last week") are relative to when the task is executed, not when this plan is generated. The agent must resolve them at execution time.
31
+
32
+ **Task description:**