nestjs-web-repl 1.0.0 → 1.1.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/README.md +15 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.js +58 -0
- package/dist/cli/install-skill.d.ts +17 -0
- package/dist/cli/install-skill.js +35 -0
- package/package.json +6 -2
- package/skill/SKILL.md +105 -0
package/README.md
CHANGED
|
@@ -274,6 +274,21 @@ for options that need DI (e.g. reading a `ConfigService`); see the
|
|
|
274
274
|
and the types `WebReplAdapter`, `WebReplModuleOptions`, `WebReplModuleAsyncOptions`,
|
|
275
275
|
`WebReplEvent`, `SseEventType`.
|
|
276
276
|
|
|
277
|
+
## AI skill
|
|
278
|
+
|
|
279
|
+
This package ships a [Claude Code](https://claude.com/claude-code) skill that
|
|
280
|
+
teaches coding agents how to wire in and use the REPL safely. After installing
|
|
281
|
+
the package, run:
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
npx nestjs-web-repl install-skill
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
This writes `.claude/skills/nestjs-web-repl/SKILL.md` into your project; your
|
|
288
|
+
agent picks it up on its next session. The command never clobbers a modified
|
|
289
|
+
skill file silently — if you have edited it, re-run with `--force` to refresh it
|
|
290
|
+
after upgrading the package.
|
|
291
|
+
|
|
277
292
|
## Limitations (v1)
|
|
278
293
|
|
|
279
294
|
- **Monaco loads from a CDN** (`cdn.jsdelivr.net`) inside the `/ui` page — the
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.run = run;
|
|
5
|
+
const install_skill_1 = require("./install-skill");
|
|
6
|
+
const USAGE = `nestjs-web-repl — CLI
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
nestjs-web-repl install-skill [--force] Install the Claude Code skill into
|
|
10
|
+
./.claude/skills/nestjs-web-repl/
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
--force Overwrite an existing, locally modified SKILL.md
|
|
14
|
+
--help Show this help
|
|
15
|
+
`;
|
|
16
|
+
function run(argv, cwd) {
|
|
17
|
+
const [subcommand, ...rest] = argv;
|
|
18
|
+
if (!subcommand || subcommand === '--help' || subcommand === '-h') {
|
|
19
|
+
return { code: 0, out: USAGE, err: '' };
|
|
20
|
+
}
|
|
21
|
+
if (subcommand === 'install-skill') {
|
|
22
|
+
const force = rest.includes('--force');
|
|
23
|
+
try {
|
|
24
|
+
const { status, path } = (0, install_skill_1.installSkill)({ cwd, force });
|
|
25
|
+
switch (status) {
|
|
26
|
+
case 'created':
|
|
27
|
+
return { code: 0, out: `created ${path}\n`, err: '' };
|
|
28
|
+
case 'up-to-date':
|
|
29
|
+
return { code: 0, out: `up to date ${path}\n`, err: '' };
|
|
30
|
+
case 'updated':
|
|
31
|
+
return { code: 0, out: `updated ${path}\n`, err: '' };
|
|
32
|
+
case 'differs':
|
|
33
|
+
return {
|
|
34
|
+
code: 1,
|
|
35
|
+
out: '',
|
|
36
|
+
err: `differs from the shipped version: ${path}\n` +
|
|
37
|
+
`re-run with --force to overwrite\n`,
|
|
38
|
+
};
|
|
39
|
+
default: {
|
|
40
|
+
const _exhaustive = status;
|
|
41
|
+
return { code: 1, out: '', err: `internal error: unhandled status ${String(_exhaustive)}\n` };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
return { code: 1, out: '', err: `error: ${err.message}\n` };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { code: 2, out: '', err: `unknown command: ${subcommand}\n\n${USAGE}` };
|
|
50
|
+
}
|
|
51
|
+
if (require.main === module) {
|
|
52
|
+
const result = run(process.argv.slice(2), process.cwd());
|
|
53
|
+
if (result.out)
|
|
54
|
+
process.stdout.write(result.out);
|
|
55
|
+
if (result.err)
|
|
56
|
+
process.stderr.write(result.err);
|
|
57
|
+
process.exit(result.code);
|
|
58
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const SKILL_TARGET_SUBPATH: string;
|
|
2
|
+
export type InstallStatus = 'created' | 'up-to-date' | 'differs' | 'updated';
|
|
3
|
+
export interface InstallSkillOptions {
|
|
4
|
+
cwd: string;
|
|
5
|
+
force?: boolean;
|
|
6
|
+
sourcePath?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface InstallSkillResult {
|
|
9
|
+
status: InstallStatus;
|
|
10
|
+
path: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the bundled skill source. Compiled to dist/cli/install-skill.js,
|
|
14
|
+
* so the package root is two levels up; skill/SKILL.md lives there.
|
|
15
|
+
*/
|
|
16
|
+
export declare function defaultSourcePath(): string;
|
|
17
|
+
export declare function installSkill(options: InstallSkillOptions): InstallSkillResult;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SKILL_TARGET_SUBPATH = void 0;
|
|
4
|
+
exports.defaultSourcePath = defaultSourcePath;
|
|
5
|
+
exports.installSkill = installSkill;
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
exports.SKILL_TARGET_SUBPATH = (0, path_1.join)('.claude', 'skills', 'nestjs-web-repl', 'SKILL.md');
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the bundled skill source. Compiled to dist/cli/install-skill.js,
|
|
11
|
+
* so the package root is two levels up; skill/SKILL.md lives there.
|
|
12
|
+
*/
|
|
13
|
+
function defaultSourcePath() {
|
|
14
|
+
return (0, path_1.join)(__dirname, '..', '..', 'skill', 'SKILL.md');
|
|
15
|
+
}
|
|
16
|
+
function installSkill(options) {
|
|
17
|
+
const { cwd, force = false } = options;
|
|
18
|
+
const sourcePath = options.sourcePath ?? defaultSourcePath();
|
|
19
|
+
const source = (0, fs_1.readFileSync)(sourcePath, 'utf8');
|
|
20
|
+
const path = (0, path_1.join)(cwd, exports.SKILL_TARGET_SUBPATH);
|
|
21
|
+
if (!(0, fs_1.existsSync)(path)) {
|
|
22
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(path), { recursive: true });
|
|
23
|
+
(0, fs_1.writeFileSync)(path, source);
|
|
24
|
+
return { status: 'created', path };
|
|
25
|
+
}
|
|
26
|
+
const current = (0, fs_1.readFileSync)(path, 'utf8');
|
|
27
|
+
if (current === source) {
|
|
28
|
+
return { status: 'up-to-date', path };
|
|
29
|
+
}
|
|
30
|
+
if (!force) {
|
|
31
|
+
return { status: 'differs', path };
|
|
32
|
+
}
|
|
33
|
+
(0, fs_1.writeFileSync)(path, source);
|
|
34
|
+
return { status: 'updated', path };
|
|
35
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nestjs-web-repl",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Expose a live NestJS REPL over the network (HTTP + SSE + Monaco UI).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"nestjs-web-repl": "dist/cli/index.js"
|
|
10
|
+
},
|
|
8
11
|
"files": [
|
|
9
|
-
"dist"
|
|
12
|
+
"dist",
|
|
13
|
+
"skill"
|
|
10
14
|
],
|
|
11
15
|
"repository": {
|
|
12
16
|
"type": "git",
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nestjs-web-repl
|
|
3
|
+
description: Use when wiring a live, in-process REPL into a NestJS app over HTTP via the nestjs-web-repl package — adding WebReplModule to AppModule, gating the REPL behind an environment flag, or calling/debugging its POST command, SSE stream, or browser-UI routes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# nestjs-web-repl
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
`nestjs-web-repl` exposes a live NestJS REPL over HTTP: a POST command endpoint,
|
|
11
|
+
a Server-Sent Events output stream, and a Monaco browser UI, backed by real
|
|
12
|
+
`@nestjs/core` REPL sessions running inside the host app.
|
|
13
|
+
|
|
14
|
+
## When to use this skill
|
|
15
|
+
|
|
16
|
+
- Adding `WebReplModule` to a NestJS `AppModule` for the first time.
|
|
17
|
+
- Reviewing or writing the code that enables/disables the REPL (the security
|
|
18
|
+
gate below is non-negotiable).
|
|
19
|
+
- Calling or troubleshooting the `POST`/SSE/browser-UI routes at runtime.
|
|
20
|
+
- Running the package's own examples (there's a `ts-node`-vs-`tsx` gotcha).
|
|
21
|
+
|
|
22
|
+
## Security first — read before wiring
|
|
23
|
+
|
|
24
|
+
The endpoints execute arbitrary code inside the running app **by design**. The
|
|
25
|
+
library ships **no authentication** (intentional). Before adding the module:
|
|
26
|
+
|
|
27
|
+
- Gate it behind an environment flag so it is OFF by default:
|
|
28
|
+
`enabled: process.env.REPL_ENABLED === 'true'`.
|
|
29
|
+
- Never enable it in production without your own authentication or network
|
|
30
|
+
restriction in front of the routes.
|
|
31
|
+
- A disabled module 404s every route and does not subscribe to the adapter —
|
|
32
|
+
keep it that way.
|
|
33
|
+
|
|
34
|
+
## Wire it up
|
|
35
|
+
|
|
36
|
+
Add the module to your `AppModule`:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { Module } from '@nestjs/common';
|
|
40
|
+
import { WebReplModule } from 'nestjs-web-repl';
|
|
41
|
+
|
|
42
|
+
@Module({
|
|
43
|
+
imports: [
|
|
44
|
+
WebReplModule.forRoot({
|
|
45
|
+
enabled: process.env.REPL_ENABLED === 'true',
|
|
46
|
+
}),
|
|
47
|
+
],
|
|
48
|
+
})
|
|
49
|
+
export class AppModule {}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Config-driven `enabled` (async form):
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
WebReplModule.forRootAsync({
|
|
56
|
+
imports: [ConfigModule],
|
|
57
|
+
inject: [ConfigService],
|
|
58
|
+
useFactory: (config: ConfigService) => ({
|
|
59
|
+
enabled: config.get('REPL_ENABLED') === 'true',
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Use it
|
|
65
|
+
|
|
66
|
+
Three routes are registered (default base path `repl`), keyed by a channel name:
|
|
67
|
+
|
|
68
|
+
| Route | Purpose |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `POST repl/{channel}` | Send a line of code to execute. |
|
|
71
|
+
| `GET repl/{channel}` | Server-Sent Events stream of output for the channel. |
|
|
72
|
+
| `GET repl/{channel}/ui` | Monaco editor + terminal browser UI. |
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# stream output (leave running in one terminal)
|
|
76
|
+
curl -N http://localhost:3000/repl/main
|
|
77
|
+
|
|
78
|
+
# in another terminal, run a command on the same channel
|
|
79
|
+
curl -X POST http://localhost:3000/repl/main \
|
|
80
|
+
-H 'content-type: application/json' \
|
|
81
|
+
-d '{"command":"1 + 1"}'
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Open `http://localhost:3000/repl/main/ui` in a browser for the interactive UI.
|
|
85
|
+
|
|
86
|
+
## When you need more
|
|
87
|
+
|
|
88
|
+
- **Multiple app instances** (load-balanced replicas): the default in-memory
|
|
89
|
+
adapter is per-process; supply a shared adapter so a command and its output
|
|
90
|
+
reach the owning instance. See "Adapter / multi-instance" in the package
|
|
91
|
+
README.
|
|
92
|
+
- **Custom adapter:** implement the adapter interface (publish/subscribe over
|
|
93
|
+
the message topics). See "Adapter / multi-instance" in the README.
|
|
94
|
+
- **Options** (base path, TTLs, heartbeats) and **exported symbols:** see
|
|
95
|
+
"Options" and "Exports" in the README.
|
|
96
|
+
- **Runnable example gotcha:** run examples with `ts-node`, not `tsx` —
|
|
97
|
+
`tsx`/esbuild strips the decorator metadata NestJS DI needs. See "Limitations"
|
|
98
|
+
in the README.
|
|
99
|
+
|
|
100
|
+
## Common mistakes
|
|
101
|
+
|
|
102
|
+
- Enabling the REPL unconditionally (or defaulting `enabled` to `true`) —
|
|
103
|
+
always key it off an environment flag that defaults to off.
|
|
104
|
+
- Running a README example with `tsx` and hitting missing-provider/DI errors —
|
|
105
|
+
use `ts-node` instead.
|