@seekprotocol/sdk 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/README.md CHANGED
@@ -22,9 +22,10 @@ export default quest({
22
22
  });
23
23
  ```
24
24
 
25
- A quest is data: the SDK describes it, and Seek's server runs it. Test and
26
- submit it with [`@seekprotocol/cli`](https://www.npmjs.com/package/@seekprotocol/cli).
25
+ Test and submit it with the `seek` command from
26
+ [`@seekprotocol/cli`](https://www.npmjs.com/package/@seekprotocol/cli).
27
27
 
28
- Seek Protocol: https://www.seekprotocol.ai/en
28
+ - Store Connect: https://storeconnect.seekprotocol.ai
29
+ - Seek Protocol: https://www.seekprotocol.ai/en
29
30
 
30
31
  Copyright (c) 2026 Seek Protocol. Licensed under the Seek Protocol SDK License, see `LICENSE.md`.
package/assets.d.ts CHANGED
@@ -1,50 +1,23 @@
1
- /**
2
- * What a minigame's files cost a phone: pixels in memory, seconds of sound,
3
- * and the bytes it loads before it can start.
4
- *
5
- * Megabytes say little about memory: a 5 MB PNG can be 8000 by 8000 pixels,
6
- * and once decoded that is 256 MB, which ends a WebView on an older phone
7
- * without a word. So images are measured by their pixels, read from the file
8
- * header without decoding anything, and sound by its length.
9
- *
10
- * Two levels, like the rest of Seek's checks: an `issue` means the game cannot
11
- * be submitted (`seek build`, Store Connect and the review all refuse it), an
12
- * `advice` is a tip that `seek test` prints and `seek optimize` can act on.
13
- * The same file runs in the CLI, the Workers and the Deno functions, so a
14
- * maker sees on their own laptop exactly what Seek will say.
15
- *
16
- * Plain JavaScript on bytes: no Node APIs, no DOM.
17
- */
18
- import { type GameIssue } from './games.js';
1
+ import { type GameIssue } from "./games.js";
19
2
  export declare const ASSET_LIMITS: {
20
- /** Longest side of an image. Above it the game is refused: phones cannot keep such a texture. */
21
3
  readonly imageMaxSide: 4096;
22
- /** Longest side `seek optimize` scales down to, and above which `seek test` advises it. */
23
4
  readonly imageAdviseSide: 2048;
24
- /** All images of a game decoded (width x height x 4 bytes), refused above this. */
25
5
  readonly decodedMaxBytes: number;
26
6
  readonly decodedAdviseBytes: number;
27
- /** What index.html loads before the game can start (its scripts, styles, images and their imports). */
28
7
  readonly startMaxBytes: number;
29
8
  readonly startAdviseBytes: number;
30
- /** A WAV file: uncompressed, so large ones are refused and any of size gets a tip. */
31
9
  readonly wavMaxBytes: number;
32
10
  readonly wavAdviseBytes: number;
33
- /** One sound, in seconds. */
34
11
  readonly audioMaxSeconds: 600;
35
- /** A PNG without transparency above this is probably a photo: JPEG or WebP is far smaller. */
36
12
  readonly opaquePngAdviseBytes: number;
37
- /** A GIF above this: animated GIFs are heavy, WebP or a sprite sheet is lighter. */
38
13
  readonly gifAdviseBytes: number;
39
14
  };
40
- /** The store cover of a quest: what Store Connect accepts, and what the app shows at 16:9. */
41
15
  export declare const COVER_SPEC: {
42
16
  readonly maxBytes: number;
43
17
  readonly minWidth: 1280;
44
18
  readonly minHeight: 720;
45
19
  readonly maxWidth: 3840;
46
20
  readonly maxHeight: 2160;
47
- /** Width divided by height, within 1%. */
48
21
  readonly ratio: number;
49
22
  readonly ratioTolerance: 0.01;
50
23
  readonly types: readonly string[];
@@ -52,19 +25,10 @@ export declare const COVER_SPEC: {
52
25
  export interface ImageSize {
53
26
  width: number;
54
27
  height: number;
55
- /** PNG only: whether the image has an alpha channel (colour type 4 or 6, or a tRNS chunk). */
56
28
  alpha?: boolean;
57
29
  }
58
- /** An image's pixel size from its header, or null when it cannot be read (SVG has none: it is drawn, not decoded). */
59
30
  export declare function imageSize(path: string, bytes: Uint8Array): ImageSize | null;
60
- /** How long a sound file plays, in seconds, from its headers; null when that cannot be told. */
61
31
  export declare function audioSeconds(path: string, bytes: Uint8Array): number | null;
62
- /**
63
- * The files index.html needs before the game can call ready(): what it names
64
- * in src and href (links excepted), what those stylesheets import and use, and
65
- * the static imports of its scripts, followed through. Dynamic import() and
66
- * fetch() are not followed: that is how a game loads the rest later.
67
- */
68
32
  export declare function startFiles(files: ReadonlyMap<string, Uint8Array>): string[];
69
33
  export interface GameAdvice {
70
34
  path: string;
@@ -85,24 +49,18 @@ export interface GameAssetReport {
85
49
  seconds: number | null;
86
50
  bytes: number;
87
51
  }[];
88
- /** All images decoded, in bytes. */
89
52
  decodedBytes: number;
90
- /** What index.html loads before ready(). */
91
53
  start: {
92
54
  files: string[];
93
55
  bytes: number;
94
56
  };
95
- /** The game cannot be submitted with these. */
96
57
  issues: GameIssue[];
97
- /** Tips: `seek test` prints them, `seek optimize` acts on most. */
98
58
  advice: GameAdvice[];
99
59
  }
100
- /** Everything this file knows about a game's files, as issues and advice. */
101
60
  export declare function analyzeGameAssets(input: Iterable<{
102
61
  path: string;
103
62
  bytes: Uint8Array;
104
63
  }> | ReadonlyMap<string, Uint8Array>): GameAssetReport;
105
- /** Why a cover cannot be used, or null. Checked by its bytes, never by its name. */
106
64
  export declare function coverProblem(bytes: Uint8Array): {
107
65
  code: string;
108
66
  message: string;
package/builders.d.ts CHANGED
@@ -1,25 +1,10 @@
1
- /**
2
- * The words a maker writes a quest in.
3
- *
4
- * import { quest, step, zone, reward, t } from '@seekprotocol/sdk';
5
- *
6
- * Every builder returns plain data. Nothing here runs later: `seek build`
7
- * calls `compile` on what the quest file exports, and the JSON that comes out
8
- * is what Seek reviews and runs.
9
- */
10
- import type { Area, ArenaStep, ArriveStep, CatchStep, ChoiceStep, Place, PlayStep, Point, QuestDefinition, Reward, TalkStep, Text, WalkStep } from './types.js';
11
- /** Player-facing text, in English. Seek translates it into the other 30 languages. */
1
+ import type { Area, ArenaStep, ArriveStep, CatchStep, ChoiceStep, Place, PlayStep, Point, QuestDefinition, Reward, TalkStep, Text, WalkStep } from "./types.js";
12
2
  export declare function t(en: string): Text;
13
- /** The quest itself. Returned unchanged; the type checks it as you write. */
14
3
  export declare function quest(definition: QuestDefinition): QuestDefinition;
15
4
  export declare const zone: {
16
- /** Playable anywhere a player is. */
17
5
  anywhere(): Area;
18
- /** A city or neighbourhood, as a centre and a radius. */
19
6
  circle(name: string, center: Point, radiusM: number): Area;
20
- /** A city, with its centre and a radius in kilometres (10 by default). */
21
7
  city(name: string, center: Point, radiusKm?: number): Area;
22
- /** A place a step points at: a landmark, a square, a park. */
23
8
  place(name: string, at: Point): Place;
24
9
  };
25
10
  type Common = {
@@ -68,7 +53,6 @@ export declare const reward: {
68
53
  card(card: string): Reward;
69
54
  cosmetic(item: string): Reward;
70
55
  boost(boost: string, count?: number): Reward;
71
- /** A coin pot. Needs the `rewards.crypto` capability, which only Seek grants. */
72
56
  pool(coin: string, perPlayer: number, maxPlayers: number): Reward;
73
57
  };
74
58
  export {};
package/compile.d.ts CHANGED
@@ -1,14 +1,5 @@
1
- /**
2
- * From a quest definition to the bundle Seek runs.
3
- *
4
- * Texts move into one table keyed by where they sit (`steps.bridge.title`),
5
- * which is what Seek translates; every `next` is resolved, so the server never
6
- * has to know the order steps were written in. The output is canonical JSON,
7
- * and its SHA-256 is the bundle's identity everywhere after this: in R2, in
8
- * the version row, in the pointer that makes it live.
9
- */
10
- import { type QuestBundle, type QuestDefinition } from './types.js';
11
- import { type Issue } from './validate.js';
1
+ import { type QuestBundle, type QuestDefinition } from "./types.js";
2
+ import { type Issue } from "./validate.js";
12
3
  export declare const VERSION_PATTERN: RegExp;
13
4
  export declare class QuestInvalidError extends Error {
14
5
  readonly issues: Issue[];
@@ -17,11 +8,8 @@ export declare class QuestInvalidError extends Error {
17
8
  export interface CompileOptions {
18
9
  version: string;
19
10
  build: number;
20
- /** GameKit: the hash of each `play` step's game, from `packageGame`. Written to `manifest.games`. */
21
11
  games?: Record<string, string>;
22
12
  }
23
13
  export declare function compile(q: QuestDefinition, opts: CompileOptions): QuestBundle;
24
- /** JSON with sorted keys and no whitespace: the same bundle always gives the same bytes. */
25
14
  export declare function canonicalJson(value: unknown): string;
26
- /** The bundle's identity: SHA-256 of its canonical JSON, in hex. */
27
15
  export declare function bundleHash(bundle: QuestBundle): Promise<string>;
package/games.d.ts CHANGED
@@ -1,12 +1,9 @@
1
- import { GAME_ID_PATTERN } from './types.js';
2
- import { type GameAssetReport } from './assets.js';
1
+ import { GAME_ID_PATTERN } from "./types.js";
2
+ import { type GameAssetReport } from "./assets.js";
3
3
  export { GAME_ID_PATTERN };
4
4
  export declare const GAME_FORMAT: 1;
5
- /** The id a game has on Seek's CDN and in launch tokens: `<gameId>-<16 hex of sha256(questId)>`. */
6
5
  export declare const GAME_APP_ID_PATTERN: RegExp;
7
- /** Where games are served from: `<appId>.play.seekprotocol.ai`. */
8
6
  export declare const GAME_HOST_SUFFIX = "play.seekprotocol.ai";
9
- /** The only origin a game may reach over the network. */
10
7
  export declare const GAME_PROXY_ORIGIN = "https://api.play.seekprotocol.ai";
11
8
  export declare const GAME_LIMITS: {
12
9
  readonly files: 200;
@@ -14,13 +11,10 @@ export declare const GAME_LIMITS: {
14
11
  readonly totalBytes: number;
15
12
  readonly pathLength: 120;
16
13
  readonly depth: 5;
17
- /** One path segment: no hidden files, no `..`, nothing a shell or a URL treats specially. */
18
14
  readonly segment: RegExp;
19
- /** Per player per game, through the proxy. */
20
15
  readonly storageBytes: number;
21
16
  };
22
- export type GameFileKind = 'html' | 'script' | 'style' | 'data' | 'image' | 'audio' | 'font';
23
- /** Every file type a game may carry, by extension. Anything else is refused. */
17
+ export type GameFileKind = "html" | "script" | "style" | "data" | "image" | "audio" | "font";
24
18
  export declare const GAME_FILE_TYPES: Record<string, {
25
19
  type: string;
26
20
  kind: GameFileKind;
@@ -30,19 +24,14 @@ export declare const GAME_ENTRY = "index.html";
30
24
  export interface GameFile {
31
25
  path: string;
32
26
  size: number;
33
- /** SHA-256 of the bytes, hex. */
34
27
  sha256: string;
35
- /** The Content-Type it is served with, from `GAME_FILE_TYPES`. */
36
28
  type: string;
37
29
  }
38
- /** What `seek build` makes of `games/<id>/`, and what its hash is taken over. */
39
30
  export interface GameManifest {
40
31
  format: typeof GAME_FORMAT;
41
32
  id: string;
42
33
  entry: typeof GAME_ENTRY;
43
- /** Sorted by path. */
44
34
  files: GameFile[];
45
- /** All files together, in bytes. */
46
35
  size: number;
47
36
  }
48
37
  export interface GameIssue {
@@ -56,44 +45,29 @@ export declare const gameFileType: (path: string) => {
56
45
  kind: GameFileKind;
57
46
  text: boolean;
58
47
  } | undefined;
59
- /** Why a path cannot be in a game, or null when it can. */
60
48
  export declare function gamePathProblem(path: string): GameIssue | null;
61
- /** Whether the bytes are what the extension claims: text is UTF-8 without NUL, binaries carry their signature, nothing is an executable. */
62
49
  export declare function sniffGameFile(path: string, bytes: Uint8Array): GameIssue | null;
63
- /** Everything about a manifest that can be checked without the bytes. */
64
50
  export declare function validateGameManifest(m: unknown): GameIssue[];
65
- /** The game's identity: SHA-256 of its manifest's canonical JSON, hex. */
66
51
  export declare const gameHash: (m: GameManifest) => Promise<string>;
67
- /** Whether one file's bytes are what the manifest says. */
68
52
  export declare function verifyGameFile(f: GameFile, bytes: Uint8Array): Promise<GameIssue | null>;
69
53
  export interface PackagedGame {
70
54
  manifest: GameManifest;
71
55
  hash: string;
72
56
  issues: GameIssue[];
73
- /** Pixels, sound and what loads before ready(): its issues are in `issues` too. */
74
57
  assets: GameAssetReport;
75
58
  }
76
- /** From the files of `games/<id>/` to a manifest and a hash. Issues mean the game cannot be submitted. */
77
59
  export declare function packageGame(id: string, files: {
78
60
  path: string;
79
61
  bytes: Uint8Array;
80
62
  }[]): Promise<PackagedGame>;
81
- /** The id a quest's game has on Seek's CDN: unique per quest, so two makers' `runner` never share a domain or a `sub`. */
82
63
  export declare function gameAppId(questId: string, gameId: string): Promise<string>;
83
64
  export declare const gameOrigin: (appId: string) => string;
84
65
  export interface GameFinding {
85
66
  path: string;
86
67
  line: number;
87
68
  code: string;
88
- /** `fail`: the version cannot pass. `flag`: a person looks at it. */
89
- severity: 'fail' | 'flag';
69
+ severity: "fail" | "flag";
90
70
  message: string;
91
71
  match: string;
92
72
  }
93
- /**
94
- * What a reviewer should know about one text file of a game. Remote URLs that
95
- * would load something fail (the CSP blocks them anyway, but a game that
96
- * relies on them is broken or trying something); remote URLs in plain text,
97
- * inline handlers and eval-like code are flagged for a person.
98
- */
99
73
  export declare function lintGameText(path: string, text: string, limit?: number): GameFinding[];
package/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- export * from './types.js';
2
- export * from './builders.js';
3
- export * from './validate.js';
4
- export * from './compile.js';
5
- export * from './sign.js';
6
- export * from './log.js';
7
- export * from './package.js';
8
- export * from './verify.js';
9
- export * from './games.js';
10
- export * from './assets.js';
1
+ export * from "./types.js";
2
+ export * from "./builders.js";
3
+ export * from "./validate.js";
4
+ export * from "./compile.js";
5
+ export * from "./sign.js";
6
+ export * from "./log.js";
7
+ export * from "./package.js";
8
+ export * from "./verify.js";
9
+ export * from "./games.js";
10
+ export * from "./assets.js";
package/log.d.ts CHANGED
@@ -1,17 +1,7 @@
1
- /**
2
- * The transparency log: every signature Seek ever made, in order.
3
- *
4
- * Each entry's hash covers the entry before it, so the newest hash commits to
5
- * the whole history: change or drop one entry and every hash after it differs.
6
- * Supabase writes the log (`platform__log_append`, which must build the same
7
- * string as `logEntryHash`); anyone holding the entries can check it here, and
8
- * compare the newest hash with one that was published earlier.
9
- */
10
1
  export declare const LOG_GENESIS: string;
11
2
  export interface LogEntry {
12
3
  seq: number;
13
- /** `signed` on approval, `resigned` when a key rotation replaced the signature. */
14
- event: 'signed' | 'resigned';
4
+ event: "signed" | "resigned";
15
5
  quest_id: string;
16
6
  version_id: string;
17
7
  hash: string;
@@ -27,10 +17,9 @@ export type LogCheck = {
27
17
  } | {
28
18
  ok: false;
29
19
  seq: number;
30
- reason: 'bad_sequence' | 'bad_link' | 'bad_hash';
20
+ reason: "bad_sequence" | "bad_link" | "bad_hash";
31
21
  };
32
- export declare function logEntryHash(e: Omit<LogEntry, 'entry_hash'>): Promise<string>;
33
- /** Walk entries from the start of the log, or on from `from` when continuing an earlier check. */
22
+ export declare function logEntryHash(e: Omit<LogEntry, "entry_hash">): Promise<string>;
34
23
  export declare function verifyLogChain(entries: LogEntry[], from?: {
35
24
  size: number;
36
25
  head: string;
package/package.d.ts CHANGED
@@ -1,24 +1,11 @@
1
- /**
2
- * The `.seek` file: one quest build, what an `.ipa` is on iOS.
3
- *
4
- * `seek build` makes it and signs it with the maker's key, because that key
5
- * never leaves the maker's machine. The maker uploads it with `seek push` or by
6
- * dropping it on Store Connect. Either way it is unpacked on the maker's side
7
- * and goes through the same API, which trusts none of it: Store Connect checks
8
- * the bundle, the signature and every game file as it always does.
9
- *
10
- * It is gzipped JSON, so nothing is ever extracted to a path, and a file that
11
- * inflates without end stops at `PACKAGE_LIMITS.unpackedBytes`.
12
- */
13
- import type { GameManifest } from './games.js';
14
- import type { Signature } from './sign.js';
15
- import type { QuestBundle } from './types.js';
1
+ import type { GameManifest } from "./games.js";
2
+ import type { Signature } from "./sign.js";
3
+ import type { QuestBundle } from "./types.js";
16
4
  export declare const PACKAGE_EXTENSION = ".seek";
17
5
  export declare const PACKAGE_LIMITS: {
18
6
  readonly fileBytes: number;
19
7
  readonly unpackedBytes: number;
20
8
  };
21
- /** One game as `POST /quests/:quest/games` takes it: `files` is path to base64. */
22
9
  export interface GameUpload {
23
10
  manifest: GameManifest;
24
11
  hash: string;
@@ -28,14 +15,11 @@ export interface QuestPackage {
28
15
  format: 1;
29
16
  bundle: QuestBundle;
30
17
  hash: string;
31
- /** Null when it was built without a signing key: the Simulator takes it, Store Connect does not. */
32
18
  signature: Signature | null;
33
- /** By game id, every game `bundle.manifest.games` names. */
34
19
  games: Record<string, GameUpload>;
35
20
  }
36
21
  export declare class PackageError extends Error {
37
22
  }
38
23
  export declare const packageFileName: (b: QuestBundle) => string;
39
- export declare function writePackage(p: Omit<QuestPackage, 'format'>): Promise<Uint8Array>;
40
- /** The contents of a `.seek` file, once it is whole and the bundle is the one its hash names. */
24
+ export declare function writePackage(p: Omit<QuestPackage, "format">): Promise<Uint8Array>;
41
25
  export declare function readPackage(bytes: Uint8Array): Promise<QuestPackage>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@seekprotocol/sdk",
3
- "version": "0.1.0",
4
- "description": "Build quests for Seek: the kits, the builders, the validator and the bundle compiler.",
3
+ "version": "0.1.1",
4
+ "description": "Write quests for the Seek app.",
5
5
  "author": {
6
6
  "name": "Seek Protocol",
7
7
  "url": "https://www.seekprotocol.ai/en"
@@ -11,7 +11,6 @@
11
11
  "seek",
12
12
  "seekprotocol",
13
13
  "quests",
14
- "location-based",
15
14
  "games"
16
15
  ],
17
16
  "license": "SEE LICENSE IN LICENSE.md",
package/sign.d.ts CHANGED
@@ -1,38 +1,17 @@
1
- /**
2
- * Signatures on a bundle, the way code signing works on iOS.
3
- *
4
- * The CLI signs the bundle hash with the maker's key on `seek submit`, so
5
- * Store Connect knows the upload came from that maker. After approval Seek
6
- * signs the same hash with its own key, and the app only shows a bundle that
7
- * carries Seek's signature. Ed25519 through WebCrypto, so the same code runs
8
- * in Node, in a Worker and in a Deno edge function.
9
- */
10
1
  export interface Signature {
11
- /** Who signed: the maker on submit, Seek on approval. */
12
- by: 'maker' | 'seek';
13
- /** Which key, so keys can be rotated. */
2
+ by: "maker" | "seek";
14
3
  keyId: string;
15
- /** Ed25519 over the UTF-8 bytes of `signedMessage(hash, by)`, base64url. */
16
4
  sig: string;
17
5
  }
18
- /**
19
- * What a signature covers: the purpose, the signer's role and the hash. A
20
- * signature made here can never pass for one on anything else, and a maker's
21
- * can never pass for Seek's.
22
- */
23
6
  export declare const signedMessage: (hash: string, by: Signature["by"]) => string;
24
- /** The public keys Seek's signatures are checked against, by `keyId`. A rotated key stays until nothing needs it. */
25
7
  export type Keyring = Record<string, JsonWebKey>;
26
8
  export interface KeyPairJwk {
27
9
  keyId: string;
28
10
  publicJwk: JsonWebKey;
29
11
  privateJwk: JsonWebKey;
30
12
  }
31
- /** A new signing key. `keyId` is the first 16 hex characters of the public key's SHA-256. */
32
13
  export declare function generateSigningKey(): Promise<KeyPairJwk>;
33
- /** The `keyId` of a key, from its public half: first 16 hex characters of the SHA-256. */
34
14
  export declare function keyIdOf(jwk: JsonWebKey): Promise<string>;
35
- export declare function signHash(hash: string, by: Signature['by'], keyId: string, privateJwk: JsonWebKey): Promise<Signature>;
15
+ export declare function signHash(hash: string, by: Signature["by"], keyId: string, privateJwk: JsonWebKey): Promise<Signature>;
36
16
  export declare function verifyHash(hash: string, signature: Signature, publicJwk: JsonWebKey): Promise<boolean>;
37
- /** True when a key of the ring, named by the signature itself, made this Seek signature. */
38
17
  export declare function verifySeekSignature(hash: string, signature: Signature, ring: Keyring): Promise<boolean>;
package/types.d.ts CHANGED
@@ -1,27 +1,6 @@
1
- /**
2
- * What a quest is, as a maker writes it and as Seek runs it.
3
- *
4
- * A maker describes a quest with the builders in `./builders`; `compile`
5
- * turns that description into a `QuestBundle`, which is plain JSON. The
6
- * bundle is the only thing that leaves the maker's machine, and Seek's
7
- * server is the only thing that runs it: no maker code is executed on a
8
- * phone or on Seek's side. What a step can ask for is exactly what this file
9
- * lists, and every one of those things is a fact the server can check
10
- * itself (an attested position, a catch, a verified step count, an arena win).
11
- */
12
- /** The bundle format. Bumped only for a change an older Seek cannot read. */
13
1
  export declare const BUNDLE_FORMAT: 1;
14
- /**
15
- * The quest runtime this SDK targets.
16
- *
17
- * The app reports which runtime it carries; a bundle that needs a newer one is
18
- * only offered to players whose app has it, the way an iOS app names its
19
- * minimum iOS version.
20
- */
21
2
  export declare const SDK_RUNTIME: 1;
22
- /** A minigame's id inside its quest: its folder `games/<id>/` and `step.play({ game })`. 3 to 30 characters. */
23
3
  export declare const GAME_ID_PATTERN: RegExp;
24
- /** A piece of player-facing text, written in English; Seek translates it. */
25
4
  export interface Text {
26
5
  en: string;
27
6
  }
@@ -29,178 +8,141 @@ export interface Point {
29
8
  lat: number;
30
9
  lng: number;
31
10
  }
32
- /**
33
- * Where the quest is played.
34
- *
35
- * Seek decides where each marker actually stands inside it (on a street a
36
- * player can walk to, never in a building or a no-go zone), so an area is a
37
- * playing field and not an address.
38
- */
39
11
  export type Area = {
40
- kind: 'anywhere';
12
+ kind: "anywhere";
41
13
  } | {
42
- kind: 'circle';
14
+ kind: "circle";
43
15
  name: string;
44
16
  center: Point;
45
17
  radiusM: number;
46
18
  };
47
- /** A place a step points at. Seek moves the marker to the nearest safe, walkable spot. */
48
19
  export interface Place {
49
20
  name: string;
50
21
  at: Point;
51
22
  }
52
- /** Everything a bundle may be granted. Each is approved by Seek on its own. */
53
- export declare const CAPABILITIES: readonly ["location.markers", "spawns.place", "catch", "steps", "arena", "rewards.cards", "rewards.cosmetics", "rewards.boosts", "rewards.crypto", "gamekit"];
23
+ export declare const CAPABILITIES: readonly [
24
+ "location.markers",
25
+ "spawns.place",
26
+ "catch",
27
+ "steps",
28
+ "arena",
29
+ "rewards.cards",
30
+ "rewards.cosmetics",
31
+ "rewards.boosts",
32
+ "rewards.crypto",
33
+ "gamekit"
34
+ ];
54
35
  export type Capability = (typeof CAPABILITIES)[number];
55
36
  interface StepBase {
56
- /** Stable across versions: a player's progress names it. */
57
37
  id: string;
58
- /** One line in the tracker: what to do now. */
59
38
  title: Text;
60
- /** The longer telling, shown when the step opens. */
61
39
  story?: Text;
62
- /** The step that follows. Defaults to the next one in the list; `null` ends the quest. */
63
40
  next?: string | null;
64
41
  }
65
- /** Reach the quest-giver and hear them out. */
66
42
  export interface TalkStep extends StepBase {
67
- kind: 'talk';
68
- /** A character from Seek's catalogue, or one the maker had approved. */
43
+ kind: "talk";
69
44
  giver: string;
70
- /** Where the giver stands. Defaults to the centre of the area. */
71
45
  at?: Place;
72
46
  }
73
- /** Reach a place. */
74
47
  export interface ArriveStep extends StepBase {
75
- kind: 'arrive';
48
+ kind: "arrive";
76
49
  at: Place;
77
- /** How close counts as there, in metres. */
78
50
  radiusM: number;
79
51
  }
80
- /** Catch coins. */
81
52
  export interface CatchStep extends StepBase {
82
- kind: 'catch';
83
- /** `memecoins.key`, or `any` for any coin. */
53
+ kind: "catch";
84
54
  coin: string;
85
55
  count: number;
86
- /** Only catches near this place count, and Seek may put coins there. */
87
56
  near?: Place;
88
57
  withinM?: number;
89
58
  }
90
- /** Walk. */
91
59
  export interface WalkStep extends StepBase {
92
- kind: 'walk';
60
+ kind: "walk";
93
61
  steps: number;
94
- /** Only steps Seek verified (walked while hunting) count. */
95
62
  verified: boolean;
96
63
  }
97
- /** Win arena matches. */
98
64
  export interface ArenaStep extends StepBase {
99
- kind: 'arena';
65
+ kind: "arena";
100
66
  wins: number;
101
67
  }
102
- /** The player chooses; each option leads to its own step. */
103
68
  export interface ChoiceStep extends StepBase {
104
- kind: 'choice';
69
+ kind: "choice";
105
70
  options: {
106
71
  id: string;
107
72
  label: Text;
108
73
  next: string | null;
109
74
  }[];
110
75
  }
111
- /** Play an HTML5 minigame. The score is advisory; Seek checks it against the reviewed envelope. */
112
76
  export interface PlayStep extends StepBase {
113
- kind: 'play';
114
- /** The minigame's folder in the quest project, `games/<game>/`. */
77
+ kind: "play";
115
78
  game: string;
116
- /** The score that completes the step. */
117
79
  minScore: number;
118
- /** The highest score a real run can reach, fixed at review. */
119
80
  maxScore: number;
120
81
  }
121
82
  export type Step = TalkStep | ArriveStep | CatchStep | WalkStep | ArenaStep | ChoiceStep | PlayStep;
122
- export type StepKind = Step['kind'];
123
- /**
124
- * What finishing the quest is worth.
125
- *
126
- * The maker picks from Seek's catalogue; Seek funds it, caps it and pays it.
127
- * The numbers here are what the bundle asks for, and the server decides what
128
- * is actually granted.
129
- */
83
+ export type StepKind = Step["kind"];
130
84
  export type Reward = {
131
- kind: 'xp';
85
+ kind: "xp";
132
86
  amount: number;
133
87
  } | {
134
- kind: 'card';
88
+ kind: "card";
135
89
  card: string;
136
90
  } | {
137
- kind: 'cosmetic';
91
+ kind: "cosmetic";
138
92
  item: string;
139
93
  } | {
140
- kind: 'boost';
94
+ kind: "boost";
141
95
  boost: string;
142
96
  count: number;
143
97
  } | {
144
- kind: 'pool';
98
+ kind: "pool";
145
99
  coin: string;
146
100
  perPlayer: number;
147
101
  maxPlayers: number;
148
102
  };
149
- /**
150
- * What the maker declares about the quest, as stores ask for a content rating
151
- * and a privacy label. Seek shows it to players and reviewers, checks what it
152
- * can against the steps, and a wrong declaration is a reason to reject.
153
- */
154
103
  export interface Declarations {
155
- /** Where players are sent: public places only, or also private ground the maker has permission for. */
156
- places: 'public' | 'private_with_permission';
157
- /** How hard it is on the body, all steps together. */
158
- effort: 'light' | 'moderate' | 'hard';
159
- /** Whether a step takes players near traffic, water, heights or other dangers they must watch for. */
104
+ places: "public" | "private_with_permission";
105
+ effort: "light" | "moderate" | "hard";
160
106
  hazards: boolean;
161
- /** Whether AI wrote texts or made art or code in the quest. */
162
- ai: 'none' | 'assisted' | 'generated';
163
- /** Whether a brand paid for the quest or appears in it. Players are told. */
107
+ ai: "none" | "assisted" | "generated";
164
108
  sponsored: boolean;
165
- /** Whether the quest's minigames record gameplay events (GameKit `track`). */
166
109
  gameEvents: boolean;
167
- /**
168
- * Required, and always true: every text, image, sound, font, character,
169
- * logo and line of code in the quest and its games is yours, or you have a
170
- * licence for this use. A build that says otherwise is not accepted.
171
- */
172
110
  rights: true;
173
111
  }
174
112
  export declare const DECLARATION_VALUES: {
175
- readonly places: readonly ["public", "private_with_permission"];
176
- readonly effort: readonly ["light", "moderate", "hard"];
177
- readonly ai: readonly ["none", "assisted", "generated"];
113
+ readonly places: readonly [
114
+ "public",
115
+ "private_with_permission"
116
+ ];
117
+ readonly effort: readonly [
118
+ "light",
119
+ "moderate",
120
+ "hard"
121
+ ];
122
+ readonly ai: readonly [
123
+ "none",
124
+ "assisted",
125
+ "generated"
126
+ ];
178
127
  };
179
128
  export interface QuestDefinition {
180
- /** Unique per maker, lowercase: `[a-z0-9-]`, 3 to 40 long. */
181
129
  id: string;
182
130
  title: Text;
183
131
  summary?: Text;
184
132
  area: Area;
185
- /** When the quest can be started and finished, ISO 8601. */
186
133
  window?: {
187
134
  from: string;
188
135
  to: string;
189
136
  };
190
- /** Whether a finished quest can be played again. */
191
- repeat?: 'never' | 'daily' | 'weekly';
137
+ repeat?: "never" | "daily" | "weekly";
192
138
  capabilities: Capability[];
193
139
  steps: Step[];
194
140
  rewards: Reward[];
195
- /** The youngest player who may see it. Anything that pays is 18. */
196
141
  minAge?: 13 | 16 | 18;
197
- /** Required: what the quest asks of players and what it contains. */
198
142
  declarations: Declarations;
199
143
  }
200
- /** The text table of a bundle: key to English. Seek fills in the other 30 languages. */
201
144
  export type TextTable = Record<string, string>;
202
- /** A step as it is stored in a bundle: texts replaced by keys, `next` resolved. */
203
- type CompileOne<S extends Step> = Omit<S, 'title' | 'story' | 'next' | 'options'> & {
145
+ type CompileOne<S extends Step> = Omit<S, "title" | "story" | "next" | "options"> & {
204
146
  title: string;
205
147
  story?: string;
206
148
  next: string | null;
@@ -218,12 +160,9 @@ export type CompiledStep = {
218
160
  }[StepKind];
219
161
  export interface QuestManifest {
220
162
  format: typeof BUNDLE_FORMAT;
221
- /** The SDK runtime the app must carry to show this bundle. */
222
163
  runtime: number;
223
164
  id: string;
224
- /** Semantic version, raised by the maker for every submission. */
225
165
  version: string;
226
- /** Strictly increasing per quest, like an iOS build number. */
227
166
  build: number;
228
167
  title: string;
229
168
  summary?: string;
@@ -232,17 +171,14 @@ export interface QuestManifest {
232
171
  from: string;
233
172
  to: string;
234
173
  };
235
- repeat: 'never' | 'daily' | 'weekly';
174
+ repeat: "never" | "daily" | "weekly";
236
175
  minAge: 13 | 16 | 18;
237
176
  declarations: Declarations;
238
177
  capabilities: Capability[];
239
178
  rewards: Reward[];
240
- /** The step a new player starts on. */
241
179
  start: string;
242
- /** GameKit: each `play` step's game, by id, to its game hash. Absent when the quest has no games. */
243
180
  games?: Record<string, string>;
244
181
  }
245
- /** What `seek build` writes and `seek submit` uploads. Plain JSON. */
246
182
  export interface QuestBundle {
247
183
  manifest: QuestManifest;
248
184
  steps: CompiledStep[];
package/validate.d.ts CHANGED
@@ -1,14 +1,4 @@
1
- /**
2
- * Whether a quest is one Seek can run.
3
- *
4
- * The same checks run in three places: `seek test` on the maker's machine,
5
- * `seek submit` before the upload, and Store Connect's review pipeline on what
6
- * arrived. A maker sees the answer the reviewer will see, before submitting.
7
- *
8
- * The limits are Seek's, not the maker's: they keep a quest walkable, finite
9
- * and inside what the reward pipeline is built to pay.
10
- */
11
- import { type Capability, type Point, type QuestDefinition, type Step } from './types.js';
1
+ import { type Capability, type Point, type QuestDefinition, type Step } from "./types.js";
12
2
  export declare const LIMITS: {
13
3
  readonly steps: {
14
4
  readonly min: 1;
@@ -61,15 +51,11 @@ export declare const LIMITS: {
61
51
  };
62
52
  };
63
53
  export interface Issue {
64
- /** Where, as a path into the definition: `steps[2].radiusM`. */
65
54
  path: string;
66
55
  code: string;
67
56
  message: string;
68
57
  }
69
- /** Metres between two points, on a sphere. Good to well under a metre at these distances. */
70
58
  export declare function distanceM(a: Point, b: Point): number;
71
- /** The capabilities a definition actually uses, whether or not it declares them. */
72
59
  export declare function usedCapabilities(q: QuestDefinition): Set<Capability>;
73
- /** Where each step leads: its own `next`, its options, or the one after it. */
74
60
  export declare function successors(steps: readonly Step[], index: number): (string | null)[];
75
61
  export declare function validate(q: QuestDefinition): Issue[];
package/verify.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { QuestBundle, QuestDefinition } from './types.js';
2
- import type { Issue } from './validate.js';
1
+ import type { QuestBundle, QuestDefinition } from "./types.js";
2
+ import type { Issue } from "./validate.js";
3
3
  export declare function decompile(bundle: QuestBundle): QuestDefinition;
4
4
  export type BundleCheck = {
5
5
  ok: true;