@domino-sdk/relay-cli 0.6.0 → 0.8.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,3 +1,7 @@
1
+ import {
2
+ pointLedgerDeclarationSchema,
3
+ collectionSlotDeclarationSchema,
4
+ } from "@domino-sdk/relay";
1
5
  import { hostedAppSchema } from "@domino-sdk/relay-cli";
2
6
  import { readFile, writeFile, mkdir, access } from "node:fs/promises";
3
7
  import { dirname, join, resolve } from "node:path";
@@ -84,16 +88,8 @@ export const projectSchema = z
84
88
  )
85
89
  .max(50)
86
90
  .default([]),
87
- collections: z
88
- .array(
89
- z
90
- .object({
91
- id: z.string().regex(/^[\w-]+$/),
92
- title: z.string().min(1).max(120),
93
- })
94
- .strict(),
95
- )
96
- .default([]),
91
+ pointLedgers: z.array(pointLedgerDeclarationSchema).max(50).default([]),
92
+ collections: z.array(collectionSlotDeclarationSchema).default([]),
97
93
  supportedInteractions: z
98
94
  .array(z.enum(["photo", "quiz", "claim", "staff", "automatic"]))
99
95
  .min(1)
@@ -129,7 +125,28 @@ export async function readJson(path) {
129
125
 
130
126
  async function loadProject(path) {
131
127
  const raw = await readJson(path);
132
- const config = projectSchema.parse(raw);
128
+ const parsed = projectSchema.safeParse(raw);
129
+ if (!parsed.success) {
130
+ const details = parsed.error.issues.map((issue) => ({
131
+ code:
132
+ issue.path[0] === "collections"
133
+ ? "COLLECTION_DECLARATION_INVALID"
134
+ : "MANIFEST_INVALID",
135
+ severity: "error",
136
+ file: path,
137
+ path: issue.path,
138
+ message: issue.message,
139
+ suggestion:
140
+ issue.path[0] === "collections"
141
+ ? "Run domino migrate to inspect collection keys. Keep the original id value as key."
142
+ : "Correct this field in relay.json, then rerun domino check --json.",
143
+ }));
144
+ throw Object.assign(new Error(`Invalid project manifest: ${path}`), {
145
+ code: "MANIFEST_INVALID",
146
+ details,
147
+ });
148
+ }
149
+ const config = parsed.data;
133
150
  const scopeSources = Object.fromEntries(
134
151
  ["apiUrl", "organization", "project", "environment"].map((key) => [
135
152
  key,
@@ -165,13 +182,14 @@ async function loadProject(path) {
165
182
  return { path, config, scopeSources };
166
183
  }
167
184
 
168
- export async function findProject(path) {
169
- if (path) return loadProject(resolve(path));
185
+ export async function findProjectFile(path) {
186
+ if (path) return resolve(path);
170
187
  let dir = process.cwd();
171
188
  while (true) {
172
189
  const path = join(dir, "relay.json");
173
190
  try {
174
- return await loadProject(path);
191
+ await access(path);
192
+ return path;
175
193
  } catch (error) {
176
194
  if (error.code !== "ENOENT") throw error;
177
195
  }
@@ -179,6 +197,10 @@ export async function findProject(path) {
179
197
  dir = dirname(dir);
180
198
  }
181
199
  }
200
+ export async function findProject(path) {
201
+ const file = await findProjectFile(path);
202
+ return file ? loadProject(file) : null;
203
+ }
182
204
 
183
205
  export async function bundleProject(project, { allowEmpty = false } = {}) {
184
206
  const quests = await discoverEntries(project, "quests");
@@ -228,6 +250,7 @@ export async function bundleProject(project, { allowEmpty = false } = {}) {
228
250
  types.push({ ...artifact, provider: type.provider });
229
251
  }
230
252
  const catalog = {
253
+ pointLedgers: project.config.pointLedgers,
231
254
  types,
232
255
  collections: project.config.collections,
233
256
  ...(project.config.supportedInteractions
@@ -250,6 +273,7 @@ export async function bundleProject(project, { allowEmpty = false } = {}) {
250
273
  );
251
274
  if (
252
275
  !allowEmpty &&
276
+ !catalog.pointLedgers.length &&
253
277
  !referrals.length &&
254
278
  !releases.length &&
255
279
  !leaderboards.length &&
@@ -258,7 +282,7 @@ export async function bundleProject(project, { allowEmpty = false } = {}) {
258
282
  !catalog.supportedInteractions
259
283
  )
260
284
  throw new Error(
261
- "No resources configured. Add files matching discover in relay.json, or configure quests, questTypes, leaderboards, referrals, collections, or supportedInteractions.",
285
+ "No resources configured. Add files matching discover in relay.json, or configure quests, questTypes, pointLedgers, leaderboards, referrals, collections, or supportedInteractions.",
262
286
  );
263
287
  return { releases, leaderboards, referrals, ...catalog };
264
288
  }
@@ -20,11 +20,55 @@ curl -fsS https://snippets.domino.run/r/registry.json
20
20
  ```
21
21
 
22
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.
23
+ 3. Read the installed `docs/snippets/<name>.md`. Adapt the source to the requested behavior, choose unique authoring keys for new quests, and configure settings, rewards, and verification prerequisites. Keep existing authoring keys stable and verification source out of the browser bundle.
24
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
25
 
26
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
27
 
28
+ ## Upgrade existing authoring code
29
+
30
+ Existing `id` properties on authoring declarations still work as aliases for `key`. They name source declarations; they do not choose server resource IDs. Keep the original value when migrating. Providing different `id` and `key` values fails. New code should use `key`.
31
+
32
+ Run these commands from the project directory after upgrading the SDK and CLI:
33
+
34
+ ```sh
35
+ domino migrate
36
+ domino migrate --write
37
+ domino check --json
38
+ ```
39
+
40
+ `migrate` previews a diff without writing files or contacting Domino. `--write` applies the changes. It renames authoring identity properties and collection declarations, and proposes `pointLedgers` entries for statically known ledger keys. Existing server IDs are not renamed. Review proposed ledger names before deploying. You may keep referencing a ledger that already exists remotely without adding a local declaration.
41
+
42
+ The migration follows configured and discovered entry modules and their local imports. It recognizes SDK import aliases and namespace imports, preserves nested tier and rule IDs, and does not execute source. Indirect objects, spreads, and dynamic ledger references receive manual instructions. It does not rewrite dependencies, unconfigured source, or files outside the project directory. Resolve conflicts before applying; a conflicting identity prevents all writes. Repeating the command after a completed migration produces no further diff.
43
+
44
+ `domino check --json` includes a `diagnostics` array with `code`, `severity`, `file`, `line`, `column`, `message`, and `suggestion` for source findings. `AUTHORING_ID_DEPRECATED` is a non-blocking migration warning. `AUTHORING_IDENTITY_CONFLICT` and `AUTHORING_KEY_MISSING` fail the check. Dynamic references and missing local ledger declarations are warnings because the selected ledger may already exist remotely. Manifest validation errors appear on stderr with `code: "MANIFEST_INVALID"` and field paths in `details`. Agents should follow the reported suggestion and rerun the check; never invent a replacement key for an existing resource.
45
+
46
+ ## Resource IDs and authoring keys
47
+
48
+ Use the IDs returned by Domino when referencing projects, quests, point ledgers, rewards, and inventory items. Creation requests do not accept a new resource's ID. The server assigns an opaque, prefixed ID. Existing stored IDs remain valid after upgrade.
49
+
50
+ In source, give `defineQuest`, `defineQuestType`, `defineTieredReward`, `defineLeaderboard`, and `defineReferral` a stable `key`. Collection declarations in `relay.json` also use `key`. Domino maps each key to a resource ID within the project and environment. Redeploying the same key updates the same resource; changing the key creates another resource. Display titles are independent of identity. Quest prerequisites can reference authoring keys, and participant quest and collection lookups accept keys or returned IDs.
51
+
52
+ Create point ledgers in Console **Rewards → Point ledgers → New ledger**, with `management.points.createLedger({ name, description })`, or declare them in `relay.json`:
53
+
54
+ ```json
55
+ {
56
+ "pointLedgers": [
57
+ { "key": "community", "name": "XP", "description": "Lifetime progress" }
58
+ ]
59
+ }
60
+ ```
61
+
62
+ Merge this property into the existing manifest. Then use `points("community", 100)` or the returned ledger ID. A deployment previews ledger declarations with the other resources and publishes them together. An unknown ledger reference fails validation instead of creating a balance. `management.points.ledgers()` lists the available ledgers.
63
+
64
+ For a selectable ledger, use `settings.resource({ label: "Point ledger", source: pointLedgers() })`. Import `pointLedgers` from `@domino-sdk/relay/authoring`. An empty selection keeps a quest inactive until an operator chooses a ledger.
65
+
66
+ The management SDK's `createProject`, `points.createLedger`, `inventory.createItem`, and `catalog.allocateQuest` helpers generate an `actionId` when omitted. They retry one uncertain network or server failure with that same key. If creation still fails, `CreationError.actionId` lets you retry the same input explicitly. For retries across process restarts, generate and persist an action ID before calling the helper. Direct HTTP creation requests still require `actionId`.
67
+
68
+ Use `actionId` to make a creation request retryable. Reuse the same action ID and payload after an uncertain response; a different payload with the same action ID fails. The action ID is not the resource ID. Provider subjects, observation delivery IDs, completion keys, and nested quiz, tier, budget, and referral-rule keys keep their own scopes.
69
+
70
+ For pickup inventory, supply a label for each new variant and omit `item`; saving returns its assigned item ID. Reuse that ID to share existing stock. For standalone stock, type a name in the Console Stock item picker and choose **Create item “NAME”**, or use `management.inventory.createItem({ name })`. Both return a server-assigned item ID. Stock adjustments accept existing item IDs only.
71
+
28
72
  ## Resource and date/time settings
29
73
 
30
74
  Use `settings.resource({ label, source: discord.channels() })` for a Discord channel selector. Import `discord` alongside `settings` from `@domino-sdk/relay/authoring`. The connected project integration supplies the server; never add a guild ID setting. Filter with `discord.channels({ types: ["announcement"] })` when only announcement channels qualify. The default includes text and announcement channels. Resource values are stable IDs, not labels.
@@ -53,13 +97,13 @@ Fixture providers simulate verification. `workers-ai` requires a configured serv
53
97
 
54
98
  ## Leaderboard definitions
55
99
 
56
- 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.
100
+ Default-export `defineLeaderboard({ key, 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.
57
101
 
58
102
  ```ts
59
103
  import { defineLeaderboard } from "@domino-sdk/relay/authoring";
60
104
 
61
105
  export default defineLeaderboard({
62
- id: "weekly",
106
+ key: "weekly",
63
107
  title: "Weekly contributors",
64
108
  balance: "community",
65
109
  access: "participants",
@@ -84,7 +128,7 @@ import { createManagementClient } from "@domino-sdk/relay-cli";
84
128
  const management = createManagementClient({
85
129
  baseUrl: "https://relay.domino.run",
86
130
  organization: "my-organization",
87
- project: "community",
131
+ project: process.env.DOMINO_PROJECT_ID!,
88
132
  environment: "test",
89
133
  getToken: async () => process.env.DOMINO_MANAGEMENT_KEY!,
90
134
  });
@@ -167,3 +211,5 @@ Author the entry quest with `manual({ actor: "staff" })` and no evidence input.
167
211
  ## Hosted server applications
168
212
 
169
213
  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.
214
+
215
+ Ledger names can repeat. Edit names and descriptions in Console, or call `management.pointLedgers.save({ id, name, description })` to update an existing ledger. `settings.pointLedger` also renders a searchable picker; existing text settings need a new type version and instance upgrade.
@@ -6,6 +6,8 @@ Inspect the app's framework, package manager, workspace boundaries, authenticati
6
6
 
7
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.
8
8
 
9
+ Use the project ID returned by Console. Domino assigns it when the project is created; the project name is a separate display label. Do not invent a project ID or derive it from the name.
10
+
9
11
  From the intended package or project root, run `domino init --project <id>` to create relay.json and RELAY.md. Init verifies access and does not install packages or generate sample quests. If these files already exist, inspect the existing connection and continue from it. Init deliberately refuses to overwrite them.
10
12
 
11
13
  Run `domino agents install` to install this guidance for both Codex and Claude, or select one with `--agent codex` or `--agent claude`. The installer leaves existing AGENTS.md and CLAUDE.md files intact. It can resume missing files, but stops before replacing customized Domino guidance.
@@ -21,3 +23,5 @@ An organization administrator creates an identity integration in Console Setting
21
23
  Mount the adapter at `/relay/*` and use `createBrowserRelayClient({ baseUrl: "/relay" })`. Verify a signed-in session, one participant action, and that logging out of the application removes access. The adapter checks application authentication on every request and derives project scope from the integration key. No custom fetch wrapper or browser project headers are needed.
22
24
 
23
25
  Read [authoring](authoring.md) when adding quests or collection slots. Run the app's normal checks and development server. Use its existing preview deployment process when available. `domino deploy` publishes Domino behavior; it does not deploy the app. `domino stage` requires a supported Domino-hosted static repository, so it is not a general deployment command for existing apps.
26
+
27
+ When upgrading an existing integration, run `domino migrate` to inspect authoring changes, apply them with `domino migrate --write`, and run `domino check --json`. Keep existing authoring names. Legacy `id` remains accepted as an alias for `key`; do not recreate projects or replace stored IDs to perform this upgrade. See [authoring upgrades](authoring.md#upgrade-existing-authoring-code) for diagnostics and manual cases.
@@ -126,3 +126,7 @@ Call `relay.me.rewardDeliveries()` to read role deliveries for the signed-in mem
126
126
  ## Removed demo shortcuts
127
127
 
128
128
  `auth.demoSignIn` and the campaign template endpoint are no longer supported. Use managed sign-in or a registered identity integration. The platform participant `me.linkPass` route remains unconfigured. An app can handle that route itself and verify a pass on its server, then call `POST /management/v1/passes/verify` with a server-only management key carrying `confirm` permission. Send `{ provider, event, subject, member, quest }` and the usual organization, project, and environment headers. Resolve the member from the authenticated participant session, never from browser input. The quest must be staff-confirmed with no evidence. Relay binds ownership uniquely per provider/event in that project, preserves the authenticated management actor, and retries the same entry attempt. There is no built-in event-code verifier.
129
+
130
+ ## Resource references
131
+
132
+ Store returned resource IDs as opaque strings. A quest's `quest` field and a collection's `id` are server-assigned. Authored resources also expose their stable `key`; `relay.quests.get(key)` and `relay.quests.collection(key)` resolve it. Console-created quests have no authoring key, so use their returned IDs. Continue supplying stable action IDs for retries; those values do not become resource IDs.
@@ -8,7 +8,7 @@ Run `domino capabilities referrals --json` against the installed release. Native
8
8
  import { defineReferral, points } from "@domino-sdk/relay/authoring";
9
9
 
10
10
  export default defineReferral({
11
- id: "friends",
11
+ key: "friends",
12
12
  title: "Invite friends",
13
13
  qualification: { kind: "quest", quest: "welcome" },
14
14
  rewards: { inviter: [points("community", 25)] },