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 +2 -0
- package/package.json +16 -10
- package/prituscode.js +830 -303
- package/README.md +0 -79
package/.env.example
ADDED
package/package.json
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prituscode",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
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": "
|
|
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
|
-
|
|
16
|
-
"license": "ISC"
|
|
17
|
-
}
|
|
22
|
+
}
|
|
23
|
+
}
|
package/prituscode.js
CHANGED
|
@@ -1,359 +1,886 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
91
|
-
|
|
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
|
|
108
|
-
|
|
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
|
-
|
|
114
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
// ---
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
171
|
-
|
|
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
|
-
|
|
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
|
-
// ---
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
211
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
const finalPrompt = processAtFiles(prompt);
|
|
223
|
-
messages.push({ role: 'user', content: finalPrompt });
|
|
529
|
+
return expanded
|
|
530
|
+
}
|
|
224
531
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
254
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
282
|
-
|
|
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
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
//
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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()
|
|
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
|