sliccy 0.1.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/LICENSE +191 -0
- package/README.md +512 -0
- package/dist/cli/chrome-launch.d.ts +47 -0
- package/dist/cli/chrome-launch.js +324 -0
- package/dist/cli/cli-log-dedup.d.ts +19 -0
- package/dist/cli/cli-log-dedup.js +62 -0
- package/dist/cli/electron-controller.d.ts +38 -0
- package/dist/cli/electron-controller.js +287 -0
- package/dist/cli/electron-main.d.ts +1 -0
- package/dist/cli/electron-main.js +183 -0
- package/dist/cli/electron-runtime.d.ts +58 -0
- package/dist/cli/electron-runtime.js +133 -0
- package/dist/cli/file-logger.d.ts +38 -0
- package/dist/cli/file-logger.js +207 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +1023 -0
- package/dist/cli/launch-url.d.ts +9 -0
- package/dist/cli/launch-url.js +34 -0
- package/dist/cli/qa-setup.d.ts +1 -0
- package/dist/cli/qa-setup.js +36 -0
- package/dist/cli/release-package.d.ts +17 -0
- package/dist/cli/release-package.js +232 -0
- package/dist/cli/runtime-flags.d.ts +21 -0
- package/dist/cli/runtime-flags.js +154 -0
- package/dist/cli/sync-release-version.d.ts +2 -0
- package/dist/cli/sync-release-version.js +34 -0
- package/dist/tray-url-shared.d.ts +11 -0
- package/dist/tray-url-shared.js +56 -0
- package/dist/ui/assets/___vite-browser-external_commonjs-proxy-7ULRRj69.js +1 -0
- package/dist/ui/assets/__vite-browser-external-D7Ct-6yo.js +1 -0
- package/dist/ui/assets/addon-fit-DOCEibfw.js +12 -0
- package/dist/ui/assets/bsh-watchdog-D19WB0U1.js +2 -0
- package/dist/ui/assets/index-BVQAdk-Y.js +8 -0
- package/dist/ui/assets/index-BjJrFm2K.js +43 -0
- package/dist/ui/assets/index-C1dglHrI.js +3 -0
- package/dist/ui/assets/index-D3Enm5ux.js +13091 -0
- package/dist/ui/assets/index-D7hjyFh1.js +2 -0
- package/dist/ui/assets/index-D8uSC2sl.js +45 -0
- package/dist/ui/assets/index-DEglHp2j.js +1 -0
- package/dist/ui/assets/index-DvjzakYY.js +14406 -0
- package/dist/ui/assets/index-deZeJCgO.js +1 -0
- package/dist/ui/assets/index-mz3VYh0I.js +131 -0
- package/dist/ui/assets/index-r2m8Dpaz.js +54 -0
- package/dist/ui/assets/index-ygVJ3eFG.js +11 -0
- package/dist/ui/assets/lick-manager-proxy-G3WuipZ-.js +1 -0
- package/dist/ui/assets/magic-string.es-BPLJknd7.js +10 -0
- package/dist/ui/assets/oauth-service-DIahkF-o.js +1 -0
- package/dist/ui/assets/offscreen-client-ByVIJGHW.js +1 -0
- package/dist/ui/assets/pdfjs-uyZuKmOq.js +59 -0
- package/dist/ui/assets/pyodide-D73G_Ygx.mjs +4 -0
- package/dist/ui/assets/sql-wasm-BggYNCID.js +2 -0
- package/dist/ui/assets/stream-lEC9OYG2.js +1 -0
- package/dist/ui/assets/xterm-Bb8UKAlD.js +27 -0
- package/dist/ui/assets/xterm-DOrYoP_4.css +32 -0
- package/dist/ui/electron-overlay-entry.js +360 -0
- package/dist/ui/favicon.png +0 -0
- package/dist/ui/fonts/AdobeClean-Bold.otf +0 -0
- package/dist/ui/fonts/AdobeClean-ExtraBold.otf +0 -0
- package/dist/ui/fonts/AdobeClean-Medium.otf +0 -0
- package/dist/ui/fonts/AdobeClean-Regular.otf +0 -0
- package/dist/ui/index.html +1981 -0
- package/dist/ui/preview-sw.js +4 -0
- package/package.json +81 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { LogLevel } from './runtime-flags.js';
|
|
2
|
+
export declare function stripAnsi(str: string): string;
|
|
3
|
+
/** Generates a filename-safe ISO timestamp + PID string. */
|
|
4
|
+
export declare function generateLogFilename(): string;
|
|
5
|
+
/**
|
|
6
|
+
* Deletes log files older than `maxAgeMs` from the given directory.
|
|
7
|
+
* Errors are logged to stderr but never thrown.
|
|
8
|
+
*/
|
|
9
|
+
export declare function cleanupOldLogs(logDir: string, maxAgeMs?: number): void;
|
|
10
|
+
export interface FileLoggerOptions {
|
|
11
|
+
/** The directory for log files. Defaults to `~/.slicc/logs/`. */
|
|
12
|
+
logDir?: string;
|
|
13
|
+
/** Minimum level for log entries. Defaults to `'info'`. */
|
|
14
|
+
logLevel?: LogLevel;
|
|
15
|
+
/** When true, monkey-patches console to tee all output. */
|
|
16
|
+
devMode?: boolean;
|
|
17
|
+
/** Run 7-day cleanup on init. Defaults to true. */
|
|
18
|
+
cleanup?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare class FileLogger {
|
|
21
|
+
readonly logDir: string;
|
|
22
|
+
readonly logFile: string;
|
|
23
|
+
private fd;
|
|
24
|
+
private logLevel;
|
|
25
|
+
private devMode;
|
|
26
|
+
private origConsole;
|
|
27
|
+
constructor(options?: FileLoggerOptions);
|
|
28
|
+
/** Write a structured log entry. Respects log level filtering. */
|
|
29
|
+
log(level: LogLevel, message: string, data?: Record<string, unknown>): void;
|
|
30
|
+
/** Flush and close the log file. Safe to call multiple times. */
|
|
31
|
+
close(): void;
|
|
32
|
+
private installConsoleTee;
|
|
33
|
+
private restoreConsole;
|
|
34
|
+
private writeLine;
|
|
35
|
+
private onExit;
|
|
36
|
+
private registerShutdownHandlers;
|
|
37
|
+
private deregisterShutdownHandlers;
|
|
38
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { mkdirSync, openSync, writeSync, closeSync, readdirSync, statSync, unlinkSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// ANSI stripping
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Matches all ANSI escape sequences (CSI sequences, OSC, etc.)
|
|
8
|
+
// eslint-disable-next-line no-control-regex
|
|
9
|
+
const ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]|\x1b\].*?\x07|\x1b[^[].?/g;
|
|
10
|
+
export function stripAnsi(str) {
|
|
11
|
+
return str.replace(ANSI_RE, '');
|
|
12
|
+
}
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Log level helpers
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
const LEVEL_PRIORITY = {
|
|
17
|
+
debug: 0,
|
|
18
|
+
info: 1,
|
|
19
|
+
warn: 2,
|
|
20
|
+
error: 3,
|
|
21
|
+
};
|
|
22
|
+
function shouldLog(messageLevel, currentLevel) {
|
|
23
|
+
return LEVEL_PRIORITY[messageLevel] >= LEVEL_PRIORITY[currentLevel];
|
|
24
|
+
}
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Timestamp formatting
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
function timestamp() {
|
|
29
|
+
return new Date().toISOString();
|
|
30
|
+
}
|
|
31
|
+
/** Safely stringify a value, handling circular refs, BigInt, and errors. */
|
|
32
|
+
function safeStringify(value) {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.stringify(value, (_, v) => {
|
|
35
|
+
if (typeof v === 'bigint')
|
|
36
|
+
return v.toString() + 'n';
|
|
37
|
+
if (v instanceof Error)
|
|
38
|
+
return { name: v.name, message: v.message, stack: v.stack };
|
|
39
|
+
return v;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return String(value);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Generates a filename-safe ISO timestamp + PID string. */
|
|
47
|
+
export function generateLogFilename() {
|
|
48
|
+
const ts = new Date().toISOString().replace(/:/g, '-').replace(/\.\d{3}Z$/, '');
|
|
49
|
+
return `${ts}_${process.pid}.log`;
|
|
50
|
+
}
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Log cleanup — 7-day retention
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
|
|
55
|
+
/**
|
|
56
|
+
* Deletes log files older than `maxAgeMs` from the given directory.
|
|
57
|
+
* Errors are logged to stderr but never thrown.
|
|
58
|
+
*/
|
|
59
|
+
export function cleanupOldLogs(logDir, maxAgeMs = SEVEN_DAYS_MS) {
|
|
60
|
+
try {
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
const entries = readdirSync(logDir);
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
if (!entry.endsWith('.log'))
|
|
65
|
+
continue;
|
|
66
|
+
try {
|
|
67
|
+
const filePath = join(logDir, entry);
|
|
68
|
+
const stat = statSync(filePath);
|
|
69
|
+
if (now - stat.mtimeMs > maxAgeMs) {
|
|
70
|
+
unlinkSync(filePath);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
// Per-file errors are non-fatal
|
|
75
|
+
console.error(`[file-logger] Failed to remove old log ${entry}:`, err);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
// Directory read error is non-fatal
|
|
81
|
+
console.error('[file-logger] Failed to scan logs directory for cleanup:', err);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export class FileLogger {
|
|
85
|
+
logDir;
|
|
86
|
+
logFile;
|
|
87
|
+
fd = null;
|
|
88
|
+
logLevel;
|
|
89
|
+
devMode;
|
|
90
|
+
origConsole = null;
|
|
91
|
+
constructor(options = {}) {
|
|
92
|
+
this.logDir = options.logDir ?? join(homedir(), '.slicc', 'logs');
|
|
93
|
+
this.logLevel = options.logLevel ?? 'info';
|
|
94
|
+
this.devMode = options.devMode ?? false;
|
|
95
|
+
this.logFile = '';
|
|
96
|
+
try {
|
|
97
|
+
// Ensure log directory exists with restrictive permissions (0o700)
|
|
98
|
+
mkdirSync(this.logDir, { recursive: true, mode: 0o700 });
|
|
99
|
+
// Run cleanup before creating the new log file
|
|
100
|
+
if (options.cleanup !== false) {
|
|
101
|
+
cleanupOldLogs(this.logDir);
|
|
102
|
+
}
|
|
103
|
+
// Open log file synchronously with restrictive permissions (0o600)
|
|
104
|
+
const filename = generateLogFilename();
|
|
105
|
+
this.logFile = join(this.logDir, filename);
|
|
106
|
+
this.fd = openSync(this.logFile, 'a', 0o600);
|
|
107
|
+
// Write header
|
|
108
|
+
this.writeLine(`--- SLICC CLI log started at ${timestamp()} (PID ${process.pid}) ---`);
|
|
109
|
+
// In dev mode, monkey-patch console to tee all output
|
|
110
|
+
if (this.devMode) {
|
|
111
|
+
this.installConsoleTee();
|
|
112
|
+
}
|
|
113
|
+
// Register shutdown handlers
|
|
114
|
+
this.registerShutdownHandlers();
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
// Logging is auxiliary — don't crash the CLI if file logging fails
|
|
118
|
+
console.error('[file-logger] Failed to initialize file logging:', err instanceof Error ? err.message : String(err));
|
|
119
|
+
console.error('[file-logger] File logging disabled for this session.');
|
|
120
|
+
this.fd = null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// -------------------------------------------------------------------------
|
|
124
|
+
// Public API — structured logging for production mode
|
|
125
|
+
// -------------------------------------------------------------------------
|
|
126
|
+
/** Write a structured log entry. Respects log level filtering. */
|
|
127
|
+
log(level, message, data) {
|
|
128
|
+
if (!shouldLog(level, this.logLevel))
|
|
129
|
+
return;
|
|
130
|
+
const entry = data
|
|
131
|
+
? `${timestamp()} [${level.toUpperCase()}] ${message} ${safeStringify(data)}`
|
|
132
|
+
: `${timestamp()} [${level.toUpperCase()}] ${message}`;
|
|
133
|
+
this.writeLine(entry);
|
|
134
|
+
}
|
|
135
|
+
/** Flush and close the log file. Safe to call multiple times. */
|
|
136
|
+
close() {
|
|
137
|
+
this.deregisterShutdownHandlers();
|
|
138
|
+
if (this.fd === null)
|
|
139
|
+
return;
|
|
140
|
+
this.writeLine(`--- SLICC CLI log ended at ${timestamp()} ---`);
|
|
141
|
+
try {
|
|
142
|
+
closeSync(this.fd);
|
|
143
|
+
}
|
|
144
|
+
catch { /* already closed */ }
|
|
145
|
+
this.fd = null;
|
|
146
|
+
this.restoreConsole();
|
|
147
|
+
}
|
|
148
|
+
// -------------------------------------------------------------------------
|
|
149
|
+
// Dev mode — console tee
|
|
150
|
+
// -------------------------------------------------------------------------
|
|
151
|
+
installConsoleTee() {
|
|
152
|
+
this.origConsole = {
|
|
153
|
+
log: console.log,
|
|
154
|
+
info: console.info,
|
|
155
|
+
warn: console.warn,
|
|
156
|
+
error: console.error,
|
|
157
|
+
debug: console.debug,
|
|
158
|
+
};
|
|
159
|
+
const self = this;
|
|
160
|
+
for (const method of ['log', 'info', 'warn', 'error', 'debug']) {
|
|
161
|
+
const original = this.origConsole[method];
|
|
162
|
+
const level = method === 'log' ? 'info' : method;
|
|
163
|
+
console[method] = function (...args) {
|
|
164
|
+
// Always call original — no change to stdout/stderr behavior
|
|
165
|
+
original.apply(console, args);
|
|
166
|
+
// Tee to file if level passes
|
|
167
|
+
if (shouldLog(level, self.logLevel)) {
|
|
168
|
+
const text = args.map((a) => (typeof a === 'string' ? a : safeStringify(a))).join(' ');
|
|
169
|
+
// writeLine already strips ANSI, so no need to call stripAnsi here
|
|
170
|
+
self.writeLine(`${timestamp()} [${level.toUpperCase()}] ${text}`);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
restoreConsole() {
|
|
176
|
+
if (!this.origConsole)
|
|
177
|
+
return;
|
|
178
|
+
console.log = this.origConsole.log;
|
|
179
|
+
console.info = this.origConsole.info;
|
|
180
|
+
console.warn = this.origConsole.warn;
|
|
181
|
+
console.error = this.origConsole.error;
|
|
182
|
+
console.debug = this.origConsole.debug;
|
|
183
|
+
this.origConsole = null;
|
|
184
|
+
}
|
|
185
|
+
// -------------------------------------------------------------------------
|
|
186
|
+
// Internal
|
|
187
|
+
// -------------------------------------------------------------------------
|
|
188
|
+
writeLine(line) {
|
|
189
|
+
if (this.fd === null)
|
|
190
|
+
return;
|
|
191
|
+
try {
|
|
192
|
+
writeSync(this.fd, stripAnsi(line) + '\n');
|
|
193
|
+
}
|
|
194
|
+
catch { /* fd may be invalid */ }
|
|
195
|
+
}
|
|
196
|
+
onExit = () => { this.close(); };
|
|
197
|
+
registerShutdownHandlers() {
|
|
198
|
+
process.once('SIGINT', this.onExit);
|
|
199
|
+
process.once('SIGTERM', this.onExit);
|
|
200
|
+
process.once('exit', this.onExit);
|
|
201
|
+
}
|
|
202
|
+
deregisterShutdownHandlers() {
|
|
203
|
+
process.removeListener('SIGINT', this.onExit);
|
|
204
|
+
process.removeListener('SIGTERM', this.onExit);
|
|
205
|
+
process.removeListener('exit', this.onExit);
|
|
206
|
+
}
|
|
207
|
+
}
|