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.
- package/package.json +1 -1
- package/prituscode.js +432 -177
package/package.json
CHANGED
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
|
-
// ---
|
|
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
|
|
70
|
-
return '\x1b[
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
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
|
|
82
|
-
return '\x1b[
|
|
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 &
|
|
98
|
-
function
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
console.log(
|
|
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
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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(
|
|
219
|
+
console.log(colorPrimary(themeId, topBorder))
|
|
233
220
|
|
|
234
|
-
const pad1 = ' '.repeat(innerWidth -
|
|
235
|
-
console.log(
|
|
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 -
|
|
238
|
-
|
|
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 -
|
|
241
|
-
console.log(
|
|
228
|
+
const pad3 = ' '.repeat(innerWidth - lines[2].length - 2)
|
|
229
|
+
console.log(colorPrimary(themeId, '│ ') + dim(lines[2]) + pad3 + colorPrimary(themeId, ' │'))
|
|
242
230
|
|
|
243
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
state.
|
|
688
|
-
|
|
689
|
-
|
|
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
|
|