prituscode 2.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/prituscode.js +223 -30
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prituscode",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
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,8 @@ 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 AUTH_DIR = path.join(os.homedir(), '.prituscode')
42
+ const AUTH_FILE = path.join(AUTH_DIR, 'auth.json')
39
43
 
40
44
  const MODELS = {
41
45
  1: { id: 'flash', label: 'Flash', desc: 'Fast & lightweight' },
@@ -53,6 +57,53 @@ CREATE_FILE: <filename>
53
57
  \`\`\`
54
58
  `
55
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
+ }
72
+
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
+ }
84
+
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
+ }
95
+
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
+ }
105
+ }
106
+
56
107
  // --- ANSI & FORMATTING HELPERS ---
57
108
  function stripAnsi(str) {
58
109
  return str.replace(/\x1B\[[0-9;]{0,}[A-Za-z]/g, '')
@@ -206,24 +257,36 @@ function renderHeader() {
206
257
  const banner = getBigBanner()
207
258
 
208
259
  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)
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]))
213
268
  }
214
269
  console.log(brightMagenta(bold('PRITUS CODE v' + VERSION)))
215
270
  console.log('')
216
271
  }
217
272
 
218
- function renderWelcomeBox(cwd) {
273
+ function renderWelcomeBox(cwd, authSession) {
219
274
  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
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
+ ]
223
283
 
224
- let innerWidth = line1.length
225
- if (line2.length > innerWidth) innerWidth = line2.length
226
- if (line3.length > innerWidth) innerWidth = line3.length
284
+ let innerWidth = 0
285
+ for (let i = 0; i < lines.length; i++) {
286
+ if (lines[i].length > innerWidth) {
287
+ innerWidth = lines[i].length
288
+ }
289
+ }
227
290
  innerWidth += 4
228
291
 
229
292
  const topBorder = '┌' + '─'.repeat(innerWidth) + '┐'
@@ -231,14 +294,18 @@ function renderWelcomeBox(cwd) {
231
294
 
232
295
  console.log(cyan(topBorder))
233
296
 
234
- const pad1 = ' '.repeat(innerWidth - line1.length - 2)
235
- console.log(cyan('│ ') + bold(line1) + pad1 + cyan(' │'))
297
+ const pad1 = ' '.repeat(innerWidth - lines[0].length - 2)
298
+ console.log(cyan('│ ') + bold(lines[0]) + pad1 + cyan(' │'))
236
299
 
237
- const pad2 = ' '.repeat(innerWidth - line2.length - 2)
238
- console.log(cyan('│ ') + dim(line2) + pad2 + cyan(' │'))
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(' │'))
239
303
 
240
- const pad3 = ' '.repeat(innerWidth - line3.length - 2)
241
- console.log(cyan('│ ') + dim(line3) + pad3 + cyan(' │'))
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(' │'))
242
309
 
243
310
  console.log(cyan(bottomBorder))
244
311
  console.log('')
@@ -253,10 +320,11 @@ function renderTips() {
253
320
  console.log('')
254
321
  }
255
322
 
256
- function renderStatusBar(currentModelId, cwd) {
323
+ function renderStatusBar(currentModelId, cwd, authSession) {
257
324
  const displayCwd = formatPath(cwd)
258
325
  const selectedObj = getModelInfo(currentModelId)
259
- const barText = 'workspace (' + displayCwd + ') /model ' + selectedObj.id + ' ? for shortcuts'
326
+ const authStatus = authSession && authSession.email ? authSession.email : 'guest'
327
+ const barText = 'workspace (' + displayCwd + ') /model ' + selectedObj.id + ' user: ' + authStatus + ' ? for shortcuts'
260
328
  console.log(dim(barText))
261
329
  console.log('')
262
330
  }
@@ -271,6 +339,76 @@ function getModelInfo(keyOrId) {
271
339
  return MODELS[2]
272
340
  }
273
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')
397
+ }
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
+ })
410
+ }
411
+
274
412
  // --- MODEL SELECTOR ---
275
413
  function promptModelSelection(currentModelId, callback) {
276
414
  console.log('Select a model:')
@@ -392,7 +530,7 @@ function expandFileAttachments(text) {
392
530
  }
393
531
 
394
532
  // --- HTTP API CLIENT ---
395
- function sendChatRequest(apiUrl, modelId, userPrompt, messagesHistory, callback) {
533
+ function sendChatRequest(apiUrl, modelId, userPrompt, messagesHistory, authSession, callback) {
396
534
  const expandedPrompt = expandFileAttachments(userPrompt)
397
535
 
398
536
  let targetUrl = apiUrl
@@ -420,15 +558,21 @@ function sendChatRequest(apiUrl, modelId, userPrompt, messagesHistory, callback)
420
558
  messages: messagesHistory
421
559
  })
422
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
+
423
570
  const reqOpts = {
424
571
  hostname: parsedUrl.hostname,
425
572
  port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
426
573
  path: parsedUrl.pathname + parsedUrl.search,
427
574
  method: 'POST',
428
- headers: {
429
- 'Content-Type': 'application/json',
430
- 'Content-Length': Buffer.byteLength(payload)
431
- }
575
+ headers: reqHeaders
432
576
  }
433
577
 
434
578
  const req = httpModule.request(reqOpts, (res) => {
@@ -476,6 +620,9 @@ function handleSlashCommand(input, state, rl) {
476
620
  case '?':
477
621
  console.log('')
478
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')
479
626
  console.log(' /help, ? Show this help message')
480
627
  console.log(' /status Show current configuration')
481
628
  console.log(' /init Create PRITUS.md with project rules')
@@ -487,11 +634,44 @@ function handleSlashCommand(input, state, rl) {
487
634
  rl.prompt()
488
635
  return true
489
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
+
490
669
  case '/status':
491
670
  console.log('')
492
671
  console.log(bold('Current Setup:'))
493
672
  console.log(' Version: ' + VERSION)
494
673
  console.log(' Model: ' + getModelInfo(state.currentModel).label + ' (' + state.currentModel + ')')
674
+ console.log(' User: ' + (state.authSession ? state.authSession.email : 'Not logged in'))
495
675
  console.log(' API URL: ' + state.apiUrl)
496
676
  console.log(' CWD: ' + process.cwd())
497
677
  console.log('')
@@ -539,7 +719,7 @@ Describe your project and architectural guidelines here for Pritus Code.
539
719
  case '/clear':
540
720
  console.clear()
541
721
  renderHeader()
542
- renderStatusBar(state.currentModel, process.cwd())
722
+ renderStatusBar(state.currentModel, process.cwd(), state.authSession)
543
723
  rl.prompt()
544
724
  return true
545
725
 
@@ -585,7 +765,7 @@ function startRepl(state) {
585
765
 
586
766
  process.stdout.write(dim('Thinking... \r'))
587
767
 
588
- sendChatRequest(state.apiUrl, state.currentModel, input, messagesHistory, (err, responseText) => {
768
+ sendChatRequest(state.apiUrl, state.currentModel, input, messagesHistory, state.authSession, (err, responseText) => {
589
769
  // Clear line
590
770
  process.stdout.write(' \r')
591
771
 
@@ -623,7 +803,8 @@ function main() {
623
803
 
624
804
  const state = {
625
805
  apiUrl: DEFAULT_API_URL,
626
- currentModel: DEFAULT_MODEL
806
+ currentModel: DEFAULT_MODEL,
807
+ authSession: loadAuthSession()
627
808
  }
628
809
 
629
810
  // Parse direct flags
@@ -636,6 +817,7 @@ function main() {
636
817
  console.log('PRITUS CODE v' + VERSION + ' - Terminal AI Coding Agent')
637
818
  console.log('\nUsage:')
638
819
  console.log(' prituscode Start interactive terminal session')
820
+ console.log(' prituscode login Sign in with Google OAuth')
639
821
  console.log(' prituscode "build a server" Run single prompt')
640
822
  console.log(' prituscode -m ultra "query" Run single prompt with specified model')
641
823
  console.log(' prituscode -v, --version Show version')
@@ -643,6 +825,17 @@ function main() {
643
825
  process.exit(0)
644
826
  }
645
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
+
646
839
  let modelOverride = null
647
840
  const queryArgs = []
648
841
 
@@ -666,7 +859,7 @@ function main() {
666
859
  if (queryArgs.length > 0) {
667
860
  const singlePrompt = queryArgs.join(' ')
668
861
  process.stdout.write(dim('Processing request...\n'))
669
- sendChatRequest(state.apiUrl, state.currentModel, singlePrompt, [], (err, responseText) => {
862
+ sendChatRequest(state.apiUrl, state.currentModel, singlePrompt, [], state.authSession, (err, responseText) => {
670
863
  if (err) {
671
864
  console.error('\x1b[31mError: ' + err.message + '\x1b[39m')
672
865
  process.exit(1)
@@ -680,12 +873,12 @@ function main() {
680
873
 
681
874
  // Interactive mode startup
682
875
  renderHeader()
683
- renderWelcomeBox(process.cwd())
876
+ renderWelcomeBox(process.cwd(), state.authSession)
684
877
  renderTips()
685
878
 
686
879
  promptModelSelection(state.currentModel, (selectedModel) => {
687
880
  state.currentModel = selectedModel
688
- renderStatusBar(state.currentModel, process.cwd())
881
+ renderStatusBar(state.currentModel, process.cwd(), state.authSession)
689
882
  startRepl(state)
690
883
  })
691
884
  }