@revos/cli 0.1.2 → 0.1.3

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 (33) hide show
  1. package/README.md +17 -13
  2. package/dist/adapters/oclif/commands/auth/login.mjs +2 -2
  3. package/dist/adapters/oclif/commands/auth/logout.mjs +2 -2
  4. package/dist/adapters/oclif/commands/auth/status.mjs +2 -2
  5. package/dist/adapters/oclif/commands/init.mjs +2 -2
  6. package/dist/adapters/oclif/commands/org/current.mjs +2 -2
  7. package/dist/adapters/oclif/commands/org/list.mjs +2 -2
  8. package/dist/adapters/oclif/commands/org/switch.mjs +2 -2
  9. package/dist/adapters/oclif/commands/overlays/diff.d.mts +1 -1
  10. package/dist/adapters/oclif/commands/overlays/diff.mjs +3 -3
  11. package/dist/adapters/oclif/commands/overlays/pull.d.mts +1 -1
  12. package/dist/adapters/oclif/commands/overlays/pull.mjs +3 -3
  13. package/dist/adapters/oclif/commands/overlays/push.d.mts +1 -1
  14. package/dist/adapters/oclif/commands/overlays/push.mjs +3 -3
  15. package/dist/adapters/oclif/commands/overlays/status.d.mts +1 -1
  16. package/dist/adapters/oclif/commands/overlays/status.mjs +3 -3
  17. package/dist/{base.command-DDSLyx5v.mjs → base.command-CaFn9EwG.mjs} +1 -1
  18. package/dist/{core-EJgxP-x5.mjs → core-BSLZ9hQU.mjs} +42 -17
  19. package/dist/{index-DH6vy050.d.mts → index-B8n2GxTc.d.mts} +1 -1
  20. package/dist/index.d.mts +3 -3
  21. package/dist/index.mjs +1 -1
  22. package/dist/templates/AGENTS.md +1 -1
  23. package/dist/templates/skills/create-dbt-transformations/SKILL.md +214 -0
  24. package/dist/templates/skills/create-dbt-transformations/references/edge-cases.md +46 -0
  25. package/dist/templates/skills/create-dbt-transformations/references/schema-conventions.md +128 -0
  26. package/dist/templates/skills/create-dbt-transformations/references/sql-templates.md +73 -0
  27. package/dist/templates/skills/create-semantic-model/SKILL.md +126 -1432
  28. package/dist/templates/skills/create-semantic-model/references/cube-examples.md +267 -0
  29. package/dist/templates/skills/create-semantic-model/references/key-patterns.md +150 -0
  30. package/dist/templates/skills/create-semantic-model/references/validation-queries.md +209 -0
  31. package/dist/templates/skills/explore-lakehouse/SKILL.md +8 -1
  32. package/dist/{types-DZssnweO.d.mts → types-DmuJzN0Z.d.mts} +5 -1
  33. package/package.json +2 -1
package/README.md CHANGED
@@ -61,7 +61,7 @@ If the destination already exists and is not empty, you will be prompted to conf
61
61
  4. Generates `.devcontainer/devcontainer.json` with Python 3.11, Node.js, Google Cloud SDK (`bq`), and dbt pre-installed. The Dev Container mounts `~/.revos/{project-name}-gsa-creds.json` automatically.
62
62
  5. Generates `dbt/profiles.yml` pre-configured for BigQuery.
63
63
  6. Generates `.gitignore` and `README.md`.
64
- 7. Scaffolds AI companion files: `CLAUDE.md`, `AGENTS.md`, and `.claude/skills/`.
64
+ 7. Scaffolds AI companion files: `CLAUDE.md`, `AGENTS.md`, and `.claude/skills/` with three pre-installed skills: `explore-lakehouse`, `create-semantic-model`, and `create-dbt-transformations`.
65
65
 
66
66
  **Requires:** `revos auth login` first.
67
67
 
@@ -117,18 +117,22 @@ revos org switch <org-id>
117
117
 
118
118
  ### Overlays Management
119
119
 
120
- Overlay files contain only Cube definition data (dimensions, measures, joins, etc.). The overlay name is derived from the filename (without `.json`). A `description` field in the file is synced with the overlay's description on push.
120
+ Overlay files are Cube.dev semantic model definitions stored as YAML in `semantic/`. The overlay name is taken from the `name` field inside the file (or derived from the filename as a fallback). A `description` field is synced with the overlay's description on push.
121
121
 
122
- Example `my-overlay.json`:
122
+ Example `semantic/my_table.yml`:
123
123
 
124
- ```json
125
- {
126
- "description": "My custom overlay",
127
- "sql_table": "my_table",
128
- "dimensions": {
129
- "id": { "sql": "id", "type": "number", "primary_key": true }
130
- }
131
- }
124
+ ```yaml
125
+ name: my_table
126
+ description: My custom overlay
127
+ sql_table: my_table
128
+ dimensions:
129
+ id:
130
+ sql: "${CUBE}.id"
131
+ type: number
132
+ primary_key: true
133
+ measures:
134
+ count:
135
+ type: count
132
136
  ```
133
137
 
134
138
  #### Push overlays to API
@@ -137,7 +141,7 @@ Example `my-overlay.json`:
137
141
  revos overlays push [files...] [-d <dir>] [-f] [--json]
138
142
 
139
143
  Options:
140
- -d, --dir <path> Directory containing overlay files (default: "./overlays")
144
+ -d, --dir <path> Directory containing overlay files (default: "./semantic")
141
145
  -f, --force Force push even if remote is newer
142
146
  ```
143
147
 
@@ -147,7 +151,7 @@ Options:
147
151
  revos overlays pull [-d <dir>] [-n <name>...] [--json]
148
152
 
149
153
  Options:
150
- -d, --dir <path> Directory to save overlay files (default: "./overlays")
154
+ -d, --dir <path> Directory to save overlay files (default: "./semantic")
151
155
  -n, --name <name> Specific overlay names to pull (repeatable)
152
156
  ```
153
157
 
@@ -1,5 +1,5 @@
1
- import { A as getCredentialsPath, C as getUserInfo, D as tokenResponseToCredentials, N as saveCredentials, O as startOAuthServer, S as generatePKCEChallenge, T as setClerkConfig, b as buildAuthorizationUrl, m as unwrap, n as selectOrganization, p as createApiClient, x as exchangeCodeForTokens } from "../../../../core-EJgxP-x5.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DDSLyx5v.mjs";
1
+ import { A as getCredentialsPath, C as getUserInfo, D as tokenResponseToCredentials, N as saveCredentials, O as startOAuthServer, S as generatePKCEChallenge, T as setClerkConfig, b as buildAuthorizationUrl, m as unwrap, n as selectOrganization, p as createApiClient, x as exchangeCodeForTokens } from "../../../../core-BSLZ9hQU.mjs";
2
+ import { t as BaseCommand } from "../../../../base.command-CaFn9EwG.mjs";
3
3
  import chalk from "chalk";
4
4
  import { Flags } from "@oclif/core";
5
5
  import open from "open";
@@ -1,5 +1,5 @@
1
- import { k as deleteCredentials } from "../../../../core-EJgxP-x5.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DDSLyx5v.mjs";
1
+ import { k as deleteCredentials } from "../../../../core-BSLZ9hQU.mjs";
2
+ import { t as BaseCommand } from "../../../../base.command-CaFn9EwG.mjs";
3
3
  //#region src/adapters/oclif/commands/auth/logout.ts
4
4
  var AuthLogout = class extends BaseCommand {
5
5
  static description = "Remove stored authentication credentials";
@@ -1,5 +1,5 @@
1
- import { A as getCredentialsPath, M as loadCredentials, j as isTokenExpired } from "../../../../core-EJgxP-x5.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DDSLyx5v.mjs";
1
+ import { A as getCredentialsPath, M as loadCredentials, j as isTokenExpired } from "../../../../core-BSLZ9hQU.mjs";
2
+ import { t as BaseCommand } from "../../../../base.command-CaFn9EwG.mjs";
3
3
  import chalk from "chalk";
4
4
  //#region src/adapters/oclif/commands/auth/status.ts
5
5
  var AuthStatus = class extends BaseCommand {
@@ -1,6 +1,6 @@
1
- import { P as ApiError, t as InitService, y as getConfig } from "../../../core-EJgxP-x5.mjs";
1
+ import { P as ApiError, t as InitService, y as getConfig } from "../../../core-BSLZ9hQU.mjs";
2
2
  import { TEMPLATES_DIR } from "../../../templates/index.mjs";
3
- import { t as BaseCommand } from "../../../base.command-DDSLyx5v.mjs";
3
+ import { t as BaseCommand } from "../../../base.command-CaFn9EwG.mjs";
4
4
  import * as fs from "fs";
5
5
  import * as path from "path";
6
6
  import select from "@inquirer/select";
@@ -1,5 +1,5 @@
1
- import { M as loadCredentials, m as unwrap, p as createApiClient, y as getConfig } from "../../../../core-EJgxP-x5.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DDSLyx5v.mjs";
1
+ import { M as loadCredentials, m as unwrap, p as createApiClient, y as getConfig } from "../../../../core-BSLZ9hQU.mjs";
2
+ import { t as BaseCommand } from "../../../../base.command-CaFn9EwG.mjs";
3
3
  //#region src/adapters/oclif/commands/org/current.ts
4
4
  var OrgCurrent = class extends BaseCommand {
5
5
  static description = "Show currently selected organization";
@@ -1,5 +1,5 @@
1
- import { m as unwrap, p as createApiClient, y as getConfig } from "../../../../core-EJgxP-x5.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DDSLyx5v.mjs";
1
+ import { m as unwrap, p as createApiClient, y as getConfig } from "../../../../core-BSLZ9hQU.mjs";
2
+ import { t as BaseCommand } from "../../../../base.command-CaFn9EwG.mjs";
3
3
  import chalk from "chalk";
4
4
  import { Flags } from "@oclif/core";
5
5
  //#region src/adapters/oclif/commands/org/list.ts
@@ -1,5 +1,5 @@
1
- import { M as loadCredentials, N as saveCredentials, m as unwrap, n as selectOrganization, p as createApiClient, y as getConfig } from "../../../../core-EJgxP-x5.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DDSLyx5v.mjs";
1
+ import { M as loadCredentials, N as saveCredentials, m as unwrap, n as selectOrganization, p as createApiClient, y as getConfig } from "../../../../core-BSLZ9hQU.mjs";
2
+ import { t as BaseCommand } from "../../../../base.command-CaFn9EwG.mjs";
3
3
  import chalk from "chalk";
4
4
  import { Args } from "@oclif/core";
5
5
  //#region src/adapters/oclif/commands/org/switch.ts
@@ -1,4 +1,4 @@
1
- import { o as DiffResult } from "../../../../types-DZssnweO.mjs";
1
+ import { o as DiffResult } from "../../../../types-DmuJzN0Z.mjs";
2
2
  import { t as BaseCommand } from "../../../../base.command-BjFWMIzL.mjs";
3
3
  import * as _$_oclif_core_interfaces0 from "@oclif/core/interfaces";
4
4
 
@@ -1,5 +1,5 @@
1
- import { p as createApiClient, r as DiffService, y as getConfig } from "../../../../core-EJgxP-x5.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DDSLyx5v.mjs";
1
+ import { p as createApiClient, r as DiffService, y as getConfig } from "../../../../core-BSLZ9hQU.mjs";
2
+ import { t as BaseCommand } from "../../../../base.command-CaFn9EwG.mjs";
3
3
  import chalk from "chalk";
4
4
  import { Args, Flags } from "@oclif/core";
5
5
  //#region src/adapters/oclif/commands/overlays/diff.ts
@@ -8,7 +8,7 @@ var OverlaysDiff = class extends BaseCommand {
8
8
  static flags = { dir: Flags.string({
9
9
  char: "d",
10
10
  description: "Directory containing overlay files",
11
- default: "./overlays"
11
+ default: "./semantic"
12
12
  }) };
13
13
  static args = { files: Args.string({
14
14
  description: "Specific files to diff (optional)",
@@ -1,4 +1,4 @@
1
- import { l as PullResult } from "../../../../types-DZssnweO.mjs";
1
+ import { u as PullResult } from "../../../../types-DmuJzN0Z.mjs";
2
2
  import { t as BaseCommand } from "../../../../base.command-BjFWMIzL.mjs";
3
3
  import * as _$_oclif_core_interfaces0 from "@oclif/core/interfaces";
4
4
 
@@ -1,5 +1,5 @@
1
- import { a as PullService, p as createApiClient, y as getConfig } from "../../../../core-EJgxP-x5.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DDSLyx5v.mjs";
1
+ import { a as PullService, p as createApiClient, y as getConfig } from "../../../../core-BSLZ9hQU.mjs";
2
+ import { t as BaseCommand } from "../../../../base.command-CaFn9EwG.mjs";
3
3
  import chalk from "chalk";
4
4
  import { Flags } from "@oclif/core";
5
5
  //#region src/adapters/oclif/commands/overlays/pull.ts
@@ -9,7 +9,7 @@ var OverlaysPull = class extends BaseCommand {
9
9
  dir: Flags.string({
10
10
  char: "d",
11
11
  description: "Directory to save overlay files",
12
- default: "./overlays"
12
+ default: "./semantic"
13
13
  }),
14
14
  name: Flags.string({
15
15
  char: "n",
@@ -1,4 +1,4 @@
1
- import { u as PushResult } from "../../../../types-DZssnweO.mjs";
1
+ import { d as PushResult } from "../../../../types-DmuJzN0Z.mjs";
2
2
  import { t as BaseCommand } from "../../../../base.command-BjFWMIzL.mjs";
3
3
  import * as _$_oclif_core_interfaces0 from "@oclif/core/interfaces";
4
4
 
@@ -1,5 +1,5 @@
1
- import { o as PushService, p as createApiClient, y as getConfig } from "../../../../core-EJgxP-x5.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DDSLyx5v.mjs";
1
+ import { o as PushService, p as createApiClient, y as getConfig } from "../../../../core-BSLZ9hQU.mjs";
2
+ import { t as BaseCommand } from "../../../../base.command-CaFn9EwG.mjs";
3
3
  import chalk from "chalk";
4
4
  import { Args, Flags } from "@oclif/core";
5
5
  //#region src/adapters/oclif/commands/overlays/push.ts
@@ -9,7 +9,7 @@ var OverlaysPush = class extends BaseCommand {
9
9
  dir: Flags.string({
10
10
  char: "d",
11
11
  description: "Directory containing overlay files",
12
- default: "./overlays"
12
+ default: "./semantic"
13
13
  }),
14
14
  force: Flags.boolean({
15
15
  char: "f",
@@ -1,4 +1,4 @@
1
- import { d as StatusResult } from "../../../../types-DZssnweO.mjs";
1
+ import { f as StatusResult } from "../../../../types-DmuJzN0Z.mjs";
2
2
  import { t as BaseCommand } from "../../../../base.command-BjFWMIzL.mjs";
3
3
  import * as _$_oclif_core_interfaces0 from "@oclif/core/interfaces";
4
4
 
@@ -1,5 +1,5 @@
1
- import { i as StatusService, p as createApiClient, y as getConfig } from "../../../../core-EJgxP-x5.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DDSLyx5v.mjs";
1
+ import { i as StatusService, p as createApiClient, y as getConfig } from "../../../../core-BSLZ9hQU.mjs";
2
+ import { t as BaseCommand } from "../../../../base.command-CaFn9EwG.mjs";
3
3
  import chalk from "chalk";
4
4
  import { Args, Flags } from "@oclif/core";
5
5
  //#region src/adapters/oclif/commands/overlays/status.ts
@@ -17,7 +17,7 @@ var OverlaysStatus = class extends BaseCommand {
17
17
  dir: Flags.string({
18
18
  char: "d",
19
19
  description: "Directory containing overlay files",
20
- default: "./overlays"
20
+ default: "./semantic"
21
21
  }),
22
22
  columns: Flags.string({
23
23
  description: `Columns to display (comma-separated). Available: ${ALL_COLUMNS.join(", ")}`,
@@ -1,4 +1,4 @@
1
- import { E as setClerkEnv, P as ApiError } from "./core-EJgxP-x5.mjs";
1
+ import { E as setClerkEnv, P as ApiError } from "./core-BSLZ9hQU.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
@@ -4,6 +4,7 @@ import * as os from "os";
4
4
  import { createServer } from "node:http";
5
5
  import * as crypto from "crypto";
6
6
  import { Client, client } from "@revos/api-client";
7
+ import { parse, stringify } from "yaml";
7
8
  import search from "@inquirer/search";
8
9
  //#region src/core/errors.ts
9
10
  var ApiError = class extends Error {
@@ -423,20 +424,20 @@ function loadOverlayFile(filePath) {
423
424
  const absolutePath = path.resolve(filePath);
424
425
  const stats = fs.statSync(absolutePath);
425
426
  const content = fs.readFileSync(absolutePath, "utf-8");
426
- let data;
427
+ let parsed;
427
428
  try {
428
- data = JSON.parse(content);
429
+ parsed = parse(content) ?? {};
429
430
  } catch (e) {
430
- throw new Error(`Invalid JSON in ${filePath}: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
431
+ throw new Error(`Invalid YAML in ${filePath}: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
431
432
  }
432
433
  const fileName = path.basename(filePath);
433
434
  return {
434
435
  filePath: absolutePath,
435
436
  fileName,
436
437
  overlay: {
437
- name: fileName.replace(/\.json$/, ""),
438
- description: data.description || "",
439
- data
438
+ name: parsed.name || fileName.replace(/\.yml$/, "").replace(/\.yaml$/, ""),
439
+ description: parsed.description || "",
440
+ data: parsed
440
441
  },
441
442
  mtime: stats.mtime
442
443
  };
@@ -447,7 +448,7 @@ function loadOverlaysFromDir(dirPath) {
447
448
  const files = fs.readdirSync(absoluteDir);
448
449
  const overlays = [];
449
450
  for (const file of files) {
450
- if (!file.endsWith(".json")) continue;
451
+ if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
451
452
  const filePath = path.join(absoluteDir, file);
452
453
  if (!fs.statSync(filePath).isFile()) continue;
453
454
  try {
@@ -464,7 +465,13 @@ function loadOverlaysByNames(dirPath, names) {
464
465
  const overlays = [];
465
466
  for (const name of names) {
466
467
  let filePath = path.join(absoluteDir, name);
467
- if (!fs.existsSync(filePath) && !name.endsWith(".json")) filePath = path.join(absoluteDir, `${name}.json`);
468
+ if (!fs.existsSync(filePath)) {
469
+ if (!name.endsWith(".yml") && !name.endsWith(".yaml")) {
470
+ const withYml = path.join(absoluteDir, `${name}.yml`);
471
+ if (fs.existsSync(withYml)) filePath = withYml;
472
+ else filePath = path.join(absoluteDir, `${name}.yaml`);
473
+ }
474
+ }
468
475
  if (!fs.existsSync(filePath)) throw new Error(`Overlay file not found: ${name}`);
469
476
  overlays.push(loadOverlayFile(filePath));
470
477
  }
@@ -476,13 +483,14 @@ function loadOverlays(dir, files) {
476
483
  function saveOverlayToFile(dirPath, overlay) {
477
484
  const absoluteDir = path.resolve(dirPath);
478
485
  if (!fs.existsSync(absoluteDir)) fs.mkdirSync(absoluteDir, { recursive: true });
479
- const fileName = `${sanitizeFileName(overlay.name)}.json`;
486
+ const fileName = `${sanitizeFileName(overlay.name)}.yml`;
480
487
  const filePath = path.join(absoluteDir, fileName);
481
- const dataToSave = {
488
+ const content = stringify({
482
489
  ...overlay.data,
483
- description: overlay.description || overlay.data.description
484
- };
485
- fs.writeFileSync(filePath, JSON.stringify(dataToSave, null, 2) + "\n");
490
+ name: overlay.name,
491
+ description: overlay.description
492
+ });
493
+ fs.writeFileSync(filePath, content);
486
494
  const updatedAt = new Date(overlay.updatedAt);
487
495
  fs.utimesSync(filePath, updatedAt, updatedAt);
488
496
  return filePath;
@@ -493,7 +501,7 @@ function getLocalOverlayNames(dirPath) {
493
501
  const files = fs.readdirSync(absoluteDir);
494
502
  const names = [];
495
503
  for (const file of files) {
496
- if (!file.endsWith(".json")) continue;
504
+ if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
497
505
  const filePath = path.join(absoluteDir, file);
498
506
  try {
499
507
  const loaded = loadOverlayFile(filePath);
@@ -806,10 +814,13 @@ var InitService = class InitService {
806
814
  ".devcontainer",
807
815
  ".claude/skills/explore-lakehouse",
808
816
  ".claude/skills/create-semantic-model",
817
+ ".claude/skills/create-semantic-model/references",
818
+ ".claude/skills/create-dbt-transformations",
819
+ ".claude/skills/create-dbt-transformations/references",
809
820
  "dbt/models/bronze",
810
821
  "dbt/models/silver",
811
822
  "dbt/models/gold",
812
- "semantic/cubes"
823
+ "semantic"
813
824
  ];
814
825
  static PROJECT_FILES = [
815
826
  ".devcontainer/devcontainer.json",
@@ -823,10 +834,17 @@ var InitService = class InitService {
823
834
  "AGENTS.md",
824
835
  ".claude/skills/explore-lakehouse/SKILL.md",
825
836
  ".claude/skills/create-semantic-model/SKILL.md",
837
+ ".claude/skills/create-semantic-model/references/cube-examples.md",
838
+ ".claude/skills/create-semantic-model/references/key-patterns.md",
839
+ ".claude/skills/create-semantic-model/references/validation-queries.md",
840
+ ".claude/skills/create-dbt-transformations/SKILL.md",
841
+ ".claude/skills/create-dbt-transformations/references/sql-templates.md",
842
+ ".claude/skills/create-dbt-transformations/references/schema-conventions.md",
843
+ ".claude/skills/create-dbt-transformations/references/edge-cases.md",
826
844
  "dbt/models/bronze/.gitkeep",
827
845
  "dbt/models/silver/.gitkeep",
828
846
  "dbt/models/gold/.gitkeep",
829
- "semantic/cubes/.gitkeep"
847
+ "semantic/.gitkeep"
830
848
  ];
831
849
  constructor(templatesDir) {
832
850
  this.templatesDir = templatesDir;
@@ -923,10 +941,17 @@ var InitService = class InitService {
923
941
  bqDataset: org.bqDataset
924
942
  }),
925
943
  ".claude/skills/create-semantic-model/SKILL.md": this.renderTemplate("skills/create-semantic-model/SKILL.md", {}),
944
+ ".claude/skills/create-semantic-model/references/cube-examples.md": this.renderTemplate("skills/create-semantic-model/references/cube-examples.md", {}),
945
+ ".claude/skills/create-semantic-model/references/key-patterns.md": this.renderTemplate("skills/create-semantic-model/references/key-patterns.md", {}),
946
+ ".claude/skills/create-semantic-model/references/validation-queries.md": this.renderTemplate("skills/create-semantic-model/references/validation-queries.md", {}),
947
+ ".claude/skills/create-dbt-transformations/SKILL.md": this.renderTemplate("skills/create-dbt-transformations/SKILL.md", {}),
948
+ ".claude/skills/create-dbt-transformations/references/sql-templates.md": this.renderTemplate("skills/create-dbt-transformations/references/sql-templates.md", {}),
949
+ ".claude/skills/create-dbt-transformations/references/schema-conventions.md": this.renderTemplate("skills/create-dbt-transformations/references/schema-conventions.md", {}),
950
+ ".claude/skills/create-dbt-transformations/references/edge-cases.md": this.renderTemplate("skills/create-dbt-transformations/references/edge-cases.md", {}),
926
951
  "dbt/models/bronze/.gitkeep": "",
927
952
  "dbt/models/silver/.gitkeep": "",
928
953
  "dbt/models/gold/.gitkeep": "",
929
- "semantic/cubes/.gitkeep": ""
954
+ "semantic/.gitkeep": ""
930
955
  };
931
956
  for (const [rel, content] of Object.entries(files)) {
932
957
  const full = path.join(projectDir, rel);
@@ -1,4 +1,4 @@
1
- import { d as StatusResult, l as PullResult, o as DiffResult, r as CubeOverlay, s as OverlayFile, t as Config, u as PushResult } from "./types-DZssnweO.mjs";
1
+ import { d as PushResult, f as StatusResult, o as DiffResult, r as CubeOverlay, s as OverlayFile, t as Config, u as PullResult } from "./types-DmuJzN0Z.mjs";
2
2
  import { c as StoredCredentials, i as OAuthCallbackResult, l as TokenResponse, r as ClerkUserInfo, s as OrganizationInfo } from "./types-DsQtGF-c.mjs";
3
3
  import { Client } from "@revos/api-client";
4
4
 
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as DiffEntry, c as OverlayStatusInfo, d as StatusResult, f as SyncStatus, i as DiffChange, l as PullResult, n as CubeDefinition, o as DiffResult, r as CubeOverlay, s as OverlayFile, t as Config, u as PushResult } from "./types-DZssnweO.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-DH6vy050.mjs";
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-DmuJzN0Z.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-B8n2GxTc.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-DsQtGF-c.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, 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, 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-EJgxP-x5.mjs";
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-BSLZ9hQU.mjs";
2
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 };
@@ -7,7 +7,7 @@ This is a RevOS data engineering project for **<%=orgName%>** organization.
7
7
  - `dbt/models/bronze/` — raw ingested data
8
8
  - `dbt/models/silver/` — cleaned & conformed
9
9
  - `dbt/models/gold/` — business-ready marts
10
- - `semantic/cubes/` — Cube.dev cube definitions
10
+ - `semantic/` — Cube.dev cube definitions
11
11
 
12
12
  ## Key Commands
13
13
 
@@ -0,0 +1,214 @@
1
+ ---
2
+ name: create-dbt-transformations
3
+ description: Create new dbt transformations (bronze/silver/gold models) in the RevOS dbt project. Use when asked to create a dbt model, build a transformation, add a new layer model, declare a raw source, or register a new Airbyte-ingested table. Covers dbt project conventions, sources, materialization, schema.yml, and validation commands.
4
+ ---
5
+
6
+ # Create dbt Transformations
7
+
8
+ Use this skill to generate SQL models, declare sources, update `schema.yml`, and validate models with `revos dbt run` / `revos dbt test`.
9
+
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
+
12
+ ```bash
13
+ bq show --format=prettyjson $REVOS_BQ_DATASET.<table>
14
+ ```
15
+
16
+ Warn the user: "The `explore-lakehouse` skill is not installed — using `bq show` as a fallback. Install it for richer schema exploration."
17
+
18
+ ---
19
+
20
+ # Part 1: Knowledge Base
21
+
22
+ ## Layer Conventions
23
+
24
+ - **gold** — business-ready models exposed for reporting or downstream consumption.
25
+ - **silver** — cleaned, deduplicated, type-conformed intermediates.
26
+ - **bronze** — thin views over raw source data. References sources via `{{ source() }}`.
27
+
28
+ When layer is not obvious from context, ask (see Checkpoint 1).
29
+
30
+ ## Sources (bronze layer)
31
+
32
+ Raw tables ingested by Airbyte are not dbt models. Declare them as dbt sources so bronze models can reference them with `{{ source() }}`.
33
+
34
+ Sources are declared in `dbt/models/bronze/schema.yml` under a `sources:` block using `schema` (the BigQuery dataset):
35
+
36
+ ```yaml
37
+ sources:
38
+ - name: raw
39
+ schema: "{{ env_var('REVOS_BQ_DATASET') }}"
40
+ tables:
41
+ - name: hubspot_contacts
42
+ ```
43
+
44
+ Reference in bronze SQL:
45
+
46
+ ```sql
47
+ SELECT * FROM {{ source('raw', 'hubspot_contacts') }}
48
+ ```
49
+
50
+ See [schema-conventions.md](references/schema-conventions.md) for the full declaration pattern alongside `models:`.
51
+
52
+ ## Materialization
53
+
54
+ Inherited globally from `dbt_project.yml` — do not add `{{ config(materialized=...) }}` unless the user explicitly asks to override.
55
+
56
+ ## `schema.yml` Convention
57
+
58
+ One shared file per layer at `dbt/models/<layer>/schema.yml`. Append new models; never create per-model YAML files. See [schema-conventions.md](references/schema-conventions.md) for full examples and composite-PK / dbt-utils patterns.
59
+
60
+ ## Resolving Physical BigQuery Tables
61
+
62
+ Materialized table lives at: `$REVOS_BQ_DATASET.<model_name>`
63
+
64
+ **When to use `{{ ref() }}` vs. `{{ source() }}`:**
65
+
66
+ | Context | Use |
67
+ | ----------------------------------- | -------------------------------- |
68
+ | dbt SQL → other dbt model | `{{ ref('<model>') }}` |
69
+ | dbt SQL → raw source table (bronze) | `{{ source('raw', '<table>') }}` |
70
+
71
+ Always declare raw tables as sources before referencing them. Do not use bare fully qualified names — that bypasses dbt's dependency graph and source freshness tracking.
72
+
73
+ ## Standard dbt Commands
74
+
75
+ Always use the `revos` wrapper:
76
+
77
+ ```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
84
+ ```
85
+
86
+ ---
87
+
88
+ # Part 2: Workflow — Create a New dbt Transformation
89
+
90
+ ## Execution Order
91
+
92
+ For each transformation (one at a time — do not batch):
93
+
94
+ 1. Determine the target layer (Checkpoint 1 if unclear).
95
+ 2. Determine the model name.
96
+ 3. Check if that model already exists (Checkpoint 2 if yes).
97
+ 4. Gather source data and transformation logic. For bridge models, apply the bridge template ([sql-templates.md](references/sql-templates.md)).
98
+ 5. For bronze models: check if required sources are declared in `dbt/models/bronze/schema.yml`; add them if missing.
99
+ 6. Generate `dbt/models/<layer>/<model_name>.sql`.
100
+ 7. Detect the primary key (Checkpoint 3 if ambiguous).
101
+ 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.
104
+ 11. Summarize (see Final Response Format).
105
+
106
+ For multiple transformations in one request: repeat steps 1–11 per model in order.
107
+
108
+ ---
109
+
110
+ ## Mandatory User Checkpoints
111
+
112
+ ### Checkpoint 1: Layer Selection
113
+
114
+ Ask if the layer is not obvious:
115
+
116
+ ```text
117
+ Which layer should this transformation live in?
118
+
119
+ - gold: business-ready, exposed for reporting or downstream consumption
120
+ - silver: cleaned/intermediate, shared across downstream uses
121
+ - bronze: close-to-source view over raw data, references sources
122
+ ```
123
+
124
+ Layer is obvious when the user explicitly names it.
125
+
126
+ ### Checkpoint 2: Existing Model Conflict
127
+
128
+ If `dbt/models/<layer>/<model_name>.sql` exists:
129
+
130
+ ```text
131
+ A model named <model_name> already exists at dbt/models/<layer>/<model_name>.sql.
132
+
133
+ Options:
134
+ - overwrite: replace with new transformation
135
+ - edit: modify existing (describe the change)
136
+ - rename: use a different name
137
+ ```
138
+
139
+ If found in a different layer, mention it too.
140
+
141
+ ### Checkpoint 3: Ambiguous Primary Key
142
+
143
+ If PK detection produces no clear result:
144
+
145
+ ```text
146
+ I could not unambiguously detect the primary key. Candidates:
147
+ - <candidate_1>
148
+ - <candidate_2>
149
+
150
+ Which column(s) should be the primary key?
151
+ ```
152
+
153
+ ---
154
+
155
+ ## Primary Key Detection
156
+
157
+ Apply in order; stop at first clear result:
158
+
159
+ 1. `ROW_NUMBER() OVER (PARTITION BY <cols>) = 1` → partition columns are PK.
160
+ 2. `SELECT DISTINCT` over a small column set → all selected columns form composite PK.
161
+ 3. `GROUP BY <cols>` at outermost level → grouping columns are PK.
162
+ 4. Single column named `id` → PK.
163
+ 5. Single column named `<entity>_id` matching the model name stem → PK.
164
+ 6. Bridge naming `<entity_a>_<entity_b>` → `(<entity_a>_id, <entity_b>_id)` composite PK.
165
+
166
+ If none produce a clear answer → Checkpoint 3.
167
+
168
+ ## Foreign Key Detection
169
+
170
+ 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
+
172
+ ## SQL File Generation
173
+
174
+ See [sql-templates.md](references/sql-templates.md) for:
175
+
176
+ - Bronze model template using `{{ source() }}`
177
+ - Standard silver/gold model template
178
+ - Bridge model (JSON array) template with concrete example
179
+ - Bridge model naming convention and SQL content rules
180
+
181
+ ## schema.yml Update
182
+
183
+ See [schema-conventions.md](references/schema-conventions.md) for full examples including sources declaration, composite PK, and dbt-utils patterns.
184
+
185
+ ## Edge Cases
186
+
187
+ See [edge-cases.md](references/edge-cases.md) for: missing SQL details, missing upstream model, undeclared source, run/test failure handling.
188
+
189
+ ---
190
+
191
+ ## Final Response Format
192
+
193
+ ```text
194
+ Created dbt transformation: <model_name>
195
+
196
+ Layer: <bronze | silver | gold>
197
+ File: dbt/models/<layer>/<model_name>.sql
198
+ Materialization: <inherited: table | overridden: <type>>
199
+ Primary key: <pk_column> (or composite: <col_1>, <col_2>)
200
+ Foreign keys: <fk_1>, <fk_2> (or "none detected")
201
+ schema.yml: dbt/models/<layer>/schema.yml (entry added)
202
+
203
+ Tests:
204
+ - not_null on <pk>: added
205
+ - unique on <pk>: added | skipped: dbt-utils unavailable
206
+ - not_null on <fk>: added
207
+
208
+ Validation:
209
+ - revos dbt run: passed | failed
210
+ - revos dbt test: passed | failed
211
+
212
+ Physical table after run:
213
+ `<resolved_dataset>.<model_name>`
214
+ ```