@rodyssey/cli 0.1.7 → 0.1.10

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.
Files changed (3) hide show
  1. package/README.md +6 -0
  2. package/dist/cli.js +104 -38
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -12,6 +12,12 @@ To run:
12
12
  bun run index.ts
13
13
  ```
14
14
 
15
+ ## Template Upgrade
16
+
17
+ `app upgrade-template` backfills additive template files that are missing from
18
+ older projects. This includes the Dynamic Worker MCP sample endpoint and the
19
+ shared `mcp/` helper folder; existing local MCP files are left untouched.
20
+
15
21
  ## Release
16
22
 
17
23
  Release automation lives in `.github/workflows/release.yml` and uses Changesets.
package/dist/cli.js CHANGED
@@ -2071,7 +2071,7 @@ var {
2071
2071
  // package.json
2072
2072
  var package_default = {
2073
2073
  name: "@rodyssey/cli",
2074
- version: "0.1.7",
2074
+ version: "0.1.10",
2075
2075
  description: "Scaffold new projects from airconcepts templates",
2076
2076
  repository: {
2077
2077
  type: "git",
@@ -2211,7 +2211,7 @@ function storeSession(session) {
2211
2211
  function resolveSessionToken(env) {
2212
2212
  const token = process.env.CMS_TOKEN || getStoredSession(env)?.token;
2213
2213
  if (!token) {
2214
- throw new Error(`No CMS auth token found for [${env}]. Run \`ro app auth login --env ${env}\` first.`);
2214
+ throw new Error(`No CMS auth token found for [${env}]. Run \`ro auth login --env ${env}\` first.`);
2215
2215
  }
2216
2216
  return token;
2217
2217
  }
@@ -2312,7 +2312,7 @@ async function me(options) {
2312
2312
  const storedSession = getStoredSession(options.env);
2313
2313
  if (!options.remote) {
2314
2314
  if (!storedSession) {
2315
- throw new Error(`No local CMS session found for [${options.env}]. Run \`ro app auth login --env ${options.env}\` first.`);
2315
+ throw new Error(`No local CMS session found for [${options.env}]. Run \`ro auth login --env ${options.env}\` first.`);
2316
2316
  }
2317
2317
  return {
2318
2318
  env: storedSession.env,
@@ -2377,12 +2377,13 @@ function replaceInFiles(dir, filenames, search, replace) {
2377
2377
  } catch {}
2378
2378
  }
2379
2379
  }
2380
- function loadEnv(envName) {
2380
+ function loadEnv(envName, options = {}) {
2381
2381
  const files = [];
2382
2382
  if (envName) {
2383
2383
  files.push(`.env.${envName}`);
2384
2384
  }
2385
2385
  files.push(".env");
2386
+ const loaded = {};
2386
2387
  for (const file of files) {
2387
2388
  if (!existsSync2(file))
2388
2389
  continue;
@@ -2400,11 +2401,16 @@ function loadEnv(envName) {
2400
2401
  if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
2401
2402
  value = value.slice(1, -1);
2402
2403
  }
2403
- if (process.env[key] === undefined) {
2404
- process.env[key] = value;
2404
+ if (loaded[key] === undefined) {
2405
+ loaded[key] = value;
2405
2406
  }
2406
2407
  }
2407
2408
  }
2409
+ for (const [key, value] of Object.entries(loaded)) {
2410
+ if (options.override || process.env[key] === undefined) {
2411
+ process.env[key] = value;
2412
+ }
2413
+ }
2408
2414
  }
2409
2415
 
2410
2416
  // src/create.ts
@@ -4142,7 +4148,59 @@ function isAffirmative(answer) {
4142
4148
  const trimmed = answer.trim().toLowerCase();
4143
4149
  return trimmed === "" || trimmed === "y" || trimmed === "yes";
4144
4150
  }
4151
+ function isExplicitYes(answer) {
4152
+ const trimmed = answer.trim().toLowerCase();
4153
+ return trimmed === "y" || trimmed === "yes";
4154
+ }
4155
+ function assertDeployOptions(options) {
4156
+ if (options.deploy && options.skipDeploy) {
4157
+ console.error("❌ Error: Use either --deploy or --skip-deploy, not both.");
4158
+ process.exit(1);
4159
+ }
4160
+ }
4161
+ async function maybeDeployProduction(options, webappId, deployToken) {
4162
+ const deployRequested = options.deploy === true;
4163
+ const deploySkipped = options.skipDeploy === true;
4164
+ if (deploySkipped) {
4165
+ console.log(`
4166
+ ↷ Skipping production deploy.`);
4167
+ console.log(`Deploy later with:
4168
+ `);
4169
+ console.log(` ro app deploy -e production
4170
+ `);
4171
+ return;
4172
+ }
4173
+ let shouldDeploy = deployRequested;
4174
+ if (!shouldDeploy) {
4175
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
4176
+ console.log(`
4177
+ ✨ Promotion complete! Deploy to production with:
4178
+ `);
4179
+ console.log(` ro app deploy -e production
4180
+ `);
4181
+ return;
4182
+ }
4183
+ const answer = await prompt(`
4184
+ Deploy to production now? (y/N): `);
4185
+ shouldDeploy = isExplicitYes(answer);
4186
+ }
4187
+ if (!shouldDeploy) {
4188
+ console.log(`
4189
+ ✨ Promotion complete! Deploy to production with:
4190
+ `);
4191
+ console.log(` ro app deploy -e production
4192
+ `);
4193
+ return;
4194
+ }
4195
+ console.log(`
4196
+ \uD83D\uDE80 Deploying promoted webapp to production...`);
4197
+ loadEnv(PROD_ENV, { override: true });
4198
+ process.env.WEBAPP_ID = webappId;
4199
+ process.env.DEPLOY_TOKEN = deployToken;
4200
+ await deploy(PROD_ENV);
4201
+ }
4145
4202
  async function promote(options) {
4203
+ assertDeployOptions(options);
4146
4204
  loadEnv();
4147
4205
  const webappId = process.env.WEBAPP_ID;
4148
4206
  if (!webappId) {
@@ -4231,16 +4289,13 @@ ${JSON.stringify(payload, null, 2)}`);
4231
4289
  });
4232
4290
  console.log(`✅ Wrote WEBAPP_ID and DEPLOY_TOKEN to ${PROD_ENV_FILE}`);
4233
4291
  console.log(`\uD83D\uDCCD Webapp ID: ${webappId}`);
4234
- console.log(`
4235
- ✨ Promotion complete! Deploy to production with:
4236
- `);
4237
- console.log(` ro app deploy -e production
4238
- `);
4292
+ await maybeDeployProduction(options, webappId, deployToken);
4239
4293
  }
4240
4294
 
4241
4295
  // src/upgrade-template.ts
4242
4296
  import { execSync as execSync3 } from "node:child_process";
4243
4297
  import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, copyFileSync, rmSync as rmSync2 } from "node:fs";
4298
+ import path3 from "node:path";
4244
4299
  var TEMPLATES = {
4245
4300
  webapp: {
4246
4301
  name: "webapp (SPA)",
@@ -4266,9 +4321,11 @@ var TEMPLATES = {
4266
4321
  "vite-plugins/widgets-manifest.ts"
4267
4322
  ],
4268
4323
  newFiles: [
4324
+ "src/api/mcp.ts",
4269
4325
  "src/exp-engine/cli.ts",
4270
4326
  "src/exp-engine/evaluate.ts",
4271
4327
  "src/exp-engine/README.md",
4328
+ "src/mcp/server.ts",
4272
4329
  "src/types/exp-engine.d.ts",
4273
4330
  "src/widgets/examples.tsx",
4274
4331
  "src/routes/widget.$id.tsx",
@@ -4281,10 +4338,12 @@ var TEMPLATES = {
4281
4338
  remoteName: "template",
4282
4339
  checkoutFiles: ["AGENTS.md", ".agent/", "app/types/webapp.d.ts", "app/types/game-sdk.d.ts"],
4283
4340
  newFiles: [
4341
+ "api/mcp.ts",
4284
4342
  "app/exp-engine/cli.ts",
4285
4343
  "app/exp-engine/evaluate.ts",
4286
4344
  "app/exp-engine/README.md",
4287
- "app/types/exp-engine.d.ts"
4345
+ "app/types/exp-engine.d.ts",
4346
+ "mcp/server.ts"
4288
4347
  ]
4289
4348
  }
4290
4349
  };
@@ -4390,6 +4449,7 @@ async function upgradeTemplate() {
4390
4449
  if (!existsSync7(file)) {
4391
4450
  console.log(`\uD83D\uDCC2 Checking out ${file}...`);
4392
4451
  try {
4452
+ mkdirSync2(path3.dirname(file), { recursive: true });
4393
4453
  execSync3(`git checkout ${template.remoteName}/main -- ${file}`, { stdio: "inherit" });
4394
4454
  } catch {
4395
4455
  console.log(`⚠️ Failed to checkout ${file}`);
@@ -4431,20 +4491,20 @@ var FILES = [
4431
4491
  description: "GameSDK TypeScript definitions"
4432
4492
  }
4433
4493
  ];
4434
- async function downloadFile(url, path3, description) {
4494
+ async function downloadFile(url, path4, description) {
4435
4495
  try {
4436
4496
  console.log(`
4437
4497
  \uD83D\uDCE5 Downloading ${description}...`);
4438
4498
  console.log(` URL: ${url}`);
4439
- console.log(` Path: ${path3}`);
4499
+ console.log(` Path: ${path4}`);
4440
4500
  const response = await fetch(url);
4441
4501
  if (!response.ok) {
4442
4502
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
4443
4503
  }
4444
4504
  const content = await response.text();
4445
- const dir = dirname2(path3);
4505
+ const dir = dirname2(path4);
4446
4506
  await mkdir(dir, { recursive: true });
4447
- await writeFile(path3, content, "utf-8");
4507
+ await writeFile(path4, content, "utf-8");
4448
4508
  console.log(`✅ Downloaded ${description} (${content.length} bytes)
4449
4509
  `);
4450
4510
  return true;
@@ -4486,9 +4546,9 @@ async function downloadDocumentation(manifest) {
4486
4546
  let failCount = 0;
4487
4547
  for (const doc of manifest.documentation) {
4488
4548
  const url = `${BASE_URL}/skills/${doc.file}`;
4489
- const path3 = join3(".agent", "skills", "game-sdk", doc.file);
4549
+ const path4 = join3(".agent", "skills", "game-sdk", doc.file);
4490
4550
  const description = `${doc.title} (${doc.category})`;
4491
- const success = await downloadFile(url, path3, description);
4551
+ const success = await downloadFile(url, path4, description);
4492
4552
  if (success) {
4493
4553
  successCount++;
4494
4554
  } else {
@@ -4593,6 +4653,28 @@ function addConfigTargetOptions(command) {
4593
4653
  function addConfigSetOptions(command) {
4594
4654
  return addConfigTargetOptions(command).option("--title <title>", "Webapp title (pass the literal 'null' to clear)").option("--description <description>", "Webapp description (pass the literal 'null' to clear)").option("--cover-img <url>", "Cover image URL (pass the literal 'null' to clear)").option("--localization <json-or-file>", "Localization JSON object, path to a JSON file, or 'null' to clear").option("--details <json-or-file>", "Partial WebappDetails JSON or path to a JSON file. Sent as a delta — only include keys you want to change. Do NOT echo a full GET response here, or empty defaults like 'tags: []' will clobber real data.").option("--dry-run", "Print the request payload without sending it");
4595
4655
  }
4656
+ function addAuthCommands(parent) {
4657
+ const auth = parent.command("auth").description("Authenticate with the CMS");
4658
+ auth.command("login").description("Log in to the CMS using a browser callback").option("-e, --env <environment>", "CMS environment (local | development | staging | production)", "development").option("--cms-url <url>", "CMS base URL. Defaults to the selected environment").option("--login-url <url>", "Full browser login URL. Defaults to <cms-url>/auth/cli-login").option("--token-url <url>", "Full token exchange URL. Defaults to <cms-url>/api/auth/cli-token").option("--callback-port <port>", "Local callback port. Defaults to a random free port", parseInt).option("--timeout <seconds>", "Seconds to wait for the browser callback", parseInt, 300).option("--no-open", "Print the login URL without opening a browser").action(async (options) => {
4659
+ const session = await login({
4660
+ env: options.env,
4661
+ cmsUrl: options.cmsUrl,
4662
+ loginUrl: options.loginUrl,
4663
+ tokenUrl: options.tokenUrl,
4664
+ callbackPort: options.callbackPort,
4665
+ open: options.open,
4666
+ timeoutMs: (options.timeout ?? 300) * 1000
4667
+ });
4668
+ console.log(`✅ Logged in to CMS [${session.env}]`);
4669
+ console.log(`\uD83D\uDCCD CMS URL: ${session.cmsUrl}`);
4670
+ });
4671
+ auth.command("me").description("Show the locally stored CMS login session").option("-e, --env <environment>", "CMS environment (local | development | staging | production)", "development").option("--remote", "Call the CMS /me endpoint instead of only reading the local session").option("--cms-url <url>", "CMS base URL for --remote. Defaults to the selected environment or stored session").option("--me-url <url>", "Full me endpoint URL for --remote. Defaults to <cms-url>/api/auth/me").action(async (options) => {
4672
+ const currentUser = await me(options);
4673
+ console.log(JSON.stringify(currentUser, null, 2));
4674
+ });
4675
+ return auth;
4676
+ }
4677
+ addAuthCommands(program);
4596
4678
  app.command("create").argument("<project-name>", "Name of the project to create").option("-t, --template <template>", "Template to use (webapp | webapp-fullstack)").option("--auto", "Create a CMS webapp and write WEBAPP_ID/DEPLOY_TOKEN to .env").option("-e, --env <environment>", "CMS environment for --auto (local | development | staging | production)", "development").option("--cms-url <url>", "CMS base URL for --auto. Defaults to the selected environment").option("--create-url <url>", "Full CMS create endpoint for --auto. Defaults to <cms-url>/api/cli/webapps/create").description("Create a new project from a template").action(async (projectName, options) => {
4597
4679
  let templateName;
4598
4680
  if (options.template) {
@@ -4612,13 +4694,15 @@ app.command("create").argument("<project-name>", "Name of the project to create"
4612
4694
  createUrl: options.createUrl
4613
4695
  });
4614
4696
  });
4615
- app.command("promote").description("Promote the current webapp to production (creates a prod record with the same WEBAPP_ID)").option("--details <json-or-file>", "Full WebappDetails JSON object or path to a JSON file. Skips the source-pull and confirmation prompts when provided.").option("-y, --yes", "Auto-accept pulling the latest details from development").option("--cms-url <url>", "Production CMS base URL. Defaults to the production environment").option("--promote-url <url>", "Full CMS promote endpoint. Defaults to <cms-url>/api/cli/webapps/promote").option("--from <env>", "Source environment to pull details from (testing override). Defaults to development", "development").action(async (options) => {
4697
+ app.command("promote").description("Promote the current webapp to production (creates a prod record with the same WEBAPP_ID)").option("--details <json-or-file>", "Full WebappDetails JSON object or path to a JSON file. Skips the source-pull and confirmation prompts when provided.").option("-y, --yes", "Auto-accept pulling the latest details from development").option("--cms-url <url>", "Production CMS base URL. Defaults to the production environment").option("--promote-url <url>", "Full CMS promote endpoint. Defaults to <cms-url>/api/cli/webapps/promote").option("--from <env>", "Source environment to pull details from (testing override). Defaults to development", "development").option("--deploy", "Deploy to production immediately after promotion").option("--skip-deploy", "Do not ask to deploy after promotion").action(async (options) => {
4616
4698
  await promote({
4617
4699
  details: options.details,
4618
4700
  yes: options.yes,
4619
4701
  cmsUrl: options.cmsUrl,
4620
4702
  promoteUrl: options.promoteUrl,
4621
- from: options.from
4703
+ from: options.from,
4704
+ deploy: options.deploy,
4705
+ skipDeploy: options.skipDeploy
4622
4706
  });
4623
4707
  });
4624
4708
  app.command("update-game-sdk").description("Download and update the GameSDK library, types, and documentation").action(async () => {
@@ -4627,24 +4711,6 @@ app.command("update-game-sdk").description("Download and update the GameSDK libr
4627
4711
  app.command("deploy").description("Build and deploy the webapp to the server").option("-e, --env <environment>", "Target environment (local | development | staging | production)", "development").option("--host <host>", "Override the deploy host").option("--port <port>", "Override the deploy port", parseInt).action(async (options) => {
4628
4712
  await deploy(options.env, { host: options.host, port: options.port });
4629
4713
  });
4630
- var auth = app.command("auth").description("Authenticate with the CMS");
4631
- auth.command("login").description("Log in to the CMS using a browser callback").option("-e, --env <environment>", "CMS environment (local | development | staging | production)", "development").option("--cms-url <url>", "CMS base URL. Defaults to the selected environment").option("--login-url <url>", "Full browser login URL. Defaults to <cms-url>/auth/cli-login").option("--token-url <url>", "Full token exchange URL. Defaults to <cms-url>/api/auth/cli-token").option("--callback-port <port>", "Local callback port. Defaults to a random free port", parseInt).option("--timeout <seconds>", "Seconds to wait for the browser callback", parseInt, 300).option("--no-open", "Print the login URL without opening a browser").action(async (options) => {
4632
- const session = await login({
4633
- env: options.env,
4634
- cmsUrl: options.cmsUrl,
4635
- loginUrl: options.loginUrl,
4636
- tokenUrl: options.tokenUrl,
4637
- callbackPort: options.callbackPort,
4638
- open: options.open,
4639
- timeoutMs: (options.timeout ?? 300) * 1000
4640
- });
4641
- console.log(`✅ Logged in to CMS [${session.env}]`);
4642
- console.log(`\uD83D\uDCCD CMS URL: ${session.cmsUrl}`);
4643
- });
4644
- auth.command("me").description("Show the locally stored CMS login session").option("-e, --env <environment>", "CMS environment (local | development | staging | production)", "development").option("--remote", "Call the CMS /me endpoint instead of only reading the local session").option("--cms-url <url>", "CMS base URL for --remote. Defaults to the selected environment or stored session").option("--me-url <url>", "Full me endpoint URL for --remote. Defaults to <cms-url>/api/auth/me").action(async (options) => {
4645
- const currentUser = await me(options);
4646
- console.log(JSON.stringify(currentUser, null, 2));
4647
- });
4648
4714
  var config = app.command("config").description("Manage webapp metadata config");
4649
4715
  addConfigTargetOptions(config.command("get").description("Pull the current webapp metadata config from the CMS").option("--out <file>", "Write the config JSON to a file")).action(async (options) => {
4650
4716
  await getWebappConfig(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rodyssey/cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.10",
4
4
  "description": "Scaffold new projects from airconcepts templates",
5
5
  "repository": {
6
6
  "type": "git",