@craftrpgs/cli 0.1.3 → 0.1.4

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 (3) hide show
  1. package/README.md +4 -2
  2. package/dist/bin.js +446 -38
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -7,7 +7,8 @@ back — git-style, with three-way sync and conflict handling.
7
7
  ```sh
8
8
  npm install -g @craftrpgs/cli
9
9
  craft login
10
- craft create "My World"
10
+ craft systems # browse published game systems
11
+ craft create "My World" --system "Ashes RPG" # build on one (--blank starts from scratch)
11
12
  cd my-world
12
13
  # edit files, then:
13
14
  craft status
@@ -28,9 +29,10 @@ know how to work with the project the moment you open it.
28
29
  | Command | What it does |
29
30
  |---|---|
30
31
  | `craft login` / `logout` | Browser sign-in (credentials stored per server) |
31
- | `craft create <name>` | Create a new project and clone it |
32
+ | `craft create <name>` | Create a new project and clone it (`--system <id\|name>` builds on a published system; `--blank` starts from scratch) |
32
33
  | `craft clone <idOrUrl>` | Download a project as a local workspace |
33
34
  | `craft projects` | List projects your account owns |
35
+ | `craft systems [query]` | Browse published game systems to build on (featured + community) |
34
36
  | `craft status` / `diff` | Show local changes (add `--remote` for drift) |
35
37
  | `craft check` | Validate the workspace locally (the same checks the server runs at push) |
36
38
  | `craft project-completeness` | Worldbuilding completeness report (recommendations; always exits 0) |
package/dist/bin.js CHANGED
@@ -27609,8 +27609,11 @@ var cliImageVariationsResponseSchema = exports_external.object({
27609
27609
  var cliCreateProjectRequestSchema = exports_external.object({
27610
27610
  name: exports_external.string().min(1).max(200),
27611
27611
  description: exports_external.string().max(2000).optional(),
27612
- seedStarterContent: exports_external.boolean().optional()
27613
- }).strict();
27612
+ seedStarterContent: exports_external.boolean().optional(),
27613
+ systemProjectId: uuidSchema.optional()
27614
+ }).strict().refine((value) => !(value.systemProjectId && value.seedStarterContent !== undefined), {
27615
+ message: "seedStarterContent does not apply when creating from a system — the system supplies the content"
27616
+ });
27614
27617
  var cliCreateProjectResponseSchema = exports_external.object({
27615
27618
  projectId: uuidSchema,
27616
27619
  containerId: uuidSchema
@@ -27626,6 +27629,26 @@ var cliProjectSummarySchema = exports_external.object({
27626
27629
  var cliProjectsListResponseSchema = exports_external.object({
27627
27630
  projects: exports_external.array(cliProjectSummarySchema)
27628
27631
  }).strict();
27632
+ var cliSystemSummarySchema = exports_external.object({
27633
+ projectId: uuidSchema,
27634
+ name: exports_external.string(),
27635
+ description: exports_external.string().nullable(),
27636
+ gameSystemName: exports_external.string().nullable(),
27637
+ ownerUsername: exports_external.string().nullable(),
27638
+ official: exports_external.boolean(),
27639
+ featured: exports_external.boolean(),
27640
+ tags: exports_external.array(exports_external.string()),
27641
+ fileTypeNames: exports_external.array(exports_external.string()),
27642
+ fileCount: exports_external.number().int().nonnegative().nullable(),
27643
+ likeCount: exports_external.number().int().nonnegative(),
27644
+ cloneCount: exports_external.number().int().nonnegative(),
27645
+ publishedAt: exports_external.string().nullable()
27646
+ }).strict();
27647
+ var cliSystemsResponseSchema = exports_external.object({
27648
+ featured: exports_external.array(cliSystemSummarySchema),
27649
+ community: exports_external.array(cliSystemSummarySchema),
27650
+ communityNextCursor: exports_external.string().nullable()
27651
+ }).strict();
27629
27652
  var cliFileVersionEntrySchema = exports_external.object({
27630
27653
  versionId: uuidSchema,
27631
27654
  createdAt: exports_external.string(),
@@ -41859,7 +41882,7 @@ function describeRemoteSource(resolved) {
41859
41882
  // package.json
41860
41883
  var package_default = {
41861
41884
  name: "@craftrpgs/cli",
41862
- version: "0.1.3",
41885
+ version: "0.1.4",
41863
41886
  description: "Sync Craft projects with a local folder — clone, edit with any coding agent, push back.",
41864
41887
  license: "UNLICENSED",
41865
41888
  homepage: "https://craftrpgs.com",
@@ -42044,6 +42067,30 @@ function createCliClient({
42044
42067
  }
42045
42068
  return parseJson(response, cliCreateProjectResponseSchema, "create project");
42046
42069
  },
42070
+ async fetchSystems({ q, tags, cursor, limit } = {}) {
42071
+ const params = new URLSearchParams;
42072
+ if (q) {
42073
+ params.set("q", q);
42074
+ }
42075
+ for (const tag of tags ?? []) {
42076
+ params.append("tags", tag);
42077
+ }
42078
+ if (cursor) {
42079
+ params.set("cursor", cursor);
42080
+ }
42081
+ if (limit !== undefined) {
42082
+ params.set("limit", String(limit));
42083
+ }
42084
+ const query = params.size > 0 ? `?${params.toString()}` : "";
42085
+ const response = await request(`/api/projects/cli/systems${query}`);
42086
+ if (response.status === 404) {
42087
+ return null;
42088
+ }
42089
+ if (!response.ok) {
42090
+ throw new CliError(`Listing systems failed: ${await readErrorMessage(response)}`, 1);
42091
+ }
42092
+ return parseJson(response, cliSystemsResponseSchema, "systems");
42093
+ },
42047
42094
  async listProjects() {
42048
42095
  const response = await request("/api/projects/cli/list");
42049
42096
  if (!response.ok) {
@@ -42397,6 +42444,7 @@ async function runClone(argv) {
42397
42444
 
42398
42445
  // src/commands/create.ts
42399
42446
  import { basename } from "node:path";
42447
+ import { createInterface } from "node:readline/promises";
42400
42448
 
42401
42449
  // src/commands/clone.ts
42402
42450
  import { existsSync as existsSync5 } from "node:fs";
@@ -42501,6 +42549,11 @@ async function downloadAndUnpackSnapshot2({
42501
42549
 
42502
42550
  // src/commands/create.ts
42503
42551
  var DIRECTORY_NAME_SEPARATORS = /[-_\s]+/;
42552
+ var UUID_IN_TEXT2 = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
42553
+ var DIGITS_ONLY = /^\d+$/;
42554
+ var MAX_SYSTEM_MATCHES_SHOWN = 8;
42555
+ var NAME_RESOLUTION_PAGE_SIZE = 48;
42556
+ var NAME_RESOLUTION_MAX_PAGES = 10;
42504
42557
  function projectNameFromDirectory(dirName) {
42505
42558
  return dirName.split(DIRECTORY_NAME_SEPARATORS).filter((word) => word.length > 0).map((word) => (word[0]?.toUpperCase() ?? "") + word.slice(1)).join(" ");
42506
42559
  }
@@ -42509,6 +42562,8 @@ async function runCreate(argv) {
42509
42562
  argv,
42510
42563
  flags: {
42511
42564
  description: { type: "string" },
42565
+ system: { type: "string" },
42566
+ blank: { type: "boolean" },
42512
42567
  empty: { type: "boolean" },
42513
42568
  remote: { type: "string" },
42514
42569
  json: { type: "boolean" }
@@ -42517,11 +42572,16 @@ async function runCreate(argv) {
42517
42572
  });
42518
42573
  const [nameArg, dirPositional] = args.positionals;
42519
42574
  if (!nameArg) {
42520
- throw new CliError('Usage: craft create <name> [dir] — quote names with spaces, e.g. craft create "My World" (or `craft create .` to create here, named after this directory)', 2);
42575
+ throw new CliError('Usage: craft create <name> [dir] [--system <id|name> | --blank] — quote names with spaces, e.g. craft create "My World" (or `craft create .` to create here, named after this directory)', 2);
42521
42576
  }
42522
42577
  if (nameArg === "." && dirPositional !== undefined) {
42523
42578
  throw new CliError('`craft create .` takes no [dir] — "." already means "create in the current directory, named after it". To pick the name yourself: craft create "My Name" .', 2);
42524
42579
  }
42580
+ const systemArg = stringFlag(args, "system");
42581
+ const wantsBlank = args.booleans.has("blank") || args.booleans.has("empty");
42582
+ if (systemArg && wantsBlank) {
42583
+ throw new CliError("--system cannot combine with --blank/--empty — a project either builds on a system or starts from scratch.", 2);
42584
+ }
42525
42585
  const namedAfterCwd = nameArg === ".";
42526
42586
  const name = namedAfterCwd ? projectNameFromDirectory(basename(process.cwd())) : nameArg;
42527
42587
  if (!name) {
@@ -42537,14 +42597,25 @@ async function runCreate(argv) {
42537
42597
  loginHint: silentDefaultHint(resolved)
42538
42598
  })
42539
42599
  });
42540
- const description = stringFlag(args, "description");
42541
- const created = await client.createProject({
42542
- request: {
42543
- name,
42544
- ...description ? { description } : {},
42545
- ...args.booleans.has("empty") ? { seedStarterContent: false } : {}
42546
- }
42600
+ const base = await resolveCreateBase({
42601
+ client,
42602
+ systemArg,
42603
+ wantsBlank,
42604
+ name,
42605
+ nameArg,
42606
+ interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY && !args.booleans.has("json"))
42547
42607
  });
42608
+ const description = stringFlag(args, "description");
42609
+ const request = { name };
42610
+ if (description) {
42611
+ request.description = description;
42612
+ }
42613
+ if (base.kind === "system") {
42614
+ request.systemProjectId = base.systemProjectId;
42615
+ } else {
42616
+ request.seedStarterContent = false;
42617
+ }
42618
+ const created = await client.createProject({ request });
42548
42619
  const { targetDir, fileCount } = await downloadAndUnpackSnapshot2({
42549
42620
  client,
42550
42621
  projectId: created.projectId,
@@ -42557,11 +42628,13 @@ async function runCreate(argv) {
42557
42628
  name,
42558
42629
  remote,
42559
42630
  dir: targetDir,
42560
- files: fileCount
42631
+ files: fileCount,
42632
+ system: base.kind === "system" ? { projectId: base.systemProjectId, name: base.systemName } : null
42561
42633
  });
42562
42634
  } else {
42635
+ const fromSystem = base.kind === "system" ? ` from system ${base.systemName ? `"${base.systemName}" ` : ""}(${base.systemProjectId})` : "";
42563
42636
  printLines([
42564
- `Created project "${name}" (${created.projectId}) on ${remote} ` + `(${describeRemoteSource(resolved)}).`,
42637
+ `Created project "${name}" (${created.projectId}) on ${remote} ` + `(${describeRemoteSource(resolved)})${fromSystem}.`,
42565
42638
  ...namedAfterCwd ? [
42566
42639
  'Named after the current directory — `craft create "My Name" .` picks the name yourself.'
42567
42640
  ] : [],
@@ -42571,6 +42644,165 @@ async function runCreate(argv) {
42571
42644
  }
42572
42645
  return 0;
42573
42646
  }
42647
+ async function resolveCreateBase({
42648
+ client,
42649
+ systemArg,
42650
+ wantsBlank,
42651
+ name,
42652
+ nameArg,
42653
+ interactive
42654
+ }) {
42655
+ if (systemArg) {
42656
+ return await resolveSystemArg({ client, value: systemArg });
42657
+ }
42658
+ if (wantsBlank) {
42659
+ return { kind: "blank" };
42660
+ }
42661
+ const catalog = await client.fetchSystems();
42662
+ const featured = catalog?.featured ?? [];
42663
+ if (featured.length === 0) {
42664
+ return { kind: "blank" };
42665
+ }
42666
+ if (!interactive) {
42667
+ throw new CliError(pickBaseGuidance({ nameArg, featured }), 2);
42668
+ }
42669
+ return await promptForBase({ name, featured });
42670
+ }
42671
+ async function resolveSystemArg({
42672
+ client,
42673
+ value
42674
+ }) {
42675
+ const id = extractProjectId(value);
42676
+ if (id) {
42677
+ return { kind: "system", systemProjectId: id, systemName: null };
42678
+ }
42679
+ const catalog = await client.fetchSystems({
42680
+ q: value,
42681
+ limit: NAME_RESOLUTION_PAGE_SIZE
42682
+ });
42683
+ if (!catalog) {
42684
+ throw new CliError("This server does not support creating from a system yet — update the server, or omit --system.", 1);
42685
+ }
42686
+ const candidates = [...catalog.featured, ...catalog.community];
42687
+ let cursor = catalog.communityNextCursor;
42688
+ for (let page = 1;cursor && page < NAME_RESOLUTION_MAX_PAGES; page += 1) {
42689
+ const next = await client.fetchSystems({
42690
+ q: value,
42691
+ cursor,
42692
+ limit: NAME_RESOLUTION_PAGE_SIZE
42693
+ });
42694
+ if (!next) {
42695
+ break;
42696
+ }
42697
+ candidates.push(...next.community);
42698
+ cursor = next.communityNextCursor;
42699
+ }
42700
+ const exact = candidates.filter((system) => system.name.toLowerCase() === value.toLowerCase());
42701
+ const pool = exact.length > 0 ? exact : candidates;
42702
+ const [only] = pool;
42703
+ if (only && pool.length === 1) {
42704
+ return {
42705
+ kind: "system",
42706
+ systemProjectId: only.projectId,
42707
+ systemName: only.name
42708
+ };
42709
+ }
42710
+ if (pool.length === 0) {
42711
+ throw new CliError(`No published system matches "${value}" — browse the catalog with \`craft systems\`, then pass a name or id.`, 2);
42712
+ }
42713
+ throw new CliError([
42714
+ `"${value}" matches ${pool.length} published systems — pass the id instead:`,
42715
+ ...pool.slice(0, MAX_SYSTEM_MATCHES_SHOWN).map((system) => ` ${systemChoiceLabel(system)} ${system.projectId}`)
42716
+ ].join(`
42717
+ `), 2);
42718
+ }
42719
+ function extractProjectId(value) {
42720
+ const matches = value.match(UUID_IN_TEXT2);
42721
+ return matches?.at(-1)?.toLowerCase() ?? null;
42722
+ }
42723
+ function systemChoiceLabel(system) {
42724
+ const parts = [
42725
+ system.name,
42726
+ system.gameSystemName ? `— ${system.gameSystemName}` : null,
42727
+ system.ownerUsername ? `by ${system.ownerUsername}` : null,
42728
+ system.official ? "(official)" : null
42729
+ ];
42730
+ return parts.filter((part) => Boolean(part)).join(" ");
42731
+ }
42732
+ function pickBaseGuidance({
42733
+ nameArg,
42734
+ featured
42735
+ }) {
42736
+ const quoted = nameArg !== "." && nameArg.includes(" ") ? `"${nameArg}"` : nameArg;
42737
+ return [
42738
+ "Pick a base for the new project — new projects usually build on a published system:",
42739
+ ` craft create ${quoted} --system <id|name> # build on a system`,
42740
+ ` craft create ${quoted} --blank # start from scratch (an empty project)`,
42741
+ "",
42742
+ "Featured systems:",
42743
+ ...featured.map((system) => ` ${systemChoiceLabel(system)} ${system.projectId}`),
42744
+ "",
42745
+ "Browse or search the full catalog with `craft systems`."
42746
+ ].join(`
42747
+ `);
42748
+ }
42749
+ function parseBaseAnswer(answer, featured) {
42750
+ const trimmed = answer.trim();
42751
+ const lowered = trimmed.toLowerCase();
42752
+ if (lowered === "q" || lowered === "quit") {
42753
+ return "quit";
42754
+ }
42755
+ if (lowered === "b" || lowered === "blank") {
42756
+ return { kind: "blank" };
42757
+ }
42758
+ if (DIGITS_ONLY.test(trimmed)) {
42759
+ const chosen = featured[Number.parseInt(trimmed, 10) - 1];
42760
+ return chosen ? {
42761
+ kind: "system",
42762
+ systemProjectId: chosen.projectId,
42763
+ systemName: chosen.name
42764
+ } : "invalid";
42765
+ }
42766
+ const id = extractProjectId(trimmed);
42767
+ if (id) {
42768
+ const known = featured.find((system) => system.projectId === id);
42769
+ return {
42770
+ kind: "system",
42771
+ systemProjectId: id,
42772
+ systemName: known?.name ?? null
42773
+ };
42774
+ }
42775
+ return "invalid";
42776
+ }
42777
+ async function promptForBase({
42778
+ name,
42779
+ featured
42780
+ }) {
42781
+ printLines([
42782
+ `Choose a base for "${name}" — new projects usually build on a system:`,
42783
+ ...featured.map((system, index) => ` ${index + 1}) ${systemChoiceLabel(system)}`),
42784
+ " b) Blank project (empty — author your own file types)",
42785
+ "More systems: `craft systems`, then rerun with --system <id|name>."
42786
+ ]);
42787
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
42788
+ try {
42789
+ for (;; ) {
42790
+ const answer = await rl.question(`Pick 1-${featured.length}, b for blank, or q to cancel: `);
42791
+ const choice = parseBaseAnswer(answer, featured);
42792
+ if (choice === "quit") {
42793
+ throw new CliError("Cancelled — nothing was created.", 2);
42794
+ }
42795
+ if (choice !== "invalid") {
42796
+ return choice;
42797
+ }
42798
+ printLines([
42799
+ `Enter a number between 1 and ${featured.length}, a system id, b, or q.`
42800
+ ]);
42801
+ }
42802
+ } finally {
42803
+ rl.close();
42804
+ }
42805
+ }
42574
42806
  // ../../node_modules/.bun/uuid@11.1.0/node_modules/uuid/dist/esm/rng.js
42575
42807
  import { randomFillSync } from "crypto";
42576
42808
  var rnds8Pool = new Uint8Array(256);
@@ -49696,6 +49928,138 @@ function renderStatus(status, openConflicts = []) {
49696
49928
  return lines;
49697
49929
  }
49698
49930
 
49931
+ // src/commands/systems.ts
49932
+ var MAX_NAME_WIDTH = 40;
49933
+ var MAX_BLURB_LENGTH = 100;
49934
+ async function runSystems(argv) {
49935
+ const args = parseCommandArgs({
49936
+ argv,
49937
+ flags: {
49938
+ tag: { type: "string", repeatable: true },
49939
+ community: { type: "boolean" },
49940
+ cursor: { type: "string" },
49941
+ limit: { type: "string" },
49942
+ remote: { type: "string" },
49943
+ json: { type: "boolean" }
49944
+ },
49945
+ maxPositionals: 1
49946
+ });
49947
+ const [query] = args.positionals;
49948
+ const tags = args.strings.get("tag") ?? [];
49949
+ const cursor = stringFlag(args, "cursor");
49950
+ const limit = parseLimit(stringFlag(args, "limit"));
49951
+ const pinned = await workspaceRemote2();
49952
+ const resolved = resolveRemote({
49953
+ flagRemote: stringFlag(args, "remote"),
49954
+ context: pinned ? { remote: pinned, source: "workspace" } : null
49955
+ });
49956
+ const remote = resolved.remote;
49957
+ const client = createCliClient({
49958
+ remote,
49959
+ auth: await resolveTokenProvider({
49960
+ remote,
49961
+ loginHint: silentDefaultHint(resolved)
49962
+ })
49963
+ });
49964
+ const catalog = await client.fetchSystems({
49965
+ ...query ? { q: query } : {},
49966
+ ...tags.length > 0 ? { tags } : {},
49967
+ ...cursor ? { cursor } : {},
49968
+ ...limit !== undefined ? { limit } : {}
49969
+ });
49970
+ if (!catalog) {
49971
+ throw new CliError("This server does not support browsing systems from the CLI yet — update the server, or browse systems in the Craft app.", 1);
49972
+ }
49973
+ if (args.booleans.has("json")) {
49974
+ printJson({
49975
+ remote,
49976
+ featured: catalog.featured.map(withIsoPublishedAt),
49977
+ community: catalog.community.map(withIsoPublishedAt),
49978
+ communityNextCursor: catalog.communityNextCursor
49979
+ });
49980
+ return 0;
49981
+ }
49982
+ const isFiltered = Boolean(query) || tags.length > 0;
49983
+ const showFeatured = !(args.booleans.has("community") || cursor);
49984
+ const featured = showFeatured ? catalog.featured : [];
49985
+ const lines = [];
49986
+ if (featured.length > 0) {
49987
+ lines.push(`${featured.length} featured system(s) on ${remote}:`, ...systemLines(featured), "");
49988
+ }
49989
+ if (catalog.community.length > 0) {
49990
+ const label = isFiltered ? `matching community system(s) on ${remote}` : `community system(s) on ${remote}, newest first`;
49991
+ lines.push(`${catalog.community.length} ${label}:`, ...systemLines(catalog.community));
49992
+ if (catalog.communityNextCursor) {
49993
+ lines.push(` …more: craft systems --community --cursor ${catalog.communityNextCursor}`);
49994
+ }
49995
+ lines.push("");
49996
+ }
49997
+ if (featured.length === 0 && catalog.community.length === 0) {
49998
+ printLines([
49999
+ isFiltered ? `No published systems match${query ? ` "${query}"` : ""}${tags.length > 0 ? ` (tags: ${tags.join(", ")})` : ""} on ${remote}.` : `No published systems on ${remote} yet.`
50000
+ ]);
50001
+ return 0;
50002
+ }
50003
+ lines.push('Start a project from one: craft create "My World" --system <id|name>');
50004
+ if (!isFiltered) {
50005
+ lines.push("Search: craft systems <query> — narrow by tag with --tag <tag>.");
50006
+ }
50007
+ printLines(lines);
50008
+ return 0;
50009
+ }
50010
+ function parseLimit(raw) {
50011
+ if (raw === undefined) {
50012
+ return;
50013
+ }
50014
+ const limit = Number.parseInt(raw, 10);
50015
+ if (!Number.isFinite(limit) || limit < 1) {
50016
+ throw new CliError(`--limit must be a positive number, got "${raw}"`, 2);
50017
+ }
50018
+ return limit;
50019
+ }
50020
+ function withIsoPublishedAt(system) {
50021
+ return {
50022
+ ...system,
50023
+ publishedAt: system.publishedAt ? toIsoTimestamp(system.publishedAt) : null
50024
+ };
50025
+ }
50026
+ function systemLines(systems) {
50027
+ const nameWidth = Math.min(MAX_NAME_WIDTH, Math.max(...systems.map((system) => system.name.length)));
50028
+ return systems.flatMap((system) => {
50029
+ const name = system.name.length > MAX_NAME_WIDTH ? `${system.name.slice(0, MAX_NAME_WIDTH - 1)}…` : system.name.padEnd(nameWidth);
50030
+ const details = [
50031
+ system.gameSystemName,
50032
+ system.ownerUsername ? `by ${system.ownerUsername}` : null,
50033
+ system.official ? "official" : null,
50034
+ `${system.fileCount ?? 0} files`,
50035
+ `${system.cloneCount} clones`,
50036
+ system.tags.length > 0 ? `tags: ${system.tags.join(", ")}` : null
50037
+ ].filter((part) => Boolean(part)).join(" · ");
50038
+ const lines = [` ${name} ${system.projectId} ${details}`];
50039
+ const blurb = descriptionBlurb(system.description);
50040
+ if (blurb) {
50041
+ lines.push(` ${blurb}`);
50042
+ }
50043
+ return lines;
50044
+ });
50045
+ }
50046
+ function descriptionBlurb(description) {
50047
+ const firstLine = description?.trim().split(`
50048
+ `, 1)[0]?.trim();
50049
+ if (!firstLine) {
50050
+ return null;
50051
+ }
50052
+ return firstLine.length > MAX_BLURB_LENGTH ? `${firstLine.slice(0, MAX_BLURB_LENGTH - 1)}…` : firstLine;
50053
+ }
50054
+ async function workspaceRemote2() {
50055
+ try {
50056
+ const workspace = await loadWorkspaceContext();
50057
+ return workspace.config.remote;
50058
+ } catch {
50059
+ return null;
50060
+ }
50061
+ }
50062
+
49699
50063
  // src/commands/type.ts
49700
50064
  import { existsSync as existsSync9 } from "node:fs";
49701
50065
  var TYPE_USAGE = "Usage: craft type new <Name> [--markdown] [--designation <token>] [--category <token>]";
@@ -49874,11 +50238,15 @@ var AUTH_COMMAND_LINES_DEV = ` login [--remote <env|origin>] Sign in with your
49874
50238
  `;
49875
50239
  var HELP_COMMAND_LINES = ` clone <projectIdOrUrl> [dir] Download a project as a local workspace
49876
50240
  ([dir] may be "." for the current directory)
49877
- create <name> [dir] [--description "..."] [--empty]
49878
- Create a new project and clone it locally:
49879
- craft create "My World" . \u2192 current directory
49880
- craft create . \u2192 here, named after the dir
49881
- (--empty skips the starter file types)
50241
+ create <name> [dir] [--system <id|name> | --blank] [--description "..."]
50242
+ Create a new project and clone it locally,
50243
+ built on a published system (--system) or
50244
+ empty (--blank). Run with neither to pick
50245
+ from the featured systems.
50246
+ craft create . \u2192 here, named after the dir
50247
+ systems [query] [--tag <t>] [--community] [--limit <n>]
50248
+ Browse published game systems to build on
50249
+ (curated featured row + community catalog)
49882
50250
  projects List the projects your account owns
49883
50251
  log <path> [--limit <n>] Show a file's version history (newest first)
49884
50252
  status [--remote] [--full] Show local changes (and remote drift with --remote;
@@ -49968,28 +50336,51 @@ Examples:
49968
50336
  craft clone <id> my-dir # into ./my-dir/
49969
50337
  craft clone <id> . # into the current directory
49970
50338
  `,
49971
- create: `Usage: craft create <name> [dir] [--description "..."] [--empty]
50339
+ create: `Usage: craft create <name> [dir] [--system <id|name> | --blank] [--description "..."]
49972
50340
  craft create . (create here, named after the current directory)
49973
50341
 
49974
50342
  Create a new project on the server and clone it into [dir] (defaults to a
49975
50343
  new folder named after the project; "." uses the current directory \u2014
49976
50344
  pre-existing dotfiles like .git/ are fine).
49977
50345
 
50346
+ New projects usually build on a published game system \u2014 a project shipping
50347
+ file types, rules content, and GM instructions to start from:
50348
+
50349
+ --system <id|name> clone that system as your starting point
50350
+ (browse them with \`craft systems\`)
50351
+ --blank start with an empty project \u2014 no file types, no
50352
+ content; author your own with \`craft type new\` and
50353
+ push them with --include-types (--empty is an older
50354
+ alias)
50355
+
50356
+ With neither, an interactive run offers the featured systems as a picker;
50357
+ scripts and agents must pass --system or --blank explicitly.
50358
+
49978
50359
  Examples:
49979
- craft create "My World" # creates ./my-world/
49980
- craft create "My World" my-dir # creates ./my-dir/
49981
- craft create "My World" . # into the current directory
49982
- craft create . # here, named after the directory
49983
-
49984
- By default the project seeds the standard starter file types (Character,
49985
- Location, Equipment, Game Start, GM Instructions) plus example files \u2014
49986
- generic fantasy-flavored; you may retool them. --empty skips all of that so
49987
- you author every file type yourself (see \`craft type new\`).
50360
+ craft create "My World" --system "Ashes RPG" # build on a system
50361
+ craft create "My World" --blank # creates ./my-world/
50362
+ craft create "My World" my-dir --blank # creates ./my-dir/
50363
+ craft create "My World" . --blank # into the current directory
50364
+ craft create . --blank # here, named after the dir
49988
50365
  `,
49989
50366
  projects: `Usage: craft projects
49990
50367
 
49991
50368
  List every project your account owns (name, id, last update, file count) \u2014
49992
50369
  newest activity first. Clone one with \`craft clone <projectId>\`.
50370
+ `,
50371
+ systems: `Usage: craft systems [query] [--tag <tag>]... [--community] [--limit <n>] [--json]
50372
+
50373
+ Browse published game systems \u2014 projects that ship file types, rules
50374
+ content, and GM instructions for other projects to build on. The featured
50375
+ row is curated; everything else is the community catalog, newest publish
50376
+ first.
50377
+
50378
+ [query] matches name, system label, description, and author. --tag keeps
50379
+ only systems carrying every given tag (repeatable). --community lists the
50380
+ community catalog alone. Long listings page with --cursor <cursor>, printed
50381
+ at the end of each page (--json carries it as communityNextCursor).
50382
+
50383
+ Start a project from a system: craft create "My World" --system <id|name>.
49993
50384
  `,
49994
50385
  log: `Usage: craft log <path> [--limit <n>]
49995
50386
 
@@ -50257,7 +50648,7 @@ Examples:
50257
50648
  craft clone <id> my-dir # into ./my-dir/
50258
50649
  craft clone <id> . # into the current directory
50259
50650
  `,
50260
- create: `Usage: craft create <name> [dir] [--description "..."] [--empty]
50651
+ create: `Usage: craft create <name> [dir] [--system <id|name> | --blank] [--description "..."]
50261
50652
  craft create . (create here, named after the current directory)
50262
50653
 
50263
50654
  Create a new project on the server and clone it into [dir] (defaults to a
@@ -50265,16 +50656,17 @@ new folder named after the project; "." uses the current directory \u2014
50265
50656
  pre-existing dotfiles like .git/ are fine). Targets the current
50266
50657
  environment \u2014 check with \`craft env\`.
50267
50658
 
50659
+ New projects usually build on a published game system: --system <id|name>
50660
+ clones one as the starting point (browse with \`craft systems\`), --blank
50661
+ starts with an empty project (no file types; --empty is an older alias).
50662
+ With neither, an interactive run offers the featured systems as a picker;
50663
+ scripts and agents must pass --system or --blank explicitly.
50664
+
50268
50665
  Examples:
50269
- craft create "My World" # creates ./my-world/
50270
- craft create "My World" my-dir # creates ./my-dir/
50271
- craft create "My World" . # into the current directory
50272
- craft create . # here, named after the directory
50273
-
50274
- By default the project seeds the standard starter file types (Character,
50275
- Location, Equipment, Game Start, GM Instructions) plus example files \u2014
50276
- generic fantasy-flavored; you may retool them. --empty skips all of that so
50277
- you author every file type yourself (see \`craft type new\`).
50666
+ craft create "My World" --system "Ashes RPG" # build on a system
50667
+ craft create "My World" --blank # creates ./my-world/
50668
+ craft create "My World" my-dir --blank # creates ./my-dir/
50669
+ craft create . --blank # here, named after the dir
50278
50670
  `,
50279
50671
  projects: `Usage: craft projects
50280
50672
 
@@ -50282,6 +50674,21 @@ List every project your account owns (name, id, last update, file count) \u2014
50282
50674
  newest activity first. Uses the current workspace's remote when run inside
50283
50675
  one; otherwise the current environment (see \`craft help env\`). Clone one
50284
50676
  with \`craft clone <projectId>\`.
50677
+ `,
50678
+ systems: `Usage: craft systems [query] [--tag <tag>]... [--community] [--limit <n>] [--json]
50679
+
50680
+ Browse published game systems \u2014 projects that ship file types, rules
50681
+ content, and GM instructions for other projects to build on. The featured
50682
+ row is curated; everything else is the community catalog, newest publish
50683
+ first. Uses the current workspace's remote when run inside one; otherwise
50684
+ the current environment (see \`craft help env\`).
50685
+
50686
+ [query] matches name, system label, description, and author. --tag keeps
50687
+ only systems carrying every given tag (repeatable). --community lists the
50688
+ community catalog alone. Long listings page with --cursor <cursor>, printed
50689
+ at the end of each page (--json carries it as communityNextCursor).
50690
+
50691
+ Start a project from a system: craft create "My World" --system <id|name>.
50285
50692
  `
50286
50693
  };
50287
50694
  function commandHelp(topic) {
@@ -50297,6 +50704,7 @@ var COMMANDS = {
50297
50704
  clone: runClone,
50298
50705
  create: runCreate,
50299
50706
  projects: runProjects,
50707
+ systems: runSystems,
50300
50708
  log: runLog,
50301
50709
  status: runStatus,
50302
50710
  check: runCheck,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craftrpgs/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Sync Craft projects with a local folder — clone, edit with any coding agent, push back.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://craftrpgs.com",