@shieldly/cli 1.0.1 → 1.0.3

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 +33 -3
  2. package/dist/cli.cjs +75 -41
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -10,10 +10,30 @@ any terminal, shell script, or CI/CD pipeline. Powered by
10
10
  npm install -g @shieldly/cli
11
11
  ```
12
12
 
13
+ ## Try it free — no account needed
14
+
15
+ Both `analyze-iam` and `analyze-cf` run in demo mode without an API key
16
+ (5 free analyses, no signup required):
17
+
18
+ ```bash
19
+ shieldly analyze-iam policy.json
20
+ shieldly analyze-cf template.yaml
21
+ ```
22
+
23
+ For higher limits and analysis history,
24
+ [create a free account](https://www.shieldly.io/app/api) and set your key:
25
+
26
+ ```bash
27
+ export SHIELDLY_API_KEY=sk_live_...
28
+ shieldly analyze-cf template.yaml
29
+ ```
30
+
31
+ Upgrade to a paid plan for higher daily limits, team access, and priority AI.
32
+
13
33
  ## Quick start
14
34
 
15
35
  ```bash
16
- # Authenticate (get a key at https://www.shieldly.io/app/api)
36
+ # Set your API key (get one free at https://www.shieldly.io/app/api)
17
37
  export SHIELDLY_API_KEY=sk_live_...
18
38
 
19
39
  # Analyze an IAM policy
@@ -26,8 +46,18 @@ shieldly analyze-cf template.yaml
26
46
  shieldly api-keys list
27
47
  ```
28
48
 
29
- Without an API key, `analyze-iam` runs in demo mode (5 lifetime analyses, IAM only).
30
- `analyze-cf` and `api-keys` require a key.
49
+ ## Free tier
50
+
51
+ | Mode | Limit | Requires |
52
+ | --- | --- | --- |
53
+ | Demo (no key) | 5 analyses total | No account |
54
+ | Free account | 20 units/day | Free sign-up |
55
+ | Builder | 150 units/day | Paid plan |
56
+ | Pro | 300 units/day | Paid plan |
57
+ | Team | 600 units/day | Paid plan |
58
+
59
+ Analysis units depend on input size — a small IAM policy costs 1 unit;
60
+ large CloudFormation templates cost more. [Compare plans →](https://www.shieldly.io/#pricing)
31
61
 
32
62
  ## Commands
33
63
 
package/dist/cli.cjs CHANGED
@@ -1,27 +1,55 @@
1
1
  #!/usr/bin/env node
2
- var B=Object.create;var O=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&&O(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?O(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.1",C=(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)(C))try{let o=JSON.parse((0,g.readFileSync)(C,"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 x(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 P(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"),B=require("node:os"),z=require("node:path"),O="Shieldly-CLI/1.0.3",U=(0,z.join)((0,B.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 a free API key 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 C(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 a free API key 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",J="\x1B[36m";function P(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(` ${J}[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(` ${J}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 a free key 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 a free key: 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(`Error: API key required. Set SHIELDLY_API_KEY env var or use --api-key.
24
- Get your free key at https://www.shieldly.io/app/api`),process.exit(1));let a=x(o);n!=="json"&&console.log(`Analyzing ${o}\u2026`);try{let l=await k("/v1/analyze/cf",{template:a},r);P(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 C("/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 a free API key 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 a free key: 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"&&P(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 C("/api/demo/analyze-iam",{template:a,policyType:"cf"});P(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=`
25
53
  Usage: shieldly analyze-iam <policy-file> [options]
26
54
 
27
55
  Analyze an AWS IAM policy for security issues using AI.
@@ -39,9 +67,9 @@ Options:
39
67
 
40
68
  Examples:
41
69
  shieldly analyze-iam policy.json
42
- shieldly analyze-iam policy.json --type resource --format json
70
+ shieldly analyze-iam policy.json --type cross_account --format json
43
71
  SHIELDLY_API_KEY=sk_... shieldly analyze-iam policy.json
44
- `;async function F(e){if(e.includes("-h")||e.includes("--help")||e.length===0){console.log(ie);return}let o=e.find(d=>!d.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=x(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 d=l?await k("/v1/analyze/iam",{policy:c,policyType:h},l):await R("/api/demo/analyze-iam",{policy:c,policyType:h});P(d,r);let u=(d.findings||[]).filter(S=>S.severity==="CRITICAL").length,J=(d.findings||[]).filter(S=>S.severity==="HIGH").length;(u>0||J>0)&&process.exit(1)}catch(d){console.error(`Error: ${d.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 a free API key for higher limits: https://www.shieldly.io/app/api"));try{let p=l?await A("/v1/analyze/iam",{policy:c,policyType:u},l):await C("/api/demo/analyze-iam",{policy:c,policyType:u});!l&&!p.cached&&j(),P(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=`
45
73
  Usage: shieldly api-keys <subcommand> [options]
46
74
 
47
75
  Manage Shieldly API keys.
@@ -61,9 +89,12 @@ Examples:
61
89
  shieldly api-keys list
62
90
  shieldly api-keys create --label "CI/CD Key" --scopes iam,cf
63
91
  shieldly api-keys revoke key_abc123
64
- `,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 G(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(`Error: API key required. Set SHIELDLY_API_KEY env var or use --api-key.
65
- Get your free key at https://www.shieldly.io/app/api`),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"),d=(c!==-1?t[c+1]:"iam,cf").split(",").map(u=>u.trim()).filter(Boolean);try{let u=await k("/v1/api-keys",{label:i,scopes:d},s);if(a==="json"){console.log(JSON.stringify(u,null,2));return}console.log(""),console.log(`${f}[OK] API key created${m}`),console.log(` ${f}Key ID:${m} ${u.keyId}`),console.log(` ${f}API Key:${m} ${T}${u.apiKey}${m}`),console.log(` ${N}Store this key securely \u2014 it won't be shown again.${m}`),console.log("")}catch(u){console.error(`Error: ${u.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
66
- 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=`
92
+ `,x="\x1B[1m",ee="\x1B[2m",oe="\x1B[36m",h="\x1B[0m";function xe(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.
93
+
94
+ Create your free account: https://www.shieldly.io/app/api
95
+
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(`${x}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(` ${x}Label:${h} ${r.label||"(unlabeled)"}`),console.log(` ${x}Scopes:${h} ${c}`),console.log(` ${x}Uses:${h} ${r.usageCount||0}`),console.log(` ${x}Created:${h} ${xe(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(`${x}[OK] API key created${h}`),console.log(` ${x}Key ID:${h} ${p.keyId}`),console.log(` ${x}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=`
67
98
  _shieldly() {
68
99
  local cur prev words cword
69
100
  _init_completion || return
@@ -97,7 +128,7 @@ _shieldly() {
97
128
  return
98
129
  ;;
99
130
  --type)
100
- COMPREPLY=($(compgen -W "identity resource trust s3 sqs kms sns cross_account" -- "$cur"))
131
+ COMPREPLY=($(compgen -W "identity cross_account" -- "$cur"))
101
132
  return
102
133
  ;;
103
134
  --format)
@@ -123,7 +154,7 @@ _shieldly() {
123
154
  fi
124
155
  } &&
125
156
  complete -F _shieldly shieldly
126
- `,pe=`
157
+ `,be=`
127
158
  #compdef shieldly
128
159
 
129
160
  _shieldly() {
@@ -145,7 +176,7 @@ _shieldly() {
145
176
  args)
146
177
  case $words[1] in
147
178
  analyze-iam)
148
- _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
149
180
  ;;
150
181
  analyze-cf)
151
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
@@ -194,7 +225,7 @@ _shieldly() {
194
225
  }
195
226
 
196
227
  _shieldly
197
- `,ye=`
228
+ `,Ee=`
198
229
  Usage: shieldly completion <shell>
199
230
 
200
231
  Generate shell completion scripts for the shieldly CLI.
@@ -208,42 +239,45 @@ Examples:
208
239
  eval "$(shieldly completion bash)" # Source bash completion
209
240
  shieldly completion zsh > /usr/local/share/zsh/site-functions/_shieldly # Install zsh completion
210
241
  shieldly completion install # Auto-detect and install
211
- `;async function W(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(be.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=`
212
243
  # shieldly CLI completion
213
244
  autoload -Uz compinit && compinit -C 2>/dev/null
214
- 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=`
215
246
  # shieldly CLI completion
216
- 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",de=`
217
- ${$}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 b="\x1B[1m",L="\x1B[36m",v="\x1B[2m",m="\x1B[0m",_e="1.0.3",Ce=`
248
+ ${b}shieldly${m} \u2014 AI-Powered Security Analysis for AWS
218
249
 
219
- ${$}Usage:${p}
250
+ ${b}Usage:${m}
220
251
  shieldly <command> [args] [options]
221
252
 
222
- ${$}Commands:${p}
223
- ${b}analyze-iam${p} <policy-file> Analyze an IAM policy for security issues
224
- ${b}analyze-cf${p} <template-file> Analyze a CloudFormation template
225
- ${b}api-keys${p} list|create|revoke Manage API keys
226
- ${b}completion${p} bash|zsh|install Generate shell completion
253
+ ${b}Commands:${m}
254
+ ${L}analyze-iam${m} <policy-file> Analyze an IAM policy for security issues
255
+ ${L}analyze-cf${m} <template-file-or-dir> Analyze a CloudFormation template or CDK output directory
256
+ ${L}api-keys${m} list|create|revoke Manage API keys
257
+ ${L}completion${m} bash|zsh|install Generate shell completion
227
258
 
228
- ${$}Global Options:${p}
259
+ ${b}Global Options:${m}
229
260
  --api-key <key> API key (or set SHIELDLY_API_KEY env var)
230
261
  --version Show version
231
262
  -h, --help Show this help
232
263
 
233
- ${$}Authentication:${p}
264
+ ${b}Authentication:${m}
234
265
  Set your API key via env var: export SHIELDLY_API_KEY=sk_...
235
- Get a free API key at: ${b}https://www.shieldly.io/app/api${p}
266
+ Get a free API key at: ${L}https://www.shieldly.io/app/api${m}
236
267
 
237
- ${$}Examples:${p}
238
- ${E}# Analyze an IAM policy${p}
268
+ ${b}Examples:${m}
269
+ ${v}# Analyze an IAM policy or CF template (no API key needed \u2014 demo mode)${m}
239
270
  shieldly analyze-iam policy.json
240
271
 
241
- ${E}# Analyze a CloudFormation template${p}
242
- shieldly analyze-cf template.yaml
272
+ ${v}# Analyze a single CloudFormation template${m}
273
+ shieldly analyze-cf template.json
274
+
275
+ ${v}# Scan all CDK stacks after synthesis (reads manifest.json \u2014 current stacks only)${m}
276
+ cdk synth && shieldly analyze-cf cdk.out/
243
277
 
244
- ${E}# List API keys${p}
278
+ ${v}# List API keys${m}
245
279
  shieldly api-keys list
246
280
 
247
- ${E}# Use in CI${p}
281
+ ${v}# Use in CI${m}
248
282
  SHIELDLY_API_KEY=\${{ secrets.SHIELDLY_API_KEY }} shieldly analyze-iam policy.json
249
- `;async function ue(){let[,,e,...o]=process.argv;switch((!e||e==="-h"||e==="--help")&&(console.log(de),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 G(o);break;case"completion":await W(o);break;default:console.error(`Unknown command: ${e}`),console.log(`Run ${$}shieldly --help${p} for usage`),process.exit(1)}}ue().catch(e=>{console.error("Fatal error:",e.message),process.exit(1)});
283
+ `;async function Pe(){let[,,e,...s]=process.argv;switch((!e||e==="-h"||e==="--help")&&(console.log(Ce),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 ${b}shieldly --help${m} for usage`),process.exit(1)}}Pe().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.1",
3
+ "version": "1.0.3",
4
4
  "description": "AI-Powered Security Analysis for AWS — official CLI",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.shieldly.io",