heyiam 0.2.19 → 0.2.20
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/dist/db.js +6 -6
- package/dist/export.js +28 -8
- package/dist/mount.js +10 -4
- package/dist/render/templates/project.liquid +1 -1
- package/dist/routes/publish.js +0 -2
- package/dist/routes/settings.js +1 -1
- package/dist/screenshot.js +8 -3
- package/dist/search.js +3 -2
- package/dist/server.js +11 -7
- package/package.json +1 -1
package/dist/db.js
CHANGED
|
@@ -23,7 +23,7 @@ export function getDatabase(dbPath = getDbPath()) {
|
|
|
23
23
|
_db = openDatabase(dbPath);
|
|
24
24
|
return _db;
|
|
25
25
|
}
|
|
26
|
-
function closeDatabase() {
|
|
26
|
+
export function closeDatabase() {
|
|
27
27
|
if (_db) {
|
|
28
28
|
_db.close();
|
|
29
29
|
_db = null;
|
|
@@ -127,10 +127,10 @@ function migrateToV2(db) {
|
|
|
127
127
|
// Upsert schema version
|
|
128
128
|
const existing = db.prepare('SELECT version FROM schema_version LIMIT 1').get();
|
|
129
129
|
if (existing) {
|
|
130
|
-
db.prepare('UPDATE schema_version SET version = ?').run(
|
|
130
|
+
db.prepare('UPDATE schema_version SET version = ?').run(2);
|
|
131
131
|
}
|
|
132
132
|
else {
|
|
133
|
-
db.prepare('INSERT INTO schema_version (version) VALUES (?)').run(
|
|
133
|
+
db.prepare('INSERT INTO schema_version (version) VALUES (?)').run(2);
|
|
134
134
|
}
|
|
135
135
|
});
|
|
136
136
|
tx();
|
|
@@ -143,7 +143,7 @@ function migrateToV3(db) {
|
|
|
143
143
|
uuid TEXT NOT NULL
|
|
144
144
|
)
|
|
145
145
|
`);
|
|
146
|
-
db.prepare('UPDATE schema_version SET version = ?').run(
|
|
146
|
+
db.prepare('UPDATE schema_version SET version = ?').run(3);
|
|
147
147
|
});
|
|
148
148
|
tx();
|
|
149
149
|
}
|
|
@@ -156,7 +156,7 @@ function migrateToV5(db) {
|
|
|
156
156
|
if (!cols.some(c => c.name === 'output_tokens')) {
|
|
157
157
|
db.exec('ALTER TABLE sessions ADD COLUMN output_tokens INTEGER NOT NULL DEFAULT 0');
|
|
158
158
|
}
|
|
159
|
-
db.prepare('UPDATE schema_version SET version = ?').run(
|
|
159
|
+
db.prepare('UPDATE schema_version SET version = ?').run(5);
|
|
160
160
|
});
|
|
161
161
|
tx();
|
|
162
162
|
}
|
|
@@ -167,7 +167,7 @@ function migrateToV4(db) {
|
|
|
167
167
|
if (!cols.some(c => c.name === 'active_intervals')) {
|
|
168
168
|
db.exec('ALTER TABLE sessions ADD COLUMN active_intervals TEXT');
|
|
169
169
|
}
|
|
170
|
-
db.prepare('UPDATE schema_version SET version = ?').run(
|
|
170
|
+
db.prepare('UPDATE schema_version SET version = ?').run(4);
|
|
171
171
|
});
|
|
172
172
|
tx();
|
|
173
173
|
}
|
package/dist/export.js
CHANGED
|
@@ -260,7 +260,9 @@ export async function exportHtml(dirName, cache, sessions, outputPath, username
|
|
|
260
260
|
arc: result.arc,
|
|
261
261
|
fullSessions: sessions,
|
|
262
262
|
});
|
|
263
|
-
const projectHtml = buildStandalonePage(title, projectBody
|
|
263
|
+
const projectHtml = buildStandalonePage(title, projectBody, {
|
|
264
|
+
description: result.narrative?.slice(0, 200) || undefined,
|
|
265
|
+
});
|
|
264
266
|
totalBytes += writeAndTrack(join(outputPath, 'index.html'), projectHtml, files);
|
|
265
267
|
// Render session pages — only featured sessions (linked from project page)
|
|
266
268
|
const featuredSessions = pickFeaturedSessions(sessions, cache);
|
|
@@ -268,17 +270,19 @@ export async function exportHtml(dirName, cache, sessions, outputPath, username
|
|
|
268
270
|
mkdirSync(sessionsDir, { recursive: true });
|
|
269
271
|
for (const session of featuredSessions) {
|
|
270
272
|
const sessionSlug = slugify(session.title);
|
|
273
|
+
const enhanced = loadEnhancedData(session.id);
|
|
271
274
|
const renderData = buildSessionRenderData({
|
|
272
275
|
sessionId: session.id,
|
|
273
276
|
session,
|
|
274
|
-
enhanced
|
|
277
|
+
enhanced,
|
|
275
278
|
username,
|
|
276
279
|
projectSlug: slug,
|
|
277
280
|
sessionSlug,
|
|
278
281
|
sourceTool: session.source ?? 'unknown',
|
|
279
282
|
});
|
|
280
283
|
const sessionBody = renderSessionHtml(renderData);
|
|
281
|
-
const
|
|
284
|
+
const sessionDesc = (enhanced?.developerTake ?? session.developerTake ?? '').slice(0, 200) || undefined;
|
|
285
|
+
const sessionHtml = buildStandalonePage(session.title, sessionBody, { description: sessionDesc });
|
|
282
286
|
totalBytes += writeAndTrack(join(sessionsDir, `${sessionSlug}.html`), sessionHtml, files);
|
|
283
287
|
}
|
|
284
288
|
return { files, totalBytes, outputPath };
|
|
@@ -364,20 +368,24 @@ export function generateHtmlFiles(dirName, cache, sessions, username = 'local',
|
|
|
364
368
|
arc: result.arc,
|
|
365
369
|
fullSessions: sessions,
|
|
366
370
|
});
|
|
367
|
-
files.push({ path: 'index.html', content: buildStandalonePage(title, projectBody
|
|
371
|
+
files.push({ path: 'index.html', content: buildStandalonePage(title, projectBody, {
|
|
372
|
+
description: result.narrative?.slice(0, 200) || undefined,
|
|
373
|
+
}) });
|
|
368
374
|
const featuredSessions = pickFeaturedSessions(sessions, cache);
|
|
369
375
|
for (const session of featuredSessions) {
|
|
370
376
|
const sessionSlug = slugify(session.title);
|
|
377
|
+
const enhanced = loadEnhancedData(session.id);
|
|
371
378
|
const renderData = buildSessionRenderData({
|
|
372
379
|
sessionId: session.id,
|
|
373
|
-
session, enhanced
|
|
380
|
+
session, enhanced, username,
|
|
374
381
|
projectSlug: slug, sessionSlug,
|
|
375
382
|
sourceTool: session.source ?? 'unknown',
|
|
376
383
|
});
|
|
377
384
|
const sessionBody = renderSessionHtml(renderData);
|
|
385
|
+
const sessionDesc = (enhanced?.developerTake ?? session.developerTake ?? '').slice(0, 200) || undefined;
|
|
378
386
|
files.push({
|
|
379
387
|
path: `sessions/${sessionSlug}.html`,
|
|
380
|
-
content: buildStandalonePage(session.title, sessionBody),
|
|
388
|
+
content: buildStandalonePage(session.title, sessionBody, { description: sessionDesc }),
|
|
381
389
|
});
|
|
382
390
|
}
|
|
383
391
|
return files;
|
|
@@ -477,19 +485,31 @@ function getInlineMountJs() {
|
|
|
477
485
|
return '';
|
|
478
486
|
}
|
|
479
487
|
}
|
|
480
|
-
function buildStandalonePage(title, bodyHtml) {
|
|
488
|
+
function buildStandalonePage(title, bodyHtml, opts) {
|
|
481
489
|
const css = getInlineCss();
|
|
482
490
|
const cssTag = css
|
|
483
491
|
? `<style>${css}\nbody { overflow: auto !important; min-height: auto !important; background: var(--color-surface, #f8f9fb); }</style>`
|
|
484
492
|
: '';
|
|
485
493
|
const mountJs = getInlineMountJs();
|
|
486
494
|
const scriptTag = mountJs ? `<script>${mountJs}</script>` : '';
|
|
495
|
+
const safeTitle = escapeHtml(title);
|
|
496
|
+
const safeDesc = opts?.description ? escapeHtml(opts.description) : '';
|
|
497
|
+
const ogTitle = `${safeTitle} — heyi.am`;
|
|
498
|
+
const ogTags = `<meta property="og:title" content="${ogTitle}" />
|
|
499
|
+
<meta property="og:site_name" content="heyi.am" />
|
|
500
|
+
<meta property="og:type" content="article" />
|
|
501
|
+
${safeDesc ? `<meta property="og:description" content="${safeDesc}" />` : ''}
|
|
502
|
+
${safeDesc ? `<meta name="description" content="${safeDesc}" />` : ''}
|
|
503
|
+
<meta name="twitter:card" content="summary" />
|
|
504
|
+
<meta name="twitter:title" content="${ogTitle}" />
|
|
505
|
+
${safeDesc ? `<meta name="twitter:description" content="${safeDesc}" />` : ''}`;
|
|
487
506
|
return `<!DOCTYPE html>
|
|
488
507
|
<html lang="en">
|
|
489
508
|
<head>
|
|
490
509
|
<meta charset="utf-8" />
|
|
491
510
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
492
|
-
<title>${
|
|
511
|
+
<title>${ogTitle}</title>
|
|
512
|
+
${ogTags}
|
|
493
513
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
494
514
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
495
515
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
package/dist/mount.js
CHANGED
|
@@ -22991,9 +22991,9 @@
|
|
|
22991
22991
|
] }),
|
|
22992
22992
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: "0.75rem", marginBottom: "1.25rem" }, children: [
|
|
22993
22993
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatBox, { label: "Active Time", value: formatDuration2(session.durationMinutes), primary: true, children: hasChildren && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SplitLine, { you: formatDuration2(Math.max(0, session.durationMinutes - session.children.reduce((s, c) => s + c.durationMinutes, 0))), agent: formatDuration2(session.children.reduce((s, c) => s + c.durationMinutes, 0)) }) }),
|
|
22994
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatBox, { label: "Turns", value: session.turns, children: hasChildren && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SplitLine, { you: "
|
|
22994
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatBox, { label: "Turns", value: session.turns, children: hasChildren && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SplitLine, { you: "Human", agent: `${session.children.length} agents` }) }),
|
|
22995
22995
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatBox, { label: "Files", value: session.filesChanged?.length === 1 && session.filesChanged[0]?.path === "(aggregate)" ? "\u2014" : session.filesChanged?.length ?? "\u2014" }),
|
|
22996
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatBox, { label: "
|
|
22996
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatBox, { label: "Lines changed", value: formatLoc2(session.linesOfCode), children: hasChildren && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SplitLine, { you: formatLoc2(Math.max(0, session.linesOfCode - session.children.reduce((s, c) => s + c.linesOfCode, 0))), agent: formatLoc2(session.children.reduce((s, c) => s + c.linesOfCode, 0)) }) })
|
|
22997
22997
|
] }),
|
|
22998
22998
|
session.developerTake && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: { fontSize: "0.9375rem", lineHeight: 1.6, color: "var(--on-surface, #191c1e)", borderLeft: "3px solid var(--primary, #084471)", paddingLeft: "0.75rem", marginBottom: "1.25rem" }, children: session.developerTake }),
|
|
22999
22999
|
session.skills && session.skills.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { display: "flex", flexWrap: "wrap", gap: "0.375rem", marginBottom: "1.25rem" }, children: session.skills.map((skill) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { fontFamily: "var(--font-mono, monospace)", fontSize: "11px", padding: "0.125rem 0.5rem", borderRadius: "0.25rem", background: "var(--violet-bg, #ede9fe)", color: "var(--violet, #6d28d9)" }, children: skill }, skill)) }),
|
|
@@ -23069,12 +23069,18 @@
|
|
|
23069
23069
|
const [active, setActive] = (0, import_react3.useState)(null);
|
|
23070
23070
|
showOverlay = (session) => setActive(session);
|
|
23071
23071
|
if (!active) return null;
|
|
23072
|
-
const
|
|
23072
|
+
const projectEl = document.querySelector("[data-session-base-url]");
|
|
23073
|
+
const baseUrl = projectEl?.getAttribute("data-session-base-url");
|
|
23074
|
+
let sessionPageUrl;
|
|
23075
|
+
if (baseUrl) {
|
|
23076
|
+
const slug = active.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 80) || "untitled";
|
|
23077
|
+
sessionPageUrl = `${baseUrl}/${slug}.html`;
|
|
23078
|
+
}
|
|
23073
23079
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
23074
23080
|
SessionOverlay,
|
|
23075
23081
|
{
|
|
23076
23082
|
session: active,
|
|
23077
|
-
sessionPageUrl
|
|
23083
|
+
sessionPageUrl,
|
|
23078
23084
|
onClose: () => setActive(null)
|
|
23079
23085
|
}
|
|
23080
23086
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<div class="heyiam-project" data-render-version="2">
|
|
1
|
+
<div class="heyiam-project" data-render-version="2"{% if sessionBaseUrl %} data-session-base-url="{{ sessionBaseUrl }}"{% endif %}>
|
|
2
2
|
|
|
3
3
|
{%- comment -%} Title {%- endcomment -%}
|
|
4
4
|
<h1 class="project-title">{{ project.title }}</h1>
|
package/dist/routes/publish.js
CHANGED
|
@@ -134,7 +134,6 @@ export function createPublishRouter(ctx) {
|
|
|
134
134
|
}
|
|
135
135
|
const projectData = await projectRes.json();
|
|
136
136
|
send({ type: 'project', status: 'created', projectId: projectData.project_id, slug: projectData.slug });
|
|
137
|
-
let screenshotUploaded = false;
|
|
138
137
|
// Step 1b: Upload screenshot (non-fatal)
|
|
139
138
|
if (screenshotBase64 || projectUrl) {
|
|
140
139
|
try {
|
|
@@ -177,7 +176,6 @@ export function createPublishRouter(ctx) {
|
|
|
177
176
|
},
|
|
178
177
|
body: JSON.stringify({ key }),
|
|
179
178
|
});
|
|
180
|
-
screenshotUploaded = true;
|
|
181
179
|
send({ type: 'screenshot', status: 'uploaded' });
|
|
182
180
|
}
|
|
183
181
|
else {
|
package/dist/routes/settings.js
CHANGED
package/dist/screenshot.js
CHANGED
|
@@ -52,14 +52,19 @@ function isUrlSafe(raw) {
|
|
|
52
52
|
return false;
|
|
53
53
|
// Reject localhost and private IPs
|
|
54
54
|
const host = parsed.hostname.toLowerCase();
|
|
55
|
-
|
|
55
|
+
// Strip IPv6 brackets for comparison
|
|
56
|
+
const bare = host.startsWith('[') ? host.slice(1, -1) : host;
|
|
57
|
+
if (bare === 'localhost' || bare === '127.0.0.1' || bare === '::1' || bare === '0.0.0.0') {
|
|
56
58
|
// Allow our own preview server
|
|
57
59
|
const port = parsed.port || (parsed.protocol === 'https:' ? '443' : '80');
|
|
58
60
|
if (port !== '17845')
|
|
59
61
|
return false;
|
|
60
62
|
}
|
|
61
|
-
// Reject private IP ranges
|
|
62
|
-
if (/^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.|0\.)/.test(
|
|
63
|
+
// Reject private IP ranges (IPv4)
|
|
64
|
+
if (/^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.|0\.)/.test(bare))
|
|
65
|
+
return false;
|
|
66
|
+
// Reject IPv6 private ranges (link-local, ULA, loopback, IPv4-mapped)
|
|
67
|
+
if (/^(fe80:|fc|fd|::ffff:(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|127\.))/i.test(bare))
|
|
63
68
|
return false;
|
|
64
69
|
if (host.endsWith('.local') || host.endsWith('.internal'))
|
|
65
70
|
return false;
|
package/dist/search.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Search logic combining FTS5 content search with metadata filters
|
|
2
2
|
import { searchFts } from './db.js';
|
|
3
|
+
import { escapeLikeWildcards } from './format-utils.js';
|
|
3
4
|
// ── Helpers ──────────────────────────────────────────────────
|
|
4
5
|
/**
|
|
5
6
|
* Decode projectDir to a human-readable project name.
|
|
@@ -69,7 +70,7 @@ function searchWithFilters(db, filters) {
|
|
|
69
70
|
const params = [];
|
|
70
71
|
if (filters?.project) {
|
|
71
72
|
conditions.push('s.project_dir LIKE ?');
|
|
72
|
-
params.push(`%${filters.project}%`);
|
|
73
|
+
params.push(`%${escapeLikeWildcards(filters.project)}%`);
|
|
73
74
|
}
|
|
74
75
|
if (filters?.source) {
|
|
75
76
|
conditions.push('s.source = ?');
|
|
@@ -103,7 +104,7 @@ function searchWithFilters(db, filters) {
|
|
|
103
104
|
ORDER BY s.start_time DESC
|
|
104
105
|
LIMIT ?
|
|
105
106
|
`;
|
|
106
|
-
params.unshift(`%${filters.file}%`);
|
|
107
|
+
params.unshift(`%${escapeLikeWildcards(filters.file)}%`);
|
|
107
108
|
params.push(MAX_RESULTS);
|
|
108
109
|
}
|
|
109
110
|
else {
|
package/dist/server.js
CHANGED
|
@@ -5,7 +5,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from '
|
|
|
5
5
|
import { execFileSync } from 'node:child_process';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { homedir } from 'node:os';
|
|
8
|
-
import { getDatabase } from './db.js';
|
|
8
|
+
import { getDatabase, closeDatabase } from './db.js';
|
|
9
9
|
import { syncWithTracking, startFileWatcher, startCursorPolling, markSyncPending } from './sync.js';
|
|
10
10
|
import { createRouteContext, createProjectsRouter, createEnhanceRouter, createPublishRouter, createSearchRouter, createSessionsRouter, createArchiveRouter, createAuthRouter, createSettingsRouter, createExportRouter, createPreviewRouter, createDashboardRouter, } from './routes/index.js';
|
|
11
11
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -77,13 +77,16 @@ export function createApp(sessionsBasePath, dbPath) {
|
|
|
77
77
|
}
|
|
78
78
|
next();
|
|
79
79
|
});
|
|
80
|
-
|
|
80
|
+
const corsOrigins = ['http://localhost:17845', 'http://127.0.0.1:17845'];
|
|
81
|
+
if (process.env.NODE_ENV !== 'production')
|
|
82
|
+
corsOrigins.push('http://localhost:5173');
|
|
83
|
+
app.use(cors({ origin: corsOrigins }));
|
|
81
84
|
app.use((_req, res, next) => {
|
|
82
85
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
83
86
|
res.setHeader('X-Frame-Options', 'DENY');
|
|
84
87
|
next();
|
|
85
88
|
});
|
|
86
|
-
app.use(express.json({ limit: '
|
|
89
|
+
app.use(express.json({ limit: '10mb' }));
|
|
87
90
|
// ── Mount domain routers ───────────────────────────────────
|
|
88
91
|
app.use(createProjectsRouter(ctx));
|
|
89
92
|
app.use(createEnhanceRouter(ctx));
|
|
@@ -108,11 +111,11 @@ export function createApp(sessionsBasePath, dbPath) {
|
|
|
108
111
|
const staticDir = existsSync(prodDir) ? prodDir : devDir;
|
|
109
112
|
app.use(express.static(staticDir));
|
|
110
113
|
// SPA fallback -- serve index.html for non-API routes
|
|
111
|
-
|
|
114
|
+
// Express 5 requires { root } option for sendFile (absolute paths fail silently)
|
|
112
115
|
app.get('/{*splat}', (_req, res) => {
|
|
113
|
-
res.sendFile(
|
|
116
|
+
res.sendFile('index.html', { root: staticDir }, (err) => {
|
|
114
117
|
if (err && !res.headersSent) {
|
|
115
|
-
console.error(`[spa] sendFile failed for ${
|
|
118
|
+
console.error(`[spa] sendFile failed for ${staticDir}/index.html:`, err.message);
|
|
116
119
|
res.status(404).send('Page not found');
|
|
117
120
|
}
|
|
118
121
|
});
|
|
@@ -146,11 +149,12 @@ export function startServer(port = 17845, options) {
|
|
|
146
149
|
server.on('close', () => {
|
|
147
150
|
stopFileWatcher();
|
|
148
151
|
stopCursorPolling();
|
|
152
|
+
closeDatabase();
|
|
149
153
|
removeServerPidFile();
|
|
150
154
|
});
|
|
151
155
|
}
|
|
152
156
|
else {
|
|
153
|
-
server.on('close', () => { removeServerPidFile(); });
|
|
157
|
+
server.on('close', () => { closeDatabase(); removeServerPidFile(); });
|
|
154
158
|
}
|
|
155
159
|
resolve(server);
|
|
156
160
|
});
|