netforge 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +55 -2
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +71 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/setup.d.ts +12 -0
- package/dist/setup.js +118 -0
- package/dist/skills.d.ts +9 -0
- package/dist/skills.js +33 -0
- package/package.json +61 -3
- package/skills/netforge-add-content/SKILL.md +44 -0
- package/skills/netforge-create-game/SKILL.md +35 -0
- package/skills/netforge-play-locally/SKILL.md +23 -0
- package/skills/netforge-playtest/SKILL.md +35 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NetNet Capital Management
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,56 @@
|
|
|
1
|
-
#
|
|
1
|
+
# netforge
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The Net Forge command line for coding agents: one command makes a project
|
|
4
|
+
ready for Claude Code, Cursor, VS Code, Codex and Gemini CLI, and the
|
|
5
|
+
package ships Agent Skills for building Net Forge games.
|
|
6
|
+
|
|
7
|
+
## Set up a project for your agent
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npx netforge setup agent
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
It writes, without replacing anything you already have:
|
|
14
|
+
|
|
15
|
+
| File | For |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `AGENTS.md` | Every agent: where the game lives, the rules, how to validate and playtest. Added as a section when the file exists. |
|
|
18
|
+
| `CLAUDE.md` | Claude Code: imports `AGENTS.md`. |
|
|
19
|
+
| `.cursor/rules/netforge.mdc` | Cursor rules. |
|
|
20
|
+
| `.mcp.json`, `.cursor/mcp.json`, `.vscode/mcp.json` | The `@netforge/mcp` server, merged next to your other servers. |
|
|
21
|
+
|
|
22
|
+
Then it prints the install lines for agents that keep MCP config outside
|
|
23
|
+
the project, such as `claude mcp add netforge -- npx -y @netforge/mcp`.
|
|
24
|
+
|
|
25
|
+
## Agent Skills
|
|
26
|
+
|
|
27
|
+
Four skills, each a folder with a `SKILL.md`:
|
|
28
|
+
|
|
29
|
+
| Skill | When the agent uses it |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `netforge-create-game` | Start a game from one of ten genre templates. |
|
|
32
|
+
| `netforge-add-content` | Add an enemy, weapon, upgrade, earn rule or store item. |
|
|
33
|
+
| `netforge-playtest` | Validate and run bot playtests after every change. |
|
|
34
|
+
| `netforge-play-locally` | Play the game in the browser with play money. |
|
|
35
|
+
|
|
36
|
+
Install them in one line with `npx skills add netnetdev/net-forge`, or copy
|
|
37
|
+
them yourself:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
npx netforge skills list
|
|
41
|
+
npx netforge skills copy <your agent's skills folder>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Start a new game
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
npm create netforge@latest
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Docs
|
|
51
|
+
|
|
52
|
+
https://forge.netnet.capital/docs/agents
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export declare const HELP = "netforge: set up a project for coding agents that build Net Forge games.\n\nUsage:\n netforge setup agent [dir] Write AGENTS.md, CLAUDE.md, .cursor/rules/netforge.mdc,\n .mcp.json, .cursor/mcp.json and .vscode/mcp.json (merged,\n never replacing other servers) and print the install lines.\n netforge skills list List the bundled Agent Skills.\n netforge skills path Print the folder that holds them.\n netforge skills copy <dir> Copy every skill folder into <dir> (your agent's skills folder).\n\nStart a new game: npm create netforge@latest\nDocs: https://forge.netnet.capital/docs";
|
|
3
|
+
export declare function run(argv: readonly string[], io: {
|
|
4
|
+
out(s: string): void;
|
|
5
|
+
err(s: string): void;
|
|
6
|
+
cwd: string;
|
|
7
|
+
}): number;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// netforge: the Net Forge command line for coding agents.
|
|
3
|
+
//
|
|
4
|
+
// netforge setup agent [dir] write AGENTS.md, CLAUDE.md, Cursor rules and MCP configs
|
|
5
|
+
// netforge skills path print where the bundled Agent Skills are
|
|
6
|
+
// netforge skills list list the bundled skills
|
|
7
|
+
// netforge skills copy <dir> copy the skills into a skills folder
|
|
8
|
+
// netforge help
|
|
9
|
+
import { realpathSync } from "node:fs";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { INSTALL_LINES, setupAgent } from "./setup.js";
|
|
13
|
+
import { copySkills, listSkills, skillsDir } from "./skills.js";
|
|
14
|
+
export const HELP = `netforge: set up a project for coding agents that build Net Forge games.
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
netforge setup agent [dir] Write AGENTS.md, CLAUDE.md, .cursor/rules/netforge.mdc,
|
|
18
|
+
.mcp.json, .cursor/mcp.json and .vscode/mcp.json (merged,
|
|
19
|
+
never replacing other servers) and print the install lines.
|
|
20
|
+
netforge skills list List the bundled Agent Skills.
|
|
21
|
+
netforge skills path Print the folder that holds them.
|
|
22
|
+
netforge skills copy <dir> Copy every skill folder into <dir> (your agent's skills folder).
|
|
23
|
+
|
|
24
|
+
Start a new game: npm create netforge@latest
|
|
25
|
+
Docs: https://forge.netnet.capital/docs`;
|
|
26
|
+
export function run(argv, io) {
|
|
27
|
+
const [cmd, sub, arg] = argv;
|
|
28
|
+
if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
29
|
+
io.out(HELP);
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
if (cmd === "setup" && (sub === "agent" || sub === undefined)) {
|
|
33
|
+
const dir = resolve(io.cwd, arg ?? ".");
|
|
34
|
+
const r = setupAgent(dir);
|
|
35
|
+
for (const f of r.written)
|
|
36
|
+
io.out(`wrote ${f}`);
|
|
37
|
+
for (const f of r.skipped)
|
|
38
|
+
io.out(`skipped ${f}`);
|
|
39
|
+
io.out("");
|
|
40
|
+
io.out(INSTALL_LINES);
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
if (cmd === "skills") {
|
|
44
|
+
if (sub === "path") {
|
|
45
|
+
io.out(skillsDir());
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
if (sub === "list" || sub === undefined) {
|
|
49
|
+
for (const s of listSkills())
|
|
50
|
+
io.out(`${s.name.padEnd(26)} ${s.description}`);
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
if (sub === "copy" && arg) {
|
|
54
|
+
for (const f of copySkills(resolve(io.cwd, arg)))
|
|
55
|
+
io.out(`copied ${f}`);
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
io.err(`Unknown command: ${argv.join(" ")}\n\n${HELP}`);
|
|
60
|
+
return 2;
|
|
61
|
+
}
|
|
62
|
+
const isMain = (() => {
|
|
63
|
+
try {
|
|
64
|
+
return process.argv[1] !== undefined && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
})();
|
|
70
|
+
if (isMain)
|
|
71
|
+
process.exitCode = run(process.argv.slice(2), { out: (s) => console.log(s), err: (s) => console.error(s), cwd: process.cwd() });
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** netforge: project setup and Agent Skills for coding agents that build Net Forge games. */
|
|
2
|
+
export { setupAgent, AGENTS_SECTION, CLAUDE_MD, CURSOR_RULE, INSTALL_LINES, MCP_PACKAGE, type SetupResult } from "./setup.js";
|
|
3
|
+
export { copySkills, listSkills, skillsDir, type SkillInfo } from "./skills.js";
|
|
4
|
+
export { run, HELP } from "./cli.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** netforge: project setup and Agent Skills for coding agents that build Net Forge games. */
|
|
2
|
+
export { setupAgent, AGENTS_SECTION, CLAUDE_MD, CURSOR_RULE, INSTALL_LINES, MCP_PACKAGE } from "./setup.js";
|
|
3
|
+
export { copySkills, listSkills, skillsDir } from "./skills.js";
|
|
4
|
+
export { run, HELP } from "./cli.js";
|
package/dist/setup.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const MCP_PACKAGE = "@netforge/mcp";
|
|
2
|
+
export declare const SKILLS_SOURCE = "netnetdev/net-forge";
|
|
3
|
+
export declare const DOCS_URL = "https://forge.netnet.capital/docs";
|
|
4
|
+
export declare const AGENTS_SECTION = "<!-- netforge:agents -->\n## Net Forge\n\nThis project builds a game on Net Forge, an MIT TypeScript engine for\nmultiplayer browser games. One simulation (ECS, Rapier physics, fixed tick,\nsnapshots) runs in the browser and on the room server, and a template draws,\nnetworks and simulates the game.\n\n- The whole game is `src/game.ts`: declarations (game, prefab, weapon,\n upgrade, director, system, earn.rule, earn.stake, store.sku and the genre's\n own). It may import only `@netforge/engine`.\n- Ids are lower_snake_case. Every game declares at least one `earn.rule`\n and one `earn.stake`.\n- Never write renderers, netcode or physics. Models are asset library ids\n such as `\"style.lowpoly.ghost\"`, never URLs or paths.\n- After each change: `netforge_validate_game`, then `netforge_run_playtest`\n (MCP server `@netforge/mcp`). To try it in a browser:\n `netforge_play_locally` or `npx kit-play`.\n- Look things up with `netforge_search_docs` and `netforge_explain_api`\n before guessing. Docs: https://forge.netnet.capital/docs\n";
|
|
5
|
+
export declare const CLAUDE_MD = "@AGENTS.md\n";
|
|
6
|
+
export declare const CURSOR_RULE = "---\ndescription: Net Forge game rules (src/game.ts, validation, playtests)\nalwaysApply: true\n---\n\nRead AGENTS.md. The game is src/game.ts and it may import only @netforge/engine.\nValidate every change with netforge_validate_game and playtest with\nnetforge_run_playtest before calling it done.\n";
|
|
7
|
+
export interface SetupResult {
|
|
8
|
+
readonly written: string[];
|
|
9
|
+
readonly skipped: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function setupAgent(dir: string): SetupResult;
|
|
12
|
+
export declare const INSTALL_LINES = "MCP server (@netforge/mcp, runs locally, no keys):\n Claude Code claude mcp add netforge -- npx -y @netforge/mcp\n Cursor written to .cursor/mcp.json (project); or add the same entry to ~/.cursor/mcp.json\n VS Code code --add-mcp '{\"name\":\"netforge\",\"command\":\"npx\",\"args\":[\"-y\",\"@netforge/mcp\"]}'\n Codex codex mcp add netforge -- npx -y @netforge/mcp\n Gemini CLI gemini mcp add netforge npx -y @netforge/mcp\n\nAgent Skills (create a game, add content, playtest, play locally):\n npx skills add netnetdev/net-forge\n or copy them from: npx netforge skills path\n\nDocs: https://forge.netnet.capital/docs";
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// `netforge setup agent`: makes a project ready for a coding agent. Writes
|
|
2
|
+
// AGENTS.md (or adds a Net Forge section to an existing one), a CLAUDE.md
|
|
3
|
+
// that imports it, Cursor rules, and the MCP server entry in .mcp.json,
|
|
4
|
+
// .cursor/mcp.json and .vscode/mcp.json (merged into existing files, never replacing other
|
|
5
|
+
// servers). Prints the install lines for the agents that configure MCP
|
|
6
|
+
// outside the project.
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
export const MCP_PACKAGE = "@netforge/mcp";
|
|
10
|
+
export const SKILLS_SOURCE = "netnetdev/net-forge";
|
|
11
|
+
export const DOCS_URL = "https://forge.netnet.capital/docs";
|
|
12
|
+
const MARK = "<!-- netforge:agents -->";
|
|
13
|
+
export const AGENTS_SECTION = `${MARK}
|
|
14
|
+
## Net Forge
|
|
15
|
+
|
|
16
|
+
This project builds a game on Net Forge, an MIT TypeScript engine for
|
|
17
|
+
multiplayer browser games. One simulation (ECS, Rapier physics, fixed tick,
|
|
18
|
+
snapshots) runs in the browser and on the room server, and a template draws,
|
|
19
|
+
networks and simulates the game.
|
|
20
|
+
|
|
21
|
+
- The whole game is \`src/game.ts\`: declarations (game, prefab, weapon,
|
|
22
|
+
upgrade, director, system, earn.rule, earn.stake, store.sku and the genre's
|
|
23
|
+
own). It may import only \`@netforge/engine\`.
|
|
24
|
+
- Ids are lower_snake_case. Every game declares at least one \`earn.rule\`
|
|
25
|
+
and one \`earn.stake\`.
|
|
26
|
+
- Never write renderers, netcode or physics. Models are asset library ids
|
|
27
|
+
such as \`"style.lowpoly.ghost"\`, never URLs or paths.
|
|
28
|
+
- After each change: \`netforge_validate_game\`, then \`netforge_run_playtest\`
|
|
29
|
+
(MCP server \`${MCP_PACKAGE}\`). To try it in a browser:
|
|
30
|
+
\`netforge_play_locally\` or \`npx kit-play\`.
|
|
31
|
+
- Look things up with \`netforge_search_docs\` and \`netforge_explain_api\`
|
|
32
|
+
before guessing. Docs: ${DOCS_URL}
|
|
33
|
+
`;
|
|
34
|
+
export const CLAUDE_MD = "@AGENTS.md\n";
|
|
35
|
+
export const CURSOR_RULE = `---
|
|
36
|
+
description: Net Forge game rules (src/game.ts, validation, playtests)
|
|
37
|
+
alwaysApply: true
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
Read AGENTS.md. The game is src/game.ts and it may import only @netforge/engine.
|
|
41
|
+
Validate every change with netforge_validate_game and playtest with
|
|
42
|
+
netforge_run_playtest before calling it done.
|
|
43
|
+
`;
|
|
44
|
+
const server = { command: "npx", args: ["-y", MCP_PACKAGE] };
|
|
45
|
+
function readJson(file) {
|
|
46
|
+
if (!existsSync(file))
|
|
47
|
+
return {};
|
|
48
|
+
try {
|
|
49
|
+
const v = JSON.parse(readFileSync(file, "utf8"));
|
|
50
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function write(dir, rel, text, r) {
|
|
57
|
+
mkdirSync(dirname(join(dir, rel)), { recursive: true });
|
|
58
|
+
writeFileSync(join(dir, rel), text);
|
|
59
|
+
r.written.push(rel);
|
|
60
|
+
}
|
|
61
|
+
/** Adds the netforge server under `key` in a JSON config, keeping everything else. */
|
|
62
|
+
function mergeMcp(dir, rel, key, entry, r) {
|
|
63
|
+
const file = join(dir, rel);
|
|
64
|
+
const cfg = readJson(file);
|
|
65
|
+
if (cfg === null) {
|
|
66
|
+
r.skipped.push(`${rel} (not plain JSON, perhaps comments; left as it is: add the netforge entry by hand)`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const current = cfg[key];
|
|
70
|
+
if (current !== undefined && (current === null || typeof current !== "object" || Array.isArray(current))) {
|
|
71
|
+
r.skipped.push(`${rel} ("${key}" is not an object, left as it is)`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const servers = (current ?? {});
|
|
75
|
+
if (servers.netforge) {
|
|
76
|
+
r.skipped.push(`${rel} (already has netforge)`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
cfg[key] = { ...servers, netforge: entry };
|
|
80
|
+
write(dir, rel, JSON.stringify(cfg, null, 2) + "\n", r);
|
|
81
|
+
}
|
|
82
|
+
export function setupAgent(dir) {
|
|
83
|
+
const r = { written: [], skipped: [] };
|
|
84
|
+
const agents = join(dir, "AGENTS.md");
|
|
85
|
+
if (!existsSync(agents))
|
|
86
|
+
write(dir, "AGENTS.md", `# Notes for coding agents\n\n${AGENTS_SECTION}`, r);
|
|
87
|
+
else if (readFileSync(agents, "utf8").includes(MARK))
|
|
88
|
+
r.skipped.push("AGENTS.md (already has the Net Forge section)");
|
|
89
|
+
else
|
|
90
|
+
write(dir, "AGENTS.md", `${readFileSync(agents, "utf8").trimEnd()}\n\n${AGENTS_SECTION}`, r);
|
|
91
|
+
const claude = join(dir, "CLAUDE.md");
|
|
92
|
+
if (!existsSync(claude))
|
|
93
|
+
write(dir, "CLAUDE.md", CLAUDE_MD, r);
|
|
94
|
+
else if (/@AGENTS\.md/.test(readFileSync(claude, "utf8")))
|
|
95
|
+
r.skipped.push("CLAUDE.md (already imports AGENTS.md)");
|
|
96
|
+
else
|
|
97
|
+
write(dir, "CLAUDE.md", `${readFileSync(claude, "utf8").trimEnd()}\n\n${CLAUDE_MD}`, r);
|
|
98
|
+
if (existsSync(join(dir, ".cursor", "rules", "netforge.mdc")))
|
|
99
|
+
r.skipped.push(".cursor/rules/netforge.mdc (exists)");
|
|
100
|
+
else
|
|
101
|
+
write(dir, ".cursor/rules/netforge.mdc", CURSOR_RULE, r);
|
|
102
|
+
mergeMcp(dir, ".mcp.json", "mcpServers", server, r);
|
|
103
|
+
mergeMcp(dir, ".cursor/mcp.json", "mcpServers", server, r);
|
|
104
|
+
mergeMcp(dir, ".vscode/mcp.json", "servers", { type: "stdio", ...server }, r);
|
|
105
|
+
return r;
|
|
106
|
+
}
|
|
107
|
+
export const INSTALL_LINES = `MCP server (${MCP_PACKAGE}, runs locally, no keys):
|
|
108
|
+
Claude Code claude mcp add netforge -- npx -y ${MCP_PACKAGE}
|
|
109
|
+
Cursor written to .cursor/mcp.json (project); or add the same entry to ~/.cursor/mcp.json
|
|
110
|
+
VS Code code --add-mcp '{"name":"netforge","command":"npx","args":["-y","${MCP_PACKAGE}"]}'
|
|
111
|
+
Codex codex mcp add netforge -- npx -y ${MCP_PACKAGE}
|
|
112
|
+
Gemini CLI gemini mcp add netforge npx -y ${MCP_PACKAGE}
|
|
113
|
+
|
|
114
|
+
Agent Skills (create a game, add content, playtest, play locally):
|
|
115
|
+
npx skills add ${SKILLS_SOURCE}
|
|
116
|
+
or copy them from: npx netforge skills path
|
|
117
|
+
|
|
118
|
+
Docs: ${DOCS_URL}`;
|
package/dist/skills.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The package's skills folder (next to dist/ and src/). */
|
|
2
|
+
export declare function skillsDir(): string;
|
|
3
|
+
export interface SkillInfo {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly description: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function listSkills(): SkillInfo[];
|
|
8
|
+
/** Copies every skill folder into `to`; returns the folders copied. */
|
|
9
|
+
export declare function copySkills(to: string): string[];
|
package/dist/skills.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// The Agent Skills shipped in this package (skills/<name>/SKILL.md).
|
|
2
|
+
import { cpSync, existsSync, readdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
/** The package's skills folder (next to dist/ and src/). */
|
|
6
|
+
export function skillsDir() {
|
|
7
|
+
return join(dirname(fileURLToPath(import.meta.url)), "..", "skills");
|
|
8
|
+
}
|
|
9
|
+
export function listSkills() {
|
|
10
|
+
const root = skillsDir();
|
|
11
|
+
if (!existsSync(root))
|
|
12
|
+
return [];
|
|
13
|
+
return readdirSync(root)
|
|
14
|
+
.filter((d) => existsSync(join(root, d, "SKILL.md")))
|
|
15
|
+
.sort()
|
|
16
|
+
.map((d) => {
|
|
17
|
+
const text = readFileSync(join(root, d, "SKILL.md"), "utf8");
|
|
18
|
+
const field = (k) => new RegExp(`^${k}:\\s*(.+)$`, "m").exec(text)?.[1]?.trim() ?? "";
|
|
19
|
+
return { name: field("name") || d, description: field("description") };
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** Copies every skill folder into `to`; returns the folders copied. */
|
|
23
|
+
export function copySkills(to) {
|
|
24
|
+
const root = skillsDir();
|
|
25
|
+
const out = [];
|
|
26
|
+
for (const s of readdirSync(root)) {
|
|
27
|
+
if (!existsSync(join(root, s, "SKILL.md")))
|
|
28
|
+
continue;
|
|
29
|
+
cpSync(join(root, s), join(to, s), { recursive: true });
|
|
30
|
+
out.push(join(to, s));
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "netforge",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Net Forge command line for coding agents: `npx netforge setup agent` writes AGENTS.md, CLAUDE.md, Cursor rules and MCP configs into a game project, and ships Agent Skills for building Net Forge games.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"netforge": "./dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"skills",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/netnetdev/net-forge.git",
|
|
32
|
+
"directory": "packages/netforge"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/netnetdev/net-forge/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://forge.netnet.capital/docs/agents",
|
|
38
|
+
"keywords": [
|
|
39
|
+
"net-forge",
|
|
40
|
+
"game-engine",
|
|
41
|
+
"ai-agents",
|
|
42
|
+
"agent-skills",
|
|
43
|
+
"agents-md",
|
|
44
|
+
"mcp",
|
|
45
|
+
"cli",
|
|
46
|
+
"vibe-coding",
|
|
47
|
+
"browser-game",
|
|
48
|
+
"multiplayer",
|
|
49
|
+
"gamedev"
|
|
50
|
+
],
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=20"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "22.20.1",
|
|
56
|
+
"typescript": "5.8.3",
|
|
57
|
+
"vitest": "3.2.7"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "tsc -p tsconfig.json",
|
|
61
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json",
|
|
62
|
+
"test": "vitest run"
|
|
63
|
+
}
|
|
6
64
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: netforge-add-content
|
|
3
|
+
description: Add or change content in a Net Forge game's src/game.ts, such as a new enemy, weapon, upgrade, earn rule, store item or spawn pacing. Use when the user asks to add an enemy, a boss, a weapon, a power-up, a reward or to rebalance a Net Forge game.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Add content to a Net Forge game
|
|
7
|
+
|
|
8
|
+
All content is declared in `src/game.ts` with functions imported from
|
|
9
|
+
`@netforge/engine`. Look up any declaration before using it:
|
|
10
|
+
`netforge_explain_api { "name": "weapon" }` or
|
|
11
|
+
`netforge_search_docs { "query": "spawn director curves" }`.
|
|
12
|
+
|
|
13
|
+
## Common declarations (action templates)
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { earn, prefab, weapon } from "@netforge/engine";
|
|
17
|
+
|
|
18
|
+
// An enemy. model is an asset library id, never a URL.
|
|
19
|
+
prefab("brute", { model: "style.lowpoly.orc_brute", health: 160, speed: 2.2, radius: 0.8, damage: 18, ai: "tank", mass: 6, drops: ["ember_mid"] });
|
|
20
|
+
|
|
21
|
+
// A weapon that fires by itself, with level-up steps.
|
|
22
|
+
weapon("spark_bolt", { kind: "bolt", damage: 14, cooldown: 0.6, range: 14, speed: 24, pierce: 1, knockback: 2,
|
|
23
|
+
levels: [{ count: 1 }, { damage: "+40%" }, { cooldown: "-20%" }] });
|
|
24
|
+
|
|
25
|
+
// A reward: pays when the event happens n times in a run.
|
|
26
|
+
earn.rule("first_light", { when: "wave.cleared", n: 2, pay: 5, oncePer: "run" });
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Board templates (tower defense, idle, cards, collector) use their own
|
|
30
|
+
declarations, such as `tower(...)`, `waves(...)`, `plant(...)`, `card(...)`,
|
|
31
|
+
`creature(...)` and `move(...)`. Read the template's own `src/game.ts` for
|
|
32
|
+
the pattern and copy it.
|
|
33
|
+
|
|
34
|
+
## Checklist
|
|
35
|
+
|
|
36
|
+
1. New ids are lower_snake_case and unique per kind.
|
|
37
|
+
2. Anything an enemy drops is a declared pickup prefab.
|
|
38
|
+
3. A new enemy appears only when the spawn `director({ pool })` (or the
|
|
39
|
+
template's own spawner) names it.
|
|
40
|
+
4. Run `netforge_validate_game` and fix every error it returns; each error
|
|
41
|
+
has the likely line and a hint.
|
|
42
|
+
5. Run `netforge_run_playtest` and act on its findings.
|
|
43
|
+
|
|
44
|
+
Docs: https://forge.netnet.capital/docs
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: netforge-create-game
|
|
3
|
+
description: Start a new multiplayer browser game with Net Forge from one of ten finished genre templates (survivor-like, arena shooter, tower defense, dungeon crawl, idle, platformer, card battler, monster collector, kart racer, party). Use when the user asks to make, scaffold or start a browser game, a multiplayer game, a co-op game or an .io-style game.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Create a Net Forge game
|
|
7
|
+
|
|
8
|
+
Net Forge games are one declarative file, `src/game.ts`, on top of a
|
|
9
|
+
template that already has rendering (three.js, WebGPU with a WebGL2
|
|
10
|
+
fallback), multiplayer netcode, physics, input, HUD and bots.
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
1. Pick the template that matches the request. With the MCP server, call
|
|
15
|
+
`netforge_list_templates`; without it, use one of these ids: `survivor`,
|
|
16
|
+
`arena`, `tower-defense`, `dungeon`, `idle`, `platformer`, `cards`,
|
|
17
|
+
`collector`, `racer`, `party`.
|
|
18
|
+
2. Create the folder:
|
|
19
|
+
- MCP: `netforge_create_game { "template": "survivor", "directory": "/abs/path/my-game" }`
|
|
20
|
+
- CLI: `npm create netforge@latest my-game -- --template survivor --yes`
|
|
21
|
+
3. `cd my-game && npm install`
|
|
22
|
+
4. Read `AGENTS.md` in the new folder, then make the user's changes in
|
|
23
|
+
`src/game.ts` (see the netforge-add-content skill).
|
|
24
|
+
5. Validate and playtest (see the netforge-playtest skill), then let the
|
|
25
|
+
user play it (see the netforge-play-locally skill).
|
|
26
|
+
|
|
27
|
+
## Rules
|
|
28
|
+
|
|
29
|
+
- `src/game.ts` may import only `@netforge/engine`.
|
|
30
|
+
- Ids are lower_snake_case.
|
|
31
|
+
- Every game keeps at least one `earn.rule(...)` and one `earn.stake(...)`.
|
|
32
|
+
- Keep `game({ style })`: the art style the template draws in.
|
|
33
|
+
- Do not write renderers, netcode or physics, and never use asset URLs.
|
|
34
|
+
|
|
35
|
+
Docs: https://forge.netnet.capital/docs
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: netforge-play-locally
|
|
3
|
+
description: Run a Net Forge game on this computer in the browser with play money, for one or more players, with no wallet, chain or servers. Use when the user wants to try, see or play their Net Forge game, or test multiplayer with several browser tabs.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Play a Net Forge game locally
|
|
7
|
+
|
|
8
|
+
1. In the game folder (it has `netnet.json` and `src/game.ts`), run
|
|
9
|
+
`npm install` once.
|
|
10
|
+
2. Start it:
|
|
11
|
+
- MCP: `netforge_play_locally { "projectDir": "/abs/path/my-game" }` returns the URL.
|
|
12
|
+
- CLI: `npm run play` or `npx kit-play` in the folder, which opens
|
|
13
|
+
`http://127.0.0.1:4663`.
|
|
14
|
+
3. Open the URL in more tabs or windows for more players. `--host 0.0.0.0`
|
|
15
|
+
lets phones on the same network join.
|
|
16
|
+
4. Stop it with `netforge_play_locally { "projectDir": "...", "stop": true }`
|
|
17
|
+
or Ctrl+C.
|
|
18
|
+
|
|
19
|
+
kit play builds the template's browser client with `src/game.ts` and runs a
|
|
20
|
+
local room host. Coins are play money and nothing pays out. It runs the
|
|
21
|
+
game's code with your permissions, so play only code you trust.
|
|
22
|
+
|
|
23
|
+
Docs: https://forge.netnet.capital/docs
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: netforge-playtest
|
|
3
|
+
description: Validate a Net Forge game and run headless bot playtests that report kills, waves, deaths, softlocks, earn awards and server milliseconds per tick. Use after every change to a Net Forge game's src/game.ts, or when the user asks whether the game works, is fun or is balanced.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Validate and playtest a Net Forge game
|
|
7
|
+
|
|
8
|
+
## With the MCP server (@netforge/mcp)
|
|
9
|
+
|
|
10
|
+
1. `netforge_validate_game { "projectDir": "/abs/path/my-game" }`
|
|
11
|
+
returns `ok` and, for each problem, the stage, the engine's message, the
|
|
12
|
+
likely line in `src/game.ts` and a hint. Fix and repeat until `ok` is true.
|
|
13
|
+
2. `netforge_run_playtest { "projectDir": "/abs/path/my-game", "bots": 4, "seconds": 120 }`
|
|
14
|
+
runs bots on the same simulation the room server uses and returns the
|
|
15
|
+
report plus plain findings. Pass `"bot": "human"` for players who react
|
|
16
|
+
like people (the fun metrics), and `"response_format": "detailed"` for
|
|
17
|
+
every metric.
|
|
18
|
+
3. Read `findings` first. Fix softlocks, failed checks, missing earn and a
|
|
19
|
+
high p99 tick cost before anything else.
|
|
20
|
+
|
|
21
|
+
## Without MCP
|
|
22
|
+
|
|
23
|
+
The playtest is `engine.test.run` from `@netforge/engine`:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { engine } from "@netforge/engine";
|
|
27
|
+
const r = await engine.test.run({ ticks: 30 * 120, bots: 4, seed: 1 });
|
|
28
|
+
console.log(r.kills, r.wave, r.softlock, r.msPerTick, r.failed);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Run it from a test file in the template package after importing the
|
|
32
|
+
template's `src/manifest.ts`, the way each template's `test/harness.test.ts`
|
|
33
|
+
does.
|
|
34
|
+
|
|
35
|
+
Docs: https://forge.netnet.capital/docs
|