@thenavidm/slipway 0.1.4 → 0.1.5
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/CHANGELOG.md +9 -0
- package/README.md +14 -1
- package/SKILL.md +1 -1
- package/dist/cli/context.js +4 -0
- package/dist/cli/help.js +17 -13
- package/dist/errors.js +5 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
What changed in Slipway, newest first.
|
|
4
4
|
|
|
5
|
+
## 0.1.5, 2026-10-04: what Bluesky's move found
|
|
6
|
+
|
|
7
|
+
- **A request that never got an answer exits 5.** A failed fetch, a refused connection or a DNS failure mapped to exit 1, "unexpected error", so a script that retries on 5 gave up instead. Bluesky 1.2.3 exited 5 for an unreachable host, 0.1.4 made it 1, and it is 5 again.
|
|
8
|
+
- **`--help` and `agent-context` list every variable Slipway reads**, `<PREFIX>_HTTP_PORT`, `_HOST`, `_TOKEN` and `_DEBUG` included. Bluesky's own help listed the HTTP ones before it moved.
|
|
9
|
+
- **A shorter general help.** An agent often reads it first and pays for it again on every later step. The header drops the description, the rarely needed commands share one line, and Slipway's own settings say only what they do: Bluesky's went from 780 tokens to 667.
|
|
10
|
+
- **The command list says `!` needs `--confirm`** when that is true of every command it lists.
|
|
11
|
+
- **The entry turns on Node's compile cache.** The README's `src/index.ts` loads the app after `module.enableCompileCache()`, so every launch after the first skips compiling it: Bluesky answers a client in 183 ms instead of 204. Node before 22.8 starts as before.
|
|
12
|
+
- **The README says what happens to a piped request after stdin closes.** The server stops without answering, as the MCP stdio binding asks; keep stdin open until you read the answer.
|
|
13
|
+
|
|
5
14
|
## 0.1.4, 2026-10-04: faster starts, cheaper results in Codex
|
|
6
15
|
|
|
7
16
|
- **A JSON Schema compiles on its tool's first call.** `jsonSchema()` compiled its validator as soon as a tool was defined, so a server paid for every schema before it could answer. On Teachable's 123 contract tools that held the first answer back by 118 ms. Building Stripe's 611 OpenAPI tools took 1,745 ms and now takes 69; GitHub's 1,230 took 646 ms and now take 60 (medians of three runs on one Mac). A tool's first call now compiles its own schema, a median of 3 ms on Stripe's and under 1 ms on GitHub's.
|
package/README.md
CHANGED
|
@@ -174,11 +174,15 @@ export const app = slipway<Context>({
|
|
|
174
174
|
|
|
175
175
|
```ts
|
|
176
176
|
#!/usr/bin/env node
|
|
177
|
-
import
|
|
177
|
+
import * as nodeModule from "node:module";
|
|
178
178
|
|
|
179
|
+
nodeModule.enableCompileCache?.();
|
|
180
|
+
const { app } = await import("./app.js");
|
|
179
181
|
await app.main();
|
|
180
182
|
```
|
|
181
183
|
|
|
184
|
+
The app loads after Node's compile cache goes on, so every launch after the first skips compiling it again: Bluesky answers a client in 183 ms instead of 204. Node before 22.8 has no compile cache and starts as before, and `NODE_DISABLE_COMPILE_CACHE=1` turns it off.
|
|
185
|
+
|
|
182
186
|
**`src/npx.ts`** is what `npx -y @you/notes-mcp-cli` runs:
|
|
183
187
|
|
|
184
188
|
```ts
|
|
@@ -530,6 +534,7 @@ const mcp = await connect(app, { era: "modern", elicit: () => ({ action: "accept
|
|
|
530
534
|
| Codex shows the server as failed at startup | The first npx download outlasted 10 seconds | `install codex` sets `startup_timeout_sec = 60`; add it by hand to an older entry |
|
|
531
535
|
| `slipway check` warns about schema size | One tool's schema is large or repeats its definitions | Send the body schema once, or advertise a short one and validate the full one in the handler |
|
|
532
536
|
| `slipway check` cannot load the app | The module starts the server when imported | Export the app from `app.ts` and call `app.main()` only in `index.ts` |
|
|
537
|
+
| A request piped to the server gets no answer | Stdin closed before the answer, and the MCP stdio binding stops a server when its input ends | Keep stdin open until you read the answer, as clients do, or run the command from the CLI |
|
|
533
538
|
| `npx slipway` prints something unexpected | Slipway is not installed in this folder, so npx fetched an unrelated package called `slipway` | Run `npm install @thenavidm/slipway`, or `npx -p @thenavidm/slipway slipway <command>` |
|
|
534
539
|
|
|
535
540
|
## Environment variables
|
|
@@ -556,6 +561,14 @@ Every server reads these, under its own prefix: the app name in capitals, `NOTES
|
|
|
556
561
|
|
|
557
562
|
See [CHANGELOG.md](CHANGELOG.md).
|
|
558
563
|
|
|
564
|
+
## Servers built on Slipway
|
|
565
|
+
|
|
566
|
+
| Server | Package | Covers |
|
|
567
|
+
| --- | --- | --- |
|
|
568
|
+
| [Teachable](https://github.com/thenavidm/teachable-mcp-cli) | [`@thenavidm/teachable-mcp-cli`](https://www.npmjs.com/package/@thenavidm/teachable-mcp-cli) 3.0.0 | Courses, users, enrollments, pricing, coupons and transactions |
|
|
569
|
+
|
|
570
|
+
Each server was measured against its previous release before it moved: startup, what a client receives, CLI exit codes, and tokens in Claude Code and Codex. Its README has the numbers.
|
|
571
|
+
|
|
559
572
|
## 15. FAQ ❓
|
|
560
573
|
|
|
561
574
|
<details>
|
package/SKILL.md
CHANGED
|
@@ -22,7 +22,7 @@ Run `npm ls @thenavidm/slipway` in the repo. If it does not list a version, STOP
|
|
|
22
22
|
|---|---|---|
|
|
23
23
|
| `src/tools.ts` | The tools, from `toolkit<Context>().defineTool` | One `defineTool` per action |
|
|
24
24
|
| `src/app.ts` | `export const app = slipway({...})` | Describes only. Never calls `main()`, so checks and tests can import it |
|
|
25
|
-
| `src/index.ts` | `await app.main()` | The only file that starts anything. Both binaries point at it |
|
|
25
|
+
| `src/index.ts` | `nodeModule.enableCompileCache?.()`, then `await import("./app.js")` and `app.main()` | The only file that starts anything. Both binaries point at it. The cache goes on before the app loads, so later launches skip compiling it |
|
|
26
26
|
| `src/npx.ts` | `import "./index.js";` | What `npx -y <package>` runs. Its binary is named after the package |
|
|
27
27
|
|
|
28
28
|
`package.json` declares `"<name>-mcp"` and `"<name>-cli"` on `dist/index.js`, and a third binary named after the package (`"<name>-mcp-cli"`) on `dist/npx.js`. npx only picks a binary by name when they point to different files; otherwise it takes whichever one the registry lists first, which may be the CLI.
|
package/dist/cli/context.js
CHANGED
|
@@ -76,6 +76,10 @@ export function agentContext(app, env, bin, options = {}) {
|
|
|
76
76
|
{ env: names.auditLog, value: policy.auditLog ?? null, description: "file that records every attempted write" },
|
|
77
77
|
{ env: names.toolTimeoutMs, value: policy.toolTimeoutMs ?? null, description: "deadline for any tool" },
|
|
78
78
|
{ env: names.confirm, value: policy.confirm, description: "who confirms a confirmed call over MCP: human asks a person where the client can, model accepts confirm: true" },
|
|
79
|
+
{ env: `${app.envPrefix}_HTTP_PORT`, value: env[`${app.envPrefix}_HTTP_PORT`] ?? null, description: "port for --http, 8787 when unset" },
|
|
80
|
+
{ env: `${app.envPrefix}_HTTP_HOST`, value: env[`${app.envPrefix}_HTTP_HOST`] ?? null, description: "address for --http, 127.0.0.1 when unset; any other needs a token" },
|
|
81
|
+
{ env: `${app.envPrefix}_HTTP_TOKEN`, set: Boolean(env[`${app.envPrefix}_HTTP_TOKEN`]), secret: true, description: "bearer token --http requires" },
|
|
82
|
+
{ env: `${app.envPrefix}_DEBUG`, value: /^(1|true|yes)$/i.test(env[`${app.envPrefix}_DEBUG`] ?? ""), description: "print debug lines on stderr" },
|
|
79
83
|
],
|
|
80
84
|
...(app.definition.toolsets ? { toolsets: app.definition.toolsets } : {}),
|
|
81
85
|
hidden_commands: hidden,
|
package/dist/cli/help.js
CHANGED
|
@@ -94,7 +94,9 @@ export function renderList(app, tools, bin, env = process.env) {
|
|
|
94
94
|
for (const tool of members)
|
|
95
95
|
lines.push(` ${riskMark(tool.risk)} ${tool.command.padEnd(width)}${tool.title}`);
|
|
96
96
|
}
|
|
97
|
-
|
|
97
|
+
// The legend says `!` needs --confirm only when that holds for every listed command.
|
|
98
|
+
const confirmByRisk = tools.every((tool) => tool.requireConfirm === (tool.risk === "destructive"));
|
|
99
|
+
lines.push(``, ` * writes ! public or irreversible${confirmByRisk ? ", needs --confirm" : ""}`, ``, ` ${bin} <command> --help what one takes, with examples`, ` ${bin} which <words> find the command for a task`, ` ${bin} --help flags, settings and setup`, ``, ...hiddenNote(app, env));
|
|
98
100
|
return lines.join("\n");
|
|
99
101
|
}
|
|
100
102
|
function shellQuote(value) {
|
|
@@ -186,16 +188,15 @@ export function renderGeneralHelp(app, bin) {
|
|
|
186
188
|
const cache = app.allTools.some((tool) => tool.cache);
|
|
187
189
|
const sync = app.allTools.some((tool) => tool.sync);
|
|
188
190
|
const jobs = app.allTools.some((tool) => tool.job);
|
|
191
|
+
// An agent often reads this first and pays for it again on every later step, so the
|
|
192
|
+
// rarely needed commands share one line and Slipway's own settings say only what they do.
|
|
189
193
|
const commands = [
|
|
190
194
|
[bin, "list the commands"],
|
|
191
195
|
[`${bin} <command> --help`, "what one takes, with examples"],
|
|
192
196
|
[`${bin} which <words>`, "find the command for a task"],
|
|
193
|
-
[`${bin} schema <command>`, "its JSON Schema; --output for the result's"],
|
|
194
|
-
[`${bin} agent-context`, "all of this as JSON; --brief for less"],
|
|
195
197
|
[`${bin} doctor [--network]`, "check the setup and say what is wrong"],
|
|
196
198
|
[`${bin} login`, "how to connect an account"],
|
|
197
|
-
[`${bin} install <client>`, "add the server to
|
|
198
|
-
[`${bin} completion <shell>`, "tab completion for bash, zsh or fish"],
|
|
199
|
+
[`${bin} install <client>`, "add the server to an MCP client; install --help lists them"],
|
|
199
200
|
...(cache || sync ? [[`${bin} data`, "what is kept on this machine; data clear [<command>] deletes it"]] : []),
|
|
200
201
|
...(sync
|
|
201
202
|
? [
|
|
@@ -209,14 +210,16 @@ export function renderGeneralHelp(app, bin) {
|
|
|
209
210
|
const settings = [
|
|
210
211
|
...(app.definition.settings ?? []).map((setting) => [setting.env, setting.description]),
|
|
211
212
|
[`${names.readOnly}=1`, "hide and refuse every write"],
|
|
212
|
-
[`${names.allowDestructive}=0`, "
|
|
213
|
+
[`${names.allowDestructive}=0`, "refuse the irreversible writes"],
|
|
213
214
|
[`${names.toolsets}=a,b`, "only these toolsets, or all"],
|
|
214
|
-
[`${names.surface}=search`, "MCP
|
|
215
|
-
[`${names.auditLog}=<file>`, "log every attempted write
|
|
216
|
-
[`${names.toolTimeoutMs}=<ms>`, "
|
|
217
|
-
[`${names.confirm}=model`, "confirm: true alone confirms
|
|
215
|
+
[`${names.surface}=search`, "MCP lists three finder tools instead"],
|
|
216
|
+
[`${names.auditLog}=<file>`, "log every attempted write"],
|
|
217
|
+
[`${names.toolTimeoutMs}=<ms>`, "deadline for any tool"],
|
|
218
|
+
[`${names.confirm}=model`, "confirm: true alone confirms over MCP"],
|
|
218
219
|
...(cache ? [[`${names.cache}=0`, "never answer from the local cache"]] : []),
|
|
219
220
|
...(cache || sync ? [[`${names.dataDir}=<dir>`, "keep local data in this folder"]] : []),
|
|
221
|
+
[`${app.envPrefix}_HTTP_PORT / _HOST / _TOKEN`, "for --http"],
|
|
222
|
+
[`${app.envPrefix}_DEBUG=1`, "debug lines on stderr"],
|
|
220
223
|
];
|
|
221
224
|
// Flags that cannot apply here (jobs, the cache) are left out; agent-context lists every one.
|
|
222
225
|
const flags = GLOBAL_FLAGS.map(([flag]) => flag).filter((flag) => flag !== "--agent" && (flag !== "--wait" || jobs) && (flag !== "--refresh" || cache));
|
|
@@ -224,16 +227,17 @@ export function renderGeneralHelp(app, bin) {
|
|
|
224
227
|
const row = ([left, help]) => ` ${left.padEnd(width)}${help}`;
|
|
225
228
|
const lines = [
|
|
226
229
|
``,
|
|
227
|
-
`${app.title} ${app.version}
|
|
230
|
+
`${app.title} ${app.version}`,
|
|
228
231
|
``,
|
|
229
232
|
...commands.map(row),
|
|
233
|
+
` Also: schema <command>, agent-context [--brief] (all of this as JSON), completion <shell>.`,
|
|
230
234
|
``,
|
|
231
|
-
`Flags: ${flags.join(", ")}, and --agent
|
|
235
|
+
`Flags: ${flags.join(", ")}, and --agent: compact JSON, no prompts, never confirms a write.`,
|
|
232
236
|
``,
|
|
233
237
|
`Settings:`,
|
|
234
238
|
...settings.map(row),
|
|
235
239
|
``,
|
|
236
|
-
`Exit codes: ${EXIT.ok} ok, ${EXIT.error} unexpected
|
|
240
|
+
`Exit codes: ${EXIT.ok} ok, ${EXIT.error} unexpected, ${EXIT.usage} usage or refused, ${EXIT.notFound} not found, ${EXIT.auth} auth, ${EXIT.api} API, ${EXIT.rateLimited} rate limited, ${EXIT.notConfigured} not configured`,
|
|
237
241
|
``,
|
|
238
242
|
];
|
|
239
243
|
if (app.definition.links?.repository)
|
package/dist/errors.js
CHANGED
|
@@ -149,5 +149,10 @@ export function toSlipwayError(error) {
|
|
|
149
149
|
return new AuthError(message, { cause: error });
|
|
150
150
|
if (/\b404\b|not found|does not exist/.test(text))
|
|
151
151
|
return new NotFoundError(message, { cause: error });
|
|
152
|
+
// A request that never got an answer is the service's failure, not a bug here: exit 5, which a script may retry.
|
|
153
|
+
const codes = [error?.code, error?.cause?.code];
|
|
154
|
+
const network = codes.some((code) => typeof code === "string" && /^(ECONN(REFUSED|RESET|ABORTED)|ENOTFOUND|EAI_AGAIN|ETIMEDOUT|E(HOST|NET)UNREACH|EPIPE|UND_ERR_\w+)$/.test(code));
|
|
155
|
+
if (network || /fetch failed|could not reach|network error|socket hang up/.test(text))
|
|
156
|
+
return new ApiError(message, { cause: error });
|
|
152
157
|
return new SlipwayError(message, "internal", EXIT.error, { cause: error });
|
|
153
158
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thenavidm/slipway",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Slipway, the TypeScript framework for MCP servers and agent-native CLIs. One tool definition ships an MCP server and a CLI, with write safety, typed results and release checks built in.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|