heyiam 0.2.26 → 0.2.28

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
@@ -46,13 +46,13 @@ export async function archiveSessionFiles(sessions, configDir) {
46
46
  * Preserves the relative structure under the project directory.
47
47
  *
48
48
  * Claude: ~/.claude/projects/{projectDir}/{sessionId}.jsonl
49
- * → ~/.config/heyiam/sessions/{projectDir}/{sessionId}.jsonl
49
+ * → ~/.local/share/heyiam/sessions/{projectDir}/{sessionId}.jsonl
50
50
  *
51
51
  * Codex: ~/.codex/sessions/{nested}/{rollout-xxx}.jsonl
52
- * → ~/.config/heyiam/sessions/{projectDir}/{rollout-xxx}.jsonl
52
+ * → ~/.local/share/heyiam/sessions/{projectDir}/{rollout-xxx}.jsonl
53
53
  *
54
54
  * Gemini: ~/.gemini/tmp/{hash}/logs.json
55
- * → ~/.config/heyiam/sessions/{projectDir}/{hash}.json
55
+ * → ~/.local/share/heyiam/sessions/{projectDir}/{hash}.json
56
56
  */
57
57
  function archiveDestination(originalPath, archiveBase, projectDir) {
58
58
  // Claude: find the project dir in the path and take everything after it
@@ -1,7 +1,7 @@
1
1
  // Download and install the heyiam tray daemon binary from GitHub releases.
2
2
  //
3
3
  // Detects platform + arch, fetches the correct binary from the latest
4
- // daemon release, and saves it to ~/.config/heyiam/daemon/heyiam-tray.
4
+ // daemon release, and saves it to ~/.local/share/heyiam/daemon/heyiam-tray.
5
5
  import { createWriteStream, mkdirSync, chmodSync, existsSync, renameSync, unlinkSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
7
  import { homedir } from 'node:os';
@@ -30,7 +30,7 @@ export function getAssetName() {
30
30
  * Returns the directory where the daemon binary is stored.
31
31
  */
32
32
  export function getDaemonDir() {
33
- return join(homedir(), '.config', 'heyiam', 'daemon');
33
+ return join(homedir(), '.local', 'share', 'heyiam', 'daemon');
34
34
  }
35
35
  /**
36
36
  * Returns the full path to the daemon binary.
package/dist/db.js CHANGED
@@ -6,14 +6,13 @@ import { mkdirSync, statSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
7
  import { homedir } from 'node:os';
8
8
  // ── Constants ────────────────────────────────────────────────
9
- function getConfigDir() {
10
- return process.env.HEYIAM_CONFIG_DIR || join(homedir(), '.config', 'heyiam');
9
+ function getDataDir() {
10
+ return process.env.HEYIAM_DATA_DIR || join(homedir(), '.local', 'share', 'heyiam');
11
11
  }
12
12
  export function getDbPath() {
13
- return join(getConfigDir(), 'sessions.db');
13
+ return join(getDataDir(), 'sessions.db');
14
14
  }
15
- // Keep backward-compat for any external consumers
16
- export const DB_PATH = join(homedir(), '.config', 'heyiam', 'sessions.db');
15
+ export const DB_PATH = join(homedir(), '.local', 'share', 'heyiam', 'sessions.db');
17
16
  const CURRENT_SCHEMA_VERSION = 5;
18
17
  // ── Singleton ────────────────────────────────────────────────
19
18
  let _db = null;
package/dist/demo-seed.js CHANGED
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * Seeds a demo environment with fake data for marketing recordings.
3
3
  *
4
- * Creates a self-contained directory at ~/.config/heyiam/demo/ with:
4
+ * Creates a self-contained directory at ~/.local/share/heyiam/demo/ with:
5
5
  * - sessions.db — SQLite database with fake sessions + FTS
6
6
  * - enhanced/ — fake enhanced data for select sessions
7
7
  * - project-enhance/ — cached project narrative
8
8
  * - settings.json — onboarding complete, fake API key
9
9
  * - sessions/ — minimal JSONL session files
10
10
  *
11
- * The real ~/.config/heyiam/ is never touched.
11
+ * The real ~/.local/share/heyiam/ is never touched.
12
12
  */
13
13
  import { join } from 'node:path';
14
14
  import { homedir } from 'node:os';
@@ -16,7 +16,7 @@ import { mkdirSync, writeFileSync, existsSync, rmSync } from 'node:fs';
16
16
  import { createHash } from 'node:crypto';
17
17
  import { openDatabase } from './db.js';
18
18
  import { DEMO_PROJECTS, DEMO_SESSIONS, DEMO_TRANSCRIPT, DEMO_ENHANCE_RESULT, } from './demo-data.js';
19
- const DEMO_DIR = join(homedir(), '.config', 'heyiam', 'demo');
19
+ const DEMO_DIR = join(homedir(), '.local', 'share', 'heyiam', 'demo');
20
20
  /**
21
21
  * Seed the demo environment. Returns the path so the caller can set
22
22
  * HEYIAM_CONFIG_DIR before starting the server.
package/dist/export.js CHANGED
@@ -90,14 +90,14 @@ function writeAndTrack(filePath, content, files) {
90
90
  return statSync(filePath).size;
91
91
  }
92
92
  // ── Markdown Export ────────────────────────────────────────────
93
- export async function exportMarkdown(dirName, cache, sessions, outputPath) {
93
+ export async function exportMarkdown(dirName, cache, sessions, outputPath, opts) {
94
94
  const files = [];
95
95
  let totalBytes = 0;
96
96
  mkdirSync(outputPath, { recursive: true });
97
97
  const { result } = cache;
98
98
  const title = cache.title ?? displayNameFromDir(dirName);
99
99
  // README.md — project narrative
100
- const readme = buildReadme(title, result, sessions);
100
+ const readme = buildReadme(title, result, sessions, opts?.totalFilesChanged);
101
101
  totalBytes += writeAndTrack(join(outputPath, 'README.md'), readme, files);
102
102
  // sessions/*.md — per-session breakdowns
103
103
  const sessionsDir = join(outputPath, 'sessions');
@@ -113,7 +113,7 @@ export async function exportMarkdown(dirName, cache, sessions, outputPath) {
113
113
  totalBytes += writeAndTrack(join(outputPath, 'project.json'), projectJson, files);
114
114
  return { files, totalBytes, outputPath };
115
115
  }
116
- function buildReadme(title, result, sessions) {
116
+ function buildReadme(title, result, sessions, totalFilesChanged) {
117
117
  const lines = [];
118
118
  lines.push(`# ${title}\n`);
119
119
  lines.push(result.narrative);
@@ -146,7 +146,7 @@ function buildReadme(title, result, sessions) {
146
146
  lines.push(`- Sessions: ${sessions.length}`);
147
147
  const totalLoc = sessions.reduce((sum, s) => sum + s.linesOfCode, 0);
148
148
  const totalMin = computeMergedSessionDuration(sessions);
149
- const totalFiles = sessions.reduce((sum, s) => sum + s.filesChanged.length, 0);
149
+ const totalFiles = totalFilesChanged ?? new Set(sessions.flatMap(s => s.filesChanged.map(f => f.path))).size;
150
150
  lines.push(`- Lines of code: ${totalLoc.toLocaleString()}`);
151
151
  lines.push(`- Total time: ${(totalMin / 60).toFixed(1)}h`);
152
152
  lines.push(`- Files changed: ${totalFiles}`);
@@ -223,7 +223,7 @@ export async function exportHtml(dirName, cache, sessions, outputPath, username
223
223
  // Compute stats the same way the dashboard does
224
224
  const totalLoc = sessions.reduce((sum, s) => sum + s.linesOfCode, 0);
225
225
  const totalDurationMinutes = computeMergedSessionDuration(sessions);
226
- const totalFilesChanged = opts?.totalFilesChanged ?? sessions.reduce((sum, s) => sum + s.filesChanged.length, 0);
226
+ const totalFilesChanged = opts?.totalFilesChanged ?? new Set(sessions.flatMap(s => s.filesChanged.map(f => f.path))).size;
227
227
  // Agent duration: sum child durations across all orchestrated sessions
228
228
  const totalAgentMinutes = sessions
229
229
  .filter((s) => s.isOrchestrated && s.children)
@@ -317,7 +317,7 @@ function buildProjectRenderInputs(dirName, cache, sessions, username, opts) {
317
317
  }));
318
318
  const totalLoc = sessions.reduce((sum, s) => sum + s.linesOfCode, 0);
319
319
  const totalDurationMinutes = computeMergedSessionDuration(sessions);
320
- const totalFilesChanged = opts?.totalFilesChanged ?? sessions.reduce((sum, s) => sum + s.filesChanged.length, 0);
320
+ const totalFilesChanged = opts?.totalFilesChanged ?? new Set(sessions.flatMap(s => s.filesChanged.map(f => f.path))).size;
321
321
  const totalAgentMinutes = sessions
322
322
  .filter((s) => s.isOrchestrated && s.children)
323
323
  .reduce((sum, s) => sum + s.children.reduce((cs, c) => cs + c.durationMinutes, 0), 0);
package/dist/index.js CHANGED
@@ -435,7 +435,7 @@ program
435
435
  const { existsSync, readFileSync } = await import('node:fs');
436
436
  const { join } = await import('node:path');
437
437
  const { homedir } = await import('node:os');
438
- const pidFile = join(homedir(), '.config', 'heyiam', 'daemon', 'daemon.pid');
438
+ const pidFile = join(homedir(), '.local', 'share', 'heyiam', 'daemon', 'daemon.pid');
439
439
  let daemonRunning = false;
440
440
  if (existsSync(pidFile)) {
441
441
  const pid = parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);
@@ -446,7 +446,7 @@ program
446
446
  catch { /* not running */ }
447
447
  }
448
448
  // Status file
449
- const statusFile = join(homedir(), '.config', 'heyiam', 'daemon', 'status.json');
449
+ const statusFile = join(homedir(), '.local', 'share', 'heyiam', 'daemon', 'status.json');
450
450
  let lastSync = 'never';
451
451
  if (existsSync(statusFile)) {
452
452
  try {
@@ -489,7 +489,7 @@ daemon
489
489
  const { homedir } = await import('node:os');
490
490
  const { spawn } = await import('node:child_process');
491
491
  const { writeFileSync, mkdirSync } = await import('node:fs');
492
- const daemonDir = join(homedir(), '.config', 'heyiam', 'daemon');
492
+ const daemonDir = join(homedir(), '.local', 'share', 'heyiam', 'daemon');
493
493
  const binaryPath = join(daemonDir, 'heyiam-tray');
494
494
  const pidFile = join(daemonDir, 'daemon.pid');
495
495
  if (!existsSync(binaryPath)) {
@@ -522,7 +522,7 @@ daemon
522
522
  const { existsSync, readFileSync, unlinkSync } = await import('node:fs');
523
523
  const { join } = await import('node:path');
524
524
  const { homedir } = await import('node:os');
525
- const pidFile = join(homedir(), '.config', 'heyiam', 'daemon', 'daemon.pid');
525
+ const pidFile = join(homedir(), '.local', 'share', 'heyiam', 'daemon', 'daemon.pid');
526
526
  if (!existsSync(pidFile)) {
527
527
  console.log('\n Daemon is not running.\n');
528
528
  return;
@@ -545,7 +545,7 @@ daemon
545
545
  const { existsSync, readFileSync } = await import('node:fs');
546
546
  const { join } = await import('node:path');
547
547
  const { homedir } = await import('node:os');
548
- const daemonDir = join(homedir(), '.config', 'heyiam', 'daemon');
548
+ const daemonDir = join(homedir(), '.local', 'share', 'heyiam', 'daemon');
549
549
  const pidFile = join(daemonDir, 'daemon.pid');
550
550
  const statusFile = join(daemonDir, 'status.json');
551
551
  const binaryPath = join(daemonDir, 'heyiam-tray');
@@ -629,7 +629,7 @@ daemon
629
629
  const { join } = await import('node:path');
630
630
  const { homedir } = await import('node:os');
631
631
  // Stop first
632
- const pidFile = join(homedir(), '.config', 'heyiam', 'daemon', 'daemon.pid');
632
+ const pidFile = join(homedir(), '.local', 'share', 'heyiam', 'daemon', 'daemon.pid');
633
633
  if (existsSync(pidFile)) {
634
634
  const pid = parseInt((await import('node:fs')).readFileSync(pidFile, 'utf-8').trim(), 10);
635
635
  try {
@@ -639,7 +639,7 @@ daemon
639
639
  unlinkSync(pidFile);
640
640
  }
641
641
  // Remove binary
642
- const binaryPath = join(homedir(), '.config', 'heyiam', 'daemon', 'heyiam-tray');
642
+ const binaryPath = join(homedir(), '.local', 'share', 'heyiam', 'daemon', 'heyiam-tray');
643
643
  if (existsSync(binaryPath))
644
644
  unlinkSync(binaryPath);
645
645
  // Remove auto-start registration (macOS launchd, Linux XDG)
@@ -212,7 +212,7 @@ export function createRouteContext(sessionsBasePath, dbPath) {
212
212
  if (!basePath) {
213
213
  const archiveResult = await archiveSessionFiles(allSessions);
214
214
  if (archiveResult.archived > 0) {
215
- console.log(`Preserved ${archiveResult.archived} sessions → ~/.config/heyiam/sessions/`);
215
+ console.log(`Preserved ${archiveResult.archived} sessions → ~/.local/share/heyiam/sessions/`);
216
216
  }
217
217
  }
218
218
  const byDir = new Map();
@@ -427,7 +427,7 @@ export function createRouteContext(sessionsBasePath, dbPath) {
427
427
  const parentMetas = proj.sessions.filter(s => !s.isSubagent);
428
428
  const allStats = await Promise.all(parentMetas.map((m) => getSessionStats(m, proj.name)));
429
429
  const totalLoc = allStats.reduce((s, st) => s + st.loc, 0);
430
- const totalFiles = allStats.reduce((s, st) => s + st.files, 0);
430
+ const totalFiles = db.prepare('SELECT COUNT(DISTINCT file_path) as c FROM session_files WHERE session_id IN (SELECT id FROM sessions WHERE project_dir = ?)').get(proj.dirName)?.c ?? allStats.reduce((s, st) => s + st.files, 0);
431
431
  const naiveDuration = allStats.reduce((s, st) => s + st.duration, 0);
432
432
  const totalDuration = computeMergedDurationFromDb(db, proj.dirName, naiveDuration);
433
433
  let totalAgentDuration = totalDuration;
@@ -15,7 +15,7 @@ export function createDashboardRouter(ctx) {
15
15
  const sync = getSyncState();
16
16
  // Count enhanced projects by checking the enhance cache directory
17
17
  let enhancedCount = 0;
18
- const enhanceDir = join(homedir(), '.config', 'heyiam', 'project-enhance');
18
+ const enhanceDir = join(homedir(), '.local', 'share', 'heyiam', 'project-enhance');
19
19
  try {
20
20
  const files = readdirSync(enhanceDir).filter((f) => f.endsWith('.json'));
21
21
  enhancedCount = files.length;
@@ -4,7 +4,7 @@ import fs from 'node:fs';
4
4
  import { execFileSync } from 'node:child_process';
5
5
  import { exportMarkdown, exportHtml, generateHtmlFiles, createZipBuffer } from '../export.js';
6
6
  import { buildProjectDetail } from './context.js';
7
- const EXPORTS_BASE = path.resolve(process.env.HOME || '~', '.config', 'heyiam', 'exports');
7
+ const EXPORTS_BASE = path.resolve(process.env.HOME || '~', '.local', 'share', 'heyiam', 'exports');
8
8
  /** Validate that an output path is within the safe exports directory. */
9
9
  function safeExportPath(outputPath, dirName, format) {
10
10
  const defaultPath = path.join(EXPORTS_BASE, dirName, format);
@@ -71,8 +71,9 @@ export function createExportRouter(ctx) {
71
71
  return;
72
72
  }
73
73
  const cache = data.enhanceCache ?? buildFallbackCache(data.sessions);
74
+ const totalFilesChanged = data.project.totalFiles;
74
75
  const outputPath = path.join(EXPORTS_BASE, dirName, 'markdown');
75
- const result = await exportMarkdown(dirName, cache, data.sessions, outputPath);
76
+ const result = await exportMarkdown(dirName, cache, data.sessions, outputPath, { totalFilesChanged });
76
77
  res.json(result);
77
78
  }
78
79
  catch (err) {
@@ -90,8 +91,9 @@ export function createExportRouter(ctx) {
90
91
  return;
91
92
  }
92
93
  const cache = data.enhanceCache ?? buildFallbackCache(data.sessions);
94
+ const totalFilesChanged = data.project.totalFiles;
93
95
  const outDir = safeExportPath(outputPath, dirName, 'markdown');
94
- const result = await exportMarkdown(dirName, cache, data.sessions, outDir);
96
+ const result = await exportMarkdown(dirName, cache, data.sessions, outDir, { totalFilesChanged });
95
97
  res.json(result);
96
98
  }
97
99
  catch (err) {
@@ -153,9 +155,10 @@ export function createExportRouter(ctx) {
153
155
  return;
154
156
  }
155
157
  const cache = data.enhanceCache ?? buildFallbackCache(data.sessions);
158
+ const totalFilesChanged = data.project.totalFiles;
156
159
  // Re-use exportMarkdown to a temp dir, then zip the result
157
160
  const tmpDir = path.join(EXPORTS_BASE, '.tmp', `${dirName}-${Date.now()}`);
158
- const result = await exportMarkdown(dirName, cache, data.sessions, tmpDir);
161
+ const result = await exportMarkdown(dirName, cache, data.sessions, tmpDir, { totalFilesChanged });
159
162
  // Read files into memory and zip
160
163
  const entries = result.files.map((filePath) => ({
161
164
  path: path.relative(tmpDir, filePath),
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { homedir, platform } from 'node:os';
5
5
  import { get as httpGet } from 'node:http';
6
- export const SCREENSHOTS_DIR = join(homedir(), '.config', 'heyiam', 'screenshots');
6
+ export const SCREENSHOTS_DIR = join(homedir(), '.local', 'share', 'heyiam', 'screenshots');
7
7
  /** Known Chrome binary paths by platform */
8
8
  const CHROME_PATHS = {
9
9
  darwin: [
package/dist/settings.js CHANGED
@@ -6,12 +6,16 @@ import { readConfig, writeConfig } from './auth.js';
6
6
  function getConfigDir() {
7
7
  return process.env.HEYIAM_CONFIG_DIR || join(homedir(), '.config', 'heyiam');
8
8
  }
9
+ /** XDG data directory — DB, enhanced data, archives, screenshots, published state. */
10
+ export function getDataDir() {
11
+ return process.env.HEYIAM_DATA_DIR || join(homedir(), '.local', 'share', 'heyiam');
12
+ }
9
13
  const ENHANCED_DIR = 'enhanced';
10
14
  const PROJECT_ENHANCE_DIR = 'project-enhance';
11
15
  const SETTINGS_FILE = 'settings.json';
12
16
  const SESSIONS_DIR = 'sessions';
13
17
  /** Directory where archived session hard links are stored. */
14
- export function getArchiveDir(configDir = getConfigDir()) {
18
+ export function getArchiveDir(configDir = getDataDir()) {
15
19
  return join(configDir, SESSIONS_DIR);
16
20
  }
17
21
  export function isArchiveEnabled(configDir) {
@@ -50,10 +54,10 @@ export function resetOnboarding(configDir) {
50
54
  export function getAnthropicApiKey(configDir) {
51
55
  return process.env.ANTHROPIC_API_KEY || getSettings(configDir).anthropicApiKey || undefined;
52
56
  }
53
- function enhancedDir(configDir = getConfigDir()) {
57
+ function enhancedDir(configDir = getDataDir()) {
54
58
  return join(configDir, ENHANCED_DIR);
55
59
  }
56
- function enhancedPath(sessionId, configDir = getConfigDir()) {
60
+ function enhancedPath(sessionId, configDir = getDataDir()) {
57
61
  return join(enhancedDir(configDir), `${sessionId}.json`);
58
62
  }
59
63
  export function saveEnhancedData(sessionId, data, configDir) {
@@ -103,10 +107,10 @@ export function buildProjectFingerprint(selectedSessionIds, configDir) {
103
107
  });
104
108
  return createHash('sha256').update(parts.join('|')).digest('hex').slice(0, 16);
105
109
  }
106
- function projectEnhanceDir(configDir = getConfigDir()) {
110
+ function projectEnhanceDir(configDir = getDataDir()) {
107
111
  return join(configDir, PROJECT_ENHANCE_DIR);
108
112
  }
109
- function projectEnhancePath(projectDirName, configDir = getConfigDir()) {
113
+ function projectEnhancePath(projectDirName, configDir = getDataDir()) {
110
114
  // Sanitize project dir name for filesystem
111
115
  const safe = projectDirName.replace(/[^a-zA-Z0-9._-]/g, '_');
112
116
  return join(projectEnhanceDir(configDir), `${safe}.json`);
@@ -157,10 +161,10 @@ export function deleteProjectEnhanceResult(projectDirName, configDir) {
157
161
  unlinkSync(path);
158
162
  }
159
163
  const UPLOADED_DIR = 'published';
160
- function uploadedDir(configDir = getConfigDir()) {
164
+ function uploadedDir(configDir = getDataDir()) {
161
165
  return join(configDir, UPLOADED_DIR);
162
166
  }
163
- function uploadedPath(projectDirName, configDir = getConfigDir()) {
167
+ function uploadedPath(projectDirName, configDir = getDataDir()) {
164
168
  const safe = projectDirName.replace(/[^a-zA-Z0-9._-]/g, '_');
165
169
  return join(uploadedDir(configDir), `${safe}.json`);
166
170
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "heyiam",
3
- "version": "0.2.26",
3
+ "version": "0.2.28",
4
4
  "description": "Turn AI coding sessions into portfolio case studies",
5
5
  "type": "module",
6
6
  "license": "MIT",