@craftrpgs/cli 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +137 -29
- package/package.json +2 -2
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),
|
|
@@ -41858,7 +41859,7 @@ function describeRemoteSource(resolved) {
|
|
|
41858
41859
|
// package.json
|
|
41859
41860
|
var package_default = {
|
|
41860
41861
|
name: "@craftrpgs/cli",
|
|
41861
|
-
version: "0.1.
|
|
41862
|
+
version: "0.1.3",
|
|
41862
41863
|
description: "Sync Craft projects with a local folder — clone, edit with any coding agent, push back.",
|
|
41863
41864
|
license: "UNLICENSED",
|
|
41864
41865
|
homepage: "https://craftrpgs.com",
|
|
@@ -42280,6 +42281,36 @@ async function defaultCloneDir(base) {
|
|
|
42280
42281
|
}
|
|
42281
42282
|
throw new CliError(`Could not find a free directory name near ${base}; pass [dir] explicitly.`, 2);
|
|
42282
42283
|
}
|
|
42284
|
+
async function assertUsableCloneTarget({
|
|
42285
|
+
targetDir,
|
|
42286
|
+
zipFilePaths
|
|
42287
|
+
}) {
|
|
42288
|
+
if (!existsSync4(targetDir)) {
|
|
42289
|
+
return;
|
|
42290
|
+
}
|
|
42291
|
+
const existing = await readdir3(targetDir);
|
|
42292
|
+
const visible = existing.filter((name) => !shouldSkipWorkspaceName(name));
|
|
42293
|
+
const collisions = zipFilePaths.filter((relative2) => {
|
|
42294
|
+
try {
|
|
42295
|
+
return existsSync4(joinUnderWorkspaceRoot(targetDir, relative2));
|
|
42296
|
+
} catch {
|
|
42297
|
+
return false;
|
|
42298
|
+
}
|
|
42299
|
+
});
|
|
42300
|
+
if (visible.length === 0 && collisions.length === 0) {
|
|
42301
|
+
return;
|
|
42302
|
+
}
|
|
42303
|
+
throw new CliError([
|
|
42304
|
+
`Refusing to clone into ${targetDir}:`,
|
|
42305
|
+
...visible.map((name) => ` ${name} — pre-existing and would be mixed into the new workspace`),
|
|
42306
|
+
...collisions.map((relative2) => ` ${relative2} — the project snapshot would overwrite it`),
|
|
42307
|
+
"Entries the workspace scanner ignores (.git/, .gitignore, other dotfiles) may pre-exist; move the listed items or pick a different [dir]."
|
|
42308
|
+
].join(`
|
|
42309
|
+
`), 2);
|
|
42310
|
+
}
|
|
42311
|
+
function nextStepHint(targetDir) {
|
|
42312
|
+
return targetDir === process.cwd() ? "Next: run `craft status`." : "Next: cd in and run `craft status`.";
|
|
42313
|
+
}
|
|
42283
42314
|
async function downloadAndUnpackSnapshot({
|
|
42284
42315
|
client,
|
|
42285
42316
|
projectId,
|
|
@@ -42290,12 +42321,8 @@ async function downloadAndUnpackSnapshot({
|
|
|
42290
42321
|
const paths = Object.keys(entries);
|
|
42291
42322
|
const rootDir = zipRootDir(paths);
|
|
42292
42323
|
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
|
-
}
|
|
42324
|
+
const zipFilePaths = paths.filter((path) => !path.endsWith("/")).map((path) => path.slice(rootDir.length + 1)).filter((relative2) => relative2 !== "");
|
|
42325
|
+
await assertUsableCloneTarget({ targetDir, zipFilePaths });
|
|
42299
42326
|
await mkdir3(targetDir, { recursive: true });
|
|
42300
42327
|
let fileCount = 0;
|
|
42301
42328
|
for (const [path, bytes] of Object.entries(entries)) {
|
|
@@ -42362,12 +42389,15 @@ async function runClone(argv) {
|
|
|
42362
42389
|
} else {
|
|
42363
42390
|
printLines([
|
|
42364
42391
|
`Cloned project ${target.projectId} into ${targetDir} (${fileCount} files).`,
|
|
42365
|
-
|
|
42392
|
+
nextStepHint(targetDir)
|
|
42366
42393
|
]);
|
|
42367
42394
|
}
|
|
42368
42395
|
return 0;
|
|
42369
42396
|
}
|
|
42370
42397
|
|
|
42398
|
+
// src/commands/create.ts
|
|
42399
|
+
import { basename } from "node:path";
|
|
42400
|
+
|
|
42371
42401
|
// src/commands/clone.ts
|
|
42372
42402
|
import { existsSync as existsSync5 } from "node:fs";
|
|
42373
42403
|
import { mkdir as mkdir4, readdir as readdir4, writeFile as writeFile4 } from "node:fs/promises";
|
|
@@ -42401,6 +42431,36 @@ async function defaultCloneDir2(base) {
|
|
|
42401
42431
|
}
|
|
42402
42432
|
throw new CliError(`Could not find a free directory name near ${base}; pass [dir] explicitly.`, 2);
|
|
42403
42433
|
}
|
|
42434
|
+
async function assertUsableCloneTarget2({
|
|
42435
|
+
targetDir,
|
|
42436
|
+
zipFilePaths
|
|
42437
|
+
}) {
|
|
42438
|
+
if (!existsSync5(targetDir)) {
|
|
42439
|
+
return;
|
|
42440
|
+
}
|
|
42441
|
+
const existing = await readdir4(targetDir);
|
|
42442
|
+
const visible = existing.filter((name) => !shouldSkipWorkspaceName(name));
|
|
42443
|
+
const collisions = zipFilePaths.filter((relative2) => {
|
|
42444
|
+
try {
|
|
42445
|
+
return existsSync5(joinUnderWorkspaceRoot(targetDir, relative2));
|
|
42446
|
+
} catch {
|
|
42447
|
+
return false;
|
|
42448
|
+
}
|
|
42449
|
+
});
|
|
42450
|
+
if (visible.length === 0 && collisions.length === 0) {
|
|
42451
|
+
return;
|
|
42452
|
+
}
|
|
42453
|
+
throw new CliError([
|
|
42454
|
+
`Refusing to clone into ${targetDir}:`,
|
|
42455
|
+
...visible.map((name) => ` ${name} — pre-existing and would be mixed into the new workspace`),
|
|
42456
|
+
...collisions.map((relative2) => ` ${relative2} — the project snapshot would overwrite it`),
|
|
42457
|
+
"Entries the workspace scanner ignores (.git/, .gitignore, other dotfiles) may pre-exist; move the listed items or pick a different [dir]."
|
|
42458
|
+
].join(`
|
|
42459
|
+
`), 2);
|
|
42460
|
+
}
|
|
42461
|
+
function nextStepHint2(targetDir) {
|
|
42462
|
+
return targetDir === process.cwd() ? "Next: run `craft status`." : "Next: cd in and run `craft status`.";
|
|
42463
|
+
}
|
|
42404
42464
|
async function downloadAndUnpackSnapshot2({
|
|
42405
42465
|
client,
|
|
42406
42466
|
projectId,
|
|
@@ -42411,12 +42471,8 @@ async function downloadAndUnpackSnapshot2({
|
|
|
42411
42471
|
const paths = Object.keys(entries);
|
|
42412
42472
|
const rootDir = zipRootDir2(paths);
|
|
42413
42473
|
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
|
-
}
|
|
42474
|
+
const zipFilePaths = paths.filter((path) => !path.endsWith("/")).map((path) => path.slice(rootDir.length + 1)).filter((relative2) => relative2 !== "");
|
|
42475
|
+
await assertUsableCloneTarget2({ targetDir, zipFilePaths });
|
|
42420
42476
|
await mkdir4(targetDir, { recursive: true });
|
|
42421
42477
|
let fileCount = 0;
|
|
42422
42478
|
for (const [path, bytes] of Object.entries(entries)) {
|
|
@@ -42444,6 +42500,10 @@ async function downloadAndUnpackSnapshot2({
|
|
|
42444
42500
|
}
|
|
42445
42501
|
|
|
42446
42502
|
// src/commands/create.ts
|
|
42503
|
+
var DIRECTORY_NAME_SEPARATORS = /[-_\s]+/;
|
|
42504
|
+
function projectNameFromDirectory(dirName) {
|
|
42505
|
+
return dirName.split(DIRECTORY_NAME_SEPARATORS).filter((word) => word.length > 0).map((word) => (word[0]?.toUpperCase() ?? "") + word.slice(1)).join(" ");
|
|
42506
|
+
}
|
|
42447
42507
|
async function runCreate(argv) {
|
|
42448
42508
|
const args = parseCommandArgs({
|
|
42449
42509
|
argv,
|
|
@@ -42455,10 +42515,19 @@ async function runCreate(argv) {
|
|
|
42455
42515
|
},
|
|
42456
42516
|
maxPositionals: 2
|
|
42457
42517
|
});
|
|
42458
|
-
const [
|
|
42518
|
+
const [nameArg, dirPositional] = args.positionals;
|
|
42519
|
+
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);
|
|
42521
|
+
}
|
|
42522
|
+
if (nameArg === "." && dirPositional !== undefined) {
|
|
42523
|
+
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
|
+
}
|
|
42525
|
+
const namedAfterCwd = nameArg === ".";
|
|
42526
|
+
const name = namedAfterCwd ? projectNameFromDirectory(basename(process.cwd())) : nameArg;
|
|
42459
42527
|
if (!name) {
|
|
42460
|
-
throw new CliError('
|
|
42528
|
+
throw new CliError('Could not derive a project name from the current directory — pass one explicitly: craft create "My World" .', 2);
|
|
42461
42529
|
}
|
|
42530
|
+
const dirArg = namedAfterCwd ? "." : dirPositional;
|
|
42462
42531
|
const resolved = resolveRemote({ flagRemote: stringFlag(args, "remote") });
|
|
42463
42532
|
const remote = resolved.remote;
|
|
42464
42533
|
const client = createCliClient({
|
|
@@ -42485,6 +42554,7 @@ async function runCreate(argv) {
|
|
|
42485
42554
|
printJson({
|
|
42486
42555
|
projectId: created.projectId,
|
|
42487
42556
|
containerId: created.containerId,
|
|
42557
|
+
name,
|
|
42488
42558
|
remote,
|
|
42489
42559
|
dir: targetDir,
|
|
42490
42560
|
files: fileCount
|
|
@@ -42492,8 +42562,11 @@ async function runCreate(argv) {
|
|
|
42492
42562
|
} else {
|
|
42493
42563
|
printLines([
|
|
42494
42564
|
`Created project "${name}" (${created.projectId}) on ${remote} ` + `(${describeRemoteSource(resolved)}).`,
|
|
42565
|
+
...namedAfterCwd ? [
|
|
42566
|
+
'Named after the current directory — `craft create "My Name" .` picks the name yourself.'
|
|
42567
|
+
] : [],
|
|
42495
42568
|
`Cloned into ${targetDir} (${fileCount} files).`,
|
|
42496
|
-
|
|
42569
|
+
nextStepHint2(targetDir)
|
|
42497
42570
|
]);
|
|
42498
42571
|
}
|
|
42499
42572
|
return 0;
|
|
@@ -43495,7 +43568,7 @@ async function workspacePin() {
|
|
|
43495
43568
|
|
|
43496
43569
|
// src/commands/image.ts
|
|
43497
43570
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
43498
|
-
import { basename, isAbsolute as isAbsolute2 } from "node:path";
|
|
43571
|
+
import { basename as basename2, isAbsolute as isAbsolute2 } from "node:path";
|
|
43499
43572
|
|
|
43500
43573
|
// ../shared/src/ledger-constants.ts
|
|
43501
43574
|
var SYSTEM_ACCOUNTS = {
|
|
@@ -43753,7 +43826,7 @@ async function runImageUpload(argv) {
|
|
|
43753
43826
|
})
|
|
43754
43827
|
});
|
|
43755
43828
|
const form = new FormData;
|
|
43756
|
-
form.append("file", new File([new Uint8Array(bytes)],
|
|
43829
|
+
form.append("file", new File([new Uint8Array(bytes)], basename2(filePath)));
|
|
43757
43830
|
let result;
|
|
43758
43831
|
try {
|
|
43759
43832
|
result = await client.uploadImage({
|
|
@@ -43768,7 +43841,7 @@ async function runImageUpload(argv) {
|
|
|
43768
43841
|
return 0;
|
|
43769
43842
|
}
|
|
43770
43843
|
printLines([
|
|
43771
|
-
`Uploaded ${
|
|
43844
|
+
`Uploaded ${basename2(filePath)} (${result.width}x${result.height}, ${Math.round(result.bytes / 1024)}KB as webp):`,
|
|
43772
43845
|
` ${result.image.url ?? "<no url returned>"}`,
|
|
43773
43846
|
"",
|
|
43774
43847
|
`To use it, set this object as the file's "image" field and push:`,
|
|
@@ -43850,7 +43923,7 @@ async function runImageGenerate(argv) {
|
|
|
43850
43923
|
const referenceImageUrls = [];
|
|
43851
43924
|
for (const reference of localReferenceImages) {
|
|
43852
43925
|
const form = new FormData;
|
|
43853
|
-
form.append("file", new File([new Uint8Array(reference.bytes)],
|
|
43926
|
+
form.append("file", new File([new Uint8Array(reference.bytes)], basename2(reference.path)));
|
|
43854
43927
|
let uploaded;
|
|
43855
43928
|
try {
|
|
43856
43929
|
uploaded = await client.uploadImage({
|
|
@@ -43953,7 +44026,7 @@ function toActionableError(error48, operation = "generation") {
|
|
|
43953
44026
|
|
|
43954
44027
|
// src/commands/import.ts
|
|
43955
44028
|
import { readFile as readFile7 } from "node:fs/promises";
|
|
43956
|
-
import { basename as
|
|
44029
|
+
import { basename as basename3 } from "node:path";
|
|
43957
44030
|
async function runImport(argv) {
|
|
43958
44031
|
const args = parseCommandArgs({
|
|
43959
44032
|
argv,
|
|
@@ -43986,7 +44059,7 @@ async function runImport(argv) {
|
|
|
43986
44059
|
throw new CliError(`Could not read "${zipPath}": ${error48 instanceof Error ? error48.message : String(error48)}`, 2);
|
|
43987
44060
|
}
|
|
43988
44061
|
const form = new FormData;
|
|
43989
|
-
form.append("file", new File([new Uint8Array(zipBytes)],
|
|
44062
|
+
form.append("file", new File([new Uint8Array(zipBytes)], basename3(zipPath)));
|
|
43990
44063
|
if (args.booleans.has("dry-run")) {
|
|
43991
44064
|
form.append("dryRun", "true");
|
|
43992
44065
|
}
|
|
@@ -49800,8 +49873,11 @@ var AUTH_COMMAND_LINES_DEV = ` login [--remote <env|origin>] Sign in with your
|
|
|
49800
49873
|
Show or set the environment used from this directory
|
|
49801
49874
|
`;
|
|
49802
49875
|
var HELP_COMMAND_LINES = ` clone <projectIdOrUrl> [dir] Download a project as a local workspace
|
|
49876
|
+
([dir] may be "." for the current directory)
|
|
49803
49877
|
create <name> [dir] [--description "..."] [--empty]
|
|
49804
|
-
Create a new project and clone it locally
|
|
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
|
|
49805
49881
|
(--empty skips the starter file types)
|
|
49806
49882
|
projects List the projects your account owns
|
|
49807
49883
|
log <path> [--limit <n>] Show a file's version history (newest first)
|
|
@@ -49882,12 +49958,28 @@ Sign out \u2014 forget the credentials stored on this machine.
|
|
|
49882
49958
|
clone: `Usage: craft clone <projectIdOrUrl> [dir]
|
|
49883
49959
|
|
|
49884
49960
|
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
|
|
49961
|
+
project URL. [dir] defaults to a new folder named after the project; pass
|
|
49962
|
+
"." (or any path) to choose the destination. The target may already hold
|
|
49963
|
+
entries the workspace ignores (.git/, .gitignore, other dotfiles), but
|
|
49964
|
+
nothing the snapshot would mix with or overwrite.
|
|
49965
|
+
|
|
49966
|
+
Examples:
|
|
49967
|
+
craft clone <id> # into ./<project-name>/
|
|
49968
|
+
craft clone <id> my-dir # into ./my-dir/
|
|
49969
|
+
craft clone <id> . # into the current directory
|
|
49886
49970
|
`,
|
|
49887
49971
|
create: `Usage: craft create <name> [dir] [--description "..."] [--empty]
|
|
49972
|
+
craft create . (create here, named after the current directory)
|
|
49888
49973
|
|
|
49889
49974
|
Create a new project on the server and clone it into [dir] (defaults to a
|
|
49890
|
-
folder named after the project
|
|
49975
|
+
new folder named after the project; "." uses the current directory \u2014
|
|
49976
|
+
pre-existing dotfiles like .git/ are fine).
|
|
49977
|
+
|
|
49978
|
+
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
|
|
49891
49983
|
|
|
49892
49984
|
By default the project seeds the standard starter file types (Character,
|
|
49893
49985
|
Location, Equipment, Game Start, GM Instructions) plus example files \u2014
|
|
@@ -50155,13 +50247,29 @@ A cloned workspace always syncs with the server it was cloned from.
|
|
|
50155
50247
|
|
|
50156
50248
|
Download a project as a local workspace. A full project URL carries its
|
|
50157
50249
|
origin; a bare project id uses the current environment (see \`craft help
|
|
50158
|
-
env\`). [dir] defaults to a folder named after the project.
|
|
50250
|
+
env\`). [dir] defaults to a new folder named after the project; pass "."
|
|
50251
|
+
(or any path) to choose the destination. The target may already hold
|
|
50252
|
+
entries the workspace ignores (.git/, .gitignore, other dotfiles), but
|
|
50253
|
+
nothing the snapshot would mix with or overwrite.
|
|
50254
|
+
|
|
50255
|
+
Examples:
|
|
50256
|
+
craft clone <id> # into ./<project-name>/
|
|
50257
|
+
craft clone <id> my-dir # into ./my-dir/
|
|
50258
|
+
craft clone <id> . # into the current directory
|
|
50159
50259
|
`,
|
|
50160
50260
|
create: `Usage: craft create <name> [dir] [--description "..."] [--empty]
|
|
50261
|
+
craft create . (create here, named after the current directory)
|
|
50161
50262
|
|
|
50162
50263
|
Create a new project on the server and clone it into [dir] (defaults to a
|
|
50163
|
-
folder named after the project
|
|
50164
|
-
|
|
50264
|
+
new folder named after the project; "." uses the current directory \u2014
|
|
50265
|
+
pre-existing dotfiles like .git/ are fine). Targets the current
|
|
50266
|
+
environment \u2014 check with \`craft env\`.
|
|
50267
|
+
|
|
50268
|
+
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
|
|
50165
50273
|
|
|
50166
50274
|
By default the project seeds the standard starter file types (Character,
|
|
50167
50275
|
Location, Equipment, Game Start, GM Instructions) plus example files \u2014
|
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.3",
|
|
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",
|