@caisual/cli 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/caisual.mjs +20 -7
- package/package.json +1 -1
package/dist/caisual.mjs
CHANGED
|
@@ -387,7 +387,10 @@ function validaManifest(valore) {
|
|
|
387
387
|
}
|
|
388
388
|
|
|
389
389
|
// ../../docs/publish.md
|
|
390
|
-
var publish_default = '# Publish a game on Caisual\n\nCaisual hosts browser games supplied as folders. Each publish creates an immutable version and moves the game\'s stable link to that version.\nThe current publishing flow is for single-player games and does not require changes in the Caisual dashboard.\n\n## Game folder\n\nUse this structure:\n\n```text\nmy-game/\n caisual.json\n client/\n index.html\n ...\n```\n\n`caisual.json` and `client/index.html` are required. Put every file used by the game under `client/`.\n\nRun `npx @caisual/cli init my-game` to create a minimal folder, or create these files yourself.\n\n## caisual.json\n\nThe file must contain one JSON object. Unknown fields are rejected. This is a complete example for the current single-player release:\n\n```json\n{\n "manifest": 1,\n "id": "my-game",\n "name": "My Game",\n "description": "A short description of the game.",\n "cover": "cover.png",\n "screenshots": ["screenshots/level-one.png"],\n "tags": ["puzzle"],\n "language": "en",\n "platform": "both",\n "orientation": "landscape",\n "input": ["keyboard", "mouse", "touch"],\n "visibility": "public",\n "network": [],\n "isolated": false,\n "players": { "min": 1, "max": 1 },\n "lobby": false,\n "roles": [],\n "teams": null,\n "voice": "none",\n "modes": []\n}\n```\n\n- `manifest` is required and must be `1`.\n- `id` is required. Use 3 to 32 lowercase ASCII letters or digits, with single hyphens only between groups. The ID becomes the URL slug. Choose it carefully because it cannot be renamed or reused after deletion.\n- `name` is required and must contain 1 to 60 characters.\n- `description` is optional, defaults to an empty string, and can contain at most 500 characters.\n- `cover` is optional. Use a relative path inside `client/`, or `null`. Do not include a query, fragment, empty segment, or parent segment.\n- `screenshots` is optional and defaults to `[]`. It accepts up to 8 relative paths inside `client/`.\n- `tags` is optional and defaults to `[]`. It accepts up to 10 values. Each value uses 1 to 24 lowercase letters, digits, or hyphens.\n- `language` is optional and defaults to `en`. Use a BCP 47 language tag such as `en`, `it`, or `pt-BR`.\n- `platform` is required. Use `desktop` when the game needs a keyboard, mouse, large display, or desktop performance. Use `mobile` when it is designed only for touch and small screens. Use `both` only after checking that layout, performance, and controls work on both.\n- `orientation` is optional and defaults to `landscape`. Use `landscape` or `portrait` to describe the intended mobile layout. The device may not honor an orientation request.\n- `input` is optional and defaults to `[]`. Include every supported input from `keyboard`, `mouse`, `touch`, and `gamepad`. Do not claim an input until the game is usable with it.\n- `visibility` is optional and defaults to `public`. Use `public` for catalog eligibility or `unlisted` for access by direct link only.\n- `network` is optional and defaults to `[]`. List every external host contacted or loaded by the game, without scheme, port, path, query, or fragment, for example `api.example.com`. If an external host is missing, the browser blocks the request. Keep the array empty when the game uses only its own files and Caisual services.\n- `isolated` is optional and defaults to `false`. Use `true` only when the game requires shared memory or threaded WebAssembly. Every external host in `network` must then send headers compatible with cross-origin isolation.\n- `players` is optional and defaults to `{ "min": 1, "max": 1 }`. Both values are integers from 1 to 16 and `max` must be at least `min`. Keep both at `1` in the current release.\n- `lobby` is optional and defaults to `false`. Keep it `false` in the current release.\n- `roles` is optional and defaults to `[]`. Each future entry has an `id` of 1 to 32 lowercase letters, digits, or internal hyphens, a `min` integer from 0 to 16, and an optional `max` in the same range. Keep it empty in the current release.\n- `teams` is optional and defaults to `null`. A future object has `min` and `max` integers from 2 to 16, with `max` at least `min`. Keep it `null` in the current release.\n- `voice` is optional and defaults to `none`. The accepted values are `none`, `room`, `team`, and `proximity`. Use `none` in the current release.\n- `modes` is optional and defaults to `[]`. A future mode has a unique `id` using 1 to 32 lowercase letters, digits, or internal hyphens. It may have `matchmaking` with a non-empty `key` array using the same format, a positive integer `timeoutMs`, and `fallback` set to `ghost` or `bot`. Keep it empty in the current release.\n\nThe CLI prints every manifest error in one run. Fix every listed field and rule before retrying.\n\n## client/index.html\n\n`index.html` must be at the root of `client/`. Use relative URLs such as `./game.js` or `assets/sprite.png`. Do not use root-relative URLs such as `/game.js`, and do not use parent paths that leave the published `client/` tree.\n\nDo not register a service worker. The game runs in an iframe on its own origin inside `caisual.com`. Test it without assuming access to the parent page, parent cookies, or files outside `client/`.\n\n## Limits\n\n- At most 2,000 files per version.\n- At most 50,000,000 bytes per file.\n- At most 200,000,000 bytes for all files in one version.\n- Dotfiles, dot-directories, and directories named `node_modules` are ignored.\n- Symbolic links and other non-regular files are rejected.\n\nReduce or split files that exceed the per-file limit. Remove generated files that the browser does not need.\n\n## Publish\n\nUse the key supplied by the creator. Set it in the environment so it does not enter shell history as a command-line flag:\n\n```sh\nexport CAISUAL_KEY=\'ck_...\'\nnpx @caisual/cli publish\n```\n\nRun the command from the game folder, or pass the folder path after `publish`. For local portal development only, set `CAISUAL_ORIGIN` to the local HTTP origin.\n\nThe CLI validates the folder, computes every file size and SHA-256 digest, creates a new version, uploads the files, completes the version, and prints the game URL. The stable URL is `https://caisual.com/g/<id>`.\n\nThe first games from a new creator are reviewed before they can appear in the public catalog. Their stable links still work while review is pending.\n\n## Update, unlist, or delete\n\nTo update a game, change its files without changing `id`, then run `npx @caisual/cli publish` again. This creates a new version and keeps the same stable game URL.\n\nTo remove a game from the catalog, set `visibility` to `unlisted` and publish, or change visibility from the dashboard. To delete a game, use the dashboard. Deletion is permanent and its ID cannot be reused.\n\n## Common errors\n\n- `CAISUAL_KEY is required`: export the creator\'s key in the same shell before publishing.\n- `The publish API key is not valid`: create a new key in the account dashboard if the old key expired or was revoked.\n- `caisual.json is not valid`: read every reported field and rule, fix all of them, then retry.\n- `client/index.html: file not found`: place `index.html` directly under `client/`, not in a nested build folder.\n- `referenced file not found`: make sure `cover` and every screenshot path match a file under `client/`, including letter case.\n- `file is larger than 50 MB`: compress, reduce, or split the asset and update its references.\n- `upload failed` or a temporary portal error: keep the files unchanged and retry the same publish command. The CLI retries temporary upload failures automatically.\n- An external request works locally but fails after publishing: add its host to `network` and publish a new version.\n- A threaded WebAssembly game fails to start: set `isolated` to `true` and verify that every declared external host supports cross-origin isolation.\n\nMultiplayer rooms arrive
|
|
390
|
+
var publish_default = '# Publish a game on Caisual\n\nCaisual hosts browser games supplied as folders. Each publish creates an immutable version and moves the game\'s stable link to that version.\nThe current publishing flow is for single-player games and does not require changes in the Caisual dashboard. Player identity, cloud saves, leaderboards, and the daily challenge come from the game kit, documented in [kit.md](./kit.md).\n\n## Game folder\n\nUse this structure:\n\n```text\nmy-game/\n caisual.json\n client/\n index.html\n ...\n```\n\n`caisual.json` and `client/index.html` are required. Put every file used by the game under `client/`.\n\nRun `npx @caisual/cli init my-game` to create a minimal folder, or create these files yourself.\n\n## caisual.json\n\nThe file must contain one JSON object. Unknown fields are rejected. This is a complete example for the current single-player release:\n\n```json\n{\n "manifest": 1,\n "id": "my-game",\n "name": "My Game",\n "description": "A short description of the game.",\n "cover": "cover.png",\n "screenshots": ["screenshots/level-one.png"],\n "tags": ["puzzle"],\n "language": "en",\n "platform": "both",\n "orientation": "landscape",\n "input": ["keyboard", "mouse", "touch"],\n "visibility": "public",\n "network": [],\n "isolated": false,\n "players": { "min": 1, "max": 1 },\n "lobby": false,\n "roles": [],\n "teams": null,\n "voice": "none",\n "modes": []\n}\n```\n\n- `manifest` is required and must be `1`.\n- `id` is required. Use 3 to 32 lowercase ASCII letters or digits, with single hyphens only between groups. The ID becomes the URL slug. Choose it carefully because it cannot be renamed or reused after deletion.\n- `name` is required and must contain 1 to 60 characters.\n- `description` is optional, defaults to an empty string, and can contain at most 500 characters.\n- `cover` is optional. Use a relative path inside `client/`, or `null`. Do not include a query, fragment, empty segment, or parent segment.\n- `screenshots` is optional and defaults to `[]`. It accepts up to 8 relative paths inside `client/`.\n- `tags` is optional and defaults to `[]`. It accepts up to 10 values. Each value uses 1 to 24 lowercase letters, digits, or hyphens.\n- `language` is optional and defaults to `en`. Use a BCP 47 language tag such as `en`, `it`, or `pt-BR`.\n- `platform` is required. Use `desktop` when the game needs a keyboard, mouse, large display, or desktop performance. Use `mobile` when it is designed only for touch and small screens. Use `both` only after checking that layout, performance, and controls work on both.\n- `orientation` is optional and defaults to `landscape`. Use `landscape` or `portrait` to describe the intended mobile layout. The device may not honor an orientation request.\n- `input` is optional and defaults to `[]`. Include every supported input from `keyboard`, `mouse`, `touch`, and `gamepad`. Do not claim an input until the game is usable with it.\n- `visibility` is optional and defaults to `public`. Use `public` for catalog eligibility or `unlisted` for access by direct link only.\n- `network` is optional and defaults to `[]`. List every external host contacted or loaded by the game, without scheme, port, path, query, or fragment, for example `api.example.com`. If an external host is missing, the browser blocks the request. Keep the array empty when the game uses only its own files and Caisual services.\n- `isolated` is optional and defaults to `false`. Use `true` only when the game requires shared memory or threaded WebAssembly. Every external host in `network` must then send headers compatible with cross-origin isolation.\n- `players` is optional and defaults to `{ "min": 1, "max": 1 }`. Both values are integers from 1 to 16 and `max` must be at least `min`. Keep both at `1` in the current release.\n- `lobby` is optional and defaults to `false`. Keep it `false` in the current release.\n- `roles` is optional and defaults to `[]`. Each future entry has an `id` of 1 to 32 lowercase letters, digits, or internal hyphens, a `min` integer from 0 to 16, and an optional `max` in the same range. Keep it empty in the current release.\n- `teams` is optional and defaults to `null`. A future object has `min` and `max` integers from 2 to 16, with `max` at least `min`. Keep it `null` in the current release.\n- `voice` is optional and defaults to `none`. The accepted values are `none`, `room`, `team`, and `proximity`. Use `none` in the current release.\n- `modes` is optional and defaults to `[]`. A future mode has a unique `id` using 1 to 32 lowercase letters, digits, or internal hyphens. It may have `matchmaking` with a non-empty `key` array using the same format, a positive integer `timeoutMs`, and `fallback` set to `ghost` or `bot`. Keep it empty in the current release.\n\nThe CLI prints every manifest error in one run. Fix every listed field and rule before retrying.\n\n## client/index.html\n\n`index.html` must be at the root of `client/`. Use relative URLs such as `./game.js` or `assets/sprite.png`. Do not use root-relative URLs such as `/game.js`, and do not use parent paths that leave the published `client/` tree.\n\nTo use player identity, saves, and leaderboards, import the kit from `/__caisual/kit/v1.js` as shown in [kit.md](./kit.md). The path `/__caisual/` is reserved: do not put game files under it.\n\nDo not register a service worker. The game runs in an iframe on its own origin inside `caisual.com`. Test it without assuming access to the parent page, parent cookies, or files outside `client/`.\n\n## Limits\n\n- At most 2,000 files per version.\n- At most 50,000,000 bytes per file.\n- At most 200,000,000 bytes for all files in one version.\n- Dotfiles, dot-directories, and directories named `node_modules` are ignored.\n- Symbolic links and other non-regular files are rejected.\n\nReduce or split files that exceed the per-file limit. Remove generated files that the browser does not need.\n\n## Publish\n\nUse the key supplied by the creator. Set it in the environment so it does not enter shell history as a command-line flag:\n\n```sh\nexport CAISUAL_KEY=\'ck_...\'\nnpx @caisual/cli publish\n```\n\nRun the command from the game folder, or pass the folder path after `publish`. For local portal development only, set `CAISUAL_ORIGIN` to the local HTTP origin.\n\nThe CLI validates the folder, computes every file size and SHA-256 digest, creates a new version, uploads the files, completes the version, and prints the game URL. The stable URL is `https://caisual.com/g/<id>`.\n\nThe first games from a new creator are reviewed before they can appear in the public catalog. Their stable links still work while review is pending.\n\n## Update, unlist, or delete\n\nTo update a game, change its files without changing `id`, then run `npx @caisual/cli publish` again. This creates a new version and keeps the same stable game URL.\n\nTo remove a game from the catalog, set `visibility` to `unlisted` and publish, or change visibility from the dashboard. To delete a game, use the dashboard. Deletion is permanent and its ID cannot be reused.\n\n## Common errors\n\n- `CAISUAL_KEY is required`: export the creator\'s key in the same shell before publishing.\n- `The publish API key is not valid`: create a new key in the account dashboard if the old key expired or was revoked.\n- `caisual.json is not valid`: read every reported field and rule, fix all of them, then retry.\n- `client/index.html: file not found`: place `index.html` directly under `client/`, not in a nested build folder.\n- `referenced file not found`: make sure `cover` and every screenshot path match a file under `client/`, including letter case.\n- `file is larger than 50 MB`: compress, reduce, or split the asset and update its references.\n- `upload failed` or a temporary portal error: keep the files unchanged and retry the same publish command. The CLI retries temporary upload failures automatically.\n- An external request works locally but fails after publishing: add its host to `network` and publish a new version.\n- A threaded WebAssembly game fails to start: set `isolated` to `true` and verify that every declared external host supports cross-origin isolation.\n\nMultiplayer rooms arrive in a later kit release; the same folder and command will publish them.\n';
|
|
391
|
+
|
|
392
|
+
// ../../docs/kit.md
|
|
393
|
+
var kit_default = '# Caisual game kit\n\nThe kit gives a published game a stable player identity, cloud saves, leaderboards, and a daily challenge seed. It works in single-player games. Rooms for games played together are not part of this release.\n\nThe kit is a single JavaScript module with no dependencies. It never touches the page: the game keeps its own rendering, input, and UI.\n\n## Load the kit\n\nEvery published game can import the kit from its own origin, without a bundler and without an npm install:\n\n```html\n<script type="module">\n import { caisual } from \'/__caisual/kit/v1.js\';\n\n const c = await caisual.connect();\n console.log(c.player.name);\n</script>\n```\n\nGames built with a bundler can install the same module from npm:\n\n```sh\nnpm install @caisual/kit\n```\n\n```js\nimport { caisual } from \'@caisual/kit\';\n```\n\nBoth forms expose the same API. The module also sets `globalThis.caisual` for classic scripts that load it first.\n\nThe path `/__caisual/` is reserved on every game origin. Do not put game files under it.\n\n## Connect\n\n```js\nconst c = await caisual.connect();\n```\n\n`connect()` completes when the game is running inside caisual.com and has received its player identity, or after a short timeout when it is not. Calling it again returns the same promise.\n\n- `c.connected` is `true` inside caisual.com and `false` when the game runs on its own, for example from a local folder during development or when its files are copied elsewhere.\n- `c.player` is `{ id, name, guest }`. `id` is stable for the player across sessions and across every version of the game. `name` is the account username, or `"Guest"`. `guest` is `true` for players without an account. When a guest later signs in, saves and scores stay attached to the same `id`.\n- When not connected, `c.player` is `{ id: "local", name: "Guest", guest: true }`.\n\nDo not store the ticket or reimplement the handshake. The kit handles identity, renewal, and retries.\n\n## Daily challenge\n\n```js\nc.daily.day; // "2026-09-04", the current UTC day\nc.daily.seed; // unsigned 32-bit integer, identical for every player on that day\nconst r = c.daily.random(); // number in [0, 1), deterministic from the seed\n```\n\n`c.daily.random()` is a deterministic generator initialized from `c.daily.seed`. Every `connect()` starts the sequence from the beginning, so two players who call it the same number of times get the same values. Use it to build the level of the day.\n\n`c.time.now()` returns milliseconds aligned with the portal clock. Prefer it to `Date.now()` for anything that must agree with the current day.\n\nWhen not connected, `day` comes from the local clock and `seed` from the local hostname, so a game copied elsewhere still runs.\n\n## Saves\n\nEach player has up to 32 saves per game. A save is any JSON value up to 256 KB when serialized.\n\n```js\nawait c.save.set(\'slot1\', { level: 3, coins: 120 }); // -> { key, bytes, updatedAt }\nconst data = await c.save.get(\'slot1\'); // -> the value, or null\nawait c.save.remove(\'slot1\');\nconst saves = await c.save.list(); // -> [{ key, bytes, updatedAt }]\n```\n\n- Keys use 1 to 32 characters: lowercase letters, digits, `_` or `-`, starting with a letter or digit.\n- `updatedAt` is a millisecond timestamp.\n- Saves are per player and per game. Another game cannot read them.\n- When not connected, saves go to the browser\'s local storage on the game origin.\n\nErrors reject the promise with an `Error` whose `code` is one of `invalid_request`, `not_found`, `save_limit`, `payload_too_large`, `rate_limited`, `invalid_ticket`, `internal_error`, or `offline`.\n\n## Leaderboards\n\nA leaderboard is identified by a board id chosen by the game. Scores are non-negative integers and higher is better. Each player keeps one entry per board, and one per board per day for daily boards: the best score is kept.\n\n```js\nconst result = await c.board.submit(\'main\', 1234);\n// -> { accepted: true, best: 1234, rank: 7, day: null }\n\nconst daily = await c.board.submit(\'main\', 1234, { daily: true });\n// -> { accepted: true, best: 1234, rank: 7, day: "2026-09-04" }\n\nconst top = await c.board.top(\'main\', { daily: true, limit: 10 });\n// -> { day: "2026-09-04", entries: [{ rank, name, score, guest, me }], me: { rank, score } | null }\n```\n\n- Board ids use the same format as save keys.\n- `submit` never rejects because of connectivity. When the game is not connected it resolves `{ accepted: false, reason: "offline" }`.\n- `best` is the score kept for this player after the submission, which can be higher than the submitted one.\n- `rank` counts players with a strictly higher score. Ties are ordered by who reached the score first.\n- Accounts and guests are ranked separately. `top()` returns account players by default; pass `guests: true` to list guests instead. `me` always refers to the current player within their own category, even beyond `limit`.\n- `limit` is 1 to 100 and defaults to 10.\n- Scores submitted from the game are recorded as unverified. Verified scores come with rooms in a later release.\n\n## Limits\n\n- 120 requests per minute per player. Beyond that the kit rejects with `rate_limited`; wait and retry.\n- Saves: 32 keys per player per game, 256 KB per value.\n- Scores: safe integers from 0 upward.\n\n## Development\n\nOpen `client/index.html` from a local static server. The kit detects that it is not inside caisual.com and switches to the standalone mode described above: saves in local storage, `submit` returning `accepted: false`, empty leaderboards, and a local daily seed. The game logic does not need a different code path.\n\nAfter publishing with `npx @caisual/cli publish`, open the game from its caisual.com page: `c.connected` becomes `true` and every call goes to the portal.\n\n## Manifest\n\nNo manifest field is required for the kit. Keep `players` at `{ "min": 1, "max": 1 }` in this release. See [publish.md](./publish.md) for the manifest and the publishing steps.\n';
|
|
391
394
|
|
|
392
395
|
// src/caisual.ts
|
|
393
396
|
var DEFAULT_ORIGIN = "https://caisual.com";
|
|
@@ -418,7 +421,7 @@ var ApiError = class extends Error {
|
|
|
418
421
|
hints;
|
|
419
422
|
};
|
|
420
423
|
function help() {
|
|
421
|
-
return `Caisual ${"0.1.
|
|
424
|
+
return `Caisual ${"0.1.1"}
|
|
422
425
|
|
|
423
426
|
Usage:
|
|
424
427
|
caisual init [folder]
|
|
@@ -484,8 +487,16 @@ async function init(folderArgument) {
|
|
|
484
487
|
<title>Hello</title>
|
|
485
488
|
</head>
|
|
486
489
|
<body>
|
|
487
|
-
<main>Hello</main>
|
|
488
|
-
|
|
490
|
+
<main>Hello, <span id="player">player</span>. Today's seed is <span id="seed">?</span>.</main>
|
|
491
|
+
<script type="module">
|
|
492
|
+
// The Caisual kit: player identity, cloud saves, leaderboards, daily seed.
|
|
493
|
+
// API reference: https://caisual.com/kit.md
|
|
494
|
+
import { caisual } from '/__caisual/kit/v1.js';
|
|
495
|
+
|
|
496
|
+
const c = await caisual.connect();
|
|
497
|
+
document.getElementById('player').textContent = c.player.name;
|
|
498
|
+
document.getElementById('seed').textContent = String(c.daily.seed);
|
|
499
|
+
</script>
|
|
489
500
|
</body>
|
|
490
501
|
</html>
|
|
491
502
|
`;
|
|
@@ -828,10 +839,12 @@ async function installSkill() {
|
|
|
828
839
|
const skillPath = join(root, ".claude", "skills", "caisual", "SKILL.md");
|
|
829
840
|
const skill = `---
|
|
830
841
|
name: caisual
|
|
831
|
-
description: Create and publish a browser game on Caisual.
|
|
842
|
+
description: Create and publish a browser game on Caisual, with player identity, cloud saves, leaderboards and a daily challenge.
|
|
832
843
|
---
|
|
833
844
|
|
|
834
845
|
${publish_default.trim()}
|
|
846
|
+
|
|
847
|
+
${kit_default.trim()}
|
|
835
848
|
`;
|
|
836
849
|
await fs.mkdir(join(root, ".claude", "skills", "caisual"), { recursive: true });
|
|
837
850
|
let currentSkill = null;
|
|
@@ -849,7 +862,7 @@ ${publish_default.trim()}
|
|
|
849
862
|
if (error.code !== "ENOENT") throw error;
|
|
850
863
|
}
|
|
851
864
|
if (!/^## Caisual\s*$/m.test(agents)) {
|
|
852
|
-
const section = "## Caisual\nRead `.claude/skills/caisual/SKILL.md` before creating or publishing a Caisual game.\nUse the current
|
|
865
|
+
const section = "## Caisual\nRead `.claude/skills/caisual/SKILL.md` before creating or publishing a Caisual game.\nUse the current guides at https://caisual.com/publish.md and https://caisual.com/kit.md.\n";
|
|
853
866
|
const separator = agents === "" ? "" : agents.endsWith("\n\n") ? "" : agents.endsWith("\n") ? "\n" : "\n\n";
|
|
854
867
|
await fs.writeFile(agentsPath, `${agents}${separator}${section}`, "utf8");
|
|
855
868
|
}
|
|
@@ -863,7 +876,7 @@ async function run(argumentsList) {
|
|
|
863
876
|
return;
|
|
864
877
|
}
|
|
865
878
|
if (command === "--version" || command === "-V") {
|
|
866
|
-
process.stdout.write(`${"0.1.
|
|
879
|
+
process.stdout.write(`${"0.1.1"}
|
|
867
880
|
`);
|
|
868
881
|
return;
|
|
869
882
|
}
|