novaprime 1.4.2 → 1.5.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/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.0",
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/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,7 +118,7 @@ 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;
@@ -112,7 +127,7 @@ async function execute(name, input) {
112
127
  console.log('');
113
128
  console.log(c.red(' ╭─ permission · run command ') + c.dim('───────────────────'));
114
129
  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.'; }
130
+ if (!(await allow(c.red(' ╰─ run this command?')))) { console.log(c.dim(' · skipped')); return 'DENIED: user did not allow running the command.'; }
116
131
  const r = spawnSync(input.command, { shell: true, encoding: 'utf8', timeout: 1000 * 120 });
117
132
  const out = (r.stdout || '') + (r.stderr || '');
118
133
  return clip(`exit_code=${r.status}\n${out}`.trim());