@revos/cli 0.1.1 → 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 (34) hide show
  1. package/README.md +41 -15
  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.d.mts +5 -1
  6. package/dist/adapters/oclif/commands/init.mjs +72 -12
  7. package/dist/adapters/oclif/commands/org/current.mjs +2 -2
  8. package/dist/adapters/oclif/commands/org/list.mjs +2 -2
  9. package/dist/adapters/oclif/commands/org/switch.mjs +2 -2
  10. package/dist/adapters/oclif/commands/overlays/diff.d.mts +1 -1
  11. package/dist/adapters/oclif/commands/overlays/diff.mjs +3 -3
  12. package/dist/adapters/oclif/commands/overlays/pull.d.mts +1 -1
  13. package/dist/adapters/oclif/commands/overlays/pull.mjs +3 -3
  14. package/dist/adapters/oclif/commands/overlays/push.d.mts +1 -1
  15. package/dist/adapters/oclif/commands/overlays/push.mjs +3 -3
  16. package/dist/adapters/oclif/commands/overlays/status.d.mts +1 -1
  17. package/dist/adapters/oclif/commands/overlays/status.mjs +3 -3
  18. package/dist/{base.command-DlYMawJ6.mjs → base.command-CaFn9EwG.mjs} +1 -1
  19. package/dist/{core-Dq15hO6f.mjs → core-BSLZ9hQU.mjs} +80 -29
  20. package/dist/{index-DuqD2b_7.d.mts → index-B8n2GxTc.d.mts} +11 -2
  21. package/dist/index.d.mts +3 -3
  22. package/dist/index.mjs +1 -1
  23. package/dist/templates/AGENTS.md +1 -1
  24. package/dist/templates/skills/create-dbt-transformations/SKILL.md +214 -0
  25. package/dist/templates/skills/create-dbt-transformations/references/edge-cases.md +46 -0
  26. package/dist/templates/skills/create-dbt-transformations/references/schema-conventions.md +128 -0
  27. package/dist/templates/skills/create-dbt-transformations/references/sql-templates.md +73 -0
  28. package/dist/templates/skills/create-semantic-model/SKILL.md +126 -1432
  29. package/dist/templates/skills/create-semantic-model/references/cube-examples.md +267 -0
  30. package/dist/templates/skills/create-semantic-model/references/key-patterns.md +150 -0
  31. package/dist/templates/skills/create-semantic-model/references/validation-queries.md +209 -0
  32. package/dist/templates/skills/explore-lakehouse/SKILL.md +8 -1
  33. package/dist/{types-DZssnweO.d.mts → types-DmuJzN0Z.d.mts} +5 -1
  34. package/package.json +11 -2
package/README.md CHANGED
@@ -26,10 +26,32 @@ revos [command] [options]
26
26
  ### Project Initialization
27
27
 
28
28
  ```bash
29
- revos init [project-name]
29
+ revos init [destination] [--yes] [--dry-run]
30
30
  ```
31
31
 
32
- Scaffolds a new RevOS data engineering project. If `project-name` is omitted, uses the current directory name.
32
+ Scaffolds a new RevOS data engineering project. If `destination` is omitted, initializes in the current directory.
33
+
34
+ **Arguments:**
35
+
36
+ ```
37
+ destination Path or project name (default: current directory)
38
+ Examples:
39
+ my-project → creates ./my-project/
40
+ ./my-project → same as above
41
+ ../my-project → one level up
42
+ /home/user/my-project → absolute path
43
+ ```
44
+
45
+ **Flags:**
46
+
47
+ ```
48
+ -y, --yes Skip confirmation prompts, use defaults
49
+ --dry-run Show what would be created without creating files or GCP resources
50
+ ```
51
+
52
+ **Non-empty directory behavior:**
53
+
54
+ If the destination already exists and is not empty, you will be prompted to confirm before proceeding. Use `--yes` to skip this prompt.
33
55
 
34
56
  **What it does:**
35
57
 
@@ -39,7 +61,7 @@ Scaffolds a new RevOS data engineering project. If `project-name` is omitted, us
39
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.
40
62
  5. Generates `dbt/profiles.yml` pre-configured for BigQuery.
41
63
  6. Generates `.gitignore` and `README.md`.
42
- 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`.
43
65
 
44
66
  **Requires:** `revos auth login` first.
45
67
 
@@ -95,18 +117,22 @@ revos org switch <org-id>
95
117
 
96
118
  ### Overlays Management
97
119
 
98
- 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.
99
121
 
100
- Example `my-overlay.json`:
122
+ Example `semantic/my_table.yml`:
101
123
 
102
- ```json
103
- {
104
- "description": "My custom overlay",
105
- "sql_table": "my_table",
106
- "dimensions": {
107
- "id": { "sql": "id", "type": "number", "primary_key": true }
108
- }
109
- }
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
110
136
  ```
111
137
 
112
138
  #### Push overlays to API
@@ -115,7 +141,7 @@ Example `my-overlay.json`:
115
141
  revos overlays push [files...] [-d <dir>] [-f] [--json]
116
142
 
117
143
  Options:
118
- -d, --dir <path> Directory containing overlay files (default: "./overlays")
144
+ -d, --dir <path> Directory containing overlay files (default: "./semantic")
119
145
  -f, --force Force push even if remote is newer
120
146
  ```
121
147
 
@@ -125,7 +151,7 @@ Options:
125
151
  revos overlays pull [-d <dir>] [-n <name>...] [--json]
126
152
 
127
153
  Options:
128
- -d, --dir <path> Directory to save overlay files (default: "./overlays")
154
+ -d, --dir <path> Directory to save overlay files (default: "./semantic")
129
155
  -n, --name <name> Specific overlay names to pull (repeatable)
130
156
  ```
131
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-Dq15hO6f.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DlYMawJ6.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-Dq15hO6f.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DlYMawJ6.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-Dq15hO6f.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DlYMawJ6.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 {
@@ -5,7 +5,11 @@ import * as _$_oclif_core_interfaces0 from "@oclif/core/interfaces";
5
5
  declare class Init extends BaseCommand<typeof Init> {
6
6
  static description: string;
7
7
  static args: {
8
- name: _$_oclif_core_interfaces0.Arg<string | undefined, Record<string, unknown>>;
8
+ destination: _$_oclif_core_interfaces0.Arg<string | undefined, Record<string, unknown>>;
9
+ };
10
+ static flags: {
11
+ yes: _$_oclif_core_interfaces0.BooleanFlag<boolean>;
12
+ "dry-run": _$_oclif_core_interfaces0.BooleanFlag<boolean>;
9
13
  };
10
14
  run(): Promise<void>;
11
15
  }
@@ -1,27 +1,88 @@
1
- import { P as ApiError, t as InitService, y as getConfig } from "../../../core-Dq15hO6f.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-DlYMawJ6.mjs";
3
+ import { t as BaseCommand } from "../../../base.command-CaFn9EwG.mjs";
4
+ import * as fs from "fs";
4
5
  import * as path from "path";
6
+ import select from "@inquirer/select";
5
7
  import chalk from "chalk";
6
- import { Args } from "@oclif/core";
8
+ import { Args, Flags } from "@oclif/core";
7
9
  //#region src/adapters/oclif/commands/init.ts
10
+ function isDirEmpty(dirPath) {
11
+ return fs.readdirSync(dirPath).length === 0;
12
+ }
8
13
  var Init = class extends BaseCommand {
9
14
  static description = "Scaffold a new RevOS data engineering project";
10
- static args = { name: Args.string({
11
- description: "Project name (used as directory name)",
15
+ static args = { destination: Args.string({
16
+ description: "Path or project name (default: current directory name). Supports relative and absolute paths.",
12
17
  required: false
13
18
  }) };
19
+ static flags = {
20
+ yes: Flags.boolean({
21
+ char: "y",
22
+ description: "Non-interactive mode, use defaults",
23
+ default: false
24
+ }),
25
+ "dry-run": Flags.boolean({
26
+ description: "Show what would be created without making any changes",
27
+ default: false
28
+ })
29
+ };
14
30
  async run() {
15
31
  const { apiUrl, token, organizationId } = await getConfig();
16
- const projectName = this.args.name ?? path.basename(process.cwd());
17
- if (!projectName || projectName.includes("/") || projectName.includes("\\") || projectName === "..") this.error(`Invalid project name: "${projectName}"`, { exit: 1 });
18
- this.log(`\nInitializing project ${chalk.bold(projectName)}...\n`);
19
- const result = await new InitService(TEMPLATES_DIR).run({
32
+ let projectName;
33
+ let targetDir;
34
+ if (this.args.destination) {
35
+ const resolved = path.resolve(this.args.destination);
36
+ projectName = path.basename(resolved);
37
+ targetDir = path.dirname(resolved);
38
+ } else {
39
+ const cwd = process.cwd();
40
+ projectName = path.basename(cwd);
41
+ targetDir = path.dirname(cwd);
42
+ }
43
+ if (!projectName) this.error("Could not determine project name from path", { exit: 1 });
44
+ const projectDir = path.join(targetDir, projectName);
45
+ if (fs.existsSync(projectDir) && !isDirEmpty(projectDir) && !this.flags.yes) {
46
+ if (await select({
47
+ message: `Directory "${projectName}" already exists and is not empty. What would you like to do?`,
48
+ choices: [{
49
+ name: "Initialize in this directory",
50
+ value: "init"
51
+ }, {
52
+ name: "Cancel",
53
+ value: "cancel"
54
+ }]
55
+ }) === "cancel") {
56
+ this.log("Cancelled.");
57
+ return;
58
+ }
59
+ }
60
+ const service = new InitService(TEMPLATES_DIR);
61
+ const org = await service.resolveOrganization(apiUrl, token, organizationId).catch((err) => {
62
+ const message = err instanceof Error ? err.message : String(err);
63
+ this.error(message, { exit: 1 });
64
+ });
65
+ if (this.flags["dry-run"]) {
66
+ const preview = service.dryRun(projectName, targetDir);
67
+ this.log(`\nWould create project ${chalk.bold(projectName)} for organization ${chalk.bold(org.name)}`);
68
+ this.log(` Path: ${chalk.dim(preview.projectDir)}\n`);
69
+ this.log("Directories:");
70
+ for (const dir of preview.dirs) this.log(` ${dir}/`);
71
+ this.log("\nFiles:");
72
+ for (const file of preview.files) this.log(` ${file}`);
73
+ this.log(chalk.dim("\nNo files created, no GCP resources provisioned."));
74
+ return;
75
+ }
76
+ this.log(`\nCreating project ${chalk.bold(projectName)} for organization ${chalk.bold(org.name)}`);
77
+ this.log(` Path: ${chalk.dim(projectDir)}\n`);
78
+ const result = await service.run({
20
79
  projectName,
21
- targetDir: process.cwd(),
80
+ targetDir,
22
81
  apiUrl,
23
82
  token,
24
- organizationId
83
+ organizationId,
84
+ organization: org,
85
+ allowExistingDir: true
25
86
  }).catch((err) => {
26
87
  if (err instanceof ApiError) {
27
88
  this.log(chalk.red(`\nAPI error: ${err.status} ${err.statusText}`));
@@ -31,7 +92,6 @@ var Init = class extends BaseCommand {
31
92
  const message = err instanceof Error ? err.message : String(err);
32
93
  this.error(message, { exit: 1 });
33
94
  });
34
- this.log(chalk.green(`\nOrganization: ${result.organization.name}`));
35
95
  this.log(chalk.green(`\nProject created at ${result.projectDir}`));
36
96
  this.log("\nGenerated files:");
37
97
  for (const f of result.createdFiles) this.log(` ${f}`);
@@ -1,5 +1,5 @@
1
- import { M as loadCredentials, m as unwrap, p as createApiClient, y as getConfig } from "../../../../core-Dq15hO6f.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DlYMawJ6.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-Dq15hO6f.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DlYMawJ6.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-Dq15hO6f.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DlYMawJ6.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-Dq15hO6f.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DlYMawJ6.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-Dq15hO6f.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DlYMawJ6.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-Dq15hO6f.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DlYMawJ6.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-Dq15hO6f.mjs";
2
- import { t as BaseCommand } from "../../../../base.command-DlYMawJ6.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-Dq15hO6f.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);
@@ -801,7 +809,43 @@ async function selectOrganization(organizations) {
801
809
  }
802
810
  //#endregion
803
811
  //#region src/core/services/init.service.ts
804
- var InitService = class {
812
+ var InitService = class InitService {
813
+ static PROJECT_DIRS = [
814
+ ".devcontainer",
815
+ ".claude/skills/explore-lakehouse",
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",
820
+ "dbt/models/bronze",
821
+ "dbt/models/silver",
822
+ "dbt/models/gold",
823
+ "semantic"
824
+ ];
825
+ static PROJECT_FILES = [
826
+ ".devcontainer/devcontainer.json",
827
+ ".devcontainer/Dockerfile",
828
+ ".devcontainer/setup.sh",
829
+ ".gitignore",
830
+ "README.md",
831
+ "dbt/profiles.yml",
832
+ "dbt/dbt_project.yml",
833
+ "CLAUDE.md",
834
+ "AGENTS.md",
835
+ ".claude/skills/explore-lakehouse/SKILL.md",
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",
844
+ "dbt/models/bronze/.gitkeep",
845
+ "dbt/models/silver/.gitkeep",
846
+ "dbt/models/gold/.gitkeep",
847
+ "semantic/.gitkeep"
848
+ ];
805
849
  constructor(templatesDir) {
806
850
  this.templatesDir = templatesDir;
807
851
  }
@@ -809,7 +853,7 @@ var InitService = class {
809
853
  const { projectName, targetDir, apiUrl, token, organizationId } = options;
810
854
  const projectDir = path.join(targetDir, projectName);
811
855
  const projectSlug = projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
812
- const org = await this.resolveOrganization(apiUrl, token, organizationId);
856
+ const org = options.organization ?? await this.resolveOrganization(apiUrl, token, organizationId);
813
857
  const gcpProjectId = await this.downloadGcpKey(apiUrl, token, org, projectSlug);
814
858
  const resolvedOrg = {
815
859
  ...org,
@@ -819,7 +863,7 @@ var InitService = class {
819
863
  return {
820
864
  projectDir,
821
865
  organization: resolvedOrg,
822
- createdFiles: this.scaffold(projectDir, projectName, projectSlug, resolvedOrg)
866
+ createdFiles: this.scaffold(projectDir, projectName, projectSlug, resolvedOrg, options.allowExistingDir)
823
867
  };
824
868
  }
825
869
  async resolveOrganization(apiUrl, token, organizationId) {
@@ -857,18 +901,18 @@ var InitService = class {
857
901
  });
858
902
  return JSON.parse(keyJson).project_id ?? "";
859
903
  }
860
- scaffold(projectDir, projectName, projectSlug, org) {
861
- if (fs.existsSync(projectDir)) throw new Error(`Directory "${projectName}" already exists. Remove it or choose a different name.`);
904
+ dryRun(projectName, targetDir) {
905
+ return {
906
+ projectDir: path.join(targetDir, projectName),
907
+ dirs: InitService.PROJECT_DIRS,
908
+ files: InitService.PROJECT_FILES
909
+ };
910
+ }
911
+ scaffold(projectDir, projectName, projectSlug, org, allowExistingDir) {
912
+ if (fs.existsSync(projectDir) && !allowExistingDir) throw new Error(`Directory "${projectName}" already exists. Remove it or choose a different name.`);
862
913
  const created = [];
863
- for (const dir of [
864
- ".devcontainer",
865
- ".claude/skills/explore-lakehouse",
866
- ".claude/skills/create-semantic-model",
867
- "dbt/models/bronze",
868
- "dbt/models/silver",
869
- "dbt/models/gold",
870
- "semantic/cubes"
871
- ]) fs.mkdirSync(path.join(projectDir, dir), { recursive: true });
914
+ const dirs = InitService.PROJECT_DIRS;
915
+ for (const dir of dirs) fs.mkdirSync(path.join(projectDir, dir), { recursive: true });
872
916
  const dbtName = projectName.replace(/[^a-zA-Z0-9_]/g, "_");
873
917
  const files = {
874
918
  ".devcontainer/devcontainer.json": this.renderTemplate(".devcontainer/devcontainer.json", {
@@ -897,10 +941,17 @@ var InitService = class {
897
941
  bqDataset: org.bqDataset
898
942
  }),
899
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", {}),
900
951
  "dbt/models/bronze/.gitkeep": "",
901
952
  "dbt/models/silver/.gitkeep": "",
902
953
  "dbt/models/gold/.gitkeep": "",
903
- "semantic/cubes/.gitkeep": ""
954
+ "semantic/.gitkeep": ""
904
955
  };
905
956
  for (const [rel, content] of Object.entries(files)) {
906
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
 
@@ -162,6 +162,8 @@ interface InitOptions {
162
162
  apiUrl: string;
163
163
  token: string;
164
164
  organizationId?: string;
165
+ organization?: OrganizationInfo;
166
+ allowExistingDir?: boolean;
165
167
  }
166
168
  interface InitResult {
167
169
  projectDir: string;
@@ -170,10 +172,17 @@ interface InitResult {
170
172
  }
171
173
  declare class InitService {
172
174
  private readonly templatesDir;
175
+ private static readonly PROJECT_DIRS;
176
+ private static readonly PROJECT_FILES;
173
177
  constructor(templatesDir: string);
174
178
  run(options: InitOptions): Promise<InitResult>;
175
- private resolveOrganization;
179
+ resolveOrganization(apiUrl: string, token: string, organizationId?: string): Promise<OrganizationInfo>;
176
180
  private downloadGcpKey;
181
+ dryRun(projectName: string, targetDir: string): {
182
+ projectDir: string;
183
+ dirs: readonly string[];
184
+ files: readonly string[];
185
+ };
177
186
  private scaffold;
178
187
  private renderTemplate;
179
188
  }
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-DuqD2b_7.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-Dq15hO6f.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 };