@anonymilyhq/cli 1.1.1 → 1.2.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/README.md CHANGED
@@ -38,16 +38,17 @@ npm install -g @anonymilyhq/cli
38
38
 
39
39
  ## Quick Start
40
40
 
41
- **No account or signup required** — works immediately with free tier (50 requests, 24h retention).
41
+ **No account or signup required** — works immediately with free tier (random ID, 50 requests, 24h retention).
42
42
 
43
43
  ```bash
44
- # Start listening — generates a random endpoint ID automatically
44
+ # Start listening — generates a random 8-char endpoint ID automatically
45
45
  npx @anonymilyhq/cli listen 3000
46
46
 
47
47
  # Output:
48
48
  # 🚀 Anonymily CLI is running!
49
49
  # Forwarding: https://api.anonymily.com/h/xk92bzte ➔ http://127.0.0.1:3000
50
50
  # Tier: 🆓 FREE | Storage: 0/50 | Retention: 24 hours
51
+ # 💡 Upgrade to Pro for custom endpoint IDs + 500 req/30d at https://anonymily.com/upgrade
51
52
  # Waiting for requests to arrive...
52
53
  ```
53
54
 
@@ -79,35 +80,18 @@ anonymily listen <port> [options]
79
80
  | Option | Description |
80
81
  |--------|-------------|
81
82
  | `<port>` | **Required.** Local port to forward to (e.g. `3000`, `8080`) |
82
- | `-i, --id <id>` | Use a custom, memorable endpoint name (e.g. `stripe-test`) |
83
- | `--pro` | Register the hook as Pro-tier (legacy). Requires `--id` and a configured Pro API key |
83
+ | `-i, --id <id>` | **Pro only.** Use a custom endpoint ID (e.g. `stripe-test`) instead of a random one. Requires Pro subscription (₹750/month). |
84
84
 
85
85
  **Examples:**
86
86
  ```bash
87
- # Random endpoint ID (ephemeral)
87
+ # Random endpoint ID (Free tier: 50 requests, 24h retention)
88
88
  npx @anonymilyhq/cli listen 3000
89
89
 
90
- # Custom named endpoint api.anonymily.com/h/stripe-dev
90
+ # Custom named endpoint (Pro only: requires subscription + claiming via dashboard)
91
91
  npx @anonymilyhq/cli listen 3000 --id stripe-dev
92
92
 
93
- # Pro-tier named endpoint (legacy hook-based Pro)
94
- npx @anonymilyhq/cli listen 3000 --id stripe-dev --pro
95
- ```
96
-
97
- ---
98
-
99
- ### `config set-key <key>`
100
-
101
- Save your Pro API key locally so you don't need to export it every session.
102
-
103
- ```bash
104
- anonymily config set-key <your-pro-api-key>
105
- ```
106
-
107
- The key is stored in `~/.anonymily/config.json`. You can also set it via environment variable:
108
-
109
- ```bash
110
- export PRO_API_KEY=<your-pro-api-key>
93
+ # Use your Pro-claimed endpoint (500 requests, 30 days)
94
+ npx @anonymilyhq/cli listen 3000 --id my-claimed-hook
111
95
  ```
112
96
 
113
97
  ---
@@ -143,20 +127,23 @@ Your feedback helps us improve Anonymily. All submissions are anonymous unless y
143
127
 
144
128
  ## Upgrading to Pro (₹750/month)
145
129
 
146
- To get Pro limits (500 requests, 30-day retention), you need a **paid Pro subscription** at [anonymily.com/upgrade](https://anonymily.com/upgrade). This is an account-level subscription — all your claimed hooks get Pro limits automatically.
147
-
148
- **Workflow:**
149
- 1. Create an account at [anonymily.com/signup](https://anonymily.com/signup) (free)
150
- 2. Subscribe to Pro at [anonymily.com/upgrade](https://anonymily.com/upgrade) (₹750/month)
151
- 3. Open the dashboard, generate or use a custom hook ID
152
- 4. Click "Claim for Pro" to associate the hook with your account
153
- 5. Run the CLI with the same hook ID:
130
+ **What Pro gives you:**
131
+ - **Custom named endpoints** - Use memorable IDs like `stripe-prod` or `github-webhooks`
132
+ - **Claim & manage** - Link endpoints to your account permanently via dashboard
133
+ - **500 requests** per endpoint (vs 50 for Free random IDs)
134
+ - **30-day retention** (vs 24 hours for Free tier)
135
+
136
+ **How to upgrade and use custom endpoints:**
137
+ 1. Subscribe to Pro at [anonymily.com/upgrade](https://anonymily.com/upgrade) (₹750/month)
138
+ 2. Log in to your dashboard at [anonymily.com/dashboard](https://anonymily.com/dashboard)
139
+ 3. Enter your custom endpoint ID (e.g. `my-webhook`) and click "Claim"
140
+ 4. Run the CLI with your claimed hook ID:
154
141
  ```bash
155
- npx @anonymilyhq/cli listen 3000 --id your-claimed-hook
142
+ npx @anonymilyhq/cli listen 3000 --id my-webhook
156
143
  ```
157
- 6. All Pro limits apply automatically — 500 requests, 30-day history
144
+ 5. Enjoy Pro limits — 500 requests, 30-day history
158
145
 
159
- **Note:** Account creation is free, but Pro features require a paid subscription.
146
+ **Important:** Custom endpoint IDs are a **Pro-only feature**. Free users can only use randomly-generated 8-character IDs with 50 requests and 24h retention.
160
147
 
161
148
  ---
162
149
 
@@ -185,12 +172,15 @@ External Service (Stripe, GitHub, etc.)
185
172
 
186
173
  | Feature | Free | Pro |
187
174
  |---------|------|-----|
175
+ | Endpoint ID format | Random 8-char (e.g. `xk92bzte`) | Custom named (e.g. `stripe-prod`) |
188
176
  | Requests stored per hook | 50 | 500 |
189
177
  | History retention | 24 hours | 30 days |
190
- | Custom endpoint ID | | ✅ |
191
- | Persistent history in dashboard | ✅ | ✅ |
178
+ | Hook claiming & management | | ✅ |
179
+ | Dashboard access | ✅ | ✅ |
192
180
  | Price | Free | ₹750/month |
193
181
 
182
+ **Note:** Custom endpoint IDs (via `--id` flag) are a **Pro-only feature**. Free users receive randomly-generated IDs.
183
+
194
184
  ---
195
185
 
196
186
  ## Testing Locally
@@ -233,7 +223,6 @@ npx @anonymilyhq/cli listen 4000
233
223
 
234
224
  | Variable | Description |
235
225
  |----------|-------------|
236
- | `PRO_API_KEY` | Legacy Pro API key (overrides `~/.anonymily/config.json`) |
237
226
  | `ANONYMILY_API_URL` | Override the backend URL (default: `https://api.anonymily.com`) |
238
227
 
239
228
  ---
package/bin/cli.js CHANGED
@@ -4,13 +4,6 @@ import { program } from 'commander';
4
4
  import pc from 'picocolors';
5
5
  import { generateHookId } from '../lib/utils.js';
6
6
  import { startForwarding } from '../lib/forwarder.js';
7
- import { writeConfig, CONFIG_PATH } from '../lib/config.js';
8
- import {
9
- registerProHook,
10
- ProApiKeyMissingError,
11
- ProApiAuthError,
12
- ProApiError,
13
- } from '../lib/proRegister.js';
14
7
  import { logger } from '../lib/logger.js';
15
8
  import { submitFeedback } from '../lib/feedback.js';
16
9
 
@@ -20,7 +13,7 @@ const API_URL = process.env.ANONYMILY_API_URL || 'https://api.anonymily.com';
20
13
  program
21
14
  .name('anonymily')
22
15
  .description('Forward webhooks from Anonymily directly to your local machine.')
23
- .version('1.1.1');
16
+ .version('1.2.0');
24
17
 
25
18
  // ---------------------------------------------------------------------------
26
19
  // listen
@@ -29,66 +22,12 @@ program
29
22
  .command('listen')
30
23
  .description('Listen for incoming webhooks and forward them to a local port')
31
24
  .argument('<port>', 'Local port to forward to (e.g., 8080)')
32
- .option('-i, --id <id>', 'Provide a custom endpoint ID to listen to')
33
- .option(
34
- '--pro',
35
- 'Register the hook as Pro-tier (requires --id and PRO_API_KEY)',
36
- )
25
+ .option('-i, --id <id>', '[Pro only] Use a custom endpoint ID instead of random (requires ₹750/month subscription)')
37
26
  .action(async (port, options) => {
38
- // --pro requires --id
39
- if (options.pro && !options.id) {
40
- logger.error(
41
- pc.bold(
42
- '--pro requires --id to specify a persistent hook name.',
43
- ),
44
- );
45
- process.exit(1);
46
- }
47
-
48
27
  const hookId = options.id || generateHookId();
49
-
50
- if (options.pro) {
51
- try {
52
- await registerProHook(API_URL, hookId);
53
- } catch (err) {
54
- if (
55
- err instanceof ProApiKeyMissingError ||
56
- err instanceof ProApiAuthError ||
57
- err instanceof ProApiError
58
- ) {
59
- logger.error(err.message);
60
- } else {
61
- logger.error(`Unexpected error during Pro registration: ${err.message}`);
62
- }
63
- process.exit(1);
64
- }
65
- }
66
-
67
28
  startForwarding(API_URL, hookId, parseInt(port, 10));
68
29
  });
69
30
 
70
- // ---------------------------------------------------------------------------
71
- // config
72
- // ---------------------------------------------------------------------------
73
- const configCmd = program
74
- .command('config')
75
- .description('Manage CLI configuration');
76
-
77
- configCmd
78
- .command('set-key <key>')
79
- .description(
80
- 'Save your Pro API key to ~/.anonymily/config.json',
81
- )
82
- .action((key) => {
83
- if (!key || !key.trim()) {
84
- logger.error('API key cannot be empty.');
85
- process.exit(1);
86
- }
87
- writeConfig({ proApiKey: key.trim() });
88
- logger.success(
89
- `Pro API key saved to ${CONFIG_PATH}`,
90
- );
91
- });
92
31
 
93
32
  // ---------------------------------------------------------------------------
94
33
  // feedback
package/lib/forwarder.js CHANGED
@@ -68,7 +68,7 @@ export async function startForwarding(apiUrl, hookId, port) {
68
68
  logger.raw(`Tier: ${tierColor(pc.bold(tierBadge))} | Storage: ${tierInfo.requestCount}/${tierInfo.maxRequests} | Retention: ${tierInfo.retention}`);
69
69
 
70
70
  if (tierInfo.tier === 'free') {
71
- logger.dim(`\n💡 Upgrade to Pro for 500 requests + 30-day retention at https://anonymily.com/upgrade`);
71
+ logger.dim(`\n💡 Upgrade to Pro for custom endpoint IDs + 500 req/30d at https://anonymily.com/upgrade`);
72
72
  }
73
73
  }
74
74
 
@@ -118,7 +118,7 @@ export async function startForwarding(apiUrl, hookId, port) {
118
118
  if (requestCount === maxRequests) {
119
119
  logger.warn(`⚠️ Tier limit reached (${maxRequests} requests). Older requests will be purged.\n`);
120
120
  } else if (requestCount === Math.floor(maxRequests * 0.9)) {
121
- logger.warn(`⚠️ 90% of tier limit reached. Consider upgrading to Pro for 500 requests.\n`);
121
+ logger.warn(`⚠️ 90% of tier limit reached. Upgrade to Pro for 500 requests at https://anonymily.com/upgrade\n`);
122
122
  }
123
123
  } catch (err) {
124
124
  logger.error(` └─ Error forwarding: ${err.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anonymilyhq/cli",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "CLI for Anonymily platform",
5
5
  "type": "module",
6
6
  "bin": {
package/lib/config.js DELETED
@@ -1,70 +0,0 @@
1
- /**
2
- * lib/config.js
3
- *
4
- * Read and write the Anonymily CLI config file at ~/.anonymily/config.json.
5
- * The config file stores user-scoped settings such as the Pro API key.
6
- *
7
- * Schema:
8
- * {
9
- * "proApiKey": "<string>" // Pro subscriber API key
10
- * }
11
- */
12
-
13
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
14
- import { homedir } from 'node:os';
15
- import { join, dirname } from 'node:path';
16
-
17
- /** Absolute path to the config directory. */
18
- export const CONFIG_DIR = join(homedir(), '.anonymily');
19
-
20
- /** Absolute path to the config JSON file. */
21
- export const CONFIG_PATH = join(CONFIG_DIR, 'config.json');
22
-
23
- /**
24
- * Read and parse the config file.
25
- * Returns an empty object if the file doesn't exist yet.
26
- *
27
- * @returns {Record<string, unknown>}
28
- */
29
- export function readConfig() {
30
- if (!existsSync(CONFIG_PATH)) return {};
31
- try {
32
- const raw = readFileSync(CONFIG_PATH, 'utf8');
33
- return JSON.parse(raw);
34
- } catch {
35
- return {};
36
- }
37
- }
38
-
39
- /**
40
- * Write (merge) key-value pairs into the config file.
41
- * Creates the config directory and file if they don't exist.
42
- *
43
- * @param {Record<string, unknown>} updates
44
- */
45
- export function writeConfig(updates) {
46
- const existing = readConfig();
47
- const merged = { ...existing, ...updates };
48
-
49
- if (!existsSync(CONFIG_DIR)) {
50
- mkdirSync(CONFIG_DIR, { recursive: true });
51
- }
52
-
53
- writeFileSync(CONFIG_PATH, JSON.stringify(merged, null, 2) + '\n', 'utf8');
54
- }
55
-
56
- /**
57
- * Retrieve the user's Pro API key.
58
- *
59
- * Resolution order (highest precedence first):
60
- * 1. PRO_API_KEY environment variable
61
- * 2. proApiKey field in ~/.anonymily/config.json
62
- *
63
- * @returns {string | undefined}
64
- */
65
- export function getProApiKey() {
66
- if (process.env.PRO_API_KEY) return process.env.PRO_API_KEY;
67
- const cfg = readConfig();
68
- return typeof cfg.proApiKey === 'string' ? cfg.proApiKey : undefined;
69
- }
70
-
@@ -1,100 +0,0 @@
1
- /**
2
- * lib/proRegister.js
3
- *
4
- * Registers a hook ID as Pro-tier by calling the backend's
5
- * POST /hooks/pro/register endpoint.
6
- *
7
- * Acceptance criteria (ANO-47):
8
- * - Uses the PRO_API_KEY from env or ~/.anonymily/config.json
9
- * - If the key is missing → throws ProApiKeyMissingError
10
- * - If the API returns 401 → throws ProApiAuthError
11
- * - If the API returns any other non-2xx → throws ProApiError
12
- * - On success → prints confirmation message and returns the response body
13
- */
14
-
15
- import pc from 'picocolors';
16
- import { logger } from './logger.js';
17
- import { getProApiKey } from './config.js';
18
-
19
- /** Thrown when no Pro API key is configured. */
20
- export class ProApiKeyMissingError extends Error {
21
- constructor() {
22
- super(
23
- 'Pro API key not configured. Run: anonymily config set-key <key>',
24
- );
25
- this.name = 'ProApiKeyMissingError';
26
- }
27
- }
28
-
29
- /** Thrown when the backend returns 401 Unauthorized. */
30
- export class ProApiAuthError extends Error {
31
- constructor() {
32
- super(
33
- 'Pro API key is invalid or revoked. Please verify your key with: anonymily config set-key <key>',
34
- );
35
- this.name = 'ProApiAuthError';
36
- }
37
- }
38
-
39
- /** Thrown when the backend returns any other non-2xx status. */
40
- export class ProApiError extends Error {
41
- /** @param {number} status @param {string} [body] */
42
- constructor(status, body = '') {
43
- super(`Pro registration failed (HTTP ${status}): ${body}`);
44
- this.name = 'ProApiError';
45
- this.status = status;
46
- }
47
- }
48
-
49
- /**
50
- * Register a hook ID as Pro-tier via the backend API.
51
- *
52
- * @param {string} apiUrl Base URL, e.g. https://api.anonymily.com
53
- * @param {string} hookId The hook ID to register
54
- * @returns {Promise<object>} Parsed response body from the API
55
- *
56
- * @throws {ProApiKeyMissingError} No API key available
57
- * @throws {ProApiAuthError} API returned 401
58
- * @throws {ProApiError} API returned other non-2xx
59
- */
60
- export async function registerProHook(apiUrl, hookId) {
61
- const apiKey = getProApiKey();
62
-
63
- if (!apiKey) {
64
- throw new ProApiKeyMissingError();
65
- }
66
-
67
- let response;
68
- try {
69
- response = await fetch(`${apiUrl}/hooks/pro/register`, {
70
- method: 'POST',
71
- headers: {
72
- 'Content-Type': 'application/json',
73
- 'x-api-key': apiKey,
74
- },
75
- body: JSON.stringify({ hookId }),
76
- });
77
- } catch (networkErr) {
78
- throw new Error(`Network error contacting Pro API: ${networkErr.message}`);
79
- }
80
-
81
- if (response.status === 401) {
82
- throw new ProApiAuthError();
83
- }
84
-
85
- if (!response.ok) {
86
- const body = await response.text().catch(() => '');
87
- throw new ProApiError(response.status, body);
88
- }
89
-
90
- const data = await response.json().catch(() => ({}));
91
-
92
- logger.success(
93
- pc.bold(
94
- `[Pro] Hook registered: persists for 30 days, up to 500 requests stored.`,
95
- ),
96
- );
97
-
98
- return data;
99
- }
100
-