@craftrpgs/cli 0.1.2 → 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.
- package/README.md +4 -2
- package/dist/bin.js +570 -54
- package/package.json +2 -2
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
|
@@ -27454,6 +27454,7 @@ function cliPushResponseForOpsSchema(ops) {
|
|
|
27454
27454
|
}
|
|
27455
27455
|
});
|
|
27456
27456
|
}
|
|
27457
|
+
var MAX_IMPORT_ZIP_BYTES = 50 * 1024 * 1024;
|
|
27457
27458
|
var cliImportOptionsSchema = exports_external.object({
|
|
27458
27459
|
dryRun: exports_external.boolean().default(false),
|
|
27459
27460
|
overwrite: exports_external.boolean().default(false),
|
|
@@ -27608,8 +27609,11 @@ var cliImageVariationsResponseSchema = exports_external.object({
|
|
|
27608
27609
|
var cliCreateProjectRequestSchema = exports_external.object({
|
|
27609
27610
|
name: exports_external.string().min(1).max(200),
|
|
27610
27611
|
description: exports_external.string().max(2000).optional(),
|
|
27611
|
-
seedStarterContent: exports_external.boolean().optional()
|
|
27612
|
-
|
|
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
|
+
});
|
|
27613
27617
|
var cliCreateProjectResponseSchema = exports_external.object({
|
|
27614
27618
|
projectId: uuidSchema,
|
|
27615
27619
|
containerId: uuidSchema
|
|
@@ -27625,6 +27629,26 @@ var cliProjectSummarySchema = exports_external.object({
|
|
|
27625
27629
|
var cliProjectsListResponseSchema = exports_external.object({
|
|
27626
27630
|
projects: exports_external.array(cliProjectSummarySchema)
|
|
27627
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();
|
|
27628
27652
|
var cliFileVersionEntrySchema = exports_external.object({
|
|
27629
27653
|
versionId: uuidSchema,
|
|
27630
27654
|
createdAt: exports_external.string(),
|
|
@@ -41858,7 +41882,7 @@ function describeRemoteSource(resolved) {
|
|
|
41858
41882
|
// package.json
|
|
41859
41883
|
var package_default = {
|
|
41860
41884
|
name: "@craftrpgs/cli",
|
|
41861
|
-
version: "0.1.
|
|
41885
|
+
version: "0.1.4",
|
|
41862
41886
|
description: "Sync Craft projects with a local folder — clone, edit with any coding agent, push back.",
|
|
41863
41887
|
license: "UNLICENSED",
|
|
41864
41888
|
homepage: "https://craftrpgs.com",
|
|
@@ -42043,6 +42067,30 @@ function createCliClient({
|
|
|
42043
42067
|
}
|
|
42044
42068
|
return parseJson(response, cliCreateProjectResponseSchema, "create project");
|
|
42045
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
|
+
},
|
|
42046
42094
|
async listProjects() {
|
|
42047
42095
|
const response = await request("/api/projects/cli/list");
|
|
42048
42096
|
if (!response.ok) {
|
|
@@ -42280,6 +42328,36 @@ async function defaultCloneDir(base) {
|
|
|
42280
42328
|
}
|
|
42281
42329
|
throw new CliError(`Could not find a free directory name near ${base}; pass [dir] explicitly.`, 2);
|
|
42282
42330
|
}
|
|
42331
|
+
async function assertUsableCloneTarget({
|
|
42332
|
+
targetDir,
|
|
42333
|
+
zipFilePaths
|
|
42334
|
+
}) {
|
|
42335
|
+
if (!existsSync4(targetDir)) {
|
|
42336
|
+
return;
|
|
42337
|
+
}
|
|
42338
|
+
const existing = await readdir3(targetDir);
|
|
42339
|
+
const visible = existing.filter((name) => !shouldSkipWorkspaceName(name));
|
|
42340
|
+
const collisions = zipFilePaths.filter((relative2) => {
|
|
42341
|
+
try {
|
|
42342
|
+
return existsSync4(joinUnderWorkspaceRoot(targetDir, relative2));
|
|
42343
|
+
} catch {
|
|
42344
|
+
return false;
|
|
42345
|
+
}
|
|
42346
|
+
});
|
|
42347
|
+
if (visible.length === 0 && collisions.length === 0) {
|
|
42348
|
+
return;
|
|
42349
|
+
}
|
|
42350
|
+
throw new CliError([
|
|
42351
|
+
`Refusing to clone into ${targetDir}:`,
|
|
42352
|
+
...visible.map((name) => ` ${name} — pre-existing and would be mixed into the new workspace`),
|
|
42353
|
+
...collisions.map((relative2) => ` ${relative2} — the project snapshot would overwrite it`),
|
|
42354
|
+
"Entries the workspace scanner ignores (.git/, .gitignore, other dotfiles) may pre-exist; move the listed items or pick a different [dir]."
|
|
42355
|
+
].join(`
|
|
42356
|
+
`), 2);
|
|
42357
|
+
}
|
|
42358
|
+
function nextStepHint(targetDir) {
|
|
42359
|
+
return targetDir === process.cwd() ? "Next: run `craft status`." : "Next: cd in and run `craft status`.";
|
|
42360
|
+
}
|
|
42283
42361
|
async function downloadAndUnpackSnapshot({
|
|
42284
42362
|
client,
|
|
42285
42363
|
projectId,
|
|
@@ -42290,12 +42368,8 @@ async function downloadAndUnpackSnapshot({
|
|
|
42290
42368
|
const paths = Object.keys(entries);
|
|
42291
42369
|
const rootDir = zipRootDir(paths);
|
|
42292
42370
|
const targetDir = dirArg ? resolve4(dirArg) : await defaultCloneDir(resolve4(rootDir));
|
|
42293
|
-
|
|
42294
|
-
|
|
42295
|
-
if (existing.length > 0) {
|
|
42296
|
-
throw new CliError(`Refusing to clone into non-empty directory ${targetDir}`, 2);
|
|
42297
|
-
}
|
|
42298
|
-
}
|
|
42371
|
+
const zipFilePaths = paths.filter((path) => !path.endsWith("/")).map((path) => path.slice(rootDir.length + 1)).filter((relative2) => relative2 !== "");
|
|
42372
|
+
await assertUsableCloneTarget({ targetDir, zipFilePaths });
|
|
42299
42373
|
await mkdir3(targetDir, { recursive: true });
|
|
42300
42374
|
let fileCount = 0;
|
|
42301
42375
|
for (const [path, bytes] of Object.entries(entries)) {
|
|
@@ -42362,12 +42436,16 @@ async function runClone(argv) {
|
|
|
42362
42436
|
} else {
|
|
42363
42437
|
printLines([
|
|
42364
42438
|
`Cloned project ${target.projectId} into ${targetDir} (${fileCount} files).`,
|
|
42365
|
-
|
|
42439
|
+
nextStepHint(targetDir)
|
|
42366
42440
|
]);
|
|
42367
42441
|
}
|
|
42368
42442
|
return 0;
|
|
42369
42443
|
}
|
|
42370
42444
|
|
|
42445
|
+
// src/commands/create.ts
|
|
42446
|
+
import { basename } from "node:path";
|
|
42447
|
+
import { createInterface } from "node:readline/promises";
|
|
42448
|
+
|
|
42371
42449
|
// src/commands/clone.ts
|
|
42372
42450
|
import { existsSync as existsSync5 } from "node:fs";
|
|
42373
42451
|
import { mkdir as mkdir4, readdir as readdir4, writeFile as writeFile4 } from "node:fs/promises";
|
|
@@ -42401,6 +42479,36 @@ async function defaultCloneDir2(base) {
|
|
|
42401
42479
|
}
|
|
42402
42480
|
throw new CliError(`Could not find a free directory name near ${base}; pass [dir] explicitly.`, 2);
|
|
42403
42481
|
}
|
|
42482
|
+
async function assertUsableCloneTarget2({
|
|
42483
|
+
targetDir,
|
|
42484
|
+
zipFilePaths
|
|
42485
|
+
}) {
|
|
42486
|
+
if (!existsSync5(targetDir)) {
|
|
42487
|
+
return;
|
|
42488
|
+
}
|
|
42489
|
+
const existing = await readdir4(targetDir);
|
|
42490
|
+
const visible = existing.filter((name) => !shouldSkipWorkspaceName(name));
|
|
42491
|
+
const collisions = zipFilePaths.filter((relative2) => {
|
|
42492
|
+
try {
|
|
42493
|
+
return existsSync5(joinUnderWorkspaceRoot(targetDir, relative2));
|
|
42494
|
+
} catch {
|
|
42495
|
+
return false;
|
|
42496
|
+
}
|
|
42497
|
+
});
|
|
42498
|
+
if (visible.length === 0 && collisions.length === 0) {
|
|
42499
|
+
return;
|
|
42500
|
+
}
|
|
42501
|
+
throw new CliError([
|
|
42502
|
+
`Refusing to clone into ${targetDir}:`,
|
|
42503
|
+
...visible.map((name) => ` ${name} — pre-existing and would be mixed into the new workspace`),
|
|
42504
|
+
...collisions.map((relative2) => ` ${relative2} — the project snapshot would overwrite it`),
|
|
42505
|
+
"Entries the workspace scanner ignores (.git/, .gitignore, other dotfiles) may pre-exist; move the listed items or pick a different [dir]."
|
|
42506
|
+
].join(`
|
|
42507
|
+
`), 2);
|
|
42508
|
+
}
|
|
42509
|
+
function nextStepHint2(targetDir) {
|
|
42510
|
+
return targetDir === process.cwd() ? "Next: run `craft status`." : "Next: cd in and run `craft status`.";
|
|
42511
|
+
}
|
|
42404
42512
|
async function downloadAndUnpackSnapshot2({
|
|
42405
42513
|
client,
|
|
42406
42514
|
projectId,
|
|
@@ -42411,12 +42519,8 @@ async function downloadAndUnpackSnapshot2({
|
|
|
42411
42519
|
const paths = Object.keys(entries);
|
|
42412
42520
|
const rootDir = zipRootDir2(paths);
|
|
42413
42521
|
const targetDir = dirArg ? resolve5(dirArg) : await defaultCloneDir2(resolve5(rootDir));
|
|
42414
|
-
|
|
42415
|
-
|
|
42416
|
-
if (existing.length > 0) {
|
|
42417
|
-
throw new CliError(`Refusing to clone into non-empty directory ${targetDir}`, 2);
|
|
42418
|
-
}
|
|
42419
|
-
}
|
|
42522
|
+
const zipFilePaths = paths.filter((path) => !path.endsWith("/")).map((path) => path.slice(rootDir.length + 1)).filter((relative2) => relative2 !== "");
|
|
42523
|
+
await assertUsableCloneTarget2({ targetDir, zipFilePaths });
|
|
42420
42524
|
await mkdir4(targetDir, { recursive: true });
|
|
42421
42525
|
let fileCount = 0;
|
|
42422
42526
|
for (const [path, bytes] of Object.entries(entries)) {
|
|
@@ -42444,21 +42548,46 @@ async function downloadAndUnpackSnapshot2({
|
|
|
42444
42548
|
}
|
|
42445
42549
|
|
|
42446
42550
|
// src/commands/create.ts
|
|
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;
|
|
42557
|
+
function projectNameFromDirectory(dirName) {
|
|
42558
|
+
return dirName.split(DIRECTORY_NAME_SEPARATORS).filter((word) => word.length > 0).map((word) => (word[0]?.toUpperCase() ?? "") + word.slice(1)).join(" ");
|
|
42559
|
+
}
|
|
42447
42560
|
async function runCreate(argv) {
|
|
42448
42561
|
const args = parseCommandArgs({
|
|
42449
42562
|
argv,
|
|
42450
42563
|
flags: {
|
|
42451
42564
|
description: { type: "string" },
|
|
42565
|
+
system: { type: "string" },
|
|
42566
|
+
blank: { type: "boolean" },
|
|
42452
42567
|
empty: { type: "boolean" },
|
|
42453
42568
|
remote: { type: "string" },
|
|
42454
42569
|
json: { type: "boolean" }
|
|
42455
42570
|
},
|
|
42456
42571
|
maxPositionals: 2
|
|
42457
42572
|
});
|
|
42458
|
-
const [
|
|
42573
|
+
const [nameArg, dirPositional] = args.positionals;
|
|
42574
|
+
if (!nameArg) {
|
|
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);
|
|
42576
|
+
}
|
|
42577
|
+
if (nameArg === "." && dirPositional !== undefined) {
|
|
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);
|
|
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
|
+
}
|
|
42585
|
+
const namedAfterCwd = nameArg === ".";
|
|
42586
|
+
const name = namedAfterCwd ? projectNameFromDirectory(basename(process.cwd())) : nameArg;
|
|
42459
42587
|
if (!name) {
|
|
42460
|
-
throw new CliError('
|
|
42588
|
+
throw new CliError('Could not derive a project name from the current directory — pass one explicitly: craft create "My World" .', 2);
|
|
42461
42589
|
}
|
|
42590
|
+
const dirArg = namedAfterCwd ? "." : dirPositional;
|
|
42462
42591
|
const resolved = resolveRemote({ flagRemote: stringFlag(args, "remote") });
|
|
42463
42592
|
const remote = resolved.remote;
|
|
42464
42593
|
const client = createCliClient({
|
|
@@ -42468,14 +42597,25 @@ async function runCreate(argv) {
|
|
|
42468
42597
|
loginHint: silentDefaultHint(resolved)
|
|
42469
42598
|
})
|
|
42470
42599
|
});
|
|
42471
|
-
const
|
|
42472
|
-
|
|
42473
|
-
|
|
42474
|
-
|
|
42475
|
-
|
|
42476
|
-
|
|
42477
|
-
|
|
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"))
|
|
42478
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 });
|
|
42479
42619
|
const { targetDir, fileCount } = await downloadAndUnpackSnapshot2({
|
|
42480
42620
|
client,
|
|
42481
42621
|
projectId: created.projectId,
|
|
@@ -42485,19 +42625,184 @@ async function runCreate(argv) {
|
|
|
42485
42625
|
printJson({
|
|
42486
42626
|
projectId: created.projectId,
|
|
42487
42627
|
containerId: created.containerId,
|
|
42628
|
+
name,
|
|
42488
42629
|
remote,
|
|
42489
42630
|
dir: targetDir,
|
|
42490
|
-
files: fileCount
|
|
42631
|
+
files: fileCount,
|
|
42632
|
+
system: base.kind === "system" ? { projectId: base.systemProjectId, name: base.systemName } : null
|
|
42491
42633
|
});
|
|
42492
42634
|
} else {
|
|
42635
|
+
const fromSystem = base.kind === "system" ? ` from system ${base.systemName ? `"${base.systemName}" ` : ""}(${base.systemProjectId})` : "";
|
|
42493
42636
|
printLines([
|
|
42494
|
-
`Created project "${name}" (${created.projectId}) on ${remote} ` + `(${describeRemoteSource(resolved)}).`,
|
|
42637
|
+
`Created project "${name}" (${created.projectId}) on ${remote} ` + `(${describeRemoteSource(resolved)})${fromSystem}.`,
|
|
42638
|
+
...namedAfterCwd ? [
|
|
42639
|
+
'Named after the current directory — `craft create "My Name" .` picks the name yourself.'
|
|
42640
|
+
] : [],
|
|
42495
42641
|
`Cloned into ${targetDir} (${fileCount} files).`,
|
|
42496
|
-
|
|
42642
|
+
nextStepHint2(targetDir)
|
|
42497
42643
|
]);
|
|
42498
42644
|
}
|
|
42499
42645
|
return 0;
|
|
42500
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
|
+
}
|
|
42501
42806
|
// ../../node_modules/.bun/uuid@11.1.0/node_modules/uuid/dist/esm/rng.js
|
|
42502
42807
|
import { randomFillSync } from "crypto";
|
|
42503
42808
|
var rnds8Pool = new Uint8Array(256);
|
|
@@ -43495,7 +43800,7 @@ async function workspacePin() {
|
|
|
43495
43800
|
|
|
43496
43801
|
// src/commands/image.ts
|
|
43497
43802
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
43498
|
-
import { basename, isAbsolute as isAbsolute2 } from "node:path";
|
|
43803
|
+
import { basename as basename2, isAbsolute as isAbsolute2 } from "node:path";
|
|
43499
43804
|
|
|
43500
43805
|
// ../shared/src/ledger-constants.ts
|
|
43501
43806
|
var SYSTEM_ACCOUNTS = {
|
|
@@ -43753,7 +44058,7 @@ async function runImageUpload(argv) {
|
|
|
43753
44058
|
})
|
|
43754
44059
|
});
|
|
43755
44060
|
const form = new FormData;
|
|
43756
|
-
form.append("file", new File([new Uint8Array(bytes)],
|
|
44061
|
+
form.append("file", new File([new Uint8Array(bytes)], basename2(filePath)));
|
|
43757
44062
|
let result;
|
|
43758
44063
|
try {
|
|
43759
44064
|
result = await client.uploadImage({
|
|
@@ -43768,7 +44073,7 @@ async function runImageUpload(argv) {
|
|
|
43768
44073
|
return 0;
|
|
43769
44074
|
}
|
|
43770
44075
|
printLines([
|
|
43771
|
-
`Uploaded ${
|
|
44076
|
+
`Uploaded ${basename2(filePath)} (${result.width}x${result.height}, ${Math.round(result.bytes / 1024)}KB as webp):`,
|
|
43772
44077
|
` ${result.image.url ?? "<no url returned>"}`,
|
|
43773
44078
|
"",
|
|
43774
44079
|
`To use it, set this object as the file's "image" field and push:`,
|
|
@@ -43850,7 +44155,7 @@ async function runImageGenerate(argv) {
|
|
|
43850
44155
|
const referenceImageUrls = [];
|
|
43851
44156
|
for (const reference of localReferenceImages) {
|
|
43852
44157
|
const form = new FormData;
|
|
43853
|
-
form.append("file", new File([new Uint8Array(reference.bytes)],
|
|
44158
|
+
form.append("file", new File([new Uint8Array(reference.bytes)], basename2(reference.path)));
|
|
43854
44159
|
let uploaded;
|
|
43855
44160
|
try {
|
|
43856
44161
|
uploaded = await client.uploadImage({
|
|
@@ -43953,7 +44258,7 @@ function toActionableError(error48, operation = "generation") {
|
|
|
43953
44258
|
|
|
43954
44259
|
// src/commands/import.ts
|
|
43955
44260
|
import { readFile as readFile7 } from "node:fs/promises";
|
|
43956
|
-
import { basename as
|
|
44261
|
+
import { basename as basename3 } from "node:path";
|
|
43957
44262
|
async function runImport(argv) {
|
|
43958
44263
|
const args = parseCommandArgs({
|
|
43959
44264
|
argv,
|
|
@@ -43986,7 +44291,7 @@ async function runImport(argv) {
|
|
|
43986
44291
|
throw new CliError(`Could not read "${zipPath}": ${error48 instanceof Error ? error48.message : String(error48)}`, 2);
|
|
43987
44292
|
}
|
|
43988
44293
|
const form = new FormData;
|
|
43989
|
-
form.append("file", new File([new Uint8Array(zipBytes)],
|
|
44294
|
+
form.append("file", new File([new Uint8Array(zipBytes)], basename3(zipPath)));
|
|
43990
44295
|
if (args.booleans.has("dry-run")) {
|
|
43991
44296
|
form.append("dryRun", "true");
|
|
43992
44297
|
}
|
|
@@ -49623,6 +49928,138 @@ function renderStatus(status, openConflicts = []) {
|
|
|
49623
49928
|
return lines;
|
|
49624
49929
|
}
|
|
49625
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
|
+
|
|
49626
50063
|
// src/commands/type.ts
|
|
49627
50064
|
import { existsSync as existsSync9 } from "node:fs";
|
|
49628
50065
|
var TYPE_USAGE = "Usage: craft type new <Name> [--markdown] [--designation <token>] [--category <token>]";
|
|
@@ -49800,9 +50237,16 @@ var AUTH_COMMAND_LINES_DEV = ` login [--remote <env|origin>] Sign in with your
|
|
|
49800
50237
|
Show or set the environment used from this directory
|
|
49801
50238
|
`;
|
|
49802
50239
|
var HELP_COMMAND_LINES = ` clone <projectIdOrUrl> [dir] Download a project as a local workspace
|
|
49803
|
-
|
|
49804
|
-
|
|
49805
|
-
|
|
50240
|
+
([dir] may be "." for the current directory)
|
|
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)
|
|
49806
50250
|
projects List the projects your account owns
|
|
49807
50251
|
log <path> [--limit <n>] Show a file's version history (newest first)
|
|
49808
50252
|
status [--remote] [--full] Show local changes (and remote drift with --remote;
|
|
@@ -49882,22 +50326,61 @@ Sign out \u2014 forget the credentials stored on this machine.
|
|
|
49882
50326
|
clone: `Usage: craft clone <projectIdOrUrl> [dir]
|
|
49883
50327
|
|
|
49884
50328
|
Download a project as a local workspace. Accepts a project id or a pasted
|
|
49885
|
-
project URL. [dir] defaults to a folder named after the project
|
|
50329
|
+
project URL. [dir] defaults to a new folder named after the project; pass
|
|
50330
|
+
"." (or any path) to choose the destination. The target may already hold
|
|
50331
|
+
entries the workspace ignores (.git/, .gitignore, other dotfiles), but
|
|
50332
|
+
nothing the snapshot would mix with or overwrite.
|
|
50333
|
+
|
|
50334
|
+
Examples:
|
|
50335
|
+
craft clone <id> # into ./<project-name>/
|
|
50336
|
+
craft clone <id> my-dir # into ./my-dir/
|
|
50337
|
+
craft clone <id> . # into the current directory
|
|
49886
50338
|
`,
|
|
49887
|
-
create: `Usage: craft create <name> [dir] [--description "..."]
|
|
50339
|
+
create: `Usage: craft create <name> [dir] [--system <id|name> | --blank] [--description "..."]
|
|
50340
|
+
craft create . (create here, named after the current directory)
|
|
49888
50341
|
|
|
49889
50342
|
Create a new project on the server and clone it into [dir] (defaults to a
|
|
49890
|
-
folder named after the project
|
|
49891
|
-
|
|
49892
|
-
|
|
49893
|
-
|
|
49894
|
-
|
|
49895
|
-
|
|
50343
|
+
new folder named after the project; "." uses the current directory \u2014
|
|
50344
|
+
pre-existing dotfiles like .git/ are fine).
|
|
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
|
+
|
|
50359
|
+
Examples:
|
|
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
|
|
49896
50365
|
`,
|
|
49897
50366
|
projects: `Usage: craft projects
|
|
49898
50367
|
|
|
49899
50368
|
List every project your account owns (name, id, last update, file count) \u2014
|
|
49900
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>.
|
|
49901
50384
|
`,
|
|
49902
50385
|
log: `Usage: craft log <path> [--limit <n>]
|
|
49903
50386
|
|
|
@@ -50155,18 +50638,35 @@ A cloned workspace always syncs with the server it was cloned from.
|
|
|
50155
50638
|
|
|
50156
50639
|
Download a project as a local workspace. A full project URL carries its
|
|
50157
50640
|
origin; a bare project id uses the current environment (see \`craft help
|
|
50158
|
-
env\`). [dir] defaults to a folder named after the project.
|
|
50641
|
+
env\`). [dir] defaults to a new folder named after the project; pass "."
|
|
50642
|
+
(or any path) to choose the destination. The target may already hold
|
|
50643
|
+
entries the workspace ignores (.git/, .gitignore, other dotfiles), but
|
|
50644
|
+
nothing the snapshot would mix with or overwrite.
|
|
50645
|
+
|
|
50646
|
+
Examples:
|
|
50647
|
+
craft clone <id> # into ./<project-name>/
|
|
50648
|
+
craft clone <id> my-dir # into ./my-dir/
|
|
50649
|
+
craft clone <id> . # into the current directory
|
|
50159
50650
|
`,
|
|
50160
|
-
create: `Usage: craft create <name> [dir] [--description "..."]
|
|
50651
|
+
create: `Usage: craft create <name> [dir] [--system <id|name> | --blank] [--description "..."]
|
|
50652
|
+
craft create . (create here, named after the current directory)
|
|
50161
50653
|
|
|
50162
50654
|
Create a new project on the server and clone it into [dir] (defaults to a
|
|
50163
|
-
folder named after the project
|
|
50164
|
-
|
|
50165
|
-
|
|
50166
|
-
|
|
50167
|
-
|
|
50168
|
-
|
|
50169
|
-
|
|
50655
|
+
new folder named after the project; "." uses the current directory \u2014
|
|
50656
|
+
pre-existing dotfiles like .git/ are fine). Targets the current
|
|
50657
|
+
environment \u2014 check with \`craft env\`.
|
|
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
|
+
|
|
50665
|
+
Examples:
|
|
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
|
|
50170
50670
|
`,
|
|
50171
50671
|
projects: `Usage: craft projects
|
|
50172
50672
|
|
|
@@ -50174,6 +50674,21 @@ List every project your account owns (name, id, last update, file count) \u2014
|
|
|
50174
50674
|
newest activity first. Uses the current workspace's remote when run inside
|
|
50175
50675
|
one; otherwise the current environment (see \`craft help env\`). Clone one
|
|
50176
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>.
|
|
50177
50692
|
`
|
|
50178
50693
|
};
|
|
50179
50694
|
function commandHelp(topic) {
|
|
@@ -50189,6 +50704,7 @@ var COMMANDS = {
|
|
|
50189
50704
|
clone: runClone,
|
|
50190
50705
|
create: runCreate,
|
|
50191
50706
|
projects: runProjects,
|
|
50707
|
+
systems: runSystems,
|
|
50192
50708
|
log: runLog,
|
|
50193
50709
|
status: runStatus,
|
|
50194
50710
|
check: runCheck,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@craftrpgs/cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Sync Craft projects with a local folder
|
|
3
|
+
"version": "0.1.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",
|
|
7
7
|
"type": "module",
|