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