@otomus/nerva-cli 0.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.
Files changed (45) hide show
  1. package/dist/commands/dev.d.ts +42 -0
  2. package/dist/commands/dev.d.ts.map +1 -0
  3. package/dist/commands/dev.js +257 -0
  4. package/dist/commands/dev.js.map +1 -0
  5. package/dist/commands/generate.d.ts +24 -0
  6. package/dist/commands/generate.d.ts.map +1 -0
  7. package/dist/commands/generate.js +162 -0
  8. package/dist/commands/generate.js.map +1 -0
  9. package/dist/commands/list.d.ts +19 -0
  10. package/dist/commands/list.d.ts.map +1 -0
  11. package/dist/commands/list.js +96 -0
  12. package/dist/commands/list.js.map +1 -0
  13. package/dist/commands/new.d.ts +23 -0
  14. package/dist/commands/new.d.ts.map +1 -0
  15. package/dist/commands/new.js +138 -0
  16. package/dist/commands/new.js.map +1 -0
  17. package/dist/commands/plugin.d.ts +79 -0
  18. package/dist/commands/plugin.d.ts.map +1 -0
  19. package/dist/commands/plugin.js +319 -0
  20. package/dist/commands/plugin.js.map +1 -0
  21. package/dist/commands/test.d.ts +52 -0
  22. package/dist/commands/test.d.ts.map +1 -0
  23. package/dist/commands/test.js +110 -0
  24. package/dist/commands/test.js.map +1 -0
  25. package/dist/commands/trace-ui.d.ts +16 -0
  26. package/dist/commands/trace-ui.d.ts.map +1 -0
  27. package/dist/commands/trace-ui.js +117 -0
  28. package/dist/commands/trace-ui.js.map +1 -0
  29. package/dist/config/nerva-yaml.d.ts +70 -0
  30. package/dist/config/nerva-yaml.d.ts.map +1 -0
  31. package/dist/config/nerva-yaml.js +185 -0
  32. package/dist/config/nerva-yaml.js.map +1 -0
  33. package/dist/index.d.ts +14 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +36 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/registry/plugin-registry.d.ts +30 -0
  38. package/dist/registry/plugin-registry.d.ts.map +1 -0
  39. package/dist/registry/plugin-registry.js +134 -0
  40. package/dist/registry/plugin-registry.js.map +1 -0
  41. package/dist/templates/render.d.ts +36 -0
  42. package/dist/templates/render.d.ts.map +1 -0
  43. package/dist/templates/render.js +52 -0
  44. package/dist/templates/render.js.map +1 -0
  45. package/package.json +32 -0
@@ -0,0 +1,138 @@
1
+ /**
2
+ * `nerva new <name>` command.
3
+ *
4
+ * Scaffolds a new Nerva project directory with the correct structure
5
+ * for either Python or TypeScript.
6
+ */
7
+ import { mkdir, writeFile } from 'node:fs/promises';
8
+ import { join } from 'node:path';
9
+ import { serializeNervaConfig } from '../config/nerva-yaml.js';
10
+ /** Directories created for a Python project. */
11
+ const PYTHON_DIRS = ['agents', 'tools', 'memory', 'middleware', 'tests'];
12
+ /** Directories created for a TypeScript project. */
13
+ const TS_DIRS = ['src/agents', 'src/tools', 'src/memory', 'src/middleware', 'tests'];
14
+ /**
15
+ * Creates a directory and all parents, silently succeeding if it already exists.
16
+ *
17
+ * @param dirPath - Absolute path to create
18
+ */
19
+ async function ensureDir(dirPath) {
20
+ await mkdir(dirPath, { recursive: true });
21
+ }
22
+ /**
23
+ * Builds a default NervaConfig for a new project.
24
+ *
25
+ * @param name - Project name
26
+ * @param lang - Project language
27
+ * @returns Default configuration
28
+ */
29
+ function buildDefaultConfig(name, lang) {
30
+ return {
31
+ name,
32
+ version: '0.1.0',
33
+ lang,
34
+ agents: [],
35
+ tools: [],
36
+ middleware: [],
37
+ routers: [],
38
+ };
39
+ }
40
+ /**
41
+ * Scaffolds the Python-specific files in the project directory.
42
+ *
43
+ * @param projectDir - Absolute path to the project root
44
+ * @param name - Project name
45
+ */
46
+ async function scaffoldPython(projectDir, name) {
47
+ for (const dir of PYTHON_DIRS) {
48
+ await ensureDir(join(projectDir, dir));
49
+ }
50
+ await writeFile(join(projectDir, 'main.py'), `"""${name} — entry point."""\n\nfrom nerva import Runtime\n\n\ndef main() -> None:\n """Start the Nerva runtime."""\n runtime = Runtime.from_config("nerva.yaml")\n runtime.start()\n\n\nif __name__ == "__main__":\n main()\n`, 'utf-8');
51
+ await writeFile(join(projectDir, 'requirements.txt'), 'nerva>=0.1.0\n', 'utf-8');
52
+ }
53
+ /**
54
+ * Scaffolds the TypeScript-specific files in the project directory.
55
+ *
56
+ * @param projectDir - Absolute path to the project root
57
+ * @param name - Project name
58
+ */
59
+ async function scaffoldTypeScript(projectDir, name) {
60
+ for (const dir of TS_DIRS) {
61
+ await ensureDir(join(projectDir, dir));
62
+ }
63
+ await writeFile(join(projectDir, 'src/index.ts'), `/**\n * ${name} — entry point.\n */\n\nimport { Runtime } from 'nerva';\n\n/** Start the Nerva runtime. */\nasync function main(): Promise<void> {\n const runtime = await Runtime.fromConfig('nerva.yaml');\n await runtime.start();\n}\n\nmain();\n`, 'utf-8');
64
+ await writeFile(join(projectDir, 'package.json'), JSON.stringify({
65
+ name,
66
+ version: '0.1.0',
67
+ type: 'module',
68
+ scripts: {
69
+ build: 'tsc',
70
+ start: 'node dist/index.js',
71
+ test: 'vitest run',
72
+ },
73
+ dependencies: {
74
+ nerva: '^0.1.0',
75
+ },
76
+ devDependencies: {
77
+ typescript: '^5.4.0',
78
+ vitest: '^1.6.0',
79
+ '@types/node': '^20.14.0',
80
+ },
81
+ }, null, 2) + '\n', 'utf-8');
82
+ await writeFile(join(projectDir, 'tsconfig.json'), JSON.stringify({
83
+ compilerOptions: {
84
+ strict: true,
85
+ target: 'ES2022',
86
+ module: 'ES2022',
87
+ moduleResolution: 'node16',
88
+ outDir: 'dist',
89
+ rootDir: 'src',
90
+ declaration: true,
91
+ esModuleInterop: true,
92
+ skipLibCheck: true,
93
+ },
94
+ include: ['src/**/*.ts'],
95
+ exclude: ['node_modules', 'dist'],
96
+ }, null, 2) + '\n', 'utf-8');
97
+ }
98
+ /**
99
+ * Registers the `nerva new` command with the CLI program.
100
+ *
101
+ * @param program - The root commander program
102
+ */
103
+ export function registerNewCommand(program) {
104
+ program
105
+ .command('new')
106
+ .description('Create a new Nerva project')
107
+ .argument('<name>', 'Project name')
108
+ .option('-l, --lang <language>', 'Project language (python or typescript)', 'python')
109
+ .action(async (name, options) => {
110
+ await executeNew(name, options.lang);
111
+ });
112
+ }
113
+ /**
114
+ * Executes the project scaffolding logic.
115
+ *
116
+ * @param name - Project name (used as directory name)
117
+ * @param lang - Target language
118
+ * @throws {Error} If the language is unsupported
119
+ */
120
+ export async function executeNew(name, lang) {
121
+ if (lang !== 'python' && lang !== 'typescript') {
122
+ throw new Error(`Unsupported language: "${lang}". Use "python" or "typescript".`);
123
+ }
124
+ const projectDir = join(process.cwd(), name);
125
+ await ensureDir(projectDir);
126
+ // Write nerva.yaml
127
+ const config = buildDefaultConfig(name, lang);
128
+ await writeFile(join(projectDir, 'nerva.yaml'), serializeNervaConfig(config), 'utf-8');
129
+ // Scaffold language-specific files
130
+ if (lang === 'python') {
131
+ await scaffoldPython(projectDir, name);
132
+ }
133
+ else {
134
+ await scaffoldTypeScript(projectDir, name);
135
+ }
136
+ console.log(`Created project "${name}" with ${lang} template at ./${name}`);
137
+ }
138
+ //# sourceMappingURL=new.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"new.js","sourceRoot":"","sources":["../../src/commands/new.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,oBAAoB,EAAsC,MAAM,yBAAyB,CAAC;AAEnG,gDAAgD;AAChD,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAU,CAAC;AAElF,oDAAoD;AACpD,MAAM,OAAO,GAAG,CAAC,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,gBAAgB,EAAE,OAAO,CAAU,CAAC;AAO9F;;;;GAIG;AACH,KAAK,UAAU,SAAS,CAAC,OAAe;IACtC,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;GAMG;AACH,SAAS,kBAAkB,CAAC,IAAY,EAAE,IAAiB;IACzD,OAAO;QACL,IAAI;QACJ,OAAO,EAAE,OAAO;QAChB,IAAI;QACJ,MAAM,EAAE,EAAE;QACV,KAAK,EAAE,EAAE;QACT,UAAU,EAAE,EAAE;QACd,OAAO,EAAE,EAAE;KACZ,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,cAAc,CAAC,UAAkB,EAAE,IAAY;IAC5D,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC9B,MAAM,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,SAAS,CACb,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,EAC3B,MAAM,IAAI,kOAAkO,EAC5O,OAAO,CACR,CAAC;IAEF,MAAM,SAAS,CACb,IAAI,CAAC,UAAU,EAAE,kBAAkB,CAAC,EACpC,gBAAgB,EAChB,OAAO,CACR,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,kBAAkB,CAAC,UAAkB,EAAE,IAAY;IAChE,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,MAAM,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,SAAS,CACb,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,EAChC,WAAW,IAAI,0OAA0O,EACzP,OAAO,CACR,CAAC;IAEF,MAAM,SAAS,CACb,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,EAChC,IAAI,CAAC,SAAS,CACZ;QACE,IAAI;QACJ,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,oBAAoB;YAC3B,IAAI,EAAE,YAAY;SACnB;QACD,YAAY,EAAE;YACZ,KAAK,EAAE,QAAQ;SAChB;QACD,eAAe,EAAE;YACf,UAAU,EAAE,QAAQ;YACpB,MAAM,EAAE,QAAQ;YAChB,aAAa,EAAE,UAAU;SAC1B;KACF,EACD,IAAI,EACJ,CAAC,CACF,GAAG,IAAI,EACR,OAAO,CACR,CAAC;IAEF,MAAM,SAAS,CACb,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,EACjC,IAAI,CAAC,SAAS,CACZ;QACE,eAAe,EAAE;YACf,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,QAAQ;YAChB,gBAAgB,EAAE,QAAQ;YAC1B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,KAAK;YACd,WAAW,EAAE,IAAI;YACjB,eAAe,EAAE,IAAI;YACrB,YAAY,EAAE,IAAI;SACnB;QACD,OAAO,EAAE,CAAC,aAAa,CAAC;QACxB,OAAO,EAAE,CAAC,cAAc,EAAE,MAAM,CAAC;KAClC,EACD,IAAI,EACJ,CAAC,CACF,GAAG,IAAI,EACR,OAAO,CACR,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAgB;IACjD,OAAO;SACJ,OAAO,CAAC,KAAK,CAAC;SACd,WAAW,CAAC,4BAA4B,CAAC;SACzC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;SAClC,MAAM,CAAC,uBAAuB,EAAE,yCAAyC,EAAE,QAAQ,CAAC;SACpF,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,OAA0B,EAAE,EAAE;QACzD,MAAM,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY,EAAE,IAAiB;IAC9D,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,kCAAkC,CAAC,CAAC;IACpF,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;IAC7C,MAAM,SAAS,CAAC,UAAU,CAAC,CAAC;IAE5B,mBAAmB;IACnB,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC9C,MAAM,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;IAEvF,mCAAmC;IACnC,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,MAAM,cAAc,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IACzC,CAAC;SAAM,CAAC;QACN,MAAM,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,UAAU,IAAI,kBAAkB,IAAI,EAAE,CAAC,CAAC;AAC9E,CAAC"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * `nerva plugin` command group.
3
+ *
4
+ * Manages template plugins that extend `nerva generate` with custom
5
+ * component types. Plugins are stored in `.nerva/plugins/` and expose
6
+ * a `nerva-plugin.json` manifest.
7
+ */
8
+ import { Command } from 'commander';
9
+ /** A single template entry inside a plugin manifest. */
10
+ export interface PluginTemplate {
11
+ type: string;
12
+ files: string[];
13
+ }
14
+ /** Schema of the `nerva-plugin.json` manifest file. */
15
+ export interface PluginManifest {
16
+ name: string;
17
+ version: string;
18
+ templates: PluginTemplate[];
19
+ }
20
+ /** Summary of an installed plugin returned by `listPlugins`. */
21
+ export interface InstalledPlugin {
22
+ name: string;
23
+ version: string;
24
+ types: string[];
25
+ }
26
+ /**
27
+ * Parses and validates a raw JSON string as a PluginManifest.
28
+ *
29
+ * @param raw - Raw JSON string from nerva-plugin.json
30
+ * @returns Validated manifest
31
+ * @throws {Error} If the JSON is malformed or required fields are missing
32
+ */
33
+ export declare function parsePluginManifest(raw: string): PluginManifest;
34
+ /**
35
+ * Returns the absolute path to the plugins directory for a project.
36
+ *
37
+ * @param projectDir - Absolute path to the project root
38
+ * @returns Absolute path to `.nerva/plugins/`
39
+ */
40
+ export declare function pluginsDir(projectDir: string): string;
41
+ /**
42
+ * Installs a plugin from a local path or npm package name.
43
+ *
44
+ * For local paths: copies the directory into `.nerva/plugins/<name>`.
45
+ * For npm packages: runs `npm pack` in a temp dir, extracts, and copies.
46
+ *
47
+ * @param source - Local directory path or npm package name
48
+ * @param projectDir - Absolute path to the project root
49
+ * @returns The parsed manifest of the installed plugin
50
+ * @throws {Error} If the source has no valid manifest or the plugin is already installed
51
+ */
52
+ export declare function installPlugin(source: string, projectDir: string): Promise<PluginManifest>;
53
+ /**
54
+ * Lists all installed plugins by reading `.nerva/plugins/`.
55
+ *
56
+ * @param projectDir - Absolute path to the project root
57
+ * @returns Array of installed plugin summaries
58
+ */
59
+ export declare function listPlugins(projectDir: string): Promise<InstalledPlugin[]>;
60
+ /**
61
+ * Removes an installed plugin by name.
62
+ *
63
+ * @param name - Plugin name to remove
64
+ * @param projectDir - Absolute path to the project root
65
+ * @throws {Error} If the plugin is not installed
66
+ */
67
+ export declare function removePlugin(name: string, projectDir: string): Promise<void>;
68
+ /**
69
+ * Registers the `nerva plugin` command group with the CLI program.
70
+ *
71
+ * Sub-commands:
72
+ * - `nerva plugin install <source>` — install from npm or local path
73
+ * - `nerva plugin list` — list installed plugins
74
+ * - `nerva plugin remove <name>` — remove an installed plugin
75
+ *
76
+ * @param program - The root commander program
77
+ */
78
+ export declare function registerPluginCommand(program: Command): void;
79
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/commands/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,wDAAwD;AACxD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,uDAAuD;AACvD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,cAAc,EAAE,CAAC;CAC7B;AAED,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAgBD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAiC/D;AA0CD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAErD;AAiBD;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,cAAc,CAAC,CAazB;AAsFD;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CA0BhF;AA2BD;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQlF;AAMD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA4C5D"}
@@ -0,0 +1,319 @@
1
+ /**
2
+ * `nerva plugin` command group.
3
+ *
4
+ * Manages template plugins that extend `nerva generate` with custom
5
+ * component types. Plugins are stored in `.nerva/plugins/` and expose
6
+ * a `nerva-plugin.json` manifest.
7
+ */
8
+ import { mkdir, readFile, readdir, rm, cp } from 'node:fs/promises';
9
+ import { join, resolve } from 'node:path';
10
+ import { existsSync } from 'node:fs';
11
+ import { execSync } from 'node:child_process';
12
+ // ---------------------------------------------------------------------------
13
+ // Constants
14
+ // ---------------------------------------------------------------------------
15
+ /** Directory name where plugins are stored, relative to project root. */
16
+ const PLUGINS_DIR = '.nerva/plugins';
17
+ /** Name of the manifest file inside each plugin directory. */
18
+ const MANIFEST_FILENAME = 'nerva-plugin.json';
19
+ // ---------------------------------------------------------------------------
20
+ // Manifest parsing
21
+ // ---------------------------------------------------------------------------
22
+ /**
23
+ * Parses and validates a raw JSON string as a PluginManifest.
24
+ *
25
+ * @param raw - Raw JSON string from nerva-plugin.json
26
+ * @returns Validated manifest
27
+ * @throws {Error} If the JSON is malformed or required fields are missing
28
+ */
29
+ export function parsePluginManifest(raw) {
30
+ let parsed;
31
+ try {
32
+ parsed = JSON.parse(raw);
33
+ }
34
+ catch {
35
+ throw new Error('nerva-plugin.json contains invalid JSON');
36
+ }
37
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
38
+ throw new Error('nerva-plugin.json must be a JSON object');
39
+ }
40
+ const obj = parsed;
41
+ if (typeof obj['name'] !== 'string' || obj['name'].trim() === '') {
42
+ throw new Error('nerva-plugin.json: "name" must be a non-empty string');
43
+ }
44
+ if (typeof obj['version'] !== 'string' || obj['version'].trim() === '') {
45
+ throw new Error('nerva-plugin.json: "version" must be a non-empty string');
46
+ }
47
+ if (!Array.isArray(obj['templates'])) {
48
+ throw new Error('nerva-plugin.json: "templates" must be an array');
49
+ }
50
+ const templates = validateTemplates(obj['templates']);
51
+ return {
52
+ name: obj['name'],
53
+ version: obj['version'],
54
+ templates,
55
+ };
56
+ }
57
+ /**
58
+ * Validates the templates array from a plugin manifest.
59
+ *
60
+ * @param raw - Unvalidated templates array
61
+ * @returns Validated PluginTemplate array
62
+ * @throws {Error} If any entry is malformed
63
+ */
64
+ function validateTemplates(raw) {
65
+ return raw.map((entry, index) => {
66
+ if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
67
+ throw new Error(`nerva-plugin.json: templates[${index}] must be an object`);
68
+ }
69
+ const item = entry;
70
+ if (typeof item['type'] !== 'string' || item['type'].trim() === '') {
71
+ throw new Error(`nerva-plugin.json: templates[${index}].type must be a non-empty string`);
72
+ }
73
+ if (!Array.isArray(item['files']) || item['files'].length === 0) {
74
+ throw new Error(`nerva-plugin.json: templates[${index}].files must be a non-empty array`);
75
+ }
76
+ const files = item['files'].map((f, fi) => {
77
+ if (typeof f !== 'string' || f.trim() === '') {
78
+ throw new Error(`nerva-plugin.json: templates[${index}].files[${fi}] must be a non-empty string`);
79
+ }
80
+ return f;
81
+ });
82
+ return { type: item['type'], files };
83
+ });
84
+ }
85
+ // ---------------------------------------------------------------------------
86
+ // Plugin directory helpers
87
+ // ---------------------------------------------------------------------------
88
+ /**
89
+ * Returns the absolute path to the plugins directory for a project.
90
+ *
91
+ * @param projectDir - Absolute path to the project root
92
+ * @returns Absolute path to `.nerva/plugins/`
93
+ */
94
+ export function pluginsDir(projectDir) {
95
+ return join(projectDir, PLUGINS_DIR);
96
+ }
97
+ /**
98
+ * Returns the absolute path to a specific plugin's directory.
99
+ *
100
+ * @param projectDir - Absolute path to the project root
101
+ * @param name - Plugin name
102
+ * @returns Absolute path to the plugin directory
103
+ */
104
+ function pluginPath(projectDir, name) {
105
+ return join(pluginsDir(projectDir), name);
106
+ }
107
+ // ---------------------------------------------------------------------------
108
+ // Install
109
+ // ---------------------------------------------------------------------------
110
+ /**
111
+ * Installs a plugin from a local path or npm package name.
112
+ *
113
+ * For local paths: copies the directory into `.nerva/plugins/<name>`.
114
+ * For npm packages: runs `npm pack` in a temp dir, extracts, and copies.
115
+ *
116
+ * @param source - Local directory path or npm package name
117
+ * @param projectDir - Absolute path to the project root
118
+ * @returns The parsed manifest of the installed plugin
119
+ * @throws {Error} If the source has no valid manifest or the plugin is already installed
120
+ */
121
+ export async function installPlugin(source, projectDir) {
122
+ const resolvedSource = resolvePluginSource(source);
123
+ const manifest = await readManifestFrom(resolvedSource);
124
+ const dest = pluginPath(projectDir, manifest.name);
125
+ if (existsSync(dest)) {
126
+ throw new Error(`Plugin "${manifest.name}" is already installed. Remove it first to reinstall.`);
127
+ }
128
+ await mkdir(dest, { recursive: true });
129
+ await copyPluginFiles(resolvedSource, dest);
130
+ return manifest;
131
+ }
132
+ /**
133
+ * Resolves a source argument to an absolute local path.
134
+ *
135
+ * If the source looks like a local path (starts with `.`, `/`, or contains
136
+ * path separators), it is resolved against cwd. Otherwise it is treated as
137
+ * an npm package name and fetched via `npm pack`.
138
+ *
139
+ * @param source - CLI argument for plugin source
140
+ * @returns Absolute path to the plugin directory
141
+ */
142
+ function resolvePluginSource(source) {
143
+ const isLocalPath = source.startsWith('.') || source.startsWith('/') || source.includes('/');
144
+ if (isLocalPath) {
145
+ return resolve(source);
146
+ }
147
+ return fetchNpmPackage(source);
148
+ }
149
+ /**
150
+ * Downloads an npm package using `npm pack` and extracts it to a temp directory.
151
+ *
152
+ * @param packageName - npm package name
153
+ * @returns Absolute path to the extracted package directory
154
+ * @throws {Error} If `npm pack` fails
155
+ */
156
+ function fetchNpmPackage(packageName) {
157
+ const tmpDir = join(process.cwd(), '.nerva', '.tmp', `npm-${Date.now()}`);
158
+ try {
159
+ execSync(`mkdir -p "${tmpDir}" && cd "${tmpDir}" && npm pack "${packageName}" --silent 2>/dev/null`, {
160
+ stdio: 'pipe',
161
+ });
162
+ // npm pack creates a tarball — extract it
163
+ const tgzFiles = execSync(`ls "${tmpDir}"/*.tgz 2>/dev/null`, { encoding: 'utf-8' }).trim();
164
+ if (!tgzFiles) {
165
+ throw new Error(`Failed to download npm package "${packageName}"`);
166
+ }
167
+ const tgzPath = tgzFiles.split('\n')[0];
168
+ execSync(`cd "${tmpDir}" && tar xzf "${tgzPath}" --strip-components=1`, { stdio: 'pipe' });
169
+ return tmpDir;
170
+ }
171
+ catch (err) {
172
+ throw new Error(`Failed to install npm package "${packageName}": ${err instanceof Error ? err.message : String(err)}`);
173
+ }
174
+ }
175
+ /**
176
+ * Reads and parses the manifest from a plugin source directory.
177
+ *
178
+ * @param sourceDir - Absolute path to the plugin source
179
+ * @returns Parsed manifest
180
+ * @throws {Error} If the manifest file is missing or invalid
181
+ */
182
+ async function readManifestFrom(sourceDir) {
183
+ const manifestPath = join(sourceDir, MANIFEST_FILENAME);
184
+ if (!existsSync(manifestPath)) {
185
+ throw new Error(`No ${MANIFEST_FILENAME} found in ${sourceDir}`);
186
+ }
187
+ const raw = await readFile(manifestPath, 'utf-8');
188
+ return parsePluginManifest(raw);
189
+ }
190
+ /**
191
+ * Copies all plugin files from source to destination.
192
+ *
193
+ * @param sourceDir - Source directory
194
+ * @param destDir - Destination directory
195
+ */
196
+ async function copyPluginFiles(sourceDir, destDir) {
197
+ await cp(sourceDir, destDir, { recursive: true });
198
+ }
199
+ // ---------------------------------------------------------------------------
200
+ // List
201
+ // ---------------------------------------------------------------------------
202
+ /**
203
+ * Lists all installed plugins by reading `.nerva/plugins/`.
204
+ *
205
+ * @param projectDir - Absolute path to the project root
206
+ * @returns Array of installed plugin summaries
207
+ */
208
+ export async function listPlugins(projectDir) {
209
+ const dir = pluginsDir(projectDir);
210
+ if (!existsSync(dir)) {
211
+ return [];
212
+ }
213
+ const entries = await readdir(dir, { withFileTypes: true });
214
+ const plugins = [];
215
+ for (const entry of entries) {
216
+ if (!entry.isDirectory()) {
217
+ continue;
218
+ }
219
+ const manifest = await readManifestSafe(join(dir, entry.name));
220
+ if (manifest) {
221
+ plugins.push({
222
+ name: manifest.name,
223
+ version: manifest.version,
224
+ types: manifest.templates.map((t) => t.type),
225
+ });
226
+ }
227
+ }
228
+ return plugins;
229
+ }
230
+ /**
231
+ * Reads a manifest from a plugin directory, returning null on failure.
232
+ *
233
+ * @param pluginDir - Absolute path to a plugin directory
234
+ * @returns Parsed manifest or null if unreadable
235
+ */
236
+ async function readManifestSafe(pluginDir) {
237
+ const manifestPath = join(pluginDir, MANIFEST_FILENAME);
238
+ if (!existsSync(manifestPath)) {
239
+ return null;
240
+ }
241
+ try {
242
+ const raw = await readFile(manifestPath, 'utf-8');
243
+ return parsePluginManifest(raw);
244
+ }
245
+ catch {
246
+ return null;
247
+ }
248
+ }
249
+ // ---------------------------------------------------------------------------
250
+ // Remove
251
+ // ---------------------------------------------------------------------------
252
+ /**
253
+ * Removes an installed plugin by name.
254
+ *
255
+ * @param name - Plugin name to remove
256
+ * @param projectDir - Absolute path to the project root
257
+ * @throws {Error} If the plugin is not installed
258
+ */
259
+ export async function removePlugin(name, projectDir) {
260
+ const dest = pluginPath(projectDir, name);
261
+ if (!existsSync(dest)) {
262
+ throw new Error(`Plugin "${name}" is not installed`);
263
+ }
264
+ await rm(dest, { recursive: true, force: true });
265
+ }
266
+ // ---------------------------------------------------------------------------
267
+ // Command registration
268
+ // ---------------------------------------------------------------------------
269
+ /**
270
+ * Registers the `nerva plugin` command group with the CLI program.
271
+ *
272
+ * Sub-commands:
273
+ * - `nerva plugin install <source>` — install from npm or local path
274
+ * - `nerva plugin list` — list installed plugins
275
+ * - `nerva plugin remove <name>` — remove an installed plugin
276
+ *
277
+ * @param program - The root commander program
278
+ */
279
+ export function registerPluginCommand(program) {
280
+ const pluginCmd = program
281
+ .command('plugin')
282
+ .description('Manage template plugins for nerva generate');
283
+ pluginCmd
284
+ .command('install')
285
+ .description('Install a plugin from npm or a local path')
286
+ .argument('<source>', 'npm package name or local directory path')
287
+ .action(async (source) => {
288
+ const manifest = await installPlugin(source, process.cwd());
289
+ const types = manifest.templates.map((t) => t.type).join(', ');
290
+ console.log(`Installed plugin "${manifest.name}" v${manifest.version}`);
291
+ console.log(`Available types: ${types}`);
292
+ });
293
+ pluginCmd
294
+ .command('list')
295
+ .alias('ls')
296
+ .description('List installed plugins')
297
+ .action(async () => {
298
+ const plugins = await listPlugins(process.cwd());
299
+ if (plugins.length === 0) {
300
+ console.log('No plugins installed.');
301
+ return;
302
+ }
303
+ console.log(`\nInstalled plugins:\n`);
304
+ for (const plugin of plugins) {
305
+ console.log(` ${plugin.name} (v${plugin.version})`);
306
+ console.log(` types: ${plugin.types.join(', ')}`);
307
+ }
308
+ console.log('');
309
+ });
310
+ pluginCmd
311
+ .command('remove')
312
+ .description('Remove an installed plugin')
313
+ .argument('<name>', 'Plugin name to remove')
314
+ .action(async (name) => {
315
+ await removePlugin(name, process.cwd());
316
+ console.log(`Removed plugin "${name}"`);
317
+ });
318
+ }
319
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/commands/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * `nerva test` command.
3
+ *
4
+ * Detects the project language from nerva.yaml and runs the appropriate
5
+ * test runner (pytest for Python, vitest for TypeScript).
6
+ */
7
+ import { Command } from 'commander';
8
+ import { type ProjectLang } from '../config/nerva-yaml.js';
9
+ /**
10
+ * Builds the full argument list for the test runner, including optional coverage flag.
11
+ *
12
+ * @param lang - Project language
13
+ * @param coverage - Whether to enable coverage reporting
14
+ * @returns Tuple of [command, args]
15
+ */
16
+ export declare function buildTestCommand(lang: ProjectLang, coverage: boolean): {
17
+ cmd: string;
18
+ args: string[];
19
+ };
20
+ /**
21
+ * Detects the project language from nerva.yaml in the given directory.
22
+ *
23
+ * @param projectDir - Absolute path to project root
24
+ * @returns Detected project language
25
+ * @throws {Error} If nerva.yaml is missing or invalid
26
+ */
27
+ export declare function detectLanguage(projectDir: string): Promise<ProjectLang>;
28
+ /**
29
+ * Runs the test command as a child process and returns its exit code.
30
+ *
31
+ * @param projectDir - Working directory for the test runner
32
+ * @param cmd - Command to execute
33
+ * @param args - Arguments to pass
34
+ * @returns Exit code from the test runner (0 = success)
35
+ */
36
+ export declare function runTestProcess(projectDir: string, cmd: string, args: string[]): Promise<number>;
37
+ /**
38
+ * Executes the test command: detects language, runs the appropriate test runner,
39
+ * and prints a trace summary.
40
+ *
41
+ * @param projectDir - Absolute path to project root
42
+ * @param coverage - Whether to enable coverage
43
+ * @returns Exit code from the test runner
44
+ */
45
+ export declare function executeTest(projectDir: string, coverage: boolean): Promise<number>;
46
+ /**
47
+ * Registers the `nerva test` command with the CLI program.
48
+ *
49
+ * @param program - The root commander program
50
+ */
51
+ export declare function registerTestCommand(program: Command): void;
52
+ //# sourceMappingURL=test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../src/commands/test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAa5E;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,OAAO,GAChB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAYjC;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAG7E;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC,MAAM,CAAC,CAiBjB;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,OAAO,GAChB,OAAO,CAAC,MAAM,CAAC,CAsBjB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAU1D"}