@ciandt-flow/cli 1.0.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/LICENSE.md ADDED
@@ -0,0 +1,13 @@
1
+ # Proprietary License
2
+
3
+ Copyright (c) 2024 CI&T Inc. All rights reserved.
4
+
5
+ This software and its source code are proprietary and confidential.
6
+ Unauthorized copying, distribution, modification, or use of this software,
7
+ via any medium, is strictly prohibited.
8
+
9
+ This package is made available on the npm public registry solely for
10
+ installation and use within authorized CI&T products and services.
11
+
12
+ No open-source rights are granted. Use is subject to a separate written
13
+ agreement with CI&T Inc.
package/README.md ADDED
@@ -0,0 +1,245 @@
1
+ # flow-plugins-cli
2
+
3
+ TUI for browsing and installing Claude Code plugins from the Flow ecosystem.
4
+
5
+ ## Installation
6
+
7
+ > **Requirements:** Node.js >= 22 LTS
8
+
9
+ ```bash
10
+ # Run without installing
11
+ npx @flow/cli
12
+
13
+ # Install globally
14
+ npm install -g @flow/cli
15
+
16
+ # Run after global install
17
+ flow
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```bash
23
+ npm install
24
+ cp .env.example .env
25
+ npm run dev
26
+ ```
27
+
28
+ > Copy `.env.example` to `.env` and adjust the values as needed before running the project.
29
+
30
+ ## Development
31
+
32
+ ```bash
33
+ npm run build # Compile TypeScript
34
+ npm run test # Run tests
35
+ npm run lint # Lint check
36
+ npm run format # Format + lint fix
37
+ ```
38
+
39
+ ## CLI Commands
40
+
41
+ ### `setup`
42
+
43
+ Setup and configuration commands.
44
+
45
+ #### `setup init`
46
+
47
+ Initialize Flow CLI configuration.
48
+
49
+ ```bash
50
+ npm run dev -- setup init
51
+ ```
52
+
53
+ ---
54
+
55
+ ### `plugin`
56
+
57
+ Plugin management commands.
58
+
59
+ #### `plugin list`
60
+
61
+ List plugins.
62
+
63
+ ```bash
64
+ flow-plugins plugin list [options]
65
+ ```
66
+
67
+ | Option | Description |
68
+ |---|---|
69
+ | `--available` | Show all plugins from catalog with install status |
70
+ | `--outdated` | Show installed plugins with updates available |
71
+ | `--json` | Output as JSON |
72
+
73
+ ```bash
74
+ # list installed plugins (default)
75
+ npm run dev -- plugin list
76
+
77
+ # list all plugins from catalog
78
+ npm run dev -- plugin list --available
79
+
80
+ # list plugins with updates available
81
+ npm run dev -- plugin list --outdated
82
+
83
+ # output as JSON
84
+ npm run dev -- plugin list --json
85
+ npm run dev -- plugin list --available --json
86
+ ```
87
+
88
+ #### `plugin install <id>`
89
+
90
+ Install a plugin by ID.
91
+
92
+ ```bash
93
+ npm run dev -- plugin install <id>
94
+ ```
95
+
96
+ #### `plugin uninstall <id>`
97
+
98
+ Uninstall a plugin by ID.
99
+
100
+ ```bash
101
+ npm run dev -- plugin uninstall <id>
102
+ ```
103
+
104
+ #### `plugin enable <id>`
105
+
106
+ Enable an installed plugin.
107
+
108
+ ```bash
109
+ npm run dev -- plugin enable <id>
110
+ ```
111
+
112
+ #### `plugin disable <id>`
113
+
114
+ Disable an installed plugin.
115
+
116
+ ```bash
117
+ npm run dev -- plugin disable <id>
118
+ ```
119
+
120
+ ---
121
+
122
+ ### `auth`
123
+
124
+ Authentication commands.
125
+
126
+ #### `auth login`
127
+
128
+ Authenticate and save credentials.
129
+
130
+ ```bash
131
+ flow-plugins auth login [options]
132
+ ```
133
+
134
+ | Option | Description |
135
+ |---|---|
136
+ | `--client-id <id>` | Client ID |
137
+ | `--client-secret <secret>` | Client Secret |
138
+ | `--tenant <tenant>` | Tenant |
139
+
140
+ When called **without options**, enters interactive mode — prompts for each field in the terminal. The tenant field is pre-filled if one is detected from the environment.
141
+
142
+ When called **with all three options**, runs non-interactively and saves credentials directly.
143
+
144
+ ```bash
145
+ # interactive mode
146
+ npm run dev -- auth login
147
+
148
+ # non-interactive mode
149
+ npm run dev -- auth login --client-id aa --client-secret bbb --tenant cit-dev
150
+ ```
151
+
152
+ #### `auth logout`
153
+
154
+ Remove saved credentials.
155
+
156
+ ```bash
157
+ flow-plugins auth logout [options]
158
+ ```
159
+
160
+ | Option | Description |
161
+ |---|---|
162
+ | `--force` | Skip confirmation prompt |
163
+
164
+ ```bash
165
+ # with confirmation prompt
166
+ npm run dev -- auth logout
167
+
168
+ # skip confirmation
169
+ npm run dev -- auth logout --force
170
+ ```
171
+
172
+ #### `auth status`
173
+
174
+ Show authentication status.
175
+
176
+ ```bash
177
+ npm run dev -- auth status
178
+ ```
179
+
180
+ ---
181
+
182
+ ### `health`
183
+
184
+ Run diagnostic checks.
185
+
186
+ ```bash
187
+ npm run dev -- health
188
+ ```
189
+
190
+ ## Versioning & Releases
191
+
192
+ This project uses [Changesets](https://github.com/changesets/changesets) for version management and automated releases.
193
+
194
+ ### How it works
195
+
196
+ 1. Developers add a **changeset** describing their changes before opening a PR
197
+ 2. On merge to `main`, a GitHub Action detects pending changesets and opens a **"Version Packages"** PR
198
+ 3. That PR bumps the version in `package.json`, updates `CHANGELOG.md`, and removes consumed changesets
199
+ 4. Merging the Version Packages PR triggers an automated **npm publish** with provenance
200
+
201
+ The version is read at runtime from `package.json` — there is no hardcoded version constant to keep in sync.
202
+
203
+ ### Adding a changeset
204
+
205
+ After making your changes and before opening a PR, run:
206
+
207
+ ```bash
208
+ npx changeset
209
+ ```
210
+
211
+ You'll be prompted to:
212
+ - Select the semver bump type (`patch`, `minor`, or `major`)
213
+ - Write a summary of the change (this goes into the CHANGELOG)
214
+
215
+ This creates a markdown file in `.changeset/`. Commit it with your PR.
216
+
217
+ > **When to use each bump type:**
218
+ > - `patch` — bug fixes, docs, internal refactors
219
+ > - `minor` — new features, non-breaking additions
220
+ > - `major` — breaking changes
221
+
222
+ ### Release scripts
223
+
224
+ ```bash
225
+ npm run changeset # Add a new changeset
226
+ npm run version-packages # Apply pending changesets (bump version + CHANGELOG)
227
+ npm run release # Build + publish to npm
228
+ ```
229
+
230
+ These are used by CI — you typically only need `npx changeset` locally.
231
+
232
+ ### CI/CD
233
+
234
+ | Workflow | Trigger | What it does |
235
+ |---|---|---|
236
+ | `ci.yml` | Pull requests to `main` | Runs lint, test, and build |
237
+ | `release.yml` | Push to `main` | Runs CI checks, then creates a Version Packages PR or publishes to npm |
238
+
239
+ The release workflow requires two secrets configured in the repository:
240
+ - `GITHUB_TOKEN` — provided automatically by GitHub Actions
241
+ - `NPM_TOKEN` — npm access token with publish permissions
242
+
243
+ ## License
244
+
245
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ import {Box,Text,render,useInput}from'ink';import xo,{useMemo,useEffect,useState,useCallback,useRef}from'react';import {jsxs,jsx,Fragment}from'react/jsx-runtime';import {create}from'zustand';import eo,{HTTPError}from'ky';import Wn from'conf';import T,{readFileSync,existsSync,chmodSync,realpathSync}from'fs';import mo from'ink-big-text';import po from'ink-gradient';import*as I from'path';import I__default,{join}from'path';import h from'chalk';import $o from'ink-spinner';import*as it from'os';import it__default,{homedir}from'os';import*as R from'fs/promises';import _o from'extract-zip';import qo from'proper-lockfile';import {randomUUID}from'crypto';import {Command}from'commander';var zn=16;function gt({label:e,value:t,onChange:n,masked:o=false,isActive:r,error:i}){useInput((a,l)=>{l.backspace||l.delete?n(t.slice(0,-1)):a&&!l.ctrl&&!l.meta&&!l.escape&&!l.return&&!l.tab&&!l.upArrow&&!l.downArrow&&!l.leftArrow&&!l.rightArrow&&n(t+a);},{isActive:r});let s=o?"\u2022".repeat(t.length):t;return jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{children:[jsx(Text,{color:r?"cyan":"gray",children:r?"\u25B8 ":" "}),jsx(Text,{color:r?"white":"gray",bold:r,children:e.padEnd(zn)}),jsx(Text,{color:r?"cyan":"gray",children:s}),r&&jsx(Text,{color:"cyan",children:"\u2588"})]}),i&&jsx(Box,{paddingLeft:3,children:jsxs(Text,{color:"red",children:["\u26A0 ",i]})})]})}var L=create(e=>({credentials:null,isAuthenticated:false,justAuthenticated:false,setCredentials:t=>e({credentials:t,isAuthenticated:true}),clearCredentials:()=>e({credentials:null,isAuthenticated:false,justAuthenticated:false}),setJustAuthenticated:t=>e({justAuthenticated:t})}));var m=create(e=>({activeTab:"discover",focus:"list",selectedIndex:0,actionMenuOpen:false,notification:null,loading:false,loadingMessage:"",catalogError:null,setActiveTab:t=>e({activeTab:t}),setFocus:t=>e({focus:t}),setSelectedIndex:t=>e({selectedIndex:t}),setActionMenuOpen:t=>e({actionMenuOpen:t}),showNotification:(t,n)=>e({notification:{message:t,type:n}}),clearNotification:()=>e({notification:null}),setLoading:(t,n="")=>e({loading:t,loadingMessage:t?n:""}),setCatalogError:t=>e({catalogError:t})}));var ge="FLOW",J="flow-skills";var B=new Wn({projectName:ge});function ht(){try{chmodSync(B.path,384);}catch{}}function P(){let e=B.get("credentials");if(!e)return null;try{return JSON.parse(e)}catch{return null}}function yt(e){B.set("credentials",JSON.stringify(e)),ht();}function wt(){B.delete("credentials"),B.delete("tokenCache");}function xt(){return B.path}function bt(e){B.set("tokenCache",{accessToken:e.accessToken,expiresAt:e.expiresAt}),ht();}function _e(){let e=B.get("tokenCache");return e?{accessToken:e.accessToken,expiresAt:e.expiresAt}:null}function N(){let e=_e();return e?new Date(e.expiresAt)>new Date:false}var Qn=[/^localhost\.?$/i,/^127\./,/^10\./,/^172\.(1[6-9]|2\d|3[01])\./,/^192\.168\./,/^169\.254\./,/^::1$/,/^\[::1\]$/,/^\[::ffff:/i,/^0\.0\.0\.0$/,/^\[f[cd]/i,/^\[fe80:/i,/^metadata\.google\.internal\.?$/i,/^metadata\.azure\.internal\.?$/i];function Yn(e){return Qn.some(t=>t.test(e))}function Zn(e,t){let n=process.env[e]??t;if(!n)throw new Error(`Missing required environment variable: ${e}`);return n}function he(e,t){let n=Zn(e,t),o;try{o=new URL(n);}catch{throw new Error(`${e} must be a valid URL. Got: "${n}"`)}if(o.protocol!=="https:")throw new Error(`${e} must be an HTTPS URL. Got protocol: "${o.protocol}"`);if(o.username||o.password)throw new Error(`${e} must not contain embedded credentials (user:pass@host is not allowed)`);if(Yn(o.hostname))throw new Error(`${e} must not point to a private/loopback address. Got: "${o.hostname}"`);return n}var no=/^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$|^[a-z0-9]$/;function oo(e){if(!no.test(e))throw new Error(`Invalid tenant value: "${e}". Tenant must be lowercase alphanumeric and hyphens (1-64 chars).`)}function ro(e){if(e instanceof HTTPError){let t=e.response.status;return t===401||t===403||t===500?new Error("Invalid credentials"):t>=400&&t<500?new Error("Authentication request was rejected"):new Error("Authentication service unavailable, please try again later")}return e instanceof Error?e:new Error("Authentication failed")}async function ne(e){let t=he("AUTH_ENGINE_URL","https://dev.flow.ciandt.com/auth-engine-api/v2/api-key/token");oo(e.tenant);let n;try{n=await eo.post(t,{headers:{FlowTenant:e.tenant},json:{clientSecret:e.clientSecret}}).json();}catch(r){throw ro(r)}let o=n.expires_at??new Date(Date.now()+(n.expires_in??3600)*1e3).toISOString();yt(e),bt({accessToken:n.access_token,expiresAt:o});}async function vt(){if(N()){let e=_e();if(!e)throw new Error("Flow CLI not configured. Run: npx @flow/cli auth login");return e.accessToken}throw new Error("Flow CLI not configured or token expired. Run: npx @flow/cli auth login")}var A=["clientId","clientSecret","tenant"],so={clientId:"Client ID",clientSecret:"Client Secret",tenant:"Tenant"};function It(){let[e,t]=useState({clientId:"",clientSecret:"",tenant:""}),[n,o]=useState("clientId"),[r,i]=useState({}),[s,a]=useState(null),[l,d]=useState(false),{setCredentials:g,setJustAuthenticated:O}=L(),{setFocus:F}=m();useInput((c,p)=>{if(!l){if(p.shift&&p.tab){let f=A.indexOf(n);f>0&&o(A[f-1]);}else if(p.tab){let f=A.indexOf(n);f<A.length-1&&o(A[f+1]);}else if(p.return)if(n==="tenant")x();else {let f=A.indexOf(n);o(A[f+1]);}}},{isActive:true});let x=async()=>{let c={};for(let p of A)e[p].trim()||(c[p]="This field is required");if(Object.keys(c).length>0){i(c);let p=A.find(f=>c[f]);p&&o(p);return}d(true),a(null);try{await ne(e);let{clientSecret:p,...f}=e;g(f),O(!0),F("list");}catch(p){a(p instanceof Error?p.message:"Authentication failed");}finally{d(false),t(p=>({...p,clientSecret:""}));}},$=c=>p=>{t(f=>({...f,[c]:p})),r[c]&&i(f=>({...f,[c]:void 0}));};return jsxs(Box,{flexDirection:"column",padding:2,children:[jsx(Box,{marginBottom:1,children:jsxs(Text,{bold:true,color:"cyan",children:[ge," \u2014 Initial Setup"]})}),jsx(Box,{flexDirection:"column",children:A.map(c=>jsx(Box,{marginBottom:1,children:jsx(gt,{label:so[c],value:e[c],onChange:$(c),masked:c==="clientSecret",isActive:n===c&&!l,error:r[c]})},c))}),l&&jsx(Box,{marginTop:1,children:jsx(Text,{color:"cyan",children:"Authenticating..."})}),s&&jsx(Box,{marginTop:1,children:jsxs(Text,{color:"red",children:["\u26A0 ",s]})}),jsx(Box,{marginTop:1,children:jsx(Text,{dimColor:true,children:"Tab next field \xB7 Enter confirm"})})]})}var co=JSON.parse(readFileSync(join(import.meta.dirname,"..","package.json"),"utf8")),we=co.version;function St(){return jsxs(Box,{flexDirection:"column",alignItems:"center",children:[jsx(po,{name:"morning",children:jsx(mo,{text:"FLOW",font:"block"})}),jsx(Box,{marginTop:-1,marginBottom:1,children:jsxs(Text,{dimColor:true,children:["Marketplace \xB7 v",we]})})]})}var ze={discover:"Discover",installed:"Installed"},fo=Object.keys(ze);function Et(){let e=m(t=>t.activeTab);return jsxs(Box,{paddingX:1,paddingBottom:1,children:[fo.map(t=>jsx(Box,{marginRight:2,children:t===e?jsx(Text,{bold:true,underline:true,color:"cyan",children:ze[t]}):jsx(Text,{dimColor:true,children:ze[t]})},t)),jsx(Text,{dimColor:true,children:"(Tab to cycle)"})]})}var E=create(e=>({query:"",setQuery:t=>e({query:t}),resetQuery:()=>e({query:""})}));function Rt(){let e=m(o=>o.focus),t=E(o=>o.query),n=e==="search";return jsxs(Box,{borderStyle:"single",borderTop:false,borderBottom:true,borderLeft:false,borderRight:false,paddingX:1,marginX:1,marginBottom:1,children:[jsx(Text,{color:n?"cyan":"gray",children:"\u03C1 "}),n?jsxs(Fragment,{children:[jsx(Text,{color:"white",children:t}),jsx(Text,{color:"cyan",children:"\u2588"})]}):jsx(Text,{dimColor:true,children:t||"Search..."})]})}function w(e){return e.replace(/\x1b\[[0-9;:<=>?]*[ -/]*[@-~]/g,"").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g,"").replace(/\x1b./g,"").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g,"")}function z(e){process.stdout.write(h.green(` \u2713 ${w(e)}
3
+ `));}function v(e){process.stderr.write(h.red(` \u2717 ${w(e)}
4
+ `));}function C(e){process.stdout.write(h.cyan(` \u2139 ${w(e)}
5
+ `));}function Ie(e,t){let n=t.map(a=>a.map(w)),o=[e,...n],r=e.map((a,l)=>Math.max(...o.map(d=>(d[l]??"").length))),i=r.map(a=>"\u2500".repeat(a)).join(" "),s=a=>a.map((l,d)=>l.padEnd(r[d])).join(" ");process.stdout.write(`
6
+ `),process.stdout.write(` ${h.bold(s(e))}
7
+ `),process.stdout.write(` ${h.dim(i)}
8
+ `);for(let a of n)process.stdout.write(` ${s(a)}
9
+ `);process.stdout.write(`
10
+ `);}function Te(e){process.stdout.write(JSON.stringify(e,null,2)+`
11
+ `);}function Pe(e){let t=new Date(e);return isNaN(t.getTime())?"\u2014":new Intl.DateTimeFormat("en-US",{dateStyle:"short"}).format(t)}var kt=xo.memo(function({item:t,isSelected:n}){let o=t.status==="disabled"?"yellow":"green";return jsxs(Box,{flexDirection:"column",paddingBottom:1,children:[jsxs(Box,{children:[jsx(Text,{color:n?"cyan":"gray",children:n?"\u203A ":"\u25CB "}),jsx(Text,{bold:n,color:n?"cyan":"white",children:t.name}),jsxs(Text,{dimColor:true,children:[t.authorName?` \xB7 ${t.authorName}`:""," \xB7 v",t.version]}),t.status&&jsxs(Text,{color:o,children:[" [",t.status,"]"]}),t.updateAvailable&&jsx(Text,{color:"yellow",children:" [update available]"}),t.installedBadge&&jsx(Text,{color:"cyan",children:" [installed]"}),t.installedAt&&jsxs(Text,{dimColor:true,children:[" \xB7 ",Pe(t.installedAt)]})]}),jsx(Box,{paddingLeft:2,children:jsx(Text,{dimColor:true,children:t.description})})]})});function Lt({message:e}){return jsx(Box,{paddingX:2,paddingY:1,children:jsx(Text,{dimColor:true,children:e??"No items found."})})}var We=5;function Dt({items:e,emptyMessage:t}){let n=m(l=>l.selectedIndex);if(e.length===0)return jsx(Lt,{message:t});let o=Math.max(0,n-Math.floor(We/2)),r=Math.min(e.length,o+We);r===e.length&&(o=Math.max(0,r-We));let i=e.slice(o,r),s=o,a=e.length-r;return jsxs(Box,{flexDirection:"column",children:[s>0&&jsx(Box,{paddingX:2,children:jsxs(Text,{dimColor:true,children:["\u2191 ",s," more above"]})}),i.map((l,d)=>jsx(kt,{item:l,isSelected:o+d===n},l.name)),a>0&&jsx(Box,{paddingX:2,children:jsxs(Text,{dimColor:true,children:["\u2193 ",a," more below"]})})]})}var Io={discover:"Discover plugins",installed:"Installed plugins"};function Ft({items:e,tabId:t,emptyMessage:n}){return jsxs(Box,{flexDirection:"column",children:[jsxs(Box,{paddingX:1,paddingBottom:1,children:[jsx(Text,{bold:true,children:Io[t]}),jsxs(Text,{dimColor:true,children:[" (",e.length,")"]})]}),jsx(Rt,{}),jsx(Dt,{items:e,emptyMessage:n})]})}function Bt({filteredItems:e,emptyMessage:t}){let n=m(o=>o.activeTab);return jsx(Box,{flexDirection:"column",flexGrow:1,children:n==="discover"?jsx(Ft,{items:e,tabId:"discover",emptyMessage:t},"discover"):jsx(Ft,{items:e,tabId:"installed",emptyMessage:t},"installed")})}var So={tabs:"Tab: switch tab | \u2191\u2193: navigate | Enter: select | /: search | q: quit",list:"Tab: switch tab | \u2191\u2193: navigate | Enter: select | /: search | q: quit",search:"Type to filter | Esc: cancel search | Enter: confirm",actionMenu:"\u2191\u2193: navigate options | Enter: confirm | Esc: close menu",auth:"Tab: next field | Enter: confirm | Ctrl+C: quit"};function Ut(){let e=m(t=>t.focus);return jsx(Box,{borderStyle:"single",borderTop:true,borderBottom:false,borderLeft:false,borderRight:false,children:jsx(Text,{dimColor:true,children:So[e]})})}function jt({message:e,type:t}){return e?jsx(Box,{paddingX:1,children:jsxs(Text,{color:t==="success"?"green":t==="info"?"cyan":"red",children:[t==="success"?"\u2713":t==="info"?"(i)":"\u2717"," ",e]})}):null}function Ht({message:e}){return jsxs(Box,{children:[jsx(Text,{color:"cyan",children:jsx($o,{type:"dots"})}),jsxs(Text,{children:[" ",e]})]})}function Vt({message:e,onRetry:t,onBack:n}){return useInput((o,r)=>{o==="r"&&t?t():r.escape&&n&&n();}),jsxs(Box,{flexDirection:"column",paddingX:2,paddingY:1,children:[jsxs(Text,{color:"red",children:["\u2717 ",e]}),t&&jsx(Text,{dimColor:true,children:"Press r to retry"}),n&&jsx(Text,{dimColor:true,children:"Press Escape to go back"})]})}function Ae({itemName:e,actions:t,onAction:n,onClose:o}){let r=m(a=>a.focus),[i,s]=useState(0);return useInput((a,l)=>{l.upArrow?s(d=>d>0?d-1:d):l.downArrow?s(d=>d<t.length-1?d+1:d):l.return?n(t[i]):l.escape&&o();},{isActive:r==="actionMenu"}),jsxs(Box,{flexDirection:"column",borderStyle:"round",paddingX:1,children:[jsx(Text,{bold:true,children:e}),jsx(Box,{flexDirection:"column",marginTop:1,children:t.map((a,l)=>jsx(Box,{children:jsxs(Text,{bold:l===i,color:l===i?"cyan":void 0,children:[l===i?"\u203A ":" ",a]})},l))})]})}function Xt({itemName:e,onInstall:t,onClose:n}){return jsx(Ae,{itemName:e,actions:["Install","Cancel"],onAction:i=>{i==="Install"?t():n();},onClose:n})}function Zt({itemName:e,itemStatus:t,updateAvailable:n,onUninstall:o,onToggleStatus:r,onUpdate:i,onClose:s}){let[a,l]=useState(false),d=m(x=>x.focus);if(useInput((x,$)=>{$.escape||x==="n"?l(false):x==="y"&&o();},{isActive:a&&d==="actionMenu"}),a)return jsxs(Box,{flexDirection:"column",borderStyle:"round",paddingX:1,children:[jsx(Text,{bold:true,children:e}),jsxs(Box,{marginTop:1,children:[jsx(Text,{children:"Uninstall "}),jsx(Text,{bold:true,color:"red",children:e}),jsx(Text,{children:"? (y/N)"})]})]});let g=t==="enabled"?"Disable":"Enable";return jsx(Ae,{itemName:e,actions:n?["Uninstall",g,"Update","Cancel"]:["Uninstall",g,"Cancel"],onAction:x=>{x==="Uninstall"?l(true):x===g?r():x==="Update"?i?.():s();},onClose:s})}var Uo=".claude",jo=".flow";function en(e){return I.join(it.homedir(),Uo,"plugins","cache",J,e)}function tn(){return I.join(it.homedir(),jo,"cache","install.lock")}async function nn(e,t){let n=I.resolve(t);await R.mkdir(n,{recursive:true});let o=I.join(I.dirname(n),`${randomUUID()}.zip`);try{await R.writeFile(o,e),await _o(o,{dir:n,onEntry(r){if((r.externalFileAttributes>>16&61440)===40960)throw new Error(`Zip Slip (symlink): entry "${r.fileName}" is a symbolic link and was rejected`);let s=I.resolve(n,r.fileName);if(!s.startsWith(n+I.sep)&&s!==n)throw new Error(`Zip Slip detected: entry "${r.fileName}" would escape target directory`)}});try{let r=realpathSync(n);if(!r.startsWith(I.resolve(I.dirname(n))))throw new Error(`Zip Slip (post-extract): resolved path "${r}" is outside expected parent`)}catch(r){if(r.code!=="ENOENT")throw r}}finally{try{await R.unlink(o);}catch{}}}async function rt(e){await R.rm(e,{recursive:true,force:true});}async function ae(){let e=tn();await R.mkdir(I.dirname(e),{recursive:true});try{await R.writeFile(e,"",{flag:"wx"});}catch(n){if(n.code!=="EEXIST")throw n}return await qo.lock(e,{stale:1e4,retries:{retries:2,minTimeout:500,maxTimeout:500}})}function on(){return I__default.join(it__default.homedir(),".claude","plugins","installed_plugins.json")}function Jo(){return I__default.join(it__default.homedir(),".claude","plugins")}function zo(e){let t=I__default.resolve(e),n=I__default.resolve(Jo());if(!t.startsWith(n+I__default.sep)&&t!==n)throw new Error(`Security error: installPath '${e}' is outside the plugins directory`)}function rn(){return I__default.join(it__default.homedir(),".claude","settings.json")}function W(){let e=on();if(!T.existsSync(e))return {version:2,plugins:{}};try{return JSON.parse(T.readFileSync(e,"utf-8"))}catch{return {version:2,plugins:{}}}}function st(e){let t=on();T.mkdirSync(I__default.dirname(t),{recursive:true});let n=`${t}.tmp`;T.writeFileSync(n,JSON.stringify(e,null,2),"utf-8"),T.renameSync(n,t);}function le(e){let t=e.lastIndexOf("@");return t===-1?{name:e,marketplace:""}:{name:e.slice(0,t),marketplace:e.slice(t+1)}}function Vo(e){let t=I__default.join(e,".claude-plugin","plugin.json");if(!T.existsSync(t))return null;try{return JSON.parse(T.readFileSync(t,"utf-8"))}catch{return null}}function at(){let e=rn();if(!T.existsSync(e))return {};try{return JSON.parse(T.readFileSync(e,"utf-8"))}catch{return {}}}function sn(e){let t=rn(),n=`${t}.tmp`;T.writeFileSync(n,JSON.stringify(e,null,2),"utf-8"),T.renameSync(n,t);}function Go(){return at().enabledPlugins??{}}function Wo(e){let t=at(),n=t.enabledPlugins;if(!n||!(e in n))return;let{[e]:o,...r}=n;t.enabledPlugins=r,sn(t);}function lt(e,t){let n=at(),o=n.enabledPlugins??{};n.enabledPlugins={...o,[e]:t},sn(n);}function k(){let e=W(),t=Go();return Object.entries(e.plugins).map(([n,o])=>{let r=o[0],{name:i,marketplace:s}=le(n),a=Vo(r.installPath),l=t[n];return {name:i,marketplace:s,version:r.version,installedAt:r.installedAt,installPath:r.installPath,scope:r.scope,description:a?.description,author:a?.author,status:l===false?"disabled":"enabled"}})}async function Ce(e){let t=await ae();try{let{name:n,marketplace:o}=le(e),r=W(),i;if(o?(i=`${n}@${o}`,r.plugins[i]||(i=void 0)):i=Object.keys(r.plugins).filter(g=>le(g).name===n)[0],!i)throw new Error(`Plugin '${n}' is not installed`);let s=r.plugins[i][0].installPath;zo(s),T.rmSync(s,{recursive:!0,force:!0});let{[i]:a,...l}=r.plugins;st({...r,plugins:l}),Wo(i);}finally{await t();}}async function ce(e,t){let n=await ae();try{let{name:o}=le(e),r=W(),i=Object.keys(r.plugins).filter(s=>le(s).name===o);if(i.length===0)throw new Error(`Plugin '${o}' is not installed`);lt(i[0],t==="enabled");}finally{await n();}}var K=create(e=>({installedItems:[],setInstalledItems:t=>e({installedItems:t}),loadFromDisk:()=>e({installedItems:k()})}));var S=["discover","installed"];function an(e,t,n){if(t.rightArrow||t.tab){let o=S.indexOf(n.activeTab);return [{type:"setTab",tab:S[(o+1)%S.length]},{type:"setSelectedIndex",index:0},{type:"setFocus",focus:"list"}]}if(t.leftArrow){let o=S.indexOf(n.activeTab);return [{type:"setTab",tab:S[(o-1+S.length)%S.length]},{type:"setSelectedIndex",index:0},{type:"setFocus",focus:"list"}]}return t.downArrow?[{type:"setFocus",focus:"list"}]:[]}function ln(e,t,n,o){if(t.upArrow&&n.selectedIndex>0)return [{type:"setSelectedIndex",index:n.selectedIndex-1}];if(t.downArrow&&n.selectedIndex<o-1)return [{type:"setSelectedIndex",index:n.selectedIndex+1}];if(t.tab){let r=S.indexOf(n.activeTab);return [{type:"setTab",tab:S[(r+1)%S.length]},{type:"setSelectedIndex",index:0}]}return t.return?[{type:"setActionMenuOpen",open:true},{type:"setFocus",focus:"actionMenu"}]:e==="/"?[{type:"setFocus",focus:"search"}]:[]}function cn(e,t){return t.escape?[{type:"setActionMenuOpen",open:false},{type:"setFocus",focus:"list"}]:[]}function un(e,t){return t.escape?{actions:[{type:"setFocus",focus:"list"}],queryUpdate:"reset"}:t.return?{actions:[{type:"setFocus",focus:"list"}],queryUpdate:null}:t.backspace||t.delete?{actions:[],queryUpdate:{backspace:true}}:e&&!t.ctrl&&!t.meta?{actions:[],queryUpdate:{append:e}}:{actions:[],queryUpdate:null}}function Re(e){let{setActiveTab:t,setFocus:n,setSelectedIndex:o,setActionMenuOpen:r}=m.getState();for(let i of e)i.type==="setTab"?t(i.tab):i.type==="setFocus"?n(i.focus):i.type==="setSelectedIndex"?o(i.index):i.type==="setActionMenuOpen"&&r(i.open);}function mn({listLength:e}){let t=m(c=>c.focus),n=m(c=>c.selectedIndex),o=m(c=>c.actionMenuOpen),r=m(c=>c.setFocus),i=m(c=>c.setActionMenuOpen),s=E(c=>c.query),a=E(c=>c.setQuery),l=E(c=>c.resetQuery),d=useRef(e);useEffect(()=>{d.current=e;},[e]);let[g,O]=useState("");useEffect(()=>{let c=setTimeout(()=>O(s),300);return ()=>clearTimeout(c)},[s]),useInput((c,p)=>{c==="q"&&process.exit(0);},{isActive:t!=="search"&&t!=="auth"}),useInput((c,p)=>{let{activeTab:f,selectedIndex:b,actionMenuOpen:Y}=m.getState();Re(an(c,p,{activeTab:f}));},{isActive:t==="tabs"}),useInput((c,p)=>{let{activeTab:f,selectedIndex:b,actionMenuOpen:Y}=m.getState();Re(ln(c,p,{activeTab:f,selectedIndex:b},d.current));},{isActive:t==="list"}),useInput((c,p)=>{Re(cn(c,p));},{isActive:t==="actionMenu"}),useInput((c,p)=>{let f=un(c,p),b=f.queryUpdate;Re(f.actions),b==="reset"?(l(),O("")):b!==null&&("backspace"in b?a(E.getState().query.slice(0,-1)):a(E.getState().query+b.append));},{isActive:t==="search"});let F=useCallback(()=>{i(false),r("list");},[i,r]),x=useCallback(c=>{let{setSelectedIndex:p,selectedIndex:f}=m.getState();c===0?p(0):f>=c&&p(c-1);},[]),$=useCallback(c=>{if(!g)return d.current=c.length,c;let p=g.toLowerCase(),f=c.filter(b=>b.name.toLowerCase().includes(p)||b.description?.toLowerCase().includes(p));return d.current=f.length,f},[g]);return {actionMenuOpen:o,closeMenu:F,selectedIndex:n,clampIndex:x,filteredItems:$}}var pn=800;var er=/^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$|^[a-z0-9]$/;function ke(){return eo.create({prefixUrl:he("PROMPT_MANAGER_URL","https://dev.flow.ciandt.com/prompt-manager-api/"),hooks:{beforeRequest:[async e=>{let t=await vt();e.headers.set("Authorization",`Bearer ${t}`);let n=P();n?.tenant&&er.test(n.tenant)&&e.headers.set("FlowTenant",n.tenant);}]}})}var tr=/^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$|^[a-z0-9]$/;function fn(e){if(!e||!tr.test(e))throw new Error(`Invalid plugin name: "${e}". Must be 1-64 chars, lowercase alphanumeric and hyphens only.`)}async function X(){try{let{plugins:e}=await ke().get("v1/plugins/catalog").json();return e}catch(e){throw e instanceof Error?e.message.includes("401")||e.message.includes("403")?new Error("Authentication failed. Run: flow auth login"):e.message.includes("timeout")?new Error("Request timed out while fetching plugin catalog"):new Error(`Failed to fetch plugin catalog: ${e.message}`):e}}async function gn(e){fn(e);try{return await ke().get(`v1/plugins/${e}/manifest`).json()}catch(t){throw t instanceof Error?t.message.includes("404")?new Error(`Plugin '${e}' not found in catalog`):t.message.includes("timeout")?new Error("Download timed out after 30s"):new Error(`Failed to fetch plugin manifest: ${t.message}`):t}}async function hn(e){fn(e);try{let t=await ke().get(`v1/plugins/${e}/archive`);return Buffer.from(await t.arrayBuffer())}catch(t){throw t instanceof Error?t.message.includes("timeout")?new Error("Download timed out after 30s"):new Error(`Failed to download plugin: ${t.message}`):t}}async function yn(e){await new Promise(t=>setTimeout(t,pn)),console.warn("Update not implemented yet");}function wn(){let[e,t]=useState([]),[n,o]=useState(true),[r,i]=useState(null),s=useCallback(async()=>{o(true),i(null);try{let a=await X();t(a);}catch(a){i(a instanceof Error?a:new Error(String(a))),t([]);}finally{o(false);}},[]);return useEffect(()=>{s();},[s]),{catalog:e,isLoading:n,error:r,refetch:s}}function xn(){return {items:K(t=>t.installedItems)}}var Q=class extends Error{constructor(n,o){super(`${n} v${o} is already installed. Use --force to reinstall.`);this.pluginName=n;this.version=o;this.name="AlreadyInstalledError";}};var rr=/^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$|^[a-z0-9]$/;function ir(e){if(!rr.test(e))throw new Error(`Invalid plugin name from manifest: "${e}". Plugin names must be lowercase alphanumeric and hyphens (1-64 chars).`)}function sr(e,t){return n=>{t&&console.log(`[${e}] ${n}`);}}function ar(e,t){let n=`${e}@${J}`,r=W().plugins[n];if(r&&!t)throw new Q(e,r[0].version);return {pluginKey:n,alreadyInstalled:r}}async function lr(e,t,n,o){n&&await rt(t);try{o(`Extracting to ${t}...`),await nn(e,t);}catch(r){throw await rt(t),r}}function cr(e,t,n){let o=W(),r={scope:"user",installPath:t,version:n,installedAt:new Date().toISOString()};o.plugins[e]=[r],st(o),lt(e,true);}async function $e(e,t={}){let n=Date.now(),o=sr(e,t.verbose),r=null;try{o("Acquiring lock..."),r=await ae();let{pluginKey:i,alreadyInstalled:s}=ar(e,t.force);o("Fetching manifest...");let a=await gn(e);ir(a.name),o("Downloading archive...");let l=await hn(e),d=en(a.name);await lr(l,d,!!s&&!!t.force,o),cr(i,d,a.version);let g=Date.now()-n;return o(`Installed successfully in ${g}ms`),{name:a.name,version:a.version,path:d,duration_ms:g}}finally{r&&await r();}}function bn(){let e=K(s=>s.installedItems),t=K(s=>s.setInstalledItems),n=useCallback(async s=>{await $e(s.name),K.getState().loadFromDisk();},[]),o=useCallback(async s=>{await Ce(s),t(e.filter(a=>a.name!==s));},[e,t]),r=useCallback(async s=>{let l=e.find(d=>d.name===s)?.status==="enabled"?"disabled":"enabled";await ce(s,l),t(e.map(d=>d.name===s?{...d,status:l}:d));},[e,t]),i=useCallback(async s=>{await yn(),t(e.map(a=>a.name===s?{...a,updateAvailable:false}:a));},[e,t]);return {install:n,uninstall:o,toggle:r,update:i}}function mr(e){return {name:e.name,version:e.version,description:e.description,authorName:e.author.name,updateAvailable:e.updateAvailable}}function pr(e){return {name:e.name,version:e.version,description:e.description,authorName:e.author?.name,status:e.status,updateAvailable:e.updateAvailable,installedAt:e.installedAt}}function vn(){let e=m(u=>u.activeTab),t=m(u=>u.notification),n=m(u=>u.showNotification),o=m(u=>u.clearNotification),r=m(u=>u.loading),i=m(u=>u.loadingMessage),s=m(u=>u.catalogError),a=m(u=>u.setLoading),l=m(u=>u.setCatalogError),d=E(u=>u.query),{catalog:g,isLoading:O,error:F,refetch:x}=wn(),{items:$}=xn(),c=K(u=>u.loadFromDisk),p=useMemo(()=>new Set($.map(u=>`${u.name}|${u.author?.name??""}|${u.marketplace}`)),[$]),{install:f,uninstall:b,toggle:Y,update:On}=bn(),mt=L(u=>u.justAuthenticated),Fe=L(u=>u.credentials),Fn=L(u=>u.setJustAuthenticated),Be=e==="discover"?g.filter(u=>!p.has(`${u.name}|${u.author.name}|${J}`)).map(u=>mr(u)):$.map(u=>pr(u)),{actionMenuOpen:pt,closeMenu:Ne,selectedIndex:Bn,clampIndex:Ue,filteredItems:Nn}=mn({listLength:Be.length}),me=Nn(Be),y=me[Bn]??null;useEffect(()=>{c();},[]),useEffect(()=>{Ue(me.length);},[me.length,Ue]),useEffect(()=>{mt&&Fe&&(n(`Authenticated. Tenant: ${Fe.tenant}`,"success"),Fn(false));},[mt,Fe]),useEffect(()=>{if(!t)return;let u=setTimeout(o,3e3);return ()=>clearTimeout(u)},[t]),useEffect(()=>{F&&(console.error("[usePluginCatalog]",F),l("Failed to load catalog"));},[F]);let ft=(u,Z)=>{n(u,Z);},pe=async(u,Z,Hn)=>{a(true,u),Ne();try{await Z();let fe=Be.length;Ue(fe),ft(Hn,"success");}catch(fe){ft(fe instanceof Error?fe.message:"Something went wrong","error");}finally{a(false);}},Un=()=>{if(!y)return;let u=g.find(Z=>Z.name===y.name);u&&pe(`Installing ${y.name}...`,()=>f(u),"\u2713 Installed successfully");},jn=()=>{y&&pe(`Uninstalling ${y.name}...`,()=>b(y.name),"\u2713 Uninstalled");},Kn=()=>{if(!y)return;let u=y.status==="enabled";pe(u?`Disabling ${y.name}...`:`Enabling ${y.name}...`,()=>Y(y.name),u?"\u2713 Disabled":"\u2713 Enabled");},_n=e==="installed"?"No plugins installed \u2014 explore the Discover tab!":d?`No plugins found for '${d}'`:"No plugins available in the catalog",qn=()=>{y&&pe(`Updating ${y.name}...`,()=>On(y.name),"\u2713 Updated");};return jsxs(Box,{flexDirection:"column",borderStyle:"round",paddingX:1,children:[jsx(St,{}),jsx(Et,{}),jsx(Bt,{filteredItems:me,emptyMessage:_n}),(r||O)&&jsx(Ht,{message:r?i:"Loading catalog..."}),s&&!O&&e==="discover"&&jsx(Vt,{message:s,onRetry:()=>{l(null),x();},onBack:()=>l(null)}),t&&jsx(jt,{message:t.message,type:t.type}),pt&&y&&e==="discover"&&jsx(Xt,{itemName:y.name,onInstall:Un,onClose:Ne}),pt&&y&&e==="installed"&&jsx(Zt,{itemName:y.name,itemStatus:y.status??"enabled",updateAvailable:y.updateAvailable??false,onUninstall:jn,onToggleStatus:Kn,onUpdate:qn,onClose:Ne}),jsx(Ut,{})]})}var In=P();if(In&&N()){let{clientSecret:e,...t}=In;L.getState().setCredentials(t),m.getState().setFocus("list");}else m.getState().setFocus("auth");function Pn(){return L(t=>t.isAuthenticated)?jsx(vn,{}):jsx(It,{})}function Me(e,t={}){return new Promise((n,o)=>{let{masked:r=false,defaultValue:i=""}=t;process.stdout.write(e+i);let s=i,a=g=>{if(g===""){l(),o(new Error("SIGINT"));return}if(g==="\r"||g===`
12
+ `){l(),process.stdout.write(`
13
+ `),n(s);return}if(g==="\x7F"){s.length>0&&(s=s.slice(0,-1),process.stdout.write("\b \b"));return}g.startsWith("\x1B")||(s+=g,process.stdout.write(r?"*".repeat(g.length):g));};function l(){process.stdin.setRawMode(false),process.stdin.removeListener("data",a),process.removeListener("uncaughtException",d),process.removeListener("unhandledRejection",d);}function d(){try{process.stdin.setRawMode(!1);}catch{}}process.on("uncaughtException",d),process.on("unhandledRejection",d),process.stdin.setRawMode(true),process.stdin.resume(),process.stdin.setEncoding("utf8"),process.stdin.on("data",a);})}async function gr(e){try{return await ne(e),process.stdout.write(h.green(` \u2713 Setup complete. Tenant: ${e.tenant}
14
+
15
+ `)),0}catch(t){let n=w(t instanceof Error?t.message:"unknown error");return process.stderr.write(h.red(` \u2717 Authentication failed: ${n}
16
+ `)),1}}async function hr(){try{process.stdout.write(`
17
+ `);let e=(await Me(h.cyan(" ? ")+"Client ID: ")).trim(),t=(await Me(h.cyan(" ? ")+"Client Secret: ",{masked:!0})).trim(),n=(await Me(h.cyan(" ? ")+"Tenant: ")).trim();if(!e||!t||!n)return process.stderr.write(h.red(` \u2717 All fields are required
18
+ `)),1;try{await ne({clientId:e,clientSecret:t,tenant:n});}catch(o){let r=w(o instanceof Error?o.message:"unknown error");return process.stderr.write(h.red(` \u2717 Authentication failed: ${r}
19
+ `)),1}return process.stdout.write(h.green(` \u2713 Setup complete. Tenant: ${n}
20
+
21
+ `)),0}catch(e){if(e instanceof Error&&e.message==="SIGINT")throw e;let t=w(e instanceof Error?e.message:"unknown error");return process.stderr.write(h.red(` \u2717 Unexpected error: ${t}
22
+ `)),1}}async function dt(e={}){if(e.clientId&&e.clientSecret&&e.tenant)return gr({clientId:e.clientId,clientSecret:e.clientSecret,tenant:e.tenant});try{return await hr()}catch(t){if(t instanceof Error&&t.message==="SIGINT")return process.stdout.write(`
23
+ `),130;throw t}}async function yr(){try{return process.stdout.write(`
24
+ `),(await Me(h.cyan(" ? ")+"Are you sure? This will remove your local credentials. (y/N): ")).toLowerCase()!=="y"?(process.stdout.write(h.yellow(` \u26A0 Logout cancelled
25
+ `)),0):null}catch(e){if(e instanceof Error&&e.message==="SIGINT")return process.stdout.write(`
26
+ `),130;throw e}}async function Sn(e){if(!P())return process.stdout.write(h.yellow(` \u26A0 You are not authenticated
27
+ `)),0;if(!e){let t=await yr();if(t!==null)return t}try{return wt(),process.stdout.write(h.green(` \u2713 Credentials removed successfully
28
+ `)),0}catch{return process.stderr.write(h.red(` \u2717 Error removing credentials
29
+ `)),1}}async function An(){let e=P();if(!e)return process.stdout.write(h.yellow(" \u26A0 Not authenticated. Run `npx @flow/cli` or `npx @flow/cli auth login` to set up.\n")),0;if(!N())return process.stdout.write(h.yellow(" \u26A0 Session expired. Run `npx @flow/cli auth login` to re-authenticate.\n")),0;let t=e.clientSecret.length>4?"****...****"+e.clientSecret.slice(-4):"****";return process.stdout.write(`
30
+ `+h.bold(` Authenticated
31
+ `)),process.stdout.write(h.dim(" Tenant: ")+w(e.tenant)+`
32
+ `),process.stdout.write(h.dim(" Client ID: ")+w(e.clientId)+`
33
+ `),process.stdout.write(h.dim(" Client Secret: ")+t+`
34
+ `),process.stdout.write(h.dim(" Config: ")+w(xt())+`
35
+ `),process.stdout.write(`
36
+ `),0}function _(){return P()?true:(v("Not authenticated. Run: flow-plugins auth"),false)}async function wr(e){let t=k(),n;try{n=await X();}catch{n=[];}let o=new Set(t.map(r=>r.name));return e.json?(Te(n.map(r=>({...r,installed:o.has(r.name)}))),0):(Ie(["","Name","Version","Category","Status"],n.map(r=>[o.has(r.name)?"*":" ",r.name,r.version,r.category,o.has(r.name)?"installed":"available"])),0)}function xr(e){let n=k().filter(o=>o.updateAvailable);return n.length===0?(C("All plugins are up to date."),0):e.json?(Te(n),0):(Ie(["Name","Installed","Available"],n.map(o=>[o.name,o.version,"\u2014"])),process.stdout.write(`
37
+ ${n.length} plugin(s) outdated.
38
+ `),0)}function br(e){let t=k();return t.length===0?(C("No plugins installed. Use `flow plugin install <name>` to install one."),0):e.json?(Te(t),0):(Ie(["Name","Version","Installed at"],t.map(n=>[n.name,n.version,Pe(n.installedAt)])),process.stdout.write(`
39
+ ${t.length} plugin(s) installed.
40
+ `),0)}async function En(e){if(!_())return 1;try{return e.available?await wr(e):e.outdated?xr(e):br(e)}catch(t){return v(t instanceof Error?t.message:"Failed to list plugins."),1}}function De(e){return e instanceof Error?e.message:String(e)}async function vr(e,t,n){n.silent||C(`${t}Installing ${e}...`);try{let o=await $e(e,n);return n.silent||z(`${t}${o.name} v${o.version} installed successfully`),{name:e,success:!0}}catch(o){if(o instanceof Q)return n.silent||C(`${t}${o.message}`),{name:e,success:true};let r=De(o);return v(`${t}Failed to install ${e}: ${r}`),{name:e,success:false,error:r}}}function Ir(e){let t=e.filter(o=>o.success).length,n=e.filter(o=>!o.success).length;C(`
41
+ Installation summary: ${t} succeeded, ${n} failed`);}async function Cn(e,t={}){if(!_())return 1;let n=[];for(let o=0;o<e.length;o++){let r=e.length>1?`[${o+1}/${e.length}] `:"",i=await vr(e[o],r,t);n.push(i);}return e.length>1&&!t.silent&&Ir(n),n.some(o=>!o.success)?1:0}async function Rn(e,t){if(!_())return 1;if(!t.force){let n=k().find(s=>s.name===e);if(!n)return v(`Plugin "${e}" is not installed`),1;let o=w(n.name),r=w(n.version);if(!await Tr(`Remove ${o} v${r}? [y/N] `))return C("Operation cancelled."),0}try{return await Ce(e),z("Plugin removed successfully"),0}catch(n){return v(De(n)),1}}async function Tr(e){let n=(await import('readline')).default.createInterface({input:process.stdin,output:process.stdout});return new Promise(o=>{n.question(e,r=>{n.close(),o(r.toLowerCase()==="y");});})}async function kn(e){if(!_())return 1;let t=k().find(n=>n.name===e);if(!t)return v(`Plugin "${e}" is not installed`),1;if(t.status==="enabled")return C(`${t.name} is already enabled`),0;try{return await ce(e,"enabled"),z(`${t.name} enabled successfully`),0}catch(n){return v(De(n)),1}}async function $n(e){if(!_())return 1;let t=k().find(n=>n.name===e);if(!t)return v(`Plugin "${e}" is not installed`),1;if(t.status==="disabled")return C(`${t.name} is already disabled`),0;try{return await ce(e,"disabled"),z(`${t.name} disabled successfully`),0}catch(n){return v(De(n)),1}}async function Ln(e){return _()?(z(`${e} updated successfully`),0):1}function Er(){return P()?{passed:true,message:"Credentials configured"}:{passed:false,message:"Credentials not configured \u2014 Run `auth login`"}}async function Cr(){let e=Date.now();try{await X();let t=Date.now()-e;return {passed:!0,message:`Prompt Manager accessible (${t}ms)`,latency:t}}catch(t){return t instanceof Error&&t.name==="TimeoutError"?{passed:false,message:"Timeout after 5s \u2014 check your connection"}:{passed:false,message:`Connection error: ${t instanceof Error?t.message:"Unknown"}`}}}function Rr(){return N()?{passed:true,message:"Token is valid"}:{passed:false,message:"Token expired \u2014 run `auth login` to re-authenticate"}}function kr(){let e=join(homedir(),".claude","settings.json");return existsSync(e)?{passed:true,message:"Claude Code detected"}:{passed:false,message:"Claude Code not detected"}}async function Mn(){process.stdout.write(`
42
+ `+h.bold(` FlowSetup CLI Diagnostics
43
+
44
+ `));let e=[{name:"Credentials",fn:Er},{name:"Prompt Manager",fn:Cr},{name:"Valid Token",fn:Rr},{name:"Claude Code",fn:kr}],t=true;for(let n of e){let o=await n.fn(),r="",i=h.green;o.passed?r=h.green("[OK] "):(r=h.red("[FAIL]"),i=h.red,t=false);let s=` ${r} ${i(n.name.padEnd(18))} ${w(o.message)}`;process.stdout.write(s+`
45
+ `);}return process.stdout.write(`
46
+ `),t?(process.stdout.write(h.green(` \u2713 All checks passed!
47
+
48
+ `)),0):(process.stdout.write(h.red(` \u2717 Some checks failed \u2014 verify your configuration
49
+
50
+ `)),1)}function Dn(e){let t=new Command;t.name("flow").description("FlowSetup CLI \u2014 Manage Flow plugins in your Claude Code").version(`@flow/cli v${we}`,"-V, --version","Display the CLI version").addHelpText("after",`
51
+ Without arguments, opens the interactive interface (TUI).
52
+ Use 'flow <command> --help' for details on each command.`).action(()=>{e();});let n=new Command("setup").description("Configure the CLI by detecting Claude Code credentials");n.command("init").description("Initialize the Flow CLI configuration").addHelpText("after",`
53
+ Examples:
54
+ $ flow setup init`).action(async()=>{let i=await dt();process.exit(i);}),t.addCommand(n);let o=new Command("plugin").description("Manage plugins from the Findr catalog installed in your Claude Code");o.command("list").description("List available or installed plugins").option("--available","show full catalog with installation status",false).option("--outdated","show only plugins with updates available",false).option("--json","output as JSON",false).addHelpText("after",`
55
+ Examples:
56
+ $ flow plugin list
57
+ $ flow plugin list --available
58
+ $ flow plugin list --outdated
59
+ $ flow plugin list --json`).action(async i=>{let s=await En(i);process.exit(s);}),o.command("install").description("Install one or more plugins from the Findr catalog into Claude Code").argument("<name...>","Name(s) of the plugin(s) to install (space-separated)").option("--force","Reinstall even if the version is already installed").option("--verbose","Display each step of the installation process").option("--silent","Output in JSON only").addHelpText("after",`
60
+ Examples:
61
+ $ flow plugin install flow-adr-writer
62
+ $ flow plugin install flow-adr-writer flow-prd-writer startup-pack-ai
63
+ $ flow plugin install flow-adr-writer --force
64
+ $ flow plugin install flow-adr-writer --silent`).action(async(i,s)=>{let a=await Cn(i,s);process.exit(a);}),o.command("uninstall").description("Remove an installed plugin from Claude Code").argument("<name>","Name of the plugin to remove").option("--force","Skip interactive confirmation").addHelpText("after",`
65
+ Examples:
66
+ $ flow plugin uninstall flow-adr-writer
67
+ $ flow plugin uninstall startup-pack-ai --force`).action(async(i,s)=>{let a=await Rn(i,s);process.exit(a);}),o.command("update").description("Update plugins to the latest version").argument("[name]","Name of the plugin to update (omit to update all)").addHelpText("after",`
68
+ Examples:
69
+ $ flow plugin update
70
+ $ flow plugin update flow-adr-writer`).action(async i=>{if(i){let s=await Ln(i);process.exit(s);}else console.log("Update all plugins coming soon."),process.exit(0);}),o.command("enable").description("Enable an installed plugin").argument("<name>","Name of the plugin to enable").addHelpText("after",`
71
+ Examples:
72
+ $ flow plugin enable flow-adr-writer`).action(async i=>{let s=await kn(i);process.exit(s);}),o.command("disable").description("Disable an installed plugin").argument("<name>","Name of the plugin to disable").addHelpText("after",`
73
+ Examples:
74
+ $ flow plugin disable flow-adr-writer`).action(async i=>{let s=await $n(i);process.exit(s);}),t.addCommand(o);let r=new Command("auth").description("Manage authentication credentials");return r.command("login").description("Authenticate and save credentials locally").option("--client-id <id>","Client ID for non-interactive authentication").option("--client-secret <secret>","Client Secret for non-interactive authentication").option("--tenant <tenant>","Tenant for non-interactive authentication").addHelpText("after",`
75
+ Examples:
76
+ $ flow auth login
77
+ $ flow auth login --client-id ID --client-secret SECRET --tenant TENANT`).action(async i=>{let s=await dt(i);process.exit(s);}),r.command("logout").description("Remove locally saved credentials").option("--force","Skip interactive confirmation").addHelpText("after",`
78
+ Examples:
79
+ $ flow auth logout
80
+ $ flow auth logout --force`).action(async i=>{let s=await Sn(i.force);process.exit(s);}),r.command("status").description("Display the current authentication status").addHelpText("after",`
81
+ Examples:
82
+ $ flow auth status`).action(async()=>{let i=await An();process.exit(i);}),t.addCommand(r),t.command("health").description("Check the CLI configuration and connectivity").addHelpText("after",`
83
+ Examples:
84
+ $ flow health`).action(async()=>{let i=await Mn();process.exit(i);}),t}var Lr=Dn(()=>{render(jsx(Pn,{}));});Lr.parse();
package/package.json ADDED
@@ -0,0 +1,97 @@
1
+ {
2
+ "name": "@ciandt-flow/cli",
3
+ "version": "1.0.1",
4
+ "description": "TUI for browsing and installing Claude Code plugins from the Flow ecosystem",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "flow": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "license": "SEE LICENSE IN LICENSE.md",
14
+ "publishConfig": {
15
+ "access": "public",
16
+ "registry": "https://registry.npmjs.org"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/CI-T-HyperX/flow-plugins-cli"
21
+ },
22
+ "scripts": {
23
+ "build": "npm run typecheck && tsup && npm run obfuscate",
24
+ "build:npm": "npm run typecheck && tsup",
25
+ "obfuscate": "javascript-obfuscator dist/index.js --output dist/index.js --options-preset medium-obfuscation --string-array true --string-array-encoding rc4",
26
+ "typecheck": "tsc --noEmit",
27
+ "dev": "tsx --env-file=.env src/cli.tsx",
28
+ "dev:watch": "tsup --watch & sleep 2 && node --watch dist/index.js",
29
+ "start": "node dist/index.js",
30
+ "test": "vitest run --coverage",
31
+ "test:watch": "vitest",
32
+ "lint": "eslint src/**/*.{ts,tsx} tests/**/*.{ts,tsx}",
33
+ "lint:fix": "eslint src/**/*.{ts,tsx} tests/**/*.{ts,tsx} --fix",
34
+ "prettier": "prettier --write \"src/**/*.{ts,tsx,js,json}\" \"tests/**/*.{ts,tsx}\"",
35
+ "format": "npm run prettier && npm run lint:fix",
36
+ "prepare": "husky",
37
+ "changeset": "changeset",
38
+ "version-packages": "changeset version",
39
+ "release": "npm run build && changeset publish",
40
+ "release:npm": "npm run build:npm && changeset publish",
41
+ "security-scan": "trufflehog filesystem src/ --fail --no-update",
42
+ "prepublishOnly": "npm run security-scan"
43
+ },
44
+ "keywords": [
45
+ "cli",
46
+ "plugins",
47
+ "flow",
48
+ "claude",
49
+ "tui"
50
+ ],
51
+ "author": "CI&T <davi.peterlini@ciandt.com>",
52
+ "dependencies": {
53
+ "chalk": "5.6.2",
54
+ "commander": "11.1.0",
55
+ "conf": "15.1.0",
56
+ "extract-zip": "2.0.1",
57
+ "ink": "6.8.0",
58
+ "ink-big-text": "2.0.0",
59
+ "ink-gradient": "4.0.0",
60
+ "ink-spinner": "5.0.0",
61
+ "ky": "1.14.3",
62
+ "proper-lockfile": "4.1.2",
63
+ "react": "19.2.4",
64
+ "zustand": "5.0.12"
65
+ },
66
+ "devDependencies": {
67
+ "@changesets/changelog-github": "^0.6.0",
68
+ "@changesets/cli": "^2.30.0",
69
+ "@eslint/js": "^9.39.2",
70
+ "@types/node": "^20.10.6",
71
+ "@types/proper-lockfile": "^4.1.4",
72
+ "@types/react": "^19.0.0",
73
+ "@vitejs/plugin-react": "^4.0.0",
74
+ "@vitest/coverage-v8": "^2.0.0",
75
+ "eslint": "^9.39.2",
76
+ "eslint-config-prettier": "^10.1.8",
77
+ "eslint-plugin-prettier": "^5.5.5",
78
+ "husky": "^9.1.7",
79
+ "ink-testing-library": "^4.0.0",
80
+ "javascript-obfuscator": "^5.4.1",
81
+ "prettier": "^3.8.1",
82
+ "tsup": "^8.0.0",
83
+ "tsx": "^4.7.0",
84
+ "typescript": "^5.3.3",
85
+ "typescript-eslint": "^8.54.0",
86
+ "vitest": "^2.0.0"
87
+ },
88
+ "engines": {
89
+ "node": ">=22.0.0"
90
+ },
91
+ "overrides": {
92
+ "ajv": "^6.12.6",
93
+ "flatted": ">=3.4.2",
94
+ "esbuild": ">=0.25.0",
95
+ "picomatch": ">=4.0.4"
96
+ }
97
+ }