palmier 0.9.5 → 0.9.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.
Files changed (258) hide show
  1. package/README.md +30 -13
  2. package/dist/agents/agent.d.ts +0 -1
  3. package/dist/agents/agent.js +0 -1
  4. package/dist/agents/aider.d.ts +0 -1
  5. package/dist/agents/aider.js +0 -1
  6. package/dist/agents/claude.d.ts +0 -1
  7. package/dist/agents/claude.js +0 -1
  8. package/dist/agents/cline.d.ts +0 -1
  9. package/dist/agents/cline.js +0 -1
  10. package/dist/agents/codex.d.ts +0 -1
  11. package/dist/agents/codex.js +0 -1
  12. package/dist/agents/copilot.d.ts +0 -1
  13. package/dist/agents/copilot.js +0 -1
  14. package/dist/agents/cursor.d.ts +0 -1
  15. package/dist/agents/cursor.js +0 -1
  16. package/dist/agents/deepagents.d.ts +0 -1
  17. package/dist/agents/deepagents.js +0 -1
  18. package/dist/agents/droid.d.ts +0 -1
  19. package/dist/agents/droid.js +0 -1
  20. package/dist/agents/gemini.d.ts +0 -1
  21. package/dist/agents/gemini.js +0 -1
  22. package/dist/agents/goose.d.ts +0 -1
  23. package/dist/agents/goose.js +0 -1
  24. package/dist/agents/hermes.d.ts +0 -1
  25. package/dist/agents/hermes.js +0 -1
  26. package/dist/agents/kimi.d.ts +0 -1
  27. package/dist/agents/kimi.js +0 -1
  28. package/dist/agents/kiro.d.ts +0 -1
  29. package/dist/agents/kiro.js +0 -1
  30. package/dist/agents/openclaw.d.ts +0 -1
  31. package/dist/agents/openclaw.js +0 -1
  32. package/dist/agents/opencode.d.ts +0 -1
  33. package/dist/agents/opencode.js +0 -1
  34. package/dist/agents/qoder.d.ts +0 -1
  35. package/dist/agents/qoder.js +0 -1
  36. package/dist/agents/qwen.d.ts +0 -1
  37. package/dist/agents/qwen.js +0 -1
  38. package/dist/agents/shared-prompt.d.ts +0 -1
  39. package/dist/agents/shared-prompt.js +0 -1
  40. package/dist/client-store.d.ts +0 -1
  41. package/dist/client-store.js +0 -1
  42. package/dist/commands/clients.d.ts +0 -1
  43. package/dist/commands/clients.js +0 -1
  44. package/dist/commands/info.d.ts +0 -1
  45. package/dist/commands/info.js +0 -1
  46. package/dist/commands/init.d.ts +0 -1
  47. package/dist/commands/init.js +3 -4
  48. package/dist/commands/pair.d.ts +0 -1
  49. package/dist/commands/pair.js +0 -1
  50. package/dist/commands/restart.d.ts +0 -1
  51. package/dist/commands/restart.js +0 -1
  52. package/dist/commands/run.d.ts +0 -1
  53. package/dist/commands/run.js +0 -1
  54. package/dist/commands/serve.d.ts +0 -1
  55. package/dist/commands/serve.js +0 -1
  56. package/dist/commands/uninstall.d.ts +0 -1
  57. package/dist/commands/uninstall.js +0 -1
  58. package/dist/config.d.ts +0 -1
  59. package/dist/config.js +0 -1
  60. package/dist/event-queues.d.ts +0 -1
  61. package/dist/event-queues.js +0 -1
  62. package/dist/events.d.ts +0 -1
  63. package/dist/events.js +0 -1
  64. package/dist/index.d.ts +0 -1
  65. package/dist/index.js +0 -1
  66. package/dist/linked-device.d.ts +0 -1
  67. package/dist/linked-device.js +0 -1
  68. package/dist/mcp-handler.d.ts +0 -1
  69. package/dist/mcp-handler.js +0 -1
  70. package/dist/mcp-tools.d.ts +0 -1
  71. package/dist/mcp-tools.js +0 -1
  72. package/dist/nats-client.d.ts +0 -1
  73. package/dist/nats-client.js +0 -1
  74. package/dist/network.d.ts +0 -1
  75. package/dist/network.js +0 -1
  76. package/dist/notification-store.d.ts +0 -1
  77. package/dist/notification-store.js +0 -1
  78. package/dist/pending-requests.d.ts +0 -1
  79. package/dist/pending-requests.js +0 -1
  80. package/dist/platform/index.d.ts +0 -1
  81. package/dist/platform/index.js +0 -1
  82. package/dist/platform/linux.d.ts +0 -1
  83. package/dist/platform/linux.js +0 -1
  84. package/dist/platform/macos.d.ts +0 -1
  85. package/dist/platform/macos.js +0 -1
  86. package/dist/platform/platform.d.ts +0 -1
  87. package/dist/platform/platform.js +0 -1
  88. package/dist/platform/windows.d.ts +0 -1
  89. package/dist/platform/windows.js +0 -1
  90. package/dist/pwa/apple-touch-icon.png +0 -0
  91. package/dist/pwa/assets/index-D1bIhEbd.css +1 -0
  92. package/dist/pwa/assets/{index-Cvffaohh.js → index-DWvRAUiy.js} +31 -31
  93. package/dist/pwa/assets/{web-qdLcAD7T.js → web-C4iZbqTC.js} +1 -1
  94. package/dist/pwa/assets/{web-ChtbM4nv.js → web-CBFqJGX6.js} +1 -1
  95. package/dist/pwa/assets/{web-hExASsqW.js → web-DL4uXOpS.js} +1 -1
  96. package/dist/pwa/favicon.ico +0 -0
  97. package/dist/pwa/index.html +3 -3
  98. package/dist/pwa/manifest.webmanifest +1 -1
  99. package/dist/pwa/pwa-192x192.png +0 -0
  100. package/dist/pwa/pwa-512x512.png +0 -0
  101. package/dist/pwa/service-worker.js +1 -1
  102. package/dist/rpc-handler.d.ts +0 -1
  103. package/dist/rpc-handler.js +3 -10
  104. package/dist/sms-store.d.ts +0 -1
  105. package/dist/sms-store.js +0 -1
  106. package/dist/spawn-command.d.ts +0 -1
  107. package/dist/spawn-command.js +0 -1
  108. package/dist/task.d.ts +1 -1
  109. package/dist/task.js +14 -1
  110. package/dist/transports/http-transport.d.ts +0 -1
  111. package/dist/transports/http-transport.js +0 -1
  112. package/dist/transports/nats-transport.d.ts +0 -1
  113. package/dist/transports/nats-transport.js +0 -1
  114. package/dist/types.d.ts +0 -1
  115. package/dist/types.js +0 -1
  116. package/dist/update-checker.d.ts +0 -1
  117. package/dist/update-checker.js +0 -1
  118. package/package.json +5 -1
  119. package/.github/workflows/ci.yml +0 -16
  120. package/.github/workflows/publish.yml +0 -37
  121. package/CLAUDE.md +0 -22
  122. package/dist/pwa/assets/index-DBgOYBrB.css +0 -1
  123. package/palmier-server/.github/workflows/ci.yml +0 -21
  124. package/palmier-server/.github/workflows/deploy.yml +0 -38
  125. package/palmier-server/CLAUDE.md +0 -17
  126. package/palmier-server/PRODUCTION.md +0 -358
  127. package/palmier-server/README.md +0 -231
  128. package/palmier-server/nats.conf +0 -19
  129. package/palmier-server/package.json +0 -15
  130. package/palmier-server/pnpm-lock.yaml +0 -7639
  131. package/palmier-server/pnpm-workspace.yaml +0 -3
  132. package/palmier-server/pwa/index.html +0 -16
  133. package/palmier-server/pwa/logo/logo-prompt.md +0 -28
  134. package/palmier-server/pwa/logo/logo_20260330.png +0 -0
  135. package/palmier-server/pwa/package.json +0 -34
  136. package/palmier-server/pwa/public/apple-touch-icon.png +0 -0
  137. package/palmier-server/pwa/public/favicon.ico +0 -0
  138. package/palmier-server/pwa/public/pwa-192x192.png +0 -0
  139. package/palmier-server/pwa/public/pwa-512x512.png +0 -0
  140. package/palmier-server/pwa/src/App.css +0 -3004
  141. package/palmier-server/pwa/src/App.tsx +0 -59
  142. package/palmier-server/pwa/src/agentLabels.ts +0 -11
  143. package/palmier-server/pwa/src/api.ts +0 -67
  144. package/palmier-server/pwa/src/components/CapabilityToggles.tsx +0 -170
  145. package/palmier-server/pwa/src/components/ConnectionStatusIcon.tsx +0 -114
  146. package/palmier-server/pwa/src/components/HostMenu.tsx +0 -429
  147. package/palmier-server/pwa/src/components/PermissionsDialog.tsx +0 -34
  148. package/palmier-server/pwa/src/components/PullToRefreshIndicator.tsx +0 -46
  149. package/palmier-server/pwa/src/components/RunDetailView.tsx +0 -343
  150. package/palmier-server/pwa/src/components/SessionComposer.tsx +0 -157
  151. package/palmier-server/pwa/src/components/SessionsView.tsx +0 -326
  152. package/palmier-server/pwa/src/components/SwipeToDeleteRow.tsx +0 -170
  153. package/palmier-server/pwa/src/components/TabBar.tsx +0 -40
  154. package/palmier-server/pwa/src/components/TaskCard.tsx +0 -255
  155. package/palmier-server/pwa/src/components/TaskForm.tsx +0 -764
  156. package/palmier-server/pwa/src/components/TasksView.tsx +0 -179
  157. package/palmier-server/pwa/src/constants.ts +0 -2
  158. package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +0 -432
  159. package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +0 -124
  160. package/palmier-server/pwa/src/draftGuard.ts +0 -24
  161. package/palmier-server/pwa/src/formatTime.ts +0 -44
  162. package/palmier-server/pwa/src/hooks/useBackClose.ts +0 -75
  163. package/palmier-server/pwa/src/hooks/useMediaQuery.ts +0 -17
  164. package/palmier-server/pwa/src/hooks/usePullToRefresh.ts +0 -102
  165. package/palmier-server/pwa/src/hooks/usePushSubscription.ts +0 -77
  166. package/palmier-server/pwa/src/main.tsx +0 -14
  167. package/palmier-server/pwa/src/native/Device.ts +0 -49
  168. package/palmier-server/pwa/src/pages/Dashboard.tsx +0 -542
  169. package/palmier-server/pwa/src/pages/PairHost.tsx +0 -232
  170. package/palmier-server/pwa/src/pages/PairSetup.tsx +0 -134
  171. package/palmier-server/pwa/src/service-worker.ts +0 -142
  172. package/palmier-server/pwa/src/types.ts +0 -75
  173. package/palmier-server/pwa/src/vite-env.d.ts +0 -11
  174. package/palmier-server/pwa/tsconfig.json +0 -21
  175. package/palmier-server/pwa/tsconfig.node.json +0 -19
  176. package/palmier-server/pwa/vite.config.ts +0 -47
  177. package/palmier-server/server/.env.example +0 -20
  178. package/palmier-server/server/package.json +0 -36
  179. package/palmier-server/server/src/db.ts +0 -44
  180. package/palmier-server/server/src/fcm.ts +0 -74
  181. package/palmier-server/server/src/index.ts +0 -688
  182. package/palmier-server/server/src/nats-jwt.ts +0 -299
  183. package/palmier-server/server/src/nats-setup.ts +0 -48
  184. package/palmier-server/server/src/nats.ts +0 -33
  185. package/palmier-server/server/src/notify.ts +0 -34
  186. package/palmier-server/server/src/push.ts +0 -68
  187. package/palmier-server/server/src/routes/device.ts +0 -224
  188. package/palmier-server/server/src/routes/fcm.ts +0 -64
  189. package/palmier-server/server/src/routes/hosts.ts +0 -56
  190. package/palmier-server/server/src/routes/push.ts +0 -101
  191. package/palmier-server/server/tsconfig.json +0 -20
  192. package/palmier-server/spec.md +0 -533
  193. package/src/agents/agent-instructions.md +0 -28
  194. package/src/agents/agent.ts +0 -114
  195. package/src/agents/aider.ts +0 -35
  196. package/src/agents/claude.ts +0 -39
  197. package/src/agents/cline.ts +0 -35
  198. package/src/agents/codex.ts +0 -40
  199. package/src/agents/copilot.ts +0 -37
  200. package/src/agents/cursor.ts +0 -36
  201. package/src/agents/deepagents.ts +0 -36
  202. package/src/agents/droid.ts +0 -35
  203. package/src/agents/gemini.ts +0 -43
  204. package/src/agents/goose.ts +0 -33
  205. package/src/agents/hermes.ts +0 -36
  206. package/src/agents/kimi.ts +0 -35
  207. package/src/agents/kiro.ts +0 -36
  208. package/src/agents/openclaw.ts +0 -29
  209. package/src/agents/opencode.ts +0 -36
  210. package/src/agents/qoder.ts +0 -36
  211. package/src/agents/qwen.ts +0 -32
  212. package/src/agents/shared-prompt.ts +0 -30
  213. package/src/client-store.ts +0 -68
  214. package/src/commands/clients.ts +0 -29
  215. package/src/commands/info.ts +0 -29
  216. package/src/commands/init.ts +0 -165
  217. package/src/commands/pair.ts +0 -137
  218. package/src/commands/restart.ts +0 -6
  219. package/src/commands/run.ts +0 -608
  220. package/src/commands/serve.ts +0 -211
  221. package/src/commands/uninstall.ts +0 -9
  222. package/src/config.ts +0 -36
  223. package/src/cross-spawn.d.ts +0 -5
  224. package/src/event-queues.ts +0 -41
  225. package/src/events.ts +0 -29
  226. package/src/index.ts +0 -111
  227. package/src/linked-device.ts +0 -52
  228. package/src/mcp-handler.ts +0 -200
  229. package/src/mcp-tools.ts +0 -839
  230. package/src/nats-client.ts +0 -19
  231. package/src/network.ts +0 -96
  232. package/src/notification-store.ts +0 -30
  233. package/src/pending-requests.ts +0 -73
  234. package/src/platform/index.ts +0 -20
  235. package/src/platform/linux.ts +0 -296
  236. package/src/platform/macos.ts +0 -329
  237. package/src/platform/platform.ts +0 -31
  238. package/src/platform/windows.ts +0 -299
  239. package/src/rpc-handler.ts +0 -694
  240. package/src/sms-store.ts +0 -28
  241. package/src/spawn-command.ts +0 -123
  242. package/src/task.ts +0 -330
  243. package/src/transports/http-transport.ts +0 -478
  244. package/src/transports/nats-transport.ts +0 -76
  245. package/src/types.ts +0 -89
  246. package/src/update-checker.ts +0 -40
  247. package/test/agent-instructions.test.ts +0 -209
  248. package/test/agent-output-parsing.test.ts +0 -74
  249. package/test/linux-cron.test.ts +0 -41
  250. package/test/macos-plist.test.ts +0 -112
  251. package/test/notification-store.test.ts +0 -57
  252. package/test/pairing.test.ts +0 -35
  253. package/test/result-state.test.ts +0 -110
  254. package/test/task-parsing.test.ts +0 -82
  255. package/test/taskrun-messages.test.ts +0 -224
  256. package/test/tsconfig.json +0 -9
  257. package/test/windows-xml.test.ts +0 -89
  258. package/tsconfig.json +0 -19
@@ -1,36 +0,0 @@
1
- import type { ParsedTask, RequiredPermission } from "../types.js";
2
- import { execSync } from "child_process";
3
- import type { AgentTool, CommandLine } from "./agent.js";
4
- import { getAgentInstructions } from "./shared-prompt.js";
5
- import { SHELL } from "../platform/index.js";
6
-
7
- export class Qoder implements AgentTool {
8
- supportsPermissions = false;
9
- supportsYolo = true;
10
- getPromptCommandLine(prompt: string): CommandLine {
11
- return { command: "qodercli", args: ["-p", prompt] };
12
- }
13
-
14
- getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine {
15
- const yolo = extraPermissions === "yolo";
16
- const prompt = followupPrompt ?? getAgentInstructions(task, yolo || !this.supportsPermissions);
17
- const args = [];
18
-
19
- if (yolo) {
20
- args.push("--yolo");
21
- }
22
- if (followupPrompt) {args.push("-c");}
23
- args.push("-p", prompt);
24
-
25
- return { command: "qodercli", args};
26
- }
27
-
28
- async init(): Promise<boolean> {
29
- try {
30
- execSync("qodercli --version", { stdio: "ignore", shell: SHELL });
31
- } catch {
32
- return false;
33
- }
34
- return true;
35
- }
36
- }
@@ -1,32 +0,0 @@
1
- import type { ParsedTask, RequiredPermission } from "../types.js";
2
- import { execSync } from "child_process";
3
- import type { AgentTool, CommandLine } from "./agent.js";
4
- import { getAgentInstructions } from "./shared-prompt.js";
5
- import { SHELL } from "../platform/index.js";
6
-
7
- export class QwenAgent implements AgentTool {
8
- supportsPermissions = false;
9
- supportsYolo = true;
10
- getPromptCommandLine(prompt: string): CommandLine {
11
- return { command: "qwen", args: ["-p", prompt] };
12
- }
13
-
14
- getTaskRunCommandLine(task: ParsedTask, followupPrompt?: string, extraPermissions?: RequiredPermission[] | "yolo"): CommandLine {
15
- const yolo = extraPermissions === "yolo";
16
- const prompt = followupPrompt ?? getAgentInstructions(task, yolo || !this.supportsPermissions);
17
- const args = ["--approval-mode", yolo ? "yolo" : "auto-edit"];
18
-
19
- if (followupPrompt) { args.push("-c"); }
20
- args.push("-p", prompt);
21
- return { command: "qwen", args };
22
- }
23
-
24
- async init(): Promise<boolean> {
25
- try {
26
- execSync("qwen --version", { stdio: "ignore", shell: SHELL });
27
- } catch {
28
- return false;
29
- }
30
- return true;
31
- }
32
- }
@@ -1,30 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import { fileURLToPath } from "url";
4
- import { loadConfig } from "../config.js";
5
- import { generateEndpointDocs } from "../mcp-tools.js";
6
- import type { ParsedTask } from "../types.js";
7
-
8
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
-
10
- const AGENT_INSTRUCTIONS_TEMPLATE = fs.readFileSync(
11
- path.join(__dirname, "agent-instructions.md"),
12
- "utf-8",
13
- );
14
-
15
- export function getAgentInstructions(task: ParsedTask, skipPermissions?: boolean): string {
16
- const port = loadConfig().httpPort ?? 7256;
17
- const taskDescription = task.frontmatter.user_prompt;
18
- let instructions = AGENT_INSTRUCTIONS_TEMPLATE
19
- .replace(/\{\{ENDPOINT_DOCS\}\}/g, generateEndpointDocs(port, task.frontmatter.id))
20
- .replace(/\{\{TASK_DESCRIPTION\}\}/g, taskDescription);
21
- if (skipPermissions) {
22
- instructions = instructions.replace(/## Permissions\r?\n[\s\S]*?(?=## |\r?\n---)/m, "");
23
- }
24
- return instructions;
25
- }
26
-
27
- export const TASK_SUCCESS_MARKER = "[PALMIER_TASK_SUCCESS]";
28
- export const TASK_FAILURE_MARKER = "[PALMIER_TASK_FAILURE]";
29
- export const TASK_REPORT_PREFIX = "[PALMIER_REPORT]";
30
- export const TASK_PERMISSION_PREFIX = "[PALMIER_PERMISSION]";
@@ -1,68 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import { randomBytes } from "crypto";
4
- import { CONFIG_DIR } from "./config.js";
5
-
6
- const CLIENTS_FILE = path.join(CONFIG_DIR, "clients.json");
7
-
8
- export interface ClientEntry {
9
- token: string;
10
- createdAt: string;
11
- label?: string;
12
- }
13
-
14
- function readFile(): ClientEntry[] {
15
- try {
16
- if (!fs.existsSync(CLIENTS_FILE)) return [];
17
- const raw = fs.readFileSync(CLIENTS_FILE, "utf-8");
18
- return JSON.parse(raw) as ClientEntry[];
19
- } catch {
20
- return [];
21
- }
22
- }
23
-
24
- function writeFile(clients: ClientEntry[]): void {
25
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
26
- fs.writeFileSync(CLIENTS_FILE, JSON.stringify(clients, null, 2), "utf-8");
27
- }
28
-
29
- export function loadClients(): ClientEntry[] {
30
- return readFile();
31
- }
32
-
33
- export function addClient(label?: string): ClientEntry {
34
- const clients = readFile();
35
- const entry: ClientEntry = {
36
- token: randomBytes(32).toString("hex"),
37
- createdAt: new Date().toISOString(),
38
- ...(label ? { label } : {}),
39
- };
40
- clients.push(entry);
41
- writeFile(clients);
42
- return entry;
43
- }
44
-
45
- export function revokeClient(token: string): boolean {
46
- const clients = readFile();
47
- const idx = clients.findIndex((c) => c.token === token);
48
- if (idx === -1) return false;
49
- clients.splice(idx, 1);
50
- writeFile(clients);
51
- return true;
52
- }
53
-
54
- export function revokeAllClients(): number {
55
- const clients = readFile();
56
- const count = clients.length;
57
- writeFile([]);
58
- return count;
59
- }
60
-
61
- export function validateClient(token: string): boolean {
62
- const clients = readFile();
63
- return clients.some((c) => c.token === token);
64
- }
65
-
66
- export function hasClients(): boolean {
67
- return readFile().length > 0;
68
- }
@@ -1,29 +0,0 @@
1
- import { loadClients, revokeClient, revokeAllClients } from "../client-store.js";
2
-
3
- export async function clientsListCommand(): Promise<void> {
4
- const clients = loadClients();
5
- if (clients.length === 0) {
6
- console.log("No active clients.");
7
- return;
8
- }
9
-
10
- console.log(`${clients.length} active client(s):\n`);
11
- for (const c of clients) {
12
- const label = c.label ? ` (${c.label})` : "";
13
- console.log(` ${c.token}${label} created ${c.createdAt}`);
14
- }
15
- }
16
-
17
- export async function clientsRevokeCommand(token: string): Promise<void> {
18
- if (revokeClient(token)) {
19
- console.log("Client revoked.");
20
- } else {
21
- console.error("Client not found.");
22
- process.exit(1);
23
- }
24
- }
25
-
26
- export async function clientsRevokeAllCommand(): Promise<void> {
27
- const count = revokeAllClients();
28
- console.log(`Revoked ${count} client(s).`);
29
- }
@@ -1,29 +0,0 @@
1
- import { loadConfig } from "../config.js";
2
- import { loadClients } from "../client-store.js";
3
- import { buildLanUrl } from "../network.js";
4
-
5
- export async function infoCommand(): Promise<void> {
6
- const config = loadConfig();
7
- const clients = loadClients();
8
- const port = config.httpPort ?? 7256;
9
-
10
- const lanUrl = buildLanUrl(port, config.defaultInterface);
11
-
12
- console.log(`Host ID: ${config.hostId}`);
13
- console.log(`Project root: ${config.projectRoot}`);
14
- console.log(`Local URL: http://localhost:${port}`);
15
- console.log(`LAN URL: ${lanUrl ?? "(default route interface unavailable — pair a device or check network)"}`);
16
-
17
- if (config.agents && config.agents.length > 0) {
18
- console.log(`Agents: ${config.agents.map((a) => a.label).join(", ")}`);
19
- } else {
20
- console.log(`Agents: (none detected — run \`palmier agents\`)`);
21
- }
22
-
23
- console.log(`Clients: ${clients.length} active`);
24
-
25
- if (clients.length === 0) {
26
- console.log("");
27
- console.log("No paired clients. Run `palmier pair` to connect a device.");
28
- }
29
- }
@@ -1,165 +0,0 @@
1
- import * as readline from "readline";
2
- import { loadConfig, saveConfig } from "../config.js";
3
- import { detectAgents } from "../agents/agent.js";
4
- import { getPlatform } from "../platform/index.js";
5
- import { pairCommand } from "./pair.js";
6
- import { detectDefaultInterface, getInterfaceIpv4 } from "../network.js";
7
- import { listTasks } from "../task.js";
8
- import type { HostConfig } from "../types.js";
9
-
10
- type AskFn = (q: string) => Promise<string>;
11
-
12
- const bold = (s: string) => `\x1b[1m${s}\x1b[0m`;
13
- const dim = (s: string) => `\x1b[2m${s}\x1b[0m`;
14
- const green = (s: string) => `\x1b[32m${s}\x1b[0m`;
15
- const cyan = (s: string) => `\x1b[36m${s}\x1b[0m`;
16
- const red = (s: string) => `\x1b[31m${s}\x1b[0m`;
17
-
18
- export async function initCommand(): Promise<void> {
19
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
20
- const ask: AskFn = (q) => new Promise<string>((resolve) => rl.question(q, resolve));
21
-
22
- try {
23
- console.log(`\n${bold("=== Palmier Host Setup ===")}\n`);
24
- console.log(`By continuing, you agree to the ${cyan("Terms of Service")} (https://www.palmier.me/terms)`);
25
- console.log(`and ${cyan("Privacy Policy")} (https://www.palmier.me/privacy).\n`);
26
-
27
- console.log("Detecting installed agents...");
28
- const agents = await detectAgents();
29
-
30
- if (agents.length === 0) {
31
- console.log(`\n${red("No agent CLIs detected.")} Palmier requires at least one supported agent CLI.\n`);
32
- console.log(`See supported agents: https://www.palmier.me/agents\n`);
33
- console.log(`Install at least one agent CLI, then run ${cyan("palmier init")} again.`);
34
- rl.close();
35
- process.exit(1);
36
- }
37
-
38
- console.log(` Found: ${green(agents.map((a) => a.label).join(", "))}\n`);
39
-
40
- let httpPort = 7256;
41
- const portAnswer = await ask(`HTTP port (default ${httpPort}): `);
42
- const parsed = parseInt(portAnswer.trim(), 10);
43
- if (parsed > 0 && parsed < 65536) httpPort = parsed;
44
-
45
- const defaultInterface = (await detectDefaultInterface()) ?? undefined;
46
- const lanIp = defaultInterface ? getInterfaceIpv4(defaultInterface) : null;
47
-
48
- console.log(`\n${bold("Setup summary:")}\n`);
49
- console.log(` ${dim("Task storage:")} ${bold(process.cwd())}`);
50
- console.log(` All tasks and execution data will be stored here.\n`);
51
- console.log(` ${dim("Local:")} ${cyan(`http://localhost:${httpPort}`)}`);
52
- console.log(` Open in a browser on this machine — no internet required.\n`);
53
- console.log(` ${dim("Remote (web):")} ${cyan("https://app.palmier.me")}`);
54
- console.log(` Pair a browser on any device. Traffic always goes through the relay.\n`);
55
- console.log(` ${dim("Remote (app):")} ${cyan("https://github.com/caihongxu/palmier-android/releases")}`);
56
- if (lanIp) {
57
- console.log(` Download the Android APK. The app uses LAN for direct RPC`);
58
- console.log(` on the same network (detected ${cyan(`http://${lanIp}:${httpPort}`)}),`);
59
- console.log(` otherwise the relay.\n`);
60
- } else {
61
- console.log(` Download the Android APK. Traffic will go through the relay —`);
62
- console.log(` ${red("could not detect a LAN interface")} for direct RPC.\n`);
63
- }
64
- console.log(` ${dim("Agents:")} ${agents.map((a) => a.label).join(", ")}\n`);
65
-
66
- const existingTasks = listTasks(process.cwd());
67
- if (existingTasks.length > 0) {
68
- console.log(` ${dim("Recover tasks:")} ${existingTasks.length} existing task(s) found:`);
69
- for (const t of existingTasks) {
70
- console.log(` - ${t.frontmatter.name || t.frontmatter.user_prompt.slice(0, 50)}`);
71
- }
72
- console.log();
73
- }
74
-
75
- const confirm = await ask("Proceed? (Y/n): ");
76
- if (confirm.trim().toLowerCase() === "n") {
77
- console.log("\nSetup cancelled.");
78
- rl.close();
79
- return;
80
- }
81
-
82
- let existingHostId: string | undefined;
83
- try { existingHostId = loadConfig().hostId; } catch { /* first init */ }
84
-
85
- const serverUrl = "https://app.palmier.me";
86
- let registerResponse: { hostId: string; natsUrl: string; natsWsUrl: string; natsJwt: string; natsNkeySeed: string };
87
-
88
- while (true) {
89
- console.log(`\nRegistering host...`);
90
- try {
91
- registerResponse = await registerHost(serverUrl, existingHostId);
92
- console.log(green("Host registered successfully."));
93
- break;
94
- } catch (err) {
95
- console.error(`\n ${red(err instanceof Error ? err.message : String(err))}`);
96
- const retry = await ask("\nRetry? (Y/n): ");
97
- if (retry.trim().toLowerCase() === "n") {
98
- console.log("\nSetup cancelled.");
99
- rl.close();
100
- return;
101
- }
102
- }
103
- }
104
-
105
- const config: HostConfig = {
106
- hostId: registerResponse.hostId,
107
- projectRoot: process.cwd(),
108
- natsUrl: registerResponse.natsUrl,
109
- natsWsUrl: registerResponse.natsWsUrl,
110
- natsJwt: registerResponse.natsJwt,
111
- natsNkeySeed: registerResponse.natsNkeySeed,
112
- agents,
113
- httpPort,
114
- defaultInterface,
115
- };
116
-
117
- saveConfig(config);
118
- console.log(`Config saved to ${dim("~/.config/palmier/host.json")}`);
119
-
120
- getPlatform().installDaemon(config);
121
-
122
- // Task recovery runs in the daemon (palmier serve) because that process
123
- // is elevated and can create S4U scheduled tasks.
124
-
125
- console.log("\nStarting pairing...");
126
- rl.close();
127
- await pairCommand();
128
- } catch (err) {
129
- rl.close();
130
- throw err;
131
- }
132
- }
133
-
134
-
135
- async function registerHost(
136
- serverUrl: string,
137
- existingHostId?: string,
138
- ): Promise<{ hostId: string; natsUrl: string; natsWsUrl: string; natsJwt: string; natsNkeySeed: string }> {
139
- try {
140
- const res = await fetch(`${serverUrl}/api/hosts/register`, {
141
- method: "POST",
142
- headers: { "Content-Type": "application/json" },
143
- body: JSON.stringify(existingHostId ? { hostId: existingHostId } : {}),
144
- });
145
-
146
- if (!res.ok) {
147
- const body = await res.text();
148
- throw new Error(`${res.status} ${res.statusText}\n${body}`);
149
- }
150
-
151
- return (await res.json()) as {
152
- hostId: string;
153
- natsUrl: string;
154
- natsWsUrl: string;
155
- natsJwt: string;
156
- natsNkeySeed: string;
157
- };
158
- } catch (err) {
159
- const message = err instanceof Error ? err.message : String(err);
160
- if (message.includes("fetch failed") || message.includes("ECONNREFUSED") || message.includes("ENOTFOUND") || message.includes("NetworkError")) {
161
- throw new Error(`Could not reach ${serverUrl} — check the URL and your network connection.`);
162
- }
163
- throw new Error(`Failed to register host: ${message}`);
164
- }
165
- }
@@ -1,137 +0,0 @@
1
- import * as http from "node:http";
2
- import * as os from "node:os";
3
- import { StringCodec } from "nats";
4
- import { loadConfig } from "../config.js";
5
- import { connectNats } from "../nats-client.js";
6
- import { addClient } from "../client-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
-
12
- export const PAIRING_EXPIRY_MS = 60 * 1000; // 1 minute
13
-
14
- export function generatePairingCode(): string {
15
- const bytes = new Uint8Array(CODE_LENGTH);
16
- crypto.getRandomValues(bytes);
17
- return Array.from(bytes, (b) => CODE_CHARS[b % CODE_CHARS.length]).join("");
18
- }
19
-
20
- function buildPairResponse(config: HostConfig, label?: string) {
21
- const client = addClient(label);
22
- return {
23
- hostId: config.hostId,
24
- clientToken: client.token,
25
- hostName: os.hostname(),
26
- };
27
- }
28
-
29
- function httpPairRegister(port: number, code: string): Promise<boolean> {
30
- const body = JSON.stringify({ code, expiryMs: PAIRING_EXPIRY_MS });
31
-
32
- return new Promise((resolve) => {
33
- const req = http.request(
34
- {
35
- hostname: "127.0.0.1",
36
- port,
37
- path: "/pair-register",
38
- method: "POST",
39
- headers: { "Content-Type": "application/json" },
40
- timeout: PAIRING_EXPIRY_MS + 5000,
41
- },
42
- (res) => {
43
- const chunks: Buffer[] = [];
44
- res.on("data", (chunk: Buffer) => chunks.push(chunk));
45
- res.on("end", () => {
46
- try {
47
- const result = JSON.parse(Buffer.concat(chunks).toString("utf-8")) as { paired: boolean };
48
- resolve(result.paired);
49
- } catch {
50
- resolve(false);
51
- }
52
- });
53
- },
54
- );
55
-
56
- req.on("error", () => resolve(false));
57
- req.on("timeout", () => { req.destroy(); resolve(false); });
58
- req.end(body);
59
- });
60
- }
61
-
62
- /** Listens on NATS (server mode) and HTTP (via serve daemon) in parallel. */
63
- export async function pairCommand(): Promise<void> {
64
- const config = loadConfig();
65
- const code = generatePairingCode();
66
- const httpPort = config.httpPort ?? 7256;
67
-
68
- let paired = false;
69
-
70
- function onPaired() {
71
- paired = true;
72
- console.log("Paired successfully!");
73
- }
74
-
75
- const cleanups: Array<() => void | Promise<void>> = [];
76
-
77
- console.log("");
78
- console.log("Enter this code in your Palmier app:");
79
- console.log("");
80
- console.log(` ${code}`);
81
- console.log("");
82
- console.log("Code expires in 1 minute.");
83
-
84
- const nc = await connectNats(config);
85
- const sc = StringCodec();
86
- const subject = `pair.${code}`;
87
- const sub = nc.subscribe(subject, { max: 1 });
88
-
89
- cleanups.push(() => {
90
- sub.unsubscribe();
91
- nc.close();
92
- });
93
-
94
- (async () => {
95
- for await (const msg of sub) {
96
- if (paired) break;
97
- let label: string | undefined;
98
- try {
99
- if (msg.data && msg.data.length > 0) {
100
- const body = JSON.parse(sc.decode(msg.data)) as { label?: string };
101
- label = body.label;
102
- }
103
- } catch { /* empty body is fine */ }
104
-
105
- const response = buildPairResponse(config, label);
106
- if (msg.reply) {
107
- msg.respond(sc.encode(JSON.stringify(response)));
108
- }
109
- onPaired();
110
- }
111
- })();
112
-
113
- (async () => {
114
- const result = await httpPairRegister(httpPort, code);
115
- if (result) onPaired();
116
- })();
117
-
118
- const start = Date.now();
119
- await new Promise<void>((resolve) => {
120
- const interval = setInterval(() => {
121
- if (paired || Date.now() - start >= PAIRING_EXPIRY_MS) {
122
- clearInterval(interval);
123
- resolve();
124
- }
125
- }, 500);
126
- });
127
-
128
- for (const cleanup of cleanups) {
129
- await cleanup();
130
- }
131
-
132
- if (!paired) {
133
- console.log("Code expired. Run `palmier pair` to try again.");
134
- }
135
-
136
- process.exit(paired ? 0 : 1);
137
- }
@@ -1,6 +0,0 @@
1
- import { getPlatform } from "../platform/index.js";
2
-
3
- export async function restartCommand(): Promise<void> {
4
- const platform = getPlatform();
5
- await platform.restartDaemon();
6
- }