prituscode 1.0.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.
package/.env.example ADDED
@@ -0,0 +1,2 @@
1
+ PRITUS_API_URL=https://pritusai.netlify.app
2
+ PRITUS_MODEL=pro
package/package.json CHANGED
@@ -1,17 +1,23 @@
1
1
  {
2
2
  "name": "prituscode",
3
- "version": "1.0.0",
4
- "description": "Terminal-based AI coding agent CLI",
3
+ "version": "2.0.1",
4
+ "description": "PRITUS CODE - Production-ready terminal AI coding agent CLI",
5
5
  "main": "prituscode.js",
6
6
  "bin": {
7
- "prituscode": "./prituscode.js"
8
- },
9
- "scripts": {
10
- "start": "node ./prituscode.js"
7
+ "prituscode": "prituscode.js"
11
8
  },
9
+ "keywords": [
10
+ "pritus",
11
+ "prituscode",
12
+ "ai",
13
+ "agent",
14
+ "cli",
15
+ "coding",
16
+ "terminal"
17
+ ],
18
+ "author": "Pritus AI",
19
+ "license": "MIT",
12
20
  "engines": {
13
21
  "node": ">=18.0.0"
14
- },
15
- "author": "",
16
- "license": "ISC"
17
- }
22
+ }
23
+ }
package/prituscode.js CHANGED
@@ -1,359 +1,886 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require('fs');
4
- const path = require('path');
5
- const os = require('os');
6
- const readline = require('readline');
7
- const { execSync } = require('child_process');
8
-
9
- // --- ANSI COLORS & STYLES ---
10
- const c = {
11
- rst: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
12
- black: '\x1b[30m', red: '\x1b[31m', green: '\x1b[32m',
13
- yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m',
14
- cyan: '\x1b[36m', white: '\x1b[37m',
15
- bgMagenta: '\x1b[45m', bgBlue: '\x1b[44m'
16
- };
17
-
18
- // --- CONFIG & STATE ---
19
- const args = process.argv.slice(2);
20
- let config = {
21
- tier: 'pro',
22
- endpoint: 'https://pritusai.vercel.app/api/pritus/chat',
23
- oneShotPrompt: ''
24
- };
25
- const messages = [];
26
-
27
- // Parse flags
28
- for (let i = 0; i < args.length; i++) {
29
- if (args[i] === '--endpoint' && args[i + 1]) {
30
- config.endpoint = args[++i];
31
- } else if (args[i] === '--model' && args[i + 1]) {
32
- config.tier = args[++i];
33
- } else if (!args[i].startsWith('--')) {
34
- config.oneShotPrompt = args.slice(i).join(' ');
35
- break;
3
+ // --- IMPORTS ---
4
+ const readline = require('readline')
5
+ const { URL } = require('url')
6
+ const path = require('path')
7
+ const os = require('os')
8
+ const fs = require('fs')
9
+ const http = require('http')
10
+ const { exec } = require('child_process')
11
+
12
+ // --- ENVIRONMENT & CONFIGURATION ---
13
+ function loadEnv() {
14
+ const envPath = path.join(process.cwd(), '.env')
15
+ if (!fs.existsSync(envPath)) return
16
+ try {
17
+ const content = fs.readFileSync(envPath, 'utf8')
18
+ const lines = content.split('\n')
19
+ for (let i = 0; i < lines.length; i++) {
20
+ const line = lines[i].trim()
21
+ if (!line || line.startsWith('#')) continue
22
+ const eqIdx = line.indexOf('=')
23
+ if (eqIdx > 0) {
24
+ const key = line.slice(0, eqIdx).trim()
25
+ const val = line.slice(eqIdx + 1).trim()
26
+ if (!process.env[key]) {
27
+ process.env[key] = val
28
+ }
29
+ }
36
30
  }
31
+ } catch (err) {
32
+ // Ignore env load errors silently
33
+ }
37
34
  }
38
35
 
39
- // --- SYSTEM PROMPT ---
40
- const AGENT_SYSTEM = `
41
- You are PRITUS CODE, an elite terminal-based AI coding agent.
42
- When generating or modifying code, you must ACT as an agent. Use the exact format below:
36
+ loadEnv()
43
37
 
44
- THINKING: <one sentence reasoning about what you will do>
45
- CREATE_FILE: <filepath>
46
- <entire file content here>
47
- DONE: <short summary of what was done>
38
+ const VERSION = '1.0.0'
39
+ const DEFAULT_API_URL = process.env.PRITUS_API_URL || 'https://pritusai.netlify.app'
40
+ const DEFAULT_MODEL = process.env.PRITUS_MODEL || 'pro'
41
+ const AUTH_DIR = path.join(os.homedir(), '.prituscode')
42
+ const AUTH_FILE = path.join(AUTH_DIR, 'auth.json')
48
43
 
49
- If the user is just asking a question and no files need to be written, simply reply conversationally without those tags. Do not reveal your underlying model name under any circumstance.
50
- `;
51
- messages.push({ role: 'system', content: AGENT_SYSTEM });
44
+ const MODELS = {
45
+ 1: { id: 'flash', label: 'Flash', desc: 'Fast & lightweight' },
46
+ 2: { id: 'pro', label: 'Pro', desc: 'Balanced — general use' },
47
+ 3: { id: 'thinking', label: 'Thinking', desc: 'Deep reasoning' },
48
+ 4: { id: 'ultra', label: 'Ultra Code', desc: 'Elite code generation' }
49
+ }
52
50
 
53
- // --- UTILS ---
54
- const sleep = ms => new Promise(r => setTimeout(r, ms));
51
+ const AGENT_SYSTEM = `You are Pritus Code, an AI coding agent in a terminal. You CREATE and MODIFY real files.
52
+ When asked to build something, respond in EXACT format:
53
+ THINKING: <one sentence>
54
+ CREATE_FILE: <filename>
55
+ \`\`\`<language>
56
+ <full file content>
57
+ \`\`\`
58
+ `
59
+
60
+ // --- AUTHENTICATION STORAGE HELPERS ---
61
+ function saveAuthSession(sessionData) {
62
+ try {
63
+ if (!fs.existsSync(AUTH_DIR)) {
64
+ fs.mkdirSync(AUTH_DIR, { recursive: true })
65
+ }
66
+ fs.writeFileSync(AUTH_FILE, JSON.stringify(sessionData, null, 2), 'utf8')
67
+ return true
68
+ } catch (err) {
69
+ return false
70
+ }
71
+ }
55
72
 
56
- const rl = readline.createInterface({
57
- input: process.stdin,
58
- output: process.stdout,
59
- prompt: `${c.magenta}${c.bold}> ${c.rst}`
60
- });
73
+ function loadAuthSession() {
74
+ try {
75
+ if (fs.existsSync(AUTH_FILE)) {
76
+ const data = fs.readFileSync(AUTH_FILE, 'utf8')
77
+ return JSON.parse(data)
78
+ }
79
+ } catch (err) {
80
+ // Ignore auth load errors
81
+ }
82
+ return null
83
+ }
61
84
 
62
- const ask = query => new Promise(resolve => {
63
- rl.question(query, resolve);
64
- });
85
+ function clearAuthSession() {
86
+ try {
87
+ if (fs.existsSync(AUTH_FILE)) {
88
+ fs.unlinkSync(AUTH_FILE)
89
+ }
90
+ return true
91
+ } catch (err) {
92
+ return false
93
+ }
94
+ }
65
95
 
66
- // --- UI COMPONENTS ---
67
- function clearConsole() {
68
- process.stdout.write('\x1B[2J\x1B[0f');
96
+ function openBrowser(url) {
97
+ const platform = process.platform
98
+ if (platform === 'win32') {
99
+ exec('start "" "' + url + '"')
100
+ } else if (platform === 'darwin') {
101
+ exec('open "' + url + '"')
102
+ } else {
103
+ exec('xdg-open "' + url + '"')
104
+ }
69
105
  }
70
106
 
71
- function printLogo() {
72
- // Blue to Magenta ANSI 256 gradient approximation
73
- const d1 = '\x1b[38;5;33m';
74
- const d2 = '\x1b[38;5;39m';
75
- const d3 = '\x1b[38;5;135m';
76
- const d4 = '\x1b[38;5;165m';
77
- const d5 = '\x1b[38;5;199m';
78
-
79
- console.log(`
80
- ${d1} /\\ ${c.bold}██████╗ ██████╗ ██╗████████╗██╗ ██╗███████╗${c.rst}
81
- ${d2} / \\ ${c.bold}██╔══██╗██╔══██╗██║╚══██╔══╝██║ ██║██╔════╝${c.rst}
82
- ${d3} /____\\ ${c.bold}██████╔╝██████╔╝██║ ██║ ██║ ██║███████╗${c.rst}
83
- ${d4} \\ / ${c.bold}██╔═══╝ ██╔══██╗██║ ██║ ██║ ██║╚════██║${c.rst}
84
- ${d5} \\ / ${c.bold}██║ ██║ ██║██║ ██║ ╚██████╔╝███████║${c.rst}
85
- ${c.magenta} \\/ ${c.bold}╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝${c.rst}
86
- ${c.dim}v1.0.0${c.rst}
87
- `);
107
+ // --- ANSI & FORMATTING HELPERS ---
108
+ function stripAnsi(str) {
109
+ return str.replace(/\x1B\[[0-9;]{0,}[A-Za-z]/g, '')
88
110
  }
89
111
 
90
- function printWelcomeBox() {
91
- const cwd = process.cwd();
92
- const boxWidth = Math.max(60, cwd.length + 20);
93
- const line = '─'.repeat(boxWidth - 2);
94
- console.log(`${c.magenta}┌${line}┐${c.rst}`);
95
- console.log(`${c.magenta}│${c.rst} ${c.bold}Welcome to PRITUS CODE${c.rst}`.padEnd(boxWidth + c.magenta.length + c.rst.length + c.bold.length + c.rst.length - 1) + `${c.magenta}│${c.rst}`);
96
- console.log(`${c.magenta}│${c.rst} ${c.dim}Type /help for commands, @filename to attach files${c.rst}`.padEnd(boxWidth + c.magenta.length + c.rst.length + c.dim.length + c.rst.length - 1) + `${c.magenta}│${c.rst}`);
97
- console.log(`${c.magenta}│${c.rst} ${c.cyan}Workspace: ${cwd}${c.rst}`.padEnd(boxWidth + c.magenta.length + c.rst.length + c.cyan.length + c.rst.length - 1) + `${c.magenta}│${c.rst}`);
98
- console.log(`${c.magenta}└${line}┘${c.rst}\n`);
99
-
100
- console.log(`${c.bold}Tips:${c.rst}`);
101
- console.log(` 1. Use @file to include file context`);
102
- console.log(` 2. Type /model to switch intelligence tiers`);
103
- console.log(` 3. Type /init to set up project instructions`);
104
- console.log(` 4. Let the agent build files for you ${c.green}✓${c.rst}\n`);
112
+ function bold(str) {
113
+ return '\x1b[1m' + str + '\x1b[22m'
105
114
  }
106
115
 
107
- function printStatusBar() {
108
- const cwd = path.basename(process.cwd());
109
- const bar = `${c.bgMagenta}${c.white} ${cwd} ${c.rst} ${c.dim}model:${c.rst} ${config.tier} ${c.dim}| /help for menu${c.rst}`;
110
- console.log(`\n${bar}`);
116
+ function dim(str) {
117
+ return '\x1b[2m' + str + '\x1b[22m'
111
118
  }
112
119
 
113
- async function startLoading(text = 'Thinking') {
114
- const frames = ['⠋', '⠙', '⠹', '⠸', '', '⠴', '⠦', '⠧', '⠇', '⠏'];
115
- let i = 0;
116
- const interval = setInterval(() => {
117
- process.stdout.write(`\r${c.magenta}${frames[i]}${c.rst} ${c.dim}${text}...${c.rst}`);
118
- i = (i + 1) % frames.length;
119
- }, 80);
120
- return () => {
121
- clearInterval(interval);
122
- process.stdout.write('\r\x1b[K'); // clear line
123
- };
120
+ function cyan(str) {
121
+ return '\x1b[36m' + str + '\x1b[39m'
124
122
  }
125
123
 
126
- async function streamText(text) {
127
- const words = text.split(' ');
128
- for (const word of words) {
129
- process.stdout.write(word + ' ');
130
- await sleep(20); // typing effect
131
- }
132
- console.log();
124
+ function green(str) {
125
+ return '\x1b[32m' + str + '\x1b[39m'
126
+ }
127
+
128
+ function magenta(str) {
129
+ return '\x1b[35m' + str + '\x1b[39m'
130
+ }
131
+
132
+ function brightMagenta(str) {
133
+ return '\x1b[95m' + str + '\x1b[39m'
134
+ }
135
+
136
+ function ansi256(code, str) {
137
+ return '\x1b[38;5;' + code + 'm' + str + '\x1b[0m'
138
+ }
139
+
140
+ function formatPath(p) {
141
+ const home = os.homedir()
142
+ if (p.startsWith(home)) {
143
+ return '~' + p.slice(home.length)
144
+ }
145
+ return p
133
146
  }
134
147
 
135
- // --- COMMANDS & FEATURES ---
136
- async function selectModel() {
137
- console.log(`\n${c.bold}Select Model Tier:${c.rst}`);
138
- console.log(` 1. Flash - Fast & lightweight`);
139
- console.log(` 2. Pro - Balanced general use`);
140
- console.log(` 3. Thinking - Deep reasoning`);
141
- console.log(` 4. Ultra Code - Elite code generation`);
142
-
143
- const choice = await ask(`\n${c.dim}Press 1-4 or Enter for Pro: ${c.rst}`);
144
- switch(choice.trim()) {
145
- case '1': config.tier = 'flash'; break;
146
- case '3': config.tier = 'thinking'; break;
147
- case '4': config.tier = 'code'; break;
148
- case '2':
149
- default: config.tier = 'pro'; break;
148
+ // --- LOGO & ASCII ART GENERATORS ---
149
+ function getDiamondLogo() {
150
+ const lines = [
151
+ ansi256(39, '██'),
152
+ ' ' + ansi256(75, '██'),
153
+ ' ' + ansi256(99, '██'),
154
+ ' ' + ansi256(141, '██'),
155
+ ' ' + ansi256(177, '██'),
156
+ ansi256(207, '██'),
157
+ ansi256(207, '████████')
158
+ ]
159
+ return lines
160
+ }
161
+
162
+ const LETTERS = {
163
+ P: [
164
+ '██████╗ ',
165
+ '██╔══██╗',
166
+ '██████╔╝',
167
+ '██╔═══╝ ',
168
+ '██║ ',
169
+ '╚═╝ '
170
+ ],
171
+ R: [
172
+ '██████╗ ',
173
+ '██╔══██╗',
174
+ '██████╔╝',
175
+ '██╔██╗ ',
176
+ '██║╚██╗ ',
177
+ '╚═╝ ╚═╝ '
178
+ ],
179
+ I: [
180
+ '██╗',
181
+ '██║',
182
+ '██║',
183
+ '██║',
184
+ '██║',
185
+ '╚═╝'
186
+ ],
187
+ T: [
188
+ '████████╗',
189
+ '╚══██╔══╝',
190
+ ' ██║ ',
191
+ ' ██║ ',
192
+ ' ██║ ',
193
+ ' ╚═╝ '
194
+ ],
195
+ U: [
196
+ '██╗ ██╗',
197
+ '██║ ██║',
198
+ '██║ ██║',
199
+ '██║ ██║',
200
+ '╚██████╔╝',
201
+ ' ╚═════╝ '
202
+ ],
203
+ S: [
204
+ '███████╗',
205
+ '██╔════╝',
206
+ '███████╗',
207
+ '╚════██║',
208
+ '███████║',
209
+ '╚══════╝'
210
+ ],
211
+ C: [
212
+ '███████╗',
213
+ '██╔════╝',
214
+ '██║ ',
215
+ '██║ ',
216
+ '╚██████╗',
217
+ ' ╚═════╝'
218
+ ],
219
+ O: [
220
+ ' █████╗ ',
221
+ '██╔══██╗',
222
+ '██║ ██║',
223
+ '██║ ██║',
224
+ '╚██████╔╝',
225
+ ' ╚═════╝ '
226
+ ],
227
+ D: [
228
+ '██████╗ ',
229
+ '██╔══██╗',
230
+ '██║ ██║',
231
+ '██║ ██║',
232
+ '╚██████╗',
233
+ ' ╚═════╝ '
234
+ ],
235
+ E: [
236
+ '███████╗',
237
+ '██╔════╝',
238
+ '███████╗',
239
+ '██╔════╝',
240
+ '███████╝',
241
+ ' '
242
+ ]
243
+ }
244
+
245
+ function getBigBanner() {
246
+ const rows = []
247
+ for (let r = 0; r < 6; r++) {
248
+ const pritusLine = LETTERS.P[r] + ' ' + LETTERS.R[r] + ' ' + LETTERS.I[r] + ' ' + LETTERS.T[r] + ' ' + LETTERS.U[r] + ' ' + LETTERS.S[r]
249
+ const codeLine = LETTERS.C[r] + ' ' + LETTERS.O[r] + ' ' + LETTERS.D[r] + ' ' + LETTERS.E[r]
250
+ rows.push(pritusLine + ' ' + codeLine)
251
+ }
252
+ return rows
253
+ }
254
+
255
+ function renderHeader() {
256
+ const diamond = getDiamondLogo()
257
+ const banner = getBigBanner()
258
+
259
+ console.log('')
260
+ // Render Diamond Logo cleanly
261
+ for (let i = 0; i < diamond.length; i++) {
262
+ console.log(diamond[i])
263
+ }
264
+ console.log('')
265
+ // Render ASCII Banner stacked cleanly on its own lines
266
+ for (let r = 0; r < banner.length; r++) {
267
+ console.log(cyan(banner[r]))
268
+ }
269
+ console.log(brightMagenta(bold('PRITUS CODE v' + VERSION)))
270
+ console.log('')
271
+ }
272
+
273
+ function renderWelcomeBox(cwd, authSession) {
274
+ const displayCwd = formatPath(cwd)
275
+ const userText = authSession && authSession.email ? 'Logged in as: ' + authSession.email : 'Not logged in (type /login to authenticate with Google)'
276
+
277
+ const lines = [
278
+ 'Welcome to Pritus Code!',
279
+ userText,
280
+ '/help for help, /status for your current setup',
281
+ 'cwd: ' + displayCwd
282
+ ]
283
+
284
+ let innerWidth = 0
285
+ for (let i = 0; i < lines.length; i++) {
286
+ if (lines[i].length > innerWidth) {
287
+ innerWidth = lines[i].length
150
288
  }
151
- console.log(`${c.green}✓ Switched to ${config.tier} tier${c.rst}\n`);
289
+ }
290
+ innerWidth += 4
291
+
292
+ const topBorder = '┌' + '─'.repeat(innerWidth) + '┐'
293
+ const bottomBorder = '└' + '─'.repeat(innerWidth) + '┘'
294
+
295
+ console.log(cyan(topBorder))
296
+
297
+ const pad1 = ' '.repeat(innerWidth - lines[0].length - 2)
298
+ console.log(cyan('│ ') + bold(lines[0]) + pad1 + cyan(' │'))
299
+
300
+ const pad2 = ' '.repeat(innerWidth - lines[1].length - 2)
301
+ const formattedUserLine = authSession && authSession.email ? green(lines[1]) : dim(lines[1])
302
+ console.log(cyan('│ ') + formattedUserLine + pad2 + cyan(' │'))
303
+
304
+ const pad3 = ' '.repeat(innerWidth - lines[2].length - 2)
305
+ console.log(cyan('│ ') + dim(lines[2]) + pad3 + cyan(' │'))
306
+
307
+ const pad4 = ' '.repeat(innerWidth - lines[3].length - 2)
308
+ console.log(cyan('│ ') + dim(lines[3]) + pad4 + cyan(' │'))
309
+
310
+ console.log(cyan(bottomBorder))
311
+ console.log('')
152
312
  }
153
313
 
154
- function processAtFiles(input) {
155
- const fileRegex = /@([\w.\-\/]+)/g;
156
- let modifiedInput = input;
157
- let match;
158
- let addedFiles = [];
314
+ function renderTips() {
315
+ console.log(bold('Tips for getting started:'))
316
+ console.log('1. Ask Pritus to build something — it will create actual files in your project')
317
+ console.log('2. Use @filename to include a file contents in your message')
318
+ console.log('3. Be as specific as you would with another engineer for the best results')
319
+ console.log(green('✓') + ' Run /init to create a PRITUS.md with project instructions')
320
+ console.log('')
321
+ }
159
322
 
160
- while ((match = fileRegex.exec(input)) !== null) {
161
- const filepath = match[1];
162
- try {
163
- const content = fs.readFileSync(filepath, 'utf-8');
164
- modifiedInput += `\n\n[Context from ${filepath}]:\n\`\`\`\n${content}\n\`\`\``;
165
- addedFiles.push(filepath);
166
- } catch (e) {
167
- console.log(`${c.red}⚠ Could not read @file: ${filepath}${c.rst}`);
168
- }
323
+ function renderStatusBar(currentModelId, cwd, authSession) {
324
+ const displayCwd = formatPath(cwd)
325
+ const selectedObj = getModelInfo(currentModelId)
326
+ const authStatus = authSession && authSession.email ? authSession.email : 'guest'
327
+ const barText = 'workspace (' + displayCwd + ') /model ' + selectedObj.id + ' user: ' + authStatus + ' ? for shortcuts'
328
+ console.log(dim(barText))
329
+ console.log('')
330
+ }
331
+
332
+ function getModelInfo(keyOrId) {
333
+ const str = String(keyOrId).toLowerCase()
334
+ for (const k in MODELS) {
335
+ if (String(k) === str || MODELS[k].id === str) {
336
+ return MODELS[k]
169
337
  }
170
- if (addedFiles.length > 0) {
171
- console.log(`${c.dim}Attached: ${addedFiles.join(', ')}${c.rst}`);
338
+ }
339
+ return MODELS[2]
340
+ }
341
+
342
+ // --- GOOGLE OAUTH LOGIN SERVER ---
343
+ function startGoogleOAuthFlow(apiUrl, onComplete) {
344
+ const port = 8585
345
+ const redirectUri = 'http://localhost:' + port + '/callback'
346
+ const loginUrl = apiUrl + '/api/auth/google?redirect_uri=' + encodeURIComponent(redirectUri)
347
+
348
+ const server = http.createServer((req, res) => {
349
+ const reqUrl = new URL(req.url, 'http://localhost:' + port)
350
+ if (reqUrl.pathname === '/callback') {
351
+ const token = reqUrl.searchParams.get('token') || reqUrl.searchParams.get('access_token') || 'demo_token'
352
+ const email = reqUrl.searchParams.get('email') || 'google_user@gmail.com'
353
+ const name = reqUrl.searchParams.get('name') || 'Google User'
354
+
355
+ const session = {
356
+ token: token,
357
+ email: email,
358
+ name: name,
359
+ loggedInAt: Date.now()
360
+ }
361
+
362
+ saveAuthSession(session)
363
+
364
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
365
+ res.end(`
366
+ <!DOCTYPE html>
367
+ <html>
368
+ <head>
369
+ <title>PRITUS CODE - Google Login Successful</title>
370
+ <style>
371
+ body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
372
+ .card { background: #1e293b; padding: 2rem; border-radius: 12px; box-shadow: 0 10px 25px rgba(0,0,0,0.5); text-align: center; max-width: 400px; }
373
+ h1 { color: #38bdf8; font-size: 1.5rem; margin-bottom: 0.5rem; }
374
+ p { color: #94a3b8; font-size: 0.95rem; }
375
+ .check { font-size: 3rem; color: #4ade80; margin-bottom: 1rem; }
376
+ </style>
377
+ </head>
378
+ <body>
379
+ <div class="card">
380
+ <div class="check">✓</div>
381
+ <h1>Google Login Successful!</h1>
382
+ <p>You have signed in as <strong>${email}</strong>.</p>
383
+ <p>You can close this tab and return to your terminal.</p>
384
+ </div>
385
+ </body>
386
+ </html>
387
+ `)
388
+
389
+ setTimeout(() => {
390
+ server.close()
391
+ }, 500)
392
+
393
+ onComplete(null, session)
394
+ } else {
395
+ res.writeHead(404, { 'Content-Type': 'text/plain' })
396
+ res.end('Not Found')
172
397
  }
173
- return modifiedInput;
398
+ })
399
+
400
+ server.listen(port, () => {
401
+ console.log(dim('Opening browser for Google Login...'))
402
+ console.log(dim('URL: ' + loginUrl))
403
+ openBrowser(loginUrl)
404
+ })
405
+
406
+ server.on('error', (err) => {
407
+ console.log('\x1b[31mCould not start local auth server on port ' + port + ': ' + err.message + '\x1b[39m')
408
+ onComplete(err, null)
409
+ })
174
410
  }
175
411
 
176
- // --- AGENT PARSER ---
177
- async function parseAndExecuteAgent(response) {
178
- // Regex to extract blocks
179
- const thinkingMatch = response.match(/THINKING:\s*(.*?)\n/i);
180
- const doneMatch = response.match(/DONE:\s*(.*)/i);
181
- const fileRegex = /CREATE_FILE:\s*(.*?)\n([\s\S]*?)(?=DONE:|CREATE_FILE:|$)/gi;
412
+ // --- MODEL SELECTOR ---
413
+ function promptModelSelection(currentModelId, callback) {
414
+ console.log('Select a model:')
415
+ for (const k in MODELS) {
416
+ const m = MODELS[k]
417
+ const isSelected = m.id === currentModelId
418
+ const pointer = isSelected ? '▸ ' : ' '
419
+ const num = k + ' '
420
+ console.log(pointer + num + bold(m.label) + ' — ' + m.desc)
421
+ }
422
+
423
+ const defaultModelObj = getModelInfo(currentModelId)
424
+ process.stdout.write('Press 1-4, or Enter for ' + defaultModelObj.id + ': ')
425
+
426
+ if (process.stdin.isTTY) {
427
+ process.stdin.setRawMode(true)
428
+ process.stdin.resume()
429
+ process.stdin.once('data', (data) => {
430
+ process.stdin.setRawMode(false)
431
+ process.stdin.pause()
432
+ const char = data.toString('utf8').trim()
433
+ console.log(char)
434
+
435
+ let choiceObj = defaultModelObj
436
+ if (MODELS[char]) {
437
+ choiceObj = MODELS[char]
438
+ }
439
+ console.log(green('✓') + ' Switched to ' + choiceObj.label)
440
+ console.log('')
441
+ callback(choiceObj.id)
442
+ })
443
+ } else {
444
+ const rlTemp = readline.createInterface({
445
+ input: process.stdin,
446
+ output: process.stdout
447
+ })
448
+ rlTemp.question('', (ans) => {
449
+ rlTemp.close()
450
+ const trimmed = ans.trim()
451
+ let choiceObj = defaultModelObj
452
+ if (MODELS[trimmed]) {
453
+ choiceObj = MODELS[trimmed]
454
+ }
455
+ console.log(green('✓') + ' Switched to ' + choiceObj.label)
456
+ console.log('')
457
+ callback(choiceObj.id)
458
+ })
459
+ }
460
+ }
182
461
 
183
- let isAgentic = false;
462
+ // --- FILE PARSER & AGENT OPERATIONS ---
463
+ function processFileOperations(aiResponse) {
464
+ let modifiedAny = false
465
+ const createPattern = /CREATE_FILE:\s{0,}([^\n]+)\n```[a-z]{0,}\n([\s\S]+?)```/gi
466
+ const modifyPattern = /MODIFY_FILE:\s{0,}([^\n]+)\n```[a-z]{0,}\n([\s\S]+?)```/gi
184
467
 
185
- if (thinkingMatch) {
186
- isAgentic = true;
187
- console.log(`\n🤔 ${c.dim}${thinkingMatch[1].trim()}${c.rst}\n`);
468
+ let match
469
+ while ((match = createPattern.exec(aiResponse)) !== null) {
470
+ const fileName = match[1].trim()
471
+ const content = match[2]
472
+ try {
473
+ const targetPath = path.resolve(process.cwd(), fileName)
474
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true })
475
+ fs.writeFileSync(targetPath, content, 'utf8')
476
+ const bytes = Buffer.byteLength(content, 'utf8')
477
+ console.log(green('✓ Created ') + fileName + ' (' + bytes + ' bytes)')
478
+ modifiedAny = true
479
+ } catch (err) {
480
+ console.log('\x1b[31mError creating file ' + fileName + ': ' + err.message + '\x1b[39m')
188
481
  }
482
+ }
189
483
 
190
- let match;
191
- let fileCount = 0;
192
- while ((match = fileRegex.exec(response)) !== null) {
193
- isAgentic = true;
194
- const filepath = match[1].trim();
195
- const content = match[2].trim();
196
-
197
- console.log(`${c.cyan}┌─ CREATE: ${filepath}${c.rst}`);
198
-
199
- try {
200
- const fullPath = path.resolve(process.cwd(), filepath);
201
- fs.mkdirSync(path.dirname(fullPath), { recursive: true });
202
- fs.writeFileSync(fullPath, content);
203
- console.log(`${c.cyan}└─ ${c.green}✓ Created successfully${c.rst}`);
204
- fileCount++;
205
- } catch (err) {
206
- console.log(`${c.cyan}└─ ${c.red}✗ Failed: ${err.message}${c.rst}`);
207
- }
484
+ while ((match = modifyPattern.exec(aiResponse)) !== null) {
485
+ const fileName = match[1].trim()
486
+ const content = match[2]
487
+ try {
488
+ const targetPath = path.resolve(process.cwd(), fileName)
489
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true })
490
+ fs.writeFileSync(targetPath, content, 'utf8')
491
+ const bytes = Buffer.byteLength(content, 'utf8')
492
+ console.log(green('✓ Modified ') + fileName + ' (' + bytes + ' bytes)')
493
+ modifiedAny = true
494
+ } catch (err) {
495
+ console.log('\x1b[31mError modifying file ' + fileName + ': ' + err.message + '\x1b[39m')
208
496
  }
497
+ }
209
498
 
210
- if (isAgentic && doneMatch) {
211
- console.log(`\n${c.green}✓ Agent Finished:${c.rst} ${doneMatch[1].trim()} (${fileCount} files created)`);
499
+ return modifiedAny
500
+ }
501
+
502
+ function expandFileAttachments(text) {
503
+ const atPattern = /@([^\s]+)/g
504
+ let match
505
+ let expanded = text
506
+ const attachedFiles = []
507
+
508
+ while ((match = atPattern.exec(text)) !== null) {
509
+ const fileRef = match[1]
510
+ const filePath = path.resolve(process.cwd(), fileRef)
511
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
512
+ try {
513
+ const fileContent = fs.readFileSync(filePath, 'utf8')
514
+ attachedFiles.push({ ref: fileRef, content: fileContent })
515
+ } catch (e) {
516
+ // file unreadable
517
+ }
212
518
  }
519
+ }
213
520
 
214
- // If it didn't use the agent format, just stream it conversationally
215
- if (!isAgentic) {
216
- await streamText(response.trim());
521
+ if (attachedFiles.length > 0) {
522
+ let header = ''
523
+ for (let i = 0; i < attachedFiles.length; i++) {
524
+ header += '[Attached file: ' + attachedFiles[i].ref + ']\n' + attachedFiles[i].content + '\n[End attached file]\n\n'
217
525
  }
218
- }
526
+ expanded = header + text
527
+ }
219
528
 
220
- // --- API LAYER ---
221
- async function handleChat(prompt) {
222
- const finalPrompt = processAtFiles(prompt);
223
- messages.push({ role: 'user', content: finalPrompt });
529
+ return expanded
530
+ }
224
531
 
225
- const stopLoading = await startLoading();
226
-
227
- try {
228
- const res = await fetch(config.endpoint, {
229
- method: 'POST',
230
- headers: { 'Content-Type': 'application/json' },
231
- body: JSON.stringify({
232
- messages,
233
- tier: config.tier,
234
- plan: 'pro'
235
- })
236
- });
237
-
238
- stopLoading();
239
-
240
- if (!res.ok) {
241
- throw new Error(`Status ${res.status}`);
532
+ // --- HTTP API CLIENT ---
533
+ function sendChatRequest(apiUrl, modelId, userPrompt, messagesHistory, authSession, callback) {
534
+ const expandedPrompt = expandFileAttachments(userPrompt)
535
+
536
+ let targetUrl = apiUrl
537
+ if (!targetUrl.endsWith('/api/pritus/chat')) {
538
+ if (targetUrl.endsWith('/')) {
539
+ targetUrl += 'api/pritus/chat'
540
+ } else {
541
+ targetUrl += '/api/pritus/chat'
542
+ }
543
+ }
544
+
545
+ let parsedUrl
546
+ try {
547
+ parsedUrl = new URL(targetUrl)
548
+ } catch (e) {
549
+ console.log('\x1b[31mInvalid API URL: ' + targetUrl + '\x1b[39m')
550
+ return callback(new Error('Invalid URL'))
551
+ }
552
+
553
+ const httpModule = parsedUrl.protocol === 'https:' ? require('https') : require('http')
554
+ const payload = JSON.stringify({
555
+ prompt: expandedPrompt,
556
+ model: modelId,
557
+ system: AGENT_SYSTEM,
558
+ messages: messagesHistory
559
+ })
560
+
561
+ const reqHeaders = {
562
+ 'Content-Type': 'application/json',
563
+ 'Content-Length': Buffer.byteLength(payload)
564
+ }
565
+
566
+ if (authSession && authSession.token) {
567
+ reqHeaders['Authorization'] = 'Bearer ' + authSession.token
568
+ }
569
+
570
+ const reqOpts = {
571
+ hostname: parsedUrl.hostname,
572
+ port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
573
+ path: parsedUrl.pathname + parsedUrl.search,
574
+ method: 'POST',
575
+ headers: reqHeaders
576
+ }
577
+
578
+ const req = httpModule.request(reqOpts, (res) => {
579
+ let body = ''
580
+ res.on('data', (chunk) => {
581
+ body += chunk.toString('utf8')
582
+ })
583
+ res.on('end', () => {
584
+ if (res.statusCode >= 200 && res.statusCode < 300) {
585
+ let textResult = body
586
+ try {
587
+ const parsed = JSON.parse(body)
588
+ if (parsed.response) textResult = parsed.response
589
+ else if (parsed.message) textResult = parsed.message
590
+ else if (parsed.content) textResult = parsed.content
591
+ else if (parsed.choices && parsed.choices[0] && parsed.choices[0].message) {
592
+ textResult = parsed.choices[0].message.content
593
+ }
594
+ } catch (e) {
595
+ // Response is raw text
242
596
  }
597
+ callback(null, textResult)
598
+ } else {
599
+ callback(new Error('API returned HTTP status ' + res.statusCode + ': ' + body))
600
+ }
601
+ })
602
+ })
603
+
604
+ req.on('error', (err) => {
605
+ callback(err)
606
+ })
607
+
608
+ req.write(payload)
609
+ req.end()
610
+ }
243
611
 
244
- const data = await res.json();
245
- // Assuming API returns { response: "..." } or similar
246
- const reply = data.response || data.reply || data.content || data.message || (data.choices && data.choices[0].message.content);
247
-
248
- if (!reply) {
249
- console.log(`${c.red}⚠ Unexpected API response format.${c.rst}`);
250
- return;
612
+ // --- SLASH COMMANDS HANDLER ---
613
+ function handleSlashCommand(input, state, rl) {
614
+ const parts = input.trim().split(/\s{1,}/)
615
+ const cmd = parts[0].toLowerCase()
616
+ const arg = parts.slice(1).join(' ')
617
+
618
+ switch (cmd) {
619
+ case '/help':
620
+ case '?':
621
+ console.log('')
622
+ console.log(bold('PRITUS CODE Commands & Shortcuts:'))
623
+ console.log(' /login Sign in with Google authentication')
624
+ console.log(' /logout Sign out of current account')
625
+ console.log(' /whoami Display active user profile')
626
+ console.log(' /help, ? Show this help message')
627
+ console.log(' /status Show current configuration')
628
+ console.log(' /init Create PRITUS.md with project rules')
629
+ console.log(' /model [1-4] Change AI model (1: Flash, 2: Pro, 3: Thinking, 4: Ultra)')
630
+ console.log(' /clear Clear terminal screen')
631
+ console.log(' /exit, /quit Exit PRITUS CODE')
632
+ console.log(' @filename Include file contents in your prompt')
633
+ console.log('')
634
+ rl.prompt()
635
+ return true
636
+
637
+ case '/login':
638
+ startGoogleOAuthFlow(state.apiUrl, (err, session) => {
639
+ if (!err && session) {
640
+ state.authSession = session
641
+ console.log(green('✓ Logged in as ') + session.email + ' (' + session.name + ')')
642
+ }
643
+ console.log('')
644
+ rl.prompt()
645
+ })
646
+ return true
647
+
648
+ case '/logout':
649
+ clearAuthSession()
650
+ state.authSession = null
651
+ console.log(green('✓ Successfully logged out.'))
652
+ console.log('')
653
+ rl.prompt()
654
+ return true
655
+
656
+ case '/whoami':
657
+ console.log('')
658
+ if (state.authSession && state.authSession.email) {
659
+ console.log(bold('Authenticated User:'))
660
+ console.log(' Email: ' + state.authSession.email)
661
+ console.log(' Name: ' + (state.authSession.name || 'N/A'))
662
+ } else {
663
+ console.log(dim('Not logged in. Type /login to authenticate with Google.'))
664
+ }
665
+ console.log('')
666
+ rl.prompt()
667
+ return true
668
+
669
+ case '/status':
670
+ console.log('')
671
+ console.log(bold('Current Setup:'))
672
+ console.log(' Version: ' + VERSION)
673
+ console.log(' Model: ' + getModelInfo(state.currentModel).label + ' (' + state.currentModel + ')')
674
+ console.log(' User: ' + (state.authSession ? state.authSession.email : 'Not logged in'))
675
+ console.log(' API URL: ' + state.apiUrl)
676
+ console.log(' CWD: ' + process.cwd())
677
+ console.log('')
678
+ rl.prompt()
679
+ return true
680
+
681
+ case '/init':
682
+ {
683
+ const docPath = path.join(process.cwd(), 'PRITUS.md')
684
+ const docContent = `# PRITUS CODE Project Instructions
685
+
686
+ ## Overview
687
+ Describe your project and architectural guidelines here for Pritus Code.
688
+
689
+ ## Guidelines
690
+ - Write clean, modular, and maintainable code.
691
+ - Always implement proper error handling.
692
+ `
693
+ try {
694
+ fs.writeFileSync(docPath, docContent, 'utf8')
695
+ console.log(green('✓ Created PRITUS.md with project instructions'))
696
+ } catch (err) {
697
+ console.log('\x1b[31mError creating PRITUS.md: ' + err.message + '\x1b[39m')
251
698
  }
699
+ console.log('')
700
+ rl.prompt()
701
+ }
702
+ return true
703
+
704
+ case '/model':
705
+ if (arg) {
706
+ const info = getModelInfo(arg)
707
+ state.currentModel = info.id
708
+ console.log(green('✓ Switched to ') + info.label)
709
+ console.log('')
710
+ rl.prompt()
711
+ } else {
712
+ promptModelSelection(state.currentModel, (newModel) => {
713
+ state.currentModel = newModel
714
+ rl.prompt()
715
+ })
716
+ }
717
+ return true
718
+
719
+ case '/clear':
720
+ console.clear()
721
+ renderHeader()
722
+ renderStatusBar(state.currentModel, process.cwd(), state.authSession)
723
+ rl.prompt()
724
+ return true
725
+
726
+ case '/exit':
727
+ case '/quit':
728
+ console.log(dim('Goodbye!'))
729
+ process.exit(0)
730
+ return true
731
+
732
+ default:
733
+ if (cmd.startsWith('/')) {
734
+ console.log('\x1b[31mUnknown command: ' + cmd + '. Type /help for assistance.\x1b[39m')
735
+ console.log('')
736
+ rl.prompt()
737
+ return true
738
+ }
739
+ return false
740
+ }
741
+ }
252
742
 
253
- messages.push({ role: 'assistant', content: reply });
254
- await parseAndExecuteAgent(reply);
743
+ // --- MAIN REPL LOOP ---
744
+ function startRepl(state) {
745
+ const rl = readline.createInterface({
746
+ input: process.stdin,
747
+ output: process.stdout,
748
+ prompt: '> '
749
+ })
255
750
 
256
- } catch (error) {
257
- stopLoading();
258
- console.log(`${c.red}Pritus AI is suffering from heavy traffic right now. (Error: ${error.message})${c.rst}`);
259
- // Remove the failed message so they can retry
260
- messages.pop();
261
- }
262
- }
751
+ const messagesHistory = []
263
752
 
264
- // --- SIMULATED GOOGLE LOGIN ---
265
- async function handleLogin() {
266
- console.log(`\n${c.bold}Google Authentication${c.rst}`);
267
- const code = Math.random().toString(36).substring(2, 10).toUpperCase();
268
- console.log(`${c.dim}Please go to: ${c.white}https://google.com/device${c.rst}`);
269
- console.log(`${c.dim}And enter this code: ${c.magenta}${c.bold}${code}${c.rst}\n`);
270
-
271
- const stop = await startLoading('Waiting for authorization');
272
- await sleep(4000); // Simulate waiting for external auth
273
- stop();
274
-
275
- const tokenPath = path.join(os.homedir(), '.prituscode_auth');
276
- fs.writeFileSync(tokenPath, `mock_token_${Date.now()}`);
277
- console.log(`${c.green}✓ Successfully logged in with Google!${c.rst}\n`);
278
- }
753
+ rl.prompt()
279
754
 
755
+ rl.on('line', (line) => {
756
+ const input = line.trim()
757
+ if (!input) {
758
+ rl.prompt()
759
+ return
760
+ }
280
761
 
281
- // --- MAIN REPL ---
282
- async function main() {
283
- if (config.oneShotPrompt) {
284
- console.log(`${c.magenta}PRITUS CODE${c.rst} ${c.dim}> One-shot mode${c.rst}\n`);
285
- await handleChat(config.oneShotPrompt);
286
- process.exit(0);
762
+ if (handleSlashCommand(input, state, rl)) {
763
+ return
287
764
  }
288
765
 
289
- clearConsole();
290
- printLogo();
291
- printWelcomeBox();
292
-
293
- // Auto-prompt for model on first run if in interactive mode
294
- await selectModel();
295
- printStatusBar();
296
-
297
- rl.prompt();
298
-
299
- rl.on('line', async (line) => {
300
- const input = line.trim();
301
- if (!input) {
302
- rl.prompt();
303
- return;
304
- }
766
+ process.stdout.write(dim('Thinking... \r'))
305
767
 
306
- switch (input.toLowerCase()) {
307
- case '/exit':
308
- case '/quit':
309
- console.log(`${c.dim}Goodbye!${c.rst}`);
310
- process.exit(0);
311
- break;
312
- case '/clear':
313
- clearConsole();
314
- messages.length = 1; // Keep system prompt
315
- console.log(`${c.green}✓ Context cleared.${c.rst}`);
316
- break;
317
- case '/model':
318
- await selectModel();
319
- break;
320
- case '/status':
321
- console.log(`\n${c.bold}PRITUS STATUS${c.rst}`);
322
- console.log(`Tier: ${c.magenta}${config.tier}${c.rst}`);
323
- console.log(`Messages in context: ${messages.length - 1}`);
324
- console.log(`Workspace: ${process.cwd()}\n`);
325
- break;
326
- case '/init':
327
- fs.writeFileSync('PRITUS.md', '# PRITUS CODE Instructions\n\nAdd your custom project rules here. The agent will read this automatically if referenced.');
328
- console.log(`${c.green}✓ Created PRITUS.md${c.rst}`);
329
- break;
330
- case '/login':
331
- await handleLogin();
332
- break;
333
- case '/help':
334
- console.log(`\n${c.bold}Commands:${c.rst}`);
335
- console.log(` @filename Include file contents in your prompt`);
336
- console.log(` /model Change AI tier (flash, pro, thinking, code)`);
337
- console.log(` /init Create a PRITUS.md instruction file`);
338
- console.log(` /clear Clear conversation history`);
339
- console.log(` /status Show current agent status`);
340
- console.log(` /login Authenticate with Google`);
341
- console.log(` /exit Close PRITUS CODE\n`);
342
- break;
343
- default:
344
- await handleChat(input);
345
- break;
346
- }
347
-
348
- printStatusBar();
349
- rl.prompt();
350
- });
768
+ sendChatRequest(state.apiUrl, state.currentModel, input, messagesHistory, state.authSession, (err, responseText) => {
769
+ // Clear line
770
+ process.stdout.write(' \r')
771
+
772
+ if (err) {
773
+ console.log('\x1b[31mError: ' + err.message + '\x1b[39m')
774
+ console.log('')
775
+ rl.prompt()
776
+ return
777
+ }
778
+
779
+ console.log(responseText)
780
+ console.log('')
781
+
782
+ const opsExecuted = processFileOperations(responseText)
783
+ if (opsExecuted) {
784
+ console.log('')
785
+ }
786
+
787
+ messagesHistory.push({ role: 'user', content: input })
788
+ messagesHistory.push({ role: 'assistant', content: responseText })
789
+
790
+ rl.prompt()
791
+ })
792
+ })
793
+
794
+ rl.on('close', () => {
795
+ console.log(dim('\nGoodbye!'))
796
+ process.exit(0)
797
+ })
351
798
  }
352
799
 
353
- // Handle interrupts gracefully
354
- rl.on('SIGINT', () => {
355
- console.log(`\n${c.dim}Operation cancelled. Type /exit to quit.${c.rst}`);
356
- rl.prompt();
357
- });
800
+ // --- ENTRY POINT ---
801
+ function main() {
802
+ const args = process.argv.slice(2)
803
+
804
+ const state = {
805
+ apiUrl: DEFAULT_API_URL,
806
+ currentModel: DEFAULT_MODEL,
807
+ authSession: loadAuthSession()
808
+ }
809
+
810
+ // Parse direct flags
811
+ if (args.includes('-v') || args.includes('--version')) {
812
+ console.log('PRITUS CODE v' + VERSION)
813
+ process.exit(0)
814
+ }
815
+
816
+ if (args.includes('-h') || args.includes('--help')) {
817
+ console.log('PRITUS CODE v' + VERSION + ' - Terminal AI Coding Agent')
818
+ console.log('\nUsage:')
819
+ console.log(' prituscode Start interactive terminal session')
820
+ console.log(' prituscode login Sign in with Google OAuth')
821
+ console.log(' prituscode "build a server" Run single prompt')
822
+ console.log(' prituscode -m ultra "query" Run single prompt with specified model')
823
+ console.log(' prituscode -v, --version Show version')
824
+ console.log(' prituscode -h, --help Show help')
825
+ process.exit(0)
826
+ }
827
+
828
+ if (args[0] === 'login') {
829
+ startGoogleOAuthFlow(state.apiUrl, (err, session) => {
830
+ if (err) {
831
+ process.exit(1)
832
+ }
833
+ console.log(green('✓ Logged in as ') + session.email + ' (' + session.name + ')')
834
+ process.exit(0)
835
+ })
836
+ return
837
+ }
838
+
839
+ let modelOverride = null
840
+ const queryArgs = []
841
+
842
+ for (let i = 0; i < args.length; i++) {
843
+ if (args[i] === '-m' || args[i] === '--model') {
844
+ if (i + 1 < args.length) {
845
+ modelOverride = args[i + 1]
846
+ i++
847
+ }
848
+ } else {
849
+ queryArgs.push(args[i])
850
+ }
851
+ }
852
+
853
+ if (modelOverride) {
854
+ const info = getModelInfo(modelOverride)
855
+ state.currentModel = info.id
856
+ }
857
+
858
+ // Non-interactive single prompt mode
859
+ if (queryArgs.length > 0) {
860
+ const singlePrompt = queryArgs.join(' ')
861
+ process.stdout.write(dim('Processing request...\n'))
862
+ sendChatRequest(state.apiUrl, state.currentModel, singlePrompt, [], state.authSession, (err, responseText) => {
863
+ if (err) {
864
+ console.error('\x1b[31mError: ' + err.message + '\x1b[39m')
865
+ process.exit(1)
866
+ }
867
+ console.log(responseText)
868
+ processFileOperations(responseText)
869
+ process.exit(0)
870
+ })
871
+ return
872
+ }
873
+
874
+ // Interactive mode startup
875
+ renderHeader()
876
+ renderWelcomeBox(process.cwd(), state.authSession)
877
+ renderTips()
878
+
879
+ promptModelSelection(state.currentModel, (selectedModel) => {
880
+ state.currentModel = selectedModel
881
+ renderStatusBar(state.currentModel, process.cwd(), state.authSession)
882
+ startRepl(state)
883
+ })
884
+ }
358
885
 
359
- main().catch(console.error);
886
+ main()
package/README.md DELETED
@@ -1,79 +0,0 @@
1
- # PRITUS CODE CLI
2
-
3
- **Terminal-based AI coding assistant.** Local Vision Intelligence in your terminal.
4
-
5
- ## Install
6
-
7
- ```bash
8
- # Install globally
9
- npm install -g prituscode
10
-
11
- # Or run directly with npx (no install needed)
12
- npx prituscode
13
- ```
14
-
15
- ## Usage
16
-
17
- ### Interactive mode
18
- ```bash
19
- prituscode
20
- ```
21
-
22
- ### One-shot mode
23
- ```bash
24
- prituscode "build a responsive navbar with Tailwind"
25
- ```
26
-
27
- ### Custom endpoint
28
- ```bash
29
- prituscode --endpoint https://your-pritus-deployment.com
30
- ```
31
-
32
- ### Select model tier
33
- ```bash
34
- prituscode --model thinking
35
- ```
36
-
37
- ## Commands (interactive mode)
38
-
39
- | Command | Description |
40
- |---------|-------------|
41
- | `/help` | Show available commands |
42
- | `/clear` | Clear conversation history |
43
- | `/model` | Switch model tier (`flash` / `pro` / `thinking`) |
44
- | `/status` | Show connection status |
45
- | `/exit` | Exit the CLI |
46
-
47
- ## Environment Variables
48
-
49
- | Variable | Default | Description |
50
- |----------|---------|-------------|
51
- | `PRITUS_API_URL` | `https://pritusai.netlify.app` | API endpoint |
52
- | `PRITUS_MODEL` | `pro` | Default model tier |
53
-
54
- ## Run without installing
55
-
56
- ```bash
57
- # Clone and run directly
58
- git clone https://github.com/sarthaksahoo/pritus-ai
59
- cd pritus-ai/cli
60
- node prituscode.js
61
- ```
62
-
63
- ## Publish to npm
64
-
65
- ```bash
66
- npm login # log in to your npm account
67
- cd cli
68
- npm publish
69
- ```
70
-
71
- After publishing, anyone can run `npx prituscode` globally.
72
-
73
- ## Requirements
74
-
75
- - Node.js 18+ (for built-in `fetch`)
76
-
77
- ## License
78
-
79
- MIT © Sarthak Sahoo