@nolimitcli/cli 1.2.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 -120
  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,163 +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.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'));
53
+ return data.apiKey;
49
54
  }
50
- catch (error) {
51
- spinner.fail(chalk.red('Authentication failed'));
52
- console.error(error instanceof Error ? error.message : error);
53
- process.exit(1);
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'));
89
+ spinner.fail('Timed out');
90
+ return false;
127
91
  }
128
- catch (error) {
129
- spinner.fail(chalk.red('Failed'));
130
- console.error(error instanceof Error ? error.message : error);
131
- process.exit(1);
132
- }
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(' └─────────────────────────────┘'));
132
+ console.log();
133
+ console.log(chalk.dim(' Balance: ') + chalk.bold.white(formatTokens(balance)) + chalk.dim(' tokens'));
169
134
  console.log();
170
- console.log(chalk.bold('NoLimit'));
171
- 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(' ─────────────────────────────────'));
172
137
  console.log();
173
- 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);
174
298
  });
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.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"