heyiam 0.3.7 → 0.3.10

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/archive.js CHANGED
@@ -64,6 +64,34 @@ function archiveDestination(originalPath, archiveBase, projectDir) {
64
64
  // Codex/Gemini: use projectDir + filename
65
65
  return join(archiveBase, projectDir, basename(originalPath));
66
66
  }
67
+ /**
68
+ * Public version of archiveDestination — given an original tool path,
69
+ * return the deterministic archive location.
70
+ */
71
+ export function resolveArchivePath(originalPath, projectDir, configDir) {
72
+ const archiveBase = getArchiveDir(configDir);
73
+ if (originalPath.startsWith(archiveBase + "/"))
74
+ return originalPath;
75
+ return archiveDestination(originalPath, archiveBase, projectDir);
76
+ }
77
+ /**
78
+ * Return a readable path for a session — the original if it still exists,
79
+ * otherwise the archive copy if it exists. Returns null if neither exists.
80
+ *
81
+ * Use this when reading session content by stored path: tool dirs (e.g.
82
+ * Claude Code) garbage-collect their own files, but the archive is under
83
+ * our control and survives.
84
+ */
85
+ export async function findReadableSessionPath(originalPath, projectDir, configDir) {
86
+ if (await stat(originalPath).catch(() => null))
87
+ return originalPath;
88
+ const archivePath = resolveArchivePath(originalPath, projectDir, configDir);
89
+ if (archivePath === originalPath)
90
+ return null;
91
+ if (await stat(archivePath).catch(() => null))
92
+ return archivePath;
93
+ return null;
94
+ }
67
95
  /**
68
96
  * Archive a Cursor session by exporting its parsed data as JSONL.
69
97
  * Cursor stores conversations in its own SQLite DB — we can't hard-link.
package/dist/db.js CHANGED
@@ -374,6 +374,15 @@ export function deleteSession(db, sessionId) {
374
374
  });
375
375
  tx();
376
376
  }
377
+ /**
378
+ * Update the stored file_path for a session. Used to heal the cache after
379
+ * we discover the originally-indexed path has been deleted by its source
380
+ * tool (e.g., Claude Code's 30-day cleanup) and we've fallen back to an
381
+ * archived copy.
382
+ */
383
+ export function updateSessionPath(db, sessionId, newPath) {
384
+ db.prepare('UPDATE sessions SET file_path = ? WHERE id = ?').run(newPath, sessionId);
385
+ }
377
386
  // ── Rebuild Index ────────────────────────────────────────────
378
387
  export function rebuildIndex(db, onProgress) {
379
388
  const tx = db.transaction(() => {
package/dist/export.js CHANGED
@@ -266,6 +266,7 @@ export async function exportHtml(dirName, cache, sessions, outputPath, username
266
266
  sessionCards,
267
267
  sessionBaseUrl: './sessions',
268
268
  sessionSuffix: '.html',
269
+ hideSessionDates: cache.hideSessionDates,
269
270
  });
270
271
  const templateName = resolveTemplate(undefined, getDefaultTemplate());
271
272
  const projectBody = renderProjectHtml(projectRenderData, {
@@ -369,6 +370,7 @@ export function generateProjectHtmlFragment(dirName, cache, sessions, username =
369
370
  sessionCards,
370
371
  sessionBaseUrl: `/${username}/${slug}`,
371
372
  sessionSuffix: '',
373
+ hideSessionDates: cache.hideSessionDates,
372
374
  });
373
375
  const templateName = resolveTemplate(undefined, getDefaultTemplate());
374
376
  return renderProjectHtml(renderData, {
@@ -394,6 +396,7 @@ export function generateHtmlFiles(dirName, cache, sessions, username = 'local',
394
396
  sessionCards,
395
397
  sessionBaseUrl: './sessions',
396
398
  sessionSuffix: '.html',
399
+ hideSessionDates: cache.hideSessionDates,
397
400
  });
398
401
  const projectBody = renderProjectHtml(projectRenderData, {
399
402
  arc: result.arc,
package/dist/index.js CHANGED
@@ -23,14 +23,7 @@ program
23
23
  .name('heyiam')
24
24
  .description('Turn AI coding sessions into portfolio case studies')
25
25
  .version(pkg.version);
26
- program
27
- .command('open')
28
- .description('Start the local server and open the browser')
29
- .option('-p, --port <number>', 'Port to run on', '17845')
30
- .option('--no-open', 'Start server without opening browser')
31
- .option('--demo', 'Start with fake data for screenshots and recordings')
32
- .option('--verbose', 'Show detailed sync and discovery logs')
33
- .action(async (opts) => {
26
+ async function runOpenServer(opts) {
34
27
  if (opts.verbose)
35
28
  process.env.HEYIAM_VERBOSE = '1';
36
29
  const port = parseInt(opts.port, 10);
@@ -120,6 +113,23 @@ program
120
113
  process.on('SIGINT', shutdown);
121
114
  process.on('SIGTERM', shutdown);
122
115
  setInterval(() => { }, 60_000);
116
+ }
117
+ program
118
+ .command('open')
119
+ .description('Start the local server and open the browser')
120
+ .option('-p, --port <number>', 'Port to run on', '17845')
121
+ .option('--no-open', 'Start server without opening browser')
122
+ .option('--demo', 'Start with fake data for screenshots and recordings')
123
+ .option('--verbose', 'Show detailed sync and discovery logs')
124
+ .action(runOpenServer);
125
+ program
126
+ .command('login')
127
+ .description('Sign in to heyiam.com (opens the dashboard in your browser)')
128
+ .option('-p, --port <number>', 'Port to run on', '17845')
129
+ .option('--verbose', 'Show detailed sync and discovery logs')
130
+ .action(async (opts) => {
131
+ console.log('\n Sign in from the dashboard that opens in your browser.\n');
132
+ await runOpenServer({ port: opts.port, open: true, verbose: opts.verbose });
123
133
  });
124
134
  program
125
135
  .command('time')
@@ -847,9 +857,16 @@ const resolvedArgv = process.argv[1] ? realpathSync(process.argv[1]) : '';
847
857
  const isDirectRun = resolvedArgv.endsWith('/dist/index.js') ||
848
858
  resolvedArgv.endsWith('/src/index.ts');
849
859
  if (isDirectRun) {
860
+ // Default to `open` when the user runs `heyiam` bare or `heyiam --flag …`,
861
+ // so `heyiam --port 1234` becomes `heyiam open --port 1234`. We only do
862
+ // this for *flags* (args starting with `-`) — bare unknown words like
863
+ // `heyiam blargh` should fall through to Commander so the user sees a
864
+ // clean "unknown command" error instead of a confusing "too many
865
+ // arguments for 'open'".
850
866
  const args = process.argv.slice(2);
851
- const knownCommands = ['open', 'time', 'search', 'context', 'reindex', 'archive', 'sync', 'status', 'daemon', 'logout'];
852
- if (args.length === 0 || !knownCommands.includes(args[0])) {
867
+ const firstArg = args[0];
868
+ const shouldDefaultToOpen = args.length === 0 || (firstArg !== undefined && firstArg.startsWith('-'));
869
+ if (shouldDefaultToOpen) {
853
870
  process.argv.splice(2, 0, 'open');
854
871
  }
855
872
  program.parseAsync(process.argv).catch((err) => {
package/dist/mount.js CHANGED
@@ -21929,11 +21929,11 @@
21929
21929
  withTime.forEach((c, i) => laneMap.set(c.id, i));
21930
21930
  return { laneMap, laneCount: withTime.length };
21931
21931
  }
21932
- function buildTooltip(s) {
21932
+ function buildTooltip(s, ordinalLabel) {
21933
21933
  const kids = getChildren(s);
21934
21934
  return {
21935
21935
  title: s.title,
21936
- timestamp: formatTimestamp(s.date),
21936
+ timestamp: ordinalLabel ?? formatTimestamp(s.date),
21937
21937
  duration: formatDuration(s.wallClockMinutes ?? s.durationMinutes),
21938
21938
  linesOfCode: s.linesOfCode,
21939
21939
  agentCount: kids.length,
@@ -21961,12 +21961,12 @@
21961
21961
  count
21962
21962
  }));
21963
21963
  }
21964
- function buildLegendEntries(sessionRanges) {
21965
- return sessionRanges.map((r) => {
21964
+ function buildLegendEntries(sessionRanges, hideDates) {
21965
+ return sessionRanges.map((r, i) => {
21966
21966
  const kids = getChildren(r.session);
21967
21967
  return {
21968
21968
  title: r.session.title,
21969
- timestamp: formatTimestamp(r.session.date),
21969
+ timestamp: hideDates ? `Session ${i + 1}` : formatTimestamp(r.session.date),
21970
21970
  agents: aggregateAgents(kids),
21971
21971
  totalAgents: kids.length,
21972
21972
  xStart: r.xStart,
@@ -22003,7 +22003,7 @@
22003
22003
  ].join(" ");
22004
22004
  }
22005
22005
  var DEFAULT_MAX_CONCURRENT = 8;
22006
- function layoutSegments(segments, maxConcurrent = DEFAULT_MAX_CONCURRENT, themeColors) {
22006
+ function layoutSegments(segments, maxConcurrent = DEFAULT_MAX_CONCURRENT, themeColors, hideDates) {
22007
22007
  const _mainColor = themeColors?.main ?? MAIN_COLOR;
22008
22008
  const _textMuted = themeColors?.muted ?? TEXT_MUTED;
22009
22009
  const nodes = [];
@@ -22014,6 +22014,8 @@
22014
22014
  const cY = 0;
22015
22015
  let minY = 0, maxY = 0;
22016
22016
  const threadStart = cx;
22017
+ let ordinalIdx = 0;
22018
+ const nextOrdinal = () => `Session ${++ordinalIdx}`;
22017
22019
  const bound = (y, h) => {
22018
22020
  if (y < minY) minY = y;
22019
22021
  if (y + h > maxY) maxY = y + h;
@@ -22034,8 +22036,9 @@
22034
22036
  const agentMinW = kids.length > 0 ? Math.max(MIN_W, kids.length * 30 + 100) : MIN_W;
22035
22037
  const w = kids.length > 0 ? Math.min(Math.max(dur * PX_PER_MIN, agentMinW), MAX_CONCURRENT_W) : Math.min(Math.max(timeToPx(dur), MIN_W), MAX_W);
22036
22038
  const sub = formatDuration(s.durationMinutes);
22037
- const tooltip = buildTooltip(s);
22038
- const ts = formatTimestamp(s.date);
22039
+ const ordinal = hideDates ? nextOrdinal() : void 0;
22040
+ const tooltip = buildTooltip(s, ordinal);
22041
+ const ts = ordinal ?? formatTimestamp(s.date);
22039
22042
  if (kids.length > 0) {
22040
22043
  const visible = kids.slice(0, MAX_AGENTS);
22041
22044
  const parentStartMs = sessionStart(s);
@@ -22108,8 +22111,9 @@
22108
22111
  const lane = laneMap.get(s.id) ?? 0;
22109
22112
  const trackY = cY + lane * dynamicTrackGap;
22110
22113
  const kids = getChildren(s);
22111
- const tooltip = buildTooltip(s);
22112
- const ts = formatTimestamp(s.date);
22114
+ const ordinal = hideDates ? nextOrdinal() : void 0;
22115
+ const tooltip = buildTooltip(s, ordinal);
22116
+ const ts = ordinal ?? formatTimestamp(s.date);
22113
22117
  const sub = formatDuration(s.durationMinutes);
22114
22118
  const sXStart = timeToX(sessionStart(s), rangeStartMs, rangeEndMs, segXStart, segXEnd);
22115
22119
  const barW = Math.min(Math.max(s.durationMinutes * PX_PER_MIN, 20), segXEnd - sXStart);
@@ -22283,21 +22287,37 @@
22283
22287
  ] })
22284
22288
  ] });
22285
22289
  }
22286
- function WorkTimeline({ sessions, onSessionClick, maxHeight, accentColor, isDark }) {
22290
+ function WorkTimeline({ sessions, onSessionClick, maxHeight, accentColor, isDark, hideDates }) {
22287
22291
  const mainColor = accentColor ?? (isDark ? "#f97316" : MAIN_COLOR);
22288
22292
  const threadColor = isDark ? "rgba(255,255,255,0.15)" : THREAD_COLOR;
22289
22293
  const textSecondary = isDark ? "rgba(255,255,255,0.65)" : TEXT_SECONDARY;
22290
22294
  const textMuted = isDark ? "rgba(255,255,255,0.4)" : TEXT_MUTED;
22291
22295
  const bgSurface = isDark ? "#111" : "#f8f9fb";
22292
- const segments = (0, import_react.useMemo)(() => computeSegments(sessions), [sessions]);
22296
+ const effectiveSessions = (0, import_react.useMemo)(() => {
22297
+ if (!hideDates) return sessions;
22298
+ return sessions.map((s, i) => ({
22299
+ ...s,
22300
+ date: s.date ?? new Date(i * 60 * 6e4).toISOString(),
22301
+ // Drop endTime to prevent any chance the renderer derives a real
22302
+ // wall-clock window from it.
22303
+ endTime: void 0,
22304
+ // Strip date from children too — agent lane math falls back to the
22305
+ // parent start when child dates are absent (already handled).
22306
+ children: s.children?.map((c) => ({ ...c, date: void 0 }))
22307
+ }));
22308
+ }, [sessions, hideDates]);
22309
+ const segments = (0, import_react.useMemo)(() => {
22310
+ const segs = computeSegments(effectiveSessions);
22311
+ return hideDates ? segs.filter((s) => s.type !== "gap") : segs;
22312
+ }, [effectiveSessions, hideDates]);
22293
22313
  const [expanded, setExpanded] = (0, import_react.useState)(false);
22294
22314
  const [fullscreen, setFullscreen] = (0, import_react.useState)(false);
22295
22315
  const [playing, setPlaying] = (0, import_react.useState)(false);
22296
22316
  const playRef = (0, import_react.useRef)(null);
22297
22317
  const concurrentLimit = expanded ? 999 : DEFAULT_MAX_CONCURRENT;
22298
22318
  const themeColors = (0, import_react.useMemo)(() => ({ main: mainColor, muted: textMuted }), [mainColor, textMuted]);
22299
- const L = (0, import_react.useMemo)(() => layoutSegments(segments, concurrentLimit, themeColors), [segments, concurrentLimit, themeColors]);
22300
- const legendEntries = (0, import_react.useMemo)(() => buildLegendEntries(L.sessionRanges), [L.sessionRanges]);
22319
+ const L = (0, import_react.useMemo)(() => layoutSegments(segments, concurrentLimit, themeColors, hideDates), [segments, concurrentLimit, themeColors, hideDates]);
22320
+ const legendEntries = (0, import_react.useMemo)(() => buildLegendEntries(L.sessionRanges, hideDates), [L.sessionRanges, hideDates]);
22301
22321
  const scrollRef = (0, import_react.useRef)(null);
22302
22322
  const [hovered, setHovered] = (0, import_react.useState)(null);
22303
22323
  const [focusedEntry, setFocusedEntry] = (0, import_react.useState)(null);
@@ -22998,7 +23018,7 @@
22998
23018
  return iso;
22999
23019
  }
23000
23020
  }
23001
- function SessionOverlay({ session, sessionPageUrl, onClose }) {
23021
+ function SessionOverlay({ session, sessionPageUrl, onClose, hideDates }) {
23002
23022
  const handleKeyDown = (0, import_react2.useCallback)((e) => {
23003
23023
  if (e.key === "Escape") onClose();
23004
23024
  }, [onClose]);
@@ -23040,8 +23060,8 @@
23040
23060
  ] }),
23041
23061
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { style: { fontFamily: "var(--font-display, sans-serif)", fontSize: "1.5rem", fontWeight: 700, color: "var(--on-surface, #191c1e)", marginBottom: "0.5rem" }, children: session.title }),
23042
23062
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { fontFamily: "var(--font-mono, monospace)", fontSize: "0.6875rem", textTransform: "uppercase", letterSpacing: "0.05em", color: "var(--on-surface-variant, #6b7280)", marginBottom: "1rem" }, children: [
23043
- formatDate(session.date),
23044
- session.source && ` \xB7 ${session.source}`,
23063
+ !hideDates && session.date && formatDate(session.date),
23064
+ session.source && (hideDates || !session.date ? session.source : ` \xB7 ${session.source}`),
23045
23065
  session.context && ` \xB7 ${session.context}`
23046
23066
  ] }),
23047
23067
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: "0.75rem", marginBottom: "1.25rem" }, children: [
@@ -23059,7 +23079,7 @@
23059
23079
  " turns over ",
23060
23080
  formatDuration2(session.durationMinutes)
23061
23081
  ] }),
23062
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(WorkTimeline, { sessions: [session], maxHeight: 200 })
23082
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(WorkTimeline, { sessions: [session], maxHeight: 200, hideDates })
23063
23083
  ] }),
23064
23084
  session.executionPath && session.executionPath.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { marginBottom: "1.25rem" }, children: [
23065
23085
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SectionLabel, { children: "Execution Path" }),
@@ -23135,6 +23155,7 @@
23135
23155
  }
23136
23156
  var allSessions = /* @__PURE__ */ new Map();
23137
23157
  var showOverlay = null;
23158
+ var pageHideDates = false;
23138
23159
  function OverlayRoot() {
23139
23160
  const [active, setActive] = (0, import_react3.useState)(null);
23140
23161
  showOverlay = (session) => setActive(session);
@@ -23160,6 +23181,7 @@
23160
23181
  {
23161
23182
  session: active,
23162
23183
  sessionPageUrl,
23184
+ hideDates: pageHideDates,
23163
23185
  onClose: () => setActive(null)
23164
23186
  }
23165
23187
  );
@@ -23169,6 +23191,8 @@
23169
23191
  document.querySelectorAll("[data-work-timeline]").forEach((el) => {
23170
23192
  const sessions = parseSessions(el);
23171
23193
  if (sessions.length === 0) return;
23194
+ const hideDates = el.dataset.hideDates === "1";
23195
+ if (hideDates) pageHideDates = true;
23172
23196
  for (const s of sessions) allSessions.set(s.id, s);
23173
23197
  (0, import_client.createRoot)(el).render(
23174
23198
  import_react3.default.createElement(WorkTimeline, {
@@ -23176,6 +23200,7 @@
23176
23200
  maxHeight: 300,
23177
23201
  isDark,
23178
23202
  accentColor,
23203
+ hideDates,
23179
23204
  onSessionClick: (session) => {
23180
23205
  if (showOverlay) showOverlay(session);
23181
23206
  }