prituscode 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example ADDED
@@ -0,0 +1,2 @@
1
+ PRITUS_API_URL=https://pritusai.netlify.app
2
+ PRITUS_MODEL=pro
package/package.json CHANGED
@@ -1,17 +1,23 @@
1
1
  {
2
2
  "name": "prituscode",
3
- "version": "1.0.0",
4
- "description": "Terminal-based AI coding agent CLI",
3
+ "version": "2.0.0",
4
+ "description": "PRITUS CODE - Production-ready terminal AI coding agent CLI",
5
5
  "main": "prituscode.js",
6
6
  "bin": {
7
- "prituscode": "./prituscode.js"
8
- },
9
- "scripts": {
10
- "start": "node ./prituscode.js"
7
+ "prituscode": "prituscode.js"
11
8
  },
9
+ "keywords": [
10
+ "pritus",
11
+ "prituscode",
12
+ "ai",
13
+ "agent",
14
+ "cli",
15
+ "coding",
16
+ "terminal"
17
+ ],
18
+ "author": "Pritus AI",
19
+ "license": "MIT",
12
20
  "engines": {
13
21
  "node": ">=18.0.0"
14
- },
15
- "author": "",
16
- "license": "ISC"
17
- }
22
+ }
23
+ }
package/prituscode.js CHANGED
@@ -1,359 +1,693 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require('fs');
4
- const path = require('path');
5
- const os = require('os');
6
- const readline = require('readline');
7
- const { execSync } = require('child_process');
8
-
9
- // --- ANSI COLORS & STYLES ---
10
- const c = {
11
- rst: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
12
- black: '\x1b[30m', red: '\x1b[31m', green: '\x1b[32m',
13
- yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m',
14
- cyan: '\x1b[36m', white: '\x1b[37m',
15
- bgMagenta: '\x1b[45m', bgBlue: '\x1b[44m'
16
- };
17
-
18
- // --- CONFIG & STATE ---
19
- const args = process.argv.slice(2);
20
- let config = {
21
- tier: 'pro',
22
- endpoint: 'https://pritusai.vercel.app/api/pritus/chat',
23
- oneShotPrompt: ''
24
- };
25
- const messages = [];
26
-
27
- // Parse flags
28
- for (let i = 0; i < args.length; i++) {
29
- if (args[i] === '--endpoint' && args[i + 1]) {
30
- config.endpoint = args[++i];
31
- } else if (args[i] === '--model' && args[i + 1]) {
32
- config.tier = args[++i];
33
- } else if (!args[i].startsWith('--')) {
34
- config.oneShotPrompt = args.slice(i).join(' ');
35
- break;
3
+ // --- IMPORTS ---
4
+ const readline = require('readline')
5
+ const { URL } = require('url')
6
+ const path = require('path')
7
+ const os = require('os')
8
+ const fs = require('fs')
9
+
10
+ // --- ENVIRONMENT & CONFIGURATION ---
11
+ function loadEnv() {
12
+ const envPath = path.join(process.cwd(), '.env')
13
+ if (!fs.existsSync(envPath)) return
14
+ try {
15
+ const content = fs.readFileSync(envPath, 'utf8')
16
+ const lines = content.split('\n')
17
+ for (let i = 0; i < lines.length; i++) {
18
+ const line = lines[i].trim()
19
+ if (!line || line.startsWith('#')) continue
20
+ const eqIdx = line.indexOf('=')
21
+ if (eqIdx > 0) {
22
+ const key = line.slice(0, eqIdx).trim()
23
+ const val = line.slice(eqIdx + 1).trim()
24
+ if (!process.env[key]) {
25
+ process.env[key] = val
26
+ }
27
+ }
36
28
  }
29
+ } catch (err) {
30
+ // Ignore env load errors silently
31
+ }
37
32
  }
38
33
 
39
- // --- SYSTEM PROMPT ---
40
- const AGENT_SYSTEM = `
41
- You are PRITUS CODE, an elite terminal-based AI coding agent.
42
- When generating or modifying code, you must ACT as an agent. Use the exact format below:
34
+ loadEnv()
43
35
 
44
- THINKING: <one sentence reasoning about what you will do>
45
- CREATE_FILE: <filepath>
46
- <entire file content here>
47
- DONE: <short summary of what was done>
36
+ const VERSION = '1.0.0'
37
+ const DEFAULT_API_URL = process.env.PRITUS_API_URL || 'https://pritusai.netlify.app'
38
+ const DEFAULT_MODEL = process.env.PRITUS_MODEL || 'pro'
48
39
 
49
- If the user is just asking a question and no files need to be written, simply reply conversationally without those tags. Do not reveal your underlying model name under any circumstance.
50
- `;
51
- messages.push({ role: 'system', content: AGENT_SYSTEM });
40
+ const MODELS = {
41
+ 1: { id: 'flash', label: 'Flash', desc: 'Fast & lightweight' },
42
+ 2: { id: 'pro', label: 'Pro', desc: 'Balanced — general use' },
43
+ 3: { id: 'thinking', label: 'Thinking', desc: 'Deep reasoning' },
44
+ 4: { id: 'ultra', label: 'Ultra Code', desc: 'Elite code generation' }
45
+ }
52
46
 
53
- // --- UTILS ---
54
- const sleep = ms => new Promise(r => setTimeout(r, ms));
47
+ const AGENT_SYSTEM = `You are Pritus Code, an AI coding agent in a terminal. You CREATE and MODIFY real files.
48
+ When asked to build something, respond in EXACT format:
49
+ THINKING: <one sentence>
50
+ CREATE_FILE: <filename>
51
+ \`\`\`<language>
52
+ <full file content>
53
+ \`\`\`
54
+ `
55
+
56
+ // --- ANSI & FORMATTING HELPERS ---
57
+ function stripAnsi(str) {
58
+ return str.replace(/\x1B\[[0-9;]{0,}[A-Za-z]/g, '')
59
+ }
55
60
 
56
- const rl = readline.createInterface({
57
- input: process.stdin,
58
- output: process.stdout,
59
- prompt: `${c.magenta}${c.bold}> ${c.rst}`
60
- });
61
+ function bold(str) {
62
+ return '\x1b[1m' + str + '\x1b[22m'
63
+ }
61
64
 
62
- const ask = query => new Promise(resolve => {
63
- rl.question(query, resolve);
64
- });
65
+ function dim(str) {
66
+ return '\x1b[2m' + str + '\x1b[22m'
67
+ }
65
68
 
66
- // --- UI COMPONENTS ---
67
- function clearConsole() {
68
- process.stdout.write('\x1B[2J\x1B[0f');
69
+ function cyan(str) {
70
+ return '\x1b[36m' + str + '\x1b[39m'
69
71
  }
70
72
 
71
- function printLogo() {
72
- // Blue to Magenta ANSI 256 gradient approximation
73
- const d1 = '\x1b[38;5;33m';
74
- const d2 = '\x1b[38;5;39m';
75
- const d3 = '\x1b[38;5;135m';
76
- const d4 = '\x1b[38;5;165m';
77
- const d5 = '\x1b[38;5;199m';
78
-
79
- console.log(`
80
- ${d1} /\\ ${c.bold}██████╗ ██████╗ ██╗████████╗██╗ ██╗███████╗${c.rst}
81
- ${d2} / \\ ${c.bold}██╔══██╗██╔══██╗██║╚══██╔══╝██║ ██║██╔════╝${c.rst}
82
- ${d3} /____\\ ${c.bold}██████╔╝██████╔╝██║ ██║ ██║ ██║███████╗${c.rst}
83
- ${d4} \\ / ${c.bold}██╔═══╝ ██╔══██╗██║ ██║ ██║ ██║╚════██║${c.rst}
84
- ${d5} \\ / ${c.bold}██║ ██║ ██║██║ ██║ ╚██████╔╝███████║${c.rst}
85
- ${c.magenta} \\/ ${c.bold}╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝${c.rst}
86
- ${c.dim}v1.0.0${c.rst}
87
- `);
73
+ function green(str) {
74
+ return '\x1b[32m' + str + '\x1b[39m'
88
75
  }
89
76
 
90
- function printWelcomeBox() {
91
- const cwd = process.cwd();
92
- const boxWidth = Math.max(60, cwd.length + 20);
93
- const line = '─'.repeat(boxWidth - 2);
94
- console.log(`${c.magenta}┌${line}┐${c.rst}`);
95
- console.log(`${c.magenta}│${c.rst} ${c.bold}Welcome to PRITUS CODE${c.rst}`.padEnd(boxWidth + c.magenta.length + c.rst.length + c.bold.length + c.rst.length - 1) + `${c.magenta}│${c.rst}`);
96
- console.log(`${c.magenta}│${c.rst} ${c.dim}Type /help for commands, @filename to attach files${c.rst}`.padEnd(boxWidth + c.magenta.length + c.rst.length + c.dim.length + c.rst.length - 1) + `${c.magenta}│${c.rst}`);
97
- console.log(`${c.magenta}│${c.rst} ${c.cyan}Workspace: ${cwd}${c.rst}`.padEnd(boxWidth + c.magenta.length + c.rst.length + c.cyan.length + c.rst.length - 1) + `${c.magenta}│${c.rst}`);
98
- console.log(`${c.magenta}└${line}┘${c.rst}\n`);
99
-
100
- console.log(`${c.bold}Tips:${c.rst}`);
101
- console.log(` 1. Use @file to include file context`);
102
- console.log(` 2. Type /model to switch intelligence tiers`);
103
- console.log(` 3. Type /init to set up project instructions`);
104
- console.log(` 4. Let the agent build files for you ${c.green}✓${c.rst}\n`);
77
+ function magenta(str) {
78
+ return '\x1b[35m' + str + '\x1b[39m'
105
79
  }
106
80
 
107
- function printStatusBar() {
108
- const cwd = path.basename(process.cwd());
109
- const bar = `${c.bgMagenta}${c.white} ${cwd} ${c.rst} ${c.dim}model:${c.rst} ${config.tier} ${c.dim}| /help for menu${c.rst}`;
110
- console.log(`\n${bar}`);
81
+ function brightMagenta(str) {
82
+ return '\x1b[95m' + str + '\x1b[39m'
111
83
  }
112
84
 
113
- async function startLoading(text = 'Thinking') {
114
- const frames = ['⠋', '⠙', '⠹', '⠸', '', '⠴', '⠦', '⠧', '', '⠏'];
115
- let i = 0;
116
- const interval = setInterval(() => {
117
- process.stdout.write(`\r${c.magenta}${frames[i]}${c.rst} ${c.dim}${text}...${c.rst}`);
118
- i = (i + 1) % frames.length;
119
- }, 80);
120
- return () => {
121
- clearInterval(interval);
122
- process.stdout.write('\r\x1b[K'); // clear line
123
- };
85
+ function ansi256(code, str) {
86
+ return '\x1b[38;5;' + code + 'm' + str + '\x1b[0m'
124
87
  }
125
88
 
126
- async function streamText(text) {
127
- const words = text.split(' ');
128
- for (const word of words) {
129
- process.stdout.write(word + ' ');
130
- await sleep(20); // typing effect
131
- }
132
- console.log();
89
+ function formatPath(p) {
90
+ const home = os.homedir()
91
+ if (p.startsWith(home)) {
92
+ return '~' + p.slice(home.length)
93
+ }
94
+ return p
133
95
  }
134
96
 
135
- // --- COMMANDS & FEATURES ---
136
- async function selectModel() {
137
- console.log(`\n${c.bold}Select Model Tier:${c.rst}`);
138
- console.log(` 1. Flash - Fast & lightweight`);
139
- console.log(` 2. Pro - Balanced general use`);
140
- console.log(` 3. Thinking - Deep reasoning`);
141
- console.log(` 4. Ultra Code - Elite code generation`);
142
-
143
- const choice = await ask(`\n${c.dim}Press 1-4 or Enter for Pro: ${c.rst}`);
144
- switch(choice.trim()) {
145
- case '1': config.tier = 'flash'; break;
146
- case '3': config.tier = 'thinking'; break;
147
- case '4': config.tier = 'code'; break;
148
- case '2':
149
- default: config.tier = 'pro'; break;
150
- }
151
- console.log(`${c.green}✓ Switched to ${config.tier} tier${c.rst}\n`);
97
+ // --- LOGO & ASCII ART GENERATORS ---
98
+ function getDiamondLogo() {
99
+ const lines = [
100
+ ansi256(39, '██'),
101
+ ' ' + ansi256(75, '██'),
102
+ ' ' + ansi256(99, '██'),
103
+ ' ' + ansi256(141, '██'),
104
+ ' ' + ansi256(177, '██'),
105
+ ansi256(207, '██'),
106
+ ansi256(207, '████████')
107
+ ]
108
+ return lines
152
109
  }
153
110
 
154
- function processAtFiles(input) {
155
- const fileRegex = /@([\w.\-\/]+)/g;
156
- let modifiedInput = input;
157
- let match;
158
- let addedFiles = [];
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
+ }
159
193
 
160
- while ((match = fileRegex.exec(input)) !== null) {
161
- const filepath = match[1];
162
- try {
163
- const content = fs.readFileSync(filepath, 'utf-8');
164
- modifiedInput += `\n\n[Context from ${filepath}]:\n\`\`\`\n${content}\n\`\`\``;
165
- addedFiles.push(filepath);
166
- } catch (e) {
167
- console.log(`${c.red}⚠ Could not read @file: ${filepath}${c.rst}`);
168
- }
169
- }
170
- if (addedFiles.length > 0) {
171
- console.log(`${c.dim}Attached: ${addedFiles.join(', ')}${c.rst}`);
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
+
208
+ console.log('')
209
+ for (let i = 0; i < 7; i++) {
210
+ const dia = diamond[i]
211
+ const ban = i < 6 ? cyan(banner[i]) : ''
212
+ console.log(dia + ' ' + ban)
213
+ }
214
+ console.log(brightMagenta(bold('PRITUS CODE v' + VERSION)))
215
+ console.log('')
216
+ }
217
+
218
+ function renderWelcomeBox(cwd) {
219
+ 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
223
+
224
+ let innerWidth = line1.length
225
+ if (line2.length > innerWidth) innerWidth = line2.length
226
+ if (line3.length > innerWidth) innerWidth = line3.length
227
+ innerWidth += 4
228
+
229
+ const topBorder = '┌' + '─'.repeat(innerWidth) + '┐'
230
+ const bottomBorder = '└' + '─'.repeat(innerWidth) + '┘'
231
+
232
+ console.log(cyan(topBorder))
233
+
234
+ const pad1 = ' '.repeat(innerWidth - line1.length - 2)
235
+ console.log(cyan('│ ') + bold(line1) + pad1 + cyan(' │'))
236
+
237
+ const pad2 = ' '.repeat(innerWidth - line2.length - 2)
238
+ console.log(cyan('│ ') + dim(line2) + pad2 + cyan(' │'))
239
+
240
+ const pad3 = ' '.repeat(innerWidth - line3.length - 2)
241
+ console.log(cyan('│ ') + dim(line3) + pad3 + cyan(' │'))
242
+
243
+ console.log(cyan(bottomBorder))
244
+ console.log('')
245
+ }
246
+
247
+ function renderTips() {
248
+ console.log(bold('Tips for getting started:'))
249
+ console.log('1. Ask Pritus to build something — it will create actual files in your project')
250
+ console.log('2. Use @filename to include a file contents in your message')
251
+ console.log('3. Be as specific as you would with another engineer for the best results')
252
+ console.log(green('✓') + ' Run /init to create a PRITUS.md with project instructions')
253
+ console.log('')
254
+ }
255
+
256
+ function renderStatusBar(currentModelId, cwd) {
257
+ const displayCwd = formatPath(cwd)
258
+ const selectedObj = getModelInfo(currentModelId)
259
+ const barText = 'workspace (' + displayCwd + ') /model ' + selectedObj.id + ' ? for shortcuts'
260
+ console.log(dim(barText))
261
+ console.log('')
262
+ }
263
+
264
+ function getModelInfo(keyOrId) {
265
+ const str = String(keyOrId).toLowerCase()
266
+ for (const k in MODELS) {
267
+ if (String(k) === str || MODELS[k].id === str) {
268
+ return MODELS[k]
172
269
  }
173
- return modifiedInput;
270
+ }
271
+ return MODELS[2]
174
272
  }
175
273
 
176
- // --- AGENT PARSER ---
177
- async function parseAndExecuteAgent(response) {
178
- // Regex to extract blocks
179
- const thinkingMatch = response.match(/THINKING:\s*(.*?)\n/i);
180
- const doneMatch = response.match(/DONE:\s*(.*)/i);
181
- const fileRegex = /CREATE_FILE:\s*(.*?)\n([\s\S]*?)(?=DONE:|CREATE_FILE:|$)/gi;
274
+ // --- MODEL SELECTOR ---
275
+ function promptModelSelection(currentModelId, callback) {
276
+ console.log('Select a model:')
277
+ for (const k in MODELS) {
278
+ const m = MODELS[k]
279
+ const isSelected = m.id === currentModelId
280
+ const pointer = isSelected ? '▸ ' : ' '
281
+ const num = k + ' '
282
+ console.log(pointer + num + bold(m.label) + ' — ' + m.desc)
283
+ }
284
+
285
+ const defaultModelObj = getModelInfo(currentModelId)
286
+ process.stdout.write('Press 1-4, or Enter for ' + defaultModelObj.id + ': ')
287
+
288
+ if (process.stdin.isTTY) {
289
+ process.stdin.setRawMode(true)
290
+ process.stdin.resume()
291
+ process.stdin.once('data', (data) => {
292
+ process.stdin.setRawMode(false)
293
+ process.stdin.pause()
294
+ const char = data.toString('utf8').trim()
295
+ console.log(char)
296
+
297
+ let choiceObj = defaultModelObj
298
+ if (MODELS[char]) {
299
+ choiceObj = MODELS[char]
300
+ }
301
+ console.log(green('✓') + ' Switched to ' + choiceObj.label)
302
+ console.log('')
303
+ callback(choiceObj.id)
304
+ })
305
+ } else {
306
+ const rlTemp = readline.createInterface({
307
+ input: process.stdin,
308
+ output: process.stdout
309
+ })
310
+ rlTemp.question('', (ans) => {
311
+ rlTemp.close()
312
+ const trimmed = ans.trim()
313
+ let choiceObj = defaultModelObj
314
+ if (MODELS[trimmed]) {
315
+ choiceObj = MODELS[trimmed]
316
+ }
317
+ console.log(green('✓') + ' Switched to ' + choiceObj.label)
318
+ console.log('')
319
+ callback(choiceObj.id)
320
+ })
321
+ }
322
+ }
182
323
 
183
- let isAgentic = false;
324
+ // --- FILE PARSER & AGENT OPERATIONS ---
325
+ function processFileOperations(aiResponse) {
326
+ let modifiedAny = false
327
+ const createPattern = /CREATE_FILE:\s{0,}([^\n]+)\n```[a-z]{0,}\n([\s\S]+?)```/gi
328
+ const modifyPattern = /MODIFY_FILE:\s{0,}([^\n]+)\n```[a-z]{0,}\n([\s\S]+?)```/gi
184
329
 
185
- if (thinkingMatch) {
186
- isAgentic = true;
187
- console.log(`\n🤔 ${c.dim}${thinkingMatch[1].trim()}${c.rst}\n`);
330
+ let match
331
+ while ((match = createPattern.exec(aiResponse)) !== null) {
332
+ const fileName = match[1].trim()
333
+ const content = match[2]
334
+ try {
335
+ const targetPath = path.resolve(process.cwd(), fileName)
336
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true })
337
+ fs.writeFileSync(targetPath, content, 'utf8')
338
+ const bytes = Buffer.byteLength(content, 'utf8')
339
+ console.log(green('✓ Created ') + fileName + ' (' + bytes + ' bytes)')
340
+ modifiedAny = true
341
+ } catch (err) {
342
+ console.log('\x1b[31mError creating file ' + fileName + ': ' + err.message + '\x1b[39m')
188
343
  }
344
+ }
189
345
 
190
- let match;
191
- let fileCount = 0;
192
- while ((match = fileRegex.exec(response)) !== null) {
193
- isAgentic = true;
194
- const filepath = match[1].trim();
195
- const content = match[2].trim();
196
-
197
- console.log(`${c.cyan}┌─ CREATE: ${filepath}${c.rst}`);
198
-
199
- try {
200
- const fullPath = path.resolve(process.cwd(), filepath);
201
- fs.mkdirSync(path.dirname(fullPath), { recursive: true });
202
- fs.writeFileSync(fullPath, content);
203
- console.log(`${c.cyan}└─ ${c.green}✓ Created successfully${c.rst}`);
204
- fileCount++;
205
- } catch (err) {
206
- console.log(`${c.cyan}└─ ${c.red}✗ Failed: ${err.message}${c.rst}`);
207
- }
346
+ while ((match = modifyPattern.exec(aiResponse)) !== null) {
347
+ const fileName = match[1].trim()
348
+ const content = match[2]
349
+ try {
350
+ const targetPath = path.resolve(process.cwd(), fileName)
351
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true })
352
+ fs.writeFileSync(targetPath, content, 'utf8')
353
+ const bytes = Buffer.byteLength(content, 'utf8')
354
+ console.log(green('✓ Modified ') + fileName + ' (' + bytes + ' bytes)')
355
+ modifiedAny = true
356
+ } catch (err) {
357
+ console.log('\x1b[31mError modifying file ' + fileName + ': ' + err.message + '\x1b[39m')
208
358
  }
359
+ }
360
+
361
+ return modifiedAny
362
+ }
209
363
 
210
- if (isAgentic && doneMatch) {
211
- console.log(`\n${c.green}✓ Agent Finished:${c.rst} ${doneMatch[1].trim()} (${fileCount} files created)`);
364
+ function expandFileAttachments(text) {
365
+ const atPattern = /@([^\s]+)/g
366
+ let match
367
+ let expanded = text
368
+ const attachedFiles = []
369
+
370
+ while ((match = atPattern.exec(text)) !== null) {
371
+ const fileRef = match[1]
372
+ const filePath = path.resolve(process.cwd(), fileRef)
373
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
374
+ try {
375
+ const fileContent = fs.readFileSync(filePath, 'utf8')
376
+ attachedFiles.push({ ref: fileRef, content: fileContent })
377
+ } catch (e) {
378
+ // file unreadable
379
+ }
212
380
  }
381
+ }
213
382
 
214
- // If it didn't use the agent format, just stream it conversationally
215
- if (!isAgentic) {
216
- await streamText(response.trim());
383
+ if (attachedFiles.length > 0) {
384
+ let header = ''
385
+ for (let i = 0; i < attachedFiles.length; i++) {
386
+ header += '[Attached file: ' + attachedFiles[i].ref + ']\n' + attachedFiles[i].content + '\n[End attached file]\n\n'
217
387
  }
218
- }
388
+ expanded = header + text
389
+ }
219
390
 
220
- // --- API LAYER ---
221
- async function handleChat(prompt) {
222
- const finalPrompt = processAtFiles(prompt);
223
- messages.push({ role: 'user', content: finalPrompt });
391
+ return expanded
392
+ }
224
393
 
225
- const stopLoading = await startLoading();
226
-
227
- try {
228
- const res = await fetch(config.endpoint, {
229
- method: 'POST',
230
- headers: { 'Content-Type': 'application/json' },
231
- body: JSON.stringify({
232
- messages,
233
- tier: config.tier,
234
- plan: 'pro'
235
- })
236
- });
237
-
238
- stopLoading();
239
-
240
- if (!res.ok) {
241
- throw new Error(`Status ${res.status}`);
394
+ // --- HTTP API CLIENT ---
395
+ function sendChatRequest(apiUrl, modelId, userPrompt, messagesHistory, callback) {
396
+ const expandedPrompt = expandFileAttachments(userPrompt)
397
+
398
+ let targetUrl = apiUrl
399
+ if (!targetUrl.endsWith('/api/pritus/chat')) {
400
+ if (targetUrl.endsWith('/')) {
401
+ targetUrl += 'api/pritus/chat'
402
+ } else {
403
+ targetUrl += '/api/pritus/chat'
404
+ }
405
+ }
406
+
407
+ let parsedUrl
408
+ try {
409
+ parsedUrl = new URL(targetUrl)
410
+ } catch (e) {
411
+ console.log('\x1b[31mInvalid API URL: ' + targetUrl + '\x1b[39m')
412
+ return callback(new Error('Invalid URL'))
413
+ }
414
+
415
+ const httpModule = parsedUrl.protocol === 'https:' ? require('https') : require('http')
416
+ const payload = JSON.stringify({
417
+ prompt: expandedPrompt,
418
+ model: modelId,
419
+ system: AGENT_SYSTEM,
420
+ messages: messagesHistory
421
+ })
422
+
423
+ const reqOpts = {
424
+ hostname: parsedUrl.hostname,
425
+ port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
426
+ path: parsedUrl.pathname + parsedUrl.search,
427
+ method: 'POST',
428
+ headers: {
429
+ 'Content-Type': 'application/json',
430
+ 'Content-Length': Buffer.byteLength(payload)
431
+ }
432
+ }
433
+
434
+ const req = httpModule.request(reqOpts, (res) => {
435
+ let body = ''
436
+ res.on('data', (chunk) => {
437
+ body += chunk.toString('utf8')
438
+ })
439
+ res.on('end', () => {
440
+ if (res.statusCode >= 200 && res.statusCode < 300) {
441
+ let textResult = body
442
+ try {
443
+ const parsed = JSON.parse(body)
444
+ if (parsed.response) textResult = parsed.response
445
+ else if (parsed.message) textResult = parsed.message
446
+ else if (parsed.content) textResult = parsed.content
447
+ else if (parsed.choices && parsed.choices[0] && parsed.choices[0].message) {
448
+ textResult = parsed.choices[0].message.content
449
+ }
450
+ } catch (e) {
451
+ // Response is raw text
242
452
  }
453
+ callback(null, textResult)
454
+ } else {
455
+ callback(new Error('API returned HTTP status ' + res.statusCode + ': ' + body))
456
+ }
457
+ })
458
+ })
459
+
460
+ req.on('error', (err) => {
461
+ callback(err)
462
+ })
463
+
464
+ req.write(payload)
465
+ req.end()
466
+ }
243
467
 
244
- const data = await res.json();
245
- // Assuming API returns { response: "..." } or similar
246
- const reply = data.response || data.reply || data.content || data.message || (data.choices && data.choices[0].message.content);
247
-
248
- if (!reply) {
249
- console.log(`${c.red}⚠ Unexpected API response format.${c.rst}`);
250
- return;
468
+ // --- SLASH COMMANDS HANDLER ---
469
+ function handleSlashCommand(input, state, rl) {
470
+ const parts = input.trim().split(/\s{1,}/)
471
+ const cmd = parts[0].toLowerCase()
472
+ const arg = parts.slice(1).join(' ')
473
+
474
+ switch (cmd) {
475
+ case '/help':
476
+ case '?':
477
+ console.log('')
478
+ console.log(bold('PRITUS CODE Commands & Shortcuts:'))
479
+ console.log(' /help, ? Show this help message')
480
+ console.log(' /status Show current configuration')
481
+ console.log(' /init Create PRITUS.md with project rules')
482
+ console.log(' /model [1-4] Change AI model (1: Flash, 2: Pro, 3: Thinking, 4: Ultra)')
483
+ console.log(' /clear Clear terminal screen')
484
+ console.log(' /exit, /quit Exit PRITUS CODE')
485
+ console.log(' @filename Include file contents in your prompt')
486
+ console.log('')
487
+ rl.prompt()
488
+ return true
489
+
490
+ case '/status':
491
+ console.log('')
492
+ console.log(bold('Current Setup:'))
493
+ console.log(' Version: ' + VERSION)
494
+ console.log(' Model: ' + getModelInfo(state.currentModel).label + ' (' + state.currentModel + ')')
495
+ console.log(' API URL: ' + state.apiUrl)
496
+ console.log(' CWD: ' + process.cwd())
497
+ console.log('')
498
+ rl.prompt()
499
+ return true
500
+
501
+ case '/init':
502
+ {
503
+ const docPath = path.join(process.cwd(), 'PRITUS.md')
504
+ const docContent = `# PRITUS CODE Project Instructions
505
+
506
+ ## Overview
507
+ Describe your project and architectural guidelines here for Pritus Code.
508
+
509
+ ## Guidelines
510
+ - Write clean, modular, and maintainable code.
511
+ - Always implement proper error handling.
512
+ `
513
+ try {
514
+ fs.writeFileSync(docPath, docContent, 'utf8')
515
+ console.log(green('✓ Created PRITUS.md with project instructions'))
516
+ } catch (err) {
517
+ console.log('\x1b[31mError creating PRITUS.md: ' + err.message + '\x1b[39m')
251
518
  }
519
+ console.log('')
520
+ rl.prompt()
521
+ }
522
+ return true
523
+
524
+ case '/model':
525
+ if (arg) {
526
+ const info = getModelInfo(arg)
527
+ state.currentModel = info.id
528
+ console.log(green('✓ Switched to ') + info.label)
529
+ console.log('')
530
+ rl.prompt()
531
+ } else {
532
+ promptModelSelection(state.currentModel, (newModel) => {
533
+ state.currentModel = newModel
534
+ rl.prompt()
535
+ })
536
+ }
537
+ return true
538
+
539
+ case '/clear':
540
+ console.clear()
541
+ renderHeader()
542
+ renderStatusBar(state.currentModel, process.cwd())
543
+ rl.prompt()
544
+ return true
545
+
546
+ case '/exit':
547
+ case '/quit':
548
+ console.log(dim('Goodbye!'))
549
+ process.exit(0)
550
+ return true
551
+
552
+ default:
553
+ if (cmd.startsWith('/')) {
554
+ console.log('\x1b[31mUnknown command: ' + cmd + '. Type /help for assistance.\x1b[39m')
555
+ console.log('')
556
+ rl.prompt()
557
+ return true
558
+ }
559
+ return false
560
+ }
561
+ }
252
562
 
253
- messages.push({ role: 'assistant', content: reply });
254
- await parseAndExecuteAgent(reply);
563
+ // --- MAIN REPL LOOP ---
564
+ function startRepl(state) {
565
+ const rl = readline.createInterface({
566
+ input: process.stdin,
567
+ output: process.stdout,
568
+ prompt: '> '
569
+ })
255
570
 
256
- } catch (error) {
257
- stopLoading();
258
- console.log(`${c.red}Pritus AI is suffering from heavy traffic right now. (Error: ${error.message})${c.rst}`);
259
- // Remove the failed message so they can retry
260
- messages.pop();
261
- }
262
- }
571
+ const messagesHistory = []
263
572
 
264
- // --- SIMULATED GOOGLE LOGIN ---
265
- async function handleLogin() {
266
- console.log(`\n${c.bold}Google Authentication${c.rst}`);
267
- const code = Math.random().toString(36).substring(2, 10).toUpperCase();
268
- console.log(`${c.dim}Please go to: ${c.white}https://google.com/device${c.rst}`);
269
- console.log(`${c.dim}And enter this code: ${c.magenta}${c.bold}${code}${c.rst}\n`);
270
-
271
- const stop = await startLoading('Waiting for authorization');
272
- await sleep(4000); // Simulate waiting for external auth
273
- stop();
274
-
275
- const tokenPath = path.join(os.homedir(), '.prituscode_auth');
276
- fs.writeFileSync(tokenPath, `mock_token_${Date.now()}`);
277
- console.log(`${c.green}✓ Successfully logged in with Google!${c.rst}\n`);
278
- }
573
+ rl.prompt()
279
574
 
575
+ rl.on('line', (line) => {
576
+ const input = line.trim()
577
+ if (!input) {
578
+ rl.prompt()
579
+ return
580
+ }
280
581
 
281
- // --- MAIN REPL ---
282
- async function main() {
283
- if (config.oneShotPrompt) {
284
- console.log(`${c.magenta}PRITUS CODE${c.rst} ${c.dim}> One-shot mode${c.rst}\n`);
285
- await handleChat(config.oneShotPrompt);
286
- process.exit(0);
582
+ if (handleSlashCommand(input, state, rl)) {
583
+ return
287
584
  }
288
585
 
289
- clearConsole();
290
- printLogo();
291
- printWelcomeBox();
292
-
293
- // Auto-prompt for model on first run if in interactive mode
294
- await selectModel();
295
- printStatusBar();
296
-
297
- rl.prompt();
298
-
299
- rl.on('line', async (line) => {
300
- const input = line.trim();
301
- if (!input) {
302
- rl.prompt();
303
- return;
304
- }
586
+ process.stdout.write(dim('Thinking... \r'))
305
587
 
306
- switch (input.toLowerCase()) {
307
- case '/exit':
308
- case '/quit':
309
- console.log(`${c.dim}Goodbye!${c.rst}`);
310
- process.exit(0);
311
- break;
312
- case '/clear':
313
- clearConsole();
314
- messages.length = 1; // Keep system prompt
315
- console.log(`${c.green}✓ Context cleared.${c.rst}`);
316
- break;
317
- case '/model':
318
- await selectModel();
319
- break;
320
- case '/status':
321
- console.log(`\n${c.bold}PRITUS STATUS${c.rst}`);
322
- console.log(`Tier: ${c.magenta}${config.tier}${c.rst}`);
323
- console.log(`Messages in context: ${messages.length - 1}`);
324
- console.log(`Workspace: ${process.cwd()}\n`);
325
- break;
326
- case '/init':
327
- fs.writeFileSync('PRITUS.md', '# PRITUS CODE Instructions\n\nAdd your custom project rules here. The agent will read this automatically if referenced.');
328
- console.log(`${c.green}✓ Created PRITUS.md${c.rst}`);
329
- break;
330
- case '/login':
331
- await handleLogin();
332
- break;
333
- case '/help':
334
- console.log(`\n${c.bold}Commands:${c.rst}`);
335
- console.log(` @filename Include file contents in your prompt`);
336
- console.log(` /model Change AI tier (flash, pro, thinking, code)`);
337
- console.log(` /init Create a PRITUS.md instruction file`);
338
- console.log(` /clear Clear conversation history`);
339
- console.log(` /status Show current agent status`);
340
- console.log(` /login Authenticate with Google`);
341
- console.log(` /exit Close PRITUS CODE\n`);
342
- break;
343
- default:
344
- await handleChat(input);
345
- break;
346
- }
347
-
348
- printStatusBar();
349
- rl.prompt();
350
- });
588
+ sendChatRequest(state.apiUrl, state.currentModel, input, messagesHistory, (err, responseText) => {
589
+ // Clear line
590
+ process.stdout.write(' \r')
591
+
592
+ if (err) {
593
+ console.log('\x1b[31mError: ' + err.message + '\x1b[39m')
594
+ console.log('')
595
+ rl.prompt()
596
+ return
597
+ }
598
+
599
+ console.log(responseText)
600
+ console.log('')
601
+
602
+ const opsExecuted = processFileOperations(responseText)
603
+ if (opsExecuted) {
604
+ console.log('')
605
+ }
606
+
607
+ messagesHistory.push({ role: 'user', content: input })
608
+ messagesHistory.push({ role: 'assistant', content: responseText })
609
+
610
+ rl.prompt()
611
+ })
612
+ })
613
+
614
+ rl.on('close', () => {
615
+ console.log(dim('\nGoodbye!'))
616
+ process.exit(0)
617
+ })
351
618
  }
352
619
 
353
- // Handle interrupts gracefully
354
- rl.on('SIGINT', () => {
355
- console.log(`\n${c.dim}Operation cancelled. Type /exit to quit.${c.rst}`);
356
- rl.prompt();
357
- });
620
+ // --- ENTRY POINT ---
621
+ function main() {
622
+ const args = process.argv.slice(2)
623
+
624
+ const state = {
625
+ apiUrl: DEFAULT_API_URL,
626
+ currentModel: DEFAULT_MODEL
627
+ }
628
+
629
+ // Parse direct flags
630
+ if (args.includes('-v') || args.includes('--version')) {
631
+ console.log('PRITUS CODE v' + VERSION)
632
+ process.exit(0)
633
+ }
634
+
635
+ if (args.includes('-h') || args.includes('--help')) {
636
+ console.log('PRITUS CODE v' + VERSION + ' - Terminal AI Coding Agent')
637
+ console.log('\nUsage:')
638
+ console.log(' prituscode Start interactive terminal session')
639
+ console.log(' prituscode "build a server" Run single prompt')
640
+ console.log(' prituscode -m ultra "query" Run single prompt with specified model')
641
+ console.log(' prituscode -v, --version Show version')
642
+ console.log(' prituscode -h, --help Show help')
643
+ process.exit(0)
644
+ }
645
+
646
+ let modelOverride = null
647
+ const queryArgs = []
648
+
649
+ for (let i = 0; i < args.length; i++) {
650
+ if (args[i] === '-m' || args[i] === '--model') {
651
+ if (i + 1 < args.length) {
652
+ modelOverride = args[i + 1]
653
+ i++
654
+ }
655
+ } else {
656
+ queryArgs.push(args[i])
657
+ }
658
+ }
659
+
660
+ if (modelOverride) {
661
+ const info = getModelInfo(modelOverride)
662
+ state.currentModel = info.id
663
+ }
664
+
665
+ // Non-interactive single prompt mode
666
+ if (queryArgs.length > 0) {
667
+ const singlePrompt = queryArgs.join(' ')
668
+ process.stdout.write(dim('Processing request...\n'))
669
+ sendChatRequest(state.apiUrl, state.currentModel, singlePrompt, [], (err, responseText) => {
670
+ if (err) {
671
+ console.error('\x1b[31mError: ' + err.message + '\x1b[39m')
672
+ process.exit(1)
673
+ }
674
+ console.log(responseText)
675
+ processFileOperations(responseText)
676
+ process.exit(0)
677
+ })
678
+ return
679
+ }
680
+
681
+ // Interactive mode startup
682
+ renderHeader()
683
+ renderWelcomeBox(process.cwd())
684
+ renderTips()
685
+
686
+ promptModelSelection(state.currentModel, (selectedModel) => {
687
+ state.currentModel = selectedModel
688
+ renderStatusBar(state.currentModel, process.cwd())
689
+ startRepl(state)
690
+ })
691
+ }
358
692
 
359
- main().catch(console.error);
693
+ 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