← back to Desktop Dotbar
realm.js
77 lines
'use strict';
// realm.js — sort each live session into a FOLDER: 'dw' (Designer Wallcoverings work) or
// 'other' (everything else), so the bar can be scoped to one folder at a time (TK-12234).
// Signals, strongest first: the ticket's project + title (tk event log), the session's
// `doing` label, then the claude process cwd. Zero deps, $0 local.
const fs = require('fs');
const { execFile } = require('child_process');
const EVENTS = `${process.env.HOME}/.claude/tickets/events.jsonl`;
// DW vocabulary. Word-ish boundaries so "dw" doesn't match inside other words.
const DW_RE = new RegExp([
'(^|[^a-z])dw([^a-z]|$)', // dw, dw-commerce, dw_unified, DWPP-, "dw ops"
'designer[\\s_-]?wall', // designerwallcoverings / Designer-Wallcoverings
'shopify', 'gmc', 'merchant center', 'filemaker',
'_catalog', 'vendor_registry', 'sample[\\s_-]?follow', 'samplesshipped',
'kravet', 'phillipe romano', 'malibu wall', 'hollywood wall', 'fentucci',
'microsite', 'mfr[\\s_-]?sku', 'memo sample',
].join('|'), 'i');
function isDwText(s) { return !!s && DW_RE.test(String(s)); }
// ---- ticket index: TK-N -> "project title", built incrementally from the append-only log ----
const idx = new Map();
let offset = 0, carry = '';
function indexTickets(file = EVENTS) {
let st;
try { st = fs.statSync(file); } catch { return idx; }
if (st.size < offset) { idx.clear(); offset = 0; carry = ''; } // log rotated/truncated -> rebuild
if (st.size === offset) return idx;
const fd = fs.openSync(file, 'r');
try {
const buf = Buffer.alloc(st.size - offset);
fs.readSync(fd, buf, 0, buf.length, offset);
offset = st.size;
const lines = (carry + buf.toString('utf8')).split('\n');
carry = lines.pop(); // partial last line waits for next pass
for (const l of lines) {
if (!l.includes('"create"')) continue; // cheap pre-filter before JSON.parse
let e; try { e = JSON.parse(l); } catch { continue; }
if (e.type !== 'create' || !e.id) continue;
const m = /^(TK-\d+)/i.exec(e.id);
if (m) idx.set(m[1].toUpperCase(), `${e.project || ''} ${e.title || ''}`);
}
} finally { fs.closeSync(fd); }
return idx;
}
function _resetIndex() { idx.clear(); offset = 0; carry = ''; }
// ---- cwd of a pid (lsof), cached — a session's cwd rarely changes ----
const cwdCache = new Map();
function cwdOf(pid) {
return new Promise((resolve) => {
if (!pid) return resolve('');
if (cwdCache.has(pid)) return resolve(cwdCache.get(pid));
execFile('lsof', ['-a', '-p', String(pid), '-d', 'cwd', '-Fn'], { timeout: 4000 }, (err, out) => {
const line = String(out || '').split('\n').find(x => x.startsWith('n')) || '';
const cwd = line.slice(1);
if (cwd) cwdCache.set(pid, cwd);
resolve(cwd);
});
});
}
// Classify one session {ticket, doing, pid}. Returns 'dw' | 'other'.
async function realmOf(s, opts = {}) {
const tix = opts.index || indexTickets();
if (s.ticket && isDwText(tix.get(s.ticket))) return 'dw';
if (isDwText(s.doing)) return 'dw';
const cwd = opts.cwd !== undefined ? opts.cwd : await cwdOf(s.pid);
if (isDwText(cwd.replace(/^.*\/Projects\//, ''))) return 'dw'; // only the project part of the path
return 'other';
}
module.exports = { realmOf, isDwText, indexTickets, _resetIndex };