neon 4.12.0 → 4.13.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 +17 -5
- package/dist/analytics.js +6 -1
- package/dist/commands/ask.js +153 -0
- package/dist/commands/ask_sse.js +101 -0
- package/dist/commands/auth.js +2 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/thinking_spinner.js +47 -0
- package/dist/context.js +3 -1
- package/dist/index.js +1 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Neon CLI
|
|
2
2
|
|
|
3
|
-
The `neon` package is a command-line interface that lets you manage [Neon](https://neon.
|
|
3
|
+
The `neon` package is a command-line interface that lets you manage [Neon](https://neon.com/) — Lakebase Postgres, Object Storage, Functions, Managed Better Auth, and the AI Gateway — directly from the terminal. For the complete documentation, see [Neon CLI](https://neon.com/docs/cli).
|
|
4
4
|
|
|
5
5
|
The legacy `neonctl` package is a lightweight compatibility package that depends
|
|
6
6
|
on this package and invokes the same CLI entry point. The implementation and
|
|
@@ -54,7 +54,7 @@ Run the following command to authenticate a connection to Neon:
|
|
|
54
54
|
neon auth
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
The `auth` command launches a browser window where you can authorize the Neon CLI to access your Neon account. Running a Neon CLI command without authenticating with [neon auth](https://neon.
|
|
57
|
+
The `auth` command launches a browser window where you can authorize the Neon CLI to access your Neon account. Running a Neon CLI command without authenticating with [neon auth](https://neon.com/docs/cli/auth) automatically launches the browser authentication process.
|
|
58
58
|
|
|
59
59
|
Alternatively, you can authenticate a connection with a Neon API key using the `--api-key` option when running a Neon CLI command. For example, an API key is used with the following `neon projects list` command:
|
|
60
60
|
|
|
@@ -165,7 +165,7 @@ neon projects update <project-id> --enable-logical-replication --yes
|
|
|
165
165
|
|
|
166
166
|
### The `psql` command
|
|
167
167
|
|
|
168
|
-
`neon psql [branch]` opens a psql session against a branch. It builds the connection string for the branch and launches psql — a shortcut for `neon connection-string --psql`. See [Neon CLI commands — psql](https://neon.com/docs/
|
|
168
|
+
`neon psql [branch]` opens a psql session against a branch. It builds the connection string for the branch and launches psql — a shortcut for `neon connection-string --psql`. See [Neon CLI commands — psql](https://neon.com/docs/cli/psql) for the full reference.
|
|
169
169
|
|
|
170
170
|
```bash
|
|
171
171
|
neon psql # default branch
|
|
@@ -259,7 +259,7 @@ regression + TAP tests.
|
|
|
259
259
|
|
|
260
260
|
## Configure autocompletion
|
|
261
261
|
|
|
262
|
-
The Neon CLI supports autocompletion, which you can configure in a few easy steps. See [Neon CLI commands — completion](https://neon.
|
|
262
|
+
The Neon CLI supports autocompletion, which you can configure in a few easy steps. See [Neon CLI commands — completion](https://neon.com/docs/cli/completion) for instructions.
|
|
263
263
|
|
|
264
264
|
## Linking a project
|
|
265
265
|
|
|
@@ -806,6 +806,17 @@ On a TTY the command asks which agents and which skills, then shows a summary to
|
|
|
806
806
|
|
|
807
807
|
Supported agents match `neon mcp`, minus agents that cannot install skills: `antigravity`, `cline`, `cline-cli`, `claude-code`, `claude-desktop`, `codex`, `cursor`, `gemini-cli`, `goose`, `github-copilot-cli`, `grok-build`, `opencode`, `vscode`, `windsurf`, `zed`. `mcporter` is a known MCP name that is then skipped. `neon skills --help` lists the same skill and agent values, and that `-y` leaves out `neon-postgres-agent-platforms`.
|
|
808
808
|
|
|
809
|
+
## Ask the Neon assistant (`ask`)
|
|
810
|
+
|
|
811
|
+
`neon ask --prompt` asks the hosted Neon assistant a question about Neon. It does not log in and does not use your Neon account.
|
|
812
|
+
|
|
813
|
+
```bash
|
|
814
|
+
neon ask --prompt "How do schema-only branches work?"
|
|
815
|
+
neon ask --prompt "How do schema-only branches work?" --output json
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
Default table output is the assistant's text. On a TTY that is a spinner, then the streamed reply. `--output json` and `--output yaml` print `{ "text": "…" }` after the full response.
|
|
819
|
+
|
|
809
820
|
## Install the Neon plugin (`plugins`)
|
|
810
821
|
|
|
811
822
|
`neon plugins` installs the Neon agent plugin (`neon-postgres`) by running `npx plugins add`. It does not call the Neon API.
|
|
@@ -1234,6 +1245,7 @@ Id Name Project Created At Last Used At Last
|
|
|
1234
1245
|
| mcp | | Install the Neon MCP server |
|
|
1235
1246
|
| plugins | | Install the Neon plugin |
|
|
1236
1247
|
| skills | `update` | Install Neon agent skills |
|
|
1248
|
+
| ask | | Ask a question about Neon |
|
|
1237
1249
|
| bucket | `create`, `list`, `delete`, `object list`, `object get`, `object put`, `object delete` (incl. `--recursive`) | Manage buckets and their objects |
|
|
1238
1250
|
| [completion](https://neon.com/docs/reference/cli-completion) | | Generate a completion script |
|
|
1239
1251
|
|
|
@@ -1292,7 +1304,7 @@ Global options are supported with any Neon CLI command.
|
|
|
1292
1304
|
|
|
1293
1305
|
- <a id="analytics"></a>`--analytics`
|
|
1294
1306
|
|
|
1295
|
-
Analytics are enabled by default to gather information about the CLI commands and options that are used by our customers. This data collection assists in offering support, and allows for a better understanding of typical usage patterns so that we can improve user experience. Neon does not collect user-defined data, such as project IDs or command payloads
|
|
1307
|
+
Analytics are enabled by default to gather information about the CLI commands and options that are used by our customers. This data collection assists in offering support, and allows for a better understanding of typical usage patterns so that we can improve user experience. Neon does not collect user-defined data, such as project IDs or command payloads, except the question passed to `neon ask --prompt`. To opt-out of analytics data collection, specify `--no-analytics` or `--analytics false`.
|
|
1296
1308
|
|
|
1297
1309
|
- <a id="version"></a>`-v, --version`
|
|
1298
1310
|
|
package/dist/analytics.js
CHANGED
|
@@ -193,9 +193,14 @@ const getErrorAnalyticsEventContext = (_args) => ({
|
|
|
193
193
|
ci: isCi(),
|
|
194
194
|
agent: getCliAgent(process.env)
|
|
195
195
|
});
|
|
196
|
+
const analyticsCommand = (args) => {
|
|
197
|
+
const command = args._.join(" ");
|
|
198
|
+
if (args._[0] !== "ask" || typeof args.prompt !== "string") return command;
|
|
199
|
+
return `${command} ${args.prompt}`;
|
|
200
|
+
};
|
|
196
201
|
const getAnalyticsEventProperties = (args) => ({
|
|
197
202
|
version: pkg_default.version,
|
|
198
|
-
command: args
|
|
203
|
+
command: analyticsCommand(args),
|
|
199
204
|
flags: { output: args.output },
|
|
200
205
|
ci: isCi(),
|
|
201
206
|
agent: getCliAgent(process.env),
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
|
+
import { isCi } from "../env.js";
|
|
3
|
+
import { isNetworkError } from "../errors.js";
|
|
4
|
+
import { writer } from "../writer.js";
|
|
5
|
+
import { noPassthrough, single } from "../utils/flags.js";
|
|
6
|
+
import { isEventStreamContentType, readAskSse } from "./ask_sse.js";
|
|
7
|
+
import { createThinkingSpinner } from "./thinking_spinner.js";
|
|
8
|
+
//#region src/commands/ask.ts
|
|
9
|
+
var ask_exports = /* @__PURE__ */ __exportAll({
|
|
10
|
+
DEFAULT_ASK_URL: () => DEFAULT_ASK_URL,
|
|
11
|
+
builder: () => builder,
|
|
12
|
+
command: () => "ask",
|
|
13
|
+
describe: () => describe,
|
|
14
|
+
handler: () => handler,
|
|
15
|
+
resolveAskUrl: () => resolveAskUrl
|
|
16
|
+
});
|
|
17
|
+
const DEFAULT_ASK_URL = "https://br-frosty-cell-a5smzg39-assistant.compute.c-1.us-east-2.aws.neon.tech/ask";
|
|
18
|
+
const ASK_TIMEOUT_MS = 12e4;
|
|
19
|
+
const command = "ask";
|
|
20
|
+
const describe = "Ask a question about Neon";
|
|
21
|
+
const builder = (argv) => argv.usage("$0 ask --prompt <question>").option("prompt", {
|
|
22
|
+
describe: "The question to ask",
|
|
23
|
+
type: "string",
|
|
24
|
+
demandOption: true,
|
|
25
|
+
coerce: single("prompt", { required: true })
|
|
26
|
+
}).option("url", {
|
|
27
|
+
describe: "Override the assistant URL",
|
|
28
|
+
type: "string",
|
|
29
|
+
hidden: true,
|
|
30
|
+
coerce: single("url")
|
|
31
|
+
}).strict().check(noPassthrough("ask")).example("$0 ask --prompt \"How do schema-only branches work?\"", describe);
|
|
32
|
+
function resolveAskUrl(opts) {
|
|
33
|
+
const fromFlag = opts.url?.trim();
|
|
34
|
+
if (fromFlag) return fromFlag;
|
|
35
|
+
const fromEnv = opts.envUrl?.trim();
|
|
36
|
+
if (fromEnv) return fromEnv;
|
|
37
|
+
return DEFAULT_ASK_URL;
|
|
38
|
+
}
|
|
39
|
+
function isMachineOutput(output) {
|
|
40
|
+
return output === "json" || output === "yaml";
|
|
41
|
+
}
|
|
42
|
+
const handler = async (props) => {
|
|
43
|
+
const url = resolveAskUrl({
|
|
44
|
+
url: props.url,
|
|
45
|
+
envUrl: process.env.NEON_ASK_URL
|
|
46
|
+
});
|
|
47
|
+
const human = !isMachineOutput(props.output);
|
|
48
|
+
const spinner = createThinkingSpinner({
|
|
49
|
+
out: process.stderr,
|
|
50
|
+
isTty: Boolean(process.stderr.isTTY) && !isCi() && human
|
|
51
|
+
});
|
|
52
|
+
let streamed = "";
|
|
53
|
+
try {
|
|
54
|
+
spinner.start();
|
|
55
|
+
const text = await askAssistant({
|
|
56
|
+
prompt: props.prompt,
|
|
57
|
+
url,
|
|
58
|
+
acceptEventStream: human,
|
|
59
|
+
onStatus: (message) => {
|
|
60
|
+
spinner.setMessage(message);
|
|
61
|
+
},
|
|
62
|
+
onText: (chunk) => {
|
|
63
|
+
if (!human) return;
|
|
64
|
+
spinner.stop();
|
|
65
|
+
streamed += chunk;
|
|
66
|
+
writer(props).text(chunk);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
spinner.stop();
|
|
70
|
+
if (!human) {
|
|
71
|
+
writer(props).end({ text }, { fields: ["text"] });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (streamed === "") {
|
|
75
|
+
writer(props).text(`${text}\n`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (!streamed.endsWith("\n")) writer(props).text("\n");
|
|
79
|
+
} catch (error) {
|
|
80
|
+
spinner.stop();
|
|
81
|
+
if (human && streamed !== "" && !streamed.endsWith("\n")) writer(props).text("\n");
|
|
82
|
+
throw error;
|
|
83
|
+
} finally {
|
|
84
|
+
spinner.stop();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
function isAskTimeout(error) {
|
|
88
|
+
return error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError");
|
|
89
|
+
}
|
|
90
|
+
function askErrorMessage(body, status) {
|
|
91
|
+
if (typeof body === "object" && body !== null && "error" in body && typeof body.error === "string" && body.error.trim() !== "") return body.error;
|
|
92
|
+
return `The Neon assistant returned ${status}.`;
|
|
93
|
+
}
|
|
94
|
+
function askText(body) {
|
|
95
|
+
if (typeof body === "object" && body !== null && "text" in body && typeof body.text === "string") return body.text;
|
|
96
|
+
throw new Error("The Neon assistant returned an unexpected response.");
|
|
97
|
+
}
|
|
98
|
+
async function readJsonBody(response) {
|
|
99
|
+
const raw = await response.text();
|
|
100
|
+
if (raw.trim() === "") return;
|
|
101
|
+
try {
|
|
102
|
+
return JSON.parse(raw);
|
|
103
|
+
} catch {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function readStreamedAsk(body, opts) {
|
|
108
|
+
let text = "";
|
|
109
|
+
let sawDone = false;
|
|
110
|
+
for await (const event of readAskSse(body)) switch (event.type) {
|
|
111
|
+
case "status":
|
|
112
|
+
opts.onStatus?.(event.message);
|
|
113
|
+
break;
|
|
114
|
+
case "text":
|
|
115
|
+
text += event.text;
|
|
116
|
+
opts.onText?.(event.text);
|
|
117
|
+
break;
|
|
118
|
+
case "error": throw new Error(event.error);
|
|
119
|
+
case "done": sawDone = true;
|
|
120
|
+
}
|
|
121
|
+
if (!sawDone) throw new Error("The Neon assistant returned an unexpected response.");
|
|
122
|
+
return text;
|
|
123
|
+
}
|
|
124
|
+
async function askAssistant(opts) {
|
|
125
|
+
let response;
|
|
126
|
+
try {
|
|
127
|
+
response = await fetch(opts.url, {
|
|
128
|
+
method: "POST",
|
|
129
|
+
headers: {
|
|
130
|
+
"Content-Type": "application/json",
|
|
131
|
+
Accept: opts.acceptEventStream ? "text/event-stream" : "application/json",
|
|
132
|
+
"x-neon-source": "cli"
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify({ prompt: opts.prompt }),
|
|
135
|
+
signal: AbortSignal.timeout(ASK_TIMEOUT_MS)
|
|
136
|
+
});
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (isAskTimeout(error)) throw new Error("The Neon assistant did not respond in time.");
|
|
139
|
+
if (isNetworkError(error)) throw new Error("Could not reach the Neon assistant. Check your internet connection and try again.");
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
if (!response.ok) {
|
|
143
|
+
const body = await readJsonBody(response);
|
|
144
|
+
throw new Error(askErrorMessage(body, response.status));
|
|
145
|
+
}
|
|
146
|
+
if (opts.acceptEventStream && isEventStreamContentType(response.headers.get("content-type") ?? void 0)) {
|
|
147
|
+
if (!response.body) throw new Error("The Neon assistant returned an unexpected response.");
|
|
148
|
+
return readStreamedAsk(response.body, opts);
|
|
149
|
+
}
|
|
150
|
+
return askText(await readJsonBody(response));
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
export { DEFAULT_ASK_URL, builder, command, describe, handler, resolveAskUrl, ask_exports as t };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
//#region src/commands/ask_sse.ts
|
|
2
|
+
function isEventStreamContentType(header) {
|
|
3
|
+
return header?.split(";")[0]?.trim().toLowerCase() === "text/event-stream";
|
|
4
|
+
}
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return typeof value === "object" && value !== null;
|
|
7
|
+
}
|
|
8
|
+
function parseSseBlock(block) {
|
|
9
|
+
let event = "message";
|
|
10
|
+
const dataLines = [];
|
|
11
|
+
for (const line of block.split(/\r?\n/)) {
|
|
12
|
+
if (line === "" || line.startsWith(":")) continue;
|
|
13
|
+
if (line.startsWith("event:")) {
|
|
14
|
+
event = line.slice(6).trim();
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (line.startsWith("data:")) dataLines.push(line.slice(5).replace(/^ /, ""));
|
|
18
|
+
}
|
|
19
|
+
if (event === "ping") return;
|
|
20
|
+
if (event === "message" && dataLines.length === 0) return;
|
|
21
|
+
return {
|
|
22
|
+
event,
|
|
23
|
+
data: dataLines.join("\n")
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function takeSseBlocks(pending) {
|
|
27
|
+
const events = [];
|
|
28
|
+
let rest = pending;
|
|
29
|
+
while (true) {
|
|
30
|
+
const lf = rest.indexOf("\n\n");
|
|
31
|
+
const crlf = rest.indexOf("\r\n\r\n");
|
|
32
|
+
let at = -1;
|
|
33
|
+
let sep = 0;
|
|
34
|
+
if (lf === -1 && crlf === -1) break;
|
|
35
|
+
if (crlf !== -1 && (lf === -1 || crlf < lf)) {
|
|
36
|
+
at = crlf;
|
|
37
|
+
sep = 4;
|
|
38
|
+
} else {
|
|
39
|
+
at = lf;
|
|
40
|
+
sep = 2;
|
|
41
|
+
}
|
|
42
|
+
const parsed = parseSseBlock(rest.slice(0, at));
|
|
43
|
+
rest = rest.slice(at + sep);
|
|
44
|
+
if (parsed) events.push(parsed);
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
events,
|
|
48
|
+
rest
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function mapAskEvent(raw) {
|
|
52
|
+
if (raw.event === "done") return { type: "done" };
|
|
53
|
+
let parsed;
|
|
54
|
+
try {
|
|
55
|
+
parsed = raw.data === "" ? void 0 : JSON.parse(raw.data);
|
|
56
|
+
} catch {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (raw.event === "status") {
|
|
60
|
+
if (!isRecord(parsed) || typeof parsed.message !== "string" || parsed.message === "") return;
|
|
61
|
+
return {
|
|
62
|
+
type: "status",
|
|
63
|
+
message: parsed.message
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (raw.event === "text") {
|
|
67
|
+
if (!isRecord(parsed) || typeof parsed.text !== "string" || parsed.text === "") return;
|
|
68
|
+
return {
|
|
69
|
+
type: "text",
|
|
70
|
+
text: parsed.text
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (raw.event === "error") return {
|
|
74
|
+
type: "error",
|
|
75
|
+
error: isRecord(parsed) && typeof parsed.error === "string" && parsed.error.trim() !== "" ? parsed.error : "The assistant failed."
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function* readAskSse(body) {
|
|
79
|
+
const decoder = new TextDecoder();
|
|
80
|
+
const reader = body.getReader();
|
|
81
|
+
let pending = "";
|
|
82
|
+
try {
|
|
83
|
+
while (true) {
|
|
84
|
+
const { done, value } = await reader.read();
|
|
85
|
+
pending += decoder.decode(value ?? /* @__PURE__ */ new Uint8Array(), { stream: !done });
|
|
86
|
+
const taken = takeSseBlocks(pending);
|
|
87
|
+
pending = taken.rest;
|
|
88
|
+
for (const raw of taken.events) {
|
|
89
|
+
const event = mapAskEvent(raw);
|
|
90
|
+
if (!event) continue;
|
|
91
|
+
yield event;
|
|
92
|
+
if (event.type === "error" || event.type === "done") return;
|
|
93
|
+
}
|
|
94
|
+
if (done) break;
|
|
95
|
+
}
|
|
96
|
+
} finally {
|
|
97
|
+
reader.releaseLock();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { isEventStreamContentType, readAskSse };
|
package/dist/commands/auth.js
CHANGED
|
@@ -5,7 +5,7 @@ import { log } from "../log.js";
|
|
|
5
5
|
import { getApiClient } from "../api.js";
|
|
6
6
|
import { a as isOwnedCredentialPath } from "../_chunks/paths-DMq0Lt7a.js";
|
|
7
7
|
import { setAuthContext } from "../auth_context.js";
|
|
8
|
-
import { currentContextFile, isClaimCommand, isConfigInit, isCurrentBranchProbe, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, readContextFile } from "../context.js";
|
|
8
|
+
import { currentContextFile, isAskCommand, isClaimCommand, isConfigInit, isCurrentBranchProbe, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, readContextFile } from "../context.js";
|
|
9
9
|
import { _ as upsertProfile, d as newProfileLocation, g as selectProfileName, h as resolveProfile, i as assertValidProfileName, l as locationForName, m as readProfiles, n as KEYRING_CREDENTIALS, p as profilesUsingPath, r as assertProfilesUsable, s as isKeyringPointer, t as DEFAULT_PROFILE, u as locationOf } from "../_chunks/profiles-CvnFEQyd.js";
|
|
10
10
|
import { extendTokenSet } from "../utils/auth.js";
|
|
11
11
|
import { auth, refreshToken } from "../auth.js";
|
|
@@ -220,6 +220,7 @@ const ensureAuth = async (props) => {
|
|
|
220
220
|
if (isMcpOauth(props)) return;
|
|
221
221
|
if (props._[0] === "open") return;
|
|
222
222
|
if (isSkillsCommand(props) || isPluginsCommand(props)) return;
|
|
223
|
+
if (isAskCommand(props)) return;
|
|
223
224
|
if (props._[0] === "init") {
|
|
224
225
|
selectCredential({
|
|
225
226
|
...credentialInputs(),
|
package/dist/commands/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { t as auth_exports } from "./auth.js";
|
|
2
2
|
import { t as api_exports } from "./api.js";
|
|
3
3
|
import { t as api_keys_exports } from "./api_keys.js";
|
|
4
|
+
import { t as ask_exports } from "./ask.js";
|
|
4
5
|
import { t as bootstrap_exports } from "./bootstrap.js";
|
|
5
6
|
import { t as branches_exports } from "./branches.js";
|
|
6
7
|
import { t as bucket_exports } from "./bucket.js";
|
|
@@ -66,6 +67,7 @@ var commands_default = [
|
|
|
66
67
|
mcp_exports,
|
|
67
68
|
plugins_exports,
|
|
68
69
|
skills_exports,
|
|
70
|
+
ask_exports,
|
|
69
71
|
data_api_exports,
|
|
70
72
|
functions_exports,
|
|
71
73
|
dev_exports,
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region src/commands/thinking_spinner.ts
|
|
2
|
+
const FRAMES = [
|
|
3
|
+
"⠋",
|
|
4
|
+
"⠙",
|
|
5
|
+
"⠹",
|
|
6
|
+
"⠸",
|
|
7
|
+
"⠼",
|
|
8
|
+
"⠴",
|
|
9
|
+
"⠦",
|
|
10
|
+
"⠧",
|
|
11
|
+
"⠇",
|
|
12
|
+
"⠏"
|
|
13
|
+
];
|
|
14
|
+
const INTERVAL_MS = 80;
|
|
15
|
+
const CLEAR_LINE = "\r\x1B[2K";
|
|
16
|
+
function createThinkingSpinner(opts) {
|
|
17
|
+
let message = "Thinking";
|
|
18
|
+
let frame = 0;
|
|
19
|
+
let timer;
|
|
20
|
+
let stopped = false;
|
|
21
|
+
const paint = () => {
|
|
22
|
+
if (!opts.isTty || stopped) return;
|
|
23
|
+
const glyph = FRAMES[frame % FRAMES.length];
|
|
24
|
+
frame += 1;
|
|
25
|
+
opts.out.write(`${CLEAR_LINE}${glyph} ${message}`);
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
start() {
|
|
29
|
+
if (!opts.isTty || stopped) return;
|
|
30
|
+
paint();
|
|
31
|
+
timer = setInterval(paint, INTERVAL_MS);
|
|
32
|
+
timer.unref();
|
|
33
|
+
},
|
|
34
|
+
setMessage(next) {
|
|
35
|
+
message = next;
|
|
36
|
+
paint();
|
|
37
|
+
},
|
|
38
|
+
stop() {
|
|
39
|
+
if (stopped) return;
|
|
40
|
+
stopped = true;
|
|
41
|
+
if (timer) clearInterval(timer);
|
|
42
|
+
if (opts.isTty) opts.out.write(CLEAR_LINE);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { createThinkingSpinner };
|
package/dist/context.js
CHANGED
|
@@ -54,6 +54,7 @@ const isApiKeysCommand = (args) => args._[0] === "api-keys" || args._[0] === "ap
|
|
|
54
54
|
const isMcpCommand = (args) => args._[0] === "mcp";
|
|
55
55
|
const isSkillsCommand = (args) => args._[0] === "skills" || args._[0] === "skill";
|
|
56
56
|
const isPluginsCommand = (args) => args._[0] === "plugins" || args._[0] === "plugin";
|
|
57
|
+
const isAskCommand = (args) => args._[0] === "ask";
|
|
57
58
|
/** Raw argv is required because auth middleware runs before MCP flags are parsed. */
|
|
58
59
|
const isMcpOauth = (args) => isMcpCommand(args) && argvEnablesMcpOauth(process.argv);
|
|
59
60
|
const OAUTH_FALSE = /* @__PURE__ */ new Set([
|
|
@@ -134,6 +135,7 @@ const enrichFromContext = (args) => {
|
|
|
134
135
|
if (isProfileCommand(args)) return;
|
|
135
136
|
if (isMcpCommand(args)) return;
|
|
136
137
|
if (isSkillsCommand(args) || isPluginsCommand(args)) return;
|
|
138
|
+
if (isAskCommand(args)) return;
|
|
137
139
|
if (isClaimCommand(args)) return;
|
|
138
140
|
const context = readContextFile(args.contextFile);
|
|
139
141
|
if (!args.orgId) args.orgId = context.orgId;
|
|
@@ -240,4 +242,4 @@ const globToRegExp = (pattern) => {
|
|
|
240
242
|
return new RegExp(`^${source}$`);
|
|
241
243
|
};
|
|
242
244
|
//#endregion
|
|
243
|
-
export { applyContext, contextBranch, currentContextFile, enrichFromContext, ensureGitignored, isApiKeysCommand, isClaimCommand, isConfigInit, isCurrentBranchProbe, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, readContextFile, setContext, updateContextFile, walkContextFile };
|
|
245
|
+
export { applyContext, contextBranch, currentContextFile, enrichFromContext, ensureGitignored, isApiKeysCommand, isAskCommand, isClaimCommand, isConfigInit, isCurrentBranchProbe, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, readContextFile, setContext, updateContextFile, walkContextFile };
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.13.1",
|
|
4
4
|
"description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
"yaml": "^2.9.0",
|
|
64
64
|
"yargs": "17.7.2",
|
|
65
65
|
"yoctocolors": "^2.1.2",
|
|
66
|
-
"@neon/config": "1.1.0",
|
|
66
|
+
"@neon/config-runtime": "1.1.0",
|
|
67
67
|
"@neon/sdk": "3.0.0",
|
|
68
|
-
"@neon/config
|
|
68
|
+
"@neon/config": "1.1.0"
|
|
69
69
|
},
|
|
70
70
|
"optionalDependencies": {
|
|
71
71
|
"@napi-rs/keyring": "1.3.0",
|