pawmode 1.0.1 → 1.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.
- package/README.md +224 -66
- package/dist/dashboard-server--wwlA0Pa.js +426 -0
- package/dist/dashboard-server-Cg_1CvKn.js +3 -0
- package/dist/index.js +668 -831
- package/dist/permissions-AJXigU7k.js +3 -0
- package/dist/scheduler-DAmd0GzB.js +888 -0
- package/dist/scheduler-DppXPNqK.js +4 -0
- package/dist/{skills-DwMXaN3R.js → skills-CUY0swcW.js} +1 -1
- package/package.json +1 -1
- package/skills/c-clipboard/SKILL.md +53 -0
- package/skills/c-contacts/SKILL.md +63 -0
- package/skills/c-core/SKILL.md +17 -1
- package/skills/c-memory/SKILL.md +32 -0
- package/skills/c-obsidian/SKILL.md +22 -3
- package/skills/c-schedule/SKILL.md +98 -0
- package/skills/c-timer/SKILL.md +59 -0
- package/skills/c-video-edit/SKILL.md +147 -0
- package/skills/c-weather/SKILL.md +61 -0
- package/dist/permissions-CoaVX2ZM.js +0 -3
- /package/dist/{permissions-BHOAvP8i.js → permissions-BlGEHCXO.js} +0 -0
- /package/dist/{skills-CJ_pyPlv.js → skills-CMqq9k1-.js} +0 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import * as os$1 from "node:os";
|
|
2
|
+
import * as fs$1 from "node:fs";
|
|
3
|
+
import * as path$1 from "node:path";
|
|
4
|
+
import * as http from "node:http";
|
|
5
|
+
import * as crypto from "node:crypto";
|
|
6
|
+
|
|
7
|
+
//#region src/core/dashboard-html.ts
|
|
8
|
+
function generateDashboardHTML(theme, botName) {
|
|
9
|
+
const themes = {
|
|
10
|
+
paw: {
|
|
11
|
+
bg: "#1a1008",
|
|
12
|
+
surface: "#241a10",
|
|
13
|
+
surfaceHover: "#2e2218",
|
|
14
|
+
border: "#3a2a18",
|
|
15
|
+
text: "#e8d8c8",
|
|
16
|
+
textDim: "#8a7a6a",
|
|
17
|
+
accent: "#b4783c",
|
|
18
|
+
accentDim: "#8a5a2a",
|
|
19
|
+
done: "#6a9a5a",
|
|
20
|
+
high: "#d44",
|
|
21
|
+
low: "#666"
|
|
22
|
+
},
|
|
23
|
+
midnight: {
|
|
24
|
+
bg: "#0a0a0f",
|
|
25
|
+
surface: "#12121a",
|
|
26
|
+
surfaceHover: "#1a1a25",
|
|
27
|
+
border: "#222233",
|
|
28
|
+
text: "#d0d0e0",
|
|
29
|
+
textDim: "#6a6a8a",
|
|
30
|
+
accent: "#6688cc",
|
|
31
|
+
accentDim: "#445588",
|
|
32
|
+
done: "#5a8a5a",
|
|
33
|
+
high: "#cc5555",
|
|
34
|
+
low: "#555566"
|
|
35
|
+
},
|
|
36
|
+
neon: {
|
|
37
|
+
bg: "#050505",
|
|
38
|
+
surface: "#0a0a0a",
|
|
39
|
+
surfaceHover: "#111111",
|
|
40
|
+
border: "#1a1a1a",
|
|
41
|
+
text: "#e0e0e0",
|
|
42
|
+
textDim: "#555",
|
|
43
|
+
accent: "#00ff88",
|
|
44
|
+
accentDim: "#008844",
|
|
45
|
+
done: "#00cc66",
|
|
46
|
+
high: "#ff3355",
|
|
47
|
+
low: "#444"
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const t = themes[theme];
|
|
51
|
+
return `<!DOCTYPE html>
|
|
52
|
+
<html lang="en">
|
|
53
|
+
<head>
|
|
54
|
+
<meta charset="UTF-8">
|
|
55
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
56
|
+
<title>${botName} — Task Dashboard</title>
|
|
57
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
58
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
59
|
+
<style>
|
|
60
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
61
|
+
:root{
|
|
62
|
+
--bg:${t.bg};--surface:${t.surface};--surface-hover:${t.surfaceHover};
|
|
63
|
+
--border:${t.border};--text:${t.text};--text-dim:${t.textDim};
|
|
64
|
+
--accent:${t.accent};--accent-dim:${t.accentDim};--done:${t.done};
|
|
65
|
+
--high:${t.high};--low:${t.low};
|
|
66
|
+
}
|
|
67
|
+
body{font-family:'JetBrains Mono',monospace;background:var(--bg);color:var(--text);min-height:100vh;font-size:13px}
|
|
68
|
+
header{display:flex;align-items:center;justify-content:space-between;padding:20px 28px;border-bottom:1px solid var(--border)}
|
|
69
|
+
.logo{display:flex;align-items:center;gap:10px;font-size:18px;font-weight:700;color:var(--accent)}
|
|
70
|
+
.theme-switcher{display:flex;gap:8px}
|
|
71
|
+
.theme-dot{width:14px;height:14px;border-radius:50%;cursor:pointer;border:2px solid var(--border);transition:border-color .2s}
|
|
72
|
+
.theme-dot:hover,.theme-dot.active{border-color:var(--text)}
|
|
73
|
+
.theme-dot[data-theme="paw"]{background:#b4783c}
|
|
74
|
+
.theme-dot[data-theme="midnight"]{background:#6688cc}
|
|
75
|
+
.theme-dot[data-theme="neon"]{background:#00ff88}
|
|
76
|
+
.board{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;padding:24px 28px;min-height:calc(100vh - 80px)}
|
|
77
|
+
.column{background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:16px;display:flex;flex-direction:column;min-height:300px}
|
|
78
|
+
.column.drag-over{border-color:var(--accent);background:var(--surface-hover)}
|
|
79
|
+
.col-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid var(--border)}
|
|
80
|
+
.col-title{font-weight:600;font-size:14px;text-transform:uppercase;letter-spacing:1px}
|
|
81
|
+
.col-count{font-size:11px;color:var(--text-dim);background:var(--bg);padding:2px 8px;border-radius:10px}
|
|
82
|
+
.col-todo .col-title{color:var(--accent)}
|
|
83
|
+
.col-progress .col-title{color:var(--text)}
|
|
84
|
+
.col-done .col-title{color:var(--done)}
|
|
85
|
+
.cards{flex:1;display:flex;flex-direction:column;gap:8px;min-height:50px}
|
|
86
|
+
.card{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:12px;cursor:grab;transition:all .15s;position:relative}
|
|
87
|
+
.card:hover{border-color:var(--accent);transform:translateY(-1px)}
|
|
88
|
+
.card.dragging{opacity:.4;transform:scale(.95)}
|
|
89
|
+
.card-title{font-size:13px;font-weight:500;margin-bottom:6px;outline:none}
|
|
90
|
+
.card-title:focus{border-bottom:1px solid var(--accent)}
|
|
91
|
+
.card-desc{font-size:11px;color:var(--text-dim);margin-bottom:8px;outline:none}
|
|
92
|
+
.card-desc:focus{border-bottom:1px solid var(--accent)}
|
|
93
|
+
.card-footer{display:flex;align-items:center;justify-content:space-between}
|
|
94
|
+
.priority{display:flex;gap:4px}
|
|
95
|
+
.priority-dot{width:8px;height:8px;border-radius:50%;cursor:pointer;transition:transform .1s}
|
|
96
|
+
.priority-dot:hover{transform:scale(1.4)}
|
|
97
|
+
.priority-dot.high{background:var(--high)}
|
|
98
|
+
.priority-dot.normal{background:var(--accent)}
|
|
99
|
+
.priority-dot.low{background:var(--low)}
|
|
100
|
+
.priority-dot.active{box-shadow:0 0 0 2px var(--bg),0 0 0 4px currentColor}
|
|
101
|
+
.card-delete{font-size:11px;color:var(--text-dim);cursor:pointer;opacity:0;transition:opacity .15s}
|
|
102
|
+
.card:hover .card-delete{opacity:1}
|
|
103
|
+
.card-delete:hover{color:var(--high)}
|
|
104
|
+
.add-btn{display:flex;align-items:center;justify-content:center;gap:6px;padding:10px;margin-top:8px;border:1px dashed var(--border);border-radius:8px;color:var(--text-dim);cursor:pointer;font-family:inherit;font-size:12px;background:none;transition:all .15s;width:100%}
|
|
105
|
+
.add-btn:hover{border-color:var(--accent);color:var(--accent)}
|
|
106
|
+
.empty{text-align:center;padding:40px 16px;color:var(--text-dim);font-size:12px;line-height:1.8}
|
|
107
|
+
.add-form{display:none;flex-direction:column;gap:8px;margin-top:8px}
|
|
108
|
+
.add-form.show{display:flex}
|
|
109
|
+
.add-form input,.add-form textarea{background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:8px 10px;color:var(--text);font-family:inherit;font-size:12px;outline:none;resize:none}
|
|
110
|
+
.add-form input:focus,.add-form textarea:focus{border-color:var(--accent)}
|
|
111
|
+
.form-actions{display:flex;gap:6px}
|
|
112
|
+
.form-actions button{flex:1;padding:6px;border:1px solid var(--border);border-radius:6px;font-family:inherit;font-size:11px;cursor:pointer;background:var(--surface);color:var(--text);transition:all .15s}
|
|
113
|
+
.form-actions .save{background:var(--accent);color:var(--bg);border-color:var(--accent);font-weight:600}
|
|
114
|
+
.form-actions .save:hover{opacity:.9}
|
|
115
|
+
.form-actions .cancel:hover{border-color:var(--text-dim)}
|
|
116
|
+
</style>
|
|
117
|
+
</head>
|
|
118
|
+
<body>
|
|
119
|
+
<header>
|
|
120
|
+
<div class="logo"><span>🐾</span> ${botName}</div>
|
|
121
|
+
<div class="theme-switcher">
|
|
122
|
+
<div class="theme-dot${theme === "paw" ? " active" : ""}" data-theme="paw" title="Paw"></div>
|
|
123
|
+
<div class="theme-dot${theme === "midnight" ? " active" : ""}" data-theme="midnight" title="Midnight"></div>
|
|
124
|
+
<div class="theme-dot${theme === "neon" ? " active" : ""}" data-theme="neon" title="Neon"></div>
|
|
125
|
+
</div>
|
|
126
|
+
</header>
|
|
127
|
+
<div class="board">
|
|
128
|
+
<div class="column col-todo" data-status="todo">
|
|
129
|
+
<div class="col-header"><span class="col-title">Todo</span><span class="col-count" id="count-todo">0</span></div>
|
|
130
|
+
<div class="cards" id="cards-todo"></div>
|
|
131
|
+
<button class="add-btn" onclick="showForm('todo')">+ Add task</button>
|
|
132
|
+
<div class="add-form" id="form-todo">
|
|
133
|
+
<input type="text" id="input-todo" placeholder="Task title..." onkeydown="if(event.key==='Enter')saveNew('todo')">
|
|
134
|
+
<textarea id="desc-todo" placeholder="Description (optional)" rows="2"></textarea>
|
|
135
|
+
<div class="form-actions">
|
|
136
|
+
<button class="cancel" onclick="hideForm('todo')">Cancel</button>
|
|
137
|
+
<button class="save" onclick="saveNew('todo')">Add</button>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="column col-progress" data-status="in-progress">
|
|
142
|
+
<div class="col-header"><span class="col-title">In Progress</span><span class="col-count" id="count-in-progress">0</span></div>
|
|
143
|
+
<div class="cards" id="cards-in-progress"></div>
|
|
144
|
+
<button class="add-btn" onclick="showForm('in-progress')">+ Add task</button>
|
|
145
|
+
<div class="add-form" id="form-in-progress">
|
|
146
|
+
<input type="text" id="input-in-progress" placeholder="Task title..." onkeydown="if(event.key==='Enter')saveNew('in-progress')">
|
|
147
|
+
<textarea id="desc-in-progress" placeholder="Description (optional)" rows="2"></textarea>
|
|
148
|
+
<div class="form-actions">
|
|
149
|
+
<button class="cancel" onclick="hideForm('in-progress')">Cancel</button>
|
|
150
|
+
<button class="save" onclick="saveNew('in-progress')">Add</button>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
<div class="column col-done" data-status="done">
|
|
155
|
+
<div class="col-header"><span class="col-title">Done</span><span class="col-count" id="count-done">0</span></div>
|
|
156
|
+
<div class="cards" id="cards-done"></div>
|
|
157
|
+
<button class="add-btn" onclick="showForm('done')">+ Add task</button>
|
|
158
|
+
<div class="add-form" id="form-done">
|
|
159
|
+
<input type="text" id="input-done" placeholder="Task title..." onkeydown="if(event.key==='Enter')saveNew('done')">
|
|
160
|
+
<textarea id="desc-done" placeholder="Description (optional)" rows="2"></textarea>
|
|
161
|
+
<div class="form-actions">
|
|
162
|
+
<button class="cancel" onclick="hideForm('done')">Cancel</button>
|
|
163
|
+
<button class="save" onclick="saveNew('done')">Add</button>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
<script>
|
|
169
|
+
let tasks=[];
|
|
170
|
+
let dragId=null;
|
|
171
|
+
|
|
172
|
+
async function api(path,opts){
|
|
173
|
+
const r=await fetch('/api/'+path,{headers:{'Content-Type':'application/json'},...opts});
|
|
174
|
+
return r.json();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function load(){
|
|
178
|
+
tasks=await api('tasks');
|
|
179
|
+
render();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function render(){
|
|
183
|
+
for(const s of['todo','in-progress','done']){
|
|
184
|
+
const container=document.getElementById('cards-'+s);
|
|
185
|
+
const filtered=tasks.filter(t=>t.status===s).sort((a,b)=>a.order-b.order);
|
|
186
|
+
document.getElementById('count-'+s).textContent=filtered.length;
|
|
187
|
+
if(filtered.length===0){
|
|
188
|
+
container.innerHTML='<div class="empty">No tasks here yet.<br>${botName} is waiting for work! 🐾</div>';
|
|
189
|
+
}else{
|
|
190
|
+
container.innerHTML=filtered.map(t=>\`
|
|
191
|
+
<div class="card" draggable="true" data-id="\${t.id}" ondragstart="onDragStart(event)" ondragend="onDragEnd(event)">
|
|
192
|
+
<div class="card-title" contenteditable="true" onblur="updateTitle('\${t.id}',this.textContent)">\${esc(t.title)}</div>
|
|
193
|
+
\${t.description?'<div class="card-desc" contenteditable="true" onblur="updateDesc(\\''+t.id+'\\',this.textContent)">'+esc(t.description)+'</div>':''}
|
|
194
|
+
<div class="card-footer">
|
|
195
|
+
<div class="priority">
|
|
196
|
+
<div class="priority-dot high\${t.priority==='high'?' active':''}" onclick="setPriority('\${t.id}','high')" title="High"></div>
|
|
197
|
+
<div class="priority-dot normal\${t.priority==='normal'?' active':''}" onclick="setPriority('\${t.id}','normal')" title="Normal"></div>
|
|
198
|
+
<div class="priority-dot low\${t.priority==='low'?' active':''}" onclick="setPriority('\${t.id}','low')" title="Low"></div>
|
|
199
|
+
</div>
|
|
200
|
+
<span class="card-delete" onclick="del('\${t.id}')">✕</span>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
\`).join('');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function esc(s){const d=document.createElement('div');d.textContent=s;return d.innerHTML}
|
|
209
|
+
|
|
210
|
+
function showForm(s){document.getElementById('form-'+s).classList.add('show');document.getElementById('input-'+s).focus()}
|
|
211
|
+
function hideForm(s){document.getElementById('form-'+s).classList.remove('show');document.getElementById('input-'+s).value='';document.getElementById('desc-'+s).value=''}
|
|
212
|
+
|
|
213
|
+
async function saveNew(status){
|
|
214
|
+
const title=document.getElementById('input-'+status).value.trim();
|
|
215
|
+
if(!title)return;
|
|
216
|
+
const desc=document.getElementById('desc-'+status).value.trim();
|
|
217
|
+
const task=await api('tasks',{method:'POST',body:JSON.stringify({title,description:desc||undefined,status,priority:'normal'})});
|
|
218
|
+
tasks.push(task);
|
|
219
|
+
render();
|
|
220
|
+
hideForm(status);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function updateTitle(id,title){
|
|
224
|
+
title=title.trim();if(!title)return;
|
|
225
|
+
await api('tasks/'+id,{method:'PUT',body:JSON.stringify({title})});
|
|
226
|
+
const t=tasks.find(x=>x.id===id);if(t)t.title=title;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function updateDesc(id,desc){
|
|
230
|
+
desc=desc.trim();
|
|
231
|
+
await api('tasks/'+id,{method:'PUT',body:JSON.stringify({description:desc||undefined})});
|
|
232
|
+
const t=tasks.find(x=>x.id===id);if(t)t.description=desc||undefined;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function setPriority(id,priority){
|
|
236
|
+
await api('tasks/'+id,{method:'PUT',body:JSON.stringify({priority})});
|
|
237
|
+
const t=tasks.find(x=>x.id===id);if(t)t.priority=priority;
|
|
238
|
+
render();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function del(id){
|
|
242
|
+
await api('tasks/'+id,{method:'DELETE'});
|
|
243
|
+
tasks=tasks.filter(t=>t.id!==id);
|
|
244
|
+
render();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function onDragStart(e){
|
|
248
|
+
dragId=e.target.dataset.id;
|
|
249
|
+
e.target.classList.add('dragging');
|
|
250
|
+
e.dataTransfer.effectAllowed='move';
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function onDragEnd(e){
|
|
254
|
+
e.target.classList.remove('dragging');
|
|
255
|
+
dragId=null;
|
|
256
|
+
document.querySelectorAll('.column').forEach(c=>c.classList.remove('drag-over'));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
document.querySelectorAll('.column').forEach(col=>{
|
|
260
|
+
col.addEventListener('dragover',e=>{e.preventDefault();e.dataTransfer.dropEffect='move';col.classList.add('drag-over')});
|
|
261
|
+
col.addEventListener('dragleave',()=>col.classList.remove('drag-over'));
|
|
262
|
+
col.addEventListener('drop',async e=>{
|
|
263
|
+
e.preventDefault();col.classList.remove('drag-over');
|
|
264
|
+
if(!dragId)return;
|
|
265
|
+
const status=col.dataset.status;
|
|
266
|
+
const order=tasks.filter(t=>t.status===status).length;
|
|
267
|
+
await api('tasks/'+dragId,{method:'PUT',body:JSON.stringify({status,order})});
|
|
268
|
+
const t=tasks.find(x=>x.id===dragId);
|
|
269
|
+
if(t){t.status=status;t.order=order}
|
|
270
|
+
render();
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
document.querySelectorAll('.theme-dot').forEach(dot=>{
|
|
275
|
+
dot.addEventListener('click',()=>{
|
|
276
|
+
window.location.href='/?theme='+dot.dataset.theme;
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
load();
|
|
281
|
+
</script>
|
|
282
|
+
</body>
|
|
283
|
+
</html>`;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region src/core/dashboard-server.ts
|
|
288
|
+
const CONFIG_DIR = path$1.join(os$1.homedir(), ".config", "openpaw");
|
|
289
|
+
const CONFIG_FILE = path$1.join(CONFIG_DIR, "dashboard.json");
|
|
290
|
+
function readConfig() {
|
|
291
|
+
try {
|
|
292
|
+
return JSON.parse(fs$1.readFileSync(CONFIG_FILE, "utf-8"));
|
|
293
|
+
} catch {
|
|
294
|
+
return {
|
|
295
|
+
theme: "paw",
|
|
296
|
+
botName: "Paw",
|
|
297
|
+
port: 3141,
|
|
298
|
+
tasks: []
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function writeConfig(config) {
|
|
303
|
+
if (!fs$1.existsSync(CONFIG_DIR)) fs$1.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
304
|
+
fs$1.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
|
|
305
|
+
}
|
|
306
|
+
function parseBody(req) {
|
|
307
|
+
return new Promise((resolve, reject) => {
|
|
308
|
+
let body = "";
|
|
309
|
+
req.on("data", (chunk) => {
|
|
310
|
+
body += chunk.toString();
|
|
311
|
+
});
|
|
312
|
+
req.on("end", () => {
|
|
313
|
+
try {
|
|
314
|
+
resolve(body ? JSON.parse(body) : {});
|
|
315
|
+
} catch {
|
|
316
|
+
reject(new Error("Invalid JSON"));
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
req.on("error", reject);
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
function json(res, data, status = 200) {
|
|
323
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
324
|
+
res.end(JSON.stringify(data));
|
|
325
|
+
}
|
|
326
|
+
function html(res, content) {
|
|
327
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
328
|
+
res.end(content);
|
|
329
|
+
}
|
|
330
|
+
function startDashboard(opts) {
|
|
331
|
+
const config = readConfig();
|
|
332
|
+
if (opts.theme) config.theme = opts.theme;
|
|
333
|
+
if (opts.botName) config.botName = opts.botName;
|
|
334
|
+
if (opts.port) config.port = opts.port;
|
|
335
|
+
writeConfig(config);
|
|
336
|
+
const port = config.port || 3141;
|
|
337
|
+
const server = http.createServer(async (req, res) => {
|
|
338
|
+
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
339
|
+
const method = req.method || "GET";
|
|
340
|
+
const pathname = url.pathname;
|
|
341
|
+
try {
|
|
342
|
+
if (method === "GET" && pathname === "/") {
|
|
343
|
+
const current = readConfig();
|
|
344
|
+
const themeParam = url.searchParams.get("theme");
|
|
345
|
+
if (themeParam && (themeParam === "paw" || themeParam === "midnight" || themeParam === "neon")) {
|
|
346
|
+
current.theme = themeParam;
|
|
347
|
+
writeConfig(current);
|
|
348
|
+
}
|
|
349
|
+
html(res, generateDashboardHTML(current.theme, current.botName));
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (method === "GET" && pathname === "/api/tasks") {
|
|
353
|
+
json(res, readConfig().tasks);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (method === "POST" && pathname === "/api/tasks") {
|
|
357
|
+
const body = await parseBody(req);
|
|
358
|
+
const current = readConfig();
|
|
359
|
+
const task = {
|
|
360
|
+
id: crypto.randomUUID().slice(0, 8),
|
|
361
|
+
title: String(body.title || "Untitled"),
|
|
362
|
+
description: body.description ? String(body.description) : void 0,
|
|
363
|
+
status: body.status || "todo",
|
|
364
|
+
priority: body.priority || "normal",
|
|
365
|
+
order: current.tasks.filter((t) => t.status === (body.status || "todo")).length,
|
|
366
|
+
createdAt: new Date().toISOString()
|
|
367
|
+
};
|
|
368
|
+
current.tasks.push(task);
|
|
369
|
+
writeConfig(current);
|
|
370
|
+
json(res, task, 201);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const putMatch = method === "PUT" && pathname.match(/^\/api\/tasks\/(.+)$/);
|
|
374
|
+
if (putMatch) {
|
|
375
|
+
const id = putMatch[1];
|
|
376
|
+
const body = await parseBody(req);
|
|
377
|
+
const current = readConfig();
|
|
378
|
+
const task = current.tasks.find((t) => t.id === id);
|
|
379
|
+
if (!task) {
|
|
380
|
+
json(res, { error: "Not found" }, 404);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (body.title !== void 0) task.title = String(body.title);
|
|
384
|
+
if (body.description !== void 0) task.description = body.description ? String(body.description) : void 0;
|
|
385
|
+
if (body.status !== void 0) task.status = body.status;
|
|
386
|
+
if (body.priority !== void 0) task.priority = body.priority;
|
|
387
|
+
if (body.order !== void 0) task.order = Number(body.order);
|
|
388
|
+
writeConfig(current);
|
|
389
|
+
json(res, task);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
const delMatch = method === "DELETE" && pathname.match(/^\/api\/tasks\/(.+)$/);
|
|
393
|
+
if (delMatch) {
|
|
394
|
+
const id = delMatch[1];
|
|
395
|
+
const current = readConfig();
|
|
396
|
+
current.tasks = current.tasks.filter((t) => t.id !== id);
|
|
397
|
+
writeConfig(current);
|
|
398
|
+
json(res, { ok: true });
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (method === "GET" && pathname === "/api/config") {
|
|
402
|
+
const { theme, botName, port: p } = readConfig();
|
|
403
|
+
json(res, {
|
|
404
|
+
theme,
|
|
405
|
+
botName,
|
|
406
|
+
port: p
|
|
407
|
+
});
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
json(res, { error: "Not found" }, 404);
|
|
411
|
+
} catch (err) {
|
|
412
|
+
json(res, { error: "Internal error" }, 500);
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
server.listen(port, () => {
|
|
416
|
+
const url = `http://localhost:${port}`;
|
|
417
|
+
console.log(`\n Dashboard running at ${url}\n`);
|
|
418
|
+
const platform = os$1.platform();
|
|
419
|
+
if (platform === "darwin") import("node:child_process").then((cp) => cp.exec(`open ${url}`));
|
|
420
|
+
else if (platform === "linux") import("node:child_process").then((cp) => cp.exec(`xdg-open ${url}`));
|
|
421
|
+
});
|
|
422
|
+
return server;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
//#endregion
|
|
426
|
+
export { CONFIG_FILE, readConfig, startDashboard, writeConfig };
|