getprismo 0.1.11 → 0.1.13

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 CHANGED
@@ -33,6 +33,7 @@ after you code npx getprismo cc timeline
33
33
  **doctor** diagnoses the repo, applies safe fixes, and shows the before/after score.
34
34
  **watch** monitors context pressure live and warns when things go wrong.
35
35
  **cc timeline** reconstructs what happened in the session so you learn from it.
36
+ **shield** runs noisy commands without dumping full output back into the agent context.
36
37
 
37
38
  ---
38
39
 
@@ -137,6 +138,51 @@ watch caught lockfiles entering context, a file being read 286 times, and tool o
137
138
 
138
139
  ---
139
140
 
141
+ ## new: context shield
142
+
143
+ if you know a command may dump huge output, run it through prismo:
144
+
145
+ ```bash
146
+ npx getprismo shield -- npm test
147
+ npx getprismo shield -- pytest -q
148
+ npx getprismo shield -- npm run build
149
+ ```
150
+
151
+ shield executes the command locally, stores full stdout/stderr under `.prismo/shield/runs/`, indexes the output in `.prismo/shield/shield.sqlite` using SQLite FTS5 when available, and prints only a compact summary plus useful error lines.
152
+
153
+ this is the lightweight context-sandbox layer: the full output stays on disk until you explicitly inspect it, instead of being pasted into the model context and re-sent every turn.
154
+
155
+ example:
156
+
157
+ ```text
158
+ Prismo Shield
159
+
160
+ Command: npm test
161
+ Exit: 1
162
+ Captured: 186 KB (~46,500 tokens kept out of chat)
163
+
164
+ Full Output Stored:
165
+ - .prismo/shield/runs/2026-05-20T.../stdout.txt
166
+ - .prismo/shield/runs/2026-05-20T.../stderr.txt
167
+ - .prismo/shield/shield.sqlite
168
+
169
+ Summary Returned To Context:
170
+ - ERROR: auth.test.ts expected 200 received 401
171
+ - FAIL src/auth/session.test.ts
172
+ ```
173
+
174
+ search previous shield output without reloading whole logs:
175
+
176
+ ```bash
177
+ npx getprismo shield last
178
+ npx getprismo shield search "auth expected 200"
179
+ npx getprismo shield search "AUTH_FAILURE" --json
180
+ ```
181
+
182
+ this is intentionally not magic interception yet. it is a safe local-first primitive you can tell agents to use for noisy commands.
183
+
184
+ ---
185
+
140
186
  ## new: live guardrails mode
141
187
 
142
188
  the easiest proactive mode is:
@@ -485,6 +531,7 @@ no install needed. npx runs it directly.
485
531
  | `scan --ci` | fail CI when token-risk gates fail |
486
532
  | `optimize` | generate `.prismo/` context packs |
487
533
  | `context` | print paste-ready prompt for agents |
534
+ | `shield` | run noisy commands while keeping full output out of chat |
488
535
  | `setup` | detect tools, logs, proxy readiness |
489
536
  | `usage` | show raw session token usage |
490
537
  | `init` | add npm scripts and .prismo/README.md |
@@ -526,6 +573,16 @@ npx getprismo watch codex # only codex sessions
526
573
  npx getprismo watch claude # only claude code sessions
527
574
  ```
528
575
 
576
+ ### shield mode
577
+
578
+ ```bash
579
+ npx getprismo shield -- npm test
580
+ npx getprismo shield -- pytest -q
581
+ npx getprismo shield --json -- npm run build
582
+ npx getprismo shield last
583
+ npx getprismo shield search "auth failure"
584
+ ```
585
+
529
586
  ---
530
587
 
531
588
  ## cc modes
@@ -0,0 +1,384 @@
1
+ module.exports = function createShield(deps) {
2
+ const {
3
+ fs,
4
+ path,
5
+ estimateTokens,
6
+ formatBytes,
7
+ color,
8
+ } = deps;
9
+ const { spawnSync } = require("child_process");
10
+
11
+ const SHIELD_SCHEMA_VERSION = 1;
12
+
13
+ function nowId() {
14
+ return new Date().toISOString().replace(/[:.]/g, "-");
15
+ }
16
+
17
+ function ensureDir(dir) {
18
+ fs.mkdirSync(dir, { recursive: true });
19
+ }
20
+
21
+ function pickInterestingLines(text, limit = 24) {
22
+ const lines = String(text || "").split(/\r?\n/).filter(Boolean);
23
+ const interesting = [];
24
+ const patterns = [
25
+ /\b(error|failed|failure|exception|traceback|fatal|panic|segmentation fault)\b/i,
26
+ /\b(warn|warning|deprecated|timeout|denied|unauthorized|forbidden)\b/i,
27
+ /\b(assert|expected|received|diff|not found|cannot find|module not found)\b/i,
28
+ ];
29
+ for (const line of lines) {
30
+ if (patterns.some((pattern) => pattern.test(line))) interesting.push(line.slice(0, 500));
31
+ if (interesting.length >= limit) break;
32
+ }
33
+ if (interesting.length) return interesting;
34
+ return lines.slice(Math.max(0, lines.length - Math.min(limit, 12))).map((line) => line.slice(0, 500));
35
+ }
36
+
37
+ function summarizeOutput(stdout, stderr) {
38
+ const stdoutText = String(stdout || "");
39
+ const stderrText = String(stderr || "");
40
+ const combined = [stderrText, stdoutText].filter(Boolean).join("\n");
41
+ const totalBytes = Buffer.byteLength(stdoutText) + Buffer.byteLength(stderrText);
42
+ const totalTokens = estimateTokens(combined);
43
+ return {
44
+ stdoutBytes: Buffer.byteLength(stdoutText),
45
+ stderrBytes: Buffer.byteLength(stderrText),
46
+ totalBytes,
47
+ estimatedTokens: totalTokens,
48
+ interestingLines: pickInterestingLines(combined),
49
+ };
50
+ }
51
+
52
+ function renderStoredReadCommand(pathValue) {
53
+ return `sed -n '1,160p' ${JSON.stringify(pathValue)}`;
54
+ }
55
+
56
+ function shieldRoot(root) {
57
+ return path.join(root, ".prismo", "shield");
58
+ }
59
+
60
+ function shieldDbPath(root) {
61
+ return path.join(shieldRoot(root), "shield.sqlite");
62
+ }
63
+
64
+ function indexPath(root) {
65
+ return path.join(shieldRoot(root), "index.jsonl");
66
+ }
67
+
68
+ function sqlString(value) {
69
+ return `'${String(value == null ? "" : value).replace(/'/g, "''")}'`;
70
+ }
71
+
72
+ function sqliteAvailable() {
73
+ const result = spawnSync("sqlite3", ["--version"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
74
+ return result.status === 0;
75
+ }
76
+
77
+ function sqliteExec(dbPath, sql, json = false) {
78
+ const args = json ? ["-json", dbPath, sql] : [dbPath, sql];
79
+ const result = spawnSync("sqlite3", args, { encoding: "utf8", maxBuffer: 20 * 1024 * 1024 });
80
+ if (result.status !== 0) {
81
+ throw new Error((result.stderr || result.error?.message || "sqlite3 failed").trim());
82
+ }
83
+ if (!json) return null;
84
+ const out = String(result.stdout || "").trim();
85
+ return out ? JSON.parse(out) : [];
86
+ }
87
+
88
+ function ensureSqliteIndex(root) {
89
+ if (!sqliteAvailable()) return { available: false, reason: "sqlite3-not-found" };
90
+ ensureDir(shieldRoot(root));
91
+ const dbPath = shieldDbPath(root);
92
+ sqliteExec(dbPath, `
93
+ PRAGMA journal_mode=WAL;
94
+ CREATE TABLE IF NOT EXISTS shield_runs (
95
+ id TEXT PRIMARY KEY,
96
+ command TEXT NOT NULL,
97
+ cwd TEXT NOT NULL,
98
+ exit_code INTEGER NOT NULL,
99
+ started_at TEXT NOT NULL,
100
+ finished_at TEXT NOT NULL,
101
+ duration_ms INTEGER NOT NULL,
102
+ stdout_path TEXT NOT NULL,
103
+ stderr_path TEXT NOT NULL,
104
+ total_bytes INTEGER NOT NULL,
105
+ estimated_tokens INTEGER NOT NULL,
106
+ summary_json TEXT NOT NULL
107
+ );
108
+ CREATE VIRTUAL TABLE IF NOT EXISTS shield_output USING fts5(
109
+ run_id UNINDEXED,
110
+ command,
111
+ stream UNINDEXED,
112
+ content,
113
+ tokenize='unicode61'
114
+ );
115
+ `);
116
+ return { available: true, path: path.relative(root, dbPath).replace(/\\/g, "/") };
117
+ }
118
+
119
+ function indexShieldRun(root, payload, stdout, stderr) {
120
+ const sqlite = ensureSqliteIndex(root);
121
+ if (!sqlite.available) return { mode: "jsonl", sqlite };
122
+ const dbPath = shieldDbPath(root);
123
+ const runId = path.basename(payload.stored.directory);
124
+ sqliteExec(dbPath, `
125
+ INSERT OR REPLACE INTO shield_runs (
126
+ id, command, cwd, exit_code, started_at, finished_at, duration_ms,
127
+ stdout_path, stderr_path, total_bytes, estimated_tokens, summary_json
128
+ ) VALUES (
129
+ ${sqlString(runId)},
130
+ ${sqlString(payload.command)},
131
+ ${sqlString(payload.cwd)},
132
+ ${Number(payload.exitCode || 0)},
133
+ ${sqlString(payload.startedAt)},
134
+ ${sqlString(payload.finishedAt)},
135
+ ${Number(payload.durationMs || 0)},
136
+ ${sqlString(payload.stored.stdout)},
137
+ ${sqlString(payload.stored.stderr)},
138
+ ${Number(payload.output.totalBytes || 0)},
139
+ ${Number(payload.output.estimatedTokens || 0)},
140
+ ${sqlString(JSON.stringify(payload))}
141
+ );
142
+ DELETE FROM shield_output WHERE run_id = ${sqlString(runId)};
143
+ INSERT INTO shield_output (run_id, command, stream, content)
144
+ VALUES (${sqlString(runId)}, ${sqlString(payload.command)}, 'stdout', ${sqlString(stdout)});
145
+ INSERT INTO shield_output (run_id, command, stream, content)
146
+ VALUES (${sqlString(runId)}, ${sqlString(payload.command)}, 'stderr', ${sqlString(stderr)});
147
+ `);
148
+ return { mode: "sqlite-fts5", sqlite };
149
+ }
150
+
151
+ function readJsonlIndex(root) {
152
+ const filePath = indexPath(root);
153
+ if (!fs.existsSync(filePath)) return [];
154
+ return fs.readFileSync(filePath, "utf8")
155
+ .split(/\r?\n/)
156
+ .filter(Boolean)
157
+ .map((line) => {
158
+ try {
159
+ return JSON.parse(line);
160
+ } catch {
161
+ return null;
162
+ }
163
+ })
164
+ .filter(Boolean);
165
+ }
166
+
167
+ function readStoredText(root, relPath) {
168
+ const fullPath = path.join(root, relPath);
169
+ try {
170
+ return fs.readFileSync(fullPath, "utf8");
171
+ } catch {
172
+ return "";
173
+ }
174
+ }
175
+
176
+ function fallbackSearch(root, query, limit = 10) {
177
+ const needle = String(query || "").toLowerCase();
178
+ if (!needle) return [];
179
+ const rows = readJsonlIndex(root).reverse();
180
+ const results = [];
181
+ for (const row of rows) {
182
+ for (const stream of ["stderr", "stdout"]) {
183
+ const relPath = row.stored?.[stream];
184
+ const text = readStoredText(root, relPath || "");
185
+ const lower = text.toLowerCase();
186
+ const index = lower.indexOf(needle);
187
+ if (index < 0) continue;
188
+ const start = Math.max(0, index - 180);
189
+ const end = Math.min(text.length, index + needle.length + 320);
190
+ results.push({
191
+ runId: path.basename(row.stored.directory),
192
+ command: row.command,
193
+ stream,
194
+ path: relPath,
195
+ exitCode: row.exitCode,
196
+ finishedAt: row.finishedAt,
197
+ snippet: text.slice(start, end).replace(/\s+/g, " ").trim(),
198
+ });
199
+ if (results.length >= limit) return results;
200
+ }
201
+ }
202
+ return results;
203
+ }
204
+
205
+ function runShieldSearch(rootDir = process.cwd(), query = "", options = {}) {
206
+ const root = path.resolve(rootDir);
207
+ const limit = options.limit || 10;
208
+ if (!String(query || "").trim()) throw new Error("No search query provided. Use: prismo shield search \"error text\"");
209
+ const sqlite = ensureSqliteIndex(root);
210
+ if (sqlite.available && fs.existsSync(shieldDbPath(root))) {
211
+ try {
212
+ const phrase = `"${String(query).replace(/"/g, '""')}"`;
213
+ const rows = sqliteExec(shieldDbPath(root), `
214
+ SELECT
215
+ shield_output.run_id AS runId,
216
+ shield_output.command AS command,
217
+ shield_output.stream AS stream,
218
+ CASE shield_output.stream
219
+ WHEN 'stderr' THEN shield_runs.stderr_path
220
+ ELSE shield_runs.stdout_path
221
+ END AS path,
222
+ shield_runs.exit_code AS exitCode,
223
+ shield_runs.finished_at AS finishedAt,
224
+ snippet(shield_output, 3, '[', ']', ' ... ', 24) AS snippet
225
+ FROM shield_output
226
+ JOIN shield_runs ON shield_runs.id = shield_output.run_id
227
+ WHERE shield_output MATCH ${sqlString(phrase)}
228
+ ORDER BY rank
229
+ LIMIT ${Number(limit)};
230
+ `, true);
231
+ return { query, mode: "sqlite-fts5", results: rows };
232
+ } catch {
233
+ // FTS queries can reject punctuation-heavy input; fall back to JSONL/plain text scan.
234
+ }
235
+ }
236
+ return { query, mode: "jsonl-fallback", results: fallbackSearch(root, query, limit) };
237
+ }
238
+
239
+ function runShieldLast(rootDir = process.cwd(), options = {}) {
240
+ const root = path.resolve(rootDir);
241
+ const limit = options.limit || 5;
242
+ return {
243
+ mode: fs.existsSync(shieldDbPath(root)) ? "sqlite-fts5" : "jsonl-fallback",
244
+ runs: readJsonlIndex(root).reverse().slice(0, limit),
245
+ };
246
+ }
247
+
248
+ function runShield(rootDir = process.cwd(), commandArgs = [], options = {}) {
249
+ const root = path.resolve(rootDir);
250
+ if (!commandArgs.length) {
251
+ throw new Error("No command provided. Use: prismo shield -- npm test");
252
+ }
253
+
254
+ const startedAt = new Date();
255
+ const id = nowId();
256
+ const runsDir = path.join(root, ".prismo", "shield", "runs", id);
257
+ ensureDir(runsDir);
258
+
259
+ const command = commandArgs[0];
260
+ const args = commandArgs.slice(1);
261
+ const result = spawnSync(command, args, {
262
+ cwd: root,
263
+ encoding: "utf8",
264
+ maxBuffer: options.maxBuffer || 30 * 1024 * 1024,
265
+ shell: false,
266
+ env: process.env,
267
+ });
268
+
269
+ const finishedAt = new Date();
270
+ const stdout = result.stdout || "";
271
+ const stderr = result.stderr || "";
272
+ const stdoutPath = path.join(runsDir, "stdout.txt");
273
+ const stderrPath = path.join(runsDir, "stderr.txt");
274
+ const summary = summarizeOutput(stdout, stderr);
275
+ fs.writeFileSync(stdoutPath, stdout, "utf8");
276
+ fs.writeFileSync(stderrPath, stderr, "utf8");
277
+
278
+ const payload = {
279
+ schemaVersion: 1,
280
+ command: commandArgs.join(" "),
281
+ cwd: root,
282
+ exitCode: typeof result.status === "number" ? result.status : 1,
283
+ signal: result.signal || null,
284
+ error: result.error ? result.error.message : null,
285
+ startedAt: startedAt.toISOString(),
286
+ finishedAt: finishedAt.toISOString(),
287
+ durationMs: finishedAt.getTime() - startedAt.getTime(),
288
+ output: summary,
289
+ stored: {
290
+ stdout: path.relative(root, stdoutPath).replace(/\\/g, "/"),
291
+ stderr: path.relative(root, stderrPath).replace(/\\/g, "/"),
292
+ directory: path.relative(root, runsDir).replace(/\\/g, "/"),
293
+ },
294
+ next: [
295
+ "Feed the summary to the agent first; inspect full output only if needed.",
296
+ `Read stdout: ${renderStoredReadCommand(path.relative(root, stdoutPath).replace(/\\/g, "/"))}`,
297
+ `Read stderr: ${renderStoredReadCommand(path.relative(root, stderrPath).replace(/\\/g, "/"))}`,
298
+ ],
299
+ };
300
+ fs.writeFileSync(path.join(runsDir, "summary.json"), JSON.stringify(payload, null, 2), "utf8");
301
+ fs.mkdirSync(path.join(root, ".prismo", "shield"), { recursive: true });
302
+ fs.appendFileSync(path.join(root, ".prismo", "shield", "index.jsonl"), `${JSON.stringify(payload)}\n`, "utf8");
303
+ payload.index = indexShieldRun(root, payload, stdout, stderr);
304
+ fs.writeFileSync(path.join(runsDir, "summary.json"), JSON.stringify(payload, null, 2), "utf8");
305
+ return payload;
306
+ }
307
+
308
+ function renderShieldTerminal(result) {
309
+ const lines = [];
310
+ const exitTone = result.exitCode === 0 ? "green" : "red";
311
+ lines.push("");
312
+ lines.push(color("Prismo Shield", "bold"));
313
+ lines.push("");
314
+ lines.push(`Command: ${result.command}`);
315
+ lines.push(`Exit: ${color(String(result.exitCode), exitTone)}`);
316
+ lines.push(`Duration: ${result.durationMs}ms`);
317
+ lines.push(`Captured: ${formatBytes(result.output.totalBytes)} (~${result.output.estimatedTokens.toLocaleString()} tokens kept out of chat)`);
318
+ lines.push("");
319
+ lines.push("Full Output Stored:");
320
+ lines.push(`- ${result.stored.stdout}`);
321
+ lines.push(`- ${result.stored.stderr}`);
322
+ lines.push(`- ${result.stored.directory}/summary.json`);
323
+ lines.push("");
324
+ lines.push("Summary Returned To Context:");
325
+ result.output.interestingLines.slice(0, 24).forEach((line) => lines.push(`- ${line}`));
326
+ lines.push("");
327
+ lines.push("Next:");
328
+ lines.push("1. Give the agent this summary first.");
329
+ lines.push("2. Inspect the stored full output only if the summary is not enough.");
330
+ lines.push(`3. ${renderStoredReadCommand(result.stored.stderr)}`);
331
+ return lines.join("\n");
332
+ }
333
+
334
+ function renderShieldSearchTerminal(result) {
335
+ const lines = [];
336
+ lines.push("");
337
+ lines.push(color("Prismo Shield Search", "bold"));
338
+ lines.push("");
339
+ lines.push(`Query: ${result.query}`);
340
+ lines.push(`Index: ${result.mode}`);
341
+ lines.push(`Results: ${result.results.length}`);
342
+ lines.push("");
343
+ if (!result.results.length) {
344
+ lines.push("No matching shield output found.");
345
+ return lines.join("\n");
346
+ }
347
+ result.results.forEach((item, index) => {
348
+ lines.push(`${index + 1}. ${item.path} (${item.stream}, exit ${item.exitCode})`);
349
+ lines.push(` ${item.command}`);
350
+ lines.push(` ${String(item.snippet || "").slice(0, 700)}`);
351
+ });
352
+ lines.push("");
353
+ lines.push("Next:");
354
+ lines.push("Use the stored path above only if the snippet is not enough.");
355
+ return lines.join("\n");
356
+ }
357
+
358
+ function renderShieldLastTerminal(result) {
359
+ const lines = [];
360
+ lines.push("");
361
+ lines.push(color("Prismo Shield Last", "bold"));
362
+ lines.push("");
363
+ lines.push(`Index: ${result.mode}`);
364
+ if (!result.runs.length) {
365
+ lines.push("No shield runs found.");
366
+ return lines.join("\n");
367
+ }
368
+ result.runs.forEach((run, index) => {
369
+ lines.push(`${index + 1}. ${run.finishedAt} exit ${run.exitCode} ${run.command}`);
370
+ lines.push(` ${run.stored.directory}/summary.json`);
371
+ lines.push(` ${formatBytes(run.output.totalBytes)} (~${run.output.estimatedTokens.toLocaleString()} tokens kept out of chat)`);
372
+ });
373
+ return lines.join("\n");
374
+ }
375
+
376
+ return {
377
+ renderShieldLastTerminal,
378
+ renderShieldSearchTerminal,
379
+ renderShieldTerminal,
380
+ runShieldLast,
381
+ runShieldSearch,
382
+ runShield,
383
+ };
384
+ };
@@ -267,6 +267,21 @@ const {
267
267
  writeGeneratedFile,
268
268
  });
269
269
 
270
+ const {
271
+ renderShieldLastTerminal,
272
+ renderShieldSearchTerminal,
273
+ renderShieldTerminal,
274
+ runShieldLast,
275
+ runShieldSearch,
276
+ runShield,
277
+ } = require("./prismo-dev/shield")({
278
+ fs,
279
+ path,
280
+ estimateTokens,
281
+ formatBytes: (...args) => formatBytes(...args),
282
+ color,
283
+ });
284
+
270
285
  function printHelp() {
271
286
  console.log(`Prismo CLI
272
287
 
@@ -275,6 +290,9 @@ Usage:
275
290
  prismo init [--json] [--dry-run] [path]
276
291
  prismo doctor [--json] [--dry-run] [--apply-ignores-only] [--no-context-packs] [--limit N] [path]
277
292
  prismo firewall [task] [--json] [--dry-run] [path]
293
+ prismo shield [--json] [path] -- <command ...>
294
+ prismo shield last [--json] [--limit N] [path]
295
+ prismo shield search <query> [--json] [--limit N] [path]
278
296
  prismo setup [--json] [--proxy-url URL] [path]
279
297
  prismo scan [--fix] [--ci] [--json] [--usage] [--simple] [--no-report] [path]
280
298
  prismo optimize [scope] [--json] [path]
@@ -289,6 +307,7 @@ Commands:
289
307
  init Add local PrismoDev helper docs and npm scripts when package.json exists.
290
308
  doctor Diagnose, safely optimize, re-scan, and show before/after payoff.
291
309
  firewall Generate allowed/blocked context policy files for an AI coding task.
310
+ shield Run a noisy command, store full output locally, and return a compact summary.
292
311
  scan Run PrismoDev for Claude Code, Codex, Cursor, and AI coding workflows.
293
312
  optimize Generate lightweight AI-readable project context files in .prismo/.
294
313
  context Print a copy-pasteable compact context prompt for AI coding tools.
@@ -475,6 +494,23 @@ Examples:
475
494
  Output:
476
495
  Writes .prismo/context-firewall.md, .prismo/allowed-context.txt, .prismo/blocked-context.txt, and .prismo/firewall-prompt.md.
477
496
  The firewall is a local policy file for the agent to follow before reading files; it does not enforce filesystem access by itself.`,
497
+ shield: `Prismo Shield
498
+
499
+ Usage:
500
+ prismo shield [--json] [path] -- <command ...>
501
+ prismo shield last [--json] [--limit N] [path]
502
+ prismo shield search <query> [--json] [--limit N] [path]
503
+
504
+ Examples:
505
+ prismo shield -- npm test
506
+ prismo shield -- pytest -q
507
+ prismo shield --json -- npm run build
508
+ prismo shield last
509
+ prismo shield search "auth failure"
510
+
511
+ Output:
512
+ Runs the command locally, stores full stdout/stderr under .prismo/shield/runs/, indexes output in SQLite FTS5 when available, and prints only a compact summary plus useful error lines.
513
+ Search and last retrieve prior shield runs without re-feeding full output into agent context.`,
478
514
  ci: `Prismo CI
479
515
 
480
516
  Usage:
@@ -521,8 +557,8 @@ async function runCli(argv) {
521
557
  printCommandHelp(command);
522
558
  return;
523
559
  }
524
- if (!["dev", "init", "doctor", "firewall", "setup", "scan", "optimize", "context", "cc", "usage", "watch", "demo"].includes(command)) {
525
- throw new Error(`Unknown command: ${command}. Try: prismo doctor, prismo watch, prismo firewall, prismo init, prismo scan, prismo optimize, prismo context, prismo cc, or prismo usage`);
560
+ if (!["dev", "init", "doctor", "firewall", "shield", "setup", "scan", "optimize", "context", "cc", "usage", "watch", "demo"].includes(command)) {
561
+ throw new Error(`Unknown command: ${command}. Try: prismo doctor, prismo watch, prismo shield, prismo firewall, prismo init, prismo scan, prismo optimize, prismo context, prismo cc, or prismo usage`);
526
562
  }
527
563
 
528
564
  if (command === "demo") {
@@ -604,6 +640,40 @@ async function runCli(argv) {
604
640
  return;
605
641
  }
606
642
 
643
+ if (command === "shield") {
644
+ const json = rest.includes("--json");
645
+ const limitIndex = rest.indexOf("--limit");
646
+ const limit = parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 5);
647
+ const positional = getPositionals(rest, new Set(["--limit"]));
648
+ if (positional[0] === "last") {
649
+ const target = positional[1] || process.cwd();
650
+ const result = runShieldLast(target, { limit });
651
+ if (json) console.log(JSON.stringify(result, null, 2));
652
+ else console.log(renderShieldLastTerminal(result));
653
+ return;
654
+ }
655
+ if (positional[0] === "search") {
656
+ const query = positional[1];
657
+ const target = positional[2] || process.cwd();
658
+ const result = runShieldSearch(target, query, { limit });
659
+ if (json) console.log(JSON.stringify(result, null, 2));
660
+ else console.log(renderShieldSearchTerminal(result));
661
+ return;
662
+ }
663
+ const separatorIndex = rest.indexOf("--");
664
+ const beforeSeparator = separatorIndex >= 0 ? rest.slice(0, separatorIndex) : [];
665
+ const commandArgs = separatorIndex >= 0 ? rest.slice(separatorIndex + 1) : getPositionals(rest);
666
+ const target = getPositionals(beforeSeparator)[0] || process.cwd();
667
+ const result = runShield(target, commandArgs);
668
+ if (json) {
669
+ console.log(JSON.stringify(result, null, 2));
670
+ } else {
671
+ console.log(renderShieldTerminal(result));
672
+ }
673
+ process.exitCode = result.exitCode;
674
+ return;
675
+ }
676
+
607
677
  if (command === "setup") {
608
678
  const json = rest.includes("--json");
609
679
  const limitIndex = rest.indexOf("--limit");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getprismo",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Local AI coding workflow scanner for Codex, Claude Code, Cursor, and token-waste diagnostics.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/shanirsh/prismodev#readme",