onflyt-cli 1.0.1-beta.2 → 1.0.1-beta.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.

Potentially problematic release.


This version of onflyt-cli might be problematic. Click here for more details.

Files changed (67) hide show
  1. package/dist/App.d.ts +3 -0
  2. package/dist/App.js +8 -0
  3. package/dist/commands/credits.d.ts +3 -0
  4. package/dist/commands/credits.js +101 -0
  5. package/dist/commands/delete.d.ts +7 -0
  6. package/dist/commands/delete.js +220 -0
  7. package/dist/commands/deploy.d.ts +6 -0
  8. package/dist/commands/deploy.js +715 -0
  9. package/dist/commands/deployments.d.ts +6 -0
  10. package/dist/commands/deployments.js +225 -0
  11. package/dist/commands/help.d.ts +3 -0
  12. package/dist/commands/help.js +76 -0
  13. package/dist/commands/init.d.ts +11 -0
  14. package/dist/commands/init.js +422 -0
  15. package/dist/commands/login.d.ts +6 -0
  16. package/dist/commands/login.js +150 -0
  17. package/dist/commands/logout.d.ts +3 -0
  18. package/dist/commands/logout.js +19 -0
  19. package/dist/commands/logs.d.ts +7 -0
  20. package/dist/commands/logs.js +307 -0
  21. package/dist/commands/projects.d.ts +3 -0
  22. package/dist/commands/projects.js +203 -0
  23. package/dist/commands/rollback.d.ts +6 -0
  24. package/dist/commands/rollback.js +316 -0
  25. package/dist/commands/teams.d.ts +3 -0
  26. package/dist/commands/teams.js +81 -0
  27. package/dist/commands/whoami.d.ts +3 -0
  28. package/dist/commands/whoami.js +34 -0
  29. package/dist/components/Loading.d.ts +13 -0
  30. package/dist/components/Loading.js +42 -0
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.js +77 -116
  33. package/dist/lib/api.d.ts +27 -0
  34. package/dist/lib/api.js +109 -0
  35. package/dist/lib/config.d.ts +32 -0
  36. package/dist/lib/config.js +52 -0
  37. package/dist/lib/deploy-api.d.ts +97 -0
  38. package/dist/lib/deploy-api.js +335 -0
  39. package/dist/lib/deploy.d.ts +36 -0
  40. package/dist/lib/deploy.js +181 -0
  41. package/dist/lib/framework.d.ts +27 -0
  42. package/dist/lib/framework.js +184 -0
  43. package/dist/lib/git.d.ts +25 -0
  44. package/dist/lib/git.js +149 -0
  45. package/dist/lib/scaffold.d.ts +21 -0
  46. package/dist/lib/scaffold.js +190 -0
  47. package/dist/shared/frameworks/registry.d.ts +21 -0
  48. package/dist/shared/frameworks/registry.js +196 -0
  49. package/dist/shared/index.d.ts +4 -0
  50. package/dist/shared/index.js +4 -0
  51. package/dist/shared/limits.d.ts +16 -0
  52. package/dist/shared/limits.js +44 -0
  53. package/dist/shared/pricing.d.ts +2 -0
  54. package/dist/shared/pricing.js +7 -0
  55. package/dist/shared/templates/registry.d.ts +9 -0
  56. package/dist/shared/templates/registry.js +47 -0
  57. package/package.json +2 -3
  58. package/src/App.tsx +1 -1
  59. package/src/commands/deploy.tsx +1 -1
  60. package/src/commands/help.tsx +1 -1
  61. package/src/commands/init.tsx +1 -1
  62. package/src/commands/projects.tsx +1 -1
  63. package/src/components/Loading.tsx +1 -1
  64. package/src/index.tsx +1 -1
  65. package/src/lib/deploy-api.ts +3 -3
  66. package/src/lib/framework.ts +2 -2
  67. package/src/lib/shared.ts +350 -0
@@ -0,0 +1,422 @@
1
+ import React from "react";
2
+ import { Text, Box, useInput } from "ink";
3
+ import { existsSync, writeFileSync } from "fs";
4
+ import { join } from "path";
5
+ import { GitDetector } from "../lib/git.js";
6
+ import { FrameworkDetector } from "../lib/framework.js";
7
+ import { hasProjectConfig, saveProjectConfig, } from "../lib/config.js";
8
+ import { initGitRepo } from "../lib/scaffold.js";
9
+ import { FRAMEWORKS, TEMPLATES, getDefaultBuildCommand, getDefaultOutputDirectory, getDefaultStartCommand, getInstallCommand, } from "../shared";
10
+ import { Logo } from "../components/Loading.js";
11
+ const FRAMEWORK_LIST = Object.entries(FRAMEWORKS).map(([id, config]) => ({
12
+ id,
13
+ name: config.label,
14
+ }));
15
+ const PACKAGE_MANAGERS = [
16
+ { id: "npm", name: "npm" },
17
+ { id: "bun", name: "Bun" },
18
+ { id: "yarn", name: "Yarn" },
19
+ { id: "pnpm", name: "pnpm" },
20
+ { id: "pip", name: "pip" },
21
+ { id: "poetry", name: "Poetry" },
22
+ ];
23
+ const FOOTER = "↑↓ select/navigate • Enter confirm • Esc back • Ctrl+C cancel";
24
+ const Init = ({ name, framework, template, packageManager, git, yes, }) => {
25
+ const [step, setStep] = React.useState("name");
26
+ const [history, setHistory] = React.useState(["name"]);
27
+ const [projectName, setProjectName] = React.useState("");
28
+ const [isEditingName, setIsEditingName] = React.useState(false);
29
+ const [useTemplate, setUseTemplate] = React.useState(0);
30
+ const [selectedTemplate, setSelectedTemplate] = React.useState(0);
31
+ const [selectedFramework, setSelectedFramework] = React.useState(0);
32
+ const [selectedPackageManager, setSelectedPackageManager] = React.useState(0);
33
+ const [connectGit, setConnectGit] = React.useState(true);
34
+ const [hasGit, setHasGit] = React.useState(false);
35
+ const [detectedName, setDetectedName] = React.useState("");
36
+ const [detectedFrameworkId, setDetectedFrameworkId] = React.useState("");
37
+ const [gitUrl, setGitUrl] = React.useState(null);
38
+ const [gitBranch, setGitBranch] = React.useState("main");
39
+ const [remoteUrl, setRemoteUrl] = React.useState("");
40
+ const [errorMsg, setErrorMsg] = React.useState("");
41
+ const [savedConfig, setSavedConfig] = React.useState(null);
42
+ const goTo = (nextStep) => {
43
+ setHistory([...history, nextStep]);
44
+ setStep(nextStep);
45
+ };
46
+ const goBack = () => {
47
+ if (history.length > 1) {
48
+ const newHistory = history.slice(0, -1);
49
+ setHistory(newHistory);
50
+ setStep(newHistory[newHistory.length - 1]);
51
+ }
52
+ };
53
+ React.useEffect(() => {
54
+ detect();
55
+ }, []);
56
+ React.useEffect(() => {
57
+ if (step === "done" || step === "error") {
58
+ const timer = setTimeout(() => {
59
+ process.exit(step === "error" ? 1 : 0);
60
+ }, 100);
61
+ return () => clearTimeout(timer);
62
+ }
63
+ }, [step]);
64
+ const detect = async () => {
65
+ const cwd = process.cwd();
66
+ if (hasProjectConfig(cwd)) {
67
+ setErrorMsg("Already initialized. Run 'onflyt deploy' to deploy, or delete onflyt.json to reinitialize.");
68
+ setStep("error");
69
+ return;
70
+ }
71
+ const gitDetector = new GitDetector(cwd);
72
+ const gitInfo = await gitDetector.detect();
73
+ const detectedHasGit = gitInfo.isGitRepo && gitInfo.remotes.length > 0;
74
+ const frameworkDetector = new FrameworkDetector(cwd);
75
+ const detectedFw = frameworkDetector.detect();
76
+ const fwId = detectedFw?.id || "node";
77
+ let detected = cwd
78
+ .split("/")
79
+ .pop()
80
+ ?.replace(/[^a-z0-9-]/g, "-")
81
+ .toLowerCase() || "my-project";
82
+ if (detectedHasGit && gitInfo.remotes[0]) {
83
+ const match = gitInfo.remotes[0].url.match(/\/([^\/]+?)(?:\.git)?$/);
84
+ if (match) {
85
+ detected = match[1].replace(/[^a-z0-9-]/g, "-").toLowerCase();
86
+ }
87
+ }
88
+ const fwIndex = FRAMEWORK_LIST.findIndex((f) => f.id === (framework || fwId));
89
+ const pmIndex = PACKAGE_MANAGERS.findIndex((p) => p.id === packageManager);
90
+ setDetectedName(detected);
91
+ setDetectedFrameworkId(fwId);
92
+ setProjectName(name || detected);
93
+ setConnectGit(git !== undefined ? git : detectedHasGit && !!gitInfo.remotes[0]);
94
+ setSelectedFramework(fwIndex >= 0 ? fwIndex : 0);
95
+ setSelectedPackageManager(pmIndex >= 0 ? pmIndex : 0);
96
+ setHasGit(detectedHasGit);
97
+ if (gitInfo.remotes[0]) {
98
+ setGitUrl(gitInfo.remotes[0].url);
99
+ setGitBranch(gitInfo.currentBranch || "main");
100
+ }
101
+ if (yes) {
102
+ autoSave();
103
+ }
104
+ };
105
+ const getFinalProjectName = () => {
106
+ return projectName || detectedName;
107
+ };
108
+ const cancel = () => {
109
+ console.log("\nCancelled.");
110
+ process.exit(0);
111
+ };
112
+ const autoSave = async () => {
113
+ setStep("saving");
114
+ let fwId;
115
+ if (useTemplate === 1) {
116
+ fwId = TEMPLATES[selectedTemplate].framework;
117
+ }
118
+ else {
119
+ fwId = FRAMEWORK_LIST[selectedFramework].id;
120
+ }
121
+ const pmId = PACKAGE_MANAGERS[selectedPackageManager].id;
122
+ const cwd = process.cwd();
123
+ const frameworkDetector = new FrameworkDetector(cwd);
124
+ const detectedOutputDir = frameworkDetector.detectOutputDirectory(fwId);
125
+ const buildCmd = getDefaultBuildCommand(fwId) || "";
126
+ const installCmd = getInstallCommand(fwId, pmId);
127
+ const outputDir = detectedOutputDir || getDefaultOutputDirectory(fwId) || ".";
128
+ const startCmd = getDefaultStartCommand(fwId) || "";
129
+ if (connectGit && !hasGit) {
130
+ initGitRepo(cwd, remoteUrl || undefined);
131
+ createGitignore(cwd);
132
+ }
133
+ const projectConfig = {
134
+ name: getFinalProjectName(),
135
+ framework: fwId,
136
+ buildCommand: buildCmd,
137
+ outputDirectory: outputDir,
138
+ installCommand: installCmd,
139
+ startCommand: startCmd,
140
+ gitRepoUrl: connectGit ? remoteUrl || gitUrl || undefined : undefined,
141
+ gitBranch: connectGit ? "main" : undefined,
142
+ };
143
+ saveProjectConfig(projectConfig);
144
+ setSavedConfig(projectConfig);
145
+ setStep("done");
146
+ };
147
+ const createGitignore = (cwd) => {
148
+ const gitignorePath = join(cwd, ".gitignore");
149
+ if (!existsSync(gitignorePath)) {
150
+ const defaultGitignore = `node_modules/
151
+ dist/
152
+ build/
153
+ .next/
154
+ .output/
155
+ .env
156
+ .env.local
157
+ *.log
158
+ .DS_Store
159
+ `;
160
+ writeFileSync(gitignorePath, defaultGitignore);
161
+ }
162
+ };
163
+ useInput((input, key) => {
164
+ if (key.ctrl && input === "c") {
165
+ cancel();
166
+ return;
167
+ }
168
+ switch (step) {
169
+ case "name":
170
+ if (key.return) {
171
+ goTo("setupType");
172
+ }
173
+ else if (key.escape) {
174
+ setIsEditingName(false);
175
+ setProjectName(detectedName);
176
+ }
177
+ else if (key.backspace || key.delete) {
178
+ setProjectName(projectName.slice(0, -1));
179
+ }
180
+ else if (input && input.match(/^[a-zA-Z0-9-_]$/)) {
181
+ if (projectName === detectedName && !isEditingName) {
182
+ setProjectName(input);
183
+ }
184
+ else {
185
+ setProjectName(projectName + input);
186
+ }
187
+ setIsEditingName(true);
188
+ }
189
+ break;
190
+ case "setupType":
191
+ if (key.downArrow || input === "j") {
192
+ setUseTemplate(1);
193
+ }
194
+ else if (key.upArrow || input === "k") {
195
+ setUseTemplate(0);
196
+ }
197
+ else if (key.return) {
198
+ if (useTemplate === 0) {
199
+ goTo("framework");
200
+ }
201
+ else {
202
+ goTo("template");
203
+ }
204
+ }
205
+ else if (key.escape) {
206
+ goBack();
207
+ }
208
+ break;
209
+ case "template":
210
+ if (key.downArrow || input === "j") {
211
+ setSelectedTemplate((prev) => Math.min(prev + 1, TEMPLATES.length - 1));
212
+ }
213
+ else if (key.upArrow || input === "k") {
214
+ setSelectedTemplate((prev) => Math.max(prev - 1, 0));
215
+ }
216
+ else if (key.return) {
217
+ goTo("git");
218
+ }
219
+ else if (key.escape) {
220
+ goBack();
221
+ }
222
+ break;
223
+ case "framework":
224
+ if (key.downArrow || input === "j") {
225
+ setSelectedFramework((prev) => Math.min(prev + 1, FRAMEWORK_LIST.length - 1));
226
+ }
227
+ else if (key.upArrow || input === "k") {
228
+ setSelectedFramework((prev) => Math.max(prev - 1, 0));
229
+ }
230
+ else if (key.return) {
231
+ goTo("packageManager");
232
+ }
233
+ else if (key.escape) {
234
+ goBack();
235
+ }
236
+ break;
237
+ case "packageManager":
238
+ if (key.downArrow || input === "j") {
239
+ setSelectedPackageManager((prev) => Math.min(prev + 1, PACKAGE_MANAGERS.length - 1));
240
+ }
241
+ else if (key.upArrow || input === "k") {
242
+ setSelectedPackageManager((prev) => Math.max(prev - 1, 0));
243
+ }
244
+ else if (key.return) {
245
+ goTo("git");
246
+ }
247
+ else if (key.escape) {
248
+ goBack();
249
+ }
250
+ break;
251
+ case "git":
252
+ if (key.downArrow || input === "j" || input === " ") {
253
+ setConnectGit((prev) => !prev);
254
+ }
255
+ else if (key.upArrow || input === "k") {
256
+ setConnectGit((prev) => !prev);
257
+ }
258
+ else if (key.return) {
259
+ if (connectGit) {
260
+ if (hasGit) {
261
+ autoSave();
262
+ }
263
+ else {
264
+ goTo("gitRemote");
265
+ }
266
+ }
267
+ else {
268
+ autoSave();
269
+ }
270
+ }
271
+ else if (key.escape) {
272
+ goBack();
273
+ }
274
+ break;
275
+ case "gitRemote":
276
+ if (key.return) {
277
+ autoSave();
278
+ }
279
+ else if (key.escape) {
280
+ goBack();
281
+ }
282
+ else if (key.backspace || key.delete) {
283
+ setRemoteUrl(remoteUrl.slice(0, -1));
284
+ }
285
+ else if (input && input.length > 0) {
286
+ setRemoteUrl(remoteUrl + input);
287
+ }
288
+ break;
289
+ }
290
+ });
291
+ if (step === "error") {
292
+ return (React.createElement(Box, { flexDirection: "column", padding: 1 },
293
+ React.createElement(Text, { bold: true, color: "red" }, "Error"),
294
+ React.createElement(Box, { marginTop: 1 },
295
+ React.createElement(Text, { color: "red" }, errorMsg))));
296
+ }
297
+ if (step === "saving") {
298
+ return (React.createElement(Box, { flexDirection: "column", padding: 1 },
299
+ React.createElement(Logo, null),
300
+ React.createElement(Box, { marginTop: 1 },
301
+ React.createElement(Text, null, "Saving configuration..."))));
302
+ }
303
+ if (step === "done") {
304
+ return (React.createElement(Box, { flexDirection: "column", padding: 1 },
305
+ React.createElement(Text, { bold: true, color: "green" }, "\u2713 Project initialized!"),
306
+ React.createElement(Box, { marginTop: 1 },
307
+ React.createElement(Text, null, "onflyt.json saved to current directory")),
308
+ savedConfig && (React.createElement(React.Fragment, null,
309
+ React.createElement(Box, { marginTop: 1 },
310
+ React.createElement(Text, { dimColor: true },
311
+ "Project: ",
312
+ savedConfig.name)),
313
+ React.createElement(Box, null,
314
+ React.createElement(Text, { dimColor: true },
315
+ "Framework: ",
316
+ FRAMEWORKS[savedConfig.framework]?.label)),
317
+ savedConfig.gitRepoUrl && (React.createElement(Box, null,
318
+ React.createElement(Text, { dimColor: true },
319
+ "Git: ",
320
+ savedConfig.gitRepoUrl))))),
321
+ React.createElement(Box, { marginTop: 2 },
322
+ React.createElement(Text, { bold: true }, "Next steps:")),
323
+ React.createElement(Box, { marginTop: 1 },
324
+ React.createElement(Text, { dimColor: true }, "1. Run 'onflyt deploy' to deploy")),
325
+ React.createElement(Box, null,
326
+ React.createElement(Text, { dimColor: true }, "2. Team selection will happen during deploy"))));
327
+ }
328
+ return (React.createElement(Box, { flexDirection: "column", padding: 1 },
329
+ React.createElement(Logo, null),
330
+ step === "name" && (React.createElement(React.Fragment, null,
331
+ React.createElement(Box, { marginTop: 1 },
332
+ React.createElement(Text, null, "Project Name")),
333
+ React.createElement(Box, { marginTop: 1 },
334
+ React.createElement(Text, { dimColor: true }, "Press keys to edit, Enter to continue")),
335
+ React.createElement(Box, { marginTop: 1 },
336
+ React.createElement(Text, { bold: true, color: "cyan" },
337
+ ">",
338
+ " "),
339
+ React.createElement(Text, null, projectName || detectedName),
340
+ React.createElement(Text, { bold: true, color: "cyan" }, "_")))),
341
+ step === "setupType" && (React.createElement(React.Fragment, null,
342
+ React.createElement(Box, { marginTop: 1 },
343
+ React.createElement(Text, null, "Setup Type")),
344
+ React.createElement(Box, { marginTop: 1, flexDirection: "column" },
345
+ React.createElement(Box, null,
346
+ React.createElement(Text, null,
347
+ useTemplate === 0 ? "❯ " : " ",
348
+ "Use existing files")),
349
+ React.createElement(Box, null,
350
+ React.createElement(Text, null,
351
+ useTemplate === 1 ? "❯ " : " ",
352
+ "Use a template (scaffold from starter)"))),
353
+ React.createElement(Box, { marginTop: 1 },
354
+ React.createElement(Text, { dimColor: true }, FOOTER)))),
355
+ step === "template" && (React.createElement(React.Fragment, null,
356
+ React.createElement(Box, { marginTop: 1 },
357
+ React.createElement(Text, null, "Select Template")),
358
+ React.createElement(Box, { marginTop: 1, flexDirection: "column" }, TEMPLATES.map((t, i) => (React.createElement(Box, { key: t.id },
359
+ React.createElement(Text, null,
360
+ i === selectedTemplate ? "❯ " : " ",
361
+ t.name,
362
+ " - ",
363
+ t.description))))),
364
+ React.createElement(Box, { marginTop: 1 },
365
+ React.createElement(Text, { dimColor: true }, FOOTER)))),
366
+ step === "framework" && (React.createElement(React.Fragment, null,
367
+ React.createElement(Box, { marginTop: 1 },
368
+ React.createElement(Text, null, "Select Framework")),
369
+ React.createElement(Box, { marginTop: 1 },
370
+ React.createElement(Text, { dimColor: true },
371
+ "Detected: ",
372
+ detectedFrameworkId)),
373
+ React.createElement(Box, { marginTop: 1, flexDirection: "column" }, FRAMEWORK_LIST.map((fw, i) => (React.createElement(Box, { key: fw.id },
374
+ React.createElement(Text, null,
375
+ i === selectedFramework ? "❯ " : " ",
376
+ fw.name))))),
377
+ React.createElement(Box, { marginTop: 1 },
378
+ React.createElement(Text, { dimColor: true }, FOOTER)))),
379
+ step === "packageManager" && (React.createElement(React.Fragment, null,
380
+ React.createElement(Box, { marginTop: 1 },
381
+ React.createElement(Text, null, "Select Package Manager")),
382
+ React.createElement(Box, { marginTop: 1, flexDirection: "column" }, PACKAGE_MANAGERS.map((pm, i) => (React.createElement(Box, { key: pm.id },
383
+ React.createElement(Text, null,
384
+ i === selectedPackageManager ? "❯ " : " ",
385
+ pm.name))))),
386
+ React.createElement(Box, { marginTop: 1 },
387
+ React.createElement(Text, { dimColor: true }, FOOTER)))),
388
+ step === "git" && (React.createElement(React.Fragment, null,
389
+ React.createElement(Box, { marginTop: 1 },
390
+ React.createElement(Text, null, "Git Repository")),
391
+ gitUrl && (React.createElement(Box, { marginTop: 1 },
392
+ React.createElement(Text, { dimColor: true },
393
+ "Detected: ",
394
+ gitUrl,
395
+ " (",
396
+ gitBranch,
397
+ ")"))),
398
+ React.createElement(Box, { marginTop: 1, flexDirection: "column" },
399
+ React.createElement(Box, null,
400
+ React.createElement(Text, null,
401
+ connectGit ? "❯ " : " ",
402
+ hasGit ? "Yes, connect git repo" : "Initialize git repo")),
403
+ React.createElement(Box, null,
404
+ React.createElement(Text, null,
405
+ !connectGit ? "❯ " : " ",
406
+ "Skip git (manual deploys)"))),
407
+ React.createElement(Box, { marginTop: 1 },
408
+ React.createElement(Text, { dimColor: true }, FOOTER)))),
409
+ step === "gitRemote" && (React.createElement(React.Fragment, null,
410
+ React.createElement(Box, { marginTop: 1 },
411
+ React.createElement(Text, null, "Git Remote URL (optional)")),
412
+ React.createElement(Box, { marginTop: 1 },
413
+ React.createElement(Text, { dimColor: true }, "Press Enter to skip, or enter GitHub repo URL")),
414
+ React.createElement(Box, { marginTop: 1 },
415
+ React.createElement(Text, null,
416
+ "> ",
417
+ remoteUrl),
418
+ React.createElement(Text, { bold: true }, "_")),
419
+ React.createElement(Box, { marginTop: 1 },
420
+ React.createElement(Text, { dimColor: true }, "Enter paste URL \u2022 Backspace delete \u2022 Enter continue"))))));
421
+ };
422
+ export default Init;
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ interface LoginProps {
3
+ openBrowser?: boolean;
4
+ }
5
+ declare const Login: React.FC<LoginProps>;
6
+ export default Login;
@@ -0,0 +1,150 @@
1
+ import React from "react";
2
+ import { Text, Box } from "ink";
3
+ import Spinner from "ink-spinner";
4
+ import open from "open";
5
+ import { API_URL, getConfig, saveConfig } from "../lib/config.js";
6
+ import { Logo } from "../components/Loading.js";
7
+ const LoadingSpinner = () => (React.createElement(Box, null,
8
+ React.createElement(Spinner, { type: "dots" }),
9
+ React.createElement(Text, null, " Starting login...")));
10
+ const AlreadyLoggedIn = ({ user, }) => (React.createElement(Box, { flexDirection: "column", padding: 1 },
11
+ React.createElement(Text, { bold: true, color: "green" },
12
+ "\u2713 Welcome back, ",
13
+ user.name,
14
+ "!"),
15
+ React.createElement(Box, { marginTop: 1 },
16
+ React.createElement(Text, { dimColor: true },
17
+ "Email: ",
18
+ user.email)),
19
+ React.createElement(Box, { marginTop: 1 },
20
+ React.createElement(Text, { dimColor: true }, "Run \"onflyt logout\" to sign out"))));
21
+ const LoginSuccess = ({ user }) => (React.createElement(Box, { flexDirection: "column", padding: 1 },
22
+ React.createElement(Text, { bold: true, color: "green" }, "\u2713 Logged in successfully!"),
23
+ React.createElement(Box, { marginTop: 1 },
24
+ React.createElement(Text, null,
25
+ "Welcome, ",
26
+ user.name,
27
+ "!"))));
28
+ const LoginError = ({ error }) => (React.createElement(Box, { flexDirection: "column", padding: 1 },
29
+ React.createElement(Text, { bold: true, color: "red" }, "\u2716 Login failed"),
30
+ React.createElement(Text, { color: "red" }, error)));
31
+ const Login = ({ openBrowser = true }) => {
32
+ const [deviceData, setDeviceData] = React.useState(null);
33
+ const [error, setError] = React.useState(null);
34
+ const [successUser, setSuccessUser] = React.useState(null);
35
+ const [checkedLogin, setCheckedLogin] = React.useState(false);
36
+ const [wasAlreadyLoggedIn, setWasAlreadyLoggedIn] = React.useState(false);
37
+ const browserOpened = React.useRef(false);
38
+ // Check if already logged in
39
+ React.useEffect(() => {
40
+ const config = getConfig();
41
+ if (config.token && config.user) {
42
+ setSuccessUser(config.user);
43
+ setWasAlreadyLoggedIn(true);
44
+ }
45
+ setCheckedLogin(true);
46
+ }, []);
47
+ React.useEffect(() => {
48
+ if (browserOpened.current || !checkedLogin || successUser)
49
+ return;
50
+ browserOpened.current = true;
51
+ const startAuth = async () => {
52
+ try {
53
+ // Step 1: Get device code from our API
54
+ const deviceRes = await fetch(`${API_URL}/auth/device/code`, {
55
+ method: "POST",
56
+ headers: { "Content-Type": "application/json" },
57
+ });
58
+ const deviceData = await deviceRes.json();
59
+ if (!deviceRes.ok || !deviceData.success) {
60
+ setError(deviceData.error || "Failed to get device code");
61
+ return;
62
+ }
63
+ setDeviceData({
64
+ user_code: deviceData.user_code,
65
+ verification_uri: deviceData.verification_uri,
66
+ device_code: deviceData.device_code,
67
+ interval: deviceData.interval || 5,
68
+ });
69
+ // Open browser if enabled
70
+ if (openBrowser) {
71
+ try {
72
+ await open(deviceData.verification_uri, { wait: true });
73
+ }
74
+ catch {
75
+ // Browser might not open in headless environment
76
+ }
77
+ }
78
+ // Step 2: Poll for token
79
+ let currentInterval = (deviceData.interval || 5) * 1000;
80
+ let remainingAttempts = 150;
81
+ while (remainingAttempts > 0) {
82
+ await new Promise((resolve) => setTimeout(resolve, currentInterval));
83
+ remainingAttempts--;
84
+ const tokenRes = await fetch(`${API_URL}/auth/device/token`, {
85
+ method: "POST",
86
+ headers: { "Content-Type": "application/json" },
87
+ body: JSON.stringify({ device_code: deviceData.device_code }),
88
+ });
89
+ const tokenData = await tokenRes.json();
90
+ if (tokenData.success &&
91
+ tokenData.status === "authorized" &&
92
+ tokenData.token) {
93
+ saveConfig({
94
+ ...getConfig(),
95
+ token: tokenData.token,
96
+ user: tokenData.user,
97
+ lastLogin: new Date().toISOString(),
98
+ });
99
+ setSuccessUser(tokenData.user);
100
+ return;
101
+ }
102
+ if (!tokenData.success && tokenData.error) {
103
+ if (tokenData.error === "authorization_pending") {
104
+ continue;
105
+ }
106
+ else if (tokenData.error === "slow_down") {
107
+ currentInterval += 5000;
108
+ continue;
109
+ }
110
+ else {
111
+ setError(tokenData.error);
112
+ return;
113
+ }
114
+ }
115
+ }
116
+ setError("Authorization timeout");
117
+ }
118
+ catch (err) {
119
+ setError(err.message || "Failed to start login");
120
+ }
121
+ };
122
+ startAuth();
123
+ }, [checkedLogin, successUser]);
124
+ if (!checkedLogin)
125
+ return React.createElement(LoadingSpinner, null);
126
+ if (successUser && wasAlreadyLoggedIn)
127
+ return React.createElement(AlreadyLoggedIn, { user: successUser });
128
+ if (successUser)
129
+ return React.createElement(LoginSuccess, { user: successUser });
130
+ if (error)
131
+ return React.createElement(LoginError, { error: error });
132
+ if (!deviceData)
133
+ return React.createElement(LoadingSpinner, null);
134
+ return (React.createElement(Box, { flexDirection: "column", padding: 1 },
135
+ React.createElement(Logo, null),
136
+ React.createElement(Box, { marginTop: 1 },
137
+ React.createElement(Text, { bold: true }, "Login to Onflyt")),
138
+ React.createElement(Box, { marginTop: 1 },
139
+ React.createElement(Text, null, "Visit: "),
140
+ React.createElement(Text, { color: "cyan", underline: true }, deviceData.verification_uri)),
141
+ React.createElement(Box, { marginTop: 1 },
142
+ React.createElement(Text, null, "Enter code: "),
143
+ React.createElement(Text, { bold: true, color: "green" }, deviceData.user_code)),
144
+ React.createElement(Box, { marginTop: 1 },
145
+ React.createElement(Spinner, { type: "dots" }),
146
+ React.createElement(Text, null, " Waiting for authorization...")),
147
+ React.createElement(Box, { marginTop: 1 },
148
+ React.createElement(Text, { dimColor: true }, "Press Ctrl+C to cancel"))));
149
+ };
150
+ export default Login;
@@ -0,0 +1,3 @@
1
+ import React from "react";
2
+ declare const Logout: React.FC;
3
+ export default Logout;
@@ -0,0 +1,19 @@
1
+ import React from "react";
2
+ import { Text, Box } from "ink";
3
+ import { getConfig, saveConfig } from "../lib/config.js";
4
+ import { Logo, ErrorDisplay } from "../components/Loading.js";
5
+ const Logout = () => {
6
+ const config = getConfig();
7
+ if (!config.token) {
8
+ return React.createElement(ErrorDisplay, { message: "Not logged in. Nothing to logout from." });
9
+ }
10
+ // Clear config
11
+ saveConfig({});
12
+ return (React.createElement(Box, { flexDirection: "column", padding: 1 },
13
+ React.createElement(Logo, null),
14
+ React.createElement(Box, { marginTop: 1 },
15
+ React.createElement(Text, { bold: true, color: "green" }, "\u2713 Logged out successfully")),
16
+ React.createElement(Box, { marginTop: 1 },
17
+ React.createElement(Text, { dimColor: true }, "Token cleared from ~/.onflyt/config.json"))));
18
+ };
19
+ export default Logout;
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ interface LogsProps {
3
+ deploymentId?: string;
4
+ live?: boolean;
5
+ }
6
+ declare const Logs: React.FC<LogsProps>;
7
+ export default Logs;