heyiam 0.2.28 → 0.3.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 +45 -0
- package/dist/config.js +10 -1
- package/dist/db.js +1 -2
- package/dist/export.js +40 -25
- package/dist/format-utils.js +5 -0
- package/dist/index.js +168 -0
- package/dist/mount.js +300 -102
- package/dist/parsers/claude.js +2 -28
- package/dist/parsers/codex.js +2 -26
- package/dist/parsers/cursor.js +2 -26
- package/dist/parsers/duration.js +35 -0
- package/dist/parsers/gemini.js +2 -20
- package/dist/parsers/types.js +0 -1
- package/dist/public/assets/index-BZ65TU_Y.js +40 -0
- package/dist/public/assets/index-CqCaW2cb.css +1 -0
- package/dist/public/index.html +2 -2
- package/dist/redact.js +4 -104
- package/dist/render/build-render-data.js +9 -2
- package/dist/render/index.js +32 -5
- package/dist/render/liquid.js +147 -7
- package/dist/render/mock-data.js +303 -0
- package/dist/render/templates/aurora/portfolio.liquid +204 -0
- package/dist/render/templates/aurora/project.liquid +260 -0
- package/dist/render/templates/aurora/session.liquid +223 -0
- package/dist/render/templates/aurora/styles.css +1178 -0
- package/dist/render/templates/bauhaus/portfolio.liquid +179 -0
- package/dist/render/templates/bauhaus/project.liquid +300 -0
- package/dist/render/templates/bauhaus/session.liquid +333 -0
- package/dist/render/templates/bauhaus/styles.css +1641 -0
- package/dist/render/templates/blueprint/portfolio.liquid +167 -0
- package/dist/render/templates/blueprint/project.liquid +286 -0
- package/dist/render/templates/blueprint/session.liquid +248 -0
- package/dist/render/templates/blueprint/styles.css +1285 -0
- package/dist/render/templates/canvas/portfolio.liquid +215 -0
- package/dist/render/templates/canvas/project.liquid +235 -0
- package/dist/render/templates/canvas/session.liquid +223 -0
- package/dist/render/templates/canvas/styles.css +1436 -0
- package/dist/render/templates/carbon/portfolio.liquid +170 -0
- package/dist/render/templates/carbon/project.liquid +249 -0
- package/dist/render/templates/carbon/session.liquid +190 -0
- package/dist/render/templates/carbon/styles.css +1091 -0
- package/dist/render/templates/chalk/portfolio.liquid +199 -0
- package/dist/render/templates/chalk/project.liquid +245 -0
- package/dist/render/templates/chalk/session.liquid +215 -0
- package/dist/render/templates/chalk/styles.css +1157 -0
- package/dist/render/templates/circuit/portfolio.liquid +162 -0
- package/dist/render/templates/circuit/project.liquid +247 -0
- package/dist/render/templates/circuit/session.liquid +205 -0
- package/dist/render/templates/circuit/styles.css +1403 -0
- package/dist/render/templates/cosmos/portfolio.liquid +232 -0
- package/dist/render/templates/cosmos/project.liquid +327 -0
- package/dist/render/templates/cosmos/session.liquid +239 -0
- package/dist/render/templates/cosmos/styles.css +1151 -0
- package/dist/render/templates/daylight/portfolio.liquid +217 -0
- package/dist/render/templates/daylight/project.liquid +229 -0
- package/dist/render/templates/daylight/session.liquid +219 -0
- package/dist/render/templates/daylight/styles.css +1311 -0
- package/dist/render/templates/editorial/portfolio.liquid +126 -0
- package/dist/render/templates/editorial/project.liquid +202 -0
- package/dist/render/templates/editorial/session.liquid +171 -0
- package/dist/render/templates/editorial/styles.css +822 -0
- package/dist/render/templates/ember/portfolio.liquid +318 -0
- package/dist/render/templates/ember/project.liquid +232 -0
- package/dist/render/templates/ember/session.liquid +202 -0
- package/dist/render/templates/ember/styles.css +1283 -0
- package/dist/render/templates/glacier/portfolio.liquid +271 -0
- package/dist/render/templates/glacier/project.liquid +288 -0
- package/dist/render/templates/glacier/session.liquid +217 -0
- package/dist/render/templates/glacier/styles.css +1200 -0
- package/dist/render/templates/grid/portfolio.liquid +265 -0
- package/dist/render/templates/grid/project.liquid +306 -0
- package/dist/render/templates/grid/session.liquid +260 -0
- package/dist/render/templates/grid/styles.css +1441 -0
- package/dist/render/templates/kinetic/portfolio.liquid +170 -0
- package/dist/render/templates/kinetic/project.liquid +242 -0
- package/dist/render/templates/kinetic/session.liquid +228 -0
- package/dist/render/templates/kinetic/styles.css +944 -0
- package/dist/render/templates/meridian/portfolio.liquid +255 -0
- package/dist/render/templates/meridian/project.liquid +376 -0
- package/dist/render/templates/meridian/session.liquid +298 -0
- package/dist/render/templates/meridian/styles.css +1369 -0
- package/dist/render/templates/minimal/portfolio.liquid +71 -0
- package/dist/render/templates/minimal/project.liquid +154 -0
- package/dist/render/templates/minimal/session.liquid +140 -0
- package/dist/render/templates/minimal/styles.css +525 -0
- package/dist/render/templates/mono/portfolio.liquid +291 -0
- package/dist/render/templates/mono/project.liquid +275 -0
- package/dist/render/templates/mono/session.liquid +276 -0
- package/dist/render/templates/mono/styles.css +1016 -0
- package/dist/render/templates/neon/portfolio.liquid +217 -0
- package/dist/render/templates/neon/project.liquid +225 -0
- package/dist/render/templates/neon/session.liquid +195 -0
- package/dist/render/templates/neon/styles.css +1265 -0
- package/dist/render/templates/noir/portfolio.liquid +137 -0
- package/dist/render/templates/noir/project.liquid +220 -0
- package/dist/render/templates/noir/session.liquid +241 -0
- package/dist/render/templates/noir/styles.css +1223 -0
- package/dist/render/templates/obsidian/portfolio.liquid +257 -0
- package/dist/render/templates/obsidian/project.liquid +280 -0
- package/dist/render/templates/obsidian/session.liquid +241 -0
- package/dist/render/templates/obsidian/styles.css +1401 -0
- package/dist/render/templates/paper/portfolio.liquid +267 -0
- package/dist/render/templates/paper/project.liquid +235 -0
- package/dist/render/templates/paper/session.liquid +271 -0
- package/dist/render/templates/paper/styles.css +1509 -0
- package/dist/render/templates/parallax/portfolio.liquid +305 -0
- package/dist/render/templates/parallax/project.liquid +275 -0
- package/dist/render/templates/parallax/session.liquid +295 -0
- package/dist/render/templates/parallax/styles.css +1874 -0
- package/dist/render/templates/parchment/portfolio.liquid +290 -0
- package/dist/render/templates/parchment/project.liquid +289 -0
- package/dist/render/templates/parchment/session.liquid +346 -0
- package/dist/render/templates/parchment/styles.css +1397 -0
- package/dist/render/templates/partials/_beats.liquid +16 -0
- package/dist/render/templates/partials/_breadcrumb.liquid +9 -0
- package/dist/render/templates/partials/_footer.liquid +7 -0
- package/dist/render/templates/partials/_growth-chart.liquid +7 -0
- package/dist/render/templates/partials/_key-decisions.liquid +20 -0
- package/dist/render/templates/partials/_links.liquid +16 -0
- package/dist/render/templates/partials/_narrative.liquid +8 -0
- package/dist/render/templates/partials/_phases.liquid +20 -0
- package/dist/render/templates/partials/_portfolio-header.liquid +20 -0
- package/dist/render/templates/partials/_portfolio-projects.liquid +16 -0
- package/dist/render/templates/partials/_portfolio-stats.liquid +19 -0
- package/dist/render/templates/partials/_qa.liquid +13 -0
- package/dist/render/templates/partials/_screenshot.liquid +15 -0
- package/dist/render/templates/partials/_session-cards.liquid +30 -0
- package/dist/render/templates/partials/_session-header.liquid +39 -0
- package/dist/render/templates/partials/_session-sidebar.liquid +30 -0
- package/dist/render/templates/partials/_skills.liquid +12 -0
- package/dist/render/templates/partials/_source-breakdown.liquid +22 -0
- package/dist/render/templates/partials/_stats.liquid +38 -0
- package/dist/render/templates/partials/_work-timeline.liquid +7 -0
- package/dist/render/templates/project.liquid +7 -4
- package/dist/render/templates/radar/portfolio.liquid +233 -0
- package/dist/render/templates/radar/project.liquid +278 -0
- package/dist/render/templates/radar/session.liquid +300 -0
- package/dist/render/templates/radar/styles.css +1049 -0
- package/dist/render/templates/showcase/portfolio.liquid +231 -0
- package/dist/render/templates/showcase/project.liquid +237 -0
- package/dist/render/templates/showcase/session.liquid +210 -0
- package/dist/render/templates/showcase/styles.css +1279 -0
- package/dist/render/templates/signal/portfolio.liquid +227 -0
- package/dist/render/templates/signal/project.liquid +278 -0
- package/dist/render/templates/signal/session.liquid +282 -0
- package/dist/render/templates/signal/styles.css +1395 -0
- package/dist/render/templates/strata/portfolio.liquid +192 -0
- package/dist/render/templates/strata/project.liquid +282 -0
- package/dist/render/templates/strata/session.liquid +261 -0
- package/dist/render/templates/strata/styles.css +1350 -0
- package/dist/render/templates/styles.css +1190 -0
- package/dist/render/templates/terminal/portfolio.liquid +118 -0
- package/dist/render/templates/terminal/project.liquid +161 -0
- package/dist/render/templates/terminal/session.liquid +145 -0
- package/dist/render/templates/terminal/styles.css +492 -0
- package/dist/render/templates/verdant/portfolio.liquid +333 -0
- package/dist/render/templates/verdant/project.liquid +309 -0
- package/dist/render/templates/verdant/session.liquid +237 -0
- package/dist/render/templates/verdant/styles.css +1257 -0
- package/dist/render/templates/zen/portfolio.liquid +136 -0
- package/dist/render/templates/zen/project.liquid +187 -0
- package/dist/render/templates/zen/session.liquid +203 -0
- package/dist/render/templates/zen/styles.css +1207 -0
- package/dist/render/templates.js +90 -0
- package/dist/routes/context.js +15 -10
- package/dist/routes/enhance.js +17 -40
- package/dist/routes/export.js +14 -4
- package/dist/routes/preview.js +480 -108
- package/dist/routes/projects.js +11 -19
- package/dist/routes/publish.js +15 -17
- package/dist/routes/settings.js +94 -1
- package/dist/routes/sse.js +9 -0
- package/dist/server.js +8 -2
- package/dist/settings.js +17 -9
- package/package.json +2 -4
- package/dist/public/assets/index-B_d6DlEI.js +0 -21
- package/dist/public/assets/index-Dalqz2mC.css +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# heyiam
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/heyiam)
|
|
4
|
+
[](https://github.com/interactivecats/heyi.am/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
Local-first CLI that indexes your AI coding sessions. Search locally, enhance with AI, and publish a portfolio of real work.
|
|
7
|
+
|
|
8
|
+
Discovers sessions from **Claude Code**, **Cursor**, **OpenAI Codex CLI**, and **Google Gemini CLI**.
|
|
9
|
+
|
|
10
|
+
## Get Started
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx heyiam
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Opens a local dashboard at `localhost:17845`. Browse projects, search sessions, and publish portfolio case studies to `heyi.am/:username`.
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
| Command | Description |
|
|
21
|
+
|---------|-------------|
|
|
22
|
+
| `heyiam` / `heyiam open` | Start local dashboard |
|
|
23
|
+
| `heyiam search [query]` | Full-text search across all sessions |
|
|
24
|
+
| `heyiam time` | Your time vs agent time per project |
|
|
25
|
+
| `heyiam context <id>` | Export session as compressed context for AI tools |
|
|
26
|
+
| `heyiam archive` | Discover and archive sessions from all sources |
|
|
27
|
+
| `heyiam sync` | Index sessions into SQLite |
|
|
28
|
+
| `heyiam status` | Archive health, session counts, daemon status |
|
|
29
|
+
|
|
30
|
+
## Privacy
|
|
31
|
+
|
|
32
|
+
Everything stays local by default. Nothing leaves your machine unless you explicitly publish.
|
|
33
|
+
|
|
34
|
+
- Sessions are read from local tool storage (read-only)
|
|
35
|
+
- SQLite search index lives at `~/.local/share/heyiam/`
|
|
36
|
+
- Config at `~/.config/heyiam/`
|
|
37
|
+
- Common secret patterns are detected and redacted before upload, but this is not a guarantee
|
|
38
|
+
|
|
39
|
+
AI enhancement runs locally using your own `ANTHROPIC_API_KEY`.
|
|
40
|
+
|
|
41
|
+
## Links
|
|
42
|
+
|
|
43
|
+
- [heyi.am](https://heyi.am) — published portfolios
|
|
44
|
+
- [heyiam.com](https://heyiam.com) — dashboard and auth
|
|
45
|
+
- [GitHub](https://github.com/interactivecats/heyi.am) — source code
|
package/dist/config.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
/** Base URL for the heyiam.com app API. Override with HEYIAM_API_URL for local dev. */
|
|
2
|
-
|
|
2
|
+
const DEFAULT_API_URL = 'https://heyiam.com';
|
|
3
|
+
export const API_URL = process.env.HEYIAM_API_URL ?? DEFAULT_API_URL;
|
|
3
4
|
/** Base URL for the heyi.am public site. Override with HEYIAM_PUBLIC_URL for local dev. */
|
|
4
5
|
export const PUBLIC_URL = process.env.HEYIAM_PUBLIC_URL ?? 'https://heyi.am';
|
|
6
|
+
/** Warn once to stderr if a non-default API URL is in use (env var override). */
|
|
7
|
+
let _apiUrlWarned = false;
|
|
8
|
+
export function warnIfNonDefaultApiUrl() {
|
|
9
|
+
if (!_apiUrlWarned && API_URL !== DEFAULT_API_URL) {
|
|
10
|
+
console.warn(`[security] API_URL overridden to ${API_URL} — auth tokens will be sent to this host`);
|
|
11
|
+
_apiUrlWarned = true;
|
|
12
|
+
}
|
|
13
|
+
}
|
package/dist/db.js
CHANGED
|
@@ -9,10 +9,9 @@ import { homedir } from 'node:os';
|
|
|
9
9
|
function getDataDir() {
|
|
10
10
|
return process.env.HEYIAM_DATA_DIR || join(homedir(), '.local', 'share', 'heyiam');
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
function getDbPath() {
|
|
13
13
|
return join(getDataDir(), 'sessions.db');
|
|
14
14
|
}
|
|
15
|
-
export const DB_PATH = join(homedir(), '.local', 'share', 'heyiam', 'sessions.db');
|
|
16
15
|
const CURRENT_SCHEMA_VERSION = 5;
|
|
17
16
|
// ── Singleton ────────────────────────────────────────────────
|
|
18
17
|
let _db = null;
|
package/dist/export.js
CHANGED
|
@@ -9,9 +9,10 @@ import { mkdirSync, writeFileSync, readFileSync, statSync, existsSync } from 'no
|
|
|
9
9
|
import { deflateRawSync } from 'node:zlib';
|
|
10
10
|
import { join, resolve, dirname } from 'node:path';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
|
-
import { loadEnhancedData } from './settings.js';
|
|
12
|
+
import { loadEnhancedData, getDefaultTemplate } from './settings.js';
|
|
13
13
|
import { renderProjectHtml, renderSessionHtml } from './render/index.js';
|
|
14
|
-
import {
|
|
14
|
+
import { resolveTemplate, getTemplateCss } from './render/templates.js';
|
|
15
|
+
import { escapeHtml, displayNameFromDir, toSlug } from './format-utils.js';
|
|
15
16
|
import { buildProjectRenderData, buildSessionRenderData, buildSessionCard, } from './render/build-render-data.js';
|
|
16
17
|
import { mergeActiveIntervals, sumIntervalMs } from './bridge.js';
|
|
17
18
|
import { SCREENSHOTS_DIR } from './screenshot.js';
|
|
@@ -42,7 +43,7 @@ function resolveScreenshotDataUri(dirName, cache) {
|
|
|
42
43
|
return `data:image/png;base64,${b64}`;
|
|
43
44
|
}
|
|
44
45
|
// Try local screenshot file
|
|
45
|
-
const slug = dirName
|
|
46
|
+
const slug = toSlug(dirName);
|
|
46
47
|
const screenshotPath = join(SCREENSHOTS_DIR, `${slug}.png`);
|
|
47
48
|
if (existsSync(screenshotPath)) {
|
|
48
49
|
const buf = readFileSync(screenshotPath);
|
|
@@ -220,16 +221,19 @@ export async function exportHtml(dirName, cache, sessions, outputPath, username
|
|
|
220
221
|
sourceTool: session.source ?? 'unknown',
|
|
221
222
|
});
|
|
222
223
|
});
|
|
223
|
-
//
|
|
224
|
+
// Prefer DB-computed stats from opts (includes subagent data); fall back to session-based computation
|
|
224
225
|
const totalLoc = sessions.reduce((sum, s) => sum + s.linesOfCode, 0);
|
|
225
226
|
const totalDurationMinutes = computeMergedSessionDuration(sessions);
|
|
226
227
|
const totalFilesChanged = opts?.totalFilesChanged ?? new Set(sessions.flatMap(s => s.filesChanged.map(f => f.path))).size;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
228
|
+
const totalAgentDurationMinutes = opts?.totalAgentDurationMinutes ?? (() => {
|
|
229
|
+
const agentMin = sessions
|
|
230
|
+
.filter((s) => s.isOrchestrated && s.children)
|
|
231
|
+
.reduce((sum, s) => sum + s.children.reduce((cs, c) => cs + c.durationMinutes, 0), 0);
|
|
232
|
+
return agentMin > 0 ? totalDurationMinutes + agentMin : undefined;
|
|
233
|
+
})();
|
|
234
|
+
const totalTokens = (opts?.totalInputTokens || opts?.totalOutputTokens)
|
|
235
|
+
? (opts.totalInputTokens ?? 0) + (opts.totalOutputTokens ?? 0) || undefined
|
|
236
|
+
: sessions.reduce((sum, s) => sum + (s.tokenUsage?.input ?? 0) + (s.tokenUsage?.output ?? 0), 0) || undefined;
|
|
233
237
|
// Resolve screenshot for embedding
|
|
234
238
|
const screenshotUrl = resolveScreenshotDataUri(dirName, cache);
|
|
235
239
|
// Render project index.html — pass same data shape as dashboard
|
|
@@ -256,12 +260,14 @@ export async function exportHtml(dirName, cache, sessions, outputPath, username
|
|
|
256
260
|
sessionCards,
|
|
257
261
|
sessionBaseUrl: './sessions',
|
|
258
262
|
});
|
|
263
|
+
const templateName = resolveTemplate(undefined, getDefaultTemplate());
|
|
259
264
|
const projectBody = renderProjectHtml(projectRenderData, {
|
|
260
265
|
arc: result.arc,
|
|
261
266
|
fullSessions: sessions,
|
|
262
|
-
});
|
|
267
|
+
}, templateName);
|
|
263
268
|
const projectHtml = buildStandalonePage(title, projectBody, {
|
|
264
269
|
description: result.narrative?.slice(0, 200) || undefined,
|
|
270
|
+
templateName,
|
|
265
271
|
});
|
|
266
272
|
totalBytes += writeAndTrack(join(outputPath, 'index.html'), projectHtml, files);
|
|
267
273
|
// Render session pages — only featured sessions (linked from project page)
|
|
@@ -279,10 +285,11 @@ export async function exportHtml(dirName, cache, sessions, outputPath, username
|
|
|
279
285
|
projectSlug: slug,
|
|
280
286
|
sessionSlug,
|
|
281
287
|
sourceTool: session.source ?? 'unknown',
|
|
288
|
+
template: templateName,
|
|
282
289
|
});
|
|
283
|
-
const sessionBody = renderSessionHtml(renderData);
|
|
290
|
+
const sessionBody = renderSessionHtml(renderData, templateName);
|
|
284
291
|
const sessionDesc = (enhanced?.developerTake ?? session.developerTake ?? '').slice(0, 200) || undefined;
|
|
285
|
-
const sessionHtml = buildStandalonePage(session.title, sessionBody, { description: sessionDesc });
|
|
292
|
+
const sessionHtml = buildStandalonePage(session.title, sessionBody, { description: sessionDesc, templateName });
|
|
286
293
|
totalBytes += writeAndTrack(join(sessionsDir, `${sessionSlug}.html`), sessionHtml, files);
|
|
287
294
|
}
|
|
288
295
|
return { files, totalBytes, outputPath };
|
|
@@ -318,11 +325,15 @@ function buildProjectRenderInputs(dirName, cache, sessions, username, opts) {
|
|
|
318
325
|
const totalLoc = sessions.reduce((sum, s) => sum + s.linesOfCode, 0);
|
|
319
326
|
const totalDurationMinutes = computeMergedSessionDuration(sessions);
|
|
320
327
|
const totalFilesChanged = opts?.totalFilesChanged ?? new Set(sessions.flatMap(s => s.filesChanged.map(f => f.path))).size;
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
328
|
+
const totalAgentDurationMinutes = opts?.totalAgentDurationMinutes ?? (() => {
|
|
329
|
+
const agentMin = sessions
|
|
330
|
+
.filter((s) => s.isOrchestrated && s.children)
|
|
331
|
+
.reduce((sum, s) => sum + s.children.reduce((cs, c) => cs + c.durationMinutes, 0), 0);
|
|
332
|
+
return agentMin > 0 ? totalDurationMinutes + agentMin : undefined;
|
|
333
|
+
})();
|
|
334
|
+
const totalTokens = (opts?.totalInputTokens || opts?.totalOutputTokens)
|
|
335
|
+
? (opts.totalInputTokens ?? 0) + (opts.totalOutputTokens ?? 0) || undefined
|
|
336
|
+
: sessions.reduce((sum, s) => sum + (s.tokenUsage?.input ?? 0) + (s.tokenUsage?.output ?? 0), 0) || undefined;
|
|
326
337
|
return { result, slug, title, sessionCards, totalLoc, totalDurationMinutes, totalAgentDurationMinutes, totalFilesChanged, totalTokens };
|
|
327
338
|
}
|
|
328
339
|
/**
|
|
@@ -346,15 +357,17 @@ export function generateProjectHtmlFragment(dirName, cache, sessions, username =
|
|
|
346
357
|
totalLoc, totalDurationMinutes, totalAgentDurationMinutes, totalFilesChanged, totalTokens,
|
|
347
358
|
sessionCards,
|
|
348
359
|
});
|
|
360
|
+
const templateName = resolveTemplate(undefined, getDefaultTemplate());
|
|
349
361
|
return renderProjectHtml(renderData, {
|
|
350
362
|
arc: result.arc,
|
|
351
363
|
fullSessions: sessions,
|
|
352
|
-
});
|
|
364
|
+
}, templateName);
|
|
353
365
|
}
|
|
354
366
|
export function generateHtmlFiles(dirName, cache, sessions, username = 'local', opts) {
|
|
355
367
|
const files = [];
|
|
356
368
|
const { result, slug, title, sessionCards, totalLoc, totalDurationMinutes, totalAgentDurationMinutes, totalFilesChanged, totalTokens } = buildProjectRenderInputs(dirName, cache, sessions, username, opts);
|
|
357
369
|
const screenshotUrl = resolveScreenshotDataUri(dirName, cache);
|
|
370
|
+
const templateName = resolveTemplate(undefined, getDefaultTemplate());
|
|
358
371
|
const projectRenderData = buildProjectRenderData({
|
|
359
372
|
username, slug, title,
|
|
360
373
|
narrative: result.narrative,
|
|
@@ -371,9 +384,10 @@ export function generateHtmlFiles(dirName, cache, sessions, username = 'local',
|
|
|
371
384
|
const projectBody = renderProjectHtml(projectRenderData, {
|
|
372
385
|
arc: result.arc,
|
|
373
386
|
fullSessions: sessions,
|
|
374
|
-
});
|
|
387
|
+
}, templateName);
|
|
375
388
|
files.push({ path: 'index.html', content: buildStandalonePage(title, projectBody, {
|
|
376
389
|
description: result.narrative?.slice(0, 200) || undefined,
|
|
390
|
+
templateName,
|
|
377
391
|
}) });
|
|
378
392
|
const featuredSessions = pickFeaturedSessions(sessions, cache);
|
|
379
393
|
for (const session of featuredSessions) {
|
|
@@ -384,12 +398,13 @@ export function generateHtmlFiles(dirName, cache, sessions, username = 'local',
|
|
|
384
398
|
session, enhanced, username,
|
|
385
399
|
projectSlug: slug, sessionSlug,
|
|
386
400
|
sourceTool: session.source ?? 'unknown',
|
|
401
|
+
template: templateName,
|
|
387
402
|
});
|
|
388
|
-
const sessionBody = renderSessionHtml(renderData);
|
|
403
|
+
const sessionBody = renderSessionHtml(renderData, templateName);
|
|
389
404
|
const sessionDesc = (enhanced?.developerTake ?? session.developerTake ?? '').slice(0, 200) || undefined;
|
|
390
405
|
files.push({
|
|
391
406
|
path: `sessions/${sessionSlug}.html`,
|
|
392
|
-
content: buildStandalonePage(session.title, sessionBody, { description: sessionDesc }),
|
|
407
|
+
content: buildStandalonePage(session.title, sessionBody, { description: sessionDesc, templateName }),
|
|
393
408
|
});
|
|
394
409
|
}
|
|
395
410
|
return files;
|
|
@@ -490,9 +505,9 @@ function getInlineMountJs() {
|
|
|
490
505
|
}
|
|
491
506
|
}
|
|
492
507
|
function buildStandalonePage(title, bodyHtml, opts) {
|
|
493
|
-
const css = getInlineCss();
|
|
508
|
+
const css = opts?.templateName ? getTemplateCss(opts.templateName) : getInlineCss();
|
|
494
509
|
const cssTag = css
|
|
495
|
-
? `<style>${css}\nbody { overflow: auto !important; min-height: auto !important;
|
|
510
|
+
? `<style>${css}\nbody { overflow: auto !important; min-height: auto !important; }\n#root { min-height: auto !important; }</style>`
|
|
496
511
|
: '';
|
|
497
512
|
const mountJs = getInlineMountJs();
|
|
498
513
|
const scriptTag = mountJs ? `<script>${mountJs}</script>` : '';
|
|
@@ -516,7 +531,7 @@ function buildStandalonePage(title, bodyHtml, opts) {
|
|
|
516
531
|
${ogTags}
|
|
517
532
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
518
533
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
519
|
-
<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" />
|
|
534
|
+
<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&family=Cormorant+Garamond:ital,wght@0,400;0,600;1,400&family=Newsreader:ital,wght@0,400;0,600;1,400&display=swap" rel="stylesheet" />
|
|
520
535
|
${cssTag}
|
|
521
536
|
</head>
|
|
522
537
|
<body>
|
package/dist/format-utils.js
CHANGED
|
@@ -16,6 +16,11 @@ export function displayNameFromDir(dirName) {
|
|
|
16
16
|
const segments = dirName.split('-').filter(Boolean);
|
|
17
17
|
return segments.length > 0 ? segments[segments.length - 1] : dirName;
|
|
18
18
|
}
|
|
19
|
+
/** Generate a URL-safe slug from a string. */
|
|
20
|
+
export function toSlug(s, maxLen) {
|
|
21
|
+
const slug = s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
22
|
+
return maxLen !== undefined ? slug.slice(0, maxLen) : slug;
|
|
23
|
+
}
|
|
19
24
|
/** Escape LIKE wildcards in user input for safe use in SQL LIKE clauses. */
|
|
20
25
|
export function escapeLikeWildcards(str) {
|
|
21
26
|
return str.replace(/[%_]/g, (c) => `\\${c}`);
|
package/dist/index.js
CHANGED
|
@@ -650,6 +650,174 @@ daemon
|
|
|
650
650
|
}
|
|
651
651
|
console.log('\n Daemon uninstalled.\n');
|
|
652
652
|
});
|
|
653
|
+
// ── Embed command ──────────────────────────────────────────
|
|
654
|
+
program
|
|
655
|
+
.command('embed')
|
|
656
|
+
.description('Generate embeddable widget snippets for your portfolio and projects')
|
|
657
|
+
.option('--project <name>', 'Generate embed for a specific project')
|
|
658
|
+
.option('--format <type>', 'Output format: widget, iframe, badge, html, or all', '')
|
|
659
|
+
.option('--sections <list>', 'Sections to include (comma-separated: stats,tools,skills,heatmap,recent)', 'stats')
|
|
660
|
+
.option('--theme <theme>', 'Color theme (dark or light)', 'dark')
|
|
661
|
+
.action(async (opts) => {
|
|
662
|
+
const { getAuthToken } = await import('./auth.js');
|
|
663
|
+
const { PUBLIC_URL } = await import('./config.js');
|
|
664
|
+
const { getUploadedState, getDataDir } = await import('./settings.js');
|
|
665
|
+
const { displayNameFromDir } = await import('./sync.js');
|
|
666
|
+
const { readdirSync, existsSync } = await import('node:fs');
|
|
667
|
+
const { join } = await import('node:path');
|
|
668
|
+
const auth = getAuthToken();
|
|
669
|
+
const username = auth?.username;
|
|
670
|
+
const sections = opts.sections || 'stats';
|
|
671
|
+
const theme = opts.theme || 'dark';
|
|
672
|
+
const queryParts = [];
|
|
673
|
+
if (sections !== 'stats')
|
|
674
|
+
queryParts.push(`sections=${sections}`);
|
|
675
|
+
if (theme !== 'dark')
|
|
676
|
+
queryParts.push(`theme=${theme}`);
|
|
677
|
+
const query = queryParts.length > 0 ? `?${queryParts.join('&')}` : '';
|
|
678
|
+
// Find published projects
|
|
679
|
+
const publishedDir = join(getDataDir(), 'published');
|
|
680
|
+
const publishedProjects = [];
|
|
681
|
+
if (existsSync(publishedDir)) {
|
|
682
|
+
for (const file of readdirSync(publishedDir)) {
|
|
683
|
+
if (!file.endsWith('.json'))
|
|
684
|
+
continue;
|
|
685
|
+
const dirName = file.replace(/\.json$/, '');
|
|
686
|
+
const state = getUploadedState(dirName);
|
|
687
|
+
if (state?.slug) {
|
|
688
|
+
publishedProjects.push({ dirName, slug: state.slug });
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
const isPublished = username && publishedProjects.length > 0;
|
|
693
|
+
const format = opts.format || (isPublished ? 'all' : 'html');
|
|
694
|
+
// Build local stats for static HTML
|
|
695
|
+
const db = openDatabase();
|
|
696
|
+
await syncSessionIndex(db);
|
|
697
|
+
const { getAllProjectStats } = await import('./db.js');
|
|
698
|
+
const allStats = getAllProjectStats(db);
|
|
699
|
+
if (opts.project) {
|
|
700
|
+
const match = publishedProjects.find((p) => p.slug === opts.project || p.dirName === opts.project || displayNameFromDir(p.dirName) === opts.project);
|
|
701
|
+
const localMatch = allStats.find((p) => p.projectName === opts.project || p.projectDir === opts.project || displayNameFromDir(p.projectDir) === opts.project);
|
|
702
|
+
if (!match && !localMatch) {
|
|
703
|
+
console.log(`\n Project "${opts.project}" not found.`);
|
|
704
|
+
if (allStats.length > 0) {
|
|
705
|
+
console.log(' Available projects:');
|
|
706
|
+
for (const p of allStats)
|
|
707
|
+
console.log(` ${p.projectName}`);
|
|
708
|
+
}
|
|
709
|
+
console.log('');
|
|
710
|
+
db.close();
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
const base = match && username ? `${PUBLIC_URL}/${username}/${match.slug}` : null;
|
|
714
|
+
const stats = localMatch || (match ? allStats.find((p) => displayNameFromDir(p.projectDir) === displayNameFromDir(match.dirName)) : null);
|
|
715
|
+
const title = stats?.projectName || match?.slug || opts.project;
|
|
716
|
+
console.log('');
|
|
717
|
+
console.log(` ── ${title} ──────────────────────────────`);
|
|
718
|
+
printFormats(format, base, query, sections, theme, stats ? {
|
|
719
|
+
name: stats.projectName,
|
|
720
|
+
sessions: stats.sessionCount,
|
|
721
|
+
loc: stats.totalLoc,
|
|
722
|
+
duration: stats.totalDuration,
|
|
723
|
+
skills: stats.skills,
|
|
724
|
+
} : null);
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
// Portfolio level
|
|
728
|
+
const base = username ? `${PUBLIC_URL}/${username}` : null;
|
|
729
|
+
// Aggregate stats across all projects
|
|
730
|
+
const totalSessions = allStats.reduce((s, p) => s + p.sessionCount, 0);
|
|
731
|
+
const totalLoc = allStats.reduce((s, p) => s + p.totalLoc, 0);
|
|
732
|
+
const totalDuration = allStats.reduce((s, p) => s + p.totalDuration, 0);
|
|
733
|
+
const allSkills = [...new Set(allStats.flatMap((p) => p.skills))].slice(0, 8);
|
|
734
|
+
console.log('');
|
|
735
|
+
console.log(' ── Portfolio ──────────────────────────────────');
|
|
736
|
+
printFormats(format, base, query, sections, theme, {
|
|
737
|
+
name: username || 'portfolio',
|
|
738
|
+
sessions: totalSessions,
|
|
739
|
+
loc: totalLoc,
|
|
740
|
+
duration: totalDuration,
|
|
741
|
+
skills: allSkills,
|
|
742
|
+
projectCount: allStats.length,
|
|
743
|
+
});
|
|
744
|
+
// Also show each published project
|
|
745
|
+
if (format !== 'html' && publishedProjects.length > 0) {
|
|
746
|
+
for (const p of publishedProjects) {
|
|
747
|
+
const projBase = `${PUBLIC_URL}/${username}/${p.slug}`;
|
|
748
|
+
const stats = allStats.find((s) => displayNameFromDir(s.projectDir) === displayNameFromDir(p.dirName));
|
|
749
|
+
console.log(` ── ${p.slug} ──────────────────────────────`);
|
|
750
|
+
printFormats(format, projBase, query, sections, theme, stats ? {
|
|
751
|
+
name: stats.projectName,
|
|
752
|
+
sessions: stats.sessionCount,
|
|
753
|
+
loc: stats.totalLoc,
|
|
754
|
+
duration: stats.totalDuration,
|
|
755
|
+
skills: stats.skills,
|
|
756
|
+
} : null);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
db.close();
|
|
761
|
+
});
|
|
762
|
+
function printFormats(format, base, query, _sections, _theme, stats) {
|
|
763
|
+
const showAll = format === 'all';
|
|
764
|
+
if (base && (showAll || format === 'badge')) {
|
|
765
|
+
console.log('');
|
|
766
|
+
console.log(' Badge (GitHub README, markdown):');
|
|
767
|
+
console.log(` [](${base})`);
|
|
768
|
+
console.log('');
|
|
769
|
+
}
|
|
770
|
+
if (base && (showAll || format === 'widget')) {
|
|
771
|
+
const dataAttrs = [`data-username="${base.split('/').slice(-2, -1)[0] || ''}"`,];
|
|
772
|
+
// If it's a project URL (3+ path segments after domain), add data-project
|
|
773
|
+
const pathParts = new URL(base).pathname.split('/').filter(Boolean);
|
|
774
|
+
if (pathParts.length >= 2)
|
|
775
|
+
dataAttrs.push(`data-project="${pathParts[1]}"`);
|
|
776
|
+
if (_sections !== 'stats')
|
|
777
|
+
dataAttrs.push(`data-sections="${_sections}"`);
|
|
778
|
+
if (_theme !== 'dark')
|
|
779
|
+
dataAttrs.push(`data-theme="${_theme}"`);
|
|
780
|
+
console.log(' Widget (personal site, blog):');
|
|
781
|
+
console.log(` <div class="heyiam-embed" ${dataAttrs.join(' ')}></div>`);
|
|
782
|
+
console.log(` <script src="${new URL(base).origin}/embed.js"></script>`);
|
|
783
|
+
console.log('');
|
|
784
|
+
}
|
|
785
|
+
if (base && (showAll || format === 'iframe')) {
|
|
786
|
+
console.log(' iframe:');
|
|
787
|
+
console.log(` <iframe src="${base}/embed${query}" width="480" height="200" frameborder="0"></iframe>`);
|
|
788
|
+
console.log('');
|
|
789
|
+
}
|
|
790
|
+
if (showAll || format === 'html') {
|
|
791
|
+
if (stats) {
|
|
792
|
+
const durationStr = stats.duration >= 60 ? `${Math.round(stats.duration / 60)}h` : `${stats.duration}m`;
|
|
793
|
+
const locStr = stats.loc >= 1000 ? `${(stats.loc / 1000).toFixed(1)}k` : String(stats.loc);
|
|
794
|
+
const skillChips = stats.skills.slice(0, 6).map((s) => `<span style="font-size:10px;padding:2px 8px;border-radius:3px;background:#1f2937;color:#9ca3af">${s}</span>`).join(' ');
|
|
795
|
+
const projectLine = stats.projectCount ? `<span style="font-size:10px;color:#6b7280">${stats.projectCount} projects</span>` : '';
|
|
796
|
+
console.log(' Static HTML (works anywhere, no JS needed):');
|
|
797
|
+
console.log(` <div style="font-family:ui-monospace,monospace;background:#0a0a0f;color:#e5e7eb;padding:16px 20px;border-radius:6px">`);
|
|
798
|
+
console.log(` <div style="font-size:15px;font-weight:600;color:#f9fafb;margin-bottom:12px">${stats.name} ${projectLine}</div>`);
|
|
799
|
+
console.log(` <div style="display:flex;gap:20px;flex-wrap:wrap;margin-bottom:10px">`);
|
|
800
|
+
console.log(` <div><div style="font-size:18px;font-weight:700;color:#f9fafb">${stats.sessions}</div><div style="font-size:10px;color:#6b7280;text-transform:uppercase;letter-spacing:0.08em">Sessions</div></div>`);
|
|
801
|
+
console.log(` <div><div style="font-size:18px;font-weight:700;color:#f9fafb">${locStr}</div><div style="font-size:10px;color:#6b7280;text-transform:uppercase;letter-spacing:0.08em">Lines Changed</div></div>`);
|
|
802
|
+
console.log(` <div><div style="font-size:18px;font-weight:700;color:#f9fafb">${durationStr}</div><div style="font-size:10px;color:#6b7280;text-transform:uppercase;letter-spacing:0.08em">Active Time</div></div>`);
|
|
803
|
+
console.log(` </div>`);
|
|
804
|
+
if (skillChips) {
|
|
805
|
+
console.log(` <div style="display:flex;gap:6px;flex-wrap:wrap">${skillChips}</div>`);
|
|
806
|
+
}
|
|
807
|
+
console.log(` </div>`);
|
|
808
|
+
console.log('');
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
console.log(' Static HTML: No local stats available for this project.');
|
|
812
|
+
console.log('');
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
if (!base && format !== 'html') {
|
|
816
|
+
console.log(' Not published yet. Only static HTML is available.');
|
|
817
|
+
console.log(' Publish from the dashboard, then run again for badge/widget/iframe.');
|
|
818
|
+
console.log('');
|
|
819
|
+
}
|
|
820
|
+
}
|
|
653
821
|
// ── Logout command ──────────────────────────────────────────
|
|
654
822
|
program
|
|
655
823
|
.command('logout')
|