@revos/cli 0.2.1 → 0.2.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 (162) hide show
  1. package/README.md +289 -77
  2. package/dist/adapters/oclif/commands/action-runs/get.mjs +1 -1
  3. package/dist/adapters/oclif/commands/action-runs/list.mjs +8 -2
  4. package/dist/adapters/oclif/commands/actions/get-input-schema.mjs +2 -2
  5. package/dist/adapters/oclif/commands/actions/get-params-schema.mjs +2 -2
  6. package/dist/adapters/oclif/commands/actions/get.mjs +1 -1
  7. package/dist/adapters/oclif/commands/actions/list.mjs +8 -4
  8. package/dist/adapters/oclif/commands/ai-instructions/create.mjs +1 -1
  9. package/dist/adapters/oclif/commands/ai-instructions/delete.mjs +1 -1
  10. package/dist/adapters/oclif/commands/ai-instructions/get.mjs +1 -1
  11. package/dist/adapters/oclif/commands/ai-instructions/list.mjs +8 -2
  12. package/dist/adapters/oclif/commands/ai-instructions/update.mjs +1 -1
  13. package/dist/adapters/oclif/commands/api.d.mts +11 -0
  14. package/dist/adapters/oclif/commands/api.mjs +112 -0
  15. package/dist/adapters/oclif/commands/apply.d.mts +29 -0
  16. package/dist/adapters/oclif/commands/apply.mjs +77 -0
  17. package/dist/adapters/oclif/commands/auth/login.d.mts +6 -4
  18. package/dist/adapters/oclif/commands/auth/login.mjs +23 -11
  19. package/dist/adapters/oclif/commands/auth/logout.d.mts +2 -1
  20. package/dist/adapters/oclif/commands/auth/logout.mjs +3 -2
  21. package/dist/adapters/oclif/commands/auth/status.d.mts +4 -2
  22. package/dist/adapters/oclif/commands/auth/status.mjs +23 -3
  23. package/dist/adapters/oclif/commands/connections/create.d.mts +6 -0
  24. package/dist/adapters/oclif/commands/connections/create.mjs +8 -0
  25. package/dist/adapters/oclif/commands/connections/delete.d.mts +6 -0
  26. package/dist/adapters/oclif/commands/connections/delete.mjs +8 -0
  27. package/dist/adapters/oclif/commands/connections/get.d.mts +6 -0
  28. package/dist/adapters/oclif/commands/connections/get.mjs +8 -0
  29. package/dist/adapters/oclif/commands/connections/list.d.mts +6 -0
  30. package/dist/adapters/oclif/commands/connections/list.mjs +14 -0
  31. package/dist/adapters/oclif/commands/connections/update.d.mts +6 -0
  32. package/dist/adapters/oclif/commands/connections/update.mjs +8 -0
  33. package/dist/adapters/oclif/commands/cubes/create.d.mts +6 -0
  34. package/dist/adapters/oclif/commands/cubes/create.mjs +8 -0
  35. package/dist/adapters/oclif/commands/cubes/delete.d.mts +6 -0
  36. package/dist/adapters/oclif/commands/cubes/delete.mjs +8 -0
  37. package/dist/adapters/oclif/commands/cubes/get.d.mts +6 -0
  38. package/dist/adapters/oclif/commands/cubes/get.mjs +8 -0
  39. package/dist/adapters/oclif/commands/cubes/list.d.mts +6 -0
  40. package/dist/adapters/oclif/commands/cubes/list.mjs +13 -0
  41. package/dist/adapters/oclif/commands/cubes/update.d.mts +6 -0
  42. package/dist/adapters/oclif/commands/cubes/update.mjs +8 -0
  43. package/dist/adapters/oclif/commands/diff.d.mts +28 -0
  44. package/dist/adapters/oclif/commands/diff.mjs +66 -0
  45. package/dist/adapters/oclif/commands/gservice-account-keys/get.mjs +1 -1
  46. package/dist/adapters/oclif/commands/gservice-account-keys/reveal.mjs +2 -2
  47. package/dist/adapters/oclif/commands/gservice-accounts/create.mjs +1 -1
  48. package/dist/adapters/oclif/commands/gservice-accounts/delete.mjs +1 -1
  49. package/dist/adapters/oclif/commands/gservice-accounts/get.mjs +1 -1
  50. package/dist/adapters/oclif/commands/gservice-accounts/list.mjs +7 -2
  51. package/dist/adapters/oclif/commands/init.d.mts +3 -1
  52. package/dist/adapters/oclif/commands/init.mjs +27 -23
  53. package/dist/adapters/oclif/commands/org/create.mjs +3 -2
  54. package/dist/adapters/oclif/commands/org/current.d.mts +12 -3
  55. package/dist/adapters/oclif/commands/org/current.mjs +27 -2
  56. package/dist/adapters/oclif/commands/org/get.mjs +3 -2
  57. package/dist/adapters/oclif/commands/org/list.d.mts +3 -11
  58. package/dist/adapters/oclif/commands/org/list.mjs +35 -26
  59. package/dist/adapters/oclif/commands/org/switch.d.mts +4 -2
  60. package/dist/adapters/oclif/commands/org/switch.mjs +16 -3
  61. package/dist/adapters/oclif/commands/pull.d.mts +29 -0
  62. package/dist/adapters/oclif/commands/pull.mjs +88 -0
  63. package/dist/adapters/oclif/commands/score-groups/create.mjs +3 -2
  64. package/dist/adapters/oclif/commands/score-groups/delete.mjs +1 -1
  65. package/dist/adapters/oclif/commands/score-groups/get.mjs +1 -1
  66. package/dist/adapters/oclif/commands/score-groups/list.mjs +3 -2
  67. package/dist/adapters/oclif/commands/score-groups/update.mjs +1 -1
  68. package/dist/adapters/oclif/commands/scores/create.mjs +3 -2
  69. package/dist/adapters/oclif/commands/scores/delete.mjs +1 -1
  70. package/dist/adapters/oclif/commands/scores/list.mjs +3 -2
  71. package/dist/adapters/oclif/commands/scores/update.mjs +1 -1
  72. package/dist/adapters/oclif/commands/segments/create.mjs +1 -1
  73. package/dist/adapters/oclif/commands/segments/delete.mjs +1 -1
  74. package/dist/adapters/oclif/commands/segments/evaluate.mjs +2 -2
  75. package/dist/adapters/oclif/commands/segments/get-evaluation-history.mjs +2 -2
  76. package/dist/adapters/oclif/commands/segments/get-version.mjs +2 -2
  77. package/dist/adapters/oclif/commands/segments/get.mjs +1 -1
  78. package/dist/adapters/oclif/commands/segments/list-versions.mjs +16 -5
  79. package/dist/adapters/oclif/commands/segments/list.mjs +9 -2
  80. package/dist/adapters/oclif/commands/segments/restore-version.mjs +2 -2
  81. package/dist/adapters/oclif/commands/segments/update.mjs +1 -1
  82. package/dist/adapters/oclif/commands/sources/create.d.mts +11 -0
  83. package/dist/adapters/oclif/commands/sources/create.mjs +16 -0
  84. package/dist/adapters/oclif/commands/sources/delete.d.mts +6 -0
  85. package/dist/adapters/oclif/commands/sources/delete.mjs +8 -0
  86. package/dist/adapters/oclif/commands/sources/get.d.mts +6 -0
  87. package/dist/adapters/oclif/commands/sources/get.mjs +8 -0
  88. package/dist/adapters/oclif/commands/sources/list-streams.d.mts +6 -0
  89. package/dist/adapters/oclif/commands/sources/list-streams.mjs +31 -0
  90. package/dist/adapters/oclif/commands/sources/list.d.mts +6 -0
  91. package/dist/adapters/oclif/commands/sources/list.mjs +13 -0
  92. package/dist/adapters/oclif/commands/{integrations/get.d.mts → sources/update.d.mts} +4 -4
  93. package/dist/adapters/oclif/commands/sources/update.mjs +21 -0
  94. package/dist/adapters/oclif/commands/status.d.mts +27 -0
  95. package/dist/adapters/oclif/commands/status.mjs +77 -0
  96. package/dist/adapters/oclif/commands/table-views/create.mjs +3 -2
  97. package/dist/adapters/oclif/commands/table-views/delete.mjs +1 -1
  98. package/dist/adapters/oclif/commands/table-views/list.mjs +3 -2
  99. package/dist/adapters/oclif/commands/table-views/update.mjs +1 -1
  100. package/dist/adapters/oclif/commands/tables/create.mjs +1 -1
  101. package/dist/adapters/oclif/commands/tables/delete.mjs +1 -1
  102. package/dist/adapters/oclif/commands/tables/get.mjs +1 -1
  103. package/dist/adapters/oclif/commands/tables/list.mjs +3 -2
  104. package/dist/adapters/oclif/commands/tables/update.mjs +1 -1
  105. package/dist/{base.command-d7VW6WTp.d.mts → base.command-BmddDbHa.d.mts} +4 -1
  106. package/dist/base.command-D8taHOFF.mjs +83 -0
  107. package/dist/chunk-CfYAbeIz.mjs +13 -0
  108. package/dist/context-D5uelKLe.d.mts +62 -0
  109. package/dist/core-B-IdeRNl.mjs +2448 -0
  110. package/dist/{factory-BrFKT8t-.mjs → factory-CCcimDhl.mjs} +45 -10
  111. package/dist/iac-render-BSZZEP0n.mjs +17 -0
  112. package/dist/index-D0ax2I61.d.mts +581 -0
  113. package/dist/index.d.mts +4 -4
  114. package/dist/index.mjs +2 -2
  115. package/dist/{presets-D9b6IWKy.mjs → presets-Bb9gwgeh.mjs} +40 -8
  116. package/dist/templates/.claude/settings.json +39 -0
  117. package/dist/templates/.devcontainer/Dockerfile +9 -0
  118. package/dist/templates/.devcontainer/devcontainer.json +4 -1
  119. package/dist/templates/.devcontainer/setup.sh +3 -0
  120. package/dist/templates/AGENTS.md +33 -20
  121. package/dist/templates/dbt/dbt_project.yml +2 -2
  122. package/dist/templates/gitignore +3 -1
  123. package/dist/templates/skills/create-connections/SKILL.md +210 -0
  124. package/dist/templates/skills/create-connections/references/mappers.md +152 -0
  125. package/dist/templates/skills/{create-semantic-model → create-cubes}/SKILL.md +20 -18
  126. package/dist/templates/skills/create-cubes/references/bq-pk-fk-conventions.md +183 -0
  127. package/dist/templates/skills/{create-semantic-model → create-cubes}/references/cube-examples.md +2 -2
  128. package/dist/templates/skills/create-cubes/references/hubspot-entities.md +289 -0
  129. package/dist/templates/skills/create-cubes/references/jira-entities.md +201 -0
  130. package/dist/templates/skills/create-cubes/references/netsuite-entities.md +121 -0
  131. package/dist/templates/skills/create-cubes/references/stripe-entities.md +114 -0
  132. package/dist/templates/skills/create-dbt-transformations/SKILL.md +43 -22
  133. package/dist/templates/skills/create-dbt-transformations/references/edge-cases.md +20 -2
  134. package/dist/templates/skills/create-dbt-transformations/references/schema-conventions.md +21 -7
  135. package/dist/templates/skills/create-dbt-transformations/references/sql-templates.md +34 -20
  136. package/dist/templates/skills/explore-lakehouse/SKILL.md +3 -3
  137. package/dist/templates/skills/load-sample-data/SKILL.md +1 -1
  138. package/dist/templates/skills/visualize-semantic-model/SKILL.md +159 -0
  139. package/dist/templates/skills/visualize-semantic-model/scripts/render_graph.py +186 -0
  140. package/dist/{types-Y_ht_ja5.d.mts → types-Bk2Cb5yt.d.mts} +9 -0
  141. package/package.json +44 -7
  142. package/dist/adapters/oclif/commands/integrations/create.d.mts +0 -11
  143. package/dist/adapters/oclif/commands/integrations/create.mjs +0 -16
  144. package/dist/adapters/oclif/commands/integrations/get.mjs +0 -21
  145. package/dist/adapters/oclif/commands/integrations/list.d.mts +0 -11
  146. package/dist/adapters/oclif/commands/integrations/list.mjs +0 -16
  147. package/dist/adapters/oclif/commands/integrations/update.d.mts +0 -15
  148. package/dist/adapters/oclif/commands/integrations/update.mjs +0 -21
  149. package/dist/adapters/oclif/commands/overlays/diff.d.mts +0 -19
  150. package/dist/adapters/oclif/commands/overlays/diff.mjs +0 -80
  151. package/dist/adapters/oclif/commands/overlays/pull.d.mts +0 -15
  152. package/dist/adapters/oclif/commands/overlays/pull.mjs +0 -45
  153. package/dist/adapters/oclif/commands/overlays/push.d.mts +0 -18
  154. package/dist/adapters/oclif/commands/overlays/push.mjs +0 -59
  155. package/dist/adapters/oclif/commands/overlays/status.d.mts +0 -18
  156. package/dist/adapters/oclif/commands/overlays/status.mjs +0 -53
  157. package/dist/base.command-YiwlGlKs.mjs +0 -62
  158. package/dist/core-jpFPylBb.mjs +0 -997
  159. package/dist/index-DD2Vr-pu.d.mts +0 -193
  160. package/dist/types-C_p_6rkj.d.mts +0 -69
  161. /package/dist/templates/skills/{create-semantic-model → create-cubes}/references/key-patterns.md +0 -0
  162. /package/dist/templates/skills/{create-semantic-model → create-cubes}/references/validation-queries.md +0 -0
@@ -1,997 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import * as os from "os";
4
- import { createServer } from "node:http";
5
- import * as crypto from "crypto";
6
- import { Client, client } from "@revos/api-client";
7
- import { parse, stringify } from "yaml";
8
- import search from "@inquirer/search";
9
- //#region src/core/errors.ts
10
- var ApiError = class extends Error {
11
- name = "ApiError";
12
- constructor(message, status, statusText, url, body) {
13
- super(message);
14
- this.status = status;
15
- this.statusText = statusText;
16
- this.url = url;
17
- this.body = body;
18
- }
19
- };
20
- //#endregion
21
- //#region src/core/auth/credentials-store.ts
22
- const REVOS_DIR = path.join(os.homedir(), ".revos");
23
- const CREDENTIALS_FILE = path.join(REVOS_DIR, "credentials.json");
24
- const isPosix = process.platform !== "win32";
25
- function getCredentialsPath() {
26
- return CREDENTIALS_FILE;
27
- }
28
- function loadCredentials() {
29
- try {
30
- if (!fs.existsSync(CREDENTIALS_FILE)) return null;
31
- const content = fs.readFileSync(CREDENTIALS_FILE, "utf-8");
32
- return JSON.parse(content);
33
- } catch {
34
- return null;
35
- }
36
- }
37
- function saveCredentials(credentials) {
38
- if (!fs.existsSync(REVOS_DIR)) fs.mkdirSync(REVOS_DIR, isPosix ? { mode: 448 } : void 0);
39
- fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), isPosix ? {
40
- mode: 384,
41
- encoding: "utf-8"
42
- } : { encoding: "utf-8" });
43
- }
44
- function deleteCredentials() {
45
- try {
46
- if (fs.existsSync(CREDENTIALS_FILE)) {
47
- fs.unlinkSync(CREDENTIALS_FILE);
48
- return true;
49
- }
50
- return false;
51
- } catch {
52
- return false;
53
- }
54
- }
55
- function isTokenExpired(credentials) {
56
- if (!credentials.expiresAt) return false;
57
- return credentials.expiresAt < Date.now() + 300 * 1e3;
58
- }
59
- //#endregion
60
- //#region src/core/auth/oauth-server.ts
61
- const CALLBACK_TIMEOUT_MS = 120 * 1e3;
62
- const OAUTH_CALLBACK_PORTS = [
63
- 54321,
64
- 54322,
65
- 54323
66
- ];
67
- async function startOAuthServer() {
68
- for (const port of OAUTH_CALLBACK_PORTS) try {
69
- return await tryStartServer(port);
70
- } catch (err) {
71
- if (err.code !== "EADDRINUSE") throw err;
72
- }
73
- throw new Error(`Could not start OAuth callback server. Ports ${OAUTH_CALLBACK_PORTS.join(", ")} are all in use.`);
74
- }
75
- function tryStartServer(port) {
76
- return new Promise((resolve, reject) => {
77
- let callbackResolve;
78
- let callbackReject;
79
- let timeoutId;
80
- const callbackPromise = new Promise((res, rej) => {
81
- callbackResolve = res;
82
- callbackReject = rej;
83
- });
84
- const server = createServer((req, res) => {
85
- const url = new URL(req.url ?? "/", `http://localhost:${port}`);
86
- if (url.pathname !== "/callback" || req.method !== "GET") {
87
- res.writeHead(404);
88
- res.end();
89
- return;
90
- }
91
- const code = url.searchParams.get("code") ?? void 0;
92
- const state = url.searchParams.get("state") ?? void 0;
93
- const error = url.searchParams.get("error") ?? void 0;
94
- const errorDescription = url.searchParams.get("error_description") ?? void 0;
95
- if (error) {
96
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
97
- res.end(getErrorHtml(errorDescription || error));
98
- callbackReject(new Error(errorDescription || error));
99
- return;
100
- }
101
- if (!code || !state) {
102
- res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
103
- res.end(getErrorHtml("Missing code or state parameter"));
104
- callbackReject(/* @__PURE__ */ new Error("Missing code or state parameter"));
105
- return;
106
- }
107
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
108
- res.end(getSuccessHtml());
109
- callbackResolve({
110
- code,
111
- state
112
- });
113
- });
114
- server.listen(port, () => {
115
- const shutdown = () => {
116
- clearTimeout(timeoutId);
117
- server.close();
118
- };
119
- const waitForCallback = async () => {
120
- timeoutId = setTimeout(() => {
121
- callbackReject(/* @__PURE__ */ new Error("Authentication timed out. Please try again."));
122
- shutdown();
123
- }, CALLBACK_TIMEOUT_MS);
124
- try {
125
- const result = await callbackPromise;
126
- shutdown();
127
- return result;
128
- } catch (error) {
129
- shutdown();
130
- throw error;
131
- }
132
- };
133
- resolve({
134
- port,
135
- waitForCallback,
136
- shutdown
137
- });
138
- });
139
- server.on("error", (err) => {
140
- reject(err);
141
- });
142
- });
143
- }
144
- function getSuccessHtml() {
145
- return `
146
- <!DOCTYPE html>
147
- <html>
148
- <head>
149
- <meta charset="utf-8">
150
- <link rel="icon" href="https://www.revos.ai/favicons/favicon.ico">
151
- <title>RevOS CLI - Authentication Successful</title>
152
- <style>
153
- body {
154
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
155
- display: flex;
156
- justify-content: center;
157
- align-items: center;
158
- height: 100vh;
159
- margin: 0;
160
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
161
- }
162
- .container {
163
- text-align: center;
164
- background: white;
165
- padding: 3rem;
166
- border-radius: 12px;
167
- box-shadow: 0 4px 24px rgba(0,0,0,0.15);
168
- }
169
- .checkmark {
170
- font-size: 4rem;
171
- margin-bottom: 1rem;
172
- }
173
- h1 { color: #333; margin-bottom: 0.5rem; }
174
- p { color: #666; }
175
- </style>
176
- </head>
177
- <body>
178
- <div class="container">
179
- <div class="checkmark">✓</div>
180
- <h1>Authentication Successful</h1>
181
- <p>You can close this window and return to the terminal.</p>
182
- </div>
183
- </body>
184
- </html>
185
- `;
186
- }
187
- function getErrorHtml(message) {
188
- return `
189
- <!DOCTYPE html>
190
- <html>
191
- <head>
192
- <meta charset="utf-8">
193
- <link rel="icon" href="https://www.revos.ai/favicons/favicon.ico">
194
- <title>RevOS CLI - Authentication Failed</title>
195
- <style>
196
- body {
197
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
198
- display: flex;
199
- justify-content: center;
200
- align-items: center;
201
- height: 100vh;
202
- margin: 0;
203
- background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
204
- }
205
- .container {
206
- text-align: center;
207
- background: white;
208
- padding: 3rem;
209
- border-radius: 12px;
210
- box-shadow: 0 4px 24px rgba(0,0,0,0.15);
211
- }
212
- .error-icon {
213
- font-size: 4rem;
214
- margin-bottom: 1rem;
215
- }
216
- h1 { color: #333; margin-bottom: 0.5rem; }
217
- p { color: #666; }
218
- .error-message { color: #e53e3e; margin-top: 1rem; }
219
- </style>
220
- </head>
221
- <body>
222
- <div class="container">
223
- <div class="error-icon">✗</div>
224
- <h1>Authentication Failed</h1>
225
- <p class="error-message">${escapeHtml(message)}</p>
226
- <p>Please close this window and try again.</p>
227
- </div>
228
- </body>
229
- </html>
230
- `;
231
- }
232
- function escapeHtml(text) {
233
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
234
- }
235
- //#endregion
236
- //#region src/core/auth/clerk-oauth.ts
237
- const CLERK_ENVS = {
238
- production: {
239
- domain: "https://clerk.revos.ai",
240
- clientId: "Hkj5lEtyyF5DUQX6"
241
- },
242
- development: {
243
- domain: "https://strong-minnow-46.clerk.accounts.dev",
244
- clientId: "o3eTaWPmeJAMfRPw"
245
- }
246
- };
247
- let configOverride = {};
248
- function setClerkConfig(config) {
249
- configOverride = config;
250
- }
251
- function getClerkDomain() {
252
- return configOverride.clerkDomain || process.env.REVOS_CLERK_DOMAIN || CLERK_ENVS.production.domain;
253
- }
254
- function getClerkClientId() {
255
- return configOverride.clerkClientId || process.env.REVOS_CLERK_CLIENT_ID || CLERK_ENVS.production.clientId;
256
- }
257
- function setClerkEnv(env) {
258
- const config = CLERK_ENVS[env];
259
- setClerkConfig({
260
- clerkDomain: config.domain,
261
- clerkClientId: config.clientId
262
- });
263
- }
264
- function generatePKCEChallenge() {
265
- const codeVerifier = crypto.randomBytes(32).toString("base64url");
266
- return {
267
- codeVerifier,
268
- codeChallenge: crypto.createHash("sha256").update(codeVerifier).digest("base64url"),
269
- state: crypto.randomBytes(16).toString("base64url")
270
- };
271
- }
272
- function buildAuthorizationUrl(redirectUri, pkce) {
273
- const clerkDomain = getClerkDomain();
274
- const clientId = getClerkClientId();
275
- return `${clerkDomain}/oauth/authorize?${new URLSearchParams({
276
- client_id: clientId,
277
- redirect_uri: redirectUri,
278
- response_type: "code",
279
- scope: "profile email offline_access",
280
- code_challenge: pkce.codeChallenge,
281
- code_challenge_method: "S256",
282
- state: pkce.state
283
- }).toString()}`;
284
- }
285
- async function exchangeCodeForTokens(code, redirectUri, codeVerifier) {
286
- const clerkDomain = getClerkDomain();
287
- const clientId = getClerkClientId();
288
- const params = new URLSearchParams({
289
- grant_type: "authorization_code",
290
- code,
291
- redirect_uri: redirectUri,
292
- code_verifier: codeVerifier,
293
- client_id: clientId
294
- });
295
- const response = await fetch(`${clerkDomain}/oauth/token`, {
296
- method: "POST",
297
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
298
- body: params.toString()
299
- });
300
- if (!response.ok) {
301
- const error = await response.text();
302
- throw new Error(`Token exchange failed: ${error}`);
303
- }
304
- return response.json();
305
- }
306
- async function getUserInfo(accessToken) {
307
- const clerkDomain = getClerkDomain();
308
- const response = await fetch(`${clerkDomain}/oauth/userinfo`, { headers: { Authorization: `Bearer ${accessToken}` } });
309
- if (!response.ok) {
310
- const error = await response.text();
311
- throw new Error(`Failed to get user info: ${error}`);
312
- }
313
- return response.json();
314
- }
315
- function tokenResponseToCredentials(tokenResponse, userInfo) {
316
- const expiresAt = tokenResponse.expires_in ? Date.now() + tokenResponse.expires_in * 1e3 : void 0;
317
- return {
318
- accessToken: tokenResponse.access_token,
319
- refreshToken: tokenResponse.refresh_token,
320
- expiresAt,
321
- userId: userInfo.sub,
322
- email: userInfo.email
323
- };
324
- }
325
- async function refreshAccessToken(refreshToken) {
326
- const clerkDomain = getClerkDomain();
327
- const clientId = getClerkClientId();
328
- const params = new URLSearchParams({
329
- grant_type: "refresh_token",
330
- refresh_token: refreshToken,
331
- client_id: clientId
332
- });
333
- const response = await fetch(`${clerkDomain}/oauth/token`, {
334
- method: "POST",
335
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
336
- body: params.toString()
337
- });
338
- if (!response.ok) {
339
- const error = await response.text();
340
- throw new Error(`Token refresh failed: ${error}`);
341
- }
342
- return response.json();
343
- }
344
- //#endregion
345
- //#region src/core/config.ts
346
- const DEFAULT_API_URL = "https://api.revos.ai";
347
- async function getStoredAccessToken() {
348
- const credentials = loadCredentials();
349
- if (!credentials) return null;
350
- if (!isTokenExpired(credentials)) return credentials.accessToken;
351
- if (!credentials.refreshToken) return null;
352
- try {
353
- const tokenResponse = await refreshAccessToken(credentials.refreshToken);
354
- const newCredentials = tokenResponseToCredentials(tokenResponse, await getUserInfo(tokenResponse.access_token));
355
- newCredentials.organizationId = credentials.organizationId;
356
- saveCredentials(newCredentials);
357
- return newCredentials.accessToken;
358
- } catch {
359
- return null;
360
- }
361
- }
362
- function getStoredOrganizationId() {
363
- return loadCredentials()?.organizationId ?? void 0;
364
- }
365
- async function getConfig() {
366
- const token = process.env.REVOS_TOKEN || await getStoredAccessToken();
367
- if (!token) throw new Error("Not authenticated. Run 'revos auth login' or set REVOS_TOKEN environment variable.");
368
- return {
369
- apiUrl: process.env.REVOS_API_URL || DEFAULT_API_URL,
370
- token,
371
- organizationId: process.env.REVOS_ORG_ID || getStoredOrganizationId()
372
- };
373
- }
374
- //#endregion
375
- //#region src/core/utils.ts
376
- function formatError(error) {
377
- return error instanceof Error ? error.message : String(error);
378
- }
379
- function sanitizeFileName(name) {
380
- return name.replace(/[/\\:*?"<>|]/g, "_");
381
- }
382
- function isContentEqual(local, remote) {
383
- const normalizeOverlay = (obj) => JSON.stringify({
384
- name: obj.name,
385
- description: obj.description || "",
386
- data: obj.data
387
- });
388
- return normalizeOverlay(local) === normalizeOverlay(remote);
389
- }
390
- function findRemoteOnlyOverlays(localOverlays, remoteOverlays) {
391
- const localNames = new Set(localOverlays.map((o) => o.overlay.name));
392
- return remoteOverlays.filter((r) => !localNames.has(r.name));
393
- }
394
- //#endregion
395
- //#region src/core/url.ts
396
- const DEFAULT_APP_URL = "https://app.revos.dev";
397
- /**
398
- * Derive the RevOS app (frontend) URL from the API URL.
399
- * `https://api.revos.ai` → `https://app.revos.ai`
400
- * `https://api.revos.dev` → `https://app.revos.dev`
401
- */
402
- function resolveAppUrl(apiUrl) {
403
- try {
404
- const parsed = new URL(apiUrl);
405
- const host = parsed.hostname;
406
- if (host.startsWith("api.")) {
407
- parsed.hostname = host.replace(/^api\./, "app.");
408
- parsed.pathname = "/";
409
- return parsed.origin;
410
- }
411
- } catch {}
412
- return DEFAULT_APP_URL;
413
- }
414
- //#endregion
415
- //#region src/core/api/create-client.ts
416
- function createApiClient(config) {
417
- const headers = {};
418
- if (config.organizationId) headers["X-Revos-Org"] = config.organizationId;
419
- client.setConfig({
420
- baseUrl: config.apiUrl,
421
- auth: config.token,
422
- headers
423
- });
424
- return new Client({ client });
425
- }
426
- function unwrap(result) {
427
- if (result.error) {
428
- const body = result.error;
429
- const status = result.response?.status ?? 0;
430
- const statusText = result.response?.statusText ?? "";
431
- const url = result.request?.url ?? "";
432
- let message;
433
- if (typeof body === "object" && body !== null && "message" in body) {
434
- const msg = body.message;
435
- message = typeof msg === "string" ? msg : JSON.stringify(msg);
436
- } else if (typeof body === "string") message = body;
437
- else message = JSON.stringify(body);
438
- throw new ApiError(message, status, statusText, url, body);
439
- }
440
- return result.data.data;
441
- }
442
- //#endregion
443
- //#region src/core/files/overlay-files.ts
444
- function loadOverlayFile(filePath) {
445
- const absolutePath = path.resolve(filePath);
446
- const stats = fs.statSync(absolutePath);
447
- const content = fs.readFileSync(absolutePath, "utf-8");
448
- let parsed;
449
- try {
450
- parsed = parse(content) ?? {};
451
- } catch (e) {
452
- throw new Error(`Invalid YAML in ${filePath}: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
453
- }
454
- const fileName = path.basename(filePath);
455
- return {
456
- filePath: absolutePath,
457
- fileName,
458
- overlay: {
459
- name: parsed.name || fileName.replace(/\.yml$/, "").replace(/\.yaml$/, ""),
460
- description: parsed.description || "",
461
- data: parsed
462
- },
463
- mtime: stats.mtime
464
- };
465
- }
466
- function loadOverlaysFromDir(dirPath) {
467
- const absoluteDir = path.resolve(dirPath);
468
- if (!fs.existsSync(absoluteDir)) throw new Error(`Directory not found: ${dirPath}`);
469
- const files = fs.readdirSync(absoluteDir);
470
- const overlays = [];
471
- for (const file of files) {
472
- if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
473
- const filePath = path.join(absoluteDir, file);
474
- if (!fs.statSync(filePath).isFile()) continue;
475
- try {
476
- overlays.push(loadOverlayFile(filePath));
477
- } catch (error) {
478
- throw new Error(`Failed to load ${file}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
479
- }
480
- }
481
- return overlays;
482
- }
483
- function loadOverlaysByNames(dirPath, names) {
484
- const absoluteDir = path.resolve(dirPath);
485
- if (!fs.existsSync(absoluteDir)) throw new Error(`Directory not found: ${dirPath}`);
486
- const overlays = [];
487
- for (const name of names) {
488
- let filePath = path.join(absoluteDir, name);
489
- if (!fs.existsSync(filePath)) {
490
- if (!name.endsWith(".yml") && !name.endsWith(".yaml")) {
491
- const withYml = path.join(absoluteDir, `${name}.yml`);
492
- if (fs.existsSync(withYml)) filePath = withYml;
493
- else filePath = path.join(absoluteDir, `${name}.yaml`);
494
- }
495
- }
496
- if (!fs.existsSync(filePath)) throw new Error(`Overlay file not found: ${name}`);
497
- overlays.push(loadOverlayFile(filePath));
498
- }
499
- return overlays;
500
- }
501
- function loadOverlays(dir, files) {
502
- return files && files.length > 0 ? loadOverlaysByNames(dir, files) : loadOverlaysFromDir(dir);
503
- }
504
- function saveOverlayToFile(dirPath, overlay) {
505
- const absoluteDir = path.resolve(dirPath);
506
- if (!fs.existsSync(absoluteDir)) fs.mkdirSync(absoluteDir, { recursive: true });
507
- const fileName = `${sanitizeFileName(overlay.name)}.yml`;
508
- const filePath = path.join(absoluteDir, fileName);
509
- const content = stringify({
510
- ...overlay.data,
511
- name: overlay.name,
512
- description: overlay.description
513
- });
514
- fs.writeFileSync(filePath, content);
515
- const updatedAt = new Date(overlay.updatedAt);
516
- fs.utimesSync(filePath, updatedAt, updatedAt);
517
- return filePath;
518
- }
519
- function getLocalOverlayNames(dirPath) {
520
- const absoluteDir = path.resolve(dirPath);
521
- if (!fs.existsSync(absoluteDir)) return [];
522
- const files = fs.readdirSync(absoluteDir);
523
- const names = [];
524
- for (const file of files) {
525
- if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue;
526
- const filePath = path.join(absoluteDir, file);
527
- try {
528
- const loaded = loadOverlayFile(filePath);
529
- names.push(loaded.overlay.name);
530
- } catch {}
531
- }
532
- return names;
533
- }
534
- //#endregion
535
- //#region src/core/services/push.service.ts
536
- var PushService = class {
537
- constructor(ctx) {
538
- this.ctx = ctx;
539
- }
540
- async execute(options) {
541
- const { api } = this.ctx;
542
- const localOverlays = loadOverlays(options.dir, options.files);
543
- if (localOverlays.length === 0) return {
544
- created: [],
545
- updated: [],
546
- skipped: [],
547
- conflicts: [],
548
- errors: []
549
- };
550
- const remoteOverlays = unwrap(await api.overlays.list());
551
- const remoteByName = new Map(remoteOverlays.map((o) => [o.name, o]));
552
- if (!options.files || options.files.length === 0) {
553
- const missingLocally = findRemoteOnlyOverlays(localOverlays, remoteOverlays);
554
- if (missingLocally.length > 0) return {
555
- created: [],
556
- updated: [],
557
- skipped: [],
558
- conflicts: missingLocally.map((o) => ({
559
- name: o.name,
560
- reason: "exists remotely but not locally"
561
- })),
562
- errors: []
563
- };
564
- }
565
- const result = {
566
- created: [],
567
- updated: [],
568
- skipped: [],
569
- conflicts: [],
570
- errors: []
571
- };
572
- for (const local of localOverlays) {
573
- const name = local.overlay.name;
574
- const remote = remoteByName.get(name);
575
- try {
576
- if (!remote) {
577
- unwrap(await api.overlays.create({ body: local.overlay }));
578
- result.created.push(name);
579
- } else {
580
- const remoteUpdatedAt = new Date(remote.updatedAt);
581
- const localMtime = local.mtime;
582
- if (remoteUpdatedAt > localMtime && !options.force) result.conflicts.push({
583
- name,
584
- reason: `Remote modified at ${remoteUpdatedAt.toISOString()}, local file at ${localMtime.toISOString()}`
585
- });
586
- else {
587
- unwrap(await api.overlays.update({
588
- id: remote.id,
589
- body: local.overlay
590
- }));
591
- result.updated.push(name);
592
- }
593
- }
594
- } catch (error) {
595
- result.errors.push({
596
- name,
597
- error: formatError(error)
598
- });
599
- }
600
- }
601
- return result;
602
- }
603
- };
604
- //#endregion
605
- //#region src/core/services/pull.service.ts
606
- var PullService = class {
607
- constructor(ctx) {
608
- this.ctx = ctx;
609
- }
610
- async execute(options) {
611
- const { api } = this.ctx;
612
- let overlays = unwrap(await api.overlays.list());
613
- const result = {
614
- pulled: [],
615
- notFound: [],
616
- errors: []
617
- };
618
- if (options.names && options.names.length > 0) {
619
- const requestedNames = new Set(options.names);
620
- const foundNames = /* @__PURE__ */ new Set();
621
- overlays = overlays.filter((o) => {
622
- if (requestedNames.has(o.name)) {
623
- foundNames.add(o.name);
624
- return true;
625
- }
626
- return false;
627
- });
628
- result.notFound = options.names.filter((n) => !foundNames.has(n));
629
- }
630
- if (overlays.length === 0 && result.notFound.length === 0) return result;
631
- for (const overlay of overlays) try {
632
- const filePath = saveOverlayToFile(options.dir, overlay);
633
- result.pulled.push({
634
- name: overlay.name,
635
- filePath
636
- });
637
- } catch (error) {
638
- result.errors.push({
639
- name: overlay.name,
640
- error: formatError(error)
641
- });
642
- }
643
- return result;
644
- }
645
- };
646
- //#endregion
647
- //#region src/core/services/status.service.ts
648
- var StatusService = class {
649
- constructor(ctx) {
650
- this.ctx = ctx;
651
- }
652
- async execute(options) {
653
- const { api } = this.ctx;
654
- let localOverlays = [];
655
- const absoluteDir = path.resolve(options.dir);
656
- if (fs.existsSync(absoluteDir)) try {
657
- localOverlays = loadOverlays(options.dir, options.files);
658
- } catch (error) {
659
- if (!formatError(error).includes("not found")) throw error;
660
- }
661
- const remoteOverlays = unwrap(await api.overlays.list());
662
- const remoteByName = new Map(remoteOverlays.map((o) => [o.name, o]));
663
- const statuses = [];
664
- for (const local of localOverlays) {
665
- const name = local.overlay.name;
666
- const remote = remoteByName.get(name);
667
- if (!remote) statuses.push({
668
- name,
669
- status: "new",
670
- localMtime: local.mtime
671
- });
672
- else {
673
- const remoteUpdatedAt = new Date(remote.updatedAt);
674
- const localMtime = local.mtime;
675
- if (isContentEqual(local.overlay, remote)) statuses.push({
676
- name,
677
- status: "synced",
678
- localMtime,
679
- remoteUpdatedAt
680
- });
681
- else if (remoteUpdatedAt > localMtime) statuses.push({
682
- name,
683
- status: "conflict",
684
- localMtime,
685
- remoteUpdatedAt
686
- });
687
- else statuses.push({
688
- name,
689
- status: "modified",
690
- localMtime,
691
- remoteUpdatedAt
692
- });
693
- }
694
- }
695
- if (!options.files || options.files.length === 0) {
696
- const remoteOnly = findRemoteOnlyOverlays(localOverlays, remoteOverlays);
697
- for (const remote of remoteOnly) statuses.push({
698
- name: remote.name,
699
- status: "remote-only",
700
- remoteUpdatedAt: new Date(remote.updatedAt)
701
- });
702
- }
703
- const statusOrder = {
704
- conflict: 0,
705
- "remote-only": 1,
706
- new: 2,
707
- modified: 3,
708
- synced: 4
709
- };
710
- statuses.sort((a, b) => statusOrder[a.status] - statusOrder[b.status]);
711
- return {
712
- statuses,
713
- hasConflicts: statuses.some((s) => s.status === "conflict"),
714
- hasRemoteOnly: statuses.some((s) => s.status === "remote-only")
715
- };
716
- }
717
- };
718
- //#endregion
719
- //#region src/core/services/diff.service.ts
720
- var DiffService = class {
721
- constructor(ctx) {
722
- this.ctx = ctx;
723
- }
724
- async execute(options) {
725
- const { api } = this.ctx;
726
- const localOverlays = loadOverlays(options.dir, options.files);
727
- if (localOverlays.length === 0) return { entries: [] };
728
- const remoteOverlays = unwrap(await api.overlays.list());
729
- const remoteByName = new Map(remoteOverlays.map((o) => [o.name, o]));
730
- const entries = [];
731
- if (!options.files || options.files.length === 0) {
732
- const remoteOnly = findRemoteOnlyOverlays(localOverlays, remoteOverlays);
733
- for (const overlay of remoteOnly) entries.push({
734
- name: overlay.name,
735
- isNew: false,
736
- isRemoteOnly: true,
737
- isSynced: false,
738
- changes: []
739
- });
740
- }
741
- for (const local of localOverlays) {
742
- const name = local.overlay.name;
743
- const remote = remoteByName.get(name);
744
- entries.push(this.computeDiff(local.overlay, remote));
745
- }
746
- return { entries };
747
- }
748
- computeDiff(local, remote) {
749
- if (!remote) return {
750
- name: local.name,
751
- isNew: true,
752
- isRemoteOnly: false,
753
- isSynced: false,
754
- changes: this.getNewOverlayChanges(local)
755
- };
756
- if (isContentEqual(local, remote)) return {
757
- name: local.name,
758
- isNew: false,
759
- isRemoteOnly: false,
760
- isSynced: true,
761
- changes: []
762
- };
763
- const changes = [];
764
- if (local.name !== remote.name) changes.push({
765
- field: "name",
766
- type: "modified",
767
- oldValue: remote.name,
768
- newValue: local.name
769
- });
770
- const localDesc = local.description || "";
771
- const remoteDesc = remote.description || "";
772
- if (localDesc !== remoteDesc) changes.push({
773
- field: "description",
774
- type: "modified",
775
- oldValue: remoteDesc || "(empty)",
776
- newValue: localDesc || "(empty)"
777
- });
778
- const localDataStr = JSON.stringify(local.data, null, 2);
779
- const remoteDataStr = JSON.stringify(remote.data, null, 2);
780
- if (localDataStr !== remoteDataStr) changes.push({
781
- field: "data",
782
- type: "modified",
783
- oldValue: remoteDataStr,
784
- newValue: localDataStr
785
- });
786
- return {
787
- name: local.name,
788
- isNew: false,
789
- isRemoteOnly: false,
790
- isSynced: false,
791
- changes
792
- };
793
- }
794
- getNewOverlayChanges(local) {
795
- const changes = [];
796
- changes.push({
797
- field: "name",
798
- type: "added",
799
- newValue: local.name
800
- });
801
- if (local.description) changes.push({
802
- field: "description",
803
- type: "added",
804
- newValue: local.description
805
- });
806
- changes.push({
807
- field: "data",
808
- type: "added",
809
- newValue: JSON.stringify(local.data, null, 2)
810
- });
811
- return changes;
812
- }
813
- };
814
- //#endregion
815
- //#region src/core/services/org-selector.ts
816
- async function selectOrganization(organizations) {
817
- const answer = await search({
818
- message: "Select an organization",
819
- source: (input) => {
820
- const term = (input ?? "").toLowerCase();
821
- return organizations.filter((org) => org.name.toLowerCase().includes(term)).map((org) => ({
822
- name: org.name,
823
- value: org.id
824
- }));
825
- }
826
- });
827
- const selected = organizations.find((o) => o.id === answer);
828
- if (!selected) throw new Error("Organization not found");
829
- return selected;
830
- }
831
- //#endregion
832
- //#region src/core/services/init.service.ts
833
- var InitService = class InitService {
834
- static PROJECT_DIRS = [
835
- ".devcontainer",
836
- ".claude/skills/explore-lakehouse",
837
- ".claude/skills/create-semantic-model",
838
- ".claude/skills/create-semantic-model/references",
839
- ".claude/skills/create-dbt-transformations",
840
- ".claude/skills/create-dbt-transformations/references",
841
- ".claude/skills/load-sample-data",
842
- "dbt/models/bronze",
843
- "dbt/models/silver",
844
- "dbt/models/gold",
845
- "semantic"
846
- ];
847
- static PROJECT_FILES = [
848
- ".devcontainer/devcontainer.json",
849
- ".devcontainer/Dockerfile",
850
- ".devcontainer/setup.sh",
851
- ".gitignore",
852
- "README.md",
853
- "dbt/profiles.yml",
854
- "dbt/dbt_project.yml",
855
- "CLAUDE.md",
856
- "AGENTS.md",
857
- ".claude/skills/explore-lakehouse/SKILL.md",
858
- ".claude/skills/create-semantic-model/SKILL.md",
859
- ".claude/skills/create-semantic-model/references/cube-examples.md",
860
- ".claude/skills/create-semantic-model/references/key-patterns.md",
861
- ".claude/skills/create-semantic-model/references/validation-queries.md",
862
- ".claude/skills/create-dbt-transformations/SKILL.md",
863
- ".claude/skills/create-dbt-transformations/references/sql-templates.md",
864
- ".claude/skills/create-dbt-transformations/references/schema-conventions.md",
865
- ".claude/skills/create-dbt-transformations/references/edge-cases.md",
866
- ".claude/skills/load-sample-data/SKILL.md",
867
- "dbt/models/bronze/.gitkeep",
868
- "dbt/models/silver/.gitkeep",
869
- "dbt/models/gold/.gitkeep",
870
- "semantic/.gitkeep"
871
- ];
872
- constructor(templatesDir) {
873
- this.templatesDir = templatesDir;
874
- }
875
- async run(options) {
876
- const { projectName, targetDir, apiUrl, token, organizationId } = options;
877
- const projectDir = path.join(targetDir, projectName);
878
- const projectSlug = projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
879
- const org = options.organization ?? await this.resolveOrganization(apiUrl, token, organizationId);
880
- const gcpProjectId = await this.downloadGcpKey(apiUrl, token, org, projectSlug);
881
- const resolvedOrg = {
882
- ...org,
883
- bqProjectId: org.bqProjectId || gcpProjectId,
884
- bqLocation: org.bqLocation || "europe-west3"
885
- };
886
- return {
887
- projectDir,
888
- organization: resolvedOrg,
889
- createdFiles: this.scaffold(projectDir, projectName, projectSlug, resolvedOrg, options.allowExistingDir)
890
- };
891
- }
892
- async resolveOrganization(apiUrl, token, organizationId) {
893
- const api = createApiClient({
894
- apiUrl,
895
- token
896
- });
897
- if (organizationId) {
898
- const full = unwrap(await api.organizations.get({ id: organizationId }));
899
- if (!full.bqDataset) throw new Error("Organization is missing BigQuery dataset configuration. Contact support.");
900
- return full;
901
- }
902
- const orgs = unwrap(await api.organizations.list()) ?? [];
903
- if (orgs.length === 0) throw new Error("No organizations found for this account.");
904
- const selected = orgs.length === 1 ? orgs[0] : await selectOrganization(orgs);
905
- const full = unwrap(await api.organizations.get({ id: selected.id }));
906
- if (!full.bqDataset) throw new Error("Organization is missing BigQuery dataset configuration. Contact support.");
907
- return full;
908
- }
909
- async downloadGcpKey(apiUrl, token, org, projectSlug) {
910
- const api = createApiClient({
911
- apiUrl,
912
- token,
913
- organizationId: org.id
914
- });
915
- const keyId = unwrap(await api.gserviceAccounts.create({ body: { name: projectSlug } }))?.gServiceAccountKeys?.[0]?.id;
916
- if (!keyId) throw new Error("Service account has no keys");
917
- const keyJson = unwrap(await api.gserviceAccountKeys.reveal({ id: keyId }))?.key;
918
- if (!keyJson) throw new Error("Service account key is empty");
919
- const gcpKeyPath = path.join(os.homedir(), ".revos", `${projectSlug}-gsa-creds.json`);
920
- fs.mkdirSync(path.dirname(gcpKeyPath), { recursive: true });
921
- fs.writeFileSync(gcpKeyPath, keyJson, process.platform !== "win32" ? {
922
- encoding: "utf-8",
923
- mode: 384
924
- } : { encoding: "utf-8" });
925
- return JSON.parse(keyJson).project_id ?? "";
926
- }
927
- dryRun(projectName, targetDir) {
928
- return {
929
- projectDir: path.join(targetDir, projectName),
930
- dirs: InitService.PROJECT_DIRS,
931
- files: InitService.PROJECT_FILES
932
- };
933
- }
934
- scaffold(projectDir, projectName, projectSlug, org, allowExistingDir) {
935
- if (fs.existsSync(projectDir) && !allowExistingDir) throw new Error(`Directory "${projectName}" already exists. Remove it or choose a different name.`);
936
- const created = [];
937
- const dirs = InitService.PROJECT_DIRS;
938
- for (const dir of dirs) fs.mkdirSync(path.join(projectDir, dir), { recursive: true });
939
- const dbtName = projectName.replace(/[^a-zA-Z0-9_]/g, "_");
940
- const files = {
941
- ".devcontainer/devcontainer.json": this.renderTemplate(".devcontainer/devcontainer.json", {
942
- projectName,
943
- projectSlug,
944
- bqProjectId: org.bqProjectId || "",
945
- bqDataset: org.bqDataset,
946
- organizationId: org.id
947
- }),
948
- ".devcontainer/Dockerfile": this.renderTemplate(".devcontainer/Dockerfile", {}),
949
- ".devcontainer/setup.sh": this.renderTemplate(".devcontainer/setup.sh", {}),
950
- ".gitignore": this.renderTemplate("gitignore", {}),
951
- "README.md": this.renderTemplate("README.md", {
952
- projectName,
953
- orgName: org.name
954
- }),
955
- "dbt/profiles.yml": this.renderTemplate("dbt/profiles.yml", { bqLocation: org.bqLocation ?? "europe-west3" }),
956
- "dbt/dbt_project.yml": this.renderTemplate("dbt/dbt_project.yml", { dbtName }),
957
- "CLAUDE.md": this.renderTemplate("CLAUDE.md", {}),
958
- "AGENTS.md": this.renderTemplate("AGENTS.md", {
959
- projectName,
960
- orgName: org.name
961
- }),
962
- ".claude/skills/explore-lakehouse/SKILL.md": this.renderTemplate("skills/explore-lakehouse/SKILL.md", {
963
- bqProjectId: org.bqProjectId || "",
964
- bqDataset: org.bqDataset
965
- }),
966
- ".claude/skills/create-semantic-model/SKILL.md": this.renderTemplate("skills/create-semantic-model/SKILL.md", {}),
967
- ".claude/skills/create-semantic-model/references/cube-examples.md": this.renderTemplate("skills/create-semantic-model/references/cube-examples.md", {}),
968
- ".claude/skills/create-semantic-model/references/key-patterns.md": this.renderTemplate("skills/create-semantic-model/references/key-patterns.md", {}),
969
- ".claude/skills/create-semantic-model/references/validation-queries.md": this.renderTemplate("skills/create-semantic-model/references/validation-queries.md", {}),
970
- ".claude/skills/create-dbt-transformations/SKILL.md": this.renderTemplate("skills/create-dbt-transformations/SKILL.md", {}),
971
- ".claude/skills/create-dbt-transformations/references/sql-templates.md": this.renderTemplate("skills/create-dbt-transformations/references/sql-templates.md", {}),
972
- ".claude/skills/create-dbt-transformations/references/schema-conventions.md": this.renderTemplate("skills/create-dbt-transformations/references/schema-conventions.md", {}),
973
- ".claude/skills/create-dbt-transformations/references/edge-cases.md": this.renderTemplate("skills/create-dbt-transformations/references/edge-cases.md", {}),
974
- ".claude/skills/load-sample-data/SKILL.md": this.renderTemplate("skills/load-sample-data/SKILL.md", {}),
975
- "dbt/models/bronze/.gitkeep": "",
976
- "dbt/models/silver/.gitkeep": "",
977
- "dbt/models/gold/.gitkeep": "",
978
- "semantic/.gitkeep": ""
979
- };
980
- for (const [rel, content] of Object.entries(files)) {
981
- const full = path.join(projectDir, rel);
982
- fs.writeFileSync(full, content, "utf-8");
983
- if (rel.endsWith(".sh") && process.platform !== "win32") fs.chmodSync(full, 493);
984
- created.push(rel);
985
- }
986
- return created;
987
- }
988
- renderTemplate(relativePath, vars) {
989
- const filePath = path.join(this.templatesDir, relativePath);
990
- if (!fs.existsSync(filePath)) throw new Error(`Template "${relativePath}" not found — try reinstalling the revos CLI`);
991
- let content = fs.readFileSync(filePath, "utf-8");
992
- for (const [key, value] of Object.entries(vars)) content = content.replaceAll(`<%=${key}%>`, value);
993
- return content;
994
- }
995
- };
996
- //#endregion
997
- export { deleteCredentials as A, generatePKCEChallenge as C, setClerkEnv as D, setClerkConfig as E, ApiError as F, isTokenExpired as M, loadCredentials as N, tokenResponseToCredentials as O, saveCredentials as P, exchangeCodeForTokens as S, refreshAccessToken as T, formatError as _, PullService as a, getConfig as b, loadOverlayFile as c, loadOverlaysFromDir as d, saveOverlayToFile as f, findRemoteOnlyOverlays as g, resolveAppUrl as h, StatusService as i, getCredentialsPath as j, startOAuthServer as k, loadOverlays as l, unwrap as m, selectOrganization as n, PushService as o, createApiClient as p, DiffService as r, getLocalOverlayNames as s, InitService as t, loadOverlaysByNames as u, isContentEqual as v, getUserInfo as w, buildAuthorizationUrl as x, sanitizeFileName as y };