@nolimitcli/cli 1.1.0 → 2.0.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.
Files changed (2) hide show
  1. package/dist/index.js +243 -122
  2. package/package.json +7 -7
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.0';
8
9
  // Persistent config storage
9
10
  const config = new Conf({
10
11
  projectName: 'nolimit',
@@ -13,165 +14,285 @@ 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.1.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
- console.log();
45
- console.log(chalk.dim('Session:'), data.sessionId.slice(0, 8) + '...');
46
- if (data.welcomeBonus > 0) {
47
- console.log(chalk.green(`+${data.welcomeBonus.toLocaleString()}`), 'welcome tokens');
48
- }
49
- console.log();
50
- console.log(chalk.dim('Run'), chalk.cyan('nolimit earn'), chalk.dim('to get more tokens'));
53
+ return data.apiKey;
51
54
  }
52
- catch (error) {
53
- spinner.fail(chalk.red('Authentication failed'));
54
- console.error(error instanceof Error ? error.message : error);
55
- process.exit(1);
56
- }
57
- });
58
- // Earn command - watch ad to earn tokens
59
- program
60
- .command('earn')
61
- .description('Watch an ad to earn tokens')
62
- .action(async () => {
63
- const apiKey = config.get('apiKey');
64
- if (!apiKey) {
65
- console.log(chalk.yellow('Not authenticated.'));
66
- console.log(chalk.dim('Run'), chalk.cyan('nolimit auth'), chalk.dim('first'));
67
- process.exit(1);
55
+ catch {
56
+ return null;
68
57
  }
69
- const spinner = ora('Requesting ad...').start();
58
+ }
59
+ async function watchAd(apiKey) {
70
60
  try {
71
61
  // Get starting balance
72
- const balanceResponse = await fetch(`${API_BASE}/api/credits/balance`, {
73
- headers: { 'Authorization': `Bearer ${apiKey}` },
74
- });
75
- const startBalance = balanceResponse.ok
76
- ? (await balanceResponse.json()).balance
77
- : 0;
62
+ const startData = await getBalance(apiKey);
63
+ const startBalance = startData?.balance || 0;
78
64
  // Request ad
79
65
  const adResponse = await fetch(`${API_BASE}/api/ads/request`, {
80
- method: 'GET',
81
- headers: {
82
- 'Authorization': `Bearer ${apiKey}`,
83
- },
66
+ headers: { 'Authorization': `Bearer ${apiKey}` },
84
67
  });
85
- if (!adResponse.ok) {
86
- throw new Error(`Ad request failed: ${adResponse.status}`);
87
- }
68
+ if (!adResponse.ok)
69
+ return false;
88
70
  const adData = await adResponse.json();
89
- spinner.succeed('Ad ready');
90
- console.log();
91
- console.log(chalk.dim('Reward:'), chalk.bold(adData.tokenReward.toLocaleString()), 'tokens');
92
- console.log(chalk.dim('Duration:'), adData.duration, 'seconds');
93
- console.log();
94
- // Open ad viewer in browser
71
+ // Open ad viewer
95
72
  const adUrl = `${API_BASE}/ad-viewer.html?adId=${adData.adId}&apiKey=${apiKey}`;
96
73
  await open(adUrl);
97
- // Auto-poll for completion (no manual Enter needed)
98
- const waitSpinner = ora('Watching ad...').start();
74
+ // Poll for completion
75
+ const spinner = ora('Watching...').start();
99
76
  let attempts = 0;
100
- const maxAttempts = 60; // 60 seconds max wait
77
+ const maxAttempts = 60;
101
78
  while (attempts < maxAttempts) {
102
79
  await new Promise(resolve => setTimeout(resolve, 1000));
103
80
  attempts++;
104
- // Update spinner with countdown
105
81
  const remaining = adData.duration - Math.min(attempts, adData.duration);
106
- if (remaining > 0) {
107
- waitSpinner.text = `${remaining}s remaining...`;
108
- }
109
- else {
110
- waitSpinner.text = 'Verifying...';
111
- }
112
- // Check if balance increased (ad completed)
113
- const checkResponse = await fetch(`${API_BASE}/api/credits/balance`, {
114
- headers: { 'Authorization': `Bearer ${apiKey}` },
115
- });
116
- if (checkResponse.ok) {
117
- const newData = await checkResponse.json();
118
- if (newData.balance > startBalance) {
119
- waitSpinner.succeed(chalk.green('Tokens earned!'));
120
- console.log();
121
- console.log(chalk.bold.green(`+${(newData.balance - startBalance).toLocaleString()}`), 'tokens');
122
- console.log(chalk.dim('Balance:'), chalk.bold(newData.balance.toLocaleString()), chalk.dim('(~' + Math.floor(newData.balance / 1500) + ' prompts)'));
123
- return;
124
- }
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;
125
87
  }
126
88
  }
127
- waitSpinner.fail('Timed out waiting for ad completion');
128
- console.log(chalk.dim('Try running'), chalk.cyan('nolimit earn'), chalk.dim('again'));
89
+ spinner.fail('Timed out');
90
+ return false;
129
91
  }
130
- catch (error) {
131
- spinner.fail(chalk.red('Failed'));
132
- console.error(error instanceof Error ? error.message : error);
133
- process.exit(1);
134
- }
135
- });
136
- // Balance command - check current balance
137
- program
138
- .command('balance')
139
- .description('Check your token balance')
140
- .action(async () => {
141
- const apiKey = config.get('apiKey');
142
- if (!apiKey) {
143
- console.log(chalk.yellow('Not authenticated.'));
144
- console.log(chalk.dim('Run'), chalk.cyan('nolimit auth'), chalk.dim('first'));
145
- process.exit(1);
92
+ catch {
93
+ return false;
146
94
  }
147
- const spinner = ora('Fetching balance...').start();
95
+ }
96
+ async function generate(apiKey, prompt) {
148
97
  try {
149
- const response = await fetch(`${API_BASE}/api/credits/balance`, {
150
- 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 }),
151
105
  });
106
+ const data = await response.json();
152
107
  if (!response.ok) {
153
- 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');
154
112
  }
155
- const data = await response.json();
156
- spinner.succeed('Balance');
157
- console.log();
158
- console.log(chalk.bold.white(data.balance.toLocaleString()), 'tokens', chalk.dim('(~' + Math.floor(data.balance / 1500) + ' prompts)'));
159
- console.log();
160
- console.log(chalk.dim('Earned:'), data.totalEarned.toLocaleString());
161
- console.log(chalk.dim('Spent:'), data.totalSpent.toLocaleString());
113
+ return {
114
+ text: data.response,
115
+ tokensUsed: data.usage?.totalTokens || 0,
116
+ };
162
117
  }
163
118
  catch (error) {
164
- spinner.fail(chalk.red('Failed'));
165
- console.error(error instanceof Error ? error.message : error);
166
- process.exit(1);
119
+ return null;
167
120
  }
168
- });
169
- // Default action - show help
170
- 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(' └─────────────────────────────┘'));
132
+ console.log();
133
+ console.log(chalk.dim(' Balance: ') + chalk.bold.white(formatTokens(balance)) + chalk.dim(' tokens'));
171
134
  console.log();
172
- console.log(chalk.bold('NoLimit'));
173
- console.log(chalk.dim('No subscription. Just code.'));
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(' ─────────────────────────────────'));
174
137
  console.log();
175
- program.outputHelp();
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
+ const trimmed = input.trim();
168
+ if (!trimmed) {
169
+ prompt();
170
+ return;
171
+ }
172
+ // Handle commands
173
+ if (trimmed.startsWith('/')) {
174
+ const cmd = trimmed.toLowerCase();
175
+ if (cmd === '/quit' || cmd === '/exit' || cmd === '/q') {
176
+ console.log(chalk.dim('\n Goodbye.\n'));
177
+ rl.close();
178
+ process.exit(0);
179
+ }
180
+ if (cmd === '/help' || cmd === '/h') {
181
+ console.log();
182
+ console.log(chalk.dim(' Commands:'));
183
+ console.log(chalk.cyan(' /earn') + chalk.dim(' Watch an ad to earn tokens'));
184
+ console.log(chalk.cyan(' /balance') + chalk.dim(' Check your token balance'));
185
+ console.log(chalk.cyan(' /clear') + chalk.dim(' Clear the screen'));
186
+ console.log(chalk.cyan(' /quit') + chalk.dim(' Exit NoLimit'));
187
+ console.log();
188
+ prompt();
189
+ return;
190
+ }
191
+ if (cmd === '/earn' || cmd === '/e') {
192
+ console.log();
193
+ const success = await watchAd(apiKey);
194
+ if (success) {
195
+ const newData = await getBalance(apiKey);
196
+ balance = newData?.balance || balance;
197
+ }
198
+ console.log();
199
+ prompt();
200
+ return;
201
+ }
202
+ if (cmd === '/balance' || cmd === '/b') {
203
+ const data = await getBalance(apiKey);
204
+ if (data) {
205
+ balance = data.balance;
206
+ console.log();
207
+ console.log(chalk.dim(' Balance: ') + chalk.bold.white(formatTokens(data.balance)) + chalk.dim(' tokens'));
208
+ console.log(chalk.dim(' Earned: ') + formatTokens(data.totalEarned));
209
+ console.log(chalk.dim(' Spent: ') + formatTokens(data.totalSpent));
210
+ console.log();
211
+ }
212
+ prompt();
213
+ return;
214
+ }
215
+ if (cmd === '/clear' || cmd === '/c') {
216
+ showWelcome(balance);
217
+ prompt();
218
+ return;
219
+ }
220
+ console.log(chalk.dim(`\n Unknown command: ${trimmed}\n`));
221
+ prompt();
222
+ return;
223
+ }
224
+ // Check if we have enough tokens
225
+ if (balance < 100) {
226
+ console.log();
227
+ console.log(chalk.yellow(' Low tokens.') + chalk.dim(' Watch an ad to continue.'));
228
+ console.log();
229
+ const watch = await new Promise((resolve) => {
230
+ rl.question(chalk.dim(' Watch now? ') + chalk.cyan('[Y/n] '), (answer) => {
231
+ resolve(answer.toLowerCase() !== 'n');
232
+ });
233
+ });
234
+ if (watch) {
235
+ const success = await watchAd(apiKey);
236
+ if (success) {
237
+ const newData = await getBalance(apiKey);
238
+ balance = newData?.balance || balance;
239
+ }
240
+ }
241
+ console.log();
242
+ prompt();
243
+ return;
244
+ }
245
+ // Generate response
246
+ console.log();
247
+ const spinner = ora({ text: chalk.dim('Thinking...'), spinner: 'dots' }).start();
248
+ const result = await generate(apiKey, trimmed);
249
+ if (!result) {
250
+ spinner.fail(chalk.red('Failed to generate response'));
251
+ console.log();
252
+ prompt();
253
+ return;
254
+ }
255
+ if (result.text === '__INSUFFICIENT_TOKENS__') {
256
+ spinner.stop();
257
+ console.log(chalk.yellow(' Insufficient tokens.') + chalk.dim(' Watch an ad to continue.'));
258
+ console.log();
259
+ const watch = await new Promise((resolve) => {
260
+ rl.question(chalk.dim(' Watch now? ') + chalk.cyan('[Y/n] '), (answer) => {
261
+ resolve(answer.toLowerCase() !== 'n');
262
+ });
263
+ });
264
+ if (watch) {
265
+ const success = await watchAd(apiKey);
266
+ if (success) {
267
+ const newData = await getBalance(apiKey);
268
+ balance = newData?.balance || balance;
269
+ }
270
+ }
271
+ console.log();
272
+ prompt();
273
+ return;
274
+ }
275
+ spinner.stop();
276
+ // Show response
277
+ console.log(chalk.white(result.text));
278
+ console.log();
279
+ console.log(chalk.dim(` ─ ${formatTokens(result.tokensUsed)} tokens used`));
280
+ console.log();
281
+ // Update balance
282
+ balance = Math.max(0, balance - result.tokensUsed);
283
+ prompt();
284
+ });
285
+ };
286
+ // Handle Ctrl+C gracefully
287
+ rl.on('close', () => {
288
+ console.log(chalk.dim('\n Goodbye.\n'));
289
+ process.exit(0);
290
+ });
291
+ // Start prompt loop
292
+ prompt();
293
+ }
294
+ // Run
295
+ main().catch((error) => {
296
+ console.error(chalk.red('Error:'), error.message);
297
+ process.exit(1);
176
298
  });
177
- program.parse();
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@nolimitcli/cli",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
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"