@datalabrotterdam/nova-ai-cli 1.1.0 → 1.2.0-alpha.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/README.md +165 -96
- package/dist/acp-ZYOQ6PON.js +65 -0
- package/dist/chunk-GZZ7PY3S.js +8878 -0
- package/dist/chunk-JAEXUFAS.js +451 -0
- package/dist/chunk-Q7WL3DXP.js +19 -0
- package/dist/chunk-ZKRZIF3S.js +954 -0
- package/dist/headless-ZYUSTZDL.js +567 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +155 -17
- package/dist/login-6U7QS7EA.js +118 -0
- package/dist/tui-XOKXPXN2.js +4801 -0
- package/dist/webui-L4GGMXJ3.js +9 -0
- package/package.json +24 -8
- package/src/acp/web/dist/assets/{index-D2Z0sfLR.js → index-DlCz3fmq.js} +1 -1
- package/src/acp/web/dist/index.html +1 -1
- package/dist/acp/agent.d.ts +0 -13
- package/dist/acp/agent.js +0 -242
- package/dist/acp/auth-server.d.ts +0 -10
- package/dist/acp/auth-server.js +0 -67
- package/dist/acp/credentials.d.ts +0 -11
- package/dist/acp/credentials.js +0 -30
- package/dist/acp/index.d.ts +0 -2
- package/dist/acp/index.js +0 -21
- package/dist/acp/mcp.d.ts +0 -11
- package/dist/acp/mcp.js +0 -90
- package/dist/acp/sessions.d.ts +0 -20
- package/dist/acp/sessions.js +0 -91
- package/dist/acp/tools/index.d.ts +0 -6
- package/dist/acp/tools/index.js +0 -5
- package/dist/acp/tools/marker.d.ts +0 -17
- package/dist/acp/tools/marker.js +0 -43
- package/dist/acp/tools/permission.d.ts +0 -2
- package/dist/acp/tools/permission.js +0 -20
- package/dist/acp/tools/read-file.d.ts +0 -2
- package/dist/acp/tools/read-file.js +0 -20
- package/dist/acp/tools/registry.d.ts +0 -4
- package/dist/acp/tools/registry.js +0 -10
- package/dist/acp/tools/reporting.d.ts +0 -3
- package/dist/acp/tools/reporting.js +0 -26
- package/dist/acp/tools/run-command.d.ts +0 -2
- package/dist/acp/tools/run-command.js +0 -29
- package/dist/acp/tools/system-prompt.d.ts +0 -2
- package/dist/acp/tools/system-prompt.js +0 -23
- package/dist/acp/tools/types.d.ts +0 -18
- package/dist/acp/tools/types.js +0 -1
- package/dist/acp/tools/write-file.d.ts +0 -2
- package/dist/acp/tools/write-file.js +0 -20
- package/dist/tui/index.d.ts +0 -7
- package/dist/tui/index.js +0 -11
- package/dist/webui/index.d.ts +0 -6
- package/dist/webui/index.js +0 -9
package/dist/index.js
CHANGED
|
@@ -1,25 +1,163 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import {
|
|
3
|
+
readPackageVersion
|
|
4
|
+
} from "./chunk-Q7WL3DXP.js";
|
|
5
|
+
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
var SUBCOMMANDS = {
|
|
8
|
+
login: "login",
|
|
9
|
+
logout: "logout",
|
|
10
|
+
help: "help",
|
|
11
|
+
version: "version"
|
|
12
|
+
};
|
|
13
|
+
var MODE_FLAGS = {
|
|
14
|
+
"--acp": "acp",
|
|
15
|
+
"-p": "print",
|
|
16
|
+
"--print": "print",
|
|
17
|
+
"--headless": "print",
|
|
18
|
+
"--setup": "login",
|
|
19
|
+
"--version": "version",
|
|
20
|
+
"-v": "version",
|
|
21
|
+
"--web": "web"
|
|
22
|
+
};
|
|
23
|
+
function parseCli(argv) {
|
|
24
|
+
const first = argv[0];
|
|
25
|
+
if (first !== void 0 && SUBCOMMANDS[first]) {
|
|
26
|
+
return { mode: SUBCOMMANDS[first], args: argv.slice(1) };
|
|
27
|
+
}
|
|
28
|
+
const end = argv.indexOf("--");
|
|
29
|
+
const options = end >= 0 ? argv.slice(0, end) : argv;
|
|
30
|
+
const modes = new Set(options.flatMap((arg) => MODE_FLAGS[arg] ? [MODE_FLAGS[arg]] : []));
|
|
31
|
+
if (modes.size > 1) {
|
|
32
|
+
return { mode: "error", message: `Choose one mode, not ${[...modes].join(" and ")}.` };
|
|
33
|
+
}
|
|
34
|
+
const mode = [...modes][0];
|
|
35
|
+
const wantsHelp = options.includes("--help") || options.includes("-h");
|
|
36
|
+
if (!mode) return wantsHelp ? { mode: "help", args: [] } : { mode: "tui", args: argv };
|
|
37
|
+
const drop = /* @__PURE__ */ new Set(["--acp", "--headless", "--setup", "--version", "-v", "--web"]);
|
|
38
|
+
return { mode, args: argv.filter((arg, index) => !(drop.has(arg) && (end < 0 || index < end))) };
|
|
39
|
+
}
|
|
40
|
+
function versionText() {
|
|
41
|
+
return `nova-ai ${readPackageVersion() ?? "unknown"}`;
|
|
42
|
+
}
|
|
43
|
+
var MAIN_HELP = `Nova AI \u2014 agentic coding in your terminal, and an ACP agent for editors.
|
|
44
|
+
|
|
45
|
+
Usage:
|
|
46
|
+
nova-ai [options] Interactive terminal UI
|
|
47
|
+
nova-ai -p [options] [prompt] Run one request headless (see nova-ai -p --help)
|
|
48
|
+
nova-ai --acp Agent Client Protocol over stdio (for editors)
|
|
49
|
+
nova-ai login [--no-browser] Connect your Nova AI account
|
|
50
|
+
nova-ai logout Remove the stored API key
|
|
51
|
+
|
|
52
|
+
Interactive options:
|
|
53
|
+
--resume <session-id> Continue a saved session
|
|
54
|
+
-c, --continue Continue the most recent session in this folder
|
|
55
|
+
--model <id> Use this model
|
|
56
|
+
|
|
57
|
+
General:
|
|
58
|
+
-h, --help Show help
|
|
59
|
+
-v, --version Show the version
|
|
60
|
+
|
|
61
|
+
Environment:
|
|
62
|
+
NOVA_API_KEY, NOVA_MODEL Use this key / model instead of the stored ones
|
|
63
|
+
NOVA_BASE_URL Use another Nova gateway
|
|
64
|
+
NOVA_AI_HOME Settings folder (default ~/.nova-ai)
|
|
65
|
+
NOVA_TOOL_PROTOCOL Force "native" or "text" tool calling`;
|
|
66
|
+
function parseTuiArgs(args) {
|
|
67
|
+
const options = { resume: null, continueLast: false, model: null };
|
|
68
|
+
for (let index = 0; index < args.length; index++) {
|
|
69
|
+
const arg = args[index];
|
|
70
|
+
const value = (name) => {
|
|
71
|
+
const next = args[++index];
|
|
72
|
+
if (!next || next.startsWith("-")) throw new Error(`${name} requires a value.`);
|
|
73
|
+
return next;
|
|
74
|
+
};
|
|
75
|
+
if (arg === "--resume") options.resume = value(arg);
|
|
76
|
+
else if (arg.startsWith("--resume=")) options.resume = arg.slice("--resume=".length);
|
|
77
|
+
else if (arg === "--continue" || arg === "-c") options.continueLast = true;
|
|
78
|
+
else if (arg === "--model") options.model = value(arg);
|
|
79
|
+
else if (arg.startsWith("--model=")) options.model = arg.slice("--model=".length);
|
|
80
|
+
else throw new Error(`Unknown option for the interactive UI: ${arg}`);
|
|
81
|
+
}
|
|
82
|
+
if (options.resume && options.continueLast) {
|
|
83
|
+
throw new Error("Use either --continue or --resume, not both.");
|
|
84
|
+
}
|
|
85
|
+
return options;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/index.ts
|
|
6
89
|
async function main() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
90
|
+
const command = parseCli(process.argv.slice(2));
|
|
91
|
+
switch (command.mode) {
|
|
92
|
+
case "error":
|
|
93
|
+
process.stderr.write(`${command.message}
|
|
94
|
+
|
|
95
|
+
${MAIN_HELP}
|
|
96
|
+
`);
|
|
97
|
+
return 2;
|
|
98
|
+
case "help":
|
|
99
|
+
process.stdout.write(`${MAIN_HELP}
|
|
100
|
+
`);
|
|
101
|
+
return 0;
|
|
102
|
+
case "version":
|
|
103
|
+
process.stdout.write(`${versionText()}
|
|
104
|
+
`);
|
|
105
|
+
return 0;
|
|
106
|
+
case "acp": {
|
|
107
|
+
if (command.args.length) {
|
|
108
|
+
process.stderr.write(`--acp takes no other arguments (got ${command.args.join(" ")}).
|
|
109
|
+
`);
|
|
110
|
+
return 2;
|
|
111
|
+
}
|
|
112
|
+
const { default: runAcp } = await import("./acp-ZYOQ6PON.js");
|
|
113
|
+
await runAcp();
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
case "print": {
|
|
117
|
+
const { default: runHeadless } = await import("./headless-ZYUSTZDL.js");
|
|
118
|
+
return runHeadless(command.args);
|
|
119
|
+
}
|
|
120
|
+
case "login": {
|
|
121
|
+
const { runLogin } = await import("./login-6U7QS7EA.js");
|
|
122
|
+
return runLogin(command.args);
|
|
123
|
+
}
|
|
124
|
+
case "logout": {
|
|
125
|
+
const { runLogout } = await import("./login-6U7QS7EA.js");
|
|
126
|
+
return runLogout();
|
|
11
127
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
128
|
+
case "web": {
|
|
129
|
+
const { default: runWebUi } = await import("./webui-L4GGMXJ3.js");
|
|
130
|
+
await runWebUi();
|
|
131
|
+
return 0;
|
|
15
132
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
133
|
+
case "tui": {
|
|
134
|
+
let options;
|
|
135
|
+
try {
|
|
136
|
+
options = parseTuiArgs(command.args);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
process.stderr.write(`${error.message}
|
|
139
|
+
|
|
140
|
+
${MAIN_HELP}
|
|
141
|
+
`);
|
|
142
|
+
return 2;
|
|
143
|
+
}
|
|
144
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
145
|
+
process.stderr.write(
|
|
146
|
+
'nova-ai needs an interactive terminal. For scripts and pipes use `nova-ai -p "<prompt>"`, for editors `nova-ai --acp`.\n'
|
|
147
|
+
);
|
|
148
|
+
return 2;
|
|
149
|
+
}
|
|
150
|
+
const { default: runChat } = await import("./tui-XOKXPXN2.js");
|
|
151
|
+
return runChat(options);
|
|
19
152
|
}
|
|
20
|
-
|
|
153
|
+
}
|
|
21
154
|
}
|
|
22
|
-
main().
|
|
155
|
+
main().then(
|
|
156
|
+
(code) => {
|
|
157
|
+
process.exitCode = code;
|
|
158
|
+
},
|
|
159
|
+
(err) => {
|
|
23
160
|
console.error(err);
|
|
24
161
|
process.exit(1);
|
|
25
|
-
}
|
|
162
|
+
}
|
|
163
|
+
);
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {
|
|
2
|
+
credentialsPath,
|
|
3
|
+
deleteCredentials,
|
|
4
|
+
runBrowserAuth,
|
|
5
|
+
validateApiKey,
|
|
6
|
+
writeCredentials
|
|
7
|
+
} from "./chunk-JAEXUFAS.js";
|
|
8
|
+
|
|
9
|
+
// src/commands/login.ts
|
|
10
|
+
import { NovaAIError } from "@datalabrotterdam/nova-sdk";
|
|
11
|
+
var defaultIo = () => ({ stdin: process.stdin, stdout: process.stdout, stderr: process.stderr });
|
|
12
|
+
var LOGIN_HELP = `Usage: nova-ai login [--no-browser]
|
|
13
|
+
|
|
14
|
+
Connects nova-ai to your Nova AI account and stores the API key in
|
|
15
|
+
${"~"}/.nova-ai/credentials.json (readable only by you).
|
|
16
|
+
|
|
17
|
+
(default) Opens a local page in your browser to enter the key
|
|
18
|
+
--no-browser Asks for the key in this terminal (input is hidden)
|
|
19
|
+
piped stdin Reads the key from stdin, e.g. echo "$KEY" | nova-ai login
|
|
20
|
+
|
|
21
|
+
Get a key at https://platform.nova.datalabrotterdam.nl/dashboard/api-keys`;
|
|
22
|
+
async function runLogin(args, io = defaultIo()) {
|
|
23
|
+
const unknown = args.filter((arg) => arg !== "--no-browser" && arg !== "--help" && arg !== "-h");
|
|
24
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
25
|
+
io.stdout.write(`${LOGIN_HELP}
|
|
26
|
+
`);
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
if (unknown.length) {
|
|
30
|
+
io.stderr.write(`Unknown option: ${unknown[0]}
|
|
31
|
+
|
|
32
|
+
${LOGIN_HELP}
|
|
33
|
+
`);
|
|
34
|
+
return 2;
|
|
35
|
+
}
|
|
36
|
+
if (!io.stdin.isTTY) {
|
|
37
|
+
return storeKey((await readAll(io.stdin)).trim(), io);
|
|
38
|
+
}
|
|
39
|
+
if (args.includes("--no-browser")) {
|
|
40
|
+
return storeKey((await readHidden(io, "Nova API key: ")).trim(), io);
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
await runBrowserAuth();
|
|
44
|
+
io.stdout.write(`Logged in. The key is stored in ${credentialsPath()}.
|
|
45
|
+
`);
|
|
46
|
+
return 0;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
io.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
49
|
+
`);
|
|
50
|
+
io.stderr.write("Tip: run `nova-ai login --no-browser` to enter the key in this terminal.\n");
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function runLogout(io = defaultIo()) {
|
|
55
|
+
const removed = deleteCredentials();
|
|
56
|
+
io.stdout.write(removed ? "Logged out: the stored API key was removed.\n" : "No stored API key to remove.\n");
|
|
57
|
+
if (process.env.NOVA_API_KEY) {
|
|
58
|
+
io.stdout.write("Note: NOVA_API_KEY is set in your environment and still logs you in.\n");
|
|
59
|
+
}
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
async function storeKey(apiKey, io) {
|
|
63
|
+
if (!apiKey) {
|
|
64
|
+
io.stderr.write("No API key given.\n");
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
writeCredentials(await validateApiKey(apiKey));
|
|
69
|
+
} catch (error) {
|
|
70
|
+
io.stderr.write(
|
|
71
|
+
error instanceof NovaAIError ? `Nova AI rejected this key (status ${error.status}): ${error.message}
|
|
72
|
+
` : `Could not validate the key: ${error instanceof Error ? error.message : String(error)}
|
|
73
|
+
`
|
|
74
|
+
);
|
|
75
|
+
return 1;
|
|
76
|
+
}
|
|
77
|
+
io.stdout.write(`Logged in. The key is stored in ${credentialsPath()}.
|
|
78
|
+
`);
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
async function readAll(stream) {
|
|
82
|
+
const chunks = [];
|
|
83
|
+
for await (const chunk of stream) chunks.push(Buffer.from(chunk));
|
|
84
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
85
|
+
}
|
|
86
|
+
function readHidden(io, prompt) {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const { stdin } = io;
|
|
89
|
+
io.stdout.write(prompt);
|
|
90
|
+
let value = "";
|
|
91
|
+
const wasRaw = stdin.isRaw;
|
|
92
|
+
stdin.setRawMode(true);
|
|
93
|
+
stdin.resume();
|
|
94
|
+
stdin.setEncoding("utf8");
|
|
95
|
+
const finish = (error) => {
|
|
96
|
+
stdin.setRawMode(wasRaw);
|
|
97
|
+
stdin.pause();
|
|
98
|
+
stdin.removeListener("data", onData);
|
|
99
|
+
io.stdout.write("\n");
|
|
100
|
+
if (error) reject(error);
|
|
101
|
+
else resolve(value);
|
|
102
|
+
};
|
|
103
|
+
const onData = (chunk) => {
|
|
104
|
+
for (const char of chunk) {
|
|
105
|
+
if (char === "\r" || char === "\n") return finish();
|
|
106
|
+
if (char === "") return finish(new Error("Login cancelled."));
|
|
107
|
+
if (char === "\x7F" || char === "\b") value = value.slice(0, -1);
|
|
108
|
+
else if (char >= " ") value += char;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
stdin.on("data", onData);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
export {
|
|
115
|
+
LOGIN_HELP,
|
|
116
|
+
runLogin,
|
|
117
|
+
runLogout
|
|
118
|
+
};
|