@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.
- package/README.md +17 -13
- package/dist/adapters/oclif/commands/auth/login.mjs +2 -2
- package/dist/adapters/oclif/commands/auth/logout.mjs +2 -2
- package/dist/adapters/oclif/commands/auth/status.mjs +2 -2
- package/dist/adapters/oclif/commands/init.mjs +2 -2
- package/dist/adapters/oclif/commands/org/current.mjs +2 -2
- package/dist/adapters/oclif/commands/org/list.mjs +2 -2
- package/dist/adapters/oclif/commands/org/switch.mjs +2 -2
- package/dist/adapters/oclif/commands/overlays/diff.d.mts +1 -1
- package/dist/adapters/oclif/commands/overlays/diff.mjs +3 -3
- package/dist/adapters/oclif/commands/overlays/pull.d.mts +1 -1
- package/dist/adapters/oclif/commands/overlays/pull.mjs +3 -3
- package/dist/adapters/oclif/commands/overlays/push.d.mts +1 -1
- package/dist/adapters/oclif/commands/overlays/push.mjs +3 -3
- package/dist/adapters/oclif/commands/overlays/status.d.mts +1 -1
- package/dist/adapters/oclif/commands/overlays/status.mjs +3 -3
- package/dist/{base.command-DDSLyx5v.mjs → base.command-CaFn9EwG.mjs} +1 -1
- package/dist/{core-EJgxP-x5.mjs → core-BSLZ9hQU.mjs} +42 -17
- package/dist/{index-DH6vy050.d.mts → index-B8n2GxTc.d.mts} +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +1 -1
- package/dist/templates/AGENTS.md +1 -1
- package/dist/templates/skills/create-dbt-transformations/SKILL.md +214 -0
- package/dist/templates/skills/create-dbt-transformations/references/edge-cases.md +46 -0
- package/dist/templates/skills/create-dbt-transformations/references/schema-conventions.md +128 -0
- package/dist/templates/skills/create-dbt-transformations/references/sql-templates.md +73 -0
- package/dist/templates/skills/create-semantic-model/SKILL.md +126 -1432
- package/dist/templates/skills/create-semantic-model/references/cube-examples.md +267 -0
- package/dist/templates/skills/create-semantic-model/references/key-patterns.md +150 -0
- package/dist/templates/skills/create-semantic-model/references/validation-queries.md +209 -0
- package/dist/templates/skills/explore-lakehouse/SKILL.md +8 -1
- package/dist/{types-DZssnweO.d.mts → types-DmuJzN0Z.d.mts} +5 -1
- 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
|
|
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 `
|
|
122
|
+
Example `semantic/my_table.yml`:
|
|
123
123
|
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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: "./
|
|
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: "./
|
|
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-
|
|
2
|
-
import { t as BaseCommand } from "../../../../base.command-
|
|
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-
|
|
2
|
-
import { t as BaseCommand } from "../../../../base.command-
|
|
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-
|
|
2
|
-
import { t as BaseCommand } from "../../../../base.command-
|
|
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-
|
|
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-
|
|
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-
|
|
2
|
-
import { t as BaseCommand } from "../../../../base.command-
|
|
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-
|
|
2
|
-
import { t as BaseCommand } from "../../../../base.command-
|
|
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-
|
|
2
|
-
import { t as BaseCommand } from "../../../../base.command-
|
|
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-
|
|
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-
|
|
2
|
-
import { t as BaseCommand } from "../../../../base.command-
|
|
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: "./
|
|
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 {
|
|
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-
|
|
2
|
-
import { t as BaseCommand } from "../../../../base.command-
|
|
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: "./
|
|
12
|
+
default: "./semantic"
|
|
13
13
|
}),
|
|
14
14
|
name: Flags.string({
|
|
15
15
|
char: "n",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
2
|
-
import { t as BaseCommand } from "../../../../base.command-
|
|
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: "./
|
|
12
|
+
default: "./semantic"
|
|
13
13
|
}),
|
|
14
14
|
force: Flags.boolean({
|
|
15
15
|
char: "f",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
2
|
-
import { t as BaseCommand } from "../../../../base.command-
|
|
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: "./
|
|
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-
|
|
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
|
|
427
|
+
let parsed;
|
|
427
428
|
try {
|
|
428
|
-
|
|
429
|
+
parsed = parse(content) ?? {};
|
|
429
430
|
} catch (e) {
|
|
430
|
-
throw new Error(`Invalid
|
|
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(/\.
|
|
438
|
-
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(".
|
|
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)
|
|
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)}.
|
|
486
|
+
const fileName = `${sanitizeFileName(overlay.name)}.yml`;
|
|
480
487
|
const filePath = path.join(absoluteDir, fileName);
|
|
481
|
-
const
|
|
488
|
+
const content = stringify({
|
|
482
489
|
...overlay.data,
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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(".
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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-
|
|
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 };
|
package/dist/templates/AGENTS.md
CHANGED
|
@@ -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
|
|
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
|
+
```
|