jgengine 0.8.0 → 0.8.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.
package/dist/cli/index.js CHANGED
@@ -7,23 +7,30 @@ 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
12
  const HELP = `jgengine ${cliVersion()} — the JGengine command line
12
13
  Pure-TypeScript, genre-agnostic game engine SDK. Packages: ${ENGINE_PACKAGES.map((name) => `@jgengine/${name}`).join(", ")}.
13
14
 
14
15
  usage: jgengine <command> [...args]
15
16
 
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
17
+ create "<Game Name>" scaffold a playable base — flat world, spawned player, HUD, verify test.
18
+ folder becomes My-Game-Name; name lands in game.config / HUD / title.
19
+ [--in-repo|--standalone] [--no-install] [--pm bun|npm|pnpm]
20
+ skills -p | -g install agent skills (api, newgame, verify). -p this project (default), -g global
21
+ doctor [dir] diagnose a game project: version skew, missing peers, unstyled-UI @source gaps, shape drift
22
+ llms [package] print packaged API docs (llms.txt) for an installed @jgengine/* package agent-ready context
23
+ assets [...] delegate to the @jgengine/assets CLI: list, search, pull CC0 3D model packs
24
+ versions show CLI + installed @jgengine/* versions
25
+ help this map
24
26
 
25
27
  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
28
+
29
+ entry (any directory, no monorepo needed):
30
+ npx jgengine create "Solitaire"
31
+ cd Solitaire
32
+ npx jgengine skills -p # or: npx jgengine skills -g (once, for every project)
33
+ # then: make Solitaire with jgengine
27
34
  `;
28
35
  function runVersions() {
29
36
  console.log(`jgengine ${cliVersion()}`);
@@ -61,14 +68,6 @@ function runLlms(argv) {
61
68
  process.stdout.write(readFileSync(join(found, "node_modules", packageName, "llms.txt"), "utf8"));
62
69
  return 0;
63
70
  }
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
71
  function runAssets(argv) {
73
72
  const require = createRequire(import.meta.url);
74
73
  let cliPath;
@@ -91,7 +90,7 @@ switch (command) {
91
90
  process.exit(runDoctor(rest));
92
91
  break;
93
92
  case "skills":
94
- process.exit(runSkills());
93
+ process.exit(runSkills(rest));
95
94
  break;
96
95
  case "llms":
97
96
  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,8 @@
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 { gameTemplate, parseCreateName } from "./templates.js";
6
6
  export function writeGame(targetDir, id, name, variant) {
7
7
  for (const file of gameTemplate({ id, name, variant, engineVersion: cliVersion() })) {
8
8
  const dest = join(targetDir, file.path);
@@ -10,7 +10,7 @@ export function writeGame(targetDir, id, name, variant) {
10
10
  writeFileSync(dest, file.contents);
11
11
  }
12
12
  }
13
- export function registerRootGameScript(rootDir, id) {
13
+ export function registerRootGameScript(rootDir, id, folderName = id) {
14
14
  const rootPackagePath = join(rootDir, "package.json");
15
15
  const root = JSON.parse(readFileSync(rootPackagePath, "utf8"));
16
16
  const scripts = root.scripts ?? {};
@@ -20,7 +20,7 @@ export function registerRootGameScript(rootDir, id) {
20
20
  const entries = Object.entries(scripts);
21
21
  const games = entries.filter(([k]) => k.startsWith("games:"));
22
22
  const rest = entries.filter(([k]) => !k.startsWith("games:"));
23
- games.push([key, `bun run --cwd Games/${id} dev`]);
23
+ games.push([key, `bun run --cwd Games/${folderName} dev`]);
24
24
  games.sort(([a], [b]) => a.localeCompare(b));
25
25
  root.scripts = Object.fromEntries([...rest, ...games]);
26
26
  writeFileSync(rootPackagePath, `${JSON.stringify(root, null, 2)}\n`);
@@ -32,7 +32,7 @@ function pickPackageManager(preferred) {
32
32
  const probe = spawnSync("bun", ["--version"], { stdio: "ignore", shell: process.platform === "win32" });
33
33
  return probe.status === 0 ? "bun" : "npm";
34
34
  }
35
- const VALUE_FLAGS = new Set(["--name", "--pm"]);
35
+ const VALUE_FLAGS = new Set(["--pm"]);
36
36
  function positionalArg(argv) {
37
37
  for (let index = 0; index < argv.length; index += 1) {
38
38
  const arg = argv[index];
@@ -45,67 +45,98 @@ function positionalArg(argv) {
45
45
  }
46
46
  return undefined;
47
47
  }
48
+ function splitCreateArg(arg) {
49
+ const normalized = arg.replace(/\\/g, "/").replace(/\/+/g, "/");
50
+ const trimmed = normalized.replace(/\/$/, "");
51
+ const slash = trimmed.lastIndexOf("/");
52
+ if (slash < 0)
53
+ return { parentHint: null, titlePart: trimmed };
54
+ return {
55
+ parentHint: trimmed.slice(0, slash) || ".",
56
+ titlePart: trimmed.slice(slash + 1),
57
+ };
58
+ }
59
+ function isInsideDir(child, parent) {
60
+ const resolvedChild = resolve(child);
61
+ const resolvedParent = resolve(parent);
62
+ return resolvedChild === resolvedParent || resolvedChild.startsWith(resolvedParent + sep);
63
+ }
48
64
  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");
65
+ const nameArg = positionalArg(argv);
66
+ if (nameArg === undefined) {
67
+ console.error('usage: jgengine create "<Game Name>" [--in-repo|--standalone] [--no-install] [--pm bun|npm|pnpm]');
73
68
  return 1;
74
69
  }
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")}`);
70
+ let displayName;
71
+ let folderName;
72
+ let id;
73
+ try {
74
+ const { parentHint, titlePart } = splitCreateArg(nameArg);
75
+ ({ displayName, folderName, id } = parseCreateName(titlePart));
76
+ let parentDir = parentHint !== null ? resolve(parentHint) : process.cwd();
77
+ let targetDir = join(parentDir, folderName);
78
+ const workspaceRoot = findWorkspaceRoot(dirname(targetDir)) ?? findWorkspaceRoot(parentDir) ?? findWorkspaceRoot(process.cwd());
79
+ const insideEngineRepo = workspaceRoot !== null && isEngineMonorepo(workspaceRoot);
80
+ const variant = hasFlag(argv, "standalone")
81
+ ? "standalone"
82
+ : hasFlag(argv, "in-repo") || insideEngineRepo
83
+ ? "in-repo"
84
+ : "standalone";
85
+ if (variant === "in-repo") {
86
+ if (!insideEngineRepo || workspaceRoot === null) {
87
+ console.error("error: --in-repo requires the jgengine engine monorepo (packages/core + Games/)");
88
+ return 1;
89
+ }
90
+ if (parentHint === null) {
91
+ parentDir = join(workspaceRoot, "Games");
92
+ targetDir = join(parentDir, folderName);
93
+ }
94
+ const gamesDir = join(workspaceRoot, "Games");
95
+ if (!isInsideDir(targetDir, gamesDir)) {
96
+ console.error(`error: in-repo games must live under Games/ (got ${targetDir}; expected under ${gamesDir})`);
97
+ return 1;
98
+ }
81
99
  }
100
+ if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
101
+ console.error(`error: ${targetDir} already exists and is not empty`);
102
+ return 1;
103
+ }
104
+ writeGame(targetDir, id, displayName, variant);
105
+ console.log(`created ${displayName} (${variant}) → ${targetDir}`);
106
+ console.log(` folder ${folderName} package ${id} name "${displayName}"`);
107
+ if (variant === "in-repo" && workspaceRoot !== null) {
108
+ if (registerRootGameScript(workspaceRoot, id, folderName)) {
109
+ console.log(`registered root script "games:${id}" in ${join(workspaceRoot, "package.json")}`);
110
+ }
111
+ console.log("\nnext steps:");
112
+ console.log(` bun install # from ${workspaceRoot}`);
113
+ console.log(` bun run games:${id} # play it standalone`);
114
+ return 0;
115
+ }
116
+ let installed = false;
117
+ if (!hasFlag(argv, "no-install")) {
118
+ const pm = pickPackageManager(flag(argv, "pm"));
119
+ console.log(`installing dependencies with ${pm}…`);
120
+ const install = spawnSync(pm, ["install"], {
121
+ cwd: targetDir,
122
+ stdio: "inherit",
123
+ shell: process.platform === "win32",
124
+ });
125
+ installed = install.status === 0;
126
+ if (!installed)
127
+ console.error(`warning: ${pm} install failed — run it manually in ${targetDir}`);
128
+ }
129
+ const cdHint = relative(process.cwd(), targetDir) || ".";
82
130
  console.log("\nnext steps:");
83
- console.log(` bun install # from ${workspaceRoot}`);
84
- console.log(` bun run games:${id} # play it standalone`);
131
+ console.log(` cd ${cdHint}`);
132
+ if (!installed)
133
+ console.log(" bun install # or npm install");
134
+ console.log(" bun dev # or npm run dev — flat world, spawned player, working HUD");
135
+ console.log(" npx jgengine skills -p # agent skills in this project (-g for global)");
85
136
  return 0;
86
137
  }
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}`);
138
+ catch (error) {
139
+ console.error(`error: ${error instanceof Error ? error.message : String(error)}`);
140
+ return 1;
99
141
  }
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
142
  }
@@ -0,0 +1,9 @@
1
+ export declare const SKILLS_SOURCE = "Noisemaker111/jgengine";
2
+ export type SkillsScope = "global" | "project";
3
+ export declare function parseSkillsArgs(argv: string[]): {
4
+ scope: SkillsScope;
5
+ } | {
6
+ error: string;
7
+ };
8
+ export declare function skillsInstallArgs(scope: SkillsScope): string[];
9
+ export declare function runSkills(argv: string[]): number;
package/dist/skills.js ADDED
@@ -0,0 +1,65 @@
1
+ import { spawnSync } from "node:child_process";
2
+ export const SKILLS_SOURCE = "Noisemaker111/jgengine";
3
+ export function parseSkillsArgs(argv) {
4
+ let scope = null;
5
+ for (const arg of argv) {
6
+ if (arg === "-g" || arg === "--global") {
7
+ if (scope === "project")
8
+ return { error: "use either -g/--global or -p/--project, not both" };
9
+ scope = "global";
10
+ continue;
11
+ }
12
+ if (arg === "-p" || arg === "--project") {
13
+ if (scope === "global")
14
+ return { error: "use either -g/--global or -p/--project, not both" };
15
+ scope = "project";
16
+ continue;
17
+ }
18
+ if (arg === "-y" || arg === "--yes")
19
+ continue;
20
+ if (arg === "-h" || arg === "--help")
21
+ return { error: "help" };
22
+ return { error: `unknown skills option: ${arg}` };
23
+ }
24
+ return { scope: scope ?? "project" };
25
+ }
26
+ export function skillsInstallArgs(scope) {
27
+ const args = ["--yes", "skills", "add", SKILLS_SOURCE, "-y", "--all"];
28
+ if (scope === "global")
29
+ args.push("-g");
30
+ return args;
31
+ }
32
+ export function runSkills(argv) {
33
+ const parsed = parseSkillsArgs(argv);
34
+ if ("error" in parsed) {
35
+ if (parsed.error === "help") {
36
+ console.log(`usage: jgengine skills (-g|--global | -p|--project)
37
+
38
+ -p, --project install into this project (.claude/skills) — default
39
+ -g, --global install for your user (every project / agent session)
40
+
41
+ Installs jgengine-api, jgengine-newgame, and jgengine-verify from ${SKILLS_SOURCE}.
42
+
43
+ entry:
44
+ npx jgengine create "Solitaire"
45
+ cd Solitaire
46
+ npx jgengine skills -p
47
+ # tell your agent: make Solitaire with jgengine
48
+
49
+ # or once for all projects:
50
+ npx jgengine skills -g
51
+ `);
52
+ return 0;
53
+ }
54
+ console.error(`error: ${parsed.error}`);
55
+ console.error('usage: jgengine skills (-g|--global | -p|--project)');
56
+ return 1;
57
+ }
58
+ const where = parsed.scope === "global" ? "globally" : "in this project";
59
+ console.log(`installing JGengine agent skills ${where} (jgengine-api, jgengine-newgame, jgengine-verify)…`);
60
+ const result = spawnSync("npx", skillsInstallArgs(parsed.scope), {
61
+ stdio: "inherit",
62
+ shell: process.platform === "win32",
63
+ });
64
+ return result.status ?? 1;
65
+ }
@@ -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>
@@ -301,7 +349,7 @@ const agentsMd = (name, variant) => `# ${name} — agent notes
301
349
  This is a JGengine game (pure-TypeScript engine SDK, \`@jgengine/*\` on npm). Load context before writing code:
302
350
 
303
351
  - 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\`.
352
+ - Agent skills — API reference, phased game-build workflow, browserless verify gate: \`npx jgengine skills -p\` (this project) or \`-g\` (global).
305
353
  - Setup broken or UI unstyled: \`npx jgengine doctor\`.
306
354
 
307
355
  Rules this project follows:
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
+ > 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 "My Game Name"`.
3
3
 
4
- Version: 0.8.0
4
+ Version: 0.8.1
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,27 @@ 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
+ - runSkills (function): function runSkills(argv: string[]): number
57
+ - SKILLS_SOURCE (const): const SKILLS_SOURCE: "Noisemaker111/jgengine"
58
+ - SkillsScope (type): type SkillsScope = "global" | "project"
59
+
52
60
  ### @jgengine/jgengine/templates
53
61
 
54
62
  - displayNameFromId (function): function displayNameFromId(id: string): string
63
+ - displayNameFromInput (function): function displayNameFromInput(input: string): string
64
+ - folderNameFromTitle (function): function folderNameFromTitle(input: string): string
65
+ - packageIdFromFolder (function): function packageIdFromFolder(folder: string): string
66
+ - parseCreateName (function): function parseCreateName(input: string): { displayName: string; folderName: string; id: string }
55
67
  - gameTemplate (function): function gameTemplate(options: TemplateOptions): TemplateFile[]
56
68
  - TemplateVariant (type): type TemplateVariant = "standalone" | "in-repo"
57
69
  - TemplateOptions (interface): interface TemplateOptions
58
70
  - TemplateFile (interface): interface TemplateFile
59
71
  - GAME_ID_PATTERN (const): const GAME_ID_PATTERN: RegExp
72
+ - FOLDER_NAME_PATTERN (const): const FOLDER_NAME_PATTERN: RegExp
60
73
  - IN_REPO_TSCONFIG_PATHS (const): const IN_REPO_TSCONFIG_PATHS: Record<string, string[]>
61
74
 
62
75
  ## Guides
@@ -292,8 +305,11 @@ Exact import paths and export names — **do not invent paths**; every row below
292
305
  Fastest path — the `jgengine` CLI scaffolds the entire canonical shape below (harness, skeleton, stub game, verify test, AGENTS.md) as a booting game:
293
306
 
294
307
  ```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)
308
+ npx jgengine create "My Game Name" # folder My-Game-Name; name lands in game.config / HUD / title
309
+ cd My-Game-Name
310
+ npx jgengine skills -p # agent skills here; use -g once for global
311
+ bun dev
312
+ npx jgengine doctor # later, if the setup drifts (version skew, unstyled HUD, shape strays)
297
313
  ```
298
314
 
299
315
  Manual equivalent:
package/package.json CHANGED
@@ -1,7 +1,7 @@
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.1",
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 \"My Game Name\"`.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",
7
7
  "sideEffects": false,