heyiam 0.2.18 → 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.
@@ -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>
@@ -180,7 +180,11 @@ export function createPreviewRouter(ctx) {
180
180
  });
181
181
  // Serve @heyiam/ui mount script for preview pages
182
182
  router.get('/heyiam-mount.js', (_req, res) => {
183
- const mountPath = path.resolve(__dirname, '..', '..', '..', 'packages', 'ui', 'dist', 'mount.js');
183
+ // In built dist: dist/mount.js (copied during build)
184
+ // In dev: ../../../packages/ui/dist/mount.js (monorepo layout)
185
+ const builtPath = path.resolve(__dirname, '..', 'mount.js');
186
+ const devPath = path.resolve(__dirname, '..', '..', '..', 'packages', 'ui', 'dist', 'mount.js');
187
+ const mountPath = existsSync(builtPath) ? builtPath : devPath;
184
188
  try {
185
189
  const js = readFileSync(mountPath, 'utf-8');
186
190
  res.type('application/javascript').send(js);
@@ -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 {
@@ -22,7 +22,7 @@ export function createSettingsRouter(_ctx) {
22
22
  const key = getAnthropicApiKey();
23
23
  res.json({
24
24
  hasKey: !!key,
25
- maskedKey: key ? `${key.slice(0, 4)}...` : null,
25
+ maskedKey: key ? `...${key.slice(-4)}` : null,
26
26
  });
27
27
  });
28
28
  return router;
@@ -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
- if (host === 'localhost' || host === '127.0.0.1' || host === '::1') {
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(host))
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
- app.use(cors({ origin: ['http://localhost:17845', 'http://127.0.0.1:17845', 'http://localhost:5173'] }));
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: '50mb' }));
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
- const indexPath = path.join(staticDir, 'index.html');
114
+ // Express 5 requires { root } option for sendFile (absolute paths fail silently)
112
115
  app.get('/{*splat}', (_req, res) => {
113
- res.sendFile(indexPath, (err) => {
116
+ res.sendFile('index.html', { root: staticDir }, (err) => {
114
117
  if (err && !res.headersSent) {
115
- console.error(`[spa] sendFile failed for ${indexPath}:`, err.message);
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "heyiam",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "description": "Turn AI coding sessions into portfolio case studies",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,7 +23,7 @@
23
23
  ],
24
24
  "scripts": {
25
25
  "clean": "rm -rf dist",
26
- "build": "rm -rf dist && tsc -p tsconfig.build.json && chmod +x dist/index.js && cp -r src/render/templates dist/render/templates && cd app && npx vite build && rm -rf ../dist/public && cp -r dist ../dist/public",
26
+ "build": "rm -rf dist && tsc -p tsconfig.build.json && chmod +x dist/index.js && cp -r src/render/templates dist/render/templates && (test -f ../packages/ui/dist/mount.js && cp ../packages/ui/dist/mount.js dist/mount.js || echo 'Warning: packages/ui/dist/mount.js not found — skipping') && cd app && npx vite build && rm -rf ../dist/public && cp -r dist ../dist/public",
27
27
  "verify-build": "node -e \"const fs=require('fs'); const assert=require('assert'); const pkg=require('./package.json'); assert(fs.existsSync('dist/index.js'),'missing dist/index.js'); assert(fs.existsSync('dist/public/index.html'),'missing dist/public/index.html'); assert(fs.existsSync('dist/render/templates'),'missing templates'); const js=fs.readdirSync('dist/public/assets').filter(f=>f.endsWith('.js')); assert(js.length===1,'expected exactly 1 JS bundle, found '+js.length); const html=fs.readFileSync('dist/public/index.html','utf8'); assert(html.includes(js[0]),'index.html does not reference the JS bundle'); const tests=fs.readdirSync('dist',{recursive:true}).filter(f=>String(f).includes('.test.')); assert(tests.length===0,'test files found in dist: '+tests.join(', ')); console.log('Build verified: v'+pkg.version+' — dist/index.js + frontend + templates, 0 test files')\"",
28
28
  "prepublishOnly": "npm run test:backend && npm run build && npm run verify-build",
29
29
  "prepack": "npm run verify-build",