@synqa/cli 1.1.0 → 1.1.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 +83 -53
- package/dist/api.js +44 -3
- package/dist/commands.js +6 -13
- package/dist/main.js +146 -40
- package/dist/paths.js +150 -38
- package/dist/prompt.js +330 -0
- package/dist/statusbar.js +16 -9
- package/dist/theme.js +47 -7
- package/dist/tools.js +125 -44
- package/dist/ui.js +129 -69
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Blink 5.5, Sonata 6.0 and Aeon 6.5.
|
|
|
12
12
|
- [Slash commands](#slash-commands)
|
|
13
13
|
- [Welcome panel and turn footer](#welcome-panel-and-turn-footer)
|
|
14
14
|
- [Diff preview](#diff-preview)
|
|
15
|
-
- [Status
|
|
15
|
+
- [Status line](#status-line)
|
|
16
16
|
- [AGENTS.md and /init](#agentsmd-and-init)
|
|
17
17
|
- [Undo](#undo)
|
|
18
18
|
- [Settings and environment](#settings-and-environment)
|
|
@@ -22,7 +22,7 @@ Blink 5.5, Sonata 6.0 and Aeon 6.5.
|
|
|
22
22
|
|
|
23
23
|
## Install
|
|
24
24
|
|
|
25
|
-
Needs Node.js 22.18 or newer (or 23.6 or newer) and npm. There are no other dependencies.
|
|
25
|
+
Needs Node.js 22.18 or newer (or 23.6 or newer) and npm. There are no other dependencies.
|
|
26
26
|
|
|
27
27
|
Try it without installing:
|
|
28
28
|
|
|
@@ -65,7 +65,7 @@ Or skip login and set `SYNQA_API_KEY` in your shell. The variable wins over the
|
|
|
65
65
|
|
|
66
66
|
- `synqa` : interactive session in the current folder.
|
|
67
67
|
- `synqa "fix the failing test"` : one task, then exit.
|
|
68
|
-
- `synqa --yes "..."` :
|
|
68
|
+
- `synqa --yes "..."` : approve ordinary file writes and edits inside the folder without asking. **Shell commands always ask, even with `--yes`** (any file the agent reads could carry an injected command), and `--yes` is not allowed in your home folder
|
|
69
69
|
(text in a README or a web page can steer the model). Some things still ask, and are denied when nobody can answer:
|
|
70
70
|
writes that can run code later (git hooks and other `.git` files, `package.json`, Makefiles, `.envrc`, `.vscode`
|
|
71
71
|
settings, CI workflows, shell startup files, dotfiles, executables), writes to secrets files, every write and
|
|
@@ -85,46 +85,62 @@ Or skip login and set `SYNQA_API_KEY` in your shell. The variable wins over the
|
|
|
85
85
|
| `/compact` | Ask the model to summarize the conversation and continue from the summary, to save context. |
|
|
86
86
|
| `/undo` | Revert the last file the agent wrote. Run it again to go further back. |
|
|
87
87
|
| `/clear` | Start a fresh conversation (reloads `AGENTS.md`). |
|
|
88
|
-
| `/help` | List the commands. |
|
|
88
|
+
| `/help` | List the commands, grouped, with the keys of the command menu. |
|
|
89
89
|
| `/exit` | Leave (also `/quit`, or Ctrl-D). |
|
|
90
90
|
|
|
91
91
|
`/status` reads your balance from the Synqa account service with your key. If the service does not
|
|
92
92
|
answer, it says so and shows everything else; your balance is always on the
|
|
93
93
|
[dashboard](https://synqa.ca/client-dashboard).
|
|
94
94
|
|
|
95
|
-
##
|
|
95
|
+
## Start screen, prompt and turn footer
|
|
96
96
|
|
|
97
|
-
In an interactive terminal the session opens with
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
In an interactive terminal the session opens with the Synqa wordmark (on a window of 60 columns or
|
|
98
|
+
more; a one-line bold `Synqa.` below that), then dim lines with the CLI version, tier and effort, and
|
|
99
|
+
the workspace folder (your home folder shown as `~`), then short `⎿` notes: whether `AGENTS.md`
|
|
100
|
+
project notes were loaded, and your credit and weekly pool balance from the Synqa account service.
|
|
101
|
+
The balance lookup waits at most 2 seconds; if it fails the note says `balance unavailable`, and
|
|
102
|
+
`balance not available yet` when the service does not offer it to API keys yet. Your key is never shown.
|
|
102
103
|
|
|
103
104
|
```
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
▄▀▀▀
|
|
106
|
+
▀▀▄ █ █ █▀▀▄ ▄▀▀█ ▄▀▀█
|
|
107
|
+
▄▄▄▀ ▀▄▄█ █ █ ▀▄▄█ ▀▄▄█ ▄
|
|
108
|
+
▄▄▄▀ █
|
|
109
|
+
|
|
110
|
+
v1.1.1 · Blink 5.5 · default effort
|
|
111
|
+
~/synqa-test
|
|
112
|
+
⎿ no AGENTS.md yet: /init writes one
|
|
113
|
+
⎿ $9.98 credit · weekly pool $4.00 of $10.00
|
|
114
|
+
───────────────────────────────────────────────────────────────────────────────
|
|
115
|
+
❯ Try "explain this project", or type / for commands
|
|
116
|
+
───────────────────────────────────────────────────────────────────────────────
|
|
117
|
+
Blink 5.5 · synqa-test · 0 tokens $9.98 credit
|
|
118
|
+
/ commands · ↑↓ history · tab complete · ctrl+c quit
|
|
110
119
|
```
|
|
111
120
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
The letters are drawn in your terminal's own text colour, so they read as ink on a light theme and
|
|
122
|
+
light on a dark one; the full stop is terracotta. The input sits between two thin rules, with a dim
|
|
123
|
+
hint while it is empty. Under it the status line shows only what is known (see below) and a line of
|
|
124
|
+
key hints.
|
|
125
|
+
|
|
126
|
+
Type `/` and a menu of commands opens under the input, filtered as you type, with a short
|
|
127
|
+
description of each. Up and Down move the highlight, Tab or Enter completes it (Enter runs a command
|
|
128
|
+
that takes no argument), Esc closes the menu. An unknown command gets a one-line hint with the
|
|
129
|
+
nearest name. The menu is drawn with Node's own readline key events, no extra package.
|
|
130
|
+
|
|
131
|
+
On a terminal that is not UTF-8 (or `TERM=dumb`, or a very old Windows console) everything is plain
|
|
132
|
+
ASCII and there is no menu or frame: the status is printed above a plain `>` prompt.
|
|
133
|
+
|
|
134
|
+
Colours: terracotta for the full stop, spinner, `❯` prompt, tier name and the selected menu row;
|
|
135
|
+
the terminal's own text colour (bold) for names, or warm bone when the terminal reports a dark
|
|
136
|
+
background (`COLORFGBG`); warm stone for rules and labels; sage for the balance and added diff lines
|
|
137
|
+
(removed lines stay red). 24-bit colour is used only where it is known to work: `COLORTERM` is
|
|
138
|
+
`truecolor` or `24bit`, or `TERM_PROGRAM` is iTerm2, WezTerm, VS Code, Ghostty and similar. macOS
|
|
139
|
+
Terminal.app (`TERM_PROGRAM=Apple_Terminal`) and unknown terminals get the 256 standard colours, with
|
|
140
|
+
terracotta mapped to colour 166 so it stays burnt orange; Terminal.app gets them even when a
|
|
141
|
+
`COLORTERM` was copied into the shell profile, because it misdraws 24-bit escapes. Every line is
|
|
142
|
+
measured by its visible width (colour codes not counted, wide characters counted as two) and kept
|
|
143
|
+
one column short of the window, so nothing wraps.
|
|
128
144
|
|
|
129
145
|
While the agent waits for the first token a spinner with the elapsed seconds is shown (`⠋ waiting 1.2s`)
|
|
130
146
|
and is erased when output starts. Each tool call is drawn as a card: a header with the tool and its
|
|
@@ -143,10 +159,10 @@ file or command, the diff preview for edits inside it, and a one line result.
|
|
|
143
159
|
After every turn a dim footer line stays in the scrollback: time to first token, output speed,
|
|
144
160
|
routing (`local` or `hybrid`) and your credit left (only when the balance is known).
|
|
145
161
|
|
|
146
|
-
The
|
|
162
|
+
The start screen, spinner, cards and footer degrade on their own: with `NO_COLOR` they have no colour; when
|
|
147
163
|
output is not a terminal (a pipe, a file, a one-shot task) there is no spinner, no cursor control and
|
|
148
164
|
the old plain output; when the terminal is not UTF-8, is `TERM=dumb`, or is the old Windows console
|
|
149
|
-
(Windows Terminal is fine), the
|
|
165
|
+
(Windows Terminal is fine), the start screen, cards and spinner use plain ASCII (`+-`, `| / - \`). Long paths are cut with an
|
|
150
166
|
ellipsis to fit narrow windows. Text from the model or from tools never reaches the terminal as
|
|
151
167
|
control sequences.
|
|
152
168
|
|
|
@@ -161,24 +177,26 @@ shell startup files, other dotfiles): those are always shown in full.
|
|
|
161
177
|
Colour is off when output is not a terminal or when `NO_COLOR` is set; the diff is then plain `+`/`-`
|
|
162
178
|
text. Control characters in file content are shown as `\x1b`-style escapes, never sent to your terminal.
|
|
163
179
|
|
|
164
|
-
## Status
|
|
180
|
+
## Status line
|
|
165
181
|
|
|
166
|
-
In an interactive terminal the
|
|
182
|
+
In an interactive terminal the line under the input shows only values that are known:
|
|
167
183
|
|
|
168
184
|
```
|
|
169
|
-
Blink 5.5
|
|
185
|
+
Blink 5.5 · low effort · synqa-test · ctx 10% · hybrid · 1,234 tokens $9.98 credit
|
|
170
186
|
```
|
|
171
187
|
|
|
172
|
-
- Tier display name (`Blink 5.5`, `Sonata 6.0` or `Aeon 6.5`), plus `effort
|
|
188
|
+
- Tier display name (`Blink 5.5`, `Sonata 6.0` or `Aeon 6.5`), plus `low effort` or `medium effort` when set.
|
|
189
|
+
- The workspace folder name.
|
|
190
|
+
- `ctx`: how much of the context window the last request used (after the first request).
|
|
173
191
|
- Routing: `local` or `hybrid`, read from the `x-synqa-routing` response header of the last request
|
|
174
|
-
(`fallback` for a fallback route
|
|
175
|
-
- TTFT: seconds from sending the request to the first token. tok/s: output tokens per second of
|
|
176
|
-
generation over the last turn. A dash means the number was not reported.
|
|
192
|
+
(`fallback` for a fallback route); left out when the server did not say.
|
|
177
193
|
- Session tokens: input plus output so far.
|
|
194
|
+
- On the right, your credit when the balance is known.
|
|
178
195
|
|
|
179
|
-
On a narrow terminal the least important parts drop off first.
|
|
180
|
-
|
|
181
|
-
|
|
196
|
+
On a narrow terminal the least important parts drop off first. Time to first token and output speed
|
|
197
|
+
are in the footer after each turn. When the output is not a terminal (a pipe, a file, `TERM=dumb`)
|
|
198
|
+
a plain line (`Blink 5.5 | local | TTFT 0.42s | 87.3 tok/s | 1,234 tokens`, unknown parts left out)
|
|
199
|
+
is printed above each prompt, and after a one-shot task.
|
|
182
200
|
|
|
183
201
|
## AGENTS.md and /init
|
|
184
202
|
|
|
@@ -203,7 +221,7 @@ it. Use git for that.
|
|
|
203
221
|
|
|
204
222
|
| Name | Meaning |
|
|
205
223
|
|---|---|
|
|
206
|
-
| `SYNQA_API_KEY` | Your key; wins over the saved one. Never passed to commands the agent runs. |
|
|
224
|
+
| `SYNQA_API_KEY` | Your key; wins over the saved one (the start screen says so, and `synqa login` warns when it differs from the key you just saved). Never passed to commands the agent runs. |
|
|
207
225
|
| `NO_COLOR` | Any non-empty value turns colour off. |
|
|
208
226
|
| `~/.config/synqa/config.json` | Saved key and default tier (mode 0600). |
|
|
209
227
|
|
|
@@ -211,7 +229,7 @@ it. Use git for that.
|
|
|
211
229
|
|
|
212
230
|
- File tools only touch files inside the folder you started in (`..`, absolute paths and symlinks
|
|
213
231
|
out of it are refused).
|
|
214
|
-
- Writes
|
|
232
|
+
- Writes and edits ask `y/N` first, also in one-shot mode, unless `--yes`; shell commands always ask. Every question shows the whole diff (only the display after a `--yes` approval is cut). Commands run without credential-looking environment variables (tokens, secrets, keys and passwords by name, `AWS_*` except region and profile, git askpass helpers, connection strings with a password in them, the API key, and `SSH_AUTH_SOCK`: run `git push` over SSH in your own terminal). `grep` stops a pattern that runs longer than 10 seconds. Without a
|
|
215
233
|
terminal the answer is no.
|
|
216
234
|
- Shell commands are not confined to the folder: read each one before you answer.
|
|
217
235
|
- Writes to `.git/hooks`, `package.json`, shell startup files and other dotfiles carry a warning, because
|
|
@@ -220,21 +238,33 @@ it. Use git for that.
|
|
|
220
238
|
delimited block as conventions only, you are told when it is loaded, and while it is loaded shell
|
|
221
239
|
commands always ask first, even with `--yes`.
|
|
222
240
|
- Files that hold secrets (`.env` and `.env.*` except examples, `.envrc`, `*.pem`, `*.key`, `*.tfvars`, `id_*`
|
|
223
|
-
private keys, `keys.json`, `credentials.json`, `secrets.*`, `.ssh/`, `.aws/`, `.gnupg/`, `.kube
|
|
224
|
-
`.docker/config.json`, `.netrc`, `.npmrc`, `.pypirc`, `.git-credentials`, `.dev.vars`,
|
|
225
|
-
|
|
241
|
+
private keys, `keys.json`, `credentials.json`, `secrets.*`, `.ssh/`, `.aws/`, `.gnupg/`, `.kube/`,
|
|
242
|
+
`.docker/config.json`, `.netrc`, `.npmrc`, `.pypirc`, `.git-credentials`, `.dev.vars`, `.config/gh`,
|
|
243
|
+
`.config/gcloud`, `.azure/`, `.password-store/`, macOS keychains (`Library/Keychains`, `*.keychain-db`),
|
|
244
|
+
browser profiles (Chrome, Chromium, Brave, Edge, Arc, Firefox, Safari, cookies and saved logins), this
|
|
245
|
+
CLI's own config) are never read, written or edited by the agent, with or without `--yes`, and are
|
|
246
|
+
skipped by `grep` and `glob`. A shell command that names one always asks first, even with `--yes`.
|
|
226
247
|
Names match without regard to case or full-width letters, as on a case-insensitive Mac.
|
|
227
|
-
- The CLI
|
|
228
|
-
|
|
248
|
+
- The CLI starts in any folder, your home folder included, like other coding CLIs. Inside your home
|
|
249
|
+
folder everything under a dot entry at its top (`~/.zsh_history`, `~/.config/...`, `~/.cargo/...`,
|
|
250
|
+
any tool's settings) and everything under `~/Library` is protected as a rule, whether or not it is
|
|
251
|
+
named above; your projects' own dot files (`~/proj/.github`) are not affected. `--yes` is refused
|
|
252
|
+
in the home folder itself. Writes to launchd agents, autostart entries, systemd units, fish and
|
|
253
|
+
other shell startup files and `~/.local/bin` always ask. Only the filesystem root (`/`) is refused
|
|
254
|
+
as a start folder. `--allow-home` is still accepted and does nothing.
|
|
229
255
|
- Your key is never printed, never put on a command line, and removed from the environment of every
|
|
230
256
|
command the agent runs.
|
|
231
257
|
|
|
232
258
|
## Troubleshooting
|
|
233
259
|
|
|
234
260
|
- `No API key`: run `synqa login` or set `SYNQA_API_KEY`.
|
|
235
|
-
- `
|
|
261
|
+
- `Your API key was rejected`: run `synqa login` again, or check on the dashboard that the key is
|
|
262
|
+
active. If `SYNQA_API_KEY` is set in your shell, that key is used instead of the saved one: remove it
|
|
263
|
+
(`unset SYNQA_API_KEY`, and the line in `~/.zshrc`).
|
|
264
|
+
- `synqa login` checks the key against the same key check the chat uses, and refuses a key with no
|
|
265
|
+
chat access.
|
|
236
266
|
- `request timed out`: a request is cut off after 10 minutes.
|
|
237
|
-
-
|
|
267
|
+
- Stray characters or a broken layout in Terminal.app: update to 1.1.1, which uses 256 colours there.
|
|
238
268
|
- No colour: check `NO_COLOR` and that stdout is a terminal.
|
|
239
269
|
|
|
240
270
|
## Develop
|
package/dist/api.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { API_BASE } from "./config.js";
|
|
1
|
+
import { API_BASE, DASHBOARD_URL } from "./config.js";
|
|
2
|
+
import { sanitize } from "./sanitize.js";
|
|
2
3
|
import { REQUEST_TIMEOUT_MS, MAX_OUTPUT_TOKENS, usageFrom } from "./tiers.js";
|
|
3
4
|
|
|
4
5
|
|
|
@@ -90,9 +91,48 @@ export class ApiError extends Error {
|
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
export const KEY_REJECTED = `Your API key was rejected. Run \`synqa login\` again, or check the key is active in your Synqa dashboard (${DASHBOARD_URL}).`;
|
|
95
|
+
|
|
96
|
+
// The gateway's short reason ({"detail": "..."}), already white-labelled by the server; sanitized
|
|
97
|
+
// and cut here because it is network text.
|
|
98
|
+
async function detailOf(res ) {
|
|
99
|
+
try {
|
|
100
|
+
const text = (await res.text()).slice(0, 4000);
|
|
101
|
+
const v = JSON.parse(text) ;
|
|
102
|
+
const d = typeof v.detail === "string" ? v.detail : typeof v.error?.message === "string" ? v.error.message : "";
|
|
103
|
+
return sanitize(d).replace(/\s+/g, " ").trim().slice(0, 200);
|
|
104
|
+
} catch {
|
|
105
|
+
return "";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// A plain sentence for a failed request, by status. Never the raw body.
|
|
110
|
+
export function friendlyStatus(status , detail = "") {
|
|
111
|
+
const why = detail ? ` (${detail})` : "";
|
|
112
|
+
if (status === 401) return KEY_REJECTED;
|
|
113
|
+
if (status === 402) return `Your Synqa balance is used up or your plan is not active${why}. Top up in your Synqa dashboard (${DASHBOARD_URL}).`;
|
|
114
|
+
if (status === 403) return `This key is not allowed to do that${why}. Check the key's access in your Synqa dashboard (${DASHBOARD_URL}).`;
|
|
115
|
+
if (status === 404) return "The Synqa service did not recognise this request (404). Update the CLI: npm i -g @synqa/cli";
|
|
116
|
+
if (status === 413) return `The conversation is too large to send${why}. Run /compact or /clear and try again.`;
|
|
117
|
+
if (status === 429) return `Rate limit reached${why}. Wait a moment and try again.`;
|
|
118
|
+
if (status === 503) return `Synqa is busy or paused right now${why}. Try again in a minute.`;
|
|
119
|
+
if (status >= 500) return `The Synqa service had a problem (status ${status}). Try again in a minute.`;
|
|
120
|
+
return `The request failed with status ${status}${why}.`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Login check: GET /v1/models goes through the same key check as chat (the gateway's caller_key),
|
|
124
|
+
// and the list is empty for a key without the chat capability, so an empty list is refused too.
|
|
93
125
|
export async function checkKey(apiKey , fetchFn = fetch) {
|
|
94
126
|
const res = await fetchFn(`${API_BASE}/models`, { headers: { authorization: `Bearer ${apiKey}` } });
|
|
95
|
-
if (!res.ok) throw new ApiError(res.status, res.status === 401
|
|
127
|
+
if (!res.ok) throw new ApiError(res.status, res.status === 401 ? "the key was rejected" : friendlyStatus(res.status, await detailOf(res)));
|
|
128
|
+
let ids = [];
|
|
129
|
+
try {
|
|
130
|
+
const body = (await res.json()) ;
|
|
131
|
+
ids = Array.isArray(body.data) ? body.data : [];
|
|
132
|
+
} catch {
|
|
133
|
+
throw new ApiError(res.status, "the service answer was not understood");
|
|
134
|
+
}
|
|
135
|
+
if (ids.length === 0) throw new ApiError(403, "this key has no chat access; check it in your Synqa dashboard");
|
|
96
136
|
}
|
|
97
137
|
|
|
98
138
|
|
|
@@ -128,7 +168,8 @@ export async function chat(o ) {
|
|
|
128
168
|
}),
|
|
129
169
|
signal: ac.signal,
|
|
130
170
|
});
|
|
131
|
-
if (!res.ok
|
|
171
|
+
if (!res.ok) throw new ApiError(res.status, friendlyStatus(res.status, res.status === 401 ? "" : await detailOf(res)));
|
|
172
|
+
if (!res.body) throw new ApiError(res.status, "the service sent an empty answer");
|
|
132
173
|
const acc = new StreamAccumulator();
|
|
133
174
|
let firstAt ;
|
|
134
175
|
for await (const ev of sseEvents(res.body )) {
|
package/dist/commands.js
CHANGED
|
@@ -15,6 +15,8 @@ import { groupDigits } from "./statusbar.js";
|
|
|
15
15
|
import { CONTEXT_TOKENS, EFFORTS, findEffort, findTier, TIERS } from "./tiers.js";
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
import { renderHelp, SLASH_HELP, unknownCommandHint } from "./prompt.js";
|
|
19
|
+
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
|
|
@@ -36,23 +38,14 @@ import { CONTEXT_TOKENS, EFFORTS, findEffort, findTier, TIERS } from "./tiers.js
|
|
|
36
38
|
|
|
37
39
|
|
|
38
40
|
|
|
41
|
+
|
|
39
42
|
|
|
40
43
|
|
|
41
44
|
|
|
42
45
|
|
|
43
46
|
|
|
44
47
|
|
|
45
|
-
export
|
|
46
|
-
"/model blink|sonata|aeon switch tier",
|
|
47
|
-
"/effort default|low|medium set reasoning effort",
|
|
48
|
-
"/status tier, routing, session tokens, credit and weekly pool",
|
|
49
|
-
"/init write a starter AGENTS.md for this project",
|
|
50
|
-
"/compact summarize the conversation to save context",
|
|
51
|
-
"/undo revert the last file the agent wrote (not shell commands)",
|
|
52
|
-
"/clear start a fresh conversation",
|
|
53
|
-
"/help this text",
|
|
54
|
-
"/exit leave",
|
|
55
|
-
].join("\n");
|
|
48
|
+
export { SLASH_HELP };
|
|
56
49
|
|
|
57
50
|
export function statusParts(st ) {
|
|
58
51
|
return {
|
|
@@ -141,7 +134,7 @@ export async function runCommand(line , st , io )
|
|
|
141
134
|
case "/quit":
|
|
142
135
|
return { handled: true, exit: true };
|
|
143
136
|
case "/help":
|
|
144
|
-
io.print(SLASH_HELP);
|
|
137
|
+
io.print(io.ui ? renderHelp(io.ui).join("\n") : SLASH_HELP);
|
|
145
138
|
break;
|
|
146
139
|
case "/clear":
|
|
147
140
|
st.history = freshHistory(st);
|
|
@@ -184,7 +177,7 @@ export async function runCommand(line , st , io )
|
|
|
184
177
|
await cmdUndo(st, io);
|
|
185
178
|
break;
|
|
186
179
|
default:
|
|
187
|
-
io.print(
|
|
180
|
+
io.print(unknownCommandHint(first));
|
|
188
181
|
}
|
|
189
182
|
return { handled: true };
|
|
190
183
|
}
|