@domino-sdk/relay-cli 0.4.0 → 0.6.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.
@@ -20,7 +20,7 @@
20
20
  {
21
21
  "id": "connected-accounts",
22
22
  "support": "native",
23
- "description": "Operators connect a Discord server and install the managed bot in Console Integrations. Participants sign in with Discord or link its identity. Declare integrations: [discord] and use ctx.discord.member() without a server ID. Captured membership and role reads survive recovery. No messages, writes, or X verification.",
23
+ "description": "Project-scoped Discord membership, roles, nickname, display name, boosting status and captured message reads. Participants link identity; operators install the bot once. No arbitrary Discord writes or X verification.",
24
24
  "entrypoints": [
25
25
  "auth.accounts",
26
26
  "auth.start",
@@ -43,33 +43,72 @@
43
43
  "apps/api/src/identity-integration.ts"
44
44
  ]
45
45
  },
46
+ {
47
+ "id": "observation-quests",
48
+ "support": "native",
49
+ "description": "Trusted backend observations and Discord reaction.add, reaction.received and message.create trigger automatic quests. Actor identity is separate from beneficiary membership. Keyed completions and atomic budgets prevent duplicate awards. Multi-action counters and streaks are not implemented.",
50
+ "entrypoints": [
51
+ "automatic",
52
+ "keyed",
53
+ "budget",
54
+ "management.observations.submit"
55
+ ],
56
+ "reference": "authoring",
57
+ "evidence": [
58
+ "tests/observation-quests.test.mjs",
59
+ "tests/discord-observations.test.mjs"
60
+ ]
61
+ },
46
62
  {
47
63
  "id": "quests",
48
64
  "support": "native",
49
65
  "description": "Claim, photo, quiz, staff and automatic quests; immutable releases pin attempts and promised rewards.",
50
- "entrypoints": ["quests.experience", "quests.observe", "quests.open"],
51
- "evidence": ["tests/quest-catalog.test.mjs", "tests/submissions.test.mjs"]
66
+ "entrypoints": [
67
+ "quests.experience",
68
+ "quests.observe",
69
+ "quests.open"
70
+ ],
71
+ "evidence": [
72
+ "tests/quest-catalog.test.mjs",
73
+ "tests/submissions.test.mjs"
74
+ ]
52
75
  },
53
76
  {
54
77
  "id": "points",
55
78
  "support": "native",
56
79
  "description": "Award points to the participant completing a quest. The server applies completion and rewards; the browser cannot write balances.",
57
- "entrypoints": ["points", "decision.accept", "me.observeProgress"],
58
- "evidence": ["tests/journey.test.mjs"]
80
+ "entrypoints": [
81
+ "points",
82
+ "decision.accept",
83
+ "me.observeProgress"
84
+ ],
85
+ "evidence": [
86
+ "tests/journey.test.mjs"
87
+ ]
59
88
  },
60
89
  {
61
90
  "id": "review",
62
91
  "support": "native",
63
92
  "description": "Queue a quest attempt for an authorized human reviewer. Review decisions and reward execution remain server-owned.",
64
- "entrypoints": ["decision.review"],
65
- "evidence": ["tests/photo-review.test.mjs"]
93
+ "entrypoints": [
94
+ "decision.review"
95
+ ],
96
+ "evidence": [
97
+ "tests/photo-review.test.mjs"
98
+ ]
66
99
  },
67
100
  {
68
101
  "id": "milestones",
69
102
  "support": "composed",
70
103
  "description": "Compose same-participant completion prerequisites with requires: [completed(quest)]. Referral programs support milestones based on distinct qualified friends. Other aggregate thresholds need application logic.",
71
- "entrypoints": ["completed", "automatic", "defineQuest"],
72
- "evidence": ["tests/availability.test.mjs"]
104
+ "entrypoints": [
105
+ "completed",
106
+ "automatic",
107
+ "defineQuest"
108
+ ],
109
+ "evidence": [
110
+ "tests/availability.test.mjs"
111
+ ]
73
112
  },
74
113
  {
75
114
  "id": "referrals",
@@ -83,15 +122,24 @@
83
122
  "referrals.history"
84
123
  ],
85
124
  "reference": "referrals",
86
- "evidence": ["tests/referrals.test.mjs", "packages/sdk/src/referrals.ts"]
125
+ "evidence": [
126
+ "tests/referrals.test.mjs",
127
+ "packages/sdk/src/referrals.ts"
128
+ ]
87
129
  },
88
130
  {
89
131
  "id": "cross-participant-rewards",
90
132
  "support": "native",
91
133
  "description": "Referral programs can reward inviter and invitee independently and award a selective percentage of eligible points to the inviter. Bonuses accumulate fractions and support limits, suspension and correction recovery. Arbitrary participant-to-participant transfers and multi-level commissions are not supported.",
92
- "entrypoints": ["defineReferral", "referrals.summary"],
134
+ "entrypoints": [
135
+ "defineReferral",
136
+ "referrals.summary"
137
+ ],
93
138
  "reference": "referrals",
94
- "evidence": ["tests/referrals.test.mjs", "apps/api/src/referrals.ts"]
139
+ "evidence": [
140
+ "tests/referrals.test.mjs",
141
+ "apps/api/src/referrals.ts"
142
+ ]
95
143
  },
96
144
  {
97
145
  "id": "rankings",
@@ -111,8 +159,44 @@
111
159
  "id": "wallet-verification",
112
160
  "support": "native",
113
161
  "description": "Connect a wallet and verify supported Solana balances. This reads a real network; it does not send assets or execute payouts.",
114
- "entrypoints": ["wallet.connection", "wallet.challenge", "wallet.verify"],
115
- "evidence": ["tests/wallet.test.mjs"]
162
+ "entrypoints": [
163
+ "wallet.connection",
164
+ "wallet.challenge",
165
+ "wallet.verify"
166
+ ],
167
+ "evidence": [
168
+ "tests/wallet.test.mjs"
169
+ ]
170
+ },
171
+ {
172
+ "id": "resource-settings",
173
+ "support": "native",
174
+ "description": "Reusable Console resource and date/time fields. Discord channels resolve from the connected project integration; backend publication validates access. Empty resource settings keep code-authored quests paused.",
175
+ "entrypoints": [
176
+ "settings.resource",
177
+ "settings.datetime",
178
+ "discord.channels",
179
+ "discord.roles"
180
+ ],
181
+ "reference": "authoring",
182
+ "evidence": [
183
+ "tests/resource-settings.test.mjs"
184
+ ]
185
+ },
186
+ {
187
+ "id": "discord-role-rewards",
188
+ "support": "native",
189
+ "description": "Quest-declared Discord role rewards commit durable delivery requests with completion, retry independently, and remain bound to the original account/server. Role selectors validate assignability. Referral reward lists do not accept integration rewards.",
190
+ "entrypoints": [
191
+ "discordRole",
192
+ "me.rewardDeliveries",
193
+ "management.rewardDeliveries"
194
+ ],
195
+ "reference": "authoring",
196
+ "evidence": [
197
+ "tests/discord-role-delivery.test.mjs",
198
+ "tests/discord-expansion.test.mjs"
199
+ ]
116
200
  }
117
201
  ]
118
202
  }
@@ -2,6 +2,7 @@ import { Option } from "commander";
2
2
  import { dirname } from "node:path";
3
3
  import { action } from "../runtime.mjs";
4
4
  import { installAgentSkills } from "../agents.mjs";
5
+ import { canPrompt, promptSelect } from "../prompts.mjs";
5
6
  import { diagnose } from "../doctor.mjs";
6
7
 
7
8
  export function registerAgentCommands(program) {
@@ -17,18 +18,29 @@ export function registerAgentCommands(program) {
17
18
  "Project root (defaults to the linked project or current directory)",
18
19
  )
19
20
  .addOption(
20
- new Option("--agent <name>", "Agent to configure")
21
- .choices(["codex", "claude", "both"])
22
- .default("both"),
21
+ new Option("--agent <name>", "Agent to configure").choices([
22
+ "codex",
23
+ "claude",
24
+ "both",
25
+ ]),
23
26
  )
24
27
  .action(
25
- action(({ project, options }) =>
26
- installAgentSkills(
28
+ action(async ({ project, options }) => {
29
+ const agent =
30
+ options.agent ??
31
+ (canPrompt(options)
32
+ ? await promptSelect("Choose your coding agent", [
33
+ { label: "Codex", value: "codex" },
34
+ { label: "Claude", value: "claude" },
35
+ { label: "Both", value: "both" },
36
+ ])
37
+ : "both");
38
+ return installAgentSkills(
27
39
  options.directory ??
28
40
  (project ? dirname(project.path) : process.cwd()),
29
- options.agent,
30
- ),
31
- ),
41
+ agent,
42
+ );
43
+ }),
32
44
  );
33
45
 
34
46
  program
@@ -1,11 +1,18 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { action } from "../runtime.mjs";
3
3
 
4
- const references = ["authoring", "participant-api", "referrals", "troubleshooting"];
4
+ const references = [
5
+ "authoring",
6
+ "participant-api",
7
+ "referrals",
8
+ "troubleshooting",
9
+ ];
5
10
  export function registerDiscoveryCommands(program) {
6
11
  program
7
12
  .command("capabilities")
8
- .description("Discover supported Domino features and their limitations")
13
+ .description(
14
+ "Read this CLI release's bundled capability catalog (not a server probe)",
15
+ )
9
16
  .argument("[id]", "Capability identifier")
10
17
  .action(
11
18
  action(async (_, id) => {
@@ -15,13 +22,25 @@ export function registerDiscoveryCommands(program) {
15
22
  "utf8",
16
23
  ),
17
24
  );
18
- if (!id) return catalog;
25
+ const { version } = JSON.parse(
26
+ await readFile(
27
+ new URL("../../package.json", import.meta.url),
28
+ "utf8",
29
+ ),
30
+ );
31
+ const provenance = {
32
+ kind: "bundled-catalog",
33
+ cliVersion: version,
34
+ serverVerified: false,
35
+ note: "Describes this CLI release. Scope flags do not make it project-specific. Verify the deployed contract before treating missing or conflicting entries as unsupported.",
36
+ };
37
+ if (!id) return { ...catalog, provenance };
19
38
  const capability = catalog.capabilities.find((item) => item.id === id);
20
39
  if (!capability)
21
40
  throw new Error(
22
41
  `Unknown capability. Choose: ${catalog.capabilities.map((item) => item.id).join(", ")}`,
23
42
  );
24
- return capability;
43
+ return { ...capability, provenance };
25
44
  }),
26
45
  );
27
46
  program
@@ -4,6 +4,8 @@ import { dirname, resolve } from "node:path";
4
4
  import { execFile } from "node:child_process";
5
5
  import { promisify } from "node:util";
6
6
  import { action } from "../runtime.mjs";
7
+ import { canPrompt, promptText, promptSelect } from "../prompts.mjs";
8
+ import { selectProject } from "../select-project.mjs";
7
9
  import { request } from "../connection.mjs";
8
10
  import { checkout, requireNewDirectory } from "../git.mjs";
9
11
  import { dev } from "../dev.mjs";
@@ -69,20 +71,54 @@ export function registerHostingCommands(program) {
69
71
  program
70
72
  .command("create")
71
73
  .description("Create a hosted campaign repository and check it out locally")
72
- .argument("<name>", "Project identifier")
74
+ .argument("[name]", "Project identifier (prompted if omitted)")
73
75
  .option("--directory <path>", "Local directory (defaults to project name)")
74
76
  .action(
75
77
  action(async ({ connection, options }, name) => {
76
- if (!/^[a-zA-Z0-9_-]+$/.test(name))
77
- throw new Error(
78
- "Use letters, numbers, underscores, or hyphens for the project name.",
79
- );
80
- const directory = resolve(options.directory ?? name);
78
+ const interactive = canPrompt(options);
79
+ const validateName = (value) =>
80
+ /^[a-zA-Z0-9_-]+$/.test(value)
81
+ ? undefined
82
+ : "Use letters, numbers, underscores, or hyphens for the project name.";
83
+ if (!name && interactive)
84
+ name = await promptText("Project name", { validate: validateName });
85
+ if (!name)
86
+ throw new Error("Provide a project name: domino create NAME.");
87
+ const nameError = validateName(name);
88
+ if (nameError) throw new Error(nameError);
89
+ const destination =
90
+ options.directory ??
91
+ (interactive
92
+ ? await promptText("Destination directory", {
93
+ defaultValue: name,
94
+ validate: async (value) => {
95
+ try {
96
+ await requireNewDirectory(resolve(value));
97
+ } catch (error) {
98
+ return error.message;
99
+ }
100
+ },
101
+ })
102
+ : name);
103
+ const directory = resolve(destination);
81
104
  await requireNewDirectory(directory);
82
105
  const access = await request(connection, "/access");
83
- const organization = connection.organization || access.organization;
106
+ let organization = connection.organization || access.organization;
107
+ if (!organization && interactive) {
108
+ const organizations = [
109
+ ...new Set(
110
+ access.projects.map((item) => item.organization).filter(Boolean),
111
+ ),
112
+ ];
113
+ organization = organizations.length
114
+ ? await promptSelect(
115
+ "Choose an organization",
116
+ organizations.map((id) => ({ label: id, value: id })),
117
+ )
118
+ : await promptText("Organization ID");
119
+ }
84
120
  if (!organization)
85
- throw new Error("Select an organization with --organization.");
121
+ throw new Error("Select an organization with --organization ID.");
86
122
  const scoped = {
87
123
  ...connection,
88
124
  organization,
@@ -101,13 +137,31 @@ export function registerHostingCommands(program) {
101
137
  .description(
102
138
  "Recover or clone a hosted campaign with Domino Git authentication",
103
139
  )
104
- .argument("<project>")
140
+ .argument(
141
+ "[project]",
142
+ "Project identifier (choose interactively if omitted)",
143
+ )
105
144
  .option("--directory <path>", "Local directory (defaults to project name)")
106
145
  .action(
107
146
  action(async ({ connection, options }, project) => {
108
- const scoped = { ...connection, project, environment: "test" };
147
+ const scoped = {
148
+ ...connection,
149
+ project: project ?? connection.project,
150
+ environment: "test",
151
+ };
152
+ if (!scoped.project) {
153
+ const access = await request(scoped, "/access");
154
+ const selected = await selectProject(access, scoped, options);
155
+ scoped.project = selected.project;
156
+ scoped.organization =
157
+ scoped.organization ?? selected.organization ?? access.organization;
158
+ }
109
159
  const repository = await request(scoped, "/repository");
110
- return checkout(scoped, repository, options.directory ?? project);
160
+ return checkout(
161
+ scoped,
162
+ repository,
163
+ options.directory ?? scoped.project,
164
+ );
111
165
  }),
112
166
  );
113
167
  }
@@ -16,7 +16,11 @@ export function registerInspectCommands(program) {
16
16
  program
17
17
  .command("status")
18
18
  .description("Show the project overview")
19
- .action(action(({ connection }) => request(connection, "/overview")));
19
+ .action(
20
+ action(({ connection }) => request(connection, "/overview"), {
21
+ projectScope: true,
22
+ }),
23
+ );
20
24
 
21
25
  for (const [name, description] of [
22
26
  ["quests", "List current quests"],
@@ -34,14 +38,17 @@ export function registerInspectCommands(program) {
34
38
  .option("--filter <value>", "Filter the collection")
35
39
  .option("--q <text>", "Search the collection")
36
40
  .action(
37
- action(({ connection, options }) => {
38
- const query = new URLSearchParams();
39
- for (const key of ["limit", "offset", "filter", "q"]) {
40
- if (options[key] !== undefined)
41
- query.set(key, String(options[key]));
42
- }
43
- return request(connection, `/${name}?${query}`);
44
- }),
41
+ action(
42
+ ({ connection, options }) => {
43
+ const query = new URLSearchParams();
44
+ for (const key of ["limit", "offset", "filter", "q"]) {
45
+ if (options[key] !== undefined)
46
+ query.set(key, String(options[key]));
47
+ }
48
+ return request(connection, `/${name}?${query}`);
49
+ },
50
+ { projectScope: true },
51
+ ),
45
52
  );
46
53
  }
47
54
  }
@@ -1,5 +1,8 @@
1
- import { writeFile } from "node:fs/promises";
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
2
3
  import { action } from "../runtime.mjs";
4
+ import { confirmAction } from "../prompts.mjs";
5
+ import { selectProject } from "../select-project.mjs";
3
6
  import { request } from "../connection.mjs";
4
7
  import { initialize, bundleProject } from "../project.mjs";
5
8
 
@@ -63,7 +66,17 @@ async function build({ project, connection, options }, deploy) {
63
66
  throw new Error(
64
67
  "Live publication requires Console approval. Run deploy --dry-run --out deployment.json, then import the project deployment in Console.",
65
68
  );
66
- if (options.resume) return publish(connection, options.resume);
69
+ const confirmPublication = (detail) =>
70
+ confirmAction(
71
+ options,
72
+ `Publish to ${connection.organization ?? "current organization"}/${connection.project} in ${connection.environment ?? "test"}.
73
+ API: ${connection.apiUrl}
74
+ ${detail}`,
75
+ );
76
+ if (options.resume) {
77
+ await confirmPublication(`Resume deployment ${options.resume}.`);
78
+ return publish(connection, options.resume);
79
+ }
67
80
  const payload = await bundleProject(project);
68
81
  if (deploy) {
69
82
  const preview = await request(
@@ -73,9 +86,13 @@ async function build({ project, connection, options }, deploy) {
73
86
  payload,
74
87
  );
75
88
  if (options.preview) return { status: "preview", ...summary(preview) };
89
+ await confirmPublication(
90
+ `Deployment ${preview.id}: ${preview.releases.length} quests, ${preview.types.length} types, ${preview.collections.length} collections, ${preview.leaderboards.length} leaderboards, ${preview.referrals.length} referral programs.`,
91
+ );
76
92
  return publish(connection, preview.id);
77
93
  }
78
94
  if (options.out) {
95
+ await mkdir(dirname(options.out), { recursive: true });
79
96
  await writeFile(options.out, JSON.stringify(payload, null, 2) + "\n");
80
97
  return {
81
98
  file: options.out,
@@ -102,21 +119,11 @@ export function registerProjectCommands(program) {
102
119
  .action(
103
120
  action(async ({ connection, options }, directory) => {
104
121
  const access = await request(connection, "/access");
105
- const selected = connection.project
106
- ? access.projects.find((item) => item.project === connection.project)
107
- : access.projects.length === 1
108
- ? access.projects[0]
109
- : null;
110
- if (!selected)
111
- throw new Error(
112
- "Select an accessible project with --project ID. Run domino whoami to list projects; create projects in Console.",
113
- );
122
+ const selected = await selectProject(access, connection, options);
114
123
  const environment = connection.environment ?? "test";
115
- if (!selected.environments.includes(environment))
116
- throw new Error(`No access to ${environment} for this project.`);
117
124
  return initialize(directory, {
118
125
  ...connection,
119
- organization: access.organization,
126
+ organization: selected.organization ?? access.organization,
120
127
  project: selected.project,
121
128
  environment,
122
129
  quests: (options.entry ?? []).map((entry) => ({
@@ -144,6 +151,7 @@ export function registerProjectCommands(program) {
144
151
  .description(
145
152
  "Atomically deploy quests, reusable types, and collection slots, preserving Console-owned settings and placement",
146
153
  )
154
+ .option("--yes", "Skip the terminal publication confirmation")
147
155
  .option(
148
156
  "--dry-run",
149
157
  "Build locally only; does not preview Relay custom values",
@@ -2,6 +2,8 @@ import { dirname } from "node:path";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { action } from "../runtime.mjs";
4
4
  import { request } from "../connection.mjs";
5
+ import { canPrompt, promptSelect, confirmAction } from "../prompts.mjs";
6
+ import { projectConnection } from "../select-project.mjs";
5
7
  import { runGit } from "../git.mjs";
6
8
 
7
9
  export function registerStagingCommands(program) {
@@ -15,21 +17,25 @@ export function registerStagingCommands(program) {
15
17
  "--request-id <uuid>",
16
18
  "Resume a previously submitted build request",
17
19
  )
20
+ .option("--yes", "Skip the terminal confirmation")
18
21
  .option("--no-wait", "Return the build ID without waiting")
19
22
  .action(
20
23
  action(async ({ connection, project, options }) => {
21
24
  if (!project)
22
25
  throw new Error("Run stage inside a hosted campaign checkout.");
23
- if (project.config.app?.kind !== "static")
26
+ if (!project.config.app)
24
27
  throw new Error(
25
- "Cloud staging currently supports static campaign checkouts with app.kind=static in relay.json. This project is not configured for cloud staging. SSR Worker apps use the Cloudflare Worker deployment workflow.",
28
+ "Configure app.kind as static or worker in relay.json before staging.",
26
29
  );
27
30
  const scoped = { ...connection, environment: "test" };
28
31
  const preflight = await request(scoped, "/preflight");
29
32
  if (preflight.staging !== "configured")
30
33
  throw new Error(
31
34
  preflight.checks
32
- .filter((check) => check.status === "error")
35
+ .filter(
36
+ (check) =>
37
+ check.id !== "hosted-test-auth" && check.status === "error",
38
+ )
33
39
  .map((check) => check.message)
34
40
  .join("\n"),
35
41
  );
@@ -45,6 +51,12 @@ export function registerStagingCommands(program) {
45
51
  options.commit ?? (await runGit(["rev-parse", "HEAD"], { cwd }));
46
52
  if (!/^[a-f0-9]{40}$/.test(commit))
47
53
  throw new Error("Use a full 40-character commit SHA.");
54
+ await confirmAction(
55
+ options,
56
+ `Stage commit ${commit} for ${scoped.organization ?? "current organization"}/${scoped.project} in test.
57
+ API: ${scoped.apiUrl}
58
+ This builds the pushed commit and updates its staging preview.`,
59
+ );
48
60
  const requestId = options.requestId ?? randomUUID();
49
61
  process.stderr.write(`Build request ${requestId}\n`);
50
62
  let build;
@@ -81,18 +93,41 @@ export function registerStagingCommands(program) {
81
93
  .command("builds")
82
94
  .description("List recent cloud builds for this campaign")
83
95
  .action(
84
- action(({ connection }) =>
85
- request({ ...connection, environment: "test" }, "/builds"),
86
- ),
96
+ action(({ connection }) => request(connection, "/builds"), {
97
+ projectScope: true,
98
+ environment: "test",
99
+ }),
87
100
  );
88
101
  program
89
102
  .command("logs")
90
103
  .description("Read retained cloud build logs")
91
- .argument("<id>", "Build ID")
104
+ .argument("[id]", "Build ID (choose a recent build if omitted)")
92
105
  .action(
93
- action(({ connection }, id) => {
94
- if (!/^[a-f0-9-]{36}$/.test(id))
106
+ action(async ({ connection, options }, id) => {
107
+ if (!id && !canPrompt(options))
108
+ throw new Error(
109
+ "Provide a build ID: domino logs ID. Run domino builds to list recent builds.",
110
+ );
111
+ if (id && !/^[a-f0-9-]{36}$/.test(id))
95
112
  throw new Error("Expected a build UUID.");
113
+ connection = await projectConnection(
114
+ { ...connection, environment: "test" },
115
+ options,
116
+ );
117
+ if (!id) {
118
+ const builds = await request(connection, "/builds");
119
+ if (!builds.length)
120
+ throw new Error(
121
+ "No builds yet. Push a commit and run domino stage first.",
122
+ );
123
+ id = await promptSelect(
124
+ "Choose a build",
125
+ builds.map((build) => ({
126
+ label: `${build.phase} • ${new Date(build.createdAt).toISOString()} • ${build.commit.slice(0, 8)} • ${build.id}${build.current ? " • current" : ""}`,
127
+ value: build.id,
128
+ })),
129
+ );
130
+ }
96
131
  return request(
97
132
  { ...connection, environment: "test" },
98
133
  `/builds/${id}/logs`,
@@ -22,7 +22,7 @@ export async function connectedDev(project, connection, options) {
22
22
  const preflight = await request(connection, "/preflight");
23
23
  if (preflight.development?.status !== "configured")
24
24
  throw new Error(
25
- "Hosted development is not configured. Run domino doctor --remote --json; a Domino operator must enable previews and test sign-in.",
25
+ "Hosted development is not configured. Run domino doctor --remote --json; a Domino operator must enable preview hosting.",
26
26
  );
27
27
  const root = dirname(project.path);
28
28
  const connector = await previewConnector(options.cloudflared);
@@ -126,6 +126,7 @@ export async function connectedDev(project, connection, options) {
126
126
  session = await request(connection, "/development/session", "POST", {
127
127
  ...lease,
128
128
  port,
129
+ routing: project.config.app.routing,
129
130
  });
130
131
  const sync = developmentSync(connection, project.path);
131
132
  await sync();
@@ -143,6 +144,7 @@ export async function connectedDev(project, connection, options) {
143
144
  start("pnpm", ["run", project.config.app.devScript], {
144
145
  DOMINO_APP_PORT: String(port),
145
146
  DOMINO_PREVIEW_ORIGIN: session.url,
147
+ DOMINO_PARTICIPANT_API: connection.apiUrl,
146
148
  });
147
149
  let appReady = false;
148
150
  for (let attempt = 0; attempt < 150 && !stopping; attempt++) {
@@ -190,7 +192,7 @@ export async function connectedDev(project, connection, options) {
190
192
  )
191
193
  );
192
194
  process.stderr.write(
193
- `Preview: ${session.url}\nUses this project's hosted test data and real sign-in. Keep this terminal running. Use domino stage for a preview that stays online.\n`,
195
+ `Preview: ${session.url}\nKeep this terminal running. Use domino stage for a preview that stays online.\n`,
194
196
  );
195
197
  let renewed = Date.now();
196
198
  while (!stopping) {
package/cli/dev.mjs CHANGED
@@ -113,7 +113,7 @@ export async function dev(project, options) {
113
113
  const child = spawn(command, args, {
114
114
  cwd: root,
115
115
  env: { ...process.env, ...extraEnv },
116
- stdio: "inherit",
116
+ stdio: ["inherit", 2, 2],
117
117
  detached: process.platform !== "win32",
118
118
  });
119
119
  children.push(child);