prituscode 2.0.0 → 2.0.2

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/package.json +1 -1
  2. package/prituscode.js +432 -177
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prituscode",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "PRITUS CODE - Production-ready terminal AI coding agent CLI",
5
5
  "main": "prituscode.js",
6
6
  "bin": {
package/prituscode.js CHANGED
@@ -6,6 +6,8 @@ const { URL } = require('url')
6
6
  const path = require('path')
7
7
  const os = require('os')
8
8
  const fs = require('fs')
9
+ const http = require('http')
10
+ const { exec } = require('child_process')
9
11
 
10
12
  // --- ENVIRONMENT & CONFIGURATION ---
11
13
  function loadEnv() {
@@ -36,6 +38,9 @@ loadEnv()
36
38
  const VERSION = '1.0.0'
37
39
  const DEFAULT_API_URL = process.env.PRITUS_API_URL || 'https://pritusai.netlify.app'
38
40
  const DEFAULT_MODEL = process.env.PRITUS_MODEL || 'pro'
41
+ const CONFIG_DIR = path.join(os.homedir(), '.prituscode')
42
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json')
43
+ const AUTH_FILE = path.join(CONFIG_DIR, 'auth.json')
39
44
 
40
45
  const MODELS = {
41
46
  1: { id: 'flash', label: 'Flash', desc: 'Fast & lightweight' },
@@ -44,6 +49,13 @@ const MODELS = {
44
49
  4: { id: 'ultra', label: 'Ultra Code', desc: 'Elite code generation' }
45
50
  }
46
51
 
52
+ const THEMES = {
53
+ 1: { id: 'dark', label: 'Dark', desc: 'Vibrant dark mode (Default)' },
54
+ 2: { id: 'light', label: 'Light', desc: 'Clean light mode' },
55
+ 3: { id: 'daltonized', label: 'Colorblind Friendly', desc: 'High contrast blue & yellow' },
56
+ 4: { id: 'monochrome', label: 'Monochrome', desc: 'Sleek black & white' }
57
+ }
58
+
47
59
  const AGENT_SYSTEM = `You are Pritus Code, an AI coding agent in a terminal. You CREATE and MODIFY real files.
48
60
  When asked to build something, respond in EXACT format:
49
61
  THINKING: <one sentence>
@@ -53,7 +65,76 @@ CREATE_FILE: <filename>
53
65
  \`\`\`
54
66
  `
55
67
 
56
- // --- ANSI & FORMATTING HELPERS ---
68
+ // --- CONFIG & AUTH STORAGE HELPERS ---
69
+ function loadConfig() {
70
+ try {
71
+ if (fs.existsSync(CONFIG_FILE)) {
72
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'))
73
+ }
74
+ } catch (e) {
75
+ // ignore
76
+ }
77
+ return { theme: 'dark', model: DEFAULT_MODEL }
78
+ }
79
+
80
+ function saveConfig(cfg) {
81
+ try {
82
+ if (!fs.existsSync(CONFIG_DIR)) {
83
+ fs.mkdirSync(CONFIG_DIR, { recursive: true })
84
+ }
85
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), 'utf8')
86
+ } catch (e) {
87
+ // ignore
88
+ }
89
+ }
90
+
91
+ function saveAuthSession(sessionData) {
92
+ try {
93
+ if (!fs.existsSync(CONFIG_DIR)) {
94
+ fs.mkdirSync(CONFIG_DIR, { recursive: true })
95
+ }
96
+ fs.writeFileSync(AUTH_FILE, JSON.stringify(sessionData, null, 2), 'utf8')
97
+ return true
98
+ } catch (err) {
99
+ return false
100
+ }
101
+ }
102
+
103
+ function loadAuthSession() {
104
+ try {
105
+ if (fs.existsSync(AUTH_FILE)) {
106
+ const data = fs.readFileSync(AUTH_FILE, 'utf8')
107
+ return JSON.parse(data)
108
+ }
109
+ } catch (err) {
110
+ // Ignore auth load errors
111
+ }
112
+ return null
113
+ }
114
+
115
+ function clearAuthSession() {
116
+ try {
117
+ if (fs.existsSync(AUTH_FILE)) {
118
+ fs.unlinkSync(AUTH_FILE)
119
+ }
120
+ return true
121
+ } catch (err) {
122
+ return false
123
+ }
124
+ }
125
+
126
+ function openBrowser(url) {
127
+ const platform = process.platform
128
+ if (platform === 'win32') {
129
+ exec('start "" "' + url + '"')
130
+ } else if (platform === 'darwin') {
131
+ exec('open "' + url + '"')
132
+ } else {
133
+ exec('xdg-open "' + url + '"')
134
+ }
135
+ }
136
+
137
+ // --- ANSI & THEME HELPERS ---
57
138
  function stripAnsi(str) {
58
139
  return str.replace(/\x1B\[[0-9;]{0,}[A-Za-z]/g, '')
59
140
  }
@@ -66,20 +147,25 @@ function dim(str) {
66
147
  return '\x1b[2m' + str + '\x1b[22m'
67
148
  }
68
149
 
69
- function cyan(str) {
70
- return '\x1b[36m' + str + '\x1b[39m'
71
- }
72
-
73
- function green(str) {
74
- return '\x1b[32m' + str + '\x1b[39m'
150
+ function colorPrimary(themeId, str) {
151
+ if (themeId === 'light') return '\x1b[34m' + str + '\x1b[39m'
152
+ if (themeId === 'daltonized') return '\x1b[34m' + str + '\x1b[39m'
153
+ if (themeId === 'monochrome') return '\x1b[37m' + str + '\x1b[39m'
154
+ return '\x1b[36m' + str + '\x1b[39m' // dark
75
155
  }
76
156
 
77
- function magenta(str) {
78
- return '\x1b[35m' + str + '\x1b[39m'
157
+ function colorSecondary(themeId, str) {
158
+ if (themeId === 'light') return '\x1b[35m' + str + '\x1b[39m'
159
+ if (themeId === 'daltonized') return '\x1b[33m' + str + '\x1b[39m'
160
+ if (themeId === 'monochrome') return '\x1b[90m' + str + '\x1b[39m'
161
+ return '\x1b[95m' + str + '\x1b[39m' // dark
79
162
  }
80
163
 
81
- function brightMagenta(str) {
82
- return '\x1b[95m' + str + '\x1b[39m'
164
+ function colorSuccess(themeId, str) {
165
+ if (themeId === 'light') return '\x1b[32m' + str + '\x1b[39m'
166
+ if (themeId === 'daltonized') return '\x1b[33m' + str + '\x1b[39m'
167
+ if (themeId === 'monochrome') return '\x1b[37m' + str + '\x1b[39m'
168
+ return '\x1b[32m' + str + '\x1b[39m' // dark
83
169
  }
84
170
 
85
171
  function ansi256(code, str) {
@@ -94,169 +180,75 @@ function formatPath(p) {
94
180
  return p
95
181
  }
96
182
 
97
- // --- LOGO & ASCII ART GENERATORS ---
98
- function getDiamondLogo() {
99
- const lines = [
100
- ansi256(39, '██'),
101
- ' ' + ansi256(75, '██'),
102
- ' ' + ansi256(99, '██'),
103
- ' ' + ansi256(141, '██'),
104
- ' ' + ansi256(177, '██'),
105
- ansi256(207, '██'),
106
- ansi256(207, '████████')
107
- ]
108
- return lines
109
- }
110
-
111
- const LETTERS = {
112
- P: [
113
- '██████╗ ',
114
- '██╔══██╗',
115
- '██████╔╝',
116
- '██╔═══╝ ',
117
- '██║ ',
118
- '╚═╝ '
119
- ],
120
- R: [
121
- '██████╗ ',
122
- '██╔══██╗',
123
- '██████╔╝',
124
- '██╔██╗ ',
125
- '██║╚██╗ ',
126
- '╚═╝ ╚═╝ '
127
- ],
128
- I: [
129
- '██╗',
130
- '██║',
131
- '██║',
132
- '██║',
133
- '██║',
134
- '╚═╝'
135
- ],
136
- T: [
137
- '████████╗',
138
- '╚══██╔══╝',
139
- ' ██║ ',
140
- ' ██║ ',
141
- ' ██║ ',
142
- ' ╚═╝ '
143
- ],
144
- U: [
145
- '██╗ ██╗',
146
- '██║ ██║',
147
- '██║ ██║',
148
- '██║ ██║',
149
- '╚██████╔╝',
150
- ' ╚═════╝ '
151
- ],
152
- S: [
153
- '███████╗',
154
- '██╔════╝',
155
- '███████╗',
156
- '╚════██║',
157
- '███████║',
158
- '╚══════╝'
159
- ],
160
- C: [
161
- '███████╗',
162
- '██╔════╝',
163
- '██║ ',
164
- '██║ ',
165
- '╚██████╗',
166
- ' ╚═════╝'
167
- ],
168
- O: [
169
- ' █████╗ ',
170
- '██╔══██╗',
171
- '██║ ██║',
172
- '██║ ██║',
173
- '╚██████╔╝',
174
- ' ╚═════╝ '
175
- ],
176
- D: [
177
- '██████╗ ',
178
- '██╔══██╗',
179
- '██║ ██║',
180
- '██║ ██║',
181
- '╚██████╗',
182
- ' ╚═════╝ '
183
- ],
184
- E: [
185
- '███████╗',
186
- '██╔════╝',
187
- '███████╗',
188
- '██╔════╝',
189
- '███████╝',
190
- ' '
191
- ]
192
- }
193
-
194
- function getBigBanner() {
195
- const rows = []
196
- for (let r = 0; r < 6; r++) {
197
- const pritusLine = LETTERS.P[r] + ' ' + LETTERS.R[r] + ' ' + LETTERS.I[r] + ' ' + LETTERS.T[r] + ' ' + LETTERS.U[r] + ' ' + LETTERS.S[r]
198
- const codeLine = LETTERS.C[r] + ' ' + LETTERS.O[r] + ' ' + LETTERS.D[r] + ' ' + LETTERS.E[r]
199
- rows.push(pritusLine + ' ' + codeLine)
200
- }
201
- return rows
202
- }
203
-
204
- function renderHeader() {
205
- const diamond = getDiamondLogo()
206
- const banner = getBigBanner()
207
-
183
+ // --- LOGO & HEADER RENDERER ---
184
+ function renderHeader(themeId) {
208
185
  console.log('')
209
- for (let i = 0; i < 7; i++) {
210
- const dia = diamond[i]
211
- const ban = i < 6 ? cyan(banner[i]) : ''
212
- console.log(dia + ' ' + ban)
213
- }
214
- console.log(brightMagenta(bold('PRITUS CODE v' + VERSION)))
186
+ // Sleek glowing diamond crystal icon + clean typography
187
+ const row1 = ansi256(39, ' ▲ ') + ' ' + bold(colorPrimary(themeId, 'P R I T U S C O D E'))
188
+ const row2 = ansi256(99, ' ► ') + ' ' + dim('Terminal AI Coding Agent ') + colorSecondary(themeId, 'v' + VERSION)
189
+ const row3 = ansi256(207, ' ▼ ') + ' ' + ansi256(207, '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
190
+
191
+ console.log(row1)
192
+ console.log(row2)
193
+ console.log(row3)
215
194
  console.log('')
216
195
  }
217
196
 
218
- function renderWelcomeBox(cwd) {
197
+ function renderWelcomeBox(cwd, authSession, themeId) {
219
198
  const displayCwd = formatPath(cwd)
220
- const line1 = 'Welcome to Pritus Code!'
221
- const line2 = '/help for help, /status for your current setup'
222
- const line3 = 'cwd: ' + displayCwd
199
+ const userText = authSession && authSession.email ? 'Logged in as: ' + authSession.email : 'Not logged in'
223
200
 
224
- let innerWidth = line1.length
225
- if (line2.length > innerWidth) innerWidth = line2.length
226
- if (line3.length > innerWidth) innerWidth = line3.length
201
+ const lines = [
202
+ 'Welcome to Pritus Code!',
203
+ userText,
204
+ '/help for help, /status for config, /theme to change colors',
205
+ 'cwd: ' + displayCwd
206
+ ]
207
+
208
+ let innerWidth = 0
209
+ for (let i = 0; i < lines.length; i++) {
210
+ if (lines[i].length > innerWidth) {
211
+ innerWidth = lines[i].length
212
+ }
213
+ }
227
214
  innerWidth += 4
228
215
 
229
216
  const topBorder = '┌' + '─'.repeat(innerWidth) + '┐'
230
217
  const bottomBorder = '└' + '─'.repeat(innerWidth) + '┘'
231
218
 
232
- console.log(cyan(topBorder))
219
+ console.log(colorPrimary(themeId, topBorder))
233
220
 
234
- const pad1 = ' '.repeat(innerWidth - line1.length - 2)
235
- console.log(cyan('│ ') + bold(line1) + pad1 + cyan(' │'))
221
+ const pad1 = ' '.repeat(innerWidth - lines[0].length - 2)
222
+ console.log(colorPrimary(themeId, '│ ') + bold(lines[0]) + pad1 + colorPrimary(themeId, ' │'))
236
223
 
237
- const pad2 = ' '.repeat(innerWidth - line2.length - 2)
238
- console.log(cyan('│ ') + dim(line2) + pad2 + cyan(' │'))
224
+ const pad2 = ' '.repeat(innerWidth - lines[1].length - 2)
225
+ const formattedUserLine = authSession && authSession.email ? colorSuccess(themeId, lines[1]) : dim(lines[1])
226
+ console.log(colorPrimary(themeId, '│ ') + formattedUserLine + pad2 + colorPrimary(themeId, ' │'))
239
227
 
240
- const pad3 = ' '.repeat(innerWidth - line3.length - 2)
241
- console.log(cyan('│ ') + dim(line3) + pad3 + cyan(' │'))
228
+ const pad3 = ' '.repeat(innerWidth - lines[2].length - 2)
229
+ console.log(colorPrimary(themeId, '│ ') + dim(lines[2]) + pad3 + colorPrimary(themeId, ' │'))
242
230
 
243
- console.log(cyan(bottomBorder))
231
+ const pad4 = ' '.repeat(innerWidth - lines[3].length - 2)
232
+ console.log(colorPrimary(themeId, '│ ') + dim(lines[3]) + pad4 + colorPrimary(themeId, ' │'))
233
+
234
+ console.log(colorPrimary(themeId, bottomBorder))
244
235
  console.log('')
245
236
  }
246
237
 
247
- function renderTips() {
238
+ function renderTips(themeId) {
248
239
  console.log(bold('Tips for getting started:'))
249
240
  console.log('1. Ask Pritus to build something — it will create actual files in your project')
250
241
  console.log('2. Use @filename to include a file contents in your message')
251
242
  console.log('3. Be as specific as you would with another engineer for the best results')
252
- console.log(green('✓') + ' Run /init to create a PRITUS.md with project instructions')
243
+ console.log(colorSuccess(themeId, '✓') + ' Run /init to create a PRITUS.md with project instructions')
253
244
  console.log('')
254
245
  }
255
246
 
256
- function renderStatusBar(currentModelId, cwd) {
247
+ function renderStatusBar(currentModelId, cwd, authSession, themeId) {
257
248
  const displayCwd = formatPath(cwd)
258
249
  const selectedObj = getModelInfo(currentModelId)
259
- const barText = 'workspace (' + displayCwd + ') /model ' + selectedObj.id + ' ? for shortcuts'
250
+ const authStatus = authSession && authSession.email ? authSession.email : 'guest'
251
+ const barText = 'workspace (' + displayCwd + ') /model ' + selectedObj.id + ' theme: ' + themeId + ' user: ' + authStatus + ' ? for help'
260
252
  console.log(dim(barText))
261
253
  console.log('')
262
254
  }
@@ -271,8 +263,184 @@ function getModelInfo(keyOrId) {
271
263
  return MODELS[2]
272
264
  }
273
265
 
266
+ function getThemeInfo(keyOrId) {
267
+ const str = String(keyOrId).toLowerCase()
268
+ for (const k in THEMES) {
269
+ if (String(k) === str || THEMES[k].id === str) {
270
+ return THEMES[k]
271
+ }
272
+ }
273
+ return THEMES[1]
274
+ }
275
+
276
+ // --- GOOGLE OAUTH LOGIN SERVER ---
277
+ function startGoogleOAuthFlow(apiUrl, onComplete) {
278
+ const port = 8585
279
+ const redirectUri = 'http://localhost:' + port + '/callback'
280
+ const loginUrl = apiUrl + '/api/auth/google?redirect_uri=' + encodeURIComponent(redirectUri)
281
+
282
+ const server = http.createServer((req, res) => {
283
+ const reqUrl = new URL(req.url, 'http://localhost:' + port)
284
+ if (reqUrl.pathname === '/callback') {
285
+ const token = reqUrl.searchParams.get('token') || reqUrl.searchParams.get('access_token') || 'demo_token'
286
+ const email = reqUrl.searchParams.get('email') || 'google_user@gmail.com'
287
+ const name = reqUrl.searchParams.get('name') || 'Google User'
288
+
289
+ const session = {
290
+ token: token,
291
+ email: email,
292
+ name: name,
293
+ loggedInAt: Date.now()
294
+ }
295
+
296
+ saveAuthSession(session)
297
+
298
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
299
+ res.end(`
300
+ <!DOCTYPE html>
301
+ <html>
302
+ <head>
303
+ <title>PRITUS CODE - Google Login Successful</title>
304
+ <style>
305
+ body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
306
+ .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; }
307
+ h1 { color: #38bdf8; font-size: 1.5rem; margin-bottom: 0.5rem; }
308
+ p { color: #94a3b8; font-size: 0.95rem; }
309
+ .check { font-size: 3rem; color: #4ade80; margin-bottom: 1rem; }
310
+ </style>
311
+ </head>
312
+ <body>
313
+ <div class="card">
314
+ <div class="check">✓</div>
315
+ <h1>Google Login Successful!</h1>
316
+ <p>You have signed in as <strong>${email}</strong>.</p>
317
+ <p>You can close this tab and return to your terminal.</p>
318
+ </div>
319
+ </body>
320
+ </html>
321
+ `)
322
+
323
+ setTimeout(() => {
324
+ server.close()
325
+ }, 500)
326
+
327
+ onComplete(null, session)
328
+ } else {
329
+ res.writeHead(404, { 'Content-Type': 'text/plain' })
330
+ res.end('Not Found')
331
+ }
332
+ })
333
+
334
+ server.listen(port, () => {
335
+ console.log(dim('Opening browser for Google Login...'))
336
+ console.log(dim('URL: ' + loginUrl))
337
+ openBrowser(loginUrl)
338
+ })
339
+
340
+ server.on('error', (err) => {
341
+ console.log('\x1b[31mCould not start local auth server on port ' + port + ': ' + err.message + '\x1b[39m')
342
+ onComplete(err, null)
343
+ })
344
+ }
345
+
346
+ function enforceCompulsoryLogin(state, callback) {
347
+ if (state.authSession && state.authSession.email) {
348
+ return callback(state.authSession)
349
+ }
350
+
351
+ console.log(bold('\x1b[33m[Authentication Required]\x1b[39m'))
352
+ console.log('Pritus Code requires a free Google sign-in to continue.')
353
+ process.stdout.write('Press Enter to sign in with Google in your browser... ')
354
+
355
+ if (process.stdin.isTTY) {
356
+ process.stdin.setRawMode(true)
357
+ process.stdin.resume()
358
+ process.stdin.once('data', () => {
359
+ process.stdin.setRawMode(false)
360
+ process.stdin.pause()
361
+ console.log('')
362
+ startGoogleOAuthFlow(state.apiUrl, (err, session) => {
363
+ if (err || !session) {
364
+ console.log('\x1b[31mAuthentication failed. Pritus Code requires Google Login to proceed.\x1b[39m')
365
+ process.exit(1)
366
+ }
367
+ state.authSession = session
368
+ console.log(colorSuccess(state.currentTheme, '✓') + ' Signed in as ' + session.email + '\n')
369
+ callback(session)
370
+ })
371
+ })
372
+ } else {
373
+ const rlTemp = readline.createInterface({
374
+ input: process.stdin,
375
+ output: process.stdout
376
+ })
377
+ rlTemp.question('', () => {
378
+ rlTemp.close()
379
+ startGoogleOAuthFlow(state.apiUrl, (err, session) => {
380
+ if (err || !session) {
381
+ console.log('\x1b[31mAuthentication failed. Pritus Code requires Google Login to proceed.\x1b[39m')
382
+ process.exit(1)
383
+ }
384
+ state.authSession = session
385
+ console.log(colorSuccess(state.currentTheme, '✓') + ' Signed in as ' + session.email + '\n')
386
+ callback(session)
387
+ })
388
+ })
389
+ }
390
+ }
391
+
392
+ // --- CLAUDE CODE STYLE THEME SELECTOR ---
393
+ function promptThemeSelection(currentThemeId, callback) {
394
+ console.log('Select a color theme:')
395
+ for (const k in THEMES) {
396
+ const t = THEMES[k]
397
+ const isSelected = t.id === currentThemeId
398
+ const pointer = isSelected ? '▸ ' : ' '
399
+ const num = k + ' '
400
+ console.log(pointer + num + bold(t.label) + ' — ' + t.desc)
401
+ }
402
+
403
+ const defaultThemeObj = getThemeInfo(currentThemeId)
404
+ process.stdout.write('Press 1-4, or Enter for ' + defaultThemeObj.label + ': ')
405
+
406
+ if (process.stdin.isTTY) {
407
+ process.stdin.setRawMode(true)
408
+ process.stdin.resume()
409
+ process.stdin.once('data', (data) => {
410
+ process.stdin.setRawMode(false)
411
+ process.stdin.pause()
412
+ const char = data.toString('utf8').trim()
413
+ console.log(char)
414
+
415
+ let choiceObj = defaultThemeObj
416
+ if (THEMES[char]) {
417
+ choiceObj = THEMES[char]
418
+ }
419
+ console.log(colorSuccess(choiceObj.id, '✓') + ' Applied ' + choiceObj.label + ' theme')
420
+ console.log('')
421
+ callback(choiceObj.id)
422
+ })
423
+ } else {
424
+ const rlTemp = readline.createInterface({
425
+ input: process.stdin,
426
+ output: process.stdout
427
+ })
428
+ rlTemp.question('', (ans) => {
429
+ rlTemp.close()
430
+ const trimmed = ans.trim()
431
+ let choiceObj = defaultThemeObj
432
+ if (THEMES[trimmed]) {
433
+ choiceObj = THEMES[trimmed]
434
+ }
435
+ console.log(colorSuccess(choiceObj.id, '✓') + ' Applied ' + choiceObj.label + ' theme')
436
+ console.log('')
437
+ callback(choiceObj.id)
438
+ })
439
+ }
440
+ }
441
+
274
442
  // --- MODEL SELECTOR ---
275
- function promptModelSelection(currentModelId, callback) {
443
+ function promptModelSelection(currentModelId, themeId, callback) {
276
444
  console.log('Select a model:')
277
445
  for (const k in MODELS) {
278
446
  const m = MODELS[k]
@@ -298,7 +466,7 @@ function promptModelSelection(currentModelId, callback) {
298
466
  if (MODELS[char]) {
299
467
  choiceObj = MODELS[char]
300
468
  }
301
- console.log(green('✓') + ' Switched to ' + choiceObj.label)
469
+ console.log(colorSuccess(themeId, '✓') + ' Switched to ' + choiceObj.label)
302
470
  console.log('')
303
471
  callback(choiceObj.id)
304
472
  })
@@ -314,7 +482,7 @@ function promptModelSelection(currentModelId, callback) {
314
482
  if (MODELS[trimmed]) {
315
483
  choiceObj = MODELS[trimmed]
316
484
  }
317
- console.log(green('✓') + ' Switched to ' + choiceObj.label)
485
+ console.log(colorSuccess(themeId, '✓') + ' Switched to ' + choiceObj.label)
318
486
  console.log('')
319
487
  callback(choiceObj.id)
320
488
  })
@@ -322,7 +490,7 @@ function promptModelSelection(currentModelId, callback) {
322
490
  }
323
491
 
324
492
  // --- FILE PARSER & AGENT OPERATIONS ---
325
- function processFileOperations(aiResponse) {
493
+ function processFileOperations(aiResponse, themeId) {
326
494
  let modifiedAny = false
327
495
  const createPattern = /CREATE_FILE:\s{0,}([^\n]+)\n```[a-z]{0,}\n([\s\S]+?)```/gi
328
496
  const modifyPattern = /MODIFY_FILE:\s{0,}([^\n]+)\n```[a-z]{0,}\n([\s\S]+?)```/gi
@@ -336,7 +504,7 @@ function processFileOperations(aiResponse) {
336
504
  fs.mkdirSync(path.dirname(targetPath), { recursive: true })
337
505
  fs.writeFileSync(targetPath, content, 'utf8')
338
506
  const bytes = Buffer.byteLength(content, 'utf8')
339
- console.log(green('✓ Created ') + fileName + ' (' + bytes + ' bytes)')
507
+ console.log(colorSuccess(themeId, '✓ Created ') + fileName + ' (' + bytes + ' bytes)')
340
508
  modifiedAny = true
341
509
  } catch (err) {
342
510
  console.log('\x1b[31mError creating file ' + fileName + ': ' + err.message + '\x1b[39m')
@@ -351,7 +519,7 @@ function processFileOperations(aiResponse) {
351
519
  fs.mkdirSync(path.dirname(targetPath), { recursive: true })
352
520
  fs.writeFileSync(targetPath, content, 'utf8')
353
521
  const bytes = Buffer.byteLength(content, 'utf8')
354
- console.log(green('✓ Modified ') + fileName + ' (' + bytes + ' bytes)')
522
+ console.log(colorSuccess(themeId, '✓ Modified ') + fileName + ' (' + bytes + ' bytes)')
355
523
  modifiedAny = true
356
524
  } catch (err) {
357
525
  console.log('\x1b[31mError modifying file ' + fileName + ': ' + err.message + '\x1b[39m')
@@ -392,7 +560,7 @@ function expandFileAttachments(text) {
392
560
  }
393
561
 
394
562
  // --- HTTP API CLIENT ---
395
- function sendChatRequest(apiUrl, modelId, userPrompt, messagesHistory, callback) {
563
+ function sendChatRequest(apiUrl, modelId, userPrompt, messagesHistory, authSession, callback) {
396
564
  const expandedPrompt = expandFileAttachments(userPrompt)
397
565
 
398
566
  let targetUrl = apiUrl
@@ -412,23 +580,33 @@ function sendChatRequest(apiUrl, modelId, userPrompt, messagesHistory, callback)
412
580
  return callback(new Error('Invalid URL'))
413
581
  }
414
582
 
583
+ const allMessages = (messagesHistory || []).concat([
584
+ { role: 'user', content: expandedPrompt }
585
+ ])
586
+
415
587
  const httpModule = parsedUrl.protocol === 'https:' ? require('https') : require('http')
416
588
  const payload = JSON.stringify({
417
589
  prompt: expandedPrompt,
418
590
  model: modelId,
419
591
  system: AGENT_SYSTEM,
420
- messages: messagesHistory
592
+ messages: allMessages
421
593
  })
422
594
 
595
+ const reqHeaders = {
596
+ 'Content-Type': 'application/json',
597
+ 'Content-Length': Buffer.byteLength(payload)
598
+ }
599
+
600
+ if (authSession && authSession.token) {
601
+ reqHeaders['Authorization'] = 'Bearer ' + authSession.token
602
+ }
603
+
423
604
  const reqOpts = {
424
605
  hostname: parsedUrl.hostname,
425
606
  port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
426
607
  path: parsedUrl.pathname + parsedUrl.search,
427
608
  method: 'POST',
428
- headers: {
429
- 'Content-Type': 'application/json',
430
- 'Content-Length': Buffer.byteLength(payload)
431
- }
609
+ headers: reqHeaders
432
610
  }
433
611
 
434
612
  const req = httpModule.request(reqOpts, (res) => {
@@ -476,6 +654,10 @@ function handleSlashCommand(input, state, rl) {
476
654
  case '?':
477
655
  console.log('')
478
656
  console.log(bold('PRITUS CODE Commands & Shortcuts:'))
657
+ console.log(' /theme Change color theme (Dark, Light, Colorblind, Monochrome)')
658
+ console.log(' /login Sign in with Google authentication')
659
+ console.log(' /logout Sign out of current account')
660
+ console.log(' /whoami Display active user profile')
479
661
  console.log(' /help, ? Show this help message')
480
662
  console.log(' /status Show current configuration')
481
663
  console.log(' /init Create PRITUS.md with project rules')
@@ -487,11 +669,62 @@ function handleSlashCommand(input, state, rl) {
487
669
  rl.prompt()
488
670
  return true
489
671
 
672
+ case '/theme':
673
+ if (arg) {
674
+ const info = getThemeInfo(arg)
675
+ state.currentTheme = info.id
676
+ saveConfig({ theme: state.currentTheme, model: state.currentModel })
677
+ console.log(colorSuccess(state.currentTheme, '✓') + ' Applied ' + info.label + ' theme')
678
+ console.log('')
679
+ rl.prompt()
680
+ } else {
681
+ promptThemeSelection(state.currentTheme, (newTheme) => {
682
+ state.currentTheme = newTheme
683
+ saveConfig({ theme: state.currentTheme, model: state.currentModel })
684
+ rl.prompt()
685
+ })
686
+ }
687
+ return true
688
+
689
+ case '/login':
690
+ startGoogleOAuthFlow(state.apiUrl, (err, session) => {
691
+ if (!err && session) {
692
+ state.authSession = session
693
+ console.log(colorSuccess(state.currentTheme, '✓ Logged in as ') + session.email + ' (' + session.name + ')')
694
+ }
695
+ console.log('')
696
+ rl.prompt()
697
+ })
698
+ return true
699
+
700
+ case '/logout':
701
+ clearAuthSession()
702
+ state.authSession = null
703
+ console.log(colorSuccess(state.currentTheme, '✓ Successfully logged out.'))
704
+ console.log('')
705
+ rl.prompt()
706
+ return true
707
+
708
+ case '/whoami':
709
+ console.log('')
710
+ if (state.authSession && state.authSession.email) {
711
+ console.log(bold('Authenticated User:'))
712
+ console.log(' Email: ' + state.authSession.email)
713
+ console.log(' Name: ' + (state.authSession.name || 'N/A'))
714
+ } else {
715
+ console.log(dim('Not logged in. Type /login to authenticate with Google.'))
716
+ }
717
+ console.log('')
718
+ rl.prompt()
719
+ return true
720
+
490
721
  case '/status':
491
722
  console.log('')
492
723
  console.log(bold('Current Setup:'))
493
724
  console.log(' Version: ' + VERSION)
725
+ console.log(' Theme: ' + getThemeInfo(state.currentTheme).label + ' (' + state.currentTheme + ')')
494
726
  console.log(' Model: ' + getModelInfo(state.currentModel).label + ' (' + state.currentModel + ')')
727
+ console.log(' User: ' + (state.authSession ? state.authSession.email : 'Not logged in'))
495
728
  console.log(' API URL: ' + state.apiUrl)
496
729
  console.log(' CWD: ' + process.cwd())
497
730
  console.log('')
@@ -512,7 +745,7 @@ Describe your project and architectural guidelines here for Pritus Code.
512
745
  `
513
746
  try {
514
747
  fs.writeFileSync(docPath, docContent, 'utf8')
515
- console.log(green('✓ Created PRITUS.md with project instructions'))
748
+ console.log(colorSuccess(state.currentTheme, '✓ Created PRITUS.md with project instructions'))
516
749
  } catch (err) {
517
750
  console.log('\x1b[31mError creating PRITUS.md: ' + err.message + '\x1b[39m')
518
751
  }
@@ -525,12 +758,14 @@ Describe your project and architectural guidelines here for Pritus Code.
525
758
  if (arg) {
526
759
  const info = getModelInfo(arg)
527
760
  state.currentModel = info.id
528
- console.log(green('✓ Switched to ') + info.label)
761
+ saveConfig({ theme: state.currentTheme, model: state.currentModel })
762
+ console.log(colorSuccess(state.currentTheme, '✓ Switched to ') + info.label)
529
763
  console.log('')
530
764
  rl.prompt()
531
765
  } else {
532
- promptModelSelection(state.currentModel, (newModel) => {
766
+ promptModelSelection(state.currentModel, state.currentTheme, (newModel) => {
533
767
  state.currentModel = newModel
768
+ saveConfig({ theme: state.currentTheme, model: state.currentModel })
534
769
  rl.prompt()
535
770
  })
536
771
  }
@@ -538,8 +773,8 @@ Describe your project and architectural guidelines here for Pritus Code.
538
773
 
539
774
  case '/clear':
540
775
  console.clear()
541
- renderHeader()
542
- renderStatusBar(state.currentModel, process.cwd())
776
+ renderHeader(state.currentTheme)
777
+ renderStatusBar(state.currentModel, process.cwd(), state.authSession, state.currentTheme)
543
778
  rl.prompt()
544
779
  return true
545
780
 
@@ -585,7 +820,7 @@ function startRepl(state) {
585
820
 
586
821
  process.stdout.write(dim('Thinking... \r'))
587
822
 
588
- sendChatRequest(state.apiUrl, state.currentModel, input, messagesHistory, (err, responseText) => {
823
+ sendChatRequest(state.apiUrl, state.currentModel, input, messagesHistory, state.authSession, (err, responseText) => {
589
824
  // Clear line
590
825
  process.stdout.write(' \r')
591
826
 
@@ -599,7 +834,7 @@ function startRepl(state) {
599
834
  console.log(responseText)
600
835
  console.log('')
601
836
 
602
- const opsExecuted = processFileOperations(responseText)
837
+ const opsExecuted = processFileOperations(responseText, state.currentTheme)
603
838
  if (opsExecuted) {
604
839
  console.log('')
605
840
  }
@@ -620,10 +855,13 @@ function startRepl(state) {
620
855
  // --- ENTRY POINT ---
621
856
  function main() {
622
857
  const args = process.argv.slice(2)
858
+ const userConfig = loadConfig()
623
859
 
624
860
  const state = {
625
861
  apiUrl: DEFAULT_API_URL,
626
- currentModel: DEFAULT_MODEL
862
+ currentModel: userConfig.model || DEFAULT_MODEL,
863
+ currentTheme: userConfig.theme || 'dark',
864
+ authSession: loadAuthSession()
627
865
  }
628
866
 
629
867
  // Parse direct flags
@@ -636,6 +874,7 @@ function main() {
636
874
  console.log('PRITUS CODE v' + VERSION + ' - Terminal AI Coding Agent')
637
875
  console.log('\nUsage:')
638
876
  console.log(' prituscode Start interactive terminal session')
877
+ console.log(' prituscode login Sign in with Google OAuth')
639
878
  console.log(' prituscode "build a server" Run single prompt')
640
879
  console.log(' prituscode -m ultra "query" Run single prompt with specified model')
641
880
  console.log(' prituscode -v, --version Show version')
@@ -643,6 +882,17 @@ function main() {
643
882
  process.exit(0)
644
883
  }
645
884
 
885
+ if (args[0] === 'login') {
886
+ startGoogleOAuthFlow(state.apiUrl, (err, session) => {
887
+ if (err) {
888
+ process.exit(1)
889
+ }
890
+ console.log(colorSuccess(state.currentTheme, '✓ Logged in as ') + session.email + ' (' + session.name + ')')
891
+ process.exit(0)
892
+ })
893
+ return
894
+ }
895
+
646
896
  let modelOverride = null
647
897
  const queryArgs = []
648
898
 
@@ -666,27 +916,32 @@ function main() {
666
916
  if (queryArgs.length > 0) {
667
917
  const singlePrompt = queryArgs.join(' ')
668
918
  process.stdout.write(dim('Processing request...\n'))
669
- sendChatRequest(state.apiUrl, state.currentModel, singlePrompt, [], (err, responseText) => {
919
+ sendChatRequest(state.apiUrl, state.currentModel, singlePrompt, [], state.authSession, (err, responseText) => {
670
920
  if (err) {
671
921
  console.error('\x1b[31mError: ' + err.message + '\x1b[39m')
672
922
  process.exit(1)
673
923
  }
674
924
  console.log(responseText)
675
- processFileOperations(responseText)
925
+ processFileOperations(responseText, state.currentTheme)
676
926
  process.exit(0)
677
927
  })
678
928
  return
679
929
  }
680
930
 
681
931
  // Interactive mode startup
682
- renderHeader()
683
- renderWelcomeBox(process.cwd())
684
- renderTips()
685
-
686
- promptModelSelection(state.currentModel, (selectedModel) => {
687
- state.currentModel = selectedModel
688
- renderStatusBar(state.currentModel, process.cwd())
689
- startRepl(state)
932
+ renderHeader(state.currentTheme)
933
+
934
+ // Compulsory Login Enforcement
935
+ enforceCompulsoryLogin(state, () => {
936
+ renderWelcomeBox(process.cwd(), state.authSession, state.currentTheme)
937
+ renderTips(state.currentTheme)
938
+
939
+ promptModelSelection(state.currentModel, state.currentTheme, (selectedModel) => {
940
+ state.currentModel = selectedModel
941
+ saveConfig({ theme: state.currentTheme, model: state.currentModel })
942
+ renderStatusBar(state.currentModel, process.cwd(), state.authSession, state.currentTheme)
943
+ startRepl(state)
944
+ })
690
945
  })
691
946
  }
692
947