@agentsean/daemon 1.0.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.
Files changed (82) hide show
  1. package/LICENSE +661 -0
  2. package/dist/action-routes.d.ts +10 -0
  3. package/dist/action-routes.d.ts.map +1 -0
  4. package/dist/action-routes.js +303 -0
  5. package/dist/action-routes.js.map +1 -0
  6. package/dist/activity-page.d.ts +2 -0
  7. package/dist/activity-page.d.ts.map +1 -0
  8. package/dist/activity-page.js +115 -0
  9. package/dist/activity-page.js.map +1 -0
  10. package/dist/bind.d.ts +12 -0
  11. package/dist/bind.d.ts.map +1 -0
  12. package/dist/bind.js +62 -0
  13. package/dist/bind.js.map +1 -0
  14. package/dist/boot.d.ts +15 -0
  15. package/dist/boot.d.ts.map +1 -0
  16. package/dist/boot.js +113 -0
  17. package/dist/boot.js.map +1 -0
  18. package/dist/connect-page.d.ts +2 -0
  19. package/dist/connect-page.d.ts.map +1 -0
  20. package/dist/connect-page.js +170 -0
  21. package/dist/connect-page.js.map +1 -0
  22. package/dist/dashboard-routes.d.ts +16 -0
  23. package/dist/dashboard-routes.d.ts.map +1 -0
  24. package/dist/dashboard-routes.js +1059 -0
  25. package/dist/dashboard-routes.js.map +1 -0
  26. package/dist/events.d.ts +6 -0
  27. package/dist/events.d.ts.map +1 -0
  28. package/dist/events.js +16 -0
  29. package/dist/events.js.map +1 -0
  30. package/dist/google-routes.d.ts +13 -0
  31. package/dist/google-routes.d.ts.map +1 -0
  32. package/dist/google-routes.js +297 -0
  33. package/dist/google-routes.js.map +1 -0
  34. package/dist/hosted-routes.d.ts +7 -0
  35. package/dist/hosted-routes.d.ts.map +1 -0
  36. package/dist/hosted-routes.js +88 -0
  37. package/dist/hosted-routes.js.map +1 -0
  38. package/dist/index.d.ts +10 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +10 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/main.d.ts +3 -0
  43. package/dist/main.d.ts.map +1 -0
  44. package/dist/main.js +25 -0
  45. package/dist/main.js.map +1 -0
  46. package/dist/paths.d.ts +13 -0
  47. package/dist/paths.d.ts.map +1 -0
  48. package/dist/paths.js +49 -0
  49. package/dist/paths.js.map +1 -0
  50. package/dist/pdf.d.ts +3 -0
  51. package/dist/pdf.d.ts.map +1 -0
  52. package/dist/pdf.js +51 -0
  53. package/dist/pdf.js.map +1 -0
  54. package/dist/pid.d.ts +11 -0
  55. package/dist/pid.d.ts.map +1 -0
  56. package/dist/pid.js +38 -0
  57. package/dist/pid.js.map +1 -0
  58. package/dist/security.d.ts +14 -0
  59. package/dist/security.d.ts.map +1 -0
  60. package/dist/security.js +93 -0
  61. package/dist/security.js.map +1 -0
  62. package/dist/server.d.ts +25 -0
  63. package/dist/server.d.ts.map +1 -0
  64. package/dist/server.js +84 -0
  65. package/dist/server.js.map +1 -0
  66. package/dist/settings.d.ts +5 -0
  67. package/dist/settings.d.ts.map +1 -0
  68. package/dist/settings.js +25 -0
  69. package/dist/settings.js.map +1 -0
  70. package/dist/spa.d.ts +4 -0
  71. package/dist/spa.d.ts.map +1 -0
  72. package/dist/spa.js +95 -0
  73. package/dist/spa.js.map +1 -0
  74. package/dist/token.d.ts +6 -0
  75. package/dist/token.d.ts.map +1 -0
  76. package/dist/token.js +35 -0
  77. package/dist/token.js.map +1 -0
  78. package/dist/version.d.ts +2 -0
  79. package/dist/version.d.ts.map +1 -0
  80. package/dist/version.js +2 -0
  81. package/dist/version.js.map +1 -0
  82. package/package.json +81 -0
package/dist/boot.js ADDED
@@ -0,0 +1,113 @@
1
+ import { createHash } from "node:crypto";
2
+ import { openSqlite, assertDbNotWorldReadable } from "@agentsean/db";
3
+ import { createPendingStore } from "@agentsean/google";
4
+ import { createHandlers, createSqliteQueue, startLoop } from "@agentsean/scheduler";
5
+ import { assertBindAllowed, BindError } from "./bind.js";
6
+ import { dbPath, defaultSeanHome, DEFAULT_HOST, DEFAULT_PORT, ensureSeanHome, isHalted, } from "./paths.js";
7
+ import { isPidAlive, readPid, removePid, writePid } from "./pid.js";
8
+ import { createServer } from "./server.js";
9
+ import { loadOrCreateToken, openDaemonStore } from "./token.js";
10
+ import { createEventBus } from "./events.js";
11
+ export async function startDaemon(options = {}) {
12
+ const host = options.host ?? DEFAULT_HOST;
13
+ const port = options.port ?? DEFAULT_PORT;
14
+ const authEnabled = options.authEnabled ?? true;
15
+ assertBindAllowed(host, authEnabled);
16
+ if (!authEnabled) {
17
+ throw new BindError("Auth cannot be disabled. The daemon is fail-closed.");
18
+ }
19
+ const seanHome = ensureSeanHome(options.seanHome ?? defaultSeanHome());
20
+ const existing = readPid(seanHome);
21
+ if (existing && isPidAlive(existing.pid) && existing.pid !== process.pid) {
22
+ throw new Error(`Daemon already running (pid ${existing.pid} on ${existing.host}:${existing.port})`);
23
+ }
24
+ const store = openDaemonStore(seanHome);
25
+ const token = await loadOrCreateToken(store);
26
+ const dbFile = dbPath(seanHome);
27
+ const { sqlite, db } = openSqlite(dbFile);
28
+ assertDbNotWorldReadable(dbFile);
29
+ const pending = createPendingStore();
30
+ const bus = createEventBus();
31
+ const queue = createSqliteQueue(db);
32
+ await queue.recoverStale();
33
+ const tokenValue = token.unwrap();
34
+ const handlers = createHandlers({
35
+ db,
36
+ store,
37
+ approvalKey: createHash("sha256").update(tokenValue).digest(),
38
+ });
39
+ const stopLoop = startLoop(queue, handlers, {
40
+ db,
41
+ halted: () => isHalted(seanHome),
42
+ intervalMs: 15_000,
43
+ onTick: (result) => {
44
+ if (result.ran > 0 || result.recovered > 0)
45
+ bus.emit("jobs");
46
+ },
47
+ });
48
+ const bound = { port };
49
+ const publicOrigin = process.env["SEAN_PUBLIC_ORIGIN"]?.trim();
50
+ const app = await createServer(publicOrigin
51
+ ? {
52
+ host,
53
+ port,
54
+ token: tokenValue,
55
+ authEnabled: true,
56
+ seanHome,
57
+ getPort: () => bound.port,
58
+ db,
59
+ sqlite,
60
+ store,
61
+ pending,
62
+ queue,
63
+ bus,
64
+ publicOrigin,
65
+ }
66
+ : {
67
+ host,
68
+ port,
69
+ token: tokenValue,
70
+ authEnabled: true,
71
+ seanHome,
72
+ getPort: () => bound.port,
73
+ db,
74
+ sqlite,
75
+ store,
76
+ pending,
77
+ queue,
78
+ bus,
79
+ });
80
+ try {
81
+ await app.listen({ host, port, listenTextResolver: () => "" });
82
+ }
83
+ catch (err) {
84
+ sqlite.close();
85
+ throw err;
86
+ }
87
+ const addr = app.server.address();
88
+ const actualPort = typeof addr === "object" && addr !== null ? addr.port : port;
89
+ bound.port = actualPort;
90
+ writePid(seanHome, {
91
+ pid: process.pid,
92
+ host,
93
+ port: actualPort,
94
+ startedAt: new Date().toISOString(),
95
+ });
96
+ const close = async () => {
97
+ await stopLoop();
98
+ await app.close();
99
+ sqlite.close();
100
+ const current = readPid(seanHome);
101
+ if (current?.pid === process.pid)
102
+ removePid(seanHome);
103
+ };
104
+ if (options.registerSignals !== false) {
105
+ const shutdown = () => {
106
+ void close().finally(() => process.exit(0));
107
+ };
108
+ process.once("SIGTERM", shutdown);
109
+ process.once("SIGINT", shutdown);
110
+ }
111
+ return { host, port: actualPort, pid: process.pid, close };
112
+ }
113
+ //# sourceMappingURL=boot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boot.js","sourceRoot":"","sources":["../src/boot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function connectPageHtml(): string;
2
+ //# sourceMappingURL=connect-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect-page.d.ts","sourceRoot":"","sources":["../src/connect-page.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,IAAI,MAAM,CAwKxC"}
@@ -0,0 +1,170 @@
1
+ export function connectPageHtml() {
2
+ return `<!doctype html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="utf-8"/>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
7
+ <title>Connect Google — Agent Sean</title>
8
+ <style>
9
+ :root { color-scheme: light dark; --fg: #111; --muted: #555; --acc: #0b57d0; --bg: #f6f7f9; --card: #fff; --bd: #d0d7de; }
10
+ @media (prefers-color-scheme: dark) {
11
+ :root { --fg: #eef; --muted: #9aa; --acc: #8ab4f8; --bg: #0f1115; --card: #171a21; --bd: #2c313a; }
12
+ }
13
+ body { font: 16px/1.45 system-ui, sans-serif; margin: 0; background: var(--bg); color: var(--fg); }
14
+ main { max-width: 720px; margin: 2rem auto; padding: 0 1.25rem 3rem; }
15
+ h1 { font-size: 1.5rem; margin: 0 0 .25rem; }
16
+ p.lead { color: var(--muted); margin-top: 0; }
17
+ .card { background: var(--card); border: 1px solid var(--bd); border-radius: 12px; padding: 1.25rem; margin: 1rem 0; }
18
+ button, .btn { background: var(--acc); color: #fff; border: 0; border-radius: 8px; padding: .6rem 1rem; font: inherit; cursor: pointer; }
19
+ button.secondary { background: transparent; color: var(--acc); border: 1px solid var(--acc); }
20
+ label { display: block; margin: .75rem 0 .25rem; font-weight: 600; }
21
+ input, select { width: 100%; box-sizing: border-box; padding: .5rem .6rem; border-radius: 8px; border: 1px solid var(--bd); font: inherit; background: var(--card); color: var(--fg); }
22
+ .muted { color: var(--muted); font-size: .9rem; }
23
+ .err { color: #c00; }
24
+ pre { overflow: auto; background: var(--bg); padding: .75rem; border-radius: 8px; font-size: .8rem; }
25
+ .row { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; }
26
+ </style>
27
+ </head>
28
+ <body>
29
+ <main>
30
+ <h1>Connect Google</h1>
31
+ <p class="lead">Search Console, Analytics, CrUX. Under two minutes. The hosted broker never talks to this machine — this page does.</p>
32
+ <div id="app"><p class="muted">Loading…</p></div>
33
+ </main>
34
+ <script>
35
+ const csrf = { "x-sean-csrf": "1" };
36
+ function tokenFromHash() {
37
+ const h = new URLSearchParams(location.hash.replace(/^#/, ""));
38
+ return h.get("token");
39
+ }
40
+ async function session() {
41
+ const t = tokenFromHash();
42
+ const headers = t ? { "x-sean-token": t } : {};
43
+ const r = await fetch("/api/session", { headers });
44
+ if (t && r.ok) history.replaceState(null, "", location.pathname + location.search);
45
+ return r.ok;
46
+ }
47
+ async function api(path, opts = {}) {
48
+ const r = await fetch(path, {
49
+ ...opts,
50
+ headers: { "content-type": "application/json", ...csrf, ...(opts.headers || {}) },
51
+ });
52
+ const text = await r.text();
53
+ let json = null;
54
+ try { json = text ? JSON.parse(text) : null; } catch { json = { raw: text }; }
55
+ if (!r.ok) throw new Error((json && (json.error || json.message)) || r.status + " " + path);
56
+ return json;
57
+ }
58
+ function el(html) { const d = document.createElement("div"); d.innerHTML = html; return d; }
59
+ function render(node) { document.getElementById("app").replaceChildren(node); }
60
+
61
+ async function main() {
62
+ const authed = await session();
63
+ if (!authed) {
64
+ render(el("<div class='card'><p>Open this page from <code>sean connect google</code> so the local session cookie can be set. The CLI prints a loopback URL — do not paste tokens into a hosted page.</p></div>"));
65
+ return;
66
+ }
67
+ const q = new URLSearchParams(location.search);
68
+ if (q.get("error")) {
69
+ render(el("<div class='card'><p class='err'>" + q.get("error") + "</p><button id='retry'>Try again</button></div>"));
70
+ document.getElementById("retry").onclick = () => { location.search = ""; };
71
+ return;
72
+ }
73
+ const status = await api("/api/google/status");
74
+ if (q.get("connected") === "1" && !status.gscSiteUrl) {
75
+ await pick(status);
76
+ return;
77
+ }
78
+ if (status.connected && status.gscSiteUrl) {
79
+ await showConnected(status);
80
+ return;
81
+ }
82
+ await showStart(status);
83
+ }
84
+
85
+ async function showStart(status) {
86
+ const n = el("<div class='card'>"
87
+ + "<p>Sean will request Search Console (including sitemap submit), Analytics read-only, and Site Verification. Business Profile waits until you need local SEO.</p>"
88
+ + "<label>Site origin</label><input id='origin' placeholder='https://example.com' value='" + (status.origin || "") + "'/>"
89
+ + "<p class='muted'>Default path uses the first-party broker on agentsean.com (no client secret in this repo). Advanced users can paste a Desktop-app client instead — Publish the Cloud project to Production or refresh tokens die in 7 days.</p>"
90
+ + "<div class='row'><button id='go'>Connect Google</button><button class='secondary' id='byo'>I have my own Cloud project</button></div>"
91
+ + "<div id='byobox' hidden><label>client_secret.json</label><textarea id='creds' rows='6' style='width:100%'></textarea><button id='gobyo'>Connect with my client</button></div>"
92
+ + "</div>");
93
+ render(n);
94
+ n.querySelector("#go").onclick = async () => {
95
+ const origin = n.querySelector("#origin").value.trim();
96
+ const body = await api("/api/google/connect/start", { method: "POST", body: JSON.stringify({ origin, mode: "broker" }) });
97
+ location.href = body.authorizationUrl;
98
+ };
99
+ n.querySelector("#byo").onclick = () => { n.querySelector("#byobox").hidden = false; };
100
+ n.querySelector("#gobyo").onclick = async () => {
101
+ const origin = n.querySelector("#origin").value.trim();
102
+ const body = await api("/api/google/connect/start", { method: "POST", body: JSON.stringify({ origin, mode: "byo", credentialsJson: n.querySelector("#creds").value }) });
103
+ location.href = body.authorizationUrl;
104
+ };
105
+ }
106
+
107
+ async function pick(status) {
108
+ const disc = await api("/api/google/discover", { method: "POST", body: "{}" });
109
+ const gscOpts = (disc.gscSites || []).map(s => "<option value='" + s.siteUrl + "'" + (s.siteUrl === disc.suggestedGsc ? " selected" : "") + ">" + s.siteUrl + " (" + s.permissionLevel + ")</option>").join("");
110
+ const ga4Opts = (disc.ga4Properties || []).map(p => "<option value='" + p.propertyId + "'>" + p.displayName + " — " + p.propertyId + "</option>").join("");
111
+ const warn = disc.testingModeSuspected ? "<p class='err'>This Google Cloud project looks like Testing mode. Refresh tokens expire after 7 days and silently kill a 24/7 agent. Publish the app to Production (the unverified warning is expected for a personal client).</p>" : "";
112
+ const n = el("<div class='card'>" + warn
113
+ + "<p>Signed in as " + (disc.email || "unknown") + "</p>"
114
+ + "<label>Search Console property</label><select id='gsc'><option value=''>Skip</option>" + gscOpts + "</select>"
115
+ + "<p class='muted'>If the property is missing, Sean can verify it via the Site Verification API — you never open a Google console.</p>"
116
+ + "<label>Verify missing property with</label><select id='method'><option value=''>Don't verify now</option><option value='META'>HTML meta tag</option><option value='FILE'>HTML file</option><option value='DNS_TXT'>DNS TXT</option><option value='ANALYTICS'>Google Analytics</option><option value='TAG_MANAGER'>Tag Manager</option></select>"
117
+ + "<label>GA4 property</label><select id='ga4'><option value=''>Skip</option>" + ga4Opts + "</select>"
118
+ + "<label>PageSpeed / CrUX API key (optional, not OAuth)</label><input id='apikey' placeholder='AIza…'/>"
119
+ + "<p><button id='save'>Save and sync</button></p></div>");
120
+ render(n);
121
+ n.querySelector("#save").onclick = async () => {
122
+ const gsc = n.querySelector("#gsc").value;
123
+ const ga4 = n.querySelector("#ga4").value;
124
+ const method = n.querySelector("#method").value;
125
+ const apikey = n.querySelector("#apikey").value.trim();
126
+ await api("/api/google/properties", { method: "POST", body: JSON.stringify({ gscSiteUrl: gsc || null, ga4PropertyId: ga4 || null, origin: status.origin }) });
127
+ if (method) {
128
+ const v = await api("/api/google/verify", { method: "POST", body: JSON.stringify({ method, origin: status.origin }) });
129
+ if (v.token && !v.verified) {
130
+ render(el("<div class='card'><p>Add this verification token to the site, then click continue.</p><pre>" + JSON.stringify(v, null, 2) + "</pre><button id='cont'>Continue sync</button></div>"));
131
+ document.getElementById("cont").onclick = () => doSync(apikey);
132
+ return;
133
+ }
134
+ }
135
+ await doSync(apikey);
136
+ };
137
+ }
138
+
139
+ async function doSync(apikey) {
140
+ if (apikey) await api("/api/google/api-key", { method: "POST", body: JSON.stringify({ apiKey: apikey }) });
141
+ render(el("<div class='card'><p>Syncing GSC, GA4, CrUX, and Google update timestamps…</p></div>"));
142
+ const result = await api("/api/google/sync", { method: "POST", body: "{}" });
143
+ await showConnected(await api("/api/google/status"), result);
144
+ }
145
+
146
+ async function showConnected(status, sync) {
147
+ const rec = await api("/api/google/reconciliation");
148
+ const inc = await api("/api/google/incidents");
149
+ const residual = (rec.rows || []).slice(-7);
150
+ const n = el("<div>"
151
+ + "<div class='card'><p><strong>Connected.</strong> GSC " + (status.gscSiteUrl || "—") + " · GA4 " + (status.ga4PropertyId || "—") + "</p>"
152
+ + (status.testingModeSuspected ? "<p class='err'>Testing-mode refresh token suspected. Publish the Cloud project to Production.</p>" : "")
153
+ + "<p class='muted'>Default metric is <strong>clicks</strong>. Impressions from 2025-05-13 to 2026-04-27 are contaminated. The &num=100 removal (2025-09-10–14) is annotated so YoY impression charts cannot page you with false decay.</p>"
154
+ + "<div class='row'><button id='resync'>Sync now</button></div></div>"
155
+ + "<div class='card'><h2>GSC vs GA4 residual (last 7 stored days)</h2><pre>" + JSON.stringify(residual, null, 2) + "</pre></div>"
156
+ + "<div class='card'><h2>Google updates on file</h2><pre>" + JSON.stringify((inc.changepoints || []).slice(0, 12), null, 2) + "</pre></div>"
157
+ + (sync ? "<div class='card'><h2>Last sync</h2><pre>" + JSON.stringify(sync, null, 2) + "</pre></div>" : "")
158
+ + "</div>");
159
+ render(n);
160
+ n.querySelector("#resync").onclick = () => doSync("");
161
+ }
162
+
163
+ main().catch((e) => {
164
+ render(el("<div class='card'><p class='err'>" + e.message + "</p></div>"));
165
+ });
166
+ </script>
167
+ </body>
168
+ </html>`;
169
+ }
170
+ //# sourceMappingURL=connect-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect-page.js","sourceRoot":"","sources":["../src/connect-page.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,eAAe;IAC7B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAsKD,CAAC;AACT,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ import { searchFindingsFts, type SqliteDatabase } from "@agentsean/db";
3
+ import { type CredentialStore } from "@agentsean/credentials";
4
+ import { type JobQueue } from "@agentsean/scheduler";
5
+ import type { EventBus } from "./events.js";
6
+ export type DashboardRouteOptions = {
7
+ db: SqliteDatabase;
8
+ sqlite: Parameters<typeof searchFindingsFts>[0];
9
+ seanHome: string;
10
+ token: string;
11
+ bus: EventBus;
12
+ queue?: JobQueue | undefined;
13
+ store?: CredentialStore | undefined;
14
+ };
15
+ export declare function registerDashboardRoutes(app: FastifyInstance, opts: DashboardRouteOptions): void;
16
+ //# sourceMappingURL=dashboard-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-routes.d.ts","sourceRoot":"","sources":["../src/dashboard-routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,EAYL,iBAAiB,EAKjB,KAAK,cAAc,EACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAU,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AA8EtE,OAAO,EAKL,KAAK,QAAQ,EACd,MAAM,sBAAsB,CAAC;AAI9B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,cAAc,CAAC;IACnB,MAAM,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,QAAQ,CAAC;IACd,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC7B,KAAK,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;CACrC,CAAC;AA2BF,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,qBAAqB,GAC1B,IAAI,CAukCN"}