@revos/cli 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +45 -0
  3. package/dist/adapters/oclif/commands/action-runs/get.mjs +1 -1
  4. package/dist/adapters/oclif/commands/action-runs/list.mjs +1 -1
  5. package/dist/adapters/oclif/commands/actions/get-input-schema.mjs +2 -2
  6. package/dist/adapters/oclif/commands/actions/get-params-schema.mjs +2 -2
  7. package/dist/adapters/oclif/commands/actions/get.mjs +1 -1
  8. package/dist/adapters/oclif/commands/actions/list.mjs +2 -2
  9. package/dist/adapters/oclif/commands/ai-instructions/create.mjs +1 -1
  10. package/dist/adapters/oclif/commands/ai-instructions/delete.mjs +1 -1
  11. package/dist/adapters/oclif/commands/ai-instructions/get.mjs +1 -1
  12. package/dist/adapters/oclif/commands/ai-instructions/list.mjs +1 -1
  13. package/dist/adapters/oclif/commands/ai-instructions/update.mjs +1 -1
  14. package/dist/adapters/oclif/commands/auth/login.mjs +2 -2
  15. package/dist/adapters/oclif/commands/auth/logout.mjs +7 -3
  16. package/dist/adapters/oclif/commands/auth/status.mjs +2 -2
  17. package/dist/adapters/oclif/commands/gservice-account-keys/get.mjs +1 -1
  18. package/dist/adapters/oclif/commands/gservice-account-keys/reveal.mjs +2 -2
  19. package/dist/adapters/oclif/commands/gservice-accounts/create.mjs +1 -1
  20. package/dist/adapters/oclif/commands/gservice-accounts/delete.mjs +1 -1
  21. package/dist/adapters/oclif/commands/gservice-accounts/get.mjs +1 -1
  22. package/dist/adapters/oclif/commands/gservice-accounts/list.mjs +1 -1
  23. package/dist/adapters/oclif/commands/init.mjs +4 -3
  24. package/dist/adapters/oclif/commands/integrations/create.d.mts +11 -0
  25. package/dist/adapters/oclif/commands/integrations/create.mjs +16 -0
  26. package/dist/adapters/oclif/commands/integrations/get.d.mts +15 -0
  27. package/dist/adapters/oclif/commands/integrations/get.mjs +21 -0
  28. package/dist/adapters/oclif/commands/integrations/list.d.mts +11 -0
  29. package/dist/adapters/oclif/commands/integrations/list.mjs +16 -0
  30. package/dist/adapters/oclif/commands/integrations/update.d.mts +15 -0
  31. package/dist/adapters/oclif/commands/integrations/update.mjs +21 -0
  32. package/dist/adapters/oclif/commands/org/create.mjs +1 -1
  33. package/dist/adapters/oclif/commands/org/current.mjs +2 -2
  34. package/dist/adapters/oclif/commands/org/get.mjs +1 -1
  35. package/dist/adapters/oclif/commands/org/list.mjs +2 -2
  36. package/dist/adapters/oclif/commands/org/switch.mjs +5 -4
  37. package/dist/adapters/oclif/commands/overlays/diff.mjs +5 -5
  38. package/dist/adapters/oclif/commands/overlays/pull.mjs +4 -3
  39. package/dist/adapters/oclif/commands/overlays/push.mjs +2 -2
  40. package/dist/adapters/oclif/commands/overlays/status.mjs +2 -2
  41. package/dist/adapters/oclif/commands/score-groups/create.mjs +1 -1
  42. package/dist/adapters/oclif/commands/score-groups/delete.mjs +1 -1
  43. package/dist/adapters/oclif/commands/score-groups/get.mjs +1 -1
  44. package/dist/adapters/oclif/commands/score-groups/list.mjs +1 -1
  45. package/dist/adapters/oclif/commands/score-groups/update.mjs +1 -1
  46. package/dist/adapters/oclif/commands/scores/create.mjs +1 -1
  47. package/dist/adapters/oclif/commands/scores/delete.mjs +1 -1
  48. package/dist/adapters/oclif/commands/scores/list.mjs +1 -1
  49. package/dist/adapters/oclif/commands/scores/update.mjs +1 -1
  50. package/dist/adapters/oclif/commands/segments/create.mjs +1 -1
  51. package/dist/adapters/oclif/commands/segments/delete.mjs +1 -1
  52. package/dist/adapters/oclif/commands/segments/evaluate.mjs +2 -2
  53. package/dist/adapters/oclif/commands/segments/get-evaluation-history.mjs +2 -2
  54. package/dist/adapters/oclif/commands/segments/get-version.mjs +2 -2
  55. package/dist/adapters/oclif/commands/segments/get.mjs +1 -1
  56. package/dist/adapters/oclif/commands/segments/list-versions.mjs +2 -2
  57. package/dist/adapters/oclif/commands/segments/list.mjs +1 -1
  58. package/dist/adapters/oclif/commands/segments/restore-version.mjs +2 -2
  59. package/dist/adapters/oclif/commands/segments/update.mjs +1 -1
  60. package/dist/adapters/oclif/commands/table-views/create.mjs +1 -1
  61. package/dist/adapters/oclif/commands/table-views/delete.mjs +1 -1
  62. package/dist/adapters/oclif/commands/table-views/list.mjs +1 -1
  63. package/dist/adapters/oclif/commands/table-views/update.mjs +1 -1
  64. package/dist/adapters/oclif/commands/tables/create.mjs +1 -1
  65. package/dist/adapters/oclif/commands/tables/delete.mjs +1 -1
  66. package/dist/adapters/oclif/commands/tables/get.mjs +1 -1
  67. package/dist/adapters/oclif/commands/tables/list.mjs +1 -1
  68. package/dist/adapters/oclif/commands/tables/update.mjs +1 -1
  69. package/dist/{base.command-DlVQ9Cqa.mjs → base.command-YiwlGlKs.mjs} +1 -1
  70. package/dist/{core-gKJ_V-K5.mjs → core-jpFPylBb.mjs} +31 -7
  71. package/dist/{factory-D9sR_S_g.mjs → factory-BrFKT8t-.mjs} +2 -2
  72. package/dist/{index-KAzwt5vr.d.mts → index-DD2Vr-pu.d.mts} +4 -1
  73. package/dist/index.d.mts +2 -2
  74. package/dist/index.mjs +2 -2
  75. package/dist/{presets-Cvazkjmu.mjs → presets-D9b6IWKy.mjs} +2 -2
  76. package/dist/templates/.devcontainer/devcontainer.json +2 -2
  77. package/dist/templates/AGENTS.md +19 -9
  78. package/dist/templates/skills/create-dbt-transformations/SKILL.md +21 -13
  79. package/dist/templates/skills/create-dbt-transformations/references/edge-cases.md +1 -1
  80. package/dist/templates/skills/create-semantic-model/SKILL.md +11 -11
  81. package/dist/templates/skills/create-semantic-model/references/cube-examples.md +83 -5
  82. package/dist/templates/skills/explore-lakehouse/SKILL.md +7 -3
  83. package/dist/templates/skills/load-sample-data/SKILL.md +119 -0
  84. package/package.json +7 -2
@@ -1,5 +1,5 @@
1
- import { m as unwrap } from "../../../../core-gKJ_V-K5.mjs";
2
- import { n as defineApiCommand } from "../../../../factory-D9sR_S_g.mjs";
1
+ import { m as unwrap } from "../../../../core-jpFPylBb.mjs";
2
+ import { n as defineApiCommand } from "../../../../factory-BrFKT8t-.mjs";
3
3
  import { Args } from "@oclif/core";
4
4
  //#region src/adapters/oclif/commands/segments/evaluate.ts
5
5
  var evaluate_default = defineApiCommand({
@@ -1,5 +1,5 @@
1
- import { m as unwrap } from "../../../../core-gKJ_V-K5.mjs";
2
- import { n as defineApiCommand } from "../../../../factory-D9sR_S_g.mjs";
1
+ import { m as unwrap } from "../../../../core-jpFPylBb.mjs";
2
+ import { n as defineApiCommand } from "../../../../factory-BrFKT8t-.mjs";
3
3
  import { Args } from "@oclif/core";
4
4
  //#region src/adapters/oclif/commands/segments/get-evaluation-history.ts
5
5
  var get_evaluation_history_default = defineApiCommand({
@@ -1,5 +1,5 @@
1
- import { m as unwrap } from "../../../../core-gKJ_V-K5.mjs";
2
- import { n as defineApiCommand } from "../../../../factory-D9sR_S_g.mjs";
1
+ import { m as unwrap } from "../../../../core-jpFPylBb.mjs";
2
+ import { n as defineApiCommand } from "../../../../factory-BrFKT8t-.mjs";
3
3
  import { Args } from "@oclif/core";
4
4
  //#region src/adapters/oclif/commands/segments/get-version.ts
5
5
  var get_version_default = defineApiCommand({
@@ -1,4 +1,4 @@
1
- import { r as getCommand } from "../../../../presets-Cvazkjmu.mjs";
1
+ import { r as getCommand } from "../../../../presets-D9b6IWKy.mjs";
2
2
  //#region src/adapters/oclif/commands/segments/get.ts
3
3
  var get_default = getCommand({
4
4
  resource: "segments",
@@ -1,5 +1,5 @@
1
- import { m as unwrap } from "../../../../core-gKJ_V-K5.mjs";
2
- import { n as defineApiCommand } from "../../../../factory-D9sR_S_g.mjs";
1
+ import { m as unwrap } from "../../../../core-jpFPylBb.mjs";
2
+ import { n as defineApiCommand } from "../../../../factory-BrFKT8t-.mjs";
3
3
  import { Args, Flags } from "@oclif/core";
4
4
  //#region src/adapters/oclif/commands/segments/list-versions.ts
5
5
  var list_versions_default = defineApiCommand({
@@ -1,4 +1,4 @@
1
- import { i as listCommand } from "../../../../presets-Cvazkjmu.mjs";
1
+ import { i as listCommand } from "../../../../presets-D9b6IWKy.mjs";
2
2
  //#region src/adapters/oclif/commands/segments/list.ts
3
3
  var list_default = listCommand({
4
4
  resource: "segments",
@@ -1,5 +1,5 @@
1
- import { m as unwrap } from "../../../../core-gKJ_V-K5.mjs";
2
- import { n as defineApiCommand } from "../../../../factory-D9sR_S_g.mjs";
1
+ import { m as unwrap } from "../../../../core-jpFPylBb.mjs";
2
+ import { n as defineApiCommand } from "../../../../factory-BrFKT8t-.mjs";
3
3
  import { Args } from "@oclif/core";
4
4
  //#region src/adapters/oclif/commands/segments/restore-version.ts
5
5
  var restore_version_default = defineApiCommand({
@@ -1,4 +1,4 @@
1
- import { a as updateCommand } from "../../../../presets-Cvazkjmu.mjs";
1
+ import { a as updateCommand } from "../../../../presets-D9b6IWKy.mjs";
2
2
  //#region src/adapters/oclif/commands/segments/update.ts
3
3
  var update_default = updateCommand({
4
4
  resource: "segments",
@@ -1,4 +1,4 @@
1
- import { t as createCommand } from "../../../../presets-Cvazkjmu.mjs";
1
+ import { t as createCommand } from "../../../../presets-D9b6IWKy.mjs";
2
2
  //#region src/adapters/oclif/commands/table-views/create.ts
3
3
  var create_default = createCommand({
4
4
  resource: "tableViews",
@@ -1,4 +1,4 @@
1
- import { n as deleteCommand } from "../../../../presets-Cvazkjmu.mjs";
1
+ import { n as deleteCommand } from "../../../../presets-D9b6IWKy.mjs";
2
2
  //#region src/adapters/oclif/commands/table-views/delete.ts
3
3
  var delete_default = deleteCommand({
4
4
  resource: "tableViews",
@@ -1,4 +1,4 @@
1
- import { i as listCommand } from "../../../../presets-Cvazkjmu.mjs";
1
+ import { i as listCommand } from "../../../../presets-D9b6IWKy.mjs";
2
2
  //#region src/adapters/oclif/commands/table-views/list.ts
3
3
  var list_default = listCommand({
4
4
  resource: "tableViews",
@@ -1,4 +1,4 @@
1
- import { a as updateCommand } from "../../../../presets-Cvazkjmu.mjs";
1
+ import { a as updateCommand } from "../../../../presets-D9b6IWKy.mjs";
2
2
  //#region src/adapters/oclif/commands/table-views/update.ts
3
3
  var update_default = updateCommand({
4
4
  resource: "tableViews",
@@ -1,4 +1,4 @@
1
- import { t as createCommand } from "../../../../presets-Cvazkjmu.mjs";
1
+ import { t as createCommand } from "../../../../presets-D9b6IWKy.mjs";
2
2
  //#region src/adapters/oclif/commands/tables/create.ts
3
3
  var create_default = createCommand({
4
4
  resource: "tables",
@@ -1,4 +1,4 @@
1
- import { n as deleteCommand } from "../../../../presets-Cvazkjmu.mjs";
1
+ import { n as deleteCommand } from "../../../../presets-D9b6IWKy.mjs";
2
2
  //#region src/adapters/oclif/commands/tables/delete.ts
3
3
  var delete_default = deleteCommand({
4
4
  resource: "tables",
@@ -1,4 +1,4 @@
1
- import { r as getCommand } from "../../../../presets-Cvazkjmu.mjs";
1
+ import { r as getCommand } from "../../../../presets-D9b6IWKy.mjs";
2
2
  //#region src/adapters/oclif/commands/tables/get.ts
3
3
  var get_default = getCommand({
4
4
  resource: "tables",
@@ -1,4 +1,4 @@
1
- import { i as listCommand } from "../../../../presets-Cvazkjmu.mjs";
1
+ import { i as listCommand } from "../../../../presets-D9b6IWKy.mjs";
2
2
  //#region src/adapters/oclif/commands/tables/list.ts
3
3
  var list_default = listCommand({
4
4
  resource: "tables",
@@ -1,4 +1,4 @@
1
- import { a as updateCommand } from "../../../../presets-Cvazkjmu.mjs";
1
+ import { a as updateCommand } from "../../../../presets-D9b6IWKy.mjs";
2
2
  //#region src/adapters/oclif/commands/tables/update.ts
3
3
  var update_default = updateCommand({
4
4
  resource: "tables",
@@ -1,4 +1,4 @@
1
- import { E as setClerkEnv, P as ApiError } from "./core-gKJ_V-K5.mjs";
1
+ import { D as setClerkEnv, F as ApiError } from "./core-jpFPylBb.mjs";
2
2
  import { Command, Flags } from "@oclif/core";
3
3
  import { makeTable } from "@oclif/table";
4
4
  //#region src/adapters/oclif/base.command.ts
@@ -21,6 +21,7 @@ var ApiError = class extends Error {
21
21
  //#region src/core/auth/credentials-store.ts
22
22
  const REVOS_DIR = path.join(os.homedir(), ".revos");
23
23
  const CREDENTIALS_FILE = path.join(REVOS_DIR, "credentials.json");
24
+ const isPosix = process.platform !== "win32";
24
25
  function getCredentialsPath() {
25
26
  return CREDENTIALS_FILE;
26
27
  }
@@ -34,11 +35,11 @@ function loadCredentials() {
34
35
  }
35
36
  }
36
37
  function saveCredentials(credentials) {
37
- if (!fs.existsSync(REVOS_DIR)) fs.mkdirSync(REVOS_DIR, { mode: 448 });
38
- fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), {
38
+ if (!fs.existsSync(REVOS_DIR)) fs.mkdirSync(REVOS_DIR, isPosix ? { mode: 448 } : void 0);
39
+ fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), isPosix ? {
39
40
  mode: 384,
40
41
  encoding: "utf-8"
41
- });
42
+ } : { encoding: "utf-8" });
42
43
  }
43
44
  function deleteCredentials() {
44
45
  try {
@@ -391,6 +392,26 @@ function findRemoteOnlyOverlays(localOverlays, remoteOverlays) {
391
392
  return remoteOverlays.filter((r) => !localNames.has(r.name));
392
393
  }
393
394
  //#endregion
395
+ //#region src/core/url.ts
396
+ const DEFAULT_APP_URL = "https://app.revos.dev";
397
+ /**
398
+ * Derive the RevOS app (frontend) URL from the API URL.
399
+ * `https://api.revos.ai` → `https://app.revos.ai`
400
+ * `https://api.revos.dev` → `https://app.revos.dev`
401
+ */
402
+ function resolveAppUrl(apiUrl) {
403
+ try {
404
+ const parsed = new URL(apiUrl);
405
+ const host = parsed.hostname;
406
+ if (host.startsWith("api.")) {
407
+ parsed.hostname = host.replace(/^api\./, "app.");
408
+ parsed.pathname = "/";
409
+ return parsed.origin;
410
+ }
411
+ } catch {}
412
+ return DEFAULT_APP_URL;
413
+ }
414
+ //#endregion
394
415
  //#region src/core/api/create-client.ts
395
416
  function createApiClient(config) {
396
417
  const headers = {};
@@ -817,6 +838,7 @@ var InitService = class InitService {
817
838
  ".claude/skills/create-semantic-model/references",
818
839
  ".claude/skills/create-dbt-transformations",
819
840
  ".claude/skills/create-dbt-transformations/references",
841
+ ".claude/skills/load-sample-data",
820
842
  "dbt/models/bronze",
821
843
  "dbt/models/silver",
822
844
  "dbt/models/gold",
@@ -841,6 +863,7 @@ var InitService = class InitService {
841
863
  ".claude/skills/create-dbt-transformations/references/sql-templates.md",
842
864
  ".claude/skills/create-dbt-transformations/references/schema-conventions.md",
843
865
  ".claude/skills/create-dbt-transformations/references/edge-cases.md",
866
+ ".claude/skills/load-sample-data/SKILL.md",
844
867
  "dbt/models/bronze/.gitkeep",
845
868
  "dbt/models/silver/.gitkeep",
846
869
  "dbt/models/gold/.gitkeep",
@@ -895,10 +918,10 @@ var InitService = class InitService {
895
918
  if (!keyJson) throw new Error("Service account key is empty");
896
919
  const gcpKeyPath = path.join(os.homedir(), ".revos", `${projectSlug}-gsa-creds.json`);
897
920
  fs.mkdirSync(path.dirname(gcpKeyPath), { recursive: true });
898
- fs.writeFileSync(gcpKeyPath, keyJson, {
921
+ fs.writeFileSync(gcpKeyPath, keyJson, process.platform !== "win32" ? {
899
922
  encoding: "utf-8",
900
923
  mode: 384
901
- });
924
+ } : { encoding: "utf-8" });
902
925
  return JSON.parse(keyJson).project_id ?? "";
903
926
  }
904
927
  dryRun(projectName, targetDir) {
@@ -948,6 +971,7 @@ var InitService = class InitService {
948
971
  ".claude/skills/create-dbt-transformations/references/sql-templates.md": this.renderTemplate("skills/create-dbt-transformations/references/sql-templates.md", {}),
949
972
  ".claude/skills/create-dbt-transformations/references/schema-conventions.md": this.renderTemplate("skills/create-dbt-transformations/references/schema-conventions.md", {}),
950
973
  ".claude/skills/create-dbt-transformations/references/edge-cases.md": this.renderTemplate("skills/create-dbt-transformations/references/edge-cases.md", {}),
974
+ ".claude/skills/load-sample-data/SKILL.md": this.renderTemplate("skills/load-sample-data/SKILL.md", {}),
951
975
  "dbt/models/bronze/.gitkeep": "",
952
976
  "dbt/models/silver/.gitkeep": "",
953
977
  "dbt/models/gold/.gitkeep": "",
@@ -956,7 +980,7 @@ var InitService = class InitService {
956
980
  for (const [rel, content] of Object.entries(files)) {
957
981
  const full = path.join(projectDir, rel);
958
982
  fs.writeFileSync(full, content, "utf-8");
959
- if (rel.endsWith(".sh")) fs.chmodSync(full, 493);
983
+ if (rel.endsWith(".sh") && process.platform !== "win32") fs.chmodSync(full, 493);
960
984
  created.push(rel);
961
985
  }
962
986
  return created;
@@ -970,4 +994,4 @@ var InitService = class InitService {
970
994
  }
971
995
  };
972
996
  //#endregion
973
- export { getCredentialsPath as A, getUserInfo as C, tokenResponseToCredentials as D, setClerkEnv as E, loadCredentials as M, saveCredentials as N, startOAuthServer as O, ApiError as P, generatePKCEChallenge as S, setClerkConfig as T, isContentEqual as _, PullService as a, buildAuthorizationUrl as b, loadOverlayFile as c, loadOverlaysFromDir as d, saveOverlayToFile as f, formatError as g, findRemoteOnlyOverlays as h, StatusService as i, isTokenExpired as j, deleteCredentials as k, loadOverlays as l, unwrap as m, selectOrganization as n, PushService as o, createApiClient as p, DiffService as r, getLocalOverlayNames as s, InitService as t, loadOverlaysByNames as u, sanitizeFileName as v, refreshAccessToken as w, exchangeCodeForTokens as x, getConfig as y };
997
+ export { deleteCredentials as A, generatePKCEChallenge as C, setClerkEnv as D, setClerkConfig as E, ApiError as F, isTokenExpired as M, loadCredentials as N, tokenResponseToCredentials as O, saveCredentials as P, exchangeCodeForTokens as S, refreshAccessToken as T, formatError as _, PullService as a, getConfig as b, loadOverlayFile as c, loadOverlaysFromDir as d, saveOverlayToFile as f, findRemoteOnlyOverlays as g, resolveAppUrl as h, StatusService as i, getCredentialsPath as j, startOAuthServer as k, loadOverlays as l, unwrap as m, selectOrganization as n, PushService as o, createApiClient as p, DiffService as r, getLocalOverlayNames as s, InitService as t, loadOverlaysByNames as u, isContentEqual as v, getUserInfo as w, buildAuthorizationUrl as x, sanitizeFileName as y };
@@ -1,5 +1,5 @@
1
- import { p as createApiClient, y as getConfig } from "./core-gKJ_V-K5.mjs";
2
- import { t as BaseCommand } from "./base.command-DlVQ9Cqa.mjs";
1
+ import { b as getConfig, p as createApiClient } from "./core-jpFPylBb.mjs";
2
+ import { t as BaseCommand } from "./base.command-YiwlGlKs.mjs";
3
3
  import * as fs from "fs";
4
4
  import * as path from "path";
5
5
  import chalk from "chalk";
@@ -80,6 +80,9 @@ declare function sanitizeFileName(name: string): string;
80
80
  declare function isContentEqual(local: OverlayFile, remote: CubeOverlay): boolean;
81
81
  declare function findRemoteOnlyOverlays(localOverlays: LoadedOverlay[], remoteOverlays: CubeOverlay[]): CubeOverlay[];
82
82
  //#endregion
83
+ //#region src/core/url.d.ts
84
+ declare function resolveAppUrl(apiUrl: string): string;
85
+ //#endregion
83
86
  //#region src/core/api/create-client.d.ts
84
87
  type ApiClient = Client;
85
88
  declare function createApiClient(config: {
@@ -187,4 +190,4 @@ declare class InitService {
187
190
  private renderTemplate;
188
191
  }
189
192
  //#endregion
190
- export { ClerkEnv as A, tokenResponseToCredentials as B, LoadedOverlay as C, loadOverlaysByNames as D, loadOverlays as E, generatePKCEChallenge as F, isTokenExpired as G, startOAuthServer as H, getUserInfo as I, getConfig as J, loadCredentials as K, refreshAccessToken as L, PKCEChallenge as M, buildAuthorizationUrl as N, loadOverlaysFromDir as O, exchangeCodeForTokens as P, setClerkConfig as R, sanitizeFileName as S, loadOverlayFile as T, deleteCredentials as U, OAuthServerResult as V, getCredentialsPath as W, ApiError as Y, createApiClient as _, DiffOptions as a, formatError as b, StatusOptions as c, PullOptions as d, PullService as f, ApiClient as g, PushService as h, DiffContext as i, ClerkOAuthConfig as j, saveOverlayToFile as k, StatusService as l, PushOptions as m, InitResult as n, DiffService as o, PushContext as p, saveCredentials as q, InitService as r, StatusContext as s, InitOptions as t, PullContext as u, unwrap as v, getLocalOverlayNames as w, isContentEqual as x, findRemoteOnlyOverlays as y, setClerkEnv as z };
193
+ export { saveOverlayToFile as A, setClerkEnv as B, sanitizeFileName as C, loadOverlays as D, loadOverlayFile as E, exchangeCodeForTokens as F, getCredentialsPath as G, OAuthServerResult as H, generatePKCEChallenge as I, saveCredentials as J, isTokenExpired as K, getUserInfo as L, ClerkOAuthConfig as M, PKCEChallenge as N, loadOverlaysByNames as O, buildAuthorizationUrl as P, refreshAccessToken as R, isContentEqual as S, getLocalOverlayNames as T, startOAuthServer as U, tokenResponseToCredentials as V, deleteCredentials as W, ApiError as X, getConfig as Y, createApiClient as _, DiffOptions as a, findRemoteOnlyOverlays as b, StatusOptions as c, PullOptions as d, PullService as f, ApiClient as g, PushService as h, DiffContext as i, ClerkEnv as j, loadOverlaysFromDir as k, StatusService as l, PushOptions as m, InitResult as n, DiffService as o, PushContext as p, loadCredentials as q, InitService as r, StatusContext as s, InitOptions as t, PullContext as u, unwrap as v, LoadedOverlay as w, formatError as x, resolveAppUrl as y, setClerkConfig as z };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
1
  import { a as DiffEntry, c as OverlayFileData, d as PushResult, f as StatusResult, i as DiffChange, l as OverlayStatusInfo, n as CubeDefinition, o as DiffResult, p as SyncStatus, r as CubeOverlay, s as OverlayFile, t as Config, u as PullResult } from "./types-C_p_6rkj.mjs";
2
- import { A as ClerkEnv, B as tokenResponseToCredentials, C as LoadedOverlay, D as loadOverlaysByNames, E as loadOverlays, F as generatePKCEChallenge, G as isTokenExpired, H as startOAuthServer, I as getUserInfo, J as getConfig, K as loadCredentials, L as refreshAccessToken, M as PKCEChallenge, N as buildAuthorizationUrl, O as loadOverlaysFromDir, P as exchangeCodeForTokens, R as setClerkConfig, S as sanitizeFileName, T as loadOverlayFile, U as deleteCredentials, V as OAuthServerResult, W as getCredentialsPath, Y as ApiError, _ as createApiClient, a as DiffOptions, b as formatError, c as StatusOptions, d as PullOptions, f as PullService, g as ApiClient, h as PushService, i as DiffContext, j as ClerkOAuthConfig, k as saveOverlayToFile, l as StatusService, m as PushOptions, n as InitResult, o as DiffService, p as PushContext, q as saveCredentials, r as InitService, s as StatusContext, t as InitOptions, u as PullContext, v as unwrap, w as getLocalOverlayNames, x as isContentEqual, y as findRemoteOnlyOverlays, z as setClerkEnv } from "./index-KAzwt5vr.mjs";
2
+ import { A as saveOverlayToFile, B as setClerkEnv, C as sanitizeFileName, D as loadOverlays, E as loadOverlayFile, F as exchangeCodeForTokens, G as getCredentialsPath, H as OAuthServerResult, I as generatePKCEChallenge, J as saveCredentials, K as isTokenExpired, L as getUserInfo, M as ClerkOAuthConfig, N as PKCEChallenge, O as loadOverlaysByNames, P as buildAuthorizationUrl, R as refreshAccessToken, S as isContentEqual, T as getLocalOverlayNames, U as startOAuthServer, V as tokenResponseToCredentials, W as deleteCredentials, X as ApiError, Y as getConfig, _ as createApiClient, a as DiffOptions, b as findRemoteOnlyOverlays, c as StatusOptions, d as PullOptions, f as PullService, g as ApiClient, h as PushService, i as DiffContext, j as ClerkEnv, k as loadOverlaysFromDir, l as StatusService, m as PushOptions, n as InitResult, o as DiffService, p as PushContext, q as loadCredentials, r as InitService, s as StatusContext, t as InitOptions, u as PullContext, v as unwrap, w as LoadedOverlay, x as formatError, y as resolveAppUrl, z as setClerkConfig } from "./index-DD2Vr-pu.mjs";
3
3
  import { a as OrgListResult, c as StoredCredentials, i as OAuthCallbackResult, l as TokenResponse, n as AuthStatusInfo, o as OrgSwitchResult, r as ClerkUserInfo, s as OrganizationInfo, t as AuthResult } from "./types-Y_ht_ja5.mjs";
4
- export { ApiClient, ApiError, AuthResult, AuthStatusInfo, ClerkEnv, ClerkOAuthConfig, ClerkUserInfo, Config, CubeDefinition, CubeOverlay, DiffChange, DiffContext, DiffEntry, DiffOptions, DiffResult, DiffService, InitOptions, InitResult, InitService, LoadedOverlay, OAuthCallbackResult, OAuthServerResult, OrgListResult, OrgSwitchResult, OrganizationInfo, OverlayFile, OverlayFileData, OverlayStatusInfo, PKCEChallenge, PullContext, PullOptions, PullResult, PullService, PushContext, PushOptions, PushResult, PushService, StatusContext, StatusOptions, StatusResult, StatusService, StoredCredentials, SyncStatus, TokenResponse, buildAuthorizationUrl, createApiClient, deleteCredentials, exchangeCodeForTokens, findRemoteOnlyOverlays, formatError, generatePKCEChallenge, getConfig, getCredentialsPath, getLocalOverlayNames, getUserInfo, isContentEqual, isTokenExpired, loadCredentials, loadOverlayFile, loadOverlays, loadOverlaysByNames, loadOverlaysFromDir, refreshAccessToken, sanitizeFileName, saveCredentials, saveOverlayToFile, setClerkConfig, setClerkEnv, startOAuthServer, tokenResponseToCredentials, unwrap };
4
+ export { ApiClient, ApiError, AuthResult, AuthStatusInfo, ClerkEnv, ClerkOAuthConfig, ClerkUserInfo, Config, CubeDefinition, CubeOverlay, DiffChange, DiffContext, DiffEntry, DiffOptions, DiffResult, DiffService, InitOptions, InitResult, InitService, LoadedOverlay, OAuthCallbackResult, OAuthServerResult, OrgListResult, OrgSwitchResult, OrganizationInfo, OverlayFile, OverlayFileData, OverlayStatusInfo, PKCEChallenge, PullContext, PullOptions, PullResult, PullService, PushContext, PushOptions, PushResult, PushService, StatusContext, StatusOptions, StatusResult, StatusService, StoredCredentials, SyncStatus, TokenResponse, buildAuthorizationUrl, createApiClient, deleteCredentials, exchangeCodeForTokens, findRemoteOnlyOverlays, formatError, generatePKCEChallenge, getConfig, getCredentialsPath, getLocalOverlayNames, getUserInfo, isContentEqual, isTokenExpired, loadCredentials, loadOverlayFile, loadOverlays, loadOverlaysByNames, loadOverlaysFromDir, refreshAccessToken, resolveAppUrl, sanitizeFileName, saveCredentials, saveOverlayToFile, setClerkConfig, setClerkEnv, startOAuthServer, tokenResponseToCredentials, unwrap };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { A as getCredentialsPath, C as getUserInfo, D as tokenResponseToCredentials, E as setClerkEnv, M as loadCredentials, N as saveCredentials, O as startOAuthServer, P as ApiError, S as generatePKCEChallenge, T as setClerkConfig, _ as isContentEqual, a as PullService, b as buildAuthorizationUrl, c as loadOverlayFile, d as loadOverlaysFromDir, f as saveOverlayToFile, g as formatError, h as findRemoteOnlyOverlays, i as StatusService, j as isTokenExpired, k as deleteCredentials, l as loadOverlays, m as unwrap, o as PushService, p as createApiClient, r as DiffService, s as getLocalOverlayNames, t as InitService, u as loadOverlaysByNames, v as sanitizeFileName, w as refreshAccessToken, x as exchangeCodeForTokens, y as getConfig } from "./core-gKJ_V-K5.mjs";
2
- export { ApiError, DiffService, InitService, PullService, PushService, StatusService, buildAuthorizationUrl, createApiClient, deleteCredentials, exchangeCodeForTokens, findRemoteOnlyOverlays, formatError, generatePKCEChallenge, getConfig, getCredentialsPath, getLocalOverlayNames, getUserInfo, isContentEqual, isTokenExpired, loadCredentials, loadOverlayFile, loadOverlays, loadOverlaysByNames, loadOverlaysFromDir, refreshAccessToken, sanitizeFileName, saveCredentials, saveOverlayToFile, setClerkConfig, setClerkEnv, startOAuthServer, tokenResponseToCredentials, unwrap };
1
+ import { A as deleteCredentials, C as generatePKCEChallenge, D as setClerkEnv, E as setClerkConfig, F as ApiError, M as isTokenExpired, N as loadCredentials, O as tokenResponseToCredentials, P as saveCredentials, S as exchangeCodeForTokens, T as refreshAccessToken, _ as formatError, a as PullService, b as getConfig, c as loadOverlayFile, d as loadOverlaysFromDir, f as saveOverlayToFile, g as findRemoteOnlyOverlays, h as resolveAppUrl, i as StatusService, j as getCredentialsPath, k as startOAuthServer, l as loadOverlays, m as unwrap, o as PushService, p as createApiClient, r as DiffService, s as getLocalOverlayNames, t as InitService, u as loadOverlaysByNames, v as isContentEqual, w as getUserInfo, x as buildAuthorizationUrl, y as sanitizeFileName } from "./core-jpFPylBb.mjs";
2
+ export { ApiError, DiffService, InitService, PullService, PushService, StatusService, buildAuthorizationUrl, createApiClient, deleteCredentials, exchangeCodeForTokens, findRemoteOnlyOverlays, formatError, generatePKCEChallenge, getConfig, getCredentialsPath, getLocalOverlayNames, getUserInfo, isContentEqual, isTokenExpired, loadCredentials, loadOverlayFile, loadOverlays, loadOverlaysByNames, loadOverlaysFromDir, refreshAccessToken, resolveAppUrl, sanitizeFileName, saveCredentials, saveOverlayToFile, setClerkConfig, setClerkEnv, startOAuthServer, tokenResponseToCredentials, unwrap };
@@ -1,5 +1,5 @@
1
- import { m as unwrap } from "./core-gKJ_V-K5.mjs";
2
- import { n as defineApiCommand, t as bodyFlag } from "./factory-D9sR_S_g.mjs";
1
+ import { m as unwrap } from "./core-jpFPylBb.mjs";
2
+ import { n as defineApiCommand, t as bodyFlag } from "./factory-BrFKT8t-.mjs";
3
3
  import { Args, Flags } from "@oclif/core";
4
4
  //#region src/adapters/oclif/presets.ts
5
5
  const listFlags = {
@@ -22,12 +22,12 @@
22
22
  "postCreateCommand": "bash .devcontainer/setup.sh",
23
23
  "mounts": [
24
24
  {
25
- "source": "${localEnv:HOME}/.revos/<%=projectSlug%>-gsa-creds.json",
25
+ "source": "${localEnv:HOME}${localEnv:USERPROFILE}/.revos/<%=projectSlug%>-gsa-creds.json",
26
26
  "target": "/tmp/.revos-gsa-creds.json",
27
27
  "type": "bind"
28
28
  },
29
29
  {
30
- "source": "${localEnv:HOME}/.revos/credentials.json",
30
+ "source": "${localEnv:HOME}${localEnv:USERPROFILE}/.revos/credentials.json",
31
31
  "target": "/tmp/.revos-credentials.json",
32
32
  "type": "bind"
33
33
  },
@@ -11,12 +11,22 @@ This is a RevOS data engineering project for **<%=orgName%>** organization.
11
11
 
12
12
  ## Key Commands
13
13
 
14
- | Command | Description |
15
- | --------------------- | -------------------------------- |
16
- | `revos auth login` | Authenticate with RevOS |
17
- | `revos overlays push` | Push Cube overlays to RevOS |
18
- | `revos overlays pull` | Pull Cube overlays from RevOS |
19
- | `revos overlays diff` | Compare local vs remote overlays |
20
- | `dbt run` | Run dbt models |
21
- | `dbt test` | Test dbt models |
22
- | `bq ls` | List BigQuery datasets/tables |
14
+ | Command | Description |
15
+ | -------------------------------- | ------------------------------------- |
16
+ | `revos auth login` | Authenticate with RevOS |
17
+ | `revos overlays push` | Push Cube overlays to RevOS |
18
+ | `revos overlays pull` | Pull Cube overlays from RevOS |
19
+ | `revos overlays diff` | Compare local vs remote overlays |
20
+ | `revos integrations list` | Open integrations page in RevOS UI |
21
+ | `revos integrations create` | Open RevOS UI to add a data source |
22
+ | `revos integrations update <id>` | Open RevOS UI to edit an integration |
23
+ | `dbt run` | Run dbt models |
24
+ | `dbt test` | Test dbt models |
25
+ | `bq ls` | List BigQuery datasets/tables |
26
+
27
+ ## Data Sources
28
+
29
+ When the user wants to add or manage data sources (integrations):
30
+ - To add a new data source: suggest `revos integrations create`
31
+ - To view existing integrations: suggest `revos integrations list`
32
+ - If a BigQuery dataset is empty or has no tables, mention that integrations can be configured with `revos integrations create`, or sample data can be loaded if available
@@ -5,7 +5,7 @@ description: Create new dbt transformations (bronze/silver/gold models) in the R
5
5
 
6
6
  # Create dbt Transformations
7
7
 
8
- Use this skill to generate SQL models, declare sources, update `schema.yml`, and validate models with `revos dbt run` / `revos dbt test`.
8
+ Use this skill to generate SQL models, declare sources, update `schema.yml`, and validate models with `dbt run` / `dbt test`.
9
9
 
10
10
  For BigQuery exploration (listing datasets, inspecting raw tables, previewing rows, null rates), load the `explore-lakehouse` skill. If that skill is not installed, fall back to:
11
11
 
@@ -72,15 +72,13 @@ Always declare raw tables as sources before referencing them. Do not use bare fu
72
72
 
73
73
  ## Standard dbt Commands
74
74
 
75
- Always use the `revos` wrapper:
76
-
77
75
  ```bash
78
- revos dbt parse # validate syntax (no warehouse)
79
- revos dbt compile --select <model> # resolve refs, produce compiled SQL
80
- revos dbt run --select <model> # execute against warehouse
81
- revos dbt test --select <model> # run tests
82
- revos dbt build --select <model> # run + test
83
- revos dbt build --select path:models/<layer> # entire layer
76
+ dbt parse # validate syntax (no warehouse)
77
+ dbt compile --select <model> # resolve refs, produce compiled SQL
78
+ dbt run --select <model> # execute against warehouse
79
+ dbt test --select <model> # run tests
80
+ dbt build --select <model> # run + test
81
+ dbt build --select path:models/<layer> # entire layer
84
82
  ```
85
83
 
86
84
  ---
@@ -99,8 +97,8 @@ For each transformation (one at a time — do not batch):
99
97
  6. Generate `dbt/models/<layer>/<model_name>.sql`.
100
98
  7. Detect the primary key (Checkpoint 3 if ambiguous).
101
99
  8. Add model entry to `dbt/models/<layer>/schema.yml` with PK and FK tests. See [schema-conventions.md](references/schema-conventions.md).
102
- 9. Run `revos dbt run --select <model_name>` and report result.
103
- 10. Run `revos dbt test --select <model_name>` and report result.
100
+ 9. Run `dbt run --select <model_name>` and report result.
101
+ 10. Run `dbt test --select <model_name>` and report result.
104
102
  11. Summarize (see Final Response Format).
105
103
 
106
104
  For multiple transformations in one request: repeat steps 1–11 per model in order.
@@ -169,6 +167,16 @@ If none produce a clear answer → Checkpoint 3.
169
167
 
170
168
  A column is a FK candidate if it matches `<entity>_id` where `<entity>` ≠ model's own entity, is not part of the PK, and is not nullable by design. Add `not_null` test only (no `relationships` tests by default).
171
169
 
170
+ ## Timestamp Column Propagation (Gold Models)
171
+
172
+ Every gold model **must** propagate at least one timestamp column so downstream Cube overlays can use SQL-based `refresh_key` (see `create-semantic-model` skill). Priority:
173
+
174
+ 1. `_airbyte_extracted_at` — present on all Airbyte sources; always propagate if available in upstream.
175
+ 2. `updated_at` / `modified_at` — CDC-friendly streams.
176
+ 3. `created_at` — insert-only fact tables.
177
+
178
+ If the upstream source has none of these, document it in a SQL comment: `-- no timestamp column available from source`.
179
+
172
180
  ## SQL File Generation
173
181
 
174
182
  See [sql-templates.md](references/sql-templates.md) for:
@@ -206,8 +214,8 @@ Tests:
206
214
  - not_null on <fk>: added
207
215
 
208
216
  Validation:
209
- - revos dbt run: passed | failed
210
- - revos dbt test: passed | failed
217
+ - dbt run: passed | failed
218
+ - dbt test: passed | failed
211
219
 
212
220
  Physical table after run:
213
221
  `<resolved_dataset>.<model_name>`
@@ -34,7 +34,7 @@ Declare it as a source in `dbt/models/bronze/schema.yml` first (see [schema-conv
34
34
 
35
35
  1. Show the error verbatim — do not paraphrase warehouse errors.
36
36
  2. Offer to fix the SQL based on the error message.
37
- 3. Do not proceed to `revos dbt test` until run succeeds.
37
+ 3. Do not proceed to `dbt test` until run succeeds.
38
38
 
39
39
  ## test fails
40
40
 
@@ -15,8 +15,7 @@ project layout, finding gold models, resolving dbt model names to BigQuery table
15
15
  creating bridge models from JSON arrays, and dbt validation commands.
16
16
 
17
17
  If `create-dbt-transformations` is not installed: discover gold models directly via
18
- `find dbt/models/gold -name "*.sql"`, run dbt commands without the `revos` wrapper
19
- (`dbt parse`, `dbt run`, `dbt test`), and skip bridge model delegation.
18
+ `find dbt/models/gold -name "*.sql"` and skip bridge model delegation.
20
19
  Warn the user: "The `create-dbt-transformations` skill is not installed — bridge model creation and dbt validation are limited."
21
20
 
22
21
  If BigQuery exploration is needed (listing tables, inspecting schemas, previewing rows),
@@ -95,7 +94,7 @@ Follow these phases in order. Do not skip ahead.
95
94
 
96
95
  1. Discover gold models via `find dbt/models/gold -name "*.sql"`.
97
96
  2. If none exist, stop and tell the user to create gold models first via `create-dbt-transformations`.
98
- 3. Inspect 1-2 existing overlays in `semantic/` to detect conventions (map-style vs list-style, `extends:`, `public:`, `refresh_key` style). Apply detected conventions to new overlays.
97
+ 3. Inspect 1-2 existing overlays in `semantic/` to detect conventions (`extends:`, `public:`, `refresh_key` style). Apply detected conventions to new overlays. Always use flat single-cube YAML (never `cubes:` or `views:` root).
99
98
  4. If the user named a specific model, find it. If not found, stop.
100
99
  5. Otherwise list all discovered gold models and ask which should participate (Checkpoint 1).
101
100
  6. Keep the full discovered list available for connector search in Phase 3.
@@ -238,13 +237,14 @@ Create Cube.dev YAML files in `semantic/`. Follow the existing style detected in
238
237
 
239
238
  Key rules:
240
239
 
241
- 1. File name = cube `name` (no `gold_` prefix) + `.yml`.
242
- 2. `sql_table` uses fully qualified BigQuery reference with `gold_` prefix.
243
- 3. Every confirmed relationship gets joins in both directions.
244
- 4. Bridge/junction cubes use `public: false`.
245
- 5. Every overlay includes `refresh_key`. Prefer `_airbyte_extracted_at`, fall back to other timestamps, then `every: 1 hour`.
246
- 6. `refresh_key.sql` references the same table as `sql_table`.
247
- 7. Tag unvalidated joins with `# UNVALIDATED: <reason>`.
240
+ 1. **One cube per file, flat YAML.** Each overlay file contains a single cube starting with `name:` at the root level. Never wrap with `cubes:` or `views:` at the root.
241
+ 2. File name = cube `name` (no `gold_` prefix) + `.yml`.
242
+ 3. `sql_table` uses fully qualified BigQuery reference with `gold_` prefix.
243
+ 4. Every confirmed relationship gets joins in both directions.
244
+ 5. Bridge/junction cubes use `public: false`.
245
+ 6. Every overlay **must** include a SQL-based `refresh_key`. Use `SELECT MAX(<timestamp_col>)` with columns in this priority: `_airbyte_extracted_at` (present on all Airbyte sources), `updated_at`/`modified_at` (CDC streams), `created_at` (insert-only facts). Only use `every: <interval>` as absolute last resort when **no timestamp column exists in the table** add a YAML comment explaining why (e.g. `# no timestamp column available`).
246
+ 7. `refresh_key.sql` references the same table as `sql_table`.
247
+ 8. Tag unvalidated joins with `# UNVALIDATED: <reason>`.
248
248
 
249
249
  See [references/cube-examples.md](references/cube-examples.md) for canonical standard cube, bridge cube, join direction examples, and refresh key variants.
250
250
 
@@ -252,7 +252,7 @@ See [references/cube-examples.md](references/cube-examples.md) for canonical sta
252
252
 
253
253
  ## Phase 9: Validate Generated Files
254
254
 
255
- 1. If `create-dbt-transformations` was invoked (bridge model), it already validated dbt models. Otherwise run `revos dbt parse`.
255
+ 1. If `create-dbt-transformations` was invoked (bridge model), it already validated dbt models. Otherwise run `dbt parse`.
256
256
  2. Verify physical tables exist in BigQuery: `bq show <dataset>.<table_name>`. If missing, document as pending.
257
257
  3. Verify generated overlays match conventions: flat YAML, correct naming, correct `sql_table`, all dimensions present, `refresh_key` included, joins in both directions.
258
258
 
@@ -9,6 +9,7 @@
9
9
  - [Refresh Key Variants](#refresh-key-variants)
10
10
  - [Type Mapping](#type-mapping)
11
11
  - [Measure Suggestions](#measure-suggestions)
12
+ - [Common Mistakes](#common-mistakes)
12
13
 
13
14
  ---
14
15
 
@@ -215,24 +216,68 @@ joins:
215
216
 
216
217
  ## Refresh Key Variants
217
218
 
218
- Priority order:
219
+ SQL-based refresh keys are **required**. They ensure caches invalidate only when data actually changes, instead of on a fixed timer.
220
+
221
+ Priority — use the first available timestamp column:
219
222
 
220
223
  ```yaml
221
- # 1. Airbyte timestamp (preferred)
224
+ # 1. Airbyte timestamp (preferred — present on all Airbyte sources)
222
225
  refresh_key:
223
226
  sql: "SELECT MAX(_airbyte_extracted_at) FROM `<dataset>.<gold_model>`"
224
227
 
225
- # 2. Other reliable timestamp
228
+ # 2. CDC / update timestamp
226
229
  refresh_key:
227
230
  sql: "SELECT MAX(updated_at) FROM `<dataset>.<gold_model>`"
228
231
 
229
- # 3. Default fallback
232
+ # 3. Insert-only fact table
230
233
  refresh_key:
231
- every: 1 hour
234
+ sql: "SELECT MAX(created_at) FROM `<dataset>.<gold_model>`"
235
+
236
+ # 4. Last resort — ONLY when the table has no timestamp column at all
237
+ # Add a comment explaining why:
238
+ refresh_key:
239
+ every: 1 hour # no timestamp column available in this table
232
240
  ```
233
241
 
234
242
  `refresh_key.sql` must reference the same fully qualified table as the cube's `sql_table`.
235
243
 
244
+ ### Common Mistakes
245
+
246
+ ```yaml
247
+ # BAD — fixed cadence, ignores actual data changes.
248
+ # This forces cache rebuild every hour even when data hasn't changed.
249
+ refresh_key:
250
+ every: 1 hour
251
+
252
+ # GOOD — only invalidates when new rows arrive
253
+ refresh_key:
254
+ sql: "SELECT MAX(_airbyte_extracted_at) FROM `<dataset>.<gold_model>`"
255
+ ```
256
+
257
+ ```yaml
258
+ # BAD — omitting refresh_key entirely (cube uses default, which may be too aggressive)
259
+ name: my_cube
260
+ sql_table: ...
261
+
262
+ # GOOD — always include an explicit refresh_key
263
+ name: my_cube
264
+ sql_table: ...
265
+ refresh_key:
266
+ sql: "SELECT MAX(_airbyte_extracted_at) FROM `<dataset>.<gold_model>`"
267
+ ```
268
+
269
+ ```yaml
270
+ # BAD — refresh_key.sql references a different table than sql_table
271
+ sql_table: "`project.dataset.gold_orders`"
272
+ refresh_key:
273
+ sql: "SELECT MAX(_airbyte_extracted_at) FROM `project.dataset.gold_customers`"
274
+
275
+ # GOOD — same table in both
276
+ sql_table: "`project.dataset.gold_orders`"
277
+ refresh_key:
278
+ sql: "SELECT MAX(_airbyte_extracted_at) FROM `project.dataset.gold_orders`"
279
+ ```
280
+
236
281
  ---
237
282
 
238
283
  ## Type Mapping
@@ -265,3 +310,36 @@ updated_at -> last_updated_at (max)
265
310
  ```
266
311
 
267
312
  `count_distinct` on FK columns: define inside the cube that owns the FK, not the parent cube. Joins can produce row fan-out that distorts distinct counts.
313
+
314
+ ---
315
+
316
+ ## Common Mistakes
317
+
318
+ ### Wrapping with `cubes:` or `views:` at the root
319
+
320
+ RevOS expects one cube per file with flat YAML — `name:` at the root level. The Cube.dev docs show a multi-cube `cubes:` list format, but RevOS does not use it.
321
+
322
+ ```yaml
323
+ # BAD — multi-cube list format
324
+ cubes:
325
+ - name: hubspot_companies
326
+ sql_table: "`dataset.gold_hubspot_companies`"
327
+ dimensions:
328
+ id:
329
+ sql: "${CUBE}.id"
330
+ type: string
331
+ primary_key: true
332
+ ```
333
+
334
+ ```yaml
335
+ # GOOD — flat single-cube format
336
+ name: hubspot_companies
337
+ sql_table: "`dataset.gold_hubspot_companies`"
338
+ dimensions:
339
+ id:
340
+ sql: "${CUBE}.id"
341
+ type: string
342
+ primary_key: true
343
+ ```
344
+
345
+ Same applies to `views:` — never use it as a root key.