prismalens 0.2.0 → 0.4.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 +29 -13
- package/dist/bin/prismalens.js +39 -50
- package/dist/chunk-2Z7QZSIS.js +20 -0
- package/dist/chunk-5BT3XQOG.js +2990 -0
- package/dist/chunk-6N4PHBII.js +30 -0
- package/dist/chunk-CTKCK7QT.js +387 -0
- package/dist/chunk-CVSS5JPC.js +130 -0
- package/dist/chunk-HPE5CTO4.js +22 -0
- package/dist/chunk-PTBCAI34.js +34 -0
- package/dist/chunk-PVQVFRIV.js +452 -0
- package/dist/chunk-QDK5PAI3.js +155 -0
- package/dist/chunk-SSEIMBQ2.js +204 -0
- package/dist/chunk-TGP6QTFO.js +156 -0
- package/dist/chunk-V6M6ISKI.js +161 -0
- package/dist/chunk-XH6DIKTA.js +83 -0
- package/dist/src/cli/auth.js +112 -101
- package/dist/src/cli/doctor.js +184 -187
- package/dist/src/cli/flags.js +6 -29
- package/dist/src/cli/grouping.js +16 -227
- package/dist/src/cli/init.js +58 -52
- package/dist/src/cli/investigate.js +23 -465
- package/dist/src/cli/listen.js +550 -286
- package/dist/src/cli/report.js +70 -67
- package/dist/src/cli/serve.js +260 -30
- package/dist/src/cli/status.js +87 -86
- package/package.json +16 -7
- package/dist/bin/prismalens.d.ts +0 -3
- package/dist/bin/prismalens.d.ts.map +0 -1
- package/dist/bin/prismalens.js.map +0 -1
- package/dist/src/cli/auth.d.ts +0 -3
- package/dist/src/cli/auth.d.ts.map +0 -1
- package/dist/src/cli/auth.js.map +0 -1
- package/dist/src/cli/doctor.d.ts +0 -18
- package/dist/src/cli/doctor.d.ts.map +0 -1
- package/dist/src/cli/doctor.js.map +0 -1
- package/dist/src/cli/flags.d.ts +0 -2
- package/dist/src/cli/flags.d.ts.map +0 -1
- package/dist/src/cli/flags.js.map +0 -1
- package/dist/src/cli/grouping.d.ts +0 -18
- package/dist/src/cli/grouping.d.ts.map +0 -1
- package/dist/src/cli/grouping.js.map +0 -1
- package/dist/src/cli/init.d.ts +0 -3
- package/dist/src/cli/init.d.ts.map +0 -1
- package/dist/src/cli/init.js.map +0 -1
- package/dist/src/cli/investigate.d.ts +0 -115
- package/dist/src/cli/investigate.d.ts.map +0 -1
- package/dist/src/cli/investigate.js.map +0 -1
- package/dist/src/cli/listen.d.ts +0 -56
- package/dist/src/cli/listen.d.ts.map +0 -1
- package/dist/src/cli/listen.js.map +0 -1
- package/dist/src/cli/report.d.ts +0 -24
- package/dist/src/cli/report.d.ts.map +0 -1
- package/dist/src/cli/report.js.map +0 -1
- package/dist/src/cli/serve.d.ts +0 -3
- package/dist/src/cli/serve.d.ts.map +0 -1
- package/dist/src/cli/serve.js.map +0 -1
- package/dist/src/cli/status.d.ts +0 -19
- package/dist/src/cli/status.d.ts.map +0 -1
- package/dist/src/cli/status.js.map +0 -1
- package/dist/src/config/loader.d.ts +0 -22
- package/dist/src/config/loader.d.ts.map +0 -1
- package/dist/src/config/loader.js +0 -193
- package/dist/src/config/loader.js.map +0 -1
- package/dist/src/config/schema.d.ts +0 -463
- package/dist/src/config/schema.d.ts.map +0 -1
- package/dist/src/config/schema.js +0 -217
- package/dist/src/config/schema.js.map +0 -1
- package/dist/src/core/caps.d.ts +0 -36
- package/dist/src/core/caps.d.ts.map +0 -1
- package/dist/src/core/caps.js +0 -36
- package/dist/src/core/caps.js.map +0 -1
- package/dist/src/core/db.d.ts +0 -3
- package/dist/src/core/db.d.ts.map +0 -1
- package/dist/src/core/db.js +0 -105
- package/dist/src/core/db.js.map +0 -1
- package/dist/src/core/detect-repo.d.ts +0 -4
- package/dist/src/core/detect-repo.d.ts.map +0 -1
- package/dist/src/core/detect-repo.js +0 -28
- package/dist/src/core/detect-repo.js.map +0 -1
- package/dist/src/core/file-session-store.d.ts +0 -10
- package/dist/src/core/file-session-store.d.ts.map +0 -1
- package/dist/src/core/file-session-store.js +0 -34
- package/dist/src/core/file-session-store.js.map +0 -1
- package/dist/src/core/parse-stdin.d.ts +0 -2
- package/dist/src/core/parse-stdin.d.ts.map +0 -1
- package/dist/src/core/parse-stdin.js +0 -21
- package/dist/src/core/parse-stdin.js.map +0 -1
- package/dist/src/core/run-investigation.d.ts +0 -54
- package/dist/src/core/run-investigation.d.ts.map +0 -1
- package/dist/src/core/run-investigation.js +0 -200
- package/dist/src/core/run-investigation.js.map +0 -1
- package/dist/src/core/session.d.ts +0 -63
- package/dist/src/core/session.d.ts.map +0 -1
- package/dist/src/core/session.js +0 -45
- package/dist/src/core/session.js.map +0 -1
- package/dist/src/core/slack-notify.d.ts +0 -9
- package/dist/src/core/slack-notify.d.ts.map +0 -1
- package/dist/src/core/slack-notify.js +0 -57
- package/dist/src/core/slack-notify.js.map +0 -1
- package/dist/src/core/sqlite-session-store.d.ts +0 -39
- package/dist/src/core/sqlite-session-store.d.ts.map +0 -1
- package/dist/src/core/sqlite-session-store.js +0 -239
- package/dist/src/core/sqlite-session-store.js.map +0 -1
- package/dist/src/http/server.d.ts +0 -31
- package/dist/src/http/server.d.ts.map +0 -1
- package/dist/src/http/server.js +0 -164
- package/dist/src/http/server.js.map +0 -1
- package/dist/src/jsonrpc/server.d.ts +0 -15
- package/dist/src/jsonrpc/server.d.ts.map +0 -1
- package/dist/src/jsonrpc/server.js +0 -244
- package/dist/src/jsonrpc/server.js.map +0 -1
- package/dist/src/jsonrpc/types.d.ts +0 -12
- package/dist/src/jsonrpc/types.d.ts.map +0 -1
- package/dist/src/jsonrpc/types.js +0 -4
- package/dist/src/jsonrpc/types.js.map +0 -1
- package/dist/src/version.d.ts +0 -8
- package/dist/src/version.d.ts.map +0 -1
- package/dist/src/version.js +0 -27
- package/dist/src/version.js.map +0 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// ../@prismalens/config/dist/investigation.js
|
|
2
|
+
var TELEMETRY_DEFAULTS = {
|
|
3
|
+
prometheusUrl: "http://localhost:9090",
|
|
4
|
+
alertmanagerUrl: "http://localhost:9093",
|
|
5
|
+
apiUrl: "http://localhost:5000"
|
|
6
|
+
};
|
|
7
|
+
var SYNTH_DEFAULTS = {
|
|
8
|
+
/** OpenAI-compatible endpoint — note the `/v1`, which the AI SDK requires. */
|
|
9
|
+
baseURL: "https://ollama.com/v1"
|
|
10
|
+
};
|
|
11
|
+
var HARNESS_INIT_TIMEOUT_MS = 12e4;
|
|
12
|
+
var HARNESS_PROMPT_TIMEOUT_MS = 18e4;
|
|
13
|
+
var HARNESS_WALL_CLOCK_MS = 9e5;
|
|
14
|
+
var PREVIEW_LIMIT = 4e3;
|
|
15
|
+
var PREVIEW_CAP = 1200;
|
|
16
|
+
var TRANSCRIPT_CAP = 24e3;
|
|
17
|
+
var INVESTIGATION_DEFAULTS = {
|
|
18
|
+
telemetry: TELEMETRY_DEFAULTS,
|
|
19
|
+
synth: SYNTH_DEFAULTS,
|
|
20
|
+
harnessInitTimeoutMs: HARNESS_INIT_TIMEOUT_MS,
|
|
21
|
+
harnessPromptTimeoutMs: HARNESS_PROMPT_TIMEOUT_MS,
|
|
22
|
+
harnessWallClockMs: HARNESS_WALL_CLOCK_MS,
|
|
23
|
+
previewLimit: PREVIEW_LIMIT,
|
|
24
|
+
previewCap: PREVIEW_CAP,
|
|
25
|
+
transcriptCap: TRANSCRIPT_CAP
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
INVESTIGATION_DEFAULTS
|
|
30
|
+
};
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getAppDataDir
|
|
3
|
+
} from "./chunk-2Z7QZSIS.js";
|
|
4
|
+
|
|
5
|
+
// src/core/session.ts
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { join as join2, resolve } from "node:path";
|
|
8
|
+
|
|
9
|
+
// src/core/db.ts
|
|
10
|
+
import { existsSync, mkdirSync, renameSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
var DatabaseSync;
|
|
13
|
+
try {
|
|
14
|
+
const mod = await import("node:sqlite");
|
|
15
|
+
DatabaseSync = mod.DatabaseSync;
|
|
16
|
+
} catch {
|
|
17
|
+
console.error("PrismaLens requires Node >= 22.13.");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
var SCHEMA = `
|
|
21
|
+
PRAGMA journal_mode = WAL;
|
|
22
|
+
PRAGMA busy_timeout = 5000;
|
|
23
|
+
PRAGMA foreign_keys = ON;
|
|
24
|
+
|
|
25
|
+
CREATE TABLE IF NOT EXISTS groups (
|
|
26
|
+
id TEXT PRIMARY KEY,
|
|
27
|
+
group_key TEXT,
|
|
28
|
+
formed_by TEXT NOT NULL DEFAULT 'window',
|
|
29
|
+
created_at TEXT NOT NULL
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
CREATE TABLE IF NOT EXISTS runs (
|
|
33
|
+
run_id TEXT PRIMARY KEY,
|
|
34
|
+
group_id TEXT REFERENCES groups(id),
|
|
35
|
+
status TEXT NOT NULL CHECK (status IN ('running','done','errored','suppressed')),
|
|
36
|
+
alertname TEXT,
|
|
37
|
+
agent TEXT,
|
|
38
|
+
repo TEXT,
|
|
39
|
+
workspace_path TEXT NOT NULL,
|
|
40
|
+
error TEXT,
|
|
41
|
+
suppression_reason TEXT,
|
|
42
|
+
created_at TEXT NOT NULL,
|
|
43
|
+
updated_at TEXT NOT NULL,
|
|
44
|
+
completed_at TEXT
|
|
45
|
+
);
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_runs_created_at ON runs(created_at DESC);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_runs_status ON runs(status);
|
|
48
|
+
|
|
49
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
50
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
51
|
+
run_id TEXT NOT NULL REFERENCES runs(run_id),
|
|
52
|
+
payload TEXT NOT NULL
|
|
53
|
+
);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_events_run ON events(run_id, id);
|
|
55
|
+
|
|
56
|
+
CREATE TABLE IF NOT EXISTS reports (
|
|
57
|
+
run_id TEXT PRIMARY KEY REFERENCES runs(run_id),
|
|
58
|
+
payload TEXT NOT NULL
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
-- One row per alert attached to a group, in insertion order (rowid).
|
|
62
|
+
-- late = 0 for the alerts that formed the window, 1 for alerts that
|
|
63
|
+
-- attached while the group's run was already in flight.
|
|
64
|
+
CREATE TABLE IF NOT EXISTS group_alerts (
|
|
65
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
66
|
+
group_id TEXT NOT NULL REFERENCES groups(id),
|
|
67
|
+
late INTEGER NOT NULL,
|
|
68
|
+
payload TEXT NOT NULL
|
|
69
|
+
);
|
|
70
|
+
CREATE INDEX IF NOT EXISTS idx_group_alerts ON group_alerts(group_id, id);
|
|
71
|
+
`;
|
|
72
|
+
var SCHEMA_CHECK = `
|
|
73
|
+
SELECT id, group_key, formed_by, created_at FROM groups LIMIT 1;
|
|
74
|
+
SELECT run_id, group_id, status, alertname, agent, repo, workspace_path, error, suppression_reason, created_at, updated_at, completed_at FROM runs LIMIT 1;
|
|
75
|
+
SELECT id, run_id, payload FROM events LIMIT 1;
|
|
76
|
+
SELECT run_id, payload FROM reports LIMIT 1;
|
|
77
|
+
SELECT id, group_id, late, payload FROM group_alerts LIMIT 1;
|
|
78
|
+
`;
|
|
79
|
+
function openDatabase(baseDir) {
|
|
80
|
+
mkdirSync(baseDir, { recursive: true });
|
|
81
|
+
const dbPath = join(baseDir, "prismalens.db");
|
|
82
|
+
let db = new DatabaseSync(dbPath);
|
|
83
|
+
try {
|
|
84
|
+
db.exec(SCHEMA);
|
|
85
|
+
db.exec(SCHEMA_CHECK);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
88
|
+
if (!/no such column|no such table|has no column named/i.test(msg)) {
|
|
89
|
+
try {
|
|
90
|
+
db.close();
|
|
91
|
+
} catch {
|
|
92
|
+
}
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
const backupPath = join(baseDir, `prismalens.db.bak-${Date.now()}`);
|
|
96
|
+
if (existsSync(`${dbPath}-wal`))
|
|
97
|
+
renameSync(`${dbPath}-wal`, `${backupPath}-wal`);
|
|
98
|
+
if (existsSync(`${dbPath}-shm`))
|
|
99
|
+
renameSync(`${dbPath}-shm`, `${backupPath}-shm`);
|
|
100
|
+
db.close();
|
|
101
|
+
renameSync(dbPath, backupPath);
|
|
102
|
+
console.warn(
|
|
103
|
+
`[!] Workspace schema is incompatible. Backed up old database to ${backupPath} and recreated.`
|
|
104
|
+
);
|
|
105
|
+
db = new DatabaseSync(dbPath);
|
|
106
|
+
db.exec(SCHEMA);
|
|
107
|
+
}
|
|
108
|
+
return db;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/core/sqlite-session-store.ts
|
|
112
|
+
function tx(db, fn) {
|
|
113
|
+
db.exec("BEGIN");
|
|
114
|
+
try {
|
|
115
|
+
const r = fn();
|
|
116
|
+
db.exec("COMMIT");
|
|
117
|
+
return r;
|
|
118
|
+
} catch (e) {
|
|
119
|
+
db.exec("ROLLBACK");
|
|
120
|
+
throw e;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function mapRun(row) {
|
|
124
|
+
const record = {
|
|
125
|
+
runId: row.run_id,
|
|
126
|
+
status: row.status,
|
|
127
|
+
workspacePath: row.workspace_path,
|
|
128
|
+
createdAt: row.created_at,
|
|
129
|
+
updatedAt: row.updated_at
|
|
130
|
+
};
|
|
131
|
+
if (row.alertname) record.alertname = row.alertname;
|
|
132
|
+
if (row.agent) record.agent = row.agent;
|
|
133
|
+
if (row.repo) record.repo = row.repo;
|
|
134
|
+
if (row.error) record.error = row.error;
|
|
135
|
+
if (row.suppression_reason) record.suppressionReason = row.suppression_reason;
|
|
136
|
+
if (row.completed_at) record.completedAt = row.completed_at;
|
|
137
|
+
return record;
|
|
138
|
+
}
|
|
139
|
+
var SqliteSessionManager = class {
|
|
140
|
+
constructor(baseDir, db, workspaceDir, assertRunId) {
|
|
141
|
+
this.baseDir = baseDir;
|
|
142
|
+
this.db = db;
|
|
143
|
+
this.workspaceDir = workspaceDir;
|
|
144
|
+
this.assertRunId = assertRunId;
|
|
145
|
+
}
|
|
146
|
+
baseDir;
|
|
147
|
+
db;
|
|
148
|
+
workspaceDir;
|
|
149
|
+
assertRunId;
|
|
150
|
+
async create(input) {
|
|
151
|
+
this.assertRunId(input.runId);
|
|
152
|
+
const dir = this.workspaceDir(input.runId);
|
|
153
|
+
const { mkdirSync: mkdirSync2 } = await import("node:fs");
|
|
154
|
+
mkdirSync2(dir, { recursive: true });
|
|
155
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
156
|
+
return tx(this.db, () => {
|
|
157
|
+
const insertGroup = this.db.prepare(`
|
|
158
|
+
INSERT INTO groups (id, formed_by, created_at)
|
|
159
|
+
VALUES (?, 'window', ?)
|
|
160
|
+
ON CONFLICT DO NOTHING
|
|
161
|
+
`);
|
|
162
|
+
insertGroup.run(input.runId, now);
|
|
163
|
+
const insertRun = this.db.prepare(`
|
|
164
|
+
INSERT INTO runs (
|
|
165
|
+
run_id, group_id, status, alertname, agent, repo,
|
|
166
|
+
workspace_path, created_at, updated_at
|
|
167
|
+
) VALUES (?, ?, 'running', ?, ?, ?, ?, ?, ?)
|
|
168
|
+
`);
|
|
169
|
+
insertRun.run(
|
|
170
|
+
input.runId,
|
|
171
|
+
input.runId,
|
|
172
|
+
input.alertname ?? null,
|
|
173
|
+
input.agent ?? null,
|
|
174
|
+
input.repo ?? null,
|
|
175
|
+
dir,
|
|
176
|
+
now,
|
|
177
|
+
now
|
|
178
|
+
);
|
|
179
|
+
const getRun = this.db.prepare("SELECT * FROM runs WHERE run_id = ?");
|
|
180
|
+
const row = getRun.get(input.runId);
|
|
181
|
+
if (!row) throw new Error("Failed to read back created session");
|
|
182
|
+
return mapRun(row);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async get(runId) {
|
|
186
|
+
this.assertRunId(runId);
|
|
187
|
+
const getRun = this.db.prepare("SELECT * FROM runs WHERE run_id = ?");
|
|
188
|
+
const row = getRun.get(runId);
|
|
189
|
+
return row ? mapRun(row) : null;
|
|
190
|
+
}
|
|
191
|
+
async update(runId, updates) {
|
|
192
|
+
this.assertRunId(runId);
|
|
193
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
194
|
+
return tx(this.db, () => {
|
|
195
|
+
const existingStmt = this.db.prepare(
|
|
196
|
+
"SELECT * FROM runs WHERE run_id = ?"
|
|
197
|
+
);
|
|
198
|
+
const existing = existingStmt.get(runId);
|
|
199
|
+
if (!existing) throw new Error(`Session "${runId}" not found`);
|
|
200
|
+
const status = updates.status ?? existing.status;
|
|
201
|
+
const alertname = updates.alertname !== void 0 ? updates.alertname : existing.alertname;
|
|
202
|
+
const agent = updates.agent !== void 0 ? updates.agent : existing.agent;
|
|
203
|
+
const repo = updates.repo !== void 0 ? updates.repo : existing.repo;
|
|
204
|
+
const error = updates.error !== void 0 ? updates.error : existing.error;
|
|
205
|
+
const completedAt = updates.completedAt !== void 0 ? updates.completedAt : existing.completed_at;
|
|
206
|
+
const updateStmt = this.db.prepare(`
|
|
207
|
+
UPDATE runs SET
|
|
208
|
+
status = ?,
|
|
209
|
+
alertname = ?,
|
|
210
|
+
agent = ?,
|
|
211
|
+
repo = ?,
|
|
212
|
+
error = ?,
|
|
213
|
+
completed_at = ?,
|
|
214
|
+
updated_at = ?
|
|
215
|
+
WHERE run_id = ?
|
|
216
|
+
`);
|
|
217
|
+
const res = updateStmt.run(
|
|
218
|
+
status,
|
|
219
|
+
alertname ?? null,
|
|
220
|
+
agent ?? null,
|
|
221
|
+
repo ?? null,
|
|
222
|
+
error ?? null,
|
|
223
|
+
completedAt ?? null,
|
|
224
|
+
now,
|
|
225
|
+
runId
|
|
226
|
+
);
|
|
227
|
+
if (res.changes === 0) {
|
|
228
|
+
throw new Error(`Session "${runId}" not found`);
|
|
229
|
+
}
|
|
230
|
+
const row = existingStmt.get(runId);
|
|
231
|
+
if (!row) throw new Error("Failed to read back updated session");
|
|
232
|
+
return mapRun(row);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
async list(filter) {
|
|
236
|
+
let query = "SELECT * FROM runs";
|
|
237
|
+
let params = [];
|
|
238
|
+
if (filter?.status && filter.status.length > 0) {
|
|
239
|
+
const placeholders = filter.status.map(() => "?").join(",");
|
|
240
|
+
query += ` WHERE status IN (${placeholders})`;
|
|
241
|
+
params = [...filter.status];
|
|
242
|
+
}
|
|
243
|
+
query += " ORDER BY created_at DESC, rowid DESC";
|
|
244
|
+
const stmt = this.db.prepare(query);
|
|
245
|
+
const rows = stmt.all(...params);
|
|
246
|
+
return rows.map(mapRun);
|
|
247
|
+
}
|
|
248
|
+
async appendEvent(runId, event) {
|
|
249
|
+
const stmt = this.db.prepare(
|
|
250
|
+
"INSERT INTO events(run_id, payload) VALUES(?, ?)"
|
|
251
|
+
);
|
|
252
|
+
stmt.run(runId, JSON.stringify(event));
|
|
253
|
+
}
|
|
254
|
+
async readEvents(runId) {
|
|
255
|
+
const stmt = this.db.prepare(
|
|
256
|
+
"SELECT payload FROM events WHERE run_id = ? ORDER BY id"
|
|
257
|
+
);
|
|
258
|
+
const rows = stmt.all(runId);
|
|
259
|
+
return rows.map((r) => JSON.parse(r.payload));
|
|
260
|
+
}
|
|
261
|
+
async writeReport(runId, report) {
|
|
262
|
+
const stmt = this.db.prepare(`
|
|
263
|
+
INSERT INTO reports (run_id, payload)
|
|
264
|
+
VALUES (?, ?)
|
|
265
|
+
ON CONFLICT(run_id) DO UPDATE SET payload=excluded.payload
|
|
266
|
+
`);
|
|
267
|
+
stmt.run(runId, JSON.stringify(report));
|
|
268
|
+
}
|
|
269
|
+
async readReport(runId) {
|
|
270
|
+
try {
|
|
271
|
+
const stmt = this.db.prepare(
|
|
272
|
+
"SELECT payload FROM reports WHERE run_id = ?"
|
|
273
|
+
);
|
|
274
|
+
const row = stmt.get(runId);
|
|
275
|
+
if (!row) return null;
|
|
276
|
+
return JSON.parse(row.payload);
|
|
277
|
+
} catch {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Persist a group's record (ADR-0012 grouping): upsert the `groups` row with
|
|
283
|
+
* the key + how it formed (taken from the record, never hardcoded), then
|
|
284
|
+
* replace its `group_alerts` with the formative alerts (late=0) followed by
|
|
285
|
+
* any already-attached late alerts (late=1), each in order. The group row may
|
|
286
|
+
* already exist (a plain `create()` seeds one) — we keep its created_at.
|
|
287
|
+
*/
|
|
288
|
+
async writeGroupRecord(runId, rec) {
|
|
289
|
+
this.assertRunId(runId);
|
|
290
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
291
|
+
tx(this.db, () => {
|
|
292
|
+
this.db.prepare(`
|
|
293
|
+
INSERT INTO groups (id, group_key, formed_by, created_at)
|
|
294
|
+
VALUES (?, ?, ?, ?)
|
|
295
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
296
|
+
group_key = excluded.group_key,
|
|
297
|
+
formed_by = excluded.formed_by
|
|
298
|
+
`).run(runId, rec.groupKey, rec.formedBy, now);
|
|
299
|
+
this.db.prepare("DELETE FROM group_alerts WHERE group_id = ?").run(runId);
|
|
300
|
+
const insertAlert = this.db.prepare(
|
|
301
|
+
"INSERT INTO group_alerts (group_id, late, payload) VALUES (?, ?, ?)"
|
|
302
|
+
);
|
|
303
|
+
for (const alert of rec.alerts) {
|
|
304
|
+
insertAlert.run(runId, 0, JSON.stringify(alert));
|
|
305
|
+
}
|
|
306
|
+
for (const alert of rec.lateAlerts) {
|
|
307
|
+
insertAlert.run(runId, 1, JSON.stringify(alert));
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
/** Attach a late alert to an existing group, appended in arrival order. */
|
|
312
|
+
async appendGroupAlert(runId, alert) {
|
|
313
|
+
this.assertRunId(runId);
|
|
314
|
+
tx(this.db, () => {
|
|
315
|
+
const group = this.db.prepare("SELECT id FROM groups WHERE id = ?").get(runId);
|
|
316
|
+
if (!group) {
|
|
317
|
+
throw new Error(
|
|
318
|
+
`Cannot append alert to missing group for run ${runId}`
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
this.db.prepare(
|
|
322
|
+
"INSERT INTO group_alerts (group_id, late, payload) VALUES (?, 1, ?)"
|
|
323
|
+
).run(runId, JSON.stringify(alert));
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
async recordSuppressed(input) {
|
|
327
|
+
this.assertRunId(input.runId);
|
|
328
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
329
|
+
tx(this.db, () => {
|
|
330
|
+
this.db.prepare(`
|
|
331
|
+
INSERT INTO groups (id, formed_by, created_at)
|
|
332
|
+
VALUES (?, 'window', ?)
|
|
333
|
+
ON CONFLICT DO NOTHING
|
|
334
|
+
`).run(input.runId, now);
|
|
335
|
+
this.db.prepare(`
|
|
336
|
+
INSERT INTO runs (
|
|
337
|
+
run_id, group_id, status, alertname,
|
|
338
|
+
workspace_path, suppression_reason,
|
|
339
|
+
created_at, updated_at, completed_at
|
|
340
|
+
) VALUES (?, ?, 'suppressed', ?, '', ?, ?, ?, ?)
|
|
341
|
+
`).run(
|
|
342
|
+
input.runId,
|
|
343
|
+
input.runId,
|
|
344
|
+
input.alertname ?? null,
|
|
345
|
+
input.reason,
|
|
346
|
+
now,
|
|
347
|
+
now,
|
|
348
|
+
now
|
|
349
|
+
);
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
/** Release the underlying db handle. Callers that own a transient manager
|
|
353
|
+
* (e.g. one per `pl listen` run) must close it or leak a connection. */
|
|
354
|
+
close() {
|
|
355
|
+
this.db.close();
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// src/core/session.ts
|
|
360
|
+
var RUN_ID_RE = /^[a-zA-Z0-9_-]{1,128}$/;
|
|
361
|
+
function expandHome(p) {
|
|
362
|
+
if (p === "~") return homedir();
|
|
363
|
+
if (p.startsWith("~/")) return join2(homedir(), p.slice(2));
|
|
364
|
+
return p;
|
|
365
|
+
}
|
|
366
|
+
function resolveBaseDir(baseDir) {
|
|
367
|
+
if (!baseDir || baseDir === "~/.prismalens") return getAppDataDir();
|
|
368
|
+
return resolve(expandHome(baseDir));
|
|
369
|
+
}
|
|
370
|
+
function createSessionManager(baseDir) {
|
|
371
|
+
const base = resolveBaseDir(baseDir);
|
|
372
|
+
const db = openDatabase(base);
|
|
373
|
+
const runsRoot = join2(base, "runs");
|
|
374
|
+
function assertRunId(runId) {
|
|
375
|
+
if (!RUN_ID_RE.test(runId)) throw new Error(`Invalid runId: "${runId}"`);
|
|
376
|
+
}
|
|
377
|
+
function workspaceDir(runId) {
|
|
378
|
+
assertRunId(runId);
|
|
379
|
+
return join2(runsRoot, runId);
|
|
380
|
+
}
|
|
381
|
+
return new SqliteSessionManager(base, db, workspaceDir, assertRunId);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export {
|
|
385
|
+
resolveBaseDir,
|
|
386
|
+
createSessionManager
|
|
387
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LLM_PROVIDERS,
|
|
3
|
+
getApiKeyEnvVar
|
|
4
|
+
} from "./chunk-XH6DIKTA.js";
|
|
5
|
+
import {
|
|
6
|
+
ensureAppDataDir,
|
|
7
|
+
getAppDataDir
|
|
8
|
+
} from "./chunk-2Z7QZSIS.js";
|
|
9
|
+
|
|
10
|
+
// ../@prismalens/config/dist/auth-store.js
|
|
11
|
+
import { chmodSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import consola from "consola";
|
|
14
|
+
var AUTH_FILE_NAME = "auth.json";
|
|
15
|
+
function getAuthFilePath() {
|
|
16
|
+
return join(getAppDataDir(), AUTH_FILE_NAME);
|
|
17
|
+
}
|
|
18
|
+
function readAuthStore() {
|
|
19
|
+
const file = getAuthFilePath();
|
|
20
|
+
try {
|
|
21
|
+
const stat = statSync(file);
|
|
22
|
+
if ((stat.mode & 511) !== 384) {
|
|
23
|
+
chmodSync(file, 384);
|
|
24
|
+
}
|
|
25
|
+
const content = readFileSync(file, "utf-8");
|
|
26
|
+
return JSON.parse(content);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
if (err && typeof err === "object" && "code" in err && (err.code === "EACCES" || err.code === "EPERM")) {
|
|
29
|
+
consola.warn("Auth store file is inaccessible due to permissions. Treating as empty.");
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
if (err instanceof SyntaxError) {
|
|
36
|
+
consola.warn("Auth store file is corrupted. Treating as empty.");
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function writeAuthStore(store) {
|
|
43
|
+
const file = getAuthFilePath();
|
|
44
|
+
ensureAppDataDir();
|
|
45
|
+
const tmpFile = `${file}.tmp`;
|
|
46
|
+
writeFileSync(tmpFile, JSON.stringify(store, null, 2), { mode: 384 });
|
|
47
|
+
renameSync(tmpFile, file);
|
|
48
|
+
}
|
|
49
|
+
function setStoredCredential(provider, key) {
|
|
50
|
+
const store = readAuthStore();
|
|
51
|
+
store[provider] = { type: "api", key };
|
|
52
|
+
writeAuthStore(store);
|
|
53
|
+
}
|
|
54
|
+
function removeStoredCredential(provider) {
|
|
55
|
+
const store = readAuthStore();
|
|
56
|
+
delete store[provider];
|
|
57
|
+
writeAuthStore(store);
|
|
58
|
+
}
|
|
59
|
+
function getStoredCredential(provider) {
|
|
60
|
+
const store = readAuthStore();
|
|
61
|
+
return store[provider]?.key;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ../@prismalens/config/dist/env/readers.js
|
|
65
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
66
|
+
function readEnv(name) {
|
|
67
|
+
if (process.env[name] !== void 0) {
|
|
68
|
+
return process.env[name];
|
|
69
|
+
}
|
|
70
|
+
const filePath = process.env[`${name}_FILE`];
|
|
71
|
+
if (filePath) {
|
|
72
|
+
try {
|
|
73
|
+
return readFileSync2(filePath, "utf-8").replace(/\r?\n$/, "");
|
|
74
|
+
} catch (error) {
|
|
75
|
+
throw new Error(`Failed to read ${name} from file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return void 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ../@prismalens/config/dist/credentials.js
|
|
82
|
+
function ensureV1(baseURL) {
|
|
83
|
+
const trimmed = baseURL.replace(/\/$/, "");
|
|
84
|
+
if (trimmed.endsWith("/v1"))
|
|
85
|
+
return trimmed;
|
|
86
|
+
return `${trimmed}/v1`;
|
|
87
|
+
}
|
|
88
|
+
function resolveCredentials(providerId, configBaseUrl) {
|
|
89
|
+
const envVar = getApiKeyEnvVar(providerId);
|
|
90
|
+
let apiKey;
|
|
91
|
+
let source = "none";
|
|
92
|
+
if (process.env[envVar]?.trim()) {
|
|
93
|
+
apiKey = process.env[envVar];
|
|
94
|
+
source = "env";
|
|
95
|
+
} else if (process.env[`${envVar}_FILE`]?.trim()) {
|
|
96
|
+
apiKey = readEnv(envVar);
|
|
97
|
+
apiKey = apiKey?.trim() ? apiKey : void 0;
|
|
98
|
+
source = apiKey ? "file" : "none";
|
|
99
|
+
} else {
|
|
100
|
+
const storedKey = getStoredCredential(providerId);
|
|
101
|
+
if (storedKey?.trim()) {
|
|
102
|
+
apiKey = storedKey;
|
|
103
|
+
source = "stored";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const provider = LLM_PROVIDERS[providerId];
|
|
107
|
+
let baseURL = configBaseUrl;
|
|
108
|
+
if (!baseURL) {
|
|
109
|
+
baseURL = process.env[`${provider.id.toUpperCase()}_BASE_URL`];
|
|
110
|
+
}
|
|
111
|
+
if (!baseURL && providerId !== "custom" && "defaultBaseUrl" in provider) {
|
|
112
|
+
baseURL = provider.defaultBaseUrl;
|
|
113
|
+
}
|
|
114
|
+
if (baseURL && (providerId === "openai" || providerId === "custom" || providerId === "ollama")) {
|
|
115
|
+
baseURL = ensureV1(baseURL);
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
providerId,
|
|
119
|
+
source,
|
|
120
|
+
...apiKey !== void 0 ? { apiKey } : {},
|
|
121
|
+
...baseURL !== void 0 ? { baseURL } : {}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export {
|
|
126
|
+
setStoredCredential,
|
|
127
|
+
removeStoredCredential,
|
|
128
|
+
ensureV1,
|
|
129
|
+
resolveCredentials
|
|
130
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/version.ts
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
function cliVersion() {
|
|
6
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
for (let i = 0; i < 4; i++) {
|
|
8
|
+
try {
|
|
9
|
+
const pkg = JSON.parse(
|
|
10
|
+
readFileSync(join(dir, "package.json"), "utf8")
|
|
11
|
+
);
|
|
12
|
+
if (pkg.name === "prismalens" && pkg.version) return pkg.version;
|
|
13
|
+
} catch {
|
|
14
|
+
}
|
|
15
|
+
dir = dirname(dir);
|
|
16
|
+
}
|
|
17
|
+
return "0.0.0";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
cliVersion
|
|
22
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// src/cli/flags.ts
|
|
2
|
+
import consola from "consola";
|
|
3
|
+
function kebabToCamel(str) {
|
|
4
|
+
return str.replace(/-([a-z])/g, (_, g) => g.toUpperCase());
|
|
5
|
+
}
|
|
6
|
+
function camelToKebab(str) {
|
|
7
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
function assertKnownFlags(args, cmd) {
|
|
10
|
+
const declaredArgs = cmd?.args || {};
|
|
11
|
+
const allowedKeys = /* @__PURE__ */ new Set(["_"]);
|
|
12
|
+
for (const key in declaredArgs) {
|
|
13
|
+
if (Object.hasOwn(declaredArgs, key)) {
|
|
14
|
+
allowedKeys.add(key);
|
|
15
|
+
allowedKeys.add(kebabToCamel(key));
|
|
16
|
+
allowedKeys.add(camelToKebab(key));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
for (const key in args) {
|
|
20
|
+
if (Object.hasOwn(args, key)) {
|
|
21
|
+
if (!allowedKeys.has(key)) {
|
|
22
|
+
consola.error(`Unknown option: --${key}`);
|
|
23
|
+
const rawArgs = process.argv.slice(2);
|
|
24
|
+
const cmdName = `pl${rawArgs[0] && !rawArgs[0].startsWith("-") ? ` ${rawArgs[0]}` : ""}`;
|
|
25
|
+
consola.info(`Run \`${cmdName} --help\` for usage.`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
assertKnownFlags
|
|
34
|
+
};
|