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.
- package/.github/workflows/publish.yml +15 -2
- package/CLAUDE.md +2 -2
- package/DISCLAIMER.md +36 -0
- package/README.md +76 -87
- package/dist/agents/agent-instructions.md +1 -1
- package/dist/agents/agent.d.ts +2 -0
- package/dist/agents/agent.js +21 -0
- package/dist/agents/aider.d.ts +9 -0
- package/dist/agents/aider.js +32 -0
- package/dist/agents/cursor.d.ts +9 -0
- package/dist/agents/cursor.js +35 -0
- package/dist/agents/deepagents.d.ts +9 -0
- package/dist/agents/deepagents.js +35 -0
- package/dist/agents/droid.d.ts +9 -0
- package/dist/agents/droid.js +32 -0
- package/dist/agents/goose.d.ts +9 -0
- package/dist/agents/goose.js +32 -0
- package/dist/agents/opencode.d.ts +9 -0
- package/dist/agents/opencode.js +35 -0
- package/dist/agents/openhands.d.ts +9 -0
- package/dist/agents/openhands.js +35 -0
- package/dist/commands/pair.d.ts +1 -1
- package/dist/commands/pair.js +1 -1
- package/dist/commands/run.js +2 -2
- package/dist/pwa/apple-touch-icon.png +0 -0
- package/dist/pwa/assets/index-ByhOhTz1.js +118 -0
- package/dist/pwa/assets/index-_AmC1Rkn.css +1 -0
- package/dist/pwa/assets/plus-jakarta-sans-latin-ext-wght-normal-DmpS2jIq.woff2 +0 -0
- package/dist/pwa/assets/plus-jakarta-sans-latin-wght-normal-eXO_dkmS.woff2 +0 -0
- package/dist/pwa/assets/plus-jakarta-sans-vietnamese-wght-normal-qRpaaN48.woff2 +0 -0
- package/dist/pwa/favicon.ico +0 -0
- package/dist/pwa/index.html +17 -0
- package/dist/pwa/manifest.webmanifest +1 -0
- package/dist/pwa/pwa-192x192.png +0 -0
- package/dist/pwa/pwa-512x512.png +0 -0
- package/dist/pwa/registerSW.js +1 -0
- package/dist/pwa/service-worker.js +2 -0
- package/dist/rpc-handler.d.ts +4 -0
- package/dist/rpc-handler.js +5 -4
- package/dist/transports/http-transport.js +29 -41
- package/package.json +2 -2
- package/palmier-server/.github/workflows/ci.yml +21 -0
- package/palmier-server/.github/workflows/deploy.yml +38 -0
- package/palmier-server/CLAUDE.md +13 -0
- package/palmier-server/PRODUCTION.md +355 -0
- package/palmier-server/README.md +187 -0
- package/palmier-server/nats.conf +15 -0
- package/palmier-server/package.json +8 -0
- package/palmier-server/pnpm-lock.yaml +6597 -0
- package/palmier-server/pnpm-workspace.yaml +3 -0
- package/palmier-server/pwa/index.html +16 -0
- package/palmier-server/pwa/logo/logo-prompt.md +28 -0
- package/palmier-server/pwa/logo/logo_20260330.png +0 -0
- package/palmier-server/pwa/package.json +30 -0
- package/palmier-server/pwa/public/apple-touch-icon.png +0 -0
- package/palmier-server/pwa/public/favicon.ico +0 -0
- package/palmier-server/pwa/public/pwa-192x192.png +0 -0
- package/palmier-server/pwa/public/pwa-512x512.png +0 -0
- package/palmier-server/pwa/src/App.css +2387 -0
- package/palmier-server/pwa/src/App.tsx +21 -0
- package/palmier-server/pwa/src/agentLabels.ts +11 -0
- package/palmier-server/pwa/src/api.ts +61 -0
- package/palmier-server/pwa/src/components/HostMenu.tsx +289 -0
- package/palmier-server/pwa/src/components/PlanDialog.tsx +41 -0
- package/palmier-server/pwa/src/components/RunDetailView.tsx +293 -0
- package/palmier-server/pwa/src/components/RunsView.tsx +254 -0
- package/palmier-server/pwa/src/components/TabBar.tsx +31 -0
- package/palmier-server/pwa/src/components/TaskCard.tsx +213 -0
- package/palmier-server/pwa/src/components/TaskForm.tsx +580 -0
- package/palmier-server/pwa/src/components/TaskListView.tsx +415 -0
- package/palmier-server/pwa/src/constants.ts +2 -0
- package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +313 -0
- package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +135 -0
- package/palmier-server/pwa/src/formatTime.ts +10 -0
- package/palmier-server/pwa/src/hooks/useBackClose.ts +75 -0
- package/palmier-server/pwa/src/hooks/useMediaQuery.ts +17 -0
- package/palmier-server/pwa/src/hooks/usePushSubscription.ts +75 -0
- package/palmier-server/pwa/src/main.tsx +14 -0
- package/palmier-server/pwa/src/pages/Dashboard.tsx +223 -0
- package/palmier-server/pwa/src/pages/PairHost.tsx +178 -0
- package/palmier-server/pwa/src/service-worker.ts +139 -0
- package/palmier-server/pwa/src/types.ts +79 -0
- package/palmier-server/pwa/src/vite-env.d.ts +11 -0
- package/palmier-server/pwa/tsconfig.json +21 -0
- package/palmier-server/pwa/tsconfig.node.json +19 -0
- package/palmier-server/pwa/vite.config.ts +47 -0
- package/palmier-server/server/.env.example +16 -0
- package/palmier-server/server/package.json +33 -0
- package/palmier-server/server/src/db.ts +34 -0
- package/palmier-server/server/src/index.ts +219 -0
- package/palmier-server/server/src/nats.ts +25 -0
- package/palmier-server/server/src/push.ts +68 -0
- package/palmier-server/server/src/routes/hosts.ts +45 -0
- package/palmier-server/server/src/routes/push.ts +100 -0
- package/palmier-server/server/tsconfig.json +20 -0
- package/palmier-server/spec.md +415 -0
- package/src/agents/agent-instructions.md +1 -1
- package/src/agents/agent.ts +23 -0
- package/src/agents/aider.ts +37 -0
- package/src/agents/cursor.ts +38 -0
- package/src/agents/deepagents.ts +38 -0
- package/src/agents/droid.ts +37 -0
- package/src/agents/goose.ts +35 -0
- package/src/agents/opencode.ts +38 -0
- package/src/agents/openhands.ts +38 -0
- package/src/commands/pair.ts +1 -1
- package/src/commands/run.ts +2 -2
- package/src/rpc-handler.ts +5 -4
- package/src/transports/http-transport.ts +31 -43
- 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">✎</span>Edit
|
|
126
|
+
</button>
|
|
127
|
+
<button onClick={() => { closeMenu(); onViewRun(task.id); }}>
|
|
128
|
+
<span className="menu-icon">📄</span>View All Runs
|
|
129
|
+
</button>
|
|
130
|
+
{isRunning ? (
|
|
131
|
+
<button className="menu-item-danger" onClick={handleAbort} disabled={aborting}>
|
|
132
|
+
<span className="menu-icon">◼</span>Abort
|
|
133
|
+
</button>
|
|
134
|
+
) : (
|
|
135
|
+
<>
|
|
136
|
+
<button onClick={handleRunNow}>
|
|
137
|
+
<span className="menu-icon">▶</span>Run Now
|
|
138
|
+
</button>
|
|
139
|
+
<button className="menu-item-danger" onClick={handleDelete}>
|
|
140
|
+
<span className="menu-icon">🗑</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
|
+
⋮
|
|
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
|
+
}
|