@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.
- package/dist/index.js +243 -120
- 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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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:
|
|
44
|
+
userAgent: `nolimit-cli/${VERSION}`,
|
|
34
45
|
consent: true,
|
|
35
46
|
}),
|
|
36
47
|
});
|
|
37
|
-
if (!response.ok)
|
|
38
|
-
|
|
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
|
-
|
|
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
|
|
51
|
-
|
|
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
|
-
|
|
58
|
+
}
|
|
59
|
+
async function watchAd(apiKey) {
|
|
68
60
|
try {
|
|
69
61
|
// Get starting balance
|
|
70
|
-
const
|
|
71
|
-
|
|
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
|
-
|
|
79
|
-
headers: {
|
|
80
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
81
|
-
},
|
|
66
|
+
headers: { 'Authorization': `Bearer ${apiKey}` },
|
|
82
67
|
});
|
|
83
|
-
if (!adResponse.ok)
|
|
84
|
-
|
|
85
|
-
}
|
|
68
|
+
if (!adResponse.ok)
|
|
69
|
+
return false;
|
|
86
70
|
const adData = await adResponse.json();
|
|
87
|
-
|
|
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
|
-
//
|
|
96
|
-
const
|
|
74
|
+
// Poll for completion
|
|
75
|
+
const spinner = ora('Watching...').start();
|
|
97
76
|
let attempts = 0;
|
|
98
|
-
const maxAttempts = 60;
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
126
|
-
|
|
89
|
+
spinner.fail('Timed out');
|
|
90
|
+
return false;
|
|
127
91
|
}
|
|
128
|
-
catch
|
|
129
|
-
|
|
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
|
-
|
|
95
|
+
}
|
|
96
|
+
async function generate(apiKey, prompt) {
|
|
146
97
|
try {
|
|
147
|
-
const response = await fetch(`${API_BASE}/api/
|
|
148
|
-
|
|
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
|
-
|
|
108
|
+
if (response.status === 402) {
|
|
109
|
+
return { text: '__INSUFFICIENT_TOKENS__', tokensUsed: 0 };
|
|
110
|
+
}
|
|
111
|
+
throw new Error(data.message || 'Generation failed');
|
|
152
112
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
163
|
-
console.error(error instanceof Error ? error.message : error);
|
|
164
|
-
process.exit(1);
|
|
119
|
+
return null;
|
|
165
120
|
}
|
|
166
|
-
}
|
|
167
|
-
//
|
|
168
|
-
|
|
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.
|
|
171
|
-
console.log(chalk.dim('
|
|
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
|
-
|
|
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": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "No subscription. Just code.
|
|
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/
|
|
28
|
+
"url": "https://github.com/buzzernetwork/nolimit"
|
|
28
29
|
},
|
|
29
30
|
"engines": {
|
|
30
|
-
"node": ">=
|
|
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"
|