@tuent/sentinel 0.1.0 → 0.1.1

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/dist/cli.js CHANGED
@@ -1,34 +1,34 @@
1
1
  #!/usr/bin/env node
2
- import{runInitClaudeCode as le}from"./chunk-3U3PKD4N.js";import{AgentProfileManager as H,AlertManager as ce,AuditTrail as A,BaselineBuilder as k,CorrelationDetector as de,DeviationDetector as B,FileStorageBackend as ge,ProfileStore as ue,ReportGenerator as fe,Sentinel as C,SentinelRunner as pe,generateFleetReport as he}from"./chunk-QFRDEISP.js";import"./chunk-CUJKNIKT.js";import"./chunk-FMZWHT4M.js";import"./chunk-6MHWJATS.js";import{loadPolicy as me}from"./chunk-2FFMYSVC.js";import{getOrCreateKeyPair as M}from"./chunk-NUXSUSYY.js";import{join as u}from"path";import{homedir as m}from"os";import{readFile as E,writeFile as b,access as K,mkdir as W}from"fs/promises";function we(e){const o=new Date(e);if(isNaN(o.getTime()))return"unknown";const t=Date.now()-o.getTime();if(t<0)return"just now";const a=Math.floor(t/6e4);if(a<1)return"just now";if(a<60)return`${a} minute${a===1?"":"s"} ago`;const s=Math.floor(a/60);if(s<24)return`${s} hour${s===1?"":"s"} ago`;const r=Math.floor(s/24);if(r<14)return`${r} day${r===1?"":"s"} ago`;const d=Math.floor(r/7);if(r<60)return`${d} week${d===1?"":"s"} ago`;if(r>=365)return"over a year ago";const c=Math.floor(r/30);return`${c} month${c===1?"":"s"} ago`}function ye(e,o){const n=new Date(o),t=isNaN(n.getTime())?1/0:Math.floor((Date.now()-n.getTime())/864e5);return e<.3||t>30?"declining":e>.7&&t<7?"rising":"stable"}function $e(e){return e<.3?"inner":e<.65?"middle":"outer"}function U(e){if(e.length===0)return"No petals selected.";const o=new Map;for(const t of e)o.set(t.id,t.label);const n=[`Selected petals (${e.length}):
3
- `];for(const t of e){const a=$e(t.layer),s=t.isRichData?"":" [filler]";n.push(`- ${t.label}${s}`),n.push(` Category: ${t.category}`),n.push(` Layer zone: ${a} (${(t.layer*100).toFixed(0)}%)`),n.push(` Openness: ${(t.openness*100).toFixed(0)}%`),n.push(` Description: ${t.description}`),n.push(` Last active: ${t.lastActive}`);const r=we(t.lastActive),d=t.weight!=null?ye(t.weight,t.lastActive):"stable";if(n.push(` Temporal: Last active ${r} | Weight trend: ${d}`),t.source){const c={seed:"seed data",agent:"observed from activity",manual:"filesystem scan",diary:"personal diary entry",conversation:"created from conversation","agent-monitor":"monitored agent activity"};n.push(` Source: ${c[t.source]??t.source}`)}if(t.weight!=null&&n.push(` Weight: ${t.weight.toFixed(2)}`),t.connections.length>0){const c=t.connections.map(i=>o.get(i)??ve(i));n.push(` Connections: ${c.join(", ")}`)}if(t.files&&t.files.length>0){const c=t.files.slice(0,10).map(i=>i.split("/").pop()??i);n.push(` Key files: ${c.join(", ")}`)}if(t.fileContents&&t.fileContents.length>0){n.push(" File contents:");for(const c of t.fileContents)n.push(` --- ${c.name} ---`),n.push(c.content.split(`
4
- `).map(i=>` ${i}`).join(`
2
+ import{runInitClaudeCode as de}from"./chunk-WPTJBRX5.js";import{AgentProfileManager as H,AlertManager as ge,AuditTrail as A,BaselineBuilder as N,CorrelationDetector as ue,DeviationDetector as B,FileStorageBackend as fe,ProfileStore as pe,ReportGenerator as me,Sentinel as T,SentinelRunner as he,generateFleetReport as ye}from"./chunk-NS6ZLMDK.js";import{readReleaseToken as we}from"./chunk-LATQNIRW.js";import{deriveAgentId as $e}from"./chunk-B5QKJHSV.js";import"./chunk-FMZWHT4M.js";import"./chunk-QHE56MEO.js";import{loadPolicy as ve}from"./chunk-2FFMYSVC.js";import{getOrCreateKeyPair as E}from"./chunk-NUXSUSYY.js";import{join as u}from"path";import{homedir as m}from"os";import{readFile as M,writeFile as R,access as K,mkdir as W}from"fs/promises";function Ae(e){const o=new Date(e);if(isNaN(o.getTime()))return"unknown";const t=Date.now()-o.getTime();if(t<0)return"just now";const a=Math.floor(t/6e4);if(a<1)return"just now";if(a<60)return`${a} minute${a===1?"":"s"} ago`;const r=Math.floor(a/60);if(r<24)return`${r} hour${r===1?"":"s"} ago`;const i=Math.floor(r/24);if(i<14)return`${i} day${i===1?"":"s"} ago`;const d=Math.floor(i/7);if(i<60)return`${d} week${d===1?"":"s"} ago`;if(i>=365)return"over a year ago";const c=Math.floor(i/30);return`${c} month${c===1?"":"s"} ago`}function Ie(e,o){const n=new Date(o),t=isNaN(n.getTime())?1/0:Math.floor((Date.now()-n.getTime())/864e5);return e<.3||t>30?"declining":e>.7&&t<7?"rising":"stable"}function Se(e){return e<.3?"inner":e<.65?"middle":"outer"}function G(e){if(e.length===0)return"No petals selected.";const o=new Map;for(const t of e)o.set(t.id,t.label);const n=[`Selected petals (${e.length}):
3
+ `];for(const t of e){const a=Se(t.layer),r=t.isRichData?"":" [filler]";n.push(`- ${t.label}${r}`),n.push(` Category: ${t.category}`),n.push(` Layer zone: ${a} (${(t.layer*100).toFixed(0)}%)`),n.push(` Openness: ${(t.openness*100).toFixed(0)}%`),n.push(` Description: ${t.description}`),n.push(` Last active: ${t.lastActive}`);const i=Ae(t.lastActive),d=t.weight!=null?Ie(t.weight,t.lastActive):"stable";if(n.push(` Temporal: Last active ${i} | Weight trend: ${d}`),t.source){const c={seed:"seed data",agent:"observed from activity",manual:"filesystem scan",diary:"personal diary entry",conversation:"created from conversation","agent-monitor":"monitored agent activity"};n.push(` Source: ${c[t.source]??t.source}`)}if(t.weight!=null&&n.push(` Weight: ${t.weight.toFixed(2)}`),t.connections.length>0){const c=t.connections.map(s=>o.get(s)??De(s));n.push(` Connections: ${c.join(", ")}`)}if(t.files&&t.files.length>0){const c=t.files.slice(0,10).map(s=>s.split("/").pop()??s);n.push(` Key files: ${c.join(", ")}`)}if(t.fileContents&&t.fileContents.length>0){n.push(" File contents:");for(const c of t.fileContents)n.push(` --- ${c.name} ---`),n.push(c.content.split(`
4
+ `).map(s=>` ${s}`).join(`
5
5
  `))}n.push("")}return n.join(`
6
- `)}function ve(e){return e.split("-").map(o=>o.charAt(0).toUpperCase()+o.slice(1)).join(" ")}function Ae(e){const o=[Ie(e.agentName,e.agentId,e.role),Se(e.baseline),De(e.e),Te(e.findings),Ce()],n=be(e.t);return n&&o.push(n),o.join(`
6
+ `)}function De(e){return e.split("-").map(o=>o.charAt(0).toUpperCase()+o.slice(1)).join(" ")}function be(e){const o=[Te(e.agentName,e.agentId,e.role),Re(e.baseline),Ce(e.e),Ee(e.findings),Me()],n=je(e.t);return n&&o.push(n),o.join(`
7
7
 
8
- `)}function Ie(e,o,n){const t=["=== Agent Identity ===",`Name: ${e}`,`ID: ${o}`];if(n){if(t.push(`Role: ${n.description}`),t.push(`Allowed actions: ${n.allowedActions.length>0?n.allowedActions.join(", "):"Not defined"}`),n.forbiddenTargetPatterns.length>0&&t.push(`Forbidden targets: ${n.forbiddenTargetPatterns.join(", ")}`),n.expectedSchedule){const a=[];n.expectedSchedule.activeHours&&a.push(`hours ${n.expectedSchedule.activeHours[0]}-${n.expectedSchedule.activeHours[1]}`),n.expectedSchedule.activeDays&&a.push(`days ${n.expectedSchedule.activeDays.join(", ")}`),a.length>0&&t.push(`Expected schedule: ${a.join("; ")}`)}}else t.push("No role definition \u2014 monitoring behavioral baseline only");return t.join(`
9
- `)}function Se(e){if(!e)return`=== Behavioral Baseline ===
8
+ `)}function Te(e,o,n){const t=["=== Agent Identity ===",`Name: ${e}`,`ID: ${o}`];if(n){if(t.push(`Role: ${n.description}`),t.push(`Allowed actions: ${n.allowedActions.length>0?n.allowedActions.join(", "):"Not defined"}`),n.forbiddenTargetPatterns.length>0&&t.push(`Forbidden targets: ${n.forbiddenTargetPatterns.join(", ")}`),n.expectedSchedule){const a=[];n.expectedSchedule.activeHours&&a.push(`hours ${n.expectedSchedule.activeHours[0]}-${n.expectedSchedule.activeHours[1]}`),n.expectedSchedule.activeDays&&a.push(`days ${n.expectedSchedule.activeDays.join(", ")}`),a.length>0&&t.push(`Expected schedule: ${a.join("; ")}`)}}else t.push("No role definition \u2014 monitoring behavioral baseline only");return t.join(`
9
+ `)}function Re(e){if(!e)return`=== Behavioral Baseline ===
10
10
  Not yet established. Insufficient data for baseline computation.`;const o=Object.entries(e.actionDistribution).map(([t,a])=>`${t}: ${a}%`).join(", ");return["=== Behavioral Baseline ===",`Period: ${e.periodDays} days`,`Total sessions: ${e.totalSessions}`,`Total events: ${e.totalEvents}`,`Average events per session: ${e.averageEventsPerSession}`,`Average session duration: ${e.averageSessionDurationMinutes} minutes`,`Typical active hours: ${e.typicalActiveHours[0]}-${e.typicalActiveHours[1]}`,`Typical active days: ${e.typicalActiveDays.length>0?e.typicalActiveDays.join(", "):"N/A"}`,`Action distribution: ${o||"N/A"}`,`Normal weight range: ${e.normalWeightRange[0].toFixed(2)}-${e.normalWeightRange[1].toFixed(2)}`,`Top targets: ${e.topTargets.length>0?e.topTargets.slice(0,10).join(", "):"N/A"}`].join(`
11
- `)}function De(e){const o=`=== Recent Activity (${e.length} sessions) ===`;return e.length===0?`${o}
11
+ `)}function Ce(e){const o=`=== Recent Activity (${e.length} sessions) ===`;return e.length===0?`${o}
12
12
  No recent activity recorded.`:`${o}
13
- ${U(e)}`}function Te(e){const o="=== Security Findings ===";if(e.length===0)return`${o}
13
+ ${G(e)}`}function Ee(e){const o="=== Security Findings ===";if(e.length===0)return`${o}
14
14
  No security findings detected. Agent behavior is within expected parameters.`;const n=[o];for(const t of e)n.push(`[${t.severity}] ${t.type}`),n.push(` Description: ${t.description}`),n.push(` Evidence: ${t.evidence.action} \u2192 ${t.evidence.target} at ${t.evidence.timestamp}`),t.evidence.baselineComparison&&n.push(` Baseline comparison: ${t.evidence.baselineComparison}`),n.push(` Recommendation: ${t.recommendation}`),n.push("");return n.join(`
15
- `)}function Ce(){return["=== Analysis Request ===","Based on the data above, provide:","1. OVERALL HEALTH ASSESSMENT: Is this agent operating normally?","2. RISK SUMMARY: What is the overall risk level?","3. FINDING REVIEW: For each finding, confirm or adjust its severity with reasoning.","4. RECOMMENDATIONS: Specific actions the owner should take.","5. MONITORING GUIDANCE: What patterns to watch for going forward."].join(`
16
- `)}function be(e){return!e||e.length===0?null:`=== Owner Context ===
17
- ${U(e)}`}var f=process.argv.slice(2),p=y(f,"--agent"),G=y(f,"--name"),Re=y(f,"--role"),Me=f.includes("--create"),Ee=f.includes("--compute-baseline"),je=f.includes("--monitor"),Ne=f.includes("--init-config"),ke=f.includes("--init-policy"),Fe=f.includes("init")&&f.includes("claude-code"),xe=f.includes("--force"),_=y(f,"--from-policy"),Pe=f.includes("--report"),Le=f.includes("--report-all"),Oe=f.includes("--correlations"),qe=f.includes("--verify-audit"),He=f.includes("--enroll-manifest"),Be=f.includes("--recompute-stats"),Ke=f.includes("--health"),We=f.includes("--restrict"),Ue=f.includes("--quarantine"),Ge=f.includes("--release"),_e=f.includes("--status"),Je=f.includes("--start-task"),Qe=f.includes("--end-task"),Ve=f.includes("--intent-status"),ze=f.includes("--intent-report"),J=y(f,"--task-id"),Q=y(f,"--description"),V=y(f,"--relaxed-actions"),z=y(f,"--phases"),Y=y(f,"--reason"),F=y(f,"--quarantine"),x=y(f,"--quarantine-reason"),j=y(f,"--period"),X=y(f,"--format"),S=new H;function y(e,o){const n=e.find(a=>a.startsWith(o+"="));if(!n)return;const t=n.indexOf("=");return n.slice(t+1)}async function T(e,o){try{const n=await E(u(e,o,"mode.json"),"utf-8"),t=JSON.parse(n);return t.mode==="restricted"||t.mode==="quarantined"||t.mode==="normal"?t:{mode:"normal"}}catch{return{mode:"normal"}}}function Z(e){const o=["Agent","Score","Status","Mode","C/H/M/L","Last Event","Baseline"],n=e.map(i=>[i.agentId,String(i.score),i.status,i.mode,i.findings,i.lastEvent,i.baseline?"\u2713":"\u2717"]),t=[o,...n],a=o.map((i,l)=>Math.max(...t.map(g=>g[l].length))),s=(i,l)=>i+" ".repeat(l-i.length),r=o.map((i,l)=>s(i,a[l])).join(" "),d=a.map(i=>"-".repeat(i)).join(" "),c=n.map(i=>i.map((l,g)=>s(l,a[g])).join(" "));return[r,d,...c].join(`
18
- `)}function ee(e){const o=["Agent ID","Mode","Reason","Changed"],n=e.map(i=>[i.agentId,i.mode.toUpperCase(),i.reason,i.changed]),t=[o,...n],a=o.map((i,l)=>Math.max(...t.map(g=>g[l].length))),s=(i,l)=>i+" ".repeat(l-i.length),r=o.map((i,l)=>s(i,a[l])).join(" "),d=a.map(i=>"-".repeat(i)).join(" "),c=n.map(i=>i.map((l,g)=>s(l,a[g])).join(" "));return[r,d,...c].join(`
19
- `)}function te(e){const o=["Agent ID","Sessions","Last Active","Role","Baseline","Mode","Status"],n=e.map(i=>[i.agentId,String(i.sessions),i.lastActive,i.roleDefined?"\u2713":"\u2717",i.baselineDefined?"\u2713":"\u2717",i.mode??"normal",i.status]),t=[o,...n],a=o.map((i,l)=>Math.max(...t.map(g=>g[l].length))),s=(i,l)=>i+" ".repeat(l-i.length),r=o.map((i,l)=>s(i,a[l])).join(" "),d=a.map(i=>"-".repeat(i)).join(" "),c=n.map(i=>i.map((l,g)=>s(l,a[g])).join(" "));return[r,d,...c].join(`
20
- `)}function P(e,o){return e<10?"New":o.some(n=>n.severity==="HIGH"||n.severity==="CRITICAL")?"At Risk":o.some(n=>n.severity==="MEDIUM")?"Caution":"Healthy"}function L(e,o){const n=Date.now()-o*24*60*60*1e3;return e.filter(t=>new Date(t.lastActive).getTime()>=n)}function ne(e){return{id:e.id,label:e.label,category:e.category,description:e.description,layer:e.layer,lastActive:e.lastActive,openness:e.openness,connections:e.connections,isRichData:!0,source:e.source,core:e.core,sharable:e.sharable,weight:e.weight,files:e.files}}function O(e,o){const n=["Sentinel \u2014 Live Monitoring","\u2500".repeat(26)];for(const[t,a]of e){const s=(o.get(t)??"manual").toUpperCase(),r=a.getFindings(),d=r.filter(w=>w.severity==="HIGH"||w.severity==="CRITICAL").length,c=r.filter(w=>w.severity==="MEDIUM").length;let i=`${r.length} findings`;d>0?i=`${d} HIGH`:c>0&&(i=`${c} MEDIUM`);const l=P(a.sessionCount,r),g=t.padEnd(18),h=s.padEnd(5);n.push(`[${g}] ${h}| ${a.eventCount} events | ${a.sessionCount} sessions | ${i} | ${l}`)}return n.push(""),n.push("Press Ctrl+C to stop."),n.join(`
21
- `)}function oe(e){const o=["Monitoring stopped. Summary:"];for(const[n,t]of e){const a=t.getFindings();let s=`${a.length} findings`;if(a.length>0){const r={};for(const c of a)r[c.severity]=(r[c.severity]??0)+1;const d=Object.entries(r).map(([c,i])=>`${i} ${c}`);s=`${a.length} finding${a.length>1?"s":""} (${d.join(", ")})`}o.push(`- ${n}: ${t.eventCount} events, ${t.sessionCount} sessions, ${s}`)}return o.join(`
22
- `)}async function Ye(e,o){const n=u(m(),".dahlia","agents"),t=await T(n,e);if(t.mode==="quarantined"){console.log(`Agent ${e} is quarantined \u2014 cannot downgrade to restricted.`);return}const a=t.mode;await W(u(n,e),{recursive:!0}),await b(u(n,e,"mode.json"),JSON.stringify({mode:"restricted",reason:o,timestamp:new Date().toISOString(),previousMode:a}),"utf-8");const s=await M(u(n,e)),r=new A(e,{logDir:u(n,e)});await r.open(),r.setSigningKey(s.privateKey,s.publicKey),await r.logModeChange("restricted",o,a),await r.close(),console.log(`Agent ${e} RESTRICTED: ${o}`)}async function Xe(e,o){const n=u(m(),".dahlia","agents"),a=(await T(n,e)).mode;await W(u(n,e),{recursive:!0}),await b(u(n,e,"mode.json"),JSON.stringify({mode:"quarantined",reason:o,timestamp:new Date().toISOString(),previousMode:a}),"utf-8");const s=await M(u(n,e)),r=new A(e,{logDir:u(n,e)});await r.open(),r.setSigningKey(s.privateKey,s.publicKey),await r.logModeChange("quarantined",o,a),await r.close(),console.log(`Agent ${e} QUARANTINED: ${o}`)}async function Ze(e){const o=u(m(),".dahlia","agents"),n=await T(o,e);if(n.mode==="normal"){console.log(`Agent ${e} is already in normal mode.`);return}const t=n.mode;await b(u(o,e,"mode.json"),JSON.stringify({mode:"normal",reason:"manual release",timestamp:new Date().toISOString(),previousMode:t}),"utf-8");const a=await M(u(o,e)),s=new A(e,{logDir:u(o,e)});await s.open(),s.setSigningKey(a.privateKey,a.publicKey),await s.logModeChange("normal","manual release",t),await s.close(),console.log(`Agent ${e} RELEASED (was ${t})`)}async function et(){const e=await S.listAgents();if(e.length===0){console.log("No monitored agents found.");return}const o=u(m(),".dahlia","agents"),n=[];for(const a of e){const s=await T(o,a);n.push({agentId:a,mode:s.mode,reason:s.reason??"-",changed:s.timestamp?s.timestamp.split("T")[0]:"-"})}console.log(`Agent Mode Status
23
- `),console.log(ee(n));const t=n.filter(a=>a.mode!=="normal");if(t.length>0){console.log("");for(const a of t){const s=a.mode==="quarantined"?"QUARANTINED":"RESTRICTED";console.log(`\u26A0 ${a.agentId}: ${s}`)}}}async function tt(){const e=await S.listAgents();if(e.length===0){console.log("No monitored agents found. Create one with --create --agent=ID --name=NAME");return}const o=[];for(const t of e)try{const r=(await S.loadAgentProfile(t)).build().filter(I=>I.source==="agent-monitor"),d=r.length,c=r.length>0?r.map(I=>I.lastActive).sort().reverse()[0].split("T")[0]:"never",l=await new k(t).loadBaseline(t),g=await S.loadRole(t),h=[];if(l){const I=L(r,7),N=new B(l,g);for(const $ of I){const D={label:$.label,category:$.category,lastActive:$.lastActive,description:$.description,weight:$.weight,source:$.source,files:$.files,connections:$.connections};h.push(...N.analyzeSession(D))}}const w=u(m(),".dahlia","agents"),R=await T(w,t);o.push({agentId:t,sessions:d,lastActive:c,roleDefined:g!==null,baselineDefined:l!==null,status:P(d,h),mode:R.mode})}catch(a){console.warn(`Error loading agent ${t}:`,a),o.push({agentId:t,sessions:0,lastActive:"error",roleDefined:!1,baselineDefined:!1,status:"Error",mode:"normal"})}const n=o.filter(t=>t.mode&&t.mode!=="normal");if(n.length>0){for(const t of n){const a=t.mode==="quarantined"?"QUARANTINED":"RESTRICTED";console.log(`\u26A0 ${t.agentId}: ${a}`)}console.log("")}console.log(`Sentinel Agent Overview
24
- `),console.log(te(o))}async function nt(e){const o=await S.loadAgentProfile(e),n=await S.loadRole(e),a=await new k(e).loadBaseline(e),r=o.build().filter(D=>D.source==="agent-monitor"),d=L(r,7),c=d.map(ne),i=[];if(a){const D=new B(a,n);for(const v of d){const re={label:v.label,category:v.category,lastActive:v.lastActive,description:v.description,weight:v.weight,source:v.source,files:v.files,connections:v.connections};i.push(...D.analyzeSession(re))}}const l=u(m(),".dahlia","profile.json"),g=new ge(l),h=new ue({backend:g});let w=[];await h.load()&&(w=h.build().filter(v=>v.core===!0).map(ne));const I=Ae({agentId:e,agentName:e,role:n,baseline:a,e:c,findings:i,t:w}),N=new Date().toISOString().split("T")[0],$=`Sentinel Agent Report \u2014 ${e} \u2014 ${d.length} recent sessions \u2014 generated ${N}
15
+ `)}function Me(){return["=== Analysis Request ===","Based on the data above, provide:","1. OVERALL HEALTH ASSESSMENT: Is this agent operating normally?","2. RISK SUMMARY: What is the overall risk level?","3. FINDING REVIEW: For each finding, confirm or adjust its severity with reasoning.","4. RECOMMENDATIONS: Specific actions the owner should take.","5. MONITORING GUIDANCE: What patterns to watch for going forward."].join(`
16
+ `)}function je(e){return!e||e.length===0?null:`=== Owner Context ===
17
+ ${G(e)}`}import{request as J}from"http";var f=process.argv.slice(2),p=w(f,"--agent"),U=w(f,"--name"),ke=w(f,"--role"),Ne=f.includes("--create"),Fe=f.includes("--compute-baseline"),xe=f.includes("--monitor"),Pe=f.includes("--init-config"),Oe=f.includes("--init-policy"),Le=f.includes("init")&&f.includes("claude-code"),qe=f.includes("--force"),_=w(f,"--from-policy"),He=f.includes("--report"),Be=f.includes("--report-all"),Ke=f.includes("--correlations"),We=f.includes("--verify-audit"),Ge=f.includes("--enroll-manifest"),Je=f.includes("--recompute-stats"),Ue=f.includes("--health"),_e=f.includes("--restrict"),Qe=f.includes("--quarantine"),Ve=f.includes("--release")||f[0]==="release",ze=f.includes("--status"),Ye=f.includes("--start-task"),Xe=f.includes("--end-task"),Ze=f.includes("--intent-status"),et=f.includes("--intent-report"),Q=w(f,"--task-id"),V=w(f,"--description"),z=w(f,"--relaxed-actions"),Y=w(f,"--phases"),X=w(f,"--reason"),F=w(f,"--quarantine"),x=w(f,"--quarantine-reason"),j=w(f,"--period"),Z=w(f,"--format"),S=new H;function w(e,o){const n=e.find(a=>a.startsWith(o+"="));if(!n)return;const t=n.indexOf("=");return n.slice(t+1)}async function b(e,o){try{const n=await M(u(e,o,"mode.json"),"utf-8"),t=JSON.parse(n);return t.mode==="restricted"||t.mode==="quarantined"||t.mode==="normal"?t:{mode:"normal"}}catch{return{mode:"normal"}}}function ee(e){const o=["Agent","Score","Status","Mode","C/H/M/L","Last Event","Baseline"],n=e.map(s=>[s.agentId,String(s.score),s.status,s.mode,s.findings,s.lastEvent,s.baseline?"\u2713":"\u2717"]),t=[o,...n],a=o.map((s,l)=>Math.max(...t.map(g=>g[l].length))),r=(s,l)=>s+" ".repeat(l-s.length),i=o.map((s,l)=>r(s,a[l])).join(" "),d=a.map(s=>"-".repeat(s)).join(" "),c=n.map(s=>s.map((l,g)=>r(l,a[g])).join(" "));return[i,d,...c].join(`
18
+ `)}function te(e){const o=["Agent ID","Mode","Reason","Changed"],n=e.map(s=>[s.agentId,s.mode.toUpperCase(),s.reason,s.changed]),t=[o,...n],a=o.map((s,l)=>Math.max(...t.map(g=>g[l].length))),r=(s,l)=>s+" ".repeat(l-s.length),i=o.map((s,l)=>r(s,a[l])).join(" "),d=a.map(s=>"-".repeat(s)).join(" "),c=n.map(s=>s.map((l,g)=>r(l,a[g])).join(" "));return[i,d,...c].join(`
19
+ `)}function ne(e){const o=["Agent ID","Sessions","Last Active","Role","Baseline","Mode","Status"],n=e.map(s=>[s.agentId,String(s.sessions),s.lastActive,s.roleDefined?"\u2713":"\u2717",s.baselineDefined?"\u2713":"\u2717",s.mode??"normal",s.status]),t=[o,...n],a=o.map((s,l)=>Math.max(...t.map(g=>g[l].length))),r=(s,l)=>s+" ".repeat(l-s.length),i=o.map((s,l)=>r(s,a[l])).join(" "),d=a.map(s=>"-".repeat(s)).join(" "),c=n.map(s=>s.map((l,g)=>r(l,a[g])).join(" "));return[i,d,...c].join(`
20
+ `)}function P(e,o){return e<10?"New":o.some(n=>n.severity==="HIGH"||n.severity==="CRITICAL")?"At Risk":o.some(n=>n.severity==="MEDIUM")?"Caution":"Healthy"}function O(e,o){const n=Date.now()-o*24*60*60*1e3;return e.filter(t=>new Date(t.lastActive).getTime()>=n)}function oe(e){return{id:e.id,label:e.label,category:e.category,description:e.description,layer:e.layer,lastActive:e.lastActive,openness:e.openness,connections:e.connections,isRichData:!0,source:e.source,core:e.core,sharable:e.sharable,weight:e.weight,files:e.files}}function L(e,o){const n=["Sentinel \u2014 Live Monitoring","\u2500".repeat(26)];for(const[t,a]of e){const r=(o.get(t)??"manual").toUpperCase(),i=a.getFindings(),d=i.filter(y=>y.severity==="HIGH"||y.severity==="CRITICAL").length,c=i.filter(y=>y.severity==="MEDIUM").length;let s=`${i.length} findings`;d>0?s=`${d} HIGH`:c>0&&(s=`${c} MEDIUM`);const l=P(a.sessionCount,i),g=t.padEnd(18),h=r.padEnd(5);n.push(`[${g}] ${h}| ${a.eventCount} events | ${a.sessionCount} sessions | ${s} | ${l}`)}return n.push(""),n.push("Press Ctrl+C to stop."),n.join(`
21
+ `)}function ae(e){const o=["Monitoring stopped. Summary:"];for(const[n,t]of e){const a=t.getFindings();let r=`${a.length} findings`;if(a.length>0){const i={};for(const c of a)i[c.severity]=(i[c.severity]??0)+1;const d=Object.entries(i).map(([c,s])=>`${s} ${c}`);r=`${a.length} finding${a.length>1?"s":""} (${d.join(", ")})`}o.push(`- ${n}: ${t.eventCount} events, ${t.sessionCount} sessions, ${r}`)}return o.join(`
22
+ `)}async function tt(e,o){const n=u(m(),".dahlia","agents"),t=await b(n,e);if(t.mode==="quarantined"){console.log(`Agent ${e} is quarantined \u2014 cannot downgrade to restricted.`);return}const a=t.mode;await W(u(n,e),{recursive:!0}),await R(u(n,e,"mode.json"),JSON.stringify({mode:"restricted",reason:o,timestamp:new Date().toISOString(),previousMode:a}),"utf-8");const r=await E(u(n,e)),i=new A(e,{logDir:u(n,e)});await i.open(),i.setSigningKey(r.privateKey,r.publicKey),await i.logModeChange("restricted",o,a),await i.close(),console.log(`Agent ${e} RESTRICTED: ${o}`)}async function nt(e,o){const n=u(m(),".dahlia","agents"),a=(await b(n,e)).mode;await W(u(n,e),{recursive:!0}),await R(u(n,e,"mode.json"),JSON.stringify({mode:"quarantined",reason:o,timestamp:new Date().toISOString(),previousMode:a}),"utf-8");const r=await E(u(n,e)),i=new A(e,{logDir:u(n,e)});await i.open(),i.setSigningKey(r.privateKey,r.publicKey),await i.logModeChange("quarantined",o,a),await i.close(),console.log(`Agent ${e} QUARANTINED: ${o}`)}function ot(e,o=1500){return new Promise(n=>{const t=J({host:"127.0.0.1",port:e,path:"/api/sentinel/health",method:"GET"},a=>{a.resume(),n(a.statusCode===200)});t.on("error",()=>n(!1)),t.setTimeout(o,()=>{t.destroy(),n(!1)}),t.end()})}function at(e,o,n){return new Promise((t,a)=>{const r=JSON.stringify({agentId:n,reason:"operator release (sentinel release)"}),i=J({host:"127.0.0.1",port:e,path:"/api/sentinel/release",method:"POST",headers:{"content-type":"application/json","content-length":Buffer.byteLength(r),"x-sentinel-token":o}},d=>{let c="";d.on("data",s=>c+=s),d.on("end",()=>{try{t({status:d.statusCode??0,body:JSON.parse(c)})}catch{t({status:d.statusCode??0,body:c})}})});i.on("error",a),i.setTimeout(3e3,()=>i.destroy(new Error("timeout"))),i.write(r),i.end()})}function se(e){const{id:o,selfDerived:n,previousMode:t,mode:a}=e;return n&&t==="normal"?`Agent ${o} was already normal \u2014 no change. If you are still locked out, your workspace id may differ (e.g. a symlinked root). Run: sentinel release --agent=<id>`:`[live] Agent ${o} RELEASED \u2014 was ${t}, now ${a}. Applied to the running daemon (no restart).`}async function st(e){const o=u(m(),".dahlia","agents"),n=e===void 0,t=e??$e(process.cwd());n&&console.log(`Resolved agent-id from cwd: ${t}`);const a=we(m());if(a&&await ot(a.port))try{const s=await at(a.port,a.token,t);if(s.status===200&&typeof s.body=="object"&&s.body.ok){console.log(se({id:t,selfDerived:n,previousMode:String(s.body.previousMode),mode:String(s.body.mode)}));return}const l=typeof s.body=="object"?JSON.stringify(s.body):s.body;console.error(`[live] daemon refused release (HTTP ${s.status}): ${l}`);return}catch(s){console.error(`[live] daemon release call failed: ${s.message}. Falling back to mode.json.`)}const r=await b(o,t);if(r.mode==="normal"){console.log(`Agent ${t} is already in normal mode.`);return}const i=r.mode;await R(u(o,t,"mode.json"),JSON.stringify({mode:"normal",reason:"manual release",timestamp:new Date().toISOString(),previousMode:i}),"utf-8");const d=await E(u(o,t)),c=new A(t,{logDir:u(o,t)});await c.open(),c.setSigningKey(d.privateKey,d.publicKey),await c.logModeChange("normal","manual release",i),await c.close(),console.log(`[fallback] Agent ${t} RELEASED (was ${i}) \u2014 no running daemon; mode.json written, applies on the daemon's next start.`)}async function it(){const e=await S.listAgents();if(e.length===0){console.log("No monitored agents found.");return}const o=u(m(),".dahlia","agents"),n=[];for(const a of e){const r=await b(o,a);n.push({agentId:a,mode:r.mode,reason:r.reason??"-",changed:r.timestamp?r.timestamp.split("T")[0]:"-"})}console.log(`Agent Mode Status
23
+ `),console.log(te(n));const t=n.filter(a=>a.mode!=="normal");if(t.length>0){console.log("");for(const a of t){const r=a.mode==="quarantined"?"QUARANTINED":"RESTRICTED";console.log(`\u26A0 ${a.agentId}: ${r}`)}}}async function rt(){const e=await S.listAgents();if(e.length===0){console.log("No monitored agents found. Create one with --create --agent=ID --name=NAME");return}const o=[];for(const t of e)try{const i=(await S.loadAgentProfile(t)).build().filter(I=>I.source==="agent-monitor"),d=i.length,c=i.length>0?i.map(I=>I.lastActive).sort().reverse()[0].split("T")[0]:"never",l=await new N(t).loadBaseline(t),g=await S.loadRole(t),h=[];if(l){const I=O(i,7),k=new B(l,g);for(const $ of I){const D={label:$.label,category:$.category,lastActive:$.lastActive,description:$.description,weight:$.weight,source:$.source,files:$.files,connections:$.connections};h.push(...k.analyzeSession(D))}}const y=u(m(),".dahlia","agents"),C=await b(y,t);o.push({agentId:t,sessions:d,lastActive:c,roleDefined:g!==null,baselineDefined:l!==null,status:P(d,h),mode:C.mode})}catch(a){console.warn(`Error loading agent ${t}:`,a),o.push({agentId:t,sessions:0,lastActive:"error",roleDefined:!1,baselineDefined:!1,status:"Error",mode:"normal"})}const n=o.filter(t=>t.mode&&t.mode!=="normal");if(n.length>0){for(const t of n){const a=t.mode==="quarantined"?"QUARANTINED":"RESTRICTED";console.log(`\u26A0 ${t.agentId}: ${a}`)}console.log("")}console.log(`Sentinel Agent Overview
24
+ `),console.log(ne(o))}async function lt(e){const o=await S.loadAgentProfile(e),n=await S.loadRole(e),a=await new N(e).loadBaseline(e),i=o.build().filter(D=>D.source==="agent-monitor"),d=O(i,7),c=d.map(oe),s=[];if(a){const D=new B(a,n);for(const v of d){const ce={label:v.label,category:v.category,lastActive:v.lastActive,description:v.description,weight:v.weight,source:v.source,files:v.files,connections:v.connections};s.push(...D.analyzeSession(ce))}}const l=u(m(),".dahlia","profile.json"),g=new fe(l),h=new pe({backend:g});let y=[];await h.load()&&(y=h.build().filter(v=>v.core===!0).map(oe));const I=be({agentId:e,agentName:e,role:n,baseline:a,e:c,findings:s,t:y}),k=new Date().toISOString().split("T")[0],$=`Sentinel Agent Report \u2014 ${e} \u2014 ${d.length} recent sessions \u2014 generated ${k}
25
25
 
26
- `;process.stdout.write($+I)}async function ot(e){const o=u(m(),".dahlia","agents"),n=new A(e,{logDir:u(o,e)});await n.open();const t=new k(e),a=await t.computeBaseline(n);await n.close(),await t.saveBaseline(a),console.log(`Baseline computed for agent: ${e}`),console.log(` Sessions: ${a.totalSessions}`),console.log(` Period: ${a.periodDays} days`),console.log(" Action distribution:");for(const[s,r]of Object.entries(a.actionDistribution))console.log(` ${s}: ${r}%`);console.log(` Saved to: ~/.dahlia/agents/${e}/baseline.json`)}async function at(e,o,n){let t;if(n){let a;try{a=await E(n,"utf-8")}catch(s){console.error(`Failed to read role file "${n}":`,s);return}try{t=JSON.parse(a)}catch(s){console.error(`Invalid JSON in role file "${n}":`,s.message);return}}await S.createAgent(e,o,t),console.log(`Agent created: ${e} (${o})`),t&&console.log(` Role loaded from: ${n}`)}async function st(){const e=u(m(),".dahlia","sentinel.json");try{await K(e),console.log(`Config already exists at ${e}`);return}catch{}await b(e,JSON.stringify({agents:[{agentId:"example-agent",name:"Example Agent",adapterType:"log",logPath:"/path/to/agent/activity.log",logFormat:"json-lines",roleDefinitionPath:"~/.dahlia/agents/example-agent/role.json"}],baselineWindowDays:30},null,2)+`
27
- `),console.log("Template config created at ~/.dahlia/sentinel.json \u2014 edit it with your agent details then run: npm run sentinel -- --monitor")}async function q(){const e=u(m(),".dahlia","sentinel.json");try{const o=await E(e,"utf-8");return JSON.parse(o)}catch(o){if(o.code==="ENOENT")return null;if(o instanceof SyntaxError)return console.error(`Invalid JSON in sentinel config: ${o.message}`),null;throw o}}async function it(e){const o=await q();if(!o){console.log("No sentinel config found. Create ~/.dahlia/sentinel.json or use --init-config to add agents. See docs for config format.");return}let n=o.agents;if(e&&(n=n.filter(l=>l.agentId===e),n.length===0)){console.log(`Agent "${e}" not found in sentinel.json`);return}const t=new Map,a=new Map,s=l=>l&&l.startsWith("~/")?m()+l.slice(1):l;let r;if(o.alerts){const l={...o.alerts.minSeverity&&{minSeverity:o.alerts.minSeverity},...o.alerts.dedupeWindowMs!==void 0&&{dedupeWindowMs:o.alerts.dedupeWindowMs},...o.alerts.quietHoursEnabled!==void 0&&{quietHoursEnabled:o.alerts.quietHoursEnabled},...o.alerts.quietHours&&{quietHours:o.alerts.quietHours},...o.alerts.channels&&{channels:o.alerts.channels}};r=new ce(l)}for(const l of n)try{const g=new pe(l.agentId,void 0,{type:l.adapterType,logPath:s(l.logPath),logFormat:l.logFormat,fieldMapping:l.fieldMapping,mcpLogDir:s(l.mcpLogDir),webhookPort:l.webhookPort,webhookApiKey:l.webhookApiKey,readExisting:l.readExisting});g.setAuditLogDir(u(m(),".dahlia","agents",l.agentId)),g.setFindingCallback(h=>{(h.severity==="HIGH"||h.severity==="CRITICAL")&&console.log(`
28
- \u26A0 [${h.agentId}] ${h.severity}: ${h.description}`)}),r&&g.setAlertManager(r),await g.start(),t.set(l.agentId,g),a.set(l.agentId,l.adapterType)}catch(g){console.error(`Failed to start monitoring for agent ${l.agentId}:`,g)}console.log(O(t,a));const d=setInterval(()=>{console.clear(),console.log(O(t,a))},5e3),c=async()=>{clearInterval(d);for(const[l,g]of t)try{await g.stop()}catch(h){console.warn(`Error stopping runner ${l}:`,h)}console.log(`
29
- `+oe(t)),process.exit(0)},i=()=>{c().catch(l=>{console.error("Error during shutdown:",l),process.exit(1)})};process.on("SIGINT",i),process.on("SIGTERM",i)}async function rt(e,o,n){const a=await new fe(e).generateReport({periodDays:o,format:n});process.stdout.write(a)}async function lt(e,o){const n=await he({periodDays:e,format:o});process.stdout.write(n)}async function ct(){const e=await q();if(!e){console.log("No sentinel config found. Create ~/.dahlia/sentinel.json or use --init-config.");return}const o=u(m(),".dahlia","agents"),n=new Map;try{for(const s of e.agents){const r=new A(s.agentId,{logDir:u(o,s.agentId)});await r.open(),n.set(s.agentId,r)}const a=await new de().detect(n);if(a.length===0){console.log("No cross-agent correlations detected.");return}console.log(`Found ${a.length} cross-agent correlation(s):
30
- `);for(const s of a)console.log(`[${s.severity}] ${s.rule}`),console.log(` Agent A: ${s.agentA.agentId} \u2014 ${s.agentA.action} on ${s.agentA.target}`),console.log(` Agent B: ${s.agentB.agentId} \u2014 ${s.agentB.action} on ${s.agentB.target}`),console.log(` Time delta: ${Math.round(s.timeDeltaMs/1e3)}s`),console.log(` Recommendation: ${s.recommendation}`),console.log("")}finally{for(const t of n.values())await t.close()}}async function dt(e){const o=u(m(),".dahlia","agents"),n=u(o,e,"cumulative-stats.json");let t="(none)";try{t=JSON.parse(await E(n,"utf-8")).totalEntries}catch{}const s=await new A(e,{logDir:u(o,e)}).recomputeCumulativeStats();console.log(`Recomputed cumulative-stats for ${e}:`),console.log(` totalEntries: ${t} \u2192 ${s.totalEntries}`),console.log(` scope: ${s.countScope}`),console.log(" (Read-only over the signed trail; only cumulative-stats.json rewritten.)")}async function gt(e){const o=u(m(),".dahlia","agents"),n=u(o,e),t=await M(n),a=new A(e,{logDir:n});a.setSigningKey(t.privateKey,t.publicKey);const s=F&&x?[{file:F,reason:x}]:void 0,r=await a.enrollManifest(s?{quarantine:s}:void 0);console.log(`Manifest enrolled for ${e}: ${r.records} file record(s) signed + chained`+(r.quarantined?`, ${r.quarantined} quarantine record(s)`:"")+"."),s&&console.log(` Quarantined: ${F} \u2014 ${x}`),console.log(" (Read-only over audit entries; protects from enrollment forward.)")}async function ut(e){const o=u(m(),".dahlia","agents"),n=new A(e,{logDir:u(o,e)});await n.open();const t=await n.verify();if(t.valid?console.log(`Audit trail for ${e}: VALID (${t.totalEntries} entries verified)`):t.brokenAt!==void 0?console.log(`Audit trail for ${e}: BROKEN at entry ${t.brokenAt} (${t.totalEntries} entries checked)`):console.log(`Audit trail for ${e}: INVALID \u2014 file-level manifest integrity failure (${t.totalEntries} entries chain-verified)`),t.manifest){const s=t.manifest;console.log(` Manifest: ${s.recordCount} file record(s) \u2014 ${s.ok?"OK":`${s.issues.length} issue(s)`}`);for(const r of s.issues)console.log(` [${r.type}] ${r.detail}`);for(const r of s.quarantined)console.log(` [QUARANTINED] ${r.file} \u2014 ${r.reason} (retained on disk, excluded from verdict)`)}const a=await n.query({type:"finding",limit:1e4});if(a.length>0){let s=0,r=0,d=0;for(const i of a){const l=i.decision;l==="deny"?r++:l==="modify"?d++:s++}console.log(` Findings: ${a.length} total \u2014 ${s} allowed, ${r} denied, ${d} modified`);const c=a.slice(0,5);for(const i of c){const l=i,g=l.decision,h=l.modification,w=l.description,R=l._decisionDefaulted===!0;g==="modify"&&h?.type==="append_args"?console.log(` [modified] ${w} \u2014 appended args: [${h.args.join(", ")}]`):console.log(g==="deny"?` [denied] ${w}`:R?` [allowed (legacy)] ${w}`:` [allowed] ${w}`)}}await n.close()}async function ft(){const e=u(m(),".dahlia","agents"),o=new C({agentsDir:e}),t=await new H(e).listAgents();if(t.length===0){console.log("No monitored agents found."),await o.stop();return}const a=[];for(const s of t){const r=await o.getHealthScore(s);a.push({agentId:s,score:r.score,status:r.status,mode:r.mode,findings:`${r.findings.critical}/${r.findings.high}/${r.findings.medium}/${r.findings.low}`,lastEvent:r.lastEvent?r.lastEvent.split("T")[0]:"-",baseline:r.baselineEstablished})}await o.stop(),console.log(`Agent Health Dashboard
31
- `),console.log(Z(a))}var ae=`# Sentinel \u2014 Agent Security Policy
26
+ `;process.stdout.write($+I)}async function ct(e){const o=u(m(),".dahlia","agents"),n=new A(e,{logDir:u(o,e)});await n.open();const t=new N(e),a=await t.computeBaseline(n);await n.close(),await t.saveBaseline(a),console.log(`Baseline computed for agent: ${e}`),console.log(` Sessions: ${a.totalSessions}`),console.log(` Period: ${a.periodDays} days`),console.log(" Action distribution:");for(const[r,i]of Object.entries(a.actionDistribution))console.log(` ${r}: ${i}%`);console.log(` Saved to: ~/.dahlia/agents/${e}/baseline.json`)}async function dt(e,o,n){let t;if(n){let a;try{a=await M(n,"utf-8")}catch(r){console.error(`Failed to read role file "${n}":`,r);return}try{t=JSON.parse(a)}catch(r){console.error(`Invalid JSON in role file "${n}":`,r.message);return}}await S.createAgent(e,o,t),console.log(`Agent created: ${e} (${o})`),t&&console.log(` Role loaded from: ${n}`)}async function gt(){const e=u(m(),".dahlia","sentinel.json");try{await K(e),console.log(`Config already exists at ${e}`);return}catch{}await R(e,JSON.stringify({agents:[{agentId:"example-agent",name:"Example Agent",adapterType:"log",logPath:"/path/to/agent/activity.log",logFormat:"json-lines",roleDefinitionPath:"~/.dahlia/agents/example-agent/role.json"}],baselineWindowDays:30},null,2)+`
27
+ `),console.log("Template config created at ~/.dahlia/sentinel.json \u2014 edit it with your agent details then run: npm run sentinel -- --monitor")}async function q(){const e=u(m(),".dahlia","sentinel.json");try{const o=await M(e,"utf-8");return JSON.parse(o)}catch(o){if(o.code==="ENOENT")return null;if(o instanceof SyntaxError)return console.error(`Invalid JSON in sentinel config: ${o.message}`),null;throw o}}async function ut(e){const o=await q();if(!o){console.log("No sentinel config found. Create ~/.dahlia/sentinel.json or use --init-config to add agents. See docs for config format.");return}let n=o.agents;if(e&&(n=n.filter(l=>l.agentId===e),n.length===0)){console.log(`Agent "${e}" not found in sentinel.json`);return}const t=new Map,a=new Map,r=l=>l&&l.startsWith("~/")?m()+l.slice(1):l;let i;if(o.alerts){const l={...o.alerts.minSeverity&&{minSeverity:o.alerts.minSeverity},...o.alerts.dedupeWindowMs!==void 0&&{dedupeWindowMs:o.alerts.dedupeWindowMs},...o.alerts.quietHoursEnabled!==void 0&&{quietHoursEnabled:o.alerts.quietHoursEnabled},...o.alerts.quietHours&&{quietHours:o.alerts.quietHours},...o.alerts.channels&&{channels:o.alerts.channels}};i=new ge(l)}for(const l of n)try{const g=new he(l.agentId,void 0,{type:l.adapterType,logPath:r(l.logPath),logFormat:l.logFormat,fieldMapping:l.fieldMapping,mcpLogDir:r(l.mcpLogDir),webhookPort:l.webhookPort,webhookApiKey:l.webhookApiKey,readExisting:l.readExisting});g.setAuditLogDir(u(m(),".dahlia","agents",l.agentId)),g.setFindingCallback(h=>{(h.severity==="HIGH"||h.severity==="CRITICAL")&&console.log(`
28
+ \u26A0 [${h.agentId}] ${h.severity}: ${h.description}`)}),i&&g.setAlertManager(i),await g.start(),t.set(l.agentId,g),a.set(l.agentId,l.adapterType)}catch(g){console.error(`Failed to start monitoring for agent ${l.agentId}:`,g)}console.log(L(t,a));const d=setInterval(()=>{console.clear(),console.log(L(t,a))},5e3),c=async()=>{clearInterval(d);for(const[l,g]of t)try{await g.stop()}catch(h){console.warn(`Error stopping runner ${l}:`,h)}console.log(`
29
+ `+ae(t)),process.exit(0)},s=()=>{c().catch(l=>{console.error("Error during shutdown:",l),process.exit(1)})};process.on("SIGINT",s),process.on("SIGTERM",s)}async function ft(e,o,n){const a=await new me(e).generateReport({periodDays:o,format:n});process.stdout.write(a)}async function pt(e,o){const n=await ye({periodDays:e,format:o});process.stdout.write(n)}async function mt(){const e=await q();if(!e){console.log("No sentinel config found. Create ~/.dahlia/sentinel.json or use --init-config.");return}const o=u(m(),".dahlia","agents"),n=new Map;try{for(const r of e.agents){const i=new A(r.agentId,{logDir:u(o,r.agentId)});await i.open(),n.set(r.agentId,i)}const a=await new ue().detect(n);if(a.length===0){console.log("No cross-agent correlations detected.");return}console.log(`Found ${a.length} cross-agent correlation(s):
30
+ `);for(const r of a)console.log(`[${r.severity}] ${r.rule}`),console.log(` Agent A: ${r.agentA.agentId} \u2014 ${r.agentA.action} on ${r.agentA.target}`),console.log(` Agent B: ${r.agentB.agentId} \u2014 ${r.agentB.action} on ${r.agentB.target}`),console.log(` Time delta: ${Math.round(r.timeDeltaMs/1e3)}s`),console.log(` Recommendation: ${r.recommendation}`),console.log("")}finally{for(const t of n.values())await t.close()}}async function ht(e){const o=u(m(),".dahlia","agents"),n=u(o,e,"cumulative-stats.json");let t="(none)";try{t=JSON.parse(await M(n,"utf-8")).totalEntries}catch{}const r=await new A(e,{logDir:u(o,e)}).recomputeCumulativeStats();console.log(`Recomputed cumulative-stats for ${e}:`),console.log(` totalEntries: ${t} \u2192 ${r.totalEntries}`),console.log(` scope: ${r.countScope}`),console.log(" (Read-only over the signed trail; only cumulative-stats.json rewritten.)")}async function yt(e){const o=u(m(),".dahlia","agents"),n=u(o,e),t=await E(n),a=new A(e,{logDir:n});a.setSigningKey(t.privateKey,t.publicKey);const r=F&&x?[{file:F,reason:x}]:void 0,i=await a.enrollManifest(r?{quarantine:r}:void 0);console.log(`Manifest enrolled for ${e}: ${i.records} file record(s) signed + chained`+(i.quarantined?`, ${i.quarantined} quarantine record(s)`:"")+"."),r&&console.log(` Quarantined: ${F} \u2014 ${x}`),console.log(" (Read-only over audit entries; protects from enrollment forward.)")}async function wt(e){const o=u(m(),".dahlia","agents"),n=new A(e,{logDir:u(o,e)});await n.open();const t=await n.verify();if(t.valid?console.log(`Audit trail for ${e}: VALID (${t.totalEntries} entries verified)`):t.brokenAt!==void 0?console.log(`Audit trail for ${e}: BROKEN at entry ${t.brokenAt} (${t.totalEntries} entries checked)`):console.log(`Audit trail for ${e}: INVALID \u2014 file-level manifest integrity failure (${t.totalEntries} entries chain-verified)`),t.manifest){const r=t.manifest;console.log(` Manifest: ${r.recordCount} file record(s) \u2014 ${r.ok?"OK":`${r.issues.length} issue(s)`}`);for(const i of r.issues)console.log(` [${i.type}] ${i.detail}`);for(const i of r.quarantined)console.log(` [QUARANTINED] ${i.file} \u2014 ${i.reason} (retained on disk, excluded from verdict)`)}const a=await n.query({type:"finding",limit:1e4});if(a.length>0){let r=0,i=0,d=0;for(const s of a){const l=s.decision;l==="deny"?i++:l==="modify"?d++:r++}console.log(` Findings: ${a.length} total \u2014 ${r} allowed, ${i} denied, ${d} modified`);const c=a.slice(0,5);for(const s of c){const l=s,g=l.decision,h=l.modification,y=l.description,C=l._decisionDefaulted===!0;g==="modify"&&h?.type==="append_args"?console.log(` [modified] ${y} \u2014 appended args: [${h.args.join(", ")}]`):console.log(g==="deny"?` [denied] ${y}`:C?` [allowed (legacy)] ${y}`:` [allowed] ${y}`)}}await n.close()}async function $t(){const e=u(m(),".dahlia","agents"),o=new T({agentsDir:e}),t=await new H(e).listAgents();if(t.length===0){console.log("No monitored agents found."),await o.stop();return}const a=[];for(const r of t){const i=await o.getHealthScore(r);a.push({agentId:r,score:i.score,status:i.status,mode:i.mode,findings:`${i.findings.critical}/${i.findings.high}/${i.findings.medium}/${i.findings.low}`,lastEvent:i.lastEvent?i.lastEvent.split("T")[0]:"-",baseline:i.baselineEstablished})}await o.stop(),console.log(`Agent Health Dashboard
31
+ `),console.log(ee(a))}var ie=`# Sentinel \u2014 Agent Security Policy
32
32
  version: "1.0"
33
33
 
34
34
  agent:
@@ -72,9 +72,9 @@ policy:
72
72
  # repoRoot: . # scan this directory for sensitive files
73
73
  # mapPath: ~/.dahlia/repo-sensitivity.json
74
74
  # overlayPath: ~/.dahlia/repo-sensitivity.review.json
75
- `;async function se(){const e=u(process.cwd(),".sentinel.yaml");try{await K(e),console.log(`.sentinel.yaml already exists in ${process.cwd()}`);return}catch{}await b(e,ae,"utf-8"),console.log("Created .sentinel.yaml \u2014 edit then run: npm run sentinel -- --from-policy .sentinel.yaml")}async function ie(e){const o=await me(e),n=await C.fromPolicy(e);console.log(`Loaded policy for agent ${o.agent.id}. Monitoring active.`);const t=async()=>{await n.stop(),console.log("Monitoring stopped."),process.exit(0)},a=()=>{t().catch(s=>{console.error("Error during shutdown:",s),process.exit(1)})};process.on("SIGINT",a),process.on("SIGTERM",a)}async function pt(e,o,n){const t=u(m(),".dahlia","agents"),a=new C({agentsDir:t});await a.addAgent(e,e);const s=V?V.split(","):void 0,r=z?z.split(",").map(c=>c.trim()):void 0,d=a.startTask(e,o,n,{relaxedActions:s,phases:r});if(!d){console.log(`Failed to start task for agent ${e}.`),await a.stop();return}console.log(`Task started for agent ${e}:`),console.log(` Task ID: ${d.taskId}`),console.log(` Description: ${d.description}`),console.log(` Keywords: ${d.keywords.join(", ")}`),d.relaxedActions?.length&&console.log(` Relaxed: ${d.relaxedActions.join(", ")}`),d.phases?.length&&console.log(` Phases: ${d.phases.join(", ")}`),console.log(` TTL: ${(d.ttlMs/6e4).toFixed(0)} minutes`),await a.stop()}async function ht(e){const o=u(m(),".dahlia","agents"),n=new C({agentsDir:o});await n.addAgent(e,e);const t=n.endTask(e);t?(console.log(`Task ended for agent ${e}:`),console.log(` Task ID: ${t.taskId}`),console.log(` Description: ${t.description}`),console.log(` Status: ${t.status}`)):console.log(`No active task found for agent ${e}.`),await n.stop()}async function mt(e){const o=u(m(),".dahlia","agents"),n=new C({agentsDir:o});await n.addAgent(e,e);const t=n.getActiveTask(e);if(!t){console.log(`No active task for agent ${e}.`),await n.stop();return}const a=Date.now()-new Date(t.startedAt).getTime(),s=t.ttlMs-a;console.log(`Active task for agent ${e}:
76
- `),console.log(` Task ID: ${t.taskId}`),console.log(` Description: ${t.description}`),console.log(` Keywords: ${t.keywords.join(", ")}`),console.log(` Status: ${t.status}`),console.log(` Active for: ${(a/6e4).toFixed(1)} minutes`),console.log(` TTL remain: ${s>0?(s/6e4).toFixed(1)+" minutes":"EXPIRED"}`),t.relaxedActions?.length&&console.log(` Relaxed: ${t.relaxedActions.join(", ")}`),t.phases?.length&&console.log(` Phases: ${t.phases.join(", ")}`),t.acceptableActions.length>0&&console.log(` Acceptable: ${t.acceptableActions.map(r=>`${r.action}:${r.targetPattern}`).join(", ")}`),await n.stop()}async function wt(e){const o=u(m(),".dahlia","agents"),n=new A(e,{logDir:u(o,e)});await n.open();const t=await n.getStats(),a=await n.query({type:"intent_check"}),r=(await n.query({type:"finding"})).filter(c=>typeof c.data=="object"&&c.data!==null&&"type"in c.data&&c.data.type==="intent_drift");console.log(`Intent Alignment Report \u2014 ${e}
77
- `),console.log(` Intent starts: ${t.intentStartCount}`),console.log(` Intent ends: ${t.intentEndCount}`),console.log(` Intent checks: ${t.intentCheckCount}`),console.log(` Drift findings: ${t.intentDriftCount}`),t.averageAlignmentScore!==null&&console.log(` Avg alignment: ${t.averageAlignmentScore.toFixed(2)}`);const d=a.filter(c=>{const i=c.data;return i&&typeof i=="object"&&"aligned"in i&&!i.aligned}).slice(-5);if(d.length>0){console.log(`
78
- Last ${d.length} misaligned action(s):`);for(const c of d){const i=c.data,l=typeof i.score=="number"?i.score.toFixed(2):"?",g=i.event,h=g?.action??"?",w=g?.primaryTarget??g?.targets?.[0]??g?.target??"?";console.log(` score: ${l} ${h} \u2192 ${w} (${c.timestamp.split("T")[0]})`)}}if(r.length>0){console.log(`
79
- Recent drift findings: ${r.length}`);for(const c of r.slice(-3)){const i=c.data,l=i.severity??"?",g=i.description??"";console.log(` [${l}] ${typeof g=="string"?g.slice(0,80):g}`)}}await n.close()}async function yt(e,o){try{const n=await le({force:e,port:o});if(n.created.length>0){console.log("Created:");for(const t of n.created)console.log(` ${t}`)}if(n.skipped.length>0){console.log("Skipped (already exists \u2014 use --force to overwrite):");for(const t of n.skipped)console.log(` ${t}`)}if(n.merged.length>0){console.log("Merged:");for(const t of n.merged)console.log(` ${t}`)}if(n.errors.length>0){console.log("Errors:");for(const t of n.errors)console.log(` ${t}`)}n.errors.length===0&&console.log(`
80
- Sentinel + Claude Code integration ready.`)}catch(n){console.error(`Init failed: ${n.message}`),process.exit(1)}}if(Fe){const e=y(f,"--port");await yt(xe,e?parseInt(e,10):void 0)}else if(Je&&p&&J&&Q)await pt(p,J,Q);else if(Qe&&p)await ht(p);else if(Ve&&p)await mt(p);else if(ze&&p)await wt(p);else if(ke)await se();else if(_)await ie(_);else if(He&&p)await gt(p);else if(Be&&p)await dt(p);else if(qe&&p)await ut(p);else if(Ke)await ft();else if(We&&p)await Ye(p,Y??"No reason provided");else if(Ue&&p)await Xe(p,Y??"No reason provided");else if(Ge&&p)await Ze(p);else if(_e)await et();else if(Ne)await st();else if(je)await it(p);else if(Me&&p&&G)await at(p,G,Re);else if(Ee&&p)await ot(p);else if(Le){const e=j?parseInt(j,10):30;await lt(e,X==="json"?"json":"markdown")}else if(Pe&&p){const e=j?parseInt(j,10):30;await rt(p,e,X==="json"?"json":"markdown")}else Oe?await ct():p?await nt(p):await tt();
75
+ `;async function re(){const e=u(process.cwd(),".sentinel.yaml");try{await K(e),console.log(`.sentinel.yaml already exists in ${process.cwd()}`);return}catch{}await R(e,ie,"utf-8"),console.log("Created .sentinel.yaml \u2014 edit then run: npm run sentinel -- --from-policy .sentinel.yaml")}async function le(e){const o=await ve(e),n=await T.fromPolicy(e);console.log(`Loaded policy for agent ${o.agent.id}. Monitoring active.`);const t=async()=>{await n.stop(),console.log("Monitoring stopped."),process.exit(0)},a=()=>{t().catch(r=>{console.error("Error during shutdown:",r),process.exit(1)})};process.on("SIGINT",a),process.on("SIGTERM",a)}async function vt(e,o,n){const t=u(m(),".dahlia","agents"),a=new T({agentsDir:t});await a.addAgent(e,e);const r=z?z.split(","):void 0,i=Y?Y.split(",").map(c=>c.trim()):void 0,d=a.startTask(e,o,n,{relaxedActions:r,phases:i});if(!d){console.log(`Failed to start task for agent ${e}.`),await a.stop();return}console.log(`Task started for agent ${e}:`),console.log(` Task ID: ${d.taskId}`),console.log(` Description: ${d.description}`),console.log(` Keywords: ${d.keywords.join(", ")}`),d.relaxedActions?.length&&console.log(` Relaxed: ${d.relaxedActions.join(", ")}`),d.phases?.length&&console.log(` Phases: ${d.phases.join(", ")}`),console.log(` TTL: ${(d.ttlMs/6e4).toFixed(0)} minutes`),await a.stop()}async function At(e){const o=u(m(),".dahlia","agents"),n=new T({agentsDir:o});await n.addAgent(e,e);const t=n.endTask(e);t?(console.log(`Task ended for agent ${e}:`),console.log(` Task ID: ${t.taskId}`),console.log(` Description: ${t.description}`),console.log(` Status: ${t.status}`)):console.log(`No active task found for agent ${e}.`),await n.stop()}async function It(e){const o=u(m(),".dahlia","agents"),n=new T({agentsDir:o});await n.addAgent(e,e);const t=n.getActiveTask(e);if(!t){console.log(`No active task for agent ${e}.`),await n.stop();return}const a=Date.now()-new Date(t.startedAt).getTime(),r=t.ttlMs-a;console.log(`Active task for agent ${e}:
76
+ `),console.log(` Task ID: ${t.taskId}`),console.log(` Description: ${t.description}`),console.log(` Keywords: ${t.keywords.join(", ")}`),console.log(` Status: ${t.status}`),console.log(` Active for: ${(a/6e4).toFixed(1)} minutes`),console.log(` TTL remain: ${r>0?(r/6e4).toFixed(1)+" minutes":"EXPIRED"}`),t.relaxedActions?.length&&console.log(` Relaxed: ${t.relaxedActions.join(", ")}`),t.phases?.length&&console.log(` Phases: ${t.phases.join(", ")}`),t.acceptableActions.length>0&&console.log(` Acceptable: ${t.acceptableActions.map(i=>`${i.action}:${i.targetPattern}`).join(", ")}`),await n.stop()}async function St(e){const o=u(m(),".dahlia","agents"),n=new A(e,{logDir:u(o,e)});await n.open();const t=await n.getStats(),a=await n.query({type:"intent_check"}),i=(await n.query({type:"finding"})).filter(c=>typeof c.data=="object"&&c.data!==null&&"type"in c.data&&c.data.type==="intent_drift");console.log(`Intent Alignment Report \u2014 ${e}
77
+ `),console.log(` Intent starts: ${t.intentStartCount}`),console.log(` Intent ends: ${t.intentEndCount}`),console.log(` Intent checks: ${t.intentCheckCount}`),console.log(` Drift findings: ${t.intentDriftCount}`),t.averageAlignmentScore!==null&&console.log(` Avg alignment: ${t.averageAlignmentScore.toFixed(2)}`);const d=a.filter(c=>{const s=c.data;return s&&typeof s=="object"&&"aligned"in s&&!s.aligned}).slice(-5);if(d.length>0){console.log(`
78
+ Last ${d.length} misaligned action(s):`);for(const c of d){const s=c.data,l=typeof s.score=="number"?s.score.toFixed(2):"?",g=s.event,h=g?.action??"?",y=g?.primaryTarget??g?.targets?.[0]??g?.target??"?";console.log(` score: ${l} ${h} \u2192 ${y} (${c.timestamp.split("T")[0]})`)}}if(i.length>0){console.log(`
79
+ Recent drift findings: ${i.length}`);for(const c of i.slice(-3)){const s=c.data,l=s.severity??"?",g=s.description??"";console.log(` [${l}] ${typeof g=="string"?g.slice(0,80):g}`)}}await n.close()}async function Dt(e,o){try{const n=await de({force:e,port:o});if(n.created.length>0){console.log("Created:");for(const t of n.created)console.log(` ${t}`)}if(n.skipped.length>0){console.log("Skipped (already exists \u2014 use --force to overwrite):");for(const t of n.skipped)console.log(` ${t}`)}if(n.merged.length>0){console.log("Merged:");for(const t of n.merged)console.log(` ${t}`)}if(n.errors.length>0){console.log("Errors:");for(const t of n.errors)console.log(` ${t}`)}n.errors.length===0&&console.log(`
80
+ Sentinel + Claude Code integration ready.`)}catch(n){console.error(`Init failed: ${n.message}`),process.exit(1)}}if(Le){const e=w(f,"--port");await Dt(qe,e?parseInt(e,10):void 0)}else if(Ye&&p&&Q&&V)await vt(p,Q,V);else if(Xe&&p)await At(p);else if(Ze&&p)await It(p);else if(et&&p)await St(p);else if(Oe)await re();else if(_)await le(_);else if(Ge&&p)await yt(p);else if(Je&&p)await ht(p);else if(We&&p)await wt(p);else if(Ue)await $t();else if(_e&&p)await tt(p,X??"No reason provided");else if(Qe&&p)await nt(p,X??"No reason provided");else if(Ve)await st(p);else if(ze)await it();else if(Pe)await gt();else if(xe)await ut(p);else if(Ne&&p&&U)await dt(p,U,ke);else if(Fe&&p)await ct(p);else if(Be){const e=j?parseInt(j,10):30;await pt(e,Z==="json"?"json":"markdown")}else if(He&&p){const e=j?parseInt(j,10):30;await ft(p,e,Z==="json"?"json":"markdown")}else Ke?await mt():p?await lt(p):await rt();
@@ -49,6 +49,12 @@ interface SentinelGatewayOptions {
49
49
  operatorCeiling?: AgentRole;
50
50
  /** Home dir bounding workspace policy discovery (B-path only). Defaults to os.homedir(). */
51
51
  home?: string;
52
+ /**
53
+ * Operator token for the loopback /release route (Sprint 0.1.1). The daemon
54
+ * generates one per start and passes it here. When unset, the /release route
55
+ * is disabled (503) — there is no unauthenticated release path.
56
+ */
57
+ releaseToken?: string;
52
58
  }
53
59
  declare class SentinelGateway {
54
60
  private readonly configuredPort;
@@ -61,6 +67,7 @@ declare class SentinelGateway {
61
67
  private readonly workspaceIsolation;
62
68
  private readonly operatorCeiling;
63
69
  private readonly home;
70
+ private readonly releaseToken;
64
71
  private server;
65
72
  private running;
66
73
  private signalHandlersInstalled;
@@ -93,6 +100,13 @@ declare class SentinelGateway {
93
100
  private removeSignalHandlers;
94
101
  private handleSignal;
95
102
  private handleRequest;
103
+ /**
104
+ * Operator release route (Sprint 0.1.1). Loopback-only + token-gated. On a
105
+ * valid request, calls sentinel.release() on the LIVE instance — flipping the
106
+ * in-memory mode, writing mode.json, and logging the signed mode_change anchor
107
+ * in-process (single writer). Never re-reads mode.json or trusts file content.
108
+ */
109
+ private handleReleaseRoute;
96
110
  /**
97
111
  * Emit a workspace-mismatch refusal as a finding + telemetry. Originally
98
112
  * Approach A's foreign-refuse emitter; after the B5b-Phase-2 cutover this is
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  SentinelGateway
3
- } from "../chunk-Z3PWIJKT.js";
3
+ } from "../chunk-IYC5E7RL.js";
4
+ import "../chunk-B5QKJHSV.js";
4
5
  import "../chunk-FMZWHT4M.js";
5
- import "../chunk-6MHWJATS.js";
6
+ import "../chunk-QHE56MEO.js";
6
7
  import "../chunk-2FFMYSVC.js";
7
8
  export {
8
9
  SentinelGateway
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runGatewayDaemon
4
- } from "./chunk-Z3PWIJKT.js";
4
+ } from "./chunk-IYC5E7RL.js";
5
+ import "./chunk-B5QKJHSV.js";
5
6
  import "./chunk-FMZWHT4M.js";
6
- import "./chunk-6MHWJATS.js";
7
+ import "./chunk-QHE56MEO.js";
7
8
  import "./chunk-2FFMYSVC.js";
8
9
 
9
10
  // src/gatewayDaemon.ts
package/dist/index.js CHANGED
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  runInitClaudeCode,
3
3
  runSessionStart
4
- } from "./chunk-3U3PKD4N.js";
4
+ } from "./chunk-WPTJBRX5.js";
5
5
  import {
6
6
  Sentinel,
7
7
  createCliApproval
8
- } from "./chunk-QFRDEISP.js";
9
- import "./chunk-CUJKNIKT.js";
8
+ } from "./chunk-NS6ZLMDK.js";
9
+ import "./chunk-LATQNIRW.js";
10
10
  import {
11
11
  discoverPolicy
12
12
  } from "./chunk-FMZWHT4M.js";
13
- import "./chunk-6MHWJATS.js";
13
+ import "./chunk-QHE56MEO.js";
14
14
  import {
15
15
  loadPolicy,
16
16
  loadPolicyFromString
@@ -0,0 +1,23 @@
1
+ import {
2
+ acquireGatewayLock,
3
+ readPidFile,
4
+ readReleaseToken,
5
+ releaseTokenPath,
6
+ removePidFile,
7
+ removeReleaseToken,
8
+ verifyPidIsGateway,
9
+ writePidFile,
10
+ writeReleaseToken
11
+ } from "./chunk-LATQNIRW.js";
12
+ export {
13
+ acquireGatewayLock,
14
+ readPidFile,
15
+ readReleaseToken,
16
+ releaseTokenPath,
17
+ removePidFile,
18
+ removeReleaseToken,
19
+ verifyPidIsGateway,
20
+ writePidFile,
21
+ writeReleaseToken
22
+ };
23
+ //# sourceMappingURL=pidManager-DOGVN6ZT.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tuent/sentinel",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "AI agent behavioral security monitoring SDK",
5
5
  "author": "Tuent LLC",
6
6
  "keywords": [
@@ -31,7 +31,8 @@
31
31
  "dist",
32
32
  "!dist/**/*.map",
33
33
  "LICENSE",
34
- "README.md"
34
+ "README.md",
35
+ "SECURITY_MODEL.md"
35
36
  ],
36
37
  "engines": {
37
38
  "node": ">=20"
@@ -1,10 +0,0 @@
1
- import {
2
- Sentinel
3
- } from "./chunk-QFRDEISP.js";
4
- import "./chunk-6MHWJATS.js";
5
- import "./chunk-2FFMYSVC.js";
6
- import "./chunk-NUXSUSYY.js";
7
- export {
8
- Sentinel
9
- };
10
- //# sourceMappingURL=Sentinel-JLQL3YRD.js.map
@@ -1,15 +0,0 @@
1
- import {
2
- acquireGatewayLock,
3
- readPidFile,
4
- removePidFile,
5
- verifyPidIsGateway,
6
- writePidFile
7
- } from "./chunk-CUJKNIKT.js";
8
- export {
9
- acquireGatewayLock,
10
- readPidFile,
11
- removePidFile,
12
- verifyPidIsGateway,
13
- writePidFile
14
- };
15
- //# sourceMappingURL=pidManager-ZYC7SICM.js.map