@shieldly/cli 1.0.2 → 1.0.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.
Files changed (3) hide show
  1. package/README.md +5 -3
  2. package/dist/cli.cjs +73 -46
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -12,14 +12,16 @@ npm install -g @shieldly/cli
12
12
 
13
13
  ## Try it free — no account needed
14
14
 
15
- `analyze-iam` runs in demo mode without an API key (5 lifetime analyses):
15
+ Both `analyze-iam` and `analyze-cf` run in demo mode without an API key
16
+ (5 free analyses, no signup required):
16
17
 
17
18
  ```bash
18
19
  shieldly analyze-iam policy.json
20
+ shieldly analyze-cf template.yaml
19
21
  ```
20
22
 
21
- To unlock `analyze-cf`, history, and 20 free analyses per day,
22
- [create a free account](https://www.shieldly.io/app/api) and set your key:
23
+ For higher limits and analysis history, create an account
24
+ ([API keys require a Builder plan or above](https://www.shieldly.io/app/api)) and set your key:
23
25
 
24
26
  ```bash
25
27
  export SHIELDLY_API_KEY=sk_live_...
package/dist/cli.cjs CHANGED
@@ -1,31 +1,55 @@
1
1
  #!/usr/bin/env node
2
- var B=Object.create;var C=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var Z=Object.getPrototypeOf,Q=Object.prototype.hasOwnProperty;var X=(e,o,t,n)=>{if(o&&typeof o=="object"||typeof o=="function")for(let s of V(o))!Q.call(e,s)&&s!==t&&C(e,s,{get:()=>o[s],enumerable:!(n=q(o,s))||n.enumerable});return e};var z=(e,o,t)=>(t=e!=null?B(Z(e)):{},X(o||!e||!e.__esModule?C(t,"default",{value:e,enumerable:!0}):t,e));var g=require("node:fs"),j=require("node:os"),D=require("node:path"),w="Shieldly-CLI/1.0.2",O=(0,D.join)((0,j.homedir)(),".shieldly","config.json"),ee="https://api.shieldly.io",oe="https://www.shieldly.io";function I(e){if(e)return e;if(process.env.SHIELDLY_API_KEY)return process.env.SHIELDLY_API_KEY;if((0,g.existsSync)(O))try{let o=JSON.parse((0,g.readFileSync)(O,"utf8"));if(o.apiKey)return o.apiKey}catch{}return null}function L(){return(process.env.SHIELDLY_API_URL||ee).replace(/\/$/,"")}function se(){return(process.env.SHIELDLY_WEB_URL||oe).replace(/\/$/,"")}async function R(e,o){let t=se(),n=await fetch(`${t}${e}`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":w},body:JSON.stringify(o)});if(!n.ok){let s=await n.json().catch(()=>({}));throw new Error(s.error||`API error ${n.status}`)}return n.json()}async function k(e,o,t){let n=L(),s=await fetch(`${n}${e}`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":w,Authorization:`Bearer ${t}`},body:JSON.stringify(o)});if(s.status===202){let r=await s.json().catch(()=>({}));if(r.jobId)return te(r.jobId,t)}if(!s.ok){let r=await s.json().catch(()=>({}));throw new Error(r.error||`API error ${s.status}`)}return s.json()}async function te(e,o){let t=[2e3,3e3,5e3],n=Date.now(),s=0;for(let r=0;r<180;r++){let a=t[Math.min(r,t.length-1)];await new Promise(c=>setTimeout(c,a));let l=Math.round((Date.now()-n)/1e3);process.stderr.write(`\rAI-Powered analysis in progress\u2026 (${l}s)`);let i;try{i=await v(`/v1/jobs/${encodeURIComponent(e)}`,o),s=0}catch(c){if(++s>=3)throw process.stderr.write(`
3
- `),c;continue}if(i.status==="complete")return process.stderr.write(`
4
- `),{...i.result,unitInfo:i.unitInfo};if(i.status==="failed")throw process.stderr.write(`
5
- `),new Error(i.error||"Analysis failed")}throw process.stderr.write(`
6
- `),new Error("Analysis timed out after polling")}async function v(e,o){let t=L(),n=await fetch(`${t}${e}`,{headers:{Authorization:`Bearer ${o}`,"User-Agent":w}});if(!n.ok){let s=await n.json().catch(()=>({}));throw new Error(s.error||`API error ${n.status}`)}return n.json()}async function M(e,o,t){let n=L(),s=await fetch(`${n}${e}`,{method:"DELETE",headers:{"Content-Type":"application/json","User-Agent":w,Authorization:`Bearer ${t}`},body:JSON.stringify(o)});if(!s.ok){let r=await s.json().catch(()=>({}));throw new Error(r.error||`API error ${s.status}`)}return s.json()}function P(e){return(0,g.existsSync)(e)||(console.error(`Error: File not found: ${e}`),process.exit(1)),(0,g.readFileSync)(e,"utf8")}var K={CRITICAL:"\x1B[31m",HIGH:"\x1B[33m",MEDIUM:"\x1B[36m",LOW:"\x1B[32m",INFO:"\x1B[90m"},y="\x1B[0m",A="\x1B[1m",_="\x1B[2m",Y="\x1B[36m";function x(e,o){if(o==="json"){console.log(JSON.stringify(e,null,2));return}let{score:t,riskLevel:n,findings:s=[],cached:r}=e;if(console.log(""),console.log(`${A}AI-Powered Security Analysis \u2014 Shieldly${y}`),console.log(`${_}${"\u2500".repeat(50)}${y}`),console.log(` ${A}Security Score:${y} ${ne(t)}${t}/100${y}`),console.log(` ${A}Risk Level:${y} ${le(n)}${n}${y}`),r&&console.log(` ${_}(cached result)${y}`),console.log(""),s.length===0)console.log(` ${Y}[PASS] No findings${y}`);else{console.log(`${A}Findings (${s.length}):${y}`);for(let a of s){let l=K[(a.severity||"").toUpperCase()]||"";console.log(`
7
- ${l}[${a.severity}]${y} ${A}${a.title}${y}`),a.description&&console.log(` ${_}${a.description}${y}`),a.remediation&&console.log(` ${Y}Fix:${y} ${a.remediation}`)}}console.log("")}function ne(e){return e>=80?"\x1B[32m":e>=50?"\x1B[33m":"\x1B[31m"}function le(e){let o=(e||"").toUpperCase();return K[o]||""}var re=`
8
- Usage: shieldly analyze-cf <template-file> [options]
2
+ var ne=Object.create;var G=Object.defineProperty;var re=Object.getOwnPropertyDescriptor;var ie=Object.getOwnPropertyNames;var le=Object.getPrototypeOf,ae=Object.prototype.hasOwnProperty;var ce=(e,s,o,n)=>{if(s&&typeof s=="object"||typeof s=="function")for(let t of ie(s))!ae.call(e,t)&&t!==o&&G(e,t,{get:()=>s[t],enumerable:!(n=re(s,t))||n.enumerable});return e};var W=(e,s,o)=>(o=e!=null?ne(le(e)):{},ce(s||!e||!e.__esModule?G(o,"default",{value:e,enumerable:!0}):o,e));var k=require("node:fs"),Y=require("node:path");var $=require("node:fs"),J=require("node:os"),z=require("node:path"),O="Shieldly-CLI/1.0.4",U=(0,z.join)((0,J.homedir)(),".shieldly","config.json"),pe="https://api.shieldly.io",ye="https://www.shieldly.io",H=5;function K(){try{return JSON.parse((0,$.readFileSync)(U,"utf8"))}catch{return{}}}function de(e){try{(0,$.mkdirSync)((0,z.dirname)(U),{recursive:!0}),(0,$.writeFileSync)(U,`${JSON.stringify(e,null,2)}
3
+ `)}catch{}}function S(e){return e||(process.env.SHIELDLY_API_KEY?process.env.SHIELDLY_API_KEY:K().apiKey||null)}function q(){let e=K().demoCount;return typeof e=="number"&&e>0?e:0}function _(){return q()>=H}function j(){let e=K();return e.demoCount=q()+1,de(e),Math.max(0,H-e.demoCount)}function D(){return`You've used all ${H} free demo analyses.
9
4
 
10
- Analyze a CloudFormation template for security issues using AI.
5
+ Get an API key (Builder plan or above) for higher limits: https://www.shieldly.io/app/api
6
+
7
+ Then set SHIELDLY_API_KEY or pass --api-key.`}function N(){return(process.env.SHIELDLY_API_URL||pe).replace(/\/$/,"")}function me(){return(process.env.SHIELDLY_WEB_URL||ye).replace(/\/$/,"")}async function P(e,s){let o=me(),n=await fetch(`${o}${e}`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":O},body:JSON.stringify(s)});if(n.status===429)throw new Error("Demo rate limit reached. Get an API key (Builder plan or above) for higher limits: https://www.shieldly.io/app/api");if(!n.ok){let t=await n.json().catch(()=>({}));throw new Error(t.error||`API error ${n.status}`)}return n.json()}async function A(e,s,o){let n=N(),t=await fetch(`${n}${e}`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":O,Authorization:`Bearer ${o}`},body:JSON.stringify(s)});if(t.status===202){let i=await t.json().catch(()=>({}));if(i.jobId)return ue(i.jobId,o);throw new Error("Analysis queued but no job ID returned \u2014 try again")}if(!t.ok){let i=await t.json().catch(()=>({}));throw new Error(i.error||`API error ${t.status}`)}return t.json()}async function ue(e,s){let o=[2e3,3e3,5e3],n=Date.now(),t=0;for(let i=0;i<180;i++){let l=o[Math.min(i,o.length-1)];await new Promise(r=>setTimeout(r,l));let y=Math.round((Date.now()-n)/1e3);process.stderr.write(`\rAI-Powered analysis in progress\u2026 (${y}s)`);let a;try{a=await T(`/v1/jobs/${encodeURIComponent(e)}`,s),t=0}catch(r){if(++t>=3)throw process.stderr.write(`
8
+ `),r;continue}if(a.status==="complete")return process.stderr.write(`
9
+ `),{...a.result,unitInfo:a.unitInfo};if(a.status==="failed")throw process.stderr.write(`
10
+ `),new Error(a.error||"Analysis failed")}throw process.stderr.write(`
11
+ `),new Error("Analysis timed out after polling")}async function T(e,s){let o=N(),n=await fetch(`${o}${e}`,{headers:{Authorization:`Bearer ${s}`,"User-Agent":O}});if(!n.ok){let t=await n.json().catch(()=>({}));throw new Error(t.error||`API error ${n.status}`)}return n.json()}async function V(e,s,o){let n=N(),t=await fetch(`${n}${e}`,{method:"DELETE",headers:{"Content-Type":"application/json","User-Agent":O,Authorization:`Bearer ${o}`},body:JSON.stringify(s)});if(!t.ok){let i=await t.json().catch(()=>({}));throw new Error(i.error||`API error ${t.status}`)}return t.json()}function R(e){return(0,$.existsSync)(e)||(console.error(`Error: File not found: ${e}`),process.exit(1)),(0,$.statSync)(e).isDirectory()&&(console.error(`Error: ${e} is a directory, not a file.
12
+ For a CDK output directory, use: shieldly analyze-cf ${e}`),process.exit(1)),(0,$.readFileSync)(e,"utf8")}var F={CRITICAL:"\x1B[31m",HIGH:"\x1B[33m",MEDIUM:"\x1B[36m",LOW:"\x1B[32m",INFO:"\x1B[90m"},d="\x1B[0m",E="\x1B[1m",w="\x1B[2m",B="\x1B[36m";function v(e,s){if(s==="json"){console.log(JSON.stringify(e,null,2));return}let{score:o,riskLevel:n,findings:t=[],cached:i,summary:l,positives:y=[],unitInfo:a}=e,r=o==null?"\u2014":`${o}/100`;if(console.log(""),console.log(`${E}AI-Powered Security Analysis \u2014 Shieldly${d}`),console.log(`${w}${"\u2500".repeat(50)}${d}`),console.log(` ${E}Security Score:${d} ${fe(o)}${r}${d}`),console.log(` ${E}Risk Level:${d} ${he(n)}${n||"Unknown"}${d}`),i&&console.log(` ${w}(cached result)${d}`),l&&(console.log(""),console.log(` ${w}${l}${d}`)),console.log(""),y.length>0){console.log(`${E}What's good:${d}`);for(let c of y)console.log(` ${F.LOW}[+]${d} ${w}${c}${d}`);console.log("")}if(t.length===0)console.log(` ${B}[PASS] No findings${d}`);else{console.log(`${E}Findings (${t.length}):${d}`);for(let c of t){let u=F[(c.severity||"").toUpperCase()]||"";console.log(`
13
+ ${u}[${c.severity}]${d} ${E}${c.title}${d}`),c.resource&&c.resource!=="*"&&console.log(` ${w}Resource: ${c.resource}${d}`),c.description&&console.log(` ${w}${c.description}${d}`),c.remediation&&console.log(` ${B}Fix:${d} ${c.remediation}`)}}a&&typeof a.unitsUsed=="number"&&typeof a.cap=="number"&&(console.log(""),console.log(` ${w}Units used: ${a.unitsUsed}/${a.cap}${d}`)),e.demoInfo&&typeof e.demoInfo.analysesRemaining=="number"&&(console.log(""),console.log(` ${w}Demo analyses remaining: ${e.demoInfo.analysesRemaining}. Get an API key (Builder plan or above) for more: https://www.shieldly.io/app/api${d}`)),console.log("")}function fe(e){return e==null?"":e>=80?"\x1B[32m":e>=50?"\x1B[33m":"\x1B[31m"}function he(e){let s=(e||"").toUpperCase();return F[s]||""}var $e=`
14
+ Usage: shieldly analyze-cf <template-file-or-dir> [options]
15
+
16
+ Analyze a CloudFormation template (or directory of templates) for security issues using AI.
11
17
 
12
18
  Arguments:
13
- template-file Path to a JSON or YAML CloudFormation template
19
+ template-file-or-dir Path to a JSON CF template, or a directory (e.g. cdk.out/)
20
+ containing synthesized stacks. All *.template.json files are
21
+ analyzed automatically.
14
22
 
15
23
  Options:
16
24
  --format <fmt> Output format: table | json (default: table)
17
25
  --api-key <key> API key (or set SHIELDLY_API_KEY env var)
18
26
  -h, --help Show this help
19
27
 
28
+ Authentication:
29
+ No key needed for demo mode (rate-limited). Set SHIELDLY_API_KEY for full access.
30
+ Get an API key (Builder plan or above): https://www.shieldly.io/app/api
31
+
20
32
  Examples:
21
33
  shieldly analyze-cf template.json
22
- shieldly analyze-cf template.yaml --format json
23
- `;async function H(e){if(e.includes("-h")||e.includes("--help")||e.length===0){console.log(re);return}let o=e.find(l=>!l.startsWith("--"));o||(console.error("Error: template-file argument is required"),process.exit(1));let t=e.indexOf("--format"),n=t!==-1?e[t+1]:"table",s=e.indexOf("--api-key"),r=I(s!==-1?e[s+1]:null);r||(console.error(`CloudFormation analysis requires an API key.
24
-
25
- Free account: 20 analyses/day \u2014 https://www.shieldly.io/app/api
26
-
27
- Set SHIELDLY_API_KEY or use --api-key once you have your key.
28
- Tip: shieldly analyze-iam runs without a key (5 free demo analyses).`),process.exit(1));let a=P(o);n!=="json"&&console.log(`Analyzing ${o}\u2026`);try{let l=await k("/v1/analyze/cf",{template:a},r);x(l,n);let i=(l.findings||[]).filter(h=>h.severity==="CRITICAL").length,c=(l.findings||[]).filter(h=>h.severity==="HIGH").length;(i>0||c>0)&&process.exit(1)}catch(l){console.error(`Error: ${l.message}`),process.exit(1)}}var ie=`
34
+ shieldly analyze-cf cdk.out/
35
+ shieldly analyze-cf cdk.out/ --format json
36
+
37
+ CDK integration (add to package.json scripts):
38
+ "synth:check": "cdk synth && shieldly analyze-cf cdk.out/"
39
+
40
+ cdk.json hook (runs after every cdk synth):
41
+ {
42
+ "hooks": {
43
+ "afterSynth": ["sh", "-c", "shieldly analyze-cf cdk.out/ || true"]
44
+ }
45
+ }
46
+ `;function ge(e){try{let s=(0,k.readFileSync)((0,Y.join)(e,"manifest.json"),"utf8"),o=JSON.parse(s);if(!o.artifacts||typeof o.artifacts!="object")return null;let n=Object.values(o.artifacts).filter(t=>t.type==="aws:cloudformation:stack"&&t.properties?.templateFile).map(t=>(0,Y.join)(e,t.properties.templateFile)).filter(t=>(0,k.existsSync)(t));return n.length>0?n:null}catch{return null}}function Ie(e){let s=ge(e);if(s)return s;let o=[],n;try{n=(0,k.readdirSync)(e,{withFileTypes:!0})}catch{return o}for(let t of n){if(!t.isFile())continue;let i=t.name;(i.endsWith(".template.json")||i.endsWith(".template.yaml"))&&o.push((0,Y.join)(e,i))}return o}async function ke(e,s,o,n,t){let i=(0,k.readFileSync)(e,"utf8");o!=="json"&&t>1?process.stdout.write(`[${n}/${t}] Analyzing ${e}\u2026
47
+ `):o!=="json"&&process.stdout.write(`Analyzing ${e}\u2026
48
+ `);let l=s?await A("/v1/analyze/cf",{template:i},s):await P("/api/demo/analyze-iam",{template:i,policyType:"cf"});return!s&&!l.cached&&j(),{filePath:e,data:l}}async function Z(e){if(e.includes("-h")||e.includes("--help")||e.length===0){console.log($e);return}let s=e.indexOf("--format"),o=s!==-1?e[s+1]:"table";s!==-1&&!["table","json"].includes(o)&&(console.error(`Error: invalid --format "${o}". Use: table | json`),process.exit(1));let n=e.indexOf("--api-key"),t=S(n!==-1?e[n+1]:null),i=new Set;for(let r of[s,n])r!==-1&&(i.add(r),i.add(r+1));let l=e.find((r,c)=>!i.has(c)&&!r.startsWith("--"));l||(console.error("Error: template-file-or-dir argument is required"),process.exit(1)),!t&&_()&&(console.error(D()),process.exit(1)),!t&&o!=="json"&&console.log("Demo mode (rate-limited, no signup required). Get an API key (Builder plan or above) for higher limits: https://www.shieldly.io/app/api");let y=(0,k.statSync)(l,{throwIfNoEntry:!1});if(y||(console.error(`Error: path not found: ${l}`),process.exit(1)),y.isDirectory()){let r=Ie(l);r.length===0&&(console.error(`Error: no CloudFormation templates (*.template.json / *.template.yaml) found in ${l}
49
+ Run "cdk synth" first to generate stack templates.`),process.exit(1)),o!=="json"&&console.log(`Found ${r.length} stack template(s) in ${l}
50
+ `);let c=[],u=0,p=0;for(let f=0;f<r.length;f++){if(!t&&_()){o!=="json"&&console.error(`
51
+ Demo allowance reached \u2014 remaining stacks skipped. Get an API key (Builder plan or above): https://www.shieldly.io/app/api`);break}try{let{filePath:g,data:I}=await ke(r[f],t,o,f+1,r.length);c.push({filePath:g,data:I}),u+=(I.findings||[]).filter(M=>M.severity?.toUpperCase()==="CRITICAL").length,p+=(I.findings||[]).filter(M=>M.severity?.toUpperCase()==="HIGH").length,o!=="json"&&v(I,o)}catch(g){if(console.error(`Error analyzing ${r[f]}: ${g.message}`),!t&&/rate limit/i.test(g.message))break}}if(o==="json")console.log(JSON.stringify(c.map(({filePath:f,data:g})=>({stack:f,...g})),null,2));else if(r.length>1){let f=c.reduce((g,I)=>g+(I.data.findings||[]).length,0);console.log(`
52
+ Summary: ${r.length} stacks \xB7 ${f} total findings \xB7 ${u} critical \xB7 ${p} high`)}(u>0||p>0)&&process.exit(1);return}let a=R(l);o!=="json"&&console.log(`Analyzing ${l}\u2026`);try{let r=t?await A("/v1/analyze/cf",{template:a},t):await P("/api/demo/analyze-iam",{template:a,policyType:"cf"});v(r,o);let c=(r.findings||[]).filter(p=>p.severity?.toUpperCase()==="CRITICAL").length,u=(r.findings||[]).filter(p=>p.severity?.toUpperCase()==="HIGH").length;(c>0||u>0)&&process.exit(1)}catch(r){console.error(`Error: ${r.message}`),process.exit(1)}}var we=`
29
53
  Usage: shieldly analyze-iam <policy-file> [options]
30
54
 
31
55
  Analyze an AWS IAM policy for security issues using AI.
@@ -43,9 +67,9 @@ Options:
43
67
 
44
68
  Examples:
45
69
  shieldly analyze-iam policy.json
46
- shieldly analyze-iam policy.json --type resource --format json
70
+ shieldly analyze-iam policy.json --type cross_account --format json
47
71
  SHIELDLY_API_KEY=sk_... shieldly analyze-iam policy.json
48
- `;async function F(e){if(e.includes("-h")||e.includes("--help")||e.length===0){console.log(ie);return}let o=e.find(u=>!u.startsWith("--"));o||(console.error("Error: policy-file argument is required"),process.exit(1));let t=e.indexOf("--type"),n=t!==-1?e[t+1]:"identity",s=e.indexOf("--format"),r=s!==-1?e[s+1]:"table",a=e.indexOf("--api-key"),l=I(a!==-1?e[a+1]:null),i=P(o);try{JSON.parse(i)}catch{console.error("Error: policy-file must be valid JSON"),process.exit(1)}let c=i.trim(),h=n==="identity"||n==="iam_identity"?"iam_identity":n==="cross_account"?"cross_account":"iam_identity";r!=="json"&&(console.log(`Analyzing ${o} (type: ${h})\u2026`),l||console.log("Demo mode (5 lifetime analyses). Get a free key at https://www.shieldly.io/app/api"));try{let u=l?await k("/v1/analyze/iam",{policy:c,policyType:h},l):await R("/api/demo/analyze-iam",{policy:c,policyType:h});x(u,r);let d=(u.findings||[]).filter(S=>S.severity==="CRITICAL").length,J=(u.findings||[]).filter(S=>S.severity==="HIGH").length;(d>0||J>0)&&process.exit(1)}catch(u){console.error(`Error: ${u.message}`),process.exit(1)}}var U=`
72
+ `;async function Q(e){if(e.includes("-h")||e.includes("--help")||e.length===0){console.log(we);return}let s=e.indexOf("--type"),o=s!==-1?e[s+1]:"identity",n=e.indexOf("--format"),t=n!==-1?e[n+1]:"table";s!==-1&&!["identity","iam_identity","cross_account"].includes(o)&&(console.error(`Error: invalid --type "${o}". Use: identity | cross_account`),process.exit(1)),n!==-1&&!["table","json"].includes(t)&&(console.error(`Error: invalid --format "${t}". Use: table | json`),process.exit(1));let i=e.indexOf("--api-key"),l=S(i!==-1?e[i+1]:null);!l&&_()&&(console.error(D()),process.exit(1));let y=new Set;for(let p of[s,n,i])p!==-1&&(y.add(p),y.add(p+1));let a=e.find((p,f)=>!y.has(f)&&!p.startsWith("--"));a||(console.error("Error: policy-file argument is required"),process.exit(1));let r=R(a);try{JSON.parse(r)}catch{console.error("Error: policy-file must be valid JSON"),process.exit(1)}let c=r.trim(),u=o==="identity"||o==="iam_identity"?"iam_identity":o==="cross_account"?"cross_account":"iam_identity";t!=="json"&&(console.log(`Analyzing ${a} (type: ${u})\u2026`),l||console.log("Demo mode (rate-limited, no signup required). Get an API key (Builder plan or above) for higher limits: https://www.shieldly.io/app/api"));try{let p=l?await A("/v1/analyze/iam",{policy:c,policyType:u},l):await P("/api/demo/analyze-iam",{policy:c,policyType:u});!l&&!p.cached&&j(),v(p,t);let f=(p.findings||[]).filter(I=>I.severity?.toUpperCase()==="CRITICAL").length,g=(p.findings||[]).filter(I=>I.severity?.toUpperCase()==="HIGH").length;(f>0||g>0)&&process.exit(1)}catch(p){console.error(`Error: ${p.message}`),process.exit(1)}}var X=`
49
73
  Usage: shieldly api-keys <subcommand> [options]
50
74
 
51
75
  Manage Shieldly API keys.
@@ -65,12 +89,12 @@ Examples:
65
89
  shieldly api-keys list
66
90
  shieldly api-keys create --label "CI/CD Key" --scopes iam,cf
67
91
  shieldly api-keys revoke key_abc123
68
- `,f="\x1B[1m",N="\x1B[2m",T="\x1B[36m",m="\x1B[0m";function ae(e){return e?new Date(e).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"}):"\u2014"}async function W(e){if(e.includes("-h")||e.includes("--help")||e.length===0){console.log(U);return}let o=e[0],t=e.slice(1),n=t.indexOf("--api-key"),s=I(n!==-1?t[n+1]:null),r=t.indexOf("--format"),a=r!==-1?t[r+1]:"table";if(s||(console.error(`API key management requires an API key to authenticate.
92
+ `,b="\x1B[1m",ee="\x1B[2m",oe="\x1B[36m",h="\x1B[0m";function be(e){return e?new Date(e).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"}):"\u2014"}async function te(e){if(e.includes("-h")||e.includes("--help")||e.length===0){console.log(X);return}let s=e[0],o=e.slice(1),n=o.indexOf("--api-key"),t=S(n!==-1?o[n+1]:null),i=o.indexOf("--format"),l=i!==-1?o[i+1]:"table";if(i!==-1&&!["table","json"].includes(l)&&(console.error(`Error: invalid --format "${l}". Use: table | json`),process.exit(1)),t||(console.error(`API key management requires an API key to authenticate.
69
93
 
70
- Create your free account: https://www.shieldly.io/app/api
94
+ Get an API key (Builder plan or above): https://www.shieldly.io/app/api
71
95
 
72
- Set SHIELDLY_API_KEY or use --api-key once you have your key.`),process.exit(1)),o==="list"){try{let i=(await v("/v1/api-keys",s)).keys||[];if(a==="json"){console.log(JSON.stringify(i,null,2));return}if(i.length===0){console.log("No API keys found. Create one at https://www.shieldly.io/app/api");return}console.log(""),console.log(`${f}API Keys (${i.length}):${m}`),console.log(`${N}${"\u2500".repeat(60)}${m}`);for(let c of i){let h=(c.scopes||[]).join(", ")||"all";console.log(` ${T}${c.keyId}${m}`),console.log(` ${f}Label:${m} ${c.label||"(unlabeled)"}`),console.log(` ${f}Scopes:${m} ${h}`),console.log(` ${f}Uses:${m} ${c.usageCount||0}`),console.log(` ${f}Created:${m} ${ae(c.createdAt)}`),console.log("")}}catch(l){console.error(`Error: ${l.message}`),process.exit(1)}return}if(o==="create"){let l=t.indexOf("--label"),i=l!==-1?t[l+1]:"CLI Key",c=t.indexOf("--scopes"),u=(c!==-1?t[c+1]:"iam,cf").split(",").map(d=>d.trim()).filter(Boolean);try{let d=await k("/v1/api-keys",{label:i,scopes:u},s);if(a==="json"){console.log(JSON.stringify(d,null,2));return}console.log(""),console.log(`${f}[OK] API key created${m}`),console.log(` ${f}Key ID:${m} ${d.keyId}`),console.log(` ${f}API Key:${m} ${T}${d.apiKey}${m}`),console.log(` ${N}Store this key securely \u2014 it won't be shown again.${m}`),console.log("")}catch(d){console.error(`Error: ${d.message}`),process.exit(1)}return}if(o==="revoke"){let l=t.find(i=>!i.startsWith("--")&&i!=="revoke");l||(console.error(`Error: key-id argument is required
73
- Usage: shieldly api-keys revoke <key-id>`),process.exit(1));try{if(await M("/v1/api-keys",{keyId:l},s),a==="json"){console.log(JSON.stringify({success:!0,keyId:l}));return}console.log(`[OK] API key ${l} revoked`)}catch(i){console.error(`Error: ${i.message}`),process.exit(1)}return}console.error(`Unknown subcommand: ${o}`),console.log(U),process.exit(1)}var ce=`
96
+ Set SHIELDLY_API_KEY or use --api-key once you have your key.`),process.exit(1)),s==="list"){try{let a=(await T("/v1/api-keys",t)).keys||[];if(l==="json"){console.log(JSON.stringify(a,null,2));return}if(a.length===0){console.log("No API keys found. Create one at https://www.shieldly.io/app/api");return}console.log(""),console.log(`${b}API Keys (${a.length}):${h}`),console.log(`${ee}${"\u2500".repeat(60)}${h}`);for(let r of a){let c=(r.scopes||[]).join(", ")||"all";console.log(` ${oe}${r.keyId}${h}`),console.log(` ${b}Label:${h} ${r.label||"(unlabeled)"}`),console.log(` ${b}Scopes:${h} ${c}`),console.log(` ${b}Uses:${h} ${r.usageCount||0}`),console.log(` ${b}Created:${h} ${be(r.createdAt)}`),console.log("")}}catch(y){console.error(`Error: ${y.message}`),process.exit(1)}return}if(s==="create"){let y=o.indexOf("--label"),a=y!==-1?o[y+1]:"CLI Key",r=o.indexOf("--scopes"),u=(r!==-1?o[r+1]:"iam,cf").split(",").map(p=>p.trim()).filter(Boolean);try{let p=await A("/v1/api-keys",{label:a,scopes:u},t);if(l==="json"){console.log(JSON.stringify(p,null,2));return}console.log(""),console.log(`${b}[OK] API key created${h}`),console.log(` ${b}Key ID:${h} ${p.keyId}`),console.log(` ${b}API Key:${h} ${oe}${p.apiKey}${h}`),console.log(` ${ee}Store this key securely \u2014 it won't be shown again.${h}`),console.log("")}catch(p){console.error(`Error: ${p.message}`),process.exit(1)}return}if(s==="revoke"){let y=new Set;n!==-1&&(y.add(n),y.add(n+1)),i!==-1&&(y.add(i),y.add(i+1));let a=o.find((r,c)=>!y.has(c)&&!r.startsWith("--"));a||(console.error(`Error: key-id argument is required
97
+ Usage: shieldly api-keys revoke <key-id>`),process.exit(1));try{if(await V("/v1/api-keys",{keyId:a},t),l==="json"){console.log(JSON.stringify({success:!0,keyId:a}));return}console.log(`[OK] API key ${a} revoked`)}catch(r){console.error(`Error: ${r.message}`),process.exit(1)}return}console.error(`Unknown subcommand: ${s}`),console.log(X),process.exit(1)}var Ae=`
74
98
  _shieldly() {
75
99
  local cur prev words cword
76
100
  _init_completion || return
@@ -104,7 +128,7 @@ _shieldly() {
104
128
  return
105
129
  ;;
106
130
  --type)
107
- COMPREPLY=($(compgen -W "identity resource trust s3 sqs kms sns cross_account" -- "$cur"))
131
+ COMPREPLY=($(compgen -W "identity cross_account" -- "$cur"))
108
132
  return
109
133
  ;;
110
134
  --format)
@@ -130,7 +154,7 @@ _shieldly() {
130
154
  fi
131
155
  } &&
132
156
  complete -F _shieldly shieldly
133
- `,pe=`
157
+ `,xe=`
134
158
  #compdef shieldly
135
159
 
136
160
  _shieldly() {
@@ -152,7 +176,7 @@ _shieldly() {
152
176
  args)
153
177
  case $words[1] in
154
178
  analyze-iam)
155
- _arguments '--type[Policy type]:type:(identity resource trust s3 sqs kms sns cross_account)' '--format[Output format]:format:(table json)' '--api-key[API key]:api key' '(-h --help)'{-h,--help}'[Show help]' '*:policy file:_files' && ret=0
179
+ _arguments '--type[Policy type]:type:(identity cross_account)' '--format[Output format]:format:(table json)' '--api-key[API key]:api key' '(-h --help)'{-h,--help}'[Show help]' '*:policy file:_files' && ret=0
156
180
  ;;
157
181
  analyze-cf)
158
182
  _arguments '--format[Output format]:format:(table json)' '--api-key[API key]:api key' '(-h --help)'{-h,--help}'[Show help]' '*:template file:_files' && ret=0
@@ -201,7 +225,7 @@ _shieldly() {
201
225
  }
202
226
 
203
227
  _shieldly
204
- `,ye=`
228
+ `,Ee=`
205
229
  Usage: shieldly completion <shell>
206
230
 
207
231
  Generate shell completion scripts for the shieldly CLI.
@@ -215,42 +239,45 @@ Examples:
215
239
  eval "$(shieldly completion bash)" # Source bash completion
216
240
  shieldly completion zsh > /usr/local/share/zsh/site-functions/_shieldly # Install zsh completion
217
241
  shieldly completion install # Auto-detect and install
218
- `;async function G(e){if(!e.length||e.includes("-h")||e.includes("--help")){console.log(ye);return}let o=e[0];switch(o){case"bash":console.log(ce.trimStart());break;case"zsh":console.log(pe.trimStart());break;case"install":await me();break;default:console.error(`Unsupported shell: ${o}`),console.log("Supported shells: bash, zsh"),process.exit(1)}}async function me(){let e=process.env.SHELL||"",o=process.env.HOME||"";if(e.includes("zsh")){let t=`${o}/.zshrc`,n=`
242
+ `;async function se(e){if(!e.length||e.includes("-h")||e.includes("--help")){console.log(Ee);return}let s=e[0];switch(s){case"bash":console.log(Ae.trimStart());break;case"zsh":console.log(xe.trimStart());break;case"install":await Se();break;default:console.error(`Unsupported shell: ${s}`),console.log("Supported shells: bash, zsh"),process.exit(1)}}async function Se(){let e=process.env.SHELL||"",s=process.env.HOME||"";if(e.includes("zsh")){let o=`${s}/.zshrc`,n=`
219
243
  # shieldly CLI completion
220
244
  autoload -Uz compinit && compinit -C 2>/dev/null
221
- eval "$(shieldly completion zsh)" 2>/dev/null`;try{let{readFileSync:s,appendFileSync:r,existsSync:a}=await import("node:fs");if(a(t)&&s(t,"utf8").includes("shieldly completion")){console.log("shieldly completion already installed in ~/.zshrc");return}r(t,n),console.log("Installed shieldly completion in ~/.zshrc"),console.log("Run: source ~/.zshrc")}catch(s){console.error(`Failed to install: ${s.message}`),process.exit(1)}}else if(e.includes("bash")){let t=`${o}/.bashrc`,n=`
245
+ eval "$(shieldly completion zsh)" 2>/dev/null`;try{let{readFileSync:t,appendFileSync:i,existsSync:l}=await import("node:fs");if(l(o)&&t(o,"utf8").includes("shieldly completion")){console.log("shieldly completion already installed in ~/.zshrc");return}i(o,n),console.log("Installed shieldly completion in ~/.zshrc"),console.log("Run: source ~/.zshrc")}catch(t){console.error(`Failed to install: ${t.message}`),process.exit(1)}}else if(e.includes("bash")){let o=`${s}/.bashrc`,n=`
222
246
  # shieldly CLI completion
223
- source /dev/stdin <<< "$(shieldly completion bash)" 2>/dev/null`;try{let{readFileSync:s,appendFileSync:r,existsSync:a}=await import("node:fs");if(a(t)&&s(t,"utf8").includes("shieldly completion")){console.log("shieldly completion already installed in ~/.bashrc");return}r(t,n),console.log("Installed shieldly completion in ~/.bashrc"),console.log("Run: source ~/.bashrc")}catch(s){console.error(`Failed to install: ${s.message}`),process.exit(1)}}else console.error(`Unsupported shell: ${e}. Install manually:`),console.log(' eval "$(shieldly completion bash)" # For bash'),console.log(' eval "$(shieldly completion zsh)" # For zsh'),process.exit(1)}var $="\x1B[1m",b="\x1B[36m",E="\x1B[2m",p="\x1B[0m",he="1.0.0",ue=`
224
- ${$}shieldly${p} \u2014 AI-Powered Security Analysis for AWS
247
+ source /dev/stdin <<< "$(shieldly completion bash)" 2>/dev/null`;try{let{readFileSync:t,appendFileSync:i,existsSync:l}=await import("node:fs");if(l(o)&&t(o,"utf8").includes("shieldly completion")){console.log("shieldly completion already installed in ~/.bashrc");return}i(o,n),console.log("Installed shieldly completion in ~/.bashrc"),console.log("Run: source ~/.bashrc")}catch(t){console.error(`Failed to install: ${t.message}`),process.exit(1)}}else console.error(`Unsupported shell: ${e}. Install manually:`),console.log(' eval "$(shieldly completion bash)" # For bash'),console.log(' eval "$(shieldly completion zsh)" # For zsh'),process.exit(1)}var x="\x1B[1m",C="\x1B[36m",L="\x1B[2m",m="\x1B[0m",_e="1.0.4",Pe=`
248
+ ${x}shieldly${m} \u2014 AI-Powered Security Analysis for AWS
225
249
 
226
- ${$}Usage:${p}
250
+ ${x}Usage:${m}
227
251
  shieldly <command> [args] [options]
228
252
 
229
- ${$}Commands:${p}
230
- ${b}analyze-iam${p} <policy-file> Analyze an IAM policy for security issues
231
- ${b}analyze-cf${p} <template-file> Analyze a CloudFormation template
232
- ${b}api-keys${p} list|create|revoke Manage API keys
233
- ${b}completion${p} bash|zsh|install Generate shell completion
253
+ ${x}Commands:${m}
254
+ ${C}analyze-iam${m} <policy-file> Analyze an IAM policy for security issues
255
+ ${C}analyze-cf${m} <template-file-or-dir> Analyze a CloudFormation template or CDK output directory
256
+ ${C}api-keys${m} list|create|revoke Manage API keys
257
+ ${C}completion${m} bash|zsh|install Generate shell completion
234
258
 
235
- ${$}Global Options:${p}
259
+ ${x}Global Options:${m}
236
260
  --api-key <key> API key (or set SHIELDLY_API_KEY env var)
237
261
  --version Show version
238
262
  -h, --help Show this help
239
263
 
240
- ${$}Authentication:${p}
264
+ ${x}Authentication:${m}
241
265
  Set your API key via env var: export SHIELDLY_API_KEY=sk_...
242
- Get a free API key at: ${b}https://www.shieldly.io/app/api${p}
266
+ Get an API key (Builder plan or above) at: ${C}https://www.shieldly.io/app/api${m}
243
267
 
244
- ${$}Examples:${p}
245
- ${E}# Analyze an IAM policy${p}
268
+ ${x}Examples:${m}
269
+ ${L}# Analyze an IAM policy or CF template (no API key needed \u2014 demo mode)${m}
246
270
  shieldly analyze-iam policy.json
247
271
 
248
- ${E}# Analyze a CloudFormation template${p}
249
- shieldly analyze-cf template.yaml
272
+ ${L}# Analyze a single CloudFormation template${m}
273
+ shieldly analyze-cf template.json
274
+
275
+ ${L}# Scan all CDK stacks after synthesis (reads manifest.json \u2014 current stacks only)${m}
276
+ cdk synth && shieldly analyze-cf cdk.out/
250
277
 
251
- ${E}# List API keys${p}
278
+ ${L}# List API keys${m}
252
279
  shieldly api-keys list
253
280
 
254
- ${E}# Use in CI${p}
281
+ ${L}# Use in CI${m}
255
282
  SHIELDLY_API_KEY=\${{ secrets.SHIELDLY_API_KEY }} shieldly analyze-iam policy.json
256
- `;async function de(){let[,,e,...o]=process.argv;switch((!e||e==="-h"||e==="--help")&&(console.log(ue),process.exit(0)),(e==="--version"||e==="-v")&&(console.log(he),process.exit(0)),e){case"analyze-iam":await F(o);break;case"analyze-cf":await H(o);break;case"api-keys":await W(o);break;case"completion":await G(o);break;default:console.error(`Unknown command: ${e}`),console.log(`Run ${$}shieldly --help${p} for usage`),process.exit(1)}}de().catch(e=>{console.error("Fatal error:",e.message),process.exit(1)});
283
+ `;async function ve(){let[,,e,...s]=process.argv;switch((!e||e==="-h"||e==="--help")&&(console.log(Pe),process.exit(0)),(e==="--version"||e==="-v")&&(console.log(_e),process.exit(0)),e){case"analyze-iam":await Q(s);break;case"analyze-cf":await Z(s);break;case"api-keys":await te(s);break;case"completion":await se(s);break;default:console.error(`Unknown command: ${e}`),console.log(`Run ${x}shieldly --help${m} for usage`),process.exit(1)}}ve().catch(e=>{console.error("Fatal error:",e.message),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shieldly/cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "AI-Powered Security Analysis for AWS — official CLI",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.shieldly.io",