@craftrpgs/cli 0.1.3 → 0.1.5
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/README.md +4 -2
- package/dist/bin.js +474 -41
- 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
|
|
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
|
@@ -26939,6 +26939,22 @@ var imageObjectSchema = exports_external.preprocess((input) => normalizeImageInp
|
|
|
26939
26939
|
variations: exports_external.array(imageVariationSchema).max(MAX_FILE_TYPE_STATES).optional()
|
|
26940
26940
|
}).strict());
|
|
26941
26941
|
|
|
26942
|
+
// ../shared/src/projects/manual.ts
|
|
26943
|
+
var MANUAL_VERSION = 1;
|
|
26944
|
+
var MAX_MANUAL_CHAPTERS = 50;
|
|
26945
|
+
var MAX_MANUAL_TITLE_LENGTH = 120;
|
|
26946
|
+
var MAX_MANUAL_CONTENT_LENGTH = 1e5;
|
|
26947
|
+
var manualChapterSchema = exports_external.object({
|
|
26948
|
+
id: exports_external.string().min(1).max(64),
|
|
26949
|
+
title: exports_external.string().trim().min(1).max(MAX_MANUAL_TITLE_LENGTH),
|
|
26950
|
+
content: exports_external.string().max(MAX_MANUAL_CONTENT_LENGTH)
|
|
26951
|
+
});
|
|
26952
|
+
var manualSchema = exports_external.object({
|
|
26953
|
+
version: exports_external.literal(MANUAL_VERSION),
|
|
26954
|
+
updatedAt: exports_external.string().max(40).optional(),
|
|
26955
|
+
chapters: exports_external.array(manualChapterSchema).max(MAX_MANUAL_CHAPTERS)
|
|
26956
|
+
});
|
|
26957
|
+
|
|
26942
26958
|
// ../shared/src/projects/project-import.ts
|
|
26943
26959
|
var projectImportJsonValueSchema = exports_external.lazy(() => exports_external.union([
|
|
26944
26960
|
exports_external.string(),
|
|
@@ -27027,7 +27043,9 @@ var projectImportProjectSettingsSchema = exports_external.object({
|
|
|
27027
27043
|
gmToolSettings: gmToolSettingsInputSchema.optional(),
|
|
27028
27044
|
rootMapFileReferenceId: exports_external.string().uuid().nullable().optional(),
|
|
27029
27045
|
gm: projectImportGameMasterSettingsSchema.optional(),
|
|
27030
|
-
preludeFlow: exports_external.unknown().nullable().optional()
|
|
27046
|
+
preludeFlow: exports_external.unknown().nullable().optional(),
|
|
27047
|
+
playerHandbook: manualSchema.nullable().optional(),
|
|
27048
|
+
builderManual: manualSchema.nullable().optional()
|
|
27031
27049
|
}).strict();
|
|
27032
27050
|
var projectImportProjectSchema = exports_external.object({
|
|
27033
27051
|
name: exports_external.string().trim().min(1),
|
|
@@ -27609,8 +27627,11 @@ var cliImageVariationsResponseSchema = exports_external.object({
|
|
|
27609
27627
|
var cliCreateProjectRequestSchema = exports_external.object({
|
|
27610
27628
|
name: exports_external.string().min(1).max(200),
|
|
27611
27629
|
description: exports_external.string().max(2000).optional(),
|
|
27612
|
-
seedStarterContent: exports_external.boolean().optional()
|
|
27613
|
-
|
|
27630
|
+
seedStarterContent: exports_external.boolean().optional(),
|
|
27631
|
+
systemProjectId: uuidSchema.optional()
|
|
27632
|
+
}).strict().refine((value) => !(value.systemProjectId && value.seedStarterContent !== undefined), {
|
|
27633
|
+
message: "seedStarterContent does not apply when creating from a system — the system supplies the content"
|
|
27634
|
+
});
|
|
27614
27635
|
var cliCreateProjectResponseSchema = exports_external.object({
|
|
27615
27636
|
projectId: uuidSchema,
|
|
27616
27637
|
containerId: uuidSchema
|
|
@@ -27626,6 +27647,26 @@ var cliProjectSummarySchema = exports_external.object({
|
|
|
27626
27647
|
var cliProjectsListResponseSchema = exports_external.object({
|
|
27627
27648
|
projects: exports_external.array(cliProjectSummarySchema)
|
|
27628
27649
|
}).strict();
|
|
27650
|
+
var cliSystemSummarySchema = exports_external.object({
|
|
27651
|
+
projectId: uuidSchema,
|
|
27652
|
+
name: exports_external.string(),
|
|
27653
|
+
description: exports_external.string().nullable(),
|
|
27654
|
+
gameSystemName: exports_external.string().nullable(),
|
|
27655
|
+
ownerUsername: exports_external.string().nullable(),
|
|
27656
|
+
official: exports_external.boolean(),
|
|
27657
|
+
featured: exports_external.boolean(),
|
|
27658
|
+
tags: exports_external.array(exports_external.string()),
|
|
27659
|
+
fileTypeNames: exports_external.array(exports_external.string()),
|
|
27660
|
+
fileCount: exports_external.number().int().nonnegative().nullable(),
|
|
27661
|
+
likeCount: exports_external.number().int().nonnegative(),
|
|
27662
|
+
cloneCount: exports_external.number().int().nonnegative(),
|
|
27663
|
+
publishedAt: exports_external.string().nullable()
|
|
27664
|
+
}).strict();
|
|
27665
|
+
var cliSystemsResponseSchema = exports_external.object({
|
|
27666
|
+
featured: exports_external.array(cliSystemSummarySchema),
|
|
27667
|
+
community: exports_external.array(cliSystemSummarySchema),
|
|
27668
|
+
communityNextCursor: exports_external.string().nullable()
|
|
27669
|
+
}).strict();
|
|
27629
27670
|
var cliFileVersionEntrySchema = exports_external.object({
|
|
27630
27671
|
versionId: uuidSchema,
|
|
27631
27672
|
createdAt: exports_external.string(),
|
|
@@ -41859,7 +41900,7 @@ function describeRemoteSource(resolved) {
|
|
|
41859
41900
|
// package.json
|
|
41860
41901
|
var package_default = {
|
|
41861
41902
|
name: "@craftrpgs/cli",
|
|
41862
|
-
version: "0.1.
|
|
41903
|
+
version: "0.1.5",
|
|
41863
41904
|
description: "Sync Craft projects with a local folder — clone, edit with any coding agent, push back.",
|
|
41864
41905
|
license: "UNLICENSED",
|
|
41865
41906
|
homepage: "https://craftrpgs.com",
|
|
@@ -42044,6 +42085,30 @@ function createCliClient({
|
|
|
42044
42085
|
}
|
|
42045
42086
|
return parseJson(response, cliCreateProjectResponseSchema, "create project");
|
|
42046
42087
|
},
|
|
42088
|
+
async fetchSystems({ q, tags, cursor, limit } = {}) {
|
|
42089
|
+
const params = new URLSearchParams;
|
|
42090
|
+
if (q) {
|
|
42091
|
+
params.set("q", q);
|
|
42092
|
+
}
|
|
42093
|
+
for (const tag of tags ?? []) {
|
|
42094
|
+
params.append("tags", tag);
|
|
42095
|
+
}
|
|
42096
|
+
if (cursor) {
|
|
42097
|
+
params.set("cursor", cursor);
|
|
42098
|
+
}
|
|
42099
|
+
if (limit !== undefined) {
|
|
42100
|
+
params.set("limit", String(limit));
|
|
42101
|
+
}
|
|
42102
|
+
const query = params.size > 0 ? `?${params.toString()}` : "";
|
|
42103
|
+
const response = await request(`/api/projects/cli/systems${query}`);
|
|
42104
|
+
if (response.status === 404) {
|
|
42105
|
+
return null;
|
|
42106
|
+
}
|
|
42107
|
+
if (!response.ok) {
|
|
42108
|
+
throw new CliError(`Listing systems failed: ${await readErrorMessage(response)}`, 1);
|
|
42109
|
+
}
|
|
42110
|
+
return parseJson(response, cliSystemsResponseSchema, "systems");
|
|
42111
|
+
},
|
|
42047
42112
|
async listProjects() {
|
|
42048
42113
|
const response = await request("/api/projects/cli/list");
|
|
42049
42114
|
if (!response.ok) {
|
|
@@ -42397,6 +42462,7 @@ async function runClone(argv) {
|
|
|
42397
42462
|
|
|
42398
42463
|
// src/commands/create.ts
|
|
42399
42464
|
import { basename } from "node:path";
|
|
42465
|
+
import { createInterface } from "node:readline/promises";
|
|
42400
42466
|
|
|
42401
42467
|
// src/commands/clone.ts
|
|
42402
42468
|
import { existsSync as existsSync5 } from "node:fs";
|
|
@@ -42501,6 +42567,11 @@ async function downloadAndUnpackSnapshot2({
|
|
|
42501
42567
|
|
|
42502
42568
|
// src/commands/create.ts
|
|
42503
42569
|
var DIRECTORY_NAME_SEPARATORS = /[-_\s]+/;
|
|
42570
|
+
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;
|
|
42571
|
+
var DIGITS_ONLY = /^\d+$/;
|
|
42572
|
+
var MAX_SYSTEM_MATCHES_SHOWN = 8;
|
|
42573
|
+
var NAME_RESOLUTION_PAGE_SIZE = 48;
|
|
42574
|
+
var NAME_RESOLUTION_MAX_PAGES = 10;
|
|
42504
42575
|
function projectNameFromDirectory(dirName) {
|
|
42505
42576
|
return dirName.split(DIRECTORY_NAME_SEPARATORS).filter((word) => word.length > 0).map((word) => (word[0]?.toUpperCase() ?? "") + word.slice(1)).join(" ");
|
|
42506
42577
|
}
|
|
@@ -42509,6 +42580,8 @@ async function runCreate(argv) {
|
|
|
42509
42580
|
argv,
|
|
42510
42581
|
flags: {
|
|
42511
42582
|
description: { type: "string" },
|
|
42583
|
+
system: { type: "string" },
|
|
42584
|
+
blank: { type: "boolean" },
|
|
42512
42585
|
empty: { type: "boolean" },
|
|
42513
42586
|
remote: { type: "string" },
|
|
42514
42587
|
json: { type: "boolean" }
|
|
@@ -42517,11 +42590,16 @@ async function runCreate(argv) {
|
|
|
42517
42590
|
});
|
|
42518
42591
|
const [nameArg, dirPositional] = args.positionals;
|
|
42519
42592
|
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);
|
|
42593
|
+
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
42594
|
}
|
|
42522
42595
|
if (nameArg === "." && dirPositional !== undefined) {
|
|
42523
42596
|
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
42597
|
}
|
|
42598
|
+
const systemArg = stringFlag(args, "system");
|
|
42599
|
+
const wantsBlank = args.booleans.has("blank") || args.booleans.has("empty");
|
|
42600
|
+
if (systemArg && wantsBlank) {
|
|
42601
|
+
throw new CliError("--system cannot combine with --blank/--empty — a project either builds on a system or starts from scratch.", 2);
|
|
42602
|
+
}
|
|
42525
42603
|
const namedAfterCwd = nameArg === ".";
|
|
42526
42604
|
const name = namedAfterCwd ? projectNameFromDirectory(basename(process.cwd())) : nameArg;
|
|
42527
42605
|
if (!name) {
|
|
@@ -42537,14 +42615,25 @@ async function runCreate(argv) {
|
|
|
42537
42615
|
loginHint: silentDefaultHint(resolved)
|
|
42538
42616
|
})
|
|
42539
42617
|
});
|
|
42540
|
-
const
|
|
42541
|
-
|
|
42542
|
-
|
|
42543
|
-
|
|
42544
|
-
|
|
42545
|
-
|
|
42546
|
-
|
|
42618
|
+
const base = await resolveCreateBase({
|
|
42619
|
+
client,
|
|
42620
|
+
systemArg,
|
|
42621
|
+
wantsBlank,
|
|
42622
|
+
name,
|
|
42623
|
+
nameArg,
|
|
42624
|
+
interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY && !args.booleans.has("json"))
|
|
42547
42625
|
});
|
|
42626
|
+
const description = stringFlag(args, "description");
|
|
42627
|
+
const request = { name };
|
|
42628
|
+
if (description) {
|
|
42629
|
+
request.description = description;
|
|
42630
|
+
}
|
|
42631
|
+
if (base.kind === "system") {
|
|
42632
|
+
request.systemProjectId = base.systemProjectId;
|
|
42633
|
+
} else {
|
|
42634
|
+
request.seedStarterContent = false;
|
|
42635
|
+
}
|
|
42636
|
+
const created = await client.createProject({ request });
|
|
42548
42637
|
const { targetDir, fileCount } = await downloadAndUnpackSnapshot2({
|
|
42549
42638
|
client,
|
|
42550
42639
|
projectId: created.projectId,
|
|
@@ -42557,11 +42646,13 @@ async function runCreate(argv) {
|
|
|
42557
42646
|
name,
|
|
42558
42647
|
remote,
|
|
42559
42648
|
dir: targetDir,
|
|
42560
|
-
files: fileCount
|
|
42649
|
+
files: fileCount,
|
|
42650
|
+
system: base.kind === "system" ? { projectId: base.systemProjectId, name: base.systemName } : null
|
|
42561
42651
|
});
|
|
42562
42652
|
} else {
|
|
42653
|
+
const fromSystem = base.kind === "system" ? ` from system ${base.systemName ? `"${base.systemName}" ` : ""}(${base.systemProjectId})` : "";
|
|
42563
42654
|
printLines([
|
|
42564
|
-
`Created project "${name}" (${created.projectId}) on ${remote} ` + `(${describeRemoteSource(resolved)}).`,
|
|
42655
|
+
`Created project "${name}" (${created.projectId}) on ${remote} ` + `(${describeRemoteSource(resolved)})${fromSystem}.`,
|
|
42565
42656
|
...namedAfterCwd ? [
|
|
42566
42657
|
'Named after the current directory — `craft create "My Name" .` picks the name yourself.'
|
|
42567
42658
|
] : [],
|
|
@@ -42571,6 +42662,165 @@ async function runCreate(argv) {
|
|
|
42571
42662
|
}
|
|
42572
42663
|
return 0;
|
|
42573
42664
|
}
|
|
42665
|
+
async function resolveCreateBase({
|
|
42666
|
+
client,
|
|
42667
|
+
systemArg,
|
|
42668
|
+
wantsBlank,
|
|
42669
|
+
name,
|
|
42670
|
+
nameArg,
|
|
42671
|
+
interactive
|
|
42672
|
+
}) {
|
|
42673
|
+
if (systemArg) {
|
|
42674
|
+
return await resolveSystemArg({ client, value: systemArg });
|
|
42675
|
+
}
|
|
42676
|
+
if (wantsBlank) {
|
|
42677
|
+
return { kind: "blank" };
|
|
42678
|
+
}
|
|
42679
|
+
const catalog = await client.fetchSystems();
|
|
42680
|
+
const featured = catalog?.featured ?? [];
|
|
42681
|
+
if (featured.length === 0) {
|
|
42682
|
+
return { kind: "blank" };
|
|
42683
|
+
}
|
|
42684
|
+
if (!interactive) {
|
|
42685
|
+
throw new CliError(pickBaseGuidance({ nameArg, featured }), 2);
|
|
42686
|
+
}
|
|
42687
|
+
return await promptForBase({ name, featured });
|
|
42688
|
+
}
|
|
42689
|
+
async function resolveSystemArg({
|
|
42690
|
+
client,
|
|
42691
|
+
value
|
|
42692
|
+
}) {
|
|
42693
|
+
const id = extractProjectId(value);
|
|
42694
|
+
if (id) {
|
|
42695
|
+
return { kind: "system", systemProjectId: id, systemName: null };
|
|
42696
|
+
}
|
|
42697
|
+
const catalog = await client.fetchSystems({
|
|
42698
|
+
q: value,
|
|
42699
|
+
limit: NAME_RESOLUTION_PAGE_SIZE
|
|
42700
|
+
});
|
|
42701
|
+
if (!catalog) {
|
|
42702
|
+
throw new CliError("This server does not support creating from a system yet — update the server, or omit --system.", 1);
|
|
42703
|
+
}
|
|
42704
|
+
const candidates = [...catalog.featured, ...catalog.community];
|
|
42705
|
+
let cursor = catalog.communityNextCursor;
|
|
42706
|
+
for (let page = 1;cursor && page < NAME_RESOLUTION_MAX_PAGES; page += 1) {
|
|
42707
|
+
const next = await client.fetchSystems({
|
|
42708
|
+
q: value,
|
|
42709
|
+
cursor,
|
|
42710
|
+
limit: NAME_RESOLUTION_PAGE_SIZE
|
|
42711
|
+
});
|
|
42712
|
+
if (!next) {
|
|
42713
|
+
break;
|
|
42714
|
+
}
|
|
42715
|
+
candidates.push(...next.community);
|
|
42716
|
+
cursor = next.communityNextCursor;
|
|
42717
|
+
}
|
|
42718
|
+
const exact = candidates.filter((system) => system.name.toLowerCase() === value.toLowerCase());
|
|
42719
|
+
const pool = exact.length > 0 ? exact : candidates;
|
|
42720
|
+
const [only] = pool;
|
|
42721
|
+
if (only && pool.length === 1) {
|
|
42722
|
+
return {
|
|
42723
|
+
kind: "system",
|
|
42724
|
+
systemProjectId: only.projectId,
|
|
42725
|
+
systemName: only.name
|
|
42726
|
+
};
|
|
42727
|
+
}
|
|
42728
|
+
if (pool.length === 0) {
|
|
42729
|
+
throw new CliError(`No published system matches "${value}" — browse the catalog with \`craft systems\`, then pass a name or id.`, 2);
|
|
42730
|
+
}
|
|
42731
|
+
throw new CliError([
|
|
42732
|
+
`"${value}" matches ${pool.length} published systems — pass the id instead:`,
|
|
42733
|
+
...pool.slice(0, MAX_SYSTEM_MATCHES_SHOWN).map((system) => ` ${systemChoiceLabel(system)} ${system.projectId}`)
|
|
42734
|
+
].join(`
|
|
42735
|
+
`), 2);
|
|
42736
|
+
}
|
|
42737
|
+
function extractProjectId(value) {
|
|
42738
|
+
const matches = value.match(UUID_IN_TEXT2);
|
|
42739
|
+
return matches?.at(-1)?.toLowerCase() ?? null;
|
|
42740
|
+
}
|
|
42741
|
+
function systemChoiceLabel(system) {
|
|
42742
|
+
const parts = [
|
|
42743
|
+
system.name,
|
|
42744
|
+
system.gameSystemName ? `— ${system.gameSystemName}` : null,
|
|
42745
|
+
system.ownerUsername ? `by ${system.ownerUsername}` : null,
|
|
42746
|
+
system.official ? "(official)" : null
|
|
42747
|
+
];
|
|
42748
|
+
return parts.filter((part) => Boolean(part)).join(" ");
|
|
42749
|
+
}
|
|
42750
|
+
function pickBaseGuidance({
|
|
42751
|
+
nameArg,
|
|
42752
|
+
featured
|
|
42753
|
+
}) {
|
|
42754
|
+
const quoted = nameArg !== "." && nameArg.includes(" ") ? `"${nameArg}"` : nameArg;
|
|
42755
|
+
return [
|
|
42756
|
+
"Pick a base for the new project — new projects usually build on a published system:",
|
|
42757
|
+
` craft create ${quoted} --system <id|name> # build on a system`,
|
|
42758
|
+
` craft create ${quoted} --blank # start from scratch (an empty project)`,
|
|
42759
|
+
"",
|
|
42760
|
+
"Featured systems:",
|
|
42761
|
+
...featured.map((system) => ` ${systemChoiceLabel(system)} ${system.projectId}`),
|
|
42762
|
+
"",
|
|
42763
|
+
"Browse or search the full catalog with `craft systems`."
|
|
42764
|
+
].join(`
|
|
42765
|
+
`);
|
|
42766
|
+
}
|
|
42767
|
+
function parseBaseAnswer(answer, featured) {
|
|
42768
|
+
const trimmed = answer.trim();
|
|
42769
|
+
const lowered = trimmed.toLowerCase();
|
|
42770
|
+
if (lowered === "q" || lowered === "quit") {
|
|
42771
|
+
return "quit";
|
|
42772
|
+
}
|
|
42773
|
+
if (lowered === "b" || lowered === "blank") {
|
|
42774
|
+
return { kind: "blank" };
|
|
42775
|
+
}
|
|
42776
|
+
if (DIGITS_ONLY.test(trimmed)) {
|
|
42777
|
+
const chosen = featured[Number.parseInt(trimmed, 10) - 1];
|
|
42778
|
+
return chosen ? {
|
|
42779
|
+
kind: "system",
|
|
42780
|
+
systemProjectId: chosen.projectId,
|
|
42781
|
+
systemName: chosen.name
|
|
42782
|
+
} : "invalid";
|
|
42783
|
+
}
|
|
42784
|
+
const id = extractProjectId(trimmed);
|
|
42785
|
+
if (id) {
|
|
42786
|
+
const known = featured.find((system) => system.projectId === id);
|
|
42787
|
+
return {
|
|
42788
|
+
kind: "system",
|
|
42789
|
+
systemProjectId: id,
|
|
42790
|
+
systemName: known?.name ?? null
|
|
42791
|
+
};
|
|
42792
|
+
}
|
|
42793
|
+
return "invalid";
|
|
42794
|
+
}
|
|
42795
|
+
async function promptForBase({
|
|
42796
|
+
name,
|
|
42797
|
+
featured
|
|
42798
|
+
}) {
|
|
42799
|
+
printLines([
|
|
42800
|
+
`Choose a base for "${name}" — new projects usually build on a system:`,
|
|
42801
|
+
...featured.map((system, index) => ` ${index + 1}) ${systemChoiceLabel(system)}`),
|
|
42802
|
+
" b) Blank project (empty — author your own file types)",
|
|
42803
|
+
"More systems: `craft systems`, then rerun with --system <id|name>."
|
|
42804
|
+
]);
|
|
42805
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
42806
|
+
try {
|
|
42807
|
+
for (;; ) {
|
|
42808
|
+
const answer = await rl.question(`Pick 1-${featured.length}, b for blank, or q to cancel: `);
|
|
42809
|
+
const choice = parseBaseAnswer(answer, featured);
|
|
42810
|
+
if (choice === "quit") {
|
|
42811
|
+
throw new CliError("Cancelled — nothing was created.", 2);
|
|
42812
|
+
}
|
|
42813
|
+
if (choice !== "invalid") {
|
|
42814
|
+
return choice;
|
|
42815
|
+
}
|
|
42816
|
+
printLines([
|
|
42817
|
+
`Enter a number between 1 and ${featured.length}, a system id, b, or q.`
|
|
42818
|
+
]);
|
|
42819
|
+
}
|
|
42820
|
+
} finally {
|
|
42821
|
+
rl.close();
|
|
42822
|
+
}
|
|
42823
|
+
}
|
|
42574
42824
|
// ../../node_modules/.bun/uuid@11.1.0/node_modules/uuid/dist/esm/rng.js
|
|
42575
42825
|
import { randomFillSync } from "crypto";
|
|
42576
42826
|
var rnds8Pool = new Uint8Array(256);
|
|
@@ -49696,6 +49946,138 @@ function renderStatus(status, openConflicts = []) {
|
|
|
49696
49946
|
return lines;
|
|
49697
49947
|
}
|
|
49698
49948
|
|
|
49949
|
+
// src/commands/systems.ts
|
|
49950
|
+
var MAX_NAME_WIDTH = 40;
|
|
49951
|
+
var MAX_BLURB_LENGTH = 100;
|
|
49952
|
+
async function runSystems(argv) {
|
|
49953
|
+
const args = parseCommandArgs({
|
|
49954
|
+
argv,
|
|
49955
|
+
flags: {
|
|
49956
|
+
tag: { type: "string", repeatable: true },
|
|
49957
|
+
community: { type: "boolean" },
|
|
49958
|
+
cursor: { type: "string" },
|
|
49959
|
+
limit: { type: "string" },
|
|
49960
|
+
remote: { type: "string" },
|
|
49961
|
+
json: { type: "boolean" }
|
|
49962
|
+
},
|
|
49963
|
+
maxPositionals: 1
|
|
49964
|
+
});
|
|
49965
|
+
const [query] = args.positionals;
|
|
49966
|
+
const tags = args.strings.get("tag") ?? [];
|
|
49967
|
+
const cursor = stringFlag(args, "cursor");
|
|
49968
|
+
const limit = parseLimit(stringFlag(args, "limit"));
|
|
49969
|
+
const pinned = await workspaceRemote2();
|
|
49970
|
+
const resolved = resolveRemote({
|
|
49971
|
+
flagRemote: stringFlag(args, "remote"),
|
|
49972
|
+
context: pinned ? { remote: pinned, source: "workspace" } : null
|
|
49973
|
+
});
|
|
49974
|
+
const remote = resolved.remote;
|
|
49975
|
+
const client = createCliClient({
|
|
49976
|
+
remote,
|
|
49977
|
+
auth: await resolveTokenProvider({
|
|
49978
|
+
remote,
|
|
49979
|
+
loginHint: silentDefaultHint(resolved)
|
|
49980
|
+
})
|
|
49981
|
+
});
|
|
49982
|
+
const catalog = await client.fetchSystems({
|
|
49983
|
+
...query ? { q: query } : {},
|
|
49984
|
+
...tags.length > 0 ? { tags } : {},
|
|
49985
|
+
...cursor ? { cursor } : {},
|
|
49986
|
+
...limit !== undefined ? { limit } : {}
|
|
49987
|
+
});
|
|
49988
|
+
if (!catalog) {
|
|
49989
|
+
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);
|
|
49990
|
+
}
|
|
49991
|
+
if (args.booleans.has("json")) {
|
|
49992
|
+
printJson({
|
|
49993
|
+
remote,
|
|
49994
|
+
featured: catalog.featured.map(withIsoPublishedAt),
|
|
49995
|
+
community: catalog.community.map(withIsoPublishedAt),
|
|
49996
|
+
communityNextCursor: catalog.communityNextCursor
|
|
49997
|
+
});
|
|
49998
|
+
return 0;
|
|
49999
|
+
}
|
|
50000
|
+
const isFiltered = Boolean(query) || tags.length > 0;
|
|
50001
|
+
const showFeatured = !(args.booleans.has("community") || cursor);
|
|
50002
|
+
const featured = showFeatured ? catalog.featured : [];
|
|
50003
|
+
const lines = [];
|
|
50004
|
+
if (featured.length > 0) {
|
|
50005
|
+
lines.push(`${featured.length} featured system(s) on ${remote}:`, ...systemLines(featured), "");
|
|
50006
|
+
}
|
|
50007
|
+
if (catalog.community.length > 0) {
|
|
50008
|
+
const label = isFiltered ? `matching community system(s) on ${remote}` : `community system(s) on ${remote}, newest first`;
|
|
50009
|
+
lines.push(`${catalog.community.length} ${label}:`, ...systemLines(catalog.community));
|
|
50010
|
+
if (catalog.communityNextCursor) {
|
|
50011
|
+
lines.push(` …more: craft systems --community --cursor ${catalog.communityNextCursor}`);
|
|
50012
|
+
}
|
|
50013
|
+
lines.push("");
|
|
50014
|
+
}
|
|
50015
|
+
if (featured.length === 0 && catalog.community.length === 0) {
|
|
50016
|
+
printLines([
|
|
50017
|
+
isFiltered ? `No published systems match${query ? ` "${query}"` : ""}${tags.length > 0 ? ` (tags: ${tags.join(", ")})` : ""} on ${remote}.` : `No published systems on ${remote} yet.`
|
|
50018
|
+
]);
|
|
50019
|
+
return 0;
|
|
50020
|
+
}
|
|
50021
|
+
lines.push('Start a project from one: craft create "My World" --system <id|name>');
|
|
50022
|
+
if (!isFiltered) {
|
|
50023
|
+
lines.push("Search: craft systems <query> — narrow by tag with --tag <tag>.");
|
|
50024
|
+
}
|
|
50025
|
+
printLines(lines);
|
|
50026
|
+
return 0;
|
|
50027
|
+
}
|
|
50028
|
+
function parseLimit(raw) {
|
|
50029
|
+
if (raw === undefined) {
|
|
50030
|
+
return;
|
|
50031
|
+
}
|
|
50032
|
+
const limit = Number.parseInt(raw, 10);
|
|
50033
|
+
if (!Number.isFinite(limit) || limit < 1) {
|
|
50034
|
+
throw new CliError(`--limit must be a positive number, got "${raw}"`, 2);
|
|
50035
|
+
}
|
|
50036
|
+
return limit;
|
|
50037
|
+
}
|
|
50038
|
+
function withIsoPublishedAt(system) {
|
|
50039
|
+
return {
|
|
50040
|
+
...system,
|
|
50041
|
+
publishedAt: system.publishedAt ? toIsoTimestamp(system.publishedAt) : null
|
|
50042
|
+
};
|
|
50043
|
+
}
|
|
50044
|
+
function systemLines(systems) {
|
|
50045
|
+
const nameWidth = Math.min(MAX_NAME_WIDTH, Math.max(...systems.map((system) => system.name.length)));
|
|
50046
|
+
return systems.flatMap((system) => {
|
|
50047
|
+
const name = system.name.length > MAX_NAME_WIDTH ? `${system.name.slice(0, MAX_NAME_WIDTH - 1)}…` : system.name.padEnd(nameWidth);
|
|
50048
|
+
const details = [
|
|
50049
|
+
system.gameSystemName,
|
|
50050
|
+
system.ownerUsername ? `by ${system.ownerUsername}` : null,
|
|
50051
|
+
system.official ? "official" : null,
|
|
50052
|
+
`${system.fileCount ?? 0} files`,
|
|
50053
|
+
`${system.cloneCount} clones`,
|
|
50054
|
+
system.tags.length > 0 ? `tags: ${system.tags.join(", ")}` : null
|
|
50055
|
+
].filter((part) => Boolean(part)).join(" · ");
|
|
50056
|
+
const lines = [` ${name} ${system.projectId} ${details}`];
|
|
50057
|
+
const blurb = descriptionBlurb(system.description);
|
|
50058
|
+
if (blurb) {
|
|
50059
|
+
lines.push(` ${blurb}`);
|
|
50060
|
+
}
|
|
50061
|
+
return lines;
|
|
50062
|
+
});
|
|
50063
|
+
}
|
|
50064
|
+
function descriptionBlurb(description) {
|
|
50065
|
+
const firstLine = description?.trim().split(`
|
|
50066
|
+
`, 1)[0]?.trim();
|
|
50067
|
+
if (!firstLine) {
|
|
50068
|
+
return null;
|
|
50069
|
+
}
|
|
50070
|
+
return firstLine.length > MAX_BLURB_LENGTH ? `${firstLine.slice(0, MAX_BLURB_LENGTH - 1)}…` : firstLine;
|
|
50071
|
+
}
|
|
50072
|
+
async function workspaceRemote2() {
|
|
50073
|
+
try {
|
|
50074
|
+
const workspace = await loadWorkspaceContext();
|
|
50075
|
+
return workspace.config.remote;
|
|
50076
|
+
} catch {
|
|
50077
|
+
return null;
|
|
50078
|
+
}
|
|
50079
|
+
}
|
|
50080
|
+
|
|
49699
50081
|
// src/commands/type.ts
|
|
49700
50082
|
import { existsSync as existsSync9 } from "node:fs";
|
|
49701
50083
|
var TYPE_USAGE = "Usage: craft type new <Name> [--markdown] [--designation <token>] [--category <token>]";
|
|
@@ -49874,11 +50256,15 @@ var AUTH_COMMAND_LINES_DEV = ` login [--remote <env|origin>] Sign in with your
|
|
|
49874
50256
|
`;
|
|
49875
50257
|
var HELP_COMMAND_LINES = ` clone <projectIdOrUrl> [dir] Download a project as a local workspace
|
|
49876
50258
|
([dir] may be "." for the current directory)
|
|
49877
|
-
create <name> [dir] [--description "..."]
|
|
49878
|
-
Create a new project and clone it locally
|
|
49879
|
-
|
|
49880
|
-
|
|
49881
|
-
|
|
50259
|
+
create <name> [dir] [--system <id|name> | --blank] [--description "..."]
|
|
50260
|
+
Create a new project and clone it locally,
|
|
50261
|
+
built on a published system (--system) or
|
|
50262
|
+
empty (--blank). Run with neither to pick
|
|
50263
|
+
from the featured systems.
|
|
50264
|
+
craft create . \u2192 here, named after the dir
|
|
50265
|
+
systems [query] [--tag <t>] [--community] [--limit <n>]
|
|
50266
|
+
Browse published game systems to build on
|
|
50267
|
+
(curated featured row + community catalog)
|
|
49882
50268
|
projects List the projects your account owns
|
|
49883
50269
|
log <path> [--limit <n>] Show a file's version history (newest first)
|
|
49884
50270
|
status [--remote] [--full] Show local changes (and remote drift with --remote;
|
|
@@ -49968,28 +50354,51 @@ Examples:
|
|
|
49968
50354
|
craft clone <id> my-dir # into ./my-dir/
|
|
49969
50355
|
craft clone <id> . # into the current directory
|
|
49970
50356
|
`,
|
|
49971
|
-
create: `Usage: craft create <name> [dir] [--description "..."]
|
|
50357
|
+
create: `Usage: craft create <name> [dir] [--system <id|name> | --blank] [--description "..."]
|
|
49972
50358
|
craft create . (create here, named after the current directory)
|
|
49973
50359
|
|
|
49974
50360
|
Create a new project on the server and clone it into [dir] (defaults to a
|
|
49975
50361
|
new folder named after the project; "." uses the current directory \u2014
|
|
49976
50362
|
pre-existing dotfiles like .git/ are fine).
|
|
49977
50363
|
|
|
50364
|
+
New projects usually build on a published game system \u2014 a project shipping
|
|
50365
|
+
file types, rules content, and GM instructions to start from:
|
|
50366
|
+
|
|
50367
|
+
--system <id|name> clone that system as your starting point
|
|
50368
|
+
(browse them with \`craft systems\`)
|
|
50369
|
+
--blank start with an empty project \u2014 no file types, no
|
|
50370
|
+
content; author your own with \`craft type new\` and
|
|
50371
|
+
push them with --include-types (--empty is an older
|
|
50372
|
+
alias)
|
|
50373
|
+
|
|
50374
|
+
With neither, an interactive run offers the featured systems as a picker;
|
|
50375
|
+
scripts and agents must pass --system or --blank explicitly.
|
|
50376
|
+
|
|
49978
50377
|
Examples:
|
|
49979
|
-
craft create "My World"
|
|
49980
|
-
craft create "My World"
|
|
49981
|
-
craft create "My World"
|
|
49982
|
-
craft create .
|
|
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\`).
|
|
50378
|
+
craft create "My World" --system "Ashes RPG" # build on a system
|
|
50379
|
+
craft create "My World" --blank # creates ./my-world/
|
|
50380
|
+
craft create "My World" my-dir --blank # creates ./my-dir/
|
|
50381
|
+
craft create "My World" . --blank # into the current directory
|
|
50382
|
+
craft create . --blank # here, named after the dir
|
|
49988
50383
|
`,
|
|
49989
50384
|
projects: `Usage: craft projects
|
|
49990
50385
|
|
|
49991
50386
|
List every project your account owns (name, id, last update, file count) \u2014
|
|
49992
50387
|
newest activity first. Clone one with \`craft clone <projectId>\`.
|
|
50388
|
+
`,
|
|
50389
|
+
systems: `Usage: craft systems [query] [--tag <tag>]... [--community] [--limit <n>] [--json]
|
|
50390
|
+
|
|
50391
|
+
Browse published game systems \u2014 projects that ship file types, rules
|
|
50392
|
+
content, and GM instructions for other projects to build on. The featured
|
|
50393
|
+
row is curated; everything else is the community catalog, newest publish
|
|
50394
|
+
first.
|
|
50395
|
+
|
|
50396
|
+
[query] matches name, system label, description, and author. --tag keeps
|
|
50397
|
+
only systems carrying every given tag (repeatable). --community lists the
|
|
50398
|
+
community catalog alone. Long listings page with --cursor <cursor>, printed
|
|
50399
|
+
at the end of each page (--json carries it as communityNextCursor).
|
|
50400
|
+
|
|
50401
|
+
Start a project from a system: craft create "My World" --system <id|name>.
|
|
49993
50402
|
`,
|
|
49994
50403
|
log: `Usage: craft log <path> [--limit <n>]
|
|
49995
50404
|
|
|
@@ -50094,12 +50503,19 @@ content. File type changes from the zip apply only with --include-types
|
|
|
50094
50503
|
(missing types are created, drifted types updated \u2014 the server revalidates
|
|
50095
50504
|
existing files against schema changes). Project settings changes from the
|
|
50096
50505
|
zip's .craft/project.json apply only with --include-settings; without it,
|
|
50097
|
-
differing settings are listed but left untouched.
|
|
50506
|
+
differing settings are listed but left untouched. Every field of
|
|
50507
|
+
project.json is diffed and applied individually: name, description, tags,
|
|
50508
|
+
and settings.* \u2014 theme, image, projectIcon, attribution, gameSystemName,
|
|
50509
|
+
headlineLabel, headline, body, authorsNotes, gallery, galleryLayout,
|
|
50510
|
+
isClonable, hideHeroTitle, imageStyleInstructions, autoImageGeneration,
|
|
50511
|
+
defaultImageModel, gmToolSettings, rootMapFileReferenceId, preludeFlow, gm,
|
|
50512
|
+
playerHandbook, builderManual.
|
|
50098
50513
|
|
|
50099
50514
|
--dry-run shows the full plan without writing. --exclude <referenceId>
|
|
50100
50515
|
skips individual rows from the plan (repeatable, or comma-separated; get the
|
|
50101
50516
|
ids from a --dry-run --json plan). --exclude-setting <key> vetoes a single
|
|
50102
|
-
settings change (e.g.
|
|
50517
|
+
settings change, named exactly as the plan's rows are keyed (e.g.
|
|
50518
|
+
settings.preludeFlow, settings.playerHandbook). Runs inside the target
|
|
50103
50519
|
project's workspace, or anywhere with --project <projectId>. The apply is
|
|
50104
50520
|
one atomic batch: on any per-file failure (409) nothing is written and every
|
|
50105
50521
|
failure is listed. Exits 1 while conflicts or failures remain.
|
|
@@ -50257,7 +50673,7 @@ Examples:
|
|
|
50257
50673
|
craft clone <id> my-dir # into ./my-dir/
|
|
50258
50674
|
craft clone <id> . # into the current directory
|
|
50259
50675
|
`,
|
|
50260
|
-
create: `Usage: craft create <name> [dir] [--description "..."]
|
|
50676
|
+
create: `Usage: craft create <name> [dir] [--system <id|name> | --blank] [--description "..."]
|
|
50261
50677
|
craft create . (create here, named after the current directory)
|
|
50262
50678
|
|
|
50263
50679
|
Create a new project on the server and clone it into [dir] (defaults to a
|
|
@@ -50265,16 +50681,17 @@ new folder named after the project; "." uses the current directory \u2014
|
|
|
50265
50681
|
pre-existing dotfiles like .git/ are fine). Targets the current
|
|
50266
50682
|
environment \u2014 check with \`craft env\`.
|
|
50267
50683
|
|
|
50684
|
+
New projects usually build on a published game system: --system <id|name>
|
|
50685
|
+
clones one as the starting point (browse with \`craft systems\`), --blank
|
|
50686
|
+
starts with an empty project (no file types; --empty is an older alias).
|
|
50687
|
+
With neither, an interactive run offers the featured systems as a picker;
|
|
50688
|
+
scripts and agents must pass --system or --blank explicitly.
|
|
50689
|
+
|
|
50268
50690
|
Examples:
|
|
50269
|
-
craft create "My World"
|
|
50270
|
-
craft create "My World"
|
|
50271
|
-
craft create "My World"
|
|
50272
|
-
craft create .
|
|
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\`).
|
|
50691
|
+
craft create "My World" --system "Ashes RPG" # build on a system
|
|
50692
|
+
craft create "My World" --blank # creates ./my-world/
|
|
50693
|
+
craft create "My World" my-dir --blank # creates ./my-dir/
|
|
50694
|
+
craft create . --blank # here, named after the dir
|
|
50278
50695
|
`,
|
|
50279
50696
|
projects: `Usage: craft projects
|
|
50280
50697
|
|
|
@@ -50282,6 +50699,21 @@ List every project your account owns (name, id, last update, file count) \u2014
|
|
|
50282
50699
|
newest activity first. Uses the current workspace's remote when run inside
|
|
50283
50700
|
one; otherwise the current environment (see \`craft help env\`). Clone one
|
|
50284
50701
|
with \`craft clone <projectId>\`.
|
|
50702
|
+
`,
|
|
50703
|
+
systems: `Usage: craft systems [query] [--tag <tag>]... [--community] [--limit <n>] [--json]
|
|
50704
|
+
|
|
50705
|
+
Browse published game systems \u2014 projects that ship file types, rules
|
|
50706
|
+
content, and GM instructions for other projects to build on. The featured
|
|
50707
|
+
row is curated; everything else is the community catalog, newest publish
|
|
50708
|
+
first. Uses the current workspace's remote when run inside one; otherwise
|
|
50709
|
+
the current environment (see \`craft help env\`).
|
|
50710
|
+
|
|
50711
|
+
[query] matches name, system label, description, and author. --tag keeps
|
|
50712
|
+
only systems carrying every given tag (repeatable). --community lists the
|
|
50713
|
+
community catalog alone. Long listings page with --cursor <cursor>, printed
|
|
50714
|
+
at the end of each page (--json carries it as communityNextCursor).
|
|
50715
|
+
|
|
50716
|
+
Start a project from a system: craft create "My World" --system <id|name>.
|
|
50285
50717
|
`
|
|
50286
50718
|
};
|
|
50287
50719
|
function commandHelp(topic) {
|
|
@@ -50297,6 +50729,7 @@ var COMMANDS = {
|
|
|
50297
50729
|
clone: runClone,
|
|
50298
50730
|
create: runCreate,
|
|
50299
50731
|
projects: runProjects,
|
|
50732
|
+
systems: runSystems,
|
|
50300
50733
|
log: runLog,
|
|
50301
50734
|
status: runStatus,
|
|
50302
50735
|
check: runCheck,
|
package/package.json
CHANGED