heyhank 0.1.0 → 0.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/LICENSE +21 -0
- package/README.md +83 -10
- package/bin/cli.ts +7 -7
- package/bin/ctl.ts +42 -42
- package/dist/assets/{AgentsPage-BPhirnCe.js → AgentsPage-B-AAmsMK.js} +3 -3
- package/dist/assets/AssistantPage-BV1Mfwdt.js +2 -0
- package/dist/assets/BusinessPage-tLpNEz19.js +1 -0
- package/dist/assets/{CronManager-DDbz-yiT.js → CronManager-B-K_n3Jg.js} +1 -1
- package/dist/assets/HelpPage-Bhf_j6Xr.js +1 -0
- package/dist/assets/{IntegrationsPage-CrOitCmJ.js → IntegrationsPage-DAMjs9tM.js} +1 -1
- package/dist/assets/JarvisHUD-C_TGXCCn.js +120 -0
- package/dist/assets/MediaPage-C48HTTrt.js +1 -0
- package/dist/assets/MemoryPage-JkC-qtgp.js +1 -0
- package/dist/assets/{PlatformDashboard-Do6F0O2p.js → PlatformDashboard-AUo7tNnE.js} +1 -1
- package/dist/assets/{Playground-Fc5cdc5p.js → Playground-AzNMsRBL.js} +1 -1
- package/dist/assets/{ProcessPanel-CslEiZkI.js → ProcessPanel-DpE_2sX3.js} +1 -1
- package/dist/assets/{PromptsPage-D2EhsdNO.js → PromptsPage-C2RQOs6p.js} +2 -2
- package/dist/assets/RunsPage-B9UOyO79.js +1 -0
- package/dist/assets/{SandboxManager-a1AVI5q2.js → SandboxManager-jHvYjwfh.js} +1 -1
- package/dist/assets/SettingsPage-BBJax6gt.js +51 -0
- package/dist/assets/SkillsMarketplace-IjmjfdjD.js +1 -0
- package/dist/assets/SocialMediaPage-DoPZHhr2.js +10 -0
- package/dist/assets/{TailscalePage-CHiFhZXF.js → TailscalePage-DDEY7ckO.js} +1 -1
- package/dist/assets/TelephonyPage-OPNBZYKt.js +9 -0
- package/dist/assets/{TerminalPage-Drwyrnfd.js → TerminalPage-BjMbHHW3.js} +1 -1
- package/dist/assets/{gemini-live-client-C7rqAW7G.js → gemini-live-client-C70FEtX2.js} +11 -8
- package/dist/assets/{index-CEqZnThB.js → index-BgYM4wXw.js} +94 -93
- package/dist/assets/index-BkjSoVgn.css +32 -0
- package/dist/assets/sw-register-C7NOHtIu.js +1 -0
- package/dist/assets/text-chat-client-BSbLJerZ.js +2 -0
- package/dist/index.html +2 -2
- package/dist/sw.js +1 -1
- package/package.json +6 -1
- package/server/agent-executor.ts +37 -2
- package/server/agent-store.ts +3 -3
- package/server/agent-types.ts +11 -0
- package/server/assistant-store.ts +232 -6
- package/server/auth-manager.ts +9 -0
- package/server/cache-headers.ts +1 -1
- package/server/calendar-service.ts +10 -0
- package/server/ceo/document-store.ts +129 -0
- package/server/ceo/finance-store.ts +343 -0
- package/server/ceo/kpi-store.ts +208 -0
- package/server/ceo/memory-import.ts +277 -0
- package/server/ceo/news-store.ts +208 -0
- package/server/ceo/template-store.ts +134 -0
- package/server/ceo/time-tracking-store.ts +227 -0
- package/server/claude-auth-monitor.ts +128 -0
- package/server/claude-code-worker.ts +86 -0
- package/server/claude-session-discovery.ts +74 -1
- package/server/cli-launcher.ts +32 -10
- package/server/codex-adapter.ts +2 -2
- package/server/codex-ws-proxy.cjs +1 -1
- package/server/container-manager.ts +4 -4
- package/server/content-intelligence/content-engine.ts +1112 -0
- package/server/content-intelligence/platform-knowledge.ts +870 -0
- package/server/cron-store.ts +3 -3
- package/server/embedding-service.ts +49 -0
- package/server/event-bus-types.ts +13 -0
- package/server/federation/node-store.ts +5 -4
- package/server/fs-utils.ts +28 -1
- package/server/hank-notifications-store.ts +91 -0
- package/server/hank-tool-executor.ts +1835 -0
- package/server/hank-tools.ts +2107 -0
- package/server/image-pull-manager.ts +2 -2
- package/server/index.ts +25 -2
- package/server/llm-providers-streaming.ts +541 -0
- package/server/llm-providers.ts +12 -0
- package/server/marketplace.ts +249 -0
- package/server/mcp-registry.ts +158 -0
- package/server/memory-service.ts +296 -0
- package/server/obsidian-sync.ts +184 -0
- package/server/provider-manager.ts +5 -2
- package/server/provider-registry.ts +12 -0
- package/server/reminder-scheduler.ts +37 -1
- package/server/routes/agent-routes.ts +2 -1
- package/server/routes/assistant-routes.ts +198 -5
- package/server/routes/ceo-finance-kpi-routes.ts +167 -0
- package/server/routes/ceo-news-time-routes.ts +137 -0
- package/server/routes/ceo-routes.ts +99 -0
- package/server/routes/content-routes.ts +116 -0
- package/server/routes/email-routes.ts +147 -0
- package/server/routes/env-routes.ts +3 -3
- package/server/routes/fs-routes.ts +12 -9
- package/server/routes/hank-chat-routes.ts +592 -0
- package/server/routes/llm-routes.ts +12 -0
- package/server/routes/marketplace-routes.ts +63 -0
- package/server/routes/media-routes.ts +1 -1
- package/server/routes/memory-routes.ts +127 -0
- package/server/routes/platform-routes.ts +14 -675
- package/server/routes/sandbox-routes.ts +1 -1
- package/server/routes/settings-routes.ts +51 -1
- package/server/routes/socialmedia-routes.ts +152 -2
- package/server/routes/system-routes.ts +2 -2
- package/server/routes/team-routes.ts +71 -0
- package/server/routes/telephony-routes.ts +98 -18
- package/server/routes.ts +36 -9
- package/server/session-creation-service.ts +2 -2
- package/server/session-orchestrator.ts +54 -2
- package/server/session-types.ts +2 -0
- package/server/settings-manager.ts +50 -2
- package/server/skill-discovery.ts +68 -0
- package/server/socialmedia/adapters/browser-adapter.ts +179 -0
- package/server/socialmedia/adapters/postiz-adapter.ts +291 -14
- package/server/socialmedia/manager.ts +234 -15
- package/server/socialmedia/store.ts +51 -1
- package/server/socialmedia/types.ts +35 -2
- package/server/socialview/browser-manager.ts +150 -0
- package/server/socialview/extractors.ts +1298 -0
- package/server/socialview/image-describe.ts +188 -0
- package/server/socialview/library.ts +119 -0
- package/server/socialview/poster.ts +276 -0
- package/server/socialview/routes.ts +371 -0
- package/server/socialview/style-analyzer.ts +187 -0
- package/server/socialview/style-profiles.ts +67 -0
- package/server/socialview/types.ts +166 -0
- package/server/socialview/vision.ts +127 -0
- package/server/socialview/vnc-manager.ts +110 -0
- package/server/style-injector.ts +135 -0
- package/server/team-service.ts +239 -0
- package/server/team-store.ts +75 -0
- package/server/team-types.ts +52 -0
- package/server/telephony/audio-bridge.ts +281 -35
- package/server/telephony/audio-recorder.ts +132 -0
- package/server/telephony/call-manager.ts +803 -104
- package/server/telephony/call-types.ts +67 -1
- package/server/telephony/esl-client.ts +319 -0
- package/server/telephony/freeswitch-sync.ts +155 -0
- package/server/telephony/phone-utils.ts +63 -0
- package/server/telephony/telephony-store.ts +9 -8
- package/server/url-validator.ts +82 -0
- package/server/vault-markdown.ts +317 -0
- package/server/vault-migration.ts +121 -0
- package/server/vault-store.ts +466 -0
- package/server/vault-watcher.ts +59 -0
- package/server/vector-store.ts +210 -0
- package/server/voice-pipeline/gemini-live-adapter.ts +97 -0
- package/server/voice-pipeline/greeting-cache.ts +200 -0
- package/server/voice-pipeline/manager.ts +249 -0
- package/server/voice-pipeline/pipeline.ts +335 -0
- package/server/voice-pipeline/providers/index.ts +47 -0
- package/server/voice-pipeline/providers/llm-internal.ts +527 -0
- package/server/voice-pipeline/providers/stt-google.ts +157 -0
- package/server/voice-pipeline/providers/tts-google.ts +126 -0
- package/server/voice-pipeline/types.ts +247 -0
- package/server/ws-bridge-types.ts +6 -1
- package/dist/assets/AssistantPage-DJ-cMQfb.js +0 -1
- package/dist/assets/HelpPage-DMfkzERp.js +0 -1
- package/dist/assets/MediaPage-CE5rdvkC.js +0 -1
- package/dist/assets/RunsPage-C5BZF5Rx.js +0 -1
- package/dist/assets/SettingsPage-DirhjQrJ.js +0 -51
- package/dist/assets/SocialMediaPage-DBuM28vD.js +0 -1
- package/dist/assets/TelephonyPage-x0VV0fOo.js +0 -1
- package/dist/assets/index-C8M_PUmX.css +0 -32
- package/dist/assets/sw-register-LSSpj6RU.js +0 -1
- package/server/socialmedia/adapters/ayrshare-adapter.ts +0 -169
|
@@ -3,8 +3,61 @@
|
|
|
3
3
|
|
|
4
4
|
import type { Hono } from "hono";
|
|
5
5
|
import * as store from "../assistant-store.js";
|
|
6
|
+
import * as calendarService from "../calendar-service.js";
|
|
7
|
+
import * as emailService from "../email-service.js";
|
|
6
8
|
|
|
7
9
|
export function registerAssistantRoutes(api: Hono): void {
|
|
10
|
+
// ─── Daily Briefing ──────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
api.get("/assistant/briefing", async (c) => {
|
|
13
|
+
const dateStr = (c.req.query("date") as string) || new Date().toISOString().slice(0, 10);
|
|
14
|
+
const today = dateStr;
|
|
15
|
+
const tomorrowDate = new Date(dateStr);
|
|
16
|
+
tomorrowDate.setDate(tomorrowDate.getDate() + 1);
|
|
17
|
+
const tomorrow = tomorrowDate.toISOString().slice(0, 10);
|
|
18
|
+
|
|
19
|
+
let emailSummary: Array<{ accountName: string; email: string; unread: number }> = [];
|
|
20
|
+
let totalUnread = 0;
|
|
21
|
+
try {
|
|
22
|
+
emailSummary = await emailService.getUnreadSummary();
|
|
23
|
+
totalUnread = emailSummary.reduce((s, a) => s + a.unread, 0);
|
|
24
|
+
} catch {}
|
|
25
|
+
|
|
26
|
+
let todayEvents: Array<Record<string, unknown>> = [];
|
|
27
|
+
try {
|
|
28
|
+
const calAccounts = calendarService.loadAccounts();
|
|
29
|
+
for (const acc of calAccounts) {
|
|
30
|
+
const events = await calendarService.listEvents(acc, { from: today, to: tomorrow });
|
|
31
|
+
todayEvents.push(...events.map((e) => ({ ...e, account: acc.name })));
|
|
32
|
+
}
|
|
33
|
+
} catch {}
|
|
34
|
+
|
|
35
|
+
const allTodos = store.listTodos({ done: false });
|
|
36
|
+
const overdueTodos = allTodos.filter((t) => t.dueDate && t.dueDate < today);
|
|
37
|
+
const dueTodayTodos = allTodos.filter((t) => t.dueDate === today);
|
|
38
|
+
|
|
39
|
+
const delegations = store.listDelegations();
|
|
40
|
+
|
|
41
|
+
const projectTodos = store.listTodos();
|
|
42
|
+
const projects: Record<string, { total: number; done: number; open: number }> = {};
|
|
43
|
+
for (const t of projectTodos) {
|
|
44
|
+
if (!t.project) continue;
|
|
45
|
+
if (!projects[t.project]) projects[t.project] = { total: 0, done: 0, open: 0 };
|
|
46
|
+
projects[t.project].total++;
|
|
47
|
+
if (t.done) projects[t.project].done++;
|
|
48
|
+
else projects[t.project].open++;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return c.json({
|
|
52
|
+
date: today,
|
|
53
|
+
email: { accounts: emailSummary, totalUnread },
|
|
54
|
+
calendar: { events: todayEvents, count: todayEvents.length },
|
|
55
|
+
todos: { open: allTodos.length, overdue: overdueTodos.length, dueToday: dueTodayTodos.length },
|
|
56
|
+
delegations: { count: delegations.length },
|
|
57
|
+
projects: Object.entries(projects).map(([name, p]) => ({ name, ...p })),
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
8
61
|
// ─── Todos ──────────────────────────────────────────────────────────
|
|
9
62
|
|
|
10
63
|
api.get("/assistant/todos", (c) => {
|
|
@@ -19,15 +72,21 @@ export function registerAssistantRoutes(api: Hono): void {
|
|
|
19
72
|
});
|
|
20
73
|
|
|
21
74
|
api.post("/assistant/todos", async (c) => {
|
|
22
|
-
const body = await c.req.json<{ text: string; priority?: string; category?: string }>();
|
|
75
|
+
const body = await c.req.json<{ text: string; priority?: string; category?: string; delegatedTo?: string; dueDate?: string; followUpDate?: string; project?: string }>();
|
|
23
76
|
if (!body.text) return c.json({ error: "text is required" }, 400);
|
|
24
|
-
const
|
|
77
|
+
const extra = {
|
|
78
|
+
delegatedTo: body.delegatedTo,
|
|
79
|
+
dueDate: body.dueDate,
|
|
80
|
+
followUpDate: body.followUpDate,
|
|
81
|
+
project: body.project,
|
|
82
|
+
};
|
|
83
|
+
const todo = store.addTodo(body.text, body.priority, body.category, extra);
|
|
25
84
|
return c.json(todo);
|
|
26
85
|
});
|
|
27
86
|
|
|
28
87
|
api.patch("/assistant/todos/:id", async (c) => {
|
|
29
88
|
const id = c.req.param("id");
|
|
30
|
-
const body = await c.req.json<{ text?: string; priority?: string; category?: string; done?: boolean }>();
|
|
89
|
+
const body = await c.req.json<{ text?: string; priority?: string; category?: string; done?: boolean; delegatedTo?: string; dueDate?: string; followUpDate?: string; project?: string }>();
|
|
31
90
|
if (body.done === true) {
|
|
32
91
|
const todo = store.completeTodo(id);
|
|
33
92
|
if (!todo) return c.json({ error: "not found" }, 404);
|
|
@@ -43,6 +102,13 @@ export function registerAssistantRoutes(api: Hono): void {
|
|
|
43
102
|
return c.json({ ok });
|
|
44
103
|
});
|
|
45
104
|
|
|
105
|
+
// ─── Delegations ──────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
api.get("/assistant/delegations", (c) => {
|
|
108
|
+
const person = c.req.query("person");
|
|
109
|
+
return c.json({ delegations: store.listDelegations(person) });
|
|
110
|
+
});
|
|
111
|
+
|
|
46
112
|
// ─── Notes ──────────────────────────────────────────────────────────
|
|
47
113
|
|
|
48
114
|
api.get("/assistant/notes", (c) => {
|
|
@@ -80,11 +146,138 @@ export function registerAssistantRoutes(api: Hono): void {
|
|
|
80
146
|
const body = await c.req.json<{ text: string; triggerAt: string }>();
|
|
81
147
|
if (!body.text || !body.triggerAt) return c.json({ error: "text and triggerAt required" }, 400);
|
|
82
148
|
const reminder = store.addReminder(body.text, body.triggerAt);
|
|
149
|
+
// Try to create a calendar event for the reminder
|
|
150
|
+
try {
|
|
151
|
+
const accounts = calendarService.loadAccounts();
|
|
152
|
+
if (accounts.length > 0) {
|
|
153
|
+
const account = accounts[0]; // Use first calendar account
|
|
154
|
+
const triggerDate = new Date(body.triggerAt);
|
|
155
|
+
const endDate = new Date(triggerDate.getTime() + 15 * 60 * 1000); // 15 min event
|
|
156
|
+
const result = await calendarService.createEvent(account, {
|
|
157
|
+
summary: `Reminder: ${body.text}`,
|
|
158
|
+
description: body.text,
|
|
159
|
+
start: triggerDate.toISOString(),
|
|
160
|
+
end: endDate.toISOString(),
|
|
161
|
+
alarm: 0, // alarm at event time
|
|
162
|
+
});
|
|
163
|
+
// Save calendar UID back to reminder
|
|
164
|
+
store.updateReminder(reminder.id, { calendarEventUid: result.uid });
|
|
165
|
+
reminder.calendarEventUid = result.uid;
|
|
166
|
+
}
|
|
167
|
+
} catch (err) {
|
|
168
|
+
// Calendar integration is best-effort — don't fail the reminder creation
|
|
169
|
+
console.warn("[assistant] Failed to create calendar event for reminder:", err instanceof Error ? err.message : err);
|
|
170
|
+
}
|
|
83
171
|
return c.json(reminder);
|
|
84
172
|
});
|
|
85
173
|
|
|
86
|
-
api.
|
|
87
|
-
const
|
|
174
|
+
api.patch("/assistant/reminders/:id", async (c) => {
|
|
175
|
+
const id = c.req.param("id");
|
|
176
|
+
const body = await c.req.json<{ text?: string; triggerAt?: string }>();
|
|
177
|
+
if (!body.text && !body.triggerAt) return c.json({ error: "Nothing to update" }, 400);
|
|
178
|
+
const updated = store.updateReminder(id, body);
|
|
179
|
+
if (!updated) return c.json({ error: "Not found" }, 404);
|
|
180
|
+
|
|
181
|
+
// If the reminder has a calendar event, try to update it
|
|
182
|
+
if (updated.calendarEventUid && body.triggerAt) {
|
|
183
|
+
try {
|
|
184
|
+
const accounts = calendarService.loadAccounts();
|
|
185
|
+
if (accounts.length > 0) {
|
|
186
|
+
// Delete old event and create new one (simpler than CalDAV update)
|
|
187
|
+
await calendarService.deleteEvent(accounts[0], updated.calendarEventUid).catch(() => {});
|
|
188
|
+
const triggerDate = new Date(body.triggerAt || updated.triggerAt);
|
|
189
|
+
const endDate = new Date(triggerDate.getTime() + 15 * 60 * 1000);
|
|
190
|
+
const result = await calendarService.createEvent(accounts[0], {
|
|
191
|
+
summary: `Reminder: ${updated.text}`,
|
|
192
|
+
description: updated.text,
|
|
193
|
+
start: triggerDate.toISOString(),
|
|
194
|
+
end: endDate.toISOString(),
|
|
195
|
+
alarm: 0,
|
|
196
|
+
});
|
|
197
|
+
store.updateReminder(id, { calendarEventUid: result.uid });
|
|
198
|
+
updated.calendarEventUid = result.uid;
|
|
199
|
+
}
|
|
200
|
+
} catch (err) {
|
|
201
|
+
console.warn("[assistant] Failed to update calendar event:", err instanceof Error ? err.message : err);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return c.json(updated);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
api.delete("/assistant/reminders/:id", async (c) => {
|
|
209
|
+
const id = c.req.param("id");
|
|
210
|
+
// Try to remove associated calendar event
|
|
211
|
+
const reminders = store.listReminders(true);
|
|
212
|
+
const reminder = reminders.find(r => r.id === id);
|
|
213
|
+
if (reminder?.calendarEventUid) {
|
|
214
|
+
try {
|
|
215
|
+
const accounts = calendarService.loadAccounts();
|
|
216
|
+
if (accounts.length > 0) {
|
|
217
|
+
await calendarService.deleteEvent(accounts[0], reminder.calendarEventUid);
|
|
218
|
+
}
|
|
219
|
+
} catch { /* best-effort */ }
|
|
220
|
+
}
|
|
221
|
+
const ok = store.deleteReminder(id);
|
|
222
|
+
return c.json({ ok });
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// ─── Contacts ──────────────────────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
api.get("/assistant/contacts", (c) => {
|
|
228
|
+
const search = c.req.query("search");
|
|
229
|
+
return c.json({ contacts: store.listContacts(search) });
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
api.post("/assistant/contacts", async (c) => {
|
|
233
|
+
const body = await c.req.json<{ name: string; company?: string; email?: string; phone?: string; notes?: string; tags?: string[] }>();
|
|
234
|
+
if (!body.name) return c.json({ error: "name is required" }, 400);
|
|
235
|
+
const contact = store.addContact(body.name, body.company, body.email, body.phone, body.notes, body.tags);
|
|
236
|
+
return c.json(contact);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
api.get("/assistant/contacts/:id", (c) => {
|
|
240
|
+
const contact = store.getContact(c.req.param("id"));
|
|
241
|
+
if (!contact) return c.json({ error: "not found" }, 404);
|
|
242
|
+
return c.json(contact);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
api.patch("/assistant/contacts/:id", async (c) => {
|
|
246
|
+
const body = await c.req.json<{ name?: string; company?: string; email?: string; phone?: string; notes?: string; tags?: string[] }>();
|
|
247
|
+
const contact = store.updateContact(c.req.param("id"), body);
|
|
248
|
+
if (!contact) return c.json({ error: "not found" }, 404);
|
|
249
|
+
return c.json(contact);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
api.delete("/assistant/contacts/:id", (c) => {
|
|
253
|
+
const ok = store.deleteContact(c.req.param("id"));
|
|
254
|
+
return c.json({ ok });
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
api.post("/assistant/contacts/:id/interactions", async (c) => {
|
|
258
|
+
const body = await c.req.json<{ type: "call" | "email" | "meeting" | "note"; summary: string }>();
|
|
259
|
+
if (!body.type || !body.summary) return c.json({ error: "type and summary are required" }, 400);
|
|
260
|
+
const contact = store.logInteraction(c.req.param("id"), { type: body.type, summary: body.summary });
|
|
261
|
+
if (!contact) return c.json({ error: "not found" }, 404);
|
|
262
|
+
return c.json(contact);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// ─── Decisions ─────────────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
api.get("/assistant/decisions", (c) => {
|
|
268
|
+
const search = c.req.query("search");
|
|
269
|
+
return c.json({ decisions: store.listDecisions(search) });
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
api.post("/assistant/decisions", async (c) => {
|
|
273
|
+
const body = await c.req.json<{ title: string; context: string; decision: string; alternatives?: string[]; reasoning?: string; tags?: string[] }>();
|
|
274
|
+
if (!body.title || !body.context || !body.decision) return c.json({ error: "title, context and decision are required" }, 400);
|
|
275
|
+
const decision = store.addDecision(body.title, body.context, body.decision, body.alternatives, body.reasoning, body.tags);
|
|
276
|
+
return c.json(decision);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
api.delete("/assistant/decisions/:id", (c) => {
|
|
280
|
+
const ok = store.deleteDecision(c.req.param("id"));
|
|
88
281
|
return c.json({ ok });
|
|
89
282
|
});
|
|
90
283
|
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import * as financeStore from "../ceo/finance-store.js";
|
|
3
|
+
import * as kpiStore from "../ceo/kpi-store.js";
|
|
4
|
+
|
|
5
|
+
export function registerCeoFinanceKpiRoutes(api: Hono) {
|
|
6
|
+
// === INVOICES ===
|
|
7
|
+
api.get("/assistant/invoices", (c) => {
|
|
8
|
+
const status = c.req.query("status") || undefined;
|
|
9
|
+
const start = c.req.query("start") || undefined;
|
|
10
|
+
const end = c.req.query("end") || undefined;
|
|
11
|
+
return c.json({ invoices: financeStore.listInvoices(status, start, end) });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
api.post("/assistant/invoices", async (c) => {
|
|
15
|
+
const body = await c.req.json();
|
|
16
|
+
if (!body.clientName || !body.items?.length) {
|
|
17
|
+
return c.json({ error: "clientName and items are required" }, 400);
|
|
18
|
+
}
|
|
19
|
+
const invoice = financeStore.createInvoice(body.clientName, body.items, {
|
|
20
|
+
clientEmail: body.clientEmail,
|
|
21
|
+
clientAddress: body.clientAddress,
|
|
22
|
+
taxRate: body.taxRate,
|
|
23
|
+
currency: body.currency,
|
|
24
|
+
dueDate: body.dueDate,
|
|
25
|
+
notes: body.notes
|
|
26
|
+
});
|
|
27
|
+
return c.json(invoice, 201);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
api.get("/assistant/invoices/:id", (c) => {
|
|
31
|
+
const invoice = financeStore.getInvoice(c.req.param("id"));
|
|
32
|
+
if (!invoice) return c.json({ error: "not found" }, 404);
|
|
33
|
+
return c.json(invoice);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
api.patch("/assistant/invoices/:id", async (c) => {
|
|
37
|
+
const body = await c.req.json();
|
|
38
|
+
const invoice = financeStore.updateInvoice(c.req.param("id"), body);
|
|
39
|
+
if (!invoice) return c.json({ error: "not found" }, 404);
|
|
40
|
+
return c.json(invoice);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
api.post("/assistant/invoices/:id/paid", (c) => {
|
|
44
|
+
const invoice = financeStore.markPaid(c.req.param("id"));
|
|
45
|
+
if (!invoice) return c.json({ error: "not found" }, 404);
|
|
46
|
+
return c.json(invoice);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
api.delete("/assistant/invoices/:id", (c) => {
|
|
50
|
+
const ok = financeStore.deleteInvoice(c.req.param("id"));
|
|
51
|
+
if (!ok) return c.json({ error: "not found" }, 404);
|
|
52
|
+
return c.json({ success: true });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// === EXPENSES ===
|
|
56
|
+
api.get("/assistant/expenses", (c) => {
|
|
57
|
+
const category = c.req.query("category") || undefined;
|
|
58
|
+
const start = c.req.query("start") || undefined;
|
|
59
|
+
const end = c.req.query("end") || undefined;
|
|
60
|
+
return c.json({ expenses: financeStore.listExpenses(category, start, end) });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
api.post("/assistant/expenses", async (c) => {
|
|
64
|
+
const body = await c.req.json();
|
|
65
|
+
if (!body.description || body.amount === undefined || !body.category) {
|
|
66
|
+
return c.json({ error: "description, amount, and category are required" }, 400);
|
|
67
|
+
}
|
|
68
|
+
const expense = financeStore.logExpense(body.description, body.amount, body.category, {
|
|
69
|
+
currency: body.currency,
|
|
70
|
+
date: body.date,
|
|
71
|
+
project: body.project,
|
|
72
|
+
vendor: body.vendor,
|
|
73
|
+
recurring: body.recurring,
|
|
74
|
+
notes: body.notes
|
|
75
|
+
});
|
|
76
|
+
return c.json(expense, 201);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
api.delete("/assistant/expenses/:id", (c) => {
|
|
80
|
+
const ok = financeStore.deleteExpense(c.req.param("id"));
|
|
81
|
+
if (!ok) return c.json({ error: "not found" }, 404);
|
|
82
|
+
return c.json({ success: true });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
api.get("/assistant/expenses/categories", (c) => {
|
|
86
|
+
return c.json({ categories: financeStore.listExpenseCategories() });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// === FINANCIAL SUMMARY ===
|
|
90
|
+
api.get("/assistant/finance/summary", (c) => {
|
|
91
|
+
const period = (c.req.query("period") || "month") as "month" | "quarter" | "year" | "custom";
|
|
92
|
+
const start = c.req.query("start") || undefined;
|
|
93
|
+
const end = c.req.query("end") || undefined;
|
|
94
|
+
return c.json(financeStore.getFinancialSummary(period, start, end));
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
api.get("/assistant/finance/settings", (c) => {
|
|
98
|
+
return c.json(financeStore.getFinanceSettings());
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
api.patch("/assistant/finance/settings", async (c) => {
|
|
102
|
+
const body = await c.req.json();
|
|
103
|
+
return c.json(financeStore.updateFinanceSettings(body));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// === KPI ===
|
|
107
|
+
api.get("/assistant/kpis", (c) => {
|
|
108
|
+
const category = c.req.query("category") || undefined;
|
|
109
|
+
return c.json({ kpis: kpiStore.listKPIs(category) });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
api.get("/assistant/kpis/dashboard", (c) => {
|
|
113
|
+
return c.json(kpiStore.getDashboard());
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
api.get("/assistant/kpis/categories", (c) => {
|
|
117
|
+
return c.json({ categories: kpiStore.listCategories() });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
api.post("/assistant/kpis", async (c) => {
|
|
121
|
+
const body = await c.req.json();
|
|
122
|
+
if (!body.name || !body.unit || !body.category) {
|
|
123
|
+
return c.json({ error: "name, unit, and category are required" }, 400);
|
|
124
|
+
}
|
|
125
|
+
const kpi = kpiStore.defineKPI(body.name, body.unit, body.category, {
|
|
126
|
+
description: body.description,
|
|
127
|
+
target: body.target,
|
|
128
|
+
direction: body.direction,
|
|
129
|
+
warningThreshold: body.warningThreshold,
|
|
130
|
+
criticalThreshold: body.criticalThreshold
|
|
131
|
+
});
|
|
132
|
+
return c.json(kpi, 201);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
api.get("/assistant/kpis/:id", (c) => {
|
|
136
|
+
const kpi = kpiStore.getKPI(c.req.param("id"));
|
|
137
|
+
if (!kpi) return c.json({ error: "not found" }, 404);
|
|
138
|
+
return c.json(kpi);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
api.patch("/assistant/kpis/:id", async (c) => {
|
|
142
|
+
const body = await c.req.json();
|
|
143
|
+
const kpi = kpiStore.updateKPI(c.req.param("id"), body);
|
|
144
|
+
if (!kpi) return c.json({ error: "not found" }, 404);
|
|
145
|
+
return c.json(kpi);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
api.post("/assistant/kpis/:id/record", async (c) => {
|
|
149
|
+
const body = await c.req.json();
|
|
150
|
+
if (body.value === undefined) return c.json({ error: "value is required" }, 400);
|
|
151
|
+
const kpi = kpiStore.recordValue(c.req.param("id"), body.value, body.date, body.note);
|
|
152
|
+
if (!kpi) return c.json({ error: "not found" }, 404);
|
|
153
|
+
return c.json(kpi);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
api.get("/assistant/kpis/:id/history", (c) => {
|
|
157
|
+
const period = c.req.query("period") as "week" | "month" | "quarter" | "year" | undefined;
|
|
158
|
+
const history = kpiStore.getKPIHistory(c.req.param("id"), period);
|
|
159
|
+
return c.json({ history });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
api.delete("/assistant/kpis/:id", (c) => {
|
|
163
|
+
const ok = kpiStore.deleteKPI(c.req.param("id"));
|
|
164
|
+
if (!ok) return c.json({ error: "not found" }, 404);
|
|
165
|
+
return c.json({ success: true });
|
|
166
|
+
});
|
|
167
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import * as newsStore from "../ceo/news-store.js";
|
|
3
|
+
import * as timeStore from "../ceo/time-tracking-store.js";
|
|
4
|
+
|
|
5
|
+
export function registerCeoNewsTimeRoutes(api: Hono) {
|
|
6
|
+
// === NEWS SOURCES ===
|
|
7
|
+
api.get("/assistant/news/sources", (c) => {
|
|
8
|
+
return c.json({ sources: newsStore.listSources() });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
api.post("/assistant/news/sources", async (c) => {
|
|
12
|
+
const body = await c.req.json();
|
|
13
|
+
if (!body.name || !body.type || !body.category) {
|
|
14
|
+
return c.json({ error: "name, type, and category are required" }, 400);
|
|
15
|
+
}
|
|
16
|
+
const source = newsStore.addSource(body.name, body.type, body.category, body.url, body.keywords, body.checkInterval);
|
|
17
|
+
return c.json(source, 201);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
api.patch("/assistant/news/sources/:id", async (c) => {
|
|
21
|
+
const body = await c.req.json();
|
|
22
|
+
const source = newsStore.updateSource(c.req.param("id"), body);
|
|
23
|
+
if (!source) return c.json({ error: "not found" }, 404);
|
|
24
|
+
return c.json(source);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
api.delete("/assistant/news/sources/:id", (c) => {
|
|
28
|
+
const ok = newsStore.deleteSource(c.req.param("id"));
|
|
29
|
+
if (!ok) return c.json({ error: "not found" }, 404);
|
|
30
|
+
return c.json({ success: true });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// === NEWS ITEMS ===
|
|
34
|
+
api.get("/assistant/news", (c) => {
|
|
35
|
+
const category = c.req.query("category") || undefined;
|
|
36
|
+
const unreadOnly = c.req.query("unread") === "true";
|
|
37
|
+
const savedOnly = c.req.query("saved") === "true";
|
|
38
|
+
const limit = parseInt(c.req.query("limit") || "50");
|
|
39
|
+
return c.json({ items: newsStore.listNews(category, unreadOnly, savedOnly, limit) });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
api.get("/assistant/news/stats", (c) => {
|
|
43
|
+
return c.json(newsStore.getNewsStats());
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
api.get("/assistant/news/search", (c) => {
|
|
47
|
+
const query = c.req.query("q") || "";
|
|
48
|
+
return c.json({ items: newsStore.searchNews(query) });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
api.post("/assistant/news/items", async (c) => {
|
|
52
|
+
const body = await c.req.json();
|
|
53
|
+
if (!body.sourceId || !body.title || !body.summary || !body.category) {
|
|
54
|
+
return c.json({ error: "sourceId, title, summary, and category are required" }, 400);
|
|
55
|
+
}
|
|
56
|
+
const item = newsStore.addNewsItem(body.sourceId, body.sourceName || "", body.title, body.summary, body.category, body.url, body.relevance);
|
|
57
|
+
return c.json(item, 201);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
api.patch("/assistant/news/:id/read", (c) => {
|
|
61
|
+
const ok = newsStore.markRead(c.req.param("id"));
|
|
62
|
+
if (!ok) return c.json({ error: "not found" }, 404);
|
|
63
|
+
return c.json({ success: true });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
api.post("/assistant/news/mark-all-read", async (c) => {
|
|
67
|
+
const body = await c.req.json().catch(() => ({}));
|
|
68
|
+
const count = newsStore.markAllRead(body.category);
|
|
69
|
+
return c.json({ markedRead: count });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
api.patch("/assistant/news/:id/save", (c) => {
|
|
73
|
+
const item = newsStore.toggleSaved(c.req.param("id"));
|
|
74
|
+
if (!item) return c.json({ error: "not found" }, 404);
|
|
75
|
+
return c.json(item);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// === TIME TRACKING ===
|
|
79
|
+
api.get("/assistant/time/timer", (c) => {
|
|
80
|
+
const timer = timeStore.getActiveTimer();
|
|
81
|
+
return c.json({ timer });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
api.post("/assistant/time/timer/start", async (c) => {
|
|
85
|
+
const body = await c.req.json();
|
|
86
|
+
if (!body.task) return c.json({ error: "task is required" }, 400);
|
|
87
|
+
const timer = timeStore.startTimer(body.task, body.project, body.category);
|
|
88
|
+
return c.json(timer, 201);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
api.post("/assistant/time/timer/stop", async (c) => {
|
|
92
|
+
const body = await c.req.json().catch(() => ({}));
|
|
93
|
+
const entry = timeStore.stopTimer(body.notes);
|
|
94
|
+
if (!entry) return c.json({ error: "no active timer" }, 400);
|
|
95
|
+
return c.json(entry);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
api.post("/assistant/time/log", async (c) => {
|
|
99
|
+
const body = await c.req.json();
|
|
100
|
+
if (!body.task || body.duration === undefined) {
|
|
101
|
+
return c.json({ error: "task and duration are required" }, 400);
|
|
102
|
+
}
|
|
103
|
+
const entry = timeStore.logTime(body.task, body.duration, body.project, body.category, body.notes, body.date);
|
|
104
|
+
return c.json(entry, 201);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
api.get("/assistant/time/entries", (c) => {
|
|
108
|
+
const startDate = c.req.query("start") || undefined;
|
|
109
|
+
const endDate = c.req.query("end") || undefined;
|
|
110
|
+
const project = c.req.query("project") || undefined;
|
|
111
|
+
return c.json({ entries: timeStore.listEntries(startDate, endDate, project) });
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
api.get("/assistant/time/report", (c) => {
|
|
115
|
+
const period = (c.req.query("period") || "week") as "today" | "week" | "month" | "custom";
|
|
116
|
+
const start = c.req.query("start") || undefined;
|
|
117
|
+
const end = c.req.query("end") || undefined;
|
|
118
|
+
return c.json(timeStore.getReport(period, start, end));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
api.get("/assistant/time/projects", (c) => {
|
|
122
|
+
return c.json({ projects: timeStore.listProjects() });
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
api.delete("/assistant/time/entries/:id", (c) => {
|
|
126
|
+
const ok = timeStore.deleteEntry(c.req.param("id"));
|
|
127
|
+
if (!ok) return c.json({ error: "not found" }, 404);
|
|
128
|
+
return c.json({ success: true });
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
api.patch("/assistant/time/entries/:id", async (c) => {
|
|
132
|
+
const body = await c.req.json();
|
|
133
|
+
const entry = timeStore.updateEntry(c.req.param("id"), body);
|
|
134
|
+
if (!entry) return c.json({ error: "not found" }, 404);
|
|
135
|
+
return c.json(entry);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import * as docStore from "../ceo/document-store.js";
|
|
3
|
+
import * as templateStore from "../ceo/template-store.js";
|
|
4
|
+
|
|
5
|
+
export function registerCeoDocumentRoutes(api: Hono) {
|
|
6
|
+
// Documents
|
|
7
|
+
api.get("/assistant/documents", (c) => {
|
|
8
|
+
const folder = c.req.query("folder") || undefined;
|
|
9
|
+
const tag = c.req.query("tag") || undefined;
|
|
10
|
+
return c.json({ documents: docStore.listDocuments(folder, tag) });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
api.post("/assistant/documents", async (c) => {
|
|
14
|
+
const body = await c.req.json();
|
|
15
|
+
if (!body.title || !body.content || !body.fileType) {
|
|
16
|
+
return c.json({ error: "title, content, and fileType are required" }, 400);
|
|
17
|
+
}
|
|
18
|
+
const doc = docStore.addDocument(body.title, body.content, body.fileType, body.folder, body.tags, body.summary);
|
|
19
|
+
return c.json(doc, 201);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
api.get("/assistant/documents/search", (c) => {
|
|
23
|
+
const query = c.req.query("q") || "";
|
|
24
|
+
return c.json({ documents: docStore.searchDocuments(query) });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
api.get("/assistant/documents/folders", (c) => {
|
|
28
|
+
return c.json({ folders: docStore.listFolders() });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
api.get("/assistant/documents/:id", (c) => {
|
|
32
|
+
const result = docStore.getDocument(c.req.param("id"));
|
|
33
|
+
if (!result) return c.json({ error: "not found" }, 404);
|
|
34
|
+
return c.json(result);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
api.patch("/assistant/documents/:id", async (c) => {
|
|
38
|
+
const body = await c.req.json();
|
|
39
|
+
const doc = docStore.updateDocument(c.req.param("id"), body);
|
|
40
|
+
if (!doc) return c.json({ error: "not found" }, 404);
|
|
41
|
+
return c.json(doc);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
api.delete("/assistant/documents/:id", (c) => {
|
|
45
|
+
const ok = docStore.deleteDocument(c.req.param("id"));
|
|
46
|
+
if (!ok) return c.json({ error: "not found" }, 404);
|
|
47
|
+
return c.json({ success: true });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Templates
|
|
51
|
+
api.get("/assistant/templates", (c) => {
|
|
52
|
+
const category = c.req.query("category") || undefined;
|
|
53
|
+
return c.json({ templates: templateStore.listTemplates(category) });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
api.post("/assistant/templates", async (c) => {
|
|
57
|
+
const body = await c.req.json();
|
|
58
|
+
if (!body.name || !body.content || !body.category) {
|
|
59
|
+
return c.json({ error: "name, content, and category are required" }, 400);
|
|
60
|
+
}
|
|
61
|
+
const tpl = templateStore.createTemplate(body.name, body.content, body.category, body.variables, body.tags);
|
|
62
|
+
return c.json(tpl, 201);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
api.get("/assistant/templates/search", (c) => {
|
|
66
|
+
const query = c.req.query("q") || "";
|
|
67
|
+
return c.json({ templates: templateStore.searchTemplates(query) });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
api.get("/assistant/templates/categories", (c) => {
|
|
71
|
+
return c.json({ categories: templateStore.listCategories() });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
api.get("/assistant/templates/:id", (c) => {
|
|
75
|
+
const tpl = templateStore.getTemplate(c.req.param("id"));
|
|
76
|
+
if (!tpl) return c.json({ error: "not found" }, 404);
|
|
77
|
+
return c.json(tpl);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
api.patch("/assistant/templates/:id", async (c) => {
|
|
81
|
+
const body = await c.req.json();
|
|
82
|
+
const tpl = templateStore.updateTemplate(c.req.param("id"), body);
|
|
83
|
+
if (!tpl) return c.json({ error: "not found" }, 404);
|
|
84
|
+
return c.json(tpl);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
api.delete("/assistant/templates/:id", (c) => {
|
|
88
|
+
const ok = templateStore.deleteTemplate(c.req.param("id"));
|
|
89
|
+
if (!ok) return c.json({ error: "not found" }, 404);
|
|
90
|
+
return c.json({ success: true });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
api.post("/assistant/templates/:id/use", async (c) => {
|
|
94
|
+
const body = await c.req.json();
|
|
95
|
+
const result = templateStore.useTemplate(c.req.param("id"), body.variables || {});
|
|
96
|
+
if (!result) return c.json({ error: "not found" }, 404);
|
|
97
|
+
return c.json(result);
|
|
98
|
+
});
|
|
99
|
+
}
|