@revos/cli 0.1.0 → 0.1.2

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 (30) hide show
  1. package/README.md +24 -2
  2. package/bin/revos.js +1 -1
  3. package/dist/adapters/oclif/commands/auth/login.mjs +2 -2
  4. package/dist/adapters/oclif/commands/auth/logout.mjs +2 -2
  5. package/dist/adapters/oclif/commands/auth/status.mjs +2 -2
  6. package/dist/adapters/oclif/commands/init.d.mts +5 -1
  7. package/dist/adapters/oclif/commands/init.mjs +74 -13
  8. package/dist/adapters/oclif/commands/org/current.mjs +3 -3
  9. package/dist/adapters/oclif/commands/org/list.mjs +3 -3
  10. package/dist/adapters/oclif/commands/org/switch.mjs +3 -3
  11. package/dist/adapters/oclif/commands/overlays/diff.mjs +3 -3
  12. package/dist/adapters/oclif/commands/overlays/pull.mjs +3 -3
  13. package/dist/adapters/oclif/commands/overlays/push.mjs +3 -3
  14. package/dist/adapters/oclif/commands/overlays/status.mjs +3 -3
  15. package/dist/{base.command-BGM225ik.mjs → base.command-DDSLyx5v.mjs} +1 -1
  16. package/dist/{core-Bif-kxlo.mjs → core-EJgxP-x5.mjs} +106 -218
  17. package/dist/{index-C0e8MXGP.d.mts → index-DH6vy050.d.mts} +12 -9
  18. package/dist/index.d.mts +1 -1
  19. package/dist/index.mjs +1 -1
  20. package/dist/templates/.devcontainer/Dockerfile +14 -0
  21. package/dist/templates/.devcontainer/devcontainer.json +54 -0
  22. package/dist/templates/.devcontainer/setup.sh +32 -0
  23. package/dist/templates/AGENTS.md +2 -3
  24. package/dist/templates/CLAUDE.md +0 -16
  25. package/dist/templates/README.md +23 -0
  26. package/dist/templates/dbt/dbt_project.yml +22 -0
  27. package/dist/templates/index.ts +4 -0
  28. package/dist/templates/skills/create-semantic-model/SKILL.md +1611 -0
  29. package/dist/templates/skills/explore-lakehouse/SKILL.md +131 -0
  30. package/package.json +10 -4
@@ -1,7 +1,7 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import * as os from "os";
4
- import express from "express";
4
+ import { createServer } from "node:http";
5
5
  import * as crypto from "crypto";
6
6
  import { Client, client } from "@revos/api-client";
7
7
  import search from "@inquirer/search";
@@ -72,7 +72,6 @@ async function startOAuthServer() {
72
72
  }
73
73
  function tryStartServer(port) {
74
74
  return new Promise((resolve, reject) => {
75
- const app = express();
76
75
  let callbackResolve;
77
76
  let callbackReject;
78
77
  let timeoutId;
@@ -80,28 +79,37 @@ function tryStartServer(port) {
80
79
  callbackResolve = res;
81
80
  callbackReject = rej;
82
81
  });
83
- app.get("/callback", (req, res) => {
84
- const code = req.query.code;
85
- const state = req.query.state;
86
- const error = req.query.error;
87
- const errorDescription = req.query.error_description;
82
+ const server = createServer((req, res) => {
83
+ const url = new URL(req.url ?? "/", `http://localhost:${port}`);
84
+ if (url.pathname !== "/callback" || req.method !== "GET") {
85
+ res.writeHead(404);
86
+ res.end();
87
+ return;
88
+ }
89
+ const code = url.searchParams.get("code") ?? void 0;
90
+ const state = url.searchParams.get("state") ?? void 0;
91
+ const error = url.searchParams.get("error") ?? void 0;
92
+ const errorDescription = url.searchParams.get("error_description") ?? void 0;
88
93
  if (error) {
89
- res.send(getErrorHtml(errorDescription || error));
94
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
95
+ res.end(getErrorHtml(errorDescription || error));
90
96
  callbackReject(new Error(errorDescription || error));
91
97
  return;
92
98
  }
93
99
  if (!code || !state) {
94
- res.status(400).send(getErrorHtml("Missing code or state parameter"));
100
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
101
+ res.end(getErrorHtml("Missing code or state parameter"));
95
102
  callbackReject(/* @__PURE__ */ new Error("Missing code or state parameter"));
96
103
  return;
97
104
  }
98
- res.send(getSuccessHtml());
105
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
106
+ res.end(getSuccessHtml());
99
107
  callbackResolve({
100
108
  code,
101
109
  state
102
110
  });
103
111
  });
104
- const server = app.listen(port, () => {
112
+ server.listen(port, () => {
105
113
  const shutdown = () => {
106
114
  clearTimeout(timeoutId);
107
115
  server.close();
@@ -136,6 +144,8 @@ function getSuccessHtml() {
136
144
  <!DOCTYPE html>
137
145
  <html>
138
146
  <head>
147
+ <meta charset="utf-8">
148
+ <link rel="icon" href="https://www.revos.ai/favicons/favicon.ico">
139
149
  <title>RevOS CLI - Authentication Successful</title>
140
150
  <style>
141
151
  body {
@@ -177,6 +187,8 @@ function getErrorHtml(message) {
177
187
  <!DOCTYPE html>
178
188
  <html>
179
189
  <head>
190
+ <meta charset="utf-8">
191
+ <link rel="icon" href="https://www.revos.ai/favicons/favicon.ico">
180
192
  <title>RevOS CLI - Authentication Failed</title>
181
193
  <style>
182
194
  body {
@@ -330,17 +342,26 @@ async function refreshAccessToken(refreshToken) {
330
342
  //#endregion
331
343
  //#region src/core/config.ts
332
344
  const DEFAULT_API_URL = "https://api.revos.ai";
333
- function getStoredAccessToken() {
345
+ async function getStoredAccessToken() {
334
346
  const credentials = loadCredentials();
335
347
  if (!credentials) return null;
336
- if (isTokenExpired(credentials)) return null;
337
- return credentials.accessToken;
348
+ if (!isTokenExpired(credentials)) return credentials.accessToken;
349
+ if (!credentials.refreshToken) return null;
350
+ try {
351
+ const tokenResponse = await refreshAccessToken(credentials.refreshToken);
352
+ const newCredentials = tokenResponseToCredentials(tokenResponse, await getUserInfo(tokenResponse.access_token));
353
+ newCredentials.organizationId = credentials.organizationId;
354
+ saveCredentials(newCredentials);
355
+ return newCredentials.accessToken;
356
+ } catch {
357
+ return null;
358
+ }
338
359
  }
339
360
  function getStoredOrganizationId() {
340
361
  return loadCredentials()?.organizationId ?? void 0;
341
362
  }
342
- function getConfig() {
343
- const token = process.env.REVOS_TOKEN || getStoredAccessToken();
363
+ async function getConfig() {
364
+ const token = process.env.REVOS_TOKEN || await getStoredAccessToken();
344
365
  if (!token) throw new Error("Not authenticated. Run 'revos auth login' or set REVOS_TOKEN environment variable.");
345
366
  return {
346
367
  apiUrl: process.env.REVOS_API_URL || DEFAULT_API_URL,
@@ -780,15 +801,41 @@ async function selectOrganization(organizations) {
780
801
  }
781
802
  //#endregion
782
803
  //#region src/core/services/init.service.ts
783
- var InitService = class {
804
+ var InitService = class InitService {
805
+ static PROJECT_DIRS = [
806
+ ".devcontainer",
807
+ ".claude/skills/explore-lakehouse",
808
+ ".claude/skills/create-semantic-model",
809
+ "dbt/models/bronze",
810
+ "dbt/models/silver",
811
+ "dbt/models/gold",
812
+ "semantic/cubes"
813
+ ];
814
+ static PROJECT_FILES = [
815
+ ".devcontainer/devcontainer.json",
816
+ ".devcontainer/Dockerfile",
817
+ ".devcontainer/setup.sh",
818
+ ".gitignore",
819
+ "README.md",
820
+ "dbt/profiles.yml",
821
+ "dbt/dbt_project.yml",
822
+ "CLAUDE.md",
823
+ "AGENTS.md",
824
+ ".claude/skills/explore-lakehouse/SKILL.md",
825
+ ".claude/skills/create-semantic-model/SKILL.md",
826
+ "dbt/models/bronze/.gitkeep",
827
+ "dbt/models/silver/.gitkeep",
828
+ "dbt/models/gold/.gitkeep",
829
+ "semantic/cubes/.gitkeep"
830
+ ];
784
831
  constructor(templatesDir) {
785
832
  this.templatesDir = templatesDir;
786
833
  }
787
834
  async run(options) {
788
- const { projectName, targetDir, apiUrl, token } = options;
835
+ const { projectName, targetDir, apiUrl, token, organizationId } = options;
789
836
  const projectDir = path.join(targetDir, projectName);
790
837
  const projectSlug = projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
791
- const org = await this.resolveOrganization(apiUrl, token);
838
+ const org = options.organization ?? await this.resolveOrganization(apiUrl, token, organizationId);
792
839
  const gcpProjectId = await this.downloadGcpKey(apiUrl, token, org, projectSlug);
793
840
  const resolvedOrg = {
794
841
  ...org,
@@ -798,14 +845,19 @@ var InitService = class {
798
845
  return {
799
846
  projectDir,
800
847
  organization: resolvedOrg,
801
- createdFiles: this.scaffold(projectDir, projectName, projectSlug, resolvedOrg)
848
+ createdFiles: this.scaffold(projectDir, projectName, projectSlug, resolvedOrg, options.allowExistingDir)
802
849
  };
803
850
  }
804
- async resolveOrganization(apiUrl, token) {
851
+ async resolveOrganization(apiUrl, token, organizationId) {
805
852
  const api = createApiClient({
806
853
  apiUrl,
807
854
  token
808
855
  });
856
+ if (organizationId) {
857
+ const full = unwrap(await api.organizations.get({ id: organizationId }));
858
+ if (!full.bqDataset) throw new Error("Organization is missing BigQuery dataset configuration. Contact support.");
859
+ return full;
860
+ }
809
861
  const orgs = unwrap(await api.organizations.list()) ?? [];
810
862
  if (orgs.length === 0) throw new Error("No organizations found for this account.");
811
863
  const selected = orgs.length === 1 ? orgs[0] : await selectOrganization(orgs);
@@ -831,41 +883,50 @@ var InitService = class {
831
883
  });
832
884
  return JSON.parse(keyJson).project_id ?? "";
833
885
  }
834
- scaffold(projectDir, projectName, projectSlug, org) {
835
- if (fs.existsSync(projectDir)) throw new Error(`Directory "${projectName}" already exists. Remove it or choose a different name.`);
886
+ dryRun(projectName, targetDir) {
887
+ return {
888
+ projectDir: path.join(targetDir, projectName),
889
+ dirs: InitService.PROJECT_DIRS,
890
+ files: InitService.PROJECT_FILES
891
+ };
892
+ }
893
+ scaffold(projectDir, projectName, projectSlug, org, allowExistingDir) {
894
+ if (fs.existsSync(projectDir) && !allowExistingDir) throw new Error(`Directory "${projectName}" already exists. Remove it or choose a different name.`);
836
895
  const created = [];
837
- for (const dir of [
838
- ".devcontainer",
839
- ".revos",
840
- ".claude/skills",
841
- "dbt/models/bronze",
842
- "dbt/models/silver",
843
- "dbt/models/gold",
844
- "semantic/cubes",
845
- "semantic/views"
846
- ]) fs.mkdirSync(path.join(projectDir, dir), { recursive: true });
896
+ const dirs = InitService.PROJECT_DIRS;
897
+ for (const dir of dirs) fs.mkdirSync(path.join(projectDir, dir), { recursive: true });
898
+ const dbtName = projectName.replace(/[^a-zA-Z0-9_]/g, "_");
847
899
  const files = {
848
- ".devcontainer/devcontainer.json": this.renderDevcontainer(projectName, projectSlug),
849
- ".devcontainer/setup.sh": this.renderSetupScript(),
850
- ".gitignore": this.renderGitignore(),
851
- "README.md": this.renderReadme(projectName, org),
852
- "dbt/profiles.yml": this.renderDbtProfiles(projectName, org),
853
- "dbt/dbt_project.yml": this.renderDbtProject(projectName),
854
- ".revos/env": this.renderRevosEnv(org),
855
- "CLAUDE.md": this.renderTemplate("CLAUDE.md", {
900
+ ".devcontainer/devcontainer.json": this.renderTemplate(".devcontainer/devcontainer.json", {
901
+ projectName,
902
+ projectSlug,
903
+ bqProjectId: org.bqProjectId || "",
904
+ bqDataset: org.bqDataset,
905
+ organizationId: org.id
906
+ }),
907
+ ".devcontainer/Dockerfile": this.renderTemplate(".devcontainer/Dockerfile", {}),
908
+ ".devcontainer/setup.sh": this.renderTemplate(".devcontainer/setup.sh", {}),
909
+ ".gitignore": this.renderTemplate(".gitignore", {}),
910
+ "README.md": this.renderTemplate("README.md", {
856
911
  projectName,
857
912
  orgName: org.name
858
913
  }),
914
+ "dbt/profiles.yml": this.renderTemplate("dbt/profiles.yml", { bqLocation: org.bqLocation ?? "europe-west3" }),
915
+ "dbt/dbt_project.yml": this.renderTemplate("dbt/dbt_project.yml", { dbtName }),
916
+ "CLAUDE.md": this.renderTemplate("CLAUDE.md", {}),
859
917
  "AGENTS.md": this.renderTemplate("AGENTS.md", {
860
918
  projectName,
861
919
  orgName: org.name
862
920
  }),
863
- ".claude/skills/.gitkeep": "",
921
+ ".claude/skills/explore-lakehouse/SKILL.md": this.renderTemplate("skills/explore-lakehouse/SKILL.md", {
922
+ bqProjectId: org.bqProjectId || "",
923
+ bqDataset: org.bqDataset
924
+ }),
925
+ ".claude/skills/create-semantic-model/SKILL.md": this.renderTemplate("skills/create-semantic-model/SKILL.md", {}),
864
926
  "dbt/models/bronze/.gitkeep": "",
865
927
  "dbt/models/silver/.gitkeep": "",
866
928
  "dbt/models/gold/.gitkeep": "",
867
- "semantic/cubes/.gitkeep": "",
868
- "semantic/views/.gitkeep": ""
929
+ "semantic/cubes/.gitkeep": ""
869
930
  };
870
931
  for (const [rel, content] of Object.entries(files)) {
871
932
  const full = path.join(projectDir, rel);
@@ -875,186 +936,13 @@ var InitService = class {
875
936
  }
876
937
  return created;
877
938
  }
878
- renderDbtProfiles(projectName, org) {
879
- return [
880
- `${projectName.replace(/[^a-zA-Z0-9_]/g, "_")}:`,
881
- ` target: dev`,
882
- ` outputs:`,
883
- ` dev:`,
884
- ` type: bigquery`,
885
- ` method: service-account`,
886
- ` project: "{{ env_var('GOOGLE_CLOUD_PROJECT') }}"`,
887
- ` dataset: "{{ env_var('REVOS_BQ_DATASET') }}"`,
888
- ` keyfile: "{{ env_var('GOOGLE_APPLICATION_CREDENTIALS') }}"`,
889
- ` threads: 4`,
890
- ` timeout_seconds: 300`,
891
- ` location: ${org.bqLocation ?? "europe-west3"}`,
892
- ``
893
- ].join("\n");
894
- }
895
- renderDbtProject(projectName) {
896
- const dbtName = projectName.replace(/[^a-zA-Z0-9_]/g, "_");
897
- return [
898
- `name: "${dbtName}"`,
899
- `version: "1.0.0"`,
900
- ``,
901
- `profile: "${dbtName}"`,
902
- ``,
903
- `model-paths: ["models"]`,
904
- `seed-paths: ["seeds"]`,
905
- `test-paths: ["tests"]`,
906
- `analysis-paths: ["analyses"]`,
907
- `macro-paths: ["macros"]`,
908
- ``,
909
- `target-path: "target"`,
910
- `clean-targets: ["target", "dbt_packages"]`,
911
- ``,
912
- `models:`,
913
- ` ${dbtName}:`,
914
- ` bronze:`,
915
- ` +materialized: table`,
916
- ` silver:`,
917
- ` +materialized: table`,
918
- ` gold:`,
919
- ` +materialized: table`,
920
- ``
921
- ].join("\n");
922
- }
923
- renderRevosEnv(org) {
924
- return [
925
- `export GOOGLE_CLOUD_PROJECT="${org.bqProjectId || ""}"`,
926
- `export REVOS_BQ_DATASET="${org.bqDataset}"`,
927
- ``
928
- ].join("\n");
929
- }
930
- renderDevcontainer(projectName, projectSlug) {
931
- const config = {
932
- name: `${projectName} — RevOS Data Engineer`,
933
- image: "mcr.microsoft.com/devcontainers/base:ubuntu-24.04",
934
- features: {
935
- "ghcr.io/devcontainers/features/python:1": { version: "3.11" },
936
- "ghcr.io/devcontainers/features/node:1": { version: "lts" },
937
- "ghcr.io/anthropics/devcontainer-features/claude-code:1": {}
938
- },
939
- postCreateCommand: "bash .devcontainer/setup.sh",
940
- mounts: [{
941
- source: `\${localEnv:HOME}/.revos/${projectSlug}-gsa-creds.json`,
942
- target: "/tmp/.revos-gsa-creds.json",
943
- type: "bind"
944
- }],
945
- customizations: { vscode: {
946
- extensions: ["anthropics.claude-code", "innoverio.vscode-dbt-power-user"],
947
- settings: { "python.defaultInterpreterPath": "/usr/local/python/current/bin/python" }
948
- } }
949
- };
950
- return JSON.stringify(config, null, 2) + "\n";
951
- }
952
- renderSetupScript() {
953
- return [
954
- "#!/usr/bin/env bash",
955
- "set -euo pipefail",
956
- "",
957
- "# Google Cloud CLI",
958
- "curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg \\",
959
- " | sudo gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg",
960
- "echo \"deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main\" \\",
961
- " | sudo tee /etc/apt/sources.list.d/google-cloud-sdk.list",
962
- "sudo apt-get update -q",
963
- "sudo apt-get install -y google-cloud-cli",
964
- "",
965
- "# dbt (cryptography<47 avoids Illegal instruction on ARM)",
966
- "pip install --quiet dbt-bigquery \"cryptography<47\"",
967
- "",
968
- "# Copy mounted GCP service account key to vscode user's home",
969
- "if [ -f /tmp/.revos-gsa-creds.json ]; then",
970
- " mkdir -p \"$HOME/.revos\"",
971
- " cp /tmp/.revos-gsa-creds.json \"$HOME/.revos/gsa-creds.json\"",
972
- " chmod 600 \"$HOME/.revos/gsa-creds.json\"",
973
- "fi",
974
- "",
975
- "# Source project-specific env (BQ project, dataset, location)",
976
- "if [ -f \".revos/env\" ]; then",
977
- " # shellcheck source=/dev/null",
978
- " . \".revos/env\"",
979
- " grep -v \"^#\" \".revos/env\" >> \"$HOME/.bashrc\"",
980
- "fi",
981
- "",
982
- "# Configure gcloud with service account and project",
983
- "GCP_KEY=\"$HOME/.revos/gsa-creds.json\"",
984
- "if [ -f \"$GCP_KEY\" ]; then",
985
- " gcloud auth activate-service-account --key-file=\"$GCP_KEY\"",
986
- " echo \"export GOOGLE_APPLICATION_CREDENTIALS=\\\"$GCP_KEY\\\"\" >> \"$HOME/.bashrc\"",
987
- "fi",
988
- "if [ -n \"${GOOGLE_CLOUD_PROJECT:-}\" ]; then",
989
- " gcloud config set project \"$GOOGLE_CLOUD_PROJECT\"",
990
- "fi",
991
- "",
992
- "# Symlink dbt profiles so `dbt run` works without --profiles-dir",
993
- "mkdir -p \"$HOME/.dbt\"",
994
- "ln -sf \"$(pwd)/dbt/profiles.yml\" \"$HOME/.dbt/profiles.yml\"",
995
- ""
996
- ].join("\n");
997
- }
998
- renderGitignore() {
999
- return [
1000
- "# dbt",
1001
- "dbt/target/",
1002
- "dbt/logs/",
1003
- "dbt/dbt_packages/",
1004
- "dbt/profiles.yml",
1005
- "",
1006
- "# Python",
1007
- "__pycache__/",
1008
- "*.py[cod]",
1009
- ".venv/",
1010
- "venv/",
1011
- "",
1012
- "# Node",
1013
- "node_modules/",
1014
- "",
1015
- "# Local env",
1016
- ".env",
1017
- ".env.*",
1018
- "!.env.example",
1019
- ""
1020
- ].join("\n");
1021
- }
1022
939
  renderTemplate(relativePath, vars) {
1023
940
  const filePath = path.join(this.templatesDir, relativePath);
1024
941
  if (!fs.existsSync(filePath)) throw new Error(`Template "${relativePath}" not found — try reinstalling the revos CLI`);
1025
942
  let content = fs.readFileSync(filePath, "utf-8");
1026
- for (const [key, value] of Object.entries(vars)) content = content.replaceAll(`{{${key}}}`, value);
943
+ for (const [key, value] of Object.entries(vars)) content = content.replaceAll(`<%=${key}%>`, value);
1027
944
  return content;
1028
945
  }
1029
- renderReadme(projectName, org) {
1030
- return [
1031
- `# ${projectName}`,
1032
- ``,
1033
- `RevOS data engineering project for **${org.name}**.`,
1034
- ``,
1035
- `## Getting started`,
1036
- ``,
1037
- `1. Open this folder in VS Code and click **Reopen in Container** — the Dev Container installs all tools automatically.`,
1038
- `2. Inside the container, verify your setup:`,
1039
- ` \`\`\`bash`,
1040
- ` dbt --version`,
1041
- ` bq version`,
1042
- ` \`\`\``,
1043
- ``,
1044
- `## Project structure`,
1045
- ``,
1046
- `\`\`\``,
1047
- `dbt/models/`,
1048
- ` bronze/ # raw ingested data`,
1049
- ` silver/ # cleaned & conformed`,
1050
- ` gold/ # business-ready marts`,
1051
- `semantic/`,
1052
- ` cubes/ # Cube.dev cube definitions`,
1053
- ` views/ # Cube.dev view definitions`,
1054
- `\`\`\``,
1055
- ``
1056
- ].join("\n");
1057
- }
1058
946
  };
1059
947
  //#endregion
1060
948
  export { getCredentialsPath as A, getUserInfo as C, tokenResponseToCredentials as D, setClerkEnv as E, loadCredentials as M, saveCredentials as N, startOAuthServer as O, ApiError as P, generatePKCEChallenge as S, setClerkConfig as T, isContentEqual as _, PullService as a, buildAuthorizationUrl as b, loadOverlayFile as c, loadOverlaysFromDir as d, saveOverlayToFile as f, formatError as g, findRemoteOnlyOverlays as h, StatusService as i, isTokenExpired as j, deleteCredentials as k, loadOverlays as l, unwrap as m, selectOrganization as n, PushService as o, createApiClient as p, DiffService as r, getLocalOverlayNames as s, InitService as t, loadOverlaysByNames as u, sanitizeFileName as v, refreshAccessToken as w, exchangeCodeForTokens as x, getConfig as y };
@@ -13,7 +13,7 @@ declare class ApiError extends Error {
13
13
  }
14
14
  //#endregion
15
15
  //#region src/core/config.d.ts
16
- declare function getConfig(): Config;
16
+ declare function getConfig(): Promise<Config>;
17
17
  //#endregion
18
18
  //#region src/core/auth/credentials-store.d.ts
19
19
  declare function getCredentialsPath(): string;
@@ -161,6 +161,9 @@ interface InitOptions {
161
161
  targetDir: string;
162
162
  apiUrl: string;
163
163
  token: string;
164
+ organizationId?: string;
165
+ organization?: OrganizationInfo;
166
+ allowExistingDir?: boolean;
164
167
  }
165
168
  interface InitResult {
166
169
  projectDir: string;
@@ -169,19 +172,19 @@ interface InitResult {
169
172
  }
170
173
  declare class InitService {
171
174
  private readonly templatesDir;
175
+ private static readonly PROJECT_DIRS;
176
+ private static readonly PROJECT_FILES;
172
177
  constructor(templatesDir: string);
173
178
  run(options: InitOptions): Promise<InitResult>;
174
- private resolveOrganization;
179
+ resolveOrganization(apiUrl: string, token: string, organizationId?: string): Promise<OrganizationInfo>;
175
180
  private downloadGcpKey;
181
+ dryRun(projectName: string, targetDir: string): {
182
+ projectDir: string;
183
+ dirs: readonly string[];
184
+ files: readonly string[];
185
+ };
176
186
  private scaffold;
177
- private renderDbtProfiles;
178
- private renderDbtProject;
179
- private renderRevosEnv;
180
- private renderDevcontainer;
181
- private renderSetupScript;
182
- private renderGitignore;
183
187
  private renderTemplate;
184
- private renderReadme;
185
188
  }
186
189
  //#endregion
187
190
  export { ClerkEnv as A, tokenResponseToCredentials as B, LoadedOverlay as C, loadOverlaysByNames as D, loadOverlays as E, generatePKCEChallenge as F, isTokenExpired as G, startOAuthServer as H, getUserInfo as I, getConfig as J, loadCredentials as K, refreshAccessToken as L, PKCEChallenge as M, buildAuthorizationUrl as N, loadOverlaysFromDir as O, exchangeCodeForTokens as P, setClerkConfig as R, sanitizeFileName as S, loadOverlayFile as T, deleteCredentials as U, OAuthServerResult as V, getCredentialsPath as W, ApiError as Y, createApiClient as _, DiffOptions as a, formatError as b, StatusOptions as c, PullOptions as d, PullService as f, ApiClient as g, PushService as h, DiffContext as i, ClerkOAuthConfig as j, saveOverlayToFile as k, StatusService as l, PushOptions as m, InitResult as n, DiffService as o, PushContext as p, saveCredentials as q, InitService as r, StatusContext as s, InitOptions as t, PullContext as u, unwrap as v, getLocalOverlayNames as w, isContentEqual as x, findRemoteOnlyOverlays as y, setClerkEnv as z };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
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-C0e8MXGP.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";
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
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 };
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-Bif-kxlo.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-EJgxP-x5.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 };
@@ -0,0 +1,14 @@
1
+ FROM mcr.microsoft.com/devcontainers/python:3.13
2
+
3
+ # Pre-install dbt so it's available before VS Code extensions activate
4
+ # (cryptography<47 avoids Illegal instruction on ARM)
5
+ RUN pip install --quiet dbt-bigquery "cryptography<47"
6
+
7
+ # Google Cloud CLI
8
+ RUN curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg \
9
+ | gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg \
10
+ && echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" \
11
+ > /etc/apt/sources.list.d/google-cloud-sdk.list \
12
+ && apt-get update -q \
13
+ && apt-get install -y --no-install-recommends google-cloud-cli \
14
+ && rm -rf /var/lib/apt/lists/*
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "<%=projectName%> — RevOS Data Engineer",
3
+ "build": {
4
+ "dockerfile": "Dockerfile"
5
+ },
6
+ "features": {
7
+ "ghcr.io/devcontainers/features/node:1": {
8
+ "version": "lts"
9
+ },
10
+ "ghcr.io/anthropics/devcontainer-features/claude-code:1": {}
11
+ },
12
+ "otherPortsAttributes": {
13
+ "onAutoForward": "notify"
14
+ },
15
+ "containerEnv": {
16
+ "DBT_PROJECT_DIR": "${containerWorkspaceFolder}/dbt",
17
+ "GOOGLE_APPLICATION_CREDENTIALS": "/home/vscode/.revos/gsa-creds.json",
18
+ "GOOGLE_CLOUD_PROJECT": "<%=bqProjectId%>",
19
+ "REVOS_BQ_DATASET": "<%=bqDataset%>",
20
+ "REVOS_ORG_ID": "<%=organizationId%>"
21
+ },
22
+ "postCreateCommand": "bash .devcontainer/setup.sh",
23
+ "mounts": [
24
+ {
25
+ "source": "${localEnv:HOME}/.revos/<%=projectSlug%>-gsa-creds.json",
26
+ "target": "/tmp/.revos-gsa-creds.json",
27
+ "type": "bind"
28
+ },
29
+ {
30
+ "source": "${localEnv:HOME}/.revos/credentials.json",
31
+ "target": "/tmp/.revos-credentials.json",
32
+ "type": "bind"
33
+ },
34
+ {
35
+ "source": "claude-code-config",
36
+ "target": "/home/vscode/.claude",
37
+ "type": "volume"
38
+ }
39
+ ],
40
+ "customizations": {
41
+ "vscode": {
42
+ "extensions": [
43
+ "anthropics.claude-code",
44
+ "innoverio.vscode-dbt-power-user"
45
+ ],
46
+ "settings": {
47
+ "files.associations": {
48
+ "*.sql": "jinja-sql",
49
+ "*.yml": "yaml"
50
+ }
51
+ }
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Copy mounted GCP service account key to vscode user's home
5
+ if [ -f /tmp/.revos-gsa-creds.json ]; then
6
+ mkdir -p "$HOME/.revos"
7
+ cp /tmp/.revos-gsa-creds.json "$HOME/.revos/gsa-creds.json"
8
+ chmod 600 "$HOME/.revos/gsa-creds.json"
9
+ fi
10
+
11
+ # Copy mounted RevOS CLI credentials so revos commands work without re-login
12
+ if [ -f /tmp/.revos-credentials.json ]; then
13
+ mkdir -p "$HOME/.revos"
14
+ cp /tmp/.revos-credentials.json "$HOME/.revos/credentials.json"
15
+ chmod 600 "$HOME/.revos/credentials.json"
16
+ fi
17
+
18
+ # Configure gcloud with service account and project
19
+ GCP_KEY="$HOME/.revos/gsa-creds.json"
20
+ if [ -f "$GCP_KEY" ]; then
21
+ gcloud auth activate-service-account --key-file="$GCP_KEY"
22
+ fi
23
+ if [ -n "${GOOGLE_CLOUD_PROJECT:-}" ]; then
24
+ gcloud config set project "$GOOGLE_CLOUD_PROJECT"
25
+ fi
26
+
27
+ # Install RevOS CLI
28
+ npm install -g @revos/cli
29
+
30
+ # Symlink dbt profiles so `dbt run` works without --profiles-dir
31
+ mkdir -p "$HOME/.dbt"
32
+ ln -sf "$(pwd)/dbt/profiles.yml" "$HOME/.dbt/profiles.yml"
@@ -1,6 +1,6 @@
1
- # {{projectName}} — AI Companion Guide
1
+ # <%=projectName%> — AI Companion Guide
2
2
 
3
- This is a RevOS data engineering project for **{{orgName}}**.
3
+ This is a RevOS data engineering project for **<%=orgName%>** organization.
4
4
 
5
5
  ## Project Structure
6
6
 
@@ -8,7 +8,6 @@ This is a RevOS data engineering project for **{{orgName}}**.
8
8
  - `dbt/models/silver/` — cleaned & conformed
9
9
  - `dbt/models/gold/` — business-ready marts
10
10
  - `semantic/cubes/` — Cube.dev cube definitions
11
- - `semantic/views/` — Cube.dev view definitions
12
11
 
13
12
  ## Key Commands
14
13
 
@@ -1,17 +1 @@
1
- # {{projectName}}
2
-
3
- RevOS data engineering project for **{{orgName}}**.
4
-
5
1
  @AGENTS.md
6
-
7
- ## Project structure
8
-
9
- ```
10
- dbt/models/
11
- bronze/ # raw ingested data
12
- silver/ # cleaned & conformed
13
- gold/ # business-ready marts
14
- semantic/
15
- cubes/ # Cube.dev cube definitions
16
- views/ # Cube.dev view definitions
17
- ```
@@ -0,0 +1,23 @@
1
+ # <%=projectName%>
2
+
3
+ RevOS data engineering project for **<%=orgName%>** organization.
4
+
5
+ ## Getting started
6
+
7
+ 1. Open this folder in VS Code and click **Reopen in Container** — the Dev Container installs all tools automatically.
8
+ 2. Inside the container, verify your setup:
9
+ ```bash
10
+ dbt --version
11
+ bq version
12
+ ```
13
+
14
+ ## Project structure
15
+
16
+ ```
17
+ dbt/models/
18
+ bronze/ # raw ingested data
19
+ silver/ # cleaned & conformed
20
+ gold/ # business-ready marts
21
+ semantic/
22
+ cubes/ # Cube.dev cube definitions
23
+ ```