clawnify 0.1.1

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.
@@ -0,0 +1,325 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/auth.ts
4
+ import fs from "fs";
5
+ import path from "path";
6
+ import crypto from "crypto";
7
+ import { homedir } from "os";
8
+ var SUPABASE_URL = "https://auth.clawnify.com";
9
+ var OAUTH_CLIENT_ID = "3defc0e6-80de-4e7a-aa36-0b501c06b697";
10
+ var OAUTH_AUTHORIZE_URL = `${SUPABASE_URL}/auth/v1/oauth/authorize`;
11
+ var OAUTH_TOKEN_URL = `${SUPABASE_URL}/auth/v1/oauth/token`;
12
+ var AUTH_DIR = path.join(homedir(), ".clawnify");
13
+ var AUTH_FILE = path.join(AUTH_DIR, "auth.json");
14
+ function loadAuth() {
15
+ try {
16
+ const data = fs.readFileSync(AUTH_FILE, "utf-8");
17
+ return JSON.parse(data);
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+ function saveAuth(data) {
23
+ fs.mkdirSync(AUTH_DIR, { recursive: true, mode: 448 });
24
+ fs.writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 384 });
25
+ }
26
+ function clearAuth() {
27
+ try {
28
+ fs.unlinkSync(AUTH_FILE);
29
+ } catch {
30
+ }
31
+ }
32
+ async function getValidToken() {
33
+ const auth = loadAuth();
34
+ if (!auth) return null;
35
+ const now = Math.floor(Date.now() / 1e3);
36
+ if (auth.expires_at - 60 > now) {
37
+ return auth.access_token;
38
+ }
39
+ const res = await fetch(OAUTH_TOKEN_URL, {
40
+ method: "POST",
41
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
42
+ body: new URLSearchParams({
43
+ grant_type: "refresh_token",
44
+ refresh_token: auth.refresh_token,
45
+ client_id: OAUTH_CLIENT_ID
46
+ })
47
+ });
48
+ if (!res.ok) {
49
+ clearAuth();
50
+ return null;
51
+ }
52
+ const tokens = await res.json();
53
+ const newAuth = {
54
+ access_token: tokens.access_token,
55
+ refresh_token: tokens.refresh_token,
56
+ expires_at: Math.floor(Date.now() / 1e3) + tokens.expires_in
57
+ };
58
+ saveAuth(newAuth);
59
+ return tokens.access_token;
60
+ }
61
+ async function requireAuth() {
62
+ const token = await getValidToken();
63
+ if (!token) {
64
+ console.error("Not logged in. Run `clawnify login` first.");
65
+ process.exit(1);
66
+ }
67
+ return loadAuth();
68
+ }
69
+ function getEmailFromToken(token) {
70
+ try {
71
+ const payload = token.split(".")[1];
72
+ const decoded = JSON.parse(
73
+ Buffer.from(payload, "base64url").toString("utf-8")
74
+ );
75
+ return decoded.email ?? null;
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+ function generatePkce() {
81
+ const codeVerifier = crypto.randomBytes(32).toString("base64url");
82
+ const codeChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");
83
+ return { codeVerifier, codeChallenge };
84
+ }
85
+
86
+ // src/lib/api.ts
87
+ import readline from "readline";
88
+ var API_BASE = "https://provision.clawnify.com";
89
+ async function apiRequest(method, path4, options) {
90
+ const headers = {};
91
+ if (options?.token) {
92
+ headers["Authorization"] = `Bearer ${options.token}`;
93
+ }
94
+ if (options?.orgId) {
95
+ headers["x-org-id"] = options.orgId;
96
+ }
97
+ let body;
98
+ if (options?.formData) {
99
+ body = options.formData;
100
+ } else if (options?.body) {
101
+ headers["Content-Type"] = "application/json";
102
+ body = JSON.stringify(options.body);
103
+ }
104
+ const res = await fetch(`${API_BASE}${path4}`, { method, headers, body });
105
+ if (res.status === 401) {
106
+ console.error("Session expired. Run `clawnify login` again.");
107
+ process.exit(1);
108
+ }
109
+ return res;
110
+ }
111
+ async function ensureOrg(auth) {
112
+ if (auth.org_id) return auth;
113
+ const orgsRes = await apiRequest("GET", "/v1/orgs", { token: auth.access_token });
114
+ if (!orgsRes.ok) return auth;
115
+ const { orgs } = await orgsRes.json();
116
+ if (orgs.length === 0) return auth;
117
+ let orgId;
118
+ if (orgs.length === 1) {
119
+ orgId = orgs[0].id;
120
+ } else {
121
+ console.log("\nSelect an organization:\n");
122
+ orgs.forEach((org, i) => {
123
+ console.log(` ${i + 1}) ${org.name} (${org.role})`);
124
+ });
125
+ console.log();
126
+ const rl = readline.createInterface({
127
+ input: process.stdin,
128
+ output: process.stdout
129
+ });
130
+ const choice = await new Promise((resolve) => {
131
+ rl.question("Enter number: ", (answer) => {
132
+ rl.close();
133
+ resolve(answer.trim());
134
+ });
135
+ });
136
+ const index = parseInt(choice, 10) - 1;
137
+ orgId = index >= 0 && index < orgs.length ? orgs[index].id : orgs[0].id;
138
+ console.log(`Selected: ${orgs[index >= 0 && index < orgs.length ? index : 0].name}
139
+ `);
140
+ }
141
+ const updated = { ...auth, org_id: orgId };
142
+ saveAuth(updated);
143
+ return updated;
144
+ }
145
+ async function authedRequest(method, path4, options) {
146
+ let auth = await requireAuth();
147
+ auth = await ensureOrg(auth);
148
+ return apiRequest(method, path4, {
149
+ ...options,
150
+ token: auth.access_token,
151
+ orgId: auth.org_id
152
+ });
153
+ }
154
+ var api = {
155
+ get: (path4) => authedRequest("GET", path4),
156
+ post: (path4, body) => authedRequest("POST", path4, { body }),
157
+ postForm: (path4, formData) => authedRequest("POST", path4, { formData }),
158
+ del: (path4) => authedRequest("DELETE", path4),
159
+ request: apiRequest
160
+ };
161
+
162
+ // src/lib/archive.ts
163
+ import fs2 from "fs";
164
+ import path2 from "path";
165
+ import { execSync } from "child_process";
166
+ import { gzipSync } from "zlib";
167
+ var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
168
+ "node_modules",
169
+ ".git",
170
+ "dist",
171
+ ".next",
172
+ ".clawnify",
173
+ "build"
174
+ ]);
175
+ var MAX_SIZE = 20 * 1024 * 1024;
176
+ function getTrackedFiles(dir) {
177
+ try {
178
+ const output = execSync("git ls-files -z", {
179
+ cwd: dir,
180
+ encoding: "utf-8",
181
+ stdio: ["pipe", "pipe", "pipe"]
182
+ });
183
+ return output.split("\0").filter(Boolean);
184
+ } catch {
185
+ return null;
186
+ }
187
+ }
188
+ function walkDir(dir, base = "") {
189
+ const files = [];
190
+ for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
191
+ if (EXCLUDE_DIRS.has(entry.name)) continue;
192
+ const rel = base ? `${base}/${entry.name}` : entry.name;
193
+ const full = path2.join(dir, entry.name);
194
+ if (entry.isDirectory()) {
195
+ files.push(...walkDir(full, rel));
196
+ } else if (entry.isFile()) {
197
+ files.push(rel);
198
+ }
199
+ }
200
+ return files;
201
+ }
202
+ function createTarHeader(filename, size) {
203
+ const header = Buffer.alloc(512);
204
+ header.write(filename, 0, Math.min(filename.length, 100), "utf-8");
205
+ header.write("0000644\0", 100, 8, "utf-8");
206
+ header.write("0000000\0", 108, 8, "utf-8");
207
+ header.write("0000000\0", 116, 8, "utf-8");
208
+ header.write(size.toString(8).padStart(11, "0") + "\0", 124, 12, "utf-8");
209
+ const mtime = Math.floor(Date.now() / 1e3);
210
+ header.write(mtime.toString(8).padStart(11, "0") + "\0", 136, 12, "utf-8");
211
+ header.write("0", 156, 1, "utf-8");
212
+ header.write("ustar\0", 257, 6, "utf-8");
213
+ header.write("00", 263, 2, "utf-8");
214
+ header.write(" ", 148, 8, "utf-8");
215
+ let checksum = 0;
216
+ for (let i = 0; i < 512; i++) {
217
+ checksum += header[i];
218
+ }
219
+ header.write(checksum.toString(8).padStart(6, "0") + "\0 ", 148, 8, "utf-8");
220
+ return header;
221
+ }
222
+ function createArchive(dir) {
223
+ const files = getTrackedFiles(dir) ?? walkDir(dir);
224
+ const parts = [];
225
+ for (const file of files) {
226
+ const fullPath = path2.join(dir, file);
227
+ let stat;
228
+ try {
229
+ stat = fs2.statSync(fullPath);
230
+ } catch {
231
+ continue;
232
+ }
233
+ if (!stat.isFile()) continue;
234
+ const content = fs2.readFileSync(fullPath);
235
+ const header = createTarHeader(file, content.length);
236
+ parts.push(header);
237
+ parts.push(content);
238
+ const remainder = content.length % 512;
239
+ if (remainder > 0) {
240
+ parts.push(Buffer.alloc(512 - remainder));
241
+ }
242
+ }
243
+ parts.push(Buffer.alloc(1024));
244
+ const tar = Buffer.concat(parts);
245
+ const gzipped = gzipSync(tar);
246
+ if (gzipped.length > MAX_SIZE) {
247
+ throw new Error(
248
+ `Archive too large (${(gzipped.length / 1024 / 1024).toFixed(1)}MB). Maximum is 20MB.`
249
+ );
250
+ }
251
+ return gzipped;
252
+ }
253
+
254
+ // src/lib/config.ts
255
+ import fs3 from "fs";
256
+ import path3 from "path";
257
+ function readManifest(dir = process.cwd()) {
258
+ try {
259
+ const data = fs3.readFileSync(path3.join(dir, "clawnify.json"), "utf-8");
260
+ const manifest = JSON.parse(data);
261
+ if (!manifest.name || !manifest.app) return null;
262
+ return manifest;
263
+ } catch {
264
+ return null;
265
+ }
266
+ }
267
+ function readProject(dir = process.cwd()) {
268
+ try {
269
+ const data = fs3.readFileSync(
270
+ path3.join(dir, ".clawnify", "project.json"),
271
+ "utf-8"
272
+ );
273
+ return JSON.parse(data);
274
+ } catch {
275
+ return null;
276
+ }
277
+ }
278
+ function saveProject(dir, data) {
279
+ const projDir = path3.join(dir, ".clawnify");
280
+ fs3.mkdirSync(projDir, { recursive: true });
281
+ fs3.writeFileSync(
282
+ path3.join(projDir, "project.json"),
283
+ JSON.stringify(data, null, 2)
284
+ );
285
+ }
286
+
287
+ // src/lib/poll.ts
288
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
289
+ async function pollUntilReady(appId, interval = 3e3, timeout = 3e5) {
290
+ const start = Date.now();
291
+ let frame = 0;
292
+ while (Date.now() - start < timeout) {
293
+ const res = await api.get(`/v1/apps/${appId}`);
294
+ const data = await res.json();
295
+ const spinner = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
296
+ const message = data.status_message || data.status;
297
+ process.stdout.write(`\r${spinner} ${message}`);
298
+ frame++;
299
+ if (data.status === "live" || data.status === "error") {
300
+ process.stdout.write("\r\x1B[K");
301
+ return data;
302
+ }
303
+ await new Promise((r) => setTimeout(r, interval));
304
+ }
305
+ process.stdout.write("\r\x1B[K");
306
+ throw new Error("Timed out waiting for deployment");
307
+ }
308
+
309
+ export {
310
+ OAUTH_CLIENT_ID,
311
+ OAUTH_AUTHORIZE_URL,
312
+ OAUTH_TOKEN_URL,
313
+ loadAuth,
314
+ saveAuth,
315
+ getValidToken,
316
+ getEmailFromToken,
317
+ generatePkce,
318
+ api,
319
+ createArchive,
320
+ readManifest,
321
+ readProject,
322
+ saveProject,
323
+ pollUntilReady
324
+ };
325
+ //# sourceMappingURL=chunk-6UAOYVZ6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/auth.ts","../src/lib/api.ts","../src/lib/archive.ts","../src/lib/config.ts","../src/lib/poll.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport crypto from \"node:crypto\";\nimport { homedir } from \"node:os\";\n\nconst SUPABASE_URL = \"https://auth.clawnify.com\";\nconst SUPABASE_ANON_KEY =\n \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imh0a3l4Zm1pbXlkeG5iemd4aGZ6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzgzNDk5MjEsImV4cCI6MjA1MzkyNTkyMX0.wopxB_gQSbNMPuxLUNK0BrGXE_0JxJqzMYFPeXFV1GE\";\nexport const OAUTH_CLIENT_ID = \"3defc0e6-80de-4e7a-aa36-0b501c06b697\";\nexport const OAUTH_AUTHORIZE_URL = `${SUPABASE_URL}/auth/v1/oauth/authorize`;\nexport const OAUTH_TOKEN_URL = `${SUPABASE_URL}/auth/v1/oauth/token`;\n\nconst AUTH_DIR = path.join(homedir(), \".clawnify\");\nconst AUTH_FILE = path.join(AUTH_DIR, \"auth.json\");\n\nexport interface AuthData {\n access_token: string;\n refresh_token: string;\n expires_at: number;\n org_id?: string;\n}\n\nexport function loadAuth(): AuthData | null {\n try {\n const data = fs.readFileSync(AUTH_FILE, \"utf-8\");\n return JSON.parse(data) as AuthData;\n } catch {\n return null;\n }\n}\n\nexport function saveAuth(data: AuthData): void {\n fs.mkdirSync(AUTH_DIR, { recursive: true, mode: 0o700 });\n fs.writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });\n}\n\nexport function clearAuth(): void {\n try {\n fs.unlinkSync(AUTH_FILE);\n } catch {\n // ignore if not exists\n }\n}\n\nexport async function getValidToken(): Promise<string | null> {\n const auth = loadAuth();\n if (!auth) return null;\n\n const now = Math.floor(Date.now() / 1000);\n if (auth.expires_at - 60 > now) {\n return auth.access_token;\n }\n\n // Token expired, refresh via OAuth token endpoint\n const res = await fetch(OAUTH_TOKEN_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: auth.refresh_token,\n client_id: OAUTH_CLIENT_ID,\n }),\n });\n\n if (!res.ok) {\n clearAuth();\n return null;\n }\n\n const tokens = (await res.json()) as {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n };\n\n const newAuth: AuthData = {\n access_token: tokens.access_token,\n refresh_token: tokens.refresh_token,\n expires_at: Math.floor(Date.now() / 1000) + tokens.expires_in,\n };\n\n saveAuth(newAuth);\n return tokens.access_token;\n}\n\nexport async function requireAuth(): Promise<AuthData> {\n const token = await getValidToken();\n if (!token) {\n console.error(\"Not logged in. Run `clawnify login` first.\");\n process.exit(1);\n }\n return loadAuth()!;\n}\n\n/** Parse email from Supabase JWT claims (base64url-decoded payload) */\nexport function getEmailFromToken(token: string): string | null {\n try {\n const payload = token.split(\".\")[1]!;\n const decoded = JSON.parse(\n Buffer.from(payload, \"base64url\").toString(\"utf-8\")\n );\n return decoded.email ?? null;\n } catch {\n return null;\n }\n}\n\n/** Generate PKCE code_verifier and code_challenge */\nexport function generatePkce(): {\n codeVerifier: string;\n codeChallenge: string;\n} {\n const codeVerifier = crypto.randomBytes(32).toString(\"base64url\");\n const codeChallenge = crypto\n .createHash(\"sha256\")\n .update(codeVerifier)\n .digest(\"base64url\");\n return { codeVerifier, codeChallenge };\n}\n","import { requireAuth, loadAuth, saveAuth, type AuthData } from \"./auth.js\";\nimport readline from \"node:readline\";\n\nconst API_BASE = \"https://provision.clawnify.com\";\n\nasync function apiRequest(\n method: string,\n path: string,\n options?: {\n body?: unknown;\n formData?: FormData;\n token?: string;\n orgId?: string;\n }\n): Promise<Response> {\n const headers: Record<string, string> = {};\n\n if (options?.token) {\n headers[\"Authorization\"] = `Bearer ${options.token}`;\n }\n if (options?.orgId) {\n headers[\"x-org-id\"] = options.orgId;\n }\n\n let body: BodyInit | undefined;\n if (options?.formData) {\n body = options.formData;\n } else if (options?.body) {\n headers[\"Content-Type\"] = \"application/json\";\n body = JSON.stringify(options.body);\n }\n\n const res = await fetch(`${API_BASE}${path}`, { method, headers, body });\n\n if (res.status === 401) {\n console.error(\"Session expired. Run `clawnify login` again.\");\n process.exit(1);\n }\n\n return res;\n}\n\nasync function ensureOrg(auth: AuthData): Promise<AuthData> {\n if (auth.org_id) return auth;\n\n // Fetch orgs and prompt\n const orgsRes = await apiRequest(\"GET\", \"/v1/orgs\", { token: auth.access_token });\n if (!orgsRes.ok) return auth;\n\n const { orgs } = (await orgsRes.json()) as {\n orgs: Array<{ id: string; name: string; slug: string; role: string }>;\n };\n\n if (orgs.length === 0) return auth;\n\n let orgId: string;\n if (orgs.length === 1) {\n orgId = orgs[0].id;\n } else {\n console.log(\"\\nSelect an organization:\\n\");\n orgs.forEach((org, i) => {\n console.log(` ${i + 1}) ${org.name} (${org.role})`);\n });\n console.log();\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const choice = await new Promise<string>((resolve) => {\n rl.question(\"Enter number: \", (answer) => {\n rl.close();\n resolve(answer.trim());\n });\n });\n\n const index = parseInt(choice, 10) - 1;\n orgId = (index >= 0 && index < orgs.length) ? orgs[index].id : orgs[0].id;\n console.log(`Selected: ${orgs[index >= 0 && index < orgs.length ? index : 0].name}\\n`);\n }\n\n const updated = { ...auth, org_id: orgId };\n saveAuth(updated);\n return updated;\n}\n\nasync function authedRequest(\n method: string,\n path: string,\n options?: { body?: unknown; formData?: FormData }\n): Promise<Response> {\n let auth = await requireAuth();\n auth = await ensureOrg(auth);\n return apiRequest(method, path, {\n ...options,\n token: auth.access_token,\n orgId: auth.org_id,\n });\n}\n\nexport const api = {\n get: (path: string) => authedRequest(\"GET\", path),\n post: (path: string, body?: unknown) => authedRequest(\"POST\", path, { body }),\n postForm: (path: string, formData: FormData) =>\n authedRequest(\"POST\", path, { formData }),\n del: (path: string) => authedRequest(\"DELETE\", path),\n request: apiRequest,\n};\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { execSync } from \"node:child_process\";\nimport { gzipSync } from \"node:zlib\";\n\nconst EXCLUDE_DIRS = new Set([\n \"node_modules\",\n \".git\",\n \"dist\",\n \".next\",\n \".clawnify\",\n \"build\",\n]);\n\nconst MAX_SIZE = 20 * 1024 * 1024; // 20MB\n\nfunction getTrackedFiles(dir: string): string[] | null {\n try {\n const output = execSync(\"git ls-files -z\", {\n cwd: dir,\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n return output.split(\"\\0\").filter(Boolean);\n } catch {\n return null;\n }\n}\n\nfunction walkDir(dir: string, base: string = \"\"): string[] {\n const files: string[] = [];\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n if (EXCLUDE_DIRS.has(entry.name)) continue;\n const rel = base ? `${base}/${entry.name}` : entry.name;\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n files.push(...walkDir(full, rel));\n } else if (entry.isFile()) {\n files.push(rel);\n }\n }\n return files;\n}\n\nfunction createTarHeader(filename: string, size: number): Buffer {\n const header = Buffer.alloc(512);\n\n // filename (0-99)\n header.write(filename, 0, Math.min(filename.length, 100), \"utf-8\");\n\n // file mode (100-107)\n header.write(\"0000644\\0\", 100, 8, \"utf-8\");\n\n // owner uid (108-115)\n header.write(\"0000000\\0\", 108, 8, \"utf-8\");\n\n // group gid (116-123)\n header.write(\"0000000\\0\", 116, 8, \"utf-8\");\n\n // file size (124-135) - octal, 11 digits + null\n header.write(size.toString(8).padStart(11, \"0\") + \"\\0\", 124, 12, \"utf-8\");\n\n // mtime (136-147)\n const mtime = Math.floor(Date.now() / 1000);\n header.write(mtime.toString(8).padStart(11, \"0\") + \"\\0\", 136, 12, \"utf-8\");\n\n // type flag (156) - '0' for regular file\n header.write(\"0\", 156, 1, \"utf-8\");\n\n // magic (257-262)\n header.write(\"ustar\\0\", 257, 6, \"utf-8\");\n\n // version (263-264)\n header.write(\"00\", 263, 2, \"utf-8\");\n\n // checksum (148-155) - fill with spaces first, then compute\n header.write(\" \", 148, 8, \"utf-8\");\n let checksum = 0;\n for (let i = 0; i < 512; i++) {\n checksum += header[i]!;\n }\n header.write(checksum.toString(8).padStart(6, \"0\") + \"\\0 \", 148, 8, \"utf-8\");\n\n return header;\n}\n\nexport function createArchive(dir: string): Buffer {\n const files = getTrackedFiles(dir) ?? walkDir(dir);\n const parts: Buffer[] = [];\n\n for (const file of files) {\n const fullPath = path.join(dir, file);\n let stat: fs.Stats;\n try {\n stat = fs.statSync(fullPath);\n } catch {\n continue;\n }\n if (!stat.isFile()) continue;\n\n const content = fs.readFileSync(fullPath);\n const header = createTarHeader(file, content.length);\n parts.push(header);\n parts.push(content);\n\n // Pad to 512-byte boundary\n const remainder = content.length % 512;\n if (remainder > 0) {\n parts.push(Buffer.alloc(512 - remainder));\n }\n }\n\n // End with two 512-byte zero blocks\n parts.push(Buffer.alloc(1024));\n\n const tar = Buffer.concat(parts);\n const gzipped = gzipSync(tar);\n\n if (gzipped.length > MAX_SIZE) {\n throw new Error(\n `Archive too large (${(gzipped.length / 1024 / 1024).toFixed(1)}MB). Maximum is 20MB.`\n );\n }\n\n return gzipped;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport interface ClawnifyManifest {\n name: string;\n description: string;\n app: { framework: string; database?: boolean; storage?: boolean };\n}\n\nexport interface ProjectConfig {\n org_id?: string;\n app_id?: string;\n app_token?: string;\n slug?: string;\n}\n\nexport function readManifest(dir: string = process.cwd()): ClawnifyManifest | null {\n try {\n const data = fs.readFileSync(path.join(dir, \"clawnify.json\"), \"utf-8\");\n const manifest = JSON.parse(data) as ClawnifyManifest;\n if (!manifest.name || !manifest.app) return null;\n return manifest;\n } catch {\n return null;\n }\n}\n\nexport function readProject(dir: string = process.cwd()): ProjectConfig | null {\n try {\n const data = fs.readFileSync(\n path.join(dir, \".clawnify\", \"project.json\"),\n \"utf-8\"\n );\n return JSON.parse(data) as ProjectConfig;\n } catch {\n return null;\n }\n}\n\nexport function saveProject(dir: string, data: ProjectConfig): void {\n const projDir = path.join(dir, \".clawnify\");\n fs.mkdirSync(projDir, { recursive: true });\n fs.writeFileSync(\n path.join(projDir, \"project.json\"),\n JSON.stringify(data, null, 2)\n );\n}\n","import { api } from \"./api.js\";\n\nconst SPINNER_FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\n\nexport interface AppStatus {\n status: string;\n status_message?: string;\n url?: string;\n error?: string;\n}\n\nexport async function pollUntilReady(\n appId: string,\n interval = 3000,\n timeout = 300000\n): Promise<AppStatus> {\n const start = Date.now();\n let frame = 0;\n\n while (Date.now() - start < timeout) {\n const res = await api.get(`/v1/apps/${appId}`);\n const data = (await res.json()) as AppStatus;\n\n const spinner = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];\n const message = data.status_message || data.status;\n process.stdout.write(`\\r${spinner} ${message}`);\n frame++;\n\n if (data.status === \"live\" || data.status === \"error\") {\n process.stdout.write(\"\\r\\x1b[K\"); // clear line\n return data;\n }\n\n await new Promise((r) => setTimeout(r, interval));\n }\n\n process.stdout.write(\"\\r\\x1b[K\");\n throw new Error(\"Timed out waiting for deployment\");\n}\n"],"mappings":"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","names":["path","fs","path","fs","path"]}