@letterblack/lbe-core 1.3.2 → 1.3.4
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 +1 -1
- package/README.md +130 -442
- package/assets/runtime-boundary.svg +36 -36
- package/dist/cli.js +141 -0
- package/dist/index.js +52 -0
- package/{release-exec/dist → dist}/wasm.lock.json +5 -4
- package/package.json +23 -54
- package/types.d.ts +2 -175
- package/.githooks/pre-commit +0 -2
- package/.githooks/pre-push +0 -2
- package/CHANGELOG.md +0 -69
- package/Release-README.md +0 -65
- package/WORKSPACE.md +0 -422
- package/_proof.mjs +0 -246
- package/bin/lbe.js +0 -12
- package/config/identity.config.json +0 -3
- package/config/policy.default.json +0 -24
- package/dist/cli/lbe.js +0 -4274
- package/dist/hooks/register.cjs +0 -505
- package/dist/state/appendCentral.cjs +0 -87
- package/dist/state/index.cjs +0 -101
- package/exec/cli.js +0 -472
- package/exec/index.js +0 -2
- package/index.js +0 -24
- package/lbe.audit.jsonl +0 -46
- package/release/README.md +0 -216
- package/release/TRUST.md +0 -90
- package/release/exec-README.md +0 -215
- package/release/exec-types.d.ts +0 -50
- package/release-exec/LICENSE +0 -1
- package/release-exec/README.md +0 -215
- package/release-exec/assets/lbe-gates.jpg +0 -0
- package/release-exec/assets/lbe-gates.png +0 -0
- package/release-exec/assets/runtime-boundary.svg +0 -36
- package/release-exec/assets/story-allow.jpg +0 -0
- package/release-exec/assets/story-allow.png +0 -0
- package/release-exec/assets/story-deny.jpg +0 -0
- package/release-exec/assets/story-deny.png +0 -0
- package/release-exec/dist/cli.js +0 -2841
- package/release-exec/dist/index.js +0 -1835
- package/release-exec/hooks/register.cjs +0 -473
- package/release-exec/package.json +0 -35
- package/release-exec/types.d.ts +0 -50
- package/runtime/engine.js +0 -322
- package/runtime/lbe_engine.wasm +0 -0
- package/src/cli/commands/auditVerify.js +0 -36
- package/src/cli/commands/dryrun.js +0 -175
- package/src/cli/commands/health.js +0 -153
- package/src/cli/commands/init.js +0 -306
- package/src/cli/commands/integrityCheck.js +0 -57
- package/src/cli/commands/logs.js +0 -53
- package/src/cli/commands/openState.js +0 -44
- package/src/cli/commands/policyAdd.js +0 -8
- package/src/cli/commands/policyMode.js +0 -7
- package/src/cli/commands/policySign.js +0 -72
- package/src/cli/commands/proof.js +0 -122
- package/src/cli/commands/run.js +0 -342
- package/src/cli/commands/status.js +0 -73
- package/src/cli/commands/verify.js +0 -144
- package/src/cli/main.js +0 -176
- package/src/cli/parseArgs.js +0 -114
- package/src/exec/localExecutor.js +0 -289
- package/src/hooks/register.cjs +0 -505
- package/src/state/appendCentral.cjs +0 -87
- package/src/state/fileIndex.js +0 -140
- package/src/state/index.cjs +0 -101
- package/src/state/index.js +0 -65
- package/src/state/intentRegistry.js +0 -83
- package/src/state/migration.js +0 -112
- package/src/state/proofRunner.js +0 -246
- package/src/state/stateRoot.js +0 -40
- package/src/state/targetRegistry.js +0 -108
- package/src/state/workspaceId.js +0 -40
- package/src/state/workspaceRegistry.js +0 -65
- /package/{release-exec/dist → dist}/lbe_engine.wasm +0 -0
package/src/cli/commands/init.js
DELETED
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
// src/cli/commands/init.js
|
|
2
|
-
// Initialize LBE in a workspace:
|
|
3
|
-
// 1. Scan project → generate workspace contract (semantics + enforcement)
|
|
4
|
-
// 2. Show compact summary → ask once
|
|
5
|
-
// 3. Write lbe.workspace.json
|
|
6
|
-
// 4. Set up crypto infrastructure silently
|
|
7
|
-
|
|
8
|
-
import fs from 'fs';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import readline from 'readline';
|
|
11
|
-
import { generateKeyPair } from '../../core/signature.js';
|
|
12
|
-
import { createPolicySignatureEnvelope } from '../../core/policySignature.js';
|
|
13
|
-
import { scanWorkspace, formatSummary } from '../../core/workspaceScanner.js';
|
|
14
|
-
|
|
15
|
-
// ─── Interactive prompt ───────────────────────────────────────────────────────
|
|
16
|
-
|
|
17
|
-
function ask(question) {
|
|
18
|
-
// Non-interactive (CI / pipe) — default accept
|
|
19
|
-
if (!process.stdin.isTTY) return Promise.resolve('y');
|
|
20
|
-
return new Promise(resolve => {
|
|
21
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
22
|
-
rl.question(question, ans => { rl.close(); resolve(ans.trim().toLowerCase()); });
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ─── Strict / relaxed adjustments ────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
function applyStrict(enforcement) {
|
|
29
|
-
// Strict: move approval items to deny, add common risky patterns
|
|
30
|
-
return {
|
|
31
|
-
...enforcement,
|
|
32
|
-
deny: [...new Set([...enforcement.deny, ...enforcement.approval, '*.json', 'config/**'])],
|
|
33
|
-
approval: [],
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function applyRelaxed(enforcement) {
|
|
38
|
-
// Relaxed: drop approval requirement, everything not denied is allowed
|
|
39
|
-
return { ...enforcement, approval: [] };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ─── Crypto setup (silent — infrastructure only) ──────────────────────────────
|
|
43
|
-
|
|
44
|
-
function setupCrypto(cwd) {
|
|
45
|
-
const nowIso = new Date().toISOString();
|
|
46
|
-
const expiresAt = new Date(Date.now() + 180 * 24 * 60 * 60 * 1000).toISOString();
|
|
47
|
-
const defaultKeyId = 'agent:gpt-v1-2026Q1';
|
|
48
|
-
const signerKeyId = 'policy-signer-v1-2026Q1';
|
|
49
|
-
|
|
50
|
-
// All LBE infrastructure lives under .lbe/ — nothing pollutes the project root
|
|
51
|
-
const lbeDir = path.join(cwd, '.lbe');
|
|
52
|
-
for (const d of ['config', 'keys', 'data']) {
|
|
53
|
-
fs.mkdirSync(path.join(lbeDir, d), { recursive: true });
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Data stores
|
|
57
|
-
const dataFiles = {
|
|
58
|
-
'.lbe/data/nonce.db.json': JSON.stringify({ entries: [] }, null, 2),
|
|
59
|
-
'.lbe/data/rate-limit.db.json': JSON.stringify({ entries: [] }, null, 2),
|
|
60
|
-
'.lbe/data/policy.state.json': JSON.stringify({ schemaVersion: '1', lastAccepted: null, updatedAt: null }, null, 2),
|
|
61
|
-
'.lbe/data/audit.log.jsonl': '',
|
|
62
|
-
};
|
|
63
|
-
for (const [rel, content] of Object.entries(dataFiles)) {
|
|
64
|
-
const p = path.join(cwd, rel);
|
|
65
|
-
if (!fs.existsSync(p)) fs.writeFileSync(p, content);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Keypair
|
|
69
|
-
const keyDir = path.join(lbeDir, 'keys');
|
|
70
|
-
const pubPath = path.join(keyDir, 'public.key');
|
|
71
|
-
const secPath = path.join(keyDir, 'secret.key');
|
|
72
|
-
let publicKeyB64, secretKeyB64;
|
|
73
|
-
if (fs.existsSync(pubPath) && fs.existsSync(secPath)) {
|
|
74
|
-
publicKeyB64 = fs.readFileSync(pubPath, 'utf8').trim();
|
|
75
|
-
secretKeyB64 = fs.readFileSync(secPath, 'utf8').trim();
|
|
76
|
-
} else {
|
|
77
|
-
const kp = generateKeyPair();
|
|
78
|
-
publicKeyB64 = kp.publicKey;
|
|
79
|
-
secretKeyB64 = kp.secretKey;
|
|
80
|
-
fs.writeFileSync(pubPath, publicKeyB64);
|
|
81
|
-
fs.writeFileSync(secPath, secretKeyB64, { mode: 0o600 });
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Keys store
|
|
85
|
-
const keysPath = path.join(lbeDir, 'config/keys.json');
|
|
86
|
-
const keysStore = fs.existsSync(keysPath)
|
|
87
|
-
? JSON.parse(fs.readFileSync(keysPath, 'utf8'))
|
|
88
|
-
: { schemaVersion: '1', defaultKeyId, trustedKeys: {} };
|
|
89
|
-
|
|
90
|
-
for (const keyId of [defaultKeyId, signerKeyId]) {
|
|
91
|
-
if (!keysStore.trustedKeys[keyId]) {
|
|
92
|
-
keysStore.trustedKeys[keyId] = {
|
|
93
|
-
publicKey: publicKeyB64,
|
|
94
|
-
notBefore: nowIso,
|
|
95
|
-
expiresAt,
|
|
96
|
-
validFrom: nowIso,
|
|
97
|
-
validUntil: expiresAt,
|
|
98
|
-
deprecated: false,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
keysStore.defaultKeyId = defaultKeyId;
|
|
103
|
-
fs.writeFileSync(keysPath, JSON.stringify(keysStore, null, 2));
|
|
104
|
-
|
|
105
|
-
// Policy
|
|
106
|
-
const policyPath = path.join(lbeDir, 'config/policy.default.json');
|
|
107
|
-
let policyObj;
|
|
108
|
-
if (fs.existsSync(policyPath)) {
|
|
109
|
-
policyObj = JSON.parse(fs.readFileSync(policyPath, 'utf8'));
|
|
110
|
-
} else {
|
|
111
|
-
policyObj = {
|
|
112
|
-
default: 'DENY',
|
|
113
|
-
version: '1.0.0',
|
|
114
|
-
createdAt: nowIso,
|
|
115
|
-
security: {
|
|
116
|
-
maxClockSkewSec: 600,
|
|
117
|
-
maxPolicyCreatedAtSkewSec: 31536000,
|
|
118
|
-
defaultRateLimit: { windowSec: 60, maxRequests: 30 }
|
|
119
|
-
},
|
|
120
|
-
requesters: {
|
|
121
|
-
'agent:gpt': {
|
|
122
|
-
allowAdapters: ['noop', 'shell'],
|
|
123
|
-
allowCommands: ['RUN_SHELL'],
|
|
124
|
-
rateLimit: { windowSec: 60, maxRequests: 30 },
|
|
125
|
-
filesystem: { roots: [cwd], denyPatterns: ['**/.git/**', '**/secrets/**', '**/*.key'] },
|
|
126
|
-
exec: { allowCmds: ['ls', 'node', 'python', 'echo'], denyCmds: ['rm', 'chmod', 'chown', 'curl', 'wget', 'su', 'sudo'] }
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
fs.writeFileSync(policyPath, JSON.stringify(policyObj, null, 2));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Policy signature
|
|
134
|
-
const sigResult = createPolicySignatureEnvelope({ policyObj, secretKeyB64, keyId: signerKeyId });
|
|
135
|
-
if (sigResult.ok) {
|
|
136
|
-
fs.writeFileSync(path.join(lbeDir, 'config/policy.sig.json'), JSON.stringify(sigResult.envelope, null, 2));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return { defaultKeyId, secretKeyB64, publicKeyB64 };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// ─── Agent governance files ───────────────────────────────────────────────────
|
|
143
|
-
// Generated once by init so every AI agent auto-discovers LBE without the user
|
|
144
|
-
// needing to explain it. Logic and internals are never mentioned — only the API.
|
|
145
|
-
|
|
146
|
-
function agentContractContent() {
|
|
147
|
-
return `# LBE Governance Contract
|
|
148
|
-
|
|
149
|
-
This project has LetterBlack LBE (Local-first execution Governance) active.
|
|
150
|
-
|
|
151
|
-
## Your role as an agent
|
|
152
|
-
|
|
153
|
-
Every file write, delete, and shell command must go through the LBE executor.
|
|
154
|
-
The executor validates each action against the project policy and audits it.
|
|
155
|
-
|
|
156
|
-
## How to perform actions
|
|
157
|
-
|
|
158
|
-
\`\`\`js
|
|
159
|
-
import { createLocalExecutor } from '@letterblack/lbe-exec';
|
|
160
|
-
const lbe = createLocalExecutor({ rootDir: process.cwd() });
|
|
161
|
-
|
|
162
|
-
await lbe.writeFile('output/report.md', content);
|
|
163
|
-
await lbe.readFile('src/config.json');
|
|
164
|
-
await lbe.patchFile('src/index.js', patch);
|
|
165
|
-
await lbe.deleteFile('tmp/scratch.txt');
|
|
166
|
-
await lbe.runShell('node', ['scripts/build.js']);
|
|
167
|
-
\`\`\`
|
|
168
|
-
|
|
169
|
-
## What you must never do
|
|
170
|
-
|
|
171
|
-
- Call fs.* or child_process.* directly
|
|
172
|
-
- Modify \`.lbe/policy.json\` directly — propose a rule via \`lbe.policy.proposeRule()\`
|
|
173
|
-
- Attempt to bypass or disable the executor
|
|
174
|
-
|
|
175
|
-
## Proposing a policy rule
|
|
176
|
-
|
|
177
|
-
\`\`\`js
|
|
178
|
-
const proposal = lbe.policy.proposeRule({
|
|
179
|
-
effect: 'deny', type: 'path', pattern: 'secrets/**',
|
|
180
|
-
from: 'agent: these files should never be modified'
|
|
181
|
-
});
|
|
182
|
-
// Return proposal to the user — never call lbe.policy.addRule() yourself.
|
|
183
|
-
\`\`\`
|
|
184
|
-
|
|
185
|
-
## Result shape
|
|
186
|
-
|
|
187
|
-
\`{ ok: boolean, decision: 'allow' | 'deny' | 'observe', executed: boolean }\`
|
|
188
|
-
|
|
189
|
-
## Files
|
|
190
|
-
|
|
191
|
-
- Policy: \`.lbe/policy.json\`
|
|
192
|
-
- Audit: \`.lbe/audit.jsonl\`
|
|
193
|
-
- Status: \`npx lbe-exec status\`
|
|
194
|
-
`;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function writeAgentContract(cwd) {
|
|
198
|
-
const lbeDir = path.join(cwd, '.lbe');
|
|
199
|
-
fs.mkdirSync(lbeDir, { recursive: true });
|
|
200
|
-
fs.writeFileSync(path.join(lbeDir, 'AGENT_CONTRACT.md'), agentContractContent());
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Migrate legacy root-level LBE files into .lbe/ so the workspace stays clean.
|
|
204
|
-
function migrateLegacyRootFiles(cwd) {
|
|
205
|
-
const lbeDir = path.join(cwd, '.lbe');
|
|
206
|
-
fs.mkdirSync(lbeDir, { recursive: true });
|
|
207
|
-
const migrations = [
|
|
208
|
-
['lbe.policy.json', '.lbe/policy.json'],
|
|
209
|
-
['lbe.workspace.json', '.lbe/workspace.json'],
|
|
210
|
-
];
|
|
211
|
-
const removed = [];
|
|
212
|
-
for (const [src, dest] of migrations) {
|
|
213
|
-
const srcPath = path.join(cwd, src);
|
|
214
|
-
const destPath = path.join(cwd, dest);
|
|
215
|
-
if (fs.existsSync(srcPath) && !fs.existsSync(destPath)) {
|
|
216
|
-
fs.renameSync(srcPath, destPath);
|
|
217
|
-
removed.push(src + ' → ' + dest);
|
|
218
|
-
} else if (fs.existsSync(srcPath)) {
|
|
219
|
-
fs.unlinkSync(srcPath); // dest already exists — just drop the old file
|
|
220
|
-
removed.push(src + ' (removed — .lbe/ version exists)');
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
// Remove files that should never have been created in the project root
|
|
224
|
-
const toDelete = ['CLAUDE.md', path.join('.github', 'copilot-instructions.md')];
|
|
225
|
-
for (const rel of toDelete) {
|
|
226
|
-
const p = path.join(cwd, rel);
|
|
227
|
-
// Only remove if it contains the lbe-governance marker — don't touch user files
|
|
228
|
-
if (fs.existsSync(p)) {
|
|
229
|
-
const content = fs.readFileSync(p, 'utf8');
|
|
230
|
-
if (content.includes('lbe-governance') || content.includes('LetterBlack LBE')) {
|
|
231
|
-
fs.unlinkSync(p);
|
|
232
|
-
removed.push(rel + ' (removed — LBE-generated file)');
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return removed;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
240
|
-
|
|
241
|
-
export async function initCommand(opts = {}) {
|
|
242
|
-
const cwd = process.cwd();
|
|
243
|
-
const yes = opts.yes || opts.y || !process.stdin.isTTY;
|
|
244
|
-
const lbeDir = path.join(cwd, '.lbe');
|
|
245
|
-
fs.mkdirSync(lbeDir, { recursive: true });
|
|
246
|
-
const outPath = path.join(lbeDir, 'workspace.json');
|
|
247
|
-
|
|
248
|
-
// 1. Scan
|
|
249
|
-
console.log('\nScanning workspace...\n');
|
|
250
|
-
const { projectTypes, primaryType, semantics, enforcement } = scanWorkspace(cwd);
|
|
251
|
-
|
|
252
|
-
// 2. Show summary
|
|
253
|
-
console.log(formatSummary(projectTypes, semantics, enforcement));
|
|
254
|
-
console.log('');
|
|
255
|
-
|
|
256
|
-
// 3. Ask once (unless --yes or non-interactive)
|
|
257
|
-
let finalEnforcement = enforcement;
|
|
258
|
-
if (!yes) {
|
|
259
|
-
const answer = await ask('Accept? [Y = accept / s = strict / r = relaxed / n = cancel] ');
|
|
260
|
-
if (answer === 'n') {
|
|
261
|
-
console.log('Cancelled.');
|
|
262
|
-
return { success: false };
|
|
263
|
-
}
|
|
264
|
-
if (answer === 's') finalEnforcement = applyStrict(enforcement);
|
|
265
|
-
if (answer === 'r') finalEnforcement = applyRelaxed(enforcement);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// 4. Write lbe.workspace.json
|
|
269
|
-
const contract = {
|
|
270
|
-
lbe: true,
|
|
271
|
-
version: '0.4.0',
|
|
272
|
-
state: 'local',
|
|
273
|
-
projectTypes,
|
|
274
|
-
primaryType,
|
|
275
|
-
semantics,
|
|
276
|
-
enforcement: finalEnforcement,
|
|
277
|
-
};
|
|
278
|
-
fs.writeFileSync(outPath, JSON.stringify(contract, null, 2));
|
|
279
|
-
console.log('✓ Wrote .lbe/workspace.json');
|
|
280
|
-
|
|
281
|
-
// 5. Crypto setup (silent)
|
|
282
|
-
setupCrypto(cwd);
|
|
283
|
-
const localPolicyPath = path.join(lbeDir, 'policy.json');
|
|
284
|
-
if (!fs.existsSync(localPolicyPath)) {
|
|
285
|
-
fs.writeFileSync(localPolicyPath, JSON.stringify({ version: 1, mode: 'observe', workspace: cwd, rules: [] }, null, 2) + '\n');
|
|
286
|
-
}
|
|
287
|
-
const localAuditPath = path.join(lbeDir, 'audit.jsonl');
|
|
288
|
-
if (!fs.existsSync(localAuditPath)) fs.writeFileSync(localAuditPath, '');
|
|
289
|
-
console.log('✓ Keys and policy ready (.lbe/)');
|
|
290
|
-
|
|
291
|
-
// 6. Agent contract inside .lbe/ only — no CLAUDE.md, no .github/ changes
|
|
292
|
-
writeAgentContract(cwd);
|
|
293
|
-
console.log('✓ Agent contract written → .lbe/AGENT_CONTRACT.md');
|
|
294
|
-
|
|
295
|
-
// 7. Migrate any legacy root files from previous LBE versions
|
|
296
|
-
const migrated = migrateLegacyRootFiles(cwd);
|
|
297
|
-
if (migrated.length) {
|
|
298
|
-
console.log('\n✓ Migrated legacy files:');
|
|
299
|
-
for (const m of migrated) console.log(' ' + m);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
console.log('\nDone. All LBE state is in .lbe/');
|
|
303
|
-
console.log('Run npx lbe-exec status to verify.\n');
|
|
304
|
-
|
|
305
|
-
return { success: true, contract };
|
|
306
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
// src/cli/commands/integrityCheck.js
|
|
2
|
-
// Controller integrity commands
|
|
3
|
-
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { performIntegrityCheck, writeIntegrityManifest } from '../../core/integrity.js';
|
|
6
|
-
|
|
7
|
-
function toBoolean(value, defaultValue = false) {
|
|
8
|
-
if (value === undefined) return defaultValue;
|
|
9
|
-
if (value === true || value === false) return value;
|
|
10
|
-
const normalized = String(value).trim().toLowerCase();
|
|
11
|
-
if (normalized === 'true' || normalized === '1' || normalized === 'yes') return true;
|
|
12
|
-
if (normalized === 'false' || normalized === '0' || normalized === 'no') return false;
|
|
13
|
-
return defaultValue;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function integrityCheckCommand(opts) {
|
|
17
|
-
const strict = toBoolean(opts.strict, false) || toBoolean(opts['integrity-strict'], false);
|
|
18
|
-
const manifestPath = opts.manifest
|
|
19
|
-
? path.resolve(opts.manifest)
|
|
20
|
-
: path.resolve(opts['integrity-manifest'] || '.lbe/config/integrity.manifest.json');
|
|
21
|
-
const jsonOutput = toBoolean(opts.json, true);
|
|
22
|
-
|
|
23
|
-
const result = await performIntegrityCheck({
|
|
24
|
-
manifestPath,
|
|
25
|
-
strict
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
if (jsonOutput) {
|
|
29
|
-
console.log(JSON.stringify({
|
|
30
|
-
ok: result.valid,
|
|
31
|
-
valid: result.valid,
|
|
32
|
-
skipped: result.skipped === true,
|
|
33
|
-
strict,
|
|
34
|
-
manifestPath,
|
|
35
|
-
checkedFiles: result.checkedFiles,
|
|
36
|
-
mismatches: result.mismatches || [],
|
|
37
|
-
missing: result.missing || [],
|
|
38
|
-
reason: result.reason || null,
|
|
39
|
-
message: result.message
|
|
40
|
-
}, null, 2));
|
|
41
|
-
} else {
|
|
42
|
-
console.log(result.message);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
process.exit(result.valid ? 0 : 8);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export async function integrityGenerateCommand(opts) {
|
|
49
|
-
const outputPath = path.resolve(opts.out || opts.output || opts.manifest || '.lbe/config/integrity.manifest.json');
|
|
50
|
-
const result = writeIntegrityManifest({ outputPath });
|
|
51
|
-
console.log(JSON.stringify({
|
|
52
|
-
ok: true,
|
|
53
|
-
outputPath: result.outputPath,
|
|
54
|
-
fileCount: result.fileCount
|
|
55
|
-
}, null, 2));
|
|
56
|
-
process.exit(0);
|
|
57
|
-
}
|
package/src/cli/commands/logs.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { resolveWorkspaceState } from '../../state/index.js';
|
|
4
|
-
|
|
5
|
-
const DEFAULT_LIMIT = 20;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* lbe logs [--limit <n>]
|
|
9
|
-
*
|
|
10
|
-
* Reads central lbe-events.jsonl and prints the last N entries.
|
|
11
|
-
* If the file does not exist, prints a clear "not yet" message — it means
|
|
12
|
-
* hook dual-write (alpha2 of the hook change) has not been enabled yet.
|
|
13
|
-
*
|
|
14
|
-
* Reads only. Does not write, migrate, or modify anything.
|
|
15
|
-
*
|
|
16
|
-
* @returns {{ eventsPath, count, entries, missing }}
|
|
17
|
-
*/
|
|
18
|
-
export async function logsCommand(opts) {
|
|
19
|
-
const workspaceRoot = path.resolve(opts.root || process.cwd());
|
|
20
|
-
const { paths } = resolveWorkspaceState(workspaceRoot);
|
|
21
|
-
const limit = opts.limit ? parseInt(opts.limit, 10) : DEFAULT_LIMIT;
|
|
22
|
-
|
|
23
|
-
if (!fs.existsSync(paths.events)) {
|
|
24
|
-
console.log('\nLBE Central Logs');
|
|
25
|
-
console.log(' No central logs yet. Hook dual-write not enabled.');
|
|
26
|
-
console.log(` Expected at: ${paths.events}`);
|
|
27
|
-
console.log('');
|
|
28
|
-
return { eventsPath: paths.events, count: 0, entries: [], missing: true };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const raw = fs.readFileSync(paths.events, 'utf8').trim();
|
|
32
|
-
const lines = raw ? raw.split('\n') : [];
|
|
33
|
-
|
|
34
|
-
const entries = lines
|
|
35
|
-
.map(line => { try { return JSON.parse(line); } catch (_) { return null; } })
|
|
36
|
-
.filter(Boolean);
|
|
37
|
-
|
|
38
|
-
const tail = entries.slice(-limit);
|
|
39
|
-
|
|
40
|
-
console.log(`\nLBE Central Logs — last ${tail.length} of ${entries.length} entries`);
|
|
41
|
-
console.log(` source: ${paths.events}\n`);
|
|
42
|
-
|
|
43
|
-
for (const entry of tail) {
|
|
44
|
-
const ts = entry.ts ? new Date(entry.ts * 1000).toISOString() : '?';
|
|
45
|
-
const action = entry.action || '?';
|
|
46
|
-
const dec = entry.decision || '?';
|
|
47
|
-
const target = entry.path || entry.cmd || '';
|
|
48
|
-
console.log(` [${ts}] ${dec.toUpperCase().padEnd(5)} ${action} ${target}`);
|
|
49
|
-
}
|
|
50
|
-
console.log('');
|
|
51
|
-
|
|
52
|
-
return { eventsPath: paths.events, count: entries.length, entries: tail, missing: false };
|
|
53
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { resolveWorkspaceState } from '../../state/index.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* lbe open-state
|
|
7
|
-
*
|
|
8
|
-
* Resolves the central state directory and opens it in the system file manager.
|
|
9
|
-
* Always prints the path regardless of whether the open succeeds — so the user
|
|
10
|
-
* can copy-paste it even if the file manager call fails.
|
|
11
|
-
*
|
|
12
|
-
* Set env LBE_NO_OPEN=1 to skip the subprocess (used in tests and CI).
|
|
13
|
-
*
|
|
14
|
-
* @returns {{ stateDir, opened }}
|
|
15
|
-
*/
|
|
16
|
-
export async function openStateCommand(opts) {
|
|
17
|
-
const workspaceRoot = path.resolve(opts.root || process.cwd());
|
|
18
|
-
const { stateDir } = resolveWorkspaceState(workspaceRoot);
|
|
19
|
-
|
|
20
|
-
console.log(`\nLBE Central State Directory`);
|
|
21
|
-
console.log(` ${stateDir}\n`);
|
|
22
|
-
|
|
23
|
-
if (process.env.LBE_NO_OPEN === '1') {
|
|
24
|
-
return { stateDir, opened: false };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
let opened = false;
|
|
28
|
-
try {
|
|
29
|
-
if (process.platform === 'win32') {
|
|
30
|
-
spawnSync('explorer.exe', [stateDir], { detached: true, stdio: 'ignore' });
|
|
31
|
-
opened = true;
|
|
32
|
-
} else if (process.platform === 'darwin') {
|
|
33
|
-
spawnSync('open', [stateDir], { detached: true, stdio: 'ignore' });
|
|
34
|
-
opened = true;
|
|
35
|
-
} else {
|
|
36
|
-
spawnSync('xdg-open', [stateDir], { detached: true, stdio: 'ignore' });
|
|
37
|
-
opened = true;
|
|
38
|
-
}
|
|
39
|
-
} catch (_) {
|
|
40
|
-
// Non-fatal — path was already printed above.
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return { stateDir, opened };
|
|
44
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { addLocalPolicyRule } from '../../core/localPolicy.js';
|
|
2
|
-
|
|
3
|
-
export async function policyAddCommand(opts = {}) {
|
|
4
|
-
const result = addLocalPolicyRule(opts.root || process.cwd(), {
|
|
5
|
-
effect: opts.effect, type: opts.type, pattern: opts.pattern, from: opts.from
|
|
6
|
-
}, opts.mode);
|
|
7
|
-
console.log(JSON.stringify(result, null, 2));
|
|
8
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { loadLocalPolicy, writeLocalPolicy } from '../../core/localPolicy.js';
|
|
2
|
-
|
|
3
|
-
export async function policyModeCommand(mode, opts = {}) {
|
|
4
|
-
const loaded = loadLocalPolicy(opts.root || process.cwd(), mode);
|
|
5
|
-
writeLocalPolicy(loaded.root, { ...loaded.policy, mode });
|
|
6
|
-
console.log(JSON.stringify({ mode, policy: loaded.policyPath }, null, 2));
|
|
7
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
// src/cli/commands/policySign.js
|
|
2
|
-
// Sign policy.json and write signature envelope
|
|
3
|
-
|
|
4
|
-
import fs from 'fs';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import { createPolicySignatureEnvelope } from '../../core/policySignature.js';
|
|
7
|
-
|
|
8
|
-
export async function policySignCommand(opts) {
|
|
9
|
-
const policyPath = path.resolve(opts.config || opts.policy || '.lbe/config/policy.default.json');
|
|
10
|
-
const sigPath = path.resolve(opts['policy-sig'] || '.lbe/config/policy.sig.json');
|
|
11
|
-
const secretKeyPath = path.resolve(opts['secret-key-file'] || '.lbe/keys/secret.key');
|
|
12
|
-
const keyId = String(opts['policy-key-id'] || 'policy-signer-v1-2026Q1');
|
|
13
|
-
|
|
14
|
-
if (!fs.existsSync(policyPath)) {
|
|
15
|
-
console.error(JSON.stringify({
|
|
16
|
-
status: 'error',
|
|
17
|
-
error: 'POLICY_FILE_MISSING',
|
|
18
|
-
message: `Policy file not found: ${policyPath}`
|
|
19
|
-
}, null, 2));
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (!fs.existsSync(secretKeyPath)) {
|
|
24
|
-
console.error(JSON.stringify({
|
|
25
|
-
status: 'error',
|
|
26
|
-
error: 'SECRET_KEY_MISSING',
|
|
27
|
-
message: `Secret key file not found: ${secretKeyPath}`
|
|
28
|
-
}, null, 2));
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const policyObj = JSON.parse(fs.readFileSync(policyPath, 'utf8'));
|
|
33
|
-
if (typeof policyObj.version === 'undefined' || typeof policyObj.createdAt === 'undefined') {
|
|
34
|
-
console.error(JSON.stringify({
|
|
35
|
-
status: 'error',
|
|
36
|
-
error: 'POLICY_VERSION_METADATA_MISSING',
|
|
37
|
-
message: 'Policy must include version and createdAt before signing'
|
|
38
|
-
}, null, 2));
|
|
39
|
-
process.exit(8);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const secretKeyB64 = fs.readFileSync(secretKeyPath, 'utf8').trim();
|
|
43
|
-
const signResult = createPolicySignatureEnvelope({
|
|
44
|
-
policyObj,
|
|
45
|
-
secretKeyB64,
|
|
46
|
-
keyId
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
if (!signResult.ok) {
|
|
50
|
-
console.error(JSON.stringify({
|
|
51
|
-
status: 'error',
|
|
52
|
-
error: signResult.reason || 'POLICY_SIGN_FAILED',
|
|
53
|
-
message: signResult.message
|
|
54
|
-
}, null, 2));
|
|
55
|
-
process.exit(8);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const outDir = path.dirname(sigPath);
|
|
59
|
-
if (!fs.existsSync(outDir)) {
|
|
60
|
-
fs.mkdirSync(outDir, { recursive: true });
|
|
61
|
-
}
|
|
62
|
-
fs.writeFileSync(sigPath, JSON.stringify(signResult.envelope, null, 2));
|
|
63
|
-
|
|
64
|
-
console.log(JSON.stringify({
|
|
65
|
-
status: 'ok',
|
|
66
|
-
message: 'Policy signature written',
|
|
67
|
-
policy: policyPath,
|
|
68
|
-
policySig: sigPath,
|
|
69
|
-
keyId
|
|
70
|
-
}, null, 2));
|
|
71
|
-
process.exit(0);
|
|
72
|
-
}
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import { resolveWorkspaceState } from '../../state/index.js';
|
|
5
|
-
import { loadLatestProof } from '../../state/proofRunner.js';
|
|
6
|
-
|
|
7
|
-
// Fields that must NEVER appear in public proof output
|
|
8
|
-
const REDACT_FIELDS = new Set([
|
|
9
|
-
'files_changed', 'failures', 'workspace', 'stateDir',
|
|
10
|
-
'target_id', 'intent_id',
|
|
11
|
-
]);
|
|
12
|
-
|
|
13
|
-
function redactPath(p) {
|
|
14
|
-
if (!p || typeof p !== 'string') return p;
|
|
15
|
-
// Replace home dir with ~, then remove machine-specific prefix
|
|
16
|
-
const home = os.homedir().replace(/\\/g, '/');
|
|
17
|
-
const pNorm = p.replace(/\\/g, '/');
|
|
18
|
-
const relative = pNorm.startsWith(home + '/')
|
|
19
|
-
? '~/' + pNorm.slice(home.length + 1)
|
|
20
|
-
: pNorm;
|
|
21
|
-
// Only keep the last two path segments
|
|
22
|
-
const parts = relative.replace(/\\/g, '/').split('/');
|
|
23
|
-
return parts.length > 2 ? '…/' + parts.slice(-2).join('/') : relative;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function buildPublicProof(proof, targets) {
|
|
27
|
-
const lastTarget = Array.isArray(targets) && targets.length > 0
|
|
28
|
-
? targets[targets.length - 1]
|
|
29
|
-
: null;
|
|
30
|
-
|
|
31
|
-
// Redact: only expose safe, non-identifying fields
|
|
32
|
-
const pub = {
|
|
33
|
-
result: proof.result,
|
|
34
|
-
profile: proof.profile,
|
|
35
|
-
checks: proof.checks_run || [],
|
|
36
|
-
allow_deny: (proof.failures && proof.failures.length > 0) ? 'deny' : 'allow',
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
if (lastTarget) {
|
|
40
|
-
pub.target_type = lastTarget.kind || null;
|
|
41
|
-
pub.target_label = lastTarget.label || null;
|
|
42
|
-
// component_file is a relative path — safe to include as-is
|
|
43
|
-
pub.target_file = lastTarget.component_file || null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Include failure reasons but NOT the raw file paths
|
|
47
|
-
if (proof.failures && proof.failures.length > 0) {
|
|
48
|
-
pub.failure_reasons = proof.failures.map(f => ({ check: f.check, reason: f.reason }));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return pub;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* lbe proof [--json] [--public] [--root <path>]
|
|
56
|
-
*
|
|
57
|
-
* Reads proof/latest.json from the central state store and prints it.
|
|
58
|
-
* Does not re-run proof; use the programmatic API for that.
|
|
59
|
-
*
|
|
60
|
-
* @returns {{ result, profile, found } | null}
|
|
61
|
-
*/
|
|
62
|
-
export async function proofCommand(opts) {
|
|
63
|
-
const workspaceRoot = path.resolve(opts.root || process.cwd());
|
|
64
|
-
const { stateDir, workspaceId, paths } = resolveWorkspaceState(workspaceRoot);
|
|
65
|
-
|
|
66
|
-
const proof = loadLatestProof(stateDir);
|
|
67
|
-
const isPublic = opts.public === true || opts.public === 'true';
|
|
68
|
-
const isJson = opts.json === true || opts.json === 'true' || isPublic;
|
|
69
|
-
|
|
70
|
-
if (!proof) {
|
|
71
|
-
if (isJson) {
|
|
72
|
-
console.log(JSON.stringify({ found: false, message: 'No proof record found. Run lbe proof after using the hook.' }, null, 2));
|
|
73
|
-
} else {
|
|
74
|
-
console.log('\nNo proof record found.');
|
|
75
|
-
console.log('Use the hook-protected workflow and then run: lbe proof\n');
|
|
76
|
-
}
|
|
77
|
-
return { found: false };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Load targets for public redaction
|
|
81
|
-
let targets = [];
|
|
82
|
-
if (isPublic) {
|
|
83
|
-
const targetPath = path.join(stateDir, 'target_registry.jsonl');
|
|
84
|
-
if (fs.existsSync(targetPath)) {
|
|
85
|
-
const raw = fs.readFileSync(targetPath, 'utf8').trim();
|
|
86
|
-
targets = raw ? raw.split('\n').reduce((acc, l) => {
|
|
87
|
-
try { acc.push(JSON.parse(l)); } catch (_) {}
|
|
88
|
-
return acc;
|
|
89
|
-
}, []) : [];
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (isPublic) {
|
|
94
|
-
const pub = buildPublicProof(proof, targets);
|
|
95
|
-
console.log(JSON.stringify(pub, null, 2));
|
|
96
|
-
return { found: true, result: proof.result, profile: proof.profile, public: true };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (isJson) {
|
|
100
|
-
console.log(JSON.stringify(proof, null, 2));
|
|
101
|
-
return { found: true, result: proof.result, profile: proof.profile };
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Human-readable output
|
|
105
|
-
const resultMark = proof.result === 'PASS' ? '✓' : proof.result === 'WEAK_PROOF' ? '⚠' : '✗';
|
|
106
|
-
const workspace = path.basename(workspaceRoot);
|
|
107
|
-
console.log(`\nLBE Proof — ${workspace}`);
|
|
108
|
-
console.log(` Result ${resultMark} ${proof.result}`);
|
|
109
|
-
console.log(` Profile ${proof.profile}`);
|
|
110
|
-
console.log(` Changed files ${(proof.files_changed || []).length}`);
|
|
111
|
-
console.log(` Checks run ${(proof.checks_run || []).join(', ')}`);
|
|
112
|
-
if (proof.failures && proof.failures.length > 0) {
|
|
113
|
-
console.log(` Failures ${proof.failures.length}`);
|
|
114
|
-
for (const f of proof.failures) {
|
|
115
|
-
console.log(` • [${f.check}] ${f.reason}${f.file ? ': ' + f.file : ''}`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
console.log(` Recorded at ${proof.ts}`);
|
|
119
|
-
console.log('');
|
|
120
|
-
|
|
121
|
-
return { found: true, result: proof.result, profile: proof.profile };
|
|
122
|
-
}
|