novaprime 1.4.2 → 1.5.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/bin/novaprime.js CHANGED
@@ -58,7 +58,11 @@ function printHelp() {
58
58
  ${c.indigo('/clear')} clear the screen
59
59
  ${c.indigo('/exit')} quit ${c.dim('(Ctrl+C also quits)')}
60
60
 
61
- ${c.muted('Type a task and press Enter. NovaPrime asks before writing files or running commands.')}
61
+ ${c.white.bold('Permissions')}
62
+ ${c.indigo('Shift+Tab')} toggle ${c.green('auto-accept')} ${c.dim('(no more permission prompts this session)')}
63
+ ${c.indigo('a')} at any prompt = "always" ${c.dim('(approve this and everything after)')}
64
+
65
+ ${c.muted('Type a task and press Enter. By default NovaPrime asks before writing files or running commands.')}
62
66
  `);
63
67
  }
64
68
 
@@ -80,6 +84,8 @@ async function repl() {
80
84
  ui.banner(meToBanner(cfg, me));
81
85
  process.on('SIGINT', () => { console.log(c.muted('\n bye')); process.exit(0); });
82
86
 
87
+ ui.hint(' Tip: press ' + c.indigo('Shift+Tab') + c.dim(' for auto-accept (no permission prompts), or answer ') + c.indigo('a') + c.dim(' at any prompt.'));
88
+
83
89
  // push the first input box toward the lower part of the screen, leaving a bottom margin
84
90
  const rows = process.stdout.rows || 24;
85
91
  process.stdout.write('\n'.repeat(Math.max(0, rows - 26)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "novaprime",
3
- "version": "1.4.2",
3
+ "version": "1.5.1",
4
4
  "description": "NovaPrime — an AI coding assistant in your terminal, powered by GLM.",
5
5
  "bin": {
6
6
  "novaprime": "bin/novaprime.js"
package/src/agent.js CHANGED
@@ -17,6 +17,9 @@ const SYSTEM_PROMPT =
17
17
  `Use clear markdown: short paragraphs, bullet lists, and fenced code blocks with a language tag. ` +
18
18
  `When asked to create or change files, call the tool RIGHT AWAY with at most a one-line intro — ` +
19
19
  `do NOT write long explanations or feature lists before creating the file. Keep any summary to 1-2 short lines after. ` +
20
+ `NEVER start dev servers or long-running/blocking processes (e.g. "npm run dev", "npm start", "vite", "next dev", ` +
21
+ `"nodemon", watchers) — they block the session forever. After creating the files, finish, then tell the user the ` +
22
+ `commands to run it themselves (e.g. cd into the folder and run "npm run dev", then open the localhost URL). ` +
20
23
  `Be concise and warm. Current OS: ${os.platform()}. Working directory: ${process.cwd()}.`;
21
24
 
22
25
  // Fetch read-only account info for the header (name, plan, usage). Never throws.
package/src/mode.js ADDED
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ // Session-wide permission mode. When auto-accept is ON, NovaPrime runs commands
4
+ // and writes/edits files without asking each time. Toggle with Shift+Tab, or by
5
+ // answering "a" (always) at any permission prompt.
6
+ let auto = false;
7
+
8
+ module.exports = {
9
+ isAuto: () => auto,
10
+ setAuto: (v) => { auto = !!v; },
11
+ toggle: () => { auto = !auto; return auto; },
12
+ };
package/src/prompt.js CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
  const readline = require('readline');
3
+ const { c } = require('./ui');
4
+ const mode = require('./mode');
3
5
 
4
6
  // Use Node's readline for single-line prompts (login key, y/N) — it handles
5
7
  // editing and history. The multi-line chat box uses a paste-aware reader below.
@@ -60,8 +62,9 @@ function handlePlain(state, s, sink) {
60
62
  }
61
63
  continue;
62
64
  }
63
- if (ch === '\x1b') { // skip CSI/SS3 escape (arrows, fn keys)
64
- if (s[i + 1] === '[' || s[i + 1] === 'O') {
65
+ if (ch === '\x1b') {
66
+ if (s.slice(i, i + 3) === '\x1b[Z') { if (sink.toggleMode) sink.toggleMode(); i += 2; continue; } // Shift+Tab
67
+ if (s[i + 1] === '[' || s[i + 1] === 'O') { // skip other CSI/SS3 escapes (arrows, fn keys)
65
68
  i += 2;
66
69
  while (i < s.length && !/[A-Za-z~]/.test(s[i])) i++;
67
70
  }
@@ -138,6 +141,14 @@ function boxInput(top, bottom, prompt) {
138
141
  out: (str) => process.stdout.write(str),
139
142
  submit: (val) => { state.finished = true; cleanup(); process.stdout.write('\n' + bottom + '\n'); resolve(val); },
140
143
  cancel: () => { state.finished = true; cleanup(); process.stdout.write('\n' + bottom + '\n'); resolve(null); },
144
+ toggleMode: () => {
145
+ const on = mode.toggle();
146
+ process.stdout.write('\r\x1b[2K'); // clear current input line
147
+ process.stdout.write((on
148
+ ? c.green(' ⚡ auto-accept: ON') + c.dim(' — running everything without asking (Shift+Tab to undo)')
149
+ : c.dim(' ⚡ auto-accept: OFF — will ask before changes')) + '\n');
150
+ process.stdout.write(prompt + state.buf); // redraw input line with whatever was typed
151
+ },
141
152
  };
142
153
 
143
154
  process.stdout.write(top + '\n');
package/src/tools.js CHANGED
@@ -2,11 +2,26 @@
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
4
  const { spawnSync } = require('child_process');
5
- const { confirm } = require('./prompt');
5
+ const { ask } = require('./prompt');
6
6
  const { c, tool } = require('./ui');
7
+ const mode = require('./mode');
7
8
 
8
9
  const MAX_OUTPUT = 20000; // cap tool output sent back to the model
9
10
 
11
+ // Permission gate. Returns true if allowed. In auto-accept mode it never asks.
12
+ // Answering "a" (always) flips auto-accept ON for the rest of the session.
13
+ async function allow(label) {
14
+ if (mode.isAuto()) { console.log(c.dim(' · auto-accepted')); return true; }
15
+ const raw = (await ask(label + c.dim(' (y/N, a=always) '))) || '';
16
+ const a = raw.trim().toLowerCase();
17
+ if (a === 'a' || a === 'always') {
18
+ mode.setAuto(true);
19
+ console.log(c.green(' ✓ auto-accept ON for this session') + c.dim(' (Shift+Tab to turn off)'));
20
+ return true;
21
+ }
22
+ return a === 'y' || a === 'yes';
23
+ }
24
+
10
25
  // ---- Tool definitions (Anthropic tool-use format) ----
11
26
  const definitions = [
12
27
  {
@@ -87,7 +102,7 @@ async function execute(name, input) {
87
102
  console.log('');
88
103
  console.log(c.amber(' ╭─ permission ') + c.dim('─────────────────────────────'));
89
104
  console.log(c.amber(' │ ') + c.bold('Write file') + ' ' + c.white(input.path) + c.muted(` (${(input.content || '').length} chars)`));
90
- if (!(await confirm(c.amber(' ╰─ allow?')))) { console.log(c.dim(' · skipped')); return 'DENIED: user did not allow writing the file.'; }
105
+ if (!(await allow(c.amber(' ╰─ allow?')))) { console.log(c.dim(' · skipped')); return 'DENIED: user did not allow writing the file.'; }
91
106
  fs.mkdirSync(path.dirname(path.resolve(input.path)), { recursive: true });
92
107
  fs.writeFileSync(input.path, input.content);
93
108
  console.log(c.green(' ✓ wrote ') + c.white(input.path));
@@ -103,16 +118,24 @@ async function execute(name, input) {
103
118
  console.log(c.amber(' │ ') + c.bold('Edit file') + ' ' + c.white(input.path));
104
119
  console.log(c.red(' │ - ' + input.old_string.split('\n').join('\n │ - ')));
105
120
  console.log(c.green(' │ + ' + input.new_string.split('\n').join('\n │ + ')));
106
- if (!(await confirm(c.amber(' ╰─ allow?')))) { console.log(c.dim(' · skipped')); return 'DENIED: user did not allow the edit.'; }
121
+ if (!(await allow(c.amber(' ╰─ allow?')))) { console.log(c.dim(' · skipped')); return 'DENIED: user did not allow the edit.'; }
107
122
  fs.writeFileSync(input.path, before.replace(input.old_string, input.new_string));
108
123
  console.log(c.green(' ✓ edited ') + c.white(input.path));
109
124
  return 'OK: edited ' + input.path;
110
125
  }
111
126
  case 'run_command': {
127
+ const cmd = String(input.command || '');
128
+ // Refuse blocking dev servers / watchers — they never exit and freeze the session.
129
+ if (/\b(npm|pnpm|yarn|bun)\s+(run\s+)?(dev|start|serve|watch)\b|\bvite\b(?!\s+build)|\bnext\s+dev\b|\bnodemon\b|webpack-dev-server|webpack\s+serve|\bnpx\s+serve\b|\bhttp-server\b|\blive-server\b|php\s+-S|python\s+-m\s+http\.server|flask\s+run|rails\s+s(erver)?\b|\bng\s+serve\b/i.test(cmd)) {
130
+ console.log('');
131
+ console.log(c.amber(' ! ') + c.amber('skipped dev server: ') + c.white(cmd));
132
+ console.log(c.dim(' (long-running — start it yourself in another terminal when ready)'));
133
+ return 'SKIPPED: "' + cmd + '" is a long-running dev/watch server and was NOT run (it would block the session forever). Do NOT start dev servers. Finish the work, then tell the user how to run it themselves in a separate terminal, e.g. `cd <project>` then `npm run dev`, and to open the localhost URL it prints.';
134
+ }
112
135
  console.log('');
113
136
  console.log(c.red(' ╭─ permission · run command ') + c.dim('───────────────────'));
114
137
  console.log(c.red(' │ ') + c.bold(input.command));
115
- if (!(await confirm(c.red(' ╰─ run this command?')))) { console.log(c.dim(' · skipped')); return 'DENIED: user did not allow running the command.'; }
138
+ if (!(await allow(c.red(' ╰─ run this command?')))) { console.log(c.dim(' · skipped')); return 'DENIED: user did not allow running the command.'; }
116
139
  const r = spawnSync(input.command, { shell: true, encoding: 'utf8', timeout: 1000 * 120 });
117
140
  const out = (r.stdout || '') + (r.stderr || '');
118
141
  return clip(`exit_code=${r.status}\n${out}`.trim());