@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 +13 -0
- package/README.md +245 -0
- package/dist/index.js +84 -0
- package/package.json +97 -0
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
|
+
}
|