@domino-sdk/relay-cli 0.3.0 → 0.5.0

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/cli/project.mjs CHANGED
@@ -1,8 +1,23 @@
1
+ import { hostedAppSchema } from "@domino-sdk/relay-cli";
1
2
  import { readFile, writeFile, mkdir, access } from "node:fs/promises";
2
3
  import { dirname, join, resolve } from "node:path";
3
4
  import { z } from "zod";
4
5
  import { buildQuest } from "@domino-sdk/relay/build";
5
6
  import { runGit } from "./git.mjs";
7
+ import { defaultDiscovery, discoverEntries } from "./discovery.mjs";
8
+
9
+ const discoveryPattern = z
10
+ .string()
11
+ .min(1)
12
+ .refine(
13
+ (pattern) =>
14
+ !pattern.startsWith("/") &&
15
+ !pattern.startsWith("!") &&
16
+ !pattern.includes("\\") &&
17
+ !pattern.includes(":") &&
18
+ !pattern.split("/").includes(".."),
19
+ "Discovery patterns must be relative to relay.json, without parent traversal or negation.",
20
+ );
6
21
 
7
22
  export const projectSchema = z
8
23
  .object({
@@ -11,16 +26,23 @@ export const projectSchema = z
11
26
  organization: z.string().min(1),
12
27
  project: z.string().min(1),
13
28
  environment: z.enum(["test", "live"]).default("test"),
14
- app: z
29
+ discover: z
15
30
  .object({
16
- kind: z.literal("static"),
17
- directory: z.string().min(1),
18
- devScript: z.string().min(1),
19
- checkScript: z.string().min(1),
20
- buildScript: z.string().min(1),
31
+ quests: z.array(discoveryPattern).default([]),
32
+ questTypes: z.array(discoveryPattern).default([]),
33
+ provider: z
34
+ .enum([
35
+ "workers-ai",
36
+ "fixture-pass",
37
+ "fixture-fail",
38
+ "fixture-unclear",
39
+ "fixture-error",
40
+ ])
41
+ .default("workers-ai"),
21
42
  })
22
43
  .strict()
23
44
  .optional(),
45
+ app: hostedAppSchema.optional(),
24
46
  questTypes: z
25
47
  .array(
26
48
  z
@@ -35,10 +57,32 @@ export const projectSchema = z
35
57
  "fixture-unclear",
36
58
  "fixture-error",
37
59
  ])
38
- .default("workers-ai"),
60
+ .optional(),
61
+ })
62
+ .strict(),
63
+ )
64
+ .default([]),
65
+ leaderboards: z
66
+ .array(
67
+ z
68
+ .object({
69
+ entry: z.string().min(1),
70
+ exportName: z.string().optional(),
39
71
  })
40
72
  .strict(),
41
73
  )
74
+ .max(50)
75
+ .default([]),
76
+ referrals: z
77
+ .array(
78
+ z
79
+ .object({
80
+ entry: z.string().min(1),
81
+ exportName: z.string().optional(),
82
+ })
83
+ .strict(),
84
+ )
85
+ .max(50)
42
86
  .default([]),
43
87
  collections: z
44
88
  .array(
@@ -70,7 +114,7 @@ export const projectSchema = z
70
114
  "fixture-error",
71
115
  "workers-ai",
72
116
  ])
73
- .default("workers-ai"),
117
+ .optional(),
74
118
  })
75
119
  .strict(),
76
120
  )
@@ -84,7 +128,14 @@ export async function readJson(path) {
84
128
  }
85
129
 
86
130
  async function loadProject(path) {
87
- const config = projectSchema.parse(await readJson(path));
131
+ const raw = await readJson(path);
132
+ const config = projectSchema.parse(raw);
133
+ const scopeSources = Object.fromEntries(
134
+ ["apiUrl", "organization", "project", "environment"].map((key) => [
135
+ key,
136
+ raw[key] === undefined ? { kind: "default" } : { kind: "manifest", path },
137
+ ]),
138
+ );
88
139
  const dir = dirname(path);
89
140
  // Worktrees keep shared connection metadata in the common Git directory.
90
141
  const gitDir = await runGit(["rev-parse", "--git-common-dir"], {
@@ -102,11 +153,16 @@ async function loadProject(path) {
102
153
  .strict()
103
154
  .parse(await readJson(resolve(dir, gitDir, "domino.json")));
104
155
  Object.assign(config, local);
156
+ for (const key of Object.keys(local))
157
+ scopeSources[key] = {
158
+ kind: "checkout",
159
+ path: resolve(dir, gitDir, "domino.json"),
160
+ };
105
161
  } catch (error) {
106
162
  if (error.code !== "ENOENT") throw error;
107
163
  }
108
164
  }
109
- return { path, config };
165
+ return { path, config, scopeSources };
110
166
  }
111
167
 
112
168
  export async function findProject(path) {
@@ -124,9 +180,34 @@ export async function findProject(path) {
124
180
  }
125
181
  }
126
182
 
127
- export async function bundleProject(project) {
183
+ export async function bundleProject(project, { allowEmpty = false } = {}) {
184
+ const quests = await discoverEntries(project, "quests");
185
+ const questTypes = await discoverEntries(project, "questTypes");
186
+ if (quests.length > 50)
187
+ throw new Error("A project deployment supports at most 50 quests.");
188
+ const questEntries = new Set(
189
+ quests.map((q) =>
190
+ JSON.stringify([
191
+ resolve(dirname(project.path), q.entry),
192
+ q.exportName ?? "default",
193
+ ]),
194
+ ),
195
+ );
196
+ for (const type of questTypes) {
197
+ if (
198
+ questEntries.has(
199
+ JSON.stringify([
200
+ resolve(dirname(project.path), type.entry),
201
+ type.exportName ?? "default",
202
+ ]),
203
+ )
204
+ )
205
+ throw new Error(
206
+ `Entry is registered as both a quest and a quest type: ${type.entry}`,
207
+ );
208
+ }
128
209
  const releases = [];
129
- for (const quest of project.config.quests) {
210
+ for (const quest of quests) {
130
211
  const artifact = await buildQuest(
131
212
  resolve(dirname(project.path), quest.entry),
132
213
  { exportName: quest.exportName },
@@ -138,30 +219,68 @@ export async function bundleProject(project) {
138
219
  provider: quest.provider,
139
220
  });
140
221
  }
141
- const catalog = await bundleCatalog(project);
222
+ const types = [];
223
+ for (const type of questTypes) {
224
+ const artifact = await buildQuest(
225
+ resolve(dirname(project.path), type.entry),
226
+ { exportName: type.exportName },
227
+ );
228
+ types.push({ ...artifact, provider: type.provider });
229
+ }
230
+ const catalog = {
231
+ types,
232
+ collections: project.config.collections,
233
+ ...(project.config.supportedInteractions
234
+ ? { supportedInteractions: project.config.supportedInteractions }
235
+ : {}),
236
+ };
237
+ const leaderboards = await Promise.all(
238
+ project.config.leaderboards.map((board) =>
239
+ buildQuest(resolve(dirname(project.path), board.entry), {
240
+ exportName: board.exportName,
241
+ }),
242
+ ),
243
+ );
244
+ const referrals = await Promise.all(
245
+ project.config.referrals.map((entry) =>
246
+ buildQuest(resolve(dirname(project.path), entry.entry), {
247
+ exportName: entry.exportName,
248
+ }),
249
+ ),
250
+ );
142
251
  if (
252
+ !allowEmpty &&
253
+ !referrals.length &&
143
254
  !releases.length &&
255
+ !leaderboards.length &&
144
256
  !catalog.types.length &&
145
257
  !catalog.collections.length &&
146
258
  !catalog.supportedInteractions
147
259
  )
148
260
  throw new Error(
149
- "No resources configured. Add quests, questTypes, collections, or supportedInteractions to relay.json.",
261
+ "No resources configured. Add files matching discover in relay.json, or configure quests, questTypes, leaderboards, referrals, collections, or supportedInteractions.",
150
262
  );
151
- return { releases, ...catalog };
263
+ return { releases, leaderboards, referrals, ...catalog };
152
264
  }
153
265
 
154
266
  export async function initialize(directory, config) {
155
267
  const root = resolve(directory);
156
268
  const files = {
157
- "relay.json": JSON.stringify(projectSchema.parse(config), null, 2) + "\n",
269
+ "relay.json":
270
+ JSON.stringify(
271
+ projectSchema.parse({ discover: defaultDiscovery, ...config }),
272
+ null,
273
+ 2,
274
+ ) + "\n",
158
275
  "RELAY.md": `# Relay project
159
276
 
160
- Use Codex or Claude to author your quests with @domino-sdk/relay/authoring. Project scope and quest entries are in relay.json. Relay imposes no framework, source directory, package manager, or application build configuration. Keep your existing project setup. Add @domino-sdk/relay with your package manager, or link the local SDK while it is unpublished. The CLI is supplied by @domino-sdk/relay-cli, separately from the runtime SDK. Commit relay.json and RELAY.md; credentials live in your user config directory.
277
+ Use Codex or Claude to author your quests with @domino-sdk/relay/authoring. Project scope, discovery patterns, and optional quest entries are in relay.json. Relay imposes no framework, source directory, package manager, or application build configuration. Keep your existing project setup. Add @domino-sdk/relay with your package manager. The CLI is supplied by @domino-sdk/relay-cli, separately from the runtime SDK. Commit relay.json and RELAY.md; credentials live in your user config directory.
278
+
279
+ Before authoring a new quest, run domino reference authoring for the snippet discovery and installation workflow. The public catalog is https://snippets.domino.run/r/registry.json. Install project agent guidance with domino agents install so Codex and Claude can find this workflow automatically.
161
280
 
162
281
  Run domino build or domino deploy --dry-run to bundle the complete manifest locally. Run domino deploy --preview to inspect effective remote settings without publishing, and domino deploy to atomically publish quests, questTypes, collections, and supportedInteractions to test. For live, export domino deploy --dry-run --out deployment.json and review the project bundle in Console. Run domino quests or domino releases to inspect the server. Live publication requires Console.
163
282
 
164
- Author quests with @domino-sdk/relay/authoring. Each entry points to any authored module relative to relay.json and default-exports defineQuest, or specifies exportName. No dedicated quests directory is required. Example entry: {"entry":"src/community/welcome.ts","settings":{},"provider":"fixture-pass"}. Fixture providers simulate verification; use workers-ai only on an API with the AI binding configured. Settings are deployment defaults validated by the server at publication. Console custom values persist in Relay and take precedence on every deployment; no source edits are needed to retain them. Deploy output reports defaults, custom values, and effective values. Use Console to reset a setting to its deployment default. Removing or invalidating a custom setting blocks the whole batch. For a rename, add settingRenames: {"oldName":"newName"} to the quest entry to carry custom values forward. A dry run builds locally and does not preview remote custom values. Multiple quests deploy as one batch so prerequisites can refer to other quests in the batch.
283
+ Author quests with @domino-sdk/relay/authoring. The discover patterns in relay.json select default-exported quests and quest types. Init configures quests/**/*.quest.ts and quests/**/*.quest-type.ts. Add matching files without registering each one. Customize the patterns for other source layouts. Discovery never executes modules on the build host. Set discover.provider for local fixture verification; it defaults to workers-ai. Explicit entries override matching discovered default exports and can supply settings, provider, and settingRenames. Each entry points to any authored module relative to relay.json and default-exports defineQuest, or specifies exportName. No dedicated quests directory is required. Example entry: {"entry":"src/community/welcome.ts","settings":{},"provider":"fixture-pass"}. Fixture providers simulate verification; use workers-ai only on an API with the AI binding configured. Settings are deployment defaults validated by the server at publication. Console custom values persist in Relay and take precedence on every deployment; no source edits are needed to retain them. Deploy output reports defaults, custom values, and effective values. Use Console to reset a setting to its deployment default. Removing or invalidating a custom setting blocks the whole batch. For a rename, add settingRenames: {"oldName":"newName"} to the quest entry to carry custom values forward. A dry run builds locally and does not preview remote custom values. Multiple quests deploy as one batch so prerequisites can refer to other quests in the batch.
165
284
 
166
285
  References: @domino-sdk/relay/authoring provides declarations; @domino-sdk/relay/build bundles without executing author code on the host; domino --help lists commands. Set RELAY_MANAGEMENT_TOKEN for CI. Use --json for machine-readable output. Never put tokens in relay.json or source files.
167
286
  `,
@@ -183,24 +302,6 @@ References: @domino-sdk/relay/authoring provides declarations; @domino-sdk/relay
183
302
  return {
184
303
  directory: root,
185
304
  files: Object.keys(files),
186
- next: "Add @domino-sdk/relay to your existing package with your package manager, then configure quest entries in relay.json and run domino build.",
187
- };
188
- }
189
-
190
- export async function bundleCatalog(project) {
191
- const types = [];
192
- for (const type of project.config.questTypes) {
193
- const artifact = await buildQuest(
194
- resolve(dirname(project.path), type.entry),
195
- { exportName: type.exportName },
196
- );
197
- types.push({ ...artifact, provider: type.provider });
198
- }
199
- return {
200
- types,
201
- collections: project.config.collections,
202
- ...(project.config.supportedInteractions
203
- ? { supportedInteractions: project.config.supportedInteractions }
204
- : {}),
305
+ next: "Add @domino-sdk/relay, then add default-exported quests in quests/*.quest.ts or reusable types in quests/*.quest-type.ts and run domino build. Customize discover in relay.json for other layouts.",
205
306
  };
206
307
  }
package/cli/runtime.mjs CHANGED
@@ -8,11 +8,18 @@ export function action(handler) {
8
8
  const options = command.optsWithGlobals();
9
9
  const project = await findProject(options.config);
10
10
  const connection = {};
11
+ const scopeSources = {};
11
12
  for (const key of ["apiUrl", "organization", "project", "environment"]) {
12
13
  const env =
13
14
  key === "apiUrl" ? "RELAY_API_URL" : `RELAY_${key.toUpperCase()}`;
14
15
  connection[key] =
15
16
  options[key] ?? process.env[env] ?? project?.config[key];
17
+ scopeSources[key] =
18
+ options[key] !== undefined
19
+ ? { kind: "flag", name: key === "apiUrl" ? "--api-url" : `--${key}` }
20
+ : process.env[env] !== undefined
21
+ ? { kind: "environment", name: env }
22
+ : (project?.scopeSources[key] ?? { kind: "default" });
16
23
  }
17
24
  connection.apiUrl = apiUrl(connection.apiUrl ?? "https://relay.domino.run");
18
25
  if (
@@ -21,7 +28,7 @@ export function action(handler) {
21
28
  )
22
29
  throw new Error("Environment must be test or live.");
23
30
  const result = await handler(
24
- { options, project, connection },
31
+ { options, project, connection, scopeSources },
25
32
  ...command.processedArgs,
26
33
  );
27
34
  console.log(JSON.stringify(result, null, options.json ? undefined : 2));
@@ -1,29 +1,59 @@
1
1
  ---
2
2
  name: domino
3
- description: Set up a Domino project, integrate Domino into an existing app, author quests, or verify and stage a participant experience with the Domino CLI and SDK.
3
+ description: Set up a Domino project, integrate Domino into an existing app, find and adapt quest snippets, author quests, leaderboards, or referrals, supply external points, or verify and stage a participant experience with the Domino CLI and SDK.
4
4
  ---
5
5
 
6
6
  # Build with Domino
7
7
 
8
8
  Deliver the requested participant experience and evidence that its main action works. Domino owns verification, completion, points, and reward execution. The app owns its presentation and integration with participant identity.
9
9
 
10
+ ## Discover and confirm the campaign
11
+
12
+ Read existing project instructions and saved decisions first. If the owner already confirmed the relevant campaign, reuse that evidence; reopen only decisions affected by a material change. Choose the procedure for the app being built.
13
+
14
+ ### Existing apps: confirm the campaign
15
+
16
+ Preserve the app's current design, components, and UI conventions. Skip brand discovery, brand-name or website questions, visual samples, and brand approval unless the owner explicitly requests a redesign.
17
+
18
+ Ask only for missing campaign basics: goal, intended participants, desired actions, and reason to participate. If these are missing, ask a few short questions, end the turn, and wait for answers before setup or implementation. Use answers already supplied. Inspect the app to identify where the participant flow belongs, then propose the campaign and smallest integration that fits its existing UI. Resolve consequential rewards, timing, and eligibility gaps with the owner. Wait for explicit campaign confirmation before implementing, and save the confirmed brief and remaining open decisions in the app's established project document. Reuse prior confirmation when it covers the work. Continue with [existing apps](references/existing-app.md) for integration steps.
19
+
20
+ ### New apps: discover the brand and campaign
21
+
22
+ Before implementing a new app, establish the brand direction and campaign brief with the owner. A starter, initial prompt, or Console brief is input to discovery, not confirmation. The procedure below applies to new apps only.
23
+
24
+ 1. **Ask before researching or designing.** Check the supplied brief and prior owner answers for a brand name, official website or reference, campaign goal, intended participants, desired actions, and reason to participate. Ask for missing basics in a few short questions, then end the turn and wait for the owner's answers. For an empty or vague brief, ask both which brand this is for and what the campaign should achieve. The owner may confirm there is no website or established brand. A project name, repository name, starter, or sample quest is not evidence of brand identity or campaign intent. Create a new brand only when the owner explicitly requests it. If the basics are already supplied, summarize them for confirmation instead of asking the same questions again. Complete this intake before login, setup commands, research, or a visual proposal.
25
+ 2. **Research and prepare the proposal.** After intake, inspect the owner's website, project documents, and supplied assets, including rendered appearance and styles. Record source URLs or file paths. If a source is inaccessible or identity remains ambiguous, ask the owner for a reference and wait rather than guessing. Show a concise brand interpretation and a small visual sample using the proposed colors, type, and voice. Draft the campaign goal, success measure, intended participants, desired actions, participant motivation, and journey from the owner's answers. Include timing, rewards, eligibility, and constraints where relevant. Suggest improvements tied to the goal and check Domino capabilities before promising supported behavior.
26
+ 3. **Resolve consequential gaps.** Separate observed facts, recommendations, and assumptions. Ask only questions whose answers materially change the experience, a few at a time, with a recommended answer where possible. Use research and existing answers instead of giving the owner a questionnaire. Keep unconfirmed rewards, budgets, inventory, dates, and eligibility explicit as open decisions; do not turn guesses into participant promises.
27
+ 4. **Confirm and save.** Present the proposed brand direction and campaign brief together for the owner to confirm or correct. End the turn and wait for their explicit response before implementation; silence and your own confidence are not confirmation. After intake, you may inspect and connect the project, research, write the draft brief, and prepare the visual sample. Keep app implementation and campaign configuration until after confirmation. Save the agreed direction, evidence of the owner's confirmation, sources, and remaining open decisions in CAMPAIGN.md, or the existing app's established brief document. Preserve the original request and link to the brief if it lives elsewhere.
28
+
29
+ Discovery is complete when the owner has confirmed the brand direction and campaign intent, including the goal, participants, actions, and motivation. Optional details can remain open if they do not affect the first implementation; record what must wait for those decisions. On subsequent sessions, read the saved brief and continue without repeating onboarding.
30
+
10
31
  ## Establish the project
11
32
 
12
33
  Read the project's agent instructions and owner brief, such as CAMPAIGN.md, when present. Inspect package scripts and relay.json before selecting commands. Use the installed CLI through the project's package manager, such as `pnpm exec domino`; the examples below abbreviate this to `domino`.
13
34
 
14
35
  Run `domino doctor --json` to inspect setup without running application code or contacting the service. Missing setup produces exit 1 with diagnostic JSON on stdout. Invalid configuration uses the CLI's standard error JSON on stderr. Fix the reported prerequisite, then rerun the check.
15
36
 
16
- Use `domino login` for browser authorization when remote work is needed. The user approves the displayed code in Console. Credentials stay in the CLI credential store. Run `domino doctor --remote --json` to verify access and inspect the effective organization, project, API, and environment before remote operations. Flags and RELAY_* environment variables override relay.json.
37
+ Use `domino login` for browser authorization when remote work is needed. As soon as the CLI prints the login details, send the user the code first, then the clickable sign-in link, in the same message. Always preserve this order, even if the CLI prints the link first. Use the exact code and URL from the current login attempt:
38
+
39
+ > Code: `<code from CLI>`
40
+ >
41
+ > [Open Domino to sign in](<URL from CLI>) and enter this code to approve the CLI connection.
42
+
43
+ Keep the login process running while the user approves in their browser. The CLI polls for approval and saves the credential itself; never ask the user to paste credentials into chat. Wait for the CLI to confirm success before continuing. If the attempt expires, run `domino login` again and send the new code first, then the new link.
44
+
45
+ Run `domino doctor --remote --json` after dependency installation finishes to verify access before remote operations. Inspect `scope` and `scopeSources`: flags override `RELAY_*` environment variables, then the Git common directory's `domino.json`, then `relay.json`. Checkout writes the untracked local connection file; starter values such as `local` and `campaign` in the manifest may therefore be valid defaults. Credentials live separately in the user config directory. For scope or setup failures, read [troubleshooting](references/troubleshooting.md).
17
46
 
18
47
  Choose the reference that matches the task:
19
48
 
20
49
  - For a new Domino-hosted app, checkout, or hosted preview, read [hosted projects](references/hosted.md).
21
50
  - For integration into an existing codebase, read [existing apps](references/existing-app.md).
22
- - For quest definitions, reusable types, collections, or publication, read [authoring](references/authoring.md).
51
+ - Before authoring a quest, read [authoring](references/authoring.md#find-a-snippet) and check the live snippet catalog for a suitable starting point. The same reference covers reusable types, collections, leaderboards, external points, and publication.
52
+ - For standings, own rank, nearby participants, or pagination, read [participant API](references/participant-api.md#leaderboard-reads).
23
53
 
24
- Before designing referrals, rankings, milestones, or rewards for another participant, run `domino capabilities --json`. Read `domino capabilities <id> --json` for the support boundary. Treat an unlisted capability as unknown and inspect its contract before promising it.
54
+ Before designing referrals, rankings, milestones, or rewards for another participant, run `domino capabilities --json`. Read `domino capabilities <id> --json` for the support boundary. The output's `provenance` identifies a bundled CLI catalog, not a project-specific server probe. Treat an unlisted capability or a disagreement with the deployed API as unknown and inspect its contract before promising it. Check recurrence, daily limits, social verification, and prize fulfillment separately; points or leaderboards alone do not establish those mechanics.
25
55
 
26
- For browser integration and submission recovery, read [participant API](references/participant-api.md). For referral campaigns, read [referrals](references/referrals.md). For startup, staging, or version failures, read [troubleshooting](references/troubleshooting.md).
56
+ For browser integration and submission recovery, read [participant API](references/participant-api.md). For invitation links, qualification, inviter bonuses, or referral milestones, read [referrals](references/referrals.md). For startup, staging, or version failures, read [troubleshooting](references/troubleshooting.md).
27
57
 
28
58
  If no CLI is installed, install `@domino-sdk/relay-cli` and `@domino-sdk/relay` through the app's package manager. Starter archives pin the packages they were verified with; retain their lockfile and `.domino/release.json` when diagnosing compatibility.
29
59
 
@@ -31,4 +61,4 @@ If no CLI is installed, install `@domino-sdk/relay-cli` and `@domino-sdk/relay`
31
61
 
32
62
  Run the app's checks and exercise the requested participant action through the running app. Verify the resulting progress or completion state. Keep fixture-provider results distinct from real external verification.
33
63
 
34
- Return the working preview or local URL, what participant action you verified, and any remaining launch dependency. A successful build, installed skill, or healthy doctor result is not proof of participant behavior. Live publication requires human review in Console; hosted staging uses test data and real sign-in.
64
+ Return the working preview or local URL, what participant action you verified, and any remaining launch dependency. Report failed or blocked browser checks explicitly, including a missing browser executable. A successful build, installed skill, or healthy doctor result is not proof of participant behavior. Live publication requires human review in Console; hosted staging uses test data and real sign-in.
@@ -2,14 +2,121 @@
2
2
 
3
3
  Read the installed `@domino-sdk/relay/authoring` declarations and the project's existing quest modules. Define the requested behavior with the supported SDK helpers rather than inventing capabilities. Authored entries in relay.json resolve relative to that manifest and can select a named export with `exportName`.
4
4
 
5
+ The `discover.quests` and `discover.questTypes` arrays in relay.json select default-exported resource files. New projects use `quests/**/*.quest.ts` and `quests/**/*.quest-type.ts`. Add matching files without individual registration. Existing manifests without `discover` keep explicit-entry behavior. An explicit entry overrides the matching discovered default export; omitted providers inherit `discover.provider` for those files. Discovery does not execute authored modules on the build host, publish them, or archive omitted resources.
6
+
7
+ ## Find a snippet
8
+
9
+ Before writing a new quest, fetch the public catalog. It requires no authentication:
10
+
11
+ ```sh
12
+ curl -fsS https://snippets.domino.run/r/registry.json
13
+ ```
14
+
15
+ 1. Match the requested behavior against each item's `title`, `description`, and `categories`. Use the catalog's `name` to fetch `https://snippets.domino.run/r/<name>.json`; inspect its `docs` and `files` contents, targets, and prerequisites before installing. The live catalog is the source of available recipes, so newly published snippets need no skill update.
16
+ 2. If a recipe fits, install it from the project root containing `relay.json` and `package.json`. For example, when the catalog lists `photo-quest`:
17
+
18
+ ```sh
19
+ pnpm dlx shadcn@4.21.0 add https://snippets.domino.run/r/photo-quest.json
20
+ ```
21
+
22
+ Preserve existing customized files; adapt those in place when the recipe is already installed. Use the project's package manager equivalent when it is not pnpm.
23
+ 3. Read the installed `docs/snippets/<name>.md`. Adapt the source to the requested behavior, choose unique IDs for new quests, and configure settings, rewards, and verification prerequisites. Keep existing quest IDs stable and verification source out of the browser bundle.
24
+ 4. Confirm the source matches `discover` in `relay.json`. Move it to a matching directory for custom layouts, or add an explicit entry if discovery is disabled or unsupported by the installed CLI. Run the project's checks and `domino build --json`, then exercise the requested participant behavior using the verification workflow in the Domino skill. Installation only copies source; publication is a separate step below.
25
+
26
+ If no recipe fits or the catalog is unavailable, author with the installed SDK declarations and existing project examples. Report an unavailable catalog as unchecked, rather than claiming no matching snippet exists. Snippets are editable starting points; check compatibility with the project's installed SDK before changing package versions.
27
+
28
+ ## Publish the authored resources
29
+
5
30
  Source owns reusable quest definitions, collection slots, and deployment defaults. Console owns operator-created quest instances, their placement, and custom setting values. Keep identifiers stable. Registering a new reusable type version does not upgrade existing instances.
6
31
 
7
- Entry `settings` supplies deployment defaults. Remote custom values take precedence on subsequent deployments. A removed or incompatible setting can block the batch. Use `settingRenames` for a deliberate rename, or have the operator reset a custom value in Console. Changing source defaults does not clear overrides.
32
+ Entry `settings` supplies deployment defaults. Remote custom values take precedence on subsequent deployments. A removed or incompatible setting can block the batch. Use `settingRenames` for a deliberate rename, or have the operator reset the custom value in Console under **Quests**. Changing source defaults does not clear overrides.
8
33
 
9
- Use `domino build --json` or `domino deploy --dry-run --json` for offline bundling. These do not validate effective remote settings. Use `domino deploy --preview --json` to resolve and validate the remote batch without publishing. Check the selected scope before publication.
34
+ Use `domino build --json` or `domino deploy --dry-run --json` for offline bundling. To save the bundle, use `domino build --out .domino-build/deployment.json --json`; the CLI creates missing parent directories. These checks do not validate effective remote settings. Use `domino deploy --preview --json` to resolve and validate the remote batch without publishing. Check the selected scope before publication.
10
35
 
11
36
  Use `domino deploy --environment test --json` when test publication is part of the task. It publishes configured quests, types, collections, and supported interactions together. After a lost publication response, use the returned `domino deploy --resume <id>` recovery command. Starting another deployment may create another immutable release.
12
37
 
13
38
  For human live review, export `domino deploy --dry-run --out deployment.json` and present that bundle for Console approval. This publishes community behavior, not the participant application. Existing attempts retain their pinned releases and promised rewards.
14
39
 
15
40
  Fixture providers simulate verification. `workers-ai` requires a configured service binding. A fixture success demonstrates the flow but does not establish that a real external activity was verified.
41
+
42
+ ## Leaderboard definitions
43
+
44
+ Default-export `defineLeaderboard({ id, title, balance, score, access, group })` from `@domino-sdk/relay/authoring`. Add `{ "entry": "src/leaderboard.ts" }` to `leaderboards` in `relay.json`; optional `exportName` selects a named export. Deploy through the existing project bundle workflow. Definitions and standings previews appear in Console Releases before live publication.
45
+
46
+ ```ts
47
+ import { defineLeaderboard } from "@domino-sdk/relay/authoring";
48
+
49
+ export default defineLeaderboard({
50
+ id: "weekly",
51
+ title: "Weekly contributors",
52
+ balance: "community",
53
+ access: "participants",
54
+ score: {
55
+ kind: "earned",
56
+ window: { kind: "calendar", period: "week", timeZone: "Europe/Budapest" },
57
+ },
58
+ });
59
+ ```
60
+
61
+ Score defaults to `{ kind: "balance" }`. Use `{ kind: "earned", window, quests? }` or `{ kind: "net", window }` for period calculations. Windows are `all-time`, `fixed` with epoch-millisecond `start`/`end`, `rolling` with `days`, or `calendar` with `period: "day" | "week" | "month"` and an IANA `timeZone`. Only earned scores can filter quest IDs. Access defaults to `participants`; `public` allows anonymous reads through the configured project endpoint. A named `group` narrows eligibility.
62
+
63
+ Leaderboards and points remain project/environment-scoped. Custom scoring runs before points ingestion. Management keys with `points` permission can award, spend, adjust, reverse, or set totals through `management.points.update`. Every command requires an action ID and reason; exact retries return the original result. `set` requires the current `expectedTotal` and explicit `earned`/`spend` classification. Reversals reference an original award and correct its original period. Do not implement negative clamping, direct database writes, browser awards, shared organization balances, or automatic top-rank prizes.
64
+
65
+ ## External points
66
+
67
+ Run this on a trusted backend with a management key scoped to the project and environment. The member must already be a project participant. Quest awards to the board's balance count automatically; use this API for externally verified activity or corrections.
68
+
69
+ ```ts
70
+ import { createManagementClient } from "@domino-sdk/relay-cli";
71
+
72
+ const management = createManagementClient({
73
+ baseUrl: "https://relay.domino.run",
74
+ organization: "my-organization",
75
+ project: "community",
76
+ environment: "test",
77
+ getToken: async () => process.env.DOMINO_MANAGEMENT_KEY!,
78
+ });
79
+
80
+ await management.points.update({
81
+ kind: "award",
82
+ actionId: "external-activity-42",
83
+ member: "member_existing",
84
+ balance: "community",
85
+ amount: 100,
86
+ reason: "Verified external activity 42",
87
+ });
88
+ ```
89
+
90
+ Persist the action ID with the source event and reuse the same payload after an uncertain response. Amounts are safe whole-number integers. Backdating uses `effectiveAt` in epoch milliseconds; future dates fail. Deductions cannot make the balance negative or consume reserved points. Read operations such as `management.points.balance(member, balance)` and `history(member, balance)` require `read` permission.
91
+
92
+ For externally owned totals, dedicate a balance to that source. Read its current total before a `set` command; on an `expectedTotal` conflict, read again and decide whether to issue a new command with a new action ID. A set replaces the whole balance, including any quest awards written there.
93
+
94
+ ## Verify a leaderboard
95
+
96
+ Publish to test and read through the participant endpoint described in [participant API](https://console.domino.run/docs/reference/participant-api#leaderboard-reads). Award known points to existing test participants and verify the expected ordering, shared ties, signed-in `me`, and access after sign-out. Confirm that retrying one external event does not award twice. For a period board, check an entry inside and outside the window. Report the tested project, environment, board, and observed scores.
97
+
98
+ Treat top-rank prizes as a separate reward workflow with an explicit tie policy and finalization time. Board exclusion changes eligibility only; Console or management calls with `publish` permission handle exclusions and named group membership.
99
+
100
+ ## Referral programs
101
+
102
+ For `defineReferral`, invitation qualification, fixed rewards, and selective inviter bonuses, read [referrals](referrals.md). Register modules in the manifest's `referrals` array; they publish atomically with quests and leaderboards. Referral bonuses use the existing project points ledger.
103
+
104
+ ## Verify Discord membership
105
+
106
+ Use the `discord-membership` snippet for a server membership check, with an optional role requirement. An organization administrator first connects the project's server in Console **Integrations**. Declare `integrations: ["discord"]` on the quest and call `await ctx.discord.member()` inside `evaluate`. There is no server ID setting. The result is `null` for absent membership, or `{ roles, joinedAt, pending }`. Author code decides eligibility and the quest's declared rewards.
107
+
108
+ The platform supplies a read-only client bound to the project's configured server and the participant's Discord identity. Quest code cannot supply a user ID, server ID, access token, or arbitrary URL. Discord permission failures and outages fail verification; they are not evidence that the participant is ineligible. Bot access problems appear in Console Integrations. Participants signing in with Discord need no extra permission flow; members using another login link their Discord identity once.
109
+
110
+ Successful reads, including absent membership, are stored with the account revision, integration revision, server, and observation time. Recovery reuses captured facts. Changing either connection invalidates unfinished reads. After a failed attempt, start a new submission if a connection changed. Membership screening (`pending`) and role requirements remain author decisions.
111
+
112
+ The campaign starter includes participant identity controls. Existing apps use the same APIs through `createParticipantBackend`; see [participant API](participant-api.md#connected-accounts). The managed bot is configured by the platform, never by a quest author. Do not invent message, reaction, or write capabilities: `member` is the only Discord operation currently available. X remains unavailable.
113
+
114
+ The release's `provider` setting selects the photo-check backend used by `ctx.review.photo`; it does not select the evaluator for Discord, quizzes, or other authored checks. `humanReview` allows quest code to return a review decision. It does not automatically route Discord failures to staff. The Discord membership snippet returns accept or reject decisions and leaves provider outages as errors.
115
+
116
+ ## Entry after app-owned pass verification
117
+
118
+ Author the entry quest with `manual({ actor: "staff" })` and no evidence input. The participant app verifies its event pass on its server, resolves the signed-in member, and calls `POST /management/v1/passes/verify` with `{ provider, event, subject, member, quest }`. Use a server-only management key with `confirm` permission in the intended project and environment. Relay records unique pass ownership and submits the entry attempt; it does not decide whether the external ticket is valid. Repeating the same pass for the same member reuses the durable attempt. A different member cannot claim that pass, and one member cannot link a second pass for the same provider and event.
119
+
120
+ ## Hosted server applications
121
+
122
+ Quest discovery and private resource artifacts work the same way for static and Worker apps. Keep quest definitions outside the public asset directory. For server-rendered applications, configure the Worker module output and browser asset output separately in `relay.json`; see [hosted apps](hosted.md). With `app.routing="transparent"`, hosting reserves no paths. Omitting the setting preserves legacy routing. The starter owns its `/relay/*` proxy as ordinary application code; custom handlers can take precedence.
@@ -1,5 +1,7 @@
1
1
  # Existing apps
2
2
 
3
+ Before the first implementation, follow the Domino skill's existing-app campaign confirmation procedure. Ask for missing campaign basics, prepare the campaign and integration proposal, and obtain the owner's confirmation. Preserve the app's current design and components; brand discovery and visual approval are not prerequisites. Reuse an already confirmed brief in later sessions.
4
+
3
5
  Inspect the app's framework, package manager, workspace boundaries, authentication, and deployment scripts. Identify where the requested participant experience belongs. Keep its repository, UI conventions, login, and hosting.
4
6
 
5
7
  Use the Domino project selected by the user. If no remote project exists, create it in Console. `domino create` provisions a hosted repository and is not the existing-app integration path.
@@ -1,17 +1,29 @@
1
1
  # Hosted projects
2
2
 
3
+ Before the first implementation, follow the Domino skill's intake, discovery, and confirmation procedure. Ask for missing brand and campaign basics and wait for answers before research or design. Then prepare a concrete proposal and obtain the owner's confirmation. Reuse an already confirmed brief in later sessions.
4
+
3
5
  If Console already created the project and repository, run `domino checkout <project>`. Use `domino create <name>` only when the requested hosted project does not exist. Both require authentication. Use `domino whoami --json` to inspect accessible projects when the target is unclear.
4
6
 
5
7
  For a failure after remote project creation, inspect the existing project's state before repeating creation. Keep partially created local directories intact while recovering. Checkout accepts `--directory` for a fresh destination.
6
8
 
7
- Install dependencies from the lockfile. The starter includes pinned SDK, CLI, and local-runtime archives in `.domino`; upgrade them through a platform package release rather than modifying their contents. Read CAMPAIGN.md for the owner's brief.
9
+ Enter the checked-out directory before installing guidance or dependencies. Use checkout's `packageManager.installCommand`, which selects the exact version declared in package.json and preserves the lockfile. For `pnpm@10.34.5`, this is `npx --yes pnpm@10.34.5 install --frozen-lockfile`. Wait for installation to succeed before running doctor; a missing SDK during installation is not a new fault. If guidance is already installed, do not reinstall it as a remedy for checkout or authentication failures. The starter includes pinned SDK, CLI, and local-runtime archives in `.domino`; upgrade them through a platform package release rather than modifying their contents. Read CAMPAIGN.md for the owner's brief.
10
+
11
+ The starter uses Vite, React, TanStack Router file-based routes, and Tailwind CSS v4, with an app-owned Worker in `server/index.ts` for participant requests and browser assets. Its look is neutral on purpose (greyscale color tokens in `src/styles.css`, no logo or accent) and it ships sample quests and copy that introduce Domino; replace them with the owner's campaign. Its AGENTS.md maps the source and describes the `src/relay` hooks for session, quest state, submission, and recovery. Build the interface on those hooks and keep their recovery behavior.
12
+
13
+ The starter's relay.json declares discovery patterns for `quests/**/*.quest.ts` and `quests/**/*.quest-type.ts`. Add a matching default-exported file to include it in builds and development sync. Installed snippets use these paths. Use explicit entries only for named exports or per-resource overrides.
8
14
 
9
15
  Run `domino dev` to start the app with hosted test data and real sign-in. Share its HTTPS preview URL. The CLI prepares the tunnel connector automatically. The URL goes offline when development stops; use `domino stage` for a preview that stays online. Use `domino dev --offline --review-mode fixture-pass` only when you explicitly need an isolated runtime with simulated photo verification. This is not evidence that an external action happened.
10
16
 
11
17
  Use `domino check` and the app's build script to verify source. Exercise the requested participant flow in the running app before staging.
12
18
 
19
+ For leaderboards, register authored modules in the manifest's `leaderboards` list. `domino check` bundles them even when the project has no quests. `domino dev` synchronizes their definitions with the other project resources. Verify signed-in rank reads through the participant proxy; a successful build alone does not establish access or scoring behavior.
20
+
13
21
  When the task includes sharing a preview, commit the intended changes and push using the repository's configured remote. Run `domino stage --json` after the push. The command builds the exact pushed commit and returns its staging URL. Git pushes alone save source; they do not activate a preview or publish live.
14
22
 
15
23
  If stage submission loses its response, repeat the printed commit and request ID. Use `domino builds --json` and `domino logs <build-id> --json` to recover progress after disconnecting. A failed build leaves the previous preview active. Report the URL returned for the successful build rather than constructing one.
16
24
 
17
- Cloud staging supports the starter's static app contract. It does not host arbitrary SSR or customer Worker code. New staging builds use the same project test data as development and real participant sign-in. Existing previews created before this update remain simulated until rebuilt. Test data does not imply that external provider operations are simulated. Hosted app live activation remains a separate platform milestone.
25
+ Cloud staging supports static apps and compiled Cloudflare Worker apps, including SSR and server routes. Configure `app.kind="worker"`, `directory` for private modules, `main` relative to that directory, `compatibilityDate`, optional `compatibilityFlags`, and optional `assetsDirectory` for browser assets. Keep `devScript`, `checkScript`, and `buildScript`. TanStack Start with Cloudflare Vite typically uses `dist/server`, `index.js`, `dist/client`, and `nodejs_compat`. The dev server must honor `DOMINO_APP_PORT` and `DOMINO_PREVIEW_ORIGIN`. The build must also emit the starter's private `.domino-build` quest/catalog artifacts. Set `app.routing="transparent"` to preserve methods, cookies, and app authorization. Hosting reserves no URL paths and forwards requests and responses unchanged. The starter explicitly proxies `/relay/*` in its own server, used in both development and staging; apps can override or remove that handler. Use `proxyParticipant(request, { baseUrl, fetch, hosted: true })` to integrate Domino-managed sign-in explicitly. The API validates the registered app origin, session, and CSRF headers. `DOMINO_PARTICIPANT_API` supplies the selected upstream during connected development. `runWorkerFirst: true` lets your Worker handle even requests matching browser assets through its ASSETS binding. Non-secret string `vars` are supported; customer secrets and resource bindings are not provisioned by this contract. New staging builds use the same project test data as development and real participant sign-in. Omitted routing or `app.routing="legacy"` preserves existing gateway behavior, including implicit `/relay/` routes. To migrate, add the app-owned proxy, explicitly select transparent routing, restart and test the preview with the updated CLI, then commit and stage. Routing is recorded per build and preview session; old records and old CLI requests remain legacy. Test data does not imply that external provider operations are simulated. Hosted app live activation remains a separate platform milestone.
26
+
27
+ For referral programs, register authored modules in `referrals`. `domino check` and `domino dev` include them even without quests. Preserve invitation codes across the app's sign-in redirect and verify acceptance through the configured participant proxy before any quest completes. See [referrals](referrals.md).
28
+
29
+ Quests declare `integrations: ["discord"]`; Relay derives the participant identities required by each quest. The campaign starter renders account controls from the quest view’s `connections` field. The Discord membership snippet needs a project Discord integration configured in Console. Sign in with Discord (or link its identity) and check membership before staging; offline photo fixtures do not simulate Discord. See [connected accounts](participant-api.md#connected-accounts).