mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 21:53:28 +08:00
* feat: add SQLite state store and ECC status CLI * fix: replace better-sqlite3 with sql.js to eliminate native module CI failures better-sqlite3 requires native C++ compilation (node-gyp, prebuild-install) which fails in CI across npm/pnpm on all platforms: - npm ci: lock file out of sync with native transitive deps - pnpm: native bindings not found at runtime - Windows: native compilation fails entirely sql.js is a pure JavaScript/WASM SQLite implementation with zero native dependencies. The adapter in index.js wraps the sql.js API to match the better-sqlite3 interface used by migrations.js and queries.js. Key implementation detail: sql.js db.export() implicitly ends active transactions, so the adapter defers disk writes (saveToDisk) until after transaction commit via an inTransaction guard flag. createStateStore is now async (sql.js requires async WASM init). Updated status.js, sessions-cli.js, and tests accordingly.
178 lines
4.6 KiB
JavaScript
178 lines
4.6 KiB
JavaScript
#!/usr/bin/env node
|
|
'use strict';
|
|
|
|
const { createStateStore } = require('./lib/state-store');
|
|
|
|
function showHelp(exitCode = 0) {
|
|
console.log(`
|
|
Usage: node scripts/sessions-cli.js [<session-id>] [--db <path>] [--json] [--limit <n>]
|
|
|
|
List recent ECC sessions from the SQLite state store or inspect a single session
|
|
with worker, skill-run, and decision detail.
|
|
`);
|
|
process.exit(exitCode);
|
|
}
|
|
|
|
function parseArgs(argv) {
|
|
const args = argv.slice(2);
|
|
const parsed = {
|
|
dbPath: null,
|
|
help: false,
|
|
json: false,
|
|
limit: 10,
|
|
sessionId: null,
|
|
};
|
|
|
|
for (let index = 0; index < args.length; index += 1) {
|
|
const arg = args[index];
|
|
|
|
if (arg === '--db') {
|
|
parsed.dbPath = args[index + 1] || null;
|
|
index += 1;
|
|
} else if (arg === '--json') {
|
|
parsed.json = true;
|
|
} else if (arg === '--limit') {
|
|
parsed.limit = args[index + 1] || null;
|
|
index += 1;
|
|
} else if (arg === '--help' || arg === '-h') {
|
|
parsed.help = true;
|
|
} else if (!arg.startsWith('--') && !parsed.sessionId) {
|
|
parsed.sessionId = arg;
|
|
} else {
|
|
throw new Error(`Unknown argument: ${arg}`);
|
|
}
|
|
}
|
|
|
|
return parsed;
|
|
}
|
|
|
|
function printSessionList(payload) {
|
|
console.log('Recent sessions:\n');
|
|
|
|
if (payload.sessions.length === 0) {
|
|
console.log('No sessions found.');
|
|
return;
|
|
}
|
|
|
|
for (const session of payload.sessions) {
|
|
console.log(`- ${session.id} [${session.harness}/${session.adapterId}] ${session.state}`);
|
|
console.log(` Repo: ${session.repoRoot || '(unknown)'}`);
|
|
console.log(` Started: ${session.startedAt || '(unknown)'}`);
|
|
console.log(` Ended: ${session.endedAt || '(active)'}`);
|
|
console.log(` Workers: ${session.workerCount}`);
|
|
}
|
|
|
|
console.log(`\nTotal sessions: ${payload.totalCount}`);
|
|
}
|
|
|
|
function printWorkers(workers) {
|
|
console.log(`Workers: ${workers.length}`);
|
|
if (workers.length === 0) {
|
|
console.log(' - none');
|
|
return;
|
|
}
|
|
|
|
for (const worker of workers) {
|
|
console.log(` - ${worker.id || worker.label || '(unknown)'} ${worker.state || 'unknown'}`);
|
|
console.log(` Branch: ${worker.branch || '(unknown)'}`);
|
|
console.log(` Worktree: ${worker.worktree || '(unknown)'}`);
|
|
}
|
|
}
|
|
|
|
function printSkillRuns(skillRuns) {
|
|
console.log(`Skill runs: ${skillRuns.length}`);
|
|
if (skillRuns.length === 0) {
|
|
console.log(' - none');
|
|
return;
|
|
}
|
|
|
|
for (const skillRun of skillRuns) {
|
|
console.log(` - ${skillRun.id} ${skillRun.outcome} ${skillRun.skillId}@${skillRun.skillVersion}`);
|
|
console.log(` Task: ${skillRun.taskDescription}`);
|
|
console.log(` Duration: ${skillRun.durationMs ?? '(unknown)'} ms`);
|
|
}
|
|
}
|
|
|
|
function printDecisions(decisions) {
|
|
console.log(`Decisions: ${decisions.length}`);
|
|
if (decisions.length === 0) {
|
|
console.log(' - none');
|
|
return;
|
|
}
|
|
|
|
for (const decision of decisions) {
|
|
console.log(` - ${decision.id} ${decision.status}`);
|
|
console.log(` Title: ${decision.title}`);
|
|
console.log(` Alternatives: ${decision.alternatives.join(', ') || '(none)'}`);
|
|
}
|
|
}
|
|
|
|
function printSessionDetail(payload) {
|
|
console.log(`Session: ${payload.session.id}`);
|
|
console.log(`Harness: ${payload.session.harness}`);
|
|
console.log(`Adapter: ${payload.session.adapterId}`);
|
|
console.log(`State: ${payload.session.state}`);
|
|
console.log(`Repo: ${payload.session.repoRoot || '(unknown)'}`);
|
|
console.log(`Started: ${payload.session.startedAt || '(unknown)'}`);
|
|
console.log(`Ended: ${payload.session.endedAt || '(active)'}`);
|
|
console.log();
|
|
printWorkers(payload.workers);
|
|
console.log();
|
|
printSkillRuns(payload.skillRuns);
|
|
console.log();
|
|
printDecisions(payload.decisions);
|
|
}
|
|
|
|
async function main() {
|
|
let store = null;
|
|
|
|
try {
|
|
const options = parseArgs(process.argv);
|
|
if (options.help) {
|
|
showHelp(0);
|
|
}
|
|
|
|
store = await createStateStore({
|
|
dbPath: options.dbPath,
|
|
homeDir: process.env.HOME,
|
|
});
|
|
|
|
if (!options.sessionId) {
|
|
const payload = store.listRecentSessions({ limit: options.limit });
|
|
if (options.json) {
|
|
console.log(JSON.stringify(payload, null, 2));
|
|
} else {
|
|
printSessionList(payload);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const payload = store.getSessionDetail(options.sessionId);
|
|
if (!payload) {
|
|
throw new Error(`Session not found: ${options.sessionId}`);
|
|
}
|
|
|
|
if (options.json) {
|
|
console.log(JSON.stringify(payload, null, 2));
|
|
} else {
|
|
printSessionDetail(payload);
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error: ${error.message}`);
|
|
process.exit(1);
|
|
} finally {
|
|
if (store) {
|
|
store.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (require.main === module) {
|
|
main();
|
|
}
|
|
|
|
module.exports = {
|
|
main,
|
|
parseArgs,
|
|
};
|