palmier 0.6.0 → 0.6.2

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 (110) hide show
  1. package/.github/workflows/publish.yml +15 -2
  2. package/CLAUDE.md +2 -2
  3. package/DISCLAIMER.md +36 -0
  4. package/README.md +76 -87
  5. package/dist/agents/agent-instructions.md +1 -1
  6. package/dist/agents/agent.d.ts +2 -0
  7. package/dist/agents/agent.js +21 -0
  8. package/dist/agents/aider.d.ts +9 -0
  9. package/dist/agents/aider.js +32 -0
  10. package/dist/agents/cursor.d.ts +9 -0
  11. package/dist/agents/cursor.js +35 -0
  12. package/dist/agents/deepagents.d.ts +9 -0
  13. package/dist/agents/deepagents.js +35 -0
  14. package/dist/agents/droid.d.ts +9 -0
  15. package/dist/agents/droid.js +32 -0
  16. package/dist/agents/goose.d.ts +9 -0
  17. package/dist/agents/goose.js +32 -0
  18. package/dist/agents/opencode.d.ts +9 -0
  19. package/dist/agents/opencode.js +35 -0
  20. package/dist/agents/openhands.d.ts +9 -0
  21. package/dist/agents/openhands.js +35 -0
  22. package/dist/commands/pair.d.ts +1 -1
  23. package/dist/commands/pair.js +1 -1
  24. package/dist/commands/run.js +2 -2
  25. package/dist/pwa/apple-touch-icon.png +0 -0
  26. package/dist/pwa/assets/index-ByhOhTz1.js +118 -0
  27. package/dist/pwa/assets/index-_AmC1Rkn.css +1 -0
  28. package/dist/pwa/assets/plus-jakarta-sans-latin-ext-wght-normal-DmpS2jIq.woff2 +0 -0
  29. package/dist/pwa/assets/plus-jakarta-sans-latin-wght-normal-eXO_dkmS.woff2 +0 -0
  30. package/dist/pwa/assets/plus-jakarta-sans-vietnamese-wght-normal-qRpaaN48.woff2 +0 -0
  31. package/dist/pwa/favicon.ico +0 -0
  32. package/dist/pwa/index.html +17 -0
  33. package/dist/pwa/manifest.webmanifest +1 -0
  34. package/dist/pwa/pwa-192x192.png +0 -0
  35. package/dist/pwa/pwa-512x512.png +0 -0
  36. package/dist/pwa/registerSW.js +1 -0
  37. package/dist/pwa/service-worker.js +2 -0
  38. package/dist/rpc-handler.d.ts +4 -0
  39. package/dist/rpc-handler.js +5 -4
  40. package/dist/transports/http-transport.js +29 -41
  41. package/package.json +2 -2
  42. package/palmier-server/.github/workflows/ci.yml +21 -0
  43. package/palmier-server/.github/workflows/deploy.yml +38 -0
  44. package/palmier-server/CLAUDE.md +13 -0
  45. package/palmier-server/PRODUCTION.md +355 -0
  46. package/palmier-server/README.md +187 -0
  47. package/palmier-server/nats.conf +15 -0
  48. package/palmier-server/package.json +8 -0
  49. package/palmier-server/pnpm-lock.yaml +6597 -0
  50. package/palmier-server/pnpm-workspace.yaml +3 -0
  51. package/palmier-server/pwa/index.html +16 -0
  52. package/palmier-server/pwa/logo/logo-prompt.md +28 -0
  53. package/palmier-server/pwa/logo/logo_20260330.png +0 -0
  54. package/palmier-server/pwa/package.json +30 -0
  55. package/palmier-server/pwa/public/apple-touch-icon.png +0 -0
  56. package/palmier-server/pwa/public/favicon.ico +0 -0
  57. package/palmier-server/pwa/public/pwa-192x192.png +0 -0
  58. package/palmier-server/pwa/public/pwa-512x512.png +0 -0
  59. package/palmier-server/pwa/src/App.css +2387 -0
  60. package/palmier-server/pwa/src/App.tsx +21 -0
  61. package/palmier-server/pwa/src/agentLabels.ts +11 -0
  62. package/palmier-server/pwa/src/api.ts +61 -0
  63. package/palmier-server/pwa/src/components/HostMenu.tsx +289 -0
  64. package/palmier-server/pwa/src/components/PlanDialog.tsx +41 -0
  65. package/palmier-server/pwa/src/components/RunDetailView.tsx +293 -0
  66. package/palmier-server/pwa/src/components/RunsView.tsx +254 -0
  67. package/palmier-server/pwa/src/components/TabBar.tsx +31 -0
  68. package/palmier-server/pwa/src/components/TaskCard.tsx +213 -0
  69. package/palmier-server/pwa/src/components/TaskForm.tsx +580 -0
  70. package/palmier-server/pwa/src/components/TaskListView.tsx +415 -0
  71. package/palmier-server/pwa/src/constants.ts +2 -0
  72. package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +313 -0
  73. package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +135 -0
  74. package/palmier-server/pwa/src/formatTime.ts +10 -0
  75. package/palmier-server/pwa/src/hooks/useBackClose.ts +75 -0
  76. package/palmier-server/pwa/src/hooks/useMediaQuery.ts +17 -0
  77. package/palmier-server/pwa/src/hooks/usePushSubscription.ts +75 -0
  78. package/palmier-server/pwa/src/main.tsx +14 -0
  79. package/palmier-server/pwa/src/pages/Dashboard.tsx +223 -0
  80. package/palmier-server/pwa/src/pages/PairHost.tsx +178 -0
  81. package/palmier-server/pwa/src/service-worker.ts +139 -0
  82. package/palmier-server/pwa/src/types.ts +79 -0
  83. package/palmier-server/pwa/src/vite-env.d.ts +11 -0
  84. package/palmier-server/pwa/tsconfig.json +21 -0
  85. package/palmier-server/pwa/tsconfig.node.json +19 -0
  86. package/palmier-server/pwa/vite.config.ts +47 -0
  87. package/palmier-server/server/.env.example +16 -0
  88. package/palmier-server/server/package.json +33 -0
  89. package/palmier-server/server/src/db.ts +34 -0
  90. package/palmier-server/server/src/index.ts +219 -0
  91. package/palmier-server/server/src/nats.ts +25 -0
  92. package/palmier-server/server/src/push.ts +68 -0
  93. package/palmier-server/server/src/routes/hosts.ts +45 -0
  94. package/palmier-server/server/src/routes/push.ts +100 -0
  95. package/palmier-server/server/tsconfig.json +20 -0
  96. package/palmier-server/spec.md +415 -0
  97. package/src/agents/agent-instructions.md +1 -1
  98. package/src/agents/agent.ts +23 -0
  99. package/src/agents/aider.ts +37 -0
  100. package/src/agents/cursor.ts +38 -0
  101. package/src/agents/deepagents.ts +38 -0
  102. package/src/agents/droid.ts +37 -0
  103. package/src/agents/goose.ts +35 -0
  104. package/src/agents/opencode.ts +38 -0
  105. package/src/agents/openhands.ts +38 -0
  106. package/src/commands/pair.ts +1 -1
  107. package/src/commands/run.ts +2 -2
  108. package/src/rpc-handler.ts +5 -4
  109. package/src/transports/http-transport.ts +31 -43
  110. package/test/result-state.test.ts +110 -0
@@ -0,0 +1,213 @@
1
+ import { useState, useRef, useEffect } from "react";
2
+ import { createPortal } from "react-dom";
3
+ import { useHostConnection } from "../contexts/HostConnectionContext";
4
+ import { formatTime } from "../formatTime";
5
+ import { getAgentLabel } from "../agentLabels";
6
+ import type { Task, TaskStatus } from "../types";
7
+
8
+ const isMobile = () => window.matchMedia("(max-width: 600px)").matches;
9
+
10
+ interface TaskCardProps {
11
+ task: Task;
12
+ lastEvent?: TaskStatus;
13
+ onEdit(task: Task): void;
14
+ onDelete(taskId: string): void;
15
+ onViewRun(taskId: string, runId?: string): void;
16
+ }
17
+
18
+ export default function TaskCard({ task, lastEvent, onEdit, onDelete, onViewRun }: TaskCardProps) {
19
+ const { request } = useHostConnection();
20
+ const [aborting, setAborting] = useState(false);
21
+ const [menuOpen, setMenuOpen] = useState(false);
22
+ const [sheetOpen, setSheetOpen] = useState(false);
23
+ const menuRef = useRef<HTMLDivElement>(null);
24
+
25
+ const isRunning = lastEvent?.running_state === "started";
26
+ const stateColor =
27
+ !task.triggers_enabled || task.triggers.length === 0
28
+ ? "var(--color-text-secondary)"
29
+ : isRunning
30
+ ? "var(--color-success)"
31
+ : lastEvent?.running_state === "failed"
32
+ ? "var(--color-error)"
33
+ : "var(--color-success)";
34
+
35
+ // Close dropdown on outside click
36
+ useEffect(() => {
37
+ if (!menuOpen) return;
38
+ function handleClick(e: MouseEvent) {
39
+ if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
40
+ setMenuOpen(false);
41
+ }
42
+ }
43
+ document.addEventListener("mousedown", handleClick);
44
+ return () => document.removeEventListener("mousedown", handleClick);
45
+ }, [menuOpen]);
46
+
47
+ function openMenu() {
48
+ if (isMobile()) {
49
+ setSheetOpen(true);
50
+ } else {
51
+ setMenuOpen((v) => !v);
52
+ }
53
+ }
54
+
55
+ function closeMenu() {
56
+ setMenuOpen(false);
57
+ setSheetOpen(false);
58
+ }
59
+
60
+ async function handleAbort() {
61
+ closeMenu();
62
+ if (!confirm("Abort this task?")) return;
63
+ setAborting(true);
64
+ try {
65
+ await request("task.abort", { id: task.id });
66
+ } catch (err) {
67
+ console.error("Abort failed:", err);
68
+ } finally {
69
+ setAborting(false);
70
+ }
71
+ }
72
+
73
+ async function handleRunNow() {
74
+ closeMenu();
75
+ try {
76
+ const result = await request<{ ok?: boolean; task_id?: string; run_id?: string }>("task.run", { id: task.id });
77
+ onViewRun(task.id, result.run_id);
78
+ } catch (err) {
79
+ console.error("Run failed:", err);
80
+ }
81
+ }
82
+
83
+ async function handleDelete() {
84
+ closeMenu();
85
+ if (!confirm("Delete this task? Results and reports will be kept.")) return;
86
+ try {
87
+ await request("task.delete", { id: task.id });
88
+ onDelete(task.id);
89
+ } catch (err) {
90
+ console.error("Delete failed:", err);
91
+ }
92
+ }
93
+
94
+ const eventLabel: Record<string, string> = {
95
+ started: "Started",
96
+ finished: "Finished",
97
+ aborted: "Aborted",
98
+ failed: "Failed",
99
+ };
100
+
101
+
102
+ function formatTrigger(t: { type: string; value: string }): string {
103
+ if (t.type === "once") {
104
+ const d = new Date(t.value);
105
+ return isNaN(d.getTime()) ? t.value : `Once on ${d.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" })} at ${d.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" })}`;
106
+ }
107
+ const parts = t.value.split(" ");
108
+ if (parts.length !== 5) return t.value;
109
+ const [min, hour, dom, , dow] = parts;
110
+ const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
111
+ if (hour === "*") return "Every hour";
112
+ const d = new Date();
113
+ d.setHours(Number(hour), Number(min), 0, 0);
114
+ const time = d.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" });
115
+ if (dow !== "*") return `Weekly on ${DAYS[Number(dow)] ?? dow} at ${time}`;
116
+ if (dom !== "*") return `Monthly on day ${dom} at ${time}`;
117
+ return `Daily at ${time}`;
118
+ }
119
+
120
+ const triggersText = task.triggers.map(formatTrigger).join(", ");
121
+
122
+ const actionItems = (
123
+ <>
124
+ <button onClick={() => { closeMenu(); onEdit(task); }}>
125
+ <span className="menu-icon">&#9998;</span>Edit
126
+ </button>
127
+ <button onClick={() => { closeMenu(); onViewRun(task.id); }}>
128
+ <span className="menu-icon">&#128196;</span>View All Runs
129
+ </button>
130
+ {isRunning ? (
131
+ <button className="menu-item-danger" onClick={handleAbort} disabled={aborting}>
132
+ <span className="menu-icon">&#9724;</span>Abort
133
+ </button>
134
+ ) : (
135
+ <>
136
+ <button onClick={handleRunNow}>
137
+ <span className="menu-icon">&#9654;</span>Run Now
138
+ </button>
139
+ <button className="menu-item-danger" onClick={handleDelete}>
140
+ <span className="menu-icon">&#128465;</span>Delete
141
+ </button>
142
+ </>
143
+ )}
144
+ </>
145
+ );
146
+
147
+ return (
148
+ <>
149
+ <div className="task-card" onClick={() => isRunning ? onViewRun(task.id, "latest") : onViewRun(task.id)}>
150
+ <div className="task-card-header">
151
+ <div className="task-card-title-row">
152
+ {isRunning ? (
153
+ <span className="status-spinner">
154
+ <span />
155
+ </span>
156
+ ) : (
157
+ <span
158
+ className="status-dot"
159
+ style={{ backgroundColor: stateColor }}
160
+ />
161
+ )}
162
+ <h3 className="task-card-name">{task.name || task.user_prompt}</h3>
163
+ </div>
164
+ <div className="task-card-actions" onClick={(e) => e.stopPropagation()}>
165
+ <div className="task-card-menu" ref={menuRef}>
166
+ <button
167
+ className="task-card-menu-btn"
168
+ onClick={openMenu}
169
+ aria-label="Task actions"
170
+ >
171
+ &#8942;
172
+ </button>
173
+ {menuOpen && (
174
+ <div className="task-card-menu-dropdown">
175
+ {actionItems}
176
+ </div>
177
+ )}
178
+ </div>
179
+ </div>
180
+ </div>
181
+ <div className="task-card-meta">
182
+ {task.agent && (
183
+ <span className="task-card-agent">{getAgentLabel(task.agent)}</span>
184
+ )}
185
+ {lastEvent && eventLabel[lastEvent.running_state] && (
186
+ <span className="task-card-last-event">
187
+ {eventLabel[lastEvent.running_state]}
188
+ {" "}{formatTime(lastEvent.time_stamp)}
189
+ </span>
190
+ )}
191
+ {task.triggers.length > 0 && (
192
+ <span className="task-card-triggers">
193
+ {task.triggers_enabled ? triggersText : "Triggers disabled"}
194
+ </span>
195
+ )}
196
+ </div>
197
+ </div>
198
+
199
+ {sheetOpen && createPortal(
200
+ <div className="bottom-sheet-overlay" onClick={() => setSheetOpen(false)}>
201
+ <div className="bottom-sheet" onClick={(e) => e.stopPropagation()}>
202
+ <div className="bottom-sheet-handle" />
203
+ <div className="bottom-sheet-title">{task.name || task.user_prompt}</div>
204
+ <div className="bottom-sheet-actions">
205
+ {actionItems}
206
+ </div>
207
+ </div>
208
+ </div>,
209
+ document.body,
210
+ )}
211
+ </>
212
+ );
213
+ }