@redreplier/cli 0.1.0
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 +21 -0
- package/README.md +438 -0
- package/dist/alert-ByDPHwjO.js +1 -0
- package/dist/api-Dcrz3Tbv.js +3 -0
- package/dist/auth-DYgYo6Le.js +1 -0
- package/dist/chunk-B_5Rb6Gi.js +1 -0
- package/dist/cli.js +4 -0
- package/dist/client-R8kBz1Ou.js +1 -0
- package/dist/color-BbAK7_Y7.js +1 -0
- package/dist/color-D6__4GPX.js +1 -0
- package/dist/completion-DueI7liE.js +81 -0
- package/dist/config-Bg2ls0gu.js +1 -0
- package/dist/config-DwEEtXDc.js +1 -0
- package/dist/config-_SbTs7a5.js +1 -0
- package/dist/doctor-BpYdCjDb.js +1 -0
- package/dist/errors-DZ-S25mn.js +1 -0
- package/dist/errors-Dkv23Z0t.js +1 -0
- package/dist/esm-CIPTesEn.js +19 -0
- package/dist/exit-codes-Dth9pkcT.js +1 -0
- package/dist/exit-codes-IaZq17dv.js +1 -0
- package/dist/http-Cwj8ofnO.js +1 -0
- package/dist/http-DpMu6AKn.js +2 -0
- package/dist/keyword-BjASkU-P.js +1 -0
- package/dist/keyword-DFHGBpip.js +1 -0
- package/dist/mcp-9bwQeg1V.js +3 -0
- package/dist/mention-Dv8hocI7.js +1 -0
- package/dist/open-_iWw6bFQ.js +1 -0
- package/dist/open-url-CovrlP9b.js +1 -0
- package/dist/output-CwNjSyRl.js +1 -0
- package/dist/output-uP7GrsdR.js +5 -0
- package/dist/product-CXL7Ga4M.js +1 -0
- package/dist/prompt-BMmbLZHM.js +1 -0
- package/dist/prompt-DR68wwvQ.js +45 -0
- package/dist/site-ChYUR_ES.js +1 -0
- package/dist/time-B5S7M7Dg.js +1 -0
- package/dist/types-Bu8scNHe.js +1 -0
- package/dist/update-check-BaOMxkuv.js +1 -0
- package/dist/update-check-DBvD8xG2.js +1 -0
- package/dist/version--HODInrs.js +1 -0
- package/dist/version-DwIEu3-z.js +1 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 RedReplier
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
# RedReplier CLI
|
|
2
|
+
|
|
3
|
+
Watch Reddit, Hacker News, X and Bluesky for your keywords, from your terminal.
|
|
4
|
+
|
|
5
|
+
`redreplier` talks to the RedReplier REST API and nothing else. It registers the sites you monitor, manages the keywords they listen for, lists and scores the lead mentions those keywords found, explains why a mention scored what it did, and tails new mentions into any script you want to run on them. Everything prints as a table for humans or as one JSON document for `jq`.
|
|
6
|
+
|
|
7
|
+
Runs on Node 22.12 or newer. Also ships as a standalone binary with no Node requirement.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# npm, the usual path
|
|
13
|
+
npm install -g @redreplier/cli
|
|
14
|
+
|
|
15
|
+
# or run it without installing
|
|
16
|
+
npx -p @redreplier/cli redreplier --help
|
|
17
|
+
|
|
18
|
+
# macOS and Linux, standalone binary, no Node required
|
|
19
|
+
brew install RedReplier/tap/redreplier
|
|
20
|
+
|
|
21
|
+
# or
|
|
22
|
+
curl -fsSL https://redreplier.com/install.sh | sh
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`npx @redreplier/cli` prompts instead of running, because npx resolves a bin named after the unscoped package. Use `npx -p @redreplier/cli redreplier`. There is no unscoped `redreplier` package, so plain `npx redreplier` will not find anything.
|
|
26
|
+
|
|
27
|
+
The install script downloads the release archive for your platform, verifies its checksum, and puts the binary in `~/.local/bin`. It never edits your shell rc files; it prints the `export PATH` line for you to add. Override the destination with `REDREPLIER_INSTALL_DIR`.
|
|
28
|
+
|
|
29
|
+
Both bins run the same program. `rr` is the short one.
|
|
30
|
+
|
|
31
|
+
## Authentication
|
|
32
|
+
|
|
33
|
+
Create an API token at <https://redreplier.com/api-tokens> and hand it to `login`:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
redreplier login
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The command opens the token page, reads the token from a hidden prompt, checks the `redreplier_` prefix locally, verifies it with one `GET /websites`, and writes it to `~/.config/redreplier/credentials.json` at mode 0600. Nothing is written if verification fails.
|
|
40
|
+
|
|
41
|
+
Token creation is gated behind the `api_access` subscription feature. On the free plan the token page answers with an error instead of a token, which is a plan limit and not a CLI fault. Plans are at <https://redreplier.com/billing>.
|
|
42
|
+
|
|
43
|
+
For CI, pipe the token in and skip the prompt:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
echo "$RR_TOKEN" | redreplier login --token-stdin --profile ci
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or skip `login` entirely and export `REDREPLIER_API_TOKEN`. The CLI reads it on every command.
|
|
50
|
+
|
|
51
|
+
There is no OAuth or device-code flow yet. There is also no `/me` endpoint on the API, so `whoami` reconstructs your identity from the cheapest authenticated listing and says where each field came from.
|
|
52
|
+
|
|
53
|
+
`logout` removes the profile from the credentials file. It does not revoke the token; do that in the dashboard.
|
|
54
|
+
|
|
55
|
+
## Quick start
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
$ redreplier login
|
|
59
|
+
Opening https://redreplier.com/api-tokens in your browser.
|
|
60
|
+
Create a token, then paste it here.
|
|
61
|
+
|
|
62
|
+
Token (starts with redreplier_): ****************************
|
|
63
|
+
|
|
64
|
+
✓ Token valid
|
|
65
|
+
profile default
|
|
66
|
+
sites 2 monitored
|
|
67
|
+
keywords 16 active, 3 pending
|
|
68
|
+
stored in ~/.config/redreplier/credentials.json (0600)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
$ redreplier site create --url https://acme.com -k "reddit monitoring" -k "lead finder"
|
|
73
|
+
! No --description given. The server will scrape acme.com and spend one AI
|
|
74
|
+
generation from this month's quota. Continue? [Y/n] y
|
|
75
|
+
|
|
76
|
+
✓ Created ws_4f21… acme.com
|
|
77
|
+
description generated (412 chars)
|
|
78
|
+
keywords 2 active, 0 pending
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
$ redreplier mention list --bucket HIGH --bucket VERY_HIGH --sort RELEVANCE --limit 5
|
|
83
|
+
SCORE SOURCE WHEN KEYWORD TITLE
|
|
84
|
+
92 r/SaaS 2h ago reddit monitoring How do you track brand mentions on Reddit?
|
|
85
|
+
84 HACKERNEWS 5h ago social listening Ask HN: tools for monitoring keywords
|
|
86
|
+
77 r/marketing 9h ago reddit leads Best way to find leads without spamming
|
|
87
|
+
71 BLUESKY 1d ago reddit monitoring anyone using a reddit alert tool?
|
|
88
|
+
68 TWITTER 1d ago social listening looking for a mention tracker
|
|
89
|
+
|
|
90
|
+
5 of 213 · open: redreplier mention show <id>
|
|
91
|
+
ℹ REJECTED mentions are hidden unless you pass --status REJECTED.
|
|
92
|
+
ℹ 41 mentions below the site's minimum score are hidden. Add --include-low.
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## The parts worth the install
|
|
96
|
+
|
|
97
|
+
**Tail mentions into a script.** `mention tail` is the reason this CLI exists. It polls `GET /mentions?sort=RECENT` on a timer, dedupes on mention id through an LRU of 10,000, and prints each new one the moment it arrives. On an empty poll the interval grows by 1.5x to a 300 s ceiling, and any new mention drops it back to the floor, so an idle workspace costs a handful of requests an hour.
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
$ redreplier mention tail --min-score 70 --exec './notify.sh'
|
|
101
|
+
Tailing new mentions for 2 sites, score ≥ 70, every 60s. Ctrl-C to stop.
|
|
102
|
+
12:04:11 92 r/SaaS How do you track brand mentions on Reddit?
|
|
103
|
+
12:09:14 78 HACKERNEWS Ask HN: tools for monitoring keywords
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`--exec` runs your command once per mention, with the mention as JSON on the child's stdin and the fields in its environment as `RR_ID`, `RR_SCORE`, `RR_URL`, `RR_SOURCE`, `RR_KEYWORD` and `RR_TITLE`. Children run one at a time in arrival order, so a slow notifier cannot fan out. A non-zero exit prints a warning and the tail keeps going. Add `--approve-on-exec-success` and a zero exit sets the mention to `APPROVED`, which turns the tail into a review queue driven by whatever script you already have.
|
|
107
|
+
|
|
108
|
+
In machine mode the output is newline-delimited JSON, one object per mention:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
redreplier mention tail --json | jq -r 'select(.relevanceScore > 80) | .url'
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Read one mention properly.** There is no single-mention GET on the API, so `mention show <id>` pages the list until it finds the id, or goes straight through `POST /mentions/{id}/explain` with `--explain`. Either way you get the thread, the score, the reason it scored that way and the suggested reply in one screen.
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
$ redreplier mention show 8f2c1b0e
|
|
118
|
+
r/SaaS · 2h ago · score 92 (VERY_HIGH) · NEW
|
|
119
|
+
keyword reddit monitoring
|
|
120
|
+
author u/throwaway_pm
|
|
121
|
+
url https://reddit.com/r/SaaS/comments/…
|
|
122
|
+
|
|
123
|
+
How do you track brand mentions on Reddit?
|
|
124
|
+
|
|
125
|
+
We keep missing threads about us until someone screenshots them in Slack…
|
|
126
|
+
|
|
127
|
+
why it scored 92
|
|
128
|
+
Directly asks for the category of tool acme.com sells, in a subreddit
|
|
129
|
+
where self-promotion is tolerated when it answers the question.
|
|
130
|
+
|
|
131
|
+
suggested reply
|
|
132
|
+
We built acme for exactly this…
|
|
133
|
+
|
|
134
|
+
approve: redreplier mention status 8f2c1b0e APPROVED
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Keyword commands that tell you what they cost.** Adding a keyword with no free slot lands it as `PENDING` rather than charging you, and the CLI prints that instead of implying a purchase. `keyword plan --count 25` prices the plan an absolute account-wide total of 25 active keywords would need, and ends by pointing at the web app, because no endpoint changes a plan.
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
$ redreplier keyword plan --count 25
|
|
141
|
+
CURRENT TARGET
|
|
142
|
+
Starter $19/mo Growth $49/mo (25 keywords)
|
|
143
|
+
|
|
144
|
+
upgrade yes
|
|
145
|
+
charged now $23.40 (prorated)
|
|
146
|
+
payment needed yes
|
|
147
|
+
|
|
148
|
+
Plans change in the RedReplier app: https://redreplier.com/billing
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Destructive commands that make you type.** `keyword delete` also deletes every mention that keyword produced, with no undo and no refund, so it asks you to type DELETE and offers `keyword disable` first. `site delete` asks you to type the domain. `--yes` skips both; `--no-input` without `--yes` refuses with exit 2 rather than guessing.
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
$ redreplier keyword delete kw_7c19
|
|
155
|
+
✗ Deleting "reddit monitoring" also deletes 1,204 mentions it produced.
|
|
156
|
+
There is no undo and no refund. To pause instead, keeping the mentions:
|
|
157
|
+
redreplier keyword disable kw_7c19
|
|
158
|
+
|
|
159
|
+
Type DELETE to confirm: DELETE
|
|
160
|
+
✓ Deleted
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Commands
|
|
164
|
+
|
|
165
|
+
Grammar is noun then verb, space-separated. `ls` works wherever `list` does, `rm` wherever `delete` does, and `view` wherever `get` does.
|
|
166
|
+
|
|
167
|
+
### site
|
|
168
|
+
|
|
169
|
+
| Command | Key flags | Notes |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| `site list` | | Every monitored site, its keyword counts and whether it has a description |
|
|
172
|
+
| `site get <id>` | | The site plus its full keyword table with statuses |
|
|
173
|
+
| `site create` | `--url`, `--name`, `-k/--keyword`, `--description`, `--no-analyze` | Without `--description` the server scrapes the URL and spends one AI generation, so it confirms first. `--description @file` reads a file and `-` reads stdin |
|
|
174
|
+
| `site update <id>` | `--name`, `--description` | Existing mentions are not rescored |
|
|
175
|
+
| `site delete <id>` | `--yes` | Deletes the site, its keywords and every mention they produced |
|
|
176
|
+
| `site analyze` | `--url`, `--yes` | Generates a description. Human mode prints it to stdout alone; a pipe is machine mode and gets the JSON envelope, so pipe it with `REDREPLIER_FORCE_TTY=1` or read `.data.description` with `jq` |
|
|
177
|
+
|
|
178
|
+
A site with no description has its mentions left unscored, and `site list` says so with the command that fixes it:
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
$ redreplier site list
|
|
182
|
+
ID DOMAIN NAME KEYWORDS DESCRIPTION
|
|
183
|
+
ws_4f21… acme.com Acme 12 active, 3 pending yes
|
|
184
|
+
ws_9b03… acmedocs.io Acme Docs 4 active missing
|
|
185
|
+
|
|
186
|
+
⚠ acmedocs.io has no description. Its mentions are not scored.
|
|
187
|
+
fix: redreplier site analyze --url https://acmedocs.io
|
|
188
|
+
Listing sites activates any PENDING keyword that fits your plan's free headroom.
|
|
189
|
+
3 keywords still PENDING, there was no free slot. Price an upgrade: redreplier keyword plan
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Reading the site list promotes `PENDING` keywords that fit free headroom, which is a server-side effect of the endpoint. The response carries no before-and-after, so the CLI states the rule and how many keywords are still `PENDING` rather than claiming a number was just promoted.
|
|
193
|
+
|
|
194
|
+
### keyword
|
|
195
|
+
|
|
196
|
+
| Command | Key flags | Notes |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| `keyword add <siteId> <keyword...>` | | Trimmed, lowercased and de-duplicated before sending, so the preview matches what the server stores |
|
|
199
|
+
| `keyword edit <id>` | `--value` | Free and unlimited, and it keeps the paid slot. A case-only change is a no-op |
|
|
200
|
+
| `keyword disable <id>` | | Stops collecting, keeps the mentions, never charges |
|
|
201
|
+
| `keyword enable <id>` | | Lands `PENDING` when no slot is free |
|
|
202
|
+
| `keyword delete <id>` | `--yes` | Deletes every mention the keyword produced. Type DELETE to confirm |
|
|
203
|
+
| `keyword activate-pending` | | Promotes pending keywords into free slots. Overflow stays pending |
|
|
204
|
+
| `keyword plan` | `--count` | `--count` is an account-wide total of active keywords, not an increment |
|
|
205
|
+
| `keyword usage` | | Keyword edits are unlimited on every current plan, and the output says that rather than drawing a meter |
|
|
206
|
+
|
|
207
|
+
`keyword` also answers to `keywords` and `kw`.
|
|
208
|
+
|
|
209
|
+
### mention
|
|
210
|
+
|
|
211
|
+
| Command | Key flags | Notes |
|
|
212
|
+
|---|---|---|
|
|
213
|
+
| `mention list` | `--site`, `--status`, `--bucket`, `--include-low`, `--keyword`, `--source`, `--sort`, `--from`, `--to`, `--limit`, `--offset`, `--all` | Repeat `--status`, `--bucket`, `--keyword` and `--source` for several values |
|
|
214
|
+
| `mention count` | The same filters | `{"total": N}` in machine mode, the API payload unchanged |
|
|
215
|
+
| `mention show <id>` | `--explain`, plus the filters | Pages the list to find the id, or fetches it through `explain` |
|
|
216
|
+
| `mention status <id> <status>` | | `NEW`, `APPROVED` or `REJECTED`, case-insensitive. Reversible, so no confirmation |
|
|
217
|
+
| `mention explain <id>` | | The first call generates and is slow, later calls are instant reads |
|
|
218
|
+
| `mention tail` | `--min-score`, `--interval`, `--since`, `--exec`, `--approve-on-exec-success`, plus the filters | `--interval` has a floor of 30 s |
|
|
219
|
+
|
|
220
|
+
Enums, in full:
|
|
221
|
+
|
|
222
|
+
| Flag | Values |
|
|
223
|
+
|---|---|
|
|
224
|
+
| `--status` | `NEW`, `APPROVED`, `REJECTED` |
|
|
225
|
+
| `--bucket` | `VERY_LOW`, `LOW`, `MEDIUM`, `HIGH`, `VERY_HIGH` |
|
|
226
|
+
| `--source` | `REDDIT_POST`, `REDDIT_COMMENT`, `TWITTER`, `BLUESKY`, `HACKERNEWS`, `FACEBOOK`, `FACEBOOK_GROUP` |
|
|
227
|
+
| `--sort` | `RELEVANCE`, `RECENT` |
|
|
228
|
+
|
|
229
|
+
Two rows are hidden by default and both are surfaced in the footer when they could be biting: `REJECTED` mentions need `--status REJECTED`, and mentions below the site's minimum score need `--include-low`. `--limit` runs from 1 to 500 and is validated before the request. `--from` and `--to` filter ingestion time, not the time the thread was posted.
|
|
230
|
+
|
|
231
|
+
`mention` also answers to `mentions`.
|
|
232
|
+
|
|
233
|
+
### alerts
|
|
234
|
+
|
|
235
|
+
| Command | Key flags | Notes |
|
|
236
|
+
|---|---|---|
|
|
237
|
+
| `alerts get` | | Whether alerts are on, the cadence, and the cadences this plan allows |
|
|
238
|
+
| `alerts set` | `--on` or `--off`, `--cadence` | `PUT /alert-settings` is a full replace, so the CLI reads the current cadence and resends it unless `--cadence` is given |
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
$ redreplier alerts get
|
|
242
|
+
enabled yes
|
|
243
|
+
cadence 60m
|
|
244
|
+
fastest 30m on this plan
|
|
245
|
+
options 30, 60, 120, 180, 240, 720, 1440
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
`--cadence` accepts 15, 30, 60, 120, 180, 240, 720 or 1440 minutes and an unlisted value is rejected with exit 2 before the request, because the server's 400 for this is generic. `availableCadences` in the response is what your plan allows, which is a subset of those eight.
|
|
249
|
+
|
|
250
|
+
### Everywhere else
|
|
251
|
+
|
|
252
|
+
| Command | Key flags | Notes |
|
|
253
|
+
|---|---|---|
|
|
254
|
+
| `login` | `--token-stdin`, `--profile`, `--api-url`, `--name` | |
|
|
255
|
+
| `logout` | `--profile`, `--all` | |
|
|
256
|
+
| `whoami` | | Prints which source each of the token and the API URL came from |
|
|
257
|
+
| `config list \| get \| set \| unset \| path` | | Reads and writes `config.json` |
|
|
258
|
+
| `open [what] [id]` | | `dashboard`, `mentions`, `mention <id>`, `sites`, `site <id>`, `keywords`, `tokens`, `billing` |
|
|
259
|
+
| `doctor` | `--json` | Node version, files, permissions, token, API reachability, rate limit |
|
|
260
|
+
| `completion <shell>` | | `bash`, `zsh`, `fish`, `powershell` |
|
|
261
|
+
| `mcp` | `--client`, `--local`, `--install` | Prints or names the file for the MCP client config |
|
|
262
|
+
| `api <method> <path>` | `-q/--query`, `-d/--data`, `-H/--header`, `-i/--include` | Raw authenticated request |
|
|
263
|
+
|
|
264
|
+
`api` is the escape hatch. A command we have not written yet never blocks you:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
redreplier api GET /mentions -q limit=5 -q statuses=NEW
|
|
268
|
+
redreplier api PATCH /mentions/8f2c1b0e/status -d '{"status":"APPROVED"}'
|
|
269
|
+
redreplier api PUT /alert-settings -d @alerts.json
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Repeat `-q` with the same key for an array parameter. `-d @-` reads the body from stdin. An `Authorization` header is rejected, because the token comes from the resolved profile and the CLI never sends it anywhere but the configured API host.
|
|
273
|
+
|
|
274
|
+
`mean-marketer` exists, is experimental, and rides on four endpoints that are in no OpenAPI document. It is hidden from `--help` and listed under `--help-all`, and every line it prints carries that warning.
|
|
275
|
+
|
|
276
|
+
## Global flags
|
|
277
|
+
|
|
278
|
+
Accepted at any position.
|
|
279
|
+
|
|
280
|
+
| Flag | Default | Meaning |
|
|
281
|
+
|---|---|---|
|
|
282
|
+
| `-p, --profile <name>` | `current` | Credential profile |
|
|
283
|
+
| `--json` | auto | Force machine mode |
|
|
284
|
+
| `-q, --quiet` | false | No spinners, hints or notices |
|
|
285
|
+
| `--no-color` | auto | Strip ANSI |
|
|
286
|
+
| `--debug` | false | Request log to stderr, token redacted |
|
|
287
|
+
| `--api-url <url>` | production | Override the base URL, for staging |
|
|
288
|
+
| `--token <token>` | resolved | One-shot token, highest precedence |
|
|
289
|
+
| `-y, --yes` | false | Skip confirmation prompts |
|
|
290
|
+
| `--no-input` | auto | Never prompt; fail with exit 2 instead |
|
|
291
|
+
| `--lang <code>` | unset | `en`, `fr`, `de`, `es`, `pt` |
|
|
292
|
+
| `--full-ids` | false | Print identifiers in full instead of truncating |
|
|
293
|
+
| `-V, --version` | | Prints `redreplier/1.3.2 node-v22.14.0 darwin-arm64` |
|
|
294
|
+
| `-h, --help` | | `--help-all` adds the experimental commands |
|
|
295
|
+
|
|
296
|
+
`--no-input` is implied when stdin is not a TTY.
|
|
297
|
+
|
|
298
|
+
## JSON output
|
|
299
|
+
|
|
300
|
+
Output mode is chosen for you. Human tables when stdout is a TTY, one JSON document when it is not, when `--json` is passed, when `CI` is set, or when `REDREPLIER_JSON=1`. So `redreplier mention list | jq '.data[0].id'` works with no flag.
|
|
301
|
+
|
|
302
|
+
```json
|
|
303
|
+
{
|
|
304
|
+
"ok": true,
|
|
305
|
+
"command": "mention.list",
|
|
306
|
+
"data": [{ "id": "8f2c1b0e", "relevanceScore": 92, "...": "..." }],
|
|
307
|
+
"meta": {
|
|
308
|
+
"total": 213,
|
|
309
|
+
"limit": 50,
|
|
310
|
+
"offset": 0,
|
|
311
|
+
"hasMore": true,
|
|
312
|
+
"rateLimit": { "limit": 600, "remaining": 598, "resetSeconds": 41 }
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Four rules that will not change without a major version:
|
|
318
|
+
|
|
319
|
+
1. `data` is the API's payload unmodified. No renamed keys, no computed fields.
|
|
320
|
+
2. `data` is an array for list commands, an object for single-object commands, `null` for commands with no payload.
|
|
321
|
+
3. `meta` carries paging and rate-limit information and nothing else.
|
|
322
|
+
4. Errors go to stderr as `{ "ok": false, "command": "...", "error": { "code", "status", "message", "details" } }` and the process exits non-zero. stdout stays clean.
|
|
323
|
+
|
|
324
|
+
`mention tail` is the one exception to rule 1's document shape: it streams newline-delimited JSON, one mention per line, because a tail has no end at which to close a document.
|
|
325
|
+
|
|
326
|
+
In human mode every spinner, prompt, hint, warning and notice goes to stderr, so a pipe never swallows them and never receives them. `REDREPLIER_FORCE_TTY=1` keeps human output when piped, which is how the tables in this file were captured.
|
|
327
|
+
|
|
328
|
+
Add `--json` to a list command and the available field names are printed to stderr, so `redreplier mention list --json 2>&1 >/dev/null` documents the shape.
|
|
329
|
+
|
|
330
|
+
## Exit codes
|
|
331
|
+
|
|
332
|
+
| Code | Meaning |
|
|
333
|
+
|---|---|
|
|
334
|
+
| 0 | Success |
|
|
335
|
+
| 1 | Generic failure, including 5xx |
|
|
336
|
+
| 2 | Usage error: bad flag, missing argument, unknown enum, prompt needed under `--no-input` |
|
|
337
|
+
| 3 | Auth failure: 401, 403, missing or malformed token |
|
|
338
|
+
| 4 | Not found: 404, or a mention that is not visible in this workspace |
|
|
339
|
+
| 5 | Validation or other 400 |
|
|
340
|
+
| 6 | Conflict: 409 |
|
|
341
|
+
| 7 | Rate limited: 429 after retries |
|
|
342
|
+
| 8 | Network failure or timeout |
|
|
343
|
+
| 9 | Quota or plan limit: 402, no AI generations left |
|
|
344
|
+
| 130 | Interrupted with Ctrl-C |
|
|
345
|
+
|
|
346
|
+
## Configuration and profiles
|
|
347
|
+
|
|
348
|
+
Two files, on macOS and Linux under `${XDG_CONFIG_HOME:-$HOME/.config}/redreplier/` and on Windows under `%APPDATA%\RedReplier\`:
|
|
349
|
+
|
|
350
|
+
- `credentials.json`, mode 0600, tokens only. Written to a temp file and renamed over the target, so a crash never leaves a half-written or world-readable file. The CLI refuses to read it when the mode is group or world readable and prints the `chmod 600` line.
|
|
351
|
+
- `config.json`, mode 0644, preferences only. Safe to commit to a dotfiles repo.
|
|
352
|
+
|
|
353
|
+
A profile is a token plus the API URL it belongs to. Use them for several workspaces or for staging:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
redreplier login --profile acme --name "Acme workspace"
|
|
357
|
+
redreplier mention list --profile acme
|
|
358
|
+
export REDREPLIER_PROFILE=acme
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Profile resolution: `--profile`, then `REDREPLIER_PROFILE`, then `current` in `credentials.json`, then `default`.
|
|
362
|
+
|
|
363
|
+
Token resolution, highest first: `--token`, `REDREPLIER_API_TOKEN`, `REDREPLIER_API_KEY`, the resolved profile.
|
|
364
|
+
|
|
365
|
+
Base URL resolution, highest first: `--api-url`, `REDREPLIER_API_URL`, the profile's stored `apiUrl`, the compiled-in default. A base URL is only ever read from your own flags, environment or config file. The CLI never takes a host from an API response.
|
|
366
|
+
|
|
367
|
+
`config` keys:
|
|
368
|
+
|
|
369
|
+
| Key | Meaning |
|
|
370
|
+
|---|---|
|
|
371
|
+
| `defaultSite` | Default for `--site` |
|
|
372
|
+
| `language` | Default `x-language` header |
|
|
373
|
+
| `updateCheck` | Set false to turn off the daily version check |
|
|
374
|
+
| `workspaceId` | The workspace this profile talks to |
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
redreplier config set defaultSite ws_4f21
|
|
378
|
+
redreplier config set language en
|
|
379
|
+
redreplier config list
|
|
380
|
+
redreplier config path
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Run `redreplier doctor` when something is off. It checks the Node version, both files and their permissions, the token, API reachability and latency, the OpenAPI document, the rate-limit budget and any proxy variables, and prints the fix for each failure. It exits 1 when any check fails and supports `--json`.
|
|
384
|
+
|
|
385
|
+
## Environment variables
|
|
386
|
+
|
|
387
|
+
| Variable | Meaning |
|
|
388
|
+
|---|---|
|
|
389
|
+
| `REDREPLIER_API_TOKEN` | API token. Read first |
|
|
390
|
+
| `REDREPLIER_API_KEY` | Same thing, accepted for compatibility with the MCP server and skills |
|
|
391
|
+
| `REDREPLIER_API_URL` | Base URL. Defaults to `https://ai.redreplier.com/ai-app/api/v1` |
|
|
392
|
+
| `REDREPLIER_PROFILE` | Profile name |
|
|
393
|
+
| `REDREPLIER_DEBUG` | Set to `1` for the request log on stderr |
|
|
394
|
+
| `REDREPLIER_JSON` | Set to `1` to force machine output |
|
|
395
|
+
| `REDREPLIER_FORCE_TTY` | Set to `1` to keep human output when piped |
|
|
396
|
+
| `REDREPLIER_NO_UPDATE_CHECK` | Set to any value to skip the daily registry check |
|
|
397
|
+
| `REDREPLIER_INSTALL_DIR` | Destination for the curl installer. Defaults to `~/.local/bin` |
|
|
398
|
+
| `NO_COLOR`, `FORCE_COLOR` | Honoured as usual |
|
|
399
|
+
| `CI` | When set, machine output and no update check |
|
|
400
|
+
|
|
401
|
+
Both token spellings work and `doctor` names the one in use. There is no third spelling; if you find one in older docs, it is wrong.
|
|
402
|
+
|
|
403
|
+
## Shell completion
|
|
404
|
+
|
|
405
|
+
The script is generated from the live command tree at runtime, so it cannot go stale. Installation instructions print to stderr, which is why `eval` on the stdout works directly.
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
# zsh
|
|
409
|
+
eval "$(redreplier completion zsh)"
|
|
410
|
+
|
|
411
|
+
# bash
|
|
412
|
+
eval "$(redreplier completion bash)"
|
|
413
|
+
|
|
414
|
+
# fish
|
|
415
|
+
redreplier completion fish | source
|
|
416
|
+
|
|
417
|
+
# powershell
|
|
418
|
+
redreplier completion powershell | Out-String | Invoke-Expression
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Add the line to your shell rc file to keep it. The Homebrew formula installs completions for you.
|
|
422
|
+
|
|
423
|
+
## Links
|
|
424
|
+
|
|
425
|
+
- Dashboard: <https://redreplier.com/dashboard>
|
|
426
|
+
- API tokens: <https://redreplier.com/api-tokens>
|
|
427
|
+
- MCP server, for Claude, Cursor, VS Code and Windsurf: <https://mcp.redreplier.com/mcp>. Run `redreplier mcp` to print the client config, or `redreplier mcp --install` to name the file it belongs in.
|
|
428
|
+
- AdaptlyPost CLI, for scheduling and publishing social posts: <https://github.com/adaptlypost/adaptlypost-cli>
|
|
429
|
+
- Flowsery CLI, for web analytics, session replay and issue triage: <https://github.com/Flowsery/flowsery-cli>
|
|
430
|
+
- Issues: <https://github.com/RedReplier/redreplier-cli/issues>
|
|
431
|
+
|
|
432
|
+
`Formula/redreplier.rb` in this repo is a reference copy of the Homebrew formula. Homebrew installs from the tap repo, `RedReplier/homebrew-tap`, and the release workflow rewrites the copy there with the real checksums. Editing the file in this repo changes nothing that users install.
|
|
433
|
+
|
|
434
|
+
Releases are tagged with a leading `v`, as in `v1.2.3`. The binary asset URLs, the install script and the formula all embed that tag.
|
|
435
|
+
|
|
436
|
+
## License
|
|
437
|
+
|
|
438
|
+
MIT. See [LICENSE](./LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./exit-codes-IaZq17dv.js";import{f as e}from"./errors-Dkv23Z0t.js";import"./version--HODInrs.js";import"./color-BbAK7_Y7.js";import{c as t,f as n,g as r,i,o as a,p as o,r as s,s as c,v as l}from"./output-uP7GrsdR.js";import"./config-Bg2ls0gu.js";import{t as u}from"./product-CXL7Ga4M.js";import"./http-DpMu6AKn.js";import{a as d,f,i as p,t as m}from"./types-Bu8scNHe.js";import{C as h,S as g,f as _,m as v,v as y,x as b}from"./client-R8kBz1Ou.js";const x=`experimental, undocumented API`;function S(e){return e%1440==0?`${e/1440}d`:e%60==0?`${e/60}h`:`${e}m`}function C(t){let n=Number(t);if(!Number.isInteger(n)||!m.includes(n))throw e(`--cadence must be one of ${m.join(`, `)} minutes, got "${t}"`,`the server rejects anything else with a generic 400`);return n}function w(t){let n=Number(t);if(!Number.isInteger(n)||n<d||n>p)throw e(`--min-score must be a whole number between ${d} and ${p}, got "${t}"`);return n}async function T(e,t){let n=c()||a()?null:f(e);try{return await t()}finally{n?.stop()}}function E(e){n([[`enabled`,e.enabled?`yes`:`no`],[`cadence`,S(e.cadenceMinutes)],[`fastest`,`${S(e.minIntervalMinutes)} on this plan`],[`options`,e.availableCadences.join(`, `)]])}async function D(){let e=await T(`Reading alert settings…`,_);if(a()){o(`alerts.get`,e);return}E(e),i(`the full server list is ${m.join(`, `)}; yours is what the plan allows`)}async function O(t){if(t.on===!0&&t.off===!0)throw e(`Pass either --on or --off, not both`);if(t.on!==!0&&t.off!==!0)throw e(`Pass --on or --off`,`${u.binName} alerts set --on --cadence 60`);let n=t.cadence===void 0?void 0:C(t.cadence),s=await T(`Reading alert settings…`,_);n!==void 0&&!s.availableCadences.includes(n)&&l(`${S(n)} is not in this plan's cadences (${s.availableCadences.join(`, `)}), so the server may refuse it.`);let c=n??s.cadenceMinutes,d=await T(`Saving alert settings…`,()=>g({enabled:t.on===!0,cadenceMinutes:c}));if(a()){o(`alerts.set`,d);return}r(d.enabled?`Alerts on every ${S(d.cadenceMinutes)}`:`Alerts off (cadence ${n===void 0?`kept at`:`set to`} ${S(d.cadenceMinutes)})`),n===void 0&&i(`PUT /alert-settings is a full replace, so the current cadence was read and resent.`)}function k(e){let t=e.command(`alerts`).alias(`alert`).description(`email alert cadence for new mentions`);t.command(`get`).alias(`list`).description(`show the alert settings`).action(async()=>{await D()}),t.command(`set`).description(`turn alerts on or off and choose the cadence`).option(`--on`,`enable alerts`).option(`--off`,`disable alerts`).option(`--cadence <minutes>`,`one of ${m.join(`, `)}`).action(async e=>{await O(e)})}function A(e){n([[`enabled`,e.enabled?`yes`:`no`],[`min score`,String(e.minScore)],[`site`,e.websiteId===null?`every site`:s(e.websiteId)],[`profanity`,e.profanity?`yes`:`no`]])}async function j(){let e=await T(`Reading mean marketer config…`,v);if(a()){o(`mean-marketer.config`,e,{experimental:!0});return}A(e),i(x)}async function M(t){if(t.on===!0&&t.off===!0)throw e(`Pass either --on or --off, not both`);let n={};if(t.on===!0&&(n.enabled=!0),t.off===!0&&(n.enabled=!1),t.minScore!==void 0&&(n.minScore=w(t.minScore)),t.site!==void 0){let e=t.site.trim();n.websiteId=e===``||e.toLowerCase()===`none`?null:e}if(t.profanity!==void 0&&(n.profanity=t.profanity),Object.keys(n).length===0)throw e(`Nothing to change.`,`Pass at least one of --on, --off, --min-score, --site, --profanity or --no-profanity`);let s=await T(`Saving mean marketer config…`,()=>h(n));if(a()){o(`mean-marketer.config.set`,s,{experimental:!0});return}r(`Saved`),A(s),i(x)}function N(e){n([[`enabled`,e.enabled?`yes`:`no`],[`min score`,String(e.minScore)],[`mood`,e.beMean?`mean, tier ${e.tier}`:`civil, tier ${e.tier}`],[`strikes`,String(e.strikes)]]),e.previous&&(t(``),t(`previous opportunity`),n([[`id`,e.previous.id],[`status`,e.previous.status],[`triaged`,e.previous.triaged?`yes`:`no`],[`outstanding`,`${e.previous.hoursOutstanding}h`]],` `)),e.opportunity?(t(``),t(`new opportunity`),n([[`id`,e.opportunity.id],[`title`,e.opportunity.title??`—`],[`source`,e.opportunity.subreddit?`r/${e.opportunity.subreddit}`:e.opportunity.source],[`keyword`,e.opportunity.keyword??`—`],[`score`,e.opportunity.relevanceScore===null?`—`:String(e.opportunity.relevanceScore)],[`url`,e.opportunity.url??`—`]],` `),e.opportunity.relevanceReason&&(t(``),t(` ${e.opportunity.relevanceReason}`))):e.noNewOpportunity&&(t(``),t(`No new opportunity above the minimum score.`)),e.message&&(t(``),t(e.message))}async function P(){let e=await T(`Polling…`,y);if(a()){o(`mean-marketer.poll`,e,{experimental:!0});return}N(e),i(`${x}; this call advances the strike state`)}async function F(){let e=await T(`Resetting…`,b);if(a()){o(`mean-marketer.reset`,e,{experimental:!0});return}e.reset?r(`Strike state reset`):t(`Nothing to reset.`),i(x)}function I(e){let t=e.command(`mean-marketer`,{hidden:!0}).alias(`mm`).description(`nag yourself into replying (${x})`);t.command(`config`).description(`show the mean marketer config`).action(async()=>{await j()}).command(`set`).description(`change the mean marketer config`).option(`--on`,`enable it`).option(`--off`,`disable it`).option(`--min-score <n>`,`only nag about mentions scoring this or higher, ${d} to ${p}`).option(`--site <id>`,`restrict it to one site, or "none" for every site`).option(`--profanity`,`allow profanity`).option(`--no-profanity`,`keep it clean`).action(async e=>{await M(e)}),t.command(`poll`).description(`ask for the next opportunity and advance the strike state`).action(async()=>{await P()}),t.command(`reset`).description(`clear the strike state`).action(async()=>{await F()})}export{C as assertCadence,w as assertMinScore,S as formatCadence,k as registerAlertCommands,I as registerMeanMarketerCommands};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{n as e,t}from"./exit-codes-IaZq17dv.js";import{f as n,n as r}from"./errors-Dkv23Z0t.js";import{i}from"./version--HODInrs.js";import{S as a,x as o}from"./config-Bg2ls0gu.js";import{t as s}from"./product-CXL7Ga4M.js";import{readFile as c}from"node:fs/promises";const l=[`GET`,`POST`,`PATCH`,`PUT`,`DELETE`,`HEAD`],u=(e,t)=>[...t,e];async function d(){let e=[];for await(let t of process.stdin)e.push(t);return Buffer.concat(e).toString(`utf8`)}function f(e){let t=new URLSearchParams;for(let r of e){let e=r.indexOf(`=`);if(e<=0)throw n(`--query expects key=value, got "${r}".`,`${s.binName} api /mentions --query limit=5 --query statuses=NEW`);t.append(r.slice(0,e),r.slice(e+1))}return t}function p(e){let t={};for(let r of e){let e=r.indexOf(`:`);if(e<=0)throw n(`--header expects "Name: value", got "${r}".`);let i=r.slice(0,e).trim();if(i.toLowerCase()===`authorization`)throw n(`Authorization comes from the resolved profile and cannot be overridden.`,`Use --token or --profile to change which token is sent.`);t[i]=r.slice(e+1).trim()}return t}async function m(e,t,r){if(e!==void 0&&t!==void 0)throw n(`Pass either --data or --data-file, not both.`);let i;if(t!==void 0)i=t===`-`?await d():await c(t,`utf8`);else if(e!==void 0)i=e===`@-`?await d():e.startsWith(`@`)?await c(e.slice(1),`utf8`):e;else if(r!==`GET`&&r!==`HEAD`&&!process.stdin.isTTY){let e=await d();i=e.trim()===``?void 0:e}if(i!==void 0){try{JSON.parse(i)}catch{throw n(`The request body is not valid JSON.`)}return i}}function h(c){c.command(`api`).argument(`<path>`,`API path such as /mentions, or an HTTP method when a path follows`).argument(`[pathAfterMethod]`,`API path, when the first argument is an HTTP method`).description(`send an authenticated request to the ${s.displayName} API`).option(`-X, --method <method>`,`HTTP method`,`GET`).option(`-q, --query <key=value>`,`query parameter, repeat the key for arrays`,u,[]).option(`-d, --data <json|@file>`,`request body, @- reads stdin`).option(`--data-file <path>`,`request body read from a file, - reads stdin`).option(`-H, --header <name:value>`,`extra request header, repeatable`,u,[]).option(`-i, --include`,`print the status and response headers to stderr`).action(async(c,u,d)=>{let h=d.method.toUpperCase(),g=c;if(u!==void 0){if(!l.includes(c.toUpperCase()))throw n(`"${c}" is not an HTTP method.`,`Methods: ${l.join(`, `)}`);h=c.toUpperCase(),g=u}if(!l.includes(h))throw n(`"${d.method}" is not an HTTP method.`,`Methods: ${l.join(`, `)}`);if(/^[a-z][a-z0-9+.-]*:\/\//i.test(g))throw n(`api takes a path, not a URL.`,`This CLI only ever talks to the resolved API host. Try: ${s.binName} api /mentions`);let _=a(),v=f(d.query).toString(),y=`${_.apiUrl}${g.startsWith(`/`)?g:`/${g}`}${v?`?${v}`:``}`,b=await m(d.data,d.dataFile,h),x=o(),S={Accept:`application/json`,"User-Agent":i(s.id),...p(d.header),Authorization:`Bearer ${_.token}`};b!==void 0&&(S[`Content-Type`]=`application/json`),x&&(S[`x-language`]=x);let C;try{C=await fetch(y,{method:h,headers:S,body:b,signal:AbortSignal.timeout(12e4)})}catch(e){throw new r(`Could not reach ${_.apiUrl}: ${e instanceof Error?e.message:String(e)}`,{exitCode:t.NETWORK,cause:e})}if(d.include){process.stderr.write(`HTTP ${C.status} ${C.statusText}\n`);for(let[e,t]of[...C.headers].sort())process.stderr.write(`${e}: ${t}\n`);process.stderr.write(`
|
|
2
|
+
`)}let w=await C.text();if(!C.ok){let t=`${h} ${g} failed with ${C.status}`,n=null;try{let e=JSON.parse(w);n=e,Array.isArray(e.message)?t=e.message.join(`; `):typeof e.message==`string`&&(t=e.message)}catch{w.trim()!==``&&(t=w.trim())}throw new r(t,{exitCode:e(C.status),details:n})}if(w===``)return;let T=w;if(process.stdout.isTTY)try{T=JSON.stringify(JSON.parse(w),null,2)}catch{T=w}process.stdout.write(T.endsWith(`
|
|
3
|
+
`)?T:`${T}\n`)})}export{h as registerApiCommand};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{t as e}from"./exit-codes-IaZq17dv.js";import{n as t}from"./errors-Dkv23Z0t.js";import"./version--HODInrs.js";import"./color-BbAK7_Y7.js";import{c as n,f as r,g as i,i as a,p as o}from"./output-uP7GrsdR.js";import{C as s,F as c,I as l,L as u,M as d,N as f,P as p,S as m,b as h,l as g,m as _,p as v,s as y,v as b,x}from"./config-Bg2ls0gu.js";import{t as S}from"./product-CXL7Ga4M.js";import{c as C,l as w}from"./http-DpMu6AKn.js";import{n as T,r as E}from"./prompt-DR68wwvQ.js";import{t as D}from"./open-url-CovrlP9b.js";import{_ as O}from"./client-R8kBz1Ou.js";const k=S.tokenPrefixes[0];function A(e){return e.map(e=>e.domain).sort()}function j(e){return e.reduce((e,t)=>e+t.keywords.filter(e=>e.status===`ACTIVE`).length,0)}async function M(){let e=[];for await(let t of process.stdin)e.push(t);return Buffer.concat(e).toString(`utf8`)}function N(n){if(S.tokenPrefixes.some(e=>n.startsWith(e)))return;if(n.split(`.`).length===3)throw new t(`That looks like an OAuth JWT, not an API token.`,{exitCode:e.AUTH,code:`unsupported_token`,hint:`OAuth login is not supported yet. Create an API token at ${S.tokensUrl}`});let r=n.indexOf(`_`),i=r>0?n.slice(0,r+1):``;throw new t(i?`That token starts with ${i}. ${S.binName} expects ${k}.`:`That does not look like a ${S.displayName} API token, which starts with ${k}.`,{exitCode:e.AUTH,code:`wrong_token_prefix`,hint:`Create one at ${S.tokensUrl}`})}async function P(n,r){if(n){let n=(await M()).trim();if(n===``)throw new t(`Nothing arrived on stdin.`,{exitCode:e.USAGE,code:`empty_stdin`,hint:`echo "$${S.envPrefix}_API_TOKEN" | ${S.binName} login --token-stdin`});return n}return r.token&&r.token.trim()!==``?r.token.trim():(await E(`Token (starts with ${k})`,{hint:`Pass --token-stdin and pipe the token in, or set ${S.envPrefix}_API_TOKEN`})).trim()}function F(E){E.command(`login`).description(`store a ${S.displayName} API token on this machine`).option(`--token-stdin`,`read the token from stdin instead of prompting`).option(`--name <label>`,`human label stored with the profile`).action(async e=>{let t=v(),n=s(),c=h(t,p(n)?.apiUrl).url;!e.tokenStdin&&!t.token&&(a(`Opening ${S.tokensUrl} in your browser.`),a(`Create a token, then paste it here.`),a(`API tokens need a plan with API access, so the token page errors on the free plan.`),D(S.tokensUrl));let d=await P(!!e.tokenStdin,t);N(d),C({profile:{name:n,token:d,tokenSource:`flag`,apiUrl:c,defaults:b(n)},debug:_(),language:x(),quiet:t.quiet===!0});let{websites:f}=await O(),m=A(f),g=j(f);u(n,{token:d,apiUrl:c,label:e.name},{makeCurrent:!0}),o(`login`,{profile:n,apiUrl:c,tokenPrefix:l(d),label:e.name??null,sites:f.length,domains:m,activeKeywords:g,credentialsPath:y()}),i(`Token valid`),r([[`profile`,n],...e.name?[[`label`,e.name]]:[],[`api`,c],[`sites`,`${f.length} monitored`],...m.length>0?[[`domains`,m.join(`, `)]]:[],[`keywords`,`${g} active`],[`stored in`,`${y()} (0600)`]])}),E.command(`logout`).description(`remove a stored token`).option(`--all`,`remove every profile`).action(async r=>{let i=v(),a=c();if(a.length===0)throw new t(`There are no stored profiles to remove.`,{exitCode:e.NOT_FOUND,code:`no_profiles`,hint:`${S.binName} login`});let l=s(),u=r.all?a.map(e=>e.name):[l];if(!r.all&&!a.some(e=>e.name===l))throw new t(`No profile named "${l}".`,{exitCode:e.NOT_FOUND,code:`unknown_profile`,hint:`Stored profiles: ${a.map(e=>e.name).join(`, `)}`});if(!await T(r.all?`Remove all ${u.length} profiles?`:`Remove profile "${l}"?`,{assumeYes:i.yes===!0,initialValue:!1})){n(`Nothing removed.`);return}r.all?d():f(l),o(`logout`,{removed:u}),n(r.all?`Removed all ${u.length} profiles.`:`Removed profile "${l}".`),n(`The token is still valid, revoke it at ${S.tokensUrl}`)}),E.command(`whoami`).description(`show which token is in use and what it can see`).action(async()=>{let e=m(),{websites:t}=await O(),n=A(t),i=j(t),s=w();o(`whoami`,{profile:e.name,tokenPrefix:l(e.token),tokenSource:e.tokenSource,apiUrl:e.apiUrl,sites:t.length,domains:n,activeKeywords:i},s?.limit!==void 0&&s.remaining!==void 0?{rateLimit:{limit:s.limit,remaining:s.remaining,resetSeconds:s.reset??0}}:void 0),r([[`profile`,e.name],[`token`,`${l(e.token)} (from ${g(e.tokenSource)})`],[`api`,e.apiUrl],[`sites`,`${t.length} ${t.length===1?`site`:`sites`}${n.length>0?` (${n.join(`, `)})`:``}`],[`keywords`,`${i} active`],...typeof e.defaults.defaultSite==`string`?[[`default site`,`${e.defaults.defaultSite} (from config.json)`]]:[],...s?.remaining!==void 0&&s.limit!==void 0?[[`limits`,`${s.remaining} of ${s.limit} requests left this minute`]]:[]]),a(`There is no /me endpoint, so this is read back from your monitored sites.`)})}export{N as assertTokenShape,F as registerAuthCommands};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createRequire as e}from"node:module";var t=Object.create,n=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.getPrototypeOf,o=Object.prototype.hasOwnProperty,s=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),c=(e,t,a,s)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var c=i(t),l=0,u=c.length,d;l<u;l++)d=c[l],!o.call(e,d)&&d!==a&&n(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(s=r(t,d))||s.enumerable});return e},l=(e,r,i)=>(i=e==null?{}:t(a(e)),c(r||!e||!e.__esModule?n(i,`default`,{value:e,enumerable:!0}):i,e)),u=e(import.meta.url);export{u as n,l as r,s as t};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const e=`22.12.0`;function t(e,t){let n=e.split(`.`),r=t.split(`.`);for(let e=0;e<3;e+=1){let t=Number.parseInt(n[e]??`0`,10)||0,i=Number.parseInt(r[e]??`0`,10)||0;if(t!==i)return t<i}return!1}t(process.versions.node,e)&&(process.stderr.write(`redreplier needs Node ${e} or newer, this is Node ${process.versions.node}. Install a current Node from https://nodejs.org and try again.
|
|
3
|
+
`),process.exit(2));const{Command:n,Option:r}=await import(`./esm-CIPTesEn.js`),{ExitCode:i}=await import(`./exit-codes-Dth9pkcT.js`),{PRODUCT:a,setGlobalOptions:o}=await import(`./config-_SbTs7a5.js`),{versionLine:s}=await import(`./version-DwIEu3-z.js`),c=[[`-p, --profile <name>`,`credential profile to use`],[`--json`,`force machine-readable JSON output`],[`-q, --quiet`,`suppress spinners, hints and notices`],[`--no-color`,`strip ANSI colour from the output`],[`--debug`,`log every request to stderr`],[`--api-url <url>`,`override the API base URL`],[`--token <token>`,`use this token for one invocation`],[`-y, --yes`,`answer yes to every confirmation`],[`--no-input`,`never prompt, fail with exit 2 instead`],[`--lang <code>`,`language for the x-language header: en, fr, de, es, pt`],[`--full-ids`,`print identifiers in full instead of truncating`]],l=[`profile`,`json`,`quiet`,`color`,`debug`,`apiUrl`,`token`,`yes`,`input`,`lang`,`fullIds`],u=new Set([`-p`,`--profile`,`--api-url`,`--token`,`--lang`]),d=`--help-all`,f=[{names:[`login`,`logout`,`whoami`],load:async()=>(await import(`./auth-DYgYo6Le.js`)).registerAuthCommands},{names:[`config`],load:async()=>(await import(`./config-DwEEtXDc.js`)).registerConfigCommands},{names:[`doctor`],load:async()=>(await import(`./doctor-BpYdCjDb.js`)).registerDoctorCommand},{names:[`completion`],load:async()=>(await import(`./completion-DueI7liE.js`)).registerCompletionCommand},{names:[`mcp`],load:async()=>(await import(`./mcp-9bwQeg1V.js`)).registerMcpCommand},{names:[`api`],load:async()=>(await import(`./api-Dcrz3Tbv.js`)).registerApiCommand},{names:[`open`],load:async()=>(await import(`./open-_iWw6bFQ.js`)).registerOpenCommand},{names:[`site`,`sites`],load:async()=>(await import(`./site-ChYUR_ES.js`)).registerSiteCommands},{names:[`keyword`,`keywords`,`kw`],load:async()=>(await import(`./keyword-DFHGBpip.js`)).registerKeywordCommands},{names:[`mention`,`mentions`],load:async()=>(await import(`./mention-Dv8hocI7.js`)).registerMentionCommands},{names:[`alerts`,`alert`],load:async()=>(await import(`./alert-ByDPHwjO.js`)).registerAlertCommands},{names:[`mean-marketer`,`mm`],load:async()=>(await import(`./alert-ByDPHwjO.js`)).registerMeanMarketerCommands}];function p(e){for(let t=2;t<e.length;t+=1){let n=e[t];if(n===`--`)return;if(n.startsWith(`-`)){!n.includes(`=`)&&u.has(n)&&(t+=1);continue}return n}}function m(e,t){let n=e.slice(2);return n.includes(`-h`)||n.includes(`--help`)||n.includes(d)?!0:t===void 0?!n.includes(`-V`)&&!n.includes(`--version`):t===`help`||t===`completion`?!0:!f.some(e=>e.names.includes(t))}async function h(e,t){let n=p(t),r=m(t,n)?f:f.filter(e=>e.names.includes(n));for(let t of r)(await t.load())(e)}function g(e,t){for(let n of e.options)if(n.attributeName()===t.attributeName()||t.short&&(n.short===t.short||n.long===t.short)||t.long&&(n.long===t.long||n.short===t.long))return!0;return!1}function _(e){for(let[t,n]of c){let i=new r(t,n).hideHelp(e.parent!==null);g(e,i)||e.addOption(i)}for(let t of e.commands)_(t)}function v(e){let t=[];for(let n=e;n;n=n.parent)t.unshift(n);let n={};for(let e of t)for(let t of l){let r=e.getOptionValueSource(t);(r===`cli`||r===`env`)&&(n[t]=e.getOptionValue(t))}return n.color===void 0&&(n.color=!0),n.input===void 0&&(n.input=!!process.stdin.isTTY),n}function y(e){let t=[];for(let n=e;n&&n.parent;n=n.parent)t.unshift(n.name());return t.join(`.`)||e.name()}let b=a.binName,x={},S=``;function C(e){if(!(e instanceof Error))return!1;let t=e.code;return typeof t==`string`&&t.startsWith(`commander.`)}async function w(e){let{isMachine:t,printError:n}=await import(`./output-CwNjSyRl.js`);if(C(e)){if(e.code===`commander.version`||e.code===`commander.helpDisplayed`)return process.stdout.write(S),i.OK;if(e.code===`commander.help`)return process.stderr.write(S),i.USAGE;if(t()){let{usageError:t}=await import(`./errors-DZ-S25mn.js`);return n(t(S.trim()||e.message),b)}return process.stderr.write(S),i.USAGE}return process.stderr.write(S),n(e,b)}process.on(`SIGINT`,()=>{process.stderr.write(`
|
|
4
|
+
`),process.exit(i.CANCELLED)});const T=new n;T.name(a.binName).description(`${a.displayName} from your terminal`).version(s(a.binName),`-V, --version`,`print the version, node and platform`).helpOption(`-h, --help`,`show help for a command`).showSuggestionAfterError(!0).enablePositionalOptions().exitOverride().configureOutput({writeErr:e=>{S+=e},writeOut:e=>{S+=e}}),T.hook(`preAction`,async(e,t)=>{let n=v(t),r=y(t);b=r,x=n,o(n);let{detectColorEnabled:i,setColorEnabled:s}=await import(`./color-D6__4GPX.js`),{setInteractive:c}=await import(`./prompt-BMmbLZHM.js`),{configureHttp:l}=await import(`./http-Cwj8ofnO.js`);s(i({noColor:n.color===!1})),E({json:n.json,quiet:n.quiet,fullIds:n.fullIds,command:r,envPrefix:a.envPrefix}),c(n.input!==!1),l(n)});for(let[e,t]of c)T.addOption(new r(e,t));T.addOption(new r(d,`show help including the experimental commands`)),await h(T,process.argv),_(T);const{initOutput:E}=await import(`./output-CwNjSyRl.js`);E({json:process.argv.includes(`--json`),envPrefix:a.envPrefix}),process.argv.includes(d)&&(T.configureHelp({visibleCommands:e=>[...e.commands]}),process.stdout.write(T.helpInformation()),process.exit(i.OK));try{await T.parseAsync(process.argv),process.stdout.write(S),S=``;let{runUpdateCheck:e}=await import(`./update-check-DBvD8xG2.js`);await e({quiet:x.quiet===!0}),process.exitCode===void 0&&(process.exitCode=i.OK)}catch(e){process.exitCode=await w(e)}export{};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{d as e}from"./http-DpMu6AKn.js";const t=e=>encodeURIComponent(e),n=()=>e({method:`GET`,path:`/websites`}),r=n=>e({method:`GET`,path:`/websites/${t(n)}`}),i=t=>e({method:`POST`,path:`/websites`,body:t}),a=(n,r)=>e({method:`PATCH`,path:`/websites/${t(n)}`,body:r}),o=n=>e({method:`DELETE`,path:`/websites/${t(n)}`}),s=t=>e({method:`POST`,path:`/websites/analyze-description`,body:t}),c=(n,r)=>e({method:`POST`,path:`/websites/${t(n)}/keywords`,body:r}),l=(n,r)=>e({method:`PATCH`,path:`/keywords/${t(n)}`,body:r}),u=n=>e({method:`POST`,path:`/keywords/${t(n)}/disable`,idempotent:!0}),d=n=>e({method:`POST`,path:`/keywords/${t(n)}/enable`,idempotent:!0}),f=n=>e({method:`DELETE`,path:`/keywords/${t(n)}`}),p=()=>e({method:`POST`,path:`/keywords/activate-pending`,idempotent:!0}),m=()=>e({method:`GET`,path:`/keywords/activate-pending/preview`}),h=t=>e({method:`GET`,path:`/keywords/billing-preview`,query:t}),g=()=>e({method:`GET`,path:`/keywords/change-usage`}),_=(t={})=>e({method:`GET`,path:`/mentions`,query:t}),v=(t={})=>e({method:`GET`,path:`/mentions/count`,query:t}),y=(n,r)=>e({method:`PATCH`,path:`/mentions/${t(n)}/status`,body:r}),b=n=>e({method:`POST`,path:`/mentions/${t(n)}/explain`,idempotent:!0}),x=()=>e({method:`GET`,path:`/alert-settings`}),S=t=>e({method:`PUT`,path:`/alert-settings`,body:t}),C=()=>e({method:`GET`,path:`/mean-marketer/config`}),w=t=>e({method:`PUT`,path:`/mean-marketer/config`,body:t}),T=()=>e({method:`POST`,path:`/mean-marketer/poll`}),E=()=>e({method:`POST`,path:`/mean-marketer/reset`,idempotent:!0});export{w as C,S,a as T,n as _,i as a,h as b,u as c,b as d,x as f,_ as g,r as h,v as i,l,C as m,c as n,f as o,g as p,s as r,o as s,p as t,d as u,T as v,y as w,E as x,m as y};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{r as e,t}from"./chunk-B_5Rb6Gi.js";var n=t(((e,t)=>{let n=process||{},r=n.argv||[],i=n.env||{},a=!(i.NO_COLOR||r.includes(`--no-color`))&&(!!i.FORCE_COLOR||r.includes(`--color`)||n.platform===`win32`||(n.stdout||{}).isTTY&&i.TERM!==`dumb`||!!i.CI),o=(e,t,n=e)=>r=>{let i=``+r,a=i.indexOf(t,e.length);return~a?e+s(i,t,n,a)+t:e+i+t},s=(e,t,n,r)=>{let i=``,a=0;do i+=e.substring(a,r)+n,a=r+t.length,r=e.indexOf(t,a);while(~r);return i+e.substring(a)},c=(e=a)=>{let t=e?o:()=>String;return{isColorSupported:e,reset:t(`\x1B[0m`,`\x1B[0m`),bold:t(`\x1B[1m`,`\x1B[22m`,`\x1B[22m\x1B[1m`),dim:t(`\x1B[2m`,`\x1B[22m`,`\x1B[22m\x1B[2m`),italic:t(`\x1B[3m`,`\x1B[23m`),underline:t(`\x1B[4m`,`\x1B[24m`),inverse:t(`\x1B[7m`,`\x1B[27m`),hidden:t(`\x1B[8m`,`\x1B[28m`),strikethrough:t(`\x1B[9m`,`\x1B[29m`),black:t(`\x1B[30m`,`\x1B[39m`),red:t(`\x1B[31m`,`\x1B[39m`),green:t(`\x1B[32m`,`\x1B[39m`),yellow:t(`\x1B[33m`,`\x1B[39m`),blue:t(`\x1B[34m`,`\x1B[39m`),magenta:t(`\x1B[35m`,`\x1B[39m`),cyan:t(`\x1B[36m`,`\x1B[39m`),white:t(`\x1B[37m`,`\x1B[39m`),gray:t(`\x1B[90m`,`\x1B[39m`),bgBlack:t(`\x1B[40m`,`\x1B[49m`),bgRed:t(`\x1B[41m`,`\x1B[49m`),bgGreen:t(`\x1B[42m`,`\x1B[49m`),bgYellow:t(`\x1B[43m`,`\x1B[49m`),bgBlue:t(`\x1B[44m`,`\x1B[49m`),bgMagenta:t(`\x1B[45m`,`\x1B[49m`),bgCyan:t(`\x1B[46m`,`\x1B[49m`),bgWhite:t(`\x1B[47m`,`\x1B[49m`),blackBright:t(`\x1B[90m`,`\x1B[39m`),redBright:t(`\x1B[91m`,`\x1B[39m`),greenBright:t(`\x1B[92m`,`\x1B[39m`),yellowBright:t(`\x1B[93m`,`\x1B[39m`),blueBright:t(`\x1B[94m`,`\x1B[39m`),magentaBright:t(`\x1B[95m`,`\x1B[39m`),cyanBright:t(`\x1B[96m`,`\x1B[39m`),whiteBright:t(`\x1B[97m`,`\x1B[39m`),bgBlackBright:t(`\x1B[100m`,`\x1B[49m`),bgRedBright:t(`\x1B[101m`,`\x1B[49m`),bgGreenBright:t(`\x1B[102m`,`\x1B[49m`),bgYellowBright:t(`\x1B[103m`,`\x1B[49m`),bgBlueBright:t(`\x1B[104m`,`\x1B[49m`),bgMagentaBright:t(`\x1B[105m`,`\x1B[49m`),bgCyanBright:t(`\x1B[106m`,`\x1B[49m`),bgWhiteBright:t(`\x1B[107m`,`\x1B[49m`)}};t.exports=c(),t.exports.createColors=c})),r=e(n(),1);const i=/\[[0-9;]*m/g;let a=s(),o=r.default.createColors(a);function s(e={}){if(e.noColor===!0)return!1;let t=e.env??process.env;if(h(t.NO_COLOR))return!1;let n=t.FORCE_COLOR;return n===void 0?t.TERM===`dumb`?!1:e.isTTY??!!process.stdout.isTTY:!(n===``||n===`0`||n.toLowerCase()===`false`)}function c(e){a=e,o=r.default.createColors(e)}function l(e){return e.replace(i,``)}function u(e){return o.dim(e)}function d(e){return o.red(e)}function f(e){return o.green(e)}function p(e){return o.yellow(e)}function m(e){return o.cyan(e)}function h(e){return e!==void 0&&e!==``}export{d as a,p as c,f as i,n as l,s as n,c as o,u as r,l as s,m as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as e,c as t,i as n,n as r,o as i,r as a,s as o,t as s}from"./color-BbAK7_Y7.js";export{s as cyan,r as detectColorEnabled,a as dim,n as green,e as red,i as setColorEnabled,o as stripAnsi,t as yellow};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import"./exit-codes-IaZq17dv.js";import{f as e}from"./errors-Dkv23Z0t.js";import"./color-BbAK7_Y7.js";import{c as t,p as n}from"./output-uP7GrsdR.js";import{t as r}from"./product-CXL7Ga4M.js";const i=[`bash`,`zsh`,`fish`,`powershell`];function a(e){return i.includes(e)}function o(t){if(!a(t))throw e(`Unknown shell "${t}".`,`Supported shells: ${i.join(`, `)}.`);return t}function s(e,t={}){return u(e,[t.name??e.name()],e)}function c(t,n,r={}){let i=s(t,r);switch(n){case`bash`:return _(i);case`zsh`:return v(i);case`fish`:return y(i);case`powershell`:return b(i);default:throw e(`Unknown shell "${String(n)}".`)}}function l(e,t){switch(e){case`bash`:return[`# Load ${t} completions for the current shell:`,`# eval "$(${t} completion bash)"`,`# Or install them permanently:`,`# ${t} completion bash > /etc/bash_completion.d/${t}`].join(`
|
|
2
|
+
`);case`zsh`:return[`# Load ${t} completions for the current shell:`,`# eval "$(${t} completion zsh)"`,`# Or install them permanently:`,`# ${t} completion zsh > "\${fpath[1]}/_${t}"`].join(`
|
|
3
|
+
`);case`fish`:return[`# Load ${t} completions for the current shell:`,`# ${t} completion fish | source`,`# Or install them permanently:`,`# ${t} completion fish > ~/.config/fish/completions/${t}.fish`].join(`
|
|
4
|
+
`);case`powershell`:return[`# Load ${t} completions for the current session:`,`# ${t} completion powershell | Out-String | Invoke-Expression`,`# Or append that line to $PROFILE.`].join(`
|
|
5
|
+
`);default:return``}}function u(e,t,n){let r=n.createHelp(),i=typeof r.visibleGlobalOptions==`function`?r.visibleGlobalOptions(e):[],a=d([...r.visibleOptions(e),...i]),o=r.visibleCommands(e).map(e=>u(e,[...t,e.name()],n));return{name:e.name(),path:t,description:e.description(),aliases:e.aliases(),options:a,subcommands:o}}function d(e){let t=new Set,n=[];for(let r of e){let e=[r.short,r.long].filter(e=>typeof e==`string`&&e!==``);if(e.length===0)continue;let i=e.join(` `);t.has(i)||(t.add(i),n.push({flags:e,description:r.description??``}))}return n}function f(e,t=[]){t.push(e);for(let n of e.subcommands)f(n,t);return t}function p(e,t){let n=new Set;for(let t of e.subcommands){n.add(t.name);for(let e of t.aliases)n.add(e)}for(let t of e.options)for(let e of t.flags)n.add(e);if(e!==t)for(let e of t.options)for(let t of e.flags)n.add(t);return[...n]}function m(e){return e.path.join(` `)}function h(e){return e.replace(/[^A-Za-z0-9]/g,`_`)}function g(e){return`'${e.replace(/'/g,`'\\''`)}'`}function _(e){let t=h(e.name);return`_${t}_words() {
|
|
6
|
+
case "$1" in
|
|
7
|
+
${f(e).map(t=>` ${g(m(t))}) echo ${g(p(t,e).join(` `))} ;;`).join(`
|
|
8
|
+
`)}
|
|
9
|
+
*) echo "" ;;
|
|
10
|
+
esac
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
_${t}_complete() {
|
|
14
|
+
local cur key next word i
|
|
15
|
+
COMPREPLY=()
|
|
16
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
17
|
+
key=${g(e.name)}
|
|
18
|
+
for (( i = 1; i < COMP_CWORD; i++ )); do
|
|
19
|
+
word="\${COMP_WORDS[i]}"
|
|
20
|
+
case "$word" in
|
|
21
|
+
-*) continue ;;
|
|
22
|
+
esac
|
|
23
|
+
next="$key $word"
|
|
24
|
+
if [ -n "$(_${t}_words "$next")" ]; then
|
|
25
|
+
key="$next"
|
|
26
|
+
fi
|
|
27
|
+
done
|
|
28
|
+
COMPREPLY=( $(compgen -W "$(_${t}_words "$key")" -- "$cur") )
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
complete -F _${t}_complete ${e.name}
|
|
32
|
+
`}function v(e){let t=h(e.name),n=f(e).map(t=>` ${g(m(t))} ${g(p(t,e).join(` `))}`).join(`
|
|
33
|
+
`);return`#compdef ${e.name}
|
|
34
|
+
|
|
35
|
+
typeset -gA _${t}_tree
|
|
36
|
+
_${t}_tree=(
|
|
37
|
+
${n}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
_${t}() {
|
|
41
|
+
local key next word i
|
|
42
|
+
key=${g(e.name)}
|
|
43
|
+
for (( i = 2; i < CURRENT; i++ )); do
|
|
44
|
+
word="\${words[i]}"
|
|
45
|
+
case "$word" in
|
|
46
|
+
-*) continue ;;
|
|
47
|
+
esac
|
|
48
|
+
next="$key $word"
|
|
49
|
+
if [[ -n "\${_${t}_tree[$next]}" ]]; then
|
|
50
|
+
key="$next"
|
|
51
|
+
fi
|
|
52
|
+
done
|
|
53
|
+
local -a candidates
|
|
54
|
+
candidates=(\${=_${t}_tree[$key]})
|
|
55
|
+
compadd -a candidates
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
compdef _${t} ${e.name}
|
|
59
|
+
`}function y(e){let t=h(e.name),n=[`function __fish_${t}_path_is`,` set -l tokens (commandline -opc)`,` set -l parts`,` for token in $tokens[2..-1]`,` if string match -q -- '-*' $token`,` continue`,` end`,` set -a parts $token`,` end`,` set -l joined (string join " " $parts)`,` test "$joined" = "$argv[1]"`,`end`,``,`complete -c ${e.name} -f`];for(let r of f(e)){let i=`__fish_${t}_path_is ${g(r.path.slice(1).join(` `))}`;for(let t of r.subcommands)n.push(`complete -c ${e.name} -n ${g(i)} -a ${g(t.name)} -d ${g(t.description)}`);for(let t of r.options){let r=t.flags.find(e=>e.startsWith(`--`)),a=t.flags.find(e=>!e.startsWith(`--`)),o=[`complete -c ${e.name} -n ${g(i)}`];a!==void 0&&o.push(`-s ${a.replace(/^-/,``)}`),r!==void 0&&o.push(`-l ${r.replace(/^--/,``)}`),o.push(`-d ${g(t.description)}`),n.push(o.join(` `))}}return`${n.join(`
|
|
60
|
+
`)}\n`}function b(e){let t=h(e.name);return`$${t}Completions = @{
|
|
61
|
+
${f(e).map(t=>{let n=p(t,e).map(e=>`'${e.replace(/'/g,`''`)}'`).join(`, `);return` '${m(t).replace(/'/g,`''`)}' = @(${n})`}).join(`
|
|
62
|
+
`)}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
Register-ArgumentCompleter -Native -CommandName ${e.name} -ScriptBlock {
|
|
66
|
+
param($wordToComplete, $commandAst, $cursorPosition)
|
|
67
|
+
$tokens = @($commandAst.CommandElements | ForEach-Object { $_.ToString() })
|
|
68
|
+
$key = '${e.name.replace(/'/g,`''`)}'
|
|
69
|
+
for ($i = 1; $i -lt $tokens.Count; $i++) {
|
|
70
|
+
$token = $tokens[$i]
|
|
71
|
+
if ($token.StartsWith('-')) { continue }
|
|
72
|
+
if ($token -eq $wordToComplete) { continue }
|
|
73
|
+
$next = "$key $token"
|
|
74
|
+
if ($${t}Completions.ContainsKey($next)) { $key = $next }
|
|
75
|
+
}
|
|
76
|
+
$${t}Completions[$key] | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
|
77
|
+
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
`}function x(e){e.command(`completion`).argument(`[shell]`,`one of ${i.join(`, `)}`).description(`print the shell completion script`).action((a,s,u)=>{let d=u.parent??e;if(a===void 0){let e=Object.fromEntries(i.map(e=>[e,l(e,r.binName)]));n(`completion`,{shells:[...i],instructions:e}),t(`Pick a shell: ${r.binName} completion <shell>`),t(``);for(let e of i){t(` ${e}`);for(let n of l(e,r.binName).split(`
|
|
81
|
+
`))t(` ${n}`);t(``)}return}let f=o(a.toLowerCase());process.stdout.write(`${c(d,f,{name:r.binName}).trimEnd()}\n`),process.stderr.write(`${l(f,r.binName)}\n`)})}export{x as registerCompletionCommand};
|