@bike4mind/cli 0.2.64-chore-7507-migrate-tsdown.21818 → 0.2.64-feat-devops-test.21818
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/bin/bike4mind-cli.mjs +6 -6
- package/dist/BubblewrapRuntime-PMIOLWKR.js +71 -0
- package/dist/HydrationEngine-YL2HWJ3V.js +9 -0
- package/dist/ImageStore-MMUOUPI2.js +224 -0
- package/dist/ProxyManager-HEB4TLVX.js +7 -0
- package/dist/SandboxOrchestrator-UIJ5GYBB.js +8 -0
- package/dist/SandboxRuntimeAdapter-FQ56MAB2.js +13 -0
- package/dist/SeatbeltRuntime-EE3TTLEP.js +98 -0
- package/dist/StderrViolationParser-7OYPM2DJ.js +59 -0
- package/dist/ViolationLogStore-RIIUVURH.js +104 -0
- package/dist/artifactExtractor-6OXIOZ3O.js +180 -0
- package/dist/bashExecute-GLGLD3JD.js +379 -0
- package/dist/chunk-4BIBE3J7.js +48 -0
- package/dist/chunk-4J5CQ4PQ.js +161 -0
- package/dist/chunk-6E3IOCFO.js +19124 -0
- package/dist/chunk-BDB5V67S.js +13770 -0
- package/dist/chunk-BDQBOLYG.js +120 -0
- package/dist/chunk-BPFEGDC7.js +192 -0
- package/dist/chunk-DRWA27ZA.js +12527 -0
- package/dist/chunk-G4ZGEQFT.js +250 -0
- package/dist/chunk-G7TAXSRL.js +1079 -0
- package/dist/chunk-GQGOWACU.js +770 -0
- package/dist/chunk-KQAMBXAW.js +163 -0
- package/dist/chunk-LTLJRF6I.js +44 -0
- package/dist/chunk-PFBYGCOW.js +449 -0
- package/dist/chunk-QWB6ZYY4.js +48 -0
- package/dist/chunk-SEDFA7B5.js +245 -0
- package/dist/chunk-TPAWLBQK.js +241 -0
- package/dist/chunk-Y4WOJJM3.js +147 -0
- package/dist/chunk-Y75ZCVMJ.js +95 -0
- package/dist/commands/doctorCommand.js +87 -0
- package/dist/commands/headlessCommand.js +380 -0
- package/dist/commands/mcpCommand.js +203 -0
- package/dist/commands/updateCommand.js +42 -0
- package/dist/create-67Z4GVO5.js +12 -0
- package/dist/createFile-6PSPLW6R.js +71 -0
- package/dist/deleteFile-AUSRLWIK.js +73 -0
- package/dist/formatConverter-5QEJDW24.js +7 -0
- package/dist/globFiles-TSRN64N2.js +120 -0
- package/dist/grepSearch-634XWZOJ.js +216 -0
- package/dist/index.js +6779 -0
- package/dist/llmMarkdownGenerator-3LVFFTCL.js +371 -0
- package/dist/markdownGenerator-O7UC4CQN.js +269 -0
- package/dist/mementoService-EH56IOHO.js +12 -0
- package/dist/notificationDeduplicator-HUC53NEW.js +9 -0
- package/dist/src-7U44VNJN.js +1121 -0
- package/dist/src-MCNGHRZA.js +319 -0
- package/dist/store-CAB6BV3P.js +11 -0
- package/dist/subtractCredits-VP7XB5YE.js +12 -0
- package/dist/terminalSetup-C5FHMLC3.js +214 -0
- package/dist/treeSitterEngine-4SGFQDY3.js +330 -0
- package/dist/types-KB5NP6T4.js +7 -0
- package/dist/utils-JCHWDM4Z.js +31 -0
- package/package.json +10 -10
- package/dist/BubblewrapRuntime-BHbtqvLx.mjs +0 -72
- package/dist/ConfigStore-CllM6jOf.mjs +0 -8614
- package/dist/ImageStore-DaKT_Ew8.mjs +0 -202
- package/dist/ProxyManager-Dl2nFk-A.mjs +0 -259
- package/dist/ProxyManager-kiOD1X8-.mjs +0 -3
- package/dist/SandboxOrchestrator-BEW3rqYi.mjs +0 -159
- package/dist/SandboxOrchestrator-CHZgSR3P.mjs +0 -3
- package/dist/SandboxRuntimeAdapter-C1B4t20N.mjs +0 -57
- package/dist/SandboxRuntimeAdapter-D7UAG13n.mjs +0 -3
- package/dist/SeatbeltRuntime-D4m0VOcD.mjs +0 -116
- package/dist/StderrViolationParser-D0afQ3-1.mjs +0 -70
- package/dist/ViolationLogStore-CZl35HcA.mjs +0 -96
- package/dist/bashExecute-BTkdqlSs-5foM20Lb.mjs +0 -466
- package/dist/commands/doctorCommand.mjs +0 -101
- package/dist/commands/headlessCommand.mjs +0 -319
- package/dist/commands/mcpCommand.mjs +0 -218
- package/dist/commands/updateCommand.mjs +0 -40
- package/dist/createFile-yQfh8uvk-I-yM5DxC.mjs +0 -63
- package/dist/deleteFile-DKHfnyny-G3b1Kj2T.mjs +0 -66
- package/dist/globFiles-D1en6joM-8jekiXdX.mjs +0 -100
- package/dist/grepSearch-aMamoBn_-DCJcY8JS.mjs +0 -173
- package/dist/index.mjs +0 -6722
- package/dist/pathValidation-Cgjh5WQO-DiCZTcq6.mjs +0 -63
- package/dist/store-Dw1nZX2Y.mjs +0 -128
- package/dist/store-nZExNOWX.mjs +0 -3
- package/dist/terminalSetup-rmr1P8KF.mjs +0 -254
- package/dist/tools-C6M5aW8W.mjs +0 -20907
- package/dist/treeSitterEngine-DCSXcm_3.mjs +0 -309
- package/dist/types-DBEjF9YS.mjs +0 -59
- package/dist/types-DK3P88Px.mjs +0 -3
- package/dist/updateChecker-CKjU5eyc.mjs +0 -120
package/bin/bike4mind-cli.mjs
CHANGED
|
@@ -149,7 +149,7 @@ if (argv['ollama-host']) {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
// Auto-detect environment: prefer production mode when dist exists
|
|
152
|
-
const distPath = join(__dirname, '../dist/index.
|
|
152
|
+
const distPath = join(__dirname, '../dist/index.js');
|
|
153
153
|
const srcPath = join(__dirname, '../src/index.tsx');
|
|
154
154
|
const hasSource = existsSync(srcPath);
|
|
155
155
|
const hasDist = existsSync(distPath);
|
|
@@ -176,7 +176,7 @@ if (argv.prompt !== undefined) {
|
|
|
176
176
|
const module = await import('../src/commands/headlessCommand.ts');
|
|
177
177
|
handleHeadlessCommand = module.handleHeadlessCommand;
|
|
178
178
|
} else {
|
|
179
|
-
const module = await import('../dist/commands/headlessCommand.
|
|
179
|
+
const module = await import('../dist/commands/headlessCommand.js');
|
|
180
180
|
handleHeadlessCommand = module.handleHeadlessCommand;
|
|
181
181
|
}
|
|
182
182
|
|
|
@@ -211,7 +211,7 @@ if (argv._[0] === 'mcp') {
|
|
|
211
211
|
handleMcpCommand = module.handleMcpCommand;
|
|
212
212
|
} else {
|
|
213
213
|
// Production: import compiled JavaScript
|
|
214
|
-
const module = await import('../dist/commands/mcpCommand.
|
|
214
|
+
const module = await import('../dist/commands/mcpCommand.js');
|
|
215
215
|
handleMcpCommand = module.handleMcpCommand;
|
|
216
216
|
}
|
|
217
217
|
|
|
@@ -234,7 +234,7 @@ if (argv._[0] === 'update') {
|
|
|
234
234
|
const module = await import('../src/commands/updateCommand.ts');
|
|
235
235
|
handleUpdateCommand = module.handleUpdateCommand;
|
|
236
236
|
} else {
|
|
237
|
-
const module = await import('../dist/commands/updateCommand.
|
|
237
|
+
const module = await import('../dist/commands/updateCommand.js');
|
|
238
238
|
handleUpdateCommand = module.handleUpdateCommand;
|
|
239
239
|
}
|
|
240
240
|
|
|
@@ -257,7 +257,7 @@ if (argv._[0] === 'doctor') {
|
|
|
257
257
|
const module = await import('../src/commands/doctorCommand.ts');
|
|
258
258
|
handleDoctorCommand = module.handleDoctorCommand;
|
|
259
259
|
} else {
|
|
260
|
-
const module = await import('../dist/commands/doctorCommand.
|
|
260
|
+
const module = await import('../dist/commands/doctorCommand.js');
|
|
261
261
|
handleDoctorCommand = module.handleDoctorCommand;
|
|
262
262
|
}
|
|
263
263
|
|
|
@@ -287,7 +287,7 @@ if (isDev) {
|
|
|
287
287
|
} else {
|
|
288
288
|
// Production: run compiled JavaScript
|
|
289
289
|
try {
|
|
290
|
-
await import(join(__dirname, '../dist/index.
|
|
290
|
+
await import(join(__dirname, '../dist/index.js'));
|
|
291
291
|
} catch (error) {
|
|
292
292
|
console.error('Failed to start CLI:', error);
|
|
293
293
|
console.error('\nTry running: pnpm build');
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
expandPath,
|
|
4
|
+
isBinaryAvailable
|
|
5
|
+
} from "./chunk-QWB6ZYY4.js";
|
|
6
|
+
|
|
7
|
+
// src/sandbox/runtime/BubblewrapRuntime.ts
|
|
8
|
+
import os from "os";
|
|
9
|
+
var SYSTEM_RO_BINDS = ["/usr", "/bin", "/lib", "/lib64", "/sbin", "/etc"];
|
|
10
|
+
var SPECIAL_MOUNTS = {
|
|
11
|
+
dev: "/dev",
|
|
12
|
+
proc: "/proc",
|
|
13
|
+
tmp: "/tmp"
|
|
14
|
+
};
|
|
15
|
+
var BubblewrapRuntime = class {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.platform = "linux";
|
|
18
|
+
this.name = "bubblewrap";
|
|
19
|
+
}
|
|
20
|
+
isAvailable() {
|
|
21
|
+
return isBinaryAvailable("bwrap");
|
|
22
|
+
}
|
|
23
|
+
wrapCommand(options) {
|
|
24
|
+
const { command, cwd, filesystemConfig, env } = options;
|
|
25
|
+
const expandedDenied = filesystemConfig.deniedPaths.map(expandPath);
|
|
26
|
+
const expandedAllowed = filesystemConfig.allowedReadPaths.map(expandPath);
|
|
27
|
+
const args = [];
|
|
28
|
+
for (const sysPath of SYSTEM_RO_BINDS) {
|
|
29
|
+
args.push("--ro-bind-try", sysPath, sysPath);
|
|
30
|
+
}
|
|
31
|
+
args.push("--dev", SPECIAL_MOUNTS.dev);
|
|
32
|
+
args.push("--proc", SPECIAL_MOUNTS.proc);
|
|
33
|
+
args.push("--tmpfs", SPECIAL_MOUNTS.tmp);
|
|
34
|
+
args.push("--bind", cwd, cwd);
|
|
35
|
+
const homeDir = os.homedir();
|
|
36
|
+
args.push("--ro-bind", homeDir, homeDir);
|
|
37
|
+
for (const allowedPath of expandedAllowed) {
|
|
38
|
+
if (!allowedPath.startsWith(homeDir)) {
|
|
39
|
+
args.push("--ro-bind-try", allowedPath, allowedPath);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const deniedPath of expandedDenied) {
|
|
43
|
+
args.push("--tmpfs", deniedPath);
|
|
44
|
+
}
|
|
45
|
+
args.push("--unshare-all");
|
|
46
|
+
args.push("--share-net");
|
|
47
|
+
args.push("--die-with-parent");
|
|
48
|
+
if (options.seccompProfile) {
|
|
49
|
+
args.push("--seccomp", options.seccompProfile);
|
|
50
|
+
}
|
|
51
|
+
for (const [key, value] of Object.entries(env ?? {})) {
|
|
52
|
+
args.push("--setenv", key, value);
|
|
53
|
+
}
|
|
54
|
+
args.push("--chdir", cwd);
|
|
55
|
+
args.push("bash", "-c", command);
|
|
56
|
+
const commandString = ["bwrap", ...args.map(shellEscape)].join(" ");
|
|
57
|
+
return {
|
|
58
|
+
executable: "bwrap",
|
|
59
|
+
args,
|
|
60
|
+
env: env ?? {},
|
|
61
|
+
commandString
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
function shellEscape(str) {
|
|
66
|
+
if (/^[a-zA-Z0-9._/=:-]+$/.test(str)) return str;
|
|
67
|
+
return `'${str.replace(/'/g, "'\\''")}'`;
|
|
68
|
+
}
|
|
69
|
+
export {
|
|
70
|
+
BubblewrapRuntime
|
|
71
|
+
};
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/storage/ImageStore.ts
|
|
4
|
+
import { createHash } from "crypto";
|
|
5
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync, unlinkSync } from "fs";
|
|
6
|
+
import { join, extname, resolve } from "path";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import Database from "better-sqlite3";
|
|
9
|
+
import sharp from "sharp";
|
|
10
|
+
var ImageStore = class _ImageStore {
|
|
11
|
+
static {
|
|
12
|
+
this.BASE_DIR = join(homedir(), ".bike4mind", "cli");
|
|
13
|
+
}
|
|
14
|
+
static {
|
|
15
|
+
this.IMAGES_DIR = join(_ImageStore.BASE_DIR, "images");
|
|
16
|
+
}
|
|
17
|
+
static {
|
|
18
|
+
this.DB_PATH = join(_ImageStore.BASE_DIR, "images.db");
|
|
19
|
+
}
|
|
20
|
+
static {
|
|
21
|
+
this.RETENTION_DAYS = 7;
|
|
22
|
+
}
|
|
23
|
+
static {
|
|
24
|
+
// Target max size before base64 encoding. Base64 adds ~33% overhead.
|
|
25
|
+
// To stay under 1MB after base64, we need images < 750KB
|
|
26
|
+
this.MAX_RAW_SIZE = 750 * 1024;
|
|
27
|
+
}
|
|
28
|
+
static {
|
|
29
|
+
// 750KB
|
|
30
|
+
// Maximum size before compression to prevent OOM with very large images
|
|
31
|
+
this.MAX_PRECOMPRESS_SIZE = 50 * 1024 * 1024;
|
|
32
|
+
}
|
|
33
|
+
constructor() {
|
|
34
|
+
this.ensureDirectories();
|
|
35
|
+
this.db = new Database(_ImageStore.DB_PATH);
|
|
36
|
+
this.initDatabase();
|
|
37
|
+
try {
|
|
38
|
+
this.cleanupOldImages();
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.warn("Failed to cleanup old images:", err);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
ensureDirectories() {
|
|
44
|
+
mkdirSync(_ImageStore.BASE_DIR, { recursive: true });
|
|
45
|
+
mkdirSync(_ImageStore.IMAGES_DIR, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
initDatabase() {
|
|
48
|
+
this.db.exec(`
|
|
49
|
+
CREATE TABLE IF NOT EXISTS images (
|
|
50
|
+
hash TEXT PRIMARY KEY,
|
|
51
|
+
format TEXT NOT NULL,
|
|
52
|
+
size INTEGER NOT NULL,
|
|
53
|
+
timestamp INTEGER NOT NULL,
|
|
54
|
+
uploaded INTEGER NOT NULL DEFAULT 0,
|
|
55
|
+
s3_url TEXT,
|
|
56
|
+
original_filename TEXT
|
|
57
|
+
)
|
|
58
|
+
`);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Store an image locally with content-based hashing for deduplication
|
|
62
|
+
*/
|
|
63
|
+
async store(imageData, originalFilename) {
|
|
64
|
+
const { buffer: processedData, format: processedFormat } = await this.processImage(imageData, originalFilename);
|
|
65
|
+
const hash = this.generateHash(processedData);
|
|
66
|
+
const format = processedFormat;
|
|
67
|
+
const ext = format.toLowerCase();
|
|
68
|
+
const localPath = join(_ImageStore.IMAGES_DIR, `${hash}.${ext}`);
|
|
69
|
+
const existing = this.getMetadata(hash);
|
|
70
|
+
if (existing) {
|
|
71
|
+
return {
|
|
72
|
+
hash,
|
|
73
|
+
placeholder: "",
|
|
74
|
+
// Will be set by caller
|
|
75
|
+
localPath,
|
|
76
|
+
metadata: existing
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
writeFileSync(localPath, processedData);
|
|
80
|
+
const metadata = {
|
|
81
|
+
hash,
|
|
82
|
+
format,
|
|
83
|
+
size: processedData.length,
|
|
84
|
+
timestamp: Date.now(),
|
|
85
|
+
uploaded: false,
|
|
86
|
+
originalFilename
|
|
87
|
+
};
|
|
88
|
+
this.db.prepare(
|
|
89
|
+
`INSERT INTO images (hash, format, size, timestamp, uploaded, original_filename)
|
|
90
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
91
|
+
).run(hash, format, metadata.size, metadata.timestamp, 0, originalFilename || null);
|
|
92
|
+
return {
|
|
93
|
+
hash,
|
|
94
|
+
placeholder: "",
|
|
95
|
+
localPath,
|
|
96
|
+
metadata
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get image metadata by hash
|
|
101
|
+
*/
|
|
102
|
+
getMetadata(hash) {
|
|
103
|
+
const row = this.db.prepare("SELECT * FROM images WHERE hash = ?").get(hash);
|
|
104
|
+
if (!row) return null;
|
|
105
|
+
return {
|
|
106
|
+
hash: row.hash,
|
|
107
|
+
format: row.format,
|
|
108
|
+
size: row.size,
|
|
109
|
+
timestamp: row.timestamp,
|
|
110
|
+
uploaded: Boolean(row.uploaded),
|
|
111
|
+
s3Url: row.s3_url,
|
|
112
|
+
originalFilename: row.original_filename
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Read image data from local cache
|
|
117
|
+
*/
|
|
118
|
+
readImage(hash) {
|
|
119
|
+
const metadata = this.getMetadata(hash);
|
|
120
|
+
if (!metadata) return null;
|
|
121
|
+
const localPath = join(_ImageStore.IMAGES_DIR, `${hash}.${metadata.format.toLowerCase()}`);
|
|
122
|
+
const normalizedPath = resolve(localPath);
|
|
123
|
+
if (!normalizedPath.startsWith(resolve(_ImageStore.IMAGES_DIR))) {
|
|
124
|
+
throw new Error("Invalid image path");
|
|
125
|
+
}
|
|
126
|
+
if (!existsSync(localPath)) return null;
|
|
127
|
+
return readFileSync(localPath);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Generate content hash for deduplication
|
|
131
|
+
*/
|
|
132
|
+
generateHash(data) {
|
|
133
|
+
return createHash("sha256").update(data).digest("hex").substring(0, 16);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Process image: compress if needed to stay under size limit
|
|
137
|
+
*/
|
|
138
|
+
async processImage(data, originalFilename) {
|
|
139
|
+
if (data.length > _ImageStore.MAX_PRECOMPRESS_SIZE) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Image too large to process (${Math.round(data.length / 1024 / 1024)}MB). Maximum size is ${_ImageStore.MAX_PRECOMPRESS_SIZE / 1024 / 1024}MB.`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
const originalFormat = this.detectFormat(data, originalFilename);
|
|
145
|
+
if (data.length <= _ImageStore.MAX_RAW_SIZE) {
|
|
146
|
+
return { buffer: data, format: originalFormat };
|
|
147
|
+
}
|
|
148
|
+
let image = sharp(data);
|
|
149
|
+
const metadata = await image.metadata();
|
|
150
|
+
if (!metadata.width || !metadata.height) {
|
|
151
|
+
throw new Error("Unable to read image dimensions");
|
|
152
|
+
}
|
|
153
|
+
const maxDimension = 2048;
|
|
154
|
+
let width = metadata.width;
|
|
155
|
+
let height = metadata.height;
|
|
156
|
+
if (width > maxDimension || height > maxDimension) {
|
|
157
|
+
if (width > height) {
|
|
158
|
+
height = Math.round(height * maxDimension / width);
|
|
159
|
+
width = maxDimension;
|
|
160
|
+
} else {
|
|
161
|
+
width = Math.round(width * maxDimension / height);
|
|
162
|
+
height = maxDimension;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
image = image.resize(width, height);
|
|
166
|
+
let quality = 85;
|
|
167
|
+
let buffer = await image.jpeg({ quality }).toBuffer();
|
|
168
|
+
while (buffer.length > _ImageStore.MAX_RAW_SIZE && quality > 30) {
|
|
169
|
+
quality -= 10;
|
|
170
|
+
buffer = await image.jpeg({ quality }).toBuffer();
|
|
171
|
+
}
|
|
172
|
+
if (buffer.length > _ImageStore.MAX_RAW_SIZE) {
|
|
173
|
+
throw new Error(`Unable to compress image below ${_ImageStore.MAX_RAW_SIZE / 1024}KB limit`);
|
|
174
|
+
}
|
|
175
|
+
return { buffer, format: "jpg" };
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Detect image format from buffer or filename
|
|
179
|
+
*/
|
|
180
|
+
detectFormat(data, filename) {
|
|
181
|
+
if (data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71) {
|
|
182
|
+
return "png";
|
|
183
|
+
}
|
|
184
|
+
if (data[0] === 255 && data[1] === 216 && data[2] === 255) {
|
|
185
|
+
return "jpg";
|
|
186
|
+
}
|
|
187
|
+
if (data[0] === 71 && data[1] === 73 && data[2] === 70) {
|
|
188
|
+
return "gif";
|
|
189
|
+
}
|
|
190
|
+
if (data[0] === 82 && data[1] === 73 && data[2] === 70 && data[3] === 70) {
|
|
191
|
+
return "webp";
|
|
192
|
+
}
|
|
193
|
+
if (filename) {
|
|
194
|
+
const ext = extname(filename).substring(1).toLowerCase();
|
|
195
|
+
if (["png", "jpg", "jpeg", "gif", "webp"].includes(ext)) {
|
|
196
|
+
return ext === "jpeg" ? "jpg" : ext;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return "png";
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Clean up images older than retention period
|
|
203
|
+
*/
|
|
204
|
+
cleanupOldImages() {
|
|
205
|
+
const cutoffTime = Date.now() - _ImageStore.RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
206
|
+
const oldImages = this.db.prepare("SELECT hash, format FROM images WHERE timestamp < ?").all(cutoffTime);
|
|
207
|
+
for (const { hash, format } of oldImages) {
|
|
208
|
+
const localPath = join(_ImageStore.IMAGES_DIR, `${hash}.${format.toLowerCase()}`);
|
|
209
|
+
if (existsSync(localPath)) {
|
|
210
|
+
unlinkSync(localPath);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
this.db.prepare("DELETE FROM images WHERE timestamp < ?").run(cutoffTime);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Close database connection
|
|
217
|
+
*/
|
|
218
|
+
close() {
|
|
219
|
+
this.db.close();
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
export {
|
|
223
|
+
ImageStore
|
|
224
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
expandPath,
|
|
4
|
+
isBinaryAvailable
|
|
5
|
+
} from "./chunk-QWB6ZYY4.js";
|
|
6
|
+
|
|
7
|
+
// src/sandbox/runtime/SeatbeltRuntime.ts
|
|
8
|
+
import { writeFileSync, mkdtempSync } from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import os from "os";
|
|
11
|
+
function escapeSeatbeltPath(p) {
|
|
12
|
+
return p.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
13
|
+
}
|
|
14
|
+
var SeatbeltRuntime = class {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.platform = "darwin";
|
|
17
|
+
this.name = "seatbelt";
|
|
18
|
+
}
|
|
19
|
+
isAvailable() {
|
|
20
|
+
return isBinaryAvailable("sandbox-exec");
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Generate a Seatbelt profile string from the filesystem config.
|
|
24
|
+
*
|
|
25
|
+
* Strategy:
|
|
26
|
+
* - Start with (allow default) to permit most operations
|
|
27
|
+
* - Deny all file writes globally
|
|
28
|
+
* - Allow file writes only to the working directory
|
|
29
|
+
* - Deny all access to explicitly denied paths
|
|
30
|
+
* - Allow read access to explicitly allowed paths
|
|
31
|
+
*/
|
|
32
|
+
generateProfile(options) {
|
|
33
|
+
const { cwd, filesystemConfig } = options;
|
|
34
|
+
const expandedDenied = filesystemConfig.deniedPaths.map(expandPath);
|
|
35
|
+
const expandedAllowed = filesystemConfig.allowedReadPaths.map(expandPath);
|
|
36
|
+
const lines = [
|
|
37
|
+
"(version 1)",
|
|
38
|
+
"",
|
|
39
|
+
"; Start with permissive defaults (process exec, network, sysctl, etc.)",
|
|
40
|
+
"(allow default)",
|
|
41
|
+
""
|
|
42
|
+
];
|
|
43
|
+
if (filesystemConfig.writeOnlyToWorkingDir) {
|
|
44
|
+
lines.push("; Deny all file writes globally");
|
|
45
|
+
lines.push("(deny file-write*)");
|
|
46
|
+
lines.push("");
|
|
47
|
+
lines.push("; Allow writes to working directory");
|
|
48
|
+
lines.push(`(allow file-write* (subpath "${escapeSeatbeltPath(cwd)}"))`);
|
|
49
|
+
lines.push("");
|
|
50
|
+
lines.push("; Allow writes to temp directories");
|
|
51
|
+
lines.push(`(allow file-write* (subpath "/tmp"))`);
|
|
52
|
+
lines.push(`(allow file-write* (subpath "/private/tmp"))`);
|
|
53
|
+
lines.push(`(allow file-write* (subpath "${escapeSeatbeltPath(os.tmpdir())}"))`);
|
|
54
|
+
lines.push("");
|
|
55
|
+
}
|
|
56
|
+
if (expandedDenied.length > 0) {
|
|
57
|
+
lines.push("; Deny access to sensitive paths");
|
|
58
|
+
for (const deniedPath of expandedDenied) {
|
|
59
|
+
lines.push(`(deny file-read* file-write* (subpath "${escapeSeatbeltPath(deniedPath)}"))`);
|
|
60
|
+
}
|
|
61
|
+
lines.push("");
|
|
62
|
+
}
|
|
63
|
+
if (expandedAllowed.length > 0) {
|
|
64
|
+
lines.push("; Explicitly allowed read paths");
|
|
65
|
+
for (const allowedPath of expandedAllowed) {
|
|
66
|
+
lines.push(`(allow file-read* (subpath "${escapeSeatbeltPath(allowedPath)}"))`);
|
|
67
|
+
}
|
|
68
|
+
lines.push("");
|
|
69
|
+
}
|
|
70
|
+
return lines.join("\n");
|
|
71
|
+
}
|
|
72
|
+
wrapCommand(options) {
|
|
73
|
+
try {
|
|
74
|
+
const profile = this.generateProfile(options);
|
|
75
|
+
const tmpDir = mkdtempSync(path.join(os.tmpdir(), "b4m-sandbox-"));
|
|
76
|
+
const profilePath = path.join(tmpDir, "sandbox.sb");
|
|
77
|
+
writeFileSync(profilePath, profile, "utf-8");
|
|
78
|
+
const args = ["-f", profilePath, "bash", "-c", options.command];
|
|
79
|
+
const envEntries = Object.entries(options.env ?? {});
|
|
80
|
+
const envPrefix = envEntries.length > 0 ? envEntries.map(([k, v]) => `${k}=${shellEscape(v)}`).join(" ") + " " : "";
|
|
81
|
+
return {
|
|
82
|
+
executable: "sandbox-exec",
|
|
83
|
+
args,
|
|
84
|
+
env: options.env ?? {},
|
|
85
|
+
commandString: `${envPrefix}sandbox-exec -f ${profilePath} bash -c ${shellEscape(options.command)}`,
|
|
86
|
+
cleanupPaths: [profilePath, tmpDir]
|
|
87
|
+
};
|
|
88
|
+
} catch (err) {
|
|
89
|
+
throw new Error(`Failed to create sandbox profile: ${err instanceof Error ? err.message : String(err)}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
function shellEscape(str) {
|
|
94
|
+
return `'${str.replace(/'/g, "'\\''")}'`;
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
SeatbeltRuntime
|
|
98
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/sandbox/logging/StderrViolationParser.ts
|
|
4
|
+
function parseSeatbeltStderr(stderr) {
|
|
5
|
+
const violations = [];
|
|
6
|
+
const regex = /sandbox-exec:\s*deny\(\d+\)\s+(\S+)\s+(.+)/g;
|
|
7
|
+
let match;
|
|
8
|
+
while ((match = regex.exec(stderr)) !== null) {
|
|
9
|
+
const operation = match[1];
|
|
10
|
+
const targetPath = match[2].trim();
|
|
11
|
+
const type = classifySeatbeltOperation(operation);
|
|
12
|
+
violations.push({
|
|
13
|
+
type,
|
|
14
|
+
operation,
|
|
15
|
+
path: type === "filesystem" ? targetPath : void 0,
|
|
16
|
+
detail: match[0]
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return violations;
|
|
20
|
+
}
|
|
21
|
+
function parseBwrapStderr(stderr) {
|
|
22
|
+
const violations = [];
|
|
23
|
+
const regex = /bwrap:\s+(.+)/g;
|
|
24
|
+
let match;
|
|
25
|
+
while ((match = regex.exec(stderr)) !== null) {
|
|
26
|
+
const message = match[1].trim();
|
|
27
|
+
const pathMatch = message.match(/(?:Can't open file |Can't bind mount )(\S+)/);
|
|
28
|
+
const extractedPath = pathMatch?.[1]?.replace(/:$/, "");
|
|
29
|
+
violations.push({
|
|
30
|
+
type: "filesystem",
|
|
31
|
+
path: extractedPath,
|
|
32
|
+
detail: match[0]
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return violations;
|
|
36
|
+
}
|
|
37
|
+
function parseSandboxStderr(stderr) {
|
|
38
|
+
return [...parseSeatbeltStderr(stderr), ...parseBwrapStderr(stderr)];
|
|
39
|
+
}
|
|
40
|
+
function toSandboxViolations(parsed, command) {
|
|
41
|
+
return parsed.map((p) => ({
|
|
42
|
+
type: p.type,
|
|
43
|
+
path: p.path,
|
|
44
|
+
command,
|
|
45
|
+
blockedBy: "sandbox",
|
|
46
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
47
|
+
detail: p.detail
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
function classifySeatbeltOperation(operation) {
|
|
51
|
+
if (operation.startsWith("network")) return "network";
|
|
52
|
+
return "filesystem";
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
parseBwrapStderr,
|
|
56
|
+
parseSandboxStderr,
|
|
57
|
+
parseSeatbeltStderr,
|
|
58
|
+
toSandboxViolations
|
|
59
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/sandbox/logging/ViolationLogStore.ts
|
|
4
|
+
import { promises as fs } from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
var MAX_VIOLATIONS = 5e3;
|
|
8
|
+
var DEFAULT_PATH = path.join(homedir(), ".bike4mind", "violations.jsonl");
|
|
9
|
+
var ViolationLogStore = class {
|
|
10
|
+
constructor(storePath) {
|
|
11
|
+
this.cache = null;
|
|
12
|
+
this.storePath = storePath ?? DEFAULT_PATH;
|
|
13
|
+
}
|
|
14
|
+
/** Ensure parent directory exists */
|
|
15
|
+
async init() {
|
|
16
|
+
const dir = path.dirname(this.storePath);
|
|
17
|
+
await fs.mkdir(dir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
/** Load all entries from disk (newest first) */
|
|
20
|
+
async load() {
|
|
21
|
+
if (this.cache) {
|
|
22
|
+
return this.cache;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const data = await fs.readFile(this.storePath, "utf-8");
|
|
26
|
+
const lines = data.trim().split("\n").filter((line) => line.length > 0);
|
|
27
|
+
const entries = lines.map((line) => {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(line);
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}).filter((entry) => entry !== null);
|
|
34
|
+
entries.sort((a, b) => b.timestamp - a.timestamp);
|
|
35
|
+
this.cache = entries;
|
|
36
|
+
return this.cache;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error.code === "ENOENT") {
|
|
39
|
+
this.cache = [];
|
|
40
|
+
return this.cache;
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Record a new violation (converts Date → epoch ms, appends JSONL) */
|
|
46
|
+
async record(violation) {
|
|
47
|
+
const entry = {
|
|
48
|
+
type: violation.type,
|
|
49
|
+
command: violation.command,
|
|
50
|
+
blockedBy: violation.blockedBy,
|
|
51
|
+
timestamp: violation.timestamp.getTime(),
|
|
52
|
+
...violation.path && { path: violation.path },
|
|
53
|
+
...violation.domain && { domain: violation.domain },
|
|
54
|
+
...violation.detail && { detail: violation.detail }
|
|
55
|
+
};
|
|
56
|
+
await this.init();
|
|
57
|
+
const line = JSON.stringify(entry) + "\n";
|
|
58
|
+
await fs.appendFile(this.storePath, line, "utf-8");
|
|
59
|
+
this.cache = [entry, ...this.cache ?? []];
|
|
60
|
+
if (this.cache.length > MAX_VIOLATIONS) {
|
|
61
|
+
await this.trim();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Get recent violations (default 50) */
|
|
65
|
+
async getRecent(count = 50) {
|
|
66
|
+
const entries = await this.load();
|
|
67
|
+
return entries.slice(0, count);
|
|
68
|
+
}
|
|
69
|
+
/** Count violations by type */
|
|
70
|
+
async countByType() {
|
|
71
|
+
const entries = await this.load();
|
|
72
|
+
let filesystem = 0;
|
|
73
|
+
let network = 0;
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
if (entry.type === "filesystem") filesystem++;
|
|
76
|
+
else if (entry.type === "network") network++;
|
|
77
|
+
}
|
|
78
|
+
return { filesystem, network };
|
|
79
|
+
}
|
|
80
|
+
/** Clear all violations */
|
|
81
|
+
async clear() {
|
|
82
|
+
try {
|
|
83
|
+
await fs.unlink(this.storePath);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error.code !== "ENOENT") {
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
this.cache = [];
|
|
90
|
+
}
|
|
91
|
+
/** Trim to MAX_VIOLATIONS (rewrite file) */
|
|
92
|
+
async trim() {
|
|
93
|
+
if (!this.cache || this.cache.length <= MAX_VIOLATIONS) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const trimmed = this.cache.slice(0, MAX_VIOLATIONS);
|
|
97
|
+
const lines = [...trimmed].reverse().map((entry) => JSON.stringify(entry)).join("\n");
|
|
98
|
+
await fs.writeFile(this.storePath, lines + "\n", "utf-8");
|
|
99
|
+
this.cache = trimmed;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
export {
|
|
103
|
+
ViolationLogStore
|
|
104
|
+
};
|