jgengine 0.8.0 → 0.8.2

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/dist/cli/index.js CHANGED
@@ -7,23 +7,28 @@ import { createRequire } from "node:module";
7
7
  import { runCreate } from "../create.js";
8
8
  import { runDoctor } from "../doctor.js";
9
9
  import { cliVersion, findUp, readPackageJson } from "../pkg.js";
10
+ import { runSkills } from "../skills.js";
10
11
  const ENGINE_PACKAGES = ["core", "react", "shell", "ws", "sql", "convex", "node", "assets"];
11
- const HELP = `jgengine ${cliVersion()} — the JGengine command line
12
- Pure-TypeScript, genre-agnostic game engine SDK. Packages: ${ENGINE_PACKAGES.map((name) => `@jgengine/${name}`).join(", ")}.
12
+ const HELP = `jgengine ${cliVersion()} — pure-TypeScript game engine SDK + agent entry
13
+ Packages: ${ENGINE_PACKAGES.map((name) => `@jgengine/${name}`).join(", ")}.
14
+ docs: https://jgengine.com · source: https://github.com/Noisemaker111/jgengine
13
15
 
14
- usage: jgengine <command> [...args]
16
+ ENTRY (agents: run this when the user says "use jgengine" / "make a game with npx jgengine"):
17
+ npx jgengine create "Game Name"
18
+ cd Game-Name
19
+ # skills install automatically · AGENTS.md briefs you · then build the game
15
20
 
16
- create <dir> scaffold a playable game — flat world, spawned player, HUD, verify test.
17
- [--name <display name>] [--in-repo|--standalone] [--no-install] [--pm bun|npm|pnpm]
18
- doctor [dir] diagnose a game project: version skew, missing peers, unstyled-UI @source gaps, shape drift
19
- skills install the JGengine agent skills (API reference, game-build workflow, verify gate) into .claude/skills
20
- llms [package] print packaged API docs (llms.txt) for an installed @jgengine/* package — agent-ready context
21
- assets [...] delegate to the @jgengine/assets CLI: list, search, pull CC0 3D model packs
22
- versions show CLI + installed @jgengine/* versions
23
- help this map
21
+ usage: jgengine <command> [...args]
24
22
 
25
- docs: https://jgengine.com · source: https://github.com/Noisemaker111/jgengine
26
- new here (human or agent)? run: npx jgengine create my-game && cd my-game && npx jgengine skills
23
+ create "<Game Name>" scaffold playable base + install agent skills into the project
24
+ folder My-Game-Name; name game.config / HUD / title
25
+ [--in-repo|--standalone] [--no-install] [--no-skills] [--pm bun|npm|pnpm]
26
+ skills -p | -g re-install skills only if create was run with --no-skills
27
+ doctor [dir] diagnose version skew, missing peers, unstyled HUD, shape drift
28
+ llms [package] print packaged API docs (llms.txt) — agent-ready
29
+ assets [...] @jgengine/assets CLI: list, search, pull CC0 packs
30
+ versions CLI + installed @jgengine/* versions
31
+ help this map
27
32
  `;
28
33
  function runVersions() {
29
34
  console.log(`jgengine ${cliVersion()}`);
@@ -61,14 +66,6 @@ function runLlms(argv) {
61
66
  process.stdout.write(readFileSync(join(found, "node_modules", packageName, "llms.txt"), "utf8"));
62
67
  return 0;
63
68
  }
64
- function runSkills() {
65
- console.log("installing JGengine agent skills (jgengine-api, jgengine-newgame, jgengine-verify)…");
66
- const result = spawnSync("npx", ["--yes", "skills", "add", "Noisemaker111/jgengine"], {
67
- stdio: "inherit",
68
- shell: process.platform === "win32",
69
- });
70
- return result.status ?? 1;
71
- }
72
69
  function runAssets(argv) {
73
70
  const require = createRequire(import.meta.url);
74
71
  let cliPath;
@@ -91,7 +88,7 @@ switch (command) {
91
88
  process.exit(runDoctor(rest));
92
89
  break;
93
90
  case "skills":
94
- process.exit(runSkills());
91
+ process.exit(runSkills(rest));
95
92
  break;
96
93
  case "llms":
97
94
  process.exit(runLlms(rest));
package/dist/create.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import { type TemplateVariant } from "./templates.js";
2
2
  export declare function writeGame(targetDir: string, id: string, name: string, variant: TemplateVariant): void;
3
- export declare function registerRootGameScript(rootDir: string, id: string): boolean;
3
+ export declare function registerRootGameScript(rootDir: string, id: string, folderName?: string): boolean;
4
4
  export declare function runCreate(argv: string[]): number;
package/dist/create.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
3
- import { basename, dirname, join, relative, resolve } from "node:path";
3
+ import { dirname, join, relative, resolve, sep } from "node:path";
4
4
  import { cliVersion, findWorkspaceRoot, flag, hasFlag, isEngineMonorepo } from "./pkg.js";
5
- import { displayNameFromId, GAME_ID_PATTERN, gameTemplate } from "./templates.js";
5
+ import { installSkills } from "./skills.js";
6
+ import { gameTemplate, parseCreateName } from "./templates.js";
6
7
  export function writeGame(targetDir, id, name, variant) {
7
8
  for (const file of gameTemplate({ id, name, variant, engineVersion: cliVersion() })) {
8
9
  const dest = join(targetDir, file.path);
@@ -10,7 +11,7 @@ export function writeGame(targetDir, id, name, variant) {
10
11
  writeFileSync(dest, file.contents);
11
12
  }
12
13
  }
13
- export function registerRootGameScript(rootDir, id) {
14
+ export function registerRootGameScript(rootDir, id, folderName = id) {
14
15
  const rootPackagePath = join(rootDir, "package.json");
15
16
  const root = JSON.parse(readFileSync(rootPackagePath, "utf8"));
16
17
  const scripts = root.scripts ?? {};
@@ -20,7 +21,7 @@ export function registerRootGameScript(rootDir, id) {
20
21
  const entries = Object.entries(scripts);
21
22
  const games = entries.filter(([k]) => k.startsWith("games:"));
22
23
  const rest = entries.filter(([k]) => !k.startsWith("games:"));
23
- games.push([key, `bun run --cwd Games/${id} dev`]);
24
+ games.push([key, `bun run --cwd Games/${folderName} dev`]);
24
25
  games.sort(([a], [b]) => a.localeCompare(b));
25
26
  root.scripts = Object.fromEntries([...rest, ...games]);
26
27
  writeFileSync(rootPackagePath, `${JSON.stringify(root, null, 2)}\n`);
@@ -32,7 +33,7 @@ function pickPackageManager(preferred) {
32
33
  const probe = spawnSync("bun", ["--version"], { stdio: "ignore", shell: process.platform === "win32" });
33
34
  return probe.status === 0 ? "bun" : "npm";
34
35
  }
35
- const VALUE_FLAGS = new Set(["--name", "--pm"]);
36
+ const VALUE_FLAGS = new Set(["--pm"]);
36
37
  function positionalArg(argv) {
37
38
  for (let index = 0; index < argv.length; index += 1) {
38
39
  const arg = argv[index];
@@ -45,67 +46,105 @@ function positionalArg(argv) {
45
46
  }
46
47
  return undefined;
47
48
  }
49
+ function splitCreateArg(arg) {
50
+ const normalized = arg.replace(/\\/g, "/").replace(/\/+/g, "/");
51
+ const trimmed = normalized.replace(/\/$/, "");
52
+ const slash = trimmed.lastIndexOf("/");
53
+ if (slash < 0)
54
+ return { parentHint: null, titlePart: trimmed };
55
+ return {
56
+ parentHint: trimmed.slice(0, slash) || ".",
57
+ titlePart: trimmed.slice(slash + 1),
58
+ };
59
+ }
60
+ function isInsideDir(child, parent) {
61
+ const resolvedChild = resolve(child);
62
+ const resolvedParent = resolve(parent);
63
+ return resolvedChild === resolvedParent || resolvedChild.startsWith(resolvedParent + sep);
64
+ }
48
65
  export function runCreate(argv) {
49
- const dirArg = positionalArg(argv);
50
- if (dirArg === undefined) {
51
- console.error("usage: jgengine create <dir> [--name <display name>] [--in-repo|--standalone] [--no-install] [--pm bun|npm|pnpm]");
52
- return 1;
53
- }
54
- const targetDir = resolve(dirArg);
55
- const id = basename(targetDir);
56
- if (!GAME_ID_PATTERN.test(id)) {
57
- console.error(`error: directory name "${id}" must be kebab-case (lowercase letters, digits, dashes, starting with a letter)`);
58
- return 1;
59
- }
60
- if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
61
- console.error(`error: ${targetDir} already exists and is not empty`);
62
- return 1;
63
- }
64
- const workspaceRoot = findWorkspaceRoot(dirname(targetDir));
65
- const insideEngineRepo = workspaceRoot !== null && isEngineMonorepo(workspaceRoot);
66
- const variant = hasFlag(argv, "standalone")
67
- ? "standalone"
68
- : hasFlag(argv, "in-repo") || insideEngineRepo
69
- ? "in-repo"
70
- : "standalone";
71
- if (variant === "in-repo" && !insideEngineRepo) {
72
- console.error("error: --in-repo requires the target to live under Games/ inside the jgengine engine monorepo");
66
+ const nameArg = positionalArg(argv);
67
+ if (nameArg === undefined) {
68
+ console.error('usage: jgengine create "<Game Name>" [--in-repo|--standalone] [--no-install] [--no-skills] [--pm bun|npm|pnpm]');
73
69
  return 1;
74
70
  }
75
- const name = flag(argv, "name") ?? displayNameFromId(id);
76
- writeGame(targetDir, id, name, variant);
77
- console.log(`created ${name} (${variant}) with ${gameTemplateFileCount()} files in ${targetDir}`);
78
- if (variant === "in-repo" && workspaceRoot !== null) {
79
- if (registerRootGameScript(workspaceRoot, id)) {
80
- console.log(`registered root script "games:${id}" in ${join(workspaceRoot, "package.json")}`);
71
+ let displayName;
72
+ let folderName;
73
+ let id;
74
+ try {
75
+ const { parentHint, titlePart } = splitCreateArg(nameArg);
76
+ ({ displayName, folderName, id } = parseCreateName(titlePart));
77
+ let parentDir = parentHint !== null ? resolve(parentHint) : process.cwd();
78
+ let targetDir = join(parentDir, folderName);
79
+ const workspaceRoot = findWorkspaceRoot(dirname(targetDir)) ?? findWorkspaceRoot(parentDir) ?? findWorkspaceRoot(process.cwd());
80
+ const insideEngineRepo = workspaceRoot !== null && isEngineMonorepo(workspaceRoot);
81
+ const variant = hasFlag(argv, "standalone")
82
+ ? "standalone"
83
+ : hasFlag(argv, "in-repo") || insideEngineRepo
84
+ ? "in-repo"
85
+ : "standalone";
86
+ if (variant === "in-repo") {
87
+ if (!insideEngineRepo || workspaceRoot === null) {
88
+ console.error("error: --in-repo requires the jgengine engine monorepo (packages/core + Games/)");
89
+ return 1;
90
+ }
91
+ if (parentHint === null) {
92
+ parentDir = join(workspaceRoot, "Games");
93
+ targetDir = join(parentDir, folderName);
94
+ }
95
+ const gamesDir = join(workspaceRoot, "Games");
96
+ if (!isInsideDir(targetDir, gamesDir)) {
97
+ console.error(`error: in-repo games must live under Games/ (got ${targetDir}; expected under ${gamesDir})`);
98
+ return 1;
99
+ }
81
100
  }
101
+ if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
102
+ console.error(`error: ${targetDir} already exists and is not empty`);
103
+ return 1;
104
+ }
105
+ writeGame(targetDir, id, displayName, variant);
106
+ console.log(`created ${displayName} (${variant}) → ${targetDir}`);
107
+ console.log(` folder ${folderName} package ${id} name "${displayName}"`);
108
+ if (variant === "in-repo" && workspaceRoot !== null) {
109
+ if (registerRootGameScript(workspaceRoot, id, folderName)) {
110
+ console.log(`registered root script "games:${id}" in ${join(workspaceRoot, "package.json")}`);
111
+ }
112
+ console.log("\nnext steps:");
113
+ console.log(` bun install # from ${workspaceRoot}`);
114
+ console.log(` bun run games:${id} # play it standalone`);
115
+ console.log(` # agent: open the project and build ${displayName} (skills live in the monorepo)`);
116
+ return 0;
117
+ }
118
+ let installed = false;
119
+ if (!hasFlag(argv, "no-install")) {
120
+ const pm = pickPackageManager(flag(argv, "pm"));
121
+ console.log(`installing dependencies with ${pm}…`);
122
+ const install = spawnSync(pm, ["install"], {
123
+ cwd: targetDir,
124
+ stdio: "inherit",
125
+ shell: process.platform === "win32",
126
+ });
127
+ installed = install.status === 0;
128
+ if (!installed)
129
+ console.error(`warning: ${pm} install failed — run it manually in ${targetDir}`);
130
+ }
131
+ if (!hasFlag(argv, "no-skills")) {
132
+ const skillsStatus = installSkills("project", targetDir);
133
+ if (skillsStatus !== 0) {
134
+ console.error("warning: skill install failed — agent can still use AGENTS.md; retry: npx jgengine skills -p");
135
+ }
136
+ }
137
+ const cdHint = relative(process.cwd(), targetDir) || ".";
82
138
  console.log("\nnext steps:");
83
- console.log(` bun install # from ${workspaceRoot}`);
84
- console.log(` bun run games:${id} # play it standalone`);
139
+ console.log(` cd ${cdHint}`);
140
+ if (!installed)
141
+ console.log(" bun install # or npm install");
142
+ console.log(" bun dev # playable base (flat world, player, HUD)");
143
+ console.log(` # agent: make ${displayName} with jgengine (skills already in the project)`);
85
144
  return 0;
86
145
  }
87
- let installed = false;
88
- if (!hasFlag(argv, "no-install")) {
89
- const pm = pickPackageManager(flag(argv, "pm"));
90
- console.log(`installing dependencies with ${pm}…`);
91
- const install = spawnSync(pm, ["install"], {
92
- cwd: targetDir,
93
- stdio: "inherit",
94
- shell: process.platform === "win32",
95
- });
96
- installed = install.status === 0;
97
- if (!installed)
98
- console.error(`warning: ${pm} install failed — run it manually in ${targetDir}`);
146
+ catch (error) {
147
+ console.error(`error: ${error instanceof Error ? error.message : String(error)}`);
148
+ return 1;
99
149
  }
100
- const cdHint = relative(process.cwd(), targetDir) || ".";
101
- console.log("\nnext steps:");
102
- console.log(` cd ${cdHint}`);
103
- if (!installed)
104
- console.log(" bun install # or npm install");
105
- console.log(" bun dev # or npm run dev — flat world, spawned player, working HUD");
106
- console.log(" npx jgengine skills # install the JGengine agent skills for AI-assisted building");
107
- return 0;
108
- }
109
- function gameTemplateFileCount() {
110
- return gameTemplate({ id: "probe", name: "Probe", variant: "standalone", engineVersion: "0.0.0" }).length;
111
150
  }
@@ -0,0 +1,11 @@
1
+ export declare const SKILLS_SOURCE = "Noisemaker111/jgengine";
2
+ export declare const GAME_SKILLS: readonly ["jgengine-api", "jgengine-newgame", "jgengine-verify"];
3
+ export type SkillsScope = "global" | "project";
4
+ export declare function parseSkillsArgs(argv: string[]): {
5
+ scope: SkillsScope;
6
+ } | {
7
+ error: string;
8
+ };
9
+ export declare function skillsInstallArgs(scope: SkillsScope): string[];
10
+ export declare function installSkills(scope: SkillsScope, cwd?: string): number;
11
+ export declare function runSkills(argv: string[]): number;
package/dist/skills.js ADDED
@@ -0,0 +1,67 @@
1
+ import { spawnSync } from "node:child_process";
2
+ export const SKILLS_SOURCE = "Noisemaker111/jgengine";
3
+ export const GAME_SKILLS = ["jgengine-api", "jgengine-newgame", "jgengine-verify"];
4
+ export function parseSkillsArgs(argv) {
5
+ let scope = null;
6
+ for (const arg of argv) {
7
+ if (arg === "-g" || arg === "--global") {
8
+ if (scope === "project")
9
+ return { error: "use either -g/--global or -p/--project, not both" };
10
+ scope = "global";
11
+ continue;
12
+ }
13
+ if (arg === "-p" || arg === "--project") {
14
+ if (scope === "global")
15
+ return { error: "use either -g/--global or -p/--project, not both" };
16
+ scope = "project";
17
+ continue;
18
+ }
19
+ if (arg === "-y" || arg === "--yes")
20
+ continue;
21
+ if (arg === "-h" || arg === "--help")
22
+ return { error: "help" };
23
+ return { error: `unknown skills option: ${arg}` };
24
+ }
25
+ return { scope: scope ?? "project" };
26
+ }
27
+ export function skillsInstallArgs(scope) {
28
+ const args = ["--yes", "skills", "add", SKILLS_SOURCE, "-y"];
29
+ for (const skill of GAME_SKILLS) {
30
+ args.push("-s", skill);
31
+ }
32
+ args.push("-a", "*");
33
+ if (scope === "global")
34
+ args.push("-g");
35
+ return args;
36
+ }
37
+ export function installSkills(scope, cwd) {
38
+ const where = scope === "global" ? "globally" : "in this project";
39
+ console.log(`installing agent skills ${where}: ${GAME_SKILLS.join(", ")}…`);
40
+ const result = spawnSync("npx", skillsInstallArgs(scope), {
41
+ stdio: "inherit",
42
+ shell: process.platform === "win32",
43
+ cwd,
44
+ });
45
+ return result.status ?? 1;
46
+ }
47
+ export function runSkills(argv) {
48
+ const parsed = parseSkillsArgs(argv);
49
+ if ("error" in parsed) {
50
+ if (parsed.error === "help") {
51
+ console.log(`usage: jgengine skills (-g|--global | -p|--project)
52
+
53
+ -p, --project install into this project (default)
54
+ -g, --global install for your user (every project / agent session)
55
+
56
+ Installs ${GAME_SKILLS.join(", ")} from ${SKILLS_SOURCE}.
57
+
58
+ create already installs project skills for you — you usually never need this command.
59
+ `);
60
+ return 0;
61
+ }
62
+ console.error(`error: ${parsed.error}`);
63
+ console.error("usage: jgengine skills (-g|--global | -p|--project)");
64
+ return 1;
65
+ }
66
+ return installSkills(parsed.scope);
67
+ }
@@ -10,6 +10,15 @@ export interface TemplateFile {
10
10
  contents: string;
11
11
  }
12
12
  export declare const GAME_ID_PATTERN: RegExp;
13
+ export declare const FOLDER_NAME_PATTERN: RegExp;
13
14
  export declare function displayNameFromId(id: string): string;
15
+ export declare function displayNameFromInput(input: string): string;
16
+ export declare function folderNameFromTitle(input: string): string;
17
+ export declare function packageIdFromFolder(folder: string): string;
18
+ export declare function parseCreateName(input: string): {
19
+ displayName: string;
20
+ folderName: string;
21
+ id: string;
22
+ };
14
23
  export declare const IN_REPO_TSCONFIG_PATHS: Record<string, string[]>;
15
24
  export declare function gameTemplate(options: TemplateOptions): TemplateFile[];
package/dist/templates.js CHANGED
@@ -1,10 +1,58 @@
1
1
  export const GAME_ID_PATTERN = /^[a-z][a-z0-9-]*$/;
2
+ export const FOLDER_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9-]*$/;
2
3
  export function displayNameFromId(id) {
3
4
  return id
4
5
  .split("-")
6
+ .filter((word) => word.length > 0)
5
7
  .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
6
8
  .join(" ");
7
9
  }
10
+ export function displayNameFromInput(input) {
11
+ const trimmed = input.trim().replace(/\s+/g, " ");
12
+ if (trimmed.length === 0) {
13
+ throw new Error("game name must not be empty");
14
+ }
15
+ if (/\s/.test(trimmed))
16
+ return trimmed;
17
+ if (trimmed.includes("-"))
18
+ return displayNameFromId(trimmed.toLowerCase());
19
+ return trimmed.charAt(0).toUpperCase() + trimmed.slice(1);
20
+ }
21
+ export function folderNameFromTitle(input) {
22
+ const cleaned = input
23
+ .trim()
24
+ .replace(/[<>:"/\\|?*\u0000-\u001f]/g, "")
25
+ .replace(/\s+/g, " ");
26
+ if (cleaned.length === 0) {
27
+ throw new Error("game name must not be empty");
28
+ }
29
+ const folder = cleaned
30
+ .split(" ")
31
+ .filter((part) => part.length > 0)
32
+ .join("-")
33
+ .replace(/-+/g, "-")
34
+ .replace(/^-+|-+$/g, "");
35
+ if (!FOLDER_NAME_PATTERN.test(folder)) {
36
+ throw new Error(`folder name "${folder}" must start with a letter and contain only letters, digits, and dashes`);
37
+ }
38
+ return folder;
39
+ }
40
+ export function packageIdFromFolder(folder) {
41
+ const id = folder
42
+ .toLowerCase()
43
+ .replace(/[^a-z0-9]+/g, "-")
44
+ .replace(/^-+|-+$/g, "");
45
+ if (!GAME_ID_PATTERN.test(id)) {
46
+ throw new Error(`package id "${id}" must be kebab-case: lowercase letters, digits, dashes, starting with a letter`);
47
+ }
48
+ return id;
49
+ }
50
+ export function parseCreateName(input) {
51
+ const displayName = displayNameFromInput(input);
52
+ const folderName = folderNameFromTitle(input);
53
+ const id = packageIdFromFolder(folderName);
54
+ return { displayName, folderName, id };
55
+ }
8
56
  const indexHtml = (name) => `<!doctype html>
9
57
  <html lang="en">
10
58
  <head>
@@ -296,21 +344,30 @@ describe("${id} world", () => {
296
344
  });
297
345
  });
298
346
  `;
299
- const agentsMd = (name, variant) => `# ${name} — agent notes
347
+ const agentsMd = (name, variant) => `# ${name} — agent briefing
348
+
349
+ You are in a **JGengine** game project. JGengine is a pure-TypeScript game engine SDK on npm (\`@jgengine/core\`, \`react\`, \`shell\`, …). The CLI is \`npx jgengine\`. Site: https://jgengine.com · source: https://github.com/Noisemaker111/jgengine
350
+
351
+ ## What to do when the user wants this game built
352
+
353
+ 1. Read skills if present: \`jgengine-newgame\` (workflow), \`jgengine-api\` (API), \`jgengine-verify\` (prove it works). They land under \`.agents/skills/\` or \`.claude/skills/\` when scaffolded via \`npx jgengine create\`.
354
+ 2. If skills are missing: \`npx jgengine skills -p\` (or use this file + \`npx jgengine llms core\`).
355
+ 3. **User-facing first reply is short** — game name, fantasy in 2–4 lines, POV (1st / 3rd / top-down / HUD-only), world kind, scale vibe. Ask a few tight questions (POV, world, multiplayer, how big). **Do not** dump file trees, catalog ids, keybind tables, or full phase plans to the user.
356
+ 4. Keep the full engineering plan (files, systems, budgets) internal. After they answer, scaffold is already here — build in phases, full game not a slice.
300
357
 
301
- This is a JGengine game (pure-TypeScript engine SDK, \`@jgengine/*\` on npm). Load context before writing code:
358
+ ## Engine loaders
302
359
 
303
- - Engine API surface: \`npx jgengine llms core\` (any package name works) prints the packaged API docs.
304
- - Agent skills — API reference, phased game-build workflow, browserless verify gate: \`npx jgengine skills\` installs them into \`.claude/skills\`.
305
- - Setup broken or UI unstyled: \`npx jgengine doctor\`.
360
+ - API docs in the tarball: \`npx jgengine llms core\` (any package: react, shell, …)
361
+ - Doctor: \`npx jgengine doctor\`
362
+ - Dev: \`bun dev\` / \`npm run dev\`
306
363
 
307
- Rules this project follows:
364
+ ## Project rules
308
365
 
309
- - Shape: \`src/\` holds only \`game.config.ts\`, \`index.tsx\`, \`main.tsx\`, \`loop.ts\`, \`world.ts\`, \`index.css\`; every game-specific module, UI component, and test lives under \`src/game/\`.
310
- - \`game.config.ts\` is the single entry — \`defineGame({...})\` from \`@jgengine/shell/defineGame\`.
311
- - Scene and world changes are proven with \`summarizeEnvironment\` assertions in \`bun test src\` (see \`src/game/world.world.test.ts\`), never by screenshots as the inner loop.
312
- - HUD styling is Tailwind v4: the \`@source\` entries in \`src/index.css\` must cover \`@jgengine/react\` and \`@jgengine/shell\`${variant === "in-repo" ? " (engine source dirs in this monorepo)" : " (their dist dirs in node_modules)"}, or engine UI classes are silently not generated and the HUD renders unstyled.
313
- - \`onNewPlayer\` spawns the player entity with \`id === ctx.player.userId\`; \`onTick\`'s \`dt\` is game time, never wall-clock.
366
+ - Shape: \`src/\` holds only \`game.config.ts\`, \`index.tsx\`, \`main.tsx\`, \`loop.ts\`, \`world.ts\`, \`index.css\`; everything else under \`src/game/\`.
367
+ - Entry: \`defineGame({...})\` from \`@jgengine/shell/defineGame\` in \`game.config.ts\`.
368
+ - Prove world content with \`summarizeEnvironment\` in \`bun test\` (\`src/game/world.world.test.ts\`), not screenshot loops.
369
+ - Tailwind v4: \`@source\` in \`src/index.css\` must cover \`@jgengine/react\` and \`@jgengine/shell\`${variant === "in-repo" ? " (engine source under packages/)" : " (dist under node_modules)"}, or the HUD is silently unstyled.
370
+ - Spawn player with \`id === ctx.player.userId\` in \`onNewPlayer\`; \`onTick\` \`dt\` is game time.
314
371
  `;
315
372
  export function gameTemplate(options) {
316
373
  const { id, name, variant, engineVersion } = options;
package/llms.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  # @jgengine/jgengine
2
- > The JGengine command line scaffold a playable game, diagnose a broken setup, and load engine docs and agent skills into any project. Start with `npx jgengine create <dir>`.
2
+ > JGengine CLIpure-TypeScript game engine entry for humans and agents. Run: npx jgengine create "My Game Name" (scaffolds a playable base and installs agent skills). Then build the game. Docs: https://jgengine.com
3
3
 
4
- Version: 0.8.0
4
+ Version: 0.8.2
5
5
  License: AGPL-3.0-only
6
6
  Repository: https://github.com/Noisemaker111/jgengine
7
7
  Docs: https://jgengine.com
@@ -12,7 +12,7 @@ Imports use deep paths: `@jgengine/jgengine/<path>`.
12
12
  ### @jgengine/jgengine/create
13
13
 
14
14
  - writeGame (function): function writeGame(targetDir: string, id: string, name: string, variant: TemplateVariant): void
15
- - registerRootGameScript (function): function registerRootGameScript(rootDir: string, id: string): boolean
15
+ - registerRootGameScript (function): function registerRootGameScript(rootDir: string, id: string, folderName: string = id): boolean
16
16
  - runCreate (function): function runCreate(argv: string[]): number
17
17
 
18
18
  ### @jgengine/jgengine/doctor
@@ -32,7 +32,7 @@ Imports use deep paths: `@jgengine/jgengine/<path>`.
32
32
  - TemplateVariant (type): type TemplateVariant = "standalone" | "in-repo"
33
33
  - runCreate (function): function runCreate(argv: string[]): number
34
34
  - writeGame (function): function writeGame(targetDir: string, id: string, name: string, variant: TemplateVariant): void
35
- - registerRootGameScript (function): function registerRootGameScript(rootDir: string, id: string): boolean
35
+ - registerRootGameScript (function): function registerRootGameScript(rootDir: string, id: string, folderName: string = id): boolean
36
36
  - diagnose (function): function diagnose(dir: string): Finding[]
37
37
  - runDoctor (function): function runDoctor(argv: string[]): number
38
38
  - Finding (interface): interface Finding
@@ -49,14 +49,29 @@ Imports use deep paths: `@jgengine/jgengine/<path>`.
49
49
  - hasFlag (function): function hasFlag(argv: string[], name: string): boolean
50
50
  - PackageJson (interface): interface PackageJson
51
51
 
52
+ ### @jgengine/jgengine/skills
53
+
54
+ - parseSkillsArgs (function): function parseSkillsArgs(argv: string[]): { scope: SkillsScope } | { error: string }
55
+ - skillsInstallArgs (function): function skillsInstallArgs(scope: SkillsScope): string[]
56
+ - installSkills (function): function installSkills(scope: SkillsScope, cwd?: string): number
57
+ - runSkills (function): function runSkills(argv: string[]): number
58
+ - SKILLS_SOURCE (const): const SKILLS_SOURCE: "Noisemaker111/jgengine"
59
+ - GAME_SKILLS (const): const GAME_SKILLS: readonly ["jgengine-api", "jgengine-newgame", "jgengine-verify"]
60
+ - SkillsScope (type): type SkillsScope = "global" | "project"
61
+
52
62
  ### @jgengine/jgengine/templates
53
63
 
54
64
  - displayNameFromId (function): function displayNameFromId(id: string): string
65
+ - displayNameFromInput (function): function displayNameFromInput(input: string): string
66
+ - folderNameFromTitle (function): function folderNameFromTitle(input: string): string
67
+ - packageIdFromFolder (function): function packageIdFromFolder(folder: string): string
68
+ - parseCreateName (function): function parseCreateName(input: string): { displayName: string; folderName: string; id: string }
55
69
  - gameTemplate (function): function gameTemplate(options: TemplateOptions): TemplateFile[]
56
70
  - TemplateVariant (type): type TemplateVariant = "standalone" | "in-repo"
57
71
  - TemplateOptions (interface): interface TemplateOptions
58
72
  - TemplateFile (interface): interface TemplateFile
59
73
  - GAME_ID_PATTERN (const): const GAME_ID_PATTERN: RegExp
74
+ - FOLDER_NAME_PATTERN (const): const FOLDER_NAME_PATTERN: RegExp
60
75
  - IN_REPO_TSCONFIG_PATHS (const): const IN_REPO_TSCONFIG_PATHS: Record<string, string[]>
61
76
 
62
77
  ## Guides
@@ -292,8 +307,9 @@ Exact import paths and export names — **do not invent paths**; every row below
292
307
  Fastest path — the `jgengine` CLI scaffolds the entire canonical shape below (harness, skeleton, stub game, verify test, AGENTS.md) as a booting game:
293
308
 
294
309
  ```sh
295
- npx jgengine create my-game # then: cd my-game && bun dev
296
- npx jgengine doctor # later, if the setup drifts (version skew, unstyled HUD, shape strays)
310
+ npx jgengine create "My Game Name" # folder My-Game-Name; skills install automatically
311
+ cd My-Game-Name && bun dev
312
+ npx jgengine doctor # later, if setup drifts (version skew, unstyled HUD, shape)
297
313
  ```
298
314
 
299
315
  Manual equivalent:
package/package.json CHANGED
@@ -1,10 +1,20 @@
1
1
  {
2
2
  "name": "jgengine",
3
- "version": "0.8.0",
4
- "description": "The JGengine command line scaffold a playable game, diagnose a broken setup, and load engine docs and agent skills into any project. Start with `npx jgengine create <dir>`.",
3
+ "version": "0.8.2",
4
+ "description": "JGengine CLIpure-TypeScript game engine entry for humans and agents. Run: npx jgengine create \"My Game Name\" (scaffolds a playable base and installs agent skills). Then build the game. Docs: https://jgengine.com",
5
+ "keywords": [
6
+ "game-engine",
7
+ "typescript",
8
+ "jgengine",
9
+ "cli",
10
+ "three.js",
11
+ "agent",
12
+ "scaffold"
13
+ ],
5
14
  "license": "AGPL-3.0-only",
6
15
  "type": "module",
7
16
  "sideEffects": false,
17
+ "homepage": "https://jgengine.com",
8
18
  "repository": {
9
19
  "type": "git",
10
20
  "url": "git+https://github.com/Noisemaker111/jgengine.git",