@nolimitcli/cli 1.2.0 → 2.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.
Files changed (3) hide show
  1. package/README.md +51 -9
  2. package/dist/index.js +249 -120
  3. package/package.json +7 -7
package/README.md CHANGED
@@ -1,25 +1,67 @@
1
- # @nolimit/cli
1
+ # @nolimitcli/cli
2
2
 
3
3
  No subscription. Just code.
4
4
 
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npx @nolimit/cli
8
+ npx @nolimitcli/cli
9
9
  ```
10
10
 
11
- ## Commands
11
+ Or install globally:
12
12
 
13
13
  ```bash
14
- nolimit auth # Authenticate (get 2K welcome tokens)
15
- nolimit earn # Watch ad to earn 8K tokens
16
- nolimit balance # Check token balance
14
+ npm install -g @nolimitcli/cli
15
+ nolimit
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ Just run the command and start chatting:
21
+
17
22
  ```
23
+ ┌─────────────────────────────┐
24
+ │ NoLimit v2.0.0 │
25
+ │ No subscription. Just code. │
26
+ └─────────────────────────────┘
27
+
28
+ Balance: 2K tokens
29
+
30
+ Commands: /earn · /balance · /help · /quit
31
+ ─────────────────────────────────
32
+
33
+ › Write a function to reverse a string
34
+
35
+ function reverse(str) {
36
+ return str.split('').reverse().join('');
37
+ }
38
+
39
+ ─ 89 tokens used
40
+
41
+
42
+ ```
43
+
44
+ ## Commands
45
+
46
+ | Command | Description |
47
+ |---------|-------------|
48
+ | `/earn` | Watch an ad to earn 8K tokens |
49
+ | `/balance` | Check your token balance |
50
+ | `/clear` | Clear the screen |
51
+ | `/help` | Show all commands |
52
+ | `/quit` | Exit |
18
53
 
19
54
  ## How it works
20
55
 
21
- 1. Watch a 30-second ad
22
- 2. Earn 8,000 tokens
23
- 3. Use tokens for AI prompts
56
+ 1. First run auto-authenticates (2K welcome tokens)
57
+ 2. Type anything to chat with AI
58
+ 3. When tokens run low, type `/earn`
59
+ 4. Watch a 30-second ad in your browser
60
+ 5. Continue chatting
24
61
 
25
62
  No subscription. No credit card. Just code.
63
+
64
+ ## Links
65
+
66
+ - [nolimit.dev](https://nolimit.dev)
67
+ - [GitHub](https://github.com/buzzernetwork/nolimit)
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from 'commander';
3
2
  import Conf from 'conf';
4
3
  import chalk from 'chalk';
5
4
  import ora from 'ora';
6
5
  import open from 'open';
6
+ import * as readline from 'readline';
7
7
  const API_BASE = 'https://nolimit-production-2589.up.railway.app';
8
+ const VERSION = '2.0.1';
8
9
  // Persistent config storage
9
10
  const config = new Conf({
10
11
  projectName: 'nolimit',
@@ -13,163 +14,291 @@ const config = new Conf({
13
14
  apiKey: { type: 'string' },
14
15
  },
15
16
  });
16
- const program = new Command();
17
- program
18
- .name('nolimit')
19
- .description('No subscription. Just code.')
20
- .version('1.2.0');
21
- // Auth command - register and get API key
22
- program
23
- .command('auth')
24
- .description('Authenticate with NoLimit')
25
- .action(async () => {
26
- const spinner = ora('Registering...').start();
17
+ // ============================================================================
18
+ // HELPERS
19
+ // ============================================================================
20
+ function formatTokens(tokens) {
21
+ if (tokens >= 1000)
22
+ return (tokens / 1000).toFixed(1).replace(/\.0$/, '') + 'K';
23
+ return tokens.toString();
24
+ }
25
+ async function getBalance(apiKey) {
26
+ try {
27
+ const response = await fetch(`${API_BASE}/api/credits/balance`, {
28
+ headers: { 'Authorization': `Bearer ${apiKey}` },
29
+ });
30
+ if (response.ok) {
31
+ return await response.json();
32
+ }
33
+ }
34
+ catch { }
35
+ return null;
36
+ }
37
+ async function authenticate() {
27
38
  try {
28
39
  const response = await fetch(`${API_BASE}/api/auth/register`, {
29
40
  method: 'POST',
30
41
  headers: { 'Content-Type': 'application/json' },
31
42
  body: JSON.stringify({
32
43
  deviceId: `cli-${Date.now()}`,
33
- userAgent: 'nolimit-cli/1.0.0',
44
+ userAgent: `nolimit-cli/${VERSION}`,
34
45
  consent: true,
35
46
  }),
36
47
  });
37
- if (!response.ok) {
38
- throw new Error(`Registration failed: ${response.status}`);
39
- }
48
+ if (!response.ok)
49
+ return null;
40
50
  const data = await response.json();
41
51
  config.set('sessionId', data.sessionId);
42
52
  config.set('apiKey', data.apiKey);
43
- spinner.succeed(chalk.green('Authenticated'));
44
- if (data.welcomeBonus > 0) {
45
- console.log(chalk.green(`+${data.welcomeBonus.toLocaleString()}`), 'welcome tokens');
46
- }
47
- console.log();
48
- console.log(chalk.dim('Run'), chalk.cyan('nolimit earn'), chalk.dim('to get more tokens'));
49
- }
50
- catch (error) {
51
- spinner.fail(chalk.red('Authentication failed'));
52
- console.error(error instanceof Error ? error.message : error);
53
- process.exit(1);
53
+ return data.apiKey;
54
54
  }
55
- });
56
- // Earn command - watch ad to earn tokens
57
- program
58
- .command('earn')
59
- .description('Watch an ad to earn tokens')
60
- .action(async () => {
61
- const apiKey = config.get('apiKey');
62
- if (!apiKey) {
63
- console.log(chalk.yellow('Not authenticated.'));
64
- console.log(chalk.dim('Run'), chalk.cyan('nolimit auth'), chalk.dim('first'));
65
- process.exit(1);
55
+ catch {
56
+ return null;
66
57
  }
67
- const spinner = ora('Requesting ad...').start();
58
+ }
59
+ async function watchAd(apiKey) {
68
60
  try {
69
61
  // Get starting balance
70
- const balanceResponse = await fetch(`${API_BASE}/api/credits/balance`, {
71
- headers: { 'Authorization': `Bearer ${apiKey}` },
72
- });
73
- const startBalance = balanceResponse.ok
74
- ? (await balanceResponse.json()).balance
75
- : 0;
62
+ const startData = await getBalance(apiKey);
63
+ const startBalance = startData?.balance || 0;
76
64
  // Request ad
77
65
  const adResponse = await fetch(`${API_BASE}/api/ads/request`, {
78
- method: 'GET',
79
- headers: {
80
- 'Authorization': `Bearer ${apiKey}`,
81
- },
66
+ headers: { 'Authorization': `Bearer ${apiKey}` },
82
67
  });
83
- if (!adResponse.ok) {
84
- throw new Error(`Ad request failed: ${adResponse.status}`);
85
- }
68
+ if (!adResponse.ok)
69
+ return false;
86
70
  const adData = await adResponse.json();
87
- spinner.succeed('Ad ready');
88
- console.log();
89
- console.log(chalk.dim('Reward:'), chalk.bold(adData.tokenReward.toLocaleString()), 'tokens');
90
- console.log(chalk.dim('Duration:'), adData.duration, 'seconds');
91
- console.log();
92
- // Open ad viewer in browser
71
+ // Open ad viewer
93
72
  const adUrl = `${API_BASE}/ad-viewer.html?adId=${adData.adId}&apiKey=${apiKey}`;
94
73
  await open(adUrl);
95
- // Auto-poll for completion (no manual Enter needed)
96
- const waitSpinner = ora('Watching ad...').start();
74
+ // Poll for completion
75
+ const spinner = ora('Watching...').start();
97
76
  let attempts = 0;
98
- const maxAttempts = 60; // 60 seconds max wait
77
+ const maxAttempts = 60;
99
78
  while (attempts < maxAttempts) {
100
79
  await new Promise(resolve => setTimeout(resolve, 1000));
101
80
  attempts++;
102
- // Update spinner with countdown
103
81
  const remaining = adData.duration - Math.min(attempts, adData.duration);
104
- if (remaining > 0) {
105
- waitSpinner.text = `${remaining}s remaining...`;
106
- }
107
- else {
108
- waitSpinner.text = 'Verifying...';
109
- }
110
- // Check if balance increased (ad completed)
111
- const checkResponse = await fetch(`${API_BASE}/api/credits/balance`, {
112
- headers: { 'Authorization': `Bearer ${apiKey}` },
113
- });
114
- if (checkResponse.ok) {
115
- const newData = await checkResponse.json();
116
- if (newData.balance > startBalance) {
117
- waitSpinner.succeed(chalk.green('Tokens earned!'));
118
- console.log();
119
- console.log(chalk.bold.green(`+${(newData.balance - startBalance).toLocaleString()}`), 'tokens');
120
- console.log(chalk.dim('Balance:'), chalk.bold(newData.balance.toLocaleString()), chalk.dim('(~' + Math.floor(newData.balance / 1500) + ' prompts)'));
121
- return;
122
- }
82
+ spinner.text = remaining > 0 ? `${remaining}s` : 'Verifying...';
83
+ const newData = await getBalance(apiKey);
84
+ if (newData && newData.balance > startBalance) {
85
+ spinner.succeed(chalk.green(`+${formatTokens(newData.balance - startBalance)} tokens`));
86
+ return true;
123
87
  }
124
88
  }
125
- waitSpinner.fail('Timed out waiting for ad completion');
126
- console.log(chalk.dim('Try running'), chalk.cyan('nolimit earn'), chalk.dim('again'));
127
- }
128
- catch (error) {
129
- spinner.fail(chalk.red('Failed'));
130
- console.error(error instanceof Error ? error.message : error);
131
- process.exit(1);
89
+ spinner.fail('Timed out');
90
+ return false;
132
91
  }
133
- });
134
- // Balance command - check current balance
135
- program
136
- .command('balance')
137
- .description('Check your token balance')
138
- .action(async () => {
139
- const apiKey = config.get('apiKey');
140
- if (!apiKey) {
141
- console.log(chalk.yellow('Not authenticated.'));
142
- console.log(chalk.dim('Run'), chalk.cyan('nolimit auth'), chalk.dim('first'));
143
- process.exit(1);
92
+ catch {
93
+ return false;
144
94
  }
145
- const spinner = ora('Fetching balance...').start();
95
+ }
96
+ async function generate(apiKey, prompt) {
146
97
  try {
147
- const response = await fetch(`${API_BASE}/api/credits/balance`, {
148
- headers: { 'Authorization': `Bearer ${apiKey}` },
98
+ const response = await fetch(`${API_BASE}/api/ai/generate`, {
99
+ method: 'POST',
100
+ headers: {
101
+ 'Authorization': `Bearer ${apiKey}`,
102
+ 'Content-Type': 'application/json',
103
+ },
104
+ body: JSON.stringify({ prompt }),
149
105
  });
106
+ const data = await response.json();
150
107
  if (!response.ok) {
151
- throw new Error(`Failed to fetch balance: ${response.status}`);
108
+ if (response.status === 402) {
109
+ return { text: '__INSUFFICIENT_TOKENS__', tokensUsed: 0 };
110
+ }
111
+ throw new Error(data.message || 'Generation failed');
152
112
  }
153
- const data = await response.json();
154
- spinner.succeed('Balance');
155
- console.log();
156
- console.log(chalk.bold.white(data.balance.toLocaleString()), 'tokens', chalk.dim('(~' + Math.floor(data.balance / 1500) + ' prompts)'));
157
- console.log();
158
- console.log(chalk.dim('Earned:'), data.totalEarned.toLocaleString());
159
- console.log(chalk.dim('Spent:'), data.totalSpent.toLocaleString());
113
+ return {
114
+ text: data.response,
115
+ tokensUsed: data.usage?.totalTokens || 0,
116
+ };
160
117
  }
161
118
  catch (error) {
162
- spinner.fail(chalk.red('Failed'));
163
- console.error(error instanceof Error ? error.message : error);
164
- process.exit(1);
119
+ return null;
165
120
  }
166
- });
167
- // Default action - show help
168
- program.action(() => {
121
+ }
122
+ // ============================================================================
123
+ // WELCOME SCREEN
124
+ // ============================================================================
125
+ function showWelcome(balance) {
126
+ console.clear();
127
+ console.log();
128
+ console.log(chalk.bold.white(' ┌─────────────────────────────┐'));
129
+ console.log(chalk.bold.white(' │') + chalk.bold(' NoLimit ') + chalk.dim(`v${VERSION}`) + chalk.bold.white(' │'));
130
+ console.log(chalk.bold.white(' │') + chalk.gray(' No subscription. Just code.') + chalk.bold.white(' │'));
131
+ console.log(chalk.bold.white(' └─────────────────────────────┘'));
169
132
  console.log();
170
- console.log(chalk.bold('NoLimit'));
171
- console.log(chalk.dim('No subscription. Just code.'));
133
+ console.log(chalk.dim(' Balance: ') + chalk.bold.white(formatTokens(balance)) + chalk.dim(' tokens'));
172
134
  console.log();
173
- program.outputHelp();
135
+ console.log(chalk.dim(' Commands: ') + chalk.cyan('/earn') + chalk.dim(' · ') + chalk.cyan('/balance') + chalk.dim(' · ') + chalk.cyan('/help') + chalk.dim(' · ') + chalk.cyan('/quit'));
136
+ console.log(chalk.dim(' ─────────────────────────────────'));
137
+ console.log();
138
+ }
139
+ // ============================================================================
140
+ // MAIN LOOP
141
+ // ============================================================================
142
+ async function main() {
143
+ // Get or create API key
144
+ let apiKey = config.get('apiKey');
145
+ if (!apiKey) {
146
+ const spinner = ora('Initializing...').start();
147
+ const newKey = await authenticate();
148
+ if (!newKey) {
149
+ spinner.fail('Could not connect to NoLimit');
150
+ process.exit(1);
151
+ }
152
+ apiKey = newKey;
153
+ spinner.succeed('Ready');
154
+ }
155
+ // Get balance
156
+ const balanceData = await getBalance(apiKey);
157
+ let balance = balanceData?.balance || 0;
158
+ // Show welcome
159
+ showWelcome(balance);
160
+ // Create readline interface
161
+ const rl = readline.createInterface({
162
+ input: process.stdin,
163
+ output: process.stdout,
164
+ });
165
+ const prompt = () => {
166
+ rl.question(chalk.green('› '), async (input) => {
167
+ try {
168
+ const trimmed = input.trim();
169
+ if (!trimmed) {
170
+ prompt();
171
+ return;
172
+ }
173
+ // Handle commands
174
+ if (trimmed.startsWith('/')) {
175
+ const cmd = trimmed.toLowerCase();
176
+ if (cmd === '/quit' || cmd === '/exit' || cmd === '/q') {
177
+ console.log(chalk.dim('\n Goodbye.\n'));
178
+ rl.close();
179
+ process.exit(0);
180
+ }
181
+ if (cmd === '/help' || cmd === '/h') {
182
+ console.log();
183
+ console.log(chalk.dim(' Commands:'));
184
+ console.log(chalk.cyan(' /earn') + chalk.dim(' Watch an ad to earn tokens'));
185
+ console.log(chalk.cyan(' /balance') + chalk.dim(' Check your token balance'));
186
+ console.log(chalk.cyan(' /clear') + chalk.dim(' Clear the screen'));
187
+ console.log(chalk.cyan(' /quit') + chalk.dim(' Exit NoLimit'));
188
+ console.log();
189
+ prompt();
190
+ return;
191
+ }
192
+ if (cmd === '/earn' || cmd === '/e') {
193
+ console.log();
194
+ const success = await watchAd(apiKey);
195
+ if (success) {
196
+ const newData = await getBalance(apiKey);
197
+ balance = newData?.balance || balance;
198
+ }
199
+ console.log();
200
+ prompt();
201
+ return;
202
+ }
203
+ if (cmd === '/balance' || cmd === '/b') {
204
+ const data = await getBalance(apiKey);
205
+ if (data) {
206
+ balance = data.balance;
207
+ console.log();
208
+ console.log(chalk.dim(' Balance: ') + chalk.bold.white(formatTokens(data.balance)) + chalk.dim(' tokens'));
209
+ console.log(chalk.dim(' Earned: ') + formatTokens(data.totalEarned));
210
+ console.log(chalk.dim(' Spent: ') + formatTokens(data.totalSpent));
211
+ console.log();
212
+ }
213
+ prompt();
214
+ return;
215
+ }
216
+ if (cmd === '/clear' || cmd === '/c') {
217
+ showWelcome(balance);
218
+ prompt();
219
+ return;
220
+ }
221
+ console.log(chalk.dim(`\n Unknown command: ${trimmed}\n`));
222
+ prompt();
223
+ return;
224
+ }
225
+ // Check if we have enough tokens
226
+ if (balance < 100) {
227
+ console.log();
228
+ console.log(chalk.yellow(' Low tokens.') + chalk.dim(' Watch an ad to continue.'));
229
+ console.log();
230
+ const watch = await new Promise((resolve) => {
231
+ rl.question(chalk.dim(' Watch now? ') + chalk.cyan('[Y/n] '), (answer) => {
232
+ resolve(answer.toLowerCase() !== 'n');
233
+ });
234
+ });
235
+ if (watch) {
236
+ const success = await watchAd(apiKey);
237
+ if (success) {
238
+ const newData = await getBalance(apiKey);
239
+ balance = newData?.balance || balance;
240
+ }
241
+ }
242
+ console.log();
243
+ prompt();
244
+ return;
245
+ }
246
+ // Generate response
247
+ console.log();
248
+ const spinner = ora({ text: chalk.dim('Thinking...'), spinner: 'dots' }).start();
249
+ const result = await generate(apiKey, trimmed);
250
+ if (!result) {
251
+ spinner.fail(chalk.red('Failed to generate response'));
252
+ console.log();
253
+ prompt();
254
+ return;
255
+ }
256
+ if (result.text === '__INSUFFICIENT_TOKENS__') {
257
+ spinner.stop();
258
+ console.log(chalk.yellow(' Insufficient tokens.') + chalk.dim(' Watch an ad to continue.'));
259
+ console.log();
260
+ const watch = await new Promise((resolve) => {
261
+ rl.question(chalk.dim(' Watch now? ') + chalk.cyan('[Y/n] '), (answer) => {
262
+ resolve(answer.toLowerCase() !== 'n');
263
+ });
264
+ });
265
+ if (watch) {
266
+ const success = await watchAd(apiKey);
267
+ if (success) {
268
+ const newData = await getBalance(apiKey);
269
+ balance = newData?.balance || balance;
270
+ }
271
+ }
272
+ console.log();
273
+ prompt();
274
+ return;
275
+ }
276
+ spinner.stop();
277
+ // Show response
278
+ console.log(chalk.white(result.text));
279
+ console.log();
280
+ console.log(chalk.dim(` ─ ${formatTokens(result.tokensUsed)} tokens used`));
281
+ console.log();
282
+ // Update balance
283
+ balance = Math.max(0, balance - result.tokensUsed);
284
+ prompt();
285
+ }
286
+ catch (error) {
287
+ console.log(chalk.red('\n Error occurred. Please try again.\n'));
288
+ prompt();
289
+ }
290
+ });
291
+ };
292
+ // Handle Ctrl+C gracefully
293
+ rl.on('close', () => {
294
+ console.log(chalk.dim('\n Goodbye.\n'));
295
+ process.exit(0);
296
+ });
297
+ // Start prompt loop
298
+ prompt();
299
+ }
300
+ // Run
301
+ main().catch((error) => {
302
+ console.error(chalk.red('Error:'), error.message);
303
+ process.exit(1);
174
304
  });
175
- program.parse();
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@nolimitcli/cli",
3
- "version": "1.2.0",
3
+ "version": "2.0.1",
4
4
  "type": "module",
5
- "description": "No subscription. Just code. Watch an ad, build without limits.",
5
+ "description": "No subscription. Just code. AI chat powered by watching ads.",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
8
  "nolimit": "dist/index.js"
@@ -14,24 +14,24 @@
14
14
  },
15
15
  "keywords": [
16
16
  "ai",
17
+ "chat",
17
18
  "coding",
18
19
  "free",
19
- "tokens",
20
20
  "cli",
21
- "developer-tools"
21
+ "developer-tools",
22
+ "gemini"
22
23
  ],
23
24
  "author": "Buzzer Network",
24
25
  "license": "MIT",
25
26
  "repository": {
26
27
  "type": "git",
27
- "url": "https://github.com/anthropics/nolimit"
28
+ "url": "https://github.com/buzzernetwork/nolimit"
28
29
  },
29
30
  "engines": {
30
- "node": ">=16"
31
+ "node": ">=18"
31
32
  },
32
33
  "dependencies": {
33
34
  "chalk": "^5.3.0",
34
- "commander": "^12.0.0",
35
35
  "conf": "^12.0.0",
36
36
  "open": "^10.0.0",
37
37
  "ora": "^8.0.0"